diff --git a/include/ui/clipboard.h b/include/ui/clipboard.h index d82cf31481..e590b453c8 100644 --- a/include/ui/clipboard.h +++ b/include/ui/clipboard.h @@ -102,6 +102,8 @@ struct QemuClipboardNotify { * @owner: clipboard owner. * @selection: clipboard selection. * @types: clipboard data array (one entry per type). + * @has_serial: whether @serial is available. + * @serial: the grab serial counter. * * Clipboard content data and metadata. */ @@ -109,6 +111,8 @@ struct QemuClipboardInfo { uint32_t refcount; QemuClipboardPeer *owner; QemuClipboardSelection selection; + bool has_serial; + uint32_t serial; struct { bool available; bool requested; diff --git a/ui/trace-events b/ui/trace-events index b9c0dd0fa1..e832c3e365 100644 --- a/ui/trace-events +++ b/ui/trace-events @@ -135,3 +135,4 @@ vdagent_recv_msg(const char *name, uint32_t size) "msg %s, size %d" vdagent_peer_cap(const char *name) "cap %s" vdagent_cb_grab_selection(const char *name) "selection %s" vdagent_cb_grab_type(const char *name) "type %s" +vdagent_cb_serial_discard(uint32_t current, uint32_t received) "current=%u, received=%u" diff --git a/ui/vdagent.c b/ui/vdagent.c index de827aad27..b4fdae6917 100644 --- a/ui/vdagent.c +++ b/ui/vdagent.c @@ -59,6 +59,7 @@ struct VDAgentChardev { /* clipboard */ QemuClipboardPeer cbpeer; + uint32_t last_serial[QEMU_CLIPBOARD_SELECTION__COUNT]; uint32_t cbpending[QEMU_CLIPBOARD_SELECTION__COUNT]; }; typedef struct VDAgentChardev VDAgentChardev; @@ -203,6 +204,9 @@ static void vdagent_send_caps(VDAgentChardev *vd) if (vd->clipboard) { caps->caps[0] |= (1 << VD_AGENT_CAP_CLIPBOARD_BY_DEMAND); caps->caps[0] |= (1 << VD_AGENT_CAP_CLIPBOARD_SELECTION); +#if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 1) + caps->caps[0] |= (1 << VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL); +#endif } vdagent_send_msg(vd, msg); @@ -333,7 +337,8 @@ static void vdagent_send_clipboard_grab(VDAgentChardev *vd, { g_autofree VDAgentMessage *msg = g_malloc0(sizeof(VDAgentMessage) + - sizeof(uint32_t) * (QEMU_CLIPBOARD_TYPE__COUNT + 1)); + sizeof(uint32_t) * (QEMU_CLIPBOARD_TYPE__COUNT + 1) + + sizeof(uint32_t)); uint8_t *s = msg->data; uint32_t *data = (uint32_t *)msg->data; uint32_t q, type; @@ -346,6 +351,19 @@ static void vdagent_send_clipboard_grab(VDAgentChardev *vd, return; } +#if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 1) + if (vd->caps & (1 << VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL)) { + if (!info->has_serial) { + /* client should win */ + info->serial = vd->last_serial[info->selection]++; + info->has_serial = true; + } + *data = info->serial; + data++; + msg->size += sizeof(uint32_t); + } +#endif + for (q = 0; q < QEMU_CLIPBOARD_TYPE__COUNT; q++) { type = type_qemu_to_vdagent(q); if (type != VD_AGENT_CLIPBOARD_NONE && info->types[q].available) { @@ -494,6 +512,24 @@ static void vdagent_clipboard_recv_grab(VDAgentChardev *vd, uint8_t s, uint32_t trace_vdagent_cb_grab_selection(GET_NAME(sel_name, s)); info = qemu_clipboard_info_new(&vd->cbpeer, s); +#if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 1) + if (vd->caps & (1 << VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL)) { + if (size < sizeof(uint32_t)) { + /* this shouldn't happen! */ + return; + } + + info->has_serial = true; + info->serial = *(uint32_t *)data; + if (info->serial < vd->last_serial[s]) { + /* discard lower-ordering guest grab */ + return; + } + vd->last_serial[s] = info->serial; + data += sizeof(uint32_t); + size -= sizeof(uint32_t); + } +#endif if (size > sizeof(uint32_t) * 10) { /* * spice has 6 types as of 2021. Limiting to 10 entries @@ -671,6 +707,7 @@ static void vdagent_chr_recv_caps(VDAgentChardev *vd, VDAgentMessage *msg) qemu_input_handler_activate(vd->mouse_hs); } if (have_clipboard(vd) && vd->cbpeer.notifier.notify == NULL) { + memset(vd->last_serial, 0, sizeof(vd->last_serial)); vd->cbpeer.name = "vdagent"; vd->cbpeer.notifier.notify = vdagent_clipboard_notify; vd->cbpeer.request = vdagent_clipboard_request;