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}