diff --git a/gio/gosxappinfo.m b/gio/gosxappinfo.m index 6de647d6b..244d6578c 100644 --- a/gio/gosxappinfo.m +++ b/gio/gosxappinfo.m @@ -28,6 +28,7 @@ #include "gfile.h" #include "gfileicon.h" #include "gioerror.h" +#include "gtask.h" #import #import @@ -119,7 +120,7 @@ g_osx_app_info_dup (GAppInfo *appinfo) static gboolean g_osx_app_info_equal (GAppInfo *appinfo1, - GAppInfo *appinfo2) + GAppInfo *appinfo2) { const gchar *str1, *str2; @@ -271,38 +272,26 @@ create_url_list_from_glist (GList *uris, return (CFArrayRef)array; } -static LSLaunchURLSpec * -create_urlspec_for_appinfo (GOsxAppInfo *info, - GList *uris, - gboolean are_files) +static void +fill_urlspec_for_appinfo (LSLaunchURLSpec *urlspec, + GOsxAppInfo *info, + GList *uris, + gboolean are_files) { - LSLaunchURLSpec *urlspec = NULL; - const gchar *app_cstr; - - g_return_val_if_fail (G_IS_OSX_APP_INFO (info), NULL); - - urlspec = g_new0 (LSLaunchURLSpec, 1); - app_cstr = g_osx_app_info_get_filename (info); - g_assert (app_cstr != NULL); - - /* Strip file:// from app url but ensure filesystem url */ - urlspec->appURL = create_url_from_cstr (app_cstr + strlen ("file://"), TRUE); - urlspec->launchFlags = kLSLaunchDefaults; + urlspec->appURL = (CFURLRef) [NSURL fileURLWithPath:[info->bundle bundlePath]]; urlspec->itemURLs = create_url_list_from_glist (uris, are_files); - - return urlspec; + urlspec->launchFlags = kLSLaunchDefaults; } static void -free_urlspec (LSLaunchURLSpec *urlspec) +clear_urlspec (LSLaunchURLSpec *urlspec) { if (urlspec->itemURLs) { - CFArrayRemoveAllValues ((CFMutableArrayRef)urlspec->itemURLs); + CFArrayRemoveAllValues ((CFMutableArrayRef) urlspec->itemURLs); CFRelease (urlspec->itemURLs); } CFRelease (urlspec->appURL); - g_free (urlspec); } static NSBundle * @@ -458,20 +447,21 @@ g_osx_app_info_get_icon (GAppInfo *appinfo) static gboolean g_osx_app_info_launch_internal (GAppInfo *appinfo, - GList *uris, - gboolean are_files, - GError **error) + GList *uris, + gboolean are_files, + GError **error) { GOsxAppInfo *info = G_OSX_APP_INFO (appinfo); - LSLaunchURLSpec *urlspec; + LSLaunchURLSpec urlspec = { 0 }; gint ret, success = TRUE; g_return_val_if_fail (G_IS_OSX_APP_INFO (appinfo), FALSE); + g_return_val_if_fail (uris == NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); - urlspec = create_urlspec_for_appinfo (info, uris, are_files); + fill_urlspec_for_appinfo (&urlspec, info, uris, are_files); - if ((ret = LSOpenFromURLSpec (urlspec, NULL))) + if ((ret = LSOpenFromURLSpec (&urlspec, NULL))) { /* TODO: Better error codes */ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, @@ -479,7 +469,7 @@ g_osx_app_info_launch_internal (GAppInfo *appinfo, success = FALSE; } - free_urlspec (urlspec); + clear_urlspec (&urlspec); return success; } @@ -497,22 +487,88 @@ g_osx_app_info_supports_files (GAppInfo *appinfo) static gboolean g_osx_app_info_launch (GAppInfo *appinfo, - GList *files, - GAppLaunchContext *launch_context, - GError **error) + 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) + GList *uris, + GAppLaunchContext *launch_context, + GError **error) { return g_osx_app_info_launch_internal (appinfo, uris, FALSE, error); } +typedef struct +{ + GList *uris; /* (element-type utf8) (owned) (nullable) */ + GAppLaunchContext *context; /* (owned) (nullable) */ +} LaunchUrisData; + +static void +launch_uris_data_free (LaunchUrisData *data) +{ + g_clear_object (&data->context); + g_list_free_full (data->uris, g_free); + g_free (data); +} + +static void +launch_uris_async_thread (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + GAppInfo *appinfo = G_APP_INFO (source_object); + LaunchUrisData *data = task_data; + GError *local_error = NULL; + gboolean succeeded; + + succeeded = g_osx_app_info_launch_internal (appinfo, data->uris, FALSE, &local_error); + + if (local_error != NULL) + g_task_return_error (task, g_steal_pointer (&local_error)); + else + g_task_return_boolean (task, succeeded); +} + +static void +g_osx_app_info_launch_uris_async (GAppInfo *appinfo, + GList *uris, + GAppLaunchContext *context, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + LaunchUrisData *data; + + task = g_task_new (appinfo, cancellable, callback, user_data); + g_task_set_source_tag (task, g_osx_app_info_launch_uris_async); + + data = g_new0 (LaunchUrisData, 1); + data->uris = g_list_copy_deep (uris, (GCopyFunc) g_strdup, NULL); + g_set_object (&data->context, context); + g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) launch_uris_data_free); + + g_task_run_in_thread (task, launch_uris_async_thread); + g_object_unref (task); +} + +static gboolean +g_osx_app_info_launch_uris_finish (GAppInfo *appinfo, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (g_task_is_valid (result, appinfo), FALSE); + + return g_task_propagate_boolean (G_TASK (result), error); +} + static gboolean g_osx_app_info_should_show (GAppInfo *appinfo) { @@ -522,8 +578,8 @@ g_osx_app_info_should_show (GAppInfo *appinfo) static gboolean g_osx_app_info_set_as_default_for_type (GAppInfo *appinfo, - const char *content_type, - GError **error) + const char *content_type, + GError **error) { return FALSE; } @@ -537,8 +593,8 @@ g_osx_app_info_get_supported_types (GAppInfo *appinfo) static gboolean g_osx_app_info_set_as_last_used_for_type (GAppInfo *appinfo, - const char *content_type, - GError **error) + const char *content_type, + GError **error) { /* Not supported. */ return FALSE; @@ -570,7 +626,8 @@ g_osx_app_info_iface_init (GAppInfoIface *iface) iface->launch = g_osx_app_info_launch; iface->launch_uris = g_osx_app_info_launch_uris; - + iface->launch_uris_async = g_osx_app_info_launch_uris_async; + iface->launch_uris_finish = g_osx_app_info_launch_uris_finish; 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; @@ -743,7 +800,7 @@ g_app_info_get_default_for_uri_scheme_impl (const char *uri_scheme) if (!bundle_id) { - g_warning ("No default handler found for url scheme '%s'.", uri_scheme); + g_info ("No default handler found for url scheme '%s'.", uri_scheme); return NULL; } diff --git a/gio/tests/meson.build b/gio/tests/meson.build index 95da6002c..f2c11e360 100644 --- a/gio/tests/meson.build +++ b/gio/tests/meson.build @@ -153,6 +153,12 @@ gio_tests = { 'win32-appinfo' : {}, } +if glib_have_cocoa + gio_tests += { + 'osx-appinfo' : {} + } +endif + if have_cxx gio_tests += { 'cxx' : { diff --git a/gio/tests/osx-appinfo.c b/gio/tests/osx-appinfo.c new file mode 100644 index 000000000..eca18bd8c --- /dev/null +++ b/gio/tests/osx-appinfo.c @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2024 GNOME Foundation + * Copyright (C) 2024 Arjan Molenaar + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * 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 . + */ + +#include +#include + + +static void +async_result_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + GAsyncResult **result_out = user_data; + + g_assert (*result_out == NULL); + *result_out = g_object_ref (result); + g_main_context_wakeup (g_main_context_get_thread_default ()); +} + +static void +test_launch_async (void) +{ + GAppInfo *app_info; + GAppLaunchContext *context; + GAsyncResult *result = NULL; + GError *error = NULL; + + app_info = g_app_info_get_default_for_uri_scheme ("file"); + g_assert_nonnull (app_info); + g_assert_true (G_IS_OSX_APP_INFO (app_info)); + + context = g_app_launch_context_new (); + + g_app_info_launch_uris_async (G_APP_INFO (app_info), NULL, context, NULL, async_result_cb, &result); + + while (result == NULL) + g_main_context_iteration (NULL, TRUE); + + // Locally, the result is TRUE, but in CI it's FALSE, due to the absense of a GUI(?) + if (g_app_info_launch_uris_finish (G_APP_INFO (app_info), result, &error)) + g_assert_no_error (error); + else + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_FAILED); + + g_clear_error (&error); + g_clear_object (&result); + g_clear_object (&context); + g_clear_object (&app_info); +} + +static void +test_invalid_uri_scheme (void) +{ + GAppInfo *app_info; + + app_info = g_app_info_get_default_for_uri_scheme ("thisisnotanurlscheme"); + g_assert_null (app_info); +} + +int +main (int argc, + char *argv[]) +{ + g_test_init (&argc, &argv, NULL, NULL); + + g_test_add_func ("/osx-app-info/launch-async", test_launch_async); + g_test_add_func ("/osx-app-info/invalid-uri-scheme", test_invalid_uri_scheme); + + return g_test_run (); +}