xref: /openbmc/qemu/ui/dbus-listener.c (revision 9c4888c9)
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