[gtk+/portal: 4/18] Initial version of file chooser portal support



commit 254085ba6f8eb3dc973dac3af578892064e17b92
Author: Alexander Larsson <alexl redhat com>
Date:   Wed Nov 11 16:31:53 2015 +0100

    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.

 gtk/Makefile.am                   |    1 +
 gtk/gtkfilechoosernative.c        |   27 +++-
 gtk/gtkfilechoosernativeportal.c  |  387 +++++++++++++++++++++++++++++++++++++
 gtk/gtkfilechoosernativeprivate.h |    3 +
 4 files changed, 417 insertions(+), 1 deletions(-)
---
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index 2f05cbb..966a60b 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -753,6 +753,7 @@ gtk_base_c_sources =                \
        gtkfilechooserembed.c   \
        gtkfilechooserentry.c   \
        gtkfilechoosernative.c  \
+       gtkfilechoosernativeportal.c    \
        gtkfilechooserutils.c   \
        gtkfilechooserwidget.c  \
        gtkfilefilter.c         \
diff --git a/gtk/gtkfilechoosernative.c b/gtk/gtkfilechoosernative.c
index 33ce9df..be96b69 100644
--- a/gtk/gtkfilechoosernative.c
+++ b/gtk/gtkfilechoosernative.c
@@ -51,7 +51,10 @@
  * for use with “File/Open” or “File/Save as” commands. By default, this
  * just uses a #GtkFileChooserDialog to implement the actual dialog.
  * However, on certain platforms, such as Windows, the native platform
- * file chooser is uses instead.
+ * file chooser is uses instead. When the application is running in a
+ * sandboxed environment without direct filesystem access (such as Flatpak),
+ * #GtkFileChooserNative may call the proper APIs (portals) to let the user
+ * choose a file and make it available to the application.
  *
  * While the API of #GtkFileChooserNative closely mirrors #GtkFileChooserDialog, the main
  * difference is that there is no access to any #GtkWindow or #GtkWidget for the dialog.
@@ -170,11 +173,26 @@
  *
  * If any of these features are used the regular #GtkFileChooserDialog
  * will be used in place of the native one.
+ *
+ * ## Portal details ## {#gtkfilechooserdialognative-portal}
+ *
+ * When the org.freedesktop.portal.FileChooser portal is available on the
+ * session bus, it is used to bring up an out-of-process file chooser. Depending
+ * on the kind of session the application is running in, this may or may not
+ * be a GTK+ file chooser. In this situation, the following things are not
+ * supported and will be silently ignored:
+ *
+ * * Extra widgets added with gtk_file_chooser_set_extra_widget().
+ *
+ * * Use of custom previews by connecting to #GtkFileChooser::update-preview.
+ *
+ * * Any #GtkFileFilter added with a custom filter.
  */
 
 enum {
   MODE_FALLBACK,
   MODE_WIN32,
+  MODE_PORTAL,
 };
 
 enum {
@@ -558,6 +576,7 @@ gtk_file_chooser_native_get_files (GtkFileChooser *chooser)
 
   switch (self->mode)
     {
+    case MODE_PORTAL:
     case MODE_WIN32:
       return g_slist_copy_deep (self->custom_files, (GCopyFunc)g_object_ref, NULL);
 
@@ -579,6 +598,10 @@ gtk_file_chooser_native_show (GtkNativeDialog *native)
     self->mode = MODE_WIN32;
 #endif
 
+  if (self->mode == MODE_FALLBACK &&
+      gtk_file_chooser_native_portal_show (self))
+    self->mode = MODE_PORTAL;
+
   if (self->mode == MODE_FALLBACK)
     show_dialog (self);
 }
@@ -597,6 +620,8 @@ gtk_file_chooser_native_hide (GtkNativeDialog *native)
 #ifdef GDK_WINDOWING_WIN32
       gtk_file_chooser_native_win32_hide (self);
 #endif
+    case MODE_PORTAL:
+      gtk_file_chooser_native_portal_hide (self);
       break;
     }
 }
diff --git a/gtk/gtkfilechoosernativeportal.c b/gtk/gtkfilechoosernativeportal.c
new file mode 100644
index 0000000..0df63c0
--- /dev/null
+++ b/gtk/gtkfilechoosernativeportal.c
@@ -0,0 +1,387 @@
+/* -*- 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);
+
+  //TODO: This causes a crash in other code. Do we double unref somewhere?
+  // g_object_unref (data->connection);
+
+  if (data->grab_widget)
+    {
+      gtk_grab_remove (data->grab_widget);
+      gtk_widget_destroy (data->grab_widget);
+    }
+
+  if (data->self)
+    g_object_unref (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);
+    }
+}
+
+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\n", error->message);
+      filechooser_portal_data_free (data);
+      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);
+    }
+
+  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,
+                                             -1,
+                                             NULL,
+                                             NULL,
+                                             open_file_msg_cb,
+                                             data);
+
+  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;
+}
diff --git a/gtk/gtkfilechoosernativeprivate.h b/gtk/gtkfilechoosernativeprivate.h
index 6e271ff..ab3b6cf 100644
--- a/gtk/gtkfilechoosernativeprivate.h
+++ b/gtk/gtkfilechoosernativeprivate.h
@@ -48,6 +48,9 @@ struct _GtkFileChooserNative
 gboolean gtk_file_chooser_native_win32_show (GtkFileChooserNative *self);
 void gtk_file_chooser_native_win32_hide (GtkFileChooserNative *self);
 
+gboolean gtk_file_chooser_native_portal_show (GtkFileChooserNative *self);
+void gtk_file_chooser_native_portal_hide (GtkFileChooserNative *self);
+
 G_END_DECLS
 
 #endif /* __GTK_FILE_CHOOSER_NATIVE_PRIVATE_H__ */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]