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