feat(host/windows): SDR-while-secure — drop SudoVDA out of HDR on Winlogon so DDA captures it

When the DDA-on-secure path is enabled (PUNKTFUNK_SECURE_DDA=1), the mux now
toggles the SudoVDA's advanced-color (HDR) state via the CCD API
(sudovda::set_advanced_color → DisplayConfigSetDeviceInfo +
DISPLAYCONFIG_SET_ADVANCED_COLOR_STATE): on entering the secure (Winlogon)
desktop it disables HDR so the lock/UAC renders SDR/composed (no fullscreen
independent-flip → DDA can duplicate it instead of storming ACCESS_LOST/black),
opens DDA fresh on the now-SDR output; on returning to normal it re-enables HDR
and rebuilds the helper so WGC re-detects the restored colorspace.

Also debounce the DesktopWatcher (publish a Default↔Winlogon change only after it
is stable ~80ms) so transient flaps during the transition don't thrash the mux.

Default (no flag) is unchanged: WGC stays live through a lock, no DDA switch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-16 10:54:58 +00:00
parent be18797df8
commit 6ea52b0372
3 changed files with 111 additions and 22 deletions
+40 -14
View File
@@ -2526,24 +2526,50 @@ fn virtual_stream_relay(
"two-process: source switch"
);
if secure {
if dda.is_none() {
match open_dda(&target, cur_mode.width, cur_mode.height, effective_hz) {
Ok(p) => dda = Some(p),
Err(e) => {
tracing::error!(error = %format!("{e:#}"),
"two-process: DDA open failed — secure desktop will freeze on last frame");
}
}
// SDR-while-secure: drop the SudoVDA out of HDR so the secure (Winlogon) desktop
// renders SDR/composed — the HDR fullscreen independent-flip is what made DDA storm
// ACCESS_LOST (black). Give the reconfig a moment to settle, then (re)open DDA fresh on
// the now-SDR output.
let toggled = unsafe {
crate::vdisplay::sudovda::set_advanced_color(target.target_id, false)
};
if toggled {
std::thread::sleep(std::time::Duration::from_millis(250));
}
if let Some(d) = dda.as_mut() {
d.enc.request_keyframe();
dda = None; // reopen so we capture the post-toggle (SDR) output
match open_dda(&target, cur_mode.width, cur_mode.height, effective_hz) {
Ok(mut p) => {
p.enc.request_keyframe();
dda = Some(p);
}
Err(e) => {
tracing::error!(error = %format!("{e:#}"),
"two-process: DDA open failed — secure desktop will freeze on last frame");
}
}
next = std::time::Instant::now();
} else {
// Returning to the helper: drain stale buffered AUs (encoded while we ignored it) and
// force a fresh IDR; await_idr then skips the stale deltas until that IDR arrives.
while relay.try_recv().is_ok() {}
relay.request_keyframe();
// Returning to the normal desktop: restore HDR on the SudoVDA (WGC captures it HDR), then
// rebuild the helper fresh so its WGC re-detects the restored colorspace, and resume.
unsafe {
crate::vdisplay::sudovda::set_advanced_color(target.target_id, true);
}
dda = None; // free the secure DDA encoder
match build(&mut vd, cur_mode) {
Ok((ka, rl, tg, hz)) => {
relay = rl;
_keepalive = ka;
target = tg;
effective_hz = hz;
interval = std::time::Duration::from_secs_f64(1.0 / hz.max(1) as f64);
}
Err(e) => {
tracing::error!(error = %format!("{e:#}"),
"two-process: helper rebuild on secure-exit failed");
while relay.try_recv().is_ok() {}
relay.request_keyframe();
}
}
}
}
if want_kf {