1use std::{ffi, mem, ptr};
2
3use windows_sys::{
4 Wdk::{
5 Foundation::OBJECT_ATTRIBUTES,
6 Storage::FileSystem::{FILE_NON_DIRECTORY_FILE, FILE_OPEN_IF, FILE_SYNCHRONOUS_IO_NONALERT, NtCreateFile},
7 System::{IO::NtDeviceIoControlFile, SystemServices::ZwClose},
8 },
9 Win32::{
10 Foundation::{GENERIC_WRITE, HANDLE, NTSTATUS, STATUS_SUCCESS, UNICODE_STRING},
11 Storage::FileSystem::{FILE_ATTRIBUTE_NORMAL, SYNCHRONIZE},
12 System::{IO::IO_STATUS_BLOCK, WindowsProgramming::RtlInitUnicodeString},
13 },
14 core::PCWSTR,
15};
16
17use crate::{KeyboardButton, MouseButton, util::InitializeObjectAttributes};
18
19#[repr(C)]
21struct MouseIO {
22 button: u8,
23 x: i8,
24 y: i8,
25 wheel: i8,
26 unk1: i8,
27}
28
29impl MouseIO {
30 const fn new(button: u8, x: i8, y: i8, wheel: i8) -> Self {
31 let unk1 = 0;
32 Self {
33 button,
34 x,
35 y,
36 wheel,
37 unk1,
38 }
39 }
40}
41
42#[repr(C)]
44struct KeyboardIO {
45 unknown1: u8,
46 unknown2: u8,
47 button1: u8,
48 button2: u8,
49 button3: u8,
50 button4: u8,
51 button5: u8,
52 button6: u8,
53}
54
55impl KeyboardIO {
56 const fn new(button1: u8, button2: u8, button3: u8, button4: u8, button5: u8, button6: u8) -> Self {
57 let unknown1 = 0;
58 let unknown2 = 0;
59 Self {
60 unknown1,
61 unknown2,
62 button1,
63 button2,
64 button3,
65 button4,
66 button5,
67 button6,
68 }
69 }
70}
71
72pub struct Device {
74 filehandle: HANDLE,
75 iostatusblock: IO_STATUS_BLOCK,
76}
77
78impl Drop for Device {
79 fn drop(&mut self) {
80 self.close();
81 }
82}
83
84impl Device {
85 pub fn try_new() -> Result<Self, &'static str> {
90 let filehandle = HANDLE::default();
91 let iostatusblock = IO_STATUS_BLOCK::default();
92
93 let mut device = Self {
94 filehandle,
95 iostatusblock,
96 };
97
98 if !device.open() {
99 return Err("Device not found. Consider to download Logitech G HUB 2021.11.1775");
100 }
101
102 Ok(device)
103 }
104
105 pub fn send_mouse(&mut self, button: MouseButton, x: i8, y: i8, wheel: i8) {
107 let mut io = MouseIO::new(button.into(), x, y, wheel);
108
109 if !self.call_mouse(&mut io) {
110 self.close();
111 self.open(); }
113 }
114
115 pub fn send_keyboard(
117 &mut self,
118 button1: KeyboardButton,
119 button2: KeyboardButton,
120 button3: KeyboardButton,
121 button4: KeyboardButton,
122 button5: KeyboardButton,
123 button6: KeyboardButton,
124 ) {
125 let mut buffer = KeyboardIO::new(
126 button1.into(),
127 button2.into(),
128 button3.into(),
129 button4.into(),
130 button5.into(),
131 button6.into(),
132 );
133
134 if !self.call_keyboard(&mut buffer) {
135 self.close();
136 self.open(); }
138 }
139
140 fn open(&mut self) -> bool {
145 let buffers: [Vec<u16>; 2] = [
146 "\\??\\ROOT#SYSTEM#0001#{1abc05c0-c378-41b9-9cef-df1aba82b015}\0"
147 .encode_utf16()
148 .collect(),
149 "\\??\\ROOT#SYSTEM#0002#{1abc05c0-c378-41b9-9cef-df1aba82b015}\0"
150 .encode_utf16()
151 .collect(),
152 ];
153
154 for buffer in buffers {
155 if self.device_initialize(buffer.as_ptr()) == STATUS_SUCCESS {
156 return true;
157 }
158 }
159
160 false
161 }
162
163 fn device_initialize(&mut self, device_name: PCWSTR) -> NTSTATUS {
171 let mut name = UNICODE_STRING::default();
172 let mut attr = OBJECT_ATTRIBUTES::default();
173
174 unsafe {
175 RtlInitUnicodeString(&raw mut name, device_name);
176 InitializeObjectAttributes(&mut attr, &raw const name, 0, ptr::null_mut(), ptr::null());
177
178 NtCreateFile(
179 &raw mut self.filehandle,
180 GENERIC_WRITE | SYNCHRONIZE,
181 &raw const attr,
182 &raw mut self.iostatusblock,
183 ptr::null::<i64>(), FILE_ATTRIBUTE_NORMAL,
185 0,
186 FILE_OPEN_IF, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
188 ptr::null(),
189 0,
190 )
191 }
192 }
193
194 fn call_mouse(&self, buffer: &mut MouseIO) -> bool {
202 #[allow(clippy::cast_possible_truncation)] const INPUTBUFFERLENGTH: u32 = mem::size_of::<MouseIO>() as u32;
204
205 let mut block = IO_STATUS_BLOCK::default();
206
207 let status = unsafe {
208 NtDeviceIoControlFile(
209 self.filehandle,
210 ptr::null_mut(),
211 None,
212 ptr::null(),
213 &raw mut block,
214 0x002A_2010,
215 ptr::from_mut(buffer).cast::<ffi::c_void>(),
216 INPUTBUFFERLENGTH,
217 ptr::null_mut(),
218 0,
219 )
220 };
221 status == STATUS_SUCCESS
222 }
223
224 fn call_keyboard(&self, buffer: &mut KeyboardIO) -> bool {
232 #[allow(clippy::cast_possible_truncation)] const INPUTBUFFERLENGTH: u32 = mem::size_of::<KeyboardIO>() as u32;
234
235 let mut block = IO_STATUS_BLOCK::default();
236
237 let status = unsafe {
238 NtDeviceIoControlFile(
239 self.filehandle,
240 ptr::null_mut(),
241 None,
242 ptr::null(),
243 &raw mut block,
244 0x002A_200C,
245 ptr::from_mut(buffer).cast::<ffi::c_void>(),
246 INPUTBUFFERLENGTH,
247 ptr::null_mut(),
248 0,
249 )
250 };
251 status == STATUS_SUCCESS
252 }
253
254 fn close(&mut self) {
256 unsafe {
257 if !self.filehandle.is_null() {
258 let _ = ZwClose(self.filehandle);
259 self.filehandle = ptr::null_mut();
260 }
261 }
262 }
263}
264
265mod tests {
266 #[allow(unused_imports)]
267 use super::*;
268
269 #[test]
270 fn test_open_close() {
271 let mut device = Device {
272 filehandle: HANDLE::default(),
273 iostatusblock: IO_STATUS_BLOCK::default(),
274 };
275 assert!(device.open(), "Device not opened");
276 device.close();
277 assert!(device.filehandle.is_null());
278 }
279
280 #[test]
281 fn test_call_mouse() {
282 let device = Device::try_new().unwrap();
283 let mut buffer = MouseIO::new(0, 0, 0, 0);
284 assert!(device.call_mouse(&mut buffer));
285 }
286
287 #[test]
288 fn test_call_keyboard() {
289 let device = Device::try_new().unwrap();
290 let mut buffer = KeyboardIO::new(0, 0, 0, 0, 0, 0);
291 assert!(device.call_keyboard(&mut buffer));
292 }
293}