smithay_client_toolkit/seat/
touch.rs

1use std::sync::Mutex;
2
3use wayland_client::protocol::wl_seat::WlSeat;
4
5use wayland_client::protocol::wl_surface::WlSurface;
6use wayland_client::protocol::wl_touch::{Event as TouchEvent, WlTouch};
7use wayland_client::{Connection, Dispatch, QueueHandle};
8
9use crate::seat::SeatState;
10
11#[derive(Debug)]
12pub struct TouchData {
13    seat: WlSeat,
14
15    inner: Mutex<TouchDataInner>,
16}
17
18impl TouchData {
19    /// Create the new touch data associated with the given seat.
20    pub fn new(seat: WlSeat) -> Self {
21        Self { seat, inner: Default::default() }
22    }
23
24    /// Get the associated seat from the data.
25    pub fn seat(&self) -> &WlSeat {
26        &self.seat
27    }
28
29    /// Serial from the latest touch down event.
30    pub fn latest_down_serial(&self) -> Option<u32> {
31        self.inner.lock().unwrap().latest_down
32    }
33}
34
35#[derive(Debug, Default)]
36pub(crate) struct TouchDataInner {
37    events: Vec<TouchEvent>,
38    active_touch_points: Vec<i32>,
39
40    /// The serial of the latest touch down event
41    latest_down: Option<u32>,
42}
43
44#[macro_export]
45macro_rules! delegate_touch {
46    ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
47        $crate::delegate_touch!(@{ $(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty }; touch: $crate::seat::touch::TouchData);
48    };
49    ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty, touch: [$($td:ty),* $(,)?]) => {
50        $crate::delegate_touch!(@{ $(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty }; [ $($td),* ]);
51    };
52    (@{$($ty:tt)*}; touch: $td:ty) => {
53        $crate::reexports::client::delegate_dispatch!($($ty)*:
54            [
55                $crate::reexports::client::protocol::wl_touch::WlTouch: $td
56            ] => $crate::seat::SeatState
57        );
58    };
59    (@$ty:tt; [$($td:ty),*] ) => {
60        $(
61            $crate::delegate_touch!(@$ty, touch: $td);
62        )*
63    };
64}
65
66pub trait TouchDataExt: Send + Sync {
67    fn touch_data(&self) -> &TouchData;
68}
69
70impl TouchDataExt for TouchData {
71    fn touch_data(&self) -> &TouchData {
72        self
73    }
74}
75
76pub trait TouchHandler: Sized {
77    /// New touch point.
78    ///
79    /// Indicates a new touch point has appeared on the surface, starting a touch sequence. The ID
80    /// associated with this event identifies this touch point for devices with multi-touch and
81    /// will be referenced in future events.
82    ///
83    /// The associated touch ID ceases to be valid after the touch up event with the associated ID
84    /// and may be reused for other touch points after that.
85    ///
86    /// Coordinates are surface-local.
87    #[allow(clippy::too_many_arguments)]
88    fn down(
89        &mut self,
90        conn: &Connection,
91        qh: &QueueHandle<Self>,
92        touch: &WlTouch,
93        serial: u32,
94        time: u32,
95        surface: WlSurface,
96        id: i32,
97        position: (f64, f64),
98    );
99
100    /// End of touch sequence.
101    fn up(
102        &mut self,
103        conn: &Connection,
104        qh: &QueueHandle<Self>,
105        touch: &WlTouch,
106        serial: u32,
107        time: u32,
108        id: i32,
109    );
110
111    /// Touch point motion.
112    ///
113    /// Coordinates are surface-local.
114    fn motion(
115        &mut self,
116        conn: &Connection,
117        qh: &QueueHandle<Self>,
118        touch: &WlTouch,
119        time: u32,
120        id: i32,
121        position: (f64, f64),
122    );
123
124    /// Touch point shape change.
125    ///
126    /// The shape of a touch point is approximated by an ellipse through the major and minor axis
127    /// length. Major always represents the larger of the two axis and is orthogonal to minor.
128    ///
129    /// The dimensions are specified in surface-local coordinates and the locations reported by
130    /// other events always report the center of the ellipse.
131    fn shape(
132        &mut self,
133        conn: &Connection,
134        qh: &QueueHandle<Self>,
135        touch: &WlTouch,
136        id: i32,
137        major: f64,
138        minor: f64,
139    );
140
141    /// Touch point shape orientation.
142    ///
143    /// The orientation describes the clockwise angle of a touch point's major axis to the positive
144    /// surface y-axis and is normalized to the -180° to +180° range.
145    fn orientation(
146        &mut self,
147        conn: &Connection,
148        qh: &QueueHandle<Self>,
149        touch: &WlTouch,
150        id: i32,
151        orientation: f64,
152    );
153
154    /// Cancel active touch sequence.
155    ///
156    /// This indicates that the compositor has cancelled the active touch sequence, for example due
157    /// to detection of a touch gesture.
158    fn cancel(&mut self, conn: &Connection, qh: &QueueHandle<Self>, touch: &WlTouch);
159}
160
161impl<D, U> Dispatch<WlTouch, U, D> for SeatState
162where
163    D: Dispatch<WlTouch, U> + TouchHandler,
164    U: TouchDataExt,
165{
166    fn event(
167        data: &mut D,
168        touch: &WlTouch,
169        event: TouchEvent,
170        udata: &U,
171        conn: &Connection,
172        qh: &QueueHandle<D>,
173    ) {
174        let udata = udata.touch_data();
175        let mut guard: std::sync::MutexGuard<'_, TouchDataInner> = udata.inner.lock().unwrap();
176
177        let mut save_event = false;
178        let mut process_events = false;
179
180        match &event {
181            // Buffer events until frame is received.
182            TouchEvent::Down { serial, id, .. } => {
183                guard.latest_down = Some(*serial);
184                save_event = true;
185                if let Err(insert_pos) = guard.active_touch_points.binary_search(id) {
186                    guard.active_touch_points.insert(insert_pos, *id);
187                }
188            }
189            TouchEvent::Up { id, .. } => {
190                save_event = true;
191                if let Ok(remove_pos) = guard.active_touch_points.binary_search(id) {
192                    guard.active_touch_points.remove(remove_pos);
193                }
194
195                // Weston doesn't always send a frame even after the last touch point was released:
196                // https://gitlab.freedesktop.org/wayland/weston/-/issues/44
197                // Work around this by processing pending events when there are no more touch points
198                // active.
199                if guard.active_touch_points.is_empty() {
200                    process_events = true;
201                }
202            }
203            TouchEvent::Motion { .. }
204            | TouchEvent::Shape { .. }
205            | TouchEvent::Orientation { .. } => {
206                save_event = true;
207            }
208            // Process all buffered events.
209            TouchEvent::Frame => {
210                process_events = true;
211            }
212            TouchEvent::Cancel => {
213                guard.events.clear();
214                guard.active_touch_points.clear();
215
216                data.cancel(conn, qh, touch);
217            }
218            _ => unreachable!(),
219        }
220
221        if save_event {
222            guard.events.push(event);
223        }
224
225        if process_events {
226            for event in guard.events.drain(..) {
227                process_framed_event(data, touch, conn, qh, event);
228            }
229        }
230    }
231}
232
233/// Process a single frame-buffered touch event.
234fn process_framed_event<D>(
235    data: &mut D,
236    touch: &WlTouch,
237    conn: &Connection,
238    qh: &QueueHandle<D>,
239    event: TouchEvent,
240) where
241    D: TouchHandler,
242{
243    match event {
244        TouchEvent::Down { serial, time, surface, id, x, y } => {
245            data.down(conn, qh, touch, serial, time, surface, id, (x, y));
246        }
247        TouchEvent::Up { serial, time, id } => {
248            data.up(conn, qh, touch, serial, time, id);
249        }
250        TouchEvent::Motion { time, id, x, y } => {
251            data.motion(conn, qh, touch, time, id, (x, y));
252        }
253        TouchEvent::Shape { id, major, minor } => {
254            data.shape(conn, qh, touch, id, major, minor);
255        }
256        TouchEvent::Orientation { id, orientation } => {
257            data.orientation(conn, qh, touch, id, orientation);
258        }
259        // No other events should be frame-buffered.
260        _ => unreachable!(),
261    }
262}