From 3197a4e887ddd8e7e3a223aa671b044c7f780804 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Sat, 8 Aug 2026 21:10:06 +0200 Subject: [PATCH] =?UTF-8?q?fix(windows/cursor):=20name=20the=20once-per-bo?= =?UTF-8?q?ot=20limit=20=E2=80=94=20/restart-device=20cannot=20clean=20bet?= =?UTF-8?q?ween=20sessions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Measured on a freshly cold-booted .173: the start-up clean succeeded (0.07 s, adapter clean, first session came up cursor_excluded=false and then declared), and the between-session clean then fired correctly — gate passed, CURSOR_DECLARED set, no session streaming — and the restart FAILED with 'Das System muss neu gestartet werden, damit Konfigurationsvorgaenge abgeschlossen werden'. So /restart-device is a ONCE-PER-BOOT lever. The first call after a cold boot works; every later one in the same boot needs a reboot first, and repeated attempts additionally drive the devnode into 'restart pending'. The earlier '0.07 s, so just do it whenever' measurement was true only of the first call. That settles what this mechanism can and cannot do: it cleans the adapter at host start-up, and nowhere else. Giving a capture session back the lossless pointer AFTER a desktop session in the same boot needs a different way to recycle the driver's WUDFHost process, which is where the declare lives. Also fixes the detector, which missed this wording entirely and logged restart_pending=false on exactly the failure it exists to name. --- .../src/vdisplay/windows/pf_vdisplay.rs | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/crates/pf-vdisplay/src/vdisplay/windows/pf_vdisplay.rs b/crates/pf-vdisplay/src/vdisplay/windows/pf_vdisplay.rs index 1a668c19..ca814113 100644 --- a/crates/pf-vdisplay/src/vdisplay/windows/pf_vdisplay.rs +++ b/crates/pf-vdisplay/src/vdisplay/windows/pf_vdisplay.rs @@ -176,7 +176,16 @@ enum AdapterCycle { /// 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 +/// ⚠⚠ **This is a ONCE-PER-BOOT lever, not a cheap one.** Measured on `.173` 2026-08-08: the first +/// `/restart-device` after a cold boot succeeds in 0.07 s; every later one in the same boot fails +/// with *"Das System muss neu gestartet werden, damit Konfigurationsvorgänge abgeschlossen +/// werden"*, and repeated attempts additionally push the devnode into `restart pending`. So this +/// can clean the adapter at host start-up and nowhere else — anything wanting to un-declare +/// mid-boot (e.g. giving a capture session back the lossless pointer after a desktop session) needs +/// a different mechanism to recycle the driver's WUDFHost process, which is where the declare +/// actually lives. +/// +/// ⚠ It 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 @@ -296,8 +305,15 @@ pub fn restart_device_for_clean_cursor() -> bool { other => { tracing::warn!( outcome = other, - restart_pending = other.contains("Systemneustart") - || other.to_ascii_lowercase().contains("restart is pending"), + // Two distinct wordings, both meaning "not until you reboot": + // "Für das Gerät steht ein Systemneustart aus" (device restart pending) + // "Das System muss neu gestartet werden, damit …" (config ops need a reboot) + // The second is what you actually hit, and it appears after the FIRST successful + // restart of a boot — see the doc on `restart_device_for_clean_cursor`. + needs_reboot = other.contains("Systemneustart") + || other.contains("muss neu gestartet werden") + || other.to_ascii_lowercase().contains("restart is pending") + || other.to_ascii_lowercase().contains("must be restarted"), "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" );