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: u32, time: u32, surface, id: i32, x: f64, y: f64 } => {
245 data.down(conn, qh, touch, serial, time, surface, id, (x, y));
246 }
247 TouchEvent::Up { serial: u32, time: u32, id: i32 } => {
248 data.up(conn, qh, touch, serial, time, id);
249 }
250 TouchEvent::Motion { time: u32, id: i32, x: f64, y: f64 } => {
251 data.motion(conn, qh, touch, time, id, (x, y));
252 }
253 TouchEvent::Shape { id: i32, major: f64, minor: f64 } => {
254 data.shape(conn, qh, touch, id, major, minor);
255 }
256 TouchEvent::Orientation { id: i32, orientation: f64 } => {
257 data.orientation(conn, qh, touch, id, orientation);
258 }
259 // No other events should be frame-buffered.
260 _ => unreachable!(),
261 }
262}
263