logitech_cve/
mouse.rs

1use core::{cmp::Ordering, time::Duration};
2use std::thread;
3
4use windows_sys::Win32::{Foundation::POINT, UI::WindowsAndMessaging::GetCursorPos};
5
6use crate::device::Device;
7
8#[repr(u8)]
9#[derive(Copy, Clone)]
10pub enum MouseButton {
11    Left = 1,
12    Right = 2,
13    Middle = 4,
14    LeftRight = 3,
15    LeftMiddle = 5,
16    RightMiddle = 6,
17    All = 7,
18    Release = 0,
19}
20
21impl From<MouseButton> for u8 {
22    #[inline]
23    fn from(button: MouseButton) -> Self {
24        button as Self
25    }
26}
27
28/// A struct for controlling a virtual mouse.
29///
30/// It holds a reference to a `Device` which is used to send the mouse commands.
31pub struct Mouse<'a> {
32    /// Reference to the device used for sending mouse commands.
33    device: &'a Device,
34}
35
36impl<'a> Mouse<'a> {
37    /// Creates a new [`Mouse`].
38    #[must_use]
39    pub const fn new(device: &'a Device) -> Self {
40        Self { device }
41    }
42
43    /// Performs a click and release action with a specified button.
44    ///
45    /// The button is pressed, held for `millis` milliseconds, and then released.
46    ///
47    /// # Arguments
48    ///
49    /// * `button` - The `MouseButton` to click.
50    /// * `millis` - The duration, in milliseconds, to hold the button down.
51    pub fn click(&self, button: MouseButton, millis: u64) {
52        self.device.call_mouse(button, 0, 0, 0);
53        thread::sleep(Duration::from_millis(millis));
54        self.device.call_mouse(MouseButton::Release, 0, 0, 0);
55    }
56
57    /// Moves the mouse cursor to an absolute screen coordinate (x, y) with a simulated smooth movement.
58    ///
59    /// The movement is broken down into smaller steps, with a delay between each step.
60    ///
61    /// # Arguments
62    ///
63    /// * `button` - The `MouseButton` to hold down during the movement (e.g., for dragging). Use `MouseButton::Release` for no buttons.
64    /// * `x` - The target horizontal coordinate.
65    /// * `y` - The target vertical coordinate.
66    /// * `millis` - The delay, in milliseconds, between each small movement step.
67    #[expect(
68        clippy::cast_possible_truncation,
69        reason = "Casting is safe here because mouse movement steps are always within i8 range."
70    )]
71    pub fn move_absolute(&self, button: MouseButton, x: u16, y: u16, millis: u64) {
72        const MIN_STEP_SIZE: i8 = -127; // -128 Does not work for some reason
73        const MAX_STEP_SIZE: i8 = 127;
74
75        #[inline]
76        fn calculate_steps_and_size(delta: i32) -> (i32, i8) {
77            if delta < 0 {
78                return (delta / i32::from(MIN_STEP_SIZE), MIN_STEP_SIZE);
79            }
80            (delta / i32::from(MAX_STEP_SIZE), MAX_STEP_SIZE)
81        }
82
83        // Get current mouse position
84        let mut current_point = POINT::default();
85        // SAFETY: `current_point` is a valid pointer to a POINT struct, as required by GetCursorPos.
86        unsafe {
87            GetCursorPos(&raw mut current_point);
88        };
89
90        // Calculate deltas
91        let delta_x = i32::from(x) - current_point.x;
92        let delta_y = i32::from(y) - current_point.y;
93
94        // Calculate the number of steps and step sizes for both X and Y
95        let (steps_x, mut x_step) = calculate_steps_and_size(delta_x);
96        let (steps_y, mut y_step) = calculate_steps_and_size(delta_y);
97
98        let (final_step_x, final_step_y);
99        if steps_x > 0 || steps_y > 0 {
100            // Determine which axis takes more steps
101            let steps;
102            match steps_x.cmp(&steps_y) {
103                Ordering::Greater => {
104                    steps = steps_x;
105                    y_step = (delta_y / steps) as i8;
106                }
107                Ordering::Less => {
108                    steps = steps_y;
109                    x_step = (delta_x / steps) as i8;
110                }
111                Ordering::Equal => {
112                    steps = steps_x; // or steps_y, they are equal
113                }
114            }
115
116            final_step_x = (delta_x - (i32::from(x_step) * steps)) as i8;
117            final_step_y = (delta_y - (i32::from(y_step) * steps)) as i8;
118            // Perform the movement in steps
119            for _ in 0..steps {
120                self.move_relative(button, x_step, y_step);
121                thread::sleep(Duration::from_millis(millis));
122            }
123        } else {
124            final_step_x = delta_x as i8;
125            final_step_y = delta_y as i8;
126        }
127
128        // Ensure the final move reaches the target
129        self.move_relative(button, final_step_x, final_step_y);
130    }
131
132    /// Moves the mouse cursor by a relative offset from its current position.
133    ///
134    /// # Arguments
135    ///
136    /// * `button` - The `MouseButton` to hold down during the movement.
137    /// * `x` - The horizontal offset. Positive values move right, negative move left.
138    /// * `y` - The vertical offset. Positive values move down, negative move up.
139    #[inline]
140    pub fn move_relative(&self, button: MouseButton, x: i8, y: i8) {
141        self.device.call_mouse(button, x, y, 0);
142    }
143
144    /// Presses and holds a specified mouse button.
145    ///
146    /// This method only presses the button; you must call `release()` to release it.
147    ///
148    /// # Arguments
149    ///
150    /// * `button` - The `MouseButton` to press.
151    #[inline]
152    pub fn press(&self, button: MouseButton) {
153        self.device.call_mouse(button, 0, 0, 0);
154    }
155
156    /// Releases any currently pressed mouse buttons.
157    ///
158    /// This should be called after a `press()` action to release the button.
159    #[inline]
160    pub fn release(&self) {
161        self.device.call_mouse(MouseButton::Release, 0, 0, 0);
162    }
163
164    /// Scrolls the mouse wheel.
165    ///
166    /// # Arguments
167    ///
168    /// * `button` - The `MouseButton` to hold down during the scroll.
169    /// * `wheel` - The scroll amount. Positive values scroll up, negative values scroll down.
170    #[inline]
171    pub fn wheel(&self, button: MouseButton, wheel: i8) {
172        self.device.call_mouse(button, 0, 0, wheel);
173    }
174}