winapi_easy/
hooking.rs

1//! Various hooking functionality.
2
3use 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
58/// A global mouse or keyboard hook.
59///
60/// This hook can be used to listen to mouse (with [`LowLevelMouseHook`]) or keyboard (with [`LowLevelKeyboardHook`]) events,
61/// no matter which application or window they occur in.
62pub 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        // Always using ID 0 only works with ThreadLocalRawClosureStore
68        let _handle = Self::add_hook::<0, _>(user_callback)?;
69        ThreadMessageLoop::run_thread_message_loop(|| Ok(()))?;
70        Ok(())
71    }
72}
73
74/// The mouse variant of [`LowLevelInputHook`].
75#[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/// The keyboard variant of [`LowLevelInputHook`].
87#[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/// Decoded mouse message.
99#[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/// Decoded keyboard message.
136#[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    /// A key press event, possibly auto-repeated by the keyboard.
172    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/// A value indicating what action should be taken after returning from the user callback
181/// in [`LowLevelInputHook::run_hook`].
182#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
183pub enum HookReturnValue {
184    /// Returns the result of calling [`CallNextHookEx`] with the original raw message,
185    /// allowing further processing by other hooks.
186    #[default]
187    CallNextHook,
188    /// Prevents the event from being passed on to the target window procedure or the rest of the hook chain.
189    BlockMessage,
190    /// Passes the event to the target window procedure but not the rest of the hook chain.
191    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        // This should be safe since for the low level mouse and keyboard hooks windows will only use
216        // the same thread as the one registering the hook to send messages to the internal callback.
217        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}