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_clipboard_register(
210 GDBusMethodInvocation
*invocation
)
212 g_autoptr(GError
) err
= NULL
;
213 const char *name
= NULL
;
214 GDBusConnection
*connection
= g_dbus_method_invocation_get_connection(invocation
);
216 if (dpy
->clipboard_proxy
) {
217 g_dbus_method_invocation_return_error(
220 DBUS_DISPLAY_ERROR_FAILED
,
221 "Clipboard peer already registered!");
222 return DBUS_METHOD_INVOCATION_HANDLED
;
225 dpy
->clipboard_proxy
=
226 qemu_dbus_display1_clipboard_proxy_new_sync(
228 G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START
,
229 g_dbus_method_invocation_get_sender(invocation
),
230 "/org/qemu/Display1/Clipboard",
233 if (!dpy
->clipboard_proxy
) {
234 g_dbus_method_invocation_return_error(
237 DBUS_DISPLAY_ERROR_FAILED
,
238 "Failed to setup proxy: %s", err
->message
);
239 return DBUS_METHOD_INVOCATION_HANDLED
;
242 name
= g_dbus_proxy_get_name(G_DBUS_PROXY(dpy
->clipboard_proxy
));
243 trace_dbus_clipboard_register(name
);
245 g_object_connect(dpy
->clipboard_proxy
,
246 "swapped-signal::notify::g-name-owner",
247 dbus_clipboard_unregister_proxy
, dpy
,
249 g_object_connect(connection
,
250 "swapped-signal::closed",
251 dbus_clipboard_unregister_proxy
, dpy
,
253 qemu_clipboard_reset_serial();
255 qemu_dbus_display1_clipboard_complete_register(dpy
->clipboard
, invocation
);
256 return DBUS_METHOD_INVOCATION_HANDLED
;
260 dbus_clipboard_check_caller(DBusDisplay
*dpy
, GDBusMethodInvocation
*invocation
)
262 if (!dpy
->clipboard_proxy
||
263 g_strcmp0(g_dbus_proxy_get_name(G_DBUS_PROXY(dpy
->clipboard_proxy
)),
264 g_dbus_method_invocation_get_sender(invocation
))) {
265 g_dbus_method_invocation_return_error(
268 DBUS_DISPLAY_ERROR_FAILED
,
269 "Unregistered caller");
277 dbus_clipboard_unregister(
279 GDBusMethodInvocation
*invocation
)
281 if (!dbus_clipboard_check_caller(dpy
, invocation
)) {
282 return DBUS_METHOD_INVOCATION_HANDLED
;
285 dbus_clipboard_unregister_proxy(dpy
);
287 qemu_dbus_display1_clipboard_complete_unregister(
288 dpy
->clipboard
, invocation
);
290 return DBUS_METHOD_INVOCATION_HANDLED
;
296 GDBusMethodInvocation
*invocation
,
299 const gchar
*const *arg_mimes
)
301 QemuClipboardSelection s
= arg_selection
;
302 g_autoptr(QemuClipboardInfo
) info
= NULL
;
304 if (!dbus_clipboard_check_caller(dpy
, invocation
)) {
305 return DBUS_METHOD_INVOCATION_HANDLED
;
308 if (s
>= QEMU_CLIPBOARD_SELECTION__COUNT
) {
309 g_dbus_method_invocation_return_error(
312 DBUS_DISPLAY_ERROR_FAILED
,
313 "Invalid clipboard selection: %d", arg_selection
);
314 return DBUS_METHOD_INVOCATION_HANDLED
;
317 info
= qemu_clipboard_info_new(&dpy
->clipboard_peer
, s
);
318 if (g_strv_contains(arg_mimes
, MIME_TEXT_PLAIN_UTF8
)) {
319 info
->types
[QEMU_CLIPBOARD_TYPE_TEXT
].available
= true;
321 info
->serial
= arg_serial
;
322 info
->has_serial
= true;
323 if (qemu_clipboard_check_serial(info
, true)) {
324 qemu_clipboard_update(info
);
326 trace_dbus_clipboard_grab_failed();
329 qemu_dbus_display1_clipboard_complete_grab(dpy
->clipboard
, invocation
);
330 return DBUS_METHOD_INVOCATION_HANDLED
;
334 dbus_clipboard_release(
336 GDBusMethodInvocation
*invocation
,
339 if (!dbus_clipboard_check_caller(dpy
, invocation
)) {
340 return DBUS_METHOD_INVOCATION_HANDLED
;
343 qemu_clipboard_peer_release(&dpy
->clipboard_peer
, arg_selection
);
345 qemu_dbus_display1_clipboard_complete_release(dpy
->clipboard
, invocation
);
346 return DBUS_METHOD_INVOCATION_HANDLED
;
350 dbus_clipboard_request_timeout(gpointer user_data
)
352 dbus_clipboard_request_cancelled(user_data
);
353 return G_SOURCE_REMOVE
;
357 dbus_clipboard_request(
359 GDBusMethodInvocation
*invocation
,
361 const gchar
*const *arg_mimes
)
363 QemuClipboardSelection s
= arg_selection
;
364 QemuClipboardType type
= QEMU_CLIPBOARD_TYPE_TEXT
;
365 QemuClipboardInfo
*info
= NULL
;
367 if (!dbus_clipboard_check_caller(dpy
, invocation
)) {
368 return DBUS_METHOD_INVOCATION_HANDLED
;
371 if (s
>= QEMU_CLIPBOARD_SELECTION__COUNT
) {
372 g_dbus_method_invocation_return_error(
375 DBUS_DISPLAY_ERROR_FAILED
,
376 "Invalid clipboard selection: %d", arg_selection
);
377 return DBUS_METHOD_INVOCATION_HANDLED
;
380 if (dpy
->clipboard_request
[s
].invocation
) {
381 g_dbus_method_invocation_return_error(
384 DBUS_DISPLAY_ERROR_FAILED
,
386 return DBUS_METHOD_INVOCATION_HANDLED
;
389 info
= qemu_clipboard_info(s
);
390 if (!info
|| !info
->owner
|| info
->owner
== &dpy
->clipboard_peer
) {
391 g_dbus_method_invocation_return_error(
394 DBUS_DISPLAY_ERROR_FAILED
,
396 return DBUS_METHOD_INVOCATION_HANDLED
;
399 if (!g_strv_contains(arg_mimes
, MIME_TEXT_PLAIN_UTF8
) ||
400 !info
->types
[type
].available
) {
401 g_dbus_method_invocation_return_error(
404 DBUS_DISPLAY_ERROR_FAILED
,
405 "Unhandled MIME types requested");
406 return DBUS_METHOD_INVOCATION_HANDLED
;
409 if (info
->types
[type
].data
) {
410 dbus_clipboard_complete_request(dpy
, invocation
, info
, type
);
412 qemu_clipboard_request(info
, type
);
414 dpy
->clipboard_request
[s
].invocation
= g_object_ref(invocation
);
415 dpy
->clipboard_request
[s
].type
= type
;
416 dpy
->clipboard_request
[s
].timeout_id
=
417 g_timeout_add_seconds(5, dbus_clipboard_request_timeout
,
418 &dpy
->clipboard_request
[s
]);
421 return DBUS_METHOD_INVOCATION_HANDLED
;
425 dbus_clipboard_init(DBusDisplay
*dpy
)
427 g_autoptr(GDBusObjectSkeleton
) clipboard
= NULL
;
429 assert(!dpy
->clipboard
);
431 clipboard
= g_dbus_object_skeleton_new(DBUS_DISPLAY1_ROOT
"/Clipboard");
432 dpy
->clipboard
= qemu_dbus_display1_clipboard_skeleton_new();
433 g_object_connect(dpy
->clipboard
,
434 "swapped-signal::handle-register",
435 dbus_clipboard_register
, dpy
,
436 "swapped-signal::handle-unregister",
437 dbus_clipboard_unregister
, dpy
,
438 "swapped-signal::handle-grab",
439 dbus_clipboard_grab
, dpy
,
440 "swapped-signal::handle-release",
441 dbus_clipboard_release
, dpy
,
442 "swapped-signal::handle-request",
443 dbus_clipboard_request
, dpy
,
446 g_dbus_object_skeleton_add_interface(
447 G_DBUS_OBJECT_SKELETON(clipboard
),
448 G_DBUS_INTERFACE_SKELETON(dpy
->clipboard
));
449 g_dbus_object_manager_server_export(dpy
->server
, clipboard
);
450 dpy
->clipboard_peer
.name
= "dbus";
451 dpy
->clipboard_peer
.notifier
.notify
= dbus_clipboard_notify
;
452 dpy
->clipboard_peer
.request
= dbus_clipboard_qemu_request
;
453 qemu_clipboard_peer_register(&dpy
->clipboard_peer
);