feat(windows/cursor): clear a sticky hardware-cursor declare at start-up — capture sessions get the OS's own pointer back

The goal this serves: a session that never engages desktop mode should get the
LOSSLESS cursor — composited by Windows itself, for free, with true XOR — and
the host should only pay for compositing when the user actually asks for the
desktop mouse model.

That is already what happens on a clean adapter. The problem is that adapters
do not stay clean. A hardware-cursor declare is irrevocable and ADAPTER-WIDE
(pf-driver-proto v6): once any desktop-mode session declares, DWM stops
compositing the pointer into every later frame on that adapter, so every
capture-latched session afterwards has to self-composite — a full-frame copy
per visible-pointer frame, and our straight-alpha approximation of an XOR
cursor instead of the real thing — for the rest of the adapter's life.

"Until the next reboot" turned out to be far longer than it sounds. With Fast
Startup on (the Windows default) a shutdown plus power-on is a HIBERBOOT: it
restores session 0 and its drivers, so the declare survives what the operator
calls a reboot. Measured on .173 — Kernel-Boot event id 27 reporting `0x1`
where a cold boot reports `0x0`, with lsass/services/wininit keeping their
pre-"reboot" start times, while `LastBootUpTime` reports the older cold boot
and makes uptime checks lie. On such a box the lossless path can be gone for
weeks.

So clear it explicitly at host start, where no session holds a display yet.
`pnputil /restart-device` recycles the WUDFHost process the driver's
`DECLARED_TARGETS` lives in, which is all it takes. Measured at **0.07 s**
against ~6 s of sleeps for the existing Disable+Enable cycle, and unlike that
cycle it is designed for a device in use, so it does not hit the refusal
`reload_vdisplay_adapter` documents as "the expected case here". In the same
call it also repaired an adapter found in CM_PROB_FAILED_POST_START (Code 43).

Best-effort throughout: a failure just leaves the adapter as it was and
sessions self-composite exactly as before. `PUNKTFUNK_CURSOR_CLEAN_START=0`
opts out.

Verified: `scripts/xcheck.sh windows` green; `cargo fmt --all` clean; full
`cargo build -p punktfunk-host --release --features nvenc` on .173 (xcheck
cannot reach punktfunk-host — it needs ffmpeg).
This commit is contained in:
2026-08-08 18:16:06 +02:00
parent 62624c1daf
commit 5819cf054b
2 changed files with 97 additions and 0 deletions
@@ -158,6 +158,86 @@ enum AdapterCycle {
Refused(String),
}
/// Restart the pf-vdisplay device to CLEAR a sticky IddCx hardware-cursor declare, so sessions that
/// do not want the host to own the pointer get the OS's own cursor compositing back (full fidelity,
/// zero host cost — no GDI poller, no per-frame blend, true XOR instead of our outline
/// approximation).
///
/// **Why this exists.** A hardware-cursor declare is irrevocable and ADAPTER-WIDE
/// (`pf-driver-proto` v6 note): once any desktop-mode session declares, DWM stops compositing the
/// pointer into EVERY later frame on that adapter, and every subsequent session — including
/// capture-latched ones that never asked for a cursor channel — has to self-composite. The state
/// lives in the driver's `DECLARED_TARGETS`, whose scope is the WUDFHost process, so recycling that
/// process clears it.
///
/// **Why `/restart-device` and not the [`reload_vdisplay_adapter`] cycle.** Measured on-glass
/// 2026-08-08 (`.173`): `pnputil /restart-device` returned in **0.07 s** with a NEW WUDFHost pid,
/// against ~6 s of sleeps for `Disable`+`Enable` — and, being designed for a device that is in use,
/// it does not hit the refusal that doc calls "the expected case here". It also repaired an adapter
/// found in `CM_PROB_FAILED_POST_START` (Code 43) in the same call.
///
/// ⚠ This tears the adapter down, so it must run only when NO session holds a display — the host
/// start-up path. `PUNKTFUNK_CURSOR_CLEAN_START=0` disables it.
///
/// Returns `true` only when pnputil reported success. Best-effort: a failure just leaves the
/// adapter as it was (sessions then self-composite exactly as before).
pub fn restart_device_for_clean_cursor() -> bool {
if std::env::var("PUNKTFUNK_CURSOR_CLEAN_START").is_ok_and(|v| v == "0") {
tracing::info!(
"pf-vdisplay: cursor clean-start disabled (PUNKTFUNK_CURSOR_CLEAN_START=0) — a sticky \
hardware-cursor declare from an earlier boot will keep sessions self-compositing"
);
return false;
}
// `$LASTEXITCODE` is pre-seeded to 1 for the same reason `reload_vdisplay_adapter` does it: if
// pnputil never launches, a stale value must not read as success.
const PS: &str = "$ErrorActionPreference='SilentlyContinue'; \
$ad = Get-PnpDevice -Class Display | Where-Object { $_.FriendlyName -match 'punktfunk Virtual Display' } | Select-Object -First 1; \
if (-not $ad) { Write-Output 'ABSENT'; exit }; \
$pnp = ($env:SystemRoot + '\\System32\\pnputil.exe'); $LASTEXITCODE = 1; \
if (Test-Path $pnp) { & $pnp /restart-device $ad.InstanceId *> $null }; \
if ($LASTEXITCODE -eq 0) { Write-Output 'RESTARTED' } else { Write-Output 'FAILED' }";
let ps = std::env::var("SystemRoot")
.map(|r| format!(r"{r}\System32\WindowsPowerShell\v1.0\powershell.exe"))
.unwrap_or_else(|_| "powershell.exe".to_string());
let out = match std::process::Command::new(&ps)
.args([
"-NoProfile",
"-NonInteractive",
"-ExecutionPolicy",
"Bypass",
"-Command",
PS,
])
.output()
{
Ok(o) => String::from_utf8_lossy(&o.stdout).trim().to_string(),
Err(e) => {
tracing::warn!(error = %e, "pf-vdisplay: cursor clean-start could not spawn powershell");
return false;
}
};
match out.as_str() {
"RESTARTED" => {
tracing::info!(
"pf-vdisplay: restarted the adapter at start-up — any sticky hardware-cursor \
declare is cleared, so sessions without a cursor channel get the OS's own \
(full-fidelity, zero-cost) pointer compositing until one declares again"
);
true
}
"ABSENT" => false, // driver not installed — nothing to clean, and `open` reports that later
other => {
tracing::warn!(
outcome = other,
"pf-vdisplay: cursor clean-start did not restart the adapter — sessions without a \
cursor channel will self-composite the pointer if an earlier declare is sticky"
);
false
}
}
}
/// Reload the pf-vdisplay ADAPTER device — the in-process equivalent of `reset-pf-vdisplay.ps1`
/// step 3. A crashed/killed WUDFHost can leave the devnode "started" yet HOSTLESS (PnP Status OK, no
/// WUDFHost process, zero device-interface instances) — a zombie no session can open until the stack
+17
View File
@@ -382,6 +382,23 @@ fn real_main() -> Result<()> {
// driver to a stray second host started while the service sat idle.
#[cfg(target_os = "windows")]
vdisplay::manager::claim_instance_eagerly();
// Clean-cursor start (design/windows-cursor-model-determinism.md §4.3): clear any
// sticky IddCx hardware-cursor declare left on the adapter by an EARLIER boot's
// desktop-mode session. That declare is irrevocable and adapter-wide, so without this
// every capture-latched session on the box self-composites the pointer for the rest of
// the adapter's life — paying a full-frame copy per visible-pointer frame and drawing
// our straight-alpha approximation of an XOR cursor — when the OS would otherwise
// composite it natively, for free, at full fidelity.
//
// It is NOT enough to wait for a reboot: with Fast Startup on (the Windows default) a
// shutdown+power-on is a hiberboot that RESTORES session 0 and its drivers, so the
// declare survives what the operator calls a reboot (measured: Kernel-Boot event id 27
// `0x1`, and `lsass`/`services` keeping their pre-"reboot" start times). Only a cold
// boot or a device restart actually clears it — and the device restart costs 0.07 s.
//
// Runs HERE, before any session holds a display: the restart tears the adapter down.
#[cfg(target_os = "windows")]
vdisplay::driver::restart_device_for_clean_cursor();
// Crash recovery for the experimental `pnp_disable_monitors` axis: re-enable any
// monitor devnodes a previous host disabled for an Exclusive session and never
// restored (crash/kill/power loss) — before any new session touches the topology.