Răsfoiți Sursa

Merge pull request #1719 from willcrichton/dev

Add custom asset handler to desktop config
Jonathan Kelley 1 an în urmă
părinte
comite
73637987f3

+ 29 - 0
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"
+            }
+        }
+    })
+}

+ 1 - 1
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.34.0"
 dioxus = { path = "../../packages/dioxus" }
 dioxus-desktop = { path = "../../packages/desktop", features = [
     "tokio_runtime",

+ 4 - 4
packages/desktop/Cargo.toml

@@ -18,8 +18,8 @@ dioxus-hot-reload = { workspace = true, optional = true }
 serde = "1.0.136"
 serde_json = "1.0.79"
 thiserror = { workspace = true }
-wry = { version = "0.28.0", default-features = false, features = ["protocol", "file-drop"] }
 tracing = { workspace = true }
+wry = { version = "0.34.0", default-features = false, features = ["tao", "protocol", "file-drop"] }
 futures-channel = { workspace = true }
 tokio = { workspace = true, features = [
     "sync",
@@ -37,10 +37,12 @@ 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]
-rfd = "0.11.3"
+rfd = "0.12"
+global-hotkey = { git = "https://github.com/tauri-apps/global-hotkey" }
 
 [target.'cfg(target_os = "ios")'.dependencies]
 objc = "0.2.7"
@@ -56,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"]
 gnu = []
 

+ 27 - 16
packages/desktop/src/desktop_context.rs

@@ -4,10 +4,11 @@ use std::rc::Weak;
 
 use crate::create_new_window;
 use crate::events::IpcMessage;
+use crate::protocol::AssetFuture;
+use crate::protocol::AssetHandlerRegistry;
 use crate::query::QueryEngine;
-use crate::shortcut::ShortcutId;
-use crate::shortcut::ShortcutRegistry;
-use crate::shortcut::ShortcutRegistryError;
+use crate::shortcut::{HotKey, ShortcutId, ShortcutRegistry, ShortcutRegistryError};
+use crate::AssetHandler;
 use crate::Config;
 use crate::WebviewHandler;
 use dioxus_core::ScopeState;
@@ -15,7 +16,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;
@@ -67,6 +67,8 @@ pub struct DesktopService {
 
     pub(crate) shortcut_manager: ShortcutRegistry,
 
+    pub(crate) asset_handlers: AssetHandlerRegistry,
+
     #[cfg(target_os = "ios")]
     pub(crate) views: Rc<RefCell<Vec<*mut objc::runtime::Object>>>,
 }
@@ -91,6 +93,7 @@ impl DesktopService {
         webviews: WebviewQueue,
         event_handlers: WindowEventHandlers,
         shortcut_manager: ShortcutRegistry,
+        asset_handlers: AssetHandlerRegistry,
     ) -> Self {
         Self {
             webview: Rc::new(webview),
@@ -100,6 +103,7 @@ impl DesktopService {
             pending_windows: webviews,
             event_handlers,
             shortcut_manager,
+            asset_handlers,
             #[cfg(target_os = "ios")]
             views: Default::default(),
         }
@@ -233,11 +237,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: HotKey,
         callback: impl FnMut() + 'static,
     ) -> Result<ShortcutId, ShortcutRegistryError> {
         self.shortcut_manager
-            .add_shortcut(accelerator, Box::new(callback))
+            .add_shortcut(hotkey, Box::new(callback))
     }
 
     /// Remove a global shortcut
@@ -250,6 +254,20 @@ 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<F: AssetFuture>(&self, f: impl AssetHandler<F>) -> usize {
+        self.asset_handlers.register_handler(f).await
+    }
+
+    /// Removes an asset handler by its identifier.
+    ///
+    /// 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
     #[cfg(target_os = "ios")]
     pub fn push_view(&self, view: objc_id::ShareId<objc::runtime::Object>) {
@@ -369,17 +387,10 @@ impl WryWindowEventHandlerInner {
         target: &EventLoopWindowTarget<UserWindowEvent>,
     ) {
         // 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),
-                ..
-            } => {
-                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)
     }

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

@@ -10,16 +10,16 @@ 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;
+use crate::shortcut::GlobalHotKeyEvent;
 pub use cfg::{Config, WindowCloseBehaviour};
 pub use desktop_context::DesktopContext;
 pub use desktop_context::{
@@ -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, AssetRequest, AssetResponse};
 use shortcut::ShortcutRegistry;
 pub use shortcut::{use_global_shortcut, ShortcutHandle, ShortcutId, ShortcutRegistryError};
 use std::cell::Cell;
@@ -43,10 +44,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};
 
@@ -120,7 +122,7 @@ pub fn launch_cfg(root: Component, config_builder: Config) {
 /// }
 /// ```
 pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, cfg: Config) {
-    let event_loop = EventLoop::<UserWindowEvent>::with_user_event();
+    let event_loop = EventLoopBuilder::<UserWindowEvent>::with_user_event().build();
 
     let proxy = event_loop.create_proxy();
 
@@ -157,7 +159,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);
+    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
@@ -166,10 +169,14 @@ pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, cfg: Config)
     let mut is_visible_before_start = true;
 
     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, ..
@@ -375,7 +382,6 @@ pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, cfg: Config)
 
                 _ => {}
             },
-            Event::GlobalShortcutEvent(id) => shortcut_manager.call_handlers(id),
             _ => {}
         }
     })
@@ -390,7 +396,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(),
@@ -398,6 +405,7 @@ fn create_new_window(
         queue.clone(),
         event_handlers.clone(),
         shortcut_manager,
+        asset_handlers,
     ));
 
     let cx = dom.base_scope();

+ 54 - 20
packages/desktop/src/mobile_shortcut.rs

@@ -1,29 +1,51 @@
 #![allow(unused)]
 
 use super::*;
-use wry::application::accelerator::Accelerator;
+use std::str::FromStr;
 use wry::application::event_loop::EventLoopWindowTarget;
 
-pub struct GlobalShortcut();
-pub struct ShortcutManager();
+use dioxus_html::input_data::keyboard_types::Modifiers;
 
-impl ShortcutManager {
-    pub fn new<T>(target: &EventLoopWindowTarget<T>) -> Self {
-        Self()
+#[derive(Clone, Debug)]
+pub struct Accelerator;
+
+#[derive(Clone, Copy)]
+pub struct HotKey;
+
+impl HotKey {
+    pub fn new(mods: Option<Modifiers>, key: Code) -> Self {
+        Self
+    }
+
+    pub fn id(&self) -> u32 {
+        0
+    }
+}
+
+impl FromStr for HotKey {
+    type Err = ();
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        Ok(HotKey)
+    }
+}
+
+pub struct GlobalHotKeyManager();
+
+impl GlobalHotKeyManager {
+    pub fn new() -> Result<Self, HotkeyError> {
+        Ok(Self())
     }
 
-    pub fn register(
-        &mut self,
-        accelerator: Accelerator,
-    ) -> Result<GlobalShortcut, ShortcutManagerError> {
-        Ok(GlobalShortcut())
+    pub fn register(&mut self, accelerator: HotKey) -> Result<HotKey, HotkeyError> {
+        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(())
     }
 }
@@ -33,23 +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),
         }
     }
 }
+
+pub struct GlobalHotKeyEvent {
+    pub id: u32,
+}
+
+impl GlobalHotKeyEvent {
+    pub fn receiver() -> crossbeam_channel::Receiver<GlobalHotKeyEvent> {
+        crossbeam_channel::unbounded().1
+    }
+}
+
+pub(crate) type Code = dioxus_html::input_data::keyboard_types::Code;

+ 170 - 8
packages/desktop/src/protocol.rs

@@ -1,13 +1,26 @@
+use dioxus_core::ScopeState;
 use dioxus_interpreter_js::{COMMON_JS, INTERPRETER_JS};
+use slab::Slab;
 use std::{
     borrow::Cow,
+    future::Future,
+    ops::{Deref, DerefMut},
     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 +64,158 @@ fn module_loader(root_name: &str) -> String {
     )
 }
 
-pub(super) fn desktop_handler(
-    request: &Request<Vec<u8>>,
+/// An arbitrary asset is an HTTP response containing a binary body.
+pub type AssetResponse = Response<Cow<'static, [u8]>>;
+
+/// 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<Output = Option<AssetResponse>> + Send + Sync + 'static {}
+impl<T: Future<Output = Option<AssetResponse>> + Send + Sync + 'static> AssetFuture for T {}
+
+/// A request for an asset. This is a wrapper around [`Request<Vec<u8>>`] that provides methods specific to asset requests.
+pub struct AssetRequest {
+    path: PathBuf,
+    request: Request<Vec<u8>>,
+}
+
+impl AssetRequest {
+    /// Get the path the asset request is for
+    pub fn path(&self) -> &Path {
+        &self.path
+    }
+}
+
+impl From<Request<Vec<u8>>> for AssetRequest {
+    fn from(request: Request<Vec<u8>>) -> 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<Vec<u8>>;
+
+    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<F: AssetFuture>: 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: AssetFuture, T: Fn(&AssetRequest) -> F + Send + Sync + 'static> AssetHandler<F> for T {
+    fn handle_request(&self, request: &AssetRequest) -> F {
+        self(request)
+    }
+}
+
+type AssetHandlerRegistryInner =
+    Slab<Box<dyn Fn(&AssetRequest) -> Pin<Box<dyn AssetFuture>> + Send + Sync + 'static>>;
+
+#[derive(Clone)]
+pub struct AssetHandlerRegistry(Arc<RwLock<AssetHandlerRegistryInner>>);
+
+impl AssetHandlerRegistry {
+    pub fn new() -> Self {
+        AssetHandlerRegistry(Arc::new(RwLock::new(Slab::new())))
+    }
+
+    pub async fn register_handler<F: AssetFuture>(&self, f: impl AssetHandler<F>) -> usize {
+        let mut registry = self.0.write().await;
+        registry.insert(Box::new(move |req| Box::pin(f.handle_request(req))))
+    }
+
+    pub async fn remove_handler(&self, id: usize) -> Option<()> {
+        let mut registry = self.0.write().await;
+        registry.try_remove(id).map(|_| ())
+    }
+
+    pub async fn try_handlers(&self, req: &AssetRequest) -> Option<AssetResponse> {
+        let registry = self.0.read().await;
+        for (_, handler) in registry.iter() {
+            if let Some(response) = handler(req).await {
+                return Some(response);
+            }
+        }
+        None
+    }
+}
+
+/// A handle to a registered asset handler.
+pub struct AssetHandlerHandle {
+    desktop: DesktopContext,
+    handler_id: Rc<OnceCell<usize>>,
+}
+
+impl AssetHandlerHandle {
+    /// 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<usize> {
+        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<F: AssetFuture>(
+    cx: &ScopeState,
+    handler: impl AssetHandler<F>,
+) -> &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<Vec<u8>>,
     custom_head: Option<String>,
     custom_index: Option<String>,
     root_name: &str,
-) -> Result<Response<Cow<'static, [u8]>>> {
+    asset_handlers: &AssetHandlerRegistry,
+) -> Result<AssetResponse> {
+    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.
@@ -91,18 +250,21 @@ pub(super) fn desktop_handler(
             .map_err(From::from);
     }
 
+    // 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(&request).await {
+        return Ok(response);
+    }
+
     // 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 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() {

+ 149 - 158
packages/desktop/src/shortcut.rs

@@ -3,11 +3,7 @@ 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};
 
@@ -20,22 +16,25 @@ use crate::{desktop_context::DesktopContext, use_window};
     target_os = "netbsd",
     target_os = "openbsd"
 ))]
-use wry::application::global_shortcut::{GlobalShortcut, ShortcutManager, ShortcutManagerError};
+pub 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<RefCell<ShortcutManager>>,
+    manager: Rc<RefCell<GlobalHotKeyManager>>,
     shortcuts: ShortcutMap,
 }
 
-type ShortcutMap = Rc<RefCell<HashMap<AcceleratorId, Shortcut>>>;
+type ShortcutMap = Rc<RefCell<HashMap<u32, Shortcut>>>;
 
 struct Shortcut {
     #[allow(unused)]
-    shortcut: GlobalShortcut,
+    shortcut: HotKey,
     callbacks: Slab<Box<dyn FnMut()>>,
 }
 
@@ -54,15 +53,15 @@ impl Shortcut {
 }
 
 impl ShortcutRegistry {
-    pub fn new<T>(target: &EventLoopWindowTarget<T>) -> 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 +70,10 @@ impl ShortcutRegistry {
 
     pub(crate) fn add_shortcut(
         &self,
-        accelerator: Accelerator,
+        hotkey: HotKey,
         callback: Box<dyn FnMut()>,
     ) -> Result<ShortcutId, ShortcutRegistryError> {
-        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 +83,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 +97,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 +112,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 +120,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 +138,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,30 +150,30 @@ 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()
     }
 }
 
@@ -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
-    }
-}
-
-impl IntoModifersState for Modifiers {
-    fn into_modifiers_state(self) -> ModifiersState {
-        let mut state = ModifiersState::empty();
-        if self.contains(Modifiers::SHIFT) {
-            state |= ModifiersState::SHIFT
+    fn into_modifiers_state(self) -> Modifiers {
+        let mut modifiers = Modifiers::default();
+        if self.shift_key() {
+            modifiers |= Modifiers::SHIFT;
         }
-        if self.contains(Modifiers::CONTROL) {
-            state |= ModifiersState::CONTROL
+        if self.control_key() {
+            modifiers |= Modifiers::CONTROL;
         }
-        if self.contains(Modifiers::ALT) {
-            state |= ModifiersState::ALT
+        if self.alt_key() {
+            modifiers |= Modifiers::ALT;
         }
-        if self.contains(Modifiers::META) || self.contains(Modifiers::SUPER) {
-            state |= ModifiersState::SUPER
+        if self.super_key() {
+            modifiers |= Modifiers::META;
         }
-        state
+
+        modifiers
+    }
+}
+
+impl IntoModifersState for Modifiers {
+    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),
         }
     }

+ 39 - 6
packages/desktop/src/webview.rs

@@ -1,17 +1,18 @@
 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;
 pub use wry::application as tao;
 use wry::application::window::Window;
+use wry::http::Response;
 use wry::webview::{WebContext, WebView, WebViewBuilder};
 
 pub fn build(
     cfg: &mut Config,
     event_loop: &EventLoopWindowTarget<UserWindowEvent>,
     proxy: EventLoopProxy<UserWindowEvent>,
-) -> (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();
@@ -32,6 +33,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()
@@ -44,8 +47,29 @@ 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_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
@@ -71,7 +95,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 {
@@ -94,5 +127,5 @@ pub fn build(
         webview = webview.with_devtools(true);
     }
 
-    (webview.build().unwrap(), web_context)
+    (webview.build().unwrap(), web_context, asset_handlers)
 }

+ 1 - 1
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 }