Skip to main content

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}