]>
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 | ||
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", | |
59127452 | 91 | #if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 0) |
de74a22c | 92 | [VD_AGENT_CAP_GRAPHICS_DEVICE_INFO] = "graphics-device-info", |
59127452 MAL |
93 | #endif |
94 | #if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 1) | |
de74a22c GH |
95 | [VD_AGENT_CAP_CLIPBOARD_NO_RELEASE_ON_REGRAB] = "clipboard-no-release-on-regrab", |
96 | [VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL] = "clipboard-grab-serial", | |
97 | #endif | |
98 | }; | |
99 | ||
100 | static const char *msg_name[] = { | |
101 | [VD_AGENT_MOUSE_STATE] = "mouse-state", | |
102 | [VD_AGENT_MONITORS_CONFIG] = "monitors-config", | |
103 | [VD_AGENT_REPLY] = "reply", | |
104 | [VD_AGENT_CLIPBOARD] = "clipboard", | |
105 | [VD_AGENT_DISPLAY_CONFIG] = "display-config", | |
106 | [VD_AGENT_ANNOUNCE_CAPABILITIES] = "announce-capabilities", | |
107 | [VD_AGENT_CLIPBOARD_GRAB] = "clipboard-grab", | |
108 | [VD_AGENT_CLIPBOARD_REQUEST] = "clipboard-request", | |
109 | [VD_AGENT_CLIPBOARD_RELEASE] = "clipboard-release", | |
110 | [VD_AGENT_FILE_XFER_START] = "file-xfer-start", | |
111 | [VD_AGENT_FILE_XFER_STATUS] = "file-xfer-status", | |
112 | [VD_AGENT_FILE_XFER_DATA] = "file-xfer-data", | |
113 | [VD_AGENT_CLIENT_DISCONNECTED] = "client-disconnected", | |
114 | [VD_AGENT_MAX_CLIPBOARD] = "max-clipboard", | |
115 | [VD_AGENT_AUDIO_VOLUME_SYNC] = "audio-volume-sync", | |
59127452 | 116 | #if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 0) |
de74a22c GH |
117 | [VD_AGENT_GRAPHICS_DEVICE_INFO] = "graphics-device-info", |
118 | #endif | |
119 | }; | |
120 | ||
f0349f4d GH |
121 | static const char *sel_name[] = { |
122 | [VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD] = "clipboard", | |
123 | [VD_AGENT_CLIPBOARD_SELECTION_PRIMARY] = "primary", | |
124 | [VD_AGENT_CLIPBOARD_SELECTION_SECONDARY] = "secondary", | |
125 | }; | |
126 | ||
127 | static const char *type_name[] = { | |
128 | [VD_AGENT_CLIPBOARD_NONE] = "none", | |
129 | [VD_AGENT_CLIPBOARD_UTF8_TEXT] = "text", | |
130 | [VD_AGENT_CLIPBOARD_IMAGE_PNG] = "png", | |
131 | [VD_AGENT_CLIPBOARD_IMAGE_BMP] = "bmp", | |
132 | [VD_AGENT_CLIPBOARD_IMAGE_TIFF] = "tiff", | |
133 | [VD_AGENT_CLIPBOARD_IMAGE_JPG] = "jpg", | |
59127452 | 134 | #if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 3) |
f0349f4d GH |
135 | [VD_AGENT_CLIPBOARD_FILE_LIST] = "files", |
136 | #endif | |
137 | }; | |
138 | ||
de74a22c GH |
139 | #define GET_NAME(_m, _v) \ |
140 | (((_v) < ARRAY_SIZE(_m) && (_m[_v])) ? (_m[_v]) : "???") | |
141 | ||
142 | /* ------------------------------------------------------------------ */ | |
143 | /* send messages */ | |
144 | ||
145 | static void vdagent_send_buf(VDAgentChardev *vd) | |
146 | { | |
147 | uint32_t len; | |
148 | ||
149 | while (!buffer_empty(&vd->outbuf)) { | |
150 | len = qemu_chr_be_can_write(CHARDEV(vd)); | |
151 | if (len == 0) { | |
152 | return; | |
153 | } | |
154 | if (len > vd->outbuf.offset) { | |
155 | len = vd->outbuf.offset; | |
156 | } | |
157 | qemu_chr_be_write(CHARDEV(vd), vd->outbuf.buffer, len); | |
158 | buffer_advance(&vd->outbuf, len); | |
159 | } | |
160 | } | |
161 | ||
162 | static void vdagent_send_msg(VDAgentChardev *vd, VDAgentMessage *msg) | |
163 | { | |
164 | uint8_t *msgbuf = (void *)msg; | |
165 | uint32_t msgsize = sizeof(VDAgentMessage) + msg->size; | |
166 | uint32_t msgoff = 0; | |
167 | VDIChunkHeader chunk; | |
168 | ||
169 | trace_vdagent_send(GET_NAME(msg_name, msg->type)); | |
170 | ||
171 | msg->protocol = VD_AGENT_PROTOCOL; | |
172 | ||
173 | if (vd->outbuf.offset + msgsize > VDAGENT_BUFFER_LIMIT) { | |
174 | error_report("buffer full, dropping message"); | |
175 | return; | |
176 | } | |
177 | ||
178 | while (msgoff < msgsize) { | |
179 | chunk.port = VDP_CLIENT_PORT; | |
180 | chunk.size = msgsize - msgoff; | |
181 | if (chunk.size > 1024) { | |
182 | chunk.size = 1024; | |
183 | } | |
184 | buffer_reserve(&vd->outbuf, sizeof(chunk) + chunk.size); | |
185 | buffer_append(&vd->outbuf, &chunk, sizeof(chunk)); | |
186 | buffer_append(&vd->outbuf, msgbuf + msgoff, chunk.size); | |
187 | msgoff += chunk.size; | |
188 | } | |
189 | vdagent_send_buf(vd); | |
190 | } | |
191 | ||
192 | static void vdagent_send_caps(VDAgentChardev *vd) | |
193 | { | |
194 | g_autofree VDAgentMessage *msg = g_malloc0(sizeof(VDAgentMessage) + | |
195 | sizeof(VDAgentAnnounceCapabilities) + | |
196 | sizeof(uint32_t)); | |
56081919 | 197 | VDAgentAnnounceCapabilities *caps = (void *)msg->data; |
de74a22c GH |
198 | |
199 | msg->type = VD_AGENT_ANNOUNCE_CAPABILITIES; | |
200 | msg->size = sizeof(VDAgentAnnounceCapabilities) + sizeof(uint32_t); | |
56081919 GH |
201 | if (vd->mouse) { |
202 | caps->caps[0] |= (1 << VD_AGENT_CAP_MOUSE_STATE); | |
203 | } | |
f0349f4d GH |
204 | if (vd->clipboard) { |
205 | caps->caps[0] |= (1 << VD_AGENT_CAP_CLIPBOARD_BY_DEMAND); | |
206 | caps->caps[0] |= (1 << VD_AGENT_CAP_CLIPBOARD_SELECTION); | |
835f69f4 MAL |
207 | #if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 1) |
208 | caps->caps[0] |= (1 << VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL); | |
209 | #endif | |
f0349f4d | 210 | } |
de74a22c GH |
211 | |
212 | vdagent_send_msg(vd, msg); | |
213 | } | |
214 | ||
56081919 GH |
215 | /* ------------------------------------------------------------------ */ |
216 | /* mouse events */ | |
217 | ||
218 | static bool have_mouse(VDAgentChardev *vd) | |
219 | { | |
220 | return vd->mouse && | |
221 | (vd->caps & (1 << VD_AGENT_CAP_MOUSE_STATE)); | |
222 | } | |
223 | ||
224 | static void vdagent_send_mouse(VDAgentChardev *vd) | |
225 | { | |
226 | g_autofree VDAgentMessage *msg = g_malloc0(sizeof(VDAgentMessage) + | |
227 | sizeof(VDAgentMouseState)); | |
228 | VDAgentMouseState *mouse = (void *)msg->data; | |
229 | ||
230 | msg->type = VD_AGENT_MOUSE_STATE; | |
231 | msg->size = sizeof(VDAgentMouseState); | |
232 | ||
233 | mouse->x = vd->mouse_x; | |
234 | mouse->y = vd->mouse_y; | |
235 | mouse->buttons = vd->mouse_btn; | |
236 | mouse->display_id = vd->mouse_display; | |
237 | ||
238 | vdagent_send_msg(vd, msg); | |
239 | } | |
240 | ||
241 | static void vdagent_pointer_event(DeviceState *dev, QemuConsole *src, | |
242 | InputEvent *evt) | |
243 | { | |
244 | static const int bmap[INPUT_BUTTON__MAX] = { | |
245 | [INPUT_BUTTON_LEFT] = VD_AGENT_LBUTTON_MASK, | |
246 | [INPUT_BUTTON_RIGHT] = VD_AGENT_RBUTTON_MASK, | |
247 | [INPUT_BUTTON_MIDDLE] = VD_AGENT_MBUTTON_MASK, | |
248 | [INPUT_BUTTON_WHEEL_UP] = VD_AGENT_UBUTTON_MASK, | |
249 | [INPUT_BUTTON_WHEEL_DOWN] = VD_AGENT_DBUTTON_MASK, | |
250 | #ifdef VD_AGENT_EBUTTON_MASK | |
251 | [INPUT_BUTTON_SIDE] = VD_AGENT_SBUTTON_MASK, | |
252 | [INPUT_BUTTON_EXTRA] = VD_AGENT_EBUTTON_MASK, | |
253 | #endif | |
254 | }; | |
255 | ||
256 | VDAgentChardev *vd = container_of(dev, struct VDAgentChardev, mouse_dev); | |
257 | InputMoveEvent *move; | |
258 | InputBtnEvent *btn; | |
259 | uint32_t xres, yres; | |
260 | ||
261 | switch (evt->type) { | |
262 | case INPUT_EVENT_KIND_ABS: | |
263 | move = evt->u.abs.data; | |
264 | xres = qemu_console_get_width(src, 1024); | |
265 | yres = qemu_console_get_height(src, 768); | |
266 | if (move->axis == INPUT_AXIS_X) { | |
267 | vd->mouse_x = qemu_input_scale_axis(move->value, | |
268 | INPUT_EVENT_ABS_MIN, | |
269 | INPUT_EVENT_ABS_MAX, | |
270 | 0, xres); | |
271 | } else if (move->axis == INPUT_AXIS_Y) { | |
272 | vd->mouse_y = qemu_input_scale_axis(move->value, | |
273 | INPUT_EVENT_ABS_MIN, | |
274 | INPUT_EVENT_ABS_MAX, | |
275 | 0, yres); | |
276 | } | |
277 | vd->mouse_display = qemu_console_get_index(src); | |
278 | break; | |
279 | ||
280 | case INPUT_EVENT_KIND_BTN: | |
281 | btn = evt->u.btn.data; | |
282 | if (btn->down) { | |
283 | vd->mouse_btn |= bmap[btn->button]; | |
284 | } else { | |
285 | vd->mouse_btn &= ~bmap[btn->button]; | |
286 | } | |
287 | break; | |
288 | ||
289 | default: | |
290 | /* keep gcc happy */ | |
291 | break; | |
292 | } | |
293 | } | |
294 | ||
295 | static void vdagent_pointer_sync(DeviceState *dev) | |
296 | { | |
297 | VDAgentChardev *vd = container_of(dev, struct VDAgentChardev, mouse_dev); | |
298 | ||
299 | if (vd->caps & (1 << VD_AGENT_CAP_MOUSE_STATE)) { | |
300 | vdagent_send_mouse(vd); | |
301 | } | |
302 | } | |
303 | ||
304 | static QemuInputHandler vdagent_mouse_handler = { | |
305 | .name = "vdagent mouse", | |
306 | .mask = INPUT_EVENT_MASK_BTN | INPUT_EVENT_MASK_ABS, | |
307 | .event = vdagent_pointer_event, | |
308 | .sync = vdagent_pointer_sync, | |
309 | }; | |
310 | ||
f0349f4d GH |
311 | /* ------------------------------------------------------------------ */ |
312 | /* clipboard */ | |
313 | ||
314 | static bool have_clipboard(VDAgentChardev *vd) | |
315 | { | |
316 | return vd->clipboard && | |
317 | (vd->caps & (1 << VD_AGENT_CAP_CLIPBOARD_BY_DEMAND)); | |
318 | } | |
319 | ||
320 | static bool have_selection(VDAgentChardev *vd) | |
321 | { | |
322 | return vd->caps & (1 << VD_AGENT_CAP_CLIPBOARD_SELECTION); | |
323 | } | |
324 | ||
325 | static uint32_t type_qemu_to_vdagent(enum QemuClipboardType type) | |
326 | { | |
327 | switch (type) { | |
328 | case QEMU_CLIPBOARD_TYPE_TEXT: | |
329 | return VD_AGENT_CLIPBOARD_UTF8_TEXT; | |
330 | default: | |
331 | return VD_AGENT_CLIPBOARD_NONE; | |
332 | } | |
333 | } | |
334 | ||
335 | static void vdagent_send_clipboard_grab(VDAgentChardev *vd, | |
336 | QemuClipboardInfo *info) | |
337 | { | |
338 | g_autofree VDAgentMessage *msg = | |
339 | g_malloc0(sizeof(VDAgentMessage) + | |
835f69f4 MAL |
340 | sizeof(uint32_t) * (QEMU_CLIPBOARD_TYPE__COUNT + 1) + |
341 | sizeof(uint32_t)); | |
f0349f4d GH |
342 | uint8_t *s = msg->data; |
343 | uint32_t *data = (uint32_t *)msg->data; | |
344 | uint32_t q, type; | |
345 | ||
346 | if (have_selection(vd)) { | |
347 | *s = info->selection; | |
348 | data++; | |
349 | msg->size += sizeof(uint32_t); | |
350 | } else if (info->selection != QEMU_CLIPBOARD_SELECTION_CLIPBOARD) { | |
351 | return; | |
352 | } | |
353 | ||
835f69f4 MAL |
354 | #if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 1) |
355 | if (vd->caps & (1 << VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL)) { | |
356 | if (!info->has_serial) { | |
357 | /* client should win */ | |
358 | info->serial = vd->last_serial[info->selection]++; | |
359 | info->has_serial = true; | |
360 | } | |
361 | *data = info->serial; | |
362 | data++; | |
363 | msg->size += sizeof(uint32_t); | |
364 | } | |
365 | #endif | |
366 | ||
f0349f4d GH |
367 | for (q = 0; q < QEMU_CLIPBOARD_TYPE__COUNT; q++) { |
368 | type = type_qemu_to_vdagent(q); | |
369 | if (type != VD_AGENT_CLIPBOARD_NONE && info->types[q].available) { | |
370 | *data = type; | |
371 | data++; | |
372 | msg->size += sizeof(uint32_t); | |
373 | } | |
374 | } | |
375 | ||
376 | msg->type = VD_AGENT_CLIPBOARD_GRAB; | |
377 | vdagent_send_msg(vd, msg); | |
378 | } | |
379 | ||
314bf500 MAL |
380 | static void vdagent_send_clipboard_release(VDAgentChardev *vd, |
381 | QemuClipboardInfo *info) | |
382 | { | |
383 | g_autofree VDAgentMessage *msg = g_malloc0(sizeof(VDAgentMessage) + | |
384 | sizeof(uint32_t)); | |
385 | ||
386 | if (have_selection(vd)) { | |
387 | uint8_t *s = msg->data; | |
388 | *s = info->selection; | |
389 | msg->size += sizeof(uint32_t); | |
390 | } else if (info->selection != QEMU_CLIPBOARD_SELECTION_CLIPBOARD) { | |
391 | return; | |
392 | } | |
393 | ||
394 | msg->type = VD_AGENT_CLIPBOARD_RELEASE; | |
395 | vdagent_send_msg(vd, msg); | |
396 | } | |
397 | ||
f0349f4d GH |
398 | static void vdagent_send_clipboard_data(VDAgentChardev *vd, |
399 | QemuClipboardInfo *info, | |
400 | QemuClipboardType type) | |
401 | { | |
402 | g_autofree VDAgentMessage *msg = g_malloc0(sizeof(VDAgentMessage) + | |
403 | sizeof(uint32_t) * 2 + | |
404 | info->types[type].size); | |
405 | ||
406 | uint8_t *s = msg->data; | |
407 | uint32_t *data = (uint32_t *)msg->data; | |
408 | ||
409 | if (have_selection(vd)) { | |
410 | *s = info->selection; | |
411 | data++; | |
412 | msg->size += sizeof(uint32_t); | |
413 | } else if (info->selection != QEMU_CLIPBOARD_SELECTION_CLIPBOARD) { | |
414 | return; | |
415 | } | |
416 | ||
417 | *data = type_qemu_to_vdagent(type); | |
418 | data++; | |
419 | msg->size += sizeof(uint32_t); | |
420 | ||
421 | memcpy(data, info->types[type].data, info->types[type].size); | |
422 | msg->size += info->types[type].size; | |
423 | ||
424 | msg->type = VD_AGENT_CLIPBOARD; | |
425 | vdagent_send_msg(vd, msg); | |
426 | } | |
427 | ||
3d3f0bc3 MAL |
428 | static void vdagent_send_empty_clipboard_data(VDAgentChardev *vd, |
429 | QemuClipboardSelection selection, | |
430 | QemuClipboardType type) | |
431 | { | |
432 | g_autoptr(QemuClipboardInfo) info = qemu_clipboard_info_new(&vd->cbpeer, selection); | |
433 | ||
434 | trace_vdagent_send_empty_clipboard(); | |
435 | vdagent_send_clipboard_data(vd, info, type); | |
436 | } | |
437 | ||
1b17f1e9 MAL |
438 | static void vdagent_clipboard_update_info(VDAgentChardev *vd, |
439 | QemuClipboardInfo *info) | |
f0349f4d | 440 | { |
f0349f4d GH |
441 | QemuClipboardSelection s = info->selection; |
442 | QemuClipboardType type; | |
443 | bool self_update = info->owner == &vd->cbpeer; | |
444 | ||
d2ed2c01 | 445 | if (info != qemu_clipboard_info(s)) { |
f0349f4d GH |
446 | vd->cbpending[s] = 0; |
447 | if (!self_update) { | |
314bf500 MAL |
448 | if (info->owner) { |
449 | vdagent_send_clipboard_grab(vd, info); | |
450 | } else { | |
451 | vdagent_send_clipboard_release(vd, info); | |
452 | } | |
f0349f4d GH |
453 | } |
454 | return; | |
455 | } | |
456 | ||
457 | if (self_update) { | |
458 | return; | |
459 | } | |
460 | ||
461 | for (type = 0; type < QEMU_CLIPBOARD_TYPE__COUNT; type++) { | |
462 | if (vd->cbpending[s] & (1 << type)) { | |
463 | vd->cbpending[s] &= ~(1 << type); | |
464 | vdagent_send_clipboard_data(vd, info, type); | |
465 | } | |
466 | } | |
467 | } | |
468 | ||
505dbf9b MAL |
469 | static void vdagent_clipboard_reset_serial(VDAgentChardev *vd) |
470 | { | |
471 | Chardev *chr = CHARDEV(vd); | |
472 | ||
473 | /* reopen the agent connection to reset the serial state */ | |
474 | qemu_chr_be_event(chr, CHR_EVENT_CLOSED); | |
475 | qemu_chr_be_event(chr, CHR_EVENT_OPENED); | |
476 | } | |
477 | ||
1b17f1e9 MAL |
478 | static void vdagent_clipboard_notify(Notifier *notifier, void *data) |
479 | { | |
480 | VDAgentChardev *vd = | |
481 | container_of(notifier, VDAgentChardev, cbpeer.notifier); | |
482 | QemuClipboardNotify *notify = data; | |
483 | ||
484 | switch (notify->type) { | |
485 | case QEMU_CLIPBOARD_UPDATE_INFO: | |
486 | vdagent_clipboard_update_info(vd, notify->info); | |
487 | return; | |
505dbf9b MAL |
488 | case QEMU_CLIPBOARD_RESET_SERIAL: |
489 | vdagent_clipboard_reset_serial(vd); | |
490 | return; | |
1b17f1e9 MAL |
491 | } |
492 | } | |
493 | ||
f0349f4d GH |
494 | static void vdagent_clipboard_request(QemuClipboardInfo *info, |
495 | QemuClipboardType qtype) | |
496 | { | |
497 | VDAgentChardev *vd = container_of(info->owner, VDAgentChardev, cbpeer); | |
498 | g_autofree VDAgentMessage *msg = g_malloc0(sizeof(VDAgentMessage) + | |
499 | sizeof(uint32_t) * 2); | |
500 | uint32_t type = type_qemu_to_vdagent(qtype); | |
501 | uint8_t *s = msg->data; | |
502 | uint32_t *data = (uint32_t *)msg->data; | |
503 | ||
504 | if (type == VD_AGENT_CLIPBOARD_NONE) { | |
505 | return; | |
506 | } | |
507 | ||
508 | if (have_selection(vd)) { | |
509 | *s = info->selection; | |
510 | data++; | |
511 | msg->size += sizeof(uint32_t); | |
512 | } | |
513 | ||
514 | *data = type; | |
515 | msg->size += sizeof(uint32_t); | |
516 | ||
517 | msg->type = VD_AGENT_CLIPBOARD_REQUEST; | |
518 | vdagent_send_msg(vd, msg); | |
519 | } | |
520 | ||
3b99bb4c MAL |
521 | static void vdagent_clipboard_recv_grab(VDAgentChardev *vd, uint8_t s, uint32_t size, void *data) |
522 | { | |
523 | g_autoptr(QemuClipboardInfo) info = NULL; | |
524 | ||
525 | trace_vdagent_cb_grab_selection(GET_NAME(sel_name, s)); | |
526 | info = qemu_clipboard_info_new(&vd->cbpeer, s); | |
835f69f4 MAL |
527 | #if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 1) |
528 | if (vd->caps & (1 << VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL)) { | |
529 | if (size < sizeof(uint32_t)) { | |
530 | /* this shouldn't happen! */ | |
531 | return; | |
532 | } | |
533 | ||
534 | info->has_serial = true; | |
535 | info->serial = *(uint32_t *)data; | |
536 | if (info->serial < vd->last_serial[s]) { | |
537 | /* discard lower-ordering guest grab */ | |
538 | return; | |
539 | } | |
540 | vd->last_serial[s] = info->serial; | |
541 | data += sizeof(uint32_t); | |
542 | size -= sizeof(uint32_t); | |
543 | } | |
544 | #endif | |
3b99bb4c MAL |
545 | if (size > sizeof(uint32_t) * 10) { |
546 | /* | |
547 | * spice has 6 types as of 2021. Limiting to 10 entries | |
548 | * so we we have some wiggle room. | |
549 | */ | |
550 | return; | |
551 | } | |
552 | while (size >= sizeof(uint32_t)) { | |
553 | trace_vdagent_cb_grab_type(GET_NAME(type_name, *(uint32_t *)data)); | |
554 | switch (*(uint32_t *)data) { | |
555 | case VD_AGENT_CLIPBOARD_UTF8_TEXT: | |
556 | info->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true; | |
557 | break; | |
558 | default: | |
559 | break; | |
560 | } | |
561 | data += sizeof(uint32_t); | |
562 | size -= sizeof(uint32_t); | |
563 | } | |
564 | qemu_clipboard_update(info); | |
565 | } | |
566 | ||
567 | static void vdagent_clipboard_recv_request(VDAgentChardev *vd, uint8_t s, uint32_t size, void *data) | |
568 | { | |
569 | QemuClipboardType type; | |
d2ed2c01 | 570 | QemuClipboardInfo *info; |
3b99bb4c MAL |
571 | |
572 | if (size < sizeof(uint32_t)) { | |
573 | return; | |
574 | } | |
575 | switch (*(uint32_t *)data) { | |
576 | case VD_AGENT_CLIPBOARD_UTF8_TEXT: | |
577 | type = QEMU_CLIPBOARD_TYPE_TEXT; | |
578 | break; | |
579 | default: | |
580 | return; | |
581 | } | |
d2ed2c01 MAL |
582 | |
583 | info = qemu_clipboard_info(s); | |
584 | if (info && info->types[type].available && info->owner != &vd->cbpeer) { | |
585 | if (info->types[type].data) { | |
586 | vdagent_send_clipboard_data(vd, info, type); | |
3b99bb4c MAL |
587 | } else { |
588 | vd->cbpending[s] |= (1 << type); | |
d2ed2c01 | 589 | qemu_clipboard_request(info, type); |
3b99bb4c | 590 | } |
3d3f0bc3 MAL |
591 | } else { |
592 | vdagent_send_empty_clipboard_data(vd, s, type); | |
3b99bb4c MAL |
593 | } |
594 | } | |
595 | ||
596 | static void vdagent_clipboard_recv_data(VDAgentChardev *vd, uint8_t s, uint32_t size, void *data) | |
597 | { | |
598 | QemuClipboardType type; | |
599 | ||
600 | if (size < sizeof(uint32_t)) { | |
601 | return; | |
602 | } | |
603 | switch (*(uint32_t *)data) { | |
604 | case VD_AGENT_CLIPBOARD_UTF8_TEXT: | |
605 | type = QEMU_CLIPBOARD_TYPE_TEXT; | |
606 | break; | |
607 | default: | |
608 | return; | |
609 | } | |
610 | data += 4; | |
611 | size -= 4; | |
d2ed2c01 MAL |
612 | |
613 | if (qemu_clipboard_peer_owns(&vd->cbpeer, s)) { | |
614 | qemu_clipboard_set_data(&vd->cbpeer, qemu_clipboard_info(s), | |
615 | type, size, data, true); | |
616 | } | |
3b99bb4c MAL |
617 | } |
618 | ||
619 | static void vdagent_clipboard_recv_release(VDAgentChardev *vd, uint8_t s) | |
620 | { | |
c98c50de | 621 | qemu_clipboard_peer_release(&vd->cbpeer, s); |
3b99bb4c MAL |
622 | } |
623 | ||
f0349f4d GH |
624 | static void vdagent_chr_recv_clipboard(VDAgentChardev *vd, VDAgentMessage *msg) |
625 | { | |
626 | uint8_t s = VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD; | |
627 | uint32_t size = msg->size; | |
628 | void *data = msg->data; | |
f0349f4d GH |
629 | |
630 | if (have_selection(vd)) { | |
631 | if (size < 4) { | |
632 | return; | |
633 | } | |
634 | s = *(uint8_t *)data; | |
635 | if (s >= QEMU_CLIPBOARD_SELECTION__COUNT) { | |
636 | return; | |
637 | } | |
638 | data += 4; | |
639 | size -= 4; | |
640 | } | |
641 | ||
642 | switch (msg->type) { | |
643 | case VD_AGENT_CLIPBOARD_GRAB: | |
3b99bb4c | 644 | return vdagent_clipboard_recv_grab(vd, s, size, data); |
f0349f4d | 645 | case VD_AGENT_CLIPBOARD_REQUEST: |
3b99bb4c | 646 | return vdagent_clipboard_recv_request(vd, s, size, data); |
f0349f4d | 647 | case VD_AGENT_CLIPBOARD: /* data */ |
3b99bb4c | 648 | return vdagent_clipboard_recv_data(vd, s, size, data); |
e7c55746 | 649 | case VD_AGENT_CLIPBOARD_RELEASE: |
3b99bb4c MAL |
650 | return vdagent_clipboard_recv_release(vd, s); |
651 | default: | |
652 | g_assert_not_reached(); | |
f0349f4d GH |
653 | } |
654 | } | |
655 | ||
de74a22c GH |
656 | /* ------------------------------------------------------------------ */ |
657 | /* chardev backend */ | |
658 | ||
659 | static void vdagent_chr_open(Chardev *chr, | |
660 | ChardevBackend *backend, | |
661 | bool *be_opened, | |
662 | Error **errp) | |
663 | { | |
56081919 GH |
664 | VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(chr); |
665 | ChardevQemuVDAgent *cfg = backend->u.qemu_vdagent.data; | |
666 | ||
de74a22c GH |
667 | #if defined(HOST_WORDS_BIGENDIAN) |
668 | /* | |
669 | * TODO: vdagent protocol is defined to be LE, | |
670 | * so we have to byteswap everything on BE hosts. | |
671 | */ | |
672 | error_setg(errp, "vdagent is not supported on bigendian hosts"); | |
673 | return; | |
674 | #endif | |
675 | ||
90208bc9 MAL |
676 | if (migrate_add_blocker(vd->migration_blocker, errp) != 0) { |
677 | return; | |
678 | } | |
679 | ||
56081919 GH |
680 | vd->mouse = VDAGENT_MOUSE_DEFAULT; |
681 | if (cfg->has_mouse) { | |
682 | vd->mouse = cfg->mouse; | |
683 | } | |
684 | ||
f0349f4d GH |
685 | vd->clipboard = VDAGENT_CLIPBOARD_DEFAULT; |
686 | if (cfg->has_clipboard) { | |
687 | vd->clipboard = cfg->clipboard; | |
688 | } | |
689 | ||
56081919 GH |
690 | if (vd->mouse) { |
691 | vd->mouse_hs = qemu_input_handler_register(&vd->mouse_dev, | |
692 | &vdagent_mouse_handler); | |
693 | } | |
694 | ||
de74a22c GH |
695 | *be_opened = true; |
696 | } | |
697 | ||
698 | static void vdagent_chr_recv_caps(VDAgentChardev *vd, VDAgentMessage *msg) | |
699 | { | |
700 | VDAgentAnnounceCapabilities *caps = (void *)msg->data; | |
701 | int i; | |
702 | ||
703 | if (msg->size < (sizeof(VDAgentAnnounceCapabilities) + | |
704 | sizeof(uint32_t))) { | |
705 | return; | |
706 | } | |
707 | ||
708 | for (i = 0; i < ARRAY_SIZE(cap_name); i++) { | |
709 | if (caps->caps[0] & (1 << i)) { | |
710 | trace_vdagent_peer_cap(GET_NAME(cap_name, i)); | |
711 | } | |
712 | } | |
713 | ||
714 | vd->caps = caps->caps[0]; | |
715 | if (caps->request) { | |
716 | vdagent_send_caps(vd); | |
717 | } | |
56081919 GH |
718 | if (have_mouse(vd) && vd->mouse_hs) { |
719 | qemu_input_handler_activate(vd->mouse_hs); | |
720 | } | |
1b17f1e9 | 721 | if (have_clipboard(vd) && vd->cbpeer.notifier.notify == NULL) { |
835f69f4 | 722 | memset(vd->last_serial, 0, sizeof(vd->last_serial)); |
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 | { | |
5fb2e8d9 | 857 | buffer_reset(&vd->outbuf); |
5e0a24e8 MAL |
858 | vdagent_reset_bufs(vd); |
859 | vd->caps = 0; | |
860 | if (vd->mouse_hs) { | |
861 | qemu_input_handler_deactivate(vd->mouse_hs); | |
862 | } | |
1b17f1e9 | 863 | if (vd->cbpeer.notifier.notify) { |
5e0a24e8 MAL |
864 | qemu_clipboard_peer_unregister(&vd->cbpeer); |
865 | memset(&vd->cbpeer, 0, sizeof(vd->cbpeer)); | |
866 | } | |
867 | } | |
868 | ||
de74a22c GH |
869 | static void vdagent_chr_set_fe_open(struct Chardev *chr, int fe_open) |
870 | { | |
de74a22c GH |
871 | if (!fe_open) { |
872 | trace_vdagent_close(); | |
de74a22c GH |
873 | return; |
874 | } | |
875 | ||
876 | trace_vdagent_open(); | |
877 | } | |
878 | ||
56081919 GH |
879 | static void vdagent_chr_parse(QemuOpts *opts, ChardevBackend *backend, |
880 | Error **errp) | |
881 | { | |
882 | ChardevQemuVDAgent *cfg; | |
883 | ||
884 | backend->type = CHARDEV_BACKEND_KIND_QEMU_VDAGENT; | |
885 | cfg = backend->u.qemu_vdagent.data = g_new0(ChardevQemuVDAgent, 1); | |
886 | qemu_chr_parse_common(opts, qapi_ChardevQemuVDAgent_base(cfg)); | |
887 | cfg->has_mouse = true; | |
888 | cfg->mouse = qemu_opt_get_bool(opts, "mouse", VDAGENT_MOUSE_DEFAULT); | |
f0349f4d GH |
889 | cfg->has_clipboard = true; |
890 | cfg->clipboard = qemu_opt_get_bool(opts, "clipboard", VDAGENT_CLIPBOARD_DEFAULT); | |
56081919 GH |
891 | } |
892 | ||
de74a22c GH |
893 | /* ------------------------------------------------------------------ */ |
894 | ||
895 | static void vdagent_chr_class_init(ObjectClass *oc, void *data) | |
896 | { | |
897 | ChardevClass *cc = CHARDEV_CLASS(oc); | |
898 | ||
56081919 | 899 | cc->parse = vdagent_chr_parse; |
de74a22c GH |
900 | cc->open = vdagent_chr_open; |
901 | cc->chr_write = vdagent_chr_write; | |
902 | cc->chr_set_fe_open = vdagent_chr_set_fe_open; | |
903 | cc->chr_accept_input = vdagent_chr_accept_input; | |
904 | } | |
905 | ||
906 | static void vdagent_chr_init(Object *obj) | |
907 | { | |
908 | VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(obj); | |
909 | ||
910 | buffer_init(&vd->outbuf, "vdagent-outbuf"); | |
90208bc9 MAL |
911 | error_setg(&vd->migration_blocker, |
912 | "The vdagent chardev doesn't yet support migration"); | |
de74a22c GH |
913 | } |
914 | ||
915 | static void vdagent_chr_fini(Object *obj) | |
916 | { | |
917 | VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(obj); | |
918 | ||
90208bc9 | 919 | migrate_del_blocker(vd->migration_blocker); |
5e0a24e8 | 920 | vdagent_disconnect(vd); |
de74a22c | 921 | buffer_free(&vd->outbuf); |
90208bc9 | 922 | error_free(vd->migration_blocker); |
de74a22c GH |
923 | } |
924 | ||
925 | static const TypeInfo vdagent_chr_type_info = { | |
926 | .name = TYPE_CHARDEV_QEMU_VDAGENT, | |
927 | .parent = TYPE_CHARDEV, | |
928 | .instance_size = sizeof(VDAgentChardev), | |
929 | .instance_init = vdagent_chr_init, | |
930 | .instance_finalize = vdagent_chr_fini, | |
931 | .class_init = vdagent_chr_class_init, | |
932 | }; | |
933 | ||
934 | static void register_types(void) | |
935 | { | |
936 | type_register_static(&vdagent_chr_type_info); | |
937 | } | |
938 | ||
939 | type_init(register_types); |