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 } 338