fix(host/windows): make SudoVDA the sole display via clean CCD (the IDD needs to be primary/composited)

Live result of the previous build: the MODE_CHANGE_IN_PROGRESS storm was FIXED
(0 occurrences) by dropping primary-promotion — but it exposed the regression
the review predicted: a non-primary EXTENDED SudoVDA is NOT DWM-composited on
this box, so DDA gets born-lost ACCESS_LOST (0x887a0026) + black frames. The
IDD genuinely must be the sole/primary/composited display here.

Apollo reaches that end state ('Virtual Desktop: 5120x1440', sole display) via
Windows AUTO-promoting the real WDDM display over the box's leftover 1024x768
basic display — but Windows does NOT auto-promote for us, leaving the IDD
extended. So make it sole explicitly, the clean way:
- create(): deactivate the other display(s) via the atomic CCD path
  (isolate_displays_ccd) by DEFAULT (opt out with PUNKTFUNK_NO_ISOLATE). Drop
  the legacy per-device GDI detach from the path (it misses iGPU-attached
  monitors and churns; kept #[allow(dead_code)] for reference).
- set_active_mode(): CDS_UPDATEREGISTRY only — set the mode in place, NO
  CDS_SET_PRIMARY / CDS_GLOBAL / DM_POSITION. A sole display is already primary,
  so there's nothing to contest → no MODE_CHANGE storm (that storm came from
  promoting primary at (0,0) WHILE the basic display was still active).

Net: sole SudoVDA → primary → composited → capturable, with no topology
contest. Keeps the prior MODE_CHANGE-as-transient handling + removed born-lost
escape as backstops.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-16 15:12:31 +00:00
parent 769fd96b87
commit c60a05dbe9
+29 -42
View File
@@ -32,7 +32,7 @@ use windows::Win32::Devices::Display::{
use windows::Win32::Foundation::{CloseHandle, HANDLE, LUID};
use windows::Win32::Graphics::Gdi::{
ChangeDisplaySettingsExW, EnumDisplayDevicesW, EnumDisplaySettingsW, CDS_GLOBAL, CDS_NORESET,
CDS_SET_PRIMARY, CDS_TEST, CDS_TYPE, CDS_UPDATEREGISTRY, DEVMODEW, DISPLAY_DEVICEW,
CDS_TEST, CDS_TYPE, CDS_UPDATEREGISTRY, DEVMODEW, DISPLAY_DEVICEW,
DISPLAY_DEVICE_ATTACHED_TO_DESKTOP, DISP_CHANGE_SUCCESSFUL, DM_BITSPERPEL, DM_DISPLAYFREQUENCY,
DM_PELSHEIGHT, DM_PELSWIDTH, DM_POSITION, ENUM_CURRENT_SETTINGS, ENUM_DISPLAY_SETTINGS_MODE,
};
@@ -341,22 +341,15 @@ fn set_active_mode(gdi_name: &str, mode: Mode) {
);
}
// Default (multi-display, Apollo-parity): set ONLY this output's mode in place. Promoting the IDD
// to PRIMARY at the virtual-screen origin (DM_POSITION 0,0) + persisting it GLOBALly contests the
// box's baseline display (e.g. a 1024x768 basic "WinDisc") so the desktop topology never reaches a
// stable fixed point → a perpetual DXGI_ERROR_MODE_CHANGE_IN_PROGRESS storm (the freeze + audible
// PnP chime measured live on the RTX4090+iGPU box). Apollo with an EMPTY config never promotes
// primary and captures the same SudoVDA cleanly (verified live). So default to CDS_UPDATEREGISTRY
// only. ONLY when isolating to a SOLE display does the IDD genuinely need to be primary — a blank
// EXTENDED IDD may not be DWM-composited and would yield no duplication frames.
let isolating = std::env::var("PUNKTFUNK_ISOLATE_DISPLAYS").is_ok();
let mut dm_fields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY | DM_BITSPERPEL;
if isolating {
dm_fields |= DM_POSITION; // pin to origin, but only as the sole/primary display
}
// Set ONLY this output's mode in place (size/refresh/bpp; NO DM_POSITION). Do NOT promote it to
// PRIMARY here and do NOT write a GLOBAL topology: promoting the IDD to primary at (0,0) while the
// box's leftover basic display is still active contests the topology and storms
// DXGI_ERROR_MODE_CHANGE_IN_PROGRESS (measured live). The IDD is made the sole → primary →
// DWM-composited display by the CCD isolation in create() (which deactivates the other display
// first), so a sole display is already primary and needs no CDS_SET_PRIMARY here.
let dm = DEVMODEW {
dmSize: size_of::<DEVMODEW>() as u16,
dmFields: dm_fields,
dmFields: DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY | DM_BITSPERPEL,
dmBitsPerPel: 32,
dmPelsWidth: mode.width,
dmPelsHeight: mode.height,
@@ -376,16 +369,8 @@ fn set_active_mode(gdi_name: &str, mode: Mode) {
);
return;
}
// Default: CDS_UPDATEREGISTRY only — set this output's mode WITHOUT promoting it to primary or
// rewriting the global topology (which storms MODE_CHANGE_IN_PROGRESS). Promote to primary only when
// isolating to a sole display.
let apply_flags = if isolating {
CDS_UPDATEREGISTRY | CDS_GLOBAL | CDS_SET_PRIMARY
} else {
CDS_UPDATEREGISTRY
};
let apply = unsafe {
ChangeDisplaySettingsExW(PCWSTR(wname.as_ptr()), Some(&dm), None, apply_flags, None)
ChangeDisplaySettingsExW(PCWSTR(wname.as_ptr()), Some(&dm), None, CDS_UPDATEREGISTRY, None)
};
if apply == DISP_CHANGE_SUCCESSFUL {
tracing::info!(
@@ -414,6 +399,11 @@ fn set_active_mode(gdi_name: &str, mode: Mode) {
/// is attached" and the secure desktop has nowhere to render but the output we capture.
///
/// Returns the displays we detached plus their saved modes so teardown can restore them.
///
/// Superseded by the atomic CCD [`isolate_displays_ccd`] (the legacy per-device GDI detach misses
/// iGPU-attached monitors on a hybrid box and churns the topology). Retained for reference / a
/// possible fallback.
#[allow(dead_code)]
unsafe fn isolate_displays(keep_gdi_name: &str) -> Vec<(String, DEVMODEW)> {
let mut saved = Vec::new();
let mut idx = 0u32;
@@ -766,32 +756,29 @@ impl VirtualDisplay for SudoVdaDisplay {
break;
}
}
let mut isolated: Vec<(String, DEVMODEW)> = Vec::new();
let isolated: Vec<(String, DEVMODEW)> = Vec::new(); // legacy GDI detach unused (CCD path below)
let mut ccd_saved: Option<SavedConfig> = None;
match &gdi_name {
Some(n) => {
tracing::info!("SudoVDA target {} -> {n}", ao.target_id);
// ADD only advertises the mode; force it active so DXGI captures the requested size.
set_active_mode(n, mode);
// Display isolation (detach all other monitors → SudoVDA becomes the SOLE display) is
// OPT-IN now. On a hybrid GPU box a SOLE active display goes into fullscreen
// independent-flip / MPO, which Desktop Duplication CANNOT capture → the born-lost
// ACCESS_LOST + MODE_CHANGE_IN_PROGRESS storm measured live on the RTX4090+iGPU box
// (hook verified-firing, DPI=2, overlay running — yet still frozen). Apollo stays rock
// solid on this exact box precisely because it KEEPS the physical monitor active and just
// arranges the virtual display alongside it (multi-display is DWM-composited, so the
// output never independent-flips). So default to NOT isolating — match Apollo's topology.
// Set PUNKTFUNK_ISOLATE_DISPLAYS=1 to force the old sole-display behaviour (a truly
// headless box with no attached monitor, where the secure/Winlogon desktop would
// otherwise render on a detached physical output).
if std::env::var("PUNKTFUNK_ISOLATE_DISPLAYS").is_ok() {
isolated = unsafe { isolate_displays(n) };
// Make the SudoVDA the SOLE active display (default). On this box an EXTENDED
// (non-primary) IDD is NOT DWM-composited → Desktop Duplication gets a born-lost
// ACCESS_LOST (measured live: MODE_CHANGE storm fixed, but the extended IDD then
// born-lost). Apollo reaches the same end state ("Virtual Desktop: WxH" — the IDD is the
// whole desktop, hence primary + composited) via Windows AUTO-promoting the real WDDM
// display over the box's leftover 1024x768 basic display; Windows does NOT auto-promote
// for us, so we deactivate the other display(s) explicitly via the clean atomic CCD path.
// Deactivating FIRST means set_active_mode's primary-promotion has nothing to contest →
// no MODE_CHANGE_IN_PROGRESS storm (that storm came from promoting primary WHILE the
// basic display stayed active). Opt out with PUNKTFUNK_NO_ISOLATE=1 (a box with a real
// second monitor to keep live). The legacy GDI detach is skipped — it misses
// iGPU-attached monitors on a hybrid box and churns per-device; CCD is atomic.
if std::env::var("PUNKTFUNK_NO_ISOLATE").is_err() {
ccd_saved = unsafe { isolate_displays_ccd(ao.target_id) };
} else {
tracing::info!(
"display isolation SKIPPED (Apollo-parity multi-display — avoids sole-display \
independent-flip; set PUNKTFUNK_ISOLATE_DISPLAYS=1 to force sole-display)"
);
tracing::info!("display isolation skipped (PUNKTFUNK_NO_ISOLATE) — IDD stays extended");
}
thread::sleep(Duration::from_millis(1500)); // let the topology settle before capture opens
}