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 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"
28 #import <CoreFoundation/CoreFoundation.h>
29 #import <Foundation/Foundation.h>
30 #import <ApplicationServices/ApplicationServices.h>
35 * @short_description: Application information from NSBundles
36 * @include: gio/gosxappinfo.h
38 * #GOsxAppInfo is an implementation of #GAppInfo based on NSBundle information.
40 * Note that `<gio/gosxappinfo.h>` is unique to OSX.
43 static void g_osx_app_info_iface_init (GAppInfoIface
*iface
);
44 static const char *g_osx_app_info_get_id (GAppInfo
*appinfo
);
49 * Information about an installed application from a NSBundle.
53 GObject parent_instance
;
57 /* Note that these are all NULL until first call
58 * to getter at which point they are cached here
67 G_DEFINE_TYPE_WITH_CODE (GOsxAppInfo
, g_osx_app_info
, G_TYPE_OBJECT
,
68 G_IMPLEMENT_INTERFACE (G_TYPE_APP_INFO
, g_osx_app_info_iface_init
))
71 g_osx_app_info_new (NSBundle
*bundle
)
73 GOsxAppInfo
*info
= g_object_new (G_TYPE_OSX_APP_INFO
, NULL
);
75 info
->bundle
= [bundle retain
];
81 g_osx_app_info_init (GOsxAppInfo
*info
)
86 g_osx_app_info_finalize (GObject
*object
)
88 GOsxAppInfo
*info
= G_OSX_APP_INFO (object
);
92 g_free (info
->executable
);
93 g_free (info
->filename
);
94 g_clear_object (&info
->icon
);
96 [info
->bundle release
];
98 G_OBJECT_CLASS (g_osx_app_info_parent_class
)->finalize (object
);
102 g_osx_app_info_class_init (GOsxAppInfoClass
*klass
)
104 GObjectClass
*gobject_class
= G_OBJECT_CLASS (klass
);
106 gobject_class
->finalize
= g_osx_app_info_finalize
;
110 g_osx_app_info_dup (GAppInfo
*appinfo
)
113 GOsxAppInfo
*new_info
;
115 g_return_val_if_fail (appinfo
!= NULL
, NULL
);
117 info
= G_OSX_APP_INFO (appinfo
);
118 new_info
= g_osx_app_info_new ([info
->bundle retain
]);
120 return G_APP_INFO (new_info
);
124 g_osx_app_info_equal (GAppInfo
*appinfo1
,
127 const gchar
*str1
, *str2
;
129 g_return_val_if_fail (appinfo1
!= NULL
, FALSE
);
130 g_return_val_if_fail (appinfo2
!= NULL
, FALSE
);
132 str1
= g_osx_app_info_get_id (appinfo1
);
133 str2
= g_osx_app_info_get_id (appinfo2
);
135 return (g_strcmp0 (str1
, str2
) == 0);
139 * get_bundle_string_value:
140 * @bundle: a #NSBundle
141 * @key: an #NSString key
143 * Returns a value from a bundles info.plist file.
144 * It will be utf8 encoded and it must be g_free()'d.
148 get_bundle_string_value (NSBundle
*bundle
,
155 g_return_val_if_fail (bundle
!= NULL
, NULL
);
157 value
= (NSString
*)[bundle objectForInfoDictionaryKey
: key
];
161 cvalue
= [value cStringUsingEncoding
: NSUTF8StringEncoding
];
162 ret
= g_strdup (cvalue
);
168 create_cfstring_from_cstr (const gchar
*cstr
)
170 return CFStringCreateWithCString (NULL
, cstr
, kCFStringEncodingUTF8
);
174 create_cstr_from_cfstring (CFStringRef str
)
181 cstr
= CFStringGetCStringPtr (str
, kCFStringEncodingUTF8
);
184 return g_strdup (cstr
);
188 url_escape_hostname (const char *url
)
190 char *host_start
, *ret
;
192 host_start
= strstr (url
, "://");
193 if (host_start
!= NULL
)
195 char *host_end
, *scheme
, *host
, *hostname
;
197 scheme
= g_strndup (url
, host_start
- url
);
199 host_end
= strchr (host_start
, '/');
201 if (host_end
!= NULL
)
202 host
= g_strndup (host_start
, host_end
- host_start
);
204 host
= g_strdup (host_start
);
206 hostname
= g_hostname_to_ascii (host
);
208 ret
= g_strconcat (scheme
, "://", hostname
, host_end
, NULL
);
217 return g_strdup (url
);
221 create_url_from_cstr (gchar
*cstr
,
228 puny_cstr
= url_escape_hostname (cstr
);
229 str
= CFStringCreateWithCString (NULL
, puny_cstr
? puny_cstr
: cstr
, kCFStringEncodingUTF8
);
232 url
= CFURLCreateWithFileSystemPath (NULL
, str
, kCFURLPOSIXPathStyle
, FALSE
);
234 url
= CFURLCreateWithString (NULL
, str
, NULL
);
237 g_debug ("Creating CFURL from %s %s failed!", cstr
, is_file
? "file" : "uri");
245 create_url_list_from_glist (GList
*uris
,
249 int len
= g_list_length (uris
);
250 CFMutableArrayRef array
;
255 array
= CFArrayCreateMutable (NULL
, len
, &kCFTypeArrayCallBacks
);
259 for (lst
= uris
; lst
!= NULL
&& lst
->data
; lst
= lst
->next
)
261 CFURLRef url
= create_url_from_cstr ((char*)lst
->data
, are_files
);
263 CFArrayAppendValue (array
, url
);
266 return (CFArrayRef
)array
;
269 static LSLaunchURLSpec
*
270 create_urlspec_for_appinfo (GOsxAppInfo
*info
,
274 LSLaunchURLSpec
*urlspec
= g_new0 (LSLaunchURLSpec
, 1);
275 gchar
*app_cstr
= g_osx_app_info_get_filename (info
);
277 /* Strip file:// from app url but ensure filesystem url */
278 urlspec
->appURL
= create_url_from_cstr (app_cstr
+ 7, TRUE
);
279 urlspec
->launchFlags
= kLSLaunchDefaults
;
280 urlspec
->itemURLs
= create_url_list_from_glist (uris
, are_files
);
286 free_urlspec (LSLaunchURLSpec
*urlspec
)
288 if (urlspec
->itemURLs
)
290 CFArrayRemoveAllValues ((CFMutableArrayRef
)urlspec
->itemURLs
);
291 CFRelease (urlspec
->itemURLs
);
293 CFRelease (urlspec
->appURL
);
298 get_bundle_for_id (CFStringRef bundle_id
)
303 #ifdef AVAILABLE_MAC_OS_VERSION_10_10_OR_LATER
304 CSArrayRef urls
= LSCopyApplicationURLsForBundleIdentifier (bundle_id
, NULL
);
307 /* TODO: if there's multiple, we should perhaps prefer one thats in $HOME,
308 * instead of just always picking the first.
310 app_url
= CFArrayGetValueAtIndex (urls
, 0);
316 if (LSFindApplicationForInfo (kLSUnknownCreator
, bundle_id
, NULL
, NULL
, &app_url
))
319 #ifdef G_ENABLE_DEBUG /* This can fail often, no reason to alloc strings */
320 gchar
*id_str
= create_cstr_from_cfstring (bundle_id
);
321 g_debug ("Application not found for id \"%s\".", id_str
);
327 bundle
= [NSBundle bundleWithURL
: (NSURL
*)app_url
];
332 g_debug ("Bundle not found for url.");
340 g_osx_app_info_get_id (GAppInfo
*appinfo
)
342 GOsxAppInfo
*info
= G_OSX_APP_INFO (appinfo
);
345 info
->id
= get_bundle_string_value (info
->bundle
, @
"CFBundleIdentifier");
351 g_osx_app_info_get_name (GAppInfo
*appinfo
)
353 GOsxAppInfo
*info
= G_OSX_APP_INFO (appinfo
);
356 info
->name
= get_bundle_string_value (info
->bundle
, @
"CFBundleName");
362 g_osx_app_info_get_display_name (GAppInfo
*appinfo
)
364 return g_osx_app_info_get_name (appinfo
);
368 g_osx_app_info_get_description (GAppInfo
*appinfo
)
370 /* Bundles do not contain descriptions */
375 g_osx_app_info_get_executable (GAppInfo
*appinfo
)
377 GOsxAppInfo
*info
= G_OSX_APP_INFO (appinfo
);
379 if (!info
->executable
)
380 info
->executable
= get_bundle_string_value (info
->bundle
, @
"CFBundleExecutable");
382 return info
->executable
;
386 g_osx_app_info_get_filename (GOsxAppInfo
*info
)
388 g_return_val_if_fail (info
!= NULL
, NULL
);
392 info
->filename
= g_strconcat ("file://", [[info
->bundle bundlePath
]
393 cStringUsingEncoding
: NSUTF8StringEncoding
],
397 return info
->filename
;
401 g_osx_app_info_get_commandline (GAppInfo
*appinfo
)
403 /* There isn't really a command line value */
408 g_osx_app_info_get_icon (GAppInfo
*appinfo
)
410 GOsxAppInfo
*info
= G_OSX_APP_INFO (appinfo
);
414 gchar
*icon_name
, *app_uri
, *icon_uri
;
417 icon_name
= get_bundle_string_value (info
->bundle
, @
"CFBundleIconFile");
421 app_uri
= g_osx_app_info_get_filename (info
);
422 icon_uri
= g_strconcat (app_uri
+ 7, "/Contents/Resources/", icon_name
,
423 g_str_has_suffix (icon_name
, ".icns") ? NULL
: ".icns", NULL
);
426 file
= g_file_new_for_path (icon_uri
);
427 info
->icon
= g_file_icon_new (file
);
428 g_object_unref (file
);
436 g_osx_app_info_launch_internal (GAppInfo
*appinfo
,
441 GOsxAppInfo
*info
= G_OSX_APP_INFO (appinfo
);
442 LSLaunchURLSpec
*urlspec
= create_urlspec_for_appinfo (info
, uris
, are_files
);
443 gint ret
, success
= TRUE
;
445 if ((ret
= LSOpenFromURLSpec (urlspec
, NULL
)))
447 /* TODO: Better error codes */
448 g_set_error (error
, G_IO_ERR
, G_IO_ERROR_FAILED
,
449 "Opening application failed with code %d", ret
);
453 free_urlspec (urlspec
);
458 g_osx_app_info_supports_uris (GAppInfo
*appinfo
)
464 g_osx_app_info_supports_files (GAppInfo
*appinfo
)
470 g_osx_app_info_launch (GAppInfo
*appinfo
,
472 GAppLaunchContext
*launch_context
,
475 return g_osx_app_info_launch_internal (appinfo
, files
, TRUE
, error
);
479 g_osx_app_info_launch_uris (GAppInfo
*appinfo
,
481 GAppLaunchContext
*launch_context
,
484 return g_osx_app_info_launch_internal (appinfo
, uris
, FALSE
, error
);
488 g_osx_app_info_should_show (GAppInfo
*appinfo
)
490 /* Bundles don't have hidden attribute */
495 g_osx_app_info_set_as_default_for_type (GAppInfo
*appinfo
,
496 const char *content_type
,
503 g_osx_app_info_get_supported_types (GAppInfo
*appinfo
)
505 /* TODO: get CFBundleDocumentTypes */
510 g_osx_app_info_set_as_last_used_for_type (GAppInfo
*appinfo
,
511 const char *content_type
,
519 g_osx_app_info_can_delete (GAppInfo
*appinfo
)
525 g_osx_app_info_iface_init (GAppInfoIface
*iface
)
527 iface
->dup
= g_osx_app_info_dup
;
528 iface
->equal
= g_osx_app_info_equal
;
530 iface
->get_id
= g_osx_app_info_get_id
;
531 iface
->get_name
= g_osx_app_info_get_name
;
532 iface
->get_display_name
= g_osx_app_info_get_display_name
;
533 iface
->get_description
= g_osx_app_info_get_description
;
534 iface
->get_executable
= g_osx_app_info_get_executable
;
535 iface
->get_commandline
= g_osx_app_info_get_commandline
;
536 iface
->get_icon
= g_osx_app_info_get_icon
;
537 iface
->get_supported_types
= g_osx_app_info_get_supported_types
;
539 iface
->set_as_last_used_for_type
= g_osx_app_info_set_as_last_used_for_type
;
540 iface
->set_as_default_for_type
= g_osx_app_info_set_as_default_for_type
;
542 iface
->launch
= g_osx_app_info_launch
;
543 iface
->launch_uris
= g_osx_app_info_launch_uris
;
545 iface
->supports_uris
= g_osx_app_info_supports_uris
;
546 iface
->supports_files
= g_osx_app_info_supports_files
;
547 iface
->should_show
= g_osx_app_info_should_show
;
548 iface
->can_delete
= g_osx_app_info_can_delete
;
552 g_app_info_create_from_commandline (const char *commandline
,
553 const char *application_name
,
554 GAppInfoCreateFlags flags
,
561 g_osx_app_info_get_all_for_scheme (const char *cscheme
)
563 CFArrayRef bundle_list
;
566 GList
*info_list
= NULL
;
569 scheme
= create_cfstring_from_cstr (cscheme
);
570 bundle_list
= LSCopyAllHandlersForURLScheme (scheme
);
576 for (i
= 0; i
< CFArrayGetCount (bundle_list
); i
++)
578 CFStringRef bundle_id
= CFArrayGetValueAtIndex (bundle_list
, i
);
581 bundle
= get_bundle_for_id (bundle_id
);
586 info
= G_APP_INFO (g_osx_app_info_new (bundle
));
587 info_list
= g_list_append (info_list
, info
);
594 g_app_info_get_all_for_type (const char *content_type
)
597 CFArrayRef bundle_list
;
600 GList
*info_list
= NULL
;
603 if (g_str_has_prefix (content_type
, "x-scheme-handler/"))
605 gchar
*scheme
= strchr (content_type
, '/') + 1;
607 return g_osx_app_info_get_all_for_scheme (scheme
);
610 type_cstr
= g_content_type_from_mime_type (content_type
);
611 type
= create_cfstring_from_cstr (type_cstr
);
614 bundle_list
= LSCopyAllRoleHandlersForContentType (type
, kLSRolesAll
);
620 for (i
= 0; i
< CFArrayGetCount (bundle_list
); i
++)
622 CFStringRef bundle_id
= CFArrayGetValueAtIndex (bundle_list
, i
);
625 bundle
= get_bundle_for_id (bundle_id
);
630 info
= G_APP_INFO (g_osx_app_info_new (bundle
));
631 info_list
= g_list_append (info_list
, info
);
638 g_app_info_get_recommended_for_type (const char *content_type
)
640 return g_app_info_get_all_for_type (content_type
);
644 g_app_info_get_fallback_for_type (const char *content_type
)
646 return g_app_info_get_all_for_type (content_type
);
650 g_app_info_get_default_for_type (const char *content_type
,
651 gboolean must_support_uris
)
654 CFStringRef type
, bundle_id
;
657 type_cstr
= g_content_type_from_mime_type (content_type
);
658 type
= create_cfstring_from_cstr (type_cstr
);
661 bundle_id
= LSCopyDefaultRoleHandlerForContentType (type
, kLSRolesAll
);
666 g_warning ("No default handler found for mimetype '%s'.", content_type
);
670 bundle
= get_bundle_for_id (bundle_id
);
671 CFRelease (bundle_id
);
676 return G_APP_INFO (g_osx_app_info_new (bundle
));
680 g_app_info_get_default_for_uri_scheme (const char *uri_scheme
)
682 CFStringRef scheme
, bundle_id
;
685 scheme
= create_cfstring_from_cstr (uri_scheme
);
686 bundle_id
= LSCopyDefaultHandlerForURLScheme (scheme
);
691 g_warning ("No default handler found for url scheme '%s'.", uri_scheme
);
695 bundle
= get_bundle_for_id (bundle_id
);
696 CFRelease (bundle_id
);
701 return G_APP_INFO (g_osx_app_info_new (bundle
));
705 g_app_info_get_all (void)
707 /* There is no API for this afaict
708 * could manually do it...
714 g_app_info_reset_type_associations (const char *content_type
)