diff --git a/.changes/wry-arc.md b/.changes/wry-arc.md new file mode 100644 index 000000000..bbdd683e2 --- /dev/null +++ b/.changes/wry-arc.md @@ -0,0 +1,5 @@ +--- +"tauri-runtime-wry": patch:bug +--- + +Use `Arc` instead of `Rc` on global shortcut and tray types to prevent crashes on macOS. diff --git a/core/tauri-runtime-wry/src/global_shortcut.rs b/core/tauri-runtime-wry/src/global_shortcut.rs index 88cc10e23..46910f59f 100644 --- a/core/tauri-runtime-wry/src/global_shortcut.rs +++ b/core/tauri-runtime-wry/src/global_shortcut.rs @@ -8,7 +8,6 @@ use std::{ collections::HashMap, error::Error as StdError, fmt, - rc::Rc, sync::{ mpsc::{channel, Sender}, Arc, Mutex, @@ -18,14 +17,34 @@ use std::{ use crate::{getter, Context, Message}; use tauri_runtime::{Error, GlobalShortcutManager, Result, UserEvent}; -#[cfg(desktop)] + pub use wry::application::{ accelerator::{Accelerator, AcceleratorId, AcceleratorParseError}, - global_shortcut::{GlobalShortcut, ShortcutManager as WryShortcutManager}, + event_loop::EventLoopWindowTarget, + global_shortcut::GlobalShortcut, }; pub type GlobalShortcutListeners = Arc>>>; +#[derive(Debug)] +pub struct WryShortcutManager(pub wry::application::global_shortcut::ShortcutManager); + +// SAFETY: we ensure this type is only used on the main thread. +#[allow(clippy::non_send_fields_in_send_ty)] +unsafe impl Send for WryShortcutManager {} + +// SAFETY: we ensure this type is only used on the main thread. +#[allow(clippy::non_send_fields_in_send_ty)] +unsafe impl Sync for WryShortcutManager {} + +impl WryShortcutManager { + pub fn new(event_loop: &EventLoopWindowTarget) -> Self { + Self(wry::application::global_shortcut::ShortcutManager::new( + event_loop, + )) + } +} + #[derive(Debug, Clone)] pub enum GlobalShortcutMessage { IsRegistered(Accelerator, Sender), @@ -139,7 +158,7 @@ impl GlobalShortcutManager for GlobalShortcutManagerHandle { pub fn handle_global_shortcut_message( message: GlobalShortcutMessage, - global_shortcut_manager: &Rc>, + global_shortcut_manager: &Mutex, ) { match message { GlobalShortcutMessage::IsRegistered(accelerator, tx) => tx @@ -147,6 +166,7 @@ pub fn handle_global_shortcut_message( global_shortcut_manager .lock() .unwrap() + .0 .is_registered(&accelerator), ) .unwrap(), @@ -155,6 +175,7 @@ pub fn handle_global_shortcut_message( global_shortcut_manager .lock() .unwrap() + .0 .register(accelerator) .map(GlobalShortcutWrapper) .map_err(|e| Error::GlobalShortcut(Box::new(e))), @@ -165,6 +186,7 @@ pub fn handle_global_shortcut_message( global_shortcut_manager .lock() .unwrap() + .0 .unregister(shortcut.0) .map_err(|e| Error::GlobalShortcut(Box::new(e))), ) @@ -174,6 +196,7 @@ pub fn handle_global_shortcut_message( global_shortcut_manager .lock() .unwrap() + .0 .unregister_all() .map_err(|e| Error::GlobalShortcut(Box::new(e))), ) diff --git a/core/tauri-runtime-wry/src/lib.rs b/core/tauri-runtime-wry/src/lib.rs index 91b0f02bf..8b234a989 100644 --- a/core/tauri-runtime-wry/src/lib.rs +++ b/core/tauri-runtime-wry/src/lib.rs @@ -211,35 +211,37 @@ impl Context { } } -impl Context { - fn create_webview(&self, pending: PendingWindow>) -> Result>> { - let label = pending.label.clone(); - let menu_ids = pending.menu_ids.clone(); - let js_event_listeners = pending.js_event_listeners.clone(); - let context = self.clone(); - let window_id = rand::random(); +fn context_create_webview( + context: Context, + pending: PendingWindow>, +) -> Result>> { + let label = pending.label.clone(); + let menu_ids = pending.menu_ids.clone(); + let js_event_listeners = pending.js_event_listeners.clone(); - send_user_message( - self, - Message::CreateWebview( - window_id, - Box::new(move |event_loop, web_context| { - create_webview(window_id, event_loop, web_context, context, pending) - }), - ), - )?; + let window_id = rand::random(); - let dispatcher = WryDispatcher { + let context_ = context.clone(); + send_user_message( + &context, + Message::CreateWebview( window_id, - context: self.clone(), - }; - Ok(DetachedWindow { - label, - dispatcher, - menu_ids, - js_event_listeners, - }) - } + Box::new(move |event_loop, web_context| { + create_webview(window_id, event_loop, web_context, context_, pending) + }), + ), + )?; + + let dispatcher = WryDispatcher { + window_id, + context: context.clone(), + }; + Ok(DetachedWindow { + label, + dispatcher, + menu_ids, + js_event_listeners, + }) } #[cfg(feature = "tracing")] @@ -281,7 +283,7 @@ pub struct DispatcherMainThreadContext { pub window_target: EventLoopWindowTarget>, pub web_context: WebContextStore, #[cfg(all(desktop, feature = "global-shortcut"))] - pub global_shortcut_manager: Rc>, + pub global_shortcut_manager: Arc>, // changing this to an Rc will cause frequent app crashes. pub windows: Arc, #[cfg(all(desktop, feature = "system-tray"))] @@ -1452,7 +1454,7 @@ impl Dispatch for WryDispatcher { &mut self, pending: PendingWindow, ) -> Result> { - self.context.create_webview(pending) + context_create_webview(self.context.clone(), pending) } fn set_resizable(&self, resizable: bool) -> Result<()> { @@ -1927,7 +1929,7 @@ impl RuntimeHandle for WryHandle { &self, pending: PendingWindow, ) -> Result> { - self.context.create_webview(pending) + context_create_webview(self.context.clone(), pending) } fn run_on_main_thread(&self, f: F) -> Result<()> { @@ -1980,7 +1982,7 @@ impl Wry { let web_context = WebContextStore::default(); #[cfg(all(desktop, feature = "global-shortcut"))] - let global_shortcut_manager = Rc::new(Mutex::new(WryShortcutManager::new(&event_loop))); + let global_shortcut_manager = Arc::new(Mutex::new(WryShortcutManager::new(&event_loop))); let windows = Arc::new(WindowsStore(RefCell::new(BTreeMap::default()))); @@ -2131,7 +2133,8 @@ impl Runtime for Wry { let id = system_tray.id; let mut listeners = Vec::new(); if let Some(l) = system_tray.on_event.take() { - listeners.push(Rc::new(l)); + #[allow(clippy::arc_with_non_send_sync)] + listeners.push(Arc::new(l)); } let (tray, items) = create_tray(WryTrayId(id), system_tray, &self.event_loop)?; self @@ -2144,9 +2147,9 @@ impl Runtime for Wry { .insert( id, TrayContext { - tray: Rc::new(RefCell::new(Some(tray))), - listeners: Rc::new(RefCell::new(listeners)), - items: Rc::new(RefCell::new(items)), + tray: Arc::new(TrayCell(RefCell::new(Some(tray)))), + listeners: Arc::new(TrayListenersCell(RefCell::new(listeners))), + items: Arc::new(TrayItemsCell(RefCell::new(items))), }, ); @@ -2280,14 +2283,14 @@ impl Runtime for Wry { fn run) + 'static>(self, mut callback: F) { let windows = self.context.main_thread.windows.clone(); let webview_id_map = self.context.webview_id_map.clone(); - let web_context = self.context.main_thread.web_context; + let web_context = self.context.main_thread.web_context.clone(); let mut plugins = self.plugins; #[cfg(feature = "tracing")] let active_tracing_spans = self.context.main_thread.active_tracing_spans.clone(); #[cfg(all(desktop, feature = "system-tray"))] - let system_tray_manager = self.context.main_thread.system_tray_manager; + let system_tray_manager = self.context.main_thread.system_tray_manager.clone(); #[cfg(all(desktop, feature = "global-shortcut"))] let global_shortcut_manager = self.context.main_thread.global_shortcut_manager.clone(); @@ -2350,7 +2353,7 @@ pub struct EventLoopIterationContext<'a, T: UserEvent> { pub webview_id_map: WebviewIdStore, pub windows: Arc, #[cfg(all(desktop, feature = "global-shortcut"))] - pub global_shortcut_manager: Rc>, + pub global_shortcut_manager: Arc>, #[cfg(all(desktop, feature = "global-shortcut"))] pub global_shortcut_manager_handle: &'a GlobalShortcutManagerHandle, #[cfg(all(desktop, feature = "system-tray"))] @@ -2363,7 +2366,7 @@ struct UserMessageContext { windows: Arc, webview_id_map: WebviewIdStore, #[cfg(all(desktop, feature = "global-shortcut"))] - global_shortcut_manager: Rc>, + global_shortcut_manager: Arc>, #[cfg(all(desktop, feature = "system-tray"))] system_tray_manager: SystemTrayManager, } @@ -2703,16 +2706,17 @@ fn handle_user_message( if let TrayMessage::Create(mut tray, tx) = tray_message { let mut listeners = Vec::new(); if let Some(l) = tray.on_event.take() { - listeners.push(Rc::new(l)); + #[allow(clippy::arc_with_non_send_sync)] + listeners.push(Arc::new(l)); } match create_tray(WryTrayId(tray_id), tray, event_loop) { Ok((tray, items)) => { trays.insert( tray_id, TrayContext { - tray: Rc::new(RefCell::new(Some(tray))), - listeners: Rc::new(RefCell::new(listeners)), - items: Rc::new(RefCell::new(items)), + tray: Arc::new(TrayCell(RefCell::new(Some(tray)))), + listeners: Arc::new(TrayListenersCell(RefCell::new(listeners))), + items: Arc::new(TrayItemsCell(RefCell::new(items))), }, ); @@ -2726,7 +2730,7 @@ fn handle_user_message( } 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().borrow_mut(); + let mut tray = tray_context.items.as_ref().0.borrow_mut(); let item = tray.get_mut(&menu_id).expect("menu item not found"); match update { MenuUpdate::SetEnabled(enabled) => item.set_enabled(enabled), @@ -2739,14 +2743,14 @@ fn handle_user_message( } } TrayMessage::UpdateMenu(menu) => { - if let Some(tray) = &mut *tray_context.tray.borrow_mut() { + if let Some(tray) = &mut *tray_context.tray.0.borrow_mut() { let mut items = HashMap::new(); tray.set_menu(&to_wry_context_menu(&mut items, menu)); - *tray_context.items.borrow_mut() = items; + *tray_context.items.0.borrow_mut() = items; } } TrayMessage::UpdateIcon(icon) => { - if let Some(tray) = &mut *tray_context.tray.borrow_mut() { + if let Some(tray) = &mut *tray_context.tray.0.borrow_mut() { if let Ok(icon) = TrayIcon::try_from(icon) { tray.set_icon(icon.0); } @@ -2754,18 +2758,18 @@ fn handle_user_message( } #[cfg(target_os = "macos")] TrayMessage::UpdateIconAsTemplate(is_template) => { - if let Some(tray) = &mut *tray_context.tray.borrow_mut() { + if let Some(tray) = &mut *tray_context.tray.0.borrow_mut() { tray.set_icon_as_template(is_template); } } #[cfg(target_os = "macos")] TrayMessage::UpdateTitle(title) => { - if let Some(tray) = &mut *tray_context.tray.borrow_mut() { + if let Some(tray) = &mut *tray_context.tray.0.borrow_mut() { tray.set_title(&title); } } TrayMessage::UpdateTooltip(tooltip) => { - if let Some(tray) = &mut *tray_context.tray.borrow_mut() { + if let Some(tray) = &mut *tray_context.tray.0.borrow_mut() { tray.set_tooltip(&tooltip); } } @@ -2773,9 +2777,9 @@ fn handle_user_message( // already handled } TrayMessage::Destroy(tx) => { - *tray_context.tray.borrow_mut() = None; - tray_context.listeners.borrow_mut().clear(); - tray_context.items.borrow_mut().clear(); + *tray_context.tray.0.borrow_mut() = None; + tray_context.listeners.0.borrow_mut().clear(); + tray_context.items.0.borrow_mut().clear(); tx.send(Ok(())).unwrap(); } } @@ -2905,11 +2909,11 @@ fn handle_event_loop( let (mut listeners, mut tray_id) = (None, 0); for (id, tray_context) in trays_iter { let has_menu = { - let items = tray_context.items.borrow(); + let items = tray_context.items.0.borrow(); items.contains_key(&menu_id.0) }; if has_menu { - listeners.replace(tray_context.listeners.borrow().clone()); + listeners.replace(tray_context.listeners.0.borrow().clone()); tray_id = *id; break; } @@ -2948,7 +2952,7 @@ fn handle_event_loop( }; let trays = system_tray_manager.trays.lock().unwrap(); if let Some(tray_context) = trays.get(&id.0) { - let listeners = tray_context.listeners.borrow(); + let listeners = tray_context.listeners.0.borrow(); let iter = listeners.iter(); for handler in iter { handler(&event); diff --git a/core/tauri-runtime-wry/src/system_tray.rs b/core/tauri-runtime-wry/src/system_tray.rs index 08aba2e0c..1f91352b3 100644 --- a/core/tauri-runtime-wry/src/system_tray.rs +++ b/core/tauri-runtime-wry/src/system_tray.rs @@ -32,7 +32,6 @@ use std::{ cell::RefCell, collections::HashMap, fmt, - rc::Rc, sync::{Arc, Mutex}, }; @@ -40,12 +39,45 @@ pub type GlobalSystemTrayEventHandler = Box>>>; pub type SystemTrayEventHandler = Box; -pub type SystemTrayEventListeners = Rc>>>; -pub type SystemTrayItems = Rc>>; +pub type SystemTrayEventListeners = Arc; +pub type SystemTrayItems = Arc; + +#[derive(Debug, Default)] +pub struct TrayItemsCell(pub RefCell>); + +// SAFETY: we ensure this type is only used on the main thread. +#[allow(clippy::non_send_fields_in_send_ty)] +unsafe impl Send for TrayItemsCell {} + +// SAFETY: we ensure this type is only used on the main thread. +#[allow(clippy::non_send_fields_in_send_ty)] +unsafe impl Sync for TrayItemsCell {} + +#[derive(Default)] +pub struct TrayCell(pub RefCell>); + +// SAFETY: we ensure this type is only used on the main thread. +#[allow(clippy::non_send_fields_in_send_ty)] +unsafe impl Send for TrayCell {} + +// SAFETY: we ensure this type is only used on the main thread. +#[allow(clippy::non_send_fields_in_send_ty)] +unsafe impl Sync for TrayCell {} + +#[derive(Default)] +pub struct TrayListenersCell(pub RefCell>>); + +// SAFETY: we ensure this type is only used on the main thread. +#[allow(clippy::non_send_fields_in_send_ty)] +unsafe impl Send for TrayListenersCell {} + +// SAFETY: we ensure this type is only used on the main thread. +#[allow(clippy::non_send_fields_in_send_ty)] +unsafe impl Sync for TrayListenersCell {} #[derive(Clone, Default)] pub struct TrayContext { - pub tray: Rc>>, + pub tray: Arc, pub listeners: SystemTrayEventListeners, pub items: SystemTrayItems, }