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 );
		}
	}
}