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