123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337 |
- /*
- * QEMU VNC display driver -- clipboard support
- *
- * Copyright (C) 2021 Gerd Hoffmann <kraxel@redhat.com>
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
- #include "qemu/osdep.h"
- #include "vnc.h"
- #include "vnc-jobs.h"
- static uint8_t *inflate_buffer(uint8_t *in, uint32_t in_len, uint32_t *size)
- {
- z_stream stream = {
- .next_in = in,
- .avail_in = in_len,
- .zalloc = Z_NULL,
- .zfree = Z_NULL,
- };
- uint32_t out_len = 8;
- uint8_t *out = g_malloc(out_len);
- int ret;
- stream.next_out = out + stream.total_out;
- stream.avail_out = out_len - stream.total_out;
- ret = inflateInit(&stream);
- if (ret != Z_OK) {
- goto err;
- }
- while (stream.avail_in) {
- ret = inflate(&stream, Z_FINISH);
- switch (ret) {
- case Z_OK:
- case Z_STREAM_END:
- break;
- case Z_BUF_ERROR:
- out_len <<= 1;
- if (out_len > (1 << 20)) {
- goto err_end;
- }
- out = g_realloc(out, out_len);
- stream.next_out = out + stream.total_out;
- stream.avail_out = out_len - stream.total_out;
- break;
- default:
- goto err_end;
- }
- }
- *size = stream.total_out;
- inflateEnd(&stream);
- return out;
- err_end:
- inflateEnd(&stream);
- err:
- g_free(out);
- return NULL;
- }
- static uint8_t *deflate_buffer(uint8_t *in, uint32_t in_len, uint32_t *size)
- {
- z_stream stream = {
- .next_in = in,
- .avail_in = in_len,
- .zalloc = Z_NULL,
- .zfree = Z_NULL,
- };
- uint32_t out_len = 8;
- uint8_t *out = g_malloc(out_len);
- int ret;
- stream.next_out = out + stream.total_out;
- stream.avail_out = out_len - stream.total_out;
- ret = deflateInit(&stream, Z_DEFAULT_COMPRESSION);
- if (ret != Z_OK) {
- goto err;
- }
- while (ret != Z_STREAM_END) {
- ret = deflate(&stream, Z_FINISH);
- switch (ret) {
- case Z_OK:
- case Z_STREAM_END:
- break;
- case Z_BUF_ERROR:
- out_len <<= 1;
- if (out_len > (1 << 20)) {
- goto err_end;
- }
- out = g_realloc(out, out_len);
- stream.next_out = out + stream.total_out;
- stream.avail_out = out_len - stream.total_out;
- break;
- default:
- goto err_end;
- }
- }
- *size = stream.total_out;
- deflateEnd(&stream);
- return out;
- err_end:
- deflateEnd(&stream);
- err:
- g_free(out);
- return NULL;
- }
- static void vnc_clipboard_send(VncState *vs, uint32_t count, uint32_t *dwords)
- {
- int i;
- vnc_lock_output(vs);
- vnc_write_u8(vs, VNC_MSG_SERVER_CUT_TEXT);
- vnc_write_u8(vs, 0);
- vnc_write_u8(vs, 0);
- vnc_write_u8(vs, 0);
- vnc_write_s32(vs, -(count * sizeof(uint32_t))); /* -(message length) */
- for (i = 0; i < count; i++) {
- vnc_write_u32(vs, dwords[i]);
- }
- vnc_unlock_output(vs);
- vnc_flush(vs);
- }
- static void vnc_clipboard_provide(VncState *vs,
- QemuClipboardInfo *info,
- QemuClipboardType type)
- {
- uint32_t flags = 0;
- g_autofree uint8_t *buf = NULL;
- g_autofree void *zbuf = NULL;
- uint32_t zsize;
- switch (type) {
- case QEMU_CLIPBOARD_TYPE_TEXT:
- flags |= VNC_CLIPBOARD_TEXT;
- break;
- default:
- return;
- }
- flags |= VNC_CLIPBOARD_PROVIDE;
- buf = g_malloc(info->types[type].size + 4);
- buf[0] = (info->types[type].size >> 24) & 0xff;
- buf[1] = (info->types[type].size >> 16) & 0xff;
- buf[2] = (info->types[type].size >> 8) & 0xff;
- buf[3] = (info->types[type].size >> 0) & 0xff;
- memcpy(buf + 4, info->types[type].data, info->types[type].size);
- zbuf = deflate_buffer(buf, info->types[type].size + 4, &zsize);
- if (!zbuf) {
- return;
- }
- vnc_lock_output(vs);
- vnc_write_u8(vs, VNC_MSG_SERVER_CUT_TEXT);
- vnc_write_u8(vs, 0);
- vnc_write_u8(vs, 0);
- vnc_write_u8(vs, 0);
- vnc_write_s32(vs, -(sizeof(uint32_t) + zsize)); /* -(message length) */
- vnc_write_u32(vs, flags);
- vnc_write(vs, zbuf, zsize);
- vnc_unlock_output(vs);
- vnc_flush(vs);
- }
- static void vnc_clipboard_update_info(VncState *vs, QemuClipboardInfo *info)
- {
- QemuClipboardType type;
- bool self_update = info->owner == &vs->cbpeer;
- uint32_t flags = 0;
- if (info != vs->cbinfo) {
- qemu_clipboard_info_unref(vs->cbinfo);
- vs->cbinfo = qemu_clipboard_info_ref(info);
- vs->cbpending = 0;
- if (!self_update) {
- if (info->types[QEMU_CLIPBOARD_TYPE_TEXT].available) {
- flags |= VNC_CLIPBOARD_TEXT;
- }
- flags |= VNC_CLIPBOARD_NOTIFY;
- vnc_clipboard_send(vs, 1, &flags);
- }
- return;
- }
- if (self_update) {
- return;
- }
- for (type = 0; type < QEMU_CLIPBOARD_TYPE__COUNT; type++) {
- if (vs->cbpending & (1 << type)) {
- vs->cbpending &= ~(1 << type);
- vnc_clipboard_provide(vs, info, type);
- }
- }
- }
- static void vnc_clipboard_notify(Notifier *notifier, void *data)
- {
- VncState *vs = container_of(notifier, VncState, cbpeer.notifier);
- QemuClipboardNotify *notify = data;
- switch (notify->type) {
- case QEMU_CLIPBOARD_UPDATE_INFO:
- vnc_clipboard_update_info(vs, notify->info);
- return;
- case QEMU_CLIPBOARD_RESET_SERIAL:
- /* ignore */
- return;
- }
- }
- static void vnc_clipboard_request(QemuClipboardInfo *info,
- QemuClipboardType type)
- {
- VncState *vs = container_of(info->owner, VncState, cbpeer);
- uint32_t flags = 0;
- if (type == QEMU_CLIPBOARD_TYPE_TEXT) {
- flags |= VNC_CLIPBOARD_TEXT;
- }
- if (!flags) {
- return;
- }
- flags |= VNC_CLIPBOARD_REQUEST;
- vnc_clipboard_send(vs, 1, &flags);
- }
- void vnc_client_cut_text_ext(VncState *vs, int32_t len, uint32_t flags, uint8_t *data)
- {
- if (flags & VNC_CLIPBOARD_CAPS) {
- /* need store caps somewhere ? */
- return;
- }
- if (flags & VNC_CLIPBOARD_NOTIFY) {
- QemuClipboardInfo *info =
- qemu_clipboard_info_new(&vs->cbpeer, QEMU_CLIPBOARD_SELECTION_CLIPBOARD);
- if (flags & VNC_CLIPBOARD_TEXT) {
- info->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true;
- }
- qemu_clipboard_update(info);
- qemu_clipboard_info_unref(info);
- return;
- }
- if (flags & VNC_CLIPBOARD_PROVIDE &&
- vs->cbinfo &&
- vs->cbinfo->owner == &vs->cbpeer) {
- uint32_t size = 0;
- g_autofree uint8_t *buf = inflate_buffer(data, len - 4, &size);
- if ((flags & VNC_CLIPBOARD_TEXT) &&
- buf && size >= 4) {
- uint32_t tsize = read_u32(buf, 0);
- uint8_t *tbuf = buf + 4;
- if (tsize < size) {
- qemu_clipboard_set_data(&vs->cbpeer, vs->cbinfo,
- QEMU_CLIPBOARD_TYPE_TEXT,
- tsize, tbuf, true);
- }
- }
- }
- if (flags & VNC_CLIPBOARD_REQUEST &&
- vs->cbinfo &&
- vs->cbinfo->owner != &vs->cbpeer) {
- if ((flags & VNC_CLIPBOARD_TEXT) &&
- vs->cbinfo->types[QEMU_CLIPBOARD_TYPE_TEXT].available) {
- if (vs->cbinfo->types[QEMU_CLIPBOARD_TYPE_TEXT].data) {
- vnc_clipboard_provide(vs, vs->cbinfo, QEMU_CLIPBOARD_TYPE_TEXT);
- } else {
- vs->cbpending |= (1 << QEMU_CLIPBOARD_TYPE_TEXT);
- qemu_clipboard_request(vs->cbinfo, QEMU_CLIPBOARD_TYPE_TEXT);
- }
- }
- }
- }
- void vnc_client_cut_text(VncState *vs, size_t len, uint8_t *text)
- {
- QemuClipboardInfo *info =
- qemu_clipboard_info_new(&vs->cbpeer, QEMU_CLIPBOARD_SELECTION_CLIPBOARD);
- qemu_clipboard_set_data(&vs->cbpeer, info, QEMU_CLIPBOARD_TYPE_TEXT,
- len, text, true);
- qemu_clipboard_info_unref(info);
- }
- void vnc_server_cut_text_caps(VncState *vs)
- {
- uint32_t caps[2];
- if (!vnc_has_feature(vs, VNC_FEATURE_CLIPBOARD_EXT)) {
- return;
- }
- caps[0] = (VNC_CLIPBOARD_PROVIDE |
- VNC_CLIPBOARD_NOTIFY |
- VNC_CLIPBOARD_REQUEST |
- VNC_CLIPBOARD_CAPS |
- VNC_CLIPBOARD_TEXT);
- caps[1] = 0;
- vnc_clipboard_send(vs, 2, caps);
- if (!vs->cbpeer.notifier.notify) {
- vs->cbpeer.name = "vnc";
- vs->cbpeer.notifier.notify = vnc_clipboard_notify;
- vs->cbpeer.request = vnc_clipboard_request;
- qemu_clipboard_peer_register(&vs->cbpeer);
- }
- }
|