Skip to content

Commit 1690c6d

Browse files
committed
macos: Migrate to objc2-core-foundation
1 parent 3ea6f97 commit 1690c6d

File tree

2 files changed

+81
-44
lines changed

2 files changed

+81
-44
lines changed

Cargo.toml

+9-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ readme = "README.md"
1010
keywords = ["webbrowser", "browser"]
1111
license = "MIT OR Apache-2.0"
1212
edition = "2021"
13-
rust-version = "1.60"
13+
rust-version = "1.69"
1414

1515
[dependencies]
1616
log = "0.4"
@@ -29,7 +29,14 @@ wasm-console = ["web-sys/console"]
2929
home = "0.5"
3030

3131
[target.'cfg(target_os = "macos")'.dependencies]
32-
core-foundation = "0.10"
32+
objc2-core-foundation = { version = "0.3", default-features = false, features = [
33+
"std",
34+
"CFArray",
35+
"CFBase",
36+
"CFError",
37+
"CFString",
38+
"CFURL",
39+
] }
3340

3441
[target.'cfg(target_os = "android")'.dependencies]
3542
jni = "0.21"

src/macos.rs

+72-42
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
1+
use std::{
2+
ffi::c_void,
3+
ffi::{CStr, OsStr},
4+
mem::MaybeUninit,
5+
os::unix::ffi::OsStrExt,
6+
path::PathBuf,
7+
ptr::NonNull,
8+
};
9+
10+
use objc2_core_foundation::{
11+
CFArray, CFArrayCreate, CFError, CFRetained, CFStringBuiltInEncodings, CFURLCreateWithBytes,
12+
CFURLGetFileSystemRepresentation, CFURL,
13+
};
14+
115
use crate::{Browser, BrowserOptions, Error, ErrorKind, Result, TargetType};
2-
use core_foundation::array::{CFArray, CFArrayRef};
3-
use core_foundation::base::TCFType;
4-
use core_foundation::error::{CFError, CFErrorRef};
5-
use core_foundation::url::{CFURLRef, CFURL};
6-
use std::os::raw::c_void;
716

817
/// Deal with opening of browsers on Mac OS X using Core Foundation framework
918
pub(super) fn open_browser_internal(
@@ -19,23 +28,22 @@ pub(super) fn open_browser_internal(
1928
Browser::Safari => create_cf_url("file:///Applications/Safari.app/"),
2029
Browser::Default => {
2130
if let Some(dummy_url) = create_cf_url("https://") {
22-
let mut err: CFErrorRef = std::ptr::null_mut();
31+
let mut err = MaybeUninit::uninit();
2332
let result = unsafe {
24-
LSCopyDefaultApplicationURLForURL(
25-
dummy_url.as_concrete_TypeRef(),
26-
LSROLE_VIEWER,
27-
&mut err,
28-
)
33+
LSCopyDefaultApplicationURLForURL(&dummy_url, LSROLE_VIEWER, err.as_mut_ptr())
2934
};
30-
if result.is_null() {
31-
log::error!("failed to get default browser: {}", unsafe {
32-
CFError::wrap_under_create_rule(err)
33-
});
34-
create_cf_url(DEFAULT_BROWSER_URL)
35-
} else {
36-
let cf_url = unsafe { CFURL::wrap_under_create_rule(result) };
35+
if let Some(result) = NonNull::new(result) {
36+
let cf_url = unsafe { CFRetained::from_raw(result) };
3737
log::trace!("default browser is {:?}", &cf_url);
3838
Some(cf_url)
39+
} else {
40+
let error = unsafe {
41+
CFRetained::from_raw(NonNull::new(err.assume_init()).expect(
42+
"Error should be set when LSCopyDefaultApplicationURLForURL() returns NULL",
43+
))
44+
};
45+
log::error!("failed to get default browser: {}", error);
46+
create_cf_url(DEFAULT_BROWSER_URL)
3947
}
4048
} else {
4149
create_cf_url(DEFAULT_BROWSER_URL)
@@ -53,19 +61,27 @@ pub(super) fn open_browser_internal(
5361
let cf_url = create_cf_url(target.as_ref())
5462
.ok_or_else(|| Error::new(ErrorKind::Other, "failed to create CFURL"))?;
5563

56-
let urls_v = [cf_url];
57-
let urls_arr = CFArray::<CFURL>::from_CFTypes(&urls_v);
64+
let mut urls_v = [cf_url];
65+
let urls_arr = unsafe {
66+
CFArrayCreate(
67+
None,
68+
urls_v.as_mut_ptr().cast(),
69+
urls_v.len() as isize,
70+
std::ptr::null(),
71+
)
72+
}
73+
.expect("Failed to create CFArray from slice");
5874
let spec = LSLaunchURLSpec {
59-
app_url: browser_cf_url.as_concrete_TypeRef(),
60-
item_urls: urls_arr.as_concrete_TypeRef(),
75+
app_url: &*browser_cf_url,
76+
item_urls: &*urls_arr,
6177
pass_thru_params: std::ptr::null(),
6278
launch_flags: LS_LAUNCH_FLAG_DEFAULTS | LS_LAUNCH_FLAG_ASYNC,
6379
async_ref_con: std::ptr::null(),
6480
};
6581

6682
// handle dry-run scenario
6783
if options.dry_run {
68-
return if let Some(path) = browser_cf_url.to_path() {
84+
return if let Some(path) = cf_url_as_path(&browser_cf_url) {
6985
if path.is_dir() {
7086
log::debug!("dry-run: not actually opening the browser {}", &browser);
7187
Ok(())
@@ -83,8 +99,7 @@ pub(super) fn open_browser_internal(
8399

84100
// launch the browser
85101
log::trace!("about to start browser: {} for {}", &browser, &target);
86-
let mut launched_app: CFURLRef = std::ptr::null_mut();
87-
let status = unsafe { LSOpenFromURLSpec(&spec, &mut launched_app) };
102+
let status = unsafe { LSOpenFromURLSpec(&spec, std::ptr::null_mut()) };
88103
log::trace!("received status: {}", status);
89104
if status == 0 {
90105
Ok(())
@@ -94,22 +109,37 @@ pub(super) fn open_browser_internal(
94109
}
95110

96111
/// Create a Core Foundation CFURL object given a rust-y `url`
97-
fn create_cf_url(url: &str) -> Option<CFURL> {
112+
fn create_cf_url(url: &str) -> Option<CFRetained<CFURL>> {
98113
let url_u8 = url.as_bytes();
99-
let url_ref = unsafe {
100-
core_foundation::url::CFURLCreateWithBytes(
101-
std::ptr::null(),
114+
unsafe {
115+
CFURLCreateWithBytes(
116+
None,
102117
url_u8.as_ptr(),
103118
url_u8.len() as isize,
104-
core_foundation::string::kCFStringEncodingUTF8,
105-
std::ptr::null(),
119+
CFStringBuiltInEncodings::EncodingUTF8.0,
120+
None,
106121
)
107-
};
122+
}
123+
}
108124

109-
if url_ref.is_null() {
110-
None
111-
} else {
112-
Some(unsafe { CFURL::wrap_under_create_rule(url_ref) })
125+
// Partially borrowed from https://docs.rs/core-foundation/0.10.0/src/core_foundation/url.rs.html#90-107
126+
fn cf_url_as_path(url: &CFURL) -> Option<PathBuf> {
127+
// From libc
128+
pub const PATH_MAX: i32 = 1024;
129+
// implementing this on Windows is more complicated because of the different OsStr representation
130+
unsafe {
131+
let mut buf = [0u8; PATH_MAX as usize];
132+
let result =
133+
CFURLGetFileSystemRepresentation(url, true, buf.as_mut_ptr(), buf.len() as isize);
134+
if !result {
135+
return None;
136+
}
137+
// let len = strlen(buf.as_ptr() as *const c_char);
138+
// let path = OsStr::from_bytes(&buf[0..len]);
139+
// TODO: Requires MSRV bump
140+
let path = CStr::from_bytes_until_nul(&buf).expect("buf must be NUL-terminated");
141+
let path = OsStr::from_bytes(path.to_bytes());
142+
Some(PathBuf::from(path))
113143
}
114144
}
115145

@@ -172,8 +202,8 @@ const LS_LAUNCH_FLAG_ASYNC: u32 = 0x00010000;
172202

173203
#[repr(C)]
174204
struct LSLaunchURLSpec {
175-
app_url: CFURLRef,
176-
item_urls: CFArrayRef,
205+
app_url: *const CFURL,
206+
item_urls: *const CFArray,
177207
pass_thru_params: *const c_void,
178208
launch_flags: u32,
179209
async_ref_con: *const c_void,
@@ -185,16 +215,16 @@ extern "C" {
185215
/// Used to get the default browser configured for the user. See:
186216
/// https://developer.apple.com/documentation/coreservices/1448824-lscopydefaultapplicationurlforur?language=objc
187217
fn LSCopyDefaultApplicationURLForURL(
188-
inURL: CFURLRef,
218+
inURL: &CFURL,
189219
inRoleMask: LSRolesMask,
190-
outError: *mut CFErrorRef,
191-
) -> CFURLRef;
220+
outError: *mut *mut CFError,
221+
) -> *mut CFURL;
192222

193223
/// Used to launch the browser to open a url
194224
/// https://developer.apple.com/documentation/coreservices/1441986-lsopenfromurlspec?language=objc
195225
fn LSOpenFromURLSpec(
196226
inLaunchSpec: *const LSLaunchURLSpec,
197-
outLaunchedURL: *mut CFURLRef,
227+
outLaunchedURL: *mut *mut CFURL,
198228
) -> OSStatus;
199229
}
200230

0 commit comments

Comments
 (0)