winapi_easy/
hooking.rs

1//! Various hooking functionality.
2
3use 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/// Deployed low level input hook.
98///
99/// The hook will be removed when this struct is dropped.
100#[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
116/// A global mouse or keyboard hook.
117///
118/// This hook can be used to listen to mouse (with [`LowLevelMouseHook`]) or keyboard (with [`LowLevelKeyboardHook`]) events,
119/// no matter which application or window they occur in.
120pub trait LowLevelInputHookType: HookType + Copy {
121    fn run_hook<F>(user_callback: F) -> io::Result<()>
122    where
123        F: FnMut(Self::Message) -> HookReturnValue,
124    {
125        // Always using ID 0 only works with ThreadLocalRawClosureStore
126        let _handle = Self::add_hook::<0, _>(user_callback)?;
127        ThreadMessageLoop::new().run()?;
128        Ok(())
129    }
130
131    /// Adds a new hook with the given ID.
132    ///
133    /// A [`ThreadMessageLoop`] must be run separately for `user_callback` to receive events.
134    ///
135    /// # Panics
136    ///
137    /// Will panic if a Hook with the given ID already exists for this thread.
138    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/// The mouse variant of [`LowLevelInputHook`].
147#[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/// The keyboard variant of [`LowLevelInputHook`].
159#[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/// Decoded mouse message.
171#[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/// Decoded keyboard message.
210#[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    /// A key press event, possibly auto-repeated by the keyboard.
248    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/// A value indicating what action should be taken after returning from the user callback.
257#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
258pub enum HookReturnValue {
259    /// Returns the result of calling [`CallNextHookEx`] with the original raw message,
260    /// allowing further processing by other hooks.
261    #[default]
262    CallNextHook,
263    /// Prevents the event from being passed on to the target window procedure or the rest of the hook chain.
264    BlockMessage,
265    /// Passes the event to the target window procedure but not the rest of the hook chain.
266    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        /// Transforms the pointer to an arbitrary closure.
289        ///
290        /// # Safety
291        ///
292        /// Unsafe both because any type is supported and because an arbitrary lifetime can be generated.
293        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        // This should be safe since for the low level mouse and keyboard hooks windows will only use
327        // the same thread as the one registering the hook to send messages to the internal callback.
328        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        /// Registers a hook and returns a handle for auto-drop.
464        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            // Manually drop for clarity
571            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/// A hook for various UI events.
687///
688/// The hook will be removed when this struct is dropped.
689#[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    /// Adds a new hook with the given ID.
700    ///
701    /// A [`ThreadMessageLoop`] must be run separately for `user_callback` to receive events.
702    ///
703    /// # Panics
704    ///
705    /// Will panic if a Hook with the given ID already exists for this thread.
706    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    /// Runs a new hook with ID `0` on a new thread message loop ([`ThreadMessageLoop`]).
715    ///
716    /// This will block the current thread to process messages.
717    ///
718    /// # Panics
719    ///
720    /// Will panic if a Hook with ID `0` already exists for this thread
721    /// or if the thread message loop lock is already acquired.
722    pub fn run_hook_loop(
723        user_callback: F,
724        filter_events: Option<&[WinEventKind]>,
725    ) -> io::Result<()> {
726        // Always using ID 0 only works with ThreadLocalRawClosureStore
727        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    /// A hidden object is shown.
812    ObjectUnhidden = EVENT_OBJECT_SHOW,
813    ObjectStateChanged = EVENT_OBJECT_STATECHANGE,
814    ObjectLocationChanged = EVENT_OBJECT_LOCATIONCHANGE,
815    /// The foreground window changed.
816    ///
817    /// **Note**: This event is not always sent when a window is unminimized ([`WinEventKind::WindowUnminimized`]).
818    ForegroundWindowChanged = EVENT_SYSTEM_FOREGROUND,
819    WindowMinimized = EVENT_SYSTEM_MINIMIZESTART,
820    /// A window has been unminimized.
821    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/// Decoded UI events.
833#[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}