vnc-clipboard.c (9352B)
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 "qemu-common.h" 27#include "vnc.h" 28#include "vnc-jobs.h" 29 30static uint8_t *inflate_buffer(uint8_t *in, uint32_t in_len, uint32_t *size) 31{ 32 z_stream stream = { 33 .next_in = in, 34 .avail_in = in_len, 35 .zalloc = Z_NULL, 36 .zfree = Z_NULL, 37 }; 38 uint32_t out_len = 8; 39 uint8_t *out = g_malloc(out_len); 40 int ret; 41 42 stream.next_out = out + stream.total_out; 43 stream.avail_out = out_len - stream.total_out; 44 45 ret = inflateInit(&stream); 46 if (ret != Z_OK) { 47 goto err; 48 } 49 50 while (stream.avail_in) { 51 ret = inflate(&stream, Z_FINISH); 52 switch (ret) { 53 case Z_OK: 54 case Z_STREAM_END: 55 break; 56 case Z_BUF_ERROR: 57 out_len <<= 1; 58 if (out_len > (1 << 20)) { 59 goto err_end; 60 } 61 out = g_realloc(out, out_len); 62 stream.next_out = out + stream.total_out; 63 stream.avail_out = out_len - stream.total_out; 64 break; 65 default: 66 goto err_end; 67 } 68 } 69 70 *size = stream.total_out; 71 inflateEnd(&stream); 72 73 return out; 74 75err_end: 76 inflateEnd(&stream); 77err: 78 g_free(out); 79 return NULL; 80} 81 82static uint8_t *deflate_buffer(uint8_t *in, uint32_t in_len, uint32_t *size) 83{ 84 z_stream stream = { 85 .next_in = in, 86 .avail_in = in_len, 87 .zalloc = Z_NULL, 88 .zfree = Z_NULL, 89 }; 90 uint32_t out_len = 8; 91 uint8_t *out = g_malloc(out_len); 92 int ret; 93 94 stream.next_out = out + stream.total_out; 95 stream.avail_out = out_len - stream.total_out; 96 97 ret = deflateInit(&stream, Z_DEFAULT_COMPRESSION); 98 if (ret != Z_OK) { 99 goto err; 100 } 101 102 while (ret != Z_STREAM_END) { 103 ret = deflate(&stream, Z_FINISH); 104 switch (ret) { 105 case Z_OK: 106 case Z_STREAM_END: 107 break; 108 case Z_BUF_ERROR: 109 out_len <<= 1; 110 if (out_len > (1 << 20)) { 111 goto err_end; 112 } 113 out = g_realloc(out, out_len); 114 stream.next_out = out + stream.total_out; 115 stream.avail_out = out_len - stream.total_out; 116 break; 117 default: 118 goto err_end; 119 } 120 } 121 122 *size = stream.total_out; 123 deflateEnd(&stream); 124 125 return out; 126 127err_end: 128 deflateEnd(&stream); 129err: 130 g_free(out); 131 return NULL; 132} 133 134static void vnc_clipboard_send(VncState *vs, uint32_t count, uint32_t *dwords) 135{ 136 int i; 137 138 vnc_lock_output(vs); 139 vnc_write_u8(vs, VNC_MSG_SERVER_CUT_TEXT); 140 vnc_write_u8(vs, 0); 141 vnc_write_u8(vs, 0); 142 vnc_write_u8(vs, 0); 143 vnc_write_s32(vs, -(count * sizeof(uint32_t))); /* -(message length) */ 144 for (i = 0; i < count; i++) { 145 vnc_write_u32(vs, dwords[i]); 146 } 147 vnc_unlock_output(vs); 148 vnc_flush(vs); 149} 150 151static void vnc_clipboard_provide(VncState *vs, 152 QemuClipboardInfo *info, 153 QemuClipboardType type) 154{ 155 uint32_t flags = 0; 156 g_autofree uint8_t *buf = NULL; 157 g_autofree void *zbuf = NULL; 158 uint32_t zsize; 159 160 switch (type) { 161 case QEMU_CLIPBOARD_TYPE_TEXT: 162 flags |= VNC_CLIPBOARD_TEXT; 163 break; 164 default: 165 return; 166 } 167 flags |= VNC_CLIPBOARD_PROVIDE; 168 169 buf = g_malloc(info->types[type].size + 4); 170 buf[0] = (info->types[type].size >> 24) & 0xff; 171 buf[1] = (info->types[type].size >> 16) & 0xff; 172 buf[2] = (info->types[type].size >> 8) & 0xff; 173 buf[3] = (info->types[type].size >> 0) & 0xff; 174 memcpy(buf + 4, info->types[type].data, info->types[type].size); 175 zbuf = deflate_buffer(buf, info->types[type].size + 4, &zsize); 176 if (!zbuf) { 177 return; 178 } 179 180 vnc_lock_output(vs); 181 vnc_write_u8(vs, VNC_MSG_SERVER_CUT_TEXT); 182 vnc_write_u8(vs, 0); 183 vnc_write_u8(vs, 0); 184 vnc_write_u8(vs, 0); 185 vnc_write_s32(vs, -(sizeof(uint32_t) + zsize)); /* -(message length) */ 186 vnc_write_u32(vs, flags); 187 vnc_write(vs, zbuf, zsize); 188 vnc_unlock_output(vs); 189 vnc_flush(vs); 190} 191 192static void vnc_clipboard_notify(Notifier *notifier, void *data) 193{ 194 VncState *vs = container_of(notifier, VncState, cbpeer.update); 195 QemuClipboardInfo *info = data; 196 QemuClipboardType type; 197 bool self_update = info->owner == &vs->cbpeer; 198 uint32_t flags = 0; 199 200 if (info != vs->cbinfo) { 201 qemu_clipboard_info_unref(vs->cbinfo); 202 vs->cbinfo = qemu_clipboard_info_ref(info); 203 vs->cbpending = 0; 204 if (!self_update) { 205 if (info->types[QEMU_CLIPBOARD_TYPE_TEXT].available) { 206 flags |= VNC_CLIPBOARD_TEXT; 207 } 208 flags |= VNC_CLIPBOARD_NOTIFY; 209 vnc_clipboard_send(vs, 1, &flags); 210 } 211 return; 212 } 213 214 if (self_update) { 215 return; 216 } 217 218 for (type = 0; type < QEMU_CLIPBOARD_TYPE__COUNT; type++) { 219 if (vs->cbpending & (1 << type)) { 220 vs->cbpending &= ~(1 << type); 221 vnc_clipboard_provide(vs, info, type); 222 } 223 } 224} 225 226static void vnc_clipboard_request(QemuClipboardInfo *info, 227 QemuClipboardType type) 228{ 229 VncState *vs = container_of(info->owner, VncState, cbpeer); 230 uint32_t flags = 0; 231 232 if (type == QEMU_CLIPBOARD_TYPE_TEXT) { 233 flags |= VNC_CLIPBOARD_TEXT; 234 } 235 if (!flags) { 236 return; 237 } 238 flags |= VNC_CLIPBOARD_REQUEST; 239 240 vnc_clipboard_send(vs, 1, &flags); 241} 242 243void vnc_client_cut_text_ext(VncState *vs, int32_t len, uint32_t flags, uint8_t *data) 244{ 245 if (flags & VNC_CLIPBOARD_CAPS) { 246 /* need store caps somewhere ? */ 247 return; 248 } 249 250 if (flags & VNC_CLIPBOARD_NOTIFY) { 251 QemuClipboardInfo *info = 252 qemu_clipboard_info_new(&vs->cbpeer, QEMU_CLIPBOARD_SELECTION_CLIPBOARD); 253 if (flags & VNC_CLIPBOARD_TEXT) { 254 info->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true; 255 } 256 qemu_clipboard_update(info); 257 qemu_clipboard_info_unref(info); 258 return; 259 } 260 261 if (flags & VNC_CLIPBOARD_PROVIDE && 262 vs->cbinfo && 263 vs->cbinfo->owner == &vs->cbpeer) { 264 uint32_t size = 0; 265 g_autofree uint8_t *buf = inflate_buffer(data, len - 4, &size); 266 if ((flags & VNC_CLIPBOARD_TEXT) && 267 buf && size >= 4) { 268 uint32_t tsize = read_u32(buf, 0); 269 uint8_t *tbuf = buf + 4; 270 if (tsize < size) { 271 qemu_clipboard_set_data(&vs->cbpeer, vs->cbinfo, 272 QEMU_CLIPBOARD_TYPE_TEXT, 273 tsize, tbuf, true); 274 } 275 } 276 } 277 278 if (flags & VNC_CLIPBOARD_REQUEST && 279 vs->cbinfo && 280 vs->cbinfo->owner != &vs->cbpeer) { 281 if ((flags & VNC_CLIPBOARD_TEXT) && 282 vs->cbinfo->types[QEMU_CLIPBOARD_TYPE_TEXT].available) { 283 if (vs->cbinfo->types[QEMU_CLIPBOARD_TYPE_TEXT].data) { 284 vnc_clipboard_provide(vs, vs->cbinfo, QEMU_CLIPBOARD_TYPE_TEXT); 285 } else { 286 vs->cbpending |= (1 << QEMU_CLIPBOARD_TYPE_TEXT); 287 qemu_clipboard_request(vs->cbinfo, QEMU_CLIPBOARD_TYPE_TEXT); 288 } 289 } 290 } 291} 292 293void vnc_client_cut_text(VncState *vs, size_t len, uint8_t *text) 294{ 295 QemuClipboardInfo *info = 296 qemu_clipboard_info_new(&vs->cbpeer, QEMU_CLIPBOARD_SELECTION_CLIPBOARD); 297 298 qemu_clipboard_set_data(&vs->cbpeer, info, QEMU_CLIPBOARD_TYPE_TEXT, 299 len, text, true); 300 qemu_clipboard_info_unref(info); 301} 302 303void vnc_server_cut_text_caps(VncState *vs) 304{ 305 uint32_t caps[2]; 306 307 if (!vnc_has_feature(vs, VNC_FEATURE_CLIPBOARD_EXT)) { 308 return; 309 } 310 311 caps[0] = (VNC_CLIPBOARD_PROVIDE | 312 VNC_CLIPBOARD_NOTIFY | 313 VNC_CLIPBOARD_REQUEST | 314 VNC_CLIPBOARD_CAPS | 315 VNC_CLIPBOARD_TEXT); 316 caps[1] = 0; 317 vnc_clipboard_send(vs, 2, caps); 318 319 vs->cbpeer.name = "vnc"; 320 vs->cbpeer.update.notify = vnc_clipboard_notify; 321 vs->cbpeer.request = vnc_clipboard_request; 322 qemu_clipboard_peer_register(&vs->cbpeer); 323}