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 "qapi/error.h"
27 #include "sysemu/sysemu.h"
28 #include "dbus.h"
29 #include "glib.h"
30 #ifdef G_OS_UNIX
31 #include <gio/gunixfdlist.h>
32 #endif
33 #ifdef WIN32
34 #include <d3d11.h>
35 #include <dxgi1_2.h>
36 #endif
37
38 #ifdef CONFIG_OPENGL
39 #include "ui/shader.h"
40 #include "ui/egl-helpers.h"
41 #include "ui/egl-context.h"
42 #include "ui/qemu-pixman.h"
43 #endif
44 #include "trace.h"
45
46 static void dbus_gfx_switch(DisplayChangeListener *dcl,
47 struct DisplaySurface *new_surface);
48
49 enum share_kind {
50 SHARE_KIND_NONE,
51 SHARE_KIND_MAPPED,
52 SHARE_KIND_D3DTEX,
53 };
54
55 struct _DBusDisplayListener {
56 GObject parent;
57
58 char *bus_name;
59 DBusDisplayConsole *console;
60 GDBusConnection *conn;
61
62 QemuDBusDisplay1Listener *proxy;
63
64 #ifdef CONFIG_PIXMAN
65 /* Keep track of the damage region */
66 pixman_region32_t gl_damage;
67 #else
68 int gl_damage;
69 #endif
70
71 DisplayChangeListener dcl;
72 DisplaySurface *ds;
73 enum share_kind ds_share;
74
75 bool ds_mapped;
76 bool can_share_map;
77
78 #ifdef WIN32
79 QemuDBusDisplay1ListenerWin32Map *map_proxy;
80 QemuDBusDisplay1ListenerWin32D3d11 *d3d11_proxy;
81 HANDLE peer_process;
82 ID3D11Texture2D *d3d_texture;
83 #ifdef CONFIG_OPENGL
84 egl_fb fb;
85 #endif
86 #else /* !WIN32 */
87 QemuDBusDisplay1ListenerUnixMap *map_proxy;
88 #endif
89
90 guint dbus_filter;
91 guint32 display_serial_to_discard;
92 guint32 cursor_serial_to_discard;
93 };
94
95 G_DEFINE_TYPE(DBusDisplayListener, dbus_display_listener, G_TYPE_OBJECT)
96
97 static void dbus_gfx_update(DisplayChangeListener *dcl,
98 int x, int y, int w, int h);
99
ddl_discard_display_messages(DBusDisplayListener * ddl)100 static void ddl_discard_display_messages(DBusDisplayListener *ddl)
101 {
102 guint32 serial = g_dbus_connection_get_last_serial(
103 g_dbus_proxy_get_connection(G_DBUS_PROXY(ddl->proxy)));
104
105 g_atomic_int_set(&ddl->display_serial_to_discard, serial);
106 }
107
ddl_discard_cursor_messages(DBusDisplayListener * ddl)108 static void ddl_discard_cursor_messages(DBusDisplayListener *ddl)
109 {
110 guint32 serial = g_dbus_connection_get_last_serial(
111 g_dbus_proxy_get_connection(G_DBUS_PROXY(ddl->proxy)));
112
113 g_atomic_int_set(&ddl->cursor_serial_to_discard, serial);
114 }
115
116 #ifdef CONFIG_OPENGL
dbus_scanout_disable(DisplayChangeListener * dcl)117 static void dbus_scanout_disable(DisplayChangeListener *dcl)
118 {
119 DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
120
121 ddl_discard_display_messages(ddl);
122
123 qemu_dbus_display1_listener_call_disable(
124 ddl->proxy, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
125 }
126
127 #ifdef WIN32
d3d_texture2d_share(ID3D11Texture2D * d3d_texture,HANDLE * handle,Error ** errp)128 static bool d3d_texture2d_share(ID3D11Texture2D *d3d_texture,
129 HANDLE *handle, Error **errp)
130 {
131 IDXGIResource1 *dxgiResource = NULL;
132 HRESULT hr;
133
134 hr = d3d_texture->lpVtbl->QueryInterface(d3d_texture,
135 &IID_IDXGIResource1,
136 (void **)&dxgiResource);
137 if (FAILED(hr)) {
138 goto fail;
139 }
140
141 hr = dxgiResource->lpVtbl->CreateSharedHandle(
142 dxgiResource,
143 NULL,
144 DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE,
145 NULL,
146 handle
147 );
148
149 dxgiResource->lpVtbl->Release(dxgiResource);
150
151 if (SUCCEEDED(hr)) {
152 return true;
153 }
154
155 fail:
156 error_setg_win32(errp, GetLastError(), "failed to create shared handle");
157 return false;
158 }
159
d3d_texture2d_acquire0(ID3D11Texture2D * d3d_texture,Error ** errp)160 static bool d3d_texture2d_acquire0(ID3D11Texture2D *d3d_texture, Error **errp)
161 {
162 IDXGIKeyedMutex *dxgiMutex = NULL;
163 HRESULT hr;
164
165 hr = d3d_texture->lpVtbl->QueryInterface(d3d_texture,
166 &IID_IDXGIKeyedMutex,
167 (void **)&dxgiMutex);
168 if (FAILED(hr)) {
169 goto fail;
170 }
171
172 hr = dxgiMutex->lpVtbl->AcquireSync(dxgiMutex, 0, INFINITE);
173
174 dxgiMutex->lpVtbl->Release(dxgiMutex);
175
176 if (SUCCEEDED(hr)) {
177 return true;
178 }
179
180 fail:
181 error_setg_win32(errp, GetLastError(), "failed to acquire texture mutex");
182 return false;
183 }
184
d3d_texture2d_release0(ID3D11Texture2D * d3d_texture,Error ** errp)185 static bool d3d_texture2d_release0(ID3D11Texture2D *d3d_texture, Error **errp)
186 {
187 IDXGIKeyedMutex *dxgiMutex = NULL;
188 HRESULT hr;
189
190 hr = d3d_texture->lpVtbl->QueryInterface(d3d_texture,
191 &IID_IDXGIKeyedMutex,
192 (void **)&dxgiMutex);
193 if (FAILED(hr)) {
194 goto fail;
195 }
196
197 hr = dxgiMutex->lpVtbl->ReleaseSync(dxgiMutex, 0);
198
199 dxgiMutex->lpVtbl->Release(dxgiMutex);
200
201 if (SUCCEEDED(hr)) {
202 return true;
203 }
204
205 fail:
206 error_setg_win32(errp, GetLastError(), "failed to release texture mutex");
207 return false;
208 }
209 #endif /* WIN32 */
210
211 #if defined(CONFIG_GBM) || defined(WIN32)
dbus_update_gl_cb(GObject * source_object,GAsyncResult * res,gpointer user_data)212 static void dbus_update_gl_cb(GObject *source_object,
213 GAsyncResult *res,
214 gpointer user_data)
215 {
216 g_autoptr(GError) err = NULL;
217 DBusDisplayListener *ddl = user_data;
218 bool success;
219
220 #ifdef CONFIG_GBM
221 success = qemu_dbus_display1_listener_call_update_dmabuf_finish(
222 ddl->proxy, res, &err);
223 #endif
224
225 #ifdef WIN32
226 success = qemu_dbus_display1_listener_win32_d3d11_call_update_texture2d_finish(
227 ddl->d3d11_proxy, res, &err);
228 d3d_texture2d_acquire0(ddl->d3d_texture, &error_warn);
229 #endif
230
231 if (!success) {
232 error_report("Failed to call update: %s", err->message);
233 }
234
235 graphic_hw_gl_block(ddl->dcl.con, false);
236 g_object_unref(ddl);
237 }
238 #endif
239
dbus_call_update_gl(DisplayChangeListener * dcl,int x,int y,int w,int h)240 static void dbus_call_update_gl(DisplayChangeListener *dcl,
241 int x, int y, int w, int h)
242 {
243 #if defined(CONFIG_GBM) || defined(WIN32)
244 DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
245 #endif
246
247 trace_dbus_update_gl(x, y, w, h);
248
249 glFlush();
250 #ifdef CONFIG_GBM
251 graphic_hw_gl_block(ddl->dcl.con, true);
252 qemu_dbus_display1_listener_call_update_dmabuf(ddl->proxy,
253 x, y, w, h,
254 G_DBUS_CALL_FLAGS_NONE,
255 DBUS_DEFAULT_TIMEOUT, NULL,
256 dbus_update_gl_cb,
257 g_object_ref(ddl));
258 #endif
259
260 #ifdef WIN32
261 switch (ddl->ds_share) {
262 case SHARE_KIND_MAPPED:
263 egl_fb_read_rect(ddl->ds, &ddl->fb, x, y, w, h);
264 dbus_gfx_update(dcl, x, y, w, h);
265 break;
266 case SHARE_KIND_D3DTEX: {
267 Error *err = NULL;
268 assert(ddl->d3d_texture);
269
270 graphic_hw_gl_block(ddl->dcl.con, true);
271 if (!d3d_texture2d_release0(ddl->d3d_texture, &err)) {
272 error_report_err(err);
273 return;
274 }
275 qemu_dbus_display1_listener_win32_d3d11_call_update_texture2d(
276 ddl->d3d11_proxy,
277 x, y, w, h,
278 G_DBUS_CALL_FLAGS_NONE,
279 DBUS_DEFAULT_TIMEOUT, NULL,
280 dbus_update_gl_cb,
281 g_object_ref(ddl));
282 break;
283 }
284 default:
285 g_warn_if_reached();
286 }
287 #endif
288 }
289
290 #ifdef CONFIG_GBM
dbus_scanout_dmabuf(DisplayChangeListener * dcl,QemuDmaBuf * dmabuf)291 static void dbus_scanout_dmabuf(DisplayChangeListener *dcl,
292 QemuDmaBuf *dmabuf)
293 {
294 DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
295 g_autoptr(GError) err = NULL;
296 g_autoptr(GUnixFDList) fd_list = NULL;
297 int fd;
298 uint32_t width, height, stride, fourcc;
299 uint64_t modifier;
300 bool y0_top;
301
302 fd = qemu_dmabuf_get_fd(dmabuf);
303 fd_list = g_unix_fd_list_new();
304 if (g_unix_fd_list_append(fd_list, fd, &err) != 0) {
305 error_report("Failed to setup dmabuf fdlist: %s", err->message);
306 return;
307 }
308
309 ddl_discard_display_messages(ddl);
310
311 width = qemu_dmabuf_get_width(dmabuf);
312 height = qemu_dmabuf_get_height(dmabuf);
313 stride = qemu_dmabuf_get_stride(dmabuf);
314 fourcc = qemu_dmabuf_get_fourcc(dmabuf);
315 modifier = qemu_dmabuf_get_modifier(dmabuf);
316 y0_top = qemu_dmabuf_get_y0_top(dmabuf);
317
318 /* FIXME: add missing x/y/w/h support */
319 qemu_dbus_display1_listener_call_scanout_dmabuf(
320 ddl->proxy, g_variant_new_handle(0),
321 width, height, stride, fourcc, modifier,
322 y0_top, G_DBUS_CALL_FLAGS_NONE,
323 -1, fd_list, NULL, NULL, NULL);
324 }
325 #endif /* GBM */
326 #endif /* OPENGL */
327
328 #ifdef WIN32
dbus_scanout_map(DBusDisplayListener * ddl)329 static bool dbus_scanout_map(DBusDisplayListener *ddl)
330 {
331 g_autoptr(GError) err = NULL;
332 BOOL success;
333 HANDLE target_handle;
334
335 if (ddl->ds_share == SHARE_KIND_MAPPED) {
336 return true;
337 }
338
339 if (!ddl->can_share_map || !ddl->ds->share_handle) {
340 return false;
341 }
342
343 success = DuplicateHandle(
344 GetCurrentProcess(),
345 ddl->ds->share_handle,
346 ddl->peer_process,
347 &target_handle,
348 FILE_MAP_READ | SECTION_QUERY,
349 FALSE, 0);
350 if (!success) {
351 g_autofree char *msg = g_win32_error_message(GetLastError());
352 g_debug("Failed to DuplicateHandle: %s", msg);
353 ddl->can_share_map = false;
354 return false;
355 }
356
357 ddl_discard_display_messages(ddl);
358
359 if (!qemu_dbus_display1_listener_win32_map_call_scanout_map_sync(
360 ddl->map_proxy,
361 GPOINTER_TO_UINT(target_handle),
362 ddl->ds->share_handle_offset,
363 surface_width(ddl->ds),
364 surface_height(ddl->ds),
365 surface_stride(ddl->ds),
366 surface_format(ddl->ds),
367 G_DBUS_CALL_FLAGS_NONE,
368 DBUS_DEFAULT_TIMEOUT,
369 NULL,
370 &err)) {
371 g_debug("Failed to call ScanoutMap: %s", err->message);
372 ddl->can_share_map = false;
373 return false;
374 }
375
376 ddl->ds_share = SHARE_KIND_MAPPED;
377
378 return true;
379 }
380
381 #ifdef CONFIG_OPENGL
382 static bool
dbus_scanout_share_d3d_texture(DBusDisplayListener * ddl,ID3D11Texture2D * tex,bool backing_y_0_top,uint32_t backing_width,uint32_t backing_height,uint32_t x,uint32_t y,uint32_t w,uint32_t h)383 dbus_scanout_share_d3d_texture(
384 DBusDisplayListener *ddl,
385 ID3D11Texture2D *tex,
386 bool backing_y_0_top,
387 uint32_t backing_width,
388 uint32_t backing_height,
389 uint32_t x, uint32_t y,
390 uint32_t w, uint32_t h)
391 {
392 Error *err = NULL;
393 BOOL success;
394 HANDLE share_handle, target_handle;
395
396 if (!d3d_texture2d_release0(tex, &err)) {
397 error_report_err(err);
398 return false;
399 }
400
401 if (!d3d_texture2d_share(tex, &share_handle, &err)) {
402 error_report_err(err);
403 return false;
404 }
405
406 success = DuplicateHandle(
407 GetCurrentProcess(),
408 share_handle,
409 ddl->peer_process,
410 &target_handle,
411 0,
412 FALSE, DUPLICATE_SAME_ACCESS);
413 if (!success) {
414 g_autofree char *msg = g_win32_error_message(GetLastError());
415 g_debug("Failed to DuplicateHandle: %s", msg);
416 CloseHandle(share_handle);
417 return false;
418 }
419
420 ddl_discard_display_messages(ddl);
421
422 qemu_dbus_display1_listener_win32_d3d11_call_scanout_texture2d(
423 ddl->d3d11_proxy,
424 GPOINTER_TO_INT(target_handle),
425 backing_width,
426 backing_height,
427 backing_y_0_top,
428 x, y, w, h,
429 G_DBUS_CALL_FLAGS_NONE,
430 -1,
431 NULL, NULL, NULL);
432
433 CloseHandle(share_handle);
434
435 if (!d3d_texture2d_acquire0(tex, &err)) {
436 error_report_err(err);
437 return false;
438 }
439
440 ddl->d3d_texture = tex;
441 ddl->ds_share = SHARE_KIND_D3DTEX;
442
443 return true;
444 }
445 #endif /* CONFIG_OPENGL */
446 #else /* !WIN32 */
dbus_scanout_map(DBusDisplayListener * ddl)447 static bool dbus_scanout_map(DBusDisplayListener *ddl)
448 {
449 g_autoptr(GError) err = NULL;
450 g_autoptr(GUnixFDList) fd_list = NULL;
451
452 if (ddl->ds_share == SHARE_KIND_MAPPED) {
453 return true;
454 }
455
456 if (!ddl->can_share_map || ddl->ds->share_handle == SHAREABLE_NONE) {
457 return false;
458 }
459
460 ddl_discard_display_messages(ddl);
461 fd_list = g_unix_fd_list_new();
462 if (g_unix_fd_list_append(fd_list, ddl->ds->share_handle, &err) != 0) {
463 g_debug("Failed to setup scanout map fdlist: %s", err->message);
464 ddl->can_share_map = false;
465 return false;
466 }
467
468 if (!qemu_dbus_display1_listener_unix_map_call_scanout_map_sync(
469 ddl->map_proxy,
470 g_variant_new_handle(0),
471 ddl->ds->share_handle_offset,
472 surface_width(ddl->ds),
473 surface_height(ddl->ds),
474 surface_stride(ddl->ds),
475 surface_format(ddl->ds),
476 G_DBUS_CALL_FLAGS_NONE,
477 DBUS_DEFAULT_TIMEOUT,
478 fd_list,
479 NULL,
480 NULL,
481 &err)) {
482 g_debug("Failed to call ScanoutMap: %s", err->message);
483 ddl->can_share_map = false;
484 return false;
485 }
486
487 ddl->ds_share = SHARE_KIND_MAPPED;
488
489 return true;
490 }
491 #endif /* WIN32 */
492
493 #ifdef CONFIG_OPENGL
dbus_scanout_texture(DisplayChangeListener * dcl,uint32_t tex_id,bool backing_y_0_top,uint32_t backing_width,uint32_t backing_height,uint32_t x,uint32_t y,uint32_t w,uint32_t h,void * d3d_tex2d)494 static void dbus_scanout_texture(DisplayChangeListener *dcl,
495 uint32_t tex_id,
496 bool backing_y_0_top,
497 uint32_t backing_width,
498 uint32_t backing_height,
499 uint32_t x, uint32_t y,
500 uint32_t w, uint32_t h,
501 void *d3d_tex2d)
502 {
503 trace_dbus_scanout_texture(tex_id, backing_y_0_top,
504 backing_width, backing_height, x, y, w, h);
505 #ifdef CONFIG_GBM
506 g_autoptr(QemuDmaBuf) dmabuf = NULL;
507 int fd;
508 uint32_t stride, fourcc;
509 uint64_t modifier;
510
511 assert(tex_id);
512 fd = egl_get_fd_for_texture(tex_id, (EGLint *)&stride, (EGLint *)&fourcc,
513 &modifier);
514 if (fd < 0) {
515 error_report("%s: failed to get fd for texture", __func__);
516 return;
517 }
518 dmabuf = qemu_dmabuf_new(w, h, stride, x, y, backing_width,
519 backing_height, fourcc, modifier, fd,
520 false, backing_y_0_top);
521
522 dbus_scanout_dmabuf(dcl, dmabuf);
523 qemu_dmabuf_close(dmabuf);
524 #endif
525
526 #ifdef WIN32
527 DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
528
529 /* there must be a matching gfx_switch before */
530 assert(surface_width(ddl->ds) == w);
531 assert(surface_height(ddl->ds) == h);
532
533 if (d3d_tex2d) {
534 dbus_scanout_share_d3d_texture(ddl, d3d_tex2d, backing_y_0_top,
535 backing_width, backing_height, x, y, w, h);
536 } else {
537 dbus_scanout_map(ddl);
538 egl_fb_setup_for_tex(&ddl->fb, backing_width, backing_height, tex_id, false);
539 }
540 #endif
541 }
542
543 #ifdef CONFIG_GBM
dbus_cursor_dmabuf(DisplayChangeListener * dcl,QemuDmaBuf * dmabuf,bool have_hot,uint32_t hot_x,uint32_t hot_y)544 static void dbus_cursor_dmabuf(DisplayChangeListener *dcl,
545 QemuDmaBuf *dmabuf, bool have_hot,
546 uint32_t hot_x, uint32_t hot_y)
547 {
548 DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
549 DisplaySurface *ds;
550 GVariant *v_data = NULL;
551 egl_fb cursor_fb = EGL_FB_INIT;
552 uint32_t width, height, texture;
553
554 if (!dmabuf) {
555 qemu_dbus_display1_listener_call_mouse_set(
556 ddl->proxy, 0, 0, false,
557 G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
558 return;
559 }
560
561 ddl_discard_cursor_messages(ddl);
562
563 egl_dmabuf_import_texture(dmabuf);
564 texture = qemu_dmabuf_get_texture(dmabuf);
565 if (!texture) {
566 return;
567 }
568
569 width = qemu_dmabuf_get_width(dmabuf);
570 height = qemu_dmabuf_get_height(dmabuf);
571
572 egl_fb_setup_for_tex(&cursor_fb, width, height, texture, false);
573 ds = qemu_create_displaysurface(width, height);
574 egl_fb_read(ds, &cursor_fb);
575
576 v_data = g_variant_new_from_data(
577 G_VARIANT_TYPE("ay"),
578 surface_data(ds),
579 surface_width(ds) * surface_height(ds) * 4,
580 TRUE,
581 (GDestroyNotify)qemu_free_displaysurface,
582 ds);
583 qemu_dbus_display1_listener_call_cursor_define(
584 ddl->proxy,
585 surface_width(ds),
586 surface_height(ds),
587 hot_x,
588 hot_y,
589 v_data,
590 G_DBUS_CALL_FLAGS_NONE,
591 -1,
592 NULL,
593 NULL,
594 NULL);
595 }
596
dbus_release_dmabuf(DisplayChangeListener * dcl,QemuDmaBuf * dmabuf)597 static void dbus_release_dmabuf(DisplayChangeListener *dcl,
598 QemuDmaBuf *dmabuf)
599 {
600 dbus_scanout_disable(dcl);
601 }
602 #endif /* GBM */
603
dbus_gl_cursor_position(DisplayChangeListener * dcl,uint32_t pos_x,uint32_t pos_y)604 static void dbus_gl_cursor_position(DisplayChangeListener *dcl,
605 uint32_t pos_x, uint32_t pos_y)
606 {
607 DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
608
609 qemu_dbus_display1_listener_call_mouse_set(
610 ddl->proxy, pos_x, pos_y, true,
611 G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
612 }
613
dbus_scanout_update(DisplayChangeListener * dcl,uint32_t x,uint32_t y,uint32_t w,uint32_t h)614 static void dbus_scanout_update(DisplayChangeListener *dcl,
615 uint32_t x, uint32_t y,
616 uint32_t w, uint32_t h)
617 {
618 dbus_call_update_gl(dcl, x, y, w, h);
619 }
620
dbus_gl_refresh(DisplayChangeListener * dcl)621 static void dbus_gl_refresh(DisplayChangeListener *dcl)
622 {
623 DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
624
625 graphic_hw_update(dcl->con);
626
627 if (!ddl->ds || qemu_console_is_gl_blocked(ddl->dcl.con)) {
628 return;
629 }
630
631 #ifdef CONFIG_PIXMAN
632 int n_rects = pixman_region32_n_rects(&ddl->gl_damage);
633
634 for (int i = 0; i < n_rects; i++) {
635 pixman_box32_t *box;
636 box = pixman_region32_rectangles(&ddl->gl_damage, NULL) + i;
637 /* TODO: Add a UpdateList call to send multiple updates at once */
638 dbus_call_update_gl(dcl, box->x1, box->y1,
639 box->x2 - box->x1, box->y2 - box->y1);
640 }
641 pixman_region32_clear(&ddl->gl_damage);
642 #else
643 if (ddl->gl_damage) {
644 dbus_call_update_gl(dcl, 0, 0,
645 surface_width(ddl->ds), surface_height(ddl->ds));
646 ddl->gl_damage = 0;
647 }
648 #endif
649 }
650 #endif /* OPENGL */
651
dbus_refresh(DisplayChangeListener * dcl)652 static void dbus_refresh(DisplayChangeListener *dcl)
653 {
654 graphic_hw_update(dcl->con);
655 }
656
657 #ifdef CONFIG_OPENGL
dbus_gl_gfx_update(DisplayChangeListener * dcl,int x,int y,int w,int h)658 static void dbus_gl_gfx_update(DisplayChangeListener *dcl,
659 int x, int y, int w, int h)
660 {
661 DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
662
663 #ifdef CONFIG_PIXMAN
664 pixman_region32_t rect_region;
665 pixman_region32_init_rect(&rect_region, x, y, w, h);
666 pixman_region32_union(&ddl->gl_damage, &ddl->gl_damage, &rect_region);
667 pixman_region32_fini(&rect_region);
668 #else
669 ddl->gl_damage++;
670 #endif
671 }
672 #endif
673
dbus_gfx_update_sub(DBusDisplayListener * ddl,int x,int y,int w,int h)674 static void dbus_gfx_update_sub(DBusDisplayListener *ddl,
675 int x, int y, int w, int h)
676 {
677 pixman_image_t *img;
678 size_t stride;
679 GVariant *v_data;
680
681 /* make a copy, since gvariant only handles linear data */
682 stride = w * DIV_ROUND_UP(PIXMAN_FORMAT_BPP(surface_format(ddl->ds)), 8);
683 img = pixman_image_create_bits(surface_format(ddl->ds),
684 w, h, NULL, stride);
685 #ifdef CONFIG_PIXMAN
686 pixman_image_composite(PIXMAN_OP_SRC, ddl->ds->image, NULL, img,
687 x, y, 0, 0, 0, 0, w, h);
688 #else
689 {
690 uint8_t *src = (uint8_t *)pixman_image_get_data(ddl->ds->image);
691 uint8_t *dst = (uint8_t *)pixman_image_get_data(img);
692 int bp = PIXMAN_FORMAT_BPP(surface_format(ddl->ds)) / 8;
693 int hh;
694
695 for (hh = 0; hh < h; hh++) {
696 memcpy(&dst[stride * hh],
697 &src[surface_stride(ddl->ds) * (hh + y) + x * bp],
698 stride);
699 }
700 }
701 #endif
702 v_data = g_variant_new_from_data(
703 G_VARIANT_TYPE("ay"),
704 pixman_image_get_data(img),
705 pixman_image_get_stride(img) * h,
706 TRUE,
707 (GDestroyNotify)pixman_image_unref,
708 img);
709 qemu_dbus_display1_listener_call_update(ddl->proxy,
710 x, y, w, h, pixman_image_get_stride(img), pixman_image_get_format(img),
711 v_data,
712 G_DBUS_CALL_FLAGS_NONE,
713 DBUS_DEFAULT_TIMEOUT, NULL, NULL, NULL);
714 }
715
ddl_scanout(DBusDisplayListener * ddl)716 static void ddl_scanout(DBusDisplayListener *ddl)
717 {
718 GVariant *v_data;
719
720 v_data = g_variant_new_from_data(
721 G_VARIANT_TYPE("ay"), surface_data(ddl->ds),
722 surface_stride(ddl->ds) * surface_height(ddl->ds), TRUE,
723 (GDestroyNotify)pixman_image_unref, pixman_image_ref(ddl->ds->image));
724
725 ddl_discard_display_messages(ddl);
726
727 qemu_dbus_display1_listener_call_scanout(
728 ddl->proxy, surface_width(ddl->ds), surface_height(ddl->ds),
729 surface_stride(ddl->ds), surface_format(ddl->ds), v_data,
730 G_DBUS_CALL_FLAGS_NONE, DBUS_DEFAULT_TIMEOUT, NULL, NULL,
731 g_object_ref(ddl));
732 }
733
dbus_gfx_update(DisplayChangeListener * dcl,int x,int y,int w,int h)734 static void dbus_gfx_update(DisplayChangeListener *dcl,
735 int x, int y, int w, int h)
736 {
737 DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
738
739 assert(ddl->ds);
740
741 trace_dbus_update(x, y, w, h);
742
743 if (dbus_scanout_map(ddl)) {
744 #ifdef WIN32
745 qemu_dbus_display1_listener_win32_map_call_update_map(
746 ddl->map_proxy,
747 x, y, w, h,
748 G_DBUS_CALL_FLAGS_NONE,
749 DBUS_DEFAULT_TIMEOUT, NULL, NULL, NULL);
750 #else
751 qemu_dbus_display1_listener_unix_map_call_update_map(
752 ddl->map_proxy,
753 x, y, w, h,
754 G_DBUS_CALL_FLAGS_NONE,
755 DBUS_DEFAULT_TIMEOUT, NULL, NULL, NULL);
756 #endif
757 return;
758 }
759
760 if (x == 0 && y == 0 && w == surface_width(ddl->ds) && h == surface_height(ddl->ds)) {
761 return ddl_scanout(ddl);
762 }
763
764 dbus_gfx_update_sub(ddl, x, y, w, h);
765 }
766
767 #ifdef CONFIG_OPENGL
dbus_gl_gfx_switch(DisplayChangeListener * dcl,struct DisplaySurface * new_surface)768 static void dbus_gl_gfx_switch(DisplayChangeListener *dcl,
769 struct DisplaySurface *new_surface)
770 {
771 DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
772
773 trace_dbus_gl_gfx_switch(new_surface);
774
775 ddl->ds = new_surface;
776 ddl->ds_share = SHARE_KIND_NONE;
777 if (ddl->ds) {
778 int width = surface_width(ddl->ds);
779 int height = surface_height(ddl->ds);
780
781 /* TODO: lazy send dmabuf (there are unnecessary sent otherwise) */
782 dbus_scanout_texture(&ddl->dcl, ddl->ds->texture, false,
783 width, height, 0, 0, width, height, NULL);
784 }
785 }
786 #endif
787
dbus_gfx_switch(DisplayChangeListener * dcl,struct DisplaySurface * new_surface)788 static void dbus_gfx_switch(DisplayChangeListener *dcl,
789 struct DisplaySurface *new_surface)
790 {
791 DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
792
793 ddl->ds = new_surface;
794 ddl->ds_share = SHARE_KIND_NONE;
795 }
796
dbus_mouse_set(DisplayChangeListener * dcl,int x,int y,bool on)797 static void dbus_mouse_set(DisplayChangeListener *dcl,
798 int x, int y, bool on)
799 {
800 DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
801
802 qemu_dbus_display1_listener_call_mouse_set(
803 ddl->proxy, x, y, on, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
804 }
805
dbus_cursor_define(DisplayChangeListener * dcl,QEMUCursor * c)806 static void dbus_cursor_define(DisplayChangeListener *dcl,
807 QEMUCursor *c)
808 {
809 DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
810 GVariant *v_data = NULL;
811
812 ddl_discard_cursor_messages(ddl);
813
814 v_data = g_variant_new_from_data(
815 G_VARIANT_TYPE("ay"),
816 c->data,
817 c->width * c->height * 4,
818 TRUE,
819 (GDestroyNotify)cursor_unref,
820 cursor_ref(c));
821
822 qemu_dbus_display1_listener_call_cursor_define(
823 ddl->proxy,
824 c->width,
825 c->height,
826 c->hot_x,
827 c->hot_y,
828 v_data,
829 G_DBUS_CALL_FLAGS_NONE,
830 -1,
831 NULL,
832 NULL,
833 NULL);
834 }
835
836 #ifdef CONFIG_OPENGL
837 const DisplayChangeListenerOps dbus_gl_dcl_ops = {
838 .dpy_name = "dbus-gl",
839 .dpy_gfx_update = dbus_gl_gfx_update,
840 .dpy_gfx_switch = dbus_gl_gfx_switch,
841 .dpy_gfx_check_format = console_gl_check_format,
842 .dpy_refresh = dbus_gl_refresh,
843 .dpy_mouse_set = dbus_mouse_set,
844 .dpy_cursor_define = dbus_cursor_define,
845
846 .dpy_gl_scanout_disable = dbus_scanout_disable,
847 .dpy_gl_scanout_texture = dbus_scanout_texture,
848 #ifdef CONFIG_GBM
849 .dpy_gl_scanout_dmabuf = dbus_scanout_dmabuf,
850 .dpy_gl_cursor_dmabuf = dbus_cursor_dmabuf,
851 .dpy_gl_release_dmabuf = dbus_release_dmabuf,
852 #endif
853 .dpy_gl_cursor_position = dbus_gl_cursor_position,
854 .dpy_gl_update = dbus_scanout_update,
855 };
856 #endif
857
858 const DisplayChangeListenerOps dbus_dcl_ops = {
859 .dpy_name = "dbus",
860 .dpy_gfx_update = dbus_gfx_update,
861 .dpy_gfx_switch = dbus_gfx_switch,
862 .dpy_refresh = dbus_refresh,
863 .dpy_mouse_set = dbus_mouse_set,
864 .dpy_cursor_define = dbus_cursor_define,
865 };
866
867 static void
dbus_display_listener_dispose(GObject * object)868 dbus_display_listener_dispose(GObject *object)
869 {
870 DBusDisplayListener *ddl = DBUS_DISPLAY_LISTENER(object);
871
872 unregister_displaychangelistener(&ddl->dcl);
873 g_clear_object(&ddl->conn);
874 g_clear_pointer(&ddl->bus_name, g_free);
875 g_clear_object(&ddl->proxy);
876 #ifdef WIN32
877 g_clear_object(&ddl->map_proxy);
878 g_clear_object(&ddl->d3d11_proxy);
879 g_clear_pointer(&ddl->peer_process, CloseHandle);
880 #ifdef CONFIG_PIXMAN
881 pixman_region32_fini(&ddl->gl_damage);
882 #endif
883 #ifdef CONFIG_OPENGL
884 egl_fb_destroy(&ddl->fb);
885 #endif
886 #endif
887
888 G_OBJECT_CLASS(dbus_display_listener_parent_class)->dispose(object);
889 }
890
891 static void
dbus_display_listener_constructed(GObject * object)892 dbus_display_listener_constructed(GObject *object)
893 {
894 DBusDisplayListener *ddl = DBUS_DISPLAY_LISTENER(object);
895
896 ddl->dcl.ops = &dbus_dcl_ops;
897 #ifdef CONFIG_OPENGL
898 if (display_opengl) {
899 ddl->dcl.ops = &dbus_gl_dcl_ops;
900 }
901 #endif
902
903 G_OBJECT_CLASS(dbus_display_listener_parent_class)->constructed(object);
904 }
905
906 static void
dbus_display_listener_class_init(DBusDisplayListenerClass * klass)907 dbus_display_listener_class_init(DBusDisplayListenerClass *klass)
908 {
909 GObjectClass *object_class = G_OBJECT_CLASS(klass);
910
911 object_class->dispose = dbus_display_listener_dispose;
912 object_class->constructed = dbus_display_listener_constructed;
913 }
914
915 static void
dbus_display_listener_init(DBusDisplayListener * ddl)916 dbus_display_listener_init(DBusDisplayListener *ddl)
917 {
918 #ifdef CONFIG_PIXMAN
919 pixman_region32_init(&ddl->gl_damage);
920 #endif
921 }
922
923 const char *
dbus_display_listener_get_bus_name(DBusDisplayListener * ddl)924 dbus_display_listener_get_bus_name(DBusDisplayListener *ddl)
925 {
926 return ddl->bus_name ?: "p2p";
927 }
928
929 DBusDisplayConsole *
dbus_display_listener_get_console(DBusDisplayListener * ddl)930 dbus_display_listener_get_console(DBusDisplayListener *ddl)
931 {
932 return ddl->console;
933 }
934
935 static bool
dbus_display_listener_implements(DBusDisplayListener * ddl,const char * iface)936 dbus_display_listener_implements(DBusDisplayListener *ddl, const char *iface)
937 {
938 QemuDBusDisplay1Listener *l = QEMU_DBUS_DISPLAY1_LISTENER(ddl->proxy);
939 bool implements;
940
941 implements = g_strv_contains(qemu_dbus_display1_listener_get_interfaces(l), iface);
942 if (!implements) {
943 g_debug("Display listener does not implement: `%s`", iface);
944 }
945
946 return implements;
947 }
948
949 #ifdef WIN32
950 static bool
dbus_display_listener_setup_peer_process(DBusDisplayListener * ddl)951 dbus_display_listener_setup_peer_process(DBusDisplayListener *ddl)
952 {
953 g_autoptr(GError) err = NULL;
954 GDBusConnection *conn;
955 GIOStream *stream;
956 GSocket *sock;
957 g_autoptr(GCredentials) creds = NULL;
958 DWORD *pid;
959
960 if (ddl->peer_process) {
961 return true;
962 }
963
964 conn = g_dbus_proxy_get_connection(G_DBUS_PROXY(ddl->proxy));
965 stream = g_dbus_connection_get_stream(conn);
966
967 if (!G_IS_UNIX_CONNECTION(stream)) {
968 return false;
969 }
970
971 sock = g_socket_connection_get_socket(G_SOCKET_CONNECTION(stream));
972 creds = g_socket_get_credentials(sock, &err);
973
974 if (!creds) {
975 g_debug("Failed to get peer credentials: %s", err->message);
976 return false;
977 }
978
979 pid = g_credentials_get_native(creds, G_CREDENTIALS_TYPE_WIN32_PID);
980
981 if (pid == NULL) {
982 g_debug("Failed to get peer PID");
983 return false;
984 }
985
986 ddl->peer_process = OpenProcess(
987 PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION,
988 false, *pid);
989
990 if (!ddl->peer_process) {
991 g_autofree char *msg = g_win32_error_message(GetLastError());
992 g_debug("Failed to OpenProcess: %s", msg);
993 return false;
994 }
995
996 return true;
997 }
998 #endif
999
1000 static void
dbus_display_listener_setup_d3d11(DBusDisplayListener * ddl)1001 dbus_display_listener_setup_d3d11(DBusDisplayListener *ddl)
1002 {
1003 #ifdef WIN32
1004 g_autoptr(GError) err = NULL;
1005
1006 if (!dbus_display_listener_implements(ddl,
1007 "org.qemu.Display1.Listener.Win32.D3d11")) {
1008 return;
1009 }
1010
1011 if (!dbus_display_listener_setup_peer_process(ddl)) {
1012 return;
1013 }
1014
1015 ddl->d3d11_proxy =
1016 qemu_dbus_display1_listener_win32_d3d11_proxy_new_sync(ddl->conn,
1017 G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
1018 NULL,
1019 "/org/qemu/Display1/Listener",
1020 NULL,
1021 &err);
1022 if (!ddl->d3d11_proxy) {
1023 g_debug("Failed to setup win32 d3d11 proxy: %s", err->message);
1024 return;
1025 }
1026 #endif
1027 }
1028
1029 static void
dbus_display_listener_setup_shared_map(DBusDisplayListener * ddl)1030 dbus_display_listener_setup_shared_map(DBusDisplayListener *ddl)
1031 {
1032 g_autoptr(GError) err = NULL;
1033
1034 #ifdef WIN32
1035 if (!dbus_display_listener_implements(
1036 ddl, "org.qemu.Display1.Listener.Win32.Map")) {
1037 return;
1038 }
1039
1040 if (!dbus_display_listener_setup_peer_process(ddl)) {
1041 return;
1042 }
1043
1044 ddl->map_proxy =
1045 qemu_dbus_display1_listener_win32_map_proxy_new_sync(ddl->conn,
1046 G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
1047 NULL,
1048 "/org/qemu/Display1/Listener",
1049 NULL,
1050 &err);
1051 if (!ddl->map_proxy) {
1052 g_debug("Failed to setup win32 map proxy: %s", err->message);
1053 return;
1054 }
1055
1056 ddl->can_share_map = true;
1057 #else /* !WIN32 */
1058 if (!dbus_display_listener_implements(
1059 ddl, "org.qemu.Display1.Listener.Unix.Map")) {
1060 return;
1061 }
1062 ddl->map_proxy = qemu_dbus_display1_listener_unix_map_proxy_new_sync(
1063 ddl->conn, G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, NULL,
1064 "/org/qemu/Display1/Listener", NULL, &err);
1065 if (!ddl->map_proxy) {
1066 g_debug("Failed to setup Unix map proxy: %s", err->message);
1067 return;
1068 }
1069
1070 ddl->can_share_map = true;
1071 #endif
1072 }
1073
1074 static GDBusMessage *
dbus_filter(GDBusConnection * connection,GDBusMessage * message,gboolean incoming,gpointer user_data)1075 dbus_filter(GDBusConnection *connection,
1076 GDBusMessage *message,
1077 gboolean incoming,
1078 gpointer user_data)
1079 {
1080 DBusDisplayListener *ddl = DBUS_DISPLAY_LISTENER(user_data);
1081 guint32 serial, discard_serial;
1082
1083 if (incoming) {
1084 return message;
1085 }
1086
1087 serial = g_dbus_message_get_serial(message);
1088
1089 discard_serial = g_atomic_int_get(&ddl->display_serial_to_discard);
1090 if (serial <= discard_serial) {
1091 const char *member = g_dbus_message_get_member(message);
1092 static const char *const display_messages[] = {
1093 "Scanout",
1094 "Update",
1095 #ifdef CONFIG_GBM
1096 "ScanoutDMABUF",
1097 "UpdateDMABUF",
1098 #endif
1099 "ScanoutMap",
1100 "UpdateMap",
1101 "Disable",
1102 NULL,
1103 };
1104
1105 if (g_strv_contains(display_messages, member)) {
1106 trace_dbus_filter(serial, discard_serial);
1107 g_object_unref(message);
1108 return NULL;
1109 }
1110 }
1111
1112 discard_serial = g_atomic_int_get(&ddl->cursor_serial_to_discard);
1113 if (serial <= discard_serial) {
1114 const gchar *member = g_dbus_message_get_member(message);
1115 static const char *const cursor_messages[] = {
1116 "CursorDefine",
1117 NULL
1118 };
1119
1120 if (g_strv_contains(cursor_messages, member)) {
1121 trace_dbus_filter(serial, discard_serial);
1122 g_object_unref(message);
1123 return NULL;
1124 }
1125 }
1126
1127 return message;
1128 }
1129
1130 DBusDisplayListener *
dbus_display_listener_new(const char * bus_name,GDBusConnection * conn,DBusDisplayConsole * console)1131 dbus_display_listener_new(const char *bus_name,
1132 GDBusConnection *conn,
1133 DBusDisplayConsole *console)
1134 {
1135 DBusDisplayListener *ddl;
1136 QemuConsole *con;
1137 g_autoptr(GError) err = NULL;
1138
1139 ddl = g_object_new(DBUS_DISPLAY_TYPE_LISTENER, NULL);
1140 ddl->proxy =
1141 qemu_dbus_display1_listener_proxy_new_sync(conn,
1142 G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
1143 NULL,
1144 "/org/qemu/Display1/Listener",
1145 NULL,
1146 &err);
1147 if (!ddl->proxy) {
1148 error_report("Failed to setup proxy: %s", err->message);
1149 g_object_unref(conn);
1150 g_object_unref(ddl);
1151 return NULL;
1152 }
1153
1154 ddl->dbus_filter = g_dbus_connection_add_filter(conn, dbus_filter, g_object_ref(ddl), g_object_unref);
1155 ddl->bus_name = g_strdup(bus_name);
1156 ddl->conn = conn;
1157 ddl->console = console;
1158
1159 dbus_display_listener_setup_shared_map(ddl);
1160 trace_dbus_can_share_map(ddl->can_share_map);
1161 dbus_display_listener_setup_d3d11(ddl);
1162
1163 con = qemu_console_lookup_by_index(dbus_display_console_get_index(console));
1164 assert(con);
1165 ddl->dcl.con = con;
1166 register_displaychangelistener(&ddl->dcl);
1167
1168 return ddl;
1169 }
1170