Merge branch 'amolenaar/macos-launch-uris-async' into 'main'

macos: Implement GAppInfo launch_uris_async interface

Closes #3403

See merge request GNOME/glib!4129
This commit is contained in:
Philip Withnall 2024-08-14 13:16:06 +00:00
commit c5e17bc37f
3 changed files with 191 additions and 41 deletions

View File

@ -28,6 +28,7 @@
#include "gfile.h" #include "gfile.h"
#include "gfileicon.h" #include "gfileicon.h"
#include "gioerror.h" #include "gioerror.h"
#include "gtask.h"
#import <CoreFoundation/CoreFoundation.h> #import <CoreFoundation/CoreFoundation.h>
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
@ -271,38 +272,26 @@ create_url_list_from_glist (GList *uris,
return (CFArrayRef)array; return (CFArrayRef)array;
} }
static LSLaunchURLSpec * static void
create_urlspec_for_appinfo (GOsxAppInfo *info, fill_urlspec_for_appinfo (LSLaunchURLSpec *urlspec,
GOsxAppInfo *info,
GList *uris, GList *uris,
gboolean are_files) gboolean are_files)
{ {
LSLaunchURLSpec *urlspec = NULL; urlspec->appURL = (CFURLRef) [NSURL fileURLWithPath:[info->bundle bundlePath]];
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->itemURLs = create_url_list_from_glist (uris, are_files); urlspec->itemURLs = create_url_list_from_glist (uris, are_files);
urlspec->launchFlags = kLSLaunchDefaults;
return urlspec;
} }
static void static void
free_urlspec (LSLaunchURLSpec *urlspec) clear_urlspec (LSLaunchURLSpec *urlspec)
{ {
if (urlspec->itemURLs) if (urlspec->itemURLs)
{ {
CFArrayRemoveAllValues ((CFMutableArrayRef)urlspec->itemURLs); CFArrayRemoveAllValues ((CFMutableArrayRef) urlspec->itemURLs);
CFRelease (urlspec->itemURLs); CFRelease (urlspec->itemURLs);
} }
CFRelease (urlspec->appURL); CFRelease (urlspec->appURL);
g_free (urlspec);
} }
static NSBundle * static NSBundle *
@ -463,15 +452,16 @@ g_osx_app_info_launch_internal (GAppInfo *appinfo,
GError **error) GError **error)
{ {
GOsxAppInfo *info = G_OSX_APP_INFO (appinfo); GOsxAppInfo *info = G_OSX_APP_INFO (appinfo);
LSLaunchURLSpec *urlspec; LSLaunchURLSpec urlspec = { 0 };
gint ret, success = TRUE; gint ret, success = TRUE;
g_return_val_if_fail (G_IS_OSX_APP_INFO (appinfo), FALSE); 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); 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 */ /* TODO: Better error codes */
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, 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; success = FALSE;
} }
free_urlspec (urlspec); clear_urlspec (&urlspec);
return success; return success;
} }
@ -513,6 +503,72 @@ g_osx_app_info_launch_uris (GAppInfo *appinfo,
return g_osx_app_info_launch_internal (appinfo, uris, FALSE, 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 static gboolean
g_osx_app_info_should_show (GAppInfo *appinfo) g_osx_app_info_should_show (GAppInfo *appinfo)
{ {
@ -570,7 +626,8 @@ g_osx_app_info_iface_init (GAppInfoIface *iface)
iface->launch = g_osx_app_info_launch; iface->launch = g_osx_app_info_launch;
iface->launch_uris = g_osx_app_info_launch_uris; 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_uris = g_osx_app_info_supports_uris;
iface->supports_files = g_osx_app_info_supports_files; iface->supports_files = g_osx_app_info_supports_files;
iface->should_show = g_osx_app_info_should_show; 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) 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; return NULL;
} }

View File

@ -153,6 +153,12 @@ gio_tests = {
'win32-appinfo' : {}, 'win32-appinfo' : {},
} }
if glib_have_cocoa
gio_tests += {
'osx-appinfo' : {}
}
endif
if have_cxx if have_cxx
gio_tests += { gio_tests += {
'cxx' : { 'cxx' : {

87
gio/tests/osx-appinfo.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <gio/gio.h>
#include <gio/gosxappinfo.h>
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 ();
}