xref: /openbmc/qemu/ui/vnc-clipboard.c (revision 43709a0c)
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_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 
226 static 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 
243 void 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 
293 void 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 
303 void 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     if (!vs->cbpeer.update.notify) {
320         vs->cbpeer.name = "vnc";
321         vs->cbpeer.update.notify = vnc_clipboard_notify;
322         vs->cbpeer.request = vnc_clipboard_request;
323         qemu_clipboard_peer_register(&vs->cbpeer);
324     }
325 }
326