From 0a1730d7ea486d07a10b238d7f6fdaa41218c5e4 Mon Sep 17 00:00:00 2001 From: Ondrej Holy Date: Mon, 28 Jan 2019 15:18:37 +0100 Subject: [PATCH 1/5] gfile: Fix leak in g_file_query_default_handler() Add missing `g_free (uri_scheme)` to fix leak when `uri_scheme[0]` is equal to `\0`. --- gio/gfile.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gio/gfile.c b/gio/gfile.c index a5709a4cc..e6b468b55 100644 --- a/gio/gfile.c +++ b/gio/gfile.c @@ -6851,6 +6851,8 @@ g_file_query_default_handler (GFile *file, if (appinfo != NULL) return appinfo; } + else + g_free (uri_scheme); info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, From f72a5d65e0e762a16c33f91e180b2edbd46e95eb Mon Sep 17 00:00:00 2001 From: Ondrej Holy Date: Tue, 22 Jan 2019 15:37:44 +0100 Subject: [PATCH 2/5] gfile: Add g_file_query_default_handler_async() This is needed as a first step to fix the g_app_info_launch_default_for_uri_async() function to be really asynchronous. It still uses the g_app_info_get_default_for_uri_scheme() and g_app_info_get_default_for_type() functions, which may use synchronous calls to local MIME DB. https://gitlab.gnome.org/GNOME/glib/issues/1347 https://gitlab.gnome.org/GNOME/glib/issues/1249 --- docs/reference/gio/gio-sections.txt | 2 + gio/gfile.c | 123 ++++++++++++++++++++++++++++ gio/gfile.h | 11 +++ 3 files changed, 136 insertions(+) diff --git a/docs/reference/gio/gio-sections.txt b/docs/reference/gio/gio-sections.txt index e61001f68..f792c5104 100644 --- a/docs/reference/gio/gio-sections.txt +++ b/docs/reference/gio/gio-sections.txt @@ -125,6 +125,8 @@ g_file_query_filesystem_info g_file_query_filesystem_info_async g_file_query_filesystem_info_finish g_file_query_default_handler +g_file_query_default_handler_async +g_file_query_default_handler_finish g_file_measure_disk_usage g_file_measure_disk_usage_async g_file_measure_disk_usage_finish diff --git a/gio/gfile.c b/gio/gfile.c index e6b468b55..c7926478c 100644 --- a/gio/gfile.c +++ b/gio/gfile.c @@ -6885,6 +6885,129 @@ g_file_query_default_handler (GFile *file, return NULL; } +static void +query_default_handler_query_info_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GFile *file = G_FILE (object); + GTask *task = G_TASK (user_data); + GError *error = NULL; + GFileInfo *info; + const char *content_type; + GAppInfo *appinfo = NULL; + + info = g_file_query_info_finish (file, result, &error); + if (info == NULL) + { + g_task_return_error (task, g_steal_pointer (&error)); + g_object_unref (task); + return; + } + + content_type = g_file_info_get_content_type (info); + if (content_type) + { + char *path; + + /* Don't use is_native(), as we want to support fuse paths if available */ + path = g_file_get_path (file); + + /* FIXME: The following still uses blocking calls. */ + appinfo = g_app_info_get_default_for_type (content_type, + path == NULL); + g_free (path); + } + + g_object_unref (info); + + if (appinfo != NULL) + g_task_return_pointer (task, g_steal_pointer (&appinfo), g_object_unref); + else + g_task_return_new_error (task, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("No application is registered as handling this file")); + g_object_unref (task); +} + +/** + * g_file_query_default_handler_async: + * @file: a #GFile to open + * @cancellable: optional #GCancellable object, %NULL to ignore + * @callback: (nullable): a #GAsyncReadyCallback to call when the request is done + * @user_data: (nullable): data to pass to @callback + * + * Async version of g_file_query_default_handler(). + * + * Since: 2.60 + */ +void +g_file_query_default_handler_async (GFile *file, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + char *uri_scheme; + + task = g_task_new (file, cancellable, callback, user_data); + g_task_set_source_tag (task, g_file_query_default_handler_async); + + uri_scheme = g_file_get_uri_scheme (file); + if (uri_scheme && uri_scheme[0] != '\0') + { + GAppInfo *appinfo; + + /* FIXME: The following still uses blocking calls. */ + appinfo = g_app_info_get_default_for_uri_scheme (uri_scheme); + g_free (uri_scheme); + + if (appinfo != NULL) + { + g_task_return_pointer (task, g_steal_pointer (&appinfo), g_object_unref); + g_object_unref (task); + return; + } + } + else + g_free (uri_scheme); + + g_file_query_info_async (file, + G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, + 0, + io_priority, + cancellable, + query_default_handler_query_info_cb, + g_steal_pointer (&task)); +} + +/** + * g_file_query_default_handler_finish: + * @file: a #GFile to open + * @result: a #GAsyncResult + * @error: (nullable): a #GError + * + * Finishes g_file_query_default_handler_async() operation. + * + * Returns: (transfer full): a #GAppInfo if the handle was found, + * %NULL if there were errors. + * When you are done with it, release it with g_object_unref() + * + * Since: 2.60 + */ +GAppInfo * +g_file_query_default_handler_finish (GFile *file, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (G_IS_FILE (file), NULL); + g_return_val_if_fail (g_task_is_valid (result, file), NULL); + + return g_task_propagate_pointer (G_TASK (result), error); +} + #define GET_CONTENT_BLOCK_SIZE 8192 /** diff --git a/gio/gfile.h b/gio/gfile.h index 4aff644c3..6e25b0de0 100644 --- a/gio/gfile.h +++ b/gio/gfile.h @@ -1183,6 +1183,17 @@ GLIB_AVAILABLE_IN_ALL GAppInfo *g_file_query_default_handler (GFile *file, GCancellable *cancellable, GError **error); +GLIB_AVAILABLE_IN_2_60 +void g_file_query_default_handler_async (GFile *file, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GLIB_AVAILABLE_IN_2_60 +GAppInfo *g_file_query_default_handler_finish (GFile *file, + GAsyncResult *result, + GError **error); + GLIB_AVAILABLE_IN_ALL gboolean g_file_load_contents (GFile *file, GCancellable *cancellable, From cec5778cad6fffd829872062eff30b27f196b758 Mon Sep 17 00:00:00 2001 From: Ondrej Holy Date: Tue, 22 Jan 2019 15:39:15 +0100 Subject: [PATCH 3/5] gappinfo: Add launch_uris_async() and launch_uris_finish() vfuncs The g_app_info_launch_uris_async() and g_app_info_launch_uris_finish() functions are crucial to fix g_app_info_launch_default_for_uri_async() to be really asynchronous. This patch also adds GDesktopAppInfo implementation of that vfuncs. The implementation may still use some synchronous calls to local MIME DB. https://gitlab.gnome.org/GNOME/glib/issues/1347 https://gitlab.gnome.org/GNOME/glib/issues/1249 --- docs/reference/gio/gio-sections.txt | 2 + gio/gappinfo.c | 81 ++++++++++++++ gio/gappinfo.h | 25 ++++- gio/gdesktopappinfo.c | 160 ++++++++++++++++++++++++++-- gio/gfile.c | 2 +- 5 files changed, 259 insertions(+), 11 deletions(-) diff --git a/docs/reference/gio/gio-sections.txt b/docs/reference/gio/gio-sections.txt index f792c5104..6aa07b462 100644 --- a/docs/reference/gio/gio-sections.txt +++ b/docs/reference/gio/gio-sections.txt @@ -1448,6 +1448,8 @@ g_app_info_launch g_app_info_supports_files g_app_info_supports_uris g_app_info_launch_uris +g_app_info_launch_uris_async +g_app_info_launch_uris_finish g_app_info_should_show g_app_info_can_delete g_app_info_delete diff --git a/gio/gappinfo.c b/gio/gappinfo.c index 47cd73366..a7798bdd4 100644 --- a/gio/gappinfo.c +++ b/gio/gappinfo.c @@ -24,6 +24,7 @@ #include "gappinfoprivate.h" #include "gcontextspecificgroup.h" #include "gtask.h" +#include "gcancellable.h" #include "glibintl.h" #include @@ -662,6 +663,86 @@ g_app_info_launch_uris (GAppInfo *appinfo, return (* iface->launch_uris) (appinfo, uris, launch_context, error); } +/** + * g_app_info_launch_uris_async: + * @appinfo: a #GAppInfo + * @uris: (nullable) (element-type utf8): a #GList containing URIs to launch. + * @context: (nullable): a #GAppLaunchContext or %NULL + * @cancellable: (nullable): a #GCancellable + * @callback: (nullable): a #GAsyncReadyCallback to call when the request is done + * @user_data: (nullable): data to pass to @callback + * + * Async version of g_app_info_launch_uris(). + * + * The @callback is invoked immediately after the application launch, but it + * waits for activation in case of D-Bus–activated applications and also provides + * extended error information for sandboxed applications, see notes for + * g_app_info_launch_default_for_uri_async(). + * + * Since: 2.60 + **/ +void +g_app_info_launch_uris_async (GAppInfo *appinfo, + GList *uris, + GAppLaunchContext *context, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GAppInfoIface *iface; + + g_return_if_fail (G_IS_APP_INFO (appinfo)); + g_return_if_fail (context == NULL || G_IS_APP_LAUNCH_CONTEXT (context)); + g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); + + iface = G_APP_INFO_GET_IFACE (appinfo); + if (iface->launch_uris_async == NULL) + { + GTask *task; + + task = g_task_new (appinfo, cancellable, callback, user_data); + g_task_set_source_tag (task, g_app_info_launch_uris_async); + g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "Operation not supported for the current backend."); + g_object_unref (task); + + return; + } + + (* iface->launch_uris_async) (appinfo, uris, context, cancellable, callback, user_data); +} + +/** + * g_app_info_launch_uris_finish: + * @appinfo: a #GAppInfo + * @result: a #GAsyncResult + * @error: (nullable): a #GError + * + * Finishes a g_app_info_launch_uris_async() operation. + * + * Returns: %TRUE on successful launch, %FALSE otherwise. + * + * Since: 2.60 + */ +gboolean +g_app_info_launch_uris_finish (GAppInfo *appinfo, + GAsyncResult *result, + GError **error) +{ + GAppInfoIface *iface; + + g_return_val_if_fail (G_IS_APP_INFO (appinfo), FALSE); + + iface = G_APP_INFO_GET_IFACE (appinfo); + if (iface->launch_uris_finish == NULL) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "Operation not supported for the current backend."); + return FALSE; + } + + return (* iface->launch_uris_finish) (appinfo, result, error); +} /** * g_app_info_should_show: diff --git a/gio/gappinfo.h b/gio/gappinfo.h index 4889be923..d26d048a5 100644 --- a/gio/gappinfo.h +++ b/gio/gappinfo.h @@ -78,7 +78,9 @@ typedef struct _GAppLaunchContextPrivate GAppLaunchContextPrivate; * @get_display_name: Gets the display name for the #GAppInfo. Since 2.24 * @set_as_last_used_for_type: Sets the application as the last used. See g_app_info_set_as_last_used_for_type(). * @get_supported_types: Retrieves the list of content types that @app_info claims to support. - * + * @launch_uris_async: Asynchronously launches an application with a list of URIs. (Since: 2.60) + * @launch_uris_finish: Finishes an operation started with @launch_uris_async. (Since: 2.60) + * Application Information interface, for operating system portability. */ typedef struct _GAppInfoIface GAppInfoIface; @@ -131,6 +133,15 @@ struct _GAppInfoIface const char *content_type, GError **error); const char ** (* get_supported_types) (GAppInfo *appinfo); + void (* launch_uris_async) (GAppInfo *appinfo, + GList *uris, + GAppLaunchContext *context, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + gboolean (* launch_uris_finish) (GAppInfo *appinfo, + GAsyncResult *result, + GError **error); }; GLIB_AVAILABLE_IN_ALL @@ -173,6 +184,18 @@ gboolean g_app_info_launch_uris (GAppInfo *appin GList *uris, GAppLaunchContext *context, GError **error); +GLIB_AVAILABLE_IN_2_60 +void g_app_info_launch_uris_async (GAppInfo *appinfo, + GList *uris, + GAppLaunchContext *context, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GLIB_AVAILABLE_IN_2_60 +gboolean g_app_info_launch_uris_finish (GAppInfo *appinfo, + GAsyncResult *result, + GError **error); + GLIB_AVAILABLE_IN_ALL gboolean g_app_info_should_show (GAppInfo *appinfo); diff --git a/gio/gdesktopappinfo.c b/gio/gdesktopappinfo.c index 9ebfce4f0..7d7425ea9 100644 --- a/gio/gdesktopappinfo.c +++ b/gio/gdesktopappinfo.c @@ -2870,7 +2870,10 @@ static void launch_uris_with_dbus (GDesktopAppInfo *info, GDBusConnection *session_bus, GList *uris, - GAppLaunchContext *launch_context) + GAppLaunchContext *launch_context, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { GVariantBuilder builder; gchar *object_path; @@ -2889,14 +2892,11 @@ launch_uris_with_dbus (GDesktopAppInfo *info, g_variant_builder_add_value (&builder, g_desktop_app_info_make_platform_data (info, uris, launch_context)); - /* This is non-blocking API. Similar to launching via fork()/exec() - * we don't wait around to see if the program crashed during startup. - * This is what startup-notification's job is... - */ object_path = object_path_from_appid (info->app_id); g_dbus_connection_call (session_bus, info->app_id, object_path, "org.freedesktop.Application", uris ? "Open" : "Activate", g_variant_builder_end (&builder), - NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL); + NULL, G_DBUS_CALL_FLAGS_NONE, -1, + cancellable, callback, user_data); g_free (object_path); } @@ -2904,7 +2904,10 @@ static gboolean g_desktop_app_info_launch_uris_with_dbus (GDesktopAppInfo *info, GDBusConnection *session_bus, GList *uris, - GAppLaunchContext *launch_context) + GAppLaunchContext *launch_context, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { GList *ruris = uris; char *app_id = NULL; @@ -2921,7 +2924,8 @@ g_desktop_app_info_launch_uris_with_dbus (GDesktopAppInfo *info, } #endif - launch_uris_with_dbus (info, session_bus, ruris, launch_context); + launch_uris_with_dbus (info, session_bus, ruris, launch_context, + cancellable, callback, user_data); if (ruris != uris) g_list_free_full (ruris, g_free); @@ -2952,7 +2956,12 @@ g_desktop_app_info_launch_uris_internal (GAppInfo *appinfo, session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); if (session_bus && info->app_id) - g_desktop_app_info_launch_uris_with_dbus (info, session_bus, uris, launch_context); + /* This is non-blocking API. Similar to launching via fork()/exec() + * we don't wait around to see if the program crashed during startup. + * This is what startup-notification's job is... + */ + g_desktop_app_info_launch_uris_with_dbus (info, session_bus, uris, launch_context, + NULL, NULL, NULL); else success = g_desktop_app_info_launch_uris_with_spawn (info, session_bus, info->exec, uris, launch_context, spawn_flags, user_setup, user_setup_data, @@ -2986,6 +2995,137 @@ g_desktop_app_info_launch_uris (GAppInfo *appinfo, error); } +typedef struct +{ + GAppInfo *appinfo; + GList *uris; + GAppLaunchContext *context; +} 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_with_dbus_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GTask *task = G_TASK (user_data); + GError *error = NULL; + + g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error); + if (error != NULL) + { + g_dbus_error_strip_remote_error (error); + g_task_return_error (task, g_steal_pointer (&error)); + } + else + g_task_return_boolean (task, TRUE); + + g_object_unref (task); +} + +static void +launch_uris_flush_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GTask *task = G_TASK (user_data); + + g_dbus_connection_flush_finish (G_DBUS_CONNECTION (object), result, NULL); + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +launch_uris_bus_get_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GTask *task = G_TASK (user_data); + GDesktopAppInfo *info = G_DESKTOP_APP_INFO (g_task_get_source_object (task)); + LaunchUrisData *data = g_task_get_task_data (task); + GCancellable *cancellable = g_task_get_cancellable (task); + GDBusConnection *session_bus; + GError *error = NULL; + + session_bus = g_bus_get_finish (result, NULL); + + if (session_bus && info->app_id) + { + /* FIXME: The g_document_portal_add_documents() function, which is called + * from the g_desktop_app_info_launch_uris_with_dbus() function, still + * uses blocking calls. + */ + g_desktop_app_info_launch_uris_with_dbus (info, session_bus, + data->uris, data->context, + cancellable, + launch_uris_with_dbus_cb, + g_steal_pointer (&task)); + } + else + { + /* FIXME: The D-Bus message from the notify_desktop_launch() function + * can be still lost even if flush is called later. See: + * https://gitlab.freedesktop.org/dbus/dbus/issues/72 + */ + g_desktop_app_info_launch_uris_with_spawn (info, session_bus, info->exec, + data->uris, data->context, + _SPAWN_FLAGS_DEFAULT, NULL, + NULL, NULL, NULL, -1, -1, -1, + &error); + if (error != NULL) + { + g_task_return_error (task, g_steal_pointer (&error)); + g_object_unref (task); + } + else + g_dbus_connection_flush (session_bus, + cancellable, + launch_uris_flush_cb, + g_steal_pointer (&task)); + } + + g_clear_object (&session_bus); +} + +static void +g_desktop_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_desktop_app_info_launch_uris_async); + + data = g_new0 (LaunchUrisData, 1); + data->uris = g_list_copy_deep (uris, (GCopyFunc) g_strdup, NULL); + data->context = (context != NULL) ? g_object_ref (context) : NULL; + g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) launch_uris_data_free); + + g_bus_get (G_BUS_TYPE_SESSION, cancellable, launch_uris_bus_get_cb, task); +} + +static gboolean +g_desktop_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_desktop_app_info_supports_uris (GAppInfo *appinfo) { @@ -3876,6 +4016,8 @@ g_desktop_app_info_iface_init (GAppInfoIface *iface) iface->supports_uris = g_desktop_app_info_supports_uris; iface->supports_files = g_desktop_app_info_supports_files; iface->launch_uris = g_desktop_app_info_launch_uris; + iface->launch_uris_async = g_desktop_app_info_launch_uris_async; + iface->launch_uris_finish = g_desktop_app_info_launch_uris_finish; iface->should_show = g_desktop_app_info_should_show; iface->set_as_default_for_type = g_desktop_app_info_set_as_default_for_type; iface->set_as_default_for_extension = g_desktop_app_info_set_as_default_for_extension; diff --git a/gio/gfile.c b/gio/gfile.c index c7926478c..1cc69166a 100644 --- a/gio/gfile.c +++ b/gio/gfile.c @@ -6989,7 +6989,7 @@ g_file_query_default_handler_async (GFile *file, * @result: a #GAsyncResult * @error: (nullable): a #GError * - * Finishes g_file_query_default_handler_async() operation. + * Finishes a g_file_query_default_handler_async() operation. * * Returns: (transfer full): a #GAppInfo if the handle was found, * %NULL if there were errors. From 904bb264e90a56f75586369810bbc50c45b3ed2f Mon Sep 17 00:00:00 2001 From: Ondrej Holy Date: Tue, 22 Jan 2019 15:44:10 +0100 Subject: [PATCH 4/5] gappinfo: Use g_app_info_launch_uris_async() for async calls Currently, the g_app_info_launch_default_for_uri_async() function uses g_app_info_launch_uris(), which is not fully asynchronous and may cause for example Nautilus freezes. Let's use the g_app_info_launch_uris_async() function to prevent the freezes. Closes: https://gitlab.gnome.org/GNOME/glib/issues/1347 https://gitlab.gnome.org/GNOME/glib/issues/1249 --- gio/gappinfo.c | 249 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 186 insertions(+), 63 deletions(-) diff --git a/gio/gappinfo.c b/gio/gappinfo.c index a7798bdd4..84e667575 100644 --- a/gio/gappinfo.c +++ b/gio/gappinfo.c @@ -765,15 +765,31 @@ g_app_info_should_show (GAppInfo *appinfo) return (* iface->should_show) (appinfo); } -static gboolean -launch_default_for_uri (const char *uri, - GAppLaunchContext *context, - GError **error) +/** + * g_app_info_launch_default_for_uri: + * @uri: the uri to show + * @context: (nullable): an optional #GAppLaunchContext + * @error: (nullable): return location for an error, or %NULL + * + * Utility function that launches the default application + * registered to handle the specified uri. Synchronous I/O + * is done on the uri to detect the type of the file if + * required. + * + * The D-Bus–activated applications don't have to be started if your application + * terminates too soon after this function. To prevent this, use + * g_app_info_launch_default_for_uri() instead. + * + * Returns: %TRUE on success, %FALSE on error. + **/ +gboolean +g_app_info_launch_default_for_uri (const char *uri, + GAppLaunchContext *launch_context, + GError **error) { char *uri_scheme; GAppInfo *app_info = NULL; - GList l; - gboolean res; + gboolean res = FALSE; /* g_file_query_default_handler() calls * g_app_info_get_default_for_uri_scheme() too, but we have to do it @@ -793,41 +809,18 @@ launch_default_for_uri (const char *uri, g_object_unref (file); } - if (app_info == NULL) - return FALSE; + if (app_info) + { + GList l; - l.data = (char *)uri; - l.next = l.prev = NULL; - res = g_app_info_launch_uris (app_info, &l, context, error); - - g_object_unref (app_info); - - return res; -} - -/** - * g_app_info_launch_default_for_uri: - * @uri: the uri to show - * @context: (nullable): an optional #GAppLaunchContext - * @error: (nullable): return location for an error, or %NULL - * - * Utility function that launches the default application - * registered to handle the specified uri. Synchronous I/O - * is done on the uri to detect the type of the file if - * required. - * - * Returns: %TRUE on success, %FALSE on error. - **/ -gboolean -g_app_info_launch_default_for_uri (const char *uri, - GAppLaunchContext *launch_context, - GError **error) -{ - if (launch_default_for_uri (uri, launch_context, error)) - return TRUE; + l.data = (char *)uri; + l.next = l.prev = NULL; + res = g_app_info_launch_uris (app_info, &l, launch_context, error); + g_object_unref (app_info); + } #ifdef G_OS_UNIX - if (glib_should_use_portal ()) + if (!res && glib_should_use_portal ()) { const char *parent_window = NULL; @@ -838,11 +831,126 @@ g_app_info_launch_default_for_uri (const char *uri, parent_window = g_environ_getenv (launch_context->priv->envp, "PARENT_WINDOW_ID"); return g_openuri_portal_open_uri (uri, parent_window, error); - } #endif - return FALSE; + return res; +} + +typedef struct +{ + gchar *uri; + GAppLaunchContext *context; +} LaunchDefaultForUriData; + +static void +launch_default_for_uri_data_free (LaunchDefaultForUriData *data) +{ + g_free (data->uri); + g_clear_object (&data->context); + g_free (data); +} + +#ifdef G_OS_UNIX +static void +launch_default_for_uri_portal_open_uri_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GTask *task = G_TASK (user_data); + GError *error = NULL; + + if (g_openuri_portal_open_uri_finish (result, &error)) + g_task_return_boolean (task, TRUE); + else + g_task_return_error (task, g_steal_pointer (&error)); + g_object_unref (task); +} +#endif + +static void +launch_default_for_uri_portal_open_uri (GTask *task, GError *error) +{ +#ifdef G_OS_UNIX + LaunchDefaultForUriData *data = g_task_get_task_data (task); + GCancellable *cancellable = g_task_get_cancellable (task); + + if (glib_should_use_portal ()) + { + const char *parent_window = NULL; + + /* Reset any error previously set by launch_default_for_uri */ + g_error_free (error); + + if (data->context && data->context->priv->envp) + parent_window = g_environ_getenv (data->context->priv->envp, + "PARENT_WINDOW_ID"); + + g_openuri_portal_open_uri_async (data->uri, + parent_window, + cancellable, + launch_default_for_uri_portal_open_uri_cb, + g_steal_pointer (&task)); + return; + } +#endif + + g_task_return_error (task, g_steal_pointer (&error)); + g_object_unref (task); +} + +static void +launch_default_for_uri_launch_uris_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GAppInfo *app_info = G_APP_INFO (object); + GTask *task = G_TASK (user_data); + GError *error = NULL; + + if (g_app_info_launch_uris_finish (app_info, result, &error)) + { + g_task_return_boolean (task, TRUE); + g_object_unref (task); + } + else + launch_default_for_uri_portal_open_uri (g_steal_pointer (&task), g_steal_pointer (&error)); +} + +static void +launch_default_for_uri_launch_uris (GTask *task, + GAppInfo *app_info) +{ + GCancellable *cancellable = g_task_get_cancellable (task); + GList l; + LaunchDefaultForUriData *data = g_task_get_task_data (task); + + l.data = (char *)data->uri; + l.next = l.prev = NULL; + g_app_info_launch_uris_async (app_info, + &l, + data->context, + cancellable, + launch_default_for_uri_launch_uris_cb, + g_steal_pointer (&task)); + g_object_unref (app_info); +} + +static void +launch_default_for_uri_default_handler_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GFile *file = G_FILE (object); + GTask *task = G_TASK (user_data); + GAppInfo *app_info = NULL; + GError *error = NULL; + + app_info = g_file_query_default_handler_finish (file, result, &error); + if (app_info) + launch_default_for_uri_launch_uris (g_steal_pointer (&task), g_steal_pointer (&app_info)); + else + launch_default_for_uri_portal_open_uri (g_steal_pointer (&task), g_steal_pointer (&error)); } /** @@ -860,6 +968,10 @@ g_app_info_launch_default_for_uri (const char *uri, * sandboxed and the portal may present an application chooser * dialog to the user. * + * This is also useful if you want to be sure that the D-Bus–activated + * applications are really started before termination and if you are interested + * in receiving error information from their activation. + * * Since: 2.50 */ void @@ -869,32 +981,45 @@ g_app_info_launch_default_for_uri_async (const char *uri, GAsyncReadyCallback callback, gpointer user_data) { - gboolean res; - GError *error = NULL; GTask *task; + char *uri_scheme; + GAppInfo *app_info = NULL; + LaunchDefaultForUriData *data; - res = launch_default_for_uri (uri, context, &error); + g_return_if_fail (uri != NULL); -#ifdef G_OS_UNIX - if (!res && glib_should_use_portal ()) + task = g_task_new (NULL, cancellable, callback, user_data); + g_task_set_source_tag (task, g_app_info_launch_default_for_uri_async); + + data = g_new (LaunchDefaultForUriData, 1); + data->uri = g_strdup (uri); + data->context = (context != NULL) ? g_object_ref (context) : NULL; + g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) launch_default_for_uri_data_free); + + /* g_file_query_default_handler_async() calls + * g_app_info_get_default_for_uri_scheme() too, but we have to do it + * here anyway in case GFile can't parse @uri correctly. + */ + uri_scheme = g_uri_parse_scheme (uri); + if (uri_scheme && uri_scheme[0] != '\0') + /* FIXME: The following still uses blocking calls. */ + app_info = g_app_info_get_default_for_uri_scheme (uri_scheme); + g_free (uri_scheme); + + if (!app_info) { - const char *parent_window = NULL; + GFile *file; - if (context && context->priv->envp) - parent_window = g_environ_getenv (context->priv->envp, "PARENT_WINDOW_ID"); - - g_openuri_portal_open_uri_async (uri, parent_window, cancellable, callback, user_data); - return; + file = g_file_new_for_uri (uri); + g_file_query_default_handler_async (file, + G_PRIORITY_DEFAULT, + cancellable, + launch_default_for_uri_default_handler_cb, + g_steal_pointer (&task)); + g_object_unref (file); } -#endif - - task = g_task_new (context, cancellable, callback, user_data); - if (!res) - g_task_return_error (task, error); else - g_task_return_boolean (task, TRUE); - - g_object_unref (task); + launch_default_for_uri_launch_uris (g_steal_pointer (&task), g_steal_pointer (&app_info)); } /** @@ -912,11 +1037,9 @@ gboolean g_app_info_launch_default_for_uri_finish (GAsyncResult *result, GError **error) { -#ifdef G_OS_UNIX - return g_openuri_portal_open_uri_finish (result, error); -#else + g_return_val_if_fail (g_task_is_valid (result, NULL), FALSE); + return g_task_propagate_boolean (G_TASK (result), error); -#endif } /** From 051c6ba4e7111b04ab417403730b82de02a1c0d8 Mon Sep 17 00:00:00 2001 From: Ondrej Holy Date: Tue, 22 Jan 2019 16:29:20 +0100 Subject: [PATCH 5/5] gio-tool-open: Use g_app_info_launch_default_for_uri_async() The recent changes of the g_app_info_launch_default_for_uri_async() function ensures that the callback is not called before DBus-activated applications start. Let's use g_app_info_launch_default_for_uri_async() and remove the workarounds for DBus-activated applications. Closes: https://gitlab.gnome.org/GNOME/glib/issues/1249 --- gio/gio-tool-open.c | 118 ++++++++++---------------------------------- 1 file changed, 25 insertions(+), 93 deletions(-) diff --git a/gio/gio-tool-open.c b/gio/gio-tool-open.c index 73863c7c5..ac6764a97 100644 --- a/gio/gio-tool-open.c +++ b/gio/gio-tool-open.c @@ -29,73 +29,32 @@ #include "gio-tool.h" +static int n_outstanding = 0; +static gboolean success = TRUE; static const GOptionEntry entries[] = { { NULL } }; -#if defined(G_OS_UNIX) && !defined(HAVE_COCOA) -static gboolean -get_bus_name_and_path_from_uri (const char *uri, - char **bus_name_out, - char **object_path_out) +static void +launch_default_for_uri_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) { - GAppInfo *app_info = NULL; - char *bus_name = NULL; - char *object_path = NULL; - char *uri_scheme; - const char *filename; - char *basename = NULL; - char *p; - gboolean got_name = FALSE; + GError *error = NULL; + gchar *uri = user_data; - uri_scheme = g_uri_parse_scheme (uri); - if (uri_scheme && uri_scheme[0] != '\0') - app_info = g_app_info_get_default_for_uri_scheme (uri_scheme); - g_free (uri_scheme); - - if (app_info == NULL) + if (!g_app_info_launch_default_for_uri_finish (res, &error)) { - GFile *file; - - file = g_file_new_for_uri (uri); - app_info = g_file_query_default_handler (file, NULL, NULL); - g_object_unref (file); + print_error ("%s: %s", uri, error->message); + g_clear_error (&error); + success = FALSE; } - if (app_info == NULL || !G_IS_DESKTOP_APP_INFO (app_info) || - !g_desktop_app_info_get_boolean (G_DESKTOP_APP_INFO (app_info), "DBusActivatable")) - goto out; + n_outstanding--; - filename = g_desktop_app_info_get_filename (G_DESKTOP_APP_INFO (app_info)); - if (filename == NULL) - goto out; - - basename = g_path_get_basename (filename); - if (!g_str_has_suffix (basename, ".desktop")) - goto out; - - basename[strlen (basename) - strlen (".desktop")] = '\0'; - if (!g_dbus_is_name (basename)) - goto out; - - bus_name = g_strdup (basename); - object_path = g_strdup_printf ("/%s", bus_name); - for (p = object_path; *p != '\0'; p++) - if (*p == '.') - *p = '/'; - - *bus_name_out = g_steal_pointer (&bus_name); - *object_path_out = g_steal_pointer (&object_path); - got_name = TRUE; - -out: - g_clear_object (&app_info); - g_clear_pointer (&basename, g_free); - - return got_name; + g_free (uri); } -#endif int handle_open (int argc, char *argv[], gboolean do_help) @@ -104,8 +63,6 @@ handle_open (int argc, char *argv[], gboolean do_help) gchar *param; GError *error = NULL; int i; - gboolean success; - gboolean res; g_set_prgname ("gio open"); @@ -143,7 +100,6 @@ handle_open (int argc, char *argv[], gboolean do_help) g_option_context_free (context); - success = TRUE; for (i = 1; i < argc; i++) { char *uri = NULL; @@ -162,47 +118,23 @@ handle_open (int argc, char *argv[], gboolean do_help) uri = g_file_get_uri (file); g_object_unref (file); } + else + uri = g_strdup (argv[i]); g_free (uri_scheme); - res = g_app_info_launch_default_for_uri (uri ? uri : argv[i], NULL, &error); - if (!res) - { - print_error ("%s: %s", uri ? uri : argv[i], error->message); - g_clear_error (&error); - success = FALSE; - } + g_app_info_launch_default_for_uri_async (uri, + NULL, + NULL, + launch_default_for_uri_cb, + g_strdup (uri)); -#if defined(G_OS_UNIX) && !defined(HAVE_COCOA) - /* FIXME: This chunk of madness is a workaround for a dbus-daemon bug. - * See https://bugzilla.gnome.org/show_bug.cgi?id=780296 - */ - if (res) - { - char *bus_name = NULL; - char *object_path = NULL; - - if (get_bus_name_and_path_from_uri (uri ? uri : argv[i], &bus_name, &object_path)) - { - GDBusConnection *connection; - connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); - - if (connection) - g_dbus_connection_call_sync (connection, - bus_name, - object_path, - "org.freedesktop.DBus.Peer", - "Ping", - NULL, NULL, - G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL); - g_clear_object (&connection); - g_free (bus_name); - g_free (object_path); - } - } -#endif + n_outstanding++; g_free (uri); } + while (n_outstanding > 0) + g_main_context_iteration (NULL, TRUE); + return success ? 0 : 2; }