1extern crate core_foundation;
2extern crate core_foundation_sys;
3#[macro_use]
4extern crate bitflags;
5extern crate libc;
6
7use core_foundation::array::{CFArray, CFArrayRef};
8use core_foundation::base::TCFType;
9use core_foundation::error::{CFError, CFErrorRef};
10use core_foundation::string::{CFString, CFStringRef};
11use core_foundation::url::{CFURLRef, CFURL};
12
13use core_foundation_sys::base::OSStatus;
14
15use libc::c_void;
16
17pub type OptionBits = u32;
18
19#[repr(C)]
20struct PrimitiveLSLaunchURLSpec {
21 app_url: CFURLRef,
22 item_urls: CFArrayRef,
23 pass_thru_params: *const c_void,
24 launch_flags: OptionBits,
25 async_ref_con: *const c_void,
26}
27
28#[link(name = "CoreServices", kind = "framework")]
29extern "C" {
30 fn LSCopyDefaultApplicationURLForURL(
31 inURL: CFURLRef,
32 inRoleMask: OptionBits,
33 outError: *mut CFErrorRef,
34 ) -> CFURLRef;
35 fn LSCopyDefaultApplicationURLForContentType(
36 inURL: CFStringRef,
37 inRoleMask: OptionBits,
38 outError: *mut CFErrorRef,
39 ) -> CFURLRef;
40 fn LSCopyApplicationURLsForURL(inURL: CFURLRef, inRoleMask: OptionBits) -> CFArrayRef;
41 fn LSCanURLAcceptURL(
42 inItemURL: CFURLRef,
43 inTargetURL: CFURLRef,
44 inRoleMask: OptionBits,
45 inFlags: OptionBits,
46 outAcceptsItem: &mut bool,
47 ) -> OSStatus;
48 fn LSCopyApplicationURLsForBundleIdentifier(
49 inBundleIdentifier: CFStringRef,
50 outError: *mut CFErrorRef,
51 ) -> CFArrayRef;
52 fn LSOpenCFURLRef(inURL: CFURLRef, outLaunchedURL: *mut CFURLRef) -> OSStatus;
53 fn LSOpenFromURLSpec(
54 inLaunchSpec: *const PrimitiveLSLaunchURLSpec,
55 outLaunchedURL: *mut CFURLRef,
56 ) -> OSStatus;
57 fn LSRegisterURL(inURL: CFURLRef, inUpdate: bool) -> OSStatus;
58 fn LSCopyAllRoleHandlersForContentType(
59 inContentType: CFStringRef,
60 inRole: OptionBits,
61 ) -> CFArrayRef;
62 fn LSCopyDefaultRoleHandlerForContentType(
63 inContentType: CFStringRef,
64 inRole: OptionBits,
65 ) -> CFStringRef;
66 fn LSSetDefaultRoleHandlerForContentType(
67 inContentType: CFStringRef,
68 inRole: OptionBits,
69 inHandlerBundleID: CFStringRef,
70 ) -> OSStatus;
71 fn LSSetDefaultHandlerForURLScheme(
72 inURLScheme: CFStringRef,
73 inHandlerBundleID: CFStringRef,
74 ) -> OSStatus;
75}
76
77bitflags! {
78 pub struct LSRolesMask: OptionBits {
79 const NONE = 0x00000001;
80 const VIEWER = 0x00000002;
81 const EDITOR = 0x00000004;
82 const SHELL = 0x00000008;
83 const ALL = ::std::u32::MAX;
84 }
85}
86
87bitflags! {
88 pub struct LSAcceptanceFlags: OptionBits {
89 const DEFAULT = 0x00000001;
90 const ALLOW_LOGIN_UI = 0x00000002;
91 }
92}
93
94bitflags! {
95 pub struct LSLaunchFlags: OptionBits {
96 const DEFAULTS = 0x00000001;
97 const PRINT = 0x00000002;
98 const DISPLAY_ERRORS = 0x00000040;
99 const DONT_ADD_TO_RECENTS = 0x00000100;
100 const DONT_SWITCH = 0x00000200;
101 const ASYNC = 0x00010000;
102 const NEW_INSTANCE = 0x00080000;
103 const HIDE = 0x00100000;
104 const HIDE_OTHERS = 0x00200000;
105 }
106}
107
108pub struct LSLaunchURLSpec {
109 pub app: Option<CFURL>,
110 pub urls: Option<CFArray<CFURL>>,
111 pub pass_thru_params: *const c_void,
112 pub flags: LSLaunchFlags,
113 pub async_ref_con: *const c_void,
114}
115
116impl LSLaunchURLSpec {
117 pub(crate) fn to_primitive(&self) -> PrimitiveLSLaunchURLSpec {
118 PrimitiveLSLaunchURLSpec {
119 app_url: self.app.iter().next()
120 .map(|v| v.as_concrete_TypeRef())
121 .unwrap_or_else(std::ptr::null),
122 item_urls: self.urls.iter().next()
123 .map(|v| v.as_concrete_TypeRef())
124 .unwrap_or_else(std::ptr::null),
125 pass_thru_params: self.pass_thru_params,
126 launch_flags: self.flags.bits(),
127 async_ref_con: self.async_ref_con,
128 }
129 }
130}
131
132impl Default for LSLaunchURLSpec {
133 fn default() -> Self {
134 LSLaunchURLSpec {
135 app: None,
136 urls: None,
137 pass_thru_params: std::ptr::null(),
138 flags: LSLaunchFlags::DEFAULTS,
139 async_ref_con: std::ptr::null(),
140 }
141 }
142}
143
144pub fn default_application_url_for_url(
145 url: &CFURL,
146 role_mask: LSRolesMask,
147) -> Result<CFURL, CFError> {
148 let mut err: CFErrorRef = std::ptr::null_mut();
149 let res = unsafe {
150 LSCopyDefaultApplicationURLForURL(url.as_concrete_TypeRef(), role_mask.bits(), &mut err)
151 };
152
153 if res.is_null() {
154 Err(unsafe { TCFType::wrap_under_create_rule(err) })
155 } else {
156 Ok(unsafe { CFURL::wrap_under_create_rule(res) })
157 }
158}
159
160pub fn default_application_url_content_type(
161 url: &CFString,
162 role_mask: LSRolesMask,
163) -> Result<CFURL, CFError> {
164 let mut err: CFErrorRef = std::ptr::null_mut();
165 let res = unsafe {
166 LSCopyDefaultApplicationURLForContentType(
167 url.as_concrete_TypeRef(),
168 role_mask.bits(),
169 &mut err,
170 )
171 };
172
173 if res.is_null() {
174 Err(unsafe { TCFType::wrap_under_create_rule(err) })
175 } else {
176 Ok(unsafe { TCFType::wrap_under_create_rule(res) })
177 }
178}
179
180pub fn application_urls_for_url(url: &CFURL, role_mask: LSRolesMask) -> Option<CFArray<CFURL>> {
181 let res = unsafe { LSCopyApplicationURLsForURL(url.as_concrete_TypeRef(), role_mask.bits()) };
182
183 if res.is_null() {
184 None
185 } else {
186 Some(unsafe { TCFType::wrap_under_create_rule(res) })
187 }
188}
189
190pub fn can_url_accept_url(
191 item_url: &CFURL,
192 target_url: &CFURL,
193 role_mask: LSRolesMask,
194 flags: LSAcceptanceFlags,
195) -> Result<bool, OSStatus> {
196 let mut res: bool = false;
197 let err = unsafe {
198 LSCanURLAcceptURL(
199 item_url.as_concrete_TypeRef(),
200 target_url.as_concrete_TypeRef(),
201 role_mask.bits(),
202 flags.bits(),
203 &mut res,
204 )
205 };
206
207 if err == 0 {
208 Ok(res)
209 } else {
210 Err(err)
211 }
212}
213
214pub fn application_urls_for_bundle_identifier(
215 bundle_identifier: &CFString,
216) -> Result<CFArray<CFURL>, CFError> {
217 let mut err: CFErrorRef = std::ptr::null_mut();
218 let res = unsafe {
219 LSCopyApplicationURLsForBundleIdentifier(bundle_identifier.as_concrete_TypeRef(), &mut err)
220 };
221
222 if res.is_null() {
223 Err(unsafe { TCFType::wrap_under_create_rule(err) })
224 } else {
225 Ok(unsafe { TCFType::wrap_under_create_rule(res) })
226 }
227}
228
229pub fn open_url(url: &CFURL) -> Result<CFURL, OSStatus> {
230 let mut res: CFURLRef = std::ptr::null_mut();
231 let err = unsafe { LSOpenCFURLRef(url.as_concrete_TypeRef(), &mut res) };
232
233 if err == 0 {
234 Ok(unsafe { TCFType::wrap_under_create_rule(res) })
235 } else {
236 Err(err)
237 }
238}
239
240pub fn open_from_url_spec(launch_spec: LSLaunchURLSpec) -> Result<CFURL, OSStatus> {
241 let mut res: CFURLRef = std::ptr::null_mut();
242 let err = unsafe { LSOpenFromURLSpec(&launch_spec.to_primitive(), &mut res) };
243
244 if err == 0 {
245 Ok(unsafe { TCFType::wrap_under_create_rule(res) })
246 } else {
247 Err(err)
248 }
249}
250
251pub fn register_url(url: &CFURL, update: bool) -> Result<(), OSStatus> {
252 let err = unsafe { LSRegisterURL(url.as_concrete_TypeRef(), update) };
253
254 if err == 0 {
255 Ok(())
256 } else {
257 Err(err)
258 }
259}
260
261pub fn role_handlers_for_content_type(
262 content_type: &CFString,
263 role: LSRolesMask,
264) -> Option<CFArray<CFString>> {
265 let res = unsafe {
266 LSCopyAllRoleHandlersForContentType(content_type.as_concrete_TypeRef(), role.bits())
267 };
268
269 if res.is_null() {
270 None
271 } else {
272 Some(unsafe { TCFType::wrap_under_create_rule(res) })
273 }
274}
275
276pub fn default_role_handler_for_content_type(
277 content_type: &CFString,
278 role: LSRolesMask,
279) -> Option<CFString> {
280 let res = unsafe {
281 LSCopyDefaultRoleHandlerForContentType(content_type.as_concrete_TypeRef(), role.bits())
282 };
283
284 if res.is_null() {
285 None
286 } else {
287 Some(unsafe { TCFType::wrap_under_create_rule(res) })
288 }
289}
290
291pub fn set_default_role_handler_for_content_type(
292 content_type: &CFString,
293 role: LSRolesMask,
294 bundle_id: &CFString,
295) -> Result<(), OSStatus> {
296 let err = unsafe {
297 LSSetDefaultRoleHandlerForContentType(
298 content_type.as_concrete_TypeRef(),
299 role.bits(),
300 bundle_id.as_concrete_TypeRef(),
301 )
302 };
303
304 if err == 0 {
305 Ok(())
306 } else {
307 Err(err)
308 }
309}
310
311pub fn set_default_handle_for_url_scheme(
312 url_scheme: &CFString,
313 bundle_id: &CFString,
314) -> Result<(), OSStatus> {
315 let err = unsafe {
316 LSSetDefaultHandlerForURLScheme(
317 url_scheme.as_concrete_TypeRef(),
318 bundle_id.as_concrete_TypeRef(),
319 )
320 };
321
322 if err == 0 {
323 Ok(())
324 } else {
325 Err(err)
326 }
327}
328
329#[cfg(test)]
330mod tests {
331 use super::{
332 default_application_url_for_url, open_from_url_spec, LSLaunchFlags, LSLaunchURLSpec,
333 LSRolesMask,
334 };
335 use core_foundation::array::CFArray;
336 use core_foundation::base::TCFType;
337 use core_foundation::string::{CFString, CFStringRef};
338 use core_foundation::url::{CFURLRef, CFURL};
339 use core_foundation_sys::base::{kCFAllocatorDefault, CFAllocatorRef};
340
341 #[link(name = "CoreServices", kind = "framework")]
342 extern "C" {
343 fn CFURLCreateWithString(
344 allocator: CFAllocatorRef,
345 urlString: CFStringRef,
346 baseURL: CFURLRef,
347 ) -> CFURLRef;
348 }
349
350 fn url(url: &str) -> Option<CFURL> {
351 let url = CFString::new(url);
352 let ptr = unsafe {
353 CFURLCreateWithString(
354 kCFAllocatorDefault,
355 url.as_concrete_TypeRef(),
356 ::std::ptr::null(),
357 )
358 };
359
360 if ptr.is_null() {
361 None
362 } else {
363 Some(unsafe { CFURL::wrap_under_create_rule(ptr) })
364 }
365 }
366
367 fn it_works() {
369 println!(
370 "{:#?}",
371 default_application_url_for_url(
372 &url("http://www.google.com/").unwrap(),
373 LSRolesMask::VIEWER
374 )
375 );
376 println!(
377 "{:#?}",
378 default_application_url_for_url(
379 &url("fail://a.big.fail").unwrap(),
380 LSRolesMask::VIEWER
381 )
382 );
383 }
384
385 #[test]
386 fn open_with_spec() {
387 let scheme = url("https://").unwrap();
388 let app = default_application_url_for_url(&scheme, LSRolesMask::VIEWER).unwrap();
389 let urls = vec![
390 url("https://news.ycombinator.com/").unwrap(),
391 url("https://www.google.com/").unwrap(),
392 ];
393 let urls = CFArray::<CFURL>::from_CFTypes(&urls[..]);
394 println!("{:#?}", app);
395 let spec = LSLaunchURLSpec {
396 app: Some(app),
397 urls: Some(urls),
398 flags: LSLaunchFlags::DEFAULTS | LSLaunchFlags::ASYNC,
399 ..Default::default()
400 };
401 open_from_url_spec(spec).unwrap();
402 }
403}