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}