windows_hotkeys/
singlethreaded.rs1#[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
19pub struct HotkeyManager<T> {
27 hwnd: HwndDropper,
29 id_offset: i32,
30 handlers: HashMap<HotkeyId, HotkeyCallback<T>>,
31 no_repeat: bool,
33
34 _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 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 fn new() -> HotkeyManager<T> {
67 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 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 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 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 if let Some(handler) = self.handlers.get(&hk_id) {
167 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
198struct 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
210fn create_hidden_window() -> Result<HwndDropper, ()> {
213 let hwnd = unsafe {
214 let hinstance = GetModuleHandleA(std::ptr::null_mut());
216 CreateWindowExA(
217 WS_EX_NOACTIVATE,
218 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}