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 if (type
!= QEMU_CLIPBOARD_TYPE_TEXT
) {
145 /* unsupported atm */
149 if (dpy
->clipboard_proxy
) {
150 if (!qemu_dbus_display1_clipboard_call_request_sync(
151 dpy
->clipboard_proxy
,
154 G_DBUS_CALL_FLAGS_NONE
, -1, &mime
, &v_data
, NULL
, &err
)) {
155 error_report("Failed to request clipboard: %s", err
->message
);
159 if (g_strcmp0(mime
, MIME_TEXT_PLAIN_UTF8
)) {
160 error_report("Unsupported returned MIME: %s", mime
);
164 data
= g_variant_get_fixed_array(v_data
, &n
, 1);
165 qemu_clipboard_set_data(&dpy
->clipboard_peer
, info
, type
,
171 dbus_clipboard_request_cancelled(DBusClipboardRequest
*req
)
173 if (!req
->invocation
) {
177 g_dbus_method_invocation_return_error(
180 DBUS_DISPLAY_ERROR_FAILED
,
181 "Cancelled clipboard request");
183 g_clear_object(&req
->invocation
);
184 g_source_remove(req
->timeout_id
);
189 dbus_clipboard_unregister_proxy(DBusDisplay
*dpy
)
191 const char *name
= NULL
;
194 for (i
= 0; i
< G_N_ELEMENTS(dpy
->clipboard_request
); ++i
) {
195 dbus_clipboard_request_cancelled(&dpy
->clipboard_request
[i
]);
198 if (!dpy
->clipboard_proxy
) {
202 name
= g_dbus_proxy_get_name(G_DBUS_PROXY(dpy
->clipboard_proxy
));
203 trace_dbus_clipboard_unregister(name
);
204 g_clear_object(&dpy
->clipboard_proxy
);
208 dbus_on_clipboard_proxy_name_owner_changed(
213 dbus_clipboard_unregister_proxy(dpy
);
217 dbus_clipboard_register(
219 GDBusMethodInvocation
*invocation
)
221 g_autoptr(GError
) err
= NULL
;
222 const char *name
= NULL
;
224 if (dpy
->clipboard_proxy
) {
225 g_dbus_method_invocation_return_error(
228 DBUS_DISPLAY_ERROR_FAILED
,
229 "Clipboard peer already registered!");
230 return DBUS_METHOD_INVOCATION_HANDLED
;
233 dpy
->clipboard_proxy
=
234 qemu_dbus_display1_clipboard_proxy_new_sync(
235 g_dbus_method_invocation_get_connection(invocation
),
236 G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START
,
237 g_dbus_method_invocation_get_sender(invocation
),
238 "/org/qemu/Display1/Clipboard",
241 if (!dpy
->clipboard_proxy
) {
242 g_dbus_method_invocation_return_error(
245 DBUS_DISPLAY_ERROR_FAILED
,
246 "Failed to setup proxy: %s", err
->message
);
247 return DBUS_METHOD_INVOCATION_HANDLED
;
250 name
= g_dbus_proxy_get_name(G_DBUS_PROXY(dpy
->clipboard_proxy
));
251 trace_dbus_clipboard_register(name
);
253 g_object_connect(dpy
->clipboard_proxy
,
254 "swapped-signal::notify::g-name-owner",
255 dbus_on_clipboard_proxy_name_owner_changed
, dpy
,
257 qemu_clipboard_reset_serial();
259 qemu_dbus_display1_clipboard_complete_register(dpy
->clipboard
, invocation
);
260 return DBUS_METHOD_INVOCATION_HANDLED
;
264 dbus_clipboard_check_caller(DBusDisplay
*dpy
, GDBusMethodInvocation
*invocation
)
266 if (!dpy
->clipboard_proxy
||
267 g_strcmp0(g_dbus_proxy_get_name(G_DBUS_PROXY(dpy
->clipboard_proxy
)),
268 g_dbus_method_invocation_get_sender(invocation
))) {
269 g_dbus_method_invocation_return_error(
272 DBUS_DISPLAY_ERROR_FAILED
,
273 "Unregistered caller");
281 dbus_clipboard_unregister(
283 GDBusMethodInvocation
*invocation
)
285 if (!dbus_clipboard_check_caller(dpy
, invocation
)) {
286 return DBUS_METHOD_INVOCATION_HANDLED
;
289 dbus_clipboard_unregister_proxy(dpy
);
291 qemu_dbus_display1_clipboard_complete_unregister(
292 dpy
->clipboard
, invocation
);
294 return DBUS_METHOD_INVOCATION_HANDLED
;
300 GDBusMethodInvocation
*invocation
,
303 const gchar
*const *arg_mimes
)
305 QemuClipboardSelection s
= arg_selection
;
306 g_autoptr(QemuClipboardInfo
) info
= NULL
;
308 if (!dbus_clipboard_check_caller(dpy
, invocation
)) {
309 return DBUS_METHOD_INVOCATION_HANDLED
;
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
);