use std::os::fd::{AsRawFd, RawFd};
use std::path::{Path, PathBuf};
use std::time::{Duration, SystemTime};
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()
}
}
#[derive(Debug)]
pub struct InputBackend {
devices: Vec<InputDevice>,
pointer: Point,
scratch: Vec<RawEvent>,
last_event_age: Option<Duration>,
}
impl InputBackend {
pub fn open_all(surface: Size) -> Result<Self, EvdevError> {
let mut devices = Vec::new();
for (path, device) in evdev::enumerate() {
let capabilities = classify(&device);
if capabilities.is_empty() {
continue;
}
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()));
}
}
}
if device.set_nonblocking(true).is_err() {
continue;
}
devices.push(InputDevice {
device,
path,
name,
capabilities,
translator,
});
}
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,
})
}
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> {
self.devices.iter().map(AsRawFd::as_raw_fd).collect()
}
pub fn resize(&mut self, size: 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>) {
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 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),
}
}