feat(host/windows): two-process secure-desktop step 5 — DDA mux on Winlogon

`virtual_stream_relay` now muxes the AU source by input desktop. A DesktopWatcher
(SYSTEM-only Winlogon-name poll) drives it: the user-session WGC helper relay
feeds the normal (Default) desktop; the host's OWN DDA capturer+encoder — opened
lazily on the first secure transition, on the same SudoVDA target with a no-op
keepalive (the host still holds the real isolation owner) — captures the secure
(Winlogon: UAC/lock/login) desktop that WGC can't see. Every switch latches
"wait for IDR" and forces the now-active source to emit a keyframe (the two
encoders keep independent infinite-GOP state, so the client must resume on an
IDR); returning to the helper also drains its stale buffered AUs first.
Reconfigure drops the stale-target DDA; keyframe requests route to the live
source. Send path (FEC/seal/paced-send) unchanged.

Also: wgc_relay gains try_recv (drain on switch-back); open_dda takes dims as
args (avoids a closure borrow of the reassigned cur_mode); the forward! macro
returns bool with `break 'outer` at the call site (no in-macro label hygiene).

cfg-gated windows-only. Live validation (UAC switch over a session) pending.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-16 07:55:29 +00:00
parent 9f50b3930d
commit 140209bbfc
3 changed files with 226 additions and 53 deletions
@@ -97,6 +97,12 @@ impl HelperRelay {
self.rx.recv_timeout(dur)
}
/// Non-blocking receive — used to drain stale buffered AUs (encoded while the secure desktop was
/// the live source) before resuming the relay. `Ok` while AUs remain, `Err` once empty.
pub fn try_recv(&self) -> Result<RelayAu, std::sync::mpsc::TryRecvError> {
self.rx.try_recv()
}
/// Ask the helper's encoder for an IDR on the next frame (client decode recovery). Best-effort:
/// a write failure means the helper is gone — the caller's recv loop will see the disconnect.
pub fn request_keyframe(&self) {