summaryrefslogtreecommitdiff
path: root/gtk/gtkfilechoosernativeportal.c
diff options
context:
space:
mode:
authorAlexander Larsson <alexl@redhat.com>2015-11-11 16:31:53 +0100
committerMatthias Clasen <mclasen@redhat.com>2016-07-08 00:07:42 -0400
commit01d0bac83e7f12338d12e5669c408da3078f6010 (patch)
treeacb09d092a909dd84f5ef9b777c5723421a98cb8 /gtk/gtkfilechoosernativeportal.c
parente47957406d60345efa624dea6517f7d793fc1a13 (diff)
downloadgtk+-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.c392
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;
+}