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