From 42cd230d4476012deafe298871ab973fa9809df0 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Tue, 29 Nov 2022 18:45:22 -0500 Subject: openuriportal: Better error handling Nested async calls are always a challenge. Hopefully, things are straightened out now, and we report GTK_DIALOG_ERROR errors for the cases we care about. --- gtk/gopenuriportal.c | 306 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 183 insertions(+), 123 deletions(-) (limited to 'gtk/gopenuriportal.c') diff --git a/gtk/gopenuriportal.c b/gtk/gopenuriportal.c index 715622ed32..16c63cbd9a 100644 --- a/gtk/gopenuriportal.c +++ b/gtk/gopenuriportal.c @@ -29,6 +29,8 @@ #include "gopenuriportal.h" #include "xdp-dbus.h" #include "gtkwindowprivate.h" +#include "gtkprivate.h" +#include "gtkdialogerror.h" #ifdef G_OS_UNIX #include @@ -56,8 +58,8 @@ init_openuri_portal (void) if (connection != NULL) { openuri = gxdp_open_uri_proxy_new_sync (connection, 0, - "org.freedesktop.portal.Desktop", - "/org/freedesktop/portal/desktop", + PORTAL_BUS_NAME, + PORTAL_OBJECT_PATH, NULL, &error); if (openuri == NULL) { @@ -92,6 +94,43 @@ enum { XDG_DESKTOP_PORTAL_FAILED = 2 }; +enum { + OPEN_URI = 0, + OPEN_FILE = 1, + OPEN_FOLDER = 2 +}; + +typedef struct { + GtkWindow *parent; + GFile *file; + gboolean open_folder; + GDBusConnection *connection; + GCancellable *cancellable; + GTask *task; + char *handle; + guint signal_id; + glong cancel_handler; + int call; +} OpenUriData; + +static void +open_uri_data_free (OpenUriData *data) +{ + if (data->signal_id) + g_dbus_connection_signal_unsubscribe (data->connection, data->signal_id); + g_clear_object (&data->connection); + if (data->cancel_handler) + g_signal_handler_disconnect (data->cancellable, data->cancel_handler); + if (data->parent) + gtk_window_unexport_handle (data->parent); + g_clear_object (&data->parent); + g_clear_object (&data->file); + g_clear_object (&data->cancellable); + g_clear_object (&data->task); + g_free (data->handle); + g_free (data); +} + static void response_received (GDBusConnection *connection, const char *sender_name, @@ -103,10 +142,6 @@ response_received (GDBusConnection *connection, { GTask *task = user_data; guint32 response; - guint signal_id; - - signal_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (task), "signal-id")); - g_dbus_connection_signal_unsubscribe (connection, signal_id); g_variant_get (parameters, "(u@a{sv})", &response, NULL); @@ -116,11 +151,11 @@ response_received (GDBusConnection *connection, g_task_return_boolean (task, TRUE); break; case XDG_DESKTOP_PORTAL_CANCELLED: - g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_CANCELLED, "Launch cancelled"); + g_task_return_new_error (task, GTK_DIALOG_ERROR, GTK_DIALOG_ERROR_CANCELLED, "The portal dialog was closed"); break; case XDG_DESKTOP_PORTAL_FAILED: default: - g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, "Launch failed"); + g_task_return_new_error (task, GTK_DIALOG_ERROR, GTK_DIALOG_ERROR_FAILED, "The application launch failed"); break; } @@ -133,108 +168,145 @@ open_call_done (GObject *source, gpointer user_data) { GXdpOpenURI *portal = GXDP_OPEN_URI (source); - GDBusConnection *connection; GTask *task = user_data; + OpenUriData *data = g_task_get_task_data (task); GError *error = NULL; - const char *call; gboolean res; char *path = NULL; - const char *handle; - guint signal_id; - - connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (portal)); - call = (const char *) g_object_get_data (G_OBJECT (task), "call"); - if (g_str_equal (call, "OpenFile")) - res = gxdp_open_uri_call_open_file_finish (portal, &path, NULL, result, &error); - else if (g_str_equal (call, "OpenDirectory")) - res = gxdp_open_uri_call_open_directory_finish (portal, &path, NULL, result, &error); - else - res = gxdp_open_uri_call_open_uri_finish (portal, &path, result, &error); + switch (data->call) + { + case OPEN_FILE: + res = gxdp_open_uri_call_open_file_finish (portal, &path, NULL, result, &error); + break; + case OPEN_FOLDER: + res = gxdp_open_uri_call_open_directory_finish (portal, &path, NULL, result, &error); + break; + case OPEN_URI: + res = gxdp_open_uri_call_open_uri_finish (portal, &path, result, &error); + break; + default: + g_assert_not_reached (); + break; + } if (!res) { + g_free (path); g_task_return_error (task, error); g_object_unref (task); - g_free (path); return; } - handle = (const char *)g_object_get_data (G_OBJECT (task), "handle"); - if (g_strcmp0 (handle, path) != 0) + if (g_strcmp0 (data->handle, path) != 0) { - signal_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (task), "signal-id")); - g_dbus_connection_signal_unsubscribe (connection, signal_id); - - signal_id = g_dbus_connection_signal_subscribe (connection, - "org.freedesktop.portal.Desktop", - "org.freedesktop.portal.Request", - "Response", - path, - NULL, - G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE, - response_received, - task, - NULL); - g_object_set_data (G_OBJECT (task), "signal-id", GINT_TO_POINTER (signal_id)); + g_dbus_connection_signal_unsubscribe (data->connection, data->signal_id); + + data->signal_id = g_dbus_connection_signal_subscribe (data->connection, + PORTAL_BUS_NAME, + PORTAL_REQUEST_INTERFACE, + "Response", + path, + NULL, + G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE, + response_received, + task, + NULL); + g_free (data->handle); + data->handle = g_strdup (path); } + + g_free (path); +} + +static void +send_close (OpenUriData *data) +{ + GDBusMessage *message; + GError *error = NULL; + + message = g_dbus_message_new_method_call (PORTAL_BUS_NAME, + PORTAL_OBJECT_PATH, + PORTAL_REQUEST_INTERFACE, + "Close"); + g_dbus_message_set_body (message, g_variant_new ("(o)", data->handle)); + + if (!g_dbus_connection_send_message (data->connection, + message, + G_DBUS_SEND_MESSAGE_FLAGS_NONE, + NULL, &error)) + { + g_warning ("unable to send Close message: %s", error->message); + g_error_free (error); + } + + g_object_unref (message); +} + +static void +canceled (GCancellable *cancellable, + GTask *task) +{ + OpenUriData *data = g_task_get_task_data (task); + + send_close (data); + + g_task_return_new_error (task, + GTK_DIALOG_ERROR, GTK_DIALOG_ERROR_ABORTED, + "The OpenURI portal call was cancelled programmatically"); + g_object_unref (task); } static void open_uri (GFile *file, gboolean open_folder, const char *parent_window, - GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { + OpenUriData *data = user_data; GTask *task; GVariant *opts = NULL; int i; - guint signal_id; - - if (callback) - { - GDBusConnection *connection; - GVariantBuilder opt_builder; - char *token; - char *sender; - char *handle; - - connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (openuri)); - - task = g_task_new (NULL, cancellable, callback, user_data); - - token = g_strdup_printf ("gtk%d", g_random_int_range (0, G_MAXINT)); - sender = g_strdup (g_dbus_connection_get_unique_name (connection) + 1); - for (i = 0; sender[i]; i++) - if (sender[i] == '.') - sender[i] = '_'; - - handle = g_strdup_printf ("/org/freedesktop/portal/desktop/request/%s/%s", sender, token); - g_object_set_data_full (G_OBJECT (task), "handle", handle, g_free); - g_free (sender); - - signal_id = g_dbus_connection_signal_subscribe (connection, - "org.freedesktop.portal.Desktop", - "org.freedesktop.portal.Request", - "Response", - handle, - NULL, - G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE, - response_received, - task, - NULL); - g_object_set_data (G_OBJECT (task), "signal-id", GINT_TO_POINTER (signal_id)); - - g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); - g_variant_builder_add (&opt_builder, "{sv}", "handle_token", g_variant_new_string (token)); - g_free (token); - - opts = g_variant_builder_end (&opt_builder); - } - else - task = NULL; + GDBusConnection *connection; + GVariantBuilder opt_builder; + char *token; + char *sender; + + connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (openuri)); + data->connection = g_object_ref (connection); + + task = g_task_new (NULL, NULL, callback, user_data); + g_task_set_check_cancellable (task, FALSE); + g_task_set_task_data (task, user_data, NULL); + if (data->cancellable) + data->cancel_handler = g_signal_connect (data->cancellable, "cancelled", G_CALLBACK (canceled), task); + + token = g_strdup_printf ("gtk%d", g_random_int_range (0, G_MAXINT)); + sender = g_strdup (g_dbus_connection_get_unique_name (connection) + 1); + for (i = 0; sender[i]; i++) + if (sender[i] == '.') + sender[i] = '_'; + + data->handle = g_strdup_printf ("/org/freedesktop/portal/desktop/request/%s/%s", sender, token); + g_free (sender); + + data->signal_id = g_dbus_connection_signal_subscribe (connection, + PORTAL_BUS_NAME, + PORTAL_REQUEST_INTERFACE, + "Response", + data->handle, + NULL, + G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE, + response_received, + task, + NULL); + + g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add (&opt_builder, "{sv}", "handle_token", g_variant_new_string (token)); + g_free (token); + + opts = g_variant_builder_end (&opt_builder); if (g_file_is_native (file)) { @@ -247,9 +319,10 @@ open_uri (GFile *file, errsv = errno; if (fd == -1) { - g_task_report_new_error (NULL, callback, user_data, NULL, + g_task_return_new_error (task, G_IO_ERROR, g_io_error_from_errno (errsv), - "OpenURI portal is not available"); + "Failed to open file"); + g_object_unref (task); return; } @@ -262,30 +335,26 @@ open_uri (GFile *file, if (open_folder) { - if (task) - g_object_set_data (G_OBJECT (task), "call", (gpointer) "OpenDirectory"); - + data->call = OPEN_FOLDER; gxdp_open_uri_call_open_directory (openuri, parent_window ? parent_window : "", g_variant_new ("h", fd_id), opts, fd_list, - cancellable, - task ? open_call_done : NULL, + NULL, + open_call_done, task); } else { - if (task) - g_object_set_data (G_OBJECT (task), "call", (gpointer) "OpenFile"); - + data->call = OPEN_FILE; gxdp_open_uri_call_open_file (openuri, parent_window ? parent_window : "", g_variant_new ("h", fd_id), opts, fd_list, - cancellable, - task ? open_call_done : NULL, + NULL, + open_call_done, task); } @@ -295,39 +364,18 @@ open_uri (GFile *file, { char *uri = g_file_get_uri (file); - if (task) - g_object_set_data (G_OBJECT (task), "call", (gpointer) "OpenURI"); - + data->call = OPEN_URI; gxdp_open_uri_call_open_uri (openuri, parent_window ? parent_window : "", uri, opts, - cancellable, - task ? open_call_done : NULL, + NULL, + open_call_done, task); - g_free (uri); } } -typedef struct { - GtkWindow *parent; - GFile *file; - gboolean open_folder; - GTask *task; -} OpenUriData; - -static void -open_uri_data_free (OpenUriData *data) -{ - if (data->parent) - gtk_window_unexport_handle (data->parent); - g_clear_object (&data->parent); - g_clear_object (&data->file); - g_clear_object (&data->task); - g_free (data); -} - static void open_uri_done (GObject *source, GAsyncResult *result, @@ -337,7 +385,17 @@ open_uri_done (GObject *source, GError *error = NULL; if (!g_task_propagate_boolean (G_TASK (result), &error)) - g_task_return_error (data->task, error); + { + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + g_error_free (error); + g_task_return_new_error (data->task, + GTK_DIALOG_ERROR, GTK_DIALOG_ERROR_ABORTED, + "The operation was aborted programmatically"); + } + else + g_task_return_error (data->task, error); + } else g_task_return_boolean (data->task, TRUE); @@ -351,7 +409,7 @@ window_handle_exported (GtkWindow *window, { OpenUriData *data = user_data; - open_uri (data->file, data->open_folder, handle, g_task_get_cancellable (data->task), open_uri_done, data); + open_uri (data->file, data->open_folder, handle, open_uri_done, data); } void @@ -367,8 +425,8 @@ g_openuri_portal_open_async (GFile *file, if (!init_openuri_portal ()) { g_task_report_new_error (NULL, callback, user_data, NULL, - G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED, - "OpenURI portal is not available"); + GTK_DIALOG_ERROR, GTK_DIALOG_ERROR_FAILED, + "The OpenURI portal is not available"); return; } @@ -376,7 +434,9 @@ g_openuri_portal_open_async (GFile *file, data->parent = parent ? g_object_ref (parent) : NULL; data->file = g_object_ref (file); data->open_folder = open_folder; + data->cancellable = cancellable ? g_object_ref (cancellable) : NULL; data->task = g_task_new (parent, cancellable, callback, user_data); + g_task_set_check_cancellable (data->task, FALSE); g_task_set_source_tag (data->task, g_openuri_portal_open_async); if (!parent || !gtk_window_export_handle (parent, window_handle_exported, data)) -- cgit v1.2.1