From 262f8f758318981f064120a44a3d62637c5094bf Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Fri, 14 Jul 2023 01:09:38 +0300 Subject: [PATCH 001/123] refactor: migrate to new crates (`muda` & `tray-icon`) --- .github/workflows/lint-core.yml | 2 +- .github/workflows/test-core.yml | 2 +- core/tauri-build/src/codegen/context.rs | 2 +- core/tauri-codegen/README.md | 2 +- core/tauri-codegen/src/context.rs | 18 +- core/tauri-codegen/src/lib.rs | 2 +- core/tauri-config-schema/schema.json | 21 +- core/tauri-runtime-wry/Cargo.toml | 15 +- core/tauri-runtime-wry/src/lib.rs | 1148 ++++++--------------- core/tauri-runtime-wry/src/system_tray.rs | 238 ----- core/tauri-runtime/Cargo.toml | 7 +- core/tauri-runtime/src/lib.rs | 252 +---- core/tauri-runtime/src/menu.rs | 747 -------------- core/tauri-runtime/src/webview.rs | 10 +- core/tauri-runtime/src/window.rs | 66 +- core/tauri-utils/Cargo.toml | 2 +- core/tauri-utils/src/config.rs | 42 +- core/tauri/Cargo.toml | 17 +- core/tauri/src/app.rs | 541 ++++++---- core/tauri/src/app/tray.rs | 704 ------------- core/tauri/src/lib.rs | 40 +- core/tauri/src/manager.rs | 161 +-- core/tauri/src/menu.rs | 73 ++ core/tauri/src/test/mock_runtime.rs | 108 +- core/tauri/src/test/mod.rs | 4 +- core/tauri/src/tray.rs | 11 + core/tauri/src/window.rs | 240 ++++- core/tauri/src/window/menu.rs | 155 --- examples/api/src-tauri/Cargo.toml | 1 - examples/api/src-tauri/src/lib.rs | 7 +- examples/api/src-tauri/src/tray.rs | 10 +- examples/api/src-tauri/tauri.conf.json | 2 +- examples/splashscreen/main.rs | 6 +- tooling/cli/Cargo.lock | 130 ++- tooling/cli/schema.json | 21 +- tooling/cli/src/build.rs | 2 +- tooling/cli/src/interface/rust.rs | 6 +- 37 files changed, 1327 insertions(+), 3488 deletions(-) delete mode 100644 core/tauri-runtime-wry/src/system_tray.rs delete mode 100644 core/tauri-runtime/src/menu.rs delete mode 100644 core/tauri/src/app/tray.rs create mode 100644 core/tauri/src/menu.rs create mode 100644 core/tauri/src/tray.rs delete mode 100644 core/tauri/src/window/menu.rs diff --git a/.github/workflows/lint-core.yml b/.github/workflows/lint-core.yml index 09dc4e09ce4a..7871eeb35c60 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,linux-protocol-headers,isolation,custom-protocol,system-tray,test', + args: '--features compression,wry,linux-protocol-headers,isolation,custom-protocol,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 711eb5781f44..d9219ff0f2b8 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,linux-protocol-headers,isolation,custom-protocol,system-tray,test, + args: --features compression,wry,linux-protocol-headers,isolation,custom-protocol,test, key: all } 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-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..e22299b517bd 100644 --- a/core/tauri-codegen/src/context.rs +++ b/core/tauri-codegen/src/context.rs @@ -319,16 +319,16 @@ pub fn context_codegen(data: ContextData) -> 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 +157,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, ); @@ -213,7 +194,6 @@ impl Context { impl Context { fn create_webview(&self, pending: PendingWindow>) -> Result>> { let label = pending.label.clone(); - let menu_ids = pending.menu_ids.clone(); let context = self.clone(); let window_id = rand::random(); @@ -231,21 +211,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 +283,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 +295,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 +531,25 @@ 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, + 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 = s.field("tabbing_identifier", &self.tabbing_identifier); + } + s.finish() + } } // SAFETY: this type is `Send` since `menu_items` are read only here @@ -772,7 +631,7 @@ impl WindowBuilder for WindowBuilderWrapper { window } - fn menu(mut self, menu: Menu) -> Self { + fn menu(mut self, menu: menu::Menu) -> Self { self.menu.replace(menu); self } @@ -979,7 +838,11 @@ impl WindowBuilder for WindowBuilderWrapper { self.inner.window.window_icon.is_some() } - fn get_menu(&self) -> Option<&Menu> { + fn has_menu(&self) -> bool { + self.menu.is_some() + } + + fn get_menu(&self) -> Option<&menu::Menu> { self.menu.as_ref() } } @@ -1060,7 +923,6 @@ pub enum ApplicationMessage { pub enum WindowMessage { WithWebview(Box), AddEventListener(Uuid, Box), - AddMenuEventListener(Uuid, Box), // Devtools #[cfg(any(debug_assertions, feature = "devtools"))] OpenDevTools, @@ -1086,7 +948,6 @@ pub enum WindowMessage { IsClosable(Sender), IsVisible(Sender), Title(Sender), - IsMenuVisible(Sender), CurrentMonitor(Sender>), PrimaryMonitor(Sender>), AvailableMonitors(Sender>), @@ -1113,8 +974,6 @@ pub enum WindowMessage { Unmaximize, Minimize, Unminimize, - ShowMenu, - HideMenu, Show, Hide, Close, @@ -1136,7 +995,6 @@ pub enum WindowMessage { SetCursorPosition(Position), SetIgnoreCursorEvents(bool), DragWindow, - UpdateMenuItem(u16, MenuUpdate), RequestRedraw, } @@ -1154,21 +1012,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, >; @@ -1179,14 +1022,14 @@ 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, Box (String, WryWindowBuilder) + Send>, Sender>>, ), + MenuEvent(menu::MenuEvent), + TrayIconEvent(tray::TrayIconEvent), UserEvent(T), } @@ -1194,8 +1037,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!(), } @@ -1230,15 +1071,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, @@ -1346,10 +1178,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())) } @@ -1492,20 +1320,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, @@ -1673,13 +1487,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)] @@ -1738,9 +1545,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 { @@ -1748,7 +1553,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() } } @@ -1794,19 +1598,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() } } @@ -1889,25 +1686,6 @@ impl RuntimeHandle for WryHandle { 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() } @@ -1950,16 +1728,34 @@ impl RuntimeHandle for WryHandle { } impl Wry { - fn init(event_loop: EventLoop>) -> Result { + fn init( + mut event_loop_builder: EventLoopBuilder>, + 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); + } + + let event_loop = event_loop_builder.build(); + + let proxy = event_loop.create_proxy(); + menu::MenuEvent::set_event_handler(Some(move |e| { + let _ = proxy.send_event(Message::MenuEvent(e)); + })); + + let proxy = event_loop.create_proxy(); + tray::TrayIconEvent::set_event_handler(Some(move |e| { + let _ = proxy.send_event(Message::TrayIconEvent(e)); + })); + let main_thread_id = current_thread().id(); let web_context = WebContextStore::default(); 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, @@ -1968,8 +1764,6 @@ impl Wry { window_target: event_loop.deref().clone(), web_context, windows, - #[cfg(all(desktop, feature = "system-tray"))] - system_tray_manager, }, plugins: Default::default(), }; @@ -1985,24 +1779,21 @@ 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(EventLoopBuilder::>::with_user_event(), args) } #[cfg(any(windows, target_os = "linux"))] - fn new_any_thread() -> Result { + fn new_any_thread(args: RuntimeInitArgs) -> Result { #[cfg(target_os = "linux")] - use wry::application::platform::unix::EventLoopExtUnix; + use wry::application::platform::unix::EventLoopBuilderExtUnix; #[cfg(windows)] - use wry::application::platform::windows::EventLoopExtWindows; - let event_loop = EventLoop::>::new_any_thread(); - Self::init(event_loop) + use wry::application::platform::windows::EventLoopBuilderExtWindows; + let mut event_loop_builder = EventLoopBuilder::>::with_user_event(); + event_loop_builder.with_any_thread(true); + Self::init(event_loop_builder, args) } fn create_proxy(&self) -> EventProxy { @@ -2017,7 +1808,6 @@ impl Runtime for Wry { fn create_window(&self, pending: PendingWindow) -> Result> { let label = pending.label.clone(); - let menu_ids = pending.menu_ids.clone(); let window_id = rand::random(); let webview = create_webview( @@ -2040,54 +1830,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 }) } #[cfg(target_os = "macos")] @@ -2125,8 +1868,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(); @@ -2150,8 +1891,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, ); @@ -2168,8 +1907,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, ); @@ -2184,9 +1921,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| { @@ -2200,8 +1934,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, ); @@ -2217,8 +1949,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, ); @@ -2230,15 +1960,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( @@ -2250,8 +1976,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(), @@ -2265,260 +1989,226 @@ 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(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 = "macos")] + { + use wry::webview::WebviewExtMacOS; + f(Webview { + webview: w.webview(), + manager: w.manager(), + ns_window: w.ns_window(), + }); } - } - // Getters - WindowMessage::Url(tx) => { - if let WindowHandle::Webview { inner: w, .. } = &window { - tx.send(w.url()).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, + }); } - } - 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::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(), + 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::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(); + } } } } @@ -2561,9 +2251,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(); @@ -2572,91 +2260,8 @@ 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::MenuEvent(_) => (), + Message::TrayIconEvent(_) => (), Message::UserEvent(_) => (), } @@ -2677,8 +2282,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; @@ -2701,118 +2304,14 @@ 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); - } + Event::UserEvent(Message::MenuEvent(event)) => { + callback(RunEvent::MenuEvent(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); - } - } + Event::UserEvent(Message::TrayIconEvent(event)) => { + callback(RunEvent::TrayIconEvent(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(); @@ -2831,6 +2330,7 @@ fn handle_event_loop( } } } + Event::WindowEvent { event, window_id, .. } => { @@ -2907,8 +2407,6 @@ fn handle_event_loop( UserMessageContext { webview_id_map, windows, - #[cfg(all(desktop, feature = "system-tray"))] - system_tray_manager, }, web_context, ); @@ -2976,39 +2474,6 @@ 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( window_id: WebviewId, event_loop: &EventLoopWindowTarget>, @@ -3024,7 +2489,6 @@ fn create_webview( ipc_handler, label, url, - menu_ids, #[cfg(target_os = "android")] on_webview_created, .. @@ -3056,21 +2520,27 @@ 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(); + if let Some(menu) = window_builder.menu { + #[cfg(windows)] + let _ = menu.init_for_hwnd(window.hwnd() as _); + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + let _ = menu.init_for_gtk_window(window.gtk_window()); + } + webview_id_map.insert(window.id(), window_id); if window_builder.center { let _ = center_window(&window, window.inner_size()); } + let mut webview_builder = WebViewBuilder::new(window) .map_err(|e| Error::CreateWebview(Box::new(e)))? .with_url(&url) @@ -3105,12 +2575,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 { webview_builder = webview_builder.with_custom_protocol(scheme, move |wry_request| { @@ -3224,9 +2690,7 @@ fn create_webview( web_context_key }, }), - menu_items, window_event_listeners, - menu_event_listeners: Default::default(), }) } @@ -3234,7 +2698,6 @@ fn create_webview( fn create_ipc_handler( context: Context, label: String, - menu_ids: Arc>>, handler: WebviewIpcHandler>, ) -> Box { Box::new(move |window, request| { @@ -3246,7 +2709,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 0d1cfd9859a7..7dfcb1e1ef33 100644 --- a/core/tauri-runtime/Cargo.toml +++ b/core/tauri-runtime/Cargo.toml @@ -33,9 +33,11 @@ http-range = "0.1.4" raw-window-handle = "0.5" rand = "0.8" url = { version = "2" } +muda = { version = "0.6", path = "../../../muda" } +tray-icon = { version = "0.6", path = "../../../tray-icon" } [target."cfg(windows)".dependencies.windows] -version = "0.44" +version = "0.48" features = [ "Win32_Foundation" ] [target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies] @@ -46,5 +48,6 @@ jni = "0.20" [features] devtools = [ ] -system-tray = [ ] macos-private-api = [ ] +libxdo = ["tray-icon/libxdo", "muda/libxdo"] +common-controls-v6 = ["tray-icon/common-controls-v6", "muda/common-controls-v6"] diff --git a/core/tauri-runtime/src/lib.rs b/core/tauri-runtime/src/lib.rs index efd409231973..4eca5661e3cd 100644 --- a/core/tauri-runtime/src/lib.rs +++ b/core/tauri-runtime/src/lib.rs @@ -19,9 +19,10 @@ use tauri_utils::Theme; use url::Url; use uuid::Uuid; +pub use muda as menu; +pub use tray_icon as tray; + 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; @@ -41,156 +42,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 +94,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), @@ -268,6 +114,14 @@ pub enum Error { Infallible(#[from] std::convert::Infallible), #[error("the event loop has been closed")] EventLoopClosed, + #[error(transparent)] + BadMenuIcon(#[from] menu::icon::BadIcon), + #[error(transparent)] + BadTrayIcon(#[from] tray::icon::BadIcon), + #[error(transparent)] + MenuError(#[from] menu::Error), + #[error(transparent)] + TrayError(#[from] tray::Error), } /// Result type. @@ -284,6 +138,22 @@ pub struct Icon { pub height: u32, } +impl TryFrom for menu::icon::Icon { + type Error = Error; + + fn try_from(value: Icon) -> std::result::Result { + menu::icon::Icon::from_rgba(value.rgba, value.width, value.height).map_err(Into::into) + } +} + +impl TryFrom for tray::icon::Icon { + type Error = Error; + + fn try_from(value: Icon) -> std::result::Result { + tray::icon::Icon::from_rgba(value.rgba, value.width, value.height).map_err(Into::into) + } +} + /// A type that can be used as an user event. pub trait UserEvent: Debug + Clone + Send + 'static {} @@ -313,6 +183,10 @@ pub enum RunEvent { /// /// This event is useful as a place to put your code that should be run after all state-changing events have been handled and you want to do stuff (updating state, performing calculations, etc) that happens as the “main body” of your event loop. MainEventsCleared, + /// An event from a menu item, could be on the window menu bar, application menu bar (on macOS) or tray icon menu. + 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. + TrayIconEvent(tray_icon::TrayIconEvent), /// A custom event defined by the user. UserEvent(T), } @@ -324,24 +198,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 { @@ -377,14 +233,6 @@ pub trait RuntimeHandle: Debug + Clone + Send + Sync + Sized + 'st /// 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; /// Shows the application, but does not automatically focus it. @@ -421,25 +269,28 @@ 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; @@ -450,16 +301,6 @@ pub trait Runtime: Debug + Sized + 'static { /// 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); - /// Sets the activation policy for the application. It is set to `NSApplicationActivationPolicyRegular` by default. #[cfg(target_os = "macos")] #[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))] @@ -510,9 +351,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<()>; @@ -596,9 +434,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. @@ -691,12 +526,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<()>; @@ -767,7 +596,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 af2277f8a467..94964a5dd44b 100644 --- a/core/tauri-runtime/src/webview.rs +++ b/core/tauri-runtime/src/webview.rs @@ -4,8 +4,9 @@ //! Items specific to the [`Runtime`](crate::Runtime)'s webview. -use crate::{menu::Menu, window::DetachedWindow, Icon}; +use crate::{window::DetachedWindow, Icon}; +use muda::Menu; #[cfg(target_os = "macos")] use tauri_utils::TitleBarStyle; use tauri_utils::{ @@ -155,7 +156,7 @@ pub trait WindowBuilder: WindowBuilderBase { /// Sets the menu for the window. #[must_use] - fn menu(self, menu: Menu) -> Self; + fn menu(self, menu: crate::menu::Menu) -> Self; /// Show window in the center of the screen. #[must_use] @@ -327,7 +328,10 @@ pub trait WindowBuilder: WindowBuilderBase { /// Whether the icon was set or not. fn has_icon(&self) -> bool; - /// Gets the window menu. + /// Whether the menu was set or not. + fn has_menu(&self) -> bool; + + /// Whether the menu was set or not. fn get_menu(&self) -> Option<&Menu>; } diff --git a/core/tauri-runtime/src/window.rs b/core/tauri-runtime/src/window.rs index fbb7899f9b92..e8c36daee5f5 100644 --- a/core/tauri-runtime/src/window.rs +++ b/core/tauri-runtime/src/window.rs @@ -6,11 +6,11 @@ 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 muda::Menu; +use serde::{Deserialize, Deserializer}; use tauri_utils::{config::WindowConfig, Theme}; use url::Url; @@ -18,7 +18,7 @@ use std::{ collections::HashMap, hash::{Hash, Hasher}, path::PathBuf, - sync::{mpsc::Sender, Arc, Mutex}, + sync::mpsc::Sender, }; type UriSchemeProtocol = @@ -80,25 +80,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)] @@ -229,9 +210,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 bool + Send>>, @@ -244,6 +222,9 @@ pub struct PendingWindow> { Option) -> Result<(), jni::errors::Error> + Send>>, pub web_resource_request_handler: Option>, + + /// Whether the menu set in the builder is app-wide + pub has_app_wide_menu: bool, } pub fn is_label_valid(label: &str) -> bool { @@ -266,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) @@ -280,12 +257,12 @@ 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")] on_webview_created: None, web_resource_request_handler: Default::default(), + has_app_wide_menu: false, }) } } @@ -298,10 +275,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) @@ -312,25 +286,35 @@ 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")] on_webview_created: None, web_resource_request_handler: Default::default(), + has_app_wide_menu: false, }) } } #[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; + pub fn set_menu(mut self, menu: crate::menu::Menu) -> Self { self.window_builder = self.window_builder.menu(menu); self } + #[must_use] + pub fn set_app_menu(mut self, menu: crate::menu::Menu) -> Self { + if !self.window_builder.has_menu() { + self.window_builder = self.window_builder.menu(menu); + self.has_app_wide_menu = true; + } + self + } + + pub fn menu(&self) -> Option<&Menu> { + self.window_builder.get_menu() + } + pub fn register_uri_scheme_protocol< N: Into, H: Fn(&HttpRequest) -> Result> + Send + Sync + 'static, @@ -365,9 +349,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 { @@ -375,7 +356,6 @@ impl> Clone for DetachedWindow { Self { label: self.label.clone(), dispatcher: self.dispatcher.clone(), - menu_ids: self.menu_ids.clone(), } } } diff --git a/core/tauri-utils/Cargo.toml b/core/tauri-utils/Cargo.toml index dbbeb07995dd..ad820b034f33 100644 --- a/core/tauri-utils/Cargo.toml +++ b/core/tauri-utils/Cargo.toml @@ -43,7 +43,7 @@ dunce = "1" heck = "0.4" [target."cfg(windows)".dependencies.windows] -version = "0.44.0" +version = "0.48" features = [ "implement", "Win32_Foundation", diff --git a/core/tauri-utils/src/config.rs b/core/tauri-utils/src/config.rs index 8415a634f97b..732909173721 100644 --- a/core/tauri-utils/src/config.rs +++ b/core/tauri-utils/src/config.rs @@ -1338,9 +1338,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, @@ -1349,20 +1349,12 @@ pub struct TauriConfig { impl TauriConfig { /// Returns all Cargo features. pub fn all_features() -> Vec<&'static str> { - vec![ - "system-tray", - "macos-private-api", - "isolation", - "protocol-asset", - ] + vec!["macos-private-api", "isolation", "protocol-asset"] } /// 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.macos_private_api { features.push("macos-private-api"); } @@ -1472,15 +1464,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. @@ -1491,6 +1483,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. @@ -1760,7 +1754,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 @@ -2484,19 +2478,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 ); } } @@ -2533,7 +2529,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!( @@ -2543,7 +2539,7 @@ mod build { windows, bundle, security, - system_tray, + tray_icon, macos_private_api ); } @@ -2635,7 +2631,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 8ccb6dcd65a1..66105220f8d1 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", @@ -70,7 +69,7 @@ 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\"))".dependencies] gtk = { version = "0.16", features = [ "v3_24" ] } glib = "0.16" -webkit2gtk = { version = "0.19.1", features = [ "v2_38" ] } +webkit2gtk = { version = "1.1", features = [ "v2_38" ] } [target."cfg(target_os = \"macos\")".dependencies] embed_plist = "1.2" @@ -78,11 +77,11 @@ cocoa = "0.24" objc = "0.2" [target."cfg(windows)".dependencies] -webview2-com = "0.22" +webview2-com = "0.25" - [target."cfg(windows)".dependencies.windows] - version = "0.44" - 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" @@ -113,18 +112,18 @@ tokio = { version = "1", features = [ "full" ] } cargo_toml = "0.15" [features] -default = [ "wry", "compression", "objc-exception" ] +default = [ "wry", "compression", "objc-exception", "tauri-runtime/common-controls-v6" ] test = [ ] compression = [ "tauri-macros/compression", "tauri-utils/compression" ] wry = [ "tauri-runtime-wry" ] objc-exception = [ "tauri-runtime-wry/objc-exception" ] -linux-protocol-headers = [ "tauri-runtime-wry/linux-headers", "webkit2gtk/v2_36" ] +linux-protocol-body = [ "tauri-runtime-wry/linux-body", "webkit2gtk/v2_40" ] +linux-libxdo = [ "tauri-runtime/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 7128c8c0ca6d..d8f0b8624a7b 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::{ api::ipc::CallbackFn, command::{CommandArg, CommandItem}, @@ -32,9 +29,14 @@ use crate::scope::FsScope; use raw_window_handle::HasRawDisplayHandle; use tauri_macros::default_runtime; -use tauri_runtime::window::{ - dpi::{PhysicalPosition, PhysicalSize}, - FileDropEvent, +use tauri_runtime::{ + menu::{Menu, MenuEvent}, + tray::{TrayIconBuilder, TrayIconEvent}, + window::{ + dpi::{PhysicalPosition, PhysicalSize}, + FileDropEvent, + }, + RuntimeInitArgs, }; use tauri_utils::PackageInfo; @@ -44,17 +46,15 @@ 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>; +pub(crate) type GlobalMenuEventListener = Box; +pub(crate) type GlobalTrayIconEventListener = + Box, crate::tray::TrayIconEvent) + Send + Sync>; pub(crate) type GlobalWindowEventListener = Box) + Send + Sync>; -#[cfg(all(desktop, feature = "system-tray"))] -type SystemTrayEventListener = Box, tray::SystemTrayEvent) + Send + Sync>; /// Api exposed on the `ExitRequested` event. #[derive(Debug)] @@ -177,6 +177,10 @@ pub enum RunEvent { /// /// This event is useful as a place to put your code that should be run after all state-changing events have been handled and you want to do stuff (updating state, performing calculations, etc) that happens as the “main body” of your event loop. MainEventsCleared, + /// An event from a menu item, could be on the window menu bar, application menu bar (on macOS) or tray icon menu. + MenuEvent(crate::menu::MenuEvent), + /// An event from a menu item, could be on the window menu bar, application menu bar (on macOS) or tray icon menu. + TrayIconEvent(crate::tray::TrayIconEvent), } impl From for RunEvent { @@ -185,26 +189,6 @@ impl From for RunEvent { } } -/// 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 - } -} - /// A window event that was triggered on the specified window. #[default_runtime(crate::Wry, wry)] #[derive(Debug)] @@ -399,12 +383,7 @@ impl AppHandle { /// 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(); - } - } + self.manager.inner.tray_icons.lock().unwrap().clear() } } @@ -485,73 +464,7 @@ 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 { - self - .manager() - .trays() - .values() - .next() - .cloned() - .expect("tray not configured; use the `Builder#system_tray`, `App#system_tray` or `AppHandle#system_tray` APIs first.") - } - - - /// Gets a handle to a system tray by its id. - /// - /// ``` - /// use tauri::{CustomMenuItem, SystemTray, SystemTrayMenu}; - /// - /// 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> { - self - .manager() - .get_tray(id) - } + // TODO(muda-migration): tray getter methods /// Gets the app's configuration, defined on the `tauri.conf.json` file. pub fn config(&self) -> Arc { @@ -575,6 +488,142 @@ macro_rules! shared_app_impl { self.manager.inner.default_window_icon.as_ref() } + /// Returns the app-wide menu. + pub fn menu(&self) -> Option { + self.manager.menu_lock().clone() + } + + /// Sets the app-wide menu and returns the previous one. + 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 + for window in self.manager.windows_lock().values() { + let mut window_menu = window.menu_lock(); + if window_menu.as_ref().map(|m| m.0).unwrap_or(true) { + #[cfg(windows)] + { + let _ = menu.init_for_hwnd(window.hwnd().unwrap().0); + } + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + { + let _ = menu.init_for_gtk_windows(window.gtk_window().unwrap()); + } + window_menu.replace((true, menu.clone())); + } + } + + // set it app-wide for macos + #[cfg(target_os = "macos")] + menu.init_for_nsapp(); + + Ok(prev_menu) + } + + /// Remove the app-wide menu and returns it. + pub fn remove_menu(&self) -> crate::Result> { + let mut current_menu = self.manager.menu_lock(); + + // remove from windows that have the app-wide menu + if let Some(menu) = &*current_menu { + for window in self.manager.windows_lock().values() { + if window.has_app_wide_menu() { + #[cfg(windows)] + { + let _ = menu.remove_for_hwnd(window.hwnd()?.0); + } + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + { + let _ = menu.remove_for_gtk_windows(window.gtk_window()?); + } + *window.menu_lock() = None; + } + } + + // remove app-wide for macos + #[cfg(target_os = "macos")] + menu.remove_for_nsapp(); + } + + let prev_menu = current_menu.take(); + + drop(current_menu); + + 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. + pub fn hide_menu(&self) -> crate::Result<()> { + if let Some(menu) = &*self.manager.menu_lock() { + for window in self.manager.windows_lock().values() { + if window.has_app_wide_menu() { + #[cfg(windows)] + { + let _ = menu.hide_for_hwnd(window.hwnd()?.0); + } + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + { + let _ = menu.hide_for_gtk_windows(window.gtk_window()?); + } + } + } + } + + Ok(()) + } + + /// Shows the app-wide menu for windows that have it. + pub fn show_menu(&self) -> crate::Result<()> { + if let Some(menu) = &*self.manager.menu_lock() { + for window in self.manager.windows_lock().values() { + if window.has_app_wide_menu() { + #[cfg(windows)] + { + let _ = menu.show_for_hwnd(window.hwnd()?.0); + } + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + { + let _ = menu.show_for_gtk_windows(window.gtk_window()?); + } + } + } + } + + Ok(()) + } + /// Shows the application, but does not automatically focus it. #[cfg(target_os = "macos")] pub fn show(&self) -> crate::Result<()> { @@ -715,7 +764,7 @@ impl App { /// /// 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). + /// Additionally, the cleanup calls [AppHandle#remove_tray_icon](`AppHandle#method.remove_tray_icon`) (Windows only). /// /// # Examples /// ```no_run @@ -790,23 +839,24 @@ pub struct Builder { /// The menu set to all windows. menu: Option, + /// A closure that returns the menu set to all windows. + menu_with: Option Menu>>, + /// Enable macOS default menu creation. #[allow(unused)] enable_macos_default_menu: bool, - /// Menu event handlers that listens to all windows. - menu_event_listeners: Vec>, + /// Menu event handlers . + 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, + /// Tray icon builder + tray_icons: Vec, - /// System tray event handlers. - #[cfg(all(desktop, feature = "system-tray"))] - system_tray_event_listeners: Vec>, + /// Tray icon event handlers. + tray_event_listeners: Vec>, /// The device event filter. device_event_filter: DeviceEventFilter, @@ -829,13 +879,12 @@ impl Builder { uri_scheme_protocols: Default::default(), state: StateManager::new(), menu: None, + menu_with: 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(), + tray_icons: Vec::new(), + tray_event_listeners: Vec::new(), device_event_filter: Default::default(), } } @@ -1060,26 +1109,25 @@ impl Builder { self } - /// Sets the given system tray to be built before the app runs. + /// Sets the given tray icon builder to be built before the app runs. /// - /// Prefer the [`SystemTray#method.build`](crate::SystemTray#method.build) method to create the tray at runtime instead. + /// Prefer the [`TrayIconBuilder#method.build`](crate::tray::TrayIconBuilder#method.build) method to create the tray at runtime instead. /// /// # Examples /// ``` - /// use tauri::{CustomMenuItem, SystemTray, SystemTrayMenu}; + /// use tauri::{tray::TrayIconBuilder, menu::{Menu, MenuItem}}; /// /// tauri::Builder::default() - /// .system_tray(SystemTray::new().with_menu( - /// SystemTrayMenu::new() - /// .add_item(CustomMenuItem::new("quit", "Quit")) - /// .add_item(CustomMenuItem::new("open", "Open")) + /// .tray_icon(TrayIconBuilder::new().with_menu( + /// Menu::with_items(&[ + /// &MenuItem::new("New window", true, None), + /// &MenuItem::new("Quit", true, None), + /// ]) /// )); /// ``` - #[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); + pub fn tray_icon(mut self, tray: TrayIconBuilder) -> Self { + self.tray_icons.push(tray); self } @@ -1103,58 +1151,37 @@ impl Builder { /// ``` #[must_use] pub fn menu(mut self, menu: Menu) -> Self { + self.menu_with = None; self.menu.replace(menu); self } - /// Enable or disable the default menu on macOS. Enabled by default. + /// Sets a closure to construct the menu to use on all windows. /// /// # Examples /// ``` /// use tauri::{MenuEntry, Submenu, MenuItem, Menu, CustomMenuItem}; /// /// tauri::Builder::default() - /// .enable_macos_default_menu(false); + /// .menu_with(tauri::menu::default); /// ``` #[must_use] - pub fn enable_macos_default_menu(mut self, enable: bool) -> Self { - self.enable_macos_default_menu = enable; + pub fn menu_with Menu + 'static>(mut self, f: F) -> Self { + self.menu_with.replace(Box::new(move |c| f(&c))); + self.menu = None; self } - /// Registers a menu event handler for all windows. + /// Enable or disable the default menu on macOS. Enabled by default. /// /// # 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); - /// } - /// } - /// }); + /// .enable_macos_default_menu(false); /// ``` #[must_use] - pub fn on_menu_event) + Send + Sync + 'static>( - mut self, - handler: F, - ) -> Self { - self.menu_event_listeners.push(Box::new(handler)); + pub fn enable_macos_default_menu(mut self, enable: bool) -> Self { + self.enable_macos_default_menu = enable; self } @@ -1182,34 +1209,61 @@ impl Builder { self } - /// Registers a system tray event handler. + /// Registers a global menu event listener. /// - /// Prefer the [`SystemTray#method.on_event`](crate::SystemTray#method.on_event) method when creating a tray at runtime instead. + /// # Examples + /// ``` + /// let quit_menu_item = &MenuItem::new("Quit", true, None); + /// let menu = Menu::with_items(&[ + /// &Submenu::with_items("File", true, &[ + /// &quit_menu_item, + /// ]), + /// ]); + /// + /// tauri::Builder::default() + /// .menu(menu) + /// .on_menu_event(|app, event| { + /// if event.id == quit_menu_item.id() { + /// // quit app + /// app.exit(0); + /// } + /// }); + /// ``` + #[must_use] + pub fn on_tray_icon_event, TrayIconEvent) + Send + Sync + 'static>( + mut self, + handler: F, + ) -> Self { + self.tray_event_listeners.push(Box::new(handler)); + self + } + + /// Registers a global menu event listener. /// /// # Examples /// ``` - /// use tauri::{Manager, SystemTrayEvent}; + /// let quit_menu_item = &MenuItem::new("Quit", true, None); + /// let menu = Menu::with_items(&[ + /// &Submenu::with_items("File", true, &[ + /// &quit_menu_item, + /// ]), + /// ]); + /// /// 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(); + /// .menu(menu) + /// .on_menu_event(|app, event| { + /// if event.id == quit_menu_item.id() { + /// // quit app + /// app.exit(0); /// } - /// _ => {} /// }); /// ``` - #[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, - >( + pub fn on_menu_event, MenuEvent) + Send + Sync + 'static>( mut self, handler: F, ) -> Self { - self.system_tray_event_listeners.push(Box::new(handler)); + self.menu_event_listeners.push(Box::new(handler)); self } @@ -1269,10 +1323,17 @@ impl Builder { #[allow(clippy::type_complexity)] 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)); + if self.menu.is_none() && self.menu_with.is_none() && self.enable_macos_default_menu { + self.menu = Some(crate::menu::default(context.config())); } + let menu = match (self.menu, self.menu_with) { + (None, Some(f)) => Some(f(context.config())), + (Some(m), None) => Some(m), + (None, None) => None, + _ => unreachable!(), + }; + let manager = WindowManager::with_handlers( context, self.plugins, @@ -1281,7 +1342,8 @@ impl Builder { self.uri_scheme_protocols, self.state, self.window_event_listeners, - (self.menu, self.menu_event_listeners), + (menu.clone(), self.menu_event_listeners, HashMap::new()), + self.tray_event_listeners, (self.invoke_responder, self.invoke_initialization_script), ); @@ -1296,15 +1358,50 @@ impl Builder { )?); } + if let Some(menu) = &menu { + manager + .inner + .menus + .lock() + .unwrap() + .insert(menu.id(), menu.clone()); + } + + 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.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()?; + #[cfg(target_os = "macos")] + if let Some(menu) = menu { + menu.init_for_nsapp(); + } + runtime.set_device_event_filter(self.device_event_filter); let runtime_handle = runtime.handle(); @@ -1356,27 +1453,32 @@ impl Builder { } } - #[cfg(all(desktop, feature = "system-tray"))] + // initialize tray icons { - if let Some(tray) = self.system_tray { - tray.build(&app)?; + let mut tray_stash = app.manager.inner.tray_icons.lock().unwrap(); + let config = app.config(); + if let Some(tray_config) = &config.tauri.tray_icon { + let mut tray = TrayIconBuilder::new() + .with_icon_as_template(tray_config.icon_as_template) + .with_menu_on_left_click(tray_config.menu_on_left_click); + if let Some(icon) = &app.manager.inner.tray_icon { + let icon: crate::runtime::Icon = icon.clone().try_into()?; + tray = tray.with_icon(icon.try_into()?); + } + if let Some(title) = &tray_config.title { + tray = tray.with_title(title); + } + if let Some(tooltip) = &tray_config.tooltip { + tray = tray.with_tooltip(tooltip); + } + tray_stash.push(tray.build().map_err(Into::::into)?); } - - 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); - } - }); + for tray_builder in self.tray_icons { + tray_stash.push( + tray_builder + .build() + .map_err(Into::::into)?, + ); } } @@ -1416,6 +1518,10 @@ fn setup(app: &mut App) -> crate::Result<()> { let pending = app .manager .prepare_window(app.handle.clone(), pending, &window_labels)?; + let menu = pending + .menu() + .cloned() + .map(|m| (pending.has_app_wide_menu, m)); let window_effects = pending.webview_attributes.window_effects.clone(); let detached = if let RuntimeOrDispatch::RuntimeHandle(runtime) = app.handle().runtime() { runtime.create_window(pending)? @@ -1423,7 +1529,7 @@ fn setup(app: &mut App) -> crate::Result<()> { // the AppHandle's runtime is always RuntimeOrDispatch::RuntimeHandle unreachable!() }; - let window = app.manager.attach_window(app.handle(), detached); + let window = app.manager.attach_window(app.handle(), detached, menu); if let Some(effects) = window_effects { crate::vibrancy::set_window_effects(&window, Some(effects))?; @@ -1486,6 +1592,41 @@ fn on_event_loop_event, RunEvent) + 'static>( } RuntimeRunEvent::Resumed => RunEvent::Resumed, RuntimeRunEvent::MainEventsCleared => RunEvent::MainEventsCleared, + RuntimeRunEvent::MenuEvent(e) => { + for listener in &*app_handle + .manager + .inner + .menu_event_listeners + .lock() + .unwrap() + { + listener(&app_handle, e) + } + 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) + } + } + RunEvent::MenuEvent(e) + } + RuntimeRunEvent::TrayIconEvent(e) => { + for listener in &*app_handle + .manager + .inner + .tray_event_listeners + .lock() + .unwrap() + { + listener(&app_handle, e) + } + RunEvent::TrayIconEvent(e) + } RuntimeRunEvent::UserEvent(t) => t.into(), _ => 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/lib.rs b/core/tauri/src/lib.rs index 4eccb29788b1..ff60ef770c19 100644 --- a/core/tauri/src/lib.rs +++ b/core/tauri/src/lib.rs @@ -16,7 +16,8 @@ //! - **test**: Enables the [`test`] module exposing unit test helpers. //! - **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-protocol-headers**: Enables headers support for custom protocol requests on Linux. Requires webkit2gtk v2.36 or above. +//! - **linux-protocol-body**: Enables body support for custom protocol requests on Linux. Requires webkit2gtk v2.36 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,6 @@ //! - **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. //! - **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 +92,7 @@ use tauri_runtime as runtime; mod ios; #[cfg(target_os = "android")] mod jni_helpers; +pub mod menu; /// Path APIs. pub mod path; pub mod process; @@ -99,6 +100,7 @@ pub mod process; pub mod scope; mod state; +pub mod tray; pub use tauri_utils as utils; /// A Tauri [`Runtime`] wrapper around wry. @@ -163,18 +165,8 @@ pub use runtime::{menu::NativeImage, 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, @@ -385,7 +377,7 @@ pub struct Context { pub(crate) default_window_icon: Option, pub(crate) app_icon: Option>, #[cfg(desktop)] - pub(crate) system_tray_icon: Option, + pub(crate) tray_icon: Option, pub(crate) package_info: PackageInfo, pub(crate) _info_plist: (), pub(crate) pattern: Pattern, @@ -401,7 +393,7 @@ impl fmt::Debug for Context { .field("pattern", &self.pattern); #[cfg(desktop)] - d.field("system_tray_icon", &self.system_tray_icon); + d.field("tray_icon", &self.tray_icon); d.finish() } @@ -447,15 +439,15 @@ impl Context { /// The icon to use on the system tray UI. #[cfg(desktop)] #[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. + /// A mutable reference to the icon to use on the tray icon. #[cfg(desktop)] #[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. @@ -494,7 +486,7 @@ impl Context { default_window_icon, app_icon, #[cfg(desktop)] - system_tray_icon: None, + tray_icon: None, package_info, _info_plist: info_plist, pattern, @@ -504,8 +496,8 @@ impl Context { /// Sets the app tray icon. #[cfg(desktop)] #[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. diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index 2db699983845..0865a978bb02 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -13,6 +13,7 @@ use std::{ use serde::Serialize; use serde_json::Value as JsonValue; use serialize_to_javascript::{default_template, DefaultTemplate, Template}; +use tauri_runtime::{menu::Menu, tray::TrayIcon}; use url::Url; use tauri_macros::default_runtime; @@ -25,13 +26,11 @@ use tauri_utils::{ html::{SCRIPT_NONCE_TOKEN, STYLE_NONCE_TOKEN}, }; -use crate::app::{GlobalMenuEventListener, WindowMenuEvent}; -use crate::hooks::IpcJavascript; #[cfg(feature = "isolation")] use crate::hooks::IsolationJavascript; -use crate::pattern::PatternJavascript; +use crate::{app::GlobalWindowEventListener, hooks::IpcJavascript}; use crate::{ - app::{AppHandle, GlobalWindowEvent, GlobalWindowEventListener}, + app::{AppHandle, GlobalWindowEvent}, event::{assert_event_name_is_valid, Event, EventHandler, Listeners}, hooks::{InvokeHandler, InvokePayload, InvokeResponder, OnPageLoad, PageLoadPayload}, plugin::PluginStore, @@ -51,12 +50,14 @@ use crate::{ Context, EventLoopMessage, Icon, Invoke, Manager, Pattern, Runtime, Scopes, StateManager, Window, WindowEvent, }; +use crate::{ + app::{GlobalMenuEventListener, GlobalTrayIconEventListener}, + pattern::PatternJavascript, +}; #[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"; @@ -68,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 STRINGIFY_IPC_MESSAGE_FN: &str = include_str!("../scripts/stringify-ipc-message-fn.js"); @@ -199,9 +199,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, @@ -222,12 +220,26 @@ pub struct InnerWindowManager { 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(windows)] + pub menus: Arc>>, /// The menu set to all windows. - menu: Option, + pub(crate) menu: Arc>>, /// Menu event listeners to all windows. - menu_event_listeners: Arc>>, + pub(crate) menu_event_listeners: Arc>>>>, + /// Menu event listeners to specific windows. + pub(crate) window_menu_event_listeners: + Arc>>>>, /// Window event listeners to all windows. window_event_listeners: Arc>>, + /// Tray icons + pub(crate) tray_icons: Arc>>, + /// Tray icon event listeners. + pub(crate) tray_event_listeners: Arc>>>, /// Responder for invoke calls. invoke_responder: Arc>, /// The script that initializes the invoke system. @@ -246,7 +258,6 @@ 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)] @@ -301,7 +312,12 @@ impl WindowManager { uri_scheme_protocols: HashMap>>, state: StateManager, window_event_listeners: Vec>, - (menu, menu_event_listeners): (Option, Vec>), + (menu, menu_event_listeners, window_menu_event_listeners): ( + Option, + Vec>>, + HashMap>>, + ), + tray_event_listeners: Vec>, (invoke_responder, invoke_initialization_script): (Arc>, String), ) -> Self { // generate a random isolation key at runtime @@ -313,8 +329,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), @@ -325,13 +339,18 @@ impl WindowManager { default_window_icon: context.default_window_icon, app_icon: context.app_icon, #[cfg(desktop)] - tray_icon: context.system_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(windows)] + menus: Default::default(), + menu: Arc::new(Mutex::new(menu)), + menu_event_listeners: Arc::new(Mutex::new(menu_event_listeners)), + window_menu_event_listeners: Arc::new(Mutex::new(window_menu_event_listeners)), window_event_listeners: Arc::new(window_event_listeners), + tray_icons: Default::default(), + tray_event_listeners: Arc::new(Mutex::new(tray_event_listeners)), invoke_responder, invoke_initialization_script, }), @@ -352,6 +371,43 @@ impl WindowManager { self.inner.state.clone() } + /// App-wide menu. + pub(crate) fn menu_lock(&self) -> MutexGuard<'_, Option> { + self.inner.menu.lock().expect("poisoned window manager") + } + + /// Menus stash. + #[cfg(windows)] + pub(crate) fn menus_stash_lock(&self) -> MutexGuard<'_, HashMap> { + self.inner.menus.lock().expect("poisoned window manager") + } + + pub(crate) fn is_menu_in_use(&self, id: u32) -> bool { + self + .menu_lock() + .as_ref() + .map(|m| m.id() == id) + .unwrap_or(false) + } + + /// Menus stash. + pub(crate) fn insert_menu_into_stash(&self, menu: &Menu) { + #[cfg(windows)] + self.menus_stash_lock().insert(menu.id(), menu.clone()); + } + + pub(crate) fn remove_menu_from_stash_by_id(&self, id: Option) { + #[cfg(windows)] + { + 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) -> Arc> { self.inner.invoke_responder.clone() @@ -923,6 +979,7 @@ mod test { StateManager::new(), Default::default(), Default::default(), + Default::default(), (std::sync::Arc::new(|_, _, _, _| ()), "".into()), ); @@ -1050,12 +1107,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| { @@ -1131,6 +1182,19 @@ impl WindowManager { } })); + if let Some(menu) = &*self.inner.menu.lock().unwrap() { + pending = pending.set_app_menu(menu.clone()); + } + + if let Some(menu) = pending.menu() { + self + .inner + .menus + .lock() + .unwrap() + .insert(menu.id(), menu.clone()); + } + Ok(pending) } @@ -1138,8 +1202,9 @@ impl WindowManager { &self, app_handle: AppHandle, window: DetachedWindow, + menu: Option<(bool, Menu)>, ) -> Window { - let window = Window::new(self.clone(), window, app_handle); + let window = Window::new(self.clone(), window, app_handle, menu); let window_ = window.clone(); let window_event_listeners = self.inner.window_event_listeners.clone(); @@ -1153,19 +1218,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 { @@ -1295,33 +1347,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, @@ -1394,10 +1419,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.rs b/core/tauri/src/menu.rs new file mode 100644 index 000000000000..775a4fb8eef6 --- /dev/null +++ b/core/tauri/src/menu.rs @@ -0,0 +1,73 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! Menu types and utility functions + +use tauri_utils::config::Config; + +pub use crate::runtime::menu::*; + +// TODO(muda-migration): wrapper types on tauri's side that will implement unsafe sync & send and hide unnecssary APIs +// TODO(muda-migration): figure out js events + +/// Creates a menu filled with default menu items and submenus. +pub fn default(config: &Config) -> Menu { + let mut about_metadata = AboutMetadata::default(); + about_metadata.name = config.package.product_name.clone(); + about_metadata.version = config.package.version.clone(); + about_metadata.copyright = config.tauri.bundle.copyright.clone(); + about_metadata.authors = config.tauri.bundle.publisher.clone().map(|p| vec![p]); + + Menu::with_items(&[ + #[cfg(target_os = "macos")] + &Submenu::with_items( + config.package.binary_name().unwrap_or_default(), + true, + &[ + &PredefinedMenuItem::separator(), + &PredefinedMenuItem::services(None), + &PredefinedMenuItem::separator(), + &PredefinedMenuItem::hide(None), + &PredefinedMenuItem::hide_others(None), + &PredefinedMenuItem::separator(), + &PredefinedMenuItem::quit(None), + ], + ), + &Submenu::with_items( + "File", + true, + &[ + &PredefinedMenuItem::close_window(None), + #[cfg(not(target_os = "macos"))] + &PredefinedMenuItem::quit(None), + ], + ), + &Submenu::with_items( + "Edit", + true, + &[ + &PredefinedMenuItem::undo(None), + &PredefinedMenuItem::redo(None), + &PredefinedMenuItem::separator(), + &PredefinedMenuItem::cut(None), + &PredefinedMenuItem::copy(None), + &PredefinedMenuItem::paste(None), + &PredefinedMenuItem::select_all(None), + ], + ), + #[cfg(target_os = "macos")] + &Submenu::with_items("View", true, &[&PredefinedMenuItem::fullscreen(None)]), + &Submenu::with_items( + "Window", + true, + &[ + &PredefinedMenuItem::minimize(None), + &PredefinedMenuItem::maximize(None), + #[cfg(target_os = "macos")] + &PredefinedMenuItem::separator(), + &PredefinedMenuItem::close_window(None), + ], + ), + ]) +} diff --git a/core/tauri/src/test/mock_runtime.rs b/core/tauri/src/test/mock_runtime.rs index 91ec2139c2f8..46155edf5708 100644 --- a/core/tauri/src/test/mock_runtime.rs +++ b/core/tauri/src/test/mock_runtime.rs @@ -6,21 +6,17 @@ #![allow(missing_docs)] use tauri_runtime::{ - menu::{Menu, MenuUpdate}, + menu::Menu, monitor::Monitor, webview::{WindowBuilder, WindowBuilderBase}, window::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, - CursorIcon, DetachedWindow, MenuEvent, PendingWindow, WindowEvent, + CursorIcon, DetachedWindow, PendingWindow, 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}; @@ -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()); @@ -346,6 +330,10 @@ impl WindowBuilder for MockWindowBuilder { false } + fn has_menu(&self) -> bool { + false + } + fn get_menu(&self) -> Option<&Menu> { None } @@ -364,10 +352,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(()) } @@ -462,10 +446,6 @@ impl Dispatch for MockDispatcher { Ok(String::new()) } - fn is_menu_visible(&self) -> Result { - Ok(true) - } - fn current_monitor(&self) -> Result> { Ok(None) } @@ -536,7 +516,6 @@ impl Dispatch for MockDispatcher { last_evaluated_script: Default::default(), url: Arc::new(Mutex::new(pending.url)), }, - menu_ids: Default::default(), }) } @@ -581,14 +560,6 @@ impl Dispatch for MockDispatcher { Ok(()) } - fn show_menu(&self) -> Result<()> { - Ok(()) - } - - fn hide_menu(&self) -> Result<()> { - Ok(()) - } - fn show(&self) -> Result<()> { Ok(()) } @@ -682,46 +653,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)] @@ -737,8 +668,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, } @@ -754,10 +683,6 @@ impl MockRuntime { }; Self { is_running, - #[cfg(all(desktop, feature = "system-tray"))] - tray_handler: MockTrayHandler { - context: context.clone(), - }, context, run_rx: rx, } @@ -767,16 +692,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()) } @@ -801,20 +724,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) {} - #[cfg(target_os = "macos")] #[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))] fn set_activation_policy(&mut self, activation_policy: tauri_runtime::ActivationPolicy) {} diff --git a/core/tauri/src/test/mod.rs b/core/tauri/src/test/mod.rs index 3884733d63ab..9d5be3214a38 100644 --- a/core/tauri/src/test/mod.rs +++ b/core/tauri/src/test/mod.rs @@ -127,7 +127,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(), @@ -137,7 +137,7 @@ pub fn mock_context(assets: A) -> crate::Context { default_window_icon: None, app_icon: None, #[cfg(desktop)] - system_tray_icon: None, + tray_icon: None, package_info: crate::PackageInfo { name: "test".into(), version: "0.1.0".parse().unwrap(), diff --git a/core/tauri/src/tray.rs b/core/tauri/src/tray.rs new file mode 100644 index 000000000000..74aef22840fc --- /dev/null +++ b/core/tauri/src/tray.rs @@ -0,0 +1,11 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! Tray icon types and utility functions + +pub use crate::runtime::tray::*; + +// TODO(muda-migration): tray icon type `on_event` handler +// TODO(muda-migration): wrapper types on tauri's side that will implement unsafe sync & send and hide unnecssary APIs +// TODO(muda-migration): figure out js events diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index 4b3fc28b5d68..9bd3500aeaf1 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -4,9 +4,7 @@ //! The Tauri window types and functions. -pub(crate) mod menu; - -pub use menu::{MenuEvent, MenuHandle}; +use tauri_runtime::menu::MenuEvent; pub use tauri_utils::{config::Color, WindowEffect as Effect, WindowEffectState as EffectState}; use url::Url; @@ -56,7 +54,7 @@ use std::{ fmt, hash::{Hash, Hasher}, path::PathBuf, - sync::{Arc, Mutex}, + sync::{Arc, Mutex, MutexGuard}, }; pub(crate) type WebResourceRequestHandler = dyn Fn(&HttpRequest, &mut HttpResponse) + Send + Sync; @@ -325,13 +323,21 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { let pending = self .manager .prepare_window(self.app_handle.clone(), pending, &labels)?; + let menu = pending + .menu() + .cloned() + .map(|m| (pending.has_app_wide_menu, m)); 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), } - .map(|window| self.manager.attach_window(self.app_handle.clone(), window))?; + .map(|window| { + self + .manager + .attach_window(self.app_handle.clone(), window, menu) + })?; if let Some(effects) = window_effects { crate::vibrancy::set_window_effects(&window, Some(effects))?; @@ -769,7 +775,6 @@ struct JsEventListenerKey { /// 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, @@ -777,6 +782,19 @@ pub struct Window { manager: WindowManager, pub(crate) app_handle: AppHandle, js_event_listeners: Arc>>>, + // The menu set for this window + 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 { @@ -792,6 +810,7 @@ impl Clone for Window { manager: self.manager.clone(), app_handle: self.app_handle.clone(), js_event_listeners: self.js_event_listeners.clone(), + menu: self.menu.clone(), } } } @@ -938,12 +957,14 @@ impl Window { manager: WindowManager, window: DetachedWindow, app_handle: AppHandle, + menu: Option<(bool, Menu)>, ) -> Self { Self { window, manager, app_handle, js_event_listeners: Default::default(), + menu: Arc::new(Mutex::new(menu)), } } @@ -989,18 +1010,193 @@ 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 }) - }) + /// 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 + /// ``` + /// tauri::Builder::default() + /// .setup(|window, event| { + /// let save_menu_item = &MenuItem::new("Save", true, None); + /// let menu = Menu::with_items(&[ + /// &Submenu::with_items("File", true, &[ + /// &save_menu_item, + /// ]), + /// ]); + /// let window = 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 + /// } + /// }); + /// }); + /// ``` + pub fn on_menu_event, 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) -> MutexGuard<'_, Option<(bool, Menu)>> { + self.menu.lock().expect("poisoned window") + } + + pub(crate) fn has_app_wide_menu(&self) -> bool { + self.menu_lock().as_ref().map(|m| m.0).unwrap_or(false) + } + + pub(crate) fn is_menu_in_use(&self, id: u32) -> bool { + self + .menu_lock() + .as_ref() + .map(|m| m.1.id() == id) + .unwrap_or(false) + } + + /// Returns this window menu . + pub fn menu(&self) -> Option { + self.menu_lock().as_ref().map(|m| m.1.clone()) + } + + /// Sets the window menu and returns the previous one. + pub fn set_menu(&self, menu: Menu) -> crate::Result> { + let prev_menu = self.remove_menu()?; + + self.manager.insert_menu_into_stash(&menu); + + #[cfg(windows)] + { + let _ = menu.init_for_hwnd(self.hwnd().unwrap().0); + } + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + { + let _ = menu.init_for_gtk_windows(self.gtk_window().unwrap()); + } + + self.menu_lock().replace((false, menu.clone())); + + Ok(prev_menu) + } + + /// Removes the window menu and returns it. + pub fn remove_menu(&self) -> crate::Result> { + let mut current_menu = self.menu_lock(); + + // remove from the window + if let Some((_, menu)) = &*current_menu { + #[cfg(windows)] + { + let _ = menu.remove_for_hwnd(self.hwnd()?.0); + } + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + { + let _ = menu.remove_for_gtk_windows(self.gtk_window()?); + } + } + + let prev_menu = current_menu.take().map(|m| m.1); + + drop(current_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 + if let Some((_, menu)) = &*self.menu_lock() { + #[cfg(windows)] + { + let _ = menu.hide_for_hwnd(self.hwnd()?.0); + } + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + { + let _ = menu.hide_for_gtk_windows(self.gtk_window()?); + } + } + + Ok(()) + } + + /// Shows the window menu. + pub fn show_menu(&self) -> crate::Result<()> { + // remove from the window + if let Some((_, menu)) = &*self.menu_lock() { + #[cfg(windows)] + { + let _ = menu.show_for_hwnd(self.hwnd()?.0); + } + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + { + let _ = menu.show_for_gtk_windows(self.gtk_window()?); + } + } + + Ok(()) + } + + /// Shows the window menu. + pub fn is_menu_visible(&self) -> crate::Result { + // remove from the window + if let Some((_, menu)) = &*self.menu_lock() { + #[cfg(windows)] + { + return Ok(menu.is_visible_on_hwnd(self.hwnd()?.0)); + } + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + { + return Ok(menu.is_visible_on_gtk_windows(self.gtk_window()?)); + } + } + + Ok(false) } /// Executes a closure, providing it with the webview handle that is specific to the current platform. @@ -1070,14 +1266,6 @@ impl Window { /// Window getters. 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(), - } - } - /// 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) 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/src-tauri/Cargo.toml b/examples/api/src-tauri/Cargo.toml index 8bd1e11634e7..0f37d40ddbd4 100644 --- a/examples/api/src-tauri/Cargo.toml +++ b/examples/api/src-tauri/Cargo.toml @@ -40,7 +40,6 @@ features = [ "icon-png", "isolation", "macos-private-api", - "system-tray" ] [dev-dependencies.tauri] diff --git a/examples/api/src-tauri/src/lib.rs b/examples/api/src-tauri/src/lib.rs index 13e428037e37..d02b7e46f266 100644 --- a/examples/api/src-tauri/src/lib.rs +++ b/examples/api/src-tauri/src/lib.rs @@ -115,11 +115,6 @@ 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![ @@ -136,7 +131,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..20703a4b9938 100644 --- a/examples/api/src-tauri/src/tray.rs +++ b/examples/api/src-tauri/src/tray.rs @@ -3,10 +3,12 @@ // SPDX-License-Identifier: MIT use std::sync::atomic::{AtomicBool, Ordering}; -use tauri::{ - CustomMenuItem, Manager, Runtime, SystemTray, SystemTrayEvent, SystemTrayMenu, WindowBuilder, - WindowUrl, -}; +use tauri::{tray::TrayIconBuilder, WindowUrl}; + +// pub fn tray() -> TrayIconBuilder { +// TrayIconBuilder::new() +// .with_tooltip() +// } pub fn create_tray(app: &tauri::App) -> tauri::Result<()> { let mut tray_menu1 = SystemTrayMenu::new() diff --git a/examples/api/src-tauri/tauri.conf.json b/examples/api/src-tauri/tauri.conf.json index fc7eef0ed0fb..eddc1fdeff0f 100644 --- a/examples/api/src-tauri/tauri.conf.json +++ b/examples/api/src-tauri/tauri.conf.json @@ -101,7 +101,7 @@ } } }, - "systemTray": { + "trayIcon": { "iconPath": "../../.icons/tray_icon_with_transparency.png", "iconAsTemplate": true, "menuOnLeftClick": false diff --git a/examples/splashscreen/main.rs b/examples/splashscreen/main.rs index 5b6e9d7b6694..915f71e596fb 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_with(tauri::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 c2dc1b0472e8..70d39850e62a 100644 --- a/tooling/cli/Cargo.lock +++ b/tooling/cli/Cargo.lock @@ -613,6 +613,15 @@ dependencies = [ "libc", ] +[[package]] +name = "core2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" +dependencies = [ + "memchr", +] + [[package]] name = "cpufeatures" version = "0.2.7" @@ -798,6 +807,12 @@ dependencies = [ "syn 2.0.17", ] +[[package]] +name = "dary_heap" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7762d17f1241643615821a8455a0b2c3e803784b058693d990b11f2dce25a0ca" + [[package]] name = "derive_more" version = "0.99.17" @@ -1437,6 +1452,15 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + [[package]] name = "heck" version = "0.4.1" @@ -1502,7 +1526,21 @@ checksum = "e5c13fb08e5d4dfc151ee5e88bae63f7773d61852f3bdc73c9f4b9e1bde03148" dependencies = [ "log", "mac", - "markup5ever", + "markup5ever 0.10.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "html5ever" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" +dependencies = [ + "log", + "mac", + "markup5ever 0.11.0", "proc-macro2", "quote", "syn 1.0.109", @@ -1679,7 +1717,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", "serde", ] @@ -2002,7 +2040,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ea8e9c6e031377cff82ee3001dc8026cdf431ed4e2e6b51f98ab8c73484a358" dependencies = [ "cssparser", - "html5ever", + "html5ever 0.25.2", + "matches", + "selectors", +] + +[[package]] +name = "kuchikiki" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e4755b7b995046f510a7520c42b2fed58b77bd94d5a87a8eb43d2fd126da8" +dependencies = [ + "cssparser", + "html5ever 0.26.0", + "indexmap", "matches", "selectors", ] @@ -2040,21 +2091,25 @@ checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" [[package]] name = "libflate" -version = "1.4.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ff4ae71b685bbad2f2f391fe74f6b7659a34871c08b210fdc039e43bee07d18" +checksum = "9f7d5654ae1795afc7ff76f4365c2c8791b0feb18e8996a96adad8ffd7c3b2bf" dependencies = [ "adler32", + "core2", "crc32fast", + "dary_heap", "libflate_lz77", ] [[package]] name = "libflate_lz77" -version = "1.2.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a52d3a8bfc85f250440e4424db7d857e241a3aebbbe301f3eb606ab15c39acbf" +checksum = "be5f52fb8c451576ec6b79d3f4deb327398bc05bbdbd99021a6e77a4c855d524" dependencies = [ + "core2", + "hashbrown 0.13.2", "rle-decode-fast", ] @@ -2137,7 +2192,21 @@ checksum = "a24f40fb03852d1cdd84330cddcaf98e9ec08a7b7768e952fad3b4cf048ec8fd" dependencies = [ "log", "phf 0.8.0", - "phf_codegen", + "phf_codegen 0.8.0", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "markup5ever" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" +dependencies = [ + "log", + "phf 0.10.1", + "phf_codegen 0.10.0", "string_cache", "string_cache_codegen", "tendril", @@ -2729,6 +2798,16 @@ dependencies = [ "phf_shared 0.8.0", ] +[[package]] +name = "phf_codegen" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", +] + [[package]] name = "phf_generator" version = "0.8.0" @@ -3335,7 +3414,7 @@ dependencies = [ "log", "matches", "phf 0.8.0", - "phf_codegen", + "phf_codegen 0.8.0", "precomputed-hash", "servo_arc", "smallvec", @@ -3896,7 +3975,7 @@ dependencies = [ "env_logger", "handlebars 4.3.7", "heck", - "html5ever", + "html5ever 0.26.0", "ignore", "image", "include_dir", @@ -3907,7 +3986,7 @@ dependencies = [ "jsonrpsee-core", "jsonrpsee-ws-client", "jsonschema", - "kuchiki", + "kuchikiki", "libc", "local-ip-address", "log", @@ -4012,7 +4091,7 @@ dependencies = [ "ctor 0.1.26", "getrandom 0.2.9", "heck", - "html5ever", + "html5ever 0.25.2", "infer", "json-patch", "json5", @@ -4041,11 +4120,11 @@ dependencies = [ "getrandom 0.2.9", "glob", "heck", - "html5ever", + "html5ever 0.26.0", "infer", "json-patch", "json5", - "kuchiki", + "kuchikiki", "memchr", "phf 0.10.1", "schemars", @@ -4058,7 +4137,7 @@ dependencies = [ "toml", "url", "walkdir", - "windows 0.44.0", + "windows 0.48.0", ] [[package]] @@ -4773,23 +4852,14 @@ dependencies = [ "windows_x86_64_msvc 0.39.0", ] -[[package]] -name = "windows" -version = "0.44.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" -dependencies = [ - "windows-implement 0.44.0", - "windows-interface", - "windows-targets 0.42.2", -] - [[package]] name = "windows" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ + "windows-implement 0.48.0", + "windows-interface", "windows-targets 0.48.0", ] @@ -4805,9 +4875,9 @@ dependencies = [ [[package]] name = "windows-implement" -version = "0.44.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce87ca8e3417b02dc2a8a22769306658670ec92d78f1bd420d6310a67c245c6" +checksum = "5e2ee588991b9e7e6c8338edf3333fbe4da35dc72092643958ebb43f0ab2c49c" dependencies = [ "proc-macro2", "quote", @@ -4816,9 +4886,9 @@ dependencies = [ [[package]] name = "windows-interface" -version = "0.44.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "853f69a591ecd4f810d29f17e902d40e349fb05b0b11fff63b08b826bfe39c7f" +checksum = "e6fb8df20c9bcaa8ad6ab513f7b40104840c8867d5751126e4df3b08388d0cc7" dependencies = [ "proc-macro2", "quote", diff --git a/tooling/cli/schema.json b/tooling/cli/schema.json index 9f7e9b58048c..5c22e193eadd 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" @@ -1959,15 +1959,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": { @@ -1986,6 +1986,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..3ba190038d7b 100644 --- a/tooling/cli/src/build.rs +++ b/tooling/cli/src/build.rs @@ -143,7 +143,7 @@ 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 config_.tauri.tray_icon.is_some() { if let Ok(tray) = std::env::var("TAURI_TRAY") { std::env::set_var( "TRAY_LIBRARY_PATH", diff --git a/tooling/cli/src/interface/rust.rs b/tooling/cli/src/interface/rust.rs index b2d816bc3729..a7f6700606e4 100644 --- a/tooling/cli/src/interface/rust.rs +++ b/tooling/cli/src/interface/rust.rs @@ -701,7 +701,7 @@ impl AppSettings for RustAppSettings { &self.manifest, features, config.tauri.bundle.clone(), - config.tauri.system_tray.clone(), + config.tauri.tray_icon.clone(), ) } @@ -1045,7 +1045,7 @@ fn tauri_config_to_bundle_settings( manifest: &Manifest, features: &[String], config: crate::helpers::config::BundleConfig, - system_tray_config: Option, + tray_icon_config: Option, ) -> crate::Result { let enabled_features = manifest.all_enabled_features(features); @@ -1068,7 +1068,7 @@ fn tauri_config_to_bundle_settings( #[cfg(target_os = "linux")] { - if let Some(system_tray_config) = &system_tray_config { + if let Some(tray_icon_config) = &tray_icon_config { let tray = std::env::var("TAURI_TRAY").unwrap_or_else(|_| "ayatana".to_string()); if tray == "ayatana" { depends.push("libayatana-appindicator3-1".into()); From 4734c01c205e7abeed958ce02508d870abf16eaf Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Tue, 18 Jul 2023 05:04:50 +0300 Subject: [PATCH 002/123] update deps to use git --- core/tauri-runtime-wry/Cargo.toml | 2 +- core/tauri-runtime/Cargo.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/tauri-runtime-wry/Cargo.toml b/core/tauri-runtime-wry/Cargo.toml index 2517006d5990..1f3f505c746c 100644 --- a/core/tauri-runtime-wry/Cargo.toml +++ b/core/tauri-runtime-wry/Cargo.toml @@ -13,7 +13,7 @@ edition = { workspace = true } rust-version = { workspace = true } [dependencies] -wry = { path ="../../../wry", version = "0.29", default-features = false, features = [ "file-drop", "protocol" ] } +wry = { git = "https://github.com/tauri-apps/wry", branch = "tao-v0.22", default-features = false, features = [ "file-drop", "protocol" ] } tauri-runtime = { version = "0.13.0-alpha.6", path = "../tauri-runtime" } tauri-utils = { version = "2.0.0-alpha.6", path = "../tauri-utils" } uuid = { version = "1", features = [ "v4" ] } diff --git a/core/tauri-runtime/Cargo.toml b/core/tauri-runtime/Cargo.toml index 7dfcb1e1ef33..0a52aee20e98 100644 --- a/core/tauri-runtime/Cargo.toml +++ b/core/tauri-runtime/Cargo.toml @@ -33,8 +33,8 @@ http-range = "0.1.4" raw-window-handle = "0.5" rand = "0.8" url = { version = "2" } -muda = { version = "0.6", path = "../../../muda" } -tray-icon = { version = "0.6", path = "../../../tray-icon" } +muda = { git = "https://github.com/tauri-apps/muda" } +tray-icon = { git = "https://github.com/tauri-apps/tray-icon" } [target."cfg(windows)".dependencies.windows] version = "0.48" From 027c94088e659fe12d6de912127db18a7ba34171 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Tue, 18 Jul 2023 05:29:22 +0300 Subject: [PATCH 003/123] add tray getters --- core/tauri/src/app.rs | 28 ++++++++++++++++++++++++++-- core/tauri/src/menu.rs | 29 +++++++++++++++++++---------- 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index d8f0b8624a7b..383bad2528e4 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -31,7 +31,7 @@ use raw_window_handle::HasRawDisplayHandle; use tauri_macros::default_runtime; use tauri_runtime::{ menu::{Menu, MenuEvent}, - tray::{TrayIconBuilder, TrayIconEvent}, + tray::{TrayIcon, TrayIconBuilder, TrayIconEvent}, window::{ dpi::{PhysicalPosition, PhysicalSize}, FileDropEvent, @@ -464,7 +464,31 @@ impl App { macro_rules! shared_app_impl { ($app: ty) => { impl $app { - // TODO(muda-migration): tray getter methods + /// Gets the first tray icon register, usually the one configured in + /// tauri config file. + pub fn tray(&self) -> Option { + self + .manager + .inner + .tray_icons + .lock() + .unwrap() + .first() + .cloned() + } + + /// Gets a tray icon using the provided id. + pub fn tray_by_id(&self, id: u32) -> Option { + self + .manager + .inner + .tray_icons + .lock() + .unwrap() + .iter() + .find(|t| t.id() == id) + .cloned() + } /// Gets the app's configuration, defined on the `tauri.conf.json` file. pub fn config(&self) -> Arc { diff --git a/core/tauri/src/menu.rs b/core/tauri/src/menu.rs index 775a4fb8eef6..7cd3c7c1499b 100644 --- a/core/tauri/src/menu.rs +++ b/core/tauri/src/menu.rs @@ -4,6 +4,7 @@ //! Menu types and utility functions +use tauri_runtime::menu::builders::AboutMetadataBuilder; use tauri_utils::config::Config; pub use crate::runtime::menu::*; @@ -13,11 +14,12 @@ pub use crate::runtime::menu::*; /// Creates a menu filled with default menu items and submenus. pub fn default(config: &Config) -> Menu { - let mut about_metadata = AboutMetadata::default(); - about_metadata.name = config.package.product_name.clone(); - about_metadata.version = config.package.version.clone(); - about_metadata.copyright = config.tauri.bundle.copyright.clone(); - about_metadata.authors = config.tauri.bundle.publisher.clone().map(|p| vec![p]); + let about_metadata = AboutMetadataBuilder::new() + .name(config.package.product_name.clone()) + .version(config.package.version.clone()) + .copyright(config.tauri.bundle.copyright.clone()) + .authors(config.tauri.bundle.publisher.clone().map(|p| vec![p])) + .build(); Menu::with_items(&[ #[cfg(target_os = "macos")] @@ -25,6 +27,7 @@ pub fn default(config: &Config) -> Menu { config.package.binary_name().unwrap_or_default(), true, &[ + &PredefinedMenuItem::about(None, Some(about_metadata)), &PredefinedMenuItem::separator(), &PredefinedMenuItem::services(None), &PredefinedMenuItem::separator(), @@ -33,7 +36,8 @@ pub fn default(config: &Config) -> Menu { &PredefinedMenuItem::separator(), &PredefinedMenuItem::quit(None), ], - ), + ) + .unwrap(), &Submenu::with_items( "File", true, @@ -42,7 +46,8 @@ pub fn default(config: &Config) -> Menu { #[cfg(not(target_os = "macos"))] &PredefinedMenuItem::quit(None), ], - ), + ) + .unwrap(), &Submenu::with_items( "Edit", true, @@ -55,9 +60,10 @@ pub fn default(config: &Config) -> Menu { &PredefinedMenuItem::paste(None), &PredefinedMenuItem::select_all(None), ], - ), + ) + .unwrap(), #[cfg(target_os = "macos")] - &Submenu::with_items("View", true, &[&PredefinedMenuItem::fullscreen(None)]), + &Submenu::with_items("View", true, &[&PredefinedMenuItem::fullscreen(None)]).unwrap(), &Submenu::with_items( "Window", true, @@ -67,7 +73,10 @@ pub fn default(config: &Config) -> Menu { #[cfg(target_os = "macos")] &PredefinedMenuItem::separator(), &PredefinedMenuItem::close_window(None), + &PredefinedMenuItem::about(None, Some(about_metadata)), ], - ), + ) + .unwrap(), ]) + .unwrap() } From d71a11c9be3e62495bb6ea70e74ef1cb3828f0c5 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Thu, 20 Jul 2023 07:57:09 -0300 Subject: [PATCH 004/123] fix linux impl --- core/tauri-runtime-wry/src/lib.rs | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/core/tauri-runtime-wry/src/lib.rs b/core/tauri-runtime-wry/src/lib.rs index de3afde6f837..bfeb6f5d77f9 100644 --- a/core/tauri-runtime-wry/src/lib.rs +++ b/core/tauri-runtime-wry/src/lib.rs @@ -1728,18 +1728,19 @@ impl RuntimeHandle for WryHandle { } impl Wry { - fn init( + fn init_with_builder( mut event_loop_builder: EventLoopBuilder>, - args: RuntimeInitArgs, + #[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()) + } - let event_loop = event_loop_builder.build(); - + fn init(event_loop: EventLoop>) -> Result { let proxy = event_loop.create_proxy(); menu::MenuEvent::set_event_handler(Some(move |e| { let _ = proxy.send_event(Message::MenuEvent(e)); @@ -1782,18 +1783,21 @@ impl Runtime for Wry { type EventLoopProxy = EventProxy; fn new(args: RuntimeInitArgs) -> Result { - Self::init(EventLoopBuilder::>::with_user_event(), args) + Self::init_with_builder(EventLoopBuilder::>::with_user_event(), args) } - #[cfg(any(windows, target_os = "linux"))] + #[cfg(target_os = "linux")] + fn new_any_thread(#[allow(unused_variables)] args: RuntimeInitArgs) -> Result { + use wry::application::platform::unix::EventLoopExtUnix; + Self::init(EventLoop::new_any_thread()) + } + + #[cfg(windows)] fn new_any_thread(args: RuntimeInitArgs) -> Result { - #[cfg(target_os = "linux")] - use wry::application::platform::unix::EventLoopBuilderExtUnix; - #[cfg(windows)] use wry::application::platform::windows::EventLoopBuilderExtWindows; let mut event_loop_builder = EventLoopBuilder::>::with_user_event(); event_loop_builder.with_any_thread(true); - Self::init(event_loop_builder, args) + Self::init_with_builder(event_loop_builder, args) } fn create_proxy(&self) -> EventProxy { From 1a75a13c54b6432559a356b402ab9530a87a1465 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Thu, 20 Jul 2023 08:09:06 -0300 Subject: [PATCH 005/123] fix gtk api usage --- core/tauri/src/app.rs | 8 ++++---- core/tauri/src/manager.rs | 15 ++++----------- core/tauri/src/window.rs | 10 +++++----- 3 files changed, 13 insertions(+), 20 deletions(-) diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 383bad2528e4..415f30ab2a9a 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -541,7 +541,7 @@ macro_rules! shared_app_impl { target_os = "openbsd" ))] { - let _ = menu.init_for_gtk_windows(window.gtk_window().unwrap()); + let _ = menu.init_for_gtk_window(&window.gtk_window().unwrap()); } window_menu.replace((true, menu.clone())); } @@ -574,7 +574,7 @@ macro_rules! shared_app_impl { target_os = "openbsd" ))] { - let _ = menu.remove_for_gtk_windows(window.gtk_window()?); + let _ = menu.remove_for_gtk_window(&window.gtk_window()?); } *window.menu_lock() = None; } @@ -613,7 +613,7 @@ macro_rules! shared_app_impl { target_os = "openbsd" ))] { - let _ = menu.hide_for_gtk_windows(window.gtk_window()?); + let _ = menu.hide_for_gtk_window(&window.gtk_window()?); } } } @@ -639,7 +639,7 @@ macro_rules! shared_app_impl { target_os = "openbsd" ))] { - let _ = menu.show_for_gtk_windows(window.gtk_window()?); + let _ = menu.show_for_gtk_window(&window.gtk_window()?); } } } diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index 0865a978bb02..512b6f7042ca 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -225,7 +225,6 @@ pub struct InnerWindowManager { /// /// This should be mainly used to acceess [`Menu::haccel`] /// to setup the accelerator handling in the event loop - #[cfg(windows)] pub menus: Arc>>, /// The menu set to all windows. pub(crate) menu: Arc>>, @@ -343,7 +342,6 @@ impl WindowManager { package_info: context.package_info, pattern: context.pattern, uri_scheme_protocols, - #[cfg(windows)] menus: Default::default(), menu: Arc::new(Mutex::new(menu)), menu_event_listeners: Arc::new(Mutex::new(menu_event_listeners)), @@ -377,7 +375,6 @@ impl WindowManager { } /// Menus stash. - #[cfg(windows)] pub(crate) fn menus_stash_lock(&self) -> MutexGuard<'_, HashMap> { self.inner.menus.lock().expect("poisoned window manager") } @@ -392,18 +389,14 @@ impl WindowManager { /// Menus stash. pub(crate) fn insert_menu_into_stash(&self, menu: &Menu) { - #[cfg(windows)] self.menus_stash_lock().insert(menu.id(), menu.clone()); } pub(crate) fn remove_menu_from_stash_by_id(&self, id: Option) { - #[cfg(windows)] - { - 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); - } + 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); } } } diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index 9bd3500aeaf1..ba5308f2b89f 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -1089,7 +1089,7 @@ impl Window { target_os = "openbsd" ))] { - let _ = menu.init_for_gtk_windows(self.gtk_window().unwrap()); + let _ = menu.init_for_gtk_window(&self.gtk_window().unwrap()); } self.menu_lock().replace((false, menu.clone())); @@ -1115,7 +1115,7 @@ impl Window { target_os = "openbsd" ))] { - let _ = menu.remove_for_gtk_windows(self.gtk_window()?); + let _ = menu.remove_for_gtk_window(&self.gtk_window()?); } } @@ -1146,7 +1146,7 @@ impl Window { target_os = "openbsd" ))] { - let _ = menu.hide_for_gtk_windows(self.gtk_window()?); + let _ = menu.hide_for_gtk_window(&self.gtk_window()?); } } @@ -1169,7 +1169,7 @@ impl Window { target_os = "openbsd" ))] { - let _ = menu.show_for_gtk_windows(self.gtk_window()?); + let _ = menu.show_for_gtk_window(&self.gtk_window()?); } } @@ -1192,7 +1192,7 @@ impl Window { target_os = "openbsd" ))] { - return Ok(menu.is_visible_on_gtk_windows(self.gtk_window()?)); + return Ok(menu.is_visible_on_gtk_window(&self.gtk_window()?)); } } From 675852526daadfc4adbdb2299999773dbee3ce1d Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Thu, 20 Jul 2023 08:10:01 -0300 Subject: [PATCH 006/123] lint --- core/tauri/src/app.rs | 6 +++--- core/tauri/src/manager.rs | 2 +- core/tauri/src/window.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 415f30ab2a9a..5ce535b8b64d 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -1191,7 +1191,7 @@ impl Builder { /// ``` #[must_use] pub fn menu_with Menu + 'static>(mut self, f: F) -> Self { - self.menu_with.replace(Box::new(move |c| f(&c))); + self.menu_with.replace(Box::new(move |c| f(c))); self.menu = None; self } @@ -1624,7 +1624,7 @@ fn on_event_loop_event, RunEvent) + 'static>( .lock() .unwrap() { - listener(&app_handle, e) + listener(app_handle, e) } for (label, listener) in &*app_handle .manager @@ -1647,7 +1647,7 @@ fn on_event_loop_event, RunEvent) + 'static>( .lock() .unwrap() { - listener(&app_handle, e) + listener(app_handle, e) } RunEvent::TrayIconEvent(e) } diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index 512b6f7042ca..51b0321d4322 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -302,7 +302,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, diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index ba5308f2b89f..94c7dabfff33 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -1250,7 +1250,7 @@ impl Window { /// }); /// } /// ``` - #[cfg(all(feature = "wry"))] + #[cfg(feature = "wry")] #[cfg_attr(doc_cfg, doc(all(feature = "wry")))] pub fn with_webview( &self, From 826a5fc33574555bf58c9e983df4abf2fdc4e7cc Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Thu, 20 Jul 2023 08:32:53 -0300 Subject: [PATCH 007/123] fix build on macos --- core/tauri-runtime-wry/src/lib.rs | 7 ++++--- core/tauri/src/app.rs | 2 +- core/tauri/src/lib.rs | 2 +- core/tauri/src/menu.rs | 16 ++++++++-------- core/tauri/src/window.rs | 2 ++ 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/core/tauri-runtime-wry/src/lib.rs b/core/tauri-runtime-wry/src/lib.rs index bfeb6f5d77f9..c96d2134b1ae 100644 --- a/core/tauri-runtime-wry/src/lib.rs +++ b/core/tauri-runtime-wry/src/lib.rs @@ -83,8 +83,7 @@ use wry::webview::{ use tauri_runtime::ActivationPolicy; #[cfg(target_os = "macos")] pub use wry::application::platform::macos::{ - ActivationPolicy as WryActivationPolicy, CustomMenuItemExtMacOS, EventLoopExtMacOS, - WindowExtMacOS, + ActivationPolicy as WryActivationPolicy, EventLoopExtMacOS, WindowExtMacOS, }; use std::{ @@ -546,7 +545,7 @@ impl std::fmt::Debug for WindowBuilderWrapper { s.field("inner", &self.inner).field("center", &self.center); #[cfg(target_os = "macos")] { - s = s.field("tabbing_identifier", &self.tabbing_identifier); + s.field("tabbing_identifier", &self.tabbing_identifier); } s.finish() } @@ -2537,6 +2536,8 @@ fn create_webview( target_os = "openbsd" ))] let _ = menu.init_for_gtk_window(window.gtk_window()); + #[cfg(target_os = "macos")] + menu.init_for_nsapp(); } webview_id_map.insert(window.id(), window_id); diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 5ce535b8b64d..37e9dac99ed0 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -1419,7 +1419,7 @@ impl Builder { R::new(runtime_args)? }; #[cfg(not(any(windows, target_os = "linux")))] - let mut runtime = R::new()?; + let mut runtime = R::new(runtime_args)?; #[cfg(target_os = "macos")] if let Some(menu) = menu { diff --git a/core/tauri/src/lib.rs b/core/tauri/src/lib.rs index ff60ef770c19..fd4340dab4ee 100644 --- a/core/tauri/src/lib.rs +++ b/core/tauri/src/lib.rs @@ -161,7 +161,7 @@ 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; diff --git a/core/tauri/src/menu.rs b/core/tauri/src/menu.rs index 7cd3c7c1499b..b53b612f6db3 100644 --- a/core/tauri/src/menu.rs +++ b/core/tauri/src/menu.rs @@ -4,7 +4,6 @@ //! Menu types and utility functions -use tauri_runtime::menu::builders::AboutMetadataBuilder; use tauri_utils::config::Config; pub use crate::runtime::menu::*; @@ -14,12 +13,13 @@ pub use crate::runtime::menu::*; /// Creates a menu filled with default menu items and submenus. pub fn default(config: &Config) -> Menu { - let about_metadata = AboutMetadataBuilder::new() - .name(config.package.product_name.clone()) - .version(config.package.version.clone()) - .copyright(config.tauri.bundle.copyright.clone()) - .authors(config.tauri.bundle.publisher.clone().map(|p| vec![p])) - .build(); + let about_metadata = AboutMetadata { + name: config.package.product_name.clone(), + version: config.package.version.clone(), + copyright: config.tauri.bundle.copyright.clone(), + authors: config.tauri.bundle.publisher.clone().map(|p| vec![p]), + ..Default::default() + }; Menu::with_items(&[ #[cfg(target_os = "macos")] @@ -27,7 +27,7 @@ pub fn default(config: &Config) -> Menu { config.package.binary_name().unwrap_or_default(), true, &[ - &PredefinedMenuItem::about(None, Some(about_metadata)), + &PredefinedMenuItem::about(None, Some(about_metadata.clone())), &PredefinedMenuItem::separator(), &PredefinedMenuItem::services(None), &PredefinedMenuItem::separator(), diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index 94c7dabfff33..ffee290daa3e 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -1117,6 +1117,8 @@ impl Window { { let _ = menu.remove_for_gtk_window(&self.gtk_window()?); } + #[cfg(target_os = "macos")] + menu.remove_for_nsapp(); } let prev_menu = current_menu.take().map(|m| m.1); From d57635d492f67aaf8e3a267a57e28d5b2d30fa8b Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Thu, 20 Jul 2023 14:33:13 +0300 Subject: [PATCH 008/123] disable default features for muda and tray-icon --- core/tauri-runtime/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/tauri-runtime/Cargo.toml b/core/tauri-runtime/Cargo.toml index 0a52aee20e98..27691233eca2 100644 --- a/core/tauri-runtime/Cargo.toml +++ b/core/tauri-runtime/Cargo.toml @@ -33,8 +33,8 @@ http-range = "0.1.4" raw-window-handle = "0.5" rand = "0.8" url = { version = "2" } -muda = { git = "https://github.com/tauri-apps/muda" } -tray-icon = { git = "https://github.com/tauri-apps/tray-icon" } +muda = { git = "https://github.com/tauri-apps/muda", default-features = false } +tray-icon = { git = "https://github.com/tauri-apps/tray-icon", default-features = false } [target."cfg(windows)".dependencies.windows] version = "0.48" From eaa17cd48e8da56202c50a914fc5c1153dc6018e Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Thu, 20 Jul 2023 16:41:04 +0300 Subject: [PATCH 009/123] remove default file menu on Linux --- core/tauri/src/menu.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/tauri/src/menu.rs b/core/tauri/src/menu.rs index b53b612f6db3..3c6afce7bd3c 100644 --- a/core/tauri/src/menu.rs +++ b/core/tauri/src/menu.rs @@ -38,6 +38,13 @@ pub fn default(config: &Config) -> Menu { ], ) .unwrap(), + #[cfg(not(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + )))] &Submenu::with_items( "File", true, From 006321655b65337c2ac67afba2e22c7555443823 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Thu, 20 Jul 2023 16:42:58 +0300 Subject: [PATCH 010/123] use builder on Linux --- core/tauri-runtime-wry/src/lib.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/core/tauri-runtime-wry/src/lib.rs b/core/tauri-runtime-wry/src/lib.rs index c96d2134b1ae..64f54e09db8d 100644 --- a/core/tauri-runtime-wry/src/lib.rs +++ b/core/tauri-runtime-wry/src/lib.rs @@ -1784,11 +1784,18 @@ impl Runtime for Wry { fn new(args: RuntimeInitArgs) -> Result { Self::init_with_builder(EventLoopBuilder::>::with_user_event(), args) } - - #[cfg(target_os = "linux")] - fn new_any_thread(#[allow(unused_variables)] args: RuntimeInitArgs) -> Result { - use wry::application::platform::unix::EventLoopExtUnix; - Self::init(EventLoop::new_any_thread()) + #[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(windows)] From ae9423b9fa43799e82f40f410c1b5ffdf73bbe0e Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Thu, 20 Jul 2023 16:54:48 +0300 Subject: [PATCH 011/123] move menu/tray handlers setup from runtime to tauri --- core/tauri-runtime-wry/src/lib.rs | 18 ------- core/tauri-runtime/src/lib.rs | 4 -- core/tauri/src/app.rs | 88 +++++++++++++++++++------------ core/tauri/src/lib.rs | 7 ++- 4 files changed, 59 insertions(+), 58 deletions(-) diff --git a/core/tauri-runtime-wry/src/lib.rs b/core/tauri-runtime-wry/src/lib.rs index 64f54e09db8d..41c5f8c064c4 100644 --- a/core/tauri-runtime-wry/src/lib.rs +++ b/core/tauri-runtime-wry/src/lib.rs @@ -1740,16 +1740,6 @@ impl Wry { } fn init(event_loop: EventLoop>) -> Result { - let proxy = event_loop.create_proxy(); - menu::MenuEvent::set_event_handler(Some(move |e| { - let _ = proxy.send_event(Message::MenuEvent(e)); - })); - - let proxy = event_loop.create_proxy(); - tray::TrayIconEvent::set_event_handler(Some(move |e| { - let _ = proxy.send_event(Message::TrayIconEvent(e)); - })); - let main_thread_id = current_thread().id(); let web_context = WebContextStore::default(); @@ -2314,14 +2304,6 @@ fn handle_event_loop( callback(RunEvent::Exit); } - Event::UserEvent(Message::MenuEvent(event)) => { - callback(RunEvent::MenuEvent(event)); - } - - Event::UserEvent(Message::TrayIconEvent(event)) => { - callback(RunEvent::TrayIconEvent(event)); - } - Event::UserEvent(Message::Webview(id, WebviewMessage::WebviewEvent(event))) => { if let Some(event) = WindowEventWrapper::from(&event).0 { let windows = windows.borrow(); diff --git a/core/tauri-runtime/src/lib.rs b/core/tauri-runtime/src/lib.rs index 4eca5661e3cd..1c082c65c833 100644 --- a/core/tauri-runtime/src/lib.rs +++ b/core/tauri-runtime/src/lib.rs @@ -183,10 +183,6 @@ pub enum RunEvent { /// /// This event is useful as a place to put your code that should be run after all state-changing events have been handled and you want to do stuff (updating state, performing calculations, etc) that happens as the “main body” of your event loop. MainEventsCleared, - /// An event from a menu item, could be on the window menu bar, application menu bar (on macOS) or tray icon menu. - 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. - TrayIconEvent(tray_icon::TrayIconEvent), /// A custom event defined by the user. UserEvent(T), } diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 37e9dac99ed0..c6b9d345679a 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -36,7 +36,7 @@ use tauri_runtime::{ dpi::{PhysicalPosition, PhysicalSize}, FileDropEvent, }, - RuntimeInitArgs, + EventLoopProxy, RuntimeInitArgs, }; use tauri_utils::PackageInfo; @@ -185,7 +185,10 @@ pub enum RunEvent { impl From for RunEvent { fn from(event: EventLoopMessage) -> Self { - match event {} + match event { + EventLoopMessage::MenuEvent(e) => Self::MenuEvent(e), + EventLoopMessage::TrayIconEvent(e) => Self::TrayIconEvent(e), + } } } @@ -1426,6 +1429,18 @@ impl Builder { menu.init_for_nsapp(); } + // 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 + 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); let runtime_handle = runtime.handle(); @@ -1616,42 +1631,45 @@ fn on_event_loop_event, RunEvent) + 'static>( } RuntimeRunEvent::Resumed => RunEvent::Resumed, RuntimeRunEvent::MainEventsCleared => RunEvent::MainEventsCleared, - RuntimeRunEvent::MenuEvent(e) => { - for listener in &*app_handle - .manager - .inner - .menu_event_listeners - .lock() - .unwrap() - { - listener(app_handle, e) - } - 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) + RuntimeRunEvent::UserEvent(t) => { + match t { + EventLoopMessage::MenuEvent(e) => { + for listener in &*app_handle + .manager + .inner + .menu_event_listeners + .lock() + .unwrap() + { + listener(app_handle, e) + } + 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) + } + } + } + EventLoopMessage::TrayIconEvent(e) => { + for listener in &*app_handle + .manager + .inner + .tray_event_listeners + .lock() + .unwrap() + { + listener(app_handle, e) + } } } - RunEvent::MenuEvent(e) - } - RuntimeRunEvent::TrayIconEvent(e) => { - for listener in &*app_handle - .manager - .inner - .tray_event_listeners - .lock() - .unwrap() - { - listener(app_handle, e) - } - RunEvent::TrayIconEvent(e) + + t.into() } - RuntimeRunEvent::UserEvent(t) => t.into(), _ => unimplemented!(), }; diff --git a/core/tauri/src/lib.rs b/core/tauri/src/lib.rs index fd4340dab4ee..0aa0ecb83ce2 100644 --- a/core/tauri/src/lib.rs +++ b/core/tauri/src/lib.rs @@ -237,7 +237,12 @@ 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. + 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. + TrayIconEvent(tray::TrayIconEvent), +} /// The webview runtime interface. A wrapper around [`runtime::Runtime`] with the proper user event type associated. pub trait Runtime: runtime::Runtime {} From 0c53ebc664cc40c535f7ddbb746505f90e0940d8 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Thu, 20 Jul 2023 14:07:36 -0300 Subject: [PATCH 012/123] remove unused messages --- core/tauri-runtime-wry/src/lib.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/core/tauri-runtime-wry/src/lib.rs b/core/tauri-runtime-wry/src/lib.rs index 41c5f8c064c4..fb93da8e5ae1 100644 --- a/core/tauri-runtime-wry/src/lib.rs +++ b/core/tauri-runtime-wry/src/lib.rs @@ -16,7 +16,6 @@ use tauri_runtime::{ http::{header::CONTENT_TYPE, Request as HttpRequest, RequestParts, Response as HttpResponse}, menu, monitor::Monitor, - tray, webview::{WebviewIpcHandler, WindowBuilder, WindowBuilderBase}, window::{ dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}, @@ -1027,8 +1026,6 @@ pub enum Message { Box (String, WryWindowBuilder) + Send>, Sender>>, ), - MenuEvent(menu::MenuEvent), - TrayIconEvent(tray::TrayIconEvent), UserEvent(T), } @@ -2260,8 +2257,6 @@ fn handle_user_message( } } - Message::MenuEvent(_) => (), - Message::TrayIconEvent(_) => (), Message::UserEvent(_) => (), } From 37497a179837361d2c0630bf59921c3b9f86ecb9 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Fri, 21 Jul 2023 02:56:28 +0300 Subject: [PATCH 013/123] revert menu changes and enhance docs --- core/tauri-runtime-wry/src/lib.rs | 2 -- core/tauri/src/app.rs | 40 ++++++++++++++++++------------- core/tauri/src/window.rs | 12 ++++++++-- 3 files changed, 33 insertions(+), 21 deletions(-) diff --git a/core/tauri-runtime-wry/src/lib.rs b/core/tauri-runtime-wry/src/lib.rs index a319d7b777df..ae25a63908f9 100644 --- a/core/tauri-runtime-wry/src/lib.rs +++ b/core/tauri-runtime-wry/src/lib.rs @@ -2562,8 +2562,6 @@ fn create_webview( target_os = "openbsd" ))] let _ = menu.init_for_gtk_window(window.gtk_window()); - #[cfg(target_os = "macos")] - menu.init_for_nsapp(); } webview_id_map.insert(window.id(), window_id); diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index f3d968bb1246..71e95afa6cf2 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -520,29 +520,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. @@ -556,6 +550,9 @@ macro_rules! shared_app_impl { } /// 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. pub fn set_menu(&self, menu: Menu) -> crate::Result> { let prev_menu = self.remove_menu()?; @@ -593,6 +590,9 @@ macro_rules! shared_app_impl { } /// 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. pub fn remove_menu(&self) -> crate::Result> { let mut current_menu = self.manager.menu_lock(); @@ -635,6 +635,9 @@ macro_rules! shared_app_impl { } /// 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. pub fn hide_menu(&self) -> crate::Result<()> { if let Some(menu) = &*self.manager.menu_lock() { for window in self.manager.windows_lock().values() { @@ -661,6 +664,9 @@ macro_rules! shared_app_impl { } /// 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. pub fn show_menu(&self) -> crate::Result<()> { if let Some(menu) = &*self.manager.menu_lock() { for window in self.manager.windows_lock().values() { diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index 0644f1cb7b0b..ab2e00c47e42 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -1072,6 +1072,11 @@ impl Window { } /// 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. pub fn set_menu(&self, menu: Menu) -> crate::Result> { let prev_menu = self.remove_menu()?; @@ -1098,6 +1103,11 @@ impl Window { } /// 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 mut current_menu = self.menu_lock(); @@ -1117,8 +1127,6 @@ impl Window { { let _ = menu.remove_for_gtk_window(&self.gtk_window()?); } - #[cfg(target_os = "macos")] - menu.remove_for_nsapp(); } let prev_menu = current_menu.take().map(|m| m.1); From 80f4f2ed7e37701b16883ce936c9ffec78891a23 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Mon, 31 Jul 2023 14:52:39 +0300 Subject: [PATCH 014/123] add show_context_menu function --- core/tauri-runtime/src/window/dpi.rs | 27 +++++++++++++++++++++++++++ core/tauri/src/window.rs | 23 ++++++++++++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/core/tauri-runtime/src/window/dpi.rs b/core/tauri-runtime/src/window/dpi.rs index 0710db98dbc6..25435c00820c 100644 --- a/core/tauri-runtime/src/window/dpi.rs +++ b/core/tauri-runtime/src/window/dpi.rs @@ -399,3 +399,30 @@ impl From> for Position { Position::Logical(position.cast()) } } + +impl

From> for crate::menu::LogicalPosition

{ + fn from(value: LogicalPosition

) -> Self { + Self { + x: value.x, + y: value.y, + } + } +} + +impl

From> for crate::menu::PhysicalPosition

{ + fn from(value: PhysicalPosition

) -> Self { + Self { + x: value.x, + y: value.y, + } + } +} + +impl From for crate::menu::Position { + fn from(value: Position) -> Self { + match value { + Position::Physical(p) => Self::Physical(p.into()), + Position::Logical(p) => Self::Logical(p.into()), + } + } +} diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index ab2e00c47e42..81f82a9c1b9a 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -4,7 +4,7 @@ //! The Tauri window types and functions. -use tauri_runtime::menu::MenuEvent; +use tauri_runtime::menu::{ContextMenu, MenuEvent}; pub use tauri_utils::{config::Color, WindowEffect as Effect, WindowEffectState as EffectState}; use url::Url; @@ -1209,6 +1209,27 @@ impl Window { Ok(false) } + /// Shows the specified menu as a context menu at the specified position. + /// If a position was not provided, the cursor position will be used. + /// + /// The position is relative to the window's top-left corner. + pub fn show_context_menu>( + &self, + menu: &dyn ContextMenu, + position: Option

, + ) -> crate::Result<()> { + let position = position.map(|p| p.into()); + + #[cfg(target_os = "windows")] + menu.show_context_menu_for_hwnd(self.hwnd()? as _, position); + #[cfg(target_os = "linux")] + menu.show_context_menu_for_gtk_window(&self.gtk_window()?, position); + #[cfg(target_os = "macos")] + menu.show_context_menu_for_nsview(self.ns_view() as _, position); + + Ok(()) + } + /// Executes a closure, providing it with the webview handle that is specific to the current platform. /// /// The closure is executed on the main thread. From 0b235db0a66daf647c96880d349c9d1f648c6687 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Mon, 31 Jul 2023 17:35:44 +0300 Subject: [PATCH 015/123] inital work for menu wrapper types --- core/tauri-runtime/Cargo.toml | 2 +- core/tauri/src/app.rs | 80 ++++++++++----------- core/tauri/src/error.rs | 9 +++ core/tauri/src/lib.rs | 6 +- core/tauri/src/manager.rs | 38 +++++----- core/tauri/src/menu.rs | 89 ----------------------- core/tauri/src/menu/builders/mod.rs | 7 ++ core/tauri/src/menu/menu.rs | 108 ++++++++++++++++++++++++++++ core/tauri/src/menu/mod.rs | 102 ++++++++++++++++++++++++++ core/tauri/src/menu/normal.rs | 80 +++++++++++++++++++++ core/tauri/src/window.rs | 62 +++++++++------- tooling/cli/Cargo.lock | 4 +- 12 files changed, 406 insertions(+), 181 deletions(-) delete mode 100644 core/tauri/src/menu.rs create mode 100644 core/tauri/src/menu/builders/mod.rs create mode 100644 core/tauri/src/menu/menu.rs create mode 100644 core/tauri/src/menu/mod.rs create mode 100644 core/tauri/src/menu/normal.rs diff --git a/core/tauri-runtime/Cargo.toml b/core/tauri-runtime/Cargo.toml index 0d09c58c889d..0877a2a2dc52 100644 --- a/core/tauri-runtime/Cargo.toml +++ b/core/tauri-runtime/Cargo.toml @@ -34,7 +34,7 @@ raw-window-handle = "0.5" rand = "0.8" url = { version = "2" } muda = { git = "https://github.com/tauri-apps/muda", default-features = false } -tray-icon = { git = "https://github.com/tauri-apps/tray-icon", default-features = false } +tray-icon = { path = "../../../tray-icon", default-features = false } [target."cfg(windows)".dependencies.windows] version = "0.48" diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 71e95afa6cf2..130626670229 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -30,7 +30,6 @@ use crate::scope::FsScope; use raw_window_handle::HasRawDisplayHandle; use tauri_macros::default_runtime; use tauri_runtime::{ - menu::{Menu, MenuEvent}, tray::{TrayIcon, TrayIconBuilder, TrayIconEvent}, window::{ dpi::{PhysicalPosition, PhysicalSize}, @@ -46,7 +45,10 @@ use std::{ sync::{mpsc::Sender, Arc, Weak}, }; -use crate::runtime::RuntimeHandle; +use crate::{ + menu::{Menu, MenuEvent}, + runtime::RuntimeHandle, +}; #[cfg(target_os = "macos")] use crate::ActivationPolicy; @@ -545,7 +547,7 @@ macro_rules! shared_app_impl { } /// Returns the app-wide menu. - pub fn menu(&self) -> Option

{ + pub fn menu(&self) -> Option> { self.manager.menu_lock().clone() } @@ -553,7 +555,7 @@ macro_rules! shared_app_impl { /// /// If a window was not created with an explicit menu or had one set explicitly, /// this menu will be assigned to it. - pub fn set_menu(&self, menu: Menu) -> crate::Result> { + pub fn set_menu(&self, menu: Menu) -> crate::Result>> { let prev_menu = self.remove_menu()?; self.manager.insert_menu_into_stash(&menu); @@ -564,9 +566,10 @@ macro_rules! shared_app_impl { for window in self.manager.windows_lock().values() { let mut window_menu = window.menu_lock(); if window_menu.as_ref().map(|m| m.0).unwrap_or(true) { + // TODO(muda-migration): make it thread-safe #[cfg(windows)] { - let _ = menu.init_for_hwnd(window.hwnd().unwrap().0); + let _ = menu.inner().init_for_hwnd(window.hwnd().unwrap().0); } #[cfg(any( target_os = "linux", @@ -576,7 +579,9 @@ macro_rules! shared_app_impl { target_os = "openbsd" ))] { - let _ = menu.init_for_gtk_window(&window.gtk_window().unwrap()); + let _ = menu + .inner() + .init_for_gtk_window(&window.gtk_window().unwrap()); } window_menu.replace((true, menu.clone())); } @@ -593,7 +598,7 @@ macro_rules! shared_app_impl { /// /// If a window was not created with an explicit menu or had one set explicitly, /// this will remove the menu from it. - pub fn remove_menu(&self) -> crate::Result> { + pub fn remove_menu(&self) -> crate::Result>> { let mut current_menu = self.manager.menu_lock(); // remove from windows that have the app-wide menu @@ -602,7 +607,7 @@ macro_rules! shared_app_impl { if window.has_app_wide_menu() { #[cfg(windows)] { - let _ = menu.remove_for_hwnd(window.hwnd()?.0); + let _ = menu.inner().remove_for_hwnd(window.hwnd()?.0); } #[cfg(any( target_os = "linux", @@ -612,7 +617,7 @@ macro_rules! shared_app_impl { target_os = "openbsd" ))] { - let _ = menu.remove_for_gtk_window(&window.gtk_window()?); + let _ = menu.inner().remove_for_gtk_window(&window.gtk_window()?); } *window.menu_lock() = None; } @@ -629,7 +634,7 @@ macro_rules! shared_app_impl { self .manager - .remove_menu_from_stash_by_id(prev_menu.as_ref().map(|m| m.id())); + .remove_menu_from_stash_by_id(prev_menu.as_ref().and_then(|m| m.id().ok())); Ok(prev_menu) } @@ -644,7 +649,7 @@ macro_rules! shared_app_impl { if window.has_app_wide_menu() { #[cfg(windows)] { - let _ = menu.hide_for_hwnd(window.hwnd()?.0); + let _ = menu.inner().hide_for_hwnd(window.hwnd()?.0); } #[cfg(any( target_os = "linux", @@ -654,7 +659,7 @@ macro_rules! shared_app_impl { target_os = "openbsd" ))] { - let _ = menu.hide_for_gtk_window(&window.gtk_window()?); + let _ = menu.inner().hide_for_gtk_window(&window.gtk_window()?); } } } @@ -673,7 +678,7 @@ macro_rules! shared_app_impl { if window.has_app_wide_menu() { #[cfg(windows)] { - let _ = menu.show_for_hwnd(window.hwnd()?.0); + let _ = menu.inner().show_for_hwnd(window.hwnd()?.0); } #[cfg(any( target_os = "linux", @@ -683,7 +688,7 @@ macro_rules! shared_app_impl { target_os = "openbsd" ))] { - let _ = menu.show_for_gtk_window(&window.gtk_window()?); + let _ = menu.inner().show_for_gtk_window(&window.gtk_window()?); } } } @@ -905,10 +910,10 @@ pub struct Builder { state: StateManager, /// The menu set to all windows. - menu: Option, + menu: Option>, /// A closure that returns the menu set to all windows. - menu_with: Option Menu>>, + menu_with: Option Menu>>, /// Enable macOS default menu creation. #[allow(unused)] @@ -1219,28 +1224,12 @@ impl Builder { /// ])); /// ``` #[must_use] - pub fn menu(mut self, menu: Menu) -> Self { + pub fn menu(mut self, menu: Menu) -> Self { self.menu_with = None; self.menu.replace(menu); self } - /// Sets a closure to construct the menu to use on all windows. - /// - /// # Examples - /// ``` - /// use tauri::{MenuEntry, Submenu, MenuItem, Menu, CustomMenuItem}; - /// - /// tauri::Builder::default() - /// .menu_with(tauri::menu::default); - /// ``` - #[must_use] - pub fn menu_with Menu + 'static>(mut self, f: F) -> Self { - self.menu_with.replace(Box::new(move |c| f(c))); - self.menu = None; - self - } - /// Enable or disable the default menu on macOS. Enabled by default. /// /// # Examples @@ -1428,12 +1417,9 @@ impl Builder { } if let Some(menu) = &menu { - manager - .inner - .menus - .lock() - .unwrap() - .insert(menu.id(), menu.clone()); + if let Ok(id) = menu.id() { + manager.inner.menus.lock().unwrap().insert(id, menu.clone()); + } } let runtime_args = RuntimeInitArgs { @@ -1445,7 +1431,8 @@ impl Builder { unsafe { let msg = msg as *const MSG; for menu in menus.lock().unwrap().values() { - let translated = TranslateAcceleratorW((*msg).hwnd, HACCEL(menu.haccel()), msg); + let translated = + TranslateAcceleratorW((*msg).hwnd, HACCEL(menu.inner().haccel()), msg); if translated == 1 { return true; } @@ -1599,10 +1586,15 @@ fn setup(app: &mut App) -> crate::Result<()> { let pending = app .manager .prepare_window(app.handle.clone(), pending, &window_labels)?; - let menu = pending - .menu() - .cloned() - .map(|m| (pending.has_app_wide_menu, m)); + let menu = pending.menu().cloned().map(|m| { + ( + pending.has_app_wide_menu, + Menu { + inner: m, + app_handle: app.handle(), + }, + ) + }); let window_effects = pending.webview_attributes.window_effects.clone(); let detached = if let RuntimeOrDispatch::RuntimeHandle(runtime) = app.handle().runtime() { runtime.create_window(pending)? diff --git a/core/tauri/src/error.rs b/core/tauri/src/error.rs index 03ad98f8900a..f5749b5a04b8 100644 --- a/core/tauri/src/error.rs +++ b/core/tauri/src/error.rs @@ -89,4 +89,13 @@ 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}")] + Menu(#[from] tauri_runtime::menu::Error), + /// Tray icon error. + #[error("tray icon error: {0}")] + Tray(#[from] tauri_runtime::tray::Error), } diff --git a/core/tauri/src/lib.rs b/core/tauri/src/lib.rs index 4e298a63a5eb..2c42e27b9783 100644 --- a/core/tauri/src/lib.rs +++ b/core/tauri/src/lib.rs @@ -150,7 +150,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; diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index 51b0321d4322..e28e5283166c 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -10,10 +10,11 @@ use std::{ sync::{Arc, Mutex, MutexGuard}, }; +use crate::menu::Menu; use serde::Serialize; use serde_json::Value as JsonValue; use serialize_to_javascript::{default_template, DefaultTemplate, Template}; -use tauri_runtime::{menu::Menu, tray::TrayIcon}; +use tauri_runtime::tray::TrayIcon; use url::Url; use tauri_macros::default_runtime; @@ -225,9 +226,9 @@ pub struct InnerWindowManager { /// /// This should be mainly used to acceess [`Menu::haccel`] /// to setup the accelerator handling in the event loop - pub menus: Arc>>, + pub menus: Arc>>>, /// The menu set to all windows. - pub(crate) menu: Arc>>, + pub(crate) menu: Arc>>>, /// Menu event listeners to all windows. pub(crate) menu_event_listeners: Arc>>>>, /// Menu event listeners to specific windows. @@ -312,7 +313,7 @@ impl WindowManager { state: StateManager, window_event_listeners: Vec>, (menu, menu_event_listeners, window_menu_event_listeners): ( - Option, + Option>, Vec>>, HashMap>>, ), @@ -370,12 +371,12 @@ impl WindowManager { } /// App-wide menu. - pub(crate) fn menu_lock(&self) -> MutexGuard<'_, Option> { + pub(crate) fn menu_lock(&self) -> MutexGuard<'_, Option>> { self.inner.menu.lock().expect("poisoned window manager") } /// Menus stash. - pub(crate) fn menus_stash_lock(&self) -> MutexGuard<'_, HashMap> { + pub(crate) fn menus_stash_lock(&self) -> MutexGuard<'_, HashMap>> { self.inner.menus.lock().expect("poisoned window manager") } @@ -383,13 +384,15 @@ impl WindowManager { self .menu_lock() .as_ref() - .map(|m| m.id() == id) + .map(|m| m.id().map(|i| i == id).unwrap_or(false)) .unwrap_or(false) } /// Menus stash. - pub(crate) fn insert_menu_into_stash(&self, menu: &Menu) { - self.menus_stash_lock().insert(menu.id(), menu.clone()); + pub(crate) fn insert_menu_into_stash(&self, menu: &Menu) { + if let Ok(id) = menu.id() { + self.menus_stash_lock().insert(id, menu.clone()); + } } pub(crate) fn remove_menu_from_stash_by_id(&self, id: Option) { @@ -1176,16 +1179,17 @@ impl WindowManager { })); if let Some(menu) = &*self.inner.menu.lock().unwrap() { - pending = pending.set_app_menu(menu.clone()); + pending = pending.set_app_menu(menu.inner().clone()); } if let Some(menu) = pending.menu() { - self - .inner - .menus - .lock() - .unwrap() - .insert(menu.id(), menu.clone()); + self.inner.menus.lock().unwrap().insert( + menu.id(), + Menu { + inner: menu.clone(), + app_handle: app_handle.clone(), + }, + ); } Ok(pending) @@ -1195,7 +1199,7 @@ impl WindowManager { &self, app_handle: AppHandle, window: DetachedWindow, - menu: Option<(bool, Menu)>, + menu: Option<(bool, Menu)>, ) -> Window { let window = Window::new(self.clone(), window, app_handle, menu); diff --git a/core/tauri/src/menu.rs b/core/tauri/src/menu.rs deleted file mode 100644 index 3c6afce7bd3c..000000000000 --- a/core/tauri/src/menu.rs +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -//! Menu types and utility functions - -use tauri_utils::config::Config; - -pub use crate::runtime::menu::*; - -// TODO(muda-migration): wrapper types on tauri's side that will implement unsafe sync & send and hide unnecssary APIs -// TODO(muda-migration): figure out js events - -/// Creates a menu filled with default menu items and submenus. -pub fn default(config: &Config) -> Menu { - let about_metadata = AboutMetadata { - name: config.package.product_name.clone(), - version: config.package.version.clone(), - copyright: config.tauri.bundle.copyright.clone(), - authors: config.tauri.bundle.publisher.clone().map(|p| vec![p]), - ..Default::default() - }; - - Menu::with_items(&[ - #[cfg(target_os = "macos")] - &Submenu::with_items( - config.package.binary_name().unwrap_or_default(), - true, - &[ - &PredefinedMenuItem::about(None, Some(about_metadata.clone())), - &PredefinedMenuItem::separator(), - &PredefinedMenuItem::services(None), - &PredefinedMenuItem::separator(), - &PredefinedMenuItem::hide(None), - &PredefinedMenuItem::hide_others(None), - &PredefinedMenuItem::separator(), - &PredefinedMenuItem::quit(None), - ], - ) - .unwrap(), - #[cfg(not(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - )))] - &Submenu::with_items( - "File", - true, - &[ - &PredefinedMenuItem::close_window(None), - #[cfg(not(target_os = "macos"))] - &PredefinedMenuItem::quit(None), - ], - ) - .unwrap(), - &Submenu::with_items( - "Edit", - true, - &[ - &PredefinedMenuItem::undo(None), - &PredefinedMenuItem::redo(None), - &PredefinedMenuItem::separator(), - &PredefinedMenuItem::cut(None), - &PredefinedMenuItem::copy(None), - &PredefinedMenuItem::paste(None), - &PredefinedMenuItem::select_all(None), - ], - ) - .unwrap(), - #[cfg(target_os = "macos")] - &Submenu::with_items("View", true, &[&PredefinedMenuItem::fullscreen(None)]).unwrap(), - &Submenu::with_items( - "Window", - true, - &[ - &PredefinedMenuItem::minimize(None), - &PredefinedMenuItem::maximize(None), - #[cfg(target_os = "macos")] - &PredefinedMenuItem::separator(), - &PredefinedMenuItem::close_window(None), - &PredefinedMenuItem::about(None, Some(about_metadata)), - ], - ) - .unwrap(), - ]) - .unwrap() -} diff --git a/core/tauri/src/menu/builders/mod.rs b/core/tauri/src/menu/builders/mod.rs new file mode 100644 index 000000000000..65e1aa0f5136 --- /dev/null +++ b/core/tauri/src/menu/builders/mod.rs @@ -0,0 +1,7 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! A module containting menu builder types + +pub use crate::runtime::menu::builders::AboutMetadataBuilder; diff --git a/core/tauri/src/menu/menu.rs b/core/tauri/src/menu/menu.rs new file mode 100644 index 000000000000..6b4a235ab742 --- /dev/null +++ b/core/tauri/src/menu/menu.rs @@ -0,0 +1,108 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use super::{IsMenuItem, MenuItemKind}; +use crate::{menu::getter, runtime::menu as muda, AppHandle, Runtime}; + +/// 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) inner: muda::Menu, + pub(crate) app_handle: AppHandle, +} + +impl Clone for Menu { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + app_handle: self.app_handle.clone(), + } + } +} + +unsafe impl super::ContextMenu for Menu {} +unsafe impl super::sealed::ContextMenuBase for Menu { + fn inner(&self) -> &dyn muda::ContextMenu { + &self.inner + } +} + +unsafe impl Sync for Menu {} +unsafe impl Send for Menu {} + +impl Menu { + /// Creates a new menu. + pub fn new(app_handle: &AppHandle) -> Self { + Self { + inner: muda::Menu::new(), + app_handle: app_handle.clone(), + } + } + + /// Creates a new menu with given `items`. It calls [`Menu::new`] and [`Menu::append_items`] internally. + pub fn with_items( + app_handle: &AppHandle, + items: &[&dyn IsMenuItem], + ) -> crate::Result { + let menu = Self::new(app_handle); + menu.append_items(items)?; + Ok(menu) + } + + pub(crate) fn inner(&self) -> &muda::Menu { + &self.inner + } + + /// Returns a unique identifier associated with this menu. + pub fn id(&self) -> crate::Result { + getter!(self, |self_: Self| self_.inner.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(); + getter!(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(()) + } + + /// 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(); + getter!(self, |self_: Self| self_ + .inner + .items() + .into_iter() + .map(|i| match i { + muda::MenuItemKind::MenuItem(i) => super::MenuItemKind::MenuItem(super::MenuItem { + inner: i, + app_handle: handle.clone(), + }), + muda::MenuItemKind::Submenu(_) => todo!(), + muda::MenuItemKind::Predefined(_) => todo!(), + muda::MenuItemKind::Check(_) => todo!(), + muda::MenuItemKind::Icon(_) => todo!(), + }) + .collect::>()) + } +} diff --git a/core/tauri/src/menu/mod.rs b/core/tauri/src/menu/mod.rs new file mode 100644 index 000000000000..059254241855 --- /dev/null +++ b/core/tauri/src/menu/mod.rs @@ -0,0 +1,102 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! Menu types and utility functions + +// TODO(muda-migration): figure out js events + +pub mod builders; +mod menu; +mod normal; +pub use menu::Menu; +pub use normal::MenuItem; + +pub use crate::runtime::menu::{AboutMetadata, MenuEvent}; +use crate::Runtime; + +use crate::runtime::menu as muda; + +macro_rules! getter { + ($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) + }}; +} + +pub(crate) use getter; + +/// An enumeration of all menu item kinds that could be added to +/// a [`Menu`] or [`Submenu`] +pub enum MenuItemKind { + /// Normal menu item + MenuItem(MenuItem), +} + +impl MenuItemKind { + /// Returns a unique identifier associated with this menu item. + pub fn id(&self) -> crate::Result { + match self { + MenuItemKind::MenuItem(i) => i.id(), + } + } + + pub(crate) fn inner(&self) -> &dyn IsMenuItem { + match self { + MenuItemKind::MenuItem(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), + } + } + + /// 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, + } + } +} + +/// 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 unsafe 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) -> crate::Result { + self.kind().id() + } +} + +/// 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 unsafe trait ContextMenu {} + +pub(crate) mod sealed { + pub unsafe trait IsMenuItemBase { + fn inner(&self) -> &dyn super::muda::IsMenuItem; + } + + pub unsafe trait ContextMenuBase { + fn inner(&self) -> &dyn super::muda::ContextMenu; + } +} diff --git a/core/tauri/src/menu/normal.rs b/core/tauri/src/menu/normal.rs new file mode 100644 index 000000000000..1b89cab13afb --- /dev/null +++ b/core/tauri/src/menu/normal.rs @@ -0,0 +1,80 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use crate::{menu::getter, runtime::menu as muda, AppHandle, Runtime}; + +/// A menu item inside a [`Menu`] or [`Submenu`] and contains only text. +/// +/// [`Menu`]: super::Menu +/// [`Submenu`]: super::Submenu +pub struct MenuItem { + pub(crate) inner: muda::MenuItem, + pub(crate) app_handle: AppHandle, +} + +impl Clone for MenuItem { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + app_handle: self.app_handle.clone(), + } + } +} + +unsafe impl Sync for MenuItem {} +unsafe impl Send for MenuItem {} + +unsafe impl super::sealed::IsMenuItemBase for MenuItem { + fn inner(&self) -> &dyn muda::IsMenuItem { + &self.inner + } +} + +unsafe impl super::IsMenuItem for MenuItem { + fn kind(&self) -> super::MenuItemKind { + super::MenuItemKind::MenuItem(self.clone()) + } + + fn id(&self) -> crate::Result { + 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>( + app_handle: &AppHandle, + text: S, + enabled: bool, + acccelerator: Option, + ) -> Self { + Self { + inner: muda::MenuItem::new( + text, + enabled, + acccelerator.and_then(|s| { + let s = s.as_ref(); + s.parse().ok() + }), + ), + app_handle: app_handle.clone(), + } + } + + /// Returns a unique identifier associated with this menu item. + pub fn id(&self) -> crate::Result { + getter!(self, |self_: Self| self_.inner.id()) + } + + /// 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(); + getter!(self, |self_: Self| self_.inner.set_text(text)) + } +} diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index 81f82a9c1b9a..706e1425cf2e 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -35,8 +35,8 @@ use crate::{ }; #[cfg(desktop)] use crate::{ + menu::Menu, runtime::{ - menu::Menu, window::dpi::{Position, Size}, UserAttentionType, }, @@ -323,10 +323,15 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { let pending = self .manager .prepare_window(self.app_handle.clone(), pending, &labels)?; - let menu = pending - .menu() - .cloned() - .map(|m| (pending.has_app_wide_menu, m)); + let menu = pending.menu().cloned().map(|m| { + ( + pending.has_app_wide_menu, + Menu { + inner: m, + app_handle: self.app_handle.clone(), + }, + ) + }); let window_effects = pending.webview_attributes.window_effects.clone(); let window = match &mut self.runtime { RuntimeOrDispatch::Runtime(runtime) => runtime.create_window(pending), @@ -365,8 +370,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.window_builder = self.window_builder.menu(menu.inner().clone()); self } @@ -783,7 +788,7 @@ pub struct Window { pub(crate) app_handle: AppHandle, js_event_listeners: Arc>>>, // The menu set for this window - pub(crate) menu: Arc>>, + pub(crate) menu: Arc)>>>, } impl std::fmt::Debug for Window { @@ -957,7 +962,7 @@ impl Window { manager: WindowManager, window: DetachedWindow, app_handle: AppHandle, - menu: Option<(bool, Menu)>, + menu: Option<(bool, Menu)>, ) -> Self { Self { window, @@ -1050,7 +1055,7 @@ impl Window { .insert(self.label().to_string(), Box::new(f)); } - pub(crate) fn menu_lock(&self) -> MutexGuard<'_, Option<(bool, Menu)>> { + pub(crate) fn menu_lock(&self) -> MutexGuard<'_, Option<(bool, Menu)>> { self.menu.lock().expect("poisoned window") } @@ -1062,12 +1067,12 @@ impl Window { self .menu_lock() .as_ref() - .map(|m| m.1.id() == id) + .map(|m| m.1.id().map(|i| i == id).unwrap_or(false)) .unwrap_or(false) } /// Returns this window menu . - pub fn menu(&self) -> Option { + pub fn menu(&self) -> Option> { self.menu_lock().as_ref().map(|m| m.1.clone()) } @@ -1077,14 +1082,15 @@ impl Window { /// /// - **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. - pub fn set_menu(&self, menu: Menu) -> crate::Result> { + pub fn set_menu(&self, menu: Menu) -> crate::Result>> { let prev_menu = self.remove_menu()?; self.manager.insert_menu_into_stash(&menu); + // TODO(muda-migration): make it thread-safe #[cfg(windows)] { - let _ = menu.init_for_hwnd(self.hwnd().unwrap().0); + let _ = menu.inner().init_for_hwnd(self.hwnd().unwrap().0); } #[cfg(any( target_os = "linux", @@ -1094,7 +1100,9 @@ impl Window { target_os = "openbsd" ))] { - let _ = menu.init_for_gtk_window(&self.gtk_window().unwrap()); + let _ = menu + .inner() + .init_for_gtk_window(&self.gtk_window().unwrap()); } self.menu_lock().replace((false, menu.clone())); @@ -1108,14 +1116,14 @@ impl Window { /// /// - **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> { + pub fn remove_menu(&self) -> crate::Result>> { let mut current_menu = self.menu_lock(); // remove from the window if let Some((_, menu)) = &*current_menu { #[cfg(windows)] { - let _ = menu.remove_for_hwnd(self.hwnd()?.0); + let _ = menu.inner().remove_for_hwnd(self.hwnd()?.0); } #[cfg(any( target_os = "linux", @@ -1125,7 +1133,7 @@ impl Window { target_os = "openbsd" ))] { - let _ = menu.remove_for_gtk_window(&self.gtk_window()?); + let _ = menu.inner().remove_for_gtk_window(&self.gtk_window()?); } } @@ -1135,7 +1143,7 @@ impl Window { self .manager - .remove_menu_from_stash_by_id(prev_menu.as_ref().map(|m| m.id())); + .remove_menu_from_stash_by_id(prev_menu.as_ref().and_then(|m| m.id().ok())); Ok(prev_menu) } @@ -1146,7 +1154,7 @@ impl Window { if let Some((_, menu)) = &*self.menu_lock() { #[cfg(windows)] { - let _ = menu.hide_for_hwnd(self.hwnd()?.0); + let _ = menu.inner().hide_for_hwnd(self.hwnd()?.0); } #[cfg(any( target_os = "linux", @@ -1156,7 +1164,7 @@ impl Window { target_os = "openbsd" ))] { - let _ = menu.hide_for_gtk_window(&self.gtk_window()?); + let _ = menu.inner().hide_for_gtk_window(&self.gtk_window()?); } } @@ -1169,7 +1177,7 @@ impl Window { if let Some((_, menu)) = &*self.menu_lock() { #[cfg(windows)] { - let _ = menu.show_for_hwnd(self.hwnd()?.0); + let _ = menu.inner().show_for_hwnd(self.hwnd()?.0); } #[cfg(any( target_os = "linux", @@ -1179,7 +1187,7 @@ impl Window { target_os = "openbsd" ))] { - let _ = menu.show_for_gtk_window(&self.gtk_window()?); + let _ = menu.inner().show_for_gtk_window(&self.gtk_window()?); } } @@ -1192,7 +1200,7 @@ impl Window { if let Some((_, menu)) = &*self.menu_lock() { #[cfg(windows)] { - return Ok(menu.is_visible_on_hwnd(self.hwnd()?.0)); + return Ok(menu.inner().is_visible_on_hwnd(self.hwnd()?.0)); } #[cfg(any( target_os = "linux", @@ -1202,7 +1210,7 @@ impl Window { target_os = "openbsd" ))] { - return Ok(menu.is_visible_on_gtk_window(&self.gtk_window()?)); + return Ok(menu.inner().is_visible_on_gtk_window(&self.gtk_window()?)); } } @@ -1218,10 +1226,10 @@ impl Window { menu: &dyn ContextMenu, position: Option

, ) -> crate::Result<()> { - let position = position.map(|p| p.into()); + let position = position.map(|p| p.into()).map(|p| p.into()); #[cfg(target_os = "windows")] - menu.show_context_menu_for_hwnd(self.hwnd()? as _, position); + menu.show_context_menu_for_hwnd(self.hwnd()?.0, position); #[cfg(target_os = "linux")] menu.show_context_menu_for_gtk_window(&self.gtk_window()?, position); #[cfg(target_os = "macos")] diff --git a/tooling/cli/Cargo.lock b/tooling/cli/Cargo.lock index 70d39850e62a..4d28da92669b 100644 --- a/tooling/cli/Cargo.lock +++ b/tooling/cli/Cargo.lock @@ -3907,9 +3907,9 @@ dependencies = [ [[package]] name = "tar" -version = "0.4.38" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" +checksum = "ec96d2ffad078296368d46ff1cb309be1c23c513b4ab0e22a45de0185275ac96" dependencies = [ "filetime", "libc", From e6fbf7648314b42bd1bd903dfaed346ae8c48e38 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Mon, 31 Jul 2023 12:31:38 -0300 Subject: [PATCH 016/123] fix build on macos [skip ci] --- core/tauri/src/app.rs | 8 ++++---- core/tauri/src/window.rs | 19 ++++++++++++++++++- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 130626670229..63ed77fe250d 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -589,7 +589,7 @@ macro_rules! shared_app_impl { // set it app-wide for macos #[cfg(target_os = "macos")] - menu.init_for_nsapp(); + menu.inner().init_for_nsapp(); Ok(prev_menu) } @@ -625,7 +625,7 @@ macro_rules! shared_app_impl { // remove app-wide for macos #[cfg(target_os = "macos")] - menu.remove_for_nsapp(); + menu.inner().remove_for_nsapp(); } let prev_menu = current_menu.take(); @@ -1382,7 +1382,7 @@ impl Builder { pub fn build(mut self, context: Context) -> crate::Result> { #[cfg(target_os = "macos")] if self.menu.is_none() && self.menu_with.is_none() && self.enable_macos_default_menu { - self.menu = Some(crate::menu::default(context.config())); + // TODO self.menu = Some(crate::menu::default(context.config())); } let menu = match (self.menu, self.menu_with) { @@ -1455,7 +1455,7 @@ impl Builder { #[cfg(target_os = "macos")] if let Some(menu) = menu { - menu.init_for_nsapp(); + menu.inner().init_for_nsapp(); } // setup menu event handler diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index 706e1425cf2e..dc33b7cd2b40 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -1233,7 +1233,7 @@ impl Window { #[cfg(target_os = "linux")] menu.show_context_menu_for_gtk_window(&self.gtk_window()?, position); #[cfg(target_os = "macos")] - menu.show_context_menu_for_nsview(self.ns_view() as _, position); + menu.show_context_menu_for_nsview(self.ns_view()? as _, position); Ok(()) } @@ -1452,6 +1452,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 { From 35dca0d16407e2b5301022b868421a876d19ba87 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Tue, 1 Aug 2023 01:02:27 +0300 Subject: [PATCH 017/123] ensure thread safety for context items --- core/tauri/build.rs | 8 ++ core/tauri/src/app.rs | 116 +++++++++++++------------- core/tauri/src/menu/menu.rs | 40 ++++++++- core/tauri/src/menu/mod.rs | 25 +++++- core/tauri/src/window.rs | 157 +++++++++++++++++++----------------- 5 files changed, 211 insertions(+), 135 deletions(-) diff --git a/core/tauri/build.rs b/core/tauri/build.rs index 29ea69f97a24..0c5a9499e24f 100644 --- a/core/tauri/build.rs +++ b/core/tauri/build.rs @@ -49,6 +49,14 @@ fn main() { let mobile = target_os == "ios" || target_os == "android"; alias("desktop", !mobile); alias("mobile", mobile); + alias( + "linux", + target_os == "linux" + || target_os == "dragonfly" + || target_os == "freebsd" + || target_os == "netbsd" + || target_os == "openbsd", + ); let checked_features_out_path = Path::new(&var("OUT_DIR").unwrap()).join("checked_features"); std::fs::write( diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 63ed77fe250d..904ff4950b24 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -566,23 +566,21 @@ macro_rules! shared_app_impl { for window in self.manager.windows_lock().values() { let mut window_menu = window.menu_lock(); if window_menu.as_ref().map(|m| m.0).unwrap_or(true) { - // TODO(muda-migration): make it thread-safe #[cfg(windows)] - { - let _ = menu.inner().init_for_hwnd(window.hwnd().unwrap().0); - } - #[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ))] - { - let _ = menu - .inner() - .init_for_gtk_window(&window.gtk_window().unwrap()); - } + let hwnd = window.hwnd()?.0; + #[cfg(linux)] + let gtk_window = window.gtk_window()?; + let menu_c = menu.clone(); + self.run_on_main_thread(move || { + #[cfg(windows)] + { + let _ = menu_c.inner().init_for_hwnd(hwnd); + } + #[cfg(linux)] + { + let _ = menu_c.inner().init_for_gtk_window(>k_window); + } + })?; window_menu.replace((true, menu.clone())); } } @@ -606,19 +604,20 @@ macro_rules! shared_app_impl { for window in self.manager.windows_lock().values() { if window.has_app_wide_menu() { #[cfg(windows)] - { - let _ = menu.inner().remove_for_hwnd(window.hwnd()?.0); - } - #[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ))] - { - let _ = menu.inner().remove_for_gtk_window(&window.gtk_window()?); - } + let hwnd = window.hwnd()?.0; + #[cfg(linux)] + let gtk_window = window.gtk_window()?; + let menu_c = menu.clone(); + self.run_on_main_thread(move || { + #[cfg(windows)] + { + let _ = menu_c.inner().remove_for_hwnd(hwnd); + } + #[cfg(linux)] + { + let _ = menu_c.inner().remove_for_gtk_window(>k_window); + } + })?; *window.menu_lock() = None; } } @@ -648,19 +647,20 @@ macro_rules! shared_app_impl { for window in self.manager.windows_lock().values() { if window.has_app_wide_menu() { #[cfg(windows)] - { - let _ = menu.inner().hide_for_hwnd(window.hwnd()?.0); - } - #[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ))] - { - let _ = menu.inner().hide_for_gtk_window(&window.gtk_window()?); - } + let hwnd = window.hwnd()?.0; + #[cfg(linux)] + let gtk_window = window.gtk_window()?; + let menu_c = menu.clone(); + self.run_on_main_thread(move || { + #[cfg(windows)] + { + let _ = menu_c.inner().hide_for_hwnd(hwnd); + } + #[cfg(linux)] + { + let _ = menu_c.inner().hide_for_gtk_window(>k_window); + } + })?; } } } @@ -677,19 +677,20 @@ macro_rules! shared_app_impl { for window in self.manager.windows_lock().values() { if window.has_app_wide_menu() { #[cfg(windows)] - { - let _ = menu.inner().show_for_hwnd(window.hwnd()?.0); - } - #[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ))] - { - let _ = menu.inner().show_for_gtk_window(&window.gtk_window()?); - } + let hwnd = window.hwnd()?.0; + #[cfg(linux)] + let gtk_window = window.gtk_window()?; + let menu_c = menu.clone(); + self.run_on_main_thread(move || { + #[cfg(windows)] + { + let _ = menu_c.inner().show_for_hwnd(hwnd); + } + #[cfg(linux)] + { + let _ = menu_c.inner().show_for_gtk_window(>k_window); + } + })?; } } } @@ -732,6 +733,11 @@ 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() diff --git a/core/tauri/src/menu/menu.rs b/core/tauri/src/menu/menu.rs index 6b4a235ab742..d7c2141db3b0 100644 --- a/core/tauri/src/menu/menu.rs +++ b/core/tauri/src/menu/menu.rs @@ -4,6 +4,7 @@ use super::{IsMenuItem, MenuItemKind}; use crate::{menu::getter, runtime::menu as muda, AppHandle, Runtime}; +use muda::ContextMenu; /// 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. @@ -12,6 +13,9 @@ pub struct Menu { pub(crate) app_handle: AppHandle, } +unsafe impl Sync for Menu {} +unsafe impl Send for Menu {} + impl Clone for Menu { fn clone(&self) -> Self { Self { @@ -26,10 +30,40 @@ unsafe impl super::sealed::ContextMenuBase for Menu { fn inner(&self) -> &dyn muda::ContextMenu { &self.inner } -} -unsafe impl Sync for Menu {} -unsafe impl Send for Menu {} + #[cfg(windows)] + fn show_context_menu_for_hwnd( + &self, + hwnd: isize, + position: Option, + ) -> crate::Result<()> { + getter!(self, |self_: Self| self_ + .inner() + .show_context_menu_for_hwnd(hwnd, position.map(Into::into))) + } + + #[cfg(linux)] + fn show_context_menu_for_gtk_window( + &self, + w: >k::ApplicationWindow, + position: Option, + ) -> crate::Result<()> { + getter!(self, |self_: Self| self_ + .inner() + .show_context_menu_for_gtk_window(w, position.map(Into::into))) + } + + #[cfg(target_os = "macos")] + fn show_context_menu_for_nsview( + &self, + view: cocoa::base::id, + position: Option, + ) -> crate::Result<()> { + getter!(self, |self_: Self| self_ + .inner() + .show_context_menu_for_ns_view(view, position.map(Into::into))) + } +} impl Menu { /// Creates a new menu. diff --git a/core/tauri/src/menu/mod.rs b/core/tauri/src/menu/mod.rs index 059254241855..a987b3394501 100644 --- a/core/tauri/src/menu/mod.rs +++ b/core/tauri/src/menu/mod.rs @@ -89,14 +89,37 @@ pub unsafe trait IsMenuItem: sealed::IsMenuItemBase { /// # Safety /// /// This trait is ONLY meant to be implemented internally by the crate. -pub unsafe trait ContextMenu {} +pub unsafe trait ContextMenu: sealed::ContextMenuBase + Sync {} pub(crate) mod sealed { + use crate::Position; + pub unsafe trait IsMenuItemBase { fn inner(&self) -> &dyn super::muda::IsMenuItem; } pub unsafe trait ContextMenuBase { fn inner(&self) -> &dyn super::muda::ContextMenu; + + #[cfg(windows)] + fn show_context_menu_for_hwnd( + &self, + hwnd: isize, + position: Option, + ) -> crate::Result<()>; + + #[cfg(linux)] + fn show_context_menu_for_gtk_window( + &self, + w: >k::ApplicationWindow, + position: Option, + ) -> crate::Result<()>; + + #[cfg(target_os = "macos")] + fn show_context_menu_for_nsview( + &self, + view: cocoa::base::id, + position: Option, + ) -> crate::Result<()>; } } diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index dc33b7cd2b40..625d2c39df0e 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -4,7 +4,7 @@ //! The Tauri window types and functions. -use tauri_runtime::menu::{ContextMenu, MenuEvent}; +use crate::menu::{ContextMenu, MenuEvent}; pub use tauri_utils::{config::Color, WindowEffect as Effect, WindowEffectState as EffectState}; use url::Url; @@ -54,7 +54,7 @@ use std::{ fmt, hash::{Hash, Hasher}, path::PathBuf, - sync::{Arc, Mutex, MutexGuard}, + sync::{mpsc::channel, Arc, Mutex, MutexGuard}, }; pub(crate) type WebResourceRequestHandler = dyn Fn(&HttpRequest, &mut HttpResponse) + Send + Sync; @@ -1087,23 +1087,21 @@ impl Window { self.manager.insert_menu_into_stash(&menu); - // TODO(muda-migration): make it thread-safe #[cfg(windows)] - { - let _ = menu.inner().init_for_hwnd(self.hwnd().unwrap().0); - } - #[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ))] - { - let _ = menu - .inner() - .init_for_gtk_window(&self.gtk_window().unwrap()); - } + let hwnd = self.hwnd()?.0; + #[cfg(linux)] + let gtk_window = self.gtk_window()?; + let menu_c = menu.clone(); + self.run_on_main_thread(move || { + #[cfg(windows)] + { + let _ = menu_c.inner().init_for_hwnd(hwnd); + } + #[cfg(linux)] + { + let _ = menu_c.inner().init_for_gtk_window(>k_window); + } + })?; self.menu_lock().replace((false, menu.clone())); @@ -1122,19 +1120,20 @@ impl Window { // remove from the window if let Some((_, menu)) = &*current_menu { #[cfg(windows)] - { - let _ = menu.inner().remove_for_hwnd(self.hwnd()?.0); - } - #[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ))] - { - let _ = menu.inner().remove_for_gtk_window(&self.gtk_window()?); - } + let hwnd = self.hwnd()?.0; + #[cfg(linux)] + let gtk_window = self.gtk_window()?; + let menu_c = menu.clone(); + self.run_on_main_thread(move || { + #[cfg(windows)] + { + let _ = menu_c.inner().remove_for_hwnd(hwnd); + } + #[cfg(linux)] + { + let _ = menu_c.inner().remove_for_gtk_window(>k_window); + } + })?; } let prev_menu = current_menu.take().map(|m| m.1); @@ -1153,19 +1152,20 @@ impl Window { // remove from the window if let Some((_, menu)) = &*self.menu_lock() { #[cfg(windows)] - { - let _ = menu.inner().hide_for_hwnd(self.hwnd()?.0); - } - #[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ))] - { - let _ = menu.inner().hide_for_gtk_window(&self.gtk_window()?); - } + let hwnd = self.hwnd()?.0; + #[cfg(linux)] + let gtk_window = self.gtk_window()?; + let menu_c = menu.clone(); + self.run_on_main_thread(move || { + #[cfg(windows)] + { + let _ = menu_c.inner().hide_for_hwnd(hwnd); + } + #[cfg(linux)] + { + let _ = menu_c.inner().hide_for_gtk_window(>k_window); + } + })?; } Ok(()) @@ -1176,19 +1176,20 @@ impl Window { // remove from the window if let Some((_, menu)) = &*self.menu_lock() { #[cfg(windows)] - { - let _ = menu.inner().show_for_hwnd(self.hwnd()?.0); - } - #[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ))] - { - let _ = menu.inner().show_for_gtk_window(&self.gtk_window()?); - } + let hwnd = self.hwnd()?.0; + #[cfg(linux)] + let gtk_window = self.gtk_window()?; + let menu_c = menu.clone(); + self.run_on_main_thread(move || { + #[cfg(windows)] + { + let _ = menu_c.inner().show_for_hwnd(hwnd); + } + #[cfg(linux)] + { + let _ = menu_c.inner().show_for_gtk_window(>k_window); + } + })?; } Ok(()) @@ -1198,20 +1199,24 @@ impl Window { pub fn is_menu_visible(&self) -> crate::Result { // remove from the window if let Some((_, menu)) = &*self.menu_lock() { + let (tx, rx) = channel(); #[cfg(windows)] - { - return Ok(menu.inner().is_visible_on_hwnd(self.hwnd()?.0)); - } - #[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ))] - { - return Ok(menu.inner().is_visible_on_gtk_window(&self.gtk_window()?)); - } + let hwnd = self.hwnd()?.0; + #[cfg(linux)] + let gtk_window = self.gtk_window()?; + let menu_c = menu.clone(); + self.run_on_main_thread(move || { + #[cfg(windows)] + { + let _ = tx.send(menu_c.inner().is_visible_on_hwnd(hwnd)); + } + #[cfg(linux)] + { + let _ = tx.send(menu_c.inner().is_visible_on_gtk_window(>k_window)); + } + })?; + + return Ok(rx.recv().unwrap_or(false)); } Ok(false) @@ -1226,14 +1231,14 @@ impl Window { menu: &dyn ContextMenu, position: Option

, ) -> crate::Result<()> { - let position = position.map(|p| p.into()).map(|p| p.into()); + let position = position.map(|p| p.into()); - #[cfg(target_os = "windows")] - menu.show_context_menu_for_hwnd(self.hwnd()?.0, position); - #[cfg(target_os = "linux")] - menu.show_context_menu_for_gtk_window(&self.gtk_window()?, position); + #[cfg(windows)] + menu.show_context_menu_for_hwnd(self.hwnd()?.0, position)?; + #[cfg(linux)] + menu.show_context_menu_for_gtk_window(&self.gtk_window()?, position)?; #[cfg(target_os = "macos")] - menu.show_context_menu_for_nsview(self.ns_view()? as _, position); + menu.show_context_menu_for_nsview(self.ns_view()? as _, position)?; Ok(()) } From dd36a3829b7f70d522966595e4742e4da457b1e7 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Tue, 1 Aug 2023 02:11:24 +0300 Subject: [PATCH 018/123] finish menu wrapper types --- core/tauri/src/menu/builders/mod.rs | 2 + core/tauri/src/menu/check.rs | 113 +++++++++++++ core/tauri/src/menu/icon.rs | 151 +++++++++++++++++ core/tauri/src/menu/menu.rs | 103 ++++++++++-- core/tauri/src/menu/mod.rs | 97 ++++++++++- core/tauri/src/menu/normal.rs | 35 +++- core/tauri/src/menu/predefined.rs | 246 ++++++++++++++++++++++++++++ core/tauri/src/menu/submenu.rs | 215 ++++++++++++++++++++++++ 8 files changed, 941 insertions(+), 21 deletions(-) create mode 100644 core/tauri/src/menu/check.rs create mode 100644 core/tauri/src/menu/icon.rs create mode 100644 core/tauri/src/menu/predefined.rs create mode 100644 core/tauri/src/menu/submenu.rs diff --git a/core/tauri/src/menu/builders/mod.rs b/core/tauri/src/menu/builders/mod.rs index 65e1aa0f5136..f06c081cd41e 100644 --- a/core/tauri/src/menu/builders/mod.rs +++ b/core/tauri/src/menu/builders/mod.rs @@ -5,3 +5,5 @@ //! A module containting menu builder types pub use crate::runtime::menu::builders::AboutMetadataBuilder; + +// TODO (muda-migration): add builder types diff --git a/core/tauri/src/menu/check.rs b/core/tauri/src/menu/check.rs new file mode 100644 index 000000000000..2d1ae9d08ca6 --- /dev/null +++ b/core/tauri/src/menu/check.rs @@ -0,0 +1,113 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use crate::{menu::run_main_thread, runtime::menu as muda, AppHandle, Runtime}; + +/// A menu item inside a [`Menu`] or [`Submenu`] and contains only text. +/// +/// [`Menu`]: super::Menu +/// [`Submenu`]: super::Submenu +pub struct CheckMenuItem { + pub(crate) inner: muda::CheckMenuItem, + pub(crate) app_handle: AppHandle, +} + +impl Clone for CheckMenuItem { + fn clone(&self) -> Self { + Self { + 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 {} + +unsafe impl super::sealed::IsMenuItemBase for CheckMenuItem { + fn inner(&self) -> &dyn muda::IsMenuItem { + &self.inner + } +} + +unsafe impl super::IsMenuItem for CheckMenuItem { + fn kind(&self) -> super::MenuItemKind { + super::MenuItemKind::Check(self.clone()) + } + + fn id(&self) -> crate::Result { + 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>( + app_handle: &AppHandle, + text: S, + enabled: bool, + chceked: bool, + acccelerator: Option, + ) -> Self { + Self { + inner: muda::CheckMenuItem::new( + text, + enabled, + chceked, + acccelerator.and_then(|s| s.as_ref().parse().ok()), + ), + app_handle: app_handle.clone(), + } + } + + /// Returns a unique identifier associated with this menu item. + pub fn id(&self) -> crate::Result { + run_main_thread!(self, |self_: Self| self_.inner.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..0700547ea335 --- /dev/null +++ b/core/tauri/src/menu/icon.rs @@ -0,0 +1,151 @@ +// 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::run_main_thread, runtime::menu as muda, AppHandle, Icon, Runtime}; + +/// A menu item inside a [`Menu`] or [`Submenu`] and contains only text. +/// +/// [`Menu`]: super::Menu +/// [`Submenu`]: super::Submenu +pub struct IconMenuItem { + pub(crate) inner: muda::IconMenuItem, + pub(crate) app_handle: AppHandle, +} + +impl Clone for IconMenuItem { + fn clone(&self) -> Self { + Self { + 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 {} + +unsafe impl super::sealed::IsMenuItemBase for IconMenuItem { + fn inner(&self) -> &dyn muda::IsMenuItem { + &self.inner + } +} + +unsafe impl super::IsMenuItem for IconMenuItem { + fn kind(&self) -> super::MenuItemKind { + super::MenuItemKind::Icon(self.clone()) + } + + fn id(&self) -> crate::Result { + 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>( + app_handle: &AppHandle, + text: S, + enabled: bool, + icon: Option, + acccelerator: Option, + ) -> Self { + Self { + inner: muda::IconMenuItem::new( + text, + enabled, + icon + .and_then(|i| -> Option { i.try_into().ok() }) + .and_then(|i| i.try_into().ok()), + acccelerator.and_then(|s| s.as_ref().parse().ok()), + ), + app_handle: 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>( + app_handle: &AppHandle, + text: S, + enabled: bool, + native_icon: Option, + acccelerator: Option, + ) -> Self { + Self { + inner: muda::IconMenuItem::with_native_icon( + text, + enabled, + native_icon, + acccelerator.and_then(|s| s.as_ref().parse().ok()), + ), + app_handle: app_handle.clone(), + } + } + + /// Returns a unique identifier associated with this menu item. + pub fn id(&self) -> crate::Result { + run_main_thread!(self, |self_: Self| self_.inner.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| -> Option { i.try_into().ok() }) + .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, |self_: Self| self_.inner.set_native_icon(_icon)); + Ok(()) + } +} diff --git a/core/tauri/src/menu/menu.rs b/core/tauri/src/menu/menu.rs index d7c2141db3b0..e08312de7cf6 100644 --- a/core/tauri/src/menu/menu.rs +++ b/core/tauri/src/menu/menu.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: MIT use super::{IsMenuItem, MenuItemKind}; -use crate::{menu::getter, runtime::menu as muda, AppHandle, Runtime}; +use crate::{menu::run_main_thread, runtime::menu as muda, AppHandle, Runtime}; use muda::ContextMenu; /// A type that is either a menu bar on the window @@ -13,6 +13,9 @@ pub struct 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 {} @@ -37,7 +40,7 @@ unsafe impl super::sealed::ContextMenuBase for Menu { hwnd: isize, position: Option, ) -> crate::Result<()> { - getter!(self, |self_: Self| self_ + run_main_thread!(self, |self_: Self| self_ .inner() .show_context_menu_for_hwnd(hwnd, position.map(Into::into))) } @@ -48,7 +51,7 @@ unsafe impl super::sealed::ContextMenuBase for Menu { w: >k::ApplicationWindow, position: Option, ) -> crate::Result<()> { - getter!(self, |self_: Self| self_ + run_main_thread!(self, |self_: Self| self_ .inner() .show_context_menu_for_gtk_window(w, position.map(Into::into))) } @@ -59,7 +62,7 @@ unsafe impl super::sealed::ContextMenuBase for Menu { view: cocoa::base::id, position: Option, ) -> crate::Result<()> { - getter!(self, |self_: Self| self_ + run_main_thread!(self, |self_: Self| self_ .inner() .show_context_menu_for_ns_view(view, position.map(Into::into))) } @@ -90,7 +93,7 @@ impl Menu { /// Returns a unique identifier associated with this menu. pub fn id(&self) -> crate::Result { - getter!(self, |self_: Self| self_.inner.id()) + run_main_thread!(self, |self_: Self| self_.inner.id()) } /// Add a menu item to the end of this menu. @@ -102,7 +105,8 @@ impl Menu { /// [`Submenu`]: super::Submenu pub fn append(&self, item: &dyn IsMenuItem) -> crate::Result<()> { let kind = item.kind(); - getter!(self, |self_: Self| self_.inner.append(kind.inner().inner()))?.map_err(Into::into) + 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. @@ -120,10 +124,73 @@ impl Menu { 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) + } + /// 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(); - getter!(self, |self_: Self| self_ + run_main_thread!(self, |self_: Self| self_ .inner .items() .into_iter() @@ -132,10 +199,24 @@ impl Menu { inner: i, app_handle: handle.clone(), }), - muda::MenuItemKind::Submenu(_) => todo!(), - muda::MenuItemKind::Predefined(_) => todo!(), - muda::MenuItemKind::Check(_) => todo!(), - muda::MenuItemKind::Icon(_) => todo!(), + muda::MenuItemKind::Submenu(i) => super::MenuItemKind::Submenu(super::Submenu { + inner: i, + app_handle: handle.clone(), + }), + muda::MenuItemKind::Predefined(i) => { + super::MenuItemKind::Predefined(super::PredefinedMenuItem { + inner: i, + app_handle: handle.clone(), + }) + } + muda::MenuItemKind::Check(i) => super::MenuItemKind::Check(super::CheckMenuItem { + inner: i, + app_handle: handle.clone(), + }), + muda::MenuItemKind::Icon(i) => super::MenuItemKind::Icon(super::IconMenuItem { + inner: i, + app_handle: handle.clone(), + }), }) .collect::>()) } diff --git a/core/tauri/src/menu/mod.rs b/core/tauri/src/menu/mod.rs index a987b3394501..f60326850e56 100644 --- a/core/tauri/src/menu/mod.rs +++ b/core/tauri/src/menu/mod.rs @@ -4,20 +4,29 @@ //! Menu types and utility functions +// TODO(muda-migration): add default menu // TODO(muda-migration): figure out js events pub mod builders; +mod check; +mod icon; mod menu; mod normal; +mod predefined; +mod submenu; +pub use check::CheckMenuItem; +pub use icon::IconMenuItem; pub use menu::Menu; pub use normal::MenuItem; +pub use predefined::PredefinedMenuItem; +pub use submenu::Submenu; -pub use crate::runtime::menu::{AboutMetadata, MenuEvent}; +pub use crate::runtime::menu::{icon::NativeIcon, AboutMetadata, MenuEvent}; use crate::Runtime; use crate::runtime::menu as muda; -macro_rules! getter { +macro_rules! run_main_thread { ($self:ident, $ex:expr) => {{ use std::sync::mpsc::channel; @@ -31,13 +40,21 @@ macro_rules! getter { }}; } -pub(crate) use getter; +pub(crate) use run_main_thread; /// 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 { @@ -45,12 +62,20 @@ impl MenuItemKind { pub fn id(&self) -> crate::Result { 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, } } @@ -58,6 +83,7 @@ impl MenuItemKind { pub fn as_menuitem(&self) -> Option<&MenuItem> { match self { MenuItemKind::MenuItem(i) => Some(i), + _ => None, } } @@ -65,6 +91,71 @@ impl MenuItemKind { 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"), } } } diff --git a/core/tauri/src/menu/normal.rs b/core/tauri/src/menu/normal.rs index 1b89cab13afb..ac82900031b5 100644 --- a/core/tauri/src/menu/normal.rs +++ b/core/tauri/src/menu/normal.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use crate::{menu::getter, runtime::menu as muda, AppHandle, Runtime}; +use crate::{menu::run_main_thread, runtime::menu as muda, AppHandle, Runtime}; /// A menu item inside a [`Menu`] or [`Submenu`] and contains only text. /// @@ -22,6 +22,9 @@ impl Clone for MenuItem { } } +/// # Safety +/// +/// We make sure it always runs on the main thread. unsafe impl Sync for MenuItem {} unsafe impl Send for MenuItem {} @@ -56,10 +59,7 @@ impl MenuItem { inner: muda::MenuItem::new( text, enabled, - acccelerator.and_then(|s| { - let s = s.as_ref(); - s.parse().ok() - }), + acccelerator.and_then(|s| s.as_ref().parse().ok()), ), app_handle: app_handle.clone(), } @@ -67,7 +67,12 @@ impl MenuItem { /// Returns a unique identifier associated with this menu item. pub fn id(&self) -> crate::Result { - getter!(self, |self_: Self| self_.inner.id()) + run_main_thread!(self, |self_: Self| self_.inner.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 @@ -75,6 +80,22 @@ impl MenuItem { /// 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(); - getter!(self, |self_: Self| self_.inner.set_text(text)) + 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..fe29b031bca0 --- /dev/null +++ b/core/tauri/src/menu/predefined.rs @@ -0,0 +1,246 @@ +// 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::run_main_thread, runtime::menu as muda, AppHandle, Runtime}; + +/// A predefined (native) menu item which has a predfined behavior by the OS or by this crate. +pub struct PredefinedMenuItem { + pub(crate) inner: muda::PredefinedMenuItem, + pub(crate) app_handle: AppHandle, +} + +impl Clone for PredefinedMenuItem { + fn clone(&self) -> Self { + Self { + 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 {} + +unsafe impl super::sealed::IsMenuItemBase for PredefinedMenuItem { + fn inner(&self) -> &dyn muda::IsMenuItem { + &self.inner + } +} + +unsafe impl super::IsMenuItem for PredefinedMenuItem { + fn kind(&self) -> super::MenuItemKind { + super::MenuItemKind::Predefined(self.clone()) + } + + fn id(&self) -> crate::Result { + self.id() + } +} + +impl PredefinedMenuItem { + /// Separator menu item + pub fn separator(app_handle: &AppHandle) -> Self { + Self { + inner: muda::PredefinedMenuItem::separator(), + app_handle: app_handle.clone(), + } + } + + /// Copy menu item + pub fn copy(app_handle: &AppHandle, text: Option<&str>) -> Self { + Self { + inner: muda::PredefinedMenuItem::copy(text), + app_handle: app_handle.clone(), + } + } + + /// Cut menu item + pub fn cut(app_handle: &AppHandle, text: Option<&str>) -> Self { + Self { + inner: muda::PredefinedMenuItem::cut(text), + app_handle: app_handle.clone(), + } + } + + /// Paste menu item + pub fn paste(app_handle: &AppHandle, text: Option<&str>) -> Self { + Self { + inner: muda::PredefinedMenuItem::paste(text), + app_handle: app_handle.clone(), + } + } + + /// SelectAll menu item + pub fn select_all(app_handle: &AppHandle, text: Option<&str>) -> Self { + Self { + inner: muda::PredefinedMenuItem::select_all(text), + app_handle: app_handle.clone(), + } + } + + /// Undo menu item + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux:** Unsupported. + pub fn undo(app_handle: &AppHandle, text: Option<&str>) -> Self { + Self { + inner: muda::PredefinedMenuItem::undo(text), + app_handle: app_handle.clone(), + } + } + /// Redo menu item + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux:** Unsupported. + pub fn redo(app_handle: &AppHandle, text: Option<&str>) -> Self { + Self { + inner: muda::PredefinedMenuItem::redo(text), + app_handle: app_handle.clone(), + } + } + + /// Minimize window menu item + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported. + pub fn minimize(app_handle: &AppHandle, text: Option<&str>) -> Self { + Self { + inner: muda::PredefinedMenuItem::minimize(text), + app_handle: app_handle.clone(), + } + } + + /// Maximize window menu item + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported. + pub fn maximize(app_handle: &AppHandle, text: Option<&str>) -> Self { + Self { + inner: muda::PredefinedMenuItem::maximize(text), + app_handle: app_handle.clone(), + } + } + + /// Fullscreen menu item + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux:** Unsupported. + pub fn fullscreen(app_handle: &AppHandle, text: Option<&str>) -> Self { + Self { + inner: muda::PredefinedMenuItem::fullscreen(text), + app_handle: app_handle.clone(), + } + } + + /// Hide window menu item + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported. + pub fn hide(app_handle: &AppHandle, text: Option<&str>) -> Self { + Self { + inner: muda::PredefinedMenuItem::hide(text), + app_handle: app_handle.clone(), + } + } + + /// Hide other windows menu item + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported. + pub fn hide_others(app_handle: &AppHandle, text: Option<&str>) -> Self { + Self { + inner: muda::PredefinedMenuItem::hide_others(text), + app_handle: app_handle.clone(), + } + } + + /// Show all app windows menu item + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux:** Unsupported. + pub fn show_all(app_handle: &AppHandle, text: Option<&str>) -> Self { + Self { + inner: muda::PredefinedMenuItem::show_all(text), + app_handle: app_handle.clone(), + } + } + + /// Close window menu item + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported. + pub fn close_window(app_handle: &AppHandle, text: Option<&str>) -> Self { + Self { + inner: muda::PredefinedMenuItem::show_all(text), + app_handle: app_handle.clone(), + } + } + + /// Quit app menu item + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported. + pub fn quit(app_handle: &AppHandle, text: Option<&str>) -> Self { + Self { + inner: muda::PredefinedMenuItem::quit(text), + app_handle: app_handle.clone(), + } + } + + /// About app menu item + pub fn about( + app_handle: &AppHandle, + text: Option<&str>, + metadata: Option, + ) -> Self { + Self { + inner: muda::PredefinedMenuItem::about(text, metadata), + app_handle: app_handle.clone(), + } + } + + /// Services menu item + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux:** Unsupported. + pub fn services(app_handle: &AppHandle, text: Option<&str>) -> Self { + Self { + inner: muda::PredefinedMenuItem::services(text), + app_handle: app_handle.clone(), + } + } + + /// Returns a unique identifier associated with this menu item. + pub fn id(&self) -> crate::Result { + Ok(0) + } + + /// 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)) + } +} diff --git a/core/tauri/src/menu/submenu.rs b/core/tauri/src/menu/submenu.rs new file mode 100644 index 000000000000..36007a0b0563 --- /dev/null +++ b/core/tauri/src/menu/submenu.rs @@ -0,0 +1,215 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use super::{IsMenuItem, MenuItemKind}; +use crate::{menu::run_main_thread, runtime::menu as muda, AppHandle, Runtime}; +use muda::ContextMenu; + +/// A type that is a submenu inside a [`Menu`] or [`Submenu`] +/// +/// [`Menu`]: super::Menu +/// [`Submenu`]: super::Submenu +pub struct Submenu { + 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 { + inner: self.inner.clone(), + app_handle: self.app_handle.clone(), + } + } +} + +unsafe impl super::sealed::IsMenuItemBase for Submenu { + fn inner(&self) -> &dyn muda::IsMenuItem { + &self.inner + } +} + +unsafe impl super::IsMenuItem for Submenu { + fn kind(&self) -> super::MenuItemKind { + super::MenuItemKind::Submenu(self.clone()) + } + + fn id(&self) -> crate::Result { + self.id() + } +} + +unsafe impl super::ContextMenu for Submenu {} +unsafe impl super::sealed::ContextMenuBase for Submenu { + fn inner(&self) -> &dyn muda::ContextMenu { + &self.inner + } + + #[cfg(windows)] + fn show_context_menu_for_hwnd( + &self, + hwnd: isize, + position: Option, + ) -> crate::Result<()> { + run_main_thread!(self, |self_: Self| { + self_ + .inner() + .show_context_menu_for_hwnd(hwnd, position.map(Into::into)) + }) + } + + #[cfg(linux)] + fn show_context_menu_for_gtk_window( + &self, + w: >k::ApplicationWindow, + position: Option, + ) -> crate::Result<()> { + run_main_thread!(self, |self_: Self| { + self_ + .inner() + .show_context_menu_for_gtk_window(w, position.map(Into::into)) + }) + } + + #[cfg(target_os = "macos")] + fn show_context_menu_for_nsview( + &self, + view: cocoa::base::id, + position: Option, + ) -> crate::Result<()> { + run_main_thread!(self, |self_: Self| { + self_ + .inner() + .show_context_menu_for_ns_view(view, position.map(Into::into)) + }) + } +} + +impl Submenu { + /// Creates a new submenu. + pub fn new>(app_handle: &AppHandle, text: S, enabled: bool) -> Self { + Self { + inner: muda::Submenu::new(text, enabled), + app_handle: app_handle.clone(), + } + } + + /// Creates a new menu with given `items`. It calls [`Submenu::new`] and [`Submenu::append_items`] internally. + pub fn with_items>( + app_handle: &AppHandle, + text: S, + enabled: bool, + items: &[&dyn IsMenuItem], + ) -> crate::Result { + let menu = Self::new(app_handle, text, enabled); + menu.append_items(items)?; + Ok(menu) + } + + pub(crate) fn inner(&self) -> &muda::Submenu { + &self.inner + } + + /// Returns a unique identifier associated with this submenu. + pub fn id(&self) -> crate::Result { + run_main_thread!(self, |self_: Self| self_.inner.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 { + inner: i, + app_handle: handle.clone(), + }), + muda::MenuItemKind::Submenu(i) => super::MenuItemKind::Submenu(super::Submenu { + inner: i, + app_handle: handle.clone(), + }), + muda::MenuItemKind::Predefined(i) => { + super::MenuItemKind::Predefined(super::PredefinedMenuItem { + inner: i, + app_handle: handle.clone(), + }) + } + muda::MenuItemKind::Check(i) => super::MenuItemKind::Check(super::CheckMenuItem { + inner: i, + app_handle: handle.clone(), + }), + muda::MenuItemKind::Icon(i) => super::MenuItemKind::Icon(super::IconMenuItem { + inner: i, + app_handle: handle.clone(), + }), + }) + .collect::>() + }) + } +} From a4391ad78115ed0c2a9188e9e65fc634893eebbd Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Tue, 1 Aug 2023 05:09:31 +0300 Subject: [PATCH 019/123] add default menu --- core/tauri/src/app.rs | 60 +++++++++++------------ core/tauri/src/manager.rs | 8 ++-- core/tauri/src/menu/menu.rs | 89 ++++++++++++++++++++++++++++++++++- examples/splashscreen/main.rs | 2 +- 4 files changed, 123 insertions(+), 36 deletions(-) diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 904ff4950b24..8a0ba5acc48d 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -915,11 +915,8 @@ 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. - menu_with: Option Menu>>, + menu: Option) -> crate::Result>>>, /// Enable macOS default menu creation. #[allow(unused)] @@ -958,7 +955,6 @@ impl Builder { uri_scheme_protocols: Default::default(), state: StateManager::new(), menu: None, - menu_with: None, enable_macos_default_menu: true, menu_event_listeners: Vec::new(), window_event_listeners: Vec::new(), @@ -1230,9 +1226,11 @@ impl Builder { /// ])); /// ``` #[must_use] - pub fn menu(mut self, menu: Menu) -> Self { - self.menu_with = None; - self.menu.replace(menu); + pub fn menu) -> crate::Result> + 'static>( + mut self, + f: F, + ) -> Self { + self.menu.replace(Box::new(f)); self } @@ -1387,17 +1385,12 @@ impl Builder { #[allow(clippy::type_complexity)] pub fn build(mut self, context: Context) -> crate::Result> { #[cfg(target_os = "macos")] - if self.menu.is_none() && self.menu_with.is_none() && self.enable_macos_default_menu { - // TODO self.menu = Some(crate::menu::default(context.config())); + if self.menu.is_none() && self.enable_macos_default_menu { + self.menu = Some(Box::new(|app_handle| { + crate::menu::Menu::default(app_handle) + })); } - let menu = match (self.menu, self.menu_with) { - (None, Some(f)) => Some(f(context.config())), - (Some(m), None) => Some(m), - (None, None) => None, - _ => unreachable!(), - }; - let manager = WindowManager::with_handlers( context, self.plugins, @@ -1406,7 +1399,7 @@ impl Builder { self.uri_scheme_protocols, self.state, self.window_event_listeners, - (menu.clone(), self.menu_event_listeners, HashMap::new()), + (self.menu_event_listeners, HashMap::new()), self.tray_event_listeners, (self.invoke_responder, self.invoke_initialization_script), ); @@ -1422,12 +1415,6 @@ impl Builder { )?); } - if let Some(menu) = &menu { - if let Ok(id) = menu.id() { - manager.inner.menus.lock().unwrap().insert(id, menu.clone()); - } - } - let runtime_args = RuntimeInitArgs { #[cfg(windows)] msg_hook: { @@ -1492,6 +1479,14 @@ impl Builder { }, }; + if let Some(menu) = self.menu { + let menu = menu(&app.handle)?; + if let Ok(id) = menu.id() { + app.manager.menus_stash_lock().insert(id, menu.clone()); + } + app.manager.menu_lock().replace(menu); + } + app.register_core_plugins()?; let env = Env::default(); @@ -1531,6 +1526,8 @@ impl Builder { { let mut tray_stash = app.manager.inner.tray_icons.lock().unwrap(); let config = app.config(); + + // config tray if let Some(tray_config) = &config.tauri.tray_icon { let mut tray = TrayIconBuilder::new() .with_icon_as_template(tray_config.icon_as_template) @@ -1547,6 +1544,8 @@ impl Builder { } tray_stash.push(tray.build().map_err(Into::::into)?); } + + // tray icon registered on the builder for tray_builder in self.tray_icons { tray_stash.push( tray_builder @@ -1588,27 +1587,28 @@ 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)?; let menu = pending.menu().cloned().map(|m| { ( pending.has_app_wide_menu, Menu { inner: m, - app_handle: app.handle(), + app_handle: app_handle.clone(), }, ) }); let window_effects = pending.webview_attributes.window_effects.clone(); - let detached = if let RuntimeOrDispatch::RuntimeHandle(runtime) = app.handle().runtime() { + let detached = if let RuntimeOrDispatch::RuntimeHandle(runtime) = app_handle.runtime() { runtime.create_window(pending)? } else { // the AppHandle's runtime is always RuntimeOrDispatch::RuntimeHandle unreachable!() }; - let window = app.manager.attach_window(app.handle(), detached, menu); + let window = manager.attach_window(app_handle.clone(), detached, menu); if let Some(effects) = window_effects { crate::vibrancy::set_window_effects(&window, Some(effects))?; diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index e28e5283166c..8bef0d48f0e7 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -312,8 +312,7 @@ impl WindowManager { uri_scheme_protocols: HashMap>>, state: StateManager, window_event_listeners: Vec>, - (menu, menu_event_listeners, window_menu_event_listeners): ( - Option>, + (menu_event_listeners, window_menu_event_listeners): ( Vec>>, HashMap>>, ), @@ -344,7 +343,7 @@ impl WindowManager { pattern: context.pattern, uri_scheme_protocols, menus: Default::default(), - menu: Arc::new(Mutex::new(menu)), + menu: Arc::new(Mutex::new(None)), menu_event_listeners: Arc::new(Mutex::new(menu_event_listeners)), window_menu_event_listeners: Arc::new(Mutex::new(window_menu_event_listeners)), window_event_listeners: Arc::new(window_event_listeners), @@ -1178,7 +1177,8 @@ impl WindowManager { } })); - if let Some(menu) = &*self.inner.menu.lock().unwrap() { + dbg!(self.menu_lock().is_some()); + if let Some(menu) = &*self.menu_lock() { pending = pending.set_app_menu(menu.inner().clone()); } diff --git a/core/tauri/src/menu/menu.rs b/core/tauri/src/menu/menu.rs index e08312de7cf6..d0a30e58210c 100644 --- a/core/tauri/src/menu/menu.rs +++ b/core/tauri/src/menu/menu.rs @@ -2,9 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use super::{IsMenuItem, MenuItemKind}; +use super::{IsMenuItem, MenuItemKind, PredefinedMenuItem, Submenu}; use crate::{menu::run_main_thread, runtime::menu as muda, AppHandle, Runtime}; use muda::ContextMenu; +use tauri_runtime::menu::AboutMetadata; /// 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. @@ -87,6 +88,92 @@ impl Menu { 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() + }; + + Menu::with_items( + app_handle, + &[ + #[cfg(target_os = "macos")] + &Submenu::with_items( + app_handle, + pkg_info.name, + true, + &[ + &PredefinedMenuItem::about(None, Some(about_metadata.clone())), + &PredefinedMenuItem::separator(), + &PredefinedMenuItem::services(None), + &PredefinedMenuItem::separator(), + &PredefinedMenuItem::hide(None), + &PredefinedMenuItem::hide_others(None), + &PredefinedMenuItem::separator(), + &PredefinedMenuItem::quit(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(None)], + )?, + &Submenu::with_items( + app_handle, + "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), + &PredefinedMenuItem::about(app_handle, None, Some(about_metadata)), + ], + )?, + ], + ) + } + pub(crate) fn inner(&self) -> &muda::Menu { &self.inner } diff --git a/examples/splashscreen/main.rs b/examples/splashscreen/main.rs index 915f71e596fb..4c3cbc86647f 100644 --- a/examples/splashscreen/main.rs +++ b/examples/splashscreen/main.rs @@ -61,7 +61,7 @@ mod ui { pub fn main() { let context = super::context(); tauri::Builder::default() - .menu_with(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( From 1cf578879af5592ca01aa9973b580f41968cb52c Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Tue, 1 Aug 2023 06:27:57 +0300 Subject: [PATCH 020/123] add tray icon wrapper types --- core/tauri-runtime/Cargo.toml | 4 +- core/tauri/src/app.rs | 51 +++-- core/tauri/src/lib.rs | 16 ++ core/tauri/src/manager.rs | 5 +- core/tauri/src/menu/check.rs | 2 +- core/tauri/src/menu/icon.rs | 2 +- core/tauri/src/menu/menu.rs | 6 +- core/tauri/src/menu/mod.rs | 20 +- core/tauri/src/menu/normal.rs | 2 +- core/tauri/src/menu/predefined.rs | 2 +- core/tauri/src/menu/submenu.rs | 6 +- core/tauri/src/tray.rs | 316 +++++++++++++++++++++++++++++- 12 files changed, 388 insertions(+), 44 deletions(-) diff --git a/core/tauri-runtime/Cargo.toml b/core/tauri-runtime/Cargo.toml index 0877a2a2dc52..f96fd175deb7 100644 --- a/core/tauri-runtime/Cargo.toml +++ b/core/tauri-runtime/Cargo.toml @@ -33,8 +33,8 @@ http-range = "0.1.4" raw-window-handle = "0.5" rand = "0.8" url = { version = "2" } -muda = { git = "https://github.com/tauri-apps/muda", default-features = false } -tray-icon = { path = "../../../tray-icon", default-features = false } +muda = { version = "0.7", default-features = false } +tray-icon = { version = "0.7", default-features = false } [target."cfg(windows)".dependencies.windows] version = "0.48" diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 8a0ba5acc48d..81b5ea284d94 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -27,10 +27,10 @@ use crate::{ #[cfg(feature = "protocol-asset")] use crate::scope::FsScope; +use crate::tray::{TrayIcon, TrayIconBuilder, TrayIconEvent}; use raw_window_handle::HasRawDisplayHandle; use tauri_macros::default_runtime; use tauri_runtime::{ - tray::{TrayIcon, TrayIconBuilder, TrayIconEvent}, window::{ dpi::{PhysicalPosition, PhysicalSize}, FileDropEvent, @@ -475,9 +475,9 @@ impl App { macro_rules! shared_app_impl { ($app: ty) => { impl $app { - /// Gets the first tray icon register, usually the one configured in + /// Gets the first tray icon registerd, usually the one configured in /// tauri config file. - pub fn tray(&self) -> Option { + pub fn tray(&self) -> Option> { self .manager .inner @@ -488,8 +488,20 @@ macro_rules! shared_app_impl { .cloned() } + /// Removes the first tray icon registerd, usually the one configured in + /// tauri config file, from tauri's internal state and returns it. + /// + /// Note that dropping the returned icon, will cause the tray icon to disappear. + 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. - pub fn tray_by_id(&self, id: u32) -> Option { + pub fn tray_by_id(&self, id: u32) -> Option> { self .manager .inner @@ -497,10 +509,27 @@ macro_rules! shared_app_impl { .lock() .unwrap() .iter() - .find(|t| t.id() == id) + .find(|t| t.id().map(|i| i == id).unwrap_or(false)) .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. + pub fn remove_tray_by_id(&self, id: u32) -> Option> { + let mut tray_icons = self.manager.inner.tray_icons.lock().unwrap(); + + let idx = tray_icons + .iter() + .position(|t| t.id().map(|i| i == id).unwrap_or(false)); + + 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. pub fn config(&self) -> Arc { self.manager.config() @@ -1526,6 +1555,7 @@ impl Builder { { let mut tray_stash = app.manager.inner.tray_icons.lock().unwrap(); let config = app.config(); + let handle = app.handle(); // config tray if let Some(tray_config) = &config.tauri.tray_icon { @@ -1533,8 +1563,7 @@ impl Builder { .with_icon_as_template(tray_config.icon_as_template) .with_menu_on_left_click(tray_config.menu_on_left_click); if let Some(icon) = &app.manager.inner.tray_icon { - let icon: crate::runtime::Icon = icon.clone().try_into()?; - tray = tray.with_icon(icon.try_into()?); + tray = tray.with_icon(icon.clone()); } if let Some(title) = &tray_config.title { tray = tray.with_title(title); @@ -1542,16 +1571,12 @@ impl Builder { if let Some(tooltip) = &tray_config.tooltip { tray = tray.with_tooltip(tooltip); } - tray_stash.push(tray.build().map_err(Into::::into)?); + tray_stash.push(tray.build(&handle)?); } // tray icon registered on the builder for tray_builder in self.tray_icons { - tray_stash.push( - tray_builder - .build() - .map_err(Into::::into)?, - ); + tray_stash.push(tray_builder.build(&handle)?); } } diff --git a/core/tauri/src/lib.rs b/core/tauri/src/lib.rs index 2c42e27b9783..86e266a431c4 100644 --- a/core/tauri/src/lib.rs +++ b/core/tauri/src/lib.rs @@ -920,3 +920,19 @@ mod test_utils { } } } + +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) + }}; +} + +pub(crate) use run_main_thread; diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index 8bef0d48f0e7..0d26bd40040f 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -11,10 +11,10 @@ use std::{ }; use crate::menu::Menu; +use crate::tray::TrayIcon; use serde::Serialize; use serde_json::Value as JsonValue; use serialize_to_javascript::{default_template, DefaultTemplate, Template}; -use tauri_runtime::tray::TrayIcon; use url::Url; use tauri_macros::default_runtime; @@ -237,7 +237,7 @@ pub struct InnerWindowManager { /// Window event listeners to all windows. window_event_listeners: Arc>>, /// Tray icons - pub(crate) tray_icons: Arc>>, + pub(crate) tray_icons: Arc>>>, /// Tray icon event listeners. pub(crate) tray_event_listeners: Arc>>>, /// Responder for invoke calls. @@ -1177,7 +1177,6 @@ impl WindowManager { } })); - dbg!(self.menu_lock().is_some()); if let Some(menu) = &*self.menu_lock() { pending = pending.set_app_menu(menu.inner().clone()); } diff --git a/core/tauri/src/menu/check.rs b/core/tauri/src/menu/check.rs index 2d1ae9d08ca6..0e4baa9bc2be 100644 --- a/core/tauri/src/menu/check.rs +++ b/core/tauri/src/menu/check.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use crate::{menu::run_main_thread, runtime::menu as muda, AppHandle, Runtime}; +use crate::{run_main_thread, runtime::menu as muda, AppHandle, Runtime}; /// A menu item inside a [`Menu`] or [`Submenu`] and contains only text. /// diff --git a/core/tauri/src/menu/icon.rs b/core/tauri/src/menu/icon.rs index 0700547ea335..6182bd5d435f 100644 --- a/core/tauri/src/menu/icon.rs +++ b/core/tauri/src/menu/icon.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: MIT use super::NativeIcon; -use crate::{menu::run_main_thread, runtime::menu as muda, AppHandle, Icon, Runtime}; +use crate::{run_main_thread, runtime::menu as muda, AppHandle, Icon, Runtime}; /// A menu item inside a [`Menu`] or [`Submenu`] and contains only text. /// diff --git a/core/tauri/src/menu/menu.rs b/core/tauri/src/menu/menu.rs index d0a30e58210c..ddbd1717ccd2 100644 --- a/core/tauri/src/menu/menu.rs +++ b/core/tauri/src/menu/menu.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: MIT use super::{IsMenuItem, MenuItemKind, PredefinedMenuItem, Submenu}; -use crate::{menu::run_main_thread, runtime::menu as muda, AppHandle, Runtime}; +use crate::{run_main_thread, runtime::menu as muda, AppHandle, Runtime}; use muda::ContextMenu; use tauri_runtime::menu::AboutMetadata; @@ -35,6 +35,10 @@ unsafe impl super::sealed::ContextMenuBase for Menu { &self.inner } + fn into_inner(&self) -> Box { + Box::new(self.clone().inner) + } + #[cfg(windows)] fn show_context_menu_for_hwnd( &self, diff --git a/core/tauri/src/menu/mod.rs b/core/tauri/src/menu/mod.rs index f60326850e56..d432fd78e1b6 100644 --- a/core/tauri/src/menu/mod.rs +++ b/core/tauri/src/menu/mod.rs @@ -4,7 +4,6 @@ //! Menu types and utility functions -// TODO(muda-migration): add default menu // TODO(muda-migration): figure out js events pub mod builders; @@ -26,22 +25,6 @@ use crate::Runtime; use crate::runtime::menu as muda; -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) - }}; -} - -pub(crate) use run_main_thread; - /// An enumeration of all menu item kinds that could be added to /// a [`Menu`] or [`Submenu`] pub enum MenuItemKind { @@ -180,7 +163,7 @@ pub unsafe trait IsMenuItem: sealed::IsMenuItemBase { /// # Safety /// /// This trait is ONLY meant to be implemented internally by the crate. -pub unsafe trait ContextMenu: sealed::ContextMenuBase + Sync {} +pub unsafe trait ContextMenu: sealed::ContextMenuBase + Send + Sync {} pub(crate) mod sealed { use crate::Position; @@ -191,6 +174,7 @@ pub(crate) mod sealed { pub unsafe trait ContextMenuBase { fn inner(&self) -> &dyn super::muda::ContextMenu; + fn into_inner(&self) -> Box; #[cfg(windows)] fn show_context_menu_for_hwnd( diff --git a/core/tauri/src/menu/normal.rs b/core/tauri/src/menu/normal.rs index ac82900031b5..88a89c740ba6 100644 --- a/core/tauri/src/menu/normal.rs +++ b/core/tauri/src/menu/normal.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use crate::{menu::run_main_thread, runtime::menu as muda, AppHandle, Runtime}; +use crate::{run_main_thread, runtime::menu as muda, AppHandle, Runtime}; /// A menu item inside a [`Menu`] or [`Submenu`] and contains only text. /// diff --git a/core/tauri/src/menu/predefined.rs b/core/tauri/src/menu/predefined.rs index fe29b031bca0..57f624f619eb 100644 --- a/core/tauri/src/menu/predefined.rs +++ b/core/tauri/src/menu/predefined.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: MIT use super::AboutMetadata; -use crate::{menu::run_main_thread, runtime::menu as muda, AppHandle, Runtime}; +use crate::{run_main_thread, runtime::menu as muda, AppHandle, Runtime}; /// A predefined (native) menu item which has a predfined behavior by the OS or by this crate. pub struct PredefinedMenuItem { diff --git a/core/tauri/src/menu/submenu.rs b/core/tauri/src/menu/submenu.rs index 36007a0b0563..bc05cbc6957b 100644 --- a/core/tauri/src/menu/submenu.rs +++ b/core/tauri/src/menu/submenu.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: MIT use super::{IsMenuItem, MenuItemKind}; -use crate::{menu::run_main_thread, runtime::menu as muda, AppHandle, Runtime}; +use crate::{run_main_thread, runtime::menu as muda, AppHandle, Runtime}; use muda::ContextMenu; /// A type that is a submenu inside a [`Menu`] or [`Submenu`] @@ -52,6 +52,10 @@ unsafe impl super::sealed::ContextMenuBase for Submenu { &self.inner } + fn into_inner(&self) -> Box { + Box::new(self.clone().inner) + } + #[cfg(windows)] fn show_context_menu_for_hwnd( &self, diff --git a/core/tauri/src/tray.rs b/core/tauri/src/tray.rs index 74aef22840fc..668afe4a9d49 100644 --- a/core/tauri/src/tray.rs +++ b/core/tauri/src/tray.rs @@ -4,8 +4,320 @@ //! Tray icon types and utility functions -pub use crate::runtime::tray::*; +use std::path::{Path, PathBuf}; + +use crate::runtime::tray as tray_icon; +pub use crate::runtime::tray::TrayIconEvent; +use crate::{run_main_thread, AppHandle, Icon, Runtime}; // TODO(muda-migration): tray icon type `on_event` handler -// TODO(muda-migration): wrapper types on tauri's side that will implement unsafe sync & send and hide unnecssary APIs // TODO(muda-migration): figure out js events + +/// Attributes to use when creating a tray icon. +#[derive(Default)] +pub struct TrayIconAttributes { + /// Tray icon tooltip + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported. + pub tooltip: Option, + + /// Tray menu + /// + /// ## Platform-specific: + /// + /// - **Linux**: once a menu is set, it cannot be removed. + pub menu: Option>, + + /// 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 icon: Option, + + /// Tray icon temp dir path. **Linux only**. + pub temp_dir_path: Option, + + /// Use the icon as a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc). **macOS only**. + pub icon_is_template: bool, + + /// Whether to show the tray menu on left click or not, default is `true`. **macOS only**. + pub menu_on_left_click: bool, + + /// 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 title: Option, +} + +impl From for tray_icon::TrayIconAttributes { + fn from(value: TrayIconAttributes) -> Self { + Self { + tooltip: value.tooltip, + menu: value.menu.map(|m| m.into_inner()), + icon: value.icon.and_then(|i| { + i.try_into() + .ok() + .and_then(|i: crate::runtime::Icon| i.try_into().ok()) + }), + temp_dir_path: value.temp_dir_path, + icon_is_template: value.icon_is_template, + menu_on_left_click: value.menu_on_left_click, + title: value.title, + } + } +} + +/// [`TrayIcon`] builder struct and associated methods. +#[derive(Default)] +pub struct TrayIconBuilder(tray_icon::TrayIconBuilder); + +impl TrayIconBuilder { + /// Creates a new [`TrayIconBuilder`] with default [`TrayIconAttributes`]. + /// + /// See [`TrayIcon::new`] for more info. + pub fn new() -> Self { + Self(tray_icon::TrayIconBuilder::new()) + } + + /// Sets the unique id to build the tray icon with. + pub fn with_id(mut self, id: u32) -> Self { + self.0 = self.0.with_id(id); + self + } + + /// 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 with_menu(mut self, menu: &dyn crate::menu::ContextMenu) -> Self { + self.0 = self.0.with_menu(menu.into_inner()); + 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 with_icon(mut self, icon: Icon) -> Self { + let icon = icon + .try_into() + .ok() + .and_then(|i: crate::runtime::Icon| i.try_into().ok()); + if let Some(icon) = icon { + self.0 = self.0.with_icon(icon); + } + self + } + + /// Set a tooltip for this tray icon. + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported. + pub fn with_tooltip>(mut self, s: S) -> Self { + self.0 = self.0.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 with_title>(mut self, title: S) -> Self { + self.0 = self.0.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 with_temp_dir_path>(mut self, s: P) -> Self { + self.0 = self.0.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 with_icon_as_template(mut self, is_template: bool) -> Self { + self.0 = self.0.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 with_menu_on_left_click(mut self, enable: bool) -> Self { + self.0 = self.0.with_menu_on_left_click(enable); + self + } + + /// Access the unique id that will be assigned to the tray icon + /// this builder will create. + pub fn id(&self) -> u32 { + self.0.id() + } + + /// Builds and adds a new [`TrayIcon`] to the system tray. + pub fn build(self, app_handle: &AppHandle) -> crate::Result> { + Ok(TrayIcon { + inner: self.0.build()?, + app_handle: app_handle.clone(), + }) + } +} + +/// Tray icon struct and associated methods. +/// +/// This type is reference-counted and the icon is removed when the last instance is dropped. +pub struct TrayIcon { + inner: tray_icon::TrayIcon, + app_handle: AppHandle, +} + +impl Clone for TrayIcon { + fn clone(&self) -> Self { + Self { + 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 { + /// Builds and adds a new tray icon to the system tray. + /// + /// ## 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(app_handle: &AppHandle, attrs: TrayIconAttributes) -> crate::Result { + Ok(Self { + inner: tray_icon::TrayIcon::new(attrs.into())?, + app_handle: app_handle.clone(), + }) + } + + /// Builds and adds a new tray icon to the system tray with the specified Id. + /// + /// See [`TrayIcon::new`] for more info. + pub fn with_id( + app_handle: &AppHandle, + attrs: TrayIconAttributes, + id: u32, + ) -> crate::Result { + Ok(Self { + inner: tray_icon::TrayIcon::with_id(attrs.into(), id)?, + app_handle: app_handle.clone(), + }) + } + + /// Returns the id associated with this tray icon. + pub fn id(&self) -> crate::Result { + run_main_thread!(self, |self_: Self| self_.inner.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() + .and_then(|i: crate::runtime::Icon| 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.into_inner()))) + } + + /// 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(()) + } +} From 5c8d40bb95bf9c5b38e2917125aae8b15697e45e Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 1 Aug 2023 08:56:51 -0300 Subject: [PATCH 021/123] fix macos build --- core/tauri-runtime-wry/src/lib.rs | 1 + core/tauri/src/app.rs | 44 +++++---- core/tauri/src/menu/icon.rs | 3 +- core/tauri/src/menu/menu.rs | 38 ++++---- core/tauri/src/menu/mod.rs | 4 +- core/tauri/src/menu/submenu.rs | 18 ++-- core/tauri/src/test/mod.rs | 4 +- core/tauri/src/tray.rs | 10 +- core/tauri/src/window.rs | 42 ++++++--- examples/api/src-tauri/Cargo.lock | 149 +++++++++++++++++++++++++----- 10 files changed, 222 insertions(+), 91 deletions(-) diff --git a/core/tauri-runtime-wry/src/lib.rs b/core/tauri-runtime-wry/src/lib.rs index e11d5c64eac0..2227458f8b21 100644 --- a/core/tauri-runtime-wry/src/lib.rs +++ b/core/tauri-runtime-wry/src/lib.rs @@ -2574,6 +2574,7 @@ fn create_webview( let is_window_transparent = window_builder.inner.window.transparent; let window = window_builder.inner.build(event_loop).unwrap(); + #[cfg(not(target_os = "macos"))] if let Some(menu) = window_builder.menu { #[cfg(windows)] let _ = menu.init_for_hwnd(window.hwnd() as _); diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 81b5ea284d94..7e9555bde8a2 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -602,13 +602,13 @@ macro_rules! shared_app_impl { let menu_c = menu.clone(); self.run_on_main_thread(move || { #[cfg(windows)] - { - let _ = menu_c.inner().init_for_hwnd(hwnd); - } + let _ = menu_c.inner().init_for_hwnd(hwnd); + #[cfg(linux)] - { - let _ = menu_c.inner().init_for_gtk_window(>k_window); - } + let _ = menu_c.inner().init_for_gtk_window(>k_window); + + #[cfg(target_os = "macos")] + menu_c.inner().init_for_nsapp(); })?; window_menu.replace((true, menu.clone())); } @@ -625,6 +625,7 @@ macro_rules! shared_app_impl { /// /// If a window was not created with an explicit menu or had one set explicitly, /// this will remove the menu from it. + pub fn remove_menu(&self) -> crate::Result>> { let mut current_menu = self.manager.menu_lock(); @@ -639,13 +640,13 @@ macro_rules! shared_app_impl { let menu_c = menu.clone(); self.run_on_main_thread(move || { #[cfg(windows)] - { - let _ = menu_c.inner().remove_for_hwnd(hwnd); - } + let _ = menu_c.inner().remove_for_hwnd(hwnd); + #[cfg(linux)] - { - let _ = menu_c.inner().remove_for_gtk_window(>k_window); - } + let _ = menu_c.inner().remove_for_gtk_window(>k_window); + + #[cfg(target_os = "macos")] + let _ = menu_c.inner().remove_for_nsapp(); })?; *window.menu_lock() = None; } @@ -671,6 +672,8 @@ macro_rules! shared_app_impl { /// /// If a window was not created with an explicit menu or had one set explicitly, /// this will hide the menu from it. + #[cfg(not(target_os = "macos"))] + #[cfg_attr(doc_cfg, doc(cfg(not(target_os = "macos"))))] pub fn hide_menu(&self) -> crate::Result<()> { if let Some(menu) = &*self.manager.menu_lock() { for window in self.manager.windows_lock().values() { @@ -701,6 +704,8 @@ macro_rules! shared_app_impl { /// /// If a window was not created with an explicit menu or had one set explicitly, /// this will show the menu for it. + #[cfg(not(target_os = "macos"))] + #[cfg_attr(doc_cfg, doc(cfg(not(target_os = "macos"))))] pub fn show_menu(&self) -> crate::Result<()> { if let Some(menu) = &*self.manager.menu_lock() { for window in self.manager.windows_lock().values() { @@ -1225,9 +1230,9 @@ impl Builder { /// tauri::Builder::default() /// .tray_icon(TrayIconBuilder::new().with_menu( /// Menu::with_items(&[ - /// &MenuItem::new("New window", true, None), - /// &MenuItem::new("Quit", true, None), - /// ]) + /// &MenuItem::new("New window", true, None).unwrap(), + /// &MenuItem::new("Quit", true, None).unwrap(), + /// ]).unwrap() /// )); /// ``` #[must_use] @@ -1475,11 +1480,6 @@ impl Builder { #[cfg(not(any(windows, target_os = "linux")))] let mut runtime = R::new(runtime_args)?; - #[cfg(target_os = "macos")] - if let Some(menu) = menu { - menu.inner().init_for_nsapp(); - } - // setup menu event handler let proxy = runtime.create_proxy(); crate::menu::MenuEvent::set_event_handler(Some(move |e| { @@ -1513,6 +1513,10 @@ impl Builder { if let Ok(id) = menu.id() { app.manager.menus_stash_lock().insert(id, menu.clone()); } + + #[cfg(target_os = "macos")] + menu.inner().init_for_nsapp(); + app.manager.menu_lock().replace(menu); } diff --git a/core/tauri/src/menu/icon.rs b/core/tauri/src/menu/icon.rs index 6182bd5d435f..69c1cad37c60 100644 --- a/core/tauri/src/menu/icon.rs +++ b/core/tauri/src/menu/icon.rs @@ -145,7 +145,8 @@ impl IconMenuItem { /// - **Windows / Linux**: Unsupported. pub fn set_native_icon(&mut self, _icon: Option) -> crate::Result<()> { #[cfg(target_os = "macos")] - return run_main_thread!(self, |self_: Self| self_.inner.set_native_icon(_icon)); + 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 index ddbd1717ccd2..34cb0db46cb9 100644 --- a/core/tauri/src/menu/menu.rs +++ b/core/tauri/src/menu/menu.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: MIT use super::{IsMenuItem, MenuItemKind, PredefinedMenuItem, Submenu}; -use crate::{run_main_thread, runtime::menu as muda, AppHandle, Runtime}; +use crate::{run_main_thread, runtime::menu as muda, AppHandle, Position, Runtime}; use muda::ContextMenu; use tauri_runtime::menu::AboutMetadata; @@ -43,7 +43,7 @@ unsafe impl super::sealed::ContextMenuBase for Menu { fn show_context_menu_for_hwnd( &self, hwnd: isize, - position: Option, + position: Option, ) -> crate::Result<()> { run_main_thread!(self, |self_: Self| self_ .inner() @@ -62,14 +62,18 @@ unsafe impl super::sealed::ContextMenuBase for Menu { } #[cfg(target_os = "macos")] - fn show_context_menu_for_nsview( + fn show_context_menu_for_nsview( &self, - view: cocoa::base::id, + window: crate::Window, position: Option, ) -> crate::Result<()> { - run_main_thread!(self, |self_: Self| self_ - .inner() - .show_context_menu_for_ns_view(view, position.map(Into::into))) + run_main_thread!(self, |self_: Self| { + if let Ok(view) = window.ns_view() { + self_ + .inner() + .show_context_menu_for_nsview(view as _, position.map(Into::into)) + } + }) } } @@ -110,17 +114,17 @@ impl Menu { #[cfg(target_os = "macos")] &Submenu::with_items( app_handle, - pkg_info.name, + pkg_info.name.clone(), true, &[ - &PredefinedMenuItem::about(None, Some(about_metadata.clone())), - &PredefinedMenuItem::separator(), - &PredefinedMenuItem::services(None), - &PredefinedMenuItem::separator(), - &PredefinedMenuItem::hide(None), - &PredefinedMenuItem::hide_others(None), - &PredefinedMenuItem::separator(), - &PredefinedMenuItem::quit(None), + &PredefinedMenuItem::about(app_handle, None, Some(about_metadata.clone())), + &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( @@ -159,7 +163,7 @@ impl Menu { app_handle, "View", true, - &[&PredefinedMenuItem::fullscreen(None)], + &[&PredefinedMenuItem::fullscreen(app_handle, None)], )?, &Submenu::with_items( app_handle, diff --git a/core/tauri/src/menu/mod.rs b/core/tauri/src/menu/mod.rs index d432fd78e1b6..848443079a04 100644 --- a/core/tauri/src/menu/mod.rs +++ b/core/tauri/src/menu/mod.rs @@ -191,9 +191,9 @@ pub(crate) mod sealed { ) -> crate::Result<()>; #[cfg(target_os = "macos")] - fn show_context_menu_for_nsview( + fn show_context_menu_for_nsview( &self, - view: cocoa::base::id, + window: crate::Window, position: Option, ) -> crate::Result<()>; } diff --git a/core/tauri/src/menu/submenu.rs b/core/tauri/src/menu/submenu.rs index bc05cbc6957b..1d8e8d110174 100644 --- a/core/tauri/src/menu/submenu.rs +++ b/core/tauri/src/menu/submenu.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: MIT use super::{IsMenuItem, MenuItemKind}; -use crate::{run_main_thread, runtime::menu as muda, AppHandle, Runtime}; +use crate::{run_main_thread, runtime::menu as muda, AppHandle, Position, Runtime}; use muda::ContextMenu; /// A type that is a submenu inside a [`Menu`] or [`Submenu`] @@ -60,7 +60,7 @@ unsafe impl super::sealed::ContextMenuBase for Submenu { fn show_context_menu_for_hwnd( &self, hwnd: isize, - position: Option, + position: Option, ) -> crate::Result<()> { run_main_thread!(self, |self_: Self| { self_ @@ -83,15 +83,17 @@ unsafe impl super::sealed::ContextMenuBase for Submenu { } #[cfg(target_os = "macos")] - fn show_context_menu_for_nsview( + fn show_context_menu_for_nsview( &self, - view: cocoa::base::id, + window: crate::Window, position: Option, ) -> crate::Result<()> { - run_main_thread!(self, |self_: Self| { - self_ - .inner() - .show_context_menu_for_ns_view(view, position.map(Into::into)) + run_main_thread!(self, move |self_: Self| { + if let Ok(view) = window.ns_view() { + self_ + .inner() + .show_context_menu_for_nsview(view as _, position.map(Into::into)) + } }) } } diff --git a/core/tauri/src/test/mod.rs b/core/tauri/src/test/mod.rs index 9d5be3214a38..d88af801ea8d 100644 --- a/core/tauri/src/test/mod.rs +++ b/core/tauri/src/test/mod.rs @@ -166,7 +166,9 @@ pub fn mock_context(assets: A) -> crate::Context { /// } /// ``` pub fn mock_builder() -> Builder { - let mut builder = Builder::::new().manage(Ipc(Default::default())); + let mut builder = Builder::::new() + .enable_macos_default_menu(false) + .manage(Ipc(Default::default())); builder.invoke_responder = Arc::new(|window, response, callback, error| { let window_ = window.clone(); diff --git a/core/tauri/src/tray.rs b/core/tauri/src/tray.rs index 668afe4a9d49..ecfc48b1ffdf 100644 --- a/core/tauri/src/tray.rs +++ b/core/tauri/src/tray.rs @@ -6,8 +6,8 @@ use std::path::{Path, PathBuf}; -use crate::runtime::tray as tray_icon; pub use crate::runtime::tray::TrayIconEvent; +use crate::{menu::ContextMenu, runtime::tray as tray_icon}; use crate::{run_main_thread, AppHandle, Icon, Runtime}; // TODO(muda-migration): tray icon type `on_event` handler @@ -28,7 +28,7 @@ pub struct TrayIconAttributes { /// ## Platform-specific: /// /// - **Linux**: once a menu is set, it cannot be removed. - pub menu: Option>, + pub menu: Option>, /// Tray icon /// @@ -64,7 +64,7 @@ impl From for tray_icon::TrayIconAttributes { fn from(value: TrayIconAttributes) -> Self { Self { tooltip: value.tooltip, - menu: value.menu.map(|m| m.into_inner()), + menu: value.menu, icon: value.icon.and_then(|i| { i.try_into() .ok() @@ -101,7 +101,7 @@ impl TrayIconBuilder { /// ## Platform-specific: /// /// - **Linux**: once a menu is set, it cannot be removed or replaced but you can change its content. - pub fn with_menu(mut self, menu: &dyn crate::menu::ContextMenu) -> Self { + pub fn with_menu(mut self, menu: &M) -> Self { self.0 = self.0.with_menu(menu.into_inner()); self } @@ -255,7 +255,7 @@ impl TrayIcon { /// ## 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<()> { + pub fn set_menu(&self, menu: Option) -> crate::Result<()> { run_main_thread!(self, |self_: Self| self_ .inner .set_menu(menu.map(|m| m.into_inner()))) diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index 463126117100..49530e4a8d5c 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -54,7 +54,7 @@ use std::{ fmt, hash::{Hash, Hasher}, path::PathBuf, - sync::{mpsc::channel, Arc, Mutex, MutexGuard}, + sync::{Arc, Mutex, MutexGuard}, }; pub(crate) type WebResourceRequestHandler = dyn Fn(&HttpRequest, &mut HttpResponse) + Send + Sync; @@ -1034,24 +1034,28 @@ impl Window { /// /// # Examples /// ``` + /// use tauri::menu::{Menu, Submenu, MenuItem}; /// tauri::Builder::default() - /// .setup(|window, event| { - /// let save_menu_item = &MenuItem::new("Save", true, None); - /// let menu = Menu::with_items(&[ - /// &Submenu::with_items("File", true, &[ - /// &save_menu_item, - /// ]), - /// ]); - /// let window = WindowBuilder::new(app, "editor", tauri::WindowUrl::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() { + /// if event.id == save_menu_item.id().unwrap() { /// // save menu item /// } /// }); + /// + /// Ok(()) /// }); /// ``` pub fn on_menu_event, MenuEvent) + Send + Sync + 'static>(&self, f: F) { @@ -1091,6 +1095,8 @@ impl Window { /// /// - **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(not(target_os = "macos"))] + #[cfg_attr(doc_cfg, doc(cfg(not(target_os = "macos"))))] pub fn set_menu(&self, menu: Menu) -> crate::Result>> { let prev_menu = self.remove_menu()?; @@ -1123,6 +1129,8 @@ impl Window { /// /// - **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. + #[cfg(not(target_os = "macos"))] + #[cfg_attr(doc_cfg, doc(cfg(not(target_os = "macos"))))] pub fn remove_menu(&self) -> crate::Result>> { let mut current_menu = self.menu_lock(); @@ -1157,6 +1165,8 @@ impl Window { } /// Hides the window menu. + #[cfg(not(target_os = "macos"))] + #[cfg_attr(doc_cfg, doc(cfg(not(target_os = "macos"))))] pub fn hide_menu(&self) -> crate::Result<()> { // remove from the window if let Some((_, menu)) = &*self.menu_lock() { @@ -1181,6 +1191,8 @@ impl Window { } /// Shows the window menu. + #[cfg(not(target_os = "macos"))] + #[cfg_attr(doc_cfg, doc(cfg(not(target_os = "macos"))))] pub fn show_menu(&self) -> crate::Result<()> { // remove from the window if let Some((_, menu)) = &*self.menu_lock() { @@ -1205,10 +1217,12 @@ impl Window { } /// Shows the window menu. + #[cfg(not(target_os = "macos"))] + #[cfg_attr(doc_cfg, doc(cfg(not(target_os = "macos"))))] pub fn is_menu_visible(&self) -> crate::Result { // remove from the window if let Some((_, menu)) = &*self.menu_lock() { - let (tx, rx) = channel(); + let (tx, rx) = std::sync::mpsc::channel(); #[cfg(windows)] let hwnd = self.hwnd()?.0; #[cfg(linux)] @@ -1235,9 +1249,9 @@ impl Window { /// If a position was not provided, the cursor position will be used. /// /// The position is relative to the window's top-left corner. - pub fn show_context_menu>( + pub fn show_context_menu>( &self, - menu: &dyn ContextMenu, + menu: &M, position: Option

, ) -> crate::Result<()> { let position = position.map(|p| p.into()); @@ -1247,7 +1261,7 @@ impl Window { #[cfg(linux)] menu.show_context_menu_for_gtk_window(&self.gtk_window()?, position)?; #[cfg(target_os = "macos")] - menu.show_context_menu_for_nsview(self.ns_view()? as _, position)?; + menu.show_context_menu_for_nsview(self.clone(), position)?; Ok(()) } diff --git a/examples/api/src-tauri/Cargo.lock b/examples/api/src-tauri/Cargo.lock index 40832146e1c5..9b1afd4f9732 100644 --- a/examples/api/src-tauri/Cargo.lock +++ b/examples/api/src-tauri/Cargo.lock @@ -576,8 +576,24 @@ dependencies = [ "block", "cocoa-foundation", "core-foundation", - "core-graphics", - "foreign-types", + "core-graphics 0.22.3", + "foreign-types 0.3.2", + "libc", + "objc", +] + +[[package]] +name = "cocoa" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" +dependencies = [ + "bitflags", + "block", + "cocoa-foundation", + "core-foundation", + "core-graphics 0.23.1", + "foreign-types 0.5.0", "libc", "objc", ] @@ -592,7 +608,7 @@ dependencies = [ "block", "core-foundation", "core-graphics-types", - "foreign-types", + "foreign-types 0.3.2", "libc", "objc", ] @@ -669,7 +685,20 @@ dependencies = [ "bitflags", "core-foundation", "core-graphics-types", - "foreign-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", + "core-foundation", + "core-graphics-types", + "foreign-types 0.5.0", "libc", ] @@ -681,7 +710,7 @@ checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" dependencies = [ "bitflags", "core-foundation", - "foreign-types", + "foreign-types 0.3.2", "libc", ] @@ -1085,7 +1114,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.18", ] [[package]] @@ -1094,6 +1144,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" @@ -1871,6 +1927,17 @@ dependencies = [ "treediff", ] +[[package]] +name = "keyboard-types" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7668b7cff6a51fe61cdde64cd27c8a220786f399501b57ebe36f7d8112fd68" +dependencies = [ + "bitflags", + "serde", + "unicode-segmentation", +] + [[package]] name = "kuchiki" version = "0.8.1" @@ -2109,6 +2176,25 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "muda" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0294ee1db0c35b1a2d3fea0bcc66aaea00d4b9693c7b3228865baefdd48850e8" +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" @@ -3244,17 +3330,15 @@ dependencies = [ [[package]] name = "tao" version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b87728a671df8520c274fa9bed48d7384f5a965ef2fc364f01a942f6ff1ae6d2" +source = "git+https://github.com/tauri-apps/tao?branch=muda#4725b52719225d82dc9604069354e161a550a954" dependencies = [ "bitflags", "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 +3353,6 @@ dependencies = [ "instant", "jni", "lazy_static", - "libappindicator", "libc", "log", "ndk", @@ -3295,8 +3378,7 @@ dependencies = [ [[package]] name = "tao-macros" version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b27a4bcc5eb524658234589bdffc7e7bfb996dbae6ce9393bfd39cb4159b445" +source = "git+https://github.com/tauri-apps/tao?branch=muda#4725b52719225d82dc9604069354e161a550a954" dependencies = [ "proc-macro2", "quote", @@ -3315,7 +3397,7 @@ version = "2.0.0-alpha.10" dependencies = [ "anyhow", "bytes", - "cocoa", + "cocoa 0.24.1", "dirs-next", "embed_plist", "futures-util", @@ -3364,6 +3446,7 @@ dependencies = [ "cargo_toml", "heck", "json-patch", + "plist", "quote", "semver", "serde", @@ -3431,7 +3514,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", @@ -3463,12 +3546,14 @@ dependencies = [ "http", "http-range", "jni", + "muda", "rand 0.8.5", "raw-window-handle", "serde", "serde_json", "tauri-utils", "thiserror", + "tray-icon", "url", "uuid", "windows", @@ -3478,7 +3563,7 @@ dependencies = [ name = "tauri-runtime-wry" version = "0.13.0-alpha.6" dependencies = [ - "cocoa", + "cocoa 0.24.1", "gtk", "jni", "percent-encoding", @@ -3792,6 +3877,25 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "tray-icon" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85848705438afb36546193edd75e3a9f85833fe8799d13a00924774bf7277edb" +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" @@ -4196,7 +4300,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", @@ -4410,14 +4514,13 @@ dependencies = [ [[package]] name = "wry" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "430d086d4626265e9427fe2908a06fb2e10ea2ff37d4a711eb9461b6ea3511dd" +version = "0.29.0" +source = "git+https://github.com/tauri-apps/wry?branch=tao-v0.22#7580eda6b927a59cc945ed4c4a1d1cf1e7318542" dependencies = [ "base64", "block", - "cocoa", - "core-graphics", + "cocoa 0.24.1", + "core-graphics 0.22.3", "crossbeam-channel", "dunce", "gdk", From 9534f6a2846326fbe16f6c58d786d6e601ce8542 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 1 Aug 2023 09:00:36 -0300 Subject: [PATCH 022/123] lint --- core/tauri/src/app.rs | 64 +++++++++++++++++++--- core/tauri/src/menu/check.rs | 4 +- core/tauri/src/menu/icon.rs | 4 +- core/tauri/src/menu/menu.rs | 14 +++-- core/tauri/src/menu/mod.rs | 19 ++++--- core/tauri/src/menu/normal.rs | 4 +- core/tauri/src/menu/predefined.rs | 4 +- core/tauri/src/menu/submenu.rs | 18 ++++--- core/tauri/src/tray.rs | 4 +- core/tauri/src/window.rs | 89 +++++++++++++++++++++++++++---- 10 files changed, 179 insertions(+), 45 deletions(-) diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 7e9555bde8a2..57c46c958da7 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -597,14 +597,26 @@ macro_rules! shared_app_impl { if window_menu.as_ref().map(|m| m.0).unwrap_or(true) { #[cfg(windows)] let hwnd = window.hwnd()?.0; - #[cfg(linux)] + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] let gtk_window = window.gtk_window()?; let menu_c = menu.clone(); self.run_on_main_thread(move || { #[cfg(windows)] let _ = menu_c.inner().init_for_hwnd(hwnd); - #[cfg(linux)] + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] let _ = menu_c.inner().init_for_gtk_window(>k_window); #[cfg(target_os = "macos")] @@ -635,14 +647,26 @@ macro_rules! shared_app_impl { if window.has_app_wide_menu() { #[cfg(windows)] let hwnd = window.hwnd()?.0; - #[cfg(linux)] + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] let gtk_window = window.gtk_window()?; let menu_c = menu.clone(); self.run_on_main_thread(move || { #[cfg(windows)] let _ = menu_c.inner().remove_for_hwnd(hwnd); - #[cfg(linux)] + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] let _ = menu_c.inner().remove_for_gtk_window(>k_window); #[cfg(target_os = "macos")] @@ -680,7 +704,13 @@ macro_rules! shared_app_impl { if window.has_app_wide_menu() { #[cfg(windows)] let hwnd = window.hwnd()?.0; - #[cfg(linux)] + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] let gtk_window = window.gtk_window()?; let menu_c = menu.clone(); self.run_on_main_thread(move || { @@ -688,7 +718,13 @@ macro_rules! shared_app_impl { { let _ = menu_c.inner().hide_for_hwnd(hwnd); } - #[cfg(linux)] + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] { let _ = menu_c.inner().hide_for_gtk_window(>k_window); } @@ -712,7 +748,13 @@ macro_rules! shared_app_impl { if window.has_app_wide_menu() { #[cfg(windows)] let hwnd = window.hwnd()?.0; - #[cfg(linux)] + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] let gtk_window = window.gtk_window()?; let menu_c = menu.clone(); self.run_on_main_thread(move || { @@ -720,7 +762,13 @@ macro_rules! shared_app_impl { { let _ = menu_c.inner().show_for_hwnd(hwnd); } - #[cfg(linux)] + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] { let _ = menu_c.inner().show_for_gtk_window(>k_window); } diff --git a/core/tauri/src/menu/check.rs b/core/tauri/src/menu/check.rs index 0e4baa9bc2be..96ceccd9813d 100644 --- a/core/tauri/src/menu/check.rs +++ b/core/tauri/src/menu/check.rs @@ -28,13 +28,13 @@ impl Clone for CheckMenuItem { unsafe impl Sync for CheckMenuItem {} unsafe impl Send for CheckMenuItem {} -unsafe impl super::sealed::IsMenuItemBase for CheckMenuItem { +impl super::sealed::IsMenuItemBase for CheckMenuItem { fn inner(&self) -> &dyn muda::IsMenuItem { &self.inner } } -unsafe impl super::IsMenuItem for CheckMenuItem { +impl super::IsMenuItem for CheckMenuItem { fn kind(&self) -> super::MenuItemKind { super::MenuItemKind::Check(self.clone()) } diff --git a/core/tauri/src/menu/icon.rs b/core/tauri/src/menu/icon.rs index 69c1cad37c60..da5237f9a002 100644 --- a/core/tauri/src/menu/icon.rs +++ b/core/tauri/src/menu/icon.rs @@ -29,13 +29,13 @@ impl Clone for IconMenuItem { unsafe impl Sync for IconMenuItem {} unsafe impl Send for IconMenuItem {} -unsafe impl super::sealed::IsMenuItemBase for IconMenuItem { +impl super::sealed::IsMenuItemBase for IconMenuItem { fn inner(&self) -> &dyn muda::IsMenuItem { &self.inner } } -unsafe impl super::IsMenuItem for IconMenuItem { +impl super::IsMenuItem for IconMenuItem { fn kind(&self) -> super::MenuItemKind { super::MenuItemKind::Icon(self.clone()) } diff --git a/core/tauri/src/menu/menu.rs b/core/tauri/src/menu/menu.rs index 34cb0db46cb9..8f74356a193b 100644 --- a/core/tauri/src/menu/menu.rs +++ b/core/tauri/src/menu/menu.rs @@ -29,13 +29,13 @@ impl Clone for Menu { } } -unsafe impl super::ContextMenu for Menu {} -unsafe impl super::sealed::ContextMenuBase for Menu { +impl super::ContextMenu for Menu {} +impl super::sealed::ContextMenuBase for Menu { fn inner(&self) -> &dyn muda::ContextMenu { &self.inner } - fn into_inner(&self) -> Box { + fn inner_owned(&self) -> Box { Box::new(self.clone().inner) } @@ -50,7 +50,13 @@ unsafe impl super::sealed::ContextMenuBase for Menu { .show_context_menu_for_hwnd(hwnd, position.map(Into::into))) } - #[cfg(linux)] + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] fn show_context_menu_for_gtk_window( &self, w: >k::ApplicationWindow, diff --git a/core/tauri/src/menu/mod.rs b/core/tauri/src/menu/mod.rs index 848443079a04..a8bf1718f08e 100644 --- a/core/tauri/src/menu/mod.rs +++ b/core/tauri/src/menu/mod.rs @@ -9,6 +9,7 @@ pub mod builders; mod check; mod icon; +#[allow(clippy::module_inception)] mod menu; mod normal; mod predefined; @@ -148,7 +149,7 @@ impl MenuItemKind { /// # Safety /// /// This trait is ONLY meant to be implemented internally by the crate. -pub unsafe trait IsMenuItem: sealed::IsMenuItemBase { +pub trait IsMenuItem: sealed::IsMenuItemBase { /// Returns the kind of this menu item. fn kind(&self) -> MenuItemKind; @@ -163,18 +164,18 @@ pub unsafe trait IsMenuItem: sealed::IsMenuItemBase { /// # Safety /// /// This trait is ONLY meant to be implemented internally by the crate. -pub unsafe trait ContextMenu: sealed::ContextMenuBase + Send + Sync {} +pub trait ContextMenu: sealed::ContextMenuBase + Send + Sync {} pub(crate) mod sealed { use crate::Position; - pub unsafe trait IsMenuItemBase { + pub trait IsMenuItemBase { fn inner(&self) -> &dyn super::muda::IsMenuItem; } - pub unsafe trait ContextMenuBase { + pub trait ContextMenuBase { fn inner(&self) -> &dyn super::muda::ContextMenu; - fn into_inner(&self) -> Box; + fn inner_owned(&self) -> Box; #[cfg(windows)] fn show_context_menu_for_hwnd( @@ -183,7 +184,13 @@ pub(crate) mod sealed { position: Option, ) -> crate::Result<()>; - #[cfg(linux)] + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] fn show_context_menu_for_gtk_window( &self, w: >k::ApplicationWindow, diff --git a/core/tauri/src/menu/normal.rs b/core/tauri/src/menu/normal.rs index 88a89c740ba6..987fa047c237 100644 --- a/core/tauri/src/menu/normal.rs +++ b/core/tauri/src/menu/normal.rs @@ -28,13 +28,13 @@ impl Clone for MenuItem { unsafe impl Sync for MenuItem {} unsafe impl Send for MenuItem {} -unsafe impl super::sealed::IsMenuItemBase for MenuItem { +impl super::sealed::IsMenuItemBase for MenuItem { fn inner(&self) -> &dyn muda::IsMenuItem { &self.inner } } -unsafe impl super::IsMenuItem for MenuItem { +impl super::IsMenuItem for MenuItem { fn kind(&self) -> super::MenuItemKind { super::MenuItemKind::MenuItem(self.clone()) } diff --git a/core/tauri/src/menu/predefined.rs b/core/tauri/src/menu/predefined.rs index 57f624f619eb..ba6f132219fd 100644 --- a/core/tauri/src/menu/predefined.rs +++ b/core/tauri/src/menu/predefined.rs @@ -26,13 +26,13 @@ impl Clone for PredefinedMenuItem { unsafe impl Sync for PredefinedMenuItem {} unsafe impl Send for PredefinedMenuItem {} -unsafe impl super::sealed::IsMenuItemBase for PredefinedMenuItem { +impl super::sealed::IsMenuItemBase for PredefinedMenuItem { fn inner(&self) -> &dyn muda::IsMenuItem { &self.inner } } -unsafe impl super::IsMenuItem for PredefinedMenuItem { +impl super::IsMenuItem for PredefinedMenuItem { fn kind(&self) -> super::MenuItemKind { super::MenuItemKind::Predefined(self.clone()) } diff --git a/core/tauri/src/menu/submenu.rs b/core/tauri/src/menu/submenu.rs index 1d8e8d110174..9f27e6f0a6b6 100644 --- a/core/tauri/src/menu/submenu.rs +++ b/core/tauri/src/menu/submenu.rs @@ -30,13 +30,13 @@ impl Clone for Submenu { } } -unsafe impl super::sealed::IsMenuItemBase for Submenu { +impl super::sealed::IsMenuItemBase for Submenu { fn inner(&self) -> &dyn muda::IsMenuItem { &self.inner } } -unsafe impl super::IsMenuItem for Submenu { +impl super::IsMenuItem for Submenu { fn kind(&self) -> super::MenuItemKind { super::MenuItemKind::Submenu(self.clone()) } @@ -46,13 +46,13 @@ unsafe impl super::IsMenuItem for Submenu { } } -unsafe impl super::ContextMenu for Submenu {} -unsafe impl super::sealed::ContextMenuBase for Submenu { +impl super::ContextMenu for Submenu {} +impl super::sealed::ContextMenuBase for Submenu { fn inner(&self) -> &dyn muda::ContextMenu { &self.inner } - fn into_inner(&self) -> Box { + fn inner_owned(&self) -> Box { Box::new(self.clone().inner) } @@ -69,7 +69,13 @@ unsafe impl super::sealed::ContextMenuBase for Submenu { }) } - #[cfg(linux)] + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] fn show_context_menu_for_gtk_window( &self, w: >k::ApplicationWindow, diff --git a/core/tauri/src/tray.rs b/core/tauri/src/tray.rs index ecfc48b1ffdf..1c9d87691c48 100644 --- a/core/tauri/src/tray.rs +++ b/core/tauri/src/tray.rs @@ -102,7 +102,7 @@ impl TrayIconBuilder { /// /// - **Linux**: once a menu is set, it cannot be removed or replaced but you can change its content. pub fn with_menu(mut self, menu: &M) -> Self { - self.0 = self.0.with_menu(menu.into_inner()); + self.0 = self.0.with_menu(menu.inner_owned()); self } @@ -258,7 +258,7 @@ impl TrayIcon { pub fn set_menu(&self, menu: Option) -> crate::Result<()> { run_main_thread!(self, |self_: Self| self_ .inner - .set_menu(menu.map(|m| m.into_inner()))) + .set_menu(menu.map(|m| m.inner_owned()))) } /// Sets the tooltip for this tray icon. diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index 49530e4a8d5c..cbc5923af30b 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -797,6 +797,7 @@ pub struct Window { pub(crate) app_handle: AppHandle, js_event_listeners: Arc>>>, // The menu set for this window + #[allow(clippy::type_complexity)] pub(crate) menu: Arc)>>>, } @@ -1104,7 +1105,13 @@ impl Window { #[cfg(windows)] let hwnd = self.hwnd()?.0; - #[cfg(linux)] + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] let gtk_window = self.gtk_window()?; let menu_c = menu.clone(); self.run_on_main_thread(move || { @@ -1112,7 +1119,13 @@ impl Window { { let _ = menu_c.inner().init_for_hwnd(hwnd); } - #[cfg(linux)] + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] { let _ = menu_c.inner().init_for_gtk_window(>k_window); } @@ -1138,7 +1151,13 @@ impl Window { if let Some((_, menu)) = &*current_menu { #[cfg(windows)] let hwnd = self.hwnd()?.0; - #[cfg(linux)] + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] let gtk_window = self.gtk_window()?; let menu_c = menu.clone(); self.run_on_main_thread(move || { @@ -1146,7 +1165,13 @@ impl Window { { let _ = menu_c.inner().remove_for_hwnd(hwnd); } - #[cfg(linux)] + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] { let _ = menu_c.inner().remove_for_gtk_window(>k_window); } @@ -1172,7 +1197,13 @@ impl Window { if let Some((_, menu)) = &*self.menu_lock() { #[cfg(windows)] let hwnd = self.hwnd()?.0; - #[cfg(linux)] + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] let gtk_window = self.gtk_window()?; let menu_c = menu.clone(); self.run_on_main_thread(move || { @@ -1180,7 +1211,13 @@ impl Window { { let _ = menu_c.inner().hide_for_hwnd(hwnd); } - #[cfg(linux)] + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] { let _ = menu_c.inner().hide_for_gtk_window(>k_window); } @@ -1198,7 +1235,13 @@ impl Window { if let Some((_, menu)) = &*self.menu_lock() { #[cfg(windows)] let hwnd = self.hwnd()?.0; - #[cfg(linux)] + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] let gtk_window = self.gtk_window()?; let menu_c = menu.clone(); self.run_on_main_thread(move || { @@ -1206,7 +1249,13 @@ impl Window { { let _ = menu_c.inner().show_for_hwnd(hwnd); } - #[cfg(linux)] + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] { let _ = menu_c.inner().show_for_gtk_window(>k_window); } @@ -1225,7 +1274,13 @@ impl Window { let (tx, rx) = std::sync::mpsc::channel(); #[cfg(windows)] let hwnd = self.hwnd()?.0; - #[cfg(linux)] + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] let gtk_window = self.gtk_window()?; let menu_c = menu.clone(); self.run_on_main_thread(move || { @@ -1233,7 +1288,13 @@ impl Window { { let _ = tx.send(menu_c.inner().is_visible_on_hwnd(hwnd)); } - #[cfg(linux)] + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] { let _ = tx.send(menu_c.inner().is_visible_on_gtk_window(>k_window)); } @@ -1258,7 +1319,13 @@ impl Window { #[cfg(windows)] menu.show_context_menu_for_hwnd(self.hwnd()?.0, position)?; - #[cfg(linux)] + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] menu.show_context_menu_for_gtk_window(&self.gtk_window()?, position)?; #[cfg(target_os = "macos")] menu.show_context_menu_for_nsview(self.clone(), position)?; From f5b5aaccb121de180f8755e41808f9a2b249ef2f Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 1 Aug 2023 09:12:55 -0300 Subject: [PATCH 023/123] small fix on api example --- examples/api/src-tauri/Cargo.lock | 692 +++++++++++++++-------------- examples/api/src-tauri/src/tray.rs | 27 +- 2 files changed, 379 insertions(+), 340 deletions(-) diff --git a/examples/api/src-tauri/Cargo.lock b/examples/api/src-tauri/Cargo.lock index 9b1afd4f9732..e0bb5a3bb579 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", @@ -495,9 +525,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 +570,21 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.2" +version = "4.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "401a4694d2bf92537b6867d94de48c4842089645fdcdf6c71865b175d836e9c2" +checksum = "5fd304a20bff958a57f04c4e96a2e7594cc4490a0e809cbd48bb6437edaa452d" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.3.1" +version = "4.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72394f3339a76daf211e57d4bcb374410f3965dcc606dd0e03738c7888766980" +checksum = "01c6a3f08f1fe5662a35cfe393aec09c4df95f60ee93b7556505260f75eee9e1" dependencies = [ "anstream", "anstyle", - "bitflags", "clap_lex", "strsim", ] @@ -572,7 +601,7 @@ 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", @@ -588,7 +617,7 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" dependencies = [ - "bitflags", + "bitflags 1.3.2", "block", "cocoa-foundation", "core-foundation", @@ -604,7 +633,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "931d3837c286f56e3c58423ce4eba12d08db2374461a785c86f672b08b5650d6" dependencies = [ - "bitflags", + "bitflags 1.3.2", "block", "core-foundation", "core-graphics-types", @@ -613,16 +642,6 @@ dependencies = [ "objc", ] -[[package]] -name = "codespan-reporting" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" -dependencies = [ - "termcolor", - "unicode-width", -] - [[package]] name = "color_quant" version = "1.1.0" @@ -682,7 +701,7 @@ 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", @@ -695,7 +714,7 @@ version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "970a29baf4110c26fedbc7f82107d42c23f7e88e404c4577ed73fe99ff85a212" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", "core-graphics-types", "foreign-types 0.5.0", @@ -704,21 +723,20 @@ dependencies = [ [[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 0.3.2", "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", ] @@ -744,9 +762,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", ] @@ -786,7 +804,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.18", + "syn 2.0.28", ] [[package]] @@ -808,55 +826,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", @@ -864,27 +838,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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8810e7e2cf385b1e9b50d68264908ec367ba642c96d02edfe61c39e88e2a3c01" +dependencies = [ + "serde", ] [[package]] @@ -950,9 +933,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" @@ -971,9 +954,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", @@ -1015,7 +998,7 @@ checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.28", ] [[package]] @@ -1028,11 +1011,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", @@ -1064,6 +1053,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" @@ -1135,7 +1130,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.28", ] [[package]] @@ -1207,7 +1202,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", @@ -1224,7 +1219,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.28", ] [[package]] @@ -1271,7 +1266,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", @@ -1287,7 +1282,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", @@ -1353,9 +1348,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", @@ -1406,13 +1401,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", @@ -1445,7 +1446,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", @@ -1510,7 +1511,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", @@ -1560,9 +1561,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", @@ -1570,7 +1571,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 1.9.3", "slab", "tokio", "tokio-util", @@ -1584,25 +1585,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" @@ -1646,7 +1644,7 @@ checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ "bytes", "fnv", - "itoa 1.0.6", + "itoa 1.0.9", ] [[package]] @@ -1680,9 +1678,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", @@ -1693,7 +1691,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 1.0.6", + "itoa 1.0.9", "pin-project-lite", "socket2", "tokio", @@ -1704,9 +1702,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", @@ -1718,12 +1716,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]] @@ -1782,10 +1779,20 @@ 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", +] + [[package]] name = "infer" version = "0.9.0" @@ -1828,26 +1835,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.4", "windows-sys 0.48.0", ] @@ -1859,9 +1865,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" @@ -1869,7 +1875,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", ] @@ -1908,9 +1914,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", ] @@ -1933,7 +1939,7 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7668b7cff6a51fe61cdde64cd27c8a220786f399501b57ebe36f7d8112fd68" dependencies = [ - "bitflags", + "bitflags 1.3.2", "serde", "unicode-segmentation", ] @@ -1958,7 +1964,7 @@ checksum = "f29e4755b7b995046f510a7520c42b2fed58b77bd94d5a87a8eb43d2fd126da8" dependencies = [ "cssparser", "html5ever 0.26.0", - "indexmap", + "indexmap 1.9.3", "matches", "selectors", ] @@ -1995,9 +2001,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" @@ -2019,19 +2025,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" @@ -2045,9 +2048,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", ] @@ -2116,7 +2119,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]] @@ -2201,7 +2204,7 @@ 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", @@ -2236,7 +2239,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", @@ -2282,20 +2285,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", ] @@ -2357,6 +2360,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" @@ -2391,7 +2403,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", @@ -2556,9 +2568,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" [[package]] name = "pin-utils" @@ -2574,12 +2586,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", @@ -2588,11 +2600,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", @@ -2606,7 +2618,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", @@ -2617,9 +2629,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", @@ -2681,27 +2693,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", ] @@ -2799,7 +2811,7 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -2808,7 +2820,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -2824,13 +2836,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.8.4" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" +checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.2", + "regex-automata 0.3.4", + "regex-syntax 0.7.4", ] [[package]] @@ -2842,6 +2855,17 @@ dependencies = [ "regex-syntax 0.6.29", ] +[[package]] +name = "regex-automata" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.7.4", +] + [[package]] name = "regex-syntax" version = "0.6.29" @@ -2850,9 +2874,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" @@ -2890,6 +2914,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" @@ -2901,29 +2931,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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5" +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" @@ -2948,15 +2991,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" @@ -2964,7 +3001,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", @@ -2980,57 +3017,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.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" +checksum = "0ea67f183f058fe88a4e3ec6e2788e003840893b91bac4559cabedd00863b3ed" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.164" +version = "1.0.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" +checksum = "24e744d7782b686ab3b73267ef05697159cc0e5abbed3f47f9933165e5219036" 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", ] @@ -3042,21 +3079,21 @@ 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.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f02d8aa6e3c385bf084924f660ce2a3a6bd333ba55b35e8590b321f35d88513" +checksum = "21e47d95bc83ed33b2ecf84f4187ad1ab9685d18ff28db000c99deac8ce180e3" dependencies = [ "base64", "chrono", "hex", - "indexmap", + "indexmap 1.9.3", "serde", "serde_json", "serde_with_macros", @@ -3065,14 +3102,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.0.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc7d5d3932fb12ce722ee5e64dd38c504efba37567f0c402f6ca728c3b8b070" +checksum = "ea3cee93715c2e266b9338b7544da68a9f24e227722ba482bd1c024367c77c65" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.28", ] [[package]] @@ -3120,9 +3157,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", @@ -3140,9 +3177,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", @@ -3159,9 +3196,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" @@ -3180,9 +3217,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" @@ -3200,7 +3237,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", @@ -3305,9 +3342,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", @@ -3316,9 +3353,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", @@ -3329,10 +3366,10 @@ dependencies = [ [[package]] name = "tao" -version = "0.21.0" -source = "git+https://github.com/tauri-apps/tao?branch=muda#4725b52719225d82dc9604069354e161a550a954" +version = "0.21.1" +source = "git+https://github.com/tauri-apps/tao?branch=muda#44699626ddfe6fc89d67785e93dd9f5251a666c0" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cairo-rs", "cc", "cocoa 0.24.1", @@ -3378,7 +3415,7 @@ dependencies = [ [[package]] name = "tao-macros" version = "0.1.1" -source = "git+https://github.com/tauri-apps/tao?branch=muda#4725b52719225d82dc9604069354e161a550a954" +source = "git+https://github.com/tauri-apps/tao?branch=muda#44699626ddfe6fc89d67785e93dd9f5251a666c0" dependencies = [ "proc-macro2", "quote", @@ -3387,9 +3424,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" @@ -3620,15 +3657,14 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.6.0" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" +checksum = "5486094ee78b2e5038a6382ed7645bc084dc2ec433426ca4c3cb61e2007b8998" dependencies = [ - "autocfg", "cfg-if", - "fastrand", + "fastrand 2.0.0", "redox_syscall 0.3.5", - "rustix", + "rustix 0.38.4", "windows-sys 0.48.0", ] @@ -3643,15 +3679,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" @@ -3660,22 +3687,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]] @@ -3690,11 +3717,12 @@ dependencies = [ [[package]] name = "time" -version = "0.3.22" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd" +checksum = "b79eabcd964882a646b3584543ccabeae7869e9ac32a46f6f22b7a5bd405308b" dependencies = [ - "itoa 1.0.6", + "deranged", + "itoa 1.0.9", "libc", "num_threads", "serde", @@ -3710,9 +3738,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", ] @@ -3747,11 +3775,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", @@ -3777,9 +3806,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", @@ -3789,20 +3818,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", @@ -3829,13 +3858,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]] @@ -3935,9 +3964,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" @@ -3954,12 +3983,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" @@ -4002,9 +4025,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", ] @@ -4017,9 +4040,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" @@ -4071,11 +4094,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", ] @@ -4093,9 +4115,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", @@ -4103,24 +4125,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", @@ -4130,9 +4152,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", @@ -4140,22 +4162,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" @@ -4172,9 +4194,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", @@ -4186,7 +4208,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", @@ -4210,7 +4232,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", @@ -4245,7 +4267,7 @@ checksum = "ac1345798ecd8122468840bcdf1b95e5dc6d2206c5e4b0eafa078d061f59c9bc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.28", ] [[package]] @@ -4381,9 +4403,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.48.0" +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", @@ -4486,9 +4508,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winnow" -version = "0.4.6" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699" +checksum = "8bd122eb777186e60c3fdf765a58ac76e41c582f1f535fbf3314434c6b58f3f7" dependencies = [ "memchr", ] diff --git a/examples/api/src-tauri/src/tray.rs b/examples/api/src-tauri/src/tray.rs index 20703a4b9938..696b687dda22 100644 --- a/examples/api/src-tauri/src/tray.rs +++ b/examples/api/src-tauri/src/tray.rs @@ -3,13 +3,29 @@ // SPDX-License-Identifier: MIT use std::sync::atomic::{AtomicBool, Ordering}; -use tauri::{tray::TrayIconBuilder, WindowUrl}; +use tauri::{ + menu::{Menu, MenuItem}, + tray::{TrayIcon, TrayIconBuilder}, + Runtime, WindowUrl, +}; -// pub fn tray() -> TrayIconBuilder { -// TrayIconBuilder::new() -// .with_tooltip() -// } +pub fn create_tray(app: &tauri::App) -> tauri::Result> { + let app = app.handle(); + TrayIconBuilder::new() + .with_tooltip("Tauri") + .with_menu(&Menu::with_items( + &app, + &[ + &MenuItem::new(&app, "Toggle", true, None), + &MenuItem::new(&app, "New window", true, None), + &MenuItem::new(&app, "Tray Icon 1", true, None), + &MenuItem::new(&app, "Tray Icon 2", true, None), + ], + )?) + .build(&app) +} +/* pub fn create_tray(app: &tauri::App) -> tauri::Result<()> { let mut tray_menu1 = SystemTrayMenu::new() .add_item(CustomMenuItem::new("toggle", "Toggle")) @@ -124,3 +140,4 @@ pub fn create_tray(app: &tauri::App) -> tauri::Result<()> { .build(app) .map(|_| ()) } +*/ From 741a2d7dcd943812bfd8f46b0f3c91ab6ac1ec78 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 1 Aug 2023 09:41:25 -0300 Subject: [PATCH 024/123] fix linux build --- core/tauri-runtime-wry/src/lib.rs | 43 +++++++++- core/tauri-runtime/src/lib.rs | 10 +++ core/tauri/src/app.rs | 90 +++++++------------- core/tauri/src/menu/menu.rs | 14 ++-- core/tauri/src/menu/mod.rs | 4 +- core/tauri/src/menu/submenu.rs | 12 +-- core/tauri/src/test/mock_runtime.rs | 11 +++ core/tauri/src/window.rs | 125 +++++++++++----------------- 8 files changed, 160 insertions(+), 149 deletions(-) diff --git a/core/tauri-runtime-wry/src/lib.rs b/core/tauri-runtime-wry/src/lib.rs index 2227458f8b21..e5a063ae5035 100644 --- a/core/tauri-runtime-wry/src/lib.rs +++ b/core/tauri-runtime-wry/src/lib.rs @@ -916,6 +916,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 {} @@ -965,6 +983,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 @@ -1204,7 +1230,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", @@ -1216,6 +1241,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) } @@ -2172,6 +2208,9 @@ fn handle_user_message( target_os = "openbsd" ))] WindowMessage::GtkWindow(tx) => tx.send(GtkWindow(window.gtk_window().clone())).unwrap(), + WindowMessage::GtkBox(tx) => tx + .send(GtkBox(window.default_vbox().unwrap().clone())) + .unwrap(), WindowMessage::RawWindowHandle(tx) => tx .send(RawWindowHandle(window.raw_window_handle())) .unwrap(), @@ -2585,7 +2624,7 @@ fn create_webview( target_os = "netbsd", target_os = "openbsd" ))] - let _ = menu.init_for_gtk_window(window.gtk_window()); + let _ = menu.init_for_gtk_window(window.gtk_window(), window.default_vbox()); } webview_id_map.insert(window.id(), window_id); diff --git a/core/tauri-runtime/src/lib.rs b/core/tauri-runtime/src/lib.rs index e1970f63580c..5567d65cdb43 100644 --- a/core/tauri-runtime/src/lib.rs +++ b/core/tauri-runtime/src/lib.rs @@ -463,6 +463,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. diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 57c46c958da7..14158afbc034 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -595,20 +595,13 @@ macro_rules! shared_app_impl { for window in self.manager.windows_lock().values() { let mut window_menu = window.menu_lock(); if window_menu.as_ref().map(|m| m.0).unwrap_or(true) { - #[cfg(windows)] - let hwnd = window.hwnd()?.0; - #[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ))] - let gtk_window = window.gtk_window()?; - let menu_c = menu.clone(); + let window = window.clone(); + let menu_ = menu.clone(); self.run_on_main_thread(move || { #[cfg(windows)] - let _ = menu_c.inner().init_for_hwnd(hwnd); + if let Ok(hwnd) = window.hwnd() { + let _ = menu_.inner().init_for_hwnd(hwnd.0); + } #[cfg(any( target_os = "linux", @@ -617,10 +610,14 @@ macro_rules! shared_app_impl { target_os = "netbsd", target_os = "openbsd" ))] - let _ = menu_c.inner().init_for_gtk_window(>k_window); + 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)); + } #[cfg(target_os = "macos")] - menu_c.inner().init_for_nsapp(); + menu_.inner().init_for_nsapp(); })?; window_menu.replace((true, menu.clone())); } @@ -645,20 +642,13 @@ macro_rules! shared_app_impl { if let Some(menu) = &*current_menu { for window in self.manager.windows_lock().values() { if window.has_app_wide_menu() { - #[cfg(windows)] - let hwnd = window.hwnd()?.0; - #[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ))] - let gtk_window = window.gtk_window()?; - let menu_c = menu.clone(); + let window_ = window.clone(); + let menu_ = menu.clone(); self.run_on_main_thread(move || { #[cfg(windows)] - let _ = menu_c.inner().remove_for_hwnd(hwnd); + if let Ok(hwnd) = window_.hwnd() { + let _ = menu_.inner().remove_for_hwnd(hwnd.0); + } #[cfg(any( target_os = "linux", @@ -667,10 +657,12 @@ macro_rules! shared_app_impl { target_os = "netbsd", target_os = "openbsd" ))] - let _ = menu_c.inner().remove_for_gtk_window(>k_window); + if let Ok(gtk_window) = window_.gtk_window() { + let _ = menu_.inner().remove_for_gtk_window(>k_window); + } #[cfg(target_os = "macos")] - let _ = menu_c.inner().remove_for_nsapp(); + let _ = menu_.inner().remove_for_nsapp(); })?; *window.menu_lock() = None; } @@ -702,21 +694,12 @@ macro_rules! shared_app_impl { if let Some(menu) = &*self.manager.menu_lock() { for window in self.manager.windows_lock().values() { if window.has_app_wide_menu() { - #[cfg(windows)] - let hwnd = window.hwnd()?.0; - #[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ))] - let gtk_window = window.gtk_window()?; - let menu_c = menu.clone(); + let window = window.clone(); + let menu_ = menu.clone(); self.run_on_main_thread(move || { #[cfg(windows)] - { - let _ = menu_c.inner().hide_for_hwnd(hwnd); + if let Ok(hwnd) = window.hwnd() { + let _ = menu_.inner().hide_for_hwnd(hwnd.0); } #[cfg(any( target_os = "linux", @@ -725,8 +708,8 @@ macro_rules! shared_app_impl { target_os = "netbsd", target_os = "openbsd" ))] - { - let _ = menu_c.inner().hide_for_gtk_window(>k_window); + if let Ok(gtk_window) = window.gtk_window() { + let _ = menu_.inner().hide_for_gtk_window(>k_window); } })?; } @@ -746,21 +729,12 @@ macro_rules! shared_app_impl { if let Some(menu) = &*self.manager.menu_lock() { for window in self.manager.windows_lock().values() { if window.has_app_wide_menu() { - #[cfg(windows)] - let hwnd = window.hwnd()?.0; - #[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ))] - let gtk_window = window.gtk_window()?; - let menu_c = menu.clone(); + let window = window.clone(); + let menu_ = menu.clone(); self.run_on_main_thread(move || { #[cfg(windows)] - { - let _ = menu_c.inner().show_for_hwnd(hwnd); + if let Ok(hwnd) = window.hwnd() { + let _ = menu_.inner().show_for_hwnd(hwnd.0); } #[cfg(any( target_os = "linux", @@ -769,8 +743,8 @@ macro_rules! shared_app_impl { target_os = "netbsd", target_os = "openbsd" ))] - { - let _ = menu_c.inner().show_for_gtk_window(>k_window); + if let Ok(gtk_window) = window.gtk_window() { + let _ = menu_.inner().show_for_gtk_window(>k_window); } })?; } diff --git a/core/tauri/src/menu/menu.rs b/core/tauri/src/menu/menu.rs index 8f74356a193b..5f7d4b17359e 100644 --- a/core/tauri/src/menu/menu.rs +++ b/core/tauri/src/menu/menu.rs @@ -57,14 +57,18 @@ impl super::sealed::ContextMenuBase for Menu { target_os = "netbsd", target_os = "openbsd" ))] - fn show_context_menu_for_gtk_window( + fn show_context_menu_for_gtk_window( &self, - w: >k::ApplicationWindow, + window: crate::Window, position: Option, ) -> crate::Result<()> { - run_main_thread!(self, |self_: Self| self_ - .inner() - .show_context_menu_for_gtk_window(w, position.map(Into::into))) + run_main_thread!(self, |self_: Self| { + if let Ok(gtk_window) = window.gtk_window() { + self_ + .inner() + .show_context_menu_for_gtk_window(>k_window, position.map(Into::into)) + } + }) } #[cfg(target_os = "macos")] diff --git a/core/tauri/src/menu/mod.rs b/core/tauri/src/menu/mod.rs index a8bf1718f08e..4cb7342dc069 100644 --- a/core/tauri/src/menu/mod.rs +++ b/core/tauri/src/menu/mod.rs @@ -191,9 +191,9 @@ pub(crate) mod sealed { target_os = "netbsd", target_os = "openbsd" ))] - fn show_context_menu_for_gtk_window( + fn show_context_menu_for_gtk_window( &self, - w: >k::ApplicationWindow, + window: crate::Window, position: Option, ) -> crate::Result<()>; diff --git a/core/tauri/src/menu/submenu.rs b/core/tauri/src/menu/submenu.rs index 9f27e6f0a6b6..33f6c76b23ff 100644 --- a/core/tauri/src/menu/submenu.rs +++ b/core/tauri/src/menu/submenu.rs @@ -76,15 +76,17 @@ impl super::sealed::ContextMenuBase for Submenu { target_os = "netbsd", target_os = "openbsd" ))] - fn show_context_menu_for_gtk_window( + fn show_context_menu_for_gtk_window( &self, - w: >k::ApplicationWindow, + window: crate::Window, position: Option, ) -> crate::Result<()> { run_main_thread!(self, |self_: Self| { - self_ - .inner() - .show_context_menu_for_gtk_window(w, position.map(Into::into)) + if let Ok(w) = window.gtk_window() { + self_ + .inner() + .show_context_menu_for_gtk_window(&w, position.map(Into::into)) + } }) } diff --git a/core/tauri/src/test/mock_runtime.rs b/core/tauri/src/test/mock_runtime.rs index 2023a1922198..301564274d72 100644 --- a/core/tauri/src/test/mock_runtime.rs +++ b/core/tauri/src/test/mock_runtime.rs @@ -485,6 +485,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( diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index cbc5923af30b..f532138a068a 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -1103,21 +1103,12 @@ impl Window { self.manager.insert_menu_into_stash(&menu); - #[cfg(windows)] - let hwnd = self.hwnd()?.0; - #[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ))] - let gtk_window = self.gtk_window()?; - let menu_c = menu.clone(); + let window = self.clone(); + let menu_ = menu.clone(); self.run_on_main_thread(move || { #[cfg(windows)] - { - let _ = menu_c.inner().init_for_hwnd(hwnd); + if let Ok(hwnd) = window.hwnd() { + let _ = menu_.inner().init_for_hwnd(hwnd.0); } #[cfg(any( target_os = "linux", @@ -1126,8 +1117,10 @@ impl Window { target_os = "netbsd", target_os = "openbsd" ))] - { - let _ = menu_c.inner().init_for_gtk_window(>k_window); + 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)); } })?; @@ -1149,21 +1142,12 @@ impl Window { // remove from the window if let Some((_, menu)) = &*current_menu { - #[cfg(windows)] - let hwnd = self.hwnd()?.0; - #[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ))] - let gtk_window = self.gtk_window()?; - let menu_c = menu.clone(); + let window = self.clone(); + let menu_ = menu.clone(); self.run_on_main_thread(move || { #[cfg(windows)] - { - let _ = menu_c.inner().remove_for_hwnd(hwnd); + if let Ok(hwnd) = window.hwnd() { + let _ = menu_.inner().remove_for_hwnd(hwnd.0); } #[cfg(any( target_os = "linux", @@ -1172,8 +1156,8 @@ impl Window { target_os = "netbsd", target_os = "openbsd" ))] - { - let _ = menu_c.inner().remove_for_gtk_window(>k_window); + if let Ok(gtk_window) = window.gtk_window() { + let _ = menu_.inner().remove_for_gtk_window(>k_window); } })?; } @@ -1195,21 +1179,12 @@ impl Window { pub fn hide_menu(&self) -> crate::Result<()> { // remove from the window if let Some((_, menu)) = &*self.menu_lock() { - #[cfg(windows)] - let hwnd = self.hwnd()?.0; - #[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ))] - let gtk_window = self.gtk_window()?; - let menu_c = menu.clone(); + let window = self.clone(); + let menu_ = menu.clone(); self.run_on_main_thread(move || { #[cfg(windows)] - { - let _ = menu_c.inner().hide_for_hwnd(hwnd); + if let Ok(hwnd) = window.hwnd() { + let _ = menu_.inner().hide_for_hwnd(hwnd.0); } #[cfg(any( target_os = "linux", @@ -1218,8 +1193,8 @@ impl Window { target_os = "netbsd", target_os = "openbsd" ))] - { - let _ = menu_c.inner().hide_for_gtk_window(>k_window); + if let Ok(gtk_window) = window.gtk_window() { + let _ = menu_.inner().hide_for_gtk_window(>k_window); } })?; } @@ -1233,21 +1208,12 @@ impl Window { pub fn show_menu(&self) -> crate::Result<()> { // remove from the window if let Some((_, menu)) = &*self.menu_lock() { - #[cfg(windows)] - let hwnd = self.hwnd()?.0; - #[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ))] - let gtk_window = self.gtk_window()?; - let menu_c = menu.clone(); + let window = self.clone(); + let menu_ = menu.clone(); self.run_on_main_thread(move || { #[cfg(windows)] - { - let _ = menu_c.inner().show_for_hwnd(hwnd); + if let Ok(hwnd) = widnow.hwnd() { + let _ = menu_.inner().show_for_hwnd(hwnd.0); } #[cfg(any( target_os = "linux", @@ -1256,8 +1222,8 @@ impl Window { target_os = "netbsd", target_os = "openbsd" ))] - { - let _ = menu_c.inner().show_for_gtk_window(>k_window); + if let Ok(gtk_window) = window.gtk_window() { + let _ = menu_.inner().show_for_gtk_window(>k_window); } })?; } @@ -1272,21 +1238,12 @@ impl Window { // remove from the window if let Some((_, menu)) = &*self.menu_lock() { let (tx, rx) = std::sync::mpsc::channel(); - #[cfg(windows)] - let hwnd = self.hwnd()?.0; - #[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ))] - let gtk_window = self.gtk_window()?; - let menu_c = menu.clone(); + let window = self.clone(); + let menu_ = menu.clone(); self.run_on_main_thread(move || { #[cfg(windows)] - { - let _ = tx.send(menu_c.inner().is_visible_on_hwnd(hwnd)); + if let Ok(hwnd) = window.hwnd() { + let _ = tx.send(menu_.inner().is_visible_on_hwnd(hwnd.0)); } #[cfg(any( target_os = "linux", @@ -1295,8 +1252,8 @@ impl Window { target_os = "netbsd", target_os = "openbsd" ))] - { - let _ = tx.send(menu_c.inner().is_visible_on_gtk_window(>k_window)); + if let Ok(gtk_window) = window.gtk_window() { + let _ = tx.send(menu_.inner().is_visible_on_gtk_window(>k_window)); } })?; @@ -1326,7 +1283,7 @@ impl Window { target_os = "netbsd", target_os = "openbsd" ))] - menu.show_context_menu_for_gtk_window(&self.gtk_window()?, position)?; + menu.show_context_menu_for_gtk_window(self.clone(), position)?; #[cfg(target_os = "macos")] menu.show_context_menu_for_nsview(self.clone(), position)?; @@ -1583,7 +1540,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", @@ -1595,6 +1552,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 From ad18aac66675ca07ff38aa9d45d8bbafee6e7f93 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Tue, 1 Aug 2023 16:33:42 +0300 Subject: [PATCH 025/123] change `Builder::tray_icon` to be a function --- core/tauri-runtime-wry/src/lib.rs | 7 +++++++ core/tauri/src/app.rs | 15 +++++++++------ core/tauri/src/window.rs | 2 +- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/core/tauri-runtime-wry/src/lib.rs b/core/tauri-runtime-wry/src/lib.rs index e5a063ae5035..03c552d9f3f1 100644 --- a/core/tauri-runtime-wry/src/lib.rs +++ b/core/tauri-runtime-wry/src/lib.rs @@ -2208,6 +2208,13 @@ fn handle_user_message( 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(), diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 14158afbc034..b52de0a7b265 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -985,7 +985,7 @@ pub struct Builder { window_event_listeners: Vec>, /// Tray icon builder - tray_icons: Vec, + tray_icons_builders: Vec) -> crate::Result>>>, /// Tray icon event handlers. tray_event_listeners: Vec>, @@ -1014,7 +1014,7 @@ impl Builder { enable_macos_default_menu: true, menu_event_listeners: Vec::new(), window_event_listeners: Vec::new(), - tray_icons: Vec::new(), + tray_icons_builders: Vec::new(), tray_event_listeners: Vec::new(), device_event_filter: Default::default(), } @@ -1258,8 +1258,11 @@ impl Builder { /// )); /// ``` #[must_use] - pub fn tray_icon(mut self, tray: TrayIconBuilder) -> Self { - self.tray_icons.push(tray); + pub fn tray_icon) -> crate::Result> + 'static>( + mut self, + tray_builder: F, + ) -> Self { + self.tray_icons_builders.push(Box::new(tray_builder)); self } @@ -1601,8 +1604,8 @@ impl Builder { } // tray icon registered on the builder - for tray_builder in self.tray_icons { - tray_stash.push(tray_builder.build(&handle)?); + for tray_builder in self.tray_icons_builders { + tray_stash.push(tray_builder(&handle)?); } } diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index f532138a068a..a023f48b8fc1 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -1212,7 +1212,7 @@ impl Window { let menu_ = menu.clone(); self.run_on_main_thread(move || { #[cfg(windows)] - if let Ok(hwnd) = widnow.hwnd() { + if let Ok(hwnd) = window.hwnd() { let _ = menu_.inner().show_for_hwnd(hwnd.0); } #[cfg(any( From be778716168b61461049121cf31e8d9c3c9888e6 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Tue, 1 Aug 2023 17:01:36 +0300 Subject: [PATCH 026/123] Add a getter for apphandle --- Cargo.toml | 1 - core/tauri/src/menu/check.rs | 5 +++++ core/tauri/src/menu/icon.rs | 5 +++++ core/tauri/src/menu/menu.rs | 5 +++++ core/tauri/src/menu/normal.rs | 5 +++++ core/tauri/src/menu/predefined.rs | 5 +++++ core/tauri/src/menu/submenu.rs | 5 +++++ core/tauri/src/tray.rs | 5 +++++ 8 files changed, 35 insertions(+), 1 deletion(-) 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/src/menu/check.rs b/core/tauri/src/menu/check.rs index 96ceccd9813d..c2c277b630ab 100644 --- a/core/tauri/src/menu/check.rs +++ b/core/tauri/src/menu/check.rs @@ -67,6 +67,11 @@ impl CheckMenuItem { } } + /// Gets a reference to the [`AppHandle`] + pub fn app_handle(&self) -> &AppHandle { + &self.app_handle + } + /// Returns a unique identifier associated with this menu item. pub fn id(&self) -> crate::Result { run_main_thread!(self, |self_: Self| self_.inner.id()) diff --git a/core/tauri/src/menu/icon.rs b/core/tauri/src/menu/icon.rs index da5237f9a002..79fa552ca872 100644 --- a/core/tauri/src/menu/icon.rs +++ b/core/tauri/src/menu/icon.rs @@ -95,6 +95,11 @@ impl IconMenuItem { } } + /// Gets a reference to the [`AppHandle`] + pub fn app_handle(&self) -> &AppHandle { + &self.app_handle + } + /// Returns a unique identifier associated with this menu item. pub fn id(&self) -> crate::Result { run_main_thread!(self, |self_: Self| self_.inner.id()) diff --git a/core/tauri/src/menu/menu.rs b/core/tauri/src/menu/menu.rs index 5f7d4b17359e..cfc15c12cbfb 100644 --- a/core/tauri/src/menu/menu.rs +++ b/core/tauri/src/menu/menu.rs @@ -196,6 +196,11 @@ impl Menu { &self.inner } + /// Gets a reference to the [`AppHandle`] + pub fn app_handle(&self) -> &AppHandle { + &self.app_handle + } + /// Returns a unique identifier associated with this menu. pub fn id(&self) -> crate::Result { run_main_thread!(self, |self_: Self| self_.inner.id()) diff --git a/core/tauri/src/menu/normal.rs b/core/tauri/src/menu/normal.rs index 987fa047c237..b175d135c3d9 100644 --- a/core/tauri/src/menu/normal.rs +++ b/core/tauri/src/menu/normal.rs @@ -65,6 +65,11 @@ impl MenuItem { } } + /// Gets a reference to the [`AppHandle`] + pub fn app_handle(&self) -> &AppHandle { + &self.app_handle + } + /// Returns a unique identifier associated with this menu item. pub fn id(&self) -> crate::Result { run_main_thread!(self, |self_: Self| self_.inner.id()) diff --git a/core/tauri/src/menu/predefined.rs b/core/tauri/src/menu/predefined.rs index ba6f132219fd..bf89ad37a345 100644 --- a/core/tauri/src/menu/predefined.rs +++ b/core/tauri/src/menu/predefined.rs @@ -243,4 +243,9 @@ impl PredefinedMenuItem { let text = text.as_ref().to_string(); run_main_thread!(self, |self_: Self| self_.inner.set_text(text)) } + + /// Gets a reference to the [`AppHandle`] + 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 index 33f6c76b23ff..1060f848e086 100644 --- a/core/tauri/src/menu/submenu.rs +++ b/core/tauri/src/menu/submenu.rs @@ -131,6 +131,11 @@ impl Submenu { &self.inner } + /// Gets a reference to the [`AppHandle`] + pub fn app_handle(&self) -> &AppHandle { + &self.app_handle + } + /// Returns a unique identifier associated with this submenu. pub fn id(&self) -> crate::Result { run_main_thread!(self, |self_: Self| self_.inner.id()) diff --git a/core/tauri/src/tray.rs b/core/tauri/src/tray.rs index 1c9d87691c48..8630df089ed8 100644 --- a/core/tauri/src/tray.rs +++ b/core/tauri/src/tray.rs @@ -235,6 +235,11 @@ impl TrayIcon { }) } + /// Gets a reference to the [`AppHandle`] + pub fn app_handle(&self) -> &AppHandle { + &self.app_handle + } + /// Returns the id associated with this tray icon. pub fn id(&self) -> crate::Result { run_main_thread!(self, |self_: Self| self_.inner.id()) From aef5e96af4a62e5ba29a95e489fcf24ab4b4ae5e Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Tue, 1 Aug 2023 17:03:15 +0300 Subject: [PATCH 027/123] change it to an owned handle --- core/tauri/src/menu/check.rs | 6 +++--- core/tauri/src/menu/icon.rs | 6 +++--- core/tauri/src/menu/menu.rs | 6 +++--- core/tauri/src/menu/normal.rs | 6 +++--- core/tauri/src/menu/predefined.rs | 6 +++--- core/tauri/src/menu/submenu.rs | 6 +++--- core/tauri/src/tray.rs | 6 +++--- 7 files changed, 21 insertions(+), 21 deletions(-) diff --git a/core/tauri/src/menu/check.rs b/core/tauri/src/menu/check.rs index c2c277b630ab..0c7f4a0c3bcd 100644 --- a/core/tauri/src/menu/check.rs +++ b/core/tauri/src/menu/check.rs @@ -67,9 +67,9 @@ impl CheckMenuItem { } } - /// Gets a reference to the [`AppHandle`] - pub fn app_handle(&self) -> &AppHandle { - &self.app_handle + /// The application handle associated with this type. + pub fn app_handle(&self) -> AppHandle { + self.app_handle.clone() } /// Returns a unique identifier associated with this menu item. diff --git a/core/tauri/src/menu/icon.rs b/core/tauri/src/menu/icon.rs index 79fa552ca872..3048f131bc3c 100644 --- a/core/tauri/src/menu/icon.rs +++ b/core/tauri/src/menu/icon.rs @@ -95,9 +95,9 @@ impl IconMenuItem { } } - /// Gets a reference to the [`AppHandle`] - pub fn app_handle(&self) -> &AppHandle { - &self.app_handle + /// The application handle associated with this type. + pub fn app_handle(&self) -> AppHandle { + self.app_handle.clone() } /// Returns a unique identifier associated with this menu item. diff --git a/core/tauri/src/menu/menu.rs b/core/tauri/src/menu/menu.rs index cfc15c12cbfb..8dcdd05e7f52 100644 --- a/core/tauri/src/menu/menu.rs +++ b/core/tauri/src/menu/menu.rs @@ -196,9 +196,9 @@ impl Menu { &self.inner } - /// Gets a reference to the [`AppHandle`] - pub fn app_handle(&self) -> &AppHandle { - &self.app_handle + /// The application handle associated with this type. + pub fn app_handle(&self) -> AppHandle { + self.app_handle.clone() } /// Returns a unique identifier associated with this menu. diff --git a/core/tauri/src/menu/normal.rs b/core/tauri/src/menu/normal.rs index b175d135c3d9..64dea20b602b 100644 --- a/core/tauri/src/menu/normal.rs +++ b/core/tauri/src/menu/normal.rs @@ -65,9 +65,9 @@ impl MenuItem { } } - /// Gets a reference to the [`AppHandle`] - pub fn app_handle(&self) -> &AppHandle { - &self.app_handle + /// The application handle associated with this type. + pub fn app_handle(&self) -> AppHandle { + self.app_handle.clone() } /// Returns a unique identifier associated with this menu item. diff --git a/core/tauri/src/menu/predefined.rs b/core/tauri/src/menu/predefined.rs index bf89ad37a345..dc4f0d95c77b 100644 --- a/core/tauri/src/menu/predefined.rs +++ b/core/tauri/src/menu/predefined.rs @@ -244,8 +244,8 @@ impl PredefinedMenuItem { run_main_thread!(self, |self_: Self| self_.inner.set_text(text)) } - /// Gets a reference to the [`AppHandle`] - pub fn app_handle(&self) -> &AppHandle { - &self.app_handle + /// The application handle associated with this type. + pub fn app_handle(&self) -> AppHandle { + self.app_handle.clone() } } diff --git a/core/tauri/src/menu/submenu.rs b/core/tauri/src/menu/submenu.rs index 1060f848e086..67ec297a0c28 100644 --- a/core/tauri/src/menu/submenu.rs +++ b/core/tauri/src/menu/submenu.rs @@ -131,9 +131,9 @@ impl Submenu { &self.inner } - /// Gets a reference to the [`AppHandle`] - pub fn app_handle(&self) -> &AppHandle { - &self.app_handle + /// The application handle associated with this type. + pub fn app_handle(&self) -> AppHandle { + self.app_handle.clone() } /// Returns a unique identifier associated with this submenu. diff --git a/core/tauri/src/tray.rs b/core/tauri/src/tray.rs index 8630df089ed8..623db83442f2 100644 --- a/core/tauri/src/tray.rs +++ b/core/tauri/src/tray.rs @@ -235,9 +235,9 @@ impl TrayIcon { }) } - /// Gets a reference to the [`AppHandle`] - pub fn app_handle(&self) -> &AppHandle { - &self.app_handle + /// The application handle associated with this type. + pub fn app_handle(&self) -> AppHandle { + self.app_handle.clone() } /// Returns the id associated with this tray icon. From f888fa08982c52336c70dbcb625715e3a8283e24 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Tue, 1 Aug 2023 19:41:56 +0300 Subject: [PATCH 028/123] add `on_menu_event` and `on_tray_even` for `TrayIcon` & fix api example --- core/tauri/src/app.rs | 16 +- core/tauri/src/manager.rs | 11 +- core/tauri/src/tray.rs | 130 ++++++++++++--- examples/api/dist/assets/index.css | 2 +- examples/api/dist/assets/index.js | 16 +- examples/api/src-tauri/Cargo.lock | 4 - examples/api/src-tauri/src/lib.rs | 3 +- examples/api/src-tauri/src/tray.rs | 221 +++++++++++-------------- examples/api/src-tauri/tauri.conf.json | 5 - 9 files changed, 239 insertions(+), 169 deletions(-) diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index b52de0a7b265..df9575cc35f8 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -1753,12 +1753,26 @@ fn on_event_loop_event, RunEvent) + 'static>( for listener in &*app_handle .manager .inner - .tray_event_listeners + .global_tray_event_listeners .lock() .unwrap() { listener(app_handle, e) } + + 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) + } + } + } } } diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index 0ee26fbb96a8..6d34bd5af729 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -74,6 +74,8 @@ const WINDOW_FILE_DROP_CANCELLED_EVENT: &str = "tauri://file-drop-cancelled"; pub(crate) const STRINGIFY_IPC_MESSAGE_FN: &str = include_str!("../scripts/stringify-ipc-message-fn.js"); +type TrayIconEventListener = Box, crate::tray::TrayIconEvent) + Send + Sync>; + // we need to proxy the dev server on mobile because we can't use `localhost`, so we use the local IP address // and we do not get a secure context without the custom protocol that proxies to the dev server // additionally, we need the custom protocol to inject the initialization scripts on Android @@ -238,8 +240,10 @@ pub struct InnerWindowManager { window_event_listeners: Arc>>, /// Tray icons pub(crate) tray_icons: Arc>>>, + /// Global Tray icon event listeners. + pub(crate) global_tray_event_listeners: Arc>>>, /// Tray icon event listeners. - pub(crate) tray_event_listeners: Arc>>>, + pub(crate) tray_event_listeners: Arc>>>, /// Responder for invoke calls. invoke_responder: Arc>, /// The script that initializes the invoke system. @@ -316,7 +320,7 @@ impl WindowManager { Vec>>, HashMap>>, ), - tray_event_listeners: Vec>, + global_tray_event_listeners: Vec>, (invoke_responder, invoke_initialization_script): (Arc>, String), ) -> Self { // generate a random isolation key at runtime @@ -348,7 +352,8 @@ impl WindowManager { window_menu_event_listeners: Arc::new(Mutex::new(window_menu_event_listeners)), window_event_listeners: Arc::new(window_event_listeners), tray_icons: Default::default(), - tray_event_listeners: Arc::new(Mutex::new(tray_event_listeners)), + global_tray_event_listeners: Arc::new(Mutex::new(global_tray_event_listeners)), + tray_event_listeners: Arc::new(Mutex::new(HashMap::new())), invoke_responder, invoke_initialization_script, }), diff --git a/core/tauri/src/tray.rs b/core/tauri/src/tray.rs index 623db83442f2..3dabc158199c 100644 --- a/core/tauri/src/tray.rs +++ b/core/tauri/src/tray.rs @@ -4,18 +4,17 @@ //! Tray icon types and utility functions -use std::path::{Path, PathBuf}; - -pub use crate::runtime::tray::TrayIconEvent; +use crate::menu::MenuEvent; use crate::{menu::ContextMenu, runtime::tray as tray_icon}; use crate::{run_main_thread, AppHandle, Icon, Runtime}; +use std::path::{Path, PathBuf}; +pub use tray_icon::{ClickType, Rectangle, TrayIconEvent}; -// TODO(muda-migration): tray icon type `on_event` handler // TODO(muda-migration): figure out js events /// Attributes to use when creating a tray icon. #[derive(Default)] -pub struct TrayIconAttributes { +pub struct TrayIconAttributes { /// Tray icon tooltip /// /// ## Platform-specific: @@ -30,6 +29,12 @@ pub struct TrayIconAttributes { /// - **Linux**: once a menu is set, it cannot be removed. pub menu: Option>, + /// Set a handler for menu events + pub on_menu_event: Option, MenuEvent) + Send + Sync + 'static>>, + + /// Set a handler for tray icon events + pub on_tray_event: Option, TrayIconEvent) + Send + Sync + 'static>>, + /// Tray icon /// /// ## Platform-specific: @@ -60,8 +65,8 @@ pub struct TrayIconAttributes { pub title: Option, } -impl From for tray_icon::TrayIconAttributes { - fn from(value: TrayIconAttributes) -> Self { +impl From> for tray_icon::TrayIconAttributes { + fn from(value: TrayIconAttributes) -> Self { Self { tooltip: value.tooltip, menu: value.menu, @@ -80,19 +85,27 @@ impl From for tray_icon::TrayIconAttributes { /// [`TrayIcon`] builder struct and associated methods. #[derive(Default)] -pub struct TrayIconBuilder(tray_icon::TrayIconBuilder); +pub struct TrayIconBuilder { + on_menu_event: Option, MenuEvent) + Sync + Send + 'static>>, + on_tray_event: Option, TrayIconEvent) + Sync + Send + 'static>>, + inner: tray_icon::TrayIconBuilder, +} -impl TrayIconBuilder { +impl TrayIconBuilder { /// Creates a new [`TrayIconBuilder`] with default [`TrayIconAttributes`]. /// /// See [`TrayIcon::new`] for more info. pub fn new() -> Self { - Self(tray_icon::TrayIconBuilder::new()) + Self { + inner: tray_icon::TrayIconBuilder::new(), + on_menu_event: None, + on_tray_event: None, + } } /// Sets the unique id to build the tray icon with. pub fn with_id(mut self, id: u32) -> Self { - self.0 = self.0.with_id(id); + self.inner = self.inner.with_id(id); self } @@ -102,7 +115,7 @@ impl TrayIconBuilder { /// /// - **Linux**: once a menu is set, it cannot be removed or replaced but you can change its content. pub fn with_menu(mut self, menu: &M) -> Self { - self.0 = self.0.with_menu(menu.inner_owned()); + self.inner = self.inner.with_menu(menu.inner_owned()); self } @@ -118,7 +131,7 @@ impl TrayIconBuilder { .ok() .and_then(|i: crate::runtime::Icon| i.try_into().ok()); if let Some(icon) = icon { - self.0 = self.0.with_icon(icon); + self.inner = self.inner.with_icon(icon); } self } @@ -129,7 +142,7 @@ impl TrayIconBuilder { /// /// - **Linux:** Unsupported. pub fn with_tooltip>(mut self, s: S) -> Self { - self.0 = self.0.with_tooltip(s); + self.inner = self.inner.with_tooltip(s); self } @@ -144,7 +157,7 @@ impl TrayIconBuilder { /// on the user's panel. This may not be shown in all visualizations. /// - **Windows:** Unsupported. pub fn with_title>(mut self, title: S) -> Self { - self.0 = self.0.with_title(title); + self.inner = self.inner.with_title(title); self } @@ -153,32 +166,75 @@ impl TrayIconBuilder { /// 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 with_temp_dir_path>(mut self, s: P) -> Self { - self.0 = self.0.with_temp_dir_path(s); + 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 with_icon_as_template(mut self, is_template: bool) -> Self { - self.0 = self.0.with_icon_as_template(is_template); + 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 with_menu_on_left_click(mut self, enable: bool) -> Self { - self.0 = self.0.with_menu_on_left_click(enable); + self.inner = self.inner.with_menu_on_left_click(enable); + self + } + + /// Set a handler for menu events. + /// + /// Note that this handler is global and will be triggered for all menu events. + pub fn with_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 with_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) -> u32 { - self.0.id() + self.inner.id() } /// Builds and adds a new [`TrayIcon`] to the system tray. - pub fn build(self, app_handle: &AppHandle) -> crate::Result> { + pub fn build(self, app_handle: &AppHandle) -> crate::Result> { + let id = self.id(); + let inner = self.inner.build()?; + + if let Some(handler) = self.on_menu_event { + app_handle + .manager + .inner + .menu_event_listeners + .lock() + .unwrap() + .push(handler); + } + + if let Some(handler) = self.on_tray_event { + app_handle + .manager + .inner + .tray_event_listeners + .lock() + .unwrap() + .insert(id, handler); + } + Ok(TrayIcon { - inner: self.0.build()?, + inner, app_handle: app_handle.clone(), }) } @@ -214,9 +270,35 @@ impl TrayIcon { /// /// - **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(app_handle: &AppHandle, attrs: TrayIconAttributes) -> crate::Result { + pub fn new(app_handle: &AppHandle, mut attrs: TrayIconAttributes) -> crate::Result { + let on_menu_event = attrs.on_menu_event.take(); + let on_tray_event = attrs.on_tray_event.take(); + + let inner = tray_icon::TrayIcon::new(attrs.into())?; + let id = inner.id(); + + 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(id, handler); + } + Ok(Self { - inner: tray_icon::TrayIcon::new(attrs.into())?, + inner, app_handle: app_handle.clone(), }) } @@ -226,7 +308,7 @@ impl TrayIcon { /// See [`TrayIcon::new`] for more info. pub fn with_id( app_handle: &AppHandle, - attrs: TrayIconAttributes, + attrs: TrayIconAttributes, id: u32, ) -> crate::Result { Ok(Self { diff --git a/examples/api/dist/assets/index.css b/examples/api/dist/assets/index.css index 1148ece6d18e..ed7a460dcca1 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;}.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 0ee0c82e2d75..dfe16f68e1a5 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 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 $(){}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 l(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:m,after_update:c}=e.$$;r&&r.m(t,n),i||We(()=>{const u=a.map(st).filter(vt);m?m.push(...u):V(u),e.$$.on_mount=[]}),c.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(c){if(he(e,c)&&(e=c,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:m}}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 +(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 h of a.addedNodes)h.tagName==="LINK"&&h.rel==="modulepreload"&&i(h)}).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 rt(e){return e()}function Ge(){return Object.create(null)}function F(e){e.forEach(rt)}function pt(e){return typeof e=="function"}function ue(e,t){return e!=e?t==t:e!==t||e&&typeof e=="object"||typeof e=="function"}let be;function gt(e,t){return be||(be=document.createElement("a")),be.href=t,e===be.href}function _t(e){return Object.keys(e).length===0}function vt(e,...t){if(e==null)return $;const n=e.subscribe(...t);return n.unsubscribe?()=>n.unsubscribe():n}function bt(e,t,n){e.$$.on_destroy.push(vt(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 Xe(e,t){for(let n=0;ne.removeEventListener(t,n,i)}function l(e,t,n){n==null?e.removeAttribute(t):e.getAttribute(t)!==n&&e.setAttribute(t,n)}function wt(e){return Array.from(e.childNodes)}function kt(e,t){t=""+t,e.wholeText!==t&&(e.data=t)}class Et{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=yt(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{ke.delete(e),i&&(n&&e.d(1),i())}),e.o(t)}else i&&i()}function Je(e){e&&e.c()}function We(e,t,n,i){const{fragment:r,on_mount:a,on_destroy:h,after_update:c}=e.$$;r&&r.m(t,n),i||Ie(()=>{const u=a.map(rt).filter(pt);h?h.push(...u):F(u),e.$$.on_mount=[]}),c.forEach(Ie)}function Me(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 xt(e,t){e.$$.dirty[0]===-1&&(le.push(e),Lt(),e.$$.dirty.fill(0)),e.$$.dirty[t/31|0]|=1<{const T=R.length?R[0]:D;return d.ctx&&r(d.ctx[v],d.ctx[v]=T)&&(!d.skip_bound&&d.bound[v]&&d.bound[v](T),E&&xt(e,v)),D}):[],d.update(),E=!0,F(d.before_update),d.fragment=i?i(d.ctx):!1,t.target){if(t.hydrate){const v=wt(t.target);d.fragment&&d.fragment.l(v),v.forEach(w)}else d.fragment&&d.fragment.c();t.intro&&Ne(e.$$.fragment),We(e,t.target,t.anchor,t.customElement),ct()}ce(u)}class De{$destroy(){Me(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&&!_t(t)&&(this.$$.skip_bound=!0,this.$$set(t),this.$$.skip_bound=!1)}}const K=[];function Ot(e,t=$){let n;const i=new Set;function r(c){if(ue(e,c)&&(e=c,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:h}}function At(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={}){return new Promise((n,i)=>{let r=fe(m=>{n(m),Reflect.deleteProperty(window,`_${a}`)},!0),a=fe(m=>{i(m),Reflect.deleteProperty(window,`_${r}`)},!0);window.__TAURI_IPC__({cmd:e,callback:r,error:a,...t})})}function Ut(e,t="asset"){let n=encodeURIComponent(e);return navigator.userAgent.includes("Windows")?`https://${t}.localhost/${n}`:`${t}://localhost/${n}`}var zt={};dt(zt,{TauriEvent:()=>pt,emit:()=>_t,listen:()=>Pe,once:()=>Ft});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 Ft(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,m,c,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(),m=f("button"),m.textContent="Send event to Rust",l(n,"class","btn"),l(n,"id","log"),l(r,"class","btn"),l(r,"id","request"),l(m,"class","btn"),l(m,"id","event")},m(d,E){k(d,t,E),o(t,n),o(t,i),o(t,r),o(t,a),o(t,m),c||(u=[F(n,"click",e[0]),F(r,"click",e[1]),F(m,"click",e[2])],c=!0)},p:$,i:$,o:$,d(d){d&&w(t),c=!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 m(){P("perform_request",{endpoint:"dummy endpoint arg",body:{id:5,name:"test"}}).then(i).catch(i)}function c(){_t("js-event","this is the payload string")}return e.$$set=u=>{"onMessage"in u&&n(3,i=u.onMessage)},[a,m,c,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=`

- `,l(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(c){const u=document.querySelector("video"),d=c.getVideoTracks();i("Got stream with constraints:",r),i(`Using video device: ${d[0].label}`),window.stream=c,u.srcObject=c}function m(c){if(c.name==="ConstraintNotSatisfiedError"){const u=r.video;i(`The resolution ${u.width.exact}x${u.height.exact} px is not supported by your device.`)}else c.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: ${c.name}`,c)}return xe(async()=>{try{const c=await navigator.mediaDevices.getUserMedia(r);a(c)}catch(c){m(c)}}),at(()=>{window.stream.getTracks().forEach(function(c){c.stop()})}),e.$$set=c=>{"onMessage"in c&&n(0,i=c.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"),l(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"),l(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"),l(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"),l(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+"",m,c,u,d;function E(){return e[14](e[28])}return{c(){t=f("a"),n=f("div"),i=g(),r=f("p"),m=Q(a),l(n,"class",e[28].icon+" mr-2"),l(t,"href","##"),l(t,"class",c="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,m),u||(d=F(t,"click",E),u=!0)},p(v,S){e=v,S&2&&c!==(c="nv "+(e[1]===e[28]?"nv_selected":""))&&l(t,"class",c)},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,m,c,u,d,E,v,S,H,O,Z,I,me,b,j,C,q,B,ee,te,pe,ge,p,_,D,W,A,ne,U=e[1].label+"",Te,He,_e,ie,y,je,N,ve,qe,G,be,Ue,re,ze,oe,se,Ce,Fe;function Ve(s,T){return s[0]?Qt:Kt}let ye=Ve(e),M=ye(e);function Be(s,T){return s[2]?en:Zt}let we=Be(e),R=we(e),X=e[5],L=[];for(let s=0;s`,me=g(),b=f("a"),b.innerHTML=`GitHub - `,j=g(),C=f("a"),C.innerHTML=`Source - `,q=g(),B=f("br"),ee=g(),te=f("div"),pe=g(),ge=f("br"),p=g(),_=f("div");for(let s=0;s',ze=g(),oe=f("div");for(let s=0;s{Re(h,1)}),Dt()}Y?(y=new Y(Ge(s)),Qe(y.$$.fragment),Ae(y.$$.fragment,1),Me(y,ie,null)):y=null}if(T&16){J=s[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,p=>n(4,i=p));function v(p){E.update(_=>[{html:`
[${new Date().toLocaleTimeString()}]: `+(typeof p=="string"?p:JSON.stringify(p,null,1))+"
"},..._])}function S(p){E.update(_=>[{html:`
[${new Date().toLocaleTimeString()}]: `+p+"
"},..._])}function H(){E.update(()=>[])}let O,Z,I;function me(p){I=p.clientY;const _=window.getComputedStyle(O);Z=parseInt(_.height,10);const D=A=>{const ne=A.clientY-I,U=Z-ne;n(3,O.style.height=`${U{document.removeEventListener("mouseup",W),document.removeEventListener("mousemove",D)};document.addEventListener("mouseup",W),document.addEventListener("mousemove",D)}let b=!1,j,C,q=!1,B=0,ee=0;const te=(p,_,D)=>Math.min(Math.max(_,p),D);xe(()=>{n(13,j=document.querySelector("#sidebar")),C=document.querySelector("#sidebarToggle"),document.addEventListener("click",p=>{C.contains(p.target)?n(0,b=!b):b&&!j.contains(p.target)&&n(0,b=!1)}),document.addEventListener("touchstart",p=>{if(C.contains(p.target))return;const _=p.touches[0].clientX;(0<_&&_<20&&!b||b)&&(q=!0,B=_)}),document.addEventListener("touchmove",p=>{if(q){const _=p.touches[0].clientX;ee=_;const D=(_-B)/10;j.style.setProperty("--translate-x",`-${te(0,b?0-D:18.75-D,18.75)}rem`)}}),document.addEventListener("touchend",()=>{if(q){const p=(ee-B)/10;n(0,b=b?p>-(18.75/2):p>18.75/2)}q=!1})});const pe=p=>{c(p),n(0,b=!1)};function ge(p){Ne[p?"unshift":"push"](()=>{O=p,n(3,O)})}return e.$$.update=()=>{if(e.$$.dirty&1){const p=document.querySelector("#sidebar");p&&rn(p,b)}},[b,m,u,O,i,a,c,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")}); + tests.`},m(n,i){k(n,t,i)},p:$,i:$,o:$,d(n){n&&w(t)}}}class Ct extends De{constructor(t){super(),Se(this,t,null,At,ue,{})}}var It=Object.defineProperty,at=(e,t)=>{for(var n in t)It(e,n,{get:t[n],enumerable:!0})},Nt={};at(Nt,{convertFileSrc:()=>Mt,invoke:()=>Le,transformCallback:()=>$e});function Wt(){return window.crypto.getRandomValues(new Uint32Array(1))[0]}function $e(e,t=!1){let n=Wt(),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}async function Le(e,t={}){return new Promise((n,i)=>{let r=$e(h=>{n(h),Reflect.deleteProperty(window,`_${a}`)},!0),a=$e(h=>{i(h),Reflect.deleteProperty(window,`_${r}`)},!0);window.__TAURI_IPC__({cmd:e,callback:r,error:a,...t})})}function Mt(e,t="asset"){let n=encodeURIComponent(e);return navigator.userAgent.includes("Windows")?`https://${t}.localhost/${n}`:`${t}://localhost/${n}`}async function Pe(e){return Le("tauri",e)}var Pt={};at(Pt,{TauriEvent:()=>ft,emit:()=>ht,listen:()=>mt,once:()=>qt});async function ut(e,t){return Pe({__tauriModule:"Event",message:{cmd:"unlisten",event:e,eventId:t}})}async function Rt(e,t,n){await Pe({__tauriModule:"Event",message:{cmd:"emit",event:e,windowLabel:t,payload:n}})}async function dt(e,t,n){return Pe({__tauriModule:"Event",message:{cmd:"listen",event:e,windowLabel:t,handler:$e(n)}}).then(i=>async()=>ut(e,i))}async function Ht(e,t,n){return dt(e,t,i=>{n(i),ut(e,i.id).catch(()=>{})})}var ft=(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.CHECK_UPDATE="tauri://update",e.UPDATE_AVAILABLE="tauri://update-available",e.INSTALL_UPDATE="tauri://update-install",e.STATUS_UPDATE="tauri://update-status",e.DOWNLOAD_PROGRESS="tauri://update-download-progress",e))(ft||{});async function mt(e,t){return dt(e,null,t)}async function qt(e,t){return Ht(e,null,t)}async function ht(e,t){return Rt(e,void 0,t)}function jt(e){let t,n,i,r,a,h,c,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(),h=f("button"),h.textContent="Send event to Rust",l(n,"class","btn"),l(n,"id","log"),l(r,"class","btn"),l(r,"id","request"),l(h,"class","btn"),l(h,"id","event")},m(d,E){k(d,t,E),o(t,n),o(t,i),o(t,r),o(t,a),o(t,h),c||(u=[z(n,"click",e[0]),z(r,"click",e[1]),z(h,"click",e[2])],c=!0)},p:$,i:$,o:$,d(d){d&&w(t),c=!1,F(u)}}}function Ut(e,t,n){let{onMessage:i}=t,r;Ee(async()=>{r=await mt("rust-event",i)}),lt(()=>{r&&r()});function a(){Le("log_operation",{event:"tauri-click",payload:"this payload is optional because we used Option in Rust"})}function h(){Le("perform_request",{endpoint:"dummy endpoint arg",body:{id:5,name:"test"}}).then(i).catch(i)}function c(){ht("js-event","this is the payload string")}return e.$$set=u=>{"onMessage"in u&&n(3,i=u.onMessage)},[a,h,c,i]}class zt extends De{constructor(t){super(),Se(this,t,Ut,jt,ue,{onMessage:3})}}function Ft(e){let t;return{c(){t=f("div"),t.innerHTML=`
Not available for Linux
+ `,l(t,"class","flex flex-col gap-2")},m(n,i){k(n,t,i)},p:$,i:$,o:$,d(n){n&&w(t)}}}function Vt(e,t,n){let{onMessage:i}=t;const r=window.constraints={audio:!0,video:!0};function a(c){const u=document.querySelector("video"),d=c.getVideoTracks();i("Got stream with constraints:",r),i(`Using video device: ${d[0].label}`),window.stream=c,u.srcObject=c}function h(c){if(c.name==="ConstraintNotSatisfiedError"){const u=r.video;i(`The resolution ${u.width.exact}x${u.height.exact} px is not supported by your device.`)}else c.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: ${c.name}`,c)}return Ee(async()=>{try{const c=await navigator.mediaDevices.getUserMedia(r);a(c)}catch(c){h(c)}}),lt(()=>{window.stream.getTracks().forEach(function(c){c.stop()})}),e.$$set=c=>{"onMessage"in c&&n(0,i=c.onMessage)},[i]}class Bt extends De{constructor(t){super(),Se(this,t,Vt,Ft,ue,{onMessage:0})}}function Qe(e,t,n){const i=e.slice();return i[25]=t[n],i}function Ze(e,t,n){const i=e.slice();return i[28]=t[n],i}function Gt(e){let t;return{c(){t=f("span"),l(t,"class","i-codicon-menu animate-duration-300ms animate-fade-in")},m(n,i){k(n,t,i)},d(n){n&&w(t)}}}function Xt(e){let t;return{c(){t=f("span"),l(t,"class","i-codicon-close animate-duration-300ms animate-fade-in")},m(n,i){k(n,t,i)},d(n){n&&w(t)}}}function Yt(e){let t,n;return{c(){t=J(`Switch to Dark mode + `),n=f("div"),l(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 Kt(e){let t,n;return{c(){t=J(`Switch to Light mode + `),n=f("div"),l(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 Jt(e){let t,n,i,r,a=e[28].label+"",h,c,u,d;function E(){return e[14](e[28])}return{c(){t=f("a"),n=f("div"),i=g(),r=f("p"),h=J(a),l(n,"class",e[28].icon+" mr-2"),l(t,"href","##"),l(t,"class",c="nv "+(e[1]===e[28]?"nv_selected":""))},m(v,D){k(v,t,D),o(t,n),o(t,i),o(t,r),o(r,h),u||(d=z(t,"click",E),u=!0)},p(v,D){e=v,D&2&&c!==(c="nv "+(e[1]===e[28]?"nv_selected":""))&&l(t,"class",c)},d(v){v&&w(t),u=!1,d()}}}function et(e){let t,n=e[28]&&Jt(e);return{c(){n&&n.c(),t=ot()},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 tt(e){let t,n=e[25].html+"",i;return{c(){t=new Et(!1),i=ot(),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 Qt(e){let t,n,i,r,a,h,c,u,d,E,v,D,R,T,Q,I,de,b,H,O,q,V,Z,ee,fe,me,p,_,A,N,W,te,j=e[1].label+"",Te,Re,he,ne,y,He,C,pe,qe,B,ge,je,ie,Ue,re,oe,xe,ze;function Fe(s,x){return s[0]?Xt:Gt}let _e=Fe(e),M=_e(e);function Ve(s,x){return s[2]?Kt:Yt}let ve=Ve(e),P=ve(e),G=e[5],L=[];for(let s=0;s`,de=g(),b=f("a"),b.innerHTML=`GitHub + `,H=g(),O=f("a"),O.innerHTML=`Source + `,q=g(),V=f("br"),Z=g(),ee=f("div"),fe=g(),me=f("br"),p=g(),_=f("div");for(let s=0;s',Ue=g(),re=f("div");for(let s=0;s{Me(m,1)}),Tt()}X?(y=new X(Be(s)),Je(y.$$.fragment),Ne(y.$$.fragment,1),We(y,ne,null)):y=null}if(x&16){Y=s[4];let m;for(m=0;m{n(2,u=localStorage&&localStorage.getItem("theme")=="dark"),it(u)});function d(){n(2,u=!u),it(u)}let E=Ot([]);bt(e,E,p=>n(4,i=p));function v(p){E.update(_=>[{html:`
[${new Date().toLocaleTimeString()}]: `+(typeof p=="string"?p:JSON.stringify(p,null,1))+"
"},..._])}function D(p){E.update(_=>[{html:`
[${new Date().toLocaleTimeString()}]: `+p+"
"},..._])}function R(){E.update(()=>[])}let T,Q,I;function de(p){I=p.clientY;const _=window.getComputedStyle(T);Q=parseInt(_.height,10);const A=W=>{const te=W.clientY-I,j=Q-te;n(3,T.style.height=`${j{document.removeEventListener("mouseup",N),document.removeEventListener("mousemove",A)};document.addEventListener("mouseup",N),document.addEventListener("mousemove",A)}let b=!1,H,O,q=!1,V=0,Z=0;const ee=(p,_,A)=>Math.min(Math.max(_,p),A);Ee(()=>{n(13,H=document.querySelector("#sidebar")),O=document.querySelector("#sidebarToggle"),document.addEventListener("click",p=>{O.contains(p.target)?n(0,b=!b):b&&!H.contains(p.target)&&n(0,b=!1)}),document.addEventListener("touchstart",p=>{if(O.contains(p.target))return;const _=p.touches[0].clientX;(0<_&&_<20&&!b||b)&&(q=!0,V=_)}),document.addEventListener("touchmove",p=>{if(q){const _=p.touches[0].clientX;Z=_;const A=(_-V)/10;H.style.setProperty("--translate-x",`-${ee(0,b?0-A:18.75-A,18.75)}rem`)}}),document.addEventListener("touchend",()=>{if(q){const p=(Z-V)/10;n(0,b=b?p>-(18.75/2):p>18.75/2)}q=!1})});const fe=p=>{c(p),n(0,b=!1)};function me(p){Ae[p?"unshift":"push"](()=>{T=p,n(3,T)})}return e.$$.update=()=>{if(e.$$.dirty&1){const p=document.querySelector("#sidebar");p&&Zt(p,b)}},[b,h,u,T,i,a,c,d,E,v,D,R,de,H,fe,me]}class tn extends De{constructor(t){super(),Se(this,t,en,Qt,ue,{})}}new tn({target:document.querySelector("#app")}); diff --git a/examples/api/src-tauri/Cargo.lock b/examples/api/src-tauri/Cargo.lock index e0bb5a3bb579..bbd2e036366c 100644 --- a/examples/api/src-tauri/Cargo.lock +++ b/examples/api/src-tauri/Cargo.lock @@ -2182,8 +2182,6 @@ dependencies = [ [[package]] name = "muda" version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0294ee1db0c35b1a2d3fea0bcc66aaea00d4b9693c7b3228865baefdd48850e8" dependencies = [ "cocoa 0.25.0", "crossbeam-channel", @@ -3909,8 +3907,6 @@ dependencies = [ [[package]] name = "tray-icon" version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85848705438afb36546193edd75e3a9f85833fe8799d13a00924774bf7277edb" dependencies = [ "cocoa 0.25.0", "core-graphics 0.23.1", diff --git a/examples/api/src-tauri/src/lib.rs b/examples/api/src-tauri/src/lib.rs index d02b7e46f266..fff8a25800ba 100644 --- a/examples/api/src-tauri/src/lib.rs +++ b/examples/api/src-tauri/src/lib.rs @@ -40,11 +40,10 @@ pub fn run_app) + Send + 'static>( .build(), ) .plugin(tauri_plugin_sample::init()) + .tray_icon(tray::create_tray) .setup(move |app| { #[cfg(desktop)] { - tray::create_tray(app)?; - app.handle().plugin(tauri_plugin_cli::init())?; } diff --git a/examples/api/src-tauri/src/tray.rs b/examples/api/src-tauri/src/tray.rs index 696b687dda22..22be7543c4f0 100644 --- a/examples/api/src-tauri/src/tray.rs +++ b/examples/api/src-tauri/src/tray.rs @@ -5,139 +5,118 @@ use std::sync::atomic::{AtomicBool, Ordering}; use tauri::{ menu::{Menu, MenuItem}, - tray::{TrayIcon, TrayIconBuilder}, - Runtime, WindowUrl, + tray::{ClickType, TrayIcon, TrayIconBuilder}, + Manager, Runtime, WindowBuilder, WindowUrl, }; -pub fn create_tray(app: &tauri::App) -> tauri::Result> { - let app = app.handle(); - TrayIconBuilder::new() - .with_tooltip("Tauri") - .with_menu(&Menu::with_items( - &app, - &[ - &MenuItem::new(&app, "Toggle", true, None), - &MenuItem::new(&app, "New window", true, None), - &MenuItem::new(&app, "Tray Icon 1", true, None), - &MenuItem::new(&app, "Tray Icon 2", true, None), - ], - )?) - .build(&app) -} - -/* -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::new(&app, "Toggle", true, None); + let new_window_i = MenuItem::new(&app, "New window", true, None); + let icon_i_1 = MenuItem::new(&app, "Icon 1", true, None); + let icon_i_2 = MenuItem::new(&app, "Icon 2", true, None); #[cfg(target_os = "macos")] - { - tray_menu1 = tray_menu1.add_item(CustomMenuItem::new("set_title", "Set Title")); - } + let set_title_i = MenuItem::new(&app, "Set Title", true, None); + let switch_i = MenuItem::new(&app, "Switch Menu", true, None); + let quit_i = MenuItem::new(&app, "Quit", true, None); + let remove_tray_i = MenuItem::new(&app, "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], + )?; - 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 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()) + const TRAY_ID: u32 = 21937; + + TrayIconBuilder::new() .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(); + .with_id(TRAY_ID) + .with_icon(app.default_window_icon().unwrap().clone()) + .with_menu(&menu1) + .with_on_menu_event(move |app, event| { + dbg!(event); + match event.id { + i if i == quit_i.id().unwrap() => { + // exit the app + app.exit(0); } - 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(); - - 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") + i if i == remove_tray_i.id().unwrap() => { + app.remove_tray_by_id(TRAY_ID); + } + i if i == toggle_i.id().unwrap() => { + if let Some(window) = app.get_window("main") { + let new_title = if window.is_visible().unwrap() { + window.hide().unwrap(); + "Show" + } else { + window.show().unwrap(); + "Hide" + }; + toggle_i.set_text(new_title).unwrap(); + } + } + i if i == new_window_i.id().unwrap() => { + WindowBuilder::new(app, "new", WindowUrl::App("index.html".into())) + .title("Tauri") + .build() + .unwrap(); + } + #[cfg(target_os = "macos")] + i if i == set_title_i.id().unwrap() => { + if let Some(tray) = app.tray_by_id(TRAY_ID) { + tray.set_title("Tauri").unwrap(); + } + } + i if i == icon_i_1.id().unwrap() || i == icon_i_2.id().unwrap() => { + if let Some(tray) = app.tray_by_id(TRAY_ID) { + tray + .set_icon(Some(tauri::Icon::Raw(if i == icon_i_1.id().unwrap() { + include_bytes!("../../../.icons/icon.ico").to_vec() } else { - (tray_menu1.clone(), "Tauri") - }; - tray_handle.set_menu(menu).unwrap(); - tray_handle.set_tooltip(tooltip).unwrap(); - is_menu1.store(!flag, Ordering::Relaxed); - } - _ => {} + include_bytes!("../../../.icons/tray_icon_with_transparency.png").to_vec() + }))) + .unwrap(); + } + } + i if i == switch_i.id().unwrap() => { + 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_ID) { + tray.set_menu(Some(menu)).unwrap(); + tray.set_tooltip(Some(tooltip)).unwrap(); } + is_menu1.store(!flag, Ordering::Relaxed); } + _ => {} } }) - .build(app) - .map(|_| ()) + .with_on_tray_event(|tray, event| { + if event.click_type == ClickType::Left { + let app = tray.app_handle(); + if let Some(window) = app.get_window("main") { + window.show().unwrap(); + window.set_focus().unwrap(); + } + } + }) + .build(&app) } -*/ diff --git a/examples/api/src-tauri/tauri.conf.json b/examples/api/src-tauri/tauri.conf.json index eddc1fdeff0f..73bfddb9454d 100644 --- a/examples/api/src-tauri/tauri.conf.json +++ b/examples/api/src-tauri/tauri.conf.json @@ -100,11 +100,6 @@ "deny": ["$APPDATA/db/*.stronghold"] } } - }, - "trayIcon": { - "iconPath": "../../.icons/tray_icon_with_transparency.png", - "iconAsTemplate": true, - "menuOnLeftClick": false } } } From 901fe92dd2787c23e8f284bffeb5c9a883a24073 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Tue, 1 Aug 2023 19:50:13 +0300 Subject: [PATCH 029/123] update lock file --- examples/api/src-tauri/Cargo.lock | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/examples/api/src-tauri/Cargo.lock b/examples/api/src-tauri/Cargo.lock index bbd2e036366c..27095bdd8d49 100644 --- a/examples/api/src-tauri/Cargo.lock +++ b/examples/api/src-tauri/Cargo.lock @@ -2181,7 +2181,9 @@ dependencies = [ [[package]] name = "muda" -version = "0.7.0" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0b6051ce42a7648b11a3613c0d0718f5ac155f262d9f59d5a51501418afaef5" dependencies = [ "cocoa 0.25.0", "crossbeam-channel", @@ -3906,7 +3908,9 @@ dependencies = [ [[package]] name = "tray-icon" -version = "0.7.1" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a903d0c8c8c9caa5f00f38e9465349e89ed3f72506b07994aa4393d7caf3e10" dependencies = [ "cocoa 0.25.0", "core-graphics 0.23.1", From 400269bb6f244eefcf5e2ef2f9a05490f7fdd22d Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 1 Aug 2023 14:30:03 -0300 Subject: [PATCH 030/123] macos fixes --- core/tauri-codegen/src/context.rs | 6 ++---- core/tauri/src/app.rs | 2 ++ examples/api/src-tauri/src/tray.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/tauri-codegen/src/context.rs b/core/tauri-codegen/src/context.rs index e22299b517bd..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(app: &tauri::AppHandle) -> tauri::Result { if let Some(tray) = app.tray_by_id(TRAY_ID) { - tray.set_title("Tauri").unwrap(); + tray.set_title(Some("Tauri")).unwrap(); } } i if i == icon_i_1.id().unwrap() || i == icon_i_2.id().unwrap() => { From 316d2aa8068d48cdb8e94c1a003493a144c103f8 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 1 Aug 2023 14:32:52 -0300 Subject: [PATCH 031/123] tray id return raw value --- core/tauri/src/app.rs | 6 ++---- core/tauri/src/tray.rs | 9 +++++++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index cae47b55ae18..7df2a75468f5 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -509,7 +509,7 @@ macro_rules! shared_app_impl { .lock() .unwrap() .iter() - .find(|t| t.id().map(|i| i == id).unwrap_or(false)) + .find(|t| t.id() == id) .cloned() } @@ -519,9 +519,7 @@ macro_rules! shared_app_impl { pub fn remove_tray_by_id(&self, id: u32) -> Option> { let mut tray_icons = self.manager.inner.tray_icons.lock().unwrap(); - let idx = tray_icons - .iter() - .position(|t| t.id().map(|i| i == id).unwrap_or(false)); + let idx = tray_icons.iter().position(|t| t.id() == id); if let Some(idx) = idx { return Some(tray_icons.swap_remove(idx)); diff --git a/core/tauri/src/tray.rs b/core/tauri/src/tray.rs index 3dabc158199c..146aabeadf33 100644 --- a/core/tauri/src/tray.rs +++ b/core/tauri/src/tray.rs @@ -234,6 +234,7 @@ impl TrayIconBuilder { } Ok(TrayIcon { + id, inner, app_handle: app_handle.clone(), }) @@ -244,6 +245,7 @@ impl TrayIconBuilder { /// /// This type is reference-counted and the icon is removed when the last instance is dropped. pub struct TrayIcon { + id: u32, inner: tray_icon::TrayIcon, app_handle: AppHandle, } @@ -251,6 +253,7 @@ pub struct TrayIcon { impl Clone for TrayIcon { fn clone(&self) -> Self { Self { + id: self.id, inner: self.inner.clone(), app_handle: self.app_handle.clone(), } @@ -298,6 +301,7 @@ impl TrayIcon { } Ok(Self { + id, inner, app_handle: app_handle.clone(), }) @@ -312,6 +316,7 @@ impl TrayIcon { id: u32, ) -> crate::Result { Ok(Self { + id, inner: tray_icon::TrayIcon::with_id(attrs.into(), id)?, app_handle: app_handle.clone(), }) @@ -323,8 +328,8 @@ impl TrayIcon { } /// Returns the id associated with this tray icon. - pub fn id(&self) -> crate::Result { - run_main_thread!(self, |self_: Self| self_.inner.id()) + pub fn id(&self) -> u32 { + self.id } /// Set new tray icon. If `None` is provided, it will remove the icon. From 81250dd74f4858134af5fa3a6ddf9f379c515cbf Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 1 Aug 2023 14:33:24 -0300 Subject: [PATCH 032/123] lint --- core/tauri/src/tray.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/tauri/src/tray.rs b/core/tauri/src/tray.rs index 146aabeadf33..c3a6674b9873 100644 --- a/core/tauri/src/tray.rs +++ b/core/tauri/src/tray.rs @@ -30,9 +30,11 @@ pub struct TrayIconAttributes { pub menu: Option>, /// Set a handler for menu events + #[allow(clippy::type_complexity)] pub on_menu_event: Option, MenuEvent) + Send + Sync + 'static>>, /// Set a handler for tray icon events + #[allow(clippy::type_complexity)] pub on_tray_event: Option, TrayIconEvent) + Send + Sync + 'static>>, /// Tray icon @@ -86,7 +88,9 @@ impl From> for tray_icon::TrayIconAttributes { /// [`TrayIcon`] builder struct and associated methods. #[derive(Default)] pub struct TrayIconBuilder { + #[allow(clippy::type_complexity)] on_menu_event: Option, MenuEvent) + Sync + Send + 'static>>, + #[allow(clippy::type_complexity)] on_tray_event: Option, TrayIconEvent) + Sync + Send + 'static>>, inner: tray_icon::TrayIconBuilder, } From b02b7b1f03e1486954664d530bc0085952527ef6 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 1 Aug 2023 14:34:48 -0300 Subject: [PATCH 033/123] private fields --- core/tauri/src/tray.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/core/tauri/src/tray.rs b/core/tauri/src/tray.rs index c3a6674b9873..a272d086a6e8 100644 --- a/core/tauri/src/tray.rs +++ b/core/tauri/src/tray.rs @@ -20,22 +20,22 @@ pub struct TrayIconAttributes { /// ## Platform-specific: /// /// - **Linux:** Unsupported. - pub tooltip: Option, + tooltip: Option, /// Tray menu /// /// ## Platform-specific: /// /// - **Linux**: once a menu is set, it cannot be removed. - pub menu: Option>, + menu: Option>, /// Set a handler for menu events #[allow(clippy::type_complexity)] - pub on_menu_event: Option, MenuEvent) + Send + Sync + 'static>>, + on_menu_event: Option, MenuEvent) + Send + Sync + 'static>>, /// Set a handler for tray icon events #[allow(clippy::type_complexity)] - pub on_tray_event: Option, TrayIconEvent) + Send + Sync + 'static>>, + on_tray_event: Option, TrayIconEvent) + Send + Sync + 'static>>, /// Tray icon /// @@ -43,16 +43,16 @@ pub struct TrayIconAttributes { /// /// - **Linux:** Sometimes the icon won't be visible unless a menu is set. /// Setting an empty [`Menu`](crate::menu::Menu) is enough. - pub icon: Option, + icon: Option, /// Tray icon temp dir path. **Linux only**. - pub temp_dir_path: Option, + temp_dir_path: Option, /// Use the icon as a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc). **macOS only**. - pub icon_is_template: bool, + icon_is_template: bool, /// Whether to show the tray menu on left click or not, default is `true`. **macOS only**. - pub menu_on_left_click: bool, + menu_on_left_click: bool, /// Tray icon title. /// @@ -64,7 +64,7 @@ pub struct TrayIconAttributes { /// 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 title: Option, + title: Option, } impl From> for tray_icon::TrayIconAttributes { From c65820f0916b689b2122802507d8020316601150 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 1 Aug 2023 14:40:06 -0300 Subject: [PATCH 034/123] replace show_context_menu_for_* with popup fn --- core/tauri/src/app.rs | 1 - core/tauri/src/menu/menu.rs | 50 ++++++++++++------------------- core/tauri/src/menu/mod.rs | 23 +-------------- core/tauri/src/menu/submenu.rs | 54 +++++++++++++--------------------- core/tauri/src/window.rs | 13 +------- 5 files changed, 41 insertions(+), 100 deletions(-) diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 7df2a75468f5..65b35d4570d8 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -633,7 +633,6 @@ macro_rules! shared_app_impl { /// /// If a window was not created with an explicit menu or had one set explicitly, /// this will remove the menu from it. - pub fn remove_menu(&self) -> crate::Result>> { let mut current_menu = self.manager.menu_lock(); diff --git a/core/tauri/src/menu/menu.rs b/core/tauri/src/menu/menu.rs index 8dcdd05e7f52..b4e2acf80395 100644 --- a/core/tauri/src/menu/menu.rs +++ b/core/tauri/src/menu/menu.rs @@ -39,49 +39,37 @@ impl super::sealed::ContextMenuBase for Menu { Box::new(self.clone().inner) } - #[cfg(windows)] - fn show_context_menu_for_hwnd( - &self, - hwnd: isize, - position: Option, - ) -> crate::Result<()> { - run_main_thread!(self, |self_: Self| self_ - .inner() - .show_context_menu_for_hwnd(hwnd, position.map(Into::into))) - } - - #[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ))] - fn show_context_menu_for_gtk_window( + fn popup( &self, window: crate::Window, position: Option, ) -> crate::Result<()> { run_main_thread!(self, |self_: Self| { + #[cfg(target_os = "macos")] + if let Ok(view) = window.ns_view() { + self_ + .inner() + .show_context_menu_for_nsview(view as _, position.map(Into::into)); + } + + #[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() { self_ .inner() - .show_context_menu_for_gtk_window(>k_window, position.map(Into::into)) + .show_context_menu_for_gtk_window(>k_window, position.map(Into::into)); } - }) - } - #[cfg(target_os = "macos")] - fn show_context_menu_for_nsview( - &self, - window: crate::Window, - position: Option, - ) -> crate::Result<()> { - run_main_thread!(self, |self_: Self| { - if let Ok(view) = window.ns_view() { + #[cfg(windows)] + if let Ok(hwnd) = window.hwnd() { self_ .inner() - .show_context_menu_for_nsview(view as _, position.map(Into::into)) + .show_context_menu_for_hwnd(hwnd.0, position.map(Into::into)); } }) } diff --git a/core/tauri/src/menu/mod.rs b/core/tauri/src/menu/mod.rs index 4cb7342dc069..823374105bea 100644 --- a/core/tauri/src/menu/mod.rs +++ b/core/tauri/src/menu/mod.rs @@ -177,28 +177,7 @@ pub(crate) mod sealed { fn inner(&self) -> &dyn super::muda::ContextMenu; fn inner_owned(&self) -> Box; - #[cfg(windows)] - fn show_context_menu_for_hwnd( - &self, - hwnd: isize, - position: Option, - ) -> crate::Result<()>; - - #[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ))] - fn show_context_menu_for_gtk_window( - &self, - window: crate::Window, - position: Option, - ) -> crate::Result<()>; - - #[cfg(target_os = "macos")] - fn show_context_menu_for_nsview( + fn popup( &self, window: crate::Window, position: Option, diff --git a/core/tauri/src/menu/submenu.rs b/core/tauri/src/menu/submenu.rs index 67ec297a0c28..b5024dfc52c9 100644 --- a/core/tauri/src/menu/submenu.rs +++ b/core/tauri/src/menu/submenu.rs @@ -56,51 +56,37 @@ impl super::sealed::ContextMenuBase for Submenu { Box::new(self.clone().inner) } - #[cfg(windows)] - fn show_context_menu_for_hwnd( - &self, - hwnd: isize, - position: Option, - ) -> crate::Result<()> { - run_main_thread!(self, |self_: Self| { - self_ - .inner() - .show_context_menu_for_hwnd(hwnd, position.map(Into::into)) - }) - } - - #[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ))] - fn show_context_menu_for_gtk_window( + fn popup( &self, window: crate::Window, position: Option, ) -> crate::Result<()> { - run_main_thread!(self, |self_: Self| { + 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.map(Into::into)); + } + + #[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.map(Into::into)) + .show_context_menu_for_gtk_window(&w, position.map(Into::into)); } - }) - } - #[cfg(target_os = "macos")] - fn show_context_menu_for_nsview( - &self, - window: crate::Window, - position: Option, - ) -> crate::Result<()> { - run_main_thread!(self, move |self_: Self| { - if let Ok(view) = window.ns_view() { + #[cfg(windows)] + if let Ok(hwnd) = window.hwnd() { self_ .inner() - .show_context_menu_for_nsview(view as _, position.map(Into::into)) + .show_context_menu_for_hwnd(hwnd.0, position.map(Into::into)) } }) } diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index a023f48b8fc1..f428cdc82faf 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -1274,18 +1274,7 @@ impl Window { ) -> crate::Result<()> { let position = position.map(|p| p.into()); - #[cfg(windows)] - menu.show_context_menu_for_hwnd(self.hwnd()?.0, position)?; - #[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ))] - menu.show_context_menu_for_gtk_window(self.clone(), position)?; - #[cfg(target_os = "macos")] - menu.show_context_menu_for_nsview(self.clone(), position)?; + menu.popup(self.clone(), position)?; Ok(()) } From 77a5359b07d0c42fb74b314872fb82615dc32f1b Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 1 Aug 2023 15:50:08 -0300 Subject: [PATCH 035/123] make id return u32 instead of result --- core/tauri/src/app.rs | 10 ++++--- core/tauri/src/manager.rs | 7 +++-- core/tauri/src/menu/check.rs | 26 ++++++++++-------- core/tauri/src/menu/icon.rs | 42 +++++++++++++++++------------- core/tauri/src/menu/menu.rs | 14 +++++++--- core/tauri/src/menu/mod.rs | 4 +-- core/tauri/src/menu/normal.rs | 22 +++++++++------- core/tauri/src/menu/predefined.rs | 6 ++--- core/tauri/src/menu/submenu.rs | 18 +++++++++---- core/tauri/src/window.rs | 3 ++- examples/api/src-tauri/src/tray.rs | 16 ++++++------ 11 files changed, 100 insertions(+), 68 deletions(-) diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 65b35d4570d8..336bda83fa47 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -678,7 +678,7 @@ macro_rules! shared_app_impl { self .manager - .remove_menu_from_stash_by_id(prev_menu.as_ref().and_then(|m| m.id().ok())); + .remove_menu_from_stash_by_id(prev_menu.as_ref().map(|m| m.id())); Ok(prev_menu) } @@ -1534,9 +1534,10 @@ impl Builder { if let Some(menu) = self.menu { let menu = menu(&app.handle)?; - if let Ok(id) = menu.id() { - app.manager.menus_stash_lock().insert(id, menu.clone()); - } + app + .manager + .menus_stash_lock() + .insert(menu.id(), menu.clone()); #[cfg(target_os = "macos")] menu.inner().init_for_nsapp(); @@ -1649,6 +1650,7 @@ fn setup(app: &mut App) -> crate::Result<()> { ( pending.has_app_wide_menu, Menu { + id: m.id(), inner: m, app_handle: app_handle.clone(), }, diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index 6d34bd5af729..43b9f5667dde 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -388,15 +388,13 @@ impl WindowManager { self .menu_lock() .as_ref() - .map(|m| m.id().map(|i| i == id).unwrap_or(false)) + .map(|m| m.id() == id) .unwrap_or(false) } /// Menus stash. pub(crate) fn insert_menu_into_stash(&self, menu: &Menu) { - if let Ok(id) = menu.id() { - self.menus_stash_lock().insert(id, menu.clone()); - } + self.menus_stash_lock().insert(menu.id(), menu.clone()); } pub(crate) fn remove_menu_from_stash_by_id(&self, id: Option) { @@ -1202,6 +1200,7 @@ impl WindowManager { self.inner.menus.lock().unwrap().insert( menu.id(), Menu { + id: menu.id(), inner: menu.clone(), app_handle: app_handle.clone(), }, diff --git a/core/tauri/src/menu/check.rs b/core/tauri/src/menu/check.rs index 0c7f4a0c3bcd..c291aceae885 100644 --- a/core/tauri/src/menu/check.rs +++ b/core/tauri/src/menu/check.rs @@ -9,6 +9,7 @@ use crate::{run_main_thread, runtime::menu as muda, AppHandle, Runtime}; /// [`Menu`]: super::Menu /// [`Submenu`]: super::Submenu pub struct CheckMenuItem { + pub(crate) id: u32, pub(crate) inner: muda::CheckMenuItem, pub(crate) app_handle: AppHandle, } @@ -16,6 +17,7 @@ pub struct CheckMenuItem { impl Clone for CheckMenuItem { fn clone(&self) -> Self { Self { + id: self.id, inner: self.inner.clone(), app_handle: self.app_handle.clone(), } @@ -39,8 +41,8 @@ impl super::IsMenuItem for CheckMenuItem { super::MenuItemKind::Check(self.clone()) } - fn id(&self) -> crate::Result { - self.id() + fn id(&self) -> u32 { + self.id } } @@ -53,16 +55,18 @@ impl CheckMenuItem { app_handle: &AppHandle, text: S, enabled: bool, - chceked: bool, + checked: bool, acccelerator: Option, ) -> Self { + let item = muda::CheckMenuItem::new( + text, + enabled, + checked, + acccelerator.and_then(|s| s.as_ref().parse().ok()), + ); Self { - inner: muda::CheckMenuItem::new( - text, - enabled, - chceked, - acccelerator.and_then(|s| s.as_ref().parse().ok()), - ), + id: item.id(), + inner: item, app_handle: app_handle.clone(), } } @@ -73,8 +77,8 @@ impl CheckMenuItem { } /// Returns a unique identifier associated with this menu item. - pub fn id(&self) -> crate::Result { - run_main_thread!(self, |self_: Self| self_.inner.id()) + pub fn id(&self) -> u32 { + self.id } /// Get the text for this menu item. diff --git a/core/tauri/src/menu/icon.rs b/core/tauri/src/menu/icon.rs index 3048f131bc3c..d3b5c89c9822 100644 --- a/core/tauri/src/menu/icon.rs +++ b/core/tauri/src/menu/icon.rs @@ -10,6 +10,7 @@ use crate::{run_main_thread, runtime::menu as muda, AppHandle, Icon, Runtime}; /// [`Menu`]: super::Menu /// [`Submenu`]: super::Submenu pub struct IconMenuItem { + pub(crate) id: u32, pub(crate) inner: muda::IconMenuItem, pub(crate) app_handle: AppHandle, } @@ -17,6 +18,7 @@ pub struct IconMenuItem { impl Clone for IconMenuItem { fn clone(&self) -> Self { Self { + id: self.id, inner: self.inner.clone(), app_handle: self.app_handle.clone(), } @@ -40,8 +42,8 @@ impl super::IsMenuItem for IconMenuItem { super::MenuItemKind::Icon(self.clone()) } - fn id(&self) -> crate::Result { - self.id() + fn id(&self) -> u32 { + self.id } } @@ -57,15 +59,17 @@ impl IconMenuItem { icon: Option, acccelerator: Option, ) -> Self { + let item = muda::IconMenuItem::new( + text, + enabled, + icon + .and_then(|i| -> Option { i.try_into().ok() }) + .and_then(|i| i.try_into().ok()), + acccelerator.and_then(|s| s.as_ref().parse().ok()), + ); Self { - inner: muda::IconMenuItem::new( - text, - enabled, - icon - .and_then(|i| -> Option { i.try_into().ok() }) - .and_then(|i| i.try_into().ok()), - acccelerator.and_then(|s| s.as_ref().parse().ok()), - ), + id: item.id(), + inner: item, app_handle: app_handle.clone(), } } @@ -84,13 +88,15 @@ impl IconMenuItem { 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 { - inner: muda::IconMenuItem::with_native_icon( - text, - enabled, - native_icon, - acccelerator.and_then(|s| s.as_ref().parse().ok()), - ), + id: item.id(), + inner: item, app_handle: app_handle.clone(), } } @@ -101,8 +107,8 @@ impl IconMenuItem { } /// Returns a unique identifier associated with this menu item. - pub fn id(&self) -> crate::Result { - run_main_thread!(self, |self_: Self| self_.inner.id()) + pub fn id(&self) -> u32 { + self.id } /// Get the text for this menu item. diff --git a/core/tauri/src/menu/menu.rs b/core/tauri/src/menu/menu.rs index b4e2acf80395..05409f466f6b 100644 --- a/core/tauri/src/menu/menu.rs +++ b/core/tauri/src/menu/menu.rs @@ -10,6 +10,7 @@ use tauri_runtime::menu::AboutMetadata; /// 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: u32, pub(crate) inner: muda::Menu, pub(crate) app_handle: AppHandle, } @@ -23,6 +24,7 @@ unsafe impl Send for Menu {} impl Clone for Menu { fn clone(&self) -> Self { Self { + id: self.id, inner: self.inner.clone(), app_handle: self.app_handle.clone(), } @@ -78,8 +80,10 @@ impl super::sealed::ContextMenuBase for Menu { impl Menu { /// Creates a new menu. pub fn new(app_handle: &AppHandle) -> Self { + let menu = muda::Menu::new(); Self { - inner: muda::Menu::new(), + id: menu.id(), + inner: menu, app_handle: app_handle.clone(), } } @@ -190,8 +194,8 @@ impl Menu { } /// Returns a unique identifier associated with this menu. - pub fn id(&self) -> crate::Result { - run_main_thread!(self, |self_: Self| self_.inner.id()) + pub fn id(&self) -> u32 { + self.id } /// Add a menu item to the end of this menu. @@ -294,10 +298,12 @@ impl Menu { .into_iter() .map(|i| match i { muda::MenuItemKind::MenuItem(i) => super::MenuItemKind::MenuItem(super::MenuItem { + id: i.id(), inner: i, app_handle: handle.clone(), }), muda::MenuItemKind::Submenu(i) => super::MenuItemKind::Submenu(super::Submenu { + id: i.id(), inner: i, app_handle: handle.clone(), }), @@ -308,10 +314,12 @@ impl Menu { }) } muda::MenuItemKind::Check(i) => super::MenuItemKind::Check(super::CheckMenuItem { + id: i.id(), inner: i, app_handle: handle.clone(), }), muda::MenuItemKind::Icon(i) => super::MenuItemKind::Icon(super::IconMenuItem { + id: i.id(), inner: i, app_handle: handle.clone(), }), diff --git a/core/tauri/src/menu/mod.rs b/core/tauri/src/menu/mod.rs index 823374105bea..c1ccdd4f473f 100644 --- a/core/tauri/src/menu/mod.rs +++ b/core/tauri/src/menu/mod.rs @@ -43,7 +43,7 @@ pub enum MenuItemKind { impl MenuItemKind { /// Returns a unique identifier associated with this menu item. - pub fn id(&self) -> crate::Result { + pub fn id(&self) -> u32 { match self { MenuItemKind::MenuItem(i) => i.id(), MenuItemKind::Submenu(i) => i.id(), @@ -154,7 +154,7 @@ pub trait IsMenuItem: sealed::IsMenuItemBase { fn kind(&self) -> MenuItemKind; /// Returns a unique identifier associated with this menu. - fn id(&self) -> crate::Result { + fn id(&self) -> u32 { self.kind().id() } } diff --git a/core/tauri/src/menu/normal.rs b/core/tauri/src/menu/normal.rs index 64dea20b602b..370ffd1720f1 100644 --- a/core/tauri/src/menu/normal.rs +++ b/core/tauri/src/menu/normal.rs @@ -9,6 +9,7 @@ use crate::{run_main_thread, runtime::menu as muda, AppHandle, Runtime}; /// [`Menu`]: super::Menu /// [`Submenu`]: super::Submenu pub struct MenuItem { + pub(crate) id: u32, pub(crate) inner: muda::MenuItem, pub(crate) app_handle: AppHandle, } @@ -16,6 +17,7 @@ pub struct MenuItem { impl Clone for MenuItem { fn clone(&self) -> Self { Self { + id: self.id, inner: self.inner.clone(), app_handle: self.app_handle.clone(), } @@ -39,8 +41,8 @@ impl super::IsMenuItem for MenuItem { super::MenuItemKind::MenuItem(self.clone()) } - fn id(&self) -> crate::Result { - self.id() + fn id(&self) -> u32 { + self.id } } @@ -55,12 +57,14 @@ impl MenuItem { enabled: bool, acccelerator: Option, ) -> Self { + let item = muda::MenuItem::new( + text, + enabled, + acccelerator.and_then(|s| s.as_ref().parse().ok()), + ); Self { - inner: muda::MenuItem::new( - text, - enabled, - acccelerator.and_then(|s| s.as_ref().parse().ok()), - ), + id: item.id(), + inner: item, app_handle: app_handle.clone(), } } @@ -71,8 +75,8 @@ impl MenuItem { } /// Returns a unique identifier associated with this menu item. - pub fn id(&self) -> crate::Result { - run_main_thread!(self, |self_: Self| self_.inner.id()) + pub fn id(&self) -> u32 { + self.id } /// Get the text for this menu item. diff --git a/core/tauri/src/menu/predefined.rs b/core/tauri/src/menu/predefined.rs index dc4f0d95c77b..ec93b213e04e 100644 --- a/core/tauri/src/menu/predefined.rs +++ b/core/tauri/src/menu/predefined.rs @@ -37,7 +37,7 @@ impl super::IsMenuItem for PredefinedMenuItem { super::MenuItemKind::Predefined(self.clone()) } - fn id(&self) -> crate::Result { + fn id(&self) -> u32 { self.id() } } @@ -227,8 +227,8 @@ impl PredefinedMenuItem { } /// Returns a unique identifier associated with this menu item. - pub fn id(&self) -> crate::Result { - Ok(0) + pub fn id(&self) -> u32 { + 0 } /// Get the text for this menu item. diff --git a/core/tauri/src/menu/submenu.rs b/core/tauri/src/menu/submenu.rs index b5024dfc52c9..efc8fd8c6f18 100644 --- a/core/tauri/src/menu/submenu.rs +++ b/core/tauri/src/menu/submenu.rs @@ -11,6 +11,7 @@ use muda::ContextMenu; /// [`Menu`]: super::Menu /// [`Submenu`]: super::Submenu pub struct Submenu { + pub(crate) id: u32, pub(crate) inner: muda::Submenu, pub(crate) app_handle: AppHandle, } @@ -24,6 +25,7 @@ unsafe impl Send for Submenu {} impl Clone for Submenu { fn clone(&self) -> Self { Self { + id: self.id, inner: self.inner.clone(), app_handle: self.app_handle.clone(), } @@ -41,8 +43,8 @@ impl super::IsMenuItem for Submenu { super::MenuItemKind::Submenu(self.clone()) } - fn id(&self) -> crate::Result { - self.id() + fn id(&self) -> u32 { + self.id } } @@ -95,8 +97,10 @@ impl super::sealed::ContextMenuBase for Submenu { impl Submenu { /// Creates a new submenu. pub fn new>(app_handle: &AppHandle, text: S, enabled: bool) -> Self { + let submenu = muda::Submenu::new(text, enabled); Self { - inner: muda::Submenu::new(text, enabled), + id: submenu.id(), + inner: submenu, app_handle: app_handle.clone(), } } @@ -123,8 +127,8 @@ impl Submenu { } /// Returns a unique identifier associated with this submenu. - pub fn id(&self) -> crate::Result { - run_main_thread!(self, |self_: Self| self_.inner.id()) + pub fn id(&self) -> u32 { + self.id } /// Add a menu item to the end of this submenu. @@ -192,10 +196,12 @@ impl Submenu { .into_iter() .map(|i| match i { muda::MenuItemKind::MenuItem(i) => super::MenuItemKind::MenuItem(super::MenuItem { + id: i.id(), inner: i, app_handle: handle.clone(), }), muda::MenuItemKind::Submenu(i) => super::MenuItemKind::Submenu(super::Submenu { + id: i.id(), inner: i, app_handle: handle.clone(), }), @@ -206,10 +212,12 @@ impl Submenu { }) } muda::MenuItemKind::Check(i) => super::MenuItemKind::Check(super::CheckMenuItem { + id: i.id(), inner: i, app_handle: handle.clone(), }), muda::MenuItemKind::Icon(i) => super::MenuItemKind::Icon(super::IconMenuItem { + id: i.id(), inner: i, app_handle: handle.clone(), }), diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index f428cdc82faf..e2afcbd9ea60 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -327,6 +327,7 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { ( pending.has_app_wide_menu, Menu { + id: m.id(), inner: m, app_handle: self.app_handle.clone(), }, @@ -1081,7 +1082,7 @@ impl Window { self .menu_lock() .as_ref() - .map(|m| m.1.id().map(|i| i == id).unwrap_or(false)) + .map(|m| m.1.id() == id) .unwrap_or(false) } diff --git a/examples/api/src-tauri/src/tray.rs b/examples/api/src-tauri/src/tray.rs index c3cc13e7805a..a8880fe49381 100644 --- a/examples/api/src-tauri/src/tray.rs +++ b/examples/api/src-tauri/src/tray.rs @@ -50,14 +50,14 @@ pub fn create_tray(app: &tauri::AppHandle) -> tauri::Result { + i if i == quit_i.id() => { // exit the app app.exit(0); } - i if i == remove_tray_i.id().unwrap() => { + i if i == remove_tray_i.id() => { app.remove_tray_by_id(TRAY_ID); } - i if i == toggle_i.id().unwrap() => { + i if i == toggle_i.id() => { if let Some(window) = app.get_window("main") { let new_title = if window.is_visible().unwrap() { window.hide().unwrap(); @@ -69,22 +69,22 @@ pub fn create_tray(app: &tauri::AppHandle) -> tauri::Result { + i if i == new_window_i.id() => { WindowBuilder::new(app, "new", WindowUrl::App("index.html".into())) .title("Tauri") .build() .unwrap(); } #[cfg(target_os = "macos")] - i if i == set_title_i.id().unwrap() => { + i if i == set_title_i.id() => { if let Some(tray) = app.tray_by_id(TRAY_ID) { tray.set_title(Some("Tauri")).unwrap(); } } - i if i == icon_i_1.id().unwrap() || i == icon_i_2.id().unwrap() => { + i if i == icon_i_1.id() || i == icon_i_2.id() => { if let Some(tray) = app.tray_by_id(TRAY_ID) { tray - .set_icon(Some(tauri::Icon::Raw(if i == icon_i_1.id().unwrap() { + .set_icon(Some(tauri::Icon::Raw(if i == icon_i_1.id() { include_bytes!("../../../.icons/icon.ico").to_vec() } else { include_bytes!("../../../.icons/tray_icon_with_transparency.png").to_vec() @@ -92,7 +92,7 @@ pub fn create_tray(app: &tauri::AppHandle) -> tauri::Result { + i if i == switch_i.id() => { let flag = is_menu1.load(Ordering::Relaxed); let (menu, tooltip) = if flag { (menu2.clone(), "Menu 2") From 8b07b5009fd4f9cb546c3c0a346a9123385a609b Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 1 Aug 2023 18:22:45 -0300 Subject: [PATCH 036/123] fix doctests --- core/tauri/src/app.rs | 70 ++++++++++------------------------------ core/tauri/src/window.rs | 6 ++-- 2 files changed, 20 insertions(+), 56 deletions(-) diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 336bda83fa47..1ba31bac553a 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -1249,12 +1249,12 @@ impl Builder { /// use tauri::{tray::TrayIconBuilder, menu::{Menu, MenuItem}}; /// /// tauri::Builder::default() - /// .tray_icon(TrayIconBuilder::new().with_menu( - /// Menu::with_items(&[ - /// &MenuItem::new("New window", true, None).unwrap(), - /// &MenuItem::new("Quit", true, None).unwrap(), - /// ]).unwrap() - /// )); + /// .tray_icon(|handle| TrayIconBuilder::new().with_menu( + /// &Menu::with_items(handle, &[ + /// &MenuItem::new(handle, "New window", true, None), + /// &MenuItem::new(handle, "Quit", true, None), + /// ])? + /// ).build(handle)); /// ``` #[must_use] pub fn tray_icon) -> crate::Result> + 'static>( @@ -1269,18 +1269,20 @@ impl Builder { /// /// # 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] @@ -1329,26 +1331,7 @@ impl Builder { self } - /// Registers a global menu event listener. - /// - /// # Examples - /// ``` - /// let quit_menu_item = &MenuItem::new("Quit", true, None); - /// let menu = Menu::with_items(&[ - /// &Submenu::with_items("File", true, &[ - /// &quit_menu_item, - /// ]), - /// ]); - /// - /// tauri::Builder::default() - /// .menu(menu) - /// .on_menu_event(|app, event| { - /// if event.id == quit_menu_item.id() { - /// // quit app - /// app.exit(0); - /// } - /// }); - /// ``` + /// Registers a global tray icon menu event listener. #[must_use] pub fn on_tray_icon_event, TrayIconEvent) + Send + Sync + 'static>( mut self, @@ -1359,25 +1342,6 @@ impl Builder { } /// Registers a global menu event listener. - /// - /// # Examples - /// ``` - /// let quit_menu_item = &MenuItem::new("Quit", true, None); - /// let menu = Menu::with_items(&[ - /// &Submenu::with_items("File", true, &[ - /// &quit_menu_item, - /// ]), - /// ]); - /// - /// tauri::Builder::default() - /// .menu(menu) - /// .on_menu_event(|app, event| { - /// if event.id == quit_menu_item.id() { - /// // quit app - /// app.exit(0); - /// } - /// }); - /// ``` #[must_use] pub fn on_menu_event, MenuEvent) + Send + Sync + 'static>( mut self, diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index e2afcbd9ea60..e93e333312e0 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -1040,10 +1040,10 @@ impl Window { /// tauri::Builder::default() /// .setup(|app| { /// let handle = app.handle(); - /// let save_menu_item = &MenuItem::new(&handle, "Save", true, None); + /// 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, + /// &save_menu_item, /// ])?, /// ])?; /// let window = tauri::WindowBuilder::new(app, "editor", tauri::WindowUrl::default()) @@ -1052,7 +1052,7 @@ impl Window { /// .unwrap(); /// /// window.on_menu_event(move |window, event| { - /// if event.id == save_menu_item.id().unwrap() { + /// if event.id == save_menu_item.id() { /// // save menu item /// } /// }); From 116f6db01a5994f62050317f2ad8ad5a743d29a0 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 1 Aug 2023 18:46:33 -0300 Subject: [PATCH 037/123] fix unix --- core/tauri/src/window.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index e93e333312e0..2a9e799441bb 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -1169,7 +1169,7 @@ impl Window { self .manager - .remove_menu_from_stash_by_id(prev_menu.as_ref().and_then(|m| m.id().ok())); + .remove_menu_from_stash_by_id(prev_menu.as_ref().map(|m| m.id())); Ok(prev_menu) } From 468fd3116f5eabdcf9613e140c75569fbf6e51d1 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 1 Aug 2023 19:07:18 -0300 Subject: [PATCH 038/123] remove window menu APIs on macos --- core/tauri-runtime/src/window.rs | 43 ++++--- core/tauri/src/app.rs | 14 ++- core/tauri/src/manager.rs | 40 +++--- core/tauri/src/window.rs | 203 ++++++++++++++++--------------- 4 files changed, 166 insertions(+), 134 deletions(-) diff --git a/core/tauri-runtime/src/window.rs b/core/tauri-runtime/src/window.rs index e136a19de83f..3e363be4a858 100644 --- a/core/tauri-runtime/src/window.rs +++ b/core/tauri-runtime/src/window.rs @@ -9,7 +9,7 @@ use crate::{ webview::{WebviewAttributes, WebviewIpcHandler}, Dispatch, Runtime, UserEvent, WindowBuilder, }; -use muda::Menu; + use serde::{Deserialize, Deserializer}; use tauri_utils::{config::WindowConfig, Theme}; use url::Url; @@ -242,6 +242,28 @@ pub fn assert_label_is_valid(label: &str) { ); } +#[cfg(not(target_os = "macos"))] +impl> PendingWindow { + #[must_use] + pub fn set_menu(mut self, menu: crate::menu::Menu) -> Self { + self.window_builder = self.window_builder.menu(menu); + self + } + + #[must_use] + pub fn set_app_menu(mut self, menu: crate::menu::Menu) -> Self { + if !self.window_builder.has_menu() { + self.window_builder = self.window_builder.menu(menu); + self.has_app_wide_menu = true; + } + self + } + + pub fn menu(&self) -> Option<&crate::menu::Menu> { + self.window_builder.get_menu() + } +} + impl> PendingWindow { /// Create a new [`PendingWindow`] with a label and starting url. pub fn new( @@ -298,25 +320,6 @@ impl> PendingWindow { } } - #[must_use] - pub fn set_menu(mut self, menu: crate::menu::Menu) -> Self { - self.window_builder = self.window_builder.menu(menu); - self - } - - #[must_use] - pub fn set_app_menu(mut self, menu: crate::menu::Menu) -> Self { - if !self.window_builder.has_menu() { - self.window_builder = self.window_builder.menu(menu); - self.has_app_wide_menu = true; - } - self - } - - pub fn menu(&self) -> Option<&Menu> { - self.window_builder.get_menu() - } - pub fn register_uri_scheme_protocol< N: Into, H: Fn(&HttpRequest) -> Result> + Send + Sync + 'static, diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 1ba31bac553a..9188d1387a83 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -590,6 +590,7 @@ macro_rules! shared_app_impl { 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_lock().values() { let mut window_menu = window.menu_lock(); if window_menu.as_ref().map(|m| m.0).unwrap_or(true) { @@ -636,8 +637,9 @@ macro_rules! shared_app_impl { pub fn remove_menu(&self) -> crate::Result>> { let mut current_menu = self.manager.menu_lock(); - // remove from windows that have the app-wide menu if let Some(menu) = &*current_menu { + // remove from windows that have the app-wide menu + #[cfg(not(target_os = "macos"))] for window in self.manager.windows_lock().values() { if window.has_app_wide_menu() { #[cfg(not(target_os = "macos"))] @@ -1610,6 +1612,8 @@ fn setup(app: &mut App) -> crate::Result<()> { for pending in pending_windows { let pending = manager.prepare_window(app_handle.clone(), pending, &window_labels)?; + + #[cfg(not(target_os = "macos"))] let menu = pending.menu().cloned().map(|m| { ( pending.has_app_wide_menu, @@ -1620,6 +1624,7 @@ fn setup(app: &mut App) -> crate::Result<()> { }, ) }); + let window_effects = pending.webview_attributes.window_effects.clone(); let detached = if let RuntimeOrDispatch::RuntimeHandle(runtime) = app_handle.runtime() { runtime.create_window(pending)? @@ -1627,7 +1632,12 @@ fn setup(app: &mut App) -> crate::Result<()> { // the AppHandle's runtime is always RuntimeOrDispatch::RuntimeHandle unreachable!() }; - let window = manager.attach_window(app_handle.clone(), detached, menu); + let window = manager.attach_window( + app_handle.clone(), + detached, + #[cfg(not(target_os = "macos"))] + menu, + ); if let Some(effects) = window_effects { crate::vibrancy::set_window_effects(&window, Some(effects))?; diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index 43b9f5667dde..ad1abb1b778d 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -399,6 +399,9 @@ impl WindowManager { pub(crate) fn remove_menu_from_stash_by_id(&self, id: Option) { if let Some(id) = id { + #[cfg(target_os = "macos")] + let is_used_by_a_window = false; + #[cfg(not(target_os = "macos"))] 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); @@ -1192,19 +1195,22 @@ impl WindowManager { } })); - if let Some(menu) = &*self.menu_lock() { - pending = pending.set_app_menu(menu.inner().clone()); - } + #[cfg(not(target_os = "macos"))] + { + if let Some(menu) = &*self.menu_lock() { + pending = pending.set_app_menu(menu.inner().clone()); + } - if let Some(menu) = pending.menu() { - self.inner.menus.lock().unwrap().insert( - menu.id(), - Menu { - id: menu.id(), - inner: menu.clone(), - app_handle: app_handle.clone(), - }, - ); + if let Some(menu) = pending.menu() { + self.inner.menus.lock().unwrap().insert( + menu.id(), + Menu { + id: menu.id(), + inner: menu.clone(), + app_handle: app_handle.clone(), + }, + ); + } } Ok(pending) @@ -1214,9 +1220,15 @@ impl WindowManager { &self, app_handle: AppHandle, window: DetachedWindow, - menu: Option<(bool, Menu)>, + #[cfg(not(target_os = "macos"))] menu: Option<(bool, Menu)>, ) -> Window { - let window = Window::new(self.clone(), window, app_handle, menu); + let window = Window::new( + self.clone(), + window, + app_handle, + #[cfg(not(target_os = "macos"))] + menu, + ); let window_ = window.clone(); let window_event_listeners = self.inner.window_event_listeners.clone(); diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index 2a9e799441bb..2077347a9beb 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -4,7 +4,7 @@ //! The Tauri window types and functions. -use crate::menu::{ContextMenu, MenuEvent}; +use crate::menu::ContextMenu; pub use tauri_utils::{config::Color, WindowEffect as Effect, WindowEffectState as EffectState}; use url::Url; @@ -54,7 +54,7 @@ use std::{ fmt, hash::{Hash, Hasher}, path::PathBuf, - sync::{Arc, Mutex, MutexGuard}, + sync::{Arc, Mutex}, }; pub(crate) type WebResourceRequestHandler = dyn Fn(&HttpRequest, &mut HttpResponse) + Send + Sync; @@ -323,6 +323,8 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { let pending = self .manager .prepare_window(self.app_handle.clone(), pending, &labels)?; + + #[cfg(not(target_os = "macos"))] let menu = pending.menu().cloned().map(|m| { ( pending.has_app_wide_menu, @@ -333,6 +335,7 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { }, ) }); + let window_effects = pending.webview_attributes.window_effects.clone(); let window = match &mut self.runtime { RuntimeOrDispatch::Runtime(runtime) => runtime.create_window(pending), @@ -340,9 +343,12 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { RuntimeOrDispatch::Dispatch(dispatcher) => dispatcher.create_window(pending), } .map(|window| { - self - .manager - .attach_window(self.app_handle.clone(), window, menu) + self.manager.attach_window( + self.app_handle.clone(), + window, + #[cfg(not(target_os = "macos"))] + menu, + ) })?; if let Some(effects) = window_effects { @@ -798,6 +804,7 @@ pub struct Window { pub(crate) app_handle: AppHandle, js_event_listeners: Arc>>>, // The menu set for this window + #[cfg(not(target_os = "macos"))] #[allow(clippy::type_complexity)] pub(crate) menu: Arc)>>>, } @@ -826,6 +833,7 @@ impl Clone for Window { manager: self.manager.clone(), app_handle: self.app_handle.clone(), js_event_listeners: self.js_event_listeners.clone(), + #[cfg(not(target_os = "macos"))] menu: self.menu.clone(), } } @@ -973,13 +981,14 @@ impl Window { manager: WindowManager, window: DetachedWindow, app_handle: AppHandle, - menu: Option<(bool, Menu)>, + #[cfg(not(target_os = "macos"))] menu: Option<(bool, Menu)>, ) -> Self { Self { window, manager, app_handle, js_event_listeners: Default::default(), + #[cfg(not(target_os = "macos"))] menu: Arc::new(Mutex::new(menu)), } } @@ -1026,6 +1035,91 @@ impl Window { .on_window_event(move |event| f(&event.clone().into())); } + /// Shows the specified menu as a context menu at the specified position. + /// If a position was not provided, the cursor position will be used. + /// + /// The position is relative to the window's top-left corner. + pub fn show_context_menu>( + &self, + menu: &M, + position: Option

, + ) -> crate::Result<()> { + let position = position.map(|p| p.into()); + + menu.popup(self.clone(), position)?; + + Ok(()) + } + + /// Executes a closure, providing it with the webview handle that is specific to the current platform. + /// + /// The closure is executed on the main thread. + /// + /// # Examples + /// + /// ```rust,no_run + /// #[cfg(target_os = "macos")] + /// #[macro_use] + /// extern crate objc; + /// use tauri::Manager; + /// + /// fn main() { + /// tauri::Builder::default() + /// .setup(|app| { + /// let main_window = app.get_window("main").unwrap(); + /// main_window.with_webview(|webview| { + /// #[cfg(target_os = "linux")] + /// { + /// // see https://docs.rs/webkit2gtk/0.18.2/webkit2gtk/struct.WebView.html + /// // and https://docs.rs/webkit2gtk/0.18.2/webkit2gtk/trait.WebViewExt.html + /// use webkit2gtk::traits::WebViewExt; + /// webview.inner().set_zoom_level(4.); + /// } + /// + /// #[cfg(windows)] + /// unsafe { + /// // see https://docs.rs/webview2-com/0.19.1/webview2_com/Microsoft/Web/WebView2/Win32/struct.ICoreWebView2Controller.html + /// webview.controller().SetZoomFactor(4.).unwrap(); + /// } + /// + /// #[cfg(target_os = "macos")] + /// unsafe { + /// let () = msg_send![webview.inner(), setPageZoom: 4.]; + /// let () = msg_send![webview.controller(), removeAllUserScripts]; + /// let bg_color: cocoa::base::id = msg_send![class!(NSColor), colorWithDeviceRed:0.5 green:0.2 blue:0.4 alpha:1.]; + /// let () = msg_send![webview.ns_window(), setBackgroundColor: bg_color]; + /// } + /// + /// #[cfg(target_os = "android")] + /// { + /// use jni::objects::JValue; + /// webview.jni_handle().exec(|env, _, webview| { + /// env.call_method(webview, "zoomBy", "(F)V", &[JValue::Float(4.)]).unwrap(); + /// }) + /// } + /// }); + /// Ok(()) + /// }); + /// } + /// ``` + #[cfg(feature = "wry")] + #[cfg_attr(doc_cfg, doc(feature = "wry"))] + pub fn with_webview( + &self, + f: F, + ) -> crate::Result<()> { + self + .window + .dispatcher + .with_webview(|w| f(PlatformWebview(*w.downcast().unwrap()))) + .map_err(Into::into) + } +} + +/// Menu APIs +#[cfg(not(target_os = "macos"))] +#[cfg_attr(doc_cfg, doc(cfg(not(target_os = "macos"))))] +impl Window { /// Registers a global menu event listener. /// /// Note that this handler is called for any menu event, @@ -1060,7 +1154,10 @@ impl Window { /// Ok(()) /// }); /// ``` - pub fn on_menu_event, MenuEvent) + Send + Sync + 'static>(&self, f: F) { + pub fn on_menu_event, crate::menu::MenuEvent) + Send + Sync + 'static>( + &self, + f: F, + ) { self .manager .inner @@ -1070,7 +1167,7 @@ impl Window { .insert(self.label().to_string(), Box::new(f)); } - pub(crate) fn menu_lock(&self) -> MutexGuard<'_, Option<(bool, Menu)>> { + pub(crate) fn menu_lock(&self) -> std::sync::MutexGuard<'_, Option<(bool, Menu)>> { self.menu.lock().expect("poisoned window") } @@ -1097,8 +1194,6 @@ impl Window { /// /// - **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(not(target_os = "macos"))] - #[cfg_attr(doc_cfg, doc(cfg(not(target_os = "macos"))))] pub fn set_menu(&self, menu: Menu) -> crate::Result>> { let prev_menu = self.remove_menu()?; @@ -1136,8 +1231,6 @@ impl Window { /// /// - **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. - #[cfg(not(target_os = "macos"))] - #[cfg_attr(doc_cfg, doc(cfg(not(target_os = "macos"))))] pub fn remove_menu(&self) -> crate::Result>> { let mut current_menu = self.menu_lock(); @@ -1175,8 +1268,6 @@ impl Window { } /// Hides the window menu. - #[cfg(not(target_os = "macos"))] - #[cfg_attr(doc_cfg, doc(cfg(not(target_os = "macos"))))] pub fn hide_menu(&self) -> crate::Result<()> { // remove from the window if let Some((_, menu)) = &*self.menu_lock() { @@ -1204,8 +1295,6 @@ impl Window { } /// Shows the window menu. - #[cfg(not(target_os = "macos"))] - #[cfg_attr(doc_cfg, doc(cfg(not(target_os = "macos"))))] pub fn show_menu(&self) -> crate::Result<()> { // remove from the window if let Some((_, menu)) = &*self.menu_lock() { @@ -1233,8 +1322,6 @@ impl Window { } /// Shows the window menu. - #[cfg(not(target_os = "macos"))] - #[cfg_attr(doc_cfg, doc(cfg(not(target_os = "macos"))))] pub fn is_menu_visible(&self) -> crate::Result { // remove from the window if let Some((_, menu)) = &*self.menu_lock() { @@ -1263,86 +1350,6 @@ impl Window { Ok(false) } - - /// Shows the specified menu as a context menu at the specified position. - /// If a position was not provided, the cursor position will be used. - /// - /// The position is relative to the window's top-left corner. - pub fn show_context_menu>( - &self, - menu: &M, - position: Option

, ) -> crate::Result<()> { + let position = position.map(Into::into).map(Into::into); run_main_thread!(self, |self_: Self| { #[cfg(target_os = "macos")] if let Ok(view) = window.ns_view() { self_ .inner() - .show_context_menu_for_nsview(view as _, position.map(Into::into)); + .show_context_menu_for_nsview(view as _, position); } #[cfg(any( @@ -64,18 +56,25 @@ impl super::sealed::ContextMenuBase for Menu { if let Ok(gtk_window) = window.gtk_window() { self_ .inner() - .show_context_menu_for_gtk_window(>k_window, position.map(Into::into)); + .show_context_menu_for_gtk_window(>k_window, position); } #[cfg(windows)] if let Ok(hwnd) = window.hwnd() { - self_ - .inner() - .show_context_menu_for_hwnd(hwnd.0, position.map(Into::into)); + self_.inner().show_context_menu_for_hwnd(hwnd.0, position); } }) } } +impl super::sealed::ContextMenuBase for Menu { + fn inner(&self) -> &dyn muda::ContextMenu { + &self.inner + } + + fn inner_owned(&self) -> Box { + Box::new(self.clone().inner) + } +} impl Menu { /// Creates a new menu. diff --git a/core/tauri/src/menu/mod.rs b/core/tauri/src/menu/mod.rs index 646ea287364e..3c52b0285fef 100644 --- a/core/tauri/src/menu/mod.rs +++ b/core/tauri/src/menu/mod.rs @@ -165,10 +165,16 @@ pub trait IsMenuItem: sealed::IsMenuItemBase { /// # Safety /// /// This trait is ONLY meant to be implemented internally by the crate. -pub trait ContextMenu: sealed::ContextMenuBase + Send + Sync {} +pub trait ContextMenu: sealed::ContextMenuBase + Send + Sync { + /// Popup this menu as a context menu on the specified window. + fn popup>( + &self, + window: crate::Window, + position: Option

, + ) -> crate::Result<()>; +} pub(crate) mod sealed { - use crate::Position; pub trait IsMenuItemBase { fn inner(&self) -> &dyn super::muda::IsMenuItem; @@ -177,11 +183,5 @@ pub(crate) mod sealed { pub trait ContextMenuBase { fn inner(&self) -> &dyn super::muda::ContextMenu; fn inner_owned(&self) -> Box; - - fn popup( - &self, - window: crate::Window, - position: Option, - ) -> crate::Result<()>; } } diff --git a/core/tauri/src/menu/submenu.rs b/core/tauri/src/menu/submenu.rs index fe94f9480cca..bdb7dcba8727 100644 --- a/core/tauri/src/menu/submenu.rs +++ b/core/tauri/src/menu/submenu.rs @@ -48,27 +48,19 @@ impl super::IsMenuItem for Submenu { } } -impl super::ContextMenu for Submenu {} -impl super::sealed::ContextMenuBase for Submenu { - fn inner(&self) -> &dyn muda::ContextMenu { - &self.inner - } - - fn inner_owned(&self) -> Box { - Box::new(self.clone().inner) - } - - fn popup( +impl super::ContextMenu for Submenu { + fn popup>( &self, window: crate::Window, - position: Option, + position: Option

, ) -> crate::Result<()> { + let position = position.map(Into::into).map(Into::into); 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.map(Into::into)); + .show_context_menu_for_nsview(view as _, position); } #[cfg(any( @@ -79,20 +71,25 @@ impl super::sealed::ContextMenuBase for Submenu { target_os = "openbsd" ))] if let Ok(w) = window.gtk_window() { - self_ - .inner() - .show_context_menu_for_gtk_window(&w, position.map(Into::into)); + 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.map(Into::into)) + self_.inner().show_context_menu_for_hwnd(hwnd.0, position) } }) } } +impl super::sealed::ContextMenuBase for Submenu { + fn inner(&self) -> &dyn muda::ContextMenu { + &self.inner + } + + fn inner_owned(&self) -> Box { + Box::new(self.clone().inner) + } +} impl Submenu { /// Creates a new submenu. From 575f5c836dda7e477a7dbbe51d8073d77b34d1bc Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Wed, 2 Aug 2023 21:20:34 +0300 Subject: [PATCH 062/123] rename `Window::show_context_menu` to `Window::popup_menu` --- core/tauri/src/window.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index 4cea6d46e773..6061c3a13d8d 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -1091,16 +1091,12 @@ impl Window { /// If a position was not provided, the cursor position will be used. /// /// The position is relative to the window's top-left corner. - pub fn show_context_menu>( + pub fn popup_menu>( &self, menu: &M, position: Option

, ) -> crate::Result<()> { - let position = position.map(|p| p.into()); - - menu.popup(self.clone(), position)?; - - Ok(()) + menu.popup(self.clone(), position) } /// Executes a closure, providing it with the webview handle that is specific to the current platform. From c846285de00995bdfdc87a22895d8967a1f3337a Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Wed, 2 Aug 2023 21:38:14 +0300 Subject: [PATCH 063/123] remove extranouse result return from `MenuBuilder` and `SubmenuBuilder` methods thanks to the awesome @lucasfernog Co-authored-by: Lucas Nogueira --- core/tauri/src/menu/builders/menu.rs | 231 +++++++++++----------- core/tauri/src/menu/builders/submenu.rs | 247 ++++++++++++------------ core/tauri/src/menu/mod.rs | 28 +++ 3 files changed, 268 insertions(+), 238 deletions(-) diff --git a/core/tauri/src/menu/builders/menu.rs b/core/tauri/src/menu/builders/menu.rs index 662b46694c61..60c1bbd38e82 100644 --- a/core/tauri/src/menu/builders/menu.rs +++ b/core/tauri/src/menu/builders/menu.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use crate::{menu::*, AppHandle, Icon, Manager, Runtime}; +use crate::{menu::*, Icon, Manager, Runtime}; /// A builder type for [`Menu`] /// @@ -19,7 +19,7 @@ use crate::{menu::*, AppHandle, Icon, Manager, Runtime}; /// # height: 0, /// # }; /// # let icon2 = icon1.clone(); -/// let menu = MenuBuilder::new(&handle) +/// let menu = MenuBuilder::new(&handle, "File") /// .item(&MenuItem::new(&handle, "MenuItem 1", true, None))? /// .items(&[ /// &CheckMenuItem::new(&handle, "CheckMenuItem 1", true, true, None), @@ -38,62 +38,56 @@ use crate::{menu::*, AppHandle, Icon, Manager, Runtime}; /// Ok(()) /// }); /// ``` -pub struct MenuBuilder { - menu: Menu, - app_handle: AppHandle, +pub struct MenuBuilder<'m, R: Runtime, M: Manager> { + manager: &'m M, + items: Vec>, } -impl MenuBuilder { +impl<'m, R: Runtime, M: Manager> MenuBuilder<'m, R, M> { /// Create a new menu builder. - pub fn new>(manager: &M) -> Self { + pub fn new>(manager: &'m M) -> Self { Self { - menu: Menu::new(manager), - app_handle: manager.app_handle(), + items: Vec::new(), + manager, } } /// Add this item to the menu. - pub fn item(self, item: &dyn IsMenuItem) -> crate::Result { - self.menu.append(item)?; - Ok(self) + pub fn item(mut self, item: &dyn IsMenuItem) -> Self { + self.items.push(item.kind()); + self } /// Add these items to the menu. - pub fn items(self, items: &[&dyn IsMenuItem]) -> crate::Result { - self.menu.append_items(items)?; - Ok(self) + 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>(self, text: S) -> crate::Result { + pub fn text>(mut self, text: S) -> Self { + self + .items + .push(MenuItem::new(self.manager, text, true, None).kind()); self - .menu - .append(&MenuItem::new(&self.app_handle, text, true, None))?; - Ok(self) } /// Add a [CheckMenuItem] to the menu. - pub fn check>(self, text: S) -> crate::Result { - self.menu.append(&CheckMenuItem::new( - &self.app_handle, - text, - true, - true, - None, - ))?; - Ok(self) + 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>(self, text: S, icon: Icon) -> crate::Result { - self.menu.append(&IconMenuItem::new( - &self.app_handle, - text, - true, - Some(icon), - None, - ))?; - Ok(self) + 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. @@ -101,55 +95,51 @@ impl MenuBuilder { /// ## Platform-specific: /// /// - **Windows / Linux**: Unsupported. - pub fn native_icon>(self, text: S, icon: NativeIcon) -> crate::Result { - self.menu.append(&IconMenuItem::with_native_icon( - &self.app_handle, - text, - true, - Some(icon), - None, - ))?; - Ok(self) + 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(self) -> crate::Result { + pub fn separator(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::separator(self.manager).kind()); self - .menu - .append(&PredefinedMenuItem::separator(&self.app_handle))?; - Ok(self) } /// Add Copy menu item to the menu. - pub fn copy(self) -> crate::Result { + pub fn copy(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::copy(self.manager, None).kind()); self - .menu - .append(&PredefinedMenuItem::copy(&self.app_handle, None))?; - Ok(self) } /// Add Cut menu item to the menu. - pub fn cut(self) -> crate::Result { + pub fn cut(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::cut(self.manager, None).kind()); self - .menu - .append(&PredefinedMenuItem::cut(&self.app_handle, None))?; - Ok(self) } /// Add Paste menu item to the menu. - pub fn paste(self) -> crate::Result { + pub fn paste(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::paste(self.manager, None).kind()); self - .menu - .append(&PredefinedMenuItem::paste(&self.app_handle, None))?; - Ok(self) } /// Add SelectAll menu item to the menu. - pub fn select_all(self) -> crate::Result { + pub fn select_all(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::select_all(self.manager, None).kind()); self - .menu - .append(&PredefinedMenuItem::select_all(&self.app_handle, None))?; - Ok(self) } /// Add Undo menu item to the menu. @@ -157,22 +147,22 @@ impl MenuBuilder { /// ## Platform-specific: /// /// - **Windows / Linux:** Unsupported. - pub fn undo(self) -> crate::Result { + pub fn undo(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::undo(self.manager, None).kind()); self - .menu - .append(&PredefinedMenuItem::undo(&self.app_handle, None))?; - Ok(self) } /// Add Redo menu item to the menu. /// /// ## Platform-specific: /// /// - **Windows / Linux:** Unsupported. - pub fn redo(self) -> crate::Result { + pub fn redo(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::redo(self.manager, None).kind()); self - .menu - .append(&PredefinedMenuItem::redo(&self.app_handle, None))?; - Ok(self) } /// Add Minimize window menu item to the menu. @@ -180,11 +170,11 @@ impl MenuBuilder { /// ## Platform-specific: /// /// - **Linux:** Unsupported. - pub fn minimize(self) -> crate::Result { + pub fn minimize(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::minimize(self.manager, None).kind()); self - .menu - .append(&PredefinedMenuItem::minimize(&self.app_handle, None))?; - Ok(self) } /// Add Maximize window menu item to the menu. @@ -192,11 +182,11 @@ impl MenuBuilder { /// ## Platform-specific: /// /// - **Linux:** Unsupported. - pub fn maximize(self) -> crate::Result { + pub fn maximize(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::maximize(self.manager, None).kind()); self - .menu - .append(&PredefinedMenuItem::maximize(&self.app_handle, None))?; - Ok(self) } /// Add Fullscreen menu item to the menu. @@ -204,11 +194,11 @@ impl MenuBuilder { /// ## Platform-specific: /// /// - **Windows / Linux:** Unsupported. - pub fn fullscreen(self) -> crate::Result { + pub fn fullscreen(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::fullscreen(self.manager, None).kind()); self - .menu - .append(&PredefinedMenuItem::fullscreen(&self.app_handle, None))?; - Ok(self) } /// Add Hide window menu item to the menu. @@ -216,11 +206,11 @@ impl MenuBuilder { /// ## Platform-specific: /// /// - **Linux:** Unsupported. - pub fn hide(self) -> crate::Result { + pub fn hide(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::hide(self.manager, None).kind()); self - .menu - .append(&PredefinedMenuItem::hide(&self.app_handle, None))?; - Ok(self) } /// Add Hide other windows menu item to the menu. @@ -228,11 +218,11 @@ impl MenuBuilder { /// ## Platform-specific: /// /// - **Linux:** Unsupported. - pub fn hide_others(self) -> crate::Result { + pub fn hide_others(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::hide_others(self.manager, None).kind()); self - .menu - .append(&PredefinedMenuItem::hide_others(&self.app_handle, None))?; - Ok(self) } /// Add Show all app windows menu item to the menu. @@ -240,11 +230,11 @@ impl MenuBuilder { /// ## Platform-specific: /// /// - **Windows / Linux:** Unsupported. - pub fn show_all(self) -> crate::Result { + pub fn show_all(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::show_all(self.manager, None).kind()); self - .menu - .append(&PredefinedMenuItem::show_all(&self.app_handle, None))?; - Ok(self) } /// Add Close window menu item to the menu. @@ -252,11 +242,11 @@ impl MenuBuilder { /// ## Platform-specific: /// /// - **Linux:** Unsupported. - pub fn close_window(self) -> crate::Result { + pub fn close_window(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::close_window(self.manager, None).kind()); self - .menu - .append(&PredefinedMenuItem::close_window(&self.app_handle, None))?; - Ok(self) } /// Add Quit app menu item to the menu. @@ -264,19 +254,19 @@ impl MenuBuilder { /// ## Platform-specific: /// /// - **Linux:** Unsupported. - pub fn quit(self) -> crate::Result { + pub fn quit(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::quit(self.manager, None).kind()); self - .menu - .append(&PredefinedMenuItem::quit(&self.app_handle, None))?; - Ok(self) } /// Add About app menu item to the menu. - pub fn about(self, metadata: Option) -> crate::Result { + pub fn about(mut self, metadata: Option) -> Self { + self + .items + .push(PredefinedMenuItem::about(self.manager, None, metadata).kind()); self - .menu - .append(&PredefinedMenuItem::about(&self.app_handle, None, metadata))?; - Ok(self) } /// Add Services menu item to the menu. @@ -284,15 +274,24 @@ impl MenuBuilder { /// ## Platform-specific: /// /// - **Windows / Linux:** Unsupported. - pub fn services(self) -> crate::Result { + pub fn services(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::services(self.manager, None).kind()); self - .menu - .append(&PredefinedMenuItem::services(&self.app_handle, None))?; - Ok(self) } /// Builds this menu - pub fn build(self) -> Menu { - self.menu + pub fn build(self) -> crate::Result> { + if self.items.is_empty() { + Ok(Menu::new(self.manager)) + } else { + let items = self + .items + .iter() + .map(|i| i as &dyn IsMenuItem) + .collect::>(); + Menu::with_items(self.manager, &items) + } } } diff --git a/core/tauri/src/menu/builders/submenu.rs b/core/tauri/src/menu/builders/submenu.rs index 12423f529303..66ab1dd22214 100644 --- a/core/tauri/src/menu/builders/submenu.rs +++ b/core/tauri/src/menu/builders/submenu.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use crate::{menu::*, AppHandle, Icon, Manager, Runtime}; +use crate::{menu::*, Icon, Manager, Runtime}; -/// A builder type for [`Menu`] +/// A builder type for [`Submenu`] /// /// # Example /// @@ -40,68 +40,66 @@ use crate::{menu::*, AppHandle, Icon, Manager, Runtime}; /// Ok(()) /// }); /// ``` -pub struct SubmenuBuilder { - submenu: Submenu, - app_handle: AppHandle, +pub struct SubmenuBuilder<'m, R: Runtime, M: Manager> { + manager: &'m M, + text: String, + enabled: bool, + items: Vec>, } -impl SubmenuBuilder { - /// Create a new menu builder. - pub fn new, S: AsRef>(manager: &M, text: S) -> Self { +impl<'m, R: Runtime, M: Manager> SubmenuBuilder<'m, R, M> { + /// Create a new submenu builder. + pub fn new>(manager: &'m M, text: S) -> Self { Self { - submenu: Submenu::new(manager, text, true), - app_handle: manager.app_handle(), + items: Vec::new(), + text: text.as_ref().to_string(), + enabled: false, + manager, } } - /// Set the enabled state for submenu. - pub fn enabled(self, enabled: bool) -> crate::Result { - self.submenu.set_enabled(enabled)?; - Ok(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(self, item: &dyn IsMenuItem) -> crate::Result { - self.submenu.append(item)?; - Ok(self) + pub fn item(mut self, item: &dyn IsMenuItem) -> Self { + self.items.push(item.kind()); + self } /// Add these items to the submenu. - pub fn items(self, items: &[&dyn IsMenuItem]) -> crate::Result { - self.submenu.append_items(items)?; - Ok(self) + 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>(self, text: S) -> crate::Result { + pub fn text>(mut self, text: S) -> Self { + self + .items + .push(MenuItem::new(self.manager, text, true, None).kind()); self - .submenu - .append(&MenuItem::new(&self.app_handle, text, true, None))?; - Ok(self) } /// Add a [CheckMenuItem] to the submenu. - pub fn check>(self, text: S) -> crate::Result { - self.submenu.append(&CheckMenuItem::new( - &self.app_handle, - text, - true, - true, - None, - ))?; - Ok(self) + 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>(self, text: S, icon: Icon) -> crate::Result { - self.submenu.append(&IconMenuItem::new( - &self.app_handle, - text, - true, - Some(icon), - None, - ))?; - Ok(self) + 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. @@ -109,55 +107,51 @@ impl SubmenuBuilder { /// ## Platform-specific: /// /// - **Windows / Linux**: Unsupported. - pub fn native_icon>(self, text: S, icon: NativeIcon) -> crate::Result { - self.submenu.append(&IconMenuItem::with_native_icon( - &self.app_handle, - text, - true, - Some(icon), - None, - ))?; - Ok(self) + 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(self) -> crate::Result { + pub fn separator(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::separator(self.manager).kind()); self - .submenu - .append(&PredefinedMenuItem::separator(&self.app_handle))?; - Ok(self) } /// Add Copy menu item to the submenu. - pub fn copy(self) -> crate::Result { + pub fn copy(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::copy(self.manager, None).kind()); self - .submenu - .append(&PredefinedMenuItem::copy(&self.app_handle, None))?; - Ok(self) } /// Add Cut menu item to the submenu. - pub fn cut(self) -> crate::Result { + pub fn cut(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::cut(self.manager, None).kind()); self - .submenu - .append(&PredefinedMenuItem::cut(&self.app_handle, None))?; - Ok(self) } /// Add Paste menu item to the submenu. - pub fn paste(self) -> crate::Result { + pub fn paste(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::paste(self.manager, None).kind()); self - .submenu - .append(&PredefinedMenuItem::paste(&self.app_handle, None))?; - Ok(self) } /// Add SelectAll menu item to the submenu. - pub fn select_all(self) -> crate::Result { + pub fn select_all(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::select_all(self.manager, None).kind()); self - .submenu - .append(&PredefinedMenuItem::select_all(&self.app_handle, None))?; - Ok(self) } /// Add Undo menu item to the submenu. @@ -165,22 +159,22 @@ impl SubmenuBuilder { /// ## Platform-specific: /// /// - **Windows / Linux:** Unsupported. - pub fn undo(self) -> crate::Result { + pub fn undo(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::undo(self.manager, None).kind()); self - .submenu - .append(&PredefinedMenuItem::undo(&self.app_handle, None))?; - Ok(self) } /// Add Redo menu item to the submenu. /// /// ## Platform-specific: /// /// - **Windows / Linux:** Unsupported. - pub fn redo(self) -> crate::Result { + pub fn redo(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::redo(self.manager, None).kind()); self - .submenu - .append(&PredefinedMenuItem::redo(&self.app_handle, None))?; - Ok(self) } /// Add Minimize window menu item to the submenu. @@ -188,11 +182,11 @@ impl SubmenuBuilder { /// ## Platform-specific: /// /// - **Linux:** Unsupported. - pub fn minimize(self) -> crate::Result { + pub fn minimize(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::minimize(self.manager, None).kind()); self - .submenu - .append(&PredefinedMenuItem::minimize(&self.app_handle, None))?; - Ok(self) } /// Add Maximize window menu item to the submenu. @@ -200,11 +194,11 @@ impl SubmenuBuilder { /// ## Platform-specific: /// /// - **Linux:** Unsupported. - pub fn maximize(self) -> crate::Result { + pub fn maximize(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::maximize(self.manager, None).kind()); self - .submenu - .append(&PredefinedMenuItem::maximize(&self.app_handle, None))?; - Ok(self) } /// Add Fullscreen menu item to the submenu. @@ -212,11 +206,11 @@ impl SubmenuBuilder { /// ## Platform-specific: /// /// - **Windows / Linux:** Unsupported. - pub fn fullscreen(self) -> crate::Result { + pub fn fullscreen(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::fullscreen(self.manager, None).kind()); self - .submenu - .append(&PredefinedMenuItem::fullscreen(&self.app_handle, None))?; - Ok(self) } /// Add Hide window menu item to the submenu. @@ -224,11 +218,11 @@ impl SubmenuBuilder { /// ## Platform-specific: /// /// - **Linux:** Unsupported. - pub fn hide(self) -> crate::Result { + pub fn hide(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::hide(self.manager, None).kind()); self - .submenu - .append(&PredefinedMenuItem::hide(&self.app_handle, None))?; - Ok(self) } /// Add Hide other windows menu item to the submenu. @@ -236,11 +230,11 @@ impl SubmenuBuilder { /// ## Platform-specific: /// /// - **Linux:** Unsupported. - pub fn hide_others(self) -> crate::Result { + pub fn hide_others(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::hide_others(self.manager, None).kind()); self - .submenu - .append(&PredefinedMenuItem::hide_others(&self.app_handle, None))?; - Ok(self) } /// Add Show all app windows menu item to the submenu. @@ -248,11 +242,11 @@ impl SubmenuBuilder { /// ## Platform-specific: /// /// - **Windows / Linux:** Unsupported. - pub fn show_all(self) -> crate::Result { + pub fn show_all(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::show_all(self.manager, None).kind()); self - .submenu - .append(&PredefinedMenuItem::show_all(&self.app_handle, None))?; - Ok(self) } /// Add Close window menu item to the submenu. @@ -260,11 +254,11 @@ impl SubmenuBuilder { /// ## Platform-specific: /// /// - **Linux:** Unsupported. - pub fn close_window(self) -> crate::Result { + pub fn close_window(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::close_window(self.manager, None).kind()); self - .submenu - .append(&PredefinedMenuItem::close_window(&self.app_handle, None))?; - Ok(self) } /// Add Quit app menu item to the submenu. @@ -272,19 +266,19 @@ impl SubmenuBuilder { /// ## Platform-specific: /// /// - **Linux:** Unsupported. - pub fn quit(self) -> crate::Result { + pub fn quit(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::quit(self.manager, None).kind()); self - .submenu - .append(&PredefinedMenuItem::quit(&self.app_handle, None))?; - Ok(self) } /// Add About app menu item to the submenu. - pub fn about(self, metadata: Option) -> crate::Result { + pub fn about(mut self, metadata: Option) -> Self { + self + .items + .push(PredefinedMenuItem::about(self.manager, None, metadata).kind()); self - .submenu - .append(&PredefinedMenuItem::about(&self.app_handle, None, metadata))?; - Ok(self) } /// Add Services menu item to the submenu. @@ -292,15 +286,24 @@ impl SubmenuBuilder { /// ## Platform-specific: /// /// - **Windows / Linux:** Unsupported. - pub fn services(self) -> crate::Result { + pub fn services(mut self) -> Self { + self + .items + .push(PredefinedMenuItem::services(self.manager, None).kind()); self - .submenu - .append(&PredefinedMenuItem::services(&self.app_handle, None))?; - Ok(self) } - /// Builds this menu - pub fn build(self) -> Submenu { - self.submenu + /// Builds this submenu + pub fn build(self) -> crate::Result> { + if self.items.is_empty() { + Ok(Submenu::new(self.manager, self.text, self.enabled)) + } else { + let items = self + .items + .iter() + .map(|i| i as &dyn IsMenuItem) + .collect::>(); + Submenu::with_items(self.manager, self.text, self.enabled, &items) + } } } diff --git a/core/tauri/src/menu/mod.rs b/core/tauri/src/menu/mod.rs index 3c52b0285fef..41d4cdc42637 100644 --- a/core/tauri/src/menu/mod.rs +++ b/core/tauri/src/menu/mod.rs @@ -145,6 +145,34 @@ impl MenuItemKind { } } +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) -> u32 { + self.id() + } +} + /// A trait that defines a generic item in a menu, which may be one of [`MenuItemKind`] /// /// # Safety From 0234a201681ea2d792329d2f9a2074a8fc32c2a6 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Wed, 2 Aug 2023 21:56:15 +0300 Subject: [PATCH 064/123] revert "adding result return to menu/tray/window event handlers" --- core/tauri/src/app.rs | 74 +++++++++---------------- core/tauri/src/menu/builders/menu.rs | 26 ++++----- core/tauri/src/menu/builders/submenu.rs | 24 ++++---- core/tauri/src/test/mod.rs | 1 - core/tauri/src/tray.rs | 22 ++------ core/tauri/src/window.rs | 12 +--- 6 files changed, 58 insertions(+), 101 deletions(-) diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index b9eaf73e58f5..92324270041a 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -53,12 +53,10 @@ use crate::{ #[cfg(target_os = "macos")] use crate::ActivationPolicy; -pub(crate) type GlobalMenuEventListener = - Box crate::Result<()> + Send + Sync>; +pub(crate) type GlobalMenuEventListener = Box; pub(crate) type GlobalTrayIconEventListener = - Box crate::Result<()> + Send + Sync>; -pub(crate) type GlobalWindowEventListener = - Box) -> crate::Result<()> + Send + Sync>; + Box; +pub(crate) type GlobalWindowEventListener = Box) + Send + Sync>; /// Api exposed on the `ExitRequested` event. #[derive(Debug)] @@ -473,9 +471,7 @@ macro_rules! shared_app_impl { ($app: ty) => { impl $app { /// Registers a global menu event listener. - pub fn on_menu_event< - F: Fn(&AppHandle, MenuEvent) -> crate::Result<()> + Send + Sync + 'static, - >( + pub fn on_menu_event, MenuEvent) + Send + Sync + 'static>( &self, handler: F, ) { @@ -489,9 +485,7 @@ macro_rules! shared_app_impl { } /// Registers a global tray icon menu event listener. - pub fn on_tray_icon_event< - F: Fn(&AppHandle, TrayIconEvent) -> crate::Result<()> + Send + Sync + 'static, - >( + pub fn on_tray_icon_event, TrayIconEvent) + Send + Sync + 'static>( &self, handler: F, ) { @@ -849,7 +843,7 @@ impl App { /// .expect("error while building tauri application"); /// #[cfg(target_os = "macos")] /// app.set_activation_policy(tauri::ActivationPolicy::Accessory); - /// app.run(|_app_handle, _event| Ok(())); + /// app.run(|_app_handle, _event| {}); /// ``` #[cfg(target_os = "macos")] #[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))] @@ -878,7 +872,7 @@ impl App { /// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json")) /// .expect("error while building tauri application"); /// app.set_device_event_filter(tauri::DeviceEventFilter::Always); - /// app.run(|_app_handle, _event| Ok(())); + /// app.run(|_app_handle, _event| {}); /// ``` /// /// [`tao`]: https://crates.io/crates/tao @@ -898,20 +892,14 @@ impl App { /// // on an actual app, remove the string argument /// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json")) /// .expect("error while building tauri application"); - /// app.run(|_app_handle, event| { - /// match event { - /// tauri::RunEvent::ExitRequested { api, .. } => { - /// api.prevent_exit(); - /// } - /// _ => {} + /// app.run(|_app_handle, event| match event { + /// tauri::RunEvent::ExitRequested { api, .. } => { + /// api.prevent_exit(); /// } - /// Ok(()) + /// _ => {} /// }); /// ``` - pub fn run, RunEvent) -> crate::Result<()> + 'static>( - mut self, - mut callback: F, - ) { + pub fn run, RunEvent) + 'static>(mut self, mut callback: F) { let app_handle = self.handle(); let manager = self.manager.clone(); self.runtime.take().unwrap().run(move |event| match event { @@ -969,7 +957,7 @@ impl App { &app_handle, event, &manager, - Option::<&mut Box, RunEvent) -> crate::Result<()>>>::None, + Option::<&mut Box, RunEvent)>>::None, ) }) } @@ -1322,23 +1310,18 @@ impl Builder { /// # Examples /// ``` /// tauri::Builder::default() - /// .on_window_event(|event| { - /// match event.event() { - /// tauri::WindowEvent::Focused(focused) => { - /// // hide window whenever it loses focus - /// if !focused { - /// event.window().hide().unwrap(); - /// } + /// .on_window_event(|event| match event.event() { + /// tauri::WindowEvent::Focused(focused) => { + /// // hide window whenever it loses focus + /// if !focused { + /// event.window().hide().unwrap(); /// } - /// _ => {} /// } - /// Ok(()) + /// _ => {} /// }); /// ``` #[must_use] - pub fn on_window_event< - F: Fn(GlobalWindowEvent) -> crate::Result<()> + Send + Sync + 'static, - >( + pub fn on_window_event) + Send + Sync + 'static>( mut self, handler: F, ) -> Self { @@ -1571,7 +1554,7 @@ impl Builder { /// Runs the configured Tauri application. pub fn run(self, context: Context) -> crate::Result<()> { - self.build(context)?.run(|_, _| Ok(())); + self.build(context)?.run(|_, _| {}); Ok(()) } } @@ -1641,10 +1624,7 @@ fn setup(app: &mut App) -> crate::Result<()> { Ok(()) } -fn on_event_loop_event< - R: Runtime, - F: FnMut(&AppHandle, RunEvent) -> crate::Result<()> + 'static, ->( +fn on_event_loop_event, RunEvent) + 'static>( app_handle: &AppHandle, event: RuntimeRunEvent, manager: &WindowManager, @@ -1702,7 +1682,7 @@ fn on_event_loop_event< .lock() .unwrap() { - let _ = listener(app_handle, e); + listener(app_handle, e); } for (label, listener) in &*app_handle .manager @@ -1712,7 +1692,7 @@ fn on_event_loop_event< .unwrap() { if let Some(w) = app_handle.get_window(label) { - let _ = listener(&w, e); + listener(&w, e); } } } @@ -1724,7 +1704,7 @@ fn on_event_loop_event< .lock() .unwrap() { - let _ = listener(app_handle, e); + listener(app_handle, e); } for (id, listener) in &*app_handle @@ -1736,7 +1716,7 @@ fn on_event_loop_event< { if e.id == *id { if let Some(tray) = app_handle.tray_by_id(*id) { - let _ = listener(&tray, e); + listener(&tray, e); } } } @@ -1758,7 +1738,7 @@ fn on_event_loop_event< .on_event(app_handle, &event); if let Some(c) = callback { - let _ = c(app_handle, event); + c(app_handle, event); } } diff --git a/core/tauri/src/menu/builders/menu.rs b/core/tauri/src/menu/builders/menu.rs index 60c1bbd38e82..d0935c0b87fe 100644 --- a/core/tauri/src/menu/builders/menu.rs +++ b/core/tauri/src/menu/builders/menu.rs @@ -19,21 +19,21 @@ use crate::{menu::*, Icon, Manager, Runtime}; /// # height: 0, /// # }; /// # let icon2 = icon1.clone(); -/// let menu = MenuBuilder::new(&handle, "File") -/// .item(&MenuItem::new(&handle, "MenuItem 1", true, None))? +/// 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", icon2)? -/// .build(); +/// ]) +/// .separator() +/// .cut() +/// .copy() +/// .paste() +/// .separator() +/// .text("MenuItem 2") +/// .check("CheckMenuItem 2") +/// .icon("IconMenuItem 2", icon2) +/// .build()?; /// app.set_menu(menu); /// Ok(()) /// }); @@ -45,7 +45,7 @@ pub struct MenuBuilder<'m, R: Runtime, M: Manager> { impl<'m, R: Runtime, M: Manager> MenuBuilder<'m, R, M> { /// Create a new menu builder. - pub fn new>(manager: &'m M) -> Self { + pub fn new(manager: &'m M) -> Self { Self { items: Vec::new(), manager, diff --git a/core/tauri/src/menu/builders/submenu.rs b/core/tauri/src/menu/builders/submenu.rs index 66ab1dd22214..2bace5434201 100644 --- a/core/tauri/src/menu/builders/submenu.rs +++ b/core/tauri/src/menu/builders/submenu.rs @@ -21,21 +21,21 @@ use crate::{menu::*, Icon, Manager, Runtime}; /// # let icon2 = icon1.clone(); /// let menu = Menu::new(&handle); /// let submenu = SubmenuBuilder::new(&handle, "File") -/// .item(&MenuItem::new(&handle, "MenuItem 1", true, None))? +/// .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", icon2)? -/// .build(); -/// menu.append(&submenu); +/// ]) +/// .separator() +/// .cut() +/// .copy() +/// .paste() +/// .separator() +/// .text("MenuItem 2") +/// .check("CheckMenuItem 2") +/// .icon("IconMenuItem 2", icon2) +/// .build()?; +/// menu.append(&submenu)?; /// app.set_menu(menu); /// Ok(()) /// }); diff --git a/core/tauri/src/test/mod.rs b/core/tauri/src/test/mod.rs index 1a9fafa4c9b9..d88af801ea8d 100644 --- a/core/tauri/src/test/mod.rs +++ b/core/tauri/src/test/mod.rs @@ -279,7 +279,6 @@ mod tests { app.run(|_app, event| { println!("{:?}", event); - Ok(()) }); } } diff --git a/core/tauri/src/tray.rs b/core/tauri/src/tray.rs index 62ca821cf193..ae89602d4977 100644 --- a/core/tauri/src/tray.rs +++ b/core/tauri/src/tray.rs @@ -120,9 +120,7 @@ impl TrayIconBuilder { /// /// 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< - F: Fn(&AppHandle, MenuEvent) -> crate::Result<()> + Sync + Send + 'static, - >( + pub fn on_menu_event, MenuEvent) + Sync + Send + 'static>( mut self, f: F, ) -> Self { @@ -131,9 +129,7 @@ impl TrayIconBuilder { } /// Set a handler for this tray icon events. - pub fn on_tray_event< - F: Fn(&TrayIcon, TrayIconEvent) -> crate::Result<()> + Sync + Send + 'static, - >( + pub fn on_tray_event, TrayIconEvent) + Sync + Send + 'static>( mut self, f: F, ) -> Self { @@ -235,12 +231,7 @@ impl TrayIcon { /// /// 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< - F: Fn(&AppHandle, MenuEvent) -> crate::Result<()> + Sync + Send + 'static, - >( - &self, - f: F, - ) { + pub fn on_menu_event, MenuEvent) + Sync + Send + 'static>(&self, f: F) { self .app_handle .manager @@ -252,12 +243,7 @@ impl TrayIcon { } /// Register a handler for this tray icon events. - pub fn on_tray_event< - F: Fn(&TrayIcon, TrayIconEvent) -> crate::Result<()> + Sync + Send + 'static, - >( - &self, - f: F, - ) { + pub fn on_tray_event, TrayIconEvent) + Sync + Send + 'static>(&self, f: F) { self .app_handle .manager diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index 6061c3a13d8d..474db23b1ea7 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -338,8 +338,6 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { /// if event.id == save_menu_item.id() { /// // save menu item /// } - /// - /// Ok(()) /// }) /// .build() /// .unwrap(); @@ -347,9 +345,7 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { /// Ok(()) /// }); /// ``` - pub fn on_menu_event< - F: Fn(&Window, crate::menu::MenuEvent) -> crate::Result<()> + Send + Sync + 'static, - >( + pub fn on_menu_event, crate::menu::MenuEvent) + Send + Sync + 'static>( mut self, f: F, ) -> Self { @@ -1197,16 +1193,12 @@ impl Window { /// if event.id == save_menu_item.id() { /// // save menu item /// } - /// - /// Ok(()) /// }); /// /// Ok(()) /// }); /// ``` - pub fn on_menu_event< - F: Fn(&Window, crate::menu::MenuEvent) -> crate::Result<()> + Send + Sync + 'static, - >( + pub fn on_menu_event, crate::menu::MenuEvent) + Send + Sync + 'static>( &self, f: F, ) { From 6b99f3c674271f50dd26c4bb1ab804ade35ffe6c Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Thu, 3 Aug 2023 05:27:56 +0300 Subject: [PATCH 065/123] fix deadlock --- core/tauri/src/app.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 92324270041a..e7f387c61cd1 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -1538,13 +1538,8 @@ impl Builder { if let Some(tooltip) = &tray_config.tooltip { tray = tray.tooltip(tooltip); } - app - .manager - .inner - .tray_icons - .lock() - .unwrap() - .push(tray.build(&handle)?); + let tray = tray.build(&handle)?; + app.manager.inner.tray_icons.lock().unwrap().push(tray); } app.manager.initialize_plugins(&handle)?; From 794a68a27b90495d2127ba0d09f7276b66804667 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Thu, 3 Aug 2023 05:45:03 +0300 Subject: [PATCH 066/123] move window menu initalization out of tauri-runtime into tauri crate --- core/tauri-runtime-wry/src/lib.rs | 87 ++++++++++--------- core/tauri-runtime/Cargo.toml | 5 +- core/tauri-runtime/src/lib.rs | 41 +++------ core/tauri-runtime/src/webview.rs | 11 --- core/tauri-runtime/src/window.rs | 45 ++++------ core/tauri-runtime/src/window/dpi.rs | 27 ------ core/tauri/Cargo.toml | 6 +- core/tauri/src/app.rs | 57 +++++++++---- core/tauri/src/error.rs | 10 ++- core/tauri/src/manager.rs | 25 ++---- core/tauri/src/menu/builders/icon.rs | 2 +- core/tauri/src/menu/builders/mod.rs | 2 +- core/tauri/src/menu/builders/submenu.rs | 2 +- core/tauri/src/menu/check.rs | 2 +- core/tauri/src/menu/icon.rs | 14 ++-- core/tauri/src/menu/menu.rs | 6 +- core/tauri/src/menu/mod.rs | 38 +++++++-- core/tauri/src/menu/normal.rs | 2 +- core/tauri/src/menu/predefined.rs | 2 +- core/tauri/src/menu/submenu.rs | 4 +- core/tauri/src/test/mock_runtime.rs | 27 +++--- core/tauri/src/tray.rs | 22 ++--- core/tauri/src/window.rs | 106 ++++++++++++++++-------- 23 files changed, 282 insertions(+), 261 deletions(-) diff --git a/core/tauri-runtime-wry/src/lib.rs b/core/tauri-runtime-wry/src/lib.rs index 03c552d9f3f1..dbe432f77e36 100644 --- a/core/tauri-runtime-wry/src/lib.rs +++ b/core/tauri-runtime-wry/src/lib.rs @@ -14,12 +14,11 @@ use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle}; use tauri_runtime::{ http::{header::CONTENT_TYPE, Request as HttpRequest, RequestParts, Response as HttpResponse}, - menu, monitor::Monitor, webview::{WebviewIpcHandler, WindowBuilder, WindowBuilderBase}, window::{ dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}, - CursorIcon, DetachedWindow, FileDropEvent, PendingWindow, WindowEvent, + CursorIcon, DetachedWindow, FileDropEvent, PendingWindow, RawWindow, WindowEvent, }, DeviceEventFilter, Dispatch, Error, EventLoopProxy, ExitRequestedEventAction, Icon, Result, RunEvent, RunIteration, Runtime, RuntimeHandle, RuntimeInitArgs, UserAttentionType, UserEvent, @@ -190,7 +189,11 @@ 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 context = self.clone(); let window_id = rand::random(); @@ -200,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, + ) }), ), )?; @@ -535,7 +545,6 @@ pub struct WindowBuilderWrapper { center: bool, #[cfg(target_os = "macos")] tabbing_identifier: Option, - menu: Option, } impl std::fmt::Debug for WindowBuilderWrapper { @@ -630,11 +639,6 @@ impl WindowBuilder for WindowBuilderWrapper { window } - fn menu(mut self, menu: menu::Menu) -> Self { - self.menu.replace(menu); - self - } - fn center(mut self) -> Self { self.center = true; self @@ -843,14 +847,6 @@ impl WindowBuilder for WindowBuilderWrapper { fn has_icon(&self) -> bool { self.inner.window.window_icon.is_some() } - - fn has_menu(&self) -> bool { - self.menu.is_some() - } - - fn get_menu(&self) -> Option<&menu::Menu> { - self.menu.as_ref() - } } pub struct FileDropEventWrapper(WryFileDropEvent); @@ -1284,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<()> { @@ -1726,11 +1725,14 @@ 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<()> { @@ -1877,7 +1879,11 @@ 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 window_id = rand::random(); @@ -1887,6 +1893,7 @@ impl Runtime for Wry { &self.context.main_thread.web_context, self.context.clone(), pending, + before_webview_creation, )?; let dispatcher = WryDispatcher { @@ -2572,12 +2579,13 @@ pub fn center_window(window: &Window, window_size: WryPhysicalSize) -> Resu } } -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 { @@ -2620,26 +2628,29 @@ fn create_webview( let is_window_transparent = window_builder.inner.window.transparent; let window = window_builder.inner.build(event_loop).unwrap(); - #[cfg(not(target_os = "macos"))] - if let Some(menu) = window_builder.menu { - #[cfg(windows)] - let _ = menu.init_for_hwnd(window.hwnd() as _); - #[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ))] - let _ = menu.init_for_gtk_window(window.gtk_window(), window.default_vbox()); - } - webview_id_map.insert(window.id(), window_id); 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(), + _marker: &std::marker::PhantomData, + }; + handler(raw); + } + let mut webview_builder = WebViewBuilder::new(window) .map_err(|e| Error::CreateWebview(Box::new(e)))? .with_url(&url) diff --git a/core/tauri-runtime/Cargo.toml b/core/tauri-runtime/Cargo.toml index f96fd175deb7..1773ee83e648 100644 --- a/core/tauri-runtime/Cargo.toml +++ b/core/tauri-runtime/Cargo.toml @@ -33,8 +33,6 @@ http-range = "0.1.4" raw-window-handle = "0.5" rand = "0.8" url = { version = "2" } -muda = { version = "0.7", default-features = false } -tray-icon = { version = "0.7", default-features = false } [target."cfg(windows)".dependencies.windows] version = "0.48" @@ -52,5 +50,4 @@ url = "2" [features] devtools = [ ] macos-private-api = [ ] -libxdo = ["tray-icon/libxdo", "muda/libxdo"] -common-controls-v6 = ["tray-icon/common-controls-v6", "muda/common-controls-v6"] + diff --git a/core/tauri-runtime/src/lib.rs b/core/tauri-runtime/src/lib.rs index 5567d65cdb43..f976b9e75e0b 100644 --- a/core/tauri-runtime/src/lib.rs +++ b/core/tauri-runtime/src/lib.rs @@ -19,9 +19,6 @@ use tauri_utils::Theme; use url::Url; use uuid::Uuid; -pub use muda as menu; -pub use tray_icon as tray; - pub mod http; /// Types useful for interacting with a user's monitors. pub mod monitor; @@ -32,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::{ @@ -114,14 +111,6 @@ pub enum Error { Infallible(#[from] std::convert::Infallible), #[error("the event loop has been closed")] EventLoopClosed, - #[error(transparent)] - BadMenuIcon(#[from] menu::icon::BadIcon), - #[error(transparent)] - BadTrayIcon(#[from] tray::icon::BadIcon), - #[error(transparent)] - MenuError(#[from] menu::Error), - #[error(transparent)] - TrayError(#[from] tray::Error), } /// Result type. @@ -138,22 +127,6 @@ pub struct Icon { pub height: u32, } -impl TryFrom for menu::icon::Icon { - type Error = Error; - - fn try_from(value: Icon) -> std::result::Result { - menu::icon::Icon::from_rgba(value.rgba, value.width, value.height).map_err(Into::into) - } -} - -impl TryFrom for tray::icon::Icon { - type Error = Error; - - fn try_from(value: Icon) -> std::result::Result { - tray::icon::Icon::from_rgba(value.rgba, value.width, value.height).map_err(Into::into) - } -} - /// A type that can be used as an user event. pub trait UserEvent: Debug + Clone + Send + 'static {} @@ -225,9 +198,10 @@ 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. @@ -302,7 +276,11 @@ pub trait Runtime: Debug + Sized + 'static { fn handle(&self) -> Self::Handle; /// Create a new webview window. - fn create_window(&self, pending: PendingWindow) -> Result>; + fn create_window( + &self, + pending: PendingWindow, + before_webview_creation: Option, + ) -> Result>; fn primary_monitor(&self) -> Option; fn available_monitors(&self) -> Vec; @@ -492,9 +470,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. diff --git a/core/tauri-runtime/src/webview.rs b/core/tauri-runtime/src/webview.rs index e24cc28dc954..79814a4f8071 100644 --- a/core/tauri-runtime/src/webview.rs +++ b/core/tauri-runtime/src/webview.rs @@ -6,7 +6,6 @@ use crate::{window::DetachedWindow, Icon}; -use muda::Menu; #[cfg(target_os = "macos")] use tauri_utils::TitleBarStyle; use tauri_utils::{ @@ -154,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: crate::menu::Menu) -> Self; - /// Show window in the center of the screen. #[must_use] fn center(self) -> Self; @@ -331,12 +326,6 @@ pub trait WindowBuilder: WindowBuilderBase { /// Whether the icon was set or not. fn has_icon(&self) -> bool; - - /// Whether the menu was set or not. - fn has_menu(&self) -> bool; - - /// Whether the menu was set or not. - 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 3e363be4a858..08c7ca739b98 100644 --- a/core/tauri-runtime/src/window.rs +++ b/core/tauri-runtime/src/window.rs @@ -17,6 +17,7 @@ use url::Url; use std::{ collections::HashMap, hash::{Hash, Hasher}, + marker::PhantomData, path::PathBuf, sync::mpsc::Sender, }; @@ -224,9 +225,6 @@ pub struct PendingWindow> { Option) -> Result<(), jni::errors::Error> + Send>>, pub web_resource_request_handler: Option>, - - /// Whether the menu set in the builder is app-wide - pub has_app_wide_menu: bool, } pub fn is_label_valid(label: &str) -> bool { @@ -242,28 +240,6 @@ pub fn assert_label_is_valid(label: &str) { ); } -#[cfg(not(target_os = "macos"))] -impl> PendingWindow { - #[must_use] - pub fn set_menu(mut self, menu: crate::menu::Menu) -> Self { - self.window_builder = self.window_builder.menu(menu); - self - } - - #[must_use] - pub fn set_app_menu(mut self, menu: crate::menu::Menu) -> Self { - if !self.window_builder.has_menu() { - self.window_builder = self.window_builder.menu(menu); - self.has_app_wide_menu = true; - } - self - } - - pub fn menu(&self) -> Option<&crate::menu::Menu> { - self.window_builder.get_menu() - } -} - impl> PendingWindow { /// Create a new [`PendingWindow`] with a label and starting url. pub fn new( @@ -286,7 +262,6 @@ impl> PendingWindow { #[cfg(target_os = "android")] on_webview_created: None, web_resource_request_handler: Default::default(), - has_app_wide_menu: false, }) } } @@ -315,7 +290,6 @@ impl> PendingWindow { #[cfg(target_os = "android")] on_webview_created: None, web_resource_request_handler: Default::default(), - has_app_wide_menu: false, }) } } @@ -379,3 +353,20 @@ 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: *mut std::ffi::c_void, + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + pub gtk_window: &'a gtk::ApplicationWindow, + pub _marker: &'a PhantomData<()>, +} diff --git a/core/tauri-runtime/src/window/dpi.rs b/core/tauri-runtime/src/window/dpi.rs index 25435c00820c..0710db98dbc6 100644 --- a/core/tauri-runtime/src/window/dpi.rs +++ b/core/tauri-runtime/src/window/dpi.rs @@ -399,30 +399,3 @@ impl From> for Position { Position::Logical(position.cast()) } } - -impl

From> for crate::menu::LogicalPosition

{ - fn from(value: LogicalPosition

) -> Self { - Self { - x: value.x, - y: value.y, - } - } -} - -impl

From> for crate::menu::PhysicalPosition

{ - fn from(value: PhysicalPosition

) -> Self { - Self { - x: value.x, - y: value.y, - } - } -} - -impl From for crate::menu::Position { - fn from(value: Position) -> Self { - match value { - Position::Physical(p) => Self::Physical(p.into()), - Position::Logical(p) => Self::Logical(p.into()), - } - } -} diff --git a/core/tauri/Cargo.toml b/core/tauri/Cargo.toml index c2b51386f62e..2838cf7ec6bc 100644 --- a/core/tauri/Cargo.toml +++ b/core/tauri/Cargo.toml @@ -65,6 +65,8 @@ serialize-to-javascript = "=0.1.1" infer = { version = "0.9", optional = true } png = { version = "0.17", optional = true } ico = { version = "0.2.0", optional = true } +muda = { version = "0.7", default-features = false } +tray-icon = { version = "0.7", default-features = false } [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" ] } @@ -112,13 +114,13 @@ tokio = { version = "1", features = [ "full" ] } cargo_toml = "0.15" [features] -default = [ "wry", "compression", "objc-exception", "tauri-runtime/common-controls-v6" ] +default = [ "wry", "compression", "objc-exception", "tray-icon/common-controls-v6", "muda/common-controls-v6" ] test = [ ] compression = [ "tauri-macros/compression", "tauri-utils/compression" ] wry = [ "tauri-runtime-wry" ] objc-exception = [ "tauri-runtime-wry/objc-exception" ] linux-protocol-headers = [ "tauri-runtime-wry/linux-headers" ] -linux-libxdo = [ "tauri-runtime/libxdo", ] +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" ] diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index e7f387c61cd1..df392fd12eab 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -20,6 +20,7 @@ use crate::{ sealed::{ManagerBase, RuntimeOrDispatch}, utils::config::Config, utils::{assets::Assets, Env}, + window::WindowMenu, Context, DeviceEventFilter, EventLoopMessage, Icon, Invoke, InvokeError, InvokeResponse, Manager, Monitor, Runtime, Scopes, StateManager, Theme, Window, }; @@ -33,7 +34,7 @@ use tauri_macros::default_runtime; use tauri_runtime::{ window::{ dpi::{PhysicalPosition, PhysicalSize}, - FileDropEvent, + FileDropEvent, RawWindow, }, EventLoopProxy, RuntimeInitArgs, }; @@ -616,7 +617,7 @@ macro_rules! shared_app_impl { #[cfg(not(target_os = "macos"))] for window in self.manager.windows_lock().values() { let mut window_menu = window.menu_lock(); - if window_menu.as_ref().map(|m| m.0).unwrap_or(true) { + if window_menu.as_ref().map(|m| m.is_app_wide).unwrap_or(true) { #[cfg(not(target_os = "macos"))] let window = window.clone(); let menu_ = menu.clone(); @@ -639,7 +640,10 @@ macro_rules! shared_app_impl { .init_for_gtk_window(>k_window, Some(>k_box)); } })?; - window_menu.replace((true, menu.clone())); + window_menu.replace(WindowMenu { + is_app_wide: true, + menu: menu.clone(), + }); } } @@ -1578,23 +1582,44 @@ fn setup(app: &mut App) -> crate::Result<()> { let manager = app.manager(); for pending in pending_windows { - let pending = manager.prepare_window(app_handle.clone(), pending, &window_labels)?; - #[cfg(not(target_os = "macos"))] - let menu = pending.menu().cloned().map(|m| { - ( - pending.has_app_wide_menu, - Menu { - id: m.id(), - inner: m, - app_handle: app_handle.clone(), - }, - ) + let menu = app.manager.menu_lock().as_ref().map(|m| WindowMenu { + is_app_wide: true, + menu: m.clone(), }); + let pending = manager.prepare_window( + app_handle.clone(), + pending, + &window_labels, + #[cfg(not(target_os = "macos"))] + menu.as_ref().map(|m| m.menu.clone()), + )?; + + #[cfg(target_os = "macos")] + let handler = None; + #[cfg(not(target_os = "macos"))] + let handler = if let Some(menu) = &menu { + let menu = menu.menu.clone(); + Some(move |raw: RawWindow<'_>| { + #[cfg(target_os = "windows")] + let _ = menu.inner().init_for_hwnd(raw.hwnd as _).unwrap(); + #[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); + }) + } else { + None + }; + let window_effects = pending.webview_attributes.window_effects.clone(); let detached = if let RuntimeOrDispatch::RuntimeHandle(runtime) = app_handle.runtime() { - runtime.create_window(pending)? + runtime.create_window(pending, handler)? } else { // the AppHandle's runtime is always RuntimeOrDispatch::RuntimeHandle unreachable!() @@ -1603,7 +1628,7 @@ fn setup(app: &mut App) -> crate::Result<()> { app_handle.clone(), detached, #[cfg(not(target_os = "macos"))] - menu, + None, ); if let Some(effects) = window_effects { diff --git a/core/tauri/src/error.rs b/core/tauri/src/error.rs index f5749b5a04b8..ae18895cf439 100644 --- a/core/tauri/src/error.rs +++ b/core/tauri/src/error.rs @@ -94,8 +94,14 @@ pub enum Error { FailedToReceiveMessage, /// Menu error. #[error("menu error: {0}")] - Menu(#[from] tauri_runtime::menu::Error), + Menu(#[from] muda::Error), + /// Bad menu icon error. + #[error(transparent)] + BadMenuIcon(#[from] muda::icon::BadIcon), /// Tray icon error. #[error("tray icon error: {0}")] - Tray(#[from] tauri_runtime::tray::Error), + Tray(#[from] tray_icon::Error), + /// Bad tray icon error. + #[error(transparent)] + BadTrayIcon(#[from] tray_icon::icon::BadIcon), } diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index 4af20d93e42a..edbb791fcec7 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -10,8 +10,8 @@ use std::{ sync::{Arc, Mutex, MutexGuard}, }; -use crate::menu::Menu; use crate::tray::TrayIcon; +use crate::{menu::Menu, window::WindowMenu}; use serde::Serialize; use serde_json::Value as JsonValue; use serialize_to_javascript::{default_template, DefaultTemplate, Template}; @@ -1034,6 +1034,7 @@ impl WindowManager { app_handle: AppHandle, mut pending: PendingWindow, window_labels: &[String], + #[cfg(not(target_os = "macos"))] menu: Option>, ) -> crate::Result> { if self.windows_lock().contains_key(&pending.label) { return Err(crate::Error::WindowLabelAlreadyExists(pending.label)); @@ -1192,30 +1193,20 @@ impl WindowManager { #[cfg(not(target_os = "macos"))] { - if let Some(menu) = &*self.menu_lock() { - pending = pending.set_app_menu(menu.inner().clone()); - } - - if let Some(menu) = pending.menu() { - self.inner.menus.lock().unwrap().insert( - menu.id(), - Menu { - id: menu.id(), - inner: menu.clone(), - app_handle, - }, - ); + let menu = menu.or_else(|| self.menu_lock().clone()); + if let Some(menu) = menu { + self.inner.menus.lock().unwrap().insert(menu.id(), menu); } } Ok(pending) } - pub fn attach_window( + pub(crate) fn attach_window( &self, app_handle: AppHandle, window: DetachedWindow, - #[cfg(not(target_os = "macos"))] menu: Option<(bool, Menu)>, + #[cfg(not(target_os = "macos"))] menu: Option>, ) -> Window { let window = Window::new( self.clone(), @@ -1231,7 +1222,7 @@ impl WindowManager { window.on_window_event(move |event| { let _ = on_window_event(&window_, &manager, event); for handler in window_event_listeners.iter() { - let _ = handler(GlobalWindowEvent { + handler(GlobalWindowEvent { window: window_.clone(), event: event.clone(), }); diff --git a/core/tauri/src/menu/builders/icon.rs b/core/tauri/src/menu/builders/icon.rs index 74ed18e36511..c69bb2ecc051 100644 --- a/core/tauri/src/menu/builders/icon.rs +++ b/core/tauri/src/menu/builders/icon.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use tauri_runtime::menu::icon::NativeIcon; +use muda::icon::NativeIcon; use crate::{menu::IconMenuItem, Icon, Manager, Runtime}; diff --git a/core/tauri/src/menu/builders/mod.rs b/core/tauri/src/menu/builders/mod.rs index 80f57d005588..b1ddd0e42f8a 100644 --- a/core/tauri/src/menu/builders/mod.rs +++ b/core/tauri/src/menu/builders/mod.rs @@ -4,7 +4,7 @@ //! A module containting menu builder types -pub use crate::runtime::menu::builders::AboutMetadataBuilder; +pub use muda::builders::AboutMetadataBuilder; mod menu; pub use menu::MenuBuilder; diff --git a/core/tauri/src/menu/builders/submenu.rs b/core/tauri/src/menu/builders/submenu.rs index 2bace5434201..a9dcd119385a 100644 --- a/core/tauri/src/menu/builders/submenu.rs +++ b/core/tauri/src/menu/builders/submenu.rs @@ -53,7 +53,7 @@ impl<'m, R: Runtime, M: Manager> SubmenuBuilder<'m, R, M> { Self { items: Vec::new(), text: text.as_ref().to_string(), - enabled: false, + enabled: true, manager, } } diff --git a/core/tauri/src/menu/check.rs b/core/tauri/src/menu/check.rs index 7b3f9c072d2c..5c35d6e8c15a 100644 --- a/core/tauri/src/menu/check.rs +++ b/core/tauri/src/menu/check.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use crate::{run_main_thread, runtime::menu as muda, AppHandle, Manager, Runtime}; +use crate::{run_main_thread, AppHandle, Manager, Runtime}; /// A menu item inside a [`Menu`] or [`Submenu`] and contains only text. /// diff --git a/core/tauri/src/menu/icon.rs b/core/tauri/src/menu/icon.rs index c47a685eec1f..b9a07f2e1051 100644 --- a/core/tauri/src/menu/icon.rs +++ b/core/tauri/src/menu/icon.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: MIT use super::NativeIcon; -use crate::{run_main_thread, runtime::menu as muda, AppHandle, Icon, Manager, Runtime}; +use crate::{run_main_thread, AppHandle, Icon, Manager, Runtime}; /// A menu item inside a [`Menu`] or [`Submenu`] and contains only text. /// @@ -62,9 +62,7 @@ impl IconMenuItem { let item = muda::IconMenuItem::new( text, enabled, - icon - .and_then(|i| -> Option { i.try_into().ok() }) - .and_then(|i| i.try_into().ok()), + icon.and_then(|i| i.try_into().ok()), acccelerator.and_then(|s| s.as_ref().parse().ok()), ); Self { @@ -142,11 +140,9 @@ impl IconMenuItem { /// 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| -> Option { i.try_into().ok() }) - .and_then(|i| i.try_into().ok()) - )) + 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. diff --git a/core/tauri/src/menu/menu.rs b/core/tauri/src/menu/menu.rs index dd9915dc22dd..aacd559a69b5 100644 --- a/core/tauri/src/menu/menu.rs +++ b/core/tauri/src/menu/menu.rs @@ -3,9 +3,9 @@ // SPDX-License-Identifier: MIT use super::{IsMenuItem, MenuItemKind, PredefinedMenuItem, Submenu}; -use crate::{run_main_thread, runtime::menu as muda, AppHandle, Manager, Position, Runtime}; +use crate::{run_main_thread, AppHandle, Manager, Position, Runtime}; +use muda::AboutMetadata; use muda::ContextMenu; -use tauri_runtime::menu::AboutMetadata; /// 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. @@ -37,7 +37,7 @@ impl super::ContextMenu for Menu { window: crate::Window, position: Option

, ) -> crate::Result<()> { - let position = position.map(Into::into).map(Into::into); + let position = position.map(Into::into).map(super::into_position); run_main_thread!(self, |self_: Self| { #[cfg(target_os = "macos")] if let Ok(view) = window.ns_view() { diff --git a/core/tauri/src/menu/mod.rs b/core/tauri/src/menu/mod.rs index 41d4cdc42637..e7cc25a004a9 100644 --- a/core/tauri/src/menu/mod.rs +++ b/core/tauri/src/menu/mod.rs @@ -22,10 +22,8 @@ pub use normal::MenuItem; pub use predefined::PredefinedMenuItem; pub use submenu::Submenu; -pub use crate::runtime::menu::{icon::NativeIcon, AboutMetadata, MenuEvent}; use crate::Runtime; - -use crate::runtime::menu as muda; +pub use muda::{icon::NativeIcon, AboutMetadata, MenuEvent}; /// An enumeration of all menu item kinds that could be added to /// a [`Menu`] or [`Submenu`] @@ -205,11 +203,39 @@ pub trait ContextMenu: sealed::ContextMenuBase + Send + Sync { pub(crate) mod sealed { pub trait IsMenuItemBase { - fn inner(&self) -> &dyn super::muda::IsMenuItem; + fn inner(&self) -> &dyn muda::IsMenuItem; } pub trait ContextMenuBase { - fn inner(&self) -> &dyn super::muda::ContextMenu; - fn inner_owned(&self) -> Box; + fn inner(&self) -> &dyn muda::ContextMenu; + fn inner_owned(&self) -> Box; + } +} + +impl TryFrom for muda::icon::Icon { + type Error = crate::Error; + + fn try_from(value: crate::Icon) -> Result { + let value: crate::runtime::Icon = value.try_into()?; + muda::icon::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 index dbd115be292b..fe394e1e2aa3 100644 --- a/core/tauri/src/menu/normal.rs +++ b/core/tauri/src/menu/normal.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use crate::{run_main_thread, runtime::menu as muda, AppHandle, Manager, Runtime}; +use crate::{run_main_thread, AppHandle, Manager, Runtime}; /// A menu item inside a [`Menu`] or [`Submenu`] and contains only text. /// diff --git a/core/tauri/src/menu/predefined.rs b/core/tauri/src/menu/predefined.rs index ef9e369a7640..3e64d00b43e2 100644 --- a/core/tauri/src/menu/predefined.rs +++ b/core/tauri/src/menu/predefined.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: MIT use super::AboutMetadata; -use crate::{run_main_thread, runtime::menu as muda, AppHandle, Manager, Runtime}; +use crate::{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 { diff --git a/core/tauri/src/menu/submenu.rs b/core/tauri/src/menu/submenu.rs index bdb7dcba8727..078d38a2e7d4 100644 --- a/core/tauri/src/menu/submenu.rs +++ b/core/tauri/src/menu/submenu.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: MIT use super::{IsMenuItem, MenuItemKind}; -use crate::{run_main_thread, runtime::menu as muda, AppHandle, Manager, Position, Runtime}; +use crate::{run_main_thread, AppHandle, Manager, Position, Runtime}; use muda::ContextMenu; /// A type that is a submenu inside a [`Menu`] or [`Submenu`] @@ -54,7 +54,7 @@ impl super::ContextMenu for Submenu { window: crate::Window, position: Option

, ) -> crate::Result<()> { - let position = position.map(Into::into).map(Into::into); + 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() { diff --git a/core/tauri/src/test/mock_runtime.rs b/core/tauri/src/test/mock_runtime.rs index 301564274d72..da8954e7f4f6 100644 --- a/core/tauri/src/test/mock_runtime.rs +++ b/core/tauri/src/test/mock_runtime.rs @@ -6,12 +6,11 @@ #![allow(missing_docs)] use tauri_runtime::{ - menu::Menu, monitor::Monitor, webview::{WindowBuilder, WindowBuilderBase}, window::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, - CursorIcon, DetachedWindow, PendingWindow, WindowEvent, + CursorIcon, DetachedWindow, PendingWindow, RawWindow, WindowEvent, }, DeviceEventFilter, Dispatch, Error, EventLoopProxy, ExitRequestedEventAction, Icon, Result, RunEvent, Runtime, RuntimeHandle, RuntimeInitArgs, UserAttentionType, UserEvent, @@ -101,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); @@ -207,10 +207,6 @@ impl WindowBuilder for MockWindowBuilder { Self {} } - fn menu(self, menu: Menu) -> Self { - self - } - fn center(self) -> Self { self } @@ -341,14 +337,6 @@ impl WindowBuilder for MockWindowBuilder { fn has_icon(&self) -> bool { false } - - fn has_menu(&self) -> bool { - false - } - - fn get_menu(&self) -> Option<&Menu> { - None - } } impl Dispatch for MockDispatcher { @@ -525,9 +513,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); @@ -740,7 +729,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 { diff --git a/core/tauri/src/tray.rs b/core/tauri/src/tray.rs index ae89602d4977..1f0052152084 100644 --- a/core/tauri/src/tray.rs +++ b/core/tauri/src/tray.rs @@ -5,8 +5,8 @@ //! Tray icon types and utility functions use crate::app::{GlobalMenuEventListener, GlobalTrayIconEventListener}; +use crate::menu::ContextMenu; use crate::menu::MenuEvent; -use crate::{menu::ContextMenu, runtime::tray as tray_icon}; use crate::{run_main_thread, AppHandle, Icon, Manager, Runtime}; use std::path::Path; pub use tray_icon::{ClickType, Rectangle, TrayIconEvent}; @@ -60,10 +60,7 @@ impl TrayIconBuilder { /// - **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() - .and_then(|i: crate::runtime::Icon| i.try_into().ok()); + let icon = icon.try_into().ok(); if let Some(icon) = icon { self.inner = self.inner.with_icon(icon); } @@ -261,11 +258,7 @@ impl TrayIcon { /// 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() - .and_then(|i: crate::runtime::Icon| i.try_into().ok()) - }); + 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) } @@ -340,3 +333,12 @@ impl TrayIcon { Ok(()) } } + +impl TryFrom for tray_icon::icon::Icon { + type Error = crate::Error; + + fn try_from(value: Icon) -> Result { + let value: crate::runtime::Icon = value.try_into()?; + tray_icon::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 474db23b1ea7..195c959c378b 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -5,6 +5,7 @@ //! The Tauri window types and functions. use crate::{app::GlobalMenuEventListener, menu::ContextMenu}; +use tauri_runtime::window::RawWindow; pub use tauri_utils::{config::Color, WindowEffect as Effect, WindowEffectState as EffectState}; use url::Url; @@ -117,6 +118,7 @@ pub struct WindowBuilder<'a, R: Runtime> { app_handle: AppHandle, label: String, pub(crate) window_builder: >::WindowBuilder, + pub(crate) menu: Option>, pub(crate) webview_attributes: WebviewAttributes, web_resource_request_handler: Option>, navigation_handler: Option>, @@ -192,6 +194,7 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { app_handle, label: label.into(), window_builder: >::WindowBuilder::new(), + menu: None, webview_attributes: WebviewAttributes::new(url), web_resource_request_handler: None, navigation_handler: None, @@ -233,6 +236,7 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { config, ), web_resource_request_handler: None, + menu: None, navigation_handler: None, on_menu_event: None, }; @@ -363,28 +367,50 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { pending.navigation_handler = self.navigation_handler.take(); pending.web_resource_request_handler = self.web_resource_request_handler.take(); + #[cfg(not(target_os = "macos"))] + let menu = { + let is_app_wide = self.menu.is_none(); + self + .menu + .or_else(|| self.app_handle.menu()) + .map(|menu| WindowMenu { is_app_wide, menu }) + }; + let labels = self.manager.labels().into_iter().collect::>(); - let pending = self - .manager - .prepare_window(self.app_handle.clone(), pending, &labels)?; + let pending = self.manager.prepare_window( + self.app_handle.clone(), + pending, + &labels, + #[cfg(not(target_os = "macos"))] + menu.as_ref().map(|m| m.menu.clone()), + )?; + #[cfg(target_os = "macos")] + let handler = None; #[cfg(not(target_os = "macos"))] - let menu = pending.menu().cloned().map(|m| { - ( - pending.has_app_wide_menu, - Menu { - id: m.id(), - inner: m, - app_handle: self.app_handle.clone(), - }, - ) - }); + let handler = if let Some(menu) = &menu { + let menu = menu.menu.clone(); + Some(move |raw: 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); + }) + } else { + 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( @@ -426,7 +452,7 @@ 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.inner().clone()); + self.menu.replace(menu); self } @@ -838,6 +864,13 @@ struct JsEventListenerKey { pub event: String, } +/// A wrapper struct to hold the window menu state +/// and whether it is global per-app or specific to this window. +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. /// @@ -854,7 +887,7 @@ pub struct Window { // The menu set for this window #[cfg(not(target_os = "macos"))] #[allow(clippy::type_complexity)] - pub(crate) menu: Arc)>>>, + pub(crate) menu: Arc>>>, } impl std::fmt::Debug for Window { @@ -1029,7 +1062,7 @@ impl Window { manager: WindowManager, window: DetachedWindow, app_handle: AppHandle, - #[cfg(not(target_os = "macos"))] menu: Option<(bool, Menu)>, + #[cfg(not(target_os = "macos"))] menu: Option>, ) -> Self { Self { window, @@ -1211,25 +1244,29 @@ impl Window { .insert(self.label().to_string(), Box::new(f)); } - pub(crate) fn menu_lock(&self) -> std::sync::MutexGuard<'_, Option<(bool, Menu)>> { + pub(crate) fn menu_lock(&self) -> std::sync::MutexGuard<'_, Option>> { self.menu.lock().expect("poisoned window") } pub(crate) fn has_app_wide_menu(&self) -> bool { - self.menu_lock().as_ref().map(|m| m.0).unwrap_or(false) + self + .menu_lock() + .as_ref() + .map(|m| m.is_app_wide) + .unwrap_or(false) } pub(crate) fn is_menu_in_use(&self, id: u32) -> bool { self .menu_lock() .as_ref() - .map(|m| m.1.id() == id) + .map(|m| m.menu.id() == id) .unwrap_or(false) } /// Returns this window menu . pub fn menu(&self) -> Option> { - self.menu_lock().as_ref().map(|m| m.1.clone()) + self.menu_lock().as_ref().map(|m| m.menu.clone()) } /// Sets the window menu and returns the previous one. @@ -1264,7 +1301,10 @@ impl Window { } })?; - self.menu_lock().replace((false, menu)); + self.menu_lock().replace(WindowMenu { + is_app_wide: false, + menu, + }); Ok(prev_menu) } @@ -1279,9 +1319,9 @@ impl Window { let mut current_menu = self.menu_lock(); // remove from the window - if let Some((_, menu)) = &*current_menu { + if let Some(window_menu) = &*current_menu { let window = self.clone(); - let menu_ = menu.clone(); + let menu_ = window_menu.menu.clone(); self.run_on_main_thread(move || { #[cfg(windows)] if let Ok(hwnd) = window.hwnd() { @@ -1300,7 +1340,7 @@ impl Window { })?; } - let prev_menu = current_menu.take().map(|m| m.1); + let prev_menu = current_menu.take().map(|m| m.menu); drop(current_menu); @@ -1314,9 +1354,9 @@ impl Window { /// Hides the window menu. pub fn hide_menu(&self) -> crate::Result<()> { // remove from the window - if let Some((_, menu)) = &*self.menu_lock() { + if let Some(window_menu) = &*self.menu_lock() { let window = self.clone(); - let menu_ = menu.clone(); + let menu_ = window_menu.menu.clone(); self.run_on_main_thread(move || { #[cfg(windows)] if let Ok(hwnd) = window.hwnd() { @@ -1341,9 +1381,9 @@ impl Window { /// Shows the window menu. pub fn show_menu(&self) -> crate::Result<()> { // remove from the window - if let Some((_, menu)) = &*self.menu_lock() { + if let Some(window_menu) = &*self.menu_lock() { let window = self.clone(); - let menu_ = menu.clone(); + let menu_ = window_menu.menu.clone(); self.run_on_main_thread(move || { #[cfg(windows)] if let Ok(hwnd) = window.hwnd() { @@ -1368,10 +1408,10 @@ impl Window { /// Shows the window menu. pub fn is_menu_visible(&self) -> crate::Result { // remove from the window - if let Some((_, menu)) = &*self.menu_lock() { + if let Some(window_menu) = &*self.menu_lock() { let (tx, rx) = std::sync::mpsc::channel(); let window = self.clone(); - let menu_ = menu.clone(); + let menu_ = window_menu.menu.clone(); self.run_on_main_thread(move || { #[cfg(windows)] if let Ok(hwnd) = window.hwnd() { From 209ba582a25b539848dfd691d3be9444bcdcbd9f Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Thu, 3 Aug 2023 05:53:16 +0300 Subject: [PATCH 067/123] dedup logic --- core/tauri/src/app.rs | 35 ++++------------------------ core/tauri/src/manager.rs | 49 ++++++++++++++++++++++++++++++++------- core/tauri/src/window.rs | 42 ++++++++------------------------- 3 files changed, 55 insertions(+), 71 deletions(-) diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index df392fd12eab..ff8498b5b063 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -34,7 +34,7 @@ use tauri_macros::default_runtime; use tauri_runtime::{ window::{ dpi::{PhysicalPosition, PhysicalSize}, - FileDropEvent, RawWindow, + FileDropEvent, }, EventLoopProxy, RuntimeInitArgs, }; @@ -1582,40 +1582,15 @@ fn setup(app: &mut App) -> crate::Result<()> { let manager = app.manager(); for pending in pending_windows { + let pending = manager.prepare_window(app_handle.clone(), pending, &window_labels)?; + #[cfg(not(target_os = "macos"))] - let menu = app.manager.menu_lock().as_ref().map(|m| WindowMenu { + let window_menu = app.manager.menu_lock().as_ref().map(|m| WindowMenu { is_app_wide: true, menu: m.clone(), }); - let pending = manager.prepare_window( - app_handle.clone(), - pending, - &window_labels, - #[cfg(not(target_os = "macos"))] - menu.as_ref().map(|m| m.menu.clone()), - )?; - - #[cfg(target_os = "macos")] - let handler = None; - #[cfg(not(target_os = "macos"))] - let handler = if let Some(menu) = &menu { - let menu = menu.menu.clone(); - Some(move |raw: RawWindow<'_>| { - #[cfg(target_os = "windows")] - let _ = menu.inner().init_for_hwnd(raw.hwnd as _).unwrap(); - #[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); - }) - } else { - None - }; + let handler = manager.create_webview_before_creation_handler(window_menu.as_ref()); let window_effects = pending.webview_attributes.window_effects.clone(); let detached = if let RuntimeOrDispatch::RuntimeHandle(runtime) = app_handle.runtime() { diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index edbb791fcec7..3280f509ac20 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -15,6 +15,7 @@ use crate::{menu::Menu, window::WindowMenu}; use serde::Serialize; use serde_json::Value as JsonValue; use serialize_to_javascript::{default_template, DefaultTemplate, Template}; +use tauri_runtime::window::RawWindow; use url::Url; use tauri_macros::default_runtime; @@ -370,6 +371,45 @@ impl WindowManager { self.inner.state.clone() } + pub(crate) fn create_webview_before_creation_handler( + &self, + window_menu: Option<&WindowMenu>, + ) -> Option)> { + #[cfg(not(target_os = "macos"))] + { + if let Some(menu) = window_menu { + self + .inner + .menus + .lock() + .unwrap() + .insert(menu.menu.id(), menu.menu.clone()); + } + } + + #[cfg(target_os = "macos")] + return None; + + #[cfg(not(target_os = "macos"))] + if let Some(menu) = &window_menu { + let menu = menu.menu.clone(); + Some(move |raw: 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); + }) + } else { + None + } + } + /// App-wide menu. pub(crate) fn menu_lock(&self) -> MutexGuard<'_, Option>> { self.inner.menu.lock().expect("poisoned window manager") @@ -1034,7 +1074,6 @@ impl WindowManager { app_handle: AppHandle, mut pending: PendingWindow, window_labels: &[String], - #[cfg(not(target_os = "macos"))] menu: Option>, ) -> crate::Result> { if self.windows_lock().contains_key(&pending.label) { return Err(crate::Error::WindowLabelAlreadyExists(pending.label)); @@ -1191,14 +1230,6 @@ impl WindowManager { } })); - #[cfg(not(target_os = "macos"))] - { - let menu = menu.or_else(|| self.menu_lock().clone()); - if let Some(menu) = menu { - self.inner.menus.lock().unwrap().insert(menu.id(), menu); - } - } - Ok(pending) } diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index 195c959c378b..24c9360f4409 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -5,7 +5,6 @@ //! The Tauri window types and functions. use crate::{app::GlobalMenuEventListener, menu::ContextMenu}; -use tauri_runtime::window::RawWindow; pub use tauri_utils::{config::Color, WindowEffect as Effect, WindowEffectState as EffectState}; use url::Url; @@ -367,8 +366,13 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { pending.navigation_handler = self.navigation_handler.take(); pending.web_resource_request_handler = self.web_resource_request_handler.take(); + let labels = self.manager.labels().into_iter().collect::>(); + let pending = self + .manager + .prepare_window(self.app_handle.clone(), pending, &labels)?; + #[cfg(not(target_os = "macos"))] - let menu = { + let window_menu = { let is_app_wide = self.menu.is_none(); self .menu @@ -376,35 +380,9 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { .map(|menu| WindowMenu { is_app_wide, menu }) }; - let labels = self.manager.labels().into_iter().collect::>(); - let pending = self.manager.prepare_window( - self.app_handle.clone(), - pending, - &labels, - #[cfg(not(target_os = "macos"))] - menu.as_ref().map(|m| m.menu.clone()), - )?; - - #[cfg(target_os = "macos")] - let handler = None; - #[cfg(not(target_os = "macos"))] - let handler = if let Some(menu) = &menu { - let menu = menu.menu.clone(); - Some(move |raw: 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); - }) - } else { - None - }; + let handler = self + .manager + .create_webview_before_creation_handler(window_menu.as_ref()); let window_effects = pending.webview_attributes.window_effects.clone(); let window = match &mut self.runtime { @@ -417,7 +395,7 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { self.app_handle.clone(), window, #[cfg(not(target_os = "macos"))] - menu, + window_menu, ) })?; From a325675592e677414b09ac6d54ae57aa64256e2b Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Thu, 3 Aug 2023 05:54:19 +0300 Subject: [PATCH 068/123] remove todo --- core/tauri/src/menu/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/core/tauri/src/menu/mod.rs b/core/tauri/src/menu/mod.rs index e7cc25a004a9..3a846d812b13 100644 --- a/core/tauri/src/menu/mod.rs +++ b/core/tauri/src/menu/mod.rs @@ -4,7 +4,6 @@ //! Menu types and utility functions -// TODO(muda-migration): look for a way to initalize menu for a window without routing through tauri-runtime-wry // TODO(muda-migration): figure out js events pub mod builders; From b1b8756313851b33850ce5cebe084ade81acba26 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Thu, 3 Aug 2023 05:57:10 +0300 Subject: [PATCH 069/123] fix ci file --- .github/workflows/test-core.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-core.yml b/.github/workflows/test-core.yml index c5fd6e267757..824cc5045e10 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,linux-protocol-headers,isolation,custom-protocol,test + args: --features compression,wry,linux-protocol-headers,isolation,custom-protocol,test, key: all } From 30561ad0cf21ed26ff619b5eb051af9e40762b81 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Thu, 3 Aug 2023 06:02:12 +0300 Subject: [PATCH 070/123] fix unix --- core/tauri-runtime-wry/src/lib.rs | 8 ++++++++ core/tauri-runtime/src/window.rs | 8 ++++++++ core/tauri/src/manager.rs | 4 +++- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/core/tauri-runtime-wry/src/lib.rs b/core/tauri-runtime-wry/src/lib.rs index dbe432f77e36..10c8c80d2028 100644 --- a/core/tauri-runtime-wry/src/lib.rs +++ b/core/tauri-runtime-wry/src/lib.rs @@ -2646,6 +2646,14 @@ fn create_webview( 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); diff --git a/core/tauri-runtime/src/window.rs b/core/tauri-runtime/src/window.rs index 08c7ca739b98..0d2eb480c61c 100644 --- a/core/tauri-runtime/src/window.rs +++ b/core/tauri-runtime/src/window.rs @@ -368,5 +368,13 @@ pub struct RawWindow<'a> { 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/src/manager.rs b/core/tauri/src/manager.rs index 3280f509ac20..af66fd33bd36 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -403,7 +403,9 @@ impl WindowManager { target_os = "netbsd", target_os = "openbsd" ))] - let _ = menu.inner().init_for_gtk_window(raw.gtk_window); + let _ = menu + .inner() + .init_for_gtk_window(raw.gtk_window, raw.default_vbox); }) } else { None From c17b8f7307263fe65204c4a16ece9ddac1329ac3 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Thu, 3 Aug 2023 09:19:08 -0300 Subject: [PATCH 071/123] fix macos --- core/tauri/src/app.rs | 19 +++++++++++++------ core/tauri/src/manager.rs | 17 ++++++----------- core/tauri/src/window.rs | 15 +++++++++++++-- 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index ff8498b5b063..ecfa263ac75d 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -20,7 +20,6 @@ use crate::{ sealed::{ManagerBase, RuntimeOrDispatch}, utils::config::Config, utils::{assets::Assets, Env}, - window::WindowMenu, Context, DeviceEventFilter, EventLoopMessage, Icon, Invoke, InvokeError, InvokeResponse, Manager, Monitor, Runtime, Scopes, StateManager, Theme, Window, }; @@ -640,7 +639,7 @@ macro_rules! shared_app_impl { .init_for_gtk_window(>k_window, Some(>k_box)); } })?; - window_menu.replace(WindowMenu { + window_menu.replace(crate::window::WindowMenu { is_app_wide: true, menu: menu.clone(), }); @@ -1585,12 +1584,20 @@ fn setup(app: &mut App) -> crate::Result<()> { let pending = manager.prepare_window(app_handle.clone(), pending, &window_labels)?; #[cfg(not(target_os = "macos"))] - let window_menu = app.manager.menu_lock().as_ref().map(|m| WindowMenu { - is_app_wide: true, - menu: m.clone(), - }); + let window_menu = app + .manager + .menu_lock() + .as_ref() + .map(|m| crate::window::WindowMenu { + is_app_wide: true, + menu: m.clone(), + }); + #[cfg(not(target_os = "macos"))] let handler = manager.create_webview_before_creation_handler(window_menu.as_ref()); + #[cfg(target_os = "macos")] + #[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() { diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index af66fd33bd36..b28a37a6dfed 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -10,12 +10,11 @@ use std::{ sync::{Arc, Mutex, MutexGuard}, }; +use crate::menu::Menu; use crate::tray::TrayIcon; -use crate::{menu::Menu, window::WindowMenu}; use serde::Serialize; use serde_json::Value as JsonValue; use serialize_to_javascript::{default_template, DefaultTemplate, Template}; -use tauri_runtime::window::RawWindow; use url::Url; use tauri_macros::default_runtime; @@ -371,11 +370,11 @@ impl WindowManager { self.inner.state.clone() } + #[cfg(not(target_os = "macos"))] pub(crate) fn create_webview_before_creation_handler( &self, - window_menu: Option<&WindowMenu>, - ) -> Option)> { - #[cfg(not(target_os = "macos"))] + window_menu: Option<&crate::window::WindowMenu>, + ) -> Option)> { { if let Some(menu) = window_menu { self @@ -387,13 +386,9 @@ impl WindowManager { } } - #[cfg(target_os = "macos")] - return None; - - #[cfg(not(target_os = "macos"))] if let Some(menu) = &window_menu { let menu = menu.menu.clone(); - Some(move |raw: RawWindow<'_>| { + Some(move |raw: tauri_runtime::window::RawWindow<'_>| { #[cfg(target_os = "windows")] let _ = menu.inner().init_for_hwnd(raw.hwnd as _); #[cfg(any( @@ -1239,7 +1234,7 @@ impl WindowManager { &self, app_handle: AppHandle, window: DetachedWindow, - #[cfg(not(target_os = "macos"))] menu: Option>, + #[cfg(not(target_os = "macos"))] menu: Option>, ) -> Window { let window = Window::new( self.clone(), diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index 24c9360f4409..418c2b9d0b75 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -4,7 +4,7 @@ //! The Tauri window types and functions. -use crate::{app::GlobalMenuEventListener, menu::ContextMenu}; +use crate::menu::ContextMenu; pub use tauri_utils::{config::Color, WindowEffect as Effect, WindowEffectState as EffectState}; use url::Url; @@ -121,7 +121,8 @@ pub struct WindowBuilder<'a, R: Runtime> { pub(crate) webview_attributes: WebviewAttributes, web_resource_request_handler: Option>, navigation_handler: Option>, - on_menu_event: Option>>, + #[cfg(not(target_os = "macos"))] + on_menu_event: Option>>, } impl<'a, R: Runtime> fmt::Debug for WindowBuilder<'a, R> { @@ -197,6 +198,7 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { webview_attributes: WebviewAttributes::new(url), web_resource_request_handler: None, navigation_handler: None, + #[cfg(not(target_os = "macos"))] on_menu_event: None, } } @@ -237,6 +239,7 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { web_resource_request_handler: None, menu: None, navigation_handler: None, + #[cfg(not(target_os = "macos"))] on_menu_event: None, }; @@ -348,6 +351,8 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { /// Ok(()) /// }); /// ``` + #[cfg(not(target_os = "macos"))] + #[cfg_attr(doc_cfg, doc(cfg(not(target_os = "macos"))))] pub fn on_menu_event, crate::menu::MenuEvent) + Send + Sync + 'static>( mut self, f: F, @@ -380,9 +385,13 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { .map(|menu| WindowMenu { is_app_wide, menu }) }; + #[cfg(not(target_os = "macos"))] let handler = self .manager .create_webview_before_creation_handler(window_menu.as_ref()); + #[cfg(target_os = "macos")] + #[allow(clippy::type_complexity)] + let handler: Option) + Send>> = None; let window_effects = pending.webview_attributes.window_effects.clone(); let window = match &mut self.runtime { @@ -399,6 +408,7 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { ) })?; + #[cfg(not(target_os = "macos"))] if let Some(handler) = self.on_menu_event { window.on_menu_event(handler); } @@ -844,6 +854,7 @@ struct JsEventListenerKey { /// A wrapper struct to hold the window menu state /// and whether it is global per-app or specific to this window. +#[cfg(not(target_os = "macos"))] pub(crate) struct WindowMenu { pub(crate) is_app_wide: bool, pub(crate) menu: Menu, From cbf411f0a100f3acc1135a46016389dedd12d619 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Thu, 3 Aug 2023 09:24:11 -0300 Subject: [PATCH 072/123] fix api build --- examples/api/src-tauri/Cargo.lock | 4 +- examples/api/src-tauri/src/lib.rs | 1 - examples/api/src-tauri/src/tray.rs | 106 ++++++++++++++--------------- 3 files changed, 52 insertions(+), 59 deletions(-) diff --git a/examples/api/src-tauri/Cargo.lock b/examples/api/src-tauri/Cargo.lock index 8bb8ffb63fdf..f2bd890a2b3c 100644 --- a/examples/api/src-tauri/Cargo.lock +++ b/examples/api/src-tauri/Cargo.lock @@ -3448,6 +3448,7 @@ dependencies = [ "jni", "libc", "log", + "muda", "objc", "once_cell", "percent-encoding", @@ -3468,6 +3469,7 @@ dependencies = [ "tauri-utils", "thiserror", "tokio", + "tray-icon", "url", "uuid", "webkit2gtk", @@ -3583,14 +3585,12 @@ dependencies = [ "http", "http-range", "jni", - "muda", "rand 0.8.5", "raw-window-handle", "serde", "serde_json", "tauri-utils", "thiserror", - "tray-icon", "url", "uuid", "windows", diff --git a/examples/api/src-tauri/src/lib.rs b/examples/api/src-tauri/src/lib.rs index e40e069905e0..a3a6d52e1640 100644 --- a/examples/api/src-tauri/src/lib.rs +++ b/examples/api/src-tauri/src/lib.rs @@ -134,7 +134,6 @@ pub fn run_app) + Send + 'static>( // This allow us to catch tray icon events when there is no window api.prevent_exit(); } - Ok(()) }) } diff --git a/examples/api/src-tauri/src/tray.rs b/examples/api/src-tauri/src/tray.rs index 0553f4483fb7..e5b8a9ab6914 100644 --- a/examples/api/src-tauri/src/tray.rs +++ b/examples/api/src-tauri/src/tray.rs @@ -46,75 +46,69 @@ pub fn create_tray(app: &tauri::AppHandle) -> tauri::Result<()> { .tooltip("Tauri") .icon(app.default_window_icon().unwrap().clone()) .menu(&menu1) - .on_menu_event(move |app, event| { - match event.id { - i if i == quit_i.id() => { - app.exit(0); - } - i if i == remove_tray_i.id() => { - app.remove_tray_by_id(TRAY_ID); - } - i if i == toggle_i.id() => { - if let Some(window) = app.get_window("main") { - let new_title = if window.is_visible()? { - window.hide()?; - "Show" - } else { - window.show()?; - "Hide" - }; - toggle_i.set_text(new_title)?; - } - } - i if i == new_window_i.id() => { - WindowBuilder::new(app, "new", WindowUrl::App("index.html".into())) - .title("Tauri") - .build()?; - } - #[cfg(target_os = "macos")] - i if i == set_title_i.id() => { - if let Some(tray) = app.tray_by_id(TRAY_ID) { - tray.set_title(Some("Tauri"))?; - } + .on_menu_event(move |app, event| match event.id { + i if i == quit_i.id() => { + app.exit(0); + } + i if i == remove_tray_i.id() => { + app.remove_tray_by_id(TRAY_ID); + } + i if i == toggle_i.id() => { + 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(); + "Hide" + }; + toggle_i.set_text(new_title).unwrap(); } - i if i == icon_i_1.id() || i == icon_i_2.id() => { - if let Some(tray) = app.tray_by_id(TRAY_ID) { - tray.set_icon(Some(tauri::Icon::Raw(if i == icon_i_1.id() { - include_bytes!("../../../.icons/icon.ico").to_vec() - } else { - include_bytes!("../../../.icons/tray_icon_with_transparency.png").to_vec() - })))?; - } + } + i if i == new_window_i.id() => { + let _ = WindowBuilder::new(app, "new", WindowUrl::App("index.html".into())) + .title("Tauri") + .build(); + } + #[cfg(target_os = "macos")] + i if i == set_title_i.id() => { + if let Some(tray) = app.tray_by_id(TRAY_ID) { + let _ = tray.set_title(Some("Tauri")); } - i if i == switch_i.id() => { - let flag = is_menu1.load(Ordering::Relaxed); - let (menu, tooltip) = if flag { - (menu2.clone(), "Menu 2") + } + i if i == icon_i_1.id() || i == icon_i_2.id() => { + if let Some(tray) = app.tray_by_id(TRAY_ID) { + let _ = tray.set_icon(Some(tauri::Icon::Raw(if i == icon_i_1.id() { + include_bytes!("../../../.icons/icon.ico").to_vec() } else { - (menu1.clone(), "Tauri") - }; - if let Some(tray) = app.tray_by_id(TRAY_ID) { - tray.set_menu(Some(menu))?; - tray.set_tooltip(Some(tooltip))?; - } - is_menu1.store(!flag, Ordering::Relaxed); + include_bytes!("../../../.icons/tray_icon_with_transparency.png").to_vec() + }))); } - - _ => {} + } + i if i == switch_i.id() => { + 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_ID) { + let _ = tray.set_menu(Some(menu)); + let _ = tray.set_tooltip(Some(tooltip)); + } + is_menu1.store(!flag, Ordering::Relaxed); } - Ok(()) + _ => {} }) .on_tray_event(|tray, event| { if event.click_type == ClickType::Left { let app = tray.app_handle(); if let Some(window) = app.get_window("main") { - window.show().unwrap(); - window.set_focus().unwrap(); + let _ = window.show(); + let _ = window.set_focus(); } } - - Ok(()) }) .build(app); From f541c489a8c6ff75f992554b58f807551c8e7bef Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Thu, 3 Aug 2023 16:26:35 +0300 Subject: [PATCH 073/123] expose window menu apis on macOS, no-op --- core/tauri/src/app.rs | 39 +++++++++++---------------------------- core/tauri/src/manager.rs | 24 +++++++----------------- core/tauri/src/window.rs | 31 +++++-------------------------- 3 files changed, 23 insertions(+), 71 deletions(-) diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index ecfa263ac75d..a28ff7a347e0 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -20,6 +20,7 @@ use crate::{ sealed::{ManagerBase, RuntimeOrDispatch}, utils::config::Config, utils::{assets::Assets, Env}, + window::WindowMenu, Context, DeviceEventFilter, EventLoopMessage, Icon, Invoke, InvokeError, InvokeResponse, Manager, Monitor, Runtime, Scopes, StateManager, Theme, Window, }; @@ -617,7 +618,6 @@ macro_rules! shared_app_impl { for window in self.manager.windows_lock().values() { let mut window_menu = window.menu_lock(); if window_menu.as_ref().map(|m| m.is_app_wide).unwrap_or(true) { - #[cfg(not(target_os = "macos"))] let window = window.clone(); let menu_ = menu.clone(); self.run_on_main_thread(move || { @@ -639,7 +639,7 @@ macro_rules! shared_app_impl { .init_for_gtk_window(>k_window, Some(>k_box)); } })?; - window_menu.replace(crate::window::WindowMenu { + window_menu.replace(WindowMenu { is_app_wide: true, menu: menu.clone(), }); @@ -670,7 +670,6 @@ macro_rules! shared_app_impl { #[cfg(not(target_os = "macos"))] for window in self.manager.windows_lock().values() { if window.has_app_wide_menu() { - #[cfg(not(target_os = "macos"))] let window_ = window.clone(); let menu_ = menu.clone(); self.run_on_main_thread(move || { @@ -719,9 +718,8 @@ macro_rules! shared_app_impl { /// /// If a window was not created with an explicit menu or had one set explicitly, /// this will hide the menu from it. - #[cfg(not(target_os = "macos"))] - #[cfg_attr(doc_cfg, doc(cfg(not(target_os = "macos"))))] pub fn hide_menu(&self) -> crate::Result<()> { + #[cfg(not(target_os = "macos"))] if let Some(menu) = &*self.manager.menu_lock() { for window in self.manager.windows_lock().values() { if window.has_app_wide_menu() { @@ -754,9 +752,8 @@ macro_rules! shared_app_impl { /// /// If a window was not created with an explicit menu or had one set explicitly, /// this will show the menu for it. - #[cfg(not(target_os = "macos"))] - #[cfg_attr(doc_cfg, doc(cfg(not(target_os = "macos"))))] pub fn show_menu(&self) -> crate::Result<()> { + #[cfg(not(target_os = "macos"))] if let Some(menu) = &*self.manager.menu_lock() { for window in self.manager.windows_lock().values() { if window.has_app_wide_menu() { @@ -1583,21 +1580,12 @@ fn setup(app: &mut App) -> crate::Result<()> { for pending in pending_windows { let pending = manager.prepare_window(app_handle.clone(), pending, &window_labels)?; - #[cfg(not(target_os = "macos"))] - let window_menu = app - .manager - .menu_lock() - .as_ref() - .map(|m| crate::window::WindowMenu { - is_app_wide: true, - menu: m.clone(), - }); - - #[cfg(not(target_os = "macos"))] - let handler = manager.create_webview_before_creation_handler(window_menu.as_ref()); - #[cfg(target_os = "macos")] - #[allow(clippy::type_complexity)] - let handler: Option) + Send>> = None; + let window_menu = app.manager.menu_lock().as_ref().map(|m| WindowMenu { + is_app_wide: true, + menu: m.clone(), + }); + + let handler = manager.prepare_window_menu_creation_handler(window_menu.as_ref()); let window_effects = pending.webview_attributes.window_effects.clone(); let detached = if let RuntimeOrDispatch::RuntimeHandle(runtime) = app_handle.runtime() { @@ -1606,12 +1594,7 @@ fn setup(app: &mut App) -> crate::Result<()> { // the AppHandle's runtime is always RuntimeOrDispatch::RuntimeHandle unreachable!() }; - let window = manager.attach_window( - app_handle.clone(), - detached, - #[cfg(not(target_os = "macos"))] - None, - ); + let window = manager.attach_window(app_handle.clone(), detached, None); if let Some(effects) = window_effects { crate::vibrancy::set_window_effects(&window, Some(effects))?; diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index b28a37a6dfed..cdacbd295a9e 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -370,22 +370,21 @@ impl WindowManager { self.inner.state.clone() } - #[cfg(not(target_os = "macos"))] - pub(crate) fn create_webview_before_creation_handler( + pub(crate) fn prepare_window_menu_creation_handler( &self, window_menu: Option<&crate::window::WindowMenu>, ) -> Option)> { { if let Some(menu) = window_menu { self - .inner - .menus - .lock() - .unwrap() + .menus_stash_lock() .insert(menu.menu.id(), menu.menu.clone()); } } + #[cfg(target_os = "macos")] + return None; + if let Some(menu) = &window_menu { let menu = menu.menu.clone(); Some(move |raw: tauri_runtime::window::RawWindow<'_>| { @@ -432,9 +431,6 @@ impl WindowManager { pub(crate) fn remove_menu_from_stash_by_id(&self, id: Option) { if let Some(id) = id { - #[cfg(target_os = "macos")] - let is_used_by_a_window = false; - #[cfg(not(target_os = "macos"))] 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); @@ -1234,15 +1230,9 @@ impl WindowManager { &self, app_handle: AppHandle, window: DetachedWindow, - #[cfg(not(target_os = "macos"))] menu: Option>, + menu: Option>, ) -> Window { - let window = Window::new( - self.clone(), - window, - app_handle, - #[cfg(not(target_os = "macos"))] - menu, - ); + let window = Window::new(self.clone(), window, app_handle, menu); let window_ = window.clone(); let window_event_listeners = self.inner.window_event_listeners.clone(); diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index 418c2b9d0b75..58785d2ea4d8 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -121,7 +121,6 @@ pub struct WindowBuilder<'a, R: Runtime> { pub(crate) webview_attributes: WebviewAttributes, web_resource_request_handler: Option>, navigation_handler: Option>, - #[cfg(not(target_os = "macos"))] on_menu_event: Option>>, } @@ -198,7 +197,6 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { webview_attributes: WebviewAttributes::new(url), web_resource_request_handler: None, navigation_handler: None, - #[cfg(not(target_os = "macos"))] on_menu_event: None, } } @@ -239,7 +237,6 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { web_resource_request_handler: None, menu: None, navigation_handler: None, - #[cfg(not(target_os = "macos"))] on_menu_event: None, }; @@ -351,8 +348,6 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { /// Ok(()) /// }); /// ``` - #[cfg(not(target_os = "macos"))] - #[cfg_attr(doc_cfg, doc(cfg(not(target_os = "macos"))))] pub fn on_menu_event, crate::menu::MenuEvent) + Send + Sync + 'static>( mut self, f: F, @@ -376,7 +371,6 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { .manager .prepare_window(self.app_handle.clone(), pending, &labels)?; - #[cfg(not(target_os = "macos"))] let window_menu = { let is_app_wide = self.menu.is_none(); self @@ -385,13 +379,9 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { .map(|menu| WindowMenu { is_app_wide, menu }) }; - #[cfg(not(target_os = "macos"))] let handler = self .manager - .create_webview_before_creation_handler(window_menu.as_ref()); - #[cfg(target_os = "macos")] - #[allow(clippy::type_complexity)] - let handler: Option) + Send>> = None; + .prepare_window_menu_creation_handler(window_menu.as_ref()); let window_effects = pending.webview_attributes.window_effects.clone(); let window = match &mut self.runtime { @@ -400,15 +390,11 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { RuntimeOrDispatch::Dispatch(dispatcher) => dispatcher.create_window(pending, handler), } .map(|window| { - self.manager.attach_window( - self.app_handle.clone(), - window, - #[cfg(not(target_os = "macos"))] - window_menu, - ) + self + .manager + .attach_window(self.app_handle.clone(), window, window_menu) })?; - #[cfg(not(target_os = "macos"))] if let Some(handler) = self.on_menu_event { window.on_menu_event(handler); } @@ -854,7 +840,6 @@ struct JsEventListenerKey { /// A wrapper struct to hold the window menu state /// and whether it is global per-app or specific to this window. -#[cfg(not(target_os = "macos"))] pub(crate) struct WindowMenu { pub(crate) is_app_wide: bool, pub(crate) menu: Menu, @@ -874,8 +859,6 @@ pub struct Window { pub(crate) app_handle: AppHandle, js_event_listeners: Arc>>>, // The menu set for this window - #[cfg(not(target_os = "macos"))] - #[allow(clippy::type_complexity)] pub(crate) menu: Arc>>>, } @@ -903,7 +886,6 @@ impl Clone for Window { manager: self.manager.clone(), app_handle: self.app_handle.clone(), js_event_listeners: self.js_event_listeners.clone(), - #[cfg(not(target_os = "macos"))] menu: self.menu.clone(), } } @@ -1051,14 +1033,13 @@ impl Window { manager: WindowManager, window: DetachedWindow, app_handle: AppHandle, - #[cfg(not(target_os = "macos"))] menu: Option>, + menu: Option>, ) -> Self { Self { window, manager, app_handle, js_event_listeners: Default::default(), - #[cfg(not(target_os = "macos"))] menu: Arc::new(Mutex::new(menu)), } } @@ -1183,8 +1164,6 @@ impl Window { } /// Menu APIs -#[cfg(not(target_os = "macos"))] -#[cfg_attr(doc_cfg, doc(cfg(not(target_os = "macos"))))] impl Window { /// Registers a global menu event listener. /// From 80ea1def7a0460f47baf48d07a64f1107da5c2e7 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Thu, 3 Aug 2023 16:34:45 +0300 Subject: [PATCH 074/123] Add missing submenu APIs --- core/tauri/src/menu/submenu.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/core/tauri/src/menu/submenu.rs b/core/tauri/src/menu/submenu.rs index 078d38a2e7d4..c8d4e78ae3ba 100644 --- a/core/tauri/src/menu/submenu.rs +++ b/core/tauri/src/menu/submenu.rs @@ -245,4 +245,30 @@ impl 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_windows_menu_for_nsapp(enabled))?; + 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_help_menu_for_nsapp(enabled))?; + Ok(()) + } } From 4ace7f12eb368ef259f08abfac5cf467ef0e596b Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Thu, 3 Aug 2023 16:37:57 +0300 Subject: [PATCH 075/123] fix macos build --- core/tauri/src/menu/submenu.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/core/tauri/src/menu/submenu.rs b/core/tauri/src/menu/submenu.rs index c8d4e78ae3ba..bb86835d5328 100644 --- a/core/tauri/src/menu/submenu.rs +++ b/core/tauri/src/menu/submenu.rs @@ -252,9 +252,7 @@ impl Submenu { /// 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_windows_menu_for_nsapp(enabled))?; + run_main_thread!(self, |self_: Self| self_.inner.set_windows_menu_for_nsapp())?; Ok(()) } @@ -266,9 +264,7 @@ impl Submenu { /// 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_help_menu_for_nsapp(enabled))?; + run_main_thread!(self, |self_: Self| self_.inner.set_help_menu_for_nsapp())?; Ok(()) } } From 78616b4a5fb364f6f0360518d7331a772b24feda Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Thu, 3 Aug 2023 10:38:57 -0300 Subject: [PATCH 076/123] update deps [skip ci] --- core/tauri/Cargo.toml | 4 ++-- examples/api/src-tauri/Cargo.lock | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/tauri/Cargo.toml b/core/tauri/Cargo.toml index 2838cf7ec6bc..e9a1a51beebc 100644 --- a/core/tauri/Cargo.toml +++ b/core/tauri/Cargo.toml @@ -65,8 +65,8 @@ serialize-to-javascript = "=0.1.1" infer = { version = "0.9", optional = true } png = { version = "0.17", optional = true } ico = { version = "0.2.0", optional = true } -muda = { version = "0.7", default-features = false } -tray-icon = { version = "0.7", default-features = false } +muda = { version = "0.7.2", default-features = false } +tray-icon = { version = "0.7.6", default-features = false } [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" ] } diff --git a/examples/api/src-tauri/Cargo.lock b/examples/api/src-tauri/Cargo.lock index f2bd890a2b3c..c96c18fa7d4f 100644 --- a/examples/api/src-tauri/Cargo.lock +++ b/examples/api/src-tauri/Cargo.lock @@ -2181,9 +2181,9 @@ dependencies = [ [[package]] name = "muda" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0b6051ce42a7648b11a3613c0d0718f5ac155f262d9f59d5a51501418afaef5" +checksum = "30b3e7038d93632247a6c2484ee912e13079978aadb4ab70e80703041c5ed9b6" dependencies = [ "cocoa 0.25.0", "crossbeam-channel", @@ -3908,9 +3908,9 @@ dependencies = [ [[package]] name = "tray-icon" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abc6ebb68e3591924520845d34aacb1b2a7951f439cabcbc2447a16b70eb5bdd" +checksum = "a1c2322e51a707be81967524a6c1ab97862f50a767b351c56f7341ec8ef24e9a" dependencies = [ "cocoa 0.25.0", "core-graphics 0.23.1", From 98e6ff487582509a24906c3762a710e9019c8bd5 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Thu, 3 Aug 2023 11:00:06 -0300 Subject: [PATCH 077/123] lint macos --- core/tauri/src/manager.rs | 1 + core/tauri/src/window.rs | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index cdacbd295a9e..5b9ab8c66b23 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -385,6 +385,7 @@ impl WindowManager { #[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<'_>| { diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index 58785d2ea4d8..c94ecb4ff197 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -1216,6 +1216,7 @@ impl Window { 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() @@ -1224,6 +1225,7 @@ impl Window { .unwrap_or(false) } + #[cfg_attr(target_os = "macos", allow(dead_code))] pub(crate) fn is_menu_in_use(&self, id: u32) -> bool { self .menu_lock() @@ -1243,6 +1245,7 @@ impl Window { /// /// - **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()?; @@ -1287,6 +1290,7 @@ impl Window { let mut current_menu = self.menu_lock(); // remove from the window + #[cfg_attr(target_os = "macos", allow(unused_variables))] if let Some(window_menu) = &*current_menu { let window = self.clone(); let menu_ = window_menu.menu.clone(); @@ -1322,6 +1326,7 @@ impl Window { /// 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(); @@ -1349,6 +1354,7 @@ impl Window { /// 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(); @@ -1374,8 +1380,10 @@ impl Window { } /// 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(); From b431bc292211f03154f2bb08264a2b2c15e98f7a Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Thu, 3 Aug 2023 17:47:18 +0300 Subject: [PATCH 078/123] hide menu APIs behind `#cfg(desktop)]` --- core/tauri/Cargo.toml | 3 + core/tauri/build.rs | 8 -- core/tauri/src/app.rs | 111 +++++++++++++++++++--------- core/tauri/src/error.rs | 4 + core/tauri/src/lib.rs | 7 +- core/tauri/src/manager.rs | 47 +++++++++--- core/tauri/src/menu/builders/mod.rs | 2 + core/tauri/src/menu/mod.rs | 2 + core/tauri/src/tray.rs | 2 + core/tauri/src/window.rs | 57 +++++++++----- 10 files changed, 173 insertions(+), 70 deletions(-) diff --git a/core/tauri/Cargo.toml b/core/tauri/Cargo.toml index e9a1a51beebc..2bf8f0e2fbd6 100644 --- a/core/tauri/Cargo.toml +++ b/core/tauri/Cargo.toml @@ -65,6 +65,9 @@ serialize-to-javascript = "=0.1.1" 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.7.2", default-features = false } tray-icon = { version = "0.7.6", default-features = false } diff --git a/core/tauri/build.rs b/core/tauri/build.rs index 0c5a9499e24f..29ea69f97a24 100644 --- a/core/tauri/build.rs +++ b/core/tauri/build.rs @@ -49,14 +49,6 @@ fn main() { let mobile = target_os == "ios" || target_os == "android"; alias("desktop", !mobile); alias("mobile", mobile); - alias( - "linux", - target_os == "linux" - || target_os == "dragonfly" - || target_os == "freebsd" - || target_os == "netbsd" - || target_os == "openbsd", - ); let checked_features_out_path = Path::new(&var("OUT_DIR").unwrap()).join("checked_features"); std::fs::write( diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index a28ff7a347e0..ca0f0f893d16 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -20,7 +20,6 @@ use crate::{ sealed::{ManagerBase, RuntimeOrDispatch}, utils::config::Config, utils::{assets::Assets, Env}, - window::WindowMenu, Context, DeviceEventFilter, EventLoopMessage, Icon, Invoke, InvokeError, InvokeResponse, Manager, Monitor, Runtime, Scopes, StateManager, Theme, Window, }; @@ -28,15 +27,22 @@ use crate::{ #[cfg(feature = "protocol-asset")] use crate::scope::FsScope; +#[cfg(desktop)] +use crate::menu::{Menu, MenuEvent}; +#[cfg(desktop)] use crate::tray::{TrayIcon, TrayIconBuilder, TrayIconEvent}; +#[cfg(desktop)] +use crate::window::WindowMenu; use raw_window_handle::HasRawDisplayHandle; use tauri_macros::default_runtime; +#[cfg(desktop)] +use tauri_runtime::EventLoopProxy; use tauri_runtime::{ window::{ dpi::{PhysicalPosition, PhysicalSize}, FileDropEvent, }, - EventLoopProxy, RuntimeInitArgs, + RuntimeInitArgs, }; use tauri_utils::PackageInfo; @@ -46,15 +52,14 @@ use std::{ sync::{mpsc::Sender, Arc, Weak}, }; -use crate::{ - menu::{Menu, MenuEvent}, - runtime::RuntimeHandle, -}; +use crate::runtime::RuntimeHandle; #[cfg(target_os = "macos")] use crate::ActivationPolicy; +#[cfg(desktop)] pub(crate) type GlobalMenuEventListener = Box; +#[cfg(desktop)] pub(crate) type GlobalTrayIconEventListener = Box; pub(crate) type GlobalWindowEventListener = Box) + Send + Sync>; @@ -187,15 +192,19 @@ pub enum RunEvent { 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)] MenuEvent(crate::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(desktop)] TrayIconEvent(crate::tray::TrayIconEvent), } impl From for RunEvent { fn from(event: EventLoopMessage) -> Self { match event { + #[cfg(desktop)] EventLoopMessage::MenuEvent(e) => Self::MenuEvent(e), + #[cfg(desktop)] EventLoopMessage::TrayIconEvent(e) => Self::TrayIconEvent(e), } } @@ -472,6 +481,7 @@ macro_rules! shared_app_impl { ($app: ty) => { impl $app { /// Registers a global menu event listener. + #[cfg(desktop)] pub fn on_menu_event, MenuEvent) + Send + Sync + 'static>( &self, handler: F, @@ -486,6 +496,7 @@ macro_rules! shared_app_impl { } /// Registers a global tray icon menu event listener. + #[cfg(desktop)] pub fn on_tray_icon_event, TrayIconEvent) + Send + Sync + 'static>( &self, handler: F, @@ -501,6 +512,7 @@ macro_rules! shared_app_impl { /// Gets the first tray icon registerd, usually the one configured in /// tauri config file. + #[cfg(desktop)] pub fn tray(&self) -> Option> { self .manager @@ -516,6 +528,7 @@ macro_rules! shared_app_impl { /// tauri config file, from tauri's internal state and returns it. /// /// Note that dropping the returned icon, will cause the tray icon to disappear. + #[cfg(desktop)] pub fn remove_tray(&self) -> Option> { let mut tray_icons = self.manager.inner.tray_icons.lock().unwrap(); if !tray_icons.is_empty() { @@ -525,6 +538,7 @@ macro_rules! shared_app_impl { } /// Gets a tray icon using the provided id. + #[cfg(desktop)] pub fn tray_by_id(&self, id: u32) -> Option> { self .manager @@ -540,6 +554,7 @@ macro_rules! shared_app_impl { /// 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(desktop)] pub fn remove_tray_by_id(&self, id: u32) -> Option> { let mut tray_icons = self.manager.inner.tray_icons.lock().unwrap(); @@ -598,6 +613,7 @@ macro_rules! shared_app_impl { } /// Returns the app-wide menu. + #[cfg(desktop)] pub fn menu(&self) -> Option> { self.manager.menu_lock().clone() } @@ -606,6 +622,7 @@ macro_rules! shared_app_impl { /// /// 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()?; @@ -662,6 +679,7 @@ macro_rules! shared_app_impl { /// /// 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 mut current_menu = self.manager.menu_lock(); @@ -718,6 +736,7 @@ macro_rules! shared_app_impl { /// /// 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"))] if let Some(menu) = &*self.manager.menu_lock() { @@ -752,6 +771,7 @@ macro_rules! shared_app_impl { /// /// 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"))] if let Some(menu) = &*self.manager.menu_lock() { @@ -807,6 +827,7 @@ macro_rules! shared_app_impl { /// Runs necessary cleanup tasks before exiting the process. /// **You sould always exit the process immediately after this function returns.** pub fn cleanup_before_exit(&self) { + #[cfg(desktop)] self.manager.inner.tray_icons.lock().unwrap().clear() } } @@ -1006,6 +1027,7 @@ pub struct Builder { state: StateManager, /// A closure that returns the menu set to all windows. + #[cfg(desktop)] menu: Option) -> crate::Result>>>, /// Enable macOS default menu creation. @@ -1035,6 +1057,7 @@ impl Builder { plugins: PluginStore::default(), uri_scheme_protocols: Default::default(), state: StateManager::new(), + #[cfg(desktop)] menu: None, enable_macos_default_menu: true, window_event_listeners: Vec::new(), @@ -1284,6 +1307,7 @@ impl Builder { /// ])); /// ``` #[must_use] + #[cfg(desktop)] pub fn menu) -> crate::Result> + 'static>( mut self, f: F, @@ -1399,6 +1423,7 @@ impl Builder { self.uri_scheme_protocols, self.state, self.window_event_listeners, + #[cfg(desktop)] HashMap::new(), (self.invoke_responder, self.invoke_initialization_script), ); @@ -1445,17 +1470,20 @@ impl Builder { #[cfg(not(any(windows, target_os = "linux")))] let mut runtime = R::new(runtime_args)?; - // 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)); - })); + #[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 - let proxy = runtime.create_proxy(); - crate::tray::TrayIconEvent::set_event_handler(Some(move |e| { - let _ = proxy.send_event(EventLoopMessage::TrayIconEvent(e)); - })); + // setup tray event handler + 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); @@ -1473,6 +1501,7 @@ impl Builder { }, }; + #[cfg(desktop)] if let Some(menu) = self.menu { let menu = menu(&app.handle)?; app @@ -1523,23 +1552,26 @@ impl Builder { let handle = app.handle(); - // initialize default tray icon if defined - 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); + #[cfg(desktop)] + { + // initialize default tray icon if defined + 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); } - let tray = tray.build(&handle)?; - app.manager.inner.tray_icons.lock().unwrap().push(tray); } app.manager.initialize_plugins(&handle)?; @@ -1580,12 +1612,17 @@ fn setup(app: &mut App) -> crate::Result<()> { for pending in pending_windows { 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() { @@ -1594,7 +1631,12 @@ fn setup(app: &mut App) -> crate::Result<()> { // the AppHandle's runtime is always RuntimeOrDispatch::RuntimeHandle unreachable!() }; - let window = manager.attach_window(app_handle.clone(), detached, None); + 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))?; @@ -1659,6 +1701,7 @@ fn on_event_loop_event, RunEvent) + 'static>( RuntimeRunEvent::MainEventsCleared => RunEvent::MainEventsCleared, RuntimeRunEvent::UserEvent(t) => { match t { + #[cfg(desktop)] EventLoopMessage::MenuEvent(e) => { for listener in &*app_handle .manager @@ -1681,6 +1724,7 @@ fn on_event_loop_event, RunEvent) + 'static>( } } } + #[cfg(desktop)] EventLoopMessage::TrayIconEvent(e) => { for listener in &*app_handle .manager @@ -1708,6 +1752,7 @@ fn on_event_loop_event, RunEvent) + 'static>( } } + #[allow(unreachable_code)] t.into() } #[cfg(any(target_os = "macos", target_os = "ios"))] diff --git a/core/tauri/src/error.rs b/core/tauri/src/error.rs index ae18895cf439..395ef25d8e67 100644 --- a/core/tauri/src/error.rs +++ b/core/tauri/src/error.rs @@ -94,14 +94,18 @@ pub enum Error { FailedToReceiveMessage, /// Menu error. #[error("menu error: {0}")] + #[cfg(desktop)] Menu(#[from] muda::Error), /// Bad menu icon error. #[error(transparent)] + #[cfg(desktop)] BadMenuIcon(#[from] muda::icon::BadIcon), /// Tray icon error. #[error("tray icon error: {0}")] + #[cfg(desktop)] Tray(#[from] tray_icon::Error), /// Bad tray icon error. #[error(transparent)] + #[cfg(desktop)] BadTrayIcon(#[from] tray_icon::icon::BadIcon), } diff --git a/core/tauri/src/lib.rs b/core/tauri/src/lib.rs index 9eae231aa77e..8780dd24b378 100644 --- a/core/tauri/src/lib.rs +++ b/core/tauri/src/lib.rs @@ -92,6 +92,7 @@ use tauri_runtime as runtime; mod ios; #[cfg(target_os = "android")] mod jni_helpers; +#[cfg(desktop)] pub mod menu; /// Path APIs. pub mod path; @@ -100,6 +101,7 @@ pub mod process; pub mod scope; mod state; +#[cfg(desktop)] pub mod tray; pub use tauri_utils as utils; @@ -243,8 +245,10 @@ pub fn log_stdout() { #[derive(Debug, Clone)] 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(desktop)] TrayIconEvent(tray::TrayIconEvent), } @@ -892,10 +896,10 @@ 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 || { @@ -906,6 +910,7 @@ macro_rules! run_main_thread { }}; } +#[allow(unused)] pub(crate) use run_main_thread; #[cfg(test)] diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index 5b9ab8c66b23..11c8b15c48c4 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -10,8 +10,8 @@ use std::{ sync::{Arc, Mutex, MutexGuard}, }; -use crate::menu::Menu; -use crate::tray::TrayIcon; +#[cfg(desktop)] +use crate::{menu::Menu, tray::TrayIcon}; use serde::Serialize; use serde_json::Value as JsonValue; use serialize_to_javascript::{default_template, DefaultTemplate, Template}; @@ -51,10 +51,10 @@ use crate::{ Context, EventLoopMessage, Icon, Invoke, Manager, Pattern, Runtime, Scopes, StateManager, Window, WindowEvent, }; -use crate::{ - app::{GlobalMenuEventListener, GlobalTrayIconEventListener}, - pattern::PatternJavascript, -}; + +#[cfg(desktop)] +use crate::app::{GlobalMenuEventListener, GlobalTrayIconEventListener}; +use crate::pattern::PatternJavascript; #[cfg(any(target_os = "linux", target_os = "windows"))] use crate::path::BaseDirectory; @@ -226,22 +226,29 @@ pub struct InnerWindowManager { /// /// 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. + #[cfg(desktop)] pub(crate) menu: Arc>>>, /// Menu event listeners to all windows. + #[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(desktop)] pub(crate) tray_icons: Arc>>>, /// Global Tray icon event listeners. + #[cfg(desktop)] pub(crate) global_tray_event_listeners: Arc>>>>, /// Tray icon event listeners. + #[cfg(desktop)] pub(crate) tray_event_listeners: Arc>>>>, /// Responder for invoke calls. @@ -316,7 +323,10 @@ impl WindowManager { uri_scheme_protocols: HashMap>>, state: StateManager, window_event_listeners: Vec>, - window_menu_event_listeners: HashMap>>, + #[cfg(desktop)] window_menu_event_listeners: HashMap< + String, + GlobalMenuEventListener>, + >, (invoke_responder, invoke_initialization_script): (Arc>, String), ) -> Self { // generate a random isolation key at runtime @@ -342,13 +352,20 @@ impl WindowManager { package_info: context.package_info, pattern: context.pattern, uri_scheme_protocols, + #[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(desktop)] tray_icons: Default::default(), + #[cfg(desktop)] global_tray_event_listeners: Default::default(), + #[cfg(desktop)] tray_event_listeners: Default::default(), invoke_responder, invoke_initialization_script, @@ -370,6 +387,7 @@ impl WindowManager { self.inner.state.clone() } + #[cfg(desktop)] pub(crate) fn prepare_window_menu_creation_handler( &self, window_menu: Option<&crate::window::WindowMenu>, @@ -408,15 +426,18 @@ impl WindowManager { } /// 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: u32) -> bool { self .menu_lock() @@ -426,10 +447,12 @@ impl WindowManager { } /// Menus stash. + #[cfg(desktop)] pub(crate) fn insert_menu_into_stash(&self, menu: &Menu) { self.menus_stash_lock().insert(menu.id(), menu.clone()); } + #[cfg(desktop)] pub(crate) fn remove_menu_from_stash_by_id(&self, id: Option) { if let Some(id) = id { let is_used_by_a_window = self.windows_lock().values().any(|w| w.is_menu_in_use(id)); @@ -1231,9 +1254,15 @@ impl WindowManager { &self, app_handle: AppHandle, window: DetachedWindow, - menu: Option>, + #[cfg(desktop)] menu: Option>, ) -> Window { - let window = Window::new(self.clone(), window, app_handle, menu); + 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(); diff --git a/core/tauri/src/menu/builders/mod.rs b/core/tauri/src/menu/builders/mod.rs index b1ddd0e42f8a..9c9b8f87a7c2 100644 --- a/core/tauri/src/menu/builders/mod.rs +++ b/core/tauri/src/menu/builders/mod.rs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT +#![cfg(desktop)] + //! A module containting menu builder types pub use muda::builders::AboutMetadataBuilder; diff --git a/core/tauri/src/menu/mod.rs b/core/tauri/src/menu/mod.rs index 3a846d812b13..32be9f6e0617 100644 --- a/core/tauri/src/menu/mod.rs +++ b/core/tauri/src/menu/mod.rs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT +#![cfg(desktop)] + //! Menu types and utility functions // TODO(muda-migration): figure out js events diff --git a/core/tauri/src/tray.rs b/core/tauri/src/tray.rs index 1f0052152084..ffb17b680a79 100644 --- a/core/tauri/src/tray.rs +++ b/core/tauri/src/tray.rs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT +#![cfg(desktop)] + //! Tray icon types and utility functions use crate::app::{GlobalMenuEventListener, GlobalTrayIconEventListener}; diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index c94ecb4ff197..5177a75b21cf 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -4,7 +4,6 @@ //! The Tauri window types and functions. -use crate::menu::ContextMenu; pub use tauri_utils::{config::Color, WindowEffect as Effect, WindowEffectState as EffectState}; use url::Url; @@ -35,7 +34,7 @@ use crate::{ }; #[cfg(desktop)] use crate::{ - menu::Menu, + menu::{ContextMenu, Menu}, runtime::{ window::dpi::{Position, Size}, UserAttentionType, @@ -117,10 +116,12 @@ 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>>, } @@ -193,10 +194,12 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { 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, } } @@ -235,8 +238,10 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { config, ), web_resource_request_handler: None, + #[cfg(desktop)] menu: None, navigation_handler: None, + #[cfg(desktop)] on_menu_event: None, }; @@ -348,6 +353,7 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { /// Ok(()) /// }); /// ``` + #[cfg(desktop)] pub fn on_menu_event, crate::menu::MenuEvent) + Send + Sync + 'static>( mut self, f: F, @@ -371,6 +377,7 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { .manager .prepare_window(self.app_handle.clone(), pending, &labels)?; + #[cfg(desktop)] let window_menu = { let is_app_wide = self.menu.is_none(); self @@ -379,9 +386,13 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { .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 { @@ -390,11 +401,15 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { RuntimeOrDispatch::Dispatch(dispatcher) => dispatcher.create_window(pending, handler), } .map(|window| { - self - .manager - .attach_window(self.app_handle.clone(), window, window_menu) + 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); } @@ -840,6 +855,7 @@ struct JsEventListenerKey { /// 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, @@ -859,6 +875,7 @@ pub struct Window { pub(crate) app_handle: AppHandle, js_event_listeners: Arc>>>, // The menu set for this window + #[cfg(desktop)] pub(crate) menu: Arc>>>, } @@ -886,6 +903,7 @@ 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(), } } @@ -1033,13 +1051,14 @@ impl Window { manager: WindowManager, window: DetachedWindow, app_handle: AppHandle, - menu: Option>, + #[cfg(desktop)] menu: Option>, ) -> Self { Self { window, manager, app_handle, js_event_listeners: Default::default(), + #[cfg(desktop)] menu: Arc::new(Mutex::new(menu)), } } @@ -1086,18 +1105,6 @@ impl Window { .on_window_event(move |event| f(&event.clone().into())); } - /// Shows the specified menu as a context menu at the specified position. - /// If a position was not provided, the cursor position will be used. - /// - /// The position is relative to the window's top-left corner. - pub fn popup_menu>( - &self, - menu: &M, - position: Option

, - ) -> crate::Result<()> { - menu.popup(self.clone(), position) - } - /// Executes a closure, providing it with the webview handle that is specific to the current platform. /// /// The closure is executed on the main thread. @@ -1164,6 +1171,7 @@ impl Window { } /// Menu APIs +#[cfg(desktop)] impl Window { /// Registers a global menu event listener. /// @@ -1380,7 +1388,6 @@ impl Window { } /// Shows the window menu. - pub fn is_menu_visible(&self) -> crate::Result { // remove from the window #[cfg_attr(target_os = "macos", allow(unused_variables))] @@ -1410,6 +1417,18 @@ impl Window { Ok(false) } + + /// Shows the specified menu as a context menu at the specified position. + /// If a position was not provided, the cursor position will be used. + /// + /// The position is relative to the window's top-left corner. + pub fn popup_menu>( + &self, + menu: &M, + position: Option

, + ) -> crate::Result<()> { + menu.popup(self.clone(), position) + } } /// Window getters. From 0fd9fdff6091a1e1f856fd9c06030292de1fcbe3 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Thu, 3 Aug 2023 17:55:38 +0300 Subject: [PATCH 079/123] update api example lockfile --- examples/api/src-tauri/Cargo.lock | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/examples/api/src-tauri/Cargo.lock b/examples/api/src-tauri/Cargo.lock index c96c18fa7d4f..64a08b9c1fa4 100644 --- a/examples/api/src-tauri/Cargo.lock +++ b/examples/api/src-tauri/Cargo.lock @@ -502,9 +502,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.79" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "6c6b2562119bf28c3439f7f02db99faf0aa1a8cdfe5772a2ee155d32227239f0" +dependencies = [ + "libc", +] [[package]] name = "cesu8" @@ -863,9 +866,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8810e7e2cf385b1e9b50d68264908ec367ba642c96d02edfe61c39e88e2a3c01" +checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929" dependencies = [ "serde", ] @@ -1853,7 +1856,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", - "rustix 0.38.4", + "rustix 0.38.6", "windows-sys 0.48.0", ] @@ -2945,9 +2948,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.4" +version = "0.38.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5" +checksum = "1ee020b1716f0a80e2ace9b03441a749e402e86712f15f16fe8a8f75afac732f" dependencies = [ "bitflags 2.3.3", "errno", @@ -3367,7 +3370,7 @@ dependencies = [ [[package]] name = "tao" version = "0.21.1" -source = "git+https://github.com/tauri-apps/tao?branch=muda#44699626ddfe6fc89d67785e93dd9f5251a666c0" +source = "git+https://github.com/tauri-apps/tao?branch=muda#627caa988855c87d6c4d5d34df9bce881489fa35" dependencies = [ "bitflags 1.3.2", "cairo-rs", @@ -3415,7 +3418,7 @@ dependencies = [ [[package]] name = "tao-macros" version = "0.1.1" -source = "git+https://github.com/tauri-apps/tao?branch=muda#44699626ddfe6fc89d67785e93dd9f5251a666c0" +source = "git+https://github.com/tauri-apps/tao?branch=muda#627caa988855c87d6c4d5d34df9bce881489fa35" dependencies = [ "proc-macro2", "quote", @@ -3664,7 +3667,7 @@ dependencies = [ "cfg-if", "fastrand 2.0.0", "redox_syscall 0.3.5", - "rustix 0.38.4", + "rustix 0.38.6", "windows-sys 0.48.0", ] @@ -3717,9 +3720,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b79eabcd964882a646b3584543ccabeae7869e9ac32a46f6f22b7a5bd405308b" +checksum = "b0fdd63d58b18d663fbdf70e049f00a22c8e42be082203be7f26589213cd75ea" dependencies = [ "deranged", "itoa 1.0.9", @@ -4508,9 +4511,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winnow" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bd122eb777186e60c3fdf765a58ac76e41c582f1f535fbf3314434c6b58f3f7" +checksum = "f46aab759304e4d7b2075a9aecba26228bb073ee8c50db796b2c72c676b5d807" dependencies = [ "memchr", ] From 64e931fbfe99ec10abedbc2a6341d4daab702f12 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Thu, 3 Aug 2023 12:30:12 -0300 Subject: [PATCH 080/123] disable menu on left click --- examples/api/src-tauri/src/tray.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/api/src-tauri/src/tray.rs b/examples/api/src-tauri/src/tray.rs index e5b8a9ab6914..92042b0483a8 100644 --- a/examples/api/src-tauri/src/tray.rs +++ b/examples/api/src-tauri/src/tray.rs @@ -46,6 +46,7 @@ pub fn create_tray(app: &tauri::AppHandle) -> tauri::Result<()> { .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 { i if i == quit_i.id() => { app.exit(0); From a0103c1f209c12103cd7d5773e9878cf692d1f60 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Thu, 3 Aug 2023 12:30:17 -0300 Subject: [PATCH 081/123] focus window --- examples/api/src-tauri/src/tray.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/api/src-tauri/src/tray.rs b/examples/api/src-tauri/src/tray.rs index 92042b0483a8..df56646f35f5 100644 --- a/examples/api/src-tauri/src/tray.rs +++ b/examples/api/src-tauri/src/tray.rs @@ -61,6 +61,7 @@ pub fn create_tray(app: &tauri::AppHandle) -> tauri::Result<()> { "Show" } else { let _ = window.show(); + let _ = window.set_focus(); "Hide" }; toggle_i.set_text(new_title).unwrap(); From b26e3b32627d1be5307d7bea67e7a89b4c3f2baa Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Thu, 3 Aug 2023 13:17:37 -0300 Subject: [PATCH 082/123] implement migration for config and cargo manifest --- tooling/cli/src/migrate/config.rs | 4 ++++ tooling/cli/src/migrate/manifest.rs | 1 + 2 files changed, 5 insertions(+) diff --git a/tooling/cli/src/migrate/config.rs b/tooling/cli/src/migrate/config.rs index 7312359212ea..4813d8be0610 100644 --- a/tooling/cli/src/migrate/config.rs +++ b/tooling/cli/src/migrate/config.rs @@ -47,6 +47,10 @@ fn migrate_config(config: &mut Value) -> Result<()> { process_allowlist(tauri_config, &mut plugins, allowlist)?; } + 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..3edb6561d80f 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 From a9c57c7d739555418209ff57045b82b1149e899b Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Thu, 3 Aug 2023 13:46:59 -0300 Subject: [PATCH 083/123] add menu toggle to api example --- examples/api/src-tauri/src/cmd.rs | 27 ++++++++++++++++++++++++++- examples/api/src-tauri/src/lib.rs | 10 ++++++++++ examples/api/src/App.svelte | 8 ++++++-- 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/examples/api/src-tauri/src/cmd.rs b/examples/api/src-tauri/src/cmd.rs index 221881fc1b3b..a1900d2af028 100644 --- a/examples/api/src-tauri/src/cmd.rs +++ b/examples/api/src-tauri/src/cmd.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: MIT use serde::Deserialize; -use tauri::command; +use tauri::{command, Runtime}; #[derive(Debug, Deserialize)] #[allow(unused)] @@ -22,3 +22,28 @@ pub fn perform_request(endpoint: String, body: RequestBody) -> String { println!("{} {:?}", endpoint, body); "message response".into() } + +#[cfg(not(target_os = "macos"))] +#[command] +pub fn toggle_menu(window: tauri::Window) { + if window.is_menu_visible().unwrap_or_default() { + let _ = window.show_menu(); + } else { + let _ = window.hide_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(); + } +} diff --git a/examples/api/src-tauri/src/lib.rs b/examples/api/src-tauri/src/lib.rs index a3a6d52e1640..9613fb252af2 100644 --- a/examples/api/src-tauri/src/lib.rs +++ b/examples/api/src-tauri/src/lib.rs @@ -20,6 +20,9 @@ struct Reply { data: String, } +#[cfg(target_os = "macos")] +pub struct AppMenu(pub std::sync::Mutex>>); + pub type SetupHook = Box Result<(), Box> + Send>; pub type OnEvent = Box; @@ -48,6 +51,12 @@ pub fn run_app) + Send + 'static>( handle.plugin(tauri_plugin_cli::init())?; } + #[cfg(target_os = "macos")] + { + use tauri::Manager; + app.manage(AppMenu::(Default::default())); + } + let mut window_builder = WindowBuilder::new(app, "main", WindowUrl::default()); #[cfg(desktop)] { @@ -120,6 +129,7 @@ pub fn run_app) + Send + 'static>( .invoke_handler(tauri::generate_handler![ cmd::log_operation, cmd::perform_request, + cmd::toggle_menu ]) .build(tauri::tauri_build_context!()) .expect("error while building tauri application"); 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 @@ + +

From 0621382d5f0cd5e9abd4f81b1ce1fb45ff0da40c Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Thu, 3 Aug 2023 15:45:09 -0300 Subject: [PATCH 090/123] fix macos --- core/tauri/src/app.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index b56ed43dd472..53e20b1daff2 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -669,7 +669,8 @@ macro_rules! shared_app_impl { /// this will remove the menu from it. #[cfg(desktop)] pub fn remove_menu(&self) -> crate::Result>> { - if self.manager.menu_lock().is_some() { + let menu = self.manager.menu_lock().as_ref().cloned(); + if let Some(menu) = menu { // remove from windows that have the app-wide menu #[cfg(not(target_os = "macos"))] { @@ -691,9 +692,8 @@ macro_rules! shared_app_impl { // remove app-wide for macos #[cfg(target_os = "macos")] { - let menu_ = menu.clone(); self.run_on_main_thread(move || { - menu_.inner().remove_for_nsapp(); + menu.inner().remove_for_nsapp(); })?; } } From 431ace70d64b8a43ae892482fca176b4d6b3a6b0 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Thu, 3 Aug 2023 15:46:47 -0300 Subject: [PATCH 091/123] update tray-icon --- core/tauri/Cargo.toml | 2 +- examples/api/src-tauri/Cargo.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/tauri/Cargo.toml b/core/tauri/Cargo.toml index 2bf8f0e2fbd6..fc1f8cb081c8 100644 --- a/core/tauri/Cargo.toml +++ b/core/tauri/Cargo.toml @@ -69,7 +69,7 @@ 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.7.2", default-features = false } -tray-icon = { version = "0.7.6", default-features = false } +tray-icon = { version = "0.7.7", default-features = false } [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" ] } diff --git a/examples/api/src-tauri/Cargo.lock b/examples/api/src-tauri/Cargo.lock index 64a08b9c1fa4..687927f85215 100644 --- a/examples/api/src-tauri/Cargo.lock +++ b/examples/api/src-tauri/Cargo.lock @@ -3911,9 +3911,9 @@ dependencies = [ [[package]] name = "tray-icon" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1c2322e51a707be81967524a6c1ab97862f50a767b351c56f7341ec8ef24e9a" +checksum = "62cfab965795a1d83dde8d38c00161edb80ea5a5c860cfb3a8e0d9dfc211860b" dependencies = [ "cocoa 0.25.0", "core-graphics 0.23.1", From 2e65ed9cc985e6250a2d9b1eed450b5bb498fa40 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Thu, 3 Aug 2023 15:48:09 -0300 Subject: [PATCH 092/123] update muda --- core/tauri/Cargo.toml | 2 +- examples/api/src-tauri/Cargo.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/tauri/Cargo.toml b/core/tauri/Cargo.toml index fc1f8cb081c8..b78af5b00c38 100644 --- a/core/tauri/Cargo.toml +++ b/core/tauri/Cargo.toml @@ -68,7 +68,7 @@ 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.7.2", default-features = false } +muda = { version = "0.7.3", default-features = false } tray-icon = { version = "0.7.7", default-features = false } [target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies] diff --git a/examples/api/src-tauri/Cargo.lock b/examples/api/src-tauri/Cargo.lock index 687927f85215..be966ead8e09 100644 --- a/examples/api/src-tauri/Cargo.lock +++ b/examples/api/src-tauri/Cargo.lock @@ -2184,9 +2184,9 @@ dependencies = [ [[package]] name = "muda" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b3e7038d93632247a6c2484ee912e13079978aadb4ab70e80703041c5ed9b6" +checksum = "e375211006d809e35f8b1488b37d0716a474a6a1d8ecc268ec6d2756cf4fbb49" dependencies = [ "cocoa 0.25.0", "crossbeam-channel", From 1c9d911c6f2596c44c03d6c35ed421cc4a2bfeff Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Thu, 3 Aug 2023 15:49:14 -0300 Subject: [PATCH 093/123] clippy --- core/tauri/src/app.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 53e20b1daff2..1b2ca784927e 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -670,6 +670,7 @@ macro_rules! shared_app_impl { #[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"))] From 1a821eb37ca242c6cce688f85962a541eda1589f Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Thu, 3 Aug 2023 16:04:02 -0300 Subject: [PATCH 094/123] reuse popup menu --- examples/api/src-tauri/src/cmd.rs | 21 +++++---------------- examples/api/src-tauri/src/lib.rs | 25 ++++++++++++++++++------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/examples/api/src-tauri/src/cmd.rs b/examples/api/src-tauri/src/cmd.rs index e3703195678b..3b050d9c06e3 100644 --- a/examples/api/src-tauri/src/cmd.rs +++ b/examples/api/src-tauri/src/cmd.rs @@ -2,8 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT +use crate::PopupMenu; use serde::Deserialize; -use tauri::{command, menu::builders::MenuBuilder, Runtime, Window}; +use tauri::{command, Runtime, State, Window}; #[derive(Debug, Deserialize)] #[allow(unused)] @@ -35,10 +36,7 @@ pub fn toggle_menu(window: Window) { #[cfg(target_os = "macos")] #[command] -pub fn toggle_menu( - app: tauri::AppHandle, - app_menu: tauri::State<'_, crate::AppMenu>, -) { +pub fn toggle_menu(app: tauri::AppHandle, app_menu: State<'_, crate::AppMenu>) { if let Some(menu) = app.remove_menu().unwrap() { app_menu.0.lock().unwrap().replace(menu); } else { @@ -49,15 +47,6 @@ pub fn toggle_menu( } #[command] -pub fn popup_context_menu(window: Window) { - window - .popup_menu( - &MenuBuilder::new(&window) - .check("Tauri is awesome!") - .text("Do something") - .copy() - .build() - .unwrap(), - ) - .unwrap(); +pub fn popup_context_menu(window: Window, popup_menu: State<'_, 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 faaac4b060dd..34b1fde71ef9 100644 --- a/examples/api/src-tauri/src/lib.rs +++ b/examples/api/src-tauri/src/lib.rs @@ -12,9 +12,16 @@ mod cmd; mod tray; use serde::Serialize; -use tauri::{window::WindowBuilder, App, AppHandle, RunEvent, Runtime, WindowUrl}; +use tauri::{ + menu::{builders::MenuBuilder, Menu}, + window::WindowBuilder, + App, AppHandle, Manager, RunEvent, Runtime, WindowUrl, +}; use tauri_plugin_sample::{PingRequest, SampleExt}; +pub type SetupHook = Box Result<(), Box> + Send>; +pub type OnEvent = Box; + #[derive(Clone, Serialize)] struct Reply { data: String, @@ -23,8 +30,7 @@ struct Reply { #[cfg(target_os = "macos")] pub struct AppMenu(pub std::sync::Mutex>>); -pub type SetupHook = Box Result<(), Box> + Send>; -pub type OnEvent = Box; +pub struct PopupMenu(Menu); #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { @@ -52,10 +58,15 @@ pub fn run_app) + Send + 'static>( } #[cfg(target_os = "macos")] - { - use tauri::Manager; - app.manage(AppMenu::(Default::default())); - } + app.manage(AppMenu::(Default::default())); + + app.manage(PopupMenu( + MenuBuilder::new(app) + .check("Tauri is awesome!") + .text("Do something") + .copy() + .build()?, + )); let mut window_builder = WindowBuilder::new(app, "main", WindowUrl::default()); #[cfg(desktop)] From e0282a3df1664a466b53d08ca7807dc44d102062 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Fri, 4 Aug 2023 04:07:32 +0300 Subject: [PATCH 095/123] add `ContextMenu::popup_at` --- core/tauri/src/menu/menu.rs | 30 +++++++++++++++++++++--------- core/tauri/src/menu/mod.rs | 16 +++++++++++++--- core/tauri/src/menu/submenu.rs | 23 ++++++++++++++++++----- core/tauri/src/window.rs | 6 ++---- 4 files changed, 54 insertions(+), 21 deletions(-) diff --git a/core/tauri/src/menu/menu.rs b/core/tauri/src/menu/menu.rs index aacd559a69b5..1650c337b0ba 100644 --- a/core/tauri/src/menu/menu.rs +++ b/core/tauri/src/menu/menu.rs @@ -2,7 +2,9 @@ // 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::AboutMetadata; use muda::ContextMenu; @@ -32,13 +34,27 @@ impl Clone for Menu { } impl super::ContextMenu for Menu { - fn popup>( + 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, |self_: Self| { + run_main_thread!(self, move |self_: Self| { #[cfg(target_os = "macos")] if let Ok(view) = window.ns_view() { self_ @@ -53,20 +69,16 @@ impl super::ContextMenu for Menu { target_os = "netbsd", target_os = "openbsd" ))] - if let Ok(gtk_window) = window.gtk_window() { - self_ - .inner() - .show_context_menu_for_gtk_window(>k_window, position); + 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); + self_.inner().show_context_menu_for_hwnd(hwnd.0, position) } }) } -} -impl super::sealed::ContextMenuBase for Menu { fn inner(&self) -> &dyn muda::ContextMenu { &self.inner } diff --git a/core/tauri/src/menu/mod.rs b/core/tauri/src/menu/mod.rs index 32be9f6e0617..5e81734cd730 100644 --- a/core/tauri/src/menu/mod.rs +++ b/core/tauri/src/menu/mod.rs @@ -193,11 +193,16 @@ pub trait IsMenuItem: sealed::IsMenuItemBase { /// /// 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. - fn popup>( + /// Popup this menu as a context menu on the specified window at the specified 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: Option

, + position: P, ) -> crate::Result<()>; } @@ -210,6 +215,11 @@ pub(crate) mod sealed { 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<()>; } } diff --git a/core/tauri/src/menu/submenu.rs b/core/tauri/src/menu/submenu.rs index bb86835d5328..3c013837881c 100644 --- a/core/tauri/src/menu/submenu.rs +++ b/core/tauri/src/menu/submenu.rs @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use super::{IsMenuItem, MenuItemKind}; -use crate::{run_main_thread, AppHandle, Manager, Position, Runtime}; +use super::{sealed::ContextMenuBase, IsMenuItem, MenuItemKind}; +use crate::{run_main_thread, AppHandle, Manager, Position, Runtime, Window}; use muda::ContextMenu; /// A type that is a submenu inside a [`Menu`] or [`Submenu`] @@ -49,7 +49,21 @@ impl super::IsMenuItem for Submenu { } impl super::ContextMenu for Submenu { - fn popup>( + 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

, @@ -80,8 +94,7 @@ impl super::ContextMenu for Submenu { } }) } -} -impl super::sealed::ContextMenuBase for Submenu { + fn inner(&self) -> &dyn muda::ContextMenu { &self.inner } diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index 9abb9328e813..593069013485 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -1416,10 +1416,8 @@ impl Window { } /// Shows the specified menu as a context menu at the cursor position. - /// - /// The position is relative to the window's top-left corner. pub fn popup_menu(&self, menu: &M) -> crate::Result<()> { - menu.popup(self.clone(), Option::>::None) + menu.popup(self.clone()) } /// Shows the specified menu as a context menu at the specified position. @@ -1430,7 +1428,7 @@ impl Window { menu: &M, position: P, ) -> crate::Result<()> { - menu.popup(self.clone(), Some(position)) + menu.popup_at(self.clone(), position) } } From 5203e9646b9eb4fba38de973280850fc8fe800bf Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Fri, 4 Aug 2023 04:17:04 +0300 Subject: [PATCH 096/123] Update core/tauri/src/app.rs Co-authored-by: Lucas Fernandes Nogueira --- core/tauri/src/app.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 1b2ca784927e..836be1aaebc9 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -785,7 +785,7 @@ macro_rules! shared_app_impl { } /// Runs necessary cleanup tasks before exiting the process. - /// **You sould always exit the process immediately after this function returns.** + /// **You should always exit the process immediately after this function returns.** pub fn cleanup_before_exit(&self) { #[cfg(desktop)] self.manager.inner.tray_icons.lock().unwrap().clear() From 3c0190730c012c02935a94c109b3176e15086192 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Fri, 4 Aug 2023 05:04:20 +0300 Subject: [PATCH 097/123] Add change files --- .changes/config-tray-icon-tooltip.md | 5 +++++ .changes/config-tray-icon.md | 5 +++++ .changes/runtime-create-window-handler.md | 6 ++++++ .changes/runtime-defaultvbox.md | 5 +++++ .changes/runtime-menu-system-tray.md | 6 ++++++ .changes/runtime-new-args.md | 6 ++++++ .changes/system-tray-feat.md | 7 +++++++ .changes/tauri-cleanup-before-exit.md | 5 +++++ .changes/tauri-defaultvbox.md | 5 +++++ .changes/tauri-libxdo-feat.md | 5 +++++ .changes/tauri-menu-tray-refactor.md | 17 +++++++++++++++++ .changes/tauri-nsview.md | 5 +++++ .changes/tauri-run_on_main_thread.md | 5 +++++ 13 files changed, 82 insertions(+) create mode 100644 .changes/config-tray-icon-tooltip.md create mode 100644 .changes/config-tray-icon.md create mode 100644 .changes/runtime-create-window-handler.md create mode 100644 .changes/runtime-defaultvbox.md create mode 100644 .changes/runtime-menu-system-tray.md create mode 100644 .changes/runtime-new-args.md create mode 100644 .changes/system-tray-feat.md create mode 100644 .changes/tauri-cleanup-before-exit.md create mode 100644 .changes/tauri-defaultvbox.md create mode 100644 .changes/tauri-libxdo-feat.md create mode 100644 .changes/tauri-menu-tray-refactor.md create mode 100644 .changes/tauri-nsview.md create mode 100644 .changes/tauri-run_on_main_thread.md 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..0091941fd319 --- /dev/null +++ b/.changes/system-tray-feat.md @@ -0,0 +1,7 @@ +--- +'tauri': 'major:breaking' +'tauri-runtime': 'major:breaking' +'tauri-runtime-wry': 'major:breaking' +--- + +Removed `system-tray` feature flag 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`. From 8baab5ae479d53e5ff2d56176193984cb43c9140 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Fri, 4 Aug 2023 05:04:36 +0300 Subject: [PATCH 098/123] set windows menu on macOS for default menu --- core/tauri/src/app.rs | 2 +- core/tauri/src/menu/menu.rs | 37 ++++++++++++++++++++++--------------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 836be1aaebc9..d89201070f04 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -785,7 +785,7 @@ macro_rules! shared_app_impl { } /// Runs necessary cleanup tasks before exiting the process. - /// **You should always exit the process immediately after this function returns.** + /// **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(desktop)] self.manager.inner.tray_icons.lock().unwrap().clear() diff --git a/core/tauri/src/menu/menu.rs b/core/tauri/src/menu/menu.rs index 1650c337b0ba..4735bc4b02c7 100644 --- a/core/tauri/src/menu/menu.rs +++ b/core/tauri/src/menu/menu.rs @@ -121,7 +121,21 @@ impl Menu { ..Default::default() }; - Menu::with_items( + let window_menu = Submenu::with_items( + app_handle, + "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), + &PredefinedMenuItem::about(app_handle, None, Some(about_metadata)), + ], + )?; + + let menu = Menu::with_items( app_handle, &[ #[cfg(target_os = "macos")] @@ -178,21 +192,14 @@ impl Menu { true, &[&PredefinedMenuItem::fullscreen(app_handle, None)], )?, - &Submenu::with_items( - app_handle, - "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), - &PredefinedMenuItem::about(app_handle, None, Some(about_metadata)), - ], - )?, + &window_menu, ], - ) + )?; + + #[cfg(target_os = "macos")] + window_menu.set_as_windows_menu_for_nsapp()?; + + Ok(menu) } pub(crate) fn inner(&self) -> &muda::Menu { From f677e79d02c4ce0c843b3d4d310f7d6a3e02121f Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Fri, 4 Aug 2023 05:09:21 +0300 Subject: [PATCH 099/123] remvoe default impls for builders and enhance docs --- core/tauri/src/menu/builders/check.rs | 12 ------------ core/tauri/src/menu/builders/icon.rs | 12 ------------ core/tauri/src/menu/builders/menu.rs | 3 +-- core/tauri/src/menu/builders/normal.rs | 12 ------------ core/tauri/src/menu/builders/submenu.rs | 2 +- 5 files changed, 2 insertions(+), 39 deletions(-) diff --git a/core/tauri/src/menu/builders/check.rs b/core/tauri/src/menu/builders/check.rs index cbef93a0976f..d7b221139f33 100644 --- a/core/tauri/src/menu/builders/check.rs +++ b/core/tauri/src/menu/builders/check.rs @@ -12,12 +12,6 @@ pub struct CheckMenuItemBuilder { accelerator: Option, } -impl Default for CheckMenuItemBuilder { - fn default() -> Self { - Self::new("") - } -} - impl CheckMenuItemBuilder { /// Create a new menu item builder. pub fn new>(text: S) -> Self { @@ -29,12 +23,6 @@ impl CheckMenuItemBuilder { } } - /// Set the text for this menu item. - pub fn text>(mut self, text: S) -> Self { - self.text = text.as_ref().to_string(); - self - } - /// Set the enabled state for this menu item. pub fn enabled(mut self, enabled: bool) -> Self { self.enabled = enabled; diff --git a/core/tauri/src/menu/builders/icon.rs b/core/tauri/src/menu/builders/icon.rs index c69bb2ecc051..1f26cda77948 100644 --- a/core/tauri/src/menu/builders/icon.rs +++ b/core/tauri/src/menu/builders/icon.rs @@ -15,12 +15,6 @@ pub struct IconMenuItemBuilder { accelerator: Option, } -impl Default for IconMenuItemBuilder { - fn default() -> Self { - Self::new("") - } -} - impl IconMenuItemBuilder { /// Create a new menu item builder. pub fn new>(text: S) -> Self { @@ -33,12 +27,6 @@ impl IconMenuItemBuilder { } } - /// Set the text for this menu item. - pub fn text>(mut self, text: S) -> Self { - self.text = text.as_ref().to_string(); - self - } - /// Set the enabled state for this menu item. pub fn enabled(mut self, enabled: bool) -> Self { self.enabled = enabled; diff --git a/core/tauri/src/menu/builders/menu.rs b/core/tauri/src/menu/builders/menu.rs index d0935c0b87fe..ccfe3f767946 100644 --- a/core/tauri/src/menu/builders/menu.rs +++ b/core/tauri/src/menu/builders/menu.rs @@ -18,7 +18,6 @@ use crate::{menu::*, Icon, Manager, Runtime}; /// # width: 0, /// # height: 0, /// # }; -/// # let icon2 = icon1.clone(); /// let menu = MenuBuilder::new(&handle) /// .item(&MenuItem::new(&handle, "MenuItem 1", true, None)) /// .items(&[ @@ -32,7 +31,7 @@ use crate::{menu::*, Icon, Manager, Runtime}; /// .separator() /// .text("MenuItem 2") /// .check("CheckMenuItem 2") -/// .icon("IconMenuItem 2", icon2) +/// .icon("IconMenuItem 2", app.default_window_icon().cloned().unwrap()) /// .build()?; /// app.set_menu(menu); /// Ok(()) diff --git a/core/tauri/src/menu/builders/normal.rs b/core/tauri/src/menu/builders/normal.rs index 3e7ec3229ea4..b3e6572d615e 100644 --- a/core/tauri/src/menu/builders/normal.rs +++ b/core/tauri/src/menu/builders/normal.rs @@ -11,12 +11,6 @@ pub struct MenuItemBuilder { accelerator: Option, } -impl Default for MenuItemBuilder { - fn default() -> Self { - Self::new("") - } -} - impl MenuItemBuilder { /// Create a new menu item builder. pub fn new>(text: S) -> Self { @@ -27,12 +21,6 @@ impl MenuItemBuilder { } } - /// Set the text for this menu item. - pub fn text>(mut self, text: S) -> Self { - self.text = text.as_ref().to_string(); - self - } - /// Set the enabled state for this menu item. pub fn enabled(mut self, enabled: bool) -> Self { self.enabled = enabled; diff --git a/core/tauri/src/menu/builders/submenu.rs b/core/tauri/src/menu/builders/submenu.rs index a9dcd119385a..98d7d080c33e 100644 --- a/core/tauri/src/menu/builders/submenu.rs +++ b/core/tauri/src/menu/builders/submenu.rs @@ -33,7 +33,7 @@ use crate::{menu::*, Icon, Manager, Runtime}; /// .separator() /// .text("MenuItem 2") /// .check("CheckMenuItem 2") -/// .icon("IconMenuItem 2", icon2) +/// .icon("IconMenuItem 2", app.default_window_icon().cloned().unwrap()) /// .build()?; /// menu.append(&submenu)?; /// app.set_menu(menu); From f47934601a00377d408da713b7b468b11ed59ba1 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Fri, 4 Aug 2023 07:37:49 -0300 Subject: [PATCH 100/123] export builders --- core/tauri/src/menu/builders/menu.rs | 2 +- core/tauri/src/menu/builders/submenu.rs | 2 +- core/tauri/src/menu/mod.rs | 3 ++- examples/api/src-tauri/src/lib.rs | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/core/tauri/src/menu/builders/menu.rs b/core/tauri/src/menu/builders/menu.rs index ccfe3f767946..1e1165187086 100644 --- a/core/tauri/src/menu/builders/menu.rs +++ b/core/tauri/src/menu/builders/menu.rs @@ -9,7 +9,7 @@ use crate::{menu::*, Icon, Manager, Runtime}; /// # Example /// /// ```no_run -/// # use tauri::menu::{*, builders::*}; +/// use tauri::menu::*; /// tauri::Builder::default() /// .setup(move |app| { /// let handle = app.handle(); diff --git a/core/tauri/src/menu/builders/submenu.rs b/core/tauri/src/menu/builders/submenu.rs index 98d7d080c33e..283d752459f4 100644 --- a/core/tauri/src/menu/builders/submenu.rs +++ b/core/tauri/src/menu/builders/submenu.rs @@ -9,7 +9,7 @@ use crate::{menu::*, Icon, Manager, Runtime}; /// # Example /// /// ```no_run -/// # use tauri::menu::{*, builders::*}; +/// use tauri::menu::*; /// tauri::Builder::default() /// .setup(move |app| { /// let handle = app.handle(); diff --git a/core/tauri/src/menu/mod.rs b/core/tauri/src/menu/mod.rs index 5e81734cd730..5b47ccfc6fbc 100644 --- a/core/tauri/src/menu/mod.rs +++ b/core/tauri/src/menu/mod.rs @@ -8,7 +8,7 @@ // TODO(muda-migration): figure out js events -pub mod builders; +mod builders; mod check; mod icon; #[allow(clippy::module_inception)] @@ -16,6 +16,7 @@ mod menu; mod normal; mod predefined; mod submenu; +pub use builders::*; pub use check::CheckMenuItem; pub use icon::IconMenuItem; pub use menu::Menu; diff --git a/examples/api/src-tauri/src/lib.rs b/examples/api/src-tauri/src/lib.rs index 34b1fde71ef9..21db44ca7712 100644 --- a/examples/api/src-tauri/src/lib.rs +++ b/examples/api/src-tauri/src/lib.rs @@ -13,7 +13,7 @@ mod tray; use serde::Serialize; use tauri::{ - menu::{builders::MenuBuilder, Menu}, + menu::{Menu, MenuBuilder}, window::WindowBuilder, App, AppHandle, Manager, RunEvent, Runtime, WindowUrl, }; From 27c023d9e93cae1658f02f19ea557afd71e4ed1e Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Sat, 5 Aug 2023 08:38:49 -0300 Subject: [PATCH 101/123] use manager.windows() [skip ci] --- core/tauri/src/app.rs | 32 ++++---------------------------- 1 file changed, 4 insertions(+), 28 deletions(-) diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index d89201070f04..91f924069488 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -633,13 +633,7 @@ macro_rules! shared_app_impl { // set it on all windows that don't have one or previously had the app-wide menu #[cfg(not(target_os = "macos"))] { - let windows = self - .manager - .windows_lock() - .values() - .cloned() - .collect::>(); - for window in windows { + 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())?; @@ -675,13 +669,7 @@ macro_rules! shared_app_impl { // remove from windows that have the app-wide menu #[cfg(not(target_os = "macos"))] { - let windows = self - .manager - .windows_lock() - .values() - .cloned() - .collect::>(); - for window in windows { + 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()?; @@ -718,13 +706,7 @@ macro_rules! shared_app_impl { { let is_app_menu_set = self.manager.menu_lock().is_some(); if is_app_menu_set { - let windows = self - .manager - .windows_lock() - .values() - .cloned() - .collect::>(); - for window in windows { + for window in self.manager.windows().values() { if window.has_app_wide_menu() { window.hide_menu()?; } @@ -745,13 +727,7 @@ macro_rules! shared_app_impl { { let is_app_menu_set = self.manager.menu_lock().is_some(); if is_app_menu_set { - let windows = self - .manager - .windows_lock() - .values() - .cloned() - .collect::>(); - for window in windows { + for window in self.manager.windows().values() { if window.has_app_wide_menu() { window.show_menu()?; } From 4eb031af95258718437dd717e7b9bc6980a0e143 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Sat, 5 Aug 2023 08:43:41 -0300 Subject: [PATCH 102/123] fix macos build --- core/tauri/src/menu/menu.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/tauri/src/menu/menu.rs b/core/tauri/src/menu/menu.rs index 4735bc4b02c7..9d48d2b78c4d 100644 --- a/core/tauri/src/menu/menu.rs +++ b/core/tauri/src/menu/menu.rs @@ -131,7 +131,7 @@ impl Menu { #[cfg(target_os = "macos")] &PredefinedMenuItem::separator(app_handle), &PredefinedMenuItem::close_window(app_handle, None), - &PredefinedMenuItem::about(app_handle, None, Some(about_metadata)), + &PredefinedMenuItem::about(app_handle, None, Some(about_metadata.clone())), ], )?; @@ -144,7 +144,7 @@ impl Menu { pkg_info.name.clone(), true, &[ - &PredefinedMenuItem::about(app_handle, None, Some(about_metadata.clone())), + &PredefinedMenuItem::about(app_handle, None, Some(about_metadata)), &PredefinedMenuItem::separator(app_handle), &PredefinedMenuItem::services(app_handle, None), &PredefinedMenuItem::separator(app_handle), From 1bdd397a9948b4d2d6a40f1d6228e31c07a8a616 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Sat, 5 Aug 2023 08:44:49 -0300 Subject: [PATCH 103/123] fix api example build for mobile --- examples/api/src-tauri/src/cmd.rs | 18 ++++++++++++------ examples/api/src-tauri/src/lib.rs | 17 ++++++++++------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/examples/api/src-tauri/src/cmd.rs b/examples/api/src-tauri/src/cmd.rs index 3b050d9c06e3..939ceba12f89 100644 --- a/examples/api/src-tauri/src/cmd.rs +++ b/examples/api/src-tauri/src/cmd.rs @@ -2,9 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use crate::PopupMenu; use serde::Deserialize; -use tauri::{command, Runtime, State, Window}; +use tauri::command; #[derive(Debug, Deserialize)] #[allow(unused)] @@ -24,9 +23,9 @@ pub fn perform_request(endpoint: String, body: RequestBody) -> String { "message response".into() } -#[cfg(not(target_os = "macos"))] +#[cfg(all(desktop, not(target_os = "macos")))] #[command] -pub fn toggle_menu(window: Window) { +pub fn toggle_menu(window: tauri::Window) { if window.is_menu_visible().unwrap_or_default() { let _ = window.hide_menu(); } else { @@ -36,7 +35,10 @@ pub fn toggle_menu(window: Window) { #[cfg(target_os = "macos")] #[command] -pub fn toggle_menu(app: tauri::AppHandle, app_menu: State<'_, crate::AppMenu>) { +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 { @@ -46,7 +48,11 @@ pub fn toggle_menu(app: tauri::AppHandle, app_menu: State<'_, cra } } +#[cfg(desktop)] #[command] -pub fn popup_context_menu(window: Window, popup_menu: State<'_, PopupMenu>) { +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 21db44ca7712..0d62b43c9a65 100644 --- a/examples/api/src-tauri/src/lib.rs +++ b/examples/api/src-tauri/src/lib.rs @@ -12,13 +12,12 @@ mod cmd; mod tray; use serde::Serialize; -use tauri::{ - menu::{Menu, MenuBuilder}, - window::WindowBuilder, - App, AppHandle, Manager, RunEvent, Runtime, WindowUrl, -}; +use tauri::{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; @@ -30,7 +29,8 @@ struct Reply { #[cfg(target_os = "macos")] pub struct AppMenu(pub std::sync::Mutex>>); -pub struct PopupMenu(Menu); +#[cfg(desktop)] +pub struct PopupMenu(tauri::menu::Menu); #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { @@ -60,8 +60,9 @@ pub fn run_app) + Send + 'static>( #[cfg(target_os = "macos")] app.manage(AppMenu::(Default::default())); + #[cfg(desktop)] app.manage(PopupMenu( - MenuBuilder::new(app) + tauri::menu::MenuBuilder::new(app) .check("Tauri is awesome!") .text("Do something") .copy() @@ -141,7 +142,9 @@ pub fn run_app) + Send + 'static>( .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!()) From 235f34c02367ea97874687314b8287a74de12b63 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Tue, 8 Aug 2023 17:55:02 +0300 Subject: [PATCH 104/123] Update core/tauri/src/menu/mod.rs Co-authored-by: Lucas Fernandes Nogueira --- core/tauri/src/menu/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/tauri/src/menu/mod.rs b/core/tauri/src/menu/mod.rs index 5b47ccfc6fbc..6df31547fa97 100644 --- a/core/tauri/src/menu/mod.rs +++ b/core/tauri/src/menu/mod.rs @@ -194,7 +194,7 @@ pub trait IsMenuItem: sealed::IsMenuItemBase { /// /// 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 specified position. + /// 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. From 84467d46cae9e54d4a659d998b1070ddf69ed004 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Tue, 8 Aug 2023 18:12:37 +0300 Subject: [PATCH 105/123] change app_handle function to return a reference instead --- .changes/tauri-app-handle-ref.md | 5 ++++ core/tauri/src/app.rs | 20 ++++++------- core/tauri/src/lib.rs | 6 ++-- core/tauri/src/menu/builders/menu.rs | 8 +++--- core/tauri/src/menu/builders/submenu.rs | 10 +++---- core/tauri/src/menu/check.rs | 6 ++-- core/tauri/src/menu/icon.rs | 8 +++--- core/tauri/src/menu/menu.rs | 6 ++-- core/tauri/src/menu/normal.rs | 6 ++-- core/tauri/src/menu/predefined.rs | 38 ++++++++++++------------- core/tauri/src/menu/submenu.rs | 6 ++-- core/tauri/src/tray.rs | 6 ++-- core/tauri/src/window.rs | 22 +++++++------- 13 files changed, 76 insertions(+), 71 deletions(-) create mode 100644 .changes/tauri-app-handle-ref.md 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/core/tauri/src/app.rs b/core/tauri/src/app.rs index 91f924069488..d912e4e0d425 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -324,7 +324,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()); /// }); @@ -372,7 +372,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); /// }); @@ -413,8 +413,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 } } @@ -454,7 +454,7 @@ impl ManagerBase for App { } } - fn managed_app_handle(&self) -> AppHandle { + fn managed_app_handle(&self) -> &AppHandle { self.handle() } } @@ -786,8 +786,8 @@ impl App { } /// 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. @@ -857,7 +857,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 => { @@ -908,7 +908,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, @@ -1505,7 +1505,7 @@ impl Builder { if let Some(tooltip) = &tray_config.tooltip { tray = tray.tooltip(tooltip); } - let tray = tray.build(&handle)?; + let tray = tray.build(handle)?; app.manager.inner.tray_icons.lock().unwrap().push(tray); } } diff --git a/core/tauri/src/lib.rs b/core/tauri/src/lib.rs index 8780dd24b378..fe63b61b4ab9 100644 --- a/core/tauri/src/lib.rs +++ b/core/tauri/src/lib.rs @@ -525,7 +525,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() } @@ -649,7 +649,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"); /// @@ -846,7 +846,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; } } diff --git a/core/tauri/src/menu/builders/menu.rs b/core/tauri/src/menu/builders/menu.rs index 1e1165187086..8cabbe541152 100644 --- a/core/tauri/src/menu/builders/menu.rs +++ b/core/tauri/src/menu/builders/menu.rs @@ -18,11 +18,11 @@ use crate::{menu::*, Icon, Manager, Runtime}; /// # width: 0, /// # height: 0, /// # }; -/// let menu = MenuBuilder::new(&handle) -/// .item(&MenuItem::new(&handle, "MenuItem 1", true, None)) +/// 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), +/// &CheckMenuItem::new(handle, "CheckMenuItem 1", true, true, None), +/// &IconMenuItem::new(handle, "IconMenuItem 1", true, Some(icon1), None), /// ]) /// .separator() /// .cut() diff --git a/core/tauri/src/menu/builders/submenu.rs b/core/tauri/src/menu/builders/submenu.rs index 283d752459f4..4b8bb5541938 100644 --- a/core/tauri/src/menu/builders/submenu.rs +++ b/core/tauri/src/menu/builders/submenu.rs @@ -19,12 +19,12 @@ use crate::{menu::*, Icon, Manager, Runtime}; /// # 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)) +/// 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), +/// &CheckMenuItem::new(handle, "CheckMenuItem 1", true, true, None), +/// &IconMenuItem::new(handle, "IconMenuItem 1", true, Some(icon1), None), /// ]) /// .separator() /// .cut() diff --git a/core/tauri/src/menu/check.rs b/core/tauri/src/menu/check.rs index 5c35d6e8c15a..3d04dc904725 100644 --- a/core/tauri/src/menu/check.rs +++ b/core/tauri/src/menu/check.rs @@ -67,13 +67,13 @@ impl CheckMenuItem { Self { id: item.id(), inner: item, - app_handle: manager.app_handle(), + app_handle: manager.app_handle().clone(), } } /// The application handle associated with this type. - pub fn app_handle(&self) -> AppHandle { - self.app_handle.clone() + pub fn app_handle(&self) -> &AppHandle { + &self.app_handle } /// Returns a unique identifier associated with this menu item. diff --git a/core/tauri/src/menu/icon.rs b/core/tauri/src/menu/icon.rs index b9a07f2e1051..ae18162988ef 100644 --- a/core/tauri/src/menu/icon.rs +++ b/core/tauri/src/menu/icon.rs @@ -68,7 +68,7 @@ impl IconMenuItem { Self { id: item.id(), inner: item, - app_handle: manager.app_handle(), + app_handle: manager.app_handle().clone(), } } @@ -95,13 +95,13 @@ impl IconMenuItem { Self { id: item.id(), inner: item, - app_handle: manager.app_handle(), + app_handle: manager.app_handle().clone(), } } /// The application handle associated with this type. - pub fn app_handle(&self) -> AppHandle { - self.app_handle.clone() + pub fn app_handle(&self) -> &AppHandle { + &self.app_handle } /// Returns a unique identifier associated with this menu item. diff --git a/core/tauri/src/menu/menu.rs b/core/tauri/src/menu/menu.rs index 9d48d2b78c4d..143b054d348d 100644 --- a/core/tauri/src/menu/menu.rs +++ b/core/tauri/src/menu/menu.rs @@ -95,7 +95,7 @@ impl Menu { Self { id: menu.id(), inner: menu, - app_handle: manager.app_handle(), + app_handle: manager.app_handle().clone(), } } @@ -207,8 +207,8 @@ impl Menu { } /// The application handle associated with this type. - pub fn app_handle(&self) -> AppHandle { - self.app_handle.clone() + pub fn app_handle(&self) -> &AppHandle { + &self.app_handle } /// Returns a unique identifier associated with this menu. diff --git a/core/tauri/src/menu/normal.rs b/core/tauri/src/menu/normal.rs index fe394e1e2aa3..9c8ecef931b8 100644 --- a/core/tauri/src/menu/normal.rs +++ b/core/tauri/src/menu/normal.rs @@ -65,13 +65,13 @@ impl MenuItem { Self { id: item.id(), inner: item, - app_handle: manager.app_handle(), + app_handle: manager.app_handle().clone(), } } /// The application handle associated with this type. - pub fn app_handle(&self) -> AppHandle { - self.app_handle.clone() + pub fn app_handle(&self) -> &AppHandle { + &self.app_handle } /// Returns a unique identifier associated with this menu item. diff --git a/core/tauri/src/menu/predefined.rs b/core/tauri/src/menu/predefined.rs index 3e64d00b43e2..244ef9e412a9 100644 --- a/core/tauri/src/menu/predefined.rs +++ b/core/tauri/src/menu/predefined.rs @@ -47,7 +47,7 @@ impl PredefinedMenuItem { pub fn separator>(manager: &M) -> Self { Self { inner: muda::PredefinedMenuItem::separator(), - app_handle: manager.app_handle(), + app_handle: manager.app_handle().clone(), } } @@ -55,7 +55,7 @@ impl PredefinedMenuItem { pub fn copy>(manager: &M, text: Option<&str>) -> Self { Self { inner: muda::PredefinedMenuItem::copy(text), - app_handle: manager.app_handle(), + app_handle: manager.app_handle().clone(), } } @@ -63,7 +63,7 @@ impl PredefinedMenuItem { pub fn cut>(manager: &M, text: Option<&str>) -> Self { Self { inner: muda::PredefinedMenuItem::cut(text), - app_handle: manager.app_handle(), + app_handle: manager.app_handle().clone(), } } @@ -71,7 +71,7 @@ impl PredefinedMenuItem { pub fn paste>(manager: &M, text: Option<&str>) -> Self { Self { inner: muda::PredefinedMenuItem::paste(text), - app_handle: manager.app_handle(), + app_handle: manager.app_handle().clone(), } } @@ -79,7 +79,7 @@ impl PredefinedMenuItem { pub fn select_all>(manager: &M, text: Option<&str>) -> Self { Self { inner: muda::PredefinedMenuItem::select_all(text), - app_handle: manager.app_handle(), + app_handle: manager.app_handle().clone(), } } @@ -91,7 +91,7 @@ impl PredefinedMenuItem { pub fn undo>(manager: &M, text: Option<&str>) -> Self { Self { inner: muda::PredefinedMenuItem::undo(text), - app_handle: manager.app_handle(), + app_handle: manager.app_handle().clone(), } } /// Redo menu item @@ -102,7 +102,7 @@ impl PredefinedMenuItem { pub fn redo>(manager: &M, text: Option<&str>) -> Self { Self { inner: muda::PredefinedMenuItem::redo(text), - app_handle: manager.app_handle(), + app_handle: manager.app_handle().clone(), } } @@ -114,7 +114,7 @@ impl PredefinedMenuItem { pub fn minimize>(manager: &M, text: Option<&str>) -> Self { Self { inner: muda::PredefinedMenuItem::minimize(text), - app_handle: manager.app_handle(), + app_handle: manager.app_handle().clone(), } } @@ -126,7 +126,7 @@ impl PredefinedMenuItem { pub fn maximize>(manager: &M, text: Option<&str>) -> Self { Self { inner: muda::PredefinedMenuItem::maximize(text), - app_handle: manager.app_handle(), + app_handle: manager.app_handle().clone(), } } @@ -138,7 +138,7 @@ impl PredefinedMenuItem { pub fn fullscreen>(manager: &M, text: Option<&str>) -> Self { Self { inner: muda::PredefinedMenuItem::fullscreen(text), - app_handle: manager.app_handle(), + app_handle: manager.app_handle().clone(), } } @@ -150,7 +150,7 @@ impl PredefinedMenuItem { pub fn hide>(manager: &M, text: Option<&str>) -> Self { Self { inner: muda::PredefinedMenuItem::hide(text), - app_handle: manager.app_handle(), + app_handle: manager.app_handle().clone(), } } @@ -162,7 +162,7 @@ impl PredefinedMenuItem { pub fn hide_others>(manager: &M, text: Option<&str>) -> Self { Self { inner: muda::PredefinedMenuItem::hide_others(text), - app_handle: manager.app_handle(), + app_handle: manager.app_handle().clone(), } } @@ -174,7 +174,7 @@ impl PredefinedMenuItem { pub fn show_all>(manager: &M, text: Option<&str>) -> Self { Self { inner: muda::PredefinedMenuItem::show_all(text), - app_handle: manager.app_handle(), + app_handle: manager.app_handle().clone(), } } @@ -186,7 +186,7 @@ impl PredefinedMenuItem { pub fn close_window>(manager: &M, text: Option<&str>) -> Self { Self { inner: muda::PredefinedMenuItem::show_all(text), - app_handle: manager.app_handle(), + app_handle: manager.app_handle().clone(), } } @@ -198,7 +198,7 @@ impl PredefinedMenuItem { pub fn quit>(manager: &M, text: Option<&str>) -> Self { Self { inner: muda::PredefinedMenuItem::quit(text), - app_handle: manager.app_handle(), + app_handle: manager.app_handle().clone(), } } @@ -210,7 +210,7 @@ impl PredefinedMenuItem { ) -> Self { Self { inner: muda::PredefinedMenuItem::about(text, metadata), - app_handle: manager.app_handle(), + app_handle: manager.app_handle().clone(), } } @@ -222,7 +222,7 @@ impl PredefinedMenuItem { pub fn services>(manager: &M, text: Option<&str>) -> Self { Self { inner: muda::PredefinedMenuItem::services(text), - app_handle: manager.app_handle(), + app_handle: manager.app_handle().clone(), } } @@ -245,7 +245,7 @@ impl PredefinedMenuItem { } /// The application handle associated with this type. - pub fn app_handle(&self) -> AppHandle { - self.app_handle.clone() + 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 index 3c013837881c..23de2c6f1e76 100644 --- a/core/tauri/src/menu/submenu.rs +++ b/core/tauri/src/menu/submenu.rs @@ -111,7 +111,7 @@ impl Submenu { Self { id: submenu.id(), inner: submenu, - app_handle: manager.app_handle(), + app_handle: manager.app_handle().clone(), } } @@ -132,8 +132,8 @@ impl Submenu { } /// The application handle associated with this type. - pub fn app_handle(&self) -> AppHandle { - self.app_handle.clone() + pub fn app_handle(&self) -> &AppHandle { + &self.app_handle } /// Returns a unique identifier associated with this submenu. diff --git a/core/tauri/src/tray.rs b/core/tauri/src/tray.rs index ffb17b680a79..40d8f76e8ac7 100644 --- a/core/tauri/src/tray.rs +++ b/core/tauri/src/tray.rs @@ -149,7 +149,7 @@ impl TrayIconBuilder { let icon = TrayIcon { id, inner, - app_handle: manager.app_handle(), + app_handle: manager.app_handle().clone(), }; icon.register(&icon.app_handle, self.on_menu_event, self.on_tray_event); @@ -222,8 +222,8 @@ impl TrayIcon { } /// The application handle associated with this type. - pub fn app_handle(&self) -> AppHandle { - self.app_handle.clone() + pub fn app_handle(&self) -> &AppHandle { + &self.app_handle } /// Register a handler for menu events. diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index 593069013485..30133aaa2172 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -163,7 +163,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() @@ -187,7 +187,7 @@ 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, @@ -231,7 +231,7 @@ 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( @@ -334,9 +334,9 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { /// 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, &[ + /// 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, /// ])?, /// ])?; @@ -951,8 +951,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 } } @@ -1187,9 +1187,9 @@ impl Window { /// 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, &[ + /// 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, /// ])?, /// ])?; From d9ac3fd3e302d950faa358e4007ff625e5a707a3 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Wed, 9 Aug 2023 01:53:43 +0300 Subject: [PATCH 106/123] update to muda and tray-icon 0.8 --- core/tauri/Cargo.toml | 4 +- core/tauri/src/app.rs | 43 ++++++++------ core/tauri/src/error.rs | 4 +- core/tauri/src/manager.rs | 25 ++++---- core/tauri/src/menu/builders/check.rs | 52 ++++++++++++++--- core/tauri/src/menu/builders/icon.rs | 52 ++++++++++++++++- core/tauri/src/menu/builders/menu.rs | 29 ++++++++- core/tauri/src/menu/builders/mod.rs | 2 +- core/tauri/src/menu/builders/normal.rs | 32 +++++++++- core/tauri/src/menu/builders/submenu.rs | 37 +++++++++++- core/tauri/src/menu/check.rs | 42 ++++++++++--- core/tauri/src/menu/icon.rs | 73 ++++++++++++++++++++--- core/tauri/src/menu/menu.rs | 45 ++++++++++---- core/tauri/src/menu/mod.rs | 14 ++--- core/tauri/src/menu/normal.rs | 40 ++++++++++--- core/tauri/src/menu/predefined.rs | 78 ++++++++++++++++++------- core/tauri/src/menu/submenu.rs | 54 +++++++++++++---- core/tauri/src/tray.rs | 31 +++++----- core/tauri/src/window.rs | 5 +- examples/api/src-tauri/Cargo.lock | 72 ++++++++++++----------- examples/api/src-tauri/src/tray.rs | 46 +++++++-------- 21 files changed, 584 insertions(+), 196 deletions(-) diff --git a/core/tauri/Cargo.toml b/core/tauri/Cargo.toml index b78af5b00c38..a413655e0dd2 100644 --- a/core/tauri/Cargo.toml +++ b/core/tauri/Cargo.toml @@ -68,8 +68,8 @@ 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.7.3", default-features = false } -tray-icon = { version = "0.7.7", default-features = false } +muda = { version = "0.8", default-features = false } +tray-icon = { version = "0.8", default-features = false } [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" ] } diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index d912e4e0d425..6342e7f9d74b 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -30,7 +30,7 @@ use crate::scope::FsScope; #[cfg(desktop)] use crate::menu::{Menu, MenuEvent}; #[cfg(desktop)] -use crate::tray::{TrayIcon, TrayIconBuilder, TrayIconEvent}; +use crate::tray::{TrayIcon, TrayIconBuilder, TrayIconEvent, TrayIconId}; #[cfg(desktop)] use crate::window::WindowMenu; use raw_window_handle::HasRawDisplayHandle; @@ -414,7 +414,7 @@ impl ManagerBase for AppHandle { } fn managed_app_handle(&self) -> &AppHandle { - &self + self } } @@ -539,7 +539,11 @@ macro_rules! shared_app_impl { /// Gets a tray icon using the provided id. #[cfg(desktop)] - pub fn tray_by_id(&self, id: u32) -> Option> { + pub fn tray_by_id<'a, I>(&self, id: &'a I) -> Option> + where + I: PartialEq + ?Sized, + TrayIconId: PartialEq<&'a I>, + { self .manager .inner @@ -547,7 +551,7 @@ macro_rules! shared_app_impl { .lock() .unwrap() .iter() - .find(|t| t.id() == id) + .find(|t| t.id().eq(&id)) .cloned() } @@ -555,15 +559,16 @@ macro_rules! shared_app_impl { /// /// Note that dropping the returned icon, will cause the tray icon to disappear. #[cfg(desktop)] - pub fn remove_tray_by_id(&self, id: u32) -> Option> { + pub fn remove_tray_by_id<'a, I>(&self, id: &'a I) -> Option> + where + I: PartialEq + ?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); - + let idx = tray_icons.iter().position(|t| t.id().eq(&id)); if let Some(idx) = idx { return Some(tray_icons.swap_remove(idx)); } - None } @@ -1443,7 +1448,7 @@ impl Builder { app .manager .menus_stash_lock() - .insert(menu.id(), menu.clone()); + .insert(menu.id().clone(), menu.clone()); #[cfg(target_os = "macos")] menu.inner().init_for_nsapp(); @@ -1510,7 +1515,7 @@ impl Builder { } } - app.manager.initialize_plugins(&handle)?; + app.manager.initialize_plugins(handle)?; Ok(app) } @@ -1638,7 +1643,7 @@ fn on_event_loop_event, RunEvent) + 'static>( RuntimeRunEvent::UserEvent(t) => { match t { #[cfg(desktop)] - EventLoopMessage::MenuEvent(e) => { + EventLoopMessage::MenuEvent(ref e) => { for listener in &*app_handle .manager .inner @@ -1646,7 +1651,7 @@ fn on_event_loop_event, RunEvent) + 'static>( .lock() .unwrap() { - listener(app_handle, e); + listener(app_handle, e.clone()); } for (label, listener) in &*app_handle .manager @@ -1656,12 +1661,12 @@ fn on_event_loop_event, RunEvent) + 'static>( .unwrap() { if let Some(w) = app_handle.get_window(label) { - listener(&w, e); + listener(&w, e.clone()); } } } #[cfg(desktop)] - EventLoopMessage::TrayIconEvent(e) => { + EventLoopMessage::TrayIconEvent(ref e) => { for listener in &*app_handle .manager .inner @@ -1669,7 +1674,7 @@ fn on_event_loop_event, RunEvent) + 'static>( .lock() .unwrap() { - listener(app_handle, e); + listener(app_handle, e.clone()); } for (id, listener) in &*app_handle @@ -1679,9 +1684,9 @@ fn on_event_loop_event, RunEvent) + 'static>( .lock() .unwrap() { - if e.id == *id { - if let Some(tray) = app_handle.tray_by_id(*id) { - listener(&tray, e); + if e.id == id { + if let Some(tray) = app_handle.tray_by_id(id) { + listener(&tray, e.clone()); } } } diff --git a/core/tauri/src/error.rs b/core/tauri/src/error.rs index 395ef25d8e67..26f9c4e389eb 100644 --- a/core/tauri/src/error.rs +++ b/core/tauri/src/error.rs @@ -99,7 +99,7 @@ pub enum Error { /// Bad menu icon error. #[error(transparent)] #[cfg(desktop)] - BadMenuIcon(#[from] muda::icon::BadIcon), + BadMenuIcon(#[from] muda::BadIcon), /// Tray icon error. #[error("tray icon error: {0}")] #[cfg(desktop)] @@ -107,5 +107,5 @@ pub enum Error { /// Bad tray icon error. #[error(transparent)] #[cfg(desktop)] - BadTrayIcon(#[from] tray_icon::icon::BadIcon), + BadTrayIcon(#[from] tray_icon::BadIcon), } diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index 11c8b15c48c4..52d193836f63 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -11,7 +11,10 @@ use std::{ }; #[cfg(desktop)] -use crate::{menu::Menu, tray::TrayIcon}; +use crate::{ + menu::{Menu, MenuId}, + tray::{TrayIcon, TrayIconId}, +}; use serde::Serialize; use serde_json::Value as JsonValue; use serialize_to_javascript::{default_template, DefaultTemplate, Template}; @@ -227,7 +230,7 @@ pub struct InnerWindowManager { /// This should be mainly used to acceess [`Menu::haccel`] /// to setup the accelerator handling in the event loop #[cfg(desktop)] - pub menus: Arc>>>, + pub menus: Arc>>>, /// The menu set to all windows. #[cfg(desktop)] pub(crate) menu: Arc>>>, @@ -250,7 +253,7 @@ pub struct InnerWindowManager { /// Tray icon event listeners. #[cfg(desktop)] pub(crate) tray_event_listeners: - Arc>>>>, + Arc>>>>, /// Responder for invoke calls. invoke_responder: Arc>, /// The script that initializes the invoke system. @@ -396,7 +399,7 @@ impl WindowManager { if let Some(menu) = window_menu { self .menus_stash_lock() - .insert(menu.menu.id(), menu.menu.clone()); + .insert(menu.menu.id().clone(), menu.menu.clone()); } } @@ -433,31 +436,33 @@ impl WindowManager { /// Menus stash. #[cfg(desktop)] - pub(crate) fn menus_stash_lock(&self) -> MutexGuard<'_, HashMap>> { + 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: u32) -> bool { + pub(crate) fn is_menu_in_use>(&self, id: &I) -> bool { self .menu_lock() .as_ref() - .map(|m| m.id() == id) + .map(|m| PartialEq::eq(id, 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(), menu.clone()); + self + .menus_stash_lock() + .insert(menu.id().clone(), menu.clone()); } #[cfg(desktop)] - pub(crate) fn remove_menu_from_stash_by_id(&self, id: Option) { + 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); + self.menus_stash_lock().remove(id); } } } diff --git a/core/tauri/src/menu/builders/check.rs b/core/tauri/src/menu/builders/check.rs index d7b221139f33..9df4e8eb0742 100644 --- a/core/tauri/src/menu/builders/check.rs +++ b/core/tauri/src/menu/builders/check.rs @@ -2,10 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT +use muda::MenuId; + use crate::{menu::CheckMenuItem, Manager, Runtime}; /// A builder type for [`CheckMenuItem`] pub struct CheckMenuItemBuilder { + id: Option, text: String, enabled: bool, checked: bool, @@ -14,8 +17,12 @@ pub struct CheckMenuItemBuilder { 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, @@ -23,6 +30,26 @@ impl CheckMenuItemBuilder { } } + /// 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; @@ -43,12 +70,23 @@ impl CheckMenuItemBuilder { /// Build the menu item pub fn build>(self, manager: &M) -> CheckMenuItem { - CheckMenuItem::new( - manager, - self.text, - self.enabled, - self.checked, - self.accelerator, - ) + 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 index 1f26cda77948..d2b2aa0d54cd 100644 --- a/core/tauri/src/menu/builders/icon.rs +++ b/core/tauri/src/menu/builders/icon.rs @@ -2,12 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use muda::icon::NativeIcon; +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, @@ -17,8 +18,12 @@ pub struct IconMenuItemBuilder { 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, @@ -27,6 +32,27 @@ impl IconMenuItemBuilder { } } + /// 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; @@ -62,11 +88,31 @@ impl IconMenuItemBuilder { /// Build the menu item pub fn build>(self, manager: &M) -> IconMenuItem { if self.icon.is_some() { - IconMenuItem::new( + 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.icon, + self.native_icon, self.accelerator, ) } else { diff --git a/core/tauri/src/menu/builders/menu.rs b/core/tauri/src/menu/builders/menu.rs index 8cabbe541152..215596fa8e79 100644 --- a/core/tauri/src/menu/builders/menu.rs +++ b/core/tauri/src/menu/builders/menu.rs @@ -38,6 +38,7 @@ use crate::{menu::*, Icon, Manager, Runtime}; /// }); /// ``` pub struct MenuBuilder<'m, R: Runtime, M: Manager> { + id: Option, manager: &'m M, items: Vec>, } @@ -46,11 +47,27 @@ 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()); @@ -283,14 +300,22 @@ impl<'m, R: Runtime, M: Manager> MenuBuilder<'m, R, M> { /// Builds this menu pub fn build(self) -> crate::Result> { if self.items.is_empty() { - Ok(Menu::new(self.manager)) + 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::>(); - Menu::with_items(self.manager, &items) + 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 index 9c9b8f87a7c2..f86baee6b27b 100644 --- a/core/tauri/src/menu/builders/mod.rs +++ b/core/tauri/src/menu/builders/mod.rs @@ -6,7 +6,7 @@ //! A module containting menu builder types -pub use muda::builders::AboutMetadataBuilder; +pub use muda::AboutMetadataBuilder; mod menu; pub use menu::MenuBuilder; diff --git a/core/tauri/src/menu/builders/normal.rs b/core/tauri/src/menu/builders/normal.rs index b3e6572d615e..9512b0d0b2dd 100644 --- a/core/tauri/src/menu/builders/normal.rs +++ b/core/tauri/src/menu/builders/normal.rs @@ -2,10 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT +use muda::MenuId; + use crate::{menu::MenuItem, Manager, Runtime}; /// A builder type for [`MenuItem`] pub struct MenuItemBuilder { + id: Option, text: String, enabled: bool, accelerator: Option, @@ -13,14 +16,37 @@ pub struct MenuItemBuilder { 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; @@ -35,6 +61,10 @@ impl MenuItemBuilder { /// Build the menu item pub fn build>(self, manager: &M) -> MenuItem { - MenuItem::new(manager, self.text, self.enabled, self.accelerator) + 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 index 4b8bb5541938..ee06420dea10 100644 --- a/core/tauri/src/menu/builders/submenu.rs +++ b/core/tauri/src/menu/builders/submenu.rs @@ -41,6 +41,7 @@ use crate::{menu::*, Icon, Manager, Runtime}; /// }); /// ``` pub struct SubmenuBuilder<'m, R: Runtime, M: Manager> { + id: Option, manager: &'m M, text: String, enabled: bool, @@ -49,8 +50,12 @@ pub struct SubmenuBuilder<'m, R: Runtime, M: Manager> { 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, @@ -58,6 +63,26 @@ impl<'m, R: Runtime, M: Manager> SubmenuBuilder<'m, R, M> { } } + /// 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; @@ -296,14 +321,22 @@ impl<'m, R: Runtime, M: Manager> SubmenuBuilder<'m, R, M> { /// Builds this submenu pub fn build(self) -> crate::Result> { if self.items.is_empty() { - Ok(Submenu::new(self.manager, self.text, self.enabled)) + 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::>(); - Submenu::with_items(self.manager, self.text, self.enabled, &items) + 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 index 3d04dc904725..cbc64f9f1bfc 100644 --- a/core/tauri/src/menu/check.rs +++ b/core/tauri/src/menu/check.rs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT +use muda::MenuId; + use crate::{run_main_thread, AppHandle, Manager, Runtime}; /// A menu item inside a [`Menu`] or [`Submenu`] and contains only text. @@ -9,7 +11,7 @@ use crate::{run_main_thread, AppHandle, Manager, Runtime}; /// [`Menu`]: super::Menu /// [`Submenu`]: super::Submenu pub struct CheckMenuItem { - pub(crate) id: u32, + pub(crate) id: MenuId, pub(crate) inner: muda::CheckMenuItem, pub(crate) app_handle: AppHandle, } @@ -17,7 +19,7 @@ pub struct CheckMenuItem { impl Clone for CheckMenuItem { fn clone(&self) -> Self { Self { - id: self.id, + id: self.id.clone(), inner: self.inner.clone(), app_handle: self.app_handle.clone(), } @@ -41,8 +43,8 @@ impl super::IsMenuItem for CheckMenuItem { super::MenuItemKind::Check(self.clone()) } - fn id(&self) -> u32 { - self.id + fn id(&self) -> &MenuId { + &self.id } } @@ -65,7 +67,33 @@ impl CheckMenuItem { acccelerator.and_then(|s| s.as_ref().parse().ok()), ); Self { - id: item.id(), + 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(), } @@ -77,8 +105,8 @@ impl CheckMenuItem { } /// Returns a unique identifier associated with this menu item. - pub fn id(&self) -> u32 { - self.id + pub fn id(&self) -> &MenuId { + &self.id } /// Get the text for this menu item. diff --git a/core/tauri/src/menu/icon.rs b/core/tauri/src/menu/icon.rs index ae18162988ef..8bb061fdcc1a 100644 --- a/core/tauri/src/menu/icon.rs +++ b/core/tauri/src/menu/icon.rs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT +use muda::MenuId; + use super::NativeIcon; use crate::{run_main_thread, AppHandle, Icon, Manager, Runtime}; @@ -10,7 +12,7 @@ use crate::{run_main_thread, AppHandle, Icon, Manager, Runtime}; /// [`Menu`]: super::Menu /// [`Submenu`]: super::Submenu pub struct IconMenuItem { - pub(crate) id: u32, + pub(crate) id: MenuId, pub(crate) inner: muda::IconMenuItem, pub(crate) app_handle: AppHandle, } @@ -18,7 +20,7 @@ pub struct IconMenuItem { impl Clone for IconMenuItem { fn clone(&self) -> Self { Self { - id: self.id, + id: self.id.clone(), inner: self.inner.clone(), app_handle: self.app_handle.clone(), } @@ -42,8 +44,8 @@ impl super::IsMenuItem for IconMenuItem { super::MenuItemKind::Icon(self.clone()) } - fn id(&self) -> u32 { - self.id + fn id(&self) -> &MenuId { + &self.id } } @@ -66,7 +68,33 @@ impl IconMenuItem { acccelerator.and_then(|s| s.as_ref().parse().ok()), ); Self { - id: item.id(), + 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(), } @@ -93,7 +121,36 @@ impl IconMenuItem { acccelerator.and_then(|s| s.as_ref().parse().ok()), ); Self { - id: item.id(), + 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(), } @@ -105,8 +162,8 @@ impl IconMenuItem { } /// Returns a unique identifier associated with this menu item. - pub fn id(&self) -> u32 { - self.id + pub fn id(&self) -> &MenuId { + &self.id } /// Get the text for this menu item. diff --git a/core/tauri/src/menu/menu.rs b/core/tauri/src/menu/menu.rs index 143b054d348d..b7d7a44fdb1e 100644 --- a/core/tauri/src/menu/menu.rs +++ b/core/tauri/src/menu/menu.rs @@ -6,13 +6,13 @@ use super::sealed::ContextMenuBase; use super::{IsMenuItem, MenuItemKind, PredefinedMenuItem, Submenu}; use crate::Window; use crate::{run_main_thread, AppHandle, Manager, Position, Runtime}; -use muda::AboutMetadata; use muda::ContextMenu; +use muda::{AboutMetadata, MenuId}; /// 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: u32, + pub(crate) id: MenuId, pub(crate) inner: muda::Menu, pub(crate) app_handle: AppHandle, } @@ -26,7 +26,7 @@ unsafe impl Send for Menu {} impl Clone for Menu { fn clone(&self) -> Self { Self { - id: self.id, + id: self.id.clone(), inner: self.inner.clone(), app_handle: self.app_handle.clone(), } @@ -93,7 +93,17 @@ impl Menu { pub fn new>(manager: &M) -> Self { let menu = muda::Menu::new(); Self { - id: menu.id(), + 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(), } @@ -109,6 +119,18 @@ impl Menu { 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(); @@ -131,7 +153,7 @@ impl Menu { #[cfg(target_os = "macos")] &PredefinedMenuItem::separator(app_handle), &PredefinedMenuItem::close_window(app_handle, None), - &PredefinedMenuItem::about(app_handle, None, Some(about_metadata.clone())), + &PredefinedMenuItem::about(app_handle, None, Some(about_metadata)), ], )?; @@ -212,8 +234,8 @@ impl Menu { } /// Returns a unique identifier associated with this menu. - pub fn id(&self) -> u32 { - self.id + pub fn id(&self) -> &MenuId { + &self.id } /// Add a menu item to the end of this menu. @@ -316,28 +338,29 @@ impl Menu { .into_iter() .map(|i| match i { muda::MenuItemKind::MenuItem(i) => super::MenuItemKind::MenuItem(super::MenuItem { - id: i.id(), + id: i.id().clone(), inner: i, app_handle: handle.clone(), }), muda::MenuItemKind::Submenu(i) => super::MenuItemKind::Submenu(super::Submenu { - id: i.id(), + 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(), + id: i.id().clone(), inner: i, app_handle: handle.clone(), }), muda::MenuItemKind::Icon(i) => super::MenuItemKind::Icon(super::IconMenuItem { - id: i.id(), + id: i.id().clone(), inner: i, app_handle: handle.clone(), }), diff --git a/core/tauri/src/menu/mod.rs b/core/tauri/src/menu/mod.rs index 6df31547fa97..7c8354d83039 100644 --- a/core/tauri/src/menu/mod.rs +++ b/core/tauri/src/menu/mod.rs @@ -25,7 +25,7 @@ pub use predefined::PredefinedMenuItem; pub use submenu::Submenu; use crate::Runtime; -pub use muda::{icon::NativeIcon, AboutMetadata, MenuEvent}; +pub use muda::{AboutMetadata, MenuEvent, MenuId, NativeIcon}; /// An enumeration of all menu item kinds that could be added to /// a [`Menu`] or [`Submenu`] @@ -44,7 +44,7 @@ pub enum MenuItemKind { impl MenuItemKind { /// Returns a unique identifier associated with this menu item. - pub fn id(&self) -> u32 { + pub fn id(&self) -> &MenuId { match self { MenuItemKind::MenuItem(i) => i.id(), MenuItemKind::Submenu(i) => i.id(), @@ -168,7 +168,7 @@ impl IsMenuItem for MenuItemKind { self.clone() } - fn id(&self) -> u32 { + fn id(&self) -> &MenuId { self.id() } } @@ -183,9 +183,7 @@ pub trait IsMenuItem: sealed::IsMenuItemBase { fn kind(&self) -> MenuItemKind; /// Returns a unique identifier associated with this menu. - fn id(&self) -> u32 { - self.kind().id() - } + fn id(&self) -> &MenuId; } /// A helper trait with methods to help creating a context menu. @@ -224,12 +222,12 @@ pub(crate) mod sealed { } } -impl TryFrom for muda::icon::Icon { +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::Icon::from_rgba(value.rgba, value.width, value.height).map_err(Into::into) + muda::Icon::from_rgba(value.rgba, value.width, value.height).map_err(Into::into) } } diff --git a/core/tauri/src/menu/normal.rs b/core/tauri/src/menu/normal.rs index 9c8ecef931b8..25d79919365a 100644 --- a/core/tauri/src/menu/normal.rs +++ b/core/tauri/src/menu/normal.rs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT +use muda::MenuId; + use crate::{run_main_thread, AppHandle, Manager, Runtime}; /// A menu item inside a [`Menu`] or [`Submenu`] and contains only text. @@ -9,7 +11,7 @@ use crate::{run_main_thread, AppHandle, Manager, Runtime}; /// [`Menu`]: super::Menu /// [`Submenu`]: super::Submenu pub struct MenuItem { - pub(crate) id: u32, + pub(crate) id: MenuId, pub(crate) inner: muda::MenuItem, pub(crate) app_handle: AppHandle, } @@ -17,7 +19,7 @@ pub struct MenuItem { impl Clone for MenuItem { fn clone(&self) -> Self { Self { - id: self.id, + id: self.id.clone(), inner: self.inner.clone(), app_handle: self.app_handle.clone(), } @@ -41,8 +43,8 @@ impl super::IsMenuItem for MenuItem { super::MenuItemKind::MenuItem(self.clone()) } - fn id(&self) -> u32 { - self.id + fn id(&self) -> &MenuId { + &self.id } } @@ -63,7 +65,31 @@ impl MenuItem { acccelerator.and_then(|s| s.as_ref().parse().ok()), ); Self { - id: item.id(), + 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(), } @@ -75,8 +101,8 @@ impl MenuItem { } /// Returns a unique identifier associated with this menu item. - pub fn id(&self) -> u32 { - self.id + pub fn id(&self) -> &MenuId { + &self.id } /// Get the text for this menu item. diff --git a/core/tauri/src/menu/predefined.rs b/core/tauri/src/menu/predefined.rs index 244ef9e412a9..12499fa2a8e7 100644 --- a/core/tauri/src/menu/predefined.rs +++ b/core/tauri/src/menu/predefined.rs @@ -2,11 +2,14 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT +use muda::MenuId; + use super::AboutMetadata; use crate::{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, } @@ -14,6 +17,7 @@ pub struct PredefinedMenuItem { impl Clone for PredefinedMenuItem { fn clone(&self) -> Self { Self { + id: self.id.clone(), inner: self.inner.clone(), app_handle: self.app_handle.clone(), } @@ -37,7 +41,7 @@ impl super::IsMenuItem for PredefinedMenuItem { super::MenuItemKind::Predefined(self.clone()) } - fn id(&self) -> u32 { + fn id(&self) -> &MenuId { self.id() } } @@ -45,40 +49,50 @@ impl super::IsMenuItem for PredefinedMenuItem { impl PredefinedMenuItem { /// Separator menu item pub fn separator>(manager: &M) -> Self { + let inner = muda::PredefinedMenuItem::separator(); Self { - inner: muda::PredefinedMenuItem::separator(), + 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 { - inner: muda::PredefinedMenuItem::copy(text), + 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 { - inner: muda::PredefinedMenuItem::cut(text), + 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 { - inner: muda::PredefinedMenuItem::paste(text), + 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 { - inner: muda::PredefinedMenuItem::select_all(text), + id: inner.id().clone(), + inner, app_handle: manager.app_handle().clone(), } } @@ -89,8 +103,10 @@ impl PredefinedMenuItem { /// /// - **Windows / Linux:** Unsupported. pub fn undo>(manager: &M, text: Option<&str>) -> Self { + let inner = muda::PredefinedMenuItem::undo(text); Self { - inner: muda::PredefinedMenuItem::undo(text), + id: inner.id().clone(), + inner, app_handle: manager.app_handle().clone(), } } @@ -100,8 +116,10 @@ impl PredefinedMenuItem { /// /// - **Windows / Linux:** Unsupported. pub fn redo>(manager: &M, text: Option<&str>) -> Self { + let inner = muda::PredefinedMenuItem::redo(text); Self { - inner: muda::PredefinedMenuItem::redo(text), + id: inner.id().clone(), + inner, app_handle: manager.app_handle().clone(), } } @@ -112,8 +130,10 @@ impl PredefinedMenuItem { /// /// - **Linux:** Unsupported. pub fn minimize>(manager: &M, text: Option<&str>) -> Self { + let inner = muda::PredefinedMenuItem::minimize(text); Self { - inner: muda::PredefinedMenuItem::minimize(text), + id: inner.id().clone(), + inner, app_handle: manager.app_handle().clone(), } } @@ -124,8 +144,10 @@ impl PredefinedMenuItem { /// /// - **Linux:** Unsupported. pub fn maximize>(manager: &M, text: Option<&str>) -> Self { + let inner = muda::PredefinedMenuItem::maximize(text); Self { - inner: muda::PredefinedMenuItem::maximize(text), + id: inner.id().clone(), + inner, app_handle: manager.app_handle().clone(), } } @@ -136,8 +158,10 @@ impl PredefinedMenuItem { /// /// - **Windows / Linux:** Unsupported. pub fn fullscreen>(manager: &M, text: Option<&str>) -> Self { + let inner = muda::PredefinedMenuItem::fullscreen(text); Self { - inner: muda::PredefinedMenuItem::fullscreen(text), + id: inner.id().clone(), + inner, app_handle: manager.app_handle().clone(), } } @@ -148,8 +172,10 @@ impl PredefinedMenuItem { /// /// - **Linux:** Unsupported. pub fn hide>(manager: &M, text: Option<&str>) -> Self { + let inner = muda::PredefinedMenuItem::hide(text); Self { - inner: muda::PredefinedMenuItem::hide(text), + id: inner.id().clone(), + inner, app_handle: manager.app_handle().clone(), } } @@ -160,8 +186,10 @@ impl PredefinedMenuItem { /// /// - **Linux:** Unsupported. pub fn hide_others>(manager: &M, text: Option<&str>) -> Self { + let inner = muda::PredefinedMenuItem::hide_others(text); Self { - inner: muda::PredefinedMenuItem::hide_others(text), + id: inner.id().clone(), + inner, app_handle: manager.app_handle().clone(), } } @@ -172,8 +200,10 @@ impl PredefinedMenuItem { /// /// - **Windows / Linux:** Unsupported. pub fn show_all>(manager: &M, text: Option<&str>) -> Self { + let inner = muda::PredefinedMenuItem::show_all(text); Self { - inner: muda::PredefinedMenuItem::show_all(text), + id: inner.id().clone(), + inner, app_handle: manager.app_handle().clone(), } } @@ -184,8 +214,10 @@ impl PredefinedMenuItem { /// /// - **Linux:** Unsupported. pub fn close_window>(manager: &M, text: Option<&str>) -> Self { + let inner = muda::PredefinedMenuItem::show_all(text); Self { - inner: muda::PredefinedMenuItem::show_all(text), + id: inner.id().clone(), + inner, app_handle: manager.app_handle().clone(), } } @@ -196,8 +228,10 @@ impl PredefinedMenuItem { /// /// - **Linux:** Unsupported. pub fn quit>(manager: &M, text: Option<&str>) -> Self { + let inner = muda::PredefinedMenuItem::quit(text); Self { - inner: muda::PredefinedMenuItem::quit(text), + id: inner.id().clone(), + inner, app_handle: manager.app_handle().clone(), } } @@ -208,8 +242,10 @@ impl PredefinedMenuItem { text: Option<&str>, metadata: Option, ) -> Self { + let inner = muda::PredefinedMenuItem::about(text, metadata); Self { - inner: muda::PredefinedMenuItem::about(text, metadata), + id: inner.id().clone(), + inner, app_handle: manager.app_handle().clone(), } } @@ -220,15 +256,17 @@ impl PredefinedMenuItem { /// /// - **Windows / Linux:** Unsupported. pub fn services>(manager: &M, text: Option<&str>) -> Self { + let inner = muda::PredefinedMenuItem::services(text); Self { - inner: muda::PredefinedMenuItem::services(text), + id: inner.id().clone(), + inner, app_handle: manager.app_handle().clone(), } } /// Returns a unique identifier associated with this menu item. - pub fn id(&self) -> u32 { - 0 + pub fn id(&self) -> &MenuId { + &self.id } /// Get the text for this menu item. diff --git a/core/tauri/src/menu/submenu.rs b/core/tauri/src/menu/submenu.rs index 23de2c6f1e76..1abec3cae478 100644 --- a/core/tauri/src/menu/submenu.rs +++ b/core/tauri/src/menu/submenu.rs @@ -4,14 +4,14 @@ use super::{sealed::ContextMenuBase, IsMenuItem, MenuItemKind}; use crate::{run_main_thread, AppHandle, Manager, Position, Runtime, Window}; -use muda::ContextMenu; +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: u32, + pub(crate) id: MenuId, pub(crate) inner: muda::Submenu, pub(crate) app_handle: AppHandle, } @@ -25,7 +25,7 @@ unsafe impl Send for Submenu {} impl Clone for Submenu { fn clone(&self) -> Self { Self { - id: self.id, + id: self.id.clone(), inner: self.inner.clone(), app_handle: self.app_handle.clone(), } @@ -43,8 +43,8 @@ impl super::IsMenuItem for Submenu { super::MenuItemKind::Submenu(self.clone()) } - fn id(&self) -> u32 { - self.id + fn id(&self) -> &MenuId { + &self.id } } @@ -109,12 +109,27 @@ impl Submenu { pub fn new, S: AsRef>(manager: &M, text: S, enabled: bool) -> Self { let submenu = muda::Submenu::new(text, enabled); Self { - id: submenu.id(), + 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, @@ -127,6 +142,20 @@ impl Submenu { 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 } @@ -137,8 +166,8 @@ impl Submenu { } /// Returns a unique identifier associated with this submenu. - pub fn id(&self) -> u32 { - self.id + pub fn id(&self) -> &MenuId { + &self.id } /// Add a menu item to the end of this submenu. @@ -206,28 +235,29 @@ impl Submenu { .into_iter() .map(|i| match i { muda::MenuItemKind::MenuItem(i) => super::MenuItemKind::MenuItem(super::MenuItem { - id: i.id(), + id: i.id().clone(), inner: i, app_handle: handle.clone(), }), muda::MenuItemKind::Submenu(i) => super::MenuItemKind::Submenu(super::Submenu { - id: i.id(), + 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(), + id: i.id().clone(), inner: i, app_handle: handle.clone(), }), muda::MenuItemKind::Icon(i) => super::MenuItemKind::Icon(super::IconMenuItem { - id: i.id(), + id: i.id().clone(), inner: i, app_handle: handle.clone(), }), diff --git a/core/tauri/src/tray.rs b/core/tauri/src/tray.rs index 40d8f76e8ac7..f10b3ee28301 100644 --- a/core/tauri/src/tray.rs +++ b/core/tauri/src/tray.rs @@ -11,7 +11,7 @@ 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}; +pub use tray_icon::{ClickType, Rectangle, TrayIconEvent, TrayIconId}; // TODO(muda-migration): figure out js events @@ -38,8 +38,13 @@ impl TrayIconBuilder { } } - /// Sets the unique id to build the tray icon with. - pub fn with_id(id: u32) -> Self { + /// 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 @@ -138,13 +143,13 @@ impl TrayIconBuilder { /// Access the unique id that will be assigned to the tray icon /// this builder will create. - pub fn id(&self) -> u32 { + 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(); + let id = self.id().clone(); let inner = self.inner.build()?; let icon = TrayIcon { id, @@ -164,7 +169,7 @@ impl TrayIconBuilder { /// /// See [TrayIconBuilder] to construct this type. pub struct TrayIcon { - id: u32, + id: TrayIconId, inner: tray_icon::TrayIcon, app_handle: AppHandle, } @@ -172,7 +177,7 @@ pub struct TrayIcon { impl Clone for TrayIcon { fn clone(&self) -> Self { Self { - id: self.id, + id: self.id.clone(), inner: self.inner.clone(), app_handle: self.app_handle.clone(), } @@ -209,7 +214,7 @@ impl TrayIcon { .tray_event_listeners .lock() .unwrap() - .insert(self.id, handler); + .insert(self.id.clone(), handler); } app_handle @@ -250,12 +255,12 @@ impl TrayIcon { .tray_event_listeners .lock() .unwrap() - .insert(self.id, Box::new(f)); + .insert(self.id.clone(), Box::new(f)); } /// Returns the id associated with this tray icon. - pub fn id(&self) -> u32 { - self.id + pub fn id(&self) -> &TrayIconId { + &self.id } /// Set new tray icon. If `None` is provided, it will remove the icon. @@ -336,11 +341,11 @@ impl TrayIcon { } } -impl TryFrom for tray_icon::icon::Icon { +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::Icon::from_rgba(value.rgba, value.width, value.height).map_err(Into::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 30133aaa2172..e6e3cf7f4a65 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -4,6 +4,7 @@ //! The Tauri window types and functions. +use muda::MenuId; pub use tauri_utils::{config::Color, WindowEffect as Effect, WindowEffectState as EffectState}; use url::Url; @@ -1234,11 +1235,11 @@ impl Window { } #[cfg_attr(target_os = "macos", allow(dead_code))] - pub(crate) fn is_menu_in_use(&self, id: u32) -> bool { + pub(crate) fn is_menu_in_use>(&self, id: &I) -> bool { self .menu_lock() .as_ref() - .map(|m| m.menu.id() == id) + .map(|m| PartialEq::eq(id, m.menu.id())) .unwrap_or(false) } diff --git a/examples/api/src-tauri/Cargo.lock b/examples/api/src-tauri/Cargo.lock index be966ead8e09..4810482f575a 100644 --- a/examples/api/src-tauri/Cargo.lock +++ b/examples/api/src-tauri/Cargo.lock @@ -502,9 +502,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c6b2562119bf28c3439f7f02db99faf0aa1a8cdfe5772a2ee155d32227239f0" +checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" dependencies = [ "libc", ] @@ -573,18 +573,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.19" +version = "4.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd304a20bff958a57f04c4e96a2e7594cc4490a0e809cbd48bb6437edaa452d" +checksum = "c27cdf28c0f604ba3f512b0c9a409f8de8513e4816705deb0498b627e7c3a3fd" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.3.19" +version = "4.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01c6a3f08f1fe5662a35cfe393aec09c4df95f60ee93b7556505260f75eee9e1" +checksum = "08a9f1ab5e9f01a9b81f202e8562eb9a10de70abf9eaeac1be465c28b75aa4aa" dependencies = [ "anstream", "anstyle", @@ -1794,6 +1794,7 @@ checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" dependencies = [ "equivalent", "hashbrown 0.14.0", + "serde", ] [[package]] @@ -1856,7 +1857,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", - "rustix 0.38.6", + "rustix 0.38.7", "windows-sys 0.48.0", ] @@ -2184,9 +2185,9 @@ dependencies = [ [[package]] name = "muda" -version = "0.7.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e375211006d809e35f8b1488b37d0716a474a6a1d8ecc268ec6d2756cf4fbb49" +checksum = "ed8a1fd60c7b522b2e5c14c059195f16aa94d826e4549dfbedef5fbbc015a243" dependencies = [ "cocoa 0.25.0", "crossbeam-channel", @@ -2571,9 +2572,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" +checksum = "2c516611246607d0c04186886dbb3a754368ef82c79e9827a802c6d836dd111c" [[package]] name = "pin-utils" @@ -2839,13 +2840,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.1" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.3.4", + "regex-automata 0.3.6", "regex-syntax 0.7.4", ] @@ -2860,9 +2861,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.4" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294" +checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" dependencies = [ "aho-corasick", "memchr", @@ -2948,9 +2949,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.6" +version = "0.38.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ee020b1716f0a80e2ace9b03441a749e402e86712f15f16fe8a8f75afac732f" +checksum = "172891ebdceb05aa0005f533a6cbfca599ddd7d966f6f5d4d9b2e70478e70399" dependencies = [ "bitflags 2.3.3", "errno", @@ -3026,18 +3027,18 @@ checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" [[package]] name = "serde" -version = "1.0.180" +version = "1.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea67f183f058fe88a4e3ec6e2788e003840893b91bac4559cabedd00863b3ed" +checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.180" +version = "1.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24e744d7782b686ab3b73267ef05697159cc0e5abbed3f47f9933165e5219036" +checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816" dependencies = [ "proc-macro2", "quote", @@ -3089,14 +3090,15 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e47d95bc83ed33b2ecf84f4187ad1ab9685d18ff28db000c99deac8ce180e3" +checksum = "1402f54f9a3b9e2efe71c1cea24e648acce55887983553eeb858cf3115acfd49" dependencies = [ "base64", "chrono", "hex", "indexmap 1.9.3", + "indexmap 2.0.0", "serde", "serde_json", "serde_with_macros", @@ -3105,9 +3107,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea3cee93715c2e266b9338b7544da68a9f24e227722ba482bd1c024367c77c65" +checksum = "9197f1ad0e3c173a0222d3c4404fb04c3afe87e962bcb327af73e8301fa203c7" dependencies = [ "darling", "proc-macro2", @@ -3323,9 +3325,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", @@ -3660,14 +3662,14 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.7.0" +version = "3.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5486094ee78b2e5038a6382ed7645bc084dc2ec433426ca4c3cb61e2007b8998" +checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651" dependencies = [ "cfg-if", "fastrand 2.0.0", "redox_syscall 0.3.5", - "rustix 0.38.6", + "rustix 0.38.7", "windows-sys 0.48.0", ] @@ -3911,9 +3913,9 @@ dependencies = [ [[package]] name = "tray-icon" -version = "0.7.7" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62cfab965795a1d83dde8d38c00161edb80ea5a5c860cfb3a8e0d9dfc211860b" +checksum = "25ac0bf931ce61a25e0bfcaffbbcd619c8489c3947df98dbf369bb55a90e18a3" dependencies = [ "cocoa 0.25.0", "core-graphics 0.23.1", @@ -4511,9 +4513,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winnow" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46aab759304e4d7b2075a9aecba26228bb073ee8c50db796b2c72c676b5d807" +checksum = "acaaa1190073b2b101e15083c38ee8ec891b5e05cbee516521e94ec008f61e64" dependencies = [ "memchr", ] diff --git a/examples/api/src-tauri/src/tray.rs b/examples/api/src-tauri/src/tray.rs index df56646f35f5..698255061ca5 100644 --- a/examples/api/src-tauri/src/tray.rs +++ b/examples/api/src-tauri/src/tray.rs @@ -10,15 +10,15 @@ use tauri::{ }; pub fn create_tray(app: &tauri::AppHandle) -> tauri::Result<()> { - let toggle_i = MenuItem::new(app, "Toggle", true, None); - let new_window_i = MenuItem::new(app, "New window", true, None); - let icon_i_1 = MenuItem::new(app, "Icon 1", true, None); - let icon_i_2 = MenuItem::new(app, "Icon 2", true, None); + 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")] - let set_title_i = MenuItem::new(app, "Set Title", true, None); - let switch_i = MenuItem::new(app, "Switch Menu", true, None); - let quit_i = MenuItem::new(app, "Quit", true, None); - let remove_tray_i = MenuItem::new(app, "Remove Tray icon", true, None); + 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, &[ @@ -40,21 +40,19 @@ pub fn create_tray(app: &tauri::AppHandle) -> tauri::Result<()> { let is_menu1 = AtomicBool::new(true); - const TRAY_ID: u32 = 21937; - - let _ = TrayIconBuilder::with_id(TRAY_ID) + 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 { - i if i == quit_i.id() => { + .on_menu_event(move |app, event| match event.id.as_ref() { + "quit" => { app.exit(0); } - i if i == remove_tray_i.id() => { - app.remove_tray_by_id(TRAY_ID); + "remove-tray" => { + app.remove_tray_by_id("tray-1"); } - i if i == toggle_i.id() => { + "toggle" => { if let Some(window) = app.get_window("main") { let new_title = if window.is_visible().unwrap_or_default() { let _ = window.hide(); @@ -67,34 +65,34 @@ pub fn create_tray(app: &tauri::AppHandle) -> tauri::Result<()> { toggle_i.set_text(new_title).unwrap(); } } - i if i == new_window_i.id() => { + "new-window" => { let _ = WindowBuilder::new(app, "new", WindowUrl::App("index.html".into())) .title("Tauri") .build(); } #[cfg(target_os = "macos")] - i if i == set_title_i.id() => { - if let Some(tray) = app.tray_by_id(TRAY_ID) { + "set-title" => { + if let Some(tray) = app.tray_by_id("tray-1") { let _ = tray.set_title(Some("Tauri")); } } - i if i == icon_i_1.id() || i == icon_i_2.id() => { - if let Some(tray) = app.tray_by_id(TRAY_ID) { - let _ = tray.set_icon(Some(tauri::Icon::Raw(if i == icon_i_1.id() { + 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() }))); } } - i if i == switch_i.id() => { + "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_ID) { + if let Some(tray) = app.tray_by_id("tray-1") { let _ = tray.set_menu(Some(menu)); let _ = tray.set_tooltip(Some(tooltip)); } From 12837732c346517e142e0ea6fc66c0d26085cc04 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Wed, 9 Aug 2023 02:26:53 +0300 Subject: [PATCH 107/123] fix ios and android build --- core/tauri/src/menu/builders/check.rs | 4 +--- core/tauri/src/menu/builders/normal.rs | 4 +--- core/tauri/src/menu/check.rs | 4 +--- core/tauri/src/menu/icon.rs | 4 +--- core/tauri/src/menu/normal.rs | 4 +--- core/tauri/src/menu/predefined.rs | 4 +--- core/tauri/src/window.rs | 3 +-- 7 files changed, 7 insertions(+), 20 deletions(-) diff --git a/core/tauri/src/menu/builders/check.rs b/core/tauri/src/menu/builders/check.rs index 9df4e8eb0742..fa900d206d58 100644 --- a/core/tauri/src/menu/builders/check.rs +++ b/core/tauri/src/menu/builders/check.rs @@ -2,9 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use muda::MenuId; - -use crate::{menu::CheckMenuItem, Manager, Runtime}; +use crate::{menu::CheckMenuItem, menu::MenuId, Manager, Runtime}; /// A builder type for [`CheckMenuItem`] pub struct CheckMenuItemBuilder { diff --git a/core/tauri/src/menu/builders/normal.rs b/core/tauri/src/menu/builders/normal.rs index 9512b0d0b2dd..f70abe388db9 100644 --- a/core/tauri/src/menu/builders/normal.rs +++ b/core/tauri/src/menu/builders/normal.rs @@ -2,9 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use muda::MenuId; - -use crate::{menu::MenuItem, Manager, Runtime}; +use crate::{menu::MenuId, menu::MenuItem, Manager, Runtime}; /// A builder type for [`MenuItem`] pub struct MenuItemBuilder { diff --git a/core/tauri/src/menu/check.rs b/core/tauri/src/menu/check.rs index cbc64f9f1bfc..28d90990f2e4 100644 --- a/core/tauri/src/menu/check.rs +++ b/core/tauri/src/menu/check.rs @@ -2,9 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use muda::MenuId; - -use crate::{run_main_thread, AppHandle, Manager, Runtime}; +use crate::{menu::MenuId, run_main_thread, AppHandle, Manager, Runtime}; /// A menu item inside a [`Menu`] or [`Submenu`] and contains only text. /// diff --git a/core/tauri/src/menu/icon.rs b/core/tauri/src/menu/icon.rs index 8bb061fdcc1a..9a2d8b0dda5e 100644 --- a/core/tauri/src/menu/icon.rs +++ b/core/tauri/src/menu/icon.rs @@ -2,10 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use muda::MenuId; - use super::NativeIcon; -use crate::{run_main_thread, AppHandle, Icon, Manager, Runtime}; +use crate::{menu::MenuId, run_main_thread, AppHandle, Icon, Manager, Runtime}; /// A menu item inside a [`Menu`] or [`Submenu`] and contains only text. /// diff --git a/core/tauri/src/menu/normal.rs b/core/tauri/src/menu/normal.rs index 25d79919365a..a7eeb3e6b71e 100644 --- a/core/tauri/src/menu/normal.rs +++ b/core/tauri/src/menu/normal.rs @@ -2,9 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use muda::MenuId; - -use crate::{run_main_thread, AppHandle, Manager, Runtime}; +use crate::{menu::MenuId, run_main_thread, AppHandle, Manager, Runtime}; /// A menu item inside a [`Menu`] or [`Submenu`] and contains only text. /// diff --git a/core/tauri/src/menu/predefined.rs b/core/tauri/src/menu/predefined.rs index 12499fa2a8e7..37ba63a78465 100644 --- a/core/tauri/src/menu/predefined.rs +++ b/core/tauri/src/menu/predefined.rs @@ -2,10 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use muda::MenuId; - use super::AboutMetadata; -use crate::{run_main_thread, AppHandle, Manager, Runtime}; +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 { diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index e6e3cf7f4a65..5c8d675888a9 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -4,7 +4,6 @@ //! The Tauri window types and functions. -use muda::MenuId; pub use tauri_utils::{config::Color, WindowEffect as Effect, WindowEffectState as EffectState}; use url::Url; @@ -35,7 +34,7 @@ use crate::{ }; #[cfg(desktop)] use crate::{ - menu::{ContextMenu, Menu}, + menu::{ContextMenu, Menu, MenuId}, runtime::{ window::dpi::{Position, Size}, UserAttentionType, From 678350179343bc8f877f18d2bbcdbaed59a58dfa Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Wed, 9 Aug 2023 02:37:57 +0300 Subject: [PATCH 108/123] relax bounds --- core/tauri/src/app.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 6342e7f9d74b..8bebfe4c1f45 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -541,7 +541,7 @@ macro_rules! shared_app_impl { #[cfg(desktop)] pub fn tray_by_id<'a, I>(&self, id: &'a I) -> Option> where - I: PartialEq + ?Sized, + I: ?Sized, TrayIconId: PartialEq<&'a I>, { self @@ -561,7 +561,7 @@ macro_rules! shared_app_impl { #[cfg(desktop)] pub fn remove_tray_by_id<'a, I>(&self, id: &'a I) -> Option> where - I: PartialEq + ?Sized, + I: ?Sized, TrayIconId: PartialEq<&'a I>, { let mut tray_icons = self.manager.inner.tray_icons.lock().unwrap(); From c71665bac94e482dbb26bf0794f9c2207b521888 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Wed, 9 Aug 2023 02:38:19 +0300 Subject: [PATCH 109/123] fix macos build --- core/tauri/src/menu/submenu.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/tauri/src/menu/submenu.rs b/core/tauri/src/menu/submenu.rs index 1abec3cae478..3cda039dbc91 100644 --- a/core/tauri/src/menu/submenu.rs +++ b/core/tauri/src/menu/submenu.rs @@ -295,7 +295,9 @@ impl Submenu { /// 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_windows_menu_for_nsapp())?; + run_main_thread!(self, |self_: Self| self_ + .inner + .set_as_windows_menu_for_nsapp())?; Ok(()) } @@ -307,7 +309,7 @@ impl Submenu { /// 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_help_menu_for_nsapp())?; + run_main_thread!(self, |self_: Self| self_.inner.set_as_help_menu_for_nsapp())?; Ok(()) } } From 743e0b61057b9561f55512121aa04bd1fe89d60e Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Wed, 9 Aug 2023 02:54:41 +0300 Subject: [PATCH 110/123] fix macos build --- core/tauri/src/menu/menu.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/core/tauri/src/menu/menu.rs b/core/tauri/src/menu/menu.rs index b7d7a44fdb1e..00376df0dcb9 100644 --- a/core/tauri/src/menu/menu.rs +++ b/core/tauri/src/menu/menu.rs @@ -153,6 +153,15 @@ impl Menu { #[cfg(target_os = "macos")] &PredefinedMenuItem::separator(app_handle), &PredefinedMenuItem::close_window(app_handle, None), + ], + )?; + + let help_menu = Submenu::with_items( + app_handle, + "Help", + true, + &[ + #[cfg(not(target_os = "macos"))] &PredefinedMenuItem::about(app_handle, None, Some(about_metadata)), ], )?; @@ -215,11 +224,15 @@ impl Menu { &[&PredefinedMenuItem::fullscreen(app_handle, None)], )?, &window_menu, + &help_menu, ], )?; #[cfg(target_os = "macos")] - window_menu.set_as_windows_menu_for_nsapp()?; + { + window_menu.set_as_windows_menu_for_nsapp()?; + help_menu.set_as_help_menu_for_nsapp()?; + } Ok(menu) } From 175c536747f998473e0f91ee8673d77cdcc35fda Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Wed, 9 Aug 2023 02:56:41 +0300 Subject: [PATCH 111/123] update lock file --- examples/api/src-tauri/Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/api/src-tauri/Cargo.lock b/examples/api/src-tauri/Cargo.lock index 4810482f575a..2a5413ea82ed 100644 --- a/examples/api/src-tauri/Cargo.lock +++ b/examples/api/src-tauri/Cargo.lock @@ -2185,9 +2185,9 @@ dependencies = [ [[package]] name = "muda" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed8a1fd60c7b522b2e5c14c059195f16aa94d826e4549dfbedef5fbbc015a243" +checksum = "47e33f46fb20f85553edb85e30a6a5231567f4103276ccdb5a2050613d253b1d" dependencies = [ "cocoa 0.25.0", "crossbeam-channel", @@ -3913,9 +3913,9 @@ dependencies = [ [[package]] name = "tray-icon" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ac0bf931ce61a25e0bfcaffbbcd619c8489c3947df98dbf369bb55a90e18a3" +checksum = "e3b0e5bec13da15e62330e9bcf8b9fd42489b5acfe29ac8fec7ed659dbee21d9" dependencies = [ "cocoa 0.25.0", "core-graphics 0.23.1", From 676c690fb4e1ba9197dfcf4450260cc3b7a0425c Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Thu, 10 Aug 2023 08:27:53 -0300 Subject: [PATCH 112/123] simplify comparisons [skip ci] --- core/tauri/src/app.rs | 4 ++-- core/tauri/src/manager.rs | 2 +- core/tauri/src/window.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 8bebfe4c1f45..64a865173143 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -551,7 +551,7 @@ macro_rules! shared_app_impl { .lock() .unwrap() .iter() - .find(|t| t.id().eq(&id)) + .find(|t| t.id() == &id) .cloned() } @@ -565,7 +565,7 @@ macro_rules! shared_app_impl { TrayIconId: PartialEq<&'a I>, { let mut tray_icons = self.manager.inner.tray_icons.lock().unwrap(); - let idx = tray_icons.iter().position(|t| t.id().eq(&id)); + let idx = tray_icons.iter().position(|t| t.id() == &id); if let Some(idx) = idx { return Some(tray_icons.swap_remove(idx)); } diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index 52d193836f63..4b02c8cdda21 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -445,7 +445,7 @@ impl WindowManager { self .menu_lock() .as_ref() - .map(|m| PartialEq::eq(id, m.id())) + .map(|m| id.eq(m.id())) .unwrap_or(false) } diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index 5c8d675888a9..29dd46996150 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -1238,7 +1238,7 @@ impl Window { self .menu_lock() .as_ref() - .map(|m| PartialEq::eq(id, m.menu.id())) + .map(|m| id.eq(m.menu.id())) .unwrap_or(false) } From 32aba4d19a190f86d2eebce66b49d9f1306a9f67 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Thu, 10 Aug 2023 09:50:55 -0300 Subject: [PATCH 113/123] fix window and help submenu init logic --- core/tauri/src/app.rs | 22 ++++++++++++++++++++-- core/tauri/src/menu/menu.rs | 27 +++++++++++++++++++-------- core/tauri/src/menu/mod.rs | 2 +- 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 64a865173143..68b7f876adaa 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -655,7 +655,7 @@ macro_rules! shared_app_impl { { let menu_ = menu.clone(); self.run_on_main_thread(move || { - menu_.inner().init_for_nsapp(); + let _ = init_app_menu(&menu_); })?; } @@ -1451,7 +1451,7 @@ impl Builder { .insert(menu.id().clone(), menu.clone()); #[cfg(target_os = "macos")] - menu.inner().init_for_nsapp(); + init_app_menu(&menu)?; app.manager.menu_lock().replace(menu); } @@ -1527,6 +1527,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() diff --git a/core/tauri/src/menu/menu.rs b/core/tauri/src/menu/menu.rs index 00376df0dcb9..451becb58de7 100644 --- a/core/tauri/src/menu/menu.rs +++ b/core/tauri/src/menu/menu.rs @@ -9,6 +9,11 @@ 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 { @@ -143,8 +148,9 @@ impl Menu { ..Default::default() }; - let window_menu = Submenu::with_items( + let window_menu = Submenu::with_id_and_items( app_handle, + WINDOW_SUBMENU_ID, "Window", true, &[ @@ -156,8 +162,9 @@ impl Menu { ], )?; - let help_menu = Submenu::with_items( + let help_menu = Submenu::with_id_and_items( app_handle, + HELP_SUBMENU_ID, "Help", true, &[ @@ -228,12 +235,6 @@ impl Menu { ], )?; - #[cfg(target_os = "macos")] - { - window_menu.set_as_windows_menu_for_nsapp()?; - help_menu.set_as_help_menu_for_nsapp()?; - } - Ok(menu) } @@ -342,6 +343,16 @@ impl Menu { .map_err(Into::into) } + /// Retrieves the menu item matching the given identifier. + pub fn get>(&self, id: I) -> Option> { + let id = id.into(); + 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(); diff --git a/core/tauri/src/menu/mod.rs b/core/tauri/src/menu/mod.rs index 7c8354d83039..4f594f9905f8 100644 --- a/core/tauri/src/menu/mod.rs +++ b/core/tauri/src/menu/mod.rs @@ -19,7 +19,7 @@ mod submenu; pub use builders::*; pub use check::CheckMenuItem; pub use icon::IconMenuItem; -pub use menu::Menu; +pub use menu::{Menu, HELP_SUBMENU_ID, WINDOW_SUBMENU_ID}; pub use normal::MenuItem; pub use predefined::PredefinedMenuItem; pub use submenu::Submenu; From e30ffc58ee2b44e9c455780bfe98a1961b16958f Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Thu, 10 Aug 2023 17:57:03 +0300 Subject: [PATCH 114/123] add tray-icon feature flag --- .changes/system-tray-feat.md | 1 - .changes/tauri-tray-icon-feat-flag.md | 5 ++++ .github/workflows/lint-core.yml | 2 +- .github/workflows/test-core.yml | 2 +- core/tauri-runtime-wry/src/lib.rs | 4 +-- core/tauri-runtime/src/window.rs | 2 +- core/tauri-utils/src/config.rs | 10 ++++++- core/tauri/Cargo.toml | 3 +- core/tauri/src/app.rs | 41 ++++++++++++++------------- core/tauri/src/error.rs | 4 +-- core/tauri/src/lib.rs | 17 +++++------ core/tauri/src/manager.rs | 29 ++++++++++--------- core/tauri/src/test/mod.rs | 2 +- core/tauri/src/tray.rs | 2 +- 14 files changed, 70 insertions(+), 54 deletions(-) create mode 100644 .changes/tauri-tray-icon-feat-flag.md diff --git a/.changes/system-tray-feat.md b/.changes/system-tray-feat.md index 0091941fd319..2516e4e432da 100644 --- a/.changes/system-tray-feat.md +++ b/.changes/system-tray-feat.md @@ -1,5 +1,4 @@ --- -'tauri': 'major:breaking' 'tauri-runtime': 'major:breaking' 'tauri-runtime-wry': 'major:breaking' --- 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 ad3bd47ec150..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,linux-ipc-protocol,isolation,custom-protocol,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 c21d085372ab..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,linux-ipc-protocol,isolation,custom-protocol,test, + args: --features compression,wry,linux-ipc-protocol,isolation,custom-protocol,tray-icon,test, key: all } diff --git a/core/tauri-runtime-wry/src/lib.rs b/core/tauri-runtime-wry/src/lib.rs index 22da4503b6e3..078361297984 100644 --- a/core/tauri-runtime-wry/src/lib.rs +++ b/core/tauri-runtime-wry/src/lib.rs @@ -763,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 } @@ -775,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 } diff --git a/core/tauri-runtime/src/window.rs b/core/tauri-runtime/src/window.rs index 0d2eb480c61c..7ad69fbffdfd 100644 --- a/core/tauri-runtime/src/window.rs +++ b/core/tauri-runtime/src/window.rs @@ -359,7 +359,7 @@ impl> PartialEq for DetachedWindow { /// NSView on macOS. pub struct RawWindow<'a> { #[cfg(windows)] - pub hwnd: *mut std::ffi::c_void, + pub hwnd: isize, #[cfg(any( target_os = "linux", target_os = "dragonfly", diff --git a/core/tauri-utils/src/config.rs b/core/tauri-utils/src/config.rs index 40bdb5c00c8d..8ccd4d84ca4c 100644 --- a/core/tauri-utils/src/config.rs +++ b/core/tauri-utils/src/config.rs @@ -1427,12 +1427,20 @@ pub struct TauriConfig { impl TauriConfig { /// Returns all Cargo features. pub fn all_features() -> Vec<&'static str> { - vec!["macos-private-api", "isolation", "protocol-asset"] + vec![ + "tray-icon", + "macos-private-api", + "isolation", + "protocol-asset", + ] } /// Returns the enabled Cargo features. pub fn features(&self) -> Vec<&str> { let mut features = Vec::new(); + if self.tray_icon.is_some() { + features.push("tray-icon"); + } if self.macos_private_api { features.push("macos-private-api"); } diff --git a/core/tauri/Cargo.toml b/core/tauri/Cargo.toml index 905eddc3bb5f..051abab7171d 100644 --- a/core/tauri/Cargo.toml +++ b/core/tauri/Cargo.toml @@ -70,7 +70,7 @@ 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 } +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" ] } @@ -119,6 +119,7 @@ cargo_toml = "0.15" [features] 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" ] diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 4c0878fd56d1..bcc8db72fcec 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -29,7 +29,7 @@ use crate::scope::FsScope; #[cfg(desktop)] use crate::menu::{Menu, MenuEvent}; -#[cfg(desktop)] +#[cfg(all(desktop, feature = "tray-icon"))] use crate::tray::{TrayIcon, TrayIconBuilder, TrayIconEvent, TrayIconId}; #[cfg(desktop)] use crate::window::WindowMenu; @@ -61,12 +61,10 @@ use crate::ActivationPolicy; #[cfg(desktop)] pub(crate) type GlobalMenuEventListener = Box; -#[cfg(desktop)] +#[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>; @@ -216,8 +214,8 @@ pub enum RunEvent { /// 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(crate::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(desktop)] + /// An event from a tray icon. + #[cfg(all(desktop, feature = "tray-icon"))] TrayIconEvent(crate::tray::TrayIconEvent), } @@ -226,7 +224,7 @@ impl From for RunEvent { match event { #[cfg(desktop)] EventLoopMessage::MenuEvent(e) => Self::MenuEvent(e), - #[cfg(desktop)] + #[cfg(all(desktop, feature = "tray-icon"))] EventLoopMessage::TrayIconEvent(e) => Self::TrayIconEvent(e), } } @@ -518,7 +516,7 @@ macro_rules! shared_app_impl { } /// Registers a global tray icon menu event listener. - #[cfg(desktop)] + #[cfg(all(desktop, feature = "tray-icon"))] pub fn on_tray_icon_event, TrayIconEvent) + Send + Sync + 'static>( &self, handler: F, @@ -534,7 +532,7 @@ macro_rules! shared_app_impl { /// Gets the first tray icon registerd, usually the one configured in /// tauri config file. - #[cfg(desktop)] + #[cfg(all(desktop, feature = "tray-icon"))] pub fn tray(&self) -> Option> { self .manager @@ -550,7 +548,7 @@ macro_rules! shared_app_impl { /// tauri config file, from tauri's internal state and returns it. /// /// Note that dropping the returned icon, will cause the tray icon to disappear. - #[cfg(desktop)] + #[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() { @@ -560,7 +558,7 @@ macro_rules! shared_app_impl { } /// Gets a tray icon using the provided id. - #[cfg(desktop)] + #[cfg(all(desktop, feature = "tray-icon"))] pub fn tray_by_id<'a, I>(&self, id: &'a I) -> Option> where I: ?Sized, @@ -580,7 +578,7 @@ macro_rules! shared_app_impl { /// 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(desktop)] + #[cfg(all(desktop, feature = "tray-icon"))] pub fn remove_tray_by_id<'a, I>(&self, id: &'a I) -> Option> where I: ?Sized, @@ -790,7 +788,7 @@ macro_rules! shared_app_impl { /// 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(desktop)] + #[cfg(all(desktop, feature = "tray-icon"))] self.manager.inner.tray_icons.lock().unwrap().clear() } } @@ -1460,10 +1458,13 @@ impl Builder { })); // setup tray event handler - let proxy = runtime.create_proxy(); - crate::tray::TrayIconEvent::set_event_handler(Some(move |e| { - let _ = proxy.send_event(EventLoopMessage::TrayIconEvent(e)); - })); + #[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); @@ -1536,9 +1537,9 @@ impl Builder { let handle = app.handle(); - #[cfg(desktop)] + // initialize default tray icon if defined + #[cfg(all(desktop, feature = "tray-icon"))] { - // initialize default tray icon if defined let config = app.config(); if let Some(tray_config) = &config.tauri.tray_icon { let mut tray = TrayIconBuilder::new() @@ -1726,7 +1727,7 @@ fn on_event_loop_event, RunEvent) + 'static>( } } } - #[cfg(desktop)] + #[cfg(all(desktop, feature = "tray-icon"))] EventLoopMessage::TrayIconEvent(ref e) => { for listener in &*app_handle .manager diff --git a/core/tauri/src/error.rs b/core/tauri/src/error.rs index 26f9c4e389eb..a367e8f766cd 100644 --- a/core/tauri/src/error.rs +++ b/core/tauri/src/error.rs @@ -102,10 +102,10 @@ pub enum Error { BadMenuIcon(#[from] muda::BadIcon), /// Tray icon error. #[error("tray icon error: {0}")] - #[cfg(desktop)] + #[cfg(all(desktop, feature = "tray-icon"))] Tray(#[from] tray_icon::Error), /// Bad tray icon error. #[error(transparent)] - #[cfg(desktop)] + #[cfg(all(desktop, feature = "tray-icon"))] BadTrayIcon(#[from] tray_icon::BadIcon), } diff --git a/core/tauri/src/lib.rs b/core/tauri/src/lib.rs index 1a695a6222a6..370a2353693f 100644 --- a/core/tauri/src/lib.rs +++ b/core/tauri/src/lib.rs @@ -26,6 +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). +//! - **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. @@ -101,7 +102,7 @@ pub mod process; pub mod scope; mod state; -#[cfg(desktop)] +#[cfg(all(desktop, feature = "tray-icon"))] pub mod tray; pub use tauri_utils as utils; @@ -252,7 +253,7 @@ pub enum EventLoopMessage { #[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(desktop)] + #[cfg(all(desktop, feature = "tray-icon"))] TrayIconEvent(tray::TrayIconEvent), } @@ -393,7 +394,7 @@ pub struct Context { pub(crate) 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, pub(crate) package_info: PackageInfo, pub(crate) _info_plist: (), @@ -409,7 +410,7 @@ impl fmt::Debug for Context { .field("package_info", &self.package_info) .field("pattern", &self.pattern); - #[cfg(desktop)] + #[cfg(all(desktop, feature = "tray-icon"))] d.field("tray_icon", &self.tray_icon); d.finish() @@ -454,14 +455,14 @@ impl Context { } /// The icon to use on the system tray UI. - #[cfg(desktop)] + #[cfg(all(desktop, feature = "tray-icon"))] #[inline(always)] pub fn tray_icon(&self) -> Option<&Icon> { self.tray_icon.as_ref() } /// A mutable reference to the icon to use on the tray icon. - #[cfg(desktop)] + #[cfg(all(desktop, feature = "tray-icon"))] #[inline(always)] pub fn tray_icon_mut(&mut self) -> &mut Option { &mut self.tray_icon @@ -502,7 +503,7 @@ impl Context { assets, default_window_icon, app_icon, - #[cfg(desktop)] + #[cfg(all(desktop, feature = "tray-icon"))] tray_icon: None, package_info, _info_plist: info_plist, @@ -511,7 +512,7 @@ impl Context { } /// Sets the app tray icon. - #[cfg(desktop)] + #[cfg(all(desktop, feature = "tray-icon"))] #[inline(always)] pub fn set_tray_icon(&mut self, icon: Icon) { self.tray_icon.replace(icon); diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index d2f6ed83950c..ddf05e1ad8db 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -11,10 +11,9 @@ use std::{ }; #[cfg(desktop)] -use crate::{ - menu::{Menu, MenuId}, - tray::{TrayIcon, TrayIconId}, -}; +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; @@ -52,7 +51,9 @@ use crate::{ }; #[cfg(desktop)] -use crate::app::{GlobalMenuEventListener, GlobalTrayIconEventListener}; +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; @@ -227,7 +228,7 @@ 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, @@ -253,14 +254,14 @@ pub struct InnerWindowManager { /// Window event listeners to all windows. window_event_listeners: Arc>>, /// Tray icons - #[cfg(desktop)] + #[cfg(all(desktop, feature = "tray-icon"))] pub(crate) tray_icons: Arc>>>, /// Global Tray icon event listeners. - #[cfg(desktop)] + #[cfg(all(desktop, feature = "tray-icon"))] pub(crate) global_tray_event_listeners: Arc>>>>, /// Tray icon event listeners. - #[cfg(desktop)] + #[cfg(all(desktop, feature = "tray-icon"))] pub(crate) tray_event_listeners: Arc>>>>, /// Responder for invoke calls. @@ -283,7 +284,7 @@ impl fmt::Debug for InnerWindowManager { .field("package_info", &self.package_info) .field("pattern", &self.pattern); - #[cfg(desktop)] + #[cfg(all(desktop, feature = "tray-icon"))] d.field("tray_icon", &self.tray_icon); d.finish() @@ -359,7 +360,7 @@ impl WindowManager { assets: context.assets, default_window_icon: context.default_window_icon, app_icon: context.app_icon, - #[cfg(desktop)] + #[cfg(all(desktop, feature = "tray-icon"))] tray_icon: context.tray_icon, package_info: context.package_info, pattern: context.pattern, @@ -373,11 +374,11 @@ impl WindowManager { #[cfg(desktop)] window_menu_event_listeners: Arc::new(Mutex::new(window_menu_event_listeners)), window_event_listeners: Arc::new(window_event_listeners), - #[cfg(desktop)] + #[cfg(all(desktop, feature = "tray-icon"))] tray_icons: Default::default(), - #[cfg(desktop)] + #[cfg(all(desktop, feature = "tray-icon"))] global_tray_event_listeners: Default::default(), - #[cfg(desktop)] + #[cfg(all(desktop, feature = "tray-icon"))] tray_event_listeners: Default::default(), invoke_responder, invoke_initialization_script, diff --git a/core/tauri/src/test/mod.rs b/core/tauri/src/test/mod.rs index bf92bbd60a24..7b0a20e4385d 100644 --- a/core/tauri/src/test/mod.rs +++ b/core/tauri/src/test/mod.rs @@ -133,7 +133,7 @@ pub fn mock_context(assets: A) -> crate::Context { assets: Arc::new(assets), default_window_icon: None, app_icon: None, - #[cfg(desktop)] + #[cfg(all(desktop, feature = "tray-icon"))] tray_icon: None, package_info: crate::PackageInfo { name: "test".into(), diff --git a/core/tauri/src/tray.rs b/core/tauri/src/tray.rs index f10b3ee28301..77b3e3c1c75d 100644 --- a/core/tauri/src/tray.rs +++ b/core/tauri/src/tray.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -#![cfg(desktop)] +#![cfg(all(desktop, feature = "tray-icon"))] //! Tray icon types and utility functions From 3fb8cf08ec3b96e3f7b2ac22d48be978ca6c8db2 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Thu, 10 Aug 2023 18:17:37 +0300 Subject: [PATCH 115/123] add `Submenu::get` and use `PartialEq` --- core/tauri/src/menu/menu.rs | 7 +++++-- core/tauri/src/menu/submenu.rs | 13 +++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/core/tauri/src/menu/menu.rs b/core/tauri/src/menu/menu.rs index 451becb58de7..f4752d665c56 100644 --- a/core/tauri/src/menu/menu.rs +++ b/core/tauri/src/menu/menu.rs @@ -344,8 +344,11 @@ impl Menu { } /// Retrieves the menu item matching the given identifier. - pub fn get>(&self, id: I) -> Option> { - let id = id.into(); + pub fn get<'a, I>(&self, id: &'a I) -> Option> + where + I: ?Sized, + MenuId: PartialEq<&'a I>, + { self .items() .unwrap_or_default() diff --git a/core/tauri/src/menu/submenu.rs b/core/tauri/src/menu/submenu.rs index 3cda039dbc91..905f04830c98 100644 --- a/core/tauri/src/menu/submenu.rs +++ b/core/tauri/src/menu/submenu.rs @@ -312,4 +312,17 @@ impl Submenu { 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) + } } From 8432965908cf20847fb9a2ce59427f88d320971c Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Fri, 11 Aug 2023 08:05:45 -0300 Subject: [PATCH 116/123] update jni --- core/tauri-runtime-wry/Cargo.toml | 2 +- core/tauri-runtime-wry/src/lib.rs | 10 ++--- core/tauri-runtime/Cargo.toml | 2 +- core/tauri-runtime/src/lib.rs | 10 ++--- core/tauri-runtime/src/window.rs | 4 +- core/tauri/Cargo.toml | 2 +- core/tauri/src/jni_helpers.rs | 47 ++++++++++++-------- core/tauri/src/lib.rs | 14 ++++-- core/tauri/src/manager.rs | 2 +- core/tauri/src/plugin/mobile.rs | 68 ++++++++++++++++------------- core/tauri/src/test/mock_runtime.rs | 8 ++-- examples/api/src-tauri/Cargo.lock | 42 ++++++++++++++---- 12 files changed, 127 insertions(+), 84 deletions(-) diff --git a/core/tauri-runtime-wry/Cargo.toml b/core/tauri-runtime-wry/Cargo.toml index 08ff72fdfabd..f31d3e356e74 100644 --- a/core/tauri-runtime-wry/Cargo.toml +++ b/core/tauri-runtime-wry/Cargo.toml @@ -36,7 +36,7 @@ percent-encoding = "2.1" cocoa = "0.24" [target."cfg(target_os = \"android\")".dependencies] -jni = "0.20" +jni = "0.21" [features] dox = [ "wry/dox" ] diff --git a/core/tauri-runtime-wry/src/lib.rs b/core/tauri-runtime-wry/src/lib.rs index 078361297984..0e48f05a7f73 100644 --- a/core/tauri-runtime-wry/src/lib.rs +++ b/core/tauri-runtime-wry/src/lib.rs @@ -1780,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()) @@ -1791,9 +1791,7 @@ 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) } diff --git a/core/tauri-runtime/Cargo.toml b/core/tauri-runtime/Cargo.toml index 1773ee83e648..6c40c1c3c6a4 100644 --- a/core/tauri-runtime/Cargo.toml +++ b/core/tauri-runtime/Cargo.toml @@ -42,7 +42,7 @@ 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" diff --git a/core/tauri-runtime/src/lib.rs b/core/tauri-runtime/src/lib.rs index f976b9e75e0b..25c743b8ae35 100644 --- a/core/tauri-runtime/src/lib.rs +++ b/core/tauri-runtime/src/lib.rs @@ -225,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>; @@ -237,9 +237,7 @@ 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 { diff --git a/core/tauri-runtime/src/window.rs b/core/tauri-runtime/src/window.rs index 7ad69fbffdfd..41609f304b14 100644 --- a/core/tauri-runtime/src/window.rs +++ b/core/tauri-runtime/src/window.rs @@ -193,8 +193,8 @@ 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 activity: &'a jni::objects::JObject<'a>, + pub webview: &'a jni::objects::JObject<'a>, } /// A webview window that has yet to be built. diff --git a/core/tauri/Cargo.toml b/core/tauri/Cargo.toml index 051abab7171d..84fced69cef0 100644 --- a/core/tauri/Cargo.toml +++ b/core/tauri/Cargo.toml @@ -94,7 +94,7 @@ 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" 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 370a2353693f..abdf2ef2c0e4 100644 --- a/core/tauri/src/lib.rs +++ b/core/tauri/src/lib.rs @@ -135,14 +135,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); } }; } diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index ddf05e1ad8db..4fa6a41f99b1 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -1153,7 +1153,7 @@ impl WindowManager { #[cfg(target_os = "android")] { - pending = pending.on_webview_created(move |ctx| { + pending = pending.on_webview_created(move |mut ctx| { let plugin_manager = ctx .env .call_method( 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 da8954e7f4f6..b466de798ec0 100644 --- a/core/tauri/src/test/mock_runtime.rs +++ b/core/tauri/src/test/mock_runtime.rs @@ -161,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!() @@ -171,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!() } diff --git a/examples/api/src-tauri/Cargo.lock b/examples/api/src-tauri/Cargo.lock index 77dff8f2fd0a..2aca6f4096c6 100644 --- a/examples/api/src-tauri/Cargo.lock +++ b/examples/api/src-tauri/Cargo.lock @@ -1898,16 +1898,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]] @@ -2453,7 +2455,7 @@ dependencies = [ "libc", "redox_syscall 0.3.5", "smallvec", - "windows-targets", + "windows-targets 0.48.1", ] [[package]] @@ -3372,7 +3374,7 @@ dependencies = [ [[package]] name = "tao" version = "0.21.1" -source = "git+https://github.com/tauri-apps/tao?branch=muda#627caa988855c87d6c4d5d34df9bce881489fa35" +source = "git+https://github.com/tauri-apps/tao?branch=dev#d0b20c94eaf555ba27f3cfbbf2636e3f3b036a97" dependencies = [ "bitflags 1.3.2", "cairo-rs", @@ -3420,7 +3422,7 @@ dependencies = [ [[package]] name = "tao-macros" version = "0.1.1" -source = "git+https://github.com/tauri-apps/tao?branch=muda#627caa988855c87d6c4d5d34df9bce881489fa35" +source = "git+https://github.com/tauri-apps/tao?branch=dev#d0b20c94eaf555ba27f3cfbbf2636e3f3b036a97" dependencies = [ "proc-macro2", "quote", @@ -4342,7 +4344,7 @@ checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ "windows-implement", "windows-interface", - "windows-targets", + "windows-targets 0.48.1", ] [[package]] @@ -4398,13 +4400,37 @@ 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.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]] @@ -4543,7 +4569,7 @@ dependencies = [ [[package]] name = "wry" version = "0.29.0" -source = "git+https://github.com/tauri-apps/wry?branch=tao-v0.22#7580eda6b927a59cc945ed4c4a1d1cf1e7318542" +source = "git+https://github.com/tauri-apps/wry?branch=tao-v0.22#c9340fa557e6efa225747d4ef4476c957ebe97fc" dependencies = [ "base64", "block", From 88b418ac8a39bd1e1799ce4942f26b03bd8c4b4a Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Fri, 11 Aug 2023 09:23:43 -0300 Subject: [PATCH 117/123] update wry [skip ci] --- core/tauri-runtime/src/window.rs | 12 +++---- core/tauri/src/manager.rs | 2 +- examples/api/src-tauri/Cargo.lock | 58 +++++-------------------------- 3 files changed, 16 insertions(+), 56 deletions(-) diff --git a/core/tauri-runtime/src/window.rs b/core/tauri-runtime/src/window.rs index 41609f304b14..d2dfcb8a4b4b 100644 --- a/core/tauri-runtime/src/window.rs +++ b/core/tauri-runtime/src/window.rs @@ -191,10 +191,10 @@ impl<'de> Deserialize<'de> for CursorIcon { } #[cfg(target_os = "android")] -pub struct CreationContext<'a> { - pub env: jni::JNIEnv<'a>, - pub activity: &'a jni::objects::JObject<'a>, - pub webview: &'a 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. @@ -222,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>, } @@ -310,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, diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index 4fa6a41f99b1..ddf05e1ad8db 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -1153,7 +1153,7 @@ impl WindowManager { #[cfg(target_os = "android")] { - pending = pending.on_webview_created(move |mut ctx| { + pending = pending.on_webview_created(move |ctx| { let plugin_manager = ctx .env .call_method( diff --git a/examples/api/src-tauri/Cargo.lock b/examples/api/src-tauri/Cargo.lock index 2aca6f4096c6..5dcf0cc7993a 100644 --- a/examples/api/src-tauri/Cargo.lock +++ b/examples/api/src-tauri/Cargo.lock @@ -1611,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" @@ -1633,7 +1619,7 @@ checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" dependencies = [ "log", "mac", - "markup5ever 0.11.0", + "markup5ever", "proc-macro2", "quote", "syn 1.0.109", @@ -1950,18 +1936,6 @@ dependencies = [ "unicode-segmentation", ] -[[package]] -name = "kuchiki" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ea8e9c6e031377cff82ee3001dc8026cdf431ed4e2e6b51f98ab8c73484a358" -dependencies = [ - "cssparser", - "html5ever 0.25.2", - "matches", - "selectors", -] - [[package]] name = "kuchikiki" version = "0.8.2" @@ -1969,7 +1943,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f29e4755b7b995046f510a7520c42b2fed58b77bd94d5a87a8eb43d2fd126da8" dependencies = [ "cssparser", - "html5ever 0.26.0", + "html5ever", "indexmap 1.9.3", "matches", "selectors", @@ -2091,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" @@ -3374,7 +3334,7 @@ dependencies = [ [[package]] name = "tao" version = "0.21.1" -source = "git+https://github.com/tauri-apps/tao?branch=dev#d0b20c94eaf555ba27f3cfbbf2636e3f3b036a97" +source = "git+https://github.com/tauri-apps/tao?branch=dev#448ec06c0f852c52d84f40505c8ae4f65ace0637" dependencies = [ "bitflags 1.3.2", "cairo-rs", @@ -3422,7 +3382,7 @@ dependencies = [ [[package]] name = "tao-macros" version = "0.1.1" -source = "git+https://github.com/tauri-apps/tao?branch=dev#d0b20c94eaf555ba27f3cfbbf2636e3f3b036a97" +source = "git+https://github.com/tauri-apps/tao?branch=dev#448ec06c0f852c52d84f40505c8ae4f65ace0637" dependencies = [ "proc-macro2", "quote", @@ -3634,7 +3594,7 @@ dependencies = [ "getrandom 0.2.10", "glob", "heck", - "html5ever 0.26.0", + "html5ever", "infer 0.12.0", "json-patch", "kuchikiki", @@ -4568,8 +4528,8 @@ dependencies = [ [[package]] name = "wry" -version = "0.29.0" -source = "git+https://github.com/tauri-apps/wry?branch=tao-v0.22#c9340fa557e6efa225747d4ef4476c957ebe97fc" +version = "0.30.0" +source = "git+https://github.com/tauri-apps/wry?branch=tao-v0.22#f3bc82c16b3ba4978f46a9b6762e079854aaf188" dependencies = [ "base64", "block", @@ -4581,10 +4541,10 @@ dependencies = [ "gio", "glib", "gtk", - "html5ever 0.25.2", + "html5ever", "http", "javascriptcore-rs", - "kuchiki", + "kuchikiki", "libc", "log", "objc", From 4f65b2691a4971e1c8d453bd33399580c6ebc9d5 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Sun, 13 Aug 2023 20:10:33 -0300 Subject: [PATCH 118/123] wry 0.31 --- core/tauri-runtime-wry/Cargo.toml | 2 +- examples/api/src-tauri/Cargo.lock | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/core/tauri-runtime-wry/Cargo.toml b/core/tauri-runtime-wry/Cargo.toml index f31d3e356e74..7b0d3a340470 100644 --- a/core/tauri-runtime-wry/Cargo.toml +++ b/core/tauri-runtime-wry/Cargo.toml @@ -13,7 +13,7 @@ edition = { workspace = true } rust-version = { workspace = true } [dependencies] -wry = { git = "https://github.com/tauri-apps/wry", branch = "tao-v0.22", default-features = false, features = [ "file-drop", "protocol" ] } +wry = { version = "0.31", default-features = false, features = [ "file-drop", "protocol" ] } tauri-runtime = { version = "0.13.0-alpha.6", path = "../tauri-runtime" } tauri-utils = { version = "2.0.0-alpha.6", path = "../tauri-utils" } uuid = { version = "1", features = [ "v4" ] } diff --git a/examples/api/src-tauri/Cargo.lock b/examples/api/src-tauri/Cargo.lock index 5dcf0cc7993a..3765f3c6a9d3 100644 --- a/examples/api/src-tauri/Cargo.lock +++ b/examples/api/src-tauri/Cargo.lock @@ -3333,8 +3333,9 @@ dependencies = [ [[package]] name = "tao" -version = "0.21.1" -source = "git+https://github.com/tauri-apps/tao?branch=dev#448ec06c0f852c52d84f40505c8ae4f65ace0637" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60279ecb16c33a6cef45cd37a9602455c190942d20e360bd8499bff49f2a48f3" dependencies = [ "bitflags 1.3.2", "cairo-rs", @@ -3381,8 +3382,9 @@ dependencies = [ [[package]] name = "tao-macros" -version = "0.1.1" -source = "git+https://github.com/tauri-apps/tao?branch=dev#448ec06c0f852c52d84f40505c8ae4f65ace0637" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec114582505d158b669b136e6851f85840c109819d77c42bb7c0709f727d18c2" dependencies = [ "proc-macro2", "quote", @@ -4528,8 +4530,9 @@ dependencies = [ [[package]] name = "wry" -version = "0.30.0" -source = "git+https://github.com/tauri-apps/wry?branch=tao-v0.22#f3bc82c16b3ba4978f46a9b6762e079854aaf188" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6289018fa3cbc051c13f4ae1a102d80c3f35a527456c75567eb2cad6989020" dependencies = [ "base64", "block", From 9a62c320e3f477dac1875a10e3dbb8f32c4f08c3 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Sun, 13 Aug 2023 20:14:55 -0300 Subject: [PATCH 119/123] update migration --- tooling/cli/src/migrate/manifest.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tooling/cli/src/migrate/manifest.rs b/tooling/cli/src/migrate/manifest.rs index 3edb6561d80f..22024f9898d9 100644 --- a/tooling/cli/src/migrate/manifest.rs +++ b/tooling/cli/src/migrate/manifest.rs @@ -153,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"); } } } From 2c20d6b9a88e3a4909554ad50bc4566e84f6e4c1 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Sun, 13 Aug 2023 20:35:48 -0300 Subject: [PATCH 120/123] do not enable tray-icon by default --- core/tauri-build/src/allowlist.rs | 7 ++++++- core/tauri/Cargo.toml | 2 +- examples/api/src-tauri/Cargo.toml | 1 + 3 files changed, 8 insertions(+), 2 deletions(-) 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/Cargo.toml b/core/tauri/Cargo.toml index 84fced69cef0..4db8d809ab27 100644 --- a/core/tauri/Cargo.toml +++ b/core/tauri/Cargo.toml @@ -118,7 +118,7 @@ tokio = { version = "1", features = [ "full" ] } cargo_toml = "0.15" [features] -default = [ "wry", "compression", "objc-exception", "tray-icon/common-controls-v6", "muda/common-controls-v6" ] +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" ] diff --git a/examples/api/src-tauri/Cargo.toml b/examples/api/src-tauri/Cargo.toml index 0f37d40ddbd4..a70dda3e2374 100644 --- a/examples/api/src-tauri/Cargo.toml +++ b/examples/api/src-tauri/Cargo.toml @@ -40,6 +40,7 @@ features = [ "icon-png", "isolation", "macos-private-api", + "tray-icon" ] [dev-dependencies.tauri] From 6842939a7afc5d64965084cb747f7692a544d171 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Sun, 13 Aug 2023 20:37:57 -0300 Subject: [PATCH 121/123] enhance docs --- core/tauri/src/app.rs | 8 ++++++++ core/tauri/src/error.rs | 2 ++ core/tauri/src/lib.rs | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index bcc8db72fcec..2cd80bdeee44 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -207,15 +207,18 @@ 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), } @@ -517,6 +520,7 @@ macro_rules! shared_app_impl { /// 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, @@ -533,6 +537,7 @@ macro_rules! shared_app_impl { /// 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 @@ -549,6 +554,7 @@ macro_rules! shared_app_impl { /// /// 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() { @@ -559,6 +565,7 @@ macro_rules! shared_app_impl { /// 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, @@ -579,6 +586,7 @@ macro_rules! shared_app_impl { /// /// 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, diff --git a/core/tauri/src/error.rs b/core/tauri/src/error.rs index a367e8f766cd..8de7cec0d01b 100644 --- a/core/tauri/src/error.rs +++ b/core/tauri/src/error.rs @@ -103,9 +103,11 @@ pub enum Error { /// 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/lib.rs b/core/tauri/src/lib.rs index abdf2ef2c0e4..a66aca8bd832 100644 --- a/core/tauri/src/lib.rs +++ b/core/tauri/src/lib.rs @@ -103,6 +103,7 @@ 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; @@ -260,6 +261,7 @@ pub enum EventLoopMessage { 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), } @@ -462,6 +464,7 @@ impl Context { /// The icon to use on the system tray UI. #[cfg(all(desktop, feature = "tray-icon"))] + #[cfg_attr(doc_cfg, doc(cfg(all(desktop, feature = "tray-icon"))))] #[inline(always)] pub fn tray_icon(&self) -> Option<&Icon> { self.tray_icon.as_ref() @@ -469,6 +472,7 @@ impl Context { /// 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 tray_icon_mut(&mut self) -> &mut Option { &mut self.tray_icon @@ -519,6 +523,7 @@ impl Context { /// Sets the app tray icon. #[cfg(all(desktop, feature = "tray-icon"))] + #[cfg_attr(doc_cfg, doc(cfg(all(desktop, feature = "tray-icon"))))] #[inline(always)] pub fn set_tray_icon(&mut self, icon: Icon) { self.tray_icon.replace(icon); From f1f1bf01928e75322f28a9fdee9723033d127a7e Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Sun, 13 Aug 2023 21:14:00 -0300 Subject: [PATCH 122/123] fix tray detection --- tooling/cli/src/build.rs | 59 ---------------------------- tooling/cli/src/interface/rust.rs | 65 +++++++++++++++++++++++++++---- 2 files changed, 57 insertions(+), 67 deletions(-) diff --git a/tooling/cli/src/build.rs b/tooling/cli/src/build.rs index 3ba190038d7b..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.tray_icon.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 053e394e67c8..f4a957aff909 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.tray_icon.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, - tray_icon_config: Option, ) -> crate::Result { let enabled_features = manifest.all_enabled_features(features); @@ -1066,14 +1060,35 @@ 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(tray_icon_config) = &tray_icon_config { + if enabled_features.contains(&"tray-icon".into()) + || enabled_features.contains(&"tauri/tray-icon".into()) + { let tray = std::env::var("TAURI_TRAY").unwrap_or_else(|_| "ayatana".to_string()); if tray == "ayatana" { depends.push("libayatana-appindicator3-1".into()); + + std::env::set_var( + "TRAY_LIBRARY_PATH", + 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 { depends.push("libappindicator3-1".into()); + + std::env::set_var( + "TRAY_LIBRARY_PATH", + format!( + "{}/libappindicator3.so.1", + pkgconfig_utils::get_library_path("appindicator3-0.1") + .expect("failed to get libappindicator-gtk library path using pkg-config.") + ), + ); } } @@ -1184,3 +1199,37 @@ fn tauri_config_to_bundle_settings( ..Default::default() }) } + +#[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 + } + } +} From fb73c966553c9bbbb0ab34630b0c46e0c735d782 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Sun, 13 Aug 2023 21:41:12 -0300 Subject: [PATCH 123/123] fix logic --- tooling/cli/src/interface/rust.rs | 74 ++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 27 deletions(-) diff --git a/tooling/cli/src/interface/rust.rs b/tooling/cli/src/interface/rust.rs index f4a957aff909..239a2a48ed10 100644 --- a/tooling/cli/src/interface/rust.rs +++ b/tooling/cli/src/interface/rust.rs @@ -1066,30 +1066,39 @@ fn tauri_config_to_bundle_settings( if enabled_features.contains(&"tray-icon".into()) || enabled_features.contains(&"tauri/tray-icon".into()) { - let tray = std::env::var("TAURI_TRAY").unwrap_or_else(|_| "ayatana".to_string()); - if tray == "ayatana" { - depends.push("libayatana-appindicator3-1".into()); - - std::env::set_var( - "TRAY_LIBRARY_PATH", - 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 { - depends.push("libappindicator3-1".into()); - - std::env::set_var( - "TRAY_LIBRARY_PATH", - format!( - "{}/libappindicator3.so.1", - pkgconfig_utils::get_library_path("appindicator3-0.1") - .expect("failed to get libappindicator-gtk library path using pkg-config.") - ), - ); + 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 @@ -1202,13 +1211,24 @@ fn tauri_config_to_bundle_settings( #[cfg(target_os = "linux")] mod pkgconfig_utils { - use std::{path::PathBuf, process::Command}; + use std::process::Command; + + pub enum TrayKind { + Ayatana, + Libappindicator, + } - pub fn get_appindicator_library_path() -> PathBuf { + pub fn get_appindicator_library_path() -> (TrayKind, String) { match get_library_path("ayatana-appindicator3-0.1") { - Some(p) => format!("{p}/libayatana-appindicator3.so.1").into(), + Some(p) => ( + TrayKind::Ayatana, + format!("{p}/libayatana-appindicator3.so.1"), + ), None => match get_library_path("appindicator3-0.1") { - Some(p) => format!("{p}/libappindicator3.so.1").into(), + Some(p) => ( + TrayKind::Libappindicator, + format!("{p}/libappindicator3.so.1"), + ), None => panic!("Can't detect any appindicator library"), }, }

, - ) -> crate::Result<()> { - let position = position.map(|p| p.into()); - - menu.popup(self.clone(), position)?; - - Ok(()) - } - - /// Executes a closure, providing it with the webview handle that is specific to the current platform. - /// - /// The closure is executed on the main thread. - /// - /// # Examples - /// - /// ```rust,no_run - /// #[cfg(target_os = "macos")] - /// #[macro_use] - /// extern crate objc; - /// use tauri::Manager; - /// - /// fn main() { - /// tauri::Builder::default() - /// .setup(|app| { - /// let main_window = app.get_window("main").unwrap(); - /// main_window.with_webview(|webview| { - /// #[cfg(target_os = "linux")] - /// { - /// // see https://docs.rs/webkit2gtk/0.18.2/webkit2gtk/struct.WebView.html - /// // and https://docs.rs/webkit2gtk/0.18.2/webkit2gtk/trait.WebViewExt.html - /// use webkit2gtk::traits::WebViewExt; - /// webview.inner().set_zoom_level(4.); - /// } - /// - /// #[cfg(windows)] - /// unsafe { - /// // see https://docs.rs/webview2-com/0.19.1/webview2_com/Microsoft/Web/WebView2/Win32/struct.ICoreWebView2Controller.html - /// webview.controller().SetZoomFactor(4.).unwrap(); - /// } - /// - /// #[cfg(target_os = "macos")] - /// unsafe { - /// let () = msg_send![webview.inner(), setPageZoom: 4.]; - /// let () = msg_send![webview.controller(), removeAllUserScripts]; - /// let bg_color: cocoa::base::id = msg_send![class!(NSColor), colorWithDeviceRed:0.5 green:0.2 blue:0.4 alpha:1.]; - /// let () = msg_send![webview.ns_window(), setBackgroundColor: bg_color]; - /// } - /// - /// #[cfg(target_os = "android")] - /// { - /// use jni::objects::JValue; - /// webview.jni_handle().exec(|env, _, webview| { - /// env.call_method(webview, "zoomBy", "(F)V", &[JValue::Float(4.)]).unwrap(); - /// }) - /// } - /// }); - /// Ok(()) - /// }); - /// } - /// ``` - #[cfg(feature = "wry")] - #[cfg_attr(doc_cfg, doc(feature = "wry"))] - pub fn with_webview( - &self, - f: F, - ) -> crate::Result<()> { - self - .window - .dispatcher - .with_webview(|w| f(PlatformWebview(*w.downcast().unwrap()))) - .map_err(Into::into) - } } /// Window getters. From e8ea27b1eb5dab1a9d1790332c6d14192aeeed95 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Wed, 2 Aug 2023 03:01:22 +0300 Subject: [PATCH 039/123] remove `Builder::tray_icon`, move init to `TrayIcon` type itself --- core/tauri/src/app.rs | 78 +++++++----------------- core/tauri/src/tray.rs | 96 ++++++++++++++++-------------- examples/api/src-tauri/src/lib.rs | 5 +- examples/api/src-tauri/src/tray.rs | 11 ++-- 4 files changed, 84 insertions(+), 106 deletions(-) diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 9188d1387a83..85e4bbc39558 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -985,9 +985,6 @@ pub struct Builder { /// Window event handlers that listens to all windows. window_event_listeners: Vec>, - /// Tray icon builder - tray_icons_builders: Vec) -> crate::Result>>>, - /// Tray icon event handlers. tray_event_listeners: Vec>, @@ -1015,7 +1012,6 @@ impl Builder { enable_macos_default_menu: true, menu_event_listeners: Vec::new(), window_event_listeners: Vec::new(), - tray_icons_builders: Vec::new(), tray_event_listeners: Vec::new(), device_event_filter: Default::default(), } @@ -1242,31 +1238,6 @@ impl Builder { self } - /// Sets the given tray icon builder to be built before the app runs. - /// - /// Prefer the [`TrayIconBuilder#method.build`](crate::tray::TrayIconBuilder#method.build) method to create the tray at runtime instead. - /// - /// # Examples - /// ``` - /// use tauri::{tray::TrayIconBuilder, menu::{Menu, MenuItem}}; - /// - /// tauri::Builder::default() - /// .tray_icon(|handle| TrayIconBuilder::new().with_menu( - /// &Menu::with_items(handle, &[ - /// &MenuItem::new(handle, "New window", true, None), - /// &MenuItem::new(handle, "Quit", true, None), - /// ])? - /// ).build(handle)); - /// ``` - #[must_use] - pub fn tray_icon) -> crate::Result> + 'static>( - mut self, - tray_builder: F, - ) -> Self { - self.tray_icons_builders.push(Box::new(tray_builder)); - self - } - /// Sets the menu to use on all windows. /// /// # Examples @@ -1546,36 +1517,33 @@ impl Builder { } } - // initialize tray icons - { - let mut tray_stash = app.manager.inner.tray_icons.lock().unwrap(); - let config = app.config(); - let handle = app.handle(); - - // config tray - if let Some(tray_config) = &config.tauri.tray_icon { - let mut tray = TrayIconBuilder::new() - .with_icon_as_template(tray_config.icon_as_template) - .with_menu_on_left_click(tray_config.menu_on_left_click); - if let Some(icon) = &app.manager.inner.tray_icon { - tray = tray.with_icon(icon.clone()); - } - if let Some(title) = &tray_config.title { - tray = tray.with_title(title); - } - if let Some(tooltip) = &tray_config.tooltip { - tray = tray.with_tooltip(tooltip); - } - tray_stash.push(tray.build(&handle)?); - } + let handle = app.handle(); - // tray icon registered on the builder - for tray_builder in self.tray_icons_builders { - tray_stash.push(tray_builder(&handle)?); + // initialize default tray icon if defined + let config = app.config(); + if let Some(tray_config) = &config.tauri.tray_icon { + let mut tray = TrayIconBuilder::new() + .with_icon_as_template(tray_config.icon_as_template) + .with_menu_on_left_click(tray_config.menu_on_left_click); + if let Some(icon) = &app.manager.inner.tray_icon { + tray = tray.with_icon(icon.clone()); + } + if let Some(title) = &tray_config.title { + tray = tray.with_title(title); + } + if let Some(tooltip) = &tray_config.tooltip { + tray = tray.with_tooltip(tooltip); } + app + .manager + .inner + .tray_icons + .lock() + .unwrap() + .push(tray.build(&handle)?); } - app.manager.initialize_plugins(&app.handle())?; + app.manager.initialize_plugins(&handle)?; Ok(app) } diff --git a/core/tauri/src/tray.rs b/core/tauri/src/tray.rs index a272d086a6e8..da6cffc20ccf 100644 --- a/core/tauri/src/tray.rs +++ b/core/tauri/src/tray.rs @@ -216,32 +216,15 @@ impl TrayIconBuilder { pub fn build(self, app_handle: &AppHandle) -> crate::Result> { let id = self.id(); let inner = self.inner.build()?; - - if let Some(handler) = self.on_menu_event { - app_handle - .manager - .inner - .menu_event_listeners - .lock() - .unwrap() - .push(handler); - } - - if let Some(handler) = self.on_tray_event { - app_handle - .manager - .inner - .tray_event_listeners - .lock() - .unwrap() - .insert(id, handler); - } - - Ok(TrayIcon { + let icon = TrayIcon { id, inner, app_handle: app_handle.clone(), - }) + }; + + icon.register(app_handle, self.on_menu_event, self.on_tray_event); + + Ok(icon) } } @@ -271,19 +254,12 @@ unsafe impl Sync for TrayIcon {} unsafe impl Send for TrayIcon {} impl TrayIcon { - /// Builds and adds a new tray icon to the system tray. - /// - /// ## 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(app_handle: &AppHandle, mut attrs: TrayIconAttributes) -> crate::Result { - let on_menu_event = attrs.on_menu_event.take(); - let on_tray_event = attrs.on_tray_event.take(); - - let inner = tray_icon::TrayIcon::new(attrs.into())?; - let id = inner.id(); - + fn register( + &self, + app_handle: &AppHandle, + on_menu_event: Option, MenuEvent) + Sync + Send + 'static>>, + on_tray_event: Option, TrayIconEvent) + Sync + Send + 'static>>, + ) { if let Some(handler) = on_menu_event { app_handle .manager @@ -301,14 +277,38 @@ impl TrayIcon { .tray_event_listeners .lock() .unwrap() - .insert(id, handler); + .insert(self.id, handler); } - Ok(Self { - id, + app_handle + .manager + .inner + .tray_icons + .lock() + .unwrap() + .push(self.clone()); + } + + /// Builds and adds a new tray icon to the system tray. + /// + /// ## 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(app_handle: &AppHandle, mut attrs: TrayIconAttributes) -> crate::Result { + let on_menu_event = attrs.on_menu_event.take(); + let on_tray_event = attrs.on_tray_event.take(); + + let inner = tray_icon::TrayIcon::new(attrs.into())?; + let icon = Self { + id: inner.id(), inner, app_handle: app_handle.clone(), - }) + }; + + icon.register(app_handle, on_menu_event, on_tray_event); + + Ok(icon) } /// Builds and adds a new tray icon to the system tray with the specified Id. @@ -316,14 +316,22 @@ impl TrayIcon { /// See [`TrayIcon::new`] for more info. pub fn with_id( app_handle: &AppHandle, - attrs: TrayIconAttributes, + mut attrs: TrayIconAttributes, id: u32, ) -> crate::Result { - Ok(Self { + let on_menu_event = attrs.on_menu_event.take(); + let on_tray_event = attrs.on_tray_event.take(); + + let inner = tray_icon::TrayIcon::with_id(attrs.into(), id)?; + let icon = Self { id, - inner: tray_icon::TrayIcon::with_id(attrs.into(), id)?, + inner, app_handle: app_handle.clone(), - }) + }; + + icon.register(app_handle, on_menu_event, on_tray_event); + + Ok(icon) } /// The application handle associated with this type. diff --git a/examples/api/src-tauri/src/lib.rs b/examples/api/src-tauri/src/lib.rs index fff8a25800ba..a3a6d52e1640 100644 --- a/examples/api/src-tauri/src/lib.rs +++ b/examples/api/src-tauri/src/lib.rs @@ -40,11 +40,12 @@ pub fn run_app) + Send + 'static>( .build(), ) .plugin(tauri_plugin_sample::init()) - .tray_icon(tray::create_tray) .setup(move |app| { #[cfg(desktop)] { - app.handle().plugin(tauri_plugin_cli::init())?; + let handle = app.handle(); + tray::create_tray(&handle)?; + handle.plugin(tauri_plugin_cli::init())?; } let mut window_builder = WindowBuilder::new(app, "main", WindowUrl::default()); diff --git a/examples/api/src-tauri/src/tray.rs b/examples/api/src-tauri/src/tray.rs index a8880fe49381..dcfa9f04a0f9 100644 --- a/examples/api/src-tauri/src/tray.rs +++ b/examples/api/src-tauri/src/tray.rs @@ -5,11 +5,11 @@ use std::sync::atomic::{AtomicBool, Ordering}; use tauri::{ menu::{Menu, MenuItem}, - tray::{ClickType, TrayIcon, TrayIconBuilder}, + tray::{ClickType, TrayIconBuilder}, Manager, Runtime, WindowBuilder, WindowUrl, }; -pub fn create_tray(app: &tauri::AppHandle) -> tauri::Result> { +pub fn create_tray(app: &tauri::AppHandle) -> tauri::Result<()> { let toggle_i = MenuItem::new(&app, "Toggle", true, None); let new_window_i = MenuItem::new(&app, "New window", true, None); let icon_i_1 = MenuItem::new(&app, "Icon 1", true, None); @@ -42,7 +42,7 @@ pub fn create_tray(app: &tauri::AppHandle) -> tauri::Result(app: &tauri::AppHandle) -> tauri::Result { - // exit the app app.exit(0); } i if i == remove_tray_i.id() => { @@ -118,5 +117,7 @@ pub fn create_tray(app: &tauri::AppHandle) -> tauri::Result Date: Wed, 2 Aug 2023 03:06:24 +0300 Subject: [PATCH 040/123] Remove `with_` prefix from `TrayIconBuilder` --- core/tauri/src/app.rs | 10 +++++----- core/tauri/src/tray.rs | 25 +++++++++++++------------ 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 85e4bbc39558..af76e47709da 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -1523,16 +1523,16 @@ impl Builder { let config = app.config(); if let Some(tray_config) = &config.tauri.tray_icon { let mut tray = TrayIconBuilder::new() - .with_icon_as_template(tray_config.icon_as_template) - .with_menu_on_left_click(tray_config.menu_on_left_click); + .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.with_icon(icon.clone()); + tray = tray.icon(icon.clone()); } if let Some(title) = &tray_config.title { - tray = tray.with_title(title); + tray = tray.title(title); } if let Some(tooltip) = &tray_config.tooltip { - tray = tray.with_tooltip(tooltip); + tray = tray.tooltip(tooltip); } app .manager diff --git a/core/tauri/src/tray.rs b/core/tauri/src/tray.rs index da6cffc20ccf..ef4ac80464fd 100644 --- a/core/tauri/src/tray.rs +++ b/core/tauri/src/tray.rs @@ -108,9 +108,10 @@ impl TrayIconBuilder { } /// Sets the unique id to build the tray icon with. - pub fn with_id(mut self, id: u32) -> Self { - self.inner = self.inner.with_id(id); - self + pub fn with_id(id: u32) -> Self { + let mut builder = Self::new(); + builder.inner = builder.inner.with_id(id); + builder } /// Set the a menu for this tray icon. @@ -118,7 +119,7 @@ impl TrayIconBuilder { /// ## Platform-specific: /// /// - **Linux**: once a menu is set, it cannot be removed or replaced but you can change its content. - pub fn with_menu(mut self, menu: &M) -> Self { + pub fn menu(mut self, menu: &M) -> Self { self.inner = self.inner.with_menu(menu.inner_owned()); self } @@ -129,7 +130,7 @@ impl TrayIconBuilder { /// /// - **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_icon(mut self, icon: Icon) -> Self { + pub fn icon(mut self, icon: Icon) -> Self { let icon = icon .try_into() .ok() @@ -145,7 +146,7 @@ impl TrayIconBuilder { /// ## Platform-specific: /// /// - **Linux:** Unsupported. - pub fn with_tooltip>(mut self, s: S) -> Self { + pub fn tooltip>(mut self, s: S) -> Self { self.inner = self.inner.with_tooltip(s); self } @@ -160,7 +161,7 @@ impl TrayIconBuilder { /// 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 with_title>(mut self, title: S) -> Self { + pub fn title>(mut self, title: S) -> Self { self.inner = self.inner.with_title(title); self } @@ -169,19 +170,19 @@ impl TrayIconBuilder { /// /// 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 with_temp_dir_path>(mut self, s: P) -> Self { + 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 with_icon_as_template(mut self, is_template: bool) -> Self { + 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 with_menu_on_left_click(mut self, enable: bool) -> Self { + pub fn menu_on_left_click(mut self, enable: bool) -> Self { self.inner = self.inner.with_menu_on_left_click(enable); self } @@ -189,7 +190,7 @@ impl TrayIconBuilder { /// Set a handler for menu events. /// /// Note that this handler is global and will be triggered for all menu events. - pub fn with_on_menu_event, MenuEvent) + Sync + Send + 'static>( + pub fn on_menu_event, MenuEvent) + Sync + Send + 'static>( mut self, f: F, ) -> Self { @@ -198,7 +199,7 @@ impl TrayIconBuilder { } /// Set a handler for this tray icon events. - pub fn with_on_tray_event, TrayIconEvent) + Sync + Send + 'static>( + pub fn on_tray_event, TrayIconEvent) + Sync + Send + 'static>( mut self, f: F, ) -> Self { From 012a681220b79306dec955efe09eff4eb4d5c829 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Wed, 2 Aug 2023 03:19:31 +0300 Subject: [PATCH 041/123] move `on_menu/tray_icon_event` from `tauri::Builder` to `App/AppHandle` --- core/tauri/src/app.rs | 59 +++++++++++++++++++-------------------- core/tauri/src/manager.rs | 15 ++++------ 2 files changed, 34 insertions(+), 40 deletions(-) diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index af76e47709da..1b8729e9e0e4 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -475,6 +475,34 @@ impl App { macro_rules! shared_app_impl { ($app: ty) => { impl $app { + /// Registers a global menu event listener. + pub fn on_menu_event, MenuEvent) + Send + Sync + 'static>( + &self, + handler: F, + ) { + self + .manager + .inner + .menu_event_listeners + .lock() + .unwrap() + .push(Box::new(handler)); + } + + /// Registers a global tray icon menu event listener. + 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 the first tray icon registerd, usually the one configured in /// tauri config file. pub fn tray(&self) -> Option> { @@ -979,15 +1007,9 @@ pub struct Builder { #[allow(unused)] enable_macos_default_menu: bool, - /// Menu event handlers . - menu_event_listeners: Vec>>, - /// Window event handlers that listens to all windows. window_event_listeners: Vec>, - /// Tray icon event handlers. - tray_event_listeners: Vec>, - /// The device event filter. device_event_filter: DeviceEventFilter, } @@ -1010,9 +1032,7 @@ impl Builder { state: StateManager::new(), menu: None, enable_macos_default_menu: true, - menu_event_listeners: Vec::new(), window_event_listeners: Vec::new(), - tray_event_listeners: Vec::new(), device_event_filter: Default::default(), } } @@ -1304,26 +1324,6 @@ impl Builder { self } - /// Registers a global tray icon menu event listener. - #[must_use] - pub fn on_tray_icon_event, TrayIconEvent) + Send + Sync + 'static>( - mut self, - handler: F, - ) -> Self { - self.tray_event_listeners.push(Box::new(handler)); - self - } - - /// Registers a global menu event listener. - #[must_use] - pub fn on_menu_event, MenuEvent) + Send + Sync + 'static>( - mut self, - handler: F, - ) -> Self { - self.menu_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 @@ -1394,8 +1394,7 @@ impl Builder { self.uri_scheme_protocols, self.state, self.window_event_listeners, - (self.menu_event_listeners, HashMap::new()), - self.tray_event_listeners, + HashMap::new(), (self.invoke_responder, self.invoke_initialization_script), ); diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index ad1abb1b778d..18755fe51725 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -316,11 +316,7 @@ impl WindowManager { uri_scheme_protocols: HashMap>>, state: StateManager, window_event_listeners: Vec>, - (menu_event_listeners, window_menu_event_listeners): ( - Vec>>, - HashMap>>, - ), - global_tray_event_listeners: Vec>, + window_menu_event_listeners: HashMap>>, (invoke_responder, invoke_initialization_script): (Arc>, String), ) -> Self { // generate a random isolation key at runtime @@ -347,13 +343,13 @@ impl WindowManager { pattern: context.pattern, uri_scheme_protocols, menus: Default::default(), - menu: Arc::new(Mutex::new(None)), - menu_event_listeners: Arc::new(Mutex::new(menu_event_listeners)), + menu: Default::default(), + menu_event_listeners: Default::default(), window_menu_event_listeners: Arc::new(Mutex::new(window_menu_event_listeners)), window_event_listeners: Arc::new(window_event_listeners), tray_icons: Default::default(), - global_tray_event_listeners: Arc::new(Mutex::new(global_tray_event_listeners)), - tray_event_listeners: Arc::new(Mutex::new(HashMap::new())), + global_tray_event_listeners: Default::default(), + tray_event_listeners: Default::default(), invoke_responder, invoke_initialization_script, }), @@ -980,7 +976,6 @@ mod test { StateManager::new(), Default::default(), Default::default(), - Default::default(), (std::sync::Arc::new(|_, _, _, _| ()), "".into()), ); From 69488163982f2f68fb6a909b4551cb5a25670802 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Wed, 2 Aug 2023 03:19:42 +0300 Subject: [PATCH 042/123] Add `on_menu_event` for `WindowBuilder` --- core/tauri/src/window.rs | 50 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index 2077347a9beb..3201ce8d7d36 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -59,6 +59,8 @@ use std::{ pub(crate) type WebResourceRequestHandler = dyn Fn(&HttpRequest, &mut HttpResponse) + Send + Sync; pub(crate) type NavigationHandler = dyn Fn(&Url) -> bool + Send; +pub(crate) type MenuEventHandler = + Box, crate::menu::MenuEvent) + Send + Sync + 'static>; #[derive(Clone, Serialize)] struct WindowCreatedEvent { @@ -120,6 +122,7 @@ pub struct WindowBuilder<'a, R: Runtime> { pub(crate) webview_attributes: WebviewAttributes, web_resource_request_handler: Option>, navigation_handler: Option>, + on_menu_event: Option>, } impl<'a, R: Runtime> fmt::Debug for WindowBuilder<'a, R> { @@ -194,6 +197,7 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { webview_attributes: WebviewAttributes::new(url), web_resource_request_handler: None, navigation_handler: None, + on_menu_event: None, } } @@ -232,6 +236,7 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { ), web_resource_request_handler: None, navigation_handler: None, + on_menu_event: None, }; builder @@ -309,6 +314,47 @@ 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(()) + /// }); + /// ``` + 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( @@ -351,6 +397,10 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { ) })?; + if let Some(handler) = self.on_menu_event { + window.on_menu_event(handler); + } + if let Some(effects) = window_effects { crate::vibrancy::set_window_effects(&window, Some(effects))?; } From 70c0bfc7d3be0b0e2bfa7c2e61d9f42fa5ad99c3 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Wed, 2 Aug 2023 03:22:36 +0300 Subject: [PATCH 043/123] Add `TrayIcon::on_menu_event` and `TrayIcon::on_tray_icon_event` --- core/tauri/src/tray.rs | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/core/tauri/src/tray.rs b/core/tauri/src/tray.rs index ef4ac80464fd..11ef7fa7075b 100644 --- a/core/tauri/src/tray.rs +++ b/core/tauri/src/tray.rs @@ -189,7 +189,8 @@ impl TrayIconBuilder { /// Set a handler for menu events. /// - /// Note that this handler is global and will be triggered for all 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, @@ -340,6 +341,33 @@ impl TrayIcon { self.app_handle.clone() } + /// 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, Box::new(f)); + } + /// Returns the id associated with this tray icon. pub fn id(&self) -> u32 { self.id From 8b7739fb84843f08e17a5a48a5569e005cead330 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Wed, 2 Aug 2023 03:24:15 +0300 Subject: [PATCH 044/123] fix api example --- examples/api/src-tauri/src/tray.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/examples/api/src-tauri/src/tray.rs b/examples/api/src-tauri/src/tray.rs index dcfa9f04a0f9..6bc7a6bbeea6 100644 --- a/examples/api/src-tauri/src/tray.rs +++ b/examples/api/src-tauri/src/tray.rs @@ -42,12 +42,11 @@ pub fn create_tray(app: &tauri::AppHandle) -> tauri::Result<()> { const TRAY_ID: u32 = 21937; - let _ = TrayIconBuilder::new() - .with_tooltip("Tauri") - .with_id(TRAY_ID) - .with_icon(app.default_window_icon().unwrap().clone()) - .with_menu(&menu1) - .with_on_menu_event(move |app, event| { + let _ = TrayIconBuilder::with_id(TRAY_ID) + .tooltip("Tauri") + .icon(app.default_window_icon().unwrap().clone()) + .menu(&menu1) + .on_menu_event(move |app, event| { dbg!(event); match event.id { i if i == quit_i.id() => { @@ -108,7 +107,7 @@ pub fn create_tray(app: &tauri::AppHandle) -> tauri::Result<()> { _ => {} } }) - .with_on_tray_event(|tray, event| { + .on_tray_event(|tray, event| { if event.click_type == ClickType::Left { let app = tray.app_handle(); if let Some(window) = app.get_window("main") { From 74cc6822e09bfde25437156dfa015ee86685a1fa Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Wed, 2 Aug 2023 03:37:56 +0300 Subject: [PATCH 045/123] take a `Manager` argument instead of just `AppHandle` --- core/tauri/src/menu/check.rs | 8 ++-- core/tauri/src/menu/icon.rs | 14 +++--- core/tauri/src/menu/menu.rs | 12 ++--- core/tauri/src/menu/normal.rs | 8 ++-- core/tauri/src/menu/predefined.rs | 72 +++++++++++++++--------------- core/tauri/src/menu/submenu.rs | 12 ++--- core/tauri/src/tray.rs | 14 +++--- examples/api/src-tauri/src/tray.rs | 22 ++++----- 8 files changed, 81 insertions(+), 81 deletions(-) diff --git a/core/tauri/src/menu/check.rs b/core/tauri/src/menu/check.rs index c291aceae885..7b3f9c072d2c 100644 --- a/core/tauri/src/menu/check.rs +++ b/core/tauri/src/menu/check.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use crate::{run_main_thread, runtime::menu as muda, AppHandle, Runtime}; +use crate::{run_main_thread, runtime::menu as muda, AppHandle, Manager, Runtime}; /// A menu item inside a [`Menu`] or [`Submenu`] and contains only text. /// @@ -51,8 +51,8 @@ impl CheckMenuItem { /// /// - `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>( - app_handle: &AppHandle, + pub fn new, S: AsRef>( + manager: &M, text: S, enabled: bool, checked: bool, @@ -67,7 +67,7 @@ impl CheckMenuItem { Self { id: item.id(), inner: item, - app_handle: app_handle.clone(), + app_handle: manager.app_handle(), } } diff --git a/core/tauri/src/menu/icon.rs b/core/tauri/src/menu/icon.rs index d3b5c89c9822..c47a685eec1f 100644 --- a/core/tauri/src/menu/icon.rs +++ b/core/tauri/src/menu/icon.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: MIT use super::NativeIcon; -use crate::{run_main_thread, runtime::menu as muda, AppHandle, Icon, Runtime}; +use crate::{run_main_thread, runtime::menu as muda, AppHandle, Icon, Manager, Runtime}; /// A menu item inside a [`Menu`] or [`Submenu`] and contains only text. /// @@ -52,8 +52,8 @@ impl IconMenuItem { /// /// - `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>( - app_handle: &AppHandle, + pub fn new, S: AsRef>( + manager: &M, text: S, enabled: bool, icon: Option, @@ -70,7 +70,7 @@ impl IconMenuItem { Self { id: item.id(), inner: item, - app_handle: app_handle.clone(), + app_handle: manager.app_handle(), } } @@ -81,8 +81,8 @@ impl IconMenuItem { /// ## Platform-specific: /// /// - **Windows / Linux**: Unsupported. - pub fn with_native_icon>( - app_handle: &AppHandle, + pub fn with_native_icon, S: AsRef>( + manager: &M, text: S, enabled: bool, native_icon: Option, @@ -97,7 +97,7 @@ impl IconMenuItem { Self { id: item.id(), inner: item, - app_handle: app_handle.clone(), + app_handle: manager.app_handle(), } } diff --git a/core/tauri/src/menu/menu.rs b/core/tauri/src/menu/menu.rs index 05409f466f6b..e9ae8428c026 100644 --- a/core/tauri/src/menu/menu.rs +++ b/core/tauri/src/menu/menu.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: MIT use super::{IsMenuItem, MenuItemKind, PredefinedMenuItem, Submenu}; -use crate::{run_main_thread, runtime::menu as muda, AppHandle, Position, Runtime}; +use crate::{run_main_thread, runtime::menu as muda, AppHandle, Manager, Position, Runtime}; use muda::ContextMenu; use tauri_runtime::menu::AboutMetadata; @@ -79,21 +79,21 @@ impl super::sealed::ContextMenuBase for Menu { impl Menu { /// Creates a new menu. - pub fn new(app_handle: &AppHandle) -> Self { + pub fn new>(manager: &M) -> Self { let menu = muda::Menu::new(); Self { id: menu.id(), inner: menu, - app_handle: app_handle.clone(), + app_handle: manager.app_handle(), } } /// Creates a new menu with given `items`. It calls [`Menu::new`] and [`Menu::append_items`] internally. - pub fn with_items( - app_handle: &AppHandle, + pub fn with_items>( + manager: &M, items: &[&dyn IsMenuItem], ) -> crate::Result { - let menu = Self::new(app_handle); + let menu = Self::new(manager); menu.append_items(items)?; Ok(menu) } diff --git a/core/tauri/src/menu/normal.rs b/core/tauri/src/menu/normal.rs index 370ffd1720f1..dbd115be292b 100644 --- a/core/tauri/src/menu/normal.rs +++ b/core/tauri/src/menu/normal.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use crate::{run_main_thread, runtime::menu as muda, AppHandle, Runtime}; +use crate::{run_main_thread, runtime::menu as muda, AppHandle, Manager, Runtime}; /// A menu item inside a [`Menu`] or [`Submenu`] and contains only text. /// @@ -51,8 +51,8 @@ impl MenuItem { /// /// - `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>( - app_handle: &AppHandle, + pub fn new, S: AsRef>( + manager: &M, text: S, enabled: bool, acccelerator: Option, @@ -65,7 +65,7 @@ impl MenuItem { Self { id: item.id(), inner: item, - app_handle: app_handle.clone(), + app_handle: manager.app_handle(), } } diff --git a/core/tauri/src/menu/predefined.rs b/core/tauri/src/menu/predefined.rs index ec93b213e04e..ef9e369a7640 100644 --- a/core/tauri/src/menu/predefined.rs +++ b/core/tauri/src/menu/predefined.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: MIT use super::AboutMetadata; -use crate::{run_main_thread, runtime::menu as muda, AppHandle, Runtime}; +use crate::{run_main_thread, runtime::menu as muda, AppHandle, Manager, Runtime}; /// A predefined (native) menu item which has a predfined behavior by the OS or by this crate. pub struct PredefinedMenuItem { @@ -44,42 +44,42 @@ impl super::IsMenuItem for PredefinedMenuItem { impl PredefinedMenuItem { /// Separator menu item - pub fn separator(app_handle: &AppHandle) -> Self { + pub fn separator>(manager: &M) -> Self { Self { inner: muda::PredefinedMenuItem::separator(), - app_handle: app_handle.clone(), + app_handle: manager.app_handle(), } } /// Copy menu item - pub fn copy(app_handle: &AppHandle, text: Option<&str>) -> Self { + pub fn copy>(manager: &M, text: Option<&str>) -> Self { Self { inner: muda::PredefinedMenuItem::copy(text), - app_handle: app_handle.clone(), + app_handle: manager.app_handle(), } } /// Cut menu item - pub fn cut(app_handle: &AppHandle, text: Option<&str>) -> Self { + pub fn cut>(manager: &M, text: Option<&str>) -> Self { Self { inner: muda::PredefinedMenuItem::cut(text), - app_handle: app_handle.clone(), + app_handle: manager.app_handle(), } } /// Paste menu item - pub fn paste(app_handle: &AppHandle, text: Option<&str>) -> Self { + pub fn paste>(manager: &M, text: Option<&str>) -> Self { Self { inner: muda::PredefinedMenuItem::paste(text), - app_handle: app_handle.clone(), + app_handle: manager.app_handle(), } } /// SelectAll menu item - pub fn select_all(app_handle: &AppHandle, text: Option<&str>) -> Self { + pub fn select_all>(manager: &M, text: Option<&str>) -> Self { Self { inner: muda::PredefinedMenuItem::select_all(text), - app_handle: app_handle.clone(), + app_handle: manager.app_handle(), } } @@ -88,10 +88,10 @@ impl PredefinedMenuItem { /// ## Platform-specific: /// /// - **Windows / Linux:** Unsupported. - pub fn undo(app_handle: &AppHandle, text: Option<&str>) -> Self { + pub fn undo>(manager: &M, text: Option<&str>) -> Self { Self { inner: muda::PredefinedMenuItem::undo(text), - app_handle: app_handle.clone(), + app_handle: manager.app_handle(), } } /// Redo menu item @@ -99,10 +99,10 @@ impl PredefinedMenuItem { /// ## Platform-specific: /// /// - **Windows / Linux:** Unsupported. - pub fn redo(app_handle: &AppHandle, text: Option<&str>) -> Self { + pub fn redo>(manager: &M, text: Option<&str>) -> Self { Self { inner: muda::PredefinedMenuItem::redo(text), - app_handle: app_handle.clone(), + app_handle: manager.app_handle(), } } @@ -111,10 +111,10 @@ impl PredefinedMenuItem { /// ## Platform-specific: /// /// - **Linux:** Unsupported. - pub fn minimize(app_handle: &AppHandle, text: Option<&str>) -> Self { + pub fn minimize>(manager: &M, text: Option<&str>) -> Self { Self { inner: muda::PredefinedMenuItem::minimize(text), - app_handle: app_handle.clone(), + app_handle: manager.app_handle(), } } @@ -123,10 +123,10 @@ impl PredefinedMenuItem { /// ## Platform-specific: /// /// - **Linux:** Unsupported. - pub fn maximize(app_handle: &AppHandle, text: Option<&str>) -> Self { + pub fn maximize>(manager: &M, text: Option<&str>) -> Self { Self { inner: muda::PredefinedMenuItem::maximize(text), - app_handle: app_handle.clone(), + app_handle: manager.app_handle(), } } @@ -135,10 +135,10 @@ impl PredefinedMenuItem { /// ## Platform-specific: /// /// - **Windows / Linux:** Unsupported. - pub fn fullscreen(app_handle: &AppHandle, text: Option<&str>) -> Self { + pub fn fullscreen>(manager: &M, text: Option<&str>) -> Self { Self { inner: muda::PredefinedMenuItem::fullscreen(text), - app_handle: app_handle.clone(), + app_handle: manager.app_handle(), } } @@ -147,10 +147,10 @@ impl PredefinedMenuItem { /// ## Platform-specific: /// /// - **Linux:** Unsupported. - pub fn hide(app_handle: &AppHandle, text: Option<&str>) -> Self { + pub fn hide>(manager: &M, text: Option<&str>) -> Self { Self { inner: muda::PredefinedMenuItem::hide(text), - app_handle: app_handle.clone(), + app_handle: manager.app_handle(), } } @@ -159,10 +159,10 @@ impl PredefinedMenuItem { /// ## Platform-specific: /// /// - **Linux:** Unsupported. - pub fn hide_others(app_handle: &AppHandle, text: Option<&str>) -> Self { + pub fn hide_others>(manager: &M, text: Option<&str>) -> Self { Self { inner: muda::PredefinedMenuItem::hide_others(text), - app_handle: app_handle.clone(), + app_handle: manager.app_handle(), } } @@ -171,10 +171,10 @@ impl PredefinedMenuItem { /// ## Platform-specific: /// /// - **Windows / Linux:** Unsupported. - pub fn show_all(app_handle: &AppHandle, text: Option<&str>) -> Self { + pub fn show_all>(manager: &M, text: Option<&str>) -> Self { Self { inner: muda::PredefinedMenuItem::show_all(text), - app_handle: app_handle.clone(), + app_handle: manager.app_handle(), } } @@ -183,10 +183,10 @@ impl PredefinedMenuItem { /// ## Platform-specific: /// /// - **Linux:** Unsupported. - pub fn close_window(app_handle: &AppHandle, text: Option<&str>) -> Self { + pub fn close_window>(manager: &M, text: Option<&str>) -> Self { Self { inner: muda::PredefinedMenuItem::show_all(text), - app_handle: app_handle.clone(), + app_handle: manager.app_handle(), } } @@ -195,22 +195,22 @@ impl PredefinedMenuItem { /// ## Platform-specific: /// /// - **Linux:** Unsupported. - pub fn quit(app_handle: &AppHandle, text: Option<&str>) -> Self { + pub fn quit>(manager: &M, text: Option<&str>) -> Self { Self { inner: muda::PredefinedMenuItem::quit(text), - app_handle: app_handle.clone(), + app_handle: manager.app_handle(), } } /// About app menu item - pub fn about( - app_handle: &AppHandle, + pub fn about>( + manager: &M, text: Option<&str>, metadata: Option, ) -> Self { Self { inner: muda::PredefinedMenuItem::about(text, metadata), - app_handle: app_handle.clone(), + app_handle: manager.app_handle(), } } @@ -219,10 +219,10 @@ impl PredefinedMenuItem { /// ## Platform-specific: /// /// - **Windows / Linux:** Unsupported. - pub fn services(app_handle: &AppHandle, text: Option<&str>) -> Self { + pub fn services>(manager: &M, text: Option<&str>) -> Self { Self { inner: muda::PredefinedMenuItem::services(text), - app_handle: app_handle.clone(), + app_handle: manager.app_handle(), } } diff --git a/core/tauri/src/menu/submenu.rs b/core/tauri/src/menu/submenu.rs index efc8fd8c6f18..583748a3b627 100644 --- a/core/tauri/src/menu/submenu.rs +++ b/core/tauri/src/menu/submenu.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: MIT use super::{IsMenuItem, MenuItemKind}; -use crate::{run_main_thread, runtime::menu as muda, AppHandle, Position, Runtime}; +use crate::{run_main_thread, runtime::menu as muda, AppHandle, Manager, Position, Runtime}; use muda::ContextMenu; /// A type that is a submenu inside a [`Menu`] or [`Submenu`] @@ -96,23 +96,23 @@ impl super::sealed::ContextMenuBase for Submenu { impl Submenu { /// Creates a new submenu. - pub fn new>(app_handle: &AppHandle, text: S, enabled: bool) -> Self { + pub fn new, S: AsRef>(manager: &M, text: S, enabled: bool) -> Self { let submenu = muda::Submenu::new(text, enabled); Self { id: submenu.id(), inner: submenu, - app_handle: app_handle.clone(), + app_handle: manager.app_handle(), } } /// Creates a new menu with given `items`. It calls [`Submenu::new`] and [`Submenu::append_items`] internally. - pub fn with_items>( - app_handle: &AppHandle, + pub fn with_items, S: AsRef>( + manager: &M, text: S, enabled: bool, items: &[&dyn IsMenuItem], ) -> crate::Result { - let menu = Self::new(app_handle, text, enabled); + let menu = Self::new(manager, text, enabled); menu.append_items(items)?; Ok(menu) } diff --git a/core/tauri/src/tray.rs b/core/tauri/src/tray.rs index 11ef7fa7075b..744e9d9f66df 100644 --- a/core/tauri/src/tray.rs +++ b/core/tauri/src/tray.rs @@ -6,7 +6,7 @@ use crate::menu::MenuEvent; use crate::{menu::ContextMenu, runtime::tray as tray_icon}; -use crate::{run_main_thread, AppHandle, Icon, Runtime}; +use crate::{run_main_thread, AppHandle, Icon, Manager, Runtime}; use std::path::{Path, PathBuf}; pub use tray_icon::{ClickType, Rectangle, TrayIconEvent}; @@ -215,16 +215,16 @@ impl TrayIconBuilder { } /// Builds and adds a new [`TrayIcon`] to the system tray. - pub fn build(self, app_handle: &AppHandle) -> crate::Result> { + pub fn build>(self, manager: &M) -> crate::Result> { let id = self.id(); let inner = self.inner.build()?; let icon = TrayIcon { id, inner, - app_handle: app_handle.clone(), + app_handle: manager.app_handle(), }; - icon.register(app_handle, self.on_menu_event, self.on_tray_event); + icon.register(&icon.app_handle, self.on_menu_event, self.on_tray_event); Ok(icon) } @@ -297,7 +297,7 @@ impl TrayIcon { /// /// - **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(app_handle: &AppHandle, mut attrs: TrayIconAttributes) -> crate::Result { + pub fn new>(manager: &M, mut attrs: TrayIconAttributes) -> crate::Result { let on_menu_event = attrs.on_menu_event.take(); let on_tray_event = attrs.on_tray_event.take(); @@ -305,10 +305,10 @@ impl TrayIcon { let icon = Self { id: inner.id(), inner, - app_handle: app_handle.clone(), + app_handle: manager.app_handle(), }; - icon.register(app_handle, on_menu_event, on_tray_event); + icon.register(&icon.app_handle, on_menu_event, on_tray_event); Ok(icon) } diff --git a/examples/api/src-tauri/src/tray.rs b/examples/api/src-tauri/src/tray.rs index 6bc7a6bbeea6..78a998008f19 100644 --- a/examples/api/src-tauri/src/tray.rs +++ b/examples/api/src-tauri/src/tray.rs @@ -10,17 +10,17 @@ use tauri::{ }; pub fn create_tray(app: &tauri::AppHandle) -> tauri::Result<()> { - let toggle_i = MenuItem::new(&app, "Toggle", true, None); - let new_window_i = MenuItem::new(&app, "New window", true, None); - let icon_i_1 = MenuItem::new(&app, "Icon 1", true, None); - let icon_i_2 = MenuItem::new(&app, "Icon 2", true, None); + let toggle_i = MenuItem::new(app, "Toggle", true, None); + let new_window_i = MenuItem::new(app, "New window", true, None); + let icon_i_1 = MenuItem::new(app, "Icon 1", true, None); + let icon_i_2 = MenuItem::new(app, "Icon 2", true, None); #[cfg(target_os = "macos")] - let set_title_i = MenuItem::new(&app, "Set Title", true, None); - let switch_i = MenuItem::new(&app, "Switch Menu", true, None); - let quit_i = MenuItem::new(&app, "Quit", true, None); - let remove_tray_i = MenuItem::new(&app, "Remove Tray icon", true, None); + let set_title_i = MenuItem::new(app, "Set Title", true, None); + let switch_i = MenuItem::new(app, "Switch Menu", true, None); + let quit_i = MenuItem::new(app, "Quit", true, None); + let remove_tray_i = MenuItem::new(app, "Remove Tray icon", true, None); let menu1 = Menu::with_items( - &app, + app, &[ &toggle_i, &new_window_i, @@ -34,7 +34,7 @@ pub fn create_tray(app: &tauri::AppHandle) -> tauri::Result<()> { ], )?; let menu2 = Menu::with_items( - &app, + app, &[&toggle_i, &new_window_i, &switch_i, &quit_i, &remove_tray_i], )?; @@ -116,7 +116,7 @@ pub fn create_tray(app: &tauri::AppHandle) -> tauri::Result<()> { } } }) - .build(&app); + .build(app); Ok(()) } From 6c7155b0f4be3da57c384bb2f6893cf1ebccdda7 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Wed, 2 Aug 2023 03:40:08 +0300 Subject: [PATCH 046/123] add todo --- core/tauri/src/menu/builders/mod.rs | 2 +- core/tauri/src/menu/mod.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/core/tauri/src/menu/builders/mod.rs b/core/tauri/src/menu/builders/mod.rs index f06c081cd41e..6eb7ec8e9009 100644 --- a/core/tauri/src/menu/builders/mod.rs +++ b/core/tauri/src/menu/builders/mod.rs @@ -6,4 +6,4 @@ pub use crate::runtime::menu::builders::AboutMetadataBuilder; -// TODO (muda-migration): add builder types +// TODO(muda-migration): add builder types diff --git a/core/tauri/src/menu/mod.rs b/core/tauri/src/menu/mod.rs index c1ccdd4f473f..646ea287364e 100644 --- a/core/tauri/src/menu/mod.rs +++ b/core/tauri/src/menu/mod.rs @@ -4,6 +4,7 @@ //! Menu types and utility functions +// TODO(muda-migration): look for a way to initalize menu for a window without routing through tauri-runtime-wry // TODO(muda-migration): figure out js events pub mod builders; From a9c52445dbefc7946b64e0c1a91e15442de48271 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Wed, 2 Aug 2023 16:07:04 +0300 Subject: [PATCH 047/123] docs & expose `cleanup_before_exit()` --- core/tauri/src/app.rs | 15 ++++++++------- core/tauri/src/path/desktop.rs | 14 +++++++------- core/tauri/src/path/mod.rs | 2 +- core/tauri/src/window.rs | 4 ++-- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 1b8729e9e0e4..387647101204 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -391,11 +391,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) { - self.manager.inner.tray_icons.lock().unwrap().clear() - } } impl Manager for AppHandle {} @@ -804,6 +799,12 @@ macro_rules! shared_app_impl { } Ok(()) } + + /// Runs necessary cleanup tasks before exiting the process. + /// **You sould always exit the process immediately after this function returns.** + pub fn cleanup_before_exit(&self) { + self.manager.inner.tray_icons.lock().unwrap().clear() + } } }; } @@ -927,8 +928,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_tray_icon](`AppHandle#method.remove_tray_icon`) (Windows only). + /// The cleanup calls [`App::cleanup_before_exit`] so you may want to call that function before exiting the application. /// /// # Examples /// ```no_run @@ -939,6 +939,7 @@ impl App { /// loop { /// let iteration = app.run_iteration(); /// if iteration.window_count == 0 { + /// app.cleanup_before_exit(); /// break; /// } /// } 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/window.rs b/core/tauri/src/window.rs index 3201ce8d7d36..7f87d85eecbb 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -721,7 +721,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); @@ -1817,7 +1817,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(); From 9d4a67641dedf0418cffe64fa975c2ace9f49471 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Wed, 2 Aug 2023 17:20:28 +0300 Subject: [PATCH 048/123] all tauri handlers return result, make it easier to use `?` --- core/tauri/src/app.rs | 30 ++++++--- core/tauri/src/manager.rs | 10 +-- core/tauri/src/tray.rs | 105 ++++++++++++++++------------- core/tauri/src/window.rs | 14 ++-- examples/api/src-tauri/src/tray.rs | 34 +++++----- 5 files changed, 107 insertions(+), 86 deletions(-) diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 387647101204..9977474fe518 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -53,10 +53,12 @@ use crate::{ #[cfg(target_os = "macos")] use crate::ActivationPolicy; -pub(crate) type GlobalMenuEventListener = Box; -pub(crate) type GlobalTrayIconEventListener = - Box, crate::tray::TrayIconEvent) + Send + Sync>; -pub(crate) type GlobalWindowEventListener = Box) + Send + Sync>; +pub(crate) type GlobalMenuEventListener = + Box crate::Result<()> + Send + Sync>; +pub(crate) type GlobalTrayIconEventListener = + Box crate::Result<()> + Send + Sync>; +pub(crate) type GlobalWindowEventListener = + Box) -> crate::Result<()> + Send + Sync>; /// Api exposed on the `ExitRequested` event. #[derive(Debug)] @@ -471,7 +473,9 @@ macro_rules! shared_app_impl { ($app: ty) => { impl $app { /// Registers a global menu event listener. - pub fn on_menu_event, MenuEvent) + Send + Sync + 'static>( + pub fn on_menu_event< + F: Fn(&AppHandle, MenuEvent) -> crate::Result<()> + Send + Sync + 'static, + >( &self, handler: F, ) { @@ -485,7 +489,9 @@ macro_rules! shared_app_impl { } /// Registers a global tray icon menu event listener. - pub fn on_tray_icon_event, TrayIconEvent) + Send + Sync + 'static>( + pub fn on_tray_icon_event< + F: Fn(&AppHandle, TrayIconEvent) -> crate::Result<()> + Send + Sync + 'static, + >( &self, handler: F, ) { @@ -1317,7 +1323,9 @@ impl Builder { /// }); /// ``` #[must_use] - pub fn on_window_event) + Send + Sync + 'static>( + pub fn on_window_event< + F: Fn(GlobalWindowEvent) -> crate::Result<()> + Send + Sync + 'static, + >( mut self, handler: F, ) -> Self { @@ -1678,7 +1686,7 @@ fn on_event_loop_event, RunEvent) + 'static>( .lock() .unwrap() { - listener(app_handle, e) + let _ = listener(app_handle, e); } for (label, listener) in &*app_handle .manager @@ -1688,7 +1696,7 @@ fn on_event_loop_event, RunEvent) + 'static>( .unwrap() { if let Some(w) = app_handle.get_window(label) { - listener(&w, e) + let _ = listener(&w, e); } } } @@ -1700,7 +1708,7 @@ fn on_event_loop_event, RunEvent) + 'static>( .lock() .unwrap() { - listener(app_handle, e) + let _ = listener(app_handle, e); } for (id, listener) in &*app_handle @@ -1712,7 +1720,7 @@ fn on_event_loop_event, RunEvent) + 'static>( { if e.id == *id { if let Some(tray) = app_handle.tray_by_id(*id) { - listener(&tray, e) + let _ = listener(&tray, e); } } } diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index 18755fe51725..53c285acb214 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -74,8 +74,6 @@ const WINDOW_FILE_DROP_CANCELLED_EVENT: &str = "tauri://file-drop-cancelled"; pub(crate) const STRINGIFY_IPC_MESSAGE_FN: &str = include_str!("../scripts/stringify-ipc-message-fn.js"); -type TrayIconEventListener = Box, crate::tray::TrayIconEvent) + Send + Sync>; - // we need to proxy the dev server on mobile because we can't use `localhost`, so we use the local IP address // and we do not get a secure context without the custom protocol that proxies to the dev server // additionally, we need the custom protocol to inject the initialization scripts on Android @@ -241,9 +239,11 @@ pub struct InnerWindowManager { /// Tray icons pub(crate) tray_icons: Arc>>>, /// Global Tray icon event listeners. - pub(crate) global_tray_event_listeners: Arc>>>, + pub(crate) global_tray_event_listeners: + Arc>>>>, /// Tray icon event listeners. - pub(crate) tray_event_listeners: Arc>>>, + pub(crate) tray_event_listeners: + Arc>>>>, /// Responder for invoke calls. invoke_responder: Arc>, /// The script that initializes the invoke system. @@ -1231,7 +1231,7 @@ impl WindowManager { window.on_window_event(move |event| { let _ = on_window_event(&window_, &manager, event); for handler in window_event_listeners.iter() { - handler(GlobalWindowEvent { + let _ = handler(GlobalWindowEvent { window: window_.clone(), event: event.clone(), }); diff --git a/core/tauri/src/tray.rs b/core/tauri/src/tray.rs index 744e9d9f66df..55bd445f757a 100644 --- a/core/tauri/src/tray.rs +++ b/core/tauri/src/tray.rs @@ -4,6 +4,7 @@ //! Tray icon types and utility functions +use crate::app::{GlobalMenuEventListener, GlobalTrayIconEventListener}; use crate::menu::MenuEvent; use crate::{menu::ContextMenu, runtime::tray as tray_icon}; use crate::{run_main_thread, AppHandle, Icon, Manager, Runtime}; @@ -30,12 +31,10 @@ pub struct TrayIconAttributes { menu: Option>, /// Set a handler for menu events - #[allow(clippy::type_complexity)] - on_menu_event: Option, MenuEvent) + Send + Sync + 'static>>, + on_menu_event: Option>>, /// Set a handler for tray icon events - #[allow(clippy::type_complexity)] - on_tray_event: Option, TrayIconEvent) + Send + Sync + 'static>>, + on_tray_event: Option>>, /// Tray icon /// @@ -88,10 +87,8 @@ impl From> for tray_icon::TrayIconAttributes { /// [`TrayIcon`] builder struct and associated methods. #[derive(Default)] pub struct TrayIconBuilder { - #[allow(clippy::type_complexity)] - on_menu_event: Option, MenuEvent) + Sync + Send + 'static>>, - #[allow(clippy::type_complexity)] - on_tray_event: Option, TrayIconEvent) + Sync + Send + 'static>>, + on_menu_event: Option>>, + on_tray_event: Option>>, inner: tray_icon::TrayIconBuilder, } @@ -191,7 +188,9 @@ impl TrayIconBuilder { /// /// 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>( + pub fn on_menu_event< + F: Fn(&AppHandle, MenuEvent) -> crate::Result<()> + Sync + Send + 'static, + >( mut self, f: F, ) -> Self { @@ -200,7 +199,9 @@ impl TrayIconBuilder { } /// Set a handler for this tray icon events. - pub fn on_tray_event, TrayIconEvent) + Sync + Send + 'static>( + pub fn on_tray_event< + F: Fn(&TrayIcon, TrayIconEvent) -> crate::Result<()> + Sync + Send + 'static, + >( mut self, f: F, ) -> Self { @@ -256,41 +257,6 @@ unsafe impl Sync for TrayIcon {} unsafe impl Send for TrayIcon {} impl TrayIcon { - fn register( - &self, - app_handle: &AppHandle, - on_menu_event: Option, MenuEvent) + Sync + Send + 'static>>, - on_tray_event: Option, TrayIconEvent) + Sync + Send + 'static>>, - ) { - 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, handler); - } - - app_handle - .manager - .inner - .tray_icons - .lock() - .unwrap() - .push(self.clone()); - } - /// Builds and adds a new tray icon to the system tray. /// /// ## Platform-specific: @@ -336,6 +302,41 @@ impl TrayIcon { Ok(icon) } + 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, 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.clone() @@ -345,7 +346,12 @@ impl TrayIcon { /// /// 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) { + pub fn on_menu_event< + F: Fn(&AppHandle, MenuEvent) -> crate::Result<()> + Sync + Send + 'static, + >( + &self, + f: F, + ) { self .app_handle .manager @@ -357,7 +363,12 @@ impl TrayIcon { } /// Register a handler for this tray icon events. - pub fn on_tray_event, TrayIconEvent) + Sync + Send + 'static>(&self, f: F) { + pub fn on_tray_event< + F: Fn(&TrayIcon, TrayIconEvent) -> crate::Result<()> + Sync + Send + 'static, + >( + &self, + f: F, + ) { self .app_handle .manager diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index 7f87d85eecbb..c24239274c47 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -4,7 +4,7 @@ //! The Tauri window types and functions. -use crate::menu::ContextMenu; +use crate::{app::GlobalMenuEventListener, menu::ContextMenu}; pub use tauri_utils::{config::Color, WindowEffect as Effect, WindowEffectState as EffectState}; use url::Url; @@ -59,8 +59,6 @@ use std::{ pub(crate) type WebResourceRequestHandler = dyn Fn(&HttpRequest, &mut HttpResponse) + Send + Sync; pub(crate) type NavigationHandler = dyn Fn(&Url) -> bool + Send; -pub(crate) type MenuEventHandler = - Box, crate::menu::MenuEvent) + Send + Sync + 'static>; #[derive(Clone, Serialize)] struct WindowCreatedEvent { @@ -122,7 +120,7 @@ pub struct WindowBuilder<'a, R: Runtime> { pub(crate) webview_attributes: WebviewAttributes, web_resource_request_handler: Option>, navigation_handler: Option>, - on_menu_event: Option>, + on_menu_event: Option>>, } impl<'a, R: Runtime> fmt::Debug for WindowBuilder<'a, R> { @@ -347,7 +345,9 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { /// Ok(()) /// }); /// ``` - pub fn on_menu_event, crate::menu::MenuEvent) + Send + Sync + 'static>( + pub fn on_menu_event< + F: Fn(&Window, crate::menu::MenuEvent) -> crate::Result<()> + Send + Sync + 'static, + >( mut self, f: F, ) -> Self { @@ -1204,7 +1204,9 @@ impl Window { /// Ok(()) /// }); /// ``` - pub fn on_menu_event, crate::menu::MenuEvent) + Send + Sync + 'static>( + pub fn on_menu_event< + F: Fn(&Window, crate::menu::MenuEvent) -> crate::Result<()> + Send + Sync + 'static, + >( &self, f: F, ) { diff --git a/examples/api/src-tauri/src/tray.rs b/examples/api/src-tauri/src/tray.rs index 78a998008f19..0553f4483fb7 100644 --- a/examples/api/src-tauri/src/tray.rs +++ b/examples/api/src-tauri/src/tray.rs @@ -47,7 +47,6 @@ pub fn create_tray(app: &tauri::AppHandle) -> tauri::Result<()> { .icon(app.default_window_icon().unwrap().clone()) .menu(&menu1) .on_menu_event(move |app, event| { - dbg!(event); match event.id { i if i == quit_i.id() => { app.exit(0); @@ -57,37 +56,34 @@ pub fn create_tray(app: &tauri::AppHandle) -> tauri::Result<()> { } i if i == toggle_i.id() => { if let Some(window) = app.get_window("main") { - let new_title = if window.is_visible().unwrap() { - window.hide().unwrap(); + let new_title = if window.is_visible()? { + window.hide()?; "Show" } else { - window.show().unwrap(); + window.show()?; "Hide" }; - toggle_i.set_text(new_title).unwrap(); + toggle_i.set_text(new_title)?; } } i if i == new_window_i.id() => { WindowBuilder::new(app, "new", WindowUrl::App("index.html".into())) .title("Tauri") - .build() - .unwrap(); + .build()?; } #[cfg(target_os = "macos")] i if i == set_title_i.id() => { if let Some(tray) = app.tray_by_id(TRAY_ID) { - tray.set_title(Some("Tauri")).unwrap(); + tray.set_title(Some("Tauri"))?; } } i if i == icon_i_1.id() || i == icon_i_2.id() => { if let Some(tray) = app.tray_by_id(TRAY_ID) { - tray - .set_icon(Some(tauri::Icon::Raw(if i == icon_i_1.id() { - include_bytes!("../../../.icons/icon.ico").to_vec() - } else { - include_bytes!("../../../.icons/tray_icon_with_transparency.png").to_vec() - }))) - .unwrap(); + tray.set_icon(Some(tauri::Icon::Raw(if i == icon_i_1.id() { + include_bytes!("../../../.icons/icon.ico").to_vec() + } else { + include_bytes!("../../../.icons/tray_icon_with_transparency.png").to_vec() + })))?; } } i if i == switch_i.id() => { @@ -98,14 +94,16 @@ pub fn create_tray(app: &tauri::AppHandle) -> tauri::Result<()> { (menu1.clone(), "Tauri") }; if let Some(tray) = app.tray_by_id(TRAY_ID) { - tray.set_menu(Some(menu)).unwrap(); - tray.set_tooltip(Some(tooltip)).unwrap(); + tray.set_menu(Some(menu))?; + tray.set_tooltip(Some(tooltip))?; } is_menu1.store(!flag, Ordering::Relaxed); } _ => {} } + + Ok(()) }) .on_tray_event(|tray, event| { if event.click_type == ClickType::Left { @@ -115,6 +113,8 @@ pub fn create_tray(app: &tauri::AppHandle) -> tauri::Result<()> { window.set_focus().unwrap(); } } + + Ok(()) }) .build(app); From 8062ddbb7cab21bad648f401772f3ba4ddefdac7 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Wed, 2 Aug 2023 17:39:22 +0300 Subject: [PATCH 049/123] remove `TrayIconAttributes` --- core/tauri/src/tray.rs | 120 ++--------------------------------------- 1 file changed, 3 insertions(+), 117 deletions(-) diff --git a/core/tauri/src/tray.rs b/core/tauri/src/tray.rs index 55bd445f757a..e3f6feeba837 100644 --- a/core/tauri/src/tray.rs +++ b/core/tauri/src/tray.rs @@ -8,82 +8,11 @@ use crate::app::{GlobalMenuEventListener, GlobalTrayIconEventListener}; use crate::menu::MenuEvent; use crate::{menu::ContextMenu, runtime::tray as tray_icon}; use crate::{run_main_thread, AppHandle, Icon, Manager, Runtime}; -use std::path::{Path, PathBuf}; +use std::path::Path; pub use tray_icon::{ClickType, Rectangle, TrayIconEvent}; // TODO(muda-migration): figure out js events -/// Attributes to use when creating a tray icon. -#[derive(Default)] -pub struct TrayIconAttributes { - /// Tray icon tooltip - /// - /// ## Platform-specific: - /// - /// - **Linux:** Unsupported. - tooltip: Option, - - /// Tray menu - /// - /// ## Platform-specific: - /// - /// - **Linux**: once a menu is set, it cannot be removed. - menu: Option>, - - /// Set a handler for menu events - on_menu_event: Option>>, - - /// Set a handler for tray icon events - on_tray_event: Option>>, - - /// 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. - icon: Option, - - /// Tray icon temp dir path. **Linux only**. - temp_dir_path: Option, - - /// Use the icon as a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc). **macOS only**. - icon_is_template: bool, - - /// Whether to show the tray menu on left click or not, default is `true`. **macOS only**. - menu_on_left_click: bool, - - /// 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. - title: Option, -} - -impl From> for tray_icon::TrayIconAttributes { - fn from(value: TrayIconAttributes) -> Self { - Self { - tooltip: value.tooltip, - menu: value.menu, - icon: value.icon.and_then(|i| { - i.try_into() - .ok() - .and_then(|i: crate::runtime::Icon| i.try_into().ok()) - }), - temp_dir_path: value.temp_dir_path, - icon_is_template: value.icon_is_template, - menu_on_left_click: value.menu_on_left_click, - title: value.title, - } - } -} - /// [`TrayIcon`] builder struct and associated methods. #[derive(Default)] pub struct TrayIconBuilder { @@ -234,6 +163,8 @@ impl TrayIconBuilder { /// 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: u32, inner: tray_icon::TrayIcon, @@ -257,51 +188,6 @@ unsafe impl Sync for TrayIcon {} unsafe impl Send for TrayIcon {} impl TrayIcon { - /// Builds and adds a new tray icon to the system tray. - /// - /// ## 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>(manager: &M, mut attrs: TrayIconAttributes) -> crate::Result { - let on_menu_event = attrs.on_menu_event.take(); - let on_tray_event = attrs.on_tray_event.take(); - - let inner = tray_icon::TrayIcon::new(attrs.into())?; - let icon = Self { - id: inner.id(), - inner, - app_handle: manager.app_handle(), - }; - - icon.register(&icon.app_handle, on_menu_event, on_tray_event); - - Ok(icon) - } - - /// Builds and adds a new tray icon to the system tray with the specified Id. - /// - /// See [`TrayIcon::new`] for more info. - pub fn with_id( - app_handle: &AppHandle, - mut attrs: TrayIconAttributes, - id: u32, - ) -> crate::Result { - let on_menu_event = attrs.on_menu_event.take(); - let on_tray_event = attrs.on_tray_event.take(); - - let inner = tray_icon::TrayIcon::with_id(attrs.into(), id)?; - let icon = Self { - id, - inner, - app_handle: app_handle.clone(), - }; - - icon.register(app_handle, on_menu_event, on_tray_event); - - Ok(icon) - } - fn register( &self, app_handle: &AppHandle, From 64b48c3ab663613c503d8b68b48c5146ec1702a8 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Wed, 2 Aug 2023 18:03:57 +0300 Subject: [PATCH 050/123] add missing submenu methods --- core/tauri/src/menu/submenu.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/core/tauri/src/menu/submenu.rs b/core/tauri/src/menu/submenu.rs index 583748a3b627..fe94f9480cca 100644 --- a/core/tauri/src/menu/submenu.rs +++ b/core/tauri/src/menu/submenu.rs @@ -225,4 +225,27 @@ impl Submenu { .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)) + } } From aa88466927ed266a37dc02def405e9955ffe51f5 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Wed, 2 Aug 2023 18:08:50 +0300 Subject: [PATCH 051/123] run menu_for_nsapp on main thread --- core/tauri/src/app.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 9977474fe518..c64f1196e86b 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -644,9 +644,6 @@ macro_rules! shared_app_impl { .inner() .init_for_gtk_window(>k_window, Some(>k_box)); } - - #[cfg(target_os = "macos")] - menu_.inner().init_for_nsapp(); })?; window_menu.replace((true, menu.clone())); } @@ -654,7 +651,12 @@ macro_rules! shared_app_impl { // set it app-wide for macos #[cfg(target_os = "macos")] - menu.inner().init_for_nsapp(); + { + let menu_ = menu.clone(); + self.run_on_main_thread(move || { + menu_.inner().init_for_nsapp(); + })?; + } Ok(prev_menu) } @@ -690,9 +692,6 @@ macro_rules! shared_app_impl { if let Ok(gtk_window) = window_.gtk_window() { let _ = menu_.inner().remove_for_gtk_window(>k_window); } - - #[cfg(target_os = "macos")] - let _ = menu_.inner().remove_for_nsapp(); })?; *window.menu_lock() = None; } @@ -700,7 +699,12 @@ macro_rules! shared_app_impl { // remove app-wide for macos #[cfg(target_os = "macos")] - menu.inner().remove_for_nsapp(); + { + let menu_ = menu.clone(); + self.run_on_main_thread(move || { + menu_.inner().remove_for_nsapp(); + })?; + } } let prev_menu = current_menu.take(); From 501dda03628a7ee11ed2cab70bc30e25ef3813da Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Wed, 2 Aug 2023 18:23:56 +0300 Subject: [PATCH 052/123] make App::run handler also return a result --- core/tauri/src/app.rs | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index c64f1196e86b..40e21a8854a1 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -898,14 +898,20 @@ impl App { /// // on an actual app, remove the string argument /// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json")) /// .expect("error while building tauri application"); - /// app.run(|_app_handle, event| match event { - /// tauri::RunEvent::ExitRequested { api, .. } => { - /// api.prevent_exit(); + /// app.run(|_app_handle, event| { + /// match event { + /// tauri::RunEvent::ExitRequested { api, .. } => { + /// api.prevent_exit(); + /// } + /// _ => {} /// } - /// _ => {} + /// Ok(()) /// }); /// ``` - pub fn run, RunEvent) + 'static>(mut self, mut callback: F) { + pub fn run, RunEvent) -> crate::Result<()> + 'static>( + mut self, + mut callback: F, + ) { let app_handle = self.handle(); let manager = self.manager.clone(); self.runtime.take().unwrap().run(move |event| match event { @@ -963,7 +969,7 @@ impl App { &app_handle, event, &manager, - Option::<&mut Box, RunEvent)>>::None, + Option::<&mut Box, RunEvent) -> crate::Result<()>>>::None, ) }) } @@ -1562,7 +1568,7 @@ impl Builder { /// Runs the configured Tauri application. pub fn run(self, context: Context) -> crate::Result<()> { - self.build(context)?.run(|_, _| {}); + self.build(context)?.run(|_, _| Ok(())); Ok(()) } } @@ -1632,7 +1638,10 @@ fn setup(app: &mut App) -> crate::Result<()> { Ok(()) } -fn on_event_loop_event, RunEvent) + 'static>( +fn on_event_loop_event< + R: Runtime, + F: FnMut(&AppHandle, RunEvent) -> crate::Result<()> + 'static, +>( app_handle: &AppHandle, event: RuntimeRunEvent, manager: &WindowManager, @@ -1746,7 +1755,7 @@ fn on_event_loop_event, RunEvent) + 'static>( .on_event(app_handle, &event); if let Some(c) = callback { - c(app_handle, event); + let _ = c(app_handle, event); } } From a9a6850a98999eb5a78dc27b8bb59900ed498393 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Wed, 2 Aug 2023 18:24:30 +0300 Subject: [PATCH 053/123] fix test --- core/tauri/src/test/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/core/tauri/src/test/mod.rs b/core/tauri/src/test/mod.rs index d88af801ea8d..1a9fafa4c9b9 100644 --- a/core/tauri/src/test/mod.rs +++ b/core/tauri/src/test/mod.rs @@ -279,6 +279,7 @@ mod tests { app.run(|_app, event| { println!("{:?}", event); + Ok(()) }); } } From 0349a38d0a998ac39a7210660dc9aee64a562bf3 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Wed, 2 Aug 2023 18:39:54 +0300 Subject: [PATCH 054/123] add menu item builders --- core/tauri/src/menu/builders/check.rs | 66 ++++++ core/tauri/src/menu/builders/icon.rs | 94 ++++++++ core/tauri/src/menu/builders/menu.rs | 291 +++++++++++++++++++++++ core/tauri/src/menu/builders/mod.rs | 11 +- core/tauri/src/menu/builders/normal.rs | 52 +++++ core/tauri/src/menu/builders/submenu.rs | 297 ++++++++++++++++++++++++ 6 files changed, 810 insertions(+), 1 deletion(-) create mode 100644 core/tauri/src/menu/builders/check.rs create mode 100644 core/tauri/src/menu/builders/icon.rs create mode 100644 core/tauri/src/menu/builders/menu.rs create mode 100644 core/tauri/src/menu/builders/normal.rs create mode 100644 core/tauri/src/menu/builders/submenu.rs diff --git a/core/tauri/src/menu/builders/check.rs b/core/tauri/src/menu/builders/check.rs new file mode 100644 index 000000000000..cbef93a0976f --- /dev/null +++ b/core/tauri/src/menu/builders/check.rs @@ -0,0 +1,66 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use crate::{menu::CheckMenuItem, Manager, Runtime}; + +/// A builder type for [`CheckMenuItem`] +pub struct CheckMenuItemBuilder { + text: String, + enabled: bool, + checked: bool, + accelerator: Option, +} + +impl Default for CheckMenuItemBuilder { + fn default() -> Self { + Self::new("") + } +} + +impl CheckMenuItemBuilder { + /// Create a new menu item builder. + pub fn new>(text: S) -> Self { + Self { + text: text.as_ref().to_string(), + enabled: true, + checked: true, + accelerator: None, + } + } + + /// Set the text for this menu item. + pub fn text>(mut self, text: S) -> Self { + self.text = text.as_ref().to_string(); + 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 { + 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..74ed18e36511 --- /dev/null +++ b/core/tauri/src/menu/builders/icon.rs @@ -0,0 +1,94 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use tauri_runtime::menu::icon::NativeIcon; + +use crate::{menu::IconMenuItem, Icon, Manager, Runtime}; + +/// A builder type for [`IconMenuItem`] +pub struct IconMenuItemBuilder { + text: String, + enabled: bool, + icon: Option, + native_icon: Option, + accelerator: Option, +} + +impl Default for IconMenuItemBuilder { + fn default() -> Self { + Self::new("") + } +} + +impl IconMenuItemBuilder { + /// Create a new menu item builder. + pub fn new>(text: S) -> Self { + Self { + text: text.as_ref().to_string(), + enabled: true, + icon: None, + native_icon: None, + accelerator: None, + } + } + + /// Set the text for this menu item. + pub fn text>(mut self, text: S) -> Self { + self.text = text.as_ref().to_string(); + 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() { + IconMenuItem::new( + manager, + self.text, + self.enabled, + self.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..5eb70834965f --- /dev/null +++ b/core/tauri/src/menu/builders/menu.rs @@ -0,0 +1,291 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use crate::{menu::*, AppHandle, Icon, Manager, Runtime}; + +/// A builder type for [`Menu`] +/// +/// # Example +/// +/// ```no_run +/// # let icon1 = Icon::Rgba { +/// # rgba: Vec::new(), +/// # width: 0, +/// # height: 0, +/// # }; +/// # let icon2 = icon1.clone(); +/// 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", icon2)? +/// .build(); +/// ``` +pub struct MenuBuilder { + menu: Menu, + app_handle: AppHandle, +} + +impl MenuBuilder { + /// Create a new menu builder. + pub fn new>(manager: &M) -> Self { + Self { + menu: Menu::new(manager), + app_handle: manager.app_handle(), + } + } + + /// Add this item to the menu. + pub fn item(self, item: &dyn IsMenuItem) -> crate::Result { + self.menu.append(item)?; + Ok(self) + } + + /// Add these items to the menu. + pub fn items(self, items: &[&dyn IsMenuItem]) -> crate::Result { + self.menu.append_items(items)?; + Ok(self) + } + + /// Add a [MenuItem] to the menu. + pub fn text>(self, text: S) -> crate::Result { + self + .menu + .append(&MenuItem::new(&self.app_handle, text, true, None))?; + Ok(self) + } + + /// Add a [CheckMenuItem] to the menu. + pub fn check>(self, text: S) -> crate::Result { + self.menu.append(&CheckMenuItem::new( + &self.app_handle, + text, + true, + true, + None, + ))?; + Ok(self) + } + + /// Add an [IconMenuItem] to the menu. + pub fn icon>(self, text: S, icon: Icon) -> crate::Result { + self.menu.append(&IconMenuItem::new( + &self.app_handle, + text, + true, + Some(icon), + None, + ))?; + Ok(self) + } + + /// Add an [IconMenuItem] with a native icon to the menu. + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux**: Unsupported. + pub fn native_icon>(self, text: S, icon: NativeIcon) -> crate::Result { + self.menu.append(&IconMenuItem::with_native_icon( + &self.app_handle, + text, + true, + Some(icon), + None, + ))?; + Ok(self) + } + + /// Add Separator menu item to the menu. + pub fn separator(self) -> crate::Result { + self + .menu + .append(&PredefinedMenuItem::separator(&self.app_handle))?; + Ok(self) + } + + /// Add Copy menu item to the menu. + pub fn copy(self) -> crate::Result { + self + .menu + .append(&PredefinedMenuItem::copy(&self.app_handle, None))?; + Ok(self) + } + + /// Add Cut menu item to the menu. + pub fn cut(self) -> crate::Result { + self + .menu + .append(&PredefinedMenuItem::cut(&self.app_handle, None))?; + Ok(self) + } + + /// Add Paste menu item to the menu. + pub fn paste(self) -> crate::Result { + self + .menu + .append(&PredefinedMenuItem::paste(&self.app_handle, None))?; + Ok(self) + } + + /// Add SelectAll menu item to the menu. + pub fn select_all(self) -> crate::Result { + self + .menu + .append(&PredefinedMenuItem::select_all(&self.app_handle, None))?; + Ok(self) + } + + /// Add Undo menu item to the menu. + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux:** Unsupported. + pub fn undo(self) -> crate::Result { + self + .menu + .append(&PredefinedMenuItem::undo(&self.app_handle, None))?; + Ok(self) + } + /// Add Redo menu item to the menu. + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux:** Unsupported. + pub fn redo(self) -> crate::Result { + self + .menu + .append(&PredefinedMenuItem::redo(&self.app_handle, None))?; + Ok(self) + } + + /// Add Minimize window menu item to the menu. + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported. + pub fn minimize(self) -> crate::Result { + self + .menu + .append(&PredefinedMenuItem::minimize(&self.app_handle, None))?; + Ok(self) + } + + /// Add Maximize window menu item to the menu. + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported. + pub fn maximize(self) -> crate::Result { + self + .menu + .append(&PredefinedMenuItem::maximize(&self.app_handle, None))?; + Ok(self) + } + + /// Add Fullscreen menu item to the menu. + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux:** Unsupported. + pub fn fullscreen(self) -> crate::Result { + self + .menu + .append(&PredefinedMenuItem::fullscreen(&self.app_handle, None))?; + Ok(self) + } + + /// Add Hide window menu item to the menu. + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported. + pub fn hide(self) -> crate::Result { + self + .menu + .append(&PredefinedMenuItem::hide(&self.app_handle, None))?; + Ok(self) + } + + /// Add Hide other windows menu item to the menu. + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported. + pub fn hide_others(self) -> crate::Result { + self + .menu + .append(&PredefinedMenuItem::hide_others(&self.app_handle, None))?; + Ok(self) + } + + /// Add Show all app windows menu item to the menu. + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux:** Unsupported. + pub fn show_all(self) -> crate::Result { + self + .menu + .append(&PredefinedMenuItem::show_all(&self.app_handle, None))?; + Ok(self) + } + + /// Add Close window menu item to the menu. + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported. + pub fn close_window(self) -> crate::Result { + self + .menu + .append(&PredefinedMenuItem::close_window(&self.app_handle, None))?; + Ok(self) + } + + /// Add Quit app menu item to the menu. + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported. + pub fn quit(self) -> crate::Result { + self + .menu + .append(&PredefinedMenuItem::quit(&self.app_handle, None))?; + Ok(self) + } + + /// Add About app menu item to the menu. + pub fn about(self, metadata: Option) -> crate::Result { + self + .menu + .append(&PredefinedMenuItem::about(&self.app_handle, None, metadata))?; + Ok(self) + } + + /// Add Services menu item to the menu. + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux:** Unsupported. + pub fn services(self) -> crate::Result { + self + .menu + .append(&PredefinedMenuItem::services(&self.app_handle, None))?; + Ok(self) + } + + /// Builds this menu + pub fn build(self) -> Menu { + self.menu + } +} diff --git a/core/tauri/src/menu/builders/mod.rs b/core/tauri/src/menu/builders/mod.rs index 6eb7ec8e9009..80f57d005588 100644 --- a/core/tauri/src/menu/builders/mod.rs +++ b/core/tauri/src/menu/builders/mod.rs @@ -6,4 +6,13 @@ pub use crate::runtime::menu::builders::AboutMetadataBuilder; -// TODO(muda-migration): add builder types +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..3e7ec3229ea4 --- /dev/null +++ b/core/tauri/src/menu/builders/normal.rs @@ -0,0 +1,52 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use crate::{menu::MenuItem, Manager, Runtime}; + +/// A builder type for [`MenuItem`] +pub struct MenuItemBuilder { + text: String, + enabled: bool, + accelerator: Option, +} + +impl Default for MenuItemBuilder { + fn default() -> Self { + Self::new("") + } +} + +impl MenuItemBuilder { + /// Create a new menu item builder. + pub fn new>(text: S) -> Self { + Self { + text: text.as_ref().to_string(), + enabled: true, + accelerator: None, + } + } + + /// Set the text for this menu item. + pub fn text>(mut self, text: S) -> Self { + self.text = text.as_ref().to_string(); + 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 { + 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..464b20dc1332 --- /dev/null +++ b/core/tauri/src/menu/builders/submenu.rs @@ -0,0 +1,297 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use crate::{menu::*, AppHandle, Icon, Manager, Runtime}; + +/// A builder type for [`Menu`] +/// +/// # Example +/// +/// ```no_run +/// # let icon1 = Icon::Rgba { +/// # rgba: Vec::new(), +/// # width: 0, +/// # height: 0, +/// # }; +/// # let icon2 = icon1.clone(); +/// SubmenuBuilder::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", icon2)? +/// .build(); +/// ``` +pub struct SubmenuBuilder { + submenu: Submenu, + app_handle: AppHandle, +} + +impl SubmenuBuilder { + /// Create a new menu builder. + pub fn new, S: AsRef>(manager: &M, text: S) -> Self { + Self { + submenu: Submenu::new(manager, text, true), + app_handle: manager.app_handle(), + } + } + + /// Set the enabled state for submenu. + pub fn enabled(self, enabled: bool) -> crate::Result { + self.submenu.set_enabled(enabled)?; + Ok(self) + } + + /// Add this item to the submenu. + pub fn item(self, item: &dyn IsMenuItem) -> crate::Result { + self.submenu.append(item)?; + Ok(self) + } + + /// Add these items to the submenu. + pub fn items(self, items: &[&dyn IsMenuItem]) -> crate::Result { + self.submenu.append_items(items)?; + Ok(self) + } + + /// Add a [MenuItem] to the submenu. + pub fn text>(self, text: S) -> crate::Result { + self + .submenu + .append(&MenuItem::new(&self.app_handle, text, true, None))?; + Ok(self) + } + + /// Add a [CheckMenuItem] to the submenu. + pub fn check>(self, text: S) -> crate::Result { + self.submenu.append(&CheckMenuItem::new( + &self.app_handle, + text, + true, + true, + None, + ))?; + Ok(self) + } + + /// Add an [IconMenuItem] to the submenu. + pub fn icon>(self, text: S, icon: Icon) -> crate::Result { + self.submenu.append(&IconMenuItem::new( + &self.app_handle, + text, + true, + Some(icon), + None, + ))?; + Ok(self) + } + + /// Add an [IconMenuItem] with a native icon to the submenu. + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux**: Unsupported. + pub fn native_icon>(self, text: S, icon: NativeIcon) -> crate::Result { + self.submenu.append(&IconMenuItem::with_native_icon( + &self.app_handle, + text, + true, + Some(icon), + None, + ))?; + Ok(self) + } + + /// Add Separator menu item to the submenu. + pub fn separator(self) -> crate::Result { + self + .submenu + .append(&PredefinedMenuItem::separator(&self.app_handle))?; + Ok(self) + } + + /// Add Copy menu item to the submenu. + pub fn copy(self) -> crate::Result { + self + .submenu + .append(&PredefinedMenuItem::copy(&self.app_handle, None))?; + Ok(self) + } + + /// Add Cut menu item to the submenu. + pub fn cut(self) -> crate::Result { + self + .submenu + .append(&PredefinedMenuItem::cut(&self.app_handle, None))?; + Ok(self) + } + + /// Add Paste menu item to the submenu. + pub fn paste(self) -> crate::Result { + self + .submenu + .append(&PredefinedMenuItem::paste(&self.app_handle, None))?; + Ok(self) + } + + /// Add SelectAll menu item to the submenu. + pub fn select_all(self) -> crate::Result { + self + .submenu + .append(&PredefinedMenuItem::select_all(&self.app_handle, None))?; + Ok(self) + } + + /// Add Undo menu item to the submenu. + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux:** Unsupported. + pub fn undo(self) -> crate::Result { + self + .submenu + .append(&PredefinedMenuItem::undo(&self.app_handle, None))?; + Ok(self) + } + /// Add Redo menu item to the submenu. + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux:** Unsupported. + pub fn redo(self) -> crate::Result { + self + .submenu + .append(&PredefinedMenuItem::redo(&self.app_handle, None))?; + Ok(self) + } + + /// Add Minimize window menu item to the submenu. + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported. + pub fn minimize(self) -> crate::Result { + self + .submenu + .append(&PredefinedMenuItem::minimize(&self.app_handle, None))?; + Ok(self) + } + + /// Add Maximize window menu item to the submenu. + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported. + pub fn maximize(self) -> crate::Result { + self + .submenu + .append(&PredefinedMenuItem::maximize(&self.app_handle, None))?; + Ok(self) + } + + /// Add Fullscreen menu item to the submenu. + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux:** Unsupported. + pub fn fullscreen(self) -> crate::Result { + self + .submenu + .append(&PredefinedMenuItem::fullscreen(&self.app_handle, None))?; + Ok(self) + } + + /// Add Hide window menu item to the submenu. + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported. + pub fn hide(self) -> crate::Result { + self + .submenu + .append(&PredefinedMenuItem::hide(&self.app_handle, None))?; + Ok(self) + } + + /// Add Hide other windows menu item to the submenu. + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported. + pub fn hide_others(self) -> crate::Result { + self + .submenu + .append(&PredefinedMenuItem::hide_others(&self.app_handle, None))?; + Ok(self) + } + + /// Add Show all app windows menu item to the submenu. + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux:** Unsupported. + pub fn show_all(self) -> crate::Result { + self + .submenu + .append(&PredefinedMenuItem::show_all(&self.app_handle, None))?; + Ok(self) + } + + /// Add Close window menu item to the submenu. + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported. + pub fn close_window(self) -> crate::Result { + self + .submenu + .append(&PredefinedMenuItem::close_window(&self.app_handle, None))?; + Ok(self) + } + + /// Add Quit app menu item to the submenu. + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported. + pub fn quit(self) -> crate::Result { + self + .submenu + .append(&PredefinedMenuItem::quit(&self.app_handle, None))?; + Ok(self) + } + + /// Add About app menu item to the submenu. + pub fn about(self, metadata: Option) -> crate::Result { + self + .submenu + .append(&PredefinedMenuItem::about(&self.app_handle, None, metadata))?; + Ok(self) + } + + /// Add Services menu item to the submenu. + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux:** Unsupported. + pub fn services(self) -> crate::Result { + self + .submenu + .append(&PredefinedMenuItem::services(&self.app_handle, None))?; + Ok(self) + } + + /// Builds this menu + pub fn build(self) -> Submenu { + self.submenu + } +} From 40df0d0b5d835c8b73c7d959658273e43fdcb3e1 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Wed, 2 Aug 2023 18:43:34 +0300 Subject: [PATCH 055/123] fix tray icon builder docs --- core/tauri/src/tray.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/core/tauri/src/tray.rs b/core/tauri/src/tray.rs index e3f6feeba837..62ca821cf193 100644 --- a/core/tauri/src/tray.rs +++ b/core/tauri/src/tray.rs @@ -22,9 +22,12 @@ pub struct TrayIconBuilder { } impl TrayIconBuilder { - /// Creates a new [`TrayIconBuilder`] with default [`TrayIconAttributes`]. + /// Creates a new tray icon builder. /// - /// See [`TrayIcon::new`] for more info. + /// ## 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(), From dc1b949cb12a19b586b6f8f56e53418b253bbd33 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Wed, 2 Aug 2023 18:53:27 +0300 Subject: [PATCH 056/123] pin time crate in CI --- .github/workflows/test-core.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/test-core.yml b/.github/workflows/test-core.yml index d9219ff0f2b8..64d6701b24cd 100644 --- a/.github/workflows/test-core.yml +++ b/.github/workflows/test-core.yml @@ -98,6 +98,11 @@ jobs: workspaces: core -> ../target save-if: ${{ matrix.features.key == 'all' }} + - name: Downgrade crates with MSRV conflict + # The --precise flag can only be used once per invocation. + run: | + cargo update -p time --precise 0.3.23 + - name: test uses: actions-rs/cargo@v1 with: From f4956f35a39443cb6c6d5f76e3ee55f17671d510 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Wed, 2 Aug 2023 19:02:12 +0300 Subject: [PATCH 057/123] fix test-core workflow --- .github/workflows/test-core.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-core.yml b/.github/workflows/test-core.yml index 64d6701b24cd..824cc5045e10 100644 --- a/.github/workflows/test-core.yml +++ b/.github/workflows/test-core.yml @@ -98,7 +98,7 @@ jobs: workspaces: core -> ../target save-if: ${{ matrix.features.key == 'all' }} - - name: Downgrade crates with MSRV conflict + - name: Downgrade crates with MSRV conflict # The --precise flag can only be used once per invocation. run: | cargo update -p time --precise 0.3.23 From 3fc7a6a5762d18a7a46667984258c5752b9c1ebd Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Wed, 2 Aug 2023 19:13:08 +0300 Subject: [PATCH 058/123] lints and doctests --- .github/workflows/test-core.yml | 2 +- core/tauri-build/src/mobile.rs | 10 ++--- core/tauri/src/app.rs | 19 +++++---- core/tauri/src/lib.rs | 32 ++++++++-------- core/tauri/src/manager.rs | 2 +- core/tauri/src/menu/builders/menu.rs | 49 ++++++++++++++---------- core/tauri/src/menu/builders/submenu.rs | 51 +++++++++++++++---------- core/tauri/src/window.rs | 8 +++- 8 files changed, 97 insertions(+), 76 deletions(-) diff --git a/.github/workflows/test-core.yml b/.github/workflows/test-core.yml index 824cc5045e10..c5fd6e267757 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,linux-protocol-headers,isolation,custom-protocol,test, + args: --features compression,wry,linux-protocol-headers,isolation,custom-protocol,test key: all } 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/src/app.rs b/core/tauri/src/app.rs index 40e21a8854a1..b9eaf73e58f5 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -849,7 +849,7 @@ impl App { /// .expect("error while building tauri application"); /// #[cfg(target_os = "macos")] /// app.set_activation_policy(tauri::ActivationPolicy::Accessory); - /// app.run(|_app_handle, _event| {}); + /// app.run(|_app_handle, _event| Ok(())); /// ``` #[cfg(target_os = "macos")] #[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))] @@ -878,7 +878,7 @@ impl App { /// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json")) /// .expect("error while building tauri application"); /// app.set_device_event_filter(tauri::DeviceEventFilter::Always); - /// app.run(|_app_handle, _event| {}); + /// app.run(|_app_handle, _event| Ok(())); /// ``` /// /// [`tao`]: https://crates.io/crates/tao @@ -1322,14 +1322,17 @@ impl Builder { /// # Examples /// ``` /// tauri::Builder::default() - /// .on_window_event(|event| match event.event() { - /// tauri::WindowEvent::Focused(focused) => { - /// // hide window whenever it loses focus - /// if !focused { - /// event.window().hide().unwrap(); + /// .on_window_event(|event| { + /// match event.event() { + /// tauri::WindowEvent::Focused(focused) => { + /// // hide window whenever it loses focus + /// if !focused { + /// event.window().hide().unwrap(); + /// } /// } + /// _ => {} /// } - /// _ => {} + /// Ok(()) /// }); /// ``` #[must_use] diff --git a/core/tauri/src/lib.rs b/core/tauri/src/lib.rs index 86e266a431c4..9eae231aa77e 100644 --- a/core/tauri/src/lib.rs +++ b/core/tauri/src/lib.rs @@ -892,6 +892,22 @@ mod tests { } } +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) + }}; +} + +pub(crate) use run_main_thread; + #[cfg(test)] mod test_utils { use proptest::prelude::*; @@ -920,19 +936,3 @@ mod test_utils { } } } - -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) - }}; -} - -pub(crate) use run_main_thread; diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index 53c285acb214..4af20d93e42a 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -1202,7 +1202,7 @@ impl WindowManager { Menu { id: menu.id(), inner: menu.clone(), - app_handle: app_handle.clone(), + app_handle, }, ); } diff --git a/core/tauri/src/menu/builders/menu.rs b/core/tauri/src/menu/builders/menu.rs index 5eb70834965f..662b46694c61 100644 --- a/core/tauri/src/menu/builders/menu.rs +++ b/core/tauri/src/menu/builders/menu.rs @@ -9,27 +9,34 @@ use crate::{menu::*, AppHandle, Icon, Manager, Runtime}; /// # Example /// /// ```no_run -/// # let icon1 = Icon::Rgba { -/// # rgba: Vec::new(), -/// # width: 0, -/// # height: 0, -/// # }; -/// # let icon2 = icon1.clone(); -/// 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", icon2)? -/// .build(); +/// # use tauri::menu::{*, builders::*}; +/// 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 = 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", icon2)? +/// .build(); +/// app.set_menu(menu); +/// Ok(()) +/// }); /// ``` pub struct MenuBuilder { menu: Menu, diff --git a/core/tauri/src/menu/builders/submenu.rs b/core/tauri/src/menu/builders/submenu.rs index 464b20dc1332..12423f529303 100644 --- a/core/tauri/src/menu/builders/submenu.rs +++ b/core/tauri/src/menu/builders/submenu.rs @@ -9,27 +9,36 @@ use crate::{menu::*, AppHandle, Icon, Manager, Runtime}; /// # Example /// /// ```no_run -/// # let icon1 = Icon::Rgba { -/// # rgba: Vec::new(), -/// # width: 0, -/// # height: 0, -/// # }; -/// # let icon2 = icon1.clone(); -/// SubmenuBuilder::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", icon2)? -/// .build(); +/// # use tauri::menu::{*, builders::*}; +/// 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", icon2)? +/// .build(); +/// menu.append(&submenu); +/// app.set_menu(menu); +/// Ok(()) +/// }); /// ``` pub struct SubmenuBuilder { submenu: Submenu, diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index c24239274c47..4cea6d46e773 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -334,10 +334,12 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { /// ])?; /// let window = tauri::WindowBuilder::new(app, "editor", tauri::WindowUrl::default()) /// .menu(menu) - /// on_menu_event(move |window, event| { + /// .on_menu_event(move |window, event| { /// if event.id == save_menu_item.id() { /// // save menu item /// } + /// + /// Ok(()) /// }) /// .build() /// .unwrap(); @@ -1199,6 +1201,8 @@ impl Window { /// if event.id == save_menu_item.id() { /// // save menu item /// } + /// + /// Ok(()) /// }); /// /// Ok(()) @@ -1272,7 +1276,7 @@ impl Window { } })?; - self.menu_lock().replace((false, menu.clone())); + self.menu_lock().replace((false, menu)); Ok(prev_menu) } From 1e33d62aafec793785ffedd2f9d344fd36cc5af9 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 2 Aug 2023 13:43:37 -0300 Subject: [PATCH 059/123] update tray-icon [skip ci] --- examples/api/src-tauri/Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/api/src-tauri/Cargo.lock b/examples/api/src-tauri/Cargo.lock index 27095bdd8d49..8bb8ffb63fdf 100644 --- a/examples/api/src-tauri/Cargo.lock +++ b/examples/api/src-tauri/Cargo.lock @@ -3908,9 +3908,9 @@ dependencies = [ [[package]] name = "tray-icon" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a903d0c8c8c9caa5f00f38e9465349e89ed3f72506b07994aa4393d7caf3e10" +checksum = "abc6ebb68e3591924520845d34aacb1b2a7951f439cabcbc2447a16b70eb5bdd" dependencies = [ "cocoa 0.25.0", "core-graphics 0.23.1", From 37bd9e62d6cde2a47c29fd0631143b4cc656b3ee Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 2 Aug 2023 13:43:51 -0300 Subject: [PATCH 060/123] fix api example build [skip ci] --- examples/api/src-tauri/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/api/src-tauri/src/lib.rs b/examples/api/src-tauri/src/lib.rs index a3a6d52e1640..e40e069905e0 100644 --- a/examples/api/src-tauri/src/lib.rs +++ b/examples/api/src-tauri/src/lib.rs @@ -134,6 +134,7 @@ pub fn run_app) + Send + 'static>( // This allow us to catch tray icon events when there is no window api.prevent_exit(); } + Ok(()) }) } From 7076ce5e579946fd41403260416bb99d94d4500a Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Wed, 2 Aug 2023 21:19:28 +0300 Subject: [PATCH 061/123] expose `popup` on `ContextMenu` trait --- core/tauri/src/menu/menu.rs | 33 ++++++++++++++++---------------- core/tauri/src/menu/mod.rs | 16 ++++++++-------- core/tauri/src/menu/submenu.rs | 35 ++++++++++++++++------------------ 3 files changed, 40 insertions(+), 44 deletions(-) diff --git a/core/tauri/src/menu/menu.rs b/core/tauri/src/menu/menu.rs index e9ae8428c026..dd9915dc22dd 100644 --- a/core/tauri/src/menu/menu.rs +++ b/core/tauri/src/menu/menu.rs @@ -31,27 +31,19 @@ impl Clone for Menu { } } -impl super::ContextMenu for Menu {} -impl super::sealed::ContextMenuBase for Menu { - fn inner(&self) -> &dyn muda::ContextMenu { - &self.inner - } - - fn inner_owned(&self) -> Box { - Box::new(self.clone().inner) - } - - fn popup( +impl super::ContextMenu for Menu { + fn popup>( &self, window: crate::Window, - position: Option, + position: Option