All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 00/37] Add D-Bus display backend
@ 2021-10-09 21:08 marcandre.lureau
  2021-10-09 21:08 ` [PATCH v2 01/37] build-sys: move Spice configure handling to meson marcandre.lureau
                   ` (37 more replies)
  0 siblings, 38 replies; 54+ messages in thread
From: marcandre.lureau @ 2021-10-09 21:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, kraxel

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Hi,

Both Spice and VNC are relatively complex and inefficient for local-only
display/console export.

The goal of this display backend is to export over D-Bus an interface close to
the QEMU internal APIs. Any -display or -audio backend should be possible to
implement externally that way. It will allow third-parties to maintain their own
backends (UI toolkits, servers etc), and eventually reduce the responsability on
QEMU.

D-Bus is the protocol of choice for the desktop, it has many convenient bindings
for various languages and tools. Data blob transfer is more efficient than QMP
too. Backends can come and go as needed: you can have several display opened
(say Boxes & virt-manager), while exporting the display over VNC for example
from a different process. It works best on Unix, but there is some Windows
support too (even Windows has some AF_UNIX nowadays, and the WSL2 situation may
change the future of QEMU on Windows anyway).

Using it only requires "-display dbus" on any reasonable Linux desktop with a
D-Bus session bus. Then you use can use busctl, d-feet or gdbus, ex:
$ gdbus introspect --session -r -d org.qemu -o /

See the different patches and documentation for further options. The p2p=on mode
should also allow users running bus-less (on MacOS for ex). We can also add TCP
socket if needed (although more work would be needed in this case to replace
the FD-passing with some extra TCP listening socket).

A WIP Rust/Gtk4 client and VNC server is: https://gitlab.com/marcandre.lureau/qemu-display/
(check README.md for details, then `cargo run` should connect to QEMU)

The Sphinx build support works best with "[PATCH 0/6] Some Sphinx improvements",
where module dependency tracking is improved.

I can resend the first set of preliminary patches after some feedback.

Thanks

v2:
 - rebased
 - drop for the vhost-user-gpu work for now
 - add documentation, including D-Bus Sphinx directive
 - add bus-less option (p2p=on)
 - add some basic tests for the Console/Keyboard interfaces
 - add clipboard sharing support
 - add chardev redirection support (allowing USB redirection, monitors,
   serials...)
 - register a VC handler to export default serial/monitors
 - probably a few bug fixes here and there too

Marc-André Lureau (37):
  build-sys: move Spice configure handling to meson
  ui/vdagent: add CHECK_SPICE_PROTOCOL_VERSION
  ui/vdagent: replace #if 0 with protocol version check
  ui: generalize clipboard notifier
  ui/vdagent: add serial capability support
  ui/clipboard: add qemu_clipboard_check_serial()
  ui/clipboard: add a clipboard reset serial event
  hw/display: report an error if virgl initialization failed
  virtio-gpu: use VIRTIO_GPU_RESOURCE_FLAG_Y_0_TOP
  ui: do not delay further remote resize
  ui: factor out qemu_console_set_display_gl_ctx()
  ui: associate GL context outside of display listener registration
  ui: make gl_block use a counter
  ui: add a gl-unblock warning timer
  ui: simplify gl unblock & flush
  ui: dispatch GL events to all listeners
  ui: split the GL context in a different object
  ui: move qemu_spice_fill_device_address to ui/util.c
  console: save current scanout details
  scripts: teach modinfo to skip non-C sources
  docs/sphinx: add sphinx modules to include D-Bus documentation
  backends: move dbus-vmstate1.xml to backends/
  docs: move D-Bus VMState documentation to source XML
  docs: add dbus-display documentation
  build-sys: set glib dependency version
  ui: add a D-Bus display backend
  ui/dbus: add p2p=on/off option
  tests/qtests: add qtest_qmp_add_client()
  tests: start dbus-display-test
  audio: add "dbus" audio backend
  ui/dbus: add clipboard interface
  chardev: teach socket to accept no addresses
  chardev: make socket derivable
  option: add g_auto for QemuOpts
  ui/dbus: add chardev backend & interface
  ui/dbus: register D-Bus VC handler
  MAINTAINERS: update D-Bus section

 docs/conf.py                    |   8 +
 docs/interop/dbus-display.rst   |  31 ++
 docs/interop/dbus-vmstate.rst   |  52 +--
 docs/interop/dbus.rst           |   2 +
 docs/interop/index.rst          |   1 +
 docs/sphinx/dbusdoc.py          | 166 +++++++
 docs/sphinx/dbusdomain.py       | 406 +++++++++++++++++
 docs/sphinx/dbusparser.py       | 373 ++++++++++++++++
 docs/sphinx/fakedbusdoc.py      |  25 ++
 configure                       |  49 +-
 meson.build                     |  46 +-
 qapi/audio.json                 |   3 +-
 qapi/char.json                  |  27 ++
 qapi/misc.json                  |   4 +-
 qapi/ui.json                    |  34 +-
 audio/audio_int.h               |   7 +
 audio/audio_template.h          |   2 +
 include/chardev/char-socket.h   |  86 ++++
 include/qemu/cutils.h           |   5 +
 include/qemu/dbus.h             |  24 +
 include/qemu/option.h           |   2 +
 include/ui/clipboard.h          |  55 ++-
 include/ui/console.h            |  70 ++-
 include/ui/dbus-display.h       |  17 +
 include/ui/dbus-module.h        |  11 +
 include/ui/egl-context.h        |   6 +-
 include/ui/gtk.h                |  11 +-
 include/ui/sdl2.h               |   7 +-
 include/ui/spice-display.h      |   5 +-
 tests/qtest/libqos/libqtest.h   |  10 +
 ui/dbus.h                       | 144 ++++++
 audio/audio.c                   |   1 +
 audio/dbusaudio.c               | 654 +++++++++++++++++++++++++++
 chardev/char-socket.c           |  72 +--
 hw/display/qxl.c                |   7 +-
 hw/display/vhost-user-gpu.c     |   2 +-
 hw/display/virtio-gpu-base.c    |   5 +-
 hw/display/virtio-gpu-virgl.c   |   3 +-
 hw/display/virtio-vga.c         |  11 -
 monitor/qmp-cmds.c              |  13 +
 tests/qtest/dbus-display-test.c | 281 ++++++++++++
 tests/qtest/libqtest.c          |  19 +
 ui/clipboard.c                  |  34 +-
 ui/console.c                    | 305 +++++++++----
 ui/dbus-chardev.c               | 296 +++++++++++++
 ui/dbus-clipboard.c             | 457 +++++++++++++++++++
 ui/dbus-console.c               | 497 +++++++++++++++++++++
 ui/dbus-error.c                 |  48 ++
 ui/dbus-listener.c              | 486 ++++++++++++++++++++
 ui/dbus-module.c                |  35 ++
 ui/dbus.c                       | 477 ++++++++++++++++++++
 ui/egl-context.c                |   6 +-
 ui/egl-headless.c               |  20 +-
 ui/gtk-clipboard.c              |  23 +-
 ui/gtk-egl.c                    |  11 +-
 ui/gtk-gl-area.c                |   9 +-
 ui/gtk.c                        |  28 +-
 ui/sdl2-gl.c                    |  12 +-
 ui/sdl2.c                       |  16 +-
 ui/spice-core.c                 |  50 ---
 ui/spice-display.c              |  27 +-
 ui/util.c                       |  75 ++++
 ui/vdagent.c                    |  94 +++-
 ui/vnc-clipboard.c              |  21 +-
 ui/vnc.c                        |   4 +-
 MAINTAINERS                     |  10 +-
 audio/meson.build               |   8 +-
 audio/trace-events              |   5 +
 backends/dbus-vmstate1.xml      |  52 +++
 chardev/meson.build             |   2 +-
 meson_options.txt               |   6 +
 qemu-options.hx                 |  20 +
 scripts/modinfo-collect.py      |   3 +
 tests/qtest/dbus-vmstate1.xml   |  12 -
 tests/qtest/meson.build         |  10 +-
 ui/cocoa.m                      |  20 +-
 ui/dbus-display1.xml            | 761 ++++++++++++++++++++++++++++++++
 ui/meson.build                  |  32 +-
 ui/trace-events                 |  15 +
 79 files changed, 6284 insertions(+), 460 deletions(-)
 create mode 100644 docs/interop/dbus-display.rst
 create mode 100644 docs/sphinx/dbusdoc.py
 create mode 100644 docs/sphinx/dbusdomain.py
 create mode 100644 docs/sphinx/dbusparser.py
 create mode 100644 docs/sphinx/fakedbusdoc.py
 create mode 100644 include/chardev/char-socket.h
 create mode 100644 include/ui/dbus-display.h
 create mode 100644 include/ui/dbus-module.h
 create mode 100644 ui/dbus.h
 create mode 100644 audio/dbusaudio.c
 create mode 100644 tests/qtest/dbus-display-test.c
 create mode 100644 ui/dbus-chardev.c
 create mode 100644 ui/dbus-clipboard.c
 create mode 100644 ui/dbus-console.c
 create mode 100644 ui/dbus-error.c
 create mode 100644 ui/dbus-listener.c
 create mode 100644 ui/dbus-module.c
 create mode 100644 ui/dbus.c
 create mode 100644 ui/util.c
 create mode 100644 backends/dbus-vmstate1.xml
 delete mode 100644 tests/qtest/dbus-vmstate1.xml
 create mode 100644 ui/dbus-display1.xml

-- 
2.33.0.721.g106298f7f9




^ permalink raw reply	[flat|nested] 54+ messages in thread

* [PATCH v2 01/37] build-sys: move Spice configure handling to meson
  2021-10-09 21:08 [PATCH v2 00/37] Add D-Bus display backend marcandre.lureau
@ 2021-10-09 21:08 ` marcandre.lureau
  2021-10-09 21:08 ` [PATCH v2 02/37] ui/vdagent: add CHECK_SPICE_PROTOCOL_VERSION marcandre.lureau
                   ` (36 subsequent siblings)
  37 siblings, 0 replies; 54+ messages in thread
From: marcandre.lureau @ 2021-10-09 21:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, kraxel

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Add meson feature options for Spice and Spice protocol, and move
detection logic out of configure.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 configure           | 48 ++-------------------------------------------
 meson.build         | 27 ++++++++++++-------------
 chardev/meson.build |  2 +-
 meson_options.txt   |  4 ++++
 ui/meson.build      |  4 ++--
 5 files changed, 22 insertions(+), 63 deletions(-)

diff --git a/configure b/configure
index 877bf3d76a..375cde2b44 100755
--- a/configure
+++ b/configure
@@ -372,7 +372,7 @@ pie=""
 qom_cast_debug="yes"
 trace_backends="log"
 trace_file="trace"
-spice="$default_feature"
+spice="auto"
 spice_protocol="auto"
 rbd="auto"
 smartcard="auto"
@@ -3525,41 +3525,6 @@ EOF
   fi
 fi
 
-##########################################
-# spice probe
-if test "$spice_protocol" != "no" ; then
-  spice_protocol_cflags=$($pkg_config --cflags spice-protocol 2>/dev/null)
-  if $pkg_config --atleast-version=0.12.3 spice-protocol; then
-    spice_protocol="yes"
-  else
-    if test "$spice_protocol" = "yes" ; then
-      feature_not_found "spice_protocol" \
-          "Install spice-protocol(>=0.12.3) devel"
-    fi
-    spice_protocol="no"
-  fi
-fi
-
-if test "$spice" != "no" ; then
-  cat > $TMPC << EOF
-#include <spice.h>
-int main(void) { spice_server_new(); return 0; }
-EOF
-  spice_cflags=$($pkg_config --cflags spice-protocol spice-server 2>/dev/null)
-  spice_libs=$($pkg_config --libs spice-protocol spice-server 2>/dev/null)
-  if $pkg_config --atleast-version=0.12.5 spice-server && \
-     test "$spice_protocol" = "yes" && \
-     compile_prog "$spice_cflags" "$spice_libs" ; then
-    spice="yes"
-  else
-    if test "$spice" = "yes" ; then
-      feature_not_found "spice" \
-          "Install spice-server(>=0.12.5) devel"
-    fi
-    spice="no"
-  fi
-fi
-
 ##########################################
 # check if we have VSS SDK headers for win
 
@@ -4659,16 +4624,6 @@ if test "$tcg" = "enabled" -a "$tcg_interpreter" = "true" ; then
   echo "CONFIG_TCG_INTERPRETER=y" >> $config_host_mak
 fi
 
-if test "$spice_protocol" = "yes" ; then
-  echo "CONFIG_SPICE_PROTOCOL=y" >> $config_host_mak
-  echo "SPICE_PROTOCOL_CFLAGS=$spice_protocol_cflags" >> $config_host_mak
-fi
-if test "$spice" = "yes" ; then
-  echo "CONFIG_SPICE=y" >> $config_host_mak
-  echo "SPICE_CFLAGS=$spice_cflags $spice_protocol_cflags" >> $config_host_mak
-  echo "SPICE_LIBS=$spice_libs" >> $config_host_mak
-fi
-
 if test "$opengl" = "yes" ; then
   echo "CONFIG_OPENGL=y" >> $config_host_mak
   echo "OPENGL_CFLAGS=$opengl_cflags" >> $config_host_mak
@@ -5202,6 +5157,7 @@ if test "$skip_meson" = no; then
         -Ddocs=$docs -Dsphinx_build=$sphinx_build -Dinstall_blobs=$blobs \
         -Dvhost_user_blk_server=$vhost_user_blk_server -Dmultiprocess=$multiprocess \
         -Dfuse=$fuse -Dfuse_lseek=$fuse_lseek -Dguest_agent_msi=$guest_agent_msi -Dbpf=$bpf\
+        -Dspice=$spice -Dspice_protocol=$spice_protocol \
         $(if test "$default_feature" = no; then echo "-Dauto_features=disabled"; fi) \
 	-Dtcg_interpreter=$tcg_interpreter \
         $cross_arg \
diff --git a/meson.build b/meson.build
index 99a0a3e689..fe621413a4 100644
--- a/meson.build
+++ b/meson.build
@@ -442,17 +442,14 @@ jack = not_found
 if 'CONFIG_LIBJACK' in config_host
   jack = declare_dependency(link_args: config_host['JACK_LIBS'].split())
 endif
-spice = not_found
-spice_headers = not_found
-spice_protocol = not_found
-if 'CONFIG_SPICE' in config_host
-  spice = declare_dependency(compile_args: config_host['SPICE_CFLAGS'].split(),
-                             link_args: config_host['SPICE_LIBS'].split())
-  spice_headers = declare_dependency(compile_args: config_host['SPICE_CFLAGS'].split())
-endif
-if 'CONFIG_SPICE_PROTOCOL' in config_host
-  spice_protocol = declare_dependency(compile_args: config_host['SPICE_PROTOCOL_CFLAGS'].split())
-endif
+spice_protocol = dependency('spice-protocol', version: '>=0.12.3',
+                            required: get_option('spice_protocol'))
+spice = dependency('spice-server', version: '>=0.12.5',
+                   required: get_option('spice'))
+if spice.found()
+  config_host += { 'CONFIG_SPICE': 'y' } # for audio/meson.build
+endif
+spice_headers = spice.partial_dependency(compile_args: true, includes: true)
 rt = cc.find_library('rt', required: false)
 libdl = not_found
 if 'CONFIG_PLUGIN' in config_host
@@ -1293,6 +1290,9 @@ config_host_data.set('CONFIG_ZSTD', zstd.found())
 config_host_data.set('CONFIG_FUSE', fuse.found())
 config_host_data.set('CONFIG_FUSE_LSEEK', fuse_lseek.found())
 config_host_data.set('CONFIG_X11', x11.found())
+config_host_data.set('CONFIG_SPICE_PROTOCOL', spice_protocol.found())
+config_host_data.set('CONFIG_SPICE', spice.found())
+config_host_data.set('CONFIG_X11', x11.found())
 config_host_data.set('CONFIG_CFI', get_option('cfi'))
 config_host_data.set('QEMU_VERSION', '"@0@"'.format(meson.project_version()))
 config_host_data.set('QEMU_VERSION_MAJOR', meson.project_version().split('.')[0])
@@ -1516,7 +1516,7 @@ endif
 have_ivshmem = config_host_data.get('CONFIG_EVENTFD')
 host_kconfig = \
   ('CONFIG_TPM' in config_host ? ['CONFIG_TPM=y'] : []) + \
-  ('CONFIG_SPICE' in config_host ? ['CONFIG_SPICE=y'] : []) + \
+  (spice.found() ? ['CONFIG_SPICE=y'] : []) + \
   (have_ivshmem ? ['CONFIG_IVSHMEM=y'] : []) + \
   ('CONFIG_OPENGL' in config_host ? ['CONFIG_OPENGL=y'] : []) + \
   (x11.found() ? ['CONFIG_X11=y'] : []) + \
@@ -3082,8 +3082,7 @@ summary_info += {'PVRDMA support':    config_host.has_key('CONFIG_PVRDMA')}
 summary_info += {'fdt support':       fdt_opt == 'disabled' ? false : fdt_opt}
 summary_info += {'libcap-ng support': libcap_ng}
 summary_info += {'bpf support':       libbpf}
-# TODO: add back protocol and server version
-summary_info += {'spice support':     config_host.has_key('CONFIG_SPICE')}
+summary_info += {'spice support':     spice}
 summary_info += {'rbd support':       rbd}
 summary_info += {'xfsctl support':    config_host.has_key('CONFIG_XFS')}
 summary_info += {'smartcard support': cacard}
diff --git a/chardev/meson.build b/chardev/meson.build
index 32377af383..325ba2bdb9 100644
--- a/chardev/meson.build
+++ b/chardev/meson.build
@@ -35,7 +35,7 @@ if brlapi.found()
   chardev_modules += { 'baum': module_ss }
 endif
 
-if config_host.has_key('CONFIG_SPICE')
+if spice.found()
   module_ss = ss.source_set()
   module_ss.add(when: [spice], if_true: files('spice.c'))
   chardev_modules += { 'spice': module_ss }
diff --git a/meson_options.txt b/meson_options.txt
index 2c89e79e8b..b064b0b46c 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -114,6 +114,10 @@ option('smartcard', type : 'feature', value : 'auto',
        description: 'CA smartcard emulation support')
 option('snappy', type : 'feature', value : 'auto',
        description: 'snappy compression support')
+option('spice', type : 'feature', value : 'auto',
+       description: 'Spice support')
+option('spice_protocol', type : 'feature', value : 'auto',
+       description: 'Spice protocol support')
 option('u2f', type : 'feature', value : 'auto',
        description: 'U2F emulation support')
 option('usb_redir', type : 'feature', value : 'auto',
diff --git a/ui/meson.build b/ui/meson.build
index a73beb0e54..ee8ef27714 100644
--- a/ui/meson.build
+++ b/ui/meson.build
@@ -89,7 +89,7 @@ if sdl.found()
   ui_modules += {'sdl' : sdl_ss}
 endif
 
-if config_host.has_key('CONFIG_SPICE')
+if spice.found()
   spice_core_ss = ss.source_set()
   spice_core_ss.add(spice, pixman, files(
     'spice-core.c',
@@ -99,7 +99,7 @@ if config_host.has_key('CONFIG_SPICE')
   ui_modules += {'spice-core' : spice_core_ss}
 endif
 
-if config_host.has_key('CONFIG_SPICE') and config_host.has_key('CONFIG_GIO')
+if spice.found() and config_host.has_key('CONFIG_GIO')
   spice_ss = ss.source_set()
   spice_ss.add(spice, gio, pixman, files('spice-app.c'))
   ui_modules += {'spice-app': spice_ss}
-- 
2.33.0.721.g106298f7f9



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v2 02/37] ui/vdagent: add CHECK_SPICE_PROTOCOL_VERSION
  2021-10-09 21:08 [PATCH v2 00/37] Add D-Bus display backend marcandre.lureau
  2021-10-09 21:08 ` [PATCH v2 01/37] build-sys: move Spice configure handling to meson marcandre.lureau
@ 2021-10-09 21:08 ` marcandre.lureau
  2021-10-09 21:08 ` [PATCH v2 03/37] ui/vdagent: replace #if 0 with protocol version check marcandre.lureau
                   ` (35 subsequent siblings)
  37 siblings, 0 replies; 54+ messages in thread
From: marcandre.lureau @ 2021-10-09 21:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, kraxel

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 meson.build  | 5 +++++
 ui/vdagent.c | 8 ++++++++
 2 files changed, 13 insertions(+)

diff --git a/meson.build b/meson.build
index fe621413a4..e1cddf5139 100644
--- a/meson.build
+++ b/meson.build
@@ -1291,6 +1291,11 @@ config_host_data.set('CONFIG_FUSE', fuse.found())
 config_host_data.set('CONFIG_FUSE_LSEEK', fuse_lseek.found())
 config_host_data.set('CONFIG_X11', x11.found())
 config_host_data.set('CONFIG_SPICE_PROTOCOL', spice_protocol.found())
+if spice_protocol.found()
+config_host_data.set('CONFIG_SPICE_PROTOCOL_MAJOR', spice_protocol.version().split('.')[0])
+config_host_data.set('CONFIG_SPICE_PROTOCOL_MINOR', spice_protocol.version().split('.')[1])
+config_host_data.set('CONFIG_SPICE_PROTOCOL_MICRO', spice_protocol.version().split('.')[2])
+endif
 config_host_data.set('CONFIG_SPICE', spice.found())
 config_host_data.set('CONFIG_X11', x11.found())
 config_host_data.set('CONFIG_CFI', get_option('cfi'))
diff --git a/ui/vdagent.c b/ui/vdagent.c
index 19e8fbfc96..1f8fc77ee8 100644
--- a/ui/vdagent.c
+++ b/ui/vdagent.c
@@ -17,6 +17,14 @@
 
 #include "spice/vd_agent.h"
 
+#define CHECK_SPICE_PROTOCOL_VERSION(major, minor, micro) \
+    (CONFIG_SPICE_PROTOCOL_MAJOR > (major) ||             \
+     (CONFIG_SPICE_PROTOCOL_MAJOR == (major) &&           \
+      CONFIG_SPICE_PROTOCOL_MINOR > (minor)) ||           \
+     (CONFIG_SPICE_PROTOCOL_MAJOR == (major) &&           \
+      CONFIG_SPICE_PROTOCOL_MINOR == (minor) &&           \
+      CONFIG_SPICE_PROTOCOL_MICRO >= (micro)))
+
 #define VDAGENT_BUFFER_LIMIT (1 * MiB)
 #define VDAGENT_MOUSE_DEFAULT true
 #define VDAGENT_CLIPBOARD_DEFAULT false
-- 
2.33.0.721.g106298f7f9



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v2 03/37] ui/vdagent: replace #if 0 with protocol version check
  2021-10-09 21:08 [PATCH v2 00/37] Add D-Bus display backend marcandre.lureau
  2021-10-09 21:08 ` [PATCH v2 01/37] build-sys: move Spice configure handling to meson marcandre.lureau
  2021-10-09 21:08 ` [PATCH v2 02/37] ui/vdagent: add CHECK_SPICE_PROTOCOL_VERSION marcandre.lureau
@ 2021-10-09 21:08 ` marcandre.lureau
  2021-10-09 21:08 ` [PATCH v2 04/37] ui: generalize clipboard notifier marcandre.lureau
                   ` (34 subsequent siblings)
  37 siblings, 0 replies; 54+ messages in thread
From: marcandre.lureau @ 2021-10-09 21:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, kraxel

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 ui/vdagent.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/ui/vdagent.c b/ui/vdagent.c
index 1f8fc77ee8..64e0017001 100644
--- a/ui/vdagent.c
+++ b/ui/vdagent.c
@@ -87,8 +87,10 @@ static const char *cap_name[] = {
     [VD_AGENT_CAP_MONITORS_CONFIG_POSITION]       = "monitors-config-position",
     [VD_AGENT_CAP_FILE_XFER_DISABLED]             = "file-xfer-disabled",
     [VD_AGENT_CAP_FILE_XFER_DETAILED_ERRORS]      = "file-xfer-detailed-errors",
-#if 0
+#if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 0)
     [VD_AGENT_CAP_GRAPHICS_DEVICE_INFO]           = "graphics-device-info",
+#endif
+#if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 1)
     [VD_AGENT_CAP_CLIPBOARD_NO_RELEASE_ON_REGRAB] = "clipboard-no-release-on-regrab",
     [VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL]          = "clipboard-grab-serial",
 #endif
@@ -110,7 +112,7 @@ static const char *msg_name[] = {
     [VD_AGENT_CLIENT_DISCONNECTED]   = "client-disconnected",
     [VD_AGENT_MAX_CLIPBOARD]         = "max-clipboard",
     [VD_AGENT_AUDIO_VOLUME_SYNC]     = "audio-volume-sync",
-#if 0
+#if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 0)
     [VD_AGENT_GRAPHICS_DEVICE_INFO]  = "graphics-device-info",
 #endif
 };
@@ -128,7 +130,7 @@ static const char *type_name[] = {
     [VD_AGENT_CLIPBOARD_IMAGE_BMP]  = "bmp",
     [VD_AGENT_CLIPBOARD_IMAGE_TIFF] = "tiff",
     [VD_AGENT_CLIPBOARD_IMAGE_JPG]  = "jpg",
-#if 0
+#if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 3)
     [VD_AGENT_CLIPBOARD_FILE_LIST]  = "files",
 #endif
 };
-- 
2.33.0.721.g106298f7f9



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v2 04/37] ui: generalize clipboard notifier
  2021-10-09 21:08 [PATCH v2 00/37] Add D-Bus display backend marcandre.lureau
                   ` (2 preceding siblings ...)
  2021-10-09 21:08 ` [PATCH v2 03/37] ui/vdagent: replace #if 0 with protocol version check marcandre.lureau
@ 2021-10-09 21:08 ` marcandre.lureau
  2021-10-09 21:08 ` [PATCH v2 05/37] ui/vdagent: add serial capability support marcandre.lureau
                   ` (33 subsequent siblings)
  37 siblings, 0 replies; 54+ messages in thread
From: marcandre.lureau @ 2021-10-09 21:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, kraxel

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Use a QemuClipboardNotify union type for extendable clipboard events.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/ui/clipboard.h | 32 ++++++++++++++++++++++++++++++--
 ui/clipboard.c         | 12 ++++++++----
 ui/gtk-clipboard.c     | 20 ++++++++++++++++----
 ui/vdagent.c           | 27 ++++++++++++++++++---------
 ui/vnc-clipboard.c     | 18 ++++++++++++++----
 ui/vnc.c               |  2 +-
 ui/cocoa.m             | 17 +++++++++++++----
 7 files changed, 100 insertions(+), 28 deletions(-)

diff --git a/include/ui/clipboard.h b/include/ui/clipboard.h
index 6298986b15..d82cf31481 100644
--- a/include/ui/clipboard.h
+++ b/include/ui/clipboard.h
@@ -20,8 +20,10 @@
  */
 
 typedef enum QemuClipboardType QemuClipboardType;
+typedef enum QemuClipboardNotifyType QemuClipboardNotifyType;
 typedef enum QemuClipboardSelection QemuClipboardSelection;
 typedef struct QemuClipboardPeer QemuClipboardPeer;
+typedef struct QemuClipboardNotify QemuClipboardNotify;
 typedef struct QemuClipboardInfo QemuClipboardInfo;
 
 /**
@@ -55,18 +57,44 @@ enum QemuClipboardSelection {
  * struct QemuClipboardPeer
  *
  * @name: peer name.
- * @update: notifier for clipboard updates.
+ * @notifier: notifier for clipboard updates.
  * @request: callback for clipboard data requests.
  *
  * Clipboard peer description.
  */
 struct QemuClipboardPeer {
     const char *name;
-    Notifier update;
+    Notifier notifier;
     void (*request)(QemuClipboardInfo *info,
                     QemuClipboardType type);
 };
 
+/**
+ * enum QemuClipboardNotifyType
+ *
+ * @QEMU_CLIPBOARD_UPDATE_INFO: clipboard info update
+ *
+ * Clipboard notify type.
+ */
+enum QemuClipboardNotifyType {
+    QEMU_CLIPBOARD_UPDATE_INFO,
+};
+
+/**
+ * struct QemuClipboardNotify
+ *
+ * @type: the type of event.
+ * @info: a QemuClipboardInfo event.
+ *
+ * Clipboard notify data.
+ */
+struct QemuClipboardNotify {
+    QemuClipboardNotifyType type;
+    union {
+        QemuClipboardInfo *info;
+    };
+};
+
 /**
  * struct QemuClipboardInfo
  *
diff --git a/ui/clipboard.c b/ui/clipboard.c
index d7b008d62a..743b39edf4 100644
--- a/ui/clipboard.c
+++ b/ui/clipboard.c
@@ -8,7 +8,7 @@ static QemuClipboardInfo *cbinfo[QEMU_CLIPBOARD_SELECTION__COUNT];
 
 void qemu_clipboard_peer_register(QemuClipboardPeer *peer)
 {
-    notifier_list_add(&clipboard_notifiers, &peer->update);
+    notifier_list_add(&clipboard_notifiers, &peer->notifier);
 }
 
 void qemu_clipboard_peer_unregister(QemuClipboardPeer *peer)
@@ -18,8 +18,7 @@ void qemu_clipboard_peer_unregister(QemuClipboardPeer *peer)
     for (i = 0; i < QEMU_CLIPBOARD_SELECTION__COUNT; i++) {
         qemu_clipboard_peer_release(peer, i);
     }
-
-    notifier_remove(&peer->update);
+    notifier_remove(&peer->notifier);
 }
 
 bool qemu_clipboard_peer_owns(QemuClipboardPeer *peer,
@@ -44,10 +43,15 @@ void qemu_clipboard_peer_release(QemuClipboardPeer *peer,
 
 void qemu_clipboard_update(QemuClipboardInfo *info)
 {
+    QemuClipboardNotify notify = {
+        .type = QEMU_CLIPBOARD_UPDATE_INFO,
+        .info = info,
+    };
     g_autoptr(QemuClipboardInfo) old = NULL;
+
     assert(info->selection < QEMU_CLIPBOARD_SELECTION__COUNT);
 
-    notifier_list_notify(&clipboard_notifiers, info);
+    notifier_list_notify(&clipboard_notifiers, &notify);
 
     old = cbinfo[info->selection];
     cbinfo[info->selection] = qemu_clipboard_info_ref(info);
diff --git a/ui/gtk-clipboard.c b/ui/gtk-clipboard.c
index 35b7a2c228..44ff810234 100644
--- a/ui/gtk-clipboard.c
+++ b/ui/gtk-clipboard.c
@@ -74,10 +74,9 @@ static void gd_clipboard_clear(GtkClipboard *clipboard,
     gd->cbowner[s] = false;
 }
 
-static void gd_clipboard_notify(Notifier *notifier, void *data)
+static void gd_clipboard_update_info(GtkDisplayState *gd,
+                                     QemuClipboardInfo *info)
 {
-    GtkDisplayState *gd = container_of(notifier, GtkDisplayState, cbpeer.update);
-    QemuClipboardInfo *info = data;
     QemuClipboardSelection s = info->selection;
     bool self_update = info->owner == &gd->cbpeer;
 
@@ -118,6 +117,19 @@ static void gd_clipboard_notify(Notifier *notifier, void *data)
      */
 }
 
+static void gd_clipboard_notify(Notifier *notifier, void *data)
+{
+    GtkDisplayState *gd =
+        container_of(notifier, GtkDisplayState, cbpeer.notifier);
+    QemuClipboardNotify *notify = data;
+
+    switch (notify->type) {
+    case QEMU_CLIPBOARD_UPDATE_INFO:
+        gd_clipboard_update_info(gd, notify->info);
+        return;
+    }
+}
+
 static void gd_clipboard_request(QemuClipboardInfo *info,
                                  QemuClipboardType type)
 {
@@ -172,7 +184,7 @@ static void gd_owner_change(GtkClipboard *clipboard,
 void gd_clipboard_init(GtkDisplayState *gd)
 {
     gd->cbpeer.name = "gtk";
-    gd->cbpeer.update.notify = gd_clipboard_notify;
+    gd->cbpeer.notifier.notify = gd_clipboard_notify;
     gd->cbpeer.request = gd_clipboard_request;
     qemu_clipboard_peer_register(&gd->cbpeer);
 
diff --git a/ui/vdagent.c b/ui/vdagent.c
index 64e0017001..de827aad27 100644
--- a/ui/vdagent.c
+++ b/ui/vdagent.c
@@ -417,10 +417,9 @@ static void vdagent_send_empty_clipboard_data(VDAgentChardev *vd,
     vdagent_send_clipboard_data(vd, info, type);
 }
 
-static void vdagent_clipboard_notify(Notifier *notifier, void *data)
+static void vdagent_clipboard_update_info(VDAgentChardev *vd,
+                                          QemuClipboardInfo *info)
 {
-    VDAgentChardev *vd = container_of(notifier, VDAgentChardev, cbpeer.update);
-    QemuClipboardInfo *info = data;
     QemuClipboardSelection s = info->selection;
     QemuClipboardType type;
     bool self_update = info->owner == &vd->cbpeer;
@@ -449,6 +448,19 @@ static void vdagent_clipboard_notify(Notifier *notifier, void *data)
     }
 }
 
+static void vdagent_clipboard_notify(Notifier *notifier, void *data)
+{
+    VDAgentChardev *vd =
+        container_of(notifier, VDAgentChardev, cbpeer.notifier);
+    QemuClipboardNotify *notify = data;
+
+    switch (notify->type) {
+    case QEMU_CLIPBOARD_UPDATE_INFO:
+        vdagent_clipboard_update_info(vd, notify->info);
+        return;
+    }
+}
+
 static void vdagent_clipboard_request(QemuClipboardInfo *info,
                                       QemuClipboardType qtype)
 {
@@ -658,9 +670,9 @@ static void vdagent_chr_recv_caps(VDAgentChardev *vd, VDAgentMessage *msg)
     if (have_mouse(vd) && vd->mouse_hs) {
         qemu_input_handler_activate(vd->mouse_hs);
     }
-    if (have_clipboard(vd) && vd->cbpeer.update.notify == NULL) {
+    if (have_clipboard(vd) && vd->cbpeer.notifier.notify == NULL) {
         vd->cbpeer.name = "vdagent";
-        vd->cbpeer.update.notify = vdagent_clipboard_notify;
+        vd->cbpeer.notifier.notify = vdagent_clipboard_notify;
         vd->cbpeer.request = vdagent_clipboard_request;
         qemu_clipboard_peer_register(&vd->cbpeer);
     }
@@ -799,7 +811,7 @@ static void vdagent_disconnect(VDAgentChardev *vd)
     if (vd->mouse_hs) {
         qemu_input_handler_deactivate(vd->mouse_hs);
     }
-    if (vd->cbpeer.update.notify) {
+    if (vd->cbpeer.notifier.notify) {
         qemu_clipboard_peer_unregister(&vd->cbpeer);
         memset(&vd->cbpeer, 0, sizeof(vd->cbpeer));
     }
@@ -807,11 +819,8 @@ static void vdagent_disconnect(VDAgentChardev *vd)
 
 static void vdagent_chr_set_fe_open(struct Chardev *chr, int fe_open)
 {
-    VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(chr);
-
     if (!fe_open) {
         trace_vdagent_close();
-        vdagent_disconnect(vd);
         return;
     }
 
diff --git a/ui/vnc-clipboard.c b/ui/vnc-clipboard.c
index 9f077965d0..41aeff1085 100644
--- a/ui/vnc-clipboard.c
+++ b/ui/vnc-clipboard.c
@@ -189,10 +189,8 @@ static void vnc_clipboard_provide(VncState *vs,
     vnc_flush(vs);
 }
 
-static void vnc_clipboard_notify(Notifier *notifier, void *data)
+static void vnc_clipboard_update_info(VncState *vs, QemuClipboardInfo *info)
 {
-    VncState *vs = container_of(notifier, VncState, cbpeer.update);
-    QemuClipboardInfo *info = data;
     QemuClipboardType type;
     bool self_update = info->owner == &vs->cbpeer;
     uint32_t flags = 0;
@@ -223,6 +221,18 @@ static void vnc_clipboard_notify(Notifier *notifier, void *data)
     }
 }
 
+static void vnc_clipboard_notify(Notifier *notifier, void *data)
+{
+    VncState *vs = container_of(notifier, VncState, cbpeer.notifier);
+    QemuClipboardNotify *notify = data;
+
+    switch (notify->type) {
+    case QEMU_CLIPBOARD_UPDATE_INFO:
+        vnc_clipboard_update_info(vs, notify->info);
+        return;
+    }
+}
+
 static void vnc_clipboard_request(QemuClipboardInfo *info,
                                   QemuClipboardType type)
 {
@@ -317,7 +327,7 @@ void vnc_server_cut_text_caps(VncState *vs)
     vnc_clipboard_send(vs, 2, caps);
 
     vs->cbpeer.name = "vnc";
-    vs->cbpeer.update.notify = vnc_clipboard_notify;
+    vs->cbpeer.notifier.notify = vnc_clipboard_notify;
     vs->cbpeer.request = vnc_clipboard_request;
     qemu_clipboard_peer_register(&vs->cbpeer);
 }
diff --git a/ui/vnc.c b/ui/vnc.c
index af02522e84..9b603382e7 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
@@ -1354,7 +1354,7 @@ void vnc_disconnect_finish(VncState *vs)
         /* last client gone */
         vnc_update_server_surface(vs->vd);
     }
-    if (vs->cbpeer.update.notify) {
+    if (vs->cbpeer.notifier.notify) {
         qemu_clipboard_peer_unregister(&vs->cbpeer);
     }
 
diff --git a/ui/cocoa.m b/ui/cocoa.m
index 68a6302184..6745e3fc8c 100644
--- a/ui/cocoa.m
+++ b/ui/cocoa.m
@@ -1808,14 +1808,12 @@ static void cocoa_clipboard_request(QemuClipboardInfo *info,
 
 static QemuClipboardPeer cbpeer = {
     .name = "cocoa",
-    .update = { .notify = cocoa_clipboard_notify },
+    .notifier = { .notify = cocoa_clipboard_notify },
     .request = cocoa_clipboard_request
 };
 
-static void cocoa_clipboard_notify(Notifier *notifier, void *data)
+static void cocoa_clipboard_update_info(QemuClipboardInfo *info)
 {
-    QemuClipboardInfo *info = data;
-
     if (info->owner == &cbpeer || info->selection != QEMU_CLIPBOARD_SELECTION_CLIPBOARD) {
         return;
     }
@@ -1831,6 +1829,17 @@ static void cocoa_clipboard_notify(Notifier *notifier, void *data)
     qemu_event_set(&cbevent);
 }
 
+static void cocoa_clipboard_notify(Notifier *notifier, void *data)
+{
+    QemuClipboardNotify *notify = data;
+
+    switch (notify->event) {
+    case QEMU_CLIPBOARD_UPDATE_INFO:
+        cocoa_clipboard_update_info(notify->info);
+        return;
+    }
+}
+
 static void cocoa_clipboard_request(QemuClipboardInfo *info,
                                     QemuClipboardType type)
 {
-- 
2.33.0.721.g106298f7f9



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v2 05/37] ui/vdagent: add serial capability support
  2021-10-09 21:08 [PATCH v2 00/37] Add D-Bus display backend marcandre.lureau
                   ` (3 preceding siblings ...)
  2021-10-09 21:08 ` [PATCH v2 04/37] ui: generalize clipboard notifier marcandre.lureau
@ 2021-10-09 21:08 ` marcandre.lureau
  2021-10-09 21:08 ` [PATCH v2 06/37] ui/clipboard: add qemu_clipboard_check_serial() marcandre.lureau
                   ` (32 subsequent siblings)
  37 siblings, 0 replies; 54+ messages in thread
From: marcandre.lureau @ 2021-10-09 21:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, kraxel

From: Marc-André Lureau <marcandre.lureau@redhat.com>

The Spice agent implements a simple serial mechanism to avoid clipboard
races between client & guest. See:
https://gitlab.freedesktop.org/spice/spice-protocol/-/commit/045a6978d6dbbf7046affc5c321fa8177c8cce56

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/ui/clipboard.h |  4 ++++
 ui/vdagent.c           | 39 ++++++++++++++++++++++++++++++++++++++-
 ui/trace-events        |  1 +
 3 files changed, 43 insertions(+), 1 deletion(-)

diff --git a/include/ui/clipboard.h b/include/ui/clipboard.h
index d82cf31481..e590b453c8 100644
--- a/include/ui/clipboard.h
+++ b/include/ui/clipboard.h
@@ -102,6 +102,8 @@ struct QemuClipboardNotify {
  * @owner: clipboard owner.
  * @selection: clipboard selection.
  * @types: clipboard data array (one entry per type).
+ * @has_serial: whether @serial is available.
+ * @serial: the grab serial counter.
  *
  * Clipboard content data and metadata.
  */
@@ -109,6 +111,8 @@ struct QemuClipboardInfo {
     uint32_t refcount;
     QemuClipboardPeer *owner;
     QemuClipboardSelection selection;
+    bool has_serial;
+    uint32_t serial;
     struct {
         bool available;
         bool requested;
diff --git a/ui/vdagent.c b/ui/vdagent.c
index de827aad27..b4fdae6917 100644
--- a/ui/vdagent.c
+++ b/ui/vdagent.c
@@ -59,6 +59,7 @@ struct VDAgentChardev {
 
     /* clipboard */
     QemuClipboardPeer cbpeer;
+    uint32_t last_serial[QEMU_CLIPBOARD_SELECTION__COUNT];
     uint32_t cbpending[QEMU_CLIPBOARD_SELECTION__COUNT];
 };
 typedef struct VDAgentChardev VDAgentChardev;
@@ -203,6 +204,9 @@ static void vdagent_send_caps(VDAgentChardev *vd)
     if (vd->clipboard) {
         caps->caps[0] |= (1 << VD_AGENT_CAP_CLIPBOARD_BY_DEMAND);
         caps->caps[0] |= (1 << VD_AGENT_CAP_CLIPBOARD_SELECTION);
+#if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 1)
+        caps->caps[0] |= (1 << VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL);
+#endif
     }
 
     vdagent_send_msg(vd, msg);
@@ -333,7 +337,8 @@ static void vdagent_send_clipboard_grab(VDAgentChardev *vd,
 {
     g_autofree VDAgentMessage *msg =
         g_malloc0(sizeof(VDAgentMessage) +
-                  sizeof(uint32_t) * (QEMU_CLIPBOARD_TYPE__COUNT + 1));
+                  sizeof(uint32_t) * (QEMU_CLIPBOARD_TYPE__COUNT + 1) +
+                  sizeof(uint32_t));
     uint8_t *s = msg->data;
     uint32_t *data = (uint32_t *)msg->data;
     uint32_t q, type;
@@ -346,6 +351,19 @@ static void vdagent_send_clipboard_grab(VDAgentChardev *vd,
         return;
     }
 
+#if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 1)
+    if (vd->caps & (1 << VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL)) {
+        if (!info->has_serial) {
+            /* client should win */
+            info->serial = vd->last_serial[info->selection]++;
+            info->has_serial = true;
+        }
+        *data = info->serial;
+        data++;
+        msg->size += sizeof(uint32_t);
+    }
+#endif
+
     for (q = 0; q < QEMU_CLIPBOARD_TYPE__COUNT; q++) {
         type = type_qemu_to_vdagent(q);
         if (type != VD_AGENT_CLIPBOARD_NONE && info->types[q].available) {
@@ -494,6 +512,24 @@ static void vdagent_clipboard_recv_grab(VDAgentChardev *vd, uint8_t s, uint32_t
 
     trace_vdagent_cb_grab_selection(GET_NAME(sel_name, s));
     info = qemu_clipboard_info_new(&vd->cbpeer, s);
+#if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 1)
+    if (vd->caps & (1 << VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL)) {
+        if (size < sizeof(uint32_t)) {
+            /* this shouldn't happen! */
+            return;
+        }
+
+        info->has_serial = true;
+        info->serial = *(uint32_t *)data;
+        if (info->serial < vd->last_serial[s]) {
+            /* discard lower-ordering guest grab */
+            return;
+        }
+        vd->last_serial[s] = info->serial;
+        data += sizeof(uint32_t);
+        size -= sizeof(uint32_t);
+    }
+#endif
     if (size > sizeof(uint32_t) * 10) {
         /*
          * spice has 6 types as of 2021. Limiting to 10 entries
@@ -671,6 +707,7 @@ static void vdagent_chr_recv_caps(VDAgentChardev *vd, VDAgentMessage *msg)
         qemu_input_handler_activate(vd->mouse_hs);
     }
     if (have_clipboard(vd) && vd->cbpeer.notifier.notify == NULL) {
+        memset(vd->last_serial, 0, sizeof(vd->last_serial));
         vd->cbpeer.name = "vdagent";
         vd->cbpeer.notifier.notify = vdagent_clipboard_notify;
         vd->cbpeer.request = vdagent_clipboard_request;
diff --git a/ui/trace-events b/ui/trace-events
index b9c0dd0fa1..e832c3e365 100644
--- a/ui/trace-events
+++ b/ui/trace-events
@@ -135,3 +135,4 @@ vdagent_recv_msg(const char *name, uint32_t size) "msg %s, size %d"
 vdagent_peer_cap(const char *name) "cap %s"
 vdagent_cb_grab_selection(const char *name) "selection %s"
 vdagent_cb_grab_type(const char *name) "type %s"
+vdagent_cb_serial_discard(uint32_t current, uint32_t received) "current=%u, received=%u"
-- 
2.33.0.721.g106298f7f9



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v2 06/37] ui/clipboard: add qemu_clipboard_check_serial()
  2021-10-09 21:08 [PATCH v2 00/37] Add D-Bus display backend marcandre.lureau
                   ` (4 preceding siblings ...)
  2021-10-09 21:08 ` [PATCH v2 05/37] ui/vdagent: add serial capability support marcandre.lureau
@ 2021-10-09 21:08 ` marcandre.lureau
  2021-10-09 21:08 ` [PATCH v2 07/37] ui/clipboard: add a clipboard reset serial event marcandre.lureau
                   ` (31 subsequent siblings)
  37 siblings, 0 replies; 54+ messages in thread
From: marcandre.lureau @ 2021-10-09 21:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, kraxel

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/ui/clipboard.h | 10 ++++++++++
 ui/clipboard.c         | 15 +++++++++++++++
 2 files changed, 25 insertions(+)

diff --git a/include/ui/clipboard.h b/include/ui/clipboard.h
index e590b453c8..2c6488c1ee 100644
--- a/include/ui/clipboard.h
+++ b/include/ui/clipboard.h
@@ -172,6 +172,16 @@ void qemu_clipboard_peer_release(QemuClipboardPeer *peer,
  */
 QemuClipboardInfo *qemu_clipboard_info(QemuClipboardSelection selection);
 
+/**
+ * qemu_clipboard_check_serial
+ *
+ * @info: clipboard info.
+ * @client: whether to check from the client context and priority.
+ *
+ * Return TRUE if the @info has a higher serial than the current clipboard.
+ */
+bool qemu_clipboard_check_serial(QemuClipboardInfo *info, bool client);
+
 /**
  * qemu_clipboard_info_new
  *
diff --git a/ui/clipboard.c b/ui/clipboard.c
index 743b39edf4..ffbd80e5c6 100644
--- a/ui/clipboard.c
+++ b/ui/clipboard.c
@@ -41,6 +41,21 @@ void qemu_clipboard_peer_release(QemuClipboardPeer *peer,
     }
 }
 
+bool qemu_clipboard_check_serial(QemuClipboardInfo *info, bool client)
+{
+    if (!info->has_serial ||
+        !cbinfo[info->selection] ||
+        !cbinfo[info->selection]->has_serial) {
+        return true;
+    }
+
+    if (client) {
+        return cbinfo[info->selection]->serial >= info->serial;
+    } else {
+        return cbinfo[info->selection]->serial > info->serial;
+    }
+}
+
 void qemu_clipboard_update(QemuClipboardInfo *info)
 {
     QemuClipboardNotify notify = {
-- 
2.33.0.721.g106298f7f9



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v2 07/37] ui/clipboard: add a clipboard reset serial event
  2021-10-09 21:08 [PATCH v2 00/37] Add D-Bus display backend marcandre.lureau
                   ` (5 preceding siblings ...)
  2021-10-09 21:08 ` [PATCH v2 06/37] ui/clipboard: add qemu_clipboard_check_serial() marcandre.lureau
@ 2021-10-09 21:08 ` marcandre.lureau
  2021-10-09 21:08 ` [PATCH v2 08/37] hw/display: report an error if virgl initialization failed marcandre.lureau
                   ` (30 subsequent siblings)
  37 siblings, 0 replies; 54+ messages in thread
From: marcandre.lureau @ 2021-10-09 21:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, kraxel

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/ui/clipboard.h |  9 +++++++++
 ui/clipboard.c         |  7 +++++++
 ui/gtk-clipboard.c     |  3 +++
 ui/vdagent.c           | 12 ++++++++++++
 ui/vnc-clipboard.c     |  3 +++
 ui/cocoa.m             |  3 +++
 6 files changed, 37 insertions(+)

diff --git a/include/ui/clipboard.h b/include/ui/clipboard.h
index 2c6488c1ee..ce76aa451f 100644
--- a/include/ui/clipboard.h
+++ b/include/ui/clipboard.h
@@ -73,11 +73,13 @@ struct QemuClipboardPeer {
  * enum QemuClipboardNotifyType
  *
  * @QEMU_CLIPBOARD_UPDATE_INFO: clipboard info update
+ * @QEMU_CLIPBOARD_RESET_SERIAL: reset clipboard serial
  *
  * Clipboard notify type.
  */
 enum QemuClipboardNotifyType {
     QEMU_CLIPBOARD_UPDATE_INFO,
+    QEMU_CLIPBOARD_RESET_SERIAL,
 };
 
 /**
@@ -230,6 +232,13 @@ void qemu_clipboard_info_unref(QemuClipboardInfo *info);
  */
 void qemu_clipboard_update(QemuClipboardInfo *info);
 
+/**
+ * qemu_clipboard_reset_serial
+ *
+ * Reset the clipboard serial.
+ */
+void qemu_clipboard_reset_serial(void);
+
 /**
  * qemu_clipboard_request
  *
diff --git a/ui/clipboard.c b/ui/clipboard.c
index ffbd80e5c6..82572ea116 100644
--- a/ui/clipboard.c
+++ b/ui/clipboard.c
@@ -129,6 +129,13 @@ void qemu_clipboard_request(QemuClipboardInfo *info,
     info->owner->request(info, type);
 }
 
+void qemu_clipboard_reset_serial(void)
+{
+    QemuClipboardNotify notify = { .type = QEMU_CLIPBOARD_RESET_SERIAL };
+
+    notifier_list_notify(&clipboard_notifiers, &notify);
+}
+
 void qemu_clipboard_set_data(QemuClipboardPeer *peer,
                              QemuClipboardInfo *info,
                              QemuClipboardType type,
diff --git a/ui/gtk-clipboard.c b/ui/gtk-clipboard.c
index 44ff810234..e0b8b283fe 100644
--- a/ui/gtk-clipboard.c
+++ b/ui/gtk-clipboard.c
@@ -127,6 +127,9 @@ static void gd_clipboard_notify(Notifier *notifier, void *data)
     case QEMU_CLIPBOARD_UPDATE_INFO:
         gd_clipboard_update_info(gd, notify->info);
         return;
+    case QEMU_CLIPBOARD_RESET_SERIAL:
+        /* ignore */
+        return;
     }
 }
 
diff --git a/ui/vdagent.c b/ui/vdagent.c
index b4fdae6917..7ea4bc5d9a 100644
--- a/ui/vdagent.c
+++ b/ui/vdagent.c
@@ -466,6 +466,15 @@ static void vdagent_clipboard_update_info(VDAgentChardev *vd,
     }
 }
 
+static void vdagent_clipboard_reset_serial(VDAgentChardev *vd)
+{
+    Chardev *chr = CHARDEV(vd);
+
+    /* reopen the agent connection to reset the serial state */
+    qemu_chr_be_event(chr, CHR_EVENT_CLOSED);
+    qemu_chr_be_event(chr, CHR_EVENT_OPENED);
+}
+
 static void vdagent_clipboard_notify(Notifier *notifier, void *data)
 {
     VDAgentChardev *vd =
@@ -476,6 +485,9 @@ static void vdagent_clipboard_notify(Notifier *notifier, void *data)
     case QEMU_CLIPBOARD_UPDATE_INFO:
         vdagent_clipboard_update_info(vd, notify->info);
         return;
+    case QEMU_CLIPBOARD_RESET_SERIAL:
+        vdagent_clipboard_reset_serial(vd);
+        return;
     }
 }
 
diff --git a/ui/vnc-clipboard.c b/ui/vnc-clipboard.c
index 41aeff1085..8b2dd0a64a 100644
--- a/ui/vnc-clipboard.c
+++ b/ui/vnc-clipboard.c
@@ -230,6 +230,9 @@ static void vnc_clipboard_notify(Notifier *notifier, void *data)
     case QEMU_CLIPBOARD_UPDATE_INFO:
         vnc_clipboard_update_info(vs, notify->info);
         return;
+    case QEMU_CLIPBOARD_RESET_SERIAL:
+        /* ignore */
+        return;
     }
 }
 
diff --git a/ui/cocoa.m b/ui/cocoa.m
index 6745e3fc8c..69b6c07d53 100644
--- a/ui/cocoa.m
+++ b/ui/cocoa.m
@@ -1837,6 +1837,9 @@ static void cocoa_clipboard_notify(Notifier *notifier, void *data)
     case QEMU_CLIPBOARD_UPDATE_INFO:
         cocoa_clipboard_update_info(notify->info);
         return;
+    case QEMU_CLIPBOARD_RESET_SERIAL:
+        /* ignore */
+        return;
     }
 }
 
-- 
2.33.0.721.g106298f7f9



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v2 08/37] hw/display: report an error if virgl initialization failed
  2021-10-09 21:08 [PATCH v2 00/37] Add D-Bus display backend marcandre.lureau
                   ` (6 preceding siblings ...)
  2021-10-09 21:08 ` [PATCH v2 07/37] ui/clipboard: add a clipboard reset serial event marcandre.lureau
@ 2021-10-09 21:08 ` marcandre.lureau
  2021-12-17 12:40   ` Philippe Mathieu-Daudé
  2021-10-09 21:08 ` [PATCH v2 09/37] virtio-gpu: use VIRTIO_GPU_RESOURCE_FLAG_Y_0_TOP marcandre.lureau
                   ` (29 subsequent siblings)
  37 siblings, 1 reply; 54+ messages in thread
From: marcandre.lureau @ 2021-10-09 21:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, kraxel

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Currently, virgl initialization error is silent. Make it verbose instead.

(this is likely going to bug later on, as the device isn't fully
initialized)

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 hw/display/virtio-gpu-virgl.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/hw/display/virtio-gpu-virgl.c b/hw/display/virtio-gpu-virgl.c
index 18d054922f..0d87de65d7 100644
--- a/hw/display/virtio-gpu-virgl.c
+++ b/hw/display/virtio-gpu-virgl.c
@@ -609,6 +609,7 @@ int virtio_gpu_virgl_init(VirtIOGPU *g)
 
     ret = virgl_renderer_init(g, 0, &virtio_gpu_3d_cbs);
     if (ret != 0) {
+        error_report("virgl could not be initialized: %d", ret);
         return ret;
     }
 
-- 
2.33.0.721.g106298f7f9



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v2 09/37] virtio-gpu: use VIRTIO_GPU_RESOURCE_FLAG_Y_0_TOP
  2021-10-09 21:08 [PATCH v2 00/37] Add D-Bus display backend marcandre.lureau
                   ` (7 preceding siblings ...)
  2021-10-09 21:08 ` [PATCH v2 08/37] hw/display: report an error if virgl initialization failed marcandre.lureau
@ 2021-10-09 21:08 ` marcandre.lureau
  2021-12-17 12:51   ` Philippe Mathieu-Daudé
  2021-10-09 21:08 ` [PATCH v2 10/37] ui: do not delay further remote resize marcandre.lureau
                   ` (28 subsequent siblings)
  37 siblings, 1 reply; 54+ messages in thread
From: marcandre.lureau @ 2021-10-09 21:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, kraxel

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 hw/display/virtio-gpu-virgl.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/hw/display/virtio-gpu-virgl.c b/hw/display/virtio-gpu-virgl.c
index 0d87de65d7..73cb92c8d5 100644
--- a/hw/display/virtio-gpu-virgl.c
+++ b/hw/display/virtio-gpu-virgl.c
@@ -175,7 +175,7 @@ static void virgl_cmd_set_scanout(VirtIOGPU *g,
         virgl_renderer_force_ctx_0();
         dpy_gl_scanout_texture(
             g->parent_obj.scanout[ss.scanout_id].con, info.tex_id,
-            info.flags & 1 /* FIXME: Y_0_TOP */,
+            info.flags & VIRTIO_GPU_RESOURCE_FLAG_Y_0_TOP,
             info.width, info.height,
             ss.r.x, ss.r.y, ss.r.width, ss.r.height);
     } else {
-- 
2.33.0.721.g106298f7f9



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v2 10/37] ui: do not delay further remote resize
  2021-10-09 21:08 [PATCH v2 00/37] Add D-Bus display backend marcandre.lureau
                   ` (8 preceding siblings ...)
  2021-10-09 21:08 ` [PATCH v2 09/37] virtio-gpu: use VIRTIO_GPU_RESOURCE_FLAG_Y_0_TOP marcandre.lureau
@ 2021-10-09 21:08 ` marcandre.lureau
  2021-10-09 21:08 ` [PATCH v2 11/37] ui: factor out qemu_console_set_display_gl_ctx() marcandre.lureau
                   ` (27 subsequent siblings)
  37 siblings, 0 replies; 54+ messages in thread
From: marcandre.lureau @ 2021-10-09 21:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, kraxel

From: Marc-André Lureau <marcandre.lureau@redhat.com>

A remote client, such as Spice, will already avoid flooding the stream
by delaying the resize requests.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/ui/console.h | 2 +-
 ui/console.c         | 5 +++--
 ui/gtk.c             | 2 +-
 ui/sdl2.c            | 2 +-
 ui/spice-display.c   | 2 +-
 ui/vnc.c             | 2 +-
 6 files changed, 8 insertions(+), 7 deletions(-)

diff --git a/include/ui/console.h b/include/ui/console.h
index 244664d727..7b2f624e93 100644
--- a/include/ui/console.h
+++ b/include/ui/console.h
@@ -287,7 +287,7 @@ void unregister_displaychangelistener(DisplayChangeListener *dcl);
 
 bool dpy_ui_info_supported(QemuConsole *con);
 const QemuUIInfo *dpy_get_ui_info(const QemuConsole *con);
-int dpy_set_ui_info(QemuConsole *con, QemuUIInfo *info);
+int dpy_set_ui_info(QemuConsole *con, QemuUIInfo *info, bool delay);
 
 void dpy_gfx_update(QemuConsole *con, int x, int y, int w, int h);
 void dpy_gfx_update_full(QemuConsole *con);
diff --git a/ui/console.c b/ui/console.c
index eabbbc951c..7eea1fc641 100644
--- a/ui/console.c
+++ b/ui/console.c
@@ -1596,7 +1596,7 @@ const QemuUIInfo *dpy_get_ui_info(const QemuConsole *con)
     return &con->ui_info;
 }
 
-int dpy_set_ui_info(QemuConsole *con, QemuUIInfo *info)
+int dpy_set_ui_info(QemuConsole *con, QemuUIInfo *info, bool delay)
 {
     if (con == NULL) {
         con = active_console;
@@ -1616,7 +1616,8 @@ int dpy_set_ui_info(QemuConsole *con, QemuUIInfo *info)
      * go notify the guest.
      */
     con->ui_info = *info;
-    timer_mod(con->ui_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 1000);
+    timer_mod(con->ui_timer,
+              qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + (delay ? 1000 : 0));
     return 0;
 }
 
diff --git a/ui/gtk.c b/ui/gtk.c
index b0564d80c1..163b6bcb6a 100644
--- a/ui/gtk.c
+++ b/ui/gtk.c
@@ -698,7 +698,7 @@ static void gd_set_ui_info(VirtualConsole *vc, gint width, gint height)
     memset(&info, 0, sizeof(info));
     info.width = width;
     info.height = height;
-    dpy_set_ui_info(vc->gfx.dcl.con, &info);
+    dpy_set_ui_info(vc->gfx.dcl.con, &info, true);
 }
 
 #if defined(CONFIG_OPENGL)
diff --git a/ui/sdl2.c b/ui/sdl2.c
index 17c0ec30eb..9ba3bc49e7 100644
--- a/ui/sdl2.c
+++ b/ui/sdl2.c
@@ -561,7 +561,7 @@ static void handle_windowevent(SDL_Event *ev)
             memset(&info, 0, sizeof(info));
             info.width = ev->window.data1;
             info.height = ev->window.data2;
-            dpy_set_ui_info(scon->dcl.con, &info);
+            dpy_set_ui_info(scon->dcl.con, &info, true);
         }
         sdl2_redraw(scon);
         break;
diff --git a/ui/spice-display.c b/ui/spice-display.c
index f59c69882d..52d9f3260a 100644
--- a/ui/spice-display.c
+++ b/ui/spice-display.c
@@ -692,7 +692,7 @@ static int interface_client_monitors_config(QXLInstance *sin,
     }
 
     trace_qemu_spice_ui_info(ssd->qxl.id, info.width, info.height);
-    dpy_set_ui_info(ssd->dcl.con, &info);
+    dpy_set_ui_info(ssd->dcl.con, &info, false);
     return 1;
 }
 
diff --git a/ui/vnc.c b/ui/vnc.c
index 9b603382e7..1ed1c7efc6 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
@@ -2596,7 +2596,7 @@ static int protocol_client_msg(VncState *vs, uint8_t *data, size_t len)
             memset(&info, 0, sizeof(info));
             info.width = w;
             info.height = h;
-            dpy_set_ui_info(vs->vd->dcl.con, &info);
+            dpy_set_ui_info(vs->vd->dcl.con, &info, false);
             vnc_desktop_resize_ext(vs, 4 /* Request forwarded */);
         } else {
             vnc_desktop_resize_ext(vs, 3 /* Invalid screen layout */);
-- 
2.33.0.721.g106298f7f9



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v2 11/37] ui: factor out qemu_console_set_display_gl_ctx()
  2021-10-09 21:08 [PATCH v2 00/37] Add D-Bus display backend marcandre.lureau
                   ` (9 preceding siblings ...)
  2021-10-09 21:08 ` [PATCH v2 10/37] ui: do not delay further remote resize marcandre.lureau
@ 2021-10-09 21:08 ` marcandre.lureau
  2021-12-17 13:36   ` Philippe Mathieu-Daudé
  2021-10-09 21:08 ` [PATCH v2 12/37] ui: associate GL context outside of display listener registration marcandre.lureau
                   ` (26 subsequent siblings)
  37 siblings, 1 reply; 54+ messages in thread
From: marcandre.lureau @ 2021-10-09 21:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, kraxel

From: Marc-André Lureau <marcandre.lureau@redhat.com>

The next patch will make use of this function to dissociate
DisplayChangeListener from GL context.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/ui/console.h |  3 +++
 ui/console.c         | 22 ++++++++++++++--------
 2 files changed, 17 insertions(+), 8 deletions(-)

diff --git a/include/ui/console.h b/include/ui/console.h
index 7b2f624e93..1617b4c59a 100644
--- a/include/ui/console.h
+++ b/include/ui/console.h
@@ -406,6 +406,9 @@ void graphic_hw_gl_flushed(QemuConsole *con);
 
 void qemu_console_early_init(void);
 
+void qemu_console_set_display_gl_ctx(QemuConsole *con,
+                                     DisplayChangeListener *dcl);
+
 QemuConsole *qemu_console_lookup_by_index(unsigned int index);
 QemuConsole *qemu_console_lookup_by_device(DeviceState *dev, uint32_t head);
 QemuConsole *qemu_console_lookup_by_device_name(const char *device_id,
diff --git a/ui/console.c b/ui/console.c
index 7eea1fc641..64f35e163c 100644
--- a/ui/console.c
+++ b/ui/console.c
@@ -1501,6 +1501,19 @@ static bool dpy_compatible_with(QemuConsole *con,
     return true;
 }
 
+void qemu_console_set_display_gl_ctx(QemuConsole *con,
+                                     DisplayChangeListener *dcl)
+{
+    /* display has opengl support */
+    assert(dcl->con);
+    if (dcl->con->gl) {
+        fprintf(stderr, "can't register two opengl displays (%s, %s)\n",
+                dcl->ops->dpy_name, dcl->con->gl->ops->dpy_name);
+        exit(1);
+    }
+    dcl->con->gl = dcl;
+}
+
 void register_displaychangelistener(DisplayChangeListener *dcl)
 {
     static const char nodev[] =
@@ -1511,14 +1524,7 @@ void register_displaychangelistener(DisplayChangeListener *dcl)
     assert(!dcl->ds);
 
     if (dcl->ops->dpy_gl_ctx_create) {
-        /* display has opengl support */
-        assert(dcl->con);
-        if (dcl->con->gl) {
-            fprintf(stderr, "can't register two opengl displays (%s, %s)\n",
-                    dcl->ops->dpy_name, dcl->con->gl->ops->dpy_name);
-            exit(1);
-        }
-        dcl->con->gl = dcl;
+        qemu_console_set_display_gl_ctx(dcl->con, dcl);
     }
 
     if (dcl->con) {
-- 
2.33.0.721.g106298f7f9



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v2 12/37] ui: associate GL context outside of display listener registration
  2021-10-09 21:08 [PATCH v2 00/37] Add D-Bus display backend marcandre.lureau
                   ` (10 preceding siblings ...)
  2021-10-09 21:08 ` [PATCH v2 11/37] ui: factor out qemu_console_set_display_gl_ctx() marcandre.lureau
@ 2021-10-09 21:08 ` marcandre.lureau
  2021-10-09 21:08 ` [PATCH v2 13/37] ui: make gl_block use a counter marcandre.lureau
                   ` (25 subsequent siblings)
  37 siblings, 0 replies; 54+ messages in thread
From: marcandre.lureau @ 2021-10-09 21:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, kraxel

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 ui/console.c       | 7 +++++--
 ui/egl-headless.c  | 1 +
 ui/gtk.c           | 3 +++
 ui/sdl2.c          | 3 +++
 ui/spice-display.c | 3 +++
 5 files changed, 15 insertions(+), 2 deletions(-)

diff --git a/ui/console.c b/ui/console.c
index 64f35e163c..3c4012271c 100644
--- a/ui/console.c
+++ b/ui/console.c
@@ -1523,8 +1523,11 @@ void register_displaychangelistener(DisplayChangeListener *dcl)
 
     assert(!dcl->ds);
 
-    if (dcl->ops->dpy_gl_ctx_create) {
-        qemu_console_set_display_gl_ctx(dcl->con, dcl);
+    if (dcl->con && dcl->con->gl &&
+        dcl->con->gl != dcl) {
+        error_report("Display %s is incompatible with the GL context",
+                     dcl->ops->dpy_name);
+        exit(1);
     }
 
     if (dcl->con) {
diff --git a/ui/egl-headless.c b/ui/egl-headless.c
index a26a2520c4..08327c40c6 100644
--- a/ui/egl-headless.c
+++ b/ui/egl-headless.c
@@ -197,6 +197,7 @@ static void egl_headless_init(DisplayState *ds, DisplayOptions *opts)
         edpy->dcl.con = con;
         edpy->dcl.ops = &egl_ops;
         edpy->gls = qemu_gl_init_shader();
+        qemu_console_set_display_gl_ctx(con, &edpy->dcl);
         register_displaychangelistener(&edpy->dcl);
     }
 }
diff --git a/ui/gtk.c b/ui/gtk.c
index 163b6bcb6a..f8beddbb91 100644
--- a/ui/gtk.c
+++ b/ui/gtk.c
@@ -2057,6 +2057,9 @@ static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc,
     vc->gfx.kbd = qkbd_state_init(con);
     vc->gfx.dcl.con = con;
 
+    if (display_opengl) {
+        qemu_console_set_display_gl_ctx(con, &vc->gfx.dcl);
+    }
     register_displaychangelistener(&vc->gfx.dcl);
 
     gd_connect_vc_gfx_signals(vc);
diff --git a/ui/sdl2.c b/ui/sdl2.c
index 9ba3bc49e7..bb186a381a 100644
--- a/ui/sdl2.c
+++ b/ui/sdl2.c
@@ -866,6 +866,9 @@ static void sdl2_display_init(DisplayState *ds, DisplayOptions *o)
 #endif
         sdl2_console[i].dcl.con = con;
         sdl2_console[i].kbd = qkbd_state_init(con);
+        if (display_opengl) {
+            qemu_console_set_display_gl_ctx(con, &sdl2_console[i].dcl);
+        }
         register_displaychangelistener(&sdl2_console[i].dcl);
 
 #if defined(SDL_VIDEO_DRIVER_WINDOWS) || defined(SDL_VIDEO_DRIVER_X11)
diff --git a/ui/spice-display.c b/ui/spice-display.c
index 52d9f3260a..2c204bceee 100644
--- a/ui/spice-display.c
+++ b/ui/spice-display.c
@@ -1156,6 +1156,9 @@ static void qemu_spice_display_init_one(QemuConsole *con)
 
     qemu_spice_create_host_memslot(ssd);
 
+    if (spice_opengl) {
+        qemu_console_set_display_gl_ctx(con, &ssd->dcl);
+    }
     register_displaychangelistener(&ssd->dcl);
 }
 
-- 
2.33.0.721.g106298f7f9



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v2 13/37] ui: make gl_block use a counter
  2021-10-09 21:08 [PATCH v2 00/37] Add D-Bus display backend marcandre.lureau
                   ` (11 preceding siblings ...)
  2021-10-09 21:08 ` [PATCH v2 12/37] ui: associate GL context outside of display listener registration marcandre.lureau
@ 2021-10-09 21:08 ` marcandre.lureau
  2021-10-09 21:08 ` [PATCH v2 14/37] ui: add a gl-unblock warning timer marcandre.lureau
                   ` (24 subsequent siblings)
  37 siblings, 0 replies; 54+ messages in thread
From: marcandre.lureau @ 2021-10-09 21:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, kraxel

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Track multiple callers blocking requests.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 ui/console.c | 17 +++++++++++++----
 1 file changed, 13 insertions(+), 4 deletions(-)

diff --git a/ui/console.c b/ui/console.c
index 3c4012271c..8d641b9530 100644
--- a/ui/console.c
+++ b/ui/console.c
@@ -128,7 +128,7 @@ struct QemuConsole {
     DisplaySurface *surface;
     int dcls;
     DisplayChangeListener *gl;
-    bool gl_block;
+    int gl_block;
     int window_id;
 
     /* Graphic console state.  */
@@ -288,10 +288,19 @@ void graphic_hw_gl_block(QemuConsole *con, bool block)
 {
     assert(con != NULL);
 
-    con->gl_block = block;
-    if (con->hw_ops->gl_block) {
-        con->hw_ops->gl_block(con->hw, block);
+    if (block) {
+        con->gl_block++;
+    } else {
+        con->gl_block--;
+    }
+    assert(con->gl_block >= 0);
+    if (!con->hw_ops->gl_block) {
+        return;
+    }
+    if ((block && con->gl_block != 1) || (!block && con->gl_block != 0)) {
+        return;
     }
+    con->hw_ops->gl_block(con->hw, block);
 }
 
 void graphic_hw_gl_flushed(QemuConsole *con)
-- 
2.33.0.721.g106298f7f9



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v2 14/37] ui: add a gl-unblock warning timer
  2021-10-09 21:08 [PATCH v2 00/37] Add D-Bus display backend marcandre.lureau
                   ` (12 preceding siblings ...)
  2021-10-09 21:08 ` [PATCH v2 13/37] ui: make gl_block use a counter marcandre.lureau
@ 2021-10-09 21:08 ` marcandre.lureau
  2021-10-09 21:08 ` [PATCH v2 15/37] ui: simplify gl unblock & flush marcandre.lureau
                   ` (23 subsequent siblings)
  37 siblings, 0 replies; 54+ messages in thread
From: marcandre.lureau @ 2021-10-09 21:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, kraxel

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Similar to the one that exists for Spice, so we can investigate if
something is locked.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 ui/console.c | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/ui/console.c b/ui/console.c
index 8d641b9530..c62a33b1ec 100644
--- a/ui/console.c
+++ b/ui/console.c
@@ -129,6 +129,7 @@ struct QemuConsole {
     int dcls;
     DisplayChangeListener *gl;
     int gl_block;
+    QEMUTimer *gl_unblock_timer;
     int window_id;
 
     /* Graphic console state.  */
@@ -284,8 +285,14 @@ void graphic_hw_update(QemuConsole *con)
     }
 }
 
+static void graphic_hw_gl_unblock_timer(void *opaque)
+{
+    warn_report("console: no gl-unblock within one second");
+}
+
 void graphic_hw_gl_block(QemuConsole *con, bool block)
 {
+    uint64_t timeout;
     assert(con != NULL);
 
     if (block) {
@@ -301,6 +308,14 @@ void graphic_hw_gl_block(QemuConsole *con, bool block)
         return;
     }
     con->hw_ops->gl_block(con->hw, block);
+
+    if (block) {
+        timeout = qemu_clock_get_ms(QEMU_CLOCK_REALTIME);
+        timeout += 1000; /* one sec */
+        timer_mod(con->gl_unblock_timer, timeout);
+    } else {
+        timer_del(con->gl_unblock_timer);
+    }
 }
 
 void graphic_hw_gl_flushed(QemuConsole *con)
@@ -2024,6 +2039,8 @@ QemuConsole *graphic_console_init(DeviceState *dev, uint32_t head,
 
     surface = qemu_create_placeholder_surface(width, height, noinit);
     dpy_gfx_replace_surface(s, surface);
+    s->gl_unblock_timer = timer_new_ms(QEMU_CLOCK_REALTIME,
+                                       graphic_hw_gl_unblock_timer, s);
     return s;
 }
 
-- 
2.33.0.721.g106298f7f9



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v2 15/37] ui: simplify gl unblock & flush
  2021-10-09 21:08 [PATCH v2 00/37] Add D-Bus display backend marcandre.lureau
                   ` (13 preceding siblings ...)
  2021-10-09 21:08 ` [PATCH v2 14/37] ui: add a gl-unblock warning timer marcandre.lureau
@ 2021-10-09 21:08 ` marcandre.lureau
  2021-10-09 21:08 ` [PATCH v2 16/37] ui: dispatch GL events to all listeners marcandre.lureau
                   ` (22 subsequent siblings)
  37 siblings, 0 replies; 54+ messages in thread
From: marcandre.lureau @ 2021-10-09 21:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, kraxel

From: Marc-André Lureau <marcandre.lureau@redhat.com>

GraphicHw.gl_flushed was introduced to notify the
device (vhost-user-gpu) that the GL resources (the display scanout) are
no longer needed.

It was decoupled from QEMU own gl-blocking mechanism, but that
difference isn't helping. Instead, we can reuse QEMU gl-blocking and
notify virtio_gpu_gl_flushed() when unblocking (to unlock
vhost-user-gpu).

An extra block/unblock is added arount dpy_gl_update() so existing
backends that don't block will have the flush event handled. It will
also help when there are no backends associated.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/ui/console.h         |  2 --
 hw/display/vhost-user-gpu.c  |  2 +-
 hw/display/virtio-gpu-base.c |  5 ++++-
 hw/display/virtio-vga.c      | 11 -----------
 ui/console.c                 | 12 +++---------
 ui/gtk-egl.c                 |  1 -
 ui/gtk-gl-area.c             |  1 -
 ui/gtk.c                     |  1 -
 ui/sdl2-gl.c                 |  2 --
 ui/spice-display.c           |  1 -
 10 files changed, 8 insertions(+), 30 deletions(-)

diff --git a/include/ui/console.h b/include/ui/console.h
index 1617b4c59a..cd6f103bd9 100644
--- a/include/ui/console.h
+++ b/include/ui/console.h
@@ -386,7 +386,6 @@ typedef struct GraphicHwOps {
     void (*update_interval)(void *opaque, uint64_t interval);
     int (*ui_info)(void *opaque, uint32_t head, QemuUIInfo *info);
     void (*gl_block)(void *opaque, bool block);
-    void (*gl_flushed)(void *opaque);
 } GraphicHwOps;
 
 QemuConsole *graphic_console_init(DeviceState *dev, uint32_t head,
@@ -402,7 +401,6 @@ void graphic_hw_update_done(QemuConsole *con);
 void graphic_hw_invalidate(QemuConsole *con);
 void graphic_hw_text_update(QemuConsole *con, console_ch_t *chardata);
 void graphic_hw_gl_block(QemuConsole *con, bool block);
-void graphic_hw_gl_flushed(QemuConsole *con);
 
 void qemu_console_early_init(void);
 
diff --git a/hw/display/vhost-user-gpu.c b/hw/display/vhost-user-gpu.c
index 49df56cd14..09818231bd 100644
--- a/hw/display/vhost-user-gpu.c
+++ b/hw/display/vhost-user-gpu.c
@@ -254,8 +254,8 @@ vhost_user_gpu_handle_display(VhostUserGPU *g, VhostUserGpuMsg *msg)
             vhost_user_gpu_unblock(g);
             break;
         }
-        dpy_gl_update(con, m->x, m->y, m->width, m->height);
         g->backend_blocked = true;
+        dpy_gl_update(con, m->x, m->y, m->width, m->height);
         break;
     }
     case VHOST_USER_GPU_UPDATE: {
diff --git a/hw/display/virtio-gpu-base.c b/hw/display/virtio-gpu-base.c
index c8da4806e0..fff0fb4a82 100644
--- a/hw/display/virtio-gpu-base.c
+++ b/hw/display/virtio-gpu-base.c
@@ -117,6 +117,10 @@ virtio_gpu_gl_block(void *opaque, bool block)
         g->renderer_blocked--;
     }
     assert(g->renderer_blocked >= 0);
+
+    if (!block && g->renderer_blocked == 0) {
+        virtio_gpu_gl_flushed(g);
+    }
 }
 
 static int
@@ -143,7 +147,6 @@ static const GraphicHwOps virtio_gpu_ops = {
     .text_update = virtio_gpu_text_update,
     .ui_info = virtio_gpu_ui_info,
     .gl_block = virtio_gpu_gl_block,
-    .gl_flushed = virtio_gpu_gl_flushed,
 };
 
 bool
diff --git a/hw/display/virtio-vga.c b/hw/display/virtio-vga.c
index 9e57f61e9e..b23a75a04b 100644
--- a/hw/display/virtio-vga.c
+++ b/hw/display/virtio-vga.c
@@ -68,16 +68,6 @@ static void virtio_vga_base_gl_block(void *opaque, bool block)
     }
 }
 
-static void virtio_vga_base_gl_flushed(void *opaque)
-{
-    VirtIOVGABase *vvga = opaque;
-    VirtIOGPUBase *g = vvga->vgpu;
-
-    if (g->hw_ops->gl_flushed) {
-        g->hw_ops->gl_flushed(g);
-    }
-}
-
 static int virtio_vga_base_get_flags(void *opaque)
 {
     VirtIOVGABase *vvga = opaque;
@@ -93,7 +83,6 @@ static const GraphicHwOps virtio_vga_base_ops = {
     .text_update = virtio_vga_base_text_update,
     .ui_info = virtio_vga_base_ui_info,
     .gl_block = virtio_vga_base_gl_block,
-    .gl_flushed = virtio_vga_base_gl_flushed,
 };
 
 static const VMStateDescription vmstate_virtio_vga_base = {
diff --git a/ui/console.c b/ui/console.c
index c62a33b1ec..016ace5029 100644
--- a/ui/console.c
+++ b/ui/console.c
@@ -318,15 +318,6 @@ void graphic_hw_gl_block(QemuConsole *con, bool block)
     }
 }
 
-void graphic_hw_gl_flushed(QemuConsole *con)
-{
-    assert(con != NULL);
-
-    if (con->hw_ops->gl_flushed) {
-        con->hw_ops->gl_flushed(con->hw);
-    }
-}
-
 int qemu_console_get_window_id(QemuConsole *con)
 {
     return con->window_id;
@@ -1952,7 +1943,10 @@ void dpy_gl_update(QemuConsole *con,
                    uint32_t x, uint32_t y, uint32_t w, uint32_t h)
 {
     assert(con->gl);
+
+    graphic_hw_gl_block(con, true);
     con->gl->ops->dpy_gl_update(con->gl, x, y, w, h);
+    graphic_hw_gl_block(con, false);
 }
 
 /***********************************************************/
diff --git a/ui/gtk-egl.c b/ui/gtk-egl.c
index 72ce5e1f8f..0f64baee63 100644
--- a/ui/gtk-egl.c
+++ b/ui/gtk-egl.c
@@ -107,7 +107,6 @@ void gd_egl_draw(VirtualConsole *vc)
         graphic_hw_gl_block(vc->gfx.dcl.con, false);
     }
 #endif
-    graphic_hw_gl_flushed(vc->gfx.dcl.con);
 }
 
 void gd_egl_update(DisplayChangeListener *dcl,
diff --git a/ui/gtk-gl-area.c b/ui/gtk-gl-area.c
index b23523748e..5e79505291 100644
--- a/ui/gtk-gl-area.c
+++ b/ui/gtk-gl-area.c
@@ -91,7 +91,6 @@ void gd_gl_area_draw(VirtualConsole *vc)
         graphic_hw_gl_block(vc->gfx.dcl.con, false);
     }
 #endif
-    graphic_hw_gl_flushed(vc->gfx.dcl.con);
 }
 
 void gd_gl_area_update(DisplayChangeListener *dcl,
diff --git a/ui/gtk.c b/ui/gtk.c
index f8beddbb91..71bfe29793 100644
--- a/ui/gtk.c
+++ b/ui/gtk.c
@@ -590,7 +590,6 @@ void gd_hw_gl_flushed(void *vcon)
     QemuDmaBuf *dmabuf = vc->gfx.guest_fb.dmabuf;
 
     graphic_hw_gl_block(vc->gfx.dcl.con, false);
-    graphic_hw_gl_flushed(vc->gfx.dcl.con);
     qemu_set_fd_handler(dmabuf->fence_fd, NULL, NULL, NULL);
     close(dmabuf->fence_fd);
     dmabuf->fence_fd = -1;
diff --git a/ui/sdl2-gl.c b/ui/sdl2-gl.c
index a21d2deed9..5b950fbbea 100644
--- a/ui/sdl2-gl.c
+++ b/ui/sdl2-gl.c
@@ -58,7 +58,6 @@ static void sdl2_gl_render_surface(struct sdl2_console *scon)
 
     surface_gl_render_texture(scon->gls, scon->surface);
     SDL_GL_SwapWindow(scon->real_window);
-    graphic_hw_gl_flushed(scon->dcl.con);
 }
 
 void sdl2_gl_update(DisplayChangeListener *dcl,
@@ -241,5 +240,4 @@ void sdl2_gl_scanout_flush(DisplayChangeListener *dcl,
     egl_fb_blit(&scon->win_fb, &scon->guest_fb, !scon->y0_top);
 
     SDL_GL_SwapWindow(scon->real_window);
-    graphic_hw_gl_flushed(dcl->con);
 }
diff --git a/ui/spice-display.c b/ui/spice-display.c
index 2c204bceee..ec501f129f 100644
--- a/ui/spice-display.c
+++ b/ui/spice-display.c
@@ -830,7 +830,6 @@ static void qemu_spice_gl_unblock_bh(void *opaque)
     SimpleSpiceDisplay *ssd = opaque;
 
     qemu_spice_gl_block(ssd, false);
-    graphic_hw_gl_flushed(ssd->dcl.con);
 }
 
 static void qemu_spice_gl_block_timer(void *opaque)
-- 
2.33.0.721.g106298f7f9



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v2 16/37] ui: dispatch GL events to all listeners
  2021-10-09 21:08 [PATCH v2 00/37] Add D-Bus display backend marcandre.lureau
                   ` (14 preceding siblings ...)
  2021-10-09 21:08 ` [PATCH v2 15/37] ui: simplify gl unblock & flush marcandre.lureau
@ 2021-10-09 21:08 ` marcandre.lureau
  2021-10-09 21:08 ` [PATCH v2 17/37] ui: split the GL context in a different object marcandre.lureau
                   ` (21 subsequent siblings)
  37 siblings, 0 replies; 54+ messages in thread
From: marcandre.lureau @ 2021-10-09 21:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, kraxel

From: Marc-André Lureau <marcandre.lureau@redhat.com>

For now, only one listener can receive GL events. Let's dispatch to all
listeners. (preliminary check ensure there is a single listener now
during regitration, and in next patches, compatible listeners only)

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 ui/console.c | 58 +++++++++++++++++++++++++++++++++++++---------------
 1 file changed, 42 insertions(+), 16 deletions(-)

diff --git a/ui/console.c b/ui/console.c
index 016ace5029..75e432106b 100644
--- a/ui/console.c
+++ b/ui/console.c
@@ -1882,8 +1882,12 @@ int dpy_gl_ctx_make_current(QemuConsole *con, QEMUGLContext ctx)
 
 void dpy_gl_scanout_disable(QemuConsole *con)
 {
-    assert(con->gl);
-    con->gl->ops->dpy_gl_scanout_disable(con->gl);
+    DisplayState *s = con->ds;
+    DisplayChangeListener *dcl;
+
+    QLIST_FOREACH(dcl, &s->listeners, next) {
+        dcl->ops->dpy_gl_scanout_disable(dcl);
+    }
 }
 
 void dpy_gl_scanout_texture(QemuConsole *con,
@@ -1894,58 +1898,80 @@ void dpy_gl_scanout_texture(QemuConsole *con,
                             uint32_t x, uint32_t y,
                             uint32_t width, uint32_t height)
 {
-    assert(con->gl);
-    con->gl->ops->dpy_gl_scanout_texture(con->gl, backing_id,
+    DisplayState *s = con->ds;
+    DisplayChangeListener *dcl;
+
+    QLIST_FOREACH(dcl, &s->listeners, next) {
+        dcl->ops->dpy_gl_scanout_texture(dcl, backing_id,
                                          backing_y_0_top,
                                          backing_width, backing_height,
                                          x, y, width, height);
+    }
 }
 
 void dpy_gl_scanout_dmabuf(QemuConsole *con,
                            QemuDmaBuf *dmabuf)
 {
-    assert(con->gl);
-    con->gl->ops->dpy_gl_scanout_dmabuf(con->gl, dmabuf);
+    DisplayState *s = con->ds;
+    DisplayChangeListener *dcl;
+
+    QLIST_FOREACH(dcl, &s->listeners, next) {
+        dcl->ops->dpy_gl_scanout_dmabuf(dcl, dmabuf);
+    }
 }
 
 void dpy_gl_cursor_dmabuf(QemuConsole *con, QemuDmaBuf *dmabuf,
                           bool have_hot, uint32_t hot_x, uint32_t hot_y)
 {
-    assert(con->gl);
+    DisplayState *s = con->ds;
+    DisplayChangeListener *dcl;
 
-    if (con->gl->ops->dpy_gl_cursor_dmabuf) {
-        con->gl->ops->dpy_gl_cursor_dmabuf(con->gl, dmabuf,
+    QLIST_FOREACH(dcl, &s->listeners, next) {
+        if (dcl->ops->dpy_gl_cursor_dmabuf) {
+            dcl->ops->dpy_gl_cursor_dmabuf(dcl, dmabuf,
                                            have_hot, hot_x, hot_y);
+        }
     }
 }
 
 void dpy_gl_cursor_position(QemuConsole *con,
                             uint32_t pos_x, uint32_t pos_y)
 {
-    assert(con->gl);
+    DisplayState *s = con->ds;
+    DisplayChangeListener *dcl;
 
-    if (con->gl->ops->dpy_gl_cursor_position) {
-        con->gl->ops->dpy_gl_cursor_position(con->gl, pos_x, pos_y);
+    QLIST_FOREACH(dcl, &s->listeners, next) {
+        if (dcl->ops->dpy_gl_cursor_position) {
+            dcl->ops->dpy_gl_cursor_position(dcl, pos_x, pos_y);
+        }
     }
 }
 
 void dpy_gl_release_dmabuf(QemuConsole *con,
                           QemuDmaBuf *dmabuf)
 {
-    assert(con->gl);
+    DisplayState *s = con->ds;
+    DisplayChangeListener *dcl;
 
-    if (con->gl->ops->dpy_gl_release_dmabuf) {
-        con->gl->ops->dpy_gl_release_dmabuf(con->gl, dmabuf);
+    QLIST_FOREACH(dcl, &s->listeners, next) {
+        if (dcl->ops->dpy_gl_release_dmabuf) {
+            dcl->ops->dpy_gl_release_dmabuf(dcl, dmabuf);
+        }
     }
 }
 
 void dpy_gl_update(QemuConsole *con,
                    uint32_t x, uint32_t y, uint32_t w, uint32_t h)
 {
+    DisplayState *s = con->ds;
+    DisplayChangeListener *dcl;
+
     assert(con->gl);
 
     graphic_hw_gl_block(con, true);
-    con->gl->ops->dpy_gl_update(con->gl, x, y, w, h);
+    QLIST_FOREACH(dcl, &s->listeners, next) {
+        dcl->ops->dpy_gl_update(dcl, x, y, w, h);
+    }
     graphic_hw_gl_block(con, false);
 }
 
-- 
2.33.0.721.g106298f7f9



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v2 17/37] ui: split the GL context in a different object
  2021-10-09 21:08 [PATCH v2 00/37] Add D-Bus display backend marcandre.lureau
                   ` (15 preceding siblings ...)
  2021-10-09 21:08 ` [PATCH v2 16/37] ui: dispatch GL events to all listeners marcandre.lureau
@ 2021-10-09 21:08 ` marcandre.lureau
  2021-10-09 21:08 ` [PATCH v2 18/37] ui: move qemu_spice_fill_device_address to ui/util.c marcandre.lureau
                   ` (20 subsequent siblings)
  37 siblings, 0 replies; 54+ messages in thread
From: marcandre.lureau @ 2021-10-09 21:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, kraxel

From: Marc-André Lureau <marcandre.lureau@redhat.com>

This will allow to have one GL context but a variable number of
listeners.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/ui/console.h       | 34 ++++++++++++++++++++++------------
 include/ui/egl-context.h   |  6 +++---
 include/ui/gtk.h           | 11 ++++++-----
 include/ui/sdl2.h          |  7 ++++---
 include/ui/spice-display.h |  1 +
 ui/console.c               | 26 ++++++++++++++++----------
 ui/egl-context.c           |  6 +++---
 ui/egl-headless.c          | 21 ++++++++++++++-------
 ui/gtk-egl.c               | 10 +++++-----
 ui/gtk-gl-area.c           |  8 ++++----
 ui/gtk.c                   | 24 ++++++++++++++++--------
 ui/sdl2-gl.c               | 10 +++++-----
 ui/sdl2.c                  | 13 +++++++++----
 ui/spice-display.c         | 18 +++++++++++-------
 14 files changed, 119 insertions(+), 76 deletions(-)

diff --git a/include/ui/console.h b/include/ui/console.h
index cd6f103bd9..7a35c4fc6a 100644
--- a/include/ui/console.h
+++ b/include/ui/console.h
@@ -174,6 +174,7 @@ typedef struct QemuDmaBuf {
 } QemuDmaBuf;
 
 typedef struct DisplayState DisplayState;
+typedef struct DisplayGLCtx DisplayGLCtx;
 
 typedef struct DisplayChangeListenerOps {
     const char *dpy_name;
@@ -208,16 +209,6 @@ typedef struct DisplayChangeListenerOps {
     void (*dpy_cursor_define)(DisplayChangeListener *dcl,
                               QEMUCursor *cursor);
 
-    /* required if GL */
-    QEMUGLContext (*dpy_gl_ctx_create)(DisplayChangeListener *dcl,
-                                       QEMUGLParams *params);
-    /* required if GL */
-    void (*dpy_gl_ctx_destroy)(DisplayChangeListener *dcl,
-                               QEMUGLContext ctx);
-    /* required if GL */
-    int (*dpy_gl_ctx_make_current)(DisplayChangeListener *dcl,
-                                   QEMUGLContext ctx);
-
     /* required if GL */
     void (*dpy_gl_scanout_disable)(DisplayChangeListener *dcl);
     /* required if GL */
@@ -258,6 +249,26 @@ struct DisplayChangeListener {
     QLIST_ENTRY(DisplayChangeListener) next;
 };
 
+typedef struct DisplayGLCtxOps {
+    /*
+     * We only check if the GLCtx is compatible with a DCL via ops. A natural
+     * evolution of this would be a callback to check some runtime requirements
+     * and allow various DCL kinds.
+     */
+    const DisplayChangeListenerOps *compatible_dcl;
+
+    QEMUGLContext (*dpy_gl_ctx_create)(DisplayGLCtx *dgc,
+                                       QEMUGLParams *params);
+    void (*dpy_gl_ctx_destroy)(DisplayGLCtx *dgc,
+                               QEMUGLContext ctx);
+    int (*dpy_gl_ctx_make_current)(DisplayGLCtx *dgc,
+                                   QEMUGLContext ctx);
+} DisplayGLCtxOps;
+
+struct DisplayGLCtx {
+    const DisplayGLCtxOps *ops;
+};
+
 DisplayState *init_displaystate(void);
 DisplaySurface *qemu_create_displaysurface_from(int width, int height,
                                                 pixman_format_code_t format,
@@ -404,8 +415,7 @@ void graphic_hw_gl_block(QemuConsole *con, bool block);
 
 void qemu_console_early_init(void);
 
-void qemu_console_set_display_gl_ctx(QemuConsole *con,
-                                     DisplayChangeListener *dcl);
+void qemu_console_set_display_gl_ctx(QemuConsole *con, DisplayGLCtx *ctx);
 
 QemuConsole *qemu_console_lookup_by_index(unsigned int index);
 QemuConsole *qemu_console_lookup_by_device(DeviceState *dev, uint32_t head);
diff --git a/include/ui/egl-context.h b/include/ui/egl-context.h
index 9374fe41e3..c2761d747a 100644
--- a/include/ui/egl-context.h
+++ b/include/ui/egl-context.h
@@ -4,10 +4,10 @@
 #include "ui/console.h"
 #include "ui/egl-helpers.h"
 
-QEMUGLContext qemu_egl_create_context(DisplayChangeListener *dcl,
+QEMUGLContext qemu_egl_create_context(DisplayGLCtx *dgc,
                                       QEMUGLParams *params);
-void qemu_egl_destroy_context(DisplayChangeListener *dcl, QEMUGLContext ctx);
-int qemu_egl_make_context_current(DisplayChangeListener *dcl,
+void qemu_egl_destroy_context(DisplayGLCtx *dgc, QEMUGLContext ctx);
+int qemu_egl_make_context_current(DisplayGLCtx *dgc,
                                   QEMUGLContext ctx);
 
 #endif /* EGL_CONTEXT_H */
diff --git a/include/ui/gtk.h b/include/ui/gtk.h
index 7d22affd38..101b147d1b 100644
--- a/include/ui/gtk.h
+++ b/include/ui/gtk.h
@@ -35,6 +35,7 @@ typedef struct GtkDisplayState GtkDisplayState;
 
 typedef struct VirtualGfxConsole {
     GtkWidget *drawing_area;
+    DisplayGLCtx dgc;
     DisplayChangeListener dcl;
     QKbdState *kbd;
     DisplaySurface *ds;
@@ -165,7 +166,7 @@ void gd_egl_update(DisplayChangeListener *dcl,
 void gd_egl_refresh(DisplayChangeListener *dcl);
 void gd_egl_switch(DisplayChangeListener *dcl,
                    DisplaySurface *surface);
-QEMUGLContext gd_egl_create_context(DisplayChangeListener *dcl,
+QEMUGLContext gd_egl_create_context(DisplayGLCtx *dgc,
                                     QEMUGLParams *params);
 void gd_egl_scanout_disable(DisplayChangeListener *dcl);
 void gd_egl_scanout_texture(DisplayChangeListener *dcl,
@@ -187,7 +188,7 @@ void gd_egl_flush(DisplayChangeListener *dcl,
 void gd_egl_scanout_flush(DisplayChangeListener *dcl,
                           uint32_t x, uint32_t y, uint32_t w, uint32_t h);
 void gtk_egl_init(DisplayGLMode mode);
-int gd_egl_make_current(DisplayChangeListener *dcl,
+int gd_egl_make_current(DisplayGLCtx *dgc,
                         QEMUGLContext ctx);
 
 /* ui/gtk-gl-area.c */
@@ -198,9 +199,9 @@ void gd_gl_area_update(DisplayChangeListener *dcl,
 void gd_gl_area_refresh(DisplayChangeListener *dcl);
 void gd_gl_area_switch(DisplayChangeListener *dcl,
                        DisplaySurface *surface);
-QEMUGLContext gd_gl_area_create_context(DisplayChangeListener *dcl,
+QEMUGLContext gd_gl_area_create_context(DisplayGLCtx *dgc,
                                         QEMUGLParams *params);
-void gd_gl_area_destroy_context(DisplayChangeListener *dcl,
+void gd_gl_area_destroy_context(DisplayGLCtx *dgc,
                                 QEMUGLContext ctx);
 void gd_gl_area_scanout_dmabuf(DisplayChangeListener *dcl,
                                QemuDmaBuf *dmabuf);
@@ -215,7 +216,7 @@ void gd_gl_area_scanout_disable(DisplayChangeListener *dcl);
 void gd_gl_area_scanout_flush(DisplayChangeListener *dcl,
                               uint32_t x, uint32_t y, uint32_t w, uint32_t h);
 void gtk_gl_area_init(void);
-int gd_gl_area_make_current(DisplayChangeListener *dcl,
+int gd_gl_area_make_current(DisplayGLCtx *dgc,
                             QEMUGLContext ctx);
 
 /* gtk-clipboard.c */
diff --git a/include/ui/sdl2.h b/include/ui/sdl2.h
index f85c117a78..71bcf7ebda 100644
--- a/include/ui/sdl2.h
+++ b/include/ui/sdl2.h
@@ -16,6 +16,7 @@
 #endif
 
 struct sdl2_console {
+    DisplayGLCtx dgc;
     DisplayChangeListener dcl;
     DisplaySurface *surface;
     DisplayOptions *opts;
@@ -65,10 +66,10 @@ void sdl2_gl_switch(DisplayChangeListener *dcl,
 void sdl2_gl_refresh(DisplayChangeListener *dcl);
 void sdl2_gl_redraw(struct sdl2_console *scon);
 
-QEMUGLContext sdl2_gl_create_context(DisplayChangeListener *dcl,
+QEMUGLContext sdl2_gl_create_context(DisplayGLCtx *dgc,
                                      QEMUGLParams *params);
-void sdl2_gl_destroy_context(DisplayChangeListener *dcl, QEMUGLContext ctx);
-int sdl2_gl_make_context_current(DisplayChangeListener *dcl,
+void sdl2_gl_destroy_context(DisplayGLCtx *dgc, QEMUGLContext ctx);
+int sdl2_gl_make_context_current(DisplayGLCtx *dgc,
                                  QEMUGLContext ctx);
 
 void sdl2_gl_scanout_disable(DisplayChangeListener *dcl);
diff --git a/include/ui/spice-display.h b/include/ui/spice-display.h
index ed298d58f0..a2fbf62c52 100644
--- a/include/ui/spice-display.h
+++ b/include/ui/spice-display.h
@@ -86,6 +86,7 @@ typedef struct SimpleSpiceCursor SimpleSpiceCursor;
 
 struct SimpleSpiceDisplay {
     DisplaySurface *ds;
+    DisplayGLCtx dgc;
     DisplayChangeListener dcl;
     void *buf;
     int bufsize;
diff --git a/ui/console.c b/ui/console.c
index 75e432106b..e5a2c84dd9 100644
--- a/ui/console.c
+++ b/ui/console.c
@@ -127,7 +127,7 @@ struct QemuConsole {
     DisplayState *ds;
     DisplaySurface *surface;
     int dcls;
-    DisplayChangeListener *gl;
+    DisplayGLCtx *gl;
     int gl_block;
     QEMUTimer *gl_unblock_timer;
     int window_id;
@@ -1516,17 +1516,24 @@ static bool dpy_compatible_with(QemuConsole *con,
     return true;
 }
 
-void qemu_console_set_display_gl_ctx(QemuConsole *con,
-                                     DisplayChangeListener *dcl)
+void qemu_console_set_display_gl_ctx(QemuConsole *con, DisplayGLCtx *gl)
 {
     /* display has opengl support */
-    assert(dcl->con);
-    if (dcl->con->gl) {
-        fprintf(stderr, "can't register two opengl displays (%s, %s)\n",
-                dcl->ops->dpy_name, dcl->con->gl->ops->dpy_name);
+    assert(con);
+    if (con->gl) {
+        error_report("The console already has an OpenGL context.");
         exit(1);
     }
-    dcl->con->gl = dcl;
+    con->gl = gl;
+}
+
+static bool dpy_gl_compatible_with(QemuConsole *con, DisplayChangeListener *dcl)
+{
+    if (!con->gl) {
+        return true;
+    }
+
+    return con->gl->ops->compatible_dcl == dcl->ops;
 }
 
 void register_displaychangelistener(DisplayChangeListener *dcl)
@@ -1538,8 +1545,7 @@ void register_displaychangelistener(DisplayChangeListener *dcl)
 
     assert(!dcl->ds);
 
-    if (dcl->con && dcl->con->gl &&
-        dcl->con->gl != dcl) {
+    if (dcl->con && !dpy_gl_compatible_with(dcl->con, dcl)) {
         error_report("Display %s is incompatible with the GL context",
                      dcl->ops->dpy_name);
         exit(1);
diff --git a/ui/egl-context.c b/ui/egl-context.c
index 368ffa49d8..eb5f520fc4 100644
--- a/ui/egl-context.c
+++ b/ui/egl-context.c
@@ -1,7 +1,7 @@
 #include "qemu/osdep.h"
 #include "ui/egl-context.h"
 
-QEMUGLContext qemu_egl_create_context(DisplayChangeListener *dcl,
+QEMUGLContext qemu_egl_create_context(DisplayGLCtx *dgc,
                                       QEMUGLParams *params)
 {
    EGLContext ctx;
@@ -24,12 +24,12 @@ QEMUGLContext qemu_egl_create_context(DisplayChangeListener *dcl,
    return ctx;
 }
 
-void qemu_egl_destroy_context(DisplayChangeListener *dcl, QEMUGLContext ctx)
+void qemu_egl_destroy_context(DisplayGLCtx *dgc, QEMUGLContext ctx)
 {
     eglDestroyContext(qemu_egl_display, ctx);
 }
 
-int qemu_egl_make_context_current(DisplayChangeListener *dcl,
+int qemu_egl_make_context_current(DisplayGLCtx *dgc,
                                   QEMUGLContext ctx)
 {
    return eglMakeCurrent(qemu_egl_display,
diff --git a/ui/egl-headless.c b/ui/egl-headless.c
index 08327c40c6..94082a9da9 100644
--- a/ui/egl-headless.c
+++ b/ui/egl-headless.c
@@ -38,12 +38,12 @@ static void egl_gfx_switch(DisplayChangeListener *dcl,
     edpy->ds = new_surface;
 }
 
-static QEMUGLContext egl_create_context(DisplayChangeListener *dcl,
+static QEMUGLContext egl_create_context(DisplayGLCtx *dgc,
                                         QEMUGLParams *params)
 {
     eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
                    qemu_egl_rn_ctx);
-    return qemu_egl_create_context(dcl, params);
+    return qemu_egl_create_context(dgc, params);
 }
 
 static void egl_scanout_disable(DisplayChangeListener *dcl)
@@ -157,10 +157,6 @@ static const DisplayChangeListenerOps egl_ops = {
     .dpy_gfx_update          = egl_gfx_update,
     .dpy_gfx_switch          = egl_gfx_switch,
 
-    .dpy_gl_ctx_create       = egl_create_context,
-    .dpy_gl_ctx_destroy      = qemu_egl_destroy_context,
-    .dpy_gl_ctx_make_current = qemu_egl_make_context_current,
-
     .dpy_gl_scanout_disable  = egl_scanout_disable,
     .dpy_gl_scanout_texture  = egl_scanout_texture,
     .dpy_gl_scanout_dmabuf   = egl_scanout_dmabuf,
@@ -170,6 +166,13 @@ static const DisplayChangeListenerOps egl_ops = {
     .dpy_gl_update           = egl_scanout_flush,
 };
 
+static const DisplayGLCtxOps eglctx_ops = {
+    .compatible_dcl          = &egl_ops,
+    .dpy_gl_ctx_create       = egl_create_context,
+    .dpy_gl_ctx_destroy      = qemu_egl_destroy_context,
+    .dpy_gl_ctx_make_current = qemu_egl_make_context_current,
+};
+
 static void early_egl_headless_init(DisplayOptions *opts)
 {
     display_opengl = 1;
@@ -188,6 +191,8 @@ static void egl_headless_init(DisplayState *ds, DisplayOptions *opts)
     }
 
     for (idx = 0;; idx++) {
+        DisplayGLCtx *ctx;
+
         con = qemu_console_lookup_by_index(idx);
         if (!con || !qemu_console_is_graphic(con)) {
             break;
@@ -197,7 +202,9 @@ static void egl_headless_init(DisplayState *ds, DisplayOptions *opts)
         edpy->dcl.con = con;
         edpy->dcl.ops = &egl_ops;
         edpy->gls = qemu_gl_init_shader();
-        qemu_console_set_display_gl_ctx(con, &edpy->dcl);
+        ctx = g_new0(DisplayGLCtx, 1);
+        ctx->ops = &eglctx_ops;
+        qemu_console_set_display_gl_ctx(con, ctx);
         register_displaychangelistener(&edpy->dcl);
     }
 }
diff --git a/ui/gtk-egl.c b/ui/gtk-egl.c
index 0f64baee63..7087e5a5cf 100644
--- a/ui/gtk-egl.c
+++ b/ui/gtk-egl.c
@@ -176,14 +176,14 @@ void gd_egl_switch(DisplayChangeListener *dcl,
     }
 }
 
-QEMUGLContext gd_egl_create_context(DisplayChangeListener *dcl,
+QEMUGLContext gd_egl_create_context(DisplayGLCtx *dgc,
                                     QEMUGLParams *params)
 {
-    VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
+    VirtualConsole *vc = container_of(dgc, VirtualConsole, gfx.dgc);
 
     eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
                    vc->gfx.esurface, vc->gfx.ectx);
-    return qemu_egl_create_context(dcl, params);
+    return qemu_egl_create_context(dgc, params);
 }
 
 void gd_egl_scanout_disable(DisplayChangeListener *dcl)
@@ -335,10 +335,10 @@ void gtk_egl_init(DisplayGLMode mode)
     display_opengl = 1;
 }
 
-int gd_egl_make_current(DisplayChangeListener *dcl,
+int gd_egl_make_current(DisplayGLCtx *dgc,
                         QEMUGLContext ctx)
 {
-    VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
+    VirtualConsole *vc = container_of(dgc, VirtualConsole, gfx.dgc);
 
     return eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
                           vc->gfx.esurface, ctx);
diff --git a/ui/gtk-gl-area.c b/ui/gtk-gl-area.c
index 5e79505291..cd6c455f22 100644
--- a/ui/gtk-gl-area.c
+++ b/ui/gtk-gl-area.c
@@ -157,10 +157,10 @@ void gd_gl_area_switch(DisplayChangeListener *dcl,
     }
 }
 
-QEMUGLContext gd_gl_area_create_context(DisplayChangeListener *dcl,
+QEMUGLContext gd_gl_area_create_context(DisplayGLCtx *dgc,
                                         QEMUGLParams *params)
 {
-    VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
+    VirtualConsole *vc = container_of(dgc, VirtualConsole, gfx.dgc);
     GdkWindow *window;
     GdkGLContext *ctx;
     GError *err = NULL;
@@ -186,7 +186,7 @@ QEMUGLContext gd_gl_area_create_context(DisplayChangeListener *dcl,
     return ctx;
 }
 
-void gd_gl_area_destroy_context(DisplayChangeListener *dcl, QEMUGLContext ctx)
+void gd_gl_area_destroy_context(DisplayGLCtx *dgc, QEMUGLContext ctx)
 {
     /* FIXME */
 }
@@ -264,7 +264,7 @@ void gtk_gl_area_init(void)
     display_opengl = 1;
 }
 
-int gd_gl_area_make_current(DisplayChangeListener *dcl,
+int gd_gl_area_make_current(DisplayGLCtx *dgc,
                             QEMUGLContext ctx)
 {
     gdk_gl_context_make_current(ctx);
diff --git a/ui/gtk.c b/ui/gtk.c
index 71bfe29793..db9bcb6f1a 100644
--- a/ui/gtk.c
+++ b/ui/gtk.c
@@ -606,9 +606,6 @@ static const DisplayChangeListenerOps dcl_gl_area_ops = {
     .dpy_mouse_set        = gd_mouse_set,
     .dpy_cursor_define    = gd_cursor_define,
 
-    .dpy_gl_ctx_create       = gd_gl_area_create_context,
-    .dpy_gl_ctx_destroy      = gd_gl_area_destroy_context,
-    .dpy_gl_ctx_make_current = gd_gl_area_make_current,
     .dpy_gl_scanout_texture  = gd_gl_area_scanout_texture,
     .dpy_gl_scanout_disable  = gd_gl_area_scanout_disable,
     .dpy_gl_update           = gd_gl_area_scanout_flush,
@@ -617,8 +614,14 @@ static const DisplayChangeListenerOps dcl_gl_area_ops = {
     .dpy_has_dmabuf          = gd_has_dmabuf,
 };
 
-#ifdef CONFIG_X11
+static const DisplayGLCtxOps gl_area_ctx_ops = {
+    .compatible_dcl          = &dcl_gl_area_ops,
+    .dpy_gl_ctx_create       = gd_gl_area_create_context,
+    .dpy_gl_ctx_destroy      = gd_gl_area_destroy_context,
+    .dpy_gl_ctx_make_current = gd_gl_area_make_current,
+};
 
+#ifdef CONFIG_X11
 static const DisplayChangeListenerOps dcl_egl_ops = {
     .dpy_name             = "gtk-egl",
     .dpy_gfx_update       = gd_egl_update,
@@ -628,9 +631,6 @@ static const DisplayChangeListenerOps dcl_egl_ops = {
     .dpy_mouse_set        = gd_mouse_set,
     .dpy_cursor_define    = gd_cursor_define,
 
-    .dpy_gl_ctx_create       = gd_egl_create_context,
-    .dpy_gl_ctx_destroy      = qemu_egl_destroy_context,
-    .dpy_gl_ctx_make_current = gd_egl_make_current,
     .dpy_gl_scanout_disable  = gd_egl_scanout_disable,
     .dpy_gl_scanout_texture  = gd_egl_scanout_texture,
     .dpy_gl_scanout_dmabuf   = gd_egl_scanout_dmabuf,
@@ -641,6 +641,12 @@ static const DisplayChangeListenerOps dcl_egl_ops = {
     .dpy_has_dmabuf          = gd_has_dmabuf,
 };
 
+static const DisplayGLCtxOps egl_ctx_ops = {
+    .compatible_dcl          = &dcl_egl_ops,
+    .dpy_gl_ctx_create       = gd_egl_create_context,
+    .dpy_gl_ctx_destroy      = qemu_egl_destroy_context,
+    .dpy_gl_ctx_make_current = gd_egl_make_current,
+};
 #endif
 
 #endif /* CONFIG_OPENGL */
@@ -2008,6 +2014,7 @@ static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc,
             g_signal_connect(vc->gfx.drawing_area, "realize",
                              G_CALLBACK(gl_area_realize), vc);
             vc->gfx.dcl.ops = &dcl_gl_area_ops;
+            vc->gfx.dgc.ops = &gl_area_ctx_ops;
         } else {
 #ifdef CONFIG_X11
             vc->gfx.drawing_area = gtk_drawing_area_new();
@@ -2022,6 +2029,7 @@ static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc,
             gtk_widget_set_double_buffered(vc->gfx.drawing_area, FALSE);
 #pragma GCC diagnostic pop
             vc->gfx.dcl.ops = &dcl_egl_ops;
+            vc->gfx.dgc.ops = &egl_ctx_ops;
             vc->gfx.has_dmabuf = qemu_egl_has_dmabuf();
 #else
             abort();
@@ -2057,7 +2065,7 @@ static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc,
     vc->gfx.dcl.con = con;
 
     if (display_opengl) {
-        qemu_console_set_display_gl_ctx(con, &vc->gfx.dcl);
+        qemu_console_set_display_gl_ctx(con, &vc->gfx.dgc);
     }
     register_displaychangelistener(&vc->gfx.dcl);
 
diff --git a/ui/sdl2-gl.c b/ui/sdl2-gl.c
index 5b950fbbea..39cab8cde7 100644
--- a/ui/sdl2-gl.c
+++ b/ui/sdl2-gl.c
@@ -132,10 +132,10 @@ void sdl2_gl_redraw(struct sdl2_console *scon)
     }
 }
 
-QEMUGLContext sdl2_gl_create_context(DisplayChangeListener *dcl,
+QEMUGLContext sdl2_gl_create_context(DisplayGLCtx *dgc,
                                      QEMUGLParams *params)
 {
-    struct sdl2_console *scon = container_of(dcl, struct sdl2_console, dcl);
+    struct sdl2_console *scon = container_of(dgc, struct sdl2_console, dgc);
     SDL_GLContext ctx;
 
     assert(scon->opengl);
@@ -167,17 +167,17 @@ QEMUGLContext sdl2_gl_create_context(DisplayChangeListener *dcl,
     return (QEMUGLContext)ctx;
 }
 
-void sdl2_gl_destroy_context(DisplayChangeListener *dcl, QEMUGLContext ctx)
+void sdl2_gl_destroy_context(DisplayGLCtx *dgc, QEMUGLContext ctx)
 {
     SDL_GLContext sdlctx = (SDL_GLContext)ctx;
 
     SDL_GL_DeleteContext(sdlctx);
 }
 
-int sdl2_gl_make_context_current(DisplayChangeListener *dcl,
+int sdl2_gl_make_context_current(DisplayGLCtx *dgc,
                                  QEMUGLContext ctx)
 {
-    struct sdl2_console *scon = container_of(dcl, struct sdl2_console, dcl);
+    struct sdl2_console *scon = container_of(dgc, struct sdl2_console, dgc);
     SDL_GLContext sdlctx = (SDL_GLContext)ctx;
 
     assert(scon->opengl);
diff --git a/ui/sdl2.c b/ui/sdl2.c
index bb186a381a..0bd30504cf 100644
--- a/ui/sdl2.c
+++ b/ui/sdl2.c
@@ -778,13 +778,17 @@ static const DisplayChangeListenerOps dcl_gl_ops = {
     .dpy_mouse_set           = sdl_mouse_warp,
     .dpy_cursor_define       = sdl_mouse_define,
 
-    .dpy_gl_ctx_create       = sdl2_gl_create_context,
-    .dpy_gl_ctx_destroy      = sdl2_gl_destroy_context,
-    .dpy_gl_ctx_make_current = sdl2_gl_make_context_current,
     .dpy_gl_scanout_disable  = sdl2_gl_scanout_disable,
     .dpy_gl_scanout_texture  = sdl2_gl_scanout_texture,
     .dpy_gl_update           = sdl2_gl_scanout_flush,
 };
+
+static const DisplayGLCtxOps gl_ctx_ops = {
+    .compatible_dcl          = &dcl_gl_ops,
+    .dpy_gl_ctx_create       = sdl2_gl_create_context,
+    .dpy_gl_ctx_destroy      = sdl2_gl_destroy_context,
+    .dpy_gl_ctx_make_current = sdl2_gl_make_context_current,
+};
 #endif
 
 static void sdl2_display_early_init(DisplayOptions *o)
@@ -860,6 +864,7 @@ static void sdl2_display_init(DisplayState *ds, DisplayOptions *o)
 #ifdef CONFIG_OPENGL
         sdl2_console[i].opengl = display_opengl;
         sdl2_console[i].dcl.ops = display_opengl ? &dcl_gl_ops : &dcl_2d_ops;
+        sdl2_console[i].dgc.ops = display_opengl ? &gl_ctx_ops : NULL;
 #else
         sdl2_console[i].opengl = 0;
         sdl2_console[i].dcl.ops = &dcl_2d_ops;
@@ -867,7 +872,7 @@ static void sdl2_display_init(DisplayState *ds, DisplayOptions *o)
         sdl2_console[i].dcl.con = con;
         sdl2_console[i].kbd = qkbd_state_init(con);
         if (display_opengl) {
-            qemu_console_set_display_gl_ctx(con, &sdl2_console[i].dcl);
+            qemu_console_set_display_gl_ctx(con, &sdl2_console[i].dgc);
         }
         register_displaychangelistener(&sdl2_console[i].dcl);
 
diff --git a/ui/spice-display.c b/ui/spice-display.c
index ec501f129f..798e0f6167 100644
--- a/ui/spice-display.c
+++ b/ui/spice-display.c
@@ -908,12 +908,12 @@ static void spice_gl_switch(DisplayChangeListener *dcl,
     }
 }
 
-static QEMUGLContext qemu_spice_gl_create_context(DisplayChangeListener *dcl,
+static QEMUGLContext qemu_spice_gl_create_context(DisplayGLCtx *dgc,
                                                   QEMUGLParams *params)
 {
     eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
                    qemu_egl_rn_ctx);
-    return qemu_egl_create_context(dcl, params);
+    return qemu_egl_create_context(dgc, params);
 }
 
 static void qemu_spice_gl_scanout_disable(DisplayChangeListener *dcl)
@@ -1105,10 +1105,6 @@ static const DisplayChangeListenerOps display_listener_gl_ops = {
     .dpy_mouse_set           = display_mouse_set,
     .dpy_cursor_define       = display_mouse_define,
 
-    .dpy_gl_ctx_create       = qemu_spice_gl_create_context,
-    .dpy_gl_ctx_destroy      = qemu_egl_destroy_context,
-    .dpy_gl_ctx_make_current = qemu_egl_make_context_current,
-
     .dpy_gl_scanout_disable  = qemu_spice_gl_scanout_disable,
     .dpy_gl_scanout_texture  = qemu_spice_gl_scanout_texture,
     .dpy_gl_scanout_dmabuf   = qemu_spice_gl_scanout_dmabuf,
@@ -1118,6 +1114,13 @@ static const DisplayChangeListenerOps display_listener_gl_ops = {
     .dpy_gl_update           = qemu_spice_gl_update,
 };
 
+static const DisplayGLCtxOps gl_ctx_ops = {
+    .compatible_dcl          = &display_listener_gl_ops,
+    .dpy_gl_ctx_create       = qemu_spice_gl_create_context,
+    .dpy_gl_ctx_destroy      = qemu_egl_destroy_context,
+    .dpy_gl_ctx_make_current = qemu_egl_make_context_current,
+};
+
 #endif /* HAVE_SPICE_GL */
 
 static void qemu_spice_display_init_one(QemuConsole *con)
@@ -1130,6 +1133,7 @@ static void qemu_spice_display_init_one(QemuConsole *con)
 #ifdef HAVE_SPICE_GL
     if (spice_opengl) {
         ssd->dcl.ops = &display_listener_gl_ops;
+        ssd->dgc.ops = &gl_ctx_ops;
         ssd->gl_unblock_bh = qemu_bh_new(qemu_spice_gl_unblock_bh, ssd);
         ssd->gl_unblock_timer = timer_new_ms(QEMU_CLOCK_REALTIME,
                                              qemu_spice_gl_block_timer, ssd);
@@ -1156,7 +1160,7 @@ static void qemu_spice_display_init_one(QemuConsole *con)
     qemu_spice_create_host_memslot(ssd);
 
     if (spice_opengl) {
-        qemu_console_set_display_gl_ctx(con, &ssd->dcl);
+        qemu_console_set_display_gl_ctx(con, &ssd->dgc);
     }
     register_displaychangelistener(&ssd->dcl);
 }
-- 
2.33.0.721.g106298f7f9



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v2 18/37] ui: move qemu_spice_fill_device_address to ui/util.c
  2021-10-09 21:08 [PATCH v2 00/37] Add D-Bus display backend marcandre.lureau
                   ` (16 preceding siblings ...)
  2021-10-09 21:08 ` [PATCH v2 17/37] ui: split the GL context in a different object marcandre.lureau
@ 2021-10-09 21:08 ` marcandre.lureau
  2021-10-09 21:08 ` [PATCH v2 19/37] console: save current scanout details marcandre.lureau
                   ` (19 subsequent siblings)
  37 siblings, 0 replies; 54+ messages in thread
From: marcandre.lureau @ 2021-10-09 21:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, kraxel

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Other backends can use it.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/ui/console.h       |  6 +++
 include/ui/spice-display.h |  4 --
 hw/display/qxl.c           |  7 +++-
 ui/spice-core.c            | 50 -------------------------
 ui/spice-display.c         |  5 ++-
 ui/util.c                  | 75 ++++++++++++++++++++++++++++++++++++++
 ui/meson.build             |  1 +
 7 files changed, 92 insertions(+), 56 deletions(-)
 create mode 100644 ui/util.c

diff --git a/include/ui/console.h b/include/ui/console.h
index 7a35c4fc6a..b23ae283be 100644
--- a/include/ui/console.h
+++ b/include/ui/console.h
@@ -490,4 +490,10 @@ int index_from_key(const char *key, size_t key_length);
 int udmabuf_fd(void);
 #endif
 
+/* util.c */
+bool qemu_console_fill_device_address(QemuConsole *con,
+                                      char *device_address,
+                                      size_t size,
+                                      Error **errp);
+
 #endif
diff --git a/include/ui/spice-display.h b/include/ui/spice-display.h
index a2fbf62c52..e271e011da 100644
--- a/include/ui/spice-display.h
+++ b/include/ui/spice-display.h
@@ -184,8 +184,4 @@ void qemu_spice_display_start(void);
 void qemu_spice_display_stop(void);
 int qemu_spice_display_is_running(SimpleSpiceDisplay *ssd);
 
-bool qemu_spice_fill_device_address(QemuConsole *con,
-                                    char *device_address,
-                                    size_t size);
-
 #endif
diff --git a/hw/display/qxl.c b/hw/display/qxl.c
index 29c80b4289..e2d6e317da 100644
--- a/hw/display/qxl.c
+++ b/hw/display/qxl.c
@@ -2171,12 +2171,17 @@ static void qxl_realize_common(PCIQXLDevice *qxl, Error **errp)
     }
 
 #if SPICE_SERVER_VERSION >= 0x000e02 /* release 0.14.2 */
+    Error *err = NULL;
     char device_address[256] = "";
-    if (qemu_spice_fill_device_address(qxl->vga.con, device_address, 256)) {
+    if (qemu_console_fill_device_address(qxl->vga.con,
+                                         device_address, sizeof(device_address),
+                                         &err)) {
         spice_qxl_set_device_info(&qxl->ssd.qxl,
                                   device_address,
                                   0,
                                   qxl->max_outputs);
+    } else {
+        error_report_err(err);
     }
 #endif
 
diff --git a/ui/spice-core.c b/ui/spice-core.c
index 31974b8d6c..c3ac20ad43 100644
--- a/ui/spice-core.c
+++ b/ui/spice-core.c
@@ -884,56 +884,6 @@ bool qemu_spice_have_display_interface(QemuConsole *con)
     return false;
 }
 
-/*
- * Recursively (in reverse order) appends addresses of PCI devices as it moves
- * up in the PCI hierarchy.
- *
- * @returns true on success, false when the buffer wasn't large enough
- */
-static bool append_pci_address(char *buf, size_t buf_size, const PCIDevice *pci)
-{
-    PCIBus *bus = pci_get_bus(pci);
-    /*
-     * equivalent to if (!pci_bus_is_root(bus)), but the function is not built
-     * with PCI_CONFIG=n, avoid using an #ifdef by checking directly
-     */
-    if (bus->parent_dev != NULL) {
-        append_pci_address(buf, buf_size, bus->parent_dev);
-    }
-
-    size_t len = strlen(buf);
-    ssize_t written = snprintf(buf + len, buf_size - len, "/%02x.%x",
-        PCI_SLOT(pci->devfn), PCI_FUNC(pci->devfn));
-
-    return written > 0 && written < buf_size - len;
-}
-
-bool qemu_spice_fill_device_address(QemuConsole *con,
-                                    char *device_address,
-                                    size_t size)
-{
-    DeviceState *dev = DEVICE(object_property_get_link(OBJECT(con),
-                                                       "device",
-                                                       &error_abort));
-    PCIDevice *pci = (PCIDevice *) object_dynamic_cast(OBJECT(dev),
-                                                       TYPE_PCI_DEVICE);
-
-    if (pci == NULL) {
-        warn_report("Setting device address of a display device to SPICE: "
-                    "Not a PCI device.");
-        return false;
-    }
-
-    strncpy(device_address, "pci/0000", size);
-    if (!append_pci_address(device_address, size, pci)) {
-        warn_report("Setting device address of a display device to SPICE: "
-            "Too many PCI devices in the chain.");
-        return false;
-    }
-
-    return true;
-}
-
 int qemu_spice_add_display_interface(QXLInstance *qxlin, QemuConsole *con)
 {
     if (g_slist_find(spice_consoles, con)) {
diff --git a/ui/spice-display.c b/ui/spice-display.c
index 798e0f6167..1043f47f94 100644
--- a/ui/spice-display.c
+++ b/ui/spice-display.c
@@ -1148,12 +1148,15 @@ static void qemu_spice_display_init_one(QemuConsole *con)
     qemu_spice_add_display_interface(&ssd->qxl, con);
 
 #if SPICE_SERVER_VERSION >= 0x000e02 /* release 0.14.2 */
+    Error *err = NULL;
     char device_address[256] = "";
-    if (qemu_spice_fill_device_address(con, device_address, 256)) {
+    if (qemu_console_fill_device_address(con, device_address, 256, &err)) {
         spice_qxl_set_device_info(&ssd->qxl,
                                   device_address,
                                   qemu_console_get_head(con),
                                   1);
+    } else {
+        error_report_err(err);
     }
 #endif
 
diff --git a/ui/util.c b/ui/util.c
new file mode 100644
index 0000000000..7e8fc1ea53
--- /dev/null
+++ b/ui/util.c
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2021 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+
+#include "hw/pci/pci.h"
+#include "hw/pci/pci_bus.h"
+#include "qapi/error.h"
+#include "ui/console.h"
+
+/*
+ * Recursively (in reverse order) appends addresses of PCI devices as it moves
+ * up in the PCI hierarchy.
+ *
+ * @returns true on success, false when the buffer wasn't large enough
+ */
+static bool append_pci_address(char *buf, size_t buf_size, const PCIDevice *pci)
+{
+    PCIBus *bus = pci_get_bus(pci);
+    /*
+     * equivalent to if (!pci_bus_is_root(bus)), but the function is not built
+     * with PCI_CONFIG=n, avoid using an #ifdef by checking directly
+     */
+    if (bus->parent_dev != NULL) {
+        append_pci_address(buf, buf_size, bus->parent_dev);
+    }
+
+    size_t len = strlen(buf);
+    ssize_t written = snprintf(buf + len, buf_size - len, "/%02x.%x",
+        PCI_SLOT(pci->devfn), PCI_FUNC(pci->devfn));
+
+    return written > 0 && written < buf_size - len;
+}
+
+bool qemu_console_fill_device_address(QemuConsole *con,
+                                      char *device_address,
+                                      size_t size,
+                                      Error **errp)
+{
+    ERRP_GUARD();
+    DeviceState *dev = DEVICE(object_property_get_link(OBJECT(con),
+                                                       "device",
+                                                       &error_abort));
+    PCIDevice *pci = (PCIDevice *) object_dynamic_cast(OBJECT(dev),
+                                                       TYPE_PCI_DEVICE);
+
+    if (pci == NULL) {
+        error_setg(errp, "Setting device address of a display device: "
+                   "Not a PCI device.");
+        return false;
+    }
+
+    strncpy(device_address, "pci/0000", size);
+    if (!append_pci_address(device_address, size, pci)) {
+        error_setg(errp, "Setting device address of a display device: "
+                   "Too many PCI devices in the chain.");
+        return false;
+    }
+
+    return true;
+}
diff --git a/ui/meson.build b/ui/meson.build
index ee8ef27714..a9df5b911e 100644
--- a/ui/meson.build
+++ b/ui/meson.build
@@ -12,6 +12,7 @@ softmmu_ss.add(files(
   'kbd-state.c',
   'keymaps.c',
   'qemu-pixman.c',
+  'util.c',
 ))
 softmmu_ss.add([spice_headers, files('spice-module.c')])
 softmmu_ss.add(when: spice_protocol, if_true: files('vdagent.c'))
-- 
2.33.0.721.g106298f7f9



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v2 19/37] console: save current scanout details
  2021-10-09 21:08 [PATCH v2 00/37] Add D-Bus display backend marcandre.lureau
                   ` (17 preceding siblings ...)
  2021-10-09 21:08 ` [PATCH v2 18/37] ui: move qemu_spice_fill_device_address to ui/util.c marcandre.lureau
@ 2021-10-09 21:08 ` marcandre.lureau
  2022-01-11  3:29   ` Akihiko Odaki
  2021-10-09 21:08 ` [PATCH v2 20/37] scripts: teach modinfo to skip non-C sources marcandre.lureau
                   ` (18 subsequent siblings)
  37 siblings, 1 reply; 54+ messages in thread
From: marcandre.lureau @ 2021-10-09 21:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, kraxel

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Add a new DisplayScanout structure to save the current scanout details.
This allows to attach later UI backends and set the scanout.

Introduce displaychangelistener_display_console() helper function to
handle the dpy_gfx_switch/gl_scanout() & dpy_gfx_update() calls.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/ui/console.h |  27 +++++++
 ui/console.c         | 165 +++++++++++++++++++++++++++++--------------
 2 files changed, 138 insertions(+), 54 deletions(-)

diff --git a/include/ui/console.h b/include/ui/console.h
index b23ae283be..ab55d71894 100644
--- a/include/ui/console.h
+++ b/include/ui/console.h
@@ -108,6 +108,17 @@ struct QemuConsoleClass {
 #define QEMU_ALLOCATED_FLAG     0x01
 #define QEMU_PLACEHOLDER_FLAG   0x02
 
+typedef struct ScanoutTexture {
+    uint32_t backing_id;
+    bool backing_y_0_top;
+    uint32_t backing_width;
+    uint32_t backing_height;
+    uint32_t x;
+    uint32_t y;
+    uint32_t width;
+    uint32_t height;
+} ScanoutTexture;
+
 typedef struct DisplaySurface {
     pixman_format_code_t format;
     pixman_image_t *image;
@@ -173,6 +184,22 @@ typedef struct QemuDmaBuf {
     bool      allow_fences;
 } QemuDmaBuf;
 
+enum display_scanout {
+    SCANOUT_NONE,
+    SCANOUT_SURFACE,
+    SCANOUT_TEXTURE,
+    SCANOUT_DMABUF,
+};
+
+typedef struct DisplayScanout {
+    enum display_scanout kind;
+    union {
+        /* DisplaySurface *surface; is kept in QemuConsole */
+        ScanoutTexture texture;
+        QemuDmaBuf *dmabuf;
+    };
+} DisplayScanout;
+
 typedef struct DisplayState DisplayState;
 typedef struct DisplayGLCtx DisplayGLCtx;
 
diff --git a/ui/console.c b/ui/console.c
index e5a2c84dd9..a1c6a78523 100644
--- a/ui/console.c
+++ b/ui/console.c
@@ -126,6 +126,7 @@ struct QemuConsole {
     console_type_t console_type;
     DisplayState *ds;
     DisplaySurface *surface;
+    DisplayScanout scanout;
     int dcls;
     DisplayGLCtx *gl;
     int gl_block;
@@ -197,6 +198,7 @@ static void dpy_refresh(DisplayState *s);
 static DisplayState *get_alloc_displaystate(void);
 static void text_console_update_cursor_timer(void);
 static void text_console_update_cursor(void *opaque);
+static bool displaychangelistener_has_dmabuf(DisplayChangeListener *dcl);
 
 static void gui_update(void *opaque)
 {
@@ -532,6 +534,8 @@ static void text_console_resize(QemuConsole *s)
     TextCell *cells, *c, *c1;
     int w1, x, y, last_width;
 
+    assert(s->scanout.kind == SCANOUT_SURFACE);
+
     last_width = s->width;
     s->width = surface_width(s->surface) / FONT_WIDTH;
     s->height = surface_height(s->surface) / FONT_HEIGHT;
@@ -1103,6 +1107,48 @@ static void console_putchar(QemuConsole *s, int ch)
     }
 }
 
+static void displaychangelistener_display_console(DisplayChangeListener *dcl,
+                                                  QemuConsole *con)
+{
+    static const char nodev[] =
+        "This VM has no graphic display device.";
+    static DisplaySurface *dummy;
+
+    if (!con) {
+        if (!dcl->ops->dpy_gfx_switch) {
+            return;
+        }
+        if (!dummy) {
+            dummy = qemu_create_placeholder_surface(640, 480, nodev);
+        }
+        dcl->ops->dpy_gfx_switch(dcl, dummy);
+        return;
+    }
+
+    if (con->scanout.kind == SCANOUT_DMABUF &&
+        displaychangelistener_has_dmabuf(dcl)) {
+        dcl->ops->dpy_gl_scanout_dmabuf(dcl, con->scanout.dmabuf);
+    } else if (con->scanout.kind == SCANOUT_TEXTURE &&
+               dcl->ops->dpy_gl_scanout_texture) {
+        dcl->ops->dpy_gl_scanout_texture(dcl,
+                                         con->scanout.texture.backing_id,
+                                         con->scanout.texture.backing_y_0_top,
+                                         con->scanout.texture.backing_width,
+                                         con->scanout.texture.backing_height,
+                                         con->scanout.texture.x,
+                                         con->scanout.texture.y,
+                                         con->scanout.texture.width,
+                                         con->scanout.texture.height);
+    } else if (con->scanout.kind == SCANOUT_SURFACE &&
+               dcl->ops->dpy_gfx_switch) {
+        dcl->ops->dpy_gfx_switch(dcl, con->surface);
+    }
+
+    dcl->ops->dpy_gfx_update(dcl, 0, 0,
+                             qemu_console_get_width(con, 0),
+                             qemu_console_get_height(con, 0));
+}
+
 void console_select(unsigned int index)
 {
     DisplayChangeListener *dcl;
@@ -1119,13 +1165,7 @@ void console_select(unsigned int index)
                 if (dcl->con != NULL) {
                     continue;
                 }
-                if (dcl->ops->dpy_gfx_switch) {
-                    dcl->ops->dpy_gfx_switch(dcl, s->surface);
-                }
-            }
-            if (s->surface) {
-                dpy_gfx_update(s, 0, 0, surface_width(s->surface),
-                               surface_height(s->surface));
+                displaychangelistener_display_console(dcl, s);
             }
         }
         if (ds->have_text) {
@@ -1538,9 +1578,6 @@ static bool dpy_gl_compatible_with(QemuConsole *con, DisplayChangeListener *dcl)
 
 void register_displaychangelistener(DisplayChangeListener *dcl)
 {
-    static const char nodev[] =
-        "This VM has no graphic display device.";
-    static DisplaySurface *dummy;
     QemuConsole *con;
 
     assert(!dcl->ds);
@@ -1565,16 +1602,7 @@ void register_displaychangelistener(DisplayChangeListener *dcl)
     } else {
         con = active_console;
     }
-    if (dcl->ops->dpy_gfx_switch) {
-        if (con) {
-            dcl->ops->dpy_gfx_switch(dcl, con->surface);
-        } else {
-            if (!dummy) {
-                dummy = qemu_create_placeholder_surface(640, 480, nodev);
-            }
-            dcl->ops->dpy_gfx_switch(dcl, dummy);
-        }
-    }
+    displaychangelistener_display_console(dcl, con);
     text_console_update_cursor(NULL);
 }
 
@@ -1655,13 +1683,9 @@ void dpy_gfx_update(QemuConsole *con, int x, int y, int w, int h)
 {
     DisplayState *s = con->ds;
     DisplayChangeListener *dcl;
-    int width = w;
-    int height = h;
+    int width = qemu_console_get_width(con, x + w);
+    int height = qemu_console_get_height(con, y + h);
 
-    if (con->surface) {
-        width = surface_width(con->surface);
-        height = surface_height(con->surface);
-    }
     x = MAX(x, 0);
     y = MAX(y, 0);
     x = MIN(x, width);
@@ -1684,12 +1708,10 @@ void dpy_gfx_update(QemuConsole *con, int x, int y, int w, int h)
 
 void dpy_gfx_update_full(QemuConsole *con)
 {
-    if (!con->surface) {
-        return;
-    }
-    dpy_gfx_update(con, 0, 0,
-                   surface_width(con->surface),
-                   surface_height(con->surface));
+    int w = qemu_console_get_width(con, 0);
+    int h = qemu_console_get_height(con, 0);
+
+    dpy_gfx_update(con, 0, 0, w, h);
 }
 
 void dpy_gfx_replace_surface(QemuConsole *con,
@@ -1716,6 +1738,7 @@ void dpy_gfx_replace_surface(QemuConsole *con,
 
     assert(old_surface != surface);
 
+    con->scanout.kind = SCANOUT_SURFACE;
     con->surface = surface;
     QLIST_FOREACH(dcl, &s->listeners, next) {
         if (con != (dcl->con ? dcl->con : active_console)) {
@@ -1891,6 +1914,9 @@ void dpy_gl_scanout_disable(QemuConsole *con)
     DisplayState *s = con->ds;
     DisplayChangeListener *dcl;
 
+    if (con->scanout.kind != SCANOUT_SURFACE) {
+        con->scanout.kind = SCANOUT_NONE;
+    }
     QLIST_FOREACH(dcl, &s->listeners, next) {
         dcl->ops->dpy_gl_scanout_disable(dcl);
     }
@@ -1907,6 +1933,11 @@ void dpy_gl_scanout_texture(QemuConsole *con,
     DisplayState *s = con->ds;
     DisplayChangeListener *dcl;
 
+    con->scanout.kind = SCANOUT_TEXTURE;
+    con->scanout.texture = (ScanoutTexture) {
+        backing_id, backing_y_0_top, backing_width, backing_height,
+        x, y, width, height
+    };
     QLIST_FOREACH(dcl, &s->listeners, next) {
         dcl->ops->dpy_gl_scanout_texture(dcl, backing_id,
                                          backing_y_0_top,
@@ -1921,6 +1952,8 @@ void dpy_gl_scanout_dmabuf(QemuConsole *con,
     DisplayState *s = con->ds;
     DisplayChangeListener *dcl;
 
+    con->scanout.kind = SCANOUT_DMABUF;
+    con->scanout.dmabuf = dmabuf;
     QLIST_FOREACH(dcl, &s->listeners, next) {
         dcl->ops->dpy_gl_scanout_dmabuf(dcl, dmabuf);
     }
@@ -2047,10 +2080,8 @@ QemuConsole *graphic_console_init(DeviceState *dev, uint32_t head,
     s = qemu_console_lookup_unused();
     if (s) {
         trace_console_gfx_reuse(s->index);
-        if (s->surface) {
-            width = surface_width(s->surface);
-            height = surface_height(s->surface);
-        }
+        width = qemu_console_get_width(s, 0);
+        height = qemu_console_get_height(s, 0);
     } else {
         trace_console_gfx_new();
         s = new_console(ds, GRAPHIC_CONSOLE, head);
@@ -2079,13 +2110,8 @@ void graphic_console_close(QemuConsole *con)
     static const char unplugged[] =
         "Guest display has been unplugged";
     DisplaySurface *surface;
-    int width = 640;
-    int height = 480;
-
-    if (con->surface) {
-        width = surface_width(con->surface);
-        height = surface_height(con->surface);
-    }
+    int width = qemu_console_get_width(con, 640);
+    int height = qemu_console_get_height(con, 480);
 
     trace_console_gfx_close(con->index);
     object_property_set_link(OBJECT(con), "device", NULL, &error_abort);
@@ -2237,7 +2263,19 @@ int qemu_console_get_width(QemuConsole *con, int fallback)
     if (con == NULL) {
         con = active_console;
     }
-    return con ? surface_width(con->surface) : fallback;
+    if (con == NULL) {
+        return fallback;
+    }
+    switch (con->scanout.kind) {
+    case SCANOUT_DMABUF:
+        return con->scanout.dmabuf->width;
+    case SCANOUT_TEXTURE:
+        return con->scanout.texture.width;
+    case SCANOUT_SURFACE:
+        return surface_width(con->surface);
+    default:
+        return fallback;
+    }
 }
 
 int qemu_console_get_height(QemuConsole *con, int fallback)
@@ -2245,7 +2283,19 @@ int qemu_console_get_height(QemuConsole *con, int fallback)
     if (con == NULL) {
         con = active_console;
     }
-    return con ? surface_height(con->surface) : fallback;
+    if (con == NULL) {
+        return fallback;
+    }
+    switch (con->scanout.kind) {
+    case SCANOUT_DMABUF:
+        return con->scanout.dmabuf->height;
+    case SCANOUT_TEXTURE:
+        return con->scanout.texture.height;
+    case SCANOUT_SURFACE:
+        return surface_height(con->surface);
+    default:
+        return fallback;
+    }
 }
 
 static void vc_chr_set_echo(Chardev *chr, bool echo)
@@ -2305,12 +2355,13 @@ static void text_console_do_init(Chardev *chr, DisplayState *ds)
     s->total_height = DEFAULT_BACKSCROLL;
     s->x = 0;
     s->y = 0;
-    if (!s->surface) {
-        if (active_console && active_console->surface) {
-            g_width = surface_width(active_console->surface);
-            g_height = surface_height(active_console->surface);
+    if (s->scanout.kind != SCANOUT_SURFACE) {
+        if (active_console && active_console->scanout.kind == SCANOUT_SURFACE) {
+            g_width = qemu_console_get_width(active_console, g_width);
+            g_height = qemu_console_get_height(active_console, g_height);
         }
         s->surface = qemu_create_displaysurface(g_width, g_height);
+        s->scanout.kind = SCANOUT_SURFACE;
     }
 
     s->hw_ops = &text_console_ops;
@@ -2369,6 +2420,7 @@ static void vc_chr_open(Chardev *chr,
         s = new_console(NULL, TEXT_CONSOLE, 0);
     } else {
         s = new_console(NULL, TEXT_CONSOLE_FIXED_SIZE, 0);
+        s->scanout.kind = SCANOUT_SURFACE;
         s->surface = qemu_create_displaysurface(width, height);
     }
 
@@ -2392,13 +2444,13 @@ static void vc_chr_open(Chardev *chr,
 
 void qemu_console_resize(QemuConsole *s, int width, int height)
 {
-    DisplaySurface *surface;
+    DisplaySurface *surface = qemu_console_surface(s);
 
     assert(s->console_type == GRAPHIC_CONSOLE);
 
-    if (s->surface && (s->surface->flags & QEMU_ALLOCATED_FLAG) &&
-        pixman_image_get_width(s->surface->image) == width &&
-        pixman_image_get_height(s->surface->image) == height) {
+    if (surface && (surface->flags & QEMU_ALLOCATED_FLAG) &&
+        pixman_image_get_width(surface->image) == width &&
+        pixman_image_get_height(surface->image) == height) {
         return;
     }
 
@@ -2408,7 +2460,12 @@ void qemu_console_resize(QemuConsole *s, int width, int height)
 
 DisplaySurface *qemu_console_surface(QemuConsole *console)
 {
-    return console->surface;
+    switch (console->scanout.kind) {
+    case SCANOUT_SURFACE:
+        return console->surface;
+    default:
+        return NULL;
+    }
 }
 
 PixelFormat qemu_default_pixelformat(int bpp)
-- 
2.33.0.721.g106298f7f9



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v2 20/37] scripts: teach modinfo to skip non-C sources
  2021-10-09 21:08 [PATCH v2 00/37] Add D-Bus display backend marcandre.lureau
                   ` (18 preceding siblings ...)
  2021-10-09 21:08 ` [PATCH v2 19/37] console: save current scanout details marcandre.lureau
@ 2021-10-09 21:08 ` marcandre.lureau
  2021-10-09 21:08 ` [PATCH v2 21/37] docs/sphinx: add sphinx modules to include D-Bus documentation marcandre.lureau
                   ` (17 subsequent siblings)
  37 siblings, 0 replies; 54+ messages in thread
From: marcandre.lureau @ 2021-10-09 21:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, kraxel

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 scripts/modinfo-collect.py | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/scripts/modinfo-collect.py b/scripts/modinfo-collect.py
index 4acb188c3e..61b90688c6 100755
--- a/scripts/modinfo-collect.py
+++ b/scripts/modinfo-collect.py
@@ -51,6 +51,9 @@ def main(args):
     with open('compile_commands.json') as f:
         compile_commands = json.load(f)
     for src in args:
+        if not src.endswith('.c'):
+            print("MODINFO_DEBUG skip %s" % src)
+            continue
         print("MODINFO_DEBUG src %s" % src)
         command = find_command(src, target, compile_commands)
         cmdline = process_command(src, command)
-- 
2.33.0.721.g106298f7f9



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v2 21/37] docs/sphinx: add sphinx modules to include D-Bus documentation
  2021-10-09 21:08 [PATCH v2 00/37] Add D-Bus display backend marcandre.lureau
                   ` (19 preceding siblings ...)
  2021-10-09 21:08 ` [PATCH v2 20/37] scripts: teach modinfo to skip non-C sources marcandre.lureau
@ 2021-10-09 21:08 ` marcandre.lureau
  2021-10-09 21:08 ` [PATCH v2 22/37] backends: move dbus-vmstate1.xml to backends/ marcandre.lureau
                   ` (16 subsequent siblings)
  37 siblings, 0 replies; 54+ messages in thread
From: marcandre.lureau @ 2021-10-09 21:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, kraxel

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Add a new dbus-doc directive to import D-Bus interfaces documentation
from the introspection XML. The comments annotations follow the
gtkdoc/kerneldoc style, and should be formatted with reST.

Note: I realize after the fact that I was implementing those modules
with sphinx 4, and that we have much lower requirements. Instead of
lowering the features and code (removing type annotations etc), I
propose to have a warning in the documentation when the D-Bus modules
can't be used, and point to the source XML file in that case.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 docs/conf.py               |   8 +
 docs/sphinx/dbusdoc.py     | 166 +++++++++++++++
 docs/sphinx/dbusdomain.py  | 406 +++++++++++++++++++++++++++++++++++++
 docs/sphinx/dbusparser.py  | 373 ++++++++++++++++++++++++++++++++++
 docs/sphinx/fakedbusdoc.py |  25 +++
 5 files changed, 978 insertions(+)
 create mode 100644 docs/sphinx/dbusdoc.py
 create mode 100644 docs/sphinx/dbusdomain.py
 create mode 100644 docs/sphinx/dbusparser.py
 create mode 100644 docs/sphinx/fakedbusdoc.py

diff --git a/docs/conf.py b/docs/conf.py
index ff6e92c6e2..71e2369026 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -73,6 +73,12 @@
 # ones.
 extensions = ['kerneldoc', 'qmp_lexer', 'hxtool', 'depfile', 'qapidoc']
 
+if sphinx.version_info[:3] > (4, 0, 0):
+    tags.add('sphinx4')
+    extensions += ['dbusdoc']
+else:
+    extensions += ['fakedbusdoc']
+
 # Add any paths that contain templates here, relative to this directory.
 templates_path = ['_templates']
 
@@ -301,3 +307,5 @@
 kerneldoc_srctree = os.path.join(qemu_docdir, '..')
 hxtool_srctree = os.path.join(qemu_docdir, '..')
 qapidoc_srctree = os.path.join(qemu_docdir, '..')
+dbusdoc_srctree = os.path.join(qemu_docdir, '..')
+dbus_index_common_prefix = ["org.qemu."]
diff --git a/docs/sphinx/dbusdoc.py b/docs/sphinx/dbusdoc.py
new file mode 100644
index 0000000000..be284ed08f
--- /dev/null
+++ b/docs/sphinx/dbusdoc.py
@@ -0,0 +1,166 @@
+# D-Bus XML documentation extension
+#
+# Copyright (C) 2021, Red Hat Inc.
+#
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# Author: Marc-André Lureau <marcandre.lureau@redhat.com>
+"""dbus-doc is a Sphinx extension that provides documentation from D-Bus XML."""
+
+import os
+import re
+from typing import (
+    TYPE_CHECKING,
+    Any,
+    Callable,
+    Dict,
+    Iterator,
+    List,
+    Optional,
+    Sequence,
+    Set,
+    Tuple,
+    Type,
+    TypeVar,
+    Union,
+)
+
+import sphinx
+from docutils import nodes
+from docutils.nodes import Element, Node
+from docutils.parsers.rst import Directive, directives
+from docutils.parsers.rst.states import RSTState
+from docutils.statemachine import StringList, ViewList
+from sphinx.application import Sphinx
+from sphinx.errors import ExtensionError
+from sphinx.util import logging
+from sphinx.util.docstrings import prepare_docstring
+from sphinx.util.docutils import SphinxDirective, switch_source_input
+from sphinx.util.nodes import nested_parse_with_titles
+
+import dbusdomain
+from dbusparser import parse_dbus_xml
+
+logger = logging.getLogger(__name__)
+
+__version__ = "1.0"
+
+
+class DBusDoc:
+    def __init__(self, sphinx_directive, dbusfile):
+        self._cur_doc = None
+        self._sphinx_directive = sphinx_directive
+        self._dbusfile = dbusfile
+        self._top_node = nodes.section()
+        self.result = StringList()
+        self.indent = ""
+
+    def add_line(self, line: str, *lineno: int) -> None:
+        """Append one line of generated reST to the output."""
+        if line.strip():  # not a blank line
+            self.result.append(self.indent + line, self._dbusfile, *lineno)
+        else:
+            self.result.append("", self._dbusfile, *lineno)
+
+    def add_method(self, method):
+        self.add_line(f".. dbus:method:: {method.name}")
+        self.add_line("")
+        self.indent += "   "
+        for arg in method.in_args:
+            self.add_line(f":arg {arg.signature} {arg.name}: {arg.doc_string}")
+        for arg in method.out_args:
+            self.add_line(f":ret {arg.signature} {arg.name}: {arg.doc_string}")
+        self.add_line("")
+        for line in prepare_docstring("\n" + method.doc_string):
+            self.add_line(line)
+        self.indent = self.indent[:-3]
+
+    def add_signal(self, signal):
+        self.add_line(f".. dbus:signal:: {signal.name}")
+        self.add_line("")
+        self.indent += "   "
+        for arg in signal.args:
+            self.add_line(f":arg {arg.signature} {arg.name}: {arg.doc_string}")
+        self.add_line("")
+        for line in prepare_docstring("\n" + signal.doc_string):
+            self.add_line(line)
+        self.indent = self.indent[:-3]
+
+    def add_property(self, prop):
+        self.add_line(f".. dbus:property:: {prop.name}")
+        self.indent += "   "
+        self.add_line(f":type: {prop.signature}")
+        access = {"read": "readonly", "write": "writeonly", "readwrite": "readwrite"}[
+            prop.access
+        ]
+        self.add_line(f":{access}:")
+        if prop.emits_changed_signal:
+            self.add_line(f":emits-changed: yes")
+        self.add_line("")
+        for line in prepare_docstring("\n" + prop.doc_string):
+            self.add_line(line)
+        self.indent = self.indent[:-3]
+
+    def add_interface(self, iface):
+        self.add_line(f".. dbus:interface:: {iface.name}")
+        self.add_line("")
+        self.indent += "   "
+        for line in prepare_docstring("\n" + iface.doc_string):
+            self.add_line(line)
+        for method in iface.methods:
+            self.add_method(method)
+        for sig in iface.signals:
+            self.add_signal(sig)
+        for prop in iface.properties:
+            self.add_property(prop)
+        self.indent = self.indent[:-3]
+
+
+def parse_generated_content(state: RSTState, content: StringList) -> List[Node]:
+    """Parse a generated content by Documenter."""
+    with switch_source_input(state, content):
+        node = nodes.paragraph()
+        node.document = state.document
+        state.nested_parse(content, 0, node)
+
+        return node.children
+
+
+class DBusDocDirective(SphinxDirective):
+    """Extract documentation from the specified D-Bus XML file"""
+
+    has_content = True
+    required_arguments = 1
+    optional_arguments = 0
+    final_argument_whitespace = True
+
+    def run(self):
+        reporter = self.state.document.reporter
+
+        try:
+            source, lineno = reporter.get_source_and_line(self.lineno)  # type: ignore
+        except AttributeError:
+            source, lineno = (None, None)
+
+        logger.debug("[dbusdoc] %s:%s: input:\n%s", source, lineno, self.block_text)
+
+        env = self.state.document.settings.env
+        dbusfile = env.config.qapidoc_srctree + "/" + self.arguments[0]
+        with open(dbusfile, "rb") as f:
+            xml_data = f.read()
+        xml = parse_dbus_xml(xml_data)
+        doc = DBusDoc(self, dbusfile)
+        for iface in xml:
+            doc.add_interface(iface)
+
+        result = parse_generated_content(self.state, doc.result)
+        return result
+
+
+def setup(app: Sphinx) -> Dict[str, Any]:
+    """Register dbus-doc directive with Sphinx"""
+    app.add_config_value("dbusdoc_srctree", None, "env")
+    app.add_directive("dbus-doc", DBusDocDirective)
+    dbusdomain.setup(app)
+
+    return dict(version=__version__, parallel_read_safe=True, parallel_write_safe=True)
diff --git a/docs/sphinx/dbusdomain.py b/docs/sphinx/dbusdomain.py
new file mode 100644
index 0000000000..2ea95af623
--- /dev/null
+++ b/docs/sphinx/dbusdomain.py
@@ -0,0 +1,406 @@
+# D-Bus sphinx domain extension
+#
+# Copyright (C) 2021, Red Hat Inc.
+#
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# Author: Marc-André Lureau <marcandre.lureau@redhat.com>
+
+from typing import (
+    Any,
+    Dict,
+    Iterable,
+    Iterator,
+    List,
+    NamedTuple,
+    Optional,
+    Tuple,
+    cast,
+)
+
+from docutils import nodes
+from docutils.nodes import Element, Node
+from docutils.parsers.rst import directives
+from sphinx import addnodes
+from sphinx.addnodes import desc_signature, pending_xref
+from sphinx.directives import ObjectDescription
+from sphinx.domains import Domain, Index, IndexEntry, ObjType
+from sphinx.locale import _
+from sphinx.roles import XRefRole
+from sphinx.util import nodes as node_utils
+from sphinx.util.docfields import Field, TypedField
+from sphinx.util.typing import OptionSpec
+
+
+class DBusDescription(ObjectDescription[str]):
+    """Base class for DBus objects"""
+
+    option_spec: OptionSpec = ObjectDescription.option_spec.copy()
+    option_spec.update(
+        {
+            "deprecated": directives.flag,
+        }
+    )
+
+    def get_index_text(self, modname: str, name: str) -> str:
+        """Return the text for the index entry of the object."""
+        raise NotImplementedError("must be implemented in subclasses")
+
+    def add_target_and_index(
+        self, name: str, sig: str, signode: desc_signature
+    ) -> None:
+        ifacename = self.env.ref_context.get("dbus:interface")
+        node_id = name
+        if ifacename:
+            node_id = f"{ifacename}.{node_id}"
+
+        signode["names"].append(name)
+        signode["ids"].append(node_id)
+
+        if "noindexentry" not in self.options:
+            indextext = self.get_index_text(ifacename, name)
+            if indextext:
+                self.indexnode["entries"].append(
+                    ("single", indextext, node_id, "", None)
+                )
+
+        domain = cast(DBusDomain, self.env.get_domain("dbus"))
+        domain.note_object(name, self.objtype, node_id, location=signode)
+
+
+class DBusInterface(DBusDescription):
+    """
+    Implementation of ``dbus:interface``.
+    """
+
+    def get_index_text(self, ifacename: str, name: str) -> str:
+        return ifacename
+
+    def before_content(self) -> None:
+        self.env.ref_context["dbus:interface"] = self.arguments[0]
+
+    def after_content(self) -> None:
+        self.env.ref_context.pop("dbus:interface")
+
+    def handle_signature(self, sig: str, signode: desc_signature) -> str:
+        signode += addnodes.desc_annotation("interface ", "interface ")
+        signode += addnodes.desc_name(sig, sig)
+        return sig
+
+    def run(self) -> List[Node]:
+        _, node = super().run()
+        name = self.arguments[0]
+        section = nodes.section(ids=[name + "-section"])
+        section += nodes.title(name, "%s interface" % name)
+        section += node
+        return [self.indexnode, section]
+
+
+class DBusMember(DBusDescription):
+
+    signal = False
+
+
+class DBusMethod(DBusMember):
+    """
+    Implementation of ``dbus:method``.
+    """
+
+    option_spec: OptionSpec = DBusMember.option_spec.copy()
+    option_spec.update(
+        {
+            "noreply": directives.flag,
+        }
+    )
+
+    doc_field_types: List[Field] = [
+        TypedField(
+            "arg",
+            label=_("Arguments"),
+            names=("arg",),
+            rolename="arg",
+            typerolename=None,
+            typenames=("argtype", "type"),
+        ),
+        TypedField(
+            "ret",
+            label=_("Returns"),
+            names=("ret",),
+            rolename="ret",
+            typerolename=None,
+            typenames=("rettype", "type"),
+        ),
+    ]
+
+    def get_index_text(self, ifacename: str, name: str) -> str:
+        return _("%s() (%s method)") % (name, ifacename)
+
+    def handle_signature(self, sig: str, signode: desc_signature) -> str:
+        params = addnodes.desc_parameterlist()
+        returns = addnodes.desc_parameterlist()
+
+        contentnode = addnodes.desc_content()
+        self.state.nested_parse(self.content, self.content_offset, contentnode)
+        for child in contentnode:
+            if isinstance(child, nodes.field_list):
+                for field in child:
+                    ty, sg, name = field[0].astext().split(None, 2)
+                    param = addnodes.desc_parameter()
+                    param += addnodes.desc_sig_keyword_type(sg, sg)
+                    param += addnodes.desc_sig_space()
+                    param += addnodes.desc_sig_name(name, name)
+                    if ty == "arg":
+                        params += param
+                    elif ty == "ret":
+                        returns += param
+
+        anno = "signal " if self.signal else "method "
+        signode += addnodes.desc_annotation(anno, anno)
+        signode += addnodes.desc_name(sig, sig)
+        signode += params
+        if not self.signal and "noreply" not in self.options:
+            ret = addnodes.desc_returns()
+            ret += returns
+            signode += ret
+
+        return sig
+
+
+class DBusSignal(DBusMethod):
+    """
+    Implementation of ``dbus:signal``.
+    """
+
+    doc_field_types: List[Field] = [
+        TypedField(
+            "arg",
+            label=_("Arguments"),
+            names=("arg",),
+            rolename="arg",
+            typerolename=None,
+            typenames=("argtype", "type"),
+        ),
+    ]
+    signal = True
+
+    def get_index_text(self, ifacename: str, name: str) -> str:
+        return _("%s() (%s signal)") % (name, ifacename)
+
+
+class DBusProperty(DBusMember):
+    """
+    Implementation of ``dbus:property``.
+    """
+
+    option_spec: OptionSpec = DBusMember.option_spec.copy()
+    option_spec.update(
+        {
+            "type": directives.unchanged,
+            "readonly": directives.flag,
+            "writeonly": directives.flag,
+            "readwrite": directives.flag,
+            "emits-changed": directives.unchanged,
+        }
+    )
+
+    doc_field_types: List[Field] = []
+
+    def get_index_text(self, ifacename: str, name: str) -> str:
+        return _("%s (%s property)") % (name, ifacename)
+
+    def transform_content(self, contentnode: addnodes.desc_content) -> None:
+        fieldlist = nodes.field_list()
+        access = None
+        if "readonly" in self.options:
+            access = _("read-only")
+        if "writeonly" in self.options:
+            access = _("write-only")
+        if "readwrite" in self.options:
+            access = _("read & write")
+        if access:
+            content = nodes.Text(access)
+            fieldname = nodes.field_name("", _("Access"))
+            fieldbody = nodes.field_body("", nodes.paragraph("", "", content))
+            field = nodes.field("", fieldname, fieldbody)
+            fieldlist += field
+        emits = self.options.get("emits-changed", None)
+        if emits:
+            content = nodes.Text(emits)
+            fieldname = nodes.field_name("", _("Emits Changed"))
+            fieldbody = nodes.field_body("", nodes.paragraph("", "", content))
+            field = nodes.field("", fieldname, fieldbody)
+            fieldlist += field
+        if len(fieldlist) > 0:
+            contentnode.insert(0, fieldlist)
+
+    def handle_signature(self, sig: str, signode: desc_signature) -> str:
+        contentnode = addnodes.desc_content()
+        self.state.nested_parse(self.content, self.content_offset, contentnode)
+        ty = self.options.get("type")
+
+        signode += addnodes.desc_annotation("property ", "property ")
+        signode += addnodes.desc_name(sig, sig)
+        signode += addnodes.desc_sig_punctuation("", ":")
+        signode += addnodes.desc_sig_keyword_type(ty, ty)
+        return sig
+
+    def run(self) -> List[Node]:
+        self.name = "dbus:member"
+        return super().run()
+
+
+class DBusXRef(XRefRole):
+    def process_link(self, env, refnode, has_explicit_title, title, target):
+        refnode["dbus:interface"] = env.ref_context.get("dbus:interface")
+        if not has_explicit_title:
+            title = title.lstrip(".")  # only has a meaning for the target
+            target = target.lstrip("~")  # only has a meaning for the title
+            # if the first character is a tilde, don't display the module/class
+            # parts of the contents
+            if title[0:1] == "~":
+                title = title[1:]
+                dot = title.rfind(".")
+                if dot != -1:
+                    title = title[dot + 1 :]
+        # if the first character is a dot, search more specific namespaces first
+        # else search builtins first
+        if target[0:1] == ".":
+            target = target[1:]
+            refnode["refspecific"] = True
+        return title, target
+
+
+class DBusIndex(Index):
+    """
+    Index subclass to provide a D-Bus interfaces index.
+    """
+
+    name = "dbusindex"
+    localname = _("D-Bus Interfaces Index")
+    shortname = _("dbus")
+
+    def generate(
+        self, docnames: Iterable[str] = None
+    ) -> Tuple[List[Tuple[str, List[IndexEntry]]], bool]:
+        content: Dict[str, List[IndexEntry]] = {}
+        # list of prefixes to ignore
+        ignores: List[str] = self.domain.env.config["dbus_index_common_prefix"]
+        ignores = sorted(ignores, key=len, reverse=True)
+
+        ifaces = sorted(
+            [
+                x
+                for x in self.domain.data["objects"].items()
+                if x[1].objtype == "interface"
+            ],
+            key=lambda x: x[0].lower(),
+        )
+        for name, (docname, node_id, _) in ifaces:
+            if docnames and docname not in docnames:
+                continue
+
+            for ignore in ignores:
+                if name.startswith(ignore):
+                    name = name[len(ignore) :]
+                    stripped = ignore
+                    break
+            else:
+                stripped = ""
+
+            entries = content.setdefault(name[0].lower(), [])
+            entries.append(IndexEntry(stripped + name, 0, docname, node_id, "", "", ""))
+
+        # sort by first letter
+        sorted_content = sorted(content.items())
+
+        return sorted_content, False
+
+
+class ObjectEntry(NamedTuple):
+    docname: str
+    node_id: str
+    objtype: str
+
+
+class DBusDomain(Domain):
+    """
+    Implementation of the D-Bus domain.
+    """
+
+    name = "dbus"
+    label = "D-Bus"
+    object_types: Dict[str, ObjType] = {
+        "interface": ObjType(_("interface"), "iface", "obj"),
+        "method": ObjType(_("method"), "meth", "obj"),
+        "signal": ObjType(_("signal"), "sig", "obj"),
+        "property": ObjType(_("property"), "attr", "_prop", "obj"),
+    }
+    directives = {
+        "interface": DBusInterface,
+        "method": DBusMethod,
+        "signal": DBusSignal,
+        "property": DBusProperty,
+    }
+    roles = {
+        "iface": DBusXRef(),
+        "meth": DBusXRef(),
+        "sig": DBusXRef(),
+        "prop": DBusXRef(),
+    }
+    initial_data: Dict[str, Dict[str, Tuple[Any]]] = {
+        "objects": {},  # fullname -> ObjectEntry
+    }
+    indices = [
+        DBusIndex,
+    ]
+
+    @property
+    def objects(self) -> Dict[str, ObjectEntry]:
+        return self.data.setdefault("objects", {})  # fullname -> ObjectEntry
+
+    def note_object(
+        self, name: str, objtype: str, node_id: str, location: Any = None
+    ) -> None:
+        self.objects[name] = ObjectEntry(self.env.docname, node_id, objtype)
+
+    def clear_doc(self, docname: str) -> None:
+        for fullname, obj in list(self.objects.items()):
+            if obj.docname == docname:
+                del self.objects[fullname]
+
+    def find_obj(self, typ: str, name: str) -> Optional[Tuple[str, ObjectEntry]]:
+        # skip parens
+        if name[-2:] == "()":
+            name = name[:-2]
+        if typ in ("meth", "sig", "prop"):
+            try:
+                ifacename, name = name.rsplit(".", 1)
+            except ValueError:
+                pass
+        return self.objects.get(name)
+
+    def resolve_xref(
+        self,
+        env: "BuildEnvironment",
+        fromdocname: str,
+        builder: "Builder",
+        typ: str,
+        target: str,
+        node: pending_xref,
+        contnode: Element,
+    ) -> Optional[Element]:
+        """Resolve the pending_xref *node* with the given *typ* and *target*."""
+        objdef = self.find_obj(typ, target)
+        if objdef:
+            return node_utils.make_refnode(
+                builder, fromdocname, objdef.docname, objdef.node_id, contnode
+            )
+
+    def get_objects(self) -> Iterator[Tuple[str, str, str, str, str, int]]:
+        for refname, obj in self.objects.items():
+            yield (refname, refname, obj.objtype, obj.docname, obj.node_id, 1)
+
+
+def setup(app):
+    app.add_domain(DBusDomain)
+    app.add_config_value("dbus_index_common_prefix", [], "env")
diff --git a/docs/sphinx/dbusparser.py b/docs/sphinx/dbusparser.py
new file mode 100644
index 0000000000..024553eae7
--- /dev/null
+++ b/docs/sphinx/dbusparser.py
@@ -0,0 +1,373 @@
+# Based from "GDBus - GLib D-Bus Library":
+#
+# Copyright (C) 2008-2011 Red Hat, Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General
+# Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
+#
+# Author: David Zeuthen <davidz@redhat.com>
+
+import xml.parsers.expat
+
+
+class Annotation:
+    def __init__(self, key, value):
+        self.key = key
+        self.value = value
+        self.annotations = []
+        self.since = ""
+
+
+class Arg:
+    def __init__(self, name, signature):
+        self.name = name
+        self.signature = signature
+        self.annotations = []
+        self.doc_string = ""
+        self.since = ""
+
+
+class Method:
+    def __init__(self, name, h_type_implies_unix_fd=True):
+        self.name = name
+        self.h_type_implies_unix_fd = h_type_implies_unix_fd
+        self.in_args = []
+        self.out_args = []
+        self.annotations = []
+        self.doc_string = ""
+        self.since = ""
+        self.deprecated = False
+        self.unix_fd = False
+
+
+class Signal:
+    def __init__(self, name):
+        self.name = name
+        self.args = []
+        self.annotations = []
+        self.doc_string = ""
+        self.since = ""
+        self.deprecated = False
+
+
+class Property:
+    def __init__(self, name, signature, access):
+        self.name = name
+        self.signature = signature
+        self.access = access
+        self.annotations = []
+        self.arg = Arg("value", self.signature)
+        self.arg.annotations = self.annotations
+        self.readable = False
+        self.writable = False
+        if self.access == "readwrite":
+            self.readable = True
+            self.writable = True
+        elif self.access == "read":
+            self.readable = True
+        elif self.access == "write":
+            self.writable = True
+        else:
+            raise ValueError('Invalid access type "{}"'.format(self.access))
+        self.doc_string = ""
+        self.since = ""
+        self.deprecated = False
+        self.emits_changed_signal = True
+
+
+class Interface:
+    def __init__(self, name):
+        self.name = name
+        self.methods = []
+        self.signals = []
+        self.properties = []
+        self.annotations = []
+        self.doc_string = ""
+        self.doc_string_brief = ""
+        self.since = ""
+        self.deprecated = False
+
+
+class DBusXMLParser:
+    STATE_TOP = "top"
+    STATE_NODE = "node"
+    STATE_INTERFACE = "interface"
+    STATE_METHOD = "method"
+    STATE_SIGNAL = "signal"
+    STATE_PROPERTY = "property"
+    STATE_ARG = "arg"
+    STATE_ANNOTATION = "annotation"
+    STATE_IGNORED = "ignored"
+
+    def __init__(self, xml_data, h_type_implies_unix_fd=True):
+        self._parser = xml.parsers.expat.ParserCreate()
+        self._parser.CommentHandler = self.handle_comment
+        self._parser.CharacterDataHandler = self.handle_char_data
+        self._parser.StartElementHandler = self.handle_start_element
+        self._parser.EndElementHandler = self.handle_end_element
+
+        self.parsed_interfaces = []
+        self._cur_object = None
+
+        self.state = DBusXMLParser.STATE_TOP
+        self.state_stack = []
+        self._cur_object = None
+        self._cur_object_stack = []
+
+        self.doc_comment_last_symbol = ""
+
+        self._h_type_implies_unix_fd = h_type_implies_unix_fd
+
+        self._parser.Parse(xml_data)
+
+    COMMENT_STATE_BEGIN = "begin"
+    COMMENT_STATE_PARAMS = "params"
+    COMMENT_STATE_BODY = "body"
+    COMMENT_STATE_SKIP = "skip"
+
+    def handle_comment(self, data):
+        comment_state = DBusXMLParser.COMMENT_STATE_BEGIN
+        lines = data.split("\n")
+        symbol = ""
+        body = ""
+        in_para = False
+        params = {}
+        for line in lines:
+            orig_line = line
+            line = line.lstrip()
+            if comment_state == DBusXMLParser.COMMENT_STATE_BEGIN:
+                if len(line) > 0:
+                    colon_index = line.find(": ")
+                    if colon_index == -1:
+                        if line.endswith(":"):
+                            symbol = line[0 : len(line) - 1]
+                            comment_state = DBusXMLParser.COMMENT_STATE_PARAMS
+                        else:
+                            comment_state = DBusXMLParser.COMMENT_STATE_SKIP
+                    else:
+                        symbol = line[0:colon_index]
+                        rest_of_line = line[colon_index + 2 :].strip()
+                        if len(rest_of_line) > 0:
+                            body += rest_of_line + "\n"
+                        comment_state = DBusXMLParser.COMMENT_STATE_PARAMS
+            elif comment_state == DBusXMLParser.COMMENT_STATE_PARAMS:
+                if line.startswith("@"):
+                    colon_index = line.find(": ")
+                    if colon_index == -1:
+                        comment_state = DBusXMLParser.COMMENT_STATE_BODY
+                        if not in_para:
+                            in_para = True
+                        body += orig_line + "\n"
+                    else:
+                        param = line[1:colon_index]
+                        docs = line[colon_index + 2 :]
+                        params[param] = docs
+                else:
+                    comment_state = DBusXMLParser.COMMENT_STATE_BODY
+                    if len(line) > 0:
+                        if not in_para:
+                            in_para = True
+                        body += orig_line + "\n"
+            elif comment_state == DBusXMLParser.COMMENT_STATE_BODY:
+                if len(line) > 0:
+                    if not in_para:
+                        in_para = True
+                    body += orig_line + "\n"
+                else:
+                    if in_para:
+                        body += "\n"
+                        in_para = False
+        if in_para:
+            body += "\n"
+
+        if symbol != "":
+            self.doc_comment_last_symbol = symbol
+            self.doc_comment_params = params
+            self.doc_comment_body = body
+
+    def handle_char_data(self, data):
+        # print 'char_data=%s'%data
+        pass
+
+    def handle_start_element(self, name, attrs):
+        old_state = self.state
+        old_cur_object = self._cur_object
+        if self.state == DBusXMLParser.STATE_IGNORED:
+            self.state = DBusXMLParser.STATE_IGNORED
+        elif self.state == DBusXMLParser.STATE_TOP:
+            if name == DBusXMLParser.STATE_NODE:
+                self.state = DBusXMLParser.STATE_NODE
+            else:
+                self.state = DBusXMLParser.STATE_IGNORED
+        elif self.state == DBusXMLParser.STATE_NODE:
+            if name == DBusXMLParser.STATE_INTERFACE:
+                self.state = DBusXMLParser.STATE_INTERFACE
+                iface = Interface(attrs["name"])
+                self._cur_object = iface
+                self.parsed_interfaces.append(iface)
+            elif name == DBusXMLParser.STATE_ANNOTATION:
+                self.state = DBusXMLParser.STATE_ANNOTATION
+                anno = Annotation(attrs["name"], attrs["value"])
+                self._cur_object.annotations.append(anno)
+                self._cur_object = anno
+            else:
+                self.state = DBusXMLParser.STATE_IGNORED
+
+            # assign docs, if any
+            if "name" in attrs and self.doc_comment_last_symbol == attrs["name"]:
+                self._cur_object.doc_string = self.doc_comment_body
+                if "short_description" in self.doc_comment_params:
+                    short_description = self.doc_comment_params["short_description"]
+                    self._cur_object.doc_string_brief = short_description
+                if "since" in self.doc_comment_params:
+                    self._cur_object.since = self.doc_comment_params["since"].strip()
+
+        elif self.state == DBusXMLParser.STATE_INTERFACE:
+            if name == DBusXMLParser.STATE_METHOD:
+                self.state = DBusXMLParser.STATE_METHOD
+                method = Method(
+                    attrs["name"], h_type_implies_unix_fd=self._h_type_implies_unix_fd
+                )
+                self._cur_object.methods.append(method)
+                self._cur_object = method
+            elif name == DBusXMLParser.STATE_SIGNAL:
+                self.state = DBusXMLParser.STATE_SIGNAL
+                signal = Signal(attrs["name"])
+                self._cur_object.signals.append(signal)
+                self._cur_object = signal
+            elif name == DBusXMLParser.STATE_PROPERTY:
+                self.state = DBusXMLParser.STATE_PROPERTY
+                prop = Property(attrs["name"], attrs["type"], attrs["access"])
+                self._cur_object.properties.append(prop)
+                self._cur_object = prop
+            elif name == DBusXMLParser.STATE_ANNOTATION:
+                self.state = DBusXMLParser.STATE_ANNOTATION
+                anno = Annotation(attrs["name"], attrs["value"])
+                self._cur_object.annotations.append(anno)
+                self._cur_object = anno
+            else:
+                self.state = DBusXMLParser.STATE_IGNORED
+
+            # assign docs, if any
+            if "name" in attrs and self.doc_comment_last_symbol == attrs["name"]:
+                self._cur_object.doc_string = self.doc_comment_body
+                if "since" in self.doc_comment_params:
+                    self._cur_object.since = self.doc_comment_params["since"].strip()
+
+        elif self.state == DBusXMLParser.STATE_METHOD:
+            if name == DBusXMLParser.STATE_ARG:
+                self.state = DBusXMLParser.STATE_ARG
+                arg_name = None
+                if "name" in attrs:
+                    arg_name = attrs["name"]
+                arg = Arg(arg_name, attrs["type"])
+                direction = attrs.get("direction", "in")
+                if direction == "in":
+                    self._cur_object.in_args.append(arg)
+                elif direction == "out":
+                    self._cur_object.out_args.append(arg)
+                else:
+                    raise ValueError('Invalid direction "{}"'.format(direction))
+                self._cur_object = arg
+            elif name == DBusXMLParser.STATE_ANNOTATION:
+                self.state = DBusXMLParser.STATE_ANNOTATION
+                anno = Annotation(attrs["name"], attrs["value"])
+                self._cur_object.annotations.append(anno)
+                self._cur_object = anno
+            else:
+                self.state = DBusXMLParser.STATE_IGNORED
+
+            # assign docs, if any
+            if self.doc_comment_last_symbol == old_cur_object.name:
+                if "name" in attrs and attrs["name"] in self.doc_comment_params:
+                    doc_string = self.doc_comment_params[attrs["name"]]
+                    if doc_string is not None:
+                        self._cur_object.doc_string = doc_string
+                    if "since" in self.doc_comment_params:
+                        self._cur_object.since = self.doc_comment_params[
+                            "since"
+                        ].strip()
+
+        elif self.state == DBusXMLParser.STATE_SIGNAL:
+            if name == DBusXMLParser.STATE_ARG:
+                self.state = DBusXMLParser.STATE_ARG
+                arg_name = None
+                if "name" in attrs:
+                    arg_name = attrs["name"]
+                arg = Arg(arg_name, attrs["type"])
+                self._cur_object.args.append(arg)
+                self._cur_object = arg
+            elif name == DBusXMLParser.STATE_ANNOTATION:
+                self.state = DBusXMLParser.STATE_ANNOTATION
+                anno = Annotation(attrs["name"], attrs["value"])
+                self._cur_object.annotations.append(anno)
+                self._cur_object = anno
+            else:
+                self.state = DBusXMLParser.STATE_IGNORED
+
+            # assign docs, if any
+            if self.doc_comment_last_symbol == old_cur_object.name:
+                if "name" in attrs and attrs["name"] in self.doc_comment_params:
+                    doc_string = self.doc_comment_params[attrs["name"]]
+                    if doc_string is not None:
+                        self._cur_object.doc_string = doc_string
+                    if "since" in self.doc_comment_params:
+                        self._cur_object.since = self.doc_comment_params[
+                            "since"
+                        ].strip()
+
+        elif self.state == DBusXMLParser.STATE_PROPERTY:
+            if name == DBusXMLParser.STATE_ANNOTATION:
+                self.state = DBusXMLParser.STATE_ANNOTATION
+                anno = Annotation(attrs["name"], attrs["value"])
+                self._cur_object.annotations.append(anno)
+                self._cur_object = anno
+            else:
+                self.state = DBusXMLParser.STATE_IGNORED
+
+        elif self.state == DBusXMLParser.STATE_ARG:
+            if name == DBusXMLParser.STATE_ANNOTATION:
+                self.state = DBusXMLParser.STATE_ANNOTATION
+                anno = Annotation(attrs["name"], attrs["value"])
+                self._cur_object.annotations.append(anno)
+                self._cur_object = anno
+            else:
+                self.state = DBusXMLParser.STATE_IGNORED
+
+        elif self.state == DBusXMLParser.STATE_ANNOTATION:
+            if name == DBusXMLParser.STATE_ANNOTATION:
+                self.state = DBusXMLParser.STATE_ANNOTATION
+                anno = Annotation(attrs["name"], attrs["value"])
+                self._cur_object.annotations.append(anno)
+                self._cur_object = anno
+            else:
+                self.state = DBusXMLParser.STATE_IGNORED
+
+        else:
+            raise ValueError(
+                'Unhandled state "{}" while entering element with name "{}"'.format(
+                    self.state, name
+                )
+            )
+
+        self.state_stack.append(old_state)
+        self._cur_object_stack.append(old_cur_object)
+
+    def handle_end_element(self, name):
+        self.state = self.state_stack.pop()
+        self._cur_object = self._cur_object_stack.pop()
+
+
+def parse_dbus_xml(xml_data):
+    parser = DBusXMLParser(xml_data, True)
+    return parser.parsed_interfaces
diff --git a/docs/sphinx/fakedbusdoc.py b/docs/sphinx/fakedbusdoc.py
new file mode 100644
index 0000000000..a680b25754
--- /dev/null
+++ b/docs/sphinx/fakedbusdoc.py
@@ -0,0 +1,25 @@
+# D-Bus XML documentation extension, compatibility gunk for <sphinx4
+#
+# Copyright (C) 2021, Red Hat Inc.
+#
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# Author: Marc-André Lureau <marcandre.lureau@redhat.com>
+"""dbus-doc is a Sphinx extension that provides documentation from D-Bus XML."""
+
+from sphinx.application import Sphinx
+from sphinx.util.docutils import SphinxDirective
+from typing import Any, Dict
+
+
+class FakeDBusDocDirective(SphinxDirective):
+    has_content = True
+    required_arguments = 1
+
+    def run(self):
+        return []
+
+
+def setup(app: Sphinx) -> Dict[str, Any]:
+    """Register a fake dbus-doc directive with Sphinx"""
+    app.add_directive("dbus-doc", FakeDBusDocDirective)
-- 
2.33.0.721.g106298f7f9



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v2 22/37] backends: move dbus-vmstate1.xml to backends/
  2021-10-09 21:08 [PATCH v2 00/37] Add D-Bus display backend marcandre.lureau
                   ` (20 preceding siblings ...)
  2021-10-09 21:08 ` [PATCH v2 21/37] docs/sphinx: add sphinx modules to include D-Bus documentation marcandre.lureau
@ 2021-10-09 21:08 ` marcandre.lureau
  2021-10-09 21:08 ` [PATCH v2 23/37] docs: move D-Bus VMState documentation to source XML marcandre.lureau
                   ` (15 subsequent siblings)
  37 siblings, 0 replies; 54+ messages in thread
From: marcandre.lureau @ 2021-10-09 21:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, kraxel

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Although not used by the backend itself, use a common location for
documentation and sharing purposes.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 {tests/qtest => backends}/dbus-vmstate1.xml | 0
 tests/qtest/meson.build                     | 2 +-
 2 files changed, 1 insertion(+), 1 deletion(-)
 rename {tests/qtest => backends}/dbus-vmstate1.xml (100%)

diff --git a/tests/qtest/dbus-vmstate1.xml b/backends/dbus-vmstate1.xml
similarity index 100%
rename from tests/qtest/dbus-vmstate1.xml
rename to backends/dbus-vmstate1.xml
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index c9d8458062..b6016aee48 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -93,7 +93,7 @@ if dbus_daemon.found() and config_host.has_key('GDBUS_CODEGEN')
   #qtests_i386 += ['dbus-vmstate-test']
   dbus_vmstate1 = custom_target('dbus-vmstate description',
                                 output: ['dbus-vmstate1.h', 'dbus-vmstate1.c'],
-                                input: files('dbus-vmstate1.xml'),
+                                input: meson.source_root() / 'backends/dbus-vmstate1.xml',
                                 command: [config_host['GDBUS_CODEGEN'],
                                           '@INPUT@',
                                           '--interface-prefix', 'org.qemu',
-- 
2.33.0.721.g106298f7f9



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v2 23/37] docs: move D-Bus VMState documentation to source XML
  2021-10-09 21:08 [PATCH v2 00/37] Add D-Bus display backend marcandre.lureau
                   ` (21 preceding siblings ...)
  2021-10-09 21:08 ` [PATCH v2 22/37] backends: move dbus-vmstate1.xml to backends/ marcandre.lureau
@ 2021-10-09 21:08 ` marcandre.lureau
  2021-10-09 21:08 ` [PATCH v2 24/37] docs: add dbus-display documentation marcandre.lureau
                   ` (14 subsequent siblings)
  37 siblings, 0 replies; 54+ messages in thread
From: marcandre.lureau @ 2021-10-09 21:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, kraxel

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Use the source XML document as single reference, importing its
documentation via the dbus-doc directive.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 docs/interop/dbus-vmstate.rst | 52 ++++++-----------------------------
 backends/dbus-vmstate1.xml    | 42 +++++++++++++++++++++++++++-
 2 files changed, 49 insertions(+), 45 deletions(-)

diff --git a/docs/interop/dbus-vmstate.rst b/docs/interop/dbus-vmstate.rst
index 1d719c1c60..5fb3f279e2 100644
--- a/docs/interop/dbus-vmstate.rst
+++ b/docs/interop/dbus-vmstate.rst
@@ -2,9 +2,6 @@
 D-Bus VMState
 =============
 
-Introduction
-============
-
 The QEMU dbus-vmstate object's aim is to migrate helpers' data running
 on a QEMU D-Bus bus. (refer to the :doc:`dbus` document for
 some recommendations on D-Bus usage)
@@ -26,49 +23,16 @@ dbus-vmstate object can be configured with the expected list of
 helpers by setting its ``id-list`` property, with a comma-separated
 ``Id`` list.
 
-Interface
-=========
-
-On object path ``/org/qemu/VMState1``, the following
-``org.qemu.VMState1`` interface should be implemented:
-
-.. code:: xml
-
-  <interface name="org.qemu.VMState1">
-    <property name="Id" type="s" access="read"/>
-    <method name="Load">
-      <arg type="ay" name="data" direction="in"/>
-    </method>
-    <method name="Save">
-      <arg type="ay" name="data" direction="out"/>
-    </method>
-  </interface>
-
-"Id" property
--------------
-
-A string that identifies the helper uniquely. (maximum 256 bytes
-including terminating NUL byte)
-
-.. note::
-
-   The helper ID namespace is a separate namespace. In particular, it is not
-   related to QEMU "id" used in -object/-device objects.
-
-Load(in u8[] bytes) method
---------------------------
-
-The method called on destination with the state to restore.
+.. only:: sphinx4
 
-The helper may be initially started in a waiting state (with
-an --incoming argument for example), and it may resume on success.
+   .. dbus-doc:: backends/dbus-vmstate1.xml
 
-An error may be returned to the caller.
+.. only:: not sphinx4
 
-Save(out u8[] bytes) method
----------------------------
+   .. warning::
+      Sphinx 4 is required to build D-Bus documentation.
 
-The method called on the source to get the current state to be
-migrated. The helper should continue to run normally.
+      This is the content of ``backends/dbus-vmstate1.xml``:
 
-An error may be returned to the caller.
+   .. literalinclude:: ../../backends/dbus-vmstate1.xml
+      :language: xml
diff --git a/backends/dbus-vmstate1.xml b/backends/dbus-vmstate1.xml
index cc8563be4c..601ee8dc7e 100644
--- a/backends/dbus-vmstate1.xml
+++ b/backends/dbus-vmstate1.xml
@@ -1,10 +1,50 @@
-<?xml version="1.0"?>
+<?xml version="1.0" encoding="utf-8"?>
 <node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
+  <!--
+      org.qemu.VMState1:
+
+      This interface must be implemented at the object path
+      ``/org/qemu/VMState1`` to support helper migration.
+  -->
   <interface name="org.qemu.VMState1">
+
+    <!--
+        Id:
+
+        A string that identifies the helper uniquely. (maximum 256 bytes
+        including terminating NUL byte)
+
+        .. note::
+
+           The VMState helper ID namespace is its own namespace. In particular,
+           it is not related to QEMU "id" used in -object/-device objects.
+    -->
     <property name="Id" type="s" access="read"/>
+
+    <!--
+        Load:
+        @data: data to restore the state.
+
+        The method called on destination with the state to restore.
+
+        The helper may be initially started in a waiting state (with an
+        ``-incoming`` argument for example), and it may resume on success.
+
+        An error may be returned to the caller.
+    -->
     <method name="Load">
       <arg type="ay" name="data" direction="in"/>
     </method>
+
+    <!--
+        Save:
+        @data: state data to save for later resume.
+
+        The method called on the source to get the current state to be
+        migrated. The helper should continue to run normally.
+
+        An error may be returned to the caller.
+    -->
     <method name="Save">
       <arg type="ay" name="data" direction="out"/>
     </method>
-- 
2.33.0.721.g106298f7f9



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v2 24/37] docs: add dbus-display documentation
  2021-10-09 21:08 [PATCH v2 00/37] Add D-Bus display backend marcandre.lureau
                   ` (22 preceding siblings ...)
  2021-10-09 21:08 ` [PATCH v2 23/37] docs: move D-Bus VMState documentation to source XML marcandre.lureau
@ 2021-10-09 21:08 ` marcandre.lureau
  2021-10-09 21:08 ` [PATCH v2 25/37] build-sys: set glib dependency version marcandre.lureau
                   ` (13 subsequent siblings)
  37 siblings, 0 replies; 54+ messages in thread
From: marcandre.lureau @ 2021-10-09 21:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, kraxel

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Wire up the dbus-display documentation. The interface and feature is
implemented next.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 docs/interop/dbus-display.rst | 31 +++++++++++++++++++++++++++++++
 docs/interop/dbus.rst         |  2 ++
 docs/interop/index.rst        |  1 +
 ui/dbus-display1.xml          |  0
 4 files changed, 34 insertions(+)
 create mode 100644 docs/interop/dbus-display.rst
 create mode 100644 ui/dbus-display1.xml

diff --git a/docs/interop/dbus-display.rst b/docs/interop/dbus-display.rst
new file mode 100644
index 0000000000..8c6e8e0f5a
--- /dev/null
+++ b/docs/interop/dbus-display.rst
@@ -0,0 +1,31 @@
+D-Bus display
+=============
+
+QEMU can export the VM display through D-Bus (when started with ``-display
+dbus``), to allow out-of-process UIs, remote protocol servers or other
+interactive display usages.
+
+Various specialized D-Bus interfaces are available on different object paths
+under ``/org/qemu/Display1/``, depending on the VM configuration.
+
+QEMU also implements the standard interfaces, such as
+`org.freedesktop.DBus.Introspectable
+<https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces>`_.
+
+.. contents::
+   :local:
+   :depth: 1
+
+.. only:: sphinx4
+
+   .. dbus-doc:: ui/dbus-display1.xml
+
+.. only:: not sphinx4
+
+   .. warning::
+      Sphinx 4 is required to build D-Bus documentation.
+
+      This is the content of ``ui/dbus-display1.xml``:
+
+   .. literalinclude:: ../../ui/dbus-display1.xml
+      :language: xml
diff --git a/docs/interop/dbus.rst b/docs/interop/dbus.rst
index be596d3f41..427debc9c5 100644
--- a/docs/interop/dbus.rst
+++ b/docs/interop/dbus.rst
@@ -108,3 +108,5 @@ QEMU Interfaces
 ===============
 
 :doc:`dbus-vmstate`
+
+:doc:`dbus-display`
diff --git a/docs/interop/index.rst b/docs/interop/index.rst
index 47b9ed82bb..c59bac9834 100644
--- a/docs/interop/index.rst
+++ b/docs/interop/index.rst
@@ -12,6 +12,7 @@ are useful for making QEMU interoperate with other software.
    bitmaps
    dbus
    dbus-vmstate
+   dbus-display
    live-block-operations
    pr-helper
    qemu-ga
diff --git a/ui/dbus-display1.xml b/ui/dbus-display1.xml
new file mode 100644
index 0000000000..e69de29bb2
-- 
2.33.0.721.g106298f7f9



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v2 25/37] build-sys: set glib dependency version
  2021-10-09 21:08 [PATCH v2 00/37] Add D-Bus display backend marcandre.lureau
                   ` (23 preceding siblings ...)
  2021-10-09 21:08 ` [PATCH v2 24/37] docs: add dbus-display documentation marcandre.lureau
@ 2021-10-09 21:08 ` marcandre.lureau
  2021-12-17 13:27   ` Philippe Mathieu-Daudé
  2021-10-09 21:08 ` [PATCH v2 26/37] ui: add a D-Bus display backend marcandre.lureau
                   ` (12 subsequent siblings)
  37 siblings, 1 reply; 54+ messages in thread
From: marcandre.lureau @ 2021-10-09 21:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, kraxel

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Further meson configuration tests are to be added based on the glib
version. Also correct the version reporting in the config log.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 configure   | 1 +
 meson.build | 6 ++++--
 2 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/configure b/configure
index 375cde2b44..57e363fd73 100755
--- a/configure
+++ b/configure
@@ -4889,6 +4889,7 @@ echo "QEMU_CFLAGS=$QEMU_CFLAGS" >> $config_host_mak
 echo "QEMU_CXXFLAGS=$QEMU_CXXFLAGS" >> $config_host_mak
 echo "GLIB_CFLAGS=$glib_cflags" >> $config_host_mak
 echo "GLIB_LIBS=$glib_libs" >> $config_host_mak
+echo "GLIB_VERSION=$(pkg-config --modversion glib-2.0)" >> $config_host_mak
 echo "QEMU_LDFLAGS=$QEMU_LDFLAGS" >> $config_host_mak
 echo "LD_I386_EMULATION=$ld_i386_emulation" >> $config_host_mak
 echo "EXESUF=$EXESUF" >> $config_host_mak
diff --git a/meson.build b/meson.build
index e1cddf5139..9494590aa2 100644
--- a/meson.build
+++ b/meson.build
@@ -311,14 +311,16 @@ endif
 add_project_arguments(config_host['GLIB_CFLAGS'].split(),
                       native: false, language: ['c', 'cpp', 'objc'])
 glib = declare_dependency(compile_args: config_host['GLIB_CFLAGS'].split(),
-                          link_args: config_host['GLIB_LIBS'].split())
+                          link_args: config_host['GLIB_LIBS'].split(),
+                          version: config_host['GLIB_VERSION'])
 # override glib dep with the configure results (for subprojects)
 meson.override_dependency('glib-2.0', glib)
 
 gio = not_found
 if 'CONFIG_GIO' in config_host
   gio = declare_dependency(compile_args: config_host['GIO_CFLAGS'].split(),
-                           link_args: config_host['GIO_LIBS'].split())
+                           link_args: config_host['GIO_LIBS'].split(),
+                           version: config_host['GLIB_VERSION'])
 endif
 lttng = not_found
 if 'CONFIG_TRACE_UST' in config_host
-- 
2.33.0.721.g106298f7f9



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v2 26/37] ui: add a D-Bus display backend
  2021-10-09 21:08 [PATCH v2 00/37] Add D-Bus display backend marcandre.lureau
                   ` (24 preceding siblings ...)
  2021-10-09 21:08 ` [PATCH v2 25/37] build-sys: set glib dependency version marcandre.lureau
@ 2021-10-09 21:08 ` marcandre.lureau
  2021-10-13  8:59   ` Marc-André Lureau
  2021-10-09 21:08 ` [PATCH v2 27/37] ui/dbus: add p2p=on/off option marcandre.lureau
                   ` (11 subsequent siblings)
  37 siblings, 1 reply; 54+ messages in thread
From: marcandre.lureau @ 2021-10-09 21:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, kraxel

From: Marc-André Lureau <marcandre.lureau@redhat.com>

The "dbus" display backend exports the QEMU consoles and other
UI-related interfaces over D-Bus.

By default, the connection is established on the session bus, but you
can specify a different bus with the "addr" option.

The backend takes the "org.qemu" service name, while still allowing
further instances to queue on the same name (so you can lookup all the
available instances too). It accepts any number of clients at this
point, although this is expected to evolve with options to restrict
clients, or only accept p2p via fd passing.

The interface is intentionally very close to the internal QEMU API,
and can be introspected or interacted with busctl/dfeet etc:

$ ./qemu-system-x86_64 -name MyVM -display dbus
$ busctl --user introspect org.qemu /org/qemu/Display1/Console_0

org.qemu.Display1.Console           interface -         -               -
.RegisterListener                   method    h         -               -
.SetUIInfo                          method    qqiiuu    -               -
.DeviceAddress                      property  s         "pci/0000/01.0" emits-change
.Head                               property  u         0               emits-change
.Height                             property  u         480             emits-change
.Label                              property  s         "VGA"           emits-change
.Type                               property  s         "Graphic"       emits-change
.Width                              property  u         640             emits-change
[...]

See the interfaces XML source file and Sphinx docs for the generated API
documentations.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 meson.build          |  12 +-
 qapi/ui.json         |  27 ++-
 include/qemu/dbus.h  |  19 ++
 ui/dbus.h            |  83 ++++++++
 ui/dbus-console.c    | 497 +++++++++++++++++++++++++++++++++++++++++++
 ui/dbus-error.c      |  48 +++++
 ui/dbus-listener.c   | 486 ++++++++++++++++++++++++++++++++++++++++++
 ui/dbus.c            | 262 +++++++++++++++++++++++
 meson_options.txt    |   2 +
 qemu-options.hx      |  15 ++
 ui/dbus-display1.xml | 378 ++++++++++++++++++++++++++++++++
 ui/meson.build       |  22 ++
 ui/trace-events      |  11 +
 13 files changed, 1859 insertions(+), 3 deletions(-)
 create mode 100644 ui/dbus.h
 create mode 100644 ui/dbus-console.c
 create mode 100644 ui/dbus-error.c
 create mode 100644 ui/dbus-listener.c
 create mode 100644 ui/dbus.c

diff --git a/meson.build b/meson.build
index 9494590aa2..b571fde310 100644
--- a/meson.build
+++ b/meson.build
@@ -1198,6 +1198,15 @@ endif
 have_host_block_device = (targetos != 'darwin' or
     cc.has_header('IOKit/storage/IOMedia.h'))
 
+dbus_display = false
+if not get_option('dbus_display').disabled()
+  # FIXME enable_modules shouldn't be necessary, but: https://github.com/mesonbuild/meson/issues/8333
+  dbus_display = gio.version().version_compare('>=2.64') and config_host.has_key('GDBUS_CODEGEN') and enable_modules
+  if get_option('dbus_display').enabled() and not dbus_display
+    error('Requirements missing to enable -display dbus')
+  endif
+endif
+
 #################
 # config-host.h #
 #################
@@ -1299,7 +1308,7 @@ config_host_data.set('CONFIG_SPICE_PROTOCOL_MINOR', spice_protocol.version().spl
 config_host_data.set('CONFIG_SPICE_PROTOCOL_MICRO', spice_protocol.version().split('.')[2])
 endif
 config_host_data.set('CONFIG_SPICE', spice.found())
-config_host_data.set('CONFIG_X11', x11.found())
+config_host_data.set('CONFIG_DBUS_DISPLAY', dbus_display)
 config_host_data.set('CONFIG_CFI', get_option('cfi'))
 config_host_data.set('QEMU_VERSION', '"@0@"'.format(meson.project_version()))
 config_host_data.set('QEMU_VERSION_MAJOR', meson.project_version().split('.')[0])
@@ -2892,6 +2901,7 @@ summary_info += {'fuzzing support':   config_host.has_key('CONFIG_FUZZ')}
 if have_system
   summary_info += {'Audio drivers':     config_host['CONFIG_AUDIO_DRIVERS']}
 endif
+summary_info += {'D-Bus display':     dbus_display}
 summary_info += {'Trace backends':    config_host['TRACE_BACKENDS']}
 if config_host['TRACE_BACKENDS'].split().contains('simple')
   summary_info += {'Trace output file': config_host['CONFIG_TRACE_FILE'] + '-<pid>'}
diff --git a/qapi/ui.json b/qapi/ui.json
index d7567ac866..5ca604bd90 100644
--- a/qapi/ui.json
+++ b/qapi/ui.json
@@ -1121,6 +1121,23 @@
 { 'struct'  : 'DisplayEGLHeadless',
   'data'    : { '*rendernode' : 'str' } }
 
+##
+# @DisplayDBus:
+#
+# DBus display options.
+#
+# @addr: The D-Bus bus address (default to the session bus).
+#
+# @rendernode: Which DRM render node should be used. Default is the first
+#              available node on the host.
+#
+# Since: 6.2
+#
+##
+{ 'struct'  : 'DisplayDBus',
+  'data'    : { '*rendernode' : 'str',
+                '*addr': 'str' } }
+
  ##
  # @DisplayGLMode:
  #
@@ -1186,6 +1203,8 @@
 #             application to connect to it. The server will redirect
 #             the serial console and QEMU monitors. (Since 4.0)
 #
+# @dbus: Start a D-Bus service for the display. (Since 6.2)
+#
 # Since: 2.12
 #
 ##
@@ -1199,7 +1218,10 @@
               'if': { 'all': ['CONFIG_OPENGL', 'CONFIG_GBM'] } },
     { 'name': 'curses', 'if': 'CONFIG_CURSES' },
     { 'name': 'cocoa', 'if': 'CONFIG_COCOA' },
-    { 'name': 'spice-app', 'if': 'CONFIG_SPICE'} ] }
+    { 'name': 'spice-app', 'if': 'CONFIG_SPICE' },
+    { 'name': 'dbus', 'if': 'CONFIG_DBUS_DISPLAY' }
+  ]
+}
 
 ##
 # @DisplayOptions:
@@ -1227,7 +1249,8 @@
       'gtk': { 'type': 'DisplayGTK', 'if': 'CONFIG_GTK' },
       'curses': { 'type': 'DisplayCurses', 'if': 'CONFIG_CURSES' },
       'egl-headless': { 'type': 'DisplayEGLHeadless',
-                        'if': { 'all': ['CONFIG_OPENGL', 'CONFIG_GBM'] } }
+                        'if': { 'all': ['CONFIG_OPENGL', 'CONFIG_GBM'] } },
+      'dbus': { 'type': 'DisplayDBus', 'if': 'CONFIG_DBUS_DISPLAY' }
   }
 }
 
diff --git a/include/qemu/dbus.h b/include/qemu/dbus.h
index 9d591f9ee4..c0cbb1ca44 100644
--- a/include/qemu/dbus.h
+++ b/include/qemu/dbus.h
@@ -12,6 +12,25 @@
 
 #include <gio/gio.h>
 
+/* glib/gio 2.68 */
+#define DBUS_METHOD_INVOCATION_HANDLED TRUE
+#define DBUS_METHOD_INVOCATION_UNHANDLED FALSE
+
+/* in msec */
+#define DBUS_DEFAULT_TIMEOUT 1000
+
+#define DBUS_DISPLAY1_ROOT "/org/qemu/Display1"
+
+#define DBUS_DISPLAY_ERROR (dbus_display_error_quark())
+GQuark dbus_display_error_quark(void);
+
+typedef enum {
+    DBUS_DISPLAY_ERROR_FAILED,
+    DBUS_DISPLAY_ERROR_INVALID,
+    DBUS_DISPLAY_ERROR_UNSUPPORTED,
+    DBUS_DISPLAY_N_ERRORS,
+} DBusDisplayError;
+
 GStrv qemu_dbus_get_queued_owners(GDBusConnection *connection,
                                   const char *name,
                                   Error **errp);
diff --git a/ui/dbus.h b/ui/dbus.h
new file mode 100644
index 0000000000..d3c9598dd1
--- /dev/null
+++ b/ui/dbus.h
@@ -0,0 +1,83 @@
+/*
+ * QEMU DBus display
+ *
+ * Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#ifndef UI_DBUS_H_
+#define UI_DBUS_H_
+
+#include "qemu/dbus.h"
+#include "qom/object.h"
+#include "ui/console.h"
+
+#include "dbus-display1.h"
+
+struct DBusDisplay {
+    Object parent;
+
+    DisplayGLMode gl_mode;
+    char *dbus_addr;
+    DisplayGLCtx glctx;
+
+    GDBusConnection *bus;
+    GDBusObjectManagerServer *server;
+    QemuDBusDisplay1VM *iface;
+    GPtrArray *consoles;
+};
+
+#define TYPE_DBUS_DISPLAY "dbus-display"
+OBJECT_DECLARE_SIMPLE_TYPE(DBusDisplay, DBUS_DISPLAY)
+
+#define DBUS_DISPLAY_TYPE_CONSOLE dbus_display_console_get_type()
+G_DECLARE_FINAL_TYPE(DBusDisplayConsole,
+                     dbus_display_console,
+                     DBUS_DISPLAY,
+                     CONSOLE,
+                     GDBusObjectSkeleton)
+
+DBusDisplayConsole *
+dbus_display_console_new(DBusDisplay *display, QemuConsole *con);
+
+int
+dbus_display_console_get_index(DBusDisplayConsole *ddc);
+
+#define DBUS_DISPLAY_TYPE_LISTENER dbus_display_listener_get_type()
+G_DECLARE_FINAL_TYPE(DBusDisplayListener,
+                     dbus_display_listener,
+                     DBUS_DISPLAY,
+                     LISTENER,
+                     GObject)
+
+DBusDisplayListener *
+dbus_display_listener_new(const char *bus_name,
+                          GDBusConnection *conn,
+                          DBusDisplayConsole *console);
+
+DBusDisplayConsole *
+dbus_display_listener_get_console(DBusDisplayListener *ddl);
+
+const char *
+dbus_display_listener_get_bus_name(DBusDisplayListener *ddl);
+
+extern const DisplayChangeListenerOps dbus_gl_dcl_ops;
+extern const DisplayChangeListenerOps dbus_dcl_ops;
+
+#endif /* UI_DBUS_H_ */
diff --git a/ui/dbus-console.c b/ui/dbus-console.c
new file mode 100644
index 0000000000..1ccf638c10
--- /dev/null
+++ b/ui/dbus-console.c
@@ -0,0 +1,497 @@
+/*
+ * QEMU DBus display console
+ *
+ * Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "ui/input.h"
+#include "ui/kbd-state.h"
+#include "trace.h"
+
+#include <gio/gunixfdlist.h>
+
+#include "dbus.h"
+
+struct _DBusDisplayConsole {
+    GDBusObjectSkeleton parent_instance;
+    DisplayChangeListener dcl;
+
+    DBusDisplay *display;
+    QemuConsole *con;
+    GHashTable *listeners;
+    QemuDBusDisplay1Console *iface;
+
+    QemuDBusDisplay1Keyboard *iface_kbd;
+    QKbdState *kbd;
+
+    QemuDBusDisplay1Mouse *iface_mouse;
+    gboolean last_set;
+    guint last_x;
+    guint last_y;
+    Notifier mouse_mode_notifier;
+};
+
+G_DEFINE_TYPE(DBusDisplayConsole,
+              dbus_display_console,
+              G_TYPE_DBUS_OBJECT_SKELETON)
+
+static void
+dbus_display_console_set_size(DBusDisplayConsole *ddc,
+                              uint32_t width, uint32_t height)
+{
+    g_object_set(ddc->iface,
+                 "width", width,
+                 "height", height,
+                 NULL);
+}
+
+static void
+dbus_gfx_switch(DisplayChangeListener *dcl,
+                struct DisplaySurface *new_surface)
+{
+    DBusDisplayConsole *ddc = container_of(dcl, DBusDisplayConsole, dcl);
+
+    dbus_display_console_set_size(ddc,
+                                  surface_width(new_surface),
+                                  surface_height(new_surface));
+}
+
+static void
+dbus_gfx_update(DisplayChangeListener *dcl,
+                int x, int y, int w, int h)
+{
+}
+
+static void
+dbus_gl_scanout_disable(DisplayChangeListener *dcl)
+{
+}
+
+static void
+dbus_gl_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)
+{
+    DBusDisplayConsole *ddc = container_of(dcl, DBusDisplayConsole, dcl);
+
+    dbus_display_console_set_size(ddc, w, h);
+}
+
+static void
+dbus_gl_scanout_dmabuf(DisplayChangeListener *dcl,
+                       QemuDmaBuf *dmabuf)
+{
+    DBusDisplayConsole *ddc = container_of(dcl, DBusDisplayConsole, dcl);
+
+    dbus_display_console_set_size(ddc,
+                                  dmabuf->width,
+                                  dmabuf->height);
+}
+
+static void
+dbus_gl_scanout_update(DisplayChangeListener *dcl,
+                       uint32_t x, uint32_t y,
+                       uint32_t w, uint32_t h)
+{
+}
+
+static const DisplayChangeListenerOps dbus_console_dcl_ops = {
+    .dpy_name                = "dbus-console",
+    .dpy_gfx_switch          = dbus_gfx_switch,
+    .dpy_gfx_update          = dbus_gfx_update,
+    .dpy_gl_scanout_disable  = dbus_gl_scanout_disable,
+    .dpy_gl_scanout_texture  = dbus_gl_scanout_texture,
+    .dpy_gl_scanout_dmabuf   = dbus_gl_scanout_dmabuf,
+    .dpy_gl_update           = dbus_gl_scanout_update,
+};
+
+static void
+dbus_display_console_init(DBusDisplayConsole *object)
+{
+    DBusDisplayConsole *ddc = DBUS_DISPLAY_CONSOLE(object);
+
+    ddc->listeners = g_hash_table_new_full(g_str_hash, g_str_equal,
+                                            NULL, g_object_unref);
+    ddc->dcl.ops = &dbus_console_dcl_ops;
+}
+
+static void
+dbus_display_console_dispose(GObject *object)
+{
+    DBusDisplayConsole *ddc = DBUS_DISPLAY_CONSOLE(object);
+
+    unregister_displaychangelistener(&ddc->dcl);
+    g_clear_object(&ddc->iface_kbd);
+    g_clear_object(&ddc->iface);
+    g_clear_pointer(&ddc->listeners, g_hash_table_unref);
+    g_clear_pointer(&ddc->kbd, qkbd_state_free);
+
+    G_OBJECT_CLASS(dbus_display_console_parent_class)->dispose(object);
+}
+
+static void
+dbus_display_console_class_init(DBusDisplayConsoleClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+
+    gobject_class->dispose = dbus_display_console_dispose;
+}
+
+static void
+listener_vanished_cb(DBusDisplayListener *listener)
+{
+    DBusDisplayConsole *ddc = dbus_display_listener_get_console(listener);
+    const char *name = dbus_display_listener_get_bus_name(listener);
+
+    trace_dbus_listener_vanished(name);
+
+    g_hash_table_remove(ddc->listeners, name);
+    qkbd_state_lift_all_keys(ddc->kbd);
+}
+
+static gboolean
+dbus_console_set_ui_info(DBusDisplayConsole *ddc,
+                         GDBusMethodInvocation *invocation,
+                         guint16 arg_width_mm,
+                         guint16 arg_height_mm,
+                         gint arg_xoff,
+                         gint arg_yoff,
+                         guint arg_width,
+                         guint arg_height)
+{
+    QemuUIInfo info = {
+        .width_mm = arg_width_mm,
+        .height_mm = arg_height_mm,
+        .xoff = arg_xoff,
+        .yoff = arg_yoff,
+        .width = arg_width,
+        .height = arg_height,
+    };
+
+    if (!dpy_ui_info_supported(ddc->con)) {
+        g_dbus_method_invocation_return_error(invocation,
+                                              DBUS_DISPLAY_ERROR,
+                                              DBUS_DISPLAY_ERROR_UNSUPPORTED,
+                                              "SetUIInfo is not supported");
+        return DBUS_METHOD_INVOCATION_HANDLED;
+    }
+
+    dpy_set_ui_info(ddc->con, &info, false);
+    qemu_dbus_display1_console_complete_set_uiinfo(ddc->iface, invocation);
+    return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static gboolean
+dbus_console_register_listener(DBusDisplayConsole *ddc,
+                               GDBusMethodInvocation *invocation,
+                               GUnixFDList *fd_list,
+                               GVariant *arg_listener)
+{
+    const char *sender = g_dbus_method_invocation_get_sender(invocation);
+    GDBusConnection *listener_conn;
+    g_autoptr(GError) err = NULL;
+    g_autoptr(GSocket) socket = NULL;
+    g_autoptr(GSocketConnection) socket_conn = NULL;
+    g_autofree char *guid = g_dbus_generate_guid();
+    DBusDisplayListener *listener;
+    int fd;
+
+    if (g_hash_table_contains(ddc->listeners, sender)) {
+        g_dbus_method_invocation_return_error(
+            invocation,
+            DBUS_DISPLAY_ERROR,
+            DBUS_DISPLAY_ERROR_INVALID,
+            "`%s` is already registered!",
+            sender);
+        return DBUS_METHOD_INVOCATION_HANDLED;
+    }
+
+    fd = g_unix_fd_list_get(fd_list, g_variant_get_handle(arg_listener), &err);
+    if (err) {
+        g_dbus_method_invocation_return_error(
+            invocation,
+            DBUS_DISPLAY_ERROR,
+            DBUS_DISPLAY_ERROR_FAILED,
+            "Couldn't get peer fd: %s", err->message);
+        return DBUS_METHOD_INVOCATION_HANDLED;
+    }
+
+    socket = g_socket_new_from_fd(fd, &err);
+    if (err) {
+        g_dbus_method_invocation_return_error(
+            invocation,
+            DBUS_DISPLAY_ERROR,
+            DBUS_DISPLAY_ERROR_FAILED,
+            "Couldn't make a socket: %s", err->message);
+        close(fd);
+        return DBUS_METHOD_INVOCATION_HANDLED;
+    }
+    socket_conn = g_socket_connection_factory_create_connection(socket);
+
+    qemu_dbus_display1_console_complete_register_listener(
+        ddc->iface, invocation, NULL);
+
+    listener_conn = g_dbus_connection_new_sync(
+        G_IO_STREAM(socket_conn),
+        guid,
+        G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER,
+        NULL, NULL, &err);
+    if (err) {
+        error_report("Failed to setup peer connection: %s", err->message);
+        return DBUS_METHOD_INVOCATION_HANDLED;
+    }
+
+    listener = dbus_display_listener_new(sender, listener_conn, ddc);
+    if (!listener) {
+        return DBUS_METHOD_INVOCATION_HANDLED;
+    }
+
+    g_hash_table_insert(ddc->listeners,
+                        (gpointer)dbus_display_listener_get_bus_name(listener),
+                        listener);
+    g_object_connect(listener_conn,
+                     "swapped-signal::closed", listener_vanished_cb, listener,
+                     NULL);
+
+    trace_dbus_registered_listener(sender);
+    return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static gboolean
+dbus_kbd_press(DBusDisplayConsole *ddc,
+               GDBusMethodInvocation *invocation,
+               guint arg_keycode)
+{
+    QKeyCode qcode = qemu_input_key_number_to_qcode(arg_keycode);
+
+    trace_dbus_kbd_press(arg_keycode);
+
+    qkbd_state_key_event(ddc->kbd, qcode, true);
+
+    qemu_dbus_display1_keyboard_complete_press(ddc->iface_kbd, invocation);
+
+    return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static gboolean
+dbus_kbd_release(DBusDisplayConsole *ddc,
+                 GDBusMethodInvocation *invocation,
+                 guint arg_keycode)
+{
+    QKeyCode qcode = qemu_input_key_number_to_qcode(arg_keycode);
+
+    trace_dbus_kbd_release(arg_keycode);
+
+    qkbd_state_key_event(ddc->kbd, qcode, false);
+
+    qemu_dbus_display1_keyboard_complete_release(ddc->iface_kbd, invocation);
+
+    return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static void
+dbus_kbd_qemu_leds_updated(void *data, int ledstate)
+{
+    DBusDisplayConsole *ddc = DBUS_DISPLAY_CONSOLE(data);
+
+    qemu_dbus_display1_keyboard_set_modifiers(ddc->iface_kbd, ledstate);
+}
+
+static gboolean
+dbus_mouse_rel_motion(DBusDisplayConsole *ddc,
+                      GDBusMethodInvocation *invocation,
+                      int dx, int dy)
+{
+    trace_dbus_mouse_rel_motion(dx, dy);
+
+    if (qemu_input_is_absolute()) {
+        g_dbus_method_invocation_return_error(
+            invocation, DBUS_DISPLAY_ERROR,
+            DBUS_DISPLAY_ERROR_INVALID,
+            "Mouse is not relative");
+        return DBUS_METHOD_INVOCATION_HANDLED;
+    }
+
+    qemu_input_queue_rel(ddc->con, INPUT_AXIS_X, dx);
+    qemu_input_queue_rel(ddc->con, INPUT_AXIS_Y, dy);
+    qemu_input_event_sync();
+
+    qemu_dbus_display1_mouse_complete_rel_motion(ddc->iface_mouse,
+                                                    invocation);
+
+    return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static gboolean
+dbus_mouse_set_pos(DBusDisplayConsole *ddc,
+                   GDBusMethodInvocation *invocation,
+                   guint x, guint y)
+{
+    int width, height;
+
+    trace_dbus_mouse_set_pos(x, y);
+
+    if (!qemu_input_is_absolute()) {
+        g_dbus_method_invocation_return_error(
+            invocation, DBUS_DISPLAY_ERROR,
+            DBUS_DISPLAY_ERROR_INVALID,
+            "Mouse is not absolute");
+        return DBUS_METHOD_INVOCATION_HANDLED;
+    }
+
+    width = qemu_console_get_width(ddc->con, 0);
+    height = qemu_console_get_height(ddc->con, 0);
+    if (x >= width || y >= height) {
+        g_dbus_method_invocation_return_error(
+            invocation, DBUS_DISPLAY_ERROR,
+            DBUS_DISPLAY_ERROR_INVALID,
+            "Invalid mouse position");
+        return DBUS_METHOD_INVOCATION_HANDLED;
+    }
+    qemu_input_queue_abs(ddc->con, INPUT_AXIS_X, x, 0, width);
+    qemu_input_queue_abs(ddc->con, INPUT_AXIS_Y, y, 0, height);
+    qemu_input_event_sync();
+
+    qemu_dbus_display1_mouse_complete_set_abs_position(ddc->iface_mouse,
+                                                          invocation);
+
+    return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static gboolean
+dbus_mouse_press(DBusDisplayConsole *ddc,
+                 GDBusMethodInvocation *invocation,
+                 guint button)
+{
+    trace_dbus_mouse_press(button);
+
+    qemu_input_queue_btn(ddc->con, button, true);
+    qemu_input_event_sync();
+
+    qemu_dbus_display1_mouse_complete_press(ddc->iface_mouse, invocation);
+
+    return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static gboolean
+dbus_mouse_release(DBusDisplayConsole *ddc,
+                   GDBusMethodInvocation *invocation,
+                   guint button)
+{
+    trace_dbus_mouse_release(button);
+
+    qemu_input_queue_btn(ddc->con, button, false);
+    qemu_input_event_sync();
+
+    qemu_dbus_display1_mouse_complete_release(ddc->iface_mouse, invocation);
+
+    return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static void
+dbus_mouse_mode_change(Notifier *notify, void *data)
+{
+    DBusDisplayConsole *ddc =
+        container_of(notify, DBusDisplayConsole, mouse_mode_notifier);
+
+    g_object_set(ddc->iface_mouse,
+                 "is-absolute", qemu_input_is_absolute(),
+                 NULL);
+}
+
+int dbus_display_console_get_index(DBusDisplayConsole *ddc)
+{
+    return qemu_console_get_index(ddc->con);
+}
+
+DBusDisplayConsole *
+dbus_display_console_new(DBusDisplay *display, QemuConsole *con)
+{
+    g_autofree char *path = NULL;
+    g_autofree char *label = NULL;
+    char device_addr[256] = "";
+    DBusDisplayConsole *ddc;
+    int idx;
+
+    assert(display);
+    assert(con);
+
+    label = qemu_console_get_label(con);
+    idx = qemu_console_get_index(con);
+    path = g_strdup_printf(DBUS_DISPLAY1_ROOT "/Console_%d", idx);
+    ddc = g_object_new(DBUS_DISPLAY_TYPE_CONSOLE,
+                        "g-object-path", path,
+                        NULL);
+    ddc->display = display;
+    ddc->con = con;
+    /* handle errors, and skip non graphics? */
+    qemu_console_fill_device_address(
+        con, device_addr, sizeof(device_addr), NULL);
+
+    ddc->iface = qemu_dbus_display1_console_skeleton_new();
+    g_object_set(ddc->iface,
+        "label", label,
+        "type", qemu_console_is_graphic(con) ? "Graphic" : "Text",
+        "head", qemu_console_get_head(con),
+        "width", qemu_console_get_width(con, 0),
+        "height", qemu_console_get_height(con, 0),
+        "device-address", device_addr,
+        NULL);
+    g_object_connect(ddc->iface,
+        "swapped-signal::handle-register-listener",
+        dbus_console_register_listener, ddc,
+        "swapped-signal::handle-set-uiinfo",
+        dbus_console_set_ui_info, ddc,
+        NULL);
+    g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc),
+        G_DBUS_INTERFACE_SKELETON(ddc->iface));
+
+    ddc->kbd = qkbd_state_init(con);
+    ddc->iface_kbd = qemu_dbus_display1_keyboard_skeleton_new();
+    qemu_add_led_event_handler(dbus_kbd_qemu_leds_updated, ddc);
+    g_object_connect(ddc->iface_kbd,
+        "swapped-signal::handle-press", dbus_kbd_press, ddc,
+        "swapped-signal::handle-release", dbus_kbd_release, ddc,
+        NULL);
+    g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc),
+        G_DBUS_INTERFACE_SKELETON(ddc->iface_kbd));
+
+    ddc->iface_mouse = qemu_dbus_display1_mouse_skeleton_new();
+    g_object_connect(ddc->iface_mouse,
+        "swapped-signal::handle-set-abs-position", dbus_mouse_set_pos, ddc,
+        "swapped-signal::handle-rel-motion", dbus_mouse_rel_motion, ddc,
+        "swapped-signal::handle-press", dbus_mouse_press, ddc,
+        "swapped-signal::handle-release", dbus_mouse_release, ddc,
+        NULL);
+    g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc),
+        G_DBUS_INTERFACE_SKELETON(ddc->iface_mouse));
+
+    register_displaychangelistener(&ddc->dcl);
+    ddc->mouse_mode_notifier.notify = dbus_mouse_mode_change;
+    qemu_add_mouse_mode_change_notifier(&ddc->mouse_mode_notifier);
+
+    return ddc;
+}
diff --git a/ui/dbus-error.c b/ui/dbus-error.c
new file mode 100644
index 0000000000..85a9194d57
--- /dev/null
+++ b/ui/dbus-error.c
@@ -0,0 +1,48 @@
+/*
+ * QEMU DBus display errors
+ *
+ * Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include "qemu/osdep.h"
+#include "dbus.h"
+
+static const GDBusErrorEntry dbus_display_error_entries[] = {
+    { DBUS_DISPLAY_ERROR_FAILED, "org.qemu.Display1.Error.Failed" },
+    { DBUS_DISPLAY_ERROR_INVALID, "org.qemu.Display1.Error.Invalid" },
+    { DBUS_DISPLAY_ERROR_UNSUPPORTED, "org.qemu.Display1.Error.Unsupported" },
+};
+
+G_STATIC_ASSERT(G_N_ELEMENTS(dbus_display_error_entries) ==
+                DBUS_DISPLAY_N_ERRORS);
+
+GQuark
+dbus_display_error_quark(void)
+{
+    static gsize quark;
+
+    g_dbus_error_register_error_domain(
+        "dbus-display-error-quark",
+        &quark,
+        dbus_display_error_entries,
+        G_N_ELEMENTS(dbus_display_error_entries));
+
+    return (GQuark)quark;
+}
diff --git a/ui/dbus-listener.c b/ui/dbus-listener.c
new file mode 100644
index 0000000000..20094fc18a
--- /dev/null
+++ b/ui/dbus-listener.c
@@ -0,0 +1,486 @@
+/*
+ * QEMU DBus display console
+ *
+ * Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include "qemu/osdep.h"
+#include "sysemu/sysemu.h"
+#include "dbus.h"
+#include <gio/gunixfdlist.h>
+
+#include "ui/shader.h"
+#include "ui/egl-helpers.h"
+#include "ui/egl-context.h"
+#include "trace.h"
+
+struct _DBusDisplayListener {
+    GObject parent;
+
+    char *bus_name;
+    DBusDisplayConsole *console;
+    GDBusConnection *conn;
+
+    QemuDBusDisplay1Listener *proxy;
+
+    DisplayChangeListener dcl;
+    DisplaySurface *ds;
+    QemuGLShader *gls;
+    int gl_updates;
+};
+
+G_DEFINE_TYPE(DBusDisplayListener, dbus_display_listener, G_TYPE_OBJECT)
+
+static void dbus_update_gl_cb(GObject *source_object,
+                           GAsyncResult *res,
+                           gpointer user_data)
+{
+    g_autoptr(GError) err = NULL;
+    DBusDisplayListener *ddl = user_data;
+
+    if (!qemu_dbus_display1_listener_call_update_dmabuf_finish(ddl->proxy,
+                                                               res, &err)) {
+        error_report("Failed to call update: %s", err->message);
+    }
+
+    graphic_hw_gl_block(ddl->dcl.con, false);
+    g_object_unref(ddl);
+}
+
+static void dbus_call_update_gl(DBusDisplayListener *ddl,
+                                int x, int y, int w, int h)
+{
+    graphic_hw_gl_block(ddl->dcl.con, true);
+    glFlush();
+    qemu_dbus_display1_listener_call_update_dmabuf(ddl->proxy,
+        x, y, w, h,
+        G_DBUS_CALL_FLAGS_NONE,
+        DBUS_DEFAULT_TIMEOUT, NULL,
+        dbus_update_gl_cb,
+        g_object_ref(ddl));
+}
+
+static void dbus_scanout_disable(DisplayChangeListener *dcl)
+{
+    DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
+
+    ddl->ds = NULL;
+    qemu_dbus_display1_listener_call_disable(
+        ddl->proxy, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+}
+
+static void dbus_scanout_dmabuf(DisplayChangeListener *dcl,
+                                QemuDmaBuf *dmabuf)
+{
+    DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
+    g_autoptr(GError) err = NULL;
+    g_autoptr(GUnixFDList) fd_list = NULL;
+
+    fd_list = g_unix_fd_list_new();
+    if (g_unix_fd_list_append(fd_list, dmabuf->fd, &err) != 0) {
+        error_report("Failed to setup dmabuf fdlist: %s", err->message);
+        return;
+    }
+
+    qemu_dbus_display1_listener_call_scanout_dmabuf(
+        ddl->proxy,
+        g_variant_new_handle(0),
+        dmabuf->width,
+        dmabuf->height,
+        dmabuf->stride,
+        dmabuf->fourcc,
+        dmabuf->modifier,
+        dmabuf->y0_top,
+        G_DBUS_CALL_FLAGS_NONE,
+        -1,
+        fd_list,
+        NULL, NULL, NULL);
+}
+
+static void 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)
+{
+    QemuDmaBuf dmabuf = {
+        .width = backing_width,
+        .height = backing_height,
+        .y0_top = backing_y_0_top,
+    };
+
+    assert(tex_id);
+    dmabuf.fd = egl_get_fd_for_texture(
+        tex_id, (EGLint *)&dmabuf.stride,
+        (EGLint *)&dmabuf.fourcc,
+        &dmabuf.modifier);
+    if (dmabuf.fd < 0) {
+        error_report("%s: failed to get fd for texture", __func__);
+        return;
+    }
+
+    dbus_scanout_dmabuf(dcl, &dmabuf);
+    close(dmabuf.fd);
+}
+
+static void dbus_cursor_dmabuf(DisplayChangeListener *dcl,
+                               QemuDmaBuf *dmabuf, bool have_hot,
+                               uint32_t hot_x, uint32_t hot_y)
+{
+    DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
+    DisplaySurface *ds;
+    GVariant *v_data = NULL;
+    egl_fb cursor_fb;
+
+    if (!dmabuf) {
+        qemu_dbus_display1_listener_call_mouse_set(
+            ddl->proxy, 0, 0, false,
+            G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+        return;
+    }
+
+    egl_dmabuf_import_texture(dmabuf);
+    if (!dmabuf->texture) {
+        return;
+    }
+    egl_fb_setup_for_tex(&cursor_fb, dmabuf->width, dmabuf->height,
+                         dmabuf->texture, false);
+    ds = qemu_create_displaysurface(dmabuf->width, dmabuf->height);
+    egl_fb_read(ds, &cursor_fb);
+
+    v_data = g_variant_new_from_data(
+        G_VARIANT_TYPE("ay"),
+        surface_data(ds),
+        surface_width(ds) * surface_height(ds) * 4,
+        TRUE,
+        (GDestroyNotify)qemu_free_displaysurface,
+        ds);
+    qemu_dbus_display1_listener_call_cursor_define(
+        ddl->proxy,
+        surface_width(ds),
+        surface_height(ds),
+        hot_x,
+        hot_y,
+        v_data,
+        G_DBUS_CALL_FLAGS_NONE,
+        -1,
+        NULL,
+        NULL,
+        NULL);
+}
+
+static void dbus_cursor_position(DisplayChangeListener *dcl,
+                                 uint32_t pos_x, uint32_t pos_y)
+{
+    DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
+
+    qemu_dbus_display1_listener_call_mouse_set(
+        ddl->proxy, pos_x, pos_y, true,
+        G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+}
+
+static void dbus_release_dmabuf(DisplayChangeListener *dcl,
+                                QemuDmaBuf *dmabuf)
+{
+    dbus_scanout_disable(dcl);
+}
+
+static void dbus_scanout_update(DisplayChangeListener *dcl,
+                                uint32_t x, uint32_t y,
+                                uint32_t w, uint32_t h)
+{
+    DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
+
+    dbus_call_update_gl(ddl, x, y, w, h);
+}
+
+static void dbus_gl_refresh(DisplayChangeListener *dcl)
+{
+    DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
+
+    graphic_hw_update(dcl->con);
+
+    if (!ddl->ds || qemu_console_is_gl_blocked(ddl->dcl.con)) {
+        return;
+    }
+
+    if (ddl->gl_updates) {
+        dbus_call_update_gl(ddl, 0, 0,
+                            surface_width(ddl->ds), surface_height(ddl->ds));
+        ddl->gl_updates = 0;
+    }
+}
+
+static void dbus_refresh(DisplayChangeListener *dcl)
+{
+    graphic_hw_update(dcl->con);
+}
+
+static void dbus_gl_gfx_update(DisplayChangeListener *dcl,
+                               int x, int y, int w, int h)
+{
+    DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
+
+    if (ddl->ds) {
+        surface_gl_update_texture(ddl->gls, ddl->ds, x, y, w, h);
+    }
+
+    ddl->gl_updates++;
+}
+
+static void dbus_gfx_update(DisplayChangeListener *dcl,
+                            int x, int y, int w, int h)
+{
+    DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
+    pixman_image_t *img;
+    GVariant *v_data;
+    size_t stride;
+
+    assert(ddl->ds);
+    stride = w * DIV_ROUND_UP(PIXMAN_FORMAT_BPP(surface_format(ddl->ds)), 8);
+
+    trace_dbus_update(x, y, w, h);
+
+    /* make a copy, since gvariant only handles linear data */
+    img = pixman_image_create_bits(surface_format(ddl->ds),
+                                   w, h, NULL, stride);
+    pixman_image_composite(PIXMAN_OP_SRC, ddl->ds->image, NULL, img,
+                           x, y, 0, 0, 0, 0, w, h);
+
+    v_data = g_variant_new_from_data(
+        G_VARIANT_TYPE("ay"),
+        pixman_image_get_data(img),
+        pixman_image_get_stride(img) * h,
+        TRUE,
+        (GDestroyNotify)pixman_image_unref,
+        img);
+    qemu_dbus_display1_listener_call_update(ddl->proxy,
+        x, y, w, h, pixman_image_get_stride(img), pixman_image_get_format(img),
+        v_data,
+        G_DBUS_CALL_FLAGS_NONE,
+        DBUS_DEFAULT_TIMEOUT, NULL, NULL, NULL);
+}
+
+static void dbus_gl_gfx_switch(DisplayChangeListener *dcl,
+                               struct DisplaySurface *new_surface)
+{
+    DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
+
+    if (ddl->ds) {
+        surface_gl_destroy_texture(ddl->gls, ddl->ds);
+    }
+    ddl->ds = new_surface;
+    if (ddl->ds) {
+        int width = surface_width(ddl->ds);
+        int height = surface_height(ddl->ds);
+
+        surface_gl_create_texture(ddl->gls, ddl->ds);
+        /* TODO: lazy send dmabuf (there are unnecessary sent otherwise) */
+        dbus_scanout_texture(&ddl->dcl, ddl->ds->texture, false,
+                             width, height, 0, 0, width, height);
+    }
+}
+
+static void dbus_gfx_switch(DisplayChangeListener *dcl,
+                            struct DisplaySurface *new_surface)
+{
+    DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
+    GVariant *v_data = NULL;
+
+    ddl->ds = new_surface;
+    if (!ddl->ds) {
+        /* why not call disable instead? */
+        return;
+    }
+
+    v_data = g_variant_new_from_data(
+        G_VARIANT_TYPE("ay"),
+        surface_data(ddl->ds),
+        surface_stride(ddl->ds) * surface_height(ddl->ds),
+        TRUE,
+        (GDestroyNotify)pixman_image_unref,
+        pixman_image_ref(ddl->ds->image));
+    qemu_dbus_display1_listener_call_scanout(ddl->proxy,
+        surface_width(ddl->ds),
+        surface_height(ddl->ds),
+        surface_stride(ddl->ds),
+        surface_format(ddl->ds),
+        v_data,
+        G_DBUS_CALL_FLAGS_NONE,
+        DBUS_DEFAULT_TIMEOUT, NULL, NULL, NULL);
+}
+
+static void dbus_mouse_set(DisplayChangeListener *dcl,
+                           int x, int y, int on)
+{
+    DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
+
+    qemu_dbus_display1_listener_call_mouse_set(
+        ddl->proxy, x, y, on, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+}
+
+static void dbus_cursor_define(DisplayChangeListener *dcl,
+                               QEMUCursor *c)
+{
+    DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
+    GVariant *v_data = NULL;
+
+    cursor_get(c);
+    v_data = g_variant_new_from_data(
+        G_VARIANT_TYPE("ay"),
+        c->data,
+        c->width * c->height * 4,
+        TRUE,
+        (GDestroyNotify)cursor_put,
+        c);
+
+    qemu_dbus_display1_listener_call_cursor_define(
+        ddl->proxy,
+        c->width,
+        c->height,
+        c->hot_x,
+        c->hot_y,
+        v_data,
+        G_DBUS_CALL_FLAGS_NONE,
+        -1,
+        NULL,
+        NULL,
+        NULL);
+}
+
+const DisplayChangeListenerOps dbus_gl_dcl_ops = {
+    .dpy_name                = "dbus-gl",
+    .dpy_gfx_update          = dbus_gl_gfx_update,
+    .dpy_gfx_switch          = dbus_gl_gfx_switch,
+    .dpy_gfx_check_format    = console_gl_check_format,
+    .dpy_refresh             = dbus_gl_refresh,
+    .dpy_mouse_set           = dbus_mouse_set,
+    .dpy_cursor_define       = dbus_cursor_define,
+
+    .dpy_gl_scanout_disable  = dbus_scanout_disable,
+    .dpy_gl_scanout_texture  = dbus_scanout_texture,
+    .dpy_gl_scanout_dmabuf   = dbus_scanout_dmabuf,
+    .dpy_gl_cursor_dmabuf    = dbus_cursor_dmabuf,
+    .dpy_gl_cursor_position  = dbus_cursor_position,
+    .dpy_gl_release_dmabuf   = dbus_release_dmabuf,
+    .dpy_gl_update           = dbus_scanout_update,
+};
+
+const DisplayChangeListenerOps dbus_dcl_ops = {
+    .dpy_name                = "dbus",
+    .dpy_gfx_update          = dbus_gfx_update,
+    .dpy_gfx_switch          = dbus_gfx_switch,
+    .dpy_refresh             = dbus_refresh,
+    .dpy_mouse_set           = dbus_mouse_set,
+    .dpy_cursor_define       = dbus_cursor_define,
+};
+
+static void
+dbus_display_listener_dispose(GObject *object)
+{
+    DBusDisplayListener *ddl = DBUS_DISPLAY_LISTENER(object);
+
+    unregister_displaychangelistener(&ddl->dcl);
+    g_clear_object(&ddl->conn);
+    g_clear_pointer(&ddl->bus_name, g_free);
+    g_clear_object(&ddl->proxy);
+    g_clear_pointer(&ddl->gls, qemu_gl_fini_shader);
+
+    G_OBJECT_CLASS(dbus_display_listener_parent_class)->dispose(object);
+}
+
+static void
+dbus_display_listener_constructed(GObject *object)
+{
+    DBusDisplayListener *ddl = DBUS_DISPLAY_LISTENER(object);
+
+    if (display_opengl) {
+        ddl->gls = qemu_gl_init_shader();
+        ddl->dcl.ops = &dbus_gl_dcl_ops;
+    } else {
+        ddl->dcl.ops = &dbus_dcl_ops;
+    }
+
+    G_OBJECT_CLASS(dbus_display_listener_parent_class)->constructed(object);
+}
+
+static void
+dbus_display_listener_class_init(DBusDisplayListenerClass *klass)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+    object_class->dispose = dbus_display_listener_dispose;
+    object_class->constructed = dbus_display_listener_constructed;
+}
+
+static void
+dbus_display_listener_init(DBusDisplayListener *ddl)
+{
+}
+
+const char *
+dbus_display_listener_get_bus_name(DBusDisplayListener *ddl)
+{
+    return ddl->bus_name;
+}
+
+DBusDisplayConsole *
+dbus_display_listener_get_console(DBusDisplayListener *ddl)
+{
+    return ddl->console;
+}
+
+DBusDisplayListener *
+dbus_display_listener_new(const char *bus_name,
+                          GDBusConnection *conn,
+                          DBusDisplayConsole *console)
+{
+    DBusDisplayListener *ddl;
+    QemuConsole *con;
+    g_autoptr(GError) err = NULL;
+
+    ddl = g_object_new(DBUS_DISPLAY_TYPE_LISTENER, NULL);
+    ddl->proxy =
+        qemu_dbus_display1_listener_proxy_new_sync(conn,
+            G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+            NULL,
+            "/org/qemu/Display1/Listener",
+            NULL,
+            &err);
+    if (!ddl->proxy) {
+        error_report("Failed to setup proxy: %s", err->message);
+        g_object_unref(conn);
+        g_object_unref(ddl);
+        return NULL;
+    }
+
+    ddl->bus_name = g_strdup(bus_name);
+    ddl->conn = conn;
+    ddl->console = console;
+
+    con = qemu_console_lookup_by_index(dbus_display_console_get_index(console));
+    assert(con);
+    ddl->dcl.con = con;
+    register_displaychangelistener(&ddl->dcl);
+
+    return ddl;
+}
diff --git a/ui/dbus.c b/ui/dbus.c
new file mode 100644
index 0000000000..12da8ffe31
--- /dev/null
+++ b/ui/dbus.c
@@ -0,0 +1,262 @@
+/*
+ * QEMU DBus display
+ *
+ * Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include "qemu/osdep.h"
+#include "qemu/dbus.h"
+#include "qemu/option.h"
+#include "qom/object_interfaces.h"
+#include "sysemu/sysemu.h"
+#include "ui/egl-helpers.h"
+#include "ui/egl-context.h"
+#include "qapi/error.h"
+#include "trace.h"
+
+#include "dbus.h"
+
+static QEMUGLContext dbus_create_context(DisplayGLCtx *dgc,
+                                         QEMUGLParams *params)
+{
+    eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
+                   qemu_egl_rn_ctx);
+    return qemu_egl_create_context(dgc, params);
+}
+
+static const DisplayGLCtxOps dbus_gl_ops = {
+    .compatible_dcl          = &dbus_gl_dcl_ops,
+    .dpy_gl_ctx_create       = dbus_create_context,
+    .dpy_gl_ctx_destroy      = qemu_egl_destroy_context,
+    .dpy_gl_ctx_make_current = qemu_egl_make_context_current,
+};
+
+static void
+dbus_display_init(Object *o)
+{
+    DBusDisplay *dd = DBUS_DISPLAY(o);
+    g_autoptr(GDBusObjectSkeleton) vm = NULL;
+
+    dd->glctx.ops = &dbus_gl_ops;
+    dd->iface = qemu_dbus_display1_vm_skeleton_new();
+    dd->consoles = g_ptr_array_new_with_free_func(g_object_unref);
+
+    dd->server = g_dbus_object_manager_server_new(DBUS_DISPLAY1_ROOT);
+
+    vm = g_dbus_object_skeleton_new(DBUS_DISPLAY1_ROOT "/VM");
+    g_dbus_object_skeleton_add_interface(
+        vm, G_DBUS_INTERFACE_SKELETON(dd->iface));
+    g_dbus_object_manager_server_export(dd->server, vm);
+}
+
+static void
+dbus_display_finalize(Object *o)
+{
+    DBusDisplay *dd = DBUS_DISPLAY(o);
+
+    g_clear_object(&dd->server);
+    g_clear_pointer(&dd->consoles, g_ptr_array_unref);
+    g_clear_object(&dd->bus);
+    g_clear_object(&dd->iface);
+    g_free(dd->dbus_addr);
+}
+
+static bool
+dbus_display_add_console(DBusDisplay *dd, int idx, Error **errp)
+{
+    QemuConsole *con;
+    DBusDisplayConsole *dbus_console;
+
+    con = qemu_console_lookup_by_index(idx);
+    assert(con);
+
+    if (qemu_console_is_graphic(con) &&
+        dd->gl_mode != DISPLAYGL_MODE_OFF) {
+        qemu_console_set_display_gl_ctx(con, &dd->glctx);
+    }
+
+    dbus_console = dbus_display_console_new(dd, con);
+    g_ptr_array_insert(dd->consoles, idx, dbus_console);
+    g_dbus_object_manager_server_export(dd->server,
+                                        G_DBUS_OBJECT_SKELETON(dbus_console));
+    return true;
+}
+
+static void
+dbus_display_complete(UserCreatable *uc, Error **errp)
+{
+    DBusDisplay *dd = DBUS_DISPLAY(uc);
+    g_autoptr(GError) err = NULL;
+    g_autofree char *uuid = qemu_uuid_unparse_strdup(&qemu_uuid);
+    g_autoptr(GArray) consoles = NULL;
+    GVariant *console_ids;
+    int idx;
+
+    if (!object_resolve_path_type("", TYPE_DBUS_DISPLAY, NULL)) {
+        error_setg(errp, "There is already an instance of %s",
+                   TYPE_DBUS_DISPLAY);
+        return;
+    }
+
+    if (dd->dbus_addr && *dd->dbus_addr) {
+        dd->bus = g_dbus_connection_new_for_address_sync(dd->dbus_addr,
+                        G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
+                        G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
+                        NULL, NULL, &err);
+    } else {
+        dd->bus = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &err);
+    }
+    if (err) {
+        error_setg(errp, "failed to connect to DBus: %s", err->message);
+        return;
+    }
+
+
+    consoles = g_array_new(FALSE, FALSE, sizeof(guint32));
+    for (idx = 0;; idx++) {
+        if (!qemu_console_lookup_by_index(idx)) {
+            break;
+        }
+        if (!dbus_display_add_console(dd, idx, errp)) {
+            return;
+        }
+        g_array_append_val(consoles, idx);
+    }
+
+    console_ids = g_variant_new_from_data(
+        G_VARIANT_TYPE("au"),
+        consoles->data, consoles->len * sizeof(guint32), TRUE,
+        (GDestroyNotify)g_array_unref, consoles);
+    g_steal_pointer(&consoles);
+    g_object_set(dd->iface,
+                 "name", qemu_name ?: "QEMU " QEMU_VERSION,
+                 "uuid", uuid,
+                 "console-ids", console_ids,
+                 NULL);
+
+    g_dbus_object_manager_server_set_connection(dd->server, dd->bus);
+    g_bus_own_name_on_connection(dd->bus, "org.qemu",
+                                 G_BUS_NAME_OWNER_FLAGS_NONE,
+                                 NULL, NULL, NULL, NULL);
+}
+
+static char *
+get_dbus_addr(Object *o, Error **errp)
+{
+    DBusDisplay *dd = DBUS_DISPLAY(o);
+
+    return g_strdup(dd->dbus_addr);
+}
+
+static void
+set_dbus_addr(Object *o, const char *str, Error **errp)
+{
+    DBusDisplay *dd = DBUS_DISPLAY(o);
+
+    g_free(dd->dbus_addr);
+    dd->dbus_addr = g_strdup(str);
+}
+
+static int
+get_gl_mode(Object *o, Error **errp)
+{
+    DBusDisplay *dd = DBUS_DISPLAY(o);
+
+    return dd->gl_mode;
+}
+
+static void
+set_gl_mode(Object *o, int val, Error **errp)
+{
+    DBusDisplay *dd = DBUS_DISPLAY(o);
+
+    dd->gl_mode = val;
+}
+
+static void
+dbus_display_class_init(ObjectClass *oc, void *data)
+{
+    UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
+
+    ucc->complete = dbus_display_complete;
+    object_class_property_add_str(oc, "addr", get_dbus_addr, set_dbus_addr);
+    object_class_property_add_enum(oc, "gl-mode",
+                                   "DisplayGLMode", &DisplayGLMode_lookup,
+                                   get_gl_mode, set_gl_mode);
+}
+
+static void
+early_dbus_init(DisplayOptions *opts)
+{
+    DisplayGLMode mode = opts->has_gl ? opts->gl : DISPLAYGL_MODE_OFF;
+
+    if (mode != DISPLAYGL_MODE_OFF) {
+        if (egl_rendernode_init(opts->u.dbus.rendernode, mode) < 0) {
+            error_report("dbus: render node init failed");
+            exit(1);
+        }
+
+        display_opengl = 1;
+    }
+}
+
+static void
+dbus_init(DisplayState *ds, DisplayOptions *opts)
+{
+    DisplayGLMode mode = opts->has_gl ? opts->gl : DISPLAYGL_MODE_OFF;
+
+    object_new_with_props(TYPE_DBUS_DISPLAY,
+                          object_get_objects_root(),
+                          "dbus-display", &error_fatal,
+                          "addr", opts->u.dbus.addr ?: "",
+                          "gl-mode", DisplayGLMode_str(mode),
+                          NULL);
+}
+
+static const TypeInfo dbus_display_info = {
+    .name = TYPE_DBUS_DISPLAY,
+    .parent = TYPE_OBJECT,
+    .instance_size = sizeof(DBusDisplay),
+    .instance_init = dbus_display_init,
+    .instance_finalize = dbus_display_finalize,
+    .class_init = dbus_display_class_init,
+    .interfaces = (InterfaceInfo[]) {
+        { TYPE_USER_CREATABLE },
+        { }
+    }
+};
+
+static QemuDisplay qemu_display_dbus = {
+    .type       = DISPLAY_TYPE_DBUS,
+    .early_init = early_dbus_init,
+    .init       = dbus_init,
+};
+
+static void register_dbus(void)
+{
+    type_register_static(&dbus_display_info);
+    qemu_display_register(&qemu_display_dbus);
+}
+
+type_init(register_dbus);
+
+#ifdef CONFIG_OPENGL
+module_dep("ui-opengl");
+#endif
diff --git a/meson_options.txt b/meson_options.txt
index b064b0b46c..0a6abb311e 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -49,6 +49,8 @@ option('cfi_debug', type: 'boolean', value: 'false',
        description: 'Verbose errors in case of CFI violation')
 option('multiprocess', type: 'feature', value: 'auto',
        description: 'Out of process device emulation support')
+option('dbus_display', type: 'feature', value: 'auto',
+       description: '-display dbus support')
 
 option('attr', type : 'feature', value : 'auto',
        description: 'attr/xattr support')
diff --git a/qemu-options.hx b/qemu-options.hx
index 5f375bbfa6..1a927458e6 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -1862,6 +1862,10 @@ DEF("display", HAS_ARG, QEMU_OPTION_display,
 #endif
 #if defined(CONFIG_OPENGL)
     "-display egl-headless[,rendernode=<file>]\n"
+#endif
+#if defined(CONFIG_DBUS_DISPLAY)
+    "-display dbus[,addr=<dbusaddr>]\n"
+    "             [,gl=on|core|es|off][,rendernode=<file>]\n"
 #endif
     "-display none\n"
     "                select display backend type\n"
@@ -1889,6 +1893,17 @@ SRST
         application. The Spice server will redirect the serial consoles
         and QEMU monitors. (Since 4.0)
 
+    ``dbus``
+        Export the display over D-Bus interfaces. (Since 6.2)
+
+        The connection is registered with the "org.qemu" name (and queued when
+        already owned).
+
+        ``addr=<dbusaddr>`` : D-Bus bus address to connect to.
+
+        ``gl=on|off|core|es`` : Use OpenGL for rendering (the D-interface will
+        share framebuffers with DMABUF file descriptors).
+
     ``sdl``
         Display video output via SDL (usually in a separate graphics
         window; see the SDL documentation for other possibilities).
diff --git a/ui/dbus-display1.xml b/ui/dbus-display1.xml
index e69de29bb2..0f0ae92e4d 100644
--- a/ui/dbus-display1.xml
+++ b/ui/dbus-display1.xml
@@ -0,0 +1,378 @@
+<?xml version="1.0" encoding="utf-8"?>
+<node>
+  <!--
+      org.qemu.Display1.VM:
+
+      This interface is implemented on ``/org/qemu/Display1/VM``.
+  -->
+  <interface name="org.qemu.Display1.VM">
+    <!--
+        Name:
+
+        The name of the VM.
+    -->
+    <property name="Name" type="s" access="read"/>
+
+    <!--
+        UUID:
+
+        The UUID of the VM.
+    -->
+    <property name="UUID" type="s" access="read"/>
+
+    <!--
+        ConsoleIDs:
+
+        The list of consoles available on ``/org/qemu/Display1/Console_$id``.
+    -->
+    <property name="ConsoleIDs" type="au" access="read"/>
+  </interface>
+
+  <!--
+      org.qemu.Display1.Console:
+
+      This interface is implemented on ``/org/qemu/Display1/Console_$id``. You
+      may discover available consoles through introspection or with the
+      :dbus:prop:`org.qemu.Display1.VM.ConsoleIDs` property.
+
+      A console is attached to a video device head. It may be "Graphic" or
+      "Text" (see :dbus:prop:`Type` and other properties).
+
+      Interactions with a console may be done with
+      :dbus:iface:`org.qemu.Display1.Keyboard` and
+      :dbus:iface:`org.qemu.Display1.Mouse` interfaces when available.
+  -->
+  <interface name="org.qemu.Display1.Console">
+    <!--
+        RegisterListener:
+        @listener: a Unix socket FD, for peer-to-peer D-Bus communication.
+
+        Register a console listener, which will receive display updates, until
+        it is disconnected.
+
+        Multiple listeners may be registered simultaneously.
+
+        The listener is expected to implement the
+        :dbus:iface:`org.qemu.Display1.Listener` interface.
+    -->
+    <method name="RegisterListener">
+      <arg type="h" name="listener" direction="in"/>
+    </method>
+
+    <!--
+        SetUIInfo:
+        @width_mm: the physical display width in millimeters.
+        @height_mm: the physical display height in millimeters.
+        @xoff: horizontal offset, in pixels.
+        @yoff: vertical offset, in pixels.
+        @width: console width, in pixels.
+        @height: console height, in pixels.
+
+        Modify the dimensions and display settings.
+    -->
+    <method name="SetUIInfo">
+      <arg name="width_mm" type="q" direction="in"/>
+      <arg name="height_mm" type="q" direction="in"/>
+      <arg name="xoff" type="i" direction="in"/>
+      <arg name="yoff" type="i" direction="in"/>
+      <arg name="width" type="u" direction="in"/>
+      <arg name="height" type="u" direction="in"/>
+    </method>
+
+    <!--
+        Label:
+
+        A user-friendly name for the console (for ex: "VGA").
+    -->
+    <property name="Label" type="s" access="read"/>
+
+    <!--
+        Head:
+
+        Graphical device head number.
+    -->
+    <property name="Head" type="u" access="read"/>
+
+    <!--
+        Type:
+
+        Console type ("Graphic" or "Text").
+    -->
+    <property name="Type" type="s" access="read"/>
+
+    <!--
+        Width:
+
+        Console width, in pixels.
+    -->
+    <property name="Width" type="u" access="read"/>
+
+    <!--
+        Height:
+
+        Console height, in pixels.
+    -->
+    <property name="Height" type="u" access="read"/>
+
+    <!--
+        DeviceAddress:
+
+        The device address (ex: "pci/0000/02.0").
+    -->
+    <property name="DeviceAddress" type="s" access="read"/>
+  </interface>
+
+  <!--
+      org.qemu.Display1.Keyboard:
+
+      This interface in implemented on ``/org/qemu/Display1/Console_$id`` (see
+      :dbus:iface:`~org.qemu.Display1.Console`).
+  -->
+  <interface name="org.qemu.Display1.Keyboard">
+    <!--
+        Press:
+        @keycode: QEMU key number (xtkbd + special re-encoding of high bit)
+
+        Send a key press event.
+    -->
+    <method name="Press">
+      <arg type="u" name="keycode" direction="in"/>
+    </method>
+
+    <!--
+        Release:
+        @keycode: QEMU key number (xtkbd + special re-encoding of high bit)
+
+        Send a key release event.
+    -->
+    <method name="Release">
+      <arg type="u" name="keycode" direction="in"/>
+    </method>
+
+    <!--
+        Modifiers:
+
+        The active keyboard modifiers::
+
+          Scroll = 1 << 0
+          Num    = 1 << 1
+          Caps   = 1 << 2
+    -->
+    <property name="Modifiers" type="u" access="read"/>
+  </interface>
+
+  <!--
+      org.qemu.Display1.Mouse:
+
+      This interface in implemented on ``/org/qemu/Display1/Console_$id`` (see
+      :dbus:iface:`~org.qemu.Display1.Console` documentation).
+
+      .. _dbus-button-values:
+
+      **Button values**::
+
+        Left       = 0
+        Middle     = 1
+        Right      = 2
+        Wheel-up   = 3
+        Wheel-down = 4
+        Side       = 5
+        Extra      = 6
+  -->
+  <interface name="org.qemu.Display1.Mouse">
+    <!--
+        Press:
+        @button: :ref:`button value<dbus-button-values>`.
+
+        Send a mouse button press event.
+    -->
+    <method name="Press">
+      <arg type="u" name="button" direction="in"/>
+    </method>
+
+    <!--
+        Release:
+        @button: :ref:`button value<dbus-button-values>`.
+
+        Send a mouse button release event.
+    -->
+    <method name="Release">
+      <arg type="u" name="button" direction="in"/>
+    </method>
+
+    <!--
+        SetAbsPosition:
+        @x: X position, in pixels.
+        @y: Y position, in pixels.
+
+        Set the mouse pointer position.
+
+        Returns an error if not :dbus:prop:`IsAbsolute`.
+    -->
+    <method name="SetAbsPosition">
+      <arg type="u" name="x" direction="in"/>
+      <arg type="u" name="y" direction="in"/>
+    </method>
+
+    <!--
+        RelMotion:
+        @dx: X-delta, in pixels.
+        @dy: Y-delta, in pixels.
+
+        Move the mouse pointer position, relative to the current position.
+
+        Returns an error if :dbus:prop:`IsAbsolute`.
+    -->
+    <method name="RelMotion">
+      <arg type="i" name="dx" direction="in"/>
+      <arg type="i" name="dy" direction="in"/>
+    </method>
+
+    <!--
+        IsAbsolute:
+
+        Whether the mouse is using absolute movements.
+    -->
+    <property name="IsAbsolute" type="b" access="read"/>
+  </interface>
+
+  <!--
+      org.qemu.Display1.Listener:
+
+      This client-side interface must be available on
+      ``/org/qemu/Display1/Listener`` when registering the peer-to-peer
+      connection with :dbus:meth:`~org.qemu.Display1.Console.Register`.
+  -->
+  <interface name="org.qemu.Display1.Listener">
+    <!--
+        Scanout:
+        @width: display width, in pixels.
+        @height: display height, in pixels.
+        @stride: data stride, in bytes.
+        @pixman_format: image format (ex: ``PIXMAN_X8R8G8B8``).
+        @data: image data.
+
+        Resize and update the display content.
+
+        The data to transfer for the display update may be large. The preferred
+        scanout method is :dbus:meth:`ScanoutDMABUF`, used whenever possible.
+    -->
+    <method name="Scanout">
+      <arg type="u" name="width" direction="in"/>
+      <arg type="u" name="height" direction="in"/>
+      <arg type="u" name="stride" direction="in"/>
+      <arg type="u" name="pixman_format" direction="in"/>
+      <arg type="ay" name="data" direction="in">
+        <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
+      </arg>
+    </method>
+
+    <!--
+        Update:
+        @x: X update position, in pixels.
+        @y: Y update position, in pixels.
+        @width: update width, in pixels.
+        @height: update height, in pixels.
+        @stride: data stride, in bytes.
+        @pixman_format: image format (ex: ``PIXMAN_X8R8G8B8``).
+        @data: display image data.
+
+        Update the display content.
+
+        This method is only called after a :dbus:meth:`Scanout` call.
+    -->
+    <method name="Update">
+      <arg type="i" name="x" direction="in"/>
+      <arg type="i" name="y" direction="in"/>
+      <arg type="i" name="width" direction="in"/>
+      <arg type="i" name="height" direction="in"/>
+      <arg type="u" name="stride" direction="in"/>
+      <arg type="u" name="pixman_format" direction="in"/>
+      <arg type="ay" name="data" direction="in">
+        <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
+      </arg>
+    </method>
+
+    <!--
+        ScanoutDMABUF:
+        @dmabuf: the DMABUF file descriptor.
+        @width: display width, in pixels.
+        @height: display height, in pixels.
+        @stride: stride, in bytes.
+        @fourcc: DMABUF fourcc.
+        @modifier: DMABUF modifier.
+        @y0_top: whether Y position 0 is the top or not.
+
+        Resize and update the display content with a DMABUF.
+    -->
+    <method name="ScanoutDMABUF">
+      <arg type="h" name="dmabuf" direction="in"/>
+      <arg type="u" name="width" direction="in"/>
+      <arg type="u" name="height" direction="in"/>
+      <arg type="u" name="stride" direction="in"/>
+      <arg type="u" name="fourcc" direction="in"/>
+      <!-- xywh? -->
+      <arg type="t" name="modifier" direction="in"/>
+      <arg type="b" name="y0_top" direction="in"/>
+    </method>
+
+    <!--
+        UpdateDMABUF:
+        @x: the X update position, in pixels.
+        @y: the Y update position, in pixels.
+        @width: the update width, in pixels.
+        @height: the update height, in pixels.
+
+        Update the display content with the current DMABUF and the given region.
+    -->
+    <method name="UpdateDMABUF">
+      <arg type="i" name="x" direction="in"/>
+      <arg type="i" name="y" direction="in"/>
+      <arg type="i" name="width" direction="in"/>
+      <arg type="i" name="height" direction="in"/>
+    </method>
+
+    <!--
+        Disable:
+
+        Disable the display (turn it off).
+    -->
+    <method name="Disable">
+    </method>
+
+    <!--
+        MouseSet:
+        @x: X mouse position, in pixels.
+        @y: Y mouse position, in pixels.
+        @on: whether the mouse is visible or not.
+
+        Set the mouse position and visibility.
+    -->
+    <method name="MouseSet">
+      <arg type="i" name="x" direction="in"/>
+      <arg type="i" name="y" direction="in"/>
+      <arg type="i" name="on" direction="in"/>
+    </method>
+
+    <!--
+        CursorDefine:
+        @width: cursor width, in pixels.
+        @height: cursor height, in pixels.
+        @hot_x: hot-spot X position, in pixels.
+        @hot_y: hot-spot Y position, in pixels.
+        @data: the cursor data.
+
+        Set the mouse cursor shape and hot-spot. The "data" must be ARGB, 32-bit
+        per pixel.
+    -->
+    <method name="CursorDefine">
+      <arg type="i" name="width" direction="in"/>
+      <arg type="i" name="height" direction="in"/>
+      <arg type="i" name="hot_x" direction="in"/>
+      <arg type="i" name="hot_y" direction="in"/>
+      <arg type="ay" name="data" direction="in">
+        <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
+      </arg>
+    </method>
+  </interface>
+</node>
diff --git a/ui/meson.build b/ui/meson.build
index a9df5b911e..6270aa768b 100644
--- a/ui/meson.build
+++ b/ui/meson.build
@@ -65,6 +65,28 @@ if config_host.has_key('CONFIG_OPENGL') and gbm.found()
   ui_modules += {'egl-headless' : egl_headless_ss}
 endif
 
+if dbus_display
+  dbus_ss = ss.source_set()
+  dbus_display1 = custom_target('dbus-display gdbus-codegen',
+                                output: ['dbus-display1.h', 'dbus-display1.c'],
+                                input: files('dbus-display1.xml'),
+                                command: [config_host['GDBUS_CODEGEN'],
+                                          '@INPUT@',
+                                          '--glib-min-required', '2.64',
+                                          '--output-directory', meson.current_build_dir(),
+                                          '--interface-prefix', 'org.qemu.',
+                                          '--c-namespace', 'QemuDBus',
+                                          '--generate-c-code', '@BASENAME@'])
+  dbus_ss.add(when: [gio, pixman, opengl, 'CONFIG_GIO'],
+              if_true: [files(
+                'dbus-console.c',
+                'dbus-error.c',
+                'dbus-listener.c',
+                'dbus.c',
+              ), dbus_display1])
+  ui_modules += {'dbus' : dbus_ss}
+endif
+
 if gtk.found()
   softmmu_ss.add(when: 'CONFIG_WIN32', if_true: files('win32-kbd-hook.c'))
 
diff --git a/ui/trace-events b/ui/trace-events
index e832c3e365..b1ae30159a 100644
--- a/ui/trace-events
+++ b/ui/trace-events
@@ -136,3 +136,14 @@ vdagent_peer_cap(const char *name) "cap %s"
 vdagent_cb_grab_selection(const char *name) "selection %s"
 vdagent_cb_grab_type(const char *name) "type %s"
 vdagent_cb_serial_discard(uint32_t current, uint32_t received) "current=%u, received=%u"
+
+# dbus.c
+dbus_registered_listener(const char *bus_name) "peer %s"
+dbus_listener_vanished(const char *bus_name) "peer %s"
+dbus_kbd_press(unsigned int keycode) "keycode %u"
+dbus_kbd_release(unsigned int keycode) "keycode %u"
+dbus_mouse_press(unsigned int button) "button %u"
+dbus_mouse_release(unsigned int button) "button %u"
+dbus_mouse_set_pos(unsigned int x, unsigned int y) "x=%u, y=%u"
+dbus_mouse_rel_motion(int dx, int dy) "dx=%d, dy=%d"
+dbus_update(int x, int y, int w, int h) "x=%d, y=%d, w=%d, h=%d"
-- 
2.33.0.721.g106298f7f9



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v2 27/37] ui/dbus: add p2p=on/off option
  2021-10-09 21:08 [PATCH v2 00/37] Add D-Bus display backend marcandre.lureau
                   ` (25 preceding siblings ...)
  2021-10-09 21:08 ` [PATCH v2 26/37] ui: add a D-Bus display backend marcandre.lureau
@ 2021-10-09 21:08 ` marcandre.lureau
  2021-10-09 21:08 ` [PATCH v2 28/37] tests/qtests: add qtest_qmp_add_client() marcandre.lureau
                   ` (10 subsequent siblings)
  37 siblings, 0 replies; 54+ messages in thread
From: marcandre.lureau @ 2021-10-09 21:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, kraxel

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Add an option to use direct connections instead of via the bus. Clients
are accepted via QMP add_client.

This allows to provide the D-Bus display without a bus. It also
simplifies the testing setup (some CI have issues to setup a D-Bus bus
in a container).

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 qapi/misc.json            |   4 +-
 qapi/ui.json              |   6 ++-
 include/qemu/cutils.h     |   5 ++
 include/ui/dbus-display.h |  17 +++++++
 include/ui/dbus-module.h  |  11 ++++
 ui/dbus.h                 |   2 +
 monitor/qmp-cmds.c        |  13 +++++
 ui/dbus-console.c         |   2 +-
 ui/dbus-listener.c        |   2 +-
 ui/dbus-module.c          |  35 +++++++++++++
 ui/dbus.c                 | 104 ++++++++++++++++++++++++++++++++++++--
 qemu-options.hx           |   2 +
 ui/meson.build            |   3 ++
 13 files changed, 196 insertions(+), 10 deletions(-)
 create mode 100644 include/ui/dbus-display.h
 create mode 100644 include/ui/dbus-module.h
 create mode 100644 ui/dbus-module.c

diff --git a/qapi/misc.json b/qapi/misc.json
index 5c2ca3b556..887bcf54bb 100644
--- a/qapi/misc.json
+++ b/qapi/misc.json
@@ -14,8 +14,8 @@
 # Allow client connections for VNC, Spice and socket based
 # character devices to be passed in to QEMU via SCM_RIGHTS.
 #
-# @protocol: protocol name. Valid names are "vnc", "spice" or the
-#            name of a character device (eg. from -chardev id=XXXX)
+# @protocol: protocol name. Valid names are "vnc", "spice", "@dbus-display" or
+#            the name of a character device (eg. from -chardev id=XXXX)
 #
 # @fdname: file descriptor name previously passed via 'getfd' command
 #
diff --git a/qapi/ui.json b/qapi/ui.json
index 5ca604bd90..f23d012cb8 100644
--- a/qapi/ui.json
+++ b/qapi/ui.json
@@ -1131,12 +1131,16 @@
 # @rendernode: Which DRM render node should be used. Default is the first
 #              available node on the host.
 #
+# @p2p: Whether to use peer-to-peer connections (accepted through
+#       ``add_client``).
+#
 # Since: 6.2
 #
 ##
 { 'struct'  : 'DisplayDBus',
   'data'    : { '*rendernode' : 'str',
-                '*addr': 'str' } }
+                '*addr': 'str',
+                '*p2p': 'bool' } }
 
  ##
  # @DisplayGLMode:
diff --git a/include/qemu/cutils.h b/include/qemu/cutils.h
index 986ed8e15f..320543950c 100644
--- a/include/qemu/cutils.h
+++ b/include/qemu/cutils.h
@@ -209,4 +209,9 @@ int qemu_pstrcmp0(const char **str1, const char **str2);
  */
 char *get_relocated_path(const char *dir);
 
+static inline const char *yes_no(bool b)
+{
+     return b ? "yes" : "no";
+}
+
 #endif
diff --git a/include/ui/dbus-display.h b/include/ui/dbus-display.h
new file mode 100644
index 0000000000..88f153c237
--- /dev/null
+++ b/include/ui/dbus-display.h
@@ -0,0 +1,17 @@
+#ifndef DBUS_DISPLAY_H_
+#define DBUS_DISPLAY_H_
+
+#include "qapi/error.h"
+#include "ui/dbus-module.h"
+
+static inline bool qemu_using_dbus_display(Error **errp)
+{
+    if (!using_dbus_display) {
+        error_set(errp, ERROR_CLASS_DEVICE_NOT_ACTIVE,
+                  "D-Bus display is not in use");
+        return false;
+    }
+    return true;
+}
+
+#endif /* DBUS_DISPLAY_H_ */
diff --git a/include/ui/dbus-module.h b/include/ui/dbus-module.h
new file mode 100644
index 0000000000..ace4a17a5c
--- /dev/null
+++ b/include/ui/dbus-module.h
@@ -0,0 +1,11 @@
+#ifndef DBUS_MODULE_H_
+#define DBUS_MODULE_H_
+
+struct QemuDBusDisplayOps {
+    bool (*add_client)(int csock, Error **errp);
+};
+
+extern int using_dbus_display;
+extern struct QemuDBusDisplayOps qemu_dbus_display;
+
+#endif /* DBUS_MODULE_H_*/
diff --git a/ui/dbus.h b/ui/dbus.h
index d3c9598dd1..4698d32463 100644
--- a/ui/dbus.h
+++ b/ui/dbus.h
@@ -34,6 +34,7 @@ struct DBusDisplay {
     Object parent;
 
     DisplayGLMode gl_mode;
+    bool p2p;
     char *dbus_addr;
     DisplayGLCtx glctx;
 
@@ -41,6 +42,7 @@ struct DBusDisplay {
     GDBusObjectManagerServer *server;
     QemuDBusDisplay1VM *iface;
     GPtrArray *consoles;
+    GCancellable *add_client_cancellable;
 };
 
 #define TYPE_DBUS_DISPLAY "dbus-display"
diff --git a/monitor/qmp-cmds.c b/monitor/qmp-cmds.c
index 5c0d5e116b..aa6b012313 100644
--- a/monitor/qmp-cmds.c
+++ b/monitor/qmp-cmds.c
@@ -24,6 +24,7 @@
 #include "chardev/char.h"
 #include "ui/qemu-spice.h"
 #include "ui/console.h"
+#include "ui/dbus-display.h"
 #include "sysemu/kvm.h"
 #include "sysemu/runstate.h"
 #include "sysemu/runstate-action.h"
@@ -281,6 +282,18 @@ void qmp_add_client(const char *protocol, const char *fdname,
         skipauth = has_skipauth ? skipauth : false;
         vnc_display_add_client(NULL, fd, skipauth);
         return;
+#endif
+#ifdef CONFIG_DBUS_DISPLAY
+    } else if (strcmp(protocol, "@dbus-display") == 0) {
+        if (!qemu_using_dbus_display(errp)) {
+            close(fd);
+            return;
+        }
+        if (!qemu_dbus_display.add_client(fd, errp)) {
+            close(fd);
+            return;
+        }
+        return;
 #endif
     } else if ((s = qemu_chr_find(protocol)) != NULL) {
         if (qemu_chr_add_client(s, fd) < 0) {
diff --git a/ui/dbus-console.c b/ui/dbus-console.c
index 1ccf638c10..e062f721d7 100644
--- a/ui/dbus-console.c
+++ b/ui/dbus-console.c
@@ -219,7 +219,7 @@ dbus_console_register_listener(DBusDisplayConsole *ddc,
     DBusDisplayListener *listener;
     int fd;
 
-    if (g_hash_table_contains(ddc->listeners, sender)) {
+    if (sender && g_hash_table_contains(ddc->listeners, sender)) {
         g_dbus_method_invocation_return_error(
             invocation,
             DBUS_DISPLAY_ERROR,
diff --git a/ui/dbus-listener.c b/ui/dbus-listener.c
index 20094fc18a..81c119b13a 100644
--- a/ui/dbus-listener.c
+++ b/ui/dbus-listener.c
@@ -440,7 +440,7 @@ dbus_display_listener_init(DBusDisplayListener *ddl)
 const char *
 dbus_display_listener_get_bus_name(DBusDisplayListener *ddl)
 {
-    return ddl->bus_name;
+    return ddl->bus_name ?: "p2p";
 }
 
 DBusDisplayConsole *
diff --git a/ui/dbus-module.c b/ui/dbus-module.c
new file mode 100644
index 0000000000..c8771fe48c
--- /dev/null
+++ b/ui/dbus-module.c
@@ -0,0 +1,35 @@
+/*
+ * D-Bus module support.
+ *
+ * Copyright (C) 2021 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "ui/dbus-module.h"
+
+int using_dbus_display;
+
+static bool
+qemu_dbus_display_add_client(int csock, Error **errp)
+{
+    error_setg(errp, "D-Bus display isn't enabled");
+    return false;
+}
+
+struct QemuDBusDisplayOps qemu_dbus_display = {
+    .add_client = qemu_dbus_display_add_client,
+};
diff --git a/ui/dbus.c b/ui/dbus.c
index 12da8ffe31..56e284fb97 100644
--- a/ui/dbus.c
+++ b/ui/dbus.c
@@ -22,10 +22,12 @@
  * THE SOFTWARE.
  */
 #include "qemu/osdep.h"
+#include "qemu/cutils.h"
 #include "qemu/dbus.h"
 #include "qemu/option.h"
 #include "qom/object_interfaces.h"
 #include "sysemu/sysemu.h"
+#include "ui/dbus-module.h"
 #include "ui/egl-helpers.h"
 #include "ui/egl-context.h"
 #include "qapi/error.h"
@@ -33,6 +35,8 @@
 
 #include "dbus.h"
 
+static DBusDisplay *dbus_display;
+
 static QEMUGLContext dbus_create_context(DisplayGLCtx *dgc,
                                          QEMUGLParams *params)
 {
@@ -73,9 +77,14 @@ dbus_display_finalize(Object *o)
 
     g_clear_object(&dd->server);
     g_clear_pointer(&dd->consoles, g_ptr_array_unref);
+    if (dd->add_client_cancellable) {
+        g_cancellable_cancel(dd->add_client_cancellable);
+    }
+    g_clear_object(&dd->add_client_cancellable);
     g_clear_object(&dd->bus);
     g_clear_object(&dd->iface);
     g_free(dd->dbus_addr);
+    dbus_display = NULL;
 }
 
 static bool
@@ -115,7 +124,10 @@ dbus_display_complete(UserCreatable *uc, Error **errp)
         return;
     }
 
-    if (dd->dbus_addr && *dd->dbus_addr) {
+    if (dd->p2p) {
+        /* wait for dbus_display_add_client() */
+        dbus_display = dd;
+    } else if (dd->dbus_addr && *dd->dbus_addr) {
         dd->bus = g_dbus_connection_new_for_address_sync(dd->dbus_addr,
                         G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
                         G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
@@ -151,10 +163,80 @@ dbus_display_complete(UserCreatable *uc, Error **errp)
                  "console-ids", console_ids,
                  NULL);
 
-    g_dbus_object_manager_server_set_connection(dd->server, dd->bus);
-    g_bus_own_name_on_connection(dd->bus, "org.qemu",
-                                 G_BUS_NAME_OWNER_FLAGS_NONE,
-                                 NULL, NULL, NULL, NULL);
+    if (dd->bus) {
+        g_dbus_object_manager_server_set_connection(dd->server, dd->bus);
+        g_bus_own_name_on_connection(dd->bus, "org.qemu",
+                                     G_BUS_NAME_OWNER_FLAGS_NONE,
+                                     NULL, NULL, NULL, NULL);
+    }
+}
+
+static void
+dbus_display_add_client_ready(GObject *source_object,
+                              GAsyncResult *res,
+                              gpointer user_data)
+{
+    g_autoptr(GError) err = NULL;
+    g_autoptr(GDBusConnection) conn = NULL;
+
+    g_clear_object(&dbus_display->add_client_cancellable);
+
+    conn = g_dbus_connection_new_finish(res, &err);
+    if (!conn) {
+        error_printf("Failed to accept D-Bus client: %s", err->message);
+    }
+
+    g_dbus_object_manager_server_set_connection(dbus_display->server, conn);
+}
+
+
+static bool
+dbus_display_add_client(int csock, Error **errp)
+{
+    g_autoptr(GError) err = NULL;
+    g_autoptr(GSocket) socket = NULL;
+    g_autoptr(GSocketConnection) conn = NULL;
+    g_autofree char *guid = g_dbus_generate_guid();
+
+    if (dbus_display->add_client_cancellable) {
+        g_cancellable_cancel(dbus_display->add_client_cancellable);
+    }
+
+    socket = g_socket_new_from_fd(csock, &err);
+    if (!socket) {
+        error_setg(errp, "Failed to setup D-Bus socket: %s", err->message);
+        return false;
+    }
+
+    conn = g_socket_connection_factory_create_connection(socket);
+
+    dbus_display->add_client_cancellable = g_cancellable_new();
+
+    g_dbus_connection_new(G_IO_STREAM(conn),
+                          guid,
+                          G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER,
+                          NULL,
+                          dbus_display->add_client_cancellable,
+                          dbus_display_add_client_ready,
+                          NULL);
+
+    return true;
+}
+
+static bool
+get_dbus_p2p(Object *o, Error **errp)
+{
+    DBusDisplay *dd = DBUS_DISPLAY(o);
+
+    return dd->p2p;
+}
+
+static void
+set_dbus_p2p(Object *o, bool p2p, Error **errp)
+{
+    DBusDisplay *dd = DBUS_DISPLAY(o);
+
+    dd->p2p = p2p;
 }
 
 static char *
@@ -196,6 +278,7 @@ dbus_display_class_init(ObjectClass *oc, void *data)
     UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
 
     ucc->complete = dbus_display_complete;
+    object_class_property_add_bool(oc, "p2p", get_dbus_p2p, set_dbus_p2p);
     object_class_property_add_str(oc, "addr", get_dbus_addr, set_dbus_addr);
     object_class_property_add_enum(oc, "gl-mode",
                                    "DisplayGLMode", &DisplayGLMode_lookup,
@@ -222,11 +305,19 @@ dbus_init(DisplayState *ds, DisplayOptions *opts)
 {
     DisplayGLMode mode = opts->has_gl ? opts->gl : DISPLAYGL_MODE_OFF;
 
+    if (opts->u.dbus.addr && opts->u.dbus.p2p) {
+        error_report("dbus: can't accept both addr=X and p2p=yes options");
+        exit(1);
+    }
+
+    using_dbus_display = 1;
+
     object_new_with_props(TYPE_DBUS_DISPLAY,
                           object_get_objects_root(),
                           "dbus-display", &error_fatal,
                           "addr", opts->u.dbus.addr ?: "",
                           "gl-mode", DisplayGLMode_str(mode),
+                          "p2p", yes_no(opts->u.dbus.p2p),
                           NULL);
 }
 
@@ -251,6 +342,9 @@ static QemuDisplay qemu_display_dbus = {
 
 static void register_dbus(void)
 {
+    qemu_dbus_display = (struct QemuDBusDisplayOps) {
+        .add_client = dbus_display_add_client,
+    };
     type_register_static(&dbus_display_info);
     qemu_display_register(&qemu_display_dbus);
 }
diff --git a/qemu-options.hx b/qemu-options.hx
index 1a927458e6..b3352a7fbc 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -1901,6 +1901,8 @@ SRST
 
         ``addr=<dbusaddr>`` : D-Bus bus address to connect to.
 
+        ``p2p=yes|no`` : Use peer-to-peer connection, accepted via QMP ``add_client``.
+
         ``gl=on|off|core|es`` : Use OpenGL for rendering (the D-interface will
         share framebuffers with DMABUF file descriptors).
 
diff --git a/ui/meson.build b/ui/meson.build
index 6270aa768b..80f21704ad 100644
--- a/ui/meson.build
+++ b/ui/meson.build
@@ -14,6 +14,9 @@ softmmu_ss.add(files(
   'qemu-pixman.c',
   'util.c',
 ))
+if dbus_display
+  softmmu_ss.add(files('dbus-module.c'))
+endif
 softmmu_ss.add([spice_headers, files('spice-module.c')])
 softmmu_ss.add(when: spice_protocol, if_true: files('vdagent.c'))
 
-- 
2.33.0.721.g106298f7f9



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v2 28/37] tests/qtests: add qtest_qmp_add_client()
  2021-10-09 21:08 [PATCH v2 00/37] Add D-Bus display backend marcandre.lureau
                   ` (26 preceding siblings ...)
  2021-10-09 21:08 ` [PATCH v2 27/37] ui/dbus: add p2p=on/off option marcandre.lureau
@ 2021-10-09 21:08 ` marcandre.lureau
  2021-10-09 21:08 ` [PATCH v2 29/37] tests: start dbus-display-test marcandre.lureau
                   ` (9 subsequent siblings)
  37 siblings, 0 replies; 54+ messages in thread
From: marcandre.lureau @ 2021-10-09 21:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, kraxel

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 tests/qtest/libqos/libqtest.h | 10 ++++++++++
 tests/qtest/libqtest.c        | 19 +++++++++++++++++++
 2 files changed, 29 insertions(+)

diff --git a/tests/qtest/libqos/libqtest.h b/tests/qtest/libqos/libqtest.h
index a68dcd79d4..9e5f9118f3 100644
--- a/tests/qtest/libqos/libqtest.h
+++ b/tests/qtest/libqos/libqtest.h
@@ -728,6 +728,16 @@ void qtest_qmp_device_add_qdict(QTestState *qts, const char *drv,
 void qtest_qmp_device_add(QTestState *qts, const char *driver, const char *id,
                           const char *fmt, ...) GCC_FMT_ATTR(4, 5);
 
+/**
+ * qtest_qmp_add_client:
+ * @qts: QTestState instance to operate on
+ * @protocol: the protocol to add to
+ * @fd: the client file-descriptor
+ *
+ * Call QMP `getfd` followed by `add_client` with the given @fd.
+ */
+void qtest_qmp_add_client(QTestState *qts, const char *protocol, int fd);
+
 /**
  * qtest_qmp_device_del:
  * @qts: QTestState instance to operate on
diff --git a/tests/qtest/libqtest.c b/tests/qtest/libqtest.c
index 73f6b977a6..c8ea5499a0 100644
--- a/tests/qtest/libqtest.c
+++ b/tests/qtest/libqtest.c
@@ -1367,6 +1367,25 @@ void qtest_qmp_device_add(QTestState *qts, const char *driver, const char *id,
     qobject_unref(args);
 }
 
+void qtest_qmp_add_client(QTestState *qts, const char *protocol, int fd)
+{
+    QDict *resp;
+
+    resp = qtest_qmp_fds(qts, &fd, 1, "{'execute': 'getfd',"
+                         "'arguments': {'fdname': 'fdname'}}");
+    g_assert(resp);
+    g_assert(!qdict_haskey(resp, "event")); /* We don't expect any events */
+    g_assert(!qdict_haskey(resp, "error"));
+    qobject_unref(resp);
+
+    resp = qtest_qmp(
+        qts, "{'execute': 'add_client',"
+        "'arguments': {'protocol': %s, 'fdname': 'fdname'}}", protocol);
+    g_assert(resp);
+    g_assert(!qdict_haskey(resp, "event")); /* We don't expect any events */
+    g_assert(!qdict_haskey(resp, "error"));
+    qobject_unref(resp);
+}
 
 /*
  * Generic hot-unplugging test via the device_del QMP command.
-- 
2.33.0.721.g106298f7f9



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v2 29/37] tests: start dbus-display-test
  2021-10-09 21:08 [PATCH v2 00/37] Add D-Bus display backend marcandre.lureau
                   ` (27 preceding siblings ...)
  2021-10-09 21:08 ` [PATCH v2 28/37] tests/qtests: add qtest_qmp_add_client() marcandre.lureau
@ 2021-10-09 21:08 ` marcandre.lureau
  2021-10-09 21:08 ` [PATCH v2 30/37] audio: add "dbus" audio backend marcandre.lureau
                   ` (8 subsequent siblings)
  37 siblings, 0 replies; 54+ messages in thread
From: marcandre.lureau @ 2021-10-09 21:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, kraxel

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Cover basic display interface usage. More cases to be added to cover
disconnections, multiple connections, corner cases. At this point, they
would be better written in Rust or Python though.

The proxy also covers reading the properties, since they are
automatically loaded at creation.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 tests/qtest/dbus-display-test.c | 281 ++++++++++++++++++++++++++++++++
 tests/qtest/meson.build         |   8 +
 2 files changed, 289 insertions(+)
 create mode 100644 tests/qtest/dbus-display-test.c

diff --git a/tests/qtest/dbus-display-test.c b/tests/qtest/dbus-display-test.c
new file mode 100644
index 0000000000..84278d2da6
--- /dev/null
+++ b/tests/qtest/dbus-display-test.c
@@ -0,0 +1,281 @@
+#include "qemu/osdep.h"
+#include "qemu/dbus.h"
+#include <gio/gio.h>
+#include <gio/gunixfdlist.h>
+#include "libqos/libqtest.h"
+#include "qemu-common.h"
+#include "dbus-display1.h"
+
+static GDBusConnection*
+test_dbus_p2p_from_fd(int fd)
+{
+    g_autoptr(GError) err = NULL;
+    g_autoptr(GSocket) socket = NULL;
+    g_autoptr(GSocketConnection) socketc = NULL;
+    GDBusConnection *conn;
+
+    socket = g_socket_new_from_fd(fd, &err);
+    g_assert_no_error(err);
+
+    socketc = g_socket_connection_factory_create_connection(socket);
+    g_assert(socketc != NULL);
+
+    conn = g_dbus_connection_new_sync(
+        G_IO_STREAM(socketc), NULL,
+        G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
+        G_DBUS_CONNECTION_FLAGS_DELAY_MESSAGE_PROCESSING,
+        NULL, NULL, &err);
+    g_assert_no_error(err);
+
+    return conn;
+}
+
+static void
+test_setup(QTestState **qts, GDBusConnection **conn)
+{
+    int pair[2];
+
+    *qts = qtest_init("-display dbus,p2p=yes -name dbus-test");
+
+    g_assert_cmpint(socketpair(AF_UNIX, SOCK_STREAM, 0, pair), ==, 0);
+
+    qtest_qmp_add_client(*qts, "@dbus-display", pair[1]);
+
+    *conn = test_dbus_p2p_from_fd(pair[0]);
+    g_dbus_connection_start_message_processing(*conn);
+}
+
+static void
+test_dbus_display_vm(void)
+{
+    g_autoptr(GError) err = NULL;
+    g_autoptr(GDBusConnection) conn = NULL;
+    g_autoptr(QemuDBusDisplay1VMProxy) vm = NULL;
+    QTestState *qts = NULL;
+
+    test_setup(&qts, &conn);
+
+    vm = QEMU_DBUS_DISPLAY1_VM_PROXY(
+        qemu_dbus_display1_vm_proxy_new_sync(
+            conn,
+            G_DBUS_PROXY_FLAGS_NONE,
+            NULL,
+            DBUS_DISPLAY1_ROOT "/VM",
+            NULL,
+            &err));
+    g_assert_no_error(err);
+
+    g_assert_cmpstr(
+        qemu_dbus_display1_vm_get_name(QEMU_DBUS_DISPLAY1_VM(vm)),
+        ==,
+        "dbus-test");
+    qtest_quit(qts);
+}
+
+typedef struct TestDBusConsoleRegister {
+    GMainLoop *loop;
+    GThread *thread;
+    GDBusConnection *listener_conn;
+    GDBusObjectManagerServer *server;
+} TestDBusConsoleRegister;
+
+static gboolean listener_handle_scanout(
+    QemuDBusDisplay1Listener *object,
+    GDBusMethodInvocation *invocation,
+    guint arg_width,
+    guint arg_height,
+    guint arg_stride,
+    guint arg_pixman_format,
+    GVariant *arg_data,
+    TestDBusConsoleRegister *test)
+{
+    g_main_loop_quit(test->loop);
+
+    return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static void
+test_dbus_console_setup_listener(TestDBusConsoleRegister *test)
+{
+    g_autoptr(GDBusObjectSkeleton) listener = NULL;
+    g_autoptr(QemuDBusDisplay1ListenerSkeleton) iface = NULL;
+
+    test->server = g_dbus_object_manager_server_new(DBUS_DISPLAY1_ROOT);
+    listener = g_dbus_object_skeleton_new(DBUS_DISPLAY1_ROOT "/Listener");
+    iface = QEMU_DBUS_DISPLAY1_LISTENER_SKELETON(
+        qemu_dbus_display1_listener_skeleton_new());
+    g_object_connect(iface,
+                     "signal::handle-scanout", listener_handle_scanout, test,
+                     NULL);
+    g_dbus_object_skeleton_add_interface(listener,
+                                         G_DBUS_INTERFACE_SKELETON(iface));
+    g_dbus_object_manager_server_export(test->server, listener);
+    g_dbus_object_manager_server_set_connection(test->server,
+                                                test->listener_conn);
+
+    g_dbus_connection_start_message_processing(test->listener_conn);
+}
+
+static void
+test_dbus_console_registered(GObject *source_object,
+                             GAsyncResult *res,
+                             gpointer user_data)
+{
+    TestDBusConsoleRegister *test = user_data;
+    g_autoptr(GError) err = NULL;
+
+    qemu_dbus_display1_console_call_register_listener_finish(
+        QEMU_DBUS_DISPLAY1_CONSOLE(source_object),
+        NULL, res, &err);
+    g_assert_no_error(err);
+
+    test->listener_conn = g_thread_join(test->thread);
+    test_dbus_console_setup_listener(test);
+}
+
+static gpointer
+test_dbus_p2p_server_setup_thread(gpointer data)
+{
+    return test_dbus_p2p_from_fd(GPOINTER_TO_INT(data));
+}
+
+static void
+test_dbus_display_console(void)
+{
+    g_autoptr(GError) err = NULL;
+    g_autoptr(GDBusConnection) conn = NULL;
+    g_autoptr(QemuDBusDisplay1ConsoleProxy) console = NULL;
+    g_autoptr(GUnixFDList) fd_list = NULL;
+    g_autoptr(GMainLoop) loop = NULL;
+    QTestState *qts = NULL;
+    int pair[2], idx;
+    TestDBusConsoleRegister test;
+
+    test_setup(&qts, &conn);
+
+    g_assert_cmpint(socketpair(AF_UNIX, SOCK_STREAM, 0, pair), ==, 0);
+    fd_list = g_unix_fd_list_new();
+    idx = g_unix_fd_list_append(fd_list, pair[1], NULL);
+
+    console = QEMU_DBUS_DISPLAY1_CONSOLE_PROXY(
+        qemu_dbus_display1_console_proxy_new_sync(
+            conn,
+            G_DBUS_PROXY_FLAGS_NONE,
+            NULL,
+            "/org/qemu/Display1/Console_0",
+            NULL,
+            &err));
+    g_assert_no_error(err);
+
+    test.loop = loop = g_main_loop_new(NULL, FALSE);
+    test.thread = g_thread_new(NULL, test_dbus_p2p_server_setup_thread,
+                               GINT_TO_POINTER(pair[0]));
+
+    qemu_dbus_display1_console_call_register_listener(
+        QEMU_DBUS_DISPLAY1_CONSOLE(console),
+        g_variant_new_handle(idx),
+        G_DBUS_CALL_FLAGS_NONE,
+        -1,
+        fd_list,
+        NULL,
+        test_dbus_console_registered,
+        &test);
+
+    g_main_loop_run(loop);
+
+    g_clear_object(&test.server);
+    g_clear_object(&test.listener_conn);
+    qtest_quit(qts);
+}
+
+static void
+test_dbus_display_keyboard_on_modifiers_changed(
+    QemuDBusDisplay1KeyboardProxy *keyboard,
+    GParamSpec *pspec,
+    GMainLoop *loop)
+{
+    /* there might be initial events first */
+    if (qemu_dbus_display1_keyboard_get_modifiers(
+            QEMU_DBUS_DISPLAY1_KEYBOARD(keyboard)) == 0x2)
+        g_main_loop_quit(loop);
+}
+
+static void
+test_dbus_display_keyboard(void)
+{
+    g_autoptr(GError) err = NULL;
+    g_autoptr(GDBusConnection) conn = NULL;
+    g_autoptr(QemuDBusDisplay1KeyboardProxy) keyboard = NULL;
+    g_autoptr(GMainLoop) loop = NULL;
+    QTestState *qts = NULL;
+
+    test_setup(&qts, &conn);
+
+    keyboard = QEMU_DBUS_DISPLAY1_KEYBOARD_PROXY(
+        qemu_dbus_display1_keyboard_proxy_new_sync(
+            conn,
+            G_DBUS_PROXY_FLAGS_NONE,
+            NULL,
+            "/org/qemu/Display1/Console_0",
+            NULL,
+            &err));
+    g_assert_no_error(err);
+
+
+    g_assert_cmpint(qtest_inb(qts, 0x64) & 0x1, ==, 0);
+    g_assert_cmpint(qtest_inb(qts, 0x60), ==, 0);
+
+    qemu_dbus_display1_keyboard_call_press_sync(
+        QEMU_DBUS_DISPLAY1_KEYBOARD(keyboard),
+        0x1C, /* qnum enter */
+        G_DBUS_CALL_FLAGS_NONE,
+        -1,
+        NULL,
+        &err);
+    g_assert_no_error(err);
+
+    /* may be should wait for interrupt? */
+    g_assert_cmpint(qtest_inb(qts, 0x64) & 0x1, ==, 1);
+    g_assert_cmpint(qtest_inb(qts, 0x60), ==, 0x5A); /* scan code 2 enter */
+
+    qemu_dbus_display1_keyboard_call_release_sync(
+        QEMU_DBUS_DISPLAY1_KEYBOARD(keyboard),
+        0x1C, /* qnum enter */
+        G_DBUS_CALL_FLAGS_NONE,
+        -1,
+        NULL,
+        &err);
+    g_assert_no_error(err);
+
+    g_assert_cmpint(qtest_inb(qts, 0x64) & 0x1, ==, 1);
+    g_assert_cmpint(qtest_inb(qts, 0x60), ==, 0xF0); /* scan code 2 release */
+    g_assert_cmpint(qtest_inb(qts, 0x60), ==, 0x5A); /* scan code 2 enter */
+
+    g_assert_cmpint(qemu_dbus_display1_keyboard_get_modifiers(
+                        QEMU_DBUS_DISPLAY1_KEYBOARD(keyboard)), ==, 0);
+
+    loop = g_main_loop_new(NULL, FALSE);
+
+    g_signal_connect(
+        keyboard, "notify::modifiers",
+        G_CALLBACK(test_dbus_display_keyboard_on_modifiers_changed),
+        loop);
+
+    qemu_dbus_display1_keyboard_set_modifiers(
+        QEMU_DBUS_DISPLAY1_KEYBOARD(keyboard), 0x2);
+
+    g_main_loop_run(loop);
+    qtest_quit(qts);
+}
+
+int
+main(int argc, char **argv)
+{
+    g_test_init(&argc, &argv, NULL);
+
+    qtest_add_func("/dbus-display/vm", test_dbus_display_vm);
+    qtest_add_func("/dbus-display/console", test_dbus_display_console);
+    qtest_add_func("/dbus-display/keyboard", test_dbus_display_keyboard);
+
+    return g_test_run();
+}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index b6016aee48..5cf7e54e1b 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -87,6 +87,10 @@ qtests_i386 = \
    'test-x86-cpuid-compat',
    'numa-test']
 
+if dbus_display
+  qtests_i386 += ['dbus-display-test']
+endif
+
 dbus_daemon = find_program('dbus-daemon', required: false)
 if dbus_daemon.found() and config_host.has_key('GDBUS_CODEGEN')
   # Temporarily disabled due to Patchew failures:
@@ -259,6 +263,10 @@ qtests = {
   'vmgenid-test': files('boot-sector.c', 'acpi-utils.c'),
 }
 
+if dbus_display
+qtests += {'dbus-display-test': [dbus_display1, gio]}
+endif
+
 qtest_executables = {}
 foreach dir : target_dirs
   if not dir.endswith('-softmmu')
-- 
2.33.0.721.g106298f7f9



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v2 30/37] audio: add "dbus" audio backend
  2021-10-09 21:08 [PATCH v2 00/37] Add D-Bus display backend marcandre.lureau
                   ` (28 preceding siblings ...)
  2021-10-09 21:08 ` [PATCH v2 29/37] tests: start dbus-display-test marcandre.lureau
@ 2021-10-09 21:08 ` marcandre.lureau
  2021-10-09 21:08 ` [PATCH v2 31/37] ui/dbus: add clipboard interface marcandre.lureau
                   ` (7 subsequent siblings)
  37 siblings, 0 replies; 54+ messages in thread
From: marcandre.lureau @ 2021-10-09 21:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, kraxel

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Add a new -audio backend that accepts D-Bus clients/listeners to handle
playback & recording, to be exported via the -display dbus.

Example usage:
-audiodev dbus,in.mixing-engine=off,out.mixing-engine=off,id=dbus
-display dbus,audiodev=dbus

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 qapi/audio.json        |   3 +-
 qapi/ui.json           |   5 +-
 audio/audio_int.h      |   7 +
 audio/audio_template.h |   2 +
 ui/dbus.h              |   1 +
 audio/audio.c          |   1 +
 audio/dbusaudio.c      | 654 +++++++++++++++++++++++++++++++++++++++++
 ui/dbus.c              |  35 +++
 audio/meson.build      |   8 +-
 audio/trace-events     |   5 +
 qemu-options.hx        |   3 +
 ui/dbus-display1.xml   | 211 +++++++++++++
 12 files changed, 932 insertions(+), 3 deletions(-)
 create mode 100644 audio/dbusaudio.c

diff --git a/qapi/audio.json b/qapi/audio.json
index 9cba0df8a4..693e327c6b 100644
--- a/qapi/audio.json
+++ b/qapi/audio.json
@@ -386,7 +386,7 @@
 # Since: 4.0
 ##
 { 'enum': 'AudiodevDriver',
-  'data': [ 'none', 'alsa', 'coreaudio', 'dsound', 'jack', 'oss', 'pa',
+  'data': [ 'none', 'alsa', 'coreaudio', 'dbus', 'dsound', 'jack', 'oss', 'pa',
             'sdl', 'spice', 'wav' ] }
 
 ##
@@ -412,6 +412,7 @@
     'none':      'AudiodevGenericOptions',
     'alsa':      'AudiodevAlsaOptions',
     'coreaudio': 'AudiodevCoreaudioOptions',
+    'dbus':      'AudiodevGenericOptions',
     'dsound':    'AudiodevDsoundOptions',
     'jack':      'AudiodevJackOptions',
     'oss':       'AudiodevOssOptions',
diff --git a/qapi/ui.json b/qapi/ui.json
index f23d012cb8..b9262ae1d9 100644
--- a/qapi/ui.json
+++ b/qapi/ui.json
@@ -1134,13 +1134,16 @@
 # @p2p: Whether to use peer-to-peer connections (accepted through
 #       ``add_client``).
 #
+# @audiodev: Use the specified DBus audiodev to export audio.
+#
 # Since: 6.2
 #
 ##
 { 'struct'  : 'DisplayDBus',
   'data'    : { '*rendernode' : 'str',
                 '*addr': 'str',
-                '*p2p': 'bool' } }
+                '*p2p': 'bool',
+                '*audiodev': 'str' } }
 
  ##
  # @DisplayGLMode:
diff --git a/audio/audio_int.h b/audio/audio_int.h
index 6d685e24a3..428a091d05 100644
--- a/audio/audio_int.h
+++ b/audio/audio_int.h
@@ -31,6 +31,10 @@
 #endif
 #include "mixeng.h"
 
+#ifdef CONFIG_GIO
+#include <gio/gio.h>
+#endif
+
 struct audio_pcm_ops;
 
 struct audio_callback {
@@ -140,6 +144,9 @@ struct audio_driver {
     const char *descr;
     void *(*init) (Audiodev *);
     void (*fini) (void *);
+#ifdef CONFIG_GIO
+    void (*set_dbus_server) (AudioState *s, GDBusObjectManagerServer *manager);
+#endif
     struct audio_pcm_ops *pcm_ops;
     int can_be_default;
     int max_voices_out;
diff --git a/audio/audio_template.h b/audio/audio_template.h
index c6714946aa..d2d348638b 100644
--- a/audio/audio_template.h
+++ b/audio/audio_template.h
@@ -327,6 +327,8 @@ AudiodevPerDirectionOptions *glue(audio_get_pdo_, TYPE)(Audiodev *dev)
     case AUDIODEV_DRIVER_COREAUDIO:
         return qapi_AudiodevCoreaudioPerDirectionOptions_base(
             dev->u.coreaudio.TYPE);
+    case AUDIODEV_DRIVER_DBUS:
+        return dev->u.dbus.TYPE;
     case AUDIODEV_DRIVER_DSOUND:
         return dev->u.dsound.TYPE;
     case AUDIODEV_DRIVER_JACK:
diff --git a/ui/dbus.h b/ui/dbus.h
index 4698d32463..ca1f0f4ab9 100644
--- a/ui/dbus.h
+++ b/ui/dbus.h
@@ -36,6 +36,7 @@ struct DBusDisplay {
     DisplayGLMode gl_mode;
     bool p2p;
     char *dbus_addr;
+    char *audiodev;
     DisplayGLCtx glctx;
 
     GDBusConnection *bus;
diff --git a/audio/audio.c b/audio/audio.c
index 54a153c0ef..dc28685d22 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -2000,6 +2000,7 @@ void audio_create_pdos(Audiodev *dev)
         CASE(NONE, none, );
         CASE(ALSA, alsa, Alsa);
         CASE(COREAUDIO, coreaudio, Coreaudio);
+        CASE(DBUS, dbus, );
         CASE(DSOUND, dsound, );
         CASE(JACK, jack, Jack);
         CASE(OSS, oss, Oss);
diff --git a/audio/dbusaudio.c b/audio/dbusaudio.c
new file mode 100644
index 0000000000..f178b47dee
--- /dev/null
+++ b/audio/dbusaudio.c
@@ -0,0 +1,654 @@
+/*
+ * QEMU DBus audio
+ *
+ * Copyright (c) 2021 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/error-report.h"
+#include "qemu/host-utils.h"
+#include "qemu/module.h"
+#include "qemu/timer.h"
+#include "qemu/dbus.h"
+
+#include <gio/gunixfdlist.h>
+#include "ui/dbus-display1.h"
+
+#define AUDIO_CAP "dbus"
+#include "audio.h"
+#include "audio_int.h"
+#include "trace.h"
+
+#define DBUS_DISPLAY1_AUDIO_PATH DBUS_DISPLAY1_ROOT "/Audio"
+
+#define DBUS_AUDIO_NSAMPLES 1024 /* could be configured? */
+
+typedef struct DBusAudio {
+    GDBusObjectManagerServer *server;
+    GDBusObjectSkeleton *audio;
+    QemuDBusDisplay1Audio *iface;
+    GHashTable *out_listeners;
+    GHashTable *in_listeners;
+} DBusAudio;
+
+typedef struct DBusVoiceOut {
+    HWVoiceOut hw;
+    bool enabled;
+    RateCtl rate;
+
+    void *buf;
+    size_t buf_pos;
+    size_t buf_size;
+
+    bool has_volume;
+    Volume volume;
+} DBusVoiceOut;
+
+typedef struct DBusVoiceIn {
+    HWVoiceIn hw;
+    bool enabled;
+    RateCtl rate;
+
+    bool has_volume;
+    Volume volume;
+} DBusVoiceIn;
+
+static void *dbus_get_buffer_out(HWVoiceOut *hw, size_t *size)
+{
+    DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
+
+    if (!vo->buf) {
+        vo->buf_size = hw->samples * hw->info.bytes_per_frame;
+        vo->buf = g_malloc(vo->buf_size);
+        vo->buf_pos = 0;
+    }
+
+    *size = MIN(vo->buf_size - vo->buf_pos, *size);
+    *size = audio_rate_get_bytes(&hw->info, &vo->rate, *size);
+
+    return vo->buf + vo->buf_pos;
+
+}
+
+static size_t dbus_put_buffer_out(HWVoiceOut *hw, void *buf, size_t size)
+{
+    DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+    DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
+    GHashTableIter iter;
+    QemuDBusDisplay1AudioOutListener *listener = NULL;
+    g_autoptr(GBytes) bytes = NULL;
+    g_autoptr(GVariant) v_data = NULL;
+
+    assert(buf == vo->buf + vo->buf_pos && vo->buf_pos + size <= vo->buf_size);
+    vo->buf_pos += size;
+
+    trace_dbus_audio_put_buffer_out(size);
+
+    if (vo->buf_pos < vo->buf_size) {
+        return size;
+    }
+
+    bytes = g_bytes_new_take(g_steal_pointer(&vo->buf), vo->buf_size);
+    v_data = g_variant_new_from_bytes(G_VARIANT_TYPE("ay"), bytes, TRUE);
+    g_variant_ref_sink(v_data);
+
+    g_hash_table_iter_init(&iter, da->out_listeners);
+    while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+        qemu_dbus_display1_audio_out_listener_call_write(
+            listener,
+            (uintptr_t)hw,
+            v_data,
+            G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+    }
+
+    return size;
+}
+
+#ifdef HOST_WORDS_BIGENDIAN
+#define AUDIO_HOST_BE TRUE
+#else
+#define AUDIO_HOST_BE FALSE
+#endif
+
+static void
+dbus_init_out_listener(QemuDBusDisplay1AudioOutListener *listener,
+                       HWVoiceOut *hw)
+{
+    qemu_dbus_display1_audio_out_listener_call_init(
+        listener,
+        (uintptr_t)hw,
+        hw->info.bits,
+        hw->info.is_signed,
+        hw->info.is_float,
+        hw->info.freq,
+        hw->info.nchannels,
+        hw->info.bytes_per_frame,
+        hw->info.bytes_per_second,
+        hw->info.swap_endianness ? !AUDIO_HOST_BE : AUDIO_HOST_BE,
+        G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+}
+
+static int
+dbus_init_out(HWVoiceOut *hw, struct audsettings *as, void *drv_opaque)
+{
+    DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+    DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
+    GHashTableIter iter;
+    QemuDBusDisplay1AudioOutListener *listener = NULL;
+
+    audio_pcm_init_info(&hw->info, as);
+    hw->samples = DBUS_AUDIO_NSAMPLES;
+    audio_rate_start(&vo->rate);
+
+    g_hash_table_iter_init(&iter, da->out_listeners);
+    while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+        dbus_init_out_listener(listener, hw);
+    }
+    return 0;
+}
+
+static void
+dbus_fini_out(HWVoiceOut *hw)
+{
+    DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+    DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
+    GHashTableIter iter;
+    QemuDBusDisplay1AudioOutListener *listener = NULL;
+
+    g_hash_table_iter_init(&iter, da->out_listeners);
+    while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+        qemu_dbus_display1_audio_out_listener_call_fini(
+            listener,
+            (uintptr_t)hw,
+            G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+    }
+
+    g_clear_pointer(&vo->buf, g_free);
+}
+
+static void
+dbus_enable_out(HWVoiceOut *hw, bool enable)
+{
+    DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+    DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
+    GHashTableIter iter;
+    QemuDBusDisplay1AudioOutListener *listener = NULL;
+
+    vo->enabled = enable;
+    if (enable) {
+        audio_rate_start(&vo->rate);
+    }
+
+    g_hash_table_iter_init(&iter, da->out_listeners);
+    while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+        qemu_dbus_display1_audio_out_listener_call_set_enabled(
+            listener, (uintptr_t)hw, enable,
+            G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+    }
+}
+
+static void
+dbus_volume_out_listener(HWVoiceOut *hw,
+                         QemuDBusDisplay1AudioOutListener *listener)
+{
+    DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
+    Volume *vol = &vo->volume;
+    g_autoptr(GBytes) bytes = NULL;
+    GVariant *v_vol = NULL;
+
+    if (!vo->has_volume) {
+        return;
+    }
+
+    assert(vol->channels < sizeof(vol->vol));
+    bytes = g_bytes_new(vol->vol, vol->channels);
+    v_vol = g_variant_new_from_bytes(G_VARIANT_TYPE("ay"), bytes, TRUE);
+    qemu_dbus_display1_audio_out_listener_call_set_volume(
+        listener, (uintptr_t)hw, vol->mute, v_vol,
+        G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+}
+
+static void
+dbus_volume_out(HWVoiceOut *hw, Volume *vol)
+{
+    DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+    DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
+    GHashTableIter iter;
+    QemuDBusDisplay1AudioOutListener *listener = NULL;
+
+    vo->has_volume = true;
+    vo->volume = *vol;
+
+    g_hash_table_iter_init(&iter, da->out_listeners);
+    while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+        dbus_volume_out_listener(hw, listener);
+    }
+}
+
+static void
+dbus_init_in_listener(QemuDBusDisplay1AudioInListener *listener, HWVoiceIn *hw)
+{
+    qemu_dbus_display1_audio_in_listener_call_init(
+        listener,
+        (uintptr_t)hw,
+        hw->info.bits,
+        hw->info.is_signed,
+        hw->info.is_float,
+        hw->info.freq,
+        hw->info.nchannels,
+        hw->info.bytes_per_frame,
+        hw->info.bytes_per_second,
+        hw->info.swap_endianness ? !AUDIO_HOST_BE : AUDIO_HOST_BE,
+        G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+}
+
+static int
+dbus_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque)
+{
+    DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+    DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw);
+    GHashTableIter iter;
+    QemuDBusDisplay1AudioInListener *listener = NULL;
+
+    audio_pcm_init_info(&hw->info, as);
+    hw->samples = DBUS_AUDIO_NSAMPLES;
+    audio_rate_start(&vo->rate);
+
+    g_hash_table_iter_init(&iter, da->in_listeners);
+    while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+        dbus_init_in_listener(listener, hw);
+    }
+    return 0;
+}
+
+static void
+dbus_fini_in(HWVoiceIn *hw)
+{
+    DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+    GHashTableIter iter;
+    QemuDBusDisplay1AudioInListener *listener = NULL;
+
+    g_hash_table_iter_init(&iter, da->in_listeners);
+    while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+        qemu_dbus_display1_audio_in_listener_call_fini(
+            listener,
+            (uintptr_t)hw,
+            G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+    }
+}
+
+static void
+dbus_volume_in_listener(HWVoiceIn *hw,
+                         QemuDBusDisplay1AudioInListener *listener)
+{
+    DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw);
+    Volume *vol = &vo->volume;
+    g_autoptr(GBytes) bytes = NULL;
+    GVariant *v_vol = NULL;
+
+    if (!vo->has_volume) {
+        return;
+    }
+
+    assert(vol->channels < sizeof(vol->vol));
+    bytes = g_bytes_new(vol->vol, vol->channels);
+    v_vol = g_variant_new_from_bytes(G_VARIANT_TYPE("ay"), bytes, TRUE);
+    qemu_dbus_display1_audio_in_listener_call_set_volume(
+        listener, (uintptr_t)hw, vol->mute, v_vol,
+        G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+}
+
+static void
+dbus_volume_in(HWVoiceIn *hw, Volume *vol)
+{
+    DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+    DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw);
+    GHashTableIter iter;
+    QemuDBusDisplay1AudioInListener *listener = NULL;
+
+    vo->has_volume = true;
+    vo->volume = *vol;
+
+    g_hash_table_iter_init(&iter, da->in_listeners);
+    while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+        dbus_volume_in_listener(hw, listener);
+    }
+}
+
+static size_t
+dbus_read(HWVoiceIn *hw, void *buf, size_t size)
+{
+    DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+    /* DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw); */
+    GHashTableIter iter;
+    QemuDBusDisplay1AudioInListener *listener = NULL;
+
+    trace_dbus_audio_read(size);
+
+    /* size = audio_rate_get_bytes(&hw->info, &vo->rate, size); */
+
+    g_hash_table_iter_init(&iter, da->in_listeners);
+    while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+        g_autoptr(GVariant) v_data = NULL;
+        const char *data;
+        gsize n = 0;
+
+        if (qemu_dbus_display1_audio_in_listener_call_read_sync(
+                listener,
+                (uintptr_t)hw,
+                size,
+                G_DBUS_CALL_FLAGS_NONE, -1,
+                &v_data, NULL, NULL)) {
+            data = g_variant_get_fixed_array(v_data, &n, 1);
+            g_warn_if_fail(n <= size);
+            size = MIN(n, size);
+            memcpy(buf, data, size);
+            break;
+        }
+    }
+
+    return size;
+}
+
+static void
+dbus_enable_in(HWVoiceIn *hw, bool enable)
+{
+    DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+    DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw);
+    GHashTableIter iter;
+    QemuDBusDisplay1AudioInListener *listener = NULL;
+
+    vo->enabled = enable;
+    if (enable) {
+        audio_rate_start(&vo->rate);
+    }
+
+    g_hash_table_iter_init(&iter, da->in_listeners);
+    while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+        qemu_dbus_display1_audio_in_listener_call_set_enabled(
+            listener, (uintptr_t)hw, enable,
+            G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+    }
+}
+
+static void *
+dbus_audio_init(Audiodev *dev)
+{
+    DBusAudio *da = g_new0(DBusAudio, 1);
+
+    da->out_listeners = g_hash_table_new_full(g_str_hash, g_str_equal,
+                                                g_free, g_object_unref);
+    da->in_listeners = g_hash_table_new_full(g_str_hash, g_str_equal,
+                                               g_free, g_object_unref);
+    return da;
+}
+
+static void
+dbus_audio_fini(void *opaque)
+{
+    DBusAudio *da = opaque;
+
+    if (da->server) {
+        g_dbus_object_manager_server_unexport(da->server,
+                                              DBUS_DISPLAY1_AUDIO_PATH);
+    }
+    g_clear_object(&da->audio);
+    g_clear_object(&da->iface);
+    g_clear_pointer(&da->in_listeners, g_hash_table_unref);
+    g_clear_pointer(&da->out_listeners, g_hash_table_unref);
+    g_clear_object(&da->server);
+    g_free(da);
+}
+
+static void
+listener_out_vanished_cb(GDBusConnection *connection,
+                         gboolean remote_peer_vanished,
+                         GError *error,
+                         DBusAudio *da)
+{
+    char *name = g_object_get_data(G_OBJECT(connection), "name");
+
+    g_hash_table_remove(da->out_listeners, name);
+}
+
+static void
+listener_in_vanished_cb(GDBusConnection *connection,
+                        gboolean remote_peer_vanished,
+                        GError *error,
+                        DBusAudio *da)
+{
+    char *name = g_object_get_data(G_OBJECT(connection), "name");
+
+    g_hash_table_remove(da->in_listeners, name);
+}
+
+static gboolean
+dbus_audio_register_listener(AudioState *s,
+                             GDBusMethodInvocation *invocation,
+                             GUnixFDList *fd_list,
+                             GVariant *arg_listener,
+                             bool out)
+{
+    DBusAudio *da = s->drv_opaque;
+    const char *sender = g_dbus_method_invocation_get_sender(invocation);
+    g_autoptr(GDBusConnection) listener_conn = NULL;
+    g_autoptr(GError) err = NULL;
+    g_autoptr(GSocket) socket = NULL;
+    g_autoptr(GSocketConnection) socket_conn = NULL;
+    g_autofree char *guid = g_dbus_generate_guid();
+    GHashTable *listeners = out ? da->out_listeners : da->in_listeners;
+    GObject *listener;
+    int fd;
+
+    trace_dbus_audio_register(sender, out ? "out" : "in");
+
+    if (g_hash_table_contains(listeners, sender)) {
+        g_dbus_method_invocation_return_error(invocation,
+                                              DBUS_DISPLAY_ERROR,
+                                              DBUS_DISPLAY_ERROR_INVALID,
+                                              "`%s` is already registered!",
+                                              sender);
+        return DBUS_METHOD_INVOCATION_HANDLED;
+    }
+
+    fd = g_unix_fd_list_get(fd_list, g_variant_get_handle(arg_listener), &err);
+    if (err) {
+        g_dbus_method_invocation_return_error(invocation,
+                                              DBUS_DISPLAY_ERROR,
+                                              DBUS_DISPLAY_ERROR_FAILED,
+                                              "Couldn't get peer fd: %s",
+                                              err->message);
+        return DBUS_METHOD_INVOCATION_HANDLED;
+    }
+
+    socket = g_socket_new_from_fd(fd, &err);
+    if (err) {
+        g_dbus_method_invocation_return_error(invocation,
+                                              DBUS_DISPLAY_ERROR,
+                                              DBUS_DISPLAY_ERROR_FAILED,
+                                              "Couldn't make a socket: %s",
+                                              err->message);
+        return DBUS_METHOD_INVOCATION_HANDLED;
+    }
+    socket_conn = g_socket_connection_factory_create_connection(socket);
+    if (out) {
+        qemu_dbus_display1_audio_complete_register_out_listener(
+            da->iface, invocation, NULL);
+    } else {
+        qemu_dbus_display1_audio_complete_register_in_listener(
+            da->iface, invocation, NULL);
+    }
+
+    listener_conn =
+        g_dbus_connection_new_sync(
+            G_IO_STREAM(socket_conn),
+            guid,
+            G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER,
+            NULL, NULL, &err);
+    if (err) {
+        error_report("Failed to setup peer connection: %s", err->message);
+        return DBUS_METHOD_INVOCATION_HANDLED;
+    }
+
+    listener = out ?
+        G_OBJECT(qemu_dbus_display1_audio_out_listener_proxy_new_sync(
+            listener_conn,
+            G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+            NULL,
+            "/org/qemu/Display1/AudioOutListener",
+            NULL,
+            &err)) :
+        G_OBJECT(qemu_dbus_display1_audio_in_listener_proxy_new_sync(
+            listener_conn,
+            G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+            NULL,
+            "/org/qemu/Display1/AudioInListener",
+            NULL,
+            &err));
+    if (!listener) {
+        error_report("Failed to setup proxy: %s", err->message);
+        return DBUS_METHOD_INVOCATION_HANDLED;
+    }
+
+    if (out) {
+        HWVoiceOut *hw;
+
+        QLIST_FOREACH(hw, &s->hw_head_out, entries) {
+            DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
+            QemuDBusDisplay1AudioOutListener *l =
+                QEMU_DBUS_DISPLAY1_AUDIO_OUT_LISTENER(listener);
+
+            dbus_init_out_listener(l, hw);
+            qemu_dbus_display1_audio_out_listener_call_set_enabled(
+                l, (uintptr_t)hw, vo->enabled,
+                G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+        }
+    } else {
+        HWVoiceIn *hw;
+
+        QLIST_FOREACH(hw, &s->hw_head_in, entries) {
+            DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw);
+            QemuDBusDisplay1AudioInListener *l =
+                QEMU_DBUS_DISPLAY1_AUDIO_IN_LISTENER(listener);
+
+            dbus_init_in_listener(
+                QEMU_DBUS_DISPLAY1_AUDIO_IN_LISTENER(listener), hw);
+            qemu_dbus_display1_audio_in_listener_call_set_enabled(
+                l, (uintptr_t)hw, vo->enabled,
+                G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+        }
+    }
+
+    g_object_set_data_full(G_OBJECT(listener_conn), "name",
+                           g_strdup(sender), g_free);
+    g_hash_table_insert(listeners, g_strdup(sender), listener);
+    g_object_connect(listener_conn,
+                     "signal::closed",
+                     out ? listener_out_vanished_cb : listener_in_vanished_cb,
+                     da,
+                     NULL);
+
+    return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static gboolean
+dbus_audio_register_out_listener(AudioState *s,
+                                 GDBusMethodInvocation *invocation,
+                                 GUnixFDList *fd_list,
+                                 GVariant *arg_listener)
+{
+    return dbus_audio_register_listener(s, invocation,
+                                        fd_list, arg_listener, true);
+
+}
+
+static gboolean
+dbus_audio_register_in_listener(AudioState *s,
+                                GDBusMethodInvocation *invocation,
+                                GUnixFDList *fd_list,
+                                GVariant *arg_listener)
+{
+    return dbus_audio_register_listener(s, invocation,
+                                        fd_list, arg_listener, false);
+}
+
+static void
+dbus_audio_set_server(AudioState *s, GDBusObjectManagerServer *server)
+{
+    DBusAudio *da = s->drv_opaque;
+
+    g_assert(da);
+    g_assert(!da->server);
+
+    da->server = g_object_ref(server);
+
+    da->audio = g_dbus_object_skeleton_new(DBUS_DISPLAY1_AUDIO_PATH);
+    da->iface = qemu_dbus_display1_audio_skeleton_new();
+    g_object_connect(da->iface,
+                     "swapped-signal::handle-register-in-listener",
+                     dbus_audio_register_in_listener, s,
+                     "swapped-signal::handle-register-out-listener",
+                     dbus_audio_register_out_listener, s,
+                     NULL);
+
+    g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(da->audio),
+                                         G_DBUS_INTERFACE_SKELETON(da->iface));
+    g_dbus_object_manager_server_export(da->server, da->audio);
+}
+
+static struct audio_pcm_ops dbus_pcm_ops = {
+    .init_out = dbus_init_out,
+    .fini_out = dbus_fini_out,
+    .write    = audio_generic_write,
+    .get_buffer_out = dbus_get_buffer_out,
+    .put_buffer_out = dbus_put_buffer_out,
+    .enable_out = dbus_enable_out,
+    .volume_out = dbus_volume_out,
+
+    .init_in  = dbus_init_in,
+    .fini_in  = dbus_fini_in,
+    .read     = dbus_read,
+    .run_buffer_in = audio_generic_run_buffer_in,
+    .enable_in = dbus_enable_in,
+    .volume_in = dbus_volume_in,
+};
+
+static struct audio_driver dbus_audio_driver = {
+    .name            = "dbus",
+    .descr           = "Timer based audio exposed with DBus interface",
+    .init            = dbus_audio_init,
+    .fini            = dbus_audio_fini,
+    .set_dbus_server = dbus_audio_set_server,
+    .pcm_ops         = &dbus_pcm_ops,
+    .can_be_default  = 1,
+    .max_voices_out  = INT_MAX,
+    .max_voices_in   = INT_MAX,
+    .voice_size_out  = sizeof(DBusVoiceOut),
+    .voice_size_in   = sizeof(DBusVoiceIn)
+};
+
+static void register_audio_dbus(void)
+{
+    audio_driver_register(&dbus_audio_driver);
+}
+type_init(register_audio_dbus);
+
+module_dep("ui-dbus")
diff --git a/ui/dbus.c b/ui/dbus.c
index 56e284fb97..7cfbf1a80c 100644
--- a/ui/dbus.c
+++ b/ui/dbus.c
@@ -30,6 +30,8 @@
 #include "ui/dbus-module.h"
 #include "ui/egl-helpers.h"
 #include "ui/egl-context.h"
+#include "audio/audio.h"
+#include "audio/audio_int.h"
 #include "qapi/error.h"
 #include "trace.h"
 
@@ -84,6 +86,7 @@ dbus_display_finalize(Object *o)
     g_clear_object(&dd->bus);
     g_clear_object(&dd->iface);
     g_free(dd->dbus_addr);
+    g_free(dd->audiodev);
     dbus_display = NULL;
 }
 
@@ -140,6 +143,19 @@ dbus_display_complete(UserCreatable *uc, Error **errp)
         return;
     }
 
+    if (dd->audiodev && *dd->audiodev) {
+        AudioState *audio_state = audio_state_by_name(dd->audiodev);
+        if (!audio_state) {
+            error_setg(errp, "Audiodev '%s' not found", dd->audiodev);
+            return;
+        }
+        if (!g_str_equal(audio_state->drv->name, "dbus")) {
+            error_setg(errp, "Audiodev '%s' is not compatible with DBus",
+                       dd->audiodev);
+            return;
+        }
+        audio_state->drv->set_dbus_server(audio_state, dd->server);
+    }
 
     consoles = g_array_new(FALSE, FALSE, sizeof(guint32));
     for (idx = 0;; idx++) {
@@ -256,6 +272,23 @@ set_dbus_addr(Object *o, const char *str, Error **errp)
     dd->dbus_addr = g_strdup(str);
 }
 
+static char *
+get_audiodev(Object *o, Error **errp)
+{
+    DBusDisplay *dd = DBUS_DISPLAY(o);
+
+    return g_strdup(dd->audiodev);
+}
+
+static void
+set_audiodev(Object *o, const char *str, Error **errp)
+{
+    DBusDisplay *dd = DBUS_DISPLAY(o);
+
+    g_free(dd->audiodev);
+    dd->audiodev = g_strdup(str);
+}
+
 static int
 get_gl_mode(Object *o, Error **errp)
 {
@@ -280,6 +313,7 @@ dbus_display_class_init(ObjectClass *oc, void *data)
     ucc->complete = dbus_display_complete;
     object_class_property_add_bool(oc, "p2p", get_dbus_p2p, set_dbus_p2p);
     object_class_property_add_str(oc, "addr", get_dbus_addr, set_dbus_addr);
+    object_class_property_add_str(oc, "audiodev", get_audiodev, set_audiodev);
     object_class_property_add_enum(oc, "gl-mode",
                                    "DisplayGLMode", &DisplayGLMode_lookup,
                                    get_gl_mode, set_gl_mode);
@@ -316,6 +350,7 @@ dbus_init(DisplayState *ds, DisplayOptions *opts)
                           object_get_objects_root(),
                           "dbus-display", &error_fatal,
                           "addr", opts->u.dbus.addr ?: "",
+                          "audiodev", opts->u.dbus.audiodev ?: "",
                           "gl-mode", DisplayGLMode_str(mode),
                           "p2p", yes_no(opts->u.dbus.p2p),
                           NULL);
diff --git a/audio/meson.build b/audio/meson.build
index 7d53b0f920..13d499bcbb 100644
--- a/audio/meson.build
+++ b/audio/meson.build
@@ -18,7 +18,7 @@ foreach m : [
   ['CONFIG_AUDIO_PA', 'pa', pulse, 'paaudio.c'],
   ['CONFIG_AUDIO_SDL', 'sdl', sdl, 'sdlaudio.c'],
   ['CONFIG_AUDIO_JACK', 'jack', jack, 'jackaudio.c'],
-  ['CONFIG_SPICE', 'spice', spice, 'spiceaudio.c']
+  ['CONFIG_SPICE', 'spice', spice, 'spiceaudio.c'],
 ]
   if config_host.has_key(m[0])
     module_ss = ss.source_set()
@@ -27,4 +27,10 @@ foreach m : [
   endif
 endforeach
 
+if dbus_display
+    module_ss = ss.source_set()
+    module_ss.add(when: gio, if_true: files('dbusaudio.c'))
+    audio_modules += {'dbus': module_ss}
+endif
+
 modules += {'audio': audio_modules}
diff --git a/audio/trace-events b/audio/trace-events
index 957c92337b..e1ab643add 100644
--- a/audio/trace-events
+++ b/audio/trace-events
@@ -13,6 +13,11 @@ alsa_resume_out(void) "Resuming suspended output stream"
 # ossaudio.c
 oss_version(int version) "OSS version = 0x%x"
 
+# dbusaudio.c
+dbus_audio_register(const char *s, const char *dir) "sender = %s, dir = %s"
+dbus_audio_put_buffer_out(size_t len) "len = %zu"
+dbus_audio_read(size_t len) "len = %zu"
+
 # audio.c
 audio_timer_start(int interval) "interval %d ms"
 audio_timer_stop(void) ""
diff --git a/qemu-options.hx b/qemu-options.hx
index b3352a7fbc..060fafdf51 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -659,6 +659,9 @@ DEF("audiodev", HAS_ARG, QEMU_OPTION_audiodev,
 #endif
 #ifdef CONFIG_SPICE
     "-audiodev spice,id=id[,prop[=value][,...]]\n"
+#endif
+#ifdef CONFIG_DBUS_DISPLAY
+    "-audiodev dbus,id=id[,prop[=value][,...]]\n"
 #endif
     "-audiodev wav,id=id[,prop[=value][,...]]\n"
     "                path= path of wav file to record\n",
diff --git a/ui/dbus-display1.xml b/ui/dbus-display1.xml
index 0f0ae92e4d..aff645220c 100644
--- a/ui/dbus-display1.xml
+++ b/ui/dbus-display1.xml
@@ -375,4 +375,215 @@
       </arg>
     </method>
   </interface>
+
+  <!--
+      org.qemu.Display1.Audio:
+
+      Audio backend may be available on ``/org/qemu/Display1/Audio``.
+  -->
+  <interface name="org.qemu.Display1.Audio">
+    <!--
+        RegisterOutListener:
+        @listener: a Unix socket FD, for peer-to-peer D-Bus communication.
+
+        Register an audio backend playback handler.
+
+        Multiple listeners may be registered simultaneously.
+
+        The listener is expected to implement the
+        :dbus:iface:`org.qemu.Display1.AudioOutListener` interface.
+    -->
+    <method name="RegisterOutListener">
+      <arg type="h" name="listener" direction="in"/>
+    </method>
+
+    <!--
+        RegisterInListener:
+        @listener: a Unix socket FD, for peer-to-peer D-Bus communication.
+
+        Register an audio backend record handler.
+
+        Multiple listeners may be registered simultaneously.
+
+        The listener is expected to implement the
+        :dbus:iface:`org.qemu.Display1.AudioInListener` interface.
+    -->
+    <method name="RegisterInListener">
+      <arg type="h" name="listener" direction="in"/>
+    </method>
+  </interface>
+
+  <!--
+      org.qemu.Display1.AudioOutListener:
+
+      This client-side interface must be available on
+      ``/org/qemu/Display1/AudioOutListener`` when registering the peer-to-peer
+      connection with :dbus:meth:`~org.qemu.Display1.Audio.RegisterOutListener`.
+  -->
+  <interface name="org.qemu.Display1.AudioOutListener">
+    <!--
+        Init:
+        @id: the stream ID.
+        @bits: PCM bits per sample.
+        @is_signed: whether the PCM data is signed.
+        @is_float: PCM floating point format.
+        @freq: the PCM frequency in Hz.
+        @nchannels: the number of channels.
+        @bytes_per_frame: the bytes per frame.
+        @bytes_per_second: the bytes per second.
+        @be: whether using big-endian format.
+
+        Initializes a PCM playback stream.
+    -->
+    <method name="Init">
+      <arg name="id" type="t" direction="in"/>
+      <arg name="bits" type="y" direction="in"/>
+      <arg name="is_signed" type="b" direction="in"/>
+      <arg name="is_float" type="b" direction="in"/>
+      <arg name="freq" type="u" direction="in"/>
+      <arg name="nchannels" type="y" direction="in"/>
+      <arg name="bytes_per_frame" type="u" direction="in"/>
+      <arg name="bytes_per_second" type="u" direction="in"/>
+      <arg name="be" type="b" direction="in"/>
+    </method>
+
+    <!--
+        Fini:
+        @id: the stream ID.
+
+        Finish & close a playback stream.
+    -->
+    <method name="Fini">
+      <arg name="id" type="t" direction="in"/>
+    </method>
+
+    <!--
+        SetEnabled:
+        @id: the stream ID.
+
+        Resume or suspend the playback stream.
+    -->
+    <method name="SetEnabled">
+      <arg name="id" type="t" direction="in"/>
+      <arg name="enabled" type="b" direction="in"/>
+    </method>
+
+    <!--
+        SetVolume:
+        @id: the stream ID.
+        @mute: whether the stream is muted.
+        @volume: the volume per-channel.
+
+        Set the stream volume and mute state (volume without unit, 0-255).
+    -->
+    <method name="SetVolume">
+      <arg name="id" type="t" direction="in"/>
+      <arg name="mute" type="b" direction="in"/>
+      <arg name="volume" type="ay" direction="in">
+        <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
+      </arg>
+    </method>
+
+    <!--
+        Write:
+        @id: the stream ID.
+        @data: the PCM data.
+
+        PCM stream to play.
+    -->
+    <method name="Write">
+      <arg name="id" type="t" direction="in"/>
+      <arg type="ay" name="data" direction="in">
+        <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
+      </arg>
+    </method>
+  </interface>
+
+  <!--
+      org.qemu.Display1.AudioInListener:
+
+      This client-side interface must be available on
+      ``/org/qemu/Display1/AudioInListener`` when registering the peer-to-peer
+      connection with :dbus:meth:`~org.qemu.Display1.Audio.RegisterInListener`.
+  -->
+  <interface name="org.qemu.Display1.AudioInListener">
+    <!--
+        Init:
+        @id: the stream ID.
+        @bits: PCM bits per sample.
+        @is_signed: whether the PCM data is signed.
+        @is_float: PCM floating point format.
+        @freq: the PCM frequency in Hz.
+        @nchannels: the number of channels.
+        @bytes_per_frame: the bytes per frame.
+        @bytes_per_second: the bytes per second.
+        @be: whether using big-endian format.
+
+        Initializes a PCM record stream.
+    -->
+    <method name="Init">
+      <arg name="id" type="t" direction="in"/>
+      <arg name="bits" type="y" direction="in"/>
+      <arg name="is_signed" type="b" direction="in"/>
+      <arg name="is_float" type="b" direction="in"/>
+      <arg name="freq" type="u" direction="in"/>
+      <arg name="nchannels" type="y" direction="in"/>
+      <arg name="bytes_per_frame" type="u" direction="in"/>
+      <arg name="bytes_per_second" type="u" direction="in"/>
+      <arg name="be" type="b" direction="in"/>
+    </method>
+
+    <!--
+        Fini:
+        @id: the stream ID.
+
+        Finish & close a record stream.
+    -->
+    <method name="Fini">
+      <arg name="id" type="t" direction="in"/>
+    </method>
+
+    <!--
+        SetEnabled:
+        @id: the stream ID.
+
+        Resume or suspend the record stream.
+    -->
+    <method name="SetEnabled">
+      <arg name="id" type="t" direction="in"/>
+      <arg name="enabled" type="b" direction="in"/>
+    </method>
+
+    <!--
+        SetVolume:
+        @id: the stream ID.
+        @mute: whether the stream is muted.
+        @volume: the volume per-channel.
+
+        Set the stream volume and mute state (volume without unit, 0-255).
+    -->
+    <method name="SetVolume">
+      <arg name="id" type="t" direction="in"/>
+      <arg name="mute" type="b" direction="in"/>
+      <arg name="volume" type="ay" direction="in">
+        <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
+      </arg>
+    </method>
+
+    <!--
+        Read:
+        @id: the stream ID.
+        @size: the amount to read, in bytes.
+        @data: the recorded data (which may be less than requested).
+
+        Read "size" bytes from the record stream.
+    -->
+    <method name="Read">
+      <arg name="id" type="t" direction="in"/>
+      <arg name="size" type="t" direction="in"/>
+      <arg type="ay" name="data" direction="out">
+        <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
+      </arg>
+    </method>
+  </interface>
 </node>
-- 
2.33.0.721.g106298f7f9



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v2 31/37] ui/dbus: add clipboard interface
  2021-10-09 21:08 [PATCH v2 00/37] Add D-Bus display backend marcandre.lureau
                   ` (29 preceding siblings ...)
  2021-10-09 21:08 ` [PATCH v2 30/37] audio: add "dbus" audio backend marcandre.lureau
@ 2021-10-09 21:08 ` marcandre.lureau
  2021-10-09 21:08 ` [PATCH v2 32/37] chardev: teach socket to accept no addresses marcandre.lureau
                   ` (6 subsequent siblings)
  37 siblings, 0 replies; 54+ messages in thread
From: marcandre.lureau @ 2021-10-09 21:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, kraxel

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Expose the clipboard API over D-Bus. See the interface documentation for
further details.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 ui/dbus.h            |  14 ++
 ui/dbus-clipboard.c  | 457 +++++++++++++++++++++++++++++++++++++++++++
 ui/dbus.c            |   7 +
 ui/dbus-display1.xml |  97 +++++++++
 ui/meson.build       |   1 +
 ui/trace-events      |   3 +
 6 files changed, 579 insertions(+)
 create mode 100644 ui/dbus-clipboard.c

diff --git a/ui/dbus.h b/ui/dbus.h
index ca1f0f4ab9..3e89eafcab 100644
--- a/ui/dbus.h
+++ b/ui/dbus.h
@@ -27,9 +27,16 @@
 #include "qemu/dbus.h"
 #include "qom/object.h"
 #include "ui/console.h"
+#include "ui/clipboard.h"
 
 #include "dbus-display1.h"
 
+typedef struct DBusClipboardRequest {
+    GDBusMethodInvocation *invocation;
+    QemuClipboardType type;
+    guint timeout_id;
+} DBusClipboardRequest;
+
 struct DBusDisplay {
     Object parent;
 
@@ -44,6 +51,11 @@ struct DBusDisplay {
     QemuDBusDisplay1VM *iface;
     GPtrArray *consoles;
     GCancellable *add_client_cancellable;
+
+    QemuClipboardPeer clipboard_peer;
+    QemuDBusDisplay1Clipboard *clipboard;
+    QemuDBusDisplay1Clipboard *clipboard_proxy;
+    DBusClipboardRequest clipboard_request[QEMU_CLIPBOARD_SELECTION__COUNT];
 };
 
 #define TYPE_DBUS_DISPLAY "dbus-display"
@@ -83,4 +95,6 @@ dbus_display_listener_get_bus_name(DBusDisplayListener *ddl);
 extern const DisplayChangeListenerOps dbus_gl_dcl_ops;
 extern const DisplayChangeListenerOps dbus_dcl_ops;
 
+void dbus_clipboard_init(DBusDisplay *dpy);
+
 #endif /* UI_DBUS_H_ */
diff --git a/ui/dbus-clipboard.c b/ui/dbus-clipboard.c
new file mode 100644
index 0000000000..5843d26cd2
--- /dev/null
+++ b/ui/dbus-clipboard.c
@@ -0,0 +1,457 @@
+/*
+ * QEMU DBus display
+ *
+ * Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include "qemu/osdep.h"
+#include "qemu/dbus.h"
+#include "qemu/main-loop.h"
+#include "qom/object_interfaces.h"
+#include "sysemu/sysemu.h"
+#include "qapi/error.h"
+#include "trace.h"
+
+#include "dbus.h"
+
+#define MIME_TEXT_PLAIN_UTF8 "text/plain;charset=utf-8"
+
+static void
+dbus_clipboard_complete_request(
+    DBusDisplay *dpy,
+    GDBusMethodInvocation *invocation,
+    QemuClipboardInfo *info,
+    QemuClipboardType type)
+{
+    GVariant *v_data = g_variant_new_from_data(
+        G_VARIANT_TYPE("ay"),
+        info->types[type].data,
+        info->types[type].size,
+        TRUE,
+        (GDestroyNotify)qemu_clipboard_info_unref,
+        qemu_clipboard_info_ref(info));
+
+    qemu_dbus_display1_clipboard_complete_request(
+        dpy->clipboard, invocation,
+        MIME_TEXT_PLAIN_UTF8, v_data);
+}
+
+static void
+dbus_clipboard_update_info(DBusDisplay *dpy, QemuClipboardInfo *info)
+{
+    bool self_update = info->owner == &dpy->clipboard_peer;
+    const char *mime[QEMU_CLIPBOARD_TYPE__COUNT + 1] = { 0, };
+    DBusClipboardRequest *req;
+    int i = 0;
+
+    if (info->owner == NULL) {
+        if (dpy->clipboard_proxy) {
+            qemu_dbus_display1_clipboard_call_release(
+                dpy->clipboard_proxy,
+                info->selection,
+                G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+        }
+        return;
+    }
+
+    if (self_update || !info->has_serial) {
+        return;
+    }
+
+    req = &dpy->clipboard_request[info->selection];
+    if (req->invocation && info->types[req->type].data) {
+        dbus_clipboard_complete_request(dpy, req->invocation, info, req->type);
+        g_clear_object(&req->invocation);
+        g_source_remove(req->timeout_id);
+        req->timeout_id = 0;
+        return;
+    }
+
+    if (info->types[QEMU_CLIPBOARD_TYPE_TEXT].available) {
+        mime[i++] = MIME_TEXT_PLAIN_UTF8;
+    }
+
+    if (i > 0) {
+        if (dpy->clipboard_proxy) {
+            qemu_dbus_display1_clipboard_call_grab(
+                dpy->clipboard_proxy,
+                info->selection,
+                info->serial,
+                mime,
+                G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+        }
+    }
+}
+
+static void
+dbus_clipboard_reset_serial(DBusDisplay *dpy)
+{
+    if (dpy->clipboard_proxy) {
+        qemu_dbus_display1_clipboard_call_register(
+            dpy->clipboard_proxy,
+            G_DBUS_CALL_FLAGS_NONE,
+            -1, NULL, NULL, NULL);
+    }
+}
+
+static void
+dbus_clipboard_notify(Notifier *notifier, void *data)
+{
+    DBusDisplay *dpy =
+        container_of(notifier, DBusDisplay, clipboard_peer.notifier);
+    QemuClipboardNotify *notify = data;
+
+    switch (notify->type) {
+    case QEMU_CLIPBOARD_UPDATE_INFO:
+        dbus_clipboard_update_info(dpy, notify->info);
+        return;
+    case QEMU_CLIPBOARD_RESET_SERIAL:
+        dbus_clipboard_reset_serial(dpy);
+        return;
+    }
+}
+
+static void
+dbus_clipboard_qemu_request(QemuClipboardInfo *info,
+                            QemuClipboardType type)
+{
+    DBusDisplay *dpy = container_of(info->owner, DBusDisplay, clipboard_peer);
+    g_autofree char *mime = NULL;
+    g_autoptr(GVariant) v_data = NULL;
+    g_autoptr(GError) err = NULL;
+    const char *data = NULL;
+    const char *mimes[] = { MIME_TEXT_PLAIN_UTF8, NULL };
+    size_t n;
+
+    if (type != QEMU_CLIPBOARD_TYPE_TEXT) {
+        /* unsupported atm */
+        return;
+    }
+
+    if (dpy->clipboard_proxy) {
+        if (!qemu_dbus_display1_clipboard_call_request_sync(
+                dpy->clipboard_proxy,
+                info->selection,
+                mimes,
+                G_DBUS_CALL_FLAGS_NONE, -1, &mime, &v_data, NULL, &err)) {
+            error_report("Failed to request clipboard: %s", err->message);
+            return;
+        }
+
+        if (g_strcmp0(mime, MIME_TEXT_PLAIN_UTF8)) {
+            error_report("Unsupported returned MIME: %s", mime);
+            return;
+        }
+
+        data = g_variant_get_fixed_array(v_data, &n, 1);
+        qemu_clipboard_set_data(&dpy->clipboard_peer, info, type,
+                                n, data, true);
+    }
+}
+
+static void
+dbus_clipboard_request_cancelled(DBusClipboardRequest *req)
+{
+    if (!req->invocation) {
+        return;
+    }
+
+    g_dbus_method_invocation_return_error(
+        req->invocation,
+        DBUS_DISPLAY_ERROR,
+        DBUS_DISPLAY_ERROR_FAILED,
+        "Cancelled clipboard request");
+
+    g_clear_object(&req->invocation);
+    g_source_remove(req->timeout_id);
+    req->timeout_id = 0;
+}
+
+static void
+dbus_clipboard_unregister_proxy(DBusDisplay *dpy)
+{
+    const char *name = NULL;
+    int i;
+
+    for (i = 0; i < G_N_ELEMENTS(dpy->clipboard_request); ++i) {
+        dbus_clipboard_request_cancelled(&dpy->clipboard_request[i]);
+    }
+
+    if (!dpy->clipboard_proxy) {
+        return;
+    }
+
+    name = g_dbus_proxy_get_name(G_DBUS_PROXY(dpy->clipboard_proxy));
+    trace_dbus_clipboard_unregister(name);
+    g_clear_object(&dpy->clipboard_proxy);
+}
+
+static void
+dbus_on_clipboard_proxy_name_owner_changed(
+    DBusDisplay *dpy,
+    GObject *object,
+    GParamSpec *pspec)
+{
+    dbus_clipboard_unregister_proxy(dpy);
+}
+
+static gboolean
+dbus_clipboard_register(
+    DBusDisplay *dpy,
+    GDBusMethodInvocation *invocation)
+{
+    g_autoptr(GError) err = NULL;
+    const char *name = NULL;
+
+    if (dpy->clipboard_proxy) {
+        g_dbus_method_invocation_return_error(
+            invocation,
+            DBUS_DISPLAY_ERROR,
+            DBUS_DISPLAY_ERROR_FAILED,
+            "Clipboard peer already registered!");
+        return DBUS_METHOD_INVOCATION_HANDLED;
+    }
+
+    dpy->clipboard_proxy =
+        qemu_dbus_display1_clipboard_proxy_new_sync(
+            g_dbus_method_invocation_get_connection(invocation),
+            G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+            g_dbus_method_invocation_get_sender(invocation),
+            "/org/qemu/Display1/Clipboard",
+            NULL,
+            &err);
+    if (!dpy->clipboard_proxy) {
+        g_dbus_method_invocation_return_error(
+            invocation,
+            DBUS_DISPLAY_ERROR,
+            DBUS_DISPLAY_ERROR_FAILED,
+            "Failed to setup proxy: %s", err->message);
+        return DBUS_METHOD_INVOCATION_HANDLED;
+    }
+
+    name = g_dbus_proxy_get_name(G_DBUS_PROXY(dpy->clipboard_proxy));
+    trace_dbus_clipboard_register(name);
+
+    g_object_connect(dpy->clipboard_proxy,
+                     "swapped-signal::notify::g-name-owner",
+                     dbus_on_clipboard_proxy_name_owner_changed, dpy,
+                     NULL);
+    qemu_clipboard_reset_serial();
+
+    qemu_dbus_display1_clipboard_complete_register(dpy->clipboard, invocation);
+    return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static gboolean
+dbus_clipboard_check_caller(DBusDisplay *dpy, GDBusMethodInvocation *invocation)
+{
+    if (!dpy->clipboard_proxy ||
+        g_strcmp0(g_dbus_proxy_get_name(G_DBUS_PROXY(dpy->clipboard_proxy)),
+                  g_dbus_method_invocation_get_sender(invocation))) {
+        g_dbus_method_invocation_return_error(
+            invocation,
+            DBUS_DISPLAY_ERROR,
+            DBUS_DISPLAY_ERROR_FAILED,
+            "Unregistered caller");
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+static gboolean
+dbus_clipboard_unregister(
+    DBusDisplay *dpy,
+    GDBusMethodInvocation *invocation)
+{
+    if (!dbus_clipboard_check_caller(dpy, invocation)) {
+        return DBUS_METHOD_INVOCATION_HANDLED;
+    }
+
+    dbus_clipboard_unregister_proxy(dpy);
+
+    qemu_dbus_display1_clipboard_complete_unregister(
+        dpy->clipboard, invocation);
+
+    return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static gboolean
+dbus_clipboard_grab(
+    DBusDisplay *dpy,
+    GDBusMethodInvocation *invocation,
+    gint arg_selection,
+    guint arg_serial,
+    const gchar *const *arg_mimes)
+{
+    QemuClipboardSelection s = arg_selection;
+    g_autoptr(QemuClipboardInfo) info = NULL;
+
+    if (!dbus_clipboard_check_caller(dpy, invocation)) {
+        return DBUS_METHOD_INVOCATION_HANDLED;
+    }
+
+    if (s >= QEMU_CLIPBOARD_SELECTION__COUNT) {
+        g_dbus_method_invocation_return_error(
+            invocation,
+            DBUS_DISPLAY_ERROR,
+            DBUS_DISPLAY_ERROR_FAILED,
+            "Invalid clipboard selection: %d", arg_selection);
+        return DBUS_METHOD_INVOCATION_HANDLED;
+    }
+
+    info = qemu_clipboard_info_new(&dpy->clipboard_peer, s);
+    if (g_strv_contains(arg_mimes, MIME_TEXT_PLAIN_UTF8)) {
+        info->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true;
+    }
+    info->serial = arg_serial;
+    info->has_serial = true;
+    if (qemu_clipboard_check_serial(info, true)) {
+        qemu_clipboard_update(info);
+    } else {
+        trace_dbus_clipboard_grab_failed();
+    }
+
+    qemu_dbus_display1_clipboard_complete_grab(dpy->clipboard, invocation);
+    return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static gboolean
+dbus_clipboard_release(
+    DBusDisplay *dpy,
+    GDBusMethodInvocation *invocation,
+    gint arg_selection)
+{
+    if (!dbus_clipboard_check_caller(dpy, invocation)) {
+        return DBUS_METHOD_INVOCATION_HANDLED;
+    }
+
+    qemu_clipboard_peer_release(&dpy->clipboard_peer, arg_selection);
+
+    qemu_dbus_display1_clipboard_complete_release(dpy->clipboard, invocation);
+    return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static gboolean
+dbus_clipboard_request_timeout(gpointer user_data)
+{
+    dbus_clipboard_request_cancelled(user_data);
+    return G_SOURCE_REMOVE;
+}
+
+static gboolean
+dbus_clipboard_request(
+    DBusDisplay *dpy,
+    GDBusMethodInvocation *invocation,
+    gint arg_selection,
+    const gchar *const *arg_mimes)
+{
+    QemuClipboardSelection s = arg_selection;
+    QemuClipboardType type = QEMU_CLIPBOARD_TYPE_TEXT;
+    QemuClipboardInfo *info = NULL;
+
+    if (!dbus_clipboard_check_caller(dpy, invocation)) {
+        return DBUS_METHOD_INVOCATION_HANDLED;
+    }
+
+    if (s >= QEMU_CLIPBOARD_SELECTION__COUNT) {
+        g_dbus_method_invocation_return_error(
+            invocation,
+            DBUS_DISPLAY_ERROR,
+            DBUS_DISPLAY_ERROR_FAILED,
+            "Invalid clipboard selection: %d", arg_selection);
+        return DBUS_METHOD_INVOCATION_HANDLED;
+    }
+
+    if (dpy->clipboard_request[s].invocation) {
+        g_dbus_method_invocation_return_error(
+            invocation,
+            DBUS_DISPLAY_ERROR,
+            DBUS_DISPLAY_ERROR_FAILED,
+            "Pending request");
+        return DBUS_METHOD_INVOCATION_HANDLED;
+    }
+
+    info = qemu_clipboard_info(s);
+    if (!info || !info->owner || info->owner == &dpy->clipboard_peer) {
+        g_dbus_method_invocation_return_error(
+            invocation,
+            DBUS_DISPLAY_ERROR,
+            DBUS_DISPLAY_ERROR_FAILED,
+            "Empty clipboard");
+        return DBUS_METHOD_INVOCATION_HANDLED;
+    }
+
+    if (!g_strv_contains(arg_mimes, MIME_TEXT_PLAIN_UTF8) ||
+        !info->types[type].available) {
+        g_dbus_method_invocation_return_error(
+            invocation,
+            DBUS_DISPLAY_ERROR,
+            DBUS_DISPLAY_ERROR_FAILED,
+            "Unhandled MIME types requested");
+        return DBUS_METHOD_INVOCATION_HANDLED;
+    }
+
+    if (info->types[type].data) {
+        dbus_clipboard_complete_request(dpy, invocation, info, type);
+    } else {
+        qemu_clipboard_request(info, type);
+
+        dpy->clipboard_request[s].invocation = g_object_ref(invocation);
+        dpy->clipboard_request[s].type = type;
+        dpy->clipboard_request[s].timeout_id =
+            g_timeout_add_seconds(5, dbus_clipboard_request_timeout,
+                                  &dpy->clipboard_request[s]);
+    }
+
+    return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+void
+dbus_clipboard_init(DBusDisplay *dpy)
+{
+    g_autoptr(GDBusObjectSkeleton) clipboard = NULL;
+
+    assert(!dpy->clipboard);
+
+    clipboard = g_dbus_object_skeleton_new(DBUS_DISPLAY1_ROOT "/Clipboard");
+    dpy->clipboard = qemu_dbus_display1_clipboard_skeleton_new();
+    g_object_connect(dpy->clipboard,
+                     "swapped-signal::handle-register",
+                     dbus_clipboard_register, dpy,
+                     "swapped-signal::handle-unregister",
+                     dbus_clipboard_unregister, dpy,
+                     "swapped-signal::handle-grab",
+                     dbus_clipboard_grab, dpy,
+                     "swapped-signal::handle-release",
+                     dbus_clipboard_release, dpy,
+                     "swapped-signal::handle-request",
+                     dbus_clipboard_request, dpy,
+                     NULL);
+
+    g_dbus_object_skeleton_add_interface(
+        G_DBUS_OBJECT_SKELETON(clipboard),
+        G_DBUS_INTERFACE_SKELETON(dpy->clipboard));
+    g_dbus_object_manager_server_export(dpy->server, clipboard);
+    dpy->clipboard_peer.name = "dbus";
+    dpy->clipboard_peer.notifier.notify = dbus_clipboard_notify;
+    dpy->clipboard_peer.request = dbus_clipboard_qemu_request;
+    qemu_clipboard_peer_register(&dpy->clipboard_peer);
+}
diff --git a/ui/dbus.c b/ui/dbus.c
index 7cfbf1a80c..ac34fab3cb 100644
--- a/ui/dbus.c
+++ b/ui/dbus.c
@@ -24,6 +24,7 @@
 #include "qemu/osdep.h"
 #include "qemu/cutils.h"
 #include "qemu/dbus.h"
+#include "qemu/main-loop.h"
 #include "qemu/option.h"
 #include "qom/object_interfaces.h"
 #include "sysemu/sysemu.h"
@@ -70,6 +71,8 @@ dbus_display_init(Object *o)
     g_dbus_object_skeleton_add_interface(
         vm, G_DBUS_INTERFACE_SKELETON(dd->iface));
     g_dbus_object_manager_server_export(dd->server, vm);
+
+    dbus_clipboard_init(dd);
 }
 
 static void
@@ -77,6 +80,9 @@ dbus_display_finalize(Object *o)
 {
     DBusDisplay *dd = DBUS_DISPLAY(o);
 
+    qemu_clipboard_peer_unregister(&dd->clipboard_peer);
+    g_clear_object(&dd->clipboard);
+
     g_clear_object(&dd->server);
     g_clear_pointer(&dd->consoles, g_ptr_array_unref);
     if (dd->add_client_cancellable) {
@@ -289,6 +295,7 @@ set_audiodev(Object *o, const char *str, Error **errp)
     dd->audiodev = g_strdup(str);
 }
 
+
 static int
 get_gl_mode(Object *o, Error **errp)
 {
diff --git a/ui/dbus-display1.xml b/ui/dbus-display1.xml
index aff645220c..767562ad1e 100644
--- a/ui/dbus-display1.xml
+++ b/ui/dbus-display1.xml
@@ -376,6 +376,103 @@
     </method>
   </interface>
 
+  <!--
+      org.qemu.Display1.Clipboard:
+
+      This interface must be implemented by both the client and the server on
+      ``/org/qemu/Display1/Clipboard`` to support clipboard sharing between
+      the client and the guest.
+
+      Once :dbus:meth:`Register`'ed, method calls may be sent and received in both
+      directions. Unregistered callers will get error replies.
+
+      .. _dbus-clipboard-selection:
+
+      **Selection values**::
+
+        Clipboard   = 0
+        Primary     = 1
+        Secondary   = 2
+
+      .. _dbus-clipboard-serial:
+
+      **Serial counter**
+
+      To solve potential clipboard races, clipboard grabs have an associated
+      serial counter. It is set to 0 on registration, and incremented by 1 for
+      each grab. The peer with the highest serial is the clipboard grab owner.
+
+      When a grab with a lower serial is received, it should be discarded.
+
+      When a grab is attempted with the same serial number as the current grab,
+      the one coming from the client should have higher priority, and the client
+      should gain clipboard grab ownership.
+  -->
+  <interface name="org.qemu.Display1.Clipboard">
+    <!--
+        Register:
+
+        Register a clipboard session and reinitialize the serial counter.
+
+        The client must register itself, and is granted an exclusive
+        access for handling the clipboard.
+
+        The server can reinitialize the session as well (to reset the counter).
+    -->
+    <method name="Register"/>
+
+    <!--
+        Unregister:
+
+        Unregister the clipboard session.
+    -->
+    <method name="Unregister"/>
+    <!--
+        Grab:
+        @selection: a :ref:`selection value<dbus-clipboard-selection>`.
+        @serial: the current grab :ref:`serial<dbus-clipboard-serial>`.
+        @mimes: the list of available content MIME types.
+
+        Grab the clipboard, claiming current clipboard content.
+    -->
+    <method name="Grab">
+      <arg type="u" name="selection"/>
+      <arg type="u" name="serial"/>
+      <arg type="as" name="mimes"/>
+    </method>
+
+    <!--
+        Release:
+        @selection: a :ref:`selection value<dbus-clipboard-selection>`.
+
+        Release the clipboard (does nothing if not the current owner).
+    -->
+    <method name="Release">
+      <arg type="u" name="selection"/>
+    </method>
+
+    <!--
+        Request:
+        @selection: a :ref:`selection value<dbus-clipboard-selection>`
+        @mimes: requested MIME types (by order of preference).
+        @reply_mime: the returned data MIME type.
+        @data: the clipboard data.
+
+        Request the clipboard content.
+
+        Return an error if the clipboard is empty, or the requested MIME types
+        are unavailable.
+    -->
+    <method name="Request">
+      <arg type="u" name="selection"/>
+      <arg type="as" name="mimes"/>
+      <arg type="s" name="reply_mime" direction="out"/>
+      <arg type="ay" name="data" direction="out">
+        <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
+      </arg>
+    </method>
+  </interface>
+
   <!--
       org.qemu.Display1.Audio:
 
diff --git a/ui/meson.build b/ui/meson.build
index 80f21704ad..8982ab63c4 100644
--- a/ui/meson.build
+++ b/ui/meson.build
@@ -82,6 +82,7 @@ if dbus_display
                                           '--generate-c-code', '@BASENAME@'])
   dbus_ss.add(when: [gio, pixman, opengl, 'CONFIG_GIO'],
               if_true: [files(
+                'dbus-clipboard.c',
                 'dbus-console.c',
                 'dbus-error.c',
                 'dbus-listener.c',
diff --git a/ui/trace-events b/ui/trace-events
index b1ae30159a..f78b5e6606 100644
--- a/ui/trace-events
+++ b/ui/trace-events
@@ -147,3 +147,6 @@ dbus_mouse_release(unsigned int button) "button %u"
 dbus_mouse_set_pos(unsigned int x, unsigned int y) "x=%u, y=%u"
 dbus_mouse_rel_motion(int dx, int dy) "dx=%d, dy=%d"
 dbus_update(int x, int y, int w, int h) "x=%d, y=%d, w=%d, h=%d"
+dbus_clipboard_grab_failed(void) ""
+dbus_clipboard_register(const char *bus_name) "peer %s"
+dbus_clipboard_unregister(const char *bus_name) "peer %s"
-- 
2.33.0.721.g106298f7f9



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v2 32/37] chardev: teach socket to accept no addresses
  2021-10-09 21:08 [PATCH v2 00/37] Add D-Bus display backend marcandre.lureau
                   ` (30 preceding siblings ...)
  2021-10-09 21:08 ` [PATCH v2 31/37] ui/dbus: add clipboard interface marcandre.lureau
@ 2021-10-09 21:08 ` marcandre.lureau
  2021-10-09 21:08 ` [PATCH v2 33/37] chardev: make socket derivable marcandre.lureau
                   ` (5 subsequent siblings)
  37 siblings, 0 replies; 54+ messages in thread
From: marcandre.lureau @ 2021-10-09 21:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, kraxel

From: Marc-André Lureau <marcandre.lureau@redhat.com>

The following patches are going to use CharSocket as a base class for
sockets that are created with a given fd (without a given address).

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 chardev/char-socket.c | 14 +++++++++-----
 1 file changed, 9 insertions(+), 5 deletions(-)

diff --git a/chardev/char-socket.c b/chardev/char-socket.c
index 836cfa0bc2..a2b02e021d 100644
--- a/chardev/char-socket.c
+++ b/chardev/char-socket.c
@@ -1248,6 +1248,10 @@ static int qmp_chardev_open_socket_server(Chardev *chr,
     qio_net_listener_set_name(s->listener, name);
     g_free(name);
 
+    if (s->addr->type == SOCKET_ADDRESS_TYPE_FD && !*s->addr->u.fd.str) {
+        goto skip_listen;
+    }
+
     if (qio_net_listener_open_sync(s->listener, s->addr, 1, errp) < 0) {
         object_unref(OBJECT(s->listener));
         s->listener = NULL;
@@ -1256,6 +1260,8 @@ static int qmp_chardev_open_socket_server(Chardev *chr,
 
     qapi_free_SocketAddress(s->addr);
     s->addr = socket_local_address(s->listener->sioc[0]->fd, errp);
+
+skip_listen:
     update_disconnected_filename(s);
 
     if (is_waitconnect) {
@@ -1466,9 +1472,9 @@ static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend,
     SocketAddressLegacy *addr;
     ChardevSocket *sock;
 
-    if ((!!path + !!fd + !!host) != 1) {
+    if ((!!path + !!fd + !!host) > 1) {
         error_setg(errp,
-                   "Exactly one of 'path', 'fd' or 'host' required");
+                   "None or one of 'path', 'fd' or 'host' option required.");
         return;
     }
 
@@ -1542,12 +1548,10 @@ static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend,
             .has_ipv6 = qemu_opt_get(opts, "ipv6"),
             .ipv6 = qemu_opt_get_bool(opts, "ipv6", 0),
         };
-    } else if (fd) {
+    } else {
         addr->type = SOCKET_ADDRESS_TYPE_FD;
         addr->u.fd.data = g_new(String, 1);
         addr->u.fd.data->str = g_strdup(fd);
-    } else {
-        g_assert_not_reached();
     }
     sock->addr = addr;
 }
-- 
2.33.0.721.g106298f7f9



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v2 33/37] chardev: make socket derivable
  2021-10-09 21:08 [PATCH v2 00/37] Add D-Bus display backend marcandre.lureau
                   ` (31 preceding siblings ...)
  2021-10-09 21:08 ` [PATCH v2 32/37] chardev: teach socket to accept no addresses marcandre.lureau
@ 2021-10-09 21:08 ` marcandre.lureau
  2021-12-17 13:32   ` Philippe Mathieu-Daudé
  2021-10-09 21:08 ` [PATCH v2 34/37] option: add g_auto for QemuOpts marcandre.lureau
                   ` (4 subsequent siblings)
  37 siblings, 1 reply; 54+ messages in thread
From: marcandre.lureau @ 2021-10-09 21:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, kraxel

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/chardev/char-socket.h | 84 +++++++++++++++++++++++++++++++++++
 chardev/char-socket.c         | 58 +-----------------------
 2 files changed, 85 insertions(+), 57 deletions(-)
 create mode 100644 include/chardev/char-socket.h

diff --git a/include/chardev/char-socket.h b/include/chardev/char-socket.h
new file mode 100644
index 0000000000..1a9274f2e3
--- /dev/null
+++ b/include/chardev/char-socket.h
@@ -0,0 +1,84 @@
+/*
+ * QEMU System Emulator
+ *
+ * Copyright (c) 2003-2008 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#ifndef CHAR_SOCKET_H_
+#define CHAR_SOCKET_H_
+
+#include "io/channel-socket.h"
+#include "io/channel-tls.h"
+#include "io/net-listener.h"
+#include "chardev/char.h"
+#include "qom/object.h"
+
+#define TCP_MAX_FDS 16
+
+typedef struct {
+    char buf[21];
+    size_t buflen;
+} TCPChardevTelnetInit;
+
+typedef enum {
+    TCP_CHARDEV_STATE_DISCONNECTED,
+    TCP_CHARDEV_STATE_CONNECTING,
+    TCP_CHARDEV_STATE_CONNECTED,
+} TCPChardevState;
+
+struct SocketChardev {
+    Chardev parent;
+    QIOChannel *ioc; /* Client I/O channel */
+    QIOChannelSocket *sioc; /* Client master channel */
+    QIONetListener *listener;
+    GSource *hup_source;
+    QCryptoTLSCreds *tls_creds;
+    char *tls_authz;
+    TCPChardevState state;
+    int max_size;
+    int do_telnetopt;
+    int do_nodelay;
+    int *read_msgfds;
+    size_t read_msgfds_num;
+    int *write_msgfds;
+    size_t write_msgfds_num;
+    bool registered_yank;
+
+    SocketAddress *addr;
+    bool is_listen;
+    bool is_telnet;
+    bool is_tn3270;
+    GSource *telnet_source;
+    TCPChardevTelnetInit *telnet_init;
+
+    bool is_websock;
+
+    GSource *reconnect_timer;
+    int64_t reconnect_time;
+    bool connect_err_reported;
+
+    QIOTask *connect_task;
+};
+typedef struct SocketChardev SocketChardev;
+
+DECLARE_INSTANCE_CHECKER(SocketChardev, SOCKET_CHARDEV,
+                         TYPE_CHARDEV_SOCKET)
+
+#endif /* CHAR_SOCKET_H_ */
diff --git a/chardev/char-socket.c b/chardev/char-socket.c
index a2b02e021d..d619088232 100644
--- a/chardev/char-socket.c
+++ b/chardev/char-socket.c
@@ -25,9 +25,7 @@
 #include "qemu/osdep.h"
 #include "chardev/char.h"
 #include "io/channel-socket.h"
-#include "io/channel-tls.h"
 #include "io/channel-websock.h"
-#include "io/net-listener.h"
 #include "qemu/error-report.h"
 #include "qemu/module.h"
 #include "qemu/option.h"
@@ -37,61 +35,7 @@
 #include "qemu/yank.h"
 
 #include "chardev/char-io.h"
-#include "qom/object.h"
-
-/***********************************************************/
-/* TCP Net console */
-
-#define TCP_MAX_FDS 16
-
-typedef struct {
-    char buf[21];
-    size_t buflen;
-} TCPChardevTelnetInit;
-
-typedef enum {
-    TCP_CHARDEV_STATE_DISCONNECTED,
-    TCP_CHARDEV_STATE_CONNECTING,
-    TCP_CHARDEV_STATE_CONNECTED,
-} TCPChardevState;
-
-struct SocketChardev {
-    Chardev parent;
-    QIOChannel *ioc; /* Client I/O channel */
-    QIOChannelSocket *sioc; /* Client master channel */
-    QIONetListener *listener;
-    GSource *hup_source;
-    QCryptoTLSCreds *tls_creds;
-    char *tls_authz;
-    TCPChardevState state;
-    int max_size;
-    int do_telnetopt;
-    int do_nodelay;
-    int *read_msgfds;
-    size_t read_msgfds_num;
-    int *write_msgfds;
-    size_t write_msgfds_num;
-    bool registered_yank;
-
-    SocketAddress *addr;
-    bool is_listen;
-    bool is_telnet;
-    bool is_tn3270;
-    GSource *telnet_source;
-    TCPChardevTelnetInit *telnet_init;
-
-    bool is_websock;
-
-    GSource *reconnect_timer;
-    int64_t reconnect_time;
-    bool connect_err_reported;
-
-    QIOTask *connect_task;
-};
-typedef struct SocketChardev SocketChardev;
-
-DECLARE_INSTANCE_CHECKER(SocketChardev, SOCKET_CHARDEV,
-                         TYPE_CHARDEV_SOCKET)
+#include "chardev/char-socket.h"
 
 static gboolean socket_reconnect_timeout(gpointer opaque);
 static void tcp_chr_telnet_init(Chardev *chr);
-- 
2.33.0.721.g106298f7f9



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v2 34/37] option: add g_auto for QemuOpts
  2021-10-09 21:08 [PATCH v2 00/37] Add D-Bus display backend marcandre.lureau
                   ` (32 preceding siblings ...)
  2021-10-09 21:08 ` [PATCH v2 33/37] chardev: make socket derivable marcandre.lureau
@ 2021-10-09 21:08 ` marcandre.lureau
  2021-10-09 21:08 ` [PATCH v2 35/37] ui/dbus: add chardev backend & interface marcandre.lureau
                   ` (3 subsequent siblings)
  37 siblings, 0 replies; 54+ messages in thread
From: marcandre.lureau @ 2021-10-09 21:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, kraxel

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Used in the next commit.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/qemu/option.h | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/include/qemu/option.h b/include/qemu/option.h
index 306bf07575..bbd86e1c4e 100644
--- a/include/qemu/option.h
+++ b/include/qemu/option.h
@@ -150,4 +150,6 @@ QDict *keyval_parse(const char *params, const char *implied_key,
                     bool *help, Error **errp);
 void keyval_merge(QDict *old, const QDict *new, Error **errp);
 
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(QemuOpts, qemu_opts_del)
+
 #endif
-- 
2.33.0.721.g106298f7f9



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v2 35/37] ui/dbus: add chardev backend & interface
  2021-10-09 21:08 [PATCH v2 00/37] Add D-Bus display backend marcandre.lureau
                   ` (33 preceding siblings ...)
  2021-10-09 21:08 ` [PATCH v2 34/37] option: add g_auto for QemuOpts marcandre.lureau
@ 2021-10-09 21:08 ` marcandre.lureau
  2021-10-09 21:08 ` [PATCH v2 36/37] ui/dbus: register D-Bus VC handler marcandre.lureau
                   ` (2 subsequent siblings)
  37 siblings, 0 replies; 54+ messages in thread
From: marcandre.lureau @ 2021-10-09 21:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, kraxel

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Add a new chardev backend which allows D-Bus client to handle the
chardev stream & events.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 qapi/char.json                |  27 ++++
 include/chardev/char-socket.h |   2 +
 include/qemu/dbus.h           |   5 +
 ui/dbus.h                     |  44 +++++
 ui/dbus-chardev.c             | 296 ++++++++++++++++++++++++++++++++++
 ui/dbus.c                     |  26 +++
 ui/dbus-display1.xml          |  75 +++++++++
 ui/meson.build                |   1 +
 8 files changed, 476 insertions(+)
 create mode 100644 ui/dbus-chardev.c

diff --git a/qapi/char.json b/qapi/char.json
index f5133a5eeb..6ed424d07c 100644
--- a/qapi/char.json
+++ b/qapi/char.json
@@ -358,6 +358,20 @@
   'base': 'ChardevCommon',
   'if': 'CONFIG_SPICE' }
 
+##
+# @ChardevDBus:
+#
+# Configuration info for DBus chardevs.
+#
+# @name: name of the channel (following docs/spice-port-fqdn.txt)
+#
+# Since: 6.2
+##
+{ 'struct': 'ChardevDBus',
+  'data': { 'name': 'str' },
+  'base': 'ChardevCommon',
+  'if': 'CONFIG_DBUS_DISPLAY' }
+
 ##
 # @ChardevVC:
 #
@@ -422,6 +436,7 @@
 # @spicevmc: Since 1.5
 # @spiceport: Since 1.5
 # @qemu-vdagent: Since 6.1
+# @dbus: Since 6.2
 # @vc: v1.5
 # @ringbuf: Since 1.6
 # @memory: Since 1.5
@@ -447,6 +462,7 @@
             { 'name': 'spicevmc', 'if': 'CONFIG_SPICE' },
             { 'name': 'spiceport', 'if': 'CONFIG_SPICE' },
             { 'name': 'qemu-vdagent', 'if': 'CONFIG_SPICE_PROTOCOL' },
+            { 'name': 'dbus', 'if': 'CONFIG_DBUS_DISPLAY' },
             'vc',
             'ringbuf',
             # next one is just for compatibility
@@ -535,6 +551,15 @@
   'data': { 'data': 'ChardevQemuVDAgent' },
   'if': 'CONFIG_SPICE_PROTOCOL' }
 
+##
+# @ChardevDBusWrapper:
+#
+# Since: 6.2
+##
+{ 'struct': 'ChardevDBusWrapper',
+  'data': { 'data': 'ChardevDBus' },
+  'if': 'CONFIG_DBUS_DISPLAY' }
+
 ##
 # @ChardevVCWrapper:
 #
@@ -582,6 +607,8 @@
                            'if': 'CONFIG_SPICE' },
             'qemu-vdagent': { 'type': 'ChardevQemuVDAgentWrapper',
                               'if': 'CONFIG_SPICE_PROTOCOL' },
+            'dbus': { 'type': 'ChardevDBusWrapper',
+                      'if': 'CONFIG_DBUS_DISPLAY' },
             'vc': 'ChardevVCWrapper',
             'ringbuf': 'ChardevRingbufWrapper',
             # next one is just for compatibility
diff --git a/include/chardev/char-socket.h b/include/chardev/char-socket.h
index 1a9274f2e3..6b6e2ceba1 100644
--- a/include/chardev/char-socket.h
+++ b/include/chardev/char-socket.h
@@ -43,6 +43,8 @@ typedef enum {
     TCP_CHARDEV_STATE_CONNECTED,
 } TCPChardevState;
 
+typedef ChardevClass SocketChardevClass;
+
 struct SocketChardev {
     Chardev parent;
     QIOChannel *ioc; /* Client I/O channel */
diff --git a/include/qemu/dbus.h b/include/qemu/dbus.h
index c0cbb1ca44..08f00dfd53 100644
--- a/include/qemu/dbus.h
+++ b/include/qemu/dbus.h
@@ -12,6 +12,11 @@
 
 #include <gio/gio.h>
 
+#include "qom/object.h"
+#include "chardev/char.h"
+#include "qemu/notify.h"
+#include "qemu/typedefs.h"
+
 /* glib/gio 2.68 */
 #define DBUS_METHOD_INVOCATION_HANDLED TRUE
 #define DBUS_METHOD_INVOCATION_UNHANDLED FALSE
diff --git a/ui/dbus.h b/ui/dbus.h
index 3e89eafcab..64c77cab44 100644
--- a/ui/dbus.h
+++ b/ui/dbus.h
@@ -24,6 +24,7 @@
 #ifndef UI_DBUS_H_
 #define UI_DBUS_H_
 
+#include "chardev/char-socket.h"
 #include "qemu/dbus.h"
 #include "qom/object.h"
 #include "ui/console.h"
@@ -56,11 +57,15 @@ struct DBusDisplay {
     QemuDBusDisplay1Clipboard *clipboard;
     QemuDBusDisplay1Clipboard *clipboard_proxy;
     DBusClipboardRequest clipboard_request[QEMU_CLIPBOARD_SELECTION__COUNT];
+
+    Notifier notifier;
 };
 
 #define TYPE_DBUS_DISPLAY "dbus-display"
 OBJECT_DECLARE_SIMPLE_TYPE(DBusDisplay, DBUS_DISPLAY)
 
+void dbus_display_notifier_add(Notifier *notifier);
+
 #define DBUS_DISPLAY_TYPE_CONSOLE dbus_display_console_get_type()
 G_DECLARE_FINAL_TYPE(DBusDisplayConsole,
                      dbus_display_console,
@@ -95,6 +100,45 @@ dbus_display_listener_get_bus_name(DBusDisplayListener *ddl);
 extern const DisplayChangeListenerOps dbus_gl_dcl_ops;
 extern const DisplayChangeListenerOps dbus_dcl_ops;
 
+#define TYPE_CHARDEV_DBUS "chardev-dbus"
+
+typedef struct DBusChardevClass {
+    SocketChardevClass parent_class;
+
+    void (*parent_chr_be_event)(Chardev *s, QEMUChrEvent event);
+} DBusChardevClass;
+
+DECLARE_CLASS_CHECKERS(DBusChardevClass, DBUS_CHARDEV,
+                       TYPE_CHARDEV_DBUS)
+
+typedef struct DBusChardev {
+    SocketChardev parent;
+
+    bool exported;
+    QemuDBusDisplay1Chardev *iface;
+} DBusChardev;
+
+DECLARE_INSTANCE_CHECKER(DBusChardev, DBUS_CHARDEV, TYPE_CHARDEV_DBUS)
+
+#define CHARDEV_IS_DBUS(chr) \
+    object_dynamic_cast(OBJECT(chr), TYPE_CHARDEV_DBUS)
+
+typedef enum {
+    DBUS_DISPLAY_CHARDEV_OPEN,
+    DBUS_DISPLAY_CHARDEV_CLOSE,
+} DBusDisplayEventType;
+
+typedef struct DBusDisplayEvent {
+    DBusDisplayEventType type;
+    union {
+        DBusChardev *chardev;
+    };
+} DBusDisplayEvent;
+
+void dbus_display_notify(DBusDisplayEvent *event);
+
+void dbus_chardev_init(DBusDisplay *dpy);
+
 void dbus_clipboard_init(DBusDisplay *dpy);
 
 #endif /* UI_DBUS_H_ */
diff --git a/ui/dbus-chardev.c b/ui/dbus-chardev.c
new file mode 100644
index 0000000000..940ef937cd
--- /dev/null
+++ b/ui/dbus-chardev.c
@@ -0,0 +1,296 @@
+/*
+ * QEMU DBus display
+ *
+ * Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include "qemu/osdep.h"
+#include "trace.h"
+#include "qapi/error.h"
+#include "qemu/config-file.h"
+#include "qemu/option.h"
+
+#include <gio/gunixfdlist.h>
+
+#include "dbus.h"
+
+static char *
+dbus_display_chardev_path(DBusChardev *chr)
+{
+    return g_strdup_printf(DBUS_DISPLAY1_ROOT "/Chardev_%s",
+                           CHARDEV(chr)->label);
+}
+
+static void
+dbus_display_chardev_export(DBusDisplay *dpy, DBusChardev *chr)
+{
+    g_autoptr(GDBusObjectSkeleton) sk = NULL;
+    g_autofree char *path = dbus_display_chardev_path(chr);
+
+    if (chr->exported) {
+        return;
+    }
+
+    sk = g_dbus_object_skeleton_new(path);
+    g_dbus_object_skeleton_add_interface(
+        sk, G_DBUS_INTERFACE_SKELETON(chr->iface));
+    g_dbus_object_manager_server_export(dpy->server, sk);
+    chr->exported = true;
+}
+
+static void
+dbus_display_chardev_unexport(DBusDisplay *dpy, DBusChardev *chr)
+{
+    g_autofree char *path = dbus_display_chardev_path(chr);
+
+    if (!chr->exported) {
+        return;
+    }
+
+    g_dbus_object_manager_server_unexport(dpy->server, path);
+    chr->exported = false;
+}
+
+static int
+dbus_display_chardev_foreach(Object *obj, void *data)
+{
+    DBusDisplay *dpy = DBUS_DISPLAY(data);
+
+    if (!CHARDEV_IS_DBUS(obj)) {
+        return 0;
+    }
+
+    dbus_display_chardev_export(dpy, DBUS_CHARDEV(obj));
+
+    return 0;
+}
+
+static void
+dbus_display_on_notify(Notifier *notifier, void *data)
+{
+    DBusDisplay *dpy = container_of(notifier, DBusDisplay, notifier);
+    DBusDisplayEvent *event = data;
+
+    switch (event->type) {
+    case DBUS_DISPLAY_CHARDEV_OPEN:
+        dbus_display_chardev_export(dpy, event->chardev);
+        break;
+    case DBUS_DISPLAY_CHARDEV_CLOSE:
+        dbus_display_chardev_unexport(dpy, event->chardev);
+        break;
+    }
+}
+
+void
+dbus_chardev_init(DBusDisplay *dpy)
+{
+    dpy->notifier.notify = dbus_display_on_notify;
+    dbus_display_notifier_add(&dpy->notifier);
+
+    object_child_foreach(container_get(object_get_root(), "/chardevs"),
+                         dbus_display_chardev_foreach, dpy);
+}
+
+static gboolean
+dbus_chr_register(
+    DBusChardev *dc,
+    GDBusMethodInvocation *invocation,
+    GUnixFDList *fd_list,
+    GVariant *arg_stream,
+    QemuDBusDisplay1Chardev *object)
+{
+    g_autoptr(GError) err = NULL;
+    int fd;
+
+    fd = g_unix_fd_list_get(fd_list, g_variant_get_handle(arg_stream), &err);
+    if (err) {
+        g_dbus_method_invocation_return_error(
+            invocation,
+            DBUS_DISPLAY_ERROR,
+            DBUS_DISPLAY_ERROR_FAILED,
+            "Couldn't get peer FD: %s", err->message);
+        return DBUS_METHOD_INVOCATION_HANDLED;
+    }
+
+    if (qemu_chr_add_client(CHARDEV(dc), fd) < 0) {
+        g_dbus_method_invocation_return_error(invocation,
+                                              DBUS_DISPLAY_ERROR,
+                                              DBUS_DISPLAY_ERROR_FAILED,
+                                              "Couldn't register FD!");
+        close(fd);
+        return DBUS_METHOD_INVOCATION_HANDLED;
+    }
+
+    g_object_set(dc->iface,
+                 "owner", g_dbus_method_invocation_get_sender(invocation),
+                 NULL);
+
+    qemu_dbus_display1_chardev_complete_register(object, invocation, NULL);
+    return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static gboolean
+dbus_chr_send_break(
+    DBusChardev *dc,
+    GDBusMethodInvocation *invocation,
+    QemuDBusDisplay1Chardev *object)
+{
+    qemu_chr_be_event(CHARDEV(dc), CHR_EVENT_BREAK);
+
+    qemu_dbus_display1_chardev_complete_send_break(object, invocation);
+    return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static void
+dbus_chr_open(Chardev *chr, ChardevBackend *backend,
+              bool *be_opened, Error **errp)
+{
+    ERRP_GUARD();
+
+    DBusChardev *dc = DBUS_CHARDEV(chr);
+    DBusDisplayEvent event = {
+        .type = DBUS_DISPLAY_CHARDEV_OPEN,
+        .chardev = dc,
+    };
+    g_autoptr(ChardevBackend) be = NULL;
+    g_autoptr(QemuOpts) opts = NULL;
+
+    dc->iface = qemu_dbus_display1_chardev_skeleton_new();
+    g_object_set(dc->iface, "name", backend->u.dbus.data->name, NULL);
+    g_object_connect(dc->iface,
+                     "swapped-signal::handle-register",
+                     dbus_chr_register, dc,
+                     "swapped-signal::handle-send-break",
+                     dbus_chr_send_break, dc,
+                     NULL);
+
+    dbus_display_notify(&event);
+
+    be = g_new0(ChardevBackend, 1);
+    opts = qemu_opts_create(qemu_find_opts("chardev"), NULL, 0, &error_abort);
+    qemu_opt_set(opts, "server", "on", &error_abort);
+    qemu_opt_set(opts, "wait", "off", &error_abort);
+    CHARDEV_CLASS(object_class_by_name(TYPE_CHARDEV_SOCKET))->parse(
+        opts, be, errp);
+    if (*errp) {
+        return;
+    }
+    CHARDEV_CLASS(object_class_by_name(TYPE_CHARDEV_SOCKET))->open(
+        chr, be, be_opened, errp);
+}
+
+static void
+dbus_chr_set_fe_open(Chardev *chr, int fe_open)
+{
+    DBusChardev *dc = DBUS_CHARDEV(chr);
+
+    g_object_set(dc->iface, "feopened", fe_open, NULL);
+}
+
+static void
+dbus_chr_set_echo(Chardev *chr, bool echo)
+{
+    DBusChardev *dc = DBUS_CHARDEV(chr);
+
+    g_object_set(dc->iface, "echo", echo, NULL);
+}
+
+static void
+dbus_chr_be_event(Chardev *chr, QEMUChrEvent event)
+{
+    DBusChardev *dc = DBUS_CHARDEV(chr);
+    DBusChardevClass *klass = DBUS_CHARDEV_GET_CLASS(chr);
+
+    switch (event) {
+    case CHR_EVENT_CLOSED:
+        if (dc->iface) {
+            /* on finalize, iface is set to NULL */
+            g_object_set(dc->iface, "owner", "", NULL);
+        }
+        break;
+    default:
+        break;
+    };
+
+    klass->parent_chr_be_event(chr, event);
+}
+
+static void
+dbus_chr_parse(QemuOpts *opts, ChardevBackend *backend,
+               Error **errp)
+{
+    const char *name = qemu_opt_get(opts, "name");
+    ChardevDBus *dbus;
+
+    if (name == NULL) {
+        error_setg(errp, "chardev: dbus: no name given");
+        return;
+    }
+
+    backend->type = CHARDEV_BACKEND_KIND_DBUS;
+    dbus = backend->u.dbus.data = g_new0(ChardevDBus, 1);
+    qemu_chr_parse_common(opts, qapi_ChardevDBus_base(dbus));
+    dbus->name = g_strdup(name);
+}
+
+static void
+char_dbus_class_init(ObjectClass *oc, void *data)
+{
+    DBusChardevClass *klass = DBUS_CHARDEV_CLASS(oc);
+    ChardevClass *cc = CHARDEV_CLASS(oc);
+
+    cc->parse = dbus_chr_parse;
+    cc->open = dbus_chr_open;
+    cc->chr_set_fe_open = dbus_chr_set_fe_open;
+    cc->chr_set_echo = dbus_chr_set_echo;
+    klass->parent_chr_be_event = cc->chr_be_event;
+    cc->chr_be_event = dbus_chr_be_event;
+}
+
+static void
+char_dbus_finalize(Object *obj)
+{
+    DBusChardev *dc = DBUS_CHARDEV(obj);
+    DBusDisplayEvent event = {
+        .type = DBUS_DISPLAY_CHARDEV_CLOSE,
+        .chardev = dc,
+    };
+
+    dbus_display_notify(&event);
+    g_clear_object(&dc->iface);
+}
+
+static const TypeInfo char_dbus_type_info = {
+    .name = TYPE_CHARDEV_DBUS,
+    .parent = TYPE_CHARDEV_SOCKET,
+    .class_size = sizeof(DBusChardevClass),
+    .instance_size = sizeof(DBusChardev),
+    .instance_finalize = char_dbus_finalize,
+    .class_init = char_dbus_class_init,
+};
+module_obj(TYPE_CHARDEV_DBUS);
+
+static void
+register_types(void)
+{
+    type_register_static(&char_dbus_type_info);
+}
+
+type_init(register_types);
diff --git a/ui/dbus.c b/ui/dbus.c
index ac34fab3cb..38d6ccc607 100644
--- a/ui/dbus.c
+++ b/ui/dbus.c
@@ -55,6 +55,27 @@ static const DisplayGLCtxOps dbus_gl_ops = {
     .dpy_gl_ctx_make_current = qemu_egl_make_context_current,
 };
 
+static NotifierList dbus_display_notifiers =
+    NOTIFIER_LIST_INITIALIZER(dbus_display_notifiers);
+
+void
+dbus_display_notifier_add(Notifier *notifier)
+{
+    notifier_list_add(&dbus_display_notifiers, notifier);
+}
+
+static void
+dbus_display_notifier_remove(Notifier *notifier)
+{
+    notifier_remove(notifier);
+}
+
+void
+dbus_display_notify(DBusDisplayEvent *event)
+{
+    notifier_list_notify(&dbus_display_notifiers, event);
+}
+
 static void
 dbus_display_init(Object *o)
 {
@@ -73,6 +94,7 @@ dbus_display_init(Object *o)
     g_dbus_object_manager_server_export(dd->server, vm);
 
     dbus_clipboard_init(dd);
+    dbus_chardev_init(dd);
 }
 
 static void
@@ -80,6 +102,10 @@ dbus_display_finalize(Object *o)
 {
     DBusDisplay *dd = DBUS_DISPLAY(o);
 
+    if (dd->notifier.notify) {
+        dbus_display_notifier_remove(&dd->notifier);
+    }
+
     qemu_clipboard_peer_unregister(&dd->clipboard_peer);
     g_clear_object(&dd->clipboard);
 
diff --git a/ui/dbus-display1.xml b/ui/dbus-display1.xml
index 767562ad1e..cc45078ec8 100644
--- a/ui/dbus-display1.xml
+++ b/ui/dbus-display1.xml
@@ -683,4 +683,79 @@
       </arg>
     </method>
   </interface>
+
+  <!--
+      org.qemu.Display1.Chardev:
+
+      Character devices may be available on ``/org/qemu/Display1/Chardev_$id``.
+
+      They may be used for different kind of streams, which are identified via
+      their FQDN :dbus:prop:`Name`.
+
+      .. _dbus-chardev-fqdn:
+
+      Here are some known reserved kind names (the `org.qemu` prefix is reserved
+      by QEMU):
+
+      org.qemu.console.serial.0
+        A serial console stream.
+
+      org.qemu.monitor.hmp.0
+        A QEMU HMP human monitor.
+
+      org.qemu.monitor.qmp.0
+        A QEMU QMP monitor.
+
+      org.qemu.usbredir
+        A usbredir stream.
+  -->
+  <interface name="org.qemu.Display1.Chardev">
+    <!--
+        Register:
+        @stream: a Unix FD to redirect the stream to.
+
+        Register a file-descriptor for the stream handling.
+
+        The current handler, if any, will be replaced.
+    -->
+    <method name="Register">
+      <arg type="h" name="stream" direction="in"/>
+    </method>
+
+    <!--
+        SendBreak:
+
+        Send a break event to the character device.
+    -->
+    <method name="SendBreak"/>
+
+    <!--
+        Name:
+
+        The FQDN name to identify the kind of stream. See :ref:`reserved
+        names<dbus-chardev-fqdn>`.
+    -->
+    <property name="Name" type="s" access="read"/>
+
+    <!--
+        FEOpened:
+
+        Whether the front-end side is opened.
+    -->
+    <property name="FEOpened" type="b" access="read"/>
+
+    <!--
+        Echo:
+
+        Whether the input should be echo'ed (for serial streams).
+    -->
+    <property name="Echo" type="b" access="read"/>
+
+    <!--
+        Owner:
+
+        The D-Bus unique name of the registered handler.
+    -->
+    <property name="Owner" type="s" access="read"/>
+  </interface>
 </node>
diff --git a/ui/meson.build b/ui/meson.build
index 8982ab63c4..64286ba150 100644
--- a/ui/meson.build
+++ b/ui/meson.build
@@ -82,6 +82,7 @@ if dbus_display
                                           '--generate-c-code', '@BASENAME@'])
   dbus_ss.add(when: [gio, pixman, opengl, 'CONFIG_GIO'],
               if_true: [files(
+                'dbus-chardev.c',
                 'dbus-clipboard.c',
                 'dbus-console.c',
                 'dbus-error.c',
-- 
2.33.0.721.g106298f7f9



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v2 36/37] ui/dbus: register D-Bus VC handler
  2021-10-09 21:08 [PATCH v2 00/37] Add D-Bus display backend marcandre.lureau
                   ` (34 preceding siblings ...)
  2021-10-09 21:08 ` [PATCH v2 35/37] ui/dbus: add chardev backend & interface marcandre.lureau
@ 2021-10-09 21:08 ` marcandre.lureau
  2021-12-17 13:35   ` Philippe Mathieu-Daudé
  2021-10-09 21:08 ` [PATCH v2 37/37] MAINTAINERS: update D-Bus section marcandre.lureau
  2021-10-13  5:22 ` [PATCH v2 00/37] Add D-Bus display backend Gerd Hoffmann
  37 siblings, 1 reply; 54+ messages in thread
From: marcandre.lureau @ 2021-10-09 21:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, kraxel

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Export the default consoles over the D-Bus chardev.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 ui/dbus.c | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 53 insertions(+)

diff --git a/ui/dbus.c b/ui/dbus.c
index 38d6ccc607..97248ceadb 100644
--- a/ui/dbus.c
+++ b/ui/dbus.c
@@ -352,6 +352,57 @@ dbus_display_class_init(ObjectClass *oc, void *data)
                                    get_gl_mode, set_gl_mode);
 }
 
+#define TYPE_CHARDEV_VC "chardev-vc"
+
+typedef struct DBusVCClass {
+    DBusChardevClass parent_class;
+
+    void (*parent_parse)(QemuOpts *opts, ChardevBackend *b, Error **errp);
+} DBusVCClass;
+
+DECLARE_CLASS_CHECKERS(DBusVCClass, DBUS_VC,
+                       TYPE_CHARDEV_VC)
+
+static void
+dbus_vc_parse(QemuOpts *opts, ChardevBackend *backend,
+              Error **errp)
+{
+    DBusVCClass *klass = DBUS_VC_CLASS(object_class_by_name(TYPE_CHARDEV_VC));
+    const char *name = qemu_opt_get(opts, "name");
+    const char *id = qemu_opts_id(opts);
+
+    if (name == NULL) {
+        name = "";
+        if (g_str_has_prefix(id, "compat_monitor")) {
+            name = "org.qemu.monitor.hmp.0";
+        }
+        if (g_str_has_prefix(id, "serial")) {
+            name = "org.qemu.console.serial.0";
+        }
+        if (!qemu_opt_set(opts, "name", name, errp)) {
+            return;
+        }
+    }
+
+    klass->parent_parse(opts, backend, errp);
+}
+
+static void
+dbus_vc_class_init(ObjectClass *oc, void *data)
+{
+    DBusVCClass *klass = DBUS_VC_CLASS(oc);
+    ChardevClass *cc = CHARDEV_CLASS(oc);
+
+    klass->parent_parse = cc->parse;
+    cc->parse = dbus_vc_parse;
+}
+
+static const TypeInfo dbus_vc_type_info = {
+    .name = TYPE_CHARDEV_VC,
+    .parent = TYPE_CHARDEV_DBUS,
+    .class_init = dbus_vc_class_init,
+};
+
 static void
 early_dbus_init(DisplayOptions *opts)
 {
@@ -365,6 +416,8 @@ early_dbus_init(DisplayOptions *opts)
 
         display_opengl = 1;
     }
+
+    type_register(&dbus_vc_type_info);
 }
 
 static void
-- 
2.33.0.721.g106298f7f9



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v2 37/37] MAINTAINERS: update D-Bus section
  2021-10-09 21:08 [PATCH v2 00/37] Add D-Bus display backend marcandre.lureau
                   ` (35 preceding siblings ...)
  2021-10-09 21:08 ` [PATCH v2 36/37] ui/dbus: register D-Bus VC handler marcandre.lureau
@ 2021-10-09 21:08 ` marcandre.lureau
  2021-10-13  5:22 ` [PATCH v2 00/37] Add D-Bus display backend Gerd Hoffmann
  37 siblings, 0 replies; 54+ messages in thread
From: marcandre.lureau @ 2021-10-09 21:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, kraxel

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 MAINTAINERS | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/MAINTAINERS b/MAINTAINERS
index 50435b8d2f..b429afba33 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2839,11 +2839,15 @@ D-Bus
 M: Marc-André Lureau <marcandre.lureau@redhat.com>
 S: Maintained
 F: backends/dbus-vmstate.c
-F: tests/dbus-vmstate*
+F: ui/dbus*
+F: audio/dbus*
 F: util/dbus.c
+F: include/ui/dbus*
 F: include/qemu/dbus.h
-F: docs/interop/dbus.rst
-F: docs/interop/dbus-vmstate.rst
+F: docs/interop/dbus*
+F: docs/sphinx/dbus*
+F: docs/sphinx/fakedbusdoc.py
+F: tests/qtest/dbus*
 
 Seccomp
 M: Eduardo Otubo <otubo@redhat.com>
-- 
2.33.0.721.g106298f7f9



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* Re: [PATCH v2 00/37] Add D-Bus display backend
  2021-10-09 21:08 [PATCH v2 00/37] Add D-Bus display backend marcandre.lureau
                   ` (36 preceding siblings ...)
  2021-10-09 21:08 ` [PATCH v2 37/37] MAINTAINERS: update D-Bus section marcandre.lureau
@ 2021-10-13  5:22 ` Gerd Hoffmann
  2021-12-16 20:53   ` Marc-André Lureau
  37 siblings, 1 reply; 54+ messages in thread
From: Gerd Hoffmann @ 2021-10-13  5:22 UTC (permalink / raw)
  To: marcandre.lureau; +Cc: qemu-devel

On Sun, Oct 10, 2021 at 01:08:01AM +0400, marcandre.lureau@redhat.com wrote:
> From: Marc-André Lureau <marcandre.lureau@redhat.com>
> 
> Hi,
> 
> Both Spice and VNC are relatively complex and inefficient for local-only
> display/console export.
> 
> The goal of this display backend is to export over D-Bus an interface close to
> the QEMU internal APIs. Any -display or -audio backend should be possible to
> implement externally that way. It will allow third-parties to maintain their own
> backends (UI toolkits, servers etc), and eventually reduce the responsability on
> QEMU.
> 
> D-Bus is the protocol of choice for the desktop, it has many convenient bindings
> for various languages and tools. Data blob transfer is more efficient than QMP
> too. Backends can come and go as needed: you can have several display opened
> (say Boxes & virt-manager), while exporting the display over VNC for example
> from a different process. It works best on Unix, but there is some Windows
> support too (even Windows has some AF_UNIX nowadays, and the WSL2 situation may
> change the future of QEMU on Windows anyway).
> 
> Using it only requires "-display dbus" on any reasonable Linux desktop with a
> D-Bus session bus. Then you use can use busctl, d-feet or gdbus, ex:
> $ gdbus introspect --session -r -d org.qemu -o /
> 
> See the different patches and documentation for further options. The p2p=on mode
> should also allow users running bus-less (on MacOS for ex). We can also add TCP
> socket if needed (although more work would be needed in this case to replace
> the FD-passing with some extra TCP listening socket).

Wow.  That series got a lot of fine tuning.  The patches look all good
to me.

Acked-by: Gerd Hoffmann <kraxel@redhat.com>

> A WIP Rust/Gtk4 client and VNC server is: https://gitlab.com/marcandre.lureau/qemu-display/
> (check README.md for details, then `cargo run` should connect to QEMU)

Hmm, that wants rather cutting edge versions, stock Fedora 34 isn't new
enough to build it.  And I don't feel like updating to Fedora 35 beta
for that.  So unfortunately I couldn't easily test it, but I'd love to
see that live in action.

Is it possible to keep the client running while starting and stopping
qemu (comparable to "virt-viewer --wait --reconnect" behaviour)?

take care,
  Gerd



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v2 26/37] ui: add a D-Bus display backend
  2021-10-09 21:08 ` [PATCH v2 26/37] ui: add a D-Bus display backend marcandre.lureau
@ 2021-10-13  8:59   ` Marc-André Lureau
  0 siblings, 0 replies; 54+ messages in thread
From: Marc-André Lureau @ 2021-10-13  8:59 UTC (permalink / raw)
  To: QEMU; +Cc: Gerd Hoffmann

[-- Attachment #1: Type: text/plain, Size: 74087 bytes --]

Hi

On Sun, Oct 10, 2021 at 1:30 AM <marcandre.lureau@redhat.com> wrote:

> From: Marc-André Lureau <marcandre.lureau@redhat.com>
>
> The "dbus" display backend exports the QEMU consoles and other
> UI-related interfaces over D-Bus.
>
> By default, the connection is established on the session bus, but you
> can specify a different bus with the "addr" option.
>
> The backend takes the "org.qemu" service name, while still allowing
> further instances to queue on the same name (so you can lookup all the
> available instances too). It accepts any number of clients at this
> point, although this is expected to evolve with options to restrict
> clients, or only accept p2p via fd passing.
>
> The interface is intentionally very close to the internal QEMU API,
> and can be introspected or interacted with busctl/dfeet etc:
>
> $ ./qemu-system-x86_64 -name MyVM -display dbus
> $ busctl --user introspect org.qemu /org/qemu/Display1/Console_0
>
> org.qemu.Display1.Console           interface -         -               -
> .RegisterListener                   method    h         -               -
> .SetUIInfo                          method    qqiiuu    -               -
> .DeviceAddress                      property  s         "pci/0000/01.0"
> emits-change
> .Head                               property  u         0
>  emits-change
> .Height                             property  u         480
>  emits-change
> .Label                              property  s         "VGA"
>  emits-change
> .Type                               property  s         "Graphic"
>  emits-change
> .Width                              property  u         640
>  emits-change
> [...]
>
> See the interfaces XML source file and Sphinx docs for the generated API
> documentations.
>
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
>  meson.build          |  12 +-
>  qapi/ui.json         |  27 ++-
>  include/qemu/dbus.h  |  19 ++
>  ui/dbus.h            |  83 ++++++++
>  ui/dbus-console.c    | 497 +++++++++++++++++++++++++++++++++++++++++++
>  ui/dbus-error.c      |  48 +++++
>  ui/dbus-listener.c   | 486 ++++++++++++++++++++++++++++++++++++++++++
>  ui/dbus.c            | 262 +++++++++++++++++++++++
>  meson_options.txt    |   2 +
>  qemu-options.hx      |  15 ++
>  ui/dbus-display1.xml | 378 ++++++++++++++++++++++++++++++++
>  ui/meson.build       |  22 ++
>  ui/trace-events      |  11 +
>  13 files changed, 1859 insertions(+), 3 deletions(-)
>  create mode 100644 ui/dbus.h
>  create mode 100644 ui/dbus-console.c
>  create mode 100644 ui/dbus-error.c
>  create mode 100644 ui/dbus-listener.c
>  create mode 100644 ui/dbus.c
>
> diff --git a/meson.build b/meson.build
> index 9494590aa2..b571fde310 100644
> --- a/meson.build
> +++ b/meson.build
> @@ -1198,6 +1198,15 @@ endif
>  have_host_block_device = (targetos != 'darwin' or
>      cc.has_header('IOKit/storage/IOMedia.h'))
>
> +dbus_display = false
> +if not get_option('dbus_display').disabled()
> +  # FIXME enable_modules shouldn't be necessary, but:
> https://github.com/mesonbuild/meson/issues/8333
> +  dbus_display = gio.version().version_compare('>=2.64') and
> config_host.has_key('GDBUS_CODEGEN') and enable_modules
> +  if get_option('dbus_display').enabled() and not dbus_display
> +    error('Requirements missing to enable -display dbus')
> +  endif
> +endif
>

I should add a --enable-dbus-display configure flag too.

+
>  #################
>  # config-host.h #
>  #################
> @@ -1299,7 +1308,7 @@ config_host_data.set('CONFIG_SPICE_PROTOCOL_MINOR',
> spice_protocol.version().spl
>  config_host_data.set('CONFIG_SPICE_PROTOCOL_MICRO',
> spice_protocol.version().split('.')[2])
>  endif
>  config_host_data.set('CONFIG_SPICE', spice.found())
> -config_host_data.set('CONFIG_X11', x11.found())
> +config_host_data.set('CONFIG_DBUS_DISPLAY', dbus_display)
>

oops, bad rebase here. will fix it in v3

 config_host_data.set('CONFIG_CFI', get_option('cfi'))
>  config_host_data.set('QEMU_VERSION', '"@0@
> "'.format(meson.project_version()))
>  config_host_data.set('QEMU_VERSION_MAJOR',
> meson.project_version().split('.')[0])
> @@ -2892,6 +2901,7 @@ summary_info += {'fuzzing support':
>  config_host.has_key('CONFIG_FUZZ')}
>  if have_system
>    summary_info += {'Audio drivers':
>  config_host['CONFIG_AUDIO_DRIVERS']}
>  endif
> +summary_info += {'D-Bus display':     dbus_display}
>  summary_info += {'Trace backends':    config_host['TRACE_BACKENDS']}
>  if config_host['TRACE_BACKENDS'].split().contains('simple')
>    summary_info += {'Trace output file': config_host['CONFIG_TRACE_FILE']
> + '-<pid>'}
> diff --git a/qapi/ui.json b/qapi/ui.json
> index d7567ac866..5ca604bd90 100644
> --- a/qapi/ui.json
> +++ b/qapi/ui.json
> @@ -1121,6 +1121,23 @@
>  { 'struct'  : 'DisplayEGLHeadless',
>    'data'    : { '*rendernode' : 'str' } }
>
> +##
> +# @DisplayDBus:
> +#
> +# DBus display options.
> +#
> +# @addr: The D-Bus bus address (default to the session bus).
> +#
> +# @rendernode: Which DRM render node should be used. Default is the first
> +#              available node on the host.
> +#
> +# Since: 6.2
> +#
> +##
> +{ 'struct'  : 'DisplayDBus',
> +  'data'    : { '*rendernode' : 'str',
> +                '*addr': 'str' } }
> +
>   ##
>   # @DisplayGLMode:
>   #
> @@ -1186,6 +1203,8 @@
>  #             application to connect to it. The server will redirect
>  #             the serial console and QEMU monitors. (Since 4.0)
>  #
> +# @dbus: Start a D-Bus service for the display. (Since 6.2)
> +#
>  # Since: 2.12
>  #
>  ##
> @@ -1199,7 +1218,10 @@
>                'if': { 'all': ['CONFIG_OPENGL', 'CONFIG_GBM'] } },
>      { 'name': 'curses', 'if': 'CONFIG_CURSES' },
>      { 'name': 'cocoa', 'if': 'CONFIG_COCOA' },
> -    { 'name': 'spice-app', 'if': 'CONFIG_SPICE'} ] }
> +    { 'name': 'spice-app', 'if': 'CONFIG_SPICE' },
> +    { 'name': 'dbus', 'if': 'CONFIG_DBUS_DISPLAY' }
> +  ]
> +}
>
>  ##
>  # @DisplayOptions:
> @@ -1227,7 +1249,8 @@
>        'gtk': { 'type': 'DisplayGTK', 'if': 'CONFIG_GTK' },
>        'curses': { 'type': 'DisplayCurses', 'if': 'CONFIG_CURSES' },
>        'egl-headless': { 'type': 'DisplayEGLHeadless',
> -                        'if': { 'all': ['CONFIG_OPENGL', 'CONFIG_GBM'] } }
> +                        'if': { 'all': ['CONFIG_OPENGL', 'CONFIG_GBM'] }
> },
> +      'dbus': { 'type': 'DisplayDBus', 'if': 'CONFIG_DBUS_DISPLAY' }
>    }
>  }
>
> diff --git a/include/qemu/dbus.h b/include/qemu/dbus.h
> index 9d591f9ee4..c0cbb1ca44 100644
> --- a/include/qemu/dbus.h
> +++ b/include/qemu/dbus.h
> @@ -12,6 +12,25 @@
>
>  #include <gio/gio.h>
>
> +/* glib/gio 2.68 */
> +#define DBUS_METHOD_INVOCATION_HANDLED TRUE
> +#define DBUS_METHOD_INVOCATION_UNHANDLED FALSE
> +
> +/* in msec */
> +#define DBUS_DEFAULT_TIMEOUT 1000
> +
> +#define DBUS_DISPLAY1_ROOT "/org/qemu/Display1"
> +
> +#define DBUS_DISPLAY_ERROR (dbus_display_error_quark())
> +GQuark dbus_display_error_quark(void);
> +
> +typedef enum {
> +    DBUS_DISPLAY_ERROR_FAILED,
> +    DBUS_DISPLAY_ERROR_INVALID,
> +    DBUS_DISPLAY_ERROR_UNSUPPORTED,
> +    DBUS_DISPLAY_N_ERRORS,
> +} DBusDisplayError;
> +
>  GStrv qemu_dbus_get_queued_owners(GDBusConnection *connection,
>                                    const char *name,
>                                    Error **errp);
> diff --git a/ui/dbus.h b/ui/dbus.h
> new file mode 100644
> index 0000000000..d3c9598dd1
> --- /dev/null
> +++ b/ui/dbus.h
> @@ -0,0 +1,83 @@
> +/*
> + * QEMU DBus display
> + *
> + * Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com>
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining
> a copy
> + * of this software and associated documentation files (the "Software"),
> to deal
> + * in the Software without restriction, including without limitation the
> rights
> + * to use, copy, modify, merge, publish, distribute, sublicense, and/or
> sell
> + * copies of the Software, and to permit persons to whom the Software is
> + * furnished to do so, subject to the following conditions:
> + *
> + * The above copyright notice and this permission notice shall be
> included in
> + * all copies or substantial portions of the Software.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
> EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
> MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
> + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
> OTHER
> + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
> ARISING FROM,
> + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
> IN
> + * THE SOFTWARE.
> + */
> +#ifndef UI_DBUS_H_
> +#define UI_DBUS_H_
> +
> +#include "qemu/dbus.h"
> +#include "qom/object.h"
> +#include "ui/console.h"
> +
> +#include "dbus-display1.h"
> +
> +struct DBusDisplay {
> +    Object parent;
> +
> +    DisplayGLMode gl_mode;
> +    char *dbus_addr;
> +    DisplayGLCtx glctx;
> +
> +    GDBusConnection *bus;
> +    GDBusObjectManagerServer *server;
> +    QemuDBusDisplay1VM *iface;
> +    GPtrArray *consoles;
> +};
> +
> +#define TYPE_DBUS_DISPLAY "dbus-display"
> +OBJECT_DECLARE_SIMPLE_TYPE(DBusDisplay, DBUS_DISPLAY)
> +
> +#define DBUS_DISPLAY_TYPE_CONSOLE dbus_display_console_get_type()
> +G_DECLARE_FINAL_TYPE(DBusDisplayConsole,
> +                     dbus_display_console,
> +                     DBUS_DISPLAY,
> +                     CONSOLE,
> +                     GDBusObjectSkeleton)
> +
> +DBusDisplayConsole *
> +dbus_display_console_new(DBusDisplay *display, QemuConsole *con);
> +
> +int
> +dbus_display_console_get_index(DBusDisplayConsole *ddc);
> +
> +#define DBUS_DISPLAY_TYPE_LISTENER dbus_display_listener_get_type()
> +G_DECLARE_FINAL_TYPE(DBusDisplayListener,
> +                     dbus_display_listener,
> +                     DBUS_DISPLAY,
> +                     LISTENER,
> +                     GObject)
> +
> +DBusDisplayListener *
> +dbus_display_listener_new(const char *bus_name,
> +                          GDBusConnection *conn,
> +                          DBusDisplayConsole *console);
> +
> +DBusDisplayConsole *
> +dbus_display_listener_get_console(DBusDisplayListener *ddl);
> +
> +const char *
> +dbus_display_listener_get_bus_name(DBusDisplayListener *ddl);
> +
> +extern const DisplayChangeListenerOps dbus_gl_dcl_ops;
> +extern const DisplayChangeListenerOps dbus_dcl_ops;
> +
> +#endif /* UI_DBUS_H_ */
> diff --git a/ui/dbus-console.c b/ui/dbus-console.c
> new file mode 100644
> index 0000000000..1ccf638c10
> --- /dev/null
> +++ b/ui/dbus-console.c
> @@ -0,0 +1,497 @@
> +/*
> + * QEMU DBus display console
> + *
> + * Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com>
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining
> a copy
> + * of this software and associated documentation files (the "Software"),
> to deal
> + * in the Software without restriction, including without limitation the
> rights
> + * to use, copy, modify, merge, publish, distribute, sublicense, and/or
> sell
> + * copies of the Software, and to permit persons to whom the Software is
> + * furnished to do so, subject to the following conditions:
> + *
> + * The above copyright notice and this permission notice shall be
> included in
> + * all copies or substantial portions of the Software.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
> EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
> MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
> + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
> OTHER
> + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
> ARISING FROM,
> + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
> IN
> + * THE SOFTWARE.
> + */
> +#include "qemu/osdep.h"
> +#include "qapi/error.h"
> +#include "ui/input.h"
> +#include "ui/kbd-state.h"
> +#include "trace.h"
> +
> +#include <gio/gunixfdlist.h>
> +
> +#include "dbus.h"
> +
> +struct _DBusDisplayConsole {
> +    GDBusObjectSkeleton parent_instance;
> +    DisplayChangeListener dcl;
> +
> +    DBusDisplay *display;
> +    QemuConsole *con;
> +    GHashTable *listeners;
> +    QemuDBusDisplay1Console *iface;
> +
> +    QemuDBusDisplay1Keyboard *iface_kbd;
> +    QKbdState *kbd;
> +
> +    QemuDBusDisplay1Mouse *iface_mouse;
> +    gboolean last_set;
> +    guint last_x;
> +    guint last_y;
> +    Notifier mouse_mode_notifier;
> +};
> +
> +G_DEFINE_TYPE(DBusDisplayConsole,
> +              dbus_display_console,
> +              G_TYPE_DBUS_OBJECT_SKELETON)
> +
> +static void
> +dbus_display_console_set_size(DBusDisplayConsole *ddc,
> +                              uint32_t width, uint32_t height)
> +{
> +    g_object_set(ddc->iface,
> +                 "width", width,
> +                 "height", height,
> +                 NULL);
> +}
> +
> +static void
> +dbus_gfx_switch(DisplayChangeListener *dcl,
> +                struct DisplaySurface *new_surface)
> +{
> +    DBusDisplayConsole *ddc = container_of(dcl, DBusDisplayConsole, dcl);
> +
> +    dbus_display_console_set_size(ddc,
> +                                  surface_width(new_surface),
> +                                  surface_height(new_surface));
> +}
> +
> +static void
> +dbus_gfx_update(DisplayChangeListener *dcl,
> +                int x, int y, int w, int h)
> +{
> +}
> +
> +static void
> +dbus_gl_scanout_disable(DisplayChangeListener *dcl)
> +{
> +}
> +
> +static void
> +dbus_gl_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)
> +{
> +    DBusDisplayConsole *ddc = container_of(dcl, DBusDisplayConsole, dcl);
> +
> +    dbus_display_console_set_size(ddc, w, h);
> +}
> +
> +static void
> +dbus_gl_scanout_dmabuf(DisplayChangeListener *dcl,
> +                       QemuDmaBuf *dmabuf)
> +{
> +    DBusDisplayConsole *ddc = container_of(dcl, DBusDisplayConsole, dcl);
> +
> +    dbus_display_console_set_size(ddc,
> +                                  dmabuf->width,
> +                                  dmabuf->height);
> +}
> +
> +static void
> +dbus_gl_scanout_update(DisplayChangeListener *dcl,
> +                       uint32_t x, uint32_t y,
> +                       uint32_t w, uint32_t h)
> +{
> +}
> +
> +static const DisplayChangeListenerOps dbus_console_dcl_ops = {
> +    .dpy_name                = "dbus-console",
> +    .dpy_gfx_switch          = dbus_gfx_switch,
> +    .dpy_gfx_update          = dbus_gfx_update,
> +    .dpy_gl_scanout_disable  = dbus_gl_scanout_disable,
> +    .dpy_gl_scanout_texture  = dbus_gl_scanout_texture,
> +    .dpy_gl_scanout_dmabuf   = dbus_gl_scanout_dmabuf,
> +    .dpy_gl_update           = dbus_gl_scanout_update,
> +};
> +
> +static void
> +dbus_display_console_init(DBusDisplayConsole *object)
> +{
> +    DBusDisplayConsole *ddc = DBUS_DISPLAY_CONSOLE(object);
> +
> +    ddc->listeners = g_hash_table_new_full(g_str_hash, g_str_equal,
> +                                            NULL, g_object_unref);
> +    ddc->dcl.ops = &dbus_console_dcl_ops;
> +}
> +
> +static void
> +dbus_display_console_dispose(GObject *object)
> +{
> +    DBusDisplayConsole *ddc = DBUS_DISPLAY_CONSOLE(object);
> +
> +    unregister_displaychangelistener(&ddc->dcl);
> +    g_clear_object(&ddc->iface_kbd);
> +    g_clear_object(&ddc->iface);
> +    g_clear_pointer(&ddc->listeners, g_hash_table_unref);
> +    g_clear_pointer(&ddc->kbd, qkbd_state_free);
> +
> +    G_OBJECT_CLASS(dbus_display_console_parent_class)->dispose(object);
> +}
> +
> +static void
> +dbus_display_console_class_init(DBusDisplayConsoleClass *klass)
> +{
> +    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
> +
> +    gobject_class->dispose = dbus_display_console_dispose;
> +}
> +
> +static void
> +listener_vanished_cb(DBusDisplayListener *listener)
> +{
> +    DBusDisplayConsole *ddc = dbus_display_listener_get_console(listener);
> +    const char *name = dbus_display_listener_get_bus_name(listener);
> +
> +    trace_dbus_listener_vanished(name);
> +
> +    g_hash_table_remove(ddc->listeners, name);
> +    qkbd_state_lift_all_keys(ddc->kbd);
> +}
> +
> +static gboolean
> +dbus_console_set_ui_info(DBusDisplayConsole *ddc,
> +                         GDBusMethodInvocation *invocation,
> +                         guint16 arg_width_mm,
> +                         guint16 arg_height_mm,
> +                         gint arg_xoff,
> +                         gint arg_yoff,
> +                         guint arg_width,
> +                         guint arg_height)
> +{
> +    QemuUIInfo info = {
> +        .width_mm = arg_width_mm,
> +        .height_mm = arg_height_mm,
> +        .xoff = arg_xoff,
> +        .yoff = arg_yoff,
> +        .width = arg_width,
> +        .height = arg_height,
> +    };
> +
> +    if (!dpy_ui_info_supported(ddc->con)) {
> +        g_dbus_method_invocation_return_error(invocation,
> +                                              DBUS_DISPLAY_ERROR,
> +
> DBUS_DISPLAY_ERROR_UNSUPPORTED,
> +                                              "SetUIInfo is not
> supported");
> +        return DBUS_METHOD_INVOCATION_HANDLED;
> +    }
> +
> +    dpy_set_ui_info(ddc->con, &info, false);
> +    qemu_dbus_display1_console_complete_set_uiinfo(ddc->iface,
> invocation);
> +    return DBUS_METHOD_INVOCATION_HANDLED;
> +}
> +
> +static gboolean
> +dbus_console_register_listener(DBusDisplayConsole *ddc,
> +                               GDBusMethodInvocation *invocation,
> +                               GUnixFDList *fd_list,
> +                               GVariant *arg_listener)
> +{
> +    const char *sender = g_dbus_method_invocation_get_sender(invocation);
> +    GDBusConnection *listener_conn;
> +    g_autoptr(GError) err = NULL;
> +    g_autoptr(GSocket) socket = NULL;
> +    g_autoptr(GSocketConnection) socket_conn = NULL;
> +    g_autofree char *guid = g_dbus_generate_guid();
> +    DBusDisplayListener *listener;
> +    int fd;
> +
> +    if (g_hash_table_contains(ddc->listeners, sender)) {
> +        g_dbus_method_invocation_return_error(
> +            invocation,
> +            DBUS_DISPLAY_ERROR,
> +            DBUS_DISPLAY_ERROR_INVALID,
> +            "`%s` is already registered!",
> +            sender);
> +        return DBUS_METHOD_INVOCATION_HANDLED;
> +    }
> +
> +    fd = g_unix_fd_list_get(fd_list, g_variant_get_handle(arg_listener),
> &err);
> +    if (err) {
> +        g_dbus_method_invocation_return_error(
> +            invocation,
> +            DBUS_DISPLAY_ERROR,
> +            DBUS_DISPLAY_ERROR_FAILED,
> +            "Couldn't get peer fd: %s", err->message);
> +        return DBUS_METHOD_INVOCATION_HANDLED;
> +    }
> +
> +    socket = g_socket_new_from_fd(fd, &err);
> +    if (err) {
> +        g_dbus_method_invocation_return_error(
> +            invocation,
> +            DBUS_DISPLAY_ERROR,
> +            DBUS_DISPLAY_ERROR_FAILED,
> +            "Couldn't make a socket: %s", err->message);
> +        close(fd);
> +        return DBUS_METHOD_INVOCATION_HANDLED;
> +    }
> +    socket_conn = g_socket_connection_factory_create_connection(socket);
> +
> +    qemu_dbus_display1_console_complete_register_listener(
> +        ddc->iface, invocation, NULL);
> +
> +    listener_conn = g_dbus_connection_new_sync(
> +        G_IO_STREAM(socket_conn),
> +        guid,
> +        G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER,
> +        NULL, NULL, &err);
> +    if (err) {
> +        error_report("Failed to setup peer connection: %s", err->message);
> +        return DBUS_METHOD_INVOCATION_HANDLED;
> +    }
> +
> +    listener = dbus_display_listener_new(sender, listener_conn, ddc);
> +    if (!listener) {
> +        return DBUS_METHOD_INVOCATION_HANDLED;
> +    }
> +
> +    g_hash_table_insert(ddc->listeners,
> +
> (gpointer)dbus_display_listener_get_bus_name(listener),
> +                        listener);
> +    g_object_connect(listener_conn,
> +                     "swapped-signal::closed", listener_vanished_cb,
> listener,
> +                     NULL);
> +
> +    trace_dbus_registered_listener(sender);
> +    return DBUS_METHOD_INVOCATION_HANDLED;
> +}
> +
> +static gboolean
> +dbus_kbd_press(DBusDisplayConsole *ddc,
> +               GDBusMethodInvocation *invocation,
> +               guint arg_keycode)
> +{
> +    QKeyCode qcode = qemu_input_key_number_to_qcode(arg_keycode);
> +
> +    trace_dbus_kbd_press(arg_keycode);
> +
> +    qkbd_state_key_event(ddc->kbd, qcode, true);
> +
> +    qemu_dbus_display1_keyboard_complete_press(ddc->iface_kbd,
> invocation);
> +
> +    return DBUS_METHOD_INVOCATION_HANDLED;
> +}
> +
> +static gboolean
> +dbus_kbd_release(DBusDisplayConsole *ddc,
> +                 GDBusMethodInvocation *invocation,
> +                 guint arg_keycode)
> +{
> +    QKeyCode qcode = qemu_input_key_number_to_qcode(arg_keycode);
> +
> +    trace_dbus_kbd_release(arg_keycode);
> +
> +    qkbd_state_key_event(ddc->kbd, qcode, false);
> +
> +    qemu_dbus_display1_keyboard_complete_release(ddc->iface_kbd,
> invocation);
> +
> +    return DBUS_METHOD_INVOCATION_HANDLED;
> +}
> +
> +static void
> +dbus_kbd_qemu_leds_updated(void *data, int ledstate)
> +{
> +    DBusDisplayConsole *ddc = DBUS_DISPLAY_CONSOLE(data);
> +
> +    qemu_dbus_display1_keyboard_set_modifiers(ddc->iface_kbd, ledstate);
> +}
> +
> +static gboolean
> +dbus_mouse_rel_motion(DBusDisplayConsole *ddc,
> +                      GDBusMethodInvocation *invocation,
> +                      int dx, int dy)
> +{
> +    trace_dbus_mouse_rel_motion(dx, dy);
> +
> +    if (qemu_input_is_absolute()) {
> +        g_dbus_method_invocation_return_error(
> +            invocation, DBUS_DISPLAY_ERROR,
> +            DBUS_DISPLAY_ERROR_INVALID,
> +            "Mouse is not relative");
> +        return DBUS_METHOD_INVOCATION_HANDLED;
> +    }
> +
> +    qemu_input_queue_rel(ddc->con, INPUT_AXIS_X, dx);
> +    qemu_input_queue_rel(ddc->con, INPUT_AXIS_Y, dy);
> +    qemu_input_event_sync();
> +
> +    qemu_dbus_display1_mouse_complete_rel_motion(ddc->iface_mouse,
> +                                                    invocation);
> +
> +    return DBUS_METHOD_INVOCATION_HANDLED;
> +}
> +
> +static gboolean
> +dbus_mouse_set_pos(DBusDisplayConsole *ddc,
> +                   GDBusMethodInvocation *invocation,
> +                   guint x, guint y)
> +{
> +    int width, height;
> +
> +    trace_dbus_mouse_set_pos(x, y);
> +
> +    if (!qemu_input_is_absolute()) {
> +        g_dbus_method_invocation_return_error(
> +            invocation, DBUS_DISPLAY_ERROR,
> +            DBUS_DISPLAY_ERROR_INVALID,
> +            "Mouse is not absolute");
> +        return DBUS_METHOD_INVOCATION_HANDLED;
> +    }
> +
> +    width = qemu_console_get_width(ddc->con, 0);
> +    height = qemu_console_get_height(ddc->con, 0);
> +    if (x >= width || y >= height) {
> +        g_dbus_method_invocation_return_error(
> +            invocation, DBUS_DISPLAY_ERROR,
> +            DBUS_DISPLAY_ERROR_INVALID,
> +            "Invalid mouse position");
> +        return DBUS_METHOD_INVOCATION_HANDLED;
> +    }
> +    qemu_input_queue_abs(ddc->con, INPUT_AXIS_X, x, 0, width);
> +    qemu_input_queue_abs(ddc->con, INPUT_AXIS_Y, y, 0, height);
> +    qemu_input_event_sync();
> +
> +    qemu_dbus_display1_mouse_complete_set_abs_position(ddc->iface_mouse,
> +                                                          invocation);
> +
> +    return DBUS_METHOD_INVOCATION_HANDLED;
> +}
> +
> +static gboolean
> +dbus_mouse_press(DBusDisplayConsole *ddc,
> +                 GDBusMethodInvocation *invocation,
> +                 guint button)
> +{
> +    trace_dbus_mouse_press(button);
> +
> +    qemu_input_queue_btn(ddc->con, button, true);
> +    qemu_input_event_sync();
> +
> +    qemu_dbus_display1_mouse_complete_press(ddc->iface_mouse, invocation);
> +
> +    return DBUS_METHOD_INVOCATION_HANDLED;
> +}
> +
> +static gboolean
> +dbus_mouse_release(DBusDisplayConsole *ddc,
> +                   GDBusMethodInvocation *invocation,
> +                   guint button)
> +{
> +    trace_dbus_mouse_release(button);
> +
> +    qemu_input_queue_btn(ddc->con, button, false);
> +    qemu_input_event_sync();
> +
> +    qemu_dbus_display1_mouse_complete_release(ddc->iface_mouse,
> invocation);
> +
> +    return DBUS_METHOD_INVOCATION_HANDLED;
> +}
> +
> +static void
> +dbus_mouse_mode_change(Notifier *notify, void *data)
> +{
> +    DBusDisplayConsole *ddc =
> +        container_of(notify, DBusDisplayConsole, mouse_mode_notifier);
> +
> +    g_object_set(ddc->iface_mouse,
> +                 "is-absolute", qemu_input_is_absolute(),
> +                 NULL);
> +}
> +
> +int dbus_display_console_get_index(DBusDisplayConsole *ddc)
> +{
> +    return qemu_console_get_index(ddc->con);
> +}
> +
> +DBusDisplayConsole *
> +dbus_display_console_new(DBusDisplay *display, QemuConsole *con)
> +{
> +    g_autofree char *path = NULL;
> +    g_autofree char *label = NULL;
> +    char device_addr[256] = "";
> +    DBusDisplayConsole *ddc;
> +    int idx;
> +
> +    assert(display);
> +    assert(con);
> +
> +    label = qemu_console_get_label(con);
> +    idx = qemu_console_get_index(con);
> +    path = g_strdup_printf(DBUS_DISPLAY1_ROOT "/Console_%d", idx);
> +    ddc = g_object_new(DBUS_DISPLAY_TYPE_CONSOLE,
> +                        "g-object-path", path,
> +                        NULL);
> +    ddc->display = display;
> +    ddc->con = con;
> +    /* handle errors, and skip non graphics? */
> +    qemu_console_fill_device_address(
> +        con, device_addr, sizeof(device_addr), NULL);
> +
> +    ddc->iface = qemu_dbus_display1_console_skeleton_new();
> +    g_object_set(ddc->iface,
> +        "label", label,
> +        "type", qemu_console_is_graphic(con) ? "Graphic" : "Text",
> +        "head", qemu_console_get_head(con),
> +        "width", qemu_console_get_width(con, 0),
> +        "height", qemu_console_get_height(con, 0),
> +        "device-address", device_addr,
> +        NULL);
> +    g_object_connect(ddc->iface,
> +        "swapped-signal::handle-register-listener",
> +        dbus_console_register_listener, ddc,
> +        "swapped-signal::handle-set-uiinfo",
> +        dbus_console_set_ui_info, ddc,
> +        NULL);
> +    g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc),
> +        G_DBUS_INTERFACE_SKELETON(ddc->iface));
> +
> +    ddc->kbd = qkbd_state_init(con);
> +    ddc->iface_kbd = qemu_dbus_display1_keyboard_skeleton_new();
> +    qemu_add_led_event_handler(dbus_kbd_qemu_leds_updated, ddc);
> +    g_object_connect(ddc->iface_kbd,
> +        "swapped-signal::handle-press", dbus_kbd_press, ddc,
> +        "swapped-signal::handle-release", dbus_kbd_release, ddc,
> +        NULL);
> +    g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc),
> +        G_DBUS_INTERFACE_SKELETON(ddc->iface_kbd));
> +
> +    ddc->iface_mouse = qemu_dbus_display1_mouse_skeleton_new();
> +    g_object_connect(ddc->iface_mouse,
> +        "swapped-signal::handle-set-abs-position", dbus_mouse_set_pos,
> ddc,
> +        "swapped-signal::handle-rel-motion", dbus_mouse_rel_motion, ddc,
> +        "swapped-signal::handle-press", dbus_mouse_press, ddc,
> +        "swapped-signal::handle-release", dbus_mouse_release, ddc,
> +        NULL);
> +    g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc),
> +        G_DBUS_INTERFACE_SKELETON(ddc->iface_mouse));
> +
> +    register_displaychangelistener(&ddc->dcl);
> +    ddc->mouse_mode_notifier.notify = dbus_mouse_mode_change;
> +    qemu_add_mouse_mode_change_notifier(&ddc->mouse_mode_notifier);
> +
> +    return ddc;
> +}
> diff --git a/ui/dbus-error.c b/ui/dbus-error.c
> new file mode 100644
> index 0000000000..85a9194d57
> --- /dev/null
> +++ b/ui/dbus-error.c
> @@ -0,0 +1,48 @@
> +/*
> + * QEMU DBus display errors
> + *
> + * Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com>
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining
> a copy
> + * of this software and associated documentation files (the "Software"),
> to deal
> + * in the Software without restriction, including without limitation the
> rights
> + * to use, copy, modify, merge, publish, distribute, sublicense, and/or
> sell
> + * copies of the Software, and to permit persons to whom the Software is
> + * furnished to do so, subject to the following conditions:
> + *
> + * The above copyright notice and this permission notice shall be
> included in
> + * all copies or substantial portions of the Software.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
> EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
> MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
> + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
> OTHER
> + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
> ARISING FROM,
> + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
> IN
> + * THE SOFTWARE.
> + */
> +#include "qemu/osdep.h"
> +#include "dbus.h"
> +
> +static const GDBusErrorEntry dbus_display_error_entries[] = {
> +    { DBUS_DISPLAY_ERROR_FAILED, "org.qemu.Display1.Error.Failed" },
> +    { DBUS_DISPLAY_ERROR_INVALID, "org.qemu.Display1.Error.Invalid" },
> +    { DBUS_DISPLAY_ERROR_UNSUPPORTED,
> "org.qemu.Display1.Error.Unsupported" },
> +};
> +
> +G_STATIC_ASSERT(G_N_ELEMENTS(dbus_display_error_entries) ==
> +                DBUS_DISPLAY_N_ERRORS);
> +
> +GQuark
> +dbus_display_error_quark(void)
> +{
> +    static gsize quark;
> +
> +    g_dbus_error_register_error_domain(
> +        "dbus-display-error-quark",
> +        &quark,
> +        dbus_display_error_entries,
> +        G_N_ELEMENTS(dbus_display_error_entries));
> +
> +    return (GQuark)quark;
> +}
> diff --git a/ui/dbus-listener.c b/ui/dbus-listener.c
> new file mode 100644
> index 0000000000..20094fc18a
> --- /dev/null
> +++ b/ui/dbus-listener.c
> @@ -0,0 +1,486 @@
> +/*
> + * QEMU DBus display console
> + *
> + * Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com>
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining
> a copy
> + * of this software and associated documentation files (the "Software"),
> to deal
> + * in the Software without restriction, including without limitation the
> rights
> + * to use, copy, modify, merge, publish, distribute, sublicense, and/or
> sell
> + * copies of the Software, and to permit persons to whom the Software is
> + * furnished to do so, subject to the following conditions:
> + *
> + * The above copyright notice and this permission notice shall be
> included in
> + * all copies or substantial portions of the Software.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
> EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
> MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
> + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
> OTHER
> + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
> ARISING FROM,
> + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
> IN
> + * THE SOFTWARE.
> + */
> +#include "qemu/osdep.h"
> +#include "sysemu/sysemu.h"
> +#include "dbus.h"
> +#include <gio/gunixfdlist.h>
> +
> +#include "ui/shader.h"
> +#include "ui/egl-helpers.h"
> +#include "ui/egl-context.h"
> +#include "trace.h"
> +
> +struct _DBusDisplayListener {
> +    GObject parent;
> +
> +    char *bus_name;
> +    DBusDisplayConsole *console;
> +    GDBusConnection *conn;
> +
> +    QemuDBusDisplay1Listener *proxy;
> +
> +    DisplayChangeListener dcl;
> +    DisplaySurface *ds;
> +    QemuGLShader *gls;
> +    int gl_updates;
> +};
> +
> +G_DEFINE_TYPE(DBusDisplayListener, dbus_display_listener, G_TYPE_OBJECT)
> +
> +static void dbus_update_gl_cb(GObject *source_object,
> +                           GAsyncResult *res,
> +                           gpointer user_data)
> +{
> +    g_autoptr(GError) err = NULL;
> +    DBusDisplayListener *ddl = user_data;
> +
> +    if (!qemu_dbus_display1_listener_call_update_dmabuf_finish(ddl->proxy,
> +                                                               res,
> &err)) {
> +        error_report("Failed to call update: %s", err->message);
> +    }
> +
> +    graphic_hw_gl_block(ddl->dcl.con, false);
> +    g_object_unref(ddl);
> +}
> +
> +static void dbus_call_update_gl(DBusDisplayListener *ddl,
> +                                int x, int y, int w, int h)
> +{
> +    graphic_hw_gl_block(ddl->dcl.con, true);
> +    glFlush();
> +    qemu_dbus_display1_listener_call_update_dmabuf(ddl->proxy,
> +        x, y, w, h,
> +        G_DBUS_CALL_FLAGS_NONE,
> +        DBUS_DEFAULT_TIMEOUT, NULL,
> +        dbus_update_gl_cb,
> +        g_object_ref(ddl));
> +}
> +
> +static void dbus_scanout_disable(DisplayChangeListener *dcl)
> +{
> +    DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener,
> dcl);
> +
> +    ddl->ds = NULL;
> +    qemu_dbus_display1_listener_call_disable(
> +        ddl->proxy, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
> +}
> +
> +static void dbus_scanout_dmabuf(DisplayChangeListener *dcl,
> +                                QemuDmaBuf *dmabuf)
> +{
> +    DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener,
> dcl);
> +    g_autoptr(GError) err = NULL;
> +    g_autoptr(GUnixFDList) fd_list = NULL;
> +
> +    fd_list = g_unix_fd_list_new();
> +    if (g_unix_fd_list_append(fd_list, dmabuf->fd, &err) != 0) {
> +        error_report("Failed to setup dmabuf fdlist: %s", err->message);
> +        return;
> +    }
> +
> +    qemu_dbus_display1_listener_call_scanout_dmabuf(
> +        ddl->proxy,
> +        g_variant_new_handle(0),
> +        dmabuf->width,
> +        dmabuf->height,
> +        dmabuf->stride,
> +        dmabuf->fourcc,
> +        dmabuf->modifier,
> +        dmabuf->y0_top,
> +        G_DBUS_CALL_FLAGS_NONE,
> +        -1,
> +        fd_list,
> +        NULL, NULL, NULL);
> +}
> +
> +static void 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)
> +{
> +    QemuDmaBuf dmabuf = {
> +        .width = backing_width,
> +        .height = backing_height,
> +        .y0_top = backing_y_0_top,
> +    };
> +
> +    assert(tex_id);
> +    dmabuf.fd = egl_get_fd_for_texture(
> +        tex_id, (EGLint *)&dmabuf.stride,
> +        (EGLint *)&dmabuf.fourcc,
> +        &dmabuf.modifier);
> +    if (dmabuf.fd < 0) {
> +        error_report("%s: failed to get fd for texture", __func__);
> +        return;
> +    }
> +
> +    dbus_scanout_dmabuf(dcl, &dmabuf);
> +    close(dmabuf.fd);
> +}
> +
> +static void dbus_cursor_dmabuf(DisplayChangeListener *dcl,
> +                               QemuDmaBuf *dmabuf, bool have_hot,
> +                               uint32_t hot_x, uint32_t hot_y)
> +{
> +    DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener,
> dcl);
> +    DisplaySurface *ds;
> +    GVariant *v_data = NULL;
> +    egl_fb cursor_fb;
> +
> +    if (!dmabuf) {
> +        qemu_dbus_display1_listener_call_mouse_set(
> +            ddl->proxy, 0, 0, false,
> +            G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
> +        return;
> +    }
> +
> +    egl_dmabuf_import_texture(dmabuf);
> +    if (!dmabuf->texture) {
> +        return;
> +    }
> +    egl_fb_setup_for_tex(&cursor_fb, dmabuf->width, dmabuf->height,
> +                         dmabuf->texture, false);
> +    ds = qemu_create_displaysurface(dmabuf->width, dmabuf->height);
> +    egl_fb_read(ds, &cursor_fb);
> +
> +    v_data = g_variant_new_from_data(
> +        G_VARIANT_TYPE("ay"),
> +        surface_data(ds),
> +        surface_width(ds) * surface_height(ds) * 4,
> +        TRUE,
> +        (GDestroyNotify)qemu_free_displaysurface,
> +        ds);
> +    qemu_dbus_display1_listener_call_cursor_define(
> +        ddl->proxy,
> +        surface_width(ds),
> +        surface_height(ds),
> +        hot_x,
> +        hot_y,
> +        v_data,
> +        G_DBUS_CALL_FLAGS_NONE,
> +        -1,
> +        NULL,
> +        NULL,
> +        NULL);
> +}
> +
> +static void dbus_cursor_position(DisplayChangeListener *dcl,
> +                                 uint32_t pos_x, uint32_t pos_y)
> +{
> +    DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener,
> dcl);
> +
> +    qemu_dbus_display1_listener_call_mouse_set(
> +        ddl->proxy, pos_x, pos_y, true,
> +        G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
> +}
> +
> +static void dbus_release_dmabuf(DisplayChangeListener *dcl,
> +                                QemuDmaBuf *dmabuf)
> +{
> +    dbus_scanout_disable(dcl);
> +}
> +
> +static void dbus_scanout_update(DisplayChangeListener *dcl,
> +                                uint32_t x, uint32_t y,
> +                                uint32_t w, uint32_t h)
> +{
> +    DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener,
> dcl);
> +
> +    dbus_call_update_gl(ddl, x, y, w, h);
> +}
> +
> +static void dbus_gl_refresh(DisplayChangeListener *dcl)
> +{
> +    DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener,
> dcl);
> +
> +    graphic_hw_update(dcl->con);
> +
> +    if (!ddl->ds || qemu_console_is_gl_blocked(ddl->dcl.con)) {
> +        return;
> +    }
> +
> +    if (ddl->gl_updates) {
> +        dbus_call_update_gl(ddl, 0, 0,
> +                            surface_width(ddl->ds),
> surface_height(ddl->ds));
> +        ddl->gl_updates = 0;
> +    }
> +}
> +
> +static void dbus_refresh(DisplayChangeListener *dcl)
> +{
> +    graphic_hw_update(dcl->con);
> +}
> +
> +static void dbus_gl_gfx_update(DisplayChangeListener *dcl,
> +                               int x, int y, int w, int h)
> +{
> +    DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener,
> dcl);
> +
> +    if (ddl->ds) {
> +        surface_gl_update_texture(ddl->gls, ddl->ds, x, y, w, h);
> +    }
> +
> +    ddl->gl_updates++;
> +}
> +
> +static void dbus_gfx_update(DisplayChangeListener *dcl,
> +                            int x, int y, int w, int h)
> +{
> +    DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener,
> dcl);
> +    pixman_image_t *img;
> +    GVariant *v_data;
> +    size_t stride;
> +
> +    assert(ddl->ds);
> +    stride = w * DIV_ROUND_UP(PIXMAN_FORMAT_BPP(surface_format(ddl->ds)),
> 8);
> +
> +    trace_dbus_update(x, y, w, h);
> +
> +    /* make a copy, since gvariant only handles linear data */
> +    img = pixman_image_create_bits(surface_format(ddl->ds),
> +                                   w, h, NULL, stride);
> +    pixman_image_composite(PIXMAN_OP_SRC, ddl->ds->image, NULL, img,
> +                           x, y, 0, 0, 0, 0, w, h);
> +
> +    v_data = g_variant_new_from_data(
> +        G_VARIANT_TYPE("ay"),
> +        pixman_image_get_data(img),
> +        pixman_image_get_stride(img) * h,
> +        TRUE,
> +        (GDestroyNotify)pixman_image_unref,
> +        img);
> +    qemu_dbus_display1_listener_call_update(ddl->proxy,
> +        x, y, w, h, pixman_image_get_stride(img),
> pixman_image_get_format(img),
> +        v_data,
> +        G_DBUS_CALL_FLAGS_NONE,
> +        DBUS_DEFAULT_TIMEOUT, NULL, NULL, NULL);
> +}
> +
> +static void dbus_gl_gfx_switch(DisplayChangeListener *dcl,
> +                               struct DisplaySurface *new_surface)
> +{
> +    DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener,
> dcl);
> +
> +    if (ddl->ds) {
> +        surface_gl_destroy_texture(ddl->gls, ddl->ds);
> +    }
> +    ddl->ds = new_surface;
> +    if (ddl->ds) {
> +        int width = surface_width(ddl->ds);
> +        int height = surface_height(ddl->ds);
> +
> +        surface_gl_create_texture(ddl->gls, ddl->ds);
> +        /* TODO: lazy send dmabuf (there are unnecessary sent otherwise)
> */
> +        dbus_scanout_texture(&ddl->dcl, ddl->ds->texture, false,
> +                             width, height, 0, 0, width, height);
> +    }
> +}
> +
> +static void dbus_gfx_switch(DisplayChangeListener *dcl,
> +                            struct DisplaySurface *new_surface)
> +{
> +    DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener,
> dcl);
> +    GVariant *v_data = NULL;
> +
> +    ddl->ds = new_surface;
> +    if (!ddl->ds) {
> +        /* why not call disable instead? */
> +        return;
> +    }
> +
> +    v_data = g_variant_new_from_data(
> +        G_VARIANT_TYPE("ay"),
> +        surface_data(ddl->ds),
> +        surface_stride(ddl->ds) * surface_height(ddl->ds),
> +        TRUE,
> +        (GDestroyNotify)pixman_image_unref,
> +        pixman_image_ref(ddl->ds->image));
> +    qemu_dbus_display1_listener_call_scanout(ddl->proxy,
> +        surface_width(ddl->ds),
> +        surface_height(ddl->ds),
> +        surface_stride(ddl->ds),
> +        surface_format(ddl->ds),
> +        v_data,
> +        G_DBUS_CALL_FLAGS_NONE,
> +        DBUS_DEFAULT_TIMEOUT, NULL, NULL, NULL);
> +}
> +
> +static void dbus_mouse_set(DisplayChangeListener *dcl,
> +                           int x, int y, int on)
> +{
> +    DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener,
> dcl);
> +
> +    qemu_dbus_display1_listener_call_mouse_set(
> +        ddl->proxy, x, y, on, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL,
> NULL);
> +}
> +
> +static void dbus_cursor_define(DisplayChangeListener *dcl,
> +                               QEMUCursor *c)
> +{
> +    DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener,
> dcl);
> +    GVariant *v_data = NULL;
> +
> +    cursor_get(c);
> +    v_data = g_variant_new_from_data(
> +        G_VARIANT_TYPE("ay"),
> +        c->data,
> +        c->width * c->height * 4,
> +        TRUE,
> +        (GDestroyNotify)cursor_put,
> +        c);
> +
> +    qemu_dbus_display1_listener_call_cursor_define(
> +        ddl->proxy,
> +        c->width,
> +        c->height,
> +        c->hot_x,
> +        c->hot_y,
> +        v_data,
> +        G_DBUS_CALL_FLAGS_NONE,
> +        -1,
> +        NULL,
> +        NULL,
> +        NULL);
> +}
> +
> +const DisplayChangeListenerOps dbus_gl_dcl_ops = {
> +    .dpy_name                = "dbus-gl",
> +    .dpy_gfx_update          = dbus_gl_gfx_update,
> +    .dpy_gfx_switch          = dbus_gl_gfx_switch,
> +    .dpy_gfx_check_format    = console_gl_check_format,
> +    .dpy_refresh             = dbus_gl_refresh,
> +    .dpy_mouse_set           = dbus_mouse_set,
> +    .dpy_cursor_define       = dbus_cursor_define,
> +
> +    .dpy_gl_scanout_disable  = dbus_scanout_disable,
> +    .dpy_gl_scanout_texture  = dbus_scanout_texture,
> +    .dpy_gl_scanout_dmabuf   = dbus_scanout_dmabuf,
> +    .dpy_gl_cursor_dmabuf    = dbus_cursor_dmabuf,
> +    .dpy_gl_cursor_position  = dbus_cursor_position,
> +    .dpy_gl_release_dmabuf   = dbus_release_dmabuf,
> +    .dpy_gl_update           = dbus_scanout_update,
> +};
> +
> +const DisplayChangeListenerOps dbus_dcl_ops = {
> +    .dpy_name                = "dbus",
> +    .dpy_gfx_update          = dbus_gfx_update,
> +    .dpy_gfx_switch          = dbus_gfx_switch,
> +    .dpy_refresh             = dbus_refresh,
> +    .dpy_mouse_set           = dbus_mouse_set,
> +    .dpy_cursor_define       = dbus_cursor_define,
> +};
> +
> +static void
> +dbus_display_listener_dispose(GObject *object)
> +{
> +    DBusDisplayListener *ddl = DBUS_DISPLAY_LISTENER(object);
> +
> +    unregister_displaychangelistener(&ddl->dcl);
> +    g_clear_object(&ddl->conn);
> +    g_clear_pointer(&ddl->bus_name, g_free);
> +    g_clear_object(&ddl->proxy);
> +    g_clear_pointer(&ddl->gls, qemu_gl_fini_shader);
> +
> +    G_OBJECT_CLASS(dbus_display_listener_parent_class)->dispose(object);
> +}
> +
> +static void
> +dbus_display_listener_constructed(GObject *object)
> +{
> +    DBusDisplayListener *ddl = DBUS_DISPLAY_LISTENER(object);
> +
> +    if (display_opengl) {
> +        ddl->gls = qemu_gl_init_shader();
> +        ddl->dcl.ops = &dbus_gl_dcl_ops;
> +    } else {
> +        ddl->dcl.ops = &dbus_dcl_ops;
> +    }
> +
> +
> G_OBJECT_CLASS(dbus_display_listener_parent_class)->constructed(object);
> +}
> +
> +static void
> +dbus_display_listener_class_init(DBusDisplayListenerClass *klass)
> +{
> +    GObjectClass *object_class = G_OBJECT_CLASS(klass);
> +
> +    object_class->dispose = dbus_display_listener_dispose;
> +    object_class->constructed = dbus_display_listener_constructed;
> +}
> +
> +static void
> +dbus_display_listener_init(DBusDisplayListener *ddl)
> +{
> +}
> +
> +const char *
> +dbus_display_listener_get_bus_name(DBusDisplayListener *ddl)
> +{
> +    return ddl->bus_name;
> +}
> +
> +DBusDisplayConsole *
> +dbus_display_listener_get_console(DBusDisplayListener *ddl)
> +{
> +    return ddl->console;
> +}
> +
> +DBusDisplayListener *
> +dbus_display_listener_new(const char *bus_name,
> +                          GDBusConnection *conn,
> +                          DBusDisplayConsole *console)
> +{
> +    DBusDisplayListener *ddl;
> +    QemuConsole *con;
> +    g_autoptr(GError) err = NULL;
> +
> +    ddl = g_object_new(DBUS_DISPLAY_TYPE_LISTENER, NULL);
> +    ddl->proxy =
> +        qemu_dbus_display1_listener_proxy_new_sync(conn,
> +            G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
> +            NULL,
> +            "/org/qemu/Display1/Listener",
> +            NULL,
> +            &err);
> +    if (!ddl->proxy) {
> +        error_report("Failed to setup proxy: %s", err->message);
> +        g_object_unref(conn);
> +        g_object_unref(ddl);
> +        return NULL;
> +    }
> +
> +    ddl->bus_name = g_strdup(bus_name);
> +    ddl->conn = conn;
> +    ddl->console = console;
> +
> +    con =
> qemu_console_lookup_by_index(dbus_display_console_get_index(console));
> +    assert(con);
> +    ddl->dcl.con = con;
> +    register_displaychangelistener(&ddl->dcl);
> +
> +    return ddl;
> +}
> diff --git a/ui/dbus.c b/ui/dbus.c
> new file mode 100644
> index 0000000000..12da8ffe31
> --- /dev/null
> +++ b/ui/dbus.c
> @@ -0,0 +1,262 @@
> +/*
> + * QEMU DBus display
> + *
> + * Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com>
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining
> a copy
> + * of this software and associated documentation files (the "Software"),
> to deal
> + * in the Software without restriction, including without limitation the
> rights
> + * to use, copy, modify, merge, publish, distribute, sublicense, and/or
> sell
> + * copies of the Software, and to permit persons to whom the Software is
> + * furnished to do so, subject to the following conditions:
> + *
> + * The above copyright notice and this permission notice shall be
> included in
> + * all copies or substantial portions of the Software.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
> EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
> MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
> + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
> OTHER
> + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
> ARISING FROM,
> + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
> IN
> + * THE SOFTWARE.
> + */
> +#include "qemu/osdep.h"
> +#include "qemu/dbus.h"
> +#include "qemu/option.h"
> +#include "qom/object_interfaces.h"
> +#include "sysemu/sysemu.h"
> +#include "ui/egl-helpers.h"
> +#include "ui/egl-context.h"
> +#include "qapi/error.h"
> +#include "trace.h"
> +
> +#include "dbus.h"
> +
> +static QEMUGLContext dbus_create_context(DisplayGLCtx *dgc,
> +                                         QEMUGLParams *params)
> +{
> +    eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
> +                   qemu_egl_rn_ctx);
> +    return qemu_egl_create_context(dgc, params);
> +}
> +
> +static const DisplayGLCtxOps dbus_gl_ops = {
> +    .compatible_dcl          = &dbus_gl_dcl_ops,
> +    .dpy_gl_ctx_create       = dbus_create_context,
> +    .dpy_gl_ctx_destroy      = qemu_egl_destroy_context,
> +    .dpy_gl_ctx_make_current = qemu_egl_make_context_current,
> +};
> +
> +static void
> +dbus_display_init(Object *o)
> +{
> +    DBusDisplay *dd = DBUS_DISPLAY(o);
> +    g_autoptr(GDBusObjectSkeleton) vm = NULL;
> +
> +    dd->glctx.ops = &dbus_gl_ops;
> +    dd->iface = qemu_dbus_display1_vm_skeleton_new();
> +    dd->consoles = g_ptr_array_new_with_free_func(g_object_unref);
> +
> +    dd->server = g_dbus_object_manager_server_new(DBUS_DISPLAY1_ROOT);
> +
> +    vm = g_dbus_object_skeleton_new(DBUS_DISPLAY1_ROOT "/VM");
> +    g_dbus_object_skeleton_add_interface(
> +        vm, G_DBUS_INTERFACE_SKELETON(dd->iface));
> +    g_dbus_object_manager_server_export(dd->server, vm);
> +}
> +
> +static void
> +dbus_display_finalize(Object *o)
> +{
> +    DBusDisplay *dd = DBUS_DISPLAY(o);
> +
> +    g_clear_object(&dd->server);
> +    g_clear_pointer(&dd->consoles, g_ptr_array_unref);
> +    g_clear_object(&dd->bus);
> +    g_clear_object(&dd->iface);
> +    g_free(dd->dbus_addr);
> +}
> +
> +static bool
> +dbus_display_add_console(DBusDisplay *dd, int idx, Error **errp)
> +{
> +    QemuConsole *con;
> +    DBusDisplayConsole *dbus_console;
> +
> +    con = qemu_console_lookup_by_index(idx);
> +    assert(con);
> +
> +    if (qemu_console_is_graphic(con) &&
> +        dd->gl_mode != DISPLAYGL_MODE_OFF) {
> +        qemu_console_set_display_gl_ctx(con, &dd->glctx);
> +    }
> +
> +    dbus_console = dbus_display_console_new(dd, con);
> +    g_ptr_array_insert(dd->consoles, idx, dbus_console);
> +    g_dbus_object_manager_server_export(dd->server,
> +
> G_DBUS_OBJECT_SKELETON(dbus_console));
> +    return true;
> +}
> +
> +static void
> +dbus_display_complete(UserCreatable *uc, Error **errp)
> +{
> +    DBusDisplay *dd = DBUS_DISPLAY(uc);
> +    g_autoptr(GError) err = NULL;
> +    g_autofree char *uuid = qemu_uuid_unparse_strdup(&qemu_uuid);
> +    g_autoptr(GArray) consoles = NULL;
> +    GVariant *console_ids;
> +    int idx;
> +
> +    if (!object_resolve_path_type("", TYPE_DBUS_DISPLAY, NULL)) {
> +        error_setg(errp, "There is already an instance of %s",
> +                   TYPE_DBUS_DISPLAY);
> +        return;
> +    }
> +
> +    if (dd->dbus_addr && *dd->dbus_addr) {
> +        dd->bus = g_dbus_connection_new_for_address_sync(dd->dbus_addr,
> +                        G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
> +                        G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
> +                        NULL, NULL, &err);
> +    } else {
> +        dd->bus = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &err);
> +    }
> +    if (err) {
> +        error_setg(errp, "failed to connect to DBus: %s", err->message);
> +        return;
> +    }
> +
> +
> +    consoles = g_array_new(FALSE, FALSE, sizeof(guint32));
> +    for (idx = 0;; idx++) {
> +        if (!qemu_console_lookup_by_index(idx)) {
> +            break;
> +        }
> +        if (!dbus_display_add_console(dd, idx, errp)) {
> +            return;
> +        }
> +        g_array_append_val(consoles, idx);
> +    }
> +
> +    console_ids = g_variant_new_from_data(
> +        G_VARIANT_TYPE("au"),
> +        consoles->data, consoles->len * sizeof(guint32), TRUE,
> +        (GDestroyNotify)g_array_unref, consoles);
> +    g_steal_pointer(&consoles);
> +    g_object_set(dd->iface,
> +                 "name", qemu_name ?: "QEMU " QEMU_VERSION,
> +                 "uuid", uuid,
> +                 "console-ids", console_ids,
> +                 NULL);
> +
> +    g_dbus_object_manager_server_set_connection(dd->server, dd->bus);
> +    g_bus_own_name_on_connection(dd->bus, "org.qemu",
> +                                 G_BUS_NAME_OWNER_FLAGS_NONE,
> +                                 NULL, NULL, NULL, NULL);
> +}
> +
> +static char *
> +get_dbus_addr(Object *o, Error **errp)
> +{
> +    DBusDisplay *dd = DBUS_DISPLAY(o);
> +
> +    return g_strdup(dd->dbus_addr);
> +}
> +
> +static void
> +set_dbus_addr(Object *o, const char *str, Error **errp)
> +{
> +    DBusDisplay *dd = DBUS_DISPLAY(o);
> +
> +    g_free(dd->dbus_addr);
> +    dd->dbus_addr = g_strdup(str);
> +}
> +
> +static int
> +get_gl_mode(Object *o, Error **errp)
> +{
> +    DBusDisplay *dd = DBUS_DISPLAY(o);
> +
> +    return dd->gl_mode;
> +}
> +
> +static void
> +set_gl_mode(Object *o, int val, Error **errp)
> +{
> +    DBusDisplay *dd = DBUS_DISPLAY(o);
> +
> +    dd->gl_mode = val;
> +}
> +
> +static void
> +dbus_display_class_init(ObjectClass *oc, void *data)
> +{
> +    UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
> +
> +    ucc->complete = dbus_display_complete;
> +    object_class_property_add_str(oc, "addr", get_dbus_addr,
> set_dbus_addr);
> +    object_class_property_add_enum(oc, "gl-mode",
> +                                   "DisplayGLMode", &DisplayGLMode_lookup,
> +                                   get_gl_mode, set_gl_mode);
> +}
> +
> +static void
> +early_dbus_init(DisplayOptions *opts)
> +{
> +    DisplayGLMode mode = opts->has_gl ? opts->gl : DISPLAYGL_MODE_OFF;
> +
> +    if (mode != DISPLAYGL_MODE_OFF) {
> +        if (egl_rendernode_init(opts->u.dbus.rendernode, mode) < 0) {
> +            error_report("dbus: render node init failed");
> +            exit(1);
> +        }
> +
> +        display_opengl = 1;
> +    }
> +}
> +
> +static void
> +dbus_init(DisplayState *ds, DisplayOptions *opts)
> +{
> +    DisplayGLMode mode = opts->has_gl ? opts->gl : DISPLAYGL_MODE_OFF;
> +
> +    object_new_with_props(TYPE_DBUS_DISPLAY,
> +                          object_get_objects_root(),
> +                          "dbus-display", &error_fatal,
> +                          "addr", opts->u.dbus.addr ?: "",
> +                          "gl-mode", DisplayGLMode_str(mode),
> +                          NULL);
> +}
> +
> +static const TypeInfo dbus_display_info = {
> +    .name = TYPE_DBUS_DISPLAY,
> +    .parent = TYPE_OBJECT,
> +    .instance_size = sizeof(DBusDisplay),
> +    .instance_init = dbus_display_init,
> +    .instance_finalize = dbus_display_finalize,
> +    .class_init = dbus_display_class_init,
> +    .interfaces = (InterfaceInfo[]) {
> +        { TYPE_USER_CREATABLE },
> +        { }
> +    }
> +};
> +
> +static QemuDisplay qemu_display_dbus = {
> +    .type       = DISPLAY_TYPE_DBUS,
> +    .early_init = early_dbus_init,
> +    .init       = dbus_init,
> +};
> +
> +static void register_dbus(void)
> +{
> +    type_register_static(&dbus_display_info);
> +    qemu_display_register(&qemu_display_dbus);
> +}
> +
> +type_init(register_dbus);
> +
> +#ifdef CONFIG_OPENGL
> +module_dep("ui-opengl");
> +#endif
> diff --git a/meson_options.txt b/meson_options.txt
> index b064b0b46c..0a6abb311e 100644
> --- a/meson_options.txt
> +++ b/meson_options.txt
> @@ -49,6 +49,8 @@ option('cfi_debug', type: 'boolean', value: 'false',
>         description: 'Verbose errors in case of CFI violation')
>  option('multiprocess', type: 'feature', value: 'auto',
>         description: 'Out of process device emulation support')
> +option('dbus_display', type: 'feature', value: 'auto',
> +       description: '-display dbus support')
>
>  option('attr', type : 'feature', value : 'auto',
>         description: 'attr/xattr support')
> diff --git a/qemu-options.hx b/qemu-options.hx
> index 5f375bbfa6..1a927458e6 100644
> --- a/qemu-options.hx
> +++ b/qemu-options.hx
> @@ -1862,6 +1862,10 @@ DEF("display", HAS_ARG, QEMU_OPTION_display,
>  #endif
>  #if defined(CONFIG_OPENGL)
>      "-display egl-headless[,rendernode=<file>]\n"
> +#endif
> +#if defined(CONFIG_DBUS_DISPLAY)
> +    "-display dbus[,addr=<dbusaddr>]\n"
> +    "             [,gl=on|core|es|off][,rendernode=<file>]\n"
>  #endif
>      "-display none\n"
>      "                select display backend type\n"
> @@ -1889,6 +1893,17 @@ SRST
>          application. The Spice server will redirect the serial consoles
>          and QEMU monitors. (Since 4.0)
>
> +    ``dbus``
> +        Export the display over D-Bus interfaces. (Since 6.2)
> +
> +        The connection is registered with the "org.qemu" name (and queued
> when
> +        already owned).
> +
> +        ``addr=<dbusaddr>`` : D-Bus bus address to connect to.
> +
> +        ``gl=on|off|core|es`` : Use OpenGL for rendering (the D-interface
> will
> +        share framebuffers with DMABUF file descriptors).
> +
>      ``sdl``
>          Display video output via SDL (usually in a separate graphics
>          window; see the SDL documentation for other possibilities).
> diff --git a/ui/dbus-display1.xml b/ui/dbus-display1.xml
> index e69de29bb2..0f0ae92e4d 100644
> --- a/ui/dbus-display1.xml
> +++ b/ui/dbus-display1.xml
> @@ -0,0 +1,378 @@
> +<?xml version="1.0" encoding="utf-8"?>
> +<node>
> +  <!--
> +      org.qemu.Display1.VM:
> +
> +      This interface is implemented on ``/org/qemu/Display1/VM``.
> +  -->
> +  <interface name="org.qemu.Display1.VM">
> +    <!--
> +        Name:
> +
> +        The name of the VM.
> +    -->
> +    <property name="Name" type="s" access="read"/>
> +
> +    <!--
> +        UUID:
> +
> +        The UUID of the VM.
> +    -->
> +    <property name="UUID" type="s" access="read"/>
> +
> +    <!--
> +        ConsoleIDs:
> +
> +        The list of consoles available on
> ``/org/qemu/Display1/Console_$id``.
> +    -->
> +    <property name="ConsoleIDs" type="au" access="read"/>
> +  </interface>
> +
> +  <!--
> +      org.qemu.Display1.Console:
> +
> +      This interface is implemented on
> ``/org/qemu/Display1/Console_$id``. You
> +      may discover available consoles through introspection or with the
> +      :dbus:prop:`org.qemu.Display1.VM.ConsoleIDs` property.
> +
> +      A console is attached to a video device head. It may be "Graphic" or
> +      "Text" (see :dbus:prop:`Type` and other properties).
> +
> +      Interactions with a console may be done with
> +      :dbus:iface:`org.qemu.Display1.Keyboard` and
> +      :dbus:iface:`org.qemu.Display1.Mouse` interfaces when available.
> +  -->
> +  <interface name="org.qemu.Display1.Console">
> +    <!--
> +        RegisterListener:
> +        @listener: a Unix socket FD, for peer-to-peer D-Bus communication.
> +
> +        Register a console listener, which will receive display updates,
> until
> +        it is disconnected.
> +
> +        Multiple listeners may be registered simultaneously.
> +
> +        The listener is expected to implement the
> +        :dbus:iface:`org.qemu.Display1.Listener` interface.
> +    -->
> +    <method name="RegisterListener">
> +      <arg type="h" name="listener" direction="in"/>
> +    </method>
> +
> +    <!--
> +        SetUIInfo:
> +        @width_mm: the physical display width in millimeters.
> +        @height_mm: the physical display height in millimeters.
> +        @xoff: horizontal offset, in pixels.
> +        @yoff: vertical offset, in pixels.
> +        @width: console width, in pixels.
> +        @height: console height, in pixels.
> +
> +        Modify the dimensions and display settings.
> +    -->
> +    <method name="SetUIInfo">
> +      <arg name="width_mm" type="q" direction="in"/>
> +      <arg name="height_mm" type="q" direction="in"/>
> +      <arg name="xoff" type="i" direction="in"/>
> +      <arg name="yoff" type="i" direction="in"/>
> +      <arg name="width" type="u" direction="in"/>
> +      <arg name="height" type="u" direction="in"/>
> +    </method>
> +
> +    <!--
> +        Label:
> +
> +        A user-friendly name for the console (for ex: "VGA").
> +    -->
> +    <property name="Label" type="s" access="read"/>
> +
> +    <!--
> +        Head:
> +
> +        Graphical device head number.
> +    -->
> +    <property name="Head" type="u" access="read"/>
> +
> +    <!--
> +        Type:
> +
> +        Console type ("Graphic" or "Text").
> +    -->
> +    <property name="Type" type="s" access="read"/>
> +
> +    <!--
> +        Width:
> +
> +        Console width, in pixels.
> +    -->
> +    <property name="Width" type="u" access="read"/>
> +
> +    <!--
> +        Height:
> +
> +        Console height, in pixels.
> +    -->
> +    <property name="Height" type="u" access="read"/>
> +
> +    <!--
> +        DeviceAddress:
> +
> +        The device address (ex: "pci/0000/02.0").
> +    -->
> +    <property name="DeviceAddress" type="s" access="read"/>
> +  </interface>
> +
> +  <!--
> +      org.qemu.Display1.Keyboard:
> +
> +      This interface in implemented on ``/org/qemu/Display1/Console_$id``
> (see
> +      :dbus:iface:`~org.qemu.Display1.Console`).
> +  -->
> +  <interface name="org.qemu.Display1.Keyboard">
> +    <!--
> +        Press:
> +        @keycode: QEMU key number (xtkbd + special re-encoding of high
> bit)
> +
> +        Send a key press event.
> +    -->
> +    <method name="Press">
> +      <arg type="u" name="keycode" direction="in"/>
> +    </method>
> +
> +    <!--
> +        Release:
> +        @keycode: QEMU key number (xtkbd + special re-encoding of high
> bit)
> +
> +        Send a key release event.
> +    -->
> +    <method name="Release">
> +      <arg type="u" name="keycode" direction="in"/>
> +    </method>
> +
> +    <!--
> +        Modifiers:
> +
> +        The active keyboard modifiers::
> +
> +          Scroll = 1 << 0
> +          Num    = 1 << 1
> +          Caps   = 1 << 2
> +    -->
> +    <property name="Modifiers" type="u" access="read"/>
> +  </interface>
> +
> +  <!--
> +      org.qemu.Display1.Mouse:
> +
> +      This interface in implemented on ``/org/qemu/Display1/Console_$id``
> (see
> +      :dbus:iface:`~org.qemu.Display1.Console` documentation).
> +
> +      .. _dbus-button-values:
> +
> +      **Button values**::
> +
> +        Left       = 0
> +        Middle     = 1
> +        Right      = 2
> +        Wheel-up   = 3
> +        Wheel-down = 4
> +        Side       = 5
> +        Extra      = 6
> +  -->
> +  <interface name="org.qemu.Display1.Mouse">
> +    <!--
> +        Press:
> +        @button: :ref:`button value<dbus-button-values>`.
> +
> +        Send a mouse button press event.
> +    -->
> +    <method name="Press">
> +      <arg type="u" name="button" direction="in"/>
> +    </method>
> +
> +    <!--
> +        Release:
> +        @button: :ref:`button value<dbus-button-values>`.
> +
> +        Send a mouse button release event.
> +    -->
> +    <method name="Release">
> +      <arg type="u" name="button" direction="in"/>
> +    </method>
> +
> +    <!--
> +        SetAbsPosition:
> +        @x: X position, in pixels.
> +        @y: Y position, in pixels.
> +
> +        Set the mouse pointer position.
> +
> +        Returns an error if not :dbus:prop:`IsAbsolute`.
> +    -->
> +    <method name="SetAbsPosition">
> +      <arg type="u" name="x" direction="in"/>
> +      <arg type="u" name="y" direction="in"/>
> +    </method>
> +
> +    <!--
> +        RelMotion:
> +        @dx: X-delta, in pixels.
> +        @dy: Y-delta, in pixels.
> +
> +        Move the mouse pointer position, relative to the current position.
> +
> +        Returns an error if :dbus:prop:`IsAbsolute`.
> +    -->
> +    <method name="RelMotion">
> +      <arg type="i" name="dx" direction="in"/>
> +      <arg type="i" name="dy" direction="in"/>
> +    </method>
> +
> +    <!--
> +        IsAbsolute:
> +
> +        Whether the mouse is using absolute movements.
> +    -->
> +    <property name="IsAbsolute" type="b" access="read"/>
> +  </interface>
> +
> +  <!--
> +      org.qemu.Display1.Listener:
> +
> +      This client-side interface must be available on
> +      ``/org/qemu/Display1/Listener`` when registering the peer-to-peer
> +      connection with :dbus:meth:`~org.qemu.Display1.Console.Register`.
> +  -->
> +  <interface name="org.qemu.Display1.Listener">
> +    <!--
> +        Scanout:
> +        @width: display width, in pixels.
> +        @height: display height, in pixels.
> +        @stride: data stride, in bytes.
> +        @pixman_format: image format (ex: ``PIXMAN_X8R8G8B8``).
> +        @data: image data.
> +
> +        Resize and update the display content.
> +
> +        The data to transfer for the display update may be large. The
> preferred
> +        scanout method is :dbus:meth:`ScanoutDMABUF`, used whenever
> possible.
> +    -->
> +    <method name="Scanout">
> +      <arg type="u" name="width" direction="in"/>
> +      <arg type="u" name="height" direction="in"/>
> +      <arg type="u" name="stride" direction="in"/>
> +      <arg type="u" name="pixman_format" direction="in"/>
> +      <arg type="ay" name="data" direction="in">
> +        <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
> +      </arg>
> +    </method>
> +
> +    <!--
> +        Update:
> +        @x: X update position, in pixels.
> +        @y: Y update position, in pixels.
> +        @width: update width, in pixels.
> +        @height: update height, in pixels.
> +        @stride: data stride, in bytes.
> +        @pixman_format: image format (ex: ``PIXMAN_X8R8G8B8``).
> +        @data: display image data.
> +
> +        Update the display content.
> +
> +        This method is only called after a :dbus:meth:`Scanout` call.
> +    -->
> +    <method name="Update">
> +      <arg type="i" name="x" direction="in"/>
> +      <arg type="i" name="y" direction="in"/>
> +      <arg type="i" name="width" direction="in"/>
> +      <arg type="i" name="height" direction="in"/>
> +      <arg type="u" name="stride" direction="in"/>
> +      <arg type="u" name="pixman_format" direction="in"/>
> +      <arg type="ay" name="data" direction="in">
> +        <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
> +      </arg>
> +    </method>
> +
> +    <!--
> +        ScanoutDMABUF:
> +        @dmabuf: the DMABUF file descriptor.
> +        @width: display width, in pixels.
> +        @height: display height, in pixels.
> +        @stride: stride, in bytes.
> +        @fourcc: DMABUF fourcc.
> +        @modifier: DMABUF modifier.
> +        @y0_top: whether Y position 0 is the top or not.
> +
> +        Resize and update the display content with a DMABUF.
> +    -->
> +    <method name="ScanoutDMABUF">
> +      <arg type="h" name="dmabuf" direction="in"/>
> +      <arg type="u" name="width" direction="in"/>
> +      <arg type="u" name="height" direction="in"/>
> +      <arg type="u" name="stride" direction="in"/>
> +      <arg type="u" name="fourcc" direction="in"/>
> +      <!-- xywh? -->
> +      <arg type="t" name="modifier" direction="in"/>
> +      <arg type="b" name="y0_top" direction="in"/>
> +    </method>
> +
> +    <!--
> +        UpdateDMABUF:
> +        @x: the X update position, in pixels.
> +        @y: the Y update position, in pixels.
> +        @width: the update width, in pixels.
> +        @height: the update height, in pixels.
> +
> +        Update the display content with the current DMABUF and the given
> region.
> +    -->
> +    <method name="UpdateDMABUF">
> +      <arg type="i" name="x" direction="in"/>
> +      <arg type="i" name="y" direction="in"/>
> +      <arg type="i" name="width" direction="in"/>
> +      <arg type="i" name="height" direction="in"/>
> +    </method>
> +
> +    <!--
> +        Disable:
> +
> +        Disable the display (turn it off).
> +    -->
> +    <method name="Disable">
> +    </method>
> +
> +    <!--
> +        MouseSet:
> +        @x: X mouse position, in pixels.
> +        @y: Y mouse position, in pixels.
> +        @on: whether the mouse is visible or not.
> +
> +        Set the mouse position and visibility.
> +    -->
> +    <method name="MouseSet">
> +      <arg type="i" name="x" direction="in"/>
> +      <arg type="i" name="y" direction="in"/>
> +      <arg type="i" name="on" direction="in"/>
> +    </method>
> +
> +    <!--
> +        CursorDefine:
> +        @width: cursor width, in pixels.
> +        @height: cursor height, in pixels.
> +        @hot_x: hot-spot X position, in pixels.
> +        @hot_y: hot-spot Y position, in pixels.
> +        @data: the cursor data.
> +
> +        Set the mouse cursor shape and hot-spot. The "data" must be ARGB,
> 32-bit
> +        per pixel.
> +    -->
> +    <method name="CursorDefine">
> +      <arg type="i" name="width" direction="in"/>
> +      <arg type="i" name="height" direction="in"/>
> +      <arg type="i" name="hot_x" direction="in"/>
> +      <arg type="i" name="hot_y" direction="in"/>
> +      <arg type="ay" name="data" direction="in">
> +        <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
> +      </arg>
> +    </method>
> +  </interface>
> +</node>
> diff --git a/ui/meson.build b/ui/meson.build
> index a9df5b911e..6270aa768b 100644
> --- a/ui/meson.build
> +++ b/ui/meson.build
> @@ -65,6 +65,28 @@ if config_host.has_key('CONFIG_OPENGL') and gbm.found()
>    ui_modules += {'egl-headless' : egl_headless_ss}
>  endif
>
> +if dbus_display
> +  dbus_ss = ss.source_set()
> +  dbus_display1 = custom_target('dbus-display gdbus-codegen',
> +                                output: ['dbus-display1.h',
> 'dbus-display1.c'],
> +                                input: files('dbus-display1.xml'),
> +                                command: [config_host['GDBUS_CODEGEN'],
> +                                          '@INPUT@',
> +                                          '--glib-min-required', '2.64',
> +                                          '--output-directory',
> meson.current_build_dir(),
> +                                          '--interface-prefix',
> 'org.qemu.',
> +                                          '--c-namespace', 'QemuDBus',
> +                                          '--generate-c-code', '@BASENAME@
> '])
> +  dbus_ss.add(when: [gio, pixman, opengl, 'CONFIG_GIO'],
> +              if_true: [files(
> +                'dbus-console.c',
> +                'dbus-error.c',
> +                'dbus-listener.c',
> +                'dbus.c',
> +              ), dbus_display1])
> +  ui_modules += {'dbus' : dbus_ss}
> +endif
> +
>  if gtk.found()
>    softmmu_ss.add(when: 'CONFIG_WIN32', if_true: files('win32-kbd-hook.c'))
>
> diff --git a/ui/trace-events b/ui/trace-events
> index e832c3e365..b1ae30159a 100644
> --- a/ui/trace-events
> +++ b/ui/trace-events
> @@ -136,3 +136,14 @@ vdagent_peer_cap(const char *name) "cap %s"
>  vdagent_cb_grab_selection(const char *name) "selection %s"
>  vdagent_cb_grab_type(const char *name) "type %s"
>  vdagent_cb_serial_discard(uint32_t current, uint32_t received)
> "current=%u, received=%u"
> +
> +# dbus.c
> +dbus_registered_listener(const char *bus_name) "peer %s"
> +dbus_listener_vanished(const char *bus_name) "peer %s"
> +dbus_kbd_press(unsigned int keycode) "keycode %u"
> +dbus_kbd_release(unsigned int keycode) "keycode %u"
> +dbus_mouse_press(unsigned int button) "button %u"
> +dbus_mouse_release(unsigned int button) "button %u"
> +dbus_mouse_set_pos(unsigned int x, unsigned int y) "x=%u, y=%u"
> +dbus_mouse_rel_motion(int dx, int dy) "dx=%d, dy=%d"
> +dbus_update(int x, int y, int w, int h) "x=%d, y=%d, w=%d, h=%d"
> --
> 2.33.0.721.g106298f7f9
>
>
>

-- 
Marc-André Lureau

[-- Attachment #2: Type: text/html, Size: 90832 bytes --]

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v2 00/37] Add D-Bus display backend
  2021-10-13  5:22 ` [PATCH v2 00/37] Add D-Bus display backend Gerd Hoffmann
@ 2021-12-16 20:53   ` Marc-André Lureau
  2021-12-17  7:05     ` Gerd Hoffmann
  0 siblings, 1 reply; 54+ messages in thread
From: Marc-André Lureau @ 2021-12-16 20:53 UTC (permalink / raw)
  To: Gerd Hoffmann; +Cc: QEMU

[-- Attachment #1: Type: text/plain, Size: 3289 bytes --]

Hi

On Wed, Oct 13, 2021 at 9:23 AM Gerd Hoffmann <kraxel@redhat.com> wrote:

> On Sun, Oct 10, 2021 at 01:08:01AM +0400, marcandre.lureau@redhat.com
> wrote:
> > From: Marc-André Lureau <marcandre.lureau@redhat.com>
> >
> > Hi,
> >
> > Both Spice and VNC are relatively complex and inefficient for local-only
> > display/console export.
> >
> > The goal of this display backend is to export over D-Bus an interface
> close to
> > the QEMU internal APIs. Any -display or -audio backend should be
> possible to
> > implement externally that way. It will allow third-parties to maintain
> their own
> > backends (UI toolkits, servers etc), and eventually reduce the
> responsability on
> > QEMU.
> >
> > D-Bus is the protocol of choice for the desktop, it has many convenient
> bindings
> > for various languages and tools. Data blob transfer is more efficient
> than QMP
> > too. Backends can come and go as needed: you can have several display
> opened
> > (say Boxes & virt-manager), while exporting the display over VNC for
> example
> > from a different process. It works best on Unix, but there is some
> Windows
> > support too (even Windows has some AF_UNIX nowadays, and the WSL2
> situation may
> > change the future of QEMU on Windows anyway).
> >
> > Using it only requires "-display dbus" on any reasonable Linux desktop
> with a
> > D-Bus session bus. Then you use can use busctl, d-feet or gdbus, ex:
> > $ gdbus introspect --session -r -d org.qemu -o /
> >
> > See the different patches and documentation for further options. The
> p2p=on mode
> > should also allow users running bus-less (on MacOS for ex). We can also
> add TCP
> > socket if needed (although more work would be needed in this case to
> replace
> > the FD-passing with some extra TCP listening socket).
>
> Wow.  That series got a lot of fine tuning.  The patches look all good
> to me.
>
> Acked-by: Gerd Hoffmann <kraxel@redhat.com>
>

I have rebased the series and added your acked-by (
https://gitlab.com/marcandre.lureau/qemu/-/tree/dbus)

The client side is still in development. However, the libvirt series is in
shape (
https://patchew.org/Libvirt/20211202142411.1718032-1-marcandre.lureau@redhat.com/),
waiting for QEMU side to land.

Should I make a PR or ask for more reviews? Since this is fairly long to
review but quite independent from existing code, I think sending a PR is
reasonable wdyt?


> > A WIP Rust/Gtk4 client and VNC server is:
> https://gitlab.com/marcandre.lureau/qemu-display/
> > (check README.md for details, then `cargo run` should connect to QEMU)
>
> Hmm, that wants rather cutting edge versions, stock Fedora 34 isn't new
> enough to build it.  And I don't feel like updating to Fedora 35 beta
> for that.  So unfortunately I couldn't easily test it, but I'd love to
> see that live in action.
>
> Is it possible to keep the client running while starting and stopping
> qemu (comparable to "virt-viewer --wait --reconnect" behaviour)?
>
>
That's doable. I just added a --wait option to the demo client.

For reconnect to work, we would need a better way to track name ownership
changes (connection and disconnections) in zbus. I am working on it.

thanks

-- 
Marc-André Lureau

[-- Attachment #2: Type: text/html, Size: 4594 bytes --]

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v2 00/37] Add D-Bus display backend
  2021-12-16 20:53   ` Marc-André Lureau
@ 2021-12-17  7:05     ` Gerd Hoffmann
  0 siblings, 0 replies; 54+ messages in thread
From: Gerd Hoffmann @ 2021-12-17  7:05 UTC (permalink / raw)
  To: Marc-André Lureau; +Cc: QEMU

  Hi,

> I have rebased the series and added your acked-by (
> https://gitlab.com/marcandre.lureau/qemu/-/tree/dbus)
> 
> The client side is still in development. However, the libvirt series is in
> shape (
> https://patchew.org/Libvirt/20211202142411.1718032-1-marcandre.lureau@redhat.com/),
> waiting for QEMU side to land.
> 
> Should I make a PR or ask for more reviews? Since this is fairly long to
> review but quite independent from existing code, I think sending a PR is
> reasonable wdyt?

No objections.  Unlikely to break something, also we are early
in the devel cycle so plenty of time to find & fix bugs before
this lands in a release.

> For reconnect to work, we would need a better way to track name ownership
> changes (connection and disconnections) in zbus. I am working on it.

zbus == compressed dbus ?

take care,
  Gerd



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v2 08/37] hw/display: report an error if virgl initialization failed
  2021-10-09 21:08 ` [PATCH v2 08/37] hw/display: report an error if virgl initialization failed marcandre.lureau
@ 2021-12-17 12:40   ` Philippe Mathieu-Daudé
  0 siblings, 0 replies; 54+ messages in thread
From: Philippe Mathieu-Daudé @ 2021-12-17 12:40 UTC (permalink / raw)
  To: marcandre.lureau, qemu-devel; +Cc: kraxel

On 10/9/21 23:08, marcandre.lureau@redhat.com wrote:
> From: Marc-André Lureau <marcandre.lureau@redhat.com>
> 
> Currently, virgl initialization error is silent. Make it verbose instead.
> 
> (this is likely going to bug later on, as the device isn't fully
> initialized)
> 
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
>  hw/display/virtio-gpu-virgl.c | 1 +
>  1 file changed, 1 insertion(+)

Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v2 09/37] virtio-gpu: use VIRTIO_GPU_RESOURCE_FLAG_Y_0_TOP
  2021-10-09 21:08 ` [PATCH v2 09/37] virtio-gpu: use VIRTIO_GPU_RESOURCE_FLAG_Y_0_TOP marcandre.lureau
@ 2021-12-17 12:51   ` Philippe Mathieu-Daudé
  0 siblings, 0 replies; 54+ messages in thread
From: Philippe Mathieu-Daudé @ 2021-12-17 12:51 UTC (permalink / raw)
  To: marcandre.lureau, qemu-devel; +Cc: kraxel

On 10/9/21 23:08, marcandre.lureau@redhat.com wrote:
> From: Marc-André Lureau <marcandre.lureau@redhat.com>
> 
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
>  hw/display/virtio-gpu-virgl.c | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)

Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v2 25/37] build-sys: set glib dependency version
  2021-10-09 21:08 ` [PATCH v2 25/37] build-sys: set glib dependency version marcandre.lureau
@ 2021-12-17 13:27   ` Philippe Mathieu-Daudé
  2021-12-17 13:40     ` Marc-André Lureau
  0 siblings, 1 reply; 54+ messages in thread
From: Philippe Mathieu-Daudé @ 2021-12-17 13:27 UTC (permalink / raw)
  To: marcandre.lureau, qemu-devel; +Cc: kraxel

On 10/9/21 23:08, marcandre.lureau@redhat.com wrote:
> From: Marc-André Lureau <marcandre.lureau@redhat.com>
> 
> Further meson configuration tests are to be added based on the glib
> version. Also correct the version reporting in the config log.
> 
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
>  configure   | 1 +
>  meson.build | 6 ++++--
>  2 files changed, 5 insertions(+), 2 deletions(-)
> 
> diff --git a/configure b/configure
> index 375cde2b44..57e363fd73 100755
> --- a/configure
> +++ b/configure
> @@ -4889,6 +4889,7 @@ echo "QEMU_CFLAGS=$QEMU_CFLAGS" >> $config_host_mak
>  echo "QEMU_CXXFLAGS=$QEMU_CXXFLAGS" >> $config_host_mak
>  echo "GLIB_CFLAGS=$glib_cflags" >> $config_host_mak
>  echo "GLIB_LIBS=$glib_libs" >> $config_host_mak
> +echo "GLIB_VERSION=$(pkg-config --modversion glib-2.0)" >> $config_host_mak
>  echo "QEMU_LDFLAGS=$QEMU_LDFLAGS" >> $config_host_mak
>  echo "LD_I386_EMULATION=$ld_i386_emulation" >> $config_host_mak
>  echo "EXESUF=$EXESUF" >> $config_host_mak
> diff --git a/meson.build b/meson.build
> index e1cddf5139..9494590aa2 100644
> --- a/meson.build
> +++ b/meson.build
> @@ -311,14 +311,16 @@ endif
>  add_project_arguments(config_host['GLIB_CFLAGS'].split(),
>                        native: false, language: ['c', 'cpp', 'objc'])
>  glib = declare_dependency(compile_args: config_host['GLIB_CFLAGS'].split(),
> -                          link_args: config_host['GLIB_LIBS'].split())
> +                          link_args: config_host['GLIB_LIBS'].split(),
> +                          version: config_host['GLIB_VERSION'])
>  # override glib dep with the configure results (for subprojects)
>  meson.override_dependency('glib-2.0', glib)
>  
>  gio = not_found
>  if 'CONFIG_GIO' in config_host
>    gio = declare_dependency(compile_args: config_host['GIO_CFLAGS'].split(),
> -                           link_args: config_host['GIO_LIBS'].split())
> +                           link_args: config_host['GIO_LIBS'].split(),
> +                           version: config_host['GLIB_VERSION'])
>  endif
>  lttng = not_found
>  if 'CONFIG_TRACE_UST' in config_host
> 

Can you display it in summary_info too?

Otherwise:
Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v2 33/37] chardev: make socket derivable
  2021-10-09 21:08 ` [PATCH v2 33/37] chardev: make socket derivable marcandre.lureau
@ 2021-12-17 13:32   ` Philippe Mathieu-Daudé
  0 siblings, 0 replies; 54+ messages in thread
From: Philippe Mathieu-Daudé @ 2021-12-17 13:32 UTC (permalink / raw)
  To: marcandre.lureau, qemu-devel; +Cc: kraxel

On 10/9/21 23:08, marcandre.lureau@redhat.com wrote:
> From: Marc-André Lureau <marcandre.lureau@redhat.com>
> 
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
>  include/chardev/char-socket.h | 84 +++++++++++++++++++++++++++++++++++
>  chardev/char-socket.c         | 58 +-----------------------
>  2 files changed, 85 insertions(+), 57 deletions(-)
>  create mode 100644 include/chardev/char-socket.h

Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v2 36/37] ui/dbus: register D-Bus VC handler
  2021-10-09 21:08 ` [PATCH v2 36/37] ui/dbus: register D-Bus VC handler marcandre.lureau
@ 2021-12-17 13:35   ` Philippe Mathieu-Daudé
  2021-12-17 14:21     ` Marc-André Lureau
  0 siblings, 1 reply; 54+ messages in thread
From: Philippe Mathieu-Daudé @ 2021-12-17 13:35 UTC (permalink / raw)
  To: marcandre.lureau, qemu-devel; +Cc: kraxel

On 10/9/21 23:08, marcandre.lureau@redhat.com wrote:
> From: Marc-André Lureau <marcandre.lureau@redhat.com>
> 
> Export the default consoles over the D-Bus chardev.
> 
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
>  ui/dbus.c | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 53 insertions(+)

> +static void
> +dbus_vc_parse(QemuOpts *opts, ChardevBackend *backend,
> +              Error **errp)
> +{
> +    DBusVCClass *klass = DBUS_VC_CLASS(object_class_by_name(TYPE_CHARDEV_VC));
> +    const char *name = qemu_opt_get(opts, "name");
> +    const char *id = qemu_opts_id(opts);
> +
> +    if (name == NULL) {
> +        name = "";

Could also drop this assignation, and:

> +        if (g_str_has_prefix(id, "compat_monitor")) {
> +            name = "org.qemu.monitor.hmp.0";
> +        }

           else

> +        if (g_str_has_prefix(id, "serial")) {
> +            name = "org.qemu.console.serial.0";
> +        }

           else {
              name = "";
           }

> +        if (!qemu_opt_set(opts, "name", name, errp)) {
> +            return;
> +        }
> +    }
> +
> +    klass->parent_parse(opts, backend, errp);
> +}

Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v2 11/37] ui: factor out qemu_console_set_display_gl_ctx()
  2021-10-09 21:08 ` [PATCH v2 11/37] ui: factor out qemu_console_set_display_gl_ctx() marcandre.lureau
@ 2021-12-17 13:36   ` Philippe Mathieu-Daudé
  0 siblings, 0 replies; 54+ messages in thread
From: Philippe Mathieu-Daudé @ 2021-12-17 13:36 UTC (permalink / raw)
  To: marcandre.lureau, qemu-devel; +Cc: kraxel

On 10/9/21 23:08, marcandre.lureau@redhat.com wrote:
> From: Marc-André Lureau <marcandre.lureau@redhat.com>
> 
> The next patch will make use of this function to dissociate
> DisplayChangeListener from GL context.
> 
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
>  include/ui/console.h |  3 +++
>  ui/console.c         | 22 ++++++++++++++--------
>  2 files changed, 17 insertions(+), 8 deletions(-)

Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v2 25/37] build-sys: set glib dependency version
  2021-12-17 13:27   ` Philippe Mathieu-Daudé
@ 2021-12-17 13:40     ` Marc-André Lureau
  2021-12-17 14:36       ` Philippe Mathieu-Daudé
  0 siblings, 1 reply; 54+ messages in thread
From: Marc-André Lureau @ 2021-12-17 13:40 UTC (permalink / raw)
  To: Philippe Mathieu-Daudé; +Cc: QEMU, Gerd Hoffmann

[-- Attachment #1: Type: text/plain, Size: 2882 bytes --]

Hi

On Fri, Dec 17, 2021 at 5:31 PM Philippe Mathieu-Daudé <philmd@redhat.com>
wrote:

> On 10/9/21 23:08, marcandre.lureau@redhat.com wrote:
> > From: Marc-André Lureau <marcandre.lureau@redhat.com>
> >
> > Further meson configuration tests are to be added based on the glib
> > version. Also correct the version reporting in the config log.
> >
> > Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> > ---
> >  configure   | 1 +
> >  meson.build | 6 ++++--
> >  2 files changed, 5 insertions(+), 2 deletions(-)
> >
> > diff --git a/configure b/configure
> > index 375cde2b44..57e363fd73 100755
> > --- a/configure
> > +++ b/configure
> > @@ -4889,6 +4889,7 @@ echo "QEMU_CFLAGS=$QEMU_CFLAGS" >> $config_host_mak
> >  echo "QEMU_CXXFLAGS=$QEMU_CXXFLAGS" >> $config_host_mak
> >  echo "GLIB_CFLAGS=$glib_cflags" >> $config_host_mak
> >  echo "GLIB_LIBS=$glib_libs" >> $config_host_mak
> > +echo "GLIB_VERSION=$(pkg-config --modversion glib-2.0)" >>
> $config_host_mak
> >  echo "QEMU_LDFLAGS=$QEMU_LDFLAGS" >> $config_host_mak
> >  echo "LD_I386_EMULATION=$ld_i386_emulation" >> $config_host_mak
> >  echo "EXESUF=$EXESUF" >> $config_host_mak
> > diff --git a/meson.build b/meson.build
> > index e1cddf5139..9494590aa2 100644
> > --- a/meson.build
> > +++ b/meson.build
> > @@ -311,14 +311,16 @@ endif
> >  add_project_arguments(config_host['GLIB_CFLAGS'].split(),
> >                        native: false, language: ['c', 'cpp', 'objc'])
> >  glib = declare_dependency(compile_args:
> config_host['GLIB_CFLAGS'].split(),
> > -                          link_args: config_host['GLIB_LIBS'].split())
> > +                          link_args: config_host['GLIB_LIBS'].split(),
> > +                          version: config_host['GLIB_VERSION'])
> >  # override glib dep with the configure results (for subprojects)
> >  meson.override_dependency('glib-2.0', glib)
> >
> >  gio = not_found
> >  if 'CONFIG_GIO' in config_host
> >    gio = declare_dependency(compile_args:
> config_host['GIO_CFLAGS'].split(),
> > -                           link_args: config_host['GIO_LIBS'].split())
> > +                           link_args: config_host['GIO_LIBS'].split(),
> > +                           version: config_host['GLIB_VERSION'])
> >  endif
> >  lttng = not_found
> >  if 'CONFIG_TRACE_UST' in config_host
> >
>
> Can you display it in summary_info too?
>

Yeah, although it would need a special treatment.

Since GLib is a mandatory dependency, I am not sure we want to show
    GLib                         : YES

And because it's not a pkg-config dep, it doesn't show the version (could
probably be fixed in meson).

If you don't mind,  I leave that for another day :)




> Otherwise:
> Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
>
>
>
thanks

-- 
Marc-André Lureau

[-- Attachment #2: Type: text/html, Size: 4620 bytes --]

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v2 36/37] ui/dbus: register D-Bus VC handler
  2021-12-17 13:35   ` Philippe Mathieu-Daudé
@ 2021-12-17 14:21     ` Marc-André Lureau
  0 siblings, 0 replies; 54+ messages in thread
From: Marc-André Lureau @ 2021-12-17 14:21 UTC (permalink / raw)
  To: Philippe Mathieu-Daudé; +Cc: qemu-devel, Hoffmann, Gerd

On Fri, Dec 17, 2021 at 5:35 PM Philippe Mathieu-Daudé
<philmd@redhat.com> wrote:
>
> On 10/9/21 23:08, marcandre.lureau@redhat.com wrote:
> > From: Marc-André Lureau <marcandre.lureau@redhat.com>
> >
> > Export the default consoles over the D-Bus chardev.
> >
> > Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> > ---
> >  ui/dbus.c | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++
> >  1 file changed, 53 insertions(+)
>
> > +static void
> > +dbus_vc_parse(QemuOpts *opts, ChardevBackend *backend,
> > +              Error **errp)
> > +{
> > +    DBusVCClass *klass = DBUS_VC_CLASS(object_class_by_name(TYPE_CHARDEV_VC));
> > +    const char *name = qemu_opt_get(opts, "name");
> > +    const char *id = qemu_opts_id(opts);
> > +
> > +    if (name == NULL) {
> > +        name = "";
>
> Could also drop this assignation, and:
>
> > +        if (g_str_has_prefix(id, "compat_monitor")) {
> > +            name = "org.qemu.monitor.hmp.0";
> > +        }
>
>            else
>
> > +        if (g_str_has_prefix(id, "serial")) {
> > +            name = "org.qemu.console.serial.0";
> > +        }
>
>            else {
>               name = "";
>            }
>

done

> > +        if (!qemu_opt_set(opts, "name", name, errp)) {
> > +            return;
> > +        }
> > +    }
> > +
> > +    klass->parent_parse(opts, backend, errp);
> > +}
>
> Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
>

thanks



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v2 25/37] build-sys: set glib dependency version
  2021-12-17 13:40     ` Marc-André Lureau
@ 2021-12-17 14:36       ` Philippe Mathieu-Daudé
  0 siblings, 0 replies; 54+ messages in thread
From: Philippe Mathieu-Daudé @ 2021-12-17 14:36 UTC (permalink / raw)
  To: Marc-André Lureau; +Cc: QEMU, Gerd Hoffmann

On 12/17/21 14:40, Marc-André Lureau wrote:

>     > diff --git a/meson.build b/meson.build
>     > index e1cddf5139..9494590aa2 100644
>     > --- a/meson.build
>     > +++ b/meson.build
>     > @@ -311,14 +311,16 @@ endif
>     >  add_project_arguments(config_host['GLIB_CFLAGS'].split(),
>     >                        native: false, language: ['c', 'cpp', 'objc'])
>     >  glib = declare_dependency(compile_args:
>     config_host['GLIB_CFLAGS'].split(),
>     > -                          link_args:
>     config_host['GLIB_LIBS'].split())
>     > +                          link_args:
>     config_host['GLIB_LIBS'].split(),
>     > +                          version: config_host['GLIB_VERSION'])
>     >  # override glib dep with the configure results (for subprojects)
>     >  meson.override_dependency('glib-2.0', glib)
>     > 
>     >  gio = not_found
>     >  if 'CONFIG_GIO' in config_host
>     >    gio = declare_dependency(compile_args:
>     config_host['GIO_CFLAGS'].split(),
>     > -                           link_args:
>     config_host['GIO_LIBS'].split())
>     > +                           link_args:
>     config_host['GIO_LIBS'].split(),
>     > +                           version: config_host['GLIB_VERSION'])
>     >  endif
>     >  lttng = not_found
>     >  if 'CONFIG_TRACE_UST' in config_host
>     >
> 
>     Can you display it in summary_info too?
> 
> 
> Yeah, although it would need a special treatment.
> 
> Since GLib is a mandatory dependency, I am not sure we want to show
>     GLib                         : YES
> 
> And because it's not a pkg-config dep, it doesn't show the version
> (could probably be fixed in meson).

Ah OK. Yes, I was thinking about displaying the GLib version.

> If you don't mind,  I leave that for another day :)

Sounds good, thanks.



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v2 19/37] console: save current scanout details
  2021-10-09 21:08 ` [PATCH v2 19/37] console: save current scanout details marcandre.lureau
@ 2022-01-11  3:29   ` Akihiko Odaki
  2022-01-11  8:23     ` Marc-André Lureau
  0 siblings, 1 reply; 54+ messages in thread
From: Akihiko Odaki @ 2022-01-11  3:29 UTC (permalink / raw)
  To: marcandre.lureau, qemu-devel; +Cc: kraxel

Hi,

I found this brings an inconsistency and a flaw to scanout semantics and 
think the inconsistency should be fixed or this should be reverted 
before the next release comes up.

The inconsistency is in the handling of the console size. A guest 
hardware (especially I'm looking at virtio-gpu-virgl) tells the size 
change with qemu_console_resize. It causes the replacement of the 
surface, and the host display sees the change of the size via the 
surface. The replacement of the surface does *not* mean the surface 
should be scanned out; if an OpenGL texture is already provided, the 
host display should scan out it, not the replaced surface. 
dpy_gl_scanout_disable will be called when the surface becomes the 
source of the scanouts.

However, this change brings some contradicting behaviors.
- qemu_console_get_width and qemu_console_get_height now relies on the 
texture size as the source of the console size while the resize is 
delivered via the surface.
- dpy_gfx_replace_surface makes the surface as the source of the 
scanouts while its guest hardware semantics does not mean that.
- dpy_gl_scanout_disable sets the scanout kind to SCANOUT_NONE while it 
actually means the surface is now the source of the scanout.

Besides that, displaychangelistener_display_console has a flaw that it 
does not tell the switch to a console with SCANOUT_NONE. The intention 
of SCANOUT_NONE is not entirely clear.

I think there are two options to fix the problem except reverting:
- Rework this change to make it consistent with the existing semantics.
- Remove the use of qemu_console_resize and dpy_gl_scanout_disable from
   hardwares providing OpenGL textures or DMA-BUF to make it consistent
   with the new semantics.

Regards,
Akihiko Odaki

On 2021/10/10 6:08, marcandre.lureau@redhat.com wrote:
> From: Marc-André Lureau <marcandre.lureau@redhat.com>
> 
> Add a new DisplayScanout structure to save the current scanout details.
> This allows to attach later UI backends and set the scanout.
> 
> Introduce displaychangelistener_display_console() helper function to
> handle the dpy_gfx_switch/gl_scanout() & dpy_gfx_update() calls.
> 
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
>   include/ui/console.h |  27 +++++++
>   ui/console.c         | 165 +++++++++++++++++++++++++++++--------------
>   2 files changed, 138 insertions(+), 54 deletions(-)
> 
> diff --git a/include/ui/console.h b/include/ui/console.h
> index b23ae283be..ab55d71894 100644
> --- a/include/ui/console.h
> +++ b/include/ui/console.h
> @@ -108,6 +108,17 @@ struct QemuConsoleClass {
>   #define QEMU_ALLOCATED_FLAG     0x01
>   #define QEMU_PLACEHOLDER_FLAG   0x02
>   
> +typedef struct ScanoutTexture {
> +    uint32_t backing_id;
> +    bool backing_y_0_top;
> +    uint32_t backing_width;
> +    uint32_t backing_height;
> +    uint32_t x;
> +    uint32_t y;
> +    uint32_t width;
> +    uint32_t height;
> +} ScanoutTexture;
> +
>   typedef struct DisplaySurface {
>       pixman_format_code_t format;
>       pixman_image_t *image;
> @@ -173,6 +184,22 @@ typedef struct QemuDmaBuf {
>       bool      allow_fences;
>   } QemuDmaBuf;
>   
> +enum display_scanout {
> +    SCANOUT_NONE,
> +    SCANOUT_SURFACE,
> +    SCANOUT_TEXTURE,
> +    SCANOUT_DMABUF,
> +};
> +
> +typedef struct DisplayScanout {
> +    enum display_scanout kind;
> +    union {
> +        /* DisplaySurface *surface; is kept in QemuConsole */
> +        ScanoutTexture texture;
> +        QemuDmaBuf *dmabuf;
> +    };
> +} DisplayScanout;
> +
>   typedef struct DisplayState DisplayState;
>   typedef struct DisplayGLCtx DisplayGLCtx;
>   
> diff --git a/ui/console.c b/ui/console.c
> index e5a2c84dd9..a1c6a78523 100644
> --- a/ui/console.c
> +++ b/ui/console.c
> @@ -126,6 +126,7 @@ struct QemuConsole {
>       console_type_t console_type;
>       DisplayState *ds;
>       DisplaySurface *surface;
> +    DisplayScanout scanout;
>       int dcls;
>       DisplayGLCtx *gl;
>       int gl_block;
> @@ -197,6 +198,7 @@ static void dpy_refresh(DisplayState *s);
>   static DisplayState *get_alloc_displaystate(void);
>   static void text_console_update_cursor_timer(void);
>   static void text_console_update_cursor(void *opaque);
> +static bool displaychangelistener_has_dmabuf(DisplayChangeListener *dcl);
>   
>   static void gui_update(void *opaque)
>   {
> @@ -532,6 +534,8 @@ static void text_console_resize(QemuConsole *s)
>       TextCell *cells, *c, *c1;
>       int w1, x, y, last_width;
>   
> +    assert(s->scanout.kind == SCANOUT_SURFACE);
> +
>       last_width = s->width;
>       s->width = surface_width(s->surface) / FONT_WIDTH;
>       s->height = surface_height(s->surface) / FONT_HEIGHT;
> @@ -1103,6 +1107,48 @@ static void console_putchar(QemuConsole *s, int ch)
>       }
>   }
>   
> +static void displaychangelistener_display_console(DisplayChangeListener *dcl,
> +                                                  QemuConsole *con)
> +{
> +    static const char nodev[] =
> +        "This VM has no graphic display device.";
> +    static DisplaySurface *dummy;
> +
> +    if (!con) {
> +        if (!dcl->ops->dpy_gfx_switch) {
> +            return;
> +        }
> +        if (!dummy) {
> +            dummy = qemu_create_placeholder_surface(640, 480, nodev);
> +        }
> +        dcl->ops->dpy_gfx_switch(dcl, dummy);
> +        return;
> +    }
> +
> +    if (con->scanout.kind == SCANOUT_DMABUF &&
> +        displaychangelistener_has_dmabuf(dcl)) {
> +        dcl->ops->dpy_gl_scanout_dmabuf(dcl, con->scanout.dmabuf);
> +    } else if (con->scanout.kind == SCANOUT_TEXTURE &&
> +               dcl->ops->dpy_gl_scanout_texture) {
> +        dcl->ops->dpy_gl_scanout_texture(dcl,
> +                                         con->scanout.texture.backing_id,
> +                                         con->scanout.texture.backing_y_0_top,
> +                                         con->scanout.texture.backing_width,
> +                                         con->scanout.texture.backing_height,
> +                                         con->scanout.texture.x,
> +                                         con->scanout.texture.y,
> +                                         con->scanout.texture.width,
> +                                         con->scanout.texture.height);
> +    } else if (con->scanout.kind == SCANOUT_SURFACE &&
> +               dcl->ops->dpy_gfx_switch) {
> +        dcl->ops->dpy_gfx_switch(dcl, con->surface);
> +    }
> +
> +    dcl->ops->dpy_gfx_update(dcl, 0, 0,
> +                             qemu_console_get_width(con, 0),
> +                             qemu_console_get_height(con, 0));
> +}
> +
>   void console_select(unsigned int index)
>   {
>       DisplayChangeListener *dcl;
> @@ -1119,13 +1165,7 @@ void console_select(unsigned int index)
>                   if (dcl->con != NULL) {
>                       continue;
>                   }
> -                if (dcl->ops->dpy_gfx_switch) {
> -                    dcl->ops->dpy_gfx_switch(dcl, s->surface);
> -                }
> -            }
> -            if (s->surface) {
> -                dpy_gfx_update(s, 0, 0, surface_width(s->surface),
> -                               surface_height(s->surface));
> +                displaychangelistener_display_console(dcl, s);
>               }
>           }
>           if (ds->have_text) {
> @@ -1538,9 +1578,6 @@ static bool dpy_gl_compatible_with(QemuConsole *con, DisplayChangeListener *dcl)
>   
>   void register_displaychangelistener(DisplayChangeListener *dcl)
>   {
> -    static const char nodev[] =
> -        "This VM has no graphic display device.";
> -    static DisplaySurface *dummy;
>       QemuConsole *con;
>   
>       assert(!dcl->ds);
> @@ -1565,16 +1602,7 @@ void register_displaychangelistener(DisplayChangeListener *dcl)
>       } else {
>           con = active_console;
>       }
> -    if (dcl->ops->dpy_gfx_switch) {
> -        if (con) {
> -            dcl->ops->dpy_gfx_switch(dcl, con->surface);
> -        } else {
> -            if (!dummy) {
> -                dummy = qemu_create_placeholder_surface(640, 480, nodev);
> -            }
> -            dcl->ops->dpy_gfx_switch(dcl, dummy);
> -        }
> -    }
> +    displaychangelistener_display_console(dcl, con);
>       text_console_update_cursor(NULL);
>   }
>   
> @@ -1655,13 +1683,9 @@ void dpy_gfx_update(QemuConsole *con, int x, int y, int w, int h)
>   {
>       DisplayState *s = con->ds;
>       DisplayChangeListener *dcl;
> -    int width = w;
> -    int height = h;
> +    int width = qemu_console_get_width(con, x + w);
> +    int height = qemu_console_get_height(con, y + h);
>   
> -    if (con->surface) {
> -        width = surface_width(con->surface);
> -        height = surface_height(con->surface);
> -    }
>       x = MAX(x, 0);
>       y = MAX(y, 0);
>       x = MIN(x, width);
> @@ -1684,12 +1708,10 @@ void dpy_gfx_update(QemuConsole *con, int x, int y, int w, int h)
>   
>   void dpy_gfx_update_full(QemuConsole *con)
>   {
> -    if (!con->surface) {
> -        return;
> -    }
> -    dpy_gfx_update(con, 0, 0,
> -                   surface_width(con->surface),
> -                   surface_height(con->surface));
> +    int w = qemu_console_get_width(con, 0);
> +    int h = qemu_console_get_height(con, 0);
> +
> +    dpy_gfx_update(con, 0, 0, w, h);
>   }
>   
>   void dpy_gfx_replace_surface(QemuConsole *con,
> @@ -1716,6 +1738,7 @@ void dpy_gfx_replace_surface(QemuConsole *con,
>   
>       assert(old_surface != surface);
>   
> +    con->scanout.kind = SCANOUT_SURFACE;
>       con->surface = surface;
>       QLIST_FOREACH(dcl, &s->listeners, next) {
>           if (con != (dcl->con ? dcl->con : active_console)) {
> @@ -1891,6 +1914,9 @@ void dpy_gl_scanout_disable(QemuConsole *con)
>       DisplayState *s = con->ds;
>       DisplayChangeListener *dcl;
>   
> +    if (con->scanout.kind != SCANOUT_SURFACE) {
> +        con->scanout.kind = SCANOUT_NONE;
> +    }
>       QLIST_FOREACH(dcl, &s->listeners, next) {
>           dcl->ops->dpy_gl_scanout_disable(dcl);
>       }
> @@ -1907,6 +1933,11 @@ void dpy_gl_scanout_texture(QemuConsole *con,
>       DisplayState *s = con->ds;
>       DisplayChangeListener *dcl;
>   
> +    con->scanout.kind = SCANOUT_TEXTURE;
> +    con->scanout.texture = (ScanoutTexture) {
> +        backing_id, backing_y_0_top, backing_width, backing_height,
> +        x, y, width, height
> +    };
>       QLIST_FOREACH(dcl, &s->listeners, next) {
>           dcl->ops->dpy_gl_scanout_texture(dcl, backing_id,
>                                            backing_y_0_top,
> @@ -1921,6 +1952,8 @@ void dpy_gl_scanout_dmabuf(QemuConsole *con,
>       DisplayState *s = con->ds;
>       DisplayChangeListener *dcl;
>   
> +    con->scanout.kind = SCANOUT_DMABUF;
> +    con->scanout.dmabuf = dmabuf;
>       QLIST_FOREACH(dcl, &s->listeners, next) {
>           dcl->ops->dpy_gl_scanout_dmabuf(dcl, dmabuf);
>       }
> @@ -2047,10 +2080,8 @@ QemuConsole *graphic_console_init(DeviceState *dev, uint32_t head,
>       s = qemu_console_lookup_unused();
>       if (s) {
>           trace_console_gfx_reuse(s->index);
> -        if (s->surface) {
> -            width = surface_width(s->surface);
> -            height = surface_height(s->surface);
> -        }
> +        width = qemu_console_get_width(s, 0);
> +        height = qemu_console_get_height(s, 0);
>       } else {
>           trace_console_gfx_new();
>           s = new_console(ds, GRAPHIC_CONSOLE, head);
> @@ -2079,13 +2110,8 @@ void graphic_console_close(QemuConsole *con)
>       static const char unplugged[] =
>           "Guest display has been unplugged";
>       DisplaySurface *surface;
> -    int width = 640;
> -    int height = 480;
> -
> -    if (con->surface) {
> -        width = surface_width(con->surface);
> -        height = surface_height(con->surface);
> -    }
> +    int width = qemu_console_get_width(con, 640);
> +    int height = qemu_console_get_height(con, 480);
>   
>       trace_console_gfx_close(con->index);
>       object_property_set_link(OBJECT(con), "device", NULL, &error_abort);
> @@ -2237,7 +2263,19 @@ int qemu_console_get_width(QemuConsole *con, int fallback)
>       if (con == NULL) {
>           con = active_console;
>       }
> -    return con ? surface_width(con->surface) : fallback;
> +    if (con == NULL) {
> +        return fallback;
> +    }
> +    switch (con->scanout.kind) {
> +    case SCANOUT_DMABUF:
> +        return con->scanout.dmabuf->width;
> +    case SCANOUT_TEXTURE:
> +        return con->scanout.texture.width;
> +    case SCANOUT_SURFACE:
> +        return surface_width(con->surface);
> +    default:
> +        return fallback;
> +    }
>   }
>   
>   int qemu_console_get_height(QemuConsole *con, int fallback)
> @@ -2245,7 +2283,19 @@ int qemu_console_get_height(QemuConsole *con, int fallback)
>       if (con == NULL) {
>           con = active_console;
>       }
> -    return con ? surface_height(con->surface) : fallback;
> +    if (con == NULL) {
> +        return fallback;
> +    }
> +    switch (con->scanout.kind) {
> +    case SCANOUT_DMABUF:
> +        return con->scanout.dmabuf->height;
> +    case SCANOUT_TEXTURE:
> +        return con->scanout.texture.height;
> +    case SCANOUT_SURFACE:
> +        return surface_height(con->surface);
> +    default:
> +        return fallback;
> +    }
>   }
>   
>   static void vc_chr_set_echo(Chardev *chr, bool echo)
> @@ -2305,12 +2355,13 @@ static void text_console_do_init(Chardev *chr, DisplayState *ds)
>       s->total_height = DEFAULT_BACKSCROLL;
>       s->x = 0;
>       s->y = 0;
> -    if (!s->surface) {
> -        if (active_console && active_console->surface) {
> -            g_width = surface_width(active_console->surface);
> -            g_height = surface_height(active_console->surface);
> +    if (s->scanout.kind != SCANOUT_SURFACE) {
> +        if (active_console && active_console->scanout.kind == SCANOUT_SURFACE) {
> +            g_width = qemu_console_get_width(active_console, g_width);
> +            g_height = qemu_console_get_height(active_console, g_height);
>           }
>           s->surface = qemu_create_displaysurface(g_width, g_height);
> +        s->scanout.kind = SCANOUT_SURFACE;
>       }
>   
>       s->hw_ops = &text_console_ops;
> @@ -2369,6 +2420,7 @@ static void vc_chr_open(Chardev *chr,
>           s = new_console(NULL, TEXT_CONSOLE, 0);
>       } else {
>           s = new_console(NULL, TEXT_CONSOLE_FIXED_SIZE, 0);
> +        s->scanout.kind = SCANOUT_SURFACE;
>           s->surface = qemu_create_displaysurface(width, height);
>       }
>   
> @@ -2392,13 +2444,13 @@ static void vc_chr_open(Chardev *chr,
>   
>   void qemu_console_resize(QemuConsole *s, int width, int height)
>   {
> -    DisplaySurface *surface;
> +    DisplaySurface *surface = qemu_console_surface(s);
>   
>       assert(s->console_type == GRAPHIC_CONSOLE);
>   
> -    if (s->surface && (s->surface->flags & QEMU_ALLOCATED_FLAG) &&
> -        pixman_image_get_width(s->surface->image) == width &&
> -        pixman_image_get_height(s->surface->image) == height) {
> +    if (surface && (surface->flags & QEMU_ALLOCATED_FLAG) &&
> +        pixman_image_get_width(surface->image) == width &&
> +        pixman_image_get_height(surface->image) == height) {
>           return;
>       }
>   
> @@ -2408,7 +2460,12 @@ void qemu_console_resize(QemuConsole *s, int width, int height)
>   
>   DisplaySurface *qemu_console_surface(QemuConsole *console)
>   {
> -    return console->surface;
> +    switch (console->scanout.kind) {
> +    case SCANOUT_SURFACE:
> +        return console->surface;
> +    default:
> +        return NULL;
> +    }
>   }
>   
>   PixelFormat qemu_default_pixelformat(int bpp)


^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v2 19/37] console: save current scanout details
  2022-01-11  3:29   ` Akihiko Odaki
@ 2022-01-11  8:23     ` Marc-André Lureau
  2022-01-11 12:45       ` Akihiko Odaki
  0 siblings, 1 reply; 54+ messages in thread
From: Marc-André Lureau @ 2022-01-11  8:23 UTC (permalink / raw)
  To: Akihiko Odaki; +Cc: qemu-devel, Hoffmann, Gerd

Hi Akihiko

On Tue, Jan 11, 2022 at 7:30 AM Akihiko Odaki <akihiko.odaki@gmail.com> wrote:
>
> Hi,
>
> I found this brings an inconsistency and a flaw to scanout semantics and
> think the inconsistency should be fixed or this should be reverted
> before the next release comes up.
>
> The inconsistency is in the handling of the console size. A guest
> hardware (especially I'm looking at virtio-gpu-virgl) tells the size
> change with qemu_console_resize. It causes the replacement of the
> surface, and the host display sees the change of the size via the
> surface. The replacement of the surface does *not* mean the surface
> should be scanned out; if an OpenGL texture is already provided, the
> host display should scan out it, not the replaced surface.

Isn't that an inconsistent state? Is the host display supposed to
scale the GL texture in this case, or what else?

> dpy_gl_scanout_disable will be called when the surface becomes the
> source of the scanouts.

I don't think the code was/is so consistent about it, but I can agree
with that rule eventually.

>
> However, this change brings some contradicting behaviors.
> - qemu_console_get_width and qemu_console_get_height now relies on the
> texture size as the source of the console size while the resize is
> delivered via the surface.

The texture update should follow, otherwise what do you do?

> - dpy_gfx_replace_surface makes the surface as the source of the
> scanouts while its guest hardware semantics does not mean that.

Here also, I am not convinced this is always consistent. But that
should be fairly easy to change.

Do you have a particular example / test case where it's problematic?

> - dpy_gl_scanout_disable sets the scanout kind to SCANOUT_NONE while it
> actually means the surface is now the source of the scanout.

You make it sound like it is/was obvious. All those unwritten "rules"
should probably be documented. If you have a good grasp of how the API
should behave, it would be worth it to write some documentation,
tests...

> Besides that, displaychangelistener_display_console has a flaw that it
> does not tell the switch to a console with SCANOUT_NONE. The intention
> of SCANOUT_NONE is not entirely clear.

I agree, it is not obvious to me what you were/are supposed to do when
the GL scanout is disabled.

> I think there are two options to fix the problem except reverting:

Well, reverting would be a pain, as it would break -display dbus.

> - Rework this change to make it consistent with the existing semantics.

Yes, that seems the way to go. I need your help to understand what is
actually broken: please give me broken test cases. Otherwise, it feels
needless.

> - Remove the use of qemu_console_resize and dpy_gl_scanout_disable from
>    hardwares providing OpenGL textures or DMA-BUF to make it consistent
>    with the new semantics.

It may make sense to make qemu_console_resize() work implicit when
calling dpy_gl_scanout_texture().

Removing dpy_gl_scanout_disable() is not possible, since hardware will
continue to provide 2d/fallback.

thanks


>
> Regards,
> Akihiko Odaki
>
> On 2021/10/10 6:08, marcandre.lureau@redhat.com wrote:
> > From: Marc-André Lureau <marcandre.lureau@redhat.com>
> >
> > Add a new DisplayScanout structure to save the current scanout details.
> > This allows to attach later UI backends and set the scanout.
> >
> > Introduce displaychangelistener_display_console() helper function to
> > handle the dpy_gfx_switch/gl_scanout() & dpy_gfx_update() calls.
> >
> > Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> > ---
> >   include/ui/console.h |  27 +++++++
> >   ui/console.c         | 165 +++++++++++++++++++++++++++++--------------
> >   2 files changed, 138 insertions(+), 54 deletions(-)
> >
> > diff --git a/include/ui/console.h b/include/ui/console.h
> > index b23ae283be..ab55d71894 100644
> > --- a/include/ui/console.h
> > +++ b/include/ui/console.h
> > @@ -108,6 +108,17 @@ struct QemuConsoleClass {
> >   #define QEMU_ALLOCATED_FLAG     0x01
> >   #define QEMU_PLACEHOLDER_FLAG   0x02
> >
> > +typedef struct ScanoutTexture {
> > +    uint32_t backing_id;
> > +    bool backing_y_0_top;
> > +    uint32_t backing_width;
> > +    uint32_t backing_height;
> > +    uint32_t x;
> > +    uint32_t y;
> > +    uint32_t width;
> > +    uint32_t height;
> > +} ScanoutTexture;
> > +
> >   typedef struct DisplaySurface {
> >       pixman_format_code_t format;
> >       pixman_image_t *image;
> > @@ -173,6 +184,22 @@ typedef struct QemuDmaBuf {
> >       bool      allow_fences;
> >   } QemuDmaBuf;
> >
> > +enum display_scanout {
> > +    SCANOUT_NONE,
> > +    SCANOUT_SURFACE,
> > +    SCANOUT_TEXTURE,
> > +    SCANOUT_DMABUF,
> > +};
> > +
> > +typedef struct DisplayScanout {
> > +    enum display_scanout kind;
> > +    union {
> > +        /* DisplaySurface *surface; is kept in QemuConsole */
> > +        ScanoutTexture texture;
> > +        QemuDmaBuf *dmabuf;
> > +    };
> > +} DisplayScanout;
> > +
> >   typedef struct DisplayState DisplayState;
> >   typedef struct DisplayGLCtx DisplayGLCtx;
> >
> > diff --git a/ui/console.c b/ui/console.c
> > index e5a2c84dd9..a1c6a78523 100644
> > --- a/ui/console.c
> > +++ b/ui/console.c
> > @@ -126,6 +126,7 @@ struct QemuConsole {
> >       console_type_t console_type;
> >       DisplayState *ds;
> >       DisplaySurface *surface;
> > +    DisplayScanout scanout;
> >       int dcls;
> >       DisplayGLCtx *gl;
> >       int gl_block;
> > @@ -197,6 +198,7 @@ static void dpy_refresh(DisplayState *s);
> >   static DisplayState *get_alloc_displaystate(void);
> >   static void text_console_update_cursor_timer(void);
> >   static void text_console_update_cursor(void *opaque);
> > +static bool displaychangelistener_has_dmabuf(DisplayChangeListener *dcl);
> >
> >   static void gui_update(void *opaque)
> >   {
> > @@ -532,6 +534,8 @@ static void text_console_resize(QemuConsole *s)
> >       TextCell *cells, *c, *c1;
> >       int w1, x, y, last_width;
> >
> > +    assert(s->scanout.kind == SCANOUT_SURFACE);
> > +
> >       last_width = s->width;
> >       s->width = surface_width(s->surface) / FONT_WIDTH;
> >       s->height = surface_height(s->surface) / FONT_HEIGHT;
> > @@ -1103,6 +1107,48 @@ static void console_putchar(QemuConsole *s, int ch)
> >       }
> >   }
> >
> > +static void displaychangelistener_display_console(DisplayChangeListener *dcl,
> > +                                                  QemuConsole *con)
> > +{
> > +    static const char nodev[] =
> > +        "This VM has no graphic display device.";
> > +    static DisplaySurface *dummy;
> > +
> > +    if (!con) {
> > +        if (!dcl->ops->dpy_gfx_switch) {
> > +            return;
> > +        }
> > +        if (!dummy) {
> > +            dummy = qemu_create_placeholder_surface(640, 480, nodev);
> > +        }
> > +        dcl->ops->dpy_gfx_switch(dcl, dummy);
> > +        return;
> > +    }
> > +
> > +    if (con->scanout.kind == SCANOUT_DMABUF &&
> > +        displaychangelistener_has_dmabuf(dcl)) {
> > +        dcl->ops->dpy_gl_scanout_dmabuf(dcl, con->scanout.dmabuf);
> > +    } else if (con->scanout.kind == SCANOUT_TEXTURE &&
> > +               dcl->ops->dpy_gl_scanout_texture) {
> > +        dcl->ops->dpy_gl_scanout_texture(dcl,
> > +                                         con->scanout.texture.backing_id,
> > +                                         con->scanout.texture.backing_y_0_top,
> > +                                         con->scanout.texture.backing_width,
> > +                                         con->scanout.texture.backing_height,
> > +                                         con->scanout.texture.x,
> > +                                         con->scanout.texture.y,
> > +                                         con->scanout.texture.width,
> > +                                         con->scanout.texture.height);
> > +    } else if (con->scanout.kind == SCANOUT_SURFACE &&
> > +               dcl->ops->dpy_gfx_switch) {
> > +        dcl->ops->dpy_gfx_switch(dcl, con->surface);
> > +    }
> > +
> > +    dcl->ops->dpy_gfx_update(dcl, 0, 0,
> > +                             qemu_console_get_width(con, 0),
> > +                             qemu_console_get_height(con, 0));
> > +}
> > +
> >   void console_select(unsigned int index)
> >   {
> >       DisplayChangeListener *dcl;
> > @@ -1119,13 +1165,7 @@ void console_select(unsigned int index)
> >                   if (dcl->con != NULL) {
> >                       continue;
> >                   }
> > -                if (dcl->ops->dpy_gfx_switch) {
> > -                    dcl->ops->dpy_gfx_switch(dcl, s->surface);
> > -                }
> > -            }
> > -            if (s->surface) {
> > -                dpy_gfx_update(s, 0, 0, surface_width(s->surface),
> > -                               surface_height(s->surface));
> > +                displaychangelistener_display_console(dcl, s);
> >               }
> >           }
> >           if (ds->have_text) {
> > @@ -1538,9 +1578,6 @@ static bool dpy_gl_compatible_with(QemuConsole *con, DisplayChangeListener *dcl)
> >
> >   void register_displaychangelistener(DisplayChangeListener *dcl)
> >   {
> > -    static const char nodev[] =
> > -        "This VM has no graphic display device.";
> > -    static DisplaySurface *dummy;
> >       QemuConsole *con;
> >
> >       assert(!dcl->ds);
> > @@ -1565,16 +1602,7 @@ void register_displaychangelistener(DisplayChangeListener *dcl)
> >       } else {
> >           con = active_console;
> >       }
> > -    if (dcl->ops->dpy_gfx_switch) {
> > -        if (con) {
> > -            dcl->ops->dpy_gfx_switch(dcl, con->surface);
> > -        } else {
> > -            if (!dummy) {
> > -                dummy = qemu_create_placeholder_surface(640, 480, nodev);
> > -            }
> > -            dcl->ops->dpy_gfx_switch(dcl, dummy);
> > -        }
> > -    }
> > +    displaychangelistener_display_console(dcl, con);
> >       text_console_update_cursor(NULL);
> >   }
> >
> > @@ -1655,13 +1683,9 @@ void dpy_gfx_update(QemuConsole *con, int x, int y, int w, int h)
> >   {
> >       DisplayState *s = con->ds;
> >       DisplayChangeListener *dcl;
> > -    int width = w;
> > -    int height = h;
> > +    int width = qemu_console_get_width(con, x + w);
> > +    int height = qemu_console_get_height(con, y + h);
> >
> > -    if (con->surface) {
> > -        width = surface_width(con->surface);
> > -        height = surface_height(con->surface);
> > -    }
> >       x = MAX(x, 0);
> >       y = MAX(y, 0);
> >       x = MIN(x, width);
> > @@ -1684,12 +1708,10 @@ void dpy_gfx_update(QemuConsole *con, int x, int y, int w, int h)
> >
> >   void dpy_gfx_update_full(QemuConsole *con)
> >   {
> > -    if (!con->surface) {
> > -        return;
> > -    }
> > -    dpy_gfx_update(con, 0, 0,
> > -                   surface_width(con->surface),
> > -                   surface_height(con->surface));
> > +    int w = qemu_console_get_width(con, 0);
> > +    int h = qemu_console_get_height(con, 0);
> > +
> > +    dpy_gfx_update(con, 0, 0, w, h);
> >   }
> >
> >   void dpy_gfx_replace_surface(QemuConsole *con,
> > @@ -1716,6 +1738,7 @@ void dpy_gfx_replace_surface(QemuConsole *con,
> >
> >       assert(old_surface != surface);
> >
> > +    con->scanout.kind = SCANOUT_SURFACE;
> >       con->surface = surface;
> >       QLIST_FOREACH(dcl, &s->listeners, next) {
> >           if (con != (dcl->con ? dcl->con : active_console)) {
> > @@ -1891,6 +1914,9 @@ void dpy_gl_scanout_disable(QemuConsole *con)
> >       DisplayState *s = con->ds;
> >       DisplayChangeListener *dcl;
> >
> > +    if (con->scanout.kind != SCANOUT_SURFACE) {
> > +        con->scanout.kind = SCANOUT_NONE;
> > +    }
> >       QLIST_FOREACH(dcl, &s->listeners, next) {
> >           dcl->ops->dpy_gl_scanout_disable(dcl);
> >       }
> > @@ -1907,6 +1933,11 @@ void dpy_gl_scanout_texture(QemuConsole *con,
> >       DisplayState *s = con->ds;
> >       DisplayChangeListener *dcl;
> >
> > +    con->scanout.kind = SCANOUT_TEXTURE;
> > +    con->scanout.texture = (ScanoutTexture) {
> > +        backing_id, backing_y_0_top, backing_width, backing_height,
> > +        x, y, width, height
> > +    };
> >       QLIST_FOREACH(dcl, &s->listeners, next) {
> >           dcl->ops->dpy_gl_scanout_texture(dcl, backing_id,
> >                                            backing_y_0_top,
> > @@ -1921,6 +1952,8 @@ void dpy_gl_scanout_dmabuf(QemuConsole *con,
> >       DisplayState *s = con->ds;
> >       DisplayChangeListener *dcl;
> >
> > +    con->scanout.kind = SCANOUT_DMABUF;
> > +    con->scanout.dmabuf = dmabuf;
> >       QLIST_FOREACH(dcl, &s->listeners, next) {
> >           dcl->ops->dpy_gl_scanout_dmabuf(dcl, dmabuf);
> >       }
> > @@ -2047,10 +2080,8 @@ QemuConsole *graphic_console_init(DeviceState *dev, uint32_t head,
> >       s = qemu_console_lookup_unused();
> >       if (s) {
> >           trace_console_gfx_reuse(s->index);
> > -        if (s->surface) {
> > -            width = surface_width(s->surface);
> > -            height = surface_height(s->surface);
> > -        }
> > +        width = qemu_console_get_width(s, 0);
> > +        height = qemu_console_get_height(s, 0);
> >       } else {
> >           trace_console_gfx_new();
> >           s = new_console(ds, GRAPHIC_CONSOLE, head);
> > @@ -2079,13 +2110,8 @@ void graphic_console_close(QemuConsole *con)
> >       static const char unplugged[] =
> >           "Guest display has been unplugged";
> >       DisplaySurface *surface;
> > -    int width = 640;
> > -    int height = 480;
> > -
> > -    if (con->surface) {
> > -        width = surface_width(con->surface);
> > -        height = surface_height(con->surface);
> > -    }
> > +    int width = qemu_console_get_width(con, 640);
> > +    int height = qemu_console_get_height(con, 480);
> >
> >       trace_console_gfx_close(con->index);
> >       object_property_set_link(OBJECT(con), "device", NULL, &error_abort);
> > @@ -2237,7 +2263,19 @@ int qemu_console_get_width(QemuConsole *con, int fallback)
> >       if (con == NULL) {
> >           con = active_console;
> >       }
> > -    return con ? surface_width(con->surface) : fallback;
> > +    if (con == NULL) {
> > +        return fallback;
> > +    }
> > +    switch (con->scanout.kind) {
> > +    case SCANOUT_DMABUF:
> > +        return con->scanout.dmabuf->width;
> > +    case SCANOUT_TEXTURE:
> > +        return con->scanout.texture.width;
> > +    case SCANOUT_SURFACE:
> > +        return surface_width(con->surface);
> > +    default:
> > +        return fallback;
> > +    }
> >   }
> >
> >   int qemu_console_get_height(QemuConsole *con, int fallback)
> > @@ -2245,7 +2283,19 @@ int qemu_console_get_height(QemuConsole *con, int fallback)
> >       if (con == NULL) {
> >           con = active_console;
> >       }
> > -    return con ? surface_height(con->surface) : fallback;
> > +    if (con == NULL) {
> > +        return fallback;
> > +    }
> > +    switch (con->scanout.kind) {
> > +    case SCANOUT_DMABUF:
> > +        return con->scanout.dmabuf->height;
> > +    case SCANOUT_TEXTURE:
> > +        return con->scanout.texture.height;
> > +    case SCANOUT_SURFACE:
> > +        return surface_height(con->surface);
> > +    default:
> > +        return fallback;
> > +    }
> >   }
> >
> >   static void vc_chr_set_echo(Chardev *chr, bool echo)
> > @@ -2305,12 +2355,13 @@ static void text_console_do_init(Chardev *chr, DisplayState *ds)
> >       s->total_height = DEFAULT_BACKSCROLL;
> >       s->x = 0;
> >       s->y = 0;
> > -    if (!s->surface) {
> > -        if (active_console && active_console->surface) {
> > -            g_width = surface_width(active_console->surface);
> > -            g_height = surface_height(active_console->surface);
> > +    if (s->scanout.kind != SCANOUT_SURFACE) {
> > +        if (active_console && active_console->scanout.kind == SCANOUT_SURFACE) {
> > +            g_width = qemu_console_get_width(active_console, g_width);
> > +            g_height = qemu_console_get_height(active_console, g_height);
> >           }
> >           s->surface = qemu_create_displaysurface(g_width, g_height);
> > +        s->scanout.kind = SCANOUT_SURFACE;
> >       }
> >
> >       s->hw_ops = &text_console_ops;
> > @@ -2369,6 +2420,7 @@ static void vc_chr_open(Chardev *chr,
> >           s = new_console(NULL, TEXT_CONSOLE, 0);
> >       } else {
> >           s = new_console(NULL, TEXT_CONSOLE_FIXED_SIZE, 0);
> > +        s->scanout.kind = SCANOUT_SURFACE;
> >           s->surface = qemu_create_displaysurface(width, height);
> >       }
> >
> > @@ -2392,13 +2444,13 @@ static void vc_chr_open(Chardev *chr,
> >
> >   void qemu_console_resize(QemuConsole *s, int width, int height)
> >   {
> > -    DisplaySurface *surface;
> > +    DisplaySurface *surface = qemu_console_surface(s);
> >
> >       assert(s->console_type == GRAPHIC_CONSOLE);
> >
> > -    if (s->surface && (s->surface->flags & QEMU_ALLOCATED_FLAG) &&
> > -        pixman_image_get_width(s->surface->image) == width &&
> > -        pixman_image_get_height(s->surface->image) == height) {
> > +    if (surface && (surface->flags & QEMU_ALLOCATED_FLAG) &&
> > +        pixman_image_get_width(surface->image) == width &&
> > +        pixman_image_get_height(surface->image) == height) {
> >           return;
> >       }
> >
> > @@ -2408,7 +2460,12 @@ void qemu_console_resize(QemuConsole *s, int width, int height)
> >
> >   DisplaySurface *qemu_console_surface(QemuConsole *console)
> >   {
> > -    return console->surface;
> > +    switch (console->scanout.kind) {
> > +    case SCANOUT_SURFACE:
> > +        return console->surface;
> > +    default:
> > +        return NULL;
> > +    }
> >   }
> >
> >   PixelFormat qemu_default_pixelformat(int bpp)
>



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v2 19/37] console: save current scanout details
  2022-01-11  8:23     ` Marc-André Lureau
@ 2022-01-11 12:45       ` Akihiko Odaki
  0 siblings, 0 replies; 54+ messages in thread
From: Akihiko Odaki @ 2022-01-11 12:45 UTC (permalink / raw)
  To: Marc-André Lureau; +Cc: qemu-devel, Hoffmann, Gerd



On 2022/01/11 17:23, Marc-André Lureau wrote:
> Hi Akihiko
> 
> On Tue, Jan 11, 2022 at 7:30 AM Akihiko Odaki <akihiko.odaki@gmail.com> wrote:
>>
>> Hi,
>>
>> I found this brings an inconsistency and a flaw to scanout semantics and
>> think the inconsistency should be fixed or this should be reverted
>> before the next release comes up.
>>
>> The inconsistency is in the handling of the console size. A guest
>> hardware (especially I'm looking at virtio-gpu-virgl) tells the size
>> change with qemu_console_resize. It causes the replacement of the
>> surface, and the host display sees the change of the size via the
>> surface. The replacement of the surface does *not* mean the surface
>> should be scanned out; if an OpenGL texture is already provided, the
>> host display should scan out it, not the replaced surface.
> 
> Isn't that an inconsistent state? Is the host display supposed to
> scale the GL texture in this case, or what else?

Well, actually yes, there is a short time window where the window size 
and texture size are inconsistent. In reality, it has not been a problem 
since the resize is immidately followed by the texture scanout. If it 
was not the case, I guess the host display implementations would break 
in different ways.

> 
>> dpy_gl_scanout_disable will be called when the surface becomes the
>> source of the scanouts.
> 
> I don't think the code was/is so consistent about it, but I can agree
> with that rule eventually.

Skimming the code, apparently egl-headless, gtk-egl and sdl2-gl does not 
change the source of the scanouts in dpy_gfx_switch, which replaces the 
surface, and solely relies on dpy_gl_scanout_disable for the 
functionality. spice-egl *does* change the source of the scanouts in 
dpy_gfx_switch, which I was not aware. So you are right; it is not 
consistent as I thought.

Still the intention of the guest hardware is clear here; 
qemu_console_resize is obviously for resizing the console and not for 
changing the scanout source.

> 
>>
>> However, this change brings some contradicting behaviors.
>> - qemu_console_get_width and qemu_console_get_height now relies on the
>> texture size as the source of the console size while the resize is
>> delivered via the surface.
> 
> The texture update should follow, otherwise what do you do >
>> - dpy_gfx_replace_surface makes the surface as the source of the
>> scanouts while its guest hardware semantics does not mean that.
> 
> Here also, I am not convinced this is always consistent. But that
> should be fairly easy to change.
> 
> Do you have a particular example / test case where it's problematic?

When you resize a window, the change of the size is delivered to the 
guest, and the guest responds with a new texture which matches the 
current window size. Changing the scanout source in 
dpy_gfx_replace_surface causes a small disruption in the case and makes 
the screen black for a moment.

> 
>> - dpy_gl_scanout_disable sets the scanout kind to SCANOUT_NONE while it
>> actually means the surface is now the source of the scanout.
> 
> You make it sound like it is/was obvious. All those unwritten "rules"
> should probably be documented. If you have a good grasp of how the API
> should behave, it would be worth it to write some documentation,
> tests...

I think the cause of the problem is the design failures. The scanout 
details are delivered via several different channels and they interact 
with each other in a non-obvious way. The "surface" is overloaded and 
tasked to deliver pixmap *and* the console size. These design failures 
are artificial and can be fixed.

Another cause is that the OpenGL interface was designed for the single 
listener and driven by it. That listener-driven nature of OpenGL 
interface made it hard to understand and caused fragmentation. It is 
somewhat cannot be helped though if I understand correctly; you can't 
determine the graphics accelerator where OpenGL contexts should reside 
if you don't know which physical display actually outputs the result. 
That fundamental limitation makes the listener and the OpenGL context 
provieder inseperatable.
Now the interface allows to have multiple listeners for OpenGL, but I'm 
a bit concerning if it actually works. DisplaySurface has OpenGL-related 
fields managed by each listeners and having multiple would cause 
conflicts. The listener-driven nature still remains.

> 
>> Besides that, displaychangelistener_display_console has a flaw that it
>> does not tell the switch to a console with SCANOUT_NONE. The intention
>> of SCANOUT_NONE is not entirely clear.
> 
> I agree, it is not obvious to me what you were/are supposed to do when
> the GL scanout is disabled >
>> I think there are two options to fix the problem except reverting:
> 
> Well, reverting would be a pain, as it would break -display dbus.
> 
>> - Rework this change to make it consistent with the existing semantics.
> 
> Yes, that seems the way to go. I need your help to understand what is
> actually broken: please give me broken test cases. Otherwise, it feels
> needless.

There are two scenarios:
- Have a virtio-gpu-virgl device and let the guest automatically decide 
the screen resolution. Resize the window and see the source switches 
from the texture to the surface (black screen), and back to the texture.
- Have a virtio-gpu-virgl device. Reset it and switch to another console 
(e.g. serial) and switch back to the virtio-gpu-virgl. Now the console 
shows the stale output of serial.

> 
>> - Remove the use of qemu_console_resize and dpy_gl_scanout_disable from
>>     hardwares providing OpenGL textures or DMA-BUF to make it consistent
>>     with the new semantics.
> 
> It may make sense to make qemu_console_resize() work implicit when
> calling dpy_gl_scanout_texture().
> 
> Removing dpy_gl_scanout_disable() is not possible, since hardware will
> continue to provide 2d/fallback.

If dpy_gfx_replace_surface() does the switch to 2D, 
dpy_gl_scanout_disable() can be removed or its responsibility can be 
reduced at least.

Regards,
Akihiko Odaki

> 
> thanks
> 
> 
>>
>> Regards,
>> Akihiko Odaki
>>
>> On 2021/10/10 6:08, marcandre.lureau@redhat.com wrote:
>>> From: Marc-André Lureau <marcandre.lureau@redhat.com>
>>>
>>> Add a new DisplayScanout structure to save the current scanout details.
>>> This allows to attach later UI backends and set the scanout.
>>>
>>> Introduce displaychangelistener_display_console() helper function to
>>> handle the dpy_gfx_switch/gl_scanout() & dpy_gfx_update() calls.
>>>
>>> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
>>> ---
>>>    include/ui/console.h |  27 +++++++
>>>    ui/console.c         | 165 +++++++++++++++++++++++++++++--------------
>>>    2 files changed, 138 insertions(+), 54 deletions(-)
>>>
>>> diff --git a/include/ui/console.h b/include/ui/console.h
>>> index b23ae283be..ab55d71894 100644
>>> --- a/include/ui/console.h
>>> +++ b/include/ui/console.h
>>> @@ -108,6 +108,17 @@ struct QemuConsoleClass {
>>>    #define QEMU_ALLOCATED_FLAG     0x01
>>>    #define QEMU_PLACEHOLDER_FLAG   0x02
>>>
>>> +typedef struct ScanoutTexture {
>>> +    uint32_t backing_id;
>>> +    bool backing_y_0_top;
>>> +    uint32_t backing_width;
>>> +    uint32_t backing_height;
>>> +    uint32_t x;
>>> +    uint32_t y;
>>> +    uint32_t width;
>>> +    uint32_t height;
>>> +} ScanoutTexture;
>>> +
>>>    typedef struct DisplaySurface {
>>>        pixman_format_code_t format;
>>>        pixman_image_t *image;
>>> @@ -173,6 +184,22 @@ typedef struct QemuDmaBuf {
>>>        bool      allow_fences;
>>>    } QemuDmaBuf;
>>>
>>> +enum display_scanout {
>>> +    SCANOUT_NONE,
>>> +    SCANOUT_SURFACE,
>>> +    SCANOUT_TEXTURE,
>>> +    SCANOUT_DMABUF,
>>> +};
>>> +
>>> +typedef struct DisplayScanout {
>>> +    enum display_scanout kind;
>>> +    union {
>>> +        /* DisplaySurface *surface; is kept in QemuConsole */
>>> +        ScanoutTexture texture;
>>> +        QemuDmaBuf *dmabuf;
>>> +    };
>>> +} DisplayScanout;
>>> +
>>>    typedef struct DisplayState DisplayState;
>>>    typedef struct DisplayGLCtx DisplayGLCtx;
>>>
>>> diff --git a/ui/console.c b/ui/console.c
>>> index e5a2c84dd9..a1c6a78523 100644
>>> --- a/ui/console.c
>>> +++ b/ui/console.c
>>> @@ -126,6 +126,7 @@ struct QemuConsole {
>>>        console_type_t console_type;
>>>        DisplayState *ds;
>>>        DisplaySurface *surface;
>>> +    DisplayScanout scanout;
>>>        int dcls;
>>>        DisplayGLCtx *gl;
>>>        int gl_block;
>>> @@ -197,6 +198,7 @@ static void dpy_refresh(DisplayState *s);
>>>    static DisplayState *get_alloc_displaystate(void);
>>>    static void text_console_update_cursor_timer(void);
>>>    static void text_console_update_cursor(void *opaque);
>>> +static bool displaychangelistener_has_dmabuf(DisplayChangeListener *dcl);
>>>
>>>    static void gui_update(void *opaque)
>>>    {
>>> @@ -532,6 +534,8 @@ static void text_console_resize(QemuConsole *s)
>>>        TextCell *cells, *c, *c1;
>>>        int w1, x, y, last_width;
>>>
>>> +    assert(s->scanout.kind == SCANOUT_SURFACE);
>>> +
>>>        last_width = s->width;
>>>        s->width = surface_width(s->surface) / FONT_WIDTH;
>>>        s->height = surface_height(s->surface) / FONT_HEIGHT;
>>> @@ -1103,6 +1107,48 @@ static void console_putchar(QemuConsole *s, int ch)
>>>        }
>>>    }
>>>
>>> +static void displaychangelistener_display_console(DisplayChangeListener *dcl,
>>> +                                                  QemuConsole *con)
>>> +{
>>> +    static const char nodev[] =
>>> +        "This VM has no graphic display device.";
>>> +    static DisplaySurface *dummy;
>>> +
>>> +    if (!con) {
>>> +        if (!dcl->ops->dpy_gfx_switch) {
>>> +            return;
>>> +        }
>>> +        if (!dummy) {
>>> +            dummy = qemu_create_placeholder_surface(640, 480, nodev);
>>> +        }
>>> +        dcl->ops->dpy_gfx_switch(dcl, dummy);
>>> +        return;
>>> +    }
>>> +
>>> +    if (con->scanout.kind == SCANOUT_DMABUF &&
>>> +        displaychangelistener_has_dmabuf(dcl)) {
>>> +        dcl->ops->dpy_gl_scanout_dmabuf(dcl, con->scanout.dmabuf);
>>> +    } else if (con->scanout.kind == SCANOUT_TEXTURE &&
>>> +               dcl->ops->dpy_gl_scanout_texture) {
>>> +        dcl->ops->dpy_gl_scanout_texture(dcl,
>>> +                                         con->scanout.texture.backing_id,
>>> +                                         con->scanout.texture.backing_y_0_top,
>>> +                                         con->scanout.texture.backing_width,
>>> +                                         con->scanout.texture.backing_height,
>>> +                                         con->scanout.texture.x,
>>> +                                         con->scanout.texture.y,
>>> +                                         con->scanout.texture.width,
>>> +                                         con->scanout.texture.height);
>>> +    } else if (con->scanout.kind == SCANOUT_SURFACE &&
>>> +               dcl->ops->dpy_gfx_switch) {
>>> +        dcl->ops->dpy_gfx_switch(dcl, con->surface);
>>> +    }
>>> +
>>> +    dcl->ops->dpy_gfx_update(dcl, 0, 0,
>>> +                             qemu_console_get_width(con, 0),
>>> +                             qemu_console_get_height(con, 0));
>>> +}
>>> +
>>>    void console_select(unsigned int index)
>>>    {
>>>        DisplayChangeListener *dcl;
>>> @@ -1119,13 +1165,7 @@ void console_select(unsigned int index)
>>>                    if (dcl->con != NULL) {
>>>                        continue;
>>>                    }
>>> -                if (dcl->ops->dpy_gfx_switch) {
>>> -                    dcl->ops->dpy_gfx_switch(dcl, s->surface);
>>> -                }
>>> -            }
>>> -            if (s->surface) {
>>> -                dpy_gfx_update(s, 0, 0, surface_width(s->surface),
>>> -                               surface_height(s->surface));
>>> +                displaychangelistener_display_console(dcl, s);
>>>                }
>>>            }
>>>            if (ds->have_text) {
>>> @@ -1538,9 +1578,6 @@ static bool dpy_gl_compatible_with(QemuConsole *con, DisplayChangeListener *dcl)
>>>
>>>    void register_displaychangelistener(DisplayChangeListener *dcl)
>>>    {
>>> -    static const char nodev[] =
>>> -        "This VM has no graphic display device.";
>>> -    static DisplaySurface *dummy;
>>>        QemuConsole *con;
>>>
>>>        assert(!dcl->ds);
>>> @@ -1565,16 +1602,7 @@ void register_displaychangelistener(DisplayChangeListener *dcl)
>>>        } else {
>>>            con = active_console;
>>>        }
>>> -    if (dcl->ops->dpy_gfx_switch) {
>>> -        if (con) {
>>> -            dcl->ops->dpy_gfx_switch(dcl, con->surface);
>>> -        } else {
>>> -            if (!dummy) {
>>> -                dummy = qemu_create_placeholder_surface(640, 480, nodev);
>>> -            }
>>> -            dcl->ops->dpy_gfx_switch(dcl, dummy);
>>> -        }
>>> -    }
>>> +    displaychangelistener_display_console(dcl, con);
>>>        text_console_update_cursor(NULL);
>>>    }
>>>
>>> @@ -1655,13 +1683,9 @@ void dpy_gfx_update(QemuConsole *con, int x, int y, int w, int h)
>>>    {
>>>        DisplayState *s = con->ds;
>>>        DisplayChangeListener *dcl;
>>> -    int width = w;
>>> -    int height = h;
>>> +    int width = qemu_console_get_width(con, x + w);
>>> +    int height = qemu_console_get_height(con, y + h);
>>>
>>> -    if (con->surface) {
>>> -        width = surface_width(con->surface);
>>> -        height = surface_height(con->surface);
>>> -    }
>>>        x = MAX(x, 0);
>>>        y = MAX(y, 0);
>>>        x = MIN(x, width);
>>> @@ -1684,12 +1708,10 @@ void dpy_gfx_update(QemuConsole *con, int x, int y, int w, int h)
>>>
>>>    void dpy_gfx_update_full(QemuConsole *con)
>>>    {
>>> -    if (!con->surface) {
>>> -        return;
>>> -    }
>>> -    dpy_gfx_update(con, 0, 0,
>>> -                   surface_width(con->surface),
>>> -                   surface_height(con->surface));
>>> +    int w = qemu_console_get_width(con, 0);
>>> +    int h = qemu_console_get_height(con, 0);
>>> +
>>> +    dpy_gfx_update(con, 0, 0, w, h);
>>>    }
>>>
>>>    void dpy_gfx_replace_surface(QemuConsole *con,
>>> @@ -1716,6 +1738,7 @@ void dpy_gfx_replace_surface(QemuConsole *con,
>>>
>>>        assert(old_surface != surface);
>>>
>>> +    con->scanout.kind = SCANOUT_SURFACE;
>>>        con->surface = surface;
>>>        QLIST_FOREACH(dcl, &s->listeners, next) {
>>>            if (con != (dcl->con ? dcl->con : active_console)) {
>>> @@ -1891,6 +1914,9 @@ void dpy_gl_scanout_disable(QemuConsole *con)
>>>        DisplayState *s = con->ds;
>>>        DisplayChangeListener *dcl;
>>>
>>> +    if (con->scanout.kind != SCANOUT_SURFACE) {
>>> +        con->scanout.kind = SCANOUT_NONE;
>>> +    }
>>>        QLIST_FOREACH(dcl, &s->listeners, next) {
>>>            dcl->ops->dpy_gl_scanout_disable(dcl);
>>>        }
>>> @@ -1907,6 +1933,11 @@ void dpy_gl_scanout_texture(QemuConsole *con,
>>>        DisplayState *s = con->ds;
>>>        DisplayChangeListener *dcl;
>>>
>>> +    con->scanout.kind = SCANOUT_TEXTURE;
>>> +    con->scanout.texture = (ScanoutTexture) {
>>> +        backing_id, backing_y_0_top, backing_width, backing_height,
>>> +        x, y, width, height
>>> +    };
>>>        QLIST_FOREACH(dcl, &s->listeners, next) {
>>>            dcl->ops->dpy_gl_scanout_texture(dcl, backing_id,
>>>                                             backing_y_0_top,
>>> @@ -1921,6 +1952,8 @@ void dpy_gl_scanout_dmabuf(QemuConsole *con,
>>>        DisplayState *s = con->ds;
>>>        DisplayChangeListener *dcl;
>>>
>>> +    con->scanout.kind = SCANOUT_DMABUF;
>>> +    con->scanout.dmabuf = dmabuf;
>>>        QLIST_FOREACH(dcl, &s->listeners, next) {
>>>            dcl->ops->dpy_gl_scanout_dmabuf(dcl, dmabuf);
>>>        }
>>> @@ -2047,10 +2080,8 @@ QemuConsole *graphic_console_init(DeviceState *dev, uint32_t head,
>>>        s = qemu_console_lookup_unused();
>>>        if (s) {
>>>            trace_console_gfx_reuse(s->index);
>>> -        if (s->surface) {
>>> -            width = surface_width(s->surface);
>>> -            height = surface_height(s->surface);
>>> -        }
>>> +        width = qemu_console_get_width(s, 0);
>>> +        height = qemu_console_get_height(s, 0);
>>>        } else {
>>>            trace_console_gfx_new();
>>>            s = new_console(ds, GRAPHIC_CONSOLE, head);
>>> @@ -2079,13 +2110,8 @@ void graphic_console_close(QemuConsole *con)
>>>        static const char unplugged[] =
>>>            "Guest display has been unplugged";
>>>        DisplaySurface *surface;
>>> -    int width = 640;
>>> -    int height = 480;
>>> -
>>> -    if (con->surface) {
>>> -        width = surface_width(con->surface);
>>> -        height = surface_height(con->surface);
>>> -    }
>>> +    int width = qemu_console_get_width(con, 640);
>>> +    int height = qemu_console_get_height(con, 480);
>>>
>>>        trace_console_gfx_close(con->index);
>>>        object_property_set_link(OBJECT(con), "device", NULL, &error_abort);
>>> @@ -2237,7 +2263,19 @@ int qemu_console_get_width(QemuConsole *con, int fallback)
>>>        if (con == NULL) {
>>>            con = active_console;
>>>        }
>>> -    return con ? surface_width(con->surface) : fallback;
>>> +    if (con == NULL) {
>>> +        return fallback;
>>> +    }
>>> +    switch (con->scanout.kind) {
>>> +    case SCANOUT_DMABUF:
>>> +        return con->scanout.dmabuf->width;
>>> +    case SCANOUT_TEXTURE:
>>> +        return con->scanout.texture.width;
>>> +    case SCANOUT_SURFACE:
>>> +        return surface_width(con->surface);
>>> +    default:
>>> +        return fallback;
>>> +    }
>>>    }
>>>
>>>    int qemu_console_get_height(QemuConsole *con, int fallback)
>>> @@ -2245,7 +2283,19 @@ int qemu_console_get_height(QemuConsole *con, int fallback)
>>>        if (con == NULL) {
>>>            con = active_console;
>>>        }
>>> -    return con ? surface_height(con->surface) : fallback;
>>> +    if (con == NULL) {
>>> +        return fallback;
>>> +    }
>>> +    switch (con->scanout.kind) {
>>> +    case SCANOUT_DMABUF:
>>> +        return con->scanout.dmabuf->height;
>>> +    case SCANOUT_TEXTURE:
>>> +        return con->scanout.texture.height;
>>> +    case SCANOUT_SURFACE:
>>> +        return surface_height(con->surface);
>>> +    default:
>>> +        return fallback;
>>> +    }
>>>    }
>>>
>>>    static void vc_chr_set_echo(Chardev *chr, bool echo)
>>> @@ -2305,12 +2355,13 @@ static void text_console_do_init(Chardev *chr, DisplayState *ds)
>>>        s->total_height = DEFAULT_BACKSCROLL;
>>>        s->x = 0;
>>>        s->y = 0;
>>> -    if (!s->surface) {
>>> -        if (active_console && active_console->surface) {
>>> -            g_width = surface_width(active_console->surface);
>>> -            g_height = surface_height(active_console->surface);
>>> +    if (s->scanout.kind != SCANOUT_SURFACE) {
>>> +        if (active_console && active_console->scanout.kind == SCANOUT_SURFACE) {
>>> +            g_width = qemu_console_get_width(active_console, g_width);
>>> +            g_height = qemu_console_get_height(active_console, g_height);
>>>            }
>>>            s->surface = qemu_create_displaysurface(g_width, g_height);
>>> +        s->scanout.kind = SCANOUT_SURFACE;
>>>        }
>>>
>>>        s->hw_ops = &text_console_ops;
>>> @@ -2369,6 +2420,7 @@ static void vc_chr_open(Chardev *chr,
>>>            s = new_console(NULL, TEXT_CONSOLE, 0);
>>>        } else {
>>>            s = new_console(NULL, TEXT_CONSOLE_FIXED_SIZE, 0);
>>> +        s->scanout.kind = SCANOUT_SURFACE;
>>>            s->surface = qemu_create_displaysurface(width, height);
>>>        }
>>>
>>> @@ -2392,13 +2444,13 @@ static void vc_chr_open(Chardev *chr,
>>>
>>>    void qemu_console_resize(QemuConsole *s, int width, int height)
>>>    {
>>> -    DisplaySurface *surface;
>>> +    DisplaySurface *surface = qemu_console_surface(s);
>>>
>>>        assert(s->console_type == GRAPHIC_CONSOLE);
>>>
>>> -    if (s->surface && (s->surface->flags & QEMU_ALLOCATED_FLAG) &&
>>> -        pixman_image_get_width(s->surface->image) == width &&
>>> -        pixman_image_get_height(s->surface->image) == height) {
>>> +    if (surface && (surface->flags & QEMU_ALLOCATED_FLAG) &&
>>> +        pixman_image_get_width(surface->image) == width &&
>>> +        pixman_image_get_height(surface->image) == height) {
>>>            return;
>>>        }
>>>
>>> @@ -2408,7 +2460,12 @@ void qemu_console_resize(QemuConsole *s, int width, int height)
>>>
>>>    DisplaySurface *qemu_console_surface(QemuConsole *console)
>>>    {
>>> -    return console->surface;
>>> +    switch (console->scanout.kind) {
>>> +    case SCANOUT_SURFACE:
>>> +        return console->surface;
>>> +    default:
>>> +        return NULL;
>>> +    }
>>>    }
>>>
>>>    PixelFormat qemu_default_pixelformat(int bpp)
>>
> 


^ permalink raw reply	[flat|nested] 54+ messages in thread

end of thread, other threads:[~2022-01-11 13:03 UTC | newest]

Thread overview: 54+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-10-09 21:08 [PATCH v2 00/37] Add D-Bus display backend marcandre.lureau
2021-10-09 21:08 ` [PATCH v2 01/37] build-sys: move Spice configure handling to meson marcandre.lureau
2021-10-09 21:08 ` [PATCH v2 02/37] ui/vdagent: add CHECK_SPICE_PROTOCOL_VERSION marcandre.lureau
2021-10-09 21:08 ` [PATCH v2 03/37] ui/vdagent: replace #if 0 with protocol version check marcandre.lureau
2021-10-09 21:08 ` [PATCH v2 04/37] ui: generalize clipboard notifier marcandre.lureau
2021-10-09 21:08 ` [PATCH v2 05/37] ui/vdagent: add serial capability support marcandre.lureau
2021-10-09 21:08 ` [PATCH v2 06/37] ui/clipboard: add qemu_clipboard_check_serial() marcandre.lureau
2021-10-09 21:08 ` [PATCH v2 07/37] ui/clipboard: add a clipboard reset serial event marcandre.lureau
2021-10-09 21:08 ` [PATCH v2 08/37] hw/display: report an error if virgl initialization failed marcandre.lureau
2021-12-17 12:40   ` Philippe Mathieu-Daudé
2021-10-09 21:08 ` [PATCH v2 09/37] virtio-gpu: use VIRTIO_GPU_RESOURCE_FLAG_Y_0_TOP marcandre.lureau
2021-12-17 12:51   ` Philippe Mathieu-Daudé
2021-10-09 21:08 ` [PATCH v2 10/37] ui: do not delay further remote resize marcandre.lureau
2021-10-09 21:08 ` [PATCH v2 11/37] ui: factor out qemu_console_set_display_gl_ctx() marcandre.lureau
2021-12-17 13:36   ` Philippe Mathieu-Daudé
2021-10-09 21:08 ` [PATCH v2 12/37] ui: associate GL context outside of display listener registration marcandre.lureau
2021-10-09 21:08 ` [PATCH v2 13/37] ui: make gl_block use a counter marcandre.lureau
2021-10-09 21:08 ` [PATCH v2 14/37] ui: add a gl-unblock warning timer marcandre.lureau
2021-10-09 21:08 ` [PATCH v2 15/37] ui: simplify gl unblock & flush marcandre.lureau
2021-10-09 21:08 ` [PATCH v2 16/37] ui: dispatch GL events to all listeners marcandre.lureau
2021-10-09 21:08 ` [PATCH v2 17/37] ui: split the GL context in a different object marcandre.lureau
2021-10-09 21:08 ` [PATCH v2 18/37] ui: move qemu_spice_fill_device_address to ui/util.c marcandre.lureau
2021-10-09 21:08 ` [PATCH v2 19/37] console: save current scanout details marcandre.lureau
2022-01-11  3:29   ` Akihiko Odaki
2022-01-11  8:23     ` Marc-André Lureau
2022-01-11 12:45       ` Akihiko Odaki
2021-10-09 21:08 ` [PATCH v2 20/37] scripts: teach modinfo to skip non-C sources marcandre.lureau
2021-10-09 21:08 ` [PATCH v2 21/37] docs/sphinx: add sphinx modules to include D-Bus documentation marcandre.lureau
2021-10-09 21:08 ` [PATCH v2 22/37] backends: move dbus-vmstate1.xml to backends/ marcandre.lureau
2021-10-09 21:08 ` [PATCH v2 23/37] docs: move D-Bus VMState documentation to source XML marcandre.lureau
2021-10-09 21:08 ` [PATCH v2 24/37] docs: add dbus-display documentation marcandre.lureau
2021-10-09 21:08 ` [PATCH v2 25/37] build-sys: set glib dependency version marcandre.lureau
2021-12-17 13:27   ` Philippe Mathieu-Daudé
2021-12-17 13:40     ` Marc-André Lureau
2021-12-17 14:36       ` Philippe Mathieu-Daudé
2021-10-09 21:08 ` [PATCH v2 26/37] ui: add a D-Bus display backend marcandre.lureau
2021-10-13  8:59   ` Marc-André Lureau
2021-10-09 21:08 ` [PATCH v2 27/37] ui/dbus: add p2p=on/off option marcandre.lureau
2021-10-09 21:08 ` [PATCH v2 28/37] tests/qtests: add qtest_qmp_add_client() marcandre.lureau
2021-10-09 21:08 ` [PATCH v2 29/37] tests: start dbus-display-test marcandre.lureau
2021-10-09 21:08 ` [PATCH v2 30/37] audio: add "dbus" audio backend marcandre.lureau
2021-10-09 21:08 ` [PATCH v2 31/37] ui/dbus: add clipboard interface marcandre.lureau
2021-10-09 21:08 ` [PATCH v2 32/37] chardev: teach socket to accept no addresses marcandre.lureau
2021-10-09 21:08 ` [PATCH v2 33/37] chardev: make socket derivable marcandre.lureau
2021-12-17 13:32   ` Philippe Mathieu-Daudé
2021-10-09 21:08 ` [PATCH v2 34/37] option: add g_auto for QemuOpts marcandre.lureau
2021-10-09 21:08 ` [PATCH v2 35/37] ui/dbus: add chardev backend & interface marcandre.lureau
2021-10-09 21:08 ` [PATCH v2 36/37] ui/dbus: register D-Bus VC handler marcandre.lureau
2021-12-17 13:35   ` Philippe Mathieu-Daudé
2021-12-17 14:21     ` Marc-André Lureau
2021-10-09 21:08 ` [PATCH v2 37/37] MAINTAINERS: update D-Bus section marcandre.lureau
2021-10-13  5:22 ` [PATCH v2 00/37] Add D-Bus display backend Gerd Hoffmann
2021-12-16 20:53   ` Marc-André Lureau
2021-12-17  7:05     ` Gerd Hoffmann

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.