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) },
|
control::IOCTL_UPDATE_MODES => unsafe { update_modes(request) },
|
||||||
// SAFETY: `request` is the framework WDFREQUEST.
|
// SAFETY: `request` is the framework WDFREQUEST.
|
||||||
control::IOCTL_SET_CURSOR_CHANNEL => unsafe { set_cursor_channel(request) },
|
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),
|
_ => 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) {
|
unsafe fn set_frame_channel(request: WDFREQUEST) {
|
||||||
// SAFETY: `request` is the framework WDFREQUEST.
|
// SAFETY: `request` is the framework WDFREQUEST.
|
||||||
let Some(req) = (unsafe { read_input::<control::SetFrameChannelRequest>(request) }) else {
|
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) }
|
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.
|
// SAFETY: `stop` is an event handle value; the worker owns every other resource.
|
||||||
unsafe impl Send for CursorWorker {}
|
unsafe impl Send for CursorWorker {}
|
||||||
|
|
||||||
|
|||||||
@@ -80,6 +80,11 @@ pub struct MonitorObject {
|
|||||||
pub hw_cursor: bool,
|
pub hw_cursor: bool,
|
||||||
/// The live cursor query→publish worker (drops = stop+join) — set by [`set_cursor_channel`].
|
/// The live cursor query→publish worker (drops = stop+join) — set by [`set_cursor_channel`].
|
||||||
pub cursor_worker: Option<crate::cursor_worker::CursorWorker>,
|
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.
|
/// When the entry was created — the watchdog skips still-initializing monitors.
|
||||||
pub created_at: Instant,
|
pub created_at: Instant,
|
||||||
}
|
}
|
||||||
@@ -506,6 +511,9 @@ pub fn resetup_cursor(object: iddcx::IDDCX_MONITOR) {
|
|||||||
let lock = lock_monitors();
|
let lock = lock_monitors();
|
||||||
lock.iter()
|
lock.iter()
|
||||||
.find(|m| m.object == Some(object))
|
.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())
|
.and_then(|m| m.cursor_worker.as_ref())
|
||||||
.map(|w| w.data_event())
|
.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
|
/// 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
|
/// 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
|
/// 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,
|
preserved_stash: None,
|
||||||
hw_cursor,
|
hw_cursor,
|
||||||
cursor_worker: None,
|
cursor_worker: None,
|
||||||
|
cursor_forward_on: true,
|
||||||
created_at: Instant::now(),
|
created_at: Instant::now(),
|
||||||
});
|
});
|
||||||
id
|
id
|
||||||
|
|||||||
Reference in New Issue
Block a user