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