@@ -403,6 +403,7 @@ cfi_debug="false"
seccomp="auto"
glusterfs="auto"
gtk="auto"
+wayland="auto"
tls_priority="NORMAL"
gnutls="$default_feature"
nettle="$default_feature"
@@ -1372,6 +1373,10 @@ for opt do
;;
--enable-gtk) gtk="enabled"
;;
+ --disable-wayland) wayland="disabled"
+ ;;
+ --enable-wayland) wayland="enabled"
+ ;;
--tls-priority=*) tls_priority="$optarg"
;;
--disable-gnutls) gnutls="no"
@@ -1845,6 +1850,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
@@ -3616,6 +3622,11 @@ if $pkg_config gbm; then
gbm="yes"
fi
+if $pkg_config wayland-client; then
+ wayland_cflags="$($pkg_config --cflags wayland-client)"
+ wayland_libs="$($pkg_config --libs wayland-client)"
+fi
+
if test "$opengl" != "no" ; then
epoxy=no
if $pkg_config epoxy; then
@@ -5870,6 +5881,11 @@ if test "$gbm" = "yes" ; then
echo "GBM_CFLAGS=$gbm_cflags" >> $config_host_mak
fi
+if test "$wayland" = "enabled" ; then
+ echo "CONFIG_WAYLAND=y" >> $config_host_mak
+ echo "WAYLAND_LIBS=$wayland_libs" >> $config_host_mak
+ echo "WAYLAND_CFLAGS=$wayland_cflags" >> $config_host_mak
+fi
if test "$avx2_opt" = "yes" ; then
echo "CONFIG_AVX2_OPT=y" >> $config_host_mak
@@ -6436,6 +6452,7 @@ if test "$skip_meson" = no; then
-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 \
+ -Dwayland=$wayland \
-Dvnc=$vnc -Dvnc_sasl=$vnc_sasl -Dvnc_jpeg=$vnc_jpeg -Dvnc_png=$vnc_png \
-Dgettext=$gettext -Dxkbcommon=$xkbcommon -Du2f=$u2f -Dvirtiofsd=$virtiofsd \
-Dcapstone=$capstone -Dslirp=$slirp -Dfdt=$fdt -Dbrlapi=$brlapi \
@@ -855,6 +855,29 @@ 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],
+ compile_args: config_host['WAYLAND_CFLAGS'].split(),
+ link_args: config_host['WAYLAND_LIBS'].split())
+endif
+
vnc = not_found
png = not_found
jpeg = not_found
@@ -1146,6 +1169,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_LIBATTR', have_old_libattr)
config_host_data.set('CONFIG_LIBCAP_NG', libcap_ng.found())
config_host_data.set('CONFIG_EBPF', libbpf.found())
@@ -2695,6 +2719,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': config_host.has_key('CONFIG_VTE')}
@@ -86,6 +86,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',
@@ -1057,6 +1057,20 @@
{ 'struct' : 'DisplayEGLHeadless',
'data' : { '*rendernode' : 'str' } }
+##
+# @DisplayWayland:
+#
+# Wayland display options.
+#
+# @rendernode: Which DRM render node should be used. Default is the first
+# available node on the host.
+#
+# Since: 3.1
+#
+##
+{ 'struct' : 'DisplayWayland',
+ 'data' : { '*rendernode' : 'str' } }
+
##
# @DisplayGLMode:
#
@@ -1108,6 +1122,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
@@ -1128,7 +1144,7 @@
{ 'enum' : 'DisplayType',
'data' : [ 'default', 'none', 'gtk', 'sdl',
'egl-headless', 'curses', 'cocoa',
- 'spice-app'] }
+ 'wayland', 'spice-app'] }
##
# @DisplayOptions:
@@ -1154,6 +1170,7 @@
'discriminator' : 'type',
'data' : { 'gtk' : 'DisplayGTK',
'curses' : 'DisplayCurses',
+ 'wayland' : 'DisplayWayland',
'egl-headless' : 'DisplayEGLHeadless'} }
##
@@ -62,6 +62,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'))
new file mode 100644
@@ -0,0 +1,402 @@
+/*
+ * Wayland UI -- A simple Qemu UI backend to share buffers with Wayland compositors
+ *
+ * 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 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 <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 3
+
+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;
+ 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;
+ bool redraw;
+} wayland_window;
+
+static const struct wl_callback_listener frame_listener;
+
+static void
+xdg_surface_handle_configure(void *data, struct xdg_surface *surface,
+ uint32_t serial)
+{
+ xdg_surface_ack_configure(surface, serial);
+}
+
+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_dispatch_handler(void *opaque)
+{
+ wayland_display *wldpy = opaque;
+
+ wl_display_prepare_read(wldpy->display);
+ wl_display_read_events(wldpy->display);
+ wl_display_dispatch_pending(wldpy->display);
+ wl_display_flush(wldpy->display);
+}
+
+static void wayland_window_redraw(void *data, struct wl_callback *callback,
+ uint32_t time)
+{
+ wayland_window *window = data;
+ QemuDmaBuf *dmabuf = window->new_buffer->dmabuf;
+
+ if (callback) {
+ wl_callback_destroy(callback);
+ window->callback = NULL;
+ }
+ if (!window->redraw) {
+ return;
+ }
+ 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, dmabuf->width, dmabuf->height);
+ wl_surface_commit(window->surface);
+ wl_display_flush(window->display->display);
+ window->redraw = false;
+}
+
+static const struct wl_callback_listener frame_listener = {
+ wayland_window_redraw
+};
+
+static void buffer_release(void *data, struct wl_buffer *buf)
+{
+ wayland_buffer *buffer = data;
+ QemuDmaBuf *dmabuf = buffer->dmabuf;
+
+ dmabuf->fence_fd = -1;
+ graphic_hw_gl_block(buffer->con, false);
+ graphic_hw_gl_flushed(buffer->con);
+ buffer->busy = false;
+ wl_buffer_destroy(buf);
+}
+
+static const struct wl_buffer_listener buffer_listener = {
+ 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;
+ 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);
+ static bool first = true;
+
+ if (!window->new_buffer->busy && !first) {
+ graphic_hw_gl_block(window->new_buffer->con, true);
+ }
+
+ window->redraw = true;
+ if (first || !window->callback) {
+ wayland_window_redraw(window, NULL, 0);
+ }
+ window->new_buffer->busy = true;
+ first = false;
+}
+
+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 void early_wayland_init(DisplayOptions *opts)
+{
+ display_opengl = 1;
+}
+
+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
+registry_handle_global(void *data, struct wl_registry *registry,
+ uint32_t id, const char *interface, uint32_t version)
+{
+ wayland_display *d = data;
+
+ if (strcmp(interface, "wl_compositor") == 0) {
+ d->compositor = wl_registry_bind(registry,
+ id, &wl_compositor_interface, 1);
+ } else if (strcmp(interface, "xdg_wm_base") == 0) {
+ d->wm_base = wl_registry_bind(registry,
+ id, &xdg_wm_base_interface, 1);
+ xdg_wm_base_add_listener(d->wm_base, &wm_base_listener, d);
+ } else if (strcmp(interface, "zwp_fullscreen_shell_v1") == 0) {
+ d->fshell = wl_registry_bind(registry,
+ id, &zwp_fullscreen_shell_v1_interface,
+ 1);
+ } else if (strcmp(interface, "zwp_linux_dmabuf_v1") == 0) {
+ d->dmabuf = wl_registry_bind(registry,
+ id, &zwp_linux_dmabuf_v1_interface, 3);
+ zwp_linux_dmabuf_v1_add_listener(d->dmabuf, &dmabuf_listener,
+ d);
+ }
+}
+
+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 wayland_display *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,
+ ®istry_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 *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);
+
+ 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");
+ 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);
+ }
+
+ return window;
+}
+
+static void wayland_init(DisplayState *ds, DisplayOptions *opts)
+{
+ QemuConsole *con;
+ wayland_display *wldpy;
+ wayland_window *wlwdw;
+ int idx;
+
+ wldpy = create_display();
+ for (idx = 0;; idx++) {
+ con = qemu_console_lookup_by_index(idx);
+ if (!con || !qemu_console_is_graphic(con)) {
+ break;
+ }
+
+ wlwdw = create_window(wldpy);
+ wlwdw->dcl.con = con;
+ wlwdw->dcl.ops = &wayland_ops;
+ register_displaychangelistener(&wlwdw->dcl);
+ }
+ wl_display_roundtrip(wldpy->display);
+ qemu_set_fd_handler(wl_display_get_fd(wldpy->display),
+ wayland_dispatch_handler, NULL, wldpy);
+}
+
+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);
Cc: Gerd Hoffmann <kraxel@redhat.com> Signed-off-by: Vivek Kasireddy <vivek.kasireddy@intel.com> --- configure | 17 ++ meson.build | 25 +++ meson_options.txt | 2 + qapi/ui.json | 19 ++- ui/meson.build | 52 ++++++ ui/wayland.c | 402 ++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 516 insertions(+), 1 deletion(-) create mode 100644 ui/wayland.c