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