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