1use num_enum::FromPrimitive;
4use windows::Win32::Foundation::{
5 HMODULE,
6 LPARAM,
7 LRESULT,
8 POINT,
9 WPARAM,
10};
11use windows::Win32::UI::WindowsAndMessaging::{
12 CallNextHookEx,
13 SetWindowsHookExW,
14 UnhookWindowsHookEx,
15 HHOOK,
16 KBDLLHOOKSTRUCT,
17 MSLLHOOKSTRUCT,
18 WH_KEYBOARD_LL,
19 WH_MOUSE_LL,
20 WINDOWS_HOOK_ID,
21 WM_KEYDOWN,
22 WM_KEYUP,
23 WM_LBUTTONDOWN,
24 WM_LBUTTONUP,
25 WM_MBUTTONDOWN,
26 WM_MBUTTONUP,
27 WM_MOUSEMOVE,
28 WM_MOUSEWHEEL,
29 WM_RBUTTONDOWN,
30 WM_RBUTTONUP,
31 WM_SYSKEYDOWN,
32 WM_SYSKEYUP,
33 WM_XBUTTONDOWN,
34 WM_XBUTTONUP,
35};
36
37use std::cell::RefCell;
38use std::collections::HashMap;
39use std::fmt::Debug;
40use std::io;
41use std::marker::PhantomData;
42use std::sync::{
43 Mutex,
44 OnceLock,
45};
46
47use crate::input::{
48 KeyboardKey,
49 MouseButton,
50 MouseScrollEvent,
51};
52use crate::internal::catch_unwind_and_abort;
53use crate::internal::windows_missing::HIWORD;
54use crate::messaging::ThreadMessageLoop;
55
56use private::*;
57
58pub trait LowLevelInputHook: HookType + Copy {
63 fn run_hook<F>(user_callback: &mut F) -> io::Result<()>
64 where
65 F: FnMut(Self::Message) -> HookReturnValue + Send,
66 {
67 let _handle = Self::add_hook::<0, _>(user_callback)?;
69 ThreadMessageLoop::run_thread_message_loop(|| Ok(()))?;
70 Ok(())
71 }
72}
73
74#[derive(Copy, Clone, Debug)]
76pub enum LowLevelMouseHook {}
77
78impl HookType for LowLevelMouseHook {
79 const TYPE_ID: WINDOWS_HOOK_ID = WH_MOUSE_LL;
80 type Message = LowLevelMouseMessage;
81 type ClosureStore = ThreadLocalRawClosureStore;
82}
83
84impl LowLevelInputHook for LowLevelMouseHook {}
85
86#[derive(Copy, Clone, Debug)]
88pub enum LowLevelKeyboardHook {}
89
90impl HookType for LowLevelKeyboardHook {
91 const TYPE_ID: WINDOWS_HOOK_ID = WH_KEYBOARD_LL;
92 type Message = LowLevelKeyboardMessage;
93 type ClosureStore = ThreadLocalRawClosureStore;
94}
95
96impl LowLevelInputHook for LowLevelKeyboardHook {}
97
98#[derive(Copy, Clone, Debug)]
100pub struct LowLevelMouseMessage {
101 pub action: LowLevelMouseAction,
102 pub coords: POINT,
103 pub timestamp_ms: u32,
104}
105
106impl From<RawLowLevelMessage> for LowLevelMouseMessage {
107 fn from(value: RawLowLevelMessage) -> Self {
108 let w_param = u32::try_from(value.w_param).unwrap();
109 let message_data = unsafe { *(value.l_param as *const MSLLHOOKSTRUCT) };
110 let action = match (w_param, HIWORD(message_data.mouseData)) {
111 (WM_MOUSEMOVE, _) => LowLevelMouseAction::Move,
112 (WM_LBUTTONDOWN, _) => LowLevelMouseAction::ButtonDown(MouseButton::Left),
113 (WM_RBUTTONDOWN, _) => LowLevelMouseAction::ButtonDown(MouseButton::Right),
114 (WM_MBUTTONDOWN, _) => LowLevelMouseAction::ButtonDown(MouseButton::Middle),
115 (WM_XBUTTONDOWN, 1) => LowLevelMouseAction::ButtonDown(MouseButton::X1),
116 (WM_XBUTTONDOWN, 2) => LowLevelMouseAction::ButtonDown(MouseButton::X2),
117 (WM_LBUTTONUP, _) => LowLevelMouseAction::ButtonUp(MouseButton::Left),
118 (WM_RBUTTONUP, _) => LowLevelMouseAction::ButtonUp(MouseButton::Right),
119 (WM_MBUTTONUP, _) => LowLevelMouseAction::ButtonUp(MouseButton::Middle),
120 (WM_XBUTTONUP, 1) => LowLevelMouseAction::ButtonUp(MouseButton::X1),
121 (WM_XBUTTONUP, 2) => LowLevelMouseAction::ButtonUp(MouseButton::X2),
122 (WM_MOUSEWHEEL, raw_movement) => {
123 LowLevelMouseAction::WheelScroll(MouseScrollEvent::from_raw_movement(raw_movement))
124 }
125 (_, _) => LowLevelMouseAction::Other(w_param),
126 };
127 LowLevelMouseMessage {
128 action,
129 coords: message_data.pt,
130 timestamp_ms: message_data.time,
131 }
132 }
133}
134
135#[derive(Copy, Clone, Debug)]
137pub struct LowLevelKeyboardMessage {
138 pub action: LowLevelKeyboardAction,
139 pub key: KeyboardKey,
140 pub scan_code: u32,
141 pub timestamp_ms: u32,
142}
143
144impl From<RawLowLevelMessage> for LowLevelKeyboardMessage {
145 fn from(value: RawLowLevelMessage) -> Self {
146 let w_param = u32::try_from(value.w_param).unwrap();
147 let message_data = unsafe { *(value.l_param as *const KBDLLHOOKSTRUCT) };
148 let key = KeyboardKey::from(message_data.vkCode as u16);
149 let action = LowLevelKeyboardAction::from(w_param);
150 LowLevelKeyboardMessage {
151 action,
152 key,
153 scan_code: message_data.scanCode,
154 timestamp_ms: message_data.time,
155 }
156 }
157}
158
159#[derive(Copy, Clone, Eq, PartialEq, Debug)]
160pub enum LowLevelMouseAction {
161 Move,
162 ButtonDown(MouseButton),
163 ButtonUp(MouseButton),
164 WheelScroll(MouseScrollEvent),
165 Other(u32),
166}
167
168#[derive(FromPrimitive, Copy, Clone, Eq, PartialEq, Debug)]
169#[repr(u32)]
170pub enum LowLevelKeyboardAction {
171 KeyDown = WM_KEYDOWN,
173 KeyUp = WM_KEYUP,
174 SysKeyDown = WM_SYSKEYDOWN,
175 SysKeyUp = WM_SYSKEYUP,
176 #[num_enum(catch_all)]
177 Other(u32),
178}
179
180#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
183pub enum HookReturnValue {
184 #[default]
187 CallNextHook,
188 BlockMessage,
190 PassToWindowProcOnly,
192 ExplicitValue(LRESULT),
193}
194
195mod private {
196 use super::*;
197
198 type StoredFunction = usize;
199
200 pub trait RawClosureStore {
201 fn get_raw_closure<'a, HT, F>(id: IdType) -> Option<&'a mut F>
202 where
203 HT: HookType,
204 F: FnMut(HT::Message) -> HookReturnValue + Send;
205
206 fn set_raw_closure<HT, F>(id: IdType, user_callback: Option<&mut F>)
207 where
208 HT: HookType,
209 F: FnMut(HT::Message) -> HookReturnValue + Send;
210 }
211
212 pub enum ThreadLocalRawClosureStore {}
213
214 impl ThreadLocalRawClosureStore {
215 thread_local! {
218 static RAW_CLOSURE: RefCell<HashMap<IdType, StoredFunction>> = RefCell::new(HashMap::new());
219 }
220 }
221
222 impl RawClosureStore for ThreadLocalRawClosureStore {
223 fn get_raw_closure<'a, HT, F>(id: IdType) -> Option<&'a mut F>
224 where
225 HT: HookType,
226 F: FnMut(HT::Message) -> HookReturnValue,
227 {
228 let unwrapped_closure: Option<StoredFunction> =
229 Self::RAW_CLOSURE.with(|cell| cell.borrow_mut().get(&id).copied());
230 let closure: Option<&mut F> = unwrapped_closure.map(|x| unsafe { &mut *(x as *mut F) });
231 closure
232 }
233
234 fn set_raw_closure<HT, F>(id: IdType, maybe_user_callback: Option<&mut F>)
235 where
236 HT: HookType,
237 F: FnMut(HT::Message) -> HookReturnValue,
238 {
239 Self::RAW_CLOSURE.with(|cell| {
240 let mut map_ref = cell.borrow_mut();
241 assert_ne!(maybe_user_callback.is_some(), map_ref.contains_key(&id));
242 if let Some(user_callback) = maybe_user_callback {
243 map_ref.insert(id, user_callback as *mut F as StoredFunction);
244 } else {
245 map_ref.remove(&id);
246 }
247 });
248 }
249 }
250
251 type IdType = u128;
252
253 pub enum GlobalRawClosureStore {}
254
255 impl GlobalRawClosureStore {
256 fn closures() -> &'static Mutex<HashMap<IdType, StoredFunction>> {
257 static CLOSURES: OnceLock<Mutex<HashMap<IdType, StoredFunction>>> = OnceLock::new();
258 CLOSURES.get_or_init(|| Mutex::new(HashMap::new()))
259 }
260
261 fn get_raw_closure_with_id<'a, HT, F>(id: IdType) -> Option<&'a mut F>
262 where
263 HT: HookType,
264 F: FnMut(HT::Message) -> HookReturnValue + Send,
265 {
266 let raw_hooks = Self::closures().lock().unwrap();
267 let maybe_stored_fn: Option<StoredFunction> = raw_hooks.get(&id).copied();
268 let closure: Option<&mut F> =
269 maybe_stored_fn.map(|ptr_usize| unsafe { &mut *(ptr_usize as *mut F) });
270 closure
271 }
272
273 fn set_raw_closure_with_id<HT, F>(id: IdType, user_callback: Option<&mut F>)
274 where
275 HT: HookType,
276 F: FnMut(HT::Message) -> HookReturnValue + Send,
277 {
278 let mut hooks = Self::closures().lock().unwrap();
279 assert_ne!(user_callback.is_some(), hooks.contains_key(&id));
280 match user_callback {
281 Some(user_callback) => {
282 let value = user_callback as *mut F as StoredFunction;
283 hooks.insert(id, value);
284 }
285 None => {
286 hooks.remove(&id);
287 }
288 }
289 }
290 }
291
292 impl RawClosureStore for GlobalRawClosureStore {
293 fn get_raw_closure<'a, HT, F>(id: IdType) -> Option<&'a mut F>
294 where
295 HT: HookType,
296 F: FnMut(HT::Message) -> HookReturnValue + Send,
297 {
298 Self::get_raw_closure_with_id::<HT, _>(id)
299 }
300
301 fn set_raw_closure<HT, F>(id: IdType, user_callback: Option<&mut F>)
302 where
303 HT: HookType,
304 F: FnMut(HT::Message) -> HookReturnValue + Send,
305 {
306 Self::set_raw_closure_with_id::<HT, _>(id, user_callback)
307 }
308 }
309
310 #[derive(Copy, Clone, Debug)]
311 pub struct RawLowLevelMessage {
312 pub code: i32,
313 pub w_param: usize,
314 pub l_param: isize,
315 }
316
317 pub trait HookType: Sized {
318 const TYPE_ID: WINDOWS_HOOK_ID;
319 type Message: From<RawLowLevelMessage>;
320 type ClosureStore: RawClosureStore;
321
322 fn add_hook<const ID: IdType, F>(user_callback: &mut F) -> io::Result<HookHandle<Self>>
323 where
324 F: FnMut(Self::Message) -> HookReturnValue + Send,
325 {
326 unsafe extern "system" fn internal_callback<const ID: IdType, HT, F>(
327 code: i32,
328 w_param: WPARAM,
329 l_param: LPARAM,
330 ) -> LRESULT
331 where
332 HT: HookType,
333 F: FnMut(HT::Message) -> HookReturnValue + Send,
334 {
335 if code < 0 {
336 unsafe { return CallNextHookEx(HHOOK::default(), code, w_param, l_param) }
337 }
338 let call = move || {
339 let raw_message = RawLowLevelMessage {
340 code,
341 w_param: w_param.0,
342 l_param: l_param.0,
343 };
344 let message = HT::Message::from(raw_message);
345 let maybe_closure: Option<&mut F> =
346 HT::ClosureStore::get_raw_closure::<HT, F>(ID);
347 if let Some(closure) = maybe_closure {
348 closure(message)
349 } else {
350 panic!("Callback called without installed hook")
351 }
352 };
353 let result = catch_unwind_and_abort(call);
354 match result {
355 HookReturnValue::CallNextHook => unsafe {
356 CallNextHookEx(HHOOK::default(), code, w_param, l_param)
357 },
358 HookReturnValue::BlockMessage => LRESULT(1),
359 HookReturnValue::PassToWindowProcOnly => LRESULT(0),
360 HookReturnValue::ExplicitValue(l_result) => l_result,
361 }
362 }
363 Self::ClosureStore::set_raw_closure::<Self, F>(ID, Some(user_callback));
364 let handle = unsafe {
365 SetWindowsHookExW(
366 Self::TYPE_ID,
367 Some(internal_callback::<ID, Self, F>),
368 HMODULE::default(),
369 0,
370 )?
371 };
372 Ok(HookHandle::new(ID, handle))
373 }
374 }
375
376 #[derive(Debug)]
377 pub struct HookHandle<HT: HookType> {
378 id: IdType,
379 handle: HHOOK,
380 remove_initiated: bool,
381 phantom: PhantomData<HT>,
382 }
383
384 impl<HT: HookType> HookHandle<HT> {
385 fn new(id: IdType, handle: HHOOK) -> Self {
386 Self {
387 id,
388 handle,
389 remove_initiated: false,
390 phantom: PhantomData,
391 }
392 }
393
394 fn remove(&mut self) -> io::Result<()> {
395 if !self.remove_initiated {
396 self.remove_initiated = true;
397 unsafe { UnhookWindowsHookEx(self.handle)? };
398 HT::ClosureStore::set_raw_closure::<HT, fn(_) -> _>(self.id, None);
399 }
400 Ok(())
401 }
402 }
403
404 impl<HT: HookType> Drop for HookHandle<HT> {
405 fn drop(&mut self) {
406 self.remove().unwrap()
407 }
408 }
409}
410
411#[cfg(test)]
412mod tests {
413 use windows::Win32::System::Threading::GetCurrentThreadId;
414 use windows::Win32::UI::WindowsAndMessaging::{
415 PostThreadMessageW,
416 WM_QUIT,
417 };
418
419 use super::*;
420
421 #[test]
422 fn ll_hook_and_unhook() -> windows::core::Result<()> {
423 let mut callback =
424 |_message: LowLevelMouseMessage| -> HookReturnValue { HookReturnValue::CallNextHook };
425 unsafe {
426 PostThreadMessageW(
427 GetCurrentThreadId(),
428 WM_QUIT,
429 WPARAM::default(),
430 LPARAM::default(),
431 )?
432 };
433 LowLevelMouseHook::run_hook(&mut callback)?;
434 Ok(())
435 }
436}