xref: /qemu/ui/dbus-listener.c (revision 32cad1ffb81dcecf6f4a8af56d6e5892682839b1)
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 "system/system.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  
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  
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
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
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  
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  
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)
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  
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
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
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
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 */
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
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
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  
597  static void dbus_release_dmabuf(DisplayChangeListener *dcl,
598                                  QemuDmaBuf *dmabuf)
599  {
600      dbus_scanout_disable(dcl);
601  }
602  #endif /* GBM */
603  
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  
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  
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  
652  static void dbus_refresh(DisplayChangeListener *dcl)
653  {
654      graphic_hw_update(dcl->con);
655  }
656  
657  #ifdef CONFIG_OPENGL
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  
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  
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  
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
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  
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  
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  
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
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
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
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
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 *
924  dbus_display_listener_get_bus_name(DBusDisplayListener *ddl)
925  {
926      return ddl->bus_name ?: "p2p";
927  }
928  
929  DBusDisplayConsole *
930  dbus_display_listener_get_console(DBusDisplayListener *ddl)
931  {
932      return ddl->console;
933  }
934  
935  static bool
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
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
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
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 *
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 *
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