diff options
author | Alexander Larsson <alexl@redhat.com> | 2015-11-11 16:31:53 +0100 |
---|---|---|
committer | Matthias Clasen <mclasen@redhat.com> | 2016-07-08 00:07:42 -0400 |
commit | 01d0bac83e7f12338d12e5669c408da3078f6010 (patch) | |
tree | acb09d092a909dd84f5ef9b777c5723421a98cb8 /gtk/gtkfilechoosernativeportal.c | |
parent | e47957406d60345efa624dea6517f7d793fc1a13 (diff) | |
download | gtk+-01d0bac83e7f12338d12e5669c408da3078f6010.tar.gz |
Initial version of file chooser portal support
Implement GtkFileChooserNative for sandboxed applications
by talking to org.freedesktop.portal.FileChooser. Currently,
this supports OPEN and SAVE mode.
https://bugzilla.gnome.org/show_bug.cgi?id=768499
Diffstat (limited to 'gtk/gtkfilechoosernativeportal.c')
-rw-r--r-- | gtk/gtkfilechoosernativeportal.c | 392 |
1 files changed, 392 insertions, 0 deletions
diff --git a/gtk/gtkfilechoosernativeportal.c b/gtk/gtkfilechoosernativeportal.c new file mode 100644 index 0000000000..a43ca60ddb --- /dev/null +++ b/gtk/gtkfilechoosernativeportal.c @@ -0,0 +1,392 @@ +/* -*- Mode: C; c-file-style: "gnu"; tab-width: 8 -*- */ +/* GTK - The GIMP Toolkit + * gtkfilechoosernativeportal.c: Portal File selector dialog + * Copyright (C) 2015, Red Hat, Inc. + * + * 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 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 "config.h" + +#include "gtkfilechoosernativeprivate.h" +#include "gtknativedialogprivate.h" + +#include "gtkprivate.h" +#include "gtkfilechooserdialog.h" +#include "gtkfilechooserprivate.h" +#include "gtkfilechooserwidget.h" +#include "gtkfilechooserwidgetprivate.h" +#include "gtkfilechooserutils.h" +#include "gtkfilechooserembed.h" +#include "gtkfilesystem.h" +#include "gtksizerequest.h" +#include "gtktypebuiltins.h" +#include "gtkintl.h" +#include "gtksettings.h" +#include "gtktogglebutton.h" +#include "gtkstylecontext.h" +#include "gtkheaderbar.h" +#include "gtklabel.h" +#include "gtkmain.h" +#include "gtkinvisible.h" +#include "gtkfilechooserentry.h" +#include "gtkfilefilterprivate.h" + +#ifdef GDK_WINDOWING_X11 +#include "x11/gdkx.h" +#endif + +typedef struct { + GtkFileChooserNative *self; + + GtkWidget *grab_widget; + + GDBusConnection *connection; + char *portal_handle; + guint portal_response_signal_id; + gboolean modal; + + gboolean hidden; + +} FilechooserPortalData; + + +static void +filechooser_portal_data_free (FilechooserPortalData *data) +{ + if (data->portal_response_signal_id != 0) + g_dbus_connection_signal_unsubscribe (data->connection, + data->portal_response_signal_id); + + g_object_unref (data->connection); + + if (data->grab_widget) + { + gtk_grab_remove (data->grab_widget); + gtk_widget_destroy (data->grab_widget); + } + + g_clear_object (&data->self); + + g_free (data->portal_handle); + + g_free (data); +} + +static void +one_file_response (GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + GtkFileChooserNative *self = user_data; + FilechooserPortalData *data = self->mode_data; + guint32 portal_response; + int gtk_response; + char *uri, *handle; + GVariant *response_data; + + g_variant_get (parameters, "(&ou&s@a{sv})", &handle, &portal_response, &uri, &response_data); + + if (data->portal_handle == NULL || + strcmp (handle, data->portal_handle) != 0) + return; + + g_slist_free_full (self->custom_files, g_object_unref); + self->custom_files = g_slist_prepend (NULL, g_file_new_for_uri (uri)); + + switch (portal_response) + { + case 0: + gtk_response = GTK_RESPONSE_OK; + break; + case 1: + gtk_response = GTK_RESPONSE_CANCEL; + break; + case 2: + default: + gtk_response = GTK_RESPONSE_DELETE_EVENT; + break; + } + + filechooser_portal_data_free (data); + self->mode_data = NULL; + + _gtk_native_dialog_emit_response (GTK_NATIVE_DIALOG (self), gtk_response); +} + +static void +multi_file_response (GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + GtkFileChooserNative *self = user_data; + FilechooserPortalData *data = self->mode_data; + guint32 portal_response; + int gtk_response; + char *handle; + char **uris; + int i; + GVariant *response_data; + + g_variant_get (parameters, "(&ou^a&s@a{sv})", &handle, &portal_response, &uris, &response_data); + + if (data->portal_handle == NULL || + strcmp (handle, data->portal_handle) != 0) + return; + + g_slist_free_full (self->custom_files, g_object_unref); + self->custom_files = NULL; + for (i = 0; uris[i]; i++) + self->custom_files = g_slist_prepend (self->custom_files, g_file_new_for_uri (uris[i])); + + switch (portal_response) + { + case 0: + gtk_response = GTK_RESPONSE_OK; + break; + case 1: + gtk_response = GTK_RESPONSE_CANCEL; + break; + case 2: + default: + gtk_response = GTK_RESPONSE_DELETE_EVENT; + break; + } + + filechooser_portal_data_free (data); + self->mode_data = NULL; + + _gtk_native_dialog_emit_response (GTK_NATIVE_DIALOG (self), gtk_response); +} + +static void +send_close (FilechooserPortalData *data) +{ + GDBusMessage *message; + GError *error = NULL; + + message = g_dbus_message_new_method_call ("org.freedesktop.portal.Desktop", + "/org/freedesktop/portal/desktop", + "org.freedesktop.portal.FileChooser", + "Close"); + g_dbus_message_set_body (message, + g_variant_new ("(o)", data->portal_handle)); + + if (!g_dbus_connection_send_message (data->connection, + message, + G_DBUS_SEND_MESSAGE_FLAGS_NONE, + NULL, &error)) + { + g_warning ("unable to send FileChooser Close message: %s", error->message); + g_error_free (error); + } + + g_object_unref (message); +} + +static void +open_file_msg_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + FilechooserPortalData *data = user_data; + GtkFileChooserNative *self = data->self; + GDBusMessage *reply; + GError *error = NULL; + + reply = g_dbus_connection_send_message_with_reply_finish (data->connection, res, &error); + + if (reply && g_dbus_message_to_gerror (reply, &error)) + g_clear_object (&reply); + + if (reply == NULL) + { + if (!data->hidden) + _gtk_native_dialog_emit_response (GTK_NATIVE_DIALOG (self), GTK_RESPONSE_DELETE_EVENT); + g_warning ("Can't open portal file chooser: %s", error->message); + g_error_free (error); + filechooser_portal_data_free (data); + self->mode_data = NULL; + return; + } + + g_variant_get_child (g_dbus_message_get_body (reply), 0, "o", + &data->portal_handle); + + if (data->hidden) + { + /* The dialog was hidden before we got the handle, close it now */ + send_close (data); + filechooser_portal_data_free (data); + self->mode_data = NULL; + } + + g_object_unref (reply); +} + +gboolean +gtk_file_chooser_native_portal_show (GtkFileChooserNative *self) +{ + FilechooserPortalData *data; + GtkWindow *transient_for; + guint update_preview_signal; + GDBusConnection *connection; + char *parent_window_str; + GDBusMessage *message; + GVariantBuilder opt_builder; + GtkFileChooserAction action; + gboolean multiple; + const char *method_name; + const char *signal_name; + GDBusSignalCallback signal_callback; + + if (g_getenv ("GTK_USE_PORTAL") == NULL) + return FALSE; + + if (gtk_file_chooser_get_extra_widget (GTK_FILE_CHOOSER (self)) != NULL) + return FALSE; + + update_preview_signal = g_signal_lookup ("update-preview", GTK_TYPE_FILE_CHOOSER); + if (g_signal_has_handler_pending (self, update_preview_signal, 0, TRUE)) + return FALSE; + + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); + if (connection == NULL) + return FALSE; + + action = gtk_file_chooser_get_action (GTK_FILE_CHOOSER (self)); + multiple = gtk_file_chooser_get_select_multiple (GTK_FILE_CHOOSER (self)); + + if (action == GTK_FILE_CHOOSER_ACTION_OPEN && !multiple) + { + method_name = "OpenFile"; + signal_name = "OpenFileResponse"; + signal_callback = one_file_response; + } + else if (action == GTK_FILE_CHOOSER_ACTION_OPEN && multiple) + { + method_name = "OpenFiles"; + signal_name = "OpenFilesResponse"; + signal_callback = multi_file_response; + } + else if (action == GTK_FILE_CHOOSER_ACTION_SAVE) + { + method_name = "SaveFile"; + signal_name = "SaveFileResponse"; + signal_callback = one_file_response; + } + else + { + g_warning ("GTK_FILE_CHOOSER_ACTION_%s is not supported by GtkFileChooserNativePortal", action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER ? "SELECT_FOLDER" : "CREATE_FOLDER"); + return FALSE; + } + + data = g_new0 (FilechooserPortalData, 1); + data->self = g_object_ref (self); + data->connection = connection; + + message = g_dbus_message_new_method_call ("org.freedesktop.portal.Desktop", + "/org/freedesktop/portal/desktop", + "org.freedesktop.portal.FileChooser", + method_name); + + parent_window_str = NULL; + transient_for = gtk_native_dialog_get_transient_for (GTK_NATIVE_DIALOG (self)); + if (transient_for != NULL && gtk_widget_is_visible (GTK_WIDGET (transient_for))) + { + GdkWindow *window = gtk_widget_get_window (GTK_WIDGET (transient_for)); +#ifdef GDK_WINDOWING_X11 + if (GDK_IS_X11_WINDOW(window)) + parent_window_str = g_strdup_printf ("x11:%x", (guint32)gdk_x11_window_get_xid (window)); +#endif + } + + if (gtk_native_dialog_get_modal (GTK_NATIVE_DIALOG (self))) + data->modal = TRUE; + + if (data->modal && transient_for) + { + data->grab_widget = gtk_invisible_new_for_screen (gtk_widget_get_screen (GTK_WIDGET (transient_for))); + gtk_grab_add (GTK_WIDGET (data->grab_widget)); + } + + g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); + if (self->accept_label) + g_variant_builder_add (&opt_builder, "{sv}", "accept_label", + g_variant_new_string (self->accept_label)); + if (self->cancel_label) + g_variant_builder_add (&opt_builder, "{sv}", "cancel_label", + g_variant_new_string (self->cancel_label)); + g_variant_builder_add (&opt_builder, "{sv}", "modal", + g_variant_new_boolean (data->modal)); + + g_dbus_message_set_body (message, + g_variant_new ("(ss@a{sv})", + parent_window_str ? parent_window_str : "", + gtk_native_dialog_get_title (GTK_NATIVE_DIALOG (self)), + g_variant_builder_end (&opt_builder))); + g_free (parent_window_str); + + data->portal_response_signal_id = + g_dbus_connection_signal_subscribe (data->connection, + "org.freedesktop.portal.Desktop", + "org.freedesktop.portal.FileChooser", + signal_name, + "/org/freedesktop/portal/desktop", + NULL, + G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE, + signal_callback, + self, NULL); + + g_dbus_connection_send_message_with_reply (data->connection, + message, + G_DBUS_SEND_MESSAGE_FLAGS_NONE, + G_MAXINT, + NULL, + NULL, + open_file_msg_cb, + data); + + g_object_unref (message); + + self->mode_data = data; + return TRUE; +} + +void +gtk_file_chooser_native_portal_hide (GtkFileChooserNative *self) +{ + FilechooserPortalData *data = self->mode_data; + + /* This is always set while dialog visible */ + g_assert (data != NULL); + + data->hidden = TRUE; + + if (data->portal_handle) + { + send_close (data); + filechooser_portal_data_free (data); + } + + self->mode_data = NULL; +} |