<div dir="ltr"><div dir="ltr">Hi,<br></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Wed, Sep 9, 2020 at 5:50 PM GitLab Mirror <<a href="mailto:gitlab-mirror@kemper.freedesktop.org">gitlab-mirror@kemper.freedesktop.org</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"> meson.build              |   13 -<br>
 src/map-file             |    1 <br>
 src/spice-glib-sym-file  |    1 <br>
 src/spice-gtk-session.c  |  399 +++++++++++++++++++++++++++++++++++++++++++++++<br>
 src/spice-session-priv.h |    1 <br>
 src/spice-session.c      |   30 +++<br>
 src/spice-session.h      |    5 <br>
 7 files changed, 443 insertions(+), 7 deletions(-)<br>
<br>
New commits:<br>
commit f33d589d747f4f7ee6a1241c344ca611a36e9c71<br>
Author: Jakub Janků <<a href="mailto:jjanku@redhat.com" target="_blank">jjanku@redhat.com</a>><br>
Date:   Fri May 29 17:59:38 2020 +0200<br>
<br>
    clipboard: enable copying files to guest using webdav<br>
<br>
    When an app advertises the "text/uri-list" target, the user<br>
    probably wants to copy/move files. Spice-gtk then sends<br>
    a grab message to the vdagent advertising the<br>
    VD_AGENT_CLIPBOARD_FILE_LIST type.<br>
<br>
    Vdagent can then request clipboard data in this type.<br>
<br>
    Spice-gtk tries to talk to the app that owns the clipboard<br>
    in its native format in order to determine the preferred<br>
    file operation (copy X move).<br>
<br>
    For GNOME Nautilus, that's simply "UTF8_TEXT",<br>
    for KDE Dolphin, "application/x-kde-cutselection".<br>
<br>
    Otherwise the generic "text/uri-list" is used that does not<br>
    provide any additional information.<br>
<br>
    Once the uri list is obtained from the app, spice-gtk<br>
    creates a unique virtual dir in the ".spice-clipboard"<br>
    directory that is designated for this purpose.<br>
<br>
    Each file is attached inside this virtual dir using<br>
    phodav_virtual_dir_attach_real_child(), see phodav API<br>
    for details.<br>
<br>
    A list of paths in the phodav server is then sent to vdagent,<br>
    as specified in the spice-protocol.<br>
    Such path can for example look like this:<br>
        /.spice-clipboard/b8f0249c-082a-4da9-9a38-2de3237a66f0/file<br>
<br>
    It is up to the vdagent to ensure that the spice shared folder<br>
    is accessible and to set the clipboard data in a format that<br>
    other apps understand.<br>
<br>
    This requires new phodav with PhodavVirtualDir API.<br>
<br>
    Signed-off-by: Jakub Janků <<a href="mailto:jjanku@redhat.com" target="_blank">jjanku@redhat.com</a>><br>
    Acked-by: Frediano Ziglio <<a href="mailto:fziglio@redhat.com" target="_blank">fziglio@redhat.com</a>><br>
<br>
diff --git a/meson.build b/meson.build<br>
index 7ade460..e43139e 100644<br>
--- a/meson.build<br>
+++ b/meson.build<br>
@@ -33,7 +33,7 @@ spice_glib_deps = []<br>
 spice_gtk_deps = []<br>
 spice_wayland_deps = []<br>
 spice_acl_deps = []<br>
-spice_protocol_version = '0.14.2'<br>
+spice_protocol_version = '0.14.3'<br>
<br>
 #<br>
 # Set up subprojects<br>
diff --git a/src/spice-gtk-session.c b/src/spice-gtk-session.c<br>
index 5e6be4a..dfbd8fa 100644<br>
--- a/src/spice-gtk-session.c<br>
+++ b/src/spice-gtk-session.c<br>
@@ -34,6 +34,10 @@<br>
 #endif<br>
 #endif<br>
<br>
+#ifdef HAVE_PHODAV_VIRTUAL<br>
+#include <libphodav/phodav.h><br>
+#endif<br>
+<br>
 #include <gtk/gtk.h><br>
 #include <spice/vd_agent.h><br>
 #include "desktop-integration.h"<br>
@@ -61,6 +65,8 @@ struct _SpiceGtkSessionPrivate {<br>
     gboolean                clip_grabbed[CLIPBOARD_LAST];<br>
     gboolean                clipboard_by_guest[CLIPBOARD_LAST];<br>
     guint                   clipboard_release_delay[CLIPBOARD_LAST];<br>
+    /* TODO: maybe add a way of restoring this? */<br>
+    GHashTable              *cb_shared_files;<br>
     /* auto-usbredir related */<br>
     gboolean                auto_usbredir_enable;<br>
     int                     auto_usbredir_reqs;<br>
@@ -191,6 +197,12 @@ static void spice_gtk_session_init(SpiceGtkSession *self)<br>
<br>
     s = self->priv = spice_gtk_session_get_instance_private(self);<br>
<br>
+    s->cb_shared_files =<br>
+        g_hash_table_new_full(g_file_hash,<br>
+                              (GEqualFunc)g_file_equal,<br>
+                              g_object_unref, /* unref GFile */<br>
+                              g_free /* free gchar * */<br>
+                             );<br>
     s->clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);<br>
     g_signal_connect(G_OBJECT(s->clipboard), "owner-change",<br>
                      G_CALLBACK(clipboard_owner_change), self);<br>
@@ -252,6 +264,7 @@ static void spice_gtk_session_dispose(GObject *gobject)<br>
                                              self);<br>
         s->session = NULL;<br>
     }<br>
+    g_clear_pointer(&s->cb_shared_files, g_hash_table_destroy);<br>
<br>
     /* Chain up to the parent class */<br>
     if (G_OBJECT_CLASS(spice_gtk_session_parent_class)->dispose)<br>
@@ -544,6 +557,9 @@ static const struct {<br>
     },{<br>
         .vdagent = VD_AGENT_CLIPBOARD_IMAGE_JPG,<br>
         .xatom   = "image/jpeg"<br>
+    },{<br>
+        .vdagent = VD_AGENT_CLIPBOARD_FILE_LIST,<br>
+        .xatom   = "text/uri-list"<br>
     }<br>
 };<br>
<br>
@@ -660,6 +676,18 @@ static void clipboard_get_targets(GtkClipboard *clipboard,<br>
                 continue;<br>
             }<br>
<br>
+            if (atom2agent[m].vdagent == VD_AGENT_CLIPBOARD_FILE_LIST) {<br>
+#ifdef HAVE_PHODAV_VIRTUAL<br>
+                if (!clipboard_get_open_webdav(s->session)) {<br>
+                    SPICE_DEBUG("Received %s target, but the clipboard webdav channel "<br>
+                                "isn't available, skipping", atom2agent[m].xatom);<br>
+                    break;<br>
+                }<br>
+#else<br>
+                break;<br>
+#endif<br>
+            }<br>
+<br>
             /* check if type is already in list */<br>
             for (t = 0; t < num_types; t++) {<br>
                 if (types[t] == atom2agent[m].vdagent) {<br>
@@ -1037,6 +1065,318 @@ notify_agent:<br>
     g_free(conv);<br>
 }<br>
<br>
+#ifdef HAVE_PHODAV_VIRTUAL<br>
+/* returns path to @file under @root in clipboard phodav server, or NULL on error */<br>
+static gchar *clipboard_webdav_share_file(PhodavVirtualDir *root, GFile *file)<br>
+{<br>
+    gchar *uuid;<br>
+    PhodavVirtualDir *dir;<br>
+    GError *err = NULL;<br>
+<br>
+    /* separate directory is created for each file,<br>
+     * as we want to preserve the original filename and avoid conflicts */<br>
+    for (guint i = 0; i < 8; i++) {<br>
+        uuid = g_uuid_string_random();<br>
+        gchar *dir_path = g_strdup_printf(SPICE_WEBDAV_CLIPBOARD_FOLDER_PATH "/%s", uuid);<br>
+        dir = phodav_virtual_dir_new_dir(root, dir_path, &err);<br>
+        g_free(dir_path);<br>
+        if (!err) {<br>
+            break;<br>
+        }<br>
+        g_clear_pointer(&uuid, g_free);<br>
+        if (!g_error_matches(err, G_IO_ERROR, G_IO_ERROR_EXISTS)) {<br>
+            g_warning("failed to create phodav virtual dir: %s", err->message);<br>
+            g_error_free(err);<br>
+            return NULL;<br>
+        }<br>
+        g_clear_error(&err);<br>
+    }<br>
+<br>
+    if (!dir) {<br>
+        g_warning("failed to create phodav virtual dir: all attempts failed");<br>
+        return NULL;<br>
+    }<br>
+<br>
+    phodav_virtual_dir_attach_real_child(dir, file);<br>
+    g_object_unref(dir);<br>
+<br>
+    gchar *base = g_file_get_basename(file);<br>
+    gchar *path = g_strdup_printf(SPICE_WEBDAV_CLIPBOARD_FOLDER_PATH "/%s/%s", uuid, base);<br>
+    g_free(uuid);<br>
+    g_free(base);<br>
+<br>
+    return path;<br>
+}<br>
+<br>
+/* join all strings in @strv into a new char array,<br>
+ * including all terminating NULL-chars */<br>
+static gchar *strv_concat(gchar **strv, gsize *size_out)<br>
+{<br>
+    gchar **str_p, *arr, *curr;<br>
+<br>
+    g_return_val_if_fail(strv && size_out, NULL);<br>
+<br>
+    for (str_p = strv, *size_out = 0; *str_p != NULL; str_p++) {<br>
+        *size_out += strlen(*str_p) + 1;<br>
+    }<br>
+<br>
+    arr = g_malloc(*size_out);<br>
+<br>
+    for (str_p = strv, curr = arr; *str_p != NULL; str_p++) {<br>
+        curr = g_stpcpy(curr, *str_p) + 1;<br>
+    }<br>
+<br>
+    return arr;<br>
+}<br>
+<br>
+/* if not done alreay, share all files in @uris using the webdav server<br>
+ * and return a new buffer with VD_AGENT_CLIPBOARD_FILE_LIST data */<br>
+static gchar *strv_uris_transform_to_data(SpiceGtkSessionPrivate *s,<br>
+    gchar **uris, gsize *size_out, GdkDragAction action)<br>
+{<br>
+    SpiceWebdavChannel *webdav;<br>
+    PhodavServer *phodav;<br>
+    PhodavVirtualDir *root;<br>
+<br>
+    gchar **uri_ptr, *path, **paths, *data;<br>
+    GFile *file;<br>
+    guint n;<br>
+<br>
+    *size_out = 0;<br>
+<br>
+    if (!uris || g_strv_length(uris) < 1) {<br>
+        return NULL;<br>
+    }<br>
+<br>
+    webdav = clipboard_get_open_webdav(s->session);<br>
+    if (!webdav) {<br>
+        SPICE_DEBUG("Received uris, but no webdav channel");<br>
+        return NULL;<br>
+    }<br>
+<br>
+    phodav = spice_session_get_webdav_server(s->session);<br>
+    g_object_get(phodav, "root-file", &root, NULL);<br>
+<br>
+    paths = g_new0(gchar *, g_strv_length(uris) + 2);<br>
+<br>
+    paths[0] = action == GDK_ACTION_MOVE ? "cut" : "copy";<br>
+    n = 1;<br>
+<br>
+    for (uri_ptr = uris; *uri_ptr != NULL; uri_ptr++) {<br>
+        file = g_file_new_for_uri(*uri_ptr);<br>
+<br>
+        /* clipboard data is usually requested multiple times for no obvious reasons<br>
+         * (clipboar managers to blame?), we don't want to create multiple dirs for the same file */<br>
+        path = g_hash_table_lookup(s->cb_shared_files, file);<br>
+        if (path) {<br>
+            SPICE_DEBUG("found %s with path %s", *uri_ptr, path);<br>
+            g_object_unref(file);<br>
+        } else {<br>
+            path = clipboard_webdav_share_file(root, file);<br>
+            g_return_val_if_fail(path != NULL, NULL);<br>
+            SPICE_DEBUG("publishing %s under %s", *uri_ptr, path);<br>
+            /* file and path gets freed once the hash table gets destroyed */<br>
+            g_hash_table_insert(s->cb_shared_files, file, path);<br>
+        }<br>
+        paths[n] = path;<br>
+        n++;<br>
+    }<br>
+<br>
+    g_object_unref(root);<br>
+    data = strv_concat(paths, size_out);<br>
+    g_free(paths);<br>
+<br>
+    return data;<br>
+}<br>
+<br>
+static GdkAtom a_gnome, a_mate, a_nautilus, a_uri_list, a_kde_cut;<br>
+<br>
+static void init_uris_atoms()<br>
+{<br>
+    if (a_gnome != GDK_NONE) {<br>
+        return;<br>
+    }<br>
+    a_gnome = gdk_atom_intern_static_string("x-special/gnome-copied-files");<br>
+    a_mate = gdk_atom_intern_static_string("x-special/mate-copied-files");<br>
+    a_nautilus = gdk_atom_intern_static_string("UTF8_STRING");<br>
+    a_uri_list = gdk_atom_intern_static_string("text/uri-list");<br>
+    a_kde_cut = gdk_atom_intern_static_string("application/x-kde-cutselection");<br>
+}<br>
+<br>
+static GdkAtom clipboard_select_uris_atom(SpiceGtkSessionPrivate *s, guint selection)<br>
+{<br>
+    init_uris_atoms();<br>
+    if (clipboard_find_atom(s, selection, a_gnome)) {<br>
+        return a_gnome;<br>
+    }<br>
+    if (clipboard_find_atom(s, selection, a_mate)) {<br>
+        return a_mate;<br>
+    }<br>
+    if (clipboard_find_atom(s, selection, a_nautilus)) {<br>
+        return a_nautilus;<br>
+    }<br>
+    return clipboard_find_atom(s, selection, a_uri_list);<br>
+}<br>
+<br>
+/* common handler for "x-special/gnome-copied-files" and "x-special/mate-copied-files" */<br>
+static gchar *x_special_copied_files_transform_to_data(SpiceGtkSessionPrivate *s,<br>
+    GtkSelectionData *selection_data, gsize *size_out)<br>
+{<br>
+    const gchar *text;<br>
+    gchar **lines, *data = NULL;<br>
+    GdkDragAction action;<br>
+<br>
+    *size_out = 0;<br>
+<br>
+    text = (gchar *)gtk_selection_data_get_data(selection_data);<br>
+    if (!text) {<br>
+        return NULL;<br>
+    }<br>
+    lines = g_strsplit(text, "\n", -1);<br>
+    if (g_strv_length(lines) < 2) {<br>
+        goto err;<br>
+    }<br>
+<br>
+    if (!g_strcmp0(lines[0], "cut")) {<br>
+        action = GDK_ACTION_MOVE;<br>
+    } else if (!g_strcmp0(lines[0], "copy")) {<br>
+        action = GDK_ACTION_COPY;<br>
+    } else {<br>
+        goto err;<br>
+    }<br>
+<br>
+    data = strv_uris_transform_to_data(s, &lines[1], size_out, action);<br>
+err:<br>
+    g_strfreev(lines);<br>
+    return data;<br>
+}<br>
+<br>
+/* used with newer Nautilus */<br>
+static gchar *nautilus_uris_transform_to_data(SpiceGtkSessionPrivate *s,<br>
+    GtkSelectionData *selection_data, gsize *size_out, gboolean *retry_out)<br>
+{<br>
+    gchar **lines, *text, *data = NULL;<br>
+    guint n_lines;<br>
+    GdkDragAction action;<br>
+<br>
+    *size_out = 0;<br>
+<br>
+    text = (gchar *)gtk_selection_data_get_text(selection_data);<br>
+    if (!text) {<br>
+        return NULL;<br>
+    }<br>
+    lines = g_strsplit(text, "\n", -1);<br>
+    g_free(text);<br>
+    n_lines = g_strv_length(lines);<br>
+<br>
+    if (n_lines < 4) {<br>
+        *retry_out = TRUE;<br>
+        goto err;<br>
+    }<br>
+<br>
+    if (g_strcmp0(lines[0], "x-special/nautilus-clipboard")) {<br>
+        *retry_out = TRUE;<br>
+        goto err;<br>
+    }<br>
+<br>
+    if (!g_strcmp0(lines[1], "cut")) {<br>
+        action = GDK_ACTION_MOVE;<br>
+    } else if (!g_strcmp0(lines[1], "copy")) {<br>
+        action = GDK_ACTION_COPY;<br>
+    } else {<br>
+        goto err;<br>
+    }<br>
+<br>
+    /* the list of uris must end with \n,<br>
+     * so there must be an empty string after the split */<br>
+    if (g_strcmp0(lines[n_lines-1], "")) {<br>
+        goto err;<br>
+    }<br>
+    g_clear_pointer(&lines[n_lines-1], g_free);<br>
+<br>
+    data = strv_uris_transform_to_data(s, &lines[2], size_out, action);<br>
+err:<br>
+    g_strfreev(lines);<br>
+    return data;<br>
+}<br>
+<br>
+static GdkDragAction kde_get_clipboard_action(SpiceGtkSessionPrivate *s, GtkClipboard *clipboard)<br>
+{<br>
+    GtkSelectionData *selection_data;<br>
+    GdkDragAction action;<br>
+    const guchar *data;<br>
+<br>
+    /* this uses another GMainLoop, basically the same mechanism<br>
+     * as we use in clipboard_get(), so it doesn't block */<br>
+    selection_data = gtk_clipboard_wait_for_contents(clipboard, a_kde_cut);<br>
+    data = gtk_selection_data_get_data(selection_data);<br>
+    if (data && data[0] == '1') {<br>
+        action = GDK_ACTION_MOVE;<br>
+    } else {<br>
+        action = GDK_ACTION_COPY;<br>
+    }<br>
+    gtk_selection_data_free(selection_data);<br>
+<br>
+    return action;<br>
+}<br>
+<br>
+static void clipboard_received_uri_contents_cb(GtkClipboard *clipboard,<br>
+                                               GtkSelectionData *selection_data,<br>
+                                               gpointer user_data)<br>
+{<br>
+    SpiceGtkSession *self = free_weak_ref(user_data);<br>
+    SpiceGtkSessionPrivate *s;<br>
+    guint selection;<br>
+<br>
+    if (!self) {<br>
+        return;<br>
+    }<br>
+    s = self->priv;<br>
+<br>
+    selection = get_selection_from_clipboard(s, clipboard);<br>
+    g_return_if_fail(selection != -1);<br>
+<br>
+    init_uris_atoms();<br>
+    GdkAtom type = gtk_selection_data_get_data_type(selection_data);<br>
+    gchar *data;<br>
+    gsize len;<br>
+<br>
+    if (type == a_gnome || type == a_mate) {<br>
+        /* used by old Nautilus + many other file managers  */<br>
+        data = x_special_copied_files_transform_to_data(s, selection_data, &len);<br>
+    } else if (type == a_nautilus) {<br>
+        gboolean retry = FALSE;<br>
+        data = nautilus_uris_transform_to_data(s, selection_data, &len, &retry);<br>
+<br>
+        if (retry && clipboard_find_atom(s, selection, a_uri_list) != GDK_NONE) {<br>
+            /* it's not Nautilus, so we give it one more try with the generic uri-list target */<br>
+            gtk_clipboard_request_contents(clipboard, a_uri_list,<br>
+                clipboard_received_uri_contents_cb, get_weak_ref(self));<br>
+            return;<br>
+        }<br>
+    } else if (type == a_uri_list) {<br>
+        GdkDragAction action = GDK_ACTION_COPY;<br>
+        gchar **uris = gtk_selection_data_get_uris(selection_data);<br>
+<br>
+        /* KDE uses a separate atom to distinguish between copy and move operation */<br>
+        if (clipboard_find_atom(s, selection, a_kde_cut) != GDK_NONE) {<br>
+            action = kde_get_clipboard_action(s, clipboard);<br>
+        }<br>
+<br>
+        data = strv_uris_transform_to_data(s, uris, &len, action);<br>
+        g_strfreev(uris);<br>
+    } else {<br>
+        g_warning("received uris in unsupported type");<br>
+        data = NULL;<br>
+        len = 0;<br>
+    }<br>
+<br>
+    spice_main_channel_clipboard_selection_notify(s->main, selection,<br>
+        VD_AGENT_CLIPBOARD_FILE_LIST, (guchar *)data, len);<br>
+    g_free(data);<br>
+}<br>
+#endif<br>
+<br>
 static void clipboard_received_cb(GtkClipboard *clipboard,<br>
                                   GtkSelectionData *selection_data,<br>
                                   gpointer user_data)<br>
@@ -1111,6 +1451,17 @@ static gboolean clipboard_request(SpiceMainChannel *main, guint selection,<br>
     if (type == VD_AGENT_CLIPBOARD_UTF8_TEXT) {<br>
         gtk_clipboard_request_text(cb, clipboard_received_text_cb,<br>
                                    get_weak_ref(self));<br>
+    } else if (type == VD_AGENT_CLIPBOARD_FILE_LIST) {<br>
+#ifdef HAVE_PHODAV_VIRTUAL<br>
+        atom = clipboard_select_uris_atom(s, selection);<br>
+        if (atom == GDK_NONE) {<br>
+            return FALSE;<br>
+        }<br>
+        gtk_clipboard_request_contents(cb, atom,<br>
+            clipboard_received_uri_contents_cb, get_weak_ref(self));<br>
+#else<br>
+        return FALSE;<br>
+#endif<br>
     } else {<br>
         for (m = 0; m < SPICE_N_ELEMENTS(atom2agent); m++) {<br>
             if (atom2agent[m].vdagent == type)<br>
diff --git a/src/spice-session.c b/src/spice-session.c<br>
index f0ac891..8831bc5 100644<br>
--- a/src/spice-session.c<br>
+++ b/src/spice-session.c<br>
@@ -2666,6 +2666,17 @@ static void spice_session_set_shared_dir(SpiceSession *session, const gchar *dir<br>
<br>
     g_free(s->shared_dir);<br>
     s->shared_dir = g_strdup(dir);<br>
+<br>
+#ifdef HAVE_PHODAV_VIRTUAL<br>
+    if (s->webdav == NULL) {<br>
+        return;<br>
+    }<br>
+<br>
+    PhodavVirtualDir *root;<br>
+    g_object_get(s->webdav, "root-file", &root, NULL);<br>
+    phodav_virtual_dir_root_set_real(root, s->shared_dir);<br>
+    g_object_unref(root);<br>
+#endif<br>
 }<br>
<br>
 G_GNUC_INTERNAL<br>
@@ -2807,21 +2818,39 @@ PhodavServer* spice_session_get_webdav_server(SpiceSession *session)<br>
     static GMutex mutex;<br>
<br>
     const gchar *shared_dir = spice_session_get_shared_dir(session);<br>
+    /* with HAVE_PHODAV_VIRTUAL, PhodavServer must be created even if shared_dir is NULL */<br>
+#ifndef HAVE_PHODAV_VIRTUAL<br>
     if (shared_dir == NULL) {<br>
         SPICE_DEBUG("No shared dir set, not creating webdav server");<br>
         return NULL;<br>
     }<br>
+#endif<br>
<br>
     g_mutex_lock(&mutex);<br>
<br>
     if (priv->webdav == NULL) {<br>
+#ifdef HAVE_PHODAV_VIRTUAL<br>
+        PhodavVirtualDir *root = phodav_virtual_dir_new_root();<br>
+        priv->webdav = phodav_server_new_for_root_file(G_FILE(root));<br>
+<br>
+        phodav_virtual_dir_root_set_real(root, shared_dir);<br>
+<br>
+        g_object_unref(phodav_virtual_dir_new_dir(root, SPICE_WEBDAV_CLIPBOARD_FOLDER_PATH, NULL));<br>
+        g_object_unref(root);<br>
+#else<br>
         priv->webdav = phodav_server_new(shared_dir);<br>
+#endif<br>
+<br>
         g_object_bind_property(session,  "share-dir-ro",<br>
                                priv->webdav, "read-only",<br>
                                G_BINDING_SYNC_CREATE|G_BINDING_BIDIRECTIONAL);<br>
+<br>
+        /* with HAVE_PHODAV_VIRTUAL, the update is done in spice_session_set_shared_dir() */<br>
+#ifndef HAVE_PHODAV_VIRTUAL<br>
         g_object_bind_property(session,  "shared-dir",<br>
                                priv->webdav, "root",<br>
                                G_BINDING_SYNC_CREATE|G_BINDING_BIDIRECTIONAL);<br>
+#endif<br>
     }<br>
<br>
     g_mutex_unlock(&mutex);<br>
diff --git a/src/spice-session.h b/src/spice-session.h<br>
index 9436be8..665d2f3 100644<br>
--- a/src/spice-session.h<br>
+++ b/src/spice-session.h<br>
@@ -36,6 +36,8 @@ G_BEGIN_DECLS<br>
 #define SPICE_IS_SESSION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SPICE_TYPE_SESSION))<br>
 #define SPICE_SESSION_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SPICE_TYPE_SESSION, SpiceSessionClass))<br>
<br>
+#define SPICE_WEBDAV_CLIPBOARD_FOLDER_PATH "/.spice-clipboard"<br>
+<br>
 typedef struct _PhodavServer PhodavServer;<br>
<br>
 /**<br>
commit 996bfb48dc5d1d17bb56b4936f79d8705bcd5c48<br>
Author: Jakub Janků <<a href="mailto:jjanku@redhat.com" target="_blank">jjanku@redhat.com</a>><br>
Date:   Tue Jun 30 15:15:39 2020 +0200<br>
<br>
    spice-gtk-session: cache atoms<br>
<br>
    At the moment, spice-gtk only sends a grab message to the vdagent<br>
    based on the retrieved atoms.<br>
<br>
    With the upcoming changes, spice-gtk will have to know which<br>
    targets were advertised outside of clipboard_get_targets() callback.<br>
<br>
    We could use gtk_clipboard_wait_for_targets() or<br>
    gtk_clipboard_wait_is_*_available(), but the targets are not cached<br>
    by GTK+ on wayland for some reason. So let's cache them in spice-gtk<br>
    to avoid having to talk to the clipboard owner.<br>
<br>
    Signed-off-by: Jakub Janků <<a href="mailto:jjanku@redhat.com" target="_blank">jjanku@redhat.com</a>><br>
    Acked-by: Frediano Ziglio <<a href="mailto:fziglio@redhat.com" target="_blank">fziglio@redhat.com</a>><br>
<br>
diff --git a/src/spice-gtk-session.c b/src/spice-gtk-session.c<br>
index 2b86616..5e6be4a 100644<br>
--- a/src/spice-gtk-session.c<br>
+++ b/src/spice-gtk-session.c<br>
@@ -55,6 +55,8 @@ struct _SpiceGtkSessionPrivate {<br>
     GtkClipboard            *clipboard_primary;<br>
     GtkTargetEntry          *clip_targets[CLIPBOARD_LAST];<br>
     guint                   nclip_targets[CLIPBOARD_LAST];<br>
+    GdkAtom                 *atoms[CLIPBOARD_LAST];<br>
+    guint                   n_atoms[CLIPBOARD_LAST];<br>
     gboolean                clip_hasdata[CLIPBOARD_LAST];<br>
     gboolean                clip_grabbed[CLIPBOARD_LAST];<br>
     gboolean                clipboard_by_guest[CLIPBOARD_LAST];<br>
@@ -284,6 +286,8 @@ static void spice_gtk_session_finalize(GObject *gobject)<br>
     for (i = 0; i < CLIPBOARD_LAST; ++i) {<br>
         g_clear_pointer(&s->clip_targets[i], g_free);<br>
         clipboard_release_delay_remove(self, i, true);<br>
+        g_clear_pointer(&s->atoms[i], g_free);<br>
+        s->n_atoms[i] = 0;<br>
     }<br>
<br>
     /* Chain up to the parent class */<br>
@@ -589,6 +593,16 @@ static SpiceWebdavChannel *clipboard_get_open_webdav(SpiceSession *session)<br>
     g_list_free(list);<br>
     return open ? SPICE_WEBDAV_CHANNEL(channel) : NULL;<br>
 }<br>
+<br>
+static GdkAtom clipboard_find_atom(SpiceGtkSessionPrivate *s, guint selection, GdkAtom a)<br>
+{<br>
+    for (int i = 0; i < s->n_atoms[selection]; i++) {<br>
+        if (s->atoms[selection][i] == a) {<br>
+            return a;<br>
+        }<br>
+    }<br>
+    return GDK_NONE;<br>
+}<br>
 #endif<br>
<br>
 static void clipboard_get_targets(GtkClipboard *clipboard,<br>
@@ -622,6 +636,11 @@ static void clipboard_get_targets(GtkClipboard *clipboard,<br>
     selection = get_selection_from_clipboard(s, clipboard);<br>
     g_return_if_fail(selection != -1);<br>
<br>
+    /* GTK+ does seem to cache atoms, but not for Wayland */<br>
+    g_free(s->atoms[selection]);<br>
+    s->atoms[selection] = g_memdup(atoms, n_atoms * sizeof(GdkAtom));<br>
+    s->n_atoms[selection] = n_atoms;<br>
+<br>
     if (s->clip_grabbed[selection]) {<br>
         SPICE_DEBUG("Clipboard is already grabbed, re-grab: %d atoms", n_atoms);<br>
     }<br>
@@ -705,6 +724,9 @@ static void clipboard_owner_change(GtkClipboard        *clipboard,<br>
         return;<br>
     }<br>
<br>
+    g_clear_pointer(&s->atoms[selection], g_free);<br>
+    s->n_atoms[selection] = 0;<br>
+<br>
     if (event->reason != GDK_OWNER_CHANGE_NEW_OWNER) {<br>
         if (s->clip_grabbed[selection]) {<br>
             /* grab was sent to the agent, so release it */<br>
commit 852b847c868a199b5127644ca689f8a7d70fbda1<br>
Author: Jakub Janků <<a href="mailto:jjanku@redhat.com" target="_blank">jjanku@redhat.com</a>><br>
Date:   Fri May 29 17:57:51 2020 +0200<br>
<br>
    spice-gtk-session: add clipboard_get_open_webdav()<br>
<br>
    File copy&paste functionality will only be enabled when there is an open<br>
    webdav channel.<br>
<br>
    Signed-off-by: Jakub Janků <<a href="mailto:jjanku@redhat.com" target="_blank">jjanku@redhat.com</a>><br>
    Acked-by: Frediano Ziglio <<a href="mailto:fziglio@redhat.com" target="_blank">fziglio@redhat.com</a>><br>
<br>
diff --git a/src/spice-gtk-session.c b/src/spice-gtk-session.c<br>
index 48058c7..2b86616 100644<br>
--- a/src/spice-gtk-session.c<br>
+++ b/src/spice-gtk-session.c<br>
@@ -565,6 +565,32 @@ static gpointer free_weak_ref(gpointer data)<br>
     return object;<br>
 }<br>
<br>
+#ifdef HAVE_PHODAV_VIRTUAL<br>
+static SpiceWebdavChannel *clipboard_get_open_webdav(SpiceSession *session)<br>
+{<br>
+    GList *list, *l;<br>
+    SpiceChannel *channel = NULL;<br>
+    gboolean open = FALSE;<br>
+<br>
+    g_return_val_if_fail(session != NULL, NULL);<br>
+<br>
+    list = spice_session_get_channels(session);<br>
+    for (l = g_list_first(list); l != NULL; l = g_list_next(l)) {<br>
+        channel = l->data;<br>
+<br>
+        if (!SPICE_IS_WEBDAV_CHANNEL(channel)) {<br>
+            continue;<br>
+        }<br>
+<br>
+        g_object_get(channel, "port-opened", &open, NULL);<br>
+        break;<br>
+    }<br>
+<br>
+    g_list_free(list);<br>
+    return open ? SPICE_WEBDAV_CHANNEL(channel) : NULL;<br>
+}<br>
+#endif<br>
+<br>
 static void clipboard_get_targets(GtkClipboard *clipboard,<br>
                                   GdkAtom *atoms,<br>
                                   gint n_atoms,<br>
commit c1b5433815e5cd7683671d33a0d579b7b185efe8<br>
Author: Jakub Janků <<a href="mailto:jjanku@redhat.com" target="_blank">jjanku@redhat.com</a>><br>
Date:   Mon Jun 29 19:40:25 2020 +0200<br>
<br>
    build: require GLib 2.52+<br>
<br>
    This adds g_uuid_string_random()<br>
    which is necessary for the following file copy&paste<br>
    functionality.<br>
<br>
    Signed-off-by: Jakub Janků <<a href="mailto:jjanku@redhat.com" target="_blank">jjanku@redhat.com</a>><br>
    Acked-by: Frediano Ziglio <<a href="mailto:fziglio@redhat.com" target="_blank">fziglio@redhat.com</a>><br>
<br>
diff --git a/meson.build b/meson.build<br>
index 1c4e9d9..7ade460 100644<br>
--- a/meson.build<br>
+++ b/meson.build<br>
@@ -89,7 +89,7 @@ endforeach<br>
 #<br>
 # check for mandatory dependencies<br>
 #<br>
-glib_version = '2.46'<br>
+glib_version = '2.52'<br>
 glib_version_info = '>= @0@'.format(glib_version)<br>
 pixman_version = '>= 0.17.7'<br>
<br>
commit 979b752b24d6f8d7089a23760fd5adda18f0e7ed<br>
Author: Jakub Janků <<a href="mailto:jjanku@redhat.com" target="_blank">jjanku@redhat.com</a>><br>
Date:   Sat May 23 13:40:39 2020 +0200<br>
<br>
    build: define HAVE_PHODAV_VIRTUAL if phodav >= 2.5<br>
<br>
    Phodav 2.5 brings PhodavVirtualDir API needed for the<br>
    file copy and paste functionality.<br>
<br>
    If the library version is not sufficient, this new feature<br>
    will be disabled, but the standard shared folders can still<br>
    be used.<br>
<br>
    Signed-off-by: Jakub Janků <<a href="mailto:jjanku@redhat.com" target="_blank">jjanku@redhat.com</a>><br>
    Acked-by: Frediano Ziglio <<a href="mailto:fziglio@redhat.com" target="_blank">fziglio@redhat.com</a>><br>
<br>
diff --git a/meson.build b/meson.build<br>
index 6bbb4a8..1c4e9d9 100644<br>
--- a/meson.build<br>
+++ b/meson.build<br>
@@ -177,14 +177,17 @@ endif<br>
<br>
 # webdav<br>
 spice_gtk_has_phodav = false<br>
-d = dependency('libphodav-2.0', required: get_option('webdav'))<br>
-if d.found()<br>
-  spice_glib_deps += d<br>
+phodav_dep = dependency('libphodav-2.0', required: get_option('webdav'))<br>
+if phodav_dep.found()<br>
+  spice_glib_deps += phodav_dep<br>
   d = dependency('libsoup-2.4', version : '>= 2.49.91', required: get_option('webdav'))<br>
   if d.found()<br>
     spice_glib_deps += d<br>
     spice_gtk_config_data.set('USE_PHODAV', '1')<br>
     spice_gtk_has_phodav = true<br>
+    if phodav_dep.version().version_compare('>= 2.5')<br>
+      spice_gtk_config_data.set('HAVE_PHODAV_VIRTUAL', '1')<br>
+    endif<br>
   endif<br>
 endif<br>
<br>
commit 4b9092b96b8da946ff3d17922b0fcf225c5dc81f<br>
Author: Jakub Janků <<a href="mailto:jjanku@redhat.com" target="_blank">jjanku@redhat.com</a>><br>
Date:   Sat May 23 16:28:52 2020 +0200<br>
<br>
    session: make spice_session_get_webdav_server() public<br>
<br>
    It will be necessary to access the webdav server from spice-gtk-session.c<br>
    which isn't compiled with spice-session-priv.h, so make<br>
    spice_session_get_webdav_server() public.<br></blockquote><div><br></div><div>I haven't looked at the whole series. Wouldn't it make sense to make it a read-only property instead?<br></div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
<br>
    Signed-off-by: Jakub Janků <<a href="mailto:jjanku@redhat.com" target="_blank">jjanku@redhat.com</a>><br>
    Acked-by: Frediano Ziglio <<a href="mailto:fziglio@redhat.com" target="_blank">fziglio@redhat.com</a>><br>
<br>
diff --git a/src/map-file b/src/map-file<br>
index acdd38f..86f371d 100644<br>
--- a/src/map-file<br>
+++ b/src/map-file<br>
@@ -144,6 +144,7 @@ spice_session_new;<br>
 spice_session_open_fd;<br>
 spice_session_verify_get_type;<br>
 spice_set_session_option;<br>
+spice_session_get_webdav_server;<br>
 spice_smartcard_channel_get_type;<br>
 spice_smartcard_manager_get;<br>
 spice_smartcard_manager_get_readers;<br>
diff --git a/src/spice-glib-sym-file b/src/spice-glib-sym-file<br>
index 72e6ef0..effcd09 100644<br>
--- a/src/spice-glib-sym-file<br>
+++ b/src/spice-glib-sym-file<br>
@@ -123,6 +123,7 @@ spice_session_new<br>
 spice_session_open_fd<br>
 spice_session_verify_get_type<br>
 spice_set_session_option<br>
+spice_session_get_webdav_server<br>
 spice_smartcard_channel_get_type<br>
 spice_smartcard_manager_get<br>
 spice_smartcard_manager_get_readers<br>
diff --git a/src/spice-session-priv.h b/src/spice-session-priv.h<br>
index b4919a4..5b52f8d 100644<br>
--- a/src/spice-session-priv.h<br>
+++ b/src/spice-session-priv.h<br>
@@ -87,7 +87,6 @@ gboolean spice_session_get_smartcard_enabled(SpiceSession *session);<br>
 gboolean spice_session_get_usbredir_enabled(SpiceSession *session);<br>
 gboolean spice_session_get_gl_scanout_enabled(SpiceSession *session);<br>
<br>
-PhodavServer *spice_session_get_webdav_server(SpiceSession *session);<br>
 guint spice_session_get_n_display_channels(SpiceSession *session);<br>
 gboolean spice_session_set_migration_session(SpiceSession *session, SpiceSession *mig_session);<br>
 SpiceAudio *spice_audio_get(SpiceSession *session, GMainContext *context);<br>
diff --git a/src/spice-session.c b/src/spice-session.c<br>
index 6915736..f0ac891 100644<br>
--- a/src/spice-session.c<br>
+++ b/src/spice-session.c<br>
@@ -2796,7 +2796,6 @@ gboolean spice_session_get_smartcard_enabled(SpiceSession *session)<br>
     return session->priv->smartcard;<br>
 }<br>
<br>
-G_GNUC_INTERNAL<br>
 PhodavServer* spice_session_get_webdav_server(SpiceSession *session)<br>
 {<br>
     SpiceSessionPrivate *priv;<br>
diff --git a/src/spice-session.h b/src/spice-session.h<br>
index ed01c01..9436be8 100644<br>
--- a/src/spice-session.h<br>
+++ b/src/spice-session.h<br>
@@ -36,6 +36,8 @@ G_BEGIN_DECLS<br>
 #define SPICE_IS_SESSION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SPICE_TYPE_SESSION))<br>
 #define SPICE_SESSION_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SPICE_TYPE_SESSION, SpiceSessionClass))<br>
<br>
+typedef struct _PhodavServer PhodavServer;<br>
+<br>
 /**<br>
  * SpiceSessionVerify:<br>
  * @SPICE_SESSION_VERIFY_PUBKEY: verify certificate public key matching<br>
@@ -113,6 +115,7 @@ gboolean spice_session_has_channel_type(SpiceSession *session, gint type);<br>
 gboolean spice_session_get_read_only(SpiceSession *session);<br>
 SpiceURI *spice_session_get_proxy_uri(SpiceSession *session);<br>
 gboolean spice_session_is_for_migration(SpiceSession *session);<br>
+PhodavServer *spice_session_get_webdav_server(SpiceSession *session);<br>
<br>
 G_END_DECLS<br>
<br>
_______________________________________________<br>
Spice-commits mailing list<br>
<a href="mailto:Spice-commits@lists.freedesktop.org" target="_blank">Spice-commits@lists.freedesktop.org</a><br>
<a href="https://lists.freedesktop.org/mailman/listinfo/spice-commits" rel="noreferrer" target="_blank">https://lists.freedesktop.org/mailman/listinfo/spice-commits</a><br>
</blockquote></div><br clear="all"><br>-- <br><div dir="ltr" class="gmail_signature">Marc-André Lureau<br></div></div>