Skip to main content

crcbl_input/evdev/
linux.rs

1//! The real device source: `/dev/input/event*` nodes, opened non-blocking,
2//! probed with `EVIOCG*` ioctls, and read as runs of `input_event`.
3
4use super::ffi::{
5    ABS_CNT, AbsBits, AbsInfo, EV_ABS, EV_KEY, EVENT_SIZE, EVIOCGID_NR, EVIOCGKEY_NR, InputEvent,
6    InputId, KernelUlong, KeyBits, O_NONBLOCK, decode_events, evioc_read, eviocgabs_nr,
7    eviocgbit_nr, ioctl, ioctl_request,
8};
9use super::map::Probe;
10use super::{Device, Node, Poller, Source};
11use crate::GamepadEvent;
12use crate::evdev::EvdevError;
13use std::fs::{self, File, OpenOptions};
14use std::io::{self, Read};
15use std::os::fd::AsRawFd;
16use std::os::unix::fs::{DirEntryExt, OpenOptionsExt};
17use std::path::{Path, PathBuf};
18use std::time::Instant;
19
20/// Where the kernel's input nodes are.
21const INPUT_DIR: &str = "/dev/input";
22
23/// Events read per `read` call.
24const READ_BATCH: usize = 64;
25
26/// A type the kernel may fill with any bytes.
27///
28/// # Safety
29/// Every bit pattern of the type's size must be a valid value of it: plain
30/// integers, or `repr(C)` structures of them with no padding.
31unsafe trait Plain {}
32// SAFETY: four `u16`s, no padding (the layout test).
33unsafe impl Plain for InputId {}
34// SAFETY: six `i32`s, no padding (the layout test).
35unsafe impl Plain for AbsInfo {}
36// SAFETY: an array of integers.
37unsafe impl<const WORDS: usize> Plain for [KernelUlong; WORDS] {}
38
39/// Runs the evdev request `nr` on `file`, with the kernel writing into `out`.
40///
41/// The request's size field is `size_of::<T>()`, so the size the kernel is
42/// told is the size of `out` by construction, not by a caller's care.
43fn read_ioctl<T: Plain>(file: &File, nr: u32, out: &mut T) -> io::Result<()> {
44    let request = ioctl_request(evioc_read(nr, size_of::<T>()));
45    // SAFETY: `file` is open for the call. Every `EVIOCG*` request copies at
46    // most the request's size field of bytes to the pointer, and that field
47    // is `size_of::<T>()`, the size of the exclusive borrow `out`; `T: Plain`
48    // makes any bytes the kernel writes a valid `T`. A node that is not an
49    // evdev device answers `ENOTTY` and writes nothing.
50    let result = unsafe {
51        ioctl(
52            file.as_raw_fd(),
53            request,
54            core::ptr::from_mut(out).cast::<core::ffi::c_void>(),
55        )
56    };
57    if result < 0 {
58        Err(io::Error::last_os_error())
59    } else {
60        Ok(())
61    }
62}
63
64/// `/dev/input`, or a test's stand-in directory.
65#[derive(Debug)]
66struct InputDir {
67    dir: PathBuf,
68}
69
70impl Source for InputDir {
71    type Device = EventNode;
72
73    fn scan(&mut self) -> io::Result<Vec<Node>> {
74        let mut nodes = Vec::new();
75        for entry in fs::read_dir(&self.dir)? {
76            let entry = entry?;
77            if entry.file_name().as_encoded_bytes().starts_with(b"event") {
78                nodes.push(Node {
79                    path: entry.path(),
80                    ino: entry.ino(),
81                });
82            }
83        }
84        nodes.sort();
85        Ok(nodes)
86    }
87
88    fn open(&mut self, path: &Path) -> io::Result<EventNode> {
89        // `std` adds `O_CLOEXEC` itself.
90        let file = OpenOptions::new()
91            .read(true)
92            .custom_flags(O_NONBLOCK)
93            .open(path)?;
94        Ok(EventNode { file })
95    }
96}
97
98/// One open event node.
99#[derive(Debug)]
100struct EventNode {
101    file: File,
102}
103
104impl Device for EventNode {
105    fn probe(&mut self) -> io::Result<Probe> {
106        let mut probe = Probe {
107            id: InputId::default(),
108            keys: KeyBits::EMPTY,
109            abs: AbsBits::EMPTY,
110            held: KeyBits::EMPTY,
111            axes: [AbsInfo::default(); ABS_CNT],
112        };
113        read_ioctl(&self.file, EVIOCGID_NR, &mut probe.id)?;
114        read_ioctl(&self.file, eviocgbit_nr(EV_KEY), &mut probe.keys.0)?;
115        read_ioctl(&self.file, eviocgbit_nr(EV_ABS), &mut probe.abs.0)?;
116        read_ioctl(&self.file, EVIOCGKEY_NR, &mut probe.held.0)?;
117        for code in (0..ABS_CNT as u16).filter(|&code| probe.abs.has(code)) {
118            read_ioctl(
119                &self.file,
120                eviocgabs_nr(code),
121                &mut probe.axes[usize::from(code)],
122            )?;
123        }
124        Ok(probe)
125    }
126
127    fn read(&mut self, events: &mut Vec<InputEvent>) -> io::Result<()> {
128        let mut buffer = [0; READ_BATCH * EVENT_SIZE];
129        loop {
130            match self.file.read(&mut buffer) {
131                Ok(read) => {
132                    events.extend(decode_events(&buffer[..read])?);
133                    if read < buffer.len() {
134                        return Ok(());
135                    }
136                }
137                Err(error) if error.kind() == io::ErrorKind::WouldBlock => return Ok(()),
138                Err(error) if error.kind() == io::ErrorKind::Interrupted => {}
139                Err(error) => return Err(error),
140            }
141        }
142    }
143}
144
145/// The evdev pads on this machine, and their connection state.
146#[derive(Debug)]
147pub struct Evdev {
148    source: InputDir,
149    poller: Poller<EventNode>,
150}
151
152impl Evdev {
153    /// Pads under `/dev/input`, none of them found yet: the first
154    /// [`poll`](Self::poll) scans.
155    #[must_use]
156    pub fn new() -> Self {
157        Self {
158            source: InputDir {
159                dir: PathBuf::from(INPUT_DIR),
160            },
161            poller: Poller::default(),
162        }
163    }
164
165    /// Reads every open pad and calls `emit` with what changed: connections,
166    /// disconnections, and snapshots that differ from the last one reported.
167    /// Open pads are read on every call; `/dev/input` is re-scanned for new
168    /// ones only once [`RESCAN_INTERVAL`](super::RESCAN_INTERVAL) has passed
169    /// since the last scan.
170    ///
171    /// # Errors
172    /// An [`EvdevError`] for the last thing that went wrong unexpectedly; see
173    /// its variants. Everything else was still polled.
174    pub fn poll(&mut self, mut emit: impl FnMut(GamepadEvent)) -> Result<(), EvdevError> {
175        self.poller
176            .poll(&mut self.source, Instant::now(), &mut emit)
177    }
178}
179
180impl Default for Evdev {
181    fn default() -> Self {
182        Self::new()
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189    use crate::evdev::ffi::{ABS_Y, EV_SYN, SYN_REPORT};
190
191    /// A fresh directory under the system temp dir, removed by name on drop.
192    struct Scratch {
193        dir: PathBuf,
194        files: Vec<PathBuf>,
195    }
196
197    impl Scratch {
198        fn new(name: &str) -> Self {
199            let dir =
200                std::env::temp_dir().join(format!("crcbl-evdev-{name}-{}", std::process::id()));
201            fs::create_dir(&dir).expect("a fresh scratch dir");
202            Self {
203                dir,
204                files: Vec::new(),
205            }
206        }
207
208        fn file(&mut self, name: &str, bytes: &[u8]) -> PathBuf {
209            let path = self.dir.join(name);
210            fs::write(&path, bytes).expect("scratch file written");
211            self.files.push(path.clone());
212            path
213        }
214    }
215
216    impl Drop for Scratch {
217        fn drop(&mut self) {
218            for file in &self.files {
219                fs::remove_file(file).expect("scratch file removed");
220            }
221            fs::remove_dir(&self.dir).expect("scratch dir removed");
222        }
223    }
224
225    /// **A scan lists the `event*` nodes and nothing else**, sorted, each
226    /// with its inode.
227    #[test]
228    fn a_scan_lists_only_event_nodes() {
229        let mut scratch = Scratch::new("scan");
230        let event1 = scratch.file("event1", b"");
231        scratch.file("js0", b"");
232        scratch.file("mouse0", b"");
233        let event0 = scratch.file("event0", b"");
234        let mut source = InputDir {
235            dir: scratch.dir.clone(),
236        };
237        let nodes = source.scan().expect("a readable dir");
238        let paths: Vec<_> = nodes.iter().map(|node| node.path.clone()).collect();
239        assert_eq!(paths, [event0, event1]);
240        assert_ne!(nodes[0].ino, nodes[1].ino);
241    }
242
243    /// **The real `ioctl` answers a node that is not an evdev device with
244    /// `ENOTTY`**, and the poller reports that as an error rather than a pad
245    /// or silence.
246    #[test]
247    fn a_non_evdev_node_fails_its_probe_with_enotty() {
248        const ENOTTY: i32 = 25;
249        let mut scratch = Scratch::new("probe");
250        let path = scratch.file("event0", b"");
251        let mut source = InputDir {
252            dir: scratch.dir.clone(),
253        };
254        let mut node = source.open(&path).expect("a regular file opens");
255        let error = node.probe().expect_err("a regular file has no EVIOCGID");
256        assert_eq!(error.raw_os_error(), Some(ENOTTY), "{error}");
257
258        let mut events = Vec::new();
259        let result = Poller::default().poll(&mut source, Instant::now(), &mut |event| {
260            events.push(event);
261        });
262        assert!(
263            matches!(&result, Err(EvdevError::Open { path: failed, .. }) if *failed == path),
264            "{result:?}"
265        );
266        assert!(events.is_empty());
267    }
268
269    /// **The real `read` path decodes what the kernel's layout puts down**:
270    /// a regular file holding two events, read through `EventNode`.
271    #[test]
272    fn a_read_decodes_whole_events() {
273        let mut bytes = Vec::new();
274        for event in [
275            InputEvent::new(EV_ABS, ABS_Y, 32767),
276            InputEvent::new(EV_SYN, SYN_REPORT, 0),
277        ] {
278            bytes.extend_from_slice(&[0; 2 * size_of::<KernelUlong>()]);
279            bytes.extend_from_slice(&event.kind.to_ne_bytes());
280            bytes.extend_from_slice(&event.code.to_ne_bytes());
281            bytes.extend_from_slice(&event.value.to_ne_bytes());
282        }
283        let mut scratch = Scratch::new("read");
284        let path = scratch.file("event0", &bytes);
285        let mut node = InputDir {
286            dir: scratch.dir.clone(),
287        }
288        .open(&path)
289        .expect("a regular file opens");
290        let mut events = Vec::new();
291        node.read(&mut events).expect("whole events");
292        assert_eq!(
293            events,
294            [
295                InputEvent::new(EV_ABS, ABS_Y, 32767),
296                InputEvent::new(EV_SYN, SYN_REPORT, 0),
297            ]
298        );
299    }
300}