diff --git a/Cargo.toml b/Cargo.toml index 4352b955a..e1622da04 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,11 +30,8 @@ targets = [ members = [ "tao-macros" ] [features] -default = [ "macos-open-files" ] dox = [ "gtk/dox"] tray = [ "libappindicator", "dirs-next" ] -macos-open-urls = [] -macos-open-files = [] [build-dependencies] cc = "1" diff --git a/src/platform/macos.rs b/src/platform/macos.rs index 04c52da94..bdae9bc82 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -171,6 +171,21 @@ impl From for NSApplicationActivationPolicy { } } +/// Kind of resource the application opens. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum OpenResourceKind { + /// The application expects the user to open an URL with it. + Url, + /// The application expects the user to open files with it. + File, +} + +impl Default for OpenResourceKind { + fn default() -> Self { + Self::File + } +} + pub trait CustomMenuItemExtMacOS { fn set_native_image(&mut self, native_image: NativeImage); } @@ -511,7 +526,11 @@ pub trait EventLoopExtMacOS { /// [`run`](crate::event_loop::EventLoop::run) or /// [`run_return`](crate::platform::run_return::EventLoopExtRunReturn::run_return) fn set_activate_ignoring_other_apps(&mut self, ignore: bool); + + /// Sets which kind of resource the application opens. + fn set_application_open_resource_kind(&mut self, kind: OpenResourceKind); } + impl EventLoopExtMacOS for EventLoop { #[inline] fn set_activation_policy(&mut self, activation_policy: ActivationPolicy) { @@ -533,6 +552,11 @@ impl EventLoopExtMacOS for EventLoop { get_aux_state_mut(&**self.event_loop.delegate).activate_ignoring_other_apps = ignore; } } + + #[inline] + fn set_application_open_resource_kind(&mut self, kind: OpenResourceKind) { + self.event_loop.open_resource_kind = kind; + } } /// Additional methods on `MonitorHandle` that are specific to MacOS. diff --git a/src/platform_impl/macos/app_delegate.rs b/src/platform_impl/macos/app_delegate.rs index 87ea469c9..8f22679e8 100644 --- a/src/platform_impl/macos/app_delegate.rs +++ b/src/platform_impl/macos/app_delegate.rs @@ -2,29 +2,26 @@ // Copyright 2021-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 -use crate::{platform::macos::ActivationPolicy, platform_impl::platform::app_state::AppState}; +use crate::{ + platform::macos::{ActivationPolicy, OpenResourceKind}, + platform_impl::platform::app_state::AppState, +}; use cocoa::base::id; use cocoa::foundation::NSString; use objc::{ declare::ClassDecl, - runtime::{Class, Object, Sel}, + runtime::{Class, Object, Sel, BOOL, YES}, }; use std::{ cell::{RefCell, RefMut}, os::raw::c_void, }; -#[cfg(any(feature = "macos-open-urls", feature = "macos-open-files"))] use cocoa::foundation::NSArray; -#[cfg(all(feature = "macos-open-urls", not(feature = "macos-open-files")))] use cocoa::foundation::NSURL; -#[cfg(any(feature = "macos-open-urls", feature = "macos-open-files"))] use std::ffi::CStr; -#[cfg(all(feature = "macos-open-files", feature = "macos-open-urls"))] -compile_error!("`macos-open-files` and `macos-open-urls` feature flags can't be used at the same time, please choose only one of them."); - static AUX_DELEGATE_STATE_NAME: &str = "auxState"; pub struct AuxDelegateState { @@ -42,10 +39,39 @@ pub struct AppDelegateClass(pub *const Class); unsafe impl Send for AppDelegateClass {} unsafe impl Sync for AppDelegateClass {} +pub unsafe fn app_delegate_class( + classname: &str, + open_resource_kind: OpenResourceKind, +) -> AppDelegateClass { + let superclass = &*APP_DELEGATE_CLASS.0; + let mut decl = ClassDecl::new(classname, superclass).unwrap(); + + match open_resource_kind { + OpenResourceKind::Url => { + decl.add_method( + sel!(application:openURLs:), + application_open_urls as extern "C" fn(&Object, Sel, id, id), + ); + } + OpenResourceKind::File => { + decl.add_method( + sel!(application:openFile:), + application_open_file as extern "C" fn(&Object, Sel, id, id) -> cocoa::base::BOOL, + ); + decl.add_method( + sel!(application:openFiles:), + application_open_files as extern "C" fn(&Object, Sel, id, id), + ); + } + } + + AppDelegateClass(decl.register()) +} + lazy_static! { - pub static ref APP_DELEGATE_CLASS: AppDelegateClass = unsafe { + static ref APP_DELEGATE_CLASS: AppDelegateClass = unsafe { let superclass = class!(NSResponder); - let mut decl = ClassDecl::new("TaoAppDelegate", superclass).unwrap(); + let mut decl = ClassDecl::new("TaoAppDelegateParent", superclass).unwrap(); decl.add_class_method(sel!(new), new as extern "C" fn(&Class, Sel) -> id); decl.add_method(sel!(dealloc), dealloc as extern "C" fn(&Object, Sel)); @@ -58,21 +84,6 @@ lazy_static! { sel!(applicationWillTerminate:), application_will_terminate as extern "C" fn(&Object, Sel, id), ); - #[cfg(all(feature = "macos-open-urls", not(feature = "macos-open-files")))] - decl.add_method( - sel!(application:openURLs:), - application_open_urls as extern "C" fn(&Object, Sel, id, id), - ); - #[cfg(all(feature = "macos-open-files", not(feature = "macos-open-urls")))] - decl.add_method( - sel!(application:openFile:), - application_open_file as extern "C" fn(&Object, Sel, id, id) -> cocoa::base::BOOL, - ); - #[cfg(all(feature = "macos-open-files", not(feature = "macos-open-urls")))] - decl.add_method( - sel!(application:openFiles:), - application_open_files as extern "C" fn(&Object, Sel, id, id), - ); decl.add_ivar::<*mut c_void>(AUX_DELEGATE_STATE_NAME); AppDelegateClass(decl.register()) @@ -123,7 +134,6 @@ extern "C" fn application_will_terminate(_: &Object, _: Sel, _: id) { trace!("Completed `applicationWillTerminate`"); } -#[cfg(all(feature = "macos-open-urls", not(feature = "macos-open-files")))] extern "C" fn application_open_urls(_: &Object, _: Sel, _: id, urls: id) -> () { trace!("Trigger `application:openURLs:`"); @@ -142,8 +152,7 @@ extern "C" fn application_open_urls(_: &Object, _: Sel, _: id, urls: id) -> () { trace!("Completed `application:openURLs:`"); } -#[cfg(all(feature = "macos-open-files", not(feature = "macos-open-urls")))] -extern "C" fn application_open_file(_: &Object, _: Sel, _: id, file: id) -> objc::runtime::BOOL { +extern "C" fn application_open_file(_: &Object, _: Sel, _: id, file: id) -> BOOL { use std::{ffi::OsStr, os::unix::prelude::OsStrExt}; trace!("Trigger `application:openFile:`"); @@ -154,10 +163,9 @@ extern "C" fn application_open_file(_: &Object, _: Sel, _: id, file: id) -> objc AppState::open_file(filename); trace!("Completed `application:openFile:`"); - objc::runtime::YES + YES } -#[cfg(all(feature = "macos-open-files", not(feature = "macos-open-urls")))] extern "C" fn application_open_files(_: &Object, _: Sel, _: id, files: id) -> () { use std::{ffi::OsStr, os::unix::prelude::OsStrExt}; diff --git a/src/platform_impl/macos/app_state.rs b/src/platform_impl/macos/app_state.rs index 8e7810d83..b8dae7cdf 100644 --- a/src/platform_impl/macos/app_state.rs +++ b/src/platform_impl/macos/app_state.rs @@ -302,14 +302,12 @@ impl AppState { HANDLER.set_in_callback(false); } - #[cfg(all(feature = "macos-open-urls", not(feature = "macos-open-files")))] pub fn open_urls(urls: Vec) { HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::Opened { event: crate::event::OpenEvent::Url(urls), })); } - #[cfg(all(feature = "macos-open-files", not(feature = "macos-open-urls")))] pub fn open_file(filename: std::path::PathBuf) { if let Some(ref mut callback) = *HANDLER.callback.lock().unwrap() { callback.handle_nonuser_event( @@ -320,7 +318,6 @@ impl AppState { ) } } - #[cfg(all(feature = "macos-open-files", not(feature = "macos-open-urls")))] pub fn open_files(files: Vec) { HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::Opened { event: crate::event::OpenEvent::File(files), diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index 78ebd13a8..22420eb5c 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -29,9 +29,10 @@ use crate::{ event::Event, event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootWindowTarget}, monitor::MonitorHandle as RootMonitorHandle, + platform::macos::OpenResourceKind, platform_impl::platform::{ app::APP_CLASS, - app_delegate::APP_DELEGATE_CLASS, + app_delegate::app_delegate_class, app_state::AppState, monitor::{self, MonitorHandle}, observer::*, @@ -115,6 +116,7 @@ impl EventLoopWindowTarget { pub struct EventLoop { pub(crate) delegate: IdRef, + pub(crate) open_resource_kind: OpenResourceKind, window_target: Rc>, panic_info: Rc, @@ -130,28 +132,11 @@ pub struct EventLoop { impl EventLoop { pub fn new() -> Self { - let delegate = unsafe { - let is_main_thread: BOOL = msg_send!(class!(NSThread), isMainThread); - if is_main_thread == NO { - panic!("On macOS, `EventLoop` must be created on the main thread!"); - } - - // This must be done before `NSApp()` (equivalent to sending - // `sharedApplication`) is called anywhere else, or we'll end up - // with the wrong `NSApplication` class and the wrong thread could - // be marked as main. - let app: id = msg_send![APP_CLASS.0, sharedApplication]; - - let delegate = IdRef::new(msg_send![APP_DELEGATE_CLASS.0, new]); - let pool = NSAutoreleasePool::new(nil); - let _: () = msg_send![app, setDelegate:*delegate]; - let _: () = msg_send![pool, drain]; - delegate - }; let panic_info: Rc = Default::default(); setup_control_flow_observers(Rc::downgrade(&panic_info)); EventLoop { - delegate, + delegate: IdRef::new(nil), + open_resource_kind: Default::default(), window_target: Rc::new(RootWindowTarget { p: Default::default(), _marker: PhantomData, @@ -177,6 +162,32 @@ impl EventLoop { where F: FnMut(Event<'_, T>, &RootWindowTarget, &mut ControlFlow), { + // initialize app delegate if needed + if self.delegate.is_null() { + let delegate = unsafe { + let is_main_thread: BOOL = msg_send!(class!(NSThread), isMainThread); + if is_main_thread == NO { + panic!("On macOS, `EventLoop` must be created on the main thread!"); + } + + // This must be done before `NSApp()` (equivalent to sending + // `sharedApplication`) is called anywhere else, or we'll end up + // with the wrong `NSApplication` class and the wrong thread could + // be marked as main. + let app: id = msg_send![APP_CLASS.0, sharedApplication]; + + let delegate = IdRef::new(msg_send![ + app_delegate_class("TaoAppDelegate", self.open_resource_kind).0, + new + ]); + let pool = NSAutoreleasePool::new(nil); + let _: () = msg_send![app, setDelegate:*delegate]; + let _: () = msg_send![pool, drain]; + delegate + }; + self.delegate = delegate; + } + // This transmute is always safe, in case it was reached through `run`, since our // lifetime will be already 'static. In other cases caller should ensure that all data // they passed to callback will actually outlive it, some apps just can't move