From c67ae98588e0218dc434283052ec8bebbf9a7659 Mon Sep 17 00:00:00 2001 From: Arjan Molenaar Date: Fri, 28 Jun 2024 08:46:42 +0200 Subject: [PATCH 1/9] macos: Implement GAppInfo.launch_uris_async interface The implementation is heavily inspired by the Windows implementation. --- gio/gosxappinfo.m | 70 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/gio/gosxappinfo.m b/gio/gosxappinfo.m index 6de647d6b..50c2083c7 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 @@ -513,6 +514,72 @@ g_osx_app_info_launch_uris (GAppInfo *appinfo, 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) { @@ -570,7 +637,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; From e9ee147ac94c0a011b465f6b02ae82f8de7ceb18 Mon Sep 17 00:00:00 2001 From: Arjan Molenaar Date: Tue, 2 Jul 2024 09:08:31 +0200 Subject: [PATCH 2/9] macos: fix header indentation --- gio/gosxappinfo.m | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gio/gosxappinfo.m b/gio/gosxappinfo.m index 50c2083c7..974b2da8e 100644 --- a/gio/gosxappinfo.m +++ b/gio/gosxappinfo.m @@ -459,9 +459,9 @@ 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; @@ -572,8 +572,8 @@ g_osx_app_info_launch_uris_async (GAppInfo *appinfo, static gboolean g_osx_app_info_launch_uris_finish (GAppInfo *appinfo, - GAsyncResult *result, - GError **error) + GAsyncResult *result, + GError **error) { g_return_val_if_fail (g_task_is_valid (result, appinfo), FALSE); From 71e87fc29c99f3939ec189ed66ad13d81b7439aa Mon Sep 17 00:00:00 2001 From: Arjan Molenaar Date: Tue, 2 Jul 2024 21:30:31 +0200 Subject: [PATCH 3/9] more indentation fixes --- gio/gosxappinfo.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gio/gosxappinfo.m b/gio/gosxappinfo.m index 974b2da8e..0e8103d51 100644 --- a/gio/gosxappinfo.m +++ b/gio/gosxappinfo.m @@ -274,8 +274,8 @@ create_url_list_from_glist (GList *uris, static LSLaunchURLSpec * create_urlspec_for_appinfo (GOsxAppInfo *info, - GList *uris, - gboolean are_files) + GList *uris, + gboolean are_files) { LSLaunchURLSpec *urlspec = NULL; const gchar *app_cstr; From 9f0ff882c19bf0746ec1735850f4b4dce11a6db4 Mon Sep 17 00:00:00 2001 From: Arjan Molenaar Date: Tue, 2 Jul 2024 21:30:39 +0200 Subject: [PATCH 4/9] macos: Fill appUrl directly No need to convert it to a char* first. This also avoids threading issues with OsxAppInfo's get_filename() method. --- gio/gosxappinfo.m | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/gio/gosxappinfo.m b/gio/gosxappinfo.m index 0e8103d51..459f6ff33 100644 --- a/gio/gosxappinfo.m +++ b/gio/gosxappinfo.m @@ -278,16 +278,11 @@ create_urlspec_for_appinfo (GOsxAppInfo *info, 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->appURL = CFURLCreateWithFileSystemPath (NULL, CFBridgingRetain([info->bundle bundlePath]), kCFURLPOSIXPathStyle, FALSE); urlspec->launchFlags = kLSLaunchDefaults; urlspec->itemURLs = create_url_list_from_glist (uris, are_files); From edd36a907ba87ffb51ccc447ed7c283dde291c6a Mon Sep 17 00:00:00 2001 From: Arjan Molenaar Date: Wed, 24 Jul 2024 16:31:27 +0200 Subject: [PATCH 5/9] macos: simplify urlspec setup for launch_uris_async NB. Using toll-free bridging to cast NSURL to a CFURLRef See https://developer.apple.com/library/archive/documentation/General/Conceptual/CocoaEncyclopedia/Toll-FreeBridgin/Toll-FreeBridgin.html --- gio/gosxappinfo.m | 32 +++++++++++++------------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/gio/gosxappinfo.m b/gio/gosxappinfo.m index 459f6ff33..82194b4bc 100644 --- a/gio/gosxappinfo.m +++ b/gio/gosxappinfo.m @@ -272,25 +272,19 @@ 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; - - g_return_val_if_fail (G_IS_OSX_APP_INFO (info), NULL); - - urlspec = g_new0 (LSLaunchURLSpec, 1); - urlspec->appURL = CFURLCreateWithFileSystemPath (NULL, CFBridgingRetain([info->bundle bundlePath]), kCFURLPOSIXPathStyle, FALSE); - 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) { @@ -298,7 +292,6 @@ free_urlspec (LSLaunchURLSpec *urlspec) CFRelease (urlspec->itemURLs); } CFRelease (urlspec->appURL); - g_free (urlspec); } static NSBundle * @@ -459,15 +452,16 @@ g_osx_app_info_launch_internal (GAppInfo *appinfo, 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, @@ -475,7 +469,7 @@ g_osx_app_info_launch_internal (GAppInfo *appinfo, success = FALSE; } - free_urlspec (urlspec); + clear_urlspec (&urlspec); return success; } From fac8a8c8d848101ee6f07c9a6744b372c379134a Mon Sep 17 00:00:00 2001 From: Arjan Molenaar Date: Wed, 24 Jul 2024 16:35:48 +0200 Subject: [PATCH 6/9] macos: Add test for async launcher The test brings a Finder window to the front. It's not ideal, but I have no better idea at the moment. It would be cool if we can make the test case register itself as handler for a particular uri scheme, but I have no idea how to do that. --- gio/tests/meson.build | 6 ++++ gio/tests/osx-appinfo.c | 74 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 gio/tests/osx-appinfo.c 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..53717ac24 --- /dev/null +++ b/gio/tests/osx-appinfo.c @@ -0,0 +1,74 @@ +/* + * 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); + + g_assert_true (g_app_info_launch_uris_finish (G_APP_INFO (app_info), result, &error)); + g_assert_no_error (error); + + g_clear_error (&error); + g_clear_object (&result); + g_clear_object (&context); + g_clear_object (&app_info); +} + +int +main (int argc, + char *argv[]) +{ + g_test_init (&argc, &argv, G_TEST_OPTION_ISOLATE_DIRS, NULL); + + g_test_add_func ("/osx-app-info/launch-async", test_launch_async); + + return g_test_run (); +} From 1053c016d949cd0a1d043db34221322e573259e5 Mon Sep 17 00:00:00 2001 From: Arjan Molenaar Date: Wed, 24 Jul 2024 17:24:46 +0200 Subject: [PATCH 7/9] macos: Add test case for invalid scheme You may not always know which schemes are available. The library should not bail out, but only show an informal message. It's the responsibility of the application to deal with invalid URI schemes. --- gio/gosxappinfo.m | 2 +- gio/tests/osx-appinfo.c | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/gio/gosxappinfo.m b/gio/gosxappinfo.m index 82194b4bc..3cfbb29b1 100644 --- a/gio/gosxappinfo.m +++ b/gio/gosxappinfo.m @@ -800,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/osx-appinfo.c b/gio/tests/osx-appinfo.c index 53717ac24..6f39968b3 100644 --- a/gio/tests/osx-appinfo.c +++ b/gio/tests/osx-appinfo.c @@ -62,13 +62,23 @@ test_launch_async (void) 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, G_TEST_OPTION_ISOLATE_DIRS, NULL); + 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 (); } From d23c781e7b5eaa6f98320744de33499c55bf771a Mon Sep 17 00:00:00 2001 From: Arjan Molenaar Date: Wed, 24 Jul 2024 17:42:48 +0200 Subject: [PATCH 8/9] macos: Tune launch results for CI It looks like we have no Finder running. --- gio/tests/osx-appinfo.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/gio/tests/osx-appinfo.c b/gio/tests/osx-appinfo.c index 6f39968b3..1aa859958 100644 --- a/gio/tests/osx-appinfo.c +++ b/gio/tests/osx-appinfo.c @@ -53,8 +53,11 @@ test_launch_async (void) while (result == NULL) g_main_context_iteration (NULL, TRUE); - g_assert_true (g_app_info_launch_uris_finish (G_APP_INFO (app_info), result, &error)); - g_assert_no_error (error); + // 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); From 2ad61ed0ee9c91f77fdc3998fe958084aa1232a6 Mon Sep 17 00:00:00 2001 From: Arjan Molenaar Date: Sat, 10 Aug 2024 19:42:32 +0200 Subject: [PATCH 9/9] Fix formatting issues --- gio/gosxappinfo.m | 28 ++++++++++++++-------------- gio/tests/osx-appinfo.c | 4 ++-- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/gio/gosxappinfo.m b/gio/gosxappinfo.m index 3cfbb29b1..244d6578c 100644 --- a/gio/gosxappinfo.m +++ b/gio/gosxappinfo.m @@ -120,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; @@ -278,7 +278,7 @@ fill_urlspec_for_appinfo (LSLaunchURLSpec *urlspec, GList *uris, gboolean are_files) { - urlspec->appURL = (CFURLRef) [NSURL fileURLWithPath: [info->bundle bundlePath]]; + urlspec->appURL = (CFURLRef) [NSURL fileURLWithPath:[info->bundle bundlePath]]; urlspec->itemURLs = create_url_list_from_glist (uris, are_files); urlspec->launchFlags = kLSLaunchDefaults; } @@ -288,7 +288,7 @@ clear_urlspec (LSLaunchURLSpec *urlspec) { if (urlspec->itemURLs) { - CFArrayRemoveAllValues ((CFMutableArrayRef)urlspec->itemURLs); + CFArrayRemoveAllValues ((CFMutableArrayRef) urlspec->itemURLs); CFRelease (urlspec->itemURLs); } CFRelease (urlspec->appURL); @@ -452,7 +452,7 @@ g_osx_app_info_launch_internal (GAppInfo *appinfo, GError **error) { GOsxAppInfo *info = G_OSX_APP_INFO (appinfo); - LSLaunchURLSpec urlspec = {0}; + LSLaunchURLSpec urlspec = { 0 }; gint ret, success = TRUE; g_return_val_if_fail (G_IS_OSX_APP_INFO (appinfo), FALSE); @@ -487,18 +487,18 @@ 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); } @@ -578,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; } @@ -593,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; diff --git a/gio/tests/osx-appinfo.c b/gio/tests/osx-appinfo.c index 1aa859958..eca18bd8c 100644 --- a/gio/tests/osx-appinfo.c +++ b/gio/tests/osx-appinfo.c @@ -42,7 +42,7 @@ test_launch_async (void) GAsyncResult *result = NULL; GError *error = NULL; - app_info = g_app_info_get_default_for_uri_scheme("file"); + 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)); @@ -70,7 +70,7 @@ test_invalid_uri_scheme (void) { GAppInfo *app_info; - app_info = g_app_info_get_default_for_uri_scheme("thisisnotanurlscheme"); + app_info = g_app_info_get_default_for_uri_scheme ("thisisnotanurlscheme"); g_assert_null (app_info); }