fix(host): activate lid-closed pf-vdisplay targets — explicit CCD path-commit fallback
ci / docs-site (push) Successful in 1m3s
ci / web (push) Successful in 1m3s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 10s
decky / build-publish (push) Successful in 17s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 8s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 8s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 8s
apple / swift (push) Successful in 5m2s
ci / bench (push) Successful in 8m3s
docker / deploy-docs (push) Successful in 26s
windows-host / package (push) Successful in 9m3s
arch / build-publish (push) Successful in 14m56s
android / android (push) Successful in 16m48s
ci / rust (push) Successful in 18m17s
deb / build-publish (push) Successful in 18m31s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 18m9s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 18m32s
apple / screenshots (push) Successful in 20m40s
ci / docs-site (push) Successful in 1m3s
ci / web (push) Successful in 1m3s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 10s
decky / build-publish (push) Successful in 17s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 8s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 8s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 8s
apple / swift (push) Successful in 5m2s
ci / bench (push) Successful in 8m3s
docker / deploy-docs (push) Successful in 26s
windows-host / package (push) Successful in 9m3s
arch / build-publish (push) Successful in 14m56s
android / android (push) Successful in 16m48s
ci / rust (push) Successful in 18m17s
deb / build-publish (push) Successful in 18m31s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 18m9s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 18m32s
apple / screenshots (push) Successful in 20m40s
A lid-closed laptop defeats both activation stages for a fresh IddCx target: the clamshell lid policy suppresses the new-monitor auto-activate, and the SDC_TOPOLOGY_EXTEND preset returns success without committing a path for the IDD — so every session retry burned ~10s in resolve_target_gdi and the stream died with "not yet an active display path" after 8 attempts (RDP/Parsec still work there: neither needs a NEW console display path). Field report: Windows laptop host, Intel iGPU, lid closed, v0.10.1. New activate_target_path() (win_display.rs) is the supplied-config apply Windows' own display Settings uses to turn a monitor on, which doesn't consult the lid policy: QueryDisplayConfig(QDC_ALL_PATHS), keep every active path verbatim, append the target's inactive path with a source no active display is using (never a clone), both mode idxs DISPLAYCONFIG_PATH_MODE_IDX_INVALID, then SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_ALLOW_CHANGES | SDC_SAVE_TO_DATABASE — SAVE_TO_DATABASE so the next same-identity ADD auto-activates from the persistence DB and skips the ladder. Wired as the THIRD stage of resolve_target_gdi; the on-glass-validated auto-activate → force-EXTEND order is unchanged. Also sweep stale "SudoVDA" out of logs/errors and current-behavior doc comments (the backend was removed; pf-vdisplay is the sole one): the capture error now names pf-vdisplay, the HDR toggle logs virtual-display, and the not-active warns list the exhausted fallbacks. Genuinely historical SudoVDA notes stay. cargo check + clippy green on the Windows box; on-glass lid-closed repro still owed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -405,7 +405,8 @@ pub fn capture_virtual_output(
|
|||||||
) -> Result<Box<dyn Capturer>> {
|
) -> Result<Box<dyn Capturer>> {
|
||||||
let target = vout.win_capture.clone().ok_or_else(|| {
|
let target = vout.win_capture.clone().ok_or_else(|| {
|
||||||
anyhow::anyhow!(
|
anyhow::anyhow!(
|
||||||
"SudoVDA target not yet an active display (needs a WDDM GPU to activate it)"
|
"pf-vdisplay target not yet an active display path (activation failed — see the \
|
||||||
|
virtual-display warnings above)"
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
let pref = vout.preferred_mode;
|
let pref = vout.preferred_mode;
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ pub struct WinCaptureTarget {
|
|||||||
pub adapter_luid: i64,
|
pub adapter_luid: i64,
|
||||||
/// The output's GDI device name, e.g. `\\.\DISPLAY3`. Can CHANGE across a secure-desktop switch.
|
/// The output's GDI device name, e.g. `\\.\DISPLAY3`. Can CHANGE across a secure-desktop switch.
|
||||||
pub gdi_name: String,
|
pub gdi_name: String,
|
||||||
/// Stable SudoVDA target id — re-resolved to the current GDI name on every recovery.
|
/// Stable virtual-display (IddCx) target id — re-resolved to the current GDI name on every recovery.
|
||||||
pub target_id: u32,
|
pub target_id: u32,
|
||||||
/// The pf-vdisplay driver's WUDFHost pid (from the ADD reply) — the process the IDD-push capturer
|
/// The pf-vdisplay driver's WUDFHost pid (from the ADD reply) — the process the IDD-push capturer
|
||||||
/// duplicates the sealed frame channel's handles INTO (`idd_push::ChannelBroker`). `0` = unknown
|
/// duplicates the sealed frame channel's handles INTO (`idd_push::ChannelBroker`). `0` = unknown
|
||||||
|
|||||||
@@ -3122,7 +3122,7 @@ fn resolve_compositor(
|
|||||||
dedicated_launch: bool,
|
dedicated_launch: bool,
|
||||||
) -> Result<crate::vdisplay::Compositor> {
|
) -> Result<crate::vdisplay::Compositor> {
|
||||||
use crate::vdisplay::Compositor;
|
use crate::vdisplay::Compositor;
|
||||||
// Windows has a single virtual-display backend (SudoVDA); vdisplay::open ignores the compositor
|
// Windows has a single virtual-display backend (pf-vdisplay); vdisplay::open ignores the compositor
|
||||||
// arg there, so short-circuit the Linux session-detection state machine with a placeholder.
|
// arg there, so short-circuit the Linux session-detection state machine with a placeholder.
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ pub struct VirtualOutput {
|
|||||||
/// gamescope outputs are created at the exact size, so this just confirms it; **Mutter sizes
|
/// gamescope outputs are created at the exact size, so this just confirms it; **Mutter sizes
|
||||||
/// its virtual monitor FROM the negotiation**, so here it's what makes the client's mode real.
|
/// its virtual monitor FROM the negotiation**, so here it's what makes the client's mode real.
|
||||||
pub preferred_mode: Option<(u32, u32, u32)>,
|
pub preferred_mode: Option<(u32, u32, u32)>,
|
||||||
/// Windows capture identity (DXGI adapter LUID + GDI output name) for the SudoVDA backend —
|
/// Windows capture identity (DXGI adapter LUID + GDI output name) for the pf-vdisplay backend —
|
||||||
/// what [`crate::capture::capture_virtual_output`] needs to duplicate the right output.
|
/// what [`crate::capture::capture_virtual_output`] needs to duplicate the right output.
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub win_capture: Option<crate::capture::dxgi::WinCaptureTarget>,
|
pub win_capture: Option<crate::capture::dxgi::WinCaptureTarget>,
|
||||||
|
|||||||
@@ -822,6 +822,13 @@ impl VirtualDisplayManager {
|
|||||||
/// catch. If that ever shows up, widen the gate to also fire when the IDD target's source is shared
|
/// catch. If that ever shows up, widen the gate to also fire when the IDD target's source is shared
|
||||||
/// with another active path (a `target_is_cloned` helper) — needs on-laptop validation first.
|
/// with another active path (a `target_is_cloned` helper) — needs on-laptop validation first.
|
||||||
///
|
///
|
||||||
|
/// LAST RESORT — explicit path activation: a lid-closed laptop (field report, Intel iGPU) defeats
|
||||||
|
/// BOTH stages above — the clamshell lid policy suppresses the new-monitor auto-activation, and
|
||||||
|
/// the `SDC_TOPOLOGY_EXTEND` preset "succeeds" without committing a path for the IDD — so the
|
||||||
|
/// target stays connected-but-inactive for the session's whole retry budget. `activate_target_path`
|
||||||
|
/// commits the target's path directly (supplied-config apply, the same thing display Settings
|
||||||
|
/// does), which doesn't consult the lid policy at all.
|
||||||
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// Runs the CCD (QueryDisplayConfig / SetDisplayConfig) FFI; call under the `state` lock.
|
/// Runs the CCD (QueryDisplayConfig / SetDisplayConfig) FFI; call under the `state` lock.
|
||||||
unsafe fn resolve_target_gdi(&self, target_id: u32) -> Option<String> {
|
unsafe fn resolve_target_gdi(&self, target_id: u32) -> Option<String> {
|
||||||
@@ -842,6 +849,17 @@ impl VirtualDisplayManager {
|
|||||||
return Some(n);
|
return Some(n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// SAFETY: `activate_target_path` runs the CCD query/apply FFI with owned local buffers; the
|
||||||
|
// `Copy` target id is passed by value, under the `state` lock — the sole topology mutator.
|
||||||
|
if unsafe { crate::win_display::activate_target_path(target_id) } {
|
||||||
|
for _ in 0..15 {
|
||||||
|
thread::sleep(Duration::from_millis(200));
|
||||||
|
// SAFETY: as the resolve loops above.
|
||||||
|
if let Some(n) = unsafe { resolve_gdi_name(target_id) } {
|
||||||
|
return Some(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -886,7 +904,11 @@ impl VirtualDisplayManager {
|
|||||||
let gdi_name = unsafe { self.resolve_target_gdi(added.target_id) };
|
let gdi_name = unsafe { self.resolve_target_gdi(added.target_id) };
|
||||||
match &gdi_name {
|
match &gdi_name {
|
||||||
Some(n) => {
|
Some(n) => {
|
||||||
tracing::info!(backend = self.driver.name(), "target {} -> {n}", added.target_id);
|
tracing::info!(
|
||||||
|
backend = self.driver.name(),
|
||||||
|
"target {} -> {n}",
|
||||||
|
added.target_id
|
||||||
|
);
|
||||||
// ADD only advertises the mode; force it active so DXGI captures the requested size.
|
// ADD only advertises the mode; force it active so DXGI captures the requested size.
|
||||||
set_active_mode(n, mode);
|
set_active_mode(n, mode);
|
||||||
// Apply the display-management topology (Stage 2, GROUP-scoped since Stage W2).
|
// Apply the display-management topology (Stage 2, GROUP-scoped since Stage W2).
|
||||||
@@ -977,8 +999,7 @@ impl VirtualDisplayManager {
|
|||||||
}
|
}
|
||||||
// SAFETY: `set_virtual_primary_ccd` takes the `Copy` target id by value and returns
|
// SAFETY: `set_virtual_primary_ccd` takes the `Copy` target id by value and returns
|
||||||
// an owned `SavedConfig` (no borrowed memory crosses), under the `state` lock.
|
// an owned `SavedConfig` (no borrowed memory crosses), under the `state` lock.
|
||||||
inner.group.ccd_saved =
|
inner.group.ccd_saved = unsafe { set_virtual_primary_ccd(added.target_id) };
|
||||||
unsafe { set_virtual_primary_ccd(added.target_id) };
|
|
||||||
}
|
}
|
||||||
Topology::Primary => {
|
Topology::Primary => {
|
||||||
// A sibling already holds primary (the group's designated member) — the new
|
// A sibling already holds primary (the group's designated member) — the new
|
||||||
@@ -996,7 +1017,8 @@ impl VirtualDisplayManager {
|
|||||||
thread::sleep(Duration::from_millis(1500)); // let the topology settle before capture opens
|
thread::sleep(Duration::from_millis(1500)); // let the topology settle before capture opens
|
||||||
}
|
}
|
||||||
None => tracing::warn!(
|
None => tracing::warn!(
|
||||||
"virtual-display target {} not yet an active display path (needs a WDDM GPU to activate)",
|
"virtual-display target {} not yet an active display path (auto-activate, EXTEND \
|
||||||
|
preset and explicit path activation all failed — GPU-less box?)",
|
||||||
added.target_id
|
added.target_id
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
@@ -1081,7 +1103,11 @@ impl VirtualDisplayManager {
|
|||||||
let gdi_name = unsafe { self.resolve_target_gdi(added.target_id) };
|
let gdi_name = unsafe { self.resolve_target_gdi(added.target_id) };
|
||||||
match &gdi_name {
|
match &gdi_name {
|
||||||
Some(n) => {
|
Some(n) => {
|
||||||
tracing::info!(backend = self.driver.name(), "re-arrival target {} -> {n}", added.target_id);
|
tracing::info!(
|
||||||
|
backend = self.driver.name(),
|
||||||
|
"re-arrival target {} -> {n}",
|
||||||
|
added.target_id
|
||||||
|
);
|
||||||
// ADD only advertises the mode; force it active so DXGI/IDD captures the new size.
|
// ADD only advertises the mode; force it active so DXGI/IDD captures the new size.
|
||||||
set_active_mode(n, mode);
|
set_active_mode(n, mode);
|
||||||
// 4. Re-isolate the composited set with the NEW target replacing the old — preserving
|
// 4. Re-isolate the composited set with the NEW target replacing the old — preserving
|
||||||
@@ -1091,7 +1117,8 @@ impl VirtualDisplayManager {
|
|||||||
thread::sleep(Duration::from_millis(1500)); // let the topology settle before capture reopens
|
thread::sleep(Duration::from_millis(1500)); // let the topology settle before capture reopens
|
||||||
}
|
}
|
||||||
None => tracing::warn!(
|
None => tracing::warn!(
|
||||||
"re-arrival target {} not yet an active display path (needs a WDDM GPU to activate)",
|
"re-arrival target {} not yet an active display path (auto-activate, EXTEND preset \
|
||||||
|
and explicit path activation all failed — GPU-less box?)",
|
||||||
added.target_id
|
added.target_id
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ use windows::Win32::Devices::Display::{
|
|||||||
DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME, DISPLAYCONFIG_DEVICE_INFO_SET_ADVANCED_COLOR_STATE,
|
DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME, DISPLAYCONFIG_DEVICE_INFO_SET_ADVANCED_COLOR_STATE,
|
||||||
DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO, DISPLAYCONFIG_MODE_INFO,
|
DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO, DISPLAYCONFIG_MODE_INFO,
|
||||||
DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE, DISPLAYCONFIG_PATH_INFO,
|
DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE, DISPLAYCONFIG_PATH_INFO,
|
||||||
DISPLAYCONFIG_SET_ADVANCED_COLOR_STATE, DISPLAYCONFIG_SOURCE_DEVICE_NAME,
|
DISPLAYCONFIG_SET_ADVANCED_COLOR_STATE, DISPLAYCONFIG_SOURCE_DEVICE_NAME, QDC_ALL_PATHS,
|
||||||
QDC_ONLY_ACTIVE_PATHS, SDC_ALLOW_CHANGES, SDC_APPLY, SDC_FORCE_MODE_ENUMERATION,
|
QDC_ONLY_ACTIVE_PATHS, SDC_ALLOW_CHANGES, SDC_APPLY, SDC_FORCE_MODE_ENUMERATION,
|
||||||
SDC_SAVE_TO_DATABASE, SDC_TOPOLOGY_EXTEND, SDC_USE_SUPPLIED_DISPLAY_CONFIG,
|
SDC_SAVE_TO_DATABASE, SDC_TOPOLOGY_EXTEND, SDC_USE_SUPPLIED_DISPLAY_CONFIG,
|
||||||
};
|
};
|
||||||
@@ -56,7 +56,107 @@ pub(crate) unsafe fn force_extend_topology() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve the `\\.\DisplayN` GDI name for a SudoVDA target id via the CCD API. Returns `None`
|
/// EXPLICITLY activate `target_id` into its own display path — the last-resort fallback when neither
|
||||||
|
/// the OS auto-activate nor the EXTEND topology preset lights a freshly-ADDed IDD target. Observed on
|
||||||
|
/// a lid-closed laptop (field report, Intel iGPU): the clamshell lid policy makes Windows skip the
|
||||||
|
/// new-monitor auto-activation AND the `SDC_TOPOLOGY_EXTEND` preset returns success without ever
|
||||||
|
/// committing a path for the IDD, so the target sits connected-but-inactive for the whole retry
|
||||||
|
/// budget (RDP/Parsec don't need a new console display path, which is why they still work there).
|
||||||
|
///
|
||||||
|
/// This is the supplied-config apply Windows' own display Settings uses to turn a monitor on: query
|
||||||
|
/// ALL paths, keep every currently-active path verbatim, and append the target's inactive path with a
|
||||||
|
/// source not already driving another display — mode indices invalidated so `SDC_ALLOW_CHANGES` lets
|
||||||
|
/// the OS pick modes for the new path. Returns `true` when the apply reports success; the caller
|
||||||
|
/// still re-polls [`resolve_gdi_name`] to confirm the path actually committed.
|
||||||
|
pub(crate) unsafe fn activate_target_path(target_id: u32) -> bool {
|
||||||
|
let mut np = 0u32;
|
||||||
|
let mut nm = 0u32;
|
||||||
|
if GetDisplayConfigBufferSizes(QDC_ALL_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_ALL_PATHS,
|
||||||
|
&mut np,
|
||||||
|
paths.as_mut_ptr(),
|
||||||
|
&mut nm,
|
||||||
|
modes.as_mut_ptr(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
paths.truncate(np as usize);
|
||||||
|
modes.truncate(nm as usize);
|
||||||
|
|
||||||
|
// Keep the currently-active paths verbatim — their mode indices stay valid because the queried
|
||||||
|
// modes array is passed through unchanged.
|
||||||
|
let mut supplied: Vec<DISPLAYCONFIG_PATH_INFO> = paths
|
||||||
|
.iter()
|
||||||
|
.filter(|p| p.flags & DISPLAYCONFIG_PATH_ACTIVE != 0)
|
||||||
|
.copied()
|
||||||
|
.collect();
|
||||||
|
if supplied.iter().any(|p| p.targetInfo.id == target_id) {
|
||||||
|
return true; // already active — we raced the OS auto-activate
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pick an inactive path for our target whose SOURCE isn't already driving an active display on
|
||||||
|
// the same adapter (sharing one would make the IDD a clone — exactly the no-frames state this
|
||||||
|
// fallback exists to break out of).
|
||||||
|
let Some(cand) = paths.iter().find(|p| {
|
||||||
|
p.targetInfo.id == target_id
|
||||||
|
&& p.flags & DISPLAYCONFIG_PATH_ACTIVE == 0
|
||||||
|
&& !supplied.iter().any(|a| {
|
||||||
|
(
|
||||||
|
a.sourceInfo.adapterId.LowPart,
|
||||||
|
a.sourceInfo.adapterId.HighPart,
|
||||||
|
a.sourceInfo.id,
|
||||||
|
) == (
|
||||||
|
p.sourceInfo.adapterId.LowPart,
|
||||||
|
p.sourceInfo.adapterId.HighPart,
|
||||||
|
p.sourceInfo.id,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}) else {
|
||||||
|
tracing::warn!(
|
||||||
|
target_id,
|
||||||
|
"explicit path activation: no inactive path with a free source for this target"
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let mut new_path = *cand;
|
||||||
|
new_path.flags |= DISPLAYCONFIG_PATH_ACTIVE;
|
||||||
|
new_path.sourceInfo.Anonymous.modeInfoIdx = DISPLAYCONFIG_PATH_MODE_IDX_INVALID;
|
||||||
|
new_path.targetInfo.Anonymous.modeInfoIdx = DISPLAYCONFIG_PATH_MODE_IDX_INVALID;
|
||||||
|
supplied.push(new_path);
|
||||||
|
|
||||||
|
// SAVE_TO_DATABASE so Windows remembers the arrangement — the next same-identity ADD (the driver
|
||||||
|
// reuses the slot's EDID serial/ConnectorIndex) then auto-activates from the persistence DB and
|
||||||
|
// skips this whole fallback ladder.
|
||||||
|
let rc = SetDisplayConfig(
|
||||||
|
Some(supplied.as_slice()),
|
||||||
|
Some(modes.as_slice()),
|
||||||
|
SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_ALLOW_CHANGES | SDC_SAVE_TO_DATABASE,
|
||||||
|
);
|
||||||
|
if rc == 0 {
|
||||||
|
tracing::info!(
|
||||||
|
target_id,
|
||||||
|
"explicit path activation: supplied-config apply succeeded (target committed alongside {} active path(s))",
|
||||||
|
supplied.len() - 1
|
||||||
|
);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
tracing::warn!(
|
||||||
|
target_id,
|
||||||
|
"explicit path activation: SetDisplayConfig rc={rc:#x}"
|
||||||
|
);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolve the `\\.\DisplayN` GDI name for a virtual-display 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
|
/// 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).
|
/// GPU-less box this stays `None` even though ADD succeeded).
|
||||||
pub(crate) unsafe fn resolve_gdi_name(target_id: u32) -> Option<String> {
|
pub(crate) unsafe fn resolve_gdi_name(target_id: u32) -> Option<String> {
|
||||||
@@ -116,7 +216,7 @@ pub(crate) unsafe fn active_resolution(target_id: u32) -> Option<(u32, u32)> {
|
|||||||
Some((dm.dmPelsWidth, dm.dmPelsHeight))
|
Some((dm.dmPelsWidth, dm.dmPelsHeight))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Toggle the SudoVDA target's advanced-color (HDR) state via the CCD API. Disabling HDR while on the
|
/// Toggle the virtual-display 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
|
/// 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
|
/// (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`.
|
/// WGC keeps HDR on the normal desktop. Returns true on a successful `DisplayConfigSetDeviceInfo`.
|
||||||
@@ -153,7 +253,7 @@ pub(crate) unsafe fn set_advanced_color(target_id: u32, enable: bool) -> bool {
|
|||||||
target_id,
|
target_id,
|
||||||
enable,
|
enable,
|
||||||
rc,
|
rc,
|
||||||
"SudoVDA set advanced-color (HDR) state"
|
"virtual-display set advanced-color (HDR) state"
|
||||||
);
|
);
|
||||||
return rc == 0;
|
return rc == 0;
|
||||||
}
|
}
|
||||||
@@ -165,7 +265,7 @@ pub(crate) unsafe fn set_advanced_color(target_id: u32, enable: bool) -> bool {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read the SudoVDA target's CURRENT advanced-color (HDR) state via the CCD API — i.e. whether HDR is
|
/// Read the virtual-display 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
|
/// 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
|
/// 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
|
/// Main10 BT.2020 PQ), so this is the authoritative "is this an HDR session" signal — NOT the
|
||||||
@@ -210,7 +310,7 @@ pub(crate) unsafe fn advanced_color_enabled(target_id: u32) -> Option<bool> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Force the freshly-added SudoVDA monitor to the client's exact `WxH@Hz`. The ADD IOCTL only
|
/// Force the freshly-added virtual 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
|
/// 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
|
/// 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.
|
/// mode the driver didn't advertise just leaves the default instead of erroring the session.
|
||||||
@@ -221,7 +321,7 @@ pub(crate) fn set_active_mode(gdi_name: &str, mode: Mode) {
|
|||||||
|
|
||||||
// Enumerate the modes the driver actually advertises for this output and pick the best match for
|
// 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
|
// 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
|
// <= requested, else the highest available at that resolution. The pf-vdisplay ADD IOCTL advertises
|
||||||
// the client mode, but a very high pixel rate (e.g. 5120x1440@240 = 1.77 Gpix/s) can be clamped
|
// 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
|
// 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.
|
// resolution (what the user sees) instead of collapsing to the 1280x720/1920x1080 OS default.
|
||||||
@@ -358,6 +458,11 @@ pub(crate) type SavedConfig = (Vec<DISPLAYCONFIG_PATH_INFO>, Vec<DISPLAYCONFIG_M
|
|||||||
/// doesn't export it, so define it here.
|
/// doesn't export it, so define it here.
|
||||||
const DISPLAYCONFIG_PATH_ACTIVE: u32 = 0x0000_0001;
|
const DISPLAYCONFIG_PATH_ACTIVE: u32 = 0x0000_0001;
|
||||||
|
|
||||||
|
/// `DISPLAYCONFIG_PATH_MODE_IDX_INVALID` (wingdi.h) — "no mode pinned" for a path's source/target
|
||||||
|
/// mode index; with `SDC_ALLOW_CHANGES` the OS picks the modes itself. Not exported by the `windows`
|
||||||
|
/// crate either.
|
||||||
|
const DISPLAYCONFIG_PATH_MODE_IDX_INVALID: u32 = 0xffff_ffff;
|
||||||
|
|
||||||
/// Query the current ACTIVE display config (paths + modes), truncated to the real counts. `None` on
|
/// Query the current ACTIVE display config (paths + modes), truncated to the real counts. `None` on
|
||||||
/// API failure. Shared by [`isolate_displays_ccd`] (snapshot + per-attempt re-query) and
|
/// API failure. Shared by [`isolate_displays_ccd`] (snapshot + per-attempt re-query) and
|
||||||
/// [`count_other_active`].
|
/// [`count_other_active`].
|
||||||
|
|||||||
Reference in New Issue
Block a user