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