fix(vdisplay/capture): channel-less sessions composite the pointer on a sticky-declared target
A declared IddCx hardware cursor is IRREVOCABLE for its OS target's life (§8.6), and the sticky exclusion survives monitor REMOVE→ADD because each client gets a STABLE target id — so once any desktop-mode session declared, every later pure-capture session on that target streamed a cursor-less desktop: DWM excluded the pointer, no channel forwarded it, no blend drew it (the exact no-regression gap §8.6's per-session cap gate cannot see). Driver: track every successful SetupHardwareCursor per target (DECLARED_TARGETS — scoped to the WUDFHost's life, exactly the sticky state's scope) and report it in a new AddReply::cursor_excluded tail field (dual-size discipline, both skews degrade cleanly; no proto bump). Host: the flag rides AddedMonitor → Monitor → WinCaptureTarget; a session WITHOUT the cursor channel on a flagged target forces composite mode in the IDD-push capturer — GDI poller + blend for the session's life, pinned on (set_cursor_forward cannot clear it: with no client drawing, un-compositing would erase the pointer entirely). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -331,13 +331,13 @@ fn update_shape(shape: &mut Shape, img: &GetCursorImageReply) {
|
||||
}
|
||||
|
||||
/// One request+reply — x11rb splits errors (the request is `ConnectionError`, `reply()` is
|
||||
/// `ReplyError` which is `From<ConnectionError>`), so `?` collapses both.
|
||||
/// `ReplyError` which is `From<ConnectionError>`), so the request `?` converts into the reply error.
|
||||
fn fetch_cursor_image(conn: &RustConnection) -> Result<GetCursorImageReply, ReplyError> {
|
||||
Ok(conn.xfixes_get_cursor_image()?.reply()?)
|
||||
conn.xfixes_get_cursor_image()?.reply()
|
||||
}
|
||||
|
||||
fn fetch_pointer(conn: &RustConnection, root: Window) -> Result<QueryPointerReply, ReplyError> {
|
||||
Ok(conn.query_pointer(root)?.reply()?)
|
||||
conn.query_pointer(root)?.reply()
|
||||
}
|
||||
|
||||
/// XFixes cursor pixels are packed `0xAARRGGBB` with **premultiplied** alpha (the Xrender / Xcursor
|
||||
|
||||
@@ -399,8 +399,9 @@ pub struct IddPushCapturer {
|
||||
/// from [`cursor_poll::CursorPoller`] (the IddCx query is alpha-only — see cursor_poll.rs),
|
||||
/// and this shm read is the fallback if that poller dies.
|
||||
cursor_shared: Option<cursor::CursorShared>,
|
||||
/// The GDI cursor-shape poller (design §8): the overlay source while alive. `Some` exactly
|
||||
/// when `cursor_shared` is (both ride the negotiated cursor channel + successful delivery).
|
||||
/// The GDI cursor-shape poller (design §8): the overlay source while alive. `Some` when
|
||||
/// `cursor_shared` is (both ride the negotiated cursor channel + successful delivery) — or
|
||||
/// when `composite_forced` (no channel, but the target's sticky declare needs a blend source).
|
||||
cursor_poll: Option<cursor_poll::CursorPoller>,
|
||||
/// Retained delivery sender (`IOCTL_SET_CURSOR_CHANNEL`) for RE-delivery: a driver-side
|
||||
/// monitor re-arrival (match-window re-arrival resize, a sibling session recreating the
|
||||
@@ -410,6 +411,12 @@ pub struct IddPushCapturer {
|
||||
/// The CAPTURE mouse model is active — the HOST composites the pointer into the frame
|
||||
/// (see cursor_blend.rs for why DWM cannot: a declared IddCx hardware cursor is forever).
|
||||
composite_cursor: bool,
|
||||
/// This session never negotiated the cursor channel but its target carries an IRREVOCABLE
|
||||
/// hardware-cursor declare from an earlier session (`WinCaptureTarget::cursor_excluded`,
|
||||
/// §8.6): DWM delivers pointer-free frames and no client draws the cursor, so the ONLY path
|
||||
/// to a visible pointer is compositing here. Pins `composite_cursor` on — nothing may turn
|
||||
/// it off (there is no channel to hand the pointer to).
|
||||
composite_forced: bool,
|
||||
/// The cursor-quad blend pass (lazy; per capture device). `None` after a build failure —
|
||||
/// composite mode then degrades to pointer-less frames (warned once).
|
||||
cursor_blend: Option<cursor_blend::CursorBlendPass>,
|
||||
@@ -1016,10 +1023,23 @@ impl IddPushCapturer {
|
||||
}
|
||||
}
|
||||
});
|
||||
// No channel this session, but the target's sticky declare (an EARLIER session's —
|
||||
// irrevocable, §8.6) keeps DWM's frames pointer-free with no client drawing either:
|
||||
// the only visible pointer is the one composited here, so force composite mode on.
|
||||
let composite_forced = target.cursor_excluded && cursor_sender.is_none();
|
||||
if composite_forced {
|
||||
tracing::info!(
|
||||
target_id = target.target_id,
|
||||
"target carries an irrevocable hardware-cursor declare from an earlier \
|
||||
desktop-mode session and this session has no cursor channel — the host \
|
||||
composites the pointer into frames (forced, for the session's life)"
|
||||
);
|
||||
}
|
||||
// The GDI shape poller rides the SAME gate as the delivered channel: with the driver's
|
||||
// hardware cursor keeping the frame cursor-free, the poller supplies the full-fidelity
|
||||
// shape (masked/monochrome included — the IddCx query can't; see cursor_poll.rs).
|
||||
let cursor_poll = cursor_shared.as_ref().map(|_| {
|
||||
// Forced-composite sessions need it too — it is their only shape/position source.
|
||||
let cursor_poll = (cursor_shared.is_some() || composite_forced).then(|| {
|
||||
// Safety of the CCD call: read-only QueryDisplayConfig over owned locals (same
|
||||
// call CursorShared::create makes) — already inside open_on's unsafe region.
|
||||
let rect = pf_win_display::win_display::source_desktop_rect(target.target_id)
|
||||
@@ -1088,7 +1108,8 @@ impl IddPushCapturer {
|
||||
cursor_shared,
|
||||
cursor_poll,
|
||||
cursor_sender,
|
||||
composite_cursor: false,
|
||||
composite_cursor: composite_forced,
|
||||
composite_forced,
|
||||
cursor_blend: None,
|
||||
cursor_blend_failed: false,
|
||||
blend_scratch: None,
|
||||
@@ -2314,7 +2335,9 @@ impl Capturer for IddPushCapturer {
|
||||
// stays declared for the session's whole life — the only dependable state (there is NO
|
||||
// working un-declare; see cursor_blend.rs) — keeping every frame pointer-free, and the
|
||||
// capturer blends the GDI poller's shape into the frame itself. No driver round-trip.
|
||||
let composite = !on && self.cursor_shared.is_some();
|
||||
// `composite_forced` (a channel-less session on a sticky-declared target) is pinned ON:
|
||||
// with no client drawing, un-compositing would erase the pointer entirely.
|
||||
let composite = (!on && self.cursor_shared.is_some()) || self.composite_forced;
|
||||
if self.composite_cursor != composite {
|
||||
self.composite_cursor = composite;
|
||||
self.last_blend_key = None; // regenerate immediately at the current pointer state
|
||||
|
||||
@@ -79,6 +79,16 @@ pub const fn interface_guid_fields() -> (u32, u16, u16, [u8; 8]) {
|
||||
/// LIVE monitor, so the capture mouse model gets DWM's composited pointer back (full fidelity)
|
||||
/// and the desktop model gets exclusion + forwarding. Nothing existing changed; against a v5
|
||||
/// driver the unknown IOCTL fails and the host logs + keeps the declared-at-ADD behavior.
|
||||
/// v6 tail ext (no bump, the `AddRequest` luminance-tail discipline):
|
||||
/// [`control::AddReply::cursor_excluded`] — the driver reports whether the ADDed monitor's OS
|
||||
/// target already carries a hardware-cursor declare from an earlier session. A declare is
|
||||
/// IRREVOCABLE for the target's life (remote-desktop-sweep §8.6, proven on-glass): DWM never
|
||||
/// composites the software cursor back into that target's frames, and the sticky state survives
|
||||
/// monitor REMOVE→ADD because the host hands every client a STABLE target id. The host uses the
|
||||
/// flag to self-composite the pointer (GDI poller + blend) in sessions that never negotiate the
|
||||
/// cursor channel — without it those sessions are silently cursor-less. Both skews degrade
|
||||
/// cleanly: an old driver writes only the 20-byte reply prefix (host reads `0` = unknown/clean),
|
||||
/// an old host retrieves a 20-byte buffer (driver writes just the prefix).
|
||||
pub const PROTOCOL_VERSION: u32 = 6;
|
||||
|
||||
/// The OLDEST driver protocol this host still drives (v4 is additive over v3 — see the v4 note on
|
||||
@@ -207,8 +217,21 @@ pub mod control {
|
||||
/// `DuplicateHandle`, then [`IOCTL_SET_FRAME_CHANNEL`]). Reported per-ADD, not per-open, so a
|
||||
/// WUDFHost restart between sessions can never leave the host duplicating into a dead process.
|
||||
pub wudf_pid: u32,
|
||||
/// Non-zero = this monitor's OS target already carries an IRREVOCABLE hardware-cursor
|
||||
/// declare from an earlier session (remote-desktop-sweep §8.6): DWM excludes the pointer
|
||||
/// from every frame on this target, forever, and a session without the cursor channel must
|
||||
/// self-composite (GDI poller + blend) or stream a cursor-less desktop. Appended after
|
||||
/// [`ADD_REPLY_LEGACY_SIZE`] under the same dual-size discipline as the `AddRequest`
|
||||
/// luminance tail: an un-upgraded driver writes only the legacy prefix (the host's
|
||||
/// zero-initialized buffer then reads `0` = unknown/clean), an un-upgraded host retrieves a
|
||||
/// legacy-size buffer (the driver writes just the prefix).
|
||||
pub cursor_excluded: u32,
|
||||
}
|
||||
|
||||
/// [`AddReply`]'s size before the `cursor_excluded` tail — the prefix an un-upgraded driver
|
||||
/// writes and an un-upgraded host retrieves (see the field docs).
|
||||
pub const ADD_REPLY_LEGACY_SIZE: usize = 20;
|
||||
|
||||
/// `IOCTL_REMOVE` input.
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Pod, Zeroable, Debug, PartialEq, Eq)]
|
||||
@@ -329,12 +352,14 @@ pub mod control {
|
||||
assert!(offset_of!(AddRequest, hw_cursor) == 36);
|
||||
assert!(size_of::<AddRequest>() == 40);
|
||||
|
||||
assert!(size_of::<AddReply>() == 20);
|
||||
assert!(size_of::<AddReply>() == 24);
|
||||
assert!(offset_of!(AddReply, adapter_luid_low) == 0);
|
||||
assert!(offset_of!(AddReply, adapter_luid_high) == 4);
|
||||
assert!(offset_of!(AddReply, target_id) == 8);
|
||||
assert!(offset_of!(AddReply, resolved_monitor_id) == 12);
|
||||
assert!(offset_of!(AddReply, wudf_pid) == 16);
|
||||
// The cursor-excluded tail starts exactly at the legacy boundary (prefix-compat).
|
||||
assert!(offset_of!(AddReply, cursor_excluded) == ADD_REPLY_LEGACY_SIZE);
|
||||
|
||||
assert!(size_of::<SetFrameChannelRequest>() == 32 + 8 * RING_LEN_USIZE);
|
||||
assert!(offset_of!(SetFrameChannelRequest, target_id) == 0);
|
||||
|
||||
@@ -32,6 +32,11 @@ pub struct WinCaptureTarget {
|
||||
/// duplicates the sealed frame channel's handles INTO (`idd_push::ChannelBroker`). `0` = unknown
|
||||
/// (a pre-v2 pairing can't occur — the version handshake is hard — so this only guards misuse).
|
||||
pub wudf_pid: u32,
|
||||
/// The ADD reply flagged this target as carrying an IRREVOCABLE IddCx hardware-cursor declare
|
||||
/// from an earlier session (remote-desktop-sweep §8.6): DWM excludes the pointer from its
|
||||
/// frames forever, so a session WITHOUT the cursor channel must self-composite (the IDD-push
|
||||
/// capturer's forced-composite gate) or the streamed desktop has no cursor at all.
|
||||
pub cursor_excluded: bool,
|
||||
}
|
||||
|
||||
/// The PyroWave (Windows) zero-copy sharing payload attached to a captured frame: the SECOND plane
|
||||
|
||||
@@ -74,6 +74,12 @@ struct Monitor {
|
||||
/// This monitor was ADDed with the v5 hardware-cursor flag (already driver-proto-gated) —
|
||||
/// preserved across a re-arrival resize so the recreated monitor keeps the cursor channel.
|
||||
hw_cursor: bool,
|
||||
/// The ADD reply flagged this monitor's OS target as carrying an IRREVOCABLE hardware-cursor
|
||||
/// declare from an earlier session (§8.6): DWM excludes the pointer from its frames forever
|
||||
/// (the sticky state survives monitor REMOVE→ADD via the stable per-client target id), so a
|
||||
/// session WITHOUT the cursor channel must self-composite — carried into
|
||||
/// [`WinCaptureTarget`] for the IDD-push capturer's forced-composite gate.
|
||||
cursor_excluded: bool,
|
||||
/// The driver's WUDFHost pid (from the ADD reply) — carried into [`WinCaptureTarget`] so the
|
||||
/// IDD-push capturer knows where to duplicate the sealed frame channel's handles. The SAME
|
||||
/// process for every parallel monitor (one devnode → one WUDFHost hosts all publishers), which
|
||||
@@ -101,6 +107,7 @@ impl Monitor {
|
||||
gdi_name: n,
|
||||
target_id: self.target_id,
|
||||
wudf_pid: self.wudf_pid,
|
||||
cursor_excluded: self.cursor_excluded,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1092,6 +1099,7 @@ impl VirtualDisplayManager {
|
||||
position: (0, 0),
|
||||
gen: self.gen.fetch_add(1, Ordering::Relaxed),
|
||||
hw_cursor,
|
||||
cursor_excluded: added.cursor_excluded,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1315,6 +1323,9 @@ impl VirtualDisplayManager {
|
||||
position: old.position,
|
||||
gen: old.gen,
|
||||
hw_cursor: old.hw_cursor,
|
||||
// Fresh from THIS reply, not `old`: the driver's per-target declare registry is the
|
||||
// ground truth (this session may itself have declared since the original ADD).
|
||||
cursor_excluded: added.cursor_excluded,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,11 @@ pub(crate) struct AddedMonitor {
|
||||
pub luid: LUID,
|
||||
pub wudf_pid: u32,
|
||||
pub resolved_monitor_id: u32,
|
||||
/// The driver reports the OS target already carries an IRREVOCABLE hardware-cursor declare
|
||||
/// from an earlier session (`AddReply::cursor_excluded`, remote-desktop-sweep §8.6): DWM
|
||||
/// excludes the pointer from this target's frames forever, so a session without the cursor
|
||||
/// channel must self-composite (GDI poller + blend) or stream a cursor-less desktop.
|
||||
pub cursor_excluded: bool,
|
||||
}
|
||||
|
||||
/// The backend-specific IOCTL surface — the *only* thing that differs between SudoVDA and pf-vdisplay.
|
||||
|
||||
@@ -605,10 +605,13 @@ impl VdisplayDriver for PfVdisplayDriver {
|
||||
})?;
|
||||
// Fail closed on a short reply — `target_id`/`wudf_pid`/`luid` below feed OpenProcess + the
|
||||
// WUDFHost verification, so don't decode a partially-written (zeroed) reply as authoritative.
|
||||
if (n as usize) < size_of::<control::AddReply>() {
|
||||
// The LEGACY size, not the full struct: an un-upgraded driver writes only the prefix before
|
||||
// the `cursor_excluded` tail; `out` is zero-initialized, so the missing tail reads `0`
|
||||
// (= unknown/clean — exactly what a driver that can't track declares should report).
|
||||
if (n as usize) < control::ADD_REPLY_LEGACY_SIZE {
|
||||
anyhow::bail!(
|
||||
"pf-vdisplay ADD returned {n} bytes, expected {}",
|
||||
size_of::<control::AddReply>()
|
||||
"pf-vdisplay ADD returned {n} bytes, expected at least {}",
|
||||
control::ADD_REPLY_LEGACY_SIZE
|
||||
);
|
||||
}
|
||||
// `pod_read_unaligned` (NOT `from_bytes`): `out` is a stack `[u8; N]` with no guaranteed 4-byte
|
||||
@@ -623,6 +626,7 @@ impl VdisplayDriver for PfVdisplayDriver {
|
||||
target_id = reply.target_id,
|
||||
adapter_luid = %format_args!("{:#x}", luid.LowPart),
|
||||
wudf_pid = reply.wudf_pid,
|
||||
cursor_excluded = reply.cursor_excluded != 0,
|
||||
"pf-vdisplay monitor created {}x{}@{}",
|
||||
mode.width,
|
||||
mode.height,
|
||||
@@ -659,6 +663,7 @@ impl VdisplayDriver for PfVdisplayDriver {
|
||||
luid,
|
||||
wudf_pid: reply.wudf_pid,
|
||||
resolved_monitor_id: reply.resolved_monitor_id,
|
||||
cursor_excluded: reply.cursor_excluded != 0,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -165,9 +165,14 @@ unsafe fn add(request: WDFREQUEST) {
|
||||
// This WUDFHost's pid — where the host duplicates the sealed frame channel's handles INTO
|
||||
// (`ProcessSharingDisabled`: this process is exclusively ours and dies with the device).
|
||||
wudf_pid: std::process::id(),
|
||||
// The target already carries an irrevocable hardware-cursor declare from an earlier
|
||||
// session — a channel-less session must self-composite the pointer (§8.6 gap).
|
||||
cursor_excluded: crate::monitor::target_declared(target_id) as u32,
|
||||
};
|
||||
// Dual-size reply (the `cursor_excluded` tail ext): an un-upgraded host retrieves only the
|
||||
// legacy 20-byte buffer — write the prefix it asked for instead of failing its ADD.
|
||||
// SAFETY: `request` is the framework WDFREQUEST.
|
||||
unsafe { write_output_complete(request, &reply) };
|
||||
unsafe { write_output_prefix_complete(request, &reply, control::ADD_REPLY_LEGACY_SIZE) };
|
||||
}
|
||||
|
||||
/// `IOCTL_SET_FRAME_CHANNEL`: adopt the handle values the host duplicated into this process and stash
|
||||
@@ -356,6 +361,18 @@ unsafe fn read_input<T: Copy>(request: WDFREQUEST) -> Option<T> {
|
||||
/// # Safety
|
||||
/// `request` is the framework `WDFREQUEST`.
|
||||
unsafe fn write_output_complete<T: Copy>(request: WDFREQUEST, value: &T) {
|
||||
// SAFETY: forwarded per this function's own contract; min = the full struct.
|
||||
unsafe { write_output_prefix_complete(request, value, core::mem::size_of::<T>()) }
|
||||
}
|
||||
|
||||
/// [`write_output_complete`] for a reply with an APPENDED tail field (the `AddReply
|
||||
/// cursor_excluded` dual-size discipline): accepts any output buffer of at least `min_size`
|
||||
/// bytes and writes `min(buffer, size_of::<T>())` bytes of the struct — an un-upgraded host
|
||||
/// retrieving only the legacy prefix gets exactly that prefix instead of a failed IOCTL.
|
||||
///
|
||||
/// # Safety
|
||||
/// `request` is the framework `WDFREQUEST`.
|
||||
unsafe fn write_output_prefix_complete<T: Copy>(request: WDFREQUEST, value: &T, min_size: usize) {
|
||||
let mut buf: *mut core::ffi::c_void = core::ptr::null_mut();
|
||||
let mut len: usize = 0;
|
||||
// SAFETY: `request` valid; `buf`/`len` are out-params written by the framework.
|
||||
@@ -363,7 +380,7 @@ unsafe fn write_output_complete<T: Copy>(request: WDFREQUEST, value: &T) {
|
||||
call_unsafe_wdf_function_binding!(
|
||||
WdfRequestRetrieveOutputBuffer,
|
||||
request,
|
||||
core::mem::size_of::<T>(),
|
||||
min_size,
|
||||
&mut buf,
|
||||
&mut len
|
||||
)
|
||||
@@ -372,9 +389,13 @@ unsafe fn write_output_complete<T: Copy>(request: WDFREQUEST, value: &T) {
|
||||
complete(request, st);
|
||||
return;
|
||||
}
|
||||
// SAFETY: `buf` has >= size_of::<T>() writable bytes; T is a Pod control struct.
|
||||
unsafe { buf.cast::<T>().write_unaligned(*value) };
|
||||
complete_info(request, STATUS_SUCCESS, core::mem::size_of::<T>());
|
||||
let take = len.min(core::mem::size_of::<T>());
|
||||
// SAFETY: the framework guarantees `buf` has >= `len` writable bytes and `take <= len`;
|
||||
// T is a Pod control struct, so any byte prefix of it is valid to copy out.
|
||||
unsafe {
|
||||
core::ptr::copy_nonoverlapping((value as *const T).cast::<u8>(), buf.cast::<u8>(), take);
|
||||
}
|
||||
complete_info(request, STATUS_SUCCESS, take);
|
||||
}
|
||||
|
||||
/// Complete a request with just a status (no output).
|
||||
|
||||
@@ -199,6 +199,35 @@ fn cursor_forward_desired(target_id: u32) -> bool {
|
||||
.unwrap_or(true)
|
||||
}
|
||||
|
||||
/// OS target ids on which `IddCxMonitorSetupHardwareCursor` ever SUCCEEDED. A declare is
|
||||
/// IRREVOCABLE for the target's life (no un-declare DDI; empty-caps re-setup is rejected; even a
|
||||
/// successful same-mode re-commit never brings DWM's composited pointer back — all proven
|
||||
/// on-glass, remote-desktop-sweep §8.6) and it survives monitor REMOVE→ADD because the host hands
|
||||
/// each client a STABLE target id. Reported back on every ADD
|
||||
/// ([`AddReply::cursor_excluded`](pf_driver_proto::control::AddReply)) so a session that never
|
||||
/// negotiates the cursor channel knows it must self-composite the pointer instead of streaming a
|
||||
/// cursor-less desktop. Never cleared: the state's true scope is this WUDFHost's life
|
||||
/// (`ProcessSharingDisabled` — the process, and with it the adapter and every sticky declare,
|
||||
/// dies on adapter reset), which is exactly when this static resets too. Bounded: ≤ 16 ids.
|
||||
static DECLARED_TARGETS: Mutex<Vec<u32>> = Mutex::new(Vec::new());
|
||||
|
||||
/// Record a SUCCESSFUL hardware-cursor declare on `target_id` (see [`DECLARED_TARGETS`]).
|
||||
fn mark_declared(target_id: u32) {
|
||||
let mut declared = DECLARED_TARGETS.lock().unwrap_or_else(|e| e.into_inner());
|
||||
if !declared.contains(&target_id) {
|
||||
declared.push(target_id);
|
||||
dbglog!("[pf-vd] cursor: target {target_id} marked hardware-cursor declared (irrevocable)");
|
||||
}
|
||||
}
|
||||
|
||||
/// True if `target_id` ever had a hardware cursor declared (see [`DECLARED_TARGETS`]).
|
||||
pub fn target_declared(target_id: u32) -> bool {
|
||||
DECLARED_TARGETS
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner())
|
||||
.contains(&target_id)
|
||||
}
|
||||
|
||||
/// Record a departing monitor's advertised list for its id ([`MODE_HISTORY`]).
|
||||
fn remember_modes(id: u32, modes: &[Mode]) {
|
||||
let mut hist = MODE_HISTORY.lock().unwrap_or_else(|e| e.into_inner());
|
||||
@@ -432,6 +461,10 @@ pub fn set_cursor_channel(
|
||||
// the dispatch layer is wrong here — the handles are gone, so this is a plain failure.
|
||||
return Ok(()); // adopted (handles consumed); the host detects no-publish and moves on
|
||||
};
|
||||
if declare {
|
||||
// The worker only spawns after `IddCxMonitorSetupHardwareCursor` succeeded.
|
||||
mark_declared(target_id);
|
||||
}
|
||||
let mut lock = lock_monitors();
|
||||
if let Some(m) = lock.iter_mut().find(|m| m.target_id == target_id) {
|
||||
m.cursor_worker = Some(worker);
|
||||
@@ -535,12 +568,18 @@ pub fn resetup_cursor(object: iddcx::IDDCX_MONITOR) {
|
||||
// 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())
|
||||
.and_then(|m| {
|
||||
m.cursor_worker
|
||||
.as_ref()
|
||||
.map(|w| (w.data_event(), m.target_id))
|
||||
})
|
||||
};
|
||||
if let Some(ev) = data_event {
|
||||
if let Some((ev, target_id)) = data_event {
|
||||
let st = crate::cursor_worker::setup_hardware_cursor(object, ev);
|
||||
dbglog!("[pf-vd] cursor: re-setup on swap-chain assign -> {st:#x}");
|
||||
if wdk_iddcx::nt_success(st) {
|
||||
mark_declared(target_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -591,6 +630,9 @@ pub fn set_cursor_forward(target_id: u32, enable: bool) -> bool {
|
||||
// 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}");
|
||||
if wdk_iddcx::nt_success(st) {
|
||||
mark_declared(target_id);
|
||||
}
|
||||
}
|
||||
(false, _, _) => {
|
||||
// There is NO un-declare DDI: re-issuing the setup with empty caps is rejected
|
||||
|
||||
Reference in New Issue
Block a user