win_hotkey/lib.rs
1#![allow(clippy::doc_lazy_continuation)]
2#[cfg(windows)]
3pub mod error;
4#[cfg(all(windows, feature = "thread_safe"))]
5pub mod global;
6#[cfg(windows)]
7pub mod keys;
8#[cfg(windows)]
9pub mod single_thread;
10#[cfg(all(windows, feature = "thread_safe"))]
11pub mod thread_safe;
12
13use core::fmt;
14
15#[cfg(all(windows, feature = "thread_safe"))]
16pub use thread_safe::HotkeyManager;
17
18#[cfg(all(windows, not(feature = "thread_safe")))]
19pub use single_thread::HotkeyManager;
20
21#[cfg(windows)]
22use windows_sys::Win32::Foundation::HWND;
23#[cfg(windows)]
24use windows_sys::Win32::UI::WindowsAndMessaging::{PostMessageW, WM_NULL};
25
26#[cfg(windows)]
27use crate::error::HotkeyError;
28#[cfg(windows)]
29use crate::keys::*;
30
31#[cfg(windows)]
32#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
33pub struct HotkeyId(u16);
34
35/// HotkeyCallback contains the callback function and a list of extra_keys that need to be pressed
36/// together with the hotkey when executing the callback.
37///
38#[cfg(windows)]
39struct HotkeyCallback<T> {
40 /// Callback function to execute when the hotkey & extrakeys match
41 callback: Option<Box<dyn Fn() -> T + 'static>>,
42 /// List of additional VKeys that are required to be pressed to execute
43 /// the callback
44 extra_keys: Option<Vec<VirtualKey>>,
45}
46
47#[cfg(windows)]
48impl<T> fmt::Debug for HotkeyCallback<T>
49where
50 T: fmt::Debug, // Ensure that T can be printed if necessary
51{
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 f.debug_struct("HotkeyCallback")
54 .field(
55 "callback",
56 &self.callback.as_ref().map_or_else(
57 || "None".to_string(),
58 |_| "Some(Fn() -> T + 'static)".to_string(),
59 ),
60 )
61 .field("extra_keys", &self.extra_keys)
62 .finish()
63 }
64}
65
66#[cfg(windows)]
67pub trait HotkeyManagerImpl<T> {
68 fn new() -> Self;
69
70 /// Register a new hotkey with additional required extra keys.
71 ///
72 /// This will try to register the specified hotkey with windows, but not actively listen for it.
73 /// To listen for hotkeys in order to actually execute the callbacks, the `event_loop` function
74 /// must be called.
75 ///
76 /// # Arguments
77 ///
78 /// * `key` - The main hotkey. For example `VKey::Return` for the CTRL + ALT + ENTER
79 /// combination.
80 ///
81 /// * `key_modifiers` - The modifier keys that need to be combined with the main key. The
82 /// modifier keys are the keys that need to be pressed in addition to the main hotkey in order
83 /// for the hotkey event to fire. For example `&[ModKey::Ctrl, ModKey::Alt]` for the
84 /// CTRL + ALT + ENTER combination.
85 ///
86 /// * `extra_keys` - A list of additional VKeys that also need to be pressed for the hotkey
87 /// callback to be executed. This is enforced after the windows hotkey event is fired, but
88 /// before executing the callback. So these keys need to be pressed before the main hotkey.
89 ///
90 /// * `callback` - A callback function or closure that will be executed when the hotkey is
91 /// triggered. The return type for all callbacks in the same HotkeyManager must be the same.
92 ///
93 /// # Windows API Functions used
94 /// - <https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-registerhotkey>
95 ///
96 fn register_extrakeys(
97 &mut self,
98 virtual_key: VirtualKey,
99 modifiers_key: Option<&[ModifiersKey]>,
100 extra_keys: Option<&[VirtualKey]>,
101 callback: Option<impl Fn() -> T + Send + 'static>,
102 ) -> Result<HotkeyId, HotkeyError>;
103
104 /// Same as `register_extrakeys` but without extra keys.
105 ///
106 /// # Windows API Functions used
107 /// - <https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-registerhotkey>
108 ///
109 fn register(
110 &mut self,
111 virtual_key: VirtualKey,
112 modifiers_key: Option<&[ModifiersKey]>,
113 callback: Option<impl Fn() -> T + Send + 'static>,
114 ) -> Result<HotkeyId, HotkeyError>;
115
116 /// Unregister a hotkey. This will prevent the hotkey from being triggered in the future.
117 ///
118 /// # Windows API Functions used
119 /// - <https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-unregisterhotkey>
120 ///
121 fn unregister(&mut self, id: HotkeyId) -> Result<(), HotkeyError>;
122
123 /// Unregister all registered hotkeys. This will be called automatically when dropping the
124 /// HotkeyManager instance.
125 ///
126 /// # Windows API Functions used
127 /// - <https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-unregisterhotkey>
128 ///
129 fn unregister_all(&mut self) -> Result<(), HotkeyError>;
130
131 /// Wait for a single a hotkey event and execute the callback if all keys match. This returns
132 /// the callback result if it was not interrupted. The function call will block until a hotkey
133 /// is triggered or it is interrupted.
134 ///
135 /// If the event is interrupted, `None` is returned, otherwise `Some` is returned with the
136 /// return value of the executed callback function.
137 ///
138 /// ## Windows API Functions used
139 /// - <https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmessagew>
140 ///
141 fn handle_hotkey(&self) -> Option<T>;
142
143 /// Run the event loop, listening for hotkeys. This will run indefinitely until interrupted and
144 /// execute any hotkeys registered before.
145 ///
146 fn event_loop(&self);
147
148 /// Get an `InterruptHandle` for this `HotkeyManager` that can be used to interrupt the event
149 /// loop.
150 ///
151 fn interrupt_handle(&self) -> InterruptHandle;
152}
153
154// The `InterruptHandle` can be used to interrupt the event loop of the originating `HotkeyManager`.
155/// This handle can be used from any thread and can be used multiple times.
156///
157/// # Note
158/// This handle will technically stay valid even after the `HotkeyManager` is dropped, but it will
159/// simply not do anything.
160///
161#[cfg(windows)]
162pub struct InterruptHandle(HWND);
163
164#[cfg(windows)]
165unsafe impl Sync for InterruptHandle {}
166
167#[cfg(windows)]
168unsafe impl Send for InterruptHandle {}
169
170#[cfg(windows)]
171impl InterruptHandle {
172 /// Interrupt the evet loop of the associated `HotkeyManager`.
173 ///
174 pub fn interrupt(&self) {
175 unsafe {
176 PostMessageW(self.0, WM_NULL, 0, 0);
177 }
178 }
179}
180
181/// Get the global keystate for a given Virtual Key.
182///
183/// Return true if the key is pressed, false otherwise.
184///
185/// ## Windows API Functions used
186/// - <https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getasynckeystate>
187///
188#[cfg(windows)]
189pub fn get_global_keystate(vk: VirtualKey) -> bool {
190 // Most significant bit represents key state (1 => pressed, 0 => not pressed)
191 use windows_sys::Win32::UI::Input::KeyboardAndMouse::GetAsyncKeyState;
192 let key_state = unsafe { GetAsyncKeyState(vk.to_vk_code() as i32) };
193 // Get most significant bit only
194 let key_state = key_state as u32 >> 31;
195
196 key_state == 1
197}