fix(windows/cursor): record the declare where it actually happens, and retire the stale control handle

Two fixes that together make the reconnect case work.

1. The declare point is send_cursor_channel, not the ADD request's hw_cursor
   flag. The driver declares its IddCx hardware cursor when the cursor CHANNEL
   arrives -- that is what emits 'cursor channel delivered - driver declares the
   hardware cursor'. Recording it at ADD time (and before that in
   capture_virtual_output) left CURSOR_DECLARED false, so the between-session
   clean returned at its first gate and logged nothing, three runs in a row.

2. The cached control handle dies with the recycled WUDFHost. The first run that
   actually reached the recycle produced a session with NO FRAMES, because the
   ADD that followed used a stale handle. Retire it via
   manager::invalidate_cached_device so the next ensure_device reopens against
   the respawned host, and give WUDF a moment to come back.
This commit is contained in:
2026-08-09 00:32:39 +02:00
parent 06086de328
commit 7f141eb9a4
@@ -291,6 +291,11 @@ pub fn clean_cursor_for_next_session(session_wants_declare: bool) -> bool {
return false;
}
if recycle_wudfhost() {
// The cached control handle died with the host process. Retire it so the next
// `ensure_device` reopens against the respawned WUDFHost — without this the ADD that
// follows runs on a stale handle and the session comes up with no frames at all.
super::manager::invalidate_cached_device("cursor clean: recycled the driver host");
std::thread::sleep(std::time::Duration::from_millis(1500));
CURSOR_DECLARED.store(false, Ordering::Relaxed);
tracing::info!(
previously_declared,
@@ -568,6 +573,12 @@ pub unsafe fn send_cursor_channel(
dev: HANDLE,
req: &control::SetCursorChannelRequest,
) -> Result<()> {
// THE declare point. The driver declares its IddCx hardware cursor when this channel arrives —
// not from the ADD request's `hw_cursor` flag, which is why recording the declare there (and,
// before that, in `capture_virtual_output`) left the flag false and the between-session clean
// silently inert. The log line that names this moment is "cursor channel delivered - driver
// declares the hardware cursor".
CURSOR_DECLARED.store(true, std::sync::atomic::Ordering::Relaxed);
let mut none: [u8; 0] = [];
// SAFETY: per this fn's contract `dev` is the live control handle; `bytes_of(req)` borrows the
// caller's request across this synchronous call; no output buffer.
@@ -930,16 +941,7 @@ impl VdisplayDriver for PfVdisplayDriver {
// (DWM stops compositing the pointer into the frame); the capture layer delivers the
// CursorShm section right after its ring. Zero toward older drivers is harmless —
// the host only sets this when the handshake-reported proto is >= 5.
hw_cursor: {
// The ONE place guaranteed to see every declare: the ADD request that asks for it.
// (An earlier attempt recorded this from `capture_virtual_output`, which is not on
// the Windows prep path — the flag stayed false and the between-session clean
// silently never ran.)
if hw_cursor {
CURSOR_DECLARED.store(true, std::sync::atomic::Ordering::Relaxed);
}
hw_cursor as u32
},
hw_cursor: hw_cursor as u32,
};
// SET_RENDER_ADAPTER (opt-in; pf-vdisplay IMPLEMENTS it). Non-fatal on failure: the driver reports
// its real render LUID in the shared header, so the host binds correctly even if this is ignored.