diff options
author | Gerd Hoffmann <kraxel@redhat.com> | 2021-05-19 07:39:37 +0200 |
---|---|---|
committer | Gerd Hoffmann <kraxel@redhat.com> | 2021-05-21 09:42:44 +0200 |
commit | f0349f4d8947ad32d0fa4678cbf5dbb356fcbda1 (patch) | |
tree | dacf0ee99c7340083b12a3d36e2dc4b758d5c92c /ui/vdagent.c | |
parent | 56081919801f1a118983b3a500fb2270844ac8c2 (diff) |
ui/vdagent: add clipboard support
This patch adds support for clipboard messages to the qemu vdagent
implementation, which allows the guest exchange clipboard data with
qemu. Clipboard support can be enabled/disabled using the new
'clipboard' parameter for the vdagent chardev. Default is off.
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-id: 20210519053940.1888907-1-kraxel@redhat.com
Message-Id: <20210519053940.1888907-7-kraxel@redhat.com>
Diffstat (limited to 'ui/vdagent.c')
-rw-r--r-- | ui/vdagent.c | 293 |
1 files changed, 293 insertions, 0 deletions
diff --git a/ui/vdagent.c b/ui/vdagent.c index cf81ab6beb..a253a8fe63 100644 --- a/ui/vdagent.c +++ b/ui/vdagent.c @@ -6,6 +6,7 @@ #include "qemu/option.h" #include "qemu/units.h" #include "hw/qdev-core.h" +#include "ui/clipboard.h" #include "ui/console.h" #include "ui/input.h" #include "trace.h" @@ -17,12 +18,14 @@ #define VDAGENT_BUFFER_LIMIT (1 * MiB) #define VDAGENT_MOUSE_DEFAULT true +#define VDAGENT_CLIPBOARD_DEFAULT false struct VDAgentChardev { Chardev parent; /* config */ bool mouse; + bool clipboard; /* guest vdagent */ uint32_t caps; @@ -41,6 +44,11 @@ struct VDAgentChardev { uint32_t mouse_btn; uint32_t mouse_display; QemuInputHandlerState *mouse_hs; + + /* clipboard */ + QemuClipboardPeer cbpeer; + QemuClipboardInfo *cbinfo[QEMU_CLIPBOARD_SELECTION__COUNT]; + uint32_t cbpending[QEMU_CLIPBOARD_SELECTION__COUNT]; }; typedef struct VDAgentChardev VDAgentChardev; @@ -96,6 +104,24 @@ static const char *msg_name[] = { #endif }; +static const char *sel_name[] = { + [VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD] = "clipboard", + [VD_AGENT_CLIPBOARD_SELECTION_PRIMARY] = "primary", + [VD_AGENT_CLIPBOARD_SELECTION_SECONDARY] = "secondary", +}; + +static const char *type_name[] = { + [VD_AGENT_CLIPBOARD_NONE] = "none", + [VD_AGENT_CLIPBOARD_UTF8_TEXT] = "text", + [VD_AGENT_CLIPBOARD_IMAGE_PNG] = "png", + [VD_AGENT_CLIPBOARD_IMAGE_BMP] = "bmp", + [VD_AGENT_CLIPBOARD_IMAGE_TIFF] = "tiff", + [VD_AGENT_CLIPBOARD_IMAGE_JPG] = "jpg", +#if 0 + [VD_AGENT_CLIPBOARD_FILE_LIST] = "files", +#endif +}; + #define GET_NAME(_m, _v) \ (((_v) < ARRAY_SIZE(_m) && (_m[_v])) ? (_m[_v]) : "???") @@ -161,6 +187,10 @@ static void vdagent_send_caps(VDAgentChardev *vd) if (vd->mouse) { caps->caps[0] |= (1 << VD_AGENT_CAP_MOUSE_STATE); } + if (vd->clipboard) { + caps->caps[0] |= (1 << VD_AGENT_CAP_CLIPBOARD_BY_DEMAND); + caps->caps[0] |= (1 << VD_AGENT_CAP_CLIPBOARD_SELECTION); + } vdagent_send_msg(vd, msg); } @@ -262,6 +292,244 @@ static QemuInputHandler vdagent_mouse_handler = { }; /* ------------------------------------------------------------------ */ +/* clipboard */ + +static bool have_clipboard(VDAgentChardev *vd) +{ + return vd->clipboard && + (vd->caps & (1 << VD_AGENT_CAP_CLIPBOARD_BY_DEMAND)); +} + +static bool have_selection(VDAgentChardev *vd) +{ + return vd->caps & (1 << VD_AGENT_CAP_CLIPBOARD_SELECTION); +} + +static uint32_t type_qemu_to_vdagent(enum QemuClipboardType type) +{ + switch (type) { + case QEMU_CLIPBOARD_TYPE_TEXT: + return VD_AGENT_CLIPBOARD_UTF8_TEXT; + default: + return VD_AGENT_CLIPBOARD_NONE; + } +} + +static void vdagent_send_clipboard_grab(VDAgentChardev *vd, + QemuClipboardInfo *info) +{ + g_autofree VDAgentMessage *msg = + g_malloc0(sizeof(VDAgentMessage) + + sizeof(uint32_t) * (QEMU_CLIPBOARD_TYPE__COUNT + 1)); + uint8_t *s = msg->data; + uint32_t *data = (uint32_t *)msg->data; + uint32_t q, type; + + if (have_selection(vd)) { + *s = info->selection; + data++; + msg->size += sizeof(uint32_t); + } else if (info->selection != QEMU_CLIPBOARD_SELECTION_CLIPBOARD) { + return; + } + + 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) { + *data = type; + data++; + msg->size += sizeof(uint32_t); + } + } + + msg->type = VD_AGENT_CLIPBOARD_GRAB; + vdagent_send_msg(vd, msg); +} + +static void vdagent_send_clipboard_data(VDAgentChardev *vd, + QemuClipboardInfo *info, + QemuClipboardType type) +{ + g_autofree VDAgentMessage *msg = g_malloc0(sizeof(VDAgentMessage) + + sizeof(uint32_t) * 2 + + info->types[type].size); + + uint8_t *s = msg->data; + uint32_t *data = (uint32_t *)msg->data; + + if (have_selection(vd)) { + *s = info->selection; + data++; + msg->size += sizeof(uint32_t); + } else if (info->selection != QEMU_CLIPBOARD_SELECTION_CLIPBOARD) { + return; + } + + *data = type_qemu_to_vdagent(type); + data++; + msg->size += sizeof(uint32_t); + + memcpy(data, info->types[type].data, info->types[type].size); + msg->size += info->types[type].size; + + msg->type = VD_AGENT_CLIPBOARD; + vdagent_send_msg(vd, msg); +} + +static void vdagent_clipboard_notify(Notifier *notifier, void *data) +{ + VDAgentChardev *vd = container_of(notifier, VDAgentChardev, cbpeer.update); + QemuClipboardInfo *info = data; + QemuClipboardSelection s = info->selection; + QemuClipboardType type; + bool self_update = info->owner == &vd->cbpeer; + + if (info != vd->cbinfo[s]) { + qemu_clipboard_info_unref(vd->cbinfo[s]); + vd->cbinfo[s] = qemu_clipboard_info_ref(info); + vd->cbpending[s] = 0; + if (!self_update) { + vdagent_send_clipboard_grab(vd, info); + } + return; + } + + if (self_update) { + return; + } + + for (type = 0; type < QEMU_CLIPBOARD_TYPE__COUNT; type++) { + if (vd->cbpending[s] & (1 << type)) { + vd->cbpending[s] &= ~(1 << type); + vdagent_send_clipboard_data(vd, info, type); + } + } +} + +static void vdagent_clipboard_request(QemuClipboardInfo *info, + QemuClipboardType qtype) +{ + VDAgentChardev *vd = container_of(info->owner, VDAgentChardev, cbpeer); + g_autofree VDAgentMessage *msg = g_malloc0(sizeof(VDAgentMessage) + + sizeof(uint32_t) * 2); + uint32_t type = type_qemu_to_vdagent(qtype); + uint8_t *s = msg->data; + uint32_t *data = (uint32_t *)msg->data; + + if (type == VD_AGENT_CLIPBOARD_NONE) { + return; + } + + if (have_selection(vd)) { + *s = info->selection; + data++; + msg->size += sizeof(uint32_t); + } + + *data = type; + msg->size += sizeof(uint32_t); + + msg->type = VD_AGENT_CLIPBOARD_REQUEST; + vdagent_send_msg(vd, msg); +} + +static void vdagent_chr_recv_clipboard(VDAgentChardev *vd, VDAgentMessage *msg) +{ + uint8_t s = VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD; + uint32_t size = msg->size; + void *data = msg->data; + QemuClipboardInfo *info; + QemuClipboardType type; + + if (have_selection(vd)) { + if (size < 4) { + return; + } + s = *(uint8_t *)data; + if (s >= QEMU_CLIPBOARD_SELECTION__COUNT) { + return; + } + data += 4; + size -= 4; + } + + switch (msg->type) { + case VD_AGENT_CLIPBOARD_GRAB: + trace_vdagent_cb_grab_selection(GET_NAME(sel_name, s)); + info = qemu_clipboard_info_new(&vd->cbpeer, s); + if (size > sizeof(uint32_t) * 10) { + /* + * spice has 6 types as of 2021. Limiting to 10 entries + * so we we have some wiggle room. + */ + return; + } + while (size >= sizeof(uint32_t)) { + trace_vdagent_cb_grab_type(GET_NAME(type_name, *(uint32_t *)data)); + switch (*(uint32_t *)data) { + case VD_AGENT_CLIPBOARD_UTF8_TEXT: + info->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true; + break; + default: + break; + } + data += sizeof(uint32_t); + size -= sizeof(uint32_t); + } + qemu_clipboard_update(info); + qemu_clipboard_info_unref(info); + break; + case VD_AGENT_CLIPBOARD_REQUEST: + if (size < sizeof(uint32_t)) { + return; + } + switch (*(uint32_t *)data) { + case VD_AGENT_CLIPBOARD_UTF8_TEXT: + type = QEMU_CLIPBOARD_TYPE_TEXT; + break; + default: + return; + } + if (vd->cbinfo[s] && + vd->cbinfo[s]->types[type].available && + vd->cbinfo[s]->owner != &vd->cbpeer) { + if (vd->cbinfo[s]->types[type].data) { + vdagent_send_clipboard_data(vd, vd->cbinfo[s], type); + } else { + vd->cbpending[s] |= (1 << type); + qemu_clipboard_request(vd->cbinfo[s], type); + } + } + break; + case VD_AGENT_CLIPBOARD: /* data */ + if (size < sizeof(uint32_t)) { + return; + } + switch (*(uint32_t *)data) { + case VD_AGENT_CLIPBOARD_UTF8_TEXT: + type = QEMU_CLIPBOARD_TYPE_TEXT; + break; + default: + return; + } + data += 4; + size -= 4; + qemu_clipboard_set_data(&vd->cbpeer, vd->cbinfo[s], type, + size, data, true); + break; + case VD_AGENT_CLIPBOARD_RELEASE: /* data */ + if (vd->cbinfo[s] && + vd->cbinfo[s]->owner == &vd->cbpeer) { + /* set empty clipboard info */ + info = qemu_clipboard_info_new(NULL, s); + qemu_clipboard_update(info); + qemu_clipboard_info_unref(info); + } + break; + } +} + +/* ------------------------------------------------------------------ */ /* chardev backend */ static void vdagent_chr_open(Chardev *chr, @@ -286,6 +554,11 @@ static void vdagent_chr_open(Chardev *chr, vd->mouse = cfg->mouse; } + vd->clipboard = VDAGENT_CLIPBOARD_DEFAULT; + if (cfg->has_clipboard) { + vd->clipboard = cfg->clipboard; + } + if (vd->mouse) { vd->mouse_hs = qemu_input_handler_register(&vd->mouse_dev, &vdagent_mouse_handler); @@ -317,6 +590,12 @@ static void vdagent_chr_recv_caps(VDAgentChardev *vd, VDAgentMessage *msg) if (have_mouse(vd) && vd->mouse_hs) { qemu_input_handler_activate(vd->mouse_hs); } + if (have_clipboard(vd) && vd->cbpeer.update.notify == NULL) { + vd->cbpeer.name = "vdagent"; + vd->cbpeer.update.notify = vdagent_clipboard_notify; + vd->cbpeer.request = vdagent_clipboard_request; + qemu_clipboard_peer_register(&vd->cbpeer); + } } static void vdagent_chr_recv_msg(VDAgentChardev *vd, VDAgentMessage *msg) @@ -327,6 +606,14 @@ static void vdagent_chr_recv_msg(VDAgentChardev *vd, VDAgentMessage *msg) case VD_AGENT_ANNOUNCE_CAPABILITIES: vdagent_chr_recv_caps(vd, msg); break; + case VD_AGENT_CLIPBOARD: + case VD_AGENT_CLIPBOARD_GRAB: + case VD_AGENT_CLIPBOARD_REQUEST: + case VD_AGENT_CLIPBOARD_RELEASE: + if (have_clipboard(vd)) { + vdagent_chr_recv_clipboard(vd, msg); + } + break; default: break; } @@ -448,6 +735,10 @@ static void vdagent_chr_set_fe_open(struct Chardev *chr, int fe_open) if (vd->mouse_hs) { qemu_input_handler_deactivate(vd->mouse_hs); } + if (vd->cbpeer.update.notify) { + qemu_clipboard_peer_unregister(&vd->cbpeer); + memset(&vd->cbpeer, 0, sizeof(vd->cbpeer)); + } return; } @@ -464,6 +755,8 @@ static void vdagent_chr_parse(QemuOpts *opts, ChardevBackend *backend, qemu_chr_parse_common(opts, qapi_ChardevQemuVDAgent_base(cfg)); cfg->has_mouse = true; cfg->mouse = qemu_opt_get_bool(opts, "mouse", VDAGENT_MOUSE_DEFAULT); + cfg->has_clipboard = true; + cfg->clipboard = qemu_opt_get_bool(opts, "clipboard", VDAGENT_CLIPBOARD_DEFAULT); } /* ------------------------------------------------------------------ */ |