drmkit_input/repeat.rs
1// SPDX-FileCopyrightText: (c) 2026 Joel Winarske
2// SPDX-License-Identifier: MIT
3
4//! Deciding what auto-repeat should do, with no timer and no keymap in sight.
5
6use std::time::Duration;
7
8use crate::event::KeyEvent;
9
10/// How many synthesized events one dispatch will produce.
11///
12/// A loop that stalled comes back to a timerfd holding every expiration it
13/// missed. Emitting all of them hands the consumer a burst of keystrokes the
14/// user did not type, seconds after they stopped typing; stopping at a
15/// handful keeps the key alive without the flood.
16const MAX_BURST: u64 = 8;
17
18/// When a held key starts repeating, and how fast.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20#[allow(
21 clippy::exhaustive_structs,
22 reason = "built by callers with `..Default::default()`, which a new field does not break"
23)]
24pub struct RepeatConfig {
25 /// How long a key must be held before the first repeat.
26 pub delay: Duration,
27 /// The gap between repeats after that.
28 pub interval: Duration,
29}
30
31impl Default for RepeatConfig {
32 /// The reference's default (`key_repeater.hpp`): 600 ms, then 25 Hz.
33 fn default() -> Self {
34 Self {
35 delay: Duration::from_millis(600),
36 interval: Duration::from_millis(40),
37 }
38 }
39}
40
41/// What the timer should do about a key event.
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43#[non_exhaustive]
44pub enum Timer {
45 /// Start the delay, then repeat at the interval.
46 Arm,
47 /// Stop.
48 Disarm,
49 /// Whatever it was doing, keep doing.
50 Unchanged,
51}
52
53/// Which key is repeating, and what each event does to that.
54///
55/// Split out from the timer so it can be reasoned about, and tested, without
56/// one. libinput deliberately does not auto-repeat -- it reports what the
57/// hardware did, and the repeat rate is a user preference the compositor
58/// owns -- so this is the piece that has to exist somewhere.
59#[derive(Debug, Clone)]
60pub struct Repeat {
61 config: RepeatConfig,
62 held: Option<u32>,
63}
64
65impl Repeat {
66 /// Start with nothing held.
67 #[must_use]
68 pub const fn new(config: RepeatConfig) -> Self {
69 Self { config, held: None }
70 }
71
72 /// The delay and interval this was built with.
73 #[must_use]
74 pub const fn config(&self) -> RepeatConfig {
75 self.config
76 }
77
78 /// The key currently repeating.
79 #[must_use]
80 pub const fn held_key(&self) -> Option<u32> {
81 self.held
82 }
83
84 /// Feed a real key event.
85 ///
86 /// `repeats` is the keymap's answer for this key --
87 /// [`Keyboard::should_repeat`](crate::Keyboard::should_repeat). It is
88 /// passed in rather than looked up so this stays usable without a keymap,
89 /// and so a caller with its own policy can impose it.
90 ///
91 /// Synthesized events are ignored: feeding this its own output back would
92 /// re-arm the delay on every tick and the key would never repeat.
93 pub fn on_key(&mut self, event: &KeyEvent, repeats: bool) -> Timer {
94 if event.repeat {
95 return Timer::Unchanged;
96 }
97
98 if event.pressed {
99 if !repeats {
100 // A modifier, or a lock. It does not disturb a key already
101 // repeating -- holding `a` and pressing Shift should go on
102 // repeating, in upper case from the next tick.
103 return Timer::Unchanged;
104 }
105 self.held = Some(event.key);
106 return Timer::Arm;
107 }
108
109 // A release only ends the repeat if it is the key that was repeating.
110 // Letting go of something else -- a modifier, a key pressed and
111 // released while this one is held -- leaves it running.
112 if self.held == Some(event.key) {
113 self.held = None;
114 Timer::Disarm
115 } else {
116 Timer::Unchanged
117 }
118 }
119
120 /// Stop repeating.
121 ///
122 /// For a session pause, or any point where the held-key state has stopped
123 /// being trustworthy.
124 pub fn cancel(&mut self) -> Timer {
125 self.held = None;
126 Timer::Disarm
127 }
128
129 /// Turn a batch of timer expirations into events to deliver.
130 ///
131 /// Returns how many were dropped to the burst cap, which is worth saying
132 /// out loud rather than swallowing: a nonzero answer means the loop is not
133 /// keeping up.
134 ///
135 /// The events carry only the key. A caller that wants `sym` and `utf8`
136 /// runs them through [`Keyboard::resolve`](crate::Keyboard::resolve),
137 /// which reads against the modifiers held *now* -- so pressing Shift
138 /// partway through a held key changes what the next repeat types.
139 pub fn synthesize(&self, expirations: u64, time_ms: u32, out: &mut Vec<KeyEvent>) -> u64 {
140 let Some(key) = self.held else {
141 return 0;
142 };
143 let emit = expirations.min(MAX_BURST);
144 out.reserve(usize::try_from(emit).unwrap_or(usize::MAX));
145 for _ in 0..emit {
146 out.push(KeyEvent {
147 time_ms,
148 key,
149 pressed: true,
150 repeat: true,
151 sym: 0,
152 utf8: String::new(),
153 });
154 }
155 expirations - emit
156 }
157}