drmkit_input/repeater.rs
1// SPDX-FileCopyrightText: (c) 2026 Joel Winarske
2// SPDX-License-Identifier: MIT
3
4//! Auto-repeat on a timerfd, for a caller that already has an event loop.
5
6use std::os::fd::{AsFd, BorrowedFd, OwnedFd};
7use std::time::Duration;
8
9use rustix::time::{
10 Itimerspec, TimerfdClockId, TimerfdFlags, TimerfdTimerFlags, Timespec, timerfd_create,
11 timerfd_settime,
12};
13
14use crate::InputError;
15use crate::event::KeyEvent;
16use crate::repeat::{Repeat, RepeatConfig, Timer};
17
18/// Auto-repeat driven by a timer the caller polls.
19///
20/// Register [`fd`](Self::fd) on the same epoll as everything else, feed every
21/// real key event to [`on_key`](Self::on_key), and call
22/// [`dispatch`](Self::dispatch) when the descriptor is readable.
23///
24/// The decisions live in [`Repeat`]; this is the timer around them.
25#[derive(Debug)]
26pub struct KeyRepeater {
27 repeat: Repeat,
28 timer: OwnedFd,
29}
30
31impl KeyRepeater {
32 /// Create a repeater with its own timer.
33 ///
34 /// # Errors
35 ///
36 /// [`InputError::Config`] for a zero interval, which would leave the timer
37 /// firing as fast as the kernel can wake the loop. A zero *delay* is
38 /// allowed and means the first repeat lands on the next tick.
39 ///
40 /// [`InputError::Timer`] if the timer cannot be created.
41 pub fn new(config: RepeatConfig) -> Result<Self, InputError> {
42 if config.interval.is_zero() {
43 return Err(InputError::Config("the repeat interval cannot be zero"));
44 }
45 let timer = timerfd_create(
46 TimerfdClockId::Monotonic,
47 TimerfdFlags::NONBLOCK | TimerfdFlags::CLOEXEC,
48 )
49 .map_err(|errno| InputError::Timer(std::io::Error::from(errno)))?;
50 Ok(Self {
51 repeat: Repeat::new(config),
52 timer,
53 })
54 }
55
56 /// The descriptor to poll. Readable when a repeat is due.
57 #[must_use]
58 pub fn fd(&self) -> BorrowedFd<'_> {
59 self.timer.as_fd()
60 }
61
62 /// The key currently repeating.
63 #[must_use]
64 pub const fn held_key(&self) -> Option<u32> {
65 self.repeat.held_key()
66 }
67
68 /// Feed a real key event.
69 ///
70 /// `repeats` is the keymap's answer for this key; see
71 /// [`Repeat::on_key`].
72 pub fn on_key(&mut self, event: &KeyEvent, repeats: bool) {
73 let command = self.repeat.on_key(event, repeats);
74 self.apply(self.repeat.config(), command);
75 }
76
77 /// Stop repeating, and stop the timer.
78 pub fn cancel(&mut self) {
79 let command = self.repeat.cancel();
80 self.apply(self.repeat.config(), command);
81 }
82
83 /// Drain the timer and append whatever repeats are due.
84 ///
85 /// Nothing is appended if no key is held, or if the descriptor had nothing
86 /// to say. The events carry only the key -- run them through
87 /// [`Keyboard::resolve`](crate::Keyboard::resolve) for `sym` and `utf8`.
88 ///
89 /// # Errors
90 ///
91 /// [`InputError::Timer`] if the timer cannot be read. `EAGAIN` is not one:
92 /// a descriptor that was not ready simply produces nothing.
93 pub fn dispatch(&mut self, time_ms: u32, out: &mut Vec<KeyEvent>) -> Result<(), InputError> {
94 let mut buf = [0_u8; 8];
95 let read = match rustix::io::read(&self.timer, &mut buf) {
96 Ok(read) => read,
97 Err(rustix::io::Errno::AGAIN) => return Ok(()),
98 Err(errno) => return Err(InputError::Timer(std::io::Error::from(errno))),
99 };
100 if read != buf.len() {
101 // The kernel writes the count as one u64 or nothing at all, so a
102 // short read is not a thing that happens; treating it as "no
103 // expirations" is the safe reading if it ever does.
104 return Ok(());
105 }
106
107 let dropped = self
108 .repeat
109 .synthesize(u64::from_ne_bytes(buf), time_ms, out);
110 if dropped > 0 {
111 drmkit_log::log_debug!("dropped {dropped} repeat tick(s): the loop is behind");
112 }
113 Ok(())
114 }
115
116 fn apply(&self, config: RepeatConfig, command: Timer) {
117 let spec = match command {
118 Timer::Unchanged => return,
119 Timer::Disarm => Itimerspec {
120 it_value: Timespec {
121 tv_sec: 0,
122 tv_nsec: 0,
123 },
124 it_interval: Timespec {
125 tv_sec: 0,
126 tv_nsec: 0,
127 },
128 },
129 Timer::Arm => Itimerspec {
130 // An it_value of zero means "disarm" to the kernel, which
131 // would quietly turn a zero delay into no repeats at all.
132 it_value: timespec(config.delay.max(Duration::from_nanos(1))),
133 it_interval: timespec(config.interval),
134 },
135 };
136 // Nothing here can fail: the descriptor is one this owns and the spec
137 // is in range by construction. A failure would mean a key that stops
138 // repeating, which is not worth an error on every keystroke.
139 if let Err(errno) = timerfd_settime(&self.timer, TimerfdTimerFlags::empty(), &spec) {
140 drmkit_log::log_warn!("arming the repeat timer failed: {errno}");
141 }
142 }
143}
144
145fn timespec(duration: Duration) -> Timespec {
146 Timespec {
147 tv_sec: i64::try_from(duration.as_secs()).unwrap_or(i64::MAX),
148 tv_nsec: i64::from(duration.subsec_nanos()),
149 }
150}