From 9f07715b469a481849ee35ffa33d6dab34356c7d Mon Sep 17 00:00:00 2001 From: srdusr <99972264+srdusr@users.noreply.github.com> Date: Tue, 7 Oct 2025 22:20:00 +0200 Subject: Fix intermittent cursor ghosting when crossing between monitors Live report: the real cursor sometimes leaves a brief ghost behind right after moving between monitors. The bare-metal render loop already forces a full repaint (ages = [0, 0]) on a workspace switch or any window move/ resize/open/close/restack, both added earlier for the same underlying gap: the damage tracker's own element diffing doesn't always catch a vacated region on its own. Neither reset noticed the pointer leaving one monitor for another - no window moved, no workspace changed - so that head's own vacated cursor-sized region was left entirely to the tracker's diffing, intermittently. Adds UdevState::last_cursor_head, compared each frame the same way the other two resets are; only the head the pointer just left gets forced back to ages = [0, 0] (the newly-entered head draws a genuinely new element there and diffs correctly on its own). --- crates/wayland/src/udev/mod.rs | 22 ++++++++++++++++++++++ crates/wayland/src/udev/platform.rs | 1 + crates/wayland/src/udev/render.rs | 18 ++++++++++++++++++ 3 files changed, 41 insertions(+) (limited to 'crates/wayland') diff --git a/crates/wayland/src/udev/mod.rs b/crates/wayland/src/udev/mod.rs index b0a2639..22df0e1 100644 --- a/crates/wayland/src/udev/mod.rs +++ b/crates/wayland/src/udev/mod.rs @@ -278,6 +278,28 @@ pub(crate) struct UdevState { /// `None` before the first frame, same reasoning as `last_rendered_ /// workspace` above (renders fully regardless). pub(crate) last_rendered_layout: Option, + /// Index into `heads` of whichever head the pointer was actually drawn + /// on last frame (`None` before the first frame, or if it was on none + /// of them). Compared each frame in `render_udev_frame`, same pattern + /// as `last_rendered_workspace`/`last_rendered_layout` above, so that + /// when the pointer crosses from one monitor to another the head it + /// just *left* gets its own `ages` forced back to `[0, 0]` too. + /// + /// Needed because neither of those two other resets notices this + /// transition at all: no window moved, and the workspace didn't + /// change, so both stay silent while the cursor sprite simply drops + /// out of that head's `custom_elements` list from one frame to the + /// next. That leaves the departing head's vacated cursor-sized region + /// resting entirely on `OutputDamageTracker`'s own element diffing -- + /// already documented, for the same "an element disappeared" shape of + /// bug on a window vacating part of the screen, as not reliable on its + /// own (see `layout_signature`'s own doc comment above `render_udev_ + /// frame`). Reported live as an intermittent cursor "ghost" briefly + /// left behind right after moving the pointer between monitors -- + /// intermittent because it depends on whatever else that head's own + /// diffing already had queued that frame, exactly like the window + /// case did. + pub(crate) last_cursor_head: Option, /// Set only when `SRDWM_GPU=1` and `gpu::probe` succeeds on this /// hardware - see that function's own doc comment for exactly what /// it does and does not do yet. `None` (the default, every session diff --git a/crates/wayland/src/udev/platform.rs b/crates/wayland/src/udev/platform.rs index b09bd4b..bf82b32 100644 --- a/crates/wayland/src/udev/platform.rs +++ b/crates/wayland/src/udev/platform.rs @@ -170,6 +170,7 @@ impl UdevPlatform { disabled_connectors: std::collections::HashSet::new(), last_rendered_workspace: None, last_rendered_layout: None, + last_cursor_head: None, gpu: gpu_context, }; diff --git a/crates/wayland/src/udev/render.rs b/crates/wayland/src/udev/render.rs index dc84a2e..f850a19 100644 --- a/crates/wayland/src/udev/render.rs +++ b/crates/wayland/src/udev/render.rs @@ -167,6 +167,24 @@ impl CompState { head.ages = [0, 0]; } } + // See `UdevState::last_cursor_head`'s own doc comment: neither reset + // above notices the pointer crossing from one monitor to another, + // so that head's own vacated cursor-sized region was left entirely + // to `OutputDamageTracker`'s own diffing - reported live as an + // intermittent cursor "ghost" briefly left behind on the monitor + // just departed. Only the head being *left* needs the forced + // repaint; the one being entered draws a genuinely new element + // there this frame regardless, which diffs correctly on its own. + let current_cursor_head = udev.heads.iter().position(|h| { + let local = (udev.pointer_pos.x as i32 - h.location.x, udev.pointer_pos.y as i32 - h.location.y); + local.0 >= 0 && local.1 >= 0 && local.0 < h.size.0 && local.1 < h.size.1 + }); + if !locked && udev.last_cursor_head != current_cursor_head { + if let Some(old) = udev.last_cursor_head.and_then(|i| udev.heads.get_mut(i)) { + old.ages = [0, 0]; + } + udev.last_cursor_head = current_cursor_head; + } // A head whose page-flip event never arrives (kernel-dropped, or a // DRM event this driver never sends for reasons this backend has no // visibility into) would otherwise sit in `flip_pending` forever: -- cgit v1.2.3