Skip to content

Commit 093d8fb

Browse files
meowteclucasfernogamrbashir
authored
feat: add support to deeplink and file association on macOS (#422)
Co-authored-by: Lucas Nogueira <lucas@tauri.studio> Co-authored-by: amrbashir <amr.bashir2015@gmail.com> Co-authored-by: Lucas Nogueira <lucas@tauri.app>
1 parent ea14c6b commit 093d8fb

File tree

10 files changed

+75
-44
lines changed

10 files changed

+75
-44
lines changed

.changes/support-open-file.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tao": minor
3+
---
4+
5+
Add `Event::OpenURLs` on macOS.

Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ members = [ "tao-macros" ]
3131

3232
[features]
3333
default = []
34-
dox = ["gtk/dox"]
35-
tray = ["libappindicator", "dirs-next"]
34+
dox = [ "gtk/dox"]
35+
tray = [ "libappindicator", "dirs-next" ]
3636

3737
[build-dependencies]
3838
cc = "1"

src/event.rs

+5-14
Original file line numberDiff line numberDiff line change
@@ -165,15 +165,8 @@ pub enum Event<'a, T: 'static> {
165165
/// gets emitted. You generally want to treat this as an "do on quit" event.
166166
LoopDestroyed,
167167

168-
/// Emitted when the app is open by external resources, like opening a Url.
169-
Opened { event: OpenEvent },
170-
}
171-
172-
/// What the app is opening.
173-
#[derive(Debug, PartialEq, Clone)]
174-
pub enum OpenEvent {
175-
/// App is opening an URL.
176-
Url(url::Url),
168+
/// Emitted when the app is open by external resources, like opening a file or deeplink.
169+
Opened { urls: Vec<url::Url> },
177170
}
178171

179172
impl<T: Clone> Clone for Event<'static, T> {
@@ -217,9 +210,7 @@ impl<T: Clone> Clone for Event<'static, T> {
217210
position: *position,
218211
},
219212
GlobalShortcutEvent(accelerator_id) => GlobalShortcutEvent(*accelerator_id),
220-
Opened { event } => Opened {
221-
event: event.clone(),
222-
},
213+
Opened { urls } => Opened { urls: urls.clone() },
223214
}
224215
}
225216
}
@@ -259,7 +250,7 @@ impl<'a, T> Event<'a, T> {
259250
position,
260251
}),
261252
GlobalShortcutEvent(accelerator_id) => Ok(GlobalShortcutEvent(accelerator_id)),
262-
Opened { event } => Ok(Opened { event }),
253+
Opened { urls } => Ok(Opened { urls }),
263254
}
264255
}
265256

@@ -301,7 +292,7 @@ impl<'a, T> Event<'a, T> {
301292
position,
302293
}),
303294
GlobalShortcutEvent(accelerator_id) => Some(GlobalShortcutEvent(accelerator_id)),
304-
Opened { event } => Some(Opened { event }),
295+
Opened { urls } => Some(Opened { urls }),
305296
}
306297
}
307298
}

src/platform/macos.rs

+1
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,7 @@ pub trait EventLoopExtMacOS {
512512
/// [`run_return`](crate::platform::run_return::EventLoopExtRunReturn::run_return)
513513
fn set_activate_ignoring_other_apps(&mut self, ignore: bool);
514514
}
515+
515516
impl<T> EventLoopExtMacOS for EventLoop<T> {
516517
#[inline]
517518
fn set_activation_policy(&mut self, activation_policy: ActivationPolicy) {

src/platform_impl/ios/view.rs

+2-4
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use objc::{
1111

1212
use crate::{
1313
dpi::PhysicalPosition,
14-
event::{DeviceId as RootDeviceId, Event, Force, OpenEvent, Touch, TouchPhase, WindowEvent},
14+
event::{DeviceId as RootDeviceId, Event, Force, Touch, TouchPhase, WindowEvent},
1515
platform::ios::MonitorHandleExtIOS,
1616
platform_impl::platform::{
1717
app_state::{self, OSCapabilities},
@@ -580,9 +580,7 @@ pub fn create_delegate_class() {
580580

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

583-
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Opened {
584-
event: OpenEvent::Url(url),
585-
}));
583+
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Opened { urls: vec![url] }));
586584

587585
YES
588586
}

src/platform_impl/macos/app_delegate.rs

+29-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use crate::{platform::macos::ActivationPolicy, platform_impl::platform::app_state::AppState};
66

77
use cocoa::base::id;
8+
use cocoa::foundation::NSString;
89
use objc::{
910
declare::ClassDecl,
1011
runtime::{Class, Object, Sel},
@@ -14,6 +15,10 @@ use std::{
1415
os::raw::c_void,
1516
};
1617

18+
use cocoa::foundation::NSArray;
19+
use cocoa::foundation::NSURL;
20+
use std::ffi::CStr;
21+
1722
static AUX_DELEGATE_STATE_NAME: &str = "auxState";
1823

1924
pub struct AuxDelegateState {
@@ -34,7 +39,7 @@ unsafe impl Sync for AppDelegateClass {}
3439
lazy_static! {
3540
pub static ref APP_DELEGATE_CLASS: AppDelegateClass = unsafe {
3641
let superclass = class!(NSResponder);
37-
let mut decl = ClassDecl::new("TaoAppDelegate", superclass).unwrap();
42+
let mut decl = ClassDecl::new("TaoAppDelegateParent", superclass).unwrap();
3843

3944
decl.add_class_method(sel!(new), new as extern "C" fn(&Class, Sel) -> id);
4045
decl.add_method(sel!(dealloc), dealloc as extern "C" fn(&Object, Sel));
@@ -47,6 +52,10 @@ lazy_static! {
4752
sel!(applicationWillTerminate:),
4853
application_will_terminate as extern "C" fn(&Object, Sel, id),
4954
);
55+
decl.add_method(
56+
sel!(application:openURLs:),
57+
application_open_urls as extern "C" fn(&Object, Sel, id, id),
58+
);
5059
decl.add_ivar::<*mut c_void>(AUX_DELEGATE_STATE_NAME);
5160

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

@@ -96,3 +105,21 @@ extern "C" fn application_will_terminate(_: &Object, _: Sel, _: id) {
96105
AppState::exit();
97106
trace!("Completed `applicationWillTerminate`");
98107
}
108+
109+
extern "C" fn application_open_urls(_: &Object, _: Sel, _: id, urls: id) -> () {
110+
trace!("Trigger `application:openURLs:`");
111+
112+
let urls = unsafe {
113+
(0..urls.count())
114+
.map(|i| {
115+
url::Url::parse(
116+
&CStr::from_ptr(urls.objectAtIndex(i).absoluteString().UTF8String()).to_string_lossy(),
117+
)
118+
})
119+
.flatten()
120+
.collect::<Vec<_>>()
121+
};
122+
trace!("Get `application:openURLs:` URLs: {:?}", urls);
123+
AppState::open_urls(urls);
124+
trace!("Completed `application:openURLs:`");
125+
}

src/platform_impl/macos/app_state.rs

+4
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,10 @@ impl AppState {
302302
HANDLER.set_in_callback(false);
303303
}
304304

305+
pub fn open_urls(urls: Vec<url::Url>) {
306+
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::Opened { urls }));
307+
}
308+
305309
pub fn wakeup(panic_info: Weak<PanicInfo>) {
306310
let panic_info = panic_info
307311
.upgrade()

src/platform_impl/macos/event_loop.rs

+24-19
Original file line numberDiff line numberDiff line change
@@ -130,28 +130,10 @@ pub struct EventLoop<T: 'static> {
130130

131131
impl<T> EventLoop<T> {
132132
pub fn new() -> Self {
133-
let delegate = unsafe {
134-
let is_main_thread: BOOL = msg_send!(class!(NSThread), isMainThread);
135-
if is_main_thread == NO {
136-
panic!("On macOS, `EventLoop` must be created on the main thread!");
137-
}
138-
139-
// This must be done before `NSApp()` (equivalent to sending
140-
// `sharedApplication`) is called anywhere else, or we'll end up
141-
// with the wrong `NSApplication` class and the wrong thread could
142-
// be marked as main.
143-
let app: id = msg_send![APP_CLASS.0, sharedApplication];
144-
145-
let delegate = IdRef::new(msg_send![APP_DELEGATE_CLASS.0, new]);
146-
let pool = NSAutoreleasePool::new(nil);
147-
let _: () = msg_send![app, setDelegate:*delegate];
148-
let _: () = msg_send![pool, drain];
149-
delegate
150-
};
151133
let panic_info: Rc<PanicInfo> = Default::default();
152134
setup_control_flow_observers(Rc::downgrade(&panic_info));
153135
EventLoop {
154-
delegate,
136+
delegate: IdRef::new(nil),
155137
window_target: Rc::new(RootWindowTarget {
156138
p: Default::default(),
157139
_marker: PhantomData,
@@ -177,6 +159,29 @@ impl<T> EventLoop<T> {
177159
where
178160
F: FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow),
179161
{
162+
// initialize app delegate if needed
163+
if self.delegate.is_null() {
164+
let delegate = unsafe {
165+
let is_main_thread: BOOL = msg_send!(class!(NSThread), isMainThread);
166+
if is_main_thread == NO {
167+
panic!("On macOS, `EventLoop` must be created on the main thread!");
168+
}
169+
170+
// This must be done before `NSApp()` (equivalent to sending
171+
// `sharedApplication`) is called anywhere else, or we'll end up
172+
// with the wrong `NSApplication` class and the wrong thread could
173+
// be marked as main.
174+
let app: id = msg_send![APP_CLASS.0, sharedApplication];
175+
176+
let delegate = IdRef::new(msg_send![APP_DELEGATE_CLASS.0, new]);
177+
let pool = NSAutoreleasePool::new(nil);
178+
let _: () = msg_send![app, setDelegate:*delegate];
179+
let _: () = msg_send![pool, drain];
180+
delegate
181+
};
182+
self.delegate = delegate;
183+
}
184+
180185
// This transmute is always safe, in case it was reached through `run`, since our
181186
// lifetime will be already 'static. In other cases caller should ensure that all data
182187
// they passed to callback will actually outlive it, some apps just can't move

src/platform_impl/macos/view.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ extern "C" fn dealloc(this: &Object, _sel: Sel) {
308308
let state: *mut c_void = *this.get_ivar("taoState");
309309
let marked_text: id = *this.get_ivar("markedText");
310310
let _: () = msg_send![marked_text, release];
311-
Box::from_raw(state as *mut ViewState);
311+
drop(Box::from_raw(state as *mut ViewState));
312312
}
313313
}
314314

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

576-
extern "C" fn do_command_by_selector(this: &Object, _sel: Sel, command: Sel) {
576+
extern "C" fn do_command_by_selector(_this: &Object, _sel: Sel, _command: Sel) {
577577
trace!("Triggered `doCommandBySelector`");
578578
// TODO: (Artur) all these inputs seem to trigger a key event with the correct text
579579
// content so this is not needed anymore, it seems.

src/platform_impl/macos/window_delegate.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ fn with_state<F: FnOnce(&mut WindowDelegateState) -> T, T>(this: &Object, callba
276276

277277
extern "C" fn dealloc(this: &Object, _sel: Sel) {
278278
with_state(this, |state| unsafe {
279-
Box::from_raw(state as *mut WindowDelegateState);
279+
drop(Box::from_raw(state as *mut WindowDelegateState));
280280
});
281281
}
282282

0 commit comments

Comments
 (0)