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
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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user