feat(windows/vdisplay): the driver leg of the mid-stream cursor-render flip

Belongs to 493f4fae (which shipped only the crates/ side — this
packaging/ half was left unstaged, so the deployed driver silently
lacked the IOCTL and every flip died NOT_FOUND at dispatch):
IOCTL_SET_CURSOR_FORWARD dispatch + monitor::set_cursor_forward
(cursor_forward_on flag, resetup gate) + cursor_worker::
unsetup_hardware_cursor (empty-caps un-declare experiment).

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 d86073dc6a
commit 030a779391
3 changed files with 88 additions and 0 deletions
@@ -80,6 +80,11 @@ pub struct MonitorObject {
pub hw_cursor: bool,
/// The live cursor query→publish worker (drops = stop+join) — set by [`set_cursor_channel`].
pub cursor_worker: Option<crate::cursor_worker::CursorWorker>,
/// The mid-stream cursor-render flip (`IOCTL_SET_CURSOR_FORWARD`, proto v6): while `false`
/// the hardware cursor stays UN-declared (DWM composites the pointer — the capture mouse
/// model) and [`resetup_cursor`] skips its per-mode-commit re-declare. Starts `true` — the
/// channel delivery declares.
pub cursor_forward_on: bool,
/// When the entry was created — the watchdog skips still-initializing monitors.
pub created_at: Instant,
}
@@ -506,6 +511,9 @@ pub fn resetup_cursor(object: iddcx::IDDCX_MONITOR) {
let lock = lock_monitors();
lock.iter()
.find(|m| m.object == Some(object))
// A composite-mode monitor (`cursor_forward_on == false`, the mid-stream flip) must
// NOT re-declare — the commit's software-cursor default is exactly what it wants.
.filter(|m| m.cursor_forward_on)
.and_then(|m| m.cursor_worker.as_ref())
.map(|w| w.data_event())
};
@@ -515,6 +523,35 @@ pub fn resetup_cursor(object: iddcx::IDDCX_MONITOR) {
}
}
/// The mid-stream cursor-render flip (`IOCTL_SET_CURSOR_FORWARD`, proto v6): `enable` declares
/// the hardware cursor again (DWM excludes the pointer; per-mode-commit re-declares resume);
/// disable re-issues the setup with EMPTY caps — asking the OS to route the cursor back to the
/// software path (composited frames) — and stops the per-commit re-declare. `false` when no
/// monitor with `target_id` has a live cursor worker (channel never delivered / gone) — the
/// host logs and keeps its current behavior. Flag update UNDER the lock; DDI call OUTSIDE it
/// (it can re-enter the mode callbacks, which take this lock).
pub fn set_cursor_forward(target_id: u32, enable: bool) -> bool {
let found = {
let mut lock = lock_monitors();
lock.iter_mut()
.find(|m| m.target_id == target_id && m.cursor_worker.is_some())
.map(|m| {
m.cursor_forward_on = enable;
(m.object, m.cursor_worker.as_ref().map(|w| w.data_event()))
})
};
let Some((Some(object), Some(ev))) = found else {
return false;
};
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}");
true
}
/// Install a swap-chain processor on the monitor whose handle matches, returning any PREVIOUS processor
/// for the caller to drop OUTSIDE the lock. Dropping a processor RAII-joins its worker thread, so it must
/// never happen while holding `MONITOR_MODES` (the worker would block the whole control plane / risk a
@@ -617,6 +654,7 @@ pub fn create_monitor(
preserved_stash: None,
hw_cursor,
cursor_worker: None,
cursor_forward_on: true,
created_at: Instant::now(),
});
id