10bf41cabSGerd Hoffmann /*
20bf41cabSGerd Hoffmann * QEMU VNC display driver -- clipboard support
30bf41cabSGerd Hoffmann *
40bf41cabSGerd Hoffmann * Copyright (C) 2021 Gerd Hoffmann <kraxel@redhat.com>
50bf41cabSGerd Hoffmann *
60bf41cabSGerd Hoffmann * Permission is hereby granted, free of charge, to any person obtaining a copy
70bf41cabSGerd Hoffmann * of this software and associated documentation files (the "Software"), to deal
80bf41cabSGerd Hoffmann * in the Software without restriction, including without limitation the rights
90bf41cabSGerd Hoffmann * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
100bf41cabSGerd Hoffmann * copies of the Software, and to permit persons to whom the Software is
110bf41cabSGerd Hoffmann * furnished to do so, subject to the following conditions:
120bf41cabSGerd Hoffmann *
130bf41cabSGerd Hoffmann * The above copyright notice and this permission notice shall be included in
140bf41cabSGerd Hoffmann * all copies or substantial portions of the Software.
150bf41cabSGerd Hoffmann *
160bf41cabSGerd Hoffmann * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
170bf41cabSGerd Hoffmann * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
180bf41cabSGerd Hoffmann * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
190bf41cabSGerd Hoffmann * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
200bf41cabSGerd Hoffmann * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
210bf41cabSGerd Hoffmann * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
220bf41cabSGerd Hoffmann * THE SOFTWARE.
230bf41cabSGerd Hoffmann */
240bf41cabSGerd Hoffmann
250bf41cabSGerd Hoffmann #include "qemu/osdep.h"
260bf41cabSGerd Hoffmann #include "vnc.h"
270bf41cabSGerd Hoffmann #include "vnc-jobs.h"
280bf41cabSGerd Hoffmann
inflate_buffer(uint8_t * in,uint32_t in_len,uint32_t * size)290bf41cabSGerd Hoffmann static uint8_t *inflate_buffer(uint8_t *in, uint32_t in_len, uint32_t *size)
300bf41cabSGerd Hoffmann {
310bf41cabSGerd Hoffmann z_stream stream = {
320bf41cabSGerd Hoffmann .next_in = in,
330bf41cabSGerd Hoffmann .avail_in = in_len,
340bf41cabSGerd Hoffmann .zalloc = Z_NULL,
350bf41cabSGerd Hoffmann .zfree = Z_NULL,
360bf41cabSGerd Hoffmann };
370bf41cabSGerd Hoffmann uint32_t out_len = 8;
380bf41cabSGerd Hoffmann uint8_t *out = g_malloc(out_len);
390bf41cabSGerd Hoffmann int ret;
400bf41cabSGerd Hoffmann
410bf41cabSGerd Hoffmann stream.next_out = out + stream.total_out;
420bf41cabSGerd Hoffmann stream.avail_out = out_len - stream.total_out;
430bf41cabSGerd Hoffmann
440bf41cabSGerd Hoffmann ret = inflateInit(&stream);
450bf41cabSGerd Hoffmann if (ret != Z_OK) {
460bf41cabSGerd Hoffmann goto err;
470bf41cabSGerd Hoffmann }
480bf41cabSGerd Hoffmann
490bf41cabSGerd Hoffmann while (stream.avail_in) {
500bf41cabSGerd Hoffmann ret = inflate(&stream, Z_FINISH);
510bf41cabSGerd Hoffmann switch (ret) {
520bf41cabSGerd Hoffmann case Z_OK:
530bf41cabSGerd Hoffmann break;
54d921fea3SMauro Matteo Cascella case Z_STREAM_END:
55d921fea3SMauro Matteo Cascella *size = stream.total_out;
56d921fea3SMauro Matteo Cascella inflateEnd(&stream);
57d921fea3SMauro Matteo Cascella return out;
580bf41cabSGerd Hoffmann case Z_BUF_ERROR:
590bf41cabSGerd Hoffmann out_len <<= 1;
600bf41cabSGerd Hoffmann if (out_len > (1 << 20)) {
610bf41cabSGerd Hoffmann goto err_end;
620bf41cabSGerd Hoffmann }
630bf41cabSGerd Hoffmann out = g_realloc(out, out_len);
640bf41cabSGerd Hoffmann stream.next_out = out + stream.total_out;
650bf41cabSGerd Hoffmann stream.avail_out = out_len - stream.total_out;
660bf41cabSGerd Hoffmann break;
670bf41cabSGerd Hoffmann default:
680bf41cabSGerd Hoffmann goto err_end;
690bf41cabSGerd Hoffmann }
700bf41cabSGerd Hoffmann }
710bf41cabSGerd Hoffmann
72*ebfbf394SFiona Ebner *size = stream.total_out;
73*ebfbf394SFiona Ebner inflateEnd(&stream);
74*ebfbf394SFiona Ebner
75*ebfbf394SFiona Ebner return out;
76*ebfbf394SFiona Ebner
770bf41cabSGerd Hoffmann err_end:
780bf41cabSGerd Hoffmann inflateEnd(&stream);
790bf41cabSGerd Hoffmann err:
800bf41cabSGerd Hoffmann g_free(out);
810bf41cabSGerd Hoffmann return NULL;
820bf41cabSGerd Hoffmann }
830bf41cabSGerd Hoffmann
deflate_buffer(uint8_t * in,uint32_t in_len,uint32_t * size)840bf41cabSGerd Hoffmann static uint8_t *deflate_buffer(uint8_t *in, uint32_t in_len, uint32_t *size)
850bf41cabSGerd Hoffmann {
860bf41cabSGerd Hoffmann z_stream stream = {
870bf41cabSGerd Hoffmann .next_in = in,
880bf41cabSGerd Hoffmann .avail_in = in_len,
890bf41cabSGerd Hoffmann .zalloc = Z_NULL,
900bf41cabSGerd Hoffmann .zfree = Z_NULL,
910bf41cabSGerd Hoffmann };
920bf41cabSGerd Hoffmann uint32_t out_len = 8;
930bf41cabSGerd Hoffmann uint8_t *out = g_malloc(out_len);
940bf41cabSGerd Hoffmann int ret;
950bf41cabSGerd Hoffmann
960bf41cabSGerd Hoffmann stream.next_out = out + stream.total_out;
970bf41cabSGerd Hoffmann stream.avail_out = out_len - stream.total_out;
980bf41cabSGerd Hoffmann
990bf41cabSGerd Hoffmann ret = deflateInit(&stream, Z_DEFAULT_COMPRESSION);
1000bf41cabSGerd Hoffmann if (ret != Z_OK) {
1010bf41cabSGerd Hoffmann goto err;
1020bf41cabSGerd Hoffmann }
1030bf41cabSGerd Hoffmann
1040bf41cabSGerd Hoffmann while (ret != Z_STREAM_END) {
1050bf41cabSGerd Hoffmann ret = deflate(&stream, Z_FINISH);
1060bf41cabSGerd Hoffmann switch (ret) {
1070bf41cabSGerd Hoffmann case Z_OK:
1080bf41cabSGerd Hoffmann case Z_STREAM_END:
1090bf41cabSGerd Hoffmann break;
1100bf41cabSGerd Hoffmann case Z_BUF_ERROR:
1110bf41cabSGerd Hoffmann out_len <<= 1;
1120bf41cabSGerd Hoffmann if (out_len > (1 << 20)) {
1130bf41cabSGerd Hoffmann goto err_end;
1140bf41cabSGerd Hoffmann }
1150bf41cabSGerd Hoffmann out = g_realloc(out, out_len);
1160bf41cabSGerd Hoffmann stream.next_out = out + stream.total_out;
1170bf41cabSGerd Hoffmann stream.avail_out = out_len - stream.total_out;
1180bf41cabSGerd Hoffmann break;
1190bf41cabSGerd Hoffmann default:
1200bf41cabSGerd Hoffmann goto err_end;
1210bf41cabSGerd Hoffmann }
1220bf41cabSGerd Hoffmann }
1230bf41cabSGerd Hoffmann
1240bf41cabSGerd Hoffmann *size = stream.total_out;
1250bf41cabSGerd Hoffmann deflateEnd(&stream);
1260bf41cabSGerd Hoffmann
1270bf41cabSGerd Hoffmann return out;
1280bf41cabSGerd Hoffmann
1290bf41cabSGerd Hoffmann err_end:
1300bf41cabSGerd Hoffmann deflateEnd(&stream);
1310bf41cabSGerd Hoffmann err:
1320bf41cabSGerd Hoffmann g_free(out);
1330bf41cabSGerd Hoffmann return NULL;
1340bf41cabSGerd Hoffmann }
1350bf41cabSGerd Hoffmann
vnc_clipboard_send(VncState * vs,uint32_t count,uint32_t * dwords)1360bf41cabSGerd Hoffmann static void vnc_clipboard_send(VncState *vs, uint32_t count, uint32_t *dwords)
1370bf41cabSGerd Hoffmann {
1380bf41cabSGerd Hoffmann int i;
1390bf41cabSGerd Hoffmann
1400bf41cabSGerd Hoffmann vnc_lock_output(vs);
1410bf41cabSGerd Hoffmann vnc_write_u8(vs, VNC_MSG_SERVER_CUT_TEXT);
1420bf41cabSGerd Hoffmann vnc_write_u8(vs, 0);
1430bf41cabSGerd Hoffmann vnc_write_u8(vs, 0);
1440bf41cabSGerd Hoffmann vnc_write_u8(vs, 0);
1450bf41cabSGerd Hoffmann vnc_write_s32(vs, -(count * sizeof(uint32_t))); /* -(message length) */
1460bf41cabSGerd Hoffmann for (i = 0; i < count; i++) {
1470bf41cabSGerd Hoffmann vnc_write_u32(vs, dwords[i]);
1480bf41cabSGerd Hoffmann }
1490bf41cabSGerd Hoffmann vnc_unlock_output(vs);
1500bf41cabSGerd Hoffmann vnc_flush(vs);
1510bf41cabSGerd Hoffmann }
1520bf41cabSGerd Hoffmann
vnc_clipboard_provide(VncState * vs,QemuClipboardInfo * info,QemuClipboardType type)1530bf41cabSGerd Hoffmann static void vnc_clipboard_provide(VncState *vs,
1540bf41cabSGerd Hoffmann QemuClipboardInfo *info,
1550bf41cabSGerd Hoffmann QemuClipboardType type)
1560bf41cabSGerd Hoffmann {
1570bf41cabSGerd Hoffmann uint32_t flags = 0;
1580bf41cabSGerd Hoffmann g_autofree uint8_t *buf = NULL;
1590bf41cabSGerd Hoffmann g_autofree void *zbuf = NULL;
1600bf41cabSGerd Hoffmann uint32_t zsize;
1610bf41cabSGerd Hoffmann
1620bf41cabSGerd Hoffmann switch (type) {
1630bf41cabSGerd Hoffmann case QEMU_CLIPBOARD_TYPE_TEXT:
1640bf41cabSGerd Hoffmann flags |= VNC_CLIPBOARD_TEXT;
1650bf41cabSGerd Hoffmann break;
1660bf41cabSGerd Hoffmann default:
1670bf41cabSGerd Hoffmann return;
1680bf41cabSGerd Hoffmann }
1690bf41cabSGerd Hoffmann flags |= VNC_CLIPBOARD_PROVIDE;
1700bf41cabSGerd Hoffmann
1710bf41cabSGerd Hoffmann buf = g_malloc(info->types[type].size + 4);
1720bf41cabSGerd Hoffmann buf[0] = (info->types[type].size >> 24) & 0xff;
1730bf41cabSGerd Hoffmann buf[1] = (info->types[type].size >> 16) & 0xff;
1740bf41cabSGerd Hoffmann buf[2] = (info->types[type].size >> 8) & 0xff;
1750bf41cabSGerd Hoffmann buf[3] = (info->types[type].size >> 0) & 0xff;
1760bf41cabSGerd Hoffmann memcpy(buf + 4, info->types[type].data, info->types[type].size);
1770bf41cabSGerd Hoffmann zbuf = deflate_buffer(buf, info->types[type].size + 4, &zsize);
1780bf41cabSGerd Hoffmann if (!zbuf) {
1790bf41cabSGerd Hoffmann return;
1800bf41cabSGerd Hoffmann }
1810bf41cabSGerd Hoffmann
1820bf41cabSGerd Hoffmann vnc_lock_output(vs);
1830bf41cabSGerd Hoffmann vnc_write_u8(vs, VNC_MSG_SERVER_CUT_TEXT);
1840bf41cabSGerd Hoffmann vnc_write_u8(vs, 0);
1850bf41cabSGerd Hoffmann vnc_write_u8(vs, 0);
1860bf41cabSGerd Hoffmann vnc_write_u8(vs, 0);
1870bf41cabSGerd Hoffmann vnc_write_s32(vs, -(sizeof(uint32_t) + zsize)); /* -(message length) */
1880bf41cabSGerd Hoffmann vnc_write_u32(vs, flags);
1890bf41cabSGerd Hoffmann vnc_write(vs, zbuf, zsize);
1900bf41cabSGerd Hoffmann vnc_unlock_output(vs);
1910bf41cabSGerd Hoffmann vnc_flush(vs);
1920bf41cabSGerd Hoffmann }
1930bf41cabSGerd Hoffmann
vnc_clipboard_update_info(VncState * vs,QemuClipboardInfo * info)1941b17f1e9SMarc-André Lureau static void vnc_clipboard_update_info(VncState *vs, QemuClipboardInfo *info)
1950bf41cabSGerd Hoffmann {
1960bf41cabSGerd Hoffmann QemuClipboardType type;
1970bf41cabSGerd Hoffmann bool self_update = info->owner == &vs->cbpeer;
1980bf41cabSGerd Hoffmann uint32_t flags = 0;
1990bf41cabSGerd Hoffmann
2000bf41cabSGerd Hoffmann if (info != vs->cbinfo) {
2010bf41cabSGerd Hoffmann qemu_clipboard_info_unref(vs->cbinfo);
2020bf41cabSGerd Hoffmann vs->cbinfo = qemu_clipboard_info_ref(info);
2030bf41cabSGerd Hoffmann vs->cbpending = 0;
2040bf41cabSGerd Hoffmann if (!self_update) {
2050bf41cabSGerd Hoffmann if (info->types[QEMU_CLIPBOARD_TYPE_TEXT].available) {
2060bf41cabSGerd Hoffmann flags |= VNC_CLIPBOARD_TEXT;
2070bf41cabSGerd Hoffmann }
2080bf41cabSGerd Hoffmann flags |= VNC_CLIPBOARD_NOTIFY;
2090bf41cabSGerd Hoffmann vnc_clipboard_send(vs, 1, &flags);
2100bf41cabSGerd Hoffmann }
2110bf41cabSGerd Hoffmann return;
2120bf41cabSGerd Hoffmann }
2130bf41cabSGerd Hoffmann
2140bf41cabSGerd Hoffmann if (self_update) {
2150bf41cabSGerd Hoffmann return;
2160bf41cabSGerd Hoffmann }
2170bf41cabSGerd Hoffmann
2180bf41cabSGerd Hoffmann for (type = 0; type < QEMU_CLIPBOARD_TYPE__COUNT; type++) {
2190bf41cabSGerd Hoffmann if (vs->cbpending & (1 << type)) {
2200bf41cabSGerd Hoffmann vs->cbpending &= ~(1 << type);
2210bf41cabSGerd Hoffmann vnc_clipboard_provide(vs, info, type);
2220bf41cabSGerd Hoffmann }
2230bf41cabSGerd Hoffmann }
2240bf41cabSGerd Hoffmann }
2250bf41cabSGerd Hoffmann
vnc_clipboard_notify(Notifier * notifier,void * data)2261b17f1e9SMarc-André Lureau static void vnc_clipboard_notify(Notifier *notifier, void *data)
2271b17f1e9SMarc-André Lureau {
2281b17f1e9SMarc-André Lureau VncState *vs = container_of(notifier, VncState, cbpeer.notifier);
2291b17f1e9SMarc-André Lureau QemuClipboardNotify *notify = data;
2301b17f1e9SMarc-André Lureau
2311b17f1e9SMarc-André Lureau switch (notify->type) {
2321b17f1e9SMarc-André Lureau case QEMU_CLIPBOARD_UPDATE_INFO:
2331b17f1e9SMarc-André Lureau vnc_clipboard_update_info(vs, notify->info);
2341b17f1e9SMarc-André Lureau return;
235505dbf9bSMarc-André Lureau case QEMU_CLIPBOARD_RESET_SERIAL:
236505dbf9bSMarc-André Lureau /* ignore */
237505dbf9bSMarc-André Lureau return;
2381b17f1e9SMarc-André Lureau }
2391b17f1e9SMarc-André Lureau }
2401b17f1e9SMarc-André Lureau
vnc_clipboard_request(QemuClipboardInfo * info,QemuClipboardType type)2410bf41cabSGerd Hoffmann static void vnc_clipboard_request(QemuClipboardInfo *info,
2420bf41cabSGerd Hoffmann QemuClipboardType type)
2430bf41cabSGerd Hoffmann {
2440bf41cabSGerd Hoffmann VncState *vs = container_of(info->owner, VncState, cbpeer);
2450bf41cabSGerd Hoffmann uint32_t flags = 0;
2460bf41cabSGerd Hoffmann
2470bf41cabSGerd Hoffmann if (type == QEMU_CLIPBOARD_TYPE_TEXT) {
2480bf41cabSGerd Hoffmann flags |= VNC_CLIPBOARD_TEXT;
2490bf41cabSGerd Hoffmann }
2500bf41cabSGerd Hoffmann if (!flags) {
2510bf41cabSGerd Hoffmann return;
2520bf41cabSGerd Hoffmann }
2530bf41cabSGerd Hoffmann flags |= VNC_CLIPBOARD_REQUEST;
2540bf41cabSGerd Hoffmann
2550bf41cabSGerd Hoffmann vnc_clipboard_send(vs, 1, &flags);
2560bf41cabSGerd Hoffmann }
2570bf41cabSGerd Hoffmann
vnc_client_cut_text_ext(VncState * vs,int32_t len,uint32_t flags,uint8_t * data)2580bf41cabSGerd Hoffmann void vnc_client_cut_text_ext(VncState *vs, int32_t len, uint32_t flags, uint8_t *data)
2590bf41cabSGerd Hoffmann {
2600bf41cabSGerd Hoffmann if (flags & VNC_CLIPBOARD_CAPS) {
2610bf41cabSGerd Hoffmann /* need store caps somewhere ? */
2620bf41cabSGerd Hoffmann return;
2630bf41cabSGerd Hoffmann }
2640bf41cabSGerd Hoffmann
2650bf41cabSGerd Hoffmann if (flags & VNC_CLIPBOARD_NOTIFY) {
2660bf41cabSGerd Hoffmann QemuClipboardInfo *info =
2670bf41cabSGerd Hoffmann qemu_clipboard_info_new(&vs->cbpeer, QEMU_CLIPBOARD_SELECTION_CLIPBOARD);
2680bf41cabSGerd Hoffmann if (flags & VNC_CLIPBOARD_TEXT) {
2690bf41cabSGerd Hoffmann info->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true;
2700bf41cabSGerd Hoffmann }
2710bf41cabSGerd Hoffmann qemu_clipboard_update(info);
2720bf41cabSGerd Hoffmann qemu_clipboard_info_unref(info);
2730bf41cabSGerd Hoffmann return;
2740bf41cabSGerd Hoffmann }
2750bf41cabSGerd Hoffmann
2760bf41cabSGerd Hoffmann if (flags & VNC_CLIPBOARD_PROVIDE &&
2770bf41cabSGerd Hoffmann vs->cbinfo &&
2780bf41cabSGerd Hoffmann vs->cbinfo->owner == &vs->cbpeer) {
2790bf41cabSGerd Hoffmann uint32_t size = 0;
2800bf41cabSGerd Hoffmann g_autofree uint8_t *buf = inflate_buffer(data, len - 4, &size);
2810bf41cabSGerd Hoffmann if ((flags & VNC_CLIPBOARD_TEXT) &&
2820bf41cabSGerd Hoffmann buf && size >= 4) {
2830bf41cabSGerd Hoffmann uint32_t tsize = read_u32(buf, 0);
2840bf41cabSGerd Hoffmann uint8_t *tbuf = buf + 4;
2850bf41cabSGerd Hoffmann if (tsize < size) {
2860bf41cabSGerd Hoffmann qemu_clipboard_set_data(&vs->cbpeer, vs->cbinfo,
2870bf41cabSGerd Hoffmann QEMU_CLIPBOARD_TYPE_TEXT,
2880bf41cabSGerd Hoffmann tsize, tbuf, true);
2890bf41cabSGerd Hoffmann }
2900bf41cabSGerd Hoffmann }
2910bf41cabSGerd Hoffmann }
2920bf41cabSGerd Hoffmann
2930bf41cabSGerd Hoffmann if (flags & VNC_CLIPBOARD_REQUEST &&
2940bf41cabSGerd Hoffmann vs->cbinfo &&
2950bf41cabSGerd Hoffmann vs->cbinfo->owner != &vs->cbpeer) {
2960bf41cabSGerd Hoffmann if ((flags & VNC_CLIPBOARD_TEXT) &&
2970bf41cabSGerd Hoffmann vs->cbinfo->types[QEMU_CLIPBOARD_TYPE_TEXT].available) {
2980bf41cabSGerd Hoffmann if (vs->cbinfo->types[QEMU_CLIPBOARD_TYPE_TEXT].data) {
2990bf41cabSGerd Hoffmann vnc_clipboard_provide(vs, vs->cbinfo, QEMU_CLIPBOARD_TYPE_TEXT);
3000bf41cabSGerd Hoffmann } else {
3010bf41cabSGerd Hoffmann vs->cbpending |= (1 << QEMU_CLIPBOARD_TYPE_TEXT);
3020bf41cabSGerd Hoffmann qemu_clipboard_request(vs->cbinfo, QEMU_CLIPBOARD_TYPE_TEXT);
3030bf41cabSGerd Hoffmann }
3040bf41cabSGerd Hoffmann }
3050bf41cabSGerd Hoffmann }
3060bf41cabSGerd Hoffmann }
3070bf41cabSGerd Hoffmann
vnc_client_cut_text(VncState * vs,size_t len,uint8_t * text)3080bf41cabSGerd Hoffmann void vnc_client_cut_text(VncState *vs, size_t len, uint8_t *text)
3090bf41cabSGerd Hoffmann {
3100bf41cabSGerd Hoffmann QemuClipboardInfo *info =
3110bf41cabSGerd Hoffmann qemu_clipboard_info_new(&vs->cbpeer, QEMU_CLIPBOARD_SELECTION_CLIPBOARD);
3120bf41cabSGerd Hoffmann
3130bf41cabSGerd Hoffmann qemu_clipboard_set_data(&vs->cbpeer, info, QEMU_CLIPBOARD_TYPE_TEXT,
3140bf41cabSGerd Hoffmann len, text, true);
3150bf41cabSGerd Hoffmann qemu_clipboard_info_unref(info);
3160bf41cabSGerd Hoffmann }
3170bf41cabSGerd Hoffmann
vnc_server_cut_text_caps(VncState * vs)3180bf41cabSGerd Hoffmann void vnc_server_cut_text_caps(VncState *vs)
3190bf41cabSGerd Hoffmann {
3200bf41cabSGerd Hoffmann uint32_t caps[2];
3210bf41cabSGerd Hoffmann
3220bf41cabSGerd Hoffmann if (!vnc_has_feature(vs, VNC_FEATURE_CLIPBOARD_EXT)) {
3230bf41cabSGerd Hoffmann return;
3240bf41cabSGerd Hoffmann }
3250bf41cabSGerd Hoffmann
3260bf41cabSGerd Hoffmann caps[0] = (VNC_CLIPBOARD_PROVIDE |
3270bf41cabSGerd Hoffmann VNC_CLIPBOARD_NOTIFY |
3280bf41cabSGerd Hoffmann VNC_CLIPBOARD_REQUEST |
3290bf41cabSGerd Hoffmann VNC_CLIPBOARD_CAPS |
3300bf41cabSGerd Hoffmann VNC_CLIPBOARD_TEXT);
3310bf41cabSGerd Hoffmann caps[1] = 0;
3320bf41cabSGerd Hoffmann vnc_clipboard_send(vs, 2, caps);
3330bf41cabSGerd Hoffmann
3341b17f1e9SMarc-André Lureau if (!vs->cbpeer.notifier.notify) {
3350bf41cabSGerd Hoffmann vs->cbpeer.name = "vnc";
3361b17f1e9SMarc-André Lureau vs->cbpeer.notifier.notify = vnc_clipboard_notify;
3370bf41cabSGerd Hoffmann vs->cbpeer.request = vnc_clipboard_request;
3380bf41cabSGerd Hoffmann qemu_clipboard_peer_register(&vs->cbpeer);
3390bf41cabSGerd Hoffmann }
3402e572bafSVladimir Sementsov-Ogievskiy }
341