feat(host/windows): WGC helper subcommand (two-process secure-desktop, step 3)

`m3-host wgc-helper --target-id N --gdi NAME --mode WxHxHz --bitrate K`: the
USER-session half of the two-process secure-desktop design
(docs/windows-secure-desktop.md). Opens WGC on the EXISTING SudoVDA output by
GDI name only (never creates a virtual output — a second topology owner re-trips
the ACCESS_LOST born-lost storm), encodes via NVENC, and ships framed Annex-B
AUs on stdout for the SYSTEM host to relay onto the live QUIC session:
`[u32 magic "PFAU"][u32 len][u64 pts_ns][u8 keyframe][data]`. tracing → stderr so
stdout stays the pure AU stream. cfg-gated windows-only; Linux build unaffected.

scripts/headless/win-build.cmd: the canonical box build script (sets
PUNKTFUNK_BUILD_VERSION so build.rs stamps the version + the NVENC LIB path).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-16 07:28:05 +00:00
parent 644274c33e
commit a0f6cddc70
3 changed files with 130 additions and 0 deletions
+30
View File
@@ -32,6 +32,8 @@ mod native_pairing;
mod pipeline;
mod pwinit;
mod vdisplay;
#[cfg(target_os = "windows")]
mod wgc_helper;
#[cfg(target_os = "linux")]
mod zerocopy;
@@ -195,6 +197,34 @@ fn real_main() -> Result<()> {
paired_store: None,
})
}
// USER-session WGC helper (Windows two-process secure-desktop design): capture the EXISTING
// SudoVDA via WGC + NVENC, stream AUs on stdout to the SYSTEM host. Spawned by the host
// (CreateProcessAsUser), not run by hand. See docs/windows-secure-desktop.md.
#[cfg(target_os = "windows")]
Some("wgc-helper") => {
let get = |flag: &str| {
args.iter()
.skip_while(|a| *a != flag)
.nth(1)
.map(String::as_str)
};
let (width, height, fps) = get("--mode")
.and_then(|m| {
let p: Vec<u32> = m.split('x').filter_map(|s| s.parse().ok()).collect();
(p.len() == 3).then(|| (p[0], p[1], p[2]))
})
.unwrap_or((1920, 1080, 60));
wgc_helper::run(wgc_helper::HelperOptions {
target_id: get("--target-id").and_then(|s| s.parse().ok()).unwrap_or(0),
gdi_name: get("--gdi").unwrap_or("").to_string(),
width,
height,
fps,
bitrate_kbps: get("--bitrate")
.and_then(|s| s.parse().ok())
.unwrap_or(20000),
})
}
Some("-h") | Some("--help") | Some("help") | None => {
print_usage();
Ok(())