nex_core/
hotkey_runtime.rs1use crate::hotkey::parse_hotkey;
2
3#[derive(Debug, Clone, PartialEq, Eq)]
4pub enum HotkeyRegistration {
5 Native(i32),
6 Noop(String),
7}
8
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum HotkeyRuntimeError {
11 InvalidHotkey(String),
12 RegistrationFailed(String),
13 EventLoopFailed(String),
14 UnsupportedPlatform,
15}
16
17pub trait HotkeyRegistrar: Send {
18 fn register_hotkey(&mut self, hotkey: &str) -> Result<HotkeyRegistration, HotkeyRuntimeError>;
19 fn unregister_all(&mut self) -> Result<(), HotkeyRuntimeError>;
20}
21
22#[derive(Default)]
23pub struct MockHotkeyRegistrar {
24 registrations: Vec<String>,
25}
26
27impl MockHotkeyRegistrar {
28 pub fn registrations(&self) -> &[String] {
29 &self.registrations
30 }
31}
32
33impl HotkeyRegistrar for MockHotkeyRegistrar {
34 fn register_hotkey(&mut self, hotkey: &str) -> Result<HotkeyRegistration, HotkeyRuntimeError> {
35 parse_hotkey(hotkey).map_err(HotkeyRuntimeError::InvalidHotkey)?;
36 self.registrations.push(hotkey.to_string());
37 Ok(HotkeyRegistration::Noop(hotkey.to_string()))
38 }
39
40 fn unregister_all(&mut self) -> Result<(), HotkeyRuntimeError> {
41 self.registrations.clear();
42 Ok(())
43 }
44}
45
46#[cfg(not(target_os = "windows"))]
47#[derive(Default)]
48pub struct NoopHotkeyRegistrar {
49 registrations: Vec<String>,
50}
51
52#[cfg(not(target_os = "windows"))]
53impl NoopHotkeyRegistrar {
54 pub fn registrations(&self) -> &[String] {
55 &self.registrations
56 }
57}
58
59#[cfg(not(target_os = "windows"))]
60impl HotkeyRegistrar for NoopHotkeyRegistrar {
61 fn register_hotkey(&mut self, hotkey: &str) -> Result<HotkeyRegistration, HotkeyRuntimeError> {
62 parse_hotkey(hotkey).map_err(HotkeyRuntimeError::InvalidHotkey)?;
63 self.registrations.push(hotkey.to_string());
64 Ok(HotkeyRegistration::Noop(hotkey.to_string()))
65 }
66
67 fn unregister_all(&mut self) -> Result<(), HotkeyRuntimeError> {
68 self.registrations.clear();
69 Ok(())
70 }
71}
72
73#[cfg(target_os = "windows")]
74pub struct WindowsHotkeyRegistrar {
75 next_id: i32,
76 registered_ids: Vec<i32>,
77}
78
79#[cfg(target_os = "windows")]
80impl Default for WindowsHotkeyRegistrar {
81 fn default() -> Self {
82 Self {
83 next_id: 1,
84 registered_ids: Vec::new(),
85 }
86 }
87}
88
89#[cfg(target_os = "windows")]
90impl HotkeyRegistrar for WindowsHotkeyRegistrar {
91 fn register_hotkey(&mut self, hotkey: &str) -> Result<HotkeyRegistration, HotkeyRuntimeError> {
92 use windows_sys::Win32::UI::Input::KeyboardAndMouse::{
93 RegisterHotKey, MOD_ALT, MOD_CONTROL, MOD_SHIFT, MOD_WIN, VK_F1, VK_F10, VK_F11,
94 VK_F12, VK_F2, VK_F3, VK_F4, VK_F5, VK_F6, VK_F7, VK_F8, VK_F9, VK_SPACE,
95 };
96
97 let parsed = parse_hotkey(hotkey).map_err(HotkeyRuntimeError::InvalidHotkey)?;
98
99 let mut modifiers = 0_u32;
100 for modifier in &parsed.modifiers {
101 match modifier.to_ascii_lowercase().as_str() {
102 "alt" => modifiers |= MOD_ALT,
103 "ctrl" | "control" => modifiers |= MOD_CONTROL,
104 "shift" => modifiers |= MOD_SHIFT,
105 "win" | "meta" | "super" => modifiers |= MOD_WIN,
106 _ => {
107 return Err(HotkeyRuntimeError::InvalidHotkey(format!(
108 "unsupported modifier: {modifier}"
109 )))
110 }
111 }
112 }
113
114 let key_upper = parsed.key.to_ascii_uppercase();
115 let vk: u32 = match key_upper.as_str() {
116 "SPACE" => VK_SPACE as u32,
117 "F1" => VK_F1 as u32,
118 "F2" => VK_F2 as u32,
119 "F3" => VK_F3 as u32,
120 "F4" => VK_F4 as u32,
121 "F5" => VK_F5 as u32,
122 "F6" => VK_F6 as u32,
123 "F7" => VK_F7 as u32,
124 "F8" => VK_F8 as u32,
125 "F9" => VK_F9 as u32,
126 "F10" => VK_F10 as u32,
127 "F11" => VK_F11 as u32,
128 "F12" => VK_F12 as u32,
129 _ if key_upper.len() == 1 => key_upper.as_bytes()[0] as u32,
130 _ => {
131 return Err(HotkeyRuntimeError::InvalidHotkey(format!(
132 "unsupported key: {}",
133 parsed.key
134 )))
135 }
136 };
137
138 let id = self.next_id;
139 self.next_id += 1;
140
141 let ok = unsafe { RegisterHotKey(std::ptr::null_mut(), id, modifiers, vk) };
142 if ok == 0 {
143 return Err(HotkeyRuntimeError::RegistrationFailed(format!(
144 "RegisterHotKey failed for '{hotkey}'"
145 )));
146 }
147
148 self.registered_ids.push(id);
149 Ok(HotkeyRegistration::Native(id))
150 }
151
152 fn unregister_all(&mut self) -> Result<(), HotkeyRuntimeError> {
153 use windows_sys::Win32::UI::Input::KeyboardAndMouse::UnregisterHotKey;
154
155 for id in self.registered_ids.drain(..) {
156 unsafe {
157 UnregisterHotKey(std::ptr::null_mut(), id);
158 }
159 }
160 Ok(())
161 }
162}
163
164pub fn default_hotkey_registrar() -> Box<dyn HotkeyRegistrar> {
165 #[cfg(target_os = "windows")]
166 {
167 Box::new(WindowsHotkeyRegistrar::default())
168 }
169
170 #[cfg(not(target_os = "windows"))]
171 {
172 Box::new(NoopHotkeyRegistrar::default())
173 }
174}
175
176#[cfg(target_os = "windows")]
177pub fn run_message_loop<F>(mut on_hotkey: F) -> Result<(), HotkeyRuntimeError>
178where
179 F: FnMut(i32),
180{
181 use windows_sys::Win32::UI::WindowsAndMessaging::{
182 DispatchMessageW, GetMessageW, TranslateMessage, MSG, WM_HOTKEY,
183 };
184
185 let mut msg: MSG = unsafe { std::mem::zeroed() };
186 loop {
187 let status = unsafe { GetMessageW(&mut msg, std::ptr::null_mut(), 0, 0) };
188 if status == -1 {
189 return Err(HotkeyRuntimeError::EventLoopFailed(
190 "GetMessageW returned -1".to_string(),
191 ));
192 }
193
194 if status == 0 {
195 return Ok(());
196 }
197
198 if msg.message == WM_HOTKEY {
199 on_hotkey(msg.wParam as i32);
200 }
201
202 unsafe {
203 TranslateMessage(&msg);
204 DispatchMessageW(&msg);
205 }
206 }
207}
208
209#[cfg(not(target_os = "windows"))]
210pub fn run_message_loop<F>(_on_hotkey: F) -> Result<(), HotkeyRuntimeError>
211where
212 F: FnMut(i32),
213{
214 Err(HotkeyRuntimeError::UnsupportedPlatform)
215}