win_hotkey/
single_thread.rs

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