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