1 /* 2 * QEMU DBus display console 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/error-report.h" 26 #include "qapi/error.h" 27 #include "ui/input.h" 28 #include "ui/kbd-state.h" 29 #include "trace.h" 30 31 #ifdef G_OS_UNIX 32 #include <gio/gunixfdlist.h> 33 #endif 34 35 #include "dbus.h" 36 37 static struct touch_slot touch_slots[INPUT_EVENT_SLOTS_MAX]; 38 39 struct _DBusDisplayConsole { 40 GDBusObjectSkeleton parent_instance; 41 DisplayChangeListener dcl; 42 43 DBusDisplay *display; 44 GHashTable *listeners; 45 QemuDBusDisplay1Console *iface; 46 47 QemuDBusDisplay1Keyboard *iface_kbd; 48 QKbdState *kbd; 49 50 QemuDBusDisplay1Mouse *iface_mouse; 51 QemuDBusDisplay1MultiTouch *iface_touch; 52 gboolean last_set; 53 guint last_x; 54 guint last_y; 55 Notifier mouse_mode_notifier; 56 }; 57 58 G_DEFINE_TYPE(DBusDisplayConsole, 59 dbus_display_console, 60 G_TYPE_DBUS_OBJECT_SKELETON) 61 62 static void 63 dbus_display_console_set_size(DBusDisplayConsole *ddc, 64 uint32_t width, uint32_t height) 65 { 66 g_object_set(ddc->iface, 67 "width", width, 68 "height", height, 69 NULL); 70 } 71 72 static void 73 dbus_gfx_switch(DisplayChangeListener *dcl, 74 struct DisplaySurface *new_surface) 75 { 76 DBusDisplayConsole *ddc = container_of(dcl, DBusDisplayConsole, dcl); 77 78 dbus_display_console_set_size(ddc, 79 surface_width(new_surface), 80 surface_height(new_surface)); 81 } 82 83 static void 84 dbus_gfx_update(DisplayChangeListener *dcl, 85 int x, int y, int w, int h) 86 { 87 } 88 89 static void 90 dbus_gl_scanout_disable(DisplayChangeListener *dcl) 91 { 92 } 93 94 static void 95 dbus_gl_scanout_texture(DisplayChangeListener *dcl, 96 uint32_t tex_id, 97 bool backing_y_0_top, 98 uint32_t backing_width, 99 uint32_t backing_height, 100 uint32_t x, uint32_t y, 101 uint32_t w, uint32_t h) 102 { 103 DBusDisplayConsole *ddc = container_of(dcl, DBusDisplayConsole, dcl); 104 105 dbus_display_console_set_size(ddc, w, h); 106 } 107 108 static void 109 dbus_gl_scanout_dmabuf(DisplayChangeListener *dcl, 110 QemuDmaBuf *dmabuf) 111 { 112 DBusDisplayConsole *ddc = container_of(dcl, DBusDisplayConsole, dcl); 113 114 dbus_display_console_set_size(ddc, 115 dmabuf->width, 116 dmabuf->height); 117 } 118 119 static void 120 dbus_gl_scanout_update(DisplayChangeListener *dcl, 121 uint32_t x, uint32_t y, 122 uint32_t w, uint32_t h) 123 { 124 } 125 126 const DisplayChangeListenerOps dbus_console_dcl_ops = { 127 .dpy_name = "dbus-console", 128 .dpy_gfx_switch = dbus_gfx_switch, 129 .dpy_gfx_update = dbus_gfx_update, 130 .dpy_gl_scanout_disable = dbus_gl_scanout_disable, 131 .dpy_gl_scanout_texture = dbus_gl_scanout_texture, 132 .dpy_gl_scanout_dmabuf = dbus_gl_scanout_dmabuf, 133 .dpy_gl_update = dbus_gl_scanout_update, 134 }; 135 136 static void 137 dbus_display_console_init(DBusDisplayConsole *object) 138 { 139 DBusDisplayConsole *ddc = DBUS_DISPLAY_CONSOLE(object); 140 141 ddc->listeners = g_hash_table_new_full(g_str_hash, g_str_equal, 142 NULL, g_object_unref); 143 ddc->dcl.ops = &dbus_console_dcl_ops; 144 } 145 146 static void 147 dbus_display_console_dispose(GObject *object) 148 { 149 DBusDisplayConsole *ddc = DBUS_DISPLAY_CONSOLE(object); 150 151 unregister_displaychangelistener(&ddc->dcl); 152 g_clear_object(&ddc->iface_kbd); 153 g_clear_object(&ddc->iface); 154 g_clear_pointer(&ddc->listeners, g_hash_table_unref); 155 g_clear_pointer(&ddc->kbd, qkbd_state_free); 156 157 G_OBJECT_CLASS(dbus_display_console_parent_class)->dispose(object); 158 } 159 160 static void 161 dbus_display_console_class_init(DBusDisplayConsoleClass *klass) 162 { 163 GObjectClass *gobject_class = G_OBJECT_CLASS(klass); 164 165 gobject_class->dispose = dbus_display_console_dispose; 166 } 167 168 static void 169 listener_vanished_cb(DBusDisplayListener *listener) 170 { 171 DBusDisplayConsole *ddc = dbus_display_listener_get_console(listener); 172 const char *name = dbus_display_listener_get_bus_name(listener); 173 174 trace_dbus_listener_vanished(name); 175 176 g_hash_table_remove(ddc->listeners, name); 177 qkbd_state_lift_all_keys(ddc->kbd); 178 } 179 180 static gboolean 181 dbus_console_set_ui_info(DBusDisplayConsole *ddc, 182 GDBusMethodInvocation *invocation, 183 guint16 arg_width_mm, 184 guint16 arg_height_mm, 185 gint arg_xoff, 186 gint arg_yoff, 187 guint arg_width, 188 guint arg_height) 189 { 190 QemuUIInfo info = { 191 .width_mm = arg_width_mm, 192 .height_mm = arg_height_mm, 193 .xoff = arg_xoff, 194 .yoff = arg_yoff, 195 .width = arg_width, 196 .height = arg_height, 197 }; 198 199 if (!dpy_ui_info_supported(ddc->dcl.con)) { 200 g_dbus_method_invocation_return_error(invocation, 201 DBUS_DISPLAY_ERROR, 202 DBUS_DISPLAY_ERROR_UNSUPPORTED, 203 "SetUIInfo is not supported"); 204 return DBUS_METHOD_INVOCATION_HANDLED; 205 } 206 207 dpy_set_ui_info(ddc->dcl.con, &info, false); 208 qemu_dbus_display1_console_complete_set_uiinfo(ddc->iface, invocation); 209 return DBUS_METHOD_INVOCATION_HANDLED; 210 } 211 212 #ifdef G_OS_WIN32 213 bool 214 dbus_win32_import_socket(GDBusMethodInvocation *invocation, 215 GVariant *arg_listener, int *socket) 216 { 217 gsize n; 218 WSAPROTOCOL_INFOW *info = (void *)g_variant_get_fixed_array(arg_listener, &n, 1); 219 220 if (!info || n != sizeof(*info)) { 221 g_dbus_method_invocation_return_error( 222 invocation, 223 DBUS_DISPLAY_ERROR, 224 DBUS_DISPLAY_ERROR_FAILED, 225 "Failed to get socket infos"); 226 return false; 227 } 228 229 *socket = WSASocketW(FROM_PROTOCOL_INFO, 230 FROM_PROTOCOL_INFO, 231 FROM_PROTOCOL_INFO, 232 info, 0, 0); 233 if (*socket == INVALID_SOCKET) { 234 g_autofree gchar *emsg = g_win32_error_message(WSAGetLastError()); 235 g_dbus_method_invocation_return_error( 236 invocation, 237 DBUS_DISPLAY_ERROR, 238 DBUS_DISPLAY_ERROR_FAILED, 239 "Couldn't create socket: %s", emsg); 240 return false; 241 } 242 243 return true; 244 } 245 #endif 246 247 static gboolean 248 dbus_console_register_listener(DBusDisplayConsole *ddc, 249 GDBusMethodInvocation *invocation, 250 #ifdef G_OS_UNIX 251 GUnixFDList *fd_list, 252 #endif 253 GVariant *arg_listener) 254 { 255 const char *sender = g_dbus_method_invocation_get_sender(invocation); 256 GDBusConnection *listener_conn; 257 g_autoptr(GError) err = NULL; 258 g_autoptr(GSocket) socket = NULL; 259 g_autoptr(GSocketConnection) socket_conn = NULL; 260 g_autofree char *guid = g_dbus_generate_guid(); 261 DBusDisplayListener *listener; 262 int fd; 263 264 if (sender && g_hash_table_contains(ddc->listeners, sender)) { 265 g_dbus_method_invocation_return_error( 266 invocation, 267 DBUS_DISPLAY_ERROR, 268 DBUS_DISPLAY_ERROR_INVALID, 269 "`%s` is already registered!", 270 sender); 271 return DBUS_METHOD_INVOCATION_HANDLED; 272 } 273 274 #ifdef G_OS_WIN32 275 if (!dbus_win32_import_socket(invocation, arg_listener, &fd)) { 276 return DBUS_METHOD_INVOCATION_HANDLED; 277 } 278 #else 279 fd = g_unix_fd_list_get(fd_list, g_variant_get_handle(arg_listener), &err); 280 if (err) { 281 g_dbus_method_invocation_return_error( 282 invocation, 283 DBUS_DISPLAY_ERROR, 284 DBUS_DISPLAY_ERROR_FAILED, 285 "Couldn't get peer fd: %s", err->message); 286 return DBUS_METHOD_INVOCATION_HANDLED; 287 } 288 #endif 289 290 socket = g_socket_new_from_fd(fd, &err); 291 if (err) { 292 g_dbus_method_invocation_return_error( 293 invocation, 294 DBUS_DISPLAY_ERROR, 295 DBUS_DISPLAY_ERROR_FAILED, 296 "Couldn't make a socket: %s", err->message); 297 #ifdef G_OS_WIN32 298 closesocket(fd); 299 #else 300 close(fd); 301 #endif 302 return DBUS_METHOD_INVOCATION_HANDLED; 303 } 304 socket_conn = g_socket_connection_factory_create_connection(socket); 305 306 qemu_dbus_display1_console_complete_register_listener( 307 ddc->iface, invocation 308 #ifdef G_OS_UNIX 309 , NULL 310 #endif 311 ); 312 313 listener_conn = g_dbus_connection_new_sync( 314 G_IO_STREAM(socket_conn), 315 guid, 316 G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER, 317 NULL, NULL, &err); 318 if (err) { 319 error_report("Failed to setup peer connection: %s", err->message); 320 return DBUS_METHOD_INVOCATION_HANDLED; 321 } 322 323 listener = dbus_display_listener_new(sender, listener_conn, ddc); 324 if (!listener) { 325 return DBUS_METHOD_INVOCATION_HANDLED; 326 } 327 328 g_hash_table_insert(ddc->listeners, 329 (gpointer)dbus_display_listener_get_bus_name(listener), 330 listener); 331 g_object_connect(listener_conn, 332 "swapped-signal::closed", listener_vanished_cb, listener, 333 NULL); 334 335 trace_dbus_registered_listener(sender); 336 return DBUS_METHOD_INVOCATION_HANDLED; 337 } 338 339 static gboolean 340 dbus_kbd_press(DBusDisplayConsole *ddc, 341 GDBusMethodInvocation *invocation, 342 guint arg_keycode) 343 { 344 QKeyCode qcode = qemu_input_key_number_to_qcode(arg_keycode); 345 346 trace_dbus_kbd_press(arg_keycode); 347 348 qkbd_state_key_event(ddc->kbd, qcode, true); 349 350 qemu_dbus_display1_keyboard_complete_press(ddc->iface_kbd, invocation); 351 352 return DBUS_METHOD_INVOCATION_HANDLED; 353 } 354 355 static gboolean 356 dbus_kbd_release(DBusDisplayConsole *ddc, 357 GDBusMethodInvocation *invocation, 358 guint arg_keycode) 359 { 360 QKeyCode qcode = qemu_input_key_number_to_qcode(arg_keycode); 361 362 trace_dbus_kbd_release(arg_keycode); 363 364 qkbd_state_key_event(ddc->kbd, qcode, false); 365 366 qemu_dbus_display1_keyboard_complete_release(ddc->iface_kbd, invocation); 367 368 return DBUS_METHOD_INVOCATION_HANDLED; 369 } 370 371 static void 372 dbus_kbd_qemu_leds_updated(void *data, int ledstate) 373 { 374 DBusDisplayConsole *ddc = DBUS_DISPLAY_CONSOLE(data); 375 376 qemu_dbus_display1_keyboard_set_modifiers(ddc->iface_kbd, ledstate); 377 } 378 379 static gboolean 380 dbus_mouse_rel_motion(DBusDisplayConsole *ddc, 381 GDBusMethodInvocation *invocation, 382 int dx, int dy) 383 { 384 trace_dbus_mouse_rel_motion(dx, dy); 385 386 if (qemu_input_is_absolute()) { 387 g_dbus_method_invocation_return_error( 388 invocation, DBUS_DISPLAY_ERROR, 389 DBUS_DISPLAY_ERROR_INVALID, 390 "Mouse is not relative"); 391 return DBUS_METHOD_INVOCATION_HANDLED; 392 } 393 394 qemu_input_queue_rel(ddc->dcl.con, INPUT_AXIS_X, dx); 395 qemu_input_queue_rel(ddc->dcl.con, INPUT_AXIS_Y, dy); 396 qemu_input_event_sync(); 397 398 qemu_dbus_display1_mouse_complete_rel_motion(ddc->iface_mouse, 399 invocation); 400 401 return DBUS_METHOD_INVOCATION_HANDLED; 402 } 403 404 static gboolean 405 dbus_touch_send_event(DBusDisplayConsole *ddc, 406 GDBusMethodInvocation *invocation, 407 guint kind, uint64_t num_slot, 408 double x, double y) 409 { 410 Error *error = NULL; 411 int width, height; 412 trace_dbus_touch_send_event(kind, num_slot, x, y); 413 414 if (kind != INPUT_MULTI_TOUCH_TYPE_BEGIN && 415 kind != INPUT_MULTI_TOUCH_TYPE_UPDATE && 416 kind != INPUT_MULTI_TOUCH_TYPE_CANCEL && 417 kind != INPUT_MULTI_TOUCH_TYPE_END) 418 { 419 g_dbus_method_invocation_return_error( 420 invocation, DBUS_DISPLAY_ERROR, 421 DBUS_DISPLAY_ERROR_INVALID, 422 "Invalid touch event kind"); 423 return DBUS_METHOD_INVOCATION_HANDLED; 424 } 425 width = qemu_console_get_width(ddc->dcl.con, 0); 426 height = qemu_console_get_height(ddc->dcl.con, 0); 427 428 console_handle_touch_event(ddc->dcl.con, touch_slots, 429 num_slot, width, height, 430 x, y, kind, &error); 431 if (error != NULL) { 432 g_dbus_method_invocation_return_error( 433 invocation, DBUS_DISPLAY_ERROR, 434 DBUS_DISPLAY_ERROR_INVALID, 435 error_get_pretty(error), NULL); 436 error_free(error); 437 } else { 438 qemu_dbus_display1_multi_touch_complete_send_event(ddc->iface_touch, 439 invocation); 440 } 441 return DBUS_METHOD_INVOCATION_HANDLED; 442 } 443 444 static gboolean 445 dbus_mouse_set_pos(DBusDisplayConsole *ddc, 446 GDBusMethodInvocation *invocation, 447 guint x, guint y) 448 { 449 int width, height; 450 451 trace_dbus_mouse_set_pos(x, y); 452 453 if (!qemu_input_is_absolute()) { 454 g_dbus_method_invocation_return_error( 455 invocation, DBUS_DISPLAY_ERROR, 456 DBUS_DISPLAY_ERROR_INVALID, 457 "Mouse is not absolute"); 458 return DBUS_METHOD_INVOCATION_HANDLED; 459 } 460 461 width = qemu_console_get_width(ddc->dcl.con, 0); 462 height = qemu_console_get_height(ddc->dcl.con, 0); 463 if (x >= width || y >= height) { 464 g_dbus_method_invocation_return_error( 465 invocation, DBUS_DISPLAY_ERROR, 466 DBUS_DISPLAY_ERROR_INVALID, 467 "Invalid mouse position"); 468 return DBUS_METHOD_INVOCATION_HANDLED; 469 } 470 qemu_input_queue_abs(ddc->dcl.con, INPUT_AXIS_X, x, 0, width); 471 qemu_input_queue_abs(ddc->dcl.con, INPUT_AXIS_Y, y, 0, height); 472 qemu_input_event_sync(); 473 474 qemu_dbus_display1_mouse_complete_set_abs_position(ddc->iface_mouse, 475 invocation); 476 477 return DBUS_METHOD_INVOCATION_HANDLED; 478 } 479 480 static gboolean 481 dbus_mouse_press(DBusDisplayConsole *ddc, 482 GDBusMethodInvocation *invocation, 483 guint button) 484 { 485 trace_dbus_mouse_press(button); 486 487 qemu_input_queue_btn(ddc->dcl.con, button, true); 488 qemu_input_event_sync(); 489 490 qemu_dbus_display1_mouse_complete_press(ddc->iface_mouse, invocation); 491 492 return DBUS_METHOD_INVOCATION_HANDLED; 493 } 494 495 static gboolean 496 dbus_mouse_release(DBusDisplayConsole *ddc, 497 GDBusMethodInvocation *invocation, 498 guint button) 499 { 500 trace_dbus_mouse_release(button); 501 502 qemu_input_queue_btn(ddc->dcl.con, button, false); 503 qemu_input_event_sync(); 504 505 qemu_dbus_display1_mouse_complete_release(ddc->iface_mouse, invocation); 506 507 return DBUS_METHOD_INVOCATION_HANDLED; 508 } 509 510 static void 511 dbus_mouse_update_is_absolute(DBusDisplayConsole *ddc) 512 { 513 g_object_set(ddc->iface_mouse, 514 "is-absolute", qemu_input_is_absolute(), 515 NULL); 516 } 517 518 static void 519 dbus_mouse_mode_change(Notifier *notify, void *data) 520 { 521 DBusDisplayConsole *ddc = 522 container_of(notify, DBusDisplayConsole, mouse_mode_notifier); 523 524 dbus_mouse_update_is_absolute(ddc); 525 } 526 527 int dbus_display_console_get_index(DBusDisplayConsole *ddc) 528 { 529 return qemu_console_get_index(ddc->dcl.con); 530 } 531 532 DBusDisplayConsole * 533 dbus_display_console_new(DBusDisplay *display, QemuConsole *con) 534 { 535 g_autofree char *path = NULL; 536 g_autofree char *label = NULL; 537 char device_addr[256] = ""; 538 DBusDisplayConsole *ddc; 539 int idx, i; 540 const char *interfaces[] = { 541 "org.qemu.Display1.Keyboard", 542 "org.qemu.Display1.Mouse", 543 "org.qemu.Display1.MultiTouch", 544 NULL 545 }; 546 547 assert(display); 548 assert(con); 549 550 label = qemu_console_get_label(con); 551 idx = qemu_console_get_index(con); 552 path = g_strdup_printf(DBUS_DISPLAY1_ROOT "/Console_%d", idx); 553 ddc = g_object_new(DBUS_DISPLAY_TYPE_CONSOLE, 554 "g-object-path", path, 555 NULL); 556 ddc->display = display; 557 ddc->dcl.con = con; 558 /* handle errors, and skip non graphics? */ 559 qemu_console_fill_device_address( 560 con, device_addr, sizeof(device_addr), NULL); 561 562 ddc->iface = qemu_dbus_display1_console_skeleton_new(); 563 g_object_set(ddc->iface, 564 "label", label, 565 "type", qemu_console_is_graphic(con) ? "Graphic" : "Text", 566 "head", qemu_console_get_head(con), 567 "width", qemu_console_get_width(con, 0), 568 "height", qemu_console_get_height(con, 0), 569 "device-address", device_addr, 570 "interfaces", interfaces, 571 NULL); 572 g_object_connect(ddc->iface, 573 "swapped-signal::handle-register-listener", 574 dbus_console_register_listener, ddc, 575 "swapped-signal::handle-set-uiinfo", 576 dbus_console_set_ui_info, ddc, 577 NULL); 578 g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc), 579 G_DBUS_INTERFACE_SKELETON(ddc->iface)); 580 581 ddc->kbd = qkbd_state_init(con); 582 ddc->iface_kbd = qemu_dbus_display1_keyboard_skeleton_new(); 583 qemu_add_led_event_handler(dbus_kbd_qemu_leds_updated, ddc); 584 g_object_connect(ddc->iface_kbd, 585 "swapped-signal::handle-press", dbus_kbd_press, ddc, 586 "swapped-signal::handle-release", dbus_kbd_release, ddc, 587 NULL); 588 g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc), 589 G_DBUS_INTERFACE_SKELETON(ddc->iface_kbd)); 590 591 ddc->iface_mouse = qemu_dbus_display1_mouse_skeleton_new(); 592 g_object_connect(ddc->iface_mouse, 593 "swapped-signal::handle-set-abs-position", dbus_mouse_set_pos, ddc, 594 "swapped-signal::handle-rel-motion", dbus_mouse_rel_motion, ddc, 595 "swapped-signal::handle-press", dbus_mouse_press, ddc, 596 "swapped-signal::handle-release", dbus_mouse_release, ddc, 597 NULL); 598 g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc), 599 G_DBUS_INTERFACE_SKELETON(ddc->iface_mouse)); 600 601 ddc->iface_touch = qemu_dbus_display1_multi_touch_skeleton_new(); 602 g_object_connect(ddc->iface_touch, 603 "swapped-signal::handle-send-event", dbus_touch_send_event, ddc, 604 NULL); 605 qemu_dbus_display1_multi_touch_set_max_slots(ddc->iface_touch, 606 INPUT_EVENT_SLOTS_MAX); 607 g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc), 608 G_DBUS_INTERFACE_SKELETON(ddc->iface_touch)); 609 610 for (i = 0; i < INPUT_EVENT_SLOTS_MAX; i++) { 611 struct touch_slot *slot = &touch_slots[i]; 612 slot->tracking_id = -1; 613 } 614 615 register_displaychangelistener(&ddc->dcl); 616 ddc->mouse_mode_notifier.notify = dbus_mouse_mode_change; 617 qemu_add_mouse_mode_change_notifier(&ddc->mouse_mode_notifier); 618 dbus_mouse_update_is_absolute(ddc); 619 620 return ddc; 621 } 622