qemu/ui/dbus-listener.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 "sysemu/sysemu.h"
  26#include "dbus.h"
  27#include <gio/gunixfdlist.h>
  28
  29#include "ui/shader.h"
  30#include "ui/egl-helpers.h"
  31#include "ui/egl-context.h"
  32#include "trace.h"
  33
  34struct _DBusDisplayListener {
  35    GObject parent;
  36
  37    char *bus_name;
  38    DBusDisplayConsole *console;
  39    GDBusConnection *conn;
  40
  41    QemuDBusDisplay1Listener *proxy;
  42
  43    DisplayChangeListener dcl;
  44    DisplaySurface *ds;
  45    int gl_updates;
  46};
  47
  48G_DEFINE_TYPE(DBusDisplayListener, dbus_display_listener, G_TYPE_OBJECT)
  49
  50static void dbus_update_gl_cb(GObject *source_object,
  51                           GAsyncResult *res,
  52                           gpointer user_data)
  53{
  54    g_autoptr(GError) err = NULL;
  55    DBusDisplayListener *ddl = user_data;
  56
  57    if (!qemu_dbus_display1_listener_call_update_dmabuf_finish(ddl->proxy,
  58                                                               res, &err)) {
  59        error_report("Failed to call update: %s", err->message);
  60    }
  61
  62    graphic_hw_gl_block(ddl->dcl.con, false);
  63    g_object_unref(ddl);
  64}
  65
  66static void dbus_call_update_gl(DBusDisplayListener *ddl,
  67                                int x, int y, int w, int h)
  68{
  69    graphic_hw_gl_block(ddl->dcl.con, true);
  70    glFlush();
  71    qemu_dbus_display1_listener_call_update_dmabuf(ddl->proxy,
  72        x, y, w, h,
  73        G_DBUS_CALL_FLAGS_NONE,
  74        DBUS_DEFAULT_TIMEOUT, NULL,
  75        dbus_update_gl_cb,
  76        g_object_ref(ddl));
  77}
  78
  79static void dbus_scanout_disable(DisplayChangeListener *dcl)
  80{
  81    DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
  82
  83    ddl->ds = NULL;
  84    qemu_dbus_display1_listener_call_disable(
  85        ddl->proxy, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
  86}
  87
  88static void dbus_scanout_dmabuf(DisplayChangeListener *dcl,
  89                                QemuDmaBuf *dmabuf)
  90{
  91    DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
  92    g_autoptr(GError) err = NULL;
  93    g_autoptr(GUnixFDList) fd_list = NULL;
  94
  95    fd_list = g_unix_fd_list_new();
  96    if (g_unix_fd_list_append(fd_list, dmabuf->fd, &err) != 0) {
  97        error_report("Failed to setup dmabuf fdlist: %s", err->message);
  98        return;
  99    }
 100
 101    qemu_dbus_display1_listener_call_scanout_dmabuf(
 102        ddl->proxy,
 103        g_variant_new_handle(0),
 104        dmabuf->width,
 105        dmabuf->height,
 106        dmabuf->stride,
 107        dmabuf->fourcc,
 108        dmabuf->modifier,
 109        dmabuf->y0_top,
 110        G_DBUS_CALL_FLAGS_NONE,
 111        -1,
 112        fd_list,
 113        NULL, NULL, NULL);
 114}
 115
 116static void dbus_scanout_texture(DisplayChangeListener *dcl,
 117                                 uint32_t tex_id,
 118                                 bool backing_y_0_top,
 119                                 uint32_t backing_width,
 120                                 uint32_t backing_height,
 121                                 uint32_t x, uint32_t y,
 122                                 uint32_t w, uint32_t h)
 123{
 124    QemuDmaBuf dmabuf = {
 125        .width = backing_width,
 126        .height = backing_height,
 127        .y0_top = backing_y_0_top,
 128    };
 129
 130    assert(tex_id);
 131    dmabuf.fd = egl_get_fd_for_texture(
 132        tex_id, (EGLint *)&dmabuf.stride,
 133        (EGLint *)&dmabuf.fourcc,
 134        &dmabuf.modifier);
 135    if (dmabuf.fd < 0) {
 136        error_report("%s: failed to get fd for texture", __func__);
 137        return;
 138    }
 139
 140    dbus_scanout_dmabuf(dcl, &dmabuf);
 141    close(dmabuf.fd);
 142}
 143
 144static void dbus_cursor_dmabuf(DisplayChangeListener *dcl,
 145                               QemuDmaBuf *dmabuf, bool have_hot,
 146                               uint32_t hot_x, uint32_t hot_y)
 147{
 148    DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
 149    DisplaySurface *ds;
 150    GVariant *v_data = NULL;
 151    egl_fb cursor_fb;
 152
 153    if (!dmabuf) {
 154        qemu_dbus_display1_listener_call_mouse_set(
 155            ddl->proxy, 0, 0, false,
 156            G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
 157        return;
 158    }
 159
 160    egl_dmabuf_import_texture(dmabuf);
 161    if (!dmabuf->texture) {
 162        return;
 163    }
 164    egl_fb_setup_for_tex(&cursor_fb, dmabuf->width, dmabuf->height,
 165                         dmabuf->texture, false);
 166    ds = qemu_create_displaysurface(dmabuf->width, dmabuf->height);
 167    egl_fb_read(ds, &cursor_fb);
 168
 169    v_data = g_variant_new_from_data(
 170        G_VARIANT_TYPE("ay"),
 171        surface_data(ds),
 172        surface_width(ds) * surface_height(ds) * 4,
 173        TRUE,
 174        (GDestroyNotify)qemu_free_displaysurface,
 175        ds);
 176    qemu_dbus_display1_listener_call_cursor_define(
 177        ddl->proxy,
 178        surface_width(ds),
 179        surface_height(ds),
 180        hot_x,
 181        hot_y,
 182        v_data,
 183        G_DBUS_CALL_FLAGS_NONE,
 184        -1,
 185        NULL,
 186        NULL,
 187        NULL);
 188}
 189
 190static void dbus_cursor_position(DisplayChangeListener *dcl,
 191                                 uint32_t pos_x, uint32_t pos_y)
 192{
 193    DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
 194
 195    qemu_dbus_display1_listener_call_mouse_set(
 196        ddl->proxy, pos_x, pos_y, true,
 197        G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
 198}
 199
 200static void dbus_release_dmabuf(DisplayChangeListener *dcl,
 201                                QemuDmaBuf *dmabuf)
 202{
 203    dbus_scanout_disable(dcl);
 204}
 205
 206static void dbus_scanout_update(DisplayChangeListener *dcl,
 207                                uint32_t x, uint32_t y,
 208                                uint32_t w, uint32_t h)
 209{
 210    DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
 211
 212    dbus_call_update_gl(ddl, x, y, w, h);
 213}
 214
 215static void dbus_gl_refresh(DisplayChangeListener *dcl)
 216{
 217    DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
 218
 219    graphic_hw_update(dcl->con);
 220
 221    if (!ddl->ds || qemu_console_is_gl_blocked(ddl->dcl.con)) {
 222        return;
 223    }
 224
 225    if (ddl->gl_updates) {
 226        dbus_call_update_gl(ddl, 0, 0,
 227                            surface_width(ddl->ds), surface_height(ddl->ds));
 228        ddl->gl_updates = 0;
 229    }
 230}
 231
 232static void dbus_refresh(DisplayChangeListener *dcl)
 233{
 234    graphic_hw_update(dcl->con);
 235}
 236
 237static void dbus_gl_gfx_update(DisplayChangeListener *dcl,
 238                               int x, int y, int w, int h)
 239{
 240    DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
 241
 242    ddl->gl_updates++;
 243}
 244
 245static void dbus_gfx_update(DisplayChangeListener *dcl,
 246                            int x, int y, int w, int h)
 247{
 248    DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
 249    pixman_image_t *img;
 250    GVariant *v_data;
 251    size_t stride;
 252
 253    assert(ddl->ds);
 254    stride = w * DIV_ROUND_UP(PIXMAN_FORMAT_BPP(surface_format(ddl->ds)), 8);
 255
 256    trace_dbus_update(x, y, w, h);
 257
 258    if (x == 0 && y == 0 && w == surface_width(ddl->ds) && h == surface_height(ddl->ds)) {
 259        v_data = g_variant_new_from_data(
 260            G_VARIANT_TYPE("ay"),
 261            surface_data(ddl->ds),
 262            surface_stride(ddl->ds) * surface_height(ddl->ds),
 263            TRUE,
 264            (GDestroyNotify)pixman_image_unref,
 265            pixman_image_ref(ddl->ds->image));
 266        qemu_dbus_display1_listener_call_scanout(
 267            ddl->proxy,
 268            surface_width(ddl->ds),
 269            surface_height(ddl->ds),
 270            surface_stride(ddl->ds),
 271            surface_format(ddl->ds),
 272            v_data,
 273            G_DBUS_CALL_FLAGS_NONE,
 274            DBUS_DEFAULT_TIMEOUT, NULL, NULL, NULL);
 275        return;
 276    }
 277
 278    /* make a copy, since gvariant only handles linear data */
 279    img = pixman_image_create_bits(surface_format(ddl->ds),
 280                                   w, h, NULL, stride);
 281    pixman_image_composite(PIXMAN_OP_SRC, ddl->ds->image, NULL, img,
 282                           x, y, 0, 0, 0, 0, w, h);
 283
 284    v_data = g_variant_new_from_data(
 285        G_VARIANT_TYPE("ay"),
 286        pixman_image_get_data(img),
 287        pixman_image_get_stride(img) * h,
 288        TRUE,
 289        (GDestroyNotify)pixman_image_unref,
 290        img);
 291    qemu_dbus_display1_listener_call_update(ddl->proxy,
 292        x, y, w, h, pixman_image_get_stride(img), pixman_image_get_format(img),
 293        v_data,
 294        G_DBUS_CALL_FLAGS_NONE,
 295        DBUS_DEFAULT_TIMEOUT, NULL, NULL, NULL);
 296}
 297
 298static void dbus_gl_gfx_switch(DisplayChangeListener *dcl,
 299                               struct DisplaySurface *new_surface)
 300{
 301    DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
 302
 303    ddl->ds = new_surface;
 304    if (ddl->ds) {
 305        int width = surface_width(ddl->ds);
 306        int height = surface_height(ddl->ds);
 307
 308        /* TODO: lazy send dmabuf (there are unnecessary sent otherwise) */
 309        dbus_scanout_texture(&ddl->dcl, ddl->ds->texture, false,
 310                             width, height, 0, 0, width, height);
 311    }
 312}
 313
 314static void dbus_gfx_switch(DisplayChangeListener *dcl,
 315                            struct DisplaySurface *new_surface)
 316{
 317    DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
 318
 319    ddl->ds = new_surface;
 320    if (!ddl->ds) {
 321        /* why not call disable instead? */
 322        return;
 323    }
 324}
 325
 326static void dbus_mouse_set(DisplayChangeListener *dcl,
 327                           int x, int y, int on)
 328{
 329    DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
 330
 331    qemu_dbus_display1_listener_call_mouse_set(
 332        ddl->proxy, x, y, on, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
 333}
 334
 335static void dbus_cursor_define(DisplayChangeListener *dcl,
 336                               QEMUCursor *c)
 337{
 338    DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
 339    GVariant *v_data = NULL;
 340
 341    cursor_get(c);
 342    v_data = g_variant_new_from_data(
 343        G_VARIANT_TYPE("ay"),
 344        c->data,
 345        c->width * c->height * 4,
 346        TRUE,
 347        (GDestroyNotify)cursor_put,
 348        c);
 349
 350    qemu_dbus_display1_listener_call_cursor_define(
 351        ddl->proxy,
 352        c->width,
 353        c->height,
 354        c->hot_x,
 355        c->hot_y,
 356        v_data,
 357        G_DBUS_CALL_FLAGS_NONE,
 358        -1,
 359        NULL,
 360        NULL,
 361        NULL);
 362}
 363
 364const DisplayChangeListenerOps dbus_gl_dcl_ops = {
 365    .dpy_name                = "dbus-gl",
 366    .dpy_gfx_update          = dbus_gl_gfx_update,
 367    .dpy_gfx_switch          = dbus_gl_gfx_switch,
 368    .dpy_gfx_check_format    = console_gl_check_format,
 369    .dpy_refresh             = dbus_gl_refresh,
 370    .dpy_mouse_set           = dbus_mouse_set,
 371    .dpy_cursor_define       = dbus_cursor_define,
 372
 373    .dpy_gl_scanout_disable  = dbus_scanout_disable,
 374    .dpy_gl_scanout_texture  = dbus_scanout_texture,
 375    .dpy_gl_scanout_dmabuf   = dbus_scanout_dmabuf,
 376    .dpy_gl_cursor_dmabuf    = dbus_cursor_dmabuf,
 377    .dpy_gl_cursor_position  = dbus_cursor_position,
 378    .dpy_gl_release_dmabuf   = dbus_release_dmabuf,
 379    .dpy_gl_update           = dbus_scanout_update,
 380};
 381
 382const DisplayChangeListenerOps dbus_dcl_ops = {
 383    .dpy_name                = "dbus",
 384    .dpy_gfx_update          = dbus_gfx_update,
 385    .dpy_gfx_switch          = dbus_gfx_switch,
 386    .dpy_refresh             = dbus_refresh,
 387    .dpy_mouse_set           = dbus_mouse_set,
 388    .dpy_cursor_define       = dbus_cursor_define,
 389};
 390
 391static void
 392dbus_display_listener_dispose(GObject *object)
 393{
 394    DBusDisplayListener *ddl = DBUS_DISPLAY_LISTENER(object);
 395
 396    unregister_displaychangelistener(&ddl->dcl);
 397    g_clear_object(&ddl->conn);
 398    g_clear_pointer(&ddl->bus_name, g_free);
 399    g_clear_object(&ddl->proxy);
 400
 401    G_OBJECT_CLASS(dbus_display_listener_parent_class)->dispose(object);
 402}
 403
 404static void
 405dbus_display_listener_constructed(GObject *object)
 406{
 407    DBusDisplayListener *ddl = DBUS_DISPLAY_LISTENER(object);
 408
 409    if (display_opengl) {
 410        ddl->dcl.ops = &dbus_gl_dcl_ops;
 411    } else {
 412        ddl->dcl.ops = &dbus_dcl_ops;
 413    }
 414
 415    G_OBJECT_CLASS(dbus_display_listener_parent_class)->constructed(object);
 416}
 417
 418static void
 419dbus_display_listener_class_init(DBusDisplayListenerClass *klass)
 420{
 421    GObjectClass *object_class = G_OBJECT_CLASS(klass);
 422
 423    object_class->dispose = dbus_display_listener_dispose;
 424    object_class->constructed = dbus_display_listener_constructed;
 425}
 426
 427static void
 428dbus_display_listener_init(DBusDisplayListener *ddl)
 429{
 430}
 431
 432const char *
 433dbus_display_listener_get_bus_name(DBusDisplayListener *ddl)
 434{
 435    return ddl->bus_name ?: "p2p";
 436}
 437
 438DBusDisplayConsole *
 439dbus_display_listener_get_console(DBusDisplayListener *ddl)
 440{
 441    return ddl->console;
 442}
 443
 444DBusDisplayListener *
 445dbus_display_listener_new(const char *bus_name,
 446                          GDBusConnection *conn,
 447                          DBusDisplayConsole *console)
 448{
 449    DBusDisplayListener *ddl;
 450    QemuConsole *con;
 451    g_autoptr(GError) err = NULL;
 452
 453    ddl = g_object_new(DBUS_DISPLAY_TYPE_LISTENER, NULL);
 454    ddl->proxy =
 455        qemu_dbus_display1_listener_proxy_new_sync(conn,
 456            G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
 457            NULL,
 458            "/org/qemu/Display1/Listener",
 459            NULL,
 460            &err);
 461    if (!ddl->proxy) {
 462        error_report("Failed to setup proxy: %s", err->message);
 463        g_object_unref(conn);
 464        g_object_unref(ddl);
 465        return NULL;
 466    }
 467
 468    ddl->bus_name = g_strdup(bus_name);
 469    ddl->conn = conn;
 470    ddl->console = console;
 471
 472    con = qemu_console_lookup_by_index(dbus_display_console_get_index(console));
 473    assert(con);
 474    ddl->dcl.con = con;
 475    register_displaychangelistener(&ddl->dcl);
 476
 477    return ddl;
 478}
 479