feat: MacOS: add universal applink support (#1108)

by implementing `application:willContinueUserActivityWithType:` and `application:continueUserActivity:restorationHandler:`,
reusing the existing `Event::Opened { urls }` event for the user facing api.
This commit is contained in:
Simon Laux
2025-08-18 11:47:08 +00:00
committed by GitHub
parent 6cf83e64d0
commit 60a47340c9
4 changed files with 92 additions and 5 deletions

View File

@@ -0,0 +1,7 @@
---
"tao": patch
---
feat: MacOS: add universal applink support
by implementing `application:willContinueUserActivityWithType:` and `application:continueUserActivity:restorationHandler:`,
reusing the existing `Event::Opened { urls }` event for the user facing api.

16
Cargo.lock generated
View File

@@ -193,6 +193,15 @@ dependencies = [
"objc2 0.5.2",
]
[[package]]
name = "block2"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d59b4c170e16f0405a2e95aff44432a0d41aa97675f3d52623effe95792a037"
dependencies = [
"objc2 0.6.0",
]
[[package]]
name = "built"
version = "0.7.5"
@@ -1594,7 +1603,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8"
dependencies = [
"bitflags 2.8.0",
"block2",
"block2 0.5.1",
"libc",
"objc2 0.5.2",
]
@@ -1617,7 +1626,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6"
dependencies = [
"bitflags 2.8.0",
"block2",
"block2 0.5.1",
"objc2 0.5.2",
"objc2-foundation 0.2.2",
]
@@ -1629,7 +1638,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a"
dependencies = [
"bitflags 2.8.0",
"block2",
"block2 0.5.1",
"objc2 0.5.2",
"objc2-foundation 0.2.2",
"objc2-metal",
@@ -2219,6 +2228,7 @@ name = "tao"
version = "0.34.1"
dependencies = [
"bitflags 2.8.0",
"block2 0.6.0",
"core-foundation",
"core-graphics",
"crossbeam-channel",

View File

@@ -106,6 +106,7 @@ tao-macros = { version = "0.1.0", path = "./tao-macros" }
[target."cfg(any(target_os = \"ios\", target_os = \"macos\"))".dependencies]
objc2 = "0.6"
block2 = "0.6"
[target."cfg(target_os = \"macos\")".dependencies]
objc2-foundation = { version = "0.3", default-features = false, features = [
@@ -140,6 +141,7 @@ objc2-app-kit = { version = "0.3", default-features = false, features = [
"NSScreen",
"NSView",
"NSWindow",
"NSUserActivity"
] }
core-foundation = "0.10"
core-graphics = "0.24"

View File

@@ -10,8 +10,12 @@ use crate::{
},
};
use objc2::runtime::{AnyClass as Class, AnyObject as Object, ClassBuilder as ClassDecl, Sel};
use objc2_foundation::{NSArray, NSURL};
use objc2::runtime::{
AnyClass as Class, AnyObject as Object, Bool, ClassBuilder as ClassDecl, Sel,
};
use objc2_foundation::{
NSArray, NSError, NSString, NSUserActivity, NSUserActivityTypeBrowsingWeb, NSURL,
};
use std::{
cell::{RefCell, RefMut},
ffi::{CStr, CString},
@@ -63,6 +67,14 @@ lazy_static! {
sel!(application:openURLs:),
application_open_urls as extern "C" fn(_, _, _, _),
);
decl.add_method(
sel!(application:willContinueUserActivityWithType:),
application_will_continue_user_activity_with_type as extern "C" fn(_, _, _, _) -> _,
);
decl.add_method(
sel!(application:continueUserActivity:restorationHandler:),
application_continue_user_activity as extern "C" fn(_, _, _, _, _) -> _,
);
decl.add_method(
sel!(applicationShouldHandleReopen:hasVisibleWindows:),
application_should_handle_reopen as extern "C" fn(_, _, _, _) -> _,
@@ -136,6 +148,62 @@ extern "C" fn application_open_urls(_: &Object, _: Sel, _: id, urls: &NSArray<NS
trace!("Completed `application:openURLs:`");
}
extern "C" fn application_will_continue_user_activity_with_type(
_: &Object,
_: Sel,
_: id,
user_activity_type: &NSString,
) -> Bool {
trace!("Trigger `application:willContinueUserActivityWithType:`");
let result = unsafe { Bool::new(user_activity_type == NSUserActivityTypeBrowsingWeb) };
trace!("Completed `application:willContinueUserActivityWithType:`");
result
}
extern "C" fn application_continue_user_activity(
_: &Object,
_: Sel,
_: id,
user_activity: &NSUserActivity,
_restoration_handler: &block2::Block<dyn Fn(*mut NSError)>,
) -> Bool {
trace!("Trigger `application:continueUserActivity:restorationHandler:`");
let url = unsafe {
if user_activity
.activityType()
.isEqualToString(NSUserActivityTypeBrowsingWeb)
{
match user_activity
.webpageURL()
.and_then(|url| url.absoluteString())
.and_then(|s| Some(s.to_string()))
{
None => {
error!(
"`application:continueUserActivity:restorationHandler:`: restore webbrowsing activity but url is empty"
);
return Bool::new(false);
}
Some(url_string) => match url::Url::parse(&url_string) {
Ok(url) => url,
Err(err) => {
error!(
"`application:continueUserActivity:restorationHandler:`: failed to parse url {err}"
);
return Bool::new(false);
}
},
}
} else {
return Bool::new(false);
}
};
AppState::open_urls(vec![url]);
trace!("Completed `application:continueUserActivity:restorationHandler:`");
return Bool::new(true);
}
extern "C" fn application_should_handle_reopen(
_: &Object,
_: Sel,