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