Skip to main content

windows_hotkeys/
singlethreaded.rs

1#[cfg(not(target_os = "windows"))]
2compile_error!("Only supported on windows");
3
4use std::collections::HashMap;
5use std::marker::PhantomData;
6
7use winapi::shared::windef::HWND;
8use winapi::um::libloaderapi::GetModuleHandleA;
9use winapi::um::winuser::{
10    CreateWindowExA, DestroyWindow, GetMessageW, RegisterHotKey, UnregisterHotKey, HWND_MESSAGE,
11    MSG, WM_HOTKEY, WM_NULL, WS_DISABLED, WS_EX_NOACTIVATE,
12};
13
14use crate::{
15    error::HkError, get_global_keystate, keys::*, HotkeyCallback, HotkeyId, HotkeyManagerImpl,
16    InterruptHandle,
17};
18
19/// The HotkeyManager is used to register, unregister and await hotkeys with their callback
20/// functions.
21///
22/// # Note
23/// Due to limitations with the windows event system the HotkeyManager can't be moved to other
24/// threads.
25///
26pub struct HotkeyManager<T> {
27    /// Handle to the hidden window that is used to receive the hotkey events
28    hwnd: HwndDropper,
29    id_offset: i32,
30    handlers: HashMap<HotkeyId, HotkeyCallback<T>>,
31    /// Automatically set the `ModKey::NoRepeat` when registering hotkeys. Defaults to `true`
32    no_repeat: bool,
33
34    /// Make sure that `HotkeyManager` is not Send / Sync. This prevents it from being moved
35    /// between threads, which would prevent hotkey-events from being received.
36    ///
37    /// Being stuck on the same thread is an inherent limitation of the windows event system.
38    _unimpl_send_sync: PhantomData<*const u8>,
39}
40
41impl<T> Default for HotkeyManager<T> {
42    fn default() -> Self {
43        Self::new()
44    }
45}
46
47impl<T> HotkeyManager<T> {
48    /// Enable or disable the automatically applied `ModKey::NoRepeat` modifier. By default, this
49    /// option is set to `true` which causes all hotkey registration calls to add the `NoRepeat`
50    /// modifier, thereby disabling automatic retriggers of hotkeys when holding down the keys.
51    ///
52    /// When this option is disabled, the `ModKey::NoRepeat` can still be manually added while
53    /// registering hotkeys.
54    ///
55    /// Note: Setting this flag doesn't change previously registered hotkeys. It only applies to
56    /// registrations performed after calling this function.
57    pub fn set_no_repeat(&mut self, no_repeat: bool) {
58        self.no_repeat = no_repeat;
59    }
60}
61
62impl<T> HotkeyManagerImpl<T> for HotkeyManager<T> {
63    /// Create a new HotkeyManager instance. This instance can't be moved to other threads due to
64    /// limitations in the windows events system.
65    ///
66    fn new() -> HotkeyManager<T> {
67        // Try to create a hidden window to receive the hotkey events for the HotkeyManager.
68        // If the window creation fails, HWND 0 (null) is used which registers hotkeys to the thread
69        // message queue and gets messages from all thread associated windows
70        let hwnd = create_hidden_window().unwrap_or(HwndDropper(std::ptr::null_mut()));
71        HotkeyManager {
72            hwnd,
73            id_offset: 0,
74            handlers: HashMap::new(),
75            no_repeat: true,
76            _unimpl_send_sync: PhantomData,
77        }
78    }
79
80    fn register_extrakeys(
81        &mut self,
82        key: VKey,
83        key_modifiers: &[ModKey],
84        extra_keys: &[VKey],
85        callback: impl Fn() -> T + Send + 'static,
86    ) -> Result<HotkeyId, HkError> {
87        let register_id = HotkeyId(self.id_offset);
88        self.id_offset += 1;
89
90        let mut modifiers = ModKey::combine(key_modifiers);
91        if self.no_repeat {
92            modifiers |= ModKey::NoRepeat.to_mod_code();
93        }
94
95        // Try to register the hotkey combination with windows
96        let reg_ok = unsafe {
97            RegisterHotKey(
98                self.hwnd.0,
99                register_id.0,
100                modifiers,
101                key.to_vk_code() as u32,
102            )
103        };
104
105        if reg_ok == 0 {
106            Err(HkError::RegistrationFailed)
107        } else {
108            // Add the HotkeyCallback to the handlers when the hotkey was registered
109            self.handlers.insert(
110                register_id,
111                HotkeyCallback {
112                    callback: Box::new(callback),
113                    extra_keys: extra_keys.to_owned(),
114                },
115            );
116
117            Ok(register_id)
118        }
119    }
120
121    fn register(
122        &mut self,
123        key: VKey,
124        key_modifiers: &[ModKey],
125        callback: impl Fn() -> T + Send + 'static,
126    ) -> Result<HotkeyId, HkError> {
127        self.register_extrakeys(key, key_modifiers, &[], callback)
128    }
129
130    fn unregister(&mut self, id: HotkeyId) -> Result<(), HkError> {
131        let ok = unsafe { UnregisterHotKey(self.hwnd.0, id.0) };
132
133        match ok {
134            0 => Err(HkError::UnregistrationFailed),
135            _ => {
136                self.handlers.remove(&id);
137                Ok(())
138            }
139        }
140    }
141
142    fn unregister_all(&mut self) -> Result<(), HkError> {
143        let ids: Vec<_> = self.handlers.keys().copied().collect();
144        for id in ids {
145            self.unregister(id)?;
146        }
147
148        Ok(())
149    }
150
151    fn handle_hotkey(&self) -> Option<T> {
152        loop {
153            let mut msg = std::mem::MaybeUninit::<MSG>::uninit();
154
155            // Block and read a message from the message queue. Filtered to receive messages from
156            // WM_NULL to WM_HOTKEY
157            let ok = unsafe { GetMessageW(msg.as_mut_ptr(), self.hwnd.0, WM_NULL, WM_HOTKEY) };
158
159            if ok != 0 {
160                let msg = unsafe { msg.assume_init() };
161
162                if WM_HOTKEY == msg.message {
163                    let hk_id = HotkeyId(msg.wParam as i32);
164
165                    // Get the callback for the received ID
166                    if let Some(handler) = self.handlers.get(&hk_id) {
167                        // Check if all extra keys are pressed
168                        if !handler
169                            .extra_keys
170                            .iter()
171                            .any(|vk| !get_global_keystate(*vk))
172                        {
173                            return Some((handler.callback)());
174                        }
175                    }
176                } else if WM_NULL == msg.message {
177                    return None;
178                }
179            }
180        }
181    }
182
183    fn event_loop(&self) {
184        while self.handle_hotkey().is_some() {}
185    }
186
187    fn interrupt_handle(&self) -> InterruptHandle {
188        InterruptHandle(self.hwnd.0)
189    }
190}
191
192impl<T> Drop for HotkeyManager<T> {
193    fn drop(&mut self) {
194        let _ = self.unregister_all();
195    }
196}
197
198/// Wrapper around a HWND windows pointer that destroys the window on drop
199///
200struct HwndDropper(HWND);
201
202impl Drop for HwndDropper {
203    fn drop(&mut self) {
204        if !self.0.is_null() {
205            let _ = unsafe { DestroyWindow(self.0) };
206        }
207    }
208}
209
210/// Try to create a hidden "message-only" window
211///
212fn create_hidden_window() -> Result<HwndDropper, ()> {
213    let hwnd = unsafe {
214        // Get the current module handle
215        let hinstance = GetModuleHandleA(std::ptr::null_mut());
216        CreateWindowExA(
217            WS_EX_NOACTIVATE,
218            // The "Static" class is not intended for windows, but this shouldn't matter since the
219            // window is hidden anyways
220            b"Static\0".as_ptr() as *const i8,
221            b"\0".as_ptr() as *const i8,
222            WS_DISABLED,
223            0,
224            0,
225            0,
226            0,
227            HWND_MESSAGE,
228            std::ptr::null_mut(),
229            hinstance,
230            std::ptr::null_mut(),
231        )
232    };
233    if hwnd.is_null() {
234        Err(())
235    } else {
236        Ok(HwndDropper(hwnd))
237    }
238}