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}