Skip to main content

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}