From 885580f3af9aa092e5da1c6ab30433a9147faec8 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 2 Sep 2023 18:24:42 -0500 Subject: [PATCH 1/9] update to wry 0.31.0 --- examples/mobile_demo/Cargo.toml | 2 +- packages/desktop/Cargo.toml | 4 +- packages/desktop/src/desktop_context.rs | 15 +- packages/desktop/src/lib.rs | 30 ++- packages/desktop/src/mobile_shortcut.rs | 21 +- packages/desktop/src/shortcut.rs | 325 ++++++++++++------------ 6 files changed, 207 insertions(+), 190 deletions(-) diff --git a/examples/mobile_demo/Cargo.toml b/examples/mobile_demo/Cargo.toml index d22269a0c..ba2af2312 100644 --- a/examples/mobile_demo/Cargo.toml +++ b/examples/mobile_demo/Cargo.toml @@ -35,7 +35,7 @@ frameworks = ["WebKit"] [dependencies] anyhow = "1.0.56" log = "0.4.11" -wry = "0.28.0" +wry = "0.31.0" dioxus = { path = "../../packages/dioxus" } dioxus-desktop = { path = "../../packages/desktop", features = [ "tokio_runtime", diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index a85c2e045..311e224bb 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -19,7 +19,7 @@ serde = "1.0.136" serde_json = "1.0.79" thiserror = { workspace = true } log = { workspace = true } -wry = { version = "0.28.0" } +wry = { version = "0.31.0" } futures-channel = { workspace = true } tokio = { workspace = true, features = [ "sync", @@ -41,6 +41,7 @@ async-trait = "0.1.68" [target.'cfg(any(target_os = "windows",target_os = "macos",target_os = "linux",target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))'.dependencies] rfd = "0.11.3" +global-hotkey = { git = "https://github.com/tauri-apps/global-hotkey" } [target.'cfg(target_os = "ios")'.dependencies] objc = "0.2.7" @@ -55,7 +56,6 @@ default = ["tokio_runtime", "hot-reload"] tokio_runtime = ["tokio"] fullscreen = ["wry/fullscreen"] transparent = ["wry/transparent"] -tray = ["wry/tray"] hot-reload = ["dioxus-hot-reload"] [dev-dependencies] diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index 166366379..5393fe113 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -5,9 +5,7 @@ use std::rc::Weak; use crate::create_new_window; use crate::events::IpcMessage; use crate::query::QueryEngine; -use crate::shortcut::ShortcutId; -use crate::shortcut::ShortcutRegistry; -use crate::shortcut::ShortcutRegistryError; +use crate::shortcut::{ShortcutId, ShortcutRegistry, ShortcutRegistryError}; use crate::Config; use crate::WebviewHandler; use dioxus_core::ScopeState; @@ -15,7 +13,6 @@ use dioxus_core::VirtualDom; #[cfg(all(feature = "hot-reload", debug_assertions))] use dioxus_hot_reload::HotReloadMsg; use slab::Slab; -use wry::application::accelerator::Accelerator; use wry::application::event::Event; use wry::application::event_loop::EventLoopProxy; use wry::application::event_loop::EventLoopWindowTarget; @@ -233,11 +230,11 @@ impl DesktopService { /// Linux: Only works on x11. See [this issue](https://github.com/tauri-apps/tao/issues/331) for more information. pub fn create_shortcut( &self, - accelerator: Accelerator, + hotkey: global_hotkey::hotkey::HotKey, callback: impl FnMut() + 'static, ) -> Result { self.shortcut_manager - .add_shortcut(accelerator, Box::new(callback)) + .add_shortcut(hotkey, Box::new(callback)) } /// Remove a global shortcut @@ -370,11 +367,7 @@ impl WryWindowEventHandlerInner { ) { // if this event does not apply to the window this listener cares about, return match event { - Event::WindowEvent { window_id, .. } - | Event::MenuEvent { - window_id: Some(window_id), - .. - } => { + Event::WindowEvent { window_id, .. } => { if *window_id != self.window_id { return; } diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 070bfa7d1..130170b6f 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -10,15 +10,14 @@ mod escape; mod eval; mod events; mod file_upload; +#[cfg(any(target_os = "ios", target_os = "android"))] +mod mobile_shortcut; mod protocol; mod query; mod shortcut; mod waker; mod webview; -#[cfg(any(target_os = "ios", target_os = "android"))] -mod mobile_shortcut; - use crate::query::QueryResult; pub use cfg::{Config, WindowCloseBehaviour}; pub use desktop_context::DesktopContext; @@ -32,6 +31,16 @@ use dioxus_html::{native_bind::NativeFileEngine, FormData, HtmlEvent}; use element::DesktopElement; use eval::init_eval; use futures_util::{pin_mut, FutureExt}; +#[cfg(any( + target_os = "windows", + target_os = "macos", + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] +use global_hotkey::GlobalHotKeyEvent; use shortcut::ShortcutRegistry; pub use shortcut::{use_global_shortcut, ShortcutHandle, ShortcutId, ShortcutRegistryError}; use std::cell::Cell; @@ -43,10 +52,11 @@ use tao::event_loop::{EventLoopProxy, EventLoopWindowTarget}; pub use tao::window::WindowBuilder; use tao::{ event::{Event, StartCause, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, + event_loop::ControlFlow, }; pub use wry; pub use wry::application as tao; +use wry::application::event_loop::EventLoopBuilder; use wry::webview::WebView; use wry::{application::window::WindowId, webview::WebContext}; @@ -118,7 +128,7 @@ pub fn launch_cfg(root: Component, config_builder: Config) { /// } /// ``` pub fn launch_with_props(root: Component

, props: P, cfg: Config) { - let event_loop = EventLoop::::with_user_event(); + let event_loop = EventLoopBuilder::::with_user_event().build(); let proxy = event_loop.create_proxy(); @@ -155,7 +165,8 @@ pub fn launch_with_props(root: Component

, props: P, cfg: Config) let queue = WebviewQueue::default(); - let shortcut_manager = ShortcutRegistry::new(&event_loop); + let shortcut_manager = ShortcutRegistry::new(); + let global_hotkey_channel = GlobalHotKeyEvent::receiver(); // move the props into a cell so we can pop it out later to create the first window // iOS panics if we create a window before the event loop is started @@ -163,10 +174,14 @@ pub fn launch_with_props(root: Component

, props: P, cfg: Config) let cfg = Rc::new(Cell::new(Some(cfg))); event_loop.run(move |window_event, event_loop, control_flow| { - *control_flow = ControlFlow::Wait; + *control_flow = ControlFlow::Poll; event_handlers.apply_event(&window_event, event_loop); + if let Ok(event) = global_hotkey_channel.try_recv() { + shortcut_manager.call_handlers(event); + } + match window_event { Event::WindowEvent { event, window_id, .. @@ -366,7 +381,6 @@ pub fn launch_with_props(root: Component

, props: P, cfg: Config) _ => {} }, - Event::GlobalShortcutEvent(id) => shortcut_manager.call_handlers(id), _ => {} } }) diff --git a/packages/desktop/src/mobile_shortcut.rs b/packages/desktop/src/mobile_shortcut.rs index 29149b55b..b4919dfdf 100644 --- a/packages/desktop/src/mobile_shortcut.rs +++ b/packages/desktop/src/mobile_shortcut.rs @@ -4,7 +4,16 @@ use super::*; use wry::application::accelerator::Accelerator; use wry::application::event_loop::EventLoopWindowTarget; -pub struct GlobalShortcut(); +pub struct HotKey(); + +impl FromStr for HotKey { + type Err = (); + + fn from_str(s: &str) -> Result { + Ok(HotKey()) + } +} + pub struct ShortcutManager(); impl ShortcutManager { @@ -53,3 +62,13 @@ impl fmt::Display for ShortcutManagerError { } } } + +struct HotkeyError; + +struct GlobalHotKeyEvent { + id: u32, +} + +pub(crate) type Code = dioxus::prelude::Code; + +struct GlobalHotKeyManager {} diff --git a/packages/desktop/src/shortcut.rs b/packages/desktop/src/shortcut.rs index ffc64a232..9696fa520 100644 --- a/packages/desktop/src/shortcut.rs +++ b/packages/desktop/src/shortcut.rs @@ -3,39 +3,29 @@ 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 wry::application::keyboard::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}; +use global_hotkey::{ + hotkey::{Code, HotKey}, + Error as HotkeyError, GlobalHotKeyEvent, GlobalHotKeyManager, +}; #[cfg(any(target_os = "ios", target_os = "android"))] pub use crate::mobile_shortcut::*; #[derive(Clone)] pub(crate) struct ShortcutRegistry { - manager: Rc>, + manager: Rc>, shortcuts: ShortcutMap, } -type ShortcutMap = Rc>>; +type ShortcutMap = Rc>>; struct Shortcut { #[allow(unused)] - shortcut: GlobalShortcut, + shortcut: HotKey, callbacks: Slab>, } @@ -54,15 +44,15 @@ impl Shortcut { } impl ShortcutRegistry { - pub fn new(target: &EventLoopWindowTarget) -> Self { + pub fn new() -> Self { Self { - manager: Rc::new(RefCell::new(ShortcutManager::new(target))), + manager: Rc::new(RefCell::new(GlobalHotKeyManager::new().unwrap())), 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) { + pub(crate) fn call_handlers(&self, id: GlobalHotKeyEvent) { + if let Some(Shortcut { callbacks, .. }) = self.shortcuts.borrow_mut().get_mut(&id.id) { for (_, callback) in callbacks.iter_mut() { (callback)(); } @@ -71,10 +61,10 @@ impl ShortcutRegistry { pub(crate) fn add_shortcut( &self, - accelerator: Accelerator, + hotkey: global_hotkey::hotkey::HotKey, callback: Box, ) -> Result { - let accelerator_id = accelerator.clone().id(); + let accelerator_id = hotkey.clone().id(); let mut shortcuts = self.shortcuts.borrow_mut(); Ok( if let Some(callbacks) = shortcuts.get_mut(&accelerator_id) { @@ -84,12 +74,12 @@ impl ShortcutRegistry { number: id, } } else { - match self.manager.borrow_mut().register(accelerator) { - Ok(global_shortcut) => { + match self.manager.borrow_mut().register(hotkey) { + Ok(_) => { let mut slab = Slab::new(); let id = slab.insert(callback); let shortcut = Shortcut { - shortcut: global_shortcut, + shortcut: hotkey, callbacks: slab, }; shortcuts.insert(accelerator_id, shortcut); @@ -98,7 +88,7 @@ impl ShortcutRegistry { number: id, } } - Err(ShortcutManagerError::InvalidAccelerator(shortcut)) => { + Err(HotkeyError::HotKeyParseError(shortcut)) => { return Err(ShortcutRegistryError::InvalidShortcut(shortcut)) } Err(err) => return Err(ShortcutRegistryError::Other(Box::new(err))), @@ -113,15 +103,6 @@ impl ShortcutRegistry { 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); } } @@ -130,8 +111,8 @@ impl ShortcutRegistry { pub(crate) fn remove_all(&self) { let mut shortcuts = self.shortcuts.borrow_mut(); - shortcuts.clear(); - let _ = self.manager.borrow_mut().unregister_all(); + let hotkeys: Vec<_> = shortcuts.drain().map(|(_, v)| v.shortcut).collect(); + let _ = self.manager.borrow_mut().unregister_all(&hotkeys); } } @@ -148,7 +129,7 @@ pub enum ShortcutRegistryError { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] /// An global id for a shortcut. pub struct ShortcutId { - id: AcceleratorId, + id: u32, number: usize, } @@ -160,33 +141,42 @@ pub struct ShortcutHandle { } pub trait IntoAccelerator { - fn accelerator(&self) -> Accelerator; + fn accelerator(&self) -> HotKey; } impl IntoAccelerator for (dioxus_html::KeyCode, ModifiersState) { - fn accelerator(&self) -> Accelerator { - Accelerator::new(Some(self.1), self.0.into_key_code()) + fn accelerator(&self) -> HotKey { + HotKey::new(Some(self.1.into_modifiers_state()), 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()) + fn accelerator(&self) -> HotKey { + HotKey::new(Some(self.0.into_modifiers_state()), self.1.into_key_code()) } } impl IntoAccelerator for dioxus_html::KeyCode { - fn accelerator(&self) -> Accelerator { - Accelerator::new(None, self.into_key_code()) + fn accelerator(&self) -> HotKey { + HotKey::new(None, self.into_key_code()) } } impl IntoAccelerator for &str { - fn accelerator(&self) -> Accelerator { - Accelerator::from_str(self).unwrap() + fn accelerator(&self) -> HotKey { + HotKey::from_str(self).unwrap() } } +#[cfg(any( + target_os = "windows", + target_os = "macos", + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] /// Get a closure that executes any JavaScript in the WebView context. pub fn use_global_shortcut( cx: &ScopeState, @@ -220,143 +210,144 @@ impl Drop for ShortcutHandle { } pub trait IntoModifersState { - fn into_modifiers_state(self) -> ModifiersState; + fn into_modifiers_state(self) -> Modifiers; } impl IntoModifersState for ModifiersState { - fn into_modifiers_state(self) -> ModifiersState { - self + fn into_modifiers_state(self) -> Modifiers { + let mut modifiers = Modifiers::default(); + if self.shift_key() { + modifiers |= Modifiers::SHIFT; + } + if self.control_key() { + modifiers |= Modifiers::CONTROL; + } + if self.alt_key() { + modifiers |= Modifiers::ALT; + } + if self.super_key() { + modifiers |= Modifiers::META; + } + + modifiers } } 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 + fn into_modifiers_state(self) -> Modifiers { + self } } pub trait IntoKeyCode { - fn into_key_code(self) -> KeyCode; + fn into_key_code(self) -> Code; } -impl IntoKeyCode for KeyCode { - fn into_key_code(self) -> KeyCode { +impl IntoKeyCode for Code { + fn into_key_code(self) -> Code { self } } impl IntoKeyCode for dioxus_html::KeyCode { - fn into_key_code(self) -> KeyCode { + fn into_key_code(self) -> Code { 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, + dioxus_html::KeyCode::Backspace => Code::Backspace, + dioxus_html::KeyCode::Tab => Code::Tab, + dioxus_html::KeyCode::Clear => Code::NumpadClear, + dioxus_html::KeyCode::Enter => Code::Enter, + dioxus_html::KeyCode::Shift => Code::ShiftLeft, + dioxus_html::KeyCode::Ctrl => Code::ControlLeft, + dioxus_html::KeyCode::Alt => Code::AltLeft, + dioxus_html::KeyCode::Pause => Code::Pause, + dioxus_html::KeyCode::CapsLock => Code::CapsLock, + dioxus_html::KeyCode::Escape => Code::Escape, + dioxus_html::KeyCode::Space => Code::Space, + dioxus_html::KeyCode::PageUp => Code::PageUp, + dioxus_html::KeyCode::PageDown => Code::PageDown, + dioxus_html::KeyCode::End => Code::End, + dioxus_html::KeyCode::Home => Code::Home, + dioxus_html::KeyCode::LeftArrow => Code::ArrowLeft, + dioxus_html::KeyCode::UpArrow => Code::ArrowUp, + dioxus_html::KeyCode::RightArrow => Code::ArrowRight, + dioxus_html::KeyCode::DownArrow => Code::ArrowDown, + dioxus_html::KeyCode::Insert => Code::Insert, + dioxus_html::KeyCode::Delete => Code::Delete, + dioxus_html::KeyCode::Num0 => Code::Numpad0, + dioxus_html::KeyCode::Num1 => Code::Numpad1, + dioxus_html::KeyCode::Num2 => Code::Numpad2, + dioxus_html::KeyCode::Num3 => Code::Numpad3, + dioxus_html::KeyCode::Num4 => Code::Numpad4, + dioxus_html::KeyCode::Num5 => Code::Numpad5, + dioxus_html::KeyCode::Num6 => Code::Numpad6, + dioxus_html::KeyCode::Num7 => Code::Numpad7, + dioxus_html::KeyCode::Num8 => Code::Numpad8, + dioxus_html::KeyCode::Num9 => Code::Numpad9, + dioxus_html::KeyCode::A => Code::KeyA, + dioxus_html::KeyCode::B => Code::KeyB, + dioxus_html::KeyCode::C => Code::KeyC, + dioxus_html::KeyCode::D => Code::KeyD, + dioxus_html::KeyCode::E => Code::KeyE, + dioxus_html::KeyCode::F => Code::KeyF, + dioxus_html::KeyCode::G => Code::KeyG, + dioxus_html::KeyCode::H => Code::KeyH, + dioxus_html::KeyCode::I => Code::KeyI, + dioxus_html::KeyCode::J => Code::KeyJ, + dioxus_html::KeyCode::K => Code::KeyK, + dioxus_html::KeyCode::L => Code::KeyL, + dioxus_html::KeyCode::M => Code::KeyM, + dioxus_html::KeyCode::N => Code::KeyN, + dioxus_html::KeyCode::O => Code::KeyO, + dioxus_html::KeyCode::P => Code::KeyP, + dioxus_html::KeyCode::Q => Code::KeyQ, + dioxus_html::KeyCode::R => Code::KeyR, + dioxus_html::KeyCode::S => Code::KeyS, + dioxus_html::KeyCode::T => Code::KeyT, + dioxus_html::KeyCode::U => Code::KeyU, + dioxus_html::KeyCode::V => Code::KeyV, + dioxus_html::KeyCode::W => Code::KeyW, + dioxus_html::KeyCode::X => Code::KeyX, + dioxus_html::KeyCode::Y => Code::KeyY, + dioxus_html::KeyCode::Z => Code::KeyZ, + dioxus_html::KeyCode::Numpad0 => Code::Numpad0, + dioxus_html::KeyCode::Numpad1 => Code::Numpad1, + dioxus_html::KeyCode::Numpad2 => Code::Numpad2, + dioxus_html::KeyCode::Numpad3 => Code::Numpad3, + dioxus_html::KeyCode::Numpad4 => Code::Numpad4, + dioxus_html::KeyCode::Numpad5 => Code::Numpad5, + dioxus_html::KeyCode::Numpad6 => Code::Numpad6, + dioxus_html::KeyCode::Numpad7 => Code::Numpad7, + dioxus_html::KeyCode::Numpad8 => Code::Numpad8, + dioxus_html::KeyCode::Numpad9 => Code::Numpad9, + dioxus_html::KeyCode::Multiply => Code::NumpadMultiply, + dioxus_html::KeyCode::Add => Code::NumpadAdd, + dioxus_html::KeyCode::Subtract => Code::NumpadSubtract, + dioxus_html::KeyCode::DecimalPoint => Code::NumpadDecimal, + dioxus_html::KeyCode::Divide => Code::NumpadDivide, + dioxus_html::KeyCode::F1 => Code::F1, + dioxus_html::KeyCode::F2 => Code::F2, + dioxus_html::KeyCode::F3 => Code::F3, + dioxus_html::KeyCode::F4 => Code::F4, + dioxus_html::KeyCode::F5 => Code::F5, + dioxus_html::KeyCode::F6 => Code::F6, + dioxus_html::KeyCode::F7 => Code::F7, + dioxus_html::KeyCode::F8 => Code::F8, + dioxus_html::KeyCode::F9 => Code::F9, + dioxus_html::KeyCode::F10 => Code::F10, + dioxus_html::KeyCode::F11 => Code::F11, + dioxus_html::KeyCode::F12 => Code::F12, + dioxus_html::KeyCode::NumLock => Code::NumLock, + dioxus_html::KeyCode::ScrollLock => Code::ScrollLock, + dioxus_html::KeyCode::Semicolon => Code::Semicolon, + dioxus_html::KeyCode::EqualSign => Code::Equal, + dioxus_html::KeyCode::Comma => Code::Comma, + dioxus_html::KeyCode::Period => Code::Period, + dioxus_html::KeyCode::ForwardSlash => Code::Slash, + dioxus_html::KeyCode::GraveAccent => Code::Backquote, + dioxus_html::KeyCode::OpenBracket => Code::BracketLeft, + dioxus_html::KeyCode::BackSlash => Code::Backslash, + dioxus_html::KeyCode::CloseBraket => Code::BracketRight, + dioxus_html::KeyCode::SingleQuote => Code::Quote, key => panic!("Failed to convert {:?} to tao::keyboard::KeyCode, try using tao::keyboard::KeyCode directly", key), } } From d1575b40d1648768ce80e621e1c75f6ffcd428b8 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 4 Sep 2023 08:24:38 -0500 Subject: [PATCH 2/9] fix mobile build --- packages/desktop/Cargo.toml | 1 + packages/desktop/src/desktop_context.rs | 4 +- packages/desktop/src/lib.rs | 11 +--- packages/desktop/src/mobile_shortcut.rs | 69 +++++++++++++++---------- packages/desktop/src/shortcut.rs | 22 ++++---- 5 files changed, 57 insertions(+), 50 deletions(-) diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index 311e224bb..a17b52b1a 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -37,6 +37,7 @@ slab = { workspace = true } futures-util = { workspace = true } urlencoding = "2.1.2" async-trait = "0.1.68" +crossbeam-channel = "0.5.8" [target.'cfg(any(target_os = "windows",target_os = "macos",target_os = "linux",target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))'.dependencies] diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index 5393fe113..c5f9f5ca5 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -5,7 +5,7 @@ use std::rc::Weak; use crate::create_new_window; use crate::events::IpcMessage; use crate::query::QueryEngine; -use crate::shortcut::{ShortcutId, ShortcutRegistry, ShortcutRegistryError}; +use crate::shortcut::{HotKey, ShortcutId, ShortcutRegistry, ShortcutRegistryError}; use crate::Config; use crate::WebviewHandler; use dioxus_core::ScopeState; @@ -230,7 +230,7 @@ impl DesktopService { /// Linux: Only works on x11. See [this issue](https://github.com/tauri-apps/tao/issues/331) for more information. pub fn create_shortcut( &self, - hotkey: global_hotkey::hotkey::HotKey, + hotkey: HotKey, callback: impl FnMut() + 'static, ) -> Result { self.shortcut_manager diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 130170b6f..a96cd5abf 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -19,6 +19,7 @@ mod waker; mod webview; use crate::query::QueryResult; +use crate::shortcut::GlobalHotKeyEvent; pub use cfg::{Config, WindowCloseBehaviour}; pub use desktop_context::DesktopContext; pub use desktop_context::{ @@ -31,16 +32,6 @@ use dioxus_html::{native_bind::NativeFileEngine, FormData, HtmlEvent}; use element::DesktopElement; use eval::init_eval; use futures_util::{pin_mut, FutureExt}; -#[cfg(any( - target_os = "windows", - target_os = "macos", - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" -))] -use global_hotkey::GlobalHotKeyEvent; use shortcut::ShortcutRegistry; pub use shortcut::{use_global_shortcut, ShortcutHandle, ShortcutId, ShortcutRegistryError}; use std::cell::Cell; diff --git a/packages/desktop/src/mobile_shortcut.rs b/packages/desktop/src/mobile_shortcut.rs index b4919dfdf..c6144344b 100644 --- a/packages/desktop/src/mobile_shortcut.rs +++ b/packages/desktop/src/mobile_shortcut.rs @@ -1,38 +1,51 @@ #![allow(unused)] use super::*; -use wry::application::accelerator::Accelerator; +use std::str::FromStr; use wry::application::event_loop::EventLoopWindowTarget; -pub struct HotKey(); +use dioxus_html::input_data::keyboard_types::Modifiers; + +#[derive(Clone, Debug)] +pub struct Accelerator; + +#[derive(Clone, Copy)] +pub struct HotKey; + +impl HotKey { + pub fn new(mods: Option, key: Code) -> Self { + Self + } + + pub fn id(&self) -> u32 { + 0 + } +} impl FromStr for HotKey { type Err = (); fn from_str(s: &str) -> Result { - Ok(HotKey()) + Ok(HotKey) } } -pub struct ShortcutManager(); +pub struct GlobalHotKeyManager(); -impl ShortcutManager { - pub fn new(target: &EventLoopWindowTarget) -> Self { - Self() +impl GlobalHotKeyManager { + pub fn new() -> Result { + Ok(Self()) } - pub fn register( - &mut self, - accelerator: Accelerator, - ) -> Result { - Ok(GlobalShortcut()) + pub fn register(&mut self, accelerator: HotKey) -> Result { + Ok(HotKey) } - pub fn unregister(&mut self, id: ShortcutId) -> Result<(), ShortcutManagerError> { + pub fn unregister(&mut self, id: HotKey) -> Result<(), HotkeyError> { Ok(()) } - pub fn unregister_all(&mut self) -> Result<(), ShortcutManagerError> { + pub fn unregister_all(&mut self, _: &[HotKey]) -> Result<(), HotkeyError> { Ok(()) } } @@ -42,33 +55,35 @@ use std::{error, fmt}; /// An error whose cause the `ShortcutManager` to fail. #[non_exhaustive] #[derive(Debug)] -pub enum ShortcutManagerError { +pub enum HotkeyError { AcceleratorAlreadyRegistered(Accelerator), AcceleratorNotRegistered(Accelerator), - InvalidAccelerator(String), + HotKeyParseError(String), } -impl error::Error for ShortcutManagerError {} -impl fmt::Display for ShortcutManagerError { +impl error::Error for HotkeyError {} +impl fmt::Display for HotkeyError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { match self { - ShortcutManagerError::AcceleratorAlreadyRegistered(e) => { + HotkeyError::AcceleratorAlreadyRegistered(e) => { f.pad(&format!("hotkey already registered: {:?}", e)) } - ShortcutManagerError::AcceleratorNotRegistered(e) => { + HotkeyError::AcceleratorNotRegistered(e) => { f.pad(&format!("hotkey not registered: {:?}", e)) } - ShortcutManagerError::InvalidAccelerator(e) => e.fmt(f), + HotkeyError::HotKeyParseError(e) => e.fmt(f), } } } -struct HotkeyError; - -struct GlobalHotKeyEvent { - id: u32, +pub struct GlobalHotKeyEvent { + pub id: u32, } -pub(crate) type Code = dioxus::prelude::Code; +impl GlobalHotKeyEvent { + pub fn receiver() -> crossbeam_channel::Receiver { + crossbeam_channel::unbounded().1 + } +} -struct GlobalHotKeyManager {} +pub(crate) type Code = dioxus_html::input_data::keyboard_types::Code; diff --git a/packages/desktop/src/shortcut.rs b/packages/desktop/src/shortcut.rs index 9696fa520..66ab4ba7a 100644 --- a/packages/desktop/src/shortcut.rs +++ b/packages/desktop/src/shortcut.rs @@ -7,7 +7,16 @@ use wry::application::keyboard::ModifiersState; use crate::{desktop_context::DesktopContext, use_window}; -use global_hotkey::{ +#[cfg(any( + target_os = "windows", + target_os = "macos", + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] +pub use global_hotkey::{ hotkey::{Code, HotKey}, Error as HotkeyError, GlobalHotKeyEvent, GlobalHotKeyManager, }; @@ -61,7 +70,7 @@ impl ShortcutRegistry { pub(crate) fn add_shortcut( &self, - hotkey: global_hotkey::hotkey::HotKey, + hotkey: HotKey, callback: Box, ) -> Result { let accelerator_id = hotkey.clone().id(); @@ -168,15 +177,6 @@ impl IntoAccelerator for &str { } } -#[cfg(any( - target_os = "windows", - target_os = "macos", - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" -))] /// Get a closure that executes any JavaScript in the WebView context. pub fn use_global_shortcut( cx: &ScopeState, From e86c1647d835b72688279603af16cffada43c13a Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 4 Sep 2023 08:33:56 -0500 Subject: [PATCH 3/9] fix clippy --- packages/core/src/diff.rs | 3 +-- packages/desktop/src/desktop_context.rs | 9 +++------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index dd4cf8147..58fceaa48 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -577,8 +577,7 @@ impl<'b> VirtualDom { } // 4. Compute the LIS of this list - let mut lis_sequence = Vec::default(); - lis_sequence.reserve(new_index_to_old_index.len()); + let mut lis_sequence = Vec::with_capacity(new_index_to_old_index.len()); let mut predecessors = vec![0; new_index_to_old_index.len()]; let mut starts = vec![0; new_index_to_old_index.len()]; diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index c5f9f5ca5..d2c29f676 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -366,13 +366,10 @@ impl WryWindowEventHandlerInner { target: &EventLoopWindowTarget, ) { // if this event does not apply to the window this listener cares about, return - match event { - Event::WindowEvent { window_id, .. } => { - if *window_id != self.window_id { - return; - } + if let Event::WindowEvent { window_id, .. } = event { + if *window_id != self.window_id { + return; } - _ => (), } (self.handler)(event, target) } From 5fdff4b7ed1d2ea5b6af9ff98261f96c2cdb6578 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 31 Oct 2023 13:49:29 -0500 Subject: [PATCH 4/9] bump wry to 0.34 --- examples/mobile_demo/Cargo.toml | 2 +- packages/desktop/Cargo.toml | 6 ++---- packages/desktop/src/webview.rs | 33 +++++++++++++++++++++++++++++---- packages/html/Cargo.toml | 2 +- 4 files changed, 33 insertions(+), 10 deletions(-) diff --git a/examples/mobile_demo/Cargo.toml b/examples/mobile_demo/Cargo.toml index ba2af2312..a90ff4f36 100644 --- a/examples/mobile_demo/Cargo.toml +++ b/examples/mobile_demo/Cargo.toml @@ -35,7 +35,7 @@ frameworks = ["WebKit"] [dependencies] anyhow = "1.0.56" log = "0.4.11" -wry = "0.31.0" +wry = "0.34.0" dioxus = { path = "../../packages/dioxus" } dioxus-desktop = { path = "../../packages/desktop", features = [ "tokio_runtime", diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index e9b95e573..31324e728 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -19,7 +19,7 @@ serde = "1.0.136" serde_json = "1.0.79" thiserror = { workspace = true } tracing = { workspace = true } -wry = { version = "0.31.0", default-features = false, features = ["protocol", "file-drop"] } +wry = { version = "0.34.0", default-features = false, features = ["tao", "protocol", "file-drop"] } futures-channel = { workspace = true } tokio = { workspace = true, features = [ "sync", @@ -41,7 +41,7 @@ crossbeam-channel = "0.5.8" [target.'cfg(any(target_os = "windows",target_os = "macos",target_os = "linux",target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))'.dependencies] -rfd = "0.11.3" +rfd = "0.12" global-hotkey = { git = "https://github.com/tauri-apps/global-hotkey" } [target.'cfg(target_os = "ios")'.dependencies] @@ -58,8 +58,6 @@ tokio_runtime = ["tokio"] fullscreen = ["wry/fullscreen"] transparent = ["wry/transparent"] devtools = ["wry/devtools"] -tray = ["wry/tray"] -dox = ["wry/dox"] hot-reload = ["dioxus-hot-reload"] [package.metadata.docs.rs] diff --git a/packages/desktop/src/webview.rs b/packages/desktop/src/webview.rs index 6e6d26b17..cffb6f6b5 100644 --- a/packages/desktop/src/webview.rs +++ b/packages/desktop/src/webview.rs @@ -5,6 +5,7 @@ use tao::event_loop::{EventLoopProxy, EventLoopWindowTarget}; pub use wry; pub use wry::application as tao; use wry::application::window::Window; +use wry::http::Response; use wry::webview::{WebContext, WebView, WebViewBuilder}; pub fn build( @@ -44,9 +45,24 @@ pub fn build( _ = proxy.send_event(UserWindowEvent(EventData::Ipc(message), window.id())); } }) - .with_custom_protocol(String::from("dioxus"), move |r| { - protocol::desktop_handler(r, custom_head.clone(), index_file.clone(), &root_name) - }) + .with_custom_protocol( + String::from("dioxus"), + move |r| match protocol::desktop_handler( + &r, + custom_head.clone(), + index_file.clone(), + &root_name, + ) { + Ok(response) => response, + Err(err) => { + tracing::error!("Error: {}", err); + Response::builder() + .status(500) + .body(err.to_string().into_bytes().into()) + .unwrap() + } + }, + ) .with_file_drop_handler(move |window, evet| { file_handler .as_ref() @@ -71,7 +87,16 @@ pub fn build( // .with_web_context(&mut web_context); for (name, handler) in cfg.protocols.drain(..) { - webview = webview.with_custom_protocol(name, handler) + webview = webview.with_custom_protocol(name, move |r| match handler(&r) { + Ok(response) => response, + Err(err) => { + tracing::error!("Error: {}", err); + Response::builder() + .status(500) + .body(err.to_string().into_bytes().into()) + .unwrap() + } + }) } if cfg.disable_context_menu { diff --git a/packages/html/Cargo.toml b/packages/html/Cargo.toml index 5644b440c..39b5455f2 100644 --- a/packages/html/Cargo.toml +++ b/packages/html/Cargo.toml @@ -21,7 +21,7 @@ keyboard-types = "0.7" async-trait = "0.1.58" serde-value = "0.7.0" tokio = { workspace = true, features = ["fs", "io-util"], optional = true } -rfd = { version = "0.11.3", optional = true } +rfd = { version = "0.12", optional = true } async-channel = "1.8.0" serde_json = { version = "1", optional = true } From d5ec22a26f3148f12712e2ff1a4ea4849ddc12f6 Mon Sep 17 00:00:00 2001 From: Will Crichton Date: Fri, 15 Dec 2023 11:16:25 -0800 Subject: [PATCH 5/9] Add custom asynchronous asset handlers --- packages/desktop/src/desktop_context.rs | 23 ++++ packages/desktop/src/lib.rs | 5 +- packages/desktop/src/protocol.rs | 143 +++++++++++++++++++++++- packages/desktop/src/webview.rs | 42 ++++--- 4 files changed, 194 insertions(+), 19 deletions(-) diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index a48068d84..7f4ef9d1d 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -1,11 +1,18 @@ use std::cell::RefCell; +use std::future::Future; +use std::path::Path; use std::rc::Rc; use std::rc::Weak; use crate::create_new_window; use crate::events::IpcMessage; +use crate::protocol::AssetFuture; +use crate::protocol::AssetHandlerId; +use crate::protocol::AssetHandlerRegistry; +use crate::protocol::AssetResponse; use crate::query::QueryEngine; use crate::shortcut::{HotKey, ShortcutId, ShortcutRegistry, ShortcutRegistryError}; +use crate::AssetHandler; use crate::Config; use crate::WebviewHandler; use dioxus_core::ScopeState; @@ -64,6 +71,8 @@ pub struct DesktopService { pub(crate) shortcut_manager: ShortcutRegistry, + pub(crate) asset_handlers: AssetHandlerRegistry, + #[cfg(target_os = "ios")] pub(crate) views: Rc>>, } @@ -88,6 +97,7 @@ impl DesktopService { webviews: WebviewQueue, event_handlers: WindowEventHandlers, shortcut_manager: ShortcutRegistry, + asset_handlers: AssetHandlerRegistry, ) -> Self { Self { webview: Rc::new(webview), @@ -97,6 +107,7 @@ impl DesktopService { pending_windows: webviews, event_handlers, shortcut_manager, + asset_handlers, #[cfg(target_os = "ios")] views: Default::default(), } @@ -247,6 +258,18 @@ impl DesktopService { self.shortcut_manager.remove_all() } + /// Provide a callback to handle asset loading yourself. + /// + /// See [`use_asset_handle`](crate::use_asset_handle) for a convenient hook. + pub async fn register_asset_handler(&self, f: impl AssetHandler) { + self.asset_handlers.register_handler(f).await; + } + + /// Removes an asset handler by its identifier. + pub async fn remove_asset_handler(&self, id: AssetHandlerId) { + self.asset_handlers.remove_handler(id).await; + } + /// Push an objc view to the window #[cfg(target_os = "ios")] pub fn push_view(&self, view: objc_id::ShareId) { diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index d6d5ef4a5..f8155c333 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -32,6 +32,7 @@ use dioxus_html::{native_bind::NativeFileEngine, FormData, HtmlEvent}; use element::DesktopElement; use eval::init_eval; use futures_util::{pin_mut, FutureExt}; +pub use protocol::{use_asset_handler, AssetFuture, AssetHandler, AssetHandlerId, AssetResponse}; use shortcut::ShortcutRegistry; pub use shortcut::{use_global_shortcut, ShortcutHandle, ShortcutId, ShortcutRegistryError}; use std::cell::Cell; @@ -393,7 +394,8 @@ fn create_new_window( event_handlers: &WindowEventHandlers, shortcut_manager: ShortcutRegistry, ) -> WebviewHandler { - let (webview, web_context) = webview::build(&mut cfg, event_loop, proxy.clone()); + let (webview, web_context, asset_handlers) = + webview::build(&mut cfg, event_loop, proxy.clone()); let desktop_context = Rc::from(DesktopService::new( webview, proxy.clone(), @@ -401,6 +403,7 @@ fn create_new_window( queue.clone(), event_handlers.clone(), shortcut_manager, + asset_handlers, )); let cx = dom.base_scope(); diff --git a/packages/desktop/src/protocol.rs b/packages/desktop/src/protocol.rs index 2f660b7a2..774c0900a 100644 --- a/packages/desktop/src/protocol.rs +++ b/packages/desktop/src/protocol.rs @@ -1,13 +1,25 @@ +use dioxus_core::ScopeState; use dioxus_interpreter_js::{COMMON_JS, INTERPRETER_JS}; use std::{ borrow::Cow, + collections::HashMap, + future::Future, path::{Path, PathBuf}, + pin::Pin, + rc::Rc, + sync::Arc, +}; +use tokio::{ + runtime::Handle, + sync::{OnceCell, RwLock}, }; use wry::{ http::{status::StatusCode, Request, Response}, Result, }; +use crate::{use_window, DesktopContext}; + fn module_loader(root_name: &str) -> String { let js = INTERPRETER_JS.replace( "/*POST_HANDLE_EDITS*/", @@ -51,12 +63,132 @@ fn module_loader(root_name: &str) -> String { ) } -pub(super) fn desktop_handler( +/// An arbitrary asset is an HTTP response containing a binary body. +pub type AssetResponse = Response>; + +/// A future that returns an [`AssetResponse`]. This future may be spawned in a new thread, +/// so it must be [`Send`], [`Sync`], and `'static`. +pub trait AssetFuture: Future> + Send + Sync + 'static {} +impl> + Send + Sync + 'static> AssetFuture for T {} + +/// A handler that takes an asset [`Path`] and returns a future that loads the path. +/// This handler is stashed indefinitely in a context object, so it must be `'static`. +pub trait AssetHandler: Fn(&Path) -> F + Send + Sync + 'static {} +impl F + Send + Sync + 'static> AssetHandler for T {} + +/// An identifier for a registered asset handler, returned by [`AssetHandlerRegistry::register_handler`]. +#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)] +pub struct AssetHandlerId(usize); + +struct AssetHandlerRegistryInner { + handlers: HashMap< + AssetHandlerId, + Box Pin> + Send + Sync + 'static>, + >, + counter: AssetHandlerId, +} + +#[derive(Clone)] +pub struct AssetHandlerRegistry(Arc>); + +impl AssetHandlerRegistry { + pub fn new() -> Self { + AssetHandlerRegistry(Arc::new(RwLock::new(AssetHandlerRegistryInner { + handlers: HashMap::new(), + counter: AssetHandlerId(0), + }))) + } + + pub async fn register_handler( + &self, + f: impl AssetHandler, + ) -> AssetHandlerId { + let mut registry = self.0.write().await; + let id = registry.counter; + registry + .handlers + .insert(id, Box::new(move |path| Box::pin(f(path)))); + registry.counter.0 += 1; + id + } + + pub async fn remove_handler(&self, id: AssetHandlerId) -> Option<()> { + let mut registry = self.0.write().await; + registry.handlers.remove(&id).map(|_| ()) + } + + pub async fn try_handlers(&self, path: &Path) -> Option { + let registry = self.0.read().await; + for handler in registry.handlers.values() { + if let Some(response) = handler(path).await { + return Some(response); + } + } + None + } +} + +/// A handle to a registered asset handler. +pub struct AssetHandlerHandle { + desktop: DesktopContext, + handler_id: Rc>, +} + +impl AssetHandlerHandle { + /// Returns the [`AssetHandlerId`] for this handle. + /// + /// Because registering an ID is asynchronous, this may return `None` if the + /// registration has not completed yet. + pub fn handler_id(&self) -> Option { + self.handler_id.get().copied() + } +} + +impl Drop for AssetHandlerHandle { + fn drop(&mut self) { + let cell = Rc::clone(&self.handler_id); + let desktop = Rc::clone(&self.desktop); + tokio::task::block_in_place(move || { + Handle::current().block_on(async move { + if let Some(id) = cell.get() { + desktop.asset_handlers.remove_handler(*id).await; + } + }) + }); + } +} + +/// Provide a callback to handle asset loading yourself. +/// +/// The callback takes a path as requested by the web view, and it should return `Some(response)` +/// if you want to load the asset, and `None` if you want to fallback on the default behavior. +pub fn use_asset_handler( + cx: &ScopeState, + handler: impl AssetHandler, +) -> &AssetHandlerHandle { + let desktop = Rc::clone(&use_window(cx)); + cx.use_hook(|| { + let handler_id = Rc::new(OnceCell::new()); + let handler_id_ref = Rc::clone(&handler_id); + let desktop_ref = Rc::clone(&desktop); + cx.push_future(async move { + let id = desktop.asset_handlers.register_handler(handler).await; + handler_id.set(id).unwrap(); + }); + AssetHandlerHandle { + desktop: desktop_ref, + handler_id: handler_id_ref, + } + }) +} + +pub(super) async fn desktop_handler( request: &Request>, custom_head: Option, custom_index: Option, root_name: &str, -) -> Result>> { + asset_handlers: &AssetHandlerRegistry, +) -> Result { // If the request is for the root, we'll serve the index.html file. if request.uri().path() == "/" { // If a custom index is provided, just defer to that, expecting the user to know what they're doing. @@ -96,6 +228,13 @@ pub(super) fn desktop_handler( .expect("expected URL to be UTF-8 encoded"); let path = PathBuf::from(&*decoded); + // If the user provided a custom asset handler, then call it and return the response + // if the request was handled. + if let Some(response) = asset_handlers.try_handlers(&path).await { + return Ok(response); + } + + // Else, try to serve a file from the filesystem. // If the path is relative, we'll try to serve it from the assets directory. let mut asset = get_asset_root() .unwrap_or_else(|| Path::new(".").to_path_buf()) diff --git a/packages/desktop/src/webview.rs b/packages/desktop/src/webview.rs index cffb6f6b5..e2179352f 100644 --- a/packages/desktop/src/webview.rs +++ b/packages/desktop/src/webview.rs @@ -1,5 +1,7 @@ +use std::sync::Arc; + use crate::desktop_context::EventData; -use crate::protocol; +use crate::protocol::{self, AssetHandlerRegistry}; use crate::{desktop_context::UserWindowEvent, Config}; use tao::event_loop::{EventLoopProxy, EventLoopWindowTarget}; pub use wry; @@ -12,7 +14,7 @@ pub fn build( cfg: &mut Config, event_loop: &EventLoopWindowTarget, proxy: EventLoopProxy, -) -> (WebView, WebContext) { +) -> (WebView, WebContext, AssetHandlerRegistry) { let builder = cfg.window.clone(); let window = builder.with_visible(false).build(event_loop).unwrap(); let file_handler = cfg.file_drop_handler.take(); @@ -33,6 +35,8 @@ pub fn build( } let mut web_context = WebContext::new(cfg.data_dir.clone()); + let asset_handlers = AssetHandlerRegistry::new(); + let asset_handlers_ref = asset_handlers.clone(); let mut webview = WebViewBuilder::new(window) .unwrap() @@ -45,24 +49,30 @@ pub fn build( _ = proxy.send_event(UserWindowEvent(EventData::Ipc(message), window.id())); } }) - .with_custom_protocol( - String::from("dioxus"), - move |r| match protocol::desktop_handler( - &r, - custom_head.clone(), - index_file.clone(), - &root_name, - ) { - Ok(response) => response, - Err(err) => { + .with_asynchronous_custom_protocol(String::from("dioxus"), move |request, responder| { + let custom_head = custom_head.clone(); + let index_file = index_file.clone(); + let root_name = root_name.clone(); + let asset_handlers_ref = asset_handlers_ref.clone(); + tokio::spawn(async move { + let response_res = protocol::desktop_handler( + &request, + custom_head.clone(), + index_file.clone(), + &root_name, + &asset_handlers_ref, + ) + .await; + let response = response_res.unwrap_or_else(|err| { tracing::error!("Error: {}", err); Response::builder() .status(500) .body(err.to_string().into_bytes().into()) .unwrap() - } - }, - ) + }); + responder.respond(response); + }); + }) .with_file_drop_handler(move |window, evet| { file_handler .as_ref() @@ -119,5 +129,5 @@ pub fn build( webview = webview.with_devtools(true); } - (webview.build().unwrap(), web_context) + (webview.build().unwrap(), web_context, asset_handlers) } From 7bede0be59c9e48d55342c9f01978c34f8cc3a53 Mon Sep 17 00:00:00 2001 From: Will Crichton Date: Fri, 15 Dec 2023 11:30:19 -0800 Subject: [PATCH 6/9] Remove unused imports --- packages/desktop/src/desktop_context.rs | 3 --- packages/desktop/src/webview.rs | 2 -- 2 files changed, 5 deletions(-) diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index 7f4ef9d1d..cab3b4589 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -1,6 +1,4 @@ use std::cell::RefCell; -use std::future::Future; -use std::path::Path; use std::rc::Rc; use std::rc::Weak; @@ -9,7 +7,6 @@ use crate::events::IpcMessage; use crate::protocol::AssetFuture; use crate::protocol::AssetHandlerId; use crate::protocol::AssetHandlerRegistry; -use crate::protocol::AssetResponse; use crate::query::QueryEngine; use crate::shortcut::{HotKey, ShortcutId, ShortcutRegistry, ShortcutRegistryError}; use crate::AssetHandler; diff --git a/packages/desktop/src/webview.rs b/packages/desktop/src/webview.rs index e2179352f..1b13216c7 100644 --- a/packages/desktop/src/webview.rs +++ b/packages/desktop/src/webview.rs @@ -1,5 +1,3 @@ -use std::sync::Arc; - use crate::desktop_context::EventData; use crate::protocol::{self, AssetHandlerRegistry}; use crate::{desktop_context::UserWindowEvent, Config}; From 1002bc955b5f569eb9e74da90b62b16c45d43bc6 Mon Sep 17 00:00:00 2001 From: Will Crichton Date: Sat, 16 Dec 2023 21:44:39 -0800 Subject: [PATCH 7/9] Replace AssetHandlerId with slab and usize --- packages/desktop/src/desktop_context.rs | 11 +++--- packages/desktop/src/lib.rs | 2 +- packages/desktop/src/protocol.rs | 45 +++++++------------------ 3 files changed, 20 insertions(+), 38 deletions(-) diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index cab3b4589..fb0615900 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -5,7 +5,6 @@ use std::rc::Weak; use crate::create_new_window; use crate::events::IpcMessage; use crate::protocol::AssetFuture; -use crate::protocol::AssetHandlerId; use crate::protocol::AssetHandlerRegistry; use crate::query::QueryEngine; use crate::shortcut::{HotKey, ShortcutId, ShortcutRegistry, ShortcutRegistryError}; @@ -258,13 +257,15 @@ impl DesktopService { /// Provide a callback to handle asset loading yourself. /// /// See [`use_asset_handle`](crate::use_asset_handle) for a convenient hook. - pub async fn register_asset_handler(&self, f: impl AssetHandler) { - self.asset_handlers.register_handler(f).await; + pub async fn register_asset_handler(&self, f: impl AssetHandler) -> usize { + self.asset_handlers.register_handler(f).await } /// Removes an asset handler by its identifier. - pub async fn remove_asset_handler(&self, id: AssetHandlerId) { - self.asset_handlers.remove_handler(id).await; + /// + /// Returns `None` if the handler did not exist. + pub async fn remove_asset_handler(&self, id: usize) -> Option<()> { + self.asset_handlers.remove_handler(id).await } /// Push an objc view to the window diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index f8155c333..515c26009 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -32,7 +32,7 @@ use dioxus_html::{native_bind::NativeFileEngine, FormData, HtmlEvent}; use element::DesktopElement; use eval::init_eval; use futures_util::{pin_mut, FutureExt}; -pub use protocol::{use_asset_handler, AssetFuture, AssetHandler, AssetHandlerId, AssetResponse}; +pub use protocol::{use_asset_handler, AssetFuture, AssetHandler, AssetResponse}; use shortcut::ShortcutRegistry; pub use shortcut::{use_global_shortcut, ShortcutHandle, ShortcutId, ShortcutRegistryError}; use std::cell::Cell; diff --git a/packages/desktop/src/protocol.rs b/packages/desktop/src/protocol.rs index 774c0900a..cb21245f8 100644 --- a/packages/desktop/src/protocol.rs +++ b/packages/desktop/src/protocol.rs @@ -1,5 +1,6 @@ use dioxus_core::ScopeState; use dioxus_interpreter_js::{COMMON_JS, INTERPRETER_JS}; +use slab::Slab; use std::{ borrow::Cow, collections::HashMap, @@ -76,50 +77,30 @@ impl> + Send + Sync + 'static> AssetFut pub trait AssetHandler: Fn(&Path) -> F + Send + Sync + 'static {} impl F + Send + Sync + 'static> AssetHandler for T {} -/// An identifier for a registered asset handler, returned by [`AssetHandlerRegistry::register_handler`]. -#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)] -pub struct AssetHandlerId(usize); - -struct AssetHandlerRegistryInner { - handlers: HashMap< - AssetHandlerId, - Box Pin> + Send + Sync + 'static>, - >, - counter: AssetHandlerId, -} +type AssetHandlerRegistryInner = + Slab Pin> + Send + Sync + 'static>>; #[derive(Clone)] pub struct AssetHandlerRegistry(Arc>); impl AssetHandlerRegistry { pub fn new() -> Self { - AssetHandlerRegistry(Arc::new(RwLock::new(AssetHandlerRegistryInner { - handlers: HashMap::new(), - counter: AssetHandlerId(0), - }))) + AssetHandlerRegistry(Arc::new(RwLock::new(Slab::new()))) } - pub async fn register_handler( - &self, - f: impl AssetHandler, - ) -> AssetHandlerId { + pub async fn register_handler(&self, f: impl AssetHandler) -> usize { let mut registry = self.0.write().await; - let id = registry.counter; - registry - .handlers - .insert(id, Box::new(move |path| Box::pin(f(path)))); - registry.counter.0 += 1; - id + registry.insert(Box::new(move |path| Box::pin(f(path)))) } - pub async fn remove_handler(&self, id: AssetHandlerId) -> Option<()> { + pub async fn remove_handler(&self, id: usize) -> Option<()> { let mut registry = self.0.write().await; - registry.handlers.remove(&id).map(|_| ()) + registry.try_remove(id).map(|_| ()) } pub async fn try_handlers(&self, path: &Path) -> Option { let registry = self.0.read().await; - for handler in registry.handlers.values() { + for (_, handler) in registry.iter() { if let Some(response) = handler(path).await { return Some(response); } @@ -131,15 +112,15 @@ impl AssetHandlerRegistry { /// A handle to a registered asset handler. pub struct AssetHandlerHandle { desktop: DesktopContext, - handler_id: Rc>, + handler_id: Rc>, } impl AssetHandlerHandle { - /// Returns the [`AssetHandlerId`] for this handle. + /// Returns the ID for this handle. /// /// Because registering an ID is asynchronous, this may return `None` if the /// registration has not completed yet. - pub fn handler_id(&self) -> Option { + pub fn handler_id(&self) -> Option { self.handler_id.get().copied() } } @@ -166,7 +147,7 @@ pub fn use_asset_handler( cx: &ScopeState, handler: impl AssetHandler, ) -> &AssetHandlerHandle { - let desktop = Rc::clone(&use_window(cx)); + let desktop = Rc::clone(use_window(cx)); cx.use_hook(|| { let handler_id = Rc::new(OnceCell::new()); let handler_id_ref = Rc::clone(&handler_id); From 7649ad84a14aaf920f6ac9facbbdbb5787f41a25 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 17 Dec 2023 17:14:49 -0600 Subject: [PATCH 8/9] allow asset handlers to read the whole request --- packages/desktop/src/lib.rs | 2 +- packages/desktop/src/protocol.rs | 76 +++++++++++++++++++++++++------- packages/desktop/src/webview.rs | 2 +- 3 files changed, 61 insertions(+), 19 deletions(-) diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 515c26009..18739aeda 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -32,7 +32,7 @@ use dioxus_html::{native_bind::NativeFileEngine, FormData, HtmlEvent}; use element::DesktopElement; use eval::init_eval; use futures_util::{pin_mut, FutureExt}; -pub use protocol::{use_asset_handler, AssetFuture, AssetHandler, AssetResponse}; +pub use protocol::{use_asset_handler, AssetFuture, AssetHandler, AssetRequest, AssetResponse}; use shortcut::ShortcutRegistry; pub use shortcut::{use_global_shortcut, ShortcutHandle, ShortcutId, ShortcutRegistryError}; use std::cell::Cell; diff --git a/packages/desktop/src/protocol.rs b/packages/desktop/src/protocol.rs index cb21245f8..31930da50 100644 --- a/packages/desktop/src/protocol.rs +++ b/packages/desktop/src/protocol.rs @@ -3,8 +3,8 @@ use dioxus_interpreter_js::{COMMON_JS, INTERPRETER_JS}; use slab::Slab; use std::{ borrow::Cow, - collections::HashMap, future::Future, + ops::{Deref, DerefMut}, path::{Path, PathBuf}, pin::Pin, rc::Rc, @@ -72,13 +72,57 @@ pub type AssetResponse = Response>; pub trait AssetFuture: Future> + Send + Sync + 'static {} impl> + Send + Sync + 'static> AssetFuture for T {} -/// A handler that takes an asset [`Path`] and returns a future that loads the path. +/// A request for an asset. This is a wrapper around [`Request>`] that provides methods specific to asset requests. +pub struct AssetRequest { + path: PathBuf, + request: Request>, +} + +impl AssetRequest { + /// Get the path the asset request is for + pub fn path(&self) -> &Path { + &self.path + } +} + +impl From>> for AssetRequest { + fn from(request: Request>) -> Self { + let decoded = urlencoding::decode(request.uri().path().trim_start_matches('/')) + .expect("expected URL to be UTF-8 encoded"); + let path = PathBuf::from(&*decoded); + Self { request, path } + } +} + +impl Deref for AssetRequest { + type Target = Request>; + + fn deref(&self) -> &Self::Target { + &self.request + } +} + +impl DerefMut for AssetRequest { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.request + } +} + +/// A handler that takes an [`AssetRequest`] and returns a future that either loads the asset, or returns `None`. /// This handler is stashed indefinitely in a context object, so it must be `'static`. -pub trait AssetHandler: Fn(&Path) -> F + Send + Sync + 'static {} -impl F + Send + Sync + 'static> AssetHandler for T {} +pub trait AssetHandler: Send + Sync + 'static { + /// Handle an asset request, returning a future that either loads the asset, or returns `None` + fn handle_request(&self, request: &AssetRequest) -> F; +} + +impl F + Send + Sync + 'static> AssetHandler for T { + fn handle_request(&self, request: &AssetRequest) -> F { + self(request) + } +} type AssetHandlerRegistryInner = - Slab Pin> + Send + Sync + 'static>>; + Slab Pin> + Send + Sync + 'static>>; #[derive(Clone)] pub struct AssetHandlerRegistry(Arc>); @@ -90,7 +134,7 @@ impl AssetHandlerRegistry { pub async fn register_handler(&self, f: impl AssetHandler) -> usize { let mut registry = self.0.write().await; - registry.insert(Box::new(move |path| Box::pin(f(path)))) + registry.insert(Box::new(move |req| Box::pin(f.handle_request(req)))) } pub async fn remove_handler(&self, id: usize) -> Option<()> { @@ -98,10 +142,10 @@ impl AssetHandlerRegistry { registry.try_remove(id).map(|_| ()) } - pub async fn try_handlers(&self, path: &Path) -> Option { + pub async fn try_handlers(&self, req: &AssetRequest) -> Option { let registry = self.0.read().await; for (_, handler) in registry.iter() { - if let Some(response) = handler(path).await { + if let Some(response) = handler(req).await { return Some(response); } } @@ -164,12 +208,14 @@ pub fn use_asset_handler( } pub(super) async fn desktop_handler( - request: &Request>, + request: Request>, custom_head: Option, custom_index: Option, root_name: &str, asset_handlers: &AssetHandlerRegistry, ) -> Result { + let request = AssetRequest::from(request); + // If the request is for the root, we'll serve the index.html file. if request.uri().path() == "/" { // If a custom index is provided, just defer to that, expecting the user to know what they're doing. @@ -204,25 +250,21 @@ pub(super) async fn desktop_handler( .map_err(From::from); } - // Else, try to serve a file from the filesystem. - let decoded = urlencoding::decode(request.uri().path().trim_start_matches('/')) - .expect("expected URL to be UTF-8 encoded"); - let path = PathBuf::from(&*decoded); - // If the user provided a custom asset handler, then call it and return the response // if the request was handled. - if let Some(response) = asset_handlers.try_handlers(&path).await { + if let Some(response) = asset_handlers.try_handlers(&request).await { return Ok(response); } // Else, try to serve a file from the filesystem. + // If the path is relative, we'll try to serve it from the assets directory. let mut asset = get_asset_root() .unwrap_or_else(|| Path::new(".").to_path_buf()) - .join(&path); + .join(&request.path); if !asset.exists() { - asset = PathBuf::from("/").join(path); + asset = PathBuf::from("/").join(request.path); } if asset.exists() { diff --git a/packages/desktop/src/webview.rs b/packages/desktop/src/webview.rs index 1b13216c7..0294d3460 100644 --- a/packages/desktop/src/webview.rs +++ b/packages/desktop/src/webview.rs @@ -54,7 +54,7 @@ pub fn build( let asset_handlers_ref = asset_handlers_ref.clone(); tokio::spawn(async move { let response_res = protocol::desktop_handler( - &request, + request, custom_head.clone(), index_file.clone(), &root_name, From 9eee4ab8f8ae0c58929adca2c411b84c02d82372 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 17 Dec 2023 17:14:55 -0600 Subject: [PATCH 9/9] add a dynamic asset example --- examples/dynamic_asset.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 examples/dynamic_asset.rs diff --git a/examples/dynamic_asset.rs b/examples/dynamic_asset.rs new file mode 100644 index 000000000..1c004e015 --- /dev/null +++ b/examples/dynamic_asset.rs @@ -0,0 +1,29 @@ +use dioxus::prelude::*; +use dioxus_desktop::wry::http::Response; +use dioxus_desktop::{use_asset_handler, AssetRequest}; +use std::path::Path; + +fn main() { + dioxus_desktop::launch(app); +} + +fn app(cx: Scope) -> Element { + use_asset_handler(cx, |request: &AssetRequest| { + let path = request.path().to_path_buf(); + async move { + if path != Path::new("logo.png") { + return None; + } + let image_data: &[u8] = include_bytes!("./assets/logo.png"); + Some(Response::new(image_data.into())) + } + }); + + cx.render(rsx! { + div { + img { + src: "logo.png" + } + } + }) +}