xref: /openbmc/qemu/ui/vnc-clipboard.c (revision 0fbb5d2d)
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 
30 static 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 
75 err_end:
76     inflateEnd(&stream);
77 err:
78     g_free(out);
79     return NULL;
80 }
81 
82 static 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 
127 err_end:
128     deflateEnd(&stream);
129 err:
130     g_free(out);
131     return NULL;
132 }
133 
134 static 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 
151 static 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 
192 static void vnc_clipboard_update_info(VncState *vs, QemuClipboardInfo *info)
193 {
194     QemuClipboardType type;
195     bool self_update = info->owner == &vs->cbpeer;
196     uint32_t flags = 0;
197 
198     if (info != vs->cbinfo) {
199         qemu_clipboard_info_unref(vs->cbinfo);
200         vs->cbinfo = qemu_clipboard_info_ref(info);
201         vs->cbpending = 0;
202         if (!self_update) {
203             if (info->types[QEMU_CLIPBOARD_TYPE_TEXT].available) {
204                 flags |= VNC_CLIPBOARD_TEXT;
205             }
206             flags |= VNC_CLIPBOARD_NOTIFY;
207             vnc_clipboard_send(vs, 1, &flags);
208         }
209         return;
210     }
211 
212     if (self_update) {
213         return;
214     }
215 
216     for (type = 0; type < QEMU_CLIPBOARD_TYPE__COUNT; type++) {
217         if (vs->cbpending & (1 << type)) {
218             vs->cbpending &= ~(1 << type);
219             vnc_clipboard_provide(vs, info, type);
220         }
221     }
222 }
223 
224 static void vnc_clipboard_notify(Notifier *notifier, void *data)
225 {
226     VncState *vs = container_of(notifier, VncState, cbpeer.notifier);
227     QemuClipboardNotify *notify = data;
228 
229     switch (notify->type) {
230     case QEMU_CLIPBOARD_UPDATE_INFO:
231         vnc_clipboard_update_info(vs, notify->info);
232         return;
233     case QEMU_CLIPBOARD_RESET_SERIAL:
234         /* ignore */
235         return;
236     }
237 }
238 
239 static void vnc_clipboard_request(QemuClipboardInfo *info,
240                                   QemuClipboardType type)
241 {
242     VncState *vs = container_of(info->owner, VncState, cbpeer);
243     uint32_t flags = 0;
244 
245     if (type == QEMU_CLIPBOARD_TYPE_TEXT) {
246         flags |= VNC_CLIPBOARD_TEXT;
247     }
248     if (!flags) {
249         return;
250     }
251     flags |= VNC_CLIPBOARD_REQUEST;
252 
253     vnc_clipboard_send(vs, 1, &flags);
254 }
255 
256 void vnc_client_cut_text_ext(VncState *vs, int32_t len, uint32_t flags, uint8_t *data)
257 {
258     if (flags & VNC_CLIPBOARD_CAPS) {
259         /* need store caps somewhere ? */
260         return;
261     }
262 
263     if (flags & VNC_CLIPBOARD_NOTIFY) {
264         QemuClipboardInfo *info =
265             qemu_clipboard_info_new(&vs->cbpeer, QEMU_CLIPBOARD_SELECTION_CLIPBOARD);
266         if (flags & VNC_CLIPBOARD_TEXT) {
267             info->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true;
268         }
269         qemu_clipboard_update(info);
270         qemu_clipboard_info_unref(info);
271         return;
272     }
273 
274     if (flags & VNC_CLIPBOARD_PROVIDE &&
275         vs->cbinfo &&
276         vs->cbinfo->owner == &vs->cbpeer) {
277         uint32_t size = 0;
278         g_autofree uint8_t *buf = inflate_buffer(data, len - 4, &size);
279         if ((flags & VNC_CLIPBOARD_TEXT) &&
280             buf && size >= 4) {
281             uint32_t tsize = read_u32(buf, 0);
282             uint8_t *tbuf = buf + 4;
283             if (tsize < size) {
284                 qemu_clipboard_set_data(&vs->cbpeer, vs->cbinfo,
285                                         QEMU_CLIPBOARD_TYPE_TEXT,
286                                         tsize, tbuf, true);
287             }
288         }
289     }
290 
291     if (flags & VNC_CLIPBOARD_REQUEST &&
292         vs->cbinfo &&
293         vs->cbinfo->owner != &vs->cbpeer) {
294         if ((flags & VNC_CLIPBOARD_TEXT) &&
295             vs->cbinfo->types[QEMU_CLIPBOARD_TYPE_TEXT].available) {
296             if (vs->cbinfo->types[QEMU_CLIPBOARD_TYPE_TEXT].data) {
297                 vnc_clipboard_provide(vs, vs->cbinfo, QEMU_CLIPBOARD_TYPE_TEXT);
298             } else {
299                 vs->cbpending |= (1 << QEMU_CLIPBOARD_TYPE_TEXT);
300                 qemu_clipboard_request(vs->cbinfo, QEMU_CLIPBOARD_TYPE_TEXT);
301             }
302         }
303     }
304 }
305 
306 void vnc_client_cut_text(VncState *vs, size_t len, uint8_t *text)
307 {
308     QemuClipboardInfo *info =
309         qemu_clipboard_info_new(&vs->cbpeer, QEMU_CLIPBOARD_SELECTION_CLIPBOARD);
310 
311     qemu_clipboard_set_data(&vs->cbpeer, info, QEMU_CLIPBOARD_TYPE_TEXT,
312                             len, text, true);
313     qemu_clipboard_info_unref(info);
314 }
315 
316 void vnc_server_cut_text_caps(VncState *vs)
317 {
318     uint32_t caps[2];
319 
320     if (!vnc_has_feature(vs, VNC_FEATURE_CLIPBOARD_EXT)) {
321         return;
322     }
323 
324     caps[0] = (VNC_CLIPBOARD_PROVIDE |
325                VNC_CLIPBOARD_NOTIFY  |
326                VNC_CLIPBOARD_REQUEST |
327                VNC_CLIPBOARD_CAPS    |
328                VNC_CLIPBOARD_TEXT);
329     caps[1] = 0;
330     vnc_clipboard_send(vs, 2, caps);
331 
332     if (!vs->cbpeer.notifier.notify) {
333         vs->cbpeer.name = "vnc";
334         vs->cbpeer.notifier.notify = vnc_clipboard_notify;
335         vs->cbpeer.request = vnc_clipboard_request;
336         qemu_clipboard_peer_register(&vs->cbpeer);
337     }
338 }
339