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