Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support to deeplink and file association on macOS #422

Merged
merged 21 commits into from
Jul 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/support-open-file.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tao": minor
---

Add `Event::OpenURLs` on macOS.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ members = [ "tao-macros" ]

[features]
default = []
dox = ["gtk/dox"]
tray = ["libappindicator", "dirs-next"]
dox = [ "gtk/dox"]
tray = [ "libappindicator", "dirs-next" ]

[build-dependencies]
cc = "1"
Expand Down
19 changes: 5 additions & 14 deletions src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,15 +165,8 @@ pub enum Event<'a, T: 'static> {
/// gets emitted. You generally want to treat this as an "do on quit" event.
LoopDestroyed,

/// Emitted when the app is open by external resources, like opening a Url.
Opened { event: OpenEvent },
}

/// What the app is opening.
#[derive(Debug, PartialEq, Clone)]
pub enum OpenEvent {
/// App is opening an URL.
Url(url::Url),
/// Emitted when the app is open by external resources, like opening a file or deeplink.
Opened { urls: Vec<url::Url> },
}

impl<T: Clone> Clone for Event<'static, T> {
Expand Down Expand Up @@ -217,9 +210,7 @@ impl<T: Clone> Clone for Event<'static, T> {
position: *position,
},
GlobalShortcutEvent(accelerator_id) => GlobalShortcutEvent(*accelerator_id),
Opened { event } => Opened {
event: event.clone(),
},
Opened { urls } => Opened { urls: urls.clone() },
}
}
}
Expand Down Expand Up @@ -259,7 +250,7 @@ impl<'a, T> Event<'a, T> {
position,
}),
GlobalShortcutEvent(accelerator_id) => Ok(GlobalShortcutEvent(accelerator_id)),
Opened { event } => Ok(Opened { event }),
Opened { urls } => Ok(Opened { urls }),
}
}

Expand Down Expand Up @@ -301,7 +292,7 @@ impl<'a, T> Event<'a, T> {
position,
}),
GlobalShortcutEvent(accelerator_id) => Some(GlobalShortcutEvent(accelerator_id)),
Opened { event } => Some(Opened { event }),
Opened { urls } => Some(Opened { urls }),
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/platform/macos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,7 @@ pub trait EventLoopExtMacOS {
/// [`run_return`](crate::platform::run_return::EventLoopExtRunReturn::run_return)
fn set_activate_ignoring_other_apps(&mut self, ignore: bool);
}

impl<T> EventLoopExtMacOS for EventLoop<T> {
#[inline]
fn set_activation_policy(&mut self, activation_policy: ActivationPolicy) {
Expand Down
6 changes: 2 additions & 4 deletions src/platform_impl/ios/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use objc::{

use crate::{
dpi::PhysicalPosition,
event::{DeviceId as RootDeviceId, Event, Force, OpenEvent, Touch, TouchPhase, WindowEvent},
event::{DeviceId as RootDeviceId, Event, Force, Touch, TouchPhase, WindowEvent},
platform::ios::MonitorHandleExtIOS,
platform_impl::platform::{
app_state::{self, OSCapabilities},
Expand Down Expand Up @@ -580,9 +580,7 @@ pub fn create_delegate_class() {

let url = url::Url::parse(std::str::from_utf8(bytes).unwrap()).unwrap();

app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Opened {
event: OpenEvent::Url(url),
}));
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Opened { urls: vec![url] }));

YES
}
Expand Down
31 changes: 29 additions & 2 deletions src/platform_impl/macos/app_delegate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use crate::{platform::macos::ActivationPolicy, platform_impl::platform::app_state::AppState};

use cocoa::base::id;
use cocoa::foundation::NSString;
use objc::{
declare::ClassDecl,
runtime::{Class, Object, Sel},
Expand All @@ -14,6 +15,10 @@ use std::{
os::raw::c_void,
};

use cocoa::foundation::NSArray;
use cocoa::foundation::NSURL;
use std::ffi::CStr;

static AUX_DELEGATE_STATE_NAME: &str = "auxState";

pub struct AuxDelegateState {
Expand All @@ -34,7 +39,7 @@ unsafe impl Sync for AppDelegateClass {}
lazy_static! {
pub 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));
Expand All @@ -47,6 +52,10 @@ lazy_static! {
sel!(applicationWillTerminate:),
application_will_terminate as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(application:openURLs:),
application_open_urls as extern "C" fn(&Object, Sel, id, id),
);
decl.add_ivar::<*mut c_void>(AUX_DELEGATE_STATE_NAME);

AppDelegateClass(decl.register())
Expand Down Expand Up @@ -81,7 +90,7 @@ extern "C" fn dealloc(this: &Object, _: Sel) {
let state_ptr: *mut c_void = *(this.get_ivar(AUX_DELEGATE_STATE_NAME));
// As soon as the box is constructed it is immediately dropped, releasing the underlying
// memory
Box::from_raw(state_ptr as *mut RefCell<AuxDelegateState>);
drop(Box::from_raw(state_ptr as *mut RefCell<AuxDelegateState>));
}
}

Expand All @@ -96,3 +105,21 @@ extern "C" fn application_will_terminate(_: &Object, _: Sel, _: id) {
AppState::exit();
trace!("Completed `applicationWillTerminate`");
}

extern "C" fn application_open_urls(_: &Object, _: Sel, _: id, urls: id) -> () {
trace!("Trigger `application:openURLs:`");

let urls = unsafe {
(0..urls.count())
.map(|i| {
url::Url::parse(
&CStr::from_ptr(urls.objectAtIndex(i).absoluteString().UTF8String()).to_string_lossy(),
)
})
.flatten()
.collect::<Vec<_>>()
};
trace!("Get `application:openURLs:` URLs: {:?}", urls);
AppState::open_urls(urls);
trace!("Completed `application:openURLs:`");
}
4 changes: 4 additions & 0 deletions src/platform_impl/macos/app_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,10 @@ impl AppState {
HANDLER.set_in_callback(false);
}

pub fn open_urls(urls: Vec<url::Url>) {
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::Opened { urls }));
}

pub fn wakeup(panic_info: Weak<PanicInfo>) {
let panic_info = panic_info
.upgrade()
Expand Down
43 changes: 24 additions & 19 deletions src/platform_impl/macos/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,28 +130,10 @@ pub struct EventLoop<T: 'static> {

impl<T> EventLoop<T> {
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<PanicInfo> = Default::default();
setup_control_flow_observers(Rc::downgrade(&panic_info));
EventLoop {
delegate,
delegate: IdRef::new(nil),
window_target: Rc::new(RootWindowTarget {
p: Default::default(),
_marker: PhantomData,
Expand All @@ -177,6 +159,29 @@ impl<T> EventLoop<T> {
where
F: FnMut(Event<'_, T>, &RootWindowTarget<T>, &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.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
Expand Down
4 changes: 2 additions & 2 deletions src/platform_impl/macos/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ extern "C" fn dealloc(this: &Object, _sel: Sel) {
let state: *mut c_void = *this.get_ivar("taoState");
let marked_text: id = *this.get_ivar("markedText");
let _: () = msg_send![marked_text, release];
Box::from_raw(state as *mut ViewState);
drop(Box::from_raw(state as *mut ViewState));
}
}

Expand Down Expand Up @@ -573,7 +573,7 @@ extern "C" fn insert_text(this: &Object, _sel: Sel, string: id, _replacement_ran
trace!("Completed `insertText`");
}

extern "C" fn do_command_by_selector(this: &Object, _sel: Sel, command: Sel) {
extern "C" fn do_command_by_selector(_this: &Object, _sel: Sel, _command: Sel) {
trace!("Triggered `doCommandBySelector`");
// TODO: (Artur) all these inputs seem to trigger a key event with the correct text
// content so this is not needed anymore, it seems.
Expand Down
2 changes: 1 addition & 1 deletion src/platform_impl/macos/window_delegate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ fn with_state<F: FnOnce(&mut WindowDelegateState) -> T, T>(this: &Object, callba

extern "C" fn dealloc(this: &Object, _sel: Sel) {
with_state(this, |state| unsafe {
Box::from_raw(state as *mut WindowDelegateState);
drop(Box::from_raw(state as *mut WindowDelegateState));
});
}

Expand Down