fix(vdisplay/windows): exclusive topology gets re-asserted — a verified isolate is not durable
ci / docs-site (push) Successful in 1m8s
ci / web (push) Successful in 1m40s
apple / swift (push) Successful in 5m9s
ci / bench (push) Successful in 6m36s
ci / rust-arm64 (push) Successful in 10m12s
deb / build-publish (push) Successful in 9m32s
decky / build-publish (push) Successful in 34s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 30s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 59s
android / android (push) Canceled after 13m40s
apple / screenshots (push) Canceled after 8m25s
arch / build-publish (push) Canceled after 13m44s
ci / rust (push) Canceled after 13m45s
deb / build-publish-host (push) Canceled after 12m5s
deb / build-publish-client-arm64 (push) Canceled after 7m5s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Canceled after 2s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Canceled after 0s
docker / build-push-arm64cross (push) Canceled after 0s
docker / deploy-docs (push) Canceled after 0s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Canceled after 2m28s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Canceled after 1m57s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Canceled after 2s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Canceled after 1s
windows-host / package (push) Canceled after 13m45s

The CCD isolate verifies "sole active desktop" at commit time and is never
checked again. On a hybrid Intel+NVIDIA box the internal panel is re-activated
moments later (the isolated topology is deliberately not saved to the CCD
database so teardown can restore the user's layout, and the post-isolate
resize/HDR churn — or the panel's own driver, or display-poller software —
re-resolves back to the stored layout). Proven on-glass: seconds after the
verify, CCD showed the panel ACTIVE beside the virtual display while the host
still believed it was exclusive — cursor and windows can land off-stream, and
the lock screen can land on the physical panel.

A group-scoped watchdog now re-queries every PUNKTFUNK_EXCLUSIVE_REASSERT_MS
(default 2 s, 0 disables) while an EXCLUSIVE isolate is live and re-issues the
isolate when a non-managed display crept back, logging who-is-fighting
escalation (3×WARN → 1×ERROR → DEBUG). Gated on a new GroupState.ccd_exclusive
flag so a Primary group's deliberately-lit panels are never "fixed". Cycles
take the state lock via try_lock with a sliced sleep, so teardown's stop+join
under the state lock cannot deadlock and is bounded by ~250 ms.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-26 16:53:19 +02:00
co-authored by Claude Fable 5
parent 6c2c8b6eab
commit 8362d57001
7 changed files with 379 additions and 5 deletions
+55 -3
View File
@@ -43,9 +43,9 @@ use windows::Win32::Devices::Display::{
};
use windows::Win32::Foundation::POINTL;
use windows::Win32::Graphics::Gdi::{
ChangeDisplaySettingsExW, EnumDisplaySettingsW, CDS_TEST, CDS_UPDATEREGISTRY, DEVMODEW,
DISP_CHANGE_FAILED, DISP_CHANGE_SUCCESSFUL, DM_BITSPERPEL, DM_DISPLAYFREQUENCY, DM_PELSHEIGHT,
DM_PELSWIDTH, ENUM_CURRENT_SETTINGS, ENUM_DISPLAY_SETTINGS_MODE,
ChangeDisplaySettingsExW, EnumDisplaySettingsW, CDS_RESET, CDS_TEST, CDS_UPDATEREGISTRY,
DEVMODEW, DISP_CHANGE_FAILED, DISP_CHANGE_SUCCESSFUL, DM_BITSPERPEL, DM_DISPLAYFREQUENCY,
DM_PELSHEIGHT, DM_PELSWIDTH, ENUM_CURRENT_SETTINGS, ENUM_DISPLAY_SETTINGS_MODE,
};
use punktfunk_core::Mode;
@@ -540,6 +540,50 @@ pub unsafe fn sdr_white_level_scale(target_id: u32) -> Option<f32> {
/// mode the driver didn't advertise just leaves the default instead of erroring the session.
// pub 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).
/// Force a REAL mode-set at the output's CURRENT mode — `CDS_RESET` applies even when nothing
/// changed (a plain re-apply of the same mode is treated as a no-op by the OS). This is the
/// presentation-restart hammer for a virtual display DWM silently stopped composing to after an
/// exclusive-eviction topology commit: measured on-glass, the eviction's forced re-commit
/// reassigned the swap-chain and the ring re-attach delivered the driver's stashed frame, but the
/// source's new-frame rate stayed 0 forever — only a real mode-set (the lever bring-up's ADD path
/// relies on via [`set_active_mode`]) makes the OS present again. Same input-desktop retry as the
/// mode-set proper. Returns `true` when the reset applied.
pub fn force_mode_reset(gdi_name: &str) -> bool {
let wname: Vec<u16> = gdi_name.encode_utf16().chain(std::iter::once(0)).collect();
let mut dm = DEVMODEW {
dmSize: size_of::<DEVMODEW>() as u16,
..Default::default()
};
// SAFETY: `wname` is a live NUL-terminated UTF-16 device name and `&mut dm` a live DEVMODEW
// out-param with `dmSize` set; the synchronous query only reads the name and fills `dm`.
let ok =
unsafe { EnumDisplaySettingsW(PCWSTR(wname.as_ptr()), ENUM_CURRENT_SETTINGS, &mut dm) }
.as_bool();
if !ok {
tracing::warn!("{gdi_name}: force_mode_reset — no current mode to re-apply");
return false;
}
// SAFETY: same liveness as the query above; CDS_RESET re-applies the identical mode, the two
// trailing args are null, and the API only reads its inputs. The input-desktop retry mirrors
// `set_active_mode` (a CDS write off the input desktop is refused with DISP_CHANGE_FAILED).
let rc = crate::input_desktop::retry_on_input_desktop(
|rc| *rc == DISP_CHANGE_FAILED,
|| unsafe {
ChangeDisplaySettingsExW(PCWSTR(wname.as_ptr()), Some(&dm), None, CDS_RESET, None)
},
);
if rc != DISP_CHANGE_SUCCESSFUL {
tracing::warn!(
result = rc.0,
"{gdi_name}: force_mode_reset rejected ({})",
disp_change_reason(rc.0)
);
return false;
}
tracing::info!("{gdi_name}: forced same-mode reset applied (presentation restart)");
true
}
pub fn set_active_mode(gdi_name: &str, mode: Mode) {
let wname: Vec<u16> = gdi_name.encode_utf16().chain(std::iter::once(0)).collect();
@@ -1045,6 +1089,14 @@ pub unsafe fn isolate_displays_ccd(keep_target_ids: &[u32]) -> Option<SavedConfi
Some(saved)
}
// (A "gentle" eviction variant — deactivate the stray paths WITHOUT `SDC_FORCE_MODE_ENUMERATION`,
// kept paths supplied verbatim — was tried for the re-assert watchdog and REMOVED: on-glass it
// still bounced the live virtual display's swap-chain (a real topology change drives COMMIT_MODES
// on its own) AND additionally left the OS not presenting to the virtual display — capture
// received one stashed frame and then nothing. Eviction therefore always goes through
// [`isolate_displays_ccd`], whose forced re-commit restarts presentation, and the SESSION pairs it
// with an in-place capture re-attach; see the vdisplay manager's re-assert watchdog.)
/// Build the ESCALATED supplied config for [`isolate_displays_ccd`]: ONLY the paths still flagged
/// ACTIVE (the keep set — the caller already cleared ACTIVE on every doomed path), with the mode
/// table rebuilt to just the entries those paths reference (indexes remapped). Docs-wise the