Skip to main content

crcbl_input/
evdev.rs

1//! The Linux gamepad backend: evdev, polled, onto the gamepad seam.
2//!
3//! [`Evdev::new`] names `/dev/input`, and [`Evdev::poll`] reads every open pad
4//! and hands what changed to a closure as [`GamepadEvent`]s — the same shape
5//! as `xinput`, so a game consumes it the same way:
6//!
7//! ```no_run
8//! # #[cfg(target_os = "linux")]
9//! # fn main() -> Result<(), crcbl_input::evdev::EvdevError> {
10//! use crcbl_input::{GamepadEvent, Stick};
11//!
12//! let mut pads = crcbl_input::evdev::Evdev::new();
13//! // Once a frame, before the frame's input is read:
14//! pads.poll(|event| {
15//!     if let GamepadEvent::State { id, snapshot } = event {
16//!         let (x, y) = snapshot.stick(Stick::Left); // raw, +Y up, no dead zone
17//!         println!("pad {} left stick at ({x}, {y})", id.0);
18//!     }
19//! })?;
20//! # Ok(())
21//! # }
22//! # #[cfg(not(target_os = "linux"))]
23//! # fn main() {}
24//! ```
25//!
26//! # Which nodes are pads
27//!
28//! Every `/dev/input/event*` node is opened read-only and non-blocking and
29//! probed: `EVIOCGBIT` for its keys and axes, and it is a pad if it has
30//! `BTN_GAMEPAD` and both `ABS_X` and `ABS_Y`. A node that is not a pad is
31//! closed and not probed again while the same node stays in the directory. A
32//! node that cannot be opened — a keyboard's is usually root-only, and a
33//! pad's is too for the moment before udev grants the seat user access — is
34//! skipped without an error and tried again at the next scan, since what cannot
35//! be opened cannot be told apart from a pad. **A pad whose node the user
36//! cannot read is therefore never found**: desktop distributions and SteamOS
37//! grant it through udev's `uaccess` tag, which systemd's rules put on
38//! joysticks.
39//!
40//! # What it reports
41//!
42//! - **Connected** when a pad node appears, with a fresh [`GamepadId`] and the
43//!   [`PadKind`](crate::PadKind) its USB vendor and product name, then a **State** if the pad
44//!   is not at rest (the probe reads what is held and where every axis is).
45//! - **State** at each `SYN_REPORT` whose mapped snapshot differs from the
46//!   last one reported — change-only, as `xinput` does and for the same
47//!   reasons: the seam's snapshot is a level, and an identical one carries
48//!   nothing.
49//! - **Disconnected** when a read fails: `ENODEV` is the ordinary unplug and
50//!   is not an error; anything else also returns an [`EvdevError`]. A pad
51//!   plugged back in is a new node and a new id.
52//!
53//! After `SYN_DROPPED` — the kernel's buffer overflowed and events were lost —
54//! everything up to the next `SYN_REPORT` is discarded and the pad's keys and
55//! axes are read afresh, as the evdev docs prescribe.
56//!
57//! # New pads are found by re-scanning
58//!
59//! Open pads are read on every poll. `/dev/input` is listed again only once
60//! [`RESCAN_INTERVAL`] has passed since the last scan, so a pad plugged in is
61//! reported at most that late — the same trade as `xinput`'s re-probe, and
62//! the same one second. inotify would report it at once, at the price of one
63//! more descriptor and its own event parsing; nothing has needed it yet.
64//!
65//! # The mapping
66//!
67//! | evdev                                   | seam                                 |
68//! | --------------------------------------- | ------------------------------------ |
69//! | `BTN_SOUTH`, `BTN_EAST`                 | `South`, `East`                      |
70//! | `BTN_NORTH` (`BTN_X`), `BTN_WEST` (`BTN_Y`) | `West`, `North` on Xbox-style drivers; `North`, `West` on Sony and Nintendo ones (below) |
71//! | `BTN_TL`, `BTN_TR`                      | `LeftShoulder`, `RightShoulder`      |
72//! | `BTN_THUMBL`, `BTN_THUMBR`              | `LeftStick`, `RightStick`            |
73//! | `BTN_START`, `BTN_SELECT`, `BTN_MODE`   | `Start`, `Select`, `Guide`           |
74//! | `BTN_DPAD_*`, or else `ABS_HAT0X`/`Y`   | `DpadUp`/`Down`/`Left`/`Right`       |
75//! | `ABS_X`, `ABS_Y`, `ABS_RX`, `ABS_RY`    | the sticks, **Y negated** (evdev's +Y is down) |
76//! | `ABS_Z` / `ABS_BRAKE` / `ABS_HAT2Y`     | `LeftTrigger`, first present; `BTN_TL2` if none |
77//! | `ABS_RZ` / `ABS_GAS` / `ABS_HAT2X`      | `RightTrigger`, first present; `BTN_TR2` if none |
78//!
79//! Sticks are scaled by [`stick_axis`] and triggers by [`trigger_axis`], each
80//! from the axis's own `EVIOCGABS` minimum and maximum. The driver's `flat` is
81//! read but **not applied**: the seam's axes are raw and the dead zone is the
82//! binding's (`gamepad.rs`).
83//!
84//! **The top and left face buttons depend on the driver**, the one place the
85//! kernel disagrees with itself. The header spells `BTN_NORTH` and `BTN_X` as
86//! one code, and `BTN_WEST` and `BTN_Y` as another; `hid-playstation`,
87//! `hid-sony` and `hid-nintendo` send the positional names, while `xpad`,
88//! Steam Input's virtual pad and `hid-steam` send the Xbox letters, so their
89//! X — the left button — arrives as `BTN_NORTH`. The split is by USB vendor:
90//! Sony and Nintendo positional, everything else lettered. Other drivers that
91//! disagree are the "quirk zoo" the input design scopes out; the table
92//! grows by demand.
93//!
94//! # Steam Input's virtual pad is read like any other
95//!
96//! Valve's vendor id, 0x28DE, is **not** filtered out. Without Steam Input,
97//! Steam's gamepad emulation reaching this backend as an ordinary controller
98//! is how a game launched from Steam on a Deck gets the built-in controls. That
99//! emulated pad is the only copy: per `hid-steam`'s source, the driver
100//! removes its own gamepad node while Steam holds the controller's hidraw
101//! node, so one physical pad is not reported twice. Unverified on a Deck. A
102//! game that turns Steam Input on (`crcbl-steam`'s `SteamPads`) would see the
103//! pad twice, once from each, so `crcbl::engine::steam::steam_input` replaces
104//! this backend rather than polling beside it; a vendor filter here is what
105//! would let the two run together, and `docs/backlog.md` carries it.
106//!
107//! # Not on other targets
108//!
109//! This module is compiled on Linux (and into every target's tests, which is
110//! how the mapping, the layouts and the poller are checked on the Windows and
111//! macOS runners); only the device access is Linux-only. No other target gets
112//! a stand-in that answers "no pads".
113
114mod ffi;
115#[cfg(target_os = "linux")]
116mod linux;
117mod map;
118
119use crate::{GamepadEvent, GamepadId, GamepadSnapshot};
120use ffi::{ABS_CNT, EV_ABS, EV_KEY, EV_SYN, InputEvent, KeyBits, SYN_DROPPED, SYN_REPORT};
121#[cfg(target_os = "linux")]
122pub use linux::Evdev;
123use map::{Layout, Probe};
124pub use map::{stick_axis, trigger_axis};
125use std::collections::HashSet;
126use std::io;
127use std::path::{Path, PathBuf};
128use std::time::{Duration, Instant};
129
130/// What went wrong reading evdev. Each variant carries the OS error's text.
131#[derive(Clone, Debug, PartialEq, Eq)]
132pub enum EvdevError {
133    /// `/dev/input` could not be listed, so no new pad can be found. Open pads
134    /// were still read.
135    Scan {
136        /// What the OS said.
137        message: String,
138    },
139    /// A node opened but could not be probed, or failed to open for a reason
140    /// other than permission or its having gone. It is tried again at the next
141    /// scan.
142    Open {
143        /// The node.
144        path: PathBuf,
145        /// What the OS said.
146        message: String,
147    },
148    /// Reading an open pad failed with something other than `ENODEV`. The pad
149    /// was reported as disconnected; its node is found again at a later scan
150    /// if it is still there.
151    Read {
152        /// The node.
153        path: PathBuf,
154        /// What the OS said.
155        message: String,
156    },
157}
158
159impl std::fmt::Display for EvdevError {
160    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161        match self {
162            Self::Scan { message } => write!(f, "could not list /dev/input: {message}"),
163            Self::Open { path, message } => {
164                write!(f, "could not probe {}: {message}", path.display())
165            }
166            Self::Read { path, message } => {
167                write!(f, "could not read {}: {message}", path.display())
168            }
169        }
170    }
171}
172
173impl std::error::Error for EvdevError {}
174
175/// How long `/dev/input` goes unlisted between scans for new pads.
176///
177/// A second, as `xinput`'s `REPROBE_INTERVAL` is and for its reason: a pad
178/// takes a moment to enumerate anyway, and a scan opens and probes every node
179/// it has not placed yet.
180pub const RESCAN_INTERVAL: Duration = Duration::from_secs(1);
181
182/// One directory entry that may be a device: its path, and its inode, which
183/// changes when the kernel reuses the name for another device.
184#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
185struct Node {
186    path: PathBuf,
187    ino: u64,
188}
189
190/// An open node the poller reads: the real `EventNode`, or a test's script.
191trait Device {
192    /// The device's ids, capabilities and current state.
193    fn probe(&mut self) -> io::Result<Probe>;
194    /// Appends every event waiting on the node, returning once none is left.
195    fn read(&mut self, events: &mut Vec<InputEvent>) -> io::Result<()>;
196}
197
198/// Where the poller finds nodes: `/dev/input`, or a test's script.
199trait Source {
200    /// The open node type.
201    type Device: Device;
202    /// Every node that may be a device, now.
203    fn scan(&mut self) -> io::Result<Vec<Node>>;
204    /// Opens one.
205    fn open(&mut self, path: &Path) -> io::Result<Self::Device>;
206}
207
208/// Whether a failed open is the ordinary kind a scan skips without an error:
209/// a node the user may not read, or one gone between the listing and the open.
210fn skipped_quietly(error: &io::Error) -> bool {
211    matches!(
212        error.kind(),
213        io::ErrorKind::PermissionDenied | io::ErrorKind::NotFound
214    ) || error.raw_os_error() == Some(ffi::ENODEV)
215}
216
217/// A connected pad.
218#[derive(Debug)]
219struct Pad<D> {
220    node: Node,
221    device: D,
222    id: GamepadId,
223    layout: Layout,
224    /// The keys held, as far as the events read so far say.
225    held: KeyBits,
226    /// Every axis's value, likewise.
227    values: [i32; ABS_CNT],
228    /// Between a `SYN_DROPPED` and the `SYN_REPORT` that ends it.
229    dropping: bool,
230    /// The snapshot last reported.
231    last: GamepadSnapshot,
232}
233
234impl<D: Device> Pad<D> {
235    /// Takes the state `probe` read as the pad's own.
236    fn take_state(&mut self, probe: &Probe) {
237        self.held = probe.held;
238        self.values = probe.values();
239    }
240
241    /// Emits a State if the snapshot differs from the last one reported.
242    fn report(&mut self, emit: &mut impl FnMut(GamepadEvent)) {
243        let snapshot = self.layout.snapshot(&self.held, &self.values);
244        if snapshot != self.last {
245            self.last = snapshot;
246            emit(GamepadEvent::State {
247                id: self.id,
248                snapshot,
249            });
250        }
251    }
252
253    /// Reads every waiting event and reports at each `SYN_REPORT`.
254    fn pump(
255        &mut self,
256        events: &mut Vec<InputEvent>,
257        emit: &mut impl FnMut(GamepadEvent),
258    ) -> io::Result<()> {
259        events.clear();
260        self.device.read(events)?;
261        for event in events.iter() {
262            match (event.kind, event.code) {
263                (EV_SYN, SYN_DROPPED) => self.dropping = true,
264                (EV_SYN, SYN_REPORT) => {
265                    if self.dropping {
266                        let probe = self.device.probe()?;
267                        self.take_state(&probe);
268                        self.dropping = false;
269                    }
270                    self.report(emit);
271                }
272                _ if self.dropping => {}
273                (EV_KEY, code) => self.held.set(code, event.value != 0),
274                (EV_ABS, code) => {
275                    if let Some(value) = self.values.get_mut(usize::from(code)) {
276                        *value = event.value;
277                    }
278                }
279                _ => {}
280            }
281        }
282        Ok(())
283    }
284}
285
286/// The open pads, the nodes known not to be pads, and the scan clock.
287#[derive(Debug)]
288struct Poller<D> {
289    pads: Vec<Pad<D>>,
290    /// Nodes probed and found not to be pads, forgotten once they leave the
291    /// directory so a reused name is probed afresh.
292    not_pads: HashSet<Node>,
293    /// When `/dev/input` was last listed.
294    scanned: Option<Instant>,
295    /// What went wrong at the last scan, returned by every poll until the
296    /// next scan says otherwise — so a node that keeps failing its probe
297    /// reads as one lasting failure, not one that clears between scans.
298    scan_failure: Option<EvdevError>,
299    /// The read buffer, kept between polls.
300    events: Vec<InputEvent>,
301}
302
303impl<D> Default for Poller<D> {
304    fn default() -> Self {
305        Self {
306            pads: Vec::new(),
307            not_pads: HashSet::new(),
308            scanned: None,
309            scan_failure: None,
310            events: Vec::new(),
311        }
312    }
313}
314
315impl<D: Device> Poller<D> {
316    /// Reads every open pad, then scans for new ones if the interval has
317    /// passed, emitting what changed — see the module docs. `now` is the
318    /// caller's, so a test can step it.
319    fn poll<S: Source<Device = D>>(
320        &mut self,
321        source: &mut S,
322        now: Instant,
323        emit: &mut impl FnMut(GamepadEvent),
324    ) -> Result<(), EvdevError> {
325        let mut failure = None;
326        let events = &mut self.events;
327        self.pads.retain_mut(|pad| match pad.pump(events, emit) {
328            Ok(()) => true,
329            Err(error) => {
330                emit(GamepadEvent::Disconnected { id: pad.id });
331                if error.raw_os_error() != Some(ffi::ENODEV) {
332                    failure = Some(EvdevError::Read {
333                        path: pad.node.path.clone(),
334                        message: error.to_string(),
335                    });
336                }
337                false
338            }
339        });
340
341        if self
342            .scanned
343            .is_none_or(|at| now.duration_since(at) >= RESCAN_INTERVAL)
344        {
345            self.scanned = Some(now);
346            self.scan_failure = None;
347            match source.scan() {
348                Ok(nodes) => {
349                    self.not_pads.retain(|node| nodes.contains(node));
350                    for node in nodes {
351                        if let Err(error) = self.connect(source, node, emit) {
352                            self.scan_failure = Some(error);
353                        }
354                    }
355                }
356                Err(error) => {
357                    self.scan_failure = Some(EvdevError::Scan {
358                        message: error.to_string(),
359                    });
360                }
361            }
362        }
363        match failure.or_else(|| self.scan_failure.clone()) {
364            Some(failure) => Err(failure),
365            None => Ok(()),
366        }
367    }
368
369    /// Opens and probes `node` unless it is already placed, and connects it
370    /// if it is a pad.
371    fn connect<S: Source<Device = D>>(
372        &mut self,
373        source: &mut S,
374        node: Node,
375        emit: &mut impl FnMut(GamepadEvent),
376    ) -> Result<(), EvdevError> {
377        if self.not_pads.contains(&node) || self.pads.iter().any(|pad| pad.node == node) {
378            return Ok(());
379        }
380        let opened = source.open(&node.path).and_then(|mut device| {
381            let probe = device.probe()?;
382            Ok((device, probe))
383        });
384        let (device, probe) = match opened {
385            Ok(opened) => opened,
386            Err(error) if skipped_quietly(&error) => return Ok(()),
387            Err(error) => {
388                return Err(EvdevError::Open {
389                    path: node.path,
390                    message: error.to_string(),
391                });
392            }
393        };
394        if !map::is_gamepad(&probe.keys, &probe.abs) {
395            self.not_pads.insert(node);
396            return Ok(());
397        }
398        let layout = Layout::new(&probe);
399        let id = GamepadId::allocate();
400        let kind = layout.kind;
401        emit(GamepadEvent::Connected { id, kind });
402        let mut pad = Pad {
403            node,
404            device,
405            id,
406            layout,
407            held: KeyBits::EMPTY,
408            values: [0; ABS_CNT],
409            dropping: false,
410            last: GamepadSnapshot::neutral(kind),
411        };
412        pad.take_state(&probe);
413        pad.report(emit);
414        self.pads.push(pad);
415        Ok(())
416    }
417}
418
419#[cfg(test)]
420mod tests {
421    use super::*;
422    use crate::{ActionDecl, ActionKind, ActionMap, Binding, PadAxis, PadButton, PadKind};
423    use ffi::{ABS_X, ABS_Y, BTN_EAST, BTN_SOUTH, ENODEV};
424    use map::tests::{XPAD_STICK, xpad};
425    use std::cell::RefCell;
426    use std::collections::BTreeMap;
427    use std::rc::Rc;
428
429    /// One scripted node: what a probe answers, the events waiting on it, and
430    /// the errors it is set to fail with.
431    #[derive(Debug)]
432    struct Script {
433        probe: Probe,
434        pending: Vec<InputEvent>,
435        open_error: Option<io::ErrorKind>,
436        read_error: Option<i32>,
437        probes: u32,
438    }
439
440    type Shared = Rc<RefCell<Script>>;
441
442    #[derive(Debug)]
443    struct FakeDevice(Shared);
444
445    impl Device for FakeDevice {
446        fn probe(&mut self) -> io::Result<Probe> {
447            let mut script = self.0.borrow_mut();
448            script.probes += 1;
449            Ok(script.probe.clone())
450        }
451
452        fn read(&mut self, events: &mut Vec<InputEvent>) -> io::Result<()> {
453            let mut script = self.0.borrow_mut();
454            if let Some(code) = script.read_error {
455                return Err(io::Error::from_raw_os_error(code));
456            }
457            events.append(&mut script.pending);
458            Ok(())
459        }
460    }
461
462    /// A directory of scripted nodes, by name and inode, counting scans and
463    /// opens.
464    #[derive(Debug, Default)]
465    struct Fake {
466        nodes: BTreeMap<PathBuf, (u64, Shared)>,
467        scans: u32,
468        opens: u32,
469        now: Option<Instant>,
470    }
471
472    impl Fake {
473        /// Puts a node with `probe` at `/dev/input/{name}` with inode `ino`,
474        /// returning its script.
475        fn plug(&mut self, name: &str, ino: u64, probe: Probe) -> Shared {
476            let script = Rc::new(RefCell::new(Script {
477                probe,
478                pending: Vec::new(),
479                open_error: None,
480                read_error: None,
481                probes: 0,
482            }));
483            self.nodes.insert(
484                Path::new("/dev/input").join(name),
485                (ino, Rc::clone(&script)),
486            );
487            script
488        }
489
490        fn unplug(&mut self, name: &str) {
491            self.nodes.remove(&Path::new("/dev/input").join(name));
492        }
493    }
494
495    impl Source for Fake {
496        type Device = FakeDevice;
497
498        fn scan(&mut self) -> io::Result<Vec<Node>> {
499            self.scans += 1;
500            Ok(self
501                .nodes
502                .iter()
503                .map(|(path, (ino, _))| Node {
504                    path: path.clone(),
505                    ino: *ino,
506                })
507                .collect())
508        }
509
510        fn open(&mut self, path: &Path) -> io::Result<FakeDevice> {
511            self.opens += 1;
512            let (_, script) = self.nodes.get(path).ok_or(io::ErrorKind::NotFound)?;
513            if let Some(kind) = script.borrow().open_error {
514                return Err(kind.into());
515            }
516            Ok(FakeDevice(Rc::clone(script)))
517        }
518    }
519
520    /// Polls `after` the fake's clock, moving the clock there.
521    fn poll_after(
522        poller: &mut Poller<FakeDevice>,
523        fake: &mut Fake,
524        after: Duration,
525    ) -> (Vec<GamepadEvent>, Result<(), EvdevError>) {
526        let now = fake.now.map_or_else(Instant::now, |now| now + after);
527        fake.now = Some(now);
528        let mut events = Vec::new();
529        let result = poller.poll(fake, now, &mut |event| events.push(event));
530        (events, result)
531    }
532
533    /// Polls a whole scan interval after the last poll, so a scan runs.
534    fn poll(
535        poller: &mut Poller<FakeDevice>,
536        fake: &mut Fake,
537    ) -> (Vec<GamepadEvent>, Result<(), EvdevError>) {
538        poll_after(poller, fake, RESCAN_INTERVAL)
539    }
540
541    const fn key(code: u16, value: i32) -> InputEvent {
542        InputEvent::new(EV_KEY, code, value)
543    }
544    const fn abs(code: u16, value: i32) -> InputEvent {
545        InputEvent::new(EV_ABS, code, value)
546    }
547    const REPORT: InputEvent = InputEvent::new(EV_SYN, SYN_REPORT, 0);
548    const DROPPED: InputEvent = InputEvent::new(EV_SYN, SYN_DROPPED, 0);
549
550    /// A keyboard: keys, no pad button, no axes.
551    fn keyboard() -> Probe {
552        map::tests::probe(map::tests::XBOX, &[1, 30, 57], &[])
553    }
554
555    /// **Connect, change, disconnect on `ENODEV`, reconnect**, each reported
556    /// once and only when it happens, with a fresh id for the new node.
557    #[test]
558    fn transitions_are_reported_once_each() {
559        let mut poller = Poller::default();
560        let mut fake = Fake::default();
561        assert_eq!(poll(&mut poller, &mut fake), (vec![], Ok(())), "no nodes");
562
563        let pad = fake.plug("event3", 10, xpad());
564        let (events, result) = poll(&mut poller, &mut fake);
565        assert_eq!(result, Ok(()));
566        let [GamepadEvent::Connected { id, kind }] = events[..] else {
567            panic!("a pad at rest connects with no state: {events:?}");
568        };
569        assert_eq!(kind, PadKind::Xbox);
570
571        pad.borrow_mut().pending = vec![key(BTN_SOUTH, 1), abs(ABS_Y, -32768), REPORT];
572        let (events, _) = poll_after(&mut poller, &mut fake, Duration::ZERO);
573        let [GamepadEvent::State { id: same, snapshot }] = events[..] else {
574            panic!("one report, one state: {events:?}");
575        };
576        assert_eq!(same, id);
577        assert!(snapshot.buttons.contains(PadButton::South));
578        assert_eq!(snapshot.axis(PadAxis::LeftY), 1.0, "pushed up");
579
580        pad.borrow_mut().pending = vec![key(BTN_SOUTH, 2), REPORT];
581        let (events, _) = poll_after(&mut poller, &mut fake, Duration::ZERO);
582        assert!(events.is_empty(), "autorepeat changes nothing: {events:?}");
583
584        pad.borrow_mut().pending = vec![key(BTN_SOUTH, 0), abs(ABS_Y, 0)];
585        let (events, _) = poll_after(&mut poller, &mut fake, Duration::ZERO);
586        assert!(
587            events.is_empty(),
588            "nothing before the SYN_REPORT: {events:?}"
589        );
590        pad.borrow_mut().pending = vec![REPORT];
591        let (events, _) = poll_after(&mut poller, &mut fake, Duration::ZERO);
592        assert_eq!(
593            events,
594            [GamepadEvent::State {
595                id,
596                snapshot: GamepadSnapshot::neutral(PadKind::Xbox),
597            }]
598        );
599
600        pad.borrow_mut().read_error = Some(ENODEV);
601        fake.unplug("event3");
602        assert_eq!(
603            poll(&mut poller, &mut fake),
604            (vec![GamepadEvent::Disconnected { id }], Ok(())),
605            "ENODEV is an unplug, not an error"
606        );
607
608        fake.plug("event3", 11, xpad());
609        let (events, _) = poll(&mut poller, &mut fake);
610        let [GamepadEvent::Connected { id: again, .. }] = events[..] else {
611            panic!("{events:?}");
612        };
613        assert_ne!(again, id, "a new node is a new pad");
614    }
615
616    /// A pad that is not at rest when it connects says so at once.
617    #[test]
618    fn a_pad_connected_mid_press_reports_its_state() {
619        let mut held = xpad();
620        held.held.set(BTN_EAST, true);
621        held.axes[usize::from(ABS_X)].value = XPAD_STICK.1;
622        let mut poller = Poller::default();
623        let mut fake = Fake::default();
624        fake.plug("event0", 1, held);
625        let (events, _) = poll(&mut poller, &mut fake);
626        let [
627            GamepadEvent::Connected { id, .. },
628            GamepadEvent::State { id: same, snapshot },
629        ] = events[..]
630        else {
631            panic!("{events:?}");
632        };
633        assert_eq!(same, id);
634        assert!(snapshot.buttons.contains(PadButton::East));
635        assert_eq!(snapshot.axis(PadAxis::LeftX), 1.0);
636    }
637
638    /// Any read error but `ENODEV` disconnects the pad **and** is returned,
639    /// and the other pads are still read.
640    #[test]
641    fn an_unexpected_read_error_disconnects_and_is_reported() {
642        const EIO: i32 = 5;
643        let mut poller = Poller::default();
644        let mut fake = Fake::default();
645        let broken = fake.plug("event0", 1, xpad());
646        let fine = fake.plug("event1", 2, xpad());
647        let (events, _) = poll(&mut poller, &mut fake);
648        let [
649            GamepadEvent::Connected { id: first, .. },
650            GamepadEvent::Connected { id: second, .. },
651        ] = events[..]
652        else {
653            panic!("{events:?}");
654        };
655
656        broken.borrow_mut().read_error = Some(EIO);
657        fine.borrow_mut().pending = vec![key(BTN_SOUTH, 1), REPORT];
658        let (events, result) = poll_after(&mut poller, &mut fake, Duration::ZERO);
659        assert_eq!(events[0], GamepadEvent::Disconnected { id: first });
660        assert!(
661            matches!(events[1], GamepadEvent::State { id, .. } if id == second),
662            "{events:?}"
663        );
664        let Err(EvdevError::Read { path, .. }) = result else {
665            panic!("{result:?}");
666        };
667        assert_eq!(path, Path::new("/dev/input/event0"));
668    }
669
670    /// **A keyboard is probed once and then left alone** while its node
671    /// stays; a pad beside it connects.
672    #[test]
673    fn a_keyboard_is_not_a_pad_and_is_not_reopened() {
674        let mut poller = Poller::default();
675        let mut fake = Fake::default();
676        let keys = fake.plug("event0", 1, keyboard());
677        fake.plug("event1", 2, xpad());
678        let (events, _) = poll(&mut poller, &mut fake);
679        assert!(
680            matches!(events[..], [GamepadEvent::Connected { .. }]),
681            "only the pad: {events:?}"
682        );
683        assert_eq!(fake.opens, 2);
684        for _ in 0..3 {
685            poll(&mut poller, &mut fake).1.expect("scripted");
686        }
687        assert_eq!(fake.scans, 4);
688        assert_eq!(fake.opens, 2, "neither node is opened again");
689        assert_eq!(keys.borrow().probes, 1);
690    }
691
692    /// A node name reused by a different device — a new inode — is probed
693    /// afresh, so a pad plugged into a keyboard's old name is found.
694    #[test]
695    fn a_reused_node_name_is_probed_again() {
696        let mut poller = Poller::default();
697        let mut fake = Fake::default();
698        fake.plug("event0", 1, keyboard());
699        assert_eq!(poll(&mut poller, &mut fake).0, []);
700        fake.plug("event0", 2, xpad());
701        let (events, _) = poll(&mut poller, &mut fake);
702        assert!(
703            matches!(events[..], [GamepadEvent::Connected { .. }]),
704            "{events:?}"
705        );
706    }
707
708    /// **A node the user may not open is skipped with no error**, and tried
709    /// again at every scan, so a pad whose permissions arrive a moment late
710    /// is found. Any other open failure is returned.
711    #[test]
712    fn an_unreadable_node_is_skipped_and_retried() {
713        let mut poller = Poller::default();
714        let mut fake = Fake::default();
715        let pad = fake.plug("event0", 1, xpad());
716        pad.borrow_mut().open_error = Some(io::ErrorKind::PermissionDenied);
717        assert_eq!(poll(&mut poller, &mut fake), (vec![], Ok(())));
718        assert_eq!(poll(&mut poller, &mut fake), (vec![], Ok(())));
719        assert_eq!(fake.opens, 2, "retried at the second scan");
720
721        pad.borrow_mut().open_error = Some(io::ErrorKind::OutOfMemory);
722        let (events, result) = poll(&mut poller, &mut fake);
723        assert!(events.is_empty());
724        assert!(matches!(result, Err(EvdevError::Open { .. })), "{result:?}");
725        let (_, between) = poll_after(&mut poller, &mut fake, Duration::ZERO);
726        assert_eq!(between, result, "still failing between scans");
727
728        pad.borrow_mut().open_error = None;
729        let (events, result) = poll(&mut poller, &mut fake);
730        assert_eq!(result, Ok(()));
731        assert!(matches!(events[..], [GamepadEvent::Connected { .. }]));
732    }
733
734    /// **`/dev/input` is re-listed only once the interval has passed**, while
735    /// an open pad is read on every poll.
736    #[test]
737    fn scans_are_throttled_and_pads_are_read_every_poll() {
738        let mut poller = Poller::default();
739        let mut fake = Fake::default();
740        let pad = fake.plug("event0", 1, xpad());
741        poll(&mut poller, &mut fake).1.expect("scripted");
742        assert_eq!(fake.scans, 1);
743
744        fake.plug("event1", 2, xpad());
745        let almost = RESCAN_INTERVAL - Duration::from_millis(1);
746        pad.borrow_mut().pending = vec![key(BTN_SOUTH, 1), REPORT];
747        let (events, _) = poll_after(&mut poller, &mut fake, almost);
748        assert!(
749            matches!(events[..], [GamepadEvent::State { .. }]),
750            "the open pad is read, the new node not yet seen: {events:?}"
751        );
752        assert_eq!(fake.scans, 1);
753
754        let (events, _) = poll_after(&mut poller, &mut fake, Duration::from_millis(1));
755        assert_eq!(fake.scans, 2, "scanned once the interval passed");
756        assert!(
757            matches!(events[..], [GamepadEvent::Connected { .. }]),
758            "{events:?}"
759        );
760    }
761
762    /// **After `SYN_DROPPED`**, events up to the next `SYN_REPORT` are
763    /// discarded and the pad's state is read afresh from the device.
764    #[test]
765    fn a_dropped_buffer_resynchronises_from_the_device() {
766        let mut poller = Poller::default();
767        let mut fake = Fake::default();
768        let pad = fake.plug("event0", 1, xpad());
769        let (events, _) = poll(&mut poller, &mut fake);
770        let [GamepadEvent::Connected { id, .. }] = events[..] else {
771            panic!("{events:?}");
772        };
773
774        // The truth after the overflow: East held, not South.
775        pad.borrow_mut().probe.held.set(BTN_EAST, true);
776        pad.borrow_mut().pending = vec![DROPPED, key(BTN_SOUTH, 1), REPORT];
777        let (events, _) = poll_after(&mut poller, &mut fake, Duration::ZERO);
778        let [GamepadEvent::State { id: same, snapshot }] = events[..] else {
779            panic!("{events:?}");
780        };
781        assert_eq!(same, id);
782        assert!(snapshot.buttons.contains(PadButton::East), "re-read");
783        assert!(!snapshot.buttons.contains(PadButton::South), "discarded");
784        assert_eq!(
785            pad.borrow().probes,
786            2,
787            "one probe to connect, one to resync"
788        );
789
790        pad.borrow_mut().pending = vec![key(BTN_SOUTH, 1), REPORT];
791        let (events, _) = poll_after(&mut poller, &mut fake, Duration::ZERO);
792        let [GamepadEvent::State { snapshot, .. }] = events[..] else {
793            panic!("{events:?}");
794        };
795        assert!(
796            snapshot.buttons.contains(PadButton::South),
797            "read normally again"
798        );
799    }
800
801    /// **The poller's events drive a binding**, and an unplug releases it.
802    #[test]
803    fn polled_events_drive_an_action_map() {
804        let mut map = ActionMap::new();
805        map.declare(ActionDecl {
806            name: "jump".to_owned(),
807            kind: ActionKind::Button,
808            bindings: vec![Binding::PadButton(PadButton::South)],
809        });
810        let mut poller = Poller::default();
811        let mut fake = Fake::default();
812        let pad = fake.plug("event0", 1, xpad());
813        pad.borrow_mut().probe.held.set(BTN_SOUTH, true);
814        let now = Instant::now();
815        poller
816            .poll(&mut fake, now, &mut |event| map.gamepad_event(&event))
817            .expect("scripted");
818        assert!(map.just_pressed("jump"));
819
820        pad.borrow_mut().read_error = Some(ENODEV);
821        poller
822            .poll(&mut fake, now, &mut |event| map.gamepad_event(&event))
823            .expect("scripted");
824        assert!(map.just_released("jump"), "unplugging releases");
825    }
826
827    #[test]
828    fn errors_name_what_failed() {
829        let error = EvdevError::Read {
830            path: PathBuf::from("/dev/input/event4"),
831            message: "Input/output error".to_owned(),
832        };
833        assert_eq!(
834            error.to_string(),
835            "could not read /dev/input/event4: Input/output error"
836        );
837    }
838}