[continued from previous message]
+ 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);
+ free_add_file_data (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;
+ AddFileData *afd;
+ GVariantBuilder options;
+
+ task = g_task_new (NULL, NULL, callback, data);
+
+ if (file_transfer_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 = g_strdupv ((char **)files);
+ afd->len = g_strv_length (afd->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 (file_transfer_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;
+}
+
+char *
+file_transfer_portal_register_files_sync (const char **files,
+ gboolean writable,
+ GError **error)
+{
+ const char *value;
+ char *key;
+ GUnixFDList *fd_list;
+ GVariantBuilder fds, options;
+ int i;
+ GVariant *ret;
+
+ g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
+ ret = g_dbus_proxy_call_sync (file_transfer_proxy,
+ "StartTransfer",
+ g_variant_new ("(a{sv})", &options),
+ 0,
+ -1,
+ NULL,
+ error);
+ if (ret == NULL)
+ return NULL;
+
+ g_variant_get (ret, "(&s)", &value);
+ key = g_strdup (value);
+ g_variant_unref (ret);
+
+ fd_list = NULL;
+
+ for (i = 0; files[i]; i++)
+ {
+ int fd;
+ int fd_in;
+
+ if (fd_list == NULL)
+ {
+ g_variant_builder_init (&fds, G_VARIANT_TYPE ("ah"));
+ fd_list = g_unix_fd_list_new ();
+ }
+
+ fd = open (files[i], O_PATH | O_CLOEXEC);
+ if (fd == -1)
+ {
+ g_set_error (error,
+ G_IO_ERROR, g_io_error_from_errno (errno),
+ "Failed to open %s", files[i]);
+ g_variant_builder_clear (&fds);
+ g_object_unref (fd_list);
+ g_free (key);
+ return NULL;
+ }
+
+#ifndef HAVE_O_CLOEXEC
+ fcntl (fd, F_SETFD, FD_CLOEXEC);
+#endif
+ fd_in = g_unix_fd_list_append (fd_list, fd, error);
+ close (fd);
+
+ if (fd_in == -1)
+ {
+ g_variant_builder_clear (&fds);
+ g_object_unref (fd_list);
+ g_free (key);
+ return NULL;
+ }
+
+ g_variant_builder_add (&fds, "h", fd_in);
+
+ if ((i + 1) % 16 == 0 || files[i + 1] == NULL)
+ {
+ g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
+ ret = g_dbus_proxy_call_with_unix_fd_list_sync (file_transfer_proxy, + "AddFiles",
+ g_variant_new ("(saha{sv})",
+ key, + &fds, + &options),
+ 0,
+ -1,
+ fd_list,
+ NULL,
+ NULL,
+ error);
+ g_clear_object (&fd_list);
+
+ if (ret == NULL)
+ {
+ g_free (key);
+ return NULL;
+ }
+
+ g_variant_unref (ret);
+ }
+ }
+
+ return key;
+}
+
+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)
+{
+ GTask *task;
+ GVariantBuilder options;
+
+ task = g_task_new (NULL, NULL, callback, data);
+
+ if (file_transfer_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 (file_transfer_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;
+}
+
+char **
+file_transfer_portal_retrieve_files_sync (const char *key,
+ GError **error)
+{
+ GVariantBuilder options;
+ GVariant *ret;
+ char **files = NULL;
+
+ g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
+ ret = g_dbus_proxy_call_sync (file_transfer_proxy,
+ "RetrieveFiles",
+ g_variant_new ("(sa{sv})", key, &options),
+ 0, -1, NULL,
+ error);
+ if (ret)
+ {
+ const char **value;
+ g_variant_get (ret, "(^a&s)", &value);
+ files = g_strdupv ((char **)value);
+ g_variant_unref (ret);
+ }
+
+ return files;
+}
+
+static void
+connection_closed (GDBusConnection *connection,
+ gboolean remote_peer_vanished,
+ GError *error)
+{
+ g_clear_object (&file_transfer_proxy);
+}
+
+static void
+finish_registration (void)
+{
+ /* Free the singleton when the connection closes, important for test */
+ g_signal_connect (g_dbus_proxy_get_connection (G_DBUS_PROXY (file_transfer_proxy)),
+ "closed", G_CALLBACK (connection_closed), NULL);
+}
+
+static gboolean
+proxy_has_owner (GDBusProxy *proxy)
+{
+ char *owner;
+
+ owner = g_dbus_proxy_get_name_owner (proxy);
+ if (owner)
+ {
+ g_free (owner);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+void
+file_transfer_portal_register (void)
+{
+ static gboolean called;
+
+ if (!called)
+ {
+ called = TRUE;
+
+ 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,
+ NULL);
+
+ if (file_transfer_proxy && !proxy_has_owner (file_transfer_proxy))
+ g_clear_object (&file_transfer_proxy);
+
+ if (file_transfer_proxy)
+ finish_registration ();
+ }
+}
+
+gboolean
+file_transfer_portal_supported (void)
+{
+ file_transfer_portal_register ();
+
+ return file_transfer_proxy != NULL;
+}
+
+#endif /* G_OS_UNIX */
diff --git a/gtk/filetransferportalprivate.h b/gtk/filetransferportalprivate.h new file mode 100644
index 0000000000..d136b53345
--- /dev/null
+++ b/gtk/filetransferportalprivate.h
@@ -0,0 +1,49 @@
+/*
+ * 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__
+
+
+void file_transfer_portal_register (void);
+
+void file_transfer_portal_register_files (