Skip to main content

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    #[inline]
52    pub const fn new(device: &'a Device) -> Self {
53        Self { device }
54    }
55
56    /// Performs a click and release action with a specified button.
57    ///
58    /// The button is pressed, held for `millis` milliseconds, and then released.
59    ///
60    /// # Arguments
61    ///
62    /// * `button` - The `MouseButton` to click.
63    /// * `millis` - The duration, in milliseconds, to hold the button down.
64    #[inline]
65    pub fn click(&self, button: MouseButton, millis: u64) {
66        self.device.call_mouse(button, 0, 0, 0);
67        thread::sleep(Duration::from_millis(millis));
68        self.device.call_mouse(MouseButton::Release, 0, 0, 0);
69    }
70
71    /// Moves the mouse cursor to an absolute screen coordinate (x, y) with a simulated smooth movement.
72    ///
73    /// The movement is broken down into smaller steps, with a delay between each step.
74    ///
75    /// # Arguments
76    ///
77    /// * `button` - The `MouseButton` to hold down during the movement (e.g., for dragging). Use `MouseButton::Release` for no buttons.
78    /// * `x` - The target horizontal coordinate.
79    /// * `y` - The target vertical coordinate.
80    /// * `millis` - The delay, in milliseconds, between each small movement step.
81    #[expect(
82        clippy::cast_possible_truncation,
83        reason = "Casting is safe here because mouse movement steps are always within i8 range."
84    )]
85    #[inline]
86    pub fn move_absolute(&self, button: MouseButton, x: u16, y: u16, millis: u64) {
87        const MIN_STEP_SIZE: i8 = -127; // -128 Does not work for some reason
88        const MAX_STEP_SIZE: i8 = 127;
89
90        #[inline]
91        fn calculate_steps_and_size(delta: i32) -> (i32, i8) {
92            if delta < 0 {
93                return (delta / i32::from(MIN_STEP_SIZE), MIN_STEP_SIZE);
94            }
95            (delta / i32::from(MAX_STEP_SIZE), MAX_STEP_SIZE)
96        }
97
98        // Get current mouse position
99        let mut current_point = POINT::default();
100        // SAFETY: `current_point` is a valid pointer to a POINT struct, as required by GetCursorPos.
101        unsafe {
102            GetCursorPos(&raw mut current_point);
103        };
104
105        // Calculate deltas
106        let delta_x = i32::from(x) - current_point.x;
107        let delta_y = i32::from(y) - current_point.y;
108
109        // Calculate the number of steps and step sizes for both X and Y
110        let (steps_x, mut x_step) = calculate_steps_and_size(delta_x);
111        let (steps_y, mut y_step) = calculate_steps_and_size(delta_y);
112
113        let (final_step_x, final_step_y);
114        if steps_x > 0 || steps_y > 0 {
115            // Determine which axis takes more steps
116            let steps;
117            match steps_x.cmp(&steps_y) {
118                Ordering::Greater => {
119                    steps = steps_x;
120                    y_step = (delta_y / steps) as i8;
121                }
122                Ordering::Less => {
123                    steps = steps_y;
124                    x_step = (delta_x / steps) as i8;
125                }
126                Ordering::Equal => {
127                    steps = steps_x; // or steps_y, they are equal
128                }
129            }
130
131            final_step_x = (delta_x - (i32::from(x_step) * steps)) as i8;
132            final_step_y = (delta_y - (i32::from(y_step) * steps)) as i8;
133            // Perform the movement in steps
134            for _ in 0..steps {
135                self.move_relative(button, x_step, y_step);
136                thread::sleep(Duration::from_millis(millis));
137            }
138        } else {
139            final_step_x = delta_x as i8;
140            final_step_y = delta_y as i8;
141        }
142
143        // Ensure the final move reaches the target
144        self.move_relative(button, final_step_x, final_step_y);
145    }
146
147    /// Moves the mouse cursor by a relative offset from its current position.
148    ///
149    /// # Arguments
150    ///
151    /// * `button` - The `MouseButton` to hold down during the movement.
152    /// * `x` - The horizontal offset. Positive values move right, negative move left.
153    /// * `y` - The vertical offset. Positive values move down, negative move up.
154    #[inline]
155    pub fn move_relative(&self, button: MouseButton, x: i8, y: i8) {
156        self.device.call_mouse(button, x, y, 0);
157    }
158
159    /// Presses and holds a specified mouse button.
160    ///
161    /// This method only presses the button; you must call `release()` to release it.
162    ///
163    /// # Arguments
164    ///
165    /// * `button` - The `MouseButton` to press.
166    #[inline]
167    pub fn press(&self, button: MouseButton) {
168        self.device.call_mouse(button, 0, 0, 0);
169    }
170
171    /// Releases any currently pressed mouse buttons.
172    ///
173    /// This should be called after a `press()` action to release the button.
174    #[inline]
175    pub fn release(&self) {
176        self.device.call_mouse(MouseButton::Release, 0, 0, 0);
177    }
178
179    /// Scrolls the mouse wheel.
180    ///
181    /// # Arguments
182    ///
183    /// * `button` - The `MouseButton` to hold down during the scroll.
184    /// * `wheel` - The scroll amount. Positive values scroll up, negative values scroll down.
185    #[inline]
186    pub fn wheel(&self, button: MouseButton, wheel: i8) {
187        self.device.call_mouse(button, 0, 0, wheel);
188    }
189}