launch_services/
lib.rs

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    // #[test]
368    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}