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:
@@ -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
|
||||
/// 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
|
||||
/// Runs the CCD (QueryDisplayConfig / SetDisplayConfig) FFI; call under the `state` lock.
|
||||
unsafe fn resolve_target_gdi(&self, target_id: u32) -> Option<String> {
|
||||
@@ -842,6 +849,17 @@ impl VirtualDisplayManager {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -886,7 +904,11 @@ impl VirtualDisplayManager {
|
||||
let gdi_name = unsafe { self.resolve_target_gdi(added.target_id) };
|
||||
match &gdi_name {
|
||||
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.
|
||||
set_active_mode(n, mode);
|
||||
// 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
|
||||
// an owned `SavedConfig` (no borrowed memory crosses), under the `state` lock.
|
||||
inner.group.ccd_saved =
|
||||
unsafe { set_virtual_primary_ccd(added.target_id) };
|
||||
inner.group.ccd_saved = unsafe { set_virtual_primary_ccd(added.target_id) };
|
||||
}
|
||||
Topology::Primary => {
|
||||
// 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
|
||||
}
|
||||
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
|
||||
),
|
||||
}
|
||||
@@ -1081,7 +1103,11 @@ impl VirtualDisplayManager {
|
||||
let gdi_name = unsafe { self.resolve_target_gdi(added.target_id) };
|
||||
match &gdi_name {
|
||||
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.
|
||||
set_active_mode(n, mode);
|
||||
// 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
|
||||
}
|
||||
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
|
||||
),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user