From 51792536d1ea6faf6df84e5e7d46afa7484c7834 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Thu, 23 Jul 2026 16:50:44 +0200 Subject: [PATCH] fix(pf-win-display): escalate CCD isolate to a keep-only supplied config on 0x57 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Field bug (Steam Deck client, exclusive topology): every isolate retry failed SetDisplayConfig 0x57 with the physical panel left lit, while iPhone sessions worked — their virtual monitor's connected-set already had a sole-display topology persisted, so Windows deactivated the physical on hotplug and the isolate was a no-op re-commit (deactivated 0). The Deck's monitor identity had no such entry, forcing the live-deactivate path: supplying the doomed path (inactive, modes unpinned) plus its orphaned mode entries is rejected 0x57 by some driver/OS validation combos, so the retry loop could never converge — the prior mode-index unpin (5e088af7) was necessary but not sufficient on this box. Attempt 1 keeps the shipped shape; once a survivor is seen, later attempts supply ONLY the keep paths with the mode table rebuilt to just their referenced entries (indexes remapped, clone-shared entries deduped). The same host applies exactly this keep-only shape rc=0 whenever the DB has pre-isolated the virtual display, so the escalated shape is field-validated on the failing machine. The final attempt also drops SDC_FORCE_MODE_ENUMERATION — a real path removal drives COMMIT_MODES on its own. Co-Authored-By: Claude Fable 5 --- crates/pf-win-display/src/win_display.rs | 94 ++++++++++++++++++++++-- 1 file changed, 86 insertions(+), 8 deletions(-) diff --git a/crates/pf-win-display/src/win_display.rs b/crates/pf-win-display/src/win_display.rs index 5850c94c..5763d8ac 100644 --- a/crates/pf-win-display/src/win_display.rs +++ b/crates/pf-win-display/src/win_display.rs @@ -928,14 +928,35 @@ pub unsafe fn isolate_displays_ccd(keep_target_ids: &[u32]) -> Option 0 && attempt >= 2 { + // ESCALATION (attempt 2+): supply ONLY the keep paths. Field-reported (AMD + + // pf-vdisplay): carrying the doomed path in the array — inactive, modes unpinned — + // gets the whole config rejected 0x57 on EVERY retry, so the loop alone never + // converged; the same host applies the keep-only shape rc=0 whenever the topology + // database has already made the virtual display sole. The final attempt also drops + // SDC_FORCE_MODE_ENUMERATION in case the driver rejects it combined with a real + // topology change — an actual path removal drives COMMIT_MODES on its own, so the + // re-commit rationale doesn't need the flag here. + let (kp, km) = keep_only_supplied(&paths, &modes); + let mut esc = SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_ALLOW_CHANGES; + if attempt < 4 { + esc |= SDC_FORCE_MODE_ENUMERATION; + } + tracing::info!( + "display isolate (CCD): escalating to a keep-only supplied config (attempt {attempt}/4, paths {}→{}, modes {}→{})", + paths.len(), kp.len(), modes.len(), km.len() + ); + SetDisplayConfig(Some(kp.as_slice()), Some(km.as_slice()), esc) + } else { + let mut flags = SDC_APPLY + | SDC_USE_SUPPLIED_DISPLAY_CONFIG + | SDC_ALLOW_CHANGES + | SDC_FORCE_MODE_ENUMERATION; + if others == 0 { + flags |= SDC_SAVE_TO_DATABASE; + } + SetDisplayConfig(Some(paths.as_slice()), Some(modes.as_slice()), flags) + }; // A failed apply must be VISIBLE even when the verification below passes vacuously (nothing // else was active to deactivate — the lid-closed laptop case, where the success INFO used to // swallow rc=0x5): the re-commit above is load-bearing (COMMIT_MODES → ASSIGN_SWAPCHAIN), @@ -962,6 +983,63 @@ pub unsafe fn isolate_displays_ccd(keep_target_ids: &[u32]) -> Option (Vec, Vec) { + let mut out_paths = Vec::new(); + let mut out_modes = Vec::new(); + // old mode index → new. Shared entries dedup through here: a clone-style pair references ONE + // source mode, and the docs require each source/target mode to appear in the table only once. + let mut remap = std::collections::HashMap::new(); + for p in paths { + if p.flags & DISPLAYCONFIG_PATH_ACTIVE == 0 { + continue; + } + let mut q = *p; + q.sourceInfo.Anonymous.modeInfoIdx = remap_mode_idx( + q.sourceInfo.Anonymous.modeInfoIdx, + modes, + &mut out_modes, + &mut remap, + ); + q.targetInfo.Anonymous.modeInfoIdx = remap_mode_idx( + q.targetInfo.Anonymous.modeInfoIdx, + modes, + &mut out_modes, + &mut remap, + ); + out_paths.push(q); + } + (out_paths, out_modes) +} + +/// Move `modes[old]` into `out` (once — `remap` dedups) and return its new index. INVALID and +/// out-of-range indexes stay INVALID — `SDC_ALLOW_CHANGES` lets best-mode logic fill the gap. +fn remap_mode_idx( + old: u32, + modes: &[DISPLAYCONFIG_MODE_INFO], + out: &mut Vec, + remap: &mut std::collections::HashMap, +) -> u32 { + if old == DISPLAYCONFIG_PATH_MODE_IDX_INVALID { + return old; + } + let Some(m) = modes.get(old as usize) else { + return DISPLAYCONFIG_PATH_MODE_IDX_INVALID; + }; + *remap.entry(old).or_insert_with(|| { + out.push(*m); + (out.len() - 1) as u32 + }) +} + /// The desktop-space rectangle `(x, y, w, h)` of `target_id`'s SOURCE — where this display's /// region lives in the desktop coordinate space. `None` while the target isn't an active path. /// Used by the IDD-push compose kick to dirty THE TARGET display: with parallel displays the