]> git.proxmox.com Git - mirror_qemu.git/blame - ui/dbus-clipboard.c
bsd-user: Implement stat related syscalls
[mirror_qemu.git] / ui / dbus-clipboard.c
CommitLineData
ff1a5810
MAL
1/*
2 * QEMU DBus display
3 *
4 * Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com>
5 *
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:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
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.
23 */
24#include "qemu/osdep.h"
25#include "qemu/dbus.h"
5feed38c 26#include "qemu/error-report.h"
ff1a5810
MAL
27#include "qemu/main-loop.h"
28#include "qom/object_interfaces.h"
29#include "sysemu/sysemu.h"
30#include "qapi/error.h"
31#include "trace.h"
32
33#include "dbus.h"
34
35#define MIME_TEXT_PLAIN_UTF8 "text/plain;charset=utf-8"
36
37static void
38dbus_clipboard_complete_request(
39 DBusDisplay *dpy,
40 GDBusMethodInvocation *invocation,
41 QemuClipboardInfo *info,
42 QemuClipboardType type)
43{
44 GVariant *v_data = g_variant_new_from_data(
45 G_VARIANT_TYPE("ay"),
46 info->types[type].data,
47 info->types[type].size,
48 TRUE,
49 (GDestroyNotify)qemu_clipboard_info_unref,
50 qemu_clipboard_info_ref(info));
51
52 qemu_dbus_display1_clipboard_complete_request(
53 dpy->clipboard, invocation,
54 MIME_TEXT_PLAIN_UTF8, v_data);
55}
56
57static void
58dbus_clipboard_update_info(DBusDisplay *dpy, QemuClipboardInfo *info)
59{
60 bool self_update = info->owner == &dpy->clipboard_peer;
61 const char *mime[QEMU_CLIPBOARD_TYPE__COUNT + 1] = { 0, };
62 DBusClipboardRequest *req;
63 int i = 0;
64
65 if (info->owner == NULL) {
66 if (dpy->clipboard_proxy) {
67 qemu_dbus_display1_clipboard_call_release(
68 dpy->clipboard_proxy,
69 info->selection,
70 G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
71 }
72 return;
73 }
74
75 if (self_update || !info->has_serial) {
76 return;
77 }
78
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);
84 req->timeout_id = 0;
85 return;
86 }
87
88 if (info->types[QEMU_CLIPBOARD_TYPE_TEXT].available) {
89 mime[i++] = MIME_TEXT_PLAIN_UTF8;
90 }
91
92 if (i > 0) {
93 if (dpy->clipboard_proxy) {
94 qemu_dbus_display1_clipboard_call_grab(
95 dpy->clipboard_proxy,
96 info->selection,
97 info->serial,
98 mime,
99 G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
100 }
101 }
102}
103
104static void
105dbus_clipboard_reset_serial(DBusDisplay *dpy)
106{
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);
112 }
113}
114
115static void
116dbus_clipboard_notify(Notifier *notifier, void *data)
117{
118 DBusDisplay *dpy =
119 container_of(notifier, DBusDisplay, clipboard_peer.notifier);
120 QemuClipboardNotify *notify = data;
121
122 switch (notify->type) {
123 case QEMU_CLIPBOARD_UPDATE_INFO:
124 dbus_clipboard_update_info(dpy, notify->info);
125 return;
126 case QEMU_CLIPBOARD_RESET_SERIAL:
127 dbus_clipboard_reset_serial(dpy);
128 return;
129 }
130}
131
132static void
133dbus_clipboard_qemu_request(QemuClipboardInfo *info,
134 QemuClipboardType type)
135{
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 };
142 size_t n;
143
144 if (type != QEMU_CLIPBOARD_TYPE_TEXT) {
145 /* unsupported atm */
146 return;
147 }
148
149 if (dpy->clipboard_proxy) {
150 if (!qemu_dbus_display1_clipboard_call_request_sync(
151 dpy->clipboard_proxy,
152 info->selection,
153 mimes,
154 G_DBUS_CALL_FLAGS_NONE, -1, &mime, &v_data, NULL, &err)) {
155 error_report("Failed to request clipboard: %s", err->message);
156 return;
157 }
158
159 if (g_strcmp0(mime, MIME_TEXT_PLAIN_UTF8)) {
160 error_report("Unsupported returned MIME: %s", mime);
161 return;
162 }
163
164 data = g_variant_get_fixed_array(v_data, &n, 1);
165 qemu_clipboard_set_data(&dpy->clipboard_peer, info, type,
166 n, data, true);
167 }
168}
169
170static void
171dbus_clipboard_request_cancelled(DBusClipboardRequest *req)
172{
173 if (!req->invocation) {
174 return;
175 }
176
177 g_dbus_method_invocation_return_error(
178 req->invocation,
179 DBUS_DISPLAY_ERROR,
180 DBUS_DISPLAY_ERROR_FAILED,
181 "Cancelled clipboard request");
182
183 g_clear_object(&req->invocation);
184 g_source_remove(req->timeout_id);
185 req->timeout_id = 0;
186}
187
188static void
189dbus_clipboard_unregister_proxy(DBusDisplay *dpy)
190{
191 const char *name = NULL;
192 int i;
193
194 for (i = 0; i < G_N_ELEMENTS(dpy->clipboard_request); ++i) {
195 dbus_clipboard_request_cancelled(&dpy->clipboard_request[i]);
196 }
197
198 if (!dpy->clipboard_proxy) {
199 return;
200 }
201
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);
205}
206
ff1a5810
MAL
207static gboolean
208dbus_clipboard_register(
209 DBusDisplay *dpy,
210 GDBusMethodInvocation *invocation)
211{
212 g_autoptr(GError) err = NULL;
213 const char *name = NULL;
b289bb30 214 GDBusConnection *connection = g_dbus_method_invocation_get_connection(invocation);
ff1a5810
MAL
215
216 if (dpy->clipboard_proxy) {
217 g_dbus_method_invocation_return_error(
218 invocation,
219 DBUS_DISPLAY_ERROR,
220 DBUS_DISPLAY_ERROR_FAILED,
221 "Clipboard peer already registered!");
222 return DBUS_METHOD_INVOCATION_HANDLED;
223 }
224
225 dpy->clipboard_proxy =
226 qemu_dbus_display1_clipboard_proxy_new_sync(
b289bb30 227 connection,
ff1a5810
MAL
228 G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
229 g_dbus_method_invocation_get_sender(invocation),
230 "/org/qemu/Display1/Clipboard",
231 NULL,
232 &err);
233 if (!dpy->clipboard_proxy) {
234 g_dbus_method_invocation_return_error(
235 invocation,
236 DBUS_DISPLAY_ERROR,
237 DBUS_DISPLAY_ERROR_FAILED,
238 "Failed to setup proxy: %s", err->message);
239 return DBUS_METHOD_INVOCATION_HANDLED;
240 }
241
242 name = g_dbus_proxy_get_name(G_DBUS_PROXY(dpy->clipboard_proxy));
243 trace_dbus_clipboard_register(name);
244
245 g_object_connect(dpy->clipboard_proxy,
246 "swapped-signal::notify::g-name-owner",
b289bb30
MAL
247 dbus_clipboard_unregister_proxy, dpy,
248 NULL);
249 g_object_connect(connection,
250 "swapped-signal::closed",
251 dbus_clipboard_unregister_proxy, dpy,
ff1a5810
MAL
252 NULL);
253 qemu_clipboard_reset_serial();
254
255 qemu_dbus_display1_clipboard_complete_register(dpy->clipboard, invocation);
256 return DBUS_METHOD_INVOCATION_HANDLED;
257}
258
259static gboolean
260dbus_clipboard_check_caller(DBusDisplay *dpy, GDBusMethodInvocation *invocation)
261{
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(
266 invocation,
267 DBUS_DISPLAY_ERROR,
268 DBUS_DISPLAY_ERROR_FAILED,
269 "Unregistered caller");
270 return FALSE;
271 }
272
273 return TRUE;
274}
275
276static gboolean
277dbus_clipboard_unregister(
278 DBusDisplay *dpy,
279 GDBusMethodInvocation *invocation)
280{
281 if (!dbus_clipboard_check_caller(dpy, invocation)) {
282 return DBUS_METHOD_INVOCATION_HANDLED;
283 }
284
285 dbus_clipboard_unregister_proxy(dpy);
286
287 qemu_dbus_display1_clipboard_complete_unregister(
288 dpy->clipboard, invocation);
289
290 return DBUS_METHOD_INVOCATION_HANDLED;
291}
292
293static gboolean
294dbus_clipboard_grab(
295 DBusDisplay *dpy,
296 GDBusMethodInvocation *invocation,
297 gint arg_selection,
298 guint arg_serial,
299 const gchar *const *arg_mimes)
300{
301 QemuClipboardSelection s = arg_selection;
302 g_autoptr(QemuClipboardInfo) info = NULL;
303
304 if (!dbus_clipboard_check_caller(dpy, invocation)) {
305 return DBUS_METHOD_INVOCATION_HANDLED;
306 }
307
308 if (s >= QEMU_CLIPBOARD_SELECTION__COUNT) {
309 g_dbus_method_invocation_return_error(
310 invocation,
311 DBUS_DISPLAY_ERROR,
312 DBUS_DISPLAY_ERROR_FAILED,
313 "Invalid clipboard selection: %d", arg_selection);
314 return DBUS_METHOD_INVOCATION_HANDLED;
315 }
316
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;
320 }
321 info->serial = arg_serial;
322 info->has_serial = true;
323 if (qemu_clipboard_check_serial(info, true)) {
324 qemu_clipboard_update(info);
325 } else {
326 trace_dbus_clipboard_grab_failed();
327 }
328
329 qemu_dbus_display1_clipboard_complete_grab(dpy->clipboard, invocation);
330 return DBUS_METHOD_INVOCATION_HANDLED;
331}
332
333static gboolean
334dbus_clipboard_release(
335 DBusDisplay *dpy,
336 GDBusMethodInvocation *invocation,
337 gint arg_selection)
338{
339 if (!dbus_clipboard_check_caller(dpy, invocation)) {
340 return DBUS_METHOD_INVOCATION_HANDLED;
341 }
342
343 qemu_clipboard_peer_release(&dpy->clipboard_peer, arg_selection);
344
345 qemu_dbus_display1_clipboard_complete_release(dpy->clipboard, invocation);
346 return DBUS_METHOD_INVOCATION_HANDLED;
347}
348
349static gboolean
350dbus_clipboard_request_timeout(gpointer user_data)
351{
352 dbus_clipboard_request_cancelled(user_data);
353 return G_SOURCE_REMOVE;
354}
355
356static gboolean
357dbus_clipboard_request(
358 DBusDisplay *dpy,
359 GDBusMethodInvocation *invocation,
360 gint arg_selection,
361 const gchar *const *arg_mimes)
362{
363 QemuClipboardSelection s = arg_selection;
364 QemuClipboardType type = QEMU_CLIPBOARD_TYPE_TEXT;
365 QemuClipboardInfo *info = NULL;
366
367 if (!dbus_clipboard_check_caller(dpy, invocation)) {
368 return DBUS_METHOD_INVOCATION_HANDLED;
369 }
370
371 if (s >= QEMU_CLIPBOARD_SELECTION__COUNT) {
372 g_dbus_method_invocation_return_error(
373 invocation,
374 DBUS_DISPLAY_ERROR,
375 DBUS_DISPLAY_ERROR_FAILED,
376 "Invalid clipboard selection: %d", arg_selection);
377 return DBUS_METHOD_INVOCATION_HANDLED;
378 }
379
380 if (dpy->clipboard_request[s].invocation) {
381 g_dbus_method_invocation_return_error(
382 invocation,
383 DBUS_DISPLAY_ERROR,
384 DBUS_DISPLAY_ERROR_FAILED,
385 "Pending request");
386 return DBUS_METHOD_INVOCATION_HANDLED;
387 }
388
389 info = qemu_clipboard_info(s);
390 if (!info || !info->owner || info->owner == &dpy->clipboard_peer) {
391 g_dbus_method_invocation_return_error(
392 invocation,
393 DBUS_DISPLAY_ERROR,
394 DBUS_DISPLAY_ERROR_FAILED,
395 "Empty clipboard");
396 return DBUS_METHOD_INVOCATION_HANDLED;
397 }
398
399 if (!g_strv_contains(arg_mimes, MIME_TEXT_PLAIN_UTF8) ||
400 !info->types[type].available) {
401 g_dbus_method_invocation_return_error(
402 invocation,
403 DBUS_DISPLAY_ERROR,
404 DBUS_DISPLAY_ERROR_FAILED,
405 "Unhandled MIME types requested");
406 return DBUS_METHOD_INVOCATION_HANDLED;
407 }
408
409 if (info->types[type].data) {
410 dbus_clipboard_complete_request(dpy, invocation, info, type);
411 } else {
412 qemu_clipboard_request(info, type);
413
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]);
419 }
420
421 return DBUS_METHOD_INVOCATION_HANDLED;
422}
423
424void
425dbus_clipboard_init(DBusDisplay *dpy)
426{
427 g_autoptr(GDBusObjectSkeleton) clipboard = NULL;
428
429 assert(!dpy->clipboard);
430
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,
444 NULL);
445
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);
454}