denise_evdev/device.rs
1//! Finding and reading `/dev/input/event*`.
2
3use std::os::fd::{AsRawFd, RawFd};
4use std::path::{Path, PathBuf};
5use std::time::{Duration, SystemTime};
6
7use denise::{InputEvent, InputSource, Point, Size};
8
9use crate::codes::abs;
10use crate::error::EvdevError;
11use crate::layout::{self, Layout};
12use crate::translate::{AbsAxis, RawEvent, Translator};
13
14/// What a device can report.
15///
16/// A set rather than a single kind, because plenty of real hardware is more than
17/// one thing: a Logitech K400 is a keyboard with a touchpad on one event node, and
18/// most laptops present their touchpad and keyboard together. Picking a single
19/// label for those either loses the pointer or loses the keys.
20#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
21pub struct Capabilities {
22 /// Reports a mouse or an absolute pointing device.
23 pub pointer: bool,
24 /// Reports multitouch contacts.
25 pub touch: bool,
26 /// Reports letter keys.
27 pub keyboard: bool,
28}
29
30impl Capabilities {
31 /// Returns `true` if the device reports nothing this backend can use.
32 #[inline]
33 pub const fn is_empty(self) -> bool {
34 !self.pointer && !self.touch && !self.keyboard
35 }
36}
37
38impl core::fmt::Display for Capabilities {
39 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
40 let mut first = true;
41 for (present, name) in [
42 (self.keyboard, "keyboard"),
43 (self.pointer, "pointer"),
44 (self.touch, "touch"),
45 ] {
46 if present {
47 if !first {
48 f.write_str("+")?;
49 }
50 f.write_str(name)?;
51 first = false;
52 }
53 }
54 if first {
55 f.write_str("none")?;
56 }
57 Ok(())
58 }
59}
60
61/// One open input device, with its own translation state.
62#[derive(Debug)]
63pub struct InputDevice {
64 device: evdev::Device,
65 path: PathBuf,
66 name: String,
67 capabilities: Capabilities,
68 translator: Translator,
69}
70
71impl InputDevice {
72 /// Where the device node lives.
73 pub fn path(&self) -> &Path {
74 &self.path
75 }
76
77 /// The device's self-reported name.
78 pub fn name(&self) -> &str {
79 &self.name
80 }
81
82 /// What the device can report.
83 pub fn capabilities(&self) -> Capabilities {
84 self.capabilities
85 }
86
87 /// The absolute-axis calibration read from the device, as `(x, y)`.
88 ///
89 /// `None` for a relative device. An absolute device reporting `None` here
90 /// would be unmappable, so this is worth looking at when a touchscreen lands
91 /// in the wrong place.
92 pub fn abs_ranges(&self) -> (Option<AbsAxis>, Option<AbsAxis>) {
93 self.translator.abs_ranges()
94 }
95}
96
97impl AsRawFd for InputDevice {
98 fn as_raw_fd(&self) -> RawFd {
99 self.device.as_raw_fd()
100 }
101}
102
103/// Every usable input device, read together.
104#[derive(Debug)]
105pub struct InputBackend {
106 devices: Vec<InputDevice>,
107 /// The pointer position shared across devices, so a mouse and a tablet move
108 /// the same cursor rather than fighting over two.
109 pointer: Point,
110 scratch: Vec<RawEvent>,
111 last_event_age: Option<Duration>,
112}
113
114impl InputBackend {
115 /// Opens every pointer, touch and keyboard device the process can read.
116 ///
117 /// Devices that cannot be opened are skipped rather than fatal: a machine with
118 /// one unreadable node and one good keyboard should still take input.
119 pub fn open_all(surface: Size) -> Result<Self, EvdevError> {
120 let mut devices = Vec::new();
121
122 for (path, device) in evdev::enumerate() {
123 let capabilities = classify(&device);
124 if capabilities.is_empty() {
125 continue;
126 }
127
128 let name = device.name().unwrap_or("<unnamed>").to_owned();
129 let mut translator = Translator::new(surface);
130
131 // An absolute device is unusable without knowing what its readings are
132 // out of, and every device has its own range.
133 if let Some(axes) = device.supported_absolute_axes() {
134 for axis in axes.iter() {
135 let info = device.get_absinfo().ok().and_then(|mut all| {
136 all.find(|(code, _)| *code == axis).map(|(_, info)| info)
137 });
138 if let Some(info) = info {
139 translator
140 .set_abs_range(axis.0, AbsAxis::new(info.minimum(), info.maximum()));
141 }
142 }
143 }
144
145 // Polling must never stall the frame loop; the loop decides when to
146 // sleep, and it does that on the descriptors, not in here.
147 if device.set_nonblocking(true).is_err() {
148 continue;
149 }
150
151 devices.push(InputDevice {
152 device,
153 path,
154 name,
155 capabilities,
156 translator,
157 });
158 }
159
160 if devices.is_empty() {
161 return Err(EvdevError::NoDevices);
162 }
163
164 Ok(Self {
165 devices,
166 pointer: Point::new(surface.width as i32 / 2, surface.height as i32 / 2),
167 scratch: Vec::new(),
168 last_event_age: None,
169 })
170 }
171
172 /// The devices that were opened.
173 pub fn devices(&self) -> &[InputDevice] {
174 &self.devices
175 }
176
177 /// Reads every keyboard with `layout`.
178 ///
179 /// Defaults to [`layout::US`](crate::layout::US), because [`KeyCode`] names US
180 /// positions and a different default would make the two disagree out of the
181 /// box. A panel shipped to Norway sets this once at startup; there is no
182 /// runtime layout switching to discover, because a kiosk has one keyboard and
183 /// it does not change.
184 ///
185 /// [`KeyCode`]: denise::KeyCode
186 pub fn set_layout(&mut self, layout: &'static Layout) {
187 for device in &mut self.devices {
188 device.translator.set_layout(layout);
189 }
190 }
191
192 /// Reads every keyboard with the layout this system is configured for.
193 ///
194 /// Checks `DENISE_KEYMAP`, then `XKB_DEFAULT_LAYOUT`, then the console
195 /// keyboard configuration files distributions actually write — so a Pi whose
196 /// `/etc/conf.d/loadkmap` says Norwegian gets Norwegian without anyone having
197 /// to remember an environment variable.
198 ///
199 /// Returns what was chosen and where it came from, which is worth logging: a
200 /// system configured for a layout Denise has no table for falls back to US,
201 /// and that is far easier to diagnose when the panel says so.
202 pub fn set_layout_from_system(&mut self) -> (&'static Layout, layout::LayoutSource) {
203 let (chosen, source) = layout::from_system();
204 self.set_layout(chosen);
205 (chosen, source)
206 }
207
208 /// Descriptors to wait on.
209 ///
210 /// Hand these, plus the DRM device's, to `poll`/`epoll` so the process sleeps
211 /// until either input arrives or the display retires a flip.
212 pub fn raw_fds(&self) -> Vec<RawFd> {
213 self.devices.iter().map(AsRawFd::as_raw_fd).collect()
214 }
215
216 /// Tells every device the surface changed size.
217 pub fn resize(&mut self, size: Size) {
218 self.pointer = Point::new(size.width as i32 / 2, size.height as i32 / 2);
219 for device in &mut self.devices {
220 device.translator.resize(size);
221 }
222 }
223
224 /// The pointer position shared by all pointing devices.
225 pub fn pointer(&self) -> Point {
226 self.pointer
227 }
228
229 /// How long the most recently read event had been waiting.
230 ///
231 /// The kernel timestamps an event when the driver receives it, so this is the
232 /// delay between the hardware reporting and this process reading — time spent
233 /// queued, which no measurement taken after the read can see. `None` until
234 /// something has been read.
235 ///
236 /// A frame loop keeping up reads events within a millisecond or so of the
237 /// kernel taking them. A growing figure means the loop is falling behind and
238 /// is drawing positions the user has already moved on from.
239 pub fn last_event_age(&self) -> Option<Duration> {
240 self.last_event_age
241 }
242}
243
244impl InputSource for InputBackend {
245 fn poll(&mut self, out: &mut Vec<InputEvent>) {
246 for device in &mut self.devices {
247 let Ok(events) = device.device.fetch_events() else {
248 // WouldBlock is the normal case: nothing to read right now.
249 continue;
250 };
251
252 self.scratch.clear();
253 let now = SystemTime::now();
254 for event in events {
255 // The kernel's own timestamp, so queuing before this read is
256 // included rather than invisible.
257 self.last_event_age = now.duration_since(event.timestamp()).ok();
258 self.scratch.push(RawEvent::new(
259 event.event_type().0,
260 event.code(),
261 event.value(),
262 ));
263 }
264
265 if self.scratch.is_empty() {
266 continue;
267 }
268
269 // Every pointing device drives the same cursor, so hand the shared
270 // position in and take back whatever it became.
271 device.translator.set_pointer(self.pointer);
272 device.translator.feed_all(&self.scratch, out);
273 self.pointer = device.translator.pointer();
274 }
275 }
276}
277
278/// Works out what a device can report, from what it says it supports.
279fn classify(device: &evdev::Device) -> Capabilities {
280 let abs_axes = device.supported_absolute_axes();
281 let keys = device.supported_keys();
282
283 let has_abs = |code: u16| abs_axes.is_some_and(|axes| axes.iter().any(|axis| axis.0 == code));
284 let has_key = |code: u16| keys.is_some_and(|k| k.iter().any(|key| key.0 == code));
285
286 Capabilities {
287 // BTN_LEFT is what separates a pointing device from something that merely
288 // has axes, such as a joystick or an accelerometer.
289 pointer: has_key(crate::codes::btn::LEFT),
290 // Slots mean a real touchscreen rather than a tablet or a touchpad.
291 touch: has_abs(abs::MT_POSITION_X),
292 // Letter keys, not any key at all: a power button and a lid switch both
293 // report EV_KEY and neither is a keyboard. KEY_A is 30, KEY_Z is 44.
294 keyboard: has_key(30) && has_key(44),
295 }
296}