shortcut.rs 12 KB

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