]>
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 | ||
1b17f1e9 MAL |
469 | static void vdagent_clipboard_notify(Notifier *notifier, void *data) |
470 | { | |
471 | VDAgentChardev *vd = | |
472 | container_of(notifier, VDAgentChardev, cbpeer.notifier); | |
473 | QemuClipboardNotify *notify = data; | |
474 | ||
475 | switch (notify->type) { | |
476 | case QEMU_CLIPBOARD_UPDATE_INFO: | |
477 | vdagent_clipboard_update_info(vd, notify->info); | |
478 | return; | |
479 | } | |
480 | } | |
481 | ||
f0349f4d GH |
482 | static void vdagent_clipboard_request(QemuClipboardInfo *info, |
483 | QemuClipboardType qtype) | |
484 | { | |
485 | VDAgentChardev *vd = container_of(info->owner, VDAgentChardev, cbpeer); | |
486 | g_autofree VDAgentMessage *msg = g_malloc0(sizeof(VDAgentMessage) + | |
487 | sizeof(uint32_t) * 2); | |
488 | uint32_t type = type_qemu_to_vdagent(qtype); | |
489 | uint8_t *s = msg->data; | |
490 | uint32_t *data = (uint32_t *)msg->data; | |
491 | ||
492 | if (type == VD_AGENT_CLIPBOARD_NONE) { | |
493 | return; | |
494 | } | |
495 | ||
496 | if (have_selection(vd)) { | |
497 | *s = info->selection; | |
498 | data++; | |
499 | msg->size += sizeof(uint32_t); | |
500 | } | |
501 | ||
502 | *data = type; | |
503 | msg->size += sizeof(uint32_t); | |
504 | ||
505 | msg->type = VD_AGENT_CLIPBOARD_REQUEST; | |
506 | vdagent_send_msg(vd, msg); | |
507 | } | |
508 | ||
3b99bb4c MAL |
509 | static void vdagent_clipboard_recv_grab(VDAgentChardev *vd, uint8_t s, uint32_t size, void *data) |
510 | { | |
511 | g_autoptr(QemuClipboardInfo) info = NULL; | |
512 | ||
513 | trace_vdagent_cb_grab_selection(GET_NAME(sel_name, s)); | |
514 | info = qemu_clipboard_info_new(&vd->cbpeer, s); | |
835f69f4 MAL |
515 | #if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 1) |
516 | if (vd->caps & (1 << VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL)) { | |
517 | if (size < sizeof(uint32_t)) { | |
518 | /* this shouldn't happen! */ | |
519 | return; | |
520 | } | |
521 | ||
522 | info->has_serial = true; | |
523 | info->serial = *(uint32_t *)data; | |
524 | if (info->serial < vd->last_serial[s]) { | |
525 | /* discard lower-ordering guest grab */ | |
526 | return; | |
527 | } | |
528 | vd->last_serial[s] = info->serial; | |
529 | data += sizeof(uint32_t); | |
530 | size -= sizeof(uint32_t); | |
531 | } | |
532 | #endif | |
3b99bb4c MAL |
533 | if (size > sizeof(uint32_t) * 10) { |
534 | /* | |
535 | * spice has 6 types as of 2021. Limiting to 10 entries | |
536 | * so we we have some wiggle room. | |
537 | */ | |
538 | return; | |
539 | } | |
540 | while (size >= sizeof(uint32_t)) { | |
541 | trace_vdagent_cb_grab_type(GET_NAME(type_name, *(uint32_t *)data)); | |
542 | switch (*(uint32_t *)data) { | |
543 | case VD_AGENT_CLIPBOARD_UTF8_TEXT: | |
544 | info->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true; | |
545 | break; | |
546 | default: | |
547 | break; | |
548 | } | |
549 | data += sizeof(uint32_t); | |
550 | size -= sizeof(uint32_t); | |
551 | } | |
552 | qemu_clipboard_update(info); | |
553 | } | |
554 | ||
555 | static void vdagent_clipboard_recv_request(VDAgentChardev *vd, uint8_t s, uint32_t size, void *data) | |
556 | { | |
557 | QemuClipboardType type; | |
d2ed2c01 | 558 | QemuClipboardInfo *info; |
3b99bb4c MAL |
559 | |
560 | if (size < sizeof(uint32_t)) { | |
561 | return; | |
562 | } | |
563 | switch (*(uint32_t *)data) { | |
564 | case VD_AGENT_CLIPBOARD_UTF8_TEXT: | |
565 | type = QEMU_CLIPBOARD_TYPE_TEXT; | |
566 | break; | |
567 | default: | |
568 | return; | |
569 | } | |
d2ed2c01 MAL |
570 | |
571 | info = qemu_clipboard_info(s); | |
572 | if (info && info->types[type].available && info->owner != &vd->cbpeer) { | |
573 | if (info->types[type].data) { | |
574 | vdagent_send_clipboard_data(vd, info, type); | |
3b99bb4c MAL |
575 | } else { |
576 | vd->cbpending[s] |= (1 << type); | |
d2ed2c01 | 577 | qemu_clipboard_request(info, type); |
3b99bb4c | 578 | } |
3d3f0bc3 MAL |
579 | } else { |
580 | vdagent_send_empty_clipboard_data(vd, s, type); | |
3b99bb4c MAL |
581 | } |
582 | } | |
583 | ||
584 | static void vdagent_clipboard_recv_data(VDAgentChardev *vd, uint8_t s, uint32_t size, void *data) | |
585 | { | |
586 | QemuClipboardType type; | |
587 | ||
588 | if (size < sizeof(uint32_t)) { | |
589 | return; | |
590 | } | |
591 | switch (*(uint32_t *)data) { | |
592 | case VD_AGENT_CLIPBOARD_UTF8_TEXT: | |
593 | type = QEMU_CLIPBOARD_TYPE_TEXT; | |
594 | break; | |
595 | default: | |
596 | return; | |
597 | } | |
598 | data += 4; | |
599 | size -= 4; | |
d2ed2c01 MAL |
600 | |
601 | if (qemu_clipboard_peer_owns(&vd->cbpeer, s)) { | |
602 | qemu_clipboard_set_data(&vd->cbpeer, qemu_clipboard_info(s), | |
603 | type, size, data, true); | |
604 | } | |
3b99bb4c MAL |
605 | } |
606 | ||
607 | static void vdagent_clipboard_recv_release(VDAgentChardev *vd, uint8_t s) | |
608 | { | |
c98c50de | 609 | qemu_clipboard_peer_release(&vd->cbpeer, s); |
3b99bb4c MAL |
610 | } |
611 | ||
f0349f4d GH |
612 | static void vdagent_chr_recv_clipboard(VDAgentChardev *vd, VDAgentMessage *msg) |
613 | { | |
614 | uint8_t s = VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD; | |
615 | uint32_t size = msg->size; | |
616 | void *data = msg->data; | |
f0349f4d GH |
617 | |
618 | if (have_selection(vd)) { | |
619 | if (size < 4) { | |
620 | return; | |
621 | } | |
622 | s = *(uint8_t *)data; | |
623 | if (s >= QEMU_CLIPBOARD_SELECTION__COUNT) { | |
624 | return; | |
625 | } | |
626 | data += 4; | |
627 | size -= 4; | |
628 | } | |
629 | ||
630 | switch (msg->type) { | |
631 | case VD_AGENT_CLIPBOARD_GRAB: | |
3b99bb4c | 632 | return vdagent_clipboard_recv_grab(vd, s, size, data); |
f0349f4d | 633 | case VD_AGENT_CLIPBOARD_REQUEST: |
3b99bb4c | 634 | return vdagent_clipboard_recv_request(vd, s, size, data); |
f0349f4d | 635 | case VD_AGENT_CLIPBOARD: /* data */ |
3b99bb4c | 636 | return vdagent_clipboard_recv_data(vd, s, size, data); |
e7c55746 | 637 | case VD_AGENT_CLIPBOARD_RELEASE: |
3b99bb4c MAL |
638 | return vdagent_clipboard_recv_release(vd, s); |
639 | default: | |
640 | g_assert_not_reached(); | |
f0349f4d GH |
641 | } |
642 | } | |
643 | ||
de74a22c GH |
644 | /* ------------------------------------------------------------------ */ |
645 | /* chardev backend */ | |
646 | ||
647 | static void vdagent_chr_open(Chardev *chr, | |
648 | ChardevBackend *backend, | |
649 | bool *be_opened, | |
650 | Error **errp) | |
651 | { | |
56081919 GH |
652 | VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(chr); |
653 | ChardevQemuVDAgent *cfg = backend->u.qemu_vdagent.data; | |
654 | ||
de74a22c GH |
655 | #if defined(HOST_WORDS_BIGENDIAN) |
656 | /* | |
657 | * TODO: vdagent protocol is defined to be LE, | |
658 | * so we have to byteswap everything on BE hosts. | |
659 | */ | |
660 | error_setg(errp, "vdagent is not supported on bigendian hosts"); | |
661 | return; | |
662 | #endif | |
663 | ||
90208bc9 MAL |
664 | if (migrate_add_blocker(vd->migration_blocker, errp) != 0) { |
665 | return; | |
666 | } | |
667 | ||
56081919 GH |
668 | vd->mouse = VDAGENT_MOUSE_DEFAULT; |
669 | if (cfg->has_mouse) { | |
670 | vd->mouse = cfg->mouse; | |
671 | } | |
672 | ||
f0349f4d GH |
673 | vd->clipboard = VDAGENT_CLIPBOARD_DEFAULT; |
674 | if (cfg->has_clipboard) { | |
675 | vd->clipboard = cfg->clipboard; | |
676 | } | |
677 | ||
56081919 GH |
678 | if (vd->mouse) { |
679 | vd->mouse_hs = qemu_input_handler_register(&vd->mouse_dev, | |
680 | &vdagent_mouse_handler); | |
681 | } | |
682 | ||
de74a22c GH |
683 | *be_opened = true; |
684 | } | |
685 | ||
686 | static void vdagent_chr_recv_caps(VDAgentChardev *vd, VDAgentMessage *msg) | |
687 | { | |
688 | VDAgentAnnounceCapabilities *caps = (void *)msg->data; | |
689 | int i; | |
690 | ||
691 | if (msg->size < (sizeof(VDAgentAnnounceCapabilities) + | |
692 | sizeof(uint32_t))) { | |
693 | return; | |
694 | } | |
695 | ||
696 | for (i = 0; i < ARRAY_SIZE(cap_name); i++) { | |
697 | if (caps->caps[0] & (1 << i)) { | |
698 | trace_vdagent_peer_cap(GET_NAME(cap_name, i)); | |
699 | } | |
700 | } | |
701 | ||
702 | vd->caps = caps->caps[0]; | |
703 | if (caps->request) { | |
704 | vdagent_send_caps(vd); | |
705 | } | |
56081919 GH |
706 | if (have_mouse(vd) && vd->mouse_hs) { |
707 | qemu_input_handler_activate(vd->mouse_hs); | |
708 | } | |
1b17f1e9 | 709 | if (have_clipboard(vd) && vd->cbpeer.notifier.notify == NULL) { |
835f69f4 | 710 | memset(vd->last_serial, 0, sizeof(vd->last_serial)); |
f0349f4d | 711 | vd->cbpeer.name = "vdagent"; |
1b17f1e9 | 712 | vd->cbpeer.notifier.notify = vdagent_clipboard_notify; |
f0349f4d GH |
713 | vd->cbpeer.request = vdagent_clipboard_request; |
714 | qemu_clipboard_peer_register(&vd->cbpeer); | |
715 | } | |
de74a22c GH |
716 | } |
717 | ||
718 | static void vdagent_chr_recv_msg(VDAgentChardev *vd, VDAgentMessage *msg) | |
719 | { | |
720 | trace_vdagent_recv_msg(GET_NAME(msg_name, msg->type), msg->size); | |
721 | ||
722 | switch (msg->type) { | |
723 | case VD_AGENT_ANNOUNCE_CAPABILITIES: | |
724 | vdagent_chr_recv_caps(vd, msg); | |
725 | break; | |
f0349f4d GH |
726 | case VD_AGENT_CLIPBOARD: |
727 | case VD_AGENT_CLIPBOARD_GRAB: | |
728 | case VD_AGENT_CLIPBOARD_REQUEST: | |
729 | case VD_AGENT_CLIPBOARD_RELEASE: | |
730 | if (have_clipboard(vd)) { | |
731 | vdagent_chr_recv_clipboard(vd, msg); | |
732 | } | |
733 | break; | |
de74a22c GH |
734 | default: |
735 | break; | |
736 | } | |
737 | } | |
738 | ||
739 | static void vdagent_reset_xbuf(VDAgentChardev *vd) | |
740 | { | |
741 | g_clear_pointer(&vd->xbuf, g_free); | |
742 | vd->xoff = 0; | |
743 | vd->xsize = 0; | |
744 | } | |
745 | ||
746 | static void vdagent_chr_recv_chunk(VDAgentChardev *vd) | |
747 | { | |
748 | VDAgentMessage *msg = (void *)vd->msgbuf; | |
749 | ||
750 | if (!vd->xsize) { | |
751 | if (vd->msgsize < sizeof(*msg)) { | |
752 | error_report("%s: message too small: %d < %zd", __func__, | |
753 | vd->msgsize, sizeof(*msg)); | |
754 | return; | |
755 | } | |
756 | if (vd->msgsize == msg->size + sizeof(*msg)) { | |
757 | vdagent_chr_recv_msg(vd, msg); | |
758 | return; | |
759 | } | |
760 | } | |
761 | ||
762 | if (!vd->xsize) { | |
763 | vd->xsize = msg->size + sizeof(*msg); | |
764 | vd->xbuf = g_malloc0(vd->xsize); | |
765 | } | |
766 | ||
767 | if (vd->xoff + vd->msgsize > vd->xsize) { | |
768 | error_report("%s: Oops: %d+%d > %d", __func__, | |
769 | vd->xoff, vd->msgsize, vd->xsize); | |
770 | vdagent_reset_xbuf(vd); | |
771 | return; | |
772 | } | |
773 | ||
774 | memcpy(vd->xbuf + vd->xoff, vd->msgbuf, vd->msgsize); | |
775 | vd->xoff += vd->msgsize; | |
776 | if (vd->xoff < vd->xsize) { | |
777 | return; | |
778 | } | |
779 | ||
780 | msg = (void *)vd->xbuf; | |
781 | vdagent_chr_recv_msg(vd, msg); | |
782 | vdagent_reset_xbuf(vd); | |
783 | } | |
784 | ||
785 | static void vdagent_reset_bufs(VDAgentChardev *vd) | |
786 | { | |
787 | memset(&vd->chunk, 0, sizeof(vd->chunk)); | |
788 | vd->chunksize = 0; | |
789 | g_free(vd->msgbuf); | |
790 | vd->msgbuf = NULL; | |
791 | vd->msgsize = 0; | |
792 | } | |
793 | ||
794 | static int vdagent_chr_write(Chardev *chr, const uint8_t *buf, int len) | |
795 | { | |
796 | VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(chr); | |
797 | uint32_t copy, ret = len; | |
798 | ||
799 | while (len) { | |
800 | if (vd->chunksize < sizeof(vd->chunk)) { | |
801 | copy = sizeof(vd->chunk) - vd->chunksize; | |
802 | if (copy > len) { | |
803 | copy = len; | |
804 | } | |
805 | memcpy((void *)(&vd->chunk) + vd->chunksize, buf, copy); | |
806 | vd->chunksize += copy; | |
807 | buf += copy; | |
808 | len -= copy; | |
809 | if (vd->chunksize < sizeof(vd->chunk)) { | |
810 | break; | |
811 | } | |
812 | ||
813 | assert(vd->msgbuf == NULL); | |
814 | vd->msgbuf = g_malloc0(vd->chunk.size); | |
815 | } | |
816 | ||
817 | copy = vd->chunk.size - vd->msgsize; | |
818 | if (copy > len) { | |
819 | copy = len; | |
820 | } | |
821 | memcpy(vd->msgbuf + vd->msgsize, buf, copy); | |
822 | vd->msgsize += copy; | |
823 | buf += copy; | |
824 | len -= copy; | |
825 | ||
826 | if (vd->msgsize == vd->chunk.size) { | |
827 | trace_vdagent_recv_chunk(vd->chunk.size); | |
828 | vdagent_chr_recv_chunk(vd); | |
829 | vdagent_reset_bufs(vd); | |
830 | } | |
831 | } | |
832 | ||
833 | return ret; | |
834 | } | |
835 | ||
836 | static void vdagent_chr_accept_input(Chardev *chr) | |
837 | { | |
838 | VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(chr); | |
839 | ||
840 | vdagent_send_buf(vd); | |
841 | } | |
842 | ||
5e0a24e8 MAL |
843 | static void vdagent_disconnect(VDAgentChardev *vd) |
844 | { | |
5fb2e8d9 | 845 | buffer_reset(&vd->outbuf); |
5e0a24e8 MAL |
846 | vdagent_reset_bufs(vd); |
847 | vd->caps = 0; | |
848 | if (vd->mouse_hs) { | |
849 | qemu_input_handler_deactivate(vd->mouse_hs); | |
850 | } | |
1b17f1e9 | 851 | if (vd->cbpeer.notifier.notify) { |
5e0a24e8 MAL |
852 | qemu_clipboard_peer_unregister(&vd->cbpeer); |
853 | memset(&vd->cbpeer, 0, sizeof(vd->cbpeer)); | |
854 | } | |
855 | } | |
856 | ||
de74a22c GH |
857 | static void vdagent_chr_set_fe_open(struct Chardev *chr, int fe_open) |
858 | { | |
de74a22c GH |
859 | if (!fe_open) { |
860 | trace_vdagent_close(); | |
de74a22c GH |
861 | return; |
862 | } | |
863 | ||
864 | trace_vdagent_open(); | |
865 | } | |
866 | ||
56081919 GH |
867 | static void vdagent_chr_parse(QemuOpts *opts, ChardevBackend *backend, |
868 | Error **errp) | |
869 | { | |
870 | ChardevQemuVDAgent *cfg; | |
871 | ||
872 | backend->type = CHARDEV_BACKEND_KIND_QEMU_VDAGENT; | |
873 | cfg = backend->u.qemu_vdagent.data = g_new0(ChardevQemuVDAgent, 1); | |
874 | qemu_chr_parse_common(opts, qapi_ChardevQemuVDAgent_base(cfg)); | |
875 | cfg->has_mouse = true; | |
876 | cfg->mouse = qemu_opt_get_bool(opts, "mouse", VDAGENT_MOUSE_DEFAULT); | |
f0349f4d GH |
877 | cfg->has_clipboard = true; |
878 | cfg->clipboard = qemu_opt_get_bool(opts, "clipboard", VDAGENT_CLIPBOARD_DEFAULT); | |
56081919 GH |
879 | } |
880 | ||
de74a22c GH |
881 | /* ------------------------------------------------------------------ */ |
882 | ||
883 | static void vdagent_chr_class_init(ObjectClass *oc, void *data) | |
884 | { | |
885 | ChardevClass *cc = CHARDEV_CLASS(oc); | |
886 | ||
56081919 | 887 | cc->parse = vdagent_chr_parse; |
de74a22c GH |
888 | cc->open = vdagent_chr_open; |
889 | cc->chr_write = vdagent_chr_write; | |
890 | cc->chr_set_fe_open = vdagent_chr_set_fe_open; | |
891 | cc->chr_accept_input = vdagent_chr_accept_input; | |
892 | } | |
893 | ||
894 | static void vdagent_chr_init(Object *obj) | |
895 | { | |
896 | VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(obj); | |
897 | ||
898 | buffer_init(&vd->outbuf, "vdagent-outbuf"); | |
90208bc9 MAL |
899 | error_setg(&vd->migration_blocker, |
900 | "The vdagent chardev doesn't yet support migration"); | |
de74a22c GH |
901 | } |
902 | ||
903 | static void vdagent_chr_fini(Object *obj) | |
904 | { | |
905 | VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(obj); | |
906 | ||
90208bc9 | 907 | migrate_del_blocker(vd->migration_blocker); |
5e0a24e8 | 908 | vdagent_disconnect(vd); |
de74a22c | 909 | buffer_free(&vd->outbuf); |
90208bc9 | 910 | error_free(vd->migration_blocker); |
de74a22c GH |
911 | } |
912 | ||
913 | static const TypeInfo vdagent_chr_type_info = { | |
914 | .name = TYPE_CHARDEV_QEMU_VDAGENT, | |
915 | .parent = TYPE_CHARDEV, | |
916 | .instance_size = sizeof(VDAgentChardev), | |
917 | .instance_init = vdagent_chr_init, | |
918 | .instance_finalize = vdagent_chr_fini, | |
919 | .class_init = vdagent_chr_class_init, | |
920 | }; | |
921 | ||
922 | static void register_types(void) | |
923 | { | |
924 | type_register_static(&vdagent_chr_type_info); | |
925 | } | |
926 | ||
927 | type_init(register_types); |