gravel_core/hotkeys/
mod.rs

1use enumflags2::BitFlags;
2use std::fmt::Debug;
3
4pub use self::{parsing::ParseError, structs::*};
5
6mod parsing;
7mod structs;
8
9struct Hotkey {
10	pub modifiers: BitFlags<Modifier>,
11	pub key: Key,
12	pub callback: Box<dyn Fn() + 'static + Send>,
13}
14
15impl Debug for Hotkey {
16	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17		f.debug_struct("Hotkey")
18			.field("modifiers", &self.modifiers)
19			.field("key", &self.key)
20			.field("value", &"Box<dyn FnMut>")
21			.finish()
22	}
23}
24
25/// Listens for system-wide hotkeys and calls closures.
26///
27/// The listener runs in a separate thread to avoid blocking.
28#[derive(Default)]
29pub struct Listener {
30	hotkeys: Vec<Hotkey>,
31}
32
33impl Listener {
34	/// Registers a hotkey given the modifiers and key.
35	pub fn register(&mut self, modifiers: BitFlags<Modifier>, key: Key, f: impl Fn() + 'static + Send) -> &mut Self {
36		let hotkey = Hotkey {
37			modifiers,
38			key,
39			callback: Box::from(f),
40		};
41
42		self.hotkeys.push(hotkey);
43
44		self
45	}
46
47	/// Registers a hotkey given an emacs-like binding.
48	///
49	/// Examples:
50	/// - `A-<Space>` => Alt + Space
51	/// - `C-M-s` => Control + Super/Windows + S
52	/// - `a` => A
53	///
54	/// For a complete list of keys and modifiers, see [`Key`], [`Modifier`]
55	pub fn register_emacs(&mut self, binding: &str, f: impl Fn() + 'static + Send) -> Result<&mut Self, ParseError> {
56		let result = parsing::parse_binding(binding)?;
57		self.register(result.modifiers, result.key, f);
58
59		Ok(self)
60	}
61
62	/// Spawns the listener with the registered hotkeys.
63	pub fn spawn_listener(self) {
64		// run the listener on another thread to avoid blocking the current one
65		std::thread::spawn(move || {
66			log::trace!("starting hotkey listener thread");
67			init_hotkeys(self.hotkeys).listen();
68		});
69	}
70}
71
72/// Registers the given hotkeys with a new [`hotkey::Listener`] and returns it.
73///
74/// If a hotkey cannot be registered, a warning is logged and the hotkey is skipped.
75fn init_hotkeys(hotkeys: Vec<Hotkey>) -> hotkey::Listener {
76	let mut hk = hotkey::Listener::new();
77
78	for hotkey in hotkeys {
79		let modifiers = hotkey.modifiers.iter().fold(0, |r, v| r | convert_modifier(v));
80		let key = convert_key(hotkey.key);
81
82		hk.register_hotkey(modifiers, key, move || (hotkey.callback)())
83			.inspect_err(|e| log::warn!("failed to register hotkey {:?}: {e}", (hotkey.key, hotkey.modifiers)))
84			.ok();
85	}
86
87	hk
88}
89
90fn convert_modifier(value: Modifier) -> u32 {
91	match value {
92		Modifier::Alt => hotkey::modifiers::ALT,
93		Modifier::Control => hotkey::modifiers::CONTROL,
94		Modifier::Shift => hotkey::modifiers::SHIFT,
95		Modifier::Super => hotkey::modifiers::SUPER,
96	}
97}
98
99fn convert_key(value: Key) -> u32 {
100	match value {
101		Key::A => 'A' as u32,
102		Key::B => 'B' as u32,
103		Key::C => 'C' as u32,
104		Key::D => 'D' as u32,
105		Key::E => 'E' as u32,
106		Key::F => 'F' as u32,
107		Key::G => 'G' as u32,
108		Key::H => 'H' as u32,
109		Key::I => 'I' as u32,
110		Key::J => 'J' as u32,
111		Key::K => 'K' as u32,
112		Key::L => 'L' as u32,
113		Key::M => 'M' as u32,
114		Key::N => 'N' as u32,
115		Key::O => 'O' as u32,
116		Key::P => 'P' as u32,
117		Key::Q => 'Q' as u32,
118		Key::R => 'R' as u32,
119		Key::S => 'S' as u32,
120		Key::T => 'T' as u32,
121		Key::U => 'U' as u32,
122		Key::V => 'V' as u32,
123		Key::W => 'W' as u32,
124		Key::X => 'X' as u32,
125		Key::Y => 'Y' as u32,
126		Key::Z => 'Z' as u32,
127		Key::Backspace => hotkey::keys::BACKSPACE,
128		Key::Tab => hotkey::keys::TAB,
129		Key::Enter => hotkey::keys::ENTER,
130		Key::CapsLock => hotkey::keys::CAPS_LOCK,
131		Key::Escape => hotkey::keys::ESCAPE,
132		Key::Space => hotkey::keys::SPACEBAR,
133		Key::PageUp => hotkey::keys::PAGE_UP,
134		Key::PageDown => hotkey::keys::PAGE_DOWN,
135		Key::End => hotkey::keys::END,
136		Key::Home => hotkey::keys::HOME,
137		Key::Left => hotkey::keys::ARROW_LEFT,
138		Key::Right => hotkey::keys::ARROW_RIGHT,
139		Key::Up => hotkey::keys::ARROW_UP,
140		Key::Down => hotkey::keys::ARROW_DOWN,
141		Key::PrintScreen => hotkey::keys::PRINT_SCREEN,
142		Key::Insert => hotkey::keys::INSERT,
143		Key::Delete => hotkey::keys::DELETE,
144	}
145}