1use crate::error::Result;
16use serde::{Deserialize, Serialize};
17use std::fmt;
18use std::str::FromStr;
19use std::sync::{Arc, Mutex};
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
23pub struct Modifiers {
24 pub ctrl: bool,
25 pub alt: bool,
26 pub shift: bool,
27 pub meta: bool,
29}
30
31impl Modifiers {
32 pub fn is_empty(&self) -> bool {
33 !(self.ctrl || self.alt || self.shift || self.meta)
34 }
35
36 pub fn from_raw_win(m: u32) -> Self {
38 Self {
39 ctrl: m & 0x0002 != 0,
40 alt: m & 0x0001 != 0,
41 shift: m & 0x0004 != 0,
42 meta: m & 0x0008 != 0,
43 }
44 }
45
46 pub fn from_raw_mac(f: u64) -> Self {
48 const CMD: u64 = 0x100000; const SHIFT: u64 = 0x20000; const ALT: u64 = 0x80000; const CTRL: u64 = 0x40000; Self {
53 ctrl: f & CTRL != 0,
54 alt: f & ALT != 0,
55 shift: f & SHIFT != 0,
56 meta: f & CMD != 0,
57 }
58 }
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
63pub enum Key {
64 Letter(char),
65 Digit(u8),
66 F(u8),
67 Space,
68 Enter,
69 Escape,
70 Tab,
71 Backspace,
72 Delete,
73 ArrowUp,
74 ArrowDown,
75 ArrowLeft,
76 ArrowRight,
77 Home,
78 End,
79 PageUp,
80 PageDown,
81 Insert,
82}
83
84impl Key {
85 pub fn from_token(tok: &str) -> Option<Key> {
87 let t = tok.trim();
88 if t.len() == 1 {
89 let c = t.as_bytes()[0];
90 if c.is_ascii_alphabetic() {
91 return Some(Key::Letter(c.to_ascii_lowercase() as char));
92 }
93 if c.is_ascii_digit() {
94 return Some(Key::Digit(c - b'0'));
95 }
96 }
97 match t.to_ascii_lowercase().as_str() {
98 "space" => Some(Key::Space),
99 "enter" | "return" => Some(Key::Enter),
100 "escape" | "esc" => Some(Key::Escape),
101 "tab" => Some(Key::Tab),
102 "backspace" | "back" => Some(Key::Backspace),
103 "delete" | "del" => Some(Key::Delete),
104 "up" | "arrowup" => Some(Key::ArrowUp),
105 "down" | "arrowdown" => Some(Key::ArrowDown),
106 "left" | "arrowleft" => Some(Key::ArrowLeft),
107 "right" | "arrowright" => Some(Key::ArrowRight),
108 "home" => Some(Key::Home),
109 "end" => Some(Key::End),
110 "pageup" | "prior" => Some(Key::PageUp),
111 "pagedown" | "next" => Some(Key::PageDown),
112 "insert" | "ins" => Some(Key::Insert),
113 _ => {
114 if t.len() > 1 && t.starts_with('f') {
115 if let Ok(n) = t[1..].parse::<u8>() {
116 if n >= 1 && n <= 24 {
117 return Some(Key::F(n));
118 }
119 }
120 }
121 None
122 }
123 }
124 }
125}
126
127impl fmt::Display for Key {
128 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129 match self {
130 Key::Letter(c) => write!(f, "{}", c.to_ascii_uppercase()),
131 Key::Digit(d) => write!(f, "{}", d),
132 Key::F(n) => write!(f, "F{}", n),
133 Key::Space => write!(f, "Space"),
134 Key::Enter => write!(f, "Enter"),
135 Key::Escape => write!(f, "Escape"),
136 Key::Tab => write!(f, "Tab"),
137 Key::Backspace => write!(f, "Backspace"),
138 Key::Delete => write!(f, "Delete"),
139 Key::ArrowUp => write!(f, "Up"),
140 Key::ArrowDown => write!(f, "Down"),
141 Key::ArrowLeft => write!(f, "Left"),
142 Key::ArrowRight => write!(f, "Right"),
143 Key::Home => write!(f, "Home"),
144 Key::End => write!(f, "End"),
145 Key::PageUp => write!(f, "PageUp"),
146 Key::PageDown => write!(f, "PageDown"),
147 Key::Insert => write!(f, "Insert"),
148 }
149 }
150}
151
152#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
154pub struct Hotkey {
155 pub modifiers: Modifiers,
156 pub key: Key,
157}
158
159impl fmt::Display for Hotkey {
160 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
161 let mut parts: Vec<String> = Vec::new();
162 if self.modifiers.ctrl {
163 parts.push("Ctrl".into());
164 }
165 if self.modifiers.alt {
166 parts.push("Alt".into());
167 }
168 if self.modifiers.shift {
169 parts.push("Shift".into());
170 }
171 if self.modifiers.meta {
172 parts.push("Meta".into());
173 }
174 parts.push(self.key.to_string());
175 write!(f, "{}", parts.join("+"))
176 }
177}
178
179impl FromStr for Hotkey {
180 type Err = String;
181 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
183 let mut modifiers = Modifiers::default();
184 let mut key: Option<Key> = None;
185 for tok in s.split('+') {
186 let tok = tok.trim();
187 if tok.is_empty() {
188 continue;
189 }
190 match tok.to_ascii_lowercase().as_str() {
191 "ctrl" | "control" => modifiers.ctrl = true,
192 "alt" | "option" => modifiers.alt = true,
193 "shift" => modifiers.shift = true,
194 "meta" | "win" | "cmd" | "super" | "command" => modifiers.meta = true,
195 other => {
196 key = Some(Key::from_token(other).ok_or_else(|| format!("unknown key: {tok}"))?);
197 }
198 }
199 }
200 let key = key.ok_or_else(|| "hotkey requires a non-modifier key".to_string())?;
201 Ok(Hotkey { modifiers, key })
202 }
203}
204
205pub trait HotkeyHandler: Send + Sync {
207 fn on_hotkey(&self, id: u32, hotkey: &Hotkey);
208}
209
210pub struct HotkeyManager {
212 handler: Arc<dyn HotkeyHandler>,
213 inner: Mutex<Option<PlatformHotkey>>,
214}
215
216impl HotkeyManager {
217 pub fn new(handler: Arc<dyn HotkeyHandler>) -> Self {
218 Self {
219 handler,
220 inner: Mutex::new(None),
221 }
222 }
223
224 pub fn register(&self, id: u32, hotkey: &Hotkey) -> Result<()> {
226 let mut guard = self.inner.lock().unwrap();
227 match guard.as_ref() {
228 Some(p) => p.register(id, hotkey),
229 None => {
230 let p = PlatformHotkey::start(self.handler.clone())?;
231 p.register(id, hotkey)?;
232 *guard = Some(p);
233 Ok(())
234 }
235 }
236 }
237
238 pub fn unregister(&self, id: u32) -> Result<()> {
240 if let Some(p) = self.inner.lock().unwrap().as_ref() {
241 p.unregister(id)?;
242 }
243 Ok(())
244 }
245}
246
247impl Drop for HotkeyManager {
248 fn drop(&mut self) {
249 if let Some(p) = self.inner.lock().unwrap().take() {
250 p.stop();
251 }
252 }
253}
254
255enum PlatformHotkey {
258 #[cfg(windows)]
259 Windows(crate::hotkeys_win::WinHotkey),
260 #[cfg(target_os = "macos")]
261 Macos(crate::hotkeys_mac::MacHotkey),
262 #[cfg(not(any(windows, target_os = "macos")))]
263 Unsupported,
264}
265
266impl PlatformHotkey {
267 fn start(handler: Arc<dyn HotkeyHandler>) -> Result<Self> {
268 #[cfg(windows)]
269 {
270 Ok(PlatformHotkey::Windows(crate::hotkeys_win::WinHotkey::start(handler)?))
271 }
272 #[cfg(target_os = "macos")]
273 {
274 Ok(PlatformHotkey::Macos(crate::hotkeys_mac::MacHotkey::start(handler)?))
275 }
276 #[cfg(not(any(windows, target_os = "macos")))]
277 {
278 let _ = handler;
279 Err(crate::error::RdesktopError::UnsupportedPlatform(
280 "global hotkeys are only supported on Windows and macOS".into(),
281 ))
282 }
283 }
284
285 fn register(&self, id: u32, hotkey: &Hotkey) -> Result<()> {
286 match self {
287 #[cfg(windows)]
288 PlatformHotkey::Windows(w) => w.register(id, hotkey),
289 #[cfg(target_os = "macos")]
290 PlatformHotkey::Macos(m) => m.register(id, hotkey),
291 #[cfg(not(any(windows, target_os = "macos")))]
292 PlatformHotkey::Unsupported => Ok(()),
293 }
294 }
295
296 fn unregister(&self, id: u32) -> Result<()> {
297 match self {
298 #[cfg(windows)]
299 PlatformHotkey::Windows(w) => w.unregister(id),
300 #[cfg(target_os = "macos")]
301 PlatformHotkey::Macos(m) => m.unregister(id),
302 #[cfg(not(any(windows, target_os = "macos")))]
303 PlatformHotkey::Unsupported => Ok(()),
304 }
305 }
306
307 fn stop(self) {
308 match self {
309 #[cfg(windows)]
310 PlatformHotkey::Windows(w) => w.stop(),
311 #[cfg(target_os = "macos")]
312 PlatformHotkey::Macos(m) => m.stop(),
313 #[cfg(not(any(windows, target_os = "macos")))]
314 PlatformHotkey::Unsupported => {}
315 }
316 }
317}