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