fix(windows/vdisplay): cursor-render state sticky per TARGET across monitor generations

On-glass: 're-setup on swap-chain assign -> 0x0' fired AFTER 'enable=0
stored' — duplicate/successor monitor entries (re-arrival churn) kept
their default-true flag and re-declared the hardware cursor right after
the composite flip's re-commit, undoing it.

The desired state now lives in a per-target static
(CURSOR_FORWARD_DESIRED): every arrival inherits it (no generation can
resurrect a declare the session turned off), and the flip stamps EVERY
live entry matching the target, declaring against whichever has the
live worker.

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 b50e4767de
commit 89cfef429e
@@ -181,6 +181,24 @@ fn union_modes(into: &mut Vec<Mode>, from: &[Mode]) {
/// [`MODE_LIST_CAP`] modes.
static MODE_HISTORY: Mutex<Vec<(u32, Vec<Mode>)>> = Mutex::new(Vec::new());
/// Desired cursor-render state per OS TARGET id (`IOCTL_SET_CURSOR_FORWARD`, the mid-stream
/// flip): `false` = composite (do NOT declare the hardware cursor). Kept OUTSIDE the monitor
/// entries because those are churned freely (match-window re-arrival resizes, sibling-session
/// slot re-creates) — a fresh entry inherits this at ARRIVAL, so no generation can resurrect a
/// declare the session turned off. Absent target = default `true` (declare at delivery).
static CURSOR_FORWARD_DESIRED: Mutex<Vec<(u32, bool)>> = Mutex::new(Vec::new());
/// The desired cursor-forward state for `target_id` (default `true`).
fn cursor_forward_desired(target_id: u32) -> bool {
CURSOR_FORWARD_DESIRED
.lock()
.unwrap_or_else(|e| e.into_inner())
.iter()
.find(|(t, _)| *t == target_id)
.map(|(_, on)| *on)
.unwrap_or(true)
}
/// 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());
@@ -534,18 +552,36 @@ pub fn resetup_cursor(object: iddcx::IDDCX_MONITOR) {
/// 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 {
// The flip is STATE, not an edge on one monitor generation: set the flag on the matching
// hw-cursor entry even when it has no live worker yet (a re-arrived monitor before the
// host's channel re-delivery) — the flag then steers the next delivery/re-setup. Only a
// present worker gets the immediate declare/un-declare DDI call.
// The flip is STATE, not an edge on one monitor generation: persist the desired state
// per TARGET (fresh entries inherit it at arrival) and stamp EVERY live entry matching
// the target — duplicate generations coexist during re-arrival churn, and stamping only
// the first let a sibling's still-true flag re-declare on its next swap-chain assign
// (observed on-glass: `re-setup -> 0x0` AFTER `enable=0 stored`). Only a present worker
// gets the immediate declare DDI call.
{
let mut desired = CURSOR_FORWARD_DESIRED
.lock()
.unwrap_or_else(|e| e.into_inner());
match desired.iter_mut().find(|(t, _)| *t == target_id) {
Some(slot) => slot.1 = enable,
None => desired.push((target_id, enable)),
}
}
let found = {
let mut lock = lock_monitors();
lock.iter_mut()
.find(|m| m.target_id == target_id && m.hw_cursor)
.map(|m| {
let mut any = false;
let mut declare_on: Option<(Option<iddcx::IDDCX_MONITOR>, Option<isize>)> = None;
for m in lock
.iter_mut()
.filter(|m| m.target_id == target_id && m.hw_cursor)
{
any = true;
m.cursor_forward_on = enable;
(m.object, m.cursor_worker.as_ref().map(|w| w.data_event()))
})
if m.cursor_worker.is_some() {
declare_on = Some((m.object, m.cursor_worker.as_ref().map(|w| w.data_event())));
}
}
any.then_some(declare_on.unwrap_or((None, None)))
};
let Some((object, worker_ev)) = found else {
return false; // no hw-cursor monitor with this target at all
@@ -759,11 +795,15 @@ pub fn create_monitor(
arrival_out.OsAdapterLuid.HighPart,
);
{
let inherit = cursor_forward_desired(target_id);
let mut lock = lock_monitors();
if let Some(m) = lock.iter_mut().find(|m| m.id == id) {
m.target_id = target_id;
m.adapter_luid_low = luid_low;
m.adapter_luid_high = luid_high;
// The mid-stream render flip survives entry churn: the fresh entry starts at the
// session's DESIRED state, not the declare-at-delivery default.
m.cursor_forward_on = inherit;
}
}
Some((id, target_id, luid_low, luid_high))