xref: /openbmc/qemu/ui/vnc-clipboard.c (revision 8e51bae8abe3d3433bc7c2ce42e3328923523ba8)
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