dioxus_desktop/
shortcut.rs

1#[cfg(any(
2    target_os = "windows",
3    target_os = "macos",
4    target_os = "linux",
5    target_os = "dragonfly",
6    target_os = "freebsd",
7    target_os = "netbsd",
8    target_os = "openbsd"
9))]
10pub use global_hotkey::{
11    hotkey::{Code, HotKey},
12    Error as HotkeyError, GlobalHotKeyEvent, GlobalHotKeyManager, HotKeyState,
13};
14
15#[cfg(any(target_os = "ios", target_os = "android"))]
16pub use crate::mobile_shortcut::*;
17
18use crate::window;
19use dioxus_html::input_data::keyboard_types::Modifiers;
20use slab::Slab;
21use std::{cell::RefCell, collections::HashMap, rc::Rc, str::FromStr};
22use tao::keyboard::ModifiersState;
23
24/// An global id for a shortcut.
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
26pub struct ShortcutHandle {
27    id: u32,
28    number: usize,
29}
30
31impl ShortcutHandle {
32    /// Remove the shortcut.
33    pub fn remove(&self) {
34        window().remove_shortcut(*self);
35    }
36}
37
38/// An error that can occur when registering a shortcut.
39#[non_exhaustive]
40#[derive(Debug, Clone)]
41pub enum ShortcutRegistryError {
42    /// The shortcut is invalid.
43    InvalidShortcut(String),
44    /// An unknown error occurred.
45    Other(Rc<dyn std::error::Error>),
46}
47
48pub(crate) struct ShortcutRegistry {
49    manager: GlobalHotKeyManager,
50    shortcuts: RefCell<HashMap<u32, ShortcutInner>>,
51}
52
53struct ShortcutInner {
54    #[allow(unused)]
55    shortcut: HotKey,
56    callbacks: Slab<Box<dyn FnMut(HotKeyState)>>,
57}
58
59impl ShortcutRegistry {
60    pub fn new() -> Self {
61        Self {
62            manager: GlobalHotKeyManager::new().unwrap(),
63            shortcuts: RefCell::new(HashMap::new()),
64        }
65    }
66
67    #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
68    pub(crate) fn call_handlers(&self, id: GlobalHotKeyEvent) {
69        if let Some(ShortcutInner { callbacks, .. }) = self.shortcuts.borrow_mut().get_mut(&id.id) {
70            for (_, callback) in callbacks.iter_mut() {
71                (callback)(id.state);
72            }
73        }
74    }
75
76    pub(crate) fn add_shortcut(
77        &self,
78        hotkey: HotKey,
79        callback: Box<dyn FnMut(HotKeyState)>,
80    ) -> Result<ShortcutHandle, ShortcutRegistryError> {
81        let accelerator_id = hotkey.clone().id();
82
83        let mut shortcuts = self.shortcuts.borrow_mut();
84
85        if let Some(callbacks) = shortcuts.get_mut(&accelerator_id) {
86            return Ok(ShortcutHandle {
87                id: accelerator_id,
88                number: callbacks.callbacks.insert(callback),
89            });
90        };
91
92        self.manager.register(hotkey).map_err(|e| match e {
93            HotkeyError::HotKeyParseError(shortcut) => {
94                ShortcutRegistryError::InvalidShortcut(shortcut)
95            }
96            err => ShortcutRegistryError::Other(Rc::new(err)),
97        })?;
98
99        let mut shortcut = ShortcutInner {
100            shortcut: hotkey,
101            callbacks: Slab::new(),
102        };
103
104        let id = shortcut.callbacks.insert(callback);
105
106        shortcuts.insert(accelerator_id, shortcut);
107
108        Ok(ShortcutHandle {
109            id: accelerator_id,
110            number: id,
111        })
112    }
113
114    pub(crate) fn remove_shortcut(&self, id: ShortcutHandle) {
115        let mut shortcuts = self.shortcuts.borrow_mut();
116        if let Some(callbacks) = shortcuts.get_mut(&id.id) {
117            let _ = callbacks.callbacks.remove(id.number);
118            if callbacks.callbacks.is_empty() {
119                if let Some(_shortcut) = shortcuts.remove(&id.id) {
120                    let _ = self.manager.unregister(_shortcut.shortcut);
121                }
122            }
123        }
124    }
125
126    pub(crate) fn remove_all(&self) {
127        let mut shortcuts = self.shortcuts.borrow_mut();
128        let hotkeys: Vec<_> = shortcuts.drain().map(|(_, v)| v.shortcut).collect();
129        let _ = self.manager.unregister_all(&hotkeys);
130    }
131}
132
133pub trait IntoAccelerator {
134    fn accelerator(&self) -> HotKey;
135}
136
137impl IntoAccelerator for (dioxus_html::KeyCode, ModifiersState) {
138    fn accelerator(&self) -> HotKey {
139        HotKey::new(Some(self.1.into_modifiers_state()), self.0.into_key_code())
140    }
141}
142
143impl IntoAccelerator for (ModifiersState, dioxus_html::KeyCode) {
144    fn accelerator(&self) -> HotKey {
145        HotKey::new(Some(self.0.into_modifiers_state()), self.1.into_key_code())
146    }
147}
148
149impl IntoAccelerator for dioxus_html::KeyCode {
150    fn accelerator(&self) -> HotKey {
151        HotKey::new(None, self.into_key_code())
152    }
153}
154
155impl IntoAccelerator for &str {
156    fn accelerator(&self) -> HotKey {
157        HotKey::from_str(self).unwrap()
158    }
159}
160
161pub trait IntoModifiersState {
162    fn into_modifiers_state(self) -> Modifiers;
163}
164
165impl IntoModifiersState for ModifiersState {
166    fn into_modifiers_state(self) -> Modifiers {
167        let mut modifiers = Modifiers::default();
168        if self.shift_key() {
169            modifiers |= Modifiers::SHIFT;
170        }
171        if self.control_key() {
172            modifiers |= Modifiers::CONTROL;
173        }
174        if self.alt_key() {
175            modifiers |= Modifiers::ALT;
176        }
177        if self.super_key() {
178            modifiers |= Modifiers::META;
179        }
180
181        modifiers
182    }
183}
184
185impl IntoModifiersState for Modifiers {
186    fn into_modifiers_state(self) -> Modifiers {
187        self
188    }
189}
190
191pub trait IntoKeyCode {
192    fn into_key_code(self) -> Code;
193}
194
195impl IntoKeyCode for Code {
196    fn into_key_code(self) -> Code {
197        self
198    }
199}
200
201impl IntoKeyCode for dioxus_html::KeyCode {
202    fn into_key_code(self) -> Code {
203        match self {
204            dioxus_html::KeyCode::Backspace => Code::Backspace,
205            dioxus_html::KeyCode::Tab => Code::Tab,
206            dioxus_html::KeyCode::Clear => Code::NumpadClear,
207            dioxus_html::KeyCode::Enter => Code::Enter,
208            dioxus_html::KeyCode::Shift => Code::ShiftLeft,
209            dioxus_html::KeyCode::Ctrl => Code::ControlLeft,
210            dioxus_html::KeyCode::Alt => Code::AltLeft,
211            dioxus_html::KeyCode::Pause => Code::Pause,
212            dioxus_html::KeyCode::CapsLock => Code::CapsLock,
213            dioxus_html::KeyCode::Escape => Code::Escape,
214            dioxus_html::KeyCode::Space => Code::Space,
215            dioxus_html::KeyCode::PageUp => Code::PageUp,
216            dioxus_html::KeyCode::PageDown => Code::PageDown,
217            dioxus_html::KeyCode::End => Code::End,
218            dioxus_html::KeyCode::Home => Code::Home,
219            dioxus_html::KeyCode::LeftArrow => Code::ArrowLeft,
220            dioxus_html::KeyCode::UpArrow => Code::ArrowUp,
221            dioxus_html::KeyCode::RightArrow => Code::ArrowRight,
222            dioxus_html::KeyCode::DownArrow => Code::ArrowDown,
223            dioxus_html::KeyCode::Insert => Code::Insert,
224            dioxus_html::KeyCode::Delete => Code::Delete,
225            dioxus_html::KeyCode::Num0 => Code::Numpad0,
226            dioxus_html::KeyCode::Num1 => Code::Numpad1,
227            dioxus_html::KeyCode::Num2 => Code::Numpad2,
228            dioxus_html::KeyCode::Num3 => Code::Numpad3,
229            dioxus_html::KeyCode::Num4 => Code::Numpad4,
230            dioxus_html::KeyCode::Num5 => Code::Numpad5,
231            dioxus_html::KeyCode::Num6 => Code::Numpad6,
232            dioxus_html::KeyCode::Num7 => Code::Numpad7,
233            dioxus_html::KeyCode::Num8 => Code::Numpad8,
234            dioxus_html::KeyCode::Num9 => Code::Numpad9,
235            dioxus_html::KeyCode::A => Code::KeyA,
236            dioxus_html::KeyCode::B => Code::KeyB,
237            dioxus_html::KeyCode::C => Code::KeyC,
238            dioxus_html::KeyCode::D => Code::KeyD,
239            dioxus_html::KeyCode::E => Code::KeyE,
240            dioxus_html::KeyCode::F => Code::KeyF,
241            dioxus_html::KeyCode::G => Code::KeyG,
242            dioxus_html::KeyCode::H => Code::KeyH,
243            dioxus_html::KeyCode::I => Code::KeyI,
244            dioxus_html::KeyCode::J => Code::KeyJ,
245            dioxus_html::KeyCode::K => Code::KeyK,
246            dioxus_html::KeyCode::L => Code::KeyL,
247            dioxus_html::KeyCode::M => Code::KeyM,
248            dioxus_html::KeyCode::N => Code::KeyN,
249            dioxus_html::KeyCode::O => Code::KeyO,
250            dioxus_html::KeyCode::P => Code::KeyP,
251            dioxus_html::KeyCode::Q => Code::KeyQ,
252            dioxus_html::KeyCode::R => Code::KeyR,
253            dioxus_html::KeyCode::S => Code::KeyS,
254            dioxus_html::KeyCode::T => Code::KeyT,
255            dioxus_html::KeyCode::U => Code::KeyU,
256            dioxus_html::KeyCode::V => Code::KeyV,
257            dioxus_html::KeyCode::W => Code::KeyW,
258            dioxus_html::KeyCode::X => Code::KeyX,
259            dioxus_html::KeyCode::Y => Code::KeyY,
260            dioxus_html::KeyCode::Z => Code::KeyZ,
261            dioxus_html::KeyCode::Numpad0 => Code::Numpad0,
262            dioxus_html::KeyCode::Numpad1 => Code::Numpad1,
263            dioxus_html::KeyCode::Numpad2 => Code::Numpad2,
264            dioxus_html::KeyCode::Numpad3 => Code::Numpad3,
265            dioxus_html::KeyCode::Numpad4 => Code::Numpad4,
266            dioxus_html::KeyCode::Numpad5 => Code::Numpad5,
267            dioxus_html::KeyCode::Numpad6 => Code::Numpad6,
268            dioxus_html::KeyCode::Numpad7 => Code::Numpad7,
269            dioxus_html::KeyCode::Numpad8 => Code::Numpad8,
270            dioxus_html::KeyCode::Numpad9 => Code::Numpad9,
271            dioxus_html::KeyCode::Multiply => Code::NumpadMultiply,
272            dioxus_html::KeyCode::Add => Code::NumpadAdd,
273            dioxus_html::KeyCode::Subtract => Code::NumpadSubtract,
274            dioxus_html::KeyCode::DecimalPoint => Code::NumpadDecimal,
275            dioxus_html::KeyCode::Divide => Code::NumpadDivide,
276            dioxus_html::KeyCode::F1 => Code::F1,
277            dioxus_html::KeyCode::F2 => Code::F2,
278            dioxus_html::KeyCode::F3 => Code::F3,
279            dioxus_html::KeyCode::F4 => Code::F4,
280            dioxus_html::KeyCode::F5 => Code::F5,
281            dioxus_html::KeyCode::F6 => Code::F6,
282            dioxus_html::KeyCode::F7 => Code::F7,
283            dioxus_html::KeyCode::F8 => Code::F8,
284            dioxus_html::KeyCode::F9 => Code::F9,
285            dioxus_html::KeyCode::F10 => Code::F10,
286            dioxus_html::KeyCode::F11 => Code::F11,
287            dioxus_html::KeyCode::F12 => Code::F12,
288            dioxus_html::KeyCode::NumLock => Code::NumLock,
289            dioxus_html::KeyCode::ScrollLock => Code::ScrollLock,
290            dioxus_html::KeyCode::Semicolon => Code::Semicolon,
291            dioxus_html::KeyCode::EqualSign => Code::Equal,
292            dioxus_html::KeyCode::Comma => Code::Comma,
293            dioxus_html::KeyCode::Period => Code::Period,
294            dioxus_html::KeyCode::ForwardSlash => Code::Slash,
295            dioxus_html::KeyCode::GraveAccent => Code::Backquote,
296            dioxus_html::KeyCode::OpenBracket => Code::BracketLeft,
297            dioxus_html::KeyCode::BackSlash => Code::Backslash,
298            dioxus_html::KeyCode::CloseBracket => Code::BracketRight,
299            dioxus_html::KeyCode::SingleQuote => Code::Quote,
300            key => panic!("Failed to convert {key:?} to tao::keyboard::KeyCode, try using tao::keyboard::KeyCode directly"),
301        }
302    }
303}