Skip to main content

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}