master
c 472 lines 14.3 KB
Raw
1 /*
2 * QEMU DBus display
3 *
4 * Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@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 #include "qemu/osdep.h"
25 #include "qemu/dbus.h"
26 #include "qemu/error-report.h"
27 #include "qemu/main-loop.h"
28 #include "qom/object_interfaces.h"
29 #include "system/system.h"
30 #include "qapi/error.h"
31 #include "trace.h"
32
33 #include "dbus.h"
34
35 #define MIME_TEXT_PLAIN_UTF8 "text/plain;charset=utf-8"
36
37 static void
38 dbus_clipboard_complete_request(
39 DBusDisplay *dpy,
40 GDBusMethodInvocation *invocation,
41 QemuClipboardInfo *info,
42 QemuClipboardType type)
43 {
44 GVariant *v_data = g_variant_new_from_data(
45 G_VARIANT_TYPE("ay"),
46 info->types[type].data,
47 info->types[type].size,
48 TRUE,
49 (GDestroyNotify)qemu_clipboard_info_unref,
50 qemu_clipboard_info_ref(info));
51
52 qemu_dbus_display1_clipboard_complete_request(
53 dpy->clipboard, invocation,
54 MIME_TEXT_PLAIN_UTF8, v_data);
55 }
56
57 static void
58 dbus_clipboard_update_info(DBusDisplay *dpy, QemuClipboardInfo *info)
59 {
60 bool self_update = info->owner == &dpy->clipboard_peer;
61 const char *mime[QEMU_CLIPBOARD_TYPE__COUNT + 1] = { 0, };
62 DBusClipboardRequest *req;
63 int i = 0;
64
65 if (info->owner == NULL) {
66 if (dpy->clipboard_proxy) {
67 qemu_dbus_display1_clipboard_call_release(
68 dpy->clipboard_proxy,
69 info->selection,
70 G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
71 }
72 return;
73 }
74
75 if (self_update || !info->has_serial) {
76 return;
77 }
78
79 req = &dpy->clipboard_request[info->selection];
80 if (req->invocation && info->types[req->type].data) {
81 dbus_clipboard_complete_request(dpy, req->invocation, info, req->type);
82 g_clear_object(&req->invocation);
83 g_clear_handle_id(&req->timeout_id, g_source_remove);
84 return;
85 }
86
87 if (info->types[QEMU_CLIPBOARD_TYPE_TEXT].available) {
88 mime[i++] = MIME_TEXT_PLAIN_UTF8;
89 }
90
91 if (i > 0) {
92 if (dpy->clipboard_proxy) {
93 qemu_dbus_display1_clipboard_call_grab(
94 dpy->clipboard_proxy,
95 info->selection,
96 info->serial,
97 mime,
98 G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
99 }
100 }
101 }
102
103 static void
104 dbus_clipboard_reset_serial(DBusDisplay *dpy)
105 {
106 if (dpy->clipboard_proxy) {
107 qemu_dbus_display1_clipboard_call_register(
108 dpy->clipboard_proxy,
109 G_DBUS_CALL_FLAGS_NONE,
110 -1, NULL, NULL, NULL);
111 }
112 }
113
114 static void
115 dbus_clipboard_notify(Notifier *notifier, void *data)
116 {
117 DBusDisplay *dpy =
118 container_of(notifier, DBusDisplay, clipboard_peer.notifier);
119 QemuClipboardNotify *notify = data;
120
121 switch (notify->type) {
122 case QEMU_CLIPBOARD_UPDATE_INFO:
123 dbus_clipboard_update_info(dpy, notify->info);
124 return;
125 case QEMU_CLIPBOARD_RESET_SERIAL:
126 dbus_clipboard_reset_serial(dpy);
127 return;
128 }
129 }
130
131 static void
132 dbus_clipboard_qemu_request(QemuClipboardInfo *info,
133 QemuClipboardType type)
134 {
135 DBusDisplay *dpy = container_of(info->owner, DBusDisplay, clipboard_peer);
136 g_autofree char *mime = NULL;
137 g_autoptr(GVariant) v_data = NULL;
138 g_autoptr(GError) err = NULL;
139 const char *data = NULL;
140 const char *mimes[] = { MIME_TEXT_PLAIN_UTF8, NULL };
141 size_t n;
142
143 trace_dbus_clipboard_qemu_request(type);
144
145 if (type != QEMU_CLIPBOARD_TYPE_TEXT) {
146 /* unsupported atm */
147 return;
148 }
149
150 if (dpy->clipboard_proxy) {
151 if (!qemu_dbus_display1_clipboard_call_request_sync(
152 dpy->clipboard_proxy,
153 info->selection,
154 mimes,
155 G_DBUS_CALL_FLAGS_NONE, -1, &mime, &v_data, NULL, &err)) {
156 error_report("Failed to request clipboard: %s", err->message);
157 return;
158 }
159
160 if (g_strcmp0(mime, MIME_TEXT_PLAIN_UTF8)) {
161 error_report("Unsupported returned MIME: %s", mime);
162 return;
163 }
164
165 data = g_variant_get_fixed_array(v_data, &n, 1);
166 qemu_clipboard_set_data(&dpy->clipboard_peer, info, type,
167 n, data, true);
168 }
169 }
170
171 static void
172 dbus_clipboard_request_cancelled(DBusClipboardRequest *req)
173 {
174 if (!req->invocation) {
175 return;
176 }
177
178 g_dbus_method_invocation_return_error(
179 req->invocation,
180 DBUS_DISPLAY_ERROR,
181 DBUS_DISPLAY_ERROR_FAILED,
182 "Cancelled clipboard request");
183
184 g_clear_object(&req->invocation);
185 g_clear_handle_id(&req->timeout_id, g_source_remove);
186 }
187
188 static void
189 dbus_clipboard_unregister_proxy(DBusDisplay *dpy)
190 {
191 const char *name = NULL;
192 GDBusConnection *connection = NULL;
193 int i;
194
195 for (i = 0; i < G_N_ELEMENTS(dpy->clipboard_request); ++i) {
196 dbus_clipboard_request_cancelled(&dpy->clipboard_request[i]);
197 }
198
199 if (!dpy->clipboard_proxy) {
200 return;
201 }
202
203 connection = g_dbus_proxy_get_connection(
204 G_DBUS_PROXY(dpy->clipboard_proxy));
205 if (connection) {
206 g_signal_handlers_disconnect_by_data(connection, dpy);
207 }
208 g_signal_handlers_disconnect_by_data(dpy->clipboard_proxy, dpy);
209
210 name = g_dbus_proxy_get_name(G_DBUS_PROXY(dpy->clipboard_proxy));
211 trace_dbus_clipboard_unregister(name);
212 g_clear_object(&dpy->clipboard_proxy);
213 }
214
215 static gboolean
216 dbus_clipboard_register(
217 DBusDisplay *dpy,
218 GDBusMethodInvocation *invocation)
219 {
220 g_autoptr(GError) err = NULL;
221 const char *name = NULL;
222 GDBusConnection *connection = g_dbus_method_invocation_get_connection(invocation);
223
224 if (dpy->clipboard_proxy) {
225 g_dbus_method_invocation_return_error(
226 invocation,
227 DBUS_DISPLAY_ERROR,
228 DBUS_DISPLAY_ERROR_FAILED,
229 "Clipboard peer already registered!");
230 return DBUS_METHOD_INVOCATION_HANDLED;
231 }
232
233 dpy->clipboard_proxy =
234 qemu_dbus_display1_clipboard_proxy_new_sync(
235 connection,
236 G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
237 g_dbus_method_invocation_get_sender(invocation),
238 "/org/qemu/Display1/Clipboard",
239 NULL,
240 &err);
241 if (!dpy->clipboard_proxy) {
242 g_dbus_method_invocation_return_error(
243 invocation,
244 DBUS_DISPLAY_ERROR,
245 DBUS_DISPLAY_ERROR_FAILED,
246 "Failed to setup proxy: %s", err->message);
247 return DBUS_METHOD_INVOCATION_HANDLED;
248 }
249
250 name = g_dbus_proxy_get_name(G_DBUS_PROXY(dpy->clipboard_proxy));
251 trace_dbus_clipboard_register(name);
252
253 g_object_connect(dpy->clipboard_proxy,
254 "swapped-signal::notify::g-name-owner",
255 dbus_clipboard_unregister_proxy, dpy,
256 NULL);
257 g_object_connect(connection,
258 "swapped-signal::closed",
259 dbus_clipboard_unregister_proxy, dpy,
260 NULL);
261 qemu_clipboard_reset_serial();
262
263 qemu_dbus_display1_clipboard_complete_register(dpy->clipboard, invocation);
264 return DBUS_METHOD_INVOCATION_HANDLED;
265 }
266
267 static gboolean
268 dbus_clipboard_check_caller(DBusDisplay *dpy, GDBusMethodInvocation *invocation)
269 {
270 if (!dpy->clipboard_proxy ||
271 g_strcmp0(g_dbus_proxy_get_name(G_DBUS_PROXY(dpy->clipboard_proxy)),
272 g_dbus_method_invocation_get_sender(invocation))) {
273 g_dbus_method_invocation_return_error(
274 invocation,
275 DBUS_DISPLAY_ERROR,
276 DBUS_DISPLAY_ERROR_FAILED,
277 "Unregistered caller");
278 return FALSE;
279 }
280
281 return TRUE;
282 }
283
284 static gboolean
285 dbus_clipboard_unregister(
286 DBusDisplay *dpy,
287 GDBusMethodInvocation *invocation)
288 {
289 if (!dbus_clipboard_check_caller(dpy, invocation)) {
290 return DBUS_METHOD_INVOCATION_HANDLED;
291 }
292
293 dbus_clipboard_unregister_proxy(dpy);
294
295 qemu_dbus_display1_clipboard_complete_unregister(
296 dpy->clipboard, invocation);
297
298 return DBUS_METHOD_INVOCATION_HANDLED;
299 }
300
301 static gboolean
302 dbus_clipboard_grab(
303 DBusDisplay *dpy,
304 GDBusMethodInvocation *invocation,
305 gint arg_selection,
306 guint arg_serial,
307 const gchar *const *arg_mimes)
308 {
309 QemuClipboardSelection s = arg_selection;
310 g_autoptr(QemuClipboardInfo) info = NULL;
311
312 if (!dbus_clipboard_check_caller(dpy, invocation)) {
313 return DBUS_METHOD_INVOCATION_HANDLED;
314 }
315
316 trace_dbus_clipboard_grab(arg_selection, arg_serial);
317
318 if (s >= QEMU_CLIPBOARD_SELECTION__COUNT) {
319 g_dbus_method_invocation_return_error(
320 invocation,
321 DBUS_DISPLAY_ERROR,
322 DBUS_DISPLAY_ERROR_FAILED,
323 "Invalid clipboard selection: %d", arg_selection);
324 return DBUS_METHOD_INVOCATION_HANDLED;
325 }
326
327 info = qemu_clipboard_info_new(&dpy->clipboard_peer, s);
328 if (g_strv_contains(arg_mimes, MIME_TEXT_PLAIN_UTF8)) {
329 info->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true;
330 }
331 info->serial = arg_serial;
332 info->has_serial = true;
333 if (qemu_clipboard_check_serial(info, true)) {
334 qemu_clipboard_update(info);
335 } else {
336 trace_dbus_clipboard_grab_failed();
337 }
338
339 qemu_dbus_display1_clipboard_complete_grab(dpy->clipboard, invocation);
340 return DBUS_METHOD_INVOCATION_HANDLED;
341 }
342
343 static gboolean
344 dbus_clipboard_release(
345 DBusDisplay *dpy,
346 GDBusMethodInvocation *invocation,
347 gint arg_selection)
348 {
349 if (!dbus_clipboard_check_caller(dpy, invocation)) {
350 return DBUS_METHOD_INVOCATION_HANDLED;
351 }
352
353 qemu_clipboard_peer_release(&dpy->clipboard_peer, arg_selection);
354
355 qemu_dbus_display1_clipboard_complete_release(dpy->clipboard, invocation);
356 return DBUS_METHOD_INVOCATION_HANDLED;
357 }
358
359 static gboolean
360 dbus_clipboard_request_timeout(gpointer user_data)
361 {
362 dbus_clipboard_request_cancelled(user_data);
363 return G_SOURCE_REMOVE;
364 }
365
366 static gboolean
367 dbus_clipboard_request(
368 DBusDisplay *dpy,
369 GDBusMethodInvocation *invocation,
370 gint arg_selection,
371 const gchar *const *arg_mimes)
372 {
373 QemuClipboardSelection s = arg_selection;
374 QemuClipboardType type = QEMU_CLIPBOARD_TYPE_TEXT;
375 QemuClipboardInfo *info = NULL;
376
377 if (!dbus_clipboard_check_caller(dpy, invocation)) {
378 return DBUS_METHOD_INVOCATION_HANDLED;
379 }
380
381 if (s >= QEMU_CLIPBOARD_SELECTION__COUNT) {
382 g_dbus_method_invocation_return_error(
383 invocation,
384 DBUS_DISPLAY_ERROR,
385 DBUS_DISPLAY_ERROR_FAILED,
386 "Invalid clipboard selection: %d", arg_selection);
387 return DBUS_METHOD_INVOCATION_HANDLED;
388 }
389
390 if (dpy->clipboard_request[s].invocation) {
391 g_dbus_method_invocation_return_error(
392 invocation,
393 DBUS_DISPLAY_ERROR,
394 DBUS_DISPLAY_ERROR_FAILED,
395 "Pending request");
396 return DBUS_METHOD_INVOCATION_HANDLED;
397 }
398
399 info = qemu_clipboard_info(s);
400 if (!info || !info->owner || info->owner == &dpy->clipboard_peer) {
401 g_dbus_method_invocation_return_error(
402 invocation,
403 DBUS_DISPLAY_ERROR,
404 DBUS_DISPLAY_ERROR_FAILED,
405 "Empty clipboard");
406 return DBUS_METHOD_INVOCATION_HANDLED;
407 }
408
409 if (!g_strv_contains(arg_mimes, MIME_TEXT_PLAIN_UTF8) ||
410 !info->types[type].available) {
411 g_dbus_method_invocation_return_error(
412 invocation,
413 DBUS_DISPLAY_ERROR,
414 DBUS_DISPLAY_ERROR_FAILED,
415 "Unhandled MIME types requested");
416 return DBUS_METHOD_INVOCATION_HANDLED;
417 }
418
419 if (info->types[type].data) {
420 dbus_clipboard_complete_request(dpy, invocation, info, type);
421 } else {
422 qemu_clipboard_request(info, type);
423
424 dpy->clipboard_request[s].invocation = g_object_ref(invocation);
425 dpy->clipboard_request[s].type = type;
426 dpy->clipboard_request[s].timeout_id =
427 g_timeout_add_seconds(5, dbus_clipboard_request_timeout,
428 &dpy->clipboard_request[s]);
429 }
430
431 return DBUS_METHOD_INVOCATION_HANDLED;
432 }
433
434 void
435 dbus_clipboard_fini(DBusDisplay *dpy)
436 {
437 dbus_clipboard_unregister_proxy(dpy);
438 qemu_clipboard_peer_unregister(&dpy->clipboard_peer);
439 g_clear_object(&dpy->clipboard);
440 }
441
442 void
443 dbus_clipboard_init(DBusDisplay *dpy)
444 {
445 g_autoptr(GDBusObjectSkeleton) clipboard = NULL;
446
447 assert(!dpy->clipboard);
448
449 clipboard = g_dbus_object_skeleton_new(DBUS_DISPLAY1_ROOT "/Clipboard");
450 dpy->clipboard = qemu_dbus_display1_clipboard_skeleton_new();
451 g_object_connect(dpy->clipboard,
452 "swapped-signal::handle-register",
453 dbus_clipboard_register, dpy,
454 "swapped-signal::handle-unregister",
455 dbus_clipboard_unregister, dpy,
456 "swapped-signal::handle-grab",
457 dbus_clipboard_grab, dpy,
458 "swapped-signal::handle-release",
459 dbus_clipboard_release, dpy,
460 "swapped-signal::handle-request",
461 dbus_clipboard_request, dpy,
462 NULL);
463
464 g_dbus_object_skeleton_add_interface(
465 G_DBUS_OBJECT_SKELETON(clipboard),
466 G_DBUS_INTERFACE_SKELETON(dpy->clipboard));
467 g_dbus_object_manager_server_export(dpy->server, clipboard);
468 dpy->clipboard_peer.name = "dbus";
469 dpy->clipboard_peer.notifier.notify = dbus_clipboard_notify;
470 dpy->clipboard_peer.request = dbus_clipboard_qemu_request;
471 qemu_clipboard_peer_register(&dpy->clipboard_peer);
472 }