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 QemuGLShader *gls; 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 if (ddl->ds) { 244 surface_gl_update_texture(ddl->gls, ddl->ds, x, y, w, h); 245 } 246 247 ddl->gl_updates++; 248 } 249 250 static void dbus_gfx_update(DisplayChangeListener *dcl, 251 int x, int y, int w, int h) 252 { 253 DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl); 254 pixman_image_t *img; 255 GVariant *v_data; 256 size_t stride; 257 258 assert(ddl->ds); 259 stride = w * DIV_ROUND_UP(PIXMAN_FORMAT_BPP(surface_format(ddl->ds)), 8); 260 261 trace_dbus_update(x, y, w, h); 262 263 /* make a copy, since gvariant only handles linear data */ 264 img = pixman_image_create_bits(surface_format(ddl->ds), 265 w, h, NULL, stride); 266 pixman_image_composite(PIXMAN_OP_SRC, ddl->ds->image, NULL, img, 267 x, y, 0, 0, 0, 0, w, h); 268 269 v_data = g_variant_new_from_data( 270 G_VARIANT_TYPE("ay"), 271 pixman_image_get_data(img), 272 pixman_image_get_stride(img) * h, 273 TRUE, 274 (GDestroyNotify)pixman_image_unref, 275 img); 276 qemu_dbus_display1_listener_call_update(ddl->proxy, 277 x, y, w, h, pixman_image_get_stride(img), pixman_image_get_format(img), 278 v_data, 279 G_DBUS_CALL_FLAGS_NONE, 280 DBUS_DEFAULT_TIMEOUT, NULL, NULL, NULL); 281 } 282 283 static void dbus_gl_gfx_switch(DisplayChangeListener *dcl, 284 struct DisplaySurface *new_surface) 285 { 286 DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl); 287 288 if (ddl->ds) { 289 surface_gl_destroy_texture(ddl->gls, ddl->ds); 290 } 291 ddl->ds = new_surface; 292 if (ddl->ds) { 293 int width = surface_width(ddl->ds); 294 int height = surface_height(ddl->ds); 295 296 surface_gl_create_texture(ddl->gls, ddl->ds); 297 /* TODO: lazy send dmabuf (there are unnecessary sent otherwise) */ 298 dbus_scanout_texture(&ddl->dcl, ddl->ds->texture, false, 299 width, height, 0, 0, width, height); 300 } 301 } 302 303 static void dbus_gfx_switch(DisplayChangeListener *dcl, 304 struct DisplaySurface *new_surface) 305 { 306 DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl); 307 GVariant *v_data = NULL; 308 309 ddl->ds = new_surface; 310 if (!ddl->ds) { 311 /* why not call disable instead? */ 312 return; 313 } 314 315 v_data = g_variant_new_from_data( 316 G_VARIANT_TYPE("ay"), 317 surface_data(ddl->ds), 318 surface_stride(ddl->ds) * surface_height(ddl->ds), 319 TRUE, 320 (GDestroyNotify)pixman_image_unref, 321 pixman_image_ref(ddl->ds->image)); 322 qemu_dbus_display1_listener_call_scanout(ddl->proxy, 323 surface_width(ddl->ds), 324 surface_height(ddl->ds), 325 surface_stride(ddl->ds), 326 surface_format(ddl->ds), 327 v_data, 328 G_DBUS_CALL_FLAGS_NONE, 329 DBUS_DEFAULT_TIMEOUT, NULL, NULL, NULL); 330 } 331 332 static void dbus_mouse_set(DisplayChangeListener *dcl, 333 int x, int y, int on) 334 { 335 DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl); 336 337 qemu_dbus_display1_listener_call_mouse_set( 338 ddl->proxy, x, y, on, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL); 339 } 340 341 static void dbus_cursor_define(DisplayChangeListener *dcl, 342 QEMUCursor *c) 343 { 344 DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl); 345 GVariant *v_data = NULL; 346 347 cursor_get(c); 348 v_data = g_variant_new_from_data( 349 G_VARIANT_TYPE("ay"), 350 c->data, 351 c->width * c->height * 4, 352 TRUE, 353 (GDestroyNotify)cursor_put, 354 c); 355 356 qemu_dbus_display1_listener_call_cursor_define( 357 ddl->proxy, 358 c->width, 359 c->height, 360 c->hot_x, 361 c->hot_y, 362 v_data, 363 G_DBUS_CALL_FLAGS_NONE, 364 -1, 365 NULL, 366 NULL, 367 NULL); 368 } 369 370 const DisplayChangeListenerOps dbus_gl_dcl_ops = { 371 .dpy_name = "dbus-gl", 372 .dpy_gfx_update = dbus_gl_gfx_update, 373 .dpy_gfx_switch = dbus_gl_gfx_switch, 374 .dpy_gfx_check_format = console_gl_check_format, 375 .dpy_refresh = dbus_gl_refresh, 376 .dpy_mouse_set = dbus_mouse_set, 377 .dpy_cursor_define = dbus_cursor_define, 378 379 .dpy_gl_scanout_disable = dbus_scanout_disable, 380 .dpy_gl_scanout_texture = dbus_scanout_texture, 381 .dpy_gl_scanout_dmabuf = dbus_scanout_dmabuf, 382 .dpy_gl_cursor_dmabuf = dbus_cursor_dmabuf, 383 .dpy_gl_cursor_position = dbus_cursor_position, 384 .dpy_gl_release_dmabuf = dbus_release_dmabuf, 385 .dpy_gl_update = dbus_scanout_update, 386 }; 387 388 const DisplayChangeListenerOps dbus_dcl_ops = { 389 .dpy_name = "dbus", 390 .dpy_gfx_update = dbus_gfx_update, 391 .dpy_gfx_switch = dbus_gfx_switch, 392 .dpy_refresh = dbus_refresh, 393 .dpy_mouse_set = dbus_mouse_set, 394 .dpy_cursor_define = dbus_cursor_define, 395 }; 396 397 static void 398 dbus_display_listener_dispose(GObject *object) 399 { 400 DBusDisplayListener *ddl = DBUS_DISPLAY_LISTENER(object); 401 402 unregister_displaychangelistener(&ddl->dcl); 403 g_clear_object(&ddl->conn); 404 g_clear_pointer(&ddl->bus_name, g_free); 405 g_clear_object(&ddl->proxy); 406 g_clear_pointer(&ddl->gls, qemu_gl_fini_shader); 407 408 G_OBJECT_CLASS(dbus_display_listener_parent_class)->dispose(object); 409 } 410 411 static void 412 dbus_display_listener_constructed(GObject *object) 413 { 414 DBusDisplayListener *ddl = DBUS_DISPLAY_LISTENER(object); 415 416 if (display_opengl) { 417 ddl->gls = qemu_gl_init_shader(); 418 ddl->dcl.ops = &dbus_gl_dcl_ops; 419 } else { 420 ddl->dcl.ops = &dbus_dcl_ops; 421 } 422 423 G_OBJECT_CLASS(dbus_display_listener_parent_class)->constructed(object); 424 } 425 426 static void 427 dbus_display_listener_class_init(DBusDisplayListenerClass *klass) 428 { 429 GObjectClass *object_class = G_OBJECT_CLASS(klass); 430 431 object_class->dispose = dbus_display_listener_dispose; 432 object_class->constructed = dbus_display_listener_constructed; 433 } 434 435 static void 436 dbus_display_listener_init(DBusDisplayListener *ddl) 437 { 438 } 439 440 const char * 441 dbus_display_listener_get_bus_name(DBusDisplayListener *ddl) 442 { 443 return ddl->bus_name ?: "p2p"; 444 } 445 446 DBusDisplayConsole * 447 dbus_display_listener_get_console(DBusDisplayListener *ddl) 448 { 449 return ddl->console; 450 } 451 452 DBusDisplayListener * 453 dbus_display_listener_new(const char *bus_name, 454 GDBusConnection *conn, 455 DBusDisplayConsole *console) 456 { 457 DBusDisplayListener *ddl; 458 QemuConsole *con; 459 g_autoptr(GError) err = NULL; 460 461 ddl = g_object_new(DBUS_DISPLAY_TYPE_LISTENER, NULL); 462 ddl->proxy = 463 qemu_dbus_display1_listener_proxy_new_sync(conn, 464 G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, 465 NULL, 466 "/org/qemu/Display1/Listener", 467 NULL, 468 &err); 469 if (!ddl->proxy) { 470 error_report("Failed to setup proxy: %s", err->message); 471 g_object_unref(conn); 472 g_object_unref(ddl); 473 return NULL; 474 } 475 476 ddl->bus_name = g_strdup(bus_name); 477 ddl->conn = conn; 478 ddl->console = console; 479 480 con = qemu_console_lookup_by_index(dbus_display_console_get_index(console)); 481 assert(con); 482 ddl->dcl.con = con; 483 register_displaychangelistener(&ddl->dcl); 484 485 return ddl; 486 } 487