mod ffi;
#[cfg(target_os = "linux")]
mod linux;
mod map;
use crate::{GamepadEvent, GamepadId, GamepadSnapshot};
use ffi::{ABS_CNT, EV_ABS, EV_KEY, EV_SYN, InputEvent, KeyBits, SYN_DROPPED, SYN_REPORT};
#[cfg(target_os = "linux")]
pub use linux::Evdev;
use map::{Layout, Probe};
pub use map::{stick_axis, trigger_axis};
use std::collections::HashSet;
use std::io;
use std::path::{Path, PathBuf};
use std::time::{Duration, Instant};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum EvdevError {
Scan {
message: String,
},
Open {
path: PathBuf,
message: String,
},
Read {
path: PathBuf,
message: String,
},
}
impl std::fmt::Display for EvdevError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Scan { message } => write!(f, "could not list /dev/input: {message}"),
Self::Open { path, message } => {
write!(f, "could not probe {}: {message}", path.display())
}
Self::Read { path, message } => {
write!(f, "could not read {}: {message}", path.display())
}
}
}
}
impl std::error::Error for EvdevError {}
pub const RESCAN_INTERVAL: Duration = Duration::from_secs(1);
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct Node {
path: PathBuf,
ino: u64,
}
trait Device {
fn probe(&mut self) -> io::Result<Probe>;
fn read(&mut self, events: &mut Vec<InputEvent>) -> io::Result<()>;
}
trait Source {
type Device: Device;
fn scan(&mut self) -> io::Result<Vec<Node>>;
fn open(&mut self, path: &Path) -> io::Result<Self::Device>;
}
fn skipped_quietly(error: &io::Error) -> bool {
matches!(
error.kind(),
io::ErrorKind::PermissionDenied | io::ErrorKind::NotFound
) || error.raw_os_error() == Some(ffi::ENODEV)
}
#[derive(Debug)]
struct Pad<D> {
node: Node,
device: D,
id: GamepadId,
layout: Layout,
held: KeyBits,
values: [i32; ABS_CNT],
dropping: bool,
last: GamepadSnapshot,
}
impl<D: Device> Pad<D> {
fn take_state(&mut self, probe: &Probe) {
self.held = probe.held;
self.values = probe.values();
}
fn report(&mut self, emit: &mut impl FnMut(GamepadEvent)) {
let snapshot = self.layout.snapshot(&self.held, &self.values);
if snapshot != self.last {
self.last = snapshot;
emit(GamepadEvent::State {
id: self.id,
snapshot,
});
}
}
fn pump(
&mut self,
events: &mut Vec<InputEvent>,
emit: &mut impl FnMut(GamepadEvent),
) -> io::Result<()> {
events.clear();
self.device.read(events)?;
for event in events.iter() {
match (event.kind, event.code) {
(EV_SYN, SYN_DROPPED) => self.dropping = true,
(EV_SYN, SYN_REPORT) => {
if self.dropping {
let probe = self.device.probe()?;
self.take_state(&probe);
self.dropping = false;
}
self.report(emit);
}
_ if self.dropping => {}
(EV_KEY, code) => self.held.set(code, event.value != 0),
(EV_ABS, code) => {
if let Some(value) = self.values.get_mut(usize::from(code)) {
*value = event.value;
}
}
_ => {}
}
}
Ok(())
}
}
#[derive(Debug)]
struct Poller<D> {
pads: Vec<Pad<D>>,
not_pads: HashSet<Node>,
scanned: Option<Instant>,
scan_failure: Option<EvdevError>,
events: Vec<InputEvent>,
}
impl<D> Default for Poller<D> {
fn default() -> Self {
Self {
pads: Vec::new(),
not_pads: HashSet::new(),
scanned: None,
scan_failure: None,
events: Vec::new(),
}
}
}
impl<D: Device> Poller<D> {
fn poll<S: Source<Device = D>>(
&mut self,
source: &mut S,
now: Instant,
emit: &mut impl FnMut(GamepadEvent),
) -> Result<(), EvdevError> {
let mut failure = None;
let events = &mut self.events;
self.pads.retain_mut(|pad| match pad.pump(events, emit) {
Ok(()) => true,
Err(error) => {
emit(GamepadEvent::Disconnected { id: pad.id });
if error.raw_os_error() != Some(ffi::ENODEV) {
failure = Some(EvdevError::Read {
path: pad.node.path.clone(),
message: error.to_string(),
});
}
false
}
});
if self
.scanned
.is_none_or(|at| now.duration_since(at) >= RESCAN_INTERVAL)
{
self.scanned = Some(now);
self.scan_failure = None;
match source.scan() {
Ok(nodes) => {
self.not_pads.retain(|node| nodes.contains(node));
for node in nodes {
if let Err(error) = self.connect(source, node, emit) {
self.scan_failure = Some(error);
}
}
}
Err(error) => {
self.scan_failure = Some(EvdevError::Scan {
message: error.to_string(),
});
}
}
}
match failure.or_else(|| self.scan_failure.clone()) {
Some(failure) => Err(failure),
None => Ok(()),
}
}
fn connect<S: Source<Device = D>>(
&mut self,
source: &mut S,
node: Node,
emit: &mut impl FnMut(GamepadEvent),
) -> Result<(), EvdevError> {
if self.not_pads.contains(&node) || self.pads.iter().any(|pad| pad.node == node) {
return Ok(());
}
let opened = source.open(&node.path).and_then(|mut device| {
let probe = device.probe()?;
Ok((device, probe))
});
let (device, probe) = match opened {
Ok(opened) => opened,
Err(error) if skipped_quietly(&error) => return Ok(()),
Err(error) => {
return Err(EvdevError::Open {
path: node.path,
message: error.to_string(),
});
}
};
if !map::is_gamepad(&probe.keys, &probe.abs) {
self.not_pads.insert(node);
return Ok(());
}
let layout = Layout::new(&probe);
let id = GamepadId::allocate();
let kind = layout.kind;
emit(GamepadEvent::Connected { id, kind });
let mut pad = Pad {
node,
device,
id,
layout,
held: KeyBits::EMPTY,
values: [0; ABS_CNT],
dropping: false,
last: GamepadSnapshot::neutral(kind),
};
pad.take_state(&probe);
pad.report(emit);
self.pads.push(pad);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{ActionDecl, ActionKind, ActionMap, Binding, PadAxis, PadButton, PadKind};
use ffi::{ABS_X, ABS_Y, BTN_EAST, BTN_SOUTH, ENODEV};
use map::tests::{XPAD_STICK, xpad};
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;
#[derive(Debug)]
struct Script {
probe: Probe,
pending: Vec<InputEvent>,
open_error: Option<io::ErrorKind>,
read_error: Option<i32>,
probes: u32,
}
type Shared = Rc<RefCell<Script>>;
#[derive(Debug)]
struct FakeDevice(Shared);
impl Device for FakeDevice {
fn probe(&mut self) -> io::Result<Probe> {
let mut script = self.0.borrow_mut();
script.probes += 1;
Ok(script.probe.clone())
}
fn read(&mut self, events: &mut Vec<InputEvent>) -> io::Result<()> {
let mut script = self.0.borrow_mut();
if let Some(code) = script.read_error {
return Err(io::Error::from_raw_os_error(code));
}
events.append(&mut script.pending);
Ok(())
}
}
#[derive(Debug, Default)]
struct Fake {
nodes: BTreeMap<PathBuf, (u64, Shared)>,
scans: u32,
opens: u32,
now: Option<Instant>,
}
impl Fake {
fn plug(&mut self, name: &str, ino: u64, probe: Probe) -> Shared {
let script = Rc::new(RefCell::new(Script {
probe,
pending: Vec::new(),
open_error: None,
read_error: None,
probes: 0,
}));
self.nodes.insert(
Path::new("/dev/input").join(name),
(ino, Rc::clone(&script)),
);
script
}
fn unplug(&mut self, name: &str) {
self.nodes.remove(&Path::new("/dev/input").join(name));
}
}
impl Source for Fake {
type Device = FakeDevice;
fn scan(&mut self) -> io::Result<Vec<Node>> {
self.scans += 1;
Ok(self
.nodes
.iter()
.map(|(path, (ino, _))| Node {
path: path.clone(),
ino: *ino,
})
.collect())
}
fn open(&mut self, path: &Path) -> io::Result<FakeDevice> {
self.opens += 1;
let (_, script) = self.nodes.get(path).ok_or(io::ErrorKind::NotFound)?;
if let Some(kind) = script.borrow().open_error {
return Err(kind.into());
}
Ok(FakeDevice(Rc::clone(script)))
}
}
fn poll_after(
poller: &mut Poller<FakeDevice>,
fake: &mut Fake,
after: Duration,
) -> (Vec<GamepadEvent>, Result<(), EvdevError>) {
let now = fake.now.map_or_else(Instant::now, |now| now + after);
fake.now = Some(now);
let mut events = Vec::new();
let result = poller.poll(fake, now, &mut |event| events.push(event));
(events, result)
}
fn poll(
poller: &mut Poller<FakeDevice>,
fake: &mut Fake,
) -> (Vec<GamepadEvent>, Result<(), EvdevError>) {
poll_after(poller, fake, RESCAN_INTERVAL)
}
const fn key(code: u16, value: i32) -> InputEvent {
InputEvent::new(EV_KEY, code, value)
}
const fn abs(code: u16, value: i32) -> InputEvent {
InputEvent::new(EV_ABS, code, value)
}
const REPORT: InputEvent = InputEvent::new(EV_SYN, SYN_REPORT, 0);
const DROPPED: InputEvent = InputEvent::new(EV_SYN, SYN_DROPPED, 0);
fn keyboard() -> Probe {
map::tests::probe(map::tests::XBOX, &[1, 30, 57], &[])
}
#[test]
fn transitions_are_reported_once_each() {
let mut poller = Poller::default();
let mut fake = Fake::default();
assert_eq!(poll(&mut poller, &mut fake), (vec![], Ok(())), "no nodes");
let pad = fake.plug("event3", 10, xpad());
let (events, result) = poll(&mut poller, &mut fake);
assert_eq!(result, Ok(()));
let [GamepadEvent::Connected { id, kind }] = events[..] else {
panic!("a pad at rest connects with no state: {events:?}");
};
assert_eq!(kind, PadKind::Xbox);
pad.borrow_mut().pending = vec![key(BTN_SOUTH, 1), abs(ABS_Y, -32768), REPORT];
let (events, _) = poll_after(&mut poller, &mut fake, Duration::ZERO);
let [GamepadEvent::State { id: same, snapshot }] = events[..] else {
panic!("one report, one state: {events:?}");
};
assert_eq!(same, id);
assert!(snapshot.buttons.contains(PadButton::South));
assert_eq!(snapshot.axis(PadAxis::LeftY), 1.0, "pushed up");
pad.borrow_mut().pending = vec![key(BTN_SOUTH, 2), REPORT];
let (events, _) = poll_after(&mut poller, &mut fake, Duration::ZERO);
assert!(events.is_empty(), "autorepeat changes nothing: {events:?}");
pad.borrow_mut().pending = vec![key(BTN_SOUTH, 0), abs(ABS_Y, 0)];
let (events, _) = poll_after(&mut poller, &mut fake, Duration::ZERO);
assert!(
events.is_empty(),
"nothing before the SYN_REPORT: {events:?}"
);
pad.borrow_mut().pending = vec![REPORT];
let (events, _) = poll_after(&mut poller, &mut fake, Duration::ZERO);
assert_eq!(
events,
[GamepadEvent::State {
id,
snapshot: GamepadSnapshot::neutral(PadKind::Xbox),
}]
);
pad.borrow_mut().read_error = Some(ENODEV);
fake.unplug("event3");
assert_eq!(
poll(&mut poller, &mut fake),
(vec![GamepadEvent::Disconnected { id }], Ok(())),
"ENODEV is an unplug, not an error"
);
fake.plug("event3", 11, xpad());
let (events, _) = poll(&mut poller, &mut fake);
let [GamepadEvent::Connected { id: again, .. }] = events[..] else {
panic!("{events:?}");
};
assert_ne!(again, id, "a new node is a new pad");
}
#[test]
fn a_pad_connected_mid_press_reports_its_state() {
let mut held = xpad();
held.held.set(BTN_EAST, true);
held.axes[usize::from(ABS_X)].value = XPAD_STICK.1;
let mut poller = Poller::default();
let mut fake = Fake::default();
fake.plug("event0", 1, held);
let (events, _) = poll(&mut poller, &mut fake);
let [
GamepadEvent::Connected { id, .. },
GamepadEvent::State { id: same, snapshot },
] = events[..]
else {
panic!("{events:?}");
};
assert_eq!(same, id);
assert!(snapshot.buttons.contains(PadButton::East));
assert_eq!(snapshot.axis(PadAxis::LeftX), 1.0);
}
#[test]
fn an_unexpected_read_error_disconnects_and_is_reported() {
const EIO: i32 = 5;
let mut poller = Poller::default();
let mut fake = Fake::default();
let broken = fake.plug("event0", 1, xpad());
let fine = fake.plug("event1", 2, xpad());
let (events, _) = poll(&mut poller, &mut fake);
let [
GamepadEvent::Connected { id: first, .. },
GamepadEvent::Connected { id: second, .. },
] = events[..]
else {
panic!("{events:?}");
};
broken.borrow_mut().read_error = Some(EIO);
fine.borrow_mut().pending = vec![key(BTN_SOUTH, 1), REPORT];
let (events, result) = poll_after(&mut poller, &mut fake, Duration::ZERO);
assert_eq!(events[0], GamepadEvent::Disconnected { id: first });
assert!(
matches!(events[1], GamepadEvent::State { id, .. } if id == second),
"{events:?}"
);
let Err(EvdevError::Read { path, .. }) = result else {
panic!("{result:?}");
};
assert_eq!(path, Path::new("/dev/input/event0"));
}
#[test]
fn a_keyboard_is_not_a_pad_and_is_not_reopened() {
let mut poller = Poller::default();
let mut fake = Fake::default();
let keys = fake.plug("event0", 1, keyboard());
fake.plug("event1", 2, xpad());
let (events, _) = poll(&mut poller, &mut fake);
assert!(
matches!(events[..], [GamepadEvent::Connected { .. }]),
"only the pad: {events:?}"
);
assert_eq!(fake.opens, 2);
for _ in 0..3 {
poll(&mut poller, &mut fake).1.expect("scripted");
}
assert_eq!(fake.scans, 4);
assert_eq!(fake.opens, 2, "neither node is opened again");
assert_eq!(keys.borrow().probes, 1);
}
#[test]
fn a_reused_node_name_is_probed_again() {
let mut poller = Poller::default();
let mut fake = Fake::default();
fake.plug("event0", 1, keyboard());
assert_eq!(poll(&mut poller, &mut fake).0, []);
fake.plug("event0", 2, xpad());
let (events, _) = poll(&mut poller, &mut fake);
assert!(
matches!(events[..], [GamepadEvent::Connected { .. }]),
"{events:?}"
);
}
#[test]
fn an_unreadable_node_is_skipped_and_retried() {
let mut poller = Poller::default();
let mut fake = Fake::default();
let pad = fake.plug("event0", 1, xpad());
pad.borrow_mut().open_error = Some(io::ErrorKind::PermissionDenied);
assert_eq!(poll(&mut poller, &mut fake), (vec![], Ok(())));
assert_eq!(poll(&mut poller, &mut fake), (vec![], Ok(())));
assert_eq!(fake.opens, 2, "retried at the second scan");
pad.borrow_mut().open_error = Some(io::ErrorKind::OutOfMemory);
let (events, result) = poll(&mut poller, &mut fake);
assert!(events.is_empty());
assert!(matches!(result, Err(EvdevError::Open { .. })), "{result:?}");
let (_, between) = poll_after(&mut poller, &mut fake, Duration::ZERO);
assert_eq!(between, result, "still failing between scans");
pad.borrow_mut().open_error = None;
let (events, result) = poll(&mut poller, &mut fake);
assert_eq!(result, Ok(()));
assert!(matches!(events[..], [GamepadEvent::Connected { .. }]));
}
#[test]
fn scans_are_throttled_and_pads_are_read_every_poll() {
let mut poller = Poller::default();
let mut fake = Fake::default();
let pad = fake.plug("event0", 1, xpad());
poll(&mut poller, &mut fake).1.expect("scripted");
assert_eq!(fake.scans, 1);
fake.plug("event1", 2, xpad());
let almost = RESCAN_INTERVAL - Duration::from_millis(1);
pad.borrow_mut().pending = vec![key(BTN_SOUTH, 1), REPORT];
let (events, _) = poll_after(&mut poller, &mut fake, almost);
assert!(
matches!(events[..], [GamepadEvent::State { .. }]),
"the open pad is read, the new node not yet seen: {events:?}"
);
assert_eq!(fake.scans, 1);
let (events, _) = poll_after(&mut poller, &mut fake, Duration::from_millis(1));
assert_eq!(fake.scans, 2, "scanned once the interval passed");
assert!(
matches!(events[..], [GamepadEvent::Connected { .. }]),
"{events:?}"
);
}
#[test]
fn a_dropped_buffer_resynchronises_from_the_device() {
let mut poller = Poller::default();
let mut fake = Fake::default();
let pad = fake.plug("event0", 1, xpad());
let (events, _) = poll(&mut poller, &mut fake);
let [GamepadEvent::Connected { id, .. }] = events[..] else {
panic!("{events:?}");
};
pad.borrow_mut().probe.held.set(BTN_EAST, true);
pad.borrow_mut().pending = vec![DROPPED, key(BTN_SOUTH, 1), REPORT];
let (events, _) = poll_after(&mut poller, &mut fake, Duration::ZERO);
let [GamepadEvent::State { id: same, snapshot }] = events[..] else {
panic!("{events:?}");
};
assert_eq!(same, id);
assert!(snapshot.buttons.contains(PadButton::East), "re-read");
assert!(!snapshot.buttons.contains(PadButton::South), "discarded");
assert_eq!(
pad.borrow().probes,
2,
"one probe to connect, one to resync"
);
pad.borrow_mut().pending = vec![key(BTN_SOUTH, 1), REPORT];
let (events, _) = poll_after(&mut poller, &mut fake, Duration::ZERO);
let [GamepadEvent::State { snapshot, .. }] = events[..] else {
panic!("{events:?}");
};
assert!(
snapshot.buttons.contains(PadButton::South),
"read normally again"
);
}
#[test]
fn polled_events_drive_an_action_map() {
let mut map = ActionMap::new();
map.declare(ActionDecl {
name: "jump".to_owned(),
kind: ActionKind::Button,
bindings: vec![Binding::PadButton(PadButton::South)],
});
let mut poller = Poller::default();
let mut fake = Fake::default();
let pad = fake.plug("event0", 1, xpad());
pad.borrow_mut().probe.held.set(BTN_SOUTH, true);
let now = Instant::now();
poller
.poll(&mut fake, now, &mut |event| map.gamepad_event(&event))
.expect("scripted");
assert!(map.just_pressed("jump"));
pad.borrow_mut().read_error = Some(ENODEV);
poller
.poll(&mut fake, now, &mut |event| map.gamepad_event(&event))
.expect("scripted");
assert!(map.just_released("jump"), "unplugging releases");
}
#[test]
fn errors_name_what_failed() {
let error = EvdevError::Read {
path: PathBuf::from("/dev/input/event4"),
message: "Input/output error".to_owned(),
};
assert_eq!(
error.to_string(),
"could not read /dev/input/event4: Input/output error"
);
}
}