diff --git a/.changes/config-tray-icon-tooltip.md b/.changes/config-tray-icon-tooltip.md new file mode 100644 index 000000000000..b02a90d6eed7 --- /dev/null +++ b/.changes/config-tray-icon-tooltip.md @@ -0,0 +1,5 @@ +--- +'tauri-utils': 'minor:feat' +--- + +Add option to specify a tooltip text for the tray icon in the config. diff --git a/.changes/config-tray-icon.md b/.changes/config-tray-icon.md new file mode 100644 index 000000000000..86e36cbefd75 --- /dev/null +++ b/.changes/config-tray-icon.md @@ -0,0 +1,5 @@ +--- +'tauri-utils': 'major:breaking' +--- + +`systemTray` config option has been renamed to `trayIcon`. diff --git a/.changes/runtime-create-window-handler.md b/.changes/runtime-create-window-handler.md new file mode 100644 index 000000000000..ab624f8e4503 --- /dev/null +++ b/.changes/runtime-create-window-handler.md @@ -0,0 +1,6 @@ +--- +'tauri-runtime': 'minor:breaking' +'tauri-runtime-wry': 'minor:breaking' +--- + +`Dispatch::create_window`, `Runtime::create_window` and `RuntimeHandle::create_window` has been changed to accept a 3rd parameter which is a closure that takes `RawWindow` and to be executed right after the window is created and before the webview is added to the window. diff --git a/.changes/runtime-defaultvbox.md b/.changes/runtime-defaultvbox.md new file mode 100644 index 000000000000..51c38df25354 --- /dev/null +++ b/.changes/runtime-defaultvbox.md @@ -0,0 +1,5 @@ +--- +'tauri-runtime-wry': 'minor:feat' +--- + +Add `Dispatch::default_vbox` diff --git a/.changes/runtime-menu-system-tray.md b/.changes/runtime-menu-system-tray.md new file mode 100644 index 000000000000..49102235610e --- /dev/null +++ b/.changes/runtime-menu-system-tray.md @@ -0,0 +1,6 @@ +--- +'tauri-runtime': 'major:breaking' +'tauri-runtime-wry': 'major:breaking' +--- + +System tray and menu related APIs and structs have all been removed and are now implemented in tauri outside of the runtime-space. diff --git a/.changes/runtime-new-args.md b/.changes/runtime-new-args.md new file mode 100644 index 000000000000..e5fc889aff35 --- /dev/null +++ b/.changes/runtime-new-args.md @@ -0,0 +1,6 @@ +--- +'tauri-runtime': 'minor:breaking' +'tauri-runtime-wry': 'minor:breaking' +--- + +`Runtime::new` and `Runtime::new_any_thread` now accept a `RuntimeInitArgs`. diff --git a/.changes/system-tray-feat.md b/.changes/system-tray-feat.md new file mode 100644 index 000000000000..2516e4e432da --- /dev/null +++ b/.changes/system-tray-feat.md @@ -0,0 +1,6 @@ +--- +'tauri-runtime': 'major:breaking' +'tauri-runtime-wry': 'major:breaking' +--- + +Removed `system-tray` feature flag diff --git a/.changes/tauri-app-handle-ref.md b/.changes/tauri-app-handle-ref.md new file mode 100644 index 000000000000..7afc2171ea62 --- /dev/null +++ b/.changes/tauri-app-handle-ref.md @@ -0,0 +1,5 @@ +--- +'tauri': 'major:breaking' +--- + +Changed `App::handle` and `Manager::app_handle` to return a reference to an `AppHandle` instead of an owned value. diff --git a/.changes/tauri-cleanup-before-exit.md b/.changes/tauri-cleanup-before-exit.md new file mode 100644 index 000000000000..ff99c9215f4f --- /dev/null +++ b/.changes/tauri-cleanup-before-exit.md @@ -0,0 +1,5 @@ +--- +'tauri': 'minor:feat' +--- + +Add `App::cleanup_before_exit` and `AppHandle::cleanup_before_exit` to manually call the cleanup logic. **You should always exit the tauri app immediately after this function returns and not use any tauri-related APIs.** diff --git a/.changes/tauri-defaultvbox.md b/.changes/tauri-defaultvbox.md new file mode 100644 index 000000000000..967fb66f440d --- /dev/null +++ b/.changes/tauri-defaultvbox.md @@ -0,0 +1,5 @@ +--- +'tauri': 'minor:feat' +--- + +On Linux, add `Window::default_vbox` to get a reference to the `gtk::Box` that contains the menu bar and the webview. diff --git a/.changes/tauri-libxdo-feat.md b/.changes/tauri-libxdo-feat.md new file mode 100644 index 000000000000..648376946e65 --- /dev/null +++ b/.changes/tauri-libxdo-feat.md @@ -0,0 +1,5 @@ +--- +'tauri': 'minor:feat' +--- + +Add `linux-libxdo` feature flag (disabled by default) to enable linking to `libxdo` which is used to make `Cut`, `Copy`, `Paste` and `SelectAll` native menu items work on Linux. diff --git a/.changes/tauri-menu-tray-refactor.md b/.changes/tauri-menu-tray-refactor.md new file mode 100644 index 000000000000..7c21970ce12b --- /dev/null +++ b/.changes/tauri-menu-tray-refactor.md @@ -0,0 +1,17 @@ +--- +'tauri': 'major:breaking' +--- + +The tray icon and menu have received a huge refactor with a lot of breaking changes in order to add new functionalities and improve the DX around using them and here is an overview of the changes: + +- All menu and tray types are now exported from `tauri::menu` and `tauri::tray` modules with new names so make sure to check the new types. +- Removed `tauri::Builder::system_tray`, instead you should use `tauri::tray::TrayIconBuilder` inside `tauri::Builder::setup` hook to create your tray icons. +- Changed `tauri::Builder::menu` to be a function to accomodate for new menu changes, you can passe `tauri::menu::Menu::default` to it to create a default menu. +- Renamed `tauri::Context` methods `system_tray_icon`, `tauri::Context::system_tray_icon_mut` and `tauri::Context::set_system_tray_icon` to `tauri::Context::tray_icon`, `tauri::Context::tray_icon_mut` and `tauri::Context::set_tray_icon` to be consistent with new type names. +- Added `RunEvent::MenuEvent` and `RunEvent::TrayIconEvent`. +- Added `App/AppHandle::set_menu`, `App/AppHandle::remove_menu`, `App/AppHandle::show_menu`, `App/AppHandle::hide_menu` and `App/AppHandle::menu` to access, remove, hide or show the app-wide menu that is used as the global menu on macOS and on all windows that don't have a specific menu set for it on Windows and Linux. +- Added `Window::set_menu`, `Window::remove_menu`, `Window::show_menu`, `Window::hide_menu`, `Window::is_menu_visible` and `Window::menu` to access, remove, hide or show the menu on this window. +- Added `Window::popup_menu` and `Window::popup_menu_at` to show a context menu on the window at the cursor position or at a specific position. You can also popup a context menu using `popup` and `popup_at` methods from `ContextMenu` trait which is implemented for `Menu` and `Submenu` types. +- Added `App/AppHandle::tray`, `App/AppHandle::tray_by_id`, `App/AppHandle::remove_tray` and `App/AppHandle::remove_tray_by_id` to access or remove a registered tray. +- Added `WindowBuilder/App/AppHandle::on_menu_event` to register a new menu event handler. +- Added `App/AppHandle::on_tray_icon_event` to register a new tray event handler. diff --git a/.changes/tauri-nsview.md b/.changes/tauri-nsview.md new file mode 100644 index 000000000000..e50bfb0b2ebc --- /dev/null +++ b/.changes/tauri-nsview.md @@ -0,0 +1,5 @@ +--- +'tauri': 'minor:feat' +--- + +On macOS, add `Window::ns_view` to get a pointer to the NSWindow content view. diff --git a/.changes/tauri-run_on_main_thread.md b/.changes/tauri-run_on_main_thread.md new file mode 100644 index 000000000000..a9bbc7a296c8 --- /dev/null +++ b/.changes/tauri-run_on_main_thread.md @@ -0,0 +1,5 @@ +--- +'tauri': 'minor:feat' +--- + +Expose `run_on_main_thread` method on `App` that is similar to `AppHandle::run_on_main_thread`. diff --git a/.changes/tauri-tray-icon-feat-flag.md b/.changes/tauri-tray-icon-feat-flag.md new file mode 100644 index 000000000000..7bbad7a6594a --- /dev/null +++ b/.changes/tauri-tray-icon-feat-flag.md @@ -0,0 +1,5 @@ +--- +'tauri': 'major:breaking' +--- + +Renamed `system-tray` feature flag to `tray-icon`. diff --git a/.github/workflows/lint-core.yml b/.github/workflows/lint-core.yml index 3729dff688d3..fb1251bb2ac1 100644 --- a/.github/workflows/lint-core.yml +++ b/.github/workflows/lint-core.yml @@ -50,7 +50,7 @@ jobs: clippy: - { args: '', key: 'empty' } - { - args: '--features compression,wry,isolation,custom-protocol,system-tray,test', + args: '--features compression,wry,linux-ipc-protocol,isolation,custom-protocol,tray-icon,test', key: 'all' } - { args: '--features custom-protocol', key: 'custom-protocol' } diff --git a/.github/workflows/test-core.yml b/.github/workflows/test-core.yml index 6b604201cd88..3fe76186e770 100644 --- a/.github/workflows/test-core.yml +++ b/.github/workflows/test-core.yml @@ -72,7 +72,7 @@ jobs: key: no-default } - { - args: --features compression,wry,isolation,custom-protocol,system-tray,test, + args: --features compression,wry,linux-ipc-protocol,isolation,custom-protocol,tray-icon,test, key: all } diff --git a/Cargo.toml b/Cargo.toml index 369e7c461f08..63ee69bc15d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,6 @@ exclude = [ # examples that can be compiled with the tauri CLI "examples/api/src-tauri", "examples/resources/src-tauri", - "examples/sidecar/src-tauri", "examples/web/core", "examples/file-associations/src-tauri", "examples/workspace", diff --git a/core/tauri-build/src/allowlist.rs b/core/tauri-build/src/allowlist.rs index 34654f689c37..813a315339db 100644 --- a/core/tauri-build/src/allowlist.rs +++ b/core/tauri-build/src/allowlist.rs @@ -43,7 +43,12 @@ pub fn check(config: &Config, manifest: &mut Manifest) -> Result<()> { name: "tauri".into(), alias: None, kind: DependencyKind::Normal, - all_cli_managed_features: Some(TauriConfig::all_features()), + all_cli_managed_features: Some( + TauriConfig::all_features() + .into_iter() + .filter(|f| f != &"tray-icon") + .collect(), + ), expected_features: config .tauri .features() diff --git a/core/tauri-build/src/codegen/context.rs b/core/tauri-build/src/codegen/context.rs index e17fe3b8f8d4..f702ce21205f 100644 --- a/core/tauri-build/src/codegen/context.rs +++ b/core/tauri-build/src/codegen/context.rs @@ -120,7 +120,7 @@ impl CodegenContext { config_parent.join(icon).display() ); } - if let Some(tray_icon) = config.tauri.system_tray.as_ref().map(|t| &t.icon_path) { + if let Some(tray_icon) = config.tauri.tray_icon.as_ref().map(|t| &t.icon_path) { println!( "cargo:rerun-if-changed={}", config_parent.join(tray_icon).display() diff --git a/core/tauri-build/src/mobile.rs b/core/tauri-build/src/mobile.rs index 1a29cbc771be..4f134ef78d80 100644 --- a/core/tauri-build/src/mobile.rs +++ b/core/tauri-build/src/mobile.rs @@ -222,7 +222,7 @@ fn insert_into_xml(xml: &str, block_identifier: &str, parent_tag: &str, contents rewritten.push(line.to_string()); } - rewritten.join("\n").to_string() + rewritten.join("\n") } pub fn update_android_manifest(block_identifier: &str, parent: &str, insert: String) -> Result<()> { @@ -294,16 +294,14 @@ dependencies {" mod tests { #[test] fn insert_into_xml() { - let manifest = format!( - r#" + let manifest = r#" -"# - ); +"#; let id = "tauritest"; - let new = super::insert_into_xml(&manifest, id, "application", ""); + let new = super::insert_into_xml(manifest, id, "application", ""); let block_id_comment = super::xml_block_comment(id); let expected = format!( diff --git a/core/tauri-codegen/README.md b/core/tauri-codegen/README.md index 3f3e15990f28..8d5518db7b76 100644 --- a/core/tauri-codegen/README.md +++ b/core/tauri-codegen/README.md @@ -24,7 +24,7 @@ Tauri apps can have custom menus and have tray-type interfaces. They can be upda ## This module -- Embed, hash, and compress assets, including icons for the app as well as the system-tray. +- Embed, hash, and compress assets, including icons for the app as well as the tray icon. - Parse `tauri.conf.json` at compile time and generate the Config struct. To learn more about the details of how all of these pieces fit together, please consult this [ARCHITECTURE.md](https://github.com/tauri-apps/tauri/blob/dev/ARCHITECTURE.md) document. diff --git a/core/tauri-codegen/src/context.rs b/core/tauri-codegen/src/context.rs index cf556e5120cc..788db7db5d43 100644 --- a/core/tauri-codegen/src/context.rs +++ b/core/tauri-codegen/src/context.rs @@ -264,8 +264,8 @@ pub fn context_codegen(data: ContextData) -> Result Result Result Result bool + 'static; -#[cfg(all(desktop, feature = "system-tray"))] -pub use tauri_runtime::TrayId; mod webview; pub use webview::Webview; -#[cfg(all(desktop, feature = "system-tray"))] -mod system_tray; -#[cfg(all(desktop, feature = "system-tray"))] -use system_tray::*; - pub type WebContextStore = Arc, WebContext>>>; // window pub type WindowEventHandler = Box; pub type WindowEventListeners = Arc>>; -// menu -pub type MenuEventHandler = Box; -pub type WindowMenuEventListeners = Arc>>; #[derive(Debug, Clone, Default)] pub struct WebviewIdStore(Arc>>); @@ -174,8 +154,6 @@ pub(crate) fn send_user_message( UserMessageContext { webview_id_map: context.webview_id_map.clone(), windows: context.main_thread.windows.clone(), - #[cfg(all(desktop, feature = "system-tray"))] - system_tray_manager: context.main_thread.system_tray_manager.clone(), }, &context.main_thread.web_context, ); @@ -211,9 +189,12 @@ impl Context { } impl Context { - fn create_webview(&self, pending: PendingWindow>) -> Result>> { + fn create_webview( + &self, + pending: PendingWindow>, + before_webview_creation: Option, + ) -> Result>> { let label = pending.label.clone(); - let menu_ids = pending.menu_ids.clone(); let context = self.clone(); let window_id = rand::random(); @@ -222,7 +203,14 @@ impl Context { Message::CreateWebview( window_id, Box::new(move |event_loop, web_context| { - create_webview(window_id, event_loop, web_context, context, pending) + create_webview( + window_id, + event_loop, + web_context, + context, + pending, + before_webview_creation, + ) }), ), )?; @@ -231,21 +219,25 @@ impl Context { window_id, context: self.clone(), }; - Ok(DetachedWindow { - label, - dispatcher, - menu_ids, - }) + Ok(DetachedWindow { label, dispatcher }) } } -#[derive(Debug, Clone)] +#[derive(Clone)] pub struct DispatcherMainThreadContext { pub window_target: EventLoopWindowTarget>, pub web_context: WebContextStore, pub windows: Arc>>, - #[cfg(all(desktop, feature = "system-tray"))] - system_tray_manager: SystemTrayManager, +} + +impl std::fmt::Debug for DispatcherMainThreadContext { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("DispatcherMainThreadContext") + .field("window_target", &self.window_target) + .field("web_context", &self.web_context) + .field("windows", &self.windows) + .finish() + } } // SAFETY: we ensure this type is only used on the main thread. @@ -299,67 +291,6 @@ impl From for HttpResponseWrapper { } } -pub struct MenuItemAttributesWrapper<'a>(pub WryMenuItemAttributes<'a>); - -impl<'a> From<&'a CustomMenuItem> for MenuItemAttributesWrapper<'a> { - fn from(item: &'a CustomMenuItem) -> Self { - let mut attributes = WryMenuItemAttributes::new(&item.title) - .with_enabled(item.enabled) - .with_selected(item.selected) - .with_id(WryMenuId(item.id)); - if let Some(accelerator) = item.keyboard_accelerator.as_ref() { - attributes = attributes.with_accelerators(&accelerator.parse().expect("invalid accelerator")); - } - Self(attributes) - } -} - -pub struct AboutMetadataWrapper(pub WryAboutMetadata); - -impl From for AboutMetadataWrapper { - fn from(metadata: AboutMetadata) -> Self { - Self(WryAboutMetadata { - version: metadata.version, - authors: metadata.authors, - comments: metadata.comments, - copyright: metadata.copyright, - license: metadata.license, - website: metadata.website, - website_label: metadata.website_label, - }) - } -} - -pub struct MenuItemWrapper(pub WryMenuItem); - -impl From for MenuItemWrapper { - fn from(item: MenuItem) -> Self { - match item { - MenuItem::About(name, metadata) => Self(WryMenuItem::About( - name, - AboutMetadataWrapper::from(metadata).0, - )), - MenuItem::Hide => Self(WryMenuItem::Hide), - MenuItem::Services => Self(WryMenuItem::Services), - MenuItem::HideOthers => Self(WryMenuItem::HideOthers), - MenuItem::ShowAll => Self(WryMenuItem::ShowAll), - MenuItem::CloseWindow => Self(WryMenuItem::CloseWindow), - MenuItem::Quit => Self(WryMenuItem::Quit), - MenuItem::Copy => Self(WryMenuItem::Copy), - MenuItem::Cut => Self(WryMenuItem::Cut), - MenuItem::Undo => Self(WryMenuItem::Undo), - MenuItem::Redo => Self(WryMenuItem::Redo), - MenuItem::SelectAll => Self(WryMenuItem::SelectAll), - MenuItem::Paste => Self(WryMenuItem::Paste), - MenuItem::EnterFullScreen => Self(WryMenuItem::EnterFullScreen), - MenuItem::Minimize => Self(WryMenuItem::Minimize), - MenuItem::Zoom => Self(WryMenuItem::Zoom), - MenuItem::Separator => Self(WryMenuItem::Separator), - _ => unimplemented!(), - } - } -} - pub struct DeviceEventFilterWrapper(pub WryDeviceEventFilter); impl From for DeviceEventFilterWrapper { @@ -372,82 +303,6 @@ impl From for DeviceEventFilterWrapper { } } -#[cfg(target_os = "macos")] -pub struct NativeImageWrapper(pub WryNativeImage); - -#[cfg(target_os = "macos")] -impl std::fmt::Debug for NativeImageWrapper { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("NativeImageWrapper").finish() - } -} - -#[cfg(target_os = "macos")] -impl From for NativeImageWrapper { - fn from(image: NativeImage) -> NativeImageWrapper { - let wry_image = match image { - NativeImage::Add => WryNativeImage::Add, - NativeImage::Advanced => WryNativeImage::Advanced, - NativeImage::Bluetooth => WryNativeImage::Bluetooth, - NativeImage::Bookmarks => WryNativeImage::Bookmarks, - NativeImage::Caution => WryNativeImage::Caution, - NativeImage::ColorPanel => WryNativeImage::ColorPanel, - NativeImage::ColumnView => WryNativeImage::ColumnView, - NativeImage::Computer => WryNativeImage::Computer, - NativeImage::EnterFullScreen => WryNativeImage::EnterFullScreen, - NativeImage::Everyone => WryNativeImage::Everyone, - NativeImage::ExitFullScreen => WryNativeImage::ExitFullScreen, - NativeImage::FlowView => WryNativeImage::FlowView, - NativeImage::Folder => WryNativeImage::Folder, - NativeImage::FolderBurnable => WryNativeImage::FolderBurnable, - NativeImage::FolderSmart => WryNativeImage::FolderSmart, - NativeImage::FollowLinkFreestanding => WryNativeImage::FollowLinkFreestanding, - NativeImage::FontPanel => WryNativeImage::FontPanel, - NativeImage::GoLeft => WryNativeImage::GoLeft, - NativeImage::GoRight => WryNativeImage::GoRight, - NativeImage::Home => WryNativeImage::Home, - NativeImage::IChatTheater => WryNativeImage::IChatTheater, - NativeImage::IconView => WryNativeImage::IconView, - NativeImage::Info => WryNativeImage::Info, - NativeImage::InvalidDataFreestanding => WryNativeImage::InvalidDataFreestanding, - NativeImage::LeftFacingTriangle => WryNativeImage::LeftFacingTriangle, - NativeImage::ListView => WryNativeImage::ListView, - NativeImage::LockLocked => WryNativeImage::LockLocked, - NativeImage::LockUnlocked => WryNativeImage::LockUnlocked, - NativeImage::MenuMixedState => WryNativeImage::MenuMixedState, - NativeImage::MenuOnState => WryNativeImage::MenuOnState, - NativeImage::MobileMe => WryNativeImage::MobileMe, - NativeImage::MultipleDocuments => WryNativeImage::MultipleDocuments, - NativeImage::Network => WryNativeImage::Network, - NativeImage::Path => WryNativeImage::Path, - NativeImage::PreferencesGeneral => WryNativeImage::PreferencesGeneral, - NativeImage::QuickLook => WryNativeImage::QuickLook, - NativeImage::RefreshFreestanding => WryNativeImage::RefreshFreestanding, - NativeImage::Refresh => WryNativeImage::Refresh, - NativeImage::Remove => WryNativeImage::Remove, - NativeImage::RevealFreestanding => WryNativeImage::RevealFreestanding, - NativeImage::RightFacingTriangle => WryNativeImage::RightFacingTriangle, - NativeImage::Share => WryNativeImage::Share, - NativeImage::Slideshow => WryNativeImage::Slideshow, - NativeImage::SmartBadge => WryNativeImage::SmartBadge, - NativeImage::StatusAvailable => WryNativeImage::StatusAvailable, - NativeImage::StatusNone => WryNativeImage::StatusNone, - NativeImage::StatusPartiallyAvailable => WryNativeImage::StatusPartiallyAvailable, - NativeImage::StatusUnavailable => WryNativeImage::StatusUnavailable, - NativeImage::StopProgressFreestanding => WryNativeImage::StopProgressFreestanding, - NativeImage::StopProgress => WryNativeImage::StopProgress, - - NativeImage::TrashEmpty => WryNativeImage::TrashEmpty, - NativeImage::TrashFull => WryNativeImage::TrashFull, - NativeImage::User => WryNativeImage::User, - NativeImage::UserAccounts => WryNativeImage::UserAccounts, - NativeImage::UserGroup => WryNativeImage::UserGroup, - NativeImage::UserGuest => WryNativeImage::UserGuest, - }; - Self(wry_image) - } -} - /// Wrapper around a [`wry::application::window::Icon`] that can be created from an [`Icon`]. pub struct WryIcon(pub WryWindowIcon); @@ -684,13 +539,24 @@ impl From for CursorIconWrapper { } } -#[derive(Debug, Clone, Default)] +#[derive(Clone, Default)] pub struct WindowBuilderWrapper { inner: WryWindowBuilder, center: bool, #[cfg(target_os = "macos")] tabbing_identifier: Option, - menu: Option, +} + +impl std::fmt::Debug for WindowBuilderWrapper { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut s = f.debug_struct("WindowBuilderWrapper"); + s.field("inner", &self.inner).field("center", &self.center); + #[cfg(target_os = "macos")] + { + s.field("tabbing_identifier", &self.tabbing_identifier); + } + s.finish() + } } // SAFETY: this type is `Send` since `menu_items` are read only here @@ -773,11 +639,6 @@ impl WindowBuilder for WindowBuilderWrapper { window } - fn menu(mut self, menu: Menu) -> Self { - self.menu.replace(menu); - self - } - fn center(mut self) -> Self { self.center = true; self @@ -902,7 +763,7 @@ impl WindowBuilder for WindowBuilderWrapper { #[cfg(windows)] fn parent_window(mut self, parent: HWND) -> Self { - self.inner = self.inner.with_parent_window(parent); + self.inner = self.inner.with_parent_window(parent.0); self } @@ -914,7 +775,7 @@ impl WindowBuilder for WindowBuilderWrapper { #[cfg(windows)] fn owner_window(mut self, owner: HWND) -> Self { - self.inner = self.inner.with_owner_window(owner); + self.inner = self.inner.with_owner_window(owner.0); self } @@ -986,10 +847,6 @@ impl WindowBuilder for WindowBuilderWrapper { fn has_icon(&self) -> bool { self.inner.window.window_icon.is_some() } - - fn get_menu(&self) -> Option<&Menu> { - self.menu.as_ref() - } } pub struct FileDropEventWrapper(WryFileDropEvent); @@ -1055,6 +912,24 @@ pub struct GtkWindow(pub gtk::ApplicationWindow); #[allow(clippy::non_send_fields_in_send_ty)] unsafe impl Send for GtkWindow {} +#[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] +pub struct GtkBox(pub gtk::Box); +#[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] +#[allow(clippy::non_send_fields_in_send_ty)] +unsafe impl Send for GtkBox {} + pub struct RawWindowHandle(pub raw_window_handle::RawWindowHandle); unsafe impl Send for RawWindowHandle {} @@ -1068,7 +943,6 @@ pub enum ApplicationMessage { pub enum WindowMessage { WithWebview(Box), AddEventListener(Uuid, Box), - AddMenuEventListener(Uuid, Box), // Devtools #[cfg(any(debug_assertions, feature = "devtools"))] OpenDevTools, @@ -1094,7 +968,6 @@ pub enum WindowMessage { IsClosable(Sender), IsVisible(Sender), Title(Sender), - IsMenuVisible(Sender), CurrentMonitor(Sender>), PrimaryMonitor(Sender>), AvailableMonitors(Sender>), @@ -1106,6 +979,14 @@ pub enum WindowMessage { target_os = "openbsd" ))] GtkWindow(Sender), + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + GtkBox(Sender), RawWindowHandle(Sender), Theme(Sender), // Setters @@ -1121,8 +1002,6 @@ pub enum WindowMessage { Unmaximize, Minimize, Unminimize, - ShowMenu, - HideMenu, Show, Hide, Close, @@ -1145,7 +1024,6 @@ pub enum WindowMessage { SetCursorPosition(Position), SetIgnoreCursorEvents(bool), DragWindow, - UpdateMenuItem(u16, MenuUpdate), RequestRedraw, } @@ -1163,21 +1041,6 @@ pub enum WebviewEvent { Focused(bool), } -#[cfg(all(desktop, feature = "system-tray"))] -#[derive(Debug, Clone)] -pub enum TrayMessage { - UpdateItem(u16, MenuUpdate), - UpdateMenu(SystemTrayMenu), - UpdateIcon(Icon), - #[cfg(target_os = "macos")] - UpdateIconAsTemplate(bool), - #[cfg(target_os = "macos")] - UpdateTitle(String), - UpdateTooltip(String), - Create(SystemTray, Sender>), - Destroy(Sender>), -} - pub type CreateWebviewClosure = Box< dyn FnOnce(&EventLoopWindowTarget>, &WebContextStore) -> Result + Send, >; @@ -1188,8 +1051,6 @@ pub enum Message { Application(ApplicationMessage), Window(WebviewId, WindowMessage), Webview(WebviewId, WebviewMessage), - #[cfg(all(desktop, feature = "system-tray"))] - Tray(TrayId, TrayMessage), CreateWebview(WebviewId, CreateWebviewClosure), CreateWindow( WebviewId, @@ -1203,8 +1064,6 @@ impl Clone for Message { fn clone(&self) -> Self { match self { Self::Webview(i, m) => Self::Webview(*i, m.clone()), - #[cfg(all(desktop, feature = "system-tray"))] - Self::Tray(i, m) => Self::Tray(*i, m.clone()), Self::UserEvent(t) => Self::UserEvent(t.clone()), _ => unimplemented!(), } @@ -1239,15 +1098,6 @@ impl Dispatch for WryDispatcher { id } - fn on_menu_event(&self, f: F) -> Uuid { - let id = Uuid::new_v4(); - let _ = self.context.proxy.send_event(Message::Window( - self.window_id, - WindowMessage::AddMenuEventListener(id, Box::new(f)), - )); - id - } - fn with_webview) + Send + 'static>(&self, f: F) -> Result<()> { send_user_message( &self.context, @@ -1355,10 +1205,6 @@ impl Dispatch for WryDispatcher { window_getter!(self, WindowMessage::Title) } - fn is_menu_visible(&self) -> Result { - window_getter!(self, WindowMessage::IsMenuVisible) - } - fn current_monitor(&self) -> Result> { Ok(window_getter!(self, WindowMessage::CurrentMonitor)?.map(|m| MonitorHandleWrapper(m).into())) } @@ -1380,7 +1226,6 @@ impl Dispatch for WryDispatcher { window_getter!(self, WindowMessage::Theme) } - /// Returns the `ApplicationWindow` from gtk crate that is used by this window. #[cfg(any( target_os = "linux", target_os = "dragonfly", @@ -1392,6 +1237,17 @@ impl Dispatch for WryDispatcher { window_getter!(self, WindowMessage::GtkWindow).map(|w| w.0) } + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + fn default_vbox(&self) -> Result { + window_getter!(self, WindowMessage::GtkBox).map(|w| w.0) + } + fn raw_window_handle(&self) -> Result { window_getter!(self, WindowMessage::RawWindowHandle).map(|w| w.0) } @@ -1424,11 +1280,14 @@ impl Dispatch for WryDispatcher { // Creates a window by dispatching a message to the event loop. // Note that this must be called from a separate thread, otherwise the channel will introduce a deadlock. - fn create_window( + fn create_window( &mut self, pending: PendingWindow, + before_webview_creation: Option, ) -> Result> { - self.context.create_webview(pending) + self + .context + .create_webview(pending, before_webview_creation) } fn set_resizable(&self, resizable: bool) -> Result<()> { @@ -1501,20 +1360,6 @@ impl Dispatch for WryDispatcher { ) } - fn show_menu(&self) -> Result<()> { - send_user_message( - &self.context, - Message::Window(self.window_id, WindowMessage::ShowMenu), - ) - } - - fn hide_menu(&self) -> Result<()> { - send_user_message( - &self.context, - Message::Window(self.window_id, WindowMessage::HideMenu), - ) - } - fn show(&self) -> Result<()> { send_user_message( &self.context, @@ -1692,13 +1537,6 @@ impl Dispatch for WryDispatcher { ), ) } - - fn update_menu_item(&self, id: u16, update: MenuUpdate) -> Result<()> { - send_user_message( - &self.context, - Message::Window(self.window_id, WindowMessage::UpdateMenuItem(id, update)), - ) - } } #[derive(Clone)] @@ -1757,9 +1595,7 @@ impl WindowHandle { pub struct WindowWrapper { label: String, inner: Option, - menu_items: Option>, window_event_listeners: WindowEventListeners, - menu_event_listeners: WindowMenuEventListeners, } impl fmt::Debug for WindowWrapper { @@ -1767,7 +1603,6 @@ impl fmt::Debug for WindowWrapper { f.debug_struct("WindowWrapper") .field("label", &self.label) .field("inner", &self.inner) - .field("menu_items", &self.menu_items) .finish() } } @@ -1813,19 +1648,12 @@ pub struct Wry { impl fmt::Debug for Wry { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut d = f.debug_struct("Wry"); - d.field("main_thread_id", &self.context.main_thread_id) + f.debug_struct("Wry") + .field("main_thread_id", &self.context.main_thread_id) .field("event_loop", &self.event_loop) .field("windows", &self.context.main_thread.windows) - .field("web_context", &self.context.main_thread.web_context); - - #[cfg(all(desktop, feature = "system-tray"))] - d.field( - "system_tray_manager", - &self.context.main_thread.system_tray_manager, - ); - - d.finish() + .field("web_context", &self.context.main_thread.web_context) + .finish() } } @@ -1897,36 +1725,20 @@ impl RuntimeHandle for WryHandle { // Creates a window by dispatching a message to the event loop. // Note that this must be called from a separate thread, otherwise the channel will introduce a deadlock. - fn create_window( + fn create_window( &self, pending: PendingWindow, + before_webview_creation: Option, ) -> Result> { - self.context.create_webview(pending) + self + .context + .create_webview(pending, before_webview_creation) } fn run_on_main_thread(&self, f: F) -> Result<()> { send_user_message(&self.context, Message::Task(Box::new(f))) } - #[cfg(all(desktop, feature = "system-tray"))] - fn system_tray( - &self, - system_tray: SystemTray, - ) -> Result<>::TrayHandler> { - let id = system_tray.id; - let (tx, rx) = channel(); - send_user_message( - &self.context, - Message::Tray(id, TrayMessage::Create(system_tray, tx)), - )?; - rx.recv().unwrap()?; - Ok(SystemTrayHandle { - context: self.context.clone(), - id, - proxy: self.context.proxy.clone(), - }) - } - fn raw_display_handle(&self) -> RawDisplayHandle { self.context.main_thread.window_target.raw_display_handle() } @@ -1968,9 +1780,9 @@ impl RuntimeHandle for WryHandle { #[cfg(target_os = "android")] fn find_class<'a>( - &'a self, - env: jni::JNIEnv<'a>, - activity: jni::objects::JObject<'a>, + &self, + env: &mut jni::JNIEnv<'a>, + activity: &jni::objects::JObject<'_>, name: impl Into, ) -> std::result::Result, jni::errors::Error> { find_class(env, activity, name.into()) @@ -1979,15 +1791,25 @@ impl RuntimeHandle for WryHandle { #[cfg(target_os = "android")] fn run_on_android_context(&self, f: F) where - F: FnOnce(jni::JNIEnv<'_>, jni::objects::JObject<'_>, jni::objects::JObject<'_>) - + Send - + 'static, + F: FnOnce(&mut jni::JNIEnv, &jni::objects::JObject, &jni::objects::JObject) + Send + 'static, { dispatch(f) } } impl Wry { + fn init_with_builder( + mut event_loop_builder: EventLoopBuilder>, + #[allow(unused_variables)] args: RuntimeInitArgs, + ) -> Result { + #[cfg(windows)] + if let Some(hook) = args.msg_hook { + use wry::application::platform::windows::EventLoopBuilderExtWindows; + event_loop_builder.with_msg_hook(hook); + } + Self::init(event_loop_builder.build()) + } + fn init(event_loop: EventLoop>) -> Result { let main_thread_id = current_thread().id(); let web_context = WebContextStore::default(); @@ -1995,9 +1817,6 @@ impl Wry { let windows = Arc::new(RefCell::new(HashMap::default())); let webview_id_map = WebviewIdStore::default(); - #[cfg(all(desktop, feature = "system-tray"))] - let system_tray_manager = Default::default(); - let context = Context { webview_id_map, main_thread_id, @@ -2006,8 +1825,6 @@ impl Wry { window_target: event_loop.deref().clone(), web_context, windows, - #[cfg(all(desktop, feature = "system-tray"))] - system_tray_manager, }, plugins: Default::default(), }; @@ -2023,24 +1840,31 @@ impl Runtime for Wry { type Dispatcher = WryDispatcher; type Handle = WryHandle; - #[cfg(all(desktop, feature = "system-tray"))] - type TrayHandler = SystemTrayHandle; - type EventLoopProxy = EventProxy; - fn new() -> Result { - let event_loop = EventLoop::>::with_user_event(); - Self::init(event_loop) + fn new(args: RuntimeInitArgs) -> Result { + Self::init_with_builder(EventLoopBuilder::>::with_user_event(), args) + } + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + fn new_any_thread(args: RuntimeInitArgs) -> Result { + use wry::application::platform::unix::EventLoopBuilderExtUnix; + let mut event_loop_builder = EventLoopBuilder::>::with_user_event(); + event_loop_builder.with_any_thread(true); + Self::init_with_builder(event_loop_builder, args) } - #[cfg(any(windows, target_os = "linux"))] - fn new_any_thread() -> Result { - #[cfg(target_os = "linux")] - use wry::application::platform::unix::EventLoopExtUnix; - #[cfg(windows)] - use wry::application::platform::windows::EventLoopExtWindows; - let event_loop = EventLoop::>::new_any_thread(); - Self::init(event_loop) + #[cfg(windows)] + fn new_any_thread(args: RuntimeInitArgs) -> Result { + use wry::application::platform::windows::EventLoopBuilderExtWindows; + let mut event_loop_builder = EventLoopBuilder::>::with_user_event(); + event_loop_builder.with_any_thread(true); + Self::init_with_builder(event_loop_builder, args) } fn create_proxy(&self) -> EventProxy { @@ -2053,9 +1877,12 @@ impl Runtime for Wry { } } - fn create_window(&self, pending: PendingWindow) -> Result> { + fn create_window( + &self, + pending: PendingWindow, + before_webview_creation: Option, + ) -> Result> { let label = pending.label.clone(); - let menu_ids = pending.menu_ids.clone(); let window_id = rand::random(); let webview = create_webview( @@ -2064,6 +1891,7 @@ impl Runtime for Wry { &self.context.main_thread.web_context, self.context.clone(), pending, + before_webview_creation, )?; let dispatcher = WryDispatcher { @@ -2078,54 +1906,7 @@ impl Runtime for Wry { .borrow_mut() .insert(window_id, webview); - Ok(DetachedWindow { - label, - dispatcher, - menu_ids, - }) - } - - #[cfg(all(desktop, feature = "system-tray"))] - fn system_tray(&self, mut system_tray: SystemTray) -> Result { - let id = system_tray.id; - let mut listeners = Vec::new(); - if let Some(l) = system_tray.on_event.take() { - listeners.push(Arc::new(l)); - } - let (tray, items) = create_tray(WryTrayId(id), system_tray, &self.event_loop)?; - self - .context - .main_thread - .system_tray_manager - .trays - .lock() - .unwrap() - .insert( - id, - TrayContext { - tray: Arc::new(Mutex::new(Some(tray))), - listeners: Arc::new(Mutex::new(listeners)), - items: Arc::new(Mutex::new(items)), - }, - ); - - Ok(SystemTrayHandle { - context: self.context.clone(), - id, - proxy: self.event_loop.create_proxy(), - }) - } - - #[cfg(all(desktop, feature = "system-tray"))] - fn on_system_tray_event(&mut self, f: F) { - self - .context - .main_thread - .system_tray_manager - .global_listeners - .lock() - .unwrap() - .push(Arc::new(Box::new(f))); + Ok(DetachedWindow { label, dispatcher }) } fn primary_monitor(&self) -> Option { @@ -2182,8 +1963,6 @@ impl Runtime for Wry { let webview_id_map = self.context.webview_id_map.clone(); let web_context = &self.context.main_thread.web_context; let plugins = self.context.plugins.clone(); - #[cfg(all(desktop, feature = "system-tray"))] - let system_tray_manager = self.context.main_thread.system_tray_manager.clone(); let mut iteration = RunIteration::default(); @@ -2207,8 +1986,6 @@ impl Runtime for Wry { callback: &mut callback, webview_id_map: webview_id_map.clone(), windows: windows.clone(), - #[cfg(all(desktop, feature = "system-tray"))] - system_tray_manager: system_tray_manager.clone(), }, web_context, ); @@ -2225,8 +2002,6 @@ impl Runtime for Wry { callback: &mut callback, windows: windows.clone(), webview_id_map: webview_id_map.clone(), - #[cfg(all(desktop, feature = "system-tray"))] - system_tray_manager: system_tray_manager.clone(), }, web_context, ); @@ -2241,9 +2016,6 @@ impl Runtime for Wry { let web_context = self.context.main_thread.web_context; let plugins = self.context.plugins.clone(); - #[cfg(all(desktop, feature = "system-tray"))] - let system_tray_manager = self.context.main_thread.system_tray_manager; - let proxy = self.event_loop.create_proxy(); self.event_loop.run(move |event, event_loop, control_flow| { @@ -2257,8 +2029,6 @@ impl Runtime for Wry { callback: &mut callback, webview_id_map: webview_id_map.clone(), windows: windows.clone(), - #[cfg(all(desktop, feature = "system-tray"))] - system_tray_manager: system_tray_manager.clone(), }, &web_context, ); @@ -2274,8 +2044,6 @@ impl Runtime for Wry { callback: &mut callback, webview_id_map: webview_id_map.clone(), windows: windows.clone(), - #[cfg(all(desktop, feature = "system-tray"))] - system_tray_manager: system_tray_manager.clone(), }, &web_context, ); @@ -2287,15 +2055,11 @@ pub struct EventLoopIterationContext<'a, T: UserEvent> { pub callback: &'a mut (dyn FnMut(RunEvent) + 'static), pub webview_id_map: WebviewIdStore, pub windows: Arc>>, - #[cfg(all(desktop, feature = "system-tray"))] - pub system_tray_manager: SystemTrayManager, } struct UserMessageContext { windows: Arc>>, webview_id_map: WebviewIdStore, - #[cfg(all(desktop, feature = "system-tray"))] - system_tray_manager: SystemTrayManager, } fn handle_user_message( @@ -2307,8 +2071,6 @@ fn handle_user_message( let UserMessageContext { webview_id_map, windows, - #[cfg(all(desktop, feature = "system-tray"))] - system_tray_manager, } = context; match message { Message::Task(task) => task(), @@ -2322,263 +2084,240 @@ fn handle_user_message( } }, Message::Window(id, window_message) => { - if let WindowMessage::UpdateMenuItem(item_id, update) = window_message { - if let Some(menu_items) = windows.borrow_mut().get_mut(&id).map(|w| &mut w.menu_items) { - if let Some(menu_items) = menu_items.as_mut() { - let item = menu_items.get_mut(&item_id).expect("menu item not found"); - match update { - MenuUpdate::SetEnabled(enabled) => item.set_enabled(enabled), - MenuUpdate::SetTitle(title) => item.set_title(&title), - MenuUpdate::SetSelected(selected) => item.set_selected(selected), - #[cfg(target_os = "macos")] - MenuUpdate::SetNativeImage(image) => { - item.set_native_image(NativeImageWrapper::from(image).0) + let w = windows + .borrow() + .get(&id) + .map(|w| (w.inner.clone(), w.window_event_listeners.clone())); + if let Some((Some(window), window_event_listeners)) = w { + match window_message { + WindowMessage::WithWebview(f) => { + if let WindowHandle::Webview { inner: w, .. } = &window { + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + { + use wry::webview::WebviewExtUnix; + f(w.webview()); } - } - } - } - } else { - let w = windows.borrow().get(&id).map(|w| { - ( - w.inner.clone(), - w.window_event_listeners.clone(), - w.menu_event_listeners.clone(), - ) - }); - if let Some((Some(window), window_event_listeners, menu_event_listeners)) = w { - match window_message { - WindowMessage::WithWebview(f) => { - if let WindowHandle::Webview { inner: w, .. } = &window { - #[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ))] - { - use wry::webview::WebviewExtUnix; - f(w.webview()); - } - #[cfg(target_os = "macos")] - { - use wry::webview::WebviewExtMacOS; - f(Webview { - webview: w.webview(), - manager: w.manager(), - ns_window: w.ns_window(), - }); - } - #[cfg(target_os = "ios")] - { - use wry::{application::platform::ios::WindowExtIOS, webview::WebviewExtIOS}; - - f(Webview { - webview: w.webview(), - manager: w.manager(), - view_controller: w.window().ui_view_controller() as cocoa::base::id, - }); - } - #[cfg(windows)] - { - f(Webview { - controller: w.controller(), - }); - } - #[cfg(target_os = "android")] - { - f(w.handle()) - } - } - } - - WindowMessage::AddEventListener(id, listener) => { - window_event_listeners.lock().unwrap().insert(id, listener); - } - - WindowMessage::AddMenuEventListener(id, listener) => { - menu_event_listeners.lock().unwrap().insert(id, listener); - } - - #[cfg(any(debug_assertions, feature = "devtools"))] - WindowMessage::OpenDevTools => { - if let WindowHandle::Webview { inner: w, .. } = &window { - w.open_devtools(); - } - } - #[cfg(any(debug_assertions, feature = "devtools"))] - WindowMessage::CloseDevTools => { - if let WindowHandle::Webview { inner: w, .. } = &window { - w.close_devtools(); + #[cfg(target_os = "macos")] + { + use wry::webview::WebviewExtMacOS; + f(Webview { + webview: w.webview(), + manager: w.manager(), + ns_window: w.ns_window(), + }); } - } - #[cfg(any(debug_assertions, feature = "devtools"))] - WindowMessage::IsDevToolsOpen(tx) => { - if let WindowHandle::Webview { inner: w, .. } = &window { - tx.send(w.is_devtools_open()).unwrap(); - } else { - tx.send(false).unwrap(); + #[cfg(target_os = "ios")] + { + use wry::{application::platform::ios::WindowExtIOS, webview::WebviewExtIOS}; + + f(Webview { + webview: w.webview(), + manager: w.manager(), + view_controller: w.window().ui_view_controller() as cocoa::base::id, + }); } - } - // Getters - WindowMessage::Url(tx) => { - if let WindowHandle::Webview { inner: w, .. } = &window { - tx.send(w.url()).unwrap(); - } - } - WindowMessage::ScaleFactor(tx) => tx.send(window.scale_factor()).unwrap(), - WindowMessage::InnerPosition(tx) => tx - .send( - window - .inner_position() - .map(|p| PhysicalPositionWrapper(p).into()) - .map_err(|_| Error::FailedToSendMessage), - ) - .unwrap(), - WindowMessage::OuterPosition(tx) => tx - .send( - window - .outer_position() - .map(|p| PhysicalPositionWrapper(p).into()) - .map_err(|_| Error::FailedToSendMessage), - ) - .unwrap(), - WindowMessage::InnerSize(tx) => tx - .send(PhysicalSizeWrapper(window.inner_size()).into()) - .unwrap(), - WindowMessage::OuterSize(tx) => tx - .send(PhysicalSizeWrapper(window.outer_size()).into()) - .unwrap(), - WindowMessage::IsFullscreen(tx) => tx.send(window.fullscreen().is_some()).unwrap(), - WindowMessage::IsMinimized(tx) => tx.send(window.is_minimized()).unwrap(), - WindowMessage::IsMaximized(tx) => tx.send(window.is_maximized()).unwrap(), - WindowMessage::IsFocused(tx) => tx.send(window.is_focused()).unwrap(), - WindowMessage::IsDecorated(tx) => tx.send(window.is_decorated()).unwrap(), - WindowMessage::IsResizable(tx) => tx.send(window.is_resizable()).unwrap(), - WindowMessage::IsMaximizable(tx) => tx.send(window.is_maximizable()).unwrap(), - WindowMessage::IsMinimizable(tx) => tx.send(window.is_minimizable()).unwrap(), - WindowMessage::IsClosable(tx) => tx.send(window.is_closable()).unwrap(), - WindowMessage::IsVisible(tx) => tx.send(window.is_visible()).unwrap(), - WindowMessage::Title(tx) => tx.send(window.title()).unwrap(), - WindowMessage::IsMenuVisible(tx) => tx.send(window.is_menu_visible()).unwrap(), - WindowMessage::CurrentMonitor(tx) => tx.send(window.current_monitor()).unwrap(), - WindowMessage::PrimaryMonitor(tx) => tx.send(window.primary_monitor()).unwrap(), - WindowMessage::AvailableMonitors(tx) => { - tx.send(window.available_monitors().collect()).unwrap() - } - #[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ))] - WindowMessage::GtkWindow(tx) => { - tx.send(GtkWindow(window.gtk_window().clone())).unwrap() - } - WindowMessage::RawWindowHandle(tx) => tx - .send(RawWindowHandle(window.raw_window_handle())) - .unwrap(), - WindowMessage::Theme(tx) => { - tx.send(map_theme(&window.theme())).unwrap(); - } - // Setters - WindowMessage::Center => { - let _ = center_window(&window, window.inner_size()); - } - WindowMessage::RequestUserAttention(request_type) => { - window.request_user_attention(request_type.map(|r| r.0)); - } - WindowMessage::SetResizable(resizable) => window.set_resizable(resizable), - WindowMessage::SetMaximizable(maximizable) => window.set_maximizable(maximizable), - WindowMessage::SetMinimizable(minimizable) => window.set_minimizable(minimizable), - WindowMessage::SetClosable(closable) => window.set_closable(closable), - WindowMessage::SetTitle(title) => window.set_title(&title), - WindowMessage::Navigate(url) => { - if let WindowHandle::Webview { inner: w, .. } = &window { - w.load_url(url.as_str()) - } - } - WindowMessage::Maximize => window.set_maximized(true), - WindowMessage::Unmaximize => window.set_maximized(false), - WindowMessage::Minimize => window.set_minimized(true), - WindowMessage::Unminimize => window.set_minimized(false), - WindowMessage::ShowMenu => window.show_menu(), - WindowMessage::HideMenu => window.hide_menu(), - WindowMessage::Show => window.set_visible(true), - WindowMessage::Hide => window.set_visible(false), - WindowMessage::Close => { - panic!("cannot handle `WindowMessage::Close` on the main thread") - } - WindowMessage::SetDecorations(decorations) => window.set_decorations(decorations), - WindowMessage::SetShadow(_enable) => { #[cfg(windows)] - window.set_undecorated_shadow(_enable); - #[cfg(target_os = "macos")] - window.set_has_shadow(_enable); - } - WindowMessage::SetAlwaysOnTop(always_on_top) => window.set_always_on_top(always_on_top), - WindowMessage::SetVisibleOnAllWorkspaces(visible_on_all_workspaces) => { - window.set_visible_on_all_workspaces(visible_on_all_workspaces) - } - WindowMessage::SetContentProtected(protected) => { - window.set_content_protection(protected) - } - WindowMessage::SetSize(size) => { - window.set_inner_size(SizeWrapper::from(size).0); - } - WindowMessage::SetMinSize(size) => { - window.set_min_inner_size(size.map(|s| SizeWrapper::from(s).0)); - } - WindowMessage::SetMaxSize(size) => { - window.set_max_inner_size(size.map(|s| SizeWrapper::from(s).0)); - } - WindowMessage::SetPosition(position) => { - window.set_outer_position(PositionWrapper::from(position).0) - } - WindowMessage::SetFullscreen(fullscreen) => { - if fullscreen { - window.set_fullscreen(Some(Fullscreen::Borderless(None))) - } else { - window.set_fullscreen(None) + { + f(Webview { + controller: w.controller(), + }); + } + #[cfg(target_os = "android")] + { + f(w.handle()) } } - WindowMessage::SetFocus => { - window.set_focus(); - } - WindowMessage::SetIcon(icon) => { - window.set_window_icon(Some(icon)); - } - #[allow(unused_variables)] - WindowMessage::SetSkipTaskbar(skip) => { - #[cfg(any(windows, target_os = "linux"))] - window.set_skip_taskbar(skip); - } - WindowMessage::SetCursorGrab(grab) => { - let _ = window.set_cursor_grab(grab); - } - WindowMessage::SetCursorVisible(visible) => { - window.set_cursor_visible(visible); - } - WindowMessage::SetCursorIcon(icon) => { - window.set_cursor_icon(CursorIconWrapper::from(icon).0); + } + + WindowMessage::AddEventListener(id, listener) => { + window_event_listeners.lock().unwrap().insert(id, listener); + } + + #[cfg(any(debug_assertions, feature = "devtools"))] + WindowMessage::OpenDevTools => { + if let WindowHandle::Webview { inner: w, .. } = &window { + w.open_devtools(); } - WindowMessage::SetCursorPosition(position) => { - let _ = window.set_cursor_position(PositionWrapper::from(position).0); + } + #[cfg(any(debug_assertions, feature = "devtools"))] + WindowMessage::CloseDevTools => { + if let WindowHandle::Webview { inner: w, .. } = &window { + w.close_devtools(); } - WindowMessage::SetIgnoreCursorEvents(ignore) => { - let _ = window.set_ignore_cursor_events(ignore); + } + #[cfg(any(debug_assertions, feature = "devtools"))] + WindowMessage::IsDevToolsOpen(tx) => { + if let WindowHandle::Webview { inner: w, .. } = &window { + tx.send(w.is_devtools_open()).unwrap(); + } else { + tx.send(false).unwrap(); } - WindowMessage::DragWindow => { - let _ = window.drag_window(); + } + + // Getters + WindowMessage::Url(tx) => { + if let WindowHandle::Webview { inner: w, .. } = &window { + tx.send(w.url()).unwrap(); } - WindowMessage::UpdateMenuItem(_id, _update) => { - // already handled + } + WindowMessage::ScaleFactor(tx) => tx.send(window.scale_factor()).unwrap(), + WindowMessage::InnerPosition(tx) => tx + .send( + window + .inner_position() + .map(|p| PhysicalPositionWrapper(p).into()) + .map_err(|_| Error::FailedToSendMessage), + ) + .unwrap(), + WindowMessage::OuterPosition(tx) => tx + .send( + window + .outer_position() + .map(|p| PhysicalPositionWrapper(p).into()) + .map_err(|_| Error::FailedToSendMessage), + ) + .unwrap(), + WindowMessage::InnerSize(tx) => tx + .send(PhysicalSizeWrapper(window.inner_size()).into()) + .unwrap(), + WindowMessage::OuterSize(tx) => tx + .send(PhysicalSizeWrapper(window.outer_size()).into()) + .unwrap(), + WindowMessage::IsFullscreen(tx) => tx.send(window.fullscreen().is_some()).unwrap(), + WindowMessage::IsMinimized(tx) => tx.send(window.is_minimized()).unwrap(), + WindowMessage::IsMaximized(tx) => tx.send(window.is_maximized()).unwrap(), + WindowMessage::IsFocused(tx) => tx.send(window.is_focused()).unwrap(), + WindowMessage::IsDecorated(tx) => tx.send(window.is_decorated()).unwrap(), + WindowMessage::IsResizable(tx) => tx.send(window.is_resizable()).unwrap(), + WindowMessage::IsMaximizable(tx) => tx.send(window.is_maximizable()).unwrap(), + WindowMessage::IsMinimizable(tx) => tx.send(window.is_minimizable()).unwrap(), + WindowMessage::IsClosable(tx) => tx.send(window.is_closable()).unwrap(), + WindowMessage::IsVisible(tx) => tx.send(window.is_visible()).unwrap(), + WindowMessage::Title(tx) => tx.send(window.title()).unwrap(), + WindowMessage::CurrentMonitor(tx) => tx.send(window.current_monitor()).unwrap(), + WindowMessage::PrimaryMonitor(tx) => tx.send(window.primary_monitor()).unwrap(), + WindowMessage::AvailableMonitors(tx) => { + tx.send(window.available_monitors().collect()).unwrap() + } + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + WindowMessage::GtkWindow(tx) => tx.send(GtkWindow(window.gtk_window().clone())).unwrap(), + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + WindowMessage::GtkBox(tx) => tx + .send(GtkBox(window.default_vbox().unwrap().clone())) + .unwrap(), + WindowMessage::RawWindowHandle(tx) => tx + .send(RawWindowHandle(window.raw_window_handle())) + .unwrap(), + WindowMessage::Theme(tx) => { + tx.send(map_theme(&window.theme())).unwrap(); + } + // Setters + WindowMessage::Center => { + let _ = center_window(&window, window.inner_size()); + } + WindowMessage::RequestUserAttention(request_type) => { + window.request_user_attention(request_type.map(|r| r.0)); + } + WindowMessage::SetResizable(resizable) => window.set_resizable(resizable), + WindowMessage::SetMaximizable(maximizable) => window.set_maximizable(maximizable), + WindowMessage::SetMinimizable(minimizable) => window.set_minimizable(minimizable), + WindowMessage::SetClosable(closable) => window.set_closable(closable), + WindowMessage::SetTitle(title) => window.set_title(&title), + WindowMessage::Navigate(url) => { + if let WindowHandle::Webview { inner: w, .. } = &window { + w.load_url(url.as_str()) } - WindowMessage::RequestRedraw => { - window.request_redraw(); + } + WindowMessage::Maximize => window.set_maximized(true), + WindowMessage::Unmaximize => window.set_maximized(false), + WindowMessage::Minimize => window.set_minimized(true), + WindowMessage::Unminimize => window.set_minimized(false), + WindowMessage::Show => window.set_visible(true), + WindowMessage::Hide => window.set_visible(false), + WindowMessage::Close => { + panic!("cannot handle `WindowMessage::Close` on the main thread") + } + WindowMessage::SetDecorations(decorations) => window.set_decorations(decorations), + WindowMessage::SetShadow(_enable) => { + #[cfg(windows)] + window.set_undecorated_shadow(_enable); + #[cfg(target_os = "macos")] + window.set_has_shadow(_enable); + } + WindowMessage::SetAlwaysOnTop(always_on_top) => window.set_always_on_top(always_on_top), + WindowMessage::SetVisibleOnAllWorkspaces(visible_on_all_workspaces) => { + window.set_visible_on_all_workspaces(visible_on_all_workspaces) + } + WindowMessage::SetContentProtected(protected) => window.set_content_protection(protected), + WindowMessage::SetSize(size) => { + window.set_inner_size(SizeWrapper::from(size).0); + } + WindowMessage::SetMinSize(size) => { + window.set_min_inner_size(size.map(|s| SizeWrapper::from(s).0)); + } + WindowMessage::SetMaxSize(size) => { + window.set_max_inner_size(size.map(|s| SizeWrapper::from(s).0)); + } + WindowMessage::SetPosition(position) => { + window.set_outer_position(PositionWrapper::from(position).0) + } + WindowMessage::SetFullscreen(fullscreen) => { + if fullscreen { + window.set_fullscreen(Some(Fullscreen::Borderless(None))) + } else { + window.set_fullscreen(None) } } + WindowMessage::SetFocus => { + window.set_focus(); + } + WindowMessage::SetIcon(icon) => { + window.set_window_icon(Some(icon)); + } + #[allow(unused_variables)] + WindowMessage::SetSkipTaskbar(skip) => { + #[cfg(any(windows, target_os = "linux"))] + window.set_skip_taskbar(skip); + } + WindowMessage::SetCursorGrab(grab) => { + let _ = window.set_cursor_grab(grab); + } + WindowMessage::SetCursorVisible(visible) => { + window.set_cursor_visible(visible); + } + WindowMessage::SetCursorIcon(icon) => { + window.set_cursor_icon(CursorIconWrapper::from(icon).0); + } + WindowMessage::SetCursorPosition(position) => { + let _ = window.set_cursor_position(PositionWrapper::from(position).0); + } + WindowMessage::SetIgnoreCursorEvents(ignore) => { + let _ = window.set_ignore_cursor_events(ignore); + } + WindowMessage::DragWindow => { + let _ = window.drag_window(); + } + WindowMessage::RequestRedraw => { + window.request_redraw(); + } } } } @@ -2621,9 +2360,7 @@ fn handle_user_message( WindowWrapper { label, inner: Some(WindowHandle::Window(w.clone())), - menu_items: Default::default(), window_event_listeners: Default::default(), - menu_event_listeners: Default::default(), }, ); sender.send(Ok(Arc::downgrade(&w))).unwrap(); @@ -2632,91 +2369,6 @@ fn handle_user_message( } } - #[cfg(all(desktop, feature = "system-tray"))] - Message::Tray(tray_id, tray_message) => { - let mut trays = system_tray_manager.trays.lock().unwrap(); - - if let TrayMessage::Create(mut tray, tx) = tray_message { - let mut listeners = Vec::new(); - if let Some(l) = tray.on_event.take() { - listeners.push(Arc::new(l)); - } - match create_tray(WryTrayId(tray_id), tray, event_loop) { - Ok((tray, items)) => { - trays.insert( - tray_id, - TrayContext { - tray: Arc::new(Mutex::new(Some(tray))), - listeners: Arc::new(Mutex::new(listeners)), - items: Arc::new(Mutex::new(items)), - }, - ); - - tx.send(Ok(())).unwrap(); - } - - Err(e) => { - tx.send(Err(e)).unwrap(); - } - } - } else if let Some(tray_context) = trays.get(&tray_id) { - match tray_message { - TrayMessage::UpdateItem(menu_id, update) => { - let mut tray = tray_context.items.as_ref().lock().unwrap(); - let item = tray.get_mut(&menu_id).expect("menu item not found"); - match update { - MenuUpdate::SetEnabled(enabled) => item.set_enabled(enabled), - MenuUpdate::SetTitle(title) => item.set_title(&title), - MenuUpdate::SetSelected(selected) => item.set_selected(selected), - #[cfg(target_os = "macos")] - MenuUpdate::SetNativeImage(image) => { - item.set_native_image(NativeImageWrapper::from(image).0) - } - } - } - TrayMessage::UpdateMenu(menu) => { - if let Some(tray) = &mut *tray_context.tray.lock().unwrap() { - let mut items = HashMap::new(); - tray.set_menu(&to_wry_context_menu(&mut items, menu)); - *tray_context.items.lock().unwrap() = items; - } - } - TrayMessage::UpdateIcon(icon) => { - if let Some(tray) = &mut *tray_context.tray.lock().unwrap() { - if let Ok(icon) = TrayIcon::try_from(icon) { - tray.set_icon(icon.0); - } - } - } - #[cfg(target_os = "macos")] - TrayMessage::UpdateIconAsTemplate(is_template) => { - if let Some(tray) = &mut *tray_context.tray.lock().unwrap() { - tray.set_icon_as_template(is_template); - } - } - #[cfg(target_os = "macos")] - TrayMessage::UpdateTitle(title) => { - if let Some(tray) = &mut *tray_context.tray.lock().unwrap() { - tray.set_title(&title); - } - } - TrayMessage::UpdateTooltip(tooltip) => { - if let Some(tray) = &mut *tray_context.tray.lock().unwrap() { - tray.set_tooltip(&tooltip); - } - } - TrayMessage::Create(_tray, _tx) => { - // already handled - } - TrayMessage::Destroy(tx) => { - *tray_context.tray.lock().unwrap() = None; - tray_context.listeners.lock().unwrap().clear(); - tray_context.items.lock().unwrap().clear(); - tx.send(Ok(())).unwrap(); - } - } - } - } Message::UserEvent(_) => (), } @@ -2737,8 +2389,6 @@ fn handle_event_loop( callback, webview_id_map, windows, - #[cfg(all(desktop, feature = "system-tray"))] - system_tray_manager, } = context; if *control_flow != ControlFlow::Exit { *control_flow = ControlFlow::Wait; @@ -2761,118 +2411,6 @@ fn handle_event_loop( callback(RunEvent::Exit); } - Event::MenuEvent { - window_id, - menu_id, - origin: MenuType::MenuBar, - .. - } => { - #[allow(unused_mut)] - let mut window_id = window_id.unwrap(); // always Some on MenuBar event - - #[cfg(target_os = "macos")] - { - // safety: we're only checking to see if the window_id is 0 - // which is the value sent by macOS when the window is minimized (NSApplication::sharedApplication::mainWindow is null) - if window_id == unsafe { WindowId::dummy() } { - window_id = *webview_id_map.0.lock().unwrap().keys().next().unwrap(); - } - } - - let event = MenuEvent { - menu_item_id: menu_id.0, - }; - let window_menu_event_listeners = { - // on macOS the window id might be the inspector window if it is detached - let window_id = if let Some(window_id) = webview_id_map.get(&window_id) { - window_id - } else { - *webview_id_map.0.lock().unwrap().values().next().unwrap() - }; - windows - .borrow() - .get(&window_id) - .unwrap() - .menu_event_listeners - .clone() - }; - let listeners = window_menu_event_listeners.lock().unwrap(); - let handlers = listeners.values(); - for handler in handlers { - handler(&event); - } - } - #[cfg(all(desktop, feature = "system-tray"))] - Event::MenuEvent { - window_id: _, - menu_id, - origin: MenuType::ContextMenu, - .. - } => { - let event = SystemTrayEvent::MenuItemClick(menu_id.0); - - let trays = system_tray_manager.trays.lock().unwrap(); - let trays_iter = trays.iter(); - - let (mut listeners, mut tray_id) = (None, 0); - for (id, tray_context) in trays_iter { - let has_menu = { - let items = tray_context.items.lock().unwrap(); - items.contains_key(&menu_id.0) - }; - if has_menu { - listeners.replace(tray_context.listeners.lock().unwrap().clone()); - tray_id = *id; - break; - } - } - drop(trays); - if let Some(listeners) = listeners { - let handlers = listeners.iter(); - for handler in handlers { - handler(&event); - } - - let global_listeners = system_tray_manager.global_listeners.lock().unwrap(); - let global_listeners_iter = global_listeners.iter(); - for global_listener in global_listeners_iter { - global_listener(tray_id, &event); - } - } - } - #[cfg(all(desktop, feature = "system-tray"))] - Event::TrayEvent { - id, - bounds, - event, - position: _cursor_position, - .. - } => { - let (position, size) = ( - PhysicalPositionWrapper(bounds.position).into(), - PhysicalSizeWrapper(bounds.size).into(), - ); - let event = match event { - TrayEvent::RightClick => SystemTrayEvent::RightClick { position, size }, - TrayEvent::DoubleClick => SystemTrayEvent::DoubleClick { position, size }, - // default to left click - _ => SystemTrayEvent::LeftClick { position, size }, - }; - let trays = system_tray_manager.trays.lock().unwrap(); - if let Some(tray_context) = trays.get(&id.0) { - let listeners = tray_context.listeners.lock().unwrap(); - let iter = listeners.iter(); - for handler in iter { - handler(&event); - } - } - - let global_listeners = system_tray_manager.global_listeners.lock().unwrap(); - let global_listeners_iter = global_listeners.iter(); - for global_listener in global_listeners_iter { - global_listener(id.0, &event); - } - } Event::UserEvent(Message::Webview(id, WebviewMessage::WebviewEvent(event))) => { if let Some(event) = WindowEventWrapper::from(&event).0 { let windows = windows.borrow(); @@ -2891,6 +2429,7 @@ fn handle_event_loop( } } } + Event::WindowEvent { event, window_id, .. } => { @@ -2967,8 +2506,6 @@ fn handle_event_loop( UserMessageContext { webview_id_map, windows, - #[cfg(all(desktop, feature = "system-tray"))] - system_tray_manager, }, web_context, ); @@ -3040,45 +2577,13 @@ pub fn center_window(window: &Window, window_size: WryPhysicalSize) -> Resu } } -fn to_wry_menu( - custom_menu_items: &mut HashMap, - menu: Menu, -) -> MenuBar { - let mut wry_menu = MenuBar::new(); - for item in menu.items { - match item { - MenuEntry::CustomItem(c) => { - let mut attributes = MenuItemAttributesWrapper::from(&c).0; - attributes = attributes.with_id(WryMenuId(c.id)); - #[allow(unused_mut)] - let mut item = wry_menu.add_item(attributes); - #[cfg(target_os = "macos")] - if let Some(native_image) = c.native_image { - item.set_native_image(NativeImageWrapper::from(native_image).0); - } - custom_menu_items.insert(c.id, item); - } - MenuEntry::NativeItem(i) => { - wry_menu.add_native_item(MenuItemWrapper::from(i).0); - } - MenuEntry::Submenu(submenu) => { - wry_menu.add_submenu( - &submenu.title, - submenu.enabled, - to_wry_menu(custom_menu_items, submenu.inner), - ); - } - } - } - wry_menu -} - -fn create_webview( +fn create_webview( window_id: WebviewId, event_loop: &EventLoopWindowTarget>, web_context_store: &WebContextStore, context: Context, pending: PendingWindow>, + before_webview_creation: Option, ) -> Result { #[allow(unused_mut)] let PendingWindow { @@ -3088,7 +2593,6 @@ fn create_webview( label, ipc_handler, url, - menu_ids, #[cfg(target_os = "android")] on_webview_created, .. @@ -3119,14 +2623,6 @@ fn create_webview( } let is_window_transparent = window_builder.inner.window.transparent; - let menu_items = if let Some(menu) = window_builder.menu { - let mut menu_items = HashMap::new(); - let menu = to_wry_menu(&mut menu_items, menu); - window_builder.inner = window_builder.inner.with_menu(menu); - Some(menu_items) - } else { - None - }; let window = window_builder.inner.build(event_loop).unwrap(); context.webview_id_map.insert(window.id(), window_id); @@ -3134,6 +2630,32 @@ fn create_webview( if window_builder.center { let _ = center_window(&window, window.inner_size()); } + + if let Some(handler) = before_webview_creation { + let raw = RawWindow { + #[cfg(windows)] + hwnd: window.hwnd(), + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + gtk_window: window.gtk_window(), + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + default_vbox: window.default_vbox(), + _marker: &std::marker::PhantomData, + }; + handler(raw); + } + let mut webview_builder = WebViewBuilder::new(window) .map_err(|e| Error::CreateWebview(Box::new(e)))? .with_url(&url) @@ -3171,12 +2693,8 @@ fn create_webview( } if let Some(handler) = ipc_handler { - webview_builder = webview_builder.with_ipc_handler(create_ipc_handler( - context, - label.clone(), - menu_ids, - handler, - )); + webview_builder = + webview_builder.with_ipc_handler(create_ipc_handler(context, label.clone(), handler)); } for (scheme, protocol) in uri_scheme_protocols { @@ -3291,9 +2809,7 @@ fn create_webview( web_context_key }, }), - menu_items, window_event_listeners, - menu_event_listeners: Default::default(), }) } @@ -3301,7 +2817,6 @@ fn create_webview( fn create_ipc_handler( context: Context, label: String, - menu_ids: Arc>>, handler: WebviewIpcHandler>, ) -> Box { Box::new(move |window, request| { @@ -3313,7 +2828,6 @@ fn create_ipc_handler( context: context.clone(), }, label: label.clone(), - menu_ids: menu_ids.clone(), }, request, ); diff --git a/core/tauri-runtime-wry/src/system_tray.rs b/core/tauri-runtime-wry/src/system_tray.rs deleted file mode 100644 index 594e13cc4908..000000000000 --- a/core/tauri-runtime-wry/src/system_tray.rs +++ /dev/null @@ -1,238 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -pub use tauri_runtime::{ - menu::{ - Menu, MenuEntry, MenuItem, MenuUpdate, Submenu, SystemTrayMenu, SystemTrayMenuEntry, - SystemTrayMenuItem, TrayHandle, - }, - Icon, SystemTrayEvent, -}; -use wry::application::event_loop::EventLoopWindowTarget; -pub use wry::application::{ - event::TrayEvent, - event_loop::EventLoopProxy, - menu::{ - ContextMenu as WryContextMenu, CustomMenuItem as WryCustomMenuItem, MenuItem as WryMenuItem, - }, - system_tray::Icon as WryTrayIcon, - TrayId as WryTrayId, -}; - -#[cfg(target_os = "macos")] -pub use wry::application::platform::macos::{ - CustomMenuItemExtMacOS, SystemTrayBuilderExtMacOS, SystemTrayExtMacOS, -}; - -use wry::application::system_tray::{SystemTray as WrySystemTray, SystemTrayBuilder}; - -use crate::{send_user_message, Context, Error, Message, Result, TrayId, TrayMessage}; - -use tauri_runtime::{menu::MenuHash, SystemTray, UserEvent}; - -use std::{ - collections::HashMap, - fmt, - sync::{Arc, Mutex}, -}; - -pub type GlobalSystemTrayEventHandler = Box; -pub type GlobalSystemTrayEventListeners = Arc>>>; - -pub type SystemTrayEventHandler = Box; -pub type SystemTrayEventListeners = Arc>>>; -pub type SystemTrayItems = Arc>>; - -#[derive(Clone, Default)] -pub struct TrayContext { - pub tray: Arc>>, - pub listeners: SystemTrayEventListeners, - pub items: SystemTrayItems, -} - -impl fmt::Debug for TrayContext { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("TrayContext") - .field("items", &self.items) - .finish() - } -} - -#[derive(Clone, Default)] -pub struct SystemTrayManager { - pub trays: Arc>>, - pub global_listeners: GlobalSystemTrayEventListeners, -} - -impl fmt::Debug for SystemTrayManager { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("SystemTrayManager") - .field("trays", &self.trays) - .finish() - } -} - -/// Wrapper around a [`wry::application::system_tray::Icon`] that can be created from an [`WindowIcon`]. -pub struct TrayIcon(pub(crate) WryTrayIcon); - -impl TryFrom for TrayIcon { - type Error = Error; - fn try_from(icon: Icon) -> std::result::Result { - WryTrayIcon::from_rgba(icon.rgba, icon.width, icon.height) - .map(Self) - .map_err(crate::icon_err) - } -} - -pub fn create_tray( - id: WryTrayId, - system_tray: SystemTray, - event_loop: &EventLoopWindowTarget, -) -> crate::Result<(WrySystemTray, HashMap)> { - let icon = TrayIcon::try_from(system_tray.icon.expect("tray icon not set"))?; - - let mut items = HashMap::new(); - - #[allow(unused_mut)] - let mut builder = SystemTrayBuilder::new( - icon.0, - system_tray - .menu - .map(|menu| to_wry_context_menu(&mut items, menu)), - ) - .with_id(id); - - #[cfg(target_os = "macos")] - { - builder = builder - .with_icon_as_template(system_tray.icon_as_template) - .with_menu_on_left_click(system_tray.menu_on_left_click); - - if let Some(title) = system_tray.title { - builder = builder.with_title(&title); - } - } - - if let Some(tooltip) = system_tray.tooltip { - builder = builder.with_tooltip(&tooltip); - } - - let tray = builder - .build(event_loop) - .map_err(|e| Error::SystemTray(Box::new(e)))?; - - Ok((tray, items)) -} - -#[derive(Debug, Clone)] -pub struct SystemTrayHandle { - pub(crate) context: Context, - pub(crate) id: TrayId, - pub(crate) proxy: EventLoopProxy>, -} - -impl TrayHandle for SystemTrayHandle { - fn set_icon(&self, icon: Icon) -> Result<()> { - self - .proxy - .send_event(Message::Tray(self.id, TrayMessage::UpdateIcon(icon))) - .map_err(|_| Error::FailedToSendMessage) - } - - fn set_menu(&self, menu: SystemTrayMenu) -> Result<()> { - self - .proxy - .send_event(Message::Tray(self.id, TrayMessage::UpdateMenu(menu))) - .map_err(|_| Error::FailedToSendMessage) - } - - fn update_item(&self, id: u16, update: MenuUpdate) -> Result<()> { - self - .proxy - .send_event(Message::Tray(self.id, TrayMessage::UpdateItem(id, update))) - .map_err(|_| Error::FailedToSendMessage) - } - - #[cfg(target_os = "macos")] - fn set_icon_as_template(&self, is_template: bool) -> tauri_runtime::Result<()> { - self - .proxy - .send_event(Message::Tray( - self.id, - TrayMessage::UpdateIconAsTemplate(is_template), - )) - .map_err(|_| Error::FailedToSendMessage) - } - - #[cfg(target_os = "macos")] - fn set_title(&self, title: &str) -> tauri_runtime::Result<()> { - self - .proxy - .send_event(Message::Tray( - self.id, - TrayMessage::UpdateTitle(title.to_owned()), - )) - .map_err(|_| Error::FailedToSendMessage) - } - - fn set_tooltip(&self, tooltip: &str) -> Result<()> { - self - .proxy - .send_event(Message::Tray( - self.id, - TrayMessage::UpdateTooltip(tooltip.to_owned()), - )) - .map_err(|_| Error::FailedToSendMessage) - } - - fn destroy(&self) -> Result<()> { - let (tx, rx) = std::sync::mpsc::channel(); - send_user_message( - &self.context, - Message::Tray(self.id, TrayMessage::Destroy(tx)), - )?; - rx.recv().unwrap()?; - Ok(()) - } -} - -impl From for crate::MenuItemWrapper { - fn from(item: SystemTrayMenuItem) -> Self { - match item { - SystemTrayMenuItem::Separator => Self(WryMenuItem::Separator), - _ => unimplemented!(), - } - } -} - -pub fn to_wry_context_menu( - custom_menu_items: &mut HashMap, - menu: SystemTrayMenu, -) -> WryContextMenu { - let mut tray_menu = WryContextMenu::new(); - for item in menu.items { - match item { - SystemTrayMenuEntry::CustomItem(c) => { - #[allow(unused_mut)] - let mut item = tray_menu.add_item(crate::MenuItemAttributesWrapper::from(&c).0); - #[cfg(target_os = "macos")] - if let Some(native_image) = c.native_image { - item.set_native_image(crate::NativeImageWrapper::from(native_image).0); - } - custom_menu_items.insert(c.id, item); - } - SystemTrayMenuEntry::NativeItem(i) => { - tray_menu.add_native_item(crate::MenuItemWrapper::from(i).0); - } - SystemTrayMenuEntry::Submenu(submenu) => { - tray_menu.add_submenu( - &submenu.title, - submenu.enabled, - to_wry_context_menu(custom_menu_items, submenu.inner), - ); - } - } - } - tray_menu -} diff --git a/core/tauri-runtime/Cargo.toml b/core/tauri-runtime/Cargo.toml index ca1cd004967a..6c40c1c3c6a4 100644 --- a/core/tauri-runtime/Cargo.toml +++ b/core/tauri-runtime/Cargo.toml @@ -42,12 +42,12 @@ features = [ "Win32_Foundation" ] gtk = { version = "0.16", features = [ "v3_24" ] } [target."cfg(target_os = \"android\")".dependencies] -jni = "0.20" +jni = "0.21" [target."cfg(target_os = \"macos\")".dependencies] url = "2" [features] devtools = [ ] -system-tray = [ ] macos-private-api = [ ] + diff --git a/core/tauri-runtime/src/lib.rs b/core/tauri-runtime/src/lib.rs index dca99d511553..25c743b8ae35 100644 --- a/core/tauri-runtime/src/lib.rs +++ b/core/tauri-runtime/src/lib.rs @@ -20,8 +20,6 @@ use url::Url; use uuid::Uuid; pub mod http; -/// Create window and system tray menus. -pub mod menu; /// Types useful for interacting with a user's monitors. pub mod monitor; pub mod webview; @@ -31,7 +29,7 @@ use monitor::Monitor; use webview::WindowBuilder; use window::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, - CursorIcon, DetachedWindow, PendingWindow, WindowEvent, + CursorIcon, DetachedWindow, PendingWindow, RawWindow, WindowEvent, }; use crate::http::{ @@ -41,156 +39,6 @@ use crate::http::{ InvalidUri, }; -#[cfg(all(desktop, feature = "system-tray"))] -use std::fmt; - -pub type TrayId = u16; -pub type TrayEventHandler = dyn Fn(&SystemTrayEvent) + Send + 'static; - -#[cfg(all(desktop, feature = "system-tray"))] -#[non_exhaustive] -pub struct SystemTray { - pub id: TrayId, - pub icon: Option, - pub menu: Option, - #[cfg(target_os = "macos")] - pub icon_as_template: bool, - #[cfg(target_os = "macos")] - pub menu_on_left_click: bool, - #[cfg(target_os = "macos")] - pub title: Option, - pub on_event: Option>, - pub tooltip: Option, -} - -#[cfg(all(desktop, feature = "system-tray"))] -impl fmt::Debug for SystemTray { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut d = f.debug_struct("SystemTray"); - d.field("id", &self.id) - .field("icon", &self.icon) - .field("menu", &self.menu); - #[cfg(target_os = "macos")] - { - d.field("icon_as_template", &self.icon_as_template) - .field("menu_on_left_click", &self.menu_on_left_click) - .field("title", &self.title); - } - d.finish() - } -} - -#[cfg(all(desktop, feature = "system-tray"))] -impl Clone for SystemTray { - fn clone(&self) -> Self { - Self { - id: self.id, - icon: self.icon.clone(), - menu: self.menu.clone(), - on_event: None, - #[cfg(target_os = "macos")] - icon_as_template: self.icon_as_template, - #[cfg(target_os = "macos")] - menu_on_left_click: self.menu_on_left_click, - #[cfg(target_os = "macos")] - title: self.title.clone(), - tooltip: self.tooltip.clone(), - } - } -} - -#[cfg(all(desktop, feature = "system-tray"))] -impl Default for SystemTray { - fn default() -> Self { - Self { - id: rand::random(), - icon: None, - menu: None, - #[cfg(target_os = "macos")] - icon_as_template: false, - #[cfg(target_os = "macos")] - menu_on_left_click: false, - #[cfg(target_os = "macos")] - title: None, - on_event: None, - tooltip: None, - } - } -} - -#[cfg(all(desktop, feature = "system-tray"))] -impl SystemTray { - /// Creates a new system tray that only renders an icon. - pub fn new() -> Self { - Default::default() - } - - pub fn menu(&self) -> Option<&menu::SystemTrayMenu> { - self.menu.as_ref() - } - - /// Sets the tray id. - #[must_use] - pub fn with_id(mut self, id: TrayId) -> Self { - self.id = id; - self - } - - /// Sets the tray icon. - #[must_use] - pub fn with_icon(mut self, icon: Icon) -> Self { - self.icon.replace(icon); - self - } - - /// Sets the tray icon as template. - #[cfg(target_os = "macos")] - #[must_use] - pub fn with_icon_as_template(mut self, is_template: bool) -> Self { - self.icon_as_template = is_template; - self - } - - /// Sets whether the menu should appear when the tray receives a left click. Defaults to `true`. - #[cfg(target_os = "macos")] - #[must_use] - pub fn with_menu_on_left_click(mut self, menu_on_left_click: bool) -> Self { - self.menu_on_left_click = menu_on_left_click; - self - } - - #[cfg(target_os = "macos")] - #[must_use] - pub fn with_title(mut self, title: &str) -> Self { - self.title = Some(title.to_owned()); - self - } - - /// Sets the tray icon tooltip. - /// - /// ## Platform-specific: - /// - /// - **Linux:** Unsupported - #[must_use] - pub fn with_tooltip(mut self, tooltip: &str) -> Self { - self.tooltip = Some(tooltip.to_owned()); - self - } - - /// Sets the menu to show when the system tray is right clicked. - #[must_use] - pub fn with_menu(mut self, menu: menu::SystemTrayMenu) -> Self { - self.menu.replace(menu); - self - } - - #[must_use] - pub fn on_event(mut self, f: F) -> Self { - self.on_event.replace(Box::new(f)); - self - } -} - /// Type of user attention requested on a window. #[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)] #[serde(tag = "type")] @@ -243,11 +91,6 @@ pub enum Error { /// Failed to serialize/deserialize. #[error("JSON error: {0}")] Json(#[from] serde_json::Error), - /// Encountered an error creating the app system tray. - #[cfg(all(desktop, feature = "system-tray"))] - #[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))] - #[error("error encountered during tray setup: {0}")] - SystemTray(Box), /// Failed to load window icon. #[error("invalid icon: {0}")] InvalidIcon(Box), @@ -328,24 +171,6 @@ pub enum ExitRequestedEventAction { Prevent, } -/// A system tray event. -#[derive(Debug)] -pub enum SystemTrayEvent { - MenuItemClick(u16), - LeftClick { - position: PhysicalPosition, - size: PhysicalSize, - }, - RightClick { - position: PhysicalPosition, - size: PhysicalSize, - }, - DoubleClick { - position: PhysicalPosition, - size: PhysicalSize, - }, -} - /// Metadata for a runtime event loop iteration on `run_iteration`. #[derive(Debug, Clone, Default)] pub struct RunIteration { @@ -373,22 +198,15 @@ pub trait RuntimeHandle: Debug + Clone + Send + Sync + Sized + 'st fn create_proxy(&self) -> >::EventLoopProxy; /// Create a new webview window. - fn create_window( + fn create_window( &self, pending: PendingWindow, + before_webview_creation: Option, ) -> Result>; /// Run a task on the main thread. fn run_on_main_thread(&self, f: F) -> Result<()>; - /// Adds an icon to the system tray with the specified menu items. - #[cfg(all(desktop, feature = "system-tray"))] - #[cfg_attr(doc_cfg, doc(cfg(all(desktop, feature = "system-tray"))))] - fn system_tray( - &self, - system_tray: SystemTray, - ) -> Result<>::TrayHandler>; - fn raw_display_handle(&self) -> RawDisplayHandle; fn primary_monitor(&self) -> Option; @@ -407,9 +225,9 @@ pub trait RuntimeHandle: Debug + Clone + Send + Sync + Sized + 'st /// Finds an Android class in the project scope. #[cfg(target_os = "android")] fn find_class<'a>( - &'a self, - env: jni::JNIEnv<'a>, - activity: jni::objects::JObject<'a>, + &self, + env: &mut jni::JNIEnv<'a>, + activity: &jni::objects::JObject<'_>, name: impl Into, ) -> std::result::Result, jni::errors::Error>; @@ -419,34 +237,35 @@ pub trait RuntimeHandle: Debug + Clone + Send + Sync + Sized + 'st #[cfg(target_os = "android")] fn run_on_android_context(&self, f: F) where - F: FnOnce(jni::JNIEnv<'_>, jni::objects::JObject<'_>, jni::objects::JObject<'_>) - + Send - + 'static; + F: FnOnce(&mut jni::JNIEnv, &jni::objects::JObject, &jni::objects::JObject) + Send + 'static; } pub trait EventLoopProxy: Debug + Clone + Send + Sync { fn send_event(&self, event: T) -> Result<()>; } +#[derive(Default)] +pub struct RuntimeInitArgs { + #[cfg(windows)] + pub msg_hook: Option bool + 'static>>, +} + /// The webview runtime interface. pub trait Runtime: Debug + Sized + 'static { /// The message dispatcher. type Dispatcher: Dispatch; /// The runtime handle type. type Handle: RuntimeHandle; - /// The tray handler type. - #[cfg(all(desktop, feature = "system-tray"))] - type TrayHandler: menu::TrayHandle; /// The proxy type. type EventLoopProxy: EventLoopProxy; /// Creates a new webview runtime. Must be used on the main thread. - fn new() -> Result; + fn new(args: RuntimeInitArgs) -> Result; /// Creates a new webview runtime on any thread. #[cfg(any(windows, target_os = "linux"))] #[cfg_attr(doc_cfg, doc(cfg(any(windows, target_os = "linux"))))] - fn new_any_thread() -> Result; + fn new_any_thread(args: RuntimeInitArgs) -> Result; /// Creates an `EventLoopProxy` that can be used to dispatch user events to the main event loop. fn create_proxy(&self) -> Self::EventLoopProxy; @@ -455,17 +274,11 @@ pub trait Runtime: Debug + Sized + 'static { fn handle(&self) -> Self::Handle; /// Create a new webview window. - fn create_window(&self, pending: PendingWindow) -> Result>; - - /// Adds the icon to the system tray with the specified menu items. - #[cfg(all(desktop, feature = "system-tray"))] - #[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))] - fn system_tray(&self, system_tray: SystemTray) -> Result; - - /// Registers a system tray event handler. - #[cfg(all(desktop, feature = "system-tray"))] - #[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))] - fn on_system_tray_event(&mut self, f: F); + fn create_window( + &self, + pending: PendingWindow, + before_webview_creation: Option, + ) -> Result>; fn primary_monitor(&self) -> Option; fn available_monitors(&self) -> Vec; @@ -520,9 +333,6 @@ pub trait Dispatch: Debug + Clone + Send + Sync + Sized + 'static /// Registers a window event handler. fn on_window_event(&self, f: F) -> Uuid; - /// Registers a window event handler. - fn on_menu_event(&self, f: F) -> Uuid; - /// Runs a closure with the platform webview object as argument. fn with_webview) + Send + 'static>(&self, f: F) -> Result<()>; @@ -606,9 +416,6 @@ pub trait Dispatch: Debug + Clone + Send + Sync + Sized + 'static /// Gets the window's current title. fn title(&self) -> Result; - /// Gets the window menu current visibility state. - fn is_menu_visible(&self) -> Result; - /// Returns the monitor on which the window currently resides. /// /// Returns None if current monitor can't be detected. @@ -632,6 +439,16 @@ pub trait Dispatch: Debug + Clone + Send + Sync + Sized + 'static ))] fn gtk_window(&self) -> Result; + /// Returns the vertical [`gtk::Box`] that is added by default as the sole child of this window. + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + fn default_vbox(&self) -> Result; + fn raw_window_handle(&self) -> Result; /// Returns the current window theme. @@ -651,9 +468,10 @@ pub trait Dispatch: Debug + Clone + Send + Sync + Sized + 'static fn request_user_attention(&self, request_type: Option) -> Result<()>; /// Create a new webview window. - fn create_window( + fn create_window( &mut self, pending: PendingWindow, + before_webview_creation: Option, ) -> Result>; /// Updates the window resizable flag. @@ -701,12 +519,6 @@ pub trait Dispatch: Debug + Clone + Send + Sync + Sized + 'static /// Unminimizes the window. fn unminimize(&self) -> Result<()>; - /// Shows the window menu. - fn show_menu(&self) -> Result<()>; - - /// Hides the window menu. - fn hide_menu(&self) -> Result<()>; - /// Shows the window. fn show(&self) -> Result<()>; @@ -780,7 +592,4 @@ pub trait Dispatch: Debug + Clone + Send + Sync + Sized + 'static /// Executes javascript on the window this [`Dispatch`] represents. fn eval_script>(&self, script: S) -> Result<()>; - - /// Applies the specified `update` to the menu item associated with the given `id`. - fn update_menu_item(&self, id: u16, update: menu::MenuUpdate) -> Result<()>; } diff --git a/core/tauri-runtime/src/menu.rs b/core/tauri-runtime/src/menu.rs deleted file mode 100644 index bc65aa890b06..000000000000 --- a/core/tauri-runtime/src/menu.rs +++ /dev/null @@ -1,747 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use std::{ - collections::hash_map::DefaultHasher, - fmt, - hash::{Hash, Hasher}, -}; - -pub type MenuHash = u16; -pub type MenuId = String; -pub type MenuIdRef<'a> = &'a str; - -/// Named images defined by the system. -#[cfg(target_os = "macos")] -#[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))] -#[derive(Debug, Clone)] -pub enum NativeImage { - /// An add item template image. - Add, - /// Advanced preferences toolbar icon for the preferences window. - Advanced, - /// A Bluetooth template image. - Bluetooth, - /// Bookmarks image suitable for a template. - Bookmarks, - /// A caution image. - Caution, - /// A color panel toolbar icon. - ColorPanel, - /// A column view mode template image. - ColumnView, - /// A computer icon. - Computer, - /// An enter full-screen mode template image. - EnterFullScreen, - /// Permissions for all users. - Everyone, - /// An exit full-screen mode template image. - ExitFullScreen, - /// A cover flow view mode template image. - FlowView, - /// A folder image. - Folder, - /// A burnable folder icon. - FolderBurnable, - /// A smart folder icon. - FolderSmart, - /// A link template image. - FollowLinkFreestanding, - /// A font panel toolbar icon. - FontPanel, - /// A `go back` template image. - GoLeft, - /// A `go forward` template image. - GoRight, - /// Home image suitable for a template. - Home, - /// An iChat Theater template image. - IChatTheater, - /// An icon view mode template image. - IconView, - /// An information toolbar icon. - Info, - /// A template image used to denote invalid data. - InvalidDataFreestanding, - /// A generic left-facing triangle template image. - LeftFacingTriangle, - /// A list view mode template image. - ListView, - /// A locked padlock template image. - LockLocked, - /// An unlocked padlock template image. - LockUnlocked, - /// A horizontal dash, for use in menus. - MenuMixedState, - /// A check mark template image, for use in menus. - MenuOnState, - /// A MobileMe icon. - MobileMe, - /// A drag image for multiple items. - MultipleDocuments, - /// A network icon. - Network, - /// A path button template image. - Path, - /// General preferences toolbar icon for the preferences window. - PreferencesGeneral, - /// A Quick Look template image. - QuickLook, - /// A refresh template image. - RefreshFreestanding, - /// A refresh template image. - Refresh, - /// A remove item template image. - Remove, - /// A reveal contents template image. - RevealFreestanding, - /// A generic right-facing triangle template image. - RightFacingTriangle, - /// A share view template image. - Share, - /// A slideshow template image. - Slideshow, - /// A badge for a `smart` item. - SmartBadge, - /// Small green indicator, similar to iChat’s available image. - StatusAvailable, - /// Small clear indicator. - StatusNone, - /// Small yellow indicator, similar to iChat’s idle image. - StatusPartiallyAvailable, - /// Small red indicator, similar to iChat’s unavailable image. - StatusUnavailable, - /// A stop progress template image. - StopProgressFreestanding, - /// A stop progress button template image. - StopProgress, - - /// An image of the empty trash can. - TrashEmpty, - /// An image of the full trash can. - TrashFull, - /// Permissions for a single user. - User, - /// User account toolbar icon for the preferences window. - UserAccounts, - /// Permissions for a group of users. - UserGroup, - /// Permissions for guests. - UserGuest, -} - -#[derive(Debug, Clone)] -pub enum MenuUpdate { - /// Modifies the enabled state of the menu item. - SetEnabled(bool), - /// Modifies the title (label) of the menu item. - SetTitle(String), - /// Modifies the selected state of the menu item. - SetSelected(bool), - /// Update native image. - #[cfg(target_os = "macos")] - #[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))] - SetNativeImage(NativeImage), -} - -pub trait TrayHandle: fmt::Debug + Clone + Send + Sync { - fn set_icon(&self, icon: crate::Icon) -> crate::Result<()>; - fn set_menu(&self, menu: crate::menu::SystemTrayMenu) -> crate::Result<()>; - fn update_item(&self, id: u16, update: MenuUpdate) -> crate::Result<()>; - #[cfg(target_os = "macos")] - fn set_icon_as_template(&self, is_template: bool) -> crate::Result<()>; - #[cfg(target_os = "macos")] - fn set_title(&self, title: &str) -> crate::Result<()>; - fn set_tooltip(&self, tooltip: &str) -> crate::Result<()>; - fn destroy(&self) -> crate::Result<()>; -} - -/// A window menu. -#[derive(Debug, Default, Clone)] -#[non_exhaustive] -pub struct Menu { - pub items: Vec, -} - -#[derive(Debug, Clone)] -#[non_exhaustive] -pub struct Submenu { - pub title: String, - pub enabled: bool, - pub inner: Menu, -} - -impl Submenu { - /// Creates a new submenu with the given title and menu items. - pub fn new>(title: S, menu: Menu) -> Self { - Self { - title: title.into(), - enabled: true, - inner: menu, - } - } -} - -impl Menu { - /// Creates a new window menu. - pub fn new() -> Self { - Default::default() - } - - /// Creates a menu filled with default menu items and submenus. - /// - /// ## Platform-specific: - /// - /// - **Windows**: - /// - File - /// - CloseWindow - /// - Quit - /// - Edit - /// - Cut - /// - Copy - /// - Paste - /// - Window - /// - Minimize - /// - CloseWindow - /// - /// - **Linux**: - /// - File - /// - CloseWindow - /// - Quit - /// - Window - /// - Minimize - /// - CloseWindow - /// - /// - **macOS**: - /// - App - /// - About - /// - Separator - /// - Services - /// - Separator - /// - Hide - /// - HideOthers - /// - ShowAll - /// - Separator - /// - Quit - /// - File - /// - CloseWindow - /// - Edit - /// - Undo - /// - Redo - /// - Separator - /// - Cut - /// - Copy - /// - Paste - /// - SelectAll - /// - View - /// - EnterFullScreen - /// - Window - /// - Minimize - /// - Zoom - /// - Separator - /// - CloseWindow - pub fn os_default(#[allow(unused)] app_name: &str) -> Self { - let mut menu = Menu::new(); - #[cfg(target_os = "macos")] - { - menu = menu.add_submenu(Submenu::new( - app_name, - Menu::new() - .add_native_item(MenuItem::About( - app_name.to_string(), - AboutMetadata::default(), - )) - .add_native_item(MenuItem::Separator) - .add_native_item(MenuItem::Services) - .add_native_item(MenuItem::Separator) - .add_native_item(MenuItem::Hide) - .add_native_item(MenuItem::HideOthers) - .add_native_item(MenuItem::ShowAll) - .add_native_item(MenuItem::Separator) - .add_native_item(MenuItem::Quit), - )); - } - - let mut file_menu = Menu::new(); - file_menu = file_menu.add_native_item(MenuItem::CloseWindow); - #[cfg(not(target_os = "macos"))] - { - file_menu = file_menu.add_native_item(MenuItem::Quit); - } - menu = menu.add_submenu(Submenu::new("File", file_menu)); - - #[cfg(not(target_os = "linux"))] - let mut edit_menu = Menu::new(); - #[cfg(target_os = "macos")] - { - edit_menu = edit_menu.add_native_item(MenuItem::Undo); - edit_menu = edit_menu.add_native_item(MenuItem::Redo); - edit_menu = edit_menu.add_native_item(MenuItem::Separator); - } - #[cfg(not(target_os = "linux"))] - { - edit_menu = edit_menu.add_native_item(MenuItem::Cut); - edit_menu = edit_menu.add_native_item(MenuItem::Copy); - edit_menu = edit_menu.add_native_item(MenuItem::Paste); - } - #[cfg(target_os = "macos")] - { - edit_menu = edit_menu.add_native_item(MenuItem::SelectAll); - } - #[cfg(not(target_os = "linux"))] - { - menu = menu.add_submenu(Submenu::new("Edit", edit_menu)); - } - #[cfg(target_os = "macos")] - { - menu = menu.add_submenu(Submenu::new( - "View", - Menu::new().add_native_item(MenuItem::EnterFullScreen), - )); - } - - let mut window_menu = Menu::new(); - window_menu = window_menu.add_native_item(MenuItem::Minimize); - #[cfg(target_os = "macos")] - { - window_menu = window_menu.add_native_item(MenuItem::Zoom); - window_menu = window_menu.add_native_item(MenuItem::Separator); - } - window_menu = window_menu.add_native_item(MenuItem::CloseWindow); - menu = menu.add_submenu(Submenu::new("Window", window_menu)); - - menu - } - - /// Creates a new window menu with the given items. - /// - /// # Examples - /// ``` - /// # use tauri_runtime::menu::{Menu, MenuItem, CustomMenuItem, Submenu}; - /// Menu::with_items([ - /// MenuItem::SelectAll.into(), - /// #[cfg(target_os = "macos")] - /// MenuItem::Redo.into(), - /// CustomMenuItem::new("toggle", "Toggle visibility").into(), - /// Submenu::new("View", Menu::new()).into(), - /// ]); - /// ``` - pub fn with_items>(items: I) -> Self { - Self { - items: items.into_iter().collect(), - } - } - - /// Adds the custom menu item to the menu. - #[must_use] - pub fn add_item(mut self, item: CustomMenuItem) -> Self { - self.items.push(MenuEntry::CustomItem(item)); - self - } - - /// Adds a native item to the menu. - #[must_use] - pub fn add_native_item(mut self, item: MenuItem) -> Self { - self.items.push(MenuEntry::NativeItem(item)); - self - } - - /// Adds an entry with submenu. - #[must_use] - pub fn add_submenu(mut self, submenu: Submenu) -> Self { - self.items.push(MenuEntry::Submenu(submenu)); - self - } -} - -/// A custom menu item. -#[derive(Debug, Clone)] -#[non_exhaustive] -pub struct CustomMenuItem { - pub id: MenuHash, - pub id_str: MenuId, - pub title: String, - pub keyboard_accelerator: Option, - pub enabled: bool, - pub selected: bool, - #[cfg(target_os = "macos")] - pub native_image: Option, -} - -impl CustomMenuItem { - /// Create new custom menu item. - pub fn new, T: Into>(id: I, title: T) -> Self { - let id_str = id.into(); - Self { - id: Self::hash(&id_str), - id_str, - title: title.into(), - keyboard_accelerator: None, - enabled: true, - selected: false, - #[cfg(target_os = "macos")] - native_image: None, - } - } - - /// Assign a keyboard shortcut to the menu action. - #[must_use] - pub fn accelerator>(mut self, accelerator: T) -> Self { - self.keyboard_accelerator.replace(accelerator.into()); - self - } - - #[cfg(target_os = "macos")] - #[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))] - #[must_use] - /// A native image do render on the menu item. - pub fn native_image(mut self, image: NativeImage) -> Self { - self.native_image.replace(image); - self - } - - /// Mark the item as disabled. - #[must_use] - pub fn disabled(mut self) -> Self { - self.enabled = false; - self - } - - /// Mark the item as selected. - #[must_use] - pub fn selected(mut self) -> Self { - self.selected = true; - self - } - - fn hash(id: &str) -> MenuHash { - let mut hasher = DefaultHasher::new(); - id.hash(&mut hasher); - hasher.finish() as MenuHash - } -} - -/// A system tray menu. -#[derive(Debug, Default, Clone)] -#[non_exhaustive] -pub struct SystemTrayMenu { - pub items: Vec, -} - -#[derive(Debug, Clone)] -#[non_exhaustive] -pub struct SystemTraySubmenu { - pub title: String, - pub enabled: bool, - pub inner: SystemTrayMenu, -} - -impl SystemTraySubmenu { - /// Creates a new submenu with the given title and menu items. - pub fn new>(title: S, menu: SystemTrayMenu) -> Self { - Self { - title: title.into(), - enabled: true, - inner: menu, - } - } -} - -impl SystemTrayMenu { - /// Creates a new system tray menu. - pub fn new() -> Self { - Default::default() - } - - /// Adds the custom menu item to the system tray menu. - #[must_use] - pub fn add_item(mut self, item: CustomMenuItem) -> Self { - self.items.push(SystemTrayMenuEntry::CustomItem(item)); - self - } - - /// Adds a native item to the system tray menu. - #[must_use] - pub fn add_native_item(mut self, item: SystemTrayMenuItem) -> Self { - self.items.push(SystemTrayMenuEntry::NativeItem(item)); - self - } - - /// Adds an entry with submenu. - #[must_use] - pub fn add_submenu(mut self, submenu: SystemTraySubmenu) -> Self { - self.items.push(SystemTrayMenuEntry::Submenu(submenu)); - self - } -} - -/// An entry on the system tray menu. -#[derive(Debug, Clone)] -pub enum SystemTrayMenuEntry { - /// A custom item. - CustomItem(CustomMenuItem), - /// A native item. - NativeItem(SystemTrayMenuItem), - /// An entry with submenu. - Submenu(SystemTraySubmenu), -} - -/// System tray menu item. -#[derive(Debug, Clone)] -#[non_exhaustive] -pub enum SystemTrayMenuItem { - /// A separator. - Separator, -} - -/// An entry on the system tray menu. -#[derive(Debug, Clone)] -pub enum MenuEntry { - /// A custom item. - CustomItem(CustomMenuItem), - /// A native item. - NativeItem(MenuItem), - /// An entry with submenu. - Submenu(Submenu), -} - -impl From for MenuEntry { - fn from(item: CustomMenuItem) -> Self { - Self::CustomItem(item) - } -} - -impl From for MenuEntry { - fn from(item: MenuItem) -> Self { - Self::NativeItem(item) - } -} - -impl From for MenuEntry { - fn from(submenu: Submenu) -> Self { - Self::Submenu(submenu) - } -} - -/// Application metadata for the [`MenuItem::About`] action. -/// -/// ## Platform-specific -/// -/// - **Windows / macOS / Android / iOS:** The metadata is ignored on these platforms. -#[derive(Debug, Clone, Default)] -#[non_exhaustive] -pub struct AboutMetadata { - /// The application name. - pub version: Option, - /// The authors of the application. - pub authors: Option>, - /// Application comments. - pub comments: Option, - /// The copyright of the application. - pub copyright: Option, - /// The license of the application. - pub license: Option, - /// The application website. - pub website: Option, - /// The website label. - pub website_label: Option, -} - -impl AboutMetadata { - /// Creates the default metadata for the [`MenuItem::About`] action, which is just empty. - pub fn new() -> Self { - Default::default() - } - - /// Defines the application version. - pub fn version(mut self, version: impl Into) -> Self { - self.version.replace(version.into()); - self - } - - /// Defines the application authors. - pub fn authors(mut self, authors: Vec) -> Self { - self.authors.replace(authors); - self - } - - /// Defines the application comments. - pub fn comments(mut self, comments: impl Into) -> Self { - self.comments.replace(comments.into()); - self - } - - /// Defines the application copyright. - pub fn copyright(mut self, copyright: impl Into) -> Self { - self.copyright.replace(copyright.into()); - self - } - - /// Defines the application license. - pub fn license(mut self, license: impl Into) -> Self { - self.license.replace(license.into()); - self - } - - /// Defines the application's website link. - pub fn website(mut self, website: impl Into) -> Self { - self.website.replace(website.into()); - self - } - - /// Defines the application's website link name. - pub fn website_label(mut self, website_label: impl Into) -> Self { - self.website_label.replace(website_label.into()); - self - } -} - -/// A menu item, bound to a pre-defined action or `Custom` emit an event. Note that status bar only -/// supports `Custom` menu item variants. And on the menu bar, some platforms might not support some -/// of the variants. Unsupported variant will be no-op on such platform. -#[derive(Debug, Clone)] -#[non_exhaustive] -pub enum MenuItem { - /// Shows a standard "About" item. - /// - /// The first value is the application name, and the second is its metadata. - /// - /// ## Platform-specific - /// - /// - **Windows / Android / iOS:** Unsupported - /// - **Linux:** The metadata is only applied on Linux - /// - About(String, AboutMetadata), - - /// A standard "hide the app" menu item. - /// - /// ## Platform-specific - /// - /// - **Android / iOS:** Unsupported - /// - Hide, - - /// A standard "Services" menu item. - /// - /// ## Platform-specific - /// - /// - **Windows / Linux / Android / iOS:** Unsupported - /// - Services, - - /// A "hide all other windows" menu item. - /// - /// ## Platform-specific - /// - /// - **Windows / Linux / Android / iOS:** Unsupported - /// - HideOthers, - - /// A menu item to show all the windows for this app. - /// - /// ## Platform-specific - /// - /// - **Windows / Linux / Android / iOS:** Unsupported - /// - ShowAll, - - /// Close the current window. - /// - /// ## Platform-specific - /// - /// - **Android / iOS:** Unsupported - /// - CloseWindow, - - /// A "quit this app" menu icon. - /// - /// ## Platform-specific - /// - /// - **Android / iOS:** Unsupported - /// - Quit, - - /// A menu item for enabling copying (often text) from responders. - /// - /// ## Platform-specific - /// - /// - **Android / iOS / Linux:** Unsupported - /// - Copy, - - /// A menu item for enabling cutting (often text) from responders. - /// - /// ## Platform-specific - /// - /// - **Android / iOS / Linux:** Unsupported - /// - Cut, - - /// An "undo" menu item; particularly useful for supporting the cut/copy/paste/undo lifecycle - /// of events. - /// - /// ## Platform-specific - /// - /// - **Windows / Linux / Android / iOS:** Unsupported - /// - Undo, - - /// An "redo" menu item; particularly useful for supporting the cut/copy/paste/undo lifecycle - /// of events. - /// - /// ## Platform-specific - /// - /// - **Windows / Linux / Android / iOS:** Unsupported - /// - Redo, - - /// A menu item for selecting all (often text) from responders. - /// - /// ## Platform-specific - /// - /// - **Windows / Android / iOS / Linux:** Unsupported - /// - SelectAll, - - /// A menu item for pasting (often text) into responders. - /// - /// ## Platform-specific - /// - /// - **Android / iOS / Linux:** Unsupported - /// - Paste, - - /// A standard "enter full screen" item. - /// - /// ## Platform-specific - /// - /// - **Windows / Linux / Android / iOS:** Unsupported - /// - EnterFullScreen, - - /// An item for minimizing the window with the standard system controls. - /// - /// ## Platform-specific - /// - /// - **Android / iOS:** Unsupported - /// - Minimize, - - /// An item for instructing the app to zoom - /// - /// ## Platform-specific - /// - /// - **Windows / Linux / Android / iOS:** Unsupported - /// - Zoom, - - /// Represents a Separator - /// - /// ## Platform-specific - /// - /// - **Android / iOS:** Unsupported - /// - Separator, -} diff --git a/core/tauri-runtime/src/webview.rs b/core/tauri-runtime/src/webview.rs index 9567a4024d54..79814a4f8071 100644 --- a/core/tauri-runtime/src/webview.rs +++ b/core/tauri-runtime/src/webview.rs @@ -4,7 +4,7 @@ //! Items specific to the [`Runtime`](crate::Runtime)'s webview. -use crate::{menu::Menu, window::DetachedWindow, Icon}; +use crate::{window::DetachedWindow, Icon}; #[cfg(target_os = "macos")] use tauri_utils::TitleBarStyle; @@ -153,10 +153,6 @@ pub trait WindowBuilder: WindowBuilderBase { /// Initializes a new webview builder from a [`WindowConfig`] fn with_config(config: WindowConfig) -> Self; - /// Sets the menu for the window. - #[must_use] - fn menu(self, menu: Menu) -> Self; - /// Show window in the center of the screen. #[must_use] fn center(self) -> Self; @@ -330,9 +326,6 @@ pub trait WindowBuilder: WindowBuilderBase { /// Whether the icon was set or not. fn has_icon(&self) -> bool; - - /// Gets the window menu. - fn get_menu(&self) -> Option<&Menu>; } /// IPC handler. diff --git a/core/tauri-runtime/src/window.rs b/core/tauri-runtime/src/window.rs index b04fd4ff598d..d2dfcb8a4b4b 100644 --- a/core/tauri-runtime/src/window.rs +++ b/core/tauri-runtime/src/window.rs @@ -6,19 +6,20 @@ use crate::{ http::{Request as HttpRequest, Response as HttpResponse}, - menu::{Menu, MenuEntry, MenuHash, MenuId}, webview::{WebviewAttributes, WebviewIpcHandler}, Dispatch, Runtime, UserEvent, WindowBuilder, }; -use serde::{Deserialize, Deserializer, Serialize}; + +use serde::{Deserialize, Deserializer}; use tauri_utils::{config::WindowConfig, Theme}; use url::Url; use std::{ collections::HashMap, hash::{Hash, Hasher}, + marker::PhantomData, path::PathBuf, - sync::{mpsc::Sender, Arc, Mutex}, + sync::mpsc::Sender, }; type UriSchemeProtocol = @@ -82,25 +83,6 @@ pub enum FileDropEvent { Cancelled, } -/// A menu event. -#[derive(Debug, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct MenuEvent { - pub menu_item_id: u16, -} - -fn get_menu_ids(map: &mut HashMap, menu: &Menu) { - for item in &menu.items { - match item { - MenuEntry::CustomItem(c) => { - map.insert(c.id, c.id_str.clone()); - } - MenuEntry::Submenu(s) => get_menu_ids(map, &s.inner), - _ => {} - } - } -} - /// Describes the appearance of the mouse cursor. #[non_exhaustive] #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)] @@ -209,10 +191,10 @@ impl<'de> Deserialize<'de> for CursorIcon { } #[cfg(target_os = "android")] -pub struct CreationContext<'a> { - pub env: jni::JNIEnv<'a>, - pub activity: jni::objects::JObject<'a>, - pub webview: jni::objects::JObject<'a>, +pub struct CreationContext<'a, 'b> { + pub env: &'a mut jni::JNIEnv<'b>, + pub activity: &'a jni::objects::JObject<'b>, + pub webview: &'a jni::objects::JObject<'b>, } /// A webview window that has yet to be built. @@ -231,9 +213,6 @@ pub struct PendingWindow> { /// How to handle IPC calls on the webview window. pub ipc_handler: Option>, - /// Maps runtime id to a string menu id. - pub menu_ids: Arc>>, - /// A handler to decide if incoming url is allowed to navigate. pub navigation_handler: Option>, @@ -243,7 +222,7 @@ pub struct PendingWindow> { #[cfg(target_os = "android")] #[allow(clippy::type_complexity)] pub on_webview_created: - Option) -> Result<(), jni::errors::Error> + Send>>, + Option) -> Result<(), jni::errors::Error> + Send>>, pub web_resource_request_handler: Option>, } @@ -268,10 +247,6 @@ impl> PendingWindow { webview_attributes: WebviewAttributes, label: impl Into, ) -> crate::Result { - let mut menu_ids = HashMap::new(); - if let Some(menu) = window_builder.get_menu() { - get_menu_ids(&mut menu_ids, menu); - } let label = label.into(); if !is_label_valid(&label) { Err(crate::Error::InvalidWindowLabel) @@ -282,7 +257,6 @@ impl> PendingWindow { uri_scheme_protocols: Default::default(), label, ipc_handler: None, - menu_ids: Arc::new(Mutex::new(menu_ids)), navigation_handler: Default::default(), url: "tauri://localhost".to_string(), #[cfg(target_os = "android")] @@ -300,10 +274,7 @@ impl> PendingWindow { ) -> crate::Result { let window_builder = <>::WindowBuilder>::with_config(window_config); - let mut menu_ids = HashMap::new(); - if let Some(menu) = window_builder.get_menu() { - get_menu_ids(&mut menu_ids, menu); - } + let label = label.into(); if !is_label_valid(&label) { Err(crate::Error::InvalidWindowLabel) @@ -314,7 +285,6 @@ impl> PendingWindow { uri_scheme_protocols: Default::default(), label, ipc_handler: None, - menu_ids: Arc::new(Mutex::new(menu_ids)), navigation_handler: Default::default(), url: "tauri://localhost".to_string(), #[cfg(target_os = "android")] @@ -324,15 +294,6 @@ impl> PendingWindow { } } - #[must_use] - pub fn set_menu(mut self, menu: Menu) -> Self { - let mut menu_ids = HashMap::new(); - get_menu_ids(&mut menu_ids, &menu); - *self.menu_ids.lock().unwrap() = menu_ids; - self.window_builder = self.window_builder.menu(menu); - self - } - pub fn register_uri_scheme_protocol< N: Into, H: Fn(&HttpRequest) -> Result> + Send + Sync + 'static, @@ -349,7 +310,7 @@ impl> PendingWindow { #[cfg(target_os = "android")] pub fn on_webview_created< - F: Fn(CreationContext<'_>) -> Result<(), jni::errors::Error> + Send + 'static, + F: Fn(CreationContext<'_, '_>) -> Result<(), jni::errors::Error> + Send + 'static, >( mut self, f: F, @@ -367,9 +328,6 @@ pub struct DetachedWindow> { /// The [`Dispatch`](crate::Dispatch) associated with the window. pub dispatcher: R::Dispatcher, - - /// Maps runtime id to a string menu id. - pub menu_ids: Arc>>, } impl> Clone for DetachedWindow { @@ -377,7 +335,6 @@ impl> Clone for DetachedWindow { Self { label: self.label.clone(), dispatcher: self.dispatcher.clone(), - menu_ids: self.menu_ids.clone(), } } } @@ -396,3 +353,28 @@ impl> PartialEq for DetachedWindow { self.label.eq(&other.label) } } + +/// A raw window type that contains fields to access +/// the HWND on Windows, gtk::ApplicationWindow on Linux and +/// NSView on macOS. +pub struct RawWindow<'a> { + #[cfg(windows)] + pub hwnd: isize, + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + pub gtk_window: &'a gtk::ApplicationWindow, + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + pub default_vbox: Option<&'a gtk::Box>, + pub _marker: &'a PhantomData<()>, +} diff --git a/core/tauri-utils/src/config.rs b/core/tauri-utils/src/config.rs index 872f616cba56..8ccd4d84ca4c 100644 --- a/core/tauri-utils/src/config.rs +++ b/core/tauri-utils/src/config.rs @@ -1416,9 +1416,9 @@ pub struct TauriConfig { /// Security configuration. #[serde(default)] pub security: SecurityConfig, - /// Configuration for app system tray. - #[serde(alias = "system-tray")] - pub system_tray: Option, + /// Configuration for app tray icon. + #[serde(alias = "tray-icon")] + pub tray_icon: Option, /// MacOS private API configuration. Enables the transparent background API and sets the `fullScreenEnabled` preference to `true`. #[serde(rename = "macOSPrivateApi", alias = "macos-private-api", default)] pub macos_private_api: bool, @@ -1428,7 +1428,7 @@ impl TauriConfig { /// Returns all Cargo features. pub fn all_features() -> Vec<&'static str> { vec![ - "system-tray", + "tray-icon", "macos-private-api", "isolation", "protocol-asset", @@ -1438,8 +1438,8 @@ impl TauriConfig { /// Returns the enabled Cargo features. pub fn features(&self) -> Vec<&str> { let mut features = Vec::new(); - if self.system_tray.is_some() { - features.push("system-tray"); + if self.tray_icon.is_some() { + features.push("tray-icon"); } if self.macos_private_api { features.push("macos-private-api"); @@ -1550,15 +1550,15 @@ pub struct UpdaterWindowsConfig { pub install_mode: WindowsUpdateInstallMode, } -/// Configuration for application system tray icon. +/// Configuration for application tray icon. /// -/// See more: https://tauri.app/v1/api/config#systemtrayconfig +/// See more: https://tauri.app/v1/api/config#trayiconconfig #[skip_serializing_none] #[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)] #[cfg_attr(feature = "schema", derive(JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct SystemTrayConfig { - /// Path to the default icon to use on the system tray. +pub struct TrayIconConfig { + /// Path to the default icon to use for the tray icon. #[serde(alias = "icon-path")] pub icon_path: PathBuf, /// A Boolean value that determines whether the image represents a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) image on macOS. @@ -1569,6 +1569,8 @@ pub struct SystemTrayConfig { pub menu_on_left_click: bool, /// Title for MacOS tray pub title: Option, + /// Tray icon tooltip on Windows and macOS + pub tooltip: Option, } /// General configuration for the iOS target. @@ -1838,7 +1840,7 @@ impl PackageConfig { /// The Tauri configuration object. /// It is read from a file where you can define your frontend assets, -/// configure the bundler and define a system tray. +/// configure the bundler and define a tray icon. /// /// The configuration file is generated by the /// [`tauri init`](https://tauri.app/v1/api/cli#init) command that lives in @@ -2566,19 +2568,21 @@ mod build { } } - impl ToTokens for SystemTrayConfig { + impl ToTokens for TrayIconConfig { fn to_tokens(&self, tokens: &mut TokenStream) { let icon_as_template = self.icon_as_template; let menu_on_left_click = self.menu_on_left_click; let icon_path = path_buf_lit(&self.icon_path); let title = opt_str_lit(self.title.as_ref()); + let tooltip = opt_str_lit(self.tooltip.as_ref()); literal_struct!( tokens, - SystemTrayConfig, + TrayIconConfig, icon_path, icon_as_template, menu_on_left_click, - title + title, + tooltip ); } } @@ -2615,7 +2619,7 @@ mod build { let windows = vec_lit(&self.windows, identity); let bundle = &self.bundle; let security = &self.security; - let system_tray = opt_lit(self.system_tray.as_ref()); + let tray_icon = opt_lit(self.tray_icon.as_ref()); let macos_private_api = self.macos_private_api; literal_struct!( @@ -2625,7 +2629,7 @@ mod build { windows, bundle, security, - system_tray, + tray_icon, macos_private_api ); } @@ -2718,7 +2722,7 @@ mod test { dangerous_remote_domain_ipc_access: Vec::new(), asset_protocol: AssetProtocolConfig::default(), }, - system_tray: None, + tray_icon: None, macos_private_api: false, }; diff --git a/core/tauri/Cargo.toml b/core/tauri/Cargo.toml index fa93ba5f9b59..4db8d809ab27 100644 --- a/core/tauri/Cargo.toml +++ b/core/tauri/Cargo.toml @@ -18,7 +18,6 @@ no-default-features = true features = [ "wry", "custom-protocol", - "system-tray", "devtools", "icon-png", "protocol-asset", @@ -68,6 +67,11 @@ infer = { version = "0.9", optional = true } png = { version = "0.17", optional = true } ico = { version = "0.2.0", optional = true } + +[target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\", target_os = \"windows\", target_os = \"macos\"))".dependencies] +muda = { version = "0.8", default-features = false } +tray-icon = { version = "0.8", default-features = false, optional = true } + [target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies] gtk = { version = "0.16", features = [ "v3_24" ] } glib = "0.16" @@ -81,16 +85,16 @@ objc = "0.2" [target."cfg(windows)".dependencies] webview2-com = "0.25" - [target."cfg(windows)".dependencies.windows] - version = "0.48" - features = [ "Win32_Foundation" ] +[target."cfg(windows)".dependencies.windows] +version = "0.48" +features = [ "Win32_Foundation" ] [target."cfg(any(target_os = \"android\", target_os = \"ios\"))".dependencies] log = "0.4" heck = "0.4" [target."cfg(target_os = \"android\")".dependencies] -jni = "0.20" +jni = "0.21" [target."cfg(target_os = \"ios\")".dependencies] libc = "0.2" @@ -114,18 +118,19 @@ tokio = { version = "1", features = [ "full" ] } cargo_toml = "0.15" [features] -default = [ "wry", "compression", "objc-exception" ] +default = [ "wry", "compression", "objc-exception", "tray-icon?/common-controls-v6", "muda/common-controls-v6" ] +tray-icon = [ "dep:tray-icon" ] test = [ ] compression = [ "tauri-macros/compression", "tauri-utils/compression" ] wry = [ "tauri-runtime-wry" ] objc-exception = [ "tauri-runtime-wry/objc-exception" ] linux-ipc-protocol = [ "tauri-runtime-wry/linux-protocol-body", "webkit2gtk/v2_40" ] +linux-libxdo = [ "tray-icon/libxdo", "muda/libxdo" ] isolation = [ "tauri-utils/isolation", "tauri-macros/isolation" ] custom-protocol = [ "tauri-macros/custom-protocol" ] native-tls = [ "reqwest/native-tls" ] native-tls-vendored = [ "reqwest/native-tls-vendored" ] rustls-tls = [ "reqwest/rustls-tls" ] -system-tray = [ "tauri-runtime/system-tray", "tauri-runtime-wry/system-tray" ] devtools = [ "tauri-runtime/devtools", "tauri-runtime-wry/devtools" ] dox = [ "tauri-runtime-wry/dox" ] process-relaunch-dangerous-allow-symlink-macos = [ "tauri-utils/process-relaunch-dangerous-allow-symlink-macos" ] diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index a09fb9131011..2cd80bdeee44 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -2,9 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -#[cfg(all(desktop, feature = "system-tray"))] -pub(crate) mod tray; - use crate::{ command::{CommandArg, CommandItem}, ipc::{ @@ -30,13 +27,24 @@ use crate::{ #[cfg(feature = "protocol-asset")] use crate::scope::FsScope; +#[cfg(desktop)] +use crate::menu::{Menu, MenuEvent}; +#[cfg(all(desktop, feature = "tray-icon"))] +use crate::tray::{TrayIcon, TrayIconBuilder, TrayIconEvent, TrayIconId}; +#[cfg(desktop)] +use crate::window::WindowMenu; use raw_window_handle::HasRawDisplayHandle; use serde::Deserialize; use serialize_to_javascript::{default_template, DefaultTemplate, Template}; use tauri_macros::default_runtime; -use tauri_runtime::window::{ - dpi::{PhysicalPosition, PhysicalSize}, - FileDropEvent, +#[cfg(desktop)] +use tauri_runtime::EventLoopProxy; +use tauri_runtime::{ + window::{ + dpi::{PhysicalPosition, PhysicalSize}, + FileDropEvent, + }, + RuntimeInitArgs, }; use tauri_utils::PackageInfo; @@ -46,17 +54,17 @@ use std::{ sync::{mpsc::Sender, Arc, Weak}, }; -use crate::runtime::menu::{Menu, MenuId, MenuIdRef}; - use crate::runtime::RuntimeHandle; #[cfg(target_os = "macos")] use crate::ActivationPolicy; -pub(crate) type GlobalMenuEventListener = Box) + Send + Sync>; +#[cfg(desktop)] +pub(crate) type GlobalMenuEventListener = Box; +#[cfg(all(desktop, feature = "tray-icon"))] +pub(crate) type GlobalTrayIconEventListener = + Box; pub(crate) type GlobalWindowEventListener = Box) + Send + Sync>; -#[cfg(all(desktop, feature = "system-tray"))] -type SystemTrayEventListener = Box, tray::SystemTrayEvent) + Send + Sync>; /// A closure that is run when the Tauri application is setting up. pub type SetupHook = Box) -> Result<(), Box> + Send>; @@ -199,35 +207,29 @@ pub enum RunEvent { MainEventsCleared, /// Emitted when the user wants to open the specified resource with the app. #[cfg(any(target_os = "macos", target_os = "ios"))] + #[cfg_attr(doc_cfg, doc(cfg(any(target_os = "macos", feature = "ios"))))] Opened { /// The URL of the resources that is being open. urls: Vec, }, + /// An event from a menu item, could be on the window menu bar, application menu bar (on macOS) or tray icon menu. + #[cfg(desktop)] + #[cfg_attr(doc_cfg, doc(cfg(desktop)))] + MenuEvent(crate::menu::MenuEvent), + /// An event from a tray icon. + #[cfg(all(desktop, feature = "tray-icon"))] + #[cfg_attr(doc_cfg, doc(cfg(all(desktop, feature = "tray-icon"))))] + TrayIconEvent(crate::tray::TrayIconEvent), } impl From for RunEvent { fn from(event: EventLoopMessage) -> Self { - match event {} - } -} - -/// A menu event that was triggered on a window. -#[default_runtime(crate::Wry, wry)] -#[derive(Debug)] -pub struct WindowMenuEvent { - pub(crate) menu_item_id: MenuId, - pub(crate) window: Window, -} - -impl WindowMenuEvent { - /// The menu item id. - pub fn menu_item_id(&self) -> MenuIdRef<'_> { - &self.menu_item_id - } - - /// The window that the menu belongs to. - pub fn window(&self) -> &Window { - &self.window + match event { + #[cfg(desktop)] + EventLoopMessage::MenuEvent(e) => Self::MenuEvent(e), + #[cfg(all(desktop, feature = "tray-icon"))] + EventLoopMessage::TrayIconEvent(e) => Self::TrayIconEvent(e), + } } } @@ -345,7 +347,7 @@ impl AppHandle { /// /// tauri::Builder::default() /// .setup(move |app| { - /// let handle = app.handle(); + /// let handle = app.handle().clone(); /// std::thread::spawn(move || { /// handle.plugin(init_plugin()); /// }); @@ -393,7 +395,7 @@ impl AppHandle { /// tauri::Builder::default() /// .plugin(plugin) /// .setup(move |app| { - /// let handle = app.handle(); + /// let handle = app.handle().clone(); /// std::thread::spawn(move || { /// handle.remove_plugin(plugin_name); /// }); @@ -422,16 +424,6 @@ impl AppHandle { self.cleanup_before_exit(); crate::process::restart(&self.env()); } - - /// Runs necessary cleanup tasks before exiting the process - fn cleanup_before_exit(&self) { - #[cfg(all(windows, feature = "system-tray"))] - { - for tray in self.manager().trays().values() { - let _ = tray.destroy(); - } - } - } } impl Manager for AppHandle {} @@ -444,8 +436,8 @@ impl ManagerBase for AppHandle { RuntimeOrDispatch::RuntimeHandle(self.runtime_handle.clone()) } - fn managed_app_handle(&self) -> AppHandle { - self.clone() + fn managed_app_handle(&self) -> &AppHandle { + self } } @@ -485,7 +477,7 @@ impl ManagerBase for App { } } - fn managed_app_handle(&self) -> AppHandle { + fn managed_app_handle(&self) -> &AppHandle { self.handle() } } @@ -511,72 +503,101 @@ impl App { macro_rules! shared_app_impl { ($app: ty) => { impl $app { - /// Gets a handle to the first system tray. - /// - /// Prefer [`Self::tray_handle_by_id`] when multiple system trays are created. - /// - /// # Examples - /// ``` - /// use tauri::{CustomMenuItem, SystemTray, SystemTrayMenu}; - /// - /// tauri::Builder::default() - /// .setup(|app| { - /// let app_handle = app.handle(); - /// SystemTray::new() - /// .with_menu( - /// SystemTrayMenu::new() - /// .add_item(CustomMenuItem::new("quit", "Quit")) - /// .add_item(CustomMenuItem::new("open", "Open")) - /// ) - /// .on_event(move |event| { - /// let tray_handle = app_handle.tray_handle(); - /// }) - /// .build(app)?; - /// Ok(()) - /// }); - /// ``` - #[cfg(all(desktop, feature = "system-tray"))] - #[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))] - pub fn tray_handle(&self) -> tray::SystemTrayHandle { + /// Registers a global menu event listener. + #[cfg(desktop)] + pub fn on_menu_event, MenuEvent) + Send + Sync + 'static>( + &self, + handler: F, + ) { self - .manager() - .trays() - .values() - .next() - .cloned() - .expect("tray not configured; use the `Builder#system_tray`, `App#system_tray` or `AppHandle#system_tray` APIs first.") + .manager + .inner + .menu_event_listeners + .lock() + .unwrap() + .push(Box::new(handler)); } + /// Registers a global tray icon menu event listener. + #[cfg(all(desktop, feature = "tray-icon"))] + #[cfg_attr(doc_cfg, doc(cfg(all(desktop, feature = "tray-icon"))))] + pub fn on_tray_icon_event, TrayIconEvent) + Send + Sync + 'static>( + &self, + handler: F, + ) { + self + .manager + .inner + .global_tray_event_listeners + .lock() + .unwrap() + .push(Box::new(handler)); + } - /// Gets a handle to a system tray by its id. - /// - /// ``` - /// use tauri::{CustomMenuItem, SystemTray, SystemTrayMenu}; + /// Gets the first tray icon registerd, usually the one configured in + /// tauri config file. + #[cfg(all(desktop, feature = "tray-icon"))] + #[cfg_attr(doc_cfg, doc(cfg(all(desktop, feature = "tray-icon"))))] + pub fn tray(&self) -> Option> { + self + .manager + .inner + .tray_icons + .lock() + .unwrap() + .first() + .cloned() + } + + /// Removes the first tray icon registerd, usually the one configured in + /// tauri config file, from tauri's internal state and returns it. /// - /// tauri::Builder::default() - /// .setup(|app| { - /// let app_handle = app.handle(); - /// let tray_id = "my-tray"; - /// SystemTray::new() - /// .with_id(tray_id) - /// .with_menu( - /// SystemTrayMenu::new() - /// .add_item(CustomMenuItem::new("quit", "Quit")) - /// .add_item(CustomMenuItem::new("open", "Open")) - /// ) - /// .on_event(move |event| { - /// let tray_handle = app_handle.tray_handle_by_id(tray_id).unwrap(); - /// }) - /// .build(app)?; - /// Ok(()) - /// }); - /// ``` - #[cfg(all(desktop, feature = "system-tray"))] - #[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))] - pub fn tray_handle_by_id(&self, id: &str) -> Option> { + /// Note that dropping the returned icon, will cause the tray icon to disappear. + #[cfg(all(desktop, feature = "tray-icon"))] + #[cfg_attr(doc_cfg, doc(cfg(all(desktop, feature = "tray-icon"))))] + pub fn remove_tray(&self) -> Option> { + let mut tray_icons = self.manager.inner.tray_icons.lock().unwrap(); + if !tray_icons.is_empty() { + return Some(tray_icons.swap_remove(0)); + } + None + } + + /// Gets a tray icon using the provided id. + #[cfg(all(desktop, feature = "tray-icon"))] + #[cfg_attr(doc_cfg, doc(cfg(all(desktop, feature = "tray-icon"))))] + pub fn tray_by_id<'a, I>(&self, id: &'a I) -> Option> + where + I: ?Sized, + TrayIconId: PartialEq<&'a I>, + { self - .manager() - .get_tray(id) + .manager + .inner + .tray_icons + .lock() + .unwrap() + .iter() + .find(|t| t.id() == &id) + .cloned() + } + + /// Removes a tray icon using the provided id from tauri's internal state and returns it. + /// + /// Note that dropping the returned icon, will cause the tray icon to disappear. + #[cfg(all(desktop, feature = "tray-icon"))] + #[cfg_attr(doc_cfg, doc(cfg(all(desktop, feature = "tray-icon"))))] + pub fn remove_tray_by_id<'a, I>(&self, id: &'a I) -> Option> + where + I: ?Sized, + TrayIconId: PartialEq<&'a I>, + { + let mut tray_icons = self.manager.inner.tray_icons.lock().unwrap(); + let idx = tray_icons.iter().position(|t| t.id() == &id); + if let Some(idx) = idx { + return Some(tray_icons.swap_remove(idx)); + } + None } /// Gets the app's configuration, defined on the `tauri.conf.json` file. @@ -600,29 +621,23 @@ macro_rules! shared_app_impl { /// /// Returns None if it can't identify any monitor as a primary one. pub fn primary_monitor(&self) -> crate::Result> { - Ok(match self.runtime() { - RuntimeOrDispatch::Runtime(h) => h - .primary_monitor().map(Into::into), - RuntimeOrDispatch::RuntimeHandle(h) => h - .primary_monitor().map(Into::into), - _ => unreachable!() + Ok(match self.runtime() { + RuntimeOrDispatch::Runtime(h) => h.primary_monitor().map(Into::into), + RuntimeOrDispatch::RuntimeHandle(h) => h.primary_monitor().map(Into::into), + _ => unreachable!(), }) } /// Returns the list of all the monitors available on the system. pub fn available_monitors(&self) -> crate::Result> { Ok(match self.runtime() { - RuntimeOrDispatch::Runtime(h) => h - .available_monitors() - .into_iter() - .map(Into::into) - .collect(), - RuntimeOrDispatch::RuntimeHandle(h) => h - .available_monitors() - .into_iter() - .map(Into::into) - .collect(), - _ => unreachable!() + RuntimeOrDispatch::Runtime(h) => { + h.available_monitors().into_iter().map(Into::into).collect() + } + RuntimeOrDispatch::RuntimeHandle(h) => { + h.available_monitors().into_iter().map(Into::into).collect() + } + _ => unreachable!(), }) } /// Returns the default window icon. @@ -630,6 +645,132 @@ macro_rules! shared_app_impl { self.manager.inner.default_window_icon.as_ref() } + /// Returns the app-wide menu. + #[cfg(desktop)] + pub fn menu(&self) -> Option> { + self.manager.menu_lock().clone() + } + + /// Sets the app-wide menu and returns the previous one. + /// + /// If a window was not created with an explicit menu or had one set explicitly, + /// this menu will be assigned to it. + #[cfg(desktop)] + pub fn set_menu(&self, menu: Menu) -> crate::Result>> { + let prev_menu = self.remove_menu()?; + + self.manager.insert_menu_into_stash(&menu); + + self.manager.menu_lock().replace(menu.clone()); + + // set it on all windows that don't have one or previously had the app-wide menu + #[cfg(not(target_os = "macos"))] + { + for window in self.manager.windows().values() { + let has_app_wide_menu = window.has_app_wide_menu() || window.menu().is_none(); + if has_app_wide_menu { + window.set_menu(menu.clone())?; + window.menu_lock().replace(WindowMenu { + is_app_wide: true, + menu: menu.clone(), + }); + } + } + } + + // set it app-wide for macos + #[cfg(target_os = "macos")] + { + let menu_ = menu.clone(); + self.run_on_main_thread(move || { + let _ = init_app_menu(&menu_); + })?; + } + + Ok(prev_menu) + } + + /// Remove the app-wide menu and returns it. + /// + /// If a window was not created with an explicit menu or had one set explicitly, + /// this will remove the menu from it. + #[cfg(desktop)] + pub fn remove_menu(&self) -> crate::Result>> { + let menu = self.manager.menu_lock().as_ref().cloned(); + #[allow(unused_variables)] + if let Some(menu) = menu { + // remove from windows that have the app-wide menu + #[cfg(not(target_os = "macos"))] + { + for window in self.manager.windows().values() { + let has_app_wide_menu = window.has_app_wide_menu(); + if has_app_wide_menu { + window.remove_menu()?; + *window.menu_lock() = None; + } + } + } + + // remove app-wide for macos + #[cfg(target_os = "macos")] + { + self.run_on_main_thread(move || { + menu.inner().remove_for_nsapp(); + })?; + } + } + + let prev_menu = self.manager.menu_lock().take(); + + self + .manager + .remove_menu_from_stash_by_id(prev_menu.as_ref().map(|m| m.id())); + + Ok(prev_menu) + } + + /// Hides the app-wide menu from windows that have it. + /// + /// If a window was not created with an explicit menu or had one set explicitly, + /// this will hide the menu from it. + #[cfg(desktop)] + pub fn hide_menu(&self) -> crate::Result<()> { + #[cfg(not(target_os = "macos"))] + { + let is_app_menu_set = self.manager.menu_lock().is_some(); + if is_app_menu_set { + for window in self.manager.windows().values() { + if window.has_app_wide_menu() { + window.hide_menu()?; + } + } + } + } + + Ok(()) + } + + /// Shows the app-wide menu for windows that have it. + /// + /// If a window was not created with an explicit menu or had one set explicitly, + /// this will show the menu for it. + #[cfg(desktop)] + pub fn show_menu(&self) -> crate::Result<()> { + #[cfg(not(target_os = "macos"))] + { + let is_app_menu_set = self.manager.menu_lock().is_some(); + if is_app_menu_set { + for window in self.manager.windows().values() { + if window.has_app_wide_menu() { + window.show_menu()?; + } + } + } + } + + Ok(()) + } + /// Shows the application, but does not automatically focus it. #[cfg(target_os = "macos")] pub fn show(&self) -> crate::Result<()> { @@ -651,6 +792,13 @@ macro_rules! shared_app_impl { } Ok(()) } + + /// Runs necessary cleanup tasks before exiting the process. + /// **You should always exit the tauri app immediately after this function returns and not use any tauri-related APIs.** + pub fn cleanup_before_exit(&self) { + #[cfg(all(desktop, feature = "tray-icon"))] + self.manager.inner.tray_icons.lock().unwrap().clear() + } } }; } @@ -665,9 +813,14 @@ impl App { Ok(()) } + /// Runs the given closure on the main thread. + pub fn run_on_main_thread(&self, f: F) -> crate::Result<()> { + self.app_handle().run_on_main_thread(f) + } + /// Gets a handle to the application instance. - pub fn handle(&self) -> AppHandle { - self.handle.clone() + pub fn handle(&self) -> &AppHandle { + &self.handle } /// Sets the activation policy for the application. It is set to `NSApplicationActivationPolicyRegular` by default. @@ -737,7 +890,7 @@ impl App { /// }); /// ``` pub fn run, RunEvent) + 'static>(mut self, mut callback: F) { - let app_handle = self.handle(); + let app_handle = self.handle().clone(); let manager = self.manager.clone(); self.runtime.take().unwrap().run(move |event| match event { RuntimeRunEvent::Ready => { @@ -769,8 +922,7 @@ impl App { /// Runs a iteration of the runtime event loop and immediately return. /// /// Note that when using this API, app cleanup is not automatically done. - /// The cleanup calls [`crate::process::kill_children`] so you may want to call that function before exiting the application. - /// Additionally, the cleanup calls [AppHandle#remove_system_tray](`AppHandle#method.remove_system_tray`) (Windows only). + /// The cleanup calls [`App::cleanup_before_exit`] so you may want to call that function before exiting the application. /// /// # Examples /// ```no_run @@ -781,6 +933,7 @@ impl App { /// loop { /// let iteration = app.run_iteration(); /// if iteration.window_count == 0 { + /// app.cleanup_before_exit(); /// break; /// } /// } @@ -788,7 +941,7 @@ impl App { #[cfg(desktop)] pub fn run_iteration(&mut self) -> crate::runtime::RunIteration { let manager = self.manager.clone(); - let app_handle = self.handle(); + let app_handle = self.handle().clone(); self.runtime.as_mut().unwrap().run_iteration(move |event| { on_event_loop_event( &app_handle, @@ -842,27 +995,17 @@ pub struct Builder { /// App state. state: StateManager, - /// The menu set to all windows. - menu: Option, + /// A closure that returns the menu set to all windows. + #[cfg(desktop)] + menu: Option) -> crate::Result>>>, /// Enable macOS default menu creation. #[allow(unused)] enable_macos_default_menu: bool, - /// Menu event handlers that listens to all windows. - menu_event_listeners: Vec>, - /// Window event handlers that listens to all windows. window_event_listeners: Vec>, - /// The app system tray. - #[cfg(all(desktop, feature = "system-tray"))] - system_tray: Option, - - /// System tray event handlers. - #[cfg(all(desktop, feature = "system-tray"))] - system_tray_event_listeners: Vec>, - /// The device event filter. device_event_filter: DeviceEventFilter, } @@ -901,14 +1044,10 @@ impl Builder { plugins: PluginStore::default(), uri_scheme_protocols: Default::default(), state: StateManager::new(), + #[cfg(desktop)] menu: None, enable_macos_default_menu: true, - menu_event_listeners: Vec::new(), window_event_listeners: Vec::new(), - #[cfg(all(desktop, feature = "system-tray"))] - system_tray: None, - #[cfg(all(desktop, feature = "system-tray"))] - system_tray_event_listeners: Vec::new(), device_event_filter: Default::default(), } } @@ -1134,50 +1273,33 @@ impl Builder { self } - /// Sets the given system tray to be built before the app runs. - /// - /// Prefer the [`SystemTray#method.build`](crate::SystemTray#method.build) method to create the tray at runtime instead. - /// - /// # Examples - /// ``` - /// use tauri::{CustomMenuItem, SystemTray, SystemTrayMenu}; - /// - /// tauri::Builder::default() - /// .system_tray(SystemTray::new().with_menu( - /// SystemTrayMenu::new() - /// .add_item(CustomMenuItem::new("quit", "Quit")) - /// .add_item(CustomMenuItem::new("open", "Open")) - /// )); - /// ``` - #[cfg(all(desktop, feature = "system-tray"))] - #[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))] - #[must_use] - pub fn system_tray(mut self, system_tray: tray::SystemTray) -> Self { - self.system_tray.replace(system_tray); - self - } - /// Sets the menu to use on all windows. /// /// # Examples /// ``` - /// use tauri::{MenuEntry, Submenu, MenuItem, Menu, CustomMenuItem}; + /// use tauri::menu::{Menu, MenuItem, PredefinedMenuItem, Submenu}; /// /// tauri::Builder::default() - /// .menu(Menu::with_items([ - /// MenuEntry::Submenu(Submenu::new( + /// .menu(|handle| Menu::with_items(handle, &[ + /// &Submenu::with_items( + /// handle, /// "File", - /// Menu::with_items([ - /// MenuItem::CloseWindow.into(), + /// true, + /// &[ + /// &PredefinedMenuItem::close_window(handle, None), /// #[cfg(target_os = "macos")] - /// CustomMenuItem::new("hello", "Hello").into(), - /// ]), - /// )), + /// &MenuItem::new(handle, "Hello", true, None), + /// ], + /// )? /// ])); /// ``` #[must_use] - pub fn menu(mut self, menu: Menu) -> Self { - self.menu.replace(menu); + #[cfg(desktop)] + pub fn menu) -> crate::Result> + 'static>( + mut self, + f: F, + ) -> Self { + self.menu.replace(Box::new(f)); self } @@ -1185,8 +1307,6 @@ impl Builder { /// /// # Examples /// ``` - /// use tauri::{MenuEntry, Submenu, MenuItem, Menu, CustomMenuItem}; - /// /// tauri::Builder::default() /// .enable_macos_default_menu(false); /// ``` @@ -1196,42 +1316,6 @@ impl Builder { self } - /// Registers a menu event handler for all windows. - /// - /// # Examples - /// ``` - /// use tauri::{Menu, MenuEntry, Submenu, CustomMenuItem, api, Manager}; - /// tauri::Builder::default() - /// .menu(Menu::with_items([ - /// MenuEntry::Submenu(Submenu::new( - /// "File", - /// Menu::with_items([ - /// CustomMenuItem::new("new", "New").into(), - /// CustomMenuItem::new("learn-more", "Learn More").into(), - /// ]), - /// )), - /// ])) - /// .on_menu_event(|event| { - /// match event.menu_item_id() { - /// "learn-more" => { - /// // open a link in the browser using tauri-plugin-shell - /// } - /// id => { - /// // do something with other events - /// println!("got menu event: {}", id); - /// } - /// } - /// }); - /// ``` - #[must_use] - pub fn on_menu_event) + Send + Sync + 'static>( - mut self, - handler: F, - ) -> Self { - self.menu_event_listeners.push(Box::new(handler)); - self - } - /// Registers a window event handler for all windows. /// /// # Examples @@ -1256,37 +1340,6 @@ impl Builder { self } - /// Registers a system tray event handler. - /// - /// Prefer the [`SystemTray#method.on_event`](crate::SystemTray#method.on_event) method when creating a tray at runtime instead. - /// - /// # Examples - /// ``` - /// use tauri::{Manager, SystemTrayEvent}; - /// tauri::Builder::default() - /// .on_system_tray_event(|app, event| match event { - /// // show window with id "main" when the tray is left clicked - /// SystemTrayEvent::LeftClick { .. } => { - /// let window = app.get_window("main").unwrap(); - /// window.show().unwrap(); - /// window.set_focus().unwrap(); - /// } - /// _ => {} - /// }); - /// ``` - #[cfg(all(desktop, feature = "system-tray"))] - #[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))] - #[must_use] - pub fn on_system_tray_event< - F: Fn(&AppHandle, tray::SystemTrayEvent) + Send + Sync + 'static, - >( - mut self, - handler: F, - ) -> Self { - self.system_tray_event_listeners.push(Box::new(handler)); - self - } - /// Registers a URI scheme protocol available to all webviews. /// Leverages [setURLSchemeHandler](https://developer.apple.com/documentation/webkit/wkwebviewconfiguration/2875766-seturlschemehandler) on macOS, /// [AddWebResourceRequestedFilter](https://docs.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2.addwebresourcerequestedfilter?view=webview2-dotnet-1.0.774.44) on Windows @@ -1344,7 +1397,9 @@ impl Builder { pub fn build(mut self, context: Context) -> crate::Result> { #[cfg(target_os = "macos")] if self.menu.is_none() && self.enable_macos_default_menu { - self.menu = Some(Menu::os_default(&context.package_info().name)); + self.menu = Some(Box::new(|app_handle| { + crate::menu::Menu::default(app_handle) + })); } let manager = WindowManager::with_handlers( @@ -1355,7 +1410,8 @@ impl Builder { self.uri_scheme_protocols, self.state, self.window_event_listeners, - (self.menu, self.menu_event_listeners), + #[cfg(desktop)] + HashMap::new(), (self.invoke_responder, self.invoke_initialization_script), ); @@ -1370,14 +1426,54 @@ impl Builder { )?); } + let runtime_args = RuntimeInitArgs { + #[cfg(windows)] + msg_hook: { + let menus = manager.inner.menus.clone(); + Some(Box::new(move |msg| { + use windows::Win32::UI::WindowsAndMessaging::{TranslateAcceleratorW, HACCEL, MSG}; + unsafe { + let msg = msg as *const MSG; + for menu in menus.lock().unwrap().values() { + let translated = + TranslateAcceleratorW((*msg).hwnd, HACCEL(menu.inner().haccel()), msg); + if translated == 1 { + return true; + } + } + + false + } + })) + }, + }; + #[cfg(any(windows, target_os = "linux"))] let mut runtime = if self.runtime_any_thread { - R::new_any_thread()? + R::new_any_thread(runtime_args)? } else { - R::new()? + R::new(runtime_args)? }; #[cfg(not(any(windows, target_os = "linux")))] - let mut runtime = R::new()?; + let mut runtime = R::new(runtime_args)?; + + #[cfg(desktop)] + { + // setup menu event handler + let proxy = runtime.create_proxy(); + crate::menu::MenuEvent::set_event_handler(Some(move |e| { + let _ = proxy.send_event(EventLoopMessage::MenuEvent(e)); + })); + + // setup tray event handler + #[cfg(feature = "tray-icon")] + { + let proxy = runtime.create_proxy(); + crate::tray::TrayIconEvent::set_event_handler(Some(move |e| { + let _ = proxy.send_event(EventLoopMessage::TrayIconEvent(e)); + })); + } + } runtime.set_device_event_filter(self.device_event_filter); @@ -1395,6 +1491,20 @@ impl Builder { }, }; + #[cfg(desktop)] + if let Some(menu) = self.menu { + let menu = menu(&app.handle)?; + app + .manager + .menus_stash_lock() + .insert(menu.id().clone(), menu.clone()); + + #[cfg(target_os = "macos")] + init_app_menu(&menu)?; + + app.manager.menu_lock().replace(menu); + } + app.register_core_plugins()?; let env = Env::default(); @@ -1433,31 +1543,31 @@ impl Builder { } } - #[cfg(all(desktop, feature = "system-tray"))] - { - if let Some(tray) = self.system_tray { - tray.build(&app)?; - } + let handle = app.handle(); - for listener in self.system_tray_event_listeners { - let app_handle = app.handle(); - let listener = Arc::new(std::sync::Mutex::new(listener)); - app - .runtime - .as_mut() - .unwrap() - .on_system_tray_event(move |tray_id, event| { - if let Some((tray_id, tray)) = app_handle.manager().get_tray_by_runtime_id(tray_id) { - let app_handle = app_handle.clone(); - let event = tray::SystemTrayEvent::from_runtime_event(event, tray_id, &tray.ids); - let listener = listener.clone(); - listener.lock().unwrap()(&app_handle, event); - } - }); + // initialize default tray icon if defined + #[cfg(all(desktop, feature = "tray-icon"))] + { + let config = app.config(); + if let Some(tray_config) = &config.tauri.tray_icon { + let mut tray = TrayIconBuilder::new() + .icon_as_template(tray_config.icon_as_template) + .menu_on_left_click(tray_config.menu_on_left_click); + if let Some(icon) = &app.manager.inner.tray_icon { + tray = tray.icon(icon.clone()); + } + if let Some(title) = &tray_config.title { + tray = tray.title(title); + } + if let Some(tooltip) = &tray_config.tooltip { + tray = tray.tooltip(tooltip); + } + let tray = tray.build(handle)?; + app.manager.inner.tray_icons.lock().unwrap().push(tray); } } - app.manager.initialize_plugins(&app.handle())?; + app.manager.initialize_plugins(handle)?; Ok(app) } @@ -1469,6 +1579,24 @@ impl Builder { } } +#[cfg(target_os = "macos")] +fn init_app_menu(menu: &Menu) -> crate::Result<()> { + menu.inner().init_for_nsapp(); + + if let Some(window_menu) = menu.get(crate::menu::WINDOW_SUBMENU_ID) { + if let Some(m) = window_menu.as_submenu() { + m.set_as_windows_menu_for_nsapp()?; + } + } + if let Some(help_menu) = menu.get(crate::menu::HELP_SUBMENU_ID) { + if let Some(m) = help_menu.as_submenu() { + m.set_as_help_menu_for_nsapp()?; + } + } + + Ok(()) +} + unsafe impl HasRawDisplayHandle for AppHandle { fn raw_display_handle(&self) -> raw_window_handle::RawDisplayHandle { self.runtime_handle.raw_display_handle() @@ -1489,18 +1617,37 @@ fn setup(app: &mut App) -> crate::Result<()> { .map(|p| p.label.clone()) .collect::>(); + let app_handle = app.handle(); + let manager = app.manager(); + for pending in pending_windows { - let pending = app - .manager - .prepare_window(app.handle.clone(), pending, &window_labels)?; + let pending = manager.prepare_window(app_handle.clone(), pending, &window_labels)?; + + #[cfg(desktop)] + let window_menu = app.manager.menu_lock().as_ref().map(|m| WindowMenu { + is_app_wide: true, + menu: m.clone(), + }); + + #[cfg(desktop)] + let handler = manager.prepare_window_menu_creation_handler(window_menu.as_ref()); + #[cfg(not(desktop))] + #[allow(clippy::type_complexity)] + let handler: Option) + Send>> = None; + let window_effects = pending.webview_attributes.window_effects.clone(); - let detached = if let RuntimeOrDispatch::RuntimeHandle(runtime) = app.handle().runtime() { - runtime.create_window(pending)? + let detached = if let RuntimeOrDispatch::RuntimeHandle(runtime) = app_handle.runtime() { + runtime.create_window(pending, handler)? } else { // the AppHandle's runtime is always RuntimeOrDispatch::RuntimeHandle unreachable!() }; - let window = app.manager.attach_window(app.handle(), detached); + let window = manager.attach_window( + app_handle.clone(), + detached, + #[cfg(desktop)] + None, + ); if let Some(effects) = window_effects { crate::vibrancy::set_window_effects(&window, Some(effects))?; @@ -1563,7 +1710,62 @@ fn on_event_loop_event, RunEvent) + 'static>( } RuntimeRunEvent::Resumed => RunEvent::Resumed, RuntimeRunEvent::MainEventsCleared => RunEvent::MainEventsCleared, - RuntimeRunEvent::UserEvent(t) => t.into(), + RuntimeRunEvent::UserEvent(t) => { + match t { + #[cfg(desktop)] + EventLoopMessage::MenuEvent(ref e) => { + for listener in &*app_handle + .manager + .inner + .menu_event_listeners + .lock() + .unwrap() + { + listener(app_handle, e.clone()); + } + for (label, listener) in &*app_handle + .manager + .inner + .window_menu_event_listeners + .lock() + .unwrap() + { + if let Some(w) = app_handle.get_window(label) { + listener(&w, e.clone()); + } + } + } + #[cfg(all(desktop, feature = "tray-icon"))] + EventLoopMessage::TrayIconEvent(ref e) => { + for listener in &*app_handle + .manager + .inner + .global_tray_event_listeners + .lock() + .unwrap() + { + listener(app_handle, e.clone()); + } + + for (id, listener) in &*app_handle + .manager + .inner + .tray_event_listeners + .lock() + .unwrap() + { + if e.id == id { + if let Some(tray) = app_handle.tray_by_id(id) { + listener(&tray, e.clone()); + } + } + } + } + } + + #[allow(unreachable_code)] + t.into() + } #[cfg(any(target_os = "macos", target_os = "ios"))] RuntimeRunEvent::Opened { urls } => RunEvent::Opened { urls }, _ => unimplemented!(), diff --git a/core/tauri/src/app/tray.rs b/core/tauri/src/app/tray.rs deleted file mode 100644 index b88b932c7e37..000000000000 --- a/core/tauri/src/app/tray.rs +++ /dev/null @@ -1,704 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -pub use crate::{ - runtime::{ - menu::{ - MenuHash, MenuId, MenuIdRef, MenuUpdate, SystemTrayMenu, SystemTrayMenuEntry, TrayHandle, - }, - window::dpi::{PhysicalPosition, PhysicalSize}, - RuntimeHandle, SystemTrayEvent as RuntimeSystemTrayEvent, - }, - Icon, Runtime, -}; -use crate::{sealed::RuntimeOrDispatch, Manager}; - -use rand::distributions::{Alphanumeric, DistString}; -use tauri_macros::default_runtime; -use tauri_runtime::TrayId; -use tauri_utils::debug_eprintln; - -use std::{ - collections::{hash_map::DefaultHasher, HashMap}, - fmt, - hash::{Hash, Hasher}, - sync::{Arc, Mutex}, -}; - -type TrayEventHandler = dyn Fn(SystemTrayEvent) + Send + Sync + 'static; - -pub(crate) fn get_menu_ids(map: &mut HashMap, menu: &SystemTrayMenu) { - for item in &menu.items { - match item { - SystemTrayMenuEntry::CustomItem(c) => { - map.insert(c.id, c.id_str.clone()); - } - SystemTrayMenuEntry::Submenu(s) => get_menu_ids(map, &s.inner), - _ => {} - } - } -} - -/// Represents a System Tray instance. -#[derive(Clone)] -#[non_exhaustive] -pub struct SystemTray { - /// The tray identifier. Defaults to a random string. - pub id: String, - /// The tray icon. - pub icon: Option, - /// The tray menu. - pub menu: Option, - /// Whether the icon is a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) icon or not. - #[cfg(target_os = "macos")] - pub icon_as_template: bool, - /// Whether the menu should appear when the tray receives a left click. Defaults to `true` - #[cfg(target_os = "macos")] - pub menu_on_left_click: bool, - on_event: Option>, - // TODO: icon_as_template and menu_on_left_click should be an Option instead :( - #[cfg(target_os = "macos")] - menu_on_left_click_set: bool, - #[cfg(target_os = "macos")] - icon_as_template_set: bool, - #[cfg(target_os = "macos")] - title: Option, - tooltip: Option, -} - -impl fmt::Debug for SystemTray { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut d = f.debug_struct("SystemTray"); - d.field("id", &self.id) - .field("icon", &self.icon) - .field("menu", &self.menu); - #[cfg(target_os = "macos")] - { - d.field("icon_as_template", &self.icon_as_template) - .field("menu_on_left_click", &self.menu_on_left_click); - } - d.finish() - } -} - -impl Default for SystemTray { - fn default() -> Self { - Self { - id: Alphanumeric.sample_string(&mut rand::thread_rng(), 16), - icon: None, - menu: None, - on_event: None, - #[cfg(target_os = "macos")] - icon_as_template: false, - #[cfg(target_os = "macos")] - menu_on_left_click: false, - #[cfg(target_os = "macos")] - icon_as_template_set: false, - #[cfg(target_os = "macos")] - menu_on_left_click_set: false, - #[cfg(target_os = "macos")] - title: None, - tooltip: None, - } - } -} - -impl SystemTray { - /// Creates a new system tray that only renders an icon. - /// - /// # Examples - /// - /// ``` - /// use tauri::SystemTray; - /// - /// tauri::Builder::default() - /// .setup(|app| { - /// let tray_handle = SystemTray::new().build(app)?; - /// Ok(()) - /// }); - /// ``` - pub fn new() -> Self { - Default::default() - } - - pub(crate) fn menu(&self) -> Option<&SystemTrayMenu> { - self.menu.as_ref() - } - - /// Sets the tray identifier, used to retrieve its handle and to identify a tray event source. - /// - /// # Examples - /// - /// ``` - /// use tauri::SystemTray; - /// - /// tauri::Builder::default() - /// .setup(|app| { - /// let tray_handle = SystemTray::new() - /// .with_id("tray-id") - /// .build(app)?; - /// Ok(()) - /// }); - /// ``` - #[must_use] - pub fn with_id>(mut self, id: I) -> Self { - self.id = id.into(); - self - } - - /// Sets the tray [`Icon`]. - /// - /// # Examples - /// - /// ``` - /// use tauri::{Icon, SystemTray}; - /// - /// tauri::Builder::default() - /// .setup(|app| { - /// let tray_handle = SystemTray::new() - /// // dummy and invalid Rgba icon; see the Icon documentation for more information - /// .with_icon(Icon::Rgba { rgba: Vec::new(), width: 0, height: 0 }) - /// .build(app)?; - /// Ok(()) - /// }); - /// ``` - #[must_use] - pub fn with_icon>(mut self, icon: I) -> Self - where - I::Error: std::error::Error, - { - match icon.try_into() { - Ok(icon) => { - self.icon.replace(icon); - } - Err(e) => { - debug_eprintln!("Failed to load tray icon: {}", e); - } - } - self - } - - /// Sets the icon as a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc). - /// - /// Images you mark as template images should consist of only black and clear colors. - /// You can use the alpha channel in the image to adjust the opacity of black content. - /// - /// # Examples - /// - /// ``` - /// use tauri::SystemTray; - /// - /// tauri::Builder::default() - /// .setup(|app| { - /// let mut tray_builder = SystemTray::new(); - /// #[cfg(target_os = "macos")] - /// { - /// tray_builder = tray_builder.with_icon_as_template(true); - /// } - /// let tray_handle = tray_builder.build(app)?; - /// Ok(()) - /// }); - /// ``` - #[cfg(target_os = "macos")] - #[must_use] - pub fn with_icon_as_template(mut self, is_template: bool) -> Self { - self.icon_as_template_set = true; - self.icon_as_template = is_template; - self - } - - /// Sets whether the menu should appear when the tray receives a left click. Defaults to `true`. - /// - /// # Examples - /// - /// ``` - /// use tauri::SystemTray; - /// - /// tauri::Builder::default() - /// .setup(|app| { - /// let mut tray_builder = SystemTray::new(); - /// #[cfg(target_os = "macos")] - /// { - /// tray_builder = tray_builder.with_menu_on_left_click(false); - /// } - /// let tray_handle = tray_builder.build(app)?; - /// Ok(()) - /// }); - /// ``` - #[cfg(target_os = "macos")] - #[must_use] - pub fn with_menu_on_left_click(mut self, menu_on_left_click: bool) -> Self { - self.menu_on_left_click_set = true; - self.menu_on_left_click = menu_on_left_click; - self - } - - /// Sets the menu title` - /// - /// # Examples - /// - /// ``` - /// use tauri::SystemTray; - /// - /// tauri::Builder::default() - /// .setup(|app| { - /// let mut tray_builder = SystemTray::new(); - /// #[cfg(target_os = "macos")] - /// { - /// tray_builder = tray_builder.with_title("My App"); - /// } - /// let tray_handle = tray_builder.build(app)?; - /// Ok(()) - /// }); - /// ``` - #[cfg(target_os = "macos")] - #[must_use] - pub fn with_title(mut self, title: &str) -> Self { - self.title = Some(title.to_owned()); - self - } - - /// Sets the tray icon tooltip. - /// - /// ## Platform-specific: - /// - /// - **Linux:** Unsupported - /// - /// # Examples - /// - /// ``` - /// use tauri::SystemTray; - /// - /// tauri::Builder::default() - /// .setup(|app| { - /// let tray_handle = SystemTray::new().with_tooltip("My App").build(app)?; - /// Ok(()) - /// }); - /// ``` - #[must_use] - pub fn with_tooltip(mut self, tooltip: &str) -> Self { - self.tooltip = Some(tooltip.to_owned()); - self - } - - /// Sets the event listener for this system tray. - /// - /// # Examples - /// - /// ``` - /// use tauri::{Icon, Manager, SystemTray, SystemTrayEvent}; - /// - /// tauri::Builder::default() - /// .setup(|app| { - /// let handle = app.handle(); - /// let id = "tray-id"; - /// SystemTray::new() - /// .with_id(id) - /// .on_event(move |event| { - /// let tray_handle = handle.tray_handle_by_id(id).unwrap(); - /// match event { - /// // show window with id "main" when the tray is left clicked - /// SystemTrayEvent::LeftClick { .. } => { - /// let window = handle.get_window("main").unwrap(); - /// window.show().unwrap(); - /// window.set_focus().unwrap(); - /// } - /// _ => {} - /// } - /// }) - /// .build(app)?; - /// Ok(()) - /// }); - /// ``` - #[must_use] - pub fn on_event(mut self, f: F) -> Self { - self.on_event.replace(Arc::new(f)); - self - } - - /// Sets the menu to show when the system tray is right clicked. - /// - /// # Examples - /// - /// ``` - /// use tauri::{CustomMenuItem, SystemTray, SystemTrayMenu}; - /// - /// tauri::Builder::default() - /// .setup(|app| { - /// let tray_handle = SystemTray::new() - /// .with_menu( - /// SystemTrayMenu::new() - /// .add_item(CustomMenuItem::new("quit", "Quit")) - /// .add_item(CustomMenuItem::new("open", "Open")) - /// ) - /// .build(app)?; - /// Ok(()) - /// }); - /// ``` - #[must_use] - pub fn with_menu(mut self, menu: SystemTrayMenu) -> Self { - self.menu.replace(menu); - self - } - - /// Builds and shows the system tray. - /// - /// # Examples - /// - /// ``` - /// use tauri::{CustomMenuItem, SystemTray, SystemTrayMenu}; - /// - /// tauri::Builder::default() - /// .setup(|app| { - /// let tray_handle = SystemTray::new() - /// .with_menu( - /// SystemTrayMenu::new() - /// .add_item(CustomMenuItem::new("quit", "Quit")) - /// .add_item(CustomMenuItem::new("open", "Open")) - /// ) - /// .build(app)?; - /// - /// tray_handle.get_item("quit").set_enabled(false); - /// Ok(()) - /// }); - /// ``` - pub fn build>( - mut self, - manager: &M, - ) -> crate::Result> { - let mut ids = HashMap::new(); - if let Some(menu) = self.menu() { - get_menu_ids(&mut ids, menu); - } - let ids = Arc::new(Mutex::new(ids)); - - if self.icon.is_none() { - if let Some(tray_icon) = &manager.manager().inner.tray_icon { - self = self.with_icon(tray_icon.clone()); - } - } - #[cfg(target_os = "macos")] - { - if !self.icon_as_template_set { - self.icon_as_template = manager - .config() - .tauri - .system_tray - .as_ref() - .map_or(false, |t| t.icon_as_template); - } - if !self.menu_on_left_click_set { - self.menu_on_left_click = manager - .config() - .tauri - .system_tray - .as_ref() - .map_or(false, |t| t.menu_on_left_click); - } - if self.title.is_none() { - self.title = manager - .config() - .tauri - .system_tray - .as_ref() - .and_then(|t| t.title.clone()) - } - } - - let tray_id = self.id.clone(); - - let mut runtime_tray = tauri_runtime::SystemTray::new(); - runtime_tray = runtime_tray.with_id(hash(&self.id)); - if let Some(i) = self.icon { - runtime_tray = runtime_tray.with_icon(i); - } - - if let Some(menu) = self.menu { - runtime_tray = runtime_tray.with_menu(menu); - } - - if let Some(on_event) = self.on_event { - let ids_ = ids.clone(); - let tray_id_ = tray_id.clone(); - runtime_tray = runtime_tray.on_event(move |event| { - on_event(SystemTrayEvent::from_runtime_event( - event, - tray_id_.clone(), - &ids_, - )) - }); - } - - #[cfg(target_os = "macos")] - { - runtime_tray = runtime_tray.with_icon_as_template(self.icon_as_template); - runtime_tray = runtime_tray.with_menu_on_left_click(self.menu_on_left_click); - if let Some(title) = self.title { - runtime_tray = runtime_tray.with_title(&title); - } - } - - if let Some(tooltip) = self.tooltip { - runtime_tray = runtime_tray.with_tooltip(&tooltip); - } - - let id = runtime_tray.id; - let tray_handler = match manager.runtime() { - RuntimeOrDispatch::Runtime(r) => r.system_tray(runtime_tray), - RuntimeOrDispatch::RuntimeHandle(h) => h.system_tray(runtime_tray), - RuntimeOrDispatch::Dispatch(_) => manager - .app_handle() - .runtime_handle - .system_tray(runtime_tray), - }?; - - let tray_handle = SystemTrayHandle { - id, - ids, - inner: tray_handler, - }; - manager.manager().attach_tray(tray_id, tray_handle.clone()); - - Ok(tray_handle) - } -} - -fn hash(id: &str) -> MenuHash { - let mut hasher = DefaultHasher::new(); - id.hash(&mut hasher); - hasher.finish() as MenuHash -} - -/// System tray event. -#[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))] -#[non_exhaustive] -pub enum SystemTrayEvent { - /// Tray context menu item was clicked. - #[non_exhaustive] - MenuItemClick { - /// The tray id. - tray_id: String, - /// The id of the menu item. - id: MenuId, - }, - /// Tray icon received a left click. - /// - /// ## Platform-specific - /// - /// - **Linux:** Unsupported - #[non_exhaustive] - LeftClick { - /// The tray id. - tray_id: String, - /// The position of the tray icon. - position: PhysicalPosition, - /// The size of the tray icon. - size: PhysicalSize, - }, - /// Tray icon received a right click. - /// - /// ## Platform-specific - /// - /// - **Linux:** Unsupported - /// - **macOS:** `Ctrl` + `Left click` fire this event. - #[non_exhaustive] - RightClick { - /// The tray id. - tray_id: String, - /// The position of the tray icon. - position: PhysicalPosition, - /// The size of the tray icon. - size: PhysicalSize, - }, - /// Fired when a menu item receive a `Double click` - /// - /// ## Platform-specific - /// - /// - **macOS / Linux:** Unsupported - /// - #[non_exhaustive] - DoubleClick { - /// The tray id. - tray_id: String, - /// The position of the tray icon. - position: PhysicalPosition, - /// The size of the tray icon. - size: PhysicalSize, - }, -} - -impl SystemTrayEvent { - pub(crate) fn from_runtime_event( - event: &RuntimeSystemTrayEvent, - tray_id: String, - menu_ids: &Arc>>, - ) -> Self { - match event { - RuntimeSystemTrayEvent::MenuItemClick(id) => Self::MenuItemClick { - tray_id, - id: menu_ids.lock().unwrap().get(id).unwrap().clone(), - }, - RuntimeSystemTrayEvent::LeftClick { position, size } => Self::LeftClick { - tray_id, - position: *position, - size: *size, - }, - RuntimeSystemTrayEvent::RightClick { position, size } => Self::RightClick { - tray_id, - position: *position, - size: *size, - }, - RuntimeSystemTrayEvent::DoubleClick { position, size } => Self::DoubleClick { - tray_id, - position: *position, - size: *size, - }, - } - } -} - -/// A handle to a system tray. Allows updating the context menu items. -#[default_runtime(crate::Wry, wry)] -#[derive(Debug)] -pub struct SystemTrayHandle { - pub(crate) id: TrayId, - pub(crate) ids: Arc>>, - pub(crate) inner: R::TrayHandler, -} - -impl Clone for SystemTrayHandle { - fn clone(&self) -> Self { - Self { - id: self.id, - ids: self.ids.clone(), - inner: self.inner.clone(), - } - } -} - -/// A handle to a system tray menu item. -#[default_runtime(crate::Wry, wry)] -#[derive(Debug)] -pub struct SystemTrayMenuItemHandle { - id: MenuHash, - tray_handler: R::TrayHandler, -} - -impl Clone for SystemTrayMenuItemHandle { - fn clone(&self) -> Self { - Self { - id: self.id, - tray_handler: self.tray_handler.clone(), - } - } -} - -impl SystemTrayHandle { - /// Gets a handle to the menu item that has the specified `id`. - pub fn get_item(&self, id: MenuIdRef<'_>) -> SystemTrayMenuItemHandle { - let ids = self.ids.lock().unwrap(); - let iter = ids.iter(); - for (raw, item_id) in iter { - if item_id == id { - return SystemTrayMenuItemHandle { - id: *raw, - tray_handler: self.inner.clone(), - }; - } - } - panic!("item id not found") - } - - /// Attempts to get a handle to the menu item that has the specified `id`, return an error if `id` is not found. - pub fn try_get_item(&self, id: MenuIdRef<'_>) -> Option> { - self - .ids - .lock() - .unwrap() - .iter() - .find(|i| i.1 == id) - .map(|i| SystemTrayMenuItemHandle { - id: *i.0, - tray_handler: self.inner.clone(), - }) - } - /// Updates the tray icon. - pub fn set_icon(&self, icon: Icon) -> crate::Result<()> { - self.inner.set_icon(icon.try_into()?).map_err(Into::into) - } - - /// Updates the tray menu. - pub fn set_menu(&self, menu: SystemTrayMenu) -> crate::Result<()> { - let mut ids = HashMap::new(); - get_menu_ids(&mut ids, &menu); - self.inner.set_menu(menu)?; - *self.ids.lock().unwrap() = ids; - Ok(()) - } - - /// Support [macOS tray icon template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) to adjust automatically based on taskbar color. - #[cfg(target_os = "macos")] - pub fn set_icon_as_template(&self, is_template: bool) -> crate::Result<()> { - self - .inner - .set_icon_as_template(is_template) - .map_err(Into::into) - } - - /// Adds the title to the tray menu - #[cfg(target_os = "macos")] - pub fn set_title(&self, title: &str) -> crate::Result<()> { - self.inner.set_title(title).map_err(Into::into) - } - - /// Set the tooltip for this tray icon. - /// - /// ## Platform-specific: - /// - /// - **Linux:** Unsupported - pub fn set_tooltip(&self, tooltip: &str) -> crate::Result<()> { - self.inner.set_tooltip(tooltip).map_err(Into::into) - } - - /// Destroys this system tray. - pub fn destroy(&self) -> crate::Result<()> { - self.inner.destroy().map_err(Into::into) - } -} - -impl SystemTrayMenuItemHandle { - /// Modifies the enabled state of the menu item. - pub fn set_enabled(&self, enabled: bool) -> crate::Result<()> { - self - .tray_handler - .update_item(self.id, MenuUpdate::SetEnabled(enabled)) - .map_err(Into::into) - } - - /// Modifies the title (label) of the menu item. - pub fn set_title>(&self, title: S) -> crate::Result<()> { - self - .tray_handler - .update_item(self.id, MenuUpdate::SetTitle(title.into())) - .map_err(Into::into) - } - - /// Modifies the selected state of the menu item. - pub fn set_selected(&self, selected: bool) -> crate::Result<()> { - self - .tray_handler - .update_item(self.id, MenuUpdate::SetSelected(selected)) - .map_err(Into::into) - } - - /// Sets the native image for this item. - #[cfg(target_os = "macos")] - #[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))] - pub fn set_native_image(&self, image: crate::NativeImage) -> crate::Result<()> { - self - .tray_handler - .update_item(self.id, MenuUpdate::SetNativeImage(image)) - .map_err(Into::into) - } -} diff --git a/core/tauri/src/error.rs b/core/tauri/src/error.rs index 03ad98f8900a..8de7cec0d01b 100644 --- a/core/tauri/src/error.rs +++ b/core/tauri/src/error.rs @@ -89,4 +89,25 @@ pub enum Error { #[cfg(target_os = "android")] #[error("jni error: {0}")] Jni(#[from] jni::errors::Error), + /// Failed to receive message . + #[error("failed to receive message")] + FailedToReceiveMessage, + /// Menu error. + #[error("menu error: {0}")] + #[cfg(desktop)] + Menu(#[from] muda::Error), + /// Bad menu icon error. + #[error(transparent)] + #[cfg(desktop)] + BadMenuIcon(#[from] muda::BadIcon), + /// Tray icon error. + #[error("tray icon error: {0}")] + #[cfg(all(desktop, feature = "tray-icon"))] + #[cfg_attr(doc_cfg, doc(cfg(all(desktop, feature = "tray-icon"))))] + Tray(#[from] tray_icon::Error), + /// Bad tray icon error. + #[error(transparent)] + #[cfg(all(desktop, feature = "tray-icon"))] + #[cfg_attr(doc_cfg, doc(cfg(all(desktop, feature = "tray-icon"))))] + BadTrayIcon(#[from] tray_icon::BadIcon), } diff --git a/core/tauri/src/jni_helpers.rs b/core/tauri/src/jni_helpers.rs index ae500fc5265a..4f72bfacef19 100644 --- a/core/tauri/src/jni_helpers.rs +++ b/core/tauri/src/jni_helpers.rs @@ -5,18 +5,18 @@ use crate::Runtime; use jni::{ errors::Error as JniError, - objects::{JObject, JValue}, + objects::{JObject, JValueOwned}, JNIEnv, }; use serde_json::Value as JsonValue; use tauri_runtime::RuntimeHandle; fn json_to_java<'a, R: Runtime>( - env: JNIEnv<'a>, - activity: JObject<'a>, + env: &mut JNIEnv<'a>, + activity: &JObject<'_>, runtime_handle: &R::Handle, json: &JsonValue, -) -> Result<(&'static str, JValue<'a>), JniError> { +) -> Result<(&'static str, JValueOwned<'a>), JniError> { let (class, v) = match json { JsonValue::Null => ("Ljava/lang/Object;", JObject::null().into()), JsonValue::Bool(val) => ("Z", (*val).into()), @@ -40,27 +40,30 @@ fn json_to_java<'a, R: Runtime>( for v in val { let (signature, val) = json_to_java::(env, activity, runtime_handle, v)?; env.call_method( - data, + &data, "put", format!("({signature})Lorg/json/JSONArray;"), - &[val], + &[val.borrow()], )?; } ("Ljava/lang/Object;", data.into()) } JsonValue::Object(val) => { - let js_object_class = - runtime_handle.find_class(env, activity, "app/tauri/plugin/JSObject")?; - let data = env.new_object(js_object_class, "()V", &[])?; + let data = { + let js_object_class = + runtime_handle.find_class(env, activity, "app/tauri/plugin/JSObject")?; + env.new_object(js_object_class, "()V", &[])? + }; for (key, value) in val { let (signature, val) = json_to_java::(env, activity, runtime_handle, value)?; + let key = env.new_string(key)?; env.call_method( - data, + &data, "put", format!("(Ljava/lang/String;{signature})Lapp/tauri/plugin/JSObject;"), - &[env.new_string(key)?.into(), val], + &[(&key).into(), val.borrow()], )?; } @@ -71,17 +74,25 @@ fn json_to_java<'a, R: Runtime>( } pub fn to_jsobject<'a, R: Runtime>( - env: JNIEnv<'a>, - activity: JObject<'a>, + env: &mut JNIEnv<'a>, + activity: &JObject<'_>, runtime_handle: &R::Handle, json: &JsonValue, -) -> Result, JniError> { +) -> Result, JniError> { if let JsonValue::Object(_) = json { json_to_java::(env, activity, runtime_handle, json).map(|(_class, data)| data) } else { - // currently the Kotlin lib cannot handle nulls or raw values, it must be an object - let js_object_class = runtime_handle.find_class(env, activity, "app/tauri/plugin/JSObject")?; - let data = env.new_object(js_object_class, "()V", &[])?; - Ok(data.into()) + Ok(empty_object::(env, activity, runtime_handle)?.into()) } } + +fn empty_object<'a, R: Runtime>( + env: &mut JNIEnv<'a>, + activity: &JObject<'_>, + runtime_handle: &R::Handle, +) -> Result, JniError> { + // currently the Kotlin lib cannot handle nulls or raw values, it must be an object + let js_object_class = runtime_handle.find_class(env, activity, "app/tauri/plugin/JSObject")?; + let data = env.new_object(js_object_class, "()V", &[])?; + Ok(data) +} diff --git a/core/tauri/src/lib.rs b/core/tauri/src/lib.rs index 861f96c5fe26..a66aca8bd832 100644 --- a/core/tauri/src/lib.rs +++ b/core/tauri/src/lib.rs @@ -17,6 +17,7 @@ //! - **dox**: Internal feature to generate Rust documentation without linking on Linux. //! - **objc-exception**: Wrap each msg_send! in a @try/@catch and panics if an exception is caught, preventing Objective-C from unwinding into Rust. //! - **linux-ipc-protocol**: Use custom protocol for faster IPC on Linux. Requires webkit2gtk v2.40 or above. +//! - **linux-libxdo**: Enables linking to libxdo which enables Cut, Copy, Paste and SelectAll menu items to work on Linux. //! - **isolation**: Enables the isolation pattern. Enabled by default if the `tauri > pattern > use` config option is set to `isolation` on the `tauri.conf.json` file. //! - **custom-protocol**: Feature managed by the Tauri CLI. When enabled, Tauri assumes a production environment instead of a development one. //! - **devtools**: Enables the developer tools (Web inspector) and [`Window::open_devtools`]. Enabled by default on debug builds. @@ -25,7 +26,7 @@ //! - **native-tls-vendored**: Compile and statically link to a vendored copy of OpenSSL. //! - **rustls-tls**: Provides TLS support to connect over HTTPS using rustls. //! - **process-relaunch-dangerous-allow-symlink-macos**: Allows the [`process::current_binary`] function to allow symlinks on macOS (this is dangerous, see the Security section in the documentation website). -//! - **system-tray**: Enables application system tray API. Enabled by default if the `systemTray` config is defined on the `tauri.conf.json` file. +//! - **tray-icon**: Enables application tray icon APIs. Enabled by default if the `trayIcon` config is defined on the `tauri.conf.json` file. //! - **macos-private-api**: Enables features only available in **macOS**'s private APIs, currently the `transparent` window functionality and the `fullScreenEnabled` preference setting to `true`. Enabled by default if the `tauri > macosPrivateApi` config flag is set to `true` on the `tauri.conf.json` file. //! - **window-data-url**: Enables usage of data URLs on the webview. //! - **compression** *(enabled by default): Enables asset compression. You should only disable this if you want faster compile times in release builds - it produces larger binaries. @@ -92,6 +93,8 @@ use tauri_runtime as runtime; mod ios; #[cfg(target_os = "android")] mod jni_helpers; +#[cfg(desktop)] +pub mod menu; /// Path APIs. pub mod path; pub mod process; @@ -99,6 +102,9 @@ pub mod process; pub mod scope; mod state; +#[cfg(all(desktop, feature = "tray-icon"))] +#[cfg_attr(doc_cfg, doc(cfg(all(desktop, feature = "tray-icon"))))] +pub mod tray; pub use tauri_utils as utils; /// A Tauri [`Runtime`] wrapper around wry. @@ -130,14 +136,20 @@ macro_rules! android_binding { // this function is a glue between PluginManager.kt > handlePluginResponse and Rust #[allow(non_snake_case)] - pub fn handlePluginResponse(env: JNIEnv, _: JClass, id: i32, success: JString, error: JString) { - ::tauri::handle_android_plugin_response(env, id, success, error); + pub fn handlePluginResponse( + mut env: JNIEnv, + _: JClass, + id: i32, + success: JString, + error: JString, + ) { + ::tauri::handle_android_plugin_response(&mut env, id, success, error); } // this function is a glue between PluginManager.kt > sendChannelData and Rust #[allow(non_snake_case)] - pub fn sendChannelData(env: JNIEnv, _: JClass, id: i64, data: JString) { - ::tauri::send_channel_data(env, id, data); + pub fn sendChannelData(mut env: JNIEnv, _: JClass, id: i64, data: JString) { + ::tauri::send_channel_data(&mut env, id, data); } }; } @@ -156,7 +168,11 @@ pub type Result = std::result::Result; pub type SyncTask = Box; use serde::Serialize; -use std::{collections::HashMap, fmt, sync::Arc}; +use std::{ + collections::HashMap, + fmt::{self, Debug}, + sync::Arc, +}; // Export types likely to be used by the application. pub use runtime::http; @@ -167,22 +183,12 @@ pub use tauri_runtime_wry::webview_version; #[cfg(target_os = "macos")] #[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))] -pub use runtime::{menu::NativeImage, ActivationPolicy}; +pub use runtime::ActivationPolicy; #[cfg(target_os = "macos")] pub use self::utils::TitleBarStyle; -#[cfg(all(desktop, feature = "system-tray"))] -#[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))] -pub use { - self::app::tray::{SystemTray, SystemTrayEvent, SystemTrayHandle, SystemTrayMenuItemHandle}, - self::runtime::menu::{SystemTrayMenu, SystemTrayMenuItem, SystemTraySubmenu}, -}; -pub use { - self::app::WindowMenuEvent, - self::event::{Event, EventHandler}, - self::runtime::menu::{AboutMetadata, CustomMenuItem, Menu, MenuEntry, MenuItem, Submenu}, - self::window::menu::MenuEvent, -}; + +pub use self::event::{Event, EventHandler}; pub use { self::app::{ App, AppHandle, AssetResolver, Builder, CloseRequestApi, GlobalWindowEvent, RunEvent, @@ -249,7 +255,15 @@ pub fn log_stdout() { /// The user event type. #[derive(Debug, Clone)] -pub enum EventLoopMessage {} +pub enum EventLoopMessage { + /// An event from a menu item, could be on the window menu bar, application menu bar (on macOS) or tray icon menu. + #[cfg(desktop)] + MenuEvent(menu::MenuEvent), + /// An event from a menu item, could be on the window menu bar, application menu bar (on macOS) or tray icon menu. + #[cfg(all(desktop, feature = "tray-icon"))] + #[cfg_attr(doc_cfg, doc(cfg(all(desktop, feature = "tray-icon"))))] + TrayIconEvent(tray::TrayIconEvent), +} /// The webview runtime interface. A wrapper around [`runtime::Runtime`] with the proper user event type associated. pub trait Runtime: runtime::Runtime {} @@ -388,8 +402,8 @@ pub struct Context { pub(crate) assets: Arc, pub(crate) default_window_icon: Option, pub(crate) app_icon: Option>, - #[cfg(desktop)] - pub(crate) system_tray_icon: Option, + #[cfg(all(desktop, feature = "tray-icon"))] + pub(crate) tray_icon: Option, pub(crate) package_info: PackageInfo, pub(crate) _info_plist: (), pub(crate) pattern: Pattern, @@ -404,8 +418,8 @@ impl fmt::Debug for Context { .field("package_info", &self.package_info) .field("pattern", &self.pattern); - #[cfg(desktop)] - d.field("system_tray_icon", &self.system_tray_icon); + #[cfg(all(desktop, feature = "tray-icon"))] + d.field("tray_icon", &self.tray_icon); d.finish() } @@ -449,17 +463,19 @@ impl Context { } /// The icon to use on the system tray UI. - #[cfg(desktop)] + #[cfg(all(desktop, feature = "tray-icon"))] + #[cfg_attr(doc_cfg, doc(cfg(all(desktop, feature = "tray-icon"))))] #[inline(always)] - pub fn system_tray_icon(&self) -> Option<&Icon> { - self.system_tray_icon.as_ref() + pub fn tray_icon(&self) -> Option<&Icon> { + self.tray_icon.as_ref() } - /// A mutable reference to the icon to use on the system tray UI. - #[cfg(desktop)] + /// A mutable reference to the icon to use on the tray icon. + #[cfg(all(desktop, feature = "tray-icon"))] + #[cfg_attr(doc_cfg, doc(cfg(all(desktop, feature = "tray-icon"))))] #[inline(always)] - pub fn system_tray_icon_mut(&mut self) -> &mut Option { - &mut self.system_tray_icon + pub fn tray_icon_mut(&mut self) -> &mut Option { + &mut self.tray_icon } /// Package information. @@ -497,8 +513,8 @@ impl Context { assets, default_window_icon, app_icon, - #[cfg(desktop)] - system_tray_icon: None, + #[cfg(all(desktop, feature = "tray-icon"))] + tray_icon: None, package_info, _info_plist: info_plist, pattern, @@ -506,10 +522,11 @@ impl Context { } /// Sets the app tray icon. - #[cfg(desktop)] + #[cfg(all(desktop, feature = "tray-icon"))] + #[cfg_attr(doc_cfg, doc(cfg(all(desktop, feature = "tray-icon"))))] #[inline(always)] - pub fn set_system_tray_icon(&mut self, icon: Icon) { - self.system_tray_icon.replace(icon); + pub fn set_tray_icon(&mut self, icon: Icon) { + self.tray_icon.replace(icon); } /// Sets the app shell scope. @@ -524,7 +541,7 @@ impl Context { /// Manages a running application. pub trait Manager: sealed::ManagerBase { /// The application handle associated with this manager. - fn app_handle(&self) -> AppHandle { + fn app_handle(&self) -> &AppHandle { self.managed_app_handle() } @@ -648,7 +665,7 @@ pub trait Manager: sealed::ManagerBase { /// /// tauri::Builder::default() /// .setup(|app| { - /// let handle = app.handle(); + /// let handle = app.handle().clone(); /// let handler = app.listen_global("ready", move |event| { /// println!("app is ready"); /// @@ -845,7 +862,7 @@ pub(crate) mod sealed { /// The manager behind the [`Managed`] item. fn manager(&self) -> &WindowManager; fn runtime(&self) -> RuntimeOrDispatch<'_, R>; - fn managed_app_handle(&self) -> AppHandle; + fn managed_app_handle(&self) -> &AppHandle; } } @@ -895,6 +912,23 @@ mod tests { } } +#[allow(unused)] +macro_rules! run_main_thread { + ($self:ident, $ex:expr) => {{ + use std::sync::mpsc::channel; + let (tx, rx) = channel(); + let self_ = $self.clone(); + let task = move || { + let _ = tx.send($ex(self_)); + }; + $self.app_handle.run_on_main_thread(Box::new(task))?; + rx.recv().map_err(|_| crate::Error::FailedToReceiveMessage) + }}; +} + +#[allow(unused)] +pub(crate) use run_main_thread; + #[cfg(test)] mod test_utils { use proptest::prelude::*; diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index 744631e8f3a4..bba3a8cef745 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -10,6 +10,10 @@ use std::{ sync::{Arc, Mutex, MutexGuard}, }; +#[cfg(desktop)] +use crate::menu::{Menu, MenuId}; +#[cfg(all(desktop, feature = "tray-icon"))] +use crate::tray::{TrayIcon, TrayIconId}; use serde::Serialize; use serialize_to_javascript::{default_template, DefaultTemplate, Template}; use url::Url; @@ -23,10 +27,7 @@ use tauri_utils::{ }; use crate::{ - app::{ - AppHandle, GlobalMenuEventListener, GlobalWindowEvent, GlobalWindowEventListener, OnPageLoad, - PageLoadPayload, WindowMenuEvent, - }, + app::{AppHandle, GlobalWindowEvent, GlobalWindowEventListener, OnPageLoad, PageLoadPayload}, event::{assert_event_name_is_valid, Event, EventHandler, Listeners}, ipc::{Invoke, InvokeHandler, InvokeResponder}, pattern::PatternJavascript, @@ -49,11 +50,14 @@ use crate::{ WindowEvent, }; +#[cfg(desktop)] +use crate::app::GlobalMenuEventListener; +#[cfg(all(desktop, feature = "tray-icon"))] +use crate::app::GlobalTrayIconEventListener; + #[cfg(any(target_os = "linux", target_os = "windows"))] use crate::path::BaseDirectory; -use crate::{runtime::menu::Menu, MenuEvent}; - const WINDOW_RESIZED_EVENT: &str = "tauri://resize"; const WINDOW_MOVED_EVENT: &str = "tauri://move"; const WINDOW_CLOSE_REQUESTED_EVENT: &str = "tauri://close-requested"; @@ -65,7 +69,6 @@ const WINDOW_THEME_CHANGED: &str = "tauri://theme-changed"; const WINDOW_FILE_DROP_EVENT: &str = "tauri://file-drop"; const WINDOW_FILE_DROP_HOVER_EVENT: &str = "tauri://file-drop-hover"; const WINDOW_FILE_DROP_CANCELLED_EVENT: &str = "tauri://file-drop-cancelled"; -const MENU_EVENT: &str = "tauri://menu"; pub(crate) const PROCESS_IPC_MESSAGE_FN: &str = include_str!("../scripts/process-ipc-message-fn.js"); @@ -210,9 +213,7 @@ fn replace_csp_nonce( #[default_runtime(crate::Wry, wry)] pub struct InnerWindowManager { - windows: Mutex>>, - #[cfg(all(desktop, feature = "system-tray"))] - pub(crate) trays: Mutex>>, + pub(crate) windows: Mutex>>, pub(crate) plugins: Mutex>, listeners: Listeners, pub(crate) state: Arc, @@ -227,18 +228,42 @@ pub struct InnerWindowManager { assets: Arc, pub(crate) default_window_icon: Option, pub(crate) app_icon: Option>, - #[cfg(desktop)] + #[cfg(all(desktop, feature = "tray-icon"))] pub(crate) tray_icon: Option, package_info: PackageInfo, /// The webview protocols available to all windows. uri_scheme_protocols: HashMap>>, + /// A set containing a reference to the active menus, including + /// the app-wide menu and the window-specific menus + /// + /// This should be mainly used to acceess [`Menu::haccel`] + /// to setup the accelerator handling in the event loop + #[cfg(desktop)] + pub menus: Arc>>>, /// The menu set to all windows. - menu: Option, + #[cfg(desktop)] + pub(crate) menu: Arc>>>, /// Menu event listeners to all windows. - menu_event_listeners: Arc>>, + #[cfg(desktop)] + pub(crate) menu_event_listeners: Arc>>>>, + /// Menu event listeners to specific windows. + #[cfg(desktop)] + pub(crate) window_menu_event_listeners: + Arc>>>>, /// Window event listeners to all windows. window_event_listeners: Arc>>, + /// Tray icons + #[cfg(all(desktop, feature = "tray-icon"))] + pub(crate) tray_icons: Arc>>>, + /// Global Tray icon event listeners. + #[cfg(all(desktop, feature = "tray-icon"))] + pub(crate) global_tray_event_listeners: + Arc>>>>, + /// Tray icon event listeners. + #[cfg(all(desktop, feature = "tray-icon"))] + pub(crate) tray_event_listeners: + Arc>>>>, /// Responder for invoke calls. invoke_responder: Option>>, /// The script that initializes the invoke system. @@ -257,10 +282,9 @@ impl fmt::Debug for InnerWindowManager { .field("default_window_icon", &self.default_window_icon) .field("app_icon", &self.app_icon) .field("package_info", &self.package_info) - .field("menu", &self.menu) .field("pattern", &self.pattern); - #[cfg(desktop)] + #[cfg(all(desktop, feature = "tray-icon"))] d.field("tray_icon", &self.tray_icon); d.finish() @@ -303,7 +327,7 @@ impl Clone for WindowManager { } impl WindowManager { - #[allow(clippy::too_many_arguments)] + #[allow(clippy::too_many_arguments, clippy::type_complexity)] pub(crate) fn with_handlers( #[allow(unused_mut)] mut context: Context, plugins: PluginStore, @@ -312,7 +336,10 @@ impl WindowManager { uri_scheme_protocols: HashMap>>, state: StateManager, window_event_listeners: Vec>, - (menu, menu_event_listeners): (Option, Vec>), + #[cfg(desktop)] window_menu_event_listeners: HashMap< + String, + GlobalMenuEventListener>, + >, (invoke_responder, invoke_initialization_script): (Option>>, String), ) -> Self { // generate a random isolation key at runtime @@ -324,8 +351,6 @@ impl WindowManager { Self { inner: Arc::new(InnerWindowManager { windows: Mutex::default(), - #[cfg(all(desktop, feature = "system-tray"))] - trays: Default::default(), plugins: Mutex::new(plugins), listeners: Listeners::default(), state: Arc::new(state), @@ -335,14 +360,26 @@ impl WindowManager { assets: context.assets, default_window_icon: context.default_window_icon, app_icon: context.app_icon, - #[cfg(desktop)] - tray_icon: context.system_tray_icon, + #[cfg(all(desktop, feature = "tray-icon"))] + tray_icon: context.tray_icon, package_info: context.package_info, pattern: context.pattern, uri_scheme_protocols, - menu, - menu_event_listeners: Arc::new(menu_event_listeners), + #[cfg(desktop)] + menus: Default::default(), + #[cfg(desktop)] + menu: Default::default(), + #[cfg(desktop)] + menu_event_listeners: Default::default(), + #[cfg(desktop)] + window_menu_event_listeners: Arc::new(Mutex::new(window_menu_event_listeners)), window_event_listeners: Arc::new(window_event_listeners), + #[cfg(all(desktop, feature = "tray-icon"))] + tray_icons: Default::default(), + #[cfg(all(desktop, feature = "tray-icon"))] + global_tray_event_listeners: Default::default(), + #[cfg(all(desktop, feature = "tray-icon"))] + tray_event_listeners: Default::default(), invoke_responder, invoke_initialization_script, }), @@ -363,6 +400,83 @@ impl WindowManager { self.inner.state.clone() } + #[cfg(desktop)] + pub(crate) fn prepare_window_menu_creation_handler( + &self, + window_menu: Option<&crate::window::WindowMenu>, + ) -> Option)> { + { + if let Some(menu) = window_menu { + self + .menus_stash_lock() + .insert(menu.menu.id().clone(), menu.menu.clone()); + } + } + + #[cfg(target_os = "macos")] + return None; + + #[cfg_attr(target_os = "macos", allow(unused_variables, unreachable_code))] + if let Some(menu) = &window_menu { + let menu = menu.menu.clone(); + Some(move |raw: tauri_runtime::window::RawWindow<'_>| { + #[cfg(target_os = "windows")] + let _ = menu.inner().init_for_hwnd(raw.hwnd as _); + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + let _ = menu + .inner() + .init_for_gtk_window(raw.gtk_window, raw.default_vbox); + }) + } else { + None + } + } + + /// App-wide menu. + #[cfg(desktop)] + pub(crate) fn menu_lock(&self) -> MutexGuard<'_, Option>> { + self.inner.menu.lock().expect("poisoned window manager") + } + + /// Menus stash. + #[cfg(desktop)] + pub(crate) fn menus_stash_lock(&self) -> MutexGuard<'_, HashMap>> { + self.inner.menus.lock().expect("poisoned window manager") + } + + #[cfg(desktop)] + pub(crate) fn is_menu_in_use>(&self, id: &I) -> bool { + self + .menu_lock() + .as_ref() + .map(|m| id.eq(m.id())) + .unwrap_or(false) + } + + /// Menus stash. + #[cfg(desktop)] + pub(crate) fn insert_menu_into_stash(&self, menu: &Menu) { + self + .menus_stash_lock() + .insert(menu.id().clone(), menu.clone()); + } + + #[cfg(desktop)] + pub(crate) fn remove_menu_from_stash_by_id(&self, id: Option<&MenuId>) { + if let Some(id) = id { + let is_used_by_a_window = self.windows_lock().values().any(|w| w.is_menu_in_use(id)); + if !(self.is_menu_in_use(id) || is_used_by_a_window) { + self.menus_stash_lock().remove(id); + } + } + } + /// The invoke responder. pub(crate) fn invoke_responder(&self) -> Option>> { self.inner.invoke_responder.clone() @@ -1037,12 +1151,6 @@ impl WindowManager { } } - if pending.window_builder.get_menu().is_none() { - if let Some(menu) = &self.inner.menu { - pending = pending.set_menu(menu.clone()); - } - } - #[cfg(target_os = "android")] { pending = pending.on_webview_created(move |ctx| { @@ -1136,12 +1244,19 @@ impl WindowManager { Ok(pending) } - pub fn attach_window( + pub(crate) fn attach_window( &self, app_handle: AppHandle, window: DetachedWindow, + #[cfg(desktop)] menu: Option>, ) -> Window { - let window = Window::new(self.clone(), window, app_handle); + let window = Window::new( + self.clone(), + window, + app_handle, + #[cfg(desktop)] + menu, + ); let window_ = window.clone(); let window_event_listeners = self.inner.window_event_listeners.clone(); @@ -1155,19 +1270,6 @@ impl WindowManager { }); } }); - { - let window_ = window.clone(); - let menu_event_listeners = self.inner.menu_event_listeners.clone(); - window.on_menu_event(move |event| { - let _ = on_menu_event(&window_, &event); - for handler in menu_event_listeners.iter() { - handler(WindowMenuEvent { - window: window_.clone(), - menu_item_id: event.menu_item_id.clone(), - }); - } - }); - } // insert the window into our manager { @@ -1297,33 +1399,6 @@ impl WindowManager { } } -/// Tray APIs -#[cfg(all(desktop, feature = "system-tray"))] -impl WindowManager { - pub fn get_tray(&self, id: &str) -> Option> { - self.inner.trays.lock().unwrap().get(id).cloned() - } - - pub fn trays(&self) -> HashMap> { - self.inner.trays.lock().unwrap().clone() - } - - pub fn attach_tray(&self, id: String, tray: crate::SystemTrayHandle) { - self.inner.trays.lock().unwrap().insert(id, tray); - } - - pub fn get_tray_by_runtime_id(&self, id: u16) -> Option<(String, crate::SystemTrayHandle)> { - let trays = self.inner.trays.lock().unwrap(); - let iter = trays.iter(); - for (tray_id, tray) in iter { - if tray.id == id { - return Some((tray_id.clone(), tray.clone())); - } - } - None - } -} - fn on_window_event( window: &Window, manager: &WindowManager, @@ -1396,10 +1471,6 @@ struct ScaleFactorChanged { size: PhysicalSize, } -fn on_menu_event(window: &Window, event: &MenuEvent) -> crate::Result<()> { - window.emit(MENU_EVENT, event.menu_item_id.clone()) -} - #[cfg(feature = "isolation")] fn request_to_path(request: &tauri_runtime::http::Request, base_url: &str) -> String { let mut path = request diff --git a/core/tauri/src/menu/builders/check.rs b/core/tauri/src/menu/builders/check.rs new file mode 100644 index 000000000000..fa900d206d58 --- /dev/null +++ b/core/tauri/src/menu/builders/check.rs @@ -0,0 +1,90 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use crate::{menu::CheckMenuItem, menu::MenuId, Manager, Runtime}; + +/// A builder type for [`CheckMenuItem`] +pub struct CheckMenuItemBuilder { + id: Option, + text: String, + enabled: bool, + checked: bool, + accelerator: Option, +} + +impl CheckMenuItemBuilder { + /// Create a new menu item builder. + /// + /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic + /// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`. + pub fn new>(text: S) -> Self { + Self { + id: None, + text: text.as_ref().to_string(), + enabled: true, + checked: true, + accelerator: None, + } + } + + /// Create a new menu item builder with the specified id. + /// + /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic + /// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`. + pub fn with_id, S: AsRef>(id: I, text: S) -> Self { + Self { + id: Some(id.into()), + text: text.as_ref().to_string(), + enabled: true, + checked: true, + accelerator: None, + } + } + + /// Set the id for this menu item. + pub fn id>(mut self, id: I) -> Self { + self.id.replace(id.into()); + self + } + + /// Set the enabled state for this menu item. + pub fn enabled(mut self, enabled: bool) -> Self { + self.enabled = enabled; + self + } + + /// Set the checked state for this menu item. + pub fn checked(mut self, checked: bool) -> Self { + self.checked = checked; + self + } + + /// Set the accelerator for this menu item. + pub fn accelerator>(mut self, accelerator: S) -> Self { + self.accelerator.replace(accelerator.as_ref().to_string()); + self + } + + /// Build the menu item + pub fn build>(self, manager: &M) -> CheckMenuItem { + if let Some(id) = self.id { + CheckMenuItem::with_id( + manager, + id, + self.text, + self.enabled, + self.checked, + self.accelerator, + ) + } else { + CheckMenuItem::new( + manager, + self.text, + self.enabled, + self.checked, + self.accelerator, + ) + } + } +} diff --git a/core/tauri/src/menu/builders/icon.rs b/core/tauri/src/menu/builders/icon.rs new file mode 100644 index 000000000000..d2b2aa0d54cd --- /dev/null +++ b/core/tauri/src/menu/builders/icon.rs @@ -0,0 +1,128 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use muda::{MenuId, NativeIcon}; + +use crate::{menu::IconMenuItem, Icon, Manager, Runtime}; + +/// A builder type for [`IconMenuItem`] +pub struct IconMenuItemBuilder { + id: Option, + text: String, + enabled: bool, + icon: Option, + native_icon: Option, + accelerator: Option, +} + +impl IconMenuItemBuilder { + /// Create a new menu item builder. + /// + /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic + /// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`. + pub fn new>(text: S) -> Self { + Self { + id: None, + text: text.as_ref().to_string(), + enabled: true, + icon: None, + native_icon: None, + accelerator: None, + } + } + + /// Create a new menu item builder with the specified id. + /// + /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic + /// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`. + pub fn with_id, S: AsRef>(id: I, text: S) -> Self { + Self { + id: Some(id.into()), + text: text.as_ref().to_string(), + enabled: true, + icon: None, + native_icon: None, + accelerator: None, + } + } + + /// Set the id for this menu item. + pub fn id>(mut self, id: I) -> Self { + self.id.replace(id.into()); + self + } + + /// Set the enabled state for this menu item. + pub fn enabled(mut self, enabled: bool) -> Self { + self.enabled = enabled; + self + } + + /// Set the accelerator for this menu item. + pub fn accelerator>(mut self, accelerator: S) -> Self { + self.accelerator.replace(accelerator.as_ref().to_string()); + self + } + + /// Set the icon for this menu item. + /// + /// **Note:** This method conflicts with [`Self::native_icon`] + /// so calling one of them, will reset the other. + pub fn icon(mut self, icon: Icon) -> Self { + self.icon.replace(icon); + self.native_icon = None; + self + } + + /// Set the icon for this menu item. + /// + /// **Note:** This method conflicts with [`Self::icon`] + /// so calling one of them, will reset the other. + pub fn native_icon(mut self, icon: NativeIcon) -> Self { + self.native_icon.replace(icon); + self.icon = None; + self + } + + /// Build the menu item + pub fn build>(self, manager: &M) -> IconMenuItem { + if self.icon.is_some() { + if let Some(id) = self.id { + IconMenuItem::with_id( + manager, + id, + self.text, + self.enabled, + self.icon, + self.accelerator, + ) + } else { + IconMenuItem::new( + manager, + self.text, + self.enabled, + self.icon, + self.accelerator, + ) + } + } else if let Some(id) = self.id { + IconMenuItem::with_id_and_native_icon( + manager, + id, + self.text, + self.enabled, + self.native_icon, + self.accelerator, + ) + } else { + IconMenuItem::with_native_icon( + manager, + self.text, + self.enabled, + self.native_icon, + self.accelerator, + ) + } + } +} diff --git a/core/tauri/src/menu/builders/menu.rs b/core/tauri/src/menu/builders/menu.rs new file mode 100644 index 000000000000..215596fa8e79 --- /dev/null +++ b/core/tauri/src/menu/builders/menu.rs @@ -0,0 +1,321 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use crate::{menu::*, Icon, Manager, Runtime}; + +/// A builder type for [`Menu`] +/// +/// # Example +/// +/// ```no_run +/// use tauri::menu::*; +/// tauri::Builder::default() +/// .setup(move |app| { +/// let handle = app.handle(); +/// # let icon1 = tauri::Icon::Rgba { +/// # rgba: Vec::new(), +/// # width: 0, +/// # height: 0, +/// # }; +/// let menu = MenuBuilder::new(handle) +/// .item(&MenuItem::new(handle, "MenuItem 1", true, None)) +/// .items(&[ +/// &CheckMenuItem::new(handle, "CheckMenuItem 1", true, true, None), +/// &IconMenuItem::new(handle, "IconMenuItem 1", true, Some(icon1), None), +/// ]) +/// .separator() +/// .cut() +/// .copy() +/// .paste() +/// .separator() +/// .text("MenuItem 2") +/// .check("CheckMenuItem 2") +/// .icon("IconMenuItem 2", app.default_window_icon().cloned().unwrap()) +/// .build()?; +/// app.set_menu(menu); +/// Ok(()) +/// }); +/// ``` +pub struct MenuBuilder<'m, R: Runtime, M: Manager> { + id: Option, + manager: &'m M, + items: Vec>, +} + +impl<'m, R: Runtime, M: Manager> MenuBuilder<'m, R, M> { + /// Create a new menu builder. + pub fn new(manager: &'m M) -> Self { + Self { + id: None, + items: Vec::new(), + manager, + } + } + + /// Create a new menu builder with the specified id. + pub fn with_id>(manager: &'m M, id: I) -> Self { + Self { + id: Some(id.into()), + items: Vec::new(), + manager, + } + } + + /// Set the id for this menu. + pub fn id>(mut self, id: I) -> Self { + self.id.replace(id.into()); + self + } + + /// Add this item to the menu. + pub fn item(mut self, item: &dyn IsMenuItem) -> Self { + self.items.push(item.kind()); + self + } + + /// Add these items to the menu. + pub fn items(mut self, items: &[&dyn IsMenuItem]) -> Self { + for item in items { + self = self.item(*item); + } + self + } + + /// Add a [MenuItem] to the menu. + pub fn text>(mut self, text: S) -> Self { + self + .items + .push(MenuItem::new(self.manager, text, true, None).kind()); + self + } + + /// Add a [CheckMenuItem] to the menu. + pub fn check>(mut self, text: S) -> Self { + self + .items + .push(CheckMenuItem::new(self.manager, text, true, true, None).kind()); + self + } + + /// Add an [IconMenuItem] to the menu. + pub fn icon>(mut self, text: S, icon: Icon) -> Self { + self + .items + .push(IconMenuItem::new(self.manager, text, true, Some(icon), None).kind()); + self + } + + /// Add an [IconMenuItem] with a native icon to the menu. + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux**: Unsupported. + pub fn native_icon>(mut self, text: S, icon: NativeIcon) -> Self { + self + .items + .push(IconMenuItem::with_native_icon(self.manager, text, true, Some(icon), None).kind()); + self + } + + /// Add Separator menu item to the menu. + pub fn separator(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::separator(self.manager).kind()); + self + } + + /// Add Copy menu item to the menu. + pub fn copy(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::copy(self.manager, None).kind()); + self + } + + /// Add Cut menu item to the menu. + pub fn cut(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::cut(self.manager, None).kind()); + self + } + + /// Add Paste menu item to the menu. + pub fn paste(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::paste(self.manager, None).kind()); + self + } + + /// Add SelectAll menu item to the menu. + pub fn select_all(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::select_all(self.manager, None).kind()); + self + } + + /// Add Undo menu item to the menu. + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux:** Unsupported. + pub fn undo(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::undo(self.manager, None).kind()); + self + } + /// Add Redo menu item to the menu. + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux:** Unsupported. + pub fn redo(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::redo(self.manager, None).kind()); + self + } + + /// Add Minimize window menu item to the menu. + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported. + pub fn minimize(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::minimize(self.manager, None).kind()); + self + } + + /// Add Maximize window menu item to the menu. + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported. + pub fn maximize(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::maximize(self.manager, None).kind()); + self + } + + /// Add Fullscreen menu item to the menu. + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux:** Unsupported. + pub fn fullscreen(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::fullscreen(self.manager, None).kind()); + self + } + + /// Add Hide window menu item to the menu. + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported. + pub fn hide(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::hide(self.manager, None).kind()); + self + } + + /// Add Hide other windows menu item to the menu. + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported. + pub fn hide_others(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::hide_others(self.manager, None).kind()); + self + } + + /// Add Show all app windows menu item to the menu. + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux:** Unsupported. + pub fn show_all(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::show_all(self.manager, None).kind()); + self + } + + /// Add Close window menu item to the menu. + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported. + pub fn close_window(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::close_window(self.manager, None).kind()); + self + } + + /// Add Quit app menu item to the menu. + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported. + pub fn quit(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::quit(self.manager, None).kind()); + self + } + + /// Add About app menu item to the menu. + pub fn about(mut self, metadata: Option) -> Self { + self + .items + .push(PredefinedMenuItem::about(self.manager, None, metadata).kind()); + self + } + + /// Add Services menu item to the menu. + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux:** Unsupported. + pub fn services(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::services(self.manager, None).kind()); + self + } + + /// Builds this menu + pub fn build(self) -> crate::Result> { + if self.items.is_empty() { + Ok(if let Some(id) = self.id { + Menu::with_id(self.manager, id) + } else { + Menu::new(self.manager) + }) + } else { + let items = self + .items + .iter() + .map(|i| i as &dyn IsMenuItem) + .collect::>(); + if let Some(id) = self.id { + Menu::with_id_and_items(self.manager, id, &items) + } else { + Menu::with_items(self.manager, &items) + } + } + } +} diff --git a/core/tauri/src/menu/builders/mod.rs b/core/tauri/src/menu/builders/mod.rs new file mode 100644 index 000000000000..f86baee6b27b --- /dev/null +++ b/core/tauri/src/menu/builders/mod.rs @@ -0,0 +1,20 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +#![cfg(desktop)] + +//! A module containting menu builder types + +pub use muda::AboutMetadataBuilder; + +mod menu; +pub use menu::MenuBuilder; +mod normal; +pub use normal::MenuItemBuilder; +mod submenu; +pub use submenu::SubmenuBuilder; +mod check; +pub use check::CheckMenuItemBuilder; +mod icon; +pub use icon::IconMenuItemBuilder; diff --git a/core/tauri/src/menu/builders/normal.rs b/core/tauri/src/menu/builders/normal.rs new file mode 100644 index 000000000000..f70abe388db9 --- /dev/null +++ b/core/tauri/src/menu/builders/normal.rs @@ -0,0 +1,68 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use crate::{menu::MenuId, menu::MenuItem, Manager, Runtime}; + +/// A builder type for [`MenuItem`] +pub struct MenuItemBuilder { + id: Option, + text: String, + enabled: bool, + accelerator: Option, +} + +impl MenuItemBuilder { + /// Create a new menu item builder. + /// + /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic + /// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`. + pub fn new>(text: S) -> Self { + Self { + id: None, + text: text.as_ref().to_string(), + enabled: true, + accelerator: None, + } + } + + /// Create a new menu item builder with the specified id. + /// + /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic + /// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`. + pub fn with_id, S: AsRef>(id: I, text: S) -> Self { + Self { + id: Some(id.into()), + text: text.as_ref().to_string(), + enabled: true, + accelerator: None, + } + } + + /// Set the id for this menu item. + pub fn id>(mut self, id: I) -> Self { + self.id.replace(id.into()); + self + } + + /// Set the enabled state for this menu item. + pub fn enabled(mut self, enabled: bool) -> Self { + self.enabled = enabled; + self + } + + /// Set the accelerator for this menu item. + pub fn accelerator>(mut self, accelerator: S) -> Self { + self.accelerator.replace(accelerator.as_ref().to_string()); + self + } + + /// Build the menu item + pub fn build>(self, manager: &M) -> MenuItem { + if let Some(id) = self.id { + MenuItem::with_id(manager, id, self.text, self.enabled, self.accelerator) + } else { + MenuItem::new(manager, self.text, self.enabled, self.accelerator) + } + } +} diff --git a/core/tauri/src/menu/builders/submenu.rs b/core/tauri/src/menu/builders/submenu.rs new file mode 100644 index 000000000000..ee06420dea10 --- /dev/null +++ b/core/tauri/src/menu/builders/submenu.rs @@ -0,0 +1,342 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use crate::{menu::*, Icon, Manager, Runtime}; + +/// A builder type for [`Submenu`] +/// +/// # Example +/// +/// ```no_run +/// use tauri::menu::*; +/// tauri::Builder::default() +/// .setup(move |app| { +/// let handle = app.handle(); +/// # let icon1 = tauri::Icon::Rgba { +/// # rgba: Vec::new(), +/// # width: 0, +/// # height: 0, +/// # }; +/// # let icon2 = icon1.clone(); +/// let menu = Menu::new(handle); +/// let submenu = SubmenuBuilder::new(handle, "File") +/// .item(&MenuItem::new(handle, "MenuItem 1", true, None)) +/// .items(&[ +/// &CheckMenuItem::new(handle, "CheckMenuItem 1", true, true, None), +/// &IconMenuItem::new(handle, "IconMenuItem 1", true, Some(icon1), None), +/// ]) +/// .separator() +/// .cut() +/// .copy() +/// .paste() +/// .separator() +/// .text("MenuItem 2") +/// .check("CheckMenuItem 2") +/// .icon("IconMenuItem 2", app.default_window_icon().cloned().unwrap()) +/// .build()?; +/// menu.append(&submenu)?; +/// app.set_menu(menu); +/// Ok(()) +/// }); +/// ``` +pub struct SubmenuBuilder<'m, R: Runtime, M: Manager> { + id: Option, + manager: &'m M, + text: String, + enabled: bool, + items: Vec>, +} + +impl<'m, R: Runtime, M: Manager> SubmenuBuilder<'m, R, M> { + /// Create a new submenu builder. + /// + /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic + /// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`. + pub fn new>(manager: &'m M, text: S) -> Self { + Self { + id: None, + items: Vec::new(), + text: text.as_ref().to_string(), + enabled: true, + manager, + } + } + + /// Create a new submenu builder with the specified id. + /// + /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic + /// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`. + pub fn with_id, S: AsRef>(manager: &'m M, id: I, text: S) -> Self { + Self { + id: Some(id.into()), + text: text.as_ref().to_string(), + enabled: true, + items: Vec::new(), + manager, + } + } + + /// Set the id for this submenu. + pub fn id>(mut self, id: I) -> Self { + self.id.replace(id.into()); + self + } + + /// Set the enabled state for the submenu. + pub fn enabled(mut self, enabled: bool) -> Self { + self.enabled = enabled; + self + } + + /// Add this item to the submenu. + pub fn item(mut self, item: &dyn IsMenuItem) -> Self { + self.items.push(item.kind()); + self + } + + /// Add these items to the submenu. + pub fn items(mut self, items: &[&dyn IsMenuItem]) -> Self { + for item in items { + self = self.item(*item); + } + self + } + + /// Add a [MenuItem] to the submenu. + pub fn text>(mut self, text: S) -> Self { + self + .items + .push(MenuItem::new(self.manager, text, true, None).kind()); + self + } + + /// Add a [CheckMenuItem] to the submenu. + pub fn check>(mut self, text: S) -> Self { + self + .items + .push(CheckMenuItem::new(self.manager, text, true, true, None).kind()); + self + } + + /// Add an [IconMenuItem] to the submenu. + pub fn icon>(mut self, text: S, icon: Icon) -> Self { + self + .items + .push(IconMenuItem::new(self.manager, text, true, Some(icon), None).kind()); + self + } + + /// Add an [IconMenuItem] with a native icon to the submenu. + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux**: Unsupported. + pub fn native_icon>(mut self, text: S, icon: NativeIcon) -> Self { + self + .items + .push(IconMenuItem::with_native_icon(self.manager, text, true, Some(icon), None).kind()); + self + } + + /// Add Separator menu item to the submenu. + pub fn separator(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::separator(self.manager).kind()); + self + } + + /// Add Copy menu item to the submenu. + pub fn copy(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::copy(self.manager, None).kind()); + self + } + + /// Add Cut menu item to the submenu. + pub fn cut(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::cut(self.manager, None).kind()); + self + } + + /// Add Paste menu item to the submenu. + pub fn paste(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::paste(self.manager, None).kind()); + self + } + + /// Add SelectAll menu item to the submenu. + pub fn select_all(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::select_all(self.manager, None).kind()); + self + } + + /// Add Undo menu item to the submenu. + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux:** Unsupported. + pub fn undo(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::undo(self.manager, None).kind()); + self + } + /// Add Redo menu item to the submenu. + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux:** Unsupported. + pub fn redo(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::redo(self.manager, None).kind()); + self + } + + /// Add Minimize window menu item to the submenu. + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported. + pub fn minimize(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::minimize(self.manager, None).kind()); + self + } + + /// Add Maximize window menu item to the submenu. + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported. + pub fn maximize(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::maximize(self.manager, None).kind()); + self + } + + /// Add Fullscreen menu item to the submenu. + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux:** Unsupported. + pub fn fullscreen(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::fullscreen(self.manager, None).kind()); + self + } + + /// Add Hide window menu item to the submenu. + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported. + pub fn hide(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::hide(self.manager, None).kind()); + self + } + + /// Add Hide other windows menu item to the submenu. + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported. + pub fn hide_others(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::hide_others(self.manager, None).kind()); + self + } + + /// Add Show all app windows menu item to the submenu. + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux:** Unsupported. + pub fn show_all(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::show_all(self.manager, None).kind()); + self + } + + /// Add Close window menu item to the submenu. + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported. + pub fn close_window(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::close_window(self.manager, None).kind()); + self + } + + /// Add Quit app menu item to the submenu. + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported. + pub fn quit(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::quit(self.manager, None).kind()); + self + } + + /// Add About app menu item to the submenu. + pub fn about(mut self, metadata: Option) -> Self { + self + .items + .push(PredefinedMenuItem::about(self.manager, None, metadata).kind()); + self + } + + /// Add Services menu item to the submenu. + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux:** Unsupported. + pub fn services(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::services(self.manager, None).kind()); + self + } + + /// Builds this submenu + pub fn build(self) -> crate::Result> { + if self.items.is_empty() { + Ok(if let Some(id) = self.id { + Submenu::with_id(self.manager, id, self.text, self.enabled) + } else { + Submenu::new(self.manager, self.text, self.enabled) + }) + } else { + let items = self + .items + .iter() + .map(|i| i as &dyn IsMenuItem) + .collect::>(); + if let Some(id) = self.id { + Submenu::with_id_and_items(self.manager, id, self.text, self.enabled, &items) + } else { + Submenu::with_items(self.manager, self.text, self.enabled, &items) + } + } + } +} diff --git a/core/tauri/src/menu/check.rs b/core/tauri/src/menu/check.rs new file mode 100644 index 000000000000..28d90990f2e4 --- /dev/null +++ b/core/tauri/src/menu/check.rs @@ -0,0 +1,148 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use crate::{menu::MenuId, run_main_thread, AppHandle, Manager, Runtime}; + +/// A menu item inside a [`Menu`] or [`Submenu`] and contains only text. +/// +/// [`Menu`]: super::Menu +/// [`Submenu`]: super::Submenu +pub struct CheckMenuItem { + pub(crate) id: MenuId, + pub(crate) inner: muda::CheckMenuItem, + pub(crate) app_handle: AppHandle, +} + +impl Clone for CheckMenuItem { + fn clone(&self) -> Self { + Self { + id: self.id.clone(), + inner: self.inner.clone(), + app_handle: self.app_handle.clone(), + } + } +} + +/// # Safety +/// +/// We make sure it always runs on the main thread. +unsafe impl Sync for CheckMenuItem {} +unsafe impl Send for CheckMenuItem {} + +impl super::sealed::IsMenuItemBase for CheckMenuItem { + fn inner(&self) -> &dyn muda::IsMenuItem { + &self.inner + } +} + +impl super::IsMenuItem for CheckMenuItem { + fn kind(&self) -> super::MenuItemKind { + super::MenuItemKind::Check(self.clone()) + } + + fn id(&self) -> &MenuId { + &self.id + } +} + +impl CheckMenuItem { + /// Create a new menu item. + /// + /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic + /// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`. + pub fn new, S: AsRef>( + manager: &M, + text: S, + enabled: bool, + checked: bool, + acccelerator: Option, + ) -> Self { + let item = muda::CheckMenuItem::new( + text, + enabled, + checked, + acccelerator.and_then(|s| s.as_ref().parse().ok()), + ); + Self { + id: item.id().clone(), + inner: item, + app_handle: manager.app_handle().clone(), + } + } + + /// Create a new menu item with the specified id. + /// + /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic + /// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`. + pub fn with_id, I: Into, S: AsRef>( + manager: &M, + id: I, + text: S, + enabled: bool, + checked: bool, + acccelerator: Option, + ) -> Self { + let item = muda::CheckMenuItem::with_id( + id, + text, + enabled, + checked, + acccelerator.and_then(|s| s.as_ref().parse().ok()), + ); + Self { + id: item.id().clone(), + inner: item, + app_handle: manager.app_handle().clone(), + } + } + + /// The application handle associated with this type. + pub fn app_handle(&self) -> &AppHandle { + &self.app_handle + } + + /// Returns a unique identifier associated with this menu item. + pub fn id(&self) -> &MenuId { + &self.id + } + + /// Get the text for this menu item. + pub fn text(&self) -> crate::Result { + run_main_thread!(self, |self_: Self| self_.inner.text()) + } + + /// Set the text for this menu item. `text` could optionally contain + /// an `&` before a character to assign this character as the mnemonic + /// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`. + pub fn set_text>(&self, text: S) -> crate::Result<()> { + let text = text.as_ref().to_string(); + run_main_thread!(self, |self_: Self| self_.inner.set_text(text)) + } + + /// Get whether this menu item is enabled or not. + pub fn is_enabled(&self) -> crate::Result { + run_main_thread!(self, |self_: Self| self_.inner.is_enabled()) + } + + /// Enable or disable this menu item. + pub fn set_enabled(&self, enabled: bool) -> crate::Result<()> { + run_main_thread!(self, |self_: Self| self_.inner.set_enabled(enabled)) + } + + /// Set this menu item accelerator. + pub fn set_accelerator>(&self, acccelerator: Option) -> crate::Result<()> { + let accel = acccelerator.and_then(|s| s.as_ref().parse().ok()); + run_main_thread!(self, |self_: Self| self_.inner.set_accelerator(accel))?.map_err(Into::into) + } + + /// Get whether this check menu item is checked or not. + pub fn is_checked(&self) -> crate::Result { + run_main_thread!(self, |self_: Self| self_.inner.is_checked()) + } + + /// Check or Uncheck this check menu item. + pub fn set_checked(&self, checked: bool) -> crate::Result<()> { + run_main_thread!(self, |self_: Self| self_.inner.set_checked(checked)) + } +} diff --git a/core/tauri/src/menu/icon.rs b/core/tauri/src/menu/icon.rs new file mode 100644 index 000000000000..9a2d8b0dda5e --- /dev/null +++ b/core/tauri/src/menu/icon.rs @@ -0,0 +1,214 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use super::NativeIcon; +use crate::{menu::MenuId, run_main_thread, AppHandle, Icon, Manager, Runtime}; + +/// A menu item inside a [`Menu`] or [`Submenu`] and contains only text. +/// +/// [`Menu`]: super::Menu +/// [`Submenu`]: super::Submenu +pub struct IconMenuItem { + pub(crate) id: MenuId, + pub(crate) inner: muda::IconMenuItem, + pub(crate) app_handle: AppHandle, +} + +impl Clone for IconMenuItem { + fn clone(&self) -> Self { + Self { + id: self.id.clone(), + inner: self.inner.clone(), + app_handle: self.app_handle.clone(), + } + } +} + +/// # Safety +/// +/// We make sure it always runs on the main thread. +unsafe impl Sync for IconMenuItem {} +unsafe impl Send for IconMenuItem {} + +impl super::sealed::IsMenuItemBase for IconMenuItem { + fn inner(&self) -> &dyn muda::IsMenuItem { + &self.inner + } +} + +impl super::IsMenuItem for IconMenuItem { + fn kind(&self) -> super::MenuItemKind { + super::MenuItemKind::Icon(self.clone()) + } + + fn id(&self) -> &MenuId { + &self.id + } +} + +impl IconMenuItem { + /// Create a new menu item. + /// + /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic + /// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`. + pub fn new, S: AsRef>( + manager: &M, + text: S, + enabled: bool, + icon: Option, + acccelerator: Option, + ) -> Self { + let item = muda::IconMenuItem::new( + text, + enabled, + icon.and_then(|i| i.try_into().ok()), + acccelerator.and_then(|s| s.as_ref().parse().ok()), + ); + Self { + id: item.id().clone(), + inner: item, + app_handle: manager.app_handle().clone(), + } + } + + /// Create a new menu item with the specified id. + /// + /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic + /// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`. + pub fn with_id, I: Into, S: AsRef>( + manager: &M, + id: I, + text: S, + enabled: bool, + icon: Option, + acccelerator: Option, + ) -> Self { + let item = muda::IconMenuItem::with_id( + id, + text, + enabled, + icon.and_then(|i| i.try_into().ok()), + acccelerator.and_then(|s| s.as_ref().parse().ok()), + ); + Self { + id: item.id().clone(), + inner: item, + app_handle: manager.app_handle().clone(), + } + } + + /// Create a new icon menu item but with a native icon. + /// + /// See [`IconMenuItem::new`] for more info. + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux**: Unsupported. + pub fn with_native_icon, S: AsRef>( + manager: &M, + text: S, + enabled: bool, + native_icon: Option, + acccelerator: Option, + ) -> Self { + let item = muda::IconMenuItem::with_native_icon( + text, + enabled, + native_icon, + acccelerator.and_then(|s| s.as_ref().parse().ok()), + ); + Self { + id: item.id().clone(), + inner: item, + app_handle: manager.app_handle().clone(), + } + } + + /// Create a new icon menu item with the specified id but with a native icon. + /// + /// See [`IconMenuItem::new`] for more info. + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux**: Unsupported. + pub fn with_id_and_native_icon, I: Into, S: AsRef>( + manager: &M, + id: I, + text: S, + enabled: bool, + native_icon: Option, + acccelerator: Option, + ) -> Self { + let item = muda::IconMenuItem::with_id_and_native_icon( + id, + text, + enabled, + native_icon, + acccelerator.and_then(|s| s.as_ref().parse().ok()), + ); + Self { + id: item.id().clone(), + inner: item, + app_handle: manager.app_handle().clone(), + } + } + + /// The application handle associated with this type. + pub fn app_handle(&self) -> &AppHandle { + &self.app_handle + } + + /// Returns a unique identifier associated with this menu item. + pub fn id(&self) -> &MenuId { + &self.id + } + + /// Get the text for this menu item. + pub fn text(&self) -> crate::Result { + run_main_thread!(self, |self_: Self| self_.inner.text()) + } + + /// Set the text for this menu item. `text` could optionally contain + /// an `&` before a character to assign this character as the mnemonic + /// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`. + pub fn set_text>(&self, text: S) -> crate::Result<()> { + let text = text.as_ref().to_string(); + run_main_thread!(self, |self_: Self| self_.inner.set_text(text)) + } + + /// Get whether this menu item is enabled or not. + pub fn is_enabled(&self) -> crate::Result { + run_main_thread!(self, |self_: Self| self_.inner.is_enabled()) + } + + /// Enable or disable this menu item. + pub fn set_enabled(&self, enabled: bool) -> crate::Result<()> { + run_main_thread!(self, |self_: Self| self_.inner.set_enabled(enabled)) + } + + /// Set this menu item accelerator. + pub fn set_accelerator>(&self, acccelerator: Option) -> crate::Result<()> { + let accel = acccelerator.and_then(|s| s.as_ref().parse().ok()); + run_main_thread!(self, |self_: Self| self_.inner.set_accelerator(accel))?.map_err(Into::into) + } + + /// Change this menu item icon or remove it. + pub fn set_icon(&self, icon: Option) -> crate::Result<()> { + run_main_thread!(self, |self_: Self| self_ + .inner + .set_icon(icon.and_then(|i| i.try_into().ok()))) + } + + /// Change this menu item icon to a native image or remove it. + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux**: Unsupported. + pub fn set_native_icon(&mut self, _icon: Option) -> crate::Result<()> { + #[cfg(target_os = "macos")] + return run_main_thread!(self, |mut self_: Self| self_.inner.set_native_icon(_icon)); + #[allow(unreachable_code)] + Ok(()) + } +} diff --git a/core/tauri/src/menu/menu.rs b/core/tauri/src/menu/menu.rs new file mode 100644 index 000000000000..f4752d665c56 --- /dev/null +++ b/core/tauri/src/menu/menu.rs @@ -0,0 +1,397 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use super::sealed::ContextMenuBase; +use super::{IsMenuItem, MenuItemKind, PredefinedMenuItem, Submenu}; +use crate::Window; +use crate::{run_main_thread, AppHandle, Manager, Position, Runtime}; +use muda::ContextMenu; +use muda::{AboutMetadata, MenuId}; + +/// Expected submenu id of the Window menu for macOS. +pub const WINDOW_SUBMENU_ID: &str = "__tauri_window_menu__"; +/// Expected submenu id of the Help menu for macOS. +pub const HELP_SUBMENU_ID: &str = "__tauri_help_menu__"; + +/// A type that is either a menu bar on the window +/// on Windows and Linux or as a global menu in the menubar on macOS. +pub struct Menu { + pub(crate) id: MenuId, + pub(crate) inner: muda::Menu, + pub(crate) app_handle: AppHandle, +} + +/// # Safety +/// +/// We make sure it always runs on the main thread. +unsafe impl Sync for Menu {} +unsafe impl Send for Menu {} + +impl Clone for Menu { + fn clone(&self) -> Self { + Self { + id: self.id.clone(), + inner: self.inner.clone(), + app_handle: self.app_handle.clone(), + } + } +} + +impl super::ContextMenu for Menu { + fn popup(&self, window: Window) -> crate::Result<()> { + self.popup_inner(window, None::) + } + + fn popup_at>( + &self, + window: Window, + position: P, + ) -> crate::Result<()> { + self.popup_inner(window, Some(position)) + } +} + +impl ContextMenuBase for Menu { + fn popup_inner>( + &self, + window: crate::Window, + position: Option

, + ) -> crate::Result<()> { + let position = position.map(Into::into).map(super::into_position); + run_main_thread!(self, move |self_: Self| { + #[cfg(target_os = "macos")] + if let Ok(view) = window.ns_view() { + self_ + .inner() + .show_context_menu_for_nsview(view as _, position); + } + + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + if let Ok(w) = window.gtk_window() { + self_.inner().show_context_menu_for_gtk_window(&w, position); + } + + #[cfg(windows)] + if let Ok(hwnd) = window.hwnd() { + self_.inner().show_context_menu_for_hwnd(hwnd.0, position) + } + }) + } + fn inner(&self) -> &dyn muda::ContextMenu { + &self.inner + } + + fn inner_owned(&self) -> Box { + Box::new(self.clone().inner) + } +} + +impl Menu { + /// Creates a new menu. + pub fn new>(manager: &M) -> Self { + let menu = muda::Menu::new(); + Self { + id: menu.id().clone(), + inner: menu, + app_handle: manager.app_handle().clone(), + } + } + + /// Creates a new menu with the specified id. + pub fn with_id, I: Into>(manager: &M, id: I) -> Self { + let menu = muda::Menu::with_id(id); + Self { + id: menu.id().clone(), + inner: menu, + app_handle: manager.app_handle().clone(), + } + } + + /// Creates a new menu with given `items`. It calls [`Menu::new`] and [`Menu::append_items`] internally. + pub fn with_items>( + manager: &M, + items: &[&dyn IsMenuItem], + ) -> crate::Result { + let menu = Self::new(manager); + menu.append_items(items)?; + Ok(menu) + } + + /// Creates a new menu with the specified id and given `items`. + /// It calls [`Menu::new`] and [`Menu::append_items`] internally. + pub fn with_id_and_items, I: Into>( + manager: &M, + id: I, + items: &[&dyn IsMenuItem], + ) -> crate::Result { + let menu = Self::with_id(manager, id); + menu.append_items(items)?; + Ok(menu) + } + + /// Creates a menu filled with default menu items and submenus. + pub fn default(app_handle: &AppHandle) -> crate::Result { + let pkg_info = app_handle.package_info(); + let config = app_handle.config(); + let about_metadata = AboutMetadata { + name: Some(pkg_info.name.clone()), + version: Some(pkg_info.version.to_string()), + copyright: config.tauri.bundle.copyright.clone(), + authors: config.tauri.bundle.publisher.clone().map(|p| vec![p]), + ..Default::default() + }; + + let window_menu = Submenu::with_id_and_items( + app_handle, + WINDOW_SUBMENU_ID, + "Window", + true, + &[ + &PredefinedMenuItem::minimize(app_handle, None), + &PredefinedMenuItem::maximize(app_handle, None), + #[cfg(target_os = "macos")] + &PredefinedMenuItem::separator(app_handle), + &PredefinedMenuItem::close_window(app_handle, None), + ], + )?; + + let help_menu = Submenu::with_id_and_items( + app_handle, + HELP_SUBMENU_ID, + "Help", + true, + &[ + #[cfg(not(target_os = "macos"))] + &PredefinedMenuItem::about(app_handle, None, Some(about_metadata)), + ], + )?; + + let menu = Menu::with_items( + app_handle, + &[ + #[cfg(target_os = "macos")] + &Submenu::with_items( + app_handle, + pkg_info.name.clone(), + true, + &[ + &PredefinedMenuItem::about(app_handle, None, Some(about_metadata)), + &PredefinedMenuItem::separator(app_handle), + &PredefinedMenuItem::services(app_handle, None), + &PredefinedMenuItem::separator(app_handle), + &PredefinedMenuItem::hide(app_handle, None), + &PredefinedMenuItem::hide_others(app_handle, None), + &PredefinedMenuItem::separator(app_handle), + &PredefinedMenuItem::quit(app_handle, None), + ], + )?, + #[cfg(not(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + )))] + &Submenu::with_items( + app_handle, + "File", + true, + &[ + &PredefinedMenuItem::close_window(app_handle, None), + #[cfg(not(target_os = "macos"))] + &PredefinedMenuItem::quit(app_handle, None), + ], + )?, + &Submenu::with_items( + app_handle, + "Edit", + true, + &[ + &PredefinedMenuItem::undo(app_handle, None), + &PredefinedMenuItem::redo(app_handle, None), + &PredefinedMenuItem::separator(app_handle), + &PredefinedMenuItem::cut(app_handle, None), + &PredefinedMenuItem::copy(app_handle, None), + &PredefinedMenuItem::paste(app_handle, None), + &PredefinedMenuItem::select_all(app_handle, None), + ], + )?, + #[cfg(target_os = "macos")] + &Submenu::with_items( + app_handle, + "View", + true, + &[&PredefinedMenuItem::fullscreen(app_handle, None)], + )?, + &window_menu, + &help_menu, + ], + )?; + + Ok(menu) + } + + pub(crate) fn inner(&self) -> &muda::Menu { + &self.inner + } + + /// The application handle associated with this type. + pub fn app_handle(&self) -> &AppHandle { + &self.app_handle + } + + /// Returns a unique identifier associated with this menu. + pub fn id(&self) -> &MenuId { + &self.id + } + + /// Add a menu item to the end of this menu. + /// + /// ## Platform-spcific: + /// + /// - **macOS:** Only [`Submenu`] can be added to the menu. + /// + /// [`Submenu`]: super::Submenu + pub fn append(&self, item: &dyn IsMenuItem) -> crate::Result<()> { + let kind = item.kind(); + run_main_thread!(self, |self_: Self| self_.inner.append(kind.inner().inner()))? + .map_err(Into::into) + } + + /// Add menu items to the end of this menu. It calls [`Menu::append`] in a loop internally. + /// + /// ## Platform-spcific: + /// + /// - **macOS:** Only [`Submenu`] can be added to the menu + /// + /// [`Submenu`]: super::Submenu + pub fn append_items(&self, items: &[&dyn IsMenuItem]) -> crate::Result<()> { + for item in items { + self.append(*item)? + } + + Ok(()) + } + + /// Add a menu item to the beginning of this menu. + /// + /// ## Platform-spcific: + /// + /// - **macOS:** Only [`Submenu`] can be added to the menu + /// + /// [`Submenu`]: super::Submenu + pub fn prepend(&self, item: &dyn IsMenuItem) -> crate::Result<()> { + let kind = item.kind(); + run_main_thread!(self, |self_: Self| self_ + .inner + .prepend(kind.inner().inner()))? + .map_err(Into::into) + } + + /// Add menu items to the beginning of this menu. It calls [`Menu::insert_items`] with position of `0` internally. + /// + /// ## Platform-spcific: + /// + /// - **macOS:** Only [`Submenu`] can be added to the menu + /// + /// [`Submenu`]: super::Submenu + pub fn prepend_items(&self, items: &[&dyn IsMenuItem]) -> crate::Result<()> { + self.insert_items(items, 0) + } + + /// Insert a menu item at the specified `postion` in the menu. + /// + /// ## Platform-spcific: + /// + /// - **macOS:** Only [`Submenu`] can be added to the menu + /// + /// [`Submenu`]: super::Submenu + pub fn insert(&self, item: &dyn IsMenuItem, position: usize) -> crate::Result<()> { + let kind = item.kind(); + run_main_thread!(self, |self_: Self| self_ + .inner + .insert(kind.inner().inner(), position))? + .map_err(Into::into) + } + + /// Insert menu items at the specified `postion` in the menu. + /// + /// ## Platform-spcific: + /// + /// - **macOS:** Only [`Submenu`] can be added to the menu + /// + /// [`Submenu`]: super::Submenu + pub fn insert_items(&self, items: &[&dyn IsMenuItem], position: usize) -> crate::Result<()> { + for (i, item) in items.iter().enumerate() { + self.insert(*item, position + i)? + } + + Ok(()) + } + + /// Remove a menu item from this menu. + pub fn remove(&self, item: &dyn IsMenuItem) -> crate::Result<()> { + let kind = item.kind(); + run_main_thread!(self, |self_: Self| self_.inner.remove(kind.inner().inner()))? + .map_err(Into::into) + } + + /// Retrieves the menu item matching the given identifier. + pub fn get<'a, I>(&self, id: &'a I) -> Option> + where + I: ?Sized, + MenuId: PartialEq<&'a I>, + { + self + .items() + .unwrap_or_default() + .into_iter() + .find(|i| i.id() == &id) + } + + /// Returns a list of menu items that has been added to this menu. + pub fn items(&self) -> crate::Result>> { + let handle = self.app_handle.clone(); + run_main_thread!(self, |self_: Self| self_ + .inner + .items() + .into_iter() + .map(|i| match i { + muda::MenuItemKind::MenuItem(i) => super::MenuItemKind::MenuItem(super::MenuItem { + id: i.id().clone(), + inner: i, + app_handle: handle.clone(), + }), + muda::MenuItemKind::Submenu(i) => super::MenuItemKind::Submenu(super::Submenu { + id: i.id().clone(), + inner: i, + app_handle: handle.clone(), + }), + muda::MenuItemKind::Predefined(i) => { + super::MenuItemKind::Predefined(super::PredefinedMenuItem { + id: i.id().clone(), + inner: i, + app_handle: handle.clone(), + }) + } + muda::MenuItemKind::Check(i) => super::MenuItemKind::Check(super::CheckMenuItem { + id: i.id().clone(), + inner: i, + app_handle: handle.clone(), + }), + muda::MenuItemKind::Icon(i) => super::MenuItemKind::Icon(super::IconMenuItem { + id: i.id().clone(), + inner: i, + app_handle: handle.clone(), + }), + }) + .collect::>()) + } +} diff --git a/core/tauri/src/menu/mod.rs b/core/tauri/src/menu/mod.rs new file mode 100644 index 000000000000..4f594f9905f8 --- /dev/null +++ b/core/tauri/src/menu/mod.rs @@ -0,0 +1,251 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +#![cfg(desktop)] + +//! Menu types and utility functions + +// TODO(muda-migration): figure out js events + +mod builders; +mod check; +mod icon; +#[allow(clippy::module_inception)] +mod menu; +mod normal; +mod predefined; +mod submenu; +pub use builders::*; +pub use check::CheckMenuItem; +pub use icon::IconMenuItem; +pub use menu::{Menu, HELP_SUBMENU_ID, WINDOW_SUBMENU_ID}; +pub use normal::MenuItem; +pub use predefined::PredefinedMenuItem; +pub use submenu::Submenu; + +use crate::Runtime; +pub use muda::{AboutMetadata, MenuEvent, MenuId, NativeIcon}; + +/// An enumeration of all menu item kinds that could be added to +/// a [`Menu`] or [`Submenu`] +pub enum MenuItemKind { + /// Normal menu item + MenuItem(MenuItem), + /// Submenu menu item + Submenu(Submenu), + /// Predefined menu item + Predefined(PredefinedMenuItem), + /// Check menu item + Check(CheckMenuItem), + /// Icon menu item + Icon(IconMenuItem), +} + +impl MenuItemKind { + /// Returns a unique identifier associated with this menu item. + pub fn id(&self) -> &MenuId { + match self { + MenuItemKind::MenuItem(i) => i.id(), + MenuItemKind::Submenu(i) => i.id(), + MenuItemKind::Predefined(i) => i.id(), + MenuItemKind::Check(i) => i.id(), + MenuItemKind::Icon(i) => i.id(), + } + } + + pub(crate) fn inner(&self) -> &dyn IsMenuItem { + match self { + MenuItemKind::MenuItem(i) => i, + MenuItemKind::Submenu(i) => i, + MenuItemKind::Predefined(i) => i, + MenuItemKind::Check(i) => i, + MenuItemKind::Icon(i) => i, + } + } + + /// Casts this item to a [`MenuItem`], and returns `None` if it wasn't. + pub fn as_menuitem(&self) -> Option<&MenuItem> { + match self { + MenuItemKind::MenuItem(i) => Some(i), + _ => None, + } + } + + /// Casts this item to a [`MenuItem`], and panics if it wasn't. + pub fn as_menuitem_unchecked(&self) -> &MenuItem { + match self { + MenuItemKind::MenuItem(i) => i, + _ => panic!("Not a MenuItem"), + } + } + + /// Casts this item to a [`Submenu`], and returns `None` if it wasn't. + pub fn as_submenu(&self) -> Option<&Submenu> { + match self { + MenuItemKind::Submenu(i) => Some(i), + _ => None, + } + } + + /// Casts this item to a [`Submenu`], and panics if it wasn't. + pub fn as_submenu_unchecked(&self) -> &Submenu { + match self { + MenuItemKind::Submenu(i) => i, + _ => panic!("Not a Submenu"), + } + } + + /// Casts this item to a [`PredefinedMenuItem`], and returns `None` if it wasn't. + pub fn as_predefined_menuitem(&self) -> Option<&PredefinedMenuItem> { + match self { + MenuItemKind::Predefined(i) => Some(i), + _ => None, + } + } + + /// Casts this item to a [`PredefinedMenuItem`], and panics if it wasn't. + pub fn as_predefined_menuitem_unchecked(&self) -> &PredefinedMenuItem { + match self { + MenuItemKind::Predefined(i) => i, + _ => panic!("Not a PredefinedMenuItem"), + } + } + + /// Casts this item to a [`CheckMenuItem`], and returns `None` if it wasn't. + pub fn as_check_menuitem(&self) -> Option<&CheckMenuItem> { + match self { + MenuItemKind::Check(i) => Some(i), + _ => None, + } + } + + /// Casts this item to a [`CheckMenuItem`], and panics if it wasn't. + pub fn as_check_menuitem_unchecked(&self) -> &CheckMenuItem { + match self { + MenuItemKind::Check(i) => i, + _ => panic!("Not a CheckMenuItem"), + } + } + + /// Casts this item to a [`IconMenuItem`], and returns `None` if it wasn't. + pub fn as_icon_menuitem(&self) -> Option<&IconMenuItem> { + match self { + MenuItemKind::Icon(i) => Some(i), + _ => None, + } + } + + /// Casts this item to a [`IconMenuItem`], and panics if it wasn't. + pub fn as_icon_menuitem_unchecked(&self) -> &IconMenuItem { + match self { + MenuItemKind::Icon(i) => i, + _ => panic!("Not an IconMenuItem"), + } + } +} + +impl Clone for MenuItemKind { + fn clone(&self) -> Self { + match self { + Self::MenuItem(i) => Self::MenuItem(i.clone()), + Self::Submenu(i) => Self::Submenu(i.clone()), + Self::Predefined(i) => Self::Predefined(i.clone()), + Self::Check(i) => Self::Check(i.clone()), + Self::Icon(i) => Self::Icon(i.clone()), + } + } +} + +impl sealed::IsMenuItemBase for MenuItemKind { + fn inner(&self) -> &dyn muda::IsMenuItem { + self.inner().inner() + } +} + +impl IsMenuItem for MenuItemKind { + fn kind(&self) -> MenuItemKind { + self.clone() + } + + fn id(&self) -> &MenuId { + self.id() + } +} + +/// A trait that defines a generic item in a menu, which may be one of [`MenuItemKind`] +/// +/// # Safety +/// +/// This trait is ONLY meant to be implemented internally by the crate. +pub trait IsMenuItem: sealed::IsMenuItemBase { + /// Returns the kind of this menu item. + fn kind(&self) -> MenuItemKind; + + /// Returns a unique identifier associated with this menu. + fn id(&self) -> &MenuId; +} + +/// A helper trait with methods to help creating a context menu. +/// +/// # Safety +/// +/// This trait is ONLY meant to be implemented internally by the crate. +pub trait ContextMenu: sealed::ContextMenuBase + Send + Sync { + /// Popup this menu as a context menu on the specified window at the cursor position. + fn popup(&self, window: crate::Window) -> crate::Result<()>; + + /// Popup this menu as a context menu on the specified window at the specified position. + /// + /// The position is relative to the window's top-left corner. + fn popup_at>( + &self, + window: crate::Window, + position: P, + ) -> crate::Result<()>; +} + +pub(crate) mod sealed { + + pub trait IsMenuItemBase { + fn inner(&self) -> &dyn muda::IsMenuItem; + } + + pub trait ContextMenuBase { + fn inner(&self) -> &dyn muda::ContextMenu; + fn inner_owned(&self) -> Box; + fn popup_inner>( + &self, + window: crate::Window, + position: Option

, + ) -> crate::Result<()>; + } +} + +impl TryFrom for muda::Icon { + type Error = crate::Error; + + fn try_from(value: crate::Icon) -> Result { + let value: crate::runtime::Icon = value.try_into()?; + muda::Icon::from_rgba(value.rgba, value.width, value.height).map_err(Into::into) + } +} + +pub(crate) fn into_logical_position( + p: crate::LogicalPosition

, +) -> muda::LogicalPosition

{ + muda::LogicalPosition { x: p.x, y: p.y } +} + +pub(crate) fn into_physical_position( + p: crate::PhysicalPosition

, +) -> muda::PhysicalPosition

{ + muda::PhysicalPosition { x: p.x, y: p.y } +} + +pub(crate) fn into_position(p: crate::Position) -> muda::Position { + match p { + crate::Position::Physical(p) => muda::Position::Physical(into_physical_position(p)), + crate::Position::Logical(p) => muda::Position::Logical(into_logical_position(p)), + } +} diff --git a/core/tauri/src/menu/normal.rs b/core/tauri/src/menu/normal.rs new file mode 100644 index 000000000000..a7eeb3e6b71e --- /dev/null +++ b/core/tauri/src/menu/normal.rs @@ -0,0 +1,134 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use crate::{menu::MenuId, run_main_thread, AppHandle, Manager, Runtime}; + +/// A menu item inside a [`Menu`] or [`Submenu`] and contains only text. +/// +/// [`Menu`]: super::Menu +/// [`Submenu`]: super::Submenu +pub struct MenuItem { + pub(crate) id: MenuId, + pub(crate) inner: muda::MenuItem, + pub(crate) app_handle: AppHandle, +} + +impl Clone for MenuItem { + fn clone(&self) -> Self { + Self { + id: self.id.clone(), + inner: self.inner.clone(), + app_handle: self.app_handle.clone(), + } + } +} + +/// # Safety +/// +/// We make sure it always runs on the main thread. +unsafe impl Sync for MenuItem {} +unsafe impl Send for MenuItem {} + +impl super::sealed::IsMenuItemBase for MenuItem { + fn inner(&self) -> &dyn muda::IsMenuItem { + &self.inner + } +} + +impl super::IsMenuItem for MenuItem { + fn kind(&self) -> super::MenuItemKind { + super::MenuItemKind::MenuItem(self.clone()) + } + + fn id(&self) -> &MenuId { + &self.id + } +} + +impl MenuItem { + /// Create a new menu item. + /// + /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic + /// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`. + pub fn new, S: AsRef>( + manager: &M, + text: S, + enabled: bool, + acccelerator: Option, + ) -> Self { + let item = muda::MenuItem::new( + text, + enabled, + acccelerator.and_then(|s| s.as_ref().parse().ok()), + ); + Self { + id: item.id().clone(), + inner: item, + app_handle: manager.app_handle().clone(), + } + } + + /// Create a new menu item with the specified id. + /// + /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic + /// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`. + pub fn with_id, I: Into, S: AsRef>( + manager: &M, + id: I, + text: S, + enabled: bool, + acccelerator: Option, + ) -> Self { + let item = muda::MenuItem::with_id( + id, + text, + enabled, + acccelerator.and_then(|s| s.as_ref().parse().ok()), + ); + Self { + id: item.id().clone(), + inner: item, + app_handle: manager.app_handle().clone(), + } + } + + /// The application handle associated with this type. + pub fn app_handle(&self) -> &AppHandle { + &self.app_handle + } + + /// Returns a unique identifier associated with this menu item. + pub fn id(&self) -> &MenuId { + &self.id + } + + /// Get the text for this menu item. + pub fn text(&self) -> crate::Result { + run_main_thread!(self, |self_: Self| self_.inner.text()) + } + + /// Set the text for this menu item. `text` could optionally contain + /// an `&` before a character to assign this character as the mnemonic + /// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`. + pub fn set_text>(&self, text: S) -> crate::Result<()> { + let text = text.as_ref().to_string(); + run_main_thread!(self, |self_: Self| self_.inner.set_text(text)) + } + + /// Get whether this menu item is enabled or not. + pub fn is_enabled(&self) -> crate::Result { + run_main_thread!(self, |self_: Self| self_.inner.is_enabled()) + } + + /// Enable or disable this menu item. + pub fn set_enabled(&self, enabled: bool) -> crate::Result<()> { + run_main_thread!(self, |self_: Self| self_.inner.set_enabled(enabled)) + } + + /// Set this menu item accelerator. + pub fn set_accelerator>(&self, acccelerator: Option) -> crate::Result<()> { + let accel = acccelerator.and_then(|s| s.as_ref().parse().ok()); + run_main_thread!(self, |self_: Self| self_.inner.set_accelerator(accel))?.map_err(Into::into) + } +} diff --git a/core/tauri/src/menu/predefined.rs b/core/tauri/src/menu/predefined.rs new file mode 100644 index 000000000000..37ba63a78465 --- /dev/null +++ b/core/tauri/src/menu/predefined.rs @@ -0,0 +1,287 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use super::AboutMetadata; +use crate::{menu::MenuId, run_main_thread, AppHandle, Manager, Runtime}; + +/// A predefined (native) menu item which has a predfined behavior by the OS or by this crate. +pub struct PredefinedMenuItem { + pub(crate) id: MenuId, + pub(crate) inner: muda::PredefinedMenuItem, + pub(crate) app_handle: AppHandle, +} + +impl Clone for PredefinedMenuItem { + fn clone(&self) -> Self { + Self { + id: self.id.clone(), + inner: self.inner.clone(), + app_handle: self.app_handle.clone(), + } + } +} + +/// # Safety +/// +/// We make sure it always runs on the main thread. +unsafe impl Sync for PredefinedMenuItem {} +unsafe impl Send for PredefinedMenuItem {} + +impl super::sealed::IsMenuItemBase for PredefinedMenuItem { + fn inner(&self) -> &dyn muda::IsMenuItem { + &self.inner + } +} + +impl super::IsMenuItem for PredefinedMenuItem { + fn kind(&self) -> super::MenuItemKind { + super::MenuItemKind::Predefined(self.clone()) + } + + fn id(&self) -> &MenuId { + self.id() + } +} + +impl PredefinedMenuItem { + /// Separator menu item + pub fn separator>(manager: &M) -> Self { + let inner = muda::PredefinedMenuItem::separator(); + Self { + id: inner.id().clone(), + inner, + app_handle: manager.app_handle().clone(), + } + } + + /// Copy menu item + pub fn copy>(manager: &M, text: Option<&str>) -> Self { + let inner = muda::PredefinedMenuItem::copy(text); + Self { + id: inner.id().clone(), + inner, + app_handle: manager.app_handle().clone(), + } + } + + /// Cut menu item + pub fn cut>(manager: &M, text: Option<&str>) -> Self { + let inner = muda::PredefinedMenuItem::cut(text); + Self { + id: inner.id().clone(), + inner, + app_handle: manager.app_handle().clone(), + } + } + + /// Paste menu item + pub fn paste>(manager: &M, text: Option<&str>) -> Self { + let inner = muda::PredefinedMenuItem::paste(text); + Self { + id: inner.id().clone(), + inner, + app_handle: manager.app_handle().clone(), + } + } + + /// SelectAll menu item + pub fn select_all>(manager: &M, text: Option<&str>) -> Self { + let inner = muda::PredefinedMenuItem::select_all(text); + Self { + id: inner.id().clone(), + inner, + app_handle: manager.app_handle().clone(), + } + } + + /// Undo menu item + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux:** Unsupported. + pub fn undo>(manager: &M, text: Option<&str>) -> Self { + let inner = muda::PredefinedMenuItem::undo(text); + Self { + id: inner.id().clone(), + inner, + app_handle: manager.app_handle().clone(), + } + } + /// Redo menu item + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux:** Unsupported. + pub fn redo>(manager: &M, text: Option<&str>) -> Self { + let inner = muda::PredefinedMenuItem::redo(text); + Self { + id: inner.id().clone(), + inner, + app_handle: manager.app_handle().clone(), + } + } + + /// Minimize window menu item + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported. + pub fn minimize>(manager: &M, text: Option<&str>) -> Self { + let inner = muda::PredefinedMenuItem::minimize(text); + Self { + id: inner.id().clone(), + inner, + app_handle: manager.app_handle().clone(), + } + } + + /// Maximize window menu item + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported. + pub fn maximize>(manager: &M, text: Option<&str>) -> Self { + let inner = muda::PredefinedMenuItem::maximize(text); + Self { + id: inner.id().clone(), + inner, + app_handle: manager.app_handle().clone(), + } + } + + /// Fullscreen menu item + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux:** Unsupported. + pub fn fullscreen>(manager: &M, text: Option<&str>) -> Self { + let inner = muda::PredefinedMenuItem::fullscreen(text); + Self { + id: inner.id().clone(), + inner, + app_handle: manager.app_handle().clone(), + } + } + + /// Hide window menu item + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported. + pub fn hide>(manager: &M, text: Option<&str>) -> Self { + let inner = muda::PredefinedMenuItem::hide(text); + Self { + id: inner.id().clone(), + inner, + app_handle: manager.app_handle().clone(), + } + } + + /// Hide other windows menu item + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported. + pub fn hide_others>(manager: &M, text: Option<&str>) -> Self { + let inner = muda::PredefinedMenuItem::hide_others(text); + Self { + id: inner.id().clone(), + inner, + app_handle: manager.app_handle().clone(), + } + } + + /// Show all app windows menu item + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux:** Unsupported. + pub fn show_all>(manager: &M, text: Option<&str>) -> Self { + let inner = muda::PredefinedMenuItem::show_all(text); + Self { + id: inner.id().clone(), + inner, + app_handle: manager.app_handle().clone(), + } + } + + /// Close window menu item + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported. + pub fn close_window>(manager: &M, text: Option<&str>) -> Self { + let inner = muda::PredefinedMenuItem::show_all(text); + Self { + id: inner.id().clone(), + inner, + app_handle: manager.app_handle().clone(), + } + } + + /// Quit app menu item + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported. + pub fn quit>(manager: &M, text: Option<&str>) -> Self { + let inner = muda::PredefinedMenuItem::quit(text); + Self { + id: inner.id().clone(), + inner, + app_handle: manager.app_handle().clone(), + } + } + + /// About app menu item + pub fn about>( + manager: &M, + text: Option<&str>, + metadata: Option, + ) -> Self { + let inner = muda::PredefinedMenuItem::about(text, metadata); + Self { + id: inner.id().clone(), + inner, + app_handle: manager.app_handle().clone(), + } + } + + /// Services menu item + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux:** Unsupported. + pub fn services>(manager: &M, text: Option<&str>) -> Self { + let inner = muda::PredefinedMenuItem::services(text); + Self { + id: inner.id().clone(), + inner, + app_handle: manager.app_handle().clone(), + } + } + + /// Returns a unique identifier associated with this menu item. + pub fn id(&self) -> &MenuId { + &self.id + } + + /// Get the text for this menu item. + pub fn text(&self) -> crate::Result { + run_main_thread!(self, |self_: Self| self_.inner.text()) + } + + /// Set the text for this menu item. `text` could optionally contain + /// an `&` before a character to assign this character as the mnemonic + /// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`. + pub fn set_text>(&self, text: S) -> crate::Result<()> { + let text = text.as_ref().to_string(); + run_main_thread!(self, |self_: Self| self_.inner.set_text(text)) + } + + /// The application handle associated with this type. + pub fn app_handle(&self) -> &AppHandle { + &self.app_handle + } +} diff --git a/core/tauri/src/menu/submenu.rs b/core/tauri/src/menu/submenu.rs new file mode 100644 index 000000000000..905f04830c98 --- /dev/null +++ b/core/tauri/src/menu/submenu.rs @@ -0,0 +1,328 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use super::{sealed::ContextMenuBase, IsMenuItem, MenuItemKind}; +use crate::{run_main_thread, AppHandle, Manager, Position, Runtime, Window}; +use muda::{ContextMenu, MenuId}; + +/// A type that is a submenu inside a [`Menu`] or [`Submenu`] +/// +/// [`Menu`]: super::Menu +/// [`Submenu`]: super::Submenu +pub struct Submenu { + pub(crate) id: MenuId, + pub(crate) inner: muda::Submenu, + pub(crate) app_handle: AppHandle, +} + +/// # Safety +/// +/// We make sure it always runs on the main thread. +unsafe impl Sync for Submenu {} +unsafe impl Send for Submenu {} + +impl Clone for Submenu { + fn clone(&self) -> Self { + Self { + id: self.id.clone(), + inner: self.inner.clone(), + app_handle: self.app_handle.clone(), + } + } +} + +impl super::sealed::IsMenuItemBase for Submenu { + fn inner(&self) -> &dyn muda::IsMenuItem { + &self.inner + } +} + +impl super::IsMenuItem for Submenu { + fn kind(&self) -> super::MenuItemKind { + super::MenuItemKind::Submenu(self.clone()) + } + + fn id(&self) -> &MenuId { + &self.id + } +} + +impl super::ContextMenu for Submenu { + fn popup(&self, window: Window) -> crate::Result<()> { + self.popup_inner(window, None::) + } + + fn popup_at>( + &self, + window: Window, + position: P, + ) -> crate::Result<()> { + self.popup_inner(window, Some(position)) + } +} + +impl ContextMenuBase for Submenu { + fn popup_inner>( + &self, + window: crate::Window, + position: Option

, + ) -> crate::Result<()> { + let position = position.map(Into::into).map(super::into_position); + run_main_thread!(self, move |self_: Self| { + #[cfg(target_os = "macos")] + if let Ok(view) = window.ns_view() { + self_ + .inner() + .show_context_menu_for_nsview(view as _, position); + } + + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + if let Ok(w) = window.gtk_window() { + self_.inner().show_context_menu_for_gtk_window(&w, position); + } + + #[cfg(windows)] + if let Ok(hwnd) = window.hwnd() { + self_.inner().show_context_menu_for_hwnd(hwnd.0, position) + } + }) + } + + fn inner(&self) -> &dyn muda::ContextMenu { + &self.inner + } + + fn inner_owned(&self) -> Box { + Box::new(self.clone().inner) + } +} + +impl Submenu { + /// Creates a new submenu. + pub fn new, S: AsRef>(manager: &M, text: S, enabled: bool) -> Self { + let submenu = muda::Submenu::new(text, enabled); + Self { + id: submenu.id().clone(), + inner: submenu, + app_handle: manager.app_handle().clone(), + } + } + + /// Creates a new submenu with the specified id. + pub fn with_id, I: Into, S: AsRef>( + manager: &M, + id: I, + text: S, + enabled: bool, + ) -> Self { + let menu = muda::Submenu::with_id(id, text, enabled); + Self { + id: menu.id().clone(), + inner: menu, + app_handle: manager.app_handle().clone(), + } + } + + /// Creates a new menu with given `items`. It calls [`Submenu::new`] and [`Submenu::append_items`] internally. + pub fn with_items, S: AsRef>( + manager: &M, + text: S, + enabled: bool, + items: &[&dyn IsMenuItem], + ) -> crate::Result { + let menu = Self::new(manager, text, enabled); + menu.append_items(items)?; + Ok(menu) + } + + /// Creates a new menu with the specified id and given `items`. + /// It calls [`Submenu::new`] and [`Submenu::append_items`] internally. + pub fn with_id_and_items, I: Into, S: AsRef>( + manager: &M, + id: I, + text: S, + enabled: bool, + items: &[&dyn IsMenuItem], + ) -> crate::Result { + let menu = Self::with_id(manager, id, text, enabled); + menu.append_items(items)?; + Ok(menu) + } + + pub(crate) fn inner(&self) -> &muda::Submenu { + &self.inner + } + + /// The application handle associated with this type. + pub fn app_handle(&self) -> &AppHandle { + &self.app_handle + } + + /// Returns a unique identifier associated with this submenu. + pub fn id(&self) -> &MenuId { + &self.id + } + + /// Add a menu item to the end of this submenu. + pub fn append(&self, item: &dyn IsMenuItem) -> crate::Result<()> { + let kind = item.kind(); + run_main_thread!(self, |self_: Self| self_.inner.append(kind.inner().inner()))? + .map_err(Into::into) + } + + /// Add menu items to the end of this submenu. It calls [`Submenu::append`] in a loop internally. + pub fn append_items(&self, items: &[&dyn IsMenuItem]) -> crate::Result<()> { + for item in items { + self.append(*item)? + } + + Ok(()) + } + + /// Add a menu item to the beginning of this submenu. + pub fn prepend(&self, item: &dyn IsMenuItem) -> crate::Result<()> { + let kind = item.kind(); + run_main_thread!(self, |self_: Self| { + self_.inner.prepend(kind.inner().inner()) + })? + .map_err(Into::into) + } + + /// Add menu items to the beginning of this submenu. It calls [`Submenu::insert_items`] with position of `0` internally. + pub fn prepend_items(&self, items: &[&dyn IsMenuItem]) -> crate::Result<()> { + self.insert_items(items, 0) + } + + /// Insert a menu item at the specified `postion` in this submenu. + pub fn insert(&self, item: &dyn IsMenuItem, position: usize) -> crate::Result<()> { + let kind = item.kind(); + run_main_thread!(self, |self_: Self| { + self_.inner.insert(kind.inner().inner(), position) + })? + .map_err(Into::into) + } + + /// Insert menu items at the specified `postion` in this submenu. + pub fn insert_items(&self, items: &[&dyn IsMenuItem], position: usize) -> crate::Result<()> { + for (i, item) in items.iter().enumerate() { + self.insert(*item, position + i)? + } + + Ok(()) + } + + /// Remove a menu item from this submenu. + pub fn remove(&self, item: &dyn IsMenuItem) -> crate::Result<()> { + let kind = item.kind(); + run_main_thread!(self, |self_: Self| self_.inner.remove(kind.inner().inner()))? + .map_err(Into::into) + } + + /// Returns a list of menu items that has been added to this submenu. + pub fn items(&self) -> crate::Result>> { + let handle = self.app_handle.clone(); + run_main_thread!(self, |self_: Self| { + self_ + .inner + .items() + .into_iter() + .map(|i| match i { + muda::MenuItemKind::MenuItem(i) => super::MenuItemKind::MenuItem(super::MenuItem { + id: i.id().clone(), + inner: i, + app_handle: handle.clone(), + }), + muda::MenuItemKind::Submenu(i) => super::MenuItemKind::Submenu(super::Submenu { + id: i.id().clone(), + inner: i, + app_handle: handle.clone(), + }), + muda::MenuItemKind::Predefined(i) => { + super::MenuItemKind::Predefined(super::PredefinedMenuItem { + id: i.id().clone(), + inner: i, + app_handle: handle.clone(), + }) + } + muda::MenuItemKind::Check(i) => super::MenuItemKind::Check(super::CheckMenuItem { + id: i.id().clone(), + inner: i, + app_handle: handle.clone(), + }), + muda::MenuItemKind::Icon(i) => super::MenuItemKind::Icon(super::IconMenuItem { + id: i.id().clone(), + inner: i, + app_handle: handle.clone(), + }), + }) + .collect::>() + }) + } + + /// Get the text for this submenu. + pub fn text(&self) -> crate::Result { + run_main_thread!(self, |self_: Self| self_.inner.text()) + } + + /// Set the text for this submenu. `text` could optionally contain + /// an `&` before a character to assign this character as the mnemonic + /// for this submenu. To display a `&` without assigning a mnemenonic, use `&&`. + pub fn set_text>(&self, text: S) -> crate::Result<()> { + let text = text.as_ref().to_string(); + run_main_thread!(self, |self_: Self| self_.inner.set_text(text)) + } + + /// Get whether this submenu is enabled or not. + pub fn is_enabled(&self) -> crate::Result { + run_main_thread!(self, |self_: Self| self_.inner.is_enabled()) + } + + /// Enable or disable this submenu. + pub fn set_enabled(&self, enabled: bool) -> crate::Result<()> { + run_main_thread!(self, |self_: Self| self_.inner.set_enabled(enabled)) + } + + /// Set this submenu as the Window menu for the application on macOS. + /// + /// This will cause macOS to automatically add window-switching items and + /// certain other items to the menu. + #[cfg(target_os = "macos")] + pub fn set_as_windows_menu_for_nsapp(&self) -> crate::Result<()> { + run_main_thread!(self, |self_: Self| self_ + .inner + .set_as_windows_menu_for_nsapp())?; + Ok(()) + } + + /// Set this submenu as the Help menu for the application on macOS. + /// + /// This will cause macOS to automatically add a search box to the menu. + /// + /// If no menu is set as the Help menu, macOS will automatically use any menu + /// which has a title matching the localized word "Help". + pub fn set_as_help_menu_for_nsapp(&self) -> crate::Result<()> { + #[cfg(target_os = "macos")] + run_main_thread!(self, |self_: Self| self_.inner.set_as_help_menu_for_nsapp())?; + Ok(()) + } + + /// Retrieves the menu item matching the given identifier. + pub fn get<'a, I>(&self, id: &'a I) -> Option> + where + I: ?Sized, + MenuId: PartialEq<&'a I>, + { + self + .items() + .unwrap_or_default() + .into_iter() + .find(|i| i.id() == &id) + } +} diff --git a/core/tauri/src/path/desktop.rs b/core/tauri/src/path/desktop.rs index 15622b727963..5522c0e1ff37 100644 --- a/core/tauri/src/path/desktop.rs +++ b/core/tauri/src/path/desktop.rs @@ -194,7 +194,7 @@ impl PathResolver { /// Returns the path to the suggested directory for your app's config files. /// - /// Resolves to [`config_dir`]`/${bundle_identifier}`. + /// Resolves to [`config_dir`](self.config_dir)`/${bundle_identifier}`. pub fn app_config_dir(&self) -> Result { dirs_next::config_dir() .ok_or(Error::UnknownPath) @@ -203,7 +203,7 @@ impl PathResolver { /// Returns the path to the suggested directory for your app's data files. /// - /// Resolves to [`data_dir`]`/${bundle_identifier}`. + /// Resolves to [`data_dir`](self.data_dir)`/${bundle_identifier}`. pub fn app_data_dir(&self) -> Result { dirs_next::data_dir() .ok_or(Error::UnknownPath) @@ -212,7 +212,7 @@ impl PathResolver { /// Returns the path to the suggested directory for your app's local data files. /// - /// Resolves to [`local_data_dir`]`/${bundle_identifier}`. + /// Resolves to [`local_data_dir`](self.local_data_dir)`/${bundle_identifier}`. pub fn app_local_data_dir(&self) -> Result { dirs_next::data_local_dir() .ok_or(Error::UnknownPath) @@ -221,7 +221,7 @@ impl PathResolver { /// Returns the path to the suggested directory for your app's cache files. /// - /// Resolves to [`cache_dir`]`/${bundle_identifier}`. + /// Resolves to [`cache_dir`](self.cache_dir)`/${bundle_identifier}`. pub fn app_cache_dir(&self) -> Result { dirs_next::cache_dir() .ok_or(Error::UnknownPath) @@ -232,9 +232,9 @@ impl PathResolver { /// /// ## Platform-specific /// - /// - **Linux:** Resolves to [`data_local_dir`]`/${bundle_identifier}/logs`. - /// - **macOS:** Resolves to [`home_dir`]`/Library/Logs/${bundle_identifier}` - /// - **Windows:** Resolves to [`data_local_dir`]`/${bundle_identifier}/logs`. + /// - **Linux:** Resolves to [`data_local_dir`](self.data_local_dir)`/${bundle_identifier}/logs`. + /// - **macOS:** Resolves to [`home_dir`](self.home_dir)`/Library/Logs/${bundle_identifier}` + /// - **Windows:** Resolves to [`data_local_dir`](self.data_local_dir)`/${bundle_identifier}/logs`. pub fn app_log_dir(&self) -> Result { #[cfg(target_os = "macos")] let path = dirs_next::home_dir().ok_or(Error::UnknownPath).map(|dir| { diff --git a/core/tauri/src/path/mod.rs b/core/tauri/src/path/mod.rs index ef6c2a7b9b0c..9c4b89ea336d 100644 --- a/core/tauri/src/path/mod.rs +++ b/core/tauri/src/path/mod.rs @@ -65,7 +65,7 @@ impl<'de> Deserialize<'de> for SafePathBuf { } } -/// A base directory to be used in [`resolve_directory`]. +/// A base directory for a path. /// /// The base directory is the optional root of a file system operation. /// If informed by the API call, all paths will be relative to the path of the given directory. diff --git a/core/tauri/src/plugin/mobile.rs b/core/tauri/src/plugin/mobile.rs index 9b15f2cfce3c..4aa726e3609b 100644 --- a/core/tauri/src/plugin/mobile.rs +++ b/core/tauri/src/plugin/mobile.rs @@ -60,29 +60,29 @@ pub(crate) fn register_channel(channel: Channel) { /// Glue between Rust and the Kotlin code that sends the plugin response back. #[cfg(target_os = "android")] pub fn handle_android_plugin_response( - env: jni::JNIEnv<'_>, + env: &mut jni::JNIEnv<'_>, id: i32, success: jni::objects::JString<'_>, error: jni::objects::JString<'_>, ) { let (payload, is_ok): (serde_json::Value, bool) = match ( env - .is_same_object(success, jni::objects::JObject::default()) + .is_same_object(&success, jni::objects::JObject::default()) .unwrap_or_default(), env - .is_same_object(error, jni::objects::JObject::default()) + .is_same_object(&error, jni::objects::JObject::default()) .unwrap_or_default(), ) { // both null (true, true) => (serde_json::Value::Null, true), // error null (false, true) => ( - serde_json::from_str(env.get_string(success).unwrap().to_str().unwrap()).unwrap(), + serde_json::from_str(env.get_string(&success).unwrap().to_str().unwrap()).unwrap(), true, ), // success null (true, false) => ( - serde_json::from_str(env.get_string(error).unwrap().to_str().unwrap()).unwrap(), + serde_json::from_str(env.get_string(&error).unwrap().to_str().unwrap()).unwrap(), false, ), // both are set - impossible in the Kotlin code @@ -102,12 +102,12 @@ pub fn handle_android_plugin_response( /// Glue between Rust and the Kotlin code that sends the channel data. #[cfg(target_os = "android")] pub fn send_channel_data( - env: jni::JNIEnv<'_>, + env: &mut jni::JNIEnv<'_>, channel_id: i64, data_str: jni::objects::JString<'_>, ) { let data: serde_json::Value = - serde_json::from_str(env.get_string(data_str).unwrap().to_str().unwrap()).unwrap(); + serde_json::from_str(env.get_string(&data_str).unwrap().to_str().unwrap()).unwrap(); if let Some(channel) = CHANNELS .get_or_init(Default::default) @@ -196,24 +196,15 @@ impl PluginApi { ) -> Result, PluginInvokeError> { use jni::{errors::Error as JniError, objects::JObject, JNIEnv}; - fn initialize_plugin<'a, R: Runtime>( - env: JNIEnv<'a>, - activity: JObject<'a>, - webview: JObject<'a>, + fn initialize_plugin( + env: &mut JNIEnv<'_>, + activity: &JObject<'_>, + webview: &JObject<'_>, runtime_handle: &R::Handle, plugin_name: &'static str, plugin_class: String, plugin_config: &serde_json::Value, ) -> Result<(), JniError> { - let plugin_manager = env - .call_method( - activity, - "getPluginManager", - "()Lapp/tauri/plugin/PluginManager;", - &[], - )? - .l()?; - // instantiate plugin let plugin_class = runtime_handle.find_class(env, activity, plugin_class)?; let plugin = env.new_object( @@ -223,15 +214,28 @@ impl PluginApi { )?; // load plugin + + let plugin_manager = env + .call_method( + activity, + "getPluginManager", + "()Lapp/tauri/plugin/PluginManager;", + &[], + )? + .l()?; + + let plugin_name = env.new_string(plugin_name)?; + let config = + crate::jni_helpers::to_jsobject::(env, activity, &runtime_handle, plugin_config)?; env.call_method( plugin_manager, "load", "(Landroid/webkit/WebView;Ljava/lang/String;Lapp/tauri/plugin/Plugin;Lapp/tauri/plugin/JSObject;)V", &[ webview.into(), - env.new_string(plugin_name)?.into(), - plugin.into(), - crate::jni_helpers::to_jsobject::(env, activity, runtime_handle, plugin_config)? + (&plugin_name).into(), + (&plugin).into(), + config.borrow() ], )?; @@ -393,11 +397,13 @@ pub(crate) fn run_command< plugin: &str, command: String, payload: &serde_json::Value, - runtime_handle: &R::Handle, - env: JNIEnv<'_>, - activity: JObject<'_>, + runtime_handle: R::Handle, + env: &mut JNIEnv<'_>, + activity: &JObject<'_>, ) -> Result<(), JniError> { - let data = crate::jni_helpers::to_jsobject::(env, activity, runtime_handle, payload)?; + let plugin = env.new_string(plugin)?; + let command = env.new_string(&command)?; + let data = crate::jni_helpers::to_jsobject::(env, activity, &runtime_handle, payload)?; let plugin_manager = env .call_method( activity, @@ -413,9 +419,9 @@ pub(crate) fn run_command< "(ILjava/lang/String;Ljava/lang/String;Lapp/tauri/plugin/JSObject;)V", &[ id.into(), - env.new_string(plugin)?.into(), - env.new_string(&command)?.into(), - data, + (&plugin).into(), + (&command).into(), + data.borrow(), ], )?; @@ -440,7 +446,7 @@ pub(crate) fn run_command< .insert(id, Box::new(handler.clone())); handle.run_on_android_context(move |env, activity, _webview| { - if let Err(e) = run::(id, &plugin_name, command, &payload, &handle_, env, activity) { + if let Err(e) = run::(id, &plugin_name, command, &payload, handle_, env, activity) { handler(Err(e.to_string().into())); } }); diff --git a/core/tauri/src/test/mock_runtime.rs b/core/tauri/src/test/mock_runtime.rs index 365c72c649a3..b466de798ec0 100644 --- a/core/tauri/src/test/mock_runtime.rs +++ b/core/tauri/src/test/mock_runtime.rs @@ -6,21 +6,16 @@ #![allow(missing_docs)] use tauri_runtime::{ - menu::{Menu, MenuUpdate}, monitor::Monitor, webview::{WindowBuilder, WindowBuilderBase}, window::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, - CursorIcon, DetachedWindow, MenuEvent, PendingWindow, WindowEvent, + CursorIcon, DetachedWindow, PendingWindow, RawWindow, WindowEvent, }, DeviceEventFilter, Dispatch, Error, EventLoopProxy, ExitRequestedEventAction, Icon, Result, - RunEvent, Runtime, RuntimeHandle, UserAttentionType, UserEvent, -}; -#[cfg(all(desktop, feature = "system-tray"))] -use tauri_runtime::{ - menu::{SystemTrayMenu, TrayHandle}, - SystemTray, SystemTrayEvent, TrayId, + RunEvent, Runtime, RuntimeHandle, RuntimeInitArgs, UserAttentionType, UserEvent, }; + #[cfg(target_os = "macos")] use tauri_utils::TitleBarStyle; use tauri_utils::{config::WindowConfig, Theme}; @@ -105,9 +100,10 @@ impl RuntimeHandle for MockRuntimeHandle { } /// Create a new webview window. - fn create_window( + fn create_window) + Send + 'static>( &self, pending: PendingWindow, + _before_webview_creation: Option, ) -> Result> { let id = rand::random(); self.context.windows.borrow_mut().insert(id, Window); @@ -119,7 +115,6 @@ impl RuntimeHandle for MockRuntimeHandle { last_evaluated_script: Default::default(), url: Arc::new(Mutex::new(pending.url)), }, - menu_ids: Default::default(), }) } @@ -128,17 +123,6 @@ impl RuntimeHandle for MockRuntimeHandle { self.context.send_message(Message::Task(Box::new(f))) } - #[cfg(all(desktop, feature = "system-tray"))] - #[cfg_attr(doc_cfg, doc(cfg(all(desktop, feature = "system-tray"))))] - fn system_tray( - &self, - system_tray: SystemTray, - ) -> Result<>::TrayHandler> { - Ok(MockTrayHandler { - context: self.context.clone(), - }) - } - fn raw_display_handle(&self) -> raw_window_handle::RawDisplayHandle { #[cfg(target_os = "linux")] return raw_window_handle::RawDisplayHandle::Xlib(raw_window_handle::XlibDisplayHandle::empty()); @@ -177,8 +161,8 @@ impl RuntimeHandle for MockRuntimeHandle { #[cfg(target_os = "android")] fn find_class<'a>( &'a self, - env: jni::JNIEnv<'a>, - activity: jni::objects::JObject<'a>, + env: &'a mut jni::JNIEnv<'a>, + activity: &'a jni::objects::JObject<'a>, name: impl Into, ) -> std::result::Result, jni::errors::Error> { todo!() @@ -187,9 +171,7 @@ impl RuntimeHandle for MockRuntimeHandle { #[cfg(target_os = "android")] fn run_on_android_context(&self, f: F) where - F: FnOnce(jni::JNIEnv<'_>, jni::objects::JObject<'_>, jni::objects::JObject<'_>) - + Send - + 'static, + F: FnOnce(&mut jni::JNIEnv, &jni::objects::JObject, &jni::objects::JObject) + Send + 'static, { todo!() } @@ -223,10 +205,6 @@ impl WindowBuilder for MockWindowBuilder { Self {} } - fn menu(self, menu: Menu) -> Self { - self - } - fn center(self) -> Self { self } @@ -357,10 +335,6 @@ impl WindowBuilder for MockWindowBuilder { fn has_icon(&self) -> bool { false } - - fn get_menu(&self) -> Option<&Menu> { - None - } } impl Dispatch for MockDispatcher { @@ -376,10 +350,6 @@ impl Dispatch for MockDispatcher { Uuid::new_v4() } - fn on_menu_event(&self, f: F) -> Uuid { - Uuid::new_v4() - } - fn with_webview) + Send + 'static>(&self, f: F) -> Result<()> { Ok(()) } @@ -474,10 +444,6 @@ impl Dispatch for MockDispatcher { Ok(String::new()) } - fn is_menu_visible(&self) -> Result { - Ok(true) - } - fn current_monitor(&self) -> Result> { Ok(None) } @@ -505,6 +471,17 @@ impl Dispatch for MockDispatcher { unimplemented!() } + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + fn default_vbox(&self) -> Result { + unimplemented!() + } + fn raw_window_handle(&self) -> Result { #[cfg(target_os = "linux")] return Ok(raw_window_handle::RawWindowHandle::Xlib( @@ -534,9 +511,10 @@ impl Dispatch for MockDispatcher { Ok(()) } - fn create_window( + fn create_window) + Send + 'static>( &mut self, pending: PendingWindow, + _before_webview_creation: Option, ) -> Result> { let id = rand::random(); self.context.windows.borrow_mut().insert(id, Window); @@ -548,7 +526,6 @@ impl Dispatch for MockDispatcher { last_evaluated_script: Default::default(), url: Arc::new(Mutex::new(pending.url)), }, - menu_ids: Default::default(), }) } @@ -593,14 +570,6 @@ impl Dispatch for MockDispatcher { Ok(()) } - fn show_menu(&self) -> Result<()> { - Ok(()) - } - - fn hide_menu(&self) -> Result<()> { - Ok(()) - } - fn show(&self) -> Result<()> { Ok(()) } @@ -698,46 +667,6 @@ impl Dispatch for MockDispatcher { .replace(script.into()); Ok(()) } - - fn update_menu_item(&self, id: u16, update: MenuUpdate) -> Result<()> { - Ok(()) - } -} - -#[cfg(all(desktop, feature = "system-tray"))] -#[derive(Debug, Clone)] -pub struct MockTrayHandler { - context: RuntimeContext, -} - -#[cfg(all(desktop, feature = "system-tray"))] -impl TrayHandle for MockTrayHandler { - fn set_icon(&self, icon: Icon) -> Result<()> { - Ok(()) - } - fn set_menu(&self, menu: SystemTrayMenu) -> Result<()> { - Ok(()) - } - fn update_item(&self, id: u16, update: MenuUpdate) -> Result<()> { - Ok(()) - } - #[cfg(target_os = "macos")] - fn set_icon_as_template(&self, is_template: bool) -> Result<()> { - Ok(()) - } - - #[cfg(target_os = "macos")] - fn set_title(&self, title: &str) -> tauri_runtime::Result<()> { - Ok(()) - } - - fn set_tooltip(&self, tooltip: &str) -> Result<()> { - Ok(()) - } - - fn destroy(&self) -> Result<()> { - Ok(()) - } } #[derive(Debug, Clone)] @@ -753,8 +682,6 @@ impl EventLoopProxy for EventProxy { pub struct MockRuntime { is_running: Arc, pub context: RuntimeContext, - #[cfg(all(desktop, feature = "system-tray"))] - tray_handler: MockTrayHandler, run_rx: Receiver, } @@ -770,10 +697,6 @@ impl MockRuntime { }; Self { is_running, - #[cfg(all(desktop, feature = "system-tray"))] - tray_handler: MockTrayHandler { - context: context.clone(), - }, context, run_rx: rx, } @@ -783,16 +706,14 @@ impl MockRuntime { impl Runtime for MockRuntime { type Dispatcher = MockDispatcher; type Handle = MockRuntimeHandle; - #[cfg(all(desktop, feature = "system-tray"))] - type TrayHandler = MockTrayHandler; type EventLoopProxy = EventProxy; - fn new() -> Result { + fn new(_args: RuntimeInitArgs) -> Result { Ok(Self::init()) } #[cfg(any(windows, target_os = "linux"))] - fn new_any_thread() -> Result { + fn new_any_thread(_args: RuntimeInitArgs) -> Result { Ok(Self::init()) } @@ -806,7 +727,11 @@ impl Runtime for MockRuntime { } } - fn create_window(&self, pending: PendingWindow) -> Result> { + fn create_window) + Send + 'static>( + &self, + pending: PendingWindow, + _before_webview_creation: Option, + ) -> Result> { let id = rand::random(); self.context.windows.borrow_mut().insert(id, Window); Ok(DetachedWindow { @@ -817,20 +742,9 @@ impl Runtime for MockRuntime { last_evaluated_script: Default::default(), url: Arc::new(Mutex::new(pending.url)), }, - menu_ids: Default::default(), }) } - #[cfg(all(desktop, feature = "system-tray"))] - #[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))] - fn system_tray(&self, system_tray: SystemTray) -> Result { - Ok(self.tray_handler.clone()) - } - - #[cfg(all(desktop, feature = "system-tray"))] - #[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))] - fn on_system_tray_event(&mut self, f: F) {} - fn primary_monitor(&self) -> Option { unimplemented!() } diff --git a/core/tauri/src/test/mod.rs b/core/tauri/src/test/mod.rs index 275699517508..7b0a20e4385d 100644 --- a/core/tauri/src/test/mod.rs +++ b/core/tauri/src/test/mod.rs @@ -124,7 +124,7 @@ pub fn mock_context(assets: A) -> crate::Context { windows: Vec::new(), bundle: Default::default(), security: Default::default(), - system_tray: None, + tray_icon: None, macos_private_api: false, }, build: Default::default(), @@ -133,8 +133,8 @@ pub fn mock_context(assets: A) -> crate::Context { assets: Arc::new(assets), default_window_icon: None, app_icon: None, - #[cfg(desktop)] - system_tray_icon: None, + #[cfg(all(desktop, feature = "tray-icon"))] + tray_icon: None, package_info: crate::PackageInfo { name: "test".into(), version: "0.1.0".parse().unwrap(), @@ -163,7 +163,7 @@ pub fn mock_context(assets: A) -> crate::Context { /// } /// ``` pub fn mock_builder() -> Builder { - Builder::::new() + Builder::::new().enable_macos_default_menu(false) } /// Creates a new [`App`] for testing using the [`mock_context`] with a [`noop_assets`]. diff --git a/core/tauri/src/tray.rs b/core/tauri/src/tray.rs new file mode 100644 index 000000000000..77b3e3c1c75d --- /dev/null +++ b/core/tauri/src/tray.rs @@ -0,0 +1,351 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +#![cfg(all(desktop, feature = "tray-icon"))] + +//! Tray icon types and utility functions + +use crate::app::{GlobalMenuEventListener, GlobalTrayIconEventListener}; +use crate::menu::ContextMenu; +use crate::menu::MenuEvent; +use crate::{run_main_thread, AppHandle, Icon, Manager, Runtime}; +use std::path::Path; +pub use tray_icon::{ClickType, Rectangle, TrayIconEvent, TrayIconId}; + +// TODO(muda-migration): figure out js events + +/// [`TrayIcon`] builder struct and associated methods. +#[derive(Default)] +pub struct TrayIconBuilder { + on_menu_event: Option>>, + on_tray_event: Option>>, + inner: tray_icon::TrayIconBuilder, +} + +impl TrayIconBuilder { + /// Creates a new tray icon builder. + /// + /// ## Platform-specific: + /// + /// - **Linux:** Sometimes the icon won't be visible unless a menu is set. + /// Setting an empty [`Menu`](crate::menu::Menu) is enough. + pub fn new() -> Self { + Self { + inner: tray_icon::TrayIconBuilder::new(), + on_menu_event: None, + on_tray_event: None, + } + } + + /// Creates a new tray icon builder with the specified id. + /// + /// ## Platform-specific: + /// + /// - **Linux:** Sometimes the icon won't be visible unless a menu is set. + /// Setting an empty [`Menu`](crate::menu::Menu) is enough. + pub fn with_id>(id: I) -> Self { + let mut builder = Self::new(); + builder.inner = builder.inner.with_id(id); + builder + } + + /// Set the a menu for this tray icon. + /// + /// ## Platform-specific: + /// + /// - **Linux**: once a menu is set, it cannot be removed or replaced but you can change its content. + pub fn menu(mut self, menu: &M) -> Self { + self.inner = self.inner.with_menu(menu.inner_owned()); + self + } + + /// Set an icon for this tray icon. + /// + /// ## Platform-specific: + /// + /// - **Linux:** Sometimes the icon won't be visible unless a menu is set. + /// Setting an empty [`Menu`](crate::menu::Menu) is enough. + pub fn icon(mut self, icon: Icon) -> Self { + let icon = icon.try_into().ok(); + if let Some(icon) = icon { + self.inner = self.inner.with_icon(icon); + } + self + } + + /// Set a tooltip for this tray icon. + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported. + pub fn tooltip>(mut self, s: S) -> Self { + self.inner = self.inner.with_tooltip(s); + self + } + + /// Set the tray icon title. + /// + /// ## Platform-specific + /// + /// - **Linux:** The title will not be shown unless there is an icon + /// as well. The title is useful for numerical and other frequently + /// updated information. In general, it shouldn't be shown unless a + /// user requests it as it can take up a significant amount of space + /// on the user's panel. This may not be shown in all visualizations. + /// - **Windows:** Unsupported. + pub fn title>(mut self, title: S) -> Self { + self.inner = self.inner.with_title(title); + self + } + + /// Set tray icon temp dir path. **Linux only**. + /// + /// On Linux, we need to write the icon to the disk and usually it will + /// be `$XDG_RUNTIME_DIR/tray-icon` or `$TEMP/tray-icon`. + pub fn temp_dir_path>(mut self, s: P) -> Self { + self.inner = self.inner.with_temp_dir_path(s); + self + } + + /// Use the icon as a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc). **macOS only**. + pub fn icon_as_template(mut self, is_template: bool) -> Self { + self.inner = self.inner.with_icon_as_template(is_template); + self + } + + /// Whether to show the tray menu on left click or not, default is `true`. **macOS only**. + pub fn menu_on_left_click(mut self, enable: bool) -> Self { + self.inner = self.inner.with_menu_on_left_click(enable); + self + } + + /// Set a handler for menu events. + /// + /// Note that this handler is called for any menu event, + /// whether it is coming from this window, another window or from the tray icon menu. + pub fn on_menu_event, MenuEvent) + Sync + Send + 'static>( + mut self, + f: F, + ) -> Self { + self.on_menu_event.replace(Box::new(f)); + self + } + + /// Set a handler for this tray icon events. + pub fn on_tray_event, TrayIconEvent) + Sync + Send + 'static>( + mut self, + f: F, + ) -> Self { + self.on_tray_event.replace(Box::new(f)); + self + } + + /// Access the unique id that will be assigned to the tray icon + /// this builder will create. + pub fn id(&self) -> &TrayIconId { + self.inner.id() + } + + /// Builds and adds a new [`TrayIcon`] to the system tray. + pub fn build>(self, manager: &M) -> crate::Result> { + let id = self.id().clone(); + let inner = self.inner.build()?; + let icon = TrayIcon { + id, + inner, + app_handle: manager.app_handle().clone(), + }; + + icon.register(&icon.app_handle, self.on_menu_event, self.on_tray_event); + + Ok(icon) + } +} + +/// Tray icon struct and associated methods. +/// +/// This type is reference-counted and the icon is removed when the last instance is dropped. +/// +/// See [TrayIconBuilder] to construct this type. +pub struct TrayIcon { + id: TrayIconId, + inner: tray_icon::TrayIcon, + app_handle: AppHandle, +} + +impl Clone for TrayIcon { + fn clone(&self) -> Self { + Self { + id: self.id.clone(), + inner: self.inner.clone(), + app_handle: self.app_handle.clone(), + } + } +} + +/// # Safety +/// +/// We make sure it always runs on the main thread. +unsafe impl Sync for TrayIcon {} +unsafe impl Send for TrayIcon {} + +impl TrayIcon { + fn register( + &self, + app_handle: &AppHandle, + on_menu_event: Option>>, + on_tray_event: Option>>, + ) { + if let Some(handler) = on_menu_event { + app_handle + .manager + .inner + .menu_event_listeners + .lock() + .unwrap() + .push(handler); + } + + if let Some(handler) = on_tray_event { + app_handle + .manager + .inner + .tray_event_listeners + .lock() + .unwrap() + .insert(self.id.clone(), handler); + } + + app_handle + .manager + .inner + .tray_icons + .lock() + .unwrap() + .push(self.clone()); + } + + /// The application handle associated with this type. + pub fn app_handle(&self) -> &AppHandle { + &self.app_handle + } + + /// Register a handler for menu events. + /// + /// Note that this handler is called for any menu event, + /// whether it is coming from this window, another window or from the tray icon menu. + pub fn on_menu_event, MenuEvent) + Sync + Send + 'static>(&self, f: F) { + self + .app_handle + .manager + .inner + .menu_event_listeners + .lock() + .unwrap() + .push(Box::new(f)); + } + + /// Register a handler for this tray icon events. + pub fn on_tray_event, TrayIconEvent) + Sync + Send + 'static>(&self, f: F) { + self + .app_handle + .manager + .inner + .tray_event_listeners + .lock() + .unwrap() + .insert(self.id.clone(), Box::new(f)); + } + + /// Returns the id associated with this tray icon. + pub fn id(&self) -> &TrayIconId { + &self.id + } + + /// Set new tray icon. If `None` is provided, it will remove the icon. + pub fn set_icon(&self, icon: Option) -> crate::Result<()> { + let icon = icon.and_then(|i| i.try_into().ok()); + run_main_thread!(self, |self_: Self| self_.inner.set_icon(icon))?.map_err(Into::into) + } + + /// Set new tray menu. + /// + /// ## Platform-specific: + /// + /// - **Linux**: once a menu is set it cannot be removed so `None` has no effect + pub fn set_menu(&self, menu: Option) -> crate::Result<()> { + run_main_thread!(self, |self_: Self| self_ + .inner + .set_menu(menu.map(|m| m.inner_owned()))) + } + + /// Sets the tooltip for this tray icon. + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported + pub fn set_tooltip>(&self, tooltip: Option) -> crate::Result<()> { + let s = tooltip.map(|s| s.as_ref().to_string()); + run_main_thread!(self, |self_: Self| self_.inner.set_tooltip(s))?.map_err(Into::into) + } + + /// Sets the tooltip for this tray icon. + /// + /// ## Platform-specific: + /// + /// - **Linux:** The title will not be shown unless there is an icon + /// as well. The title is useful for numerical and other frequently + /// updated information. In general, it shouldn't be shown unless a + /// user requests it as it can take up a significant amount of space + /// on the user's panel. This may not be shown in all visualizations. + /// - **Windows:** Unsupported + pub fn set_title>(&self, title: Option) -> crate::Result<()> { + let s = title.map(|s| s.as_ref().to_string()); + run_main_thread!(self, |self_: Self| self_.inner.set_title(s)) + } + + /// Show or hide this tray icon + pub fn set_visible(&self, visible: bool) -> crate::Result<()> { + run_main_thread!(self, |self_: Self| self_.inner.set_visible(visible))?.map_err(Into::into) + } + + /// Sets the tray icon temp dir path. **Linux only**. + /// + /// On Linux, we need to write the icon to the disk and usually it will + /// be `$XDG_RUNTIME_DIR/tray-icon` or `$TEMP/tray-icon`. + pub fn set_temp_dir_path>(&self, path: Option

) -> crate::Result<()> { + #[allow(unused)] + let p = path.map(|p| p.as_ref().to_path_buf()); + #[cfg(target_os = "linux")] + run_main_thread!(self, |self_: Self| self_.inner.set_temp_dir_path(p))?; + Ok(()) + } + + /// Set the current icon as a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc). **macOS only**. + pub fn set_icon_as_template(&self, #[allow(unused)] is_template: bool) -> crate::Result<()> { + #[cfg(target_os = "macos")] + run_main_thread!(self, |self_: Self| self_ + .inner + .set_icon_as_template(is_template))?; + Ok(()) + } + + /// Disable or enable showing the tray menu on left click. **macOS only**. + pub fn set_show_menu_on_left_click(&self, #[allow(unused)] enable: bool) -> crate::Result<()> { + #[cfg(target_os = "macos")] + run_main_thread!(self, |self_: Self| self_ + .inner + .set_show_menu_on_left_click(enable))?; + Ok(()) + } +} + +impl TryFrom for tray_icon::Icon { + type Error = crate::Error; + + fn try_from(value: Icon) -> Result { + let value: crate::runtime::Icon = value.try_into()?; + tray_icon::Icon::from_rgba(value.rgba, value.width, value.height).map_err(Into::into) + } +} diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index 03678ab3690d..55a91154d4b6 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -4,10 +4,7 @@ //! The Tauri window types and functions. -pub(crate) mod menu; - use http::HeaderMap; -pub use menu::{MenuEvent, MenuHandle}; pub use tauri_utils::{config::Color, WindowEffect as Effect, WindowEffectState as EffectState}; use url::Url; @@ -38,8 +35,8 @@ use crate::{ }; #[cfg(desktop)] use crate::{ + menu::{ContextMenu, Menu, MenuId}, runtime::{ - menu::Menu, window::dpi::{Position, Size}, UserAttentionType, }, @@ -125,9 +122,13 @@ pub struct WindowBuilder<'a, R: Runtime> { app_handle: AppHandle, label: String, pub(crate) window_builder: >::WindowBuilder, + #[cfg(desktop)] + pub(crate) menu: Option>, pub(crate) webview_attributes: WebviewAttributes, web_resource_request_handler: Option>, navigation_handler: Option>, + #[cfg(desktop)] + on_menu_event: Option>>, } impl<'a, R: Runtime> fmt::Debug for WindowBuilder<'a, R> { @@ -168,7 +169,7 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { /// ``` /// tauri::Builder::default() /// .setup(|app| { - /// let handle = app.handle(); + /// let handle = app.handle().clone(); /// std::thread::spawn(move || { /// let window = tauri::WindowBuilder::new(&handle, "label", tauri::WindowUrl::App("index.html".into())) /// .build() @@ -192,16 +193,20 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { /// [the Webview2 issue]: https://github.com/tauri-apps/wry/issues/583 pub fn new, L: Into>(manager: &'a M, label: L, url: WindowUrl) -> Self { let runtime = manager.runtime(); - let app_handle = manager.app_handle(); + let app_handle = manager.app_handle().clone(); Self { manager: manager.manager().clone(), runtime, app_handle, label: label.into(), window_builder: >::WindowBuilder::new(), + #[cfg(desktop)] + menu: None, webview_attributes: WebviewAttributes::new(url), web_resource_request_handler: None, navigation_handler: None, + #[cfg(desktop)] + on_menu_event: None, } } @@ -232,14 +237,18 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { let builder = Self { manager: manager.manager().clone(), runtime: manager.runtime(), - app_handle: manager.app_handle(), + app_handle: manager.app_handle().clone(), label: config.label.clone(), webview_attributes: WebviewAttributes::from(&config), window_builder: >::WindowBuilder::with_config( config, ), web_resource_request_handler: None, + #[cfg(desktop)] + menu: None, navigation_handler: None, + #[cfg(desktop)] + on_menu_event: None, }; builder @@ -317,6 +326,48 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { self } + /// Registers a global menu event listener. + /// + /// Note that this handler is called for any menu event, + /// whether it is coming from this window, another window or from the tray icon menu. + /// + /// Also note that this handler will not be called if + /// the window used to register it was closed. + /// + /// # Examples + /// ``` + /// use tauri::menu::{Menu, Submenu, MenuItem}; + /// tauri::Builder::default() + /// .setup(|app| { + /// let handle = app.handle(); + /// let save_menu_item = MenuItem::new(handle, "Save", true, None); + /// let menu = Menu::with_items(handle, &[ + /// &Submenu::with_items(handle, "File", true, &[ + /// &save_menu_item, + /// ])?, + /// ])?; + /// let window = tauri::WindowBuilder::new(app, "editor", tauri::WindowUrl::default()) + /// .menu(menu) + /// .on_menu_event(move |window, event| { + /// if event.id == save_menu_item.id() { + /// // save menu item + /// } + /// }) + /// .build() + /// .unwrap(); + /// + /// Ok(()) + /// }); + /// ``` + #[cfg(desktop)] + pub fn on_menu_event, crate::menu::MenuEvent) + Send + Sync + 'static>( + mut self, + f: F, + ) -> Self { + self.on_menu_event.replace(Box::new(f)); + self + } + /// Creates a new webview window. pub fn build(mut self) -> crate::Result> { let mut pending = PendingWindow::new( @@ -331,13 +382,43 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { let pending = self .manager .prepare_window(self.app_handle.clone(), pending, &labels)?; + + #[cfg(desktop)] + let window_menu = { + let is_app_wide = self.menu.is_none(); + self + .menu + .or_else(|| self.app_handle.menu()) + .map(|menu| WindowMenu { is_app_wide, menu }) + }; + + #[cfg(desktop)] + let handler = self + .manager + .prepare_window_menu_creation_handler(window_menu.as_ref()); + #[cfg(not(desktop))] + #[allow(clippy::type_complexity)] + let handler: Option) + Send>> = None; + let window_effects = pending.webview_attributes.window_effects.clone(); let window = match &mut self.runtime { - RuntimeOrDispatch::Runtime(runtime) => runtime.create_window(pending), - RuntimeOrDispatch::RuntimeHandle(handle) => handle.create_window(pending), - RuntimeOrDispatch::Dispatch(dispatcher) => dispatcher.create_window(pending), + RuntimeOrDispatch::Runtime(runtime) => runtime.create_window(pending, handler), + RuntimeOrDispatch::RuntimeHandle(handle) => handle.create_window(pending, handler), + RuntimeOrDispatch::Dispatch(dispatcher) => dispatcher.create_window(pending, handler), + } + .map(|window| { + self.manager.attach_window( + self.app_handle.clone(), + window, + #[cfg(desktop)] + window_menu, + ) + })?; + + #[cfg(desktop)] + if let Some(handler) = self.on_menu_event { + window.on_menu_event(handler); } - .map(|window| self.manager.attach_window(self.app_handle.clone(), window))?; if let Some(effects) = window_effects { crate::vibrancy::set_window_effects(&window, Some(effects))?; @@ -365,8 +446,8 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { impl<'a, R: Runtime> WindowBuilder<'a, R> { /// Sets the menu for the window. #[must_use] - pub fn menu(mut self, menu: Menu) -> Self { - self.window_builder = self.window_builder.menu(menu); + pub fn menu(mut self, menu: Menu) -> Self { + self.menu.replace(menu); self } @@ -659,7 +740,7 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { /// /// ## Platform-specific: /// - /// - **Windows**: If using decorations or shadows, you may want to try this workaround https://github.com/tauri-apps/tao/issues/72#issuecomment-975607891 + /// - **Windows**: If using decorations or shadows, you may want to try this workaround /// - **Linux**: Unsupported pub fn effects(mut self, effects: WindowEffectsConfig) -> Self { self.webview_attributes = self.webview_attributes.window_effects(effects); @@ -793,13 +874,20 @@ pub struct InvokeRequest { pub headers: HeaderMap, } +/// A wrapper struct to hold the window menu state +/// and whether it is global per-app or specific to this window. +#[cfg(desktop)] +pub(crate) struct WindowMenu { + pub(crate) is_app_wide: bool, + pub(crate) menu: Menu, +} + // TODO: expand these docs since this is a pretty important type /// A webview window managed by Tauri. /// /// This type also implements [`Manager`] which allows you to manage other windows attached to /// the same application. #[default_runtime(crate::Wry, wry)] -#[derive(Debug)] pub struct Window { /// The webview window created by the runtime. pub(crate) window: DetachedWindow, @@ -807,6 +895,20 @@ pub struct Window { manager: WindowManager, pub(crate) app_handle: AppHandle, js_event_listeners: Arc>>>, + // The menu set for this window + #[cfg(desktop)] + pub(crate) menu: Arc>>>, +} + +impl std::fmt::Debug for Window { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Window") + .field("window", &self.window) + .field("manager", &self.manager) + .field("app_handle", &self.app_handle) + .field("js_event_listeners", &self.js_event_listeners) + .finish() + } } unsafe impl raw_window_handle::HasRawWindowHandle for Window { @@ -822,6 +924,8 @@ impl Clone for Window { manager: self.manager.clone(), app_handle: self.app_handle.clone(), js_event_listeners: self.js_event_listeners.clone(), + #[cfg(desktop)] + menu: self.menu.clone(), } } } @@ -868,8 +972,8 @@ impl ManagerBase for Window { RuntimeOrDispatch::Dispatch(self.dispatcher()) } - fn managed_app_handle(&self) -> AppHandle { - self.app_handle.clone() + fn managed_app_handle(&self) -> &AppHandle { + &self.app_handle } } @@ -968,12 +1072,15 @@ impl Window { manager: WindowManager, window: DetachedWindow, app_handle: AppHandle, + #[cfg(desktop)] menu: Option>, ) -> Self { Self { window, manager, app_handle, js_event_listeners: Default::default(), + #[cfg(desktop)] + menu: Arc::new(Mutex::new(menu)), } } @@ -1015,20 +1122,6 @@ impl Window { .on_window_event(move |event| f(&event.clone().into())); } - /// Registers a menu event listener. - pub fn on_menu_event(&self, f: F) -> uuid::Uuid { - let menu_ids = self.window.menu_ids.clone(); - self.window.dispatcher.on_menu_event(move |event| { - let id = menu_ids - .lock() - .unwrap() - .get(&event.menu_item_id) - .unwrap() - .clone(); - f(MenuEvent { menu_item_id: id }) - }) - } - /// Executes a closure, providing it with the webview handle that is specific to the current platform. /// /// The closure is executed on the main thread. @@ -1094,16 +1187,270 @@ impl Window { } } -/// Window getters. +/// Menu APIs +#[cfg(desktop)] impl Window { - /// Gets a handle to the window menu. - pub fn menu_handle(&self) -> MenuHandle { - MenuHandle { - ids: self.window.menu_ids.clone(), - dispatcher: self.dispatcher(), + /// Registers a global menu event listener. + /// + /// Note that this handler is called for any menu event, + /// whether it is coming from this window, another window or from the tray icon menu. + /// + /// Also note that this handler will not be called if + /// the window used to register it was closed. + /// + /// # Examples + /// ``` + /// use tauri::menu::{Menu, Submenu, MenuItem}; + /// tauri::Builder::default() + /// .setup(|app| { + /// let handle = app.handle(); + /// let save_menu_item = MenuItem::new(handle, "Save", true, None); + /// let menu = Menu::with_items(handle, &[ + /// &Submenu::with_items(handle, "File", true, &[ + /// &save_menu_item, + /// ])?, + /// ])?; + /// let window = tauri::WindowBuilder::new(app, "editor", tauri::WindowUrl::default()) + /// .menu(menu) + /// .build() + /// .unwrap(); + /// + /// window.on_menu_event(move |window, event| { + /// if event.id == save_menu_item.id() { + /// // save menu item + /// } + /// }); + /// + /// Ok(()) + /// }); + /// ``` + pub fn on_menu_event, crate::menu::MenuEvent) + Send + Sync + 'static>( + &self, + f: F, + ) { + self + .manager + .inner + .window_menu_event_listeners + .lock() + .unwrap() + .insert(self.label().to_string(), Box::new(f)); + } + + pub(crate) fn menu_lock(&self) -> std::sync::MutexGuard<'_, Option>> { + self.menu.lock().expect("poisoned window") + } + + #[cfg_attr(target_os = "macos", allow(dead_code))] + pub(crate) fn has_app_wide_menu(&self) -> bool { + self + .menu_lock() + .as_ref() + .map(|m| m.is_app_wide) + .unwrap_or(false) + } + + #[cfg_attr(target_os = "macos", allow(dead_code))] + pub(crate) fn is_menu_in_use>(&self, id: &I) -> bool { + self + .menu_lock() + .as_ref() + .map(|m| id.eq(m.menu.id())) + .unwrap_or(false) + } + + /// Returns this window menu . + pub fn menu(&self) -> Option> { + self.menu_lock().as_ref().map(|m| m.menu.clone()) + } + + /// Sets the window menu and returns the previous one. + /// + /// ## Platform-specific: + /// + /// - **macOS:** Unsupported. The menu on macOS is app-wide and not specific to one + /// window, if you need to set it, use [`AppHandle::set_menu`] instead. + #[cfg_attr(target_os = "macos", allow(unused_variables))] + pub fn set_menu(&self, menu: Menu) -> crate::Result>> { + let prev_menu = self.remove_menu()?; + + self.manager.insert_menu_into_stash(&menu); + + let window = self.clone(); + let menu_ = menu.clone(); + self.run_on_main_thread(move || { + #[cfg(windows)] + if let Ok(hwnd) = window.hwnd() { + let _ = menu_.inner().init_for_hwnd(hwnd.0); + } + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + if let (Ok(gtk_window), Ok(gtk_box)) = (window.gtk_window(), window.default_vbox()) { + let _ = menu_ + .inner() + .init_for_gtk_window(>k_window, Some(>k_box)); + } + })?; + + self.menu_lock().replace(WindowMenu { + is_app_wide: false, + menu, + }); + + Ok(prev_menu) + } + + /// Removes the window menu and returns it. + /// + /// ## Platform-specific: + /// + /// - **macOS:** Unsupported. The menu on macOS is app-wide and not specific to one + /// window, if you need to remove it, use [`AppHandle::remove_menu`] instead. + pub fn remove_menu(&self) -> crate::Result>> { + let current_menu = self.menu_lock().as_ref().map(|m| m.menu.clone()); + + // remove from the window + #[cfg_attr(target_os = "macos", allow(unused_variables))] + if let Some(menu) = current_menu { + let window = self.clone(); + self.run_on_main_thread(move || { + #[cfg(windows)] + if let Ok(hwnd) = window.hwnd() { + let _ = menu.inner().remove_for_hwnd(hwnd.0); + } + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + if let Ok(gtk_window) = window.gtk_window() { + let _ = menu.inner().remove_for_gtk_window(>k_window); + } + })?; } + + let prev_menu = self.menu_lock().take().map(|m| m.menu); + + self + .manager + .remove_menu_from_stash_by_id(prev_menu.as_ref().map(|m| m.id())); + + Ok(prev_menu) + } + + /// Hides the window menu. + pub fn hide_menu(&self) -> crate::Result<()> { + // remove from the window + #[cfg_attr(target_os = "macos", allow(unused_variables))] + if let Some(window_menu) = &*self.menu_lock() { + let window = self.clone(); + let menu_ = window_menu.menu.clone(); + self.run_on_main_thread(move || { + #[cfg(windows)] + if let Ok(hwnd) = window.hwnd() { + let _ = menu_.inner().hide_for_hwnd(hwnd.0); + } + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + if let Ok(gtk_window) = window.gtk_window() { + let _ = menu_.inner().hide_for_gtk_window(>k_window); + } + })?; + } + + Ok(()) } + /// Shows the window menu. + pub fn show_menu(&self) -> crate::Result<()> { + // remove from the window + #[cfg_attr(target_os = "macos", allow(unused_variables))] + if let Some(window_menu) = &*self.menu_lock() { + let window = self.clone(); + let menu_ = window_menu.menu.clone(); + self.run_on_main_thread(move || { + #[cfg(windows)] + if let Ok(hwnd) = window.hwnd() { + let _ = menu_.inner().show_for_hwnd(hwnd.0); + } + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + if let Ok(gtk_window) = window.gtk_window() { + let _ = menu_.inner().show_for_gtk_window(>k_window); + } + })?; + } + + Ok(()) + } + + /// Shows the window menu. + pub fn is_menu_visible(&self) -> crate::Result { + // remove from the window + #[cfg_attr(target_os = "macos", allow(unused_variables))] + if let Some(window_menu) = &*self.menu_lock() { + let (tx, rx) = std::sync::mpsc::channel(); + let window = self.clone(); + let menu_ = window_menu.menu.clone(); + self.run_on_main_thread(move || { + #[cfg(windows)] + if let Ok(hwnd) = window.hwnd() { + let _ = tx.send(menu_.inner().is_visible_on_hwnd(hwnd.0)); + } + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + if let Ok(gtk_window) = window.gtk_window() { + let _ = tx.send(menu_.inner().is_visible_on_gtk_window(>k_window)); + } + })?; + + return Ok(rx.recv().unwrap_or(false)); + } + + Ok(false) + } + + /// Shows the specified menu as a context menu at the cursor position. + pub fn popup_menu(&self, menu: &M) -> crate::Result<()> { + menu.popup(self.clone()) + } + + /// Shows the specified menu as a context menu at the specified position. + /// + /// The position is relative to the window's top-left corner. + pub fn popup_menu_at>( + &self, + menu: &M, + position: P, + ) -> crate::Result<()> { + menu.popup_at(self.clone(), position) + } +} + +/// Window getters. +impl Window { /// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa. pub fn scale_factor(&self) -> crate::Result { self.window.dispatcher.scale_factor().map_err(Into::into) @@ -1251,6 +1598,23 @@ impl Window { }) } + /// Returns the pointer to the content view of this window. + #[cfg(target_os = "macos")] + pub fn ns_view(&self) -> crate::Result<*mut std::ffi::c_void> { + self + .window + .dispatcher + .raw_window_handle() + .map_err(Into::into) + .and_then(|handle| { + if let raw_window_handle::RawWindowHandle::AppKit(h) = handle { + Ok(h.ns_view) + } else { + Err(crate::Error::InvalidWindowHandle) + } + }) + } + /// Returns the native handle that is used by this window. #[cfg(windows)] pub fn hwnd(&self) -> crate::Result { @@ -1270,7 +1634,7 @@ impl Window { /// Returns the `ApplicationWindow` from gtk crate that is used by this window. /// - /// Note that this can only be used on the main thread. + /// Note that this type can only be used on the main thread. #[cfg(any( target_os = "linux", target_os = "dragonfly", @@ -1282,6 +1646,20 @@ impl Window { self.window.dispatcher.gtk_window().map_err(Into::into) } + /// Returns the vertical [`gtk::Box`] that is added by default as the sole child of this window. + /// + /// Note that this type can only be used on the main thread. + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + pub fn default_vbox(&self) -> crate::Result { + self.window.dispatcher.default_vbox().map_err(Into::into) + } + /// Returns the current window theme. /// /// ## Platform-specific @@ -1486,7 +1864,7 @@ impl Window { /// /// ## Platform-specific: /// - /// - **Windows**: If using decorations or shadows, you may want to try this workaround https://github.com/tauri-apps/tao/issues/72#issuecomment-975607891 + /// - **Windows**: If using decorations or shadows, you may want to try this workaround /// - **Linux**: Unsupported pub fn set_effects>>(&self, effects: E) -> crate::Result<()> { let effects = effects.into(); diff --git a/core/tauri/src/window/menu.rs b/core/tauri/src/window/menu.rs deleted file mode 100644 index 6888822843a1..000000000000 --- a/core/tauri/src/window/menu.rs +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use crate::{ - runtime::{ - menu::{MenuHash, MenuId, MenuIdRef, MenuUpdate}, - Dispatch, - }, - Runtime, -}; - -use tauri_macros::default_runtime; - -use std::{ - collections::HashMap, - sync::{Arc, Mutex}, -}; - -/// The window menu event. -#[derive(Debug, Clone)] -pub struct MenuEvent { - pub(crate) menu_item_id: MenuId, -} - -impl MenuEvent { - /// The menu item id. - pub fn menu_item_id(&self) -> MenuIdRef<'_> { - &self.menu_item_id - } -} - -/// A handle to a system tray. Allows updating the context menu items. -#[default_runtime(crate::Wry, wry)] -#[derive(Debug)] -pub struct MenuHandle { - pub(crate) ids: Arc>>, - pub(crate) dispatcher: R::Dispatcher, -} - -impl Clone for MenuHandle { - fn clone(&self) -> Self { - Self { - ids: self.ids.clone(), - dispatcher: self.dispatcher.clone(), - } - } -} - -/// A handle to a system tray menu item. -#[default_runtime(crate::Wry, wry)] -#[derive(Debug)] -pub struct MenuItemHandle { - id: u16, - dispatcher: R::Dispatcher, -} - -impl Clone for MenuItemHandle { - fn clone(&self) -> Self { - Self { - id: self.id, - dispatcher: self.dispatcher.clone(), - } - } -} - -impl MenuHandle { - /// Gets a handle to the menu item that has the specified `id`. - pub fn get_item(&self, id: MenuIdRef<'_>) -> MenuItemHandle { - let ids = self.ids.lock().unwrap(); - let iter = ids.iter(); - for (raw, item_id) in iter { - if item_id == id { - return MenuItemHandle { - id: *raw, - dispatcher: self.dispatcher.clone(), - }; - } - } - panic!("item id not found") - } - - /// Attempts to get a handle to the menu item that has the specified `id`, return an error if `id` is not found. - pub fn try_get_item(&self, id: MenuIdRef<'_>) -> Option> { - self - .ids - .lock() - .unwrap() - .iter() - .find(|i| i.1 == id) - .map(|i| MenuItemHandle { - id: *i.0, - dispatcher: self.dispatcher.clone(), - }) - } - - /// Shows the menu. - pub fn show(&self) -> crate::Result<()> { - self.dispatcher.show_menu().map_err(Into::into) - } - - /// Hides the menu. - pub fn hide(&self) -> crate::Result<()> { - self.dispatcher.hide_menu().map_err(Into::into) - } - - /// Whether the menu is visible or not. - pub fn is_visible(&self) -> crate::Result { - self.dispatcher.is_menu_visible().map_err(Into::into) - } - - /// Toggles the menu visibility. - pub fn toggle(&self) -> crate::Result<()> { - if self.is_visible()? { - self.hide() - } else { - self.show() - } - } -} - -impl MenuItemHandle { - /// Modifies the enabled state of the menu item. - pub fn set_enabled(&self, enabled: bool) -> crate::Result<()> { - self - .dispatcher - .update_menu_item(self.id, MenuUpdate::SetEnabled(enabled)) - .map_err(Into::into) - } - - /// Modifies the title (label) of the menu item. - pub fn set_title>(&self, title: S) -> crate::Result<()> { - self - .dispatcher - .update_menu_item(self.id, MenuUpdate::SetTitle(title.into())) - .map_err(Into::into) - } - - /// Modifies the selected state of the menu item. - pub fn set_selected(&self, selected: bool) -> crate::Result<()> { - self - .dispatcher - .update_menu_item(self.id, MenuUpdate::SetSelected(selected)) - .map_err(Into::into) - } - - #[cfg(target_os = "macos")] - #[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))] - pub fn set_native_image(&self, image: crate::NativeImage) -> crate::Result<()> { - self - .dispatcher - .update_menu_item(self.id, MenuUpdate::SetNativeImage(image)) - .map_err(Into::into) - } -} diff --git a/examples/api/dist/assets/index.css b/examples/api/dist/assets/index.css index 1148ece6d18e..b165b4364e62 100644 --- a/examples/api/dist/assets/index.css +++ b/examples/api/dist/assets/index.css @@ -1 +1 @@ -*,::before,::after{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x:var(--un-empty,/*!*/ /*!*/);--un-pan-y:var(--un-empty,/*!*/ /*!*/);--un-pinch-zoom:var(--un-empty,/*!*/ /*!*/);--un-scroll-snap-strictness:proximity;--un-ordinal:var(--un-empty,/*!*/ /*!*/);--un-slashed-zero:var(--un-empty,/*!*/ /*!*/);--un-numeric-figure:var(--un-empty,/*!*/ /*!*/);--un-numeric-spacing:var(--un-empty,/*!*/ /*!*/);--un-numeric-fraction:var(--un-empty,/*!*/ /*!*/);--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 #0000;--un-ring-shadow:0 0 #0000;--un-shadow-inset:var(--un-empty,/*!*/ /*!*/);--un-shadow:0 0 #0000;--un-ring-inset:var(--un-empty,/*!*/ /*!*/);--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgba(147,197,253,0.5);--un-blur:var(--un-empty,/*!*/ /*!*/);--un-brightness:var(--un-empty,/*!*/ /*!*/);--un-contrast:var(--un-empty,/*!*/ /*!*/);--un-drop-shadow:var(--un-empty,/*!*/ /*!*/);--un-grayscale:var(--un-empty,/*!*/ /*!*/);--un-hue-rotate:var(--un-empty,/*!*/ /*!*/);--un-invert:var(--un-empty,/*!*/ /*!*/);--un-saturate:var(--un-empty,/*!*/ /*!*/);--un-sepia:var(--un-empty,/*!*/ /*!*/);--un-backdrop-blur:var(--un-empty,/*!*/ /*!*/);--un-backdrop-brightness:var(--un-empty,/*!*/ /*!*/);--un-backdrop-contrast:var(--un-empty,/*!*/ /*!*/);--un-backdrop-grayscale:var(--un-empty,/*!*/ /*!*/);--un-backdrop-hue-rotate:var(--un-empty,/*!*/ /*!*/);--un-backdrop-invert:var(--un-empty,/*!*/ /*!*/);--un-backdrop-opacity:var(--un-empty,/*!*/ /*!*/);--un-backdrop-saturate:var(--un-empty,/*!*/ /*!*/);--un-backdrop-sepia:var(--un-empty,/*!*/ /*!*/);}@font-face { font-family: 'Fira Code'; font-style: normal; font-weight: 400; font-display: swap; src: url(https://fonts.gstatic.com/s/firacode/v21/uU9eCBsR6Z2vfE9aq3bL0fxyUs4tcw4W_D1sFVc.ttf) format('truetype');}@font-face { font-family: 'Fira Mono'; font-style: normal; font-weight: 400; font-display: swap; src: url(https://fonts.gstatic.com/s/firamono/v14/N0bX2SlFPv1weGeLZDtQIQ.ttf) format('truetype');}@font-face { font-family: 'Fira Mono'; font-style: normal; font-weight: 700; font-display: swap; src: url(https://fonts.gstatic.com/s/firamono/v14/N0bS2SlFPv1weGeLZDtondv3mQ.ttf) format('truetype');}@font-face { font-family: 'Rubik'; font-style: normal; font-weight: 400; font-display: swap; src: url(https://fonts.gstatic.com/s/rubik/v26/iJWZBXyIfDnIV5PNhY1KTN7Z-Yh-B4i1UA.ttf) format('truetype');}.i-codicon-clear-all{--un-icon:url("data:image/svg+xml;utf8,%3Csvg preserveAspectRatio='xMidYMid meet' viewBox='0 0 16 16' width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='m10 12.6l.7.7l1.6-1.6l1.6 1.6l.8-.7L13 11l1.7-1.6l-.8-.8l-1.6 1.7l-1.6-1.7l-.7.8l1.6 1.6l-1.6 1.6zM1 4h14V3H1v1zm0 3h14V6H1v1zm8 2.5V9H1v1h8v-.5zM9 13v-1H1v1h8z'/%3E%3C/svg%3E");mask:var(--un-icon) no-repeat;mask-size:100% 100%;-webkit-mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;background-color:currentColor;width:1em;height:1em;}.i-codicon-close{--un-icon:url("data:image/svg+xml;utf8,%3Csvg preserveAspectRatio='xMidYMid meet' viewBox='0 0 16 16' width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' fill-rule='evenodd' d='m8 8.707l3.646 3.647l.708-.707L8.707 8l3.647-3.646l-.707-.708L8 7.293L4.354 3.646l-.707.708L7.293 8l-3.646 3.646l.707.708L8 8.707z' clip-rule='evenodd'/%3E%3C/svg%3E");mask:var(--un-icon) no-repeat;mask-size:100% 100%;-webkit-mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;background-color:currentColor;width:1em;height:1em;}.i-codicon-link-external{--un-icon:url("data:image/svg+xml;utf8,%3Csvg preserveAspectRatio='xMidYMid meet' viewBox='0 0 16 16' width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' %3E%3Cg fill='currentColor'%3E%3Cpath d='M1.5 1H6v1H2v12h12v-4h1v4.5l-.5.5h-13l-.5-.5v-13l.5-.5z'/%3E%3Cpath d='M15 1.5V8h-1V2.707L7.243 9.465l-.707-.708L13.293 2H8V1h6.5l.5.5z'/%3E%3C/g%3E%3C/svg%3E");mask:var(--un-icon) no-repeat;mask-size:100% 100%;-webkit-mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;background-color:currentColor;width:1em;height:1em;}.i-codicon-menu{--un-icon:url("data:image/svg+xml;utf8,%3Csvg preserveAspectRatio='xMidYMid meet' viewBox='0 0 16 16' width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M16 5H0V4h16v1zm0 8H0v-1h16v1zm0-4.008H0V8h16v.992z'/%3E%3C/svg%3E");mask:var(--un-icon) no-repeat;mask-size:100% 100%;-webkit-mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;background-color:currentColor;width:1em;height:1em;}.i-codicon-radio-tower{--un-icon:url("data:image/svg+xml;utf8,%3Csvg preserveAspectRatio='xMidYMid meet' viewBox='0 0 16 16' width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' fill-rule='evenodd' d='M2.998 5.58a5.55 5.55 0 0 1 1.62-3.88l-.71-.7a6.45 6.45 0 0 0 0 9.16l.71-.7a5.55 5.55 0 0 1-1.62-3.88zm1.06 0a4.42 4.42 0 0 0 1.32 3.17l.71-.71a3.27 3.27 0 0 1-.76-1.12a3.45 3.45 0 0 1 0-2.67a3.22 3.22 0 0 1 .76-1.13l-.71-.71a4.46 4.46 0 0 0-1.32 3.17zm7.65 3.21l-.71-.71c.33-.32.59-.704.76-1.13a3.449 3.449 0 0 0 0-2.67a3.22 3.22 0 0 0-.76-1.13l.71-.7a4.468 4.468 0 0 1 0 6.34zM13.068 1l-.71.71a5.43 5.43 0 0 1 0 7.74l.71.71a6.45 6.45 0 0 0 0-9.16zM9.993 5.43a1.5 1.5 0 0 1-.245.98a2 2 0 0 1-.27.23l3.44 7.73l-.92.4l-.77-1.73h-5.54l-.77 1.73l-.92-.4l3.44-7.73a1.52 1.52 0 0 1-.33-1.63a1.55 1.55 0 0 1 .56-.68a1.5 1.5 0 0 1 2.325 1.1zm-1.595-.34a.52.52 0 0 0-.25.14a.52.52 0 0 0-.11.22a.48.48 0 0 0 0 .29c.04.09.102.17.18.23a.54.54 0 0 0 .28.08a.51.51 0 0 0 .5-.5a.54.54 0 0 0-.08-.28a.58.58 0 0 0-.23-.18a.48.48 0 0 0-.29 0zm.23 2.05h-.27l-.87 1.94h2l-.86-1.94zm2.2 4.94l-.89-2h-2.88l-.89 2h4.66z' clip-rule='evenodd'/%3E%3C/svg%3E");mask:var(--un-icon) no-repeat;mask-size:100% 100%;-webkit-mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;background-color:currentColor;width:1em;height:1em;}.i-ph-broadcast{--un-icon:url("data:image/svg+xml;utf8,%3Csvg preserveAspectRatio='xMidYMid meet' viewBox='0 0 256 256' width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M128 88a40 40 0 1 0 40 40a40 40 0 0 0-40-40Zm0 64a24 24 0 1 1 24-24a24.1 24.1 0 0 1-24 24Zm-59-48.9a64.5 64.5 0 0 0 0 49.8a65.4 65.4 0 0 0 13.7 20.4a7.9 7.9 0 0 1 0 11.3a8 8 0 0 1-5.6 2.3a8.3 8.3 0 0 1-5.7-2.3a80 80 0 0 1-17.1-25.5a79.9 79.9 0 0 1 0-62.2a80 80 0 0 1 17.1-25.5a8 8 0 0 1 11.3 0a7.9 7.9 0 0 1 0 11.3A65.4 65.4 0 0 0 69 103.1Zm132.7 56a80 80 0 0 1-17.1 25.5a8.3 8.3 0 0 1-5.7 2.3a8 8 0 0 1-5.6-2.3a7.9 7.9 0 0 1 0-11.3a65.4 65.4 0 0 0 13.7-20.4a64.5 64.5 0 0 0 0-49.8a65.4 65.4 0 0 0-13.7-20.4a7.9 7.9 0 0 1 0-11.3a8 8 0 0 1 11.3 0a80 80 0 0 1 17.1 25.5a79.9 79.9 0 0 1 0 62.2ZM54.5 201.5a8.1 8.1 0 0 1 0 11.4a8.3 8.3 0 0 1-5.7 2.3a8.5 8.5 0 0 1-5.7-2.3a121.8 121.8 0 0 1-25.7-38.2a120.7 120.7 0 0 1 0-93.4a121.8 121.8 0 0 1 25.7-38.2a8.1 8.1 0 0 1 11.4 11.4A103.5 103.5 0 0 0 24 128a103.5 103.5 0 0 0 30.5 73.5ZM248 128a120.2 120.2 0 0 1-9.4 46.7a121.8 121.8 0 0 1-25.7 38.2a8.5 8.5 0 0 1-5.7 2.3a8.3 8.3 0 0 1-5.7-2.3a8.1 8.1 0 0 1 0-11.4A103.5 103.5 0 0 0 232 128a103.5 103.5 0 0 0-30.5-73.5a8.1 8.1 0 1 1 11.4-11.4a121.8 121.8 0 0 1 25.7 38.2A120.2 120.2 0 0 1 248 128Z'/%3E%3C/svg%3E");mask:var(--un-icon) no-repeat;mask-size:100% 100%;-webkit-mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;background-color:currentColor;width:1em;height:1em;}.i-ph-hand-waving{--un-icon:url("data:image/svg+xml;utf8,%3Csvg preserveAspectRatio='xMidYMid meet' viewBox='0 0 256 256' width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='m220.2 104l-20-34.7a28.1 28.1 0 0 0-47.3-1.9l-17.3-30a28.1 28.1 0 0 0-38.3-10.3a29.4 29.4 0 0 0-9.9 9.6a27.9 27.9 0 0 0-11.5-6.2a27.2 27.2 0 0 0-21.2 2.8a27.9 27.9 0 0 0-10.3 38.2l3.4 5.8A28.5 28.5 0 0 0 36 81a28.1 28.1 0 0 0-10.2 38.2l42 72.8a88 88 0 1 0 152.4-88Zm-6.7 62.6a71.2 71.2 0 0 1-33.5 43.7A72.1 72.1 0 0 1 81.6 184l-42-72.8a12 12 0 0 1 20.8-12l22 38.1l.6.9v.2l.5.5l.2.2l.7.6h.1l.7.5h.3l.6.3h.2l.9.3h.1l.8.2h2.2l.9-.2h.3l.6-.2h.3l.9-.4a8.1 8.1 0 0 0 2.9-11l-22-38.1l-16-27.7a12 12 0 0 1-1.2-9.1a11.8 11.8 0 0 1 5.6-7.3a12 12 0 0 1 9.1-1.2a12.5 12.5 0 0 1 7.3 5.6l8 14h.1l26 45a7 7 0 0 0 1.5 1.9a8 8 0 0 0 12.3-9.9l-26-45a12 12 0 1 1 20.8-12l30 51.9l6.3 11a48.1 48.1 0 0 0-10.9 61a8 8 0 0 0 13.8-8a32 32 0 0 1 11.7-43.7l.7-.4l.5-.4h.1l.6-.6l.5-.5l.4-.5l.3-.6h.1l.2-.5v-.2a1.9 1.9 0 0 0 .2-.7h.1c0-.2.1-.4.1-.6s0-.2.1-.2v-2.1a6.4 6.4 0 0 0-.2-.7a1.9 1.9 0 0 0-.2-.7v-.2c0-.2-.1-.3-.2-.5l-.3-.7l-10-17.4a12 12 0 0 1 13.5-17.5a11.8 11.8 0 0 1 7.2 5.5l20 34.7a70.9 70.9 0 0 1 7.2 53.8Zm-125.8 78a8.2 8.2 0 0 1-6.6 3.4a8.6 8.6 0 0 1-4.6-1.4A117.9 117.9 0 0 1 41.1 208a8 8 0 1 1 13.8-8a102.6 102.6 0 0 0 30.8 33.4a8.1 8.1 0 0 1 2 11.2ZM168 31a8 8 0 0 1 8-8a60.2 60.2 0 0 1 52 30a7.9 7.9 0 0 1-3 10.9a7.1 7.1 0 0 1-4 1.1a8 8 0 0 1-6.9-4A44 44 0 0 0 176 39a8 8 0 0 1-8-8Z'/%3E%3C/svg%3E");mask:var(--un-icon) no-repeat;mask-size:100% 100%;-webkit-mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;background-color:currentColor;width:1em;height:1em;}.i-ph-moon{--un-icon:url("data:image/svg+xml;utf8,%3Csvg preserveAspectRatio='xMidYMid meet' viewBox='0 0 256 256' width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M224.3 150.3a8.1 8.1 0 0 0-7.8-5.7l-2.2.4A84 84 0 0 1 111 41.6a5.7 5.7 0 0 0 .3-1.8a7.9 7.9 0 0 0-10.3-8.1a100 100 0 1 0 123.3 123.2a7.2 7.2 0 0 0 0-4.6ZM128 212A84 84 0 0 1 92.8 51.7a99.9 99.9 0 0 0 111.5 111.5A84.4 84.4 0 0 1 128 212Z'/%3E%3C/svg%3E");mask:var(--un-icon) no-repeat;mask-size:100% 100%;-webkit-mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;background-color:currentColor;width:1em;height:1em;}.i-ph-sun{--un-icon:url("data:image/svg+xml;utf8,%3Csvg preserveAspectRatio='xMidYMid meet' viewBox='0 0 256 256' width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M128 60a68 68 0 1 0 68 68a68.1 68.1 0 0 0-68-68Zm0 120a52 52 0 1 1 52-52a52 52 0 0 1-52 52Zm-8-144V16a8 8 0 0 1 16 0v20a8 8 0 0 1-16 0ZM43.1 54.5a8.1 8.1 0 1 1 11.4-11.4l14.1 14.2a8 8 0 0 1 0 11.3a8.1 8.1 0 0 1-11.3 0ZM36 136H16a8 8 0 0 1 0-16h20a8 8 0 0 1 0 16Zm32.6 51.4a8 8 0 0 1 0 11.3l-14.1 14.2a8.3 8.3 0 0 1-5.7 2.3a8.5 8.5 0 0 1-5.7-2.3a8.1 8.1 0 0 1 0-11.4l14.2-14.1a8 8 0 0 1 11.3 0ZM136 220v20a8 8 0 0 1-16 0v-20a8 8 0 0 1 16 0Zm76.9-18.5a8.1 8.1 0 0 1 0 11.4a8.5 8.5 0 0 1-5.7 2.3a8.3 8.3 0 0 1-5.7-2.3l-14.1-14.2a8 8 0 0 1 11.3-11.3ZM248 128a8 8 0 0 1-8 8h-20a8 8 0 0 1 0-16h20a8 8 0 0 1 8 8Zm-60.6-59.4a8 8 0 0 1 0-11.3l14.1-14.2a8.1 8.1 0 0 1 11.4 11.4l-14.2 14.1a8.1 8.1 0 0 1-11.3 0Z'/%3E%3C/svg%3E");mask:var(--un-icon) no-repeat;mask-size:100% 100%;-webkit-mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;background-color:currentColor;width:1em;height:1em;}.note-red{position:relative;display:inline-flex;align-items:center;border-left-width:4px;border-left-style:solid;--un-border-opacity:1;border-color:rgba(53,120,229,var(--un-border-opacity));border-radius:0.25rem;background-color:rgba(53,120,229,0.1);background-color:rgba(185,28,28,0.1);padding:0.5rem;text-decoration:none;}.nv{position:relative;display:flex;align-items:center;border-radius:0.25rem;padding:0.5rem;--un-text-opacity:1;color:rgba(194,197,202,var(--un-text-opacity));text-decoration:none;transition-property:all;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:125ms;}.nv_selected{position:relative;display:flex;align-items:center;border-left-width:4px;border-left-style:solid;border-radius:0.25rem;--un-bg-opacity:.05;background-color:hsla(0,0%,100%,var(--un-bg-opacity));padding:0.5rem;--un-text-opacity:1;color:rgba(194,197,202,var(--un-text-opacity));color:rgba(53,120,229,var(--un-text-opacity));text-decoration:none;transition-property:all;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:125ms;}.input{height:2.5rem;display:flex;align-items:center;border-radius:0.25rem;border-style:none;--un-bg-opacity:1;background-color:rgba(233,236,239,var(--un-bg-opacity));padding:0.5rem;--un-text-opacity:1;color:rgba(28,30,33,var(--un-text-opacity));--un-shadow:var(--un-shadow-inset) 0 4px 6px -1px var(--un-shadow-color, rgba(0,0,0,0.1)),var(--un-shadow-inset) 0 2px 4px -2px var(--un-shadow-color, rgba(0,0,0,0.1));box-shadow:var(--un-ring-offset-shadow, 0 0 #0000), var(--un-ring-shadow, 0 0 #0000), var(--un-shadow);outline:2px solid transparent;outline-offset:2px;}.btn{user-select:none;border-radius:0.25rem;border-style:none;--un-bg-opacity:1;background-color:rgba(53,120,229,var(--un-bg-opacity));padding:0.5rem;font-weight:400;--un-text-opacity:1;color:rgba(28,30,33,var(--un-text-opacity));color:rgba(255,255,255,var(--un-text-opacity));--un-shadow:var(--un-shadow-inset) 0 4px 6px -1px var(--un-shadow-color, rgba(0,0,0,0.1)),var(--un-shadow-inset) 0 2px 4px -2px var(--un-shadow-color, rgba(0,0,0,0.1));box-shadow:var(--un-ring-offset-shadow, 0 0 #0000), var(--un-ring-shadow, 0 0 #0000), var(--un-shadow);outline:2px solid transparent;outline-offset:2px;}.nv_selected:hover,.nv:hover{border-left-width:4px;border-left-style:solid;--un-bg-opacity:.05;background-color:hsla(0,0%,100%,var(--un-bg-opacity));--un-text-opacity:1;color:rgba(53,120,229,var(--un-text-opacity));}.dark .note-red{--un-border-opacity:1;border-color:rgba(103,214,237,var(--un-border-opacity));background-color:rgba(103,214,237,0.1);background-color:rgba(185,28,28,0.1);}.btn:hover{--un-bg-opacity:1;background-color:rgba(45,102,195,var(--un-bg-opacity));}.dark .btn{--un-bg-opacity:1;background-color:rgba(103,214,237,var(--un-bg-opacity));font-weight:600;--un-text-opacity:1;color:rgba(28,30,33,var(--un-text-opacity));}.dark .btn:hover{--un-bg-opacity:1;background-color:rgba(57,202,232,var(--un-bg-opacity));}.dark .input{--un-bg-opacity:1;background-color:rgba(36,37,38,var(--un-bg-opacity));--un-text-opacity:1;color:rgba(227,227,227,var(--un-text-opacity));}.dark .note-red::after,.note-red::after{--un-bg-opacity:1;background-color:rgba(185,28,28,var(--un-bg-opacity));}.btn:active{--un-bg-opacity:1;background-color:rgba(37,84,160,var(--un-bg-opacity));}.dark .btn:active{--un-bg-opacity:1;background-color:rgba(25,181,213,var(--un-bg-opacity));}.dark .nv_selected,.dark .nv_selected:hover,.dark .nv:hover{--un-text-opacity:1;color:rgba(103,214,237,var(--un-text-opacity));} ::-webkit-scrollbar-thumb { background-color: #3578E5; } .dark ::-webkit-scrollbar-thumb { background-color: #67d6ed; } code { font-size: 0.75rem; font-family: "Fira Code","Fira Mono",ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace; border-radius: 0.25rem; background-color: #d6d8da; } .code-block { font-family: "Fira Code","Fira Mono",ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace; font-size: 0.875rem; } .dark code { background-color: #282a2e; } .visible{visibility:visible;}.absolute{position:absolute;}.left-2{left:0.5rem;}.top-2{top:0.5rem;}.z-2000{z-index:2000;}.grid{display:grid;}.grid-rows-\[2fr_auto\]{grid-template-rows:2fr auto;}.grid-rows-\[2px_2rem_1fr\]{grid-template-rows:2px 2rem 1fr;}.grid-rows-\[auto_1fr\]{grid-template-rows:auto 1fr;}.grid-rows-\[min-content_auto\]{grid-template-rows:min-content auto;}.mr-2{margin-right:0.5rem;}.display-none{display:none;}.children-h-10>*{height:2.5rem;}.h-15rem{height:15rem;}.h-2px{height:2px;}.h-8{height:2rem;}.h-85\%{height:85%;}.h-screen{height:100vh;}.w-8{width:2rem;}.w-screen{width:100vw;}.flex{display:flex;}.flex-1{flex:1 1 0%;}.children-flex-none>*{flex:none;}.grow{flex-grow:1;}.flex-col{flex-direction:column;}@keyframes fade-in{from{opacity:0}to{opacity:1}}.animate-fade-in{animation:fade-in 1s linear 1;}.animate-duration-300ms{animation-duration:300ms;}.cursor-ns-resize{cursor:ns-resize;}.cursor-pointer{cursor:pointer;}.select-none{user-select:none;}.items-center{align-items:center;}.self-center{align-self:center;}.justify-center{justify-content:center;}.justify-between{justify-content:space-between;}.gap-1{grid-gap:0.25rem;gap:0.25rem;}.gap-2{grid-gap:0.5rem;gap:0.5rem;}.overflow-hidden{overflow:hidden;}.overflow-y-auto{overflow-y:auto;}.rd-1{border-radius:0.25rem;}.rd-8{border-radius:2rem;}.bg-accent{--un-bg-opacity:1;background-color:rgba(53,120,229,var(--un-bg-opacity));}.bg-black\/20{background-color:rgba(0,0,0,0.2);}.bg-darkPrimaryLighter{--un-bg-opacity:1;background-color:rgba(36,37,38,var(--un-bg-opacity));}.bg-primary{--un-bg-opacity:1;background-color:rgba(255,255,255,var(--un-bg-opacity));}.bg-white\/5{background-color:rgba(255,255,255,0.05);}.dark .dark\:bg-darkAccent{--un-bg-opacity:1;background-color:rgba(103,214,237,var(--un-bg-opacity));}.dark .dark\:bg-darkPrimary{--un-bg-opacity:1;background-color:rgba(27,27,29,var(--un-bg-opacity));}.dark .dark\:hover\:bg-darkHoverOverlay:hover{--un-bg-opacity:.05;background-color:hsla(0,0%,100%,var(--un-bg-opacity));}.hover\:bg-hoverOverlay:hover{--un-bg-opacity:.05;background-color:rgba(0,0,0,var(--un-bg-opacity));}.active\:bg-accentDark:active{--un-bg-opacity:1;background-color:rgba(48,108,206,var(--un-bg-opacity));}.active\:bg-hoverOverlay\/25:active{background-color:rgba(0,0,0,0.25);}.dark .dark\:active\:bg-darkAccentDark:active{--un-bg-opacity:1;background-color:rgba(73,206,233,var(--un-bg-opacity));}.dark .dark\:active\:bg-darkHoverOverlay\/25:active{background-color:hsla(0,0%,100%,0.25);}.p-1{padding:0.25rem;}.p-7{padding:1.75rem;}.px{padding-left:1rem;padding-right:1rem;}.px-2{padding-left:0.5rem;padding-right:0.5rem;}.px-5{padding-left:1.25rem;padding-right:1.25rem;}.children-pb-2>*{padding-bottom:0.5rem;}.children-pt8>*{padding-top:2rem;}.all\:font-mono *{font-family:"Fira Code","Fira Mono",ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;}.all\:text-xs *{font-size:0.75rem;line-height:1rem;}.font-semibold{font-weight:600;}.dark .dark\:text-darkAccent{--un-text-opacity:1;color:rgba(103,214,237,var(--un-text-opacity));}.dark .dark\:text-darkPrimaryText{--un-text-opacity:1;color:rgba(227,227,227,var(--un-text-opacity));}.text-accent{--un-text-opacity:1;color:rgba(53,120,229,var(--un-text-opacity));}.text-primaryText{--un-text-opacity:1;color:rgba(28,30,33,var(--un-text-opacity));}.transition-colors-250{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:250ms;}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:150ms;}@media (max-width: 639.9px){.lt-sm\:absolute{position:absolute;}.lt-sm\:z-1999{z-index:1999;}.lt-sm\:h-screen{height:100vh;}.lt-sm\:flex{display:flex;}.lt-sm\:shadow{--un-shadow:var(--un-shadow-inset) 0 1px 3px 0 var(--un-shadow-color, rgba(0,0,0,0.1)),var(--un-shadow-inset) 0 1px 2px -1px var(--un-shadow-color, rgba(0,0,0,0.1));box-shadow:var(--un-ring-offset-shadow, 0 0 #0000), var(--un-ring-shadow, 0 0 #0000), var(--un-shadow);}.lt-sm\:shadow-lg{--un-shadow:var(--un-shadow-inset) 0 10px 15px -3px var(--un-shadow-color, rgba(0,0,0,0.1)),var(--un-shadow-inset) 0 4px 6px -4px var(--un-shadow-color, rgba(0,0,0,0.1));box-shadow:var(--un-ring-offset-shadow, 0 0 #0000), var(--un-ring-shadow, 0 0 #0000), var(--un-shadow);}.lt-sm\:transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:150ms;}}*:not(h1,h2,h3,h4,h5,h6){margin:0;padding:0}*{box-sizing:border-box;font-family:Rubik,sans-serif}::-webkit-scrollbar{width:.25rem;height:3px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{border-radius:.25rem}code{padding:.05rem .25rem}code.code-block{padding:.5rem}#sidebar{width:18.75rem}@media screen and (max-width: 640px){#sidebar{--translate-x: -18.75rem;transform:translate(var(--translate-x))}} +*,::before,::after{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x:var(--un-empty,/*!*/ /*!*/);--un-pan-y:var(--un-empty,/*!*/ /*!*/);--un-pinch-zoom:var(--un-empty,/*!*/ /*!*/);--un-scroll-snap-strictness:proximity;--un-ordinal:var(--un-empty,/*!*/ /*!*/);--un-slashed-zero:var(--un-empty,/*!*/ /*!*/);--un-numeric-figure:var(--un-empty,/*!*/ /*!*/);--un-numeric-spacing:var(--un-empty,/*!*/ /*!*/);--un-numeric-fraction:var(--un-empty,/*!*/ /*!*/);--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 #0000;--un-ring-shadow:0 0 #0000;--un-shadow-inset:var(--un-empty,/*!*/ /*!*/);--un-shadow:0 0 #0000;--un-ring-inset:var(--un-empty,/*!*/ /*!*/);--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgba(147,197,253,0.5);--un-blur:var(--un-empty,/*!*/ /*!*/);--un-brightness:var(--un-empty,/*!*/ /*!*/);--un-contrast:var(--un-empty,/*!*/ /*!*/);--un-drop-shadow:var(--un-empty,/*!*/ /*!*/);--un-grayscale:var(--un-empty,/*!*/ /*!*/);--un-hue-rotate:var(--un-empty,/*!*/ /*!*/);--un-invert:var(--un-empty,/*!*/ /*!*/);--un-saturate:var(--un-empty,/*!*/ /*!*/);--un-sepia:var(--un-empty,/*!*/ /*!*/);--un-backdrop-blur:var(--un-empty,/*!*/ /*!*/);--un-backdrop-brightness:var(--un-empty,/*!*/ /*!*/);--un-backdrop-contrast:var(--un-empty,/*!*/ /*!*/);--un-backdrop-grayscale:var(--un-empty,/*!*/ /*!*/);--un-backdrop-hue-rotate:var(--un-empty,/*!*/ /*!*/);--un-backdrop-invert:var(--un-empty,/*!*/ /*!*/);--un-backdrop-opacity:var(--un-empty,/*!*/ /*!*/);--un-backdrop-saturate:var(--un-empty,/*!*/ /*!*/);--un-backdrop-sepia:var(--un-empty,/*!*/ /*!*/);}@font-face { font-family: 'Fira Code'; font-style: normal; font-weight: 400; font-display: swap; src: url(https://fonts.gstatic.com/s/firacode/v21/uU9eCBsR6Z2vfE9aq3bL0fxyUs4tcw4W_D1sFVc.ttf) format('truetype');}@font-face { font-family: 'Fira Mono'; font-style: normal; font-weight: 400; font-display: swap; src: url(https://fonts.gstatic.com/s/firamono/v14/N0bX2SlFPv1weGeLZDtQIQ.ttf) format('truetype');}@font-face { font-family: 'Fira Mono'; font-style: normal; font-weight: 700; font-display: swap; src: url(https://fonts.gstatic.com/s/firamono/v14/N0bS2SlFPv1weGeLZDtondv3mQ.ttf) format('truetype');}@font-face { font-family: 'Rubik'; font-style: normal; font-weight: 400; font-display: swap; src: url(https://fonts.gstatic.com/s/rubik/v28/iJWZBXyIfDnIV5PNhY1KTN7Z-Yh-B4i1UA.ttf) format('truetype');}.i-codicon-clear-all{--un-icon:url("data:image/svg+xml;utf8,%3Csvg preserveAspectRatio='xMidYMid meet' viewBox='0 0 16 16' width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='m10 12.6l.7.7l1.6-1.6l1.6 1.6l.8-.7L13 11l1.7-1.6l-.8-.8l-1.6 1.7l-1.6-1.7l-.7.8l1.6 1.6l-1.6 1.6zM1 4h14V3H1v1zm0 3h14V6H1v1zm8 2.5V9H1v1h8v-.5zM9 13v-1H1v1h8z'/%3E%3C/svg%3E");mask:var(--un-icon) no-repeat;mask-size:100% 100%;-webkit-mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;background-color:currentColor;width:1em;height:1em;}.i-codicon-close{--un-icon:url("data:image/svg+xml;utf8,%3Csvg preserveAspectRatio='xMidYMid meet' viewBox='0 0 16 16' width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' fill-rule='evenodd' d='m8 8.707l3.646 3.647l.708-.707L8.707 8l3.647-3.646l-.707-.708L8 7.293L4.354 3.646l-.707.708L7.293 8l-3.646 3.646l.707.708L8 8.707z' clip-rule='evenodd'/%3E%3C/svg%3E");mask:var(--un-icon) no-repeat;mask-size:100% 100%;-webkit-mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;background-color:currentColor;width:1em;height:1em;}.i-codicon-link-external{--un-icon:url("data:image/svg+xml;utf8,%3Csvg preserveAspectRatio='xMidYMid meet' viewBox='0 0 16 16' width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' %3E%3Cg fill='currentColor'%3E%3Cpath d='M1.5 1H6v1H2v12h12v-4h1v4.5l-.5.5h-13l-.5-.5v-13l.5-.5z'/%3E%3Cpath d='M15 1.5V8h-1V2.707L7.243 9.465l-.707-.708L13.293 2H8V1h6.5l.5.5z'/%3E%3C/g%3E%3C/svg%3E");mask:var(--un-icon) no-repeat;mask-size:100% 100%;-webkit-mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;background-color:currentColor;width:1em;height:1em;}.i-codicon-menu{--un-icon:url("data:image/svg+xml;utf8,%3Csvg preserveAspectRatio='xMidYMid meet' viewBox='0 0 16 16' width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M16 5H0V4h16v1zm0 8H0v-1h16v1zm0-4.008H0V8h16v.992z'/%3E%3C/svg%3E");mask:var(--un-icon) no-repeat;mask-size:100% 100%;-webkit-mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;background-color:currentColor;width:1em;height:1em;}.i-codicon-radio-tower{--un-icon:url("data:image/svg+xml;utf8,%3Csvg preserveAspectRatio='xMidYMid meet' viewBox='0 0 16 16' width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' fill-rule='evenodd' d='M2.998 5.58a5.55 5.55 0 0 1 1.62-3.88l-.71-.7a6.45 6.45 0 0 0 0 9.16l.71-.7a5.55 5.55 0 0 1-1.62-3.88zm1.06 0a4.42 4.42 0 0 0 1.32 3.17l.71-.71a3.27 3.27 0 0 1-.76-1.12a3.45 3.45 0 0 1 0-2.67a3.22 3.22 0 0 1 .76-1.13l-.71-.71a4.46 4.46 0 0 0-1.32 3.17zm7.65 3.21l-.71-.71c.33-.32.59-.704.76-1.13a3.449 3.449 0 0 0 0-2.67a3.22 3.22 0 0 0-.76-1.13l.71-.7a4.468 4.468 0 0 1 0 6.34zM13.068 1l-.71.71a5.43 5.43 0 0 1 0 7.74l.71.71a6.45 6.45 0 0 0 0-9.16zM9.993 5.43a1.5 1.5 0 0 1-.245.98a2 2 0 0 1-.27.23l3.44 7.73l-.92.4l-.77-1.73h-5.54l-.77 1.73l-.92-.4l3.44-7.73a1.52 1.52 0 0 1-.33-1.63a1.55 1.55 0 0 1 .56-.68a1.5 1.5 0 0 1 2.325 1.1zm-1.595-.34a.52.52 0 0 0-.25.14a.52.52 0 0 0-.11.22a.48.48 0 0 0 0 .29c.04.09.102.17.18.23a.54.54 0 0 0 .28.08a.51.51 0 0 0 .5-.5a.54.54 0 0 0-.08-.28a.58.58 0 0 0-.23-.18a.48.48 0 0 0-.29 0zm.23 2.05h-.27l-.87 1.94h2l-.86-1.94zm2.2 4.94l-.89-2h-2.88l-.89 2h4.66z' clip-rule='evenodd'/%3E%3C/svg%3E");mask:var(--un-icon) no-repeat;mask-size:100% 100%;-webkit-mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;background-color:currentColor;width:1em;height:1em;}.i-ph-broadcast{--un-icon:url("data:image/svg+xml;utf8,%3Csvg preserveAspectRatio='xMidYMid meet' viewBox='0 0 256 256' width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M128 88a40 40 0 1 0 40 40a40 40 0 0 0-40-40Zm0 64a24 24 0 1 1 24-24a24.1 24.1 0 0 1-24 24Zm-59-48.9a64.5 64.5 0 0 0 0 49.8a65.4 65.4 0 0 0 13.7 20.4a7.9 7.9 0 0 1 0 11.3a8 8 0 0 1-5.6 2.3a8.3 8.3 0 0 1-5.7-2.3a80 80 0 0 1-17.1-25.5a79.9 79.9 0 0 1 0-62.2a80 80 0 0 1 17.1-25.5a8 8 0 0 1 11.3 0a7.9 7.9 0 0 1 0 11.3A65.4 65.4 0 0 0 69 103.1Zm132.7 56a80 80 0 0 1-17.1 25.5a8.3 8.3 0 0 1-5.7 2.3a8 8 0 0 1-5.6-2.3a7.9 7.9 0 0 1 0-11.3a65.4 65.4 0 0 0 13.7-20.4a64.5 64.5 0 0 0 0-49.8a65.4 65.4 0 0 0-13.7-20.4a7.9 7.9 0 0 1 0-11.3a8 8 0 0 1 11.3 0a80 80 0 0 1 17.1 25.5a79.9 79.9 0 0 1 0 62.2ZM54.5 201.5a8.1 8.1 0 0 1 0 11.4a8.3 8.3 0 0 1-5.7 2.3a8.5 8.5 0 0 1-5.7-2.3a121.8 121.8 0 0 1-25.7-38.2a120.7 120.7 0 0 1 0-93.4a121.8 121.8 0 0 1 25.7-38.2a8.1 8.1 0 0 1 11.4 11.4A103.5 103.5 0 0 0 24 128a103.5 103.5 0 0 0 30.5 73.5ZM248 128a120.2 120.2 0 0 1-9.4 46.7a121.8 121.8 0 0 1-25.7 38.2a8.5 8.5 0 0 1-5.7 2.3a8.3 8.3 0 0 1-5.7-2.3a8.1 8.1 0 0 1 0-11.4A103.5 103.5 0 0 0 232 128a103.5 103.5 0 0 0-30.5-73.5a8.1 8.1 0 1 1 11.4-11.4a121.8 121.8 0 0 1 25.7 38.2A120.2 120.2 0 0 1 248 128Z'/%3E%3C/svg%3E");mask:var(--un-icon) no-repeat;mask-size:100% 100%;-webkit-mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;background-color:currentColor;width:1em;height:1em;}.i-ph-hand-waving{--un-icon:url("data:image/svg+xml;utf8,%3Csvg preserveAspectRatio='xMidYMid meet' viewBox='0 0 256 256' width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='m220.2 104l-20-34.7a28.1 28.1 0 0 0-47.3-1.9l-17.3-30a28.1 28.1 0 0 0-38.3-10.3a29.4 29.4 0 0 0-9.9 9.6a27.9 27.9 0 0 0-11.5-6.2a27.2 27.2 0 0 0-21.2 2.8a27.9 27.9 0 0 0-10.3 38.2l3.4 5.8A28.5 28.5 0 0 0 36 81a28.1 28.1 0 0 0-10.2 38.2l42 72.8a88 88 0 1 0 152.4-88Zm-6.7 62.6a71.2 71.2 0 0 1-33.5 43.7A72.1 72.1 0 0 1 81.6 184l-42-72.8a12 12 0 0 1 20.8-12l22 38.1l.6.9v.2l.5.5l.2.2l.7.6h.1l.7.5h.3l.6.3h.2l.9.3h.1l.8.2h2.2l.9-.2h.3l.6-.2h.3l.9-.4a8.1 8.1 0 0 0 2.9-11l-22-38.1l-16-27.7a12 12 0 0 1-1.2-9.1a11.8 11.8 0 0 1 5.6-7.3a12 12 0 0 1 9.1-1.2a12.5 12.5 0 0 1 7.3 5.6l8 14h.1l26 45a7 7 0 0 0 1.5 1.9a8 8 0 0 0 12.3-9.9l-26-45a12 12 0 1 1 20.8-12l30 51.9l6.3 11a48.1 48.1 0 0 0-10.9 61a8 8 0 0 0 13.8-8a32 32 0 0 1 11.7-43.7l.7-.4l.5-.4h.1l.6-.6l.5-.5l.4-.5l.3-.6h.1l.2-.5v-.2a1.9 1.9 0 0 0 .2-.7h.1c0-.2.1-.4.1-.6s0-.2.1-.2v-2.1a6.4 6.4 0 0 0-.2-.7a1.9 1.9 0 0 0-.2-.7v-.2c0-.2-.1-.3-.2-.5l-.3-.7l-10-17.4a12 12 0 0 1 13.5-17.5a11.8 11.8 0 0 1 7.2 5.5l20 34.7a70.9 70.9 0 0 1 7.2 53.8Zm-125.8 78a8.2 8.2 0 0 1-6.6 3.4a8.6 8.6 0 0 1-4.6-1.4A117.9 117.9 0 0 1 41.1 208a8 8 0 1 1 13.8-8a102.6 102.6 0 0 0 30.8 33.4a8.1 8.1 0 0 1 2 11.2ZM168 31a8 8 0 0 1 8-8a60.2 60.2 0 0 1 52 30a7.9 7.9 0 0 1-3 10.9a7.1 7.1 0 0 1-4 1.1a8 8 0 0 1-6.9-4A44 44 0 0 0 176 39a8 8 0 0 1-8-8Z'/%3E%3C/svg%3E");mask:var(--un-icon) no-repeat;mask-size:100% 100%;-webkit-mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;background-color:currentColor;width:1em;height:1em;}.i-ph-moon{--un-icon:url("data:image/svg+xml;utf8,%3Csvg preserveAspectRatio='xMidYMid meet' viewBox='0 0 256 256' width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M224.3 150.3a8.1 8.1 0 0 0-7.8-5.7l-2.2.4A84 84 0 0 1 111 41.6a5.7 5.7 0 0 0 .3-1.8a7.9 7.9 0 0 0-10.3-8.1a100 100 0 1 0 123.3 123.2a7.2 7.2 0 0 0 0-4.6ZM128 212A84 84 0 0 1 92.8 51.7a99.9 99.9 0 0 0 111.5 111.5A84.4 84.4 0 0 1 128 212Z'/%3E%3C/svg%3E");mask:var(--un-icon) no-repeat;mask-size:100% 100%;-webkit-mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;background-color:currentColor;width:1em;height:1em;}.i-ph-sun{--un-icon:url("data:image/svg+xml;utf8,%3Csvg preserveAspectRatio='xMidYMid meet' viewBox='0 0 256 256' width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M128 60a68 68 0 1 0 68 68a68.1 68.1 0 0 0-68-68Zm0 120a52 52 0 1 1 52-52a52 52 0 0 1-52 52Zm-8-144V16a8 8 0 0 1 16 0v20a8 8 0 0 1-16 0ZM43.1 54.5a8.1 8.1 0 1 1 11.4-11.4l14.1 14.2a8 8 0 0 1 0 11.3a8.1 8.1 0 0 1-11.3 0ZM36 136H16a8 8 0 0 1 0-16h20a8 8 0 0 1 0 16Zm32.6 51.4a8 8 0 0 1 0 11.3l-14.1 14.2a8.3 8.3 0 0 1-5.7 2.3a8.5 8.5 0 0 1-5.7-2.3a8.1 8.1 0 0 1 0-11.4l14.2-14.1a8 8 0 0 1 11.3 0ZM136 220v20a8 8 0 0 1-16 0v-20a8 8 0 0 1 16 0Zm76.9-18.5a8.1 8.1 0 0 1 0 11.4a8.5 8.5 0 0 1-5.7 2.3a8.3 8.3 0 0 1-5.7-2.3l-14.1-14.2a8 8 0 0 1 11.3-11.3ZM248 128a8 8 0 0 1-8 8h-20a8 8 0 0 1 0-16h20a8 8 0 0 1 8 8Zm-60.6-59.4a8 8 0 0 1 0-11.3l14.1-14.2a8.1 8.1 0 0 1 11.4 11.4l-14.2 14.1a8.1 8.1 0 0 1-11.3 0Z'/%3E%3C/svg%3E");mask:var(--un-icon) no-repeat;mask-size:100% 100%;-webkit-mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;background-color:currentColor;width:1em;height:1em;}.note-red{position:relative;display:inline-flex;align-items:center;border-left-width:4px;border-left-style:solid;--un-border-opacity:1;border-color:rgba(53,120,229,var(--un-border-opacity));border-radius:0.25rem;background-color:rgba(53,120,229,0.1);background-color:rgba(185,28,28,0.1);padding:0.5rem;text-decoration:none;}.nv{position:relative;display:flex;align-items:center;border-radius:0.25rem;padding:0.5rem;--un-text-opacity:1;color:rgba(194,197,202,var(--un-text-opacity));text-decoration:none;transition-property:all;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:125ms;}.nv_selected{position:relative;display:flex;align-items:center;border-left-width:4px;border-left-style:solid;border-radius:0.25rem;--un-bg-opacity:.05;background-color:hsla(0,0%,100%,var(--un-bg-opacity));padding:0.5rem;--un-text-opacity:1;color:rgba(194,197,202,var(--un-text-opacity));color:rgba(53,120,229,var(--un-text-opacity));text-decoration:none;transition-property:all;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:125ms;}.input{height:2.5rem;display:flex;align-items:center;border-radius:0.25rem;border-style:none;--un-bg-opacity:1;background-color:rgba(233,236,239,var(--un-bg-opacity));padding:0.5rem;--un-text-opacity:1;color:rgba(28,30,33,var(--un-text-opacity));--un-shadow:var(--un-shadow-inset) 0 4px 6px -1px var(--un-shadow-color, rgba(0,0,0,0.1)),var(--un-shadow-inset) 0 2px 4px -2px var(--un-shadow-color, rgba(0,0,0,0.1));box-shadow:var(--un-ring-offset-shadow, 0 0 #0000), var(--un-ring-shadow, 0 0 #0000), var(--un-shadow);outline:2px solid transparent;outline-offset:2px;}.btn{user-select:none;border-radius:0.25rem;border-style:none;--un-bg-opacity:1;background-color:rgba(53,120,229,var(--un-bg-opacity));padding:0.5rem;font-weight:400;--un-text-opacity:1;color:rgba(28,30,33,var(--un-text-opacity));color:rgba(255,255,255,var(--un-text-opacity));--un-shadow:var(--un-shadow-inset) 0 4px 6px -1px var(--un-shadow-color, rgba(0,0,0,0.1)),var(--un-shadow-inset) 0 2px 4px -2px var(--un-shadow-color, rgba(0,0,0,0.1));box-shadow:var(--un-ring-offset-shadow, 0 0 #0000), var(--un-ring-shadow, 0 0 #0000), var(--un-shadow);outline:2px solid transparent;outline-offset:2px;}.nv_selected:hover,.nv:hover{border-left-width:4px;border-left-style:solid;--un-bg-opacity:.05;background-color:hsla(0,0%,100%,var(--un-bg-opacity));--un-text-opacity:1;color:rgba(53,120,229,var(--un-text-opacity));}.dark .note-red{--un-border-opacity:1;border-color:rgba(103,214,237,var(--un-border-opacity));background-color:rgba(103,214,237,0.1);background-color:rgba(185,28,28,0.1);}.btn:hover{--un-bg-opacity:1;background-color:rgba(45,102,195,var(--un-bg-opacity));}.dark .btn{--un-bg-opacity:1;background-color:rgba(103,214,237,var(--un-bg-opacity));font-weight:600;--un-text-opacity:1;color:rgba(28,30,33,var(--un-text-opacity));}.dark .btn:hover{--un-bg-opacity:1;background-color:rgba(57,202,232,var(--un-bg-opacity));}.dark .input{--un-bg-opacity:1;background-color:rgba(36,37,38,var(--un-bg-opacity));--un-text-opacity:1;color:rgba(227,227,227,var(--un-text-opacity));}.dark .note-red::after,.note-red::after{--un-bg-opacity:1;background-color:rgba(185,28,28,var(--un-bg-opacity));}.btn:active{--un-bg-opacity:1;background-color:rgba(37,84,160,var(--un-bg-opacity));}.dark .btn:active{--un-bg-opacity:1;background-color:rgba(25,181,213,var(--un-bg-opacity));}.dark .nv_selected,.dark .nv_selected:hover,.dark .nv:hover{--un-text-opacity:1;color:rgba(103,214,237,var(--un-text-opacity));} ::-webkit-scrollbar-thumb { background-color: #3578E5; } .dark ::-webkit-scrollbar-thumb { background-color: #67d6ed; } code { font-size: 0.75rem; font-family: "Fira Code","Fira Mono",ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace; border-radius: 0.25rem; background-color: #d6d8da; } .code-block { font-family: "Fira Code","Fira Mono",ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace; font-size: 0.875rem; } .dark code { background-color: #282a2e; } .visible{visibility:visible;}.absolute{position:absolute;}.left-2{left:0.5rem;}.top-2{top:0.5rem;}.z-2000{z-index:2000;}.grid{display:grid;}.grid-rows-\[2fr_auto\]{grid-template-rows:2fr auto;}.grid-rows-\[2px_2rem_1fr\]{grid-template-rows:2px 2rem 1fr;}.grid-rows-\[auto_1fr\]{grid-template-rows:auto 1fr;}.grid-rows-\[min-content_auto\]{grid-template-rows:min-content auto;}.mr-2{margin-right:0.5rem;}.display-none{display:none;}.children-h-10>*{height:2.5rem;}.h-15rem{height:15rem;}.h-2px{height:2px;}.h-8{height:2rem;}.h-85\%{height:85%;}.h-screen{height:100vh;}.w-8{width:2rem;}.w-screen{width:100vw;}.flex{display:flex;}.flex-1{flex:1 1 0%;}.children-flex-none>*{flex:none;}.grow{flex-grow:1;}.flex-col{flex-direction:column;}@keyframes fade-in{from{opacity:0}to{opacity:1}}.animate-fade-in{animation:fade-in 1s linear 1;}.animate-duration-300ms{animation-duration:300ms;}.cursor-ns-resize{cursor:ns-resize;}.cursor-pointer{cursor:pointer;}.select-none{user-select:none;}.items-center{align-items:center;}.self-center{align-self:center;}.justify-center{justify-content:center;}.justify-between{justify-content:space-between;}.gap-1{grid-gap:0.25rem;gap:0.25rem;}.gap-2{grid-gap:0.5rem;gap:0.5rem;}.overflow-hidden{overflow:hidden;}.overflow-y-auto{overflow-y:auto;}.b{border-width:1px;border-style:solid;}.rd-1{border-radius:0.25rem;}.rd-8{border-radius:2rem;}.bg-accent{--un-bg-opacity:1;background-color:rgba(53,120,229,var(--un-bg-opacity));}.bg-black\/20{background-color:rgba(0,0,0,0.2);}.bg-darkPrimaryLighter{--un-bg-opacity:1;background-color:rgba(36,37,38,var(--un-bg-opacity));}.bg-primary{--un-bg-opacity:1;background-color:rgba(255,255,255,var(--un-bg-opacity));}.bg-white\/5{background-color:rgba(255,255,255,0.05);}.dark .dark\:bg-darkAccent{--un-bg-opacity:1;background-color:rgba(103,214,237,var(--un-bg-opacity));}.dark .dark\:bg-darkPrimary{--un-bg-opacity:1;background-color:rgba(27,27,29,var(--un-bg-opacity));}.dark .dark\:hover\:bg-darkHoverOverlay:hover{--un-bg-opacity:.05;background-color:hsla(0,0%,100%,var(--un-bg-opacity));}.hover\:bg-hoverOverlay:hover{--un-bg-opacity:.05;background-color:rgba(0,0,0,var(--un-bg-opacity));}.active\:bg-accentDark:active{--un-bg-opacity:1;background-color:rgba(48,108,206,var(--un-bg-opacity));}.active\:bg-hoverOverlay\/25:active{background-color:rgba(0,0,0,0.25);}.dark .dark\:active\:bg-darkAccentDark:active{--un-bg-opacity:1;background-color:rgba(73,206,233,var(--un-bg-opacity));}.dark .dark\:active\:bg-darkHoverOverlay\/25:active{background-color:hsla(0,0%,100%,0.25);}.p-1{padding:0.25rem;}.p-7{padding:1.75rem;}.px{padding-left:1rem;padding-right:1rem;}.px-2{padding-left:0.5rem;padding-right:0.5rem;}.px-5{padding-left:1.25rem;padding-right:1.25rem;}.children-pb-2>*{padding-bottom:0.5rem;}.children-pt8>*{padding-top:2rem;}.all\:font-mono *{font-family:"Fira Code","Fira Mono",ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;}.all\:text-xs *{font-size:0.75rem;line-height:1rem;}.font-semibold{font-weight:600;}.dark .dark\:text-darkAccent{--un-text-opacity:1;color:rgba(103,214,237,var(--un-text-opacity));}.dark .dark\:text-darkPrimaryText{--un-text-opacity:1;color:rgba(227,227,227,var(--un-text-opacity));}.text-accent{--un-text-opacity:1;color:rgba(53,120,229,var(--un-text-opacity));}.text-primaryText{--un-text-opacity:1;color:rgba(28,30,33,var(--un-text-opacity));}.transition-colors-250{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:250ms;}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:150ms;}@media (max-width: 639.9px){.lt-sm\:absolute{position:absolute;}.lt-sm\:z-1999{z-index:1999;}.lt-sm\:h-screen{height:100vh;}.lt-sm\:flex{display:flex;}.lt-sm\:shadow{--un-shadow:var(--un-shadow-inset) 0 1px 3px 0 var(--un-shadow-color, rgba(0,0,0,0.1)),var(--un-shadow-inset) 0 1px 2px -1px var(--un-shadow-color, rgba(0,0,0,0.1));box-shadow:var(--un-ring-offset-shadow, 0 0 #0000), var(--un-ring-shadow, 0 0 #0000), var(--un-shadow);}.lt-sm\:shadow-lg{--un-shadow:var(--un-shadow-inset) 0 10px 15px -3px var(--un-shadow-color, rgba(0,0,0,0.1)),var(--un-shadow-inset) 0 4px 6px -4px var(--un-shadow-color, rgba(0,0,0,0.1));box-shadow:var(--un-ring-offset-shadow, 0 0 #0000), var(--un-ring-shadow, 0 0 #0000), var(--un-shadow);}.lt-sm\:transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:150ms;}}*:not(h1,h2,h3,h4,h5,h6){margin:0;padding:0}*{box-sizing:border-box;font-family:Rubik,sans-serif}::-webkit-scrollbar{width:.25rem;height:3px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{border-radius:.25rem}code{padding:.05rem .25rem}code.code-block{padding:.5rem}#sidebar{width:18.75rem}@media screen and (max-width: 640px){#sidebar{--translate-x: -18.75rem;transform:translate(var(--translate-x))}} diff --git a/examples/api/dist/assets/index.js b/examples/api/dist/assets/index.js index b7c96e9f0849..9f653c886b9c 100644 --- a/examples/api/dist/assets/index.js +++ b/examples/api/dist/assets/index.js @@ -1,9 +1,9 @@ -(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const r of document.querySelectorAll('link[rel="modulepreload"]'))i(r);new MutationObserver(r=>{for(const a of r)if(a.type==="childList")for(const p of a.addedNodes)p.tagName==="LINK"&&p.rel==="modulepreload"&&i(p)}).observe(document,{childList:!0,subtree:!0});function n(r){const a={};return r.integrity&&(a.integrity=r.integrity),r.referrerpolicy&&(a.referrerPolicy=r.referrerpolicy),r.crossorigin==="use-credentials"?a.credentials="include":r.crossorigin==="anonymous"?a.credentials="omit":a.credentials="same-origin",a}function i(r){if(r.ep)return;r.ep=!0;const a=n(r);fetch(r.href,a)}})();function $(){}function st(e){return e()}function Xe(){return Object.create(null)}function V(e){e.forEach(st)}function vt(e){return typeof e=="function"}function he(e,t){return e!=e?t==t:e!==t||e&&typeof e=="object"||typeof e=="function"}let ke;function bt(e,t){return ke||(ke=document.createElement("a")),ke.href=t,e===ke.href}function yt(e){return Object.keys(e).length===0}function wt(e,...t){if(e==null)return $;const n=e.subscribe(...t);return n.unsubscribe?()=>n.unsubscribe():n}function kt(e,t,n){e.$$.on_destroy.push(wt(t,n))}function o(e,t){e.appendChild(t)}function k(e,t,n){e.insertBefore(t,n||null)}function w(e){e.parentNode.removeChild(e)}function Ye(e,t){for(let n=0;ne.removeEventListener(t,n,i)}function c(e,t,n){n==null?e.removeAttribute(t):e.getAttribute(t)!==n&&e.setAttribute(t,n)}function $t(e){return Array.from(e.childNodes)}function Lt(e,t){t=""+t,e.wholeText!==t&&(e.data=t)}class xt{constructor(t=!1){this.is_svg=!1,this.is_svg=t,this.e=this.n=null}c(t){this.h(t)}m(t,n,i=null){this.e||(this.is_svg?this.e=Et(n.nodeName):this.e=f(n.nodeName),this.t=n,this.c(t)),this.i(i)}h(t){this.e.innerHTML=t,this.n=Array.from(this.e.childNodes)}i(t){for(let n=0;n{Le.delete(e),i&&(n&&e.d(1),i())}),e.o(t)}else i&&i()}function Qe(e){e&&e.c()}function Me(e,t,n,i){const{fragment:r,on_mount:a,on_destroy:p,after_update:s}=e.$$;r&&r.m(t,n),i||We(()=>{const u=a.map(st).filter(vt);p?p.push(...u):V(u),e.$$.on_mount=[]}),s.forEach(We)}function Re(e,t){const n=e.$$;n.fragment!==null&&(V(n.on_destroy),n.fragment&&n.fragment.d(t),n.on_destroy=n.fragment=null,n.ctx=[])}function Nt(e,t){e.$$.dirty[0]===-1&&(ce.push(e),Ot(),e.$$.dirty.fill(0)),e.$$.dirty[t/31|0]|=1<{const O=H.length?H[0]:S;return d.ctx&&r(d.ctx[v],d.ctx[v]=O)&&(!d.skip_bound&&d.bound[v]&&d.bound[v](O),E&&Nt(e,v)),S}):[],d.update(),E=!0,V(d.before_update),d.fragment=i?i(d.ctx):!1,t.target){if(t.hydrate){const v=$t(t.target);d.fragment&&d.fragment.l(v),v.forEach(w)}else d.fragment&&d.fragment.c();t.intro&&Ae(e.$$.fragment),Me(e,t.target,t.anchor,t.customElement),ut()}ue(u)}class Oe{$destroy(){Re(this,1),this.$destroy=$}$on(t,n){const i=this.$$.callbacks[t]||(this.$$.callbacks[t]=[]);return i.push(n),()=>{const r=i.indexOf(n);r!==-1&&i.splice(r,1)}}$set(t){this.$$set&&!yt(t)&&(this.$$.skip_bound=!0,this.$$set(t),this.$$.skip_bound=!1)}}const K=[];function It(e,t=$){let n;const i=new Set;function r(s){if(he(e,s)&&(e=s,n)){const u=!K.length;for(const d of i)d[1](),K.push(d,e);if(u){for(let d=0;d{i.delete(d),i.size===0&&(n(),n=null)}}return{set:r,update:a,subscribe:p}}function Wt(e){let t;return{c(){t=f("p"),t.innerHTML=`This is a demo of Tauri's API capabilities using the @tauri-apps/api package. It's used as the main validation app, serving as the test bed of our - development process. In the future, this app will be used on Tauri's integration - tests.`},m(n,i){k(n,t,i)},p:$,i:$,o:$,d(n){n&&w(t)}}}class At extends Oe{constructor(t){super(),Se(this,t,null,Wt,he,{})}}var Mt=Object.defineProperty,dt=(e,t)=>{for(var n in t)Mt(e,n,{get:t[n],enumerable:!0})},ft=(e,t,n)=>{if(!t.has(e))throw TypeError("Cannot "+n)},Ze=(e,t,n)=>(ft(e,t,"read from private field"),n?n.call(e):t.get(e)),Rt=(e,t,n)=>{if(t.has(e))throw TypeError("Cannot add the same private member more than once");t instanceof WeakSet?t.add(e):t.set(e,n)},Pt=(e,t,n,i)=>(ft(e,t,"write to private field"),i?i.call(e,n):t.set(e,n),n),Ht={};dt(Ht,{Channel:()=>ht,PluginListener:()=>mt,addPluginListener:()=>qt,convertFileSrc:()=>Ut,invoke:()=>P,transformCallback:()=>fe});function jt(){return window.crypto.getRandomValues(new Uint32Array(1))[0]}function fe(e,t=!1){let n=jt(),i=`_${n}`;return Object.defineProperty(window,i,{value:r=>(t&&Reflect.deleteProperty(window,i),e==null?void 0:e(r)),writable:!1,configurable:!0}),n}var ae,ht=class{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,Rt(this,ae,()=>{}),this.id=fe(e=>{Ze(this,ae).call(this,e)})}set onmessage(e){Pt(this,ae,e)}get onmessage(){return Ze(this,ae)}toJSON(){return`__CHANNEL__:${this.id}`}};ae=new WeakMap;var mt=class{constructor(e,t,n){this.plugin=e,this.event=t,this.channelId=n}async unregister(){return P(`plugin:${this.plugin}|remove_listener`,{event:this.event,channelId:this.channelId})}};async function qt(e,t,n){let i=new ht;return i.onmessage=n,P(`plugin:${e}|register_listener`,{event:t,handler:i}).then(()=>new mt(e,t,i.id))}async function P(e,t={},n){return new Promise((i,r)=>{let a=fe(s=>{i(s),Reflect.deleteProperty(window,`_${p}`)},!0),p=fe(s=>{r(s),Reflect.deleteProperty(window,`_${a}`)},!0);window.__TAURI_IPC__({cmd:e,callback:a,error:p,payload:t,options:n})})}function Ut(e,t="asset"){return window.__TAURI__.convertFileSrc(e,t)}var Ft={};dt(Ft,{TauriEvent:()=>pt,emit:()=>_t,listen:()=>Pe,once:()=>zt});var pt=(e=>(e.WINDOW_RESIZED="tauri://resize",e.WINDOW_MOVED="tauri://move",e.WINDOW_CLOSE_REQUESTED="tauri://close-requested",e.WINDOW_CREATED="tauri://window-created",e.WINDOW_DESTROYED="tauri://destroyed",e.WINDOW_FOCUS="tauri://focus",e.WINDOW_BLUR="tauri://blur",e.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",e.WINDOW_THEME_CHANGED="tauri://theme-changed",e.WINDOW_FILE_DROP="tauri://file-drop",e.WINDOW_FILE_DROP_HOVER="tauri://file-drop-hover",e.WINDOW_FILE_DROP_CANCELLED="tauri://file-drop-cancelled",e.MENU="tauri://menu",e))(pt||{});async function gt(e,t){await P("plugin:event|unlisten",{event:e,eventId:t})}async function Pe(e,t,n){return P("plugin:event|listen",{event:e,windowLabel:n==null?void 0:n.target,handler:fe(t)}).then(i=>async()=>gt(e,i))}async function zt(e,t,n){return Pe(e,i=>{t(i),gt(e,i.id).catch(()=>{})},n)}async function _t(e,t,n){await P("plugin:event|emit",{event:e,windowLabel:n==null?void 0:n.target,payload:t})}function Vt(e){let t,n,i,r,a,p,s,u;return{c(){t=f("div"),n=f("button"),n.textContent="Call Log API",i=g(),r=f("button"),r.textContent="Call Request (async) API",a=g(),p=f("button"),p.textContent="Send event to Rust",c(n,"class","btn"),c(n,"id","log"),c(r,"class","btn"),c(r,"id","request"),c(p,"class","btn"),c(p,"id","event")},m(d,E){k(d,t,E),o(t,n),o(t,i),o(t,r),o(t,a),o(t,p),s||(u=[z(n,"click",e[0]),z(r,"click",e[1]),z(p,"click",e[2])],s=!0)},p:$,i:$,o:$,d(d){d&&w(t),s=!1,V(u)}}}function Bt(e,t,n){let{onMessage:i}=t,r;xe(async()=>{r=await Pe("rust-event",i)}),at(()=>{r&&r()});function a(){P("log_operation",{event:"tauri-click",payload:"this payload is optional because we used Option in Rust"})}function p(){P("perform_request",{endpoint:"dummy endpoint arg",body:{id:5,name:"test"}}).then(i).catch(i)}function s(){_t("js-event","this is the payload string")}return e.$$set=u=>{"onMessage"in u&&n(3,i=u.onMessage)},[a,p,s,i]}class Gt extends Oe{constructor(t){super(),Se(this,t,Bt,Vt,he,{onMessage:3})}}function Xt(e){let t;return{c(){t=f("div"),t.innerHTML=`

- `,c(t,"class","flex flex-col gap-2")},m(n,i){k(n,t,i)},p:$,i:$,o:$,d(n){n&&w(t)}}}function Yt(e,t,n){let{onMessage:i}=t;const r=window.constraints={audio:!0,video:!0};function a(s){const u=document.querySelector("video"),d=s.getVideoTracks();i("Got stream with constraints:",r),i(`Using video device: ${d[0].label}`),window.stream=s,u.srcObject=s}function p(s){if(s.name==="ConstraintNotSatisfiedError"){const u=r.video;i(`The resolution ${u.width.exact}x${u.height.exact} px is not supported by your device.`)}else s.name==="PermissionDeniedError"&&i("Permissions have not been granted to use your camera and microphone, you need to allow the page access to your devices in order for the demo to work.");i(`getUserMedia error: ${s.name}`,s)}return xe(async()=>{try{const s=await navigator.mediaDevices.getUserMedia(r);a(s)}catch(s){p(s)}}),at(()=>{window.stream.getTracks().forEach(function(s){s.stop()})}),e.$$set=s=>{"onMessage"in s&&n(0,i=s.onMessage)},[i]}class Jt extends Oe{constructor(t){super(),Se(this,t,Yt,Xt,he,{onMessage:0})}}function et(e,t,n){const i=e.slice();return i[25]=t[n],i}function tt(e,t,n){const i=e.slice();return i[28]=t[n],i}function Kt(e){let t;return{c(){t=f("span"),c(t,"class","i-codicon-menu animate-duration-300ms animate-fade-in")},m(n,i){k(n,t,i)},d(n){n&&w(t)}}}function Qt(e){let t;return{c(){t=f("span"),c(t,"class","i-codicon-close animate-duration-300ms animate-fade-in")},m(n,i){k(n,t,i)},d(n){n&&w(t)}}}function Zt(e){let t,n;return{c(){t=Q(`Switch to Dark mode - `),n=f("div"),c(n,"class","i-ph-moon")},m(i,r){k(i,t,r),k(i,n,r)},d(i){i&&w(t),i&&w(n)}}}function en(e){let t,n;return{c(){t=Q(`Switch to Light mode - `),n=f("div"),c(n,"class","i-ph-sun")},m(i,r){k(i,t,r),k(i,n,r)},d(i){i&&w(t),i&&w(n)}}}function tn(e){let t,n,i,r,a=e[28].label+"",p,s,u,d;function E(){return e[14](e[28])}return{c(){t=f("a"),n=f("div"),i=g(),r=f("p"),p=Q(a),c(n,"class",e[28].icon+" mr-2"),c(t,"href","##"),c(t,"class",s="nv "+(e[1]===e[28]?"nv_selected":""))},m(v,S){k(v,t,S),o(t,n),o(t,i),o(t,r),o(r,p),u||(d=z(t,"click",E),u=!0)},p(v,S){e=v,S&2&&s!==(s="nv "+(e[1]===e[28]?"nv_selected":""))&&c(t,"class",s)},d(v){v&&w(t),u=!1,d()}}}function nt(e){let t,n=e[28]&&tn(e);return{c(){n&&n.c(),t=lt()},m(i,r){n&&n.m(i,r),k(i,t,r)},p(i,r){i[28]&&n.p(i,r)},d(i){n&&n.d(i),i&&w(t)}}}function it(e){let t,n=e[25].html+"",i;return{c(){t=new xt(!1),i=lt(),t.a=i},m(r,a){t.m(n,r,a),k(r,i,a)},p(r,a){a&16&&n!==(n=r[25].html+"")&&t.p(n)},d(r){r&&w(i),r&&t.d()}}}function nn(e){let t,n,i,r,a,p,s,u,d,E,v,S,H,O,Z,I,me,b,j,D,q,B,ee,te,pe,ge,m,_,C,W,A,ne,U=e[1].label+"",Te,He,_e,ie,y,je,N,ve,qe,G,be,Ue,re,Fe,oe,se,De,ze;function Ve(l,T){return l[0]?Qt:Kt}let ye=Ve(e),M=ye(e);function Be(l,T){return l[2]?en:Zt}let we=Be(e),R=we(e),X=e[5],L=[];for(let l=0;l`,me=g(),b=f("a"),b.innerHTML=`GitHub - `,j=g(),D=f("a"),D.innerHTML=`Source - `,q=g(),B=f("br"),ee=g(),te=f("div"),pe=g(),ge=f("br"),m=g(),_=f("div");for(let l=0;l',Fe=g(),oe=f("div");for(let l=0;l{Re(h,1)}),Ct()}Y?(y=new Y(Ge(l)),Qe(y.$$.fragment),Ae(y.$$.fragment,1),Me(y,ie,null)):y=null}if(T&16){J=l[4];let h;for(h=0;h{n(2,u=localStorage&&localStorage.getItem("theme")=="dark"),ot(u)});function d(){n(2,u=!u),ot(u)}let E=It([]);kt(e,E,m=>n(4,i=m));function v(m){E.update(_=>[{html:`
[${new Date().toLocaleTimeString()}]: `+(typeof m=="string"?m:JSON.stringify(m,null,1))+"
"},..._])}function S(m){E.update(_=>[{html:`
[${new Date().toLocaleTimeString()}]: `+m+"
"},..._])}function H(){E.update(()=>[])}let O,Z,I;function me(m){I=m.clientY;const _=window.getComputedStyle(O);Z=parseInt(_.height,10);const C=A=>{const ne=A.clientY-I,U=Z-ne;n(3,O.style.height=`${U{document.removeEventListener("mouseup",W),document.removeEventListener("mousemove",C)};document.addEventListener("mouseup",W),document.addEventListener("mousemove",C)}let b=!1,j,D,q=!1,B=0,ee=0;const te=(m,_,C)=>Math.min(Math.max(_,m),C);xe(()=>{n(13,j=document.querySelector("#sidebar")),D=document.querySelector("#sidebarToggle"),document.addEventListener("click",m=>{D.contains(m.target)?n(0,b=!b):b&&!j.contains(m.target)&&n(0,b=!1)}),document.addEventListener("touchstart",m=>{if(D.contains(m.target))return;const _=m.touches[0].clientX;(0<_&&_<20&&!b||b)&&(q=!0,B=_)}),document.addEventListener("touchmove",m=>{if(q){const _=m.touches[0].clientX;ee=_;const C=(_-B)/10;j.style.setProperty("--translate-x",`-${te(0,b?0-C:18.75-C,18.75)}rem`)}}),document.addEventListener("touchend",()=>{if(q){const m=(ee-B)/10;n(0,b=b?m>-(18.75/2):m>18.75/2)}q=!1})});const pe=m=>{s(m),n(0,b=!1)};function ge(m){Ne[m?"unshift":"push"](()=>{O=m,n(3,O)})}return e.$$.update=()=>{if(e.$$.dirty&1){const m=document.querySelector("#sidebar");m&&rn(m,b)}},[b,p,u,O,i,a,s,d,E,v,S,H,me,j,pe,ge]}class sn extends Oe{constructor(t){super(),Se(this,t,on,nn,he,{})}}new sn({target:document.querySelector("#app")}); +(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const r of document.querySelectorAll('link[rel="modulepreload"]'))i(r);new MutationObserver(r=>{for(const a of r)if(a.type==="childList")for(const m of a.addedNodes)m.tagName==="LINK"&&m.rel==="modulepreload"&&i(m)}).observe(document,{childList:!0,subtree:!0});function n(r){const a={};return r.integrity&&(a.integrity=r.integrity),r.referrerpolicy&&(a.referrerPolicy=r.referrerpolicy),r.crossorigin==="use-credentials"?a.credentials="include":r.crossorigin==="anonymous"?a.credentials="omit":a.credentials="same-origin",a}function i(r){if(r.ep)return;r.ep=!0;const a=n(r);fetch(r.href,a)}})();function E(){}function st(e){return e()}function Xe(){return Object.create(null)}function F(e){e.forEach(st)}function vt(e){return typeof e=="function"}function he(e,t){return e!=e?t==t:e!==t||e&&typeof e=="object"||typeof e=="function"}let ke;function bt(e,t){return ke||(ke=document.createElement("a")),ke.href=t,e===ke.href}function yt(e){return Object.keys(e).length===0}function wt(e,...t){if(e==null)return E;const n=e.subscribe(...t);return n.unsubscribe?()=>n.unsubscribe():n}function kt(e,t,n){e.$$.on_destroy.push(wt(t,n))}function s(e,t){e.appendChild(t)}function w(e,t,n){e.insertBefore(t,n||null)}function y(e){e.parentNode.removeChild(e)}function Ye(e,t){for(let n=0;ne.removeEventListener(t,n,i)}function c(e,t,n){n==null?e.removeAttribute(t):e.getAttribute(t)!==n&&e.setAttribute(t,n)}function $t(e){return Array.from(e.childNodes)}function Lt(e,t){t=""+t,e.wholeText!==t&&(e.data=t)}class xt{constructor(t=!1){this.is_svg=!1,this.is_svg=t,this.e=this.n=null}c(t){this.h(t)}m(t,n,i=null){this.e||(this.is_svg?this.e=Et(n.nodeName):this.e=d(n.nodeName),this.t=n,this.c(t)),this.i(i)}h(t){this.e.innerHTML=t,this.n=Array.from(this.e.childNodes)}i(t){for(let n=0;n{Le.delete(e),i&&(n&&e.d(1),i())}),e.o(t)}else i&&i()}function Qe(e){e&&e.c()}function Re(e,t,n,i){const{fragment:r,on_mount:a,on_destroy:m,after_update:o}=e.$$;r&&r.m(t,n),i||We(()=>{const f=a.map(st).filter(vt);m?m.push(...f):F(f),e.$$.on_mount=[]}),o.forEach(We)}function Ae(e,t){const n=e.$$;n.fragment!==null&&(F(n.on_destroy),n.fragment&&n.fragment.d(t),n.on_destroy=n.fragment=null,n.ctx=[])}function Nt(e,t){e.$$.dirty[0]===-1&&(ce.push(e),Ot(),e.$$.dirty.fill(0)),e.$$.dirty[t/31|0]|=1<{const M=O.length?O[0]:S;return u.ctx&&r(u.ctx[_],u.ctx[_]=M)&&(!u.skip_bound&&u.bound[_]&&u.bound[_](M),T&&Nt(e,_)),S}):[],u.update(),T=!0,F(u.before_update),u.fragment=i?i(u.ctx):!1,t.target){if(t.hydrate){const _=$t(t.target);u.fragment&&u.fragment.l(_),_.forEach(y)}else u.fragment&&u.fragment.c();t.intro&&Me(e.$$.fragment),Re(e,t.target,t.anchor,t.customElement),ut()}ue(f)}class Oe{$destroy(){Ae(this,1),this.$destroy=E}$on(t,n){const i=this.$$.callbacks[t]||(this.$$.callbacks[t]=[]);return i.push(n),()=>{const r=i.indexOf(n);r!==-1&&i.splice(r,1)}}$set(t){this.$$set&&!yt(t)&&(this.$$.skip_bound=!0,this.$$set(t),this.$$.skip_bound=!1)}}const J=[];function It(e,t=E){let n;const i=new Set;function r(o){if(he(e,o)&&(e=o,n)){const f=!J.length;for(const u of i)u[1](),J.push(u,e);if(f){for(let u=0;u{i.delete(u),i.size===0&&(n(),n=null)}}return{set:r,update:a,subscribe:m}}var Wt=Object.defineProperty,dt=(e,t)=>{for(var n in t)Wt(e,n,{get:t[n],enumerable:!0})},ft=(e,t,n)=>{if(!t.has(e))throw TypeError("Cannot "+n)},Ze=(e,t,n)=>(ft(e,t,"read from private field"),n?n.call(e):t.get(e)),Mt=(e,t,n)=>{if(t.has(e))throw TypeError("Cannot add the same private member more than once");t instanceof WeakSet?t.add(e):t.set(e,n)},Rt=(e,t,n,i)=>(ft(e,t,"write to private field"),i?i.call(e,n):t.set(e,n),n),At={};dt(At,{Channel:()=>ht,PluginListener:()=>mt,addPluginListener:()=>Ht,convertFileSrc:()=>jt,invoke:()=>W,transformCallback:()=>fe});function Pt(){return window.crypto.getRandomValues(new Uint32Array(1))[0]}function fe(e,t=!1){let n=Pt(),i=`_${n}`;return Object.defineProperty(window,i,{value:r=>(t&&Reflect.deleteProperty(window,i),e==null?void 0:e(r)),writable:!1,configurable:!0}),n}var ae,ht=class{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,Mt(this,ae,()=>{}),this.id=fe(e=>{Ze(this,ae).call(this,e)})}set onmessage(e){Rt(this,ae,e)}get onmessage(){return Ze(this,ae)}toJSON(){return`__CHANNEL__:${this.id}`}};ae=new WeakMap;var mt=class{constructor(e,t,n){this.plugin=e,this.event=t,this.channelId=n}async unregister(){return W(`plugin:${this.plugin}|remove_listener`,{event:this.event,channelId:this.channelId})}};async function Ht(e,t,n){let i=new ht;return i.onmessage=n,W(`plugin:${e}|register_listener`,{event:t,handler:i}).then(()=>new mt(e,t,i.id))}async function W(e,t={},n){return new Promise((i,r)=>{let a=fe(o=>{i(o),Reflect.deleteProperty(window,`_${m}`)},!0),m=fe(o=>{r(o),Reflect.deleteProperty(window,`_${a}`)},!0);window.__TAURI_IPC__({cmd:e,callback:a,error:m,payload:t,options:n})})}function jt(e,t="asset"){return window.__TAURI__.convertFileSrc(e,t)}function qt(e){let t,n,i,r,a,m;return{c(){t=d("div"),n=d("p"),n.innerHTML=`This is a demo of Tauri's API capabilities using the @tauri-apps/api package. It's used as the main validation app, serving as the test bed of our + development process. In the future, this app will be used on Tauri's integration + tests.`,i=g(),r=d("button"),r.textContent="Context menu",c(r,"class","btn")},m(o,f){w(o,t,f),s(t,n),s(t,i),s(t,r),a||(m=q(r,"click",e[0]),a=!0)},p:E,i:E,o:E,d(o){o&&y(t),a=!1,m()}}}function Ut(e){function t(){W("popup_context_menu")}return[t]}class Ft extends Oe{constructor(t){super(),Se(this,t,Ut,qt,he,{})}}var zt={};dt(zt,{TauriEvent:()=>pt,emit:()=>_t,listen:()=>Pe,once:()=>Vt});var pt=(e=>(e.WINDOW_RESIZED="tauri://resize",e.WINDOW_MOVED="tauri://move",e.WINDOW_CLOSE_REQUESTED="tauri://close-requested",e.WINDOW_CREATED="tauri://window-created",e.WINDOW_DESTROYED="tauri://destroyed",e.WINDOW_FOCUS="tauri://focus",e.WINDOW_BLUR="tauri://blur",e.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",e.WINDOW_THEME_CHANGED="tauri://theme-changed",e.WINDOW_FILE_DROP="tauri://file-drop",e.WINDOW_FILE_DROP_HOVER="tauri://file-drop-hover",e.WINDOW_FILE_DROP_CANCELLED="tauri://file-drop-cancelled",e.MENU="tauri://menu",e))(pt||{});async function gt(e,t){await W("plugin:event|unlisten",{event:e,eventId:t})}async function Pe(e,t,n){return W("plugin:event|listen",{event:e,windowLabel:n==null?void 0:n.target,handler:fe(t)}).then(i=>async()=>gt(e,i))}async function Vt(e,t,n){return Pe(e,i=>{t(i),gt(e,i.id).catch(()=>{})},n)}async function _t(e,t,n){await W("plugin:event|emit",{event:e,windowLabel:n==null?void 0:n.target,payload:t})}function Bt(e){let t,n,i,r,a,m,o,f;return{c(){t=d("div"),n=d("button"),n.textContent="Call Log API",i=g(),r=d("button"),r.textContent="Call Request (async) API",a=g(),m=d("button"),m.textContent="Send event to Rust",c(n,"class","btn"),c(n,"id","log"),c(r,"class","btn"),c(r,"id","request"),c(m,"class","btn"),c(m,"id","event")},m(u,T){w(u,t,T),s(t,n),s(t,i),s(t,r),s(t,a),s(t,m),o||(f=[q(n,"click",e[0]),q(r,"click",e[1]),q(m,"click",e[2])],o=!0)},p:E,i:E,o:E,d(u){u&&y(t),o=!1,F(f)}}}function Gt(e,t,n){let{onMessage:i}=t,r;xe(async()=>{r=await Pe("rust-event",i)}),at(()=>{r&&r()});function a(){W("log_operation",{event:"tauri-click",payload:"this payload is optional because we used Option in Rust"})}function m(){W("perform_request",{endpoint:"dummy endpoint arg",body:{id:5,name:"test"}}).then(i).catch(i)}function o(){_t("js-event","this is the payload string")}return e.$$set=f=>{"onMessage"in f&&n(3,i=f.onMessage)},[a,m,o,i]}class Xt extends Oe{constructor(t){super(),Se(this,t,Gt,Bt,he,{onMessage:3})}}function Yt(e){let t;return{c(){t=d("div"),t.innerHTML=`
Not available for Linux
+ `,c(t,"class","flex flex-col gap-2")},m(n,i){w(n,t,i)},p:E,i:E,o:E,d(n){n&&y(t)}}}function Kt(e,t,n){let{onMessage:i}=t;const r=window.constraints={audio:!0,video:!0};function a(o){const f=document.querySelector("video"),u=o.getVideoTracks();i("Got stream with constraints:",r),i(`Using video device: ${u[0].label}`),window.stream=o,f.srcObject=o}function m(o){if(o.name==="ConstraintNotSatisfiedError"){const f=r.video;i(`The resolution ${f.width.exact}x${f.height.exact} px is not supported by your device.`)}else o.name==="PermissionDeniedError"&&i("Permissions have not been granted to use your camera and microphone, you need to allow the page access to your devices in order for the demo to work.");i(`getUserMedia error: ${o.name}`,o)}return xe(async()=>{try{const o=await navigator.mediaDevices.getUserMedia(r);a(o)}catch(o){m(o)}}),at(()=>{window.stream.getTracks().forEach(function(o){o.stop()})}),e.$$set=o=>{"onMessage"in o&&n(0,i=o.onMessage)},[i]}class Jt extends Oe{constructor(t){super(),Se(this,t,Kt,Yt,he,{onMessage:0})}}function et(e,t,n){const i=e.slice();return i[23]=t[n],i}function tt(e,t,n){const i=e.slice();return i[26]=t[n],i}function Qt(e){let t;return{c(){t=d("span"),c(t,"class","i-codicon-menu animate-duration-300ms animate-fade-in")},m(n,i){w(n,t,i)},d(n){n&&y(t)}}}function Zt(e){let t;return{c(){t=d("span"),c(t,"class","i-codicon-close animate-duration-300ms animate-fade-in")},m(n,i){w(n,t,i)},d(n){n&&y(t)}}}function en(e){let t,n;return{c(){t=Q(`Switch to Dark mode + `),n=d("div"),c(n,"class","i-ph-moon")},m(i,r){w(i,t,r),w(i,n,r)},d(i){i&&y(t),i&&y(n)}}}function tn(e){let t,n;return{c(){t=Q(`Switch to Light mode + `),n=d("div"),c(n,"class","i-ph-sun")},m(i,r){w(i,t,r),w(i,n,r)},d(i){i&&y(t),i&&y(n)}}}function nn(e){let t,n,i,r,a=e[26].label+"",m,o,f,u;function T(){return e[14](e[26])}return{c(){t=d("a"),n=d("div"),i=g(),r=d("p"),m=Q(a),c(n,"class",e[26].icon+" mr-2"),c(t,"href","##"),c(t,"class",o="nv "+(e[1]===e[26]?"nv_selected":""))},m(_,S){w(_,t,S),s(t,n),s(t,i),s(t,r),s(r,m),f||(u=q(t,"click",T),f=!0)},p(_,S){e=_,S&2&&o!==(o="nv "+(e[1]===e[26]?"nv_selected":""))&&c(t,"class",o)},d(_){_&&y(t),f=!1,u()}}}function nt(e){let t,n=e[26]&&nn(e);return{c(){n&&n.c(),t=lt()},m(i,r){n&&n.m(i,r),w(i,t,r)},p(i,r){i[26]&&n.p(i,r)},d(i){n&&n.d(i),i&&y(t)}}}function it(e){let t,n=e[23].html+"",i;return{c(){t=new xt(!1),i=lt(),t.a=i},m(r,a){t.m(n,r,a),w(r,i,a)},p(r,a){a&16&&n!==(n=r[23].html+"")&&t.p(n)},d(r){r&&y(i),r&&t.d()}}}function rn(e){let t,n,i,r,a,m,o,f,u,T,_,S,O,M,Z,R,k,C,z,D,V,ee,me,te,pe,h,b,x,ne,A,P,B,ge=e[1].label+"",Te,He,_e,ie,v,je,I,ve,qe,G,be,Ue,re,Fe,oe,se,Ce,ze;function Ve(l,N){return l[0]?Zt:Qt}let ye=Ve(e),H=ye(e);function Be(l,N){return l[2]?tn:en}let we=Be(e),j=we(e),X=e[5],$=[];for(let l=0;l`,k=g(),C=d("a"),C.innerHTML=`GitHub + `,z=g(),D=d("a"),D.innerHTML=`Source + `,V=g(),ee=d("br"),me=g(),te=d("div"),pe=g(),h=d("br"),b=g(),x=d("div");for(let l=0;l<$.length;l+=1)$[l].c();ne=g(),A=d("main"),P=d("div"),B=d("h1"),Te=Q(ge),He=g(),_e=d("div"),ie=d("div"),v&&Qe(v.$$.fragment),je=g(),I=d("div"),ve=d("div"),qe=g(),G=d("div"),be=d("p"),be.textContent="Console",Ue=g(),re=d("div"),re.innerHTML='
',Fe=g(),oe=d("div");for(let l=0;l{Ae(p,1)}),Dt()}Y?(v=new Y(Ge(l)),Qe(v.$$.fragment),Me(v.$$.fragment,1),Re(v,ie,null)):v=null}if(N&16){K=l[4];let p;for(p=0;p{h.ctrlKey&&h.key==="b"&&W("toggle_menu")});const r=[{label:"Welcome",component:Ft,icon:"i-ph-hand-waving"},{label:"Communication",component:Xt,icon:"i-codicon-radio-tower"},{label:"WebRTC",component:Jt,icon:"i-ph-broadcast"}];let a=r[0];function m(h){n(1,a=h)}let o;xe(()=>{n(2,o=localStorage&&localStorage.getItem("theme")=="dark"),ot(o)});function f(){n(2,o=!o),ot(o)}let u=It([]);kt(e,u,h=>n(4,i=h));function T(h){u.update(b=>[{html:`
[${new Date().toLocaleTimeString()}]: `+(typeof h=="string"?h:JSON.stringify(h,null,1))+"
"},...b])}function _(h){u.update(b=>[{html:`
[${new Date().toLocaleTimeString()}]: `+h+"
"},...b])}function S(){u.update(()=>[])}let O,M,Z;function R(h){Z=h.clientY;const b=window.getComputedStyle(O);M=parseInt(b.height,10);const x=A=>{const P=A.clientY-Z,B=M-P;n(3,O.style.height=`${B{document.removeEventListener("mouseup",ne),document.removeEventListener("mousemove",x)};document.addEventListener("mouseup",ne),document.addEventListener("mousemove",x)}let k=!1,C,z,D=!1,V=0,ee=0;const me=(h,b,x)=>Math.min(Math.max(b,h),x);xe(()=>{n(13,C=document.querySelector("#sidebar")),z=document.querySelector("#sidebarToggle"),document.addEventListener("click",h=>{z.contains(h.target)?n(0,k=!k):k&&!C.contains(h.target)&&n(0,k=!1)}),document.addEventListener("touchstart",h=>{if(z.contains(h.target))return;const b=h.touches[0].clientX;(0{if(D){const b=h.touches[0].clientX;ee=b;const x=(b-V)/10;C.style.setProperty("--translate-x",`-${me(0,k?0-x:18.75-x,18.75)}rem`)}}),document.addEventListener("touchend",()=>{if(D){const h=(ee-V)/10;n(0,k=k?h>-(18.75/2):h>18.75/2)}D=!1})});const te=h=>{m(h),n(0,k=!1)};function pe(h){Ne[h?"unshift":"push"](()=>{O=h,n(3,O)})}return e.$$.update=()=>{if(e.$$.dirty&1){const h=document.querySelector("#sidebar");h&&on(h,k)}},[k,a,o,O,i,r,m,f,u,T,_,S,R,C,te,pe]}class ln extends Oe{constructor(t){super(),Se(this,t,sn,rn,he,{})}}new ln({target:document.querySelector("#app")}); diff --git a/examples/api/src-tauri/Cargo.lock b/examples/api/src-tauri/Cargo.lock index c6eac0884eb8..3765f3c6a9d3 100644 --- a/examples/api/src-tauri/Cargo.lock +++ b/examples/api/src-tauri/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" +dependencies = [ + "gimli", +] + [[package]] name = "adler" version = "1.0.2" @@ -20,9 +29,9 @@ dependencies = [ [[package]] name = "aes" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" dependencies = [ "cfg-if", "cipher", @@ -117,15 +126,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" +checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" [[package]] name = "anstyle-parse" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" dependencies = [ "utf8parse", ] @@ -151,9 +160,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.71" +version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" +checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" [[package]] name = "api" @@ -207,7 +216,7 @@ dependencies = [ "async-lock", "async-task", "concurrent-queue", - "fastrand", + "fastrand 1.9.0", "futures-lite", "slab", ] @@ -238,7 +247,7 @@ dependencies = [ "log", "parking", "polling", - "rustix", + "rustix 0.37.23", "slab", "socket2", "waker-fn", @@ -266,7 +275,7 @@ dependencies = [ "cfg-if", "event-listener", "futures-lite", - "rustix", + "rustix 0.37.23", "signal-hook", "windows-sys 0.48.0", ] @@ -279,7 +288,7 @@ checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.28", ] [[package]] @@ -290,13 +299,13 @@ checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" [[package]] name = "async-trait" -version = "0.1.69" +version = "0.1.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b2d0f03b3640e3a630367e40c468cb7f309529c708ed1d88597047b0e7c6ef7" +checksum = "cc6dde6e4ed435a4c1ee4e73592f5ba9da2151af10076cc04858746af9352d09" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.28", ] [[package]] @@ -306,7 +315,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39991bc421ddf72f70159011b323ff49b0f783cc676a7287c59453da2e2531cf" dependencies = [ "atk-sys", - "bitflags", + "bitflags 1.3.2", "glib", "libc", ] @@ -335,6 +344,21 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backtrace" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "base64" version = "0.21.2" @@ -347,6 +371,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" + [[package]] name = "block" version = "0.1.6" @@ -372,7 +402,7 @@ dependencies = [ "async-lock", "async-task", "atomic-waker", - "fastrand", + "fastrand 1.9.0", "futures-lite", "log", ] @@ -441,7 +471,7 @@ version = "0.16.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3125b15ec28b84c238f6f476c6034016a5f6cc0221cb514ca46c532139fc97d" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cairo-sys-rs", "glib", "libc", @@ -472,9 +502,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.79" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" +dependencies = [ + "libc", +] [[package]] name = "cesu8" @@ -495,9 +528,9 @@ dependencies = [ [[package]] name = "cfg-expr" -version = "0.15.2" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e70d3ad08698a0568b0562f22710fe6bfc1f4a61a367c77d0398c562eadd453a" +checksum = "b40ccee03b5175c18cde8f37e7d2a33bcef6f8ec8f7cc0d81090d1bb380949c9" dependencies = [ "smallvec", "target-lexicon", @@ -540,22 +573,21 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.2" +version = "4.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "401a4694d2bf92537b6867d94de48c4842089645fdcdf6c71865b175d836e9c2" +checksum = "c27cdf28c0f604ba3f512b0c9a409f8de8513e4816705deb0498b627e7c3a3fd" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.3.1" +version = "4.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72394f3339a76daf211e57d4bcb374410f3965dcc606dd0e03738c7888766980" +checksum = "08a9f1ab5e9f01a9b81f202e8562eb9a10de70abf9eaeac1be465c28b75aa4aa" dependencies = [ "anstream", "anstyle", - "bitflags", "clap_lex", "strsim", ] @@ -572,39 +604,45 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" dependencies = [ - "bitflags", + "bitflags 1.3.2", "block", "cocoa-foundation", "core-foundation", - "core-graphics", - "foreign-types", + "core-graphics 0.22.3", + "foreign-types 0.3.2", "libc", "objc", ] [[package]] -name = "cocoa-foundation" -version = "0.1.1" +name = "cocoa" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "931d3837c286f56e3c58423ce4eba12d08db2374461a785c86f672b08b5650d6" +checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" dependencies = [ - "bitflags", + "bitflags 1.3.2", "block", + "cocoa-foundation", "core-foundation", - "core-graphics-types", - "foreign-types", + "core-graphics 0.23.1", + "foreign-types 0.5.0", "libc", "objc", ] [[package]] -name = "codespan-reporting" -version = "0.11.1" +name = "cocoa-foundation" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +checksum = "931d3837c286f56e3c58423ce4eba12d08db2374461a785c86f672b08b5650d6" dependencies = [ - "termcolor", - "unicode-width", + "bitflags 1.3.2", + "block", + "core-foundation", + "core-graphics-types", + "foreign-types 0.3.2", + "libc", + "objc", ] [[package]] @@ -666,30 +704,42 @@ version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" dependencies = [ - "bitflags", + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types 0.3.2", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "970a29baf4110c26fedbc7f82107d42c23f7e88e404c4577ed73fe99ff85a212" +dependencies = [ + "bitflags 1.3.2", "core-foundation", "core-graphics-types", - "foreign-types", + "foreign-types 0.5.0", "libc", ] [[package]] name = "core-graphics-types" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" +checksum = "2bb142d41022986c1d8ff29103a1411c8a3dfad3552f87a4f8dc50d61d4f4e33" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", - "foreign-types", "libc", ] [[package]] name = "cpufeatures" -version = "0.2.7" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" dependencies = [ "libc", ] @@ -715,9 +765,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if", ] @@ -757,7 +807,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.18", + "syn 2.0.28", ] [[package]] @@ -779,55 +829,11 @@ dependencies = [ "cipher", ] -[[package]] -name = "cxx" -version = "1.0.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a140f260e6f3f79013b8bfc65e7ce630c9ab4388c6a89c71e07226f49487b72" -dependencies = [ - "cc", - "cxxbridge-flags", - "cxxbridge-macro", - "link-cplusplus", -] - -[[package]] -name = "cxx-build" -version = "1.0.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da6383f459341ea689374bf0a42979739dc421874f112ff26f829b8040b8e613" -dependencies = [ - "cc", - "codespan-reporting", - "once_cell", - "proc-macro2", - "quote", - "scratch", - "syn 1.0.109", -] - -[[package]] -name = "cxxbridge-flags" -version = "1.0.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90201c1a650e95ccff1c8c0bb5a343213bdd317c6e600a93075bca2eff54ec97" - -[[package]] -name = "cxxbridge-macro" -version = "1.0.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b75aed41bb2e6367cae39e6326ef817a851db13c13e4f3263714ca3cfb8de56" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "darling" -version = "0.20.1" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0558d22a7b463ed0241e993f76f09f30b126687447751a8638587b864e4b3944" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" dependencies = [ "darling_core", "darling_macro", @@ -835,27 +841,36 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.1" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab8bfa2e259f8ee1ce5e97824a3c55ec4404a0d772ca7fa96bf19f0752a046eb" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 2.0.18", + "syn 2.0.28", ] [[package]] name = "darling_macro" -version = "0.20.1" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", - "syn 2.0.18", + "syn 2.0.28", +] + +[[package]] +name = "deranged" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929" +dependencies = [ + "serde", ] [[package]] @@ -921,9 +936,9 @@ checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" [[package]] name = "dtoa" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65d09067bfacaa79114679b279d7f5885b53295b1e2cfb4e79c8e4bd3d633169" +checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" [[package]] name = "dtoa-short" @@ -942,9 +957,9 @@ checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" [[package]] name = "embed-resource" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80663502655af01a2902dff3f06869330782267924bf1788410b74edcd93770a" +checksum = "f7f1e82a60222fc67bfd50d752a9c89da5cce4c39ed39decc84a443b07bbd69a" dependencies = [ "cc", "rustc_version", @@ -986,7 +1001,7 @@ checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.28", ] [[package]] @@ -999,11 +1014,17 @@ dependencies = [ "regex", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" dependencies = [ "errno-dragonfly", "libc", @@ -1035,6 +1056,12 @@ dependencies = [ "instant", ] +[[package]] +name = "fastrand" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" + [[package]] name = "fdeflate" version = "0.3.0" @@ -1085,7 +1112,28 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ - "foreign-types-shared", + "foreign-types-shared 0.1.1", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared 0.3.1", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", ] [[package]] @@ -1094,6 +1142,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + [[package]] name = "form_urlencoded" version = "1.2.0" @@ -1151,7 +1205,7 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" dependencies = [ - "fastrand", + "fastrand 1.9.0", "futures-core", "futures-io", "memchr", @@ -1168,7 +1222,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.28", ] [[package]] @@ -1215,7 +1269,7 @@ version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9cb33da481c6c040404a11f8212d193889e9b435db2c14fd86987f630d3ce1" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cairo-rs", "gdk-pixbuf", "gdk-sys", @@ -1231,7 +1285,7 @@ version = "0.16.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3578c60dee9d029ad86593ed88cb40f35c1b83360e12498d055022385dd9a05" dependencies = [ - "bitflags", + "bitflags 1.3.2", "gdk-pixbuf-sys", "gio", "glib", @@ -1297,9 +1351,9 @@ dependencies = [ [[package]] name = "generator" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3e123d9ae7c02966b4d892e550bdc32164f05853cd40ab570650ad600596a8a" +checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e" dependencies = [ "cc", "libc", @@ -1350,13 +1404,19 @@ dependencies = [ "polyval", ] +[[package]] +name = "gimli" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" + [[package]] name = "gio" version = "0.16.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a1c84b4534a290a29160ef5c6eff2a9c95833111472e824fc5cb78b513dd092" dependencies = [ - "bitflags", + "bitflags 1.3.2", "futures-channel", "futures-core", "futures-io", @@ -1389,7 +1449,7 @@ version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16aa2475c9debed5a32832cb5ff2af5a3f9e1ab9e69df58eaadc1ab2004d6eba" dependencies = [ - "bitflags", + "bitflags 1.3.2", "futures-channel", "futures-core", "futures-executor", @@ -1454,7 +1514,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4d3507d43908c866c805f74c9dd593c0ce7ba5c38e576e41846639cdcd4bee6" dependencies = [ "atk", - "bitflags", + "bitflags 1.3.2", "cairo-rs", "field-offset", "futures-channel", @@ -1504,9 +1564,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.19" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782" +checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" dependencies = [ "bytes", "fnv", @@ -1514,7 +1574,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 1.9.3", "slab", "tokio", "tokio-util", @@ -1528,25 +1588,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] -name = "heck" -version = "0.4.1" +name = "hashbrown" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" [[package]] -name = "hermit-abi" -version = "0.2.6" +name = "heck" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" [[package]] name = "hex" @@ -1554,20 +1611,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "html5ever" -version = "0.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5c13fb08e5d4dfc151ee5e88bae63f7773d61852f3bdc73c9f4b9e1bde03148" -dependencies = [ - "log", - "mac", - "markup5ever 0.10.1", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "html5ever" version = "0.26.0" @@ -1576,7 +1619,7 @@ checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" dependencies = [ "log", "mac", - "markup5ever 0.11.0", + "markup5ever", "proc-macro2", "quote", "syn 1.0.109", @@ -1590,7 +1633,7 @@ checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ "bytes", "fnv", - "itoa 1.0.6", + "itoa 1.0.9", ] [[package]] @@ -1624,9 +1667,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "hyper" -version = "0.14.26" +version = "0.14.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" dependencies = [ "bytes", "futures-channel", @@ -1637,7 +1680,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 1.0.6", + "itoa 1.0.9", "pin-project-lite", "socket2", "tokio", @@ -1648,9 +1691,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.56" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1662,12 +1705,11 @@ dependencies = [ [[package]] name = "iana-time-zone-haiku" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ - "cxx", - "cxx-build", + "cc", ] [[package]] @@ -1726,7 +1768,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown 0.14.0", "serde", ] @@ -1772,26 +1825,25 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ - "hermit-abi 0.3.1", + "hermit-abi", "libc", "windows-sys 0.48.0", ] [[package]] name = "ipnet" -version = "2.7.2" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" +checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" [[package]] name = "is-terminal" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ - "hermit-abi 0.3.1", - "io-lifetimes", - "rustix", + "hermit-abi", + "rustix 0.38.7", "windows-sys 0.48.0", ] @@ -1803,9 +1855,9 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "javascriptcore-rs" @@ -1813,7 +1865,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcc681b896b083864a4a3c3b3ea196f14ff66b8641a68fde209c6d84434056" dependencies = [ - "bitflags", + "bitflags 1.3.2", "glib", "javascriptcore-rs-sys", ] @@ -1832,16 +1884,18 @@ dependencies = [ [[package]] name = "jni" -version = "0.20.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "039022cdf4d7b1cf548d31f60ae783138e5fd42013f6271049d7df7afadef96c" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" dependencies = [ "cesu8", + "cfg-if", "combine", "jni-sys", "log", "thiserror", "walkdir", + "windows-sys 0.45.0", ] [[package]] @@ -1852,9 +1906,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "js-sys" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] @@ -1872,15 +1926,14 @@ dependencies = [ ] [[package]] -name = "kuchiki" -version = "0.8.1" +name = "keyboard-types" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ea8e9c6e031377cff82ee3001dc8026cdf431ed4e2e6b51f98ab8c73484a358" +checksum = "0b7668b7cff6a51fe61cdde64cd27c8a220786f399501b57ebe36f7d8112fd68" dependencies = [ - "cssparser", - "html5ever 0.25.2", - "matches", - "selectors", + "bitflags 1.3.2", + "serde", + "unicode-segmentation", ] [[package]] @@ -1890,8 +1943,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f29e4755b7b995046f510a7520c42b2fed58b77bd94d5a87a8eb43d2fd126da8" dependencies = [ "cssparser", - "html5ever 0.26.0", - "indexmap", + "html5ever", + "indexmap 1.9.3", "matches", "selectors", ] @@ -1928,9 +1981,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.146" +version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "libloading" @@ -1952,19 +2005,16 @@ dependencies = [ ] [[package]] -name = "link-cplusplus" -version = "1.0.8" +name = "linux-raw-sys" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" -dependencies = [ - "cc", -] +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.3.8" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" [[package]] name = "lock_api" @@ -1978,9 +2028,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.18" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" dependencies = [ "value-bag", ] @@ -2015,20 +2065,6 @@ dependencies = [ "libc", ] -[[package]] -name = "markup5ever" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a24f40fb03852d1cdd84330cddcaf98e9ec08a7b7768e952fad3b4cf048ec8fd" -dependencies = [ - "log", - "phf 0.8.0", - "phf_codegen 0.8.0", - "string_cache", - "string_cache_codegen", - "tendril", -] - [[package]] name = "markup5ever" version = "0.11.0" @@ -2049,7 +2085,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" dependencies = [ - "regex-automata", + "regex-automata 0.1.10", ] [[package]] @@ -2109,13 +2145,32 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "muda" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e33f46fb20f85553edb85e30a6a5231567f4103276ccdb5a2050613d253b1d" +dependencies = [ + "cocoa 0.25.0", + "crossbeam-channel", + "gdk", + "gdk-pixbuf", + "gtk", + "keyboard-types", + "objc", + "once_cell", + "png", + "thiserror", + "windows-sys 0.48.0", +] + [[package]] name = "ndk" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0" dependencies = [ - "bitflags", + "bitflags 1.3.2", "jni-sys", "ndk-sys", "num_enum", @@ -2150,7 +2205,7 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cfg-if", "libc", "memoffset 0.7.1", @@ -2196,20 +2251,20 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" dependencies = [ "autocfg", ] [[package]] name = "num_cpus" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.2.6", + "hermit-abi", "libc", ] @@ -2271,6 +2326,15 @@ dependencies = [ "objc", ] +[[package]] +name = "object" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.18.0" @@ -2305,7 +2369,7 @@ version = "0.16.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdff66b271861037b89d028656184059e03b0b6ccb36003820be19f7200b1e94" dependencies = [ - "bitflags", + "bitflags 1.3.2", "gio", "glib", "libc", @@ -2351,7 +2415,7 @@ dependencies = [ "libc", "redox_syscall 0.3.5", "smallvec", - "windows-targets", + "windows-targets 0.48.1", ] [[package]] @@ -2470,9 +2534,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "2c516611246607d0c04186886dbb3a754368ef82c79e9827a802c6d836dd111c" [[package]] name = "pin-utils" @@ -2488,12 +2552,12 @@ checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "plist" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bd9647b268a3d3e14ff09c23201133a62589c658db02bb7388c7246aafe0590" +checksum = "bdc0001cfea3db57a2e24bc0d818e9e20e554b5f97fabb9bc231dc240269ae06" dependencies = [ "base64", - "indexmap", + "indexmap 1.9.3", "line-wrap", "quick-xml", "serde", @@ -2502,11 +2566,11 @@ dependencies = [ [[package]] name = "png" -version = "0.17.8" +version = "0.17.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaeebc51f9e7d2c150d3f3bfeb667f2aa985db5ef1e3d212847bdedb488beeaa" +checksum = "59871cc5b6cce7eaccca5a802b4173377a1c2ba90654246789a8fa2334426d11" dependencies = [ - "bitflags", + "bitflags 1.3.2", "crc32fast", "fdeflate", "flate2", @@ -2520,7 +2584,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" dependencies = [ "autocfg", - "bitflags", + "bitflags 1.3.2", "cfg-if", "concurrent-queue", "libc", @@ -2531,9 +2595,9 @@ dependencies = [ [[package]] name = "polyval" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef234e08c11dfcb2e56f79fd70f6f2eb7f025c0ce2333e82f4f0518ecad30c6" +checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb" dependencies = [ "cfg-if", "cpufeatures", @@ -2595,27 +2659,27 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.60" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] [[package]] name = "quick-xml" -version = "0.28.2" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce5e73202a820a31f8a0ee32ada5e21029c81fd9e3ebf668a40832e4219d9d1" +checksum = "81b9228215d82c7b61490fec1de287136b5de6f5700f6e58ea9ad61a7964ca51" dependencies = [ "memchr", ] [[package]] name = "quote" -version = "1.0.28" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" dependencies = [ "proc-macro2", ] @@ -2713,7 +2777,7 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -2722,7 +2786,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -2738,13 +2802,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.8.4" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" +checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.2", + "regex-automata 0.3.6", + "regex-syntax 0.7.4", ] [[package]] @@ -2756,6 +2821,17 @@ dependencies = [ "regex-syntax 0.6.29", ] +[[package]] +name = "regex-automata" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.7.4", +] + [[package]] name = "regex-syntax" version = "0.6.29" @@ -2764,9 +2840,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" [[package]] name = "reqwest" @@ -2804,6 +2880,12 @@ dependencies = [ "winreg 0.10.1", ] +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + [[package]] name = "rustc_version" version = "0.4.0" @@ -2815,29 +2897,42 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.19" +version = "0.37.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" +checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" dependencies = [ - "bitflags", + "bitflags 1.3.2", "errno", "io-lifetimes", "libc", - "linux-raw-sys", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustix" +version = "0.38.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "172891ebdceb05aa0005f533a6cbfca599ddd7d966f6f5d4d9b2e70478e70399" +dependencies = [ + "bitflags 2.3.3", + "errno", + "libc", + "linux-raw-sys 0.4.5", "windows-sys 0.48.0", ] [[package]] name = "rustversion" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "safemem" @@ -2862,15 +2957,9 @@ checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "scratch" -version = "1.0.5" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "selectors" @@ -2878,7 +2967,7 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cssparser", "derive_more", "fxhash", @@ -2894,57 +2983,57 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" [[package]] name = "serde" -version = "1.0.164" +version = "1.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" +checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.164" +version = "1.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" +checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.28", ] [[package]] name = "serde_json" -version = "1.0.96" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" dependencies = [ - "itoa 1.0.6", + "itoa 1.0.9", "ryu", "serde", ] [[package]] name = "serde_repr" -version = "0.1.12" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" +checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.28", ] [[package]] name = "serde_spanned" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93107647184f6027e3b7dcb2e11034cf95ffa1e3a682c67951963ac69c1c007d" +checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" dependencies = [ "serde", ] @@ -2956,21 +3045,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.6", + "itoa 1.0.9", "ryu", "serde", ] [[package]] name = "serde_with" -version = "3.0.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f02d8aa6e3c385bf084924f660ce2a3a6bd333ba55b35e8590b321f35d88513" +checksum = "1402f54f9a3b9e2efe71c1cea24e648acce55887983553eeb858cf3115acfd49" dependencies = [ "base64", "chrono", "hex", - "indexmap", + "indexmap 1.9.3", + "indexmap 2.0.0", "serde", "serde_json", "serde_with_macros", @@ -2979,14 +3069,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.0.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc7d5d3932fb12ce722ee5e64dd38c504efba37567f0c402f6ca728c3b8b070" +checksum = "9197f1ad0e3c173a0222d3c4404fb04c3afe87e962bcb327af73e8301fa203c7" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.28", ] [[package]] @@ -3034,9 +3124,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" dependencies = [ "cfg-if", "cpufeatures", @@ -3054,9 +3144,9 @@ dependencies = [ [[package]] name = "signal-hook" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" dependencies = [ "libc", "signal-hook-registry", @@ -3073,9 +3163,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "238abfbb77c1915110ad968465608b68e869e0772622c9656714e73e5a1a522f" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "siphasher" @@ -3094,9 +3184,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" [[package]] name = "socket2" @@ -3114,7 +3204,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82bc46048125fefd69d30b32b9d263d6556c9ffe82a7a7df181a86d912da5616" dependencies = [ - "bitflags", + "bitflags 1.3.2", "futures-channel", "gio", "glib", @@ -3197,9 +3287,9 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "swift-rs" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05e51d6f2b5fff4808614f429f8a7655ac8bcfe218185413f3a60c508482c2d6" +checksum = "1bbdb58577b6301f8d17ae2561f32002a5bae056d444e0f69e611e504a276204" dependencies = [ "base64", "serde", @@ -3219,9 +3309,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.18" +version = "2.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" +checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" dependencies = [ "proc-macro2", "quote", @@ -3230,9 +3320,9 @@ dependencies = [ [[package]] name = "system-deps" -version = "6.1.0" +version = "6.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5fa6fb9ee296c0dc2df41a656ca7948546d061958115ddb0bcaae43ad0d17d2" +checksum = "30c2de8a4d8f4b823d634affc9cd2a74ec98c53a756f317e529a48046cbf71f3" dependencies = [ "cfg-expr", "heck", @@ -3243,18 +3333,17 @@ dependencies = [ [[package]] name = "tao" -version = "0.21.1" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436fb014010f6c87561125b14add8a6091354681f190bb9ffeb42819af9218a4" +checksum = "60279ecb16c33a6cef45cd37a9602455c190942d20e360bd8499bff49f2a48f3" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cairo-rs", "cc", - "cocoa", + "cocoa 0.24.1", "core-foundation", - "core-graphics", + "core-graphics 0.22.3", "crossbeam-channel", - "dirs-next", "dispatch", "gdk", "gdk-pixbuf", @@ -3269,7 +3358,6 @@ dependencies = [ "instant", "jni", "lazy_static", - "libappindicator", "libc", "log", "ndk", @@ -3294,9 +3382,9 @@ dependencies = [ [[package]] name = "tao-macros" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b27a4bcc5eb524658234589bdffc7e7bfb996dbae6ce9393bfd39cb4159b445" +checksum = "ec114582505d158b669b136e6851f85840c109819d77c42bb7c0709f727d18c2" dependencies = [ "proc-macro2", "quote", @@ -3305,9 +3393,9 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.7" +version = "0.12.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd1ba337640d60c3e96bc6f0638a939b9c9a7f2c316a1598c279828b3d1dc8c5" +checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a" [[package]] name = "tauri" @@ -3315,7 +3403,7 @@ version = "2.0.0-alpha.10" dependencies = [ "anyhow", "bytes", - "cocoa", + "cocoa 0.24.1", "dirs-next", "embed_plist", "futures-util", @@ -3330,6 +3418,7 @@ dependencies = [ "libc", "log", "mime", + "muda", "objc", "once_cell", "percent-encoding", @@ -3350,6 +3439,7 @@ dependencies = [ "tauri-utils", "thiserror", "tokio", + "tray-icon", "url", "uuid", "webkit2gtk", @@ -3433,7 +3523,7 @@ source = "git+https://github.com/tauri-apps/plugins-workspace?branch=next#4a10f2 dependencies = [ "android_logger", "byte-unit", - "cocoa", + "cocoa 0.24.1", "fern", "log", "objc", @@ -3480,7 +3570,7 @@ dependencies = [ name = "tauri-runtime-wry" version = "0.13.0-alpha.6" dependencies = [ - "cocoa", + "cocoa 0.24.1", "gtk", "jni", "percent-encoding", @@ -3506,7 +3596,7 @@ dependencies = [ "getrandom 0.2.10", "glob", "heck", - "html5ever 0.26.0", + "html5ever", "infer 0.12.0", "json-patch", "kuchikiki", @@ -3537,15 +3627,14 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.6.0" +version = "3.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" +checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651" dependencies = [ - "autocfg", "cfg-if", - "fastrand", + "fastrand 2.0.0", "redox_syscall 0.3.5", - "rustix", + "rustix 0.38.7", "windows-sys 0.48.0", ] @@ -3560,15 +3649,6 @@ dependencies = [ "utf-8", ] -[[package]] -name = "termcolor" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" -dependencies = [ - "winapi-util", -] - [[package]] name = "thin-slice" version = "0.1.1" @@ -3577,22 +3657,22 @@ checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" [[package]] name = "thiserror" -version = "1.0.40" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.40" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.28", ] [[package]] @@ -3607,11 +3687,12 @@ dependencies = [ [[package]] name = "time" -version = "0.3.22" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd" +checksum = "b0fdd63d58b18d663fbdf70e049f00a22c8e42be082203be7f26589213cd75ea" dependencies = [ - "itoa 1.0.6", + "deranged", + "itoa 1.0.9", "libc", "num_threads", "serde", @@ -3627,9 +3708,9 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" +checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd" dependencies = [ "time-core", ] @@ -3664,11 +3745,12 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.28.2" +version = "1.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" +checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" dependencies = [ "autocfg", + "backtrace", "bytes", "libc", "mio", @@ -3694,9 +3776,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.7.3" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21" +checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" dependencies = [ "serde", "serde_spanned", @@ -3706,20 +3788,20 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.19.8" +version = "0.19.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" +checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" dependencies = [ - "indexmap", + "indexmap 2.0.0", "serde", "serde_spanned", "toml_datetime", @@ -3746,13 +3828,13 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.24" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" +checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.28", ] [[package]] @@ -3794,6 +3876,25 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "tray-icon" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b0e5bec13da15e62330e9bcf8b9fd42489b5acfe29ac8fec7ed659dbee21d9" +dependencies = [ + "cocoa 0.25.0", + "core-graphics 0.23.1", + "crossbeam-channel", + "dirs-next", + "libappindicator", + "muda", + "objc", + "once_cell", + "png", + "thiserror", + "windows-sys 0.48.0", +] + [[package]] name = "treediff" version = "4.0.2" @@ -3833,9 +3934,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "unicode-normalization" @@ -3852,12 +3953,6 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" -[[package]] -name = "unicode-width" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" - [[package]] name = "universal-hash" version = "0.5.1" @@ -3900,9 +3995,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.3.3" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2" +checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" dependencies = [ "getrandom 0.2.10", ] @@ -3915,9 +4010,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "value-bag" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4d330786735ea358f3bc09eea4caa098569c1c93f342d9aca0514915022fe7e" +checksum = "d92ccd67fb88503048c01b59152a04effd0782d035a83a6d256ce6085f08f4a3" [[package]] name = "version-compare" @@ -3969,11 +4064,10 @@ dependencies = [ [[package]] name = "want" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "log", "try-lock", ] @@ -3991,9 +4085,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -4001,24 +4095,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.28", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.36" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d1985d03709c53167ce907ff394f5316aa22cb4e12761295c5dc57dacb6297e" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" dependencies = [ "cfg-if", "js-sys", @@ -4028,9 +4122,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4038,22 +4132,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.28", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "wasm-streams" @@ -4070,9 +4164,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", @@ -4084,7 +4178,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ba4cce9085e0fb02575cfd45c328740dde78253cba516b1e8be2ca0f57bd8bf" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cairo-rs", "gdk", "gdk-sys", @@ -4108,7 +4202,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4489eb24e8cf0a3d0555fd3a8f7adec2a5ece34c1e7b7c9a62da7822fd40a59" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cairo-sys-rs", "gdk-sys", "gio-sys", @@ -4143,7 +4237,7 @@ checksum = "ac1345798ecd8122468840bcdf1b95e5dc6d2206c5e4b0eafa078d061f59c9bc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.28", ] [[package]] @@ -4198,7 +4292,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29d30320647cfc3dc45554c8ad825b84831def81f967a2f7589931328ff9b16d" dependencies = [ - "cocoa", + "cocoa 0.24.1", "objc", "raw-window-handle", "windows-sys 0.42.0", @@ -4212,7 +4306,7 @@ checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ "windows-implement", "windows-interface", - "windows-targets", + "windows-targets 0.48.1", ] [[package]] @@ -4268,20 +4362,44 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.1", ] [[package]] name = "windows-targets" -version = "0.48.0" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" dependencies = [ "windows_aarch64_gnullvm 0.48.0", "windows_aarch64_msvc 0.48.0", @@ -4384,9 +4502,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winnow" -version = "0.4.6" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699" +checksum = "acaaa1190073b2b101e15083c38ee8ec891b5e05cbee516521e94ec008f61e64" dependencies = [ "memchr", ] @@ -4412,24 +4530,24 @@ dependencies = [ [[package]] name = "wry" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "430d086d4626265e9427fe2908a06fb2e10ea2ff37d4a711eb9461b6ea3511dd" +checksum = "4c6289018fa3cbc051c13f4ae1a102d80c3f35a527456c75567eb2cad6989020" dependencies = [ "base64", "block", - "cocoa", - "core-graphics", + "cocoa 0.24.1", + "core-graphics 0.22.3", "crossbeam-channel", "dunce", "gdk", "gio", "glib", "gtk", - "html5ever 0.25.2", + "html5ever", "http", "javascriptcore-rs", - "kuchiki", + "kuchikiki", "libc", "log", "objc", diff --git a/examples/api/src-tauri/Cargo.toml b/examples/api/src-tauri/Cargo.toml index 8bd1e11634e7..a70dda3e2374 100644 --- a/examples/api/src-tauri/Cargo.toml +++ b/examples/api/src-tauri/Cargo.toml @@ -40,7 +40,7 @@ features = [ "icon-png", "isolation", "macos-private-api", - "system-tray" + "tray-icon" ] [dev-dependencies.tauri] diff --git a/examples/api/src-tauri/src/cmd.rs b/examples/api/src-tauri/src/cmd.rs index e013e50a3add..2c92decf7724 100644 --- a/examples/api/src-tauri/src/cmd.rs +++ b/examples/api/src-tauri/src/cmd.rs @@ -29,3 +29,37 @@ pub fn perform_request(endpoint: String, body: RequestBody) -> ApiResponse { message: "message response".into(), } } + +#[cfg(all(desktop, not(target_os = "macos")))] +#[command] +pub fn toggle_menu(window: tauri::Window) { + if window.is_menu_visible().unwrap_or_default() { + let _ = window.hide_menu(); + } else { + let _ = window.show_menu(); + } +} + +#[cfg(target_os = "macos")] +#[command] +pub fn toggle_menu( + app: tauri::AppHandle, + app_menu: tauri::State<'_, crate::AppMenu>, +) { + if let Some(menu) = app.remove_menu().unwrap() { + app_menu.0.lock().unwrap().replace(menu); + } else { + app + .set_menu(app_menu.0.lock().unwrap().clone().expect("no app menu")) + .unwrap(); + } +} + +#[cfg(desktop)] +#[command] +pub fn popup_context_menu( + window: tauri::Window, + popup_menu: tauri::State<'_, crate::PopupMenu>, +) { + window.popup_menu(&popup_menu.0).unwrap(); +} diff --git a/examples/api/src-tauri/src/lib.rs b/examples/api/src-tauri/src/lib.rs index 0cb1a5a629d2..8af633098e91 100644 --- a/examples/api/src-tauri/src/lib.rs +++ b/examples/api/src-tauri/src/lib.rs @@ -15,13 +15,22 @@ use serde::Serialize; use tauri::{ipc::Channel, window::WindowBuilder, App, AppHandle, RunEvent, Runtime, WindowUrl}; use tauri_plugin_sample::{PingRequest, SampleExt}; +#[cfg(desktop)] +use tauri::Manager; + +pub type SetupHook = Box Result<(), Box> + Send>; +pub type OnEvent = Box; + #[derive(Clone, Serialize)] struct Reply { data: String, } -pub type SetupHook = Box Result<(), Box> + Send>; -pub type OnEvent = Box; +#[cfg(target_os = "macos")] +pub struct AppMenu(pub std::sync::Mutex>>); + +#[cfg(desktop)] +pub struct PopupMenu(tauri::menu::Menu); #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { @@ -43,11 +52,23 @@ pub fn run_app) + Send + 'static>( .setup(move |app| { #[cfg(desktop)] { - tray::create_tray(app)?; - - app.handle().plugin(tauri_plugin_cli::init())?; + let handle = app.handle(); + tray::create_tray(&handle)?; + handle.plugin(tauri_plugin_cli::init())?; } + #[cfg(target_os = "macos")] + app.manage(AppMenu::(Default::default())); + + #[cfg(desktop)] + app.manage(PopupMenu( + tauri::menu::MenuBuilder::new(app) + .check("Tauri is awesome!") + .text("Do something") + .copy() + .build()?, + )); + let mut window_builder = WindowBuilder::new(app, "main", WindowUrl::default()); #[cfg(desktop)] { @@ -55,7 +76,8 @@ pub fn run_app) + Send + 'static>( .title("Tauri API Validation") .inner_size(1000., 800.) .min_inner_size(600., 400.) - .content_protected(true); + .content_protected(true) + .menu(tauri::menu::Menu::default(&app.handle())?); } let window = window_builder.build().unwrap(); @@ -119,16 +141,15 @@ pub fn run_app) + Send + 'static>( }); }); - #[cfg(target_os = "macos")] - { - builder = builder.menu(tauri::Menu::os_default("Tauri API Validation")); - } - #[allow(unused_mut)] let mut app = builder .invoke_handler(tauri::generate_handler![ cmd::log_operation, cmd::perform_request, + #[cfg(desktop)] + cmd::toggle_menu, + #[cfg(desktop)] + cmd::popup_context_menu ]) .build(tauri::tauri_build_context!()) .expect("error while building tauri application"); @@ -140,7 +161,7 @@ pub fn run_app) + Send + 'static>( #[cfg(all(desktop, not(test)))] if let RunEvent::ExitRequested { api, .. } = &_event { // Keep the event loop running even if all windows are closed - // This allow us to catch system tray events when there is no window + // This allow us to catch tray icon events when there is no window api.prevent_exit(); } }) diff --git a/examples/api/src-tauri/src/tray.rs b/examples/api/src-tauri/src/tray.rs index 96a8bbd9aef8..698255061ca5 100644 --- a/examples/api/src-tauri/src/tray.rs +++ b/examples/api/src-tauri/src/tray.rs @@ -4,121 +4,113 @@ use std::sync::atomic::{AtomicBool, Ordering}; use tauri::{ - CustomMenuItem, Manager, Runtime, SystemTray, SystemTrayEvent, SystemTrayMenu, WindowBuilder, - WindowUrl, + menu::{Menu, MenuItem}, + tray::{ClickType, TrayIconBuilder}, + Manager, Runtime, WindowBuilder, WindowUrl, }; -pub fn create_tray(app: &tauri::App) -> tauri::Result<()> { - let mut tray_menu1 = SystemTrayMenu::new() - .add_item(CustomMenuItem::new("toggle", "Toggle")) - .add_item(CustomMenuItem::new("new", "New window")) - .add_item(CustomMenuItem::new("icon_1", "Tray Icon 1")) - .add_item(CustomMenuItem::new("icon_2", "Tray Icon 2")); - +pub fn create_tray(app: &tauri::AppHandle) -> tauri::Result<()> { + let toggle_i = MenuItem::with_id(app, "toggle", "Toggle", true, None); + let new_window_i = MenuItem::with_id(app, "new-window", "New window", true, None); + let icon_i_1 = MenuItem::with_id(app, "icon-1", "Icon 1", true, None); + let icon_i_2 = MenuItem::with_id(app, "icon-2", "Icon 2", true, None); #[cfg(target_os = "macos")] - { - tray_menu1 = tray_menu1.add_item(CustomMenuItem::new("set_title", "Set Title")); - } - - tray_menu1 = tray_menu1 - .add_item(CustomMenuItem::new("switch_menu", "Switch Menu")) - .add_item(CustomMenuItem::new("exit_app", "Quit")) - .add_item(CustomMenuItem::new("destroy", "Destroy")); + let set_title_i = MenuItem::with_id(app, "set-title", "Set Title", true, None); + let switch_i = MenuItem::with_id(app, "switch-menu", "Switch Menu", true, None); + let quit_i = MenuItem::with_id(app, "quit", "Quit", true, None); + let remove_tray_i = MenuItem::with_id(app, "remove-tray", "Remove Tray icon", true, None); + let menu1 = Menu::with_items( + app, + &[ + &toggle_i, + &new_window_i, + &icon_i_1, + &icon_i_2, + #[cfg(target_os = "macos")] + &set_title_i, + &switch_i, + &quit_i, + &remove_tray_i, + ], + )?; + let menu2 = Menu::with_items( + app, + &[&toggle_i, &new_window_i, &switch_i, &quit_i, &remove_tray_i], + )?; - let tray_menu2 = SystemTrayMenu::new() - .add_item(CustomMenuItem::new("toggle", "Toggle")) - .add_item(CustomMenuItem::new("new", "New window")) - .add_item(CustomMenuItem::new("switch_menu", "Switch Menu")) - .add_item(CustomMenuItem::new("exit_app", "Quit")) - .add_item(CustomMenuItem::new("destroy", "Destroy")); let is_menu1 = AtomicBool::new(true); - let handle = app.handle(); - let tray_id = "my-tray".to_string(); - SystemTray::new() - .with_id(&tray_id) - .with_menu(tray_menu1.clone()) - .with_tooltip("Tauri") - .on_event(move |event| { - let tray_handle = handle.tray_handle_by_id(&tray_id).unwrap(); - match event { - SystemTrayEvent::LeftClick { - position: _, - size: _, - .. - } => { - let window = handle.get_window("main").unwrap(); - window.show().unwrap(); - window.set_focus().unwrap(); + let _ = TrayIconBuilder::with_id("tray-1") + .tooltip("Tauri") + .icon(app.default_window_icon().unwrap().clone()) + .menu(&menu1) + .menu_on_left_click(false) + .on_menu_event(move |app, event| match event.id.as_ref() { + "quit" => { + app.exit(0); + } + "remove-tray" => { + app.remove_tray_by_id("tray-1"); + } + "toggle" => { + if let Some(window) = app.get_window("main") { + let new_title = if window.is_visible().unwrap_or_default() { + let _ = window.hide(); + "Show" + } else { + let _ = window.show(); + let _ = window.set_focus(); + "Hide" + }; + toggle_i.set_text(new_title).unwrap(); } - SystemTrayEvent::MenuItemClick { id, .. } => { - let item_handle = tray_handle.get_item(&id); - match id.as_str() { - "exit_app" => { - // exit the app - handle.exit(0); - } - "destroy" => { - tray_handle.destroy().unwrap(); - } - "toggle" => { - let window = handle.get_window("main").unwrap(); - let new_title = if window.is_visible().unwrap() { - window.hide().unwrap(); - "Show" - } else { - window.show().unwrap(); - "Hide" - }; - item_handle.set_title(new_title).unwrap(); - } - "new" => { - WindowBuilder::new(&handle, "new", WindowUrl::App("index.html".into())) - .title("Tauri") - .build() - .unwrap(); - } - "set_title" => { - #[cfg(target_os = "macos")] - tray_handle.set_title("Tauri").unwrap(); - } - "icon_1" => { - #[cfg(target_os = "macos")] - tray_handle.set_icon_as_template(true).unwrap(); - - tray_handle - .set_icon(tauri::Icon::Raw( - include_bytes!("../../../.icons/tray_icon_with_transparency.png").to_vec(), - )) - .unwrap(); - } - "icon_2" => { - #[cfg(target_os = "macos")] - tray_handle.set_icon_as_template(true).unwrap(); + } + "new-window" => { + let _ = WindowBuilder::new(app, "new", WindowUrl::App("index.html".into())) + .title("Tauri") + .build(); + } + #[cfg(target_os = "macos")] + "set-title" => { + if let Some(tray) = app.tray_by_id("tray-1") { + let _ = tray.set_title(Some("Tauri")); + } + } + i @ "icon-1" | i @ "icon-2" => { + if let Some(tray) = app.tray_by_id("tray-1") { + let _ = tray.set_icon(Some(tauri::Icon::Raw(if i == "icon-1" { + include_bytes!("../../../.icons/icon.ico").to_vec() + } else { + include_bytes!("../../../.icons/tray_icon_with_transparency.png").to_vec() + }))); + } + } + "switch-menu" => { + let flag = is_menu1.load(Ordering::Relaxed); + let (menu, tooltip) = if flag { + (menu2.clone(), "Menu 2") + } else { + (menu1.clone(), "Tauri") + }; + if let Some(tray) = app.tray_by_id("tray-1") { + let _ = tray.set_menu(Some(menu)); + let _ = tray.set_tooltip(Some(tooltip)); + } + is_menu1.store(!flag, Ordering::Relaxed); + } - tray_handle - .set_icon(tauri::Icon::Raw( - include_bytes!("../../../.icons/icon.ico").to_vec(), - )) - .unwrap(); - } - "switch_menu" => { - let flag = is_menu1.load(Ordering::Relaxed); - let (menu, tooltip) = if flag { - (tray_menu2.clone(), "Menu 2") - } else { - (tray_menu1.clone(), "Tauri") - }; - tray_handle.set_menu(menu).unwrap(); - tray_handle.set_tooltip(tooltip).unwrap(); - is_menu1.store(!flag, Ordering::Relaxed); - } - _ => {} - } + _ => {} + }) + .on_tray_event(|tray, event| { + if event.click_type == ClickType::Left { + let app = tray.app_handle(); + if let Some(window) = app.get_window("main") { + let _ = window.show(); + let _ = window.set_focus(); } - _ => {} } }) - .build(app) - .map(|_| ()) + .build(app); + + Ok(()) } diff --git a/examples/api/src-tauri/tauri.conf.json b/examples/api/src-tauri/tauri.conf.json index 74c601116bea..784733dd675f 100644 --- a/examples/api/src-tauri/tauri.conf.json +++ b/examples/api/src-tauri/tauri.conf.json @@ -112,11 +112,6 @@ ] } } - }, - "systemTray": { - "iconPath": "../../.icons/tray_icon_with_transparency.png", - "iconAsTemplate": true, - "menuOnLeftClick": false } } } diff --git a/examples/api/src/App.svelte b/examples/api/src/App.svelte index 264e9a0bed2b..2d85287b551e 100644 --- a/examples/api/src/App.svelte +++ b/examples/api/src/App.svelte @@ -1,13 +1,17 @@ + +
+

+ This is a demo of Tauri's API capabilities using the @tauri-apps/api package. It's used as the main validation app, serving as the test bed of our + development process. In the future, this app will be used on Tauri's integration + tests. +

+ + +
diff --git a/examples/splashscreen/main.rs b/examples/splashscreen/main.rs index 5b6e9d7b6694..4c3cbc86647f 100644 --- a/examples/splashscreen/main.rs +++ b/examples/splashscreen/main.rs @@ -61,11 +61,7 @@ mod ui { pub fn main() { let context = super::context(); tauri::Builder::default() - .menu(if cfg!(target_os = "macos") { - tauri::Menu::os_default(&context.package_info().name) - } else { - tauri::Menu::default() - }) + .menu(tauri::menu::Menu::default) .setup(|app| { // set the splashscreen and main windows to be globally available with the tauri state API app.manage(SplashscreenWindow(Arc::new(Mutex::new( diff --git a/tooling/cli/Cargo.lock b/tooling/cli/Cargo.lock index 4b3fba4c355b..e5de1fa9dac7 100644 --- a/tooling/cli/Cargo.lock +++ b/tooling/cli/Cargo.lock @@ -299,9 +299,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.3.1" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6776fc96284a0bb647b615056fc496d1fe1644a7ab01829818a6d91cae888b84" +checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" [[package]] name = "bitness" @@ -1105,12 +1105,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "1.9.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" [[package]] name = "fdeflate" @@ -1674,9 +1671,9 @@ dependencies = [ [[package]] name = "image" -version = "0.24.6" +version = "0.24.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "527909aa81e20ac3a44803521443a765550f09b5130c2c2fa1ea59c2f8f50a3a" +checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711" dependencies = [ "bytemuck", "byteorder", @@ -1759,15 +1756,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - [[package]] name = "io-lifetimes" version = "1.0.11" @@ -1793,7 +1781,7 @@ checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" dependencies = [ "hermit-abi 0.3.1", "io-lifetimes", - "rustix", + "rustix 0.37.19", "windows-sys 0.48.0", ] @@ -2085,9 +2073,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.144" +version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "libflate" @@ -2138,6 +2126,12 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +[[package]] +name = "linux-raw-sys" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" + [[package]] name = "local-ip-address" version = "0.4.9" @@ -2314,7 +2308,7 @@ version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ede2d12cd6fce44da537a4be1f5510c73be2506c2e32dfaaafd1f36968f3a0e" dependencies = [ - "bitflags 2.3.1", + "bitflags 2.3.3", "ctor 0.2.0", "napi-derive", "napi-sys", @@ -3261,7 +3255,20 @@ dependencies = [ "errno", "io-lifetimes", "libc", - "linux-raw-sys", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustix" +version = "0.38.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "172891ebdceb05aa0005f533a6cbfca599ddd7d966f6f5d4d9b2e70478e70399" +dependencies = [ + "bitflags 2.3.3", + "errno", + "libc", + "linux-raw-sys 0.4.5", "windows-sys 0.48.0", ] @@ -3907,9 +3914,9 @@ dependencies = [ [[package]] name = "tar" -version = "0.4.39" +version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec96d2ffad078296368d46ff1cb309be1c23c513b4ab0e22a45de0185275ac96" +checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb" dependencies = [ "filetime", "libc", @@ -4142,15 +4149,14 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.6.0" +version = "3.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" +checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651" dependencies = [ - "autocfg", "cfg-if", "fastrand", "redox_syscall 0.3.5", - "rustix", + "rustix 0.38.7", "windows-sys 0.48.0", ] @@ -4232,9 +4238,9 @@ dependencies = [ [[package]] name = "tiff" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7449334f9ff2baf290d55d73983a7d6fa15e01198faef72af07e2a8db851e471" +checksum = "6d172b0f4d3fba17ba89811858b9d3d97f928aece846475bbda076ca46736211" dependencies = [ "flate2", "jpeg-decoder", @@ -5099,9 +5105,9 @@ dependencies = [ [[package]] name = "xattr" -version = "0.2.3" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" +checksum = "f4686009f71ff3e5c4dbcf1a282d0a44db3f021ba69350cd42086b3e5f1c6985" dependencies = [ "libc", ] diff --git a/tooling/cli/schema.json b/tooling/cli/schema.json index 6718f779f62e..24bb780ddf69 100644 --- a/tooling/cli/schema.json +++ b/tooling/cli/schema.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Config", - "description": "The Tauri configuration object. It is read from a file where you can define your frontend assets, configure the bundler and define a system tray.\n\nThe configuration file is generated by the [`tauri init`](https://tauri.app/v1/api/cli#init) command that lives in your Tauri application source directory (src-tauri).\n\nOnce generated, you may modify it at will to customize your Tauri application.\n\n## File Formats\n\nBy default, the configuration is defined as a JSON file named `tauri.conf.json`.\n\nTauri also supports JSON5 and TOML files via the `config-json5` and `config-toml` Cargo features, respectively. The JSON5 file name must be either `tauri.conf.json` or `tauri.conf.json5`. The TOML file name is `Tauri.toml`.\n\n## Platform-Specific Configuration\n\nIn addition to the default configuration file, Tauri can read a platform-specific configuration from `tauri.linux.conf.json`, `tauri.windows.conf.json`, `tauri.macos.conf.json`, `tauri.android.conf.json` and `tauri.ios.conf.json` (or `Tauri.linux.toml`, `Tauri.windows.toml`, `Tauri.macos.toml`, `Tauri.android.toml` and `Tauri.ios.toml` if the `Tauri.toml` format is used), which gets merged with the main configuration object.\n\n## Configuration Structure\n\nThe configuration is composed of the following objects:\n\n- [`package`](#packageconfig): Package settings - [`tauri`](#tauriconfig): The Tauri config - [`build`](#buildconfig): The build configuration - [`plugins`](#pluginconfig): The plugins config\n\n```json title=\"Example tauri.config.json file\" { \"build\": { \"beforeBuildCommand\": \"\", \"beforeDevCommand\": \"\", \"devPath\": \"../dist\", \"distDir\": \"../dist\" }, \"package\": { \"productName\": \"tauri-app\", \"version\": \"0.1.0\" }, \"tauri\": { \"bundle\": {}, \"security\": { \"csp\": null }, \"windows\": [ { \"fullscreen\": false, \"height\": 600, \"resizable\": true, \"title\": \"Tauri App\", \"width\": 800 } ] } } ```", + "description": "The Tauri configuration object. It is read from a file where you can define your frontend assets, configure the bundler and define a tray icon.\n\nThe configuration file is generated by the [`tauri init`](https://tauri.app/v1/api/cli#init) command that lives in your Tauri application source directory (src-tauri).\n\nOnce generated, you may modify it at will to customize your Tauri application.\n\n## File Formats\n\nBy default, the configuration is defined as a JSON file named `tauri.conf.json`.\n\nTauri also supports JSON5 and TOML files via the `config-json5` and `config-toml` Cargo features, respectively. The JSON5 file name must be either `tauri.conf.json` or `tauri.conf.json5`. The TOML file name is `Tauri.toml`.\n\n## Platform-Specific Configuration\n\nIn addition to the default configuration file, Tauri can read a platform-specific configuration from `tauri.linux.conf.json`, `tauri.windows.conf.json`, `tauri.macos.conf.json`, `tauri.android.conf.json` and `tauri.ios.conf.json` (or `Tauri.linux.toml`, `Tauri.windows.toml`, `Tauri.macos.toml`, `Tauri.android.toml` and `Tauri.ios.toml` if the `Tauri.toml` format is used), which gets merged with the main configuration object.\n\n## Configuration Structure\n\nThe configuration is composed of the following objects:\n\n- [`package`](#packageconfig): Package settings - [`tauri`](#tauriconfig): The Tauri config - [`build`](#buildconfig): The build configuration - [`plugins`](#pluginconfig): The plugins config\n\n```json title=\"Example tauri.config.json file\" { \"build\": { \"beforeBuildCommand\": \"\", \"beforeDevCommand\": \"\", \"devPath\": \"../dist\", \"distDir\": \"../dist\" }, \"package\": { \"productName\": \"tauri-app\", \"version\": \"0.1.0\" }, \"tauri\": { \"bundle\": {}, \"security\": { \"csp\": null }, \"windows\": [ { \"fullscreen\": false, \"height\": 600, \"resizable\": true, \"title\": \"Tauri App\", \"width\": 800 } ] } } ```", "type": "object", "properties": { "$schema": { @@ -223,11 +223,11 @@ } ] }, - "systemTray": { - "description": "Configuration for app system tray.", + "trayIcon": { + "description": "Configuration for app tray icon.", "anyOf": [ { - "$ref": "#/definitions/SystemTrayConfig" + "$ref": "#/definitions/TrayIconConfig" }, { "type": "null" @@ -2065,15 +2065,15 @@ } ] }, - "SystemTrayConfig": { - "description": "Configuration for application system tray icon.\n\nSee more: https://tauri.app/v1/api/config#systemtrayconfig", + "TrayIconConfig": { + "description": "Configuration for application tray icon.\n\nSee more: https://tauri.app/v1/api/config#trayiconconfig", "type": "object", "required": [ "iconPath" ], "properties": { "iconPath": { - "description": "Path to the default icon to use on the system tray.", + "description": "Path to the default icon to use for the tray icon.", "type": "string" }, "iconAsTemplate": { @@ -2092,6 +2092,13 @@ "string", "null" ] + }, + "tooltip": { + "description": "Tray icon tooltip on Windows and macOS", + "type": [ + "string", + "null" + ] } }, "additionalProperties": false diff --git a/tooling/cli/src/build.rs b/tooling/cli/src/build.rs index fd65e245afcc..74d33d3bf3af 100644 --- a/tooling/cli/src/build.rs +++ b/tooling/cli/src/build.rs @@ -143,31 +143,6 @@ pub fn command(mut options: Options, verbosity: u8) -> Result<()> { // set env vars used by the bundler #[cfg(target_os = "linux")] { - if config_.tauri.system_tray.is_some() { - if let Ok(tray) = std::env::var("TAURI_TRAY") { - std::env::set_var( - "TRAY_LIBRARY_PATH", - if tray == "ayatana" { - format!( - "{}/libayatana-appindicator3.so.1", - pkgconfig_utils::get_library_path("ayatana-appindicator3-0.1") - .expect("failed to get ayatana-appindicator library path using pkg-config.") - ) - } else { - format!( - "{}/libappindicator3.so.1", - pkgconfig_utils::get_library_path("appindicator3-0.1") - .expect("failed to get libappindicator-gtk library path using pkg-config.") - ) - }, - ); - } else { - std::env::set_var( - "TRAY_LIBRARY_PATH", - pkgconfig_utils::get_appindicator_library_path(), - ); - } - } if config_.tauri.bundle.appimage.bundle_media_framework { std::env::set_var("APPIMAGE_BUNDLE_GSTREAMER", "1"); } @@ -394,37 +369,3 @@ fn print_signed_updater_archive(output_paths: &[PathBuf]) -> crate::Result<()> { } Ok(()) } - -#[cfg(target_os = "linux")] -mod pkgconfig_utils { - use std::{path::PathBuf, process::Command}; - - pub fn get_appindicator_library_path() -> PathBuf { - match get_library_path("ayatana-appindicator3-0.1") { - Some(p) => format!("{p}/libayatana-appindicator3.so.1").into(), - None => match get_library_path("appindicator3-0.1") { - Some(p) => format!("{p}/libappindicator3.so.1").into(), - None => panic!("Can't detect any appindicator library"), - }, - } - } - - /// Gets the folder in which a library is located using `pkg-config`. - pub fn get_library_path(name: &str) -> Option { - let mut cmd = Command::new("pkg-config"); - cmd.env("PKG_CONFIG_ALLOW_SYSTEM_LIBS", "1"); - cmd.arg("--libs-only-L"); - cmd.arg(name); - if let Ok(output) = cmd.output() { - if !output.stdout.is_empty() { - // output would be "-L/path/to/library\n" - let word = output.stdout[2..].to_vec(); - return Some(String::from_utf8_lossy(&word).trim().to_string()); - } else { - None - } - } else { - None - } - } -} diff --git a/tooling/cli/src/interface/rust.rs b/tooling/cli/src/interface/rust.rs index 2c91db36dc45..239a2a48ed10 100644 --- a/tooling/cli/src/interface/rust.rs +++ b/tooling/cli/src/interface/rust.rs @@ -697,12 +697,7 @@ impl AppSettings for RustAppSettings { config: &Config, features: &[String], ) -> crate::Result { - tauri_config_to_bundle_settings( - &self.manifest, - features, - config.tauri.bundle.clone(), - config.tauri.system_tray.clone(), - ) + tauri_config_to_bundle_settings(&self.manifest, features, config.tauri.bundle.clone()) } fn app_binary_path(&self, options: &Options) -> crate::Result { @@ -1045,7 +1040,6 @@ fn tauri_config_to_bundle_settings( manifest: &Manifest, features: &[String], config: crate::helpers::config::BundleConfig, - system_tray_config: Option, ) -> crate::Result { let enabled_features = manifest.all_enabled_features(features); @@ -1066,15 +1060,45 @@ fn tauri_config_to_bundle_settings( #[allow(unused_mut)] let mut depends = config.deb.depends.unwrap_or_default(); + // set env vars used by the bundler and inject dependencies #[cfg(target_os = "linux")] { - if let Some(system_tray_config) = &system_tray_config { - let tray = std::env::var("TAURI_TRAY").unwrap_or_else(|_| "ayatana".to_string()); - if tray == "ayatana" { - depends.push("libayatana-appindicator3-1".into()); - } else { - depends.push("libappindicator3-1".into()); + if enabled_features.contains(&"tray-icon".into()) + || enabled_features.contains(&"tauri/tray-icon".into()) + { + let (tray_kind, path) = std::env::var("TAURI_TRAY") + .map(|kind| { + if kind == "ayatana" { + ( + pkgconfig_utils::TrayKind::Ayatana, + format!( + "{}/libayatana-appindicator3.so.1", + pkgconfig_utils::get_library_path("ayatana-appindicator3-0.1") + .expect("failed to get ayatana-appindicator library path using pkg-config.") + ), + ) + } else { + ( + pkgconfig_utils::TrayKind::Libappindicator, + format!( + "{}/libappindicator3.so.1", + pkgconfig_utils::get_library_path("appindicator3-0.1") + .expect("failed to get libappindicator-gtk library path using pkg-config.") + ), + ) + } + }) + .unwrap_or_else(|_| pkgconfig_utils::get_appindicator_library_path()); + match tray_kind { + pkgconfig_utils::TrayKind::Ayatana => { + depends.push("libayatana-appindicator3-1".into()); + } + pkgconfig_utils::TrayKind::Libappindicator => { + depends.push("libappindicator3-1".into()); + } } + + std::env::set_var("TRAY_LIBRARY_PATH", path); } // provides `libwebkit2gtk-4.1.so.37` and all `4.0` versions have the -37 package name @@ -1184,3 +1208,48 @@ fn tauri_config_to_bundle_settings( ..Default::default() }) } + +#[cfg(target_os = "linux")] +mod pkgconfig_utils { + use std::process::Command; + + pub enum TrayKind { + Ayatana, + Libappindicator, + } + + pub fn get_appindicator_library_path() -> (TrayKind, String) { + match get_library_path("ayatana-appindicator3-0.1") { + Some(p) => ( + TrayKind::Ayatana, + format!("{p}/libayatana-appindicator3.so.1"), + ), + None => match get_library_path("appindicator3-0.1") { + Some(p) => ( + TrayKind::Libappindicator, + format!("{p}/libappindicator3.so.1"), + ), + None => panic!("Can't detect any appindicator library"), + }, + } + } + + /// Gets the folder in which a library is located using `pkg-config`. + pub fn get_library_path(name: &str) -> Option { + let mut cmd = Command::new("pkg-config"); + cmd.env("PKG_CONFIG_ALLOW_SYSTEM_LIBS", "1"); + cmd.arg("--libs-only-L"); + cmd.arg(name); + if let Ok(output) = cmd.output() { + if !output.stdout.is_empty() { + // output would be "-L/path/to/library\n" + let word = output.stdout[2..].to_vec(); + return Some(String::from_utf8_lossy(&word).trim().to_string()); + } else { + None + } + } else { + None + } + } +} diff --git a/tooling/cli/src/migrate/config.rs b/tooling/cli/src/migrate/config.rs index a9ea92327b91..a6c86bebf4a4 100644 --- a/tooling/cli/src/migrate/config.rs +++ b/tooling/cli/src/migrate/config.rs @@ -54,6 +54,10 @@ fn migrate_config(config: &mut Value) -> Result<()> { process_security(security)?; } + if let Some(tray) = tauri_config.remove("systemTray") { + tauri_config.insert("trayIcon".into(), tray); + } + // cli if let Some(cli) = tauri_config.remove("cli") { process_cli(&mut plugins, cli)?; diff --git a/tooling/cli/src/migrate/manifest.rs b/tooling/cli/src/migrate/manifest.rs index d8bf3c48580e..22024f9898d9 100644 --- a/tooling/cli/src/migrate/manifest.rs +++ b/tooling/cli/src/migrate/manifest.rs @@ -89,6 +89,7 @@ fn features_to_remove() -> Vec<&'static str> { features_to_remove.push("shell-open-api"); features_to_remove.push("windows7-compat"); features_to_remove.push("updater"); + features_to_remove.push("system-tray"); // this allowlist feature was not removed let index = features_to_remove @@ -152,6 +153,8 @@ fn migrate_dependency_table(dep: &mut D, version: String, remove: features_array.remove(index); if f == "reqwest-native-tls-vendored" { add_features.push("native-tls-vendored"); + } else if f == "system-tray" { + add_features.push("tray-icon"); } } }