[RFC,spice-vdagent,8/8] clipboard: implement new SELECTION protocol

Submitted by =?UTF-8?q?Jakub=20Jank=C5=AF?= on May 31, 2018, 8:52 p.m.

Details

Message ID 20180531205225.4266-9-jjanku@redhat.com
State New
Headers show
Series "Clipboard - using MIME types" ( rev: 1 ) in Spice

Not browsing as part of any series.

Commit Message

=?UTF-8?q?Jakub=20Jank=C5=AF?= May 31, 2018, 8:52 p.m.
---
 src/vdagent/clipboard.c | 207 ++++++++++++++++++++++++++++++++++++----
 src/vdagent/clipboard.h |  11 +++
 src/vdagent/vdagent.c   |  15 +++
 src/vdagentd/vdagentd.c | 178 +++++++++++++++++++++++++++++++++-
 4 files changed, 393 insertions(+), 18 deletions(-)

Patch hide | download patch | download mbox

diff --git a/src/vdagent/clipboard.c b/src/vdagent/clipboard.c
index b8bb0ad..5493c11 100644
--- a/src/vdagent/clipboard.c
+++ b/src/vdagent/clipboard.c
@@ -22,6 +22,7 @@ 
 
 #include <gtk/gtk.h>
 #include <syslog.h>
+#include <string.h>
 
 #include "vdagentd-proto.h"
 #include "vdagentd-proto-strings.h"
@@ -96,6 +97,26 @@  static guint get_type_from_atom(GdkAtom atom)
     return VD_AGENT_CLIPBOARD_NONE;
 }
 
+static gboolean filter_target(const gchar *target)
+{
+    /* FIXME: exclude anything else? */
+    const gchar * const exclude[] = {
+        "TARGETS",
+        "SAVE_TARGETS",
+        "AVAILABLE_TARGETS",
+        "REQUESTED_TARGETS",
+        "TIMESTAMP",
+        "MULTIPLE",
+        NULL
+    };
+    guint i;
+
+    for (i = 0; exclude[i]; i++)
+        if (!g_ascii_strcasecmp(target, exclude[i]))
+            return TRUE;
+    return FALSE;
+}
+
 static gboolean send_grab(VDAgentClipboards *c, guint sel_id,
                           GdkAtom *atoms, gint n_atoms)
 {
@@ -131,6 +152,38 @@  static gboolean send_grab(VDAgentClipboards *c, guint sel_id,
                     (guint8 *)types, n_types * sizeof(guint32));
         break;
     }
+    case CLIPBOARD_PROTOCOL_SELECTION: {
+        gchar **names, *data, *ptr;
+        guint a, len;
+
+        names = g_new(gchar *, n_atoms);
+        len = 0;
+        for (a = 0; a < n_atoms; a++) {
+            names[a] = gdk_atom_name(atoms[a]);
+            if (filter_target(names[a]))
+                g_clear_pointer(&names[a], g_free);
+            else
+                len += strlen(names[a]) + 1;
+        }
+        if (len == 0) {
+            g_free(names);
+            return FALSE;
+        }
+
+        data = g_malloc(len);
+        for (a = 0, ptr = data; a < n_atoms; a++)
+            if (names[a])
+                ptr = g_stpcpy(ptr, names[a]) + 1;
+
+        udscs_write(c->conn, VDAGENTD_SELECTION_GRAB, sel_id, 0,
+                    (guint8 *)data, len);
+
+        for (a = 0; a < n_atoms; a++)
+            g_free(names[a]);
+        g_free(names);
+        g_free(data);
+        break;
+    }
     }
     return TRUE;
 }
@@ -144,6 +197,13 @@  static gboolean send_request(VDAgentClipboards *c, guint sel_id, GdkAtom target)
         udscs_write(c->conn, VDAGENTD_CLIPBOARD_REQUEST, sel_id, type, NULL, 0);
         break;
     }
+    case CLIPBOARD_PROTOCOL_SELECTION: {
+        gchar *target_name = gdk_atom_name(target);
+        udscs_write(c->conn, VDAGENTD_SELECTION_REQUEST, sel_id, 0,
+                    (guint8 *)target_name, strlen(target_name) + 1);
+        g_free(target_name);
+        break;
+    }
     }
     return TRUE;
 }
@@ -159,6 +219,21 @@  static void send_data(VDAgentClipboards *c, guint sel_id,
         udscs_write(c->conn, VDAGENTD_CLIPBOARD_DATA, sel_id,
                     get_type_from_atom(type), data, data_len);
         break;
+    case CLIPBOARD_PROTOCOL_SELECTION: {
+        gchar *type_name = gdk_atom_name(type);
+        guint type_len = strlen(type_name) + 1;
+        guchar *buff = g_malloc(type_len + data_len);
+
+        memcpy(buff, type_name, type_len);
+        memcpy(buff + type_len, data, data_len);
+
+        udscs_write(c->conn, VDAGENTD_SELECTION_DATA, sel_id,
+                    format, buff, type_len + data_len);
+
+        g_free(buff);
+        g_free(type_name);
+        break;
+    }
     }
 }
 
@@ -170,6 +245,9 @@  static void send_release(VDAgentClipboards *c, guint sel_id)
     case CLIPBOARD_PROTOCOL_COMPATIBILITY:
         udscs_write(c->conn, VDAGENTD_CLIPBOARD_RELEASE, sel_id, 0, NULL, 0);
         break;
+    case CLIPBOARD_PROTOCOL_SELECTION:
+        udscs_write(c->conn, VDAGENTD_SELECTION_RELEASE, sel_id, 0, NULL, 0);
+        break;
     }
 }
 
@@ -343,6 +421,19 @@  static void clipboard_clear_cb(GtkClipboard *clipboard, gpointer user_data)
     clipboard_new_owner(c, sel_id_from_clip(clipboard), OWNER_NONE);
 }
 
+void clipboard_grab(VDAgentClipboards *c, guint sel_id,
+                    GtkTargetEntry *targets, guint n_targets)
+{
+    if (gtk_clipboard_set_with_data(c->selections[sel_id].clipboard,
+                                    targets, n_targets,
+                                    clipboard_get_cb, clipboard_clear_cb, c))
+        clipboard_new_owner(c, sel_id, OWNER_CLIENT);
+    else {
+        syslog(LOG_ERR, "%s: sel_id=%u: clipboard grab failed", __func__, sel_id);
+        clipboard_new_owner(c, sel_id, OWNER_NONE);
+    }
+}
+
 void vdagent_clipboard_grab(VDAgentClipboards *c, guint sel_id,
                             guint32 *types, guint n_types)
 {
@@ -365,18 +456,43 @@  void vdagent_clipboard_grab(VDAgentClipboards *c, guint sel_id,
         return;
     }
 
-    if (gtk_clipboard_set_with_data(c->selections[sel_id].clipboard,
-                                    targets, n_targets,
-                                    clipboard_get_cb, clipboard_clear_cb, c))
-        clipboard_new_owner(c, sel_id, OWNER_CLIENT);
-    else {
-        syslog(LOG_ERR, "%s: sel_id=%u: clipboard grab failed", __func__, sel_id);
-        clipboard_new_owner(c, sel_id, OWNER_NONE);
-    }
+    clipboard_grab(c, sel_id, targets, n_targets);
 }
 
-void vdagent_clipboard_data(VDAgentClipboards *c, guint sel_id,
-                            guint type, guchar *data, guint size)
+void vdagent_selection_grab(VDAgentClipboards *c, guint sel_id,
+                            const gchar *data, guint size)
+{
+    GtkTargetEntry *targets;
+    guint i, n, n_targets = 0;
+
+    g_return_if_fail(sel_id < SELECTION_COUNT);
+
+    g_return_if_fail(size >= 2);
+    g_return_if_fail(data[0] != 0);
+    g_return_if_fail(data[size-1] == 0);
+
+    for (i = 1; i < size; i++)
+        if (data[i] == 0) {
+            g_return_if_fail(data[i-1] != 0);
+            n_targets++;
+        }
+
+    targets = g_new0(GtkTargetEntry, n_targets);
+
+    for (i = 0, n = 0; i < size; i++)
+        if (data[i]) {
+            if (targets[n].target == NULL)
+                targets[n].target = (gchar *)(data + i);
+        } else
+            n++;
+
+    clipboard_grab(c, sel_id, targets, n_targets);
+    g_free(targets);
+}
+
+void selection_data_set(VDAgentClipboards *c, guint sel_id,
+                        GdkAtom type, gint type_vdagent,
+                        gint format, const guchar *data, guint size)
 {
     g_return_if_fail(sel_id < SELECTION_COUNT);
     Selection *sel = &c->selections[sel_id];
@@ -385,23 +501,49 @@  void vdagent_clipboard_data(VDAgentClipboards *c, guint sel_id,
 
     for (l = sel->requests_from_apps; l != NULL; l = l->next) {
         req = l->data;
-        if (get_type_from_atom(gtk_selection_data_get_target(req->sel_data)) == type)
+        GdkAtom target = gtk_selection_data_get_target(req->sel_data);
+        if (target == type || get_type_from_atom(target) == type_vdagent)
             break;
     }
     if (l == NULL) {
+        gchar *type_name = gdk_atom_name(type);
         syslog(LOG_WARNING, "%s: sel_id=%u: no corresponding request found for "
-                            "type=%u, skipping", __func__, sel_id, type);
+                            "type=%s, type_vdagent=%u, skipping",
+                            __func__, sel_id, type_name, type_vdagent);
+        g_free(type_name);
         return;
     }
     sel->requests_from_apps = g_list_delete_link(sel->requests_from_apps, l);
 
     gtk_selection_data_set(req->sel_data,
                            gtk_selection_data_get_target(req->sel_data),
-                           8, data, size);
+                           format, data, size);
 
     g_main_loop_quit(req->loop);
 }
 
+void vdagent_clipboard_data(VDAgentClipboards *c, guint sel_id,
+                            guint type, guchar *data, guint size)
+{
+    selection_data_set(c, sel_id, GDK_NONE, type, 8, data, size);
+}
+
+void vdagent_selection_data(VDAgentClipboards *c, guint sel_id,
+                            guint format, const guchar *data, guint size)
+{
+    GdkAtom type;
+    guint offset;
+    for (offset = 0; offset < size; offset++)
+        if (data[offset] == 0)
+            break;
+    offset++;
+    g_return_if_fail(offset >= 2 && offset <= size);
+
+    type = gdk_atom_intern((gchar *)data, FALSE);
+    selection_data_set(c, sel_id, type, TYPE_COUNT, format,
+                       data + offset, size - offset);
+}
+
 void vdagent_clipboard_release(VDAgentClipboards *c, guint sel_id)
 {
     g_return_if_fail(sel_id < SELECTION_COUNT);
@@ -412,6 +554,11 @@  void vdagent_clipboard_release(VDAgentClipboards *c, guint sel_id)
     gtk_clipboard_clear(c->selections[sel_id].clipboard);
 }
 
+void vdagent_selection_release(VDAgentClipboards *c, guint sel_id)
+{
+    vdagent_clipboard_release(c, sel_id);
+}
+
 void vdagent_clipboards_release_all(VDAgentClipboards *c)
 {
     guint sel_id, owner;
@@ -426,7 +573,7 @@  void vdagent_clipboards_release_all(VDAgentClipboards *c)
     }
 }
 
-void vdagent_clipboard_request(VDAgentClipboards *c, guint sel_id, guint type)
+static void clipboard_request(VDAgentClipboards *c, guint sel_id, GdkAtom target)
 {
     Selection *sel;
 
@@ -438,21 +585,47 @@  void vdagent_clipboard_request(VDAgentClipboards *c, guint sel_id, guint type)
                             "while not owning clipboard", __func__, sel_id);
         goto err;
     }
-    if (type >= TYPE_COUNT || sel->targets[type] == GDK_NONE) {
-        syslog(LOG_WARNING, "%s: sel_id=%d: unadvertised data type requested",
+    if (target == GDK_NONE) {
+        syslog(LOG_WARNING, "%s: sel_id=%d: invalid data type requested",
                             __func__, sel_id);
         goto err;
     }
 
     gpointer *ref = request_ref_new(c);
     sel->requests_from_client = g_list_prepend(sel->requests_from_client, ref);
-    gtk_clipboard_request_contents(sel->clipboard, sel->targets[type],
+    gtk_clipboard_request_contents(sel->clipboard, target,
                                    clipboard_contents_received_cb, ref);
     return;
 err:
     send_data(c, sel_id, GDK_NONE, 8, NULL, 0);
 }
 
+void vdagent_clipboard_request(VDAgentClipboards *c, guint sel_id, guint type)
+{
+    GdkAtom target;
+    if (sel_id < SELECTION_COUNT && type < TYPE_COUNT)
+        target = c->selections[sel_id].targets[type];
+    else
+        target = GDK_NONE;
+    clipboard_request(c, sel_id, target);
+}
+
+void vdagent_selection_request(VDAgentClipboards *c, guint sel_id,
+                               const gchar *target_str, guint size)
+{
+    GdkAtom target;
+    guint i;
+    for (i = 0; i < size; i++)
+        if (target_str[i] == 0)
+            break;
+    /* make sure target is properly formatted string */
+    if (i > 0 && i == size - 1)
+        target = gdk_atom_intern(target_str, FALSE);
+    else
+        target = GDK_NONE;
+    clipboard_request(c, sel_id, target);
+}
+
 void vdagent_clipboards_set_protocol(VDAgentClipboards *c, guint protocol)
 {
     g_return_if_fail(protocol <= CLIPBOARD_PROTOCOL_SELECTION);
diff --git a/src/vdagent/clipboard.h b/src/vdagent/clipboard.h
index d9cc22c..803b49b 100644
--- a/src/vdagent/clipboard.h
+++ b/src/vdagent/clipboard.h
@@ -42,4 +42,15 @@  void vdagent_clipboard_data(VDAgentClipboards *c, guint sel_id,
 void vdagent_clipboard_grab(VDAgentClipboards *c, guint sel_id,
                             guint32 *types, guint n_types);
 
+void vdagent_selection_grab(VDAgentClipboards *c, guint sel_id,
+                            const gchar *data, guint size);
+
+void vdagent_selection_request(VDAgentClipboards *c, guint sel_id,
+                               const gchar *target_str, guint size);
+
+void vdagent_selection_data(VDAgentClipboards *c, guint sel_id,
+                            guint format, const guchar *data, guint size);
+
+void vdagent_selection_release(VDAgentClipboards *c, guint sel_id);
+
 #endif
diff --git a/src/vdagent/vdagent.c b/src/vdagent/vdagent.c
index 85aa6ae..2aa318f 100644
--- a/src/vdagent/vdagent.c
+++ b/src/vdagent/vdagent.c
@@ -195,6 +195,21 @@  static void daemon_read_complete(struct udscs_connection **connp,
     case VDAGENTD_CLIPBOARD_PROTOCOL:
         vdagent_clipboards_set_protocol(agent->clipboards, header->arg1);
         break;
+    case VDAGENTD_SELECTION_GRAB:
+        vdagent_selection_grab(agent->clipboards, header->arg1,
+                               (gchar *)data, header->size);
+        break;
+    case VDAGENTD_SELECTION_REQUEST:
+        vdagent_selection_request(agent->clipboards, header->arg1,
+                                  (gchar *)data, header->size);
+        break;
+    case VDAGENTD_SELECTION_DATA:
+        vdagent_selection_data(agent->clipboards, header->arg1,
+                               header->arg2, data, header->size);
+        break;
+    case VDAGENTD_SELECTION_RELEASE:
+        vdagent_selection_release(agent->clipboards, header->arg1);
+        break;
     case VDAGENTD_VERSION:
         if (strcmp((char *)data, VERSION) != 0) {
             syslog(LOG_INFO, "vdagentd version mismatch: got %s expected %s",
diff --git a/src/vdagentd/vdagentd.c b/src/vdagentd/vdagentd.c
index 682761a..5571308 100644
--- a/src/vdagentd/vdagentd.c
+++ b/src/vdagentd/vdagentd.c
@@ -80,6 +80,7 @@  static int quit = 0;
 static int retval = 0;
 static int client_connected = 0;
 static int max_clipboard = -1;
+static guint clipboard_protocol = CLIPBOARD_PROTOCOL_COMPATIBILITY;
 
 /* utility functions */
 static void virtio_msg_uint32_to_le(uint8_t *_msg, uint32_t size, uint32_t offset)
@@ -134,6 +135,7 @@  static void send_capabilities(struct vdagent_virtio_port *vport,
     VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_GUEST_LINEEND_LF);
     VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_MAX_CLIPBOARD);
     VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_AUDIO_VOLUME_SYNC);
+    VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_SELECTION_DATA);
     virtio_msg_uint32_to_le((uint8_t *)caps, size, 0);
 
     vdagent_virtio_port_write(vport, VDP_CLIENT_PORT,
@@ -226,6 +228,20 @@  static void do_client_volume_sync(struct vdagent_virtio_port *vport, int port_nr
                 (uint8_t *)avs, message_header->size);
 }
 
+static void agent_set_clipboard_protocol()
+{
+    if (active_session_conn == NULL || !client_connected)
+        return;
+
+    clipboard_protocol = VD_AGENT_HAS_CAPABILITY(capabilities, capabilities_size,
+                                          VD_AGENT_CAP_SELECTION_DATA) ?
+                         CLIPBOARD_PROTOCOL_SELECTION :
+                         CLIPBOARD_PROTOCOL_COMPATIBILITY;
+
+    udscs_write(active_session_conn, VDAGENTD_CLIPBOARD_PROTOCOL,
+                clipboard_protocol, 0, NULL, 0);
+}
+
 static void do_client_capabilities(struct vdagent_virtio_port *vport,
     VDAgentMessage *message_header,
     VDAgentAnnounceCapabilities *caps)
@@ -250,6 +266,7 @@  static void do_client_capabilities(struct vdagent_virtio_port *vport,
             syslog(LOG_DEBUG, "New client connected");
         client_connected = 1;
         send_capabilities(vport, 0);
+        agent_set_clipboard_protocol();
     }
 }
 
@@ -305,6 +322,62 @@  static void do_client_clipboard(struct vdagent_virtio_port *vport,
                 data, size);
 }
 
+static void do_client_selection(struct vdagent_virtio_port *vport,
+                                VDAgentMessage             *msg_header,
+                                const gpointer              msg_data)
+{
+    uint32_t msg_type, arg2, size;
+    uint8_t selection, *data;
+
+    if (!active_session_conn) {
+        syslog(LOG_WARNING,
+               "Could not find an agent connection belonging to the "
+               "active session, ignoring selection message from client");
+        return;
+    }
+
+    selection = ((uint8_t *)msg_data)[0];
+    size = msg_header->size;
+    arg2 = 0;
+
+    switch (msg_header->type) {
+    case VD_AGENT_SELECTION_GRAB: {
+        msg_type = VDAGENTD_SELECTION_GRAB;
+
+        VDAgentSelectionGrab *msg = msg_data;
+        data = msg->targets;
+        size -= sizeof(VDAgentSelectionGrab);
+
+        agent_owns_clipboard[selection] = FALSE;
+        break;
+    }
+    case VD_AGENT_SELECTION_REQUEST: {
+        msg_type = VDAGENTD_SELECTION_REQUEST;
+
+        VDAgentSelectionRequest *msg = msg_data;
+        data = msg->target;
+        size -= sizeof(VDAgentSelectionRequest);
+        break;
+    }
+    case VD_AGENT_SELECTION_DATA: {
+        msg_type = VDAGENTD_SELECTION_DATA;
+
+        VDAgentSelectionData *msg = msg_data;
+        data = msg->data;
+        size -= sizeof(VDAgentSelectionData);
+        arg2 = GINT32_FROM_LE(msg->format);
+        break;
+    }
+    case VD_AGENT_SELECTION_RELEASE:
+        msg_type = VDAGENTD_SELECTION_RELEASE;
+        data = NULL;
+        size = 0;
+        break;
+    }
+
+    udscs_write(active_session_conn, msg_type, selection, arg2, data, size);
+}
+
 /* Send file-xfer status to the client. In the case status is an error,
  * optional data for the client and log message may be specified. */
 static void send_file_xfer_status(struct vdagent_virtio_port *vport,
@@ -410,6 +483,10 @@  static gsize vdagent_message_min_size[] =
     0, /* VD_AGENT_CLIENT_DISCONNECTED */
     sizeof(VDAgentMaxClipboard), /* VD_AGENT_MAX_CLIPBOARD */
     sizeof(VDAgentAudioVolumeSync), /* VD_AGENT_AUDIO_VOLUME_SYNC */
+    sizeof(VDAgentSelectionGrab), /* VD_AGENT_SELECTION_GRAB */
+    sizeof(VDAgentSelectionRequest), /* VD_AGENT_SELECTION_REQUEST */
+    sizeof(VDAgentSelectionData), /* VD_AGENT_SELECTION_DATA */
+    sizeof(VDAgentSelectionRelease), /* VD_AGENT_SELECTION_RELEASE */
 };
 
 static void vdagent_message_clipboard_from_le(VDAgentMessage *message_header,
@@ -494,6 +571,9 @@  static gboolean vdagent_message_check_size(const VDAgentMessage *message_header)
     case VD_AGENT_CLIPBOARD_GRAB:
     case VD_AGENT_AUDIO_VOLUME_SYNC:
     case VD_AGENT_ANNOUNCE_CAPABILITIES:
+    case VD_AGENT_SELECTION_GRAB:
+    case VD_AGENT_SELECTION_REQUEST:
+    case VD_AGENT_SELECTION_DATA:
         if (message_header->size < min_size) {
             syslog(LOG_ERR, "read: invalid message size: %u for message type: %u",
                    message_header->size, message_header->type);
@@ -508,6 +588,7 @@  static gboolean vdagent_message_check_size(const VDAgentMessage *message_header)
     case VD_AGENT_CLIPBOARD_RELEASE:
     case VD_AGENT_MAX_CLIPBOARD:
     case VD_AGENT_CLIENT_DISCONNECTED:
+    case VD_AGENT_SELECTION_RELEASE:
         if (message_header->size != min_size) {
             syslog(LOG_ERR, "read: invalid message size: %u for message type: %u",
                    message_header->size, message_header->type);
@@ -552,6 +633,12 @@  static int virtio_port_read_complete(
         vdagent_message_clipboard_from_le(message_header, data);
         do_client_clipboard(vport, message_header, data);
         break;
+    case VD_AGENT_SELECTION_GRAB:
+    case VD_AGENT_SELECTION_REQUEST:
+    case VD_AGENT_SELECTION_DATA:
+    case VD_AGENT_SELECTION_RELEASE:
+        do_client_selection(vport, message_header, data);
+        break;
     case VD_AGENT_FILE_XFER_START:
     case VD_AGENT_FILE_XFER_STATUS:
     case VD_AGENT_FILE_XFER_DATA:
@@ -692,6 +779,84 @@  error:
     return 0;
 }
 
+/* vdagentd <-> vdagent communication handling */
+static void do_agent_selection(struct udscs_connection     *conn,
+                               struct udscs_message_header *header,
+                               uint8_t                     *data)
+{
+    uint8_t selection;
+    uint32_t msg_type, msg_size, data_size, type_size;
+    int32_t format;
+
+    selection = header->arg1;
+    data_size = header->size;
+    msg_size  = sizeof(uint8_t) + data_size;
+
+    if (conn != active_session_conn) {
+        if (debug)
+            syslog(LOG_DEBUG, "%p selection message from agent which is not in "
+                              "the active session?", conn);
+        goto error;
+    }
+    if (virtio_port == NULL) {
+        syslog(LOG_ERR, "Selection message from agent but no client connection");
+        goto error;
+    }
+    if (header->type != VDAGENTD_SELECTION_RELEASE && data_size == 0) {
+        syslog(LOG_ERR, "Selection message incomplete, discarding");
+        goto error;
+    }
+
+    switch (header->type) {
+    case VDAGENTD_SELECTION_GRAB:
+        msg_type = VD_AGENT_SELECTION_GRAB;
+        agent_owns_clipboard[selection] = TRUE;
+        break;
+    case VDAGENTD_SELECTION_REQUEST:
+        msg_type = VD_AGENT_SELECTION_REQUEST;
+        break;
+    case VDAGENTD_SELECTION_DATA:
+        msg_type = VD_AGENT_SELECTION_DATA;
+        format = GINT32_TO_LE(header->arg2);
+        msg_size += sizeof(int32_t);
+
+        type_size = strnlen((gchar *)data, data_size - 1) + 1;
+        if (max_clipboard != -1 && data_size - type_size > max_clipboard) {
+            syslog(LOG_WARNING, "Selection data is too large (%d > %d), discarding",
+                                data_size - type_size, max_clipboard);
+            /* discard the actual data, keep the prefixed MIME type */
+            data_size = type_size;
+            msg_size = sizeof(uint8_t) + sizeof(int32_t) + type_size;
+        }
+        break;
+    case VDAGENTD_SELECTION_RELEASE:
+        msg_type = VD_AGENT_SELECTION_RELEASE;
+        agent_owns_clipboard[selection] = FALSE;
+        break;
+    }
+
+    vdagent_virtio_port_write_start(virtio_port, VDP_CLIENT_PORT,
+                                    msg_type, 0, msg_size);
+    vdagent_virtio_port_write_append(virtio_port, &selection, sizeof(uint8_t));
+
+    if (msg_type == VD_AGENT_SELECTION_DATA) {
+        vdagent_virtio_port_write_append(virtio_port, (uint8_t *)&format,
+                                         sizeof(int32_t));
+    }
+    if (msg_type != VD_AGENT_SELECTION_RELEASE) {
+        vdagent_virtio_port_write_append(virtio_port, data, data_size);
+    }
+
+    return;
+
+error:
+    if (header->type == VDAGENTD_SELECTION_REQUEST && data_size != 0) {
+        /* Let the agent know no answer is coming */
+        type_size = strnlen((gchar *)data, data_size - 1) + 1;
+        udscs_write(conn, VDAGENTD_SELECTION_DATA, selection, 8, data, type_size);
+    }
+}
+
 /* When we open the vdagent virtio channel, the server automatically goes into
    client mouse mode, so we can only have the channel open when we know the
    active session resolution. This function checks that we have an agent in the
@@ -769,11 +934,15 @@  static int connection_matches_active_session(struct udscs_connection **connp,
 static void release_clipboards(void)
 {
     uint8_t sel;
+    uint32_t msg_type;
+
+    msg_type = clipboard_protocol == CLIPBOARD_PROTOCOL_SELECTION ?
+               VD_AGENT_SELECTION_RELEASE : VD_AGENT_CLIPBOARD_RELEASE;
 
     for (sel = 0; sel < VD_AGENT_CLIPBOARD_SELECTION_SECONDARY; ++sel) {
         if (agent_owns_clipboard[sel] && virtio_port) {
             vdagent_virtio_port_write(virtio_port, VDP_CLIENT_PORT,
-                                      VD_AGENT_CLIPBOARD_RELEASE, 0, &sel, 1);
+                                      msg_type, 0, &sel, 1);
         }
         agent_owns_clipboard[sel] = 0;
     }
@@ -860,6 +1029,7 @@  static void agent_connect(struct udscs_connection *conn)
     udscs_write(conn, VDAGENTD_VERSION, 0, 0,
                 (uint8_t *)VERSION, strlen(VERSION) + 1);
     update_active_session_connection(conn);
+    agent_set_clipboard_protocol();
 }
 
 static void agent_disconnect(struct udscs_connection *conn)
@@ -926,6 +1096,12 @@  static void agent_read_complete(struct udscs_connection **connp,
             return;
         }
         break;
+    case VDAGENTD_SELECTION_GRAB:
+    case VDAGENTD_SELECTION_REQUEST:
+    case VDAGENTD_SELECTION_DATA:
+    case VDAGENTD_SELECTION_RELEASE:
+        do_agent_selection(*connp, header, data);
+        break;
     case VDAGENTD_FILE_XFER_STATUS:{
         /* header->arg1 = file xfer task id, header->arg2 = file xfer status */
         switch (header->arg2) {