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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
26pub struct ShortcutHandle {
27 id: u32,
28 number: usize,
29}
30
31impl ShortcutHandle {
32 pub fn remove(&self) {
34 window().remove_shortcut(*self);
35 }
36}
37
38#[non_exhaustive]
40#[derive(Debug, Clone)]
41pub enum ShortcutRegistryError {
42 InvalidShortcut(String),
44 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}