diff mbox series

[RFC,v2,2/2] ui: Add a plain Wayland backend for Qemu UI

Message ID 20210913222036.3193732-3-vivek.kasireddy@intel.com (mailing list archive)
State New, archived
Headers show
Series ui: Add a Wayland backend for Qemu UI (v2) | expand

Commit Message

Kasireddy, Vivek Sept. 13, 2021, 10:20 p.m. UTC
Cc: Gerd Hoffmann <kraxel@redhat.com>
Signed-off-by: Vivek Kasireddy <vivek.kasireddy@intel.com>
---
 configure         |   8 +-
 meson.build       |  33 +++
 meson_options.txt |   2 +
 qapi/ui.json      |   3 +
 ui/meson.build    |  52 ++++
 ui/wayland.c      | 628 ++++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 725 insertions(+), 1 deletion(-)
 create mode 100644 ui/wayland.c

Comments

Eric Blake Sept. 14, 2021, 3:21 p.m. UTC | #1
On Mon, Sep 13, 2021 at 03:20:36PM -0700, Vivek Kasireddy wrote:
> Cc: Gerd Hoffmann <kraxel@redhat.com>
> Signed-off-by: Vivek Kasireddy <vivek.kasireddy@intel.com>
> ---
>  configure         |   8 +-
>  meson.build       |  33 +++
>  meson_options.txt |   2 +
>  qapi/ui.json      |   3 +
>  ui/meson.build    |  52 ++++
>  ui/wayland.c      | 628 ++++++++++++++++++++++++++++++++++++++++++++++
>  6 files changed, 725 insertions(+), 1 deletion(-)
>  create mode 100644 ui/wayland.c

UI review:

> +++ b/qapi/ui.json
> @@ -1112,6 +1112,8 @@
>  #                DRI device. Graphical display need to be paired with
>  #                VNC or Spice. (Since 3.1)
>  #
> +# @wayland: The Wayland user interface.

Missing a '(since 6.2)' tag.

> +#
>  # @curses: Display video output via curses.  For graphics device
>  #          models which support a text mode, QEMU can display this
>  #          output using a curses/ncurses interface. Nothing is
> @@ -1135,6 +1137,7 @@
>      { 'name': 'none' },
>      { 'name': 'gtk', 'if': 'CONFIG_GTK' },
>      { 'name': 'sdl', 'if': 'CONFIG_SDL' },
> +    { 'name': 'wayland', 'if': 'CONFIG_WAYLAND' },
>      { 'name': 'egl-headless',
>                'if': { 'all': ['CONFIG_OPENGL', 'CONFIG_GBM'] } },
>      { 'name': 'curses', 'if': 'CONFIG_CURSES' },
Daniel P. Berrangé Sept. 14, 2021, 4:15 p.m. UTC | #2
On Mon, Sep 13, 2021 at 03:20:36PM -0700, Vivek Kasireddy wrote:
> Cc: Gerd Hoffmann <kraxel@redhat.com>
> Signed-off-by: Vivek Kasireddy <vivek.kasireddy@intel.com>
> ---
>  configure         |   8 +-
>  meson.build       |  33 +++
>  meson_options.txt |   2 +
>  qapi/ui.json      |   3 +
>  ui/meson.build    |  52 ++++
>  ui/wayland.c      | 628 ++++++++++++++++++++++++++++++++++++++++++++++
>  6 files changed, 725 insertions(+), 1 deletion(-)
>  create mode 100644 ui/wayland.c


> diff --git a/ui/meson.build b/ui/meson.build
> index a73beb0e54..86fc324c82 100644
> --- a/ui/meson.build
> +++ b/ui/meson.build
> @@ -64,6 +64,58 @@ if config_host.has_key('CONFIG_OPENGL') and gbm.found()
>    ui_modules += {'egl-headless' : egl_headless_ss}
>  endif
>  
> +wayland_scanner = find_program('wayland-scanner')
> +proto_sources = [
> +  ['xdg-shell', 'stable', ],
> +  ['fullscreen-shell', 'unstable', 'v1', ],
> +  ['linux-dmabuf', 'unstable', 'v1', ],
> +]
> +wayland_headers = []
> +wayland_proto_sources = []
> +
> +if wayland.found()
> +  foreach p: proto_sources
> +    proto_name = p.get(0)
> +    proto_stability = p.get(1)
> +
> +    if proto_stability == 'stable'
> +      output_base = proto_name
> +      input = files(join_paths(wlproto_dir, '@0@/@1@/@2@.xml'.format(proto_stability, proto_name, output_base)))
> +    else
> +      proto_version = p.get(2)
> +      output_base = '@0@-@1@-@2@'.format(proto_name, proto_stability, proto_version)
> +      input = files(join_paths(wlproto_dir, '@0@/@1@/@2@.xml'.format(proto_stability, proto_name, output_base)))
> +    endif
> +
> +    wayland_headers += custom_target('@0@ client header'.format(output_base),
> +      input: input,
> +      output: '@0@-client-protocol.h'.format(output_base),
> +      command: [
> +        wayland_scanner,
> +        'client-header',
> +        '@INPUT@', '@OUTPUT@',
> +      ], build_by_default: true
> +    )
> +
> +    wayland_proto_sources += custom_target('@0@ source'.format(output_base),
> +      input: input,
> +      output: '@0@-protocol.c'.format(output_base),
> +      command: [
> +        wayland_scanner,
> +        'private-code',
> +        '@INPUT@', '@OUTPUT@',
> +      ], build_by_default: true
> +    )
> +  endforeach
> +endif
> +
> +if wayland.found()
> +  wayland_ss = ss.source_set()
> +  wayland_ss.add(when: wayland, if_true: files('wayland.c', 'xdg-shell-protocol.c', 'fullscreen-shell-unstable-v1-protocol.c','linux-dmabuf-unstable-v1-protocol.c'))
> +  #wayland_ss.add(when: wayland, if_true: files('wayland.c'), [wayland_proto_sources])
> +  ui_modules += {'wayland' : wayland_ss}
> +endif

Configure fails on this

  Program wayland-scanner found: YES (/usr/bin/wayland-scanner)

  ../ui/meson.build:114:13: ERROR: File xdg-shell-protocol.c does not exist.


the code a few lines above generates xdg-shell-protocol.c, but that
isn't run until you type "make", so when meson is resolving the
source files they don't exist.

The alternative line you have commented out looks more like what we
would need, but it doesn't work either as its syntax is invalid.

How did you actually compile this series ?


Regards,
Daniel
Kasireddy, Vivek Sept. 14, 2021, 11:03 p.m. UTC | #3
Hi Daniel,
 
> On Mon, Sep 13, 2021 at 03:20:36PM -0700, Vivek Kasireddy wrote:
> > Cc: Gerd Hoffmann <kraxel@redhat.com>
> > Signed-off-by: Vivek Kasireddy <vivek.kasireddy@intel.com>
> > ---
> >  configure         |   8 +-
> >  meson.build       |  33 +++
> >  meson_options.txt |   2 +
> >  qapi/ui.json      |   3 +
> >  ui/meson.build    |  52 ++++
> >  ui/wayland.c      | 628 ++++++++++++++++++++++++++++++++++++++++++++++
> >  6 files changed, 725 insertions(+), 1 deletion(-)  create mode 100644
> > ui/wayland.c
> 
> 
> > diff --git a/ui/meson.build b/ui/meson.build index
> > a73beb0e54..86fc324c82 100644
> > --- a/ui/meson.build
> > +++ b/ui/meson.build
> > @@ -64,6 +64,58 @@ if config_host.has_key('CONFIG_OPENGL') and gbm.found()
> >    ui_modules += {'egl-headless' : egl_headless_ss}  endif
> >
> > +wayland_scanner = find_program('wayland-scanner') proto_sources = [
> > +  ['xdg-shell', 'stable', ],
> > +  ['fullscreen-shell', 'unstable', 'v1', ],
> > +  ['linux-dmabuf', 'unstable', 'v1', ], ] wayland_headers = []
> > +wayland_proto_sources = []
> > +
> > +if wayland.found()
> > +  foreach p: proto_sources
> > +    proto_name = p.get(0)
> > +    proto_stability = p.get(1)
> > +
> > +    if proto_stability == 'stable'
> > +      output_base = proto_name
> > +      input = files(join_paths(wlproto_dir,
> '@0@/@1@/@2@.xml'.format(proto_stability, proto_name, output_base)))
> > +    else
> > +      proto_version = p.get(2)
> > +      output_base = '@0@-@1@-@2@'.format(proto_name, proto_stability,
> proto_version)
> > +      input = files(join_paths(wlproto_dir,
> '@0@/@1@/@2@.xml'.format(proto_stability, proto_name, output_base)))
> > +    endif
> > +
> > +    wayland_headers += custom_target('@0@ client header'.format(output_base),
> > +      input: input,
> > +      output: '@0@-client-protocol.h'.format(output_base),
> > +      command: [
> > +        wayland_scanner,
> > +        'client-header',
> > +        '@INPUT@', '@OUTPUT@',
> > +      ], build_by_default: true
> > +    )
> > +
> > +    wayland_proto_sources += custom_target('@0@ source'.format(output_base),
> > +      input: input,
> > +      output: '@0@-protocol.c'.format(output_base),
> > +      command: [
> > +        wayland_scanner,
> > +        'private-code',
> > +        '@INPUT@', '@OUTPUT@',
> > +      ], build_by_default: true
> > +    )
> > +  endforeach
> > +endif
> > +
> > +if wayland.found()
> > +  wayland_ss = ss.source_set()
> > +  wayland_ss.add(when: wayland, if_true: files('wayland.c',
> > +'xdg-shell-protocol.c',
> > +'fullscreen-shell-unstable-v1-protocol.c','linux-dmabuf-unstable-v1-p
> > +rotocol.c'))
> > +  #wayland_ss.add(when: wayland, if_true: files('wayland.c'),
> > +[wayland_proto_sources])
> > +  ui_modules += {'wayland' : wayland_ss} endif
> 
> Configure fails on this
> 
>   Program wayland-scanner found: YES (/usr/bin/wayland-scanner)
> 
>   ../ui/meson.build:114:13: ERROR: File xdg-shell-protocol.c does not exist.
> 
> 
> the code a few lines above generates xdg-shell-protocol.c, but that isn't run until you type
> "make", so when meson is resolving the source files they don't exist.
> 
> The alternative line you have commented out looks more like what we would need, but it
> doesn't work either as its syntax is invalid.
[Kasireddy, Vivek] Right, the commented line is the one we'd need but despite exhaustively
trying various different combinations, I couldn't get Meson to include the auto-generated
protocol sources. If it is not too much trouble, could you please point me to an example
where this is done elsewhere in Qemu source that I can look at?

> 
> How did you actually compile this series ?
[Kasireddy, Vivek] Oh, as a workaround, I just manually added the protocol sources. I am
sorry I did not realize that this code would be compiled/tested; I mainly posted these
RFC/WIP patches to provide additional context to the discussion associated with the DRM/
Virtio-gpu kernel patches.

Thanks,
Vivek

> 
> 
> Regards,
> Daniel
> --
> |: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
> |: https://libvirt.org         -o-            https://fstop138.berrange.com :|
> |: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|
diff mbox series

Patch

diff --git a/configure b/configure
index da2501489f..02339681dc 100755
--- a/configure
+++ b/configure
@@ -406,6 +406,7 @@  cfi_debug="false"
 seccomp="auto"
 glusterfs="auto"
 gtk="auto"
+wayland="auto"
 tls_priority="NORMAL"
 gnutls="auto"
 nettle="auto"
@@ -1383,6 +1384,10 @@  for opt do
   ;;
   --enable-gtk) gtk="enabled"
   ;;
+  --disable-wayland) wayland="disabled"
+  ;;
+  --enable-wayland) wayland="enabled"
+  ;;
   --tls-priority=*) tls_priority="$optarg"
   ;;
   --disable-gnutls) gnutls="disabled"
@@ -1868,6 +1873,7 @@  disabled with --disable-FEATURE, default is enabled if available
   sdl             SDL UI
   sdl-image       SDL Image support for icons
   gtk             gtk UI
+  wayland         Wayland UI
   vte             vte support for the gtk UI
   curses          curses UI
   iconv           font glyph conversion support
@@ -5191,7 +5197,7 @@  if test "$skip_meson" = no; then
         -Dmalloc=$malloc -Dmalloc_trim=$malloc_trim -Dsparse=$sparse \
         -Dkvm=$kvm -Dhax=$hax -Dwhpx=$whpx -Dhvf=$hvf -Dnvmm=$nvmm \
         -Dxen=$xen -Dxen_pci_passthrough=$xen_pci_passthrough -Dtcg=$tcg \
-        -Dcocoa=$cocoa -Dgtk=$gtk -Dmpath=$mpath -Dsdl=$sdl -Dsdl_image=$sdl_image \
+        -Dcocoa=$cocoa -Dgtk=$gtk -Dmpath=$mpath -Dsdl=$sdl -Dwayland=$wayland -Dsdl_image=$sdl_image \
         -Dlibusb=$libusb -Dsmartcard=$smartcard -Dusb_redir=$usb_redir -Dvte=$vte \
         -Dvnc=$vnc -Dvnc_sasl=$vnc_sasl -Dvnc_jpeg=$vnc_jpeg -Dvnc_png=$vnc_png \
         -Dgettext=$gettext -Dxkbcommon=$xkbcommon -Du2f=$u2f -Dvirtiofsd=$virtiofsd \
diff --git a/meson.build b/meson.build
index 7d7d14a4bc..cda10b7d0f 100644
--- a/meson.build
+++ b/meson.build
@@ -927,6 +927,37 @@  if gtkx11.found()
   x11 = dependency('x11', method: 'pkg-config', required: gtkx11.found(),
                    kwargs: static_kwargs)
 endif
+
+wayland = not_found
+if not get_option('wayland').auto()
+  wlclientdep = dependency('wayland-client', version: '>= 1.18.90',
+                           method: 'pkg-config',
+                           required: get_option('wayland'),
+                           kwargs: static_kwargs)
+  wlprotocolsdep = dependency('wayland-protocols', version: '>= 1.14.91',
+                              method: 'pkg-config',
+                              required: get_option('wayland'),
+                              kwargs: static_kwargs)
+
+  if not wlprotocolsdep.found()
+    wlproto_dir = subproject('wayland-protocols').get_variable('wayland_protocols_srcdir')
+  else
+    wlproto_dir = wlprotocolsdep.get_pkgconfig_variable('pkgdatadir')
+  endif
+
+  wayland = declare_dependency(dependencies: [wlclientdep, wlprotocolsdep])
+endif
+
+if wayland.found() and get_option('sdl').enabled()
+  error('Wayland and SDL cannot be enabled at the same time')
+endif
+if wayland.found() and get_option('gtk').enabled()
+  error('Wayland and GTK+ cannot be enabled at the same time')
+endif
+if wayland.found() and get_option('cocoa').enabled()
+  error('Wayland and Cocoa cannot be enabled at the same time')
+endif
+
 vnc = not_found
 png = not_found
 jpeg = not_found
@@ -1256,6 +1287,7 @@  if glusterfs.found()
   config_host_data.set('CONFIG_GLUSTERFS_IOCB_HAS_STAT', glusterfs_iocb_has_stat)
 endif
 config_host_data.set('CONFIG_GTK', gtk.found())
+config_host_data.set('CONFIG_WAYLAND', wayland.found())
 config_host_data.set('CONFIG_VTE', vte.found())
 config_host_data.set('CONFIG_LIBATTR', have_old_libattr)
 config_host_data.set('CONFIG_LIBCAP_NG', libcap_ng.found())
@@ -3052,6 +3084,7 @@  summary_info += {'SDL support':       sdl.found()}
 summary_info += {'SDL image support': sdl_image.found()}
 # TODO: add back version
 summary_info += {'GTK support':       gtk.found()}
+summary_info += {'Wayland support':   wayland.found()}
 summary_info += {'pixman':            pixman.found()}
 # TODO: add back version
 summary_info += {'VTE support':       vte.found()}
diff --git a/meson_options.txt b/meson_options.txt
index a9a9b8f4c6..6c0e27e83b 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -104,6 +104,8 @@  option('rbd', type : 'feature', value : 'auto',
        description: 'Ceph block device driver')
 option('gtk', type : 'feature', value : 'auto',
        description: 'GTK+ user interface')
+option('wayland', type : 'feature', value : 'auto',
+       description: 'Wayland user interface')
 option('sdl', type : 'feature', value : 'auto',
        description: 'SDL user interface')
 option('sdl_image', type : 'feature', value : 'auto',
diff --git a/qapi/ui.json b/qapi/ui.json
index b2cf7a6759..8da0baa0bd 100644
--- a/qapi/ui.json
+++ b/qapi/ui.json
@@ -1112,6 +1112,8 @@ 
 #                DRI device. Graphical display need to be paired with
 #                VNC or Spice. (Since 3.1)
 #
+# @wayland: The Wayland user interface.
+#
 # @curses: Display video output via curses.  For graphics device
 #          models which support a text mode, QEMU can display this
 #          output using a curses/ncurses interface. Nothing is
@@ -1135,6 +1137,7 @@ 
     { 'name': 'none' },
     { 'name': 'gtk', 'if': 'CONFIG_GTK' },
     { 'name': 'sdl', 'if': 'CONFIG_SDL' },
+    { 'name': 'wayland', 'if': 'CONFIG_WAYLAND' },
     { 'name': 'egl-headless',
               'if': { 'all': ['CONFIG_OPENGL', 'CONFIG_GBM'] } },
     { 'name': 'curses', 'if': 'CONFIG_CURSES' },
diff --git a/ui/meson.build b/ui/meson.build
index a73beb0e54..86fc324c82 100644
--- a/ui/meson.build
+++ b/ui/meson.build
@@ -64,6 +64,58 @@  if config_host.has_key('CONFIG_OPENGL') and gbm.found()
   ui_modules += {'egl-headless' : egl_headless_ss}
 endif
 
+wayland_scanner = find_program('wayland-scanner')
+proto_sources = [
+  ['xdg-shell', 'stable', ],
+  ['fullscreen-shell', 'unstable', 'v1', ],
+  ['linux-dmabuf', 'unstable', 'v1', ],
+]
+wayland_headers = []
+wayland_proto_sources = []
+
+if wayland.found()
+  foreach p: proto_sources
+    proto_name = p.get(0)
+    proto_stability = p.get(1)
+
+    if proto_stability == 'stable'
+      output_base = proto_name
+      input = files(join_paths(wlproto_dir, '@0@/@1@/@2@.xml'.format(proto_stability, proto_name, output_base)))
+    else
+      proto_version = p.get(2)
+      output_base = '@0@-@1@-@2@'.format(proto_name, proto_stability, proto_version)
+      input = files(join_paths(wlproto_dir, '@0@/@1@/@2@.xml'.format(proto_stability, proto_name, output_base)))
+    endif
+
+    wayland_headers += custom_target('@0@ client header'.format(output_base),
+      input: input,
+      output: '@0@-client-protocol.h'.format(output_base),
+      command: [
+        wayland_scanner,
+        'client-header',
+        '@INPUT@', '@OUTPUT@',
+      ], build_by_default: true
+    )
+
+    wayland_proto_sources += custom_target('@0@ source'.format(output_base),
+      input: input,
+      output: '@0@-protocol.c'.format(output_base),
+      command: [
+        wayland_scanner,
+        'private-code',
+        '@INPUT@', '@OUTPUT@',
+      ], build_by_default: true
+    )
+  endforeach
+endif
+
+if wayland.found()
+  wayland_ss = ss.source_set()
+  wayland_ss.add(when: wayland, if_true: files('wayland.c', 'xdg-shell-protocol.c', 'fullscreen-shell-unstable-v1-protocol.c','linux-dmabuf-unstable-v1-protocol.c'))
+  #wayland_ss.add(when: wayland, if_true: files('wayland.c'), [wayland_proto_sources])
+  ui_modules += {'wayland' : wayland_ss}
+endif
+
 if gtk.found()
   softmmu_ss.add(when: 'CONFIG_WIN32', if_true: files('win32-kbd-hook.c'))
 
diff --git a/ui/wayland.c b/ui/wayland.c
new file mode 100644
index 0000000000..9e011bd701
--- /dev/null
+++ b/ui/wayland.c
@@ -0,0 +1,628 @@ 
+/*
+ * Wayland UI -- A simple Qemu UI backend to share Guest Compositor buffers
+ * with Host Wayland compositors directly.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ * Mostly (boilerplate) based on:
+ * https://cgit.freedesktop.org/wayland/weston/tree/clients/simple-dmabuf-egl.c
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "qemu/main-loop.h"
+#include "sysemu/sysemu.h"
+#include "ui/console.h"
+#include "ui/input.h"
+#include "ui/kbd-state.h"
+#include "keymaps.h"
+#include <linux/input.h>
+#include <wayland-client.h>
+#include "xdg-shell-client-protocol.h"
+#include "fullscreen-shell-unstable-v1-client-protocol.h"
+#include "linux-dmabuf-unstable-v1-client-protocol.h"
+
+#define MAX_BUFFERS 4
+
+typedef struct wayland_display {
+    struct wl_display *display;
+    struct wl_registry *registry;
+    struct wl_compositor *compositor;
+    struct xdg_wm_base *wm_base;
+    struct zwp_fullscreen_shell_v1 *fshell;
+    struct zwp_linux_dmabuf_v1 *dmabuf;
+} wayland_display;
+
+typedef struct wayland_buffer {
+    QemuConsole *con;
+    QemuDmaBuf *dmabuf;
+    struct wl_buffer *buffer;
+    bool busy;
+} wayland_buffer;
+
+typedef struct wayland_window {
+    wayland_display *display;
+    DisplayChangeListener dcl;
+    QKbdState *kbd;
+    struct wl_surface *surface;
+    struct xdg_surface *xdg_surface;
+    struct xdg_toplevel *xdg_toplevel;
+    struct wl_callback *callback;
+    wayland_buffer buffers[MAX_BUFFERS];
+    wayland_buffer *new_buffer;
+    int width, height;
+    bool wait_for_configure;
+    int last_x, last_y;
+    bool last_set;
+} wayland_window;
+
+typedef struct wayland_input {
+    wayland_window *focus;
+    struct wl_seat *seat;
+    struct wl_pointer *pointer;
+    struct wl_keyboard *keyboard;
+    const uint16_t *keycode_map;
+    size_t keycode_maplen;
+} wayland_input;
+
+static const struct wl_callback_listener frame_listener;
+
+static void xdg_surface_handle_configure(void *data,
+                                         struct xdg_surface *surface,
+			                 uint32_t serial)
+{
+    wayland_window *window = data;
+
+    xdg_surface_ack_configure(surface, serial);
+    window->wait_for_configure = false;
+}
+
+static const struct xdg_surface_listener xdg_surface_listener = {
+    xdg_surface_handle_configure,
+};
+
+static void xdg_toplevel_handle_configure(void *data,
+                                          struct xdg_toplevel *toplevel,
+			                  int32_t width, int32_t height,
+			                  struct wl_array *states)
+{
+}
+
+static void xdg_toplevel_handle_close(void *data,
+                                      struct xdg_toplevel *xdg_toplevel)
+{
+}
+
+static const struct xdg_toplevel_listener xdg_toplevel_listener = {
+    xdg_toplevel_handle_configure,
+    xdg_toplevel_handle_close,
+};
+
+static void wayland_refresh(DisplayChangeListener *dcl)
+{
+    graphic_hw_update(dcl->con);
+}
+
+static QEMUGLContext wayland_create_context(DisplayChangeListener *dcl,
+                                            QEMUGLParams *params)
+{
+    return NULL;
+}
+
+static void wayland_destroy_context(DisplayChangeListener *dcl,
+                                    QEMUGLContext ctx)
+{
+}
+
+static int wayland_make_context_current(DisplayChangeListener *dcl,
+                                        QEMUGLContext ctx)
+{
+    return 0;
+}
+
+static void wayland_scanout_disable(DisplayChangeListener *dcl)
+{
+}
+
+static void wayland_scanout_texture(DisplayChangeListener *dcl,
+                                    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 w, uint32_t h)
+{
+}
+
+static void wayland_release_dmabuf(DisplayChangeListener *dcl,
+                                   QemuDmaBuf *dmabuf)
+{
+}
+
+static void wayland_window_redraw(void *data, struct wl_callback *callback,
+                                  uint32_t time)
+{
+    wayland_window *window = data;
+
+    if (callback) {
+        assert(window->callback == callback);
+        wl_callback_destroy(callback);
+        window->callback = NULL;
+    }
+    graphic_hw_gl_block(window->dcl.con, false);
+    graphic_hw_gl_flushed(window->dcl.con);
+}
+
+static const struct wl_callback_listener frame_listener = {
+    wayland_window_redraw
+};
+
+static void wayland_buffer_release(void *data, struct wl_buffer *buf)
+{
+    wayland_buffer *buffer = data;
+    QemuDmaBuf *dmabuf = buffer->dmabuf;
+
+    dmabuf->fence_fd = -1;
+    graphic_hw_gl_flushed(buffer->con);
+    buffer->busy = false;
+    wl_buffer_destroy(buf);
+}
+
+static const struct wl_buffer_listener buffer_listener = {
+    wayland_buffer_release
+};
+
+static wayland_buffer *window_next_buffer(wayland_window *window)
+{
+    int i;
+
+    for (i = 0; i < MAX_BUFFERS; i++) {
+        if (!window->buffers[i].busy) {
+            return &window->buffers[i];
+        }
+    }
+    return NULL;
+}
+
+static void wayland_scanout_dmabuf(DisplayChangeListener *dcl,
+                                   QemuDmaBuf *dmabuf)
+{
+    wayland_window *window = container_of(dcl, wayland_window, dcl);
+    wayland_display *display = window->display;
+    wayland_buffer *buffer = window_next_buffer(window);
+    struct zwp_linux_buffer_params_v1 *params;
+
+    if (!buffer) {
+        error_report("Can't find free buffer\n");
+        exit(1);
+    }
+    params = zwp_linux_dmabuf_v1_create_params(display->dmabuf);
+    zwp_linux_buffer_params_v1_add(params, dmabuf->fd, 0, 0, dmabuf->stride,
+                                   0, 0);
+    buffer->buffer = zwp_linux_buffer_params_v1_create_immed(params,
+                                                             dmabuf->width,
+                                                             dmabuf->height,
+                                                             dmabuf->fourcc,
+                                                             0);
+    zwp_linux_buffer_params_v1_destroy(params);
+    buffer->dmabuf = dmabuf;
+    buffer->con = window->dcl.con;
+    window->new_buffer = buffer;
+    window->width = dmabuf->width;
+    window->height = dmabuf->height;
+    dmabuf->fence_fd = 1;
+    wl_buffer_add_listener(buffer->buffer, &buffer_listener, buffer);
+}
+
+static void wayland_scanout_flush(DisplayChangeListener *dcl,
+                                  uint32_t x, uint32_t y,
+                                  uint32_t w, uint32_t h)
+{
+    wayland_window *window = container_of(dcl, wayland_window, dcl);
+    struct wl_region *region;
+
+    graphic_hw_gl_block(window->new_buffer->con, true);
+    region = wl_compositor_create_region(window->display->compositor);
+    wl_region_add(region, 0, 0, window->width, window->height);
+    wl_surface_set_opaque_region(window->surface, region);
+    wl_region_destroy(region);
+
+    window->callback = wl_surface_frame(window->surface);
+    wl_callback_add_listener(window->callback, &frame_listener, window);
+    wl_surface_attach(window->surface, window->new_buffer->buffer, 0, 0);
+    wl_surface_damage(window->surface, 0, 0, window->width, window->height);
+    wl_surface_commit(window->surface);
+    wl_display_flush(window->display->display);
+    window->new_buffer->busy = true;
+}
+
+static void dmabuf_modifier(void *data,
+                            struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf,
+                            uint32_t format, uint32_t modifier_hi,
+                            uint32_t modifier_lo)
+{
+}
+
+static void dmabuf_format(void *data,
+                          struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf,
+                          uint32_t format)
+{
+}
+
+static const struct zwp_linux_dmabuf_v1_listener dmabuf_listener = {
+    dmabuf_format,
+    dmabuf_modifier
+};
+
+static void xdg_wm_base_ping(void *data, struct xdg_wm_base *shell,
+                             uint32_t serial)
+{
+    xdg_wm_base_pong(shell, serial);
+}
+
+static const struct xdg_wm_base_listener wm_base_listener = {
+    xdg_wm_base_ping,
+};
+
+static void pointer_handle_enter(void *data, struct wl_pointer *pointer,
+                                 uint32_t serial, struct wl_surface *surface,
+                                 wl_fixed_t sx, wl_fixed_t sy)
+{
+    wayland_input *input = data;
+
+    if (!surface) {
+        return;
+    }
+    input->focus = wl_surface_get_user_data(surface);
+    wl_pointer_set_cursor(pointer, serial, NULL, 0, 0);
+}
+
+static void pointer_handle_leave(void *data, struct wl_pointer *pointer,
+                                 uint32_t serial, struct wl_surface *surface)
+{
+    wayland_input *input = data;
+
+    if (!surface || !input->focus) {
+        return;
+    }
+    input->focus = NULL;
+}
+
+static void pointer_handle_motion(void *data, struct wl_pointer *pointer,
+                                  uint32_t time, wl_fixed_t sx, wl_fixed_t sy)
+{
+    wayland_input *input = data;
+    wayland_window *window = input->focus;
+    int x, y;
+
+    if (!window) {
+        return;
+    }
+    x = wl_fixed_to_int(sx);
+    y = wl_fixed_to_int(sy);
+    if (qemu_input_is_absolute()) {
+        qemu_input_queue_abs(window->dcl.con, INPUT_AXIS_X,
+                             x, 0, window->width);
+        qemu_input_queue_abs(window->dcl.con, INPUT_AXIS_Y,
+                             y, 0, window->height);
+        qemu_input_event_sync();
+    } else if (window->last_set) {
+        qemu_input_queue_rel(window->dcl.con, INPUT_AXIS_X,
+                             x - window->last_x);
+        qemu_input_queue_rel(window->dcl.con, INPUT_AXIS_Y,
+                             y - window->last_y);
+        qemu_input_event_sync();
+    }
+    window->last_x = x;
+    window->last_y = y;
+    window->last_set = true;
+}
+
+static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer,
+                                  uint32_t serial, uint32_t time,
+                                  uint32_t button, uint32_t state)
+{
+    wayland_input *input = data;
+    wayland_window *window = input->focus;
+    InputButton btn;
+
+    if (!window) {
+        return;
+    }
+    if (button == BTN_LEFT) {
+        btn = INPUT_BUTTON_LEFT;
+    } else if (button == BTN_MIDDLE) {
+        btn = INPUT_BUTTON_MIDDLE;
+    } else if (button == BTN_RIGHT) {
+        btn = INPUT_BUTTON_RIGHT;
+    } else if (button == BTN_SIDE) {
+        btn = INPUT_BUTTON_SIDE;
+    } else {
+        return;
+    }
+    qemu_input_queue_btn(window->dcl.con, btn,
+                         state == WL_POINTER_BUTTON_STATE_PRESSED);
+    qemu_input_event_sync();
+}
+
+static void pointer_handle_axis(void *data, struct wl_pointer *wl_pointer,
+                                uint32_t time, uint32_t axis, wl_fixed_t value)
+{
+    wayland_input *input = data;
+    wayland_window *window = input->focus;
+    InputButton btn;
+    int delta_y = 0;
+
+    if (!window) {
+        return;
+    }
+    if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) {
+        delta_y = wl_fixed_to_int(value) / 10;
+    }
+    if (delta_y > 0) {
+        btn = INPUT_BUTTON_WHEEL_DOWN;
+    } else {
+        btn = INPUT_BUTTON_WHEEL_UP;
+    }
+
+    qemu_input_queue_btn(window->dcl.con, btn, true);
+    qemu_input_event_sync();
+    qemu_input_queue_btn(window->dcl.con, btn, false);
+    qemu_input_event_sync();
+}
+
+static const struct wl_pointer_listener pointer_listener = {
+    pointer_handle_enter,
+    pointer_handle_leave,
+    pointer_handle_motion,
+    pointer_handle_button,
+    pointer_handle_axis,
+};
+
+static void keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard,
+                                   uint32_t format, int fd, uint32_t size)
+{
+    close(fd);
+}
+
+static void keyboard_handle_enter(void *data, struct wl_keyboard *keyboard,
+                                  uint32_t serial, struct wl_surface *surface,
+                                  struct wl_array *keys)
+{
+}
+
+static void keyboard_handle_leave(void *data, struct wl_keyboard *keyboard,
+                                  uint32_t serial, struct wl_surface *surface)
+{
+}
+
+static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard,
+                                uint32_t serial, uint32_t time, uint32_t key,
+                                uint32_t state)
+{
+    wayland_input *input = data;
+    wayland_window *window = input->focus;
+    int keycode = key + 8, qcode;
+
+    if (!window || !input->keycode_map ||
+        keycode > input->keycode_maplen) {
+        return;
+    }
+    qcode = input->keycode_map[keycode];
+    qkbd_state_key_event(window->kbd, qcode, state);
+}
+
+static void keyboard_handle_modifiers(void *data, struct wl_keyboard *keyboard,
+                                      uint32_t serial, uint32_t mods_depressed,
+                                      uint32_t mods_latched, uint32_t mods_locked,
+                                      uint32_t group)
+{
+}
+
+static const struct wl_keyboard_listener keyboard_listener = {
+    keyboard_handle_keymap,
+    keyboard_handle_enter,
+    keyboard_handle_leave,
+    keyboard_handle_key,
+    keyboard_handle_modifiers,
+};
+
+static void seat_handle_capabilities(void *data, struct wl_seat *seat,
+                                     enum wl_seat_capability caps)
+{
+    wayland_input *input = data;
+
+    if ((caps & WL_SEAT_CAPABILITY_POINTER) && !input->pointer) {
+        input->pointer = wl_seat_get_pointer(seat);
+        wl_pointer_add_listener(input->pointer, &pointer_listener, input);
+    } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && input->pointer) {
+        wl_pointer_destroy(input->pointer);
+        input->pointer = NULL;
+    }
+
+    if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !input->keyboard) {
+        input->keyboard = wl_seat_get_keyboard(seat);
+        wl_keyboard_add_listener(input->keyboard, &keyboard_listener, input);
+    } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && input->keyboard) {
+        wl_keyboard_destroy(input->keyboard);
+        input->keyboard = NULL;
+    }
+}
+
+static const struct wl_seat_listener seat_listener = {
+    seat_handle_capabilities,
+};
+
+static void registry_handle_global(void *data, struct wl_registry *registry,
+                                   uint32_t id, const char *interface,
+                                   uint32_t version)
+{
+    wayland_display *display = data;
+    wayland_input *input;
+
+    if (strcmp(interface, "wl_compositor") == 0) {
+        display->compositor = wl_registry_bind(registry,
+			                 id, &wl_compositor_interface, 1);
+    } else if (strcmp(interface, "xdg_wm_base") == 0) {
+	display->wm_base = wl_registry_bind(registry,
+				      id, &xdg_wm_base_interface, 1);
+	xdg_wm_base_add_listener(display->wm_base, &wm_base_listener, display);
+    } else if (strcmp(interface, "wl_seat") == 0) {
+        input = g_new0(wayland_input, 1);
+        input->keycode_maplen = qemu_input_map_xorgevdev_to_qcode_len;
+        input->keycode_map = qemu_input_map_xorgevdev_to_qcode;
+        input->seat = wl_registry_bind(registry, id,
+                                       &wl_seat_interface, 1);
+        wl_seat_add_listener(input->seat, &seat_listener, input);
+    } else if (strcmp(interface, "zwp_fullscreen_shell_v1") == 0) {
+	display->fshell = wl_registry_bind(registry,
+	                             id, &zwp_fullscreen_shell_v1_interface,
+	                             1);
+    } else if (strcmp(interface, "zwp_linux_dmabuf_v1") == 0) {
+	display->dmabuf = wl_registry_bind(registry,
+	                             id, &zwp_linux_dmabuf_v1_interface, 3);
+	zwp_linux_dmabuf_v1_add_listener(display->dmabuf, &dmabuf_listener,
+	                                 display);
+    }
+}
+
+static void registry_handle_global_remove(void *data,
+                                          struct wl_registry *registry,
+                                          uint32_t name)
+{
+}
+
+static const struct wl_registry_listener registry_listener = {
+    registry_handle_global,
+    registry_handle_global_remove
+};
+
+static const DisplayChangeListenerOps wayland_ops = {
+    .dpy_name                = "wayland",
+    .dpy_refresh             = wayland_refresh,
+    .dpy_gl_ctx_create       = wayland_create_context,
+    .dpy_gl_ctx_destroy      = wayland_destroy_context,
+    .dpy_gl_ctx_make_current = wayland_make_context_current,
+
+    .dpy_gl_scanout_disable  = wayland_scanout_disable,
+    .dpy_gl_scanout_texture  = wayland_scanout_texture,
+    .dpy_gl_scanout_dmabuf   = wayland_scanout_dmabuf,
+    .dpy_gl_release_dmabuf   = wayland_release_dmabuf,
+    .dpy_gl_update           = wayland_scanout_flush,
+};
+
+static wayland_display *wayland_create_display(void)
+{
+    wayland_display *display;
+
+    display = g_new0(wayland_display, 1);
+    display->display = wl_display_connect(NULL);
+    assert(display->display);
+
+    display->registry = wl_display_get_registry(display->display);
+    wl_registry_add_listener(display->registry,
+                             &registry_listener, display);
+    wl_display_roundtrip(display->display);
+    if (display->dmabuf == NULL) {
+        error_report("No zwp_linux_dmabuf global\n");
+	exit(1);
+    }
+    return display;
+}
+
+static wayland_window *wayland_create_window(wayland_display *display)
+{
+    wayland_window *window;
+
+    window = g_new0(wayland_window, 1);
+    window->display = display;
+    window->surface = wl_compositor_create_surface(display->compositor);
+    wl_surface_set_user_data(window->surface, window);
+
+    if (display->wm_base) {
+        window->xdg_surface = xdg_wm_base_get_xdg_surface(display->wm_base,
+	                                                  window->surface);
+        assert(window->xdg_surface);
+        xdg_surface_add_listener(window->xdg_surface,
+                                 &xdg_surface_listener, window);
+        window->xdg_toplevel = xdg_surface_get_toplevel(window->xdg_surface);
+        assert(window->xdg_toplevel);
+        xdg_toplevel_add_listener(window->xdg_toplevel,
+                                  &xdg_toplevel_listener, window);
+        xdg_toplevel_set_title(window->xdg_toplevel, "qemu-wayland");
+        window->wait_for_configure = true;
+        wl_surface_commit(window->surface);
+    } else if (display->fshell) {
+        zwp_fullscreen_shell_v1_present_surface(display->fshell,
+	                        window->surface,
+		                ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_DEFAULT,
+		                NULL);
+    } else {
+         assert(0);
+    }
+    wl_display_roundtrip(display->display);
+    return window;
+}
+
+static void wayland_prepare_for_events(struct wl_display *display)
+{
+    while (wl_display_prepare_read(display) != 0)
+        wl_display_dispatch_pending(display);
+    wl_display_flush(display);
+}
+
+static void wayland_dispatch_handler(void *opaque)
+{
+    wayland_display *display = opaque;
+
+    wl_display_read_events(display->display);
+    wl_display_dispatch_pending(display->display);
+    wayland_prepare_for_events(display->display);
+}
+
+static void wayland_init(DisplayState *ds, DisplayOptions *opts)
+{
+    QemuConsole *con;
+    wayland_display *display;
+    wayland_window *window;
+    int idx;
+
+    warn_report("Wayland UI backend should not be used with Guest compositors \
+                 that do single buffer/frontbuffer rendering\n");
+
+    display = wayland_create_display();
+    for (idx = 0;; idx++) {
+        con = qemu_console_lookup_by_index(idx);
+        if (!con || !qemu_console_is_graphic(con)) {
+            break;
+        }
+
+        window = wayland_create_window(display);
+        window->dcl.con = con;
+        window->dcl.ops = &wayland_ops;
+        window->kbd = qkbd_state_init(con);
+        register_displaychangelistener(&window->dcl);
+    }
+    wayland_prepare_for_events(display->display);
+    qemu_set_fd_handler(wl_display_get_fd(display->display),
+                        wayland_dispatch_handler, NULL, display);
+}
+
+static void early_wayland_init(DisplayOptions *opts)
+{
+    display_opengl = 1;
+}
+
+static QemuDisplay qemu_display_wayland = {
+    .type       = DISPLAY_TYPE_WAYLAND,
+    .early_init = early_wayland_init,
+    .init       = wayland_init,
+};
+
+static void register_wayland(void)
+{
+    qemu_display_register(&qemu_display_wayland);
+}
+
+type_init(register_wayland);