diff --git a/Cargo.lock b/Cargo.lock index 0210bf7e5..063404855 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3965,7 +3965,7 @@ dependencies = [ "jni", "lazy-js-bundle", "libc", - "muda 0.17.0", + "muda", "ndk", "ndk-context", "ndk-sys 0.6.0+11769913", @@ -8985,27 +8985,6 @@ dependencies = [ "nasm-rs", ] -[[package]] -name = "muda" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4de14a9b5d569ca68d7c891d613b390cf5ab4f851c77aaa2f9e435555d3d9492" -dependencies = [ - "crossbeam-channel", - "dpi", - "gtk", - "keyboard-types", - "libxdo", - "objc2 0.6.1", - "objc2-app-kit 0.3.1", - "objc2-core-foundation", - "objc2-foundation 0.3.1", - "once_cell", - "png", - "thiserror 2.0.12", - "windows-sys 0.59.0", -] - [[package]] name = "muda" version = "0.17.0" @@ -15177,14 +15156,14 @@ dependencies = [ [[package]] name = "tray-icon" -version = "0.20.1" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7eee98ec5c90daf179d55c20a49d8c0d043054ce7c26336c09a24d31f14fa0" +checksum = "2da75ec677957aa21f6e0b361df0daab972f13a5bee3606de0638fd4ee1c666a" dependencies = [ "crossbeam-channel", "dirs 6.0.0", "libappindicator", - "muda 0.16.1", + "muda", "objc2 0.6.1", "objc2-app-kit 0.3.1", "objc2-core-foundation", diff --git a/Cargo.toml b/Cargo.toml index 0f9c38195..aa74bfb39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -325,7 +325,7 @@ cocoa = "0.26" core-foundation = "0.10.1" objc = { version = "0.2.7", features = ["exception"] } objc_id = "0.1.1" -tray-icon = "0.20.0" +tray-icon = "0.21.0" open = "5.1.2" # web diff --git a/examples/multiwindow.rs b/examples/multiwindow.rs index 1036bb5df..4ff293735 100644 --- a/examples/multiwindow.rs +++ b/examples/multiwindow.rs @@ -5,22 +5,14 @@ //! own context, root elements, etc. use dioxus::prelude::*; -use dioxus::{desktop::Config, desktop::DefaultWindowCloseBehaviour}; fn main() { - dioxus::LaunchBuilder::desktop() - // We can choose the close behavior of the last window to hide. See DefaultWindowCloseBehaviour for more options. - .with_cfg( - Config::new() - .with_default_window_close_behaviour(DefaultWindowCloseBehaviour::LastWindowHides), - ) - .launch(app); + dioxus::launch(app); } fn app() -> Element { let onclick = move |_| { - let dom = VirtualDom::new(popup); - dioxus::desktop::window().new_window(dom, Default::default()); + dioxus::desktop::window().new_window(VirtualDom::new(popup), Default::default()); }; rsx! { diff --git a/examples/multiwindow_with_tray_icon.rs b/examples/multiwindow_with_tray_icon.rs index 3d75fa49e..4215c8da1 100644 --- a/examples/multiwindow_with_tray_icon.rs +++ b/examples/multiwindow_with_tray_icon.rs @@ -3,31 +3,37 @@ //! This example shows how to implement a simple multiwindow application and tray icon using dioxus. //! This works by spawning a new window when the user clicks a button. We have to build a new virtualdom which has its //! own context, root elements, etc. +//! +//! This is useful for apps that incorporate settings panels or persistent windows like Raycast. use dioxus::desktop::{ trayicon::{default_tray_icon, init_tray_icon}, - Config, WindowCloseBehaviour, + window, WindowCloseBehaviour, }; use dioxus::prelude::*; fn main() { - dioxus::LaunchBuilder::desktop() - // We can choose the close behavior of this window to hide. See WindowCloseBehaviour for more options. - .with_cfg(Config::new().with_window_close_behaviour(WindowCloseBehaviour::WindowHides)) - .launch(app); + dioxus::launch(app); } fn app() -> Element { - // async should not be needed, check if issue 3542 has been resolved - let onclick = move |_| async { - let dom = VirtualDom::new(popup); - dioxus::desktop::window().new_window(dom, Default::default()); - }; + use_hook(|| { + // Set the close behavior for the main window + // This will hide the window instead of closing it when the user clicks the close button + window().set_close_behavior(WindowCloseBehaviour::WindowHides); - init_tray_icon(default_tray_icon(), None); + // Initialize the tray icon with a default icon and no menu + // This will provide the tray into context for the application + init_tray_icon(default_tray_icon(), None) + }); rsx! { - button { onclick, "New Window" } + button { + onclick: move |_| { + window().new_window(VirtualDom::new(popup), Default::default()); + }, + "New Window" + } } } diff --git a/packages/desktop/src/app.rs b/packages/desktop/src/app.rs index 22b975ea4..cd91a67fe 100644 --- a/packages/desktop/src/app.rs +++ b/packages/desktop/src/app.rs @@ -1,5 +1,5 @@ use crate::{ - config::{Config, DefaultWindowCloseBehaviour, WindowCloseBehaviour}, + config::{Config, WindowCloseBehaviour}, edits::EditWebsocket, event_handlers::WindowEventHandlers, file_upload::{DesktopFileUploadForm, FileDialogRequest, NativeFileEngine}, @@ -23,7 +23,7 @@ use tao::{ dpi::PhysicalSize, event::Event, event_loop::{ControlFlow, EventLoop, EventLoopBuilder, EventLoopProxy, EventLoopWindowTarget}, - window::{Window, WindowId}, + window::WindowId, }; /// The single top-level object that manages all the running windows, assets, shortcuts, etc @@ -36,7 +36,7 @@ pub(crate) struct App { // Stuff we need mutable access to pub(crate) control_flow: ControlFlow, pub(crate) is_visible_before_start: bool, - pub(crate) default_window_close_behavior: DefaultWindowCloseBehaviour, + pub(crate) exit_on_last_window_close: bool, pub(crate) webviews: HashMap, pub(crate) float_all: bool, pub(crate) show_devtools: bool, @@ -65,7 +65,7 @@ impl App { .unwrap_or_else(|| EventLoopBuilder::::with_user_event().build()); let app = Self { - default_window_close_behavior: cfg.default_window_close_behaviour, + exit_on_last_window_close: cfg.exit_on_last_window_close, is_visible_before_start: true, webviews: HashMap::new(), control_flow: ControlFlow::Wait, @@ -188,89 +188,37 @@ impl App { } } - pub fn change_window_close_behaviour( - &mut self, - id: WindowId, - behaviour: Option, - ) { - if let Some(webview) = self.webviews.get_mut(&id) { - webview.close_behaviour = behaviour - } - } - pub fn handle_close_requested(&mut self, id: WindowId) { - use DefaultWindowCloseBehaviour::*; - use WindowCloseBehaviour::*; - - let mut remove = false; - - if let Some(webview) = self.webviews.get(&id) { - if let Some(close_behaviour) = &webview.close_behaviour { - match close_behaviour { - WindowExitsApp => { - self.control_flow = ControlFlow::Exit; - return; - } - WindowHides => { - hide_window(&webview.desktop_context.window); - return; - } - WindowCloses => { - remove = true; - } - } - } - } - - // needed in case of `default_window_close_behavior WindowsHides | LastWindowHides` since they may not remove a window on `WindowCloses` - if remove { - #[cfg(debug_assertions)] - self.persist_window_state(); - - self.webviews.remove(&id); - if matches!(self.default_window_close_behavior, LastWindowExitsApp) - && self.webviews.is_empty() - { - self.control_flow = ControlFlow::Exit - } + let Some(window) = self.webviews.get(&id) else { + // If the window is not found, we can just return return; - } + }; - match self.default_window_close_behavior { - LastWindowExitsApp => { + match window.desktop_context.close_behaviour.get() { + // If the window is just set to hide when closed, we can just hide it + WindowCloseBehaviour::WindowHides => { + window.desktop_context.window.set_visible(false); + } + + // If the window is set to close, we can remove it from the list of webviews + // If the app is set to exit when the last window closes, we should also exit the app + WindowCloseBehaviour::WindowCloses => { #[cfg(debug_assertions)] self.persist_window_state(); self.webviews.remove(&id); - if self.webviews.is_empty() { + + if self.exit_on_last_window_close && self.webviews.is_empty() { self.control_flow = ControlFlow::Exit } } - - LastWindowHides if self.webviews.len() > 1 => { - self.webviews.remove(&id); - } - - WindowsHides | LastWindowHides => { - if let Some(webview) = self.webviews.get(&id) { - hide_window(&webview.desktop_context.window); - } - } - - WindowsCloses => { - self.webviews.remove(&id); - } - } + }; } pub fn window_destroyed(&mut self, id: WindowId) { self.webviews.remove(&id); - if matches!( - self.default_window_close_behavior, - DefaultWindowCloseBehaviour::LastWindowExitsApp - ) && self.webviews.is_empty() - { + if self.exit_on_last_window_close && self.webviews.is_empty() { self.control_flow = ControlFlow::Exit } } @@ -723,41 +671,6 @@ struct PreservedWindowState { url: Option, } -/// Hides a window. -/// -/// On macOS, if we use `set_visibility(false)` on the window, it will hide the window but not show -/// it again when the user switches back to the app. `NSApplication::hide:` has the correct behaviour, -/// so we need to special case it. -#[allow(unused)] -fn hide_window(window: &Window) { - #[cfg(target_os = "windows")] - { - use tao::platform::windows::WindowExtWindows; - window.set_visible(false); - } - - #[cfg(target_os = "linux")] - { - use tao::platform::unix::WindowExtUnix; - window.set_visible(false); - } - - #[cfg(target_os = "macos")] - { - // window.set_visible(false); has the wrong behaviour on macOS - // It will hide the window but not show it again when the user switches - // back to the app. `NSApplication::hide:` has the correct behaviour - use objc::runtime::Object; - use objc::{msg_send, sel, sel_impl}; - #[allow(unexpected_cfgs)] - objc::rc::autoreleasepool(|| unsafe { - let app: *mut Object = msg_send![objc::class!(NSApplication), sharedApplication]; - let nil = std::ptr::null_mut::(); - let _: () = msg_send![app, hide: nil]; - }); - } -} - /// Return the location of a tempfile with our window state in it such that we can restore it later fn restore_file() -> std::path::PathBuf { let dir = dioxus_cli_config::session_cache_dir().unwrap_or_else(std::env::temp_dir); diff --git a/packages/desktop/src/config.rs b/packages/desktop/src/config.rs index c54443e6a..bf8c81bda 100644 --- a/packages/desktop/src/config.rs +++ b/packages/desktop/src/config.rs @@ -17,28 +17,13 @@ type CustomEventHandler = Box< ), >; -/// The closing behaviour of the application when the last window is closed, you can overwrite this behaviour for specific window with WindowCloseBehaviour. -#[derive(Copy, Clone, Eq, PartialEq)] -#[non_exhaustive] -pub enum DefaultWindowCloseBehaviour { - /// Default behaviour, closing the last window will exit the app, others will close, - LastWindowExitsApp, - /// Closing the last window will hide it, others will close - LastWindowHides, - /// Every window will hide - WindowsHides, - /// Every window will close - WindowsCloses, -} - /// The closing behaviour of specific application window. #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[non_exhaustive] pub enum WindowCloseBehaviour { - /// Closing the window will exit the app - WindowExitsApp, - /// Window will hide + /// Window will hide instead of closing WindowHides, + /// Window will close WindowCloses, } @@ -75,8 +60,8 @@ pub struct Config { pub(crate) custom_index: Option, pub(crate) root_name: String, pub(crate) background_color: Option<(u8, u8, u8, u8)>, - pub(crate) default_window_close_behaviour: DefaultWindowCloseBehaviour, - pub(crate) window_close_behaviour: Option, + pub(crate) exit_on_last_window_close: bool, + pub(crate) window_close_behavior: WindowCloseBehaviour, pub(crate) custom_event_handler: Option, pub(crate) disable_file_drop_handler: bool, } @@ -122,8 +107,8 @@ impl Config { custom_index: None, root_name: "main".to_string(), background_color: None, - default_window_close_behaviour: DefaultWindowCloseBehaviour::LastWindowExitsApp, - window_close_behaviour: None, + exit_on_last_window_close: true, + window_close_behavior: WindowCloseBehaviour::WindowCloses, custom_event_handler: None, disable_file_drop_handler: false, } @@ -185,18 +170,19 @@ impl Config { self } - /// Sets the behaviour of the application when the last window is closed. - pub fn with_default_window_close_behaviour( - mut self, - behaviour: DefaultWindowCloseBehaviour, - ) -> Self { - self.default_window_close_behaviour = behaviour; + /// When the last window is closed, the application will exit. + /// + /// This is the default behaviour. + /// + /// If the last window is hidden, the application will not exit. + pub fn with_exits_when_last_window_closes(mut self, exit: bool) -> Self { + self.exit_on_last_window_close = exit; self } /// Sets the behaviour of the application when the last window is closed. - pub fn with_window_close_behaviour(mut self, behaviour: WindowCloseBehaviour) -> Self { - self.window_close_behaviour = Some(behaviour); + pub fn with_close_behaviour(mut self, behaviour: WindowCloseBehaviour) -> Self { + self.window_close_behavior = behaviour; self } diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index b248c3846..aaf60000f 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -10,6 +10,7 @@ use crate::{ }; use dioxus_core::{prelude::Callback, VirtualDom}; use std::{ + cell::Cell, future::{Future, IntoFuture}, pin::Pin, rc::{Rc, Weak}, @@ -66,6 +67,7 @@ pub struct DesktopService { pub(super) query: QueryEngine, pub(crate) asset_handlers: AssetHandlerRegistry, pub(crate) file_hover: NativeFileHover, + pub(crate) close_behaviour: Rc>, #[cfg(target_os = "ios")] pub(crate) views: Rc>>, @@ -87,6 +89,7 @@ impl DesktopService { shared: Rc, asset_handlers: AssetHandlerRegistry, file_hover: NativeFileHover, + close_behaviour: WindowCloseBehaviour, ) -> Self { Self { window, @@ -94,6 +97,7 @@ impl DesktopService { shared, asset_handlers, file_hover, + close_behaviour: Rc::new(Cell::new(close_behaviour)), query: Default::default(), #[cfg(target_os = "ios")] views: Default::default(), @@ -165,6 +169,14 @@ impl DesktopService { self.window.set_maximized(!self.window.is_maximized()) } + /// Set the close behavior of this window + /// + /// By default, windows close when the user clicks the close button. + /// If this is set to `WindowCloseBehaviour::WindowHides`, the window will hide instead of closing. + pub fn set_close_behavior(&self, behaviour: WindowCloseBehaviour) { + self.close_behaviour.set(behaviour); + } + /// Close this window pub fn close(&self) { let _ = self @@ -181,26 +193,6 @@ impl DesktopService { .send_event(UserWindowEvent::CloseWindow(id)); } - /// Change close behaviour of this window - pub fn change_close_behaviour(&self, behaviour: Option) { - let _ = self - .shared - .proxy - .send_event(UserWindowEvent::CloseBehaviour(self.id(), behaviour)); - } - - /// Change close behaviour of a specific window, given its ID - pub fn change_window_close_behaviour( - &self, - id: WindowId, - behaviour: Option, - ) { - let _ = self - .shared - .proxy - .send_event(UserWindowEvent::CloseBehaviour(id, behaviour)); - } - /// change window to fullscreen pub fn set_fullscreen(&self, fullscreen: bool) { if let Some(handle) = &self.window.current_monitor() { diff --git a/packages/desktop/src/ipc.rs b/packages/desktop/src/ipc.rs index 8aeeeb135..9dfbf104b 100644 --- a/packages/desktop/src/ipc.rs +++ b/packages/desktop/src/ipc.rs @@ -1,8 +1,6 @@ use serde::{Deserialize, Serialize}; use tao::window::WindowId; -use crate::WindowCloseBehaviour; - #[non_exhaustive] #[derive(Debug, Clone)] pub enum UserWindowEvent { @@ -19,8 +17,6 @@ pub enum UserWindowEvent { #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] TrayMenuEvent(tray_icon::menu::MenuEvent), - CloseBehaviour(WindowId, Option), - /// Poll the virtualdom Poll(WindowId), diff --git a/packages/desktop/src/launch.rs b/packages/desktop/src/launch.rs index d0d1b7ce4..d159dac8f 100644 --- a/packages/desktop/src/launch.rs +++ b/packages/desktop/src/launch.rs @@ -103,9 +103,6 @@ pub fn launch_virtual_dom_blocking(virtual_dom: VirtualDom, mut desktop_config: IpcMethod::BrowserOpen => app.handle_browser_open(msg), IpcMethod::Other(_) => {} }, - UserWindowEvent::CloseBehaviour(window_id, window_close_behaviour) => { - app.change_window_close_behaviour(window_id, window_close_behaviour) - } }, _ => {} } diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 7c951ef48..02dc2f0e3 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -47,7 +47,7 @@ pub mod trayicon; // Public exports pub use assets::AssetRequest; -pub use config::{Config, DefaultWindowCloseBehaviour, WindowCloseBehaviour}; +pub use config::{Config, WindowCloseBehaviour}; pub use desktop_context::{ window, DesktopContext, DesktopService, PendingDesktopContext, WeakDesktopContext, }; diff --git a/packages/desktop/src/webview.rs b/packages/desktop/src/webview.rs index fa37ee3c1..d2b8cbe96 100644 --- a/packages/desktop/src/webview.rs +++ b/packages/desktop/src/webview.rs @@ -161,7 +161,6 @@ pub(crate) struct WebviewInstance { pub edits: WebviewEdits, pub desktop_context: DesktopContext, pub waker: Waker, - pub close_behaviour: Option, // Wry assumes the webcontext is alive for the lifetime of the webview. // We need to keep the webcontext alive, otherwise the webview will crash @@ -414,6 +413,7 @@ impl WebviewInstance { shared.clone(), asset_handlers, file_hover, + WindowCloseBehaviour::WindowCloses, )); // Provide the desktop context to the virtual dom and edit handler @@ -433,7 +433,6 @@ impl WebviewInstance { desktop_context, _menu: menu, _web_context: web_context, - close_behaviour: cfg.window_close_behaviour, } }