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}