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/main-loop.h"
27 #include "qom/object_interfaces.h"
28 #include "sysemu/sysemu.h"
29 #include "qapi/error.h"
34 #define MIME_TEXT_PLAIN_UTF8 "text/plain;charset=utf-8"
37 dbus_clipboard_complete_request(
39 GDBusMethodInvocation
*invocation
,
40 QemuClipboardInfo
*info
,
41 QemuClipboardType type
)
43 GVariant
*v_data
= g_variant_new_from_data(
45 info
->types
[type
].data
,
46 info
->types
[type
].size
,
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
);
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
;
64 if (info
->owner
== NULL
) {
65 if (dpy
->clipboard_proxy
) {
66 qemu_dbus_display1_clipboard_call_release(
69 G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
74 if (self_update
|| !info
->has_serial
) {
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
);
87 if (info
->types
[QEMU_CLIPBOARD_TYPE_TEXT
].available
) {
88 mime
[i
++] = MIME_TEXT_PLAIN_UTF8
;
92 if (dpy
->clipboard_proxy
) {
93 qemu_dbus_display1_clipboard_call_grab(
98 G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
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
);
115 dbus_clipboard_notify(Notifier
*notifier
, void *data
)
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
);
125 case QEMU_CLIPBOARD_RESET_SERIAL
:
126 dbus_clipboard_reset_serial(dpy
);
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
};
143 if (type
!= QEMU_CLIPBOARD_TYPE_TEXT
) {
144 /* unsupported atm */
148 if (dpy
->clipboard_proxy
) {
149 if (!qemu_dbus_display1_clipboard_call_request_sync(
150 dpy
->clipboard_proxy
,
153 G_DBUS_CALL_FLAGS_NONE
, -1, &mime
, &v_data
, NULL
, &err
)) {
154 error_report("Failed to request clipboard: %s", err
->message
);
158 if (g_strcmp0(mime
, MIME_TEXT_PLAIN_UTF8
)) {
159 error_report("Unsupported returned MIME: %s", mime
);
163 data
= g_variant_get_fixed_array(v_data
, &n
, 1);
164 qemu_clipboard_set_data(&dpy
->clipboard_peer
, info
, type
,
170 dbus_clipboard_request_cancelled(DBusClipboardRequest
*req
)
172 if (!req
->invocation
) {
176 g_dbus_method_invocation_return_error(
179 DBUS_DISPLAY_ERROR_FAILED
,
180 "Cancelled clipboard request");
182 g_clear_object(&req
->invocation
);
183 g_source_remove(req
->timeout_id
);
188 dbus_clipboard_unregister_proxy(DBusDisplay
*dpy
)
190 const char *name
= NULL
;
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
) {
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
);
207 dbus_on_clipboard_proxy_name_owner_changed(
212 dbus_clipboard_unregister_proxy(dpy
);
216 dbus_clipboard_register(
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(
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",
240 if (!dpy
->clipboard_proxy
) {
241 g_dbus_method_invocation_return_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
,
256 qemu_clipboard_reset_serial();
258 qemu_dbus_display1_clipboard_complete_register(dpy
->clipboard
, invocation
);
259 return DBUS_METHOD_INVOCATION_HANDLED
;
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(
271 DBUS_DISPLAY_ERROR_FAILED
,
272 "Unregistered caller");
280 dbus_clipboard_unregister(
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
;
299 GDBusMethodInvocation
*invocation
,
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(
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
);
329 trace_dbus_clipboard_grab_failed();
332 qemu_dbus_display1_clipboard_complete_grab(dpy
->clipboard
, invocation
);
333 return DBUS_METHOD_INVOCATION_HANDLED
;
337 dbus_clipboard_release(
339 GDBusMethodInvocation
*invocation
,
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
;
353 dbus_clipboard_request_timeout(gpointer user_data
)
355 dbus_clipboard_request_cancelled(user_data
);
356 return G_SOURCE_REMOVE
;
360 dbus_clipboard_request(
362 GDBusMethodInvocation
*invocation
,
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(
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(
387 DBUS_DISPLAY_ERROR_FAILED
,
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(
397 DBUS_DISPLAY_ERROR_FAILED
,
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(
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
);
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
;
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
,
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
);