From e60cda3939ff80b9a3719bd13156e1f9f821f961 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Thu, 25 Jun 2026 14:26:25 +0000 Subject: [PATCH] =?UTF-8?q?refactor(windows-host):=20move=20CCD/HDR=20disp?= =?UTF-8?q?lay=20helpers=20to=20a=20neutral=20module=20=E2=80=94=20F1=20co?= =?UTF-8?q?mplete=20(audit=20=C2=A79)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moved the remaining 6 SudoVDA reach-in helpers + SavedConfig (resolve_gdi_name, set_advanced_color, advanced_color_enabled, set_active_mode, isolate/restore_displays_ccd) verbatim from vdisplay::sudovda into a backend-neutral crate::win_display module (the plan's windows/display_ccd.rs). The capturers (idd_push/dxgi/wgc), pf_vdisplay, and punktfunk1 now depend on these as PEERS via crate::win_display instead of reaching into the SudoVDA backend. With win_adapter (F1 pt1), all 7 reach-in helpers are now neutral — the circular reach-in is broken, so SudoVDA can eventually be deleted (Goal 2) without losing the display utilities. sudovda re-exports the ones it still uses internally; its now-unused CCD/GDI imports were removed. Verified: host clippy (nvenc) clean on the RTX box; Linux check clean (the new modules are #[cfg(windows)]). Co-Authored-By: Claude Opus 4.8 (1M context) --- crates/punktfunk-host/src/capture/dxgi.rs | 2 +- crates/punktfunk-host/src/capture/idd_push.rs | 6 +- crates/punktfunk-host/src/capture/wgc.rs | 2 +- crates/punktfunk-host/src/main.rs | 2 + crates/punktfunk-host/src/punktfunk1.rs | 4 +- .../src/vdisplay/pf_vdisplay.rs | 8 +- crates/punktfunk-host/src/vdisplay/sudovda.rs | 383 +---------------- crates/punktfunk-host/src/win_display.rs | 391 ++++++++++++++++++ 8 files changed, 412 insertions(+), 386 deletions(-) create mode 100644 crates/punktfunk-host/src/win_display.rs diff --git a/crates/punktfunk-host/src/capture/dxgi.rs b/crates/punktfunk-host/src/capture/dxgi.rs index 0fde7ca..864f307 100644 --- a/crates/punktfunk-host/src/capture/dxgi.rs +++ b/crates/punktfunk-host/src/capture/dxgi.rs @@ -2712,7 +2712,7 @@ impl DuplCapturer { } // The SudoVDA output's GDI name can CHANGE across a secure-desktop topology rebuild — // re-resolve from the STABLE target id so we find it under its current name. - if let Some(n) = crate::vdisplay::sudovda::resolve_gdi_name(self.target_id) { + if let Some(n) = crate::win_display::resolve_gdi_name(self.target_id) { self.gdi_name = n; } // Re-sync the capture thread to the CURRENT input desktop on EVERY rebuild — symmetric for diff --git a/crates/punktfunk-host/src/capture/idd_push.rs b/crates/punktfunk-host/src/capture/idd_push.rs index b7f1487..cbc3ed8 100644 --- a/crates/punktfunk-host/src/capture/idd_push.rs +++ b/crates/punktfunk-host/src/capture/idd_push.rs @@ -376,13 +376,13 @@ impl IddPushCapturer { // settled within 250 ms and would size the ring SDR while the driver composes FP16 → a format // mismatch → an immediate ring recreate + dropped first frames (audit §5.4). let enabled_hdr = - client_10bit && crate::vdisplay::sudovda::set_advanced_color(target.target_id, true); + client_10bit && crate::win_display::set_advanced_color(target.target_id, true); if enabled_hdr { // Let the colorspace change settle before the driver composes + we size the ring. std::thread::sleep(Duration::from_millis(250)); } let display_hdr = - enabled_hdr || crate::vdisplay::sudovda::advanced_color_enabled(target.target_id); + enabled_hdr || crate::win_display::advanced_color_enabled(target.target_id); let ring_fmt = if display_hdr { DXGI_FORMAT_R16G16B16A16_FLOAT } else { @@ -688,7 +688,7 @@ impl IddPushCapturer { return; } self.last_acm_poll = Instant::now(); - let now_hdr = unsafe { crate::vdisplay::sudovda::advanced_color_enabled(self.target_id) }; + let now_hdr = unsafe { crate::win_display::advanced_color_enabled(self.target_id) }; if now_hdr == self.display_hdr { return; } diff --git a/crates/punktfunk-host/src/capture/wgc.rs b/crates/punktfunk-host/src/capture/wgc.rs index 213cdc1..1d77e63 100644 --- a/crates/punktfunk-host/src/capture/wgc.rs +++ b/crates/punktfunk-host/src/capture/wgc.rs @@ -196,7 +196,7 @@ impl WgcCapturer { // The SudoVDA output appears a beat after the display is created — settle-retry like DDA. let deadline = Instant::now() + Duration::from_millis(2000); let (adapter, output) = loop { - if let Some(n) = crate::vdisplay::sudovda::resolve_gdi_name(target.target_id) { + if let Some(n) = crate::win_display::resolve_gdi_name(target.target_id) { if let Ok(found) = find_output(&n) { break found; } diff --git a/crates/punktfunk-host/src/main.rs b/crates/punktfunk-host/src/main.rs index eb1ff96..70bcfdc 100644 --- a/crates/punktfunk-host/src/main.rs +++ b/crates/punktfunk-host/src/main.rs @@ -41,6 +41,8 @@ mod vdisplay; mod wgc_helper; #[cfg(target_os = "windows")] mod win_adapter; +#[cfg(target_os = "windows")] +mod win_display; #[cfg(target_os = "linux")] mod zerocopy; diff --git a/crates/punktfunk-host/src/punktfunk1.rs b/crates/punktfunk-host/src/punktfunk1.rs index 2d09261..54023fe 100644 --- a/crates/punktfunk-host/src/punktfunk1.rs +++ b/crates/punktfunk-host/src/punktfunk1.rs @@ -2661,7 +2661,7 @@ fn virtual_stream_relay( #[cfg(target_os = "windows")] if bit_depth >= 10 { unsafe { - if crate::vdisplay::sudovda::set_advanced_color(target.target_id, true) { + if crate::win_display::set_advanced_color(target.target_id, true) { // Let the colorspace change settle before WGC creates its capture item / detects HDR. std::thread::sleep(std::time::Duration::from_millis(250)); } @@ -2884,7 +2884,7 @@ fn virtual_stream_relay( // open DDA in HDR (FP16 DuplicateOutput1 → BT.2020 PQ Main10); the normal-desktop DDA // overlay/flip issues that drove us to WGC don't apply to the composed Winlogon UI. let hdr = - unsafe { crate::vdisplay::sudovda::advanced_color_enabled(target.target_id) }; + unsafe { crate::win_display::advanced_color_enabled(target.target_id) }; dda = None; // reopen to capture the secure desktop match open_dda(&target, cur_mode.width, cur_mode.height, effective_hz, hdr) { Ok(mut p) => { diff --git a/crates/punktfunk-host/src/vdisplay/pf_vdisplay.rs b/crates/punktfunk-host/src/vdisplay/pf_vdisplay.rs index 02ec3d1..5a67bcf 100644 --- a/crates/punktfunk-host/src/vdisplay/pf_vdisplay.rs +++ b/crates/punktfunk-host/src/vdisplay/pf_vdisplay.rs @@ -40,11 +40,11 @@ use super::{Mode, VirtualDisplay, VirtualOutput}; // Backend-NEUTRAL CCD/DXGI helpers reused from the SudoVDA backend (a pf-vdisplay monitor's target_id // is a real OS target id, so these operate identically). The shared MON_GEN/CURRENT_MON_GEN generation // counter is reused too, so the IDD-push stale-ring bail works regardless of which backend is active. -use super::sudovda::{ - isolate_displays_ccd, resolve_gdi_name, restore_displays_ccd, set_active_mode, SavedConfig, - CURRENT_MON_GEN, MON_GEN, -}; +use super::sudovda::{CURRENT_MON_GEN, MON_GEN}; use crate::win_adapter::resolve_render_adapter_luid; +use crate::win_display::{ + isolate_displays_ccd, resolve_gdi_name, restore_displays_ccd, set_active_mode, SavedConfig, +}; // pf-vdisplay device-interface GUID (pf_vdisplay_proto::PF_VDISPLAY_INTERFACE_GUID_U128). Deliberately // NOT SudoVDA's `{e5bcc234-…}` — we own this driver, so a private interface GUID signals it and avoids diff --git a/crates/punktfunk-host/src/vdisplay/sudovda.rs b/crates/punktfunk-host/src/vdisplay/sudovda.rs index 431c07f..5da1ee2 100644 --- a/crates/punktfunk-host/src/vdisplay/sudovda.rs +++ b/crates/punktfunk-host/src/vdisplay/sudovda.rs @@ -40,21 +40,8 @@ use windows::Win32::Devices::DeviceAndDriverInstallation::{ SetupDiGetDeviceInterfaceDetailW, DIGCF_DEVICEINTERFACE, DIGCF_PRESENT, SP_DEVICE_INTERFACE_DATA, SP_DEVICE_INTERFACE_DETAIL_DATA_W, }; -use windows::Win32::Devices::Display::{ - DisplayConfigGetDeviceInfo, DisplayConfigSetDeviceInfo, GetDisplayConfigBufferSizes, - QueryDisplayConfig, SetDisplayConfig, DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO, - DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME, DISPLAYCONFIG_DEVICE_INFO_SET_ADVANCED_COLOR_STATE, - DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO, DISPLAYCONFIG_MODE_INFO, DISPLAYCONFIG_PATH_INFO, - DISPLAYCONFIG_SET_ADVANCED_COLOR_STATE, DISPLAYCONFIG_SOURCE_DEVICE_NAME, - QDC_ONLY_ACTIVE_PATHS, SDC_ALLOW_CHANGES, SDC_APPLY, SDC_FORCE_MODE_ENUMERATION, - SDC_SAVE_TO_DATABASE, SDC_USE_SUPPLIED_DISPLAY_CONFIG, -}; +// (CCD `Devices::Display` + `Graphics::Gdi` imports moved with the display helpers to `win_display`.) use windows::Win32::Foundation::{CloseHandle, HANDLE, LUID}; -use windows::Win32::Graphics::Gdi::{ - ChangeDisplaySettingsExW, EnumDisplaySettingsW, CDS_TEST, CDS_UPDATEREGISTRY, DEVMODEW, - DISP_CHANGE_SUCCESSFUL, DM_BITSPERPEL, DM_DISPLAYFREQUENCY, DM_PELSHEIGHT, DM_PELSWIDTH, - ENUM_DISPLAY_SETTINGS_MODE, -}; use windows::Win32::Storage::FileSystem::{ CreateFileW, FILE_FLAGS_AND_ATTRIBUTES, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING, }; @@ -164,367 +151,13 @@ unsafe fn ioctl(h: HANDLE, code: u32, input: &[u8], output: &mut [u8]) -> Result Ok(returned) } -/// Resolve the `\\.\DisplayN` GDI name for a SudoVDA target id via the CCD API. Returns `None` -/// until the OS activates the target into the desktop topology (needs a real WDDM GPU; on a -/// GPU-less box this stays `None` even though ADD succeeded). -pub(crate) unsafe fn resolve_gdi_name(target_id: u32) -> Option { - let mut np = 0u32; - let mut nm = 0u32; - if GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &mut np, &mut nm).is_err() { - return None; - } - let mut paths = vec![DISPLAYCONFIG_PATH_INFO::default(); np as usize]; - let mut modes = vec![DISPLAYCONFIG_MODE_INFO::default(); nm as usize]; - if QueryDisplayConfig( - QDC_ONLY_ACTIVE_PATHS, - &mut np, - paths.as_mut_ptr(), - &mut nm, - modes.as_mut_ptr(), - None, - ) - .is_err() - { - return None; - } - for p in paths.iter().take(np as usize) { - if p.targetInfo.id == target_id { - let mut src = DISPLAYCONFIG_SOURCE_DEVICE_NAME::default(); - src.header.r#type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME; - src.header.size = size_of::() as u32; - src.header.adapterId = p.sourceInfo.adapterId; - src.header.id = p.sourceInfo.id; - if DisplayConfigGetDeviceInfo(&mut src.header) == 0 { - let name = String::from_utf16_lossy(&src.viewGdiDeviceName); - return Some(name.trim_end_matches('\u{0}').to_string()); - } - } - } - None -} - -/// Toggle the SudoVDA target's advanced-color (HDR) state via the CCD API. Disabling HDR while on the -/// secure (Winlogon) desktop makes it render SDR/composed so DXGI Desktop Duplication can capture it -/// (the HDR fullscreen independent-flip otherwise storms `ACCESS_LOST` → black); re-enable on return so -/// WGC keeps HDR on the normal desktop. Returns true on a successful `DisplayConfigSetDeviceInfo`. -pub(crate) unsafe fn set_advanced_color(target_id: u32, enable: bool) -> bool { - let mut np = 0u32; - let mut nm = 0u32; - if GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &mut np, &mut nm).is_err() { - return false; - } - let mut paths = vec![DISPLAYCONFIG_PATH_INFO::default(); np as usize]; - let mut modes = vec![DISPLAYCONFIG_MODE_INFO::default(); nm as usize]; - if QueryDisplayConfig( - QDC_ONLY_ACTIVE_PATHS, - &mut np, - paths.as_mut_ptr(), - &mut nm, - modes.as_mut_ptr(), - None, - ) - .is_err() - { - return false; - } - for p in paths.iter().take(np as usize) { - if p.targetInfo.id == target_id { - let mut s = DISPLAYCONFIG_SET_ADVANCED_COLOR_STATE::default(); - s.header.r#type = DISPLAYCONFIG_DEVICE_INFO_SET_ADVANCED_COLOR_STATE; - s.header.size = size_of::() as u32; - s.header.adapterId = p.targetInfo.adapterId; - s.header.id = p.targetInfo.id; - s.Anonymous.value = enable as u32; // bit 0 = enableAdvancedColor - let rc = DisplayConfigSetDeviceInfo(&s.header); - tracing::info!( - target_id, - enable, - rc, - "SudoVDA set advanced-color (HDR) state" - ); - return rc == 0; - } - } - tracing::warn!( - target_id, - "set_advanced_color: target not found in active paths" - ); - false -} - -/// Read the SudoVDA target's CURRENT advanced-color (HDR) state via the CCD API — i.e. whether HDR is -/// actually ON for the virtual display right now (e.g. because the user toggled it in Windows display -/// settings). The capture/encode pipeline follows the monitor's real colorspace (WGC → FP16 → NVENC -/// Main10 BT.2020 PQ), so this is the authoritative "is this an HDR session" signal — NOT the -/// handshake-negotiated bit depth. Returns false if the target isn't found / the query fails. -pub(crate) unsafe fn advanced_color_enabled(target_id: u32) -> bool { - let mut np = 0u32; - let mut nm = 0u32; - if GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &mut np, &mut nm).is_err() { - return false; - } - let mut paths = vec![DISPLAYCONFIG_PATH_INFO::default(); np as usize]; - let mut modes = vec![DISPLAYCONFIG_MODE_INFO::default(); nm as usize]; - if QueryDisplayConfig( - QDC_ONLY_ACTIVE_PATHS, - &mut np, - paths.as_mut_ptr(), - &mut nm, - modes.as_mut_ptr(), - None, - ) - .is_err() - { - return false; - } - for p in paths.iter().take(np as usize) { - if p.targetInfo.id == target_id { - let mut info = DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO::default(); - info.header.r#type = DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO; - info.header.size = size_of::() as u32; - info.header.adapterId = p.targetInfo.adapterId; - info.header.id = p.targetInfo.id; - if DisplayConfigGetDeviceInfo(&mut info.header) == 0 { - // value bit 1 = advancedColorEnabled (bit 0 = advancedColorSupported). - return (info.Anonymous.value & 0x2) != 0; - } - return false; - } - } - false -} - -/// Force the freshly-added SudoVDA monitor to the client's exact `WxH@Hz`. The ADD IOCTL only -/// ADVERTISES the mode; Windows otherwise activates an IDD target at a 1280x720 default, so the -/// ACTIVE mode (what DXGI Desktop Duplication captures) must be set explicitly. CDS_TEST first so a -/// mode the driver didn't advertise just leaves the default instead of erroring the session. -// pub(crate) so vdisplay::pf_vdisplay can reuse this backend-neutral CCD/GDI mode-set helper -// (a pf-vdisplay monitor's GDI name is a real OS device name, so it works unchanged). -pub(crate) fn set_active_mode(gdi_name: &str, mode: Mode) { - let wname: Vec = gdi_name.encode_utf16().chain(std::iter::once(0)).collect(); - - // Enumerate the modes the driver actually advertises for this output and pick the best match for - // the requested RESOLUTION: the exact refresh if present, else the highest advertised refresh - // <= requested, else the highest available at that resolution. The SudoVDA ADD IOCTL advertises - // the client mode, but a very high pixel rate (e.g. 5120x1440@240 = 1.77 Gpix/s) can be clamped - // or absent — falling back to a lower refresh AT THE SAME RESOLUTION keeps the client's - // resolution (what the user sees) instead of collapsing to the 1280x720/1920x1080 OS default. - let mut at_res: Vec = Vec::new(); - let mut res_set: std::collections::BTreeSet<(u32, u32)> = std::collections::BTreeSet::new(); - let mut i = 0u32; - loop { - let mut dm = DEVMODEW { - dmSize: size_of::() as u16, - ..Default::default() - }; - let ok = unsafe { - EnumDisplaySettingsW( - PCWSTR(wname.as_ptr()), - ENUM_DISPLAY_SETTINGS_MODE(i), - &mut dm, - ) - } - .as_bool(); - if !ok { - break; - } - i += 1; - res_set.insert((dm.dmPelsWidth, dm.dmPelsHeight)); - if dm.dmPelsWidth == mode.width && dm.dmPelsHeight == mode.height { - at_res.push(dm.dmDisplayFrequency); - } - } - let chosen_hz = if at_res.contains(&mode.refresh_hz) { - mode.refresh_hz - } else if let Some(hz) = at_res - .iter() - .copied() - .filter(|&hz| hz <= mode.refresh_hz) - .max() - { - hz - } else if let Some(hz) = at_res.iter().copied().max() { - hz - } else { - mode.refresh_hz // resolution not advertised at all; attempt anyway (likely -> OS default) - }; - if at_res.is_empty() { - tracing::warn!( - "{gdi_name}: driver advertises no {}x{} mode (top advertised: {:?}); attempting @{} anyway", - mode.width, - mode.height, - res_set.iter().rev().take(8).collect::>(), - mode.refresh_hz - ); - } else if chosen_hz != mode.refresh_hz { - tracing::info!( - "{gdi_name}: {}x{}@{} not advertised; using {}x{}@{} (advertised refreshes here: {:?})", - mode.width, - mode.height, - mode.refresh_hz, - mode.width, - mode.height, - chosen_hz, - at_res - ); - } - - // 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::() as u16, - dmFields: DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY | DM_BITSPERPEL, - dmBitsPerPel: 32, - dmPelsWidth: mode.width, - dmPelsHeight: mode.height, - dmDisplayFrequency: chosen_hz, - ..Default::default() - }; - let test = unsafe { - ChangeDisplaySettingsExW(PCWSTR(wname.as_ptr()), Some(&dm), None, CDS_TEST, None) - }; - if test != DISP_CHANGE_SUCCESSFUL { - tracing::warn!( - result = test.0, - "{gdi_name}: driver rejected {}x{}@{} (mode not advertised?) — leaving OS default", - mode.width, - mode.height, - chosen_hz - ); - return; - } - let apply = unsafe { - ChangeDisplaySettingsExW( - PCWSTR(wname.as_ptr()), - Some(&dm), - None, - CDS_UPDATEREGISTRY, - None, - ) - }; - if apply == DISP_CHANGE_SUCCESSFUL { - tracing::info!( - "{gdi_name}: active mode set to {}x{}@{}", - mode.width, - mode.height, - chosen_hz - ); - } else { - tracing::warn!( - result = apply.0, - "{gdi_name}: failed to apply {}x{}@{}", - mode.width, - mode.height, - chosen_hz - ); - } -} - -/// Saved active display topology, for restoring on teardown. -// pub(crate) so vdisplay::pf_vdisplay's Monitor can hold the same saved-topology type. -pub(crate) type SavedConfig = (Vec, Vec); - -/// `DISPLAYCONFIG_PATH_ACTIVE` (wingdi.h) — the `flags` bit marking a path active. The `windows` crate -/// doesn't export it, so define it here. -const DISPLAYCONFIG_PATH_ACTIVE: u32 = 0x0000_0001; - -/// Robust display isolation via the CCD API. The naive GDI approach (EnumDisplayDevices + -/// ChangeDisplaySettings) MISSES displays on a hybrid box — an iGPU-attached physical monitor isn't -/// flagged `ATTACHED_TO_DESKTOP` in the GDI enum, so it's never detached and the secure desktop / -/// lock screen lands on IT while our virtual output freezes. `QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS)` -/// sees every active path; we deactivate all of them EXCEPT the SudoVDA target's, leaving the virtual -/// display as the sole desktop so ALL content (incl. Winlogon) renders to it. Apollo isolates the same -/// way (CCD). Returns the original active config to restore on teardown. -// pub(crate) so vdisplay::pf_vdisplay can reuse this backend-neutral CCD isolation helper -// (it operates on a real OS target id — a pf-vdisplay monitor's target_id qualifies). -pub(crate) unsafe fn isolate_displays_ccd(keep_target_id: u32) -> Option { - let mut np = 0u32; - let mut nm = 0u32; - if GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &mut np, &mut nm).is_err() { - return None; - } - let mut paths = vec![DISPLAYCONFIG_PATH_INFO::default(); np as usize]; - let mut modes = vec![DISPLAYCONFIG_MODE_INFO::default(); nm as usize]; - if QueryDisplayConfig( - QDC_ONLY_ACTIVE_PATHS, - &mut np, - paths.as_mut_ptr(), - &mut nm, - modes.as_mut_ptr(), - None, - ) - .is_err() - { - return None; - } - paths.truncate(np as usize); - modes.truncate(nm as usize); - let saved = (paths.clone(), modes.clone()); - let mut others = 0u32; - for p in paths.iter_mut() { - if p.targetInfo.id == keep_target_id { - continue; - } - if p.flags & DISPLAYCONFIG_PATH_ACTIVE != 0 { - p.flags &= !DISPLAYCONFIG_PATH_ACTIVE; // mark this path inactive - others += 1; - } - } - if others == 0 { - // The virtual path shows active in the CCD database (from set_active_mode's legacy - // ChangeDisplaySettingsExW), but a legacy mode-set does NOT drive the IddCx adapter's - // EVT_IDD_CX_ADAPTER_COMMIT_MODES — and without COMMIT_MODES the OS never calls - // ASSIGN_SWAPCHAIN, so the driver never receives composed frames. Force an explicit CCD - // SetDisplayConfig commit of the (sole) virtual path so the IddCx path actually activates. - // SDC_FORCE_MODE_ENUMERATION makes the OS re-enumerate + re-commit even though the CCD DB - // already lists the path active. - let rc = SetDisplayConfig( - Some(paths.as_slice()), - Some(modes.as_slice()), - SDC_APPLY - | SDC_USE_SUPPLIED_DISPLAY_CONFIG - | SDC_ALLOW_CHANGES - | SDC_SAVE_TO_DATABASE - | SDC_FORCE_MODE_ENUMERATION, - ); - tracing::info!("display isolate (CCD): forced CCD re-commit of sole virtual path {keep_target_id} rc={rc:#x} (drives IddCx COMMIT_MODES → ASSIGN_SWAPCHAIN)"); - return Some(saved); - } - let rc = SetDisplayConfig( - Some(paths.as_slice()), - Some(modes.as_slice()), - SDC_APPLY - | SDC_USE_SUPPLIED_DISPLAY_CONFIG - | SDC_ALLOW_CHANGES - | SDC_FORCE_MODE_ENUMERATION, - ); - if rc == 0 { - tracing::info!("display isolate (CCD): deactivated {others} other display(s) — SudoVDA target {keep_target_id} is now the sole desktop"); - } else { - tracing::warn!("display isolate (CCD): SetDisplayConfig failed rc={rc:#x} (tried to deactivate {others} path(s))"); - } - Some(saved) -} - -/// Restore the topology saved by [`isolate_displays_ccd`] (teardown, before the virtual output is -/// removed), re-activating the displays we deactivated. -// pub(crate) so vdisplay::pf_vdisplay can reuse this backend-neutral CCD restore helper. -pub(crate) unsafe fn restore_displays_ccd(saved: &SavedConfig) { - let (paths, modes) = saved; - if paths.is_empty() { - return; - } - let rc = SetDisplayConfig( - Some(paths.as_slice()), - Some(modes.as_slice()), - SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_ALLOW_CHANGES, - ); - tracing::info!("display isolate (CCD): restored original topology rc={rc:#x}"); -} +// The CCD/GDI display helpers (resolve_gdi_name, set_advanced_color, advanced_color_enabled, +// set_active_mode, isolate/restore_displays_ccd) + SavedConfig moved to the backend-neutral +// `crate::win_display` (audit §9 / Goal 2). Re-exported so this backend's own callers keep the short +// names; external callers use `crate::win_display` directly. +pub(crate) use crate::win_display::{ + isolate_displays_ccd, resolve_gdi_name, restore_displays_ccd, set_active_mode, SavedConfig, +}; unsafe fn open_device() -> Result { let hdev = SetupDiGetClassDevsW( diff --git a/crates/punktfunk-host/src/win_display.rs b/crates/punktfunk-host/src/win_display.rs new file mode 100644 index 0000000..1220d10 --- /dev/null +++ b/crates/punktfunk-host/src/win_display.rs @@ -0,0 +1,391 @@ +//! Backend-neutral Windows display utilities — the CCD (QueryDisplayConfig) + GDI helpers shared by the +//! virtual-display backends (pf-vdisplay, SudoVDA) and the capturers (IDD-push, WGC, DDA): GDI-name +//! resolution, advanced-color (HDR) get/set, active-mode set, and CCD topology isolate/restore. +//! +//! These are display-utility, NOT SudoVDA-specific (a pf-vdisplay monitor's target_id is a real OS target +//! id, so they operate identically), so they live here rather than in the SudoVDA backend — breaking the +//! circular reach-in where the capturers + the pf-vdisplay backend reached into `vdisplay::sudovda` for +//! them, so SudoVDA can eventually be dropped without losing them (audit §9 / Goal 2). The plan's +//! `windows/display_ccd.rs`. Moved verbatim from `vdisplay::sudovda`. + +use std::mem::size_of; + +use windows::core::PCWSTR; +use windows::Win32::Devices::Display::{ + DisplayConfigGetDeviceInfo, DisplayConfigSetDeviceInfo, GetDisplayConfigBufferSizes, + QueryDisplayConfig, SetDisplayConfig, DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO, + DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME, DISPLAYCONFIG_DEVICE_INFO_SET_ADVANCED_COLOR_STATE, + DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO, DISPLAYCONFIG_MODE_INFO, DISPLAYCONFIG_PATH_INFO, + DISPLAYCONFIG_SET_ADVANCED_COLOR_STATE, DISPLAYCONFIG_SOURCE_DEVICE_NAME, QDC_ONLY_ACTIVE_PATHS, + SDC_ALLOW_CHANGES, SDC_APPLY, SDC_FORCE_MODE_ENUMERATION, SDC_SAVE_TO_DATABASE, + SDC_USE_SUPPLIED_DISPLAY_CONFIG, +}; +use windows::Win32::Graphics::Gdi::{ + ChangeDisplaySettingsExW, EnumDisplaySettingsW, CDS_TEST, CDS_UPDATEREGISTRY, DEVMODEW, + DISP_CHANGE_SUCCESSFUL, DM_BITSPERPEL, DM_DISPLAYFREQUENCY, DM_PELSHEIGHT, DM_PELSWIDTH, + ENUM_DISPLAY_SETTINGS_MODE, +}; + +use crate::vdisplay::Mode; + +/// Resolve the `\\.\DisplayN` GDI name for a SudoVDA target id via the CCD API. Returns `None` +/// until the OS activates the target into the desktop topology (needs a real WDDM GPU; on a +/// GPU-less box this stays `None` even though ADD succeeded). +pub(crate) unsafe fn resolve_gdi_name(target_id: u32) -> Option { + let mut np = 0u32; + let mut nm = 0u32; + if GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &mut np, &mut nm).is_err() { + return None; + } + let mut paths = vec![DISPLAYCONFIG_PATH_INFO::default(); np as usize]; + let mut modes = vec![DISPLAYCONFIG_MODE_INFO::default(); nm as usize]; + if QueryDisplayConfig( + QDC_ONLY_ACTIVE_PATHS, + &mut np, + paths.as_mut_ptr(), + &mut nm, + modes.as_mut_ptr(), + None, + ) + .is_err() + { + return None; + } + for p in paths.iter().take(np as usize) { + if p.targetInfo.id == target_id { + let mut src = DISPLAYCONFIG_SOURCE_DEVICE_NAME::default(); + src.header.r#type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME; + src.header.size = size_of::() as u32; + src.header.adapterId = p.sourceInfo.adapterId; + src.header.id = p.sourceInfo.id; + if DisplayConfigGetDeviceInfo(&mut src.header) == 0 { + let name = String::from_utf16_lossy(&src.viewGdiDeviceName); + return Some(name.trim_end_matches('\u{0}').to_string()); + } + } + } + None +} + +/// Toggle the SudoVDA target's advanced-color (HDR) state via the CCD API. Disabling HDR while on the +/// secure (Winlogon) desktop makes it render SDR/composed so DXGI Desktop Duplication can capture it +/// (the HDR fullscreen independent-flip otherwise storms `ACCESS_LOST` → black); re-enable on return so +/// WGC keeps HDR on the normal desktop. Returns true on a successful `DisplayConfigSetDeviceInfo`. +pub(crate) unsafe fn set_advanced_color(target_id: u32, enable: bool) -> bool { + let mut np = 0u32; + let mut nm = 0u32; + if GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &mut np, &mut nm).is_err() { + return false; + } + let mut paths = vec![DISPLAYCONFIG_PATH_INFO::default(); np as usize]; + let mut modes = vec![DISPLAYCONFIG_MODE_INFO::default(); nm as usize]; + if QueryDisplayConfig( + QDC_ONLY_ACTIVE_PATHS, + &mut np, + paths.as_mut_ptr(), + &mut nm, + modes.as_mut_ptr(), + None, + ) + .is_err() + { + return false; + } + for p in paths.iter().take(np as usize) { + if p.targetInfo.id == target_id { + let mut s = DISPLAYCONFIG_SET_ADVANCED_COLOR_STATE::default(); + s.header.r#type = DISPLAYCONFIG_DEVICE_INFO_SET_ADVANCED_COLOR_STATE; + s.header.size = size_of::() as u32; + s.header.adapterId = p.targetInfo.adapterId; + s.header.id = p.targetInfo.id; + s.Anonymous.value = enable as u32; // bit 0 = enableAdvancedColor + let rc = DisplayConfigSetDeviceInfo(&s.header); + tracing::info!( + target_id, + enable, + rc, + "SudoVDA set advanced-color (HDR) state" + ); + return rc == 0; + } + } + tracing::warn!( + target_id, + "set_advanced_color: target not found in active paths" + ); + false +} + +/// Read the SudoVDA target's CURRENT advanced-color (HDR) state via the CCD API — i.e. whether HDR is +/// actually ON for the virtual display right now (e.g. because the user toggled it in Windows display +/// settings). The capture/encode pipeline follows the monitor's real colorspace (WGC → FP16 → NVENC +/// Main10 BT.2020 PQ), so this is the authoritative "is this an HDR session" signal — NOT the +/// handshake-negotiated bit depth. Returns false if the target isn't found / the query fails. +pub(crate) unsafe fn advanced_color_enabled(target_id: u32) -> bool { + let mut np = 0u32; + let mut nm = 0u32; + if GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &mut np, &mut nm).is_err() { + return false; + } + let mut paths = vec![DISPLAYCONFIG_PATH_INFO::default(); np as usize]; + let mut modes = vec![DISPLAYCONFIG_MODE_INFO::default(); nm as usize]; + if QueryDisplayConfig( + QDC_ONLY_ACTIVE_PATHS, + &mut np, + paths.as_mut_ptr(), + &mut nm, + modes.as_mut_ptr(), + None, + ) + .is_err() + { + return false; + } + for p in paths.iter().take(np as usize) { + if p.targetInfo.id == target_id { + let mut info = DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO::default(); + info.header.r#type = DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO; + info.header.size = size_of::() as u32; + info.header.adapterId = p.targetInfo.adapterId; + info.header.id = p.targetInfo.id; + if DisplayConfigGetDeviceInfo(&mut info.header) == 0 { + // value bit 1 = advancedColorEnabled (bit 0 = advancedColorSupported). + return (info.Anonymous.value & 0x2) != 0; + } + return false; + } + } + false +} + +/// Force the freshly-added SudoVDA monitor to the client's exact `WxH@Hz`. The ADD IOCTL only +/// ADVERTISES the mode; Windows otherwise activates an IDD target at a 1280x720 default, so the +/// ACTIVE mode (what DXGI Desktop Duplication captures) must be set explicitly. CDS_TEST first so a +/// mode the driver didn't advertise just leaves the default instead of erroring the session. +// pub(crate) so vdisplay::pf_vdisplay can reuse this backend-neutral CCD/GDI mode-set helper +// (a pf-vdisplay monitor's GDI name is a real OS device name, so it works unchanged). +pub(crate) fn set_active_mode(gdi_name: &str, mode: Mode) { + let wname: Vec = gdi_name.encode_utf16().chain(std::iter::once(0)).collect(); + + // Enumerate the modes the driver actually advertises for this output and pick the best match for + // the requested RESOLUTION: the exact refresh if present, else the highest advertised refresh + // <= requested, else the highest available at that resolution. The SudoVDA ADD IOCTL advertises + // the client mode, but a very high pixel rate (e.g. 5120x1440@240 = 1.77 Gpix/s) can be clamped + // or absent — falling back to a lower refresh AT THE SAME RESOLUTION keeps the client's + // resolution (what the user sees) instead of collapsing to the 1280x720/1920x1080 OS default. + let mut at_res: Vec = Vec::new(); + let mut res_set: std::collections::BTreeSet<(u32, u32)> = std::collections::BTreeSet::new(); + let mut i = 0u32; + loop { + let mut dm = DEVMODEW { + dmSize: size_of::() as u16, + ..Default::default() + }; + let ok = unsafe { + EnumDisplaySettingsW( + PCWSTR(wname.as_ptr()), + ENUM_DISPLAY_SETTINGS_MODE(i), + &mut dm, + ) + } + .as_bool(); + if !ok { + break; + } + i += 1; + res_set.insert((dm.dmPelsWidth, dm.dmPelsHeight)); + if dm.dmPelsWidth == mode.width && dm.dmPelsHeight == mode.height { + at_res.push(dm.dmDisplayFrequency); + } + } + let chosen_hz = if at_res.contains(&mode.refresh_hz) { + mode.refresh_hz + } else if let Some(hz) = at_res + .iter() + .copied() + .filter(|&hz| hz <= mode.refresh_hz) + .max() + { + hz + } else if let Some(hz) = at_res.iter().copied().max() { + hz + } else { + mode.refresh_hz // resolution not advertised at all; attempt anyway (likely -> OS default) + }; + if at_res.is_empty() { + tracing::warn!( + "{gdi_name}: driver advertises no {}x{} mode (top advertised: {:?}); attempting @{} anyway", + mode.width, + mode.height, + res_set.iter().rev().take(8).collect::>(), + mode.refresh_hz + ); + } else if chosen_hz != mode.refresh_hz { + tracing::info!( + "{gdi_name}: {}x{}@{} not advertised; using {}x{}@{} (advertised refreshes here: {:?})", + mode.width, + mode.height, + mode.refresh_hz, + mode.width, + mode.height, + chosen_hz, + at_res + ); + } + + // 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::() as u16, + dmFields: DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY | DM_BITSPERPEL, + dmBitsPerPel: 32, + dmPelsWidth: mode.width, + dmPelsHeight: mode.height, + dmDisplayFrequency: chosen_hz, + ..Default::default() + }; + let test = unsafe { + ChangeDisplaySettingsExW(PCWSTR(wname.as_ptr()), Some(&dm), None, CDS_TEST, None) + }; + if test != DISP_CHANGE_SUCCESSFUL { + tracing::warn!( + result = test.0, + "{gdi_name}: driver rejected {}x{}@{} (mode not advertised?) — leaving OS default", + mode.width, + mode.height, + chosen_hz + ); + return; + } + let apply = unsafe { + ChangeDisplaySettingsExW( + PCWSTR(wname.as_ptr()), + Some(&dm), + None, + CDS_UPDATEREGISTRY, + None, + ) + }; + if apply == DISP_CHANGE_SUCCESSFUL { + tracing::info!( + "{gdi_name}: active mode set to {}x{}@{}", + mode.width, + mode.height, + chosen_hz + ); + } else { + tracing::warn!( + result = apply.0, + "{gdi_name}: failed to apply {}x{}@{}", + mode.width, + mode.height, + chosen_hz + ); + } +} + +/// Saved active display topology, for restoring on teardown. +// pub(crate) so vdisplay::pf_vdisplay's Monitor can hold the same saved-topology type. +pub(crate) type SavedConfig = (Vec, Vec); + +/// `DISPLAYCONFIG_PATH_ACTIVE` (wingdi.h) — the `flags` bit marking a path active. The `windows` crate +/// doesn't export it, so define it here. +const DISPLAYCONFIG_PATH_ACTIVE: u32 = 0x0000_0001; + +/// Robust display isolation via the CCD API. The naive GDI approach (EnumDisplayDevices + +/// ChangeDisplaySettings) MISSES displays on a hybrid box — an iGPU-attached physical monitor isn't +/// flagged `ATTACHED_TO_DESKTOP` in the GDI enum, so it's never detached and the secure desktop / +/// lock screen lands on IT while our virtual output freezes. `QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS)` +/// sees every active path; we deactivate all of them EXCEPT the SudoVDA target's, leaving the virtual +/// display as the sole desktop so ALL content (incl. Winlogon) renders to it. Apollo isolates the same +/// way (CCD). Returns the original active config to restore on teardown. +// pub(crate) so vdisplay::pf_vdisplay can reuse this backend-neutral CCD isolation helper +// (it operates on a real OS target id — a pf-vdisplay monitor's target_id qualifies). +pub(crate) unsafe fn isolate_displays_ccd(keep_target_id: u32) -> Option { + let mut np = 0u32; + let mut nm = 0u32; + if GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &mut np, &mut nm).is_err() { + return None; + } + let mut paths = vec![DISPLAYCONFIG_PATH_INFO::default(); np as usize]; + let mut modes = vec![DISPLAYCONFIG_MODE_INFO::default(); nm as usize]; + if QueryDisplayConfig( + QDC_ONLY_ACTIVE_PATHS, + &mut np, + paths.as_mut_ptr(), + &mut nm, + modes.as_mut_ptr(), + None, + ) + .is_err() + { + return None; + } + paths.truncate(np as usize); + modes.truncate(nm as usize); + let saved = (paths.clone(), modes.clone()); + let mut others = 0u32; + for p in paths.iter_mut() { + if p.targetInfo.id == keep_target_id { + continue; + } + if p.flags & DISPLAYCONFIG_PATH_ACTIVE != 0 { + p.flags &= !DISPLAYCONFIG_PATH_ACTIVE; // mark this path inactive + others += 1; + } + } + if others == 0 { + // The virtual path shows active in the CCD database (from set_active_mode's legacy + // ChangeDisplaySettingsExW), but a legacy mode-set does NOT drive the IddCx adapter's + // EVT_IDD_CX_ADAPTER_COMMIT_MODES — and without COMMIT_MODES the OS never calls + // ASSIGN_SWAPCHAIN, so the driver never receives composed frames. Force an explicit CCD + // SetDisplayConfig commit of the (sole) virtual path so the IddCx path actually activates. + // SDC_FORCE_MODE_ENUMERATION makes the OS re-enumerate + re-commit even though the CCD DB + // already lists the path active. + let rc = SetDisplayConfig( + Some(paths.as_slice()), + Some(modes.as_slice()), + SDC_APPLY + | SDC_USE_SUPPLIED_DISPLAY_CONFIG + | SDC_ALLOW_CHANGES + | SDC_SAVE_TO_DATABASE + | SDC_FORCE_MODE_ENUMERATION, + ); + tracing::info!("display isolate (CCD): forced CCD re-commit of sole virtual path {keep_target_id} rc={rc:#x} (drives IddCx COMMIT_MODES → ASSIGN_SWAPCHAIN)"); + return Some(saved); + } + let rc = SetDisplayConfig( + Some(paths.as_slice()), + Some(modes.as_slice()), + SDC_APPLY + | SDC_USE_SUPPLIED_DISPLAY_CONFIG + | SDC_ALLOW_CHANGES + | SDC_FORCE_MODE_ENUMERATION, + ); + if rc == 0 { + tracing::info!("display isolate (CCD): deactivated {others} other display(s) — SudoVDA target {keep_target_id} is now the sole desktop"); + } else { + tracing::warn!("display isolate (CCD): SetDisplayConfig failed rc={rc:#x} (tried to deactivate {others} path(s))"); + } + Some(saved) +} + +/// Restore the topology saved by [`isolate_displays_ccd`] (teardown, before the virtual output is +/// removed), re-activating the displays we deactivated. +// pub(crate) so vdisplay::pf_vdisplay can reuse this backend-neutral CCD restore helper. +pub(crate) unsafe fn restore_displays_ccd(saved: &SavedConfig) { + let (paths, modes) = saved; + if paths.is_empty() { + return; + } + let rc = SetDisplayConfig( + Some(paths.as_slice()), + Some(modes.as_slice()), + SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_ALLOW_CHANGES, + ); + tracing::info!("display isolate (CCD): restored original topology rc={rc:#x}"); +}