diff options
author | Matthias Clasen <mclasen@redhat.com> | 2018-09-03 18:36:56 -0400 |
---|---|---|
committer | Matthias Clasen <mclasen@redhat.com> | 2018-09-05 17:56:33 -0400 |
commit | bba183ee0c96b6124358447a4a9747dc38a5c8a0 (patch) | |
tree | b7ecdc0071c178bc9503c7c195d86a0da51de1d7 | |
parent | 2bb97bc136620ba4cd5d9e7283f9fa94e8042989 (diff) | |
download | gtk+-bba183ee0c96b6124358447a4a9747dc38a5c8a0.tar.gz |
clipboard: file transfer portal supportfile-transfer-portal
Implement file-list <-> application/vnd.flatpak.file-list
serialization by talking to the file transfer portal.
-rw-r--r-- | gdk/gdkclipboard.c | 3 | ||||
-rw-r--r-- | gdk/gdkcontentdeserializer.c | 98 | ||||
-rw-r--r-- | gdk/gdkcontentserializer.c | 67 | ||||
-rw-r--r-- | gdk/gfiletransferportal.c | 323 | ||||
-rw-r--r-- | gdk/gfiletransferportal.h | 15 | ||||
-rw-r--r-- | gdk/meson.build | 1 | ||||
-rw-r--r-- | tests/simple.c | 67 | ||||
-rw-r--r-- | tests/testclipboard2.c | 19 |
8 files changed, 581 insertions, 12 deletions
diff --git a/gdk/gdkclipboard.c b/gdk/gdkclipboard.c index d165b0fd79..6ceb7b4b29 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, @@ -1076,6 +1077,7 @@ gdk_clipboard_write_async (GdkClipboard *clipboard, g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); g_return_if_fail (callback != NULL); +g_print ("clipboard write in %s\n", mime_type); task = g_task_new (clipboard, cancellable, callback, user_data); g_task_set_priority (task, io_priority); g_task_set_source_tag (task, gdk_clipboard_write_async); @@ -1232,6 +1234,7 @@ gdk_clipboard_set_content (GdkClipboard *clipboard, formats = gdk_content_formats_new (NULL, 0); } +g_print ("claiming clipboard for %s\n", gdk_content_formats_to_string (formats)); result = gdk_clipboard_claim (clipboard, formats, TRUE, provider); gdk_content_formats_unref (formats); diff --git a/gdk/gdkcontentdeserializer.c b/gdk/gdkcontentdeserializer.c index 93564e2002..ea3c7ad003 100644 --- a/gdk/gdkcontentdeserializer.c +++ b/gdk/gdkcontentdeserializer.c @@ -22,6 +22,7 @@ #include "gdkcontentdeserializer.h" #include "gdkcontentformats.h" +#include "gfiletransferportal.h" #include "gdktexture.h" #include <gdk-pixbuf/gdk-pixbuf.h> @@ -691,6 +692,93 @@ string_deserializer (GdkContentDeserializer *deserializer) } 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, gpointer deserializer) @@ -816,11 +904,21 @@ init (void) g_slist_free (formats); + gdk_content_register_deserializer ("application/vnd.portal.filetransfer", + 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); + gdk_content_register_deserializer ("application/vnd.portal.filetransfer", + 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..bbe66824f1 100644 --- a/gdk/gdkcontentserializer.c +++ b/gdk/gdkcontentserializer.c @@ -23,6 +23,7 @@ #include "gdkcontentformats.h" #include "gdkpixbuf.h" +#include "gfiletransferportal.h" #include "gdktextureprivate.h" #include <gdk-pixbuf/gdk-pixbuf.h> @@ -702,6 +703,62 @@ file_serializer_finish (GObject *source, } 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); + } + + 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) { GFile *file; @@ -864,6 +921,11 @@ init (void) g_slist_free (formats); gdk_content_register_serializer (G_TYPE_FILE, + "application/vnd.portal.filetransfer", + portal_file_serializer, + NULL, + NULL); + gdk_content_register_serializer (G_TYPE_FILE, "text/uri-list", file_uri_serializer, NULL, @@ -874,6 +936,11 @@ init (void) NULL, NULL); gdk_content_register_serializer (GDK_TYPE_FILE_LIST, + "application/vnd.portal.filetransfer", + portal_file_serializer, + NULL, + NULL); + gdk_content_register_serializer (GDK_TYPE_FILE_LIST, "text/uri-list", file_uri_serializer, NULL, diff --git a/gdk/gfiletransferportal.c b/gdk/gfiletransferportal.c new file mode 100644 index 0000000000..2ebb865452 --- /dev/null +++ b/gdk/gfiletransferportal.c @@ -0,0 +1,323 @@ +#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 "gfiletransferportal.h" + +static GDBusProxy * +ensure_file_transfer_portal (void) +{ + static GDBusProxy *proxy = NULL; + + if (proxy == NULL) + { + GError *error = NULL; + + 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_warning ("Failed to get proxy: %s", error->message); + g_error_free (error); + } + } + + if (proxy) + { + char *owner = g_dbus_proxy_get_name_owner (proxy); + + if (owner) + { + g_free (owner); + return proxy; + } + } + + return NULL; +} + +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; + GVariantBuilder options; + + 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_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT); + g_dbus_proxy_call_with_unix_fd_list (proxy, + "AddFiles", + g_variant_new ("(saha{sv})", key, &fds, &options), + 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 **out_key, + GError **error) +{ + char *key; + GDBusProxy *proxy; + + key = g_object_get_data (G_OBJECT (result), "key"); + + if (g_task_propagate_boolean (G_TASK (result), error)) + { + *out_key = g_strdup (key); + return TRUE; + } + + proxy = ensure_file_transfer_portal (); + + g_dbus_proxy_call (proxy, "StopTransfer", + g_variant_new ("(s)", key), + 0, -1, NULL, NULL, NULL); + + 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/gfiletransferportal.h b/gdk/gfiletransferportal.h new file mode 100644 index 0000000000..0203b4d94b --- /dev/null +++ b/gdk/gfiletransferportal.h @@ -0,0 +1,15 @@ + +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); diff --git a/gdk/meson.build b/gdk/meson.build index d68307392b..88d8a8a363 100644 --- a/gdk/meson.build +++ b/gdk/meson.build @@ -19,6 +19,7 @@ gdk_public_sources = files([ 'gdkdrawcontext.c', 'gdkdrop.c', 'gdkevents.c', + 'gfiletransferportal.c', 'gdkframeclock.c', 'gdkframeclockidle.c', 'gdkframetimings.c', diff --git a/tests/simple.c b/tests/simple.c index 63ae18f2f6..17991b3465 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 (); @@ -37,11 +81,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 86c0e5dc3f..e539c14d3e 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 |