1use std::cell::RefCell;
4use std::collections::HashMap;
5use std::ffi::c_void;
6use std::fmt::Debug;
7use std::marker::PhantomData;
8use std::sync::{
9 Mutex,
10 OnceLock,
11};
12use std::{
13 io,
14 ptr,
15};
16
17use num_enum::{
18 FromPrimitive,
19 IntoPrimitive,
20};
21use windows::Win32::Foundation::{
22 HWND,
23 LPARAM,
24 LRESULT,
25 POINT,
26 WPARAM,
27};
28use windows::Win32::UI::Accessibility::{
29 HWINEVENTHOOK,
30 SetWinEventHook,
31 UnhookWinEvent,
32};
33use windows::Win32::UI::WindowsAndMessaging::{
34 CallNextHookEx,
35 EVENT_MIN,
36 EVENT_OBJECT_CLOAKED,
37 EVENT_OBJECT_CREATE,
38 EVENT_OBJECT_DESTROY,
39 EVENT_OBJECT_END,
40 EVENT_OBJECT_FOCUS,
41 EVENT_OBJECT_LOCATIONCHANGE,
42 EVENT_OBJECT_NAMECHANGE,
43 EVENT_OBJECT_SHOW,
44 EVENT_OBJECT_STATECHANGE,
45 EVENT_OBJECT_UNCLOAKED,
46 EVENT_SYSTEM_CAPTUREEND,
47 EVENT_SYSTEM_CAPTURESTART,
48 EVENT_SYSTEM_END,
49 EVENT_SYSTEM_FOREGROUND,
50 EVENT_SYSTEM_MINIMIZEEND,
51 EVENT_SYSTEM_MINIMIZESTART,
52 EVENT_SYSTEM_MOVESIZEEND,
53 EVENT_SYSTEM_MOVESIZESTART,
54 HHOOK,
55 KBDLLHOOKSTRUCT,
56 MSLLHOOKSTRUCT,
57 SetWindowsHookExW,
58 UnhookWindowsHookEx,
59 WH_KEYBOARD_LL,
60 WH_MOUSE_LL,
61 WINDOWS_HOOK_ID,
62 WINEVENT_OUTOFCONTEXT,
63 WM_KEYDOWN,
64 WM_KEYUP,
65 WM_LBUTTONDOWN,
66 WM_LBUTTONUP,
67 WM_MBUTTONDOWN,
68 WM_MBUTTONUP,
69 WM_MOUSEMOVE,
70 WM_MOUSEWHEEL,
71 WM_RBUTTONDOWN,
72 WM_RBUTTONUP,
73 WM_SYSKEYDOWN,
74 WM_SYSKEYUP,
75 WM_XBUTTONDOWN,
76 WM_XBUTTONUP,
77};
78
79#[expect(clippy::wildcard_imports)]
80use self::private::*;
81use crate::input::{
82 MouseButton,
83 MouseScrollEvent,
84 VirtualKey,
85};
86use crate::internal::windows_missing::HIWORD;
87use crate::internal::{
88 RawBox,
89 ResultExt,
90 ReturnValue,
91 catch_unwind_and_abort,
92 values_to_ranges,
93};
94use crate::messaging::ThreadMessageLoop;
95use crate::ui::window::WindowHandle;
96
97#[must_use]
101pub struct LowLevelInputHook<HT: HookType, F> {
102 #[expect(dead_code)]
103 handle: HookHandle<HT::ClosureStore, F, HHOOK>,
104}
105
106impl<HT: HookType, F> LowLevelInputHook<HT, F> {
107 fn new<const ID: IdType>(user_callback: F) -> io::Result<Self>
108 where
109 F: FnMut(HT::Message) -> HookReturnValue,
110 {
111 let handle = HT::add_hook_internal::<ID, _>(user_callback)?;
112 Ok(Self { handle })
113 }
114}
115
116pub trait LowLevelInputHookType: HookType + Copy {
121 fn run_hook<F>(user_callback: F) -> io::Result<()>
122 where
123 F: FnMut(Self::Message) -> HookReturnValue,
124 {
125 let _handle = Self::add_hook::<0, _>(user_callback)?;
127 ThreadMessageLoop::new().run()?;
128 Ok(())
129 }
130
131 fn add_hook<const ID: IdType, F>(user_callback: F) -> io::Result<LowLevelInputHook<Self, F>>
139 where
140 F: FnMut(Self::Message) -> HookReturnValue,
141 {
142 LowLevelInputHook::new::<ID>(user_callback)
143 }
144}
145
146#[derive(Copy, Clone, Debug)]
148pub enum LowLevelMouseHook {}
149
150impl HookType for LowLevelMouseHook {
151 const TYPE_ID: WINDOWS_HOOK_ID = WH_MOUSE_LL;
152 type Message = LowLevelMouseMessage;
153 type ClosureStore = ThreadLocalRawClosureStore;
154}
155
156impl LowLevelInputHookType for LowLevelMouseHook {}
157
158#[derive(Copy, Clone, Debug)]
160pub enum LowLevelKeyboardHook {}
161
162impl HookType for LowLevelKeyboardHook {
163 const TYPE_ID: WINDOWS_HOOK_ID = WH_KEYBOARD_LL;
164 type Message = LowLevelKeyboardMessage;
165 type ClosureStore = ThreadLocalRawClosureStore;
166}
167
168impl LowLevelInputHookType for LowLevelKeyboardHook {}
169
170#[derive(Copy, Clone, PartialEq, Debug)]
172pub struct LowLevelMouseMessage {
173 pub action: LowLevelMouseAction,
174 pub coords: POINT,
175 pub timestamp_ms: u32,
176}
177
178impl FromRawLowLevelMessage for LowLevelMouseMessage {
179 unsafe fn from_raw_message(value: RawLowLevelMessage) -> Self {
180 let w_param = u32::try_from(value.w_param).unwrap();
181 let message_data = unsafe {
182 &*ptr::with_exposed_provenance::<MSLLHOOKSTRUCT>(value.l_param.cast_unsigned())
183 };
184 let action = match (w_param, HIWORD(message_data.mouseData)) {
185 (WM_MOUSEMOVE, _) => LowLevelMouseAction::Move,
186 (WM_LBUTTONDOWN, _) => LowLevelMouseAction::ButtonDown(MouseButton::Left),
187 (WM_RBUTTONDOWN, _) => LowLevelMouseAction::ButtonDown(MouseButton::Right),
188 (WM_MBUTTONDOWN, _) => LowLevelMouseAction::ButtonDown(MouseButton::Middle),
189 (WM_XBUTTONDOWN, 1) => LowLevelMouseAction::ButtonDown(MouseButton::X1),
190 (WM_XBUTTONDOWN, 2) => LowLevelMouseAction::ButtonDown(MouseButton::X2),
191 (WM_LBUTTONUP, _) => LowLevelMouseAction::ButtonUp(MouseButton::Left),
192 (WM_RBUTTONUP, _) => LowLevelMouseAction::ButtonUp(MouseButton::Right),
193 (WM_MBUTTONUP, _) => LowLevelMouseAction::ButtonUp(MouseButton::Middle),
194 (WM_XBUTTONUP, 1) => LowLevelMouseAction::ButtonUp(MouseButton::X1),
195 (WM_XBUTTONUP, 2) => LowLevelMouseAction::ButtonUp(MouseButton::X2),
196 (WM_MOUSEWHEEL, raw_movement) => LowLevelMouseAction::WheelScroll(
197 MouseScrollEvent::from_raw_movement(raw_movement.cast_signed()),
198 ),
199 (_, _) => LowLevelMouseAction::Other(w_param),
200 };
201 LowLevelMouseMessage {
202 action,
203 coords: message_data.pt,
204 timestamp_ms: message_data.time,
205 }
206 }
207}
208
209#[derive(Copy, Clone, PartialEq, Debug)]
211pub struct LowLevelKeyboardMessage {
212 pub action: LowLevelKeyboardAction,
213 pub key: VirtualKey,
214 pub scan_code: u32,
215 pub timestamp_ms: u32,
216}
217
218impl FromRawLowLevelMessage for LowLevelKeyboardMessage {
219 unsafe fn from_raw_message(value: RawLowLevelMessage) -> Self {
220 let w_param = u32::try_from(value.w_param).unwrap();
221 let message_data = unsafe {
222 &*ptr::with_exposed_provenance::<KBDLLHOOKSTRUCT>(value.l_param.cast_unsigned())
223 };
224 let key = VirtualKey::from(u16::try_from(message_data.vkCode).expect("Key code too big"));
225 let action = LowLevelKeyboardAction::from(w_param);
226 LowLevelKeyboardMessage {
227 action,
228 key,
229 scan_code: message_data.scanCode,
230 timestamp_ms: message_data.time,
231 }
232 }
233}
234
235#[derive(Copy, Clone, Eq, PartialEq, Debug)]
236pub enum LowLevelMouseAction {
237 Move,
238 ButtonDown(MouseButton),
239 ButtonUp(MouseButton),
240 WheelScroll(MouseScrollEvent),
241 Other(u32),
242}
243
244#[derive(FromPrimitive, Copy, Clone, Eq, PartialEq, Debug)]
245#[repr(u32)]
246pub enum LowLevelKeyboardAction {
247 KeyDown = WM_KEYDOWN,
249 KeyUp = WM_KEYUP,
250 SysKeyDown = WM_SYSKEYDOWN,
251 SysKeyUp = WM_SYSKEYUP,
252 #[num_enum(catch_all)]
253 Other(u32),
254}
255
256#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
258pub enum HookReturnValue {
259 #[default]
262 CallNextHook,
263 BlockMessage,
265 PassToWindowProcOnly,
267 ExplicitValue(LRESULT),
268}
269
270mod private {
271 #[expect(clippy::wildcard_imports)]
272 use super::*;
273
274 #[derive(Clone, Copy, Debug)]
275 #[repr(transparent)]
276 struct StoredClosurePtr(*mut c_void);
277
278 unsafe impl Send for StoredClosurePtr {}
279
280 impl StoredClosurePtr {
281 fn from_closure<F, I, O>(value: *mut F) -> Self
282 where
283 F: FnMut(I) -> O,
284 {
285 StoredClosurePtr(value.cast::<c_void>())
286 }
287
288 unsafe fn to_closure<'a, F, I, O>(self) -> &'a mut F
294 where
295 F: FnMut(I) -> O,
296 {
297 unsafe { &mut *(self.0.cast::<F>()) }
298 }
299 }
300
301 pub type IdType = u32;
302
303 pub trait RawClosureStore {
304 unsafe fn get_raw_closure<'a, F, I, O>(id: IdType) -> Option<&'a mut F>
305 where
306 F: FnMut(I) -> O + Send;
307
308 fn set_raw_closure<F, I, O>(id: IdType, user_callback: Option<*mut F>)
309 where
310 F: FnMut(I) -> O + Send;
311 }
312
313 pub trait RawThreadClosureStore: RawClosureStore {
314 unsafe fn get_thread_raw_closure<'a, F, I, O>(id: IdType) -> Option<&'a mut F>
315 where
316 F: FnMut(I) -> O;
317
318 fn set_thread_raw_closure<F, I, O>(id: IdType, user_callback: Option<*mut F>)
319 where
320 F: FnMut(I) -> O;
321 }
322
323 pub enum ThreadLocalRawClosureStore {}
324
325 impl ThreadLocalRawClosureStore {
326 thread_local! {
329 static RAW_CLOSURE: RefCell<HashMap<IdType, StoredClosurePtr>> = RefCell::new(HashMap::new());
330 }
331
332 pub(crate) unsafe fn get_thread_raw_closure<'a, F, I, O>(id: IdType) -> Option<&'a mut F>
333 where
334 F: FnMut(I) -> O,
335 {
336 let unwrapped_closure: Option<StoredClosurePtr> =
337 Self::RAW_CLOSURE.with(|cell| cell.borrow_mut().get(&id).copied());
338 let closure: Option<&mut F> = unwrapped_closure.map(|ptr| unsafe { ptr.to_closure() });
339 closure
340 }
341
342 pub(crate) fn set_thread_raw_closure<F, I, O>(
343 id: IdType,
344 maybe_user_callback: Option<*mut F>,
345 ) where
346 F: FnMut(I) -> O,
347 {
348 Self::RAW_CLOSURE.with(|cell| {
349 let mut map_ref = cell.borrow_mut();
350 assert_ne!(maybe_user_callback.is_some(), map_ref.contains_key(&id));
351 if let Some(user_callback) = maybe_user_callback {
352 map_ref.insert(id, StoredClosurePtr::from_closure(user_callback));
353 } else {
354 map_ref.remove(&id);
355 }
356 });
357 }
358 }
359
360 impl RawClosureStore for ThreadLocalRawClosureStore {
361 unsafe fn get_raw_closure<'a, F, I, O>(id: IdType) -> Option<&'a mut F>
362 where
363 F: FnMut(I) -> O,
364 {
365 unsafe { Self::get_thread_raw_closure(id) }
366 }
367
368 fn set_raw_closure<F, I, O>(id: IdType, maybe_user_callback: Option<*mut F>)
369 where
370 F: FnMut(I) -> O,
371 {
372 Self::set_thread_raw_closure(id, maybe_user_callback);
373 }
374 }
375
376 impl RawThreadClosureStore for ThreadLocalRawClosureStore {
377 unsafe fn get_thread_raw_closure<'a, F, I, O>(id: IdType) -> Option<&'a mut F>
378 where
379 F: FnMut(I) -> O,
380 {
381 unsafe { Self::get_thread_raw_closure(id) }
382 }
383
384 fn set_thread_raw_closure<F, I, O>(id: IdType, maybe_user_callback: Option<*mut F>)
385 where
386 F: FnMut(I) -> O,
387 {
388 Self::set_thread_raw_closure(id, maybe_user_callback);
389 }
390 }
391
392 #[allow(dead_code)]
393 pub enum GlobalRawClosureStore {}
394
395 #[allow(dead_code)]
396 impl GlobalRawClosureStore {
397 fn closures() -> &'static Mutex<HashMap<IdType, StoredClosurePtr>> {
398 static CLOSURES: OnceLock<Mutex<HashMap<IdType, StoredClosurePtr>>> = OnceLock::new();
399 CLOSURES.get_or_init(|| Mutex::new(HashMap::new()))
400 }
401
402 unsafe fn get_raw_closure_with_id<'a, F, I, O>(id: IdType) -> Option<&'a mut F>
403 where
404 F: FnMut(I) -> O + Send,
405 {
406 let raw_hooks = Self::closures().lock().unwrap();
407 let maybe_stored_fn: Option<StoredClosurePtr> = raw_hooks.get(&id).copied();
408 let closure: Option<&mut F> = maybe_stored_fn.map(|ptr| unsafe { ptr.to_closure() });
409 closure
410 }
411
412 fn set_raw_closure_with_id<F, I, O>(id: IdType, user_callback: Option<*mut F>)
413 where
414 F: FnMut(I) -> O + Send,
415 {
416 let mut hooks = Self::closures().lock().unwrap();
417 assert_ne!(user_callback.is_some(), hooks.contains_key(&id));
418 match user_callback {
419 Some(user_callback) => {
420 let value = StoredClosurePtr::from_closure(user_callback);
421 hooks.insert(id, value);
422 }
423 None => {
424 hooks.remove(&id);
425 }
426 }
427 }
428 }
429
430 impl RawClosureStore for GlobalRawClosureStore {
431 unsafe fn get_raw_closure<'a, F, I, O>(id: IdType) -> Option<&'a mut F>
432 where
433 F: FnMut(I) -> O + Send,
434 {
435 unsafe { Self::get_raw_closure_with_id(id) }
436 }
437
438 fn set_raw_closure<F, I, O>(id: IdType, user_callback: Option<*mut F>)
439 where
440 F: FnMut(I) -> O + Send,
441 {
442 Self::set_raw_closure_with_id(id, user_callback);
443 }
444 }
445
446 #[derive(Copy, Clone, Debug)]
447 pub struct RawLowLevelMessage {
448 #[expect(dead_code)]
449 pub n_code: u32,
450 pub w_param: usize,
451 pub l_param: isize,
452 }
453
454 pub trait FromRawLowLevelMessage {
455 unsafe fn from_raw_message(value: RawLowLevelMessage) -> Self;
456 }
457
458 pub trait HookType: Sized {
459 const TYPE_ID: WINDOWS_HOOK_ID;
460 type Message: FromRawLowLevelMessage;
461 type ClosureStore: RawThreadClosureStore;
462
463 fn add_hook_internal<const ID: IdType, F>(
465 user_callback: F,
466 ) -> io::Result<HookHandle<Self::ClosureStore, F, HHOOK>>
467 where
468 F: FnMut(Self::Message) -> HookReturnValue,
469 {
470 unsafe extern "system" fn internal_callback<const ID: IdType, HT, F>(
471 n_code: i32,
472 w_param: WPARAM,
473 l_param: LPARAM,
474 ) -> LRESULT
475 where
476 HT: HookType,
477 F: FnMut(HT::Message) -> HookReturnValue,
478 {
479 if n_code < 0 {
480 unsafe { return CallNextHookEx(None, n_code, w_param, l_param) }
481 }
482 let call = move || {
483 let raw_message = RawLowLevelMessage {
484 n_code: n_code.cast_unsigned(),
485 w_param: w_param.0,
486 l_param: l_param.0,
487 };
488 let message = unsafe { HT::Message::from_raw_message(raw_message) };
489 let maybe_closure: Option<&mut F> =
490 unsafe { HT::ClosureStore::get_thread_raw_closure(ID) };
491 if let Some(closure) = maybe_closure {
492 closure(message)
493 } else {
494 panic!("Callback called without installed hook")
495 }
496 };
497 let result = catch_unwind_and_abort(call);
498 match result {
499 HookReturnValue::CallNextHook => unsafe {
500 CallNextHookEx(None, n_code, w_param, l_param)
501 },
502 HookReturnValue::BlockMessage => LRESULT(1),
503 HookReturnValue::PassToWindowProcOnly => LRESULT(0),
504 HookReturnValue::ExplicitValue(l_result) => l_result,
505 }
506 }
507 let mut user_callback = RawBox::new(user_callback);
508 Self::ClosureStore::set_thread_raw_closure(ID, Some(user_callback.as_mut_ptr()));
509 let handle = unsafe {
510 SetWindowsHookExW(
511 Self::TYPE_ID,
512 Some(internal_callback::<ID, Self, F>),
513 None,
514 0,
515 )?
516 };
517 Ok(HookHandle::new(ID, handle, user_callback))
518 }
519 }
520
521 pub trait RemovableHookHandle {
522 unsafe fn unhook(&mut self) -> io::Result<()>;
523 }
524
525 #[derive(Debug)]
526 pub struct HookHandle<RCS: RawClosureStore, B, H>
527 where
528 Self: RemovableHookHandle,
529 {
530 id: IdType,
531 handle_store: H,
532 hook_dependency: RawBox<B>,
533 remove_initiated: bool,
534 phantom: PhantomData<RCS>,
535 }
536
537 #[cfg(test)]
538 static_assertions::assert_not_impl_any!(HookHandle<ThreadLocalRawClosureStore, (), HHOOK>: Send, Sync);
539
540 impl<RCS: RawClosureStore, B, H> HookHandle<RCS, B, H>
541 where
542 Self: RemovableHookHandle,
543 {
544 pub(crate) fn new(id: IdType, handle_store: H, hook_dependency: RawBox<B>) -> Self {
545 Self {
546 id,
547 handle_store,
548 hook_dependency,
549 remove_initiated: false,
550 phantom: PhantomData,
551 }
552 }
553
554 fn remove(&mut self) -> io::Result<()> {
555 if !self.remove_initiated {
556 self.remove_initiated = true;
557 unsafe { self.unhook()? };
558 RCS::set_raw_closure::<fn(_) -> _, (), ()>(self.id, None);
559 }
560 Ok(())
561 }
562 }
563
564 impl<RCS: RawClosureStore, B, H> Drop for HookHandle<RCS, B, H>
565 where
566 Self: RemovableHookHandle,
567 {
568 fn drop(&mut self) {
569 self.remove().unwrap_or_default_and_print_error();
570 let _ = self.hook_dependency;
572 }
573 }
574
575 impl<RCS: RawClosureStore, B> RemovableHookHandle for HookHandle<RCS, B, HHOOK> {
576 unsafe fn unhook(&mut self) -> io::Result<()> {
577 unsafe { UnhookWindowsHookEx(self.handle_store)? };
578 Ok(())
579 }
580 }
581
582 impl<RCS: RawClosureStore, B> RemovableHookHandle for HookHandle<RCS, B, HWINEVENTHOOK> {
583 unsafe fn unhook(&mut self) -> io::Result<()> {
584 let _ = unsafe { UnhookWinEvent(self.handle_store) }
585 .if_null_to_error(|| io::ErrorKind::Other.into())?;
586 Ok(())
587 }
588 }
589
590 impl<RCS: RawClosureStore, B> RemovableHookHandle for HookHandle<RCS, B, Vec<HWINEVENTHOOK>> {
591 unsafe fn unhook(&mut self) -> io::Result<()> {
592 for handle in &self.handle_store {
593 let _ = unsafe { UnhookWinEvent(*handle) }
594 .if_null_to_error(|| io::ErrorKind::Other.into())?;
595 }
596 Ok(())
597 }
598 }
599
600 #[cfg(test)]
601 mod tests {
602 use super::*;
603
604 const EXPECTED_MESSAGE: LowLevelMouseMessage = LowLevelMouseMessage {
605 action: LowLevelMouseAction::Move,
606 coords: POINT { x: 0, y: 0 },
607 timestamp_ms: 42,
608 };
609 const EXPECTED_HOOK_RET_VAL: HookReturnValue = HookReturnValue::BlockMessage;
610
611 #[test]
612 fn curr_thread_set_and_retrieve_closure_thread_local() {
613 curr_thread_set_and_retrieve_closure::<ThreadLocalRawClosureStore>();
614 }
615
616 #[test]
617 fn curr_thread_set_and_retrieve_closure_global() {
618 curr_thread_set_and_retrieve_closure::<GlobalRawClosureStore>();
619 }
620
621 fn curr_thread_set_and_retrieve_closure<CS>()
622 where
623 CS: RawClosureStore,
624 {
625 let mut closure = generate_closure();
626 check_retrieved_closure::<CS, LowLevelMouseHook, _>(0, &mut closure, EXPECTED_MESSAGE);
627 }
628
629 #[test]
630 fn new_thread_set_and_retrieve_closure() {
631 let mut closure = generate_closure();
632 check_retrieved_closure_new_thread::<GlobalRawClosureStore, LowLevelMouseHook, _>(
633 1,
634 &mut closure,
635 EXPECTED_MESSAGE,
636 );
637 }
638
639 const fn generate_closure()
640 -> impl Fn(<LowLevelMouseHook as HookType>::Message) -> HookReturnValue {
641 |message| {
642 assert_eq!(message, EXPECTED_MESSAGE);
643 EXPECTED_HOOK_RET_VAL
644 }
645 }
646
647 fn check_retrieved_closure<CS, HT, F>(
648 id: IdType,
649 closure: &mut F,
650 expected_message: HT::Message,
651 ) where
652 CS: RawClosureStore,
653 HT: HookType,
654 F: FnMut(HT::Message) -> HookReturnValue + Send,
655 {
656 CS::set_raw_closure(id, Some(closure));
657 let retrieved_closure: &mut F = unsafe { CS::get_raw_closure(id) }.unwrap();
658 assert_eq!(retrieved_closure(expected_message), EXPECTED_HOOK_RET_VAL)
659 }
660
661 fn check_retrieved_closure_new_thread<CS, HT, F>(
662 id: IdType,
663 closure: &mut F,
664 expected_message: HT::Message,
665 ) where
666 CS: RawClosureStore,
667 HT: HookType,
668 F: FnMut(HT::Message) -> HookReturnValue + Send,
669 <HT as HookType>::Message: Send + 'static,
670 {
671 CS::set_raw_closure(id, Some(closure));
672 std::thread::spawn(move || {
673 let retrieved_closure: &mut F = unsafe { CS::get_raw_closure(id) }.unwrap();
674 assert_eq!(retrieved_closure(expected_message), EXPECTED_HOOK_RET_VAL)
675 })
676 .join()
677 .unwrap();
678 }
679 }
680}
681
682impl ReturnValue for HWINEVENTHOOK {
683 const NULL_VALUE: HWINEVENTHOOK = HWINEVENTHOOK(ptr::null_mut());
684}
685
686#[must_use]
690pub struct WinEventHook<F> {
691 #[expect(dead_code)]
692 handle_store: HookHandle<ThreadLocalRawClosureStore, F, Vec<HWINEVENTHOOK>>,
693}
694
695impl<F> WinEventHook<F>
696where
697 F: FnMut(WinEventMessage),
698{
699 pub fn new<const ID: IdType>(
707 user_callback: F,
708 filter_events: Option<&[WinEventKind]>,
709 ) -> io::Result<Self> {
710 let handle_store = Self::add_hook_internal::<ID>(user_callback, filter_events)?;
711 Ok(Self { handle_store })
712 }
713
714 pub fn run_hook_loop(
723 user_callback: F,
724 filter_events: Option<&[WinEventKind]>,
725 ) -> io::Result<()> {
726 let _handle = Self::new::<0>(user_callback, filter_events)?;
728 ThreadMessageLoop::new().run()?;
729 Ok(())
730 }
731
732 fn add_hook_internal<const ID: IdType>(
733 user_callback: F,
734 filter_events: Option<&[WinEventKind]>,
735 ) -> io::Result<HookHandle<ThreadLocalRawClosureStore, F, Vec<HWINEVENTHOOK>>> {
736 unsafe extern "system" fn internal_callback<const ID: IdType, F>(
737 _h_win_event_hook: HWINEVENTHOOK,
738 event_id: u32,
739 hwnd: HWND,
740 id_object: i32,
741 id_child: i32,
742 _id_event_thread: u32,
743 _dwms_event_time: u32,
744 ) where
745 F: FnMut(WinEventMessage),
746 {
747 let call = move || {
748 let message =
749 unsafe { WinEventMessage::from_raw_event(event_id, hwnd, id_object, id_child) };
750 let maybe_closure: Option<&mut F> =
751 unsafe { ThreadLocalRawClosureStore::get_thread_raw_closure(ID) };
752 if let Some(closure) = maybe_closure {
753 closure(message);
754 } else {
755 panic!("Callback called without installed hook")
756 }
757 };
758 catch_unwind_and_abort(call);
759 }
760 const DEFAULT_EVENT_RANGES: [(u32, u32); 2] = [
761 (EVENT_MIN, EVENT_SYSTEM_END),
762 (EVENT_OBJECT_CREATE, EVENT_OBJECT_END),
763 ];
764
765 let add_hook = move |(event_min, event_max)| {
766 unsafe {
767 SetWinEventHook(
768 event_min,
769 event_max,
770 None,
771 Some(internal_callback::<ID, F>),
772 0,
773 0,
774 WINEVENT_OUTOFCONTEXT,
775 )
776 }
777 .if_null_to_error(|| io::ErrorKind::Other.into())
778 };
779 let mut user_callback = RawBox::new(user_callback);
780 ThreadLocalRawClosureStore::set_thread_raw_closure(ID, Some(user_callback.as_mut_ptr()));
781 let user_filter_event_ranges: Option<Vec<(u32, u32)>> =
782 filter_events.and_then(|filter_events| {
783 let ranges = values_to_ranges(
784 filter_events
785 .iter()
786 .map(|x| u32::from(*x))
787 .collect::<Vec<_>>(),
788 );
789 (!ranges.is_empty()).then_some(ranges)
790 });
791 let filter_event_ranges = user_filter_event_ranges
792 .as_deref()
793 .unwrap_or(&DEFAULT_EVENT_RANGES);
794 let handle_store: Vec<_> = filter_event_ranges
795 .iter()
796 .copied()
797 .map(add_hook)
798 .collect::<io::Result<_>>()?;
799 Ok(HookHandle::new(ID, handle_store, user_callback))
800 }
801}
802
803#[derive(FromPrimitive, IntoPrimitive, Copy, Clone, PartialEq, Eq, Debug)]
804#[non_exhaustive]
805#[repr(u32)]
806pub enum WinEventKind {
807 ObjectCreated = EVENT_OBJECT_CREATE,
808 ObjectDestroyed = EVENT_OBJECT_DESTROY,
809 ObjectKeyboardFocussed = EVENT_OBJECT_FOCUS,
810 ObjectNameChanged = EVENT_OBJECT_NAMECHANGE,
811 ObjectUnhidden = EVENT_OBJECT_SHOW,
813 ObjectStateChanged = EVENT_OBJECT_STATECHANGE,
814 ObjectLocationChanged = EVENT_OBJECT_LOCATIONCHANGE,
815 ForegroundWindowChanged = EVENT_SYSTEM_FOREGROUND,
819 WindowMinimized = EVENT_SYSTEM_MINIMIZESTART,
820 WindowUnminimized = EVENT_SYSTEM_MINIMIZEEND,
822 WindowMoveStart = EVENT_SYSTEM_MOVESIZESTART,
823 WindowMoveEnd = EVENT_SYSTEM_MOVESIZEEND,
824 WindowMouseCaptureStart = EVENT_SYSTEM_CAPTURESTART,
825 WindowMouseCaptureEnd = EVENT_SYSTEM_CAPTUREEND,
826 WindowCloaked = EVENT_OBJECT_CLOAKED,
827 WindowUncloaked = EVENT_OBJECT_UNCLOAKED,
828 #[num_enum(catch_all)]
829 Other(u32),
830}
831
832#[derive(Debug)]
834pub struct WinEventMessage {
835 pub event_kind: WinEventKind,
836 pub window_handle: Option<WindowHandle>,
837 #[expect(dead_code)]
838 object_id: i32,
839 #[expect(dead_code)]
840 child_id: i32,
841}
842
843impl WinEventMessage {
844 unsafe fn from_raw_event(event_id: u32, hwnd: HWND, id_object: i32, id_child: i32) -> Self {
845 let window_handle = WindowHandle::from_maybe_null(hwnd);
846 Self {
847 event_kind: WinEventKind::from(event_id),
848 window_handle,
849 object_id: id_object,
850 child_id: id_child,
851 }
852 }
853}
854
855#[cfg(test)]
856mod tests {
857 use windows::Win32::System::Threading::GetCurrentThreadId;
858 use windows::Win32::UI::WindowsAndMessaging::{
859 PostThreadMessageW,
860 WM_QUIT,
861 };
862
863 use super::*;
864
865 #[test]
866 fn ll_hook_and_unhook() -> windows::core::Result<()> {
867 ll_hook_and_unhook_with_ids::<0, 1>()
868 }
869
870 #[test]
871 #[should_panic]
872 fn ll_hook_and_unhook_duplicate() {
873 let _ = ll_hook_and_unhook_with_ids::<0, 0>();
874 }
875
876 fn ll_hook_and_unhook_with_ids<const ID1: IdType, const ID2: IdType>()
877 -> windows::core::Result<()> {
878 let mouse_callback =
879 |_message: LowLevelMouseMessage| -> HookReturnValue { HookReturnValue::CallNextHook };
880 let mut keyboard_counter = 0;
881 let keyboard_callback = |_message: LowLevelKeyboardMessage| -> HookReturnValue {
882 keyboard_counter += 1;
883 HookReturnValue::CallNextHook
884 };
885 unsafe {
886 PostThreadMessageW(
887 GetCurrentThreadId(),
888 WM_QUIT,
889 WPARAM::default(),
890 LPARAM::default(),
891 )?
892 };
893 let _mouse_handle = LowLevelMouseHook::add_hook_internal::<ID1, _>(mouse_callback)?;
894 let _keyboard_handle =
895 LowLevelKeyboardHook::add_hook_internal::<ID2, _>(keyboard_callback)?;
896 ThreadMessageLoop::new().run()?;
897 Ok(())
898 }
899
900 #[cfg(feature = "process")]
901 #[test]
902 fn win_event_hook_and_unhook() -> windows::core::Result<()> {
903 use crate::process::ThreadId;
904 let mut counter = 0;
905 let callback = |_message: WinEventMessage| {
906 counter += 1;
907 };
908 ThreadId::current().post_quit_message()?;
909 let _hook_handle = WinEventHook::new::<0>(callback, None)?;
910 ThreadMessageLoop::new().run()?;
911 Ok(())
912 }
913}