[gtk/file-transfer] clipboard: file transfer portal support



commit a20c8af678d6ffc05d0da088d34290b3f1ecb7ab
Author: Matthias Clasen <mclasen redhat com>
Date:   Mon Sep 3 18:36:56 2018 -0400

    clipboard: file transfer portal support
    
    Implement file-list <-> application/vnd.flatpak.file-list
    serialization by talking to the file transfer portal.
    
    See https://github.com/flatpak/xdg-desktop-portal/pull/222

 gdk/filetransferportal.c        | 341 ++++++++++++++++++++++++++++++++++++++++
 gdk/filetransferportalprivate.h |  39 +++++
 gdk/gdkclipboard.c              |   1 +
 gdk/gdkcontentdeserializer.c    | 102 ++++++++++++
 gdk/gdkcontentserializer.c      |  73 +++++++++
 gdk/meson.build                 |   1 +
 tests/simple.c                  |  67 +++++++-
 tests/testclipboard2.c          |  19 ++-
 8 files changed, 631 insertions(+), 12 deletions(-)
---
diff --git a/gdk/filetransferportal.c b/gdk/filetransferportal.c
new file mode 100644
index 0000000000..97e4134651
--- /dev/null
+++ b/gdk/filetransferportal.c
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2018 Matthias Clasen
+ *
+ * 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 <errno.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <gio/gio.h>
+#include <gio/gunixfdlist.h>
+
+#include "filetransferportalprivate.h"
+
+static GDBusProxy *file_transfer_proxy = NULL;
+
+static GDBusProxy *
+ensure_file_transfer_portal (void)
+{
+  if (file_transfer_proxy == NULL)
+    {
+      GError *error = NULL;
+
+      file_transfer_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
+                                                           G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES
+                                                           | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS
+                                                           | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+                                                           NULL,
+                                                           "org.freedesktop.portal.Documents",
+                                                           "/org/freedesktop/portal/documents",
+                                                           "org.freedesktop.portal.FileTransfer",
+                                                           NULL, &error);
+
+      if (error)
+        {
+          g_debug ("Failed to get proxy: %s", error->message);
+          g_error_free (error);
+        }
+    }
+
+  if (file_transfer_proxy)
+    {
+      char *owner = g_dbus_proxy_get_name_owner (file_transfer_proxy);
+
+      if (owner)
+        {
+          g_free (owner);
+          return file_transfer_proxy;
+        }
+    }
+
+  return NULL;
+}
+
+gboolean
+file_transfer_portal_available (void)
+{
+  gboolean available;
+
+  ensure_file_transfer_portal ();
+
+  available = file_transfer_proxy != NULL;
+
+  g_clear_object (&file_transfer_proxy);
+
+  return available;
+}
+
+typedef struct {
+  GTask *task;
+  const char **files;
+  int len;
+  int start;
+} AddFileData;
+
+static void add_files (GDBusProxy  *proxy,
+                       AddFileData *afd);
+
+static void
+add_files_done (GObject      *object,
+                GAsyncResult *result,
+                gpointer      data)
+{
+  GDBusProxy *proxy = G_DBUS_PROXY (object);
+  AddFileData *afd = data;
+  GError *error = NULL;
+  GVariant *ret;
+
+  ret = g_dbus_proxy_call_with_unix_fd_list_finish (proxy, NULL, result, &error);
+  if (ret == NULL)
+    {
+      g_task_return_error (afd->task, error);
+      g_object_unref (afd->task);
+      g_free (afd);
+      return;
+    }
+
+  g_variant_unref (ret);
+
+  if (afd->start >= afd->len)
+    {
+      g_task_return_boolean (afd->task, TRUE);
+      g_object_unref (afd->task);
+      g_free (afd);
+      return;
+    }
+
+  add_files (proxy, afd);
+}
+
+/* We call AddFiles in chunks of 16 to avoid running into
+ * the per-message fd limit of the bus.
+ */
+static void
+add_files (GDBusProxy  *proxy,
+           AddFileData *afd)
+{
+  GUnixFDList *fd_list;
+  GVariantBuilder fds;
+  int i;
+  char *key;
+
+  g_variant_builder_init (&fds, G_VARIANT_TYPE ("ah"));
+  fd_list = g_unix_fd_list_new ();
+
+  for (i = 0; afd->files[afd->start + i]; i++)
+    {
+      int fd;
+      int fd_in;
+      GError *error = NULL;
+
+      if (i == 16)
+        break;
+
+      fd = open (afd->files[afd->start + i], O_PATH | O_CLOEXEC);
+      if (fd == -1)
+        {
+          g_task_return_new_error (afd->task, G_IO_ERROR, g_io_error_from_errno (errno),
+                                   "Failed to open %s", afd->files[afd->start + i]);
+          g_object_unref (afd->task);
+          g_free (afd);
+          g_object_unref (fd_list);
+          return;
+         }
+      fd_in = g_unix_fd_list_append (fd_list, fd, &error);
+      close (fd);
+
+      if (fd_in == -1)
+        {
+          g_task_return_error (afd->task, error);
+          g_object_unref (afd->task);
+          g_free (afd);
+          g_object_unref (fd_list);
+          return;
+        }
+
+      g_variant_builder_add (&fds, "h", fd_in);
+    }
+
+   afd->start += 16;
+
+  key = (char *)g_object_get_data (G_OBJECT (afd->task), "key");
+
+  g_dbus_proxy_call_with_unix_fd_list (proxy,
+                                       "AddFiles",
+                                       g_variant_new ("(sah)", key, &fds),
+                                       0, -1,
+                                       fd_list,
+                                       NULL,
+                                       add_files_done, afd);
+
+  g_object_unref (fd_list);
+}
+
+static void
+start_session_done (GObject      *object,
+                    GAsyncResult *result,
+                    gpointer      data)
+{
+  GDBusProxy *proxy = G_DBUS_PROXY (object);
+  AddFileData *afd = data;
+  GError *error = NULL;
+  GVariant *ret;
+  const char *key;
+
+  ret = g_dbus_proxy_call_finish (proxy, result, &error);
+  if (ret == NULL)
+    {
+      g_task_return_error (afd->task, error);
+      g_object_unref (afd->task);
+      g_free (afd);
+      return;
+    }
+
+  g_variant_get (ret, "(&s)", &key);
+
+  g_object_set_data_full (G_OBJECT (afd->task), "key", g_strdup (key), g_free);
+
+  g_variant_unref (ret);
+
+  add_files (proxy, afd);
+}
+
+void
+file_transfer_portal_register_files (const char          **files,
+                                     gboolean              writable,
+                                     GAsyncReadyCallback   callback,
+                                     gpointer              data)
+{
+  GTask *task;
+  GDBusProxy *proxy;
+  AddFileData *afd;
+  GVariantBuilder options;
+
+  task = g_task_new (NULL, NULL, callback, data);
+
+  proxy = ensure_file_transfer_portal ();
+
+  if (proxy == NULL)
+    {
+      g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+                               "No portal found");
+      g_object_unref (task);
+      return;
+    }
+
+  afd = g_new (AddFileData, 1);
+  afd->task = task;
+  afd->files = files;
+  afd->len = g_strv_length ((char **)files);
+  afd->start = 0;
+
+  g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
+  g_variant_builder_add (&options, "{sv}", "writable", g_variant_new_boolean (writable));
+  g_variant_builder_add (&options, "{sv}", "autostop", g_variant_new_boolean (TRUE));
+
+  g_dbus_proxy_call (proxy, "StartTransfer",
+                     g_variant_new ("(a{sv})", &options),
+                     0, -1, NULL, start_session_done, afd);
+}
+
+gboolean
+file_transfer_portal_register_files_finish (GAsyncResult  *result,
+                                            char         **key,
+                                            GError       **error)
+{
+  if (g_task_propagate_boolean (G_TASK (result), error))
+    {
+      *key = g_strdup (g_object_get_data (G_OBJECT (result), "key"));
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static void
+retrieve_files_done (GObject      *object,
+                     GAsyncResult *result,
+                     gpointer      data)
+{
+  GDBusProxy *proxy = G_DBUS_PROXY (object);
+  GTask *task = data;
+  GError *error = NULL;
+  GVariant *ret;
+  char **files;
+
+  ret = g_dbus_proxy_call_finish (proxy, result, &error);
+  if (ret == NULL)
+    {
+      g_task_return_error (task, error);
+      g_object_unref (task);
+      return;
+    }
+
+  g_variant_get (ret, "(^a&s)", &files);
+
+  g_object_set_data_full (G_OBJECT (task), "files", g_strdupv (files), (GDestroyNotify)g_strfreev);
+
+  g_variant_unref (ret);
+
+  g_task_return_boolean (task, TRUE);
+}
+
+void
+file_transfer_portal_retrieve_files (const char          *key,
+                                     GAsyncReadyCallback  callback,
+                                     gpointer             data)
+{
+  GDBusProxy *proxy;
+  GTask *task;
+  GVariantBuilder options;
+
+  task = g_task_new (NULL, NULL, callback, data);
+
+  proxy = ensure_file_transfer_portal ();
+
+  if (proxy == NULL)
+    {
+      g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+                               "No portal found");
+      g_object_unref (task);
+      return;
+    }
+
+  g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
+  g_dbus_proxy_call (proxy,
+                     "RetrieveFiles",
+                     g_variant_new ("(sa{sv})", key, &options),
+                     0, -1, NULL,
+                     retrieve_files_done, task);
+}
+
+gboolean
+file_transfer_portal_retrieve_files_finish (GAsyncResult   *result,
+                                            char         ***files,
+                                            GError        **error)
+{
+  if (g_task_propagate_boolean (G_TASK (result), error))
+    {
+      *files = g_strdupv (g_object_get_data (G_OBJECT (result), "files"));
+      return TRUE;
+    }
+
+  return FALSE;
+}
diff --git a/gdk/filetransferportalprivate.h b/gdk/filetransferportalprivate.h
new file mode 100644
index 0000000000..d23fdfbd75
--- /dev/null
+++ b/gdk/filetransferportalprivate.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2018 Matthias Clasen
+ *
+ * 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/>.
+ */
+
+#ifndef __FILE_TRANSFER_PROTOCOL_H__
+#define __FILE_TRANSFER_PROTOCOL_H__
+
+
+gboolean file_transfer_portal_available             (void);
+void     file_transfer_portal_register_files        (const char           **files,
+                                                     gboolean               writable,
+                                                     GAsyncReadyCallback    callback,
+                                                     gpointer               data);
+gboolean file_transfer_portal_register_files_finish (GAsyncResult          *result,
+                                                     char                 **key,
+                                                     GError               **error);
+
+void     file_transfer_portal_retrieve_files        (const char            *key,
+                                                     GAsyncReadyCallback    callback,
+                                                     gpointer               data);
+gboolean file_transfer_portal_retrieve_files_finish (GAsyncResult          *result,
+                                                     char                ***files,
+                                                     GError               **error);
+
+
+#endif
diff --git a/gdk/gdkclipboard.c b/gdk/gdkclipboard.c
index 224907ab36..4406aad205 100644
--- a/gdk/gdkclipboard.c
+++ b/gdk/gdkclipboard.c
@@ -1054,6 +1054,7 @@ gdk_clipboard_write_serialize_done (GObject      *content,
 
   g_object_unref (task);
 }
+
 void
 gdk_clipboard_write_async (GdkClipboard        *clipboard,
                            const char          *mime_type,
diff --git a/gdk/gdkcontentdeserializer.c b/gdk/gdkcontentdeserializer.c
index 93564e2002..4a15fa0086 100644
--- a/gdk/gdkcontentdeserializer.c
+++ b/gdk/gdkcontentdeserializer.c
@@ -22,6 +22,7 @@
 #include "gdkcontentdeserializer.h"
 
 #include "gdkcontentformats.h"
+#include "filetransferportalprivate.h"
 #include "gdktexture.h"
 
 #include <gdk-pixbuf/gdk-pixbuf.h>
@@ -690,6 +691,93 @@ string_deserializer (GdkContentDeserializer *deserializer)
   g_object_unref (filter);
 }
 
+static void
+portal_finish (GObject *object,
+               GAsyncResult *result,
+               gpointer deserializer)
+{
+  char **files = NULL;
+  GError *error = NULL;
+  GValue *value;
+
+  if (!file_transfer_portal_retrieve_files_finish (result, &files, &error))
+    {
+      gdk_content_deserializer_return_error (deserializer, error);
+      return;
+    }
+
+  value = gdk_content_deserializer_get_value (deserializer);
+  if (G_VALUE_HOLDS (value, G_TYPE_FILE))
+    {
+      if (files[0] != NULL)
+        g_value_take_object (value, g_file_new_for_path (files[0]));
+    }
+  else
+    {
+      GSList *l = NULL;
+      gsize i;
+
+      for (i = 0; files[i] != NULL; i++)
+        l = g_slist_prepend (l, g_file_new_for_path (files[i]));
+      g_value_take_boxed (value, g_slist_reverse (l));
+    }
+  g_strfreev (files);
+
+  gdk_content_deserializer_return_success (deserializer);
+}
+
+static void
+portal_file_deserializer_finish (GObject      *source,
+                                 GAsyncResult *result,
+                                 gpointer      deserializer)
+{
+  GOutputStream *stream = G_OUTPUT_STREAM (source);
+  GError *error = NULL;
+  gssize written;
+  char *key;
+
+  written = g_output_stream_splice_finish (stream, result, &error);
+  if (written < 0)
+    {
+      gdk_content_deserializer_return_error (deserializer, error);
+      return;
+    }
+
+  /* write terminating NULL */
+  if (!g_output_stream_write (stream, "", 1, NULL, &error))
+    {
+      gdk_content_deserializer_return_error (deserializer, error);
+      return;
+    }
+
+  key = g_memory_output_stream_steal_data (G_MEMORY_OUTPUT_STREAM (stream));
+  if (key == NULL)
+    {
+      deserialize_not_found (deserializer);
+      return;
+    }
+
+  file_transfer_portal_retrieve_files (key, portal_finish, deserializer);
+  gdk_content_deserializer_set_task_data (deserializer, key, g_free);
+}
+
+static void
+portal_file_deserializer (GdkContentDeserializer *deserializer)
+{
+  GOutputStream *output;
+
+  output = g_memory_output_stream_new_resizable ();
+
+  g_output_stream_splice_async (output,
+                                gdk_content_deserializer_get_input_stream (deserializer),
+                                G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
+                                gdk_content_deserializer_get_priority (deserializer),
+                                gdk_content_deserializer_get_cancellable (deserializer),
+                                portal_file_deserializer_finish,
+                                deserializer);
+  g_object_unref (output);
+}
+
 static void
 file_uri_deserializer_finish (GObject      *source,
                               GAsyncResult *result,
@@ -764,6 +852,7 @@ init (void)
   static gboolean initialized = FALSE;
   GSList *formats, *f;
   const char *charset;
+  gboolean has_portal;
 
   if (initialized)
     return;
@@ -816,11 +905,24 @@ init (void)
 
   g_slist_free (formats);
 
+  has_portal = file_transfer_portal_available ();
+  if (has_portal)
+    gdk_content_register_deserializer ("application/vnd.portal.files",
+                                       GDK_TYPE_FILE_LIST,
+                                       portal_file_deserializer,
+                                       NULL,
+                                       NULL);
   gdk_content_register_deserializer ("text/uri-list",
                                      GDK_TYPE_FILE_LIST,
                                      file_uri_deserializer,
                                      NULL,
                                      NULL);
+  if (has_portal)
+    gdk_content_register_deserializer ("application/vnd.portal.files",
+                                       G_TYPE_FILE,
+                                       portal_file_deserializer,
+                                       NULL,
+                                       NULL);
   gdk_content_register_deserializer ("text/uri-list",
                                      G_TYPE_FILE,
                                      file_uri_deserializer,
diff --git a/gdk/gdkcontentserializer.c b/gdk/gdkcontentserializer.c
index 7b99690224..cac4378cc2 100644
--- a/gdk/gdkcontentserializer.c
+++ b/gdk/gdkcontentserializer.c
@@ -23,6 +23,7 @@
 
 #include "gdkcontentformats.h"
 #include "gdkpixbuf.h"
+#include "filetransferportalprivate.h"
 #include "gdktextureprivate.h"
 
 #include <gdk-pixbuf/gdk-pixbuf.h>
@@ -701,6 +702,63 @@ file_serializer_finish (GObject      *source,
     gdk_content_serializer_return_success (serializer);
 }
 
+static void
+portal_ready (GObject *object,
+              GAsyncResult *result,
+              gpointer serializer)
+{
+  GError *error = NULL;
+  char *key;
+
+  if (!file_transfer_portal_register_files_finish (result, &key, &error))
+    {
+      gdk_content_serializer_return_error (serializer, error);
+      return;
+    }
+
+  g_output_stream_write_all_async (gdk_content_serializer_get_output_stream (serializer),
+                                   key,
+                                   strlen (key) + 1,
+                                   gdk_content_serializer_get_priority (serializer),
+                                   gdk_content_serializer_get_cancellable (serializer),
+                                   file_serializer_finish,
+                                   serializer);
+  gdk_content_serializer_set_task_data (serializer, key, g_free);
+}
+
+static void
+portal_file_serializer (GdkContentSerializer *serializer)
+{
+  GFile *file;
+  const GValue *value;
+  GPtrArray *files;
+
+  files = g_ptr_array_new_with_free_func (g_free);
+
+  value = gdk_content_serializer_get_value (serializer);
+
+  if (G_VALUE_HOLDS (value, G_TYPE_FILE))
+    {
+      file = g_value_get_object (gdk_content_serializer_get_value (serializer));
+      if (file)
+        g_ptr_array_add (files, g_file_get_path (file));
+      g_ptr_array_add (files, NULL);
+    }
+  else if (G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST))
+    {
+      GSList *l;
+
+      for (l = g_value_get_boxed (value); l; l = l->next)
+        g_ptr_array_add (files, g_file_get_path (l->data));
+
+      g_ptr_array_add (files, NULL);
+    }
+
+  /* this call doesn't copy the strings, so keep the array around until the registration is done */
+  file_transfer_portal_register_files ((const char **)files->pdata, TRUE, portal_ready, serializer);
+  gdk_content_serializer_set_task_data (serializer, files, (GDestroyNotify)g_ptr_array_unref);
+}
+
 static void
 file_uri_serializer (GdkContentSerializer *serializer)
 {
@@ -808,6 +866,7 @@ init (void)
   static gboolean initialized = FALSE;
   GSList *formats, *f;
   const char *charset;
+  gboolean has_portal;
 
   if (initialized)
     return;
@@ -863,6 +922,14 @@ init (void)
 
   g_slist_free (formats);
 
+  has_portal = file_transfer_portal_available ();
+
+  if (has_portal)
+    gdk_content_register_serializer (G_TYPE_FILE,
+                                     "application/vnd.portal.files",
+                                     portal_file_serializer,
+                                     NULL,
+                                     NULL);
   gdk_content_register_serializer (G_TYPE_FILE,
                                    "text/uri-list",
                                    file_uri_serializer,
@@ -873,6 +940,12 @@ init (void)
                                    file_text_serializer,
                                    NULL,
                                    NULL);
+  if (has_portal)
+    gdk_content_register_serializer (GDK_TYPE_FILE_LIST,
+                                     "application/vnd.portal.files",
+                                     portal_file_serializer,
+                                     NULL,
+                                     NULL);
   gdk_content_register_serializer (GDK_TYPE_FILE_LIST,
                                    "text/uri-list",
                                    file_uri_serializer,
diff --git a/gdk/meson.build b/gdk/meson.build
index 6089e50ea5..ed41b126ea 100644
--- a/gdk/meson.build
+++ b/gdk/meson.build
@@ -19,6 +19,7 @@ gdk_public_sources = files([
   'gdkdrawcontext.c',
   'gdkdrop.c',
   'gdkevents.c',
+  'filetransferportal.c',
   'gdkframeclock.c',
   'gdkframeclockidle.c',
   'gdkframetimings.c',
diff --git a/tests/simple.c b/tests/simple.c
index 3369eebcf8..39db8a41db 100644
--- a/tests/simple.c
+++ b/tests/simple.c
@@ -20,15 +20,59 @@
 
 
 void
-hello (void)
+copy (void)
 {
-  g_print ("hello world\n");
+  GdkClipboard *clipboard = gdk_display_get_clipboard (gdk_display_get_default ());
+  GFile *file = g_file_new_for_path ("/home/mclasen/faw-sig");
+
+  gdk_clipboard_set (clipboard, G_TYPE_FILE, file);
+
+  g_object_unref (file);
+}
+
+static void
+value_received (GObject *object,
+                GAsyncResult *result,
+                gpointer data)
+{
+  const GValue *value;
+  GError *error = NULL;
+  GSList *l;
+
+  value = gdk_clipboard_read_value_finish (GDK_CLIPBOARD (object), result, &error);
+  if (value == NULL)
+    {
+      g_print ("Failed to read: %s\n", error->message);
+      g_error_free (error);
+      return;
+    }
+
+  for (l = g_value_get_boxed (value); l; l = l->next)
+    g_print ("%s\n", g_file_get_path (l->data));
+}
+
+void
+paste (void)
+{
+  GdkClipboard *clipboard = gdk_display_get_clipboard (gdk_display_get_default ());
+
+  gdk_clipboard_read_value_async (clipboard, GDK_TYPE_FILE_LIST, 0, NULL, value_received, NULL);
+
+}
+
+static void
+clipboard_changed (GdkClipboard *clipboard)
+{
+  GdkContentFormats *formats = gdk_clipboard_get_formats (clipboard);
+  g_autofree char *s = gdk_content_formats_to_string (formats);
+  g_print ("clipboard contents now: %s, local: %d\n", s, gdk_clipboard_is_local (clipboard));
 }
 
 int
 main (int argc, char *argv[])
 {
-  GtkWidget *window, *button;
+  GtkWidget *window, *button, *box;
+  GdkClipboard *clipboard;
 
   gtk_init ();
 
@@ -44,11 +88,22 @@ main (int argc, char *argv[])
   gtk_window_set_resizable (GTK_WINDOW (window), FALSE);
   g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL);
 
+  box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+
   button = gtk_button_new ();
-  gtk_button_set_label (GTK_BUTTON (button), "hello world");
-  g_signal_connect (button, "clicked", G_CALLBACK (hello), NULL);
+  gtk_button_set_label (GTK_BUTTON (button), "copy");
+  g_signal_connect (button, "clicked", G_CALLBACK (copy), NULL);
+  gtk_container_add (GTK_CONTAINER (box), button);
+
+  button = gtk_button_new ();
+  gtk_button_set_label (GTK_BUTTON (button), "paste");
+  g_signal_connect (button, "clicked", G_CALLBACK (paste), NULL);
+  gtk_container_add (GTK_CONTAINER (box), button);
+
+  gtk_container_add (GTK_CONTAINER (window), box);
 
-  gtk_container_add (GTK_CONTAINER (window), button);
+  clipboard = gdk_display_get_clipboard (gdk_display_get_default ());
+  g_signal_connect (clipboard, "changed", G_CALLBACK (clipboard_changed), NULL);
 
   gtk_widget_show (window);
 
diff --git a/tests/testclipboard2.c b/tests/testclipboard2.c
index 035afd6b1a..f296cd5ac9 100644
--- a/tests/testclipboard2.c
+++ b/tests/testclipboard2.c
@@ -103,23 +103,30 @@ visible_child_changed_cb (GtkWidget    *stack,
     }
 }
 
-static GList *
+static GSList *
 get_file_list (const char *dir)
 {
   GFileEnumerator *enumerator;
   GFile *file;
-  GList *list = NULL;
+  GFileInfo *info;
+  GSList *list = NULL;
   
   file = g_file_new_for_path (dir);
-  enumerator = g_file_enumerate_children (file, "standard::name", 0, NULL, NULL);
+  enumerator = g_file_enumerate_children (file, "standard::name,standard::type", 0, NULL, NULL);
   g_object_unref (file);
   if (enumerator == NULL)
     return NULL;
 
-  while (g_file_enumerator_iterate (enumerator, NULL, &file, NULL, NULL) && file != NULL)
-    list = g_list_prepend (list, g_object_ref (file));
+  while (g_file_enumerator_iterate (enumerator, &info, &file, NULL, NULL) && file != NULL)
+    {
+      /* the portal can't handle directories */
+      if (g_file_info_get_file_type (info) != G_FILE_TYPE_REGULAR)
+        continue;
+
+      list = g_slist_prepend (list, g_object_ref (file));
+    }
 
-  return g_list_reverse (list);
+  return g_slist_reverse (list);
 }
 
 static void


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