#include "qemu/osdep.h" #include "qapi/error.h" #include "include/qemu-common.h" #include "chardev/char.h" #include "qemu/buffer.h" #include "qemu/option.h" #include "qemu/units.h" #include "hw/qdev-core.h" #include "migration/blocker.h" #include "ui/clipboard.h" #include "ui/console.h" #include "ui/input.h" #include "trace.h" #include "qapi/qapi-types-char.h" #include "qapi/qapi-types-ui.h" #include "spice/vd_agent.h" #define CHECK_SPICE_PROTOCOL_VERSION(major, minor, micro) \ (CONFIG_SPICE_PROTOCOL_MAJOR > (major) || \ (CONFIG_SPICE_PROTOCOL_MAJOR == (major) && \ CONFIG_SPICE_PROTOCOL_MINOR > (minor)) || \ (CONFIG_SPICE_PROTOCOL_MAJOR == (major) && \ CONFIG_SPICE_PROTOCOL_MINOR == (minor) && \ CONFIG_SPICE_PROTOCOL_MICRO >= (micro))) #define VDAGENT_BUFFER_LIMIT (1 * MiB) #define VDAGENT_MOUSE_DEFAULT true #define VDAGENT_CLIPBOARD_DEFAULT false struct VDAgentChardev { Chardev parent; /* TODO: migration isn't yet supported */ Error *migration_blocker; /* config */ bool mouse; bool clipboard; /* guest vdagent */ uint32_t caps; VDIChunkHeader chunk; uint32_t chunksize; uint8_t *msgbuf; uint32_t msgsize; uint8_t *xbuf; uint32_t xoff, xsize; Buffer outbuf; /* mouse */ DeviceState mouse_dev; uint32_t mouse_x; uint32_t mouse_y; uint32_t mouse_btn; uint32_t mouse_display; QemuInputHandlerState *mouse_hs; /* clipboard */ QemuClipboardPeer cbpeer; uint32_t last_serial[QEMU_CLIPBOARD_SELECTION__COUNT]; uint32_t cbpending[QEMU_CLIPBOARD_SELECTION__COUNT]; }; typedef struct VDAgentChardev VDAgentChardev; #define TYPE_CHARDEV_QEMU_VDAGENT "chardev-qemu-vdagent" DECLARE_INSTANCE_CHECKER(VDAgentChardev, QEMU_VDAGENT_CHARDEV, TYPE_CHARDEV_QEMU_VDAGENT); /* ------------------------------------------------------------------ */ /* names, for debug logging */ static const char *cap_name[] = { [VD_AGENT_CAP_MOUSE_STATE] = "mouse-state", [VD_AGENT_CAP_MONITORS_CONFIG] = "monitors-config", [VD_AGENT_CAP_REPLY] = "reply", [VD_AGENT_CAP_CLIPBOARD] = "clipboard", [VD_AGENT_CAP_DISPLAY_CONFIG] = "display-config", [VD_AGENT_CAP_CLIPBOARD_BY_DEMAND] = "clipboard-by-demand", [VD_AGENT_CAP_CLIPBOARD_SELECTION] = "clipboard-selection", [VD_AGENT_CAP_SPARSE_MONITORS_CONFIG] = "sparse-monitors-config", [VD_AGENT_CAP_GUEST_LINEEND_LF] = "guest-lineend-lf", [VD_AGENT_CAP_GUEST_LINEEND_CRLF] = "guest-lineend-crlf", [VD_AGENT_CAP_MAX_CLIPBOARD] = "max-clipboard", [VD_AGENT_CAP_AUDIO_VOLUME_SYNC] = "audio-volume-sync", [VD_AGENT_CAP_MONITORS_CONFIG_POSITION] = "monitors-config-position", [VD_AGENT_CAP_FILE_XFER_DISABLED] = "file-xfer-disabled", [VD_AGENT_CAP_FILE_XFER_DETAILED_ERRORS] = "file-xfer-detailed-errors", #if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 0) [VD_AGENT_CAP_GRAPHICS_DEVICE_INFO] = "graphics-device-info", #endif #if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 1) [VD_AGENT_CAP_CLIPBOARD_NO_RELEASE_ON_REGRAB] = "clipboard-no-release-on-regrab", [VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL] = "clipboard-grab-serial", #endif }; static const char *msg_name[] = { [VD_AGENT_MOUSE_STATE] = "mouse-state", [VD_AGENT_MONITORS_CONFIG] = "monitors-config", [VD_AGENT_REPLY] = "reply", [VD_AGENT_CLIPBOARD] = "clipboard", [VD_AGENT_DISPLAY_CONFIG] = "display-config", [VD_AGENT_ANNOUNCE_CAPABILITIES] = "announce-capabilities", [VD_AGENT_CLIPBOARD_GRAB] = "clipboard-grab", [VD_AGENT_CLIPBOARD_REQUEST] = "clipboard-request", [VD_AGENT_CLIPBOARD_RELEASE] = "clipboard-release", [VD_AGENT_FILE_XFER_START] = "file-xfer-start", [VD_AGENT_FILE_XFER_STATUS] = "file-xfer-status", [VD_AGENT_FILE_XFER_DATA] = "file-xfer-data", [VD_AGENT_CLIENT_DISCONNECTED] = "client-disconnected", [VD_AGENT_MAX_CLIPBOARD] = "max-clipboard", [VD_AGENT_AUDIO_VOLUME_SYNC] = "audio-volume-sync", #if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 0) [VD_AGENT_GRAPHICS_DEVICE_INFO] = "graphics-device-info", #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 CHECK_SPICE_PROTOCOL_VERSION(0, 14, 3) [VD_AGENT_CLIPBOARD_FILE_LIST] = "files", #endif }; #define GET_NAME(_m, _v) \ (((_v) < ARRAY_SIZE(_m) && (_m[_v])) ? (_m[_v]) : "???") /* ------------------------------------------------------------------ */ /* send messages */ static void vdagent_send_buf(VDAgentChardev *vd) { uint32_t len; while (!buffer_empty(&vd->outbuf)) { len = qemu_chr_be_can_write(CHARDEV(vd)); if (len == 0) { return; } if (len > vd->outbuf.offset) { len = vd->outbuf.offset; } qemu_chr_be_write(CHARDEV(vd), vd->outbuf.buffer, len); buffer_advance(&vd->outbuf, len); } } static void vdagent_send_msg(VDAgentChardev *vd, VDAgentMessage *msg) { uint8_t *msgbuf = (void *)msg; uint32_t msgsize = sizeof(VDAgentMessage) + msg->size; uint32_t msgoff = 0; VDIChunkHeader chunk; trace_vdagent_send(GET_NAME(msg_name, msg->type)); msg->protocol = VD_AGENT_PROTOCOL; if (vd->outbuf.offset + msgsize > VDAGENT_BUFFER_LIMIT) { error_report("buffer full, dropping message"); return; } while (msgoff < msgsize) { chunk.port = VDP_CLIENT_PORT; chunk.size = msgsize - msgoff; if (chunk.size > 1024) { chunk.size = 1024; } buffer_reserve(&vd->outbuf, sizeof(chunk) + chunk.size); buffer_append(&vd->outbuf, &chunk, sizeof(chunk)); buffer_append(&vd->outbuf, msgbuf + msgoff, chunk.size); msgoff += chunk.size; } vdagent_send_buf(vd); } static void vdagent_send_caps(VDAgentChardev *vd) { g_autofree VDAgentMessage *msg = g_malloc0(sizeof(VDAgentMessage) + sizeof(VDAgentAnnounceCapabilities) + sizeof(uint32_t)); VDAgentAnnounceCapabilities *caps = (void *)msg->data; msg->type = VD_AGENT_ANNOUNCE_CAPABILITIES; msg->size = sizeof(VDAgentAnnounceCapabilities) + sizeof(uint32_t); 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); #if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 1) caps->caps[0] |= (1 << VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL); #endif } vdagent_send_msg(vd, msg); } /* ------------------------------------------------------------------ */ /* mouse events */ static bool have_mouse(VDAgentChardev *vd) { return vd->mouse && (vd->caps & (1 << VD_AGENT_CAP_MOUSE_STATE)); } static void vdagent_send_mouse(VDAgentChardev *vd) { g_autofree VDAgentMessage *msg = g_malloc0(sizeof(VDAgentMessage) + sizeof(VDAgentMouseState)); VDAgentMouseState *mouse = (void *)msg->data; msg->type = VD_AGENT_MOUSE_STATE; msg->size = sizeof(VDAgentMouseState); mouse->x = vd->mouse_x; mouse->y = vd->mouse_y; mouse->buttons = vd->mouse_btn; mouse->display_id = vd->mouse_display; vdagent_send_msg(vd, msg); } static void vdagent_pointer_event(DeviceState *dev, QemuConsole *src, InputEvent *evt) { static const int bmap[INPUT_BUTTON__MAX] = { [INPUT_BUTTON_LEFT] = VD_AGENT_LBUTTON_MASK, [INPUT_BUTTON_RIGHT] = VD_AGENT_RBUTTON_MASK, [INPUT_BUTTON_MIDDLE] = VD_AGENT_MBUTTON_MASK, [INPUT_BUTTON_WHEEL_UP] = VD_AGENT_UBUTTON_MASK, [INPUT_BUTTON_WHEEL_DOWN] = VD_AGENT_DBUTTON_MASK, #ifdef VD_AGENT_EBUTTON_MASK [INPUT_BUTTON_SIDE] = VD_AGENT_SBUTTON_MASK, [INPUT_BUTTON_EXTRA] = VD_AGENT_EBUTTON_MASK, #endif }; VDAgentChardev *vd = container_of(dev, struct VDAgentChardev, mouse_dev); InputMoveEvent *move; InputBtnEvent *btn; uint32_t xres, yres; switch (evt->type) { case INPUT_EVENT_KIND_ABS: move = evt->u.abs.data; xres = qemu_console_get_width(src, 1024); yres = qemu_console_get_height(src, 768); if (move->axis == INPUT_AXIS_X) { vd->mouse_x = qemu_input_scale_axis(move->value, INPUT_EVENT_ABS_MIN, INPUT_EVENT_ABS_MAX, 0, xres); } else if (move->axis == INPUT_AXIS_Y) { vd->mouse_y = qemu_input_scale_axis(move->value, INPUT_EVENT_ABS_MIN, INPUT_EVENT_ABS_MAX, 0, yres); } vd->mouse_display = qemu_console_get_index(src); break; case INPUT_EVENT_KIND_BTN: btn = evt->u.btn.data; if (btn->down) { vd->mouse_btn |= bmap[btn->button]; } else { vd->mouse_btn &= ~bmap[btn->button]; } break; default: /* keep gcc happy */ break; } } static void vdagent_pointer_sync(DeviceState *dev) { VDAgentChardev *vd = container_of(dev, struct VDAgentChardev, mouse_dev); if (vd->caps & (1 << VD_AGENT_CAP_MOUSE_STATE)) { vdagent_send_mouse(vd); } } static QemuInputHandler vdagent_mouse_handler = { .name = "vdagent mouse", .mask = INPUT_EVENT_MASK_BTN | INPUT_EVENT_MASK_ABS, .event = vdagent_pointer_event, .sync = vdagent_pointer_sync, }; /* ------------------------------------------------------------------ */ /* 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) + sizeof(uint32_t)); 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; } #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) { *data = type; data++; msg->size += sizeof(uint32_t); } } msg->type = VD_AGENT_CLIPBOARD_GRAB; vdagent_send_msg(vd, msg); } static void vdagent_send_clipboard_release(VDAgentChardev *vd, QemuClipboardInfo *info) { g_autofree VDAgentMessage *msg = g_malloc0(sizeof(VDAgentMessage) + sizeof(uint32_t)); if (have_selection(vd)) { uint8_t *s = msg->data; *s = info->selection; msg->size += sizeof(uint32_t); } else if (info->selection != QEMU_CLIPBOARD_SELECTION_CLIPBOARD) { return; } msg->type = VD_AGENT_CLIPBOARD_RELEASE; 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_send_empty_clipboard_data(VDAgentChardev *vd, QemuClipboardSelection selection, QemuClipboardType type) { g_autoptr(QemuClipboardInfo) info = qemu_clipboard_info_new(&vd->cbpeer, selection); trace_vdagent_send_empty_clipboard(); vdagent_send_clipboard_data(vd, info, type); } static void vdagent_clipboard_update_info(VDAgentChardev *vd, QemuClipboardInfo *info) { QemuClipboardSelection s = info->selection; QemuClipboardType type; bool self_update = info->owner == &vd->cbpeer; if (info != qemu_clipboard_info(s)) { vd->cbpending[s] = 0; if (!self_update) { if (info->owner) { vdagent_send_clipboard_grab(vd, info); } else { vdagent_send_clipboard_release(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_reset_serial(VDAgentChardev *vd) { Chardev *chr = CHARDEV(vd); /* reopen the agent connection to reset the serial state */ qemu_chr_be_event(chr, CHR_EVENT_CLOSED); qemu_chr_be_event(chr, CHR_EVENT_OPENED); } static void vdagent_clipboard_notify(Notifier *notifier, void *data) { VDAgentChardev *vd = container_of(notifier, VDAgentChardev, cbpeer.notifier); QemuClipboardNotify *notify = data; switch (notify->type) { case QEMU_CLIPBOARD_UPDATE_INFO: vdagent_clipboard_update_info(vd, notify->info); return; case QEMU_CLIPBOARD_RESET_SERIAL: vdagent_clipboard_reset_serial(vd); return; } } 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_clipboard_recv_grab(VDAgentChardev *vd, uint8_t s, uint32_t size, void *data) { g_autoptr(QemuClipboardInfo) info = NULL; 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 * 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); } static void vdagent_clipboard_recv_request(VDAgentChardev *vd, uint8_t s, uint32_t size, void *data) { QemuClipboardType type; QemuClipboardInfo *info; if (size < sizeof(uint32_t)) { return; } switch (*(uint32_t *)data) { case VD_AGENT_CLIPBOARD_UTF8_TEXT: type = QEMU_CLIPBOARD_TYPE_TEXT; break; default: return; } info = qemu_clipboard_info(s); if (info && info->types[type].available && info->owner != &vd->cbpeer) { if (info->types[type].data) { vdagent_send_clipboard_data(vd, info, type); } else { vd->cbpending[s] |= (1 << type); qemu_clipboard_request(info, type); } } else { vdagent_send_empty_clipboard_data(vd, s, type); } } static void vdagent_clipboard_recv_data(VDAgentChardev *vd, uint8_t s, uint32_t size, void *data) { QemuClipboardType type; 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; if (qemu_clipboard_peer_owns(&vd->cbpeer, s)) { qemu_clipboard_set_data(&vd->cbpeer, qemu_clipboard_info(s), type, size, data, true); } } static void vdagent_clipboard_recv_release(VDAgentChardev *vd, uint8_t s) { qemu_clipboard_peer_release(&vd->cbpeer, s); } 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; 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: return vdagent_clipboard_recv_grab(vd, s, size, data); case VD_AGENT_CLIPBOARD_REQUEST: return vdagent_clipboard_recv_request(vd, s, size, data); case VD_AGENT_CLIPBOARD: /* data */ return vdagent_clipboard_recv_data(vd, s, size, data); case VD_AGENT_CLIPBOARD_RELEASE: return vdagent_clipboard_recv_release(vd, s); default: g_assert_not_reached(); } } /* ------------------------------------------------------------------ */ /* chardev backend */ static void vdagent_chr_open(Chardev *chr, ChardevBackend *backend, bool *be_opened, Error **errp) { VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(chr); ChardevQemuVDAgent *cfg = backend->u.qemu_vdagent.data; #if HOST_BIG_ENDIAN /* * TODO: vdagent protocol is defined to be LE, * so we have to byteswap everything on BE hosts. */ error_setg(errp, "vdagent is not supported on bigendian hosts"); return; #endif if (migrate_add_blocker(vd->migration_blocker, errp) != 0) { return; } vd->mouse = VDAGENT_MOUSE_DEFAULT; if (cfg->has_mouse) { 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); } *be_opened = true; } static void vdagent_chr_recv_caps(VDAgentChardev *vd, VDAgentMessage *msg) { VDAgentAnnounceCapabilities *caps = (void *)msg->data; int i; if (msg->size < (sizeof(VDAgentAnnounceCapabilities) + sizeof(uint32_t))) { return; } for (i = 0; i < ARRAY_SIZE(cap_name); i++) { if (caps->caps[0] & (1 << i)) { trace_vdagent_peer_cap(GET_NAME(cap_name, i)); } } vd->caps = caps->caps[0]; if (caps->request) { vdagent_send_caps(vd); } if (have_mouse(vd) && vd->mouse_hs) { 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; qemu_clipboard_peer_register(&vd->cbpeer); } } static void vdagent_chr_recv_msg(VDAgentChardev *vd, VDAgentMessage *msg) { trace_vdagent_recv_msg(GET_NAME(msg_name, msg->type), msg->size); switch (msg->type) { 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; } } static void vdagent_reset_xbuf(VDAgentChardev *vd) { g_clear_pointer(&vd->xbuf, g_free); vd->xoff = 0; vd->xsize = 0; } static void vdagent_chr_recv_chunk(VDAgentChardev *vd) { VDAgentMessage *msg = (void *)vd->msgbuf; if (!vd->xsize) { if (vd->msgsize < sizeof(*msg)) { error_report("%s: message too small: %d < %zd", __func__, vd->msgsize, sizeof(*msg)); return; } if (vd->msgsize == msg->size + sizeof(*msg)) { vdagent_chr_recv_msg(vd, msg); return; } } if (!vd->xsize) { vd->xsize = msg->size + sizeof(*msg); vd->xbuf = g_malloc0(vd->xsize); } if (vd->xoff + vd->msgsize > vd->xsize) { error_report("%s: Oops: %d+%d > %d", __func__, vd->xoff, vd->msgsize, vd->xsize); vdagent_reset_xbuf(vd); return; } memcpy(vd->xbuf + vd->xoff, vd->msgbuf, vd->msgsize); vd->xoff += vd->msgsize; if (vd->xoff < vd->xsize) { return; } msg = (void *)vd->xbuf; vdagent_chr_recv_msg(vd, msg); vdagent_reset_xbuf(vd); } static void vdagent_reset_bufs(VDAgentChardev *vd) { memset(&vd->chunk, 0, sizeof(vd->chunk)); vd->chunksize = 0; g_free(vd->msgbuf); vd->msgbuf = NULL; vd->msgsize = 0; } static int vdagent_chr_write(Chardev *chr, const uint8_t *buf, int len) { VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(chr); uint32_t copy, ret = len; while (len) { if (vd->chunksize < sizeof(vd->chunk)) { copy = sizeof(vd->chunk) - vd->chunksize; if (copy > len) { copy = len; } memcpy((void *)(&vd->chunk) + vd->chunksize, buf, copy); vd->chunksize += copy; buf += copy; len -= copy; if (vd->chunksize < sizeof(vd->chunk)) { break; } assert(vd->msgbuf == NULL); vd->msgbuf = g_malloc0(vd->chunk.size); } copy = vd->chunk.size - vd->msgsize; if (copy > len) { copy = len; } memcpy(vd->msgbuf + vd->msgsize, buf, copy); vd->msgsize += copy; buf += copy; len -= copy; if (vd->msgsize == vd->chunk.size) { trace_vdagent_recv_chunk(vd->chunk.size); vdagent_chr_recv_chunk(vd); vdagent_reset_bufs(vd); } } return ret; } static void vdagent_chr_accept_input(Chardev *chr) { VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(chr); vdagent_send_buf(vd); } static void vdagent_disconnect(VDAgentChardev *vd) { buffer_reset(&vd->outbuf); vdagent_reset_bufs(vd); vd->caps = 0; if (vd->mouse_hs) { qemu_input_handler_deactivate(vd->mouse_hs); } if (vd->cbpeer.notifier.notify) { qemu_clipboard_peer_unregister(&vd->cbpeer); memset(&vd->cbpeer, 0, sizeof(vd->cbpeer)); } } static void vdagent_chr_set_fe_open(struct Chardev *chr, int fe_open) { if (!fe_open) { trace_vdagent_close(); return; } trace_vdagent_open(); } static void vdagent_chr_parse(QemuOpts *opts, ChardevBackend *backend, Error **errp) { ChardevQemuVDAgent *cfg; backend->type = CHARDEV_BACKEND_KIND_QEMU_VDAGENT; cfg = backend->u.qemu_vdagent.data = g_new0(ChardevQemuVDAgent, 1); 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); } /* ------------------------------------------------------------------ */ static void vdagent_chr_class_init(ObjectClass *oc, void *data) { ChardevClass *cc = CHARDEV_CLASS(oc); cc->parse = vdagent_chr_parse; cc->open = vdagent_chr_open; cc->chr_write = vdagent_chr_write; cc->chr_set_fe_open = vdagent_chr_set_fe_open; cc->chr_accept_input = vdagent_chr_accept_input; } static void vdagent_chr_init(Object *obj) { VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(obj); buffer_init(&vd->outbuf, "vdagent-outbuf"); error_setg(&vd->migration_blocker, "The vdagent chardev doesn't yet support migration"); } static void vdagent_chr_fini(Object *obj) { VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(obj); migrate_del_blocker(vd->migration_blocker); vdagent_disconnect(vd); buffer_free(&vd->outbuf); error_free(vd->migration_blocker); } static const TypeInfo vdagent_chr_type_info = { .name = TYPE_CHARDEV_QEMU_VDAGENT, .parent = TYPE_CHARDEV, .instance_size = sizeof(VDAgentChardev), .instance_init = vdagent_chr_init, .instance_finalize = vdagent_chr_fini, .class_init = vdagent_chr_class_init, }; static void register_types(void) { type_register_static(&vdagent_chr_type_info); } type_init(register_types);