qemu/ui/dbus-console.c
<<
>>
Prefs
   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
  34struct _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
  52G_DEFINE_TYPE(DBusDisplayConsole,
  53              dbus_display_console,
  54              G_TYPE_DBUS_OBJECT_SKELETON)
  55
  56static void
  57dbus_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
  66static void
  67dbus_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
  77static void
  78dbus_gfx_update(DisplayChangeListener *dcl,
  79                int x, int y, int w, int h)
  80{
  81}
  82
  83static void
  84dbus_gl_scanout_disable(DisplayChangeListener *dcl)
  85{
  86}
  87
  88static void
  89dbus_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
 102static void
 103dbus_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
 113static void
 114dbus_gl_scanout_update(DisplayChangeListener *dcl,
 115                       uint32_t x, uint32_t y,
 116                       uint32_t w, uint32_t h)
 117{
 118}
 119
 120const 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
 130static void
 131dbus_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
 140static void
 141dbus_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
 154static void
 155dbus_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
 162static void
 163listener_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
 174static gboolean
 175dbus_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
 206static gboolean
 207dbus_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
 282static gboolean
 283dbus_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
 298static gboolean
 299dbus_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
 314static void
 315dbus_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
 322static gboolean
 323dbus_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
 347static gboolean
 348dbus_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
 383static gboolean
 384dbus_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
 398static gboolean
 399dbus_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
 413static void
 414dbus_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
 424int dbus_display_console_get_index(DBusDisplayConsole *ddc)
 425{
 426    return qemu_console_get_index(ddc->dcl.con);
 427}
 428
 429DBusDisplayConsole *
 430dbus_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