Merge tag 'qemu-macppc-20230206' of https://github.com/mcayland/qemu into staging
[qemu.git] / ui / dbus-clipboard.c
blob5843d26cd2cb6b681c1f85cda4fe7cdf707904f2
1 /*
2 * QEMU DBus display
4 * Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com>
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:
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
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.
24 #include "qemu/osdep.h"
25 #include "qemu/dbus.h"
26 #include "qemu/main-loop.h"
27 #include "qom/object_interfaces.h"
28 #include "sysemu/sysemu.h"
29 #include "qapi/error.h"
30 #include "trace.h"
32 #include "dbus.h"
34 #define MIME_TEXT_PLAIN_UTF8 "text/plain;charset=utf-8"
36 static void
37 dbus_clipboard_complete_request(
38 DBusDisplay *dpy,
39 GDBusMethodInvocation *invocation,
40 QemuClipboardInfo *info,
41 QemuClipboardType type)
43 GVariant *v_data = g_variant_new_from_data(
44 G_VARIANT_TYPE("ay"),
45 info->types[type].data,
46 info->types[type].size,
47 TRUE,
48 (GDestroyNotify)qemu_clipboard_info_unref,
49 qemu_clipboard_info_ref(info));
51 qemu_dbus_display1_clipboard_complete_request(
52 dpy->clipboard, invocation,
53 MIME_TEXT_PLAIN_UTF8, v_data);
56 static void
57 dbus_clipboard_update_info(DBusDisplay *dpy, QemuClipboardInfo *info)
59 bool self_update = info->owner == &dpy->clipboard_peer;
60 const char *mime[QEMU_CLIPBOARD_TYPE__COUNT + 1] = { 0, };
61 DBusClipboardRequest *req;
62 int i = 0;
64 if (info->owner == NULL) {
65 if (dpy->clipboard_proxy) {
66 qemu_dbus_display1_clipboard_call_release(
67 dpy->clipboard_proxy,
68 info->selection,
69 G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
71 return;
74 if (self_update || !info->has_serial) {
75 return;
78 req = &dpy->clipboard_request[info->selection];
79 if (req->invocation && info->types[req->type].data) {
80 dbus_clipboard_complete_request(dpy, req->invocation, info, req->type);
81 g_clear_object(&req->invocation);
82 g_source_remove(req->timeout_id);
83 req->timeout_id = 0;
84 return;
87 if (info->types[QEMU_CLIPBOARD_TYPE_TEXT].available) {
88 mime[i++] = MIME_TEXT_PLAIN_UTF8;
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);
103 static void
104 dbus_clipboard_reset_serial(DBusDisplay *dpy)
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);
114 static void
115 dbus_clipboard_notify(Notifier *notifier, void *data)
117 DBusDisplay *dpy =
118 container_of(notifier, DBusDisplay, clipboard_peer.notifier);
119 QemuClipboardNotify *notify = data;
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;
131 static void
132 dbus_clipboard_qemu_request(QemuClipboardInfo *info,
133 QemuClipboardType type)
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;
143 if (type != QEMU_CLIPBOARD_TYPE_TEXT) {
144 /* unsupported atm */
145 return;
148 if (dpy->clipboard_proxy) {
149 if (!qemu_dbus_display1_clipboard_call_request_sync(
150 dpy->clipboard_proxy,
151 info->selection,
152 mimes,
153 G_DBUS_CALL_FLAGS_NONE, -1, &mime, &v_data, NULL, &err)) {
154 error_report("Failed to request clipboard: %s", err->message);
155 return;
158 if (g_strcmp0(mime, MIME_TEXT_PLAIN_UTF8)) {
159 error_report("Unsupported returned MIME: %s", mime);
160 return;
163 data = g_variant_get_fixed_array(v_data, &n, 1);
164 qemu_clipboard_set_data(&dpy->clipboard_peer, info, type,
165 n, data, true);
169 static void
170 dbus_clipboard_request_cancelled(DBusClipboardRequest *req)
172 if (!req->invocation) {
173 return;
176 g_dbus_method_invocation_return_error(
177 req->invocation,
178 DBUS_DISPLAY_ERROR,
179 DBUS_DISPLAY_ERROR_FAILED,
180 "Cancelled clipboard request");
182 g_clear_object(&req->invocation);
183 g_source_remove(req->timeout_id);
184 req->timeout_id = 0;
187 static void
188 dbus_clipboard_unregister_proxy(DBusDisplay *dpy)
190 const char *name = NULL;
191 int i;
193 for (i = 0; i < G_N_ELEMENTS(dpy->clipboard_request); ++i) {
194 dbus_clipboard_request_cancelled(&dpy->clipboard_request[i]);
197 if (!dpy->clipboard_proxy) {
198 return;
201 name = g_dbus_proxy_get_name(G_DBUS_PROXY(dpy->clipboard_proxy));
202 trace_dbus_clipboard_unregister(name);
203 g_clear_object(&dpy->clipboard_proxy);
206 static void
207 dbus_on_clipboard_proxy_name_owner_changed(
208 DBusDisplay *dpy,
209 GObject *object,
210 GParamSpec *pspec)
212 dbus_clipboard_unregister_proxy(dpy);
215 static gboolean
216 dbus_clipboard_register(
217 DBusDisplay *dpy,
218 GDBusMethodInvocation *invocation)
220 g_autoptr(GError) err = NULL;
221 const char *name = NULL;
223 if (dpy->clipboard_proxy) {
224 g_dbus_method_invocation_return_error(
225 invocation,
226 DBUS_DISPLAY_ERROR,
227 DBUS_DISPLAY_ERROR_FAILED,
228 "Clipboard peer already registered!");
229 return DBUS_METHOD_INVOCATION_HANDLED;
232 dpy->clipboard_proxy =
233 qemu_dbus_display1_clipboard_proxy_new_sync(
234 g_dbus_method_invocation_get_connection(invocation),
235 G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
236 g_dbus_method_invocation_get_sender(invocation),
237 "/org/qemu/Display1/Clipboard",
238 NULL,
239 &err);
240 if (!dpy->clipboard_proxy) {
241 g_dbus_method_invocation_return_error(
242 invocation,
243 DBUS_DISPLAY_ERROR,
244 DBUS_DISPLAY_ERROR_FAILED,
245 "Failed to setup proxy: %s", err->message);
246 return DBUS_METHOD_INVOCATION_HANDLED;
249 name = g_dbus_proxy_get_name(G_DBUS_PROXY(dpy->clipboard_proxy));
250 trace_dbus_clipboard_register(name);
252 g_object_connect(dpy->clipboard_proxy,
253 "swapped-signal::notify::g-name-owner",
254 dbus_on_clipboard_proxy_name_owner_changed, dpy,
255 NULL);
256 qemu_clipboard_reset_serial();
258 qemu_dbus_display1_clipboard_complete_register(dpy->clipboard, invocation);
259 return DBUS_METHOD_INVOCATION_HANDLED;
262 static gboolean
263 dbus_clipboard_check_caller(DBusDisplay *dpy, GDBusMethodInvocation *invocation)
265 if (!dpy->clipboard_proxy ||
266 g_strcmp0(g_dbus_proxy_get_name(G_DBUS_PROXY(dpy->clipboard_proxy)),
267 g_dbus_method_invocation_get_sender(invocation))) {
268 g_dbus_method_invocation_return_error(
269 invocation,
270 DBUS_DISPLAY_ERROR,
271 DBUS_DISPLAY_ERROR_FAILED,
272 "Unregistered caller");
273 return FALSE;
276 return TRUE;
279 static gboolean
280 dbus_clipboard_unregister(
281 DBusDisplay *dpy,
282 GDBusMethodInvocation *invocation)
284 if (!dbus_clipboard_check_caller(dpy, invocation)) {
285 return DBUS_METHOD_INVOCATION_HANDLED;
288 dbus_clipboard_unregister_proxy(dpy);
290 qemu_dbus_display1_clipboard_complete_unregister(
291 dpy->clipboard, invocation);
293 return DBUS_METHOD_INVOCATION_HANDLED;
296 static gboolean
297 dbus_clipboard_grab(
298 DBusDisplay *dpy,
299 GDBusMethodInvocation *invocation,
300 gint arg_selection,
301 guint arg_serial,
302 const gchar *const *arg_mimes)
304 QemuClipboardSelection s = arg_selection;
305 g_autoptr(QemuClipboardInfo) info = NULL;
307 if (!dbus_clipboard_check_caller(dpy, invocation)) {
308 return DBUS_METHOD_INVOCATION_HANDLED;
311 if (s >= QEMU_CLIPBOARD_SELECTION__COUNT) {
312 g_dbus_method_invocation_return_error(
313 invocation,
314 DBUS_DISPLAY_ERROR,
315 DBUS_DISPLAY_ERROR_FAILED,
316 "Invalid clipboard selection: %d", arg_selection);
317 return DBUS_METHOD_INVOCATION_HANDLED;
320 info = qemu_clipboard_info_new(&dpy->clipboard_peer, s);
321 if (g_strv_contains(arg_mimes, MIME_TEXT_PLAIN_UTF8)) {
322 info->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true;
324 info->serial = arg_serial;
325 info->has_serial = true;
326 if (qemu_clipboard_check_serial(info, true)) {
327 qemu_clipboard_update(info);
328 } else {
329 trace_dbus_clipboard_grab_failed();
332 qemu_dbus_display1_clipboard_complete_grab(dpy->clipboard, invocation);
333 return DBUS_METHOD_INVOCATION_HANDLED;
336 static gboolean
337 dbus_clipboard_release(
338 DBusDisplay *dpy,
339 GDBusMethodInvocation *invocation,
340 gint arg_selection)
342 if (!dbus_clipboard_check_caller(dpy, invocation)) {
343 return DBUS_METHOD_INVOCATION_HANDLED;
346 qemu_clipboard_peer_release(&dpy->clipboard_peer, arg_selection);
348 qemu_dbus_display1_clipboard_complete_release(dpy->clipboard, invocation);
349 return DBUS_METHOD_INVOCATION_HANDLED;
352 static gboolean
353 dbus_clipboard_request_timeout(gpointer user_data)
355 dbus_clipboard_request_cancelled(user_data);
356 return G_SOURCE_REMOVE;
359 static gboolean
360 dbus_clipboard_request(
361 DBusDisplay *dpy,
362 GDBusMethodInvocation *invocation,
363 gint arg_selection,
364 const gchar *const *arg_mimes)
366 QemuClipboardSelection s = arg_selection;
367 QemuClipboardType type = QEMU_CLIPBOARD_TYPE_TEXT;
368 QemuClipboardInfo *info = NULL;
370 if (!dbus_clipboard_check_caller(dpy, invocation)) {
371 return DBUS_METHOD_INVOCATION_HANDLED;
374 if (s >= QEMU_CLIPBOARD_SELECTION__COUNT) {
375 g_dbus_method_invocation_return_error(
376 invocation,
377 DBUS_DISPLAY_ERROR,
378 DBUS_DISPLAY_ERROR_FAILED,
379 "Invalid clipboard selection: %d", arg_selection);
380 return DBUS_METHOD_INVOCATION_HANDLED;
383 if (dpy->clipboard_request[s].invocation) {
384 g_dbus_method_invocation_return_error(
385 invocation,
386 DBUS_DISPLAY_ERROR,
387 DBUS_DISPLAY_ERROR_FAILED,
388 "Pending request");
389 return DBUS_METHOD_INVOCATION_HANDLED;
392 info = qemu_clipboard_info(s);
393 if (!info || !info->owner || info->owner == &dpy->clipboard_peer) {
394 g_dbus_method_invocation_return_error(
395 invocation,
396 DBUS_DISPLAY_ERROR,
397 DBUS_DISPLAY_ERROR_FAILED,
398 "Empty clipboard");
399 return DBUS_METHOD_INVOCATION_HANDLED;
402 if (!g_strv_contains(arg_mimes, MIME_TEXT_PLAIN_UTF8) ||
403 !info->types[type].available) {
404 g_dbus_method_invocation_return_error(
405 invocation,
406 DBUS_DISPLAY_ERROR,
407 DBUS_DISPLAY_ERROR_FAILED,
408 "Unhandled MIME types requested");
409 return DBUS_METHOD_INVOCATION_HANDLED;
412 if (info->types[type].data) {
413 dbus_clipboard_complete_request(dpy, invocation, info, type);
414 } else {
415 qemu_clipboard_request(info, type);
417 dpy->clipboard_request[s].invocation = g_object_ref(invocation);
418 dpy->clipboard_request[s].type = type;
419 dpy->clipboard_request[s].timeout_id =
420 g_timeout_add_seconds(5, dbus_clipboard_request_timeout,
421 &dpy->clipboard_request[s]);
424 return DBUS_METHOD_INVOCATION_HANDLED;
427 void
428 dbus_clipboard_init(DBusDisplay *dpy)
430 g_autoptr(GDBusObjectSkeleton) clipboard = NULL;
432 assert(!dpy->clipboard);
434 clipboard = g_dbus_object_skeleton_new(DBUS_DISPLAY1_ROOT "/Clipboard");
435 dpy->clipboard = qemu_dbus_display1_clipboard_skeleton_new();
436 g_object_connect(dpy->clipboard,
437 "swapped-signal::handle-register",
438 dbus_clipboard_register, dpy,
439 "swapped-signal::handle-unregister",
440 dbus_clipboard_unregister, dpy,
441 "swapped-signal::handle-grab",
442 dbus_clipboard_grab, dpy,
443 "swapped-signal::handle-release",
444 dbus_clipboard_release, dpy,
445 "swapped-signal::handle-request",
446 dbus_clipboard_request, dpy,
447 NULL);
449 g_dbus_object_skeleton_add_interface(
450 G_DBUS_OBJECT_SKELETON(clipboard),
451 G_DBUS_INTERFACE_SKELETON(dpy->clipboard));
452 g_dbus_object_manager_server_export(dpy->server, clipboard);
453 dpy->clipboard_peer.name = "dbus";
454 dpy->clipboard_peer.notifier.notify = dbus_clipboard_notify;
455 dpy->clipboard_peer.request = dbus_clipboard_qemu_request;
456 qemu_clipboard_peer_register(&dpy->clipboard_peer);