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