瀏覽代碼

create global shortcut handler for dioxus desktop

Evan Almloff 2 年之前
父節點
當前提交
cf2a79c173
共有 4 個文件被更改,包括 370 次插入0 次删除
  1. 17 0
      examples/shortcut.rs
  2. 31 0
      packages/desktop/src/desktop_context.rs
  3. 9 0
      packages/desktop/src/lib.rs
  4. 313 0
      packages/desktop/src/shortcut.rs

+ 17 - 0
examples/shortcut.rs

@@ -0,0 +1,17 @@
+use dioxus::prelude::*;
+use dioxus_desktop::tao::keyboard::ModifiersState;
+use dioxus_desktop::use_global_shortcut;
+
+fn main() {
+    dioxus_desktop::launch(app);
+}
+
+fn app(cx: Scope) -> Element {
+    let toggled = use_state(cx, || false);
+    use_global_shortcut(cx, KeyCode::S, ModifiersState::CONTROL, {
+        to_owned![toggled];
+        move || toggled.modify(|t| !*t)
+    });
+
+    cx.render(rsx!("toggle: {toggled.get()}"))
+}

+ 31 - 0
packages/desktop/src/desktop_context.rs

@@ -5,6 +5,11 @@ use std::rc::Weak;
 use crate::create_new_window;
 use crate::create_new_window;
 use crate::eval::EvalResult;
 use crate::eval::EvalResult;
 use crate::events::IpcMessage;
 use crate::events::IpcMessage;
+use crate::shortcut::IntoKeyCode;
+use crate::shortcut::IntoModifersState;
+use crate::shortcut::ShortcutId;
+use crate::shortcut::ShortcutRegistry;
+use crate::shortcut::ShortcutRegistryError;
 use crate::Config;
 use crate::Config;
 use crate::WebviewHandler;
 use crate::WebviewHandler;
 use dioxus_core::ScopeState;
 use dioxus_core::ScopeState;
@@ -63,6 +68,8 @@ pub struct DesktopContext {
 
 
     pub(crate) event_handlers: WindowEventHandlers,
     pub(crate) event_handlers: WindowEventHandlers,
 
 
+    pub(crate) shortcut_manager: ShortcutRegistry,
+
     #[cfg(target_os = "ios")]
     #[cfg(target_os = "ios")]
     pub(crate) views: Rc<RefCell<Vec<*mut objc::runtime::Object>>>,
     pub(crate) views: Rc<RefCell<Vec<*mut objc::runtime::Object>>>,
 }
 }
@@ -83,6 +90,7 @@ impl DesktopContext {
         event_loop: EventLoopWindowTarget<UserWindowEvent>,
         event_loop: EventLoopWindowTarget<UserWindowEvent>,
         webviews: WebviewQueue,
         webviews: WebviewQueue,
         event_handlers: WindowEventHandlers,
         event_handlers: WindowEventHandlers,
+        shortcut_manager: ShortcutRegistry,
     ) -> Self {
     ) -> Self {
         Self {
         Self {
             webview,
             webview,
@@ -91,6 +99,7 @@ impl DesktopContext {
             eval: tokio::sync::broadcast::channel(8).0,
             eval: tokio::sync::broadcast::channel(8).0,
             pending_windows: webviews,
             pending_windows: webviews,
             event_handlers,
             event_handlers,
+            shortcut_manager,
             #[cfg(target_os = "ios")]
             #[cfg(target_os = "ios")]
             views: Default::default(),
             views: Default::default(),
         }
         }
@@ -111,6 +120,7 @@ impl DesktopContext {
             dom,
             dom,
             &self.pending_windows,
             &self.pending_windows,
             &self.event_handlers,
             &self.event_handlers,
+            self.shortcut_manager.clone(),
         );
         );
 
 
         let id = window.webview.window().id();
         let id = window.webview.window().id();
@@ -240,6 +250,27 @@ impl DesktopContext {
         self.event_handlers.remove(id)
         self.event_handlers.remove(id)
     }
     }
 
 
+    /// Create a global shortcut
+    ///
+    /// Linux: Only works on x11. See [this issue](https://github.com/tauri-apps/tao/issues/331) for more information.
+    pub fn create_shortcut(
+        &self,
+        key: impl IntoKeyCode,
+        modifiers: impl IntoModifersState,
+        callback: impl FnMut() + 'static,
+    ) -> Result<ShortcutId, ShortcutRegistryError> {
+        self.shortcut_manager.add_shortcut(
+            modifiers.into_modifiers_state(),
+            key.into_key_code(),
+            Box::new(callback),
+        )
+    }
+
+    /// Remove a global shortcut
+    pub fn remove_shortcut(&self, id: ShortcutId) {
+        self.shortcut_manager.remove_shortcut(id)
+    }
+
     /// Push an objc view to the window
     /// Push an objc view to the window
     #[cfg(target_os = "ios")]
     #[cfg(target_os = "ios")]
     pub fn push_view(&self, view: objc_id::ShareId<objc::runtime::Object>) {
     pub fn push_view(&self, view: objc_id::ShareId<objc::runtime::Object>) {

+ 9 - 0
packages/desktop/src/lib.rs

@@ -9,6 +9,7 @@ mod escape;
 mod eval;
 mod eval;
 mod events;
 mod events;
 mod protocol;
 mod protocol;
+mod shortcut;
 mod waker;
 mod waker;
 mod webview;
 mod webview;
 
 
@@ -21,6 +22,8 @@ use dioxus_core::*;
 use dioxus_html::HtmlEvent;
 use dioxus_html::HtmlEvent;
 pub use eval::{use_eval, EvalResult};
 pub use eval::{use_eval, EvalResult};
 use futures_util::{pin_mut, FutureExt};
 use futures_util::{pin_mut, FutureExt};
+use shortcut::ShortcutRegistry;
+pub use shortcut::{use_global_shortcut, ShortcutHandle, ShortcutId, ShortcutRegistryError};
 use std::collections::HashMap;
 use std::collections::HashMap;
 use std::rc::Rc;
 use std::rc::Rc;
 use std::task::Waker;
 use std::task::Waker;
@@ -139,6 +142,8 @@ pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, cfg: Config)
 
 
     let queue = WebviewQueue::default();
     let queue = WebviewQueue::default();
 
 
+    let shortcut_manager = ShortcutRegistry::new(&event_loop);
+
     // By default, we'll create a new window when the app starts
     // By default, we'll create a new window when the app starts
     queue.borrow_mut().push(create_new_window(
     queue.borrow_mut().push(create_new_window(
         cfg,
         cfg,
@@ -147,6 +152,7 @@ pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, cfg: Config)
         VirtualDom::new_with_props(root, props),
         VirtualDom::new_with_props(root, props),
         &queue,
         &queue,
         &event_handlers,
         &event_handlers,
+        shortcut_manager.clone(),
     ));
     ));
 
 
     event_loop.run(move |window_event, event_loop, control_flow| {
     event_loop.run(move |window_event, event_loop, control_flow| {
@@ -260,6 +266,7 @@ pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, cfg: Config)
 
 
                 _ => {}
                 _ => {}
             },
             },
+            Event::GlobalShortcutEvent(id) => shortcut_manager.call_handlers(id),
             _ => {}
             _ => {}
         }
         }
     })
     })
@@ -272,6 +279,7 @@ fn create_new_window(
     dom: VirtualDom,
     dom: VirtualDom,
     queue: &WebviewQueue,
     queue: &WebviewQueue,
     event_handlers: &WindowEventHandlers,
     event_handlers: &WindowEventHandlers,
+    shortcut_manager: ShortcutRegistry,
 ) -> WebviewHandler {
 ) -> WebviewHandler {
     let webview = webview::build(&mut cfg, event_loop, proxy.clone());
     let webview = webview::build(&mut cfg, event_loop, proxy.clone());
 
 
@@ -281,6 +289,7 @@ fn create_new_window(
         event_loop.clone(),
         event_loop.clone(),
         queue.clone(),
         queue.clone(),
         event_handlers.clone(),
         event_handlers.clone(),
+        shortcut_manager,
     ));
     ));
 
 
     let id = webview.window().id();
     let id = webview.window().id();

+ 313 - 0
packages/desktop/src/shortcut.rs

@@ -0,0 +1,313 @@
+use std::{cell::RefCell, collections::HashMap, rc::Rc};
+
+use dioxus_core::ScopeState;
+use dioxus_html::input_data::keyboard_types::Modifiers;
+use slab::Slab;
+use wry::application::{
+    accelerator::{Accelerator, AcceleratorId},
+    event_loop::EventLoopWindowTarget,
+    global_shortcut::{GlobalShortcut, ShortcutManager, ShortcutManagerError},
+    keyboard::{KeyCode, ModifiersState},
+};
+
+use crate::{use_window, DesktopContext};
+
+#[derive(Clone)]
+pub(crate) struct ShortcutRegistry {
+    manager: Rc<RefCell<ShortcutManager>>,
+    shortcuts: ShortcutMap,
+}
+
+type ShortcutMap = Rc<RefCell<HashMap<AcceleratorId, Shortcut>>>;
+
+struct Shortcut {
+    shortcut: GlobalShortcut,
+    callbacks: Slab<Box<dyn FnMut()>>,
+}
+
+impl Shortcut {
+    fn insert(&mut self, callback: Box<dyn FnMut()>) -> 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<T>(target: &EventLoopWindowTarget<T>) -> 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,
+        modifiers: impl Into<Option<ModifiersState>>,
+        key: KeyCode,
+        callback: Box<dyn FnMut()>,
+    ) -> Result<ShortcutId, ShortcutRegistryError> {
+        let accelerator = Accelerator::new(modifiers, key);
+        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) {
+                    let _ = self.manager.borrow_mut().unregister(shortcut.shortcut);
+                }
+            }
+        }
+    }
+}
+
+#[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<dyn std::error::Error>),
+}
+
+#[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,
+}
+
+/// Get a closure that executes any JavaScript in the WebView context.
+pub fn use_global_shortcut(
+    cx: &ScopeState,
+    key: impl IntoKeyCode,
+    modifiers: impl IntoModifersState,
+    handler: impl FnMut() + 'static,
+) -> &Result<ShortcutHandle, ShortcutRegistryError> {
+    let desktop = use_window(cx);
+    cx.use_hook(move || {
+        let desktop = desktop.clone();
+
+        let id = desktop.create_shortcut(
+            key.into_key_code(),
+            modifiers.into_modifiers_state(),
+            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),
+        }
+    }
+}