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
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 "sysemu/sysemu.h"
30 #include "qapi/error.h"
35 #define MIME_TEXT_PLAIN_UTF8 "text/plain;charset=utf-8"
38 dbus_clipboard_complete_request(
40 GDBusMethodInvocation
*invocation
,
41 QemuClipboardInfo
*info
,
42 QemuClipboardType type
)
44 GVariant
*v_data
= g_variant_new_from_data(
46 info
->types
[type
].data
,
47 info
->types
[type
].size
,
49 (GDestroyNotify
)qemu_clipboard_info_unref
,
50 qemu_clipboard_info_ref(info
));
52 qemu_dbus_display1_clipboard_complete_request(
53 dpy
->clipboard
, invocation
,
54 MIME_TEXT_PLAIN_UTF8
, v_data
);
58 dbus_clipboard_update_info(DBusDisplay
*dpy
, QemuClipboardInfo
*info
)
60 bool self_update
= info
->owner
== &dpy
->clipboard_peer
;
61 const char *mime
[QEMU_CLIPBOARD_TYPE__COUNT
+ 1] = { 0, };
62 DBusClipboardRequest
*req
;
65 if (info
->owner
== NULL
) {
66 if (dpy
->clipboard_proxy
) {
67 qemu_dbus_display1_clipboard_call_release(
70 G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
75 if (self_update
|| !info
->has_serial
) {
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_source_remove(req
->timeout_id
);
88 if (info
->types
[QEMU_CLIPBOARD_TYPE_TEXT
].available
) {
89 mime
[i
++] = MIME_TEXT_PLAIN_UTF8
;
93 if (dpy
->clipboard_proxy
) {
94 qemu_dbus_display1_clipboard_call_grab(
99 G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
105 dbus_clipboard_reset_serial(DBusDisplay
*dpy
)
107 if (dpy
->clipboard_proxy
) {
108 qemu_dbus_display1_clipboard_call_register(
109 dpy
->clipboard_proxy
,
110 G_DBUS_CALL_FLAGS_NONE
,
111 -1, NULL
, NULL
, NULL
);
116 dbus_clipboard_notify(Notifier
*notifier
, void *data
)
119 container_of(notifier
, DBusDisplay
, clipboard_peer
.notifier
);
120 QemuClipboardNotify
*notify
= data
;
122 switch (notify
->type
) {
123 case QEMU_CLIPBOARD_UPDATE_INFO
:
124 dbus_clipboard_update_info(dpy
, notify
->info
);
126 case QEMU_CLIPBOARD_RESET_SERIAL
:
127 dbus_clipboard_reset_serial(dpy
);
133 dbus_clipboard_qemu_request(QemuClipboardInfo
*info
,
134 QemuClipboardType type
)
136 DBusDisplay
*dpy
= container_of(info
->owner
, DBusDisplay
, clipboard_peer
);
137 g_autofree
char *mime
= NULL
;
138 g_autoptr(GVariant
) v_data
= NULL
;
139 g_autoptr(GError
) err
= NULL
;
140 const char *data
= NULL
;
141 const char *mimes
[] = { MIME_TEXT_PLAIN_UTF8
, NULL
};
144 trace_dbus_clipboard_qemu_request(type
);
146 if (type
!= QEMU_CLIPBOARD_TYPE_TEXT
) {
147 /* unsupported atm */
151 if (dpy
->clipboard_proxy
) {
152 if (!qemu_dbus_display1_clipboard_call_request_sync(
153 dpy
->clipboard_proxy
,
156 G_DBUS_CALL_FLAGS_NONE
, -1, &mime
, &v_data
, NULL
, &err
)) {
157 error_report("Failed to request clipboard: %s", err
->message
);
161 if (g_strcmp0(mime
, MIME_TEXT_PLAIN_UTF8
)) {
162 error_report("Unsupported returned MIME: %s", mime
);
166 data
= g_variant_get_fixed_array(v_data
, &n
, 1);
167 qemu_clipboard_set_data(&dpy
->clipboard_peer
, info
, type
,
173 dbus_clipboard_request_cancelled(DBusClipboardRequest
*req
)
175 if (!req
->invocation
) {
179 g_dbus_method_invocation_return_error(
182 DBUS_DISPLAY_ERROR_FAILED
,
183 "Cancelled clipboard request");
185 g_clear_object(&req
->invocation
);
186 g_source_remove(req
->timeout_id
);
191 dbus_clipboard_unregister_proxy(DBusDisplay
*dpy
)
193 const char *name
= NULL
;
196 for (i
= 0; i
< G_N_ELEMENTS(dpy
->clipboard_request
); ++i
) {
197 dbus_clipboard_request_cancelled(&dpy
->clipboard_request
[i
]);
200 if (!dpy
->clipboard_proxy
) {
204 name
= g_dbus_proxy_get_name(G_DBUS_PROXY(dpy
->clipboard_proxy
));
205 trace_dbus_clipboard_unregister(name
);
206 g_clear_object(&dpy
->clipboard_proxy
);
210 dbus_clipboard_register(
212 GDBusMethodInvocation
*invocation
)
214 g_autoptr(GError
) err
= NULL
;
215 const char *name
= NULL
;
216 GDBusConnection
*connection
= g_dbus_method_invocation_get_connection(invocation
);
218 if (dpy
->clipboard_proxy
) {
219 g_dbus_method_invocation_return_error(
222 DBUS_DISPLAY_ERROR_FAILED
,
223 "Clipboard peer already registered!");
224 return DBUS_METHOD_INVOCATION_HANDLED
;
227 dpy
->clipboard_proxy
=
228 qemu_dbus_display1_clipboard_proxy_new_sync(
230 G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START
,
231 g_dbus_method_invocation_get_sender(invocation
),
232 "/org/qemu/Display1/Clipboard",
235 if (!dpy
->clipboard_proxy
) {
236 g_dbus_method_invocation_return_error(
239 DBUS_DISPLAY_ERROR_FAILED
,
240 "Failed to setup proxy: %s", err
->message
);
241 return DBUS_METHOD_INVOCATION_HANDLED
;
244 name
= g_dbus_proxy_get_name(G_DBUS_PROXY(dpy
->clipboard_proxy
));
245 trace_dbus_clipboard_register(name
);
247 g_object_connect(dpy
->clipboard_proxy
,
248 "swapped-signal::notify::g-name-owner",
249 dbus_clipboard_unregister_proxy
, dpy
,
251 g_object_connect(connection
,
252 "swapped-signal::closed",
253 dbus_clipboard_unregister_proxy
, dpy
,
255 qemu_clipboard_reset_serial();
257 qemu_dbus_display1_clipboard_complete_register(dpy
->clipboard
, invocation
);
258 return DBUS_METHOD_INVOCATION_HANDLED
;
262 dbus_clipboard_check_caller(DBusDisplay
*dpy
, GDBusMethodInvocation
*invocation
)
264 if (!dpy
->clipboard_proxy
||
265 g_strcmp0(g_dbus_proxy_get_name(G_DBUS_PROXY(dpy
->clipboard_proxy
)),
266 g_dbus_method_invocation_get_sender(invocation
))) {
267 g_dbus_method_invocation_return_error(
270 DBUS_DISPLAY_ERROR_FAILED
,
271 "Unregistered caller");
279 dbus_clipboard_unregister(
281 GDBusMethodInvocation
*invocation
)
283 if (!dbus_clipboard_check_caller(dpy
, invocation
)) {
284 return DBUS_METHOD_INVOCATION_HANDLED
;
287 dbus_clipboard_unregister_proxy(dpy
);
289 qemu_dbus_display1_clipboard_complete_unregister(
290 dpy
->clipboard
, invocation
);
292 return DBUS_METHOD_INVOCATION_HANDLED
;
298 GDBusMethodInvocation
*invocation
,
301 const gchar
*const *arg_mimes
)
303 QemuClipboardSelection s
= arg_selection
;
304 g_autoptr(QemuClipboardInfo
) info
= NULL
;
306 if (!dbus_clipboard_check_caller(dpy
, invocation
)) {
307 return DBUS_METHOD_INVOCATION_HANDLED
;
310 trace_dbus_clipboard_grab(arg_selection
, arg_serial
);
312 if (s
>= QEMU_CLIPBOARD_SELECTION__COUNT
) {
313 g_dbus_method_invocation_return_error(
316 DBUS_DISPLAY_ERROR_FAILED
,
317 "Invalid clipboard selection: %d", arg_selection
);
318 return DBUS_METHOD_INVOCATION_HANDLED
;
321 info
= qemu_clipboard_info_new(&dpy
->clipboard_peer
, s
);
322 if (g_strv_contains(arg_mimes
, MIME_TEXT_PLAIN_UTF8
)) {
323 info
->types
[QEMU_CLIPBOARD_TYPE_TEXT
].available
= true;
325 info
->serial
= arg_serial
;
326 info
->has_serial
= true;
327 if (qemu_clipboard_check_serial(info
, true)) {
328 qemu_clipboard_update(info
);
330 trace_dbus_clipboard_grab_failed();
333 qemu_dbus_display1_clipboard_complete_grab(dpy
->clipboard
, invocation
);
334 return DBUS_METHOD_INVOCATION_HANDLED
;
338 dbus_clipboard_release(
340 GDBusMethodInvocation
*invocation
,
343 if (!dbus_clipboard_check_caller(dpy
, invocation
)) {
344 return DBUS_METHOD_INVOCATION_HANDLED
;
347 qemu_clipboard_peer_release(&dpy
->clipboard_peer
, arg_selection
);
349 qemu_dbus_display1_clipboard_complete_release(dpy
->clipboard
, invocation
);
350 return DBUS_METHOD_INVOCATION_HANDLED
;
354 dbus_clipboard_request_timeout(gpointer user_data
)
356 dbus_clipboard_request_cancelled(user_data
);
357 return G_SOURCE_REMOVE
;
361 dbus_clipboard_request(
363 GDBusMethodInvocation
*invocation
,
365 const gchar
*const *arg_mimes
)
367 QemuClipboardSelection s
= arg_selection
;
368 QemuClipboardType type
= QEMU_CLIPBOARD_TYPE_TEXT
;
369 QemuClipboardInfo
*info
= NULL
;
371 if (!dbus_clipboard_check_caller(dpy
, invocation
)) {
372 return DBUS_METHOD_INVOCATION_HANDLED
;
375 if (s
>= QEMU_CLIPBOARD_SELECTION__COUNT
) {
376 g_dbus_method_invocation_return_error(
379 DBUS_DISPLAY_ERROR_FAILED
,
380 "Invalid clipboard selection: %d", arg_selection
);
381 return DBUS_METHOD_INVOCATION_HANDLED
;
384 if (dpy
->clipboard_request
[s
].invocation
) {
385 g_dbus_method_invocation_return_error(
388 DBUS_DISPLAY_ERROR_FAILED
,
390 return DBUS_METHOD_INVOCATION_HANDLED
;
393 info
= qemu_clipboard_info(s
);
394 if (!info
|| !info
->owner
|| info
->owner
== &dpy
->clipboard_peer
) {
395 g_dbus_method_invocation_return_error(
398 DBUS_DISPLAY_ERROR_FAILED
,
400 return DBUS_METHOD_INVOCATION_HANDLED
;
403 if (!g_strv_contains(arg_mimes
, MIME_TEXT_PLAIN_UTF8
) ||
404 !info
->types
[type
].available
) {
405 g_dbus_method_invocation_return_error(
408 DBUS_DISPLAY_ERROR_FAILED
,
409 "Unhandled MIME types requested");
410 return DBUS_METHOD_INVOCATION_HANDLED
;
413 if (info
->types
[type
].data
) {
414 dbus_clipboard_complete_request(dpy
, invocation
, info
, type
);
416 qemu_clipboard_request(info
, type
);
418 dpy
->clipboard_request
[s
].invocation
= g_object_ref(invocation
);
419 dpy
->clipboard_request
[s
].type
= type
;
420 dpy
->clipboard_request
[s
].timeout_id
=
421 g_timeout_add_seconds(5, dbus_clipboard_request_timeout
,
422 &dpy
->clipboard_request
[s
]);
425 return DBUS_METHOD_INVOCATION_HANDLED
;
429 dbus_clipboard_init(DBusDisplay
*dpy
)
431 g_autoptr(GDBusObjectSkeleton
) clipboard
= NULL
;
433 assert(!dpy
->clipboard
);
435 clipboard
= g_dbus_object_skeleton_new(DBUS_DISPLAY1_ROOT
"/Clipboard");
436 dpy
->clipboard
= qemu_dbus_display1_clipboard_skeleton_new();
437 g_object_connect(dpy
->clipboard
,
438 "swapped-signal::handle-register",
439 dbus_clipboard_register
, dpy
,
440 "swapped-signal::handle-unregister",
441 dbus_clipboard_unregister
, dpy
,
442 "swapped-signal::handle-grab",
443 dbus_clipboard_grab
, dpy
,
444 "swapped-signal::handle-release",
445 dbus_clipboard_release
, dpy
,
446 "swapped-signal::handle-request",
447 dbus_clipboard_request
, dpy
,
450 g_dbus_object_skeleton_add_interface(
451 G_DBUS_OBJECT_SKELETON(clipboard
),
452 G_DBUS_INTERFACE_SKELETON(dpy
->clipboard
));
453 g_dbus_object_manager_server_export(dpy
->server
, clipboard
);
454 dpy
->clipboard_peer
.name
= "dbus";
455 dpy
->clipboard_peer
.notifier
.notify
= dbus_clipboard_notify
;
456 dpy
->clipboard_peer
.request
= dbus_clipboard_qemu_request
;
457 qemu_clipboard_peer_register(&dpy
->clipboard_peer
);