refactor(windows-host): move CCD/HDR display helpers to a neutral module — F1 complete (audit §9)
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) <noreply@anthropic.com>
This commit is contained in:
@@ -2712,7 +2712,7 @@ impl DuplCapturer {
|
|||||||
}
|
}
|
||||||
// The SudoVDA output's GDI name can CHANGE across a secure-desktop topology rebuild —
|
// 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.
|
// 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;
|
self.gdi_name = n;
|
||||||
}
|
}
|
||||||
// Re-sync the capture thread to the CURRENT input desktop on EVERY rebuild — symmetric for
|
// Re-sync the capture thread to the CURRENT input desktop on EVERY rebuild — symmetric for
|
||||||
|
|||||||
@@ -376,13 +376,13 @@ impl IddPushCapturer {
|
|||||||
// settled within 250 ms and would size the ring SDR while the driver composes FP16 → a format
|
// 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).
|
// mismatch → an immediate ring recreate + dropped first frames (audit §5.4).
|
||||||
let enabled_hdr =
|
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 {
|
if enabled_hdr {
|
||||||
// Let the colorspace change settle before the driver composes + we size the ring.
|
// Let the colorspace change settle before the driver composes + we size the ring.
|
||||||
std::thread::sleep(Duration::from_millis(250));
|
std::thread::sleep(Duration::from_millis(250));
|
||||||
}
|
}
|
||||||
let display_hdr =
|
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 {
|
let ring_fmt = if display_hdr {
|
||||||
DXGI_FORMAT_R16G16B16A16_FLOAT
|
DXGI_FORMAT_R16G16B16A16_FLOAT
|
||||||
} else {
|
} else {
|
||||||
@@ -688,7 +688,7 @@ impl IddPushCapturer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.last_acm_poll = Instant::now();
|
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 {
|
if now_hdr == self.display_hdr {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ impl WgcCapturer {
|
|||||||
// The SudoVDA output appears a beat after the display is created — settle-retry like DDA.
|
// The SudoVDA output appears a beat after the display is created — settle-retry like DDA.
|
||||||
let deadline = Instant::now() + Duration::from_millis(2000);
|
let deadline = Instant::now() + Duration::from_millis(2000);
|
||||||
let (adapter, output) = loop {
|
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) {
|
if let Ok(found) = find_output(&n) {
|
||||||
break found;
|
break found;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ mod vdisplay;
|
|||||||
mod wgc_helper;
|
mod wgc_helper;
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
mod win_adapter;
|
mod win_adapter;
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
mod win_display;
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
mod zerocopy;
|
mod zerocopy;
|
||||||
|
|
||||||
|
|||||||
@@ -2661,7 +2661,7 @@ fn virtual_stream_relay(
|
|||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
if bit_depth >= 10 {
|
if bit_depth >= 10 {
|
||||||
unsafe {
|
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.
|
// Let the colorspace change settle before WGC creates its capture item / detects HDR.
|
||||||
std::thread::sleep(std::time::Duration::from_millis(250));
|
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
|
// 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.
|
// overlay/flip issues that drove us to WGC don't apply to the composed Winlogon UI.
|
||||||
let hdr =
|
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
|
dda = None; // reopen to capture the secure desktop
|
||||||
match open_dda(&target, cur_mode.width, cur_mode.height, effective_hz, hdr) {
|
match open_dda(&target, cur_mode.width, cur_mode.height, effective_hz, hdr) {
|
||||||
Ok(mut p) => {
|
Ok(mut p) => {
|
||||||
|
|||||||
@@ -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
|
// 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
|
// 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.
|
// counter is reused too, so the IDD-push stale-ring bail works regardless of which backend is active.
|
||||||
use super::sudovda::{
|
use super::sudovda::{CURRENT_MON_GEN, MON_GEN};
|
||||||
isolate_displays_ccd, resolve_gdi_name, restore_displays_ccd, set_active_mode, SavedConfig,
|
|
||||||
CURRENT_MON_GEN, MON_GEN,
|
|
||||||
};
|
|
||||||
use crate::win_adapter::resolve_render_adapter_luid;
|
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
|
// 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
|
// NOT SudoVDA's `{e5bcc234-…}` — we own this driver, so a private interface GUID signals it and avoids
|
||||||
|
|||||||
@@ -40,21 +40,8 @@ use windows::Win32::Devices::DeviceAndDriverInstallation::{
|
|||||||
SetupDiGetDeviceInterfaceDetailW, DIGCF_DEVICEINTERFACE, DIGCF_PRESENT,
|
SetupDiGetDeviceInterfaceDetailW, DIGCF_DEVICEINTERFACE, DIGCF_PRESENT,
|
||||||
SP_DEVICE_INTERFACE_DATA, SP_DEVICE_INTERFACE_DETAIL_DATA_W,
|
SP_DEVICE_INTERFACE_DATA, SP_DEVICE_INTERFACE_DETAIL_DATA_W,
|
||||||
};
|
};
|
||||||
use windows::Win32::Devices::Display::{
|
// (CCD `Devices::Display` + `Graphics::Gdi` imports moved with the display helpers to `win_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::Foundation::{CloseHandle, HANDLE, LUID};
|
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::{
|
use windows::Win32::Storage::FileSystem::{
|
||||||
CreateFileW, FILE_FLAGS_AND_ATTRIBUTES, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING,
|
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)
|
Ok(returned)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve the `\\.\DisplayN` GDI name for a SudoVDA target id via the CCD API. Returns `None`
|
// The CCD/GDI display helpers (resolve_gdi_name, set_advanced_color, advanced_color_enabled,
|
||||||
/// until the OS activates the target into the desktop topology (needs a real WDDM GPU; on a
|
// set_active_mode, isolate/restore_displays_ccd) + SavedConfig moved to the backend-neutral
|
||||||
/// GPU-less box this stays `None` even though ADD succeeded).
|
// `crate::win_display` (audit §9 / Goal 2). Re-exported so this backend's own callers keep the short
|
||||||
pub(crate) unsafe fn resolve_gdi_name(target_id: u32) -> Option<String> {
|
// names; external callers use `crate::win_display` directly.
|
||||||
let mut np = 0u32;
|
pub(crate) use crate::win_display::{
|
||||||
let mut nm = 0u32;
|
isolate_displays_ccd, resolve_gdi_name, restore_displays_ccd, set_active_mode, SavedConfig,
|
||||||
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::<DISPLAYCONFIG_SOURCE_DEVICE_NAME>() 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::<DISPLAYCONFIG_SET_ADVANCED_COLOR_STATE>() 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::<DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO>() 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<u16> = 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<u32> = 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::<DEVMODEW>() 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::<Vec<_>>(),
|
|
||||||
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::<DEVMODEW>() 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<DISPLAYCONFIG_PATH_INFO>, Vec<DISPLAYCONFIG_MODE_INFO>);
|
|
||||||
|
|
||||||
/// `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<SavedConfig> {
|
|
||||||
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}");
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn open_device() -> Result<HANDLE> {
|
unsafe fn open_device() -> Result<HANDLE> {
|
||||||
let hdev = SetupDiGetClassDevsW(
|
let hdev = SetupDiGetClassDevsW(
|
||||||
|
|||||||
@@ -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<String> {
|
||||||
|
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::<DISPLAYCONFIG_SOURCE_DEVICE_NAME>() 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::<DISPLAYCONFIG_SET_ADVANCED_COLOR_STATE>() 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::<DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO>() 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<u16> = 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<u32> = 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::<DEVMODEW>() 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::<Vec<_>>(),
|
||||||
|
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::<DEVMODEW>() 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<DISPLAYCONFIG_PATH_INFO>, Vec<DISPLAYCONFIG_MODE_INFO>);
|
||||||
|
|
||||||
|
/// `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<SavedConfig> {
|
||||||
|
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}");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user