create global shortcut handler for dioxus desktop

This commit is contained in:
Evan Almloff committed 2023-03-01 13:15:58 -06:00
1 parent 49c5a5043a
commit abcb3f57f0
4 files changed
+333

No files matched your search

+17
View File
@@ -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()}"))
}
+28
View File
@@ -5,6 +5,10 @@ use std::rc::Weak;
use crate::create_new_window;
use crate::eval::EvalResult;
use crate::events::IpcMessage;
use crate::shortcut::IntoKeyCode;
use crate::shortcut::ShortcutId;
use crate::shortcut::ShortcutRegistry;
use crate::shortcut::ShortcutRegistryError;
use crate::Config;
use crate::WebviewHandler;
use dioxus_core::ScopeState;
@@ -16,6 +20,7 @@ use slab::Slab;
use wry::application::event::Event;
use wry::application::event_loop::EventLoopProxy;
use wry::application::event_loop::EventLoopWindowTarget;
use wry::application::keyboard::ModifiersState;
#[cfg(target_os = "ios")]
use wry::application::platform::ios::WindowExtIOS;
use wry::application::window::Fullscreen as WryFullscreen;
@@ -63,6 +68,8 @@ pub struct DesktopContext {
pub(crate) event_handlers: WindowEventHandlers,
pub(crate) shortcut_manager: ShortcutRegistry,
#[cfg(target_os = "ios")]
pub(crate) views: Rc<RefCell<Vec<*mut objc::runtime::Object>>>,
}
@@ -83,6 +90,7 @@ impl DesktopContext {
event_loop: EventLoopWindowTarget<UserWindowEvent>,
webviews: WebviewQueue,
event_handlers: WindowEventHandlers,
shortcut_manager: ShortcutRegistry,
) -> Self {
Self {
webview,
@@ -91,6 +99,7 @@ impl DesktopContext {
eval: tokio::sync::broadcast::channel(8).0,
pending_windows: webviews,
event_handlers,
shortcut_manager,
#[cfg(target_os = "ios")]
views: Default::default(),
}
@@ -111,6 +120,7 @@ impl DesktopContext {
dom,
&self.pending_windows,
&self.event_handlers,
self.shortcut_manager.clone(),
);
let id = window.webview.window().id();
@@ -240,6 +250,24 @@ impl DesktopContext {
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 Into<Option<ModifiersState>>,
callback: impl FnMut() + 'static,
) -> Result<ShortcutId, ShortcutRegistryError> {
self.shortcut_manager
.add_shortcut(modifiers, 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
#[cfg(target_os = "ios")]
pub fn push_view(&self, view: objc_id::ShareId<objc::runtime::Object>) {
+9
View File
@@ -9,6 +9,7 @@ mod escape;
mod eval;
mod events;
mod protocol;
mod shortcut;
mod waker;
mod webview;
@@ -21,6 +22,8 @@ use dioxus_core::*;
use dioxus_html::HtmlEvent;
pub use eval::{use_eval, EvalResult};
use futures_util::{pin_mut, FutureExt};
use shortcut::ShortcutRegistry;
pub use shortcut::{use_global_shortcut, ShortcutHandle, ShortcutId, ShortcutRegistryError};
use std::collections::HashMap;
use std::rc::Rc;
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 shortcut_manager = ShortcutRegistry::new(&event_loop);
// By default, we'll create a new window when the app starts
queue.borrow_mut().push(create_new_window(
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),
&queue,
&event_handlers,
shortcut_manager.clone(),
));
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,
queue: &WebviewQueue,
event_handlers: &WindowEventHandlers,
shortcut_manager: ShortcutRegistry,
) -> WebviewHandler {
let webview = webview::build(&mut cfg, event_loop, proxy.clone());
@@ -281,6 +289,7 @@ fn create_new_window(
event_loop.clone(),
queue.clone(),
event_handlers.clone(),
shortcut_manager,
));
let id = webview.window().id();
+279
View File
@@ -0,0 +1,279 @@
use std::{cell::RefCell, collections::HashMap, rc::Rc};
use dioxus_core::ScopeState;
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 Into<Option<ModifiersState>>,
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, 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 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),
}
}
}