feat(host/windows): two-process mux test toggle + live-validate step 5

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) <noreply@anthropic.com>
This commit is contained in:
2026-06-16 08:13:16 +00:00
parent 372483abf0
commit 4edfcd4b43
2 changed files with 29 additions and 7 deletions
+12 -1
View File
@@ -2345,6 +2345,14 @@ fn virtual_stream_relay(
// The authoritative Default↔Winlogon signal (requires SYSTEM to read the Winlogon desktop name). // The authoritative Default↔Winlogon signal (requires SYSTEM to read the Winlogon desktop name).
let watcher = crate::capture::desktop_watch::DesktopWatcher::start(); 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<u128> = 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 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); 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 // 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 // the helper relay. On a switch, latch await_idr + force the now-active source to emit an IDR
// so the client resumes cleanly. // 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 { if secure != on_secure {
on_secure = secure; on_secure = secure;
await_idr = true; await_idr = true;
+17 -6
View File
@@ -16,15 +16,26 @@ Implemented so far:
helper relay on `Default`, the host's own DDA capturer+encoder on `Winlogon`; every switch latches 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. "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 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 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 > **Note:** the two-process path requires the host to run as SYSTEM (`run.cmd.sysbak` → `-s -i 1`).
task): connect a client → confirm video via the helper relay on the normal desktop (host log > As SYSTEM, WASAPI loopback audio (session 0) does not capture the user session's audio — a known
`source switch … normal(WGC relay)` + `WGC helper spawned`), trigger a UAC prompt → the stream shows > limitation of SYSTEM-mode capture, separate from this work.
the UAC dialog (host log `source switch … secure(DDA)`), dismiss → back to the helper; the QUIC
session stays up throughout.
## The constraint (verified live on the RTX 4090) ## The constraint (verified live on the RTX 4090)