drmkit_input/event.rs
1// SPDX-FileCopyrightText: (c) 2026 Joel Winarske
2// SPDX-License-Identifier: MIT
3
4//! What comes off an input device.
5//!
6//! Deliberately plain data. Nothing here borrows from libinput, so an event
7//! can be queued, compared, or written into a test without a device in sight.
8
9/// A key going down or coming up.
10///
11/// `key` is the evdev code -- `KEY_A`, `KEY_LEFTSHIFT` -- with no xkb offset
12/// applied. [`Keyboard::process_key`](crate::Keyboard::process_key) fills in
13/// [`sym`](Self::sym) and [`utf8`](Self::utf8); until it has, both are empty.
14#[derive(Debug, Clone, Default, PartialEq, Eq)]
15#[allow(
16 clippy::exhaustive_structs,
17 reason = "built by callers with `..Default::default()`, which a new field does not break"
18)]
19pub struct KeyEvent {
20 /// libinput's timestamp, in milliseconds.
21 pub time_ms: u32,
22 /// The evdev keycode, without xkb's `+ 8`.
23 pub key: u32,
24 /// Down, rather than up.
25 pub pressed: bool,
26 /// Synthesized by the repeater rather than produced by a device.
27 pub repeat: bool,
28 /// The xkb keysym this resolved to.
29 pub sym: u32,
30 /// What the key types, if anything.
31 ///
32 /// The reference carries a `char[8]`, so it truncates anything longer
33 /// than seven bytes. Nothing a single keysym produces reaches that, but
34 /// there is no reason to inherit the cliff.
35 pub utf8: String,
36}
37
38/// A pointer moving.
39#[derive(Debug, Clone, PartialEq)]
40#[allow(
41 clippy::exhaustive_structs,
42 reason = "built by callers, with no default to fill a new field from"
43)]
44pub struct PointerMotion {
45 /// libinput's timestamp, in milliseconds.
46 pub time_ms: u32,
47 /// Horizontal movement, in libinput's accelerated units.
48 pub dx: f64,
49 /// Vertical movement, likewise.
50 pub dy: f64,
51 /// The device this came from, if libinput named it.
52 ///
53 /// Owned rather than borrowed. The reference hands out the `const char*`
54 /// libinput owns and tells the caller it is valid for the duration of the
55 /// callback -- which is true, and is a rule a consumer only has to forget
56 /// once. It is there so a consumer can treat a built-in trackpad
57 /// differently from an external mouse, and that decision is made per
58 /// event, not per byte.
59 pub device: Option<String>,
60}
61
62/// A pointer button going down or coming up.
63#[derive(Debug, Clone, PartialEq, Eq)]
64#[allow(
65 clippy::exhaustive_structs,
66 reason = "built by callers, with no default to fill a new field from"
67)]
68pub struct PointerButton {
69 /// libinput's timestamp, in milliseconds.
70 pub time_ms: u32,
71 /// The evdev button code: `BTN_LEFT`, `BTN_RIGHT`, and so on.
72 pub button: u32,
73 /// Down, rather than up.
74 pub pressed: bool,
75}
76
77/// Where a scroll came from.
78///
79/// It changes what the scroll means. A wheel click is a discrete step and
80/// should move a fixed amount; a finger on a touchpad is continuous and wants
81/// kinetic scrolling, and its end is a real event -- the finger lifting --
82/// rather than the absence of one.
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84#[non_exhaustive]
85pub enum ScrollSource {
86 /// A mouse wheel, in discrete clicks.
87 Wheel,
88 /// One or more fingers on a touchpad.
89 Finger,
90 /// A continuous source, such as a button held and the device moved.
91 Continuous,
92}
93
94/// A scroll.
95#[derive(Debug, Clone, PartialEq)]
96#[allow(
97 clippy::exhaustive_structs,
98 reason = "built by callers, with no default to fill a new field from"
99)]
100pub struct PointerAxis {
101 /// libinput's timestamp, in milliseconds.
102 pub time_ms: u32,
103 /// Horizontal scroll.
104 pub horizontal: f64,
105 /// Vertical scroll.
106 pub vertical: f64,
107 /// What did the scrolling.
108 pub source: ScrollSource,
109}
110
111/// A pointer reporting where it is rather than how far it moved.
112///
113/// Graphics tablets, and the absolute pointing device a virtual machine
114/// gives its guest -- which is how a lot of this gets run before it reaches
115/// hardware.
116#[derive(Debug, Clone, PartialEq)]
117#[allow(
118 clippy::exhaustive_structs,
119 reason = "built by callers, with no default to fill a new field from"
120)]
121pub struct PointerAbsolute {
122 /// libinput's timestamp, in milliseconds.
123 pub time_ms: u32,
124 /// Position across the device's range, in `[0, 1)`.
125 ///
126 /// Normalized here because the raw value is in millimeters, and scaling
127 /// that to a render extent needs the digitizer's physical size -- which
128 /// the consumer would have to go and ask for.
129 pub x: f64,
130 /// Position down the device's range, likewise.
131 pub y: f64,
132 /// The device this came from, if libinput named it.
133 pub device: Option<String>,
134}
135
136/// Anything a pointer does.
137#[derive(Debug, Clone, PartialEq)]
138#[non_exhaustive]
139pub enum PointerEvent {
140 /// The pointer moved.
141 Motion(PointerMotion),
142 /// The pointer reported where it is.
143 Absolute(PointerAbsolute),
144 /// A button changed.
145 Button(PointerButton),
146 /// A scroll happened.
147 Axis(PointerAxis),
148}
149
150/// What a touch point is doing.
151#[derive(Debug, Clone, Copy, PartialEq, Eq)]
152#[non_exhaustive]
153pub enum TouchPhase {
154 /// A finger landed.
155 Down,
156 /// A finger lifted.
157 Up,
158 /// A finger moved.
159 Motion,
160 /// The end of one batch of simultaneous touch changes.
161 Frame,
162 /// The sequence was abandoned -- a palm, or a device going away.
163 Cancel,
164}
165
166/// A touch point.
167#[derive(Debug, Clone, PartialEq)]
168#[allow(
169 clippy::exhaustive_structs,
170 reason = "built by callers, with no default to fill a new field from"
171)]
172pub struct TouchEvent {
173 /// libinput's timestamp, in milliseconds.
174 pub time_ms: u32,
175 /// The multi-touch slot, which identifies one finger across a sequence.
176 ///
177 /// `-1` on a [`TouchPhase::Frame`], which closes a batch rather than
178 /// naming a finger.
179 pub slot: i32,
180 /// Position across the device's range, normalized to `[0, 1)`; zero for
181 /// up, frame and cancel.
182 pub x: f64,
183 /// Position across the device's range, normalized to `[0, 1)`; zero for
184 /// up, frame and cancel.
185 pub y: f64,
186 /// What the point is doing.
187 pub phase: TouchPhase,
188 /// The device this came from, if libinput named it.
189 pub device: Option<String>,
190}
191
192/// Which physical switch changed.
193#[derive(Debug, Clone, Copy, PartialEq, Eq)]
194#[non_exhaustive]
195pub enum Switch {
196 /// The lid.
197 Lid,
198 /// The convertible's tablet-mode hinge.
199 TabletMode,
200}
201
202/// A switch changing.
203#[derive(Debug, Clone, Copy, PartialEq, Eq)]
204#[allow(
205 clippy::exhaustive_structs,
206 reason = "built by callers, with no default to fill a new field from"
207)]
208pub struct SwitchEvent {
209 /// libinput's timestamp, in milliseconds.
210 pub time_ms: u32,
211 /// Which switch.
212 pub which: Switch,
213 /// Closed, or in tablet mode.
214 pub active: bool,
215}
216
217/// Anything an input device produces.
218#[derive(Debug, Clone, PartialEq)]
219#[non_exhaustive]
220pub enum InputEvent {
221 /// A key.
222 Key(KeyEvent),
223 /// A pointer.
224 Pointer(PointerEvent),
225 /// A touch point.
226 Touch(TouchEvent),
227 /// A switch.
228 Switch(SwitchEvent),
229}