qemu

FORK: QEMU emulator
git clone https://git.neptards.moe/neptards/qemu.git
Log | Files | Refs | Submodules | LICENSE

vnc-clipboard.c (9694B)


      1 /*
      2  * QEMU VNC display driver -- clipboard support
      3  *
      4  * Copyright (C) 2021 Gerd Hoffmann <kraxel@redhat.com>
      5  *
      6  * Permission is hereby granted, free of charge, to any person obtaining a copy
      7  * of this software and associated documentation files (the "Software"), to deal
      8  * in the Software without restriction, including without limitation the rights
      9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     10  * copies of the Software, and to permit persons to whom the Software is
     11  * furnished to do so, subject to the following conditions:
     12  *
     13  * The above copyright notice and this permission notice shall be included in
     14  * all copies or substantial portions of the Software.
     15  *
     16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
     19  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
     22  * THE SOFTWARE.
     23  */
     24 
     25 #include "qemu/osdep.h"
     26 #include "vnc.h"
     27 #include "vnc-jobs.h"
     28 
     29 static uint8_t *inflate_buffer(uint8_t *in, uint32_t in_len, uint32_t *size)
     30 {
     31     z_stream stream = {
     32         .next_in  = in,
     33         .avail_in = in_len,
     34         .zalloc   = Z_NULL,
     35         .zfree    = Z_NULL,
     36     };
     37     uint32_t out_len = 8;
     38     uint8_t *out = g_malloc(out_len);
     39     int ret;
     40 
     41     stream.next_out = out + stream.total_out;
     42     stream.avail_out = out_len - stream.total_out;
     43 
     44     ret = inflateInit(&stream);
     45     if (ret != Z_OK) {
     46         goto err;
     47     }
     48 
     49     while (stream.avail_in) {
     50         ret = inflate(&stream, Z_FINISH);
     51         switch (ret) {
     52         case Z_OK:
     53         case Z_STREAM_END:
     54             break;
     55         case Z_BUF_ERROR:
     56             out_len <<= 1;
     57             if (out_len > (1 << 20)) {
     58                 goto err_end;
     59             }
     60             out = g_realloc(out, out_len);
     61             stream.next_out = out + stream.total_out;
     62             stream.avail_out = out_len - stream.total_out;
     63             break;
     64         default:
     65             goto err_end;
     66         }
     67     }
     68 
     69     *size = stream.total_out;
     70     inflateEnd(&stream);
     71 
     72     return out;
     73 
     74 err_end:
     75     inflateEnd(&stream);
     76 err:
     77     g_free(out);
     78     return NULL;
     79 }
     80 
     81 static uint8_t *deflate_buffer(uint8_t *in, uint32_t in_len, uint32_t *size)
     82 {
     83     z_stream stream = {
     84         .next_in  = in,
     85         .avail_in = in_len,
     86         .zalloc   = Z_NULL,
     87         .zfree    = Z_NULL,
     88     };
     89     uint32_t out_len = 8;
     90     uint8_t *out = g_malloc(out_len);
     91     int ret;
     92 
     93     stream.next_out = out + stream.total_out;
     94     stream.avail_out = out_len - stream.total_out;
     95 
     96     ret = deflateInit(&stream, Z_DEFAULT_COMPRESSION);
     97     if (ret != Z_OK) {
     98         goto err;
     99     }
    100 
    101     while (ret != Z_STREAM_END) {
    102         ret = deflate(&stream, Z_FINISH);
    103         switch (ret) {
    104         case Z_OK:
    105         case Z_STREAM_END:
    106             break;
    107         case Z_BUF_ERROR:
    108             out_len <<= 1;
    109             if (out_len > (1 << 20)) {
    110                 goto err_end;
    111             }
    112             out = g_realloc(out, out_len);
    113             stream.next_out = out + stream.total_out;
    114             stream.avail_out = out_len - stream.total_out;
    115             break;
    116         default:
    117             goto err_end;
    118         }
    119     }
    120 
    121     *size = stream.total_out;
    122     deflateEnd(&stream);
    123 
    124     return out;
    125 
    126 err_end:
    127     deflateEnd(&stream);
    128 err:
    129     g_free(out);
    130     return NULL;
    131 }
    132 
    133 static void vnc_clipboard_send(VncState *vs, uint32_t count, uint32_t *dwords)
    134 {
    135     int i;
    136 
    137     vnc_lock_output(vs);
    138     vnc_write_u8(vs, VNC_MSG_SERVER_CUT_TEXT);
    139     vnc_write_u8(vs, 0);
    140     vnc_write_u8(vs, 0);
    141     vnc_write_u8(vs, 0);
    142     vnc_write_s32(vs, -(count * sizeof(uint32_t)));  /* -(message length) */
    143     for (i = 0; i < count; i++) {
    144         vnc_write_u32(vs, dwords[i]);
    145     }
    146     vnc_unlock_output(vs);
    147     vnc_flush(vs);
    148 }
    149 
    150 static void vnc_clipboard_provide(VncState *vs,
    151                                   QemuClipboardInfo *info,
    152                                   QemuClipboardType type)
    153 {
    154     uint32_t flags = 0;
    155     g_autofree uint8_t *buf = NULL;
    156     g_autofree void *zbuf = NULL;
    157     uint32_t zsize;
    158 
    159     switch (type) {
    160     case QEMU_CLIPBOARD_TYPE_TEXT:
    161         flags |= VNC_CLIPBOARD_TEXT;
    162         break;
    163     default:
    164         return;
    165     }
    166     flags |= VNC_CLIPBOARD_PROVIDE;
    167 
    168     buf = g_malloc(info->types[type].size + 4);
    169     buf[0] = (info->types[type].size >> 24) & 0xff;
    170     buf[1] = (info->types[type].size >> 16) & 0xff;
    171     buf[2] = (info->types[type].size >>  8) & 0xff;
    172     buf[3] = (info->types[type].size >>  0) & 0xff;
    173     memcpy(buf + 4, info->types[type].data, info->types[type].size);
    174     zbuf = deflate_buffer(buf, info->types[type].size + 4, &zsize);
    175     if (!zbuf) {
    176         return;
    177     }
    178 
    179     vnc_lock_output(vs);
    180     vnc_write_u8(vs, VNC_MSG_SERVER_CUT_TEXT);
    181     vnc_write_u8(vs, 0);
    182     vnc_write_u8(vs, 0);
    183     vnc_write_u8(vs, 0);
    184     vnc_write_s32(vs, -(sizeof(uint32_t) + zsize));  /* -(message length) */
    185     vnc_write_u32(vs, flags);
    186     vnc_write(vs, zbuf, zsize);
    187     vnc_unlock_output(vs);
    188     vnc_flush(vs);
    189 }
    190 
    191 static void vnc_clipboard_update_info(VncState *vs, QemuClipboardInfo *info)
    192 {
    193     QemuClipboardType type;
    194     bool self_update = info->owner == &vs->cbpeer;
    195     uint32_t flags = 0;
    196 
    197     if (info != vs->cbinfo) {
    198         qemu_clipboard_info_unref(vs->cbinfo);
    199         vs->cbinfo = qemu_clipboard_info_ref(info);
    200         vs->cbpending = 0;
    201         if (!self_update) {
    202             if (info->types[QEMU_CLIPBOARD_TYPE_TEXT].available) {
    203                 flags |= VNC_CLIPBOARD_TEXT;
    204             }
    205             flags |= VNC_CLIPBOARD_NOTIFY;
    206             vnc_clipboard_send(vs, 1, &flags);
    207         }
    208         return;
    209     }
    210 
    211     if (self_update) {
    212         return;
    213     }
    214 
    215     for (type = 0; type < QEMU_CLIPBOARD_TYPE__COUNT; type++) {
    216         if (vs->cbpending & (1 << type)) {
    217             vs->cbpending &= ~(1 << type);
    218             vnc_clipboard_provide(vs, info, type);
    219         }
    220     }
    221 }
    222 
    223 static void vnc_clipboard_notify(Notifier *notifier, void *data)
    224 {
    225     VncState *vs = container_of(notifier, VncState, cbpeer.notifier);
    226     QemuClipboardNotify *notify = data;
    227 
    228     switch (notify->type) {
    229     case QEMU_CLIPBOARD_UPDATE_INFO:
    230         vnc_clipboard_update_info(vs, notify->info);
    231         return;
    232     case QEMU_CLIPBOARD_RESET_SERIAL:
    233         /* ignore */
    234         return;
    235     }
    236 }
    237 
    238 static void vnc_clipboard_request(QemuClipboardInfo *info,
    239                                   QemuClipboardType type)
    240 {
    241     VncState *vs = container_of(info->owner, VncState, cbpeer);
    242     uint32_t flags = 0;
    243 
    244     if (type == QEMU_CLIPBOARD_TYPE_TEXT) {
    245         flags |= VNC_CLIPBOARD_TEXT;
    246     }
    247     if (!flags) {
    248         return;
    249     }
    250     flags |= VNC_CLIPBOARD_REQUEST;
    251 
    252     vnc_clipboard_send(vs, 1, &flags);
    253 }
    254 
    255 void vnc_client_cut_text_ext(VncState *vs, int32_t len, uint32_t flags, uint8_t *data)
    256 {
    257     if (flags & VNC_CLIPBOARD_CAPS) {
    258         /* need store caps somewhere ? */
    259         return;
    260     }
    261 
    262     if (flags & VNC_CLIPBOARD_NOTIFY) {
    263         QemuClipboardInfo *info =
    264             qemu_clipboard_info_new(&vs->cbpeer, QEMU_CLIPBOARD_SELECTION_CLIPBOARD);
    265         if (flags & VNC_CLIPBOARD_TEXT) {
    266             info->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true;
    267         }
    268         qemu_clipboard_update(info);
    269         qemu_clipboard_info_unref(info);
    270         return;
    271     }
    272 
    273     if (flags & VNC_CLIPBOARD_PROVIDE &&
    274         vs->cbinfo &&
    275         vs->cbinfo->owner == &vs->cbpeer) {
    276         uint32_t size = 0;
    277         g_autofree uint8_t *buf = inflate_buffer(data, len - 4, &size);
    278         if ((flags & VNC_CLIPBOARD_TEXT) &&
    279             buf && size >= 4) {
    280             uint32_t tsize = read_u32(buf, 0);
    281             uint8_t *tbuf = buf + 4;
    282             if (tsize < size) {
    283                 qemu_clipboard_set_data(&vs->cbpeer, vs->cbinfo,
    284                                         QEMU_CLIPBOARD_TYPE_TEXT,
    285                                         tsize, tbuf, true);
    286             }
    287         }
    288     }
    289 
    290     if (flags & VNC_CLIPBOARD_REQUEST &&
    291         vs->cbinfo &&
    292         vs->cbinfo->owner != &vs->cbpeer) {
    293         if ((flags & VNC_CLIPBOARD_TEXT) &&
    294             vs->cbinfo->types[QEMU_CLIPBOARD_TYPE_TEXT].available) {
    295             if (vs->cbinfo->types[QEMU_CLIPBOARD_TYPE_TEXT].data) {
    296                 vnc_clipboard_provide(vs, vs->cbinfo, QEMU_CLIPBOARD_TYPE_TEXT);
    297             } else {
    298                 vs->cbpending |= (1 << QEMU_CLIPBOARD_TYPE_TEXT);
    299                 qemu_clipboard_request(vs->cbinfo, QEMU_CLIPBOARD_TYPE_TEXT);
    300             }
    301         }
    302     }
    303 }
    304 
    305 void vnc_client_cut_text(VncState *vs, size_t len, uint8_t *text)
    306 {
    307     QemuClipboardInfo *info =
    308         qemu_clipboard_info_new(&vs->cbpeer, QEMU_CLIPBOARD_SELECTION_CLIPBOARD);
    309 
    310     qemu_clipboard_set_data(&vs->cbpeer, info, QEMU_CLIPBOARD_TYPE_TEXT,
    311                             len, text, true);
    312     qemu_clipboard_info_unref(info);
    313 }
    314 
    315 void vnc_server_cut_text_caps(VncState *vs)
    316 {
    317     uint32_t caps[2];
    318 
    319     if (!vnc_has_feature(vs, VNC_FEATURE_CLIPBOARD_EXT)) {
    320         return;
    321     }
    322 
    323     caps[0] = (VNC_CLIPBOARD_PROVIDE |
    324                VNC_CLIPBOARD_NOTIFY  |
    325                VNC_CLIPBOARD_REQUEST |
    326                VNC_CLIPBOARD_CAPS    |
    327                VNC_CLIPBOARD_TEXT);
    328     caps[1] = 0;
    329     vnc_clipboard_send(vs, 2, caps);
    330 
    331     if (!vs->cbpeer.notifier.notify) {
    332         vs->cbpeer.name = "vnc";
    333         vs->cbpeer.notifier.notify = vnc_clipboard_notify;
    334         vs->cbpeer.request = vnc_clipboard_request;
    335         qemu_clipboard_peer_register(&vs->cbpeer);
    336     }
    337 }