From 895ce91543450f6f45f6a60e097e9d294fa9809a Mon Sep 17 00:00:00 2001 From: Niklas Herder Date: Sat, 12 Sep 2026 09:48:29 +0200 Subject: [PATCH] cursor: let the hide reason tighten, and clear timer state when suppressed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hidden.is_some() guard dropped a hide-reason upgrade: an Idle-hidden cursor that received a touch stayed Idle, so a later click or scroll revealed it where the spec says a touch-hidden cursor waits for real movement. Reachable on the default config — fullscreen video, cursor idle-hidden after 3s, tap the screen, nudge the scroll wheel. The guard itself has to stay, or every keystroke with while_typing on reschedules a render on every output; so it now returns early only when the reason is unchanged or already Touch. hide_cursor also returned before clearing idle_timer and last_armed when the pointer was grabbed, leaving both stale while on_idle_timer's neighbouring Drop arm cleared them carefully. Clearing in hide_cursor before the grab check fixes both call sites at once — hide_cursor_now removes the source itself, and the Hide arm returns TimeoutAction::Drop — rather than restating the cleanup in one match arm. Drafted with Claude (Anthropic); reviewed and tested by the committer. --- src/backend/render/cursor.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/backend/render/cursor.rs b/src/backend/render/cursor.rs index 95fb7500..02da0685 100644 --- a/src/backend/render/cursor.rs +++ b/src/backend/render/cursor.rs @@ -998,20 +998,20 @@ pub fn hide_cursor_now(state: &mut State, seat: &Seat, reason: HideReason } fn hide_cursor(state: &mut State, seat: &Seat, reason: HideReason) { - if let Some(ptr) = seat.get_pointer() - && ptr.is_grabbed() - { - return; - } + let grabbed = seat.get_pointer().is_some_and(|ptr| ptr.is_grabbed()); let cursor_state = seat.user_data().get::().unwrap(); { let mut inner = cursor_state.lock().unwrap(); - if inner.hidden.is_some() { + // Every caller has already disposed of the timer source, so clear the + // bookkeeping before the grab check rather than only on success. + inner.idle_timer = None; + inner.last_armed = None; + // The reason only ever tightens: `Touch` waits for real movement, so a + // later trigger must not relax it back to click-or-scroll. + if grabbed || inner.hidden == Some(reason) || inner.hidden == Some(HideReason::Touch) { return; } inner.hidden = Some(reason); - inner.idle_timer = None; - inner.last_armed = None; } let outputs: Vec<_> = state.common.shell.read().outputs().cloned().collect(); for output in outputs {