summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakub Janků <jjanku@redhat.com>2020-06-24 20:06:41 +0200
committerFrediano Ziglio <freddy77@gmail.com>2020-09-28 10:46:09 +0100
commit09733a5c819d3e20e0a14d80bb716b37167f0930 (patch)
tree0ec58d45dd575c9a0c7b9685c50057ddb6397a2e
parent2e2feeb93bbc3d895e3389ee14f14fb956b449c3 (diff)
clipboard: enable copying files from client using webdav
When the user wants to copy files, new spice-gtk can share those files using the existing phodav server. In that case, it advertises the VD_AGENT_CLIPBOARD_FILE_LIST type in the clipboard grab message. Upon request of the clipboard data in the mentioned type, spice-gtk provides a list of absolute paths in the phodav server - these are the files that are supposed to be copied/moved. The role of the vdagent is to: 1) ensure that the phodav share is mounted, 2) adjust the provided paths given the mountpoint, 3) provide the uri list in various formats depending on which one was requested (different file managers use differenct formats) The code that accomplishes these tasks is located in a new file webdav-cb.c - the main reason for it is that vdagent currently supports two clibpoard backends: x11, gtk+. Implement this new feature only in the x11 backend since the future of the gtk+ one is not clear. Copy and move was tested with GNOME Nautilus, KDE Dolphin, Cinnamon Nemo, Mate Caja, Xfce Thunar, LXDE PCManFM, Krusader. The functionality with other file managers might be limited. Copying files from the vdagnet side to the client is not supported yet. Signed-off-by: Jakub Janků <jjanku@redhat.com> Acked-by: Frediano Ziglio <fziglio@redhat.com>
-rw-r--r--Makefile.am2
-rw-r--r--src/vdagent/webdav-cb.c292
-rw-r--r--src/vdagent/webdav-cb.h38
-rw-r--r--src/vdagent/x11-priv.h8
-rw-r--r--src/vdagent/x11.c86
5 files changed, 425 insertions, 1 deletions
diff --git a/Makefile.am b/Makefile.am
index 431e414..575ba52 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -41,6 +41,8 @@ src_spice_vdagent_SOURCES = \
src/vdagent/audio.h \
src/vdagent/clipboard.c \
src/vdagent/clipboard.h \
+ src/vdagent/webdav-cb.c \
+ src/vdagent/webdav-cb.h \
src/vdagent/device-info.c \
src/vdagent/device-info.h \
src/vdagent/display.c \
diff --git a/src/vdagent/webdav-cb.c b/src/vdagent/webdav-cb.c
new file mode 100644
index 0000000..3cf6286
--- /dev/null
+++ b/src/vdagent/webdav-cb.c
@@ -0,0 +1,292 @@
+/* webdav-cb.c - common code for x11 and GTK+ backend handling webdav copy&paste
+
+ Copyright 2020 Red Hat, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <syslog.h>
+
+#include "webdav-cb.h"
+
+/* FIXME:
+ * From my testing, gvfs-dav with avahi doesn't seem stable enough,
+ * so let's simply use the usual port 9843 when mounting the shared folder for now.
+ *
+ * This is a bit unfortunate because the port can be customized with the -p option,
+ * while the service name "Spice client folder" is hardcoded.
+ *
+ * Issues with gvfs-dav and avahi:
+ * - https://bugzilla.redhat.com/show_bug.cgi?id=1843035
+ * - similar to https://bugzilla.redhat.com/show_bug.cgi?id=1773219
+ * - https://gitlab.gnome.org/GNOME/gvfs/-/issues/498
+ * - hence the %2520 in CLIPBOARD_WEBDAV_URI below
+ * - fixed recently
+ * - https://gitlab.gnome.org/GNOME/gvfs/-/issues/449
+ */
+// #define CLIPBOARD_WEBDAV_URI "dav+sd://Spice%2520client%2520folder._webdav._tcp.local"
+#define CLIPBOARD_WEBDAV_URI "dav://localhost:9843"
+
+typedef enum clipboard_action {
+ CLIPBOARD_ACTION_COPY,
+ CLIPBOARD_ACTION_CUT,
+} clipboard_action;
+
+static GMount *webdav_mount;
+static GVolumeMonitor *monitor;
+static GCancellable *cancellable;
+
+static gchar *clipboard_data_to_uris(const gchar *target, const gchar *mount_uri,
+ const gchar *data, gsize size, GError **err)
+{
+ if (!data || size < 1) {
+ /* this is valid input */
+ return NULL;
+ }
+ if (data[size-1]) {
+ g_set_error_literal(err, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT,
+ "received list of uris that is not null-terminated");
+ return NULL;
+ }
+
+ clipboard_action action;
+ if (!g_strcmp0(data, "copy")) {
+ action = CLIPBOARD_ACTION_COPY;
+ } else if (!g_strcmp0(data, "cut")) {
+ action = CLIPBOARD_ACTION_CUT;
+ } else {
+ g_set_error_literal(err, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
+ "first line of uri list must specify clipboard action");
+ return NULL;
+ }
+ /* skip the action line, since we're only interested in
+ * the actual uris from this point forward */
+ gsize action_len = strlen(data) + 1;
+ data += action_len;
+ size -= action_len;
+
+ if (size < 1) {
+ return NULL;
+ }
+
+ GString *str = g_string_new(NULL);
+ const gchar *delimiter = "\n";
+ gboolean end_with_delimiter = FALSE;
+
+ /* TODO: add support for more file managers
+ * (and then update clipboard_format_tmpl in x11-priv.h) */
+ if (!g_strcmp0(target, "text/uri-list")) {
+ delimiter = "\r\n";
+ if (action == CLIPBOARD_ACTION_CUT) {
+ syslog(LOG_WARNING, "cutting is not supported with 'text/uri-list' target");
+ }
+ } else if (!g_strcmp0(target, "text/plain;charset=utf-8")) {
+ /* Nautilus uses text clipboard since
+ * https://gitlab.gnome.org/GNOME/nautilus/commit/1f77023b5769c773dd9261e5294c0738bf6a3115 */
+ end_with_delimiter = TRUE;
+ g_string_append(str, "x-special/nautilus-clipboard\n");
+ g_string_append (str, action == CLIPBOARD_ACTION_CUT ? "cut\n" : "copy\n");
+ } else if (!g_strcmp0(target, "application/x-kde-cutselection")) {
+ /* KDE Dolphin handles text/uri-list just fine,
+ * but this atom is needed to distinguish between copy and move */
+ g_string_append(str, action == CLIPBOARD_ACTION_CUT ? "1" : "0");
+ return g_string_free(str, FALSE);
+ } else if (!g_strcmp0(target, "x-special/gnome-copied-files") ||
+ !g_strcmp0(target, "x-special/mate-copied-files")) {
+ /* Nautilus moved away from this approach,
+ * but there's a bunch of other file managers that do use it, such as:
+ * Nemo (Cinnamon), Thunar (Xfce), Deepin File Manager (Deepin), Xfe; Caja (Mate) */
+ g_string_append(str, action == CLIPBOARD_ACTION_CUT ? "cut\n" : "copy\n");
+ } else {
+ g_set_error(err, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
+ "conversion to uri target %s is not supported", target);
+ return g_string_free(str, TRUE);
+ }
+
+ for (const gchar *item = data; item < data + size; item += strlen(item) + 1) {
+ gchar *escaped = g_uri_escape_string(item, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, TRUE);
+ gchar *uri = g_build_filename(mount_uri, escaped, NULL);
+ g_string_append(str, uri);
+ g_string_append(str, delimiter);
+ g_free(escaped);
+ g_free(uri);
+ }
+
+ if (!end_with_delimiter) {
+ g_string_truncate(str, str->len - strlen(delimiter));
+ }
+
+ return g_string_free(str, FALSE);
+}
+
+static gchar *clipboard_webdav_mount_get_uri(GMount *mount, GError **err)
+{
+ GFile *root;
+ gchar *path, *uri;
+
+ root = g_mount_get_root(mount);
+ path = g_file_get_path(root);
+
+ if (path) {
+ /* gvfs-fuse is running, so we get path as follows:
+ * "/run/user/<UID>/gvfs/dav+sd:host=SpiceClipboard._webdav._tcp.local"
+ * but we still need to convert it to uri */
+ uri = g_filename_to_uri(path, NULL, err);
+ g_free(path);
+ } else {
+ /* gvfs-fuse is not running, let's return the CLIPBOARD_WEBDAV_URI ("dav+sd://..."),
+ * so that at least gio apps can access the shared files */
+ uri = g_file_get_uri(root);
+ syslog(LOG_WARNING, "gvfs-fuse doesn't seem to be running, "
+ "file copy functionality may be limited");
+ }
+ g_object_unref(root);
+
+ return uri;
+}
+
+static void resolve_task(GTask *task, const gchar *target, GBytes *data)
+{
+ GError *err = NULL;
+ gchar *mount_uri, *uris;
+
+ mount_uri = clipboard_webdav_mount_get_uri(webdav_mount, &err);
+ if (!mount_uri) {
+ g_task_return_error(task, err);
+ g_object_unref(task);
+ return;
+ }
+
+ uris = clipboard_data_to_uris(target, mount_uri,
+ g_bytes_get_data(data, NULL), g_bytes_get_size(data), &err);
+ g_free(mount_uri);
+ if (err) {
+ g_task_return_error(task, err);
+ g_object_unref(task);
+ return;
+ }
+
+ g_task_return_pointer(task, uris, g_free);
+ g_object_unref(task);
+}
+
+static void unmounted_cb(GMount *mount, gpointer user_data)
+{
+ syslog(LOG_DEBUG, "%s unmounted", CLIPBOARD_WEBDAV_URI);
+ g_clear_object(&webdav_mount);
+}
+
+static void mount_found_cb(GObject *source, GAsyncResult *res, gpointer user_data)
+{
+ GTask *task = user_data;
+ GError *err = NULL;
+
+ webdav_mount = g_file_find_enclosing_mount_finish(G_FILE(source), res, &err);
+
+ if (err) {
+ g_task_return_error(task, err);
+ g_object_unref(task);
+ }
+ syslog(LOG_DEBUG, "mount %s found", CLIPBOARD_WEBDAV_URI);
+
+ g_signal_connect(webdav_mount, "unmounted", G_CALLBACK(unmounted_cb), NULL);
+
+ gchar *target = g_object_get_data(G_OBJECT(task), "target");
+ GBytes *data = g_task_get_task_data(task);
+ resolve_task(task, target, data);
+}
+
+static void mounted_cb(GObject *source, GAsyncResult *res, gpointer user_data)
+{
+ GFile *f = G_FILE(source);
+ GTask *task = user_data;
+ GError *err = NULL;
+
+ g_file_mount_enclosing_volume_finish(f, res, &err);
+
+ if (g_error_matches(err, G_IO_ERROR, G_IO_ERROR_ALREADY_MOUNTED)) {
+ g_clear_error(&err);
+ } else if (err) {
+ g_task_return_error(task, err);
+ g_object_unref(task);
+ return;
+ }
+ syslog(LOG_DEBUG, "%s mounted successfully", CLIPBOARD_WEBDAV_URI);
+
+ g_file_find_enclosing_mount_async(f, G_PRIORITY_DEFAULT, cancellable, mount_found_cb, task);
+}
+
+static void clipboard_webdav_mount_async(GTask *task)
+{
+ g_return_if_fail(webdav_mount == NULL);
+
+ syslog(LOG_DEBUG, "mounting %s", CLIPBOARD_WEBDAV_URI);
+
+ GFile *f = g_file_new_for_uri(CLIPBOARD_WEBDAV_URI);
+ g_file_mount_enclosing_volume(f,
+ G_MOUNT_MOUNT_NONE,
+ NULL, /* GMountOperation */
+ cancellable,
+ mounted_cb,
+ task);
+ g_object_unref(f);
+}
+
+gchar *clipboard_data_translate_to_uris_finish(GObject *source,
+ GAsyncResult *res, gsize *size, GError **err)
+{
+ *size = 0;
+ g_return_val_if_fail(g_task_is_valid(res, source), NULL);
+
+ gchar *uris = g_task_propagate_pointer(G_TASK(res), err);
+ if (uris) {
+ *size = strlen(uris);
+ }
+ return uris;
+}
+
+void clipboard_data_translate_to_uris_async(const gchar *target, GBytes *data,
+ GCancellable *cancel, GAsyncReadyCallback callback, gpointer user_data)
+{
+ GTask *task = g_task_new(NULL, cancel, callback, user_data);
+
+ if (!webdav_mount) {
+ g_task_set_task_data(task, g_bytes_ref(data), (GDestroyNotify)g_bytes_unref);
+ g_object_set_data_full(G_OBJECT(task), "target", g_strdup(target), g_free);
+ clipboard_webdav_mount_async(task);
+ return;
+ }
+
+ resolve_task(task, target, data);
+}
+
+void clipboard_webdav_init()
+{
+ /* we listen to the "unmounted" signal,
+ * but without the GVolumeMonitor, the signal is not emitted,
+ * although the docs don't seem to mention it!
+ * https://gitlab.gnome.org/GNOME/gvfs/-/issues/494 */
+
+ monitor = g_volume_monitor_get();
+ webdav_mount = NULL;
+ cancellable = g_cancellable_new();
+}
+
+void clipboard_webdav_finalize()
+{
+ g_cancellable_cancel(cancellable);
+ g_clear_object(&cancellable);
+ g_clear_object(&webdav_mount);
+ g_clear_object(&monitor);
+}
diff --git a/src/vdagent/webdav-cb.h b/src/vdagent/webdav-cb.h
new file mode 100644
index 0000000..e6e4bed
--- /dev/null
+++ b/src/vdagent/webdav-cb.h
@@ -0,0 +1,38 @@
+/* webdav-cb.h
+
+ Copyright 2020 Red Hat, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#include <gio/gio.h>
+
+void clipboard_webdav_init();
+void clipboard_webdav_finalize();
+
+/* converts the @data received from spice-gtk to the given @target;
+ * supported targets are:
+ * - "text/uri-list"
+ * - "text/plain;charset=utf-8"
+ * - "application/x-kde-cutselection"
+ * - "x-special/gnome-copied-files"
+ * - "x-special/mate-copied-files"
+ */
+void clipboard_data_translate_to_uris_async(const gchar *target, GBytes *data,
+ GCancellable *cancel, GAsyncReadyCallback callback, gpointer user_data);
+
+gchar *clipboard_data_translate_to_uris_finish(GObject *source,
+ GAsyncResult *res, gsize *size, GError **err);
diff --git a/src/vdagent/x11-priv.h b/src/vdagent/x11-priv.h
index 5fa367b..f6f7efe 100644
--- a/src/vdagent/x11-priv.h
+++ b/src/vdagent/x11-priv.h
@@ -9,6 +9,9 @@
#include <X11/extensions/Xrandr.h>
#ifndef USE_GTK_FOR_CLIPBOARD
+
+#include "webdav-cb.h"
+
/* Macros to print a message to the logfile prefixed by the selection */
#define SELPRINTF(format, ...) \
syslog(LOG_ERR, "%s: " format, \
@@ -63,6 +66,9 @@ static const struct clipboard_format_tmpl clipboard_format_templates[] = {
"image/x-MS-bmp", "image/x-win-bitmap", NULL }, },
{ VD_AGENT_CLIPBOARD_IMAGE_TIFF, { "image/tiff", NULL }, },
{ VD_AGENT_CLIPBOARD_IMAGE_JPG, { "image/jpeg", NULL }, },
+ { VD_AGENT_CLIPBOARD_FILE_LIST, { "text/uri-list",
+ "text/plain;charset=utf-8", "application/x-kde-cutselection",
+ "x-special/gnome-copied-files", "x-special/mate-copied-files", NULL } },
};
#define clipboard_format_count (sizeof(clipboard_format_templates)/sizeof(clipboard_format_templates[0]))
@@ -103,6 +109,7 @@ struct vdagent_x11 {
int clipboard_owner[256];
int clipboard_type_count[256];
uint32_t clipboard_agent_types[256][256];
+ Bool clipboard_has_files[256];
Atom clipboard_x11_targets[256][256];
/* Data for conversion_req which is currently being processed */
struct vdagent_x11_conversion_request *conversion_req;
@@ -115,6 +122,7 @@ struct vdagent_x11 {
uint8_t *selection_req_data;
uint32_t selection_req_data_pos;
uint32_t selection_req_data_size;
+ GBytes *file_list_data[256];
Atom selection_req_atom;
#endif
Window root_window[MAX_SCREENS];
diff --git a/src/vdagent/x11.c b/src/vdagent/x11.c
index fe13c76..58b99b7 100644
--- a/src/vdagent/x11.c
+++ b/src/vdagent/x11.c
@@ -74,6 +74,7 @@ static void vdagent_x11_send_selection_notify(struct vdagent_x11 *x11,
Atom prop, struct vdagent_x11_selection_request *request);
static void vdagent_x11_set_clipboard_owner(struct vdagent_x11 *x11,
uint8_t selection, int new_owner);
+static void uris_ready_cb(GObject *source, GAsyncResult *res, gpointer user_data);
static const char *vdagent_x11_sel_to_str(uint8_t selection) {
switch (selection) {
@@ -287,6 +288,8 @@ struct vdagent_x11 *vdagent_x11_create(UdscsConnection *vdagentd,
/* Be a good X11 citizen and maximize the amount of data we send at once */
if (x11->max_prop_size > 262144)
x11->max_prop_size = 262144;
+
+ clipboard_webdav_init();
#endif
for (i = 0; i < x11->screen_count; i++) {
@@ -326,6 +329,8 @@ void vdagent_x11_destroy(struct vdagent_x11 *x11, int vdagentd_disconnected)
XFree(x11->atom_name_cache[i].name);
}
}
+
+ clipboard_webdav_finalize();
#endif
g_hash_table_destroy(x11->guest_output_map);
@@ -421,6 +426,9 @@ static void vdagent_x11_set_clipboard_owner(struct vdagent_x11 *x11,
}
}
+ x11->clipboard_has_files[selection] = False;
+ g_clear_pointer(&x11->file_list_data[selection], g_bytes_unref);
+
if (new_owner == owner_none) {
/* When going from owner_guest to owner_none we need to send a
clipboard release message to the client */
@@ -799,6 +807,17 @@ static uint32_t vdagent_x11_target_to_type(struct vdagent_x11 *x11,
int i, j;
for (i = 0; i < clipboard_format_count; i++) {
+ /* targets for VD_AGENT_CLIPBOARD_FILE_LIST overlap with the text targets */
+ if (x11->clipboard_has_files[selection]) {
+ if (x11->clipboard_formats[i].type == VD_AGENT_CLIPBOARD_UTF8_TEXT) {
+ continue;
+ }
+ } else {
+ if (x11->clipboard_formats[i].type == VD_AGENT_CLIPBOARD_FILE_LIST) {
+ continue;
+ }
+ }
+
for (j = 0; j < x11->clipboard_formats[i].atom_count; j++) {
if (x11->clipboard_formats[i].atoms[j] == target) {
return x11->clipboard_formats[i].type;
@@ -968,6 +987,11 @@ static void vdagent_x11_handle_targets_notify(struct vdagent_x11 *x11,
type_count = &x11->clipboard_type_count[selection];
*type_count = 0;
for (i = 0; i < clipboard_format_count; i++) {
+ if (x11->clipboard_formats[i].type == VD_AGENT_CLIPBOARD_FILE_LIST) {
+ /* we don't support file copying in this direction yet */
+ continue;
+ }
+
atom = atom_lists_overlap(x11->clipboard_formats[i].atoms, atoms,
x11->clipboard_formats[i].atom_count, len);
if (atom) {
@@ -1029,6 +1053,11 @@ static void vdagent_x11_send_targets(struct vdagent_x11 *x11,
int i, j, k, target_count = 1;
for (i = 0; i < x11->clipboard_type_count[selection]; i++) {
+ if (x11->clipboard_agent_types[selection][i] == VD_AGENT_CLIPBOARD_UTF8_TEXT &&
+ x11->clipboard_has_files[selection]) {
+ continue;
+ }
+
for (j = 0; j < clipboard_format_count; j++) {
if (x11->clipboard_formats[j].type !=
x11->clipboard_agent_types[selection][i])
@@ -1115,6 +1144,17 @@ static void vdagent_x11_handle_selection_request(struct vdagent_x11 *x11)
return;
}
+ if (type == VD_AGENT_CLIPBOARD_FILE_LIST && x11->file_list_data[selection]) {
+ VSELPRINTF("setting file list from cache");
+
+ clipboard_data_translate_to_uris_async(
+ vdagent_x11_get_atom_name(x11, event->xselectionrequest.target),
+ x11->file_list_data[selection],
+ NULL, uris_ready_cb, x11
+ );
+ return;
+ }
+
udscs_write(x11->vdagentd, VDAGENTD_CLIPBOARD_REQUEST, selection, type,
NULL, 0);
}
@@ -1249,6 +1289,13 @@ void vdagent_x11_clipboard_grab(struct vdagent_x11 *x11, uint8_t selection,
x11->selection_window, CurrentTime);
vdagent_x11_set_clipboard_owner(x11, selection, owner_client);
+ for (int i = 0; i < x11->clipboard_type_count[selection]; i++) {
+ if (x11->clipboard_agent_types[selection][i] == VD_AGENT_CLIPBOARD_FILE_LIST) {
+ x11->clipboard_has_files[selection] = True;
+ break;
+ }
+ }
+
/* If there're pending requests for targets, ignore the returned
* targets as the XSetSelectionOwner() call above made them invalid */
x11->ignore_targets_notifies[selection] =
@@ -1318,6 +1365,32 @@ static void clipboard_data_send_to_requestor(struct vdagent_x11 *x11,
}
}
+static void uris_ready_cb(GObject *source, GAsyncResult *res, gpointer user_data)
+{
+ struct vdagent_x11 *x11 = user_data;
+ GError *err = NULL;
+ size_t size;
+
+ char *uris = clipboard_data_translate_to_uris_finish(source, res, &size, &err);
+ if (g_error_matches(err, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ g_error_free(err);
+ return;
+ }
+ if (!x11->selection_req) {
+ return;
+ }
+ uint8_t selection = x11->selection_req->selection;
+ if (err) {
+ SELPRINTF("failed to translate data to uris %s", err->message);
+ g_error_free(err);
+ }
+
+ clipboard_data_send_to_requestor(x11, selection, (uint8_t *)uris, size, True);
+
+ /* Flush output buffers and consume any pending events */
+ vdagent_x11_do_read(x11);
+}
+
void vdagent_x11_clipboard_data(struct vdagent_x11 *x11, uint8_t selection,
uint32_t type, uint8_t *data, uint32_t size)
{
@@ -1361,7 +1434,18 @@ void vdagent_x11_clipboard_data(struct vdagent_x11 *x11, uint8_t selection,
return;
}
- clipboard_data_send_to_requestor(x11, selection, data, size, False);
+ if (type == VD_AGENT_CLIPBOARD_FILE_LIST) {
+ g_clear_pointer(&x11->file_list_data[selection], g_bytes_unref);
+ x11->file_list_data[selection] = g_bytes_new(data, size);
+
+ clipboard_data_translate_to_uris_async(
+ vdagent_x11_get_atom_name(x11, event->xselectionrequest.target),
+ x11->file_list_data[selection], NULL, uris_ready_cb, x11
+ );
+ return;
+ } else {
+ clipboard_data_send_to_requestor(x11, selection, data, size, False);
+ }
/* Flush output buffers and consume any pending events */
vdagent_x11_do_read(x11);