cursor: measure the idle-hide deadline from the last pointer event
refresh_idle_timer armed for the full timeout measured from now, which is right only for notify_cursor_activity — it stamps activity immediately before arming. The other callers deliberately do not stamp, so with idle_timeout: Some(10) a user typing continuously never saw the cursor hide: every key press and release pushed the deadline out by a fresh ten seconds. That inverts the feature and regresses the shipped cursor_hide_timeout behaviour. resolve's RearmAfter cannot recover it — it only corrects a timer that fires too early. So the delay is now shortest_timeout minus the time already elapsed, extracted as CursorHideConfig::arm_delay and table tested; the arithmetic handed to Timer::from_duration was the one part of this path with no coverage. A zero delay is fine: the timer fires on the next loop iteration and resolve decides. Also rename fullscreen_focused to fullscreen_on_active_output, which is what seat.active_output() actually reports, and log an arming failure rather than silently disabling cursor hiding. Drafted with Claude (Anthropic); reviewed and tested by the committer.
This commit is contained in:
parent
7596cf7f2f
commit
115cdac039
2 changed files with 48 additions and 20 deletions
|
|
@ -251,11 +251,15 @@ impl CursorHideConfig {
|
|||
}
|
||||
}
|
||||
|
||||
/// The delay to arm the timer with, before context is known. Callers on the
|
||||
/// input path cannot read fullscreen state without deadlocking, so they arm
|
||||
/// pessimistically and `resolve` corrects it on fire.
|
||||
pub fn shortest_timeout(&self) -> Option<Duration> {
|
||||
self.effective_timeout(true)
|
||||
/// How long to wait before the idle timer should next fire, given how long
|
||||
/// it has been since the last pointer event. Pessimistic: fullscreen only
|
||||
/// ever shortens a timeout, so arming as if fullscreen cannot fire too
|
||||
/// late, and `resolve` corrects an early fire once the context is known.
|
||||
pub fn arm_delay(&self, since_pointer_activity: Duration) -> Option<Duration> {
|
||||
Some(
|
||||
self.effective_timeout(true)?
|
||||
.saturating_sub(since_pointer_activity),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -402,12 +406,6 @@ mod test {
|
|||
..OFF
|
||||
};
|
||||
assert_eq!(long_fs.resolve(secs(3), true), HideDecision::Hide);
|
||||
|
||||
// The arming delay is pessimistic: the shortest timeout that could
|
||||
// apply in any context, because arm-time code cannot read the shell.
|
||||
assert_eq!(short_fs.shortest_timeout(), Some(secs(3)));
|
||||
assert_eq!(long_fs.shortest_timeout(), Some(secs(3)));
|
||||
assert_eq!(OFF.shortest_timeout(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -423,4 +421,27 @@ mod test {
|
|||
assert_eq!(cfg.resolve(secs(5), false), HideDecision::Hide);
|
||||
assert_eq!(cfg.resolve(secs(6), false), HideDecision::Hide);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arm_delay_measures_from_the_last_pointer_event() {
|
||||
let cfg = CursorHideConfig {
|
||||
idle_timeout: Some(10),
|
||||
..OFF
|
||||
};
|
||||
assert_eq!(cfg.arm_delay(Duration::ZERO), Some(secs(10)));
|
||||
assert_eq!(cfg.arm_delay(secs(4)), Some(secs(6)));
|
||||
// Exactly due, and past due: fire immediately rather than deferring.
|
||||
assert_eq!(cfg.arm_delay(secs(10)), Some(Duration::ZERO));
|
||||
assert_eq!(cfg.arm_delay(secs(600)), Some(Duration::ZERO));
|
||||
|
||||
// Armed against the shortest timeout that any context could want.
|
||||
let fs = CursorHideConfig {
|
||||
idle_timeout: Some(10),
|
||||
fullscreen_idle_timeout: Some(3),
|
||||
..OFF
|
||||
};
|
||||
assert_eq!(fs.arm_delay(secs(1)), Some(secs(2)));
|
||||
|
||||
assert_eq!(OFF.arm_delay(Duration::ZERO), None);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -892,6 +892,10 @@ pub fn notify_cursor_activity(state: &State, seat: &Seat<State>, kind: PointerEv
|
|||
|
||||
/// (Re)arm the idle-hide timer without counting as pointer activity.
|
||||
///
|
||||
/// The deadline is measured from the last *pointer* event, not from now, so a
|
||||
/// caller that does not stamp activity — a keystroke, a config reload — cannot
|
||||
/// postpone the hide by re-arming.
|
||||
///
|
||||
/// Armed with the shortest timeout any context could want: the input that arms
|
||||
/// the timer is usually what goes on to make a window fullscreen, so the
|
||||
/// context is not yet knowable here. `on_idle_timer` evaluates it on fire and
|
||||
|
|
@ -902,7 +906,7 @@ pub fn refresh_idle_timer(state: &State, seat: &Seat<State>) {
|
|||
let cursor_state = seat.user_data().get::<CursorState>().unwrap();
|
||||
let now = Instant::now();
|
||||
|
||||
let old_token = {
|
||||
let (old_token, since_activity) = {
|
||||
let mut inner = cursor_state.lock().unwrap();
|
||||
if inner.hidden.is_some() {
|
||||
return;
|
||||
|
|
@ -915,26 +919,29 @@ pub fn refresh_idle_timer(state: &State, seat: &Seat<State>) {
|
|||
if throttled {
|
||||
return;
|
||||
}
|
||||
let since_activity = now.duration_since(inner.last_pointer_activity);
|
||||
inner.last_armed = None;
|
||||
inner.idle_timer.take()
|
||||
(inner.idle_timer.take(), since_activity)
|
||||
};
|
||||
|
||||
if let Some(token) = old_token {
|
||||
loop_handle.remove(token);
|
||||
}
|
||||
|
||||
let Some(delay) = config.shortest_timeout() else {
|
||||
let Some(delay) = config.arm_delay(since_activity) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let timer = Timer::from_duration(delay);
|
||||
let timer_seat = seat.clone();
|
||||
if let Ok(token) =
|
||||
loop_handle.insert_source(timer, move |_, _, state| on_idle_timer(state, &timer_seat))
|
||||
{
|
||||
let mut inner = cursor_state.lock().unwrap();
|
||||
inner.idle_timer = Some(token);
|
||||
inner.last_armed = Some(now);
|
||||
match loop_handle.insert_source(timer, move |_, _, state| on_idle_timer(state, &timer_seat)) {
|
||||
Ok(token) => {
|
||||
let mut inner = cursor_state.lock().unwrap();
|
||||
inner.idle_timer = Some(token);
|
||||
inner.last_armed = Some(now);
|
||||
}
|
||||
// `idle_timer` stays `None`, so the next activity is unthrottled and retries.
|
||||
Err(err) => warn!(?err, "Failed to arm the cursor idle-hide timer"),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue