fix(windows/cursor): gate the between-session clean on STREAMING sessions, not keep-alive
Measured on .173: the guard never fired in the one case it exists for. After a
desktop-mode session disconnects its monitor LINGERS, so `no_live_displays()`
— which counted keep-alive slots as held — refused the restart on exactly the
reconnect that needed it, and the capture session went back to
`cursor_excluded=true` and forced compositing.
Only a streaming session (SlotState::Active) can be damaged by the restart. A
lingering/pinned monitor has no session attached and a reconnect preempts and
recreates it anyway ("a reused IddCx swap-chain is dead"), so the restart
destroys nothing that was going to survive.
This commit is contained in:
@@ -421,23 +421,31 @@ pub fn hw_cursor_capable() -> bool {
|
||||
m.driver_proto.load(Ordering::Relaxed) >= 5
|
||||
}
|
||||
|
||||
/// Does this host currently hold NO virtual display at all (no live session, no keep-alive slot)?
|
||||
/// Is NO session currently streaming to a virtual display?
|
||||
///
|
||||
/// The safety question for anything that tears the adapter down — notably
|
||||
/// [`crate::driver::clean_cursor_for_next_session`], whose `pnputil /restart-device` would take
|
||||
/// every monitor on the adapter with it. Deliberately counts KEPT slots as well as streaming ones:
|
||||
/// a keep-alive monitor belongs to a client that is expected back, and destroying it under them is
|
||||
/// exactly the kind of cross-session damage the cursor clean-up exists to avoid causing.
|
||||
pub fn no_live_displays() -> bool {
|
||||
/// [`crate::driver::clean_cursor_for_next_session`], whose `pnputil /restart-device` takes every
|
||||
/// monitor on the adapter with it. Only [`SlotState::Active`] counts: that is a session with live
|
||||
/// references, and destroying its monitor mid-stream is the cross-session damage worth refusing.
|
||||
///
|
||||
/// `Lingering`/`Pinned` slots deliberately do NOT count. They are keep-alive monitors with no
|
||||
/// session attached, and a reconnect **already** preempts and recreates them — "a reused IddCx
|
||||
/// swap-chain is dead" (see [`SlotState::Pinned`]) — so a device restart destroys nothing the
|
||||
/// reconnect was not going to destroy anyway. Counting them was too conservative to be useful: the
|
||||
/// case this gate exists for is exactly *disconnect from a desktop session, reconnect in capture
|
||||
/// mode*, and the disconnected session's monitor is lingering at precisely that moment, so the
|
||||
/// clean-up could never fire when it was most wanted (observed on `.173`, 2026-08-08).
|
||||
pub fn no_active_sessions() -> bool {
|
||||
match VDM.get() {
|
||||
// Before the first backend open there is nothing to protect.
|
||||
None => true,
|
||||
Some(m) => m
|
||||
Some(m) => !m
|
||||
.state
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner())
|
||||
.slots
|
||||
.is_empty(),
|
||||
.values()
|
||||
.any(|s| matches!(s, SlotState::Active { .. })),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -212,11 +212,12 @@ pub fn clean_cursor_for_next_session(session_wants_declare: bool) -> bool {
|
||||
if session_wants_declare || !CURSOR_DECLARED.load(Ordering::Relaxed) {
|
||||
return false;
|
||||
}
|
||||
// Conservative: a KEPT keep-alive slot counts as held, so a client that is expected back does
|
||||
// not lose its monitor to another client's cursor preference. The cost of being wrong here is
|
||||
// someone else's session dying; the cost of skipping is one session compositing its own
|
||||
// pointer, which is merely the old behaviour.
|
||||
if !super::manager::no_live_displays() {
|
||||
// Refuse only while another session is STREAMING — a keep-alive (lingering/pinned) monitor has
|
||||
// no session attached and a reconnect recreates it regardless, so restarting the adapter costs
|
||||
// it nothing. Gating on keep-alive too made this dead code in the one case it exists for: after
|
||||
// a desktop session disconnects its monitor LINGERS, which is exactly when the next
|
||||
// capture-mode connect needs the declare gone (observed on .173).
|
||||
if !super::manager::no_active_sessions() {
|
||||
tracing::info!(
|
||||
"cursor: this session wants no hardware cursor and an earlier one declared, but a display is still held (live or keep-alive) — skipping the adapter restart, so the pointer stays host-composited for this session"
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user