glib/gio/gosxappinfo.m
Philip Withnall 00bfb3ab44 tree: Fix various typos and outdated terminology
This was mostly machine generated with the following command:
```
codespell \
    --builtin clear,rare,usage \
    --skip './po/*' --skip './.git/*' --skip './NEWS*' \
    --write-changes .
```
using the latest git version of `codespell` as per [these
instructions](https://github.com/codespell-project/codespell#user-content-updating).

Then I manually checked each change using `git add -p`, made a few
manual fixups and dropped a load of incorrect changes.

There are still some outdated or loaded terms used in GLib, mostly to do
with git branch terminology. They will need to be changed later as part
of a wider migration of git terminology.

If I’ve missed anything, please file an issue!

Signed-off-by: Philip Withnall <withnall@endlessm.com>
2020-06-12 15:01:08 +01:00

760 lines
18 KiB
Objective-C

/* GIO - GLib Input, Output and Streaming Library
*
* Copyright (C) 2014 Patrick Griffis
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "gappinfo.h"
#include "gosxappinfo.h"
#include "gcontenttype.h"
#include "gfile.h"
#include "gfileicon.h"
#include "gioerror.h"
#import <CoreFoundation/CoreFoundation.h>
#import <Foundation/Foundation.h>
#import <ApplicationServices/ApplicationServices.h>
/**
* SECTION:gosxappinfo
* @title: GOsxAppInfo
* @short_description: Application information from NSBundles
* @include: gio/gosxappinfo.h
*
* #GOsxAppInfo is an implementation of #GAppInfo based on NSBundle information.
*
* Note that `<gio/gosxappinfo.h>` is unique to OSX.
*/
static void g_osx_app_info_iface_init (GAppInfoIface *iface);
static const char *g_osx_app_info_get_id (GAppInfo *appinfo);
/**
* GOsxAppInfo:
*
* Information about an installed application from a NSBundle.
*/
struct _GOsxAppInfo
{
GObject parent_instance;
NSBundle *bundle;
/* Note that these are all NULL until first call
* to getter at which point they are cached here
*/
gchar *id;
gchar *name;
gchar *executable;
gchar *filename;
GIcon *icon;
};
G_DEFINE_TYPE_WITH_CODE (GOsxAppInfo, g_osx_app_info, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (G_TYPE_APP_INFO, g_osx_app_info_iface_init))
static GOsxAppInfo *
g_osx_app_info_new (NSBundle *bundle)
{
GOsxAppInfo *info = g_object_new (G_TYPE_OSX_APP_INFO, NULL);
info->bundle = [bundle retain];
return info;
}
static void
g_osx_app_info_init (GOsxAppInfo *info)
{
}
static void
g_osx_app_info_finalize (GObject *object)
{
GOsxAppInfo *info = G_OSX_APP_INFO (object);
g_free (info->id);
g_free (info->name);
g_free (info->executable);
g_free (info->filename);
g_clear_object (&info->icon);
[info->bundle release];
G_OBJECT_CLASS (g_osx_app_info_parent_class)->finalize (object);
}
static void
g_osx_app_info_class_init (GOsxAppInfoClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->finalize = g_osx_app_info_finalize;
}
static GAppInfo *
g_osx_app_info_dup (GAppInfo *appinfo)
{
GOsxAppInfo *info;
GOsxAppInfo *new_info;
g_return_val_if_fail (appinfo != NULL, NULL);
info = G_OSX_APP_INFO (appinfo);
new_info = g_osx_app_info_new ([info->bundle retain]);
return G_APP_INFO (new_info);
}
static gboolean
g_osx_app_info_equal (GAppInfo *appinfo1,
GAppInfo *appinfo2)
{
const gchar *str1, *str2;
g_return_val_if_fail (appinfo1 != NULL, FALSE);
g_return_val_if_fail (appinfo2 != NULL, FALSE);
str1 = g_osx_app_info_get_id (appinfo1);
str2 = g_osx_app_info_get_id (appinfo2);
return (g_strcmp0 (str1, str2) == 0);
}
/*< internal >
* get_bundle_string_value:
* @bundle: a #NSBundle
* @key: an #NSString key
*
* Returns a value from a bundles info.plist file.
* It will be utf8 encoded and it must be g_free()'d.
*
*/
static gchar *
get_bundle_string_value (NSBundle *bundle,
NSString *key)
{
NSString *value;
const gchar *cvalue;
gchar *ret;
g_return_val_if_fail (bundle != NULL, NULL);
value = (NSString *)[bundle objectForInfoDictionaryKey: key];
if (!value)
return NULL;
cvalue = [value cStringUsingEncoding: NSUTF8StringEncoding];
ret = g_strdup (cvalue);
return ret;
}
static CFStringRef
create_cfstring_from_cstr (const gchar *cstr)
{
return CFStringCreateWithCString (NULL, cstr, kCFStringEncodingUTF8);
}
#ifdef G_ENABLE_DEBUG
static gchar *
create_cstr_from_cfstring (CFStringRef str)
{
g_return_val_if_fail (str != NULL, NULL);
CFIndex length = CFStringGetLength (str);
CFIndex maxlen = CFStringGetMaximumSizeForEncoding (length, kCFStringEncodingUTF8);
gchar *buffer = g_malloc (maxlen + 1);
Boolean success = CFStringGetCString (str, (char *) buffer, maxlen,
kCFStringEncodingUTF8);
if (success)
return buffer;
else
{
g_free (buffer);
return NULL;
}
}
#endif
static char *
url_escape_hostname (const char *url)
{
char *host_start, *ret;
host_start = strstr (url, "://");
if (host_start != NULL)
{
char *host_end, *scheme, *host, *hostname;
scheme = g_strndup (url, host_start - url);
host_start += 3;
host_end = strchr (host_start, '/');
if (host_end != NULL)
host = g_strndup (host_start, host_end - host_start);
else
host = g_strdup (host_start);
hostname = g_hostname_to_ascii (host);
ret = g_strconcat (scheme, "://", hostname, host_end, NULL);
g_free (scheme);
g_free (host);
g_free (hostname);
return ret;
}
return g_strdup (url);
}
static CFURLRef
create_url_from_cstr (gchar *cstr,
gboolean is_file)
{
gchar *puny_cstr;
CFStringRef str;
CFURLRef url;
puny_cstr = url_escape_hostname (cstr);
str = CFStringCreateWithCString (NULL, puny_cstr ? puny_cstr : cstr, kCFStringEncodingUTF8);
if (is_file)
url = CFURLCreateWithFileSystemPath (NULL, str, kCFURLPOSIXPathStyle, FALSE);
else
url = CFURLCreateWithString (NULL, str, NULL);
if (!url)
g_debug ("Creating CFURL from %s %s failed!", cstr, is_file ? "file" : "uri");
g_free (puny_cstr);
CFRelease(str);
return url;
}
static CFArrayRef
create_url_list_from_glist (GList *uris,
gboolean are_files)
{
GList *lst;
int len = g_list_length (uris);
CFMutableArrayRef array;
if (!len)
return NULL;
array = CFArrayCreateMutable (NULL, len, &kCFTypeArrayCallBacks);
if (!array)
return NULL;
for (lst = uris; lst != NULL && lst->data; lst = lst->next)
{
CFURLRef url = create_url_from_cstr ((char*)lst->data, are_files);
if (url)
CFArrayAppendValue (array, url);
}
return (CFArrayRef)array;
}
static LSLaunchURLSpec *
create_urlspec_for_appinfo (GOsxAppInfo *info,
GList *uris,
gboolean are_files)
{
LSLaunchURLSpec *urlspec = g_new0 (LSLaunchURLSpec, 1);
gchar *app_cstr = g_osx_app_info_get_filename (info);
/* Strip file:// from app url but ensure filesystem url */
urlspec->appURL = create_url_from_cstr (app_cstr + 7, TRUE);
urlspec->launchFlags = kLSLaunchDefaults;
urlspec->itemURLs = create_url_list_from_glist (uris, are_files);
return urlspec;
}
static void
free_urlspec (LSLaunchURLSpec *urlspec)
{
if (urlspec->itemURLs)
{
CFArrayRemoveAllValues ((CFMutableArrayRef)urlspec->itemURLs);
CFRelease (urlspec->itemURLs);
}
CFRelease (urlspec->appURL);
g_free (urlspec);
}
static NSBundle *
get_bundle_for_url (CFURLRef app_url)
{
NSBundle *bundle = [NSBundle bundleWithURL: (NSURL*)app_url];
if (!bundle)
{
g_debug ("Bundle not found for url.");
return NULL;
}
return bundle;
}
static NSBundle *
get_bundle_for_id (CFStringRef bundle_id)
{
CFURLRef app_url;
NSBundle *bundle;
#ifdef AVAILABLE_MAC_OS_X_VERSION_10_10_AND_LATER
CFArrayRef urls = LSCopyApplicationURLsForBundleIdentifier (bundle_id, NULL);
if (urls)
{
/* TODO: if there's multiple, we should perhaps prefer one that's in $HOME,
* instead of just always picking the first.
*/
app_url = CFArrayGetValueAtIndex (urls, 0);
CFRetain (app_url);
CFRelease (urls);
}
else
#else
if (LSFindApplicationForInfo (kLSUnknownCreator, bundle_id, NULL, NULL, &app_url) == kLSApplicationNotFoundErr)
#endif
{
#ifdef G_ENABLE_DEBUG /* This can fail often, no reason to alloc strings */
gchar *id_str = create_cstr_from_cfstring (bundle_id);
if (id_str)
{
g_debug ("Application not found for id \"%s\".", id_str);
g_free (id_str);
}
else
g_debug ("Application not found for unconvertable bundle id.");
#endif
return NULL;
}
bundle = get_bundle_for_url (app_url);
CFRelease (app_url);
return bundle;
}
static const char *
g_osx_app_info_get_id (GAppInfo *appinfo)
{
GOsxAppInfo *info = G_OSX_APP_INFO (appinfo);
if (!info->id)
info->id = get_bundle_string_value (info->bundle, @"CFBundleIdentifier");
return info->id;
}
static const char *
g_osx_app_info_get_name (GAppInfo *appinfo)
{
GOsxAppInfo *info = G_OSX_APP_INFO (appinfo);
if (!info->name)
info->name = get_bundle_string_value (info->bundle, @"CFBundleName");
return info->name;
}
static const char *
g_osx_app_info_get_display_name (GAppInfo *appinfo)
{
return g_osx_app_info_get_name (appinfo);
}
static const char *
g_osx_app_info_get_description (GAppInfo *appinfo)
{
/* Bundles do not contain descriptions */
return NULL;
}
static const char *
g_osx_app_info_get_executable (GAppInfo *appinfo)
{
GOsxAppInfo *info = G_OSX_APP_INFO (appinfo);
if (!info->executable)
info->executable = get_bundle_string_value (info->bundle, @"CFBundleExecutable");
return info->executable;
}
char *
g_osx_app_info_get_filename (GOsxAppInfo *info)
{
g_return_val_if_fail (info != NULL, NULL);
if (!info->filename)
{
info->filename = g_strconcat ("file://", [[info->bundle bundlePath]
cStringUsingEncoding: NSUTF8StringEncoding],
NULL);
}
return info->filename;
}
static const char *
g_osx_app_info_get_commandline (GAppInfo *appinfo)
{
/* There isn't really a command line value */
return NULL;
}
static GIcon *
g_osx_app_info_get_icon (GAppInfo *appinfo)
{
GOsxAppInfo *info = G_OSX_APP_INFO (appinfo);
if (!info->icon)
{
gchar *icon_name, *app_uri, *icon_uri;
GFile *file;
icon_name = get_bundle_string_value (info->bundle, @"CFBundleIconFile");
if (!icon_name)
return NULL;
app_uri = g_osx_app_info_get_filename (info);
icon_uri = g_strconcat (app_uri + 7, "/Contents/Resources/", icon_name,
g_str_has_suffix (icon_name, ".icns") ? NULL : ".icns", NULL);
g_free (icon_name);
file = g_file_new_for_path (icon_uri);
info->icon = g_file_icon_new (file);
g_object_unref (file);
g_free (icon_uri);
}
return info->icon;
}
static gboolean
g_osx_app_info_launch_internal (GAppInfo *appinfo,
GList *uris,
gboolean are_files,
GError **error)
{
GOsxAppInfo *info = G_OSX_APP_INFO (appinfo);
LSLaunchURLSpec *urlspec = create_urlspec_for_appinfo (info, uris, are_files);
gint ret, success = TRUE;
if ((ret = LSOpenFromURLSpec (urlspec, NULL)))
{
/* TODO: Better error codes */
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Opening application failed with code %d", ret);
success = FALSE;
}
free_urlspec (urlspec);
return success;
}
static gboolean
g_osx_app_info_supports_uris (GAppInfo *appinfo)
{
return TRUE;
}
static gboolean
g_osx_app_info_supports_files (GAppInfo *appinfo)
{
return TRUE;
}
static gboolean
g_osx_app_info_launch (GAppInfo *appinfo,
GList *files,
GAppLaunchContext *launch_context,
GError **error)
{
return g_osx_app_info_launch_internal (appinfo, files, TRUE, error);
}
static gboolean
g_osx_app_info_launch_uris (GAppInfo *appinfo,
GList *uris,
GAppLaunchContext *launch_context,
GError **error)
{
return g_osx_app_info_launch_internal (appinfo, uris, FALSE, error);
}
static gboolean
g_osx_app_info_should_show (GAppInfo *appinfo)
{
/* Bundles don't have hidden attribute */
return TRUE;
}
static gboolean
g_osx_app_info_set_as_default_for_type (GAppInfo *appinfo,
const char *content_type,
GError **error)
{
return FALSE;
}
static const char **
g_osx_app_info_get_supported_types (GAppInfo *appinfo)
{
/* TODO: get CFBundleDocumentTypes */
return NULL;
}
static gboolean
g_osx_app_info_set_as_last_used_for_type (GAppInfo *appinfo,
const char *content_type,
GError **error)
{
/* Not supported. */
return FALSE;
}
static gboolean
g_osx_app_info_can_delete (GAppInfo *appinfo)
{
return FALSE;
}
static void
g_osx_app_info_iface_init (GAppInfoIface *iface)
{
iface->dup = g_osx_app_info_dup;
iface->equal = g_osx_app_info_equal;
iface->get_id = g_osx_app_info_get_id;
iface->get_name = g_osx_app_info_get_name;
iface->get_display_name = g_osx_app_info_get_display_name;
iface->get_description = g_osx_app_info_get_description;
iface->get_executable = g_osx_app_info_get_executable;
iface->get_commandline = g_osx_app_info_get_commandline;
iface->get_icon = g_osx_app_info_get_icon;
iface->get_supported_types = g_osx_app_info_get_supported_types;
iface->set_as_last_used_for_type = g_osx_app_info_set_as_last_used_for_type;
iface->set_as_default_for_type = g_osx_app_info_set_as_default_for_type;
iface->launch = g_osx_app_info_launch;
iface->launch_uris = g_osx_app_info_launch_uris;
iface->supports_uris = g_osx_app_info_supports_uris;
iface->supports_files = g_osx_app_info_supports_files;
iface->should_show = g_osx_app_info_should_show;
iface->can_delete = g_osx_app_info_can_delete;
}
GAppInfo *
g_app_info_create_from_commandline (const char *commandline,
const char *application_name,
GAppInfoCreateFlags flags,
GError **error)
{
return NULL;
}
GList *
g_osx_app_info_get_all_for_scheme (const char *cscheme)
{
CFArrayRef bundle_list;
CFStringRef scheme;
NSBundle *bundle;
GList *info_list = NULL;
gint i;
scheme = create_cfstring_from_cstr (cscheme);
bundle_list = LSCopyAllHandlersForURLScheme (scheme);
CFRelease (scheme);
if (!bundle_list)
return NULL;
for (i = 0; i < CFArrayGetCount (bundle_list); i++)
{
CFStringRef bundle_id = CFArrayGetValueAtIndex (bundle_list, i);
GAppInfo *info;
bundle = get_bundle_for_id (bundle_id);
if (!bundle)
continue;
info = G_APP_INFO (g_osx_app_info_new (bundle));
info_list = g_list_append (info_list, info);
}
CFRelease (bundle_list);
return info_list;
}
GList *
g_app_info_get_all_for_type (const char *content_type)
{
gchar *mime_type;
CFArrayRef bundle_list;
CFStringRef type;
NSBundle *bundle;
GList *info_list = NULL;
gint i;
mime_type = g_content_type_get_mime_type (content_type);
if (g_str_has_prefix (mime_type, "x-scheme-handler/"))
{
gchar *scheme = strchr (mime_type, '/') + 1;
GList *ret = g_osx_app_info_get_all_for_scheme (scheme);
g_free (mime_type);
return ret;
}
g_free (mime_type);
type = create_cfstring_from_cstr (content_type);
bundle_list = LSCopyAllRoleHandlersForContentType (type, kLSRolesAll);
CFRelease (type);
if (!bundle_list)
return NULL;
for (i = 0; i < CFArrayGetCount (bundle_list); i++)
{
CFStringRef bundle_id = CFArrayGetValueAtIndex (bundle_list, i);
GAppInfo *info;
bundle = get_bundle_for_id (bundle_id);
if (!bundle)
continue;
info = G_APP_INFO (g_osx_app_info_new (bundle));
info_list = g_list_append (info_list, info);
}
CFRelease (bundle_list);
return info_list;
}
GList *
g_app_info_get_recommended_for_type (const char *content_type)
{
return g_app_info_get_all_for_type (content_type);
}
GList *
g_app_info_get_fallback_for_type (const char *content_type)
{
return g_app_info_get_all_for_type (content_type);
}
GAppInfo *
g_app_info_get_default_for_type (const char *content_type,
gboolean must_support_uris)
{
gchar *mime_type;
CFStringRef type;
NSBundle *bundle;
#ifdef AVAILABLE_MAC_OS_X_VERSION_10_10_AND_LATER
CFURLRef bundle_id;
#else
CFStringRef bundle_id;
#endif
mime_type = g_content_type_get_mime_type (content_type);
if (g_str_has_prefix (mime_type, "x-scheme-handler/"))
{
gchar *scheme = strchr (mime_type, '/') + 1;
GAppInfo *ret = g_app_info_get_default_for_uri_scheme (scheme);
g_free (mime_type);
return ret;
}
g_free (mime_type);
type = create_cfstring_from_cstr (content_type);
#ifdef AVAILABLE_MAC_OS_X_VERSION_10_10_AND_LATER
bundle_id = LSCopyDefaultApplicationURLForContentType (type, kLSRolesAll, NULL);
#else
bundle_id = LSCopyDefaultRoleHandlerForContentType (type, kLSRolesAll);
#endif
CFRelease (type);
if (!bundle_id)
{
g_warning ("No default handler found for content type '%s'.", content_type);
return NULL;
}
#ifdef AVAILABLE_MAC_OS_X_VERSION_10_10_AND_LATER
bundle = get_bundle_for_url (bundle_id);
#else
bundle = get_bundle_for_id (bundle_id);
#endif
CFRelease (bundle_id);
if (!bundle)
return NULL;
return G_APP_INFO (g_osx_app_info_new (bundle));
}
GAppInfo *
g_app_info_get_default_for_uri_scheme (const char *uri_scheme)
{
CFStringRef scheme, bundle_id;
NSBundle *bundle;
scheme = create_cfstring_from_cstr (uri_scheme);
bundle_id = LSCopyDefaultHandlerForURLScheme (scheme);
CFRelease (scheme);
if (!bundle_id)
{
g_warning ("No default handler found for url scheme '%s'.", uri_scheme);
return NULL;
}
bundle = get_bundle_for_id (bundle_id);
CFRelease (bundle_id);
if (!bundle)
return NULL;
return G_APP_INFO (g_osx_app_info_new (bundle));
}
GList *
g_app_info_get_all (void)
{
/* There is no API for this afaict
* could manually do it...
*/
return NULL;
}
void
g_app_info_reset_type_associations (const char *content_type)
{
}