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