| #include "qemu/osdep.h" | 
 | #include "qapi/error.h" | 
 | #include "chardev/char.h" | 
 | #include "qemu/buffer.h" | 
 | #include "qemu/error-report.h" | 
 | #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 "migration/vmstate.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; | 
 |  | 
 |     /* config */ | 
 |     bool mouse; | 
 |     bool clipboard; | 
 |  | 
 |     /* guest vdagent */ | 
 |     bool connected; | 
 |     uint32_t caps; | 
 |     VDIChunkHeader chunk; | 
 |     uint32_t chunksize; | 
 |     uint8_t *msgbuf; | 
 |     uint32_t msgsize; | 
 |     uint8_t *xbuf; | 
 |     uint32_t xoff, xsize; | 
 |     GByteArray *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", | 
 |     [VD_AGENT_CAP_GRAPHICS_DEVICE_INFO]           = "graphics-device-info", | 
 | #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", | 
 |     [VD_AGENT_GRAPHICS_DEVICE_INFO]  = "graphics-device-info", | 
 | }; | 
 |  | 
 | 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 (vd->outbuf->len) { | 
 |         len = qemu_chr_be_can_write(CHARDEV(vd)); | 
 |         if (len == 0) { | 
 |             return; | 
 |         } | 
 |         if (len > vd->outbuf->len) { | 
 |             len = vd->outbuf->len; | 
 |         } | 
 |         qemu_chr_be_write(CHARDEV(vd), vd->outbuf->data, len); | 
 |         g_byte_array_remove_range(vd->outbuf, 0, 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->len + 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; | 
 |         } | 
 |         g_byte_array_append(vd->outbuf, (void *)&chunk, sizeof(chunk)); | 
 |         g_byte_array_append(vd->outbuf, msgbuf + msgoff, chunk.size); | 
 |         msgoff += chunk.size; | 
 |     } | 
 |     vdagent_send_buf(vd); | 
 | } | 
 |  | 
 | static void vdagent_send_caps(VDAgentChardev *vd, bool request) | 
 | { | 
 |     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 | 
 |     } | 
 |  | 
 |     caps->request = request; | 
 |     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 const 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); | 
 |     /* OPENED again after the guest disconnected, see set_fe_open */ | 
 | } | 
 |  | 
 | 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]) { | 
 |             trace_vdagent_cb_grab_discard(GET_NAME(sel_name, s), | 
 |                                           vd->last_serial[s], info->serial); | 
 |             /* 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 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 | 
 |  | 
 |     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_clipboard_peer_register(VDAgentChardev *vd) | 
 | { | 
 |     if (vd->cbpeer.notifier.notify != NULL) { | 
 |         return; | 
 |     } | 
 |  | 
 |     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_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, false); | 
 |     } | 
 |     if (have_mouse(vd) && vd->mouse_hs) { | 
 |         qemu_input_handler_activate(vd->mouse_hs); | 
 |     } | 
 |  | 
 |     memset(vd->last_serial, 0, sizeof(vd->last_serial)); | 
 |  | 
 |     if (have_clipboard(vd)) { | 
 |         qemu_clipboard_reset_serial(); | 
 |         vdagent_clipboard_peer_register(vd); | 
 |     } | 
 | } | 
 |  | 
 | 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) | 
 | { | 
 |     trace_vdagent_disconnect(); | 
 |  | 
 |     vd->connected = false; | 
 |     g_byte_array_set_size(vd->outbuf, 0); | 
 |     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) | 
 | { | 
 |     VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(chr); | 
 |  | 
 |     trace_vdagent_fe_open(fe_open); | 
 |  | 
 |     if (vd->connected == fe_open) { | 
 |         return; | 
 |     } | 
 |  | 
 |     if (!fe_open) { | 
 |         trace_vdagent_close(); | 
 |         vdagent_disconnect(vd); | 
 |         /* To reset_serial, we CLOSED our side. Make sure the other end knows we | 
 |          * are ready again. */ | 
 |         qemu_chr_be_event(chr, CHR_EVENT_OPENED); | 
 |         return; | 
 |     } | 
 |  | 
 |     vd->connected = true; | 
 |     vdagent_send_caps(vd, true); | 
 | } | 
 |  | 
 | 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, const 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 int post_load(void *opaque, int version_id) | 
 | { | 
 |     VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(opaque); | 
 |  | 
 |     if (have_mouse(vd) && vd->mouse_hs) { | 
 |         qemu_input_handler_activate(vd->mouse_hs); | 
 |     } | 
 |  | 
 |     if (have_clipboard(vd)) { | 
 |         vdagent_clipboard_peer_register(vd); | 
 |     } | 
 |  | 
 |     return 0; | 
 | } | 
 |  | 
 | static const VMStateDescription vmstate_chunk = { | 
 |     .name = "vdagent/chunk", | 
 |     .version_id = 0, | 
 |     .minimum_version_id = 0, | 
 |     .fields = (const VMStateField[]) { | 
 |         VMSTATE_UINT32(port, VDIChunkHeader), | 
 |         VMSTATE_UINT32(size, VDIChunkHeader), | 
 |         VMSTATE_END_OF_LIST() | 
 |     } | 
 | }; | 
 |  | 
 | static const VMStateDescription vmstate_vdba = { | 
 |     .name = "vdagent/bytearray", | 
 |     .version_id = 0, | 
 |     .minimum_version_id = 0, | 
 |     .fields = (const VMStateField[]) { | 
 |         VMSTATE_UINT32(len, GByteArray), | 
 |         VMSTATE_VBUFFER_ALLOC_UINT32(data, GByteArray, 0, 0, len), | 
 |         VMSTATE_END_OF_LIST() | 
 |     } | 
 | }; | 
 |  | 
 | struct CBInfoArray { | 
 |     uint32_t n; | 
 |     QemuClipboardInfo cbinfo[QEMU_CLIPBOARD_SELECTION__COUNT]; | 
 | }; | 
 |  | 
 | static const VMStateDescription vmstate_cbinfo_array = { | 
 |     .name = "cbinfoarray", | 
 |     .fields = (const VMStateField[]) { | 
 |         VMSTATE_UINT32(n, struct CBInfoArray), | 
 |         VMSTATE_STRUCT_VARRAY_UINT32(cbinfo, struct CBInfoArray, n, | 
 |                                      0, vmstate_cbinfo, QemuClipboardInfo), | 
 |         VMSTATE_END_OF_LIST() | 
 |     } | 
 | }; | 
 |  | 
 | static int put_cbinfo(QEMUFile *f, void *pv, size_t size, | 
 |                       const VMStateField *field, JSONWriter *vmdesc) | 
 | { | 
 |     VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(pv); | 
 |     struct CBInfoArray cbinfo = {}; | 
 |     int i; | 
 |  | 
 |     if (!have_clipboard(vd)) { | 
 |         return 0; | 
 |     } | 
 |  | 
 |     for (i = 0; i < QEMU_CLIPBOARD_SELECTION__COUNT; i++) { | 
 |         if (qemu_clipboard_peer_owns(&vd->cbpeer, i)) { | 
 |             cbinfo.cbinfo[cbinfo.n++] = *qemu_clipboard_info(i); | 
 |         } | 
 |     } | 
 |  | 
 |     return vmstate_save_state(f, &vmstate_cbinfo_array, &cbinfo, vmdesc); | 
 | } | 
 |  | 
 | static int get_cbinfo(QEMUFile *f, void *pv, size_t size, | 
 |                       const VMStateField *field) | 
 | { | 
 |     VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(pv); | 
 |     struct CBInfoArray cbinfo = {}; | 
 |     int i, ret; | 
 |  | 
 |     if (!have_clipboard(vd)) { | 
 |         return 0; | 
 |     } | 
 |  | 
 |     vdagent_clipboard_peer_register(vd); | 
 |  | 
 |     ret = vmstate_load_state(f, &vmstate_cbinfo_array, &cbinfo, 0); | 
 |     if (ret) { | 
 |         return ret; | 
 |     } | 
 |  | 
 |     for (i = 0; i < cbinfo.n; i++) { | 
 |         g_autoptr(QemuClipboardInfo) info = | 
 |             qemu_clipboard_info_new(&vd->cbpeer, cbinfo.cbinfo[i].selection); | 
 |         /* this will steal clipboard data pointer from cbinfo.types */ | 
 |         memcpy(info->types, cbinfo.cbinfo[i].types, sizeof(cbinfo.cbinfo[i].types)); | 
 |         qemu_clipboard_update(info); | 
 |     } | 
 |  | 
 |     return 0; | 
 | } | 
 |  | 
 | static const VMStateInfo vmstate_cbinfos = { | 
 |     .name = "vdagent/cbinfos", | 
 |     .get  = get_cbinfo, | 
 |     .put  = put_cbinfo, | 
 | }; | 
 |  | 
 | static const VMStateDescription vmstate_vdagent = { | 
 |     .name = "vdagent", | 
 |     .version_id = 0, | 
 |     .minimum_version_id = 0, | 
 |     .post_load = post_load, | 
 |     .fields = (const VMStateField[]) { | 
 |         VMSTATE_BOOL(connected, VDAgentChardev), | 
 |         VMSTATE_UINT32(caps, VDAgentChardev), | 
 |         VMSTATE_STRUCT(chunk, VDAgentChardev, 0, vmstate_chunk, VDIChunkHeader), | 
 |         VMSTATE_UINT32(chunksize, VDAgentChardev), | 
 |         VMSTATE_UINT32(msgsize, VDAgentChardev), | 
 |         VMSTATE_VBUFFER_ALLOC_UINT32(msgbuf, VDAgentChardev, 0, 0, msgsize), | 
 |         VMSTATE_UINT32(xsize, VDAgentChardev), | 
 |         VMSTATE_UINT32(xoff, VDAgentChardev), | 
 |         VMSTATE_VBUFFER_ALLOC_UINT32(xbuf, VDAgentChardev, 0, 0, xsize), | 
 |         VMSTATE_STRUCT_POINTER(outbuf, VDAgentChardev, vmstate_vdba, GByteArray), | 
 |         VMSTATE_UINT32(mouse_x, VDAgentChardev), | 
 |         VMSTATE_UINT32(mouse_y, VDAgentChardev), | 
 |         VMSTATE_UINT32(mouse_btn, VDAgentChardev), | 
 |         VMSTATE_UINT32(mouse_display, VDAgentChardev), | 
 |         VMSTATE_UINT32_ARRAY(last_serial, VDAgentChardev, | 
 |                              QEMU_CLIPBOARD_SELECTION__COUNT), | 
 |         VMSTATE_UINT32_ARRAY(cbpending, VDAgentChardev, | 
 |                              QEMU_CLIPBOARD_SELECTION__COUNT), | 
 |         { | 
 |             .name         = "cbinfos", | 
 |             .info         = &vmstate_cbinfos, | 
 |             .flags        = VMS_SINGLE, | 
 |         }, | 
 |         VMSTATE_END_OF_LIST() | 
 |     } | 
 | }; | 
 |  | 
 | static void vdagent_chr_init(Object *obj) | 
 | { | 
 |     VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(obj); | 
 |  | 
 |     vd->outbuf = g_byte_array_new(); | 
 |     vmstate_register_any(NULL, &vmstate_vdagent, vd); | 
 | } | 
 |  | 
 | static void vdagent_chr_fini(Object *obj) | 
 | { | 
 |     VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(obj); | 
 |  | 
 |     vdagent_disconnect(vd); | 
 |     if (vd->mouse_hs) { | 
 |         qemu_input_handler_unregister(vd->mouse_hs); | 
 |     } | 
 |     g_clear_pointer(&vd->outbuf, g_byte_array_unref); | 
 | } | 
 |  | 
 | 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); |