Skip to main content

ferroui_linuxframebuffer/input/ev_dev/
ev_dev_touch_screen.rs

1//! The handler of a touch screen that reports one contact, and what the
2//! handlers of event devices share (the port of `EvDevTouchScreen.cs`).
3//!
4//! A handler is a state machine over the events of its device. It runs on
5//! the UI thread and reports raw events as values ([`EvDevRawEvent`]),
6//! which the backend turns into the raw input events of the framework
7//! with its devices and the input root; that is what lets the state
8//! machines be tested against recorded event streams.
9
10use super::{EvDevDeviceInfo, EvDevTouchScreenDeviceDescription};
11use crate::native_unsafe_methods::{abs_axis, ev_key, ev_type, input_absinfo, input_event, syn_code};
12use ferroui_base::input::raw::{RawKeyEventType, RawPointerEventType};
13use ferroui_base::input::{Key, PhysicalKey, RawInputModifiers};
14use ferroui_base::{Matrix, Point, Size, Vector};
15
16/// A raw input event of an event device, before it has a device object
17/// and an input root.
18#[derive(Clone, Debug, PartialEq)]
19pub enum EvDevRawEvent {
20    Touch {
21        type_: RawPointerEventType,
22        timestamp: u64,
23        position: Point,
24        raw_pointer_id: i64,
25    },
26    Pointer {
27        type_: RawPointerEventType,
28        timestamp: u64,
29        position: Point,
30        modifiers: RawInputModifiers,
31    },
32    Wheel {
33        timestamp: u64,
34        position: Point,
35        delta: Vector,
36        modifiers: RawInputModifiers,
37    },
38    Key {
39        type_: RawKeyEventType,
40        timestamp: u64,
41        key: Key,
42        modifiers: RawInputModifiers,
43        physical_key: PhysicalKey,
44        key_symbol: Option<String>,
45    },
46    Text {
47        timestamp: u64,
48        text: String,
49    },
50}
51
52/// The handler of the events of one device (`EvDevDeviceHandler`).
53pub trait EvDevDeviceHandler {
54    /// `HandleEvent`. `screen_size` is the scaled size of the screen.
55    fn handle_event(&mut self, ev: &input_event, screen_size: Size, out: &mut Vec<EvDevRawEvent>);
56
57    /// Ends what is in progress after the kernel dropped events of the
58    /// device. Not in the reference, whose library re-synchronises.
59    fn reset(&mut self, _timestamp: u64, _screen_size: Size, _out: &mut Vec<EvDevRawEvent>) {}
60}
61
62/// `HandleEvents`: hands the events of one read to the handler.
63///
64/// After `SYN_DROPPED` the events up to the next `SYN_REPORT` belong to a
65/// report the kernel could not deliver whole: they are passed over and
66/// the handler is reset. `dropped` carries that state from one read to
67/// the next.
68pub fn handle_events(
69    handler: &mut dyn EvDevDeviceHandler,
70    dropped: &mut bool,
71    events: &[input_event],
72    screen_size: Size,
73    out: &mut Vec<EvDevRawEvent>,
74) {
75    for ev in events {
76        if ev.type_ == ev_type::EV_SYN && ev.code == syn_code::SYN_DROPPED {
77            *dropped = true;
78            continue;
79        }
80        if *dropped {
81            if ev.type_ == ev_type::EV_SYN && ev.code == syn_code::SYN_REPORT {
82                *dropped = false;
83                handler.reset(ev.timestamp(), screen_size, out);
84            }
85            continue;
86        }
87        handler.handle_event(ev, screen_size, out);
88    }
89}
90
91/// The position on the screen of a point of a touch device: normalised to
92/// the range of the axes, calibrated, and scaled to the screen.
93pub(crate) fn touch_position(
94    x: i32,
95    y: i32,
96    axis_x: &input_absinfo,
97    axis_y: &input_absinfo,
98    calibration: Matrix,
99    screen_size: Size,
100) -> Point {
101    let touch_width = (axis_x.maximum - axis_x.minimum) as f64;
102    let touch_height = (axis_y.maximum - axis_y.minimum) as f64;
103
104    // Normalize to 0-(max-min)
105    let point = Point::new((x - axis_x.minimum) as f64, (y - axis_y.minimum) as f64);
106
107    // Apply calibration matrix
108    let point = point * calibration;
109
110    // Transform to display pixel grid
111    Point::new(point.x * screen_size.width / touch_width, point.y * screen_size.height / touch_height)
112}
113
114pub struct EvDevSingleTouchScreen {
115    calibration: Matrix,
116    axis_x: input_absinfo,
117    axis_y: input_absinfo,
118    current_x: i32,
119    current_y: i32,
120    has_movement: bool,
121    press_action: Option<bool>,
122}
123
124impl EvDevSingleTouchScreen {
125    /// Fails when the device does not have the two absolute axes.
126    pub fn new(device: &EvDevDeviceInfo, description: &EvDevTouchScreenDeviceDescription) -> Result<Self, String> {
127        let (Some(axis_x), Some(axis_y)) = (device.abs_x(), device.abs_y()) else {
128            return Err("Device is not a touchscreen".to_owned());
129        };
130        Ok(Self {
131            calibration: description.calibration_matrix,
132            axis_x,
133            axis_y,
134            current_x: 0,
135            current_y: 0,
136            has_movement: false,
137            press_action: None,
138        })
139    }
140
141    fn raise_event(&self, type_: RawPointerEventType, timestamp: u64, screen_size: Size, out: &mut Vec<EvDevRawEvent>) {
142        let position =
143            touch_position(self.current_x, self.current_y, &self.axis_x, &self.axis_y, self.calibration, screen_size);
144        out.push(EvDevRawEvent::Touch { type_, timestamp, position, raw_pointer_id: 1 });
145    }
146}
147
148impl EvDevDeviceHandler for EvDevSingleTouchScreen {
149    fn handle_event(&mut self, ev: &input_event, screen_size: Size, out: &mut Vec<EvDevRawEvent>) {
150        if ev.type_ == ev_type::EV_ABS {
151            if ev.code == abs_axis::ABS_X {
152                self.current_x = ev.value;
153                self.has_movement = true;
154            }
155
156            if ev.code == abs_axis::ABS_Y {
157                self.current_y = ev.value;
158                self.has_movement = true;
159            }
160        }
161
162        if ev.type_ == ev_type::EV_KEY && ev.code == ev_key::BTN_TOUCH {
163            self.press_action = Some(ev.value != 0);
164        }
165
166        if ev.type_ == ev_type::EV_SYN {
167            if let Some(press_action) = self.press_action {
168                let type_ = if press_action { RawPointerEventType::TouchBegin } else { RawPointerEventType::TouchEnd };
169                self.raise_event(type_, ev.timestamp(), screen_size, out);
170            } else if self.has_movement {
171                self.raise_event(RawPointerEventType::TouchUpdate, ev.timestamp(), screen_size, out);
172            }
173
174            self.has_movement = false;
175            self.press_action = None;
176        }
177    }
178
179    fn reset(&mut self, _timestamp: u64, _screen_size: Size, _out: &mut Vec<EvDevRawEvent>) {
180        self.has_movement = false;
181        self.press_action = None;
182    }
183}
184
185#[cfg(test)]
186pub(crate) mod tests {
187    // Not from the reference, which has no tests for this file. The
188    // streams are what `evtest` prints for such a device.
189    use super::*;
190
191    pub fn info(axes: &[(u16, i32, i32)]) -> EvDevDeviceInfo {
192        EvDevDeviceInfo {
193            name: Some("test".to_owned()),
194            event_types: vec![ev_type::EV_SYN, ev_type::EV_KEY, ev_type::EV_ABS],
195            abs: axes
196                .iter()
197                .map(|&(code, minimum, maximum)| (code, input_absinfo { minimum, maximum, ..Default::default() }))
198                .collect(),
199        }
200    }
201
202    pub fn ev(ms: i64, type_: u16, code: u16, value: i32) -> input_event {
203        input_event { tv_sec: ms / 1000, tv_usec: (ms % 1000) * 1000, type_, code, value }
204    }
205
206    pub fn syn(ms: i64) -> input_event {
207        ev(ms, ev_type::EV_SYN, syn_code::SYN_REPORT, 0)
208    }
209
210    pub fn run(handler: &mut dyn EvDevDeviceHandler, events: &[input_event], screen: Size) -> Vec<EvDevRawEvent> {
211        let mut out = Vec::new();
212        let mut dropped = false;
213        handle_events(handler, &mut dropped, events, screen, &mut out);
214        out
215    }
216
217    fn touch(type_: RawPointerEventType, timestamp: u64, x: f64, y: f64) -> EvDevRawEvent {
218        EvDevRawEvent::Touch { type_, timestamp, position: Point::new(x, y), raw_pointer_id: 1 }
219    }
220
221    #[test]
222    fn a_press_a_move_and_a_release_are_begin_update_and_end_scaled_to_the_screen() {
223        let info = info(&[(abs_axis::ABS_X, 0, 4095), (abs_axis::ABS_Y, 0, 4095)]);
224        let mut handler = EvDevSingleTouchScreen::new(&info, &EvDevTouchScreenDeviceDescription::default()).unwrap();
225        let screen = Size::new(819.0, 409.5);
226        let events = [
227            ev(1000, ev_type::EV_ABS, abs_axis::ABS_X, 1000),
228            ev(1000, ev_type::EV_ABS, abs_axis::ABS_Y, 2000),
229            ev(1000, ev_type::EV_KEY, ev_key::BTN_TOUCH, 1),
230            syn(1000),
231            ev(1016, ev_type::EV_ABS, abs_axis::ABS_X, 1500),
232            syn(1016),
233            // A report without a change reports nothing.
234            syn(1020),
235            ev(1032, ev_type::EV_KEY, ev_key::BTN_TOUCH, 0),
236            syn(1032),
237        ];
238        assert_eq!(
239            run(&mut handler, &events, screen),
240            [
241                touch(RawPointerEventType::TouchBegin, 1000, 200.0, 200.0),
242                touch(RawPointerEventType::TouchUpdate, 1016, 300.0, 200.0),
243                touch(RawPointerEventType::TouchEnd, 1032, 300.0, 200.0),
244            ]
245        );
246    }
247
248    #[test]
249    fn the_axis_minimum_and_the_calibration_are_applied_before_the_scale() {
250        let info = info(&[(abs_axis::ABS_X, 100, 1100), (abs_axis::ABS_Y, 200, 700)]);
251        // Swaps the axes.
252        let description = EvDevTouchScreenDeviceDescription {
253            path: None,
254            calibration_matrix: Matrix::new(0.0, 1.0, 1.0, 0.0, 0.0, 0.0),
255        };
256        let mut handler = EvDevSingleTouchScreen::new(&info, &description).unwrap();
257        let events = [
258            ev(5, ev_type::EV_ABS, abs_axis::ABS_X, 600),
259            ev(5, ev_type::EV_ABS, abs_axis::ABS_Y, 300),
260            ev(5, ev_type::EV_KEY, ev_key::BTN_TOUCH, 1),
261            syn(5),
262        ];
263        // (500, 100) swapped is (100, 500); scaled by 1000/1000 and 500/500.
264        assert_eq!(
265            run(&mut handler, &events, Size::new(1000.0, 500.0)),
266            [touch(RawPointerEventType::TouchBegin, 5, 100.0, 500.0)]
267        );
268    }
269
270    #[test]
271    fn a_device_without_the_two_axes_is_not_a_touch_screen() {
272        let info = info(&[(abs_axis::ABS_X, 0, 10)]);
273        let error = EvDevSingleTouchScreen::new(&info, &EvDevTouchScreenDeviceDescription::default()).err();
274        assert_eq!(error.as_deref(), Some("Device is not a touchscreen"));
275    }
276
277    #[test]
278    fn the_events_of_a_dropped_report_are_passed_over() {
279        let info = info(&[(abs_axis::ABS_X, 0, 100), (abs_axis::ABS_Y, 0, 100)]);
280        let mut handler = EvDevSingleTouchScreen::new(&info, &EvDevTouchScreenDeviceDescription::default()).unwrap();
281        let screen = Size::new(100.0, 100.0);
282        let mut out = Vec::new();
283        let mut dropped = false;
284        let first = [
285            ev(1, ev_type::EV_ABS, abs_axis::ABS_X, 10),
286            ev(1, ev_type::EV_SYN, syn_code::SYN_DROPPED, 0),
287            ev(1, ev_type::EV_KEY, ev_key::BTN_TOUCH, 1),
288        ];
289        handle_events(&mut handler, &mut dropped, &first, screen, &mut out);
290        assert!(dropped && out.is_empty());
291        // The state is carried to the next read.
292        let second = [
293            ev(2, ev_type::EV_ABS, abs_axis::ABS_Y, 20),
294            syn(2),
295            ev(3, ev_type::EV_ABS, abs_axis::ABS_X, 50),
296            ev(3, ev_type::EV_ABS, abs_axis::ABS_Y, 60),
297            syn(3),
298        ];
299        handle_events(&mut handler, &mut dropped, &second, screen, &mut out);
300        assert!(!dropped);
301        assert_eq!(out, [touch(RawPointerEventType::TouchUpdate, 3, 50.0, 60.0)]);
302    }
303}