1use crate::coordinates::CoordinateTransformer;
8use crate::error::Result;
9use tracing::{debug, warn};
10
11const MAX_CONTACTS: usize = 256;
14
15#[derive(Debug, Clone, Copy, PartialEq)]
17pub enum TouchEvent {
18 Down {
20 slot: u32,
22 x: f64,
24 y: f64,
26 },
27 Motion {
29 slot: u32,
31 x: f64,
33 y: f64,
35 },
36 Up {
38 slot: u32,
40 },
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49enum ContactPhase {
50 OutOfRange,
51 Hovering,
52 Engaged,
53}
54
55#[derive(Debug, Clone, Copy)]
56struct ContactState {
57 phase: ContactPhase,
58 ignore: bool,
63}
64
65impl Default for ContactState {
66 fn default() -> Self {
67 Self {
68 phase: ContactPhase::OutOfRange,
69 ignore: false,
70 }
71 }
72}
73
74#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
80pub struct TouchContactFlags {
81 pub down: bool,
82 pub update: bool,
83 pub up: bool,
84 pub in_range: bool,
85 pub in_contact: bool,
86 pub canceled: bool,
87}
88
89pub struct TouchHandler {
92 contacts: Box<[ContactState; MAX_CONTACTS]>,
93}
94
95impl Default for TouchHandler {
96 fn default() -> Self {
97 Self::new()
98 }
99}
100
101impl TouchHandler {
102 pub fn new() -> Self {
103 Self {
104 contacts: Box::new([ContactState::default(); MAX_CONTACTS]),
105 }
106 }
107
108 pub fn handle_contact(
116 &mut self,
117 contact_id: u8,
118 x: i32,
119 y: i32,
120 flags: TouchContactFlags,
121 transformer: &mut CoordinateTransformer,
122 ) -> Result<Option<TouchEvent>> {
123 let slot = u32::from(contact_id);
124 let state = &mut self.contacts[contact_id as usize];
125
126 match (flags.down, flags.update, flags.up, flags.in_range, flags.in_contact) {
127 (true, false, false, true, true) => {
129 state.phase = ContactPhase::Engaged;
130 state.ignore = false;
131 Self::transform(state, transformer, x, y).map(|pos| pos.map(|(x, y)| TouchEvent::Down { slot, x, y }))
132 }
133
134 (false, true, false, true, true) => {
136 if state.phase != ContactPhase::Engaged {
137 warn!(
138 contact_id,
139 "touch UPDATE|INCONTACT for a non-engaged contact, treating as down"
140 );
141 state.phase = ContactPhase::Engaged;
142 }
143 Self::transform(state, transformer, x, y).map(|pos| pos.map(|(x, y)| TouchEvent::Motion { slot, x, y }))
144 }
145
146 (false, true, false, true, false) => {
149 if state.phase == ContactPhase::OutOfRange {
150 state.phase = ContactPhase::Hovering;
151 }
152 Ok(None)
153 }
154
155 (false, false, true, true, false) => {
159 let was_engaged = state.phase == ContactPhase::Engaged;
160 state.phase = ContactPhase::Hovering;
161 Ok(was_engaged.then_some(TouchEvent::Up { slot }))
162 }
163
164 (false, false, true, false, false) => {
166 let was_engaged = state.phase == ContactPhase::Engaged;
167 *state = ContactState::default();
168 Ok(was_engaged.then_some(TouchEvent::Up { slot }))
169 }
170
171 _ => {
172 warn!(
173 contact_id,
174 ?flags,
175 "illegal MS-RDPEI touch contact flag combination, ignoring"
176 );
177 Ok(None)
178 }
179 }
180 }
181
182 pub fn reset(&mut self) {
184 *self.contacts = [ContactState::default(); MAX_CONTACTS];
185 }
186
187 fn transform(
188 state: &mut ContactState,
189 transformer: &mut CoordinateTransformer,
190 x: i32,
191 y: i32,
192 ) -> Result<Option<(f64, f64)>> {
193 match transformer.rdp_to_stream(x, y) {
194 Ok((stream_x, stream_y)) => {
195 state.ignore = false;
196 let (stream_x, stream_y) = transformer.clamp_to_bounds(stream_x, stream_y);
197 Ok(Some((stream_x, stream_y)))
198 }
199 Err(e) => {
200 debug!(x, y, error = %e, "touch contact position outside all monitors, suppressing host event");
201 state.ignore = true;
202 Ok(None)
203 }
204 }
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211 use crate::coordinates::MonitorInfo;
212
213 fn create_test_transformer() -> CoordinateTransformer {
214 let monitor = MonitorInfo {
215 id: 1,
216 name: "Primary".to_string(),
217 x: 0,
218 y: 0,
219 width: 1920,
220 height: 1080,
221 dpi: 96.0,
222 scale_factor: 1.0,
223 stream_x: 0,
224 stream_y: 0,
225 stream_width: 1920,
226 stream_height: 1080,
227 is_primary: true,
228 };
229 CoordinateTransformer::new(vec![monitor]).unwrap()
230 }
231
232 fn down_flags() -> TouchContactFlags {
233 TouchContactFlags {
234 down: true,
235 in_range: true,
236 in_contact: true,
237 ..Default::default()
238 }
239 }
240
241 fn update_flags() -> TouchContactFlags {
242 TouchContactFlags {
243 update: true,
244 in_range: true,
245 in_contact: true,
246 ..Default::default()
247 }
248 }
249
250 fn up_flags() -> TouchContactFlags {
251 TouchContactFlags {
252 up: true,
253 ..Default::default()
254 }
255 }
256
257 #[test]
258 fn test_down_motion_up_sequence() {
259 let mut handler = TouchHandler::new();
260 let mut transformer = create_test_transformer();
261
262 let event = handler
263 .handle_contact(0, 960, 540, down_flags(), &mut transformer)
264 .unwrap();
265 assert!(matches!(event, Some(TouchEvent::Down { slot: 0, .. })));
266
267 let event = handler
268 .handle_contact(0, 970, 540, update_flags(), &mut transformer)
269 .unwrap();
270 assert!(matches!(event, Some(TouchEvent::Motion { slot: 0, .. })));
271
272 let event = handler
273 .handle_contact(0, 970, 540, up_flags(), &mut transformer)
274 .unwrap();
275 assert_eq!(event, Some(TouchEvent::Up { slot: 0 }));
276 }
277
278 #[test]
279 fn test_multiple_contacts_independent_slots() {
280 let mut handler = TouchHandler::new();
281 let mut transformer = create_test_transformer();
282
283 let a = handler
284 .handle_contact(0, 100, 100, down_flags(), &mut transformer)
285 .unwrap();
286 let b = handler
287 .handle_contact(1, 200, 200, down_flags(), &mut transformer)
288 .unwrap();
289
290 assert!(matches!(a, Some(TouchEvent::Down { slot: 0, .. })));
291 assert!(matches!(b, Some(TouchEvent::Down { slot: 1, .. })));
292 }
293
294 #[test]
295 fn test_up_with_inrange_demotes_to_hovering_not_full_release() {
296 let mut handler = TouchHandler::new();
297 let mut transformer = create_test_transformer();
298
299 handler
300 .handle_contact(0, 100, 100, down_flags(), &mut transformer)
301 .unwrap();
302
303 let hover_up = TouchContactFlags {
304 up: true,
305 in_range: true,
306 ..Default::default()
307 };
308 let event = handler.handle_contact(0, 100, 100, hover_up, &mut transformer).unwrap();
309 assert_eq!(event, Some(TouchEvent::Up { slot: 0 }));
310
311 let event = handler
314 .handle_contact(0, 100, 100, up_flags(), &mut transformer)
315 .unwrap();
316 assert_eq!(event, None);
317 }
318
319 #[test]
320 fn test_hover_only_produces_no_event() {
321 let mut handler = TouchHandler::new();
322 let mut transformer = create_test_transformer();
323
324 let hover = TouchContactFlags {
325 update: true,
326 in_range: true,
327 ..Default::default()
328 };
329 let event = handler.handle_contact(0, 100, 100, hover, &mut transformer).unwrap();
330 assert_eq!(event, None);
331 }
332
333 #[test]
334 fn test_illegal_flag_combination_is_ignored_not_erroring() {
335 let mut handler = TouchHandler::new();
336 let mut transformer = create_test_transformer();
337
338 let illegal = TouchContactFlags {
341 down: true,
342 ..Default::default()
343 };
344 let event = handler.handle_contact(0, 100, 100, illegal, &mut transformer).unwrap();
345 assert_eq!(event, None);
346 }
347
348 #[test]
349 fn test_out_of_bounds_position_clamps_rather_than_erroring() {
350 let mut handler = TouchHandler::new();
351 let mut transformer = create_test_transformer();
352
353 let event = handler
358 .handle_contact(0, 50_000, 50_000, down_flags(), &mut transformer)
359 .unwrap();
360 match event {
361 Some(TouchEvent::Down { x, y, .. }) => {
362 assert!(x <= 1920.0);
363 assert!(y <= 1080.0);
364 }
365 other => panic!("expected a clamped Down event, got {other:?}"),
366 }
367 }
368
369 #[test]
370 fn test_reset_clears_all_contacts() {
371 let mut handler = TouchHandler::new();
372 let mut transformer = create_test_transformer();
373
374 handler
375 .handle_contact(0, 100, 100, down_flags(), &mut transformer)
376 .unwrap();
377 handler.reset();
378
379 let event = handler
382 .handle_contact(0, 100, 100, up_flags(), &mut transformer)
383 .unwrap();
384 assert_eq!(event, None);
385 }
386}