ltk/input/repeat/key.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>
use std::time::Duration;
use smithay_client_toolkit::seat::keyboard::KeyEvent;
use calloop::timer::{ Timer, TimeoutAction };
use crate::app::App;
use crate::event_loop::{ AppData, SurfaceFocus };
use crate::event_loop::repeat::KeyRepeatState;
/// Built-in fallback for the initial key-repeat delay when the
/// compositor does not advertise one and the app does not override
/// [`crate::App::key_repeat_delay`].
const DEFAULT_REPEAT_DELAY: Duration = Duration::from_millis( 500 );
/// Built-in fallback for the inter-repeat interval when the compositor
/// does not advertise a rate. ~30 Hz, matching the GNOME / KDE default
/// for "fast" without straying into uncomfortably-twitchy territory.
const DEFAULT_REPEAT_INTERVAL: Duration = Duration::from_millis( 33 );
impl<A: App> AppData<A>
{
/// Compute the effective initial repeat delay — app override wins,
/// then compositor info, then a built-in default.
pub( crate ) fn effective_repeat_delay( &self ) -> Duration
{
if let Some( d ) = self.app.key_repeat_delay() { return d; }
if self.compositor_repeat_delay > 0
{
Duration::from_millis( self.compositor_repeat_delay as u64 )
} else {
DEFAULT_REPEAT_DELAY
}
}
/// Compute the effective inter-repeat interval. Same precedence as
/// [`Self::effective_repeat_delay`]: app override → compositor →
/// built-in default. Returns `None` when repeat is disabled by the
/// active source (compositor `RepeatInfo::Disable` with no app
/// override, or an app override of `Some(Duration::ZERO)`).
pub( crate ) fn effective_repeat_interval( &self ) -> Option<Duration>
{
if let Some( d ) = self.app.key_repeat_interval()
{
if d.is_zero() { return None; }
return Some( d );
}
if self.compositor_repeat_rate == 0
{
// Compositor explicitly disabled repeat, app did not
// override — fall back to a built-in default rather than
// disabling, because most environments where the
// compositor reports 0 also fail to send the event in
// the first place. The default keeps the feel close to
// what people expect from a desktop toolkit.
return Some( DEFAULT_REPEAT_INTERVAL );
}
let ms = ( 1000 / self.compositor_repeat_rate ).max( 1 );
Some( Duration::from_millis( ms as u64 ) )
}
/// Schedule a key-repeat timer for the given event. No-op when the
/// app's [`crate::App::key_repeats`] gate returns `false` for this
/// keysym, when repeat is disabled, or when the calloop timer
/// insertion fails (in which case held keys simply do not repeat).
pub( crate ) fn start_key_repeat( &mut self, focus: SurfaceFocus, event: KeyEvent )
{
if !self.app.key_repeats( event.keysym ) { return; }
let interval = match self.effective_repeat_interval()
{
Some( i ) => i,
None => return,
};
let delay = self.effective_repeat_delay();
let event_for_timer = event.clone();
let timer = Timer::from_duration( delay );
let token = self.loop_handle.insert_source( timer, move |_, _, data: &mut AppData<A>|
{
data.dispatch_key( focus, event_for_timer.clone() );
TimeoutAction::ToDuration( interval )
} );
match token
{
Ok( token ) => { self.key_repeat = Some( KeyRepeatState { event, token } ); }
Err( _ ) => {}
}
}
/// Cancel any active key-repeat timer.
pub( crate ) fn stop_key_repeat( &mut self )
{
if let Some( state ) = self.key_repeat.take()
{
self.loop_handle.remove( state.token );
}
}
}