summaryrefslogtreecommitdiff
path: root/gdk/filetransferportal.c
diff options
context:
space:
mode:
authorMatthias Clasen <mclasen@redhat.com>2018-09-03 18:36:56 -0400
committerMatthias Clasen <mclasen@redhat.com>2019-12-12 14:25:33 -0500
commita20c8af678d6ffc05d0da088d34290b3f1ecb7ab (patch)
tree77dd6f37fab90e1f94eed648f6d30abf2f5e6052 /gdk/filetransferportal.c
parent88da95d921f902ee3599ac39b26553a7c4eeb2fa (diff)
downloadgtk+-a20c8af678d6ffc05d0da088d34290b3f1ecb7ab.tar.gz
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
Diffstat (limited to 'gdk/filetransferportal.c')
-rw-r--r--gdk/filetransferportal.c341
1 files changed, 341 insertions, 0 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;
+}