diff options
| author | srdusr <[email protected]> | 2025-10-03 22:59:00 +0200 |
|---|---|---|
| committer | srdusr <[email protected]> | 2025-10-03 22:59:00 +0200 |
| commit | 4eb36873d4ca4b0faf0e54dbeab8f5f6450dd704 (patch) | |
| tree | 657c89f7398f98b0305b9cd69889c232608f3027 /crates/wayland/src/udev/mod.rs | |
| parent | d7031dd060ec12e6de334518aa75122c65794047 (diff) | |
| download | srdwm-4eb36873d4ca4b0faf0e54dbeab8f5f6450dd704.tar.gz srdwm-4eb36873d4ca4b0faf0e54dbeab8f5f6450dd704.zip | |
Make the secondary-cursor sprite opt-in and expire stale entries
Live report: a second cursor appeared uninvited and unusably (frozen,
uncontrollable) on screen. Multi-cursor Phase 1 rendered one sprite per
physical libinput pointer device that had ever reported a position, with
no way to turn it off and no expiry - so a phantom device (a real mouse's
side-button/scroll cluster enumerating as its own HID path is a common
case) that reports once and never moves again left a frozen ghost sprite
with nothing to control or dismiss it.
Adds general.multi_cursor (default false, live-settable via
`srd set multi_cursor <bool>`) and keys secondary_cursors to
(Point, Instant) so both the recording side (udev/session.rs) and the
render side (udev/render.rs) drop any entry older than
SECONDARY_CURSOR_TIMEOUT (1.5s). The "agent controls a window without
interrupting the user" use case this report also raised was never gated
on this flag - that's Multi-cursor Phase 2's pinned virtual-pointer
delivery, which never shows a visible cursor at all.
Diffstat (limited to 'crates/wayland/src/udev/mod.rs')
| -rw-r--r-- | crates/wayland/src/udev/mod.rs | 49 |
1 files changed, 35 insertions, 14 deletions
diff --git a/crates/wayland/src/udev/mod.rs b/crates/wayland/src/udev/mod.rs index 5ed96e4..b0a2639 100644 --- a/crates/wayland/src/udev/mod.rs +++ b/crates/wayland/src/udev/mod.rs @@ -107,6 +107,17 @@ pub(crate) struct DrmBuffer { image: Image<'static, 'static>, } +/// How long a secondary-cursor entry (`UdevState::secondary_cursors`) is +/// trusted after its own device's last real motion event before it's +/// treated as stale and pruned/skipped - see that field's own doc +/// comment for the frozen-ghost-cursor bug this exists to close. Short +/// enough that a genuinely idle second device's sprite actually +/// disappears at a human-noticeable timescale (not "eventually, whenever +/// something else happens to touch this map"), generous enough that +/// briefly pausing mid-gesture with a real second device doesn't flicker +/// its own cursor away and back. +pub(crate) const SECONDARY_CURSOR_TIMEOUT: Duration = Duration::from_millis(1500); + /// One connector+CRTC pair srdwm scans out to - i.e. one physical monitor. /// /// Each head owns its own scanout buffers, damage tracker and flip state, @@ -209,20 +220,30 @@ pub(crate) struct UdevState { /// monitors; clamped to the union of all head rectangles. pub(crate) pointer_pos: Point<f64, Logical>, /// Multi-cursor mode, Phase 1: every physical pointer/trackpad's own - /// last-known position, keyed by its real libinput device identity - /// (`smithay::backend::input::Event::device()`, confirmed `Device: - /// PartialEq + Eq + Hash` by reading smithay's own trait definition). - /// Purely a *visual* addition - `pointer_pos` above is still the one - /// position that actually drives clicks/drags/hit-testing, updated by - /// whichever device moved most recently exactly as before, so nothing - /// about existing interactive behaviour changes. This is what lets a - /// mouse and a trackpad each show their own live cursor sprite instead - /// of only the most-recently-moved device having a visible pointer at - /// all - see `docs/TODO.md`'s "Multi-cursor" plan for what later - /// phases would still need (per-device *interaction*, not just - /// per-device *rendering*, and the real `wl_seat` ecosystem wall a - /// second seat runs into for arbitrary client content). - pub(crate) secondary_cursors: HashMap<smithay::reexports::input::Device, Point<f64, Logical>>, + /// last-known position *and when it was last actually recorded*, keyed + /// by its real libinput device identity (`smithay::backend::input:: + /// Event::device()`, confirmed `Device: PartialEq + Eq + Hash` by + /// reading smithay's own trait definition). Purely a *visual* + /// addition - `pointer_pos` above is still the one position that + /// actually drives clicks/drags/hit-testing, updated by whichever + /// device moved most recently exactly as before, so nothing about + /// existing interactive behaviour changes. + /// + /// Gated behind `WindowManager::multi_cursor_enabled` (off by + /// default) and the timestamp both exist for the same real, reported + /// bug: real hardware routinely reports what is genuinely one mouse + /// as more than one distinct libinput device (a side-button/scroll + /// cluster on its own HID path, concretely) - the first motion event + /// from that phantom device seeded a permanent entry here, rendered + /// every frame forever after at wherever the pointer happened to be + /// at that one moment, since nothing ever moved that specific device + /// identity again. Reported live as "I see two cursors and can't even + /// control the other one" - a frozen, uncontrollable ghost, exactly + /// what an unpruned entry here looks like. `render_udev_frame` now + /// skips (and `handle_libinput_event` now prunes) any entry older + /// than `SECONDARY_CURSOR_TIMEOUT`, so only a device that has *itself* + /// moved recently ever shows a sprite. + pub(crate) secondary_cursors: HashMap<smithay::reexports::input::Device, (Point<f64, Logical>, Instant)>, /// A clone of the same `LibSeatSession` `platform.rs` opened the DRM /// device with (`LibSeatSession` is cheaply `Clone` - see its own /// derive - all clones share the same underlying seat connection). |