Skip to main content

ferroui_linuxframebuffer/input/ev_dev/
ev_dev_multi_touch_screen.rs

1//! The handler of a touch screen that reports several contacts with the
2//! multi-touch protocol B of the kernel (slots).
3//!
4//! An addition (docs/porting/linux-framebuffer-platform.md, section 7):
5//! the reference's evdev backend knows a single contact, and reads
6//! several contacts through `libinput` only.
7
8use super::ev_dev_touch_screen::touch_position;
9use super::{EvDevDeviceHandler, EvDevDeviceInfo, EvDevRawEvent};
10use crate::native_unsafe_methods::{abs_axis, ev_type, input_absinfo, input_event};
11use ferroui_base::input::raw::RawPointerEventType;
12use ferroui_base::{Matrix, Size};
13
14/// The description of a multi-touch screen:
15/// `(path):type=multitouch:[calibration=m11,m12,m21,m22,m31,m32]`.
16#[derive(Clone, Debug, PartialEq)]
17pub struct EvDevMultiTouchScreenDeviceDescription {
18    pub path: Option<String>,
19    pub calibration_matrix: Matrix,
20}
21
22impl Default for EvDevMultiTouchScreenDeviceDescription {
23    fn default() -> Self {
24        Self { path: None, calibration_matrix: Matrix::IDENTITY }
25    }
26}
27
28#[derive(Clone, Copy, Default)]
29struct Slot {
30    /// Whether a contact is in the slot, as far as was reported.
31    active: bool,
32    /// The position that was last reported for the slot.
33    reported: (i32, i32),
34    x: i32,
35    y: i32,
36    // What the current report changes:
37    /// the contact that was in the slot ends;
38    ends_first: bool,
39    /// a contact begins;
40    begins: bool,
41    /// the contact that begins ends as well;
42    ends_after_begin: bool,
43    /// the position changed.
44    moved: bool,
45}
46
47pub struct EvDevMultiTouchScreen {
48    calibration: Matrix,
49    axis_x: input_absinfo,
50    axis_y: input_absinfo,
51    slots: Vec<Slot>,
52    current_slot: usize,
53}
54
55/// More slots than any device has; a slot number beyond it is ignored.
56const MAX_SLOTS: usize = 64;
57
58impl EvDevMultiTouchScreen {
59    /// Fails when the device does not have the two position axes of the
60    /// protocol.
61    pub fn new(device: &EvDevDeviceInfo, description: &EvDevMultiTouchScreenDeviceDescription) -> Result<Self, String> {
62        let axis = |code: u16| device.abs.get(&code).copied();
63        let (Some(axis_x), Some(axis_y)) = (axis(abs_axis::ABS_MT_POSITION_X), axis(abs_axis::ABS_MT_POSITION_Y))
64        else {
65            return Err("Device is not a multi-touch screen".to_owned());
66        };
67        let slot_count =
68            axis(abs_axis::ABS_MT_SLOT).map_or(1, |slot| (slot.maximum.max(0) as usize + 1).min(MAX_SLOTS));
69        Ok(Self {
70            calibration: description.calibration_matrix,
71            axis_x,
72            axis_y,
73            slots: vec![Slot::default(); slot_count],
74            current_slot: 0,
75        })
76    }
77
78    fn slot(&mut self) -> Option<&mut Slot> {
79        if self.current_slot >= MAX_SLOTS {
80            return None;
81        }
82        if self.current_slot >= self.slots.len() {
83            self.slots.resize(self.current_slot + 1, Slot::default());
84        }
85        self.slots.get_mut(self.current_slot)
86    }
87
88    fn report(&mut self, timestamp: u64, screen_size: Size, out: &mut Vec<EvDevRawEvent>) {
89        let (axis_x, axis_y, calibration) = (self.axis_x, self.axis_y, self.calibration);
90        for (index, slot) in self.slots.iter_mut().enumerate() {
91            let mut push = |type_: RawPointerEventType, (x, y): (i32, i32)| {
92                let position = touch_position(x, y, &axis_x, &axis_y, calibration, screen_size);
93                out.push(EvDevRawEvent::Touch { type_, timestamp, position, raw_pointer_id: index as i64 });
94            };
95            // A contact that is replaced in one report ends, where it was
96            // last seen, before the new one begins.
97            if slot.ends_first {
98                push(RawPointerEventType::TouchEnd, slot.reported);
99                slot.active = false;
100            }
101            if slot.begins {
102                push(RawPointerEventType::TouchBegin, (slot.x, slot.y));
103                slot.active = true;
104                if slot.ends_after_begin {
105                    push(RawPointerEventType::TouchEnd, (slot.x, slot.y));
106                    slot.active = false;
107                }
108                slot.reported = (slot.x, slot.y);
109            } else if slot.moved && slot.active {
110                push(RawPointerEventType::TouchUpdate, (slot.x, slot.y));
111                slot.reported = (slot.x, slot.y);
112            }
113            slot.ends_first = false;
114            slot.begins = false;
115            slot.ends_after_begin = false;
116            slot.moved = false;
117        }
118    }
119}
120
121impl EvDevDeviceHandler for EvDevMultiTouchScreen {
122    fn handle_event(&mut self, ev: &input_event, screen_size: Size, out: &mut Vec<EvDevRawEvent>) {
123        if ev.type_ == ev_type::EV_ABS {
124            match ev.code {
125                abs_axis::ABS_MT_SLOT => self.current_slot = ev.value.max(0) as usize,
126                abs_axis::ABS_MT_TRACKING_ID => {
127                    let begins = ev.value >= 0;
128                    if let Some(slot) = self.slot() {
129                        if begins {
130                            // A new identifier in a slot that has a
131                            // contact replaces it.
132                            if !slot.begins {
133                                slot.ends_first = slot.active;
134                                slot.begins = true;
135                            }
136                        } else if slot.begins {
137                            slot.ends_after_begin = true;
138                        } else if slot.active {
139                            slot.ends_first = true;
140                        }
141                    }
142                }
143                abs_axis::ABS_MT_POSITION_X => {
144                    if let Some(slot) = self.slot() {
145                        slot.x = ev.value;
146                        slot.moved = true;
147                    }
148                }
149                abs_axis::ABS_MT_POSITION_Y => {
150                    if let Some(slot) = self.slot() {
151                        slot.y = ev.value;
152                        slot.moved = true;
153                    }
154                }
155                _ => {}
156            }
157        }
158
159        if ev.type_ == ev_type::EV_SYN {
160            self.report(ev.timestamp(), screen_size, out);
161        }
162    }
163
164    fn reset(&mut self, timestamp: u64, screen_size: Size, out: &mut Vec<EvDevRawEvent>) {
165        // The contacts that were reported end where they were last seen.
166        for slot in &mut self.slots {
167            slot.begins = false;
168            slot.ends_after_begin = false;
169            slot.moved = false;
170            slot.ends_first = slot.active;
171        }
172        self.report(timestamp, screen_size, out);
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    use super::super::ev_dev_touch_screen::tests::{ev, info, run, syn};
179    use super::*;
180    use crate::native_unsafe_methods::syn_code;
181    use ferroui_base::Point;
182
183    fn abs(ms: i64, code: u16, value: i32) -> input_event {
184        ev(ms, ev_type::EV_ABS, code, value)
185    }
186
187    fn touch(type_: RawPointerEventType, timestamp: u64, x: f64, y: f64, id: i64) -> EvDevRawEvent {
188        EvDevRawEvent::Touch { type_, timestamp, position: Point::new(x, y), raw_pointer_id: id }
189    }
190
191    fn screen_and_handler() -> (Size, EvDevMultiTouchScreen) {
192        let info = info(&[
193            (abs_axis::ABS_MT_SLOT, 0, 9),
194            (abs_axis::ABS_MT_POSITION_X, 0, 1000),
195            (abs_axis::ABS_MT_POSITION_Y, 0, 500),
196        ]);
197        let handler = EvDevMultiTouchScreen::new(&info, &EvDevMultiTouchScreenDeviceDescription::default()).unwrap();
198        (Size::new(1000.0, 500.0), handler)
199    }
200
201    #[test]
202    fn two_fingers_are_two_contacts_with_the_slot_as_identifier() {
203        let (screen, mut handler) = screen_and_handler();
204        use RawPointerEventType::{TouchBegin, TouchEnd, TouchUpdate};
205        let events = [
206            // The first finger: the slot is 0 until the device says otherwise.
207            abs(1, abs_axis::ABS_MT_TRACKING_ID, 45),
208            abs(1, abs_axis::ABS_MT_POSITION_X, 100),
209            abs(1, abs_axis::ABS_MT_POSITION_Y, 200),
210            syn(1),
211            // The second finger.
212            abs(2, abs_axis::ABS_MT_SLOT, 1),
213            abs(2, abs_axis::ABS_MT_TRACKING_ID, 46),
214            abs(2, abs_axis::ABS_MT_POSITION_X, 300),
215            abs(2, abs_axis::ABS_MT_POSITION_Y, 400),
216            syn(2),
217            // Both move in one report.
218            abs(3, abs_axis::ABS_MT_SLOT, 0),
219            abs(3, abs_axis::ABS_MT_POSITION_X, 110),
220            abs(3, abs_axis::ABS_MT_SLOT, 1),
221            abs(3, abs_axis::ABS_MT_POSITION_Y, 410),
222            syn(3),
223            // The first is lifted; the second stays.
224            abs(4, abs_axis::ABS_MT_SLOT, 0),
225            abs(4, abs_axis::ABS_MT_TRACKING_ID, -1),
226            syn(4),
227            abs(5, abs_axis::ABS_MT_SLOT, 1),
228            abs(5, abs_axis::ABS_MT_TRACKING_ID, -1),
229            syn(5),
230        ];
231        assert_eq!(
232            run(&mut handler, &events, screen),
233            [
234                touch(TouchBegin, 1, 100.0, 200.0, 0),
235                touch(TouchBegin, 2, 300.0, 400.0, 1),
236                touch(TouchUpdate, 3, 110.0, 200.0, 0),
237                touch(TouchUpdate, 3, 300.0, 410.0, 1),
238                touch(TouchEnd, 4, 110.0, 200.0, 0),
239                touch(TouchEnd, 5, 300.0, 410.0, 1),
240            ]
241        );
242    }
243
244    #[test]
245    fn a_contact_that_is_replaced_ends_before_the_new_one_begins() {
246        let (screen, mut handler) = screen_and_handler();
247        use RawPointerEventType::{TouchBegin, TouchEnd};
248        let events = [
249            abs(1, abs_axis::ABS_MT_TRACKING_ID, 1),
250            abs(1, abs_axis::ABS_MT_POSITION_X, 10),
251            abs(1, abs_axis::ABS_MT_POSITION_Y, 10),
252            syn(1),
253            abs(2, abs_axis::ABS_MT_TRACKING_ID, 2),
254            abs(2, abs_axis::ABS_MT_POSITION_X, 50),
255            syn(2),
256            // A tap within one report.
257            abs(3, abs_axis::ABS_MT_SLOT, 3),
258            abs(3, abs_axis::ABS_MT_TRACKING_ID, 3),
259            abs(3, abs_axis::ABS_MT_POSITION_X, 70),
260            abs(3, abs_axis::ABS_MT_POSITION_Y, 80),
261            abs(3, abs_axis::ABS_MT_TRACKING_ID, -1),
262            syn(3),
263        ];
264        assert_eq!(
265            run(&mut handler, &events, screen),
266            [
267                touch(TouchBegin, 1, 10.0, 10.0, 0),
268                touch(TouchEnd, 2, 10.0, 10.0, 0),
269                touch(TouchBegin, 2, 50.0, 10.0, 0),
270                touch(TouchBegin, 3, 70.0, 80.0, 3),
271                touch(TouchEnd, 3, 70.0, 80.0, 3),
272            ]
273        );
274    }
275
276    #[test]
277    fn a_position_without_a_contact_and_a_lift_without_one_report_nothing() {
278        let (screen, mut handler) = screen_and_handler();
279        let events = [
280            abs(1, abs_axis::ABS_MT_POSITION_X, 10),
281            syn(1),
282            abs(2, abs_axis::ABS_MT_TRACKING_ID, -1),
283            syn(2),
284            // A slot beyond any device is passed over.
285            abs(3, abs_axis::ABS_MT_SLOT, 5000),
286            abs(3, abs_axis::ABS_MT_TRACKING_ID, 9),
287            syn(3),
288        ];
289        assert_eq!(run(&mut handler, &events, screen), []);
290    }
291
292    #[test]
293    fn dropped_events_end_the_contacts() {
294        let (screen, mut handler) = screen_and_handler();
295        let events = [
296            abs(1, abs_axis::ABS_MT_TRACKING_ID, 1),
297            abs(1, abs_axis::ABS_MT_POSITION_X, 10),
298            abs(1, abs_axis::ABS_MT_POSITION_Y, 20),
299            syn(1),
300            ev(2, ev_type::EV_SYN, syn_code::SYN_DROPPED, 0),
301            abs(2, abs_axis::ABS_MT_POSITION_X, 500),
302            syn(2),
303        ];
304        let out = run(&mut handler, &events, screen);
305        assert_eq!(out[1], touch(RawPointerEventType::TouchEnd, 2, 10.0, 20.0, 0));
306        assert_eq!(out.len(), 2);
307    }
308
309    #[test]
310    fn a_device_without_the_position_axes_is_not_a_multi_touch_screen() {
311        let info = info(&[(abs_axis::ABS_X, 0, 10), (abs_axis::ABS_Y, 0, 10)]);
312        assert!(EvDevMultiTouchScreen::new(&info, &EvDevMultiTouchScreenDeviceDescription::default()).is_err());
313    }
314}