use std::os::fd::{AsFd, BorrowedFd, OwnedFd};
use std::time::Duration;
use rustix::time::{
Itimerspec, TimerfdClockId, TimerfdFlags, TimerfdTimerFlags, Timespec, timerfd_create,
timerfd_settime,
};
use crate::InputError;
use crate::event::KeyEvent;
use crate::repeat::{Repeat, RepeatConfig, Timer};
#[derive(Debug)]
pub struct KeyRepeater {
repeat: Repeat,
timer: OwnedFd,
}
impl KeyRepeater {
pub fn new(config: RepeatConfig) -> Result<Self, InputError> {
if config.interval.is_zero() {
return Err(InputError::Config("the repeat interval cannot be zero"));
}
let timer = timerfd_create(
TimerfdClockId::Monotonic,
TimerfdFlags::NONBLOCK | TimerfdFlags::CLOEXEC,
)
.map_err(|errno| InputError::Timer(std::io::Error::from(errno)))?;
Ok(Self {
repeat: Repeat::new(config),
timer,
})
}
#[must_use]
pub fn fd(&self) -> BorrowedFd<'_> {
self.timer.as_fd()
}
#[must_use]
pub const fn held_key(&self) -> Option<u32> {
self.repeat.held_key()
}
pub fn on_key(&mut self, event: &KeyEvent, repeats: bool) {
let command = self.repeat.on_key(event, repeats);
self.apply(self.repeat.config(), command);
}
pub fn cancel(&mut self) {
let command = self.repeat.cancel();
self.apply(self.repeat.config(), command);
}
pub fn dispatch(&mut self, time_ms: u32, out: &mut Vec<KeyEvent>) -> Result<(), InputError> {
let mut buf = [0_u8; 8];
let read = match rustix::io::read(&self.timer, &mut buf) {
Ok(read) => read,
Err(rustix::io::Errno::AGAIN) => return Ok(()),
Err(errno) => return Err(InputError::Timer(std::io::Error::from(errno))),
};
if read != buf.len() {
return Ok(());
}
let dropped = self
.repeat
.synthesize(u64::from_ne_bytes(buf), time_ms, out);
if dropped > 0 {
drmkit_log::log_debug!("dropped {dropped} repeat tick(s): the loop is behind");
}
Ok(())
}
fn apply(&self, config: RepeatConfig, command: Timer) {
let spec = match command {
Timer::Unchanged => return,
Timer::Disarm => Itimerspec {
it_value: Timespec {
tv_sec: 0,
tv_nsec: 0,
},
it_interval: Timespec {
tv_sec: 0,
tv_nsec: 0,
},
},
Timer::Arm => Itimerspec {
it_value: timespec(config.delay.max(Duration::from_nanos(1))),
it_interval: timespec(config.interval),
},
};
if let Err(errno) = timerfd_settime(&self.timer, TimerfdTimerFlags::empty(), &spec) {
drmkit_log::log_warn!("arming the repeat timer failed: {errno}");
}
}
}
fn timespec(duration: Duration) -> Timespec {
Timespec {
tv_sec: i64::try_from(duration.as_secs()).unwrap_or(i64::MAX),
tv_nsec: i64::from(duration.subsec_nanos()),
}
}