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:
@@ -99,6 +99,8 @@ pub unsafe fn dispatch(request: WDFREQUEST, ioctl_code: u32) {
|
||||
control::IOCTL_UPDATE_MODES => unsafe { update_modes(request) },
|
||||
// SAFETY: `request` is the framework WDFREQUEST.
|
||||
control::IOCTL_SET_CURSOR_CHANNEL => unsafe { set_cursor_channel(request) },
|
||||
// SAFETY: `request` is the framework WDFREQUEST.
|
||||
control::IOCTL_SET_CURSOR_FORWARD => unsafe { set_cursor_forward(request) },
|
||||
_ => complete(request, STATUS_NOT_FOUND),
|
||||
}
|
||||
}
|
||||
@@ -206,6 +208,28 @@ unsafe fn set_cursor_channel(request: WDFREQUEST) {
|
||||
}
|
||||
}
|
||||
|
||||
/// `IOCTL_SET_CURSOR_FORWARD` (v6): the mid-stream cursor-render flip — (un)declare a LIVE
|
||||
/// monitor's hardware cursor as the client's mouse model demands.
|
||||
///
|
||||
/// # Safety
|
||||
/// `request` is the framework `WDFREQUEST`.
|
||||
unsafe fn set_cursor_forward(request: WDFREQUEST) {
|
||||
// SAFETY: `request` is the framework WDFREQUEST.
|
||||
let Some(req) = (unsafe { read_input::<control::SetCursorForwardRequest>(request) }) else {
|
||||
complete(request, STATUS_INVALID_PARAMETER);
|
||||
return;
|
||||
};
|
||||
if crate::monitor::set_cursor_forward(req.target_id, req.enable != 0) {
|
||||
complete(request, STATUS_SUCCESS);
|
||||
} else {
|
||||
dbglog!(
|
||||
"[pf-vd] SET_CURSOR_FORWARD: no cursor-channel monitor with target_id {} — rejecting",
|
||||
req.target_id
|
||||
);
|
||||
complete(request, STATUS_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn set_frame_channel(request: WDFREQUEST) {
|
||||
// SAFETY: `request` is the framework WDFREQUEST.
|
||||
let Some(req) = (unsafe { read_input::<control::SetFrameChannelRequest>(request) }) else {
|
||||
|
||||
@@ -115,6 +115,32 @@ 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) }
|
||||
}
|
||||
|
||||
// SAFETY: `stop` is an event handle value; the worker owns every other resource.
|
||||
unsafe impl Send for CursorWorker {}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user