Skip to main content

denise_evdev/
translate.rs

1//! Turning raw evdev events into [`InputEvent`]s.
2//!
3//! This is a state machine, and it is the part of input handling that is actually
4//! difficult. It is kept platform-independent so every case below is a unit test
5//! rather than something you discover by dragging a finger across a panel in a
6//! workshop.
7//!
8//! Three things it gets right that a naive loop does not:
9//!
10//! - **Frames.** evdev delivers a position as separate `REL_X` and `REL_Y` events
11//!   terminated by `SYN_REPORT`. Emitting on each axis produces two moves per
12//!   physical motion, doubling every drag and breaking every gesture threshold.
13//!   Nothing is emitted until the frame closes.
14//! - **Ordering.** A click arrives in the same frame as the motion that positioned
15//!   it. Motion is resolved first, so a button event carries where the pointer
16//!   actually was, not where it had been.
17//! - **Contact identity.** Multitouch slots are stateful: a slot keeps reporting
18//!   for the same finger until its tracking id goes to `-1`, and only the axes
19//!   that changed are resent.
20
21use denise::{ElementState, InputEvent, KeyCode, Modifiers, Point, PointerButton, Size, TouchId};
22
23use crate::layout::{self, Composer, Layout};
24
25use crate::codes::{abs, btn, ev, key_value, rel, syn};
26use crate::keymap::key_code;
27
28/// Contacts tracked at once. Ten fingers is more than any panel this targets.
29pub const MAX_SLOTS: usize = 10;
30
31/// Pixels scrolled per wheel detent.
32const SCROLL_STEP: f32 = 40.0;
33
34/// One event as read from `/dev/input/eventN`.
35#[derive(Clone, Copy, Debug, PartialEq, Eq)]
36pub struct RawEvent {
37    /// `EV_*` event type.
38    pub kind: u16,
39    /// Type-specific code.
40    pub code: u16,
41    /// Type-specific value.
42    pub value: i32,
43}
44
45impl RawEvent {
46    /// Creates a raw event.
47    pub const fn new(kind: u16, code: u16, value: i32) -> Self {
48        Self { kind, code, value }
49    }
50
51    /// The frame separator.
52    pub const SYN: Self = Self::new(ev::SYN, syn::REPORT, 0);
53}
54
55/// The calibration of one absolute axis.
56#[derive(Clone, Copy, Debug, PartialEq, Eq)]
57pub struct AbsAxis {
58    /// Lowest value the device reports.
59    pub min: i32,
60    /// Highest value the device reports.
61    pub max: i32,
62}
63
64impl AbsAxis {
65    /// Creates an axis range.
66    pub const fn new(min: i32, max: i32) -> Self {
67        Self { min, max }
68    }
69
70    /// Maps a device reading onto `0..extent` pixels.
71    ///
72    /// A touchscreen reports in its own units — commonly `0..4095`, which is not
73    /// the panel resolution and is not even the same aspect ratio.
74    pub fn map(&self, value: i32, extent: u32) -> i32 {
75        if extent == 0 {
76            return 0;
77        }
78        let span = i64::from(self.max) - i64::from(self.min);
79        if span <= 0 {
80            return 0;
81        }
82        let offset = i64::from(value.clamp(self.min, self.max)) - i64::from(self.min);
83        (offset * (i64::from(extent) - 1) / span) as i32
84    }
85}
86
87/// One multitouch slot.
88#[derive(Clone, Copy, Debug, Default)]
89struct Slot {
90    /// Contact identity from the kernel; `None` when the slot is free.
91    tracking_id: Option<i32>,
92    x: i32,
93    y: i32,
94    /// A contact began this frame.
95    began: bool,
96    /// A position axis changed this frame.
97    moved: bool,
98    /// The contact ended this frame.
99    ended: bool,
100}
101
102/// A pointer or key event waiting for its frame to close.
103#[derive(Clone, Copy, Debug)]
104enum Deferred {
105    Button {
106        button: PointerButton,
107        state: ElementState,
108    },
109    Key {
110        code: KeyCode,
111        state: ElementState,
112        repeat: bool,
113    },
114}
115
116/// Accumulates raw events and emits [`InputEvent`]s a frame at a time.
117#[derive(Clone, Debug)]
118pub struct Translator {
119    surface: Size,
120    pointer: Point,
121    modifiers: Modifiers,
122
123    abs_x: Option<AbsAxis>,
124    abs_y: Option<AbsAxis>,
125
126    rel_x: i32,
127    rel_y: i32,
128    scroll_x: f32,
129    scroll_y: f32,
130
131    pending_abs_x: Option<i32>,
132    pending_abs_y: Option<i32>,
133
134    /// The device has used the slot protocol, so `ABS_X`/`ABS_Y` are not a pointer.
135    multitouch: bool,
136    slots: [Slot; MAX_SLOTS],
137    slot: usize,
138
139    /// Turns key positions into characters. Held here rather than beside the
140    /// keymap because composition is a property of the *sequence* of keystrokes,
141    /// and this is the only thing that sees them in order.
142    composer: Composer,
143
144    /// `BTN_TOUCH` state, for single-contact panels with no slots.
145    touch_down: bool,
146    single_touch_active: bool,
147
148    deferred: Vec<Deferred>,
149}
150
151impl Translator {
152    /// Creates a translator for a surface of `size`.
153    pub fn new(size: Size) -> Self {
154        Self {
155            surface: size,
156            pointer: Point::new(size.width as i32 / 2, size.height as i32 / 2),
157            modifiers: Modifiers::NONE,
158            abs_x: None,
159            abs_y: None,
160            rel_x: 0,
161            rel_y: 0,
162            scroll_x: 0.0,
163            scroll_y: 0.0,
164            pending_abs_x: None,
165            pending_abs_y: None,
166            multitouch: false,
167            slots: [Slot::default(); MAX_SLOTS],
168            slot: 0,
169            touch_down: false,
170            single_touch_active: false,
171            composer: Composer::new(&layout::US),
172            deferred: Vec::new(),
173        }
174    }
175
176    /// Reads this device with a different keyboard layout.
177    ///
178    /// Defaults to US, because [`KeyCode`] names US positions and defaulting to
179    /// anything else would make the two disagree by default.
180    pub fn set_layout(&mut self, layout: &'static Layout) {
181        self.composer.set_layout(layout);
182    }
183
184    /// The layout this device is being read with.
185    pub fn layout(&self) -> &'static Layout {
186        self.composer.layout()
187    }
188
189    /// Declares the range of an absolute axis, from the device's `absinfo`.
190    ///
191    /// Without this an absolute device is ignored: mapping a reading to a pixel is
192    /// impossible without knowing what the reading is out of.
193    pub fn set_abs_range(&mut self, axis: u16, range: AbsAxis) {
194        match axis {
195            abs::X | abs::MT_POSITION_X => self.abs_x = Some(range),
196            abs::Y | abs::MT_POSITION_Y => self.abs_y = Some(range),
197            _ => {}
198        }
199    }
200
201    /// Tells the translator the surface changed size.
202    pub fn resize(&mut self, size: Size) {
203        self.surface = size;
204        self.pointer = self.clamp(self.pointer);
205    }
206
207    /// Current pointer position.
208    pub fn pointer(&self) -> Point {
209        self.pointer
210    }
211
212    /// Moves the pointer without emitting anything.
213    ///
214    /// Used to share one cursor between several pointing devices: a mouse and a
215    /// tablet should move the same pointer rather than each keeping its own.
216    pub fn set_pointer(&mut self, position: Point) {
217        self.pointer = self.clamp(position);
218    }
219
220    /// Currently held modifiers.
221    pub fn modifiers(&self) -> Modifiers {
222        self.modifiers
223    }
224
225    /// The calibration read from the device, as `(x, y)`.
226    pub fn abs_ranges(&self) -> (Option<AbsAxis>, Option<AbsAxis>) {
227        (self.abs_x, self.abs_y)
228    }
229
230    /// Returns `true` once the device has identified itself as multitouch.
231    pub fn is_multitouch(&self) -> bool {
232        self.multitouch
233    }
234
235    /// Feeds one raw event, appending any completed events to `out`.
236    pub fn feed(&mut self, event: RawEvent, out: &mut Vec<InputEvent>) {
237        match event.kind {
238            ev::SYN => match event.code {
239                syn::REPORT => self.flush(out),
240                // The kernel's buffer overflowed and events were lost. Anything
241                // half-accumulated describes a state that never existed.
242                syn::DROPPED => self.discard_frame(),
243                _ => {}
244            },
245            ev::REL => self.feed_rel(event),
246            ev::ABS => self.feed_abs(event),
247            ev::KEY => self.feed_key(event),
248            _ => {}
249        }
250    }
251
252    /// Feeds a whole batch, which need not be frame-aligned.
253    pub fn feed_all(&mut self, events: &[RawEvent], out: &mut Vec<InputEvent>) {
254        for event in events {
255            self.feed(*event, out);
256        }
257    }
258
259    fn feed_rel(&mut self, event: RawEvent) {
260        match event.code {
261            rel::X => self.rel_x += event.value,
262            rel::Y => self.rel_y += event.value,
263            rel::WHEEL => self.scroll_y -= event.value as f32 * SCROLL_STEP,
264            rel::HWHEEL => self.scroll_x += event.value as f32 * SCROLL_STEP,
265            _ => {}
266        }
267    }
268
269    fn feed_abs(&mut self, event: RawEvent) {
270        match event.code {
271            abs::MT_SLOT => {
272                self.multitouch = true;
273                self.slot = (event.value.max(0) as usize).min(MAX_SLOTS - 1);
274            }
275            abs::MT_TRACKING_ID => {
276                self.multitouch = true;
277                let slot = &mut self.slots[self.slot];
278                if event.value < 0 {
279                    if slot.tracking_id.is_some() {
280                        slot.ended = true;
281                    }
282                } else {
283                    slot.tracking_id = Some(event.value);
284                    slot.began = true;
285                }
286            }
287            abs::MT_POSITION_X => {
288                self.multitouch = true;
289                let slot = &mut self.slots[self.slot];
290                slot.x = event.value;
291                slot.moved = true;
292            }
293            abs::MT_POSITION_Y => {
294                self.multitouch = true;
295                let slot = &mut self.slots[self.slot];
296                slot.y = event.value;
297                slot.moved = true;
298            }
299            abs::X => self.pending_abs_x = Some(event.value),
300            abs::Y => self.pending_abs_y = Some(event.value),
301            _ => {}
302        }
303    }
304
305    fn feed_key(&mut self, event: RawEvent) {
306        let state = match event.value {
307            key_value::UP => ElementState::Up,
308            key_value::DOWN | key_value::REPEAT => ElementState::Down,
309            _ => return,
310        };
311        let repeat = event.value == key_value::REPEAT;
312
313        match event.code {
314            btn::LEFT | btn::RIGHT | btn::MIDDLE => {
315                // Auto-repeat is meaningless for a button.
316                if repeat {
317                    return;
318                }
319                let button = match event.code {
320                    btn::LEFT => PointerButton::Left,
321                    btn::RIGHT => PointerButton::Right,
322                    _ => PointerButton::Middle,
323                };
324                self.deferred.push(Deferred::Button { button, state });
325            }
326            btn::TOUCH => self.touch_down = state.is_down(),
327            code => {
328                let key = key_code(code);
329                self.update_modifiers(key, state.is_down());
330                self.deferred.push(Deferred::Key {
331                    code: key,
332                    state,
333                    repeat,
334                });
335            }
336        }
337    }
338
339    fn update_modifiers(&mut self, key: KeyCode, down: bool) {
340        let bit = match key {
341            KeyCode::ShiftLeft | KeyCode::ShiftRight => Modifiers::SHIFT,
342            KeyCode::ControlLeft | KeyCode::ControlRight => Modifiers::CTRL,
343            KeyCode::AltLeft | KeyCode::AltRight => Modifiers::ALT,
344            KeyCode::SuperLeft | KeyCode::SuperRight => Modifiers::SUPER,
345            _ => return,
346        };
347        self.modifiers = self.modifiers.set(bit, down);
348    }
349
350    fn clamp(&self, point: Point) -> Point {
351        Point::new(
352            point
353                .x
354                .clamp(0, self.surface.width.saturating_sub(1) as i32),
355            point
356                .y
357                .clamp(0, self.surface.height.saturating_sub(1) as i32),
358        )
359    }
360
361    /// Throws away everything accumulated but not yet emitted.
362    fn discard_frame(&mut self) {
363        self.rel_x = 0;
364        self.rel_y = 0;
365        self.scroll_x = 0.0;
366        self.scroll_y = 0.0;
367        self.pending_abs_x = None;
368        self.pending_abs_y = None;
369        self.deferred.clear();
370        for slot in &mut self.slots {
371            slot.began = false;
372            slot.moved = false;
373            slot.ended = false;
374        }
375    }
376
377    /// Closes a frame: motion first, then everything positioned by it.
378    fn flush(&mut self, out: &mut Vec<InputEvent>) {
379        if self.multitouch {
380            self.flush_slots(out);
381        } else {
382            self.flush_pointer(out);
383        }
384
385        if self.scroll_x != 0.0 || self.scroll_y != 0.0 {
386            out.push(InputEvent::PointerScroll {
387                delta_x: self.scroll_x,
388                delta_y: self.scroll_y,
389                position: self.pointer,
390            });
391            self.scroll_x = 0.0;
392            self.scroll_y = 0.0;
393        }
394
395        for deferred in std::mem::take(&mut self.deferred) {
396            out.push(match deferred {
397                Deferred::Button { button, state } => InputEvent::PointerButton {
398                    button,
399                    state,
400                    position: self.pointer,
401                    modifiers: self.modifiers,
402                },
403                Deferred::Key {
404                    code,
405                    state,
406                    repeat,
407                } => {
408                    // The key event first, then whatever it typed. A binding on
409                    // Enter or Escape therefore runs before any text arrives, and
410                    // a field can insert every `Text` it sees without filtering.
411                    out.push(InputEvent::Key {
412                        code,
413                        state,
414                        repeat,
415                        modifiers: self.modifiers,
416                    });
417                    let composed = self.composer.feed(code, state, self.modifiers);
418                    for &ch in composed.as_slice() {
419                        out.push(InputEvent::Text { ch });
420                    }
421                    continue;
422                }
423            });
424        }
425    }
426
427    fn flush_pointer(&mut self, out: &mut Vec<InputEvent>) {
428        let absolute = self.pending_abs_x.is_some() || self.pending_abs_y.is_some();
429
430        let moved = if absolute {
431            let x = match (self.pending_abs_x.take(), self.abs_x) {
432                (Some(v), Some(axis)) => axis.map(v, self.surface.width),
433                _ => self.pointer.x,
434            };
435            let y = match (self.pending_abs_y.take(), self.abs_y) {
436                (Some(v), Some(axis)) => axis.map(v, self.surface.height),
437                _ => self.pointer.y,
438            };
439            let next = self.clamp(Point::new(x, y));
440            let moved = next != self.pointer;
441            self.pointer = next;
442            moved
443        } else if self.rel_x != 0 || self.rel_y != 0 {
444            let next = self.clamp(Point::new(
445                self.pointer.x + self.rel_x,
446                self.pointer.y + self.rel_y,
447            ));
448            self.rel_x = 0;
449            self.rel_y = 0;
450            let moved = next != self.pointer;
451            self.pointer = next;
452            moved
453        } else {
454            false
455        };
456
457        // A single-contact panel: BTN_TOUCH plus plain absolute axes, no slots.
458        if self.touch_down || self.single_touch_active {
459            self.flush_single_touch(out);
460            return;
461        }
462
463        if moved {
464            out.push(InputEvent::PointerMoved {
465                position: self.pointer,
466            });
467        }
468    }
469
470    fn flush_single_touch(&mut self, out: &mut Vec<InputEvent>) {
471        const ID: TouchId = 0;
472        match (self.touch_down, self.single_touch_active) {
473            (true, false) => {
474                self.single_touch_active = true;
475                out.push(InputEvent::TouchDown {
476                    id: ID,
477                    position: self.pointer,
478                });
479            }
480            (true, true) => out.push(InputEvent::TouchMoved {
481                id: ID,
482                position: self.pointer,
483            }),
484            (false, true) => {
485                self.single_touch_active = false;
486                out.push(InputEvent::TouchUp {
487                    id: ID,
488                    position: self.pointer,
489                    cancelled: false,
490                });
491            }
492            (false, false) => {}
493        }
494    }
495
496    fn flush_slots(&mut self, out: &mut Vec<InputEvent>) {
497        for index in 0..MAX_SLOTS {
498            let slot = self.slots[index];
499            if !(slot.began || slot.moved || slot.ended) {
500                continue;
501            }
502
503            // The slot index is the contact identity. A slot holds one finger at a
504            // time, which is exactly the lifetime TouchId promises, and unlike the
505            // kernel's tracking id it cannot wrap around and collide.
506            let id = index as TouchId;
507            let position = self.map_slot(&slot);
508
509            if slot.began {
510                out.push(InputEvent::TouchDown { id, position });
511            } else if slot.moved && slot.tracking_id.is_some() {
512                out.push(InputEvent::TouchMoved { id, position });
513            }
514
515            if slot.ended {
516                out.push(InputEvent::TouchUp {
517                    id,
518                    position,
519                    cancelled: false,
520                });
521            }
522
523            let slot = &mut self.slots[index];
524            slot.began = false;
525            slot.moved = false;
526            if slot.ended {
527                slot.ended = false;
528                slot.tracking_id = None;
529            }
530        }
531    }
532
533    fn map_slot(&self, slot: &Slot) -> Point {
534        let x = self
535            .abs_x
536            .map_or(slot.x, |axis| axis.map(slot.x, self.surface.width));
537        let y = self
538            .abs_y
539            .map_or(slot.y, |axis| axis.map(slot.y, self.surface.height));
540        self.clamp(Point::new(x, y))
541    }
542}
543
544#[cfg(test)]
545mod tests {
546    use super::*;
547
548    const SURFACE: Size = Size::new(800, 480);
549
550    fn translator() -> Translator {
551        Translator::new(SURFACE)
552    }
553
554    fn touchscreen() -> Translator {
555        let mut t = translator();
556        t.set_abs_range(abs::MT_POSITION_X, AbsAxis::new(0, 4095));
557        t.set_abs_range(abs::MT_POSITION_Y, AbsAxis::new(0, 4095));
558        t
559    }
560
561    fn drain(t: &mut Translator, events: &[RawEvent]) -> Vec<InputEvent> {
562        let mut out = Vec::new();
563        t.feed_all(events, &mut out);
564        out
565    }
566
567    #[test]
568    fn nothing_is_emitted_before_the_frame_closes() {
569        let mut t = translator();
570        let out = drain(
571            &mut t,
572            &[
573                RawEvent::new(ev::REL, rel::X, 10),
574                RawEvent::new(ev::REL, rel::Y, 5),
575            ],
576        );
577        assert!(out.is_empty(), "emitted mid-frame: {out:?}");
578    }
579
580    #[test]
581    fn both_axes_become_one_move() {
582        // The bug this prevents: one PointerMoved per axis, so every drag travels
583        // twice and every gesture threshold trips early.
584        let mut t = translator();
585        let start = t.pointer();
586        let out = drain(
587            &mut t,
588            &[
589                RawEvent::new(ev::REL, rel::X, 10),
590                RawEvent::new(ev::REL, rel::Y, 5),
591                RawEvent::SYN,
592            ],
593        );
594        assert_eq!(
595            out,
596            vec![InputEvent::PointerMoved {
597                position: Point::new(start.x + 10, start.y + 5)
598            }]
599        );
600    }
601
602    #[test]
603    fn relative_motion_is_clamped_to_the_surface() {
604        let mut t = translator();
605        drain(
606            &mut t,
607            &[RawEvent::new(ev::REL, rel::X, -100_000), RawEvent::SYN],
608        );
609        assert_eq!(t.pointer().x, 0);
610        drain(
611            &mut t,
612            &[RawEvent::new(ev::REL, rel::X, 100_000), RawEvent::SYN],
613        );
614        assert_eq!(t.pointer().x, SURFACE.width as i32 - 1);
615    }
616
617    #[test]
618    fn a_frame_with_no_motion_emits_nothing() {
619        let mut t = translator();
620        assert!(drain(&mut t, &[RawEvent::SYN]).is_empty());
621    }
622
623    #[test]
624    fn absolute_pointers_map_through_their_axis_range() {
625        // A QEMU tablet, and every touchscreen: device units, not pixels.
626        let mut t = translator();
627        t.set_abs_range(abs::X, AbsAxis::new(0, 32767));
628        t.set_abs_range(abs::Y, AbsAxis::new(0, 32767));
629
630        let out = drain(
631            &mut t,
632            &[
633                RawEvent::new(ev::ABS, abs::X, 32767),
634                RawEvent::new(ev::ABS, abs::Y, 0),
635                RawEvent::SYN,
636            ],
637        );
638        assert_eq!(
639            out,
640            vec![InputEvent::PointerMoved {
641                position: Point::new(799, 0)
642            }]
643        );
644    }
645
646    #[test]
647    fn an_absolute_axis_with_a_nonzero_minimum_still_reaches_the_origin() {
648        let axis = AbsAxis::new(100, 4195);
649        assert_eq!(axis.map(100, 800), 0);
650        assert_eq!(axis.map(4195, 800), 799);
651    }
652
653    #[test]
654    fn a_degenerate_axis_does_not_divide_by_zero() {
655        assert_eq!(AbsAxis::new(5, 5).map(5, 800), 0);
656        assert_eq!(AbsAxis::new(0, 100).map(50, 0), 0);
657    }
658
659    #[test]
660    fn a_click_carries_the_position_from_its_own_frame() {
661        // Motion and button arrive together; resolving the button first would
662        // report where the pointer used to be.
663        let mut t = translator();
664        t.set_abs_range(abs::X, AbsAxis::new(0, 799));
665        t.set_abs_range(abs::Y, AbsAxis::new(0, 479));
666
667        // Deliberately away from the initial centre, so the move is a real one.
668        let out = drain(
669            &mut t,
670            &[
671                RawEvent::new(ev::ABS, abs::X, 600),
672                RawEvent::new(ev::ABS, abs::Y, 300),
673                RawEvent::new(ev::KEY, btn::LEFT, key_value::DOWN),
674                RawEvent::SYN,
675            ],
676        );
677
678        assert_eq!(
679            out,
680            vec![
681                InputEvent::PointerMoved {
682                    position: Point::new(600, 300)
683                },
684                InputEvent::PointerButton {
685                    button: PointerButton::Left,
686                    state: ElementState::Down,
687                    position: Point::new(600, 300),
688                    modifiers: Modifiers::NONE,
689                },
690            ]
691        );
692    }
693
694    #[test]
695    fn wheel_detents_become_pixels_and_scroll_the_right_way() {
696        let mut t = translator();
697        let out = drain(
698            &mut t,
699            &[RawEvent::new(ev::REL, rel::WHEEL, 1), RawEvent::SYN],
700        );
701        // One detent away from the user scrolls content up, so delta_y is negative.
702        match out.as_slice() {
703            [InputEvent::PointerScroll { delta_y, .. }] => assert!(*delta_y < 0.0),
704            other => panic!("expected one scroll, got {other:?}"),
705        }
706    }
707
708    #[test]
709    fn modifiers_are_held_across_frames_and_reported_on_keys() {
710        let mut t = translator();
711        drain(
712            &mut t,
713            &[RawEvent::new(ev::KEY, 42, key_value::DOWN), RawEvent::SYN],
714        );
715        assert!(t.modifiers().contains(Modifiers::SHIFT));
716
717        let out = drain(
718            &mut t,
719            &[RawEvent::new(ev::KEY, 30, key_value::DOWN), RawEvent::SYN],
720        );
721        assert_eq!(
722            out,
723            vec![
724                InputEvent::Key {
725                    code: KeyCode::A,
726                    state: ElementState::Down,
727                    repeat: false,
728                    modifiers: Modifiers::SHIFT,
729                },
730                // The held shift decides the character as well as the flag.
731                InputEvent::Text { ch: 'A' },
732            ]
733        );
734
735        drain(
736            &mut t,
737            &[RawEvent::new(ev::KEY, 42, key_value::UP), RawEvent::SYN],
738        );
739        assert!(t.modifiers().is_empty());
740    }
741
742    #[test]
743    fn auto_repeat_is_flagged_but_still_a_press() {
744        let mut t = translator();
745        let out = drain(
746            &mut t,
747            &[RawEvent::new(ev::KEY, 30, key_value::REPEAT), RawEvent::SYN],
748        );
749        assert_eq!(
750            out,
751            vec![
752                InputEvent::Key {
753                    code: KeyCode::A,
754                    state: ElementState::Down,
755                    repeat: true,
756                    modifiers: Modifiers::NONE,
757                },
758                // A held key keeps typing, which is the entire point of repeat.
759                InputEvent::Text { ch: 'a' },
760            ]
761        );
762    }
763
764    #[test]
765    fn raw_evdev_codes_become_norwegian_text() {
766        // End to end from the wire: evdev code 39 is the US semicolon position,
767        // which on a Norwegian keyboard is where ø lives.
768        let mut t = translator();
769        t.set_layout(&crate::layout::NORWEGIAN);
770        let out = drain(
771            &mut t,
772            &[RawEvent::new(ev::KEY, 39, key_value::DOWN), RawEvent::SYN],
773        );
774        assert_eq!(
775            out,
776            vec![
777                InputEvent::Key {
778                    code: KeyCode::Semicolon,
779                    state: ElementState::Down,
780                    repeat: false,
781                    modifiers: Modifiers::NONE,
782                },
783                InputEvent::Text { ch: '\u{00f8}' },
784            ]
785        );
786    }
787
788    #[test]
789    fn a_dead_key_spans_two_evdev_frames() {
790        // Composition survives the frame boundary, which is the thing a per-frame
791        // translator could plausibly get wrong.
792        let mut t = translator();
793        t.set_layout(&crate::layout::NORWEGIAN);
794        let first = drain(
795            &mut t,
796            &[RawEvent::new(ev::KEY, 27, key_value::DOWN), RawEvent::SYN],
797        );
798        assert_eq!(
799            first.len(),
800            1,
801            "the dead key reports its position and no text: {first:?}"
802        );
803        let second = drain(
804            &mut t,
805            &[RawEvent::new(ev::KEY, 24, key_value::DOWN), RawEvent::SYN],
806        );
807        assert!(
808            second.contains(&InputEvent::Text { ch: '\u{00f6}' }),
809            "expected ö from ¨ then o, got {second:?}"
810        );
811    }
812
813    #[test]
814    fn buttons_never_auto_repeat() {
815        let mut t = translator();
816        let out = drain(
817            &mut t,
818            &[
819                RawEvent::new(ev::KEY, btn::LEFT, key_value::REPEAT),
820                RawEvent::SYN,
821            ],
822        );
823        assert!(out.is_empty(), "a held button repeated: {out:?}");
824    }
825
826    #[test]
827    fn a_single_finger_reports_down_move_and_up() {
828        let mut t = touchscreen();
829        let down = drain(
830            &mut t,
831            &[
832                RawEvent::new(ev::ABS, abs::MT_SLOT, 0),
833                RawEvent::new(ev::ABS, abs::MT_TRACKING_ID, 77),
834                RawEvent::new(ev::ABS, abs::MT_POSITION_X, 0),
835                RawEvent::new(ev::ABS, abs::MT_POSITION_Y, 0),
836                RawEvent::SYN,
837            ],
838        );
839        assert_eq!(
840            down,
841            vec![InputEvent::TouchDown {
842                id: 0,
843                position: Point::new(0, 0)
844            }]
845        );
846
847        // Only the axis that changed is resent, which is the protocol's whole point.
848        let moved = drain(
849            &mut t,
850            &[
851                RawEvent::new(ev::ABS, abs::MT_POSITION_X, 4095),
852                RawEvent::SYN,
853            ],
854        );
855        assert_eq!(
856            moved,
857            vec![InputEvent::TouchMoved {
858                id: 0,
859                position: Point::new(799, 0)
860            }]
861        );
862
863        let up = drain(
864            &mut t,
865            &[
866                RawEvent::new(ev::ABS, abs::MT_TRACKING_ID, -1),
867                RawEvent::SYN,
868            ],
869        );
870        assert_eq!(
871            up,
872            vec![InputEvent::TouchUp {
873                id: 0,
874                position: Point::new(799, 0),
875                cancelled: false
876            }]
877        );
878    }
879
880    #[test]
881    fn two_fingers_keep_separate_identities() {
882        let mut t = touchscreen();
883        let out = drain(
884            &mut t,
885            &[
886                RawEvent::new(ev::ABS, abs::MT_SLOT, 0),
887                RawEvent::new(ev::ABS, abs::MT_TRACKING_ID, 10),
888                RawEvent::new(ev::ABS, abs::MT_POSITION_X, 0),
889                RawEvent::new(ev::ABS, abs::MT_POSITION_Y, 0),
890                RawEvent::new(ev::ABS, abs::MT_SLOT, 1),
891                RawEvent::new(ev::ABS, abs::MT_TRACKING_ID, 11),
892                RawEvent::new(ev::ABS, abs::MT_POSITION_X, 4095),
893                RawEvent::new(ev::ABS, abs::MT_POSITION_Y, 4095),
894                RawEvent::SYN,
895            ],
896        );
897
898        assert_eq!(
899            out,
900            vec![
901                InputEvent::TouchDown {
902                    id: 0,
903                    position: Point::new(0, 0)
904                },
905                InputEvent::TouchDown {
906                    id: 1,
907                    position: Point::new(799, 479)
908                },
909            ]
910        );
911    }
912
913    #[test]
914    fn lifting_one_finger_leaves_the_other_down() {
915        let mut t = touchscreen();
916        drain(
917            &mut t,
918            &[
919                RawEvent::new(ev::ABS, abs::MT_SLOT, 0),
920                RawEvent::new(ev::ABS, abs::MT_TRACKING_ID, 10),
921                RawEvent::new(ev::ABS, abs::MT_SLOT, 1),
922                RawEvent::new(ev::ABS, abs::MT_TRACKING_ID, 11),
923                RawEvent::SYN,
924            ],
925        );
926
927        let out = drain(
928            &mut t,
929            &[
930                RawEvent::new(ev::ABS, abs::MT_SLOT, 0),
931                RawEvent::new(ev::ABS, abs::MT_TRACKING_ID, -1),
932                RawEvent::SYN,
933            ],
934        );
935        assert_eq!(out.len(), 1);
936        assert!(matches!(out[0], InputEvent::TouchUp { id: 0, .. }));
937
938        // Slot 1 must still be live and still report as itself.
939        let out = drain(
940            &mut t,
941            &[
942                RawEvent::new(ev::ABS, abs::MT_SLOT, 1),
943                RawEvent::new(ev::ABS, abs::MT_POSITION_X, 2048),
944                RawEvent::SYN,
945            ],
946        );
947        assert!(matches!(out[0], InputEvent::TouchMoved { id: 1, .. }));
948    }
949
950    #[test]
951    fn a_reused_slot_starts_a_new_contact() {
952        let mut t = touchscreen();
953        drain(
954            &mut t,
955            &[
956                RawEvent::new(ev::ABS, abs::MT_SLOT, 0),
957                RawEvent::new(ev::ABS, abs::MT_TRACKING_ID, 10),
958                RawEvent::SYN,
959                RawEvent::new(ev::ABS, abs::MT_TRACKING_ID, -1),
960                RawEvent::SYN,
961            ],
962        );
963        let out = drain(
964            &mut t,
965            &[
966                RawEvent::new(ev::ABS, abs::MT_TRACKING_ID, 12),
967                RawEvent::SYN,
968            ],
969        );
970        assert!(
971            matches!(out[0], InputEvent::TouchDown { id: 0, .. }),
972            "a recycled slot must report a fresh contact, got {out:?}"
973        );
974    }
975
976    #[test]
977    fn a_slot_index_beyond_capacity_does_not_panic() {
978        let mut t = touchscreen();
979        let out = drain(
980            &mut t,
981            &[
982                RawEvent::new(ev::ABS, abs::MT_SLOT, 9999),
983                RawEvent::new(ev::ABS, abs::MT_TRACKING_ID, 1),
984                RawEvent::SYN,
985            ],
986        );
987        assert_eq!(out.len(), 1);
988    }
989
990    #[test]
991    fn single_contact_panels_without_slots_still_report_touches() {
992        // BTN_TOUCH plus plain ABS_X/ABS_Y: cheap resistive panels, and the
993        // protocol-A fallback.
994        let mut t = translator();
995        t.set_abs_range(abs::X, AbsAxis::new(0, 4095));
996        t.set_abs_range(abs::Y, AbsAxis::new(0, 4095));
997
998        let down = drain(
999            &mut t,
1000            &[
1001                RawEvent::new(ev::KEY, btn::TOUCH, key_value::DOWN),
1002                RawEvent::new(ev::ABS, abs::X, 2048),
1003                RawEvent::new(ev::ABS, abs::Y, 2048),
1004                RawEvent::SYN,
1005            ],
1006        );
1007        assert!(matches!(down[0], InputEvent::TouchDown { id: 0, .. }));
1008
1009        let up = drain(
1010            &mut t,
1011            &[
1012                RawEvent::new(ev::KEY, btn::TOUCH, key_value::UP),
1013                RawEvent::SYN,
1014            ],
1015        );
1016        assert!(matches!(up[0], InputEvent::TouchUp { id: 0, .. }));
1017    }
1018
1019    #[test]
1020    fn a_dropped_frame_is_discarded_rather_than_half_applied() {
1021        // SYN_DROPPED means the kernel lost events. Whatever accumulated describes
1022        // a state that never existed, and emitting it desynchronises everything
1023        // downstream.
1024        let mut t = translator();
1025        let out = drain(
1026            &mut t,
1027            &[
1028                RawEvent::new(ev::REL, rel::X, 50),
1029                RawEvent::new(ev::KEY, btn::LEFT, key_value::DOWN),
1030                RawEvent::new(ev::SYN, syn::DROPPED, 0),
1031                RawEvent::SYN,
1032            ],
1033        );
1034        assert!(out.is_empty(), "a dropped frame was emitted: {out:?}");
1035    }
1036
1037    #[test]
1038    fn resizing_pulls_the_pointer_back_inside() {
1039        let mut t = translator();
1040        drain(
1041            &mut t,
1042            &[RawEvent::new(ev::REL, rel::X, 10_000), RawEvent::SYN],
1043        );
1044        assert_eq!(t.pointer().x, 799);
1045
1046        t.resize(Size::new(320, 240));
1047        assert_eq!(t.pointer(), Point::new(319, 239));
1048    }
1049
1050    #[test]
1051    fn an_absolute_device_never_drifts_from_repeated_frames() {
1052        // Absolute readings are positions, not deltas. Re-sending the same
1053        // reading must not move anything.
1054        let mut t = translator();
1055        t.set_abs_range(abs::X, AbsAxis::new(0, 799));
1056        t.set_abs_range(abs::Y, AbsAxis::new(0, 479));
1057
1058        drain(
1059            &mut t,
1060            &[
1061                RawEvent::new(ev::ABS, abs::X, 100),
1062                RawEvent::new(ev::ABS, abs::Y, 100),
1063                RawEvent::SYN,
1064            ],
1065        );
1066        let again = drain(
1067            &mut t,
1068            &[
1069                RawEvent::new(ev::ABS, abs::X, 100),
1070                RawEvent::new(ev::ABS, abs::Y, 100),
1071                RawEvent::SYN,
1072            ],
1073        );
1074        assert!(again.is_empty(), "a repeated position moved the pointer");
1075        assert_eq!(t.pointer(), Point::new(100, 100));
1076    }
1077}