]>
Commit | Line | Data |
---|---|---|
de74a22c GH |
1 | #include "qemu/osdep.h" |
2 | #include "qapi/error.h" | |
3 | #include "include/qemu-common.h" | |
4 | #include "chardev/char.h" | |
5 | #include "qemu/buffer.h" | |
56081919 | 6 | #include "qemu/option.h" |
de74a22c | 7 | #include "qemu/units.h" |
56081919 | 8 | #include "hw/qdev-core.h" |
90208bc9 | 9 | #include "migration/blocker.h" |
f0349f4d | 10 | #include "ui/clipboard.h" |
56081919 GH |
11 | #include "ui/console.h" |
12 | #include "ui/input.h" | |
de74a22c GH |
13 | #include "trace.h" |
14 | ||
15 | #include "qapi/qapi-types-char.h" | |
56081919 | 16 | #include "qapi/qapi-types-ui.h" |
de74a22c GH |
17 | |
18 | #include "spice/vd_agent.h" | |
19 | ||
20 | #define VDAGENT_BUFFER_LIMIT (1 * MiB) | |
56081919 | 21 | #define VDAGENT_MOUSE_DEFAULT true |
f0349f4d | 22 | #define VDAGENT_CLIPBOARD_DEFAULT false |
de74a22c GH |
23 | |
24 | struct VDAgentChardev { | |
25 | Chardev parent; | |
26 | ||
90208bc9 MAL |
27 | /* TODO: migration isn't yet supported */ |
28 | Error *migration_blocker; | |
29 | ||
56081919 GH |
30 | /* config */ |
31 | bool mouse; | |
f0349f4d | 32 | bool clipboard; |
56081919 | 33 | |
de74a22c GH |
34 | /* guest vdagent */ |
35 | uint32_t caps; | |
36 | VDIChunkHeader chunk; | |
37 | uint32_t chunksize; | |
38 | uint8_t *msgbuf; | |
39 | uint32_t msgsize; | |
40 | uint8_t *xbuf; | |
41 | uint32_t xoff, xsize; | |
42 | Buffer outbuf; | |
56081919 GH |
43 | |
44 | /* mouse */ | |
45 | DeviceState mouse_dev; | |
46 | uint32_t mouse_x; | |
47 | uint32_t mouse_y; | |
48 | uint32_t mouse_btn; | |
49 | uint32_t mouse_display; | |
50 | QemuInputHandlerState *mouse_hs; | |
f0349f4d GH |
51 | |
52 | /* clipboard */ | |
53 | QemuClipboardPeer cbpeer; | |
f0349f4d | 54 | uint32_t cbpending[QEMU_CLIPBOARD_SELECTION__COUNT]; |
de74a22c GH |
55 | }; |
56 | typedef struct VDAgentChardev VDAgentChardev; | |
57 | ||
58 | #define TYPE_CHARDEV_QEMU_VDAGENT "chardev-qemu-vdagent" | |
59 | ||
60 | DECLARE_INSTANCE_CHECKER(VDAgentChardev, QEMU_VDAGENT_CHARDEV, | |
61 | TYPE_CHARDEV_QEMU_VDAGENT); | |
62 | ||
63 | /* ------------------------------------------------------------------ */ | |
64 | /* names, for debug logging */ | |
65 | ||
66 | static const char *cap_name[] = { | |
67 | [VD_AGENT_CAP_MOUSE_STATE] = "mouse-state", | |
68 | [VD_AGENT_CAP_MONITORS_CONFIG] = "monitors-config", | |
69 | [VD_AGENT_CAP_REPLY] = "reply", | |
70 | [VD_AGENT_CAP_CLIPBOARD] = "clipboard", | |
71 | [VD_AGENT_CAP_DISPLAY_CONFIG] = "display-config", | |
72 | [VD_AGENT_CAP_CLIPBOARD_BY_DEMAND] = "clipboard-by-demand", | |
73 | [VD_AGENT_CAP_CLIPBOARD_SELECTION] = "clipboard-selection", | |
74 | [VD_AGENT_CAP_SPARSE_MONITORS_CONFIG] = "sparse-monitors-config", | |
75 | [VD_AGENT_CAP_GUEST_LINEEND_LF] = "guest-lineend-lf", | |
76 | [VD_AGENT_CAP_GUEST_LINEEND_CRLF] = "guest-lineend-crlf", | |
77 | [VD_AGENT_CAP_MAX_CLIPBOARD] = "max-clipboard", | |
78 | [VD_AGENT_CAP_AUDIO_VOLUME_SYNC] = "audio-volume-sync", | |
79 | [VD_AGENT_CAP_MONITORS_CONFIG_POSITION] = "monitors-config-position", | |
80 | [VD_AGENT_CAP_FILE_XFER_DISABLED] = "file-xfer-disabled", | |
81 | [VD_AGENT_CAP_FILE_XFER_DETAILED_ERRORS] = "file-xfer-detailed-errors", | |
82 | #if 0 | |
83 | [VD_AGENT_CAP_GRAPHICS_DEVICE_INFO] = "graphics-device-info", | |
84 | [VD_AGENT_CAP_CLIPBOARD_NO_RELEASE_ON_REGRAB] = "clipboard-no-release-on-regrab", | |
85 | [VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL] = "clipboard-grab-serial", | |
86 | #endif | |
87 | }; | |
88 | ||
89 | static const char *msg_name[] = { | |
90 | [VD_AGENT_MOUSE_STATE] = "mouse-state", | |
91 | [VD_AGENT_MONITORS_CONFIG] = "monitors-config", | |
92 | [VD_AGENT_REPLY] = "reply", | |
93 | [VD_AGENT_CLIPBOARD] = "clipboard", | |
94 | [VD_AGENT_DISPLAY_CONFIG] = "display-config", | |
95 | [VD_AGENT_ANNOUNCE_CAPABILITIES] = "announce-capabilities", | |
96 | [VD_AGENT_CLIPBOARD_GRAB] = "clipboard-grab", | |
97 | [VD_AGENT_CLIPBOARD_REQUEST] = "clipboard-request", | |
98 | [VD_AGENT_CLIPBOARD_RELEASE] = "clipboard-release", | |
99 | [VD_AGENT_FILE_XFER_START] = "file-xfer-start", | |
100 | [VD_AGENT_FILE_XFER_STATUS] = "file-xfer-status", | |
101 | [VD_AGENT_FILE_XFER_DATA] = "file-xfer-data", | |
102 | [VD_AGENT_CLIENT_DISCONNECTED] = "client-disconnected", | |
103 | [VD_AGENT_MAX_CLIPBOARD] = "max-clipboard", | |
104 | [VD_AGENT_AUDIO_VOLUME_SYNC] = "audio-volume-sync", | |
105 | #if 0 | |
106 | [VD_AGENT_GRAPHICS_DEVICE_INFO] = "graphics-device-info", | |
107 | #endif | |
108 | }; | |
109 | ||
f0349f4d GH |
110 | static const char *sel_name[] = { |
111 | [VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD] = "clipboard", | |
112 | [VD_AGENT_CLIPBOARD_SELECTION_PRIMARY] = "primary", | |
113 | [VD_AGENT_CLIPBOARD_SELECTION_SECONDARY] = "secondary", | |
114 | }; | |
115 | ||
116 | static const char *type_name[] = { | |
117 | [VD_AGENT_CLIPBOARD_NONE] = "none", | |
118 | [VD_AGENT_CLIPBOARD_UTF8_TEXT] = "text", | |
119 | [VD_AGENT_CLIPBOARD_IMAGE_PNG] = "png", | |
120 | [VD_AGENT_CLIPBOARD_IMAGE_BMP] = "bmp", | |
121 | [VD_AGENT_CLIPBOARD_IMAGE_TIFF] = "tiff", | |
122 | [VD_AGENT_CLIPBOARD_IMAGE_JPG] = "jpg", | |
123 | #if 0 | |
124 | [VD_AGENT_CLIPBOARD_FILE_LIST] = "files", | |
125 | #endif | |
126 | }; | |
127 | ||
de74a22c GH |
128 | #define GET_NAME(_m, _v) \ |
129 | (((_v) < ARRAY_SIZE(_m) && (_m[_v])) ? (_m[_v]) : "???") | |
130 | ||
131 | /* ------------------------------------------------------------------ */ | |
132 | /* send messages */ | |
133 | ||
134 | static void vdagent_send_buf(VDAgentChardev *vd) | |
135 | { | |
136 | uint32_t len; | |
137 | ||
138 | while (!buffer_empty(&vd->outbuf)) { | |
139 | len = qemu_chr_be_can_write(CHARDEV(vd)); | |
140 | if (len == 0) { | |
141 | return; | |
142 | } | |
143 | if (len > vd->outbuf.offset) { | |
144 | len = vd->outbuf.offset; | |
145 | } | |
146 | qemu_chr_be_write(CHARDEV(vd), vd->outbuf.buffer, len); | |
147 | buffer_advance(&vd->outbuf, len); | |
148 | } | |
149 | } | |
150 | ||
151 | static void vdagent_send_msg(VDAgentChardev *vd, VDAgentMessage *msg) | |
152 | { | |
153 | uint8_t *msgbuf = (void *)msg; | |
154 | uint32_t msgsize = sizeof(VDAgentMessage) + msg->size; | |
155 | uint32_t msgoff = 0; | |
156 | VDIChunkHeader chunk; | |
157 | ||
158 | trace_vdagent_send(GET_NAME(msg_name, msg->type)); | |
159 | ||
160 | msg->protocol = VD_AGENT_PROTOCOL; | |
161 | ||
162 | if (vd->outbuf.offset + msgsize > VDAGENT_BUFFER_LIMIT) { | |
163 | error_report("buffer full, dropping message"); | |
164 | return; | |
165 | } | |
166 | ||
167 | while (msgoff < msgsize) { | |
168 | chunk.port = VDP_CLIENT_PORT; | |
169 | chunk.size = msgsize - msgoff; | |
170 | if (chunk.size > 1024) { | |
171 | chunk.size = 1024; | |
172 | } | |
173 | buffer_reserve(&vd->outbuf, sizeof(chunk) + chunk.size); | |
174 | buffer_append(&vd->outbuf, &chunk, sizeof(chunk)); | |
175 | buffer_append(&vd->outbuf, msgbuf + msgoff, chunk.size); | |
176 | msgoff += chunk.size; | |
177 | } | |
178 | vdagent_send_buf(vd); | |
179 | } | |
180 | ||
181 | static void vdagent_send_caps(VDAgentChardev *vd) | |
182 | { | |
183 | g_autofree VDAgentMessage *msg = g_malloc0(sizeof(VDAgentMessage) + | |
184 | sizeof(VDAgentAnnounceCapabilities) + | |
185 | sizeof(uint32_t)); | |
56081919 | 186 | VDAgentAnnounceCapabilities *caps = (void *)msg->data; |
de74a22c GH |
187 | |
188 | msg->type = VD_AGENT_ANNOUNCE_CAPABILITIES; | |
189 | msg->size = sizeof(VDAgentAnnounceCapabilities) + sizeof(uint32_t); | |
56081919 GH |
190 | if (vd->mouse) { |
191 | caps->caps[0] |= (1 << VD_AGENT_CAP_MOUSE_STATE); | |
192 | } | |
f0349f4d GH |
193 | if (vd->clipboard) { |
194 | caps->caps[0] |= (1 << VD_AGENT_CAP_CLIPBOARD_BY_DEMAND); | |
195 | caps->caps[0] |= (1 << VD_AGENT_CAP_CLIPBOARD_SELECTION); | |
196 | } | |
de74a22c GH |
197 | |
198 | vdagent_send_msg(vd, msg); | |
199 | } | |
200 | ||
56081919 GH |
201 | /* ------------------------------------------------------------------ */ |
202 | /* mouse events */ | |
203 | ||
204 | static bool have_mouse(VDAgentChardev *vd) | |
205 | { | |
206 | return vd->mouse && | |
207 | (vd->caps & (1 << VD_AGENT_CAP_MOUSE_STATE)); | |
208 | } | |
209 | ||
210 | static void vdagent_send_mouse(VDAgentChardev *vd) | |
211 | { | |
212 | g_autofree VDAgentMessage *msg = g_malloc0(sizeof(VDAgentMessage) + | |
213 | sizeof(VDAgentMouseState)); | |
214 | VDAgentMouseState *mouse = (void *)msg->data; | |
215 | ||
216 | msg->type = VD_AGENT_MOUSE_STATE; | |
217 | msg->size = sizeof(VDAgentMouseState); | |
218 | ||
219 | mouse->x = vd->mouse_x; | |
220 | mouse->y = vd->mouse_y; | |
221 | mouse->buttons = vd->mouse_btn; | |
222 | mouse->display_id = vd->mouse_display; | |
223 | ||
224 | vdagent_send_msg(vd, msg); | |
225 | } | |
226 | ||
227 | static void vdagent_pointer_event(DeviceState *dev, QemuConsole *src, | |
228 | InputEvent *evt) | |
229 | { | |
230 | static const int bmap[INPUT_BUTTON__MAX] = { | |
231 | [INPUT_BUTTON_LEFT] = VD_AGENT_LBUTTON_MASK, | |
232 | [INPUT_BUTTON_RIGHT] = VD_AGENT_RBUTTON_MASK, | |
233 | [INPUT_BUTTON_MIDDLE] = VD_AGENT_MBUTTON_MASK, | |
234 | [INPUT_BUTTON_WHEEL_UP] = VD_AGENT_UBUTTON_MASK, | |
235 | [INPUT_BUTTON_WHEEL_DOWN] = VD_AGENT_DBUTTON_MASK, | |
236 | #ifdef VD_AGENT_EBUTTON_MASK | |
237 | [INPUT_BUTTON_SIDE] = VD_AGENT_SBUTTON_MASK, | |
238 | [INPUT_BUTTON_EXTRA] = VD_AGENT_EBUTTON_MASK, | |
239 | #endif | |
240 | }; | |
241 | ||
242 | VDAgentChardev *vd = container_of(dev, struct VDAgentChardev, mouse_dev); | |
243 | InputMoveEvent *move; | |
244 | InputBtnEvent *btn; | |
245 | uint32_t xres, yres; | |
246 | ||
247 | switch (evt->type) { | |
248 | case INPUT_EVENT_KIND_ABS: | |
249 | move = evt->u.abs.data; | |
250 | xres = qemu_console_get_width(src, 1024); | |
251 | yres = qemu_console_get_height(src, 768); | |
252 | if (move->axis == INPUT_AXIS_X) { | |
253 | vd->mouse_x = qemu_input_scale_axis(move->value, | |
254 | INPUT_EVENT_ABS_MIN, | |
255 | INPUT_EVENT_ABS_MAX, | |
256 | 0, xres); | |
257 | } else if (move->axis == INPUT_AXIS_Y) { | |
258 | vd->mouse_y = qemu_input_scale_axis(move->value, | |
259 | INPUT_EVENT_ABS_MIN, | |
260 | INPUT_EVENT_ABS_MAX, | |
261 | 0, yres); | |
262 | } | |
263 | vd->mouse_display = qemu_console_get_index(src); | |
264 | break; | |
265 | ||
266 | case INPUT_EVENT_KIND_BTN: | |
267 | btn = evt->u.btn.data; | |
268 | if (btn->down) { | |
269 | vd->mouse_btn |= bmap[btn->button]; | |
270 | } else { | |
271 | vd->mouse_btn &= ~bmap[btn->button]; | |
272 | } | |
273 | break; | |
274 | ||
275 | default: | |
276 | /* keep gcc happy */ | |
277 | break; | |
278 | } | |
279 | } | |
280 | ||
281 | static void vdagent_pointer_sync(DeviceState *dev) | |
282 | { | |
283 | VDAgentChardev *vd = container_of(dev, struct VDAgentChardev, mouse_dev); | |
284 | ||
285 | if (vd->caps & (1 << VD_AGENT_CAP_MOUSE_STATE)) { | |
286 | vdagent_send_mouse(vd); | |
287 | } | |
288 | } | |
289 | ||
290 | static QemuInputHandler vdagent_mouse_handler = { | |
291 | .name = "vdagent mouse", | |
292 | .mask = INPUT_EVENT_MASK_BTN | INPUT_EVENT_MASK_ABS, | |
293 | .event = vdagent_pointer_event, | |
294 | .sync = vdagent_pointer_sync, | |
295 | }; | |
296 | ||
f0349f4d GH |
297 | /* ------------------------------------------------------------------ */ |
298 | /* clipboard */ | |
299 | ||
300 | static bool have_clipboard(VDAgentChardev *vd) | |
301 | { | |
302 | return vd->clipboard && | |
303 | (vd->caps & (1 << VD_AGENT_CAP_CLIPBOARD_BY_DEMAND)); | |
304 | } | |
305 | ||
306 | static bool have_selection(VDAgentChardev *vd) | |
307 | { | |
308 | return vd->caps & (1 << VD_AGENT_CAP_CLIPBOARD_SELECTION); | |
309 | } | |
310 | ||
311 | static uint32_t type_qemu_to_vdagent(enum QemuClipboardType type) | |
312 | { | |
313 | switch (type) { | |
314 | case QEMU_CLIPBOARD_TYPE_TEXT: | |
315 | return VD_AGENT_CLIPBOARD_UTF8_TEXT; | |
316 | default: | |
317 | return VD_AGENT_CLIPBOARD_NONE; | |
318 | } | |
319 | } | |
320 | ||
321 | static void vdagent_send_clipboard_grab(VDAgentChardev *vd, | |
322 | QemuClipboardInfo *info) | |
323 | { | |
324 | g_autofree VDAgentMessage *msg = | |
325 | g_malloc0(sizeof(VDAgentMessage) + | |
326 | sizeof(uint32_t) * (QEMU_CLIPBOARD_TYPE__COUNT + 1)); | |
327 | uint8_t *s = msg->data; | |
328 | uint32_t *data = (uint32_t *)msg->data; | |
329 | uint32_t q, type; | |
330 | ||
331 | if (have_selection(vd)) { | |
332 | *s = info->selection; | |
333 | data++; | |
334 | msg->size += sizeof(uint32_t); | |
335 | } else if (info->selection != QEMU_CLIPBOARD_SELECTION_CLIPBOARD) { | |
336 | return; | |
337 | } | |
338 | ||
339 | for (q = 0; q < QEMU_CLIPBOARD_TYPE__COUNT; q++) { | |
340 | type = type_qemu_to_vdagent(q); | |
341 | if (type != VD_AGENT_CLIPBOARD_NONE && info->types[q].available) { | |
342 | *data = type; | |
343 | data++; | |
344 | msg->size += sizeof(uint32_t); | |
345 | } | |
346 | } | |
347 | ||
348 | msg->type = VD_AGENT_CLIPBOARD_GRAB; | |
349 | vdagent_send_msg(vd, msg); | |
350 | } | |
351 | ||
314bf500 MAL |
352 | static void vdagent_send_clipboard_release(VDAgentChardev *vd, |
353 | QemuClipboardInfo *info) | |
354 | { | |
355 | g_autofree VDAgentMessage *msg = g_malloc0(sizeof(VDAgentMessage) + | |
356 | sizeof(uint32_t)); | |
357 | ||
358 | if (have_selection(vd)) { | |
359 | uint8_t *s = msg->data; | |
360 | *s = info->selection; | |
361 | msg->size += sizeof(uint32_t); | |
362 | } else if (info->selection != QEMU_CLIPBOARD_SELECTION_CLIPBOARD) { | |
363 | return; | |
364 | } | |
365 | ||
366 | msg->type = VD_AGENT_CLIPBOARD_RELEASE; | |
367 | vdagent_send_msg(vd, msg); | |
368 | } | |
369 | ||
f0349f4d GH |
370 | static void vdagent_send_clipboard_data(VDAgentChardev *vd, |
371 | QemuClipboardInfo *info, | |
372 | QemuClipboardType type) | |
373 | { | |
374 | g_autofree VDAgentMessage *msg = g_malloc0(sizeof(VDAgentMessage) + | |
375 | sizeof(uint32_t) * 2 + | |
376 | info->types[type].size); | |
377 | ||
378 | uint8_t *s = msg->data; | |
379 | uint32_t *data = (uint32_t *)msg->data; | |
380 | ||
381 | if (have_selection(vd)) { | |
382 | *s = info->selection; | |
383 | data++; | |
384 | msg->size += sizeof(uint32_t); | |
385 | } else if (info->selection != QEMU_CLIPBOARD_SELECTION_CLIPBOARD) { | |
386 | return; | |
387 | } | |
388 | ||
389 | *data = type_qemu_to_vdagent(type); | |
390 | data++; | |
391 | msg->size += sizeof(uint32_t); | |
392 | ||
393 | memcpy(data, info->types[type].data, info->types[type].size); | |
394 | msg->size += info->types[type].size; | |
395 | ||
396 | msg->type = VD_AGENT_CLIPBOARD; | |
397 | vdagent_send_msg(vd, msg); | |
398 | } | |
399 | ||
3d3f0bc3 MAL |
400 | static void vdagent_send_empty_clipboard_data(VDAgentChardev *vd, |
401 | QemuClipboardSelection selection, | |
402 | QemuClipboardType type) | |
403 | { | |
404 | g_autoptr(QemuClipboardInfo) info = qemu_clipboard_info_new(&vd->cbpeer, selection); | |
405 | ||
406 | trace_vdagent_send_empty_clipboard(); | |
407 | vdagent_send_clipboard_data(vd, info, type); | |
408 | } | |
409 | ||
f0349f4d GH |
410 | static void vdagent_clipboard_notify(Notifier *notifier, void *data) |
411 | { | |
412 | VDAgentChardev *vd = container_of(notifier, VDAgentChardev, cbpeer.update); | |
413 | QemuClipboardInfo *info = data; | |
414 | QemuClipboardSelection s = info->selection; | |
415 | QemuClipboardType type; | |
416 | bool self_update = info->owner == &vd->cbpeer; | |
417 | ||
d2ed2c01 | 418 | if (info != qemu_clipboard_info(s)) { |
f0349f4d GH |
419 | vd->cbpending[s] = 0; |
420 | if (!self_update) { | |
314bf500 MAL |
421 | if (info->owner) { |
422 | vdagent_send_clipboard_grab(vd, info); | |
423 | } else { | |
424 | vdagent_send_clipboard_release(vd, info); | |
425 | } | |
f0349f4d GH |
426 | } |
427 | return; | |
428 | } | |
429 | ||
430 | if (self_update) { | |
431 | return; | |
432 | } | |
433 | ||
434 | for (type = 0; type < QEMU_CLIPBOARD_TYPE__COUNT; type++) { | |
435 | if (vd->cbpending[s] & (1 << type)) { | |
436 | vd->cbpending[s] &= ~(1 << type); | |
437 | vdagent_send_clipboard_data(vd, info, type); | |
438 | } | |
439 | } | |
440 | } | |
441 | ||
442 | static void vdagent_clipboard_request(QemuClipboardInfo *info, | |
443 | QemuClipboardType qtype) | |
444 | { | |
445 | VDAgentChardev *vd = container_of(info->owner, VDAgentChardev, cbpeer); | |
446 | g_autofree VDAgentMessage *msg = g_malloc0(sizeof(VDAgentMessage) + | |
447 | sizeof(uint32_t) * 2); | |
448 | uint32_t type = type_qemu_to_vdagent(qtype); | |
449 | uint8_t *s = msg->data; | |
450 | uint32_t *data = (uint32_t *)msg->data; | |
451 | ||
452 | if (type == VD_AGENT_CLIPBOARD_NONE) { | |
453 | return; | |
454 | } | |
455 | ||
456 | if (have_selection(vd)) { | |
457 | *s = info->selection; | |
458 | data++; | |
459 | msg->size += sizeof(uint32_t); | |
460 | } | |
461 | ||
462 | *data = type; | |
463 | msg->size += sizeof(uint32_t); | |
464 | ||
465 | msg->type = VD_AGENT_CLIPBOARD_REQUEST; | |
466 | vdagent_send_msg(vd, msg); | |
467 | } | |
468 | ||
3b99bb4c MAL |
469 | static void vdagent_clipboard_recv_grab(VDAgentChardev *vd, uint8_t s, uint32_t size, void *data) |
470 | { | |
471 | g_autoptr(QemuClipboardInfo) info = NULL; | |
472 | ||
473 | trace_vdagent_cb_grab_selection(GET_NAME(sel_name, s)); | |
474 | info = qemu_clipboard_info_new(&vd->cbpeer, s); | |
475 | if (size > sizeof(uint32_t) * 10) { | |
476 | /* | |
477 | * spice has 6 types as of 2021. Limiting to 10 entries | |
478 | * so we we have some wiggle room. | |
479 | */ | |
480 | return; | |
481 | } | |
482 | while (size >= sizeof(uint32_t)) { | |
483 | trace_vdagent_cb_grab_type(GET_NAME(type_name, *(uint32_t *)data)); | |
484 | switch (*(uint32_t *)data) { | |
485 | case VD_AGENT_CLIPBOARD_UTF8_TEXT: | |
486 | info->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true; | |
487 | break; | |
488 | default: | |
489 | break; | |
490 | } | |
491 | data += sizeof(uint32_t); | |
492 | size -= sizeof(uint32_t); | |
493 | } | |
494 | qemu_clipboard_update(info); | |
495 | } | |
496 | ||
497 | static void vdagent_clipboard_recv_request(VDAgentChardev *vd, uint8_t s, uint32_t size, void *data) | |
498 | { | |
499 | QemuClipboardType type; | |
d2ed2c01 | 500 | QemuClipboardInfo *info; |
3b99bb4c MAL |
501 | |
502 | if (size < sizeof(uint32_t)) { | |
503 | return; | |
504 | } | |
505 | switch (*(uint32_t *)data) { | |
506 | case VD_AGENT_CLIPBOARD_UTF8_TEXT: | |
507 | type = QEMU_CLIPBOARD_TYPE_TEXT; | |
508 | break; | |
509 | default: | |
510 | return; | |
511 | } | |
d2ed2c01 MAL |
512 | |
513 | info = qemu_clipboard_info(s); | |
514 | if (info && info->types[type].available && info->owner != &vd->cbpeer) { | |
515 | if (info->types[type].data) { | |
516 | vdagent_send_clipboard_data(vd, info, type); | |
3b99bb4c MAL |
517 | } else { |
518 | vd->cbpending[s] |= (1 << type); | |
d2ed2c01 | 519 | qemu_clipboard_request(info, type); |
3b99bb4c | 520 | } |
3d3f0bc3 MAL |
521 | } else { |
522 | vdagent_send_empty_clipboard_data(vd, s, type); | |
3b99bb4c MAL |
523 | } |
524 | } | |
525 | ||
526 | static void vdagent_clipboard_recv_data(VDAgentChardev *vd, uint8_t s, uint32_t size, void *data) | |
527 | { | |
528 | QemuClipboardType type; | |
529 | ||
530 | if (size < sizeof(uint32_t)) { | |
531 | return; | |
532 | } | |
533 | switch (*(uint32_t *)data) { | |
534 | case VD_AGENT_CLIPBOARD_UTF8_TEXT: | |
535 | type = QEMU_CLIPBOARD_TYPE_TEXT; | |
536 | break; | |
537 | default: | |
538 | return; | |
539 | } | |
540 | data += 4; | |
541 | size -= 4; | |
d2ed2c01 MAL |
542 | |
543 | if (qemu_clipboard_peer_owns(&vd->cbpeer, s)) { | |
544 | qemu_clipboard_set_data(&vd->cbpeer, qemu_clipboard_info(s), | |
545 | type, size, data, true); | |
546 | } | |
3b99bb4c MAL |
547 | } |
548 | ||
549 | static void vdagent_clipboard_recv_release(VDAgentChardev *vd, uint8_t s) | |
550 | { | |
c98c50de | 551 | qemu_clipboard_peer_release(&vd->cbpeer, s); |
3b99bb4c MAL |
552 | } |
553 | ||
f0349f4d GH |
554 | static void vdagent_chr_recv_clipboard(VDAgentChardev *vd, VDAgentMessage *msg) |
555 | { | |
556 | uint8_t s = VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD; | |
557 | uint32_t size = msg->size; | |
558 | void *data = msg->data; | |
f0349f4d GH |
559 | |
560 | if (have_selection(vd)) { | |
561 | if (size < 4) { | |
562 | return; | |
563 | } | |
564 | s = *(uint8_t *)data; | |
565 | if (s >= QEMU_CLIPBOARD_SELECTION__COUNT) { | |
566 | return; | |
567 | } | |
568 | data += 4; | |
569 | size -= 4; | |
570 | } | |
571 | ||
572 | switch (msg->type) { | |
573 | case VD_AGENT_CLIPBOARD_GRAB: | |
3b99bb4c | 574 | return vdagent_clipboard_recv_grab(vd, s, size, data); |
f0349f4d | 575 | case VD_AGENT_CLIPBOARD_REQUEST: |
3b99bb4c | 576 | return vdagent_clipboard_recv_request(vd, s, size, data); |
f0349f4d | 577 | case VD_AGENT_CLIPBOARD: /* data */ |
3b99bb4c | 578 | return vdagent_clipboard_recv_data(vd, s, size, data); |
e7c55746 | 579 | case VD_AGENT_CLIPBOARD_RELEASE: |
3b99bb4c MAL |
580 | return vdagent_clipboard_recv_release(vd, s); |
581 | default: | |
582 | g_assert_not_reached(); | |
f0349f4d GH |
583 | } |
584 | } | |
585 | ||
de74a22c GH |
586 | /* ------------------------------------------------------------------ */ |
587 | /* chardev backend */ | |
588 | ||
589 | static void vdagent_chr_open(Chardev *chr, | |
590 | ChardevBackend *backend, | |
591 | bool *be_opened, | |
592 | Error **errp) | |
593 | { | |
56081919 GH |
594 | VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(chr); |
595 | ChardevQemuVDAgent *cfg = backend->u.qemu_vdagent.data; | |
596 | ||
de74a22c GH |
597 | #if defined(HOST_WORDS_BIGENDIAN) |
598 | /* | |
599 | * TODO: vdagent protocol is defined to be LE, | |
600 | * so we have to byteswap everything on BE hosts. | |
601 | */ | |
602 | error_setg(errp, "vdagent is not supported on bigendian hosts"); | |
603 | return; | |
604 | #endif | |
605 | ||
90208bc9 MAL |
606 | if (migrate_add_blocker(vd->migration_blocker, errp) != 0) { |
607 | return; | |
608 | } | |
609 | ||
56081919 GH |
610 | vd->mouse = VDAGENT_MOUSE_DEFAULT; |
611 | if (cfg->has_mouse) { | |
612 | vd->mouse = cfg->mouse; | |
613 | } | |
614 | ||
f0349f4d GH |
615 | vd->clipboard = VDAGENT_CLIPBOARD_DEFAULT; |
616 | if (cfg->has_clipboard) { | |
617 | vd->clipboard = cfg->clipboard; | |
618 | } | |
619 | ||
56081919 GH |
620 | if (vd->mouse) { |
621 | vd->mouse_hs = qemu_input_handler_register(&vd->mouse_dev, | |
622 | &vdagent_mouse_handler); | |
623 | } | |
624 | ||
de74a22c GH |
625 | *be_opened = true; |
626 | } | |
627 | ||
628 | static void vdagent_chr_recv_caps(VDAgentChardev *vd, VDAgentMessage *msg) | |
629 | { | |
630 | VDAgentAnnounceCapabilities *caps = (void *)msg->data; | |
631 | int i; | |
632 | ||
633 | if (msg->size < (sizeof(VDAgentAnnounceCapabilities) + | |
634 | sizeof(uint32_t))) { | |
635 | return; | |
636 | } | |
637 | ||
638 | for (i = 0; i < ARRAY_SIZE(cap_name); i++) { | |
639 | if (caps->caps[0] & (1 << i)) { | |
640 | trace_vdagent_peer_cap(GET_NAME(cap_name, i)); | |
641 | } | |
642 | } | |
643 | ||
644 | vd->caps = caps->caps[0]; | |
645 | if (caps->request) { | |
646 | vdagent_send_caps(vd); | |
647 | } | |
56081919 GH |
648 | if (have_mouse(vd) && vd->mouse_hs) { |
649 | qemu_input_handler_activate(vd->mouse_hs); | |
650 | } | |
f0349f4d GH |
651 | if (have_clipboard(vd) && vd->cbpeer.update.notify == NULL) { |
652 | vd->cbpeer.name = "vdagent"; | |
653 | vd->cbpeer.update.notify = vdagent_clipboard_notify; | |
654 | vd->cbpeer.request = vdagent_clipboard_request; | |
655 | qemu_clipboard_peer_register(&vd->cbpeer); | |
656 | } | |
de74a22c GH |
657 | } |
658 | ||
659 | static void vdagent_chr_recv_msg(VDAgentChardev *vd, VDAgentMessage *msg) | |
660 | { | |
661 | trace_vdagent_recv_msg(GET_NAME(msg_name, msg->type), msg->size); | |
662 | ||
663 | switch (msg->type) { | |
664 | case VD_AGENT_ANNOUNCE_CAPABILITIES: | |
665 | vdagent_chr_recv_caps(vd, msg); | |
666 | break; | |
f0349f4d GH |
667 | case VD_AGENT_CLIPBOARD: |
668 | case VD_AGENT_CLIPBOARD_GRAB: | |
669 | case VD_AGENT_CLIPBOARD_REQUEST: | |
670 | case VD_AGENT_CLIPBOARD_RELEASE: | |
671 | if (have_clipboard(vd)) { | |
672 | vdagent_chr_recv_clipboard(vd, msg); | |
673 | } | |
674 | break; | |
de74a22c GH |
675 | default: |
676 | break; | |
677 | } | |
678 | } | |
679 | ||
680 | static void vdagent_reset_xbuf(VDAgentChardev *vd) | |
681 | { | |
682 | g_clear_pointer(&vd->xbuf, g_free); | |
683 | vd->xoff = 0; | |
684 | vd->xsize = 0; | |
685 | } | |
686 | ||
687 | static void vdagent_chr_recv_chunk(VDAgentChardev *vd) | |
688 | { | |
689 | VDAgentMessage *msg = (void *)vd->msgbuf; | |
690 | ||
691 | if (!vd->xsize) { | |
692 | if (vd->msgsize < sizeof(*msg)) { | |
693 | error_report("%s: message too small: %d < %zd", __func__, | |
694 | vd->msgsize, sizeof(*msg)); | |
695 | return; | |
696 | } | |
697 | if (vd->msgsize == msg->size + sizeof(*msg)) { | |
698 | vdagent_chr_recv_msg(vd, msg); | |
699 | return; | |
700 | } | |
701 | } | |
702 | ||
703 | if (!vd->xsize) { | |
704 | vd->xsize = msg->size + sizeof(*msg); | |
705 | vd->xbuf = g_malloc0(vd->xsize); | |
706 | } | |
707 | ||
708 | if (vd->xoff + vd->msgsize > vd->xsize) { | |
709 | error_report("%s: Oops: %d+%d > %d", __func__, | |
710 | vd->xoff, vd->msgsize, vd->xsize); | |
711 | vdagent_reset_xbuf(vd); | |
712 | return; | |
713 | } | |
714 | ||
715 | memcpy(vd->xbuf + vd->xoff, vd->msgbuf, vd->msgsize); | |
716 | vd->xoff += vd->msgsize; | |
717 | if (vd->xoff < vd->xsize) { | |
718 | return; | |
719 | } | |
720 | ||
721 | msg = (void *)vd->xbuf; | |
722 | vdagent_chr_recv_msg(vd, msg); | |
723 | vdagent_reset_xbuf(vd); | |
724 | } | |
725 | ||
726 | static void vdagent_reset_bufs(VDAgentChardev *vd) | |
727 | { | |
728 | memset(&vd->chunk, 0, sizeof(vd->chunk)); | |
729 | vd->chunksize = 0; | |
730 | g_free(vd->msgbuf); | |
731 | vd->msgbuf = NULL; | |
732 | vd->msgsize = 0; | |
733 | } | |
734 | ||
735 | static int vdagent_chr_write(Chardev *chr, const uint8_t *buf, int len) | |
736 | { | |
737 | VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(chr); | |
738 | uint32_t copy, ret = len; | |
739 | ||
740 | while (len) { | |
741 | if (vd->chunksize < sizeof(vd->chunk)) { | |
742 | copy = sizeof(vd->chunk) - vd->chunksize; | |
743 | if (copy > len) { | |
744 | copy = len; | |
745 | } | |
746 | memcpy((void *)(&vd->chunk) + vd->chunksize, buf, copy); | |
747 | vd->chunksize += copy; | |
748 | buf += copy; | |
749 | len -= copy; | |
750 | if (vd->chunksize < sizeof(vd->chunk)) { | |
751 | break; | |
752 | } | |
753 | ||
754 | assert(vd->msgbuf == NULL); | |
755 | vd->msgbuf = g_malloc0(vd->chunk.size); | |
756 | } | |
757 | ||
758 | copy = vd->chunk.size - vd->msgsize; | |
759 | if (copy > len) { | |
760 | copy = len; | |
761 | } | |
762 | memcpy(vd->msgbuf + vd->msgsize, buf, copy); | |
763 | vd->msgsize += copy; | |
764 | buf += copy; | |
765 | len -= copy; | |
766 | ||
767 | if (vd->msgsize == vd->chunk.size) { | |
768 | trace_vdagent_recv_chunk(vd->chunk.size); | |
769 | vdagent_chr_recv_chunk(vd); | |
770 | vdagent_reset_bufs(vd); | |
771 | } | |
772 | } | |
773 | ||
774 | return ret; | |
775 | } | |
776 | ||
777 | static void vdagent_chr_accept_input(Chardev *chr) | |
778 | { | |
779 | VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(chr); | |
780 | ||
781 | vdagent_send_buf(vd); | |
782 | } | |
783 | ||
5e0a24e8 MAL |
784 | static void vdagent_disconnect(VDAgentChardev *vd) |
785 | { | |
5fb2e8d9 | 786 | buffer_reset(&vd->outbuf); |
5e0a24e8 MAL |
787 | vdagent_reset_bufs(vd); |
788 | vd->caps = 0; | |
789 | if (vd->mouse_hs) { | |
790 | qemu_input_handler_deactivate(vd->mouse_hs); | |
791 | } | |
792 | if (vd->cbpeer.update.notify) { | |
793 | qemu_clipboard_peer_unregister(&vd->cbpeer); | |
794 | memset(&vd->cbpeer, 0, sizeof(vd->cbpeer)); | |
795 | } | |
796 | } | |
797 | ||
de74a22c GH |
798 | static void vdagent_chr_set_fe_open(struct Chardev *chr, int fe_open) |
799 | { | |
800 | VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(chr); | |
801 | ||
802 | if (!fe_open) { | |
803 | trace_vdagent_close(); | |
5e0a24e8 | 804 | vdagent_disconnect(vd); |
de74a22c GH |
805 | return; |
806 | } | |
807 | ||
808 | trace_vdagent_open(); | |
809 | } | |
810 | ||
56081919 GH |
811 | static void vdagent_chr_parse(QemuOpts *opts, ChardevBackend *backend, |
812 | Error **errp) | |
813 | { | |
814 | ChardevQemuVDAgent *cfg; | |
815 | ||
816 | backend->type = CHARDEV_BACKEND_KIND_QEMU_VDAGENT; | |
817 | cfg = backend->u.qemu_vdagent.data = g_new0(ChardevQemuVDAgent, 1); | |
818 | qemu_chr_parse_common(opts, qapi_ChardevQemuVDAgent_base(cfg)); | |
819 | cfg->has_mouse = true; | |
820 | cfg->mouse = qemu_opt_get_bool(opts, "mouse", VDAGENT_MOUSE_DEFAULT); | |
f0349f4d GH |
821 | cfg->has_clipboard = true; |
822 | cfg->clipboard = qemu_opt_get_bool(opts, "clipboard", VDAGENT_CLIPBOARD_DEFAULT); | |
56081919 GH |
823 | } |
824 | ||
de74a22c GH |
825 | /* ------------------------------------------------------------------ */ |
826 | ||
827 | static void vdagent_chr_class_init(ObjectClass *oc, void *data) | |
828 | { | |
829 | ChardevClass *cc = CHARDEV_CLASS(oc); | |
830 | ||
56081919 | 831 | cc->parse = vdagent_chr_parse; |
de74a22c GH |
832 | cc->open = vdagent_chr_open; |
833 | cc->chr_write = vdagent_chr_write; | |
834 | cc->chr_set_fe_open = vdagent_chr_set_fe_open; | |
835 | cc->chr_accept_input = vdagent_chr_accept_input; | |
836 | } | |
837 | ||
838 | static void vdagent_chr_init(Object *obj) | |
839 | { | |
840 | VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(obj); | |
841 | ||
842 | buffer_init(&vd->outbuf, "vdagent-outbuf"); | |
90208bc9 MAL |
843 | error_setg(&vd->migration_blocker, |
844 | "The vdagent chardev doesn't yet support migration"); | |
de74a22c GH |
845 | } |
846 | ||
847 | static void vdagent_chr_fini(Object *obj) | |
848 | { | |
849 | VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(obj); | |
850 | ||
90208bc9 | 851 | migrate_del_blocker(vd->migration_blocker); |
5e0a24e8 | 852 | vdagent_disconnect(vd); |
de74a22c | 853 | buffer_free(&vd->outbuf); |
90208bc9 | 854 | error_free(vd->migration_blocker); |
de74a22c GH |
855 | } |
856 | ||
857 | static const TypeInfo vdagent_chr_type_info = { | |
858 | .name = TYPE_CHARDEV_QEMU_VDAGENT, | |
859 | .parent = TYPE_CHARDEV, | |
860 | .instance_size = sizeof(VDAgentChardev), | |
861 | .instance_init = vdagent_chr_init, | |
862 | .instance_finalize = vdagent_chr_fini, | |
863 | .class_init = vdagent_chr_class_init, | |
864 | }; | |
865 | ||
866 | static void register_types(void) | |
867 | { | |
868 | type_register_static(&vdagent_chr_type_info); | |
869 | } | |
870 | ||
871 | type_init(register_types); |