use std::mem::MaybeUninit;
use std::os::fd::{AsFd, AsRawFd, OwnedFd, RawFd};
use std::path::{Path, PathBuf};
use std::time::{Duration, SystemTime};
use rustix::fs::inotify;
use rustix::io::Errno;
use denise::{InputEvent, InputSource, Point, Size};
use crate::codes::abs;
use crate::error::EvdevError;
use crate::layout::{self, Layout};
use crate::translate::{AbsAxis, RawEvent, Translator};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Capabilities {
pub pointer: bool,
pub touch: bool,
pub keyboard: bool,
}
impl Capabilities {
#[inline]
pub const fn is_empty(self) -> bool {
!self.pointer && !self.touch && !self.keyboard
}
}
impl core::fmt::Display for Capabilities {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let mut first = true;
for (present, name) in [
(self.keyboard, "keyboard"),
(self.pointer, "pointer"),
(self.touch, "touch"),
] {
if present {
if !first {
f.write_str("+")?;
}
f.write_str(name)?;
first = false;
}
}
if first {
f.write_str("none")?;
}
Ok(())
}
}
#[derive(Debug)]
pub struct InputDevice {
device: evdev::Device,
path: PathBuf,
name: String,
capabilities: Capabilities,
translator: Translator,
}
impl InputDevice {
pub fn path(&self) -> &Path {
&self.path
}
pub fn name(&self) -> &str {
&self.name
}
pub fn capabilities(&self) -> Capabilities {
self.capabilities
}
pub fn abs_ranges(&self) -> (Option<AbsAxis>, Option<AbsAxis>) {
self.translator.abs_ranges()
}
}
impl AsRawFd for InputDevice {
fn as_raw_fd(&self) -> RawFd {
self.device.as_raw_fd()
}
}
const DEV_INPUT: &str = "/dev/input";
#[derive(Debug)]
pub struct InputBackend {
devices: Vec<InputDevice>,
pointer: Point,
scratch: Vec<RawEvent>,
last_event_age: Option<Duration>,
surface: Size,
watch: Option<OwnedFd>,
changed: bool,
}
impl InputBackend {
pub fn open_all(surface: Size) -> Result<Self, EvdevError> {
let devices: Vec<InputDevice> = evdev::enumerate()
.filter_map(|(path, device)| adopt(path, device, surface))
.collect();
if devices.is_empty() {
return Err(EvdevError::NoDevices);
}
Ok(Self {
devices,
pointer: Point::new(surface.width as i32 / 2, surface.height as i32 / 2),
scratch: Vec::new(),
last_event_age: None,
surface,
watch: watch_dev_input(),
changed: false,
})
}
pub fn devices_changed(&mut self) -> bool {
core::mem::take(&mut self.changed)
}
fn rescan(&mut self) {
let Ok(entries) = std::fs::read_dir(DEV_INPUT) else {
return;
};
let mut present: Vec<PathBuf> = entries
.flatten()
.map(|entry| entry.path())
.filter(|path| {
path.file_name()
.and_then(|name| name.to_str())
.is_some_and(|name| name.starts_with("event"))
})
.collect();
present.sort();
let before = self.devices.len();
self.devices.retain(|device| present.contains(&device.path));
self.changed |= self.devices.len() != before;
for path in present {
if self.devices.iter().any(|device| device.path == path) {
continue;
}
let Ok(device) = evdev::Device::open(&path) else {
continue;
};
if let Some(device) = adopt(path, device, self.surface) {
self.devices.push(device);
self.changed = true;
}
}
}
fn watch_fired(&mut self) -> bool {
let Some(watch) = self.watch.as_ref() else {
return false;
};
let mut buf = [MaybeUninit::uninit(); 512];
let mut reader = inotify::Reader::new(watch.as_fd(), &mut buf);
let mut fired = false;
loop {
match reader.next() {
Ok(_) => fired = true,
Err(Errno::WOULDBLOCK) => return fired,
Err(_) => return fired,
}
}
}
pub fn devices(&self) -> &[InputDevice] {
&self.devices
}
pub fn set_layout(&mut self, layout: &'static Layout) {
for device in &mut self.devices {
device.translator.set_layout(layout);
}
}
pub fn set_layout_from_system(&mut self) -> (&'static Layout, layout::LayoutSource) {
let (chosen, source) = layout::from_system();
self.set_layout(chosen);
(chosen, source)
}
pub fn raw_fds(&self) -> Vec<RawFd> {
let mut fds: Vec<RawFd> = self.devices.iter().map(AsRawFd::as_raw_fd).collect();
if let Some(watch) = self.watch.as_ref() {
fds.push(watch.as_raw_fd());
}
fds
}
pub fn resize(&mut self, size: Size) {
self.surface = size;
self.pointer = Point::new(size.width as i32 / 2, size.height as i32 / 2);
for device in &mut self.devices {
device.translator.resize(size);
}
}
pub fn pointer(&self) -> Point {
self.pointer
}
pub fn last_event_age(&self) -> Option<Duration> {
self.last_event_age
}
}
impl InputSource for InputBackend {
fn poll(&mut self, out: &mut Vec<InputEvent>) {
if self.watch_fired() {
self.rescan();
}
for device in &mut self.devices {
let Ok(events) = device.device.fetch_events() else {
continue;
};
self.scratch.clear();
let now = SystemTime::now();
for event in events {
self.last_event_age = now.duration_since(event.timestamp()).ok();
self.scratch.push(RawEvent::new(
event.event_type().0,
event.code(),
event.value(),
));
}
if self.scratch.is_empty() {
continue;
}
device.translator.set_pointer(self.pointer);
device.translator.feed_all(&self.scratch, out);
self.pointer = device.translator.pointer();
}
}
}
fn adopt(path: PathBuf, device: evdev::Device, surface: Size) -> Option<InputDevice> {
let capabilities = classify(&device);
if capabilities.is_empty() {
return None;
}
let name = device.name().unwrap_or("<unnamed>").to_owned();
let mut translator = Translator::new(surface);
if let Some(axes) = device.supported_absolute_axes() {
for axis in axes.iter() {
let info = device
.get_absinfo()
.ok()
.and_then(|mut all| all.find(|(code, _)| *code == axis).map(|(_, info)| info));
if let Some(info) = info {
translator.set_abs_range(axis.0, AbsAxis::new(info.minimum(), info.maximum()));
}
}
}
device.set_nonblocking(true).ok()?;
Some(InputDevice {
device,
path,
name,
capabilities,
translator,
})
}
fn watch_dev_input() -> Option<OwnedFd> {
let watch =
inotify::init(inotify::CreateFlags::CLOEXEC | inotify::CreateFlags::NONBLOCK).ok()?;
inotify::add_watch(
&watch,
DEV_INPUT,
inotify::WatchFlags::CREATE
| inotify::WatchFlags::ATTRIB
| inotify::WatchFlags::DELETE
| inotify::WatchFlags::MOVED_TO
| inotify::WatchFlags::MOVED_FROM,
)
.ok()?;
Some(watch)
}
fn classify(device: &evdev::Device) -> Capabilities {
let abs_axes = device.supported_absolute_axes();
let keys = device.supported_keys();
let has_abs = |code: u16| abs_axes.is_some_and(|axes| axes.iter().any(|axis| axis.0 == code));
let has_key = |code: u16| keys.is_some_and(|k| k.iter().any(|key| key.0 == code));
Capabilities {
pointer: has_key(crate::codes::btn::LEFT),
touch: has_abs(abs::MT_POSITION_X),
keyboard: has_key(30) && has_key(44),
}
}