From 4edfcd4b43f4aeafb6a7c05d425708d5530309cc Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Tue, 16 Jun 2026 08:13:16 +0000 Subject: [PATCH] feat(host/windows): two-process mux test toggle + live-validate step 5 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PUNKTFUNK_SECURE_TEST_PERIOD_MS=N drives a square-wave secure/normal toggle in virtual_stream_relay (instead of the real DesktopWatcher), to exercise the mid-session helper↔DDA mux without a live UAC/lock. Gated behind the env var, in the style of PUNKTFUNK_VIDEO_DROP / PUNKTFUNK_FEC_PCT. Live-validated on the RTX 4090 (host as SYSTEM): with a 4s toggle the mux switched secure(DDA)↔normal(WGC relay) cleanly 5× in one session and the client decoded 308 HEVC Main-10 frames continuously across every switch — the wait-for-IDR latch held with no decode break. The real Winlogon DDA capture is pre-proven by the single-process secure path (f4b4a6c); the toggle exercises the new surface (the mux). Doc updated with the validation + the SYSTEM-mode audio caveat. Co-Authored-By: Claude Opus 4.8 (1M context) --- crates/punktfunk-host/src/m3.rs | 13 ++++++++++++- docs/windows-secure-desktop.md | 23 +++++++++++++++++------ 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/crates/punktfunk-host/src/m3.rs b/crates/punktfunk-host/src/m3.rs index e072d77..22106ff 100644 --- a/crates/punktfunk-host/src/m3.rs +++ b/crates/punktfunk-host/src/m3.rs @@ -2345,6 +2345,14 @@ fn virtual_stream_relay( // The authoritative Default↔Winlogon signal (requires SYSTEM to read the Winlogon desktop name). let watcher = crate::capture::desktop_watch::DesktopWatcher::start(); + // Test hook: PUNKTFUNK_SECURE_TEST_PERIOD_MS=N drives a square-wave secure/normal toggle every N ms + // instead of the real watcher — exercises the mid-session helper↔DDA mux without a live UAC/lock + // (the real Winlogon DDA capture is already proven by the single-process secure path). + let secure_test_ms: Option = std::env::var("PUNKTFUNK_SECURE_TEST_PERIOD_MS") + .ok() + .and_then(|s| s.parse().ok()) + .filter(|&n| n > 0); + let start = std::time::Instant::now(); let mut interval = std::time::Duration::from_secs_f64(1.0 / effective_hz.max(1) as f64); let deadline = std::time::Instant::now() + std::time::Duration::from_secs(seconds as u64); @@ -2420,7 +2428,10 @@ fn virtual_stream_relay( // Source mux: capture the secure (Winlogon) desktop via the host's DDA, the normal desktop via // the helper relay. On a switch, latch await_idr + force the now-active source to emit an IDR // so the client resumes cleanly. - let secure = watcher.is_secure(); + let secure = match secure_test_ms { + Some(p) => (start.elapsed().as_millis() / p) % 2 == 1, + None => watcher.is_secure(), + }; if secure != on_secure { on_secure = secure; await_idr = true; diff --git a/docs/windows-secure-desktop.md b/docs/windows-secure-desktop.md index a7764bc..674c7f7 100644 --- a/docs/windows-secure-desktop.md +++ b/docs/windows-secure-desktop.md @@ -16,15 +16,26 @@ Implemented so far: helper relay on `Default`, the host's own DDA capturer+encoder on `Winlogon`; every switch latches "wait for IDR" + forces the now-active source to emit a keyframe. +**Live-validated on the RTX 4090 (2026-06-16, host as SYSTEM):** +- Step 4: the helper spawns via `CreateProcessAsUserW`, runs WGC with no hang (HDR FP16 BT.2020 PQ), + opens NVENC (D3D11 Main10), and relays AUs — `client-rs` over the LAN decoded 411 HEVC Main-10 + frames. (Bug found+fixed: `CreateProcessAsUserW` gave the helper the *user's* env, dropping + `PUNKTFUNK_ENCODER=nvenc` → software-encoder fallback; fixed by `merged_env_block`.) +- Step 5: with `PUNKTFUNK_SECURE_TEST_PERIOD_MS=4000` driving a square-wave toggle, the source mux + switched `secure(DDA)`↔`normal(WGC relay)` cleanly 5× in one session; the client decoded 308 frames + continuously across every switch (the wait-for-IDR latch held — no decode break). The real Winlogon + DDA capture itself is pre-proven by the single-process secure path (commit `f4b4a6c`); step 5's new + surface is the mux, which the toggle exercises directly. + Remaining: **step 6** (helper relaunch watchdog on console connect/disconnect + crash, then a lock/unlock+UAC soak) and **step 2** (SendInput retry-on-failure refactor — input works today via the -existing path; this hardens it across the desktop boundary). +existing path; this hardens it across the desktop boundary). Also a **final user-driven smoke test**: +trigger a *real* UAC/lock on the box during a session and confirm the dialog appears on the client +(the box's UAC auto-elevates admins, so a real prompt can't be triggered headless over SSH). -Live validation to run when the box is up (single session, host as SYSTEM via the `-s -i 1` scheduled -task): connect a client → confirm video via the helper relay on the normal desktop (host log -`source switch … normal(WGC relay)` + `WGC helper spawned`), trigger a UAC prompt → the stream shows -the UAC dialog (host log `source switch … secure(DDA)`), dismiss → back to the helper; the QUIC -session stays up throughout. +> **Note:** the two-process path requires the host to run as SYSTEM (`run.cmd.sysbak` → `-s -i 1`). +> As SYSTEM, WASAPI loopback audio (session 0) does not capture the user session's audio — a known +> limitation of SYSTEM-mode capture, separate from this work. ## The constraint (verified live on the RTX 4090)