Skip to main content

denise_evdev/
device.rs

1//! Finding and reading `/dev/input/event*`.
2
3use std::mem::MaybeUninit;
4use std::os::fd::{AsFd, AsRawFd, OwnedFd, RawFd};
5use std::path::{Path, PathBuf};
6use std::time::{Duration, SystemTime};
7
8use rustix::fs::inotify;
9use rustix::io::Errno;
10
11use denise::{InputEvent, InputSource, Point, Size};
12
13use crate::codes::abs;
14use crate::error::EvdevError;
15use crate::layout::{self, Layout};
16use crate::translate::{AbsAxis, RawEvent, Translator};
17
18/// What a device can report.
19///
20/// A set rather than a single kind, because plenty of real hardware is more than
21/// one thing: a Logitech K400 is a keyboard with a touchpad on one event node, and
22/// most laptops present their touchpad and keyboard together. Picking a single
23/// label for those either loses the pointer or loses the keys.
24#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
25pub struct Capabilities {
26    /// Reports a mouse or an absolute pointing device.
27    pub pointer: bool,
28    /// Reports multitouch contacts.
29    pub touch: bool,
30    /// Reports letter keys.
31    pub keyboard: bool,
32}
33
34impl Capabilities {
35    /// Returns `true` if the device reports nothing this backend can use.
36    #[inline]
37    pub const fn is_empty(self) -> bool {
38        !self.pointer && !self.touch && !self.keyboard
39    }
40}
41
42impl core::fmt::Display for Capabilities {
43    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
44        let mut first = true;
45        for (present, name) in [
46            (self.keyboard, "keyboard"),
47            (self.pointer, "pointer"),
48            (self.touch, "touch"),
49        ] {
50            if present {
51                if !first {
52                    f.write_str("+")?;
53                }
54                f.write_str(name)?;
55                first = false;
56            }
57        }
58        if first {
59            f.write_str("none")?;
60        }
61        Ok(())
62    }
63}
64
65/// One open input device, with its own translation state.
66#[derive(Debug)]
67pub struct InputDevice {
68    device: evdev::Device,
69    path: PathBuf,
70    name: String,
71    capabilities: Capabilities,
72    translator: Translator,
73}
74
75impl InputDevice {
76    /// Where the device node lives.
77    pub fn path(&self) -> &Path {
78        &self.path
79    }
80
81    /// The device's self-reported name.
82    pub fn name(&self) -> &str {
83        &self.name
84    }
85
86    /// What the device can report.
87    pub fn capabilities(&self) -> Capabilities {
88        self.capabilities
89    }
90
91    /// The absolute-axis calibration read from the device, as `(x, y)`.
92    ///
93    /// `None` for a relative device. An absolute device reporting `None` here
94    /// would be unmappable, so this is worth looking at when a touchscreen lands
95    /// in the wrong place.
96    pub fn abs_ranges(&self) -> (Option<AbsAxis>, Option<AbsAxis>) {
97        self.translator.abs_ranges()
98    }
99}
100
101impl AsRawFd for InputDevice {
102    fn as_raw_fd(&self) -> RawFd {
103        self.device.as_raw_fd()
104    }
105}
106
107/// Where the device nodes are. Not configurable: this is the only place Linux
108/// puts them, and a backend that looked somewhere else would be looking at
109/// nothing.
110const DEV_INPUT: &str = "/dev/input";
111
112/// Every usable input device, read together.
113#[derive(Debug)]
114pub struct InputBackend {
115    devices: Vec<InputDevice>,
116    /// The pointer position shared across devices, so a mouse and a tablet move
117    /// the same cursor rather than fighting over two.
118    pointer: Point,
119    scratch: Vec<RawEvent>,
120    last_event_age: Option<Duration>,
121    /// The surface size, kept so a device opened later is calibrated like the
122    /// ones opened at startup.
123    surface: Size,
124    /// An inotify descriptor on `/dev/input`, or `None` where one could not be
125    /// had — a container with no permission, mostly. Input still works; it just
126    /// stops being noticed after startup.
127    watch: Option<OwnedFd>,
128    /// Set when [`InputBackend::poll`] opened or dropped a device, cleared by
129    /// whoever asks. See [`InputBackend::devices_changed`].
130    changed: bool,
131}
132
133impl InputBackend {
134    /// Opens every pointer, touch and keyboard device the process can read.
135    ///
136    /// Devices that cannot be opened are skipped rather than fatal: a machine with
137    /// one unreadable node and one good keyboard should still take input.
138    pub fn open_all(surface: Size) -> Result<Self, EvdevError> {
139        let devices: Vec<InputDevice> = evdev::enumerate()
140            .filter_map(|(path, device)| adopt(path, device, surface))
141            .collect();
142
143        if devices.is_empty() {
144            return Err(EvdevError::NoDevices);
145        }
146
147        Ok(Self {
148            devices,
149            pointer: Point::new(surface.width as i32 / 2, surface.height as i32 / 2),
150            scratch: Vec::new(),
151            last_event_age: None,
152            surface,
153            watch: watch_dev_input(),
154            changed: false,
155        })
156    }
157
158    /// Whether the device set changed when [`poll`](InputSource::poll) last ran,
159    /// clearing the flag.
160    ///
161    /// A loop waiting on [`raw_fds`](Self::raw_fds) has to ask, because the
162    /// descriptors it is holding are stale the moment this returns `true` — one
163    /// of them may name a device that has been closed, and a device that has just
164    /// been opened is not in the set at all.
165    pub fn devices_changed(&mut self) -> bool {
166        core::mem::take(&mut self.changed)
167    }
168
169    /// Opens devices that have appeared and drops ones that have gone.
170    ///
171    /// Called from [`poll`](InputSource::poll); there is no reason to call it
172    /// directly, and doing so costs a directory read.
173    ///
174    /// **Why this exists at all.** A wireless mouse that is asleep when the panel
175    /// starts has no `/dev/input/event*` node — the receiver enumerates, the mouse
176    /// does not, and the node is created minutes later when somebody moves it. A
177    /// backend that scanned once at startup would never see that mouse, and the
178    /// only cure would be restarting the application. Measured on a Pi 3 with a
179    /// Logitech unifying receiver: the node appeared 775 seconds after boot.
180    fn rescan(&mut self) {
181        let Ok(entries) = std::fs::read_dir(DEV_INPUT) else {
182            return;
183        };
184
185        let mut present: Vec<PathBuf> = entries
186            .flatten()
187            .map(|entry| entry.path())
188            .filter(|path| {
189                path.file_name()
190                    .and_then(|name| name.to_str())
191                    .is_some_and(|name| name.starts_with("event"))
192            })
193            .collect();
194        // Deterministic order, so a device that appears twice in one scan cannot
195        // land in a different slot than it would have at startup.
196        present.sort();
197
198        // Gone first: a descriptor for a removed device stays readable and always
199        // returns an error, which would otherwise be polled forever.
200        let before = self.devices.len();
201        self.devices.retain(|device| present.contains(&device.path));
202        self.changed |= self.devices.len() != before;
203
204        for path in present {
205            if self.devices.iter().any(|device| device.path == path) {
206                continue;
207            }
208            // A node that mdev has created but not yet chowned fails here with
209            // EACCES. That is not final: the chmod is an IN_ATTRIB of its own, so
210            // this runs again in a moment and succeeds the second time.
211            let Ok(device) = evdev::Device::open(&path) else {
212                continue;
213            };
214            if let Some(device) = adopt(path, device, self.surface) {
215                self.devices.push(device);
216                self.changed = true;
217            }
218        }
219    }
220
221    /// Drains the watch descriptor, reporting whether anything happened.
222    ///
223    /// The events themselves are thrown away. Which file changed is not worth
224    /// acting on individually: a full directory read costs microseconds and is
225    /// right in every case, including the ones inotify cannot report — a queue
226    /// overflow, or a device that was already there when the watch was set.
227    fn watch_fired(&mut self) -> bool {
228        let Some(watch) = self.watch.as_ref() else {
229            return false;
230        };
231        let mut buf = [MaybeUninit::uninit(); 512];
232        let mut reader = inotify::Reader::new(watch.as_fd(), &mut buf);
233        let mut fired = false;
234        loop {
235            match reader.next() {
236                Ok(_) => fired = true,
237                // The normal case, every frame in which nothing was plugged in.
238                Err(Errno::WOULDBLOCK) => return fired,
239                Err(_) => return fired,
240            }
241        }
242    }
243
244    /// The devices that were opened.
245    pub fn devices(&self) -> &[InputDevice] {
246        &self.devices
247    }
248
249    /// Reads every keyboard with `layout`.
250    ///
251    /// Defaults to [`layout::US`](crate::layout::US), because [`KeyCode`] names US
252    /// positions and a different default would make the two disagree out of the
253    /// box. A panel shipped to Norway sets this once at startup; there is no
254    /// runtime layout switching to discover, because a kiosk has one keyboard and
255    /// it does not change.
256    ///
257    /// [`KeyCode`]: denise::KeyCode
258    pub fn set_layout(&mut self, layout: &'static Layout) {
259        for device in &mut self.devices {
260            device.translator.set_layout(layout);
261        }
262    }
263
264    /// Reads every keyboard with the layout this system is configured for.
265    ///
266    /// Checks `DENISE_KEYMAP`, then `XKB_DEFAULT_LAYOUT`, then the console
267    /// keyboard configuration files distributions actually write — so a Pi whose
268    /// `/etc/conf.d/loadkmap` says Norwegian gets Norwegian without anyone having
269    /// to remember an environment variable.
270    ///
271    /// Returns what was chosen and where it came from, which is worth logging: a
272    /// system configured for a layout Denise has no table for falls back to US,
273    /// and that is far easier to diagnose when the panel says so.
274    pub fn set_layout_from_system(&mut self) -> (&'static Layout, layout::LayoutSource) {
275        let (chosen, source) = layout::from_system();
276        self.set_layout(chosen);
277        (chosen, source)
278    }
279
280    /// Descriptors to wait on.
281    ///
282    /// Hand these, plus the DRM device's, to `poll`/`epoll` so the process sleeps
283    /// until either input arrives or the display retires a flip.
284    ///
285    /// The last of these is the `/dev/input` watch rather than a device, which is
286    /// what wakes a sleeping loop when a mouse is plugged in — without it the new
287    /// device would sit unread until something else happened to wake the process.
288    /// Re-read this list whenever [`devices_changed`](Self::devices_changed) says
289    /// to; the old one names descriptors that may since have been closed.
290    pub fn raw_fds(&self) -> Vec<RawFd> {
291        let mut fds: Vec<RawFd> = self.devices.iter().map(AsRawFd::as_raw_fd).collect();
292        if let Some(watch) = self.watch.as_ref() {
293            fds.push(watch.as_raw_fd());
294        }
295        fds
296    }
297
298    /// Tells every device the surface changed size.
299    pub fn resize(&mut self, size: Size) {
300        self.surface = size;
301        self.pointer = Point::new(size.width as i32 / 2, size.height as i32 / 2);
302        for device in &mut self.devices {
303            device.translator.resize(size);
304        }
305    }
306
307    /// The pointer position shared by all pointing devices.
308    pub fn pointer(&self) -> Point {
309        self.pointer
310    }
311
312    /// How long the most recently read event had been waiting.
313    ///
314    /// The kernel timestamps an event when the driver receives it, so this is the
315    /// delay between the hardware reporting and this process reading — time spent
316    /// queued, which no measurement taken after the read can see. `None` until
317    /// something has been read.
318    ///
319    /// A frame loop keeping up reads events within a millisecond or so of the
320    /// kernel taking them. A growing figure means the loop is falling behind and
321    /// is drawing positions the user has already moved on from.
322    pub fn last_event_age(&self) -> Option<Duration> {
323        self.last_event_age
324    }
325}
326
327impl InputSource for InputBackend {
328    fn poll(&mut self, out: &mut Vec<InputEvent>) {
329        // Before reading, so a device that appeared during the wait is read in
330        // this frame rather than the next one.
331        if self.watch_fired() {
332            self.rescan();
333        }
334
335        for device in &mut self.devices {
336            let Ok(events) = device.device.fetch_events() else {
337                // WouldBlock is the normal case: nothing to read right now.
338                continue;
339            };
340
341            self.scratch.clear();
342            let now = SystemTime::now();
343            for event in events {
344                // The kernel's own timestamp, so queuing before this read is
345                // included rather than invisible.
346                self.last_event_age = now.duration_since(event.timestamp()).ok();
347                self.scratch.push(RawEvent::new(
348                    event.event_type().0,
349                    event.code(),
350                    event.value(),
351                ));
352            }
353
354            if self.scratch.is_empty() {
355                continue;
356            }
357
358            // Every pointing device drives the same cursor, so hand the shared
359            // position in and take back whatever it became.
360            device.translator.set_pointer(self.pointer);
361            device.translator.feed_all(&self.scratch, out);
362            self.pointer = device.translator.pointer();
363        }
364    }
365}
366
367/// Prepares one opened device, or `None` if it reports nothing usable.
368fn adopt(path: PathBuf, device: evdev::Device, surface: Size) -> Option<InputDevice> {
369    let capabilities = classify(&device);
370    if capabilities.is_empty() {
371        return None;
372    }
373
374    let name = device.name().unwrap_or("<unnamed>").to_owned();
375    let mut translator = Translator::new(surface);
376
377    // An absolute device is unusable without knowing what its readings are out
378    // of, and every device has its own range.
379    if let Some(axes) = device.supported_absolute_axes() {
380        for axis in axes.iter() {
381            let info = device
382                .get_absinfo()
383                .ok()
384                .and_then(|mut all| all.find(|(code, _)| *code == axis).map(|(_, info)| info));
385            if let Some(info) = info {
386                translator.set_abs_range(axis.0, AbsAxis::new(info.minimum(), info.maximum()));
387            }
388        }
389    }
390
391    // Polling must never stall the frame loop; the loop decides when to sleep,
392    // and it does that on the descriptors, not in here.
393    device.set_nonblocking(true).ok()?;
394
395    Some(InputDevice {
396        device,
397        path,
398        name,
399        capabilities,
400        translator,
401    })
402}
403
404/// Watches `/dev/input` for devices arriving and leaving.
405///
406/// `ATTRIB` is in the mask alongside `CREATE` because the node and its
407/// permissions are two separate events: udev and mdev both create the node as
408/// root-only and chmod it a moment later, so a backend that only listened for
409/// `CREATE` would try to open it exactly once, too early, and give up.
410///
411/// A failure here is not an error. Input works; it just stops being noticed.
412fn watch_dev_input() -> Option<OwnedFd> {
413    let watch =
414        inotify::init(inotify::CreateFlags::CLOEXEC | inotify::CreateFlags::NONBLOCK).ok()?;
415    inotify::add_watch(
416        &watch,
417        DEV_INPUT,
418        inotify::WatchFlags::CREATE
419            | inotify::WatchFlags::ATTRIB
420            | inotify::WatchFlags::DELETE
421            | inotify::WatchFlags::MOVED_TO
422            | inotify::WatchFlags::MOVED_FROM,
423    )
424    .ok()?;
425    Some(watch)
426}
427
428/// Works out what a device can report, from what it says it supports.
429fn classify(device: &evdev::Device) -> Capabilities {
430    let abs_axes = device.supported_absolute_axes();
431    let keys = device.supported_keys();
432
433    let has_abs = |code: u16| abs_axes.is_some_and(|axes| axes.iter().any(|axis| axis.0 == code));
434    let has_key = |code: u16| keys.is_some_and(|k| k.iter().any(|key| key.0 == code));
435
436    Capabilities {
437        // BTN_LEFT is what separates a pointing device from something that merely
438        // has axes, such as a joystick or an accelerometer.
439        pointer: has_key(crate::codes::btn::LEFT),
440        // Slots mean a real touchscreen rather than a tablet or a touchpad.
441        touch: has_abs(abs::MT_POSITION_X),
442        // Letter keys, not any key at all: a power button and a lid switch both
443        // report EV_KEY and neither is a keyboard. KEY_A is 30, KEY_Z is 44.
444        keyboard: has_key(30) && has_key(44),
445    }
446}