From 4eb36873d4ca4b0faf0e54dbeab8f5f6450dd704 Mon Sep 17 00:00:00 2001 From: srdusr <99972264+srdusr@users.noreply.github.com> Date: Fri, 3 Oct 2025 22:59:00 +0200 Subject: 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 `) 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. --- crates/wayland/src/udev/session.rs | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) (limited to 'crates/wayland/src/udev/session.rs') diff --git a/crates/wayland/src/udev/session.rs b/crates/wayland/src/udev/session.rs index c80592e..e2f6e2b 100644 --- a/crates/wayland/src/udev/session.rs +++ b/crates/wayland/src/udev/session.rs @@ -319,6 +319,29 @@ pub(crate) fn register_udev_monitor(handle: &LoopHandle<'static, CompState>, sea Ok(()) } +/// Records `device`'s own live position for Multi-cursor Phase 1's +/// secondary-cursor rendering, and prunes every entry (not just this +/// device's own) older than `SECONDARY_CURSOR_TIMEOUT` - see +/// `UdevState::secondary_cursors`'s own doc comment for why both the +/// config gate and the pruning exist. A no-op, and clears the map +/// outright, when the feature is off: toggling `general.multi_cursor` +/// off live must not leave a stale sprite rendering from before the +/// toggle (`render_udev_frame` also checks this same flag, but clearing +/// here too keeps the map itself from silently growing while the +/// feature is disabled). +fn record_secondary_cursor(state: &mut CompState, device: smithay::reexports::input::Device, pos: Point) { + if !state.wm.borrow().multi_cursor_enabled { + if let Some(udev) = state.udev.as_mut() { + udev.secondary_cursors.clear(); + } + return; + } + let Some(udev) = state.udev.as_mut() else { return }; + let now = Instant::now(); + udev.secondary_cursors.insert(device, (pos, now)); + udev.secondary_cursors.retain(|_, (_, seen)| now.duration_since(*seen) < SECONDARY_CURSOR_TIMEOUT); +} + fn handle_libinput_event(state: &mut CompState, event: InputEvent) { match event { InputEvent::Keyboard { event } => handle_keyboard_key_event(state, &event), @@ -339,8 +362,10 @@ fn handle_libinput_event(state: &mut CompState, event: InputEvent { -- cgit v1.2.3