win_hotkeys/
manager.rs

1//! Defines the `HotkeyManager`, which manages the registration,
2//! unregistration, and execution of hotkeys. It also handles the main event
3//! loop that listens for keyboard events and invokes associated callbacks.
4
5use crate::error::WHKError;
6use crate::error::WHKError::RegistrationFailed;
7use crate::hook;
8use crate::hook::{KeyAction, KeyboardEvent};
9use crate::hotkey::Hotkey;
10use crate::state::KeyboardState;
11use crate::VKey;
12use crossbeam_channel::Sender;
13use std::collections::HashMap;
14use std::sync::atomic::{AtomicBool, Ordering};
15use std::sync::Arc;
16
17/// Manages the lifecycle of hotkeys, including their registration, unregistration, and execution.
18///
19/// The `HotkeyManager` listens for keyboard events and triggers the corresponding
20/// hotkey callbacks when events match registered
21/// hotkeys.
22/// # Type Parameters
23/// - `T`: The return type of the hotkey callbacks.
24pub struct HotkeyManager<T> {
25    hotkeys: HashMap<u16, Vec<Hotkey<T>>>,
26    paused_ids: Vec<i32>,
27    paused: Arc<AtomicBool>,
28    interrupt: Arc<AtomicBool>,
29    callback_results_channel: Option<Sender<T>>,
30}
31
32impl<T> Default for HotkeyManager<T> {
33    fn default() -> Self {
34        Self::new()
35    }
36}
37
38impl<T> HotkeyManager<T> {
39    pub fn new() -> HotkeyManager<T> {
40        Self {
41            hotkeys: HashMap::new(),
42            paused_ids: Vec::new(),
43            paused: Arc::new(AtomicBool::new(false)),
44            interrupt: Arc::new(AtomicBool::new(false)),
45            callback_results_channel: None,
46        }
47    }
48
49    /// Registers a new hotkey.
50    pub fn register_hotkey(
51        &mut self,
52        trigger_key: VKey,
53        mod_keys: &[VKey],
54        callback: impl Fn() -> T + Send + 'static,
55    ) -> Result<i32, WHKError> {
56        let hotkey = Hotkey::new(trigger_key, mod_keys, callback);
57        let id = hotkey.generate_id();
58
59        // Check if already exists
60        if self
61            .hotkeys
62            .values()
63            .any(|vec| vec.iter().any(|hotkey| hotkey.generate_id() == id))
64        {
65            return Err(RegistrationFailed);
66        }
67
68        // Add hotkey and return id
69        self.hotkeys
70            .entry(trigger_key.to_vk_code())
71            .or_default()
72            .push(hotkey);
73        Ok(id)
74    }
75
76    /// Unregisters a hotkey by its unique id.
77    pub fn unregister_hotkey(&mut self, hotkey_id: i32) {
78        for vec in self.hotkeys.values_mut() {
79            vec.retain(|hotkey| hotkey.generate_id() != hotkey_id);
80        }
81        self.paused_ids.retain(|id| *id != hotkey_id);
82    }
83
84    /// Unregisters all hotkeys.
85    pub fn unregister_all(&mut self) {
86        self.hotkeys.clear();
87        self.paused_ids.clear();
88    }
89
90    /// Registers a hotkey that will toggle the paused state of the
91    /// `HotkeyManager`. When paused, only registered pause hotkeys
92    /// will be allowed to trigger.
93    pub fn register_pause_hotkey(
94        &mut self,
95        trigger_key: VKey,
96        mod_keys: &[VKey],
97        callback: impl Fn() -> T + Send + 'static,
98    ) -> Result<i32, WHKError> {
99        let paused = Arc::clone(&self.paused);
100        let wrapped_callback = move || {
101            let was_paused = paused.load(Ordering::Relaxed);
102            paused.store(!was_paused, Ordering::Relaxed);
103            callback()
104        };
105        let id = self.register_hotkey(trigger_key, mod_keys, wrapped_callback)?;
106        self.paused_ids.push(id);
107        Ok(id)
108    }
109
110    /// Registers a channel for callback results to be sent into.
111    pub fn register_channel(&mut self, channel: Sender<T>) {
112        self.callback_results_channel = Some(channel);
113    }
114
115    /// Runs the main event loop to listen for keyboard events.
116    ///
117    /// This method blocks and processes keyboard events until interrupted.
118    /// It matches events against registered hotkeys and executes the corresponding callbacks.
119    pub fn event_loop(&mut self) {
120        let hook = hook::start();
121        while !self.interrupt.load(Ordering::Relaxed) {
122            if let Ok(event) = hook.recv() {
123                let (key_code, state) = match event {
124                    KeyboardEvent::KeyDown {
125                        vk_code: key_code,
126                        keyboard_state: state,
127                    } => (key_code, state),
128                    _ => continue,
129                };
130
131                let mut found = false;
132                if let Some(hotkeys) = self.hotkeys.get_mut(&key_code) {
133                    for hotkey in hotkeys {
134                        if self.paused.load(Ordering::Relaxed)
135                            && !self.paused_ids.contains(&hotkey.generate_id())
136                        {
137                            continue;
138                        }
139                        if hotkey.is_trigger_state(state) {
140                            if state.is_down(VKey::LWin.to_vk_code()) {
141                                hook.key_action(KeyAction::Replace);
142                            } else {
143                                hook.key_action(KeyAction::Block);
144                            }
145                            let result = hotkey.callback();
146                            if let Some(callback_result_channel) = &self.callback_results_channel {
147                                callback_result_channel.send(result).unwrap();
148                            }
149                            found = true;
150                            break;
151                        }
152                    }
153                }
154                if !found {
155                    hook.key_action(KeyAction::Allow);
156                }
157            }
158        }
159        hook.exit();
160    }
161
162    /// Signals the `HotkeyManager` to interrupt its event loop.
163    pub fn interrupt_handle(&self) -> InterruptHandle {
164        InterruptHandle {
165            interrupt: Arc::clone(&self.interrupt),
166        }
167    }
168
169    /// Signals the `HotkeyManager` to pause processing of hotkeys.
170    pub fn pause_handle(&self) -> PauseHandle {
171        PauseHandle {
172            pause: Arc::clone(&self.paused),
173        }
174    }
175}
176
177/// A handle for signaling the `HotkeyManager` to stop its event loop.
178///
179/// The `InterruptHandle` is used to gracefully interrupt the event loop by sending
180/// a control signal. This allows the `HotkeyManager` to clean up resources and stop
181/// processing keyboard events.
182#[derive(Debug, Clone)]
183pub struct InterruptHandle {
184    interrupt: Arc<AtomicBool>,
185}
186
187impl InterruptHandle {
188    /// Interrupts the `HotkeyManager`'s event loop.
189    ///
190    /// This method sets an internal flag to indicate that the interruption has been requested.
191    /// then sends a dummy keyboard event to the event loop to force it to check the flag.
192    pub fn interrupt(&self) {
193        use crate::hook::{KeyboardEvent, HOOK_EVENT_TX};
194        let dummy_event = KeyboardEvent::KeyDown {
195            vk_code: 0,
196            keyboard_state: KeyboardState::new(),
197        };
198        self.interrupt.store(true, Ordering::Relaxed);
199        let event_tx = HOOK_EVENT_TX.read().unwrap();
200        if let Some(ke_tx) = &*event_tx {
201            ke_tx.send(dummy_event).unwrap();
202        }
203    }
204}
205
206/// A handle for signaling the `HotkeyManager` to stop processing hotkeys without
207/// exiting the event loop or unregistering hotkeys. When paused, the `HotkeyManager`
208/// will only process registered pause hotkeys.
209///
210/// The `PauseHandle` is used to manage the pause state of the `HotkeyManager`.
211#[derive(Debug, Clone)]
212pub struct PauseHandle {
213    pause: Arc<AtomicBool>,
214}
215
216impl PauseHandle {
217    /// Toggles the pause state of the `HotkeyManager`.
218    ///
219    /// If the `HotkeyManager` is currently paused, calling this method will resume
220    /// normal hotkey processing. If it is active, calling this method will pause it.
221    pub fn toggle(&self) {
222        self.pause
223            .store(!self.pause.load(Ordering::Relaxed), Ordering::Relaxed);
224    }
225
226    /// Explicitly sets the pause state.
227    pub fn set(&self, state: bool) {
228        self.pause.store(state, Ordering::Relaxed);
229    }
230
231    /// Returns whether the `HotkeyManager` is currently paused.
232    ///
233    /// When paused, only pause hotkeys will be processed while all others will
234    /// be ignored.
235    pub fn is_paused(&self) -> bool {
236        self.pause.load(Ordering::Relaxed)
237    }
238}