diff --git a/crates/punktfunk-host/src/capture/desktop_watch.rs b/crates/punktfunk-host/src/capture/desktop_watch.rs index 62590d7..c1de933 100644 --- a/crates/punktfunk-host/src/capture/desktop_watch.rs +++ b/crates/punktfunk-host/src/capture/desktop_watch.rs @@ -101,7 +101,7 @@ impl Drop for DesktopWatcher { /// True if the current input desktop is "Winlogon" (the secure desktop). Best-effort: if the desktop /// can't be opened or named, report not-secure (the safe default — keep WGC/normal capture). -unsafe fn is_secure_desktop() -> bool { +pub(crate) unsafe fn is_secure_desktop() -> bool { let desk = match OpenInputDesktop( DESKTOP_CONTROL_FLAGS(0), false, diff --git a/crates/punktfunk-host/src/capture/dxgi.rs b/crates/punktfunk-host/src/capture/dxgi.rs index 1bb4548..a6ac372 100644 --- a/crates/punktfunk-host/src/capture/dxgi.rs +++ b/crates/punktfunk-host/src/capture/dxgi.rs @@ -1401,12 +1401,18 @@ impl DuplCapturer { if let Some(n) = crate::vdisplay::sudovda::resolve_gdi_name(self.target_id) { self.gdi_name = n; } - attach_input_desktop(); - // Re-route the secure (Winlogon) desktop back to the virtual output. The lock/UAC switch can - // re-attach a physical monitor so the secure desktop lands there and our virtual output goes - // perpetually ACCESS_LOST; re-isolating (as a fresh session's `create` does) is the delta that - // makes in-session recovery work like a reconnect. Idempotent/cheap when already isolated. - crate::vdisplay::sudovda::reassert_isolation(&self.gdi_name); + // Heavy topology work — re-attach the thread to the input desktop AND re-isolate the virtual + // output — ONLY on the actual secure (Winlogon) desktop. Entering it can re-attach a physical + // monitor and move the secure desktop off our virtual output, which re-isolation fixes. But on + // the NORMAL desktop this is just routine ACCESS_LOST churn (HDR overlay / MPO / periodic IddCx + // invalidation), and re-isolating there is a DISPLAY-TOPOLOGY CHANGE that itself invalidates the + // freshly-rebuilt duplication → a self-feeding ACCESS_LOST storm (200 rebuilds/session observed). + // Apollo isolates once at startup and its recovery just re-duplicates; match that off the secure + // desktop. (The lock screen / post-login are NOT Winlogon, so they take this light path too.) + if crate::capture::desktop_watch::is_secure_desktop() { + attach_input_desktop(); + crate::vdisplay::sudovda::reassert_isolation(&self.gdi_name); + } let (dev, ctx, out, dupl) = reopen_duplication(&self.gdi_name)?; // Err → caller repeats + retries // (The born-lost guard is now the capture-acquire at the end: we adopt, then grab the current