1 /* GIO - GLib Input, Output and Streaming Library
3 * Copyright (C) 2014 Patrick Griffis
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General
16 * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
23 #include "gosxappinfo.h"
24 #include "gcontenttype.h"
26 #include "gfileicon.h"
29 #import <CoreFoundation/CoreFoundation.h>
30 #import <Foundation/Foundation.h>
31 #import <ApplicationServices/ApplicationServices.h>
36 * @short_description: Application information from NSBundles
37 * @include: gio/gosxappinfo.h
39 * #GOsxAppInfo is an implementation of #GAppInfo based on NSBundle information.
41 * Note that `<gio/gosxappinfo.h>` is unique to OSX.
44 static void g_osx_app_info_iface_init (GAppInfoIface
*iface
);
45 static const char *g_osx_app_info_get_id (GAppInfo
*appinfo
);
50 * Information about an installed application from a NSBundle.
54 GObject parent_instance
;
58 /* Note that these are all NULL until first call
59 * to getter at which point they are cached here
68 G_DEFINE_TYPE_WITH_CODE (GOsxAppInfo
, g_osx_app_info
, G_TYPE_OBJECT
,
69 G_IMPLEMENT_INTERFACE (G_TYPE_APP_INFO
, g_osx_app_info_iface_init
))
72 g_osx_app_info_new (NSBundle
*bundle
)
74 GOsxAppInfo
*info
= g_object_new (G_TYPE_OSX_APP_INFO
, NULL
);
76 info
->bundle
= [bundle retain
];
82 g_osx_app_info_init (GOsxAppInfo
*info
)
87 g_osx_app_info_finalize (GObject
*object
)
89 GOsxAppInfo
*info
= G_OSX_APP_INFO (object
);
93 g_free (info
->executable
);
94 g_free (info
->filename
);
95 g_clear_object (&info
->icon
);
97 [info
->bundle release
];
99 G_OBJECT_CLASS (g_osx_app_info_parent_class
)->finalize (object
);
103 g_osx_app_info_class_init (GOsxAppInfoClass
*klass
)
105 GObjectClass
*gobject_class
= G_OBJECT_CLASS (klass
);
107 gobject_class
->finalize
= g_osx_app_info_finalize
;
111 g_osx_app_info_dup (GAppInfo
*appinfo
)
114 GOsxAppInfo
*new_info
;
116 g_return_val_if_fail (appinfo
!= NULL
, NULL
);
118 info
= G_OSX_APP_INFO (appinfo
);
119 new_info
= g_osx_app_info_new ([info
->bundle retain
]);
121 return G_APP_INFO (new_info
);
125 g_osx_app_info_equal (GAppInfo
*appinfo1
,
128 const gchar
*str1
, *str2
;
130 g_return_val_if_fail (appinfo1
!= NULL
, FALSE
);
131 g_return_val_if_fail (appinfo2
!= NULL
, FALSE
);
133 str1
= g_osx_app_info_get_id (appinfo1
);
134 str2
= g_osx_app_info_get_id (appinfo2
);
136 return (g_strcmp0 (str1
, str2
) == 0);
140 * get_bundle_string_value:
141 * @bundle: a #NSBundle
142 * @key: an #NSString key
144 * Returns a value from a bundles info.plist file.
145 * It will be utf8 encoded and it must be g_free()'d.
149 get_bundle_string_value (NSBundle
*bundle
,
156 g_return_val_if_fail (bundle
!= NULL
, NULL
);
158 value
= (NSString
*)[bundle objectForInfoDictionaryKey
: key
];
162 cvalue
= [value cStringUsingEncoding
: NSUTF8StringEncoding
];
163 ret
= g_strdup (cvalue
);
169 create_cfstring_from_cstr (const gchar
*cstr
)
171 return CFStringCreateWithCString (NULL
, cstr
, kCFStringEncodingUTF8
);
175 create_cstr_from_cfstring (CFStringRef str
)
182 cstr
= CFStringGetCStringPtr (str
, kCFStringEncodingUTF8
);
185 return g_strdup (cstr
);
189 url_escape_hostname (const char *url
)
191 char *host_start
, *ret
;
193 host_start
= strstr (url
, "://");
194 if (host_start
!= NULL
)
196 char *host_end
, *scheme
, *host
, *hostname
;
198 scheme
= g_strndup (url
, host_start
- url
);
200 host_end
= strchr (host_start
, '/');
202 if (host_end
!= NULL
)
203 host
= g_strndup (host_start
, host_end
- host_start
);
205 host
= g_strdup (host_start
);
207 hostname
= g_hostname_to_ascii (host
);
209 ret
= g_strconcat (scheme
, "://", hostname
, host_end
, NULL
);
218 return g_strdup (url
);
222 create_url_from_cstr (gchar
*cstr
,
229 puny_cstr
= url_escape_hostname (cstr
);
230 str
= CFStringCreateWithCString (NULL
, puny_cstr
? puny_cstr
: cstr
, kCFStringEncodingUTF8
);
233 url
= CFURLCreateWithFileSystemPath (NULL
, str
, kCFURLPOSIXPathStyle
, FALSE
);
235 url
= CFURLCreateWithString (NULL
, str
, NULL
);
238 g_debug ("Creating CFURL from %s %s failed!", cstr
, is_file
? "file" : "uri");
246 create_url_list_from_glist (GList
*uris
,
250 int len
= g_list_length (uris
);
251 CFMutableArrayRef array
;
256 array
= CFArrayCreateMutable (NULL
, len
, &kCFTypeArrayCallBacks
);
260 for (lst
= uris
; lst
!= NULL
&& lst
->data
; lst
= lst
->next
)
262 CFURLRef url
= create_url_from_cstr ((char*)lst
->data
, are_files
);
264 CFArrayAppendValue (array
, url
);
267 return (CFArrayRef
)array
;
270 static LSLaunchURLSpec
*
271 create_urlspec_for_appinfo (GOsxAppInfo
*info
,
275 LSLaunchURLSpec
*urlspec
= g_new0 (LSLaunchURLSpec
, 1);
276 gchar
*app_cstr
= g_osx_app_info_get_filename (info
);
278 /* Strip file:// from app url but ensure filesystem url */
279 urlspec
->appURL
= create_url_from_cstr (app_cstr
+ 7, TRUE
);
280 urlspec
->launchFlags
= kLSLaunchDefaults
;
281 urlspec
->itemURLs
= create_url_list_from_glist (uris
, are_files
);
287 free_urlspec (LSLaunchURLSpec
*urlspec
)
289 if (urlspec
->itemURLs
)
291 CFArrayRemoveAllValues ((CFMutableArrayRef
)urlspec
->itemURLs
);
292 CFRelease (urlspec
->itemURLs
);
294 CFRelease (urlspec
->appURL
);
299 get_bundle_for_url (CFURLRef app_url
)
301 NSBundle
*bundle
= [NSBundle bundleWithURL
: (NSURL
*)app_url
];
305 g_debug ("Bundle not found for url.");
313 get_bundle_for_id (CFStringRef bundle_id
)
318 #ifdef AVAILABLE_MAC_OS_X_VERSION_10_10_AND_LATER
319 CFArrayRef urls
= LSCopyApplicationURLsForBundleIdentifier (bundle_id
, NULL
);
322 /* TODO: if there's multiple, we should perhaps prefer one thats in $HOME,
323 * instead of just always picking the first.
325 app_url
= CFArrayGetValueAtIndex (urls
, 0);
331 if (LSFindApplicationForInfo (kLSUnknownCreator
, bundle_id
, NULL
, NULL
, &app_url
))
334 #ifdef G_ENABLE_DEBUG /* This can fail often, no reason to alloc strings */
335 gchar
*id_str
= create_cstr_from_cfstring (bundle_id
);
336 g_debug ("Application not found for id \"%s\".", id_str
);
342 bundle
= get_bundle_for_url (app_url
);
348 g_osx_app_info_get_id (GAppInfo
*appinfo
)
350 GOsxAppInfo
*info
= G_OSX_APP_INFO (appinfo
);
353 info
->id
= get_bundle_string_value (info
->bundle
, @
"CFBundleIdentifier");
359 g_osx_app_info_get_name (GAppInfo
*appinfo
)
361 GOsxAppInfo
*info
= G_OSX_APP_INFO (appinfo
);
364 info
->name
= get_bundle_string_value (info
->bundle
, @
"CFBundleName");
370 g_osx_app_info_get_display_name (GAppInfo
*appinfo
)
372 return g_osx_app_info_get_name (appinfo
);
376 g_osx_app_info_get_description (GAppInfo
*appinfo
)
378 /* Bundles do not contain descriptions */
383 g_osx_app_info_get_executable (GAppInfo
*appinfo
)
385 GOsxAppInfo
*info
= G_OSX_APP_INFO (appinfo
);
387 if (!info
->executable
)
388 info
->executable
= get_bundle_string_value (info
->bundle
, @
"CFBundleExecutable");
390 return info
->executable
;
394 g_osx_app_info_get_filename (GOsxAppInfo
*info
)
396 g_return_val_if_fail (info
!= NULL
, NULL
);
400 info
->filename
= g_strconcat ("file://", [[info
->bundle bundlePath
]
401 cStringUsingEncoding
: NSUTF8StringEncoding
],
405 return info
->filename
;
409 g_osx_app_info_get_commandline (GAppInfo
*appinfo
)
411 /* There isn't really a command line value */
416 g_osx_app_info_get_icon (GAppInfo
*appinfo
)
418 GOsxAppInfo
*info
= G_OSX_APP_INFO (appinfo
);
422 gchar
*icon_name
, *app_uri
, *icon_uri
;
425 icon_name
= get_bundle_string_value (info
->bundle
, @
"CFBundleIconFile");
429 app_uri
= g_osx_app_info_get_filename (info
);
430 icon_uri
= g_strconcat (app_uri
+ 7, "/Contents/Resources/", icon_name
,
431 g_str_has_suffix (icon_name
, ".icns") ? NULL
: ".icns", NULL
);
434 file
= g_file_new_for_path (icon_uri
);
435 info
->icon
= g_file_icon_new (file
);
436 g_object_unref (file
);
444 g_osx_app_info_launch_internal (GAppInfo
*appinfo
,
449 GOsxAppInfo
*info
= G_OSX_APP_INFO (appinfo
);
450 LSLaunchURLSpec
*urlspec
= create_urlspec_for_appinfo (info
, uris
, are_files
);
451 gint ret
, success
= TRUE
;
453 if ((ret
= LSOpenFromURLSpec (urlspec
, NULL
)))
455 /* TODO: Better error codes */
456 g_set_error (error
, G_IO_ERROR
, G_IO_ERROR_FAILED
,
457 "Opening application failed with code %d", ret
);
461 free_urlspec (urlspec
);
466 g_osx_app_info_supports_uris (GAppInfo
*appinfo
)
472 g_osx_app_info_supports_files (GAppInfo
*appinfo
)
478 g_osx_app_info_launch (GAppInfo
*appinfo
,
480 GAppLaunchContext
*launch_context
,
483 return g_osx_app_info_launch_internal (appinfo
, files
, TRUE
, error
);
487 g_osx_app_info_launch_uris (GAppInfo
*appinfo
,
489 GAppLaunchContext
*launch_context
,
492 return g_osx_app_info_launch_internal (appinfo
, uris
, FALSE
, error
);
496 g_osx_app_info_should_show (GAppInfo
*appinfo
)
498 /* Bundles don't have hidden attribute */
503 g_osx_app_info_set_as_default_for_type (GAppInfo
*appinfo
,
504 const char *content_type
,
511 g_osx_app_info_get_supported_types (GAppInfo
*appinfo
)
513 /* TODO: get CFBundleDocumentTypes */
518 g_osx_app_info_set_as_last_used_for_type (GAppInfo
*appinfo
,
519 const char *content_type
,
527 g_osx_app_info_can_delete (GAppInfo
*appinfo
)
533 g_osx_app_info_iface_init (GAppInfoIface
*iface
)
535 iface
->dup
= g_osx_app_info_dup
;
536 iface
->equal
= g_osx_app_info_equal
;
538 iface
->get_id
= g_osx_app_info_get_id
;
539 iface
->get_name
= g_osx_app_info_get_name
;
540 iface
->get_display_name
= g_osx_app_info_get_display_name
;
541 iface
->get_description
= g_osx_app_info_get_description
;
542 iface
->get_executable
= g_osx_app_info_get_executable
;
543 iface
->get_commandline
= g_osx_app_info_get_commandline
;
544 iface
->get_icon
= g_osx_app_info_get_icon
;
545 iface
->get_supported_types
= g_osx_app_info_get_supported_types
;
547 iface
->set_as_last_used_for_type
= g_osx_app_info_set_as_last_used_for_type
;
548 iface
->set_as_default_for_type
= g_osx_app_info_set_as_default_for_type
;
550 iface
->launch
= g_osx_app_info_launch
;
551 iface
->launch_uris
= g_osx_app_info_launch_uris
;
553 iface
->supports_uris
= g_osx_app_info_supports_uris
;
554 iface
->supports_files
= g_osx_app_info_supports_files
;
555 iface
->should_show
= g_osx_app_info_should_show
;
556 iface
->can_delete
= g_osx_app_info_can_delete
;
560 g_app_info_create_from_commandline (const char *commandline
,
561 const char *application_name
,
562 GAppInfoCreateFlags flags
,
569 g_osx_app_info_get_all_for_scheme (const char *cscheme
)
571 CFArrayRef bundle_list
;
574 GList
*info_list
= NULL
;
577 scheme
= create_cfstring_from_cstr (cscheme
);
578 bundle_list
= LSCopyAllHandlersForURLScheme (scheme
);
584 for (i
= 0; i
< CFArrayGetCount (bundle_list
); i
++)
586 CFStringRef bundle_id
= CFArrayGetValueAtIndex (bundle_list
, i
);
589 bundle
= get_bundle_for_id (bundle_id
);
594 info
= G_APP_INFO (g_osx_app_info_new (bundle
));
595 info_list
= g_list_append (info_list
, info
);
602 g_app_info_get_all_for_type (const char *content_type
)
605 CFArrayRef bundle_list
;
608 GList
*info_list
= NULL
;
611 mime_type
= g_content_type_get_mime_type (content_type
);
612 if (g_str_has_prefix (mime_type
, "x-scheme-handler/"))
614 gchar
*scheme
= strchr (mime_type
, '/') + 1;
615 GList
*ret
= g_osx_app_info_get_all_for_scheme (scheme
);
622 type
= create_cfstring_from_cstr (content_type
);
623 bundle_list
= LSCopyAllRoleHandlersForContentType (type
, kLSRolesAll
);
629 for (i
= 0; i
< CFArrayGetCount (bundle_list
); i
++)
631 CFStringRef bundle_id
= CFArrayGetValueAtIndex (bundle_list
, i
);
634 bundle
= get_bundle_for_id (bundle_id
);
639 info
= G_APP_INFO (g_osx_app_info_new (bundle
));
640 info_list
= g_list_append (info_list
, info
);
647 g_app_info_get_recommended_for_type (const char *content_type
)
649 return g_app_info_get_all_for_type (content_type
);
653 g_app_info_get_fallback_for_type (const char *content_type
)
655 return g_app_info_get_all_for_type (content_type
);
659 g_app_info_get_default_for_type (const char *content_type
,
660 gboolean must_support_uris
)
665 #ifdef AVAILABLE_MAC_OS_X_VERSION_10_10_AND_LATER
668 CFStringRef bundle_id
;
671 mime_type
= g_content_type_get_mime_type (content_type
);
672 if (g_str_has_prefix (mime_type
, "x-scheme-handler/"))
674 gchar
*scheme
= strchr (mime_type
, '/') + 1;
675 GAppInfo
*ret
= g_app_info_get_default_for_uri_scheme (scheme
);
682 type
= create_cfstring_from_cstr (content_type
);
684 #ifdef AVAILABLE_MAC_OS_X_VERSION_10_10_AND_LATER
685 bundle_id
= LSCopyDefaultApplicationURLForContentType (type
, kLSRolesAll
, NULL
);
687 bundle_id
= LSCopyDefaultRoleHandlerForContentType (type
, kLSRolesAll
);
693 g_warning ("No default handler found for content type '%s'.", content_type
);
697 #ifdef AVAILABLE_MAC_OS_X_VERSION_10_10_AND_LATER
698 bundle
= get_bundle_for_url (bundle_id
);
700 bundle
= get_bundle_for_id (bundle_id
);
702 CFRelease (bundle_id
);
707 return G_APP_INFO (g_osx_app_info_new (bundle
));
711 g_app_info_get_default_for_uri_scheme (const char *uri_scheme
)
713 CFStringRef scheme
, bundle_id
;
716 scheme
= create_cfstring_from_cstr (uri_scheme
);
717 bundle_id
= LSCopyDefaultHandlerForURLScheme (scheme
);
722 g_warning ("No default handler found for url scheme '%s'.", uri_scheme
);
726 bundle
= get_bundle_for_id (bundle_id
);
727 CFRelease (bundle_id
);
732 return G_APP_INFO (g_osx_app_info_new (bundle
));
736 g_app_info_get_all (void)
738 /* There is no API for this afaict
739 * could manually do it...
745 g_app_info_reset_type_associations (const char *content_type
)