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