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 34 struct _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 48 G_DEFINE_TYPE(DBusDisplayListener, dbus_display_listener, G_TYPE_OBJECT) 49 50 static 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 66 static 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 79 static 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 88 static 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 116 static 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 144 static 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 190 static 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 200 static void dbus_release_dmabuf(DisplayChangeListener *dcl, 201 QemuDmaBuf *dmabuf) 202 { 203 dbus_scanout_disable(dcl); 204 } 205 206 static 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 215 static 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 232 static void dbus_refresh(DisplayChangeListener *dcl) 233 { 234 graphic_hw_update(dcl->con); 235 } 236 237 static 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 245 static 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 298 static 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 314 static 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 326 static 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 335 static 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 364 const 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 382 const 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 391 static void 392 dbus_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 404 static void 405 dbus_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 418 static void 419 dbus_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 427 static void 428 dbus_display_listener_init(DBusDisplayListener *ddl) 429 { 430 } 431 432 const char * 433 dbus_display_listener_get_bus_name(DBusDisplayListener *ddl) 434 { 435 return ddl->bus_name ?: "p2p"; 436 } 437 438 DBusDisplayConsole * 439 dbus_display_listener_get_console(DBusDisplayListener *ddl) 440 { 441 return ddl->console; 442 } 443 444 DBusDisplayListener * 445 dbus_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