fix(windows/cursor-flip): flag + forced same-mode re-commit — the working un-declare

The empty-caps un-declare is REJECTED by IddCx (STATUS_INVALID_PARAMETER,
on-glass driver 9.9.0722.1407) — there is no un-declare DDI. The
composite flip now works with the only lever the OS gives us: every
mode COMMIT reverts the path to the software cursor, and the driver
skips its per-commit re-declare while cursor_forward_on is off. So:

- driver: disable stores the flag only (no DDI call); enable still
  declares immediately against the live worker's event.
- host capturer: after a successful disable flip, force a same-mode
  re-commit (win_display::force_mode_reenumeration) — DWM composites
  the pointer from the very next commit; the swap-chain flap is the
  class the driver's preserved-publisher machinery already rides out.
- state now survives re-arrivals end-to-end: the capturer caches the
  APPLIED state (cleared on every channel (re)delivery, which
  re-created driver entries need — their flag defaults to declared),
  and the stream loop re-applies every tick instead of edge-gating
  (steady-state cost: one Option compare).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-22 23:43:23 +02:00
co-authored by Claude Opus 4.8
parent 79d30fde64
commit b50e4767de
4 changed files with 74 additions and 45 deletions
@@ -115,31 +115,12 @@ pub fn setup_hardware_cursor(monitor: iddcx::IDDCX_MONITOR, data_event: isize) -
unsafe { wdk_iddcx::IddCxMonitorSetupHardwareCursor(monitor, &setup) }
}
/// UN-declare the hardware cursor (the mid-stream cursor-render flip, proto v6): re-issue the
/// setup with EMPTY caps no alpha, no XOR, zero max dims — asking the OS to stop routing the
/// pointer to this driver and fall back to the software cursor (composited into the desktop
/// image, which is exactly what the capture mouse model wants in-frame). The IddCx DDI has no
/// documented un-setup; empty caps is the candidate mechanism — the returned status is logged
/// by the caller, and if the OS refuses it the fallback is [`crate::monitor::resetup_cursor`]'s
/// skip on the next mode commit (the OS reverts to software cursor at every commit by default).
/// The data event stays the worker's live handle; a query while un-declared just reports
/// nothing new (the host ignores the shm in composite mode).
pub fn unsetup_hardware_cursor(monitor: iddcx::IDDCX_MONITOR, data_event: isize) -> i32 {
let caps = iddcx::IDDCX_CURSOR_CAPS {
Size: core::mem::size_of::<iddcx::IDDCX_CURSOR_CAPS>() as u32,
ColorXorCursorSupport: iddcx::IDDCX_XOR_CURSOR_SUPPORT::IDDCX_XOR_CURSOR_SUPPORT_NONE,
MaxX: 0,
MaxY: 0,
AlphaCursorSupport: 0,
};
let setup = iddcx::IDARG_IN_SETUP_HWCURSOR {
CursorInfo: caps,
hNewCursorDataAvailable: data_event as *mut core::ffi::c_void,
};
// SAFETY: same contract as [`setup_hardware_cursor`] — live monitor, `setup` outlives the
// call, live event handle.
unsafe { wdk_iddcx::IddCxMonitorSetupHardwareCursor(monitor, &setup) }
}
// NOTE: there is NO un-declare path. Re-issuing `IddCxMonitorSetupHardwareCursor` with empty
// caps (no alpha, XOR NONE, zero max dims) is rejected `STATUS_INVALID_PARAMETER` — observed
// on-glass (26100, driver 9.9.0722.1407). The composite flip therefore works by FLAG + MODE
// RE-COMMIT: `monitor::set_cursor_forward(false)` stores the flag, the host forces a same-mode
// re-commit, and the OS's per-commit software-cursor default sticks because
// `monitor::resetup_cursor` skips the flagged monitor.
// SAFETY: `stop` is an event handle value; the worker owns every other resource.
unsafe impl Send for CursorWorker {}
@@ -550,18 +550,25 @@ pub fn set_cursor_forward(target_id: u32, enable: bool) -> bool {
let Some((object, worker_ev)) = found else {
return false; // no hw-cursor monitor with this target at all
};
match (object, worker_ev) {
(Some(object), Some(ev)) => {
let st = if enable {
crate::cursor_worker::setup_hardware_cursor(object, ev)
} else {
crate::cursor_worker::unsetup_hardware_cursor(object, ev)
};
dbglog!("[pf-vd] cursor: forward flip enable={enable} -> {st:#x}");
match (enable, object, worker_ev) {
(true, Some(object), Some(ev)) => {
// Enable declares immediately against the live worker's event (works any time).
let st = crate::cursor_worker::setup_hardware_cursor(object, ev);
dbglog!("[pf-vd] cursor: forward flip enable=1 (declare) -> {st:#x}");
}
(false, _, _) => {
// There is NO un-declare DDI: re-issuing the setup with empty caps is rejected
// INVALID_PARAMETER (observed on-glass, 26100). The stored flag alone steers — the
// HOST forces a same-mode re-commit, whose software-cursor default then STICKS
// because [`resetup_cursor`] skips this monitor while the flag is off.
dbglog!(
"[pf-vd] cursor: forward flip enable=0 stored — awaiting the host's mode \
re-commit (software cursor from then on)"
);
}
_ => dbglog!(
"[pf-vd] cursor: forward flip enable={enable} stored (no live worker — applies at \
the next channel delivery)"
"[pf-vd] cursor: forward flip enable=1 stored (no live worker — applies at the \
next channel delivery)"
),
}
true