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