use std::{cell::RefCell, collections::HashMap, rc::Rc, str::FromStr}; use dioxus_core::ScopeState; use dioxus_html::input_data::keyboard_types::Modifiers; use slab::Slab; use wry::application::{ accelerator::{Accelerator, AcceleratorId}, event_loop::EventLoopWindowTarget, keyboard::{KeyCode, ModifiersState}, }; use crate::{desktop_context::DesktopContext, use_window}; #[cfg(any( target_os = "windows", target_os = "macos", target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd" ))] use wry::application::global_shortcut::{GlobalShortcut, ShortcutManager, ShortcutManagerError}; #[cfg(any(target_os = "ios", target_os = "android"))] pub use crate::mobile_shortcut::*; #[derive(Clone)] pub(crate) struct ShortcutRegistry { manager: Rc>, shortcuts: ShortcutMap, } type ShortcutMap = Rc>>; struct Shortcut { #[allow(unused)] shortcut: GlobalShortcut, callbacks: Slab>, } impl Shortcut { fn insert(&mut self, callback: Box) -> usize { self.callbacks.insert(callback) } fn remove(&mut self, id: usize) { let _ = self.callbacks.remove(id); } fn is_empty(&self) -> bool { self.callbacks.is_empty() } } impl ShortcutRegistry { pub fn new(target: &EventLoopWindowTarget) -> Self { Self { manager: Rc::new(RefCell::new(ShortcutManager::new(target))), shortcuts: Rc::new(RefCell::new(HashMap::new())), } } pub(crate) fn call_handlers(&self, id: AcceleratorId) { if let Some(Shortcut { callbacks, .. }) = self.shortcuts.borrow_mut().get_mut(&id) { for (_, callback) in callbacks.iter_mut() { (callback)(); } } } pub(crate) fn add_shortcut( &self, accelerator: Accelerator, callback: Box, ) -> Result { let accelerator_id = accelerator.clone().id(); let mut shortcuts = self.shortcuts.borrow_mut(); Ok( if let Some(callbacks) = shortcuts.get_mut(&accelerator_id) { let id = callbacks.insert(callback); ShortcutId { id: accelerator_id, number: id, } } else { match self.manager.borrow_mut().register(accelerator) { Ok(global_shortcut) => { let mut slab = Slab::new(); let id = slab.insert(callback); let shortcut = Shortcut { shortcut: global_shortcut, callbacks: slab, }; shortcuts.insert(accelerator_id, shortcut); ShortcutId { id: accelerator_id, number: id, } } Err(ShortcutManagerError::InvalidAccelerator(shortcut)) => { return Err(ShortcutRegistryError::InvalidShortcut(shortcut)) } Err(err) => return Err(ShortcutRegistryError::Other(Box::new(err))), } }, ) } pub(crate) fn remove_shortcut(&self, id: ShortcutId) { let mut shortcuts = self.shortcuts.borrow_mut(); if let Some(callbacks) = shortcuts.get_mut(&id.id) { callbacks.remove(id.number); if callbacks.is_empty() { if let Some(_shortcut) = shortcuts.remove(&id.id) { #[cfg(any( target_os = "windows", target_os = "macos", target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd" ))] let _ = self.manager.borrow_mut().unregister(_shortcut.shortcut); } } } } pub(crate) fn remove_all(&self) { let mut shortcuts = self.shortcuts.borrow_mut(); shortcuts.clear(); let _ = self.manager.borrow_mut().unregister_all(); } } #[non_exhaustive] #[derive(Debug)] /// An error that can occur when registering a shortcut. pub enum ShortcutRegistryError { /// The shortcut is invalid. InvalidShortcut(String), /// An unknown error occurred. Other(Box), } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] /// An global id for a shortcut. pub struct ShortcutId { id: AcceleratorId, number: usize, } /// A global shortcut. This will be automatically removed when it is dropped. pub struct ShortcutHandle { desktop: DesktopContext, /// The id of the shortcut pub shortcut_id: ShortcutId, } pub trait IntoAccelerator { fn accelerator(&self) -> Accelerator; } impl IntoAccelerator for (dioxus_html::KeyCode, ModifiersState) { fn accelerator(&self) -> Accelerator { Accelerator::new(Some(self.1), self.0.into_key_code()) } } impl IntoAccelerator for (ModifiersState, dioxus_html::KeyCode) { fn accelerator(&self) -> Accelerator { Accelerator::new(Some(self.0), self.1.into_key_code()) } } impl IntoAccelerator for dioxus_html::KeyCode { fn accelerator(&self) -> Accelerator { Accelerator::new(None, self.into_key_code()) } } impl IntoAccelerator for &str { fn accelerator(&self) -> Accelerator { Accelerator::from_str(self).unwrap() } } /// Get a closure that executes any JavaScript in the WebView context. pub fn use_global_shortcut( cx: &ScopeState, accelerator: impl IntoAccelerator, handler: impl FnMut() + 'static, ) -> &Result { let desktop = use_window(cx); cx.use_hook(move || { let desktop = desktop.clone(); let id = desktop.create_shortcut(accelerator.accelerator(), handler); Ok(ShortcutHandle { desktop, shortcut_id: id?, }) }) } impl ShortcutHandle { /// Remove the shortcut. pub fn remove(&self) { self.desktop.remove_shortcut(self.shortcut_id); } } impl Drop for ShortcutHandle { fn drop(&mut self) { self.remove() } } pub trait IntoModifersState { fn into_modifiers_state(self) -> ModifiersState; } impl IntoModifersState for ModifiersState { fn into_modifiers_state(self) -> ModifiersState { self } } impl IntoModifersState for Modifiers { fn into_modifiers_state(self) -> ModifiersState { let mut state = ModifiersState::empty(); if self.contains(Modifiers::SHIFT) { state |= ModifiersState::SHIFT } if self.contains(Modifiers::CONTROL) { state |= ModifiersState::CONTROL } if self.contains(Modifiers::ALT) { state |= ModifiersState::ALT } if self.contains(Modifiers::META) || self.contains(Modifiers::SUPER) { state |= ModifiersState::SUPER } state } } pub trait IntoKeyCode { fn into_key_code(self) -> KeyCode; } impl IntoKeyCode for KeyCode { fn into_key_code(self) -> KeyCode { self } } impl IntoKeyCode for dioxus_html::KeyCode { fn into_key_code(self) -> KeyCode { match self { dioxus_html::KeyCode::Backspace => KeyCode::Backspace, dioxus_html::KeyCode::Tab => KeyCode::Tab, dioxus_html::KeyCode::Clear => KeyCode::NumpadClear, dioxus_html::KeyCode::Enter => KeyCode::Enter, dioxus_html::KeyCode::Shift => KeyCode::ShiftLeft, dioxus_html::KeyCode::Ctrl => KeyCode::ControlLeft, dioxus_html::KeyCode::Alt => KeyCode::AltLeft, dioxus_html::KeyCode::Pause => KeyCode::Pause, dioxus_html::KeyCode::CapsLock => KeyCode::CapsLock, dioxus_html::KeyCode::Escape => KeyCode::Escape, dioxus_html::KeyCode::Space => KeyCode::Space, dioxus_html::KeyCode::PageUp => KeyCode::PageUp, dioxus_html::KeyCode::PageDown => KeyCode::PageDown, dioxus_html::KeyCode::End => KeyCode::End, dioxus_html::KeyCode::Home => KeyCode::Home, dioxus_html::KeyCode::LeftArrow => KeyCode::ArrowLeft, dioxus_html::KeyCode::UpArrow => KeyCode::ArrowUp, dioxus_html::KeyCode::RightArrow => KeyCode::ArrowRight, dioxus_html::KeyCode::DownArrow => KeyCode::ArrowDown, dioxus_html::KeyCode::Insert => KeyCode::Insert, dioxus_html::KeyCode::Delete => KeyCode::Delete, dioxus_html::KeyCode::Num0 => KeyCode::Numpad0, dioxus_html::KeyCode::Num1 => KeyCode::Numpad1, dioxus_html::KeyCode::Num2 => KeyCode::Numpad2, dioxus_html::KeyCode::Num3 => KeyCode::Numpad3, dioxus_html::KeyCode::Num4 => KeyCode::Numpad4, dioxus_html::KeyCode::Num5 => KeyCode::Numpad5, dioxus_html::KeyCode::Num6 => KeyCode::Numpad6, dioxus_html::KeyCode::Num7 => KeyCode::Numpad7, dioxus_html::KeyCode::Num8 => KeyCode::Numpad8, dioxus_html::KeyCode::Num9 => KeyCode::Numpad9, dioxus_html::KeyCode::A => KeyCode::KeyA, dioxus_html::KeyCode::B => KeyCode::KeyB, dioxus_html::KeyCode::C => KeyCode::KeyC, dioxus_html::KeyCode::D => KeyCode::KeyD, dioxus_html::KeyCode::E => KeyCode::KeyE, dioxus_html::KeyCode::F => KeyCode::KeyF, dioxus_html::KeyCode::G => KeyCode::KeyG, dioxus_html::KeyCode::H => KeyCode::KeyH, dioxus_html::KeyCode::I => KeyCode::KeyI, dioxus_html::KeyCode::J => KeyCode::KeyJ, dioxus_html::KeyCode::K => KeyCode::KeyK, dioxus_html::KeyCode::L => KeyCode::KeyL, dioxus_html::KeyCode::M => KeyCode::KeyM, dioxus_html::KeyCode::N => KeyCode::KeyN, dioxus_html::KeyCode::O => KeyCode::KeyO, dioxus_html::KeyCode::P => KeyCode::KeyP, dioxus_html::KeyCode::Q => KeyCode::KeyQ, dioxus_html::KeyCode::R => KeyCode::KeyR, dioxus_html::KeyCode::S => KeyCode::KeyS, dioxus_html::KeyCode::T => KeyCode::KeyT, dioxus_html::KeyCode::U => KeyCode::KeyU, dioxus_html::KeyCode::V => KeyCode::KeyV, dioxus_html::KeyCode::W => KeyCode::KeyW, dioxus_html::KeyCode::X => KeyCode::KeyX, dioxus_html::KeyCode::Y => KeyCode::KeyY, dioxus_html::KeyCode::Z => KeyCode::KeyZ, dioxus_html::KeyCode::Numpad0 => KeyCode::Numpad0, dioxus_html::KeyCode::Numpad1 => KeyCode::Numpad1, dioxus_html::KeyCode::Numpad2 => KeyCode::Numpad2, dioxus_html::KeyCode::Numpad3 => KeyCode::Numpad3, dioxus_html::KeyCode::Numpad4 => KeyCode::Numpad4, dioxus_html::KeyCode::Numpad5 => KeyCode::Numpad5, dioxus_html::KeyCode::Numpad6 => KeyCode::Numpad6, dioxus_html::KeyCode::Numpad7 => KeyCode::Numpad7, dioxus_html::KeyCode::Numpad8 => KeyCode::Numpad8, dioxus_html::KeyCode::Numpad9 => KeyCode::Numpad9, dioxus_html::KeyCode::Multiply => KeyCode::NumpadMultiply, dioxus_html::KeyCode::Add => KeyCode::NumpadAdd, dioxus_html::KeyCode::Subtract => KeyCode::NumpadSubtract, dioxus_html::KeyCode::DecimalPoint => KeyCode::NumpadDecimal, dioxus_html::KeyCode::Divide => KeyCode::NumpadDivide, dioxus_html::KeyCode::F1 => KeyCode::F1, dioxus_html::KeyCode::F2 => KeyCode::F2, dioxus_html::KeyCode::F3 => KeyCode::F3, dioxus_html::KeyCode::F4 => KeyCode::F4, dioxus_html::KeyCode::F5 => KeyCode::F5, dioxus_html::KeyCode::F6 => KeyCode::F6, dioxus_html::KeyCode::F7 => KeyCode::F7, dioxus_html::KeyCode::F8 => KeyCode::F8, dioxus_html::KeyCode::F9 => KeyCode::F9, dioxus_html::KeyCode::F10 => KeyCode::F10, dioxus_html::KeyCode::F11 => KeyCode::F11, dioxus_html::KeyCode::F12 => KeyCode::F12, dioxus_html::KeyCode::NumLock => KeyCode::NumLock, dioxus_html::KeyCode::ScrollLock => KeyCode::ScrollLock, dioxus_html::KeyCode::Semicolon => KeyCode::Semicolon, dioxus_html::KeyCode::EqualSign => KeyCode::Equal, dioxus_html::KeyCode::Comma => KeyCode::Comma, dioxus_html::KeyCode::Period => KeyCode::Period, dioxus_html::KeyCode::ForwardSlash => KeyCode::Slash, dioxus_html::KeyCode::GraveAccent => KeyCode::Backquote, dioxus_html::KeyCode::OpenBracket => KeyCode::BracketLeft, dioxus_html::KeyCode::BackSlash => KeyCode::Backslash, dioxus_html::KeyCode::CloseBraket => KeyCode::BracketRight, dioxus_html::KeyCode::SingleQuote => KeyCode::Quote, key => panic!("Failed to convert {:?} to tao::keyboard::KeyCode, try using tao::keyboard::KeyCode directly", key), } } }