2 * QEMU DBus display console
4 * Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com>
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:
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
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
24 #include "qemu/osdep.h"
25 #include "sysemu/sysemu.h"
27 #include <gio/gunixfdlist.h>
29 #include "ui/shader.h"
30 #include "ui/egl-helpers.h"
31 #include "ui/egl-context.h"
34 struct _DBusDisplayListener
{
38 DBusDisplayConsole
*console
;
39 GDBusConnection
*conn
;
41 QemuDBusDisplay1Listener
*proxy
;
43 DisplayChangeListener dcl
;
49 G_DEFINE_TYPE(DBusDisplayListener
, dbus_display_listener
, G_TYPE_OBJECT
)
51 static void dbus_update_gl_cb(GObject
*source_object
,
55 g_autoptr(GError
) err
= NULL
;
56 DBusDisplayListener
*ddl
= user_data
;
58 if (!qemu_dbus_display1_listener_call_update_dmabuf_finish(ddl
->proxy
,
60 error_report("Failed to call update: %s", err
->message
);
63 graphic_hw_gl_block(ddl
->dcl
.con
, false);
67 static void dbus_call_update_gl(DBusDisplayListener
*ddl
,
68 int x
, int y
, int w
, int h
)
70 graphic_hw_gl_block(ddl
->dcl
.con
, true);
72 qemu_dbus_display1_listener_call_update_dmabuf(ddl
->proxy
,
74 G_DBUS_CALL_FLAGS_NONE
,
75 DBUS_DEFAULT_TIMEOUT
, NULL
,
80 static void dbus_scanout_disable(DisplayChangeListener
*dcl
)
82 DBusDisplayListener
*ddl
= container_of(dcl
, DBusDisplayListener
, dcl
);
85 qemu_dbus_display1_listener_call_disable(
86 ddl
->proxy
, G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
89 static void dbus_scanout_dmabuf(DisplayChangeListener
*dcl
,
92 DBusDisplayListener
*ddl
= container_of(dcl
, DBusDisplayListener
, dcl
);
93 g_autoptr(GError
) err
= NULL
;
94 g_autoptr(GUnixFDList
) fd_list
= NULL
;
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
);
102 qemu_dbus_display1_listener_call_scanout_dmabuf(
104 g_variant_new_handle(0),
111 G_DBUS_CALL_FLAGS_NONE
,
117 static void dbus_scanout_texture(DisplayChangeListener
*dcl
,
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
)
125 QemuDmaBuf dmabuf
= {
126 .width
= backing_width
,
127 .height
= backing_height
,
128 .y0_top
= backing_y_0_top
,
132 dmabuf
.fd
= egl_get_fd_for_texture(
133 tex_id
, (EGLint
*)&dmabuf
.stride
,
134 (EGLint
*)&dmabuf
.fourcc
,
137 error_report("%s: failed to get fd for texture", __func__
);
141 dbus_scanout_dmabuf(dcl
, &dmabuf
);
145 static void dbus_cursor_dmabuf(DisplayChangeListener
*dcl
,
146 QemuDmaBuf
*dmabuf
, bool have_hot
,
147 uint32_t hot_x
, uint32_t hot_y
)
149 DBusDisplayListener
*ddl
= container_of(dcl
, DBusDisplayListener
, dcl
);
151 GVariant
*v_data
= NULL
;
155 qemu_dbus_display1_listener_call_mouse_set(
156 ddl
->proxy
, 0, 0, false,
157 G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
161 egl_dmabuf_import_texture(dmabuf
);
162 if (!dmabuf
->texture
) {
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
);
170 v_data
= g_variant_new_from_data(
171 G_VARIANT_TYPE("ay"),
173 surface_width(ds
) * surface_height(ds
) * 4,
175 (GDestroyNotify
)qemu_free_displaysurface
,
177 qemu_dbus_display1_listener_call_cursor_define(
184 G_DBUS_CALL_FLAGS_NONE
,
191 static void dbus_cursor_position(DisplayChangeListener
*dcl
,
192 uint32_t pos_x
, uint32_t pos_y
)
194 DBusDisplayListener
*ddl
= container_of(dcl
, DBusDisplayListener
, dcl
);
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
);
201 static void dbus_release_dmabuf(DisplayChangeListener
*dcl
,
204 dbus_scanout_disable(dcl
);
207 static void dbus_scanout_update(DisplayChangeListener
*dcl
,
208 uint32_t x
, uint32_t y
,
209 uint32_t w
, uint32_t h
)
211 DBusDisplayListener
*ddl
= container_of(dcl
, DBusDisplayListener
, dcl
);
213 dbus_call_update_gl(ddl
, x
, y
, w
, h
);
216 static void dbus_gl_refresh(DisplayChangeListener
*dcl
)
218 DBusDisplayListener
*ddl
= container_of(dcl
, DBusDisplayListener
, dcl
);
220 graphic_hw_update(dcl
->con
);
222 if (!ddl
->ds
|| qemu_console_is_gl_blocked(ddl
->dcl
.con
)) {
226 if (ddl
->gl_updates
) {
227 dbus_call_update_gl(ddl
, 0, 0,
228 surface_width(ddl
->ds
), surface_height(ddl
->ds
));
233 static void dbus_refresh(DisplayChangeListener
*dcl
)
235 graphic_hw_update(dcl
->con
);
238 static void dbus_gl_gfx_update(DisplayChangeListener
*dcl
,
239 int x
, int y
, int w
, int h
)
241 DBusDisplayListener
*ddl
= container_of(dcl
, DBusDisplayListener
, dcl
);
244 surface_gl_update_texture(ddl
->gls
, ddl
->ds
, x
, y
, w
, h
);
250 static void dbus_gfx_update(DisplayChangeListener
*dcl
,
251 int x
, int y
, int w
, int h
)
253 DBusDisplayListener
*ddl
= container_of(dcl
, DBusDisplayListener
, dcl
);
259 stride
= w
* DIV_ROUND_UP(PIXMAN_FORMAT_BPP(surface_format(ddl
->ds
)), 8);
261 trace_dbus_update(x
, y
, w
, h
);
263 /* make a copy, since gvariant only handles linear data */
264 img
= pixman_image_create_bits(surface_format(ddl
->ds
),
266 pixman_image_composite(PIXMAN_OP_SRC
, ddl
->ds
->image
, NULL
, img
,
267 x
, y
, 0, 0, 0, 0, w
, h
);
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
,
274 (GDestroyNotify
)pixman_image_unref
,
276 qemu_dbus_display1_listener_call_update(ddl
->proxy
,
277 x
, y
, w
, h
, pixman_image_get_stride(img
), pixman_image_get_format(img
),
279 G_DBUS_CALL_FLAGS_NONE
,
280 DBUS_DEFAULT_TIMEOUT
, NULL
, NULL
, NULL
);
283 static void dbus_gl_gfx_switch(DisplayChangeListener
*dcl
,
284 struct DisplaySurface
*new_surface
)
286 DBusDisplayListener
*ddl
= container_of(dcl
, DBusDisplayListener
, dcl
);
289 surface_gl_destroy_texture(ddl
->gls
, ddl
->ds
);
291 ddl
->ds
= new_surface
;
293 int width
= surface_width(ddl
->ds
);
294 int height
= surface_height(ddl
->ds
);
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
);
303 static void dbus_gfx_switch(DisplayChangeListener
*dcl
,
304 struct DisplaySurface
*new_surface
)
306 DBusDisplayListener
*ddl
= container_of(dcl
, DBusDisplayListener
, dcl
);
307 GVariant
*v_data
= NULL
;
309 ddl
->ds
= new_surface
;
311 /* why not call disable instead? */
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
),
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
),
328 G_DBUS_CALL_FLAGS_NONE
,
329 DBUS_DEFAULT_TIMEOUT
, NULL
, NULL
, NULL
);
332 static void dbus_mouse_set(DisplayChangeListener
*dcl
,
333 int x
, int y
, int on
)
335 DBusDisplayListener
*ddl
= container_of(dcl
, DBusDisplayListener
, dcl
);
337 qemu_dbus_display1_listener_call_mouse_set(
338 ddl
->proxy
, x
, y
, on
, G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
341 static void dbus_cursor_define(DisplayChangeListener
*dcl
,
344 DBusDisplayListener
*ddl
= container_of(dcl
, DBusDisplayListener
, dcl
);
345 GVariant
*v_data
= NULL
;
348 v_data
= g_variant_new_from_data(
349 G_VARIANT_TYPE("ay"),
351 c
->width
* c
->height
* 4,
353 (GDestroyNotify
)cursor_put
,
356 qemu_dbus_display1_listener_call_cursor_define(
363 G_DBUS_CALL_FLAGS_NONE
,
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
,
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
,
388 const DisplayChangeListenerOps dbus_dcl_ops
= {
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
,
398 dbus_display_listener_dispose(GObject
*object
)
400 DBusDisplayListener
*ddl
= DBUS_DISPLAY_LISTENER(object
);
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
);
408 G_OBJECT_CLASS(dbus_display_listener_parent_class
)->dispose(object
);
412 dbus_display_listener_constructed(GObject
*object
)
414 DBusDisplayListener
*ddl
= DBUS_DISPLAY_LISTENER(object
);
416 if (display_opengl
) {
417 ddl
->gls
= qemu_gl_init_shader();
418 ddl
->dcl
.ops
= &dbus_gl_dcl_ops
;
420 ddl
->dcl
.ops
= &dbus_dcl_ops
;
423 G_OBJECT_CLASS(dbus_display_listener_parent_class
)->constructed(object
);
427 dbus_display_listener_class_init(DBusDisplayListenerClass
*klass
)
429 GObjectClass
*object_class
= G_OBJECT_CLASS(klass
);
431 object_class
->dispose
= dbus_display_listener_dispose
;
432 object_class
->constructed
= dbus_display_listener_constructed
;
436 dbus_display_listener_init(DBusDisplayListener
*ddl
)
441 dbus_display_listener_get_bus_name(DBusDisplayListener
*ddl
)
443 return ddl
->bus_name
?: "p2p";
447 dbus_display_listener_get_console(DBusDisplayListener
*ddl
)
452 DBusDisplayListener
*
453 dbus_display_listener_new(const char *bus_name
,
454 GDBusConnection
*conn
,
455 DBusDisplayConsole
*console
)
457 DBusDisplayListener
*ddl
;
459 g_autoptr(GError
) err
= NULL
;
461 ddl
= g_object_new(DBUS_DISPLAY_TYPE_LISTENER
, NULL
);
463 qemu_dbus_display1_listener_proxy_new_sync(conn
,
464 G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START
,
466 "/org/qemu/Display1/Listener",
470 error_report("Failed to setup proxy: %s", err
->message
);
471 g_object_unref(conn
);
476 ddl
->bus_name
= g_strdup(bus_name
);
478 ddl
->console
= console
;
480 con
= qemu_console_lookup_by_index(dbus_display_console_get_index(console
));
483 register_displaychangelistener(&ddl
->dcl
);