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/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" 31 #include "trace.h" 32 33 #include "dbus.h" 34 35 #define MIME_TEXT_PLAIN_UTF8 "text/plain;charset=utf-8" 36 37 static void 38 dbus_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 57 static void 58 dbus_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 104 static void 105 dbus_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 115 static void 116 dbus_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 132 static void 133 dbus_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 trace_dbus_clipboard_qemu_request(type); 145 146 if (type != QEMU_CLIPBOARD_TYPE_TEXT) { 147 /* unsupported atm */ 148 return; 149 } 150 151 if (dpy->clipboard_proxy) { 152 if (!qemu_dbus_display1_clipboard_call_request_sync( 153 dpy->clipboard_proxy, 154 info->selection, 155 mimes, 156 G_DBUS_CALL_FLAGS_NONE, -1, &mime, &v_data, NULL, &err)) { 157 error_report("Failed to request clipboard: %s", err->message); 158 return; 159 } 160 161 if (g_strcmp0(mime, MIME_TEXT_PLAIN_UTF8)) { 162 error_report("Unsupported returned MIME: %s", mime); 163 return; 164 } 165 166 data = g_variant_get_fixed_array(v_data, &n, 1); 167 qemu_clipboard_set_data(&dpy->clipboard_peer, info, type, 168 n, data, true); 169 } 170 } 171 172 static void 173 dbus_clipboard_request_cancelled(DBusClipboardRequest *req) 174 { 175 if (!req->invocation) { 176 return; 177 } 178 179 g_dbus_method_invocation_return_error( 180 req->invocation, 181 DBUS_DISPLAY_ERROR, 182 DBUS_DISPLAY_ERROR_FAILED, 183 "Cancelled clipboard request"); 184 185 g_clear_object(&req->invocation); 186 g_source_remove(req->timeout_id); 187 req->timeout_id = 0; 188 } 189 190 static void 191 dbus_clipboard_unregister_proxy(DBusDisplay *dpy) 192 { 193 const char *name = NULL; 194 int i; 195 196 for (i = 0; i < G_N_ELEMENTS(dpy->clipboard_request); ++i) { 197 dbus_clipboard_request_cancelled(&dpy->clipboard_request[i]); 198 } 199 200 if (!dpy->clipboard_proxy) { 201 return; 202 } 203 204 name = g_dbus_proxy_get_name(G_DBUS_PROXY(dpy->clipboard_proxy)); 205 trace_dbus_clipboard_unregister(name); 206 g_clear_object(&dpy->clipboard_proxy); 207 } 208 209 static gboolean 210 dbus_clipboard_register( 211 DBusDisplay *dpy, 212 GDBusMethodInvocation *invocation) 213 { 214 g_autoptr(GError) err = NULL; 215 const char *name = NULL; 216 GDBusConnection *connection = g_dbus_method_invocation_get_connection(invocation); 217 218 if (dpy->clipboard_proxy) { 219 g_dbus_method_invocation_return_error( 220 invocation, 221 DBUS_DISPLAY_ERROR, 222 DBUS_DISPLAY_ERROR_FAILED, 223 "Clipboard peer already registered!"); 224 return DBUS_METHOD_INVOCATION_HANDLED; 225 } 226 227 dpy->clipboard_proxy = 228 qemu_dbus_display1_clipboard_proxy_new_sync( 229 connection, 230 G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, 231 g_dbus_method_invocation_get_sender(invocation), 232 "/org/qemu/Display1/Clipboard", 233 NULL, 234 &err); 235 if (!dpy->clipboard_proxy) { 236 g_dbus_method_invocation_return_error( 237 invocation, 238 DBUS_DISPLAY_ERROR, 239 DBUS_DISPLAY_ERROR_FAILED, 240 "Failed to setup proxy: %s", err->message); 241 return DBUS_METHOD_INVOCATION_HANDLED; 242 } 243 244 name = g_dbus_proxy_get_name(G_DBUS_PROXY(dpy->clipboard_proxy)); 245 trace_dbus_clipboard_register(name); 246 247 g_object_connect(dpy->clipboard_proxy, 248 "swapped-signal::notify::g-name-owner", 249 dbus_clipboard_unregister_proxy, dpy, 250 NULL); 251 g_object_connect(connection, 252 "swapped-signal::closed", 253 dbus_clipboard_unregister_proxy, dpy, 254 NULL); 255 qemu_clipboard_reset_serial(); 256 257 qemu_dbus_display1_clipboard_complete_register(dpy->clipboard, invocation); 258 return DBUS_METHOD_INVOCATION_HANDLED; 259 } 260 261 static gboolean 262 dbus_clipboard_check_caller(DBusDisplay *dpy, GDBusMethodInvocation *invocation) 263 { 264 if (!dpy->clipboard_proxy || 265 g_strcmp0(g_dbus_proxy_get_name(G_DBUS_PROXY(dpy->clipboard_proxy)), 266 g_dbus_method_invocation_get_sender(invocation))) { 267 g_dbus_method_invocation_return_error( 268 invocation, 269 DBUS_DISPLAY_ERROR, 270 DBUS_DISPLAY_ERROR_FAILED, 271 "Unregistered caller"); 272 return FALSE; 273 } 274 275 return TRUE; 276 } 277 278 static gboolean 279 dbus_clipboard_unregister( 280 DBusDisplay *dpy, 281 GDBusMethodInvocation *invocation) 282 { 283 if (!dbus_clipboard_check_caller(dpy, invocation)) { 284 return DBUS_METHOD_INVOCATION_HANDLED; 285 } 286 287 dbus_clipboard_unregister_proxy(dpy); 288 289 qemu_dbus_display1_clipboard_complete_unregister( 290 dpy->clipboard, invocation); 291 292 return DBUS_METHOD_INVOCATION_HANDLED; 293 } 294 295 static gboolean 296 dbus_clipboard_grab( 297 DBusDisplay *dpy, 298 GDBusMethodInvocation *invocation, 299 gint arg_selection, 300 guint arg_serial, 301 const gchar *const *arg_mimes) 302 { 303 QemuClipboardSelection s = arg_selection; 304 g_autoptr(QemuClipboardInfo) info = NULL; 305 306 if (!dbus_clipboard_check_caller(dpy, invocation)) { 307 return DBUS_METHOD_INVOCATION_HANDLED; 308 } 309 310 trace_dbus_clipboard_grab(arg_selection, arg_serial); 311 312 if (s >= QEMU_CLIPBOARD_SELECTION__COUNT) { 313 g_dbus_method_invocation_return_error( 314 invocation, 315 DBUS_DISPLAY_ERROR, 316 DBUS_DISPLAY_ERROR_FAILED, 317 "Invalid clipboard selection: %d", arg_selection); 318 return DBUS_METHOD_INVOCATION_HANDLED; 319 } 320 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; 324 } 325 info->serial = arg_serial; 326 info->has_serial = true; 327 if (qemu_clipboard_check_serial(info, true)) { 328 qemu_clipboard_update(info); 329 } else { 330 trace_dbus_clipboard_grab_failed(); 331 } 332 333 qemu_dbus_display1_clipboard_complete_grab(dpy->clipboard, invocation); 334 return DBUS_METHOD_INVOCATION_HANDLED; 335 } 336 337 static gboolean 338 dbus_clipboard_release( 339 DBusDisplay *dpy, 340 GDBusMethodInvocation *invocation, 341 gint arg_selection) 342 { 343 if (!dbus_clipboard_check_caller(dpy, invocation)) { 344 return DBUS_METHOD_INVOCATION_HANDLED; 345 } 346 347 qemu_clipboard_peer_release(&dpy->clipboard_peer, arg_selection); 348 349 qemu_dbus_display1_clipboard_complete_release(dpy->clipboard, invocation); 350 return DBUS_METHOD_INVOCATION_HANDLED; 351 } 352 353 static gboolean 354 dbus_clipboard_request_timeout(gpointer user_data) 355 { 356 dbus_clipboard_request_cancelled(user_data); 357 return G_SOURCE_REMOVE; 358 } 359 360 static gboolean 361 dbus_clipboard_request( 362 DBusDisplay *dpy, 363 GDBusMethodInvocation *invocation, 364 gint arg_selection, 365 const gchar *const *arg_mimes) 366 { 367 QemuClipboardSelection s = arg_selection; 368 QemuClipboardType type = QEMU_CLIPBOARD_TYPE_TEXT; 369 QemuClipboardInfo *info = NULL; 370 371 if (!dbus_clipboard_check_caller(dpy, invocation)) { 372 return DBUS_METHOD_INVOCATION_HANDLED; 373 } 374 375 if (s >= QEMU_CLIPBOARD_SELECTION__COUNT) { 376 g_dbus_method_invocation_return_error( 377 invocation, 378 DBUS_DISPLAY_ERROR, 379 DBUS_DISPLAY_ERROR_FAILED, 380 "Invalid clipboard selection: %d", arg_selection); 381 return DBUS_METHOD_INVOCATION_HANDLED; 382 } 383 384 if (dpy->clipboard_request[s].invocation) { 385 g_dbus_method_invocation_return_error( 386 invocation, 387 DBUS_DISPLAY_ERROR, 388 DBUS_DISPLAY_ERROR_FAILED, 389 "Pending request"); 390 return DBUS_METHOD_INVOCATION_HANDLED; 391 } 392 393 info = qemu_clipboard_info(s); 394 if (!info || !info->owner || info->owner == &dpy->clipboard_peer) { 395 g_dbus_method_invocation_return_error( 396 invocation, 397 DBUS_DISPLAY_ERROR, 398 DBUS_DISPLAY_ERROR_FAILED, 399 "Empty clipboard"); 400 return DBUS_METHOD_INVOCATION_HANDLED; 401 } 402 403 if (!g_strv_contains(arg_mimes, MIME_TEXT_PLAIN_UTF8) || 404 !info->types[type].available) { 405 g_dbus_method_invocation_return_error( 406 invocation, 407 DBUS_DISPLAY_ERROR, 408 DBUS_DISPLAY_ERROR_FAILED, 409 "Unhandled MIME types requested"); 410 return DBUS_METHOD_INVOCATION_HANDLED; 411 } 412 413 if (info->types[type].data) { 414 dbus_clipboard_complete_request(dpy, invocation, info, type); 415 } else { 416 qemu_clipboard_request(info, type); 417 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]); 423 } 424 425 return DBUS_METHOD_INVOCATION_HANDLED; 426 } 427 428 void 429 dbus_clipboard_init(DBusDisplay *dpy) 430 { 431 g_autoptr(GDBusObjectSkeleton) clipboard = NULL; 432 433 assert(!dpy->clipboard); 434 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, 448 NULL); 449 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); 458 } 459