Skip to main content

lamco_rdp_input/
mouse.rs

1//! Mouse Event Handling
2//!
3//! Handles mouse movement, button presses, and scroll wheel events with
4//! coordinate transformation and button mapping.
5
6use crate::coordinates::CoordinateTransformer;
7use crate::error::Result;
8use std::time::Instant;
9use tracing::debug;
10
11/// Mouse button identifiers
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13pub enum MouseButton {
14    /// Left mouse button
15    Left,
16    /// Right mouse button
17    Right,
18    /// Middle mouse button
19    Middle,
20    /// Extra button 1 (side button)
21    Extra1,
22    /// Extra button 2 (side button)
23    Extra2,
24}
25
26impl MouseButton {
27    /// Convert to Linux button code
28    pub fn to_linux_button(&self) -> u32 {
29        match self {
30            MouseButton::Left => 0x110,   // BTN_LEFT
31            MouseButton::Right => 0x111,  // BTN_RIGHT
32            MouseButton::Middle => 0x112, // BTN_MIDDLE
33            MouseButton::Extra1 => 0x113, // BTN_SIDE
34            MouseButton::Extra2 => 0x114, // BTN_EXTRA
35        }
36    }
37
38    /// Convert from RDP button flags
39    pub fn from_rdp_button(button: u16) -> Option<Self> {
40        match button {
41            0x1000 => Some(MouseButton::Left),
42            0x2000 => Some(MouseButton::Right),
43            0x4000 => Some(MouseButton::Middle),
44            0x0080 => Some(MouseButton::Extra1),
45            0x0100 => Some(MouseButton::Extra2),
46            _ => None,
47        }
48    }
49}
50
51/// Mouse event types
52#[derive(Debug, Clone)]
53pub enum MouseEvent {
54    /// Mouse moved to absolute position
55    Move {
56        /// X coordinate
57        x: f64,
58        /// Y coordinate
59        y: f64,
60        /// Event timestamp
61        timestamp: Instant,
62    },
63
64    /// Mouse button pressed
65    ButtonDown {
66        /// Button that was pressed
67        button: MouseButton,
68        /// Position (stream coordinates), if the source event carried one.
69        /// `None` for sources with no absolute position (e.g. a relative
70        /// motion channel's button branch).
71        position: Option<(f64, f64)>,
72        /// Event timestamp
73        timestamp: Instant,
74    },
75
76    /// Mouse button released
77    ButtonUp {
78        /// Button that was released
79        button: MouseButton,
80        /// Position (stream coordinates), if the source event carried one.
81        position: Option<(f64, f64)>,
82        /// Event timestamp
83        timestamp: Instant,
84    },
85
86    /// Mouse wheel scrolled
87    Scroll {
88        /// Horizontal scroll delta
89        delta_x: i32,
90        /// Vertical scroll delta
91        delta_y: i32,
92        /// Event timestamp
93        timestamp: Instant,
94    },
95}
96
97/// Mouse event handler
98pub struct MouseHandler {
99    /// Current mouse position (stream coordinates)
100    current_x: f64,
101    current_y: f64,
102
103    /// Button states
104    button_states: [bool; 5],
105
106    /// Last event timestamp
107    last_event_time: Option<Instant>,
108
109    /// Enable high-precision scrolling
110    high_precision_scroll: bool,
111
112    /// Scroll accumulator for high-precision scrolling
113    scroll_accum_x: f64,
114    scroll_accum_y: f64,
115}
116
117impl MouseHandler {
118    /// Create a new mouse handler
119    pub fn new() -> Self {
120        Self {
121            current_x: 0.0,
122            current_y: 0.0,
123            button_states: [false; 5],
124            last_event_time: None,
125            high_precision_scroll: true,
126            scroll_accum_x: 0.0,
127            scroll_accum_y: 0.0,
128        }
129    }
130
131    /// Process absolute mouse movement from RDP
132    pub fn handle_absolute_move(
133        &mut self,
134        rdp_x: u32,
135        rdp_y: u32,
136        transformer: &mut CoordinateTransformer,
137    ) -> Result<MouseEvent> {
138        let (stream_x, stream_y) = transformer.rdp_to_stream(rdp_x as i32, rdp_y as i32)?;
139
140        // Clamp to bounds
141        let (stream_x, stream_y) = transformer.clamp_to_bounds(stream_x, stream_y);
142
143        self.current_x = stream_x;
144        self.current_y = stream_y;
145
146        let timestamp = Instant::now();
147        self.last_event_time = Some(timestamp);
148
149        debug!(
150            "Mouse move: RDP({}, {}) -> Stream({:.2}, {:.2})",
151            rdp_x, rdp_y, stream_x, stream_y
152        );
153
154        Ok(MouseEvent::Move {
155            x: stream_x,
156            y: stream_y,
157            timestamp,
158        })
159    }
160
161    /// Process relative mouse movement from RDP
162    pub fn handle_relative_move(
163        &mut self,
164        delta_x: i32,
165        delta_y: i32,
166        transformer: &mut CoordinateTransformer,
167    ) -> Result<MouseEvent> {
168        let (stream_x, stream_y) = transformer.apply_relative_movement(delta_x, delta_y)?;
169
170        // Clamp to bounds
171        let (stream_x, stream_y) = transformer.clamp_to_bounds(stream_x, stream_y);
172
173        self.current_x = stream_x;
174        self.current_y = stream_y;
175
176        let timestamp = Instant::now();
177        self.last_event_time = Some(timestamp);
178
179        debug!(
180            "Mouse relative move: Delta({}, {}) -> Stream({:.2}, {:.2})",
181            delta_x, delta_y, stream_x, stream_y
182        );
183
184        Ok(MouseEvent::Move {
185            x: stream_x,
186            y: stream_y,
187            timestamp,
188        })
189    }
190
191    /// Process mouse button press.
192    ///
193    /// `position`, when `Some`, is the RDP-coordinate position the source
194    /// event carried (e.g. a `MousePdu`/`MouseXPdu`-originated button PDU
195    /// always has one); it is transformed and clamped the same way
196    /// [`Self::handle_absolute_move`] does, and updates the tracked cursor
197    /// position before the button state changes, so the button lands where
198    /// the client actually clicked rather than wherever the cursor was last.
199    /// Pass `None` for sources with no absolute position to report (a
200    /// relative-motion channel's button branch).
201    pub fn handle_button_down(
202        &mut self,
203        button: MouseButton,
204        position: Option<(u32, u32)>,
205        transformer: &mut CoordinateTransformer,
206    ) -> Result<MouseEvent> {
207        let stream_position = self.apply_button_position(position, transformer)?;
208
209        let button_index = Self::button_to_index(button);
210        self.button_states[button_index] = true;
211
212        let timestamp = Instant::now();
213        self.last_event_time = Some(timestamp);
214
215        debug!("Mouse button down: {:?} at {:?}", button, stream_position);
216
217        Ok(MouseEvent::ButtonDown {
218            button,
219            position: stream_position,
220            timestamp,
221        })
222    }
223
224    /// Process mouse button release. See [`Self::handle_button_down`] for
225    /// the `position` parameter's contract.
226    pub fn handle_button_up(
227        &mut self,
228        button: MouseButton,
229        position: Option<(u32, u32)>,
230        transformer: &mut CoordinateTransformer,
231    ) -> Result<MouseEvent> {
232        let stream_position = self.apply_button_position(position, transformer)?;
233
234        let button_index = Self::button_to_index(button);
235        self.button_states[button_index] = false;
236
237        let timestamp = Instant::now();
238        self.last_event_time = Some(timestamp);
239
240        debug!("Mouse button up: {:?} at {:?}", button, stream_position);
241
242        Ok(MouseEvent::ButtonUp {
243            button,
244            position: stream_position,
245            timestamp,
246        })
247    }
248
249    /// Shared position handling for button down/up: transform, clamp, and
250    /// update the tracked cursor position, matching
251    /// [`Self::handle_absolute_move`]'s treatment of the same RDP-coordinate
252    /// input. Returns the resulting stream coordinates, or `None` if no
253    /// position was given.
254    fn apply_button_position(
255        &mut self,
256        position: Option<(u32, u32)>,
257        transformer: &mut CoordinateTransformer,
258    ) -> Result<Option<(f64, f64)>> {
259        let Some((rdp_x, rdp_y)) = position else {
260            return Ok(None);
261        };
262
263        let (stream_x, stream_y) = transformer.rdp_to_stream(rdp_x as i32, rdp_y as i32)?;
264        let (stream_x, stream_y) = transformer.clamp_to_bounds(stream_x, stream_y);
265
266        self.current_x = stream_x;
267        self.current_y = stream_y;
268
269        Ok(Some((stream_x, stream_y)))
270    }
271
272    /// Process mouse wheel scroll
273    pub fn handle_scroll(&mut self, delta_x: i32, delta_y: i32) -> Result<MouseEvent> {
274        let timestamp = Instant::now();
275        self.last_event_time = Some(timestamp);
276
277        let (final_delta_x, final_delta_y) = if self.high_precision_scroll {
278            // Accumulate fractional scrolling
279            self.scroll_accum_x += delta_x as f64 / 120.0;
280            self.scroll_accum_y += delta_y as f64 / 120.0;
281
282            let x = self.scroll_accum_x.trunc() as i32;
283            let y = self.scroll_accum_y.trunc() as i32;
284
285            self.scroll_accum_x -= x as f64;
286            self.scroll_accum_y -= y as f64;
287
288            (x, y)
289        } else {
290            // Standard scrolling
291            (delta_x / 120, delta_y / 120)
292        };
293
294        debug!("Mouse scroll: ({}, {})", final_delta_x, final_delta_y);
295
296        Ok(MouseEvent::Scroll {
297            delta_x: final_delta_x,
298            delta_y: final_delta_y,
299            timestamp,
300        })
301    }
302
303    /// Get current mouse position
304    pub fn current_position(&self) -> (f64, f64) {
305        (self.current_x, self.current_y)
306    }
307
308    /// Check if button is currently pressed
309    pub fn is_button_pressed(&self, button: MouseButton) -> bool {
310        let index = Self::button_to_index(button);
311        self.button_states[index]
312    }
313
314    /// Get time since last event
315    pub fn time_since_last_event(&self) -> Option<std::time::Duration> {
316        self.last_event_time.map(|t| t.elapsed())
317    }
318
319    /// Set high-precision scrolling enabled
320    pub fn set_high_precision_scroll(&mut self, enabled: bool) {
321        self.high_precision_scroll = enabled;
322        if !enabled {
323            self.scroll_accum_x = 0.0;
324            self.scroll_accum_y = 0.0;
325        }
326    }
327
328    /// Convert button to array index
329    fn button_to_index(button: MouseButton) -> usize {
330        match button {
331            MouseButton::Left => 0,
332            MouseButton::Right => 1,
333            MouseButton::Middle => 2,
334            MouseButton::Extra1 => 3,
335            MouseButton::Extra2 => 4,
336        }
337    }
338
339    /// Reset mouse state
340    pub fn reset(&mut self) {
341        self.button_states = [false; 5];
342        self.scroll_accum_x = 0.0;
343        self.scroll_accum_y = 0.0;
344    }
345}
346
347impl Default for MouseHandler {
348    fn default() -> Self {
349        Self::new()
350    }
351}
352
353#[cfg(test)]
354mod tests {
355    use super::*;
356    use crate::coordinates::MonitorInfo;
357
358    fn create_test_transformer() -> CoordinateTransformer {
359        let monitor = MonitorInfo {
360            id: 1,
361            name: "Primary".to_string(),
362            x: 0,
363            y: 0,
364            width: 1920,
365            height: 1080,
366            dpi: 96.0,
367            scale_factor: 1.0,
368            stream_x: 0,
369            stream_y: 0,
370            stream_width: 1920,
371            stream_height: 1080,
372            is_primary: true,
373        };
374
375        CoordinateTransformer::new(vec![monitor]).unwrap()
376    }
377
378    #[test]
379    fn test_mouse_handler_creation() {
380        let handler = MouseHandler::new();
381        let (x, y) = handler.current_position();
382        assert_eq!(x, 0.0);
383        assert_eq!(y, 0.0);
384    }
385
386    #[test]
387    fn test_absolute_move() {
388        let mut handler = MouseHandler::new();
389        let mut transformer = create_test_transformer();
390
391        let event = handler.handle_absolute_move(960, 540, &mut transformer).unwrap();
392
393        match event {
394            MouseEvent::Move { x, y, .. } => {
395                assert!(x > 0.0);
396                assert!(y > 0.0);
397            }
398            _ => panic!("Expected Move event"),
399        }
400
401        let (x, y) = handler.current_position();
402        assert!(x > 0.0);
403        assert!(y > 0.0);
404    }
405
406    #[test]
407    fn test_relative_move() {
408        let mut handler = MouseHandler::new();
409        let mut transformer = create_test_transformer();
410
411        let event = handler.handle_relative_move(10, 10, &mut transformer).unwrap();
412
413        match event {
414            MouseEvent::Move { .. } => {}
415            _ => panic!("Expected Move event"),
416        }
417    }
418
419    #[test]
420    fn test_button_press_release() {
421        let mut handler = MouseHandler::new();
422        let mut transformer = create_test_transformer();
423
424        // Press left button
425        let event = handler
426            .handle_button_down(MouseButton::Left, None, &mut transformer)
427            .unwrap();
428        match event {
429            MouseEvent::ButtonDown { button, .. } => {
430                assert_eq!(button, MouseButton::Left);
431            }
432            _ => panic!("Expected ButtonDown event"),
433        }
434
435        assert!(handler.is_button_pressed(MouseButton::Left));
436
437        // Release left button
438        let event = handler
439            .handle_button_up(MouseButton::Left, None, &mut transformer)
440            .unwrap();
441        match event {
442            MouseEvent::ButtonUp { button, .. } => {
443                assert_eq!(button, MouseButton::Left);
444            }
445            _ => panic!("Expected ButtonUp event"),
446        }
447
448        assert!(!handler.is_button_pressed(MouseButton::Left));
449    }
450
451    #[test]
452    fn test_button_press_carries_position() {
453        let mut handler = MouseHandler::new();
454        let mut transformer = create_test_transformer();
455
456        let event = handler
457            .handle_button_down(MouseButton::Left, Some((960, 540)), &mut transformer)
458            .unwrap();
459
460        match event {
461            MouseEvent::ButtonDown { button, position, .. } => {
462                assert_eq!(button, MouseButton::Left);
463                let (x, y) = position.expect("position should be carried through");
464                assert!(x > 0.0);
465                assert!(y > 0.0);
466            }
467            _ => panic!("Expected ButtonDown event"),
468        }
469
470        let (x, y) = handler.current_position();
471        assert!(x > 0.0);
472        assert!(y > 0.0);
473    }
474
475    #[test]
476    fn test_button_press_without_position_leaves_current_position_unchanged() {
477        let mut handler = MouseHandler::new();
478        let mut transformer = create_test_transformer();
479
480        handler.handle_absolute_move(960, 540, &mut transformer).unwrap();
481        let (before_x, before_y) = handler.current_position();
482
483        let event = handler
484            .handle_button_down(MouseButton::Left, None, &mut transformer)
485            .unwrap();
486
487        match event {
488            MouseEvent::ButtonDown { position, .. } => {
489                assert!(position.is_none());
490            }
491            _ => panic!("Expected ButtonDown event"),
492        }
493
494        let (after_x, after_y) = handler.current_position();
495        assert_eq!(before_x, after_x);
496        assert_eq!(before_y, after_y);
497    }
498
499    #[test]
500    fn test_scroll_event() {
501        let mut handler = MouseHandler::new();
502
503        let event = handler.handle_scroll(0, 120).unwrap();
504
505        match event {
506            MouseEvent::Scroll { delta_y, .. } => {
507                assert_eq!(delta_y, 1);
508            }
509            _ => panic!("Expected Scroll event"),
510        }
511    }
512
513    #[test]
514    fn test_high_precision_scroll() {
515        let mut handler = MouseHandler::new();
516        handler.set_high_precision_scroll(true);
517
518        // Send small scroll increments
519        for _ in 0..10 {
520            let _ = handler.handle_scroll(0, 12); // 1/10 of a standard scroll unit
521        }
522
523        // Should accumulate to one full scroll unit
524        let event = handler.handle_scroll(0, 0).unwrap();
525        match event {
526            MouseEvent::Scroll { delta_y, .. } => {
527                assert_eq!(delta_y, 0); // Accumulated but not yet reached threshold
528            }
529            _ => panic!("Expected Scroll event"),
530        }
531    }
532
533    #[test]
534    fn test_mouse_button_to_linux() {
535        assert_eq!(MouseButton::Left.to_linux_button(), 0x110);
536        assert_eq!(MouseButton::Right.to_linux_button(), 0x111);
537        assert_eq!(MouseButton::Middle.to_linux_button(), 0x112);
538        assert_eq!(MouseButton::Extra1.to_linux_button(), 0x113);
539        assert_eq!(MouseButton::Extra2.to_linux_button(), 0x114);
540    }
541
542    #[test]
543    fn test_mouse_button_from_rdp() {
544        assert_eq!(MouseButton::from_rdp_button(0x1000), Some(MouseButton::Left));
545        assert_eq!(MouseButton::from_rdp_button(0x2000), Some(MouseButton::Right));
546        assert_eq!(MouseButton::from_rdp_button(0x4000), Some(MouseButton::Middle));
547        assert_eq!(MouseButton::from_rdp_button(0x0080), Some(MouseButton::Extra1));
548        assert_eq!(MouseButton::from_rdp_button(0x0100), Some(MouseButton::Extra2));
549        assert_eq!(MouseButton::from_rdp_button(0x9999), None);
550    }
551
552    #[test]
553    fn test_multiple_button_states() {
554        let mut handler = MouseHandler::new();
555        let mut transformer = create_test_transformer();
556
557        handler
558            .handle_button_down(MouseButton::Left, None, &mut transformer)
559            .unwrap();
560        handler
561            .handle_button_down(MouseButton::Right, None, &mut transformer)
562            .unwrap();
563
564        assert!(handler.is_button_pressed(MouseButton::Left));
565        assert!(handler.is_button_pressed(MouseButton::Right));
566        assert!(!handler.is_button_pressed(MouseButton::Middle));
567
568        handler
569            .handle_button_up(MouseButton::Left, None, &mut transformer)
570            .unwrap();
571
572        assert!(!handler.is_button_pressed(MouseButton::Left));
573        assert!(handler.is_button_pressed(MouseButton::Right));
574    }
575
576    #[test]
577    fn test_mouse_reset() {
578        let mut handler = MouseHandler::new();
579        let mut transformer = create_test_transformer();
580
581        handler
582            .handle_button_down(MouseButton::Left, None, &mut transformer)
583            .unwrap();
584        handler
585            .handle_button_down(MouseButton::Right, None, &mut transformer)
586            .unwrap();
587        handler.scroll_accum_x = 5.0;
588        handler.scroll_accum_y = 5.0;
589
590        handler.reset();
591
592        assert!(!handler.is_button_pressed(MouseButton::Left));
593        assert!(!handler.is_button_pressed(MouseButton::Right));
594        assert_eq!(handler.scroll_accum_x, 0.0);
595        assert_eq!(handler.scroll_accum_y, 0.0);
596    }
597
598    #[test]
599    fn test_time_since_last_event() {
600        let mut handler = MouseHandler::new();
601        let mut transformer = create_test_transformer();
602
603        assert!(handler.time_since_last_event().is_none());
604
605        handler
606            .handle_button_down(MouseButton::Left, None, &mut transformer)
607            .unwrap();
608
609        assert!(handler.time_since_last_event().is_some());
610        assert!(handler.time_since_last_event().unwrap().as_millis() < 100);
611    }
612}