1use std::collections::{HashMap, HashSet};
4use std::sync::mpsc::{self, Receiver, Sender, TryRecvError};
5use std::sync::{Arc, Mutex};
6use std::thread::{self, JoinHandle};
7
8use crate::error::{Error, Result};
9use crate::listener::{BlockingHotkeys, KeyboardListener};
10use crate::types::{Hotkey, HotkeyEvent, HotkeyId, HotkeyState, KeyEvent};
11
12struct ManagerState {
14 hotkeys: HashMap<HotkeyId, Hotkey>,
15 next_id: u32,
16 pressed_hotkeys: HashSet<HotkeyId>,
18}
19
20impl ManagerState {
21 fn new() -> Self {
22 Self {
23 hotkeys: HashMap::new(),
24 next_id: 0,
25 pressed_hotkeys: HashSet::new(),
26 }
27 }
28
29 fn process_event(&mut self, event: &KeyEvent) -> Vec<HotkeyEvent> {
31 let mut results = Vec::new();
32
33 if event.is_key_down {
34 let to_press: Vec<HotkeyId> = self
36 .hotkeys
37 .iter()
38 .filter(|(&id, hotkey)| {
39 hotkey.modifiers.matches(event.modifiers)
40 && hotkey.key == event.key
41 && !self.pressed_hotkeys.contains(&id)
42 })
43 .map(|(&id, _)| id)
44 .collect();
45
46 for id in to_press {
47 self.pressed_hotkeys.insert(id);
48 results.push(HotkeyEvent {
49 id,
50 state: HotkeyState::Pressed,
51 });
52 }
53 } else {
54 let to_release: Vec<HotkeyId> = self
57 .hotkeys
58 .iter()
59 .filter(|(&id, hotkey)| {
60 self.pressed_hotkeys.contains(&id)
61 && (hotkey.key == event.key
62 || (event.key.is_none() && !hotkey.modifiers.matches(event.modifiers)))
63 })
64 .map(|(&id, _)| id)
65 .collect();
66
67 for id in to_release {
68 self.pressed_hotkeys.remove(&id);
69 results.push(HotkeyEvent {
70 id,
71 state: HotkeyState::Released,
72 });
73 }
74 }
75
76 results
77 }
78}
79
80pub struct HotkeyManager {
88 state: Arc<Mutex<ManagerState>>,
89 event_receiver: Receiver<HotkeyEvent>,
90 _thread_handle: Option<JoinHandle<()>>,
91 running: Arc<std::sync::atomic::AtomicBool>,
92 blocking_hotkeys: Option<BlockingHotkeys>,
94}
95
96impl HotkeyManager {
97 pub fn new() -> Result<Self> {
101 let listener = KeyboardListener::new()?;
102
103 let (tx, rx) = mpsc::channel();
104 let state = Arc::new(Mutex::new(ManagerState::new()));
105 let running = Arc::new(std::sync::atomic::AtomicBool::new(true));
106
107 let thread_state = Arc::clone(&state);
108 let thread_running = Arc::clone(&running);
109
110 let handle = thread::spawn(move || {
111 Self::event_loop(listener, thread_state, tx, thread_running);
112 });
113
114 Ok(Self {
115 state,
116 event_receiver: rx,
117 _thread_handle: Some(handle),
118 running,
119 blocking_hotkeys: None,
120 })
121 }
122
123 pub fn new_with_blocking() -> Result<Self> {
130 let blocking_hotkeys: BlockingHotkeys = Arc::new(Mutex::new(HashSet::new()));
131 let listener = KeyboardListener::new_with_blocking(blocking_hotkeys.clone())?;
132
133 let (tx, rx) = mpsc::channel();
134 let state = Arc::new(Mutex::new(ManagerState::new()));
135 let running = Arc::new(std::sync::atomic::AtomicBool::new(true));
136
137 let thread_state = Arc::clone(&state);
138 let thread_running = Arc::clone(&running);
139
140 let handle = thread::spawn(move || {
141 Self::event_loop(listener, thread_state, tx, thread_running);
142 });
143
144 Ok(Self {
145 state,
146 event_receiver: rx,
147 _thread_handle: Some(handle),
148 running,
149 blocking_hotkeys: Some(blocking_hotkeys),
150 })
151 }
152
153 fn event_loop(
155 listener: KeyboardListener,
156 state: Arc<Mutex<ManagerState>>,
157 sender: Sender<HotkeyEvent>,
158 running: Arc<std::sync::atomic::AtomicBool>,
159 ) {
160 const RECV_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(100);
161
162 while running.load(std::sync::atomic::Ordering::SeqCst) {
163 match listener.recv_timeout(RECV_TIMEOUT) {
165 Ok(key_event) => {
166 if let Ok(mut state) = state.lock() {
167 let hotkey_events = state.process_event(&key_event);
168 for event in hotkey_events {
169 if sender.send(event).is_err() {
170 return;
172 }
173 }
174 }
175 }
176 Err(crate::error::Error::Timeout) => {
177 }
179 Err(_) => {
180 return;
182 }
183 }
184 }
185 }
186
187 pub fn register(&self, hotkey: Hotkey) -> Result<HotkeyId> {
191 let mut state = self.state.lock().map_err(|_| Error::MutexPoisoned)?;
192
193 for (id, existing) in &state.hotkeys {
195 if existing == &hotkey {
196 return Err(Error::HotkeyAlreadyRegistered(format!(
197 "{} (id: {:?})",
198 hotkey, id
199 )));
200 }
201 }
202
203 let id = HotkeyId(state.next_id);
204 state.next_id += 1;
205 state.hotkeys.insert(id, hotkey);
206
207 if let Some(blocking_hotkeys) = &self.blocking_hotkeys {
209 if let Ok(mut blocking) = blocking_hotkeys.lock() {
210 blocking.insert(hotkey);
211 }
212 }
213
214 Ok(id)
215 }
216
217 pub fn unregister(&self, id: HotkeyId) -> Result<()> {
221 let mut state = self.state.lock().map_err(|_| Error::MutexPoisoned)?;
222
223 let hotkey = state.hotkeys.remove(&id);
224 if hotkey.is_none() {
225 return Err(Error::HotkeyNotFound(id));
226 }
227
228 if let Some(blocking_hotkeys) = &self.blocking_hotkeys {
230 if let Some(hotkey) = hotkey {
231 if let Ok(mut blocking) = blocking_hotkeys.lock() {
232 blocking.remove(&hotkey);
233 }
234 }
235 }
236
237 Ok(())
238 }
239
240 pub fn get_hotkey(&self, id: HotkeyId) -> Option<Hotkey> {
244 let state = self.state.lock().ok()?;
245 state.hotkeys.get(&id).copied()
246 }
247
248 pub fn recv(&self) -> Result<HotkeyEvent> {
252 self.event_receiver
253 .recv()
254 .map_err(|_| Error::EventLoopNotRunning)
255 }
256
257 pub fn try_recv(&self) -> Option<HotkeyEvent> {
261 match self.event_receiver.try_recv() {
262 Ok(event) => Some(event),
263 Err(TryRecvError::Empty) => None,
264 Err(TryRecvError::Disconnected) => None,
265 }
266 }
267
268 pub fn hotkey_count(&self) -> usize {
270 let state = if let Ok(s) = self.state.lock() {
271 s
272 } else {
273 return 0;
274 };
275 state.hotkeys.len()
276 }
277}
278
279impl Drop for HotkeyManager {
280 fn drop(&mut self) {
281 self.running
282 .store(false, std::sync::atomic::Ordering::SeqCst);
283 if let Some(handle) = self._thread_handle.take() {
285 let _ = handle.join();
286 }
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293 use crate::types::{Key, Modifiers};
294
295 fn make_key_event(modifiers: Modifiers, key: Option<Key>, is_key_down: bool) -> KeyEvent {
296 KeyEvent {
297 modifiers,
298 key,
299 is_key_down,
300 changed_modifier: None,
301 }
302 }
303
304 fn make_modifier_event(
305 modifiers: Modifiers,
306 is_key_down: bool,
307 changed: Modifiers,
308 ) -> KeyEvent {
309 KeyEvent {
310 modifiers,
311 key: None,
312 is_key_down,
313 changed_modifier: Some(changed),
314 }
315 }
316
317 mod manager_state {
318 use super::*;
319
320 #[test]
321 fn register_and_lookup_hotkey() {
322 let mut state = ManagerState::new();
323 let hotkey = Hotkey::new(Modifiers::CMD, Key::K).unwrap();
324
325 let id = HotkeyId(state.next_id);
326 state.next_id += 1;
327 state.hotkeys.insert(id, hotkey);
328
329 assert_eq!(state.hotkeys.get(&id), Some(&hotkey));
330 assert_eq!(state.hotkeys.len(), 1);
331 }
332
333 #[test]
334 fn hotkey_press_generates_event() {
335 let mut state = ManagerState::new();
336 let hotkey = Hotkey::new(Modifiers::CMD, Key::K).unwrap();
337 let id = HotkeyId(0);
338 state.hotkeys.insert(id, hotkey);
339
340 let event = make_key_event(Modifiers::CMD_LEFT, Some(Key::K), true);
342 let results = state.process_event(&event);
343
344 assert_eq!(results.len(), 1);
345 assert_eq!(results[0].id, id);
346 assert_eq!(results[0].state, HotkeyState::Pressed);
347 assert!(state.pressed_hotkeys.contains(&id));
348 }
349
350 #[test]
351 fn hotkey_release_generates_event() {
352 let mut state = ManagerState::new();
353 let hotkey = Hotkey::new(Modifiers::CMD, Key::K).unwrap();
354 let id = HotkeyId(0);
355 state.hotkeys.insert(id, hotkey);
356
357 let event = make_key_event(Modifiers::CMD_LEFT, Some(Key::K), true);
359 state.process_event(&event);
360
361 let event = make_key_event(Modifiers::CMD_LEFT, Some(Key::K), false);
363 let results = state.process_event(&event);
364
365 assert_eq!(results.len(), 1);
366 assert_eq!(results[0].id, id);
367 assert_eq!(results[0].state, HotkeyState::Released);
368 assert!(!state.pressed_hotkeys.contains(&id));
369 }
370
371 #[test]
372 fn no_duplicate_press_events() {
373 let mut state = ManagerState::new();
374 let hotkey = Hotkey::new(Modifiers::CMD, Key::K).unwrap();
375 let id = HotkeyId(0);
376 state.hotkeys.insert(id, hotkey);
377
378 let event = make_key_event(Modifiers::CMD_LEFT, Some(Key::K), true);
380 let results = state.process_event(&event);
381 assert_eq!(results.len(), 1);
382
383 let results = state.process_event(&event);
385 assert_eq!(results.len(), 0);
386 }
387
388 #[test]
389 fn modifier_release_triggers_hotkey_release() {
390 let mut state = ManagerState::new();
391 let hotkey = Hotkey::new(Modifiers::CMD, Key::K).unwrap();
392 let id = HotkeyId(0);
393 state.hotkeys.insert(id, hotkey);
394
395 let event = make_key_event(Modifiers::CMD_LEFT, Some(Key::K), true);
397 state.process_event(&event);
398 assert!(state.pressed_hotkeys.contains(&id));
399
400 let event = make_modifier_event(Modifiers::empty(), false, Modifiers::CMD_LEFT);
402 let results = state.process_event(&event);
403
404 assert_eq!(results.len(), 1);
405 assert_eq!(results[0].state, HotkeyState::Released);
406 assert!(!state.pressed_hotkeys.contains(&id));
407 }
408
409 #[test]
410 fn wrong_modifiers_dont_trigger() {
411 let mut state = ManagerState::new();
412 let hotkey = Hotkey::new(Modifiers::CMD, Key::K).unwrap();
413 state.hotkeys.insert(HotkeyId(0), hotkey);
414
415 let event = make_key_event(Modifiers::SHIFT_LEFT, Some(Key::K), true);
417 let results = state.process_event(&event);
418
419 assert_eq!(results.len(), 0);
420 }
421
422 #[test]
423 fn modifier_only_hotkey() {
424 let mut state = ManagerState::new();
425 let hotkey = Hotkey::new(Modifiers::CMD | Modifiers::SHIFT, None).unwrap();
426 let id = HotkeyId(0);
427 state.hotkeys.insert(id, hotkey);
428
429 let event = make_modifier_event(
431 Modifiers::CMD_LEFT | Modifiers::SHIFT_LEFT,
432 true,
433 Modifiers::SHIFT_LEFT,
434 );
435 let results = state.process_event(&event);
436
437 assert_eq!(results.len(), 1);
438 assert_eq!(results[0].state, HotkeyState::Pressed);
439 }
440
441 #[test]
442 fn multiple_hotkeys_same_key() {
443 let mut state = ManagerState::new();
444
445 let hotkey1 = Hotkey::new(Modifiers::CMD, Key::K).unwrap();
447 let hotkey2 = Hotkey::new(Modifiers::CTRL, Key::K).unwrap();
448 let id1 = HotkeyId(0);
449 let id2 = HotkeyId(1);
450 state.hotkeys.insert(id1, hotkey1);
451 state.hotkeys.insert(id2, hotkey2);
452
453 let event = make_key_event(Modifiers::CMD_LEFT, Some(Key::K), true);
455 let results = state.process_event(&event);
456
457 assert_eq!(results.len(), 1);
458 assert_eq!(results[0].id, id1);
459
460 state.pressed_hotkeys.clear();
462 let event = make_key_event(Modifiers::CTRL_LEFT, Some(Key::K), true);
463 let results = state.process_event(&event);
464
465 assert_eq!(results.len(), 1);
466 assert_eq!(results[0].id, id2);
467 }
468
469 #[test]
470 fn key_only_hotkey() {
471 let mut state = ManagerState::new();
472 let hotkey = Hotkey::new(Modifiers::empty(), Key::F1).unwrap();
473 let id = HotkeyId(0);
474 state.hotkeys.insert(id, hotkey);
475
476 let event = make_key_event(Modifiers::empty(), Some(Key::F1), true);
478 let results = state.process_event(&event);
479
480 assert_eq!(results.len(), 1);
481 assert_eq!(results[0].state, HotkeyState::Pressed);
482
483 state.pressed_hotkeys.clear();
485 let event = make_key_event(Modifiers::CMD_LEFT, Some(Key::F1), true);
486 let results = state.process_event(&event);
487
488 assert_eq!(results.len(), 0);
489 }
490
491 #[test]
492 fn side_specific_hotkey_matches_correct_side() {
493 let mut state = ManagerState::new();
494 let hotkey = Hotkey::new(Modifiers::CTRL_RIGHT, Key::Space).unwrap();
496 let id = HotkeyId(0);
497 state.hotkeys.insert(id, hotkey);
498
499 let event = make_key_event(Modifiers::CTRL_LEFT, Some(Key::Space), true);
501 assert_eq!(state.process_event(&event).len(), 0);
502
503 let event = make_key_event(Modifiers::CTRL_RIGHT, Some(Key::Space), true);
505 let results = state.process_event(&event);
506 assert_eq!(results.len(), 1);
507 assert_eq!(results[0].state, HotkeyState::Pressed);
508 }
509
510 #[test]
511 fn compound_hotkey_matches_either_side() {
512 let mut state = ManagerState::new();
513 let hotkey = Hotkey::new(Modifiers::CMD, Key::K).unwrap();
514 let id = HotkeyId(0);
515 state.hotkeys.insert(id, hotkey);
516
517 let event = make_key_event(Modifiers::CMD_LEFT, Some(Key::K), true);
519 let results = state.process_event(&event);
520 assert_eq!(results.len(), 1);
521
522 state.pressed_hotkeys.clear();
524
525 let event = make_key_event(Modifiers::CMD_RIGHT, Some(Key::K), true);
527 let results = state.process_event(&event);
528 assert_eq!(results.len(), 1);
529 }
530 }
531}