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:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user