FLTK logo

[master] 535211c - Wayland platform: initial commit

FLTK matrix user chat room
(using Element browser app)   FLTK gitter user chat room   GitHub FLTK Project   FLTK News RSS Feed  
  FLTK Apps      FLTK Library      Forums      Links     Login 
 All Forums  |  Back to fltk.commit  ]
 
Previous Message ]Next Message ]

[master] 535211c - Wayland platform: initial commit "ManoloFLTK" Dec 02, 2021  
 
commit 535211cc82747af4a508bc288217a7ec49a34f88
Author:     ManoloFLTK <41016272+ManoloFLTK@users.noreply.github.com>
AuthorDate: Wed May 26 15:16:35 2021 +0200
Commit:     ManoloFLTK <41016272+ManoloFLTK@users.noreply.github.com>
CommitDate: Wed Sep 29 12:14:55 2021 +0200

    Wayland platform: initial commit

 .gitignore                                         |    2 +
 FL/platform.H                                      |    2 +
 FL/platform_types.h                                |   11 +
 configure.ac                                       |   54 +-
 examples/OpenGL3-glut-test.cxx                     |    5 +-
 examples/OpenGL3test.cxx                           |    5 +
 fluid/Makefile                                     |    1 +
 libdecor/LICENSE                                   |   19 +
 libdecor/README.md                                 |   70 +
 libdecor/build/Makefile                            |   54 +
 libdecor/demo/demo.c                               | 1286 +++++++++
 libdecor/demo/egl.c                                |  314 +++
 libdecor/src/cursor-settings.c                     |  136 +
 libdecor/src/cursor-settings.h                     |    6 +
 libdecor/src/libdecor-fallback.c                   |  208 ++
 libdecor/src/libdecor-fallback.h                   |   35 +
 libdecor/src/libdecor-plugin.h                     |  176 ++
 libdecor/src/libdecor.c                            | 1512 +++++++++++
 libdecor/src/libdecor.h                            |  291 +++
 libdecor/src/plugins/cairo/libdecor-cairo-blur.c   |  255 ++
 libdecor/src/plugins/cairo/libdecor-cairo-blur.h   |   10 +
 libdecor/src/plugins/cairo/libdecor-cairo.c        | 2764 ++++++++++++++++++++
 libdecor/src/plugins/dummy/libdecor-dummy.c        |  176 ++
 libdecor/src/utils.h                               |   48 +
 src/Makefile                                       |   34 +
 src/drivers/Wayland/Fl_Font.H                      |   33 +
 .../Wayland/Fl_Wayland_Copy_Surface_Driver.cxx     |   72 +
 .../Wayland/Fl_Wayland_Gl_Window_Driver.cxx        |  559 ++++
 src/drivers/Wayland/Fl_Wayland_Graphics_Driver.H   |   89 +
 src/drivers/Wayland/Fl_Wayland_Graphics_Driver.cxx |  669 +++++
 .../Wayland/Fl_Wayland_Image_Surface_Driver.cxx    |  105 +
 src/drivers/Wayland/Fl_Wayland_Screen_Driver.H     |  175 ++
 src/drivers/Wayland/Fl_Wayland_Screen_Driver.cxx   | 1395 ++++++++++
 src/drivers/Wayland/Fl_Wayland_System_Driver.H     |   68 +
 src/drivers/Wayland/Fl_Wayland_System_Driver.cxx   |  523 ++++
 src/drivers/Wayland/Fl_Wayland_Window_Driver.H     |  168 ++
 src/drivers/Wayland/Fl_Wayland_Window_Driver.cxx   | 1150 ++++++++
 src/drivers/Wayland/Fl_get_key_wayland.cxx         |   38 +
 src/drivers/Wayland/Fl_wayland.cxx                 |  930 +++++++
 src/drivers/Wayland/wayland.H                      |   31 +
 40 files changed, 13477 insertions(+), 2 deletions(-)

diff --git .gitignore .gitignore
index 21d2cd1..0ddf88e 100644
--- .gitignore
+++ .gitignore
@@ -121,3 +121,5 @@ etc/FLTKConfig.cmake
 # see test/.gitignore
 
 **/.DS_Store
+
+save*wayland*
diff --git FL/platform.H FL/platform.H
index 7271b1f..1de9fe9 100644
--- FL/platform.H
+++ FL/platform.H
@@ -39,6 +39,8 @@ class Fl_Window;
 #    include "mac.H"
 #  elif defined(__ANDROID__)
 #    include "android.H"
+#  elif defined(__WAYLAND__)
+#    include "../src/drivers/Wayland/wayland.H"
 #  else // X11
 #   include <FL/fl_types.h>
 #   include <FL/Enumerations.H>
diff --git FL/platform_types.h FL/platform_types.h
index 9b3fa84..4987fe7 100644
--- FL/platform_types.h
+++ FL/platform_types.h
@@ -120,6 +120,17 @@ typedef struct HGLRC__ *GLContext;
    struct dirent {char d_name[1];};
 #endif
 
+#elif defined(__WAYLAND__)
+typedef struct buffer *Fl_Offscreen; /**< an offscreen drawing buffer */
+typedef void* Fl_Bitmask; /**< mask */
+typedef struct flWaylandRegion* Fl_Region;
+typedef int FL_SOCKET; /**< socket or file descriptor */
+typedef void *EGLContext;
+typedef EGLContext GLContext;
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <dirent.h>
+
 #elif defined(__ANDROID__)
 
 #ifdef __cplusplus
diff --git configure.ac configure.ac
index 68ca0a5..77fabe0 100644
--- configure.ac
+++ configure.ac
@@ -109,6 +109,8 @@ AC_ARG_ENABLE([localzlib], AS_HELP_STRING([--enable-localzlib], [use local ZLIB
 
 AC_ARG_ENABLE([pango], AS_HELP_STRING([--enable-pango], [turn on Pango support]))
 
+AC_ARG_ENABLE([wayland], AS_HELP_STRING([--enable-wayland], [turn on Wayland support]))
+
 AC_ARG_ENABLE([print], AS_HELP_STRING([--disable-print], [turn off print support (X11)]))
 AS_IF([test x$enable_print = xno], [
     AC_DEFINE([FL_NO_PRINT_SUPPORT], [Disable X11 print support?])
@@ -996,6 +998,52 @@ AS_CASE([$host_os_gui], [cygwin* | mingw*], [
         THREADS="threads$EXEEXT"
     ])
 
+    AS_IF([test x$enable_wayland = xyes], [
+      dnl Prepare for Wayland...
+      
+      AS_IF([test x$PKGCONFIG = x], [
+          dnl pkg-config is not available, issue warning and abort...
+          AC_MSG_WARN([--enable-wayland: please install pkg-config.])
+          AC_MSG_ERROR([Aborting.])
+      ])
+
+      BUILD="WAYLAND"
+      graphics="Wayland"
+      LIBS="$LIBS -L../lib -ldecor-0.1 $($PKGCONFIG --libs dbus-1) -ldl -Wl,-rpath,\`readlink -f ../lib\` -no-pie"
+      LIBS="$($PKGCONFIG --libs wayland-cursor) $($PKGCONFIG --libs wayland-client) $($PKGCONFIG --libs xkbcommon) $LIBS"
+      CFLAGS="$CFLAGS -D__WAYLAND__"
+      CXXFLAGS="$CXXFLAGS -D__WAYLAND__ -I../libdecor/src"
+      LDFLAGS="$LIBS $LDFLAGS"
+      DSOFLAGS="$LIBS $DSOFLAGS"
+      enable_pango=yes
+    
+      AC_SEARCH_LIBS([dlopen], [dl])
+      AC_CHECK_HEADER([GL/gl.h], [AC_DEFINE([HAVE_GL])])
+      AC_CHECK_HEADER([GL/glu.h], [
+          AC_DEFINE([HAVE_GL_GLU_H])
+          GLLIBS="$($PKGCONFIG --libs wayland-egl) $($PKGCONFIG --libs egl) $($PKGCONFIG --libs glu) $GLLIBS"
+      ])
+        
+      dnl Check for the Pango library ...
+      pango_found=no
+      CXXFLAGS="$($PKGCONFIG --cflags pangocairo) $CXXFLAGS"
+      LIBS="$($PKGCONFIG --libs pangocairo) $LIBS"
+ 
+      CPPFLAGS="$CXXFLAGS"
+      AC_CHECK_HEADERS([pango/pangocairo.h], [
+          AC_DEFINE([USE_PANGO])
+          AC_DEFINE([USE_XFT])
+          pango_found=yes
+      ])
+
+      dnl Early abort if Pango could not be found
+      AS_IF([test x$pango_found != xyes], [
+          AC_MSG_NOTICE([--enable-wayland: Pango libs and/or headers could not be found.])
+          AC_MSG_ERROR([Aborting.])
+      ])
+
+    ], [
+
     dnl Check for X11...
     AC_PATH_XTRA
 
@@ -1224,6 +1272,8 @@ AS_CASE([$host_os_gui], [cygwin* | mingw*], [
     AS_IF([test x$ac_cv_have_overlay = xyes], [
         AC_DEFINE([HAVE_OVERLAY])
     ])
+    
+    ])
 
     # Make symlinks since UNIX/Linux is case sensitive,
     # but Cygwin in general not.
@@ -1560,7 +1610,9 @@ AS_CASE([$host_os_gui], [cygwin* | mingw*], [
 ], [darwin*], [
     graphics="Quartz"
 ], [*], [
-    graphics="X11"
+    AS_IF([test x$enable_wayland != xyes], [
+        graphics="X11"
+    ])
     AS_IF([test x$xft_found = xyes], [
         graphics="$graphics + Xft"
     ])
diff --git examples/OpenGL3-glut-test.cxx examples/OpenGL3-glut-test.cxx
index 75ff3b0..d21de6b 100644
--- examples/OpenGL3-glut-test.cxx
+++ examples/OpenGL3-glut-test.cxx
@@ -198,7 +198,10 @@ int main (int argc, char* argv[])
   glutCreateWindow("Triangle Test");
 #ifndef __APPLE__
   GLenum err = glewInit(); // defines pters to functions of OpenGL V 1.2 and above
-  if (err) Fl::error("glewInit() failed returning %u", err);
+#ifdef __WAYLAND__
+  if (err == GLEW_ERROR_NO_GLX_DISPLAY) err = GLEW_OK;
+#endif
+  if (err != GLEW_OK) Fl::error("glewInit() failed returning %u", err);
   fprintf(stderr, "Status: Using GLEW %s\n", glewGetString(GLEW_VERSION));
 #endif
   int gl_version_major;
diff --git examples/OpenGL3test.cxx examples/OpenGL3test.cxx
index 1b79835..cbafe2d 100644
--- examples/OpenGL3test.cxx
+++ examples/OpenGL3test.cxx
@@ -146,6 +146,11 @@ public:
       make_current();
 #ifndef __APPLE__
       GLenum err = glewInit(); // defines pters to functions of OpenGL V 1.2 and above
+#  ifdef __WAYLAND__
+      // glewInit returns GLEW_ERROR_NO_GLX_DISPLAY with Wayland
+      // see https://github.com/nigels-com/glew/issues/273
+      if (err == GLEW_ERROR_NO_GLX_DISPLAY) err = GLEW_OK;
+#  endif
       if (err) Fl::warning("glewInit() failed returning %u", err);
       else add_output("Using GLEW %s\n", glewGetString(GLEW_VERSION));
 #endif
diff --git fluid/Makefile fluid/Makefile
index 7d946cb..9a1b2db 100644
--- fluid/Makefile
+++ fluid/Makefile
@@ -44,6 +44,7 @@ CPPFILES_WIN = ExternalCodeEditor_WIN32.cxx
 CPPFILES_OSX = ExternalCodeEditor_UNIX.cxx
 CPPFILES_X11 = ExternalCodeEditor_UNIX.cxx
 CPPFILES_XFT = ExternalCodeEditor_UNIX.cxx
+CPPFILES_WAYLAND = ExternalCodeEditor_UNIX.cxx
 
 CPPFILES += $(CPPFILES_$(BUILD))
 
diff --git libdecor/LICENSE libdecor/LICENSE
new file mode 100644
index 0000000..9cf1062
--- /dev/null
+++ libdecor/LICENSE
@@ -0,0 +1,19 @@
+MIT License
+
+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.
diff --git libdecor/README.md libdecor/README.md
new file mode 100644
index 0000000..af372f2
--- /dev/null
+++ libdecor/README.md
@@ -0,0 +1,70 @@
+# libdecor - A client-side decorations library for Wayland client
+
+libdecor is a library that can help Wayland clients draw window
+decorations for them. It aims to provide multiple backends that implements the
+decoration drawing.
+
+
+## Dependencies
+
+Required:
+- `meson` >= 0.47
+- `ninja`
+- `wayland-client` >= 1.18
+- `wayland-protocols` >= 1.15
+- `wayland-cursor`
+- `cairo`
+- `pangocairo`
+
+Recommended:
+- `dbus-1` (to query current cursor theme)
+
+Optional
+- `egl` (to build EGL example)
+- `xkbcommon` (to build cairo demo)
+
+Install via apt:
+`sudo apt install meson libwayland-dev wayland-protocols libpango1.0-dev libdbus-1-dev libegl-dev libxkbcommon-dev`
+
+Install via dnf:
+`sudo dnf install meson wayland-devel wayland-protocols-devel pango-devel dbus-devel mesa-libEGL-devel libxkbcommon-devel`
+
+Newer meson versions can be installed via pip: `pip3 install -U meson`.
+
+## Build & Install
+
+### Quick Start
+
+To build and run the example program:
+1. `meson build && meson compile -C build`
+2. `LIBDECOR_PLUGIN_DIR=build/src/plugins/cairo/ ./build/demo/libdecor-demo`
+
+### Release Builds
+
+The library and default plugins can be built and installed via:
+1. `meson build --buildtype release`
+2. `meson install -C build`
+
+where `build` is the build directory that will be created during this process.
+
+This will install by default to `/usr/local/`. To change this set the `prefix` during built, e.g. `meson build --buildtype release -Dprefix=$HOME/.local/`.
+
+Plugins will be installed into the same directory and from thereon will be selected automatically depending on their precedence. This behaviour can be overridden at runtime by setting the environment variable `LIBDECOR_PLUGIN_DIR` and pointing it to a directory with a valid plugin.
+
+### Debug and Development Builds
+
+During development and when debugging, it is recommended to enable the AddressSanitizer and increase the warning level:
+1. `meson build -Db_sanitize=address -Dwarning_level=3`
+2. `meson compile -C build`
+
+You may have to install `libasan6` (apt) or `libasan` (dnf). Otherwise linking will fail.
+
+By default `libdecor` will look for plugins in the target directory of the installation. Therefore, when running the demos directly from the `build` directory, no plugins will be found and the fallback plugin without any decorations will be used.
+
+The search path for plugins can be overridden by the environment variable `LIBDECOR_PLUGIN_DIR`. To use the `cairo` plugin, point to the plugin directory:
+
+`export LIBDECOR_PLUGIN_DIR=build/src/plugins/cairo/`
+
+and run the demo:
+
+`./build/demo/libdecor-demo`.
diff --git libdecor/build/Makefile libdecor/build/Makefile
new file mode 100755
index 0000000..1c9268a
--- /dev/null
+++ libdecor/build/Makefile
@@ -0,0 +1,54 @@
+CC = gcc
+BUILD = `readlink -f ../../lib`
+
+CFLAGS = -I. -I../src -fPIC 
+
+all : demo 
+
+config.h :
+	touch config.h
+
+libdecor.o : ../src/libdecor.c xdg-shell-protocol.c xdg-decoration-protocol.c config.h
+	$(CC) $(CFLAGS) -c  ../src/libdecor.c -DLIBDECOR_PLUGIN_API_VERSION=1 -DLIBDECOR_PLUGIN_DIR=\"$(BUILD)\" 
+
+libdecor-fallback.o : ../src/libdecor-fallback.c
+	$(CC) $(CFLAGS) -c  ../src/libdecor-fallback.c
+
+libdecor-cairo.o : ../src/plugins/cairo/libdecor-cairo.c
+	$(CC) $(CFLAGS) -c  ../src/plugins/cairo/libdecor-cairo.c -D_GNU_SOURCE -DLIBDECOR_PLUGIN_API_VERSION=1 `pkg-config --cflags pangocairo`
+
+
+libdecor-cairo-blur.o : ../src/plugins/cairo/libdecor-cairo-blur.c
+	$(CC) $(CFLAGS) -c  ../src/plugins/cairo/libdecor-cairo-blur.c -D_GNU_SOURCE
+
+cursor-settings.o : ../src/cursor-settings.c
+	$(CC) $(CFLAGS)  -c ../src/cursor-settings.c -DHAS_DBUS -I/usr/include/dbus-1.0 -I/usr/lib/x86_64-linux-gnu/dbus-1.0/include 
+
+xdg-shell-protocol.c :
+	wayland-scanner private-code < /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml  > xdg-shell-protocol.c
+	wayland-scanner client-header < /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml > \
+	    xdg-shell-client-protocol.h
+
+xdg-decoration-protocol.c :
+	wayland-scanner private-code < /usr/share/wayland-protocols/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml \
+	    > xdg-decoration-protocol.c
+	wayland-scanner client-header < /usr/share/wayland-protocols/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml \
+	    > xdg-decoration-client-protocol.h
+
+xdg-decoration-protocol.o : xdg-decoration-protocol.c
+	$(CC) $(CFLAGS) -c xdg-decoration-protocol.c
+
+xdg-shell-protocol.o : xdg-shell-protocol.c
+	$(CC) $(CFLAGS) -c xdg-shell-protocol.c
+	
+#this shared lib is used by Wayland client programs and also as a plugin (for the Wayland composer ?)
+libdecor-0.1.so : libdecor.o libdecor-cairo-blur.o libdecor-cairo.o libdecor-fallback.o xdg-decoration-protocol.o \
+		xdg-shell-protocol.o cursor-settings.o
+	$(CC) -shared  libdecor.o libdecor-cairo-blur.o libdecor-cairo.o libdecor-fallback.o xdg-decoration-protocol.o \
+		xdg-shell-protocol.o cursor-settings.o -o ../../lib/libdecor-0.1.so -lcairo
+
+demo : libdecor-0.1.so ../demo/demo.c  
+	$(CC)  -o demo ../demo/demo.c  xdg-decoration-protocol.o xdg-shell-protocol.o -D_GNU_SOURCE -I../src -I. -L../../lib -ldecor-0.1  -lwayland-cursor -lwayland-client -lxkbcommon -lpangocairo-1.0 -ldl -ldbus-1 -Wl,-rpath,$(BUILD)
+
+clean:
+	rm *.o ../../lib/libdecor-0.1.so
diff --git libdecor/demo/demo.c libdecor/demo/demo.c
new file mode 100644
index 0000000..0d1820f
--- /dev/null
+++ libdecor/demo/demo.c
@@ -0,0 +1,1286 @@
+/*
+ * Copyright © 2011 Benjamin Franzke
+ * Copyright © 2010 Intel Corporation
+ * Copyright © 2018 Jonas �dahl
+ *
+ * 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 (including the
+ * next paragraph) 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 "config.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/input.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <wayland-cursor.h>
+#include <xkbcommon/xkbcommon.h>
+
+#include "libdecor.h"
+#include "utils.h"
+#include "cursor-settings.h"
+
+#include "xdg-shell-client-protocol.h"
+
+struct window;
+
+static const size_t chk = 16;
+static const int DEFAULT_WIDTH = 30*chk;
+static const int DEFAULT_HEIGHT = 20*chk;
+
+static const int POPUP_WIDTH = 100;
+static const int POPUP_HEIGHT = 300;
+
+static const char *proxy_tag = "libdecor-demo";
+
+static const char *titles[] = {
+	"Hello!",
+	"Hallå!",
+	"Ð?Ñ?ивеÑ?!",
+	"Î?ειά Ï?οÏ?!",
+	"ש×?×?×?!",
+	"你好�",
+	"สวัส�ี!",
+	"ã??ã??ã?«ã?¡ã?¯ï¼?",
+	"ð??»â?¤ï¸?ð?¤?â??ð??°",
+};
+
+static const size_t N_TITLES = ARRAY_SIZE(titles);
+
+
+static bool
+own_proxy(struct wl_proxy *proxy)
+{
+	return (wl_proxy_get_tag(proxy) == &proxy_tag);
+}
+
+static bool
+own_output(struct wl_output *output)
+{
+	return own_proxy((struct wl_proxy *) output);
+}
+
+struct buffer {
+	struct wl_buffer *wl_buffer;
+	void *data;
+	size_t data_size;
+};
+
+struct popup {
+	struct wl_surface *wl_surface;
+	struct xdg_surface *xdg_surface;
+	struct xdg_popup *xdg_popup;
+	struct xdg_surface *parent;
+	struct seat *seat;
+	struct window *window;
+};
+
+struct window {
+	struct wl_surface *wl_surface;
+	struct buffer *buffer;
+	struct libdecor_frame *frame;
+	int content_width;
+	int content_height;
+	int configured_width;
+	int configured_height;
+	enum libdecor_window_state window_state;
+	struct wl_list outputs;
+	int scale;
+	struct popup *popup;
+	size_t title_index;
+};
+
+struct seat {
+	struct wl_seat *wl_seat;
+	struct wl_keyboard *wl_keyboard;
+	struct wl_pointer *wl_pointer;
+	struct wl_list link;
+	struct wl_list pointer_outputs;
+	struct wl_cursor_theme *cursor_theme;
+	struct wl_cursor *left_ptr_cursor;
+	struct wl_surface *cursor_surface;
+	struct wl_surface *pointer_focus;
+	int pointer_scale;
+	uint32_t serial;
+	wl_fixed_t pointer_sx;
+	wl_fixed_t pointer_sy;
+	char *name;
+
+	struct xkb_context *xkb_context;
+	struct xkb_state *xkb_state;
+};
+
+struct output {
+	uint32_t id;
+	struct wl_output *wl_output;
+	int scale;
+	struct wl_list link;
+};
+
+struct window_output {
+	struct output* output;
+	struct wl_list link;
+};
+
+struct pointer_output {
+	struct output* output;
+	struct wl_list link;
+};
+
+static struct wl_compositor *wl_compositor;
+static struct wl_shm *wl_shm;
+static struct xdg_wm_base *xdg_wm_base;
+static struct wl_list seats;
+static struct wl_list outputs;
+
+static bool has_xrgb = false;
+
+static struct window *window;
+
+static void
+redraw(struct window *window);
+
+static void
+resize(struct window *window, int width, int height)
+{
+	struct libdecor_state *state;
+
+	if (!libdecor_frame_is_floating(window->frame)) {
+		printf("... ignoring in non-floating mode\n");
+		return;
+	}
+
+	/* commit changes to decorations */
+	state = libdecor_state_new( width, height);
+	libdecor_frame_commit(window->frame, state, NULL);
+	libdecor_state_free(state);
+	/* force redraw of content and commit */
+	window->configured_width = width;
+	window->configured_height = height;
+	redraw(window);
+}
+
+static struct buffer *
+create_shm_buffer(int width,
+		  int height,
+		  uint32_t format);
+
+static void
+update_scale(struct window *window)
+{
+	int scale = 1;
+	struct window_output *window_output;
+
+	wl_list_for_each(window_output, &window->outputs, link) {
+		scale = MAX(scale, window_output->output->scale);
+	}
+	if (scale != window->scale) {
+		window->scale = scale;
+		redraw(window);
+	}
+}
+
+static void
+shm_format(void *data,
+	   struct wl_shm *wl_shm,
+	   uint32_t format)
+{
+	if (format == WL_SHM_FORMAT_XRGB8888)
+		has_xrgb = true;
+}
+
+static struct wl_shm_listener shm_listener = {
+	shm_format
+};
+
+static void
+try_update_cursor(struct seat *seat);
+
+static void
+cursor_surface_enter(void *data,
+	      struct wl_surface *wl_surface,
+	      struct wl_output *wl_output)
+{
+	struct seat *seat = data;
+	struct pointer_output *pointer_output;
+
+	if (!own_output(wl_output))
+		return;
+
+	pointer_output = zalloc(sizeof *pointer_output);
+	pointer_output->output = wl_output_get_user_data(wl_output);
+	wl_list_insert(&seat->pointer_outputs, &pointer_output->link);
+	try_update_cursor(seat);
+}
+
+static void
+cursor_surface_leave(void *data,
+	      struct wl_surface *wl_surface,
+	      struct wl_output *wl_output)
+{
+	struct seat *seat = data;
+	struct pointer_output *pointer_output, *tmp;
+
+	wl_list_for_each_safe(pointer_output, tmp, &seat->pointer_outputs, link) {
+		if (pointer_output->output->wl_output == wl_output) {
+			wl_list_remove(&pointer_output->link);
+			free(pointer_output);
+		}
+	}
+}
+
+static struct wl_surface_listener cursor_surface_listener = {
+	cursor_surface_enter,
+	cursor_surface_leave,
+};
+
+static void
+init_cursors(struct seat *seat)
+{
+	char *name;
+	int size;
+	struct wl_cursor_theme *theme;
+
+	if (!libdecor_get_cursor_settings(&name, &size)) {
+		name = NULL;
+		size = 24;
+	}
+	size *= seat->pointer_scale;
+
+	theme = wl_cursor_theme_load(name, size, wl_shm);
+	free(name);
+	if (theme != NULL) {
+		if (seat->cursor_theme)
+			wl_cursor_theme_destroy(seat->cursor_theme);
+		seat->cursor_theme = theme;
+	}
+	if (seat->cursor_theme)
+		seat->left_ptr_cursor
+		  = wl_cursor_theme_get_cursor(seat->cursor_theme, "left_ptr");
+	if (!seat->cursor_surface) {
+		seat->cursor_surface = wl_compositor_create_surface(
+								wl_compositor);
+		wl_surface_add_listener(seat->cursor_surface,
+					&cursor_surface_listener, seat);
+	}
+}
+
+static void
+set_cursor(struct seat *seat)
+{
+	struct wl_cursor *wl_cursor;
+	struct wl_cursor_image *image;
+	struct wl_buffer *buffer;
+	const int scale = seat->pointer_scale;
+
+	if (!seat->cursor_theme)
+		return;
+
+	wl_cursor = seat->left_ptr_cursor;
+
+	image = wl_cursor->images[0];
+	buffer = wl_cursor_image_get_buffer(image);
+	wl_pointer_set_cursor(seat->wl_pointer, seat->serial,
+			      seat->cursor_surface,
+			      image->hotspot_x / scale,
+			      image->hotspot_y / scale);
+	wl_surface_attach(seat->cursor_surface, buffer, 0, 0);
+	wl_surface_set_buffer_scale(seat->cursor_surface, scale);
+	wl_surface_damage_buffer(seat->cursor_surface, 0, 0,
+				 image->width, image->height);
+	wl_surface_commit(seat->cursor_surface);
+}
+
+static void
+try_update_cursor(struct seat *seat)
+{
+	struct pointer_output *pointer_output;
+	int scale = 1;
+
+	wl_list_for_each(pointer_output, &seat->pointer_outputs, link) {
+		scale = MAX(scale, pointer_output->output->scale);
+	}
+
+	if (scale != seat->pointer_scale) {
+		seat->pointer_scale = scale;
+		init_cursors(seat);
+		set_cursor(seat);
+	}
+}
+
+static void
+pointer_enter(void *data,
+	      struct wl_pointer *wl_pointer,
+	      uint32_t serial,
+	      struct wl_surface *surface,
+	      wl_fixed_t surface_x,
+	      wl_fixed_t surface_y)
+{
+	struct seat *seat = data;
+
+	seat->pointer_focus = surface;
+	seat->serial = serial;
+
+	if (surface != window->wl_surface)
+		return;
+
+	set_cursor(seat);
+
+	seat->pointer_sx = surface_x;
+	seat->pointer_sy = surface_y;
+}
+
+static void
+pointer_leave(void *data,
+	      struct wl_pointer *wl_pointer,
+	      uint32_t serial,
+	      struct wl_surface *surface)
+{
+	struct seat *seat = data;
+	if (seat->pointer_focus == surface)
+		seat->pointer_focus = NULL;
+}
+
+static void
+pointer_motion(void *data,
+	       struct wl_pointer *wl_pointer,
+	       uint32_t time,
+	       wl_fixed_t surface_x,
+	       wl_fixed_t surface_y)
+{
+	struct seat *seat = data;
+
+	seat->pointer_sx = surface_x;
+	seat->pointer_sy = surface_y;
+}
+
+static struct xdg_positioner *
+create_positioner(struct seat *seat)
+{
+	struct xdg_positioner *positioner;
+	enum xdg_positioner_constraint_adjustment constraint_adjustment;
+	int x, y;
+
+	positioner = xdg_wm_base_create_positioner(xdg_wm_base);
+	xdg_positioner_set_size(positioner, POPUP_WIDTH, POPUP_HEIGHT);
+
+	libdecor_frame_translate_coordinate(window->frame,
+					    wl_fixed_to_int(seat->pointer_sx),
+					    wl_fixed_to_int(seat->pointer_sy),
+					    &x, &y);
+
+	xdg_positioner_set_anchor_rect(positioner, x, y, 1, 1);
+
+	constraint_adjustment = (XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y |
+				 XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X);
+	xdg_positioner_set_constraint_adjustment (positioner,
+						  constraint_adjustment);
+
+	xdg_positioner_set_anchor (positioner,
+				   XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT);
+	xdg_positioner_set_gravity (positioner,
+				    XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
+
+	return positioner;
+}
+
+static void
+xdg_popup_configure(void *data,
+		    struct xdg_popup *xdg_popup,
+		    int32_t x,
+		    int32_t y,
+		    int32_t width,
+		    int32_t height)
+{
+}
+
+static void
+popup_destroy(struct popup *popup)
+{
+	libdecor_frame_popup_ungrab(popup->window->frame,
+				    popup->seat->name);
+	xdg_popup_destroy(popup->xdg_popup);
+	xdg_surface_destroy(popup->xdg_surface);
+	wl_surface_destroy(popup->wl_surface);
+	popup->window->popup = NULL;
+	free(popup);
+}
+
+static void
+xdg_popup_done(void             *data,
+	       struct xdg_popup *xdg_popup)
+{
+	struct popup *popup = data;
+
+	popup_destroy(popup);
+}
+
+static const struct xdg_popup_listener xdg_popup_listener = {
+	xdg_popup_configure,
+	xdg_popup_done,
+};
+
+static void
+xdg_surface_configure(void *data,
+		      struct xdg_surface *xdg_surface,
+		      uint32_t serial)
+{
+	struct popup *popup = data;
+	uint32_t *pixels;
+	struct buffer *buffer;
+	int y;
+
+	buffer = create_shm_buffer(POPUP_WIDTH, POPUP_HEIGHT,
+				   WL_SHM_FORMAT_XRGB8888);
+	pixels = buffer->data;
+	for (y = 0; y < POPUP_HEIGHT; y++) {
+		int x;
+
+		for (x = 0; x < POPUP_WIDTH; x++)
+			pixels[y * POPUP_WIDTH + x] = 0xff4455ff;
+	}
+
+	wl_surface_attach(popup->wl_surface, buffer->wl_buffer, 0, 0);
+	wl_surface_set_buffer_scale(window->wl_surface, window->scale);
+	wl_surface_damage(window->wl_surface, 0, 0,
+			  POPUP_WIDTH, POPUP_HEIGHT);
+	xdg_surface_ack_configure(popup->xdg_surface, serial);
+	wl_surface_commit(popup->wl_surface);
+}
+
+static const struct xdg_surface_listener xdg_surface_listener = {
+	xdg_surface_configure,
+};
+
+static void
+open_popup(struct seat *seat)
+{
+	struct popup *popup;
+	struct xdg_positioner *positioner;
+
+	popup = zalloc(sizeof *popup);
+
+	popup->wl_surface = wl_compositor_create_surface(wl_compositor);
+	popup->xdg_surface = xdg_wm_base_get_xdg_surface (xdg_wm_base,
+							  popup->wl_surface);
+	popup->parent = libdecor_frame_get_xdg_surface(window->frame);
+	popup->window = window;
+	popup->seat = seat;
+	positioner = create_positioner(seat);
+	popup->xdg_popup = xdg_surface_get_popup(popup->xdg_surface,
+						 popup->parent,
+						 positioner);
+	xdg_positioner_destroy(positioner);
+
+	xdg_surface_add_listener (popup->xdg_surface,
+				  &xdg_surface_listener,
+				  popup);
+	xdg_popup_add_listener (popup->xdg_popup,
+				&xdg_popup_listener,
+				popup);
+
+	window->popup = popup;
+
+	xdg_popup_grab(popup->xdg_popup, seat->wl_seat, seat->serial);
+	wl_surface_commit(popup->wl_surface);
+
+	libdecor_frame_popup_grab(window->frame, seat->name);
+}
+
+static void
+close_popup(struct window *window)
+{
+	struct popup *popup = window->popup;
+
+	popup_destroy(popup);
+}
+
+static void
+pointer_button(void *data,
+	       struct wl_pointer *wl_pointer,
+	       uint32_t serial,
+	       uint32_t time,
+	       uint32_t button,
+	       uint32_t state)
+{
+	struct seat *seat = data;
+
+	if (seat->pointer_focus != window->wl_surface)
+		return;
+
+	seat->serial = serial;
+
+	if (window->popup &&
+	    state == WL_POINTER_BUTTON_STATE_PRESSED)
+		close_popup(window);
+
+	if (button == BTN_LEFT &&
+	    state == WL_POINTER_BUTTON_STATE_PRESSED) {
+		libdecor_frame_move(window->frame, seat->wl_seat, serial);
+	} else if (button == BTN_MIDDLE &&
+	    state == WL_POINTER_BUTTON_STATE_PRESSED) {
+		libdecor_frame_show_window_menu(window->frame,
+						seat->wl_seat,
+						serial,
+						wl_fixed_to_int(seat->pointer_sx),
+						wl_fixed_to_int(seat->pointer_sy));
+	} else if (button == BTN_RIGHT &&
+		   state == WL_POINTER_BUTTON_STATE_PRESSED) {
+		if (!window->popup)
+			open_popup(seat);
+	}
+}
+
+static void
+pointer_axis(void *data,
+	     struct wl_pointer *wl_pointer,
+	     uint32_t time,
+	     uint32_t axis,
+	     wl_fixed_t value)
+{
+}
+
+static struct wl_pointer_listener pointer_listener = {
+	pointer_enter,
+	pointer_leave,
+	pointer_motion,
+	pointer_button,
+	pointer_axis
+};
+
+static void
+keyboard_keymap(void *data,
+		struct wl_keyboard *wl_keyboard,
+		uint32_t format,
+		int32_t fd,
+		uint32_t size)
+{
+	struct seat *seat = data;
+
+	if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
+	  close(fd);
+	  return;
+	}
+
+	char *map_str = (char *)(mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0));
+	if (map_str == MAP_FAILED) {
+		close(fd);
+		fprintf(stderr, "keymap mmap failed: %s", strerror(errno));
+		return;
+	}
+
+	struct xkb_keymap *keymap = xkb_keymap_new_from_string(
+				seat->xkb_context, map_str,
+				XKB_KEYMAP_FORMAT_TEXT_V1,
+				XKB_KEYMAP_COMPILE_NO_FLAGS);
+	munmap(map_str, size);
+	close(fd);
+
+	if (!keymap)
+		return;
+
+	seat->xkb_state = xkb_state_new(keymap);
+
+	xkb_keymap_unref(keymap);
+}
+
+static void
+keyboard_enter(void *data,
+	       struct wl_keyboard *wl_keyboard,
+	       uint32_t serial,
+	       struct wl_surface *surface,
+	       struct wl_array *keys)
+{
+}
+
+static void
+keyboard_leave(void *data,
+	       struct wl_keyboard *wl_keyboard,
+	       uint32_t serial,
+	       struct wl_surface *surface)
+{
+}
+
+static void
+keyboard_key(void *data,
+	     struct wl_keyboard *wl_keyboard,
+	     uint32_t serial,
+	     uint32_t time,
+	     uint32_t key,
+	     uint32_t state)
+{
+	struct seat *seat = data;
+
+	if (state & WL_KEYBOARD_KEY_STATE_PRESSED) {
+		const xkb_keysym_t *syms;
+
+		if (xkb_state_key_get_syms(seat->xkb_state, key + 8, &syms) != 1)
+			return;
+
+		switch (syms[0]) {
+		case XKB_KEY_Escape:
+			printf("close\n");
+			libdecor_frame_close(window->frame);
+			break;
+		case XKB_KEY_1: /* toggle resizability */
+			if (libdecor_frame_has_capability(
+				    window->frame, LIBDECOR_ACTION_RESIZE)) {
+				printf("set fixed-size\n");
+				libdecor_frame_unset_capabilities(window->frame,
+							LIBDECOR_ACTION_RESIZE);
+			}
+			else {
+				printf("set resizeable\n");
+				libdecor_frame_set_capabilities(window->frame,
+							LIBDECOR_ACTION_RESIZE);
+			}
+			break;
+		case XKB_KEY_2: /* maximize */
+			printf("maximize\n");
+			libdecor_frame_set_maximized(window->frame);
+			break;
+		case XKB_KEY_3: /* un-maximize / restore */
+			printf("un-maximize\n");
+			libdecor_frame_unset_maximized(window->frame);
+			break;
+		case XKB_KEY_4: /* fullscreen */
+			printf("fullscreen\n");
+			libdecor_frame_set_fullscreen(window->frame, NULL);
+			break;
+		case XKB_KEY_5: /* un-fullscreen / restore */
+			printf("un-fullscreen\n");
+			libdecor_frame_unset_fullscreen(window->frame);
+			break;
+		case XKB_KEY_minus:
+		case XKB_KEY_plus:
+			{
+				const int dd = (syms[0] == XKB_KEY_minus ? -1 : +1) * chk/2;
+				printf("resize to: %i x %i\n",
+				       window->configured_width + dd,
+				       window->configured_height + dd);
+				resize(window,
+				       window->configured_width + dd,
+				       window->configured_height + dd);
+			}
+			break;
+		case XKB_KEY_v: /* VGA: 640x480 */
+			printf("set VGA resolution: 640x480\n");
+			resize(window, 640, 480);
+			break;
+		case XKB_KEY_s: /* SVGA: 800x600 */
+			printf("set SVGA resolution: 800x600\n");
+			resize(window, 800, 600);
+			break;
+		case XKB_KEY_x: /* XVGA: 1024x768 */
+			printf("set XVGA resolution: 1024x768\n");
+			resize(window, 1024, 768);
+			break;
+		case XKB_KEY_t:
+			libdecor_frame_set_title(window->frame, titles[window->title_index]);
+			window->title_index = (window->title_index + 1) % N_TITLES;
+			break;
+		}
+	}
+}
+
+static void
+keyboard_modifiers(void *data,
+		   struct wl_keyboard *wl_keyboard,
+		   uint32_t serial,
+		   uint32_t mods_depressed,
+		   uint32_t mods_latched,
+		   uint32_t mods_locked,
+		   uint32_t group)
+{
+	struct seat *seat = data;
+
+	xkb_state_update_mask(seat->xkb_state,
+			      mods_depressed, mods_latched, mods_locked,
+			      0, 0, group);
+}
+
+static void
+keyboard_repeat_info(void *data,
+		     struct wl_keyboard *wl_keyboard,
+		     int32_t rate,
+		     int32_t delay)
+{
+}
+
+static struct wl_keyboard_listener keyboard_listener = {
+	keyboard_keymap,
+	keyboard_enter,
+	keyboard_leave,
+	keyboard_key,
+	keyboard_modifiers,
+	keyboard_repeat_info,
+};
+
+static void
+seat_capabilities(void *data,
+		  struct wl_seat *wl_seat,
+		  uint32_t capabilities)
+{
+	struct seat *seat = data;
+	if (capabilities & WL_SEAT_CAPABILITY_POINTER &&
+	    !seat->wl_pointer) {
+		seat->wl_pointer = wl_seat_get_pointer(wl_seat);
+		wl_pointer_add_listener(seat->wl_pointer, &pointer_listener,
+					seat);
+		seat->pointer_scale = 1;
+		init_cursors(seat);
+	} else if (!(capabilities & WL_SEAT_CAPABILITY_POINTER) &&
+		   seat->wl_pointer) {
+		wl_pointer_release(seat->wl_pointer);
+		seat->wl_pointer = NULL;
+	}
+
+	if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD &&
+	    !seat->wl_keyboard) {
+		seat->wl_keyboard = wl_seat_get_keyboard(wl_seat);
+		seat->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
+		wl_keyboard_add_listener(seat->wl_keyboard, &keyboard_listener,
+					 seat);
+	} else if (!(capabilities & WL_SEAT_CAPABILITY_KEYBOARD) &&
+		   seat->wl_keyboard) {
+		xkb_context_unref(seat->xkb_context);
+		wl_keyboard_release(seat->wl_keyboard);
+		seat->wl_keyboard = NULL;
+	}
+}
+
+static void
+seat_name(void *data,
+	  struct wl_seat *wl_seat,
+	  const char *name)
+{
+	struct seat *seat = data;
+
+	seat->name = strdup(name);
+}
+
+static struct wl_seat_listener seat_listener = {
+	seat_capabilities,
+	seat_name
+};
+
+static void
+output_geometry(void *data,
+		struct wl_output *wl_output,
+		int32_t x,
+		int32_t y,
+		int32_t physical_width,
+		int32_t physical_height,
+		int32_t subpixel,
+		const char *make,
+		const char *model,
+		int32_t transform)
+{
+}
+
+static void
+output_mode(void *data,
+	    struct wl_output *wl_output,
+	    uint32_t flags,
+	    int32_t width,
+	    int32_t height,
+	    int32_t refresh)
+{
+}
+
+static void
+output_done(void *data,
+	    struct wl_output *wl_output)
+{
+	struct output *output = data;
+	struct seat *seat;
+
+	if (window) {
+		if (output->scale != window->scale)
+			update_scale(window);
+	}
+
+	wl_list_for_each(seat, &seats, link) {
+		try_update_cursor(seat);
+	}
+}
+
+static void
+output_scale(void *data,
+	     struct wl_output *wl_output,
+	     int32_t factor)
+{
+	struct output *output = data;
+
+	output->scale = factor;
+}
+
+static struct wl_output_listener output_listener = {
+	output_geometry,
+	output_mode,
+	output_done,
+	output_scale
+};
+
+static void
+xdg_wm_base_ping(void *user_data,
+		 struct xdg_wm_base *xdg_wm_base,
+		 uint32_t serial)
+{
+	xdg_wm_base_pong(xdg_wm_base, serial);
+}
+
+static const struct xdg_wm_base_listener xdg_wm_base_listener = {
+	xdg_wm_base_ping,
+};
+
+static void
+registry_handle_global(void *user_data,
+		       struct wl_registry *wl_registry,
+		       uint32_t id,
+		       const char *interface,
+		       uint32_t version)
+{
+	struct seat *seat;
+	struct output *output;
+
+	if (strcmp(interface, "wl_compositor") == 0) {
+		if (version < 4) {
+			fprintf(stderr, "wl_compositor version >= 4 required");
+			exit(EXIT_FAILURE);
+		}
+		wl_compositor =
+			wl_registry_bind(wl_registry,
+					 id, &wl_compositor_interface, 4);
+	} else if (strcmp(interface, "wl_shm") == 0) {
+		wl_shm = wl_registry_bind(wl_registry,
+					  id, &wl_shm_interface, 1);
+		wl_shm_add_listener(wl_shm, &shm_listener, NULL);
+	} else if (strcmp(interface, "wl_seat") == 0) {
+		if (version < 3) {
+			fprintf(stderr, "%s version 3 required but only version "
+					"%i is available\n", interface, version);
+			exit(EXIT_FAILURE);
+		}
+		seat = zalloc(sizeof *seat);
+		wl_list_init(&seat->pointer_outputs);
+		seat->wl_seat = wl_registry_bind(wl_registry,
+						 id, &wl_seat_interface, 3);
+		wl_seat_add_listener(seat->wl_seat, &seat_listener, seat);
+	} else if (strcmp(interface, "wl_output") == 0) {
+		if (version < 2) {
+			fprintf(stderr, "%s version 3 required but only version "
+					"%i is available\n", interface, version);
+			exit(EXIT_FAILURE);
+		}
+		output = zalloc(sizeof *output);
+		output->id = id;
+		output->scale = 1;
+		output->wl_output = wl_registry_bind(wl_registry,
+						     id, &wl_output_interface,
+						     2);
+		wl_proxy_set_tag((struct wl_proxy *) output->wl_output,
+				 &proxy_tag);
+		wl_output_add_listener(output->wl_output, &output_listener,
+				       output);
+		wl_list_insert(&outputs, &output->link);
+	} else if (strcmp(interface, "xdg_wm_base") == 0) {
+		xdg_wm_base = wl_registry_bind(wl_registry, id,
+					       &xdg_wm_base_interface,
+					       1);
+		xdg_wm_base_add_listener(xdg_wm_base,
+					 &xdg_wm_base_listener,
+					 NULL);
+	}
+}
+
+static void
+registry_handle_global_remove(void *data, struct wl_registry *registry,
+			      uint32_t name)
+{
+	struct output *output;
+	struct window_output *window_output;
+
+	wl_list_for_each(output, &outputs, link) {
+		if (output->id == name) {
+			wl_list_for_each(window_output, &window->outputs,
+					 link) {
+				if (window_output->output == output) {
+					wl_list_remove(&window_output->link);
+					free(window_output);
+				}
+			}
+			wl_list_remove(&output->link);
+			wl_output_destroy(output->wl_output);
+			free(output);
+			break;
+		}
+	}
+}
+
+static const struct wl_registry_listener registry_listener = {
+	registry_handle_global,
+	registry_handle_global_remove
+};
+
+static void
+handle_error(struct libdecor *context,
+	     enum libdecor_error error,
+	     const char *message)
+{
+	fprintf(stderr, "Caught error (%d): %s\n", error, message);
+	exit(EXIT_FAILURE);
+}
+
+static struct libdecor_interface libdecor_iface = {
+	.error = handle_error,
+};
+
+static void
+buffer_release(void *user_data,
+	       struct wl_buffer *wl_buffer)
+{
+	struct buffer *buffer = user_data;
+
+	wl_buffer_destroy(buffer->wl_buffer);
+	munmap(buffer->data, buffer->data_size);
+	free(buffer);
+}
+
+static const struct wl_buffer_listener buffer_listener = {
+	buffer_release
+};
+
+static int
+create_anonymous_file(off_t size)
+{
+	int fd;
+
+	int ret;
+
+	fd = memfd_create("libdecor-demo", MFD_CLOEXEC | MFD_ALLOW_SEALING);
+
+	if (fd < 0)
+		return -1;
+
+	fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK);
+
+	do {
+		ret = posix_fallocate(fd, 0, size);
+	} while (ret == EINTR);
+	if (ret != 0) {
+		close(fd);
+		errno = ret;
+		return -1;
+	}
+
+	return fd;
+}
+
+static struct buffer *
+create_shm_buffer(int width,
+		  int height,
+		  uint32_t format)
+{
+	struct wl_shm_pool *pool;
+	int fd, size, stride;
+	void *data;
+	struct buffer *buffer;
+
+	stride = width * 4;
+	size = stride * height;
+
+	fd = create_anonymous_file(size);
+	if (fd < 0) {
+		fprintf(stderr, "creating a buffer file for %d B failed: %s\n",
+			size, strerror(errno));
+		return NULL;
+	}
+
+	data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+	if (data == MAP_FAILED) {
+		fprintf(stderr, "mmap failed: %s\n", strerror(errno));
+		close(fd);
+		return NULL;
+	}
+
+	buffer = zalloc(sizeof *buffer);
+
+	pool = wl_shm_create_pool(wl_shm, fd, size);
+	buffer->wl_buffer = wl_shm_pool_create_buffer(pool, 0,
+						      width, height,
+						      stride, format);
+	wl_buffer_add_listener(buffer->wl_buffer, &buffer_listener, buffer);
+	wl_shm_pool_destroy(pool);
+	close(fd);
+
+	buffer->data = data;
+	buffer->data_size = size;
+
+	return buffer;
+}
+
+static void
+paint_buffer(struct buffer *buffer,
+	     int width,
+	     int height,
+	     int scale,
+	     enum libdecor_window_state window_state)
+{
+	uint32_t *pixels = buffer->data;
+	uint32_t bg, fg, color;
+	int y, x, sx, sy;
+	size_t off;
+	int stride = width * scale;
+
+	if (window_state & LIBDECOR_WINDOW_STATE_ACTIVE) {
+		fg = 0xffbcbcbc;
+		bg = 0xff8e8e8e;
+	} else {
+		fg = 0xff8e8e8e;
+		bg = 0xff484848;
+	}
+
+	for (y = 0; y < height; y++) {
+		for (x = 0; x < width; x++) {
+			color = (x & chk) ^ (y & chk) ? fg : bg;
+			for (sx = 0; sx < scale; sx++) {
+				for (sy = 0; sy < scale; sy++) {
+					off = x * scale + sx
+					      + (y * scale + sy) * stride;
+					pixels[off] = color;
+				}
+			}
+		}
+	}
+}
+
+static void
+redraw(struct window *window)
+{
+	struct buffer *buffer;
+
+	buffer = create_shm_buffer(window->configured_width * window->scale,
+				   window->configured_height * window->scale,
+				   WL_SHM_FORMAT_XRGB8888);
+	paint_buffer(buffer, window->configured_width,
+		     window->configured_height, window->scale,
+		     window->window_state);
+
+	wl_surface_attach(window->wl_surface, buffer->wl_buffer, 0, 0);
+	wl_surface_set_buffer_scale(window->wl_surface, window->scale);
+	wl_surface_damage_buffer(window->wl_surface, 0, 0,
+				 window->configured_width * window->scale,
+				 window->configured_height * window->scale);
+	wl_surface_commit(window->wl_surface);
+}
+
+static void
+handle_configure(struct libdecor_frame *frame,
+		 struct libdecor_configuration *configuration,
+		 void *user_data)
+{
+	struct window *window = user_data;
+	int width, height;
+	enum libdecor_window_state window_state;
+	struct libdecor_state *state;
+
+	if (!libdecor_configuration_get_content_size(configuration, frame,
+						     &width, &height)) {
+		width = window->content_width;
+		height = window->content_height;
+	}
+
+	width = (width == 0) ? DEFAULT_WIDTH : width;
+	height = (height == 0) ? DEFAULT_HEIGHT : height;
+
+	window->configured_width = width;
+	window->configured_height = height;
+
+	if (!libdecor_configuration_get_window_state(configuration,
+						     &window_state))
+		window_state = LIBDECOR_WINDOW_STATE_NONE;
+
+	window->window_state = window_state;
+
+	state = libdecor_state_new(width, height);
+	libdecor_frame_commit(frame, state, configuration);
+	libdecor_state_free(state);
+
+	redraw(window);
+}
+
+static void
+handle_close(struct libdecor_frame *frame,
+	     void *user_data)
+{
+	exit(EXIT_SUCCESS);
+}
+
+static void
+handle_commit(struct libdecor_frame *frame,
+	      void *user_data)
+{
+	wl_surface_commit(window->wl_surface);
+}
+
+static void
+handle_dismiss_popup(struct libdecor_frame *frame,
+		     const char *seat_name,
+		     void *user_data)
+{
+	popup_destroy(window->popup);
+}
+
+static struct libdecor_frame_interface libdecor_frame_iface = {
+	handle_configure,
+	handle_close,
+	handle_commit,
+	handle_dismiss_popup,
+};
+
+static void
+surface_enter(void *data,
+	      struct wl_surface *wl_surface,
+	      struct wl_output *wl_output)
+{
+	struct window *window = data;
+	struct output *output;
+	struct window_output *window_output;
+
+	if (!own_output(wl_output))
+		return;
+
+	output = wl_output_get_user_data(wl_output);
+
+	if (output == NULL)
+		return;
+
+	window_output = zalloc(sizeof *window_output);
+	window_output->output = output;
+	wl_list_insert(&window->outputs, &window_output->link);
+	update_scale(window);
+}
+
+static void
+surface_leave(void *data,
+	      struct wl_surface *wl_surface,
+	      struct wl_output *wl_output)
+{
+	struct window *window = data;
+	struct window_output *window_output;
+
+	wl_list_for_each(window_output, &window->outputs, link) {
+		if (window_output->output->wl_output == wl_output) {
+			wl_list_remove(&window_output->link);
+			free(window_output);
+			update_scale(window);
+			break;
+		}
+	}
+}
+
+static struct wl_surface_listener surface_listener = {
+	surface_enter,
+	surface_leave,
+};
+
+static void
+free_outputs()
+{
+	struct output *output;
+	struct window_output *window_output, *window_output_tmp;
+
+	wl_list_for_each(output, &outputs, link) {
+		wl_list_for_each_safe(window_output, window_output_tmp, &window->outputs, link) {
+			if (window_output->output == output) {
+				wl_list_remove(&window_output->link);
+				free(window_output);
+			}
+		}
+		wl_list_remove(&output->link);
+		wl_output_destroy(output->wl_output);
+		free(output);
+		break;
+	}
+}
+
+int
+main(int argc,
+     char **argv)
+{
+	struct wl_display *wl_display;
+	struct wl_registry *wl_registry;
+	struct libdecor *context;
+	struct output *output;
+
+	/* write all output to stdout immediately */
+	setbuf(stdout, NULL);
+
+	wl_display = wl_display_connect(NULL);
+	if (!wl_display) {
+		fprintf(stderr, "No Wayland connection\n");
+		return EXIT_FAILURE;
+	}
+
+	wl_list_init(&seats);
+	wl_list_init(&outputs);
+
+	wl_registry = wl_display_get_registry(wl_display);
+	wl_registry_add_listener(wl_registry,
+				 &registry_listener,
+				 NULL);
+	wl_display_roundtrip(wl_display);
+	wl_display_roundtrip(wl_display);
+	if (!has_xrgb) {
+		fprintf(stderr, "No XRGB shm format\n");
+		return EXIT_FAILURE;
+	}
+
+	window = zalloc(sizeof *window);
+	window->scale = 1;
+	window->title_index = 0;
+	wl_list_for_each(output, &outputs, link) {
+		window->scale = MAX(window->scale, output->scale);
+	}
+	wl_list_init(&window->outputs);
+	window->wl_surface = wl_compositor_create_surface(wl_compositor);
+	wl_surface_add_listener(window->wl_surface, &surface_listener, window);
+
+	context = libdecor_new(wl_display, &libdecor_iface);
+	window->frame = libdecor_decorate(context, window->wl_surface,
+					  &libdecor_frame_iface, window);
+	libdecor_frame_set_app_id(window->frame, "libdecor-demo");
+	libdecor_frame_set_title(window->frame, "libdecor demo");
+	libdecor_frame_map(window->frame);
+
+	while (libdecor_dispatch(context, -1) >= 0);
+
+	free_outputs();
+
+	free(window);
+
+	return EXIT_SUCCESS;
+}
diff --git libdecor/demo/egl.c libdecor/demo/egl.c
new file mode 100644
index 0000000..53b97a6
--- /dev/null
+++ libdecor/demo/egl.c
@@ -0,0 +1,314 @@
+/*
+ * Copyright © 2011 Benjamin Franzke
+ * Copyright © 2010 Intel Corporation
+ * Copyright © 2018 Jonas �dahl
+ *
+ * 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 (including the
+ * next paragraph) 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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <math.h>
+#include <EGL/egl.h>
+#include <wayland-client.h>
+#include <wayland-egl.h>
+#include <libdecor.h>
+#include <GL/gl.h>
+#include <utils.h>
+
+static const size_t default_size = 200;
+
+struct client {
+	struct wl_display *display;
+	struct wl_compositor *compositor;
+	EGLDisplay egl_display;
+	EGLContext egl_context;
+};
+
+struct window {
+	struct client *client;
+	struct wl_surface *surface;
+	struct libdecor_frame *frame;
+	struct wl_egl_window *egl_window;
+	EGLSurface egl_surface;
+	int content_width;
+	int content_height;
+	bool open;
+	bool configured;
+};
+
+static void
+frame_configure(struct libdecor_frame *frame,
+		struct libdecor_configuration *configuration,
+		void *user_data)
+{
+	struct window *window = user_data;
+	struct libdecor_state *state;
+	int width, height;
+
+	if (!libdecor_configuration_get_content_size(configuration, frame,
+						     &width, &height)) {
+		height = width = default_size;
+	}
+
+	window->content_width = width;
+	window->content_height = height;
+
+	wl_egl_window_resize(window->egl_window,
+			     window->content_width, window->content_height,
+			     0, 0);
+
+	state = libdecor_state_new(width, height);
+	libdecor_frame_commit(frame, state, configuration);
+	libdecor_state_free(state);
+
+	window->configured = true;
+}
+
+static void
+frame_close(struct libdecor_frame *frame,
+	    void *user_data)
+{
+	struct window *window = user_data;
+
+	window->open = false;
+}
+
+static void
+frame_commit(struct libdecor_frame *frame,
+	     void *user_data)
+{
+	struct window *window = user_data;
+
+	eglSwapBuffers(window->client->display, window->egl_surface);
+}
+
+static struct libdecor_frame_interface frame_interface = {
+	frame_configure,
+	frame_close,
+	frame_commit,
+};
+
+static void
+libdecor_error(struct libdecor *context,
+	       enum libdecor_error error,
+	       const char *message)
+{
+	fprintf(stderr, "Caught error (%d): %s\n", error, message);
+	exit(EXIT_FAILURE);
+}
+
+static struct libdecor_interface libdecor_interface = {
+	libdecor_error,
+};
+
+static void
+registry_global(void *data,
+		struct wl_registry *wl_registry,
+		uint32_t name,
+		const char *interface,
+		uint32_t version)
+{
+	struct client *client = data;
+
+	if (strcmp(interface, wl_compositor_interface.name) == 0) {
+		client->compositor = wl_registry_bind(wl_registry, name,
+					     &wl_compositor_interface, 1);
+	}
+}
+
+static void
+registry_global_remove(void *data,
+		       struct wl_registry *wl_registry,
+		       uint32_t name)
+{
+}
+
+static const struct wl_registry_listener registry_listener = {
+	registry_global,
+	registry_global_remove
+};
+
+static bool
+setup(struct window *window)
+{
+	static const EGLint config_attribs[] = {
+		EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+		EGL_RED_SIZE, 8,
+		EGL_GREEN_SIZE, 8,
+		EGL_BLUE_SIZE, 8,
+		EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
+		EGL_NONE
+	};
+
+	EGLint major, minor;
+	EGLint n;
+	EGLConfig config;
+
+	window->client->egl_display =
+		eglGetDisplay((EGLNativeDisplayType)window->client->display);
+
+	if (eglInitialize(window->client->egl_display, &major, &minor) == EGL_FALSE) {
+		fprintf(stderr, "Cannot initialise EGL!\n");
+		return false;
+	}
+
+	if (eglBindAPI(EGL_OPENGL_API) == EGL_FALSE) {
+		fprintf(stderr, "Cannot bind EGL API!\n");
+		return false;
+	}
+
+	if (eglChooseConfig(window->client->egl_display,
+			    config_attribs,
+			    &config, 1, &n) == EGL_FALSE) {
+		fprintf(stderr, "No matching EGL configurations!\n");
+		return false;
+	}
+
+	window->client->egl_context = eglCreateContext(window->client->egl_display,
+						       config, EGL_NO_CONTEXT, NULL);
+
+	if (window->client->egl_context == EGL_NO_CONTEXT) {
+		fprintf(stderr, "No EGL context!\n");
+		return false;
+	}
+
+	window->surface = wl_compositor_create_surface(window->client->compositor);
+
+	window->egl_window = wl_egl_window_create(window->surface,
+						  default_size, default_size);
+
+	window->egl_surface = eglCreateWindowSurface(
+				      window->client->egl_display, config,
+				      (EGLNativeWindowType)window->egl_window,
+				      NULL);
+
+	eglMakeCurrent(window->client->egl_display, window->egl_surface,
+		       window->egl_surface, window->client->egl_context);
+
+	return true;
+}
+
+static float
+hue_to_channel(const float *const hue, const int n)
+{
+	/* convert hue to rgb channels with saturation and value equal to 1
+	 * https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB_alternative
+	 */
+	const float k = fmod(n + ((*hue) * 3 / M_PI), 6);
+	return 1 - MAX(0, MIN(MIN(k, 4 - k), 1));
+}
+
+static void
+hue_to_rgb(const float *const hue, float (*rgb)[3])
+{
+	(*rgb)[0] = hue_to_channel(hue, 5);
+	(*rgb)[1] = hue_to_channel(hue, 3);
+	(*rgb)[2] = hue_to_channel(hue, 1);
+}
+
+static void
+draw(struct window *window)
+{
+	struct timespec tv;
+	double time;
+
+	/* change of colour hue (HSV space) in rad/sec */
+	static const float hue_change = (2 * M_PI) / 10;
+	float hue;
+	float rgb[3] = {0,0,0};
+
+	clock_gettime(CLOCK_REALTIME, &tv);
+	time = tv.tv_sec + tv.tv_nsec * 1e-9;
+
+	hue = fmod(time * hue_change, 2 * M_PI);
+
+	hue_to_rgb(&hue, &rgb);
+
+	glClearColor(rgb[0], rgb[1], rgb[2], 1);
+	glClear(GL_COLOR_BUFFER_BIT);
+
+	eglSwapBuffers(window->client->egl_display, window->egl_surface);
+}
+
+int
+main(int argc, char *argv[])
+{
+	struct wl_registry *wl_registry;
+	struct libdecor *context;
+	struct window *window;
+	struct client *client;
+	int ret = EXIT_SUCCESS;
+
+	client = calloc(1, sizeof(struct client));
+
+	client->display = wl_display_connect(NULL);
+	if (!client->display) {
+		fprintf(stderr, "No Wayland connection\n");
+		return EXIT_FAILURE;
+	}
+
+	wl_registry = wl_display_get_registry(client->display);
+	wl_registry_add_listener(wl_registry, &registry_listener, client);
+	wl_display_roundtrip(client->display);
+
+	window = calloc(1, sizeof(struct window));
+	window->client = client;
+	window->open = true;
+	window->configured = false;
+
+	setup(window);
+
+	context = libdecor_new(client->display, &libdecor_interface);
+	window->frame = libdecor_decorate(context, window->surface,
+					  &frame_interface, window);
+	libdecor_frame_set_app_id(window->frame, "egl-demo");
+	libdecor_frame_set_title(window->frame, "EGL demo");
+	libdecor_frame_map(window->frame);
+
+	wl_display_roundtrip(client->display);
+	wl_display_roundtrip(client->display);
+
+	/* wait for the first configure event */
+	while (!window->configured) {
+		if (libdecor_dispatch(context, 0) < 0) {
+			ret = EXIT_FAILURE;
+			goto out;
+		}
+	}
+
+	while (window->open) {
+		if (libdecor_dispatch(context, 0) < 0) {
+			ret = EXIT_FAILURE;
+			goto out;
+		}
+		draw(window);
+	}
+
+out:
+	free(window);
+	free(client);
+
+	return ret;
+}
diff --git libdecor/src/cursor-settings.c libdecor/src/cursor-settings.c
new file mode 100644
index 0000000..b94623f
--- /dev/null
+++ libdecor/src/cursor-settings.c
@@ -0,0 +1,136 @@
+#include "cursor-settings.h"
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include "config.h"
+
+#ifdef HAS_DBUS
+#include <dbus/dbus.h>
+
+static DBusMessage *
+get_setting_sync(DBusConnection *const connection,
+		 const char *key,
+		 const char *value)
+{
+	DBusError error;
+	dbus_bool_t success;
+	DBusMessage *message;
+	DBusMessage *reply;
+
+	dbus_error_init(&error);
+
+	message = dbus_message_new_method_call(
+		"org.freedesktop.portal.Desktop",
+		"/org/freedesktop/portal/desktop",
+		"org.freedesktop.portal.Settings",
+		"Read");
+
+	success = dbus_message_append_args(message,
+		DBUS_TYPE_STRING, &key,
+		DBUS_TYPE_STRING, &value,
+		DBUS_TYPE_INVALID);
+
+	if (!success)
+		return NULL;
+
+	reply = dbus_connection_send_with_reply_and_block(
+			     connection,
+			     message,
+			     DBUS_TIMEOUT_USE_DEFAULT,
+			     &error);
+
+	dbus_message_unref(message);
+
+	if (dbus_error_is_set(&error))
+		return NULL;
+
+	return reply;
+}
+
+static bool
+parse_type(DBusMessage *const reply,
+	   const int type,
+	   void *value)
+{
+	DBusMessageIter iter[3];
+
+	dbus_message_iter_init(reply, &iter[0]);
+	if (dbus_message_iter_get_arg_type(&iter[0]) != DBUS_TYPE_VARIANT)
+		return false;
+
+	dbus_message_iter_recurse(&iter[0], &iter[1]);
+	if (dbus_message_iter_get_arg_type(&iter[1]) != DBUS_TYPE_VARIANT)
+		return false;
+
+	dbus_message_iter_recurse(&iter[1], &iter[2]);
+	if (dbus_message_iter_get_arg_type(&iter[2]) != type)
+		return false;
+
+	dbus_message_iter_get_basic(&iter[2], value);
+
+	return true;
+}
+
+bool
+libdecor_get_cursor_settings(char **theme, int *size)
+{
+	static const char name[] = "org.gnome.desktop.interface";
+	static const char key_theme[] = "cursor-theme";
+	static const char key_size[] = "cursor-size";
+
+	DBusError error;
+	DBusConnection *connection;
+	DBusMessage *reply;
+	const char *value_theme = NULL;
+
+	dbus_error_init(&error);
+
+	connection = dbus_bus_get(DBUS_BUS_SESSION, &error);
+
+	if (dbus_error_is_set(&error))
+		return false;
+
+	reply = get_setting_sync(connection, name, key_theme);
+	if (!reply)
+		return false;
+
+	if (!parse_type(reply, DBUS_TYPE_STRING, &value_theme)) {
+		dbus_message_unref(reply);
+		return false;
+	}
+
+	*theme = strdup(value_theme);
+
+	dbus_message_unref(reply);
+
+	reply = get_setting_sync(connection, name, key_size);
+	if (!reply)
+		return false;
+
+	if (!parse_type(reply, DBUS_TYPE_INT32, size)) {
+		dbus_message_unref(reply);
+		return false;
+	}
+
+	dbus_message_unref(reply);
+
+	return true;
+}
+#else
+bool
+libdecor_get_cursor_settings(char **theme, int *size)
+{
+	char *env_xtheme;
+	char *env_xsize;
+
+	env_xtheme = getenv("XCURSOR_THEME");
+	if (env_xtheme != NULL)
+		*theme = strdup(env_xtheme);
+
+	env_xsize = getenv("XCURSOR_SIZE");
+	if (env_xsize != NULL)
+		*size = atoi(env_xsize);
+
+	return env_xtheme != NULL && env_xsize != NULL;
+}
+#endif
diff --git libdecor/src/cursor-settings.h libdecor/src/cursor-settings.h
new file mode 100644
index 0000000..700bb4f
--- /dev/null
+++ libdecor/src/cursor-settings.h
@@ -0,0 +1,6 @@
+#pragma once
+
+#include <stdbool.h>
+
+bool
+libdecor_get_cursor_settings(char **theme, int *size);
diff --git libdecor/src/libdecor-fallback.c libdecor/src/libdecor-fallback.c
new file mode 100644
index 0000000..967d33a
--- /dev/null
+++ libdecor/src/libdecor-fallback.c
@@ -0,0 +1,208 @@
+/*
+ * Copyright © 2019 Jonas �dahl
+ *
+ * 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 (including the
+ * next paragraph) 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 "config.h"
+
+#include "libdecor-fallback.h"
+
+#include <poll.h>
+#include <errno.h>
+
+#include "utils.h"
+
+struct libdecor_plugin_fallback {
+	struct libdecor_plugin plugin;
+	struct libdecor *context;
+};
+
+static void
+libdecor_plugin_fallback_destroy(struct libdecor_plugin *plugin)
+{
+	free(plugin);
+}
+
+static int
+libdecor_plugin_fallback_get_fd(struct libdecor_plugin *plugin)
+{
+	struct libdecor_plugin_fallback *plugin_fallback =
+		(struct libdecor_plugin_fallback *) plugin;
+	struct wl_display *wl_display =
+		libdecor_get_wl_display(plugin_fallback->context);
+
+	return wl_display_get_fd(wl_display);
+}
+
+static int
+libdecor_plugin_fallback_dispatch(struct libdecor_plugin *plugin,
+				  int timeout)
+{
+	struct libdecor_plugin_fallback *plugin_fallback =
+		(struct libdecor_plugin_fallback *) plugin;
+	struct wl_display *wl_display =
+		libdecor_get_wl_display(plugin_fallback->context);
+	struct pollfd fds[1];
+	int ret;
+	int dispatch_count = 0;
+
+	while (wl_display_prepare_read(wl_display) != 0)
+		dispatch_count += wl_display_dispatch_pending(wl_display);
+
+	if (wl_display_flush(wl_display) < 0 &&
+	    errno != EAGAIN) {
+		wl_display_cancel_read(wl_display);
+		return -errno;
+	}
+
+	fds[0] = (struct pollfd) { wl_display_get_fd(wl_display), POLLIN };
+
+	ret = poll(fds, ARRAY_SIZE (fds), timeout);
+	if (ret > 0) {
+		if (fds[0].revents & POLLIN) {
+			wl_display_read_events(wl_display);
+			dispatch_count += wl_display_dispatch_pending(wl_display);
+			return dispatch_count;
+		} else {
+			wl_display_cancel_read(wl_display);
+			return dispatch_count;
+		}
+	} else if (ret == 0) {
+		wl_display_cancel_read(wl_display);
+		return dispatch_count;
+	} else {
+		wl_display_cancel_read(wl_display);
+		return -errno;
+	}
+}
+
+static struct libdecor_frame *
+libdecor_plugin_fallback_frame_new(struct libdecor_plugin *plugin)
+{
+	struct libdecor_frame *frame;
+
+	frame = zalloc(sizeof *frame);
+
+	return frame;
+}
+
+static void
+libdecor_plugin_fallback_frame_free(struct libdecor_plugin *plugin,
+				    struct libdecor_frame *frame)
+{
+}
+
+static void
+libdecor_plugin_fallback_frame_commit(struct libdecor_plugin *plugin,
+				      struct libdecor_frame *frame,
+				      struct libdecor_state *state,
+				      struct libdecor_configuration *configuration)
+{
+}
+
+static void
+libdecor_plugin_fallback_frame_property_changed(struct libdecor_plugin *plugin,
+						struct libdecor_frame *frame)
+{
+}
+
+static void
+libdecor_plugin_fallback_frame_translate_coordinate(struct libdecor_plugin *plugin,
+						    struct libdecor_frame *frame,
+						    int content_x,
+						    int content_y,
+						    int *frame_x,
+						    int *frame_y)
+{
+	*frame_x = content_x;
+	*frame_y = content_y;
+}
+
+static void
+libdecor_plugin_fallback_frame_popup_grab(struct libdecor_plugin *plugin,
+					  struct libdecor_frame *frame,
+					  const char *seat_name)
+{
+}
+
+static void
+libdecor_plugin_fallback_frame_popup_ungrab(struct libdecor_plugin *plugin,
+					    struct libdecor_frame *frame,
+					    const char *seat_name)
+{
+}
+
+static bool
+libdecor_plugin_fallback_configuration_get_content_size(struct libdecor_plugin *plugin,
+							struct libdecor_configuration *configuration,
+							struct libdecor_frame *frame,
+							int *content_width,
+							int *content_height)
+{
+	return libdecor_configuration_get_window_size(configuration,
+						      content_width,
+						      content_height);
+}
+
+static bool
+libdecor_plugin_fallback_frame_get_window_size_for(
+		struct libdecor_plugin *plugin,
+		struct libdecor_frame *frame,
+		struct libdecor_state *state,
+		int *window_width,
+		int *window_height)
+{
+	*window_width = libdecor_state_get_content_width (state);
+	*window_height = libdecor_state_get_content_height (state);
+	return true;
+}
+
+static struct libdecor_plugin_interface fallback_plugin_iface = {
+	.destroy = libdecor_plugin_fallback_destroy,
+	.get_fd = libdecor_plugin_fallback_get_fd,
+	.dispatch = libdecor_plugin_fallback_dispatch,
+	.frame_new = libdecor_plugin_fallback_frame_new,
+	.frame_free = libdecor_plugin_fallback_frame_free,
+	.frame_commit = libdecor_plugin_fallback_frame_commit,
+	.frame_property_changed = libdecor_plugin_fallback_frame_property_changed,
+	.frame_translate_coordinate =
+		libdecor_plugin_fallback_frame_translate_coordinate,
+	.frame_popup_grab = libdecor_plugin_fallback_frame_popup_grab,
+	.frame_popup_ungrab = libdecor_plugin_fallback_frame_popup_ungrab,
+	.configuration_get_content_size = libdecor_plugin_fallback_configuration_get_content_size,
+	.frame_get_window_size_for = libdecor_plugin_fallback_frame_get_window_size_for,
+};
+
+struct libdecor_plugin *
+libdecor_fallback_plugin_new(struct libdecor *context)
+{
+	struct libdecor_plugin_fallback *plugin;
+
+	plugin = zalloc(sizeof *plugin);
+	plugin->plugin.iface = &fallback_plugin_iface;
+	plugin->context = context;
+
+	libdecor_notify_plugin_ready(context);
+
+	return &plugin->plugin;
+}
diff --git libdecor/src/libdecor-fallback.h libdecor/src/libdecor-fallback.h
new file mode 100644
index 0000000..40a5c51
--- /dev/null
+++ libdecor/src/libdecor-fallback.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright © 2019 Jonas �dahl
+ *
+ * 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 (including the
+ * next paragraph) 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 LIBDECOR_FALLBACK_H
+#define LIBDECOR_FALLBACK_H
+
+#include "libdecor.h"
+#include "libdecor-plugin.h"
+
+struct libdecor_plugin *
+libdecor_fallback_plugin_new(struct libdecor *context);
+
+#endif /* LIBDECOR_FALLBACK_H */
diff --git libdecor/src/libdecor-plugin.h libdecor/src/libdecor-plugin.h
new file mode 100644
index 0000000..74f59d4
--- /dev/null
+++ libdecor/src/libdecor-plugin.h
@@ -0,0 +1,176 @@
+/*
+ * Copyright © 2017-2018 Red Hat Inc.
+ * Copyright © 2018 Jonas �dahl
+ *
+ * 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 (including the
+ * next paragraph) 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 LIBDECOR_PLUGIN_H
+#define LIBDECOR_PLUGIN_H
+
+#include "libdecor.h"
+
+struct libdecor_frame_private;
+
+struct libdecor_frame {
+	struct libdecor_frame_private *priv;
+	struct wl_list link;
+};
+
+struct libdecor_plugin_private;
+
+struct libdecor_plugin {
+	struct libdecor_plugin_interface *iface;
+	struct libdecor_plugin_private *private;
+};
+
+typedef struct libdecor_plugin * (* libdecor_plugin_constructor)(struct libdecor *context);
+
+#define LIBDECOR_PLUGIN_PRIORITY_HIGH 1000
+#define LIBDECOR_PLUGIN_PRIORITY_MEDIUM 100
+#define LIBDECOR_PLUGIN_PRIORITY_LOW 0
+
+struct libdecor_plugin_priority {
+	const char *desktop;
+	int priority;
+};
+
+struct libdecor_plugin_description {
+	/* API version the plugin is compatible with. */
+	int api_version;
+
+	/* Human readable string describing the plugin. */
+	char *description;
+
+	/*
+	 * The priorities field points to a list of per desktop priorities.
+	 * properties[i].desktop is matched against XDG_CURRENT_DESKTOP when
+	 * determining what plugin to use. The last entry in the list MUST have
+	 * the priorities[i].desktop pointer set to NULL as a default
+	 * priority.
+	 */
+	const struct libdecor_plugin_priority *priorities;
+
+	/* Vfunc used for constructing a plugin instance. */
+	libdecor_plugin_constructor constructor;
+};
+
+struct libdecor_plugin_interface {
+	void (* destroy)(struct libdecor_plugin *plugin);
+
+	int (* get_fd)(struct libdecor_plugin *plugin);
+	int (* dispatch)(struct libdecor_plugin *plugin,
+			 int timeout);
+
+	struct libdecor_frame * (* frame_new)(struct libdecor_plugin *plugin);
+	void (* frame_free)(struct libdecor_plugin *plugin,
+			    struct libdecor_frame *frame);
+	void (* frame_commit)(struct libdecor_plugin *plugin,
+			      struct libdecor_frame *frame,
+			      struct libdecor_state *state,
+			      struct libdecor_configuration *configuration);
+	void (*frame_property_changed)(struct libdecor_plugin *plugin,
+				       struct libdecor_frame *frame);
+	void (* frame_translate_coordinate)(struct libdecor_plugin *plugin,
+					    struct libdecor_frame *frame,
+					    int content_x,
+					    int content_y,
+					    int *window_x,
+					    int *window_y);
+	void (* frame_popup_grab)(struct libdecor_plugin *plugin,
+				  struct libdecor_frame *frame,
+				  const char *seat_name);
+	void (* frame_popup_ungrab)(struct libdecor_plugin *plugin,
+				    struct libdecor_frame *frame,
+				    const char *seat_name);
+
+	bool (* frame_get_window_size_for)(struct libdecor_plugin *plugin,
+					   struct libdecor_frame *frame,
+					   struct libdecor_state *state,
+					   int *window_width,
+					   int *window_height);
+
+	bool (* configuration_get_content_size)(struct libdecor_plugin *plugin,
+						struct libdecor_configuration *configuration,
+						struct libdecor_frame *frame,
+						int *content_width,
+						int *content_height);
+
+	/* Reserved */
+	void (* reserved0)(void);
+	void (* reserved1)(void);
+	void (* reserved2)(void);
+	void (* reserved3)(void);
+	void (* reserved4)(void);
+	void (* reserved5)(void);
+	void (* reserved6)(void);
+	void (* reserved7)(void);
+	void (* reserved8)(void);
+	void (* reserved9)(void);
+};
+
+struct wl_surface *
+libdecor_frame_get_wl_surface(struct libdecor_frame *frame);
+
+int
+libdecor_frame_get_content_width(struct libdecor_frame *frame);
+
+int
+libdecor_frame_get_content_height(struct libdecor_frame *frame);
+
+enum libdecor_window_state
+libdecor_frame_get_window_state(struct libdecor_frame *frame);
+
+void
+libdecor_frame_set_window_geometry(struct libdecor_frame *frame,
+				   int32_t x, int32_t y,
+				   int32_t width, int32_t height);
+
+enum libdecor_capabilities
+libdecor_frame_get_capabilities(const struct libdecor_frame *frame);
+
+void
+libdecor_frame_dismiss_popup(struct libdecor_frame *frame,
+			     const char *seat_name);
+
+struct wl_display *
+libdecor_get_wl_display(struct libdecor *context);
+
+void
+libdecor_notify_plugin_ready(struct libdecor *context);
+
+void
+libdecor_notify_plugin_error(struct libdecor *context,
+			     enum libdecor_error error,
+			     const char *__restrict fmt,
+			     ...);
+
+int
+libdecor_state_get_content_width (struct libdecor_state *state);
+
+int
+libdecor_state_get_content_height (struct libdecor_state *state);
+
+enum libdecor_window_state
+libdecor_state_get_window_state(struct libdecor_state *state);
+
+#endif /* LIBDECOR_PLUGIN_H */
diff --git libdecor/src/libdecor.c libdecor/src/libdecor.c
new file mode 100644
index 0000000..e9866b8
--- /dev/null
+++ libdecor/src/libdecor.c
@@ -0,0 +1,1512 @@
+/*
+ * Copyright © 2017-2018 Red Hat Inc.
+ * Copyright © 2018 Jonas �dahl
+ *
+ * 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 (including the
+ * next paragraph) 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 "config.h"
+
+#include <errno.h>
+#include <dirent.h>
+#include <dlfcn.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "libdecor.h"
+#include "libdecor-fallback.h"
+#include "libdecor-plugin.h"
+#include "utils.h"
+
+#include "xdg-shell-client-protocol.h"
+#include "xdg-decoration-client-protocol.h"
+
+struct libdecor {
+	int ref_count;
+
+	struct libdecor_interface *iface;
+
+	struct libdecor_plugin *plugin;
+	bool plugin_ready;
+
+	struct wl_display *wl_display;
+	struct wl_registry *wl_registry;
+	struct xdg_wm_base *xdg_wm_base;
+	struct zxdg_decoration_manager_v1 *decoration_manager;
+
+	struct wl_callback *init_callback;
+	bool init_done;
+	bool has_error;
+
+	struct wl_list frames;
+};
+
+struct libdecor_state {
+	enum libdecor_window_state window_state;
+
+	int content_width;
+	int content_height;
+};
+
+struct libdecor_limits {
+	int min_width;
+	int min_height;
+	int max_width;
+	int max_height;
+};
+
+struct libdecor_configuration {
+	uint32_t serial;
+
+	bool has_window_state;
+	enum libdecor_window_state window_state;
+
+	bool has_size;
+	int window_width;
+	int window_height;
+};
+
+struct libdecor_frame_private {
+	int ref_count;
+
+	struct libdecor *context;
+
+	struct wl_surface *wl_surface;
+
+	struct libdecor_frame_interface *iface;
+	void *user_data;
+
+	struct xdg_surface *xdg_surface;
+	struct xdg_toplevel *xdg_toplevel;
+	struct zxdg_toplevel_decoration_v1 *toplevel_decoration;
+
+	bool pending_map;
+
+	struct {
+		char *app_id;
+		char *title;
+		struct libdecor_limits content_limits;
+		struct xdg_toplevel *parent;
+	} state;
+
+	struct libdecor_configuration *pending_configuration;
+
+	int content_width;
+	int content_height;
+
+	enum libdecor_window_state window_state;
+
+	/* stored dimensions of the floating state */
+	int floating_width;
+	int floating_height;
+
+	enum zxdg_toplevel_decoration_v1_mode decoration_mode;
+
+	enum libdecor_capabilities capabilities;
+
+	/* original limits for interactive resize */
+	struct libdecor_limits interactive_limits;
+};
+
+/* gather all states at which a window is non-floating */
+static const enum libdecor_window_state states_non_floating =
+	LIBDECOR_WINDOW_STATE_MAXIMIZED | LIBDECOR_WINDOW_STATE_FULLSCREEN |
+	LIBDECOR_WINDOW_STATE_TILED_LEFT | LIBDECOR_WINDOW_STATE_TILED_RIGHT |
+	LIBDECOR_WINDOW_STATE_TILED_TOP | LIBDECOR_WINDOW_STATE_TILED_BOTTOM;
+
+static bool
+streql(const char *str1, const char *str2)
+{
+	return (str1 && str2) && (strcmp(str1, str2) == 0);
+}
+
+
+static void
+do_map(struct libdecor_frame *frame);
+
+static bool
+state_is_floating(enum libdecor_window_state window_state)
+{
+	return !(window_state & states_non_floating);
+}
+
+LIBDECOR_EXPORT int
+libdecor_state_get_content_width(struct libdecor_state *state)
+{
+	return state->content_width;
+}
+
+LIBDECOR_EXPORT int
+libdecor_state_get_content_height(struct libdecor_state *state)
+{
+	return state->content_height;
+}
+
+LIBDECOR_EXPORT enum libdecor_window_state
+libdecor_state_get_window_state(struct libdecor_state *state)
+{
+	return state->window_state;
+}
+
+LIBDECOR_EXPORT struct libdecor_state *
+libdecor_state_new(int width,
+		   int height)
+{
+	struct libdecor_state *state;
+
+	state = zalloc(sizeof *state);
+	state->content_width = width;
+	state->content_height = height;
+
+	return state;
+}
+
+LIBDECOR_EXPORT void
+libdecor_state_free(struct libdecor_state *state)
+{
+	free(state);
+}
+
+static struct libdecor_configuration *
+libdecor_configuration_new(void)
+{
+	struct libdecor_configuration *configuration;
+
+	configuration = zalloc(sizeof *configuration);
+
+	return configuration;
+}
+
+static void
+libdecor_configuration_free(struct libdecor_configuration *configuration)
+{
+	free(configuration);
+}
+
+static bool
+window_size_to_content_size(struct libdecor_configuration *configuration,
+			    struct libdecor_frame *frame,
+			    int *content_width,
+			    int *content_height)
+{
+	struct libdecor_frame_private *frame_priv = frame->priv;
+	struct libdecor *context = frame_priv->context;
+	struct libdecor_plugin *plugin = context->plugin;
+
+	switch (frame_priv->decoration_mode) {
+	case ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE:
+		return plugin->iface->configuration_get_content_size(
+					plugin, configuration, frame,
+					content_width, content_height);
+	case ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE:
+		*content_width = configuration->window_width;
+		*content_height = configuration->window_height;
+		return true;
+	default:
+		return false;
+	}
+}
+
+LIBDECOR_EXPORT bool
+libdecor_configuration_get_content_size(struct libdecor_configuration *configuration,
+					struct libdecor_frame *frame,
+					int *width,
+					int *height)
+{
+	int content_width;
+	int content_height;
+
+	if (!configuration->has_size)
+		return false;
+
+	if (configuration->window_width == 0 || configuration->window_height == 0)
+		return false;
+
+	if (!window_size_to_content_size(configuration,
+					 frame,
+					 &content_width,
+					 &content_height))
+		return false;
+
+	*width = content_width;
+	*height = content_height;
+	return true;
+}
+
+LIBDECOR_EXPORT bool
+libdecor_configuration_get_window_size(struct libdecor_configuration *configuration,
+				       int *width,
+				       int *height)
+{
+	if (!configuration->has_size)
+		return false;
+
+	if (configuration->window_width == 0 || configuration->window_height == 0)
+		return false;
+
+	*width = configuration->window_width;
+	*height = configuration->window_height;
+	return true;
+}
+
+LIBDECOR_EXPORT bool
+libdecor_configuration_get_window_state(struct libdecor_configuration *configuration,
+					enum libdecor_window_state *window_state)
+{
+	if (!configuration->has_window_state)
+		return false;
+
+	*window_state = configuration->window_state;
+	return true;
+}
+
+static void
+xdg_surface_configure(void *user_data,
+		      struct xdg_surface *xdg_surface,
+		      uint32_t serial)
+{
+	struct libdecor_frame *frame = user_data;
+	struct libdecor_frame_private *frame_priv = frame->priv;
+	struct libdecor_configuration *configuration;
+
+	configuration = frame_priv->pending_configuration;
+	frame_priv->pending_configuration = NULL;
+
+	if (!configuration)
+		configuration = libdecor_configuration_new();
+
+	configuration->serial = serial;
+
+	frame_priv->iface->configure(frame,
+				     configuration,
+				     frame_priv->user_data);
+
+	libdecor_configuration_free(configuration);
+}
+
+static const struct xdg_surface_listener xdg_surface_listener = {
+	xdg_surface_configure,
+};
+
+static enum libdecor_window_state
+parse_states(struct wl_array *states)
+{
+	enum libdecor_window_state pending_state = LIBDECOR_WINDOW_STATE_NONE;
+	uint32_t *p;
+
+	wl_array_for_each(p, states) {
+		enum xdg_toplevel_state state = *p;
+
+		switch (state) {
+		case XDG_TOPLEVEL_STATE_FULLSCREEN:
+			pending_state |= LIBDECOR_WINDOW_STATE_FULLSCREEN;
+			break;
+		case XDG_TOPLEVEL_STATE_MAXIMIZED:
+			pending_state |= LIBDECOR_WINDOW_STATE_MAXIMIZED;
+			break;
+		case XDG_TOPLEVEL_STATE_ACTIVATED:
+			pending_state |= LIBDECOR_WINDOW_STATE_ACTIVE;
+			break;
+		case XDG_TOPLEVEL_STATE_TILED_LEFT:
+			pending_state |= LIBDECOR_WINDOW_STATE_TILED_LEFT;
+			break;
+		case XDG_TOPLEVEL_STATE_TILED_RIGHT:
+			pending_state |= LIBDECOR_WINDOW_STATE_TILED_RIGHT;
+			break;
+		case XDG_TOPLEVEL_STATE_TILED_TOP:
+			pending_state |= LIBDECOR_WINDOW_STATE_TILED_TOP;
+			break;
+		case XDG_TOPLEVEL_STATE_TILED_BOTTOM:
+			pending_state |= LIBDECOR_WINDOW_STATE_TILED_BOTTOM;
+			break;
+		default:
+			break;
+		}
+	}
+
+	return pending_state;
+}
+
+static void
+xdg_toplevel_configure(void *user_data,
+		       struct xdg_toplevel *xdg_toplevel,
+		       int32_t width,
+		       int32_t height,
+		       struct wl_array *states)
+{
+	struct libdecor_frame *frame = user_data;
+	struct libdecor_frame_private *frame_priv = frame->priv;
+	enum libdecor_window_state window_state;
+
+	window_state = parse_states(states);
+
+	frame_priv->pending_configuration = libdecor_configuration_new();
+
+	frame_priv->pending_configuration->has_size = true;
+	if (width == 0 && height == 0) {
+		/* client needs to determine window dimensions
+		 * This might happen at the first configuration or after an
+		 * unmaximizing request. In any case, we will forward the stored
+		 * unmaximized state, which will either contain values stored
+		 * by the maximizing request, or 0.
+		 */
+		frame_priv->pending_configuration->window_width =
+				frame->priv->floating_width;
+		frame_priv->pending_configuration->window_height =
+				frame->priv->floating_height;
+	} else {
+		/* store current floating state */
+		if (state_is_floating(window_state)) {
+			frame->priv->floating_width = width;
+			frame->priv->floating_height = height;
+		}
+
+		frame_priv->pending_configuration->window_width = width;
+		frame_priv->pending_configuration->window_height = height;
+	}
+
+	frame_priv->pending_configuration->has_window_state = true;
+	frame_priv->pending_configuration->window_state = window_state;
+}
+
+static void
+xdg_toplevel_close(void *user_data,
+		   struct xdg_toplevel *xdg_toplevel)
+{
+	struct libdecor_frame *frame = user_data;
+	struct libdecor_frame_private *frame_priv = frame->priv;
+
+	frame_priv->iface->close(frame, frame_priv->user_data);
+}
+
+static const struct xdg_toplevel_listener xdg_toplevel_listener = {
+	xdg_toplevel_configure,
+	xdg_toplevel_close,
+};
+
+static void
+toplevel_decoration_configure(
+		void *data,
+		struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1,
+		uint32_t mode)
+{
+	((struct libdecor_frame_private *)(data))->decoration_mode = mode;
+}
+
+static const struct zxdg_toplevel_decoration_v1_listener
+		xdg_toplevel_decoration_listener = {
+	toplevel_decoration_configure,
+};
+
+static void
+init_shell_surface(struct libdecor_frame *frame)
+{
+	struct libdecor_frame_private *frame_priv = frame->priv;
+	struct libdecor *context = frame_priv->context;
+
+	if (frame_priv->xdg_surface)
+		return;
+
+	frame_priv->xdg_surface =
+		xdg_wm_base_get_xdg_surface(context->xdg_wm_base,
+					    frame_priv->wl_surface);
+	xdg_surface_add_listener(frame_priv->xdg_surface,
+				 &xdg_surface_listener,
+				 frame);
+
+	frame_priv->xdg_toplevel =
+		xdg_surface_get_toplevel(frame_priv->xdg_surface);
+	xdg_toplevel_add_listener(frame_priv->xdg_toplevel,
+				  &xdg_toplevel_listener,
+				  frame);
+
+	frame_priv->decoration_mode =
+			ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE;
+	if (context->decoration_manager) {
+		frame_priv->toplevel_decoration =
+			zxdg_decoration_manager_v1_get_toplevel_decoration(
+					context->decoration_manager,
+					frame_priv->xdg_toplevel);
+
+		zxdg_toplevel_decoration_v1_add_listener(
+					frame_priv->toplevel_decoration,
+					&xdg_toplevel_decoration_listener,
+					frame_priv);
+	}
+
+	if (frame_priv->state.parent) {
+		xdg_toplevel_set_parent(frame_priv->xdg_toplevel,
+					frame_priv->state.parent);
+	}
+	if (frame_priv->state.title) {
+		xdg_toplevel_set_title(frame_priv->xdg_toplevel,
+				       frame_priv->state.title);
+	}
+	if (frame_priv->state.app_id) {
+		xdg_toplevel_set_app_id(frame_priv->xdg_toplevel,
+					frame_priv->state.app_id);
+	}
+
+	if (frame_priv->pending_map)
+		do_map(frame);
+}
+
+LIBDECOR_EXPORT struct libdecor_frame *
+libdecor_decorate(struct libdecor *context,
+		  struct wl_surface *wl_surface,
+		  struct libdecor_frame_interface *iface,
+		  void *user_data)
+{
+	struct libdecor_plugin *plugin = context->plugin;
+	struct libdecor_frame *frame;
+	struct libdecor_frame_private *frame_priv;
+
+	if (context->has_error)
+		return NULL;
+
+	frame = plugin->iface->frame_new(plugin);
+	if (!frame)
+		return NULL;
+
+	frame_priv = zalloc(sizeof *frame_priv);
+	frame->priv = frame_priv;
+
+	frame_priv->ref_count = 1;
+	frame_priv->context = context;
+
+	frame_priv->wl_surface = wl_surface;
+	frame_priv->iface = iface;
+	frame_priv->user_data = user_data;
+
+	wl_list_insert(&context->frames, &frame->link);
+
+	libdecor_frame_set_capabilities(frame,
+					LIBDECOR_ACTION_MOVE |
+					LIBDECOR_ACTION_RESIZE |
+					LIBDECOR_ACTION_MINIMIZE |
+					LIBDECOR_ACTION_FULLSCREEN |
+					LIBDECOR_ACTION_CLOSE);
+
+	if (context->init_done)
+		init_shell_surface(frame);
+
+	return frame;
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_ref(struct libdecor_frame *frame)
+{
+	struct libdecor_frame_private *frame_priv = frame->priv;
+
+	frame_priv->ref_count++;
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_unref(struct libdecor_frame *frame)
+{
+	struct libdecor_frame_private *frame_priv = frame->priv;
+
+	frame_priv->ref_count--;
+	if (frame_priv->ref_count == 0) {
+		struct libdecor *context = frame_priv->context;
+		struct libdecor_plugin *plugin = context->plugin;
+
+		if (frame_priv->xdg_toplevel)
+			xdg_toplevel_destroy(frame_priv->xdg_toplevel);
+		if (frame_priv->xdg_surface)
+			xdg_surface_destroy(frame_priv->xdg_surface);
+
+		plugin->iface->frame_free(plugin, frame);
+
+		free(frame_priv->state.title);
+		free(frame_priv->state.app_id);
+
+		free(frame_priv);
+
+		free(frame);
+	}
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_set_parent(struct libdecor_frame *frame,
+			  struct libdecor_frame *parent)
+{
+	struct libdecor_frame_private *frame_priv = frame->priv;
+
+	if (!frame_priv->xdg_toplevel)
+		return;
+
+	frame_priv->state.parent = parent->priv->xdg_toplevel;
+
+	xdg_toplevel_set_parent(frame_priv->xdg_toplevel,
+				parent->priv->xdg_toplevel);
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_set_title(struct libdecor_frame *frame,
+			 const char *title)
+{
+	struct libdecor_frame_private *frame_priv = frame->priv;
+	struct libdecor_plugin *plugin = frame_priv->context->plugin;
+
+	if (!streql(frame_priv->state.title, title)) {
+		free(frame_priv->state.title);
+		frame_priv->state.title = strdup(title);
+
+		if (!frame_priv->xdg_toplevel)
+			return;
+
+		xdg_toplevel_set_title(frame_priv->xdg_toplevel, title);
+	
+		plugin->iface->frame_property_changed(plugin, frame);
+	}
+}
+
+LIBDECOR_EXPORT const char *
+libdecor_frame_get_title(struct libdecor_frame *frame)
+{
+	return frame->priv->state.title;
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_set_app_id(struct libdecor_frame *frame,
+			  const char *app_id)
+{
+	struct libdecor_frame_private *frame_priv = frame->priv;
+
+	free(frame_priv->state.app_id);
+	frame_priv->state.app_id = strdup(app_id);
+
+	if (!frame_priv->xdg_toplevel)
+		return;
+
+	xdg_toplevel_set_app_id(frame_priv->xdg_toplevel, app_id);
+}
+
+static void
+notify_on_capability_change(struct libdecor_frame *frame,
+			    const enum libdecor_capabilities old_capabilities)
+{
+	struct libdecor_plugin *plugin = frame->priv->context->plugin;
+	struct libdecor_state *state;
+
+	if (frame->priv->capabilities == old_capabilities)
+		return;
+
+	if (frame->priv->content_width == 0 ||
+	    frame->priv->content_height == 0)
+		return;
+
+	plugin->iface->frame_property_changed(plugin, frame);
+
+	if (!libdecor_frame_has_capability(frame, LIBDECOR_ACTION_RESIZE)) {
+		frame->priv->interactive_limits = frame->priv->state.content_limits;
+		/* set fixed window size */
+		libdecor_frame_set_min_content_size(frame,
+						    frame->priv->content_width,
+						    frame->priv->content_height);
+		libdecor_frame_set_max_content_size(frame,
+						    frame->priv->content_width,
+						    frame->priv->content_height);
+	} else {
+		/* restore old limits */
+		frame->priv->state.content_limits = frame->priv->interactive_limits;
+	}
+
+	state = libdecor_state_new(frame->priv->content_width,
+				   frame->priv->content_height);
+	libdecor_frame_commit(frame, state, NULL);
+	libdecor_state_free(state);
+
+	libdecor_frame_toplevel_commit(frame);
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_set_capabilities(struct libdecor_frame *frame,
+				enum libdecor_capabilities capabilities)
+{
+	const enum libdecor_capabilities old_capabilities =
+			frame->priv->capabilities;
+
+	frame->priv->capabilities |= capabilities;
+
+	notify_on_capability_change(frame, old_capabilities);
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_unset_capabilities(struct libdecor_frame *frame,
+				  enum libdecor_capabilities capabilities)
+{
+	const enum libdecor_capabilities old_capabilities =
+			frame->priv->capabilities;
+
+	frame->priv->capabilities &= ~capabilities;
+
+	notify_on_capability_change(frame, old_capabilities);
+}
+
+LIBDECOR_EXPORT bool
+libdecor_frame_has_capability(struct libdecor_frame *frame,
+			      enum libdecor_capabilities capability)
+{
+	return frame->priv->capabilities & capability;
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_popup_grab(struct libdecor_frame *frame,
+			  const char *seat_name)
+{
+	struct libdecor_frame_private *frame_priv = frame->priv;
+	struct libdecor *context = frame_priv->context;
+	struct libdecor_plugin *plugin = context->plugin;
+
+	plugin->iface->frame_popup_grab(plugin, frame, seat_name);
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_popup_ungrab(struct libdecor_frame *frame,
+			    const char *seat_name)
+{
+	struct libdecor_frame_private *frame_priv = frame->priv;
+	struct libdecor *context = frame_priv->context;
+	struct libdecor_plugin *plugin = context->plugin;
+
+	plugin->iface->frame_popup_ungrab(plugin, frame, seat_name);
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_dismiss_popup(struct libdecor_frame *frame,
+			     const char *seat_name)
+{
+	struct libdecor_frame_private *frame_priv = frame->priv;
+
+	frame_priv->iface->dismiss_popup(frame, seat_name, frame_priv->user_data);
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_show_window_menu(struct libdecor_frame *frame,
+				struct wl_seat *wl_seat,
+				uint32_t serial,
+				int x,
+				int y)
+{
+	struct libdecor_frame_private *frame_priv = frame->priv;
+
+	if (!frame_priv->xdg_toplevel) {
+		fprintf(stderr, "Can't show window menu before being mapped\n");
+		return;
+	}
+
+	xdg_toplevel_show_window_menu(frame_priv->xdg_toplevel,
+				      wl_seat, serial,
+				      x, y);
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_translate_coordinate(struct libdecor_frame *frame,
+				    int content_x,
+				    int content_y,
+				    int *frame_x,
+				    int *frame_y)
+{
+	struct libdecor_frame_private *frame_priv = frame->priv;
+	struct libdecor *context = frame_priv->context;
+	struct libdecor_plugin *plugin = context->plugin;
+
+	plugin->iface->frame_translate_coordinate(plugin, frame,
+						  content_x, content_y,
+						  frame_x, frame_y);
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_set_max_content_size(struct libdecor_frame *frame,
+				    int content_width,
+				    int content_height)
+{
+	struct libdecor_frame_private *frame_priv = frame->priv;
+
+	frame_priv->state.content_limits.max_width = content_width;
+	frame_priv->state.content_limits.max_height = content_height;
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_set_min_content_size(struct libdecor_frame *frame,
+				    int content_width,
+				    int content_height)
+{
+	struct libdecor_frame_private *frame_priv = frame->priv;
+
+	frame_priv->state.content_limits.min_width = content_width;
+	frame_priv->state.content_limits.min_height = content_height;
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_set_window_geometry(struct libdecor_frame *frame,
+				   int32_t x, int32_t y,
+				   int32_t width, int32_t height)
+{
+	xdg_surface_set_window_geometry(frame->priv->xdg_surface, x, y, width, height);
+}
+
+LIBDECOR_EXPORT enum libdecor_capabilities
+libdecor_frame_get_capabilities(const struct libdecor_frame *frame)
+{
+	return frame->priv->capabilities;
+}
+
+enum xdg_toplevel_resize_edge
+edge_to_xdg_edge(enum libdecor_resize_edge edge)
+{
+	switch (edge) {
+	case LIBDECOR_RESIZE_EDGE_NONE:
+		return XDG_TOPLEVEL_RESIZE_EDGE_NONE;
+	case LIBDECOR_RESIZE_EDGE_TOP:
+		return XDG_TOPLEVEL_RESIZE_EDGE_TOP;
+	case LIBDECOR_RESIZE_EDGE_BOTTOM:
+		return XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM;
+	case LIBDECOR_RESIZE_EDGE_LEFT:
+		return XDG_TOPLEVEL_RESIZE_EDGE_LEFT;
+	case LIBDECOR_RESIZE_EDGE_TOP_LEFT:
+		return XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT;
+	case LIBDECOR_RESIZE_EDGE_BOTTOM_LEFT:
+		return XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT;
+	case LIBDECOR_RESIZE_EDGE_RIGHT:
+		return XDG_TOPLEVEL_RESIZE_EDGE_RIGHT;
+	case LIBDECOR_RESIZE_EDGE_TOP_RIGHT:
+		return XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT;
+	case LIBDECOR_RESIZE_EDGE_BOTTOM_RIGHT:
+		return XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT;
+	}
+
+	abort();
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_resize(struct libdecor_frame *frame,
+		      struct wl_seat *wl_seat,
+		      uint32_t serial,
+		      enum libdecor_resize_edge edge)
+{
+	struct libdecor_frame_private *frame_priv = frame->priv;
+	enum xdg_toplevel_resize_edge xdg_edge;
+
+	xdg_edge = edge_to_xdg_edge(edge);
+	xdg_toplevel_resize(frame_priv->xdg_toplevel,
+			    wl_seat, serial, xdg_edge);
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_move(struct libdecor_frame *frame,
+		    struct wl_seat *wl_seat,
+		    uint32_t serial)
+{
+	struct libdecor_frame_private *frame_priv = frame->priv;
+
+	xdg_toplevel_move(frame_priv->xdg_toplevel, wl_seat, serial);
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_set_minimized(struct libdecor_frame *frame)
+{
+	xdg_toplevel_set_minimized(frame->priv->xdg_toplevel);
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_set_maximized(struct libdecor_frame *frame)
+{
+	xdg_toplevel_set_maximized(frame->priv->xdg_toplevel);
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_unset_maximized(struct libdecor_frame *frame)
+{
+	xdg_toplevel_unset_maximized(frame->priv->xdg_toplevel);
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_set_fullscreen(struct libdecor_frame *frame,
+			      struct wl_output *output)
+{
+	xdg_toplevel_set_fullscreen(frame->priv->xdg_toplevel, output);
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_unset_fullscreen(struct libdecor_frame *frame)
+{
+	xdg_toplevel_unset_fullscreen(frame->priv->xdg_toplevel);
+}
+
+LIBDECOR_EXPORT bool
+libdecor_frame_is_floating(struct libdecor_frame *frame)
+{
+	return state_is_floating(frame->priv->window_state);
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_close(struct libdecor_frame *frame)
+{
+	xdg_toplevel_close(frame, frame->priv->xdg_toplevel);
+}
+
+bool
+valid_limits(struct libdecor_frame_private *frame_priv)
+{
+	if (frame_priv->state.content_limits.min_width > 0 &&
+	    frame_priv->state.content_limits.max_width > 0 &&
+	    frame_priv->state.content_limits.min_width >
+	    frame_priv->state.content_limits.max_width)
+		return false;
+
+	if (frame_priv->state.content_limits.min_height > 0 &&
+	    frame_priv->state.content_limits.max_height > 0 &&
+	    frame_priv->state.content_limits.min_height >
+	    frame_priv->state.content_limits.max_height)
+		return false;
+
+	return true;
+}
+
+static void
+libdecor_frame_apply_limits(struct libdecor_frame *frame,
+			    enum libdecor_window_state window_state)
+{
+	struct libdecor_frame_private *frame_priv = frame->priv;
+	struct libdecor_plugin *plugin = frame_priv->context->plugin;
+
+	if (!valid_limits(frame_priv)) {
+		libdecor_notify_plugin_error(
+			frame_priv->context,
+			LIBDECOR_ERROR_INVALID_FRAME_CONFIGURATION,
+			"minimum size (%i,%i) must be smaller than maximum size (%i,%i)",
+			frame_priv->state.content_limits.min_width,
+			frame_priv->state.content_limits.min_height,
+			frame_priv->state.content_limits.max_width,
+			frame_priv->state.content_limits.max_height);
+	}
+
+	/* If the frame is configured as non-resizable before the first
+	 * configure event is received, we have to manually set the min/max
+	 * limits with the configured content size afterwards. */
+	if (!libdecor_frame_has_capability(frame, LIBDECOR_ACTION_RESIZE)) {
+		frame_priv->state.content_limits.min_width =
+				frame_priv->content_width;
+		frame_priv->state.content_limits.max_width =
+				frame_priv->content_width;
+
+		frame_priv->state.content_limits.min_height =
+				frame_priv->content_height;
+		frame_priv->state.content_limits.max_height =
+				frame_priv->content_height;
+	}
+
+	if (frame_priv->state.content_limits.min_width > 0 &&
+	    frame_priv->state.content_limits.min_height > 0) {
+		struct libdecor_state state_min;
+		int win_min_width, win_min_height;
+
+		state_min.content_width = frame_priv->state.content_limits.min_width;
+		state_min.content_height = frame_priv->state.content_limits.min_height;
+		state_min.window_state = window_state;
+
+		plugin->iface->frame_get_window_size_for(
+					plugin, frame, &state_min,
+					&win_min_width, &win_min_height);
+		xdg_toplevel_set_min_size(frame_priv->xdg_toplevel,
+					  win_min_width, win_min_height);
+	} else {
+		xdg_toplevel_set_min_size(frame_priv->xdg_toplevel, 0, 0);
+	}
+
+	if (frame_priv->state.content_limits.max_width > 0 &&
+	    frame_priv->state.content_limits.max_height > 0) {
+		struct libdecor_state state_max;
+		int win_max_width, win_max_height;
+
+		state_max.content_width = frame_priv->state.content_limits.max_width;
+		state_max.content_height = frame_priv->state.content_limits.max_height;
+		state_max.window_state = window_state;
+
+		plugin->iface->frame_get_window_size_for(
+					plugin, frame, &state_max,
+					&win_max_width, &win_max_height);
+		xdg_toplevel_set_max_size(frame_priv->xdg_toplevel,
+					  win_max_width, win_max_height);
+	} else {
+		xdg_toplevel_set_max_size(frame_priv->xdg_toplevel, 0, 0);
+	}
+}
+
+static void
+libdecor_frame_apply_state(struct libdecor_frame *frame,
+			   struct libdecor_state *state)
+{
+	struct libdecor_frame_private *frame_priv = frame->priv;
+
+	frame_priv->content_width = state->content_width;
+	frame_priv->content_height = state->content_height;
+
+	/* do not set limits in non-floating states */
+	if (state_is_floating(state->window_state)) {
+		libdecor_frame_apply_limits(frame, state->window_state);
+	}
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_toplevel_commit(struct libdecor_frame *frame)
+{
+	struct libdecor_frame_private *frame_priv = frame->priv;
+
+	frame_priv->iface->commit(frame, frame_priv->user_data);
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_commit(struct libdecor_frame *frame,
+		      struct libdecor_state *state,
+		      struct libdecor_configuration *configuration)
+{
+	struct libdecor_frame_private *frame_priv = frame->priv;
+	struct libdecor *context = frame_priv->context;
+	struct libdecor_plugin *plugin = context->plugin;
+
+	if (configuration && configuration->has_window_state) {
+		frame_priv->window_state = configuration->window_state;
+		state->window_state = configuration->window_state;
+	} else {
+		state->window_state = frame_priv->window_state;
+	}
+
+	libdecor_frame_apply_state(frame, state);
+
+	switch (frame_priv->decoration_mode) {
+	case ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE:
+		plugin->iface->frame_commit(plugin, frame, state, configuration);
+		break;
+	case ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE:
+		plugin->iface->frame_free(plugin, frame);
+		break;
+	}
+
+	/* set the floating dimensions via the application's requested content size */
+	if (configuration == NULL) {
+		plugin->iface->frame_get_window_size_for(
+					plugin, frame, state,
+					&frame->priv->floating_width,
+					&frame->priv->floating_height);
+	}
+
+	if (configuration) {
+		xdg_surface_ack_configure(frame_priv->xdg_surface,
+					  configuration->serial);
+	}
+}
+
+static void
+do_map(struct libdecor_frame *frame)
+{
+	struct libdecor_frame_private *frame_priv = frame->priv;
+
+	frame_priv->pending_map = false;
+	wl_surface_commit(frame_priv->wl_surface);
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_map(struct libdecor_frame *frame)
+{
+	struct libdecor_frame_private *frame_priv = frame->priv;
+
+	if (!frame_priv->xdg_surface) {
+		frame_priv->pending_map = true;
+		return;
+	}
+
+	do_map(frame);
+}
+
+LIBDECOR_EXPORT struct wl_surface *
+libdecor_frame_get_wl_surface(struct libdecor_frame *frame)
+{
+	struct libdecor_frame_private *frame_priv = frame->priv;
+
+	return frame_priv->wl_surface;
+}
+
+LIBDECOR_EXPORT struct xdg_surface *
+libdecor_frame_get_xdg_surface(struct libdecor_frame *frame)
+{
+	struct libdecor_frame_private *frame_priv = frame->priv;
+
+	return frame_priv->xdg_surface;
+}
+
+LIBDECOR_EXPORT struct xdg_toplevel *
+libdecor_frame_get_xdg_toplevel(struct libdecor_frame *frame)
+{
+	return frame->priv->xdg_toplevel;
+}
+
+LIBDECOR_EXPORT int
+libdecor_frame_get_content_width(struct libdecor_frame *frame)
+{
+	struct libdecor_frame_private *frame_priv = frame->priv;
+
+	return frame_priv->content_width;
+}
+
+LIBDECOR_EXPORT int
+libdecor_frame_get_content_height(struct libdecor_frame *frame)
+{
+	struct libdecor_frame_private *frame_priv = frame->priv;
+
+	return frame_priv->content_height;
+}
+
+LIBDECOR_EXPORT enum libdecor_window_state
+libdecor_frame_get_window_state(struct libdecor_frame *frame)
+{
+	struct libdecor_frame_private *frame_priv = frame->priv;
+
+	return frame_priv->window_state;
+}
+
+static void
+xdg_wm_base_ping(void *user_data,
+		 struct xdg_wm_base *xdg_wm_base,
+		 uint32_t serial)
+{
+	xdg_wm_base_pong(xdg_wm_base, serial);
+}
+
+static const struct xdg_wm_base_listener xdg_wm_base_listener = {
+	xdg_wm_base_ping,
+};
+
+static void
+init_xdg_wm_base(struct libdecor *context,
+		 uint32_t id,
+		 uint32_t version)
+{
+	context->xdg_wm_base = wl_registry_bind(context->wl_registry,
+						id,
+						&xdg_wm_base_interface,
+						MIN(version,2));
+	xdg_wm_base_add_listener(context->xdg_wm_base,
+				 &xdg_wm_base_listener,
+				 context);
+}
+
+static void
+registry_handle_global(void *user_data,
+		       struct wl_registry *wl_registry,
+		       uint32_t id,
+		       const char *interface,
+		       uint32_t version)
+{
+	struct libdecor *context = user_data;
+
+	if (!strcmp(interface, xdg_wm_base_interface.name))
+		init_xdg_wm_base(context, id, version);
+	else if (!strcmp(interface, zxdg_decoration_manager_v1_interface.name))
+		context->decoration_manager = wl_registry_bind(
+				context->wl_registry, id,
+				&zxdg_decoration_manager_v1_interface, version);
+}
+
+static void
+registry_handle_global_remove(void *user_data,
+			      struct wl_registry *wl_registry,
+			      uint32_t name)
+{
+}
+
+static const struct wl_registry_listener registry_listener = {
+	registry_handle_global,
+	registry_handle_global_remove
+};
+
+static bool
+is_compositor_compatible(struct libdecor *context)
+{
+	if (!context->xdg_wm_base)
+		return false;
+
+	return true;
+}
+
+static void
+notify_error(struct libdecor *context,
+	     enum libdecor_error error,
+	     const char *message)
+{
+	context->has_error = true;
+	context->iface->error(context, error, message);
+	context->plugin->iface->destroy(context->plugin);
+}
+
+static void
+finish_init(struct libdecor *context)
+{
+	struct libdecor_frame *frame;
+
+	wl_list_for_each(frame, &context->frames, link)
+		init_shell_surface(frame);
+}
+
+static void
+init_wl_display_callback(void *user_data,
+			 struct wl_callback *callback,
+			 uint32_t time)
+{
+	struct libdecor *context = user_data;
+
+	context->init_done = true;
+
+	wl_callback_destroy(callback);
+	context->init_callback = NULL;
+
+	if (!is_compositor_compatible(context)) {
+		notify_error(context,
+			     LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE,
+			     "Compositor is missing required interfaces");
+	}
+
+	if (context->plugin_ready) {
+		finish_init(context);
+	}
+}
+
+static const struct wl_callback_listener init_wl_display_callback_listener = {
+	init_wl_display_callback
+};
+
+struct plugin_loader {
+	struct wl_list link;
+	void *lib;
+	const struct libdecor_plugin_description *description;
+	int priority;
+	char *name;
+};
+
+static int
+calculate_priority(const struct libdecor_plugin_description *plugin_description)
+{
+	const char *current_desktop;
+	int i;
+
+	if (!plugin_description->priorities)
+		return -1;
+
+	current_desktop = getenv("XDG_CURRENT_DESKTOP");
+
+	i = 0;
+	while (true) {
+		struct libdecor_plugin_priority priority =
+			plugin_description->priorities[i];
+
+		i++;
+
+		if (priority.desktop) {
+			char *tokens;
+			char *saveptr;
+			char *token;
+
+			if (!current_desktop)
+				continue;
+
+			tokens = strdup(current_desktop);
+			token = strtok_r(tokens, ":", &saveptr);
+			while (token) {
+				if (strcmp(priority.desktop, token) == 0) {
+					free(tokens);
+					return priority.priority;
+				}
+				token = strtok_r(NULL, ":", &saveptr);
+			}
+			free(tokens);
+		} else {
+			return priority.priority;
+		}
+	}
+
+	return -1;
+}
+
+static struct plugin_loader *
+load_plugin_loader(struct libdecor *context,
+		   const char *path,
+		   const char *name)
+{
+	char *filename;
+	void *lib;
+	const struct libdecor_plugin_description *plugin_description;
+	int priority;
+	struct plugin_loader *plugin_loader;
+
+	if (!strstr(name, ".so"))
+		return NULL;
+
+	if (asprintf(&filename, "%s/%s", path, name) == -1)
+		return NULL;
+
+	lib = dlopen(filename, RTLD_NOW | RTLD_LAZY);
+	free(filename);
+	if (!lib) {
+		fprintf(stderr, "Failed to load plugin: '%s'\n", dlerror());
+		return NULL;
+	}
+
+	plugin_description = dlsym(lib, "libdecor_plugin_description");
+	if (!plugin_description) {
+		dlclose(lib);
+		fprintf(stderr,
+			"Failed to load plugin '%s': no plugin description symbol\n",
+			name);
+		return NULL;
+	}
+
+	if (plugin_description->api_version != LIBDECOR_PLUGIN_API_VERSION) {
+		dlclose(lib);
+		fprintf(stderr,
+			"Plugin '%s' found, but it's incompatible "
+			"(expected API version %d, but got %d)\n",
+			name,
+			LIBDECOR_PLUGIN_API_VERSION,
+			plugin_description->api_version);
+		return NULL;
+	}
+
+	priority = calculate_priority(plugin_description);
+	if (priority == -1) {
+		dlclose(lib);
+		fprintf(stderr,
+			"Plugin '%s' found, but has an invalid description\n",
+			name);
+		return NULL;
+	}
+
+	plugin_loader = zalloc(sizeof *plugin_loader);
+	plugin_loader->description = plugin_description;
+	plugin_loader->lib = lib;
+	plugin_loader->priority = priority;
+	plugin_loader->name = strdup(name);
+
+	return plugin_loader;
+}
+
+static bool
+plugin_loader_higher_priority(struct plugin_loader *plugin_loader,
+			      struct plugin_loader *best_plugin_loader)
+{
+	return plugin_loader->priority > best_plugin_loader->priority;
+}
+
+static int
+init_plugins(struct libdecor *context)
+{
+	const char *plugin_dir;
+	DIR *dir;
+	struct wl_list plugin_loaders;
+	struct plugin_loader *plugin_loader, *tmp;
+	struct plugin_loader *best_plugin_loader;
+	struct libdecor_plugin *plugin;
+
+	plugin_dir = getenv("LIBDECOR_PLUGIN_DIR");
+	if (!plugin_dir)
+		plugin_dir = LIBDECOR_PLUGIN_DIR;
+
+	dir = opendir(plugin_dir);
+	if (!dir) {
+		fprintf(stderr, "Couldn't open plugin directory: %s\n",
+			strerror(errno));
+		return -1;
+	}
+
+	wl_list_init(&plugin_loaders);
+
+	while (true) {
+		struct dirent *de;
+		struct plugin_loader *plugin_loader;
+
+		de = readdir(dir);
+		if (!de)
+			break;
+
+		plugin_loader = load_plugin_loader(context, plugin_dir, de->d_name);
+		if (!plugin_loader)
+			continue;
+
+		wl_list_insert(plugin_loaders.prev, &plugin_loader->link);
+	}
+
+	closedir(dir);
+
+retry_next:
+	best_plugin_loader = NULL;
+	wl_list_for_each(plugin_loader, &plugin_loaders, link) {
+		if (!best_plugin_loader) {
+			best_plugin_loader = plugin_loader;
+			continue;
+		}
+
+		if (plugin_loader_higher_priority(plugin_loader,
+						  best_plugin_loader))
+			best_plugin_loader = plugin_loader;
+	}
+
+	if (!best_plugin_loader)
+		return -1;
+
+	plugin_loader = best_plugin_loader;
+	plugin = plugin_loader->description->constructor(context);
+	if (!plugin) {
+		fprintf(stderr,
+			"Failed to load plugin '%s': failed to init\n",
+			plugin_loader->name);
+		dlclose(plugin_loader->lib);
+		wl_list_remove(&plugin_loader->link);
+		free(plugin_loader->name);
+		free(plugin_loader);
+		goto retry_next;
+	}
+
+	context->plugin = plugin;
+
+	wl_list_remove(&plugin_loader->link);
+	free(plugin_loader->name);
+	free(plugin_loader);
+
+	wl_list_for_each_safe(plugin_loader, tmp, &plugin_loaders, link) {
+		dlclose(plugin_loader->lib);
+		free(plugin_loader->name);
+		free(plugin_loader);
+	}
+
+	return 0;
+}
+
+LIBDECOR_EXPORT int
+libdecor_get_fd(struct libdecor *context)
+{
+	struct libdecor_plugin *plugin = context->plugin;
+
+	return plugin->iface->get_fd(plugin);
+}
+
+LIBDECOR_EXPORT int
+libdecor_dispatch(struct libdecor *context,
+		  int timeout)
+{
+	struct libdecor_plugin *plugin = context->plugin;
+
+	return plugin->iface->dispatch(plugin, timeout);
+}
+
+LIBDECOR_EXPORT struct wl_display *
+libdecor_get_wl_display(struct libdecor *context)
+{
+	return context->wl_display;
+}
+
+LIBDECOR_EXPORT void
+libdecor_notify_plugin_ready(struct libdecor *context)
+{
+	context->plugin_ready = true;
+
+	if (context->init_done)
+		finish_init(context);
+}
+
+LIBDECOR_EXPORT void
+libdecor_notify_plugin_error(struct libdecor *context,
+			     enum libdecor_error error,
+			     const char *__restrict fmt,
+			     ...)
+{
+	char *msg = NULL;
+	int nbytes = 0;
+	va_list argp;
+
+	if (context->has_error)
+		return;
+
+	va_start(argp, fmt);
+	nbytes = vasprintf(&msg, fmt, argp);
+	va_end(argp);
+
+	if (nbytes>0)
+		notify_error(context, error, msg);
+
+	if (msg)
+		free(msg);
+}
+
+LIBDECOR_EXPORT void
+libdecor_unref(struct libdecor *context)
+{
+	context->ref_count--;
+	if (context->ref_count == 0) {
+		if (context->plugin)
+			context->plugin->iface->destroy(context->plugin);
+		if (context->init_callback)
+			wl_callback_destroy(context->init_callback);
+		wl_registry_destroy(context->wl_registry);
+		if (context->xdg_wm_base)
+			xdg_wm_base_destroy(context->xdg_wm_base);
+		if (context->decoration_manager)
+			zxdg_decoration_manager_v1_destroy(
+						context->decoration_manager);
+		free(context);
+	}
+}
+
+LIBDECOR_EXPORT struct libdecor *
+libdecor_new(struct wl_display *wl_display,
+	     struct libdecor_interface *iface)
+{
+	struct libdecor *context;
+
+	context = zalloc(sizeof *context);
+
+	context->ref_count = 1;
+	context->iface = iface;
+	context->wl_display = wl_display;
+	context->wl_registry = wl_display_get_registry(wl_display);
+	wl_registry_add_listener(context->wl_registry,
+				 &registry_listener,
+				 context);
+	context->init_callback = wl_display_sync(context->wl_display);
+	wl_callback_add_listener(context->init_callback,
+				 &init_wl_display_callback_listener,
+				 context);
+
+	wl_list_init(&context->frames);
+
+	if (init_plugins(context) != 0) {
+		fprintf(stderr,
+			"No plugins found, falling back on no decorations\n");
+		context->plugin = libdecor_fallback_plugin_new(context);
+	}
+
+	wl_display_flush(wl_display);
+
+	return context;
+}
diff --git libdecor/src/libdecor.h libdecor/src/libdecor.h
new file mode 100644
index 0000000..67c0884
--- /dev/null
+++ libdecor/src/libdecor.h
@@ -0,0 +1,291 @@
+/*
+ * Copyright © 2017-2018 Red Hat Inc.
+ * Copyright © 2018 Jonas �dahl
+ *
+ * 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 (including the
+ * next paragraph) 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 LIBDECOR_H
+#define LIBDECOR_H
+
+#include <stdbool.h>
+#include <wayland-client.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if defined(__GNUC__) && __GNUC__ >= 4
+#define LIBDECOR_EXPORT __attribute__ ((visibility("default")))
+#else
+#define LIBDECOR_EXPORT
+#endif
+
+struct xdg_toplevel;
+
+struct libdecor;
+struct libdecor_frame;
+struct libdecor_configuration;
+struct libdecor_state;
+
+enum libdecor_error {
+	LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE,
+	LIBDECOR_ERROR_INVALID_FRAME_CONFIGURATION,
+};
+
+enum libdecor_window_state {
+	LIBDECOR_WINDOW_STATE_NONE = 0,
+	LIBDECOR_WINDOW_STATE_ACTIVE = 1 << 0,
+	LIBDECOR_WINDOW_STATE_MAXIMIZED = 1 << 1,
+	LIBDECOR_WINDOW_STATE_FULLSCREEN = 1 << 2,
+	LIBDECOR_WINDOW_STATE_TILED_LEFT = 1 << 3,
+	LIBDECOR_WINDOW_STATE_TILED_RIGHT = 1 << 4,
+	LIBDECOR_WINDOW_STATE_TILED_TOP = 1 << 5,
+	LIBDECOR_WINDOW_STATE_TILED_BOTTOM = 1 << 6,
+};
+
+enum libdecor_resize_edge {
+	LIBDECOR_RESIZE_EDGE_NONE,
+	LIBDECOR_RESIZE_EDGE_TOP,
+	LIBDECOR_RESIZE_EDGE_BOTTOM,
+	LIBDECOR_RESIZE_EDGE_LEFT,
+	LIBDECOR_RESIZE_EDGE_TOP_LEFT,
+	LIBDECOR_RESIZE_EDGE_BOTTOM_LEFT,
+	LIBDECOR_RESIZE_EDGE_RIGHT,
+	LIBDECOR_RESIZE_EDGE_TOP_RIGHT,
+	LIBDECOR_RESIZE_EDGE_BOTTOM_RIGHT,
+};
+
+enum libdecor_capabilities {
+	LIBDECOR_ACTION_MOVE = 1 << 0,
+	LIBDECOR_ACTION_RESIZE = 1 << 1,
+	LIBDECOR_ACTION_MINIMIZE = 1 << 2,
+	LIBDECOR_ACTION_FULLSCREEN = 1 << 3,
+	LIBDECOR_ACTION_CLOSE = 1 << 4,
+};
+
+struct libdecor_interface {
+	void (* error)(struct libdecor *context,
+		       enum libdecor_error error,
+		       const char *message);
+
+	/* Reserved */
+	void (* reserved0)(void);
+	void (* reserved1)(void);
+	void (* reserved2)(void);
+	void (* reserved3)(void);
+	void (* reserved4)(void);
+	void (* reserved5)(void);
+	void (* reserved6)(void);
+	void (* reserved7)(void);
+	void (* reserved8)(void);
+	void (* reserved9)(void);
+};
+
+struct libdecor_frame_interface {
+	void (* configure)(struct libdecor_frame *frame,
+			   struct libdecor_configuration *configuration,
+			   void *user_data);
+	void (* close)(struct libdecor_frame *frame,
+		       void *user_data);
+	void (* commit)(struct libdecor_frame *frame,
+			void *user_data);
+	void (* dismiss_popup)(struct libdecor_frame *frame,
+			       const char *seat_name,
+			       void *user_data);
+
+	/* Reserved */
+	void (* reserved0)(void);
+	void (* reserved1)(void);
+	void (* reserved2)(void);
+	void (* reserved3)(void);
+	void (* reserved4)(void);
+	void (* reserved5)(void);
+	void (* reserved6)(void);
+	void (* reserved7)(void);
+	void (* reserved8)(void);
+	void (* reserved9)(void);
+};
+
+void
+libdecor_unref(struct libdecor *context);
+
+struct libdecor *
+libdecor_new(struct wl_display *display,
+	     struct libdecor_interface *iface);
+
+int
+libdecor_get_fd(struct libdecor *context);
+
+int
+libdecor_dispatch(struct libdecor *context,
+		  int timeout);
+
+struct libdecor_frame *
+libdecor_decorate(struct libdecor *context,
+		  struct wl_surface *surface,
+		  struct libdecor_frame_interface *iface,
+		  void *user_data);
+
+void
+libdecor_frame_ref(struct libdecor_frame *frame);
+
+void
+libdecor_frame_unref(struct libdecor_frame *frame);
+
+void
+libdecor_frame_set_parent(struct libdecor_frame *frame,
+			  struct libdecor_frame *parent);
+
+void
+libdecor_frame_set_title(struct libdecor_frame *frame,
+			 const char *title);
+
+const char *
+libdecor_frame_get_title(struct libdecor_frame *frame);
+
+void
+libdecor_frame_set_app_id(struct libdecor_frame *frame,
+			  const char *app_id);
+
+void
+libdecor_frame_set_capabilities(struct libdecor_frame *frame,
+				enum libdecor_capabilities capabilities);
+
+void
+libdecor_frame_unset_capabilities(struct libdecor_frame *frame,
+				  enum libdecor_capabilities capabilities);
+
+bool
+libdecor_frame_has_capability(struct libdecor_frame *frame,
+			      enum libdecor_capabilities capability);
+
+void
+libdecor_frame_show_window_menu(struct libdecor_frame *frame,
+				struct wl_seat *wl_seat,
+				uint32_t serial,
+				int x,
+				int y);
+
+void
+libdecor_frame_popup_grab(struct libdecor_frame *frame,
+			  const char *seat_name);
+
+void
+libdecor_frame_popup_ungrab(struct libdecor_frame *frame,
+			    const char *seat_name);
+
+void
+libdecor_frame_translate_coordinate(struct libdecor_frame *frame,
+				    int surface_x,
+				    int surface_y,
+				    int *frame_x,
+				    int *frame_y);
+
+void
+libdecor_frame_set_max_content_size(struct libdecor_frame *frame,
+				    int content_width,
+				    int content_height);
+
+void
+libdecor_frame_set_min_content_size(struct libdecor_frame *frame,
+				    int content_width,
+				    int content_height);
+
+void
+libdecor_frame_resize(struct libdecor_frame *frame,
+		      struct wl_seat *wl_seat,
+		      uint32_t serial,
+		      enum libdecor_resize_edge edge);
+
+void
+libdecor_frame_move(struct libdecor_frame *frame,
+		    struct wl_seat *wl_seat,
+		    uint32_t serial);
+
+void
+libdecor_frame_toplevel_commit(struct libdecor_frame *frame);
+
+void
+libdecor_frame_commit(struct libdecor_frame *frame,
+		      struct libdecor_state *state,
+		      struct libdecor_configuration *configuration);
+
+void
+libdecor_frame_set_minimized(struct libdecor_frame *frame);
+
+void
+libdecor_frame_set_maximized(struct libdecor_frame *frame);
+
+void
+libdecor_frame_unset_maximized(struct libdecor_frame *frame);
+
+void
+libdecor_frame_set_fullscreen(struct libdecor_frame *frame,
+			      struct wl_output *output);
+
+void
+libdecor_frame_unset_fullscreen(struct libdecor_frame *frame);
+
+bool
+libdecor_frame_is_floating(struct libdecor_frame *frame);
+
+void
+libdecor_frame_close(struct libdecor_frame *frame);
+
+void
+libdecor_frame_map(struct libdecor_frame *frame);
+
+struct xdg_surface *
+libdecor_frame_get_xdg_surface(struct libdecor_frame *frame);
+
+struct xdg_toplevel *
+libdecor_frame_get_xdg_toplevel(struct libdecor_frame *frame);
+
+struct libdecor_state *
+libdecor_state_new(int width,
+		   int height);
+
+
+void
+libdecor_state_free(struct libdecor_state *state);
+
+bool
+libdecor_configuration_get_content_size(struct libdecor_configuration *configuration,
+					struct libdecor_frame *frame,
+					int *width,
+					int *height);
+
+bool
+libdecor_configuration_get_window_size(struct libdecor_configuration *configuration,
+				       int *width,
+				       int *height);
+
+bool
+libdecor_configuration_get_window_state(struct libdecor_configuration *configuration,
+					enum libdecor_window_state *window_state);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LIBDECOR_H */
diff --git libdecor/src/plugins/cairo/libdecor-cairo-blur.c libdecor/src/plugins/cairo/libdecor-cairo-blur.c
new file mode 100644
index 0000000..2cddc7a
--- /dev/null
+++ libdecor/src/plugins/cairo/libdecor-cairo-blur.c
@@ -0,0 +1,255 @@
+/*
+ * Copyright © 2008 Kristian Høgsberg
+ * Copyright © 2012 Intel Corporation
+ *
+ * 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 (including the
+ * next paragraph) 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.
+ */
+
+/*
+ * functions 'blur_surface' and 'render_shadow' from weston project:
+ * https://gitlab.freedesktop.org/wayland/weston/raw/master/shared/cairo-util.c
+ */
+
+#include "libdecor-cairo-blur.h"
+#include <stdint.h>
+#include <stdlib.h>
+#include <math.h>
+
+/**
+ * Compile-time computation of number of items in a hardcoded array.
+ *
+ * @param a the array being measured.
+ * @return the number of items hardcoded into the array.
+ */
+#ifndef ARRAY_LENGTH
+#define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0])
+#endif
+
+int
+blur_surface(cairo_surface_t *surface, int margin)
+{
+	int32_t width, height, stride, x, y, z, w;
+	uint8_t *src, *dst;
+	uint32_t *s, *d, a, p;
+	int i, j, k, size, half;
+	uint32_t kernel[71];
+	double f;
+
+	size = ARRAY_LENGTH(kernel);
+	width = cairo_image_surface_get_width(surface);
+	height = cairo_image_surface_get_height(surface);
+	stride = cairo_image_surface_get_stride(surface);
+	src = cairo_image_surface_get_data(surface);
+
+	dst = malloc(height * stride);
+	if (dst == NULL)
+		return -1;
+
+	half = size / 2;
+	a = 0;
+	for (i = 0; i < size; i++) {
+		f = (i - half);
+		kernel[i] = exp(- f * f / ARRAY_LENGTH(kernel)) * 10000;
+		a += kernel[i];
+	}
+
+	for (i = 0; i < height; i++) {
+		s = (uint32_t *) (src + i * stride);
+		d = (uint32_t *) (dst + i * stride);
+		for (j = 0; j < width; j++) {
+			if (margin < j && j < width - margin) {
+				d[j] = s[j];
+				continue;
+			}
+
+			x = 0;
+			y = 0;
+			z = 0;
+			w = 0;
+			for (k = 0; k < size; k++) {
+				if (j - half + k < 0 || j - half + k >= width)
+					continue;
+				p = s[j - half + k];
+
+				x += (p >> 24) * kernel[k];
+				y += ((p >> 16) & 0xff) * kernel[k];
+				z += ((p >> 8) & 0xff) * kernel[k];
+				w += (p & 0xff) * kernel[k];
+			}
+			d[j] = (x / a << 24) | (y / a << 16) | (z / a << 8) | w / a;
+		}
+	}
+
+	for (i = 0; i < height; i++) {
+		s = (uint32_t *) (dst + i * stride);
+		d = (uint32_t *) (src + i * stride);
+		for (j = 0; j < width; j++) {
+			if (margin <= i && i < height - margin) {
+				d[j] = s[j];
+				continue;
+			}
+
+			x = 0;
+			y = 0;
+			z = 0;
+			w = 0;
+			for (k = 0; k < size; k++) {
+				if (i - half + k < 0 || i - half + k >= height)
+					continue;
+				s = (uint32_t *) (dst + (i - half + k) * stride);
+				p = s[j];
+
+				x += (p >> 24) * kernel[k];
+				y += ((p >> 16) & 0xff) * kernel[k];
+				z += ((p >> 8) & 0xff) * kernel[k];
+				w += (p & 0xff) * kernel[k];
+			}
+			d[j] = (x / a << 24) | (y / a << 16) | (z / a << 8) | w / a;
+		}
+	}
+
+	free(dst);
+	cairo_surface_mark_dirty(surface);
+
+	return 0;
+}
+
+void
+render_shadow(cairo_t *cr, cairo_surface_t *surface,
+	      int x, int y, int width, int height, int margin, int top_margin)
+{
+	cairo_pattern_t *pattern;
+	cairo_matrix_t matrix;
+	int i, fx, fy, shadow_height, shadow_width;
+
+	cairo_set_source_rgba(cr, 0, 0, 0, 0.45);
+	cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
+	pattern = cairo_pattern_create_for_surface (surface);
+	cairo_pattern_set_filter(pattern, CAIRO_FILTER_NEAREST);
+
+	for (i = 0; i < 4; i++) {
+		/* when fy is set, then we are working with lower corners,
+		 * when fx is set, then we are working with right corners
+		 *
+		 *  00 ------- 01
+		 *   |         |
+		 *   |         |
+		 *  10 ------- 11
+		 */
+		fx = i & 1;
+		fy = i >> 1;
+
+		cairo_matrix_init_translate(&matrix,
+					    -x + fx * (128 - width),
+					    -y + fy * (128 - height));
+		cairo_pattern_set_matrix(pattern, &matrix);
+
+		shadow_width = margin;
+		shadow_height = fy ? margin : top_margin;
+
+		/* if the shadows together are greater than the surface, we need
+		 * to fix it - set the shadow size to the half of
+		 * the size of surface. Also handle the case when the size is
+		 * not divisible by 2. In that case we need one part of the
+		 * shadow to be one pixel greater. !fy or !fx, respectively,
+		 * will do the work.
+		 */
+		if (height < 2 * shadow_height)
+			shadow_height = (height + !fy) / 2;
+
+		if (width < 2 * shadow_width)
+			shadow_width = (width + !fx) / 2;
+
+		cairo_reset_clip(cr);
+		cairo_rectangle(cr,
+				x + fx * (width - shadow_width),
+				y + fy * (height - shadow_height),
+				shadow_width, shadow_height);
+		cairo_clip (cr);
+		cairo_mask(cr, pattern);
+	}
+
+
+	shadow_width = width - 2 * margin;
+	shadow_height = top_margin;
+	if (height < 2 * shadow_height)
+		shadow_height = height / 2;
+
+	if (shadow_width > 0 && shadow_height) {
+		/* Top stretch */
+		cairo_matrix_init_translate(&matrix, 60, 0);
+		cairo_matrix_scale(&matrix, 8.0 / width, 1);
+		cairo_matrix_translate(&matrix, -x - width / 2, -y);
+		cairo_pattern_set_matrix(pattern, &matrix);
+		cairo_rectangle(cr, x + margin, y, shadow_width, shadow_height);
+
+		cairo_reset_clip(cr);
+		cairo_rectangle(cr,
+				x + margin, y,
+				shadow_width, shadow_height);
+		cairo_clip (cr);
+		cairo_mask(cr, pattern);
+
+		/* Bottom stretch */
+		cairo_matrix_translate(&matrix, 0, -height + 128);
+		cairo_pattern_set_matrix(pattern, &matrix);
+
+		cairo_reset_clip(cr);
+		cairo_rectangle(cr, x + margin, y + height - margin,
+				shadow_width, margin);
+		cairo_clip (cr);
+		cairo_mask(cr, pattern);
+	}
+
+	shadow_width = margin;
+	if (width < 2 * shadow_width)
+		shadow_width = width / 2;
+
+	shadow_height = height - margin - top_margin;
+
+	/* if height is smaller than sum of margins,
+	 * then the shadow is already done by the corners */
+	if (shadow_height > 0 && shadow_width) {
+		/* Left stretch */
+		cairo_matrix_init_translate(&matrix, 0, 60);
+		cairo_matrix_scale(&matrix, 1, 8.0 / height);
+		cairo_matrix_translate(&matrix, -x, -y - height / 2);
+		cairo_pattern_set_matrix(pattern, &matrix);
+		cairo_reset_clip(cr);
+		cairo_rectangle(cr, x, y + top_margin,
+				shadow_width, shadow_height);
+		cairo_clip (cr);
+		cairo_mask(cr, pattern);
+
+		/* Right stretch */
+		cairo_matrix_translate(&matrix, -width + 128, 0);
+		cairo_pattern_set_matrix(pattern, &matrix);
+		cairo_rectangle(cr, x + width - shadow_width, y + top_margin,
+				shadow_width, shadow_height);
+		cairo_reset_clip(cr);
+		cairo_clip (cr);
+		cairo_mask(cr, pattern);
+	}
+
+	cairo_pattern_destroy(pattern);
+	cairo_reset_clip(cr);
+}
diff --git libdecor/src/plugins/cairo/libdecor-cairo-blur.h libdecor/src/plugins/cairo/libdecor-cairo-blur.h
new file mode 100644
index 0000000..d48e9dd
--- /dev/null
+++ libdecor/src/plugins/cairo/libdecor-cairo-blur.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include <cairo/cairo.h>
+
+int
+blur_surface(cairo_surface_t *surface, int margin);
+
+void
+render_shadow(cairo_t *cr, cairo_surface_t *surface,
+	      int x, int y, int width, int height, int margin, int top_margin);
diff --git libdecor/src/plugins/cairo/libdecor-cairo.c libdecor/src/plugins/cairo/libdecor-cairo.c
new file mode 100644
index 0000000..218d1b8
--- /dev/null
+++ libdecor/src/plugins/cairo/libdecor-cairo.c
@@ -0,0 +1,2764 @@
+/*
+ * Copyright © 2018 Jonas �dahl
+ *
+ * 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 (including the
+ * next paragraph) 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 "config.h"
+
+#include <linux/input.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <errno.h>
+#include <limits.h>
+#include <wayland-cursor.h>
+
+#include "libdecor-plugin.h"
+#include "utils.h"
+#include "cursor-settings.h"
+
+#include <cairo/cairo.h>
+#include <pango/pangocairo.h>
+
+#include "libdecor-cairo-blur.h"
+
+static const size_t SHADOW_MARGIN = 24;	/* graspable part of the border */
+static const size_t TITLE_HEIGHT = 24;
+static const size_t BUTTON_WIDTH = 32;
+static const size_t SYM_DIM = 14;
+
+static const uint32_t COL_TITLE = 0xFF080706;
+static const uint32_t COL_BUTTON_MIN = 0xFFFFBB00;
+static const uint32_t COL_BUTTON_MAX = 0xFF238823;
+static const uint32_t COL_BUTTON_CLOSE = 0xFFFB6542;
+static const uint32_t COL_SYM = 0xFFF4F4EF;
+static const uint32_t COL_SYM_ACT = 0xFF20322A;
+
+static const uint32_t DOUBLE_CLICK_TIME_MS = 400;
+
+static const char *cursor_names[] = {
+	"top_side",
+	"bottom_side",
+	"left_side",
+	"top_left_corner",
+	"bottom_left_corner",
+	"right_side",
+	"top_right_corner",
+	"bottom_right_corner"
+};
+
+
+/* color conversion function from 32bit integer to double components */
+
+double
+red(const uint32_t *const col) {
+	return ((const uint8_t*)(col))[2] / (double)(255);
+}
+
+double
+green(const uint32_t *const col) {
+	return ((const uint8_t*)(col))[1] / (double)(255);
+}
+
+double
+blue(const uint32_t *const col) {
+	return ((const uint8_t*)(col))[0] / (double)(255);
+}
+
+double
+alpha(const uint32_t *const col) {
+	return ((const uint8_t*)(col))[3] / (double)(255);
+}
+
+void
+cairo_set_rgba32(cairo_t *cr, const uint32_t *const c) {
+	cairo_set_source_rgba(cr, red(c), green(c), blue(c), alpha(c));
+}
+
+static bool
+streql(const char *str1, const char *str2)
+{
+	return (str1 && str2) && (strcmp(str1, str2) == 0);
+}
+
+enum decoration_type {
+	DECORATION_TYPE_NONE,
+	DECORATION_TYPE_ALL,
+	DECORATION_TYPE_TITLE_ONLY
+};
+
+enum component {
+	NONE = 0,
+	SHADOW,
+	TITLE,
+	BUTTON_MIN,
+	BUTTON_MAX,
+	BUTTON_CLOSE,
+};
+
+enum composite_mode {
+	COMPOSITE_SERVER,
+	COMPOSITE_CLIENT,
+};
+
+struct seat {
+	struct libdecor_plugin_cairo *plugin_cairo;
+
+	char *name;
+
+	struct wl_seat *wl_seat;
+	struct wl_pointer *wl_pointer;
+
+	struct wl_surface *cursor_surface;
+	struct wl_cursor *current_cursor;
+	int cursor_scale;
+	struct wl_list cursor_outputs;
+
+	struct wl_cursor_theme *cursor_theme;
+	/* cursors for resize edges and corners */
+	struct wl_cursor *cursors[ARRAY_LENGTH(cursor_names)];
+	struct wl_cursor *cursor_left_ptr;
+
+	struct wl_surface *pointer_focus;
+
+	int pointer_x, pointer_y;
+
+	uint32_t pointer_button_time_stamp;
+
+	uint32_t serial;
+
+	bool grabbed;
+
+	struct wl_list link;
+};
+
+struct output {
+	struct libdecor_plugin_cairo *plugin_cairo;
+
+	struct wl_output *wl_output;
+	uint32_t id;
+	int scale;
+
+	struct wl_list link;
+};
+
+struct buffer {
+	struct wl_buffer *wl_buffer;
+	bool in_use;
+	bool is_detached;
+
+	void *data;
+	size_t data_size;
+	int width;
+	int height;
+	int scale;
+	int buffer_width;
+	int buffer_height;
+};
+
+struct border_component {
+	enum component type;
+
+	bool is_hidden;
+	bool opaque;
+
+	enum composite_mode composite_mode;
+	struct {
+		struct wl_surface *wl_surface;
+		struct wl_subsurface *wl_subsurface;
+		struct buffer *buffer;
+		struct wl_list output_list;
+		int scale;
+	} server;
+	struct {
+		cairo_surface_t *image;
+		struct border_component *parent_component;
+	} client;
+
+	struct wl_list child_components; /* border_component::link */
+	struct wl_list link; /* border_component::child_components */
+};
+
+struct surface_output {
+	struct output *output;
+	struct wl_list link;
+};
+
+struct cursor_output {
+	struct output *output;
+	struct wl_list link;
+};
+
+struct libdecor_frame_cairo {
+	struct libdecor_frame frame;
+
+	struct libdecor_plugin_cairo *plugin_cairo;
+
+	int content_width;
+	int content_height;
+
+	enum decoration_type decoration_type;
+
+	char *title;
+
+	enum libdecor_capabilities capabilities;
+
+	struct border_component *focus;
+	struct border_component *active;
+	struct border_component *grab;
+
+	bool shadow_showing;
+	struct border_component shadow;
+
+	struct {
+		bool is_showing;
+		struct border_component title;
+		struct border_component min;
+		struct border_component max;
+		struct border_component close;
+	} title_bar;
+
+	/* store pre-processed shadow tile */
+	cairo_surface_t *shadow_blur;
+
+	PangoFontDescription *font;
+
+	struct wl_list link;
+};
+
+struct libdecor_plugin_cairo {
+	struct libdecor_plugin plugin;
+
+	struct wl_callback *globals_callback;
+	struct wl_callback *globals_callback_shm;
+
+	struct libdecor *context;
+
+	struct wl_registry *wl_registry;
+	struct wl_subcompositor *wl_subcompositor;
+	struct wl_compositor *wl_compositor;
+
+	struct wl_shm *wl_shm;
+	struct wl_callback *shm_callback;
+	bool has_argb;
+
+	struct wl_list visible_frame_list;
+	struct wl_list seat_list;
+	struct wl_list output_list;
+
+	char *cursor_theme_name;
+	int cursor_size;
+};
+
+static const char *libdecor_cairo_proxy_tag = "libdecor-cairo";
+
+static void
+sync_active_component(struct libdecor_frame_cairo *frame_cairo,
+		      struct seat *seat);
+
+static void
+synthesize_pointer_enter(struct seat *seat);
+
+static void
+synthesize_pointer_leave(struct seat *seat);
+
+static bool
+own_proxy(struct wl_proxy *proxy)
+{
+	return (wl_proxy_get_tag(proxy) == &libdecor_cairo_proxy_tag);
+}
+
+static bool
+own_surface(struct wl_surface *surface)
+{
+	return own_proxy((struct wl_proxy *) surface);
+}
+
+static bool
+own_output(struct wl_output *output)
+{
+	return own_proxy((struct wl_proxy *) output);
+}
+
+static bool
+moveable(struct libdecor_frame_cairo *frame_cairo) {
+	return libdecor_frame_has_capability(&frame_cairo->frame,
+					     LIBDECOR_ACTION_MOVE);
+}
+
+static bool
+resizable(struct libdecor_frame_cairo *frame_cairo) {
+	return libdecor_frame_has_capability(&frame_cairo->frame,
+					     LIBDECOR_ACTION_RESIZE);
+}
+
+static bool
+minimizable(struct libdecor_frame_cairo *frame_cairo) {
+	return libdecor_frame_has_capability(&frame_cairo->frame,
+					     LIBDECOR_ACTION_MINIMIZE);
+}
+
+static bool
+closeable(struct libdecor_frame_cairo *frame_cairo) {
+	return libdecor_frame_has_capability(&frame_cairo->frame,
+					     LIBDECOR_ACTION_CLOSE);
+}
+
+static void
+buffer_free(struct buffer *buffer);
+
+static void
+draw_border_component(struct libdecor_frame_cairo *frame_cairo,
+		      struct border_component *border_component);
+
+static void
+send_cursor(struct seat *seat);
+
+static bool
+update_local_cursor(struct seat *seat);
+
+static void
+libdecor_plugin_cairo_destroy(struct libdecor_plugin *plugin)
+{
+	struct libdecor_plugin_cairo *plugin_cairo =
+		(struct libdecor_plugin_cairo *) plugin;
+	struct seat *seat, *seat_tmp;
+	struct output *output, *output_tmp;
+	struct libdecor_frame_cairo *frame, *frame_tmp;
+
+	if (plugin_cairo->globals_callback)
+		wl_callback_destroy(plugin_cairo->globals_callback);
+	if (plugin_cairo->globals_callback_shm)
+		wl_callback_destroy(plugin_cairo->globals_callback_shm);
+	if (plugin_cairo->shm_callback)
+		wl_callback_destroy(plugin_cairo->shm_callback);
+	wl_registry_destroy(plugin_cairo->wl_registry);
+
+	wl_list_for_each_safe(seat, seat_tmp, &plugin_cairo->seat_list, link) {
+		struct cursor_output *cursor_output, *tmp;
+
+		if (seat->wl_pointer)
+			wl_pointer_destroy(seat->wl_pointer);
+		if (seat->cursor_surface)
+			wl_surface_destroy(seat->cursor_surface);
+		wl_seat_destroy(seat->wl_seat);
+		if (seat->cursor_theme)
+			wl_cursor_theme_destroy(seat->cursor_theme);
+
+		wl_list_for_each_safe(cursor_output, tmp, &seat->cursor_outputs, link) {
+			wl_list_remove(&cursor_output->link);
+			free(cursor_output);
+		}
+		free(seat->name);
+
+		free(seat);
+	}
+
+	wl_list_for_each_safe(output, output_tmp,
+			      &plugin_cairo->output_list, link) {
+		wl_output_destroy(output->wl_output);
+		free(output);
+	}
+
+	wl_list_for_each_safe(frame, frame_tmp,
+			      &plugin_cairo->visible_frame_list, link) {
+		wl_list_remove(&frame->link);
+	}
+
+	free(plugin_cairo->cursor_theme_name);
+
+	wl_shm_destroy(plugin_cairo->wl_shm);
+
+	wl_compositor_destroy(plugin_cairo->wl_compositor);
+	wl_subcompositor_destroy(plugin_cairo->wl_subcompositor);
+
+	free(plugin_cairo);
+}
+
+static void
+init_server_component(struct border_component *border_component,
+		      enum component type)
+{
+	border_component->composite_mode = COMPOSITE_SERVER;
+	wl_list_init(&border_component->child_components);
+	border_component->type = type;
+}
+
+static void
+init_client_component(struct border_component *border_component,
+		      struct border_component *parent,
+		      enum component type)
+{
+	border_component->composite_mode = COMPOSITE_CLIENT;
+	wl_list_init(&border_component->child_components);
+	wl_list_insert(parent->child_components.prev, &border_component->link);
+	border_component->client.parent_component = parent;
+	border_component->type = type;
+}
+
+static void
+init_components(struct libdecor_frame_cairo *frame_cairo)
+{
+	init_server_component(&frame_cairo->title_bar.title,
+			      TITLE);
+	init_client_component(&frame_cairo->title_bar.min,
+			      &frame_cairo->title_bar.title,
+			      BUTTON_MIN);
+	init_client_component(&frame_cairo->title_bar.max,
+			      &frame_cairo->title_bar.title,
+			      BUTTON_MAX);
+	init_client_component(&frame_cairo->title_bar.close,
+			      &frame_cairo->title_bar.title,
+			      BUTTON_CLOSE);
+	init_server_component(&frame_cairo->shadow,
+			      SHADOW);
+}
+
+static struct libdecor_frame_cairo *
+libdecor_frame_cairo_new(struct libdecor_plugin_cairo *plugin_cairo)
+{
+	struct libdecor_frame_cairo *frame_cairo = zalloc(sizeof *frame_cairo);
+	cairo_t *cr;
+
+	static const int size = 128;
+	static const int boundary = 32;
+
+	frame_cairo->plugin_cairo = plugin_cairo;
+	frame_cairo->shadow_blur = cairo_image_surface_create(
+					CAIRO_FORMAT_ARGB32, size, size);
+	wl_list_insert(&plugin_cairo->visible_frame_list, &frame_cairo->link);
+
+	init_components(frame_cairo);
+
+	cr = cairo_create(frame_cairo->shadow_blur);
+	cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
+	cairo_set_source_rgba(cr, 0, 0, 0, 1);
+	cairo_rectangle(cr, boundary, boundary, size-2*boundary, size-2*boundary);
+	cairo_fill(cr);
+	cairo_destroy(cr);
+	blur_surface(frame_cairo->shadow_blur, 64);
+
+	/* define a sens-serif bold font at symbol size */
+	frame_cairo->font = pango_font_description_new();
+	pango_font_description_set_family(frame_cairo->font, "sans");
+	pango_font_description_set_weight(frame_cairo->font, PANGO_WEIGHT_BOLD);
+	pango_font_description_set_size(frame_cairo->font, SYM_DIM * PANGO_SCALE);
+
+	return frame_cairo;
+}
+
+static int
+libdecor_plugin_cairo_get_fd(struct libdecor_plugin *plugin)
+{
+	struct libdecor_plugin_cairo *plugin_cairo =
+		(struct libdecor_plugin_cairo *) plugin;
+	struct wl_display *wl_display =
+		libdecor_get_wl_display(plugin_cairo->context);
+
+	return wl_display_get_fd(wl_display);
+}
+
+static int
+libdecor_plugin_cairo_dispatch(struct libdecor_plugin *plugin,
+			       int timeout)
+{
+	struct libdecor_plugin_cairo *plugin_cairo =
+		(struct libdecor_plugin_cairo *) plugin;
+	struct wl_display *wl_display =
+		libdecor_get_wl_display(plugin_cairo->context);
+	struct pollfd fds[1];
+	int ret;
+	int dispatch_count = 0;
+
+	while (wl_display_prepare_read(wl_display) != 0)
+		dispatch_count += wl_display_dispatch_pending(wl_display);
+
+	if (wl_display_flush(wl_display) < 0 &&
+	    errno != EAGAIN) {
+		wl_display_cancel_read(wl_display);
+		return -errno;
+	}
+
+	fds[0] = (struct pollfd) { wl_display_get_fd(wl_display), POLLIN };
+
+	ret = poll(fds, ARRAY_SIZE (fds), timeout);
+	if (ret > 0) {
+		if (fds[0].revents & POLLIN) {
+			wl_display_read_events(wl_display);
+			dispatch_count += wl_display_dispatch_pending(wl_display);
+			return dispatch_count;
+		} else {
+			wl_display_cancel_read(wl_display);
+			return dispatch_count;
+		}
+	} else if (ret == 0) {
+		wl_display_cancel_read(wl_display);
+		return dispatch_count;
+	} else {
+		wl_display_cancel_read(wl_display);
+		return -errno;
+	}
+}
+
+static struct libdecor_frame *
+libdecor_plugin_cairo_frame_new(struct libdecor_plugin *plugin)
+{
+	struct libdecor_plugin_cairo *plugin_cairo =
+		(struct libdecor_plugin_cairo *) plugin;
+	struct libdecor_frame_cairo *frame_cairo;
+
+	frame_cairo = libdecor_frame_cairo_new(plugin_cairo);
+
+	return &frame_cairo->frame;
+}
+
+static int
+create_anonymous_file(off_t size)
+{
+	int ret;
+
+	int fd;
+
+	fd = memfd_create("libdecor-cairo", MFD_CLOEXEC | MFD_ALLOW_SEALING);
+
+	if (fd < 0)
+		return -1;
+
+	fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK);
+
+	do {
+		ret = posix_fallocate(fd, 0, size);
+	} while (ret == EINTR);
+	if (ret != 0) {
+		close(fd);
+		errno = ret;
+		return -1;
+	}
+
+	return fd;
+}
+
+static void
+toggle_maximized(struct libdecor_frame *const frame)
+{
+	if (!resizable((struct libdecor_frame_cairo *)frame))
+		return;
+
+	if (!(libdecor_frame_get_window_state(frame) &
+	      LIBDECOR_WINDOW_STATE_MAXIMIZED))
+		libdecor_frame_set_maximized(frame);
+	else
+		libdecor_frame_unset_maximized(frame);
+}
+
+static void
+buffer_release(void *user_data,
+	       struct wl_buffer *wl_buffer)
+{
+	struct buffer *buffer = user_data;
+
+	if (buffer->is_detached)
+		buffer_free(buffer);
+	else
+		buffer->in_use = false;
+}
+
+static const struct wl_buffer_listener buffer_listener = {
+	buffer_release
+};
+
+static struct buffer *
+create_shm_buffer(struct libdecor_plugin_cairo *plugin_cairo,
+		  int width,
+		  int height,
+		  bool opaque,
+		  int scale)
+{
+	struct wl_shm_pool *pool;
+	int fd, size, buffer_width, buffer_height, stride;
+	void *data;
+	struct buffer *buffer;
+	enum wl_shm_format buf_fmt;
+
+	buffer_width = width * scale;
+	buffer_height = height * scale;
+	stride = buffer_width * 4;
+	size = stride * buffer_height;
+
+	fd = create_anonymous_file(size);
+	if (fd < 0) {
+		fprintf(stderr, "creating a buffer file for %d B failed: %s\n",
+			size, strerror(errno));
+		return NULL;
+	}
+
+	data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+	if (data == MAP_FAILED) {
+		fprintf(stderr, "mmap failed: %s\n", strerror(errno));
+		close(fd);
+		return NULL;
+	}
+
+	buf_fmt = opaque ? WL_SHM_FORMAT_XRGB8888 : WL_SHM_FORMAT_ARGB8888;
+
+	pool = wl_shm_create_pool(plugin_cairo->wl_shm, fd, size);
+	buffer = zalloc(sizeof *buffer);
+	buffer->wl_buffer = wl_shm_pool_create_buffer(pool, 0,
+						      buffer_width, buffer_height,
+						      stride,
+						      buf_fmt);
+	wl_buffer_add_listener(buffer->wl_buffer, &buffer_listener, buffer);
+	wl_shm_pool_destroy(pool);
+	close(fd);
+
+	buffer->data = data;
+	buffer->data_size = size;
+	buffer->width = width;
+	buffer->height = height;
+	buffer->scale = scale;
+	buffer->buffer_width = buffer_width;
+	buffer->buffer_height = buffer_height;
+
+	return buffer;
+}
+
+static void
+buffer_free(struct buffer *buffer)
+{
+	if (buffer->wl_buffer) {
+		wl_buffer_destroy(buffer->wl_buffer);
+		munmap(buffer->data, buffer->data_size);
+		buffer->wl_buffer = NULL;
+		buffer->in_use = false;
+	}
+	free(buffer);
+}
+
+static void
+free_border_component(struct border_component *border_component)
+{
+	struct surface_output *surface_output, *surface_output_tmp;
+
+	if (border_component->server.wl_surface) {
+		wl_subsurface_destroy(border_component->server.wl_subsurface);
+		border_component->server.wl_subsurface = NULL;
+		wl_surface_destroy(border_component->server.wl_surface);
+		border_component->server.wl_surface = NULL;
+	}
+	if (border_component->server.buffer) {
+		buffer_free(border_component->server.buffer);
+		border_component->server.buffer = NULL;
+	}
+	if (border_component->client.image) {
+		cairo_surface_destroy(border_component->client.image);
+		border_component->client.image = NULL;
+	}
+	if (border_component->server.output_list.next != NULL) {
+		wl_list_for_each_safe(surface_output, surface_output_tmp,
+				      &border_component->server.output_list, link) {
+			wl_list_remove(&surface_output->link);
+			free(surface_output);
+		}
+	}
+}
+
+static void
+libdecor_plugin_cairo_frame_free(struct libdecor_plugin *plugin,
+				 struct libdecor_frame *frame)
+{
+	struct libdecor_plugin_cairo *plugin_cairo =
+		(struct libdecor_plugin_cairo *) plugin;
+	struct libdecor_frame_cairo *frame_cairo =
+		(struct libdecor_frame_cairo *) frame;
+	struct seat *seat;
+
+	wl_list_for_each(seat, &plugin_cairo->seat_list, link) {
+		if (seat->pointer_focus != NULL &&
+		    wl_surface_get_user_data(seat->pointer_focus) == frame_cairo)
+			seat->pointer_focus = NULL;
+	}
+
+	free_border_component(&frame_cairo->title_bar.title);
+	free_border_component(&frame_cairo->title_bar.min);
+	free_border_component(&frame_cairo->title_bar.max);
+	free_border_component(&frame_cairo->title_bar.close);
+	frame_cairo->title_bar.is_showing = false;
+	free_border_component(&frame_cairo->shadow);
+	frame_cairo->shadow_showing = false;
+	if (frame_cairo->shadow_blur != NULL) {
+		cairo_surface_destroy(frame_cairo->shadow_blur);
+		frame_cairo->shadow_blur = NULL;
+	}
+
+	free(frame_cairo->title);
+
+	pango_font_description_free(frame_cairo->font);
+
+	frame_cairo->decoration_type = DECORATION_TYPE_NONE;
+
+	if (frame_cairo->link.next != NULL)
+		wl_list_remove(&frame_cairo->link);
+}
+
+static bool
+is_border_surfaces_showing(struct libdecor_frame_cairo *frame_cairo)
+{
+	return frame_cairo->shadow_showing;
+}
+
+static bool
+is_title_bar_surfaces_showing(struct libdecor_frame_cairo *frame_cairo)
+{
+	return frame_cairo->title_bar.is_showing;
+}
+
+static struct border_component *
+get_server_component(struct border_component *border_component)
+{
+	switch (border_component->composite_mode) {
+	case COMPOSITE_SERVER:
+		return border_component;
+	case COMPOSITE_CLIENT:
+		return get_server_component(border_component->client.parent_component);
+	}
+	return NULL;
+}
+
+static void
+redraw_border_component(struct libdecor_frame_cairo *frame_cairo,
+			struct border_component *border_component)
+{
+	struct border_component *server_component;
+
+	server_component = get_server_component(border_component);
+	draw_border_component(frame_cairo, server_component);
+}
+
+static void
+hide_border_component(struct libdecor_frame_cairo *frame_cairo,
+		      struct border_component *border_component)
+{
+	border_component->is_hidden = true;
+
+	switch (border_component->composite_mode) {
+	case COMPOSITE_SERVER:
+		if (!border_component->server.wl_surface)
+			return;
+
+		wl_surface_attach(border_component->server.wl_surface,
+				  NULL, 0, 0);
+		wl_surface_commit(border_component->server.wl_surface);
+		break;
+	case COMPOSITE_CLIENT:
+		redraw_border_component(frame_cairo, border_component);
+		break;
+	}
+}
+
+static void
+hide_border_surfaces(struct libdecor_frame_cairo *frame_cairo)
+{
+	hide_border_component(frame_cairo, &frame_cairo->shadow);
+	frame_cairo->shadow_showing = false;
+}
+
+static void
+hide_title_bar_surfaces(struct libdecor_frame_cairo *frame_cairo)
+{
+	hide_border_component(frame_cairo, &frame_cairo->title_bar.title);
+	hide_border_component(frame_cairo, &frame_cairo->title_bar.min);
+	hide_border_component(frame_cairo, &frame_cairo->title_bar.max);
+	hide_border_component(frame_cairo, &frame_cairo->title_bar.close);
+	frame_cairo->title_bar.is_showing = false;
+}
+
+static struct border_component *
+get_component_for_surface(struct libdecor_frame_cairo *frame_cairo,
+			  struct wl_surface *surface)
+{
+	if (frame_cairo->shadow.server.wl_surface == surface)
+		return &frame_cairo->shadow;
+	if (frame_cairo->title_bar.title.server.wl_surface == surface)
+		return &frame_cairo->title_bar.title;
+	return NULL;
+}
+
+static void
+calculate_component_size(struct libdecor_frame_cairo *frame_cairo,
+			 enum component component,
+			 int *component_x,
+			 int *component_y,
+			 int *component_width,
+			 int *component_height);
+
+static void
+update_component_focus(struct libdecor_frame_cairo *frame_cairo,
+		       struct wl_surface *surface,
+		       struct seat *seat)
+{
+	static struct border_component *border_component;
+	static struct border_component *child_component;
+	static struct border_component *focus_component;
+
+	border_component = get_component_for_surface(frame_cairo, surface);
+
+	focus_component = border_component;
+	wl_list_for_each(child_component, &border_component->child_components, link) {
+		int component_x = 0, component_y = 0;
+		int component_width = 0, component_height = 0;
+
+		calculate_component_size(frame_cairo, child_component->type,
+					 &component_x, &component_y,
+					 &component_width, &component_height);
+		if (seat->pointer_x >= component_x &&
+		    seat->pointer_x < component_x + component_width &&
+		    seat->pointer_y >= component_y &&
+		    seat->pointer_y < component_y + component_height) {
+			focus_component = child_component;
+			break;
+		}
+	}
+
+	if (frame_cairo->grab)
+		frame_cairo->active = frame_cairo->grab;
+	else
+		frame_cairo->active = focus_component;
+	frame_cairo->focus = focus_component;
+
+}
+
+static void
+ensure_component(struct libdecor_frame_cairo *frame_cairo,
+		 struct border_component *cmpnt);
+
+static bool
+redraw_scale(struct libdecor_frame_cairo *frame_cairo,
+	     struct border_component *cmpnt)
+{
+	struct surface_output *surface_output;
+	int scale = 1;
+
+	if (cmpnt->is_hidden)
+		return false;
+
+	ensure_component(frame_cairo, cmpnt);
+
+	wl_list_for_each(surface_output, &cmpnt->server.output_list, link) {
+		scale = MAX(scale, surface_output->output->scale);
+	}
+	if (scale != cmpnt->server.scale) {
+		cmpnt->server.scale = scale;
+		if ((cmpnt->type != SHADOW) || is_border_surfaces_showing(frame_cairo)) {
+			draw_border_component(frame_cairo, cmpnt);
+			return true;
+		}
+	}
+	return false;
+}
+
+static bool
+add_surface_output(struct libdecor_plugin_cairo *plugin_cairo,
+		   struct wl_output *wl_output,
+		   struct wl_list *list)
+{
+	struct output *output;
+	struct surface_output *surface_output;
+
+	if (!own_output(wl_output))
+		return false;
+
+	output = wl_output_get_user_data(wl_output);
+
+	if (output == NULL)
+		return false;
+
+	surface_output = zalloc(sizeof *surface_output);
+	surface_output->output = output;
+	wl_list_insert(list, &surface_output->link);
+	return true;
+}
+
+static void
+surface_enter(void *data,
+	      struct wl_surface *wl_surface,
+	      struct wl_output *wl_output)
+{
+	struct libdecor_frame_cairo *frame_cairo = data;
+	struct border_component *cmpnt;
+
+	if (!(own_surface(wl_surface) && own_output(wl_output)))
+	    return;
+
+	cmpnt = get_component_for_surface(frame_cairo, wl_surface);
+	if (cmpnt == NULL)
+		return;
+
+	if (!add_surface_output(frame_cairo->plugin_cairo, wl_output,
+				&cmpnt->server.output_list))
+		return;
+
+	if (redraw_scale(frame_cairo, cmpnt))
+		libdecor_frame_toplevel_commit(&frame_cairo->frame);
+}
+
+static bool
+remove_surface_output(struct wl_list *list, struct wl_output *wl_output)
+{
+	struct surface_output *surface_output;
+	wl_list_for_each(surface_output, list, link) {
+		if (surface_output->output->wl_output == wl_output) {
+			wl_list_remove(&surface_output->link);
+			free(surface_output);
+			return true;
+		}
+	}
+	return false;
+}
+
+static void
+surface_leave(void *data,
+	      struct wl_surface *wl_surface,
+	      struct wl_output *wl_output)
+{
+	struct libdecor_frame_cairo *frame_cairo = data;
+	struct border_component *cmpnt;
+
+	if (!(own_surface(wl_surface) && own_output(wl_output)))
+	    return;
+
+	cmpnt = get_component_for_surface(frame_cairo, wl_surface);
+	if (cmpnt == NULL)
+		return;
+
+	if (!remove_surface_output(&cmpnt->server.output_list, wl_output))
+		return;
+
+	if (redraw_scale(frame_cairo, cmpnt))
+		libdecor_frame_toplevel_commit(&frame_cairo->frame);
+}
+
+static struct wl_surface_listener surface_listener = {
+	surface_enter,
+	surface_leave,
+};
+
+static void
+create_surface_subsurface_pair(struct libdecor_frame_cairo *frame_cairo,
+			       struct wl_surface **out_wl_surface,
+			       struct wl_subsurface **out_wl_subsurface)
+{
+	struct libdecor_plugin_cairo *plugin_cairo = frame_cairo->plugin_cairo;
+	struct libdecor_frame *frame = &frame_cairo->frame;
+	struct wl_compositor *wl_compositor = plugin_cairo->wl_compositor;
+	struct wl_subcompositor *wl_subcompositor = plugin_cairo->wl_subcompositor;
+	struct wl_surface *wl_surface;
+	struct wl_surface *parent;
+	struct wl_subsurface *wl_subsurface;
+
+	wl_surface = wl_compositor_create_surface(wl_compositor);
+	wl_proxy_set_tag((struct wl_proxy *) wl_surface,
+			 &libdecor_cairo_proxy_tag);
+
+	parent = libdecor_frame_get_wl_surface(frame);
+	wl_subsurface = wl_subcompositor_get_subsurface(wl_subcompositor,
+							wl_surface,
+							parent);
+
+	*out_wl_surface = wl_surface;
+	*out_wl_subsurface = wl_subsurface;
+}
+
+static void
+ensure_component(struct libdecor_frame_cairo *frame_cairo,
+		 struct border_component *cmpnt)
+{
+	switch (cmpnt->composite_mode) {
+	case COMPOSITE_SERVER:
+		if (!cmpnt->server.wl_surface) {
+			wl_list_init(&cmpnt->server.output_list);
+			cmpnt->server.scale = 1;
+			create_surface_subsurface_pair(frame_cairo,
+						       &cmpnt->server.wl_surface,
+						       &cmpnt->server.wl_subsurface);
+			wl_surface_add_listener(cmpnt->server.wl_surface,
+						&surface_listener,
+						frame_cairo);
+		}
+		break;
+	case COMPOSITE_CLIENT:
+			wl_list_init(&cmpnt->server.output_list);
+		break;
+	}
+
+	cmpnt->is_hidden = false;
+}
+
+static void
+ensure_border_surfaces(struct libdecor_frame_cairo *frame_cairo)
+{
+	frame_cairo->shadow.opaque = false;
+	ensure_component(frame_cairo, &frame_cairo->shadow);
+
+	libdecor_frame_set_min_content_size(&frame_cairo->frame,
+					    MAX(56, 4 * BUTTON_WIDTH),
+					    MAX(56, TITLE_HEIGHT + 1));
+}
+
+static void
+ensure_title_bar_surfaces(struct libdecor_frame_cairo *frame_cairo)
+{
+	frame_cairo->title_bar.title.opaque = true;
+	ensure_component(frame_cairo, &frame_cairo->title_bar.title);
+
+	frame_cairo->title_bar.min.opaque = true;
+	ensure_component(frame_cairo, &frame_cairo->title_bar.min);
+
+	frame_cairo->title_bar.max.opaque = true;
+	ensure_component(frame_cairo, &frame_cairo->title_bar.max);
+
+	frame_cairo->title_bar.close.opaque = true;
+	ensure_component(frame_cairo, &frame_cairo->title_bar.close);
+}
+
+static void
+calculate_component_size(struct libdecor_frame_cairo *frame_cairo,
+			 enum component component,
+			 int *component_x,
+			 int *component_y,
+			 int *component_width,
+			 int *component_height)
+{
+	struct libdecor_frame *frame = &frame_cairo->frame;
+	int content_width, content_height;
+
+	content_width = libdecor_frame_get_content_width(frame);
+	content_height = libdecor_frame_get_content_height(frame);
+
+	switch (component) {
+	case NONE:
+		*component_width = 0;
+		*component_height = 0;
+		return;
+	case SHADOW:
+		*component_x = -(int)SHADOW_MARGIN;
+		*component_y = -(int)(SHADOW_MARGIN+TITLE_HEIGHT);
+		*component_width = content_width + 2 * SHADOW_MARGIN;
+		*component_height = content_height
+				    + 2 * SHADOW_MARGIN
+				    + TITLE_HEIGHT;
+		return;
+	case TITLE:
+		*component_x = 0;
+		*component_y = -(int)TITLE_HEIGHT;
+		*component_width = content_width;
+		*component_height = TITLE_HEIGHT;
+		return;
+	case BUTTON_MIN:
+		*component_x = content_width - 3 * BUTTON_WIDTH;
+		*component_y = 0;
+		*component_width = BUTTON_WIDTH;
+		*component_height = TITLE_HEIGHT;
+		return;
+	case BUTTON_MAX:
+		*component_x = content_width - 2 * BUTTON_WIDTH;
+		*component_y = 0;
+		*component_width = BUTTON_WIDTH;
+		*component_height = TITLE_HEIGHT;
+		return;
+	case BUTTON_CLOSE:
+		*component_x = content_width - BUTTON_WIDTH;
+		*component_y = 0;
+		*component_width = BUTTON_WIDTH;
+		*component_height = TITLE_HEIGHT;
+		return;
+	}
+
+	abort();
+}
+
+static int
+border_component_get_scale(struct border_component *border_component)
+{
+	switch (border_component->composite_mode) {
+	case COMPOSITE_SERVER:
+		return border_component->server.scale;
+	case COMPOSITE_CLIENT:
+		return border_component_get_scale(
+				border_component->client.parent_component);
+	}
+	return 0;
+}
+
+static void
+draw_title_text(struct libdecor_frame_cairo *frame_cairo, cairo_t *cr, const int *title_width)
+{
+	PangoLayout *layout;
+
+	/* title fade out at buttons */
+	const int fade_width = 5 * BUTTON_WIDTH;
+	int fade_start;
+	cairo_pattern_t *fade;
+
+	/* text position and dimensions */
+	int text_extents_width, text_extents_height;
+	double text_x, text_y;
+	double text_width, text_height;
+
+	layout = pango_cairo_create_layout(cr);
+
+	pango_layout_set_text(layout,
+			      libdecor_frame_get_title((struct libdecor_frame*) frame_cairo),
+			      -1);
+	pango_layout_set_font_description(layout, frame_cairo->font);
+	pango_layout_get_size(layout, &text_extents_width, &text_extents_height);
+
+	/* set text position and dimensions */
+	text_width = text_extents_width / PANGO_SCALE;
+	text_height = text_extents_height / PANGO_SCALE;
+	text_x = *title_width / 2.0 - text_width / 2.0;
+	text_x += MIN(0.0, ((*title_width - fade_width) - (text_x + text_width)));
+	text_x = MAX(text_x, BUTTON_WIDTH);
+	text_y = TITLE_HEIGHT / 2.0 - text_height / 2.0;
+
+	/* draw title text */
+	cairo_move_to(cr, text_x, text_y);
+	cairo_set_rgba32(cr, &COL_SYM);
+	pango_cairo_show_layout(cr, layout);
+
+	/* draw fade-out from title text to buttons */
+	fade_start = *title_width - fade_width;
+	fade = cairo_pattern_create_linear(fade_start, 0,
+					   fade_start + 2 * BUTTON_WIDTH, 0);
+	cairo_pattern_add_color_stop_rgba(fade, 0,
+					  red(&COL_TITLE),
+					  green(&COL_TITLE),
+					  blue(&COL_TITLE),
+					  0);
+	cairo_pattern_add_color_stop_rgb(fade, 1,
+					 red(&COL_TITLE),
+					 green(&COL_TITLE),
+					 blue(&COL_TITLE));
+	cairo_rectangle(cr, fade_start, 0, fade_width, TITLE_HEIGHT);
+	cairo_set_source(cr, fade);
+	cairo_fill(cr);
+
+	cairo_pattern_destroy(fade);
+	g_object_unref(layout);
+}
+
+static void
+draw_component_content(struct libdecor_frame_cairo *frame_cairo,
+		       struct border_component *border_component,
+		       int component_width,
+		       int component_height,
+		       enum component component)
+{
+	struct buffer *buffer;
+	cairo_surface_t *surface = NULL;
+	int width = 0, height = 0;
+	int scale;
+	cairo_t *cr;
+
+	/* button symbol origin */
+	const double x = BUTTON_WIDTH / 2 - SYM_DIM / 2 + 0.5;
+	const double y = TITLE_HEIGHT / 2 - SYM_DIM / 2 + 0.5;
+
+	enum libdecor_window_state state;
+
+	bool cap_min, cap_max, cap_close;
+
+	/* capabilities of decorations */
+	cap_min = minimizable(frame_cairo);
+	cap_max = resizable(frame_cairo);
+	cap_close = closeable(frame_cairo);
+
+	scale = border_component_get_scale(border_component);
+
+	/* clear buffer */
+	switch (border_component->composite_mode) {
+	case COMPOSITE_SERVER:
+		buffer = border_component->server.buffer;
+
+		surface = cairo_image_surface_create_for_data(
+				  buffer->data, CAIRO_FORMAT_ARGB32,
+				  buffer->buffer_width, buffer->buffer_height,
+				  cairo_format_stride_for_width(
+					  CAIRO_FORMAT_ARGB32,
+					  buffer->buffer_width)
+				  );
+		cairo_surface_set_device_scale(surface, scale, scale);
+		width = buffer->width;
+		height = buffer->height;
+		break;
+	case COMPOSITE_CLIENT:
+		surface = cairo_surface_reference(border_component->client.image);
+		width = cairo_image_surface_get_width(surface);
+		height = cairo_image_surface_get_height(surface);
+		break;
+	}
+
+	cr = cairo_create(surface);
+	cairo_save(cr);
+	cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.0);
+	cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+	cairo_paint(cr);
+	cairo_restore(cr);
+
+	/* background */
+	switch (component) {
+	case NONE:
+		break;
+	case SHADOW:
+		render_shadow(cr,
+			      frame_cairo->shadow_blur,
+			      -(int)SHADOW_MARGIN/2,
+			      -(int)SHADOW_MARGIN/2,
+			      width + SHADOW_MARGIN,
+			      height + SHADOW_MARGIN,
+			      64,
+			      64);
+		break;
+	case TITLE:
+		cairo_set_rgba32(cr, &COL_TITLE);
+		cairo_paint(cr);
+		break;
+	case BUTTON_MIN:
+		if (cap_min && frame_cairo->active == &frame_cairo->title_bar.min)
+			cairo_set_rgba32(cr, &COL_BUTTON_MIN);
+		else
+			cairo_set_rgba32(cr, &COL_TITLE);
+		cairo_paint(cr);
+		break;
+	case BUTTON_MAX:
+		if (cap_max && frame_cairo->active == &frame_cairo->title_bar.max)
+			cairo_set_rgba32(cr, &COL_BUTTON_MAX);
+		else
+			cairo_set_rgba32(cr, &COL_TITLE);
+		cairo_paint(cr);
+		break;
+	case BUTTON_CLOSE:
+		if (cap_close && frame_cairo->active == &frame_cairo->title_bar.close)
+			cairo_set_rgba32(cr, &COL_BUTTON_CLOSE);
+		else
+			cairo_set_rgba32(cr, &COL_TITLE);
+		cairo_paint(cr);
+		break;
+	}
+
+	/* button symbols */
+	/* https://www.cairographics.org/FAQ/#sharp_lines */
+	cairo_set_line_width(cr, 1);
+
+	switch (component) {
+	case TITLE:
+		draw_title_text(frame_cairo,cr, &component_width);
+		break;
+	case BUTTON_MIN:
+		if (!cap_min || frame_cairo->active == &frame_cairo->title_bar.min)
+			cairo_set_rgba32(cr, &COL_SYM_ACT);
+		else
+			cairo_set_rgba32(cr, &COL_SYM);
+		cairo_move_to(cr, x, y + SYM_DIM - 1);
+		cairo_rel_line_to(cr, SYM_DIM - 1, 0);
+		cairo_stroke(cr);
+		break;
+	case BUTTON_MAX:
+		if (!cap_max || frame_cairo->active == &frame_cairo->title_bar.max)
+			cairo_set_rgba32(cr, &COL_SYM_ACT);
+		else
+			cairo_set_rgba32(cr, &COL_SYM);
+
+		state = libdecor_frame_get_window_state(
+				(struct libdecor_frame*)frame_cairo);
+		if (state & LIBDECOR_WINDOW_STATE_MAXIMIZED) {
+			const size_t small = 12;
+			cairo_rectangle(cr,
+					x,
+					y + SYM_DIM - small,
+					small - 1,
+					small - 1);
+			cairo_move_to(cr,
+				      x + SYM_DIM - small,
+				      y + SYM_DIM - small);
+			cairo_line_to(cr, x + SYM_DIM - small, y);
+			cairo_rel_line_to(cr, small - 1, 0);
+			cairo_rel_line_to(cr, 0, small - 1);
+			cairo_line_to(cr, x + small - 1, y + small - 1);
+		}
+		else {
+			cairo_rectangle(cr, x, y, SYM_DIM - 1, SYM_DIM - 1);
+		}
+		cairo_stroke(cr);
+		break;
+	case BUTTON_CLOSE:
+		if (!cap_close || frame_cairo->active == &frame_cairo->title_bar.close)
+			cairo_set_rgba32(cr, &COL_SYM_ACT);
+		else
+			cairo_set_rgba32(cr, &COL_SYM);
+		cairo_move_to(cr, x, y);
+		cairo_rel_line_to(cr, SYM_DIM - 1, SYM_DIM - 1);
+		cairo_move_to(cr, x + SYM_DIM - 1, y);
+		cairo_line_to(cr, x, y + SYM_DIM - 1);
+		cairo_stroke(cr);
+		break;
+	default:
+		break;
+	}
+
+	/* mask the toplevel surface */
+	if (component == SHADOW) {
+		int component_x, component_y, component_width, component_height;
+		calculate_component_size(frame_cairo, component,
+					 &component_x, &component_y,
+					 &component_width, &component_height);
+		cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
+		cairo_rectangle(cr, -component_x, -component_y,
+				libdecor_frame_get_content_width(
+					&frame_cairo->frame),
+				libdecor_frame_get_content_height(
+					&frame_cairo->frame));
+		cairo_fill(cr);
+	}
+
+	cairo_destroy(cr);
+	cairo_surface_destroy(surface);
+}
+
+static void
+set_component_input_region(struct libdecor_frame_cairo *frame_cairo,
+			   struct border_component *border_component)
+{
+	if (border_component->type == SHADOW && frame_cairo->shadow_showing) {
+		struct wl_region *input_region;
+		int component_x;
+		int component_y;
+		int component_width;
+		int component_height;
+
+		calculate_component_size(frame_cairo, border_component->type,
+					 &component_x, &component_y,
+					 &component_width, &component_height);
+
+		/*
+		 * the input region is the outer surface size minus the inner
+		 * content size
+		 */
+		input_region = wl_compositor_create_region(
+				       frame_cairo->plugin_cairo->wl_compositor);
+		wl_region_add(input_region, 0, 0,
+			      component_width, component_height);
+		wl_region_subtract(input_region, -component_x, -component_y,
+			libdecor_frame_get_content_width(&frame_cairo->frame),
+			libdecor_frame_get_content_height(&frame_cairo->frame));
+		wl_surface_set_input_region(border_component->server.wl_surface,
+					    input_region);
+		wl_region_destroy(input_region);
+	}
+}
+
+static void
+ensure_component_realized_server(struct libdecor_frame_cairo *frame_cairo,
+				 struct border_component *border_component,
+				 int component_width,
+				 int component_height,
+				 int scale)
+{
+	struct buffer *old_buffer;
+	struct buffer *buffer = NULL;
+
+	old_buffer = border_component->server.buffer;
+	if (old_buffer) {
+		if (!old_buffer->in_use &&
+		    old_buffer->buffer_width == component_width * scale &&
+		    old_buffer->buffer_height == component_height * scale) {
+			buffer = old_buffer;
+		} else {
+			buffer_free(old_buffer);
+			border_component->server.buffer = NULL;
+		}
+	}
+
+	if (!buffer)
+		buffer = create_shm_buffer(frame_cairo->plugin_cairo,
+					   component_width,
+					   component_height,
+					   border_component->opaque,
+					   border_component->server.scale);
+
+	border_component->server.buffer = buffer;
+}
+
+static void
+ensure_component_realized_client(struct libdecor_frame_cairo *frame_cairo,
+				 struct border_component *border_component,
+				 int component_width,
+				 int component_height,
+				 int scale)
+{
+	cairo_surface_t *old_image;
+
+	old_image = border_component->client.image;
+	if (old_image) {
+		int cairo_buffer_width;
+		int cairo_buffer_height;
+		double x_scale;
+		double y_scale;
+
+		cairo_surface_get_device_scale(old_image, &x_scale, &y_scale);
+		cairo_buffer_width =
+			(int) round(cairo_image_surface_get_width(old_image) *
+				    x_scale);
+		cairo_buffer_height =
+			(int) round(cairo_image_surface_get_height(old_image) *
+				    y_scale);
+
+		if (cairo_buffer_width != component_width * scale ||
+		    cairo_buffer_height != component_height * scale) {
+			cairo_surface_destroy(old_image);
+			border_component->client.image = NULL;
+		}
+	}
+
+	if (!border_component->client.image) {
+		cairo_surface_t *new_image;
+
+		new_image =
+			cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
+						   component_width * scale,
+						   component_height * scale);
+		cairo_surface_set_device_scale(new_image, scale, scale);
+		border_component->client.image = new_image;
+	}
+
+}
+
+static void
+ensure_component_realized(struct libdecor_frame_cairo *frame_cairo,
+			  struct border_component *border_component,
+			  int component_width,
+			  int component_height,
+			  int scale)
+{
+	switch (border_component->composite_mode) {
+	case COMPOSITE_SERVER:
+		ensure_component_realized_server(frame_cairo, border_component,
+						 component_width,
+						 component_height,
+						 scale);
+		break;
+	case COMPOSITE_CLIENT:
+		ensure_component_realized_client(frame_cairo, border_component,
+						 component_width,
+						 component_height,
+						 scale);
+		break;
+	}
+}
+
+static cairo_t *
+create_cairo_for_parent(struct border_component *border_component)
+{
+	struct border_component *parent =
+		border_component->client.parent_component;
+	struct buffer *buffer;
+	struct border_component *server_component;
+	cairo_surface_t *parent_surface;
+	cairo_t *cr;
+
+	switch (parent->composite_mode) {
+	case COMPOSITE_SERVER:
+		buffer = parent->server.buffer;
+		parent_surface = cairo_image_surface_create_for_data(
+				  buffer->data, CAIRO_FORMAT_ARGB32,
+				  buffer->buffer_width, buffer->buffer_height,
+				  cairo_format_stride_for_width(
+					  CAIRO_FORMAT_ARGB32,
+					  buffer->buffer_width)
+				  );
+		cr = cairo_create(parent_surface);
+		cairo_surface_destroy(parent_surface);
+		cairo_scale(cr, buffer->scale, buffer->scale);
+		return cr;
+	case COMPOSITE_CLIENT:
+		cr = cairo_create(parent->client.image);
+		server_component = get_server_component(border_component);
+		cairo_scale(cr,
+			    server_component->server.scale,
+			    server_component->server.scale);
+		return cr;
+	}
+	return NULL;
+}
+
+static void
+draw_border_component(struct libdecor_frame_cairo *frame_cairo,
+		      struct border_component *border_component)
+{
+	enum component component = border_component->type;
+	struct buffer *buffer;
+	cairo_t *cr;
+	int component_x;
+	int component_y;
+	int component_width;
+	int component_height;
+	int scale;
+	struct border_component *child_component;
+
+	if (border_component->is_hidden)
+		return;
+
+	calculate_component_size(frame_cairo, component,
+				 &component_x, &component_y,
+				 &component_width, &component_height);
+
+	set_component_input_region(frame_cairo, border_component);
+
+	scale = border_component_get_scale(border_component);
+	ensure_component_realized(frame_cairo, border_component,
+				  component_width,
+				  component_height,
+				  scale);
+
+	draw_component_content(frame_cairo,
+			       border_component,
+			       component_width, component_height,
+			       component);
+
+	switch(border_component->composite_mode) {
+	case COMPOSITE_SERVER:
+		buffer = border_component->server.buffer;
+		wl_surface_attach(border_component->server.wl_surface,
+				  buffer->wl_buffer,
+				  0, 0);
+		wl_surface_set_buffer_scale(border_component->server.wl_surface,
+					    buffer->scale);
+		buffer->in_use = true;
+		wl_surface_commit(border_component->server.wl_surface);
+		wl_surface_damage_buffer(border_component->server.wl_surface, 0, 0,
+					 component_width * scale,
+					 component_height * scale);
+		wl_subsurface_set_position(border_component->server.wl_subsurface,
+					   component_x, component_y);
+		break;
+	case COMPOSITE_CLIENT:
+		cr = create_cairo_for_parent(border_component);
+		cairo_set_source_surface(cr,
+					 border_component->client.image,
+					 component_x, component_y);
+		cairo_paint(cr);
+		cairo_destroy(cr);
+		break;
+	}
+
+	wl_list_for_each(child_component, &border_component->child_components, link)
+		draw_border_component(frame_cairo, child_component);
+}
+
+static void
+draw_border(struct libdecor_frame_cairo *frame_cairo)
+{
+	draw_border_component(frame_cairo, &frame_cairo->shadow);
+	frame_cairo->shadow_showing = true;
+}
+
+static void
+draw_title_bar(struct libdecor_frame_cairo *frame_cairo)
+{
+	draw_border_component(frame_cairo, &frame_cairo->title_bar.title);
+	frame_cairo->title_bar.is_showing = true;
+}
+
+static void
+draw_decoration(struct libdecor_frame_cairo *frame_cairo)
+{
+	switch (frame_cairo->decoration_type) {
+	case DECORATION_TYPE_NONE:
+		if (frame_cairo->link.next != NULL)
+			wl_list_remove(&frame_cairo->link);
+		if (is_border_surfaces_showing(frame_cairo))
+			hide_border_surfaces(frame_cairo);
+		if (is_title_bar_surfaces_showing(frame_cairo))
+			hide_title_bar_surfaces(frame_cairo);
+		break;
+	case DECORATION_TYPE_ALL:
+		/* show borders */
+		ensure_border_surfaces(frame_cairo);
+		draw_border(frame_cairo);
+		/* show title bar */
+		ensure_title_bar_surfaces(frame_cairo);
+		draw_title_bar(frame_cairo);
+		/* link frame */
+		if (frame_cairo->link.next == NULL)
+			wl_list_insert(
+				&frame_cairo->plugin_cairo->visible_frame_list,
+				&frame_cairo->link);
+		break;
+	case DECORATION_TYPE_TITLE_ONLY:
+		/* hide borders */
+		if (is_border_surfaces_showing(frame_cairo))
+			hide_border_surfaces(frame_cairo);
+		/* show title bar */
+		ensure_title_bar_surfaces(frame_cairo);
+		draw_title_bar(frame_cairo);
+		/* link frame */
+		if (frame_cairo->link.next == NULL)
+			wl_list_insert(
+				&frame_cairo->plugin_cairo->visible_frame_list,
+				&frame_cairo->link);
+		break;
+	}
+}
+
+static void
+set_window_geometry(struct libdecor_frame_cairo *frame_cairo)
+{
+	struct libdecor_frame *frame = &frame_cairo->frame;
+	int x = 0, y = 0, width = 0, height = 0;
+
+	switch (frame_cairo->decoration_type) {
+	case DECORATION_TYPE_NONE:
+		x = 0;
+		y = 0;
+		width = libdecor_frame_get_content_width(frame);
+		height = libdecor_frame_get_content_height(frame);
+		break;
+	case DECORATION_TYPE_ALL:
+	case DECORATION_TYPE_TITLE_ONLY:
+		x = 0;
+		y = -(int)TITLE_HEIGHT;
+		width = libdecor_frame_get_content_width(frame);
+		height = libdecor_frame_get_content_height(frame) + TITLE_HEIGHT;
+		break;
+	}
+
+	libdecor_frame_set_window_geometry(frame, x, y, width, height);
+}
+
+static enum decoration_type
+window_state_to_decoration_type(enum libdecor_window_state window_state)
+{
+	if (window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN)
+		return DECORATION_TYPE_NONE;
+	else if (window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED ||
+		 window_state & LIBDECOR_WINDOW_STATE_TILED_LEFT ||
+		 window_state & LIBDECOR_WINDOW_STATE_TILED_RIGHT ||
+		 window_state & LIBDECOR_WINDOW_STATE_TILED_TOP ||
+		 window_state & LIBDECOR_WINDOW_STATE_TILED_BOTTOM)
+		/* title bar, no shadows */
+		return DECORATION_TYPE_TITLE_ONLY;
+	else
+		/* title bar, shadows */
+		return DECORATION_TYPE_ALL;
+}
+
+static void
+libdecor_plugin_cairo_frame_commit(struct libdecor_plugin *plugin,
+				   struct libdecor_frame *frame,
+				   struct libdecor_state *state,
+				   struct libdecor_configuration *configuration)
+{
+	struct libdecor_frame_cairo *frame_cairo =
+		(struct libdecor_frame_cairo *) frame;
+	enum libdecor_window_state new_window_state;
+	int old_content_width, old_content_height;
+	int new_content_width, new_content_height;
+	enum decoration_type old_decoration_type;
+	enum decoration_type new_decoration_type;
+
+	new_window_state = libdecor_frame_get_window_state(frame);
+
+	old_content_width = frame_cairo->content_width;
+	old_content_height = frame_cairo->content_height;
+	new_content_width = libdecor_frame_get_content_width(frame);
+	new_content_height = libdecor_frame_get_content_height(frame);
+
+	old_decoration_type = frame_cairo->decoration_type;
+	new_decoration_type = window_state_to_decoration_type(new_window_state);
+
+	if (old_decoration_type == new_decoration_type &&
+	    old_content_width == new_content_width &&
+	    old_content_height == new_content_height)
+		return;
+
+	frame_cairo->content_width = new_content_width;
+	frame_cairo->content_height = new_content_height;
+	frame_cairo->decoration_type = new_decoration_type;
+
+	draw_decoration(frame_cairo);
+	set_window_geometry(frame_cairo);
+}
+
+static void
+libdecor_plugin_cairo_frame_property_changed(struct libdecor_plugin *plugin,
+					     struct libdecor_frame *frame)
+{
+	struct libdecor_frame_cairo *frame_cairo =
+		(struct libdecor_frame_cairo *) frame;
+	bool redraw_needed = false;
+	const char *new_title;
+
+	new_title = libdecor_frame_get_title(frame);
+	if (frame_cairo->title_bar.is_showing) {
+		if (!streql(frame_cairo->title, new_title))
+			redraw_needed = true;
+	}
+	free(frame_cairo->title);
+	if (new_title)
+		frame_cairo->title = strdup(new_title);
+	else
+		frame_cairo->title = NULL;
+
+	if (frame_cairo->capabilities != libdecor_frame_get_capabilities(frame)) {
+		frame_cairo->capabilities = libdecor_frame_get_capabilities(frame);
+		redraw_needed = true;
+	}
+
+	if (redraw_needed) {
+		draw_decoration(frame_cairo);
+		libdecor_frame_toplevel_commit(frame);
+	}
+}
+
+static void
+libdecor_plugin_cairo_frame_translate_coordinate(struct libdecor_plugin *plugin,
+						 struct libdecor_frame *frame,
+						 int content_x,
+						 int content_y,
+						 int *frame_x,
+						 int *frame_y)
+{
+	struct libdecor_frame_cairo *frame_cairo =
+		(struct libdecor_frame_cairo *) frame;
+
+	*frame_x = content_x;
+	*frame_y = content_y;
+
+	if (frame_cairo->title_bar.is_showing)
+		*frame_y += TITLE_HEIGHT;
+}
+
+static bool
+streq(const char *str1,
+      const char *str2)
+{
+	if (!str1 && !str2)
+		return true;
+
+	if (str1 && str2)
+		return strcmp(str1, str2) == 0;
+
+	return false;
+}
+
+static void
+libdecor_plugin_cairo_frame_popup_grab(struct libdecor_plugin *plugin,
+				       struct libdecor_frame *frame,
+				       const char *seat_name)
+{
+	struct libdecor_frame_cairo *frame_cairo =
+		(struct libdecor_frame_cairo *) frame;
+	struct libdecor_plugin_cairo *plugin_cairo = frame_cairo->plugin_cairo;
+	struct seat *seat;
+
+	wl_list_for_each(seat, &plugin_cairo->seat_list, link) {
+		if (streq(seat->name, seat_name)) {
+			if (seat->grabbed) {
+				fprintf(stderr, "libdecor-WARNING: Application "
+					"tried to grab seat twice\n");
+			}
+			synthesize_pointer_leave(seat);
+			seat->grabbed = true;
+			return;
+		}
+	}
+
+	fprintf(stderr,
+		"libdecor-WARNING: Application tried to grab unknown seat\n");
+}
+
+static void
+libdecor_plugin_cairo_frame_popup_ungrab(struct libdecor_plugin *plugin,
+					 struct libdecor_frame *frame,
+					 const char *seat_name)
+{
+	struct libdecor_frame_cairo *frame_cairo =
+		(struct libdecor_frame_cairo *) frame;
+	struct libdecor_plugin_cairo *plugin_cairo = frame_cairo->plugin_cairo;
+	struct seat *seat;
+
+	wl_list_for_each(seat, &plugin_cairo->seat_list, link) {
+		if (streq(seat->name, seat_name)) {
+			if (!seat->grabbed) {
+				fprintf(stderr, "libdecor-WARNING: Application "
+					"tried to ungrab seat twice\n");
+			}
+			seat->grabbed = false;
+			synthesize_pointer_enter(seat);
+			sync_active_component(frame_cairo, seat);
+			return;
+		}
+	}
+
+	fprintf(stderr,
+		"libdecor-WARNING: Application tried to ungrab unknown seat\n");
+}
+
+static bool
+libdecor_plugin_cairo_configuration_get_content_size(
+		struct libdecor_plugin *plugin,
+		struct libdecor_configuration *configuration,
+		struct libdecor_frame *frame,
+		int *content_width,
+		int *content_height)
+{
+	int win_width, win_height;
+	if (!libdecor_configuration_get_window_size(configuration,
+						    &win_width,
+						    &win_height))
+		return false;
+
+	enum libdecor_window_state state;
+	if (!libdecor_configuration_get_window_state(configuration, &state)) {
+		return false;
+	}
+
+	switch (window_state_to_decoration_type(state)) {
+	case DECORATION_TYPE_NONE:
+		*content_width = win_width;
+		*content_height = win_height;
+		break;
+	case DECORATION_TYPE_ALL:
+	case DECORATION_TYPE_TITLE_ONLY:
+		*content_width = win_width;
+		*content_height = win_height - TITLE_HEIGHT;
+		break;
+	}
+
+	return true;
+}
+
+static bool
+libdecor_plugin_cairo_frame_get_window_size_for(
+		struct libdecor_plugin *plugin,
+		struct libdecor_frame *frame,
+		struct libdecor_state *state,
+		int *window_width,
+		int *window_height)
+{
+	enum libdecor_window_state window_state =
+		libdecor_state_get_window_state (state);
+
+	switch (window_state_to_decoration_type(window_state)) {
+	case DECORATION_TYPE_NONE:
+		*window_width = libdecor_state_get_content_width (state);
+		*window_height = libdecor_state_get_content_height (state);
+		break;
+	case DECORATION_TYPE_ALL:
+	case DECORATION_TYPE_TITLE_ONLY:
+		*window_width = libdecor_state_get_content_width (state);
+		*window_height =
+			libdecor_state_get_content_height (state) + TITLE_HEIGHT;
+		break;
+	}
+
+	return true;
+}
+
+static struct libdecor_plugin_interface cairo_plugin_iface = {
+	.destroy = libdecor_plugin_cairo_destroy,
+	.get_fd = libdecor_plugin_cairo_get_fd,
+	.dispatch = libdecor_plugin_cairo_dispatch,
+
+	.frame_new = libdecor_plugin_cairo_frame_new,
+	.frame_free = libdecor_plugin_cairo_frame_free,
+	.frame_commit = libdecor_plugin_cairo_frame_commit,
+	.frame_property_changed = libdecor_plugin_cairo_frame_property_changed,
+	.frame_translate_coordinate =
+		libdecor_plugin_cairo_frame_translate_coordinate,
+	.frame_popup_grab = libdecor_plugin_cairo_frame_popup_grab,
+	.frame_popup_ungrab = libdecor_plugin_cairo_frame_popup_ungrab,
+
+	.configuration_get_content_size =
+			libdecor_plugin_cairo_configuration_get_content_size,
+	.frame_get_window_size_for =
+			libdecor_plugin_cairo_frame_get_window_size_for,
+};
+
+static void
+init_wl_compositor(struct libdecor_plugin_cairo *plugin_cairo,
+		   uint32_t id,
+		   uint32_t version)
+{
+	plugin_cairo->wl_compositor =
+		wl_registry_bind(plugin_cairo->wl_registry,
+				 id, &wl_compositor_interface,
+				 MIN(version, 4));
+}
+
+static void
+init_wl_subcompositor(struct libdecor_plugin_cairo *plugin_cairo,
+		      uint32_t id,
+		      uint32_t version)
+{
+	plugin_cairo->wl_subcompositor =
+		wl_registry_bind(plugin_cairo->wl_registry,
+				 id, &wl_subcompositor_interface, 1);
+}
+
+static void
+shm_format(void *user_data,
+	   struct wl_shm *wl_shm,
+	   uint32_t format)
+{
+	struct libdecor_plugin_cairo *plugin_cairo = user_data;
+
+	if (format == WL_SHM_FORMAT_ARGB8888)
+		plugin_cairo->has_argb = true;
+}
+
+struct wl_shm_listener shm_listener = {
+	shm_format
+};
+
+static void
+shm_callback(void *user_data,
+		 struct wl_callback *callback,
+		 uint32_t time)
+{
+	struct libdecor_plugin_cairo *plugin_cairo = user_data;
+	struct libdecor *context = plugin_cairo->context;
+
+	wl_callback_destroy(callback);
+	plugin_cairo->globals_callback_shm = NULL;
+
+	if (!plugin_cairo->has_argb) {
+		libdecor_notify_plugin_error(
+				context,
+				LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE,
+				"Compositor is missing required shm format");
+		return;
+	}
+
+	libdecor_notify_plugin_ready(context);
+}
+
+static const struct wl_callback_listener shm_callback_listener = {
+	shm_callback
+};
+
+static void
+init_wl_shm(struct libdecor_plugin_cairo *plugin_cairo,
+	    uint32_t id,
+	    uint32_t version)
+{
+	struct libdecor *context = plugin_cairo->context;
+	struct wl_display *wl_display = libdecor_get_wl_display(context);
+
+	plugin_cairo->wl_shm =
+		wl_registry_bind(plugin_cairo->wl_registry,
+				 id, &wl_shm_interface, 1);
+	wl_shm_add_listener(plugin_cairo->wl_shm, &shm_listener, plugin_cairo);
+
+	plugin_cairo->globals_callback_shm = wl_display_sync(wl_display);
+	wl_callback_add_listener(plugin_cairo->globals_callback_shm,
+				 &shm_callback_listener,
+				 plugin_cairo);
+}
+
+static void
+cursor_surface_enter(void *data,
+		     struct wl_surface *wl_surface,
+		     struct wl_output *wl_output)
+{
+	struct seat *seat = data;
+
+	if(own_output(wl_output)) {
+		struct cursor_output *cursor_output;
+		cursor_output = zalloc(sizeof *cursor_output);
+		cursor_output->output = wl_output_get_user_data(wl_output);
+		wl_list_insert(&seat->cursor_outputs, &cursor_output->link);
+		if (update_local_cursor(seat))
+			send_cursor(seat);
+	}
+}
+
+static void
+cursor_surface_leave(void *data,
+		     struct wl_surface *wl_surface,
+		     struct wl_output *wl_output)
+{
+	struct seat *seat = data;
+
+	if(own_output(wl_output)) {
+		struct cursor_output *cursor_output, *tmp;
+		wl_list_for_each_safe(cursor_output, tmp, &seat->cursor_outputs, link) {
+			if (cursor_output->output->wl_output == wl_output) {
+				wl_list_remove(&cursor_output->link);
+				free(cursor_output);
+			}
+		}
+
+		if (update_local_cursor(seat))
+			send_cursor(seat);
+	}
+}
+
+static struct wl_surface_listener cursor_surface_listener = {
+	cursor_surface_enter,
+	cursor_surface_leave,
+};
+
+static void
+ensure_cursor_surface(struct seat *seat)
+{
+	struct wl_compositor *wl_compositor = seat->plugin_cairo->wl_compositor;
+
+	if (seat->cursor_surface)
+		return;
+
+	seat->cursor_surface = wl_compositor_create_surface(wl_compositor);
+	wl_surface_add_listener(seat->cursor_surface,
+				&cursor_surface_listener, seat);
+}
+
+static bool
+ensure_cursor_theme(struct seat *seat)
+{
+	struct libdecor_plugin_cairo *plugin_cairo = seat->plugin_cairo;
+	int scale = 1;
+	struct wl_cursor_theme *theme;
+	struct cursor_output *cursor_output;
+
+	wl_list_for_each(cursor_output, &seat->cursor_outputs, link) {
+		scale = MAX(scale, cursor_output->output->scale);
+	}
+
+	if (seat->cursor_theme && seat->cursor_scale == scale)
+		return false;
+
+	seat->cursor_scale = scale;
+	theme = wl_cursor_theme_load(plugin_cairo->cursor_theme_name,
+				     plugin_cairo->cursor_size * scale,
+				     plugin_cairo->wl_shm);
+	if (theme == NULL)
+		return false;
+
+	if (seat->cursor_theme)
+		wl_cursor_theme_destroy(seat->cursor_theme);
+
+	seat->cursor_theme = theme;
+
+	for (unsigned int i = 0; i < ARRAY_LENGTH(cursor_names); i++) {
+		seat->cursors[i] = wl_cursor_theme_get_cursor(
+						   seat->cursor_theme,
+						   cursor_names[i]);
+	}
+
+	seat->cursor_left_ptr = wl_cursor_theme_get_cursor(seat->cursor_theme,
+							   "left_ptr");
+	seat->current_cursor = seat->cursor_left_ptr;
+
+	return true;
+}
+
+enum libdecor_resize_edge
+component_edge(const struct border_component *cmpnt,
+	       const int pointer_x,
+	       const int pointer_y,
+	       const int margin)
+{
+	const bool top = pointer_y < margin;
+	const bool bottom = pointer_y > (cmpnt->server.buffer->height - margin);
+	const bool left = pointer_x < margin;
+	const bool right = pointer_x > (cmpnt->server.buffer->width - margin);
+
+	if (top)
+		if (left)
+			return LIBDECOR_RESIZE_EDGE_TOP_LEFT;
+		else if (right)
+			return LIBDECOR_RESIZE_EDGE_TOP_RIGHT;
+		else
+			return LIBDECOR_RESIZE_EDGE_TOP;
+	else if (bottom)
+		if (left)
+			return LIBDECOR_RESIZE_EDGE_BOTTOM_LEFT;
+		else if (right)
+			return LIBDECOR_RESIZE_EDGE_BOTTOM_RIGHT;
+		else
+			return LIBDECOR_RESIZE_EDGE_BOTTOM;
+	else if (left)
+		return LIBDECOR_RESIZE_EDGE_LEFT;
+	else if (right)
+		return LIBDECOR_RESIZE_EDGE_RIGHT;
+	else
+		return LIBDECOR_RESIZE_EDGE_NONE;
+}
+
+static bool
+update_local_cursor(struct seat *seat)
+{
+	if (!seat->pointer_focus) {
+		seat->current_cursor = seat->cursor_left_ptr;
+		return false;
+	}
+
+	if (!own_surface(seat->pointer_focus))
+		return false;
+
+	struct libdecor_frame_cairo *frame_cairo =
+			wl_surface_get_user_data(seat->pointer_focus);
+	struct wl_cursor *wl_cursor = NULL;
+
+	if (!frame_cairo || !frame_cairo->active) {
+		seat->current_cursor = seat->cursor_left_ptr;
+		return false;
+	}
+
+	bool theme_updated = ensure_cursor_theme(seat);
+
+	if (frame_cairo->active->type == SHADOW &&
+	    is_border_surfaces_showing(frame_cairo) &&
+	    resizable(frame_cairo)) {
+		enum libdecor_resize_edge edge;
+		edge = component_edge(frame_cairo->active,
+				      seat->pointer_x,
+				      seat->pointer_y, SHADOW_MARGIN);
+
+		if (edge != LIBDECOR_RESIZE_EDGE_NONE)
+			wl_cursor = seat->cursors[edge - 1];
+	} else {
+		wl_cursor = seat->cursor_left_ptr;
+	}
+
+	if (seat->current_cursor != wl_cursor) {
+		seat->current_cursor = wl_cursor;
+		return true;
+	}
+
+	return theme_updated;
+}
+
+static void
+send_cursor(struct seat *seat)
+{
+	struct wl_cursor_image *image;
+	struct wl_buffer *buffer;
+
+	if (seat->pointer_focus == NULL || seat->current_cursor == NULL)
+		return;
+
+	image = seat->current_cursor->images[0];
+	buffer = wl_cursor_image_get_buffer(image);
+	wl_surface_attach(seat->cursor_surface, buffer, 0, 0);
+	wl_surface_set_buffer_scale(seat->cursor_surface, seat->cursor_scale);
+	wl_surface_damage_buffer(seat->cursor_surface, 0, 0,
+				 image->width * seat->cursor_scale,
+				 image->height * seat->cursor_scale);
+	wl_surface_commit(seat->cursor_surface);
+	wl_pointer_set_cursor(seat->wl_pointer, seat->serial,
+			      seat->cursor_surface,
+			      image->hotspot_x / seat->cursor_scale,
+			      image->hotspot_y / seat->cursor_scale);
+}
+
+static void
+sync_active_component(struct libdecor_frame_cairo *frame_cairo,
+		      struct seat *seat)
+{
+	struct border_component *old_active;
+
+	if (!seat->pointer_focus)
+		return;
+
+	old_active = frame_cairo->active;
+	update_component_focus(frame_cairo, seat->pointer_focus, seat);
+	if (old_active != frame_cairo->active) {
+		draw_decoration(frame_cairo);
+		libdecor_frame_toplevel_commit(&frame_cairo->frame);
+	}
+
+	if (update_local_cursor(seat))
+		send_cursor(seat);
+}
+
+static void
+synthesize_pointer_enter(struct seat *seat)
+{
+	struct wl_surface *surface;
+	struct libdecor_frame_cairo *frame_cairo;
+
+	surface = seat->pointer_focus;
+	if (!surface)
+		return;
+
+	frame_cairo = wl_surface_get_user_data(surface);
+	if (!frame_cairo)
+		return;
+
+	update_component_focus(frame_cairo, seat->pointer_focus, seat);
+	frame_cairo->grab = NULL;
+
+	/* update decorations */
+	if (frame_cairo->active) {
+		draw_decoration(frame_cairo);
+		libdecor_frame_toplevel_commit(&frame_cairo->frame);
+	}
+
+	update_local_cursor(seat);
+	send_cursor(seat);
+}
+
+static void
+synthesize_pointer_leave(struct seat *seat)
+{
+	struct wl_surface *surface;
+	struct libdecor_frame_cairo *frame_cairo;
+
+	surface = seat->pointer_focus;
+	if (!surface)
+		return;
+
+	frame_cairo = wl_surface_get_user_data(surface);
+	if (!frame_cairo)
+		return;
+
+	if (!frame_cairo->active)
+		return;
+
+	frame_cairo->active = NULL;
+	draw_decoration(frame_cairo);
+	libdecor_frame_toplevel_commit(&frame_cairo->frame);
+	update_local_cursor(seat);
+}
+
+static void
+pointer_enter(void *data,
+	      struct wl_pointer *wl_pointer,
+	      uint32_t serial,
+	      struct wl_surface *surface,
+	      wl_fixed_t surface_x,
+	      wl_fixed_t surface_y)
+{
+	struct seat *seat = data;
+
+	if (!surface)
+		return;
+
+	if (!own_surface(surface))
+		return;
+
+	ensure_cursor_surface(seat);
+
+	seat->pointer_x = wl_fixed_to_int(surface_x);
+	seat->pointer_y = wl_fixed_to_int(surface_y);
+	seat->serial = serial;
+	seat->pointer_focus = surface;
+
+	if (seat->grabbed)
+		return;
+
+	synthesize_pointer_enter(seat);
+}
+
+static void
+pointer_leave(void *data,
+	      struct wl_pointer *wl_pointer,
+	      uint32_t serial,
+	      struct wl_surface *surface)
+{
+	struct seat *seat = data;
+
+	if (!surface)
+		return;
+
+	if (!own_surface(surface))
+		return;
+
+	synthesize_pointer_leave(seat);
+	seat->pointer_focus = NULL;
+}
+
+static void
+pointer_motion(void *data,
+	       struct wl_pointer *wl_pointer,
+	       uint32_t time,
+	       wl_fixed_t surface_x,
+	       wl_fixed_t surface_y)
+{
+	struct seat *seat = data;
+	struct libdecor_frame_cairo *frame_cairo;
+
+	seat->pointer_x = wl_fixed_to_int(surface_x);
+	seat->pointer_y = wl_fixed_to_int(surface_y);
+
+	if (seat->grabbed)
+		return;
+
+	if (!seat->pointer_focus)
+		return;
+
+	frame_cairo = wl_surface_get_user_data(seat->pointer_focus);
+
+	sync_active_component(frame_cairo, seat);
+}
+
+static void
+pointer_button(void *data,
+	       struct wl_pointer *wl_pointer,
+	       uint32_t serial,
+	       uint32_t time,
+	       uint32_t button,
+	       uint32_t state)
+{
+	struct seat *seat = data;
+	struct libdecor_frame_cairo *frame_cairo;
+
+	if (!seat->pointer_focus || !own_surface(seat->pointer_focus))
+		return;
+
+	frame_cairo = wl_surface_get_user_data(seat->pointer_focus);
+	if (!frame_cairo)
+		return;
+
+	if (seat->grabbed) {
+		libdecor_frame_dismiss_popup(&frame_cairo->frame, seat->name);
+		return;
+	}
+
+	if (!frame_cairo->active)
+		return;
+
+	if (button == BTN_LEFT) {
+		if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
+			enum libdecor_resize_edge edge =
+					LIBDECOR_RESIZE_EDGE_NONE;
+
+			frame_cairo->grab = NULL;
+
+			switch (frame_cairo->active->type) {
+			case SHADOW:
+				edge = component_edge(frame_cairo->active,
+						      seat->pointer_x,
+						      seat->pointer_y,
+						      SHADOW_MARGIN);
+				break;
+			case TITLE:
+				if (time-seat->pointer_button_time_stamp <
+				    DOUBLE_CLICK_TIME_MS) {
+					toggle_maximized(&frame_cairo->frame);
+				}
+				else if (moveable(frame_cairo)) {
+					seat->pointer_button_time_stamp = time;
+					libdecor_frame_move(&frame_cairo->frame,
+							    seat->wl_seat,
+							    serial);
+				}
+				break;
+			case BUTTON_MIN:
+			case BUTTON_MAX:
+			case BUTTON_CLOSE:
+				frame_cairo->grab = frame_cairo->active;
+				break;
+			default:
+				break;
+			}
+
+			if (edge != LIBDECOR_RESIZE_EDGE_NONE &&
+			    resizable(frame_cairo)) {
+				libdecor_frame_resize(
+					&frame_cairo->frame,
+					seat->wl_seat,
+					serial,
+					edge);
+			}
+		}
+		else if (state == WL_POINTER_BUTTON_STATE_RELEASED &&
+			 frame_cairo->grab) {
+			if (frame_cairo->grab == frame_cairo->focus) {
+				switch (frame_cairo->active->type) {
+				case BUTTON_MIN:
+					if (minimizable(frame_cairo))
+						libdecor_frame_set_minimized(
+							&frame_cairo->frame);
+					break;
+				case BUTTON_MAX:
+					toggle_maximized(&frame_cairo->frame);
+					break;
+				case BUTTON_CLOSE:
+					if (closeable(frame_cairo))
+						libdecor_frame_close(&frame_cairo->frame);
+					break;
+				default:
+					break;
+				}
+			}
+			frame_cairo->grab = NULL;
+			sync_active_component(frame_cairo, seat);
+		}
+	}
+	else if (button == BTN_RIGHT &&
+		 state == WL_POINTER_BUTTON_STATE_PRESSED &&
+		 seat->pointer_focus == frame_cairo->title_bar.title.server.wl_surface) {
+			libdecor_frame_show_window_menu(&frame_cairo->frame,
+							seat->wl_seat,
+							serial,
+							seat->pointer_x,
+							seat->pointer_y - TITLE_HEIGHT);
+	}
+}
+
+static void
+pointer_axis(void *data,
+	     struct wl_pointer *wl_pointer,
+	     uint32_t time,
+	     uint32_t axis,
+	     wl_fixed_t value)
+{
+}
+
+static struct wl_pointer_listener pointer_listener = {
+	pointer_enter,
+	pointer_leave,
+	pointer_motion,
+	pointer_button,
+	pointer_axis
+};
+
+static void
+seat_capabilities(void *data,
+		  struct wl_seat *wl_seat,
+		  uint32_t capabilities)
+{
+	struct seat *seat = data;
+
+	if ((capabilities & WL_SEAT_CAPABILITY_POINTER) &&
+	    !seat->wl_pointer) {
+		seat->wl_pointer = wl_seat_get_pointer(wl_seat);
+		wl_pointer_add_listener(seat->wl_pointer,
+					&pointer_listener, seat);
+	} else if (!(capabilities & WL_SEAT_CAPABILITY_POINTER) &&
+		   seat->wl_pointer) {
+		wl_pointer_release(seat->wl_pointer);
+		seat->wl_pointer = NULL;
+	}
+}
+
+static void
+seat_name(void *data,
+	  struct wl_seat *wl_seat,
+	  const char *name)
+{
+	struct seat *seat = data;
+
+	seat->name = strdup(name);
+}
+
+static struct wl_seat_listener seat_listener = {
+	seat_capabilities,
+	seat_name
+};
+
+static void
+init_wl_seat(struct libdecor_plugin_cairo *plugin_cairo,
+	     uint32_t id,
+	     uint32_t version)
+{
+	struct seat *seat;
+
+	if (version < 3) {
+		libdecor_notify_plugin_error(
+				plugin_cairo->context,
+				LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE,
+				"%s version 3 required but only version %i is available\n",
+				wl_seat_interface.name, version);
+	}
+
+	seat = zalloc(sizeof *seat);
+	seat->cursor_scale = 1;
+	seat->plugin_cairo = plugin_cairo;
+	wl_list_init(&seat->cursor_outputs);
+	wl_list_insert(&plugin_cairo->seat_list, &seat->link);
+	seat->wl_seat =
+		wl_registry_bind(plugin_cairo->wl_registry,
+				 id, &wl_seat_interface, 3);
+	wl_seat_add_listener(seat->wl_seat, &seat_listener, seat);
+}
+
+static void
+output_geometry(void *data,
+		struct wl_output *wl_output,
+		int32_t x,
+		int32_t y,
+		int32_t physical_width,
+		int32_t physical_height,
+		int32_t subpixel,
+		const char *make,
+		const char *model,
+		int32_t transform)
+{
+}
+
+static void
+output_mode(void *data,
+	    struct wl_output *wl_output,
+	    uint32_t flags,
+	    int32_t width,
+	    int32_t height,
+	    int32_t refresh)
+{
+}
+
+static void
+output_done(void *data,
+	    struct wl_output *wl_output)
+{
+	struct output *output = data;
+	struct libdecor_frame_cairo *frame_cairo;
+	struct seat *seat;
+
+	wl_list_for_each(frame_cairo,
+			&output->plugin_cairo->visible_frame_list, link) {
+		bool updated = false;
+		updated |= redraw_scale(frame_cairo, &frame_cairo->shadow);
+		updated |= redraw_scale(frame_cairo, &frame_cairo->title_bar.title);
+		if (updated)
+			libdecor_frame_toplevel_commit(&frame_cairo->frame);
+	}
+	wl_list_for_each(seat, &output->plugin_cairo->seat_list, link) {
+		if (update_local_cursor(seat))
+			send_cursor(seat);
+	}
+}
+
+static void
+output_scale(void *data,
+	     struct wl_output *wl_output,
+	     int32_t factor)
+{
+	struct output *output = data;
+
+	output->scale = factor;
+}
+
+static struct wl_output_listener output_listener = {
+	output_geometry,
+	output_mode,
+	output_done,
+	output_scale
+};
+
+static void
+init_wl_output(struct libdecor_plugin_cairo *plugin_cairo,
+	       uint32_t id,
+	       uint32_t version)
+{
+	struct output *output;
+
+	if (version < 2) {
+		libdecor_notify_plugin_error(
+				plugin_cairo->context,
+				LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE,
+				"%s version 2 required but only version %i is available\n",
+				wl_output_interface.name, version);
+	}
+
+	output = zalloc(sizeof *output);
+	output->plugin_cairo = plugin_cairo;
+	wl_list_insert(&plugin_cairo->output_list, &output->link);
+	output->id = id;
+	output->wl_output =
+		wl_registry_bind(plugin_cairo->wl_registry,
+				 id, &wl_output_interface, 2);
+	wl_proxy_set_tag((struct wl_proxy *) output->wl_output,
+			 &libdecor_cairo_proxy_tag);
+	wl_output_add_listener(output->wl_output, &output_listener, output);
+}
+
+static void
+registry_handle_global(void *user_data,
+		       struct wl_registry *wl_registry,
+		       uint32_t id,
+		       const char *interface,
+		       uint32_t version)
+{
+	struct libdecor_plugin_cairo *plugin_cairo = user_data;
+
+	if (strcmp(interface, "wl_compositor") == 0)
+		init_wl_compositor(plugin_cairo, id, version);
+	else if (strcmp(interface, "wl_subcompositor") == 0)
+		init_wl_subcompositor(plugin_cairo, id, version);
+	else if (strcmp(interface, "wl_shm") == 0)
+		init_wl_shm(plugin_cairo, id, version);
+	else if (strcmp(interface, "wl_seat") == 0)
+		init_wl_seat(plugin_cairo, id, version);
+	else if (strcmp(interface, "wl_output") == 0)
+		init_wl_output(plugin_cairo, id, version);
+}
+
+static void
+remove_surface_outputs(struct border_component *cmpnt, struct output *output)
+{
+	struct surface_output *surface_output;
+	wl_list_for_each(surface_output, &cmpnt->server.output_list, link) {
+		if (surface_output->output == output) {
+			wl_list_remove(&surface_output->link);
+			free(surface_output);
+			break;
+		}
+	}
+}
+
+static void
+output_removed(struct libdecor_plugin_cairo *plugin_cairo,
+	       struct output *output)
+{
+	struct libdecor_frame_cairo *frame_cairo;
+	struct seat *seat;
+
+	wl_list_for_each(frame_cairo, &plugin_cairo->visible_frame_list, link) {
+		remove_surface_outputs(&frame_cairo->shadow, output);
+		remove_surface_outputs(&frame_cairo->title_bar.title, output);
+		remove_surface_outputs(&frame_cairo->title_bar.min, output);
+		remove_surface_outputs(&frame_cairo->title_bar.max, output);
+		remove_surface_outputs(&frame_cairo->title_bar.close, output);
+	}
+	wl_list_for_each(seat, &plugin_cairo->seat_list, link) {
+		struct cursor_output *cursor_output;
+		wl_list_for_each(cursor_output, &seat->cursor_outputs, link) {
+			if (cursor_output->output == output) {
+				wl_list_remove(&cursor_output->link);
+				free(cursor_output);
+			}
+		}
+	}
+
+	wl_list_remove(&output->link);
+	wl_output_destroy(output->wl_output);
+	free(output);
+}
+
+static void
+registry_handle_global_remove(void *user_data,
+			      struct wl_registry *wl_registry,
+			      uint32_t name)
+{
+	struct libdecor_plugin_cairo *plugin_cairo = user_data;
+	struct output *output;
+
+	wl_list_for_each(output, &plugin_cairo->output_list, link) {
+		if (output->id == name) {
+			output_removed(plugin_cairo, output);
+			break;
+		}
+	}
+}
+
+static const struct wl_registry_listener registry_listener = {
+	registry_handle_global,
+	registry_handle_global_remove
+};
+
+static bool
+has_required_globals(struct libdecor_plugin_cairo *plugin_cairo)
+{
+	if (!plugin_cairo->wl_compositor)
+		return false;
+	if (!plugin_cairo->wl_subcompositor)
+		return false;
+	if (!plugin_cairo->wl_shm)
+		return false;
+
+	return true;
+}
+
+static void
+globals_callback(void *user_data,
+		 struct wl_callback *callback,
+		 uint32_t time)
+{
+	struct libdecor_plugin_cairo *plugin_cairo = user_data;
+
+	wl_callback_destroy(callback);
+	plugin_cairo->globals_callback = NULL;
+
+	if (!has_required_globals(plugin_cairo)) {
+		libdecor_notify_plugin_error(
+				plugin_cairo->context,
+				LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE,
+				"Compositor is missing required globals");
+	}
+}
+
+static const struct wl_callback_listener globals_callback_listener = {
+	globals_callback
+};
+
+static struct libdecor_plugin *
+libdecor_plugin_new(struct libdecor *context)
+{
+	struct libdecor_plugin_cairo *plugin_cairo;
+	struct wl_display *wl_display;
+
+	plugin_cairo = zalloc(sizeof *plugin_cairo);
+	plugin_cairo->plugin.iface = &cairo_plugin_iface;
+	plugin_cairo->context = context;
+
+	wl_list_init(&plugin_cairo->visible_frame_list);
+	wl_list_init(&plugin_cairo->seat_list);
+	wl_list_init(&plugin_cairo->output_list);
+
+	/* fetch cursor theme and size*/
+	if (!libdecor_get_cursor_settings(&plugin_cairo->cursor_theme_name,
+					  &plugin_cairo->cursor_size)) {
+		plugin_cairo->cursor_theme_name = NULL;
+		plugin_cairo->cursor_size = 24;
+	}
+
+	wl_display = libdecor_get_wl_display(context);
+	plugin_cairo->wl_registry = wl_display_get_registry(wl_display);
+	wl_registry_add_listener(plugin_cairo->wl_registry,
+				 &registry_listener,
+				 plugin_cairo);
+
+	plugin_cairo->globals_callback = wl_display_sync(wl_display);
+	wl_callback_add_listener(plugin_cairo->globals_callback,
+				 &globals_callback_listener,
+				 plugin_cairo);
+
+	return &plugin_cairo->plugin;
+}
+
+static struct libdecor_plugin_priority priorities[] = {
+	{ NULL, LIBDECOR_PLUGIN_PRIORITY_MEDIUM }
+};
+
+LIBDECOR_EXPORT const struct libdecor_plugin_description
+libdecor_plugin_description = {
+	.api_version = LIBDECOR_PLUGIN_API_VERSION,
+	.description = "libdecor plugin using Cairo",
+	.priorities = priorities,
+	.constructor = libdecor_plugin_new,
+};
diff --git libdecor/src/plugins/dummy/libdecor-dummy.c libdecor/src/plugins/dummy/libdecor-dummy.c
new file mode 100644
index 0000000..1891b7e
--- /dev/null
+++ libdecor/src/plugins/dummy/libdecor-dummy.c
@@ -0,0 +1,176 @@
+/*
+ * Copyright © 2021 Jonas �dahl
+ *
+ * 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 (including the
+ * next paragraph) 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 "config.h"
+
+#include "libdecor-plugin.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <wayland-cursor.h>
+
+#include "utils.h"
+
+struct libdecor_plugin_dummy {
+	struct libdecor_plugin plugin;
+	struct libdecor *context;
+};
+
+static void
+libdecor_plugin_dummy_destroy(struct libdecor_plugin *plugin)
+{
+	struct libdecor_plugin_dummy *plugin_dummy =
+		(struct libdecor_plugin_dummy *) plugin;
+
+	free(plugin_dummy);
+}
+
+static struct libdecor_frame *
+libdecor_plugin_dummy_frame_new(struct libdecor_plugin *plugin)
+{
+	struct libdecor_frame *frame;
+
+	frame = zalloc(sizeof *frame);
+
+	return frame;
+}
+
+static void
+libdecor_plugin_dummy_frame_free(struct libdecor_plugin *plugin,
+				 struct libdecor_frame *frame)
+{
+}
+
+static void
+libdecor_plugin_dummy_frame_commit(struct libdecor_plugin *plugin,
+				   struct libdecor_frame *frame,
+				   struct libdecor_state *state,
+				   struct libdecor_configuration *configuration)
+{
+}
+
+static void
+libdecor_plugin_dummy_frame_property_changed(struct libdecor_plugin *plugin,
+					     struct libdecor_frame *frame)
+{
+}
+
+static void
+libdecor_plugin_dummy_frame_translate_coordinate(struct libdecor_plugin *plugin,
+						 struct libdecor_frame *frame,
+						 int content_x,
+						 int content_y,
+						 int *frame_x,
+						 int *frame_y)
+{
+	*frame_x = content_x;
+	*frame_y = content_y;
+}
+
+static void
+libdecor_plugin_dummy_frame_popup_grab(struct libdecor_plugin *plugin,
+				       struct libdecor_frame *frame,
+				       const char *seat_name)
+{
+}
+
+static void
+libdecor_plugin_dummy_frame_popup_ungrab(struct libdecor_plugin *plugin,
+					 struct libdecor_frame *frame,
+					 const char *seat_name)
+{
+}
+
+static bool
+libdecor_plugin_dummy_configuration_get_content_size(
+		struct libdecor_plugin *plugin,
+		struct libdecor_configuration *configuration,
+		struct libdecor_frame *frame,
+		int *content_width,
+		int *content_height)
+{
+	return libdecor_configuration_get_window_size(configuration,
+						      content_width,
+						      content_height);
+}
+
+static bool
+libdecor_plugin_dummy_frame_get_window_size_for(
+		struct libdecor_plugin *plugin,
+		struct libdecor_frame *frame,
+		struct libdecor_state *state,
+		int *window_width,
+		int *window_height)
+{
+	*window_width = libdecor_state_get_content_width (state);
+	*window_height = libdecor_state_get_content_height (state);
+	return true;
+}
+
+static struct libdecor_plugin_interface dummy_plugin_iface = {
+	.destroy = libdecor_plugin_dummy_destroy,
+
+	.frame_new = libdecor_plugin_dummy_frame_new,
+	.frame_free = libdecor_plugin_dummy_frame_free,
+	.frame_commit = libdecor_plugin_dummy_frame_commit,
+	.frame_property_changed = libdecor_plugin_dummy_frame_property_changed,
+	.frame_translate_coordinate =
+		libdecor_plugin_dummy_frame_translate_coordinate,
+	.frame_popup_grab = libdecor_plugin_dummy_frame_popup_grab,
+	.frame_popup_ungrab = libdecor_plugin_dummy_frame_popup_ungrab,
+
+	.configuration_get_content_size =
+			libdecor_plugin_dummy_configuration_get_content_size,
+	.frame_get_window_size_for =
+			libdecor_plugin_dummy_frame_get_window_size_for,
+};
+
+static struct libdecor_plugin *
+libdecor_plugin_new(struct libdecor *context)
+{
+	struct libdecor_plugin_dummy *plugin_dummy;
+
+	plugin_dummy = zalloc(sizeof *plugin_dummy);
+	plugin_dummy->plugin.iface = &dummy_plugin_iface;
+	plugin_dummy->context = context;
+
+	libdecor_notify_plugin_ready(context);
+
+	return &plugin_dummy->plugin;
+}
+
+static struct libdecor_plugin_priority priorities[] = {
+	{ NULL, LIBDECOR_PLUGIN_PRIORITY_LOW }
+};
+
+LIBDECOR_EXPORT const struct libdecor_plugin_description
+libdecor_plugin_description = {
+	.api_version = LIBDECOR_PLUGIN_API_VERSION,
+	.description = "dummy libdecor plugin",
+	.priorities = priorities,
+	.constructor = libdecor_plugin_new,
+};
diff --git libdecor/src/utils.h libdecor/src/utils.h
new file mode 100644
index 0000000..7892793
--- /dev/null
+++ libdecor/src/utils.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright © 2017 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 (including the
+ * next paragraph) 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 UTILS_H
+#define UTILS_H
+
+#include <stdlib.h>
+
+#define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#define MAX(a, b) (((a) > (b)) ? (a) : (b))
+
+#ifndef ARRAY_LENGTH
+#define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0])
+#endif
+
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+#endif
+
+static inline void *
+zalloc(size_t size)
+{
+	return calloc(1, size);
+}
+
+#endif /* UTILS_H */
diff --git src/Makefile src/Makefile
old mode 100644
new mode 100755
index 1415be1..23f6ceb
--- src/Makefile
+++ src/Makefile
@@ -203,6 +203,8 @@ GLCPPFILES_X11 = drivers/X11/Fl_X11_Gl_Window_Driver.cxx
 GLCPPFILES_XFT = $(GLCPPFILES_X11)
 GLCPPFILES_WIN = drivers/WinAPI/Fl_WinAPI_Gl_Window_Driver.cxx
 
+GLCPPFILES_WAYLAND = drivers/Wayland/Fl_Wayland_Gl_Window_Driver.cxx
+
 GLCPPFILES += $(GLCPPFILES_$(BUILD))
 
 #	the following file currently doesn't contribute code to GLCPPFILES
@@ -266,6 +268,24 @@ XLIBCPPFILES = \
         Fl_Native_File_Chooser_FLTK.cxx \
         Fl_Native_File_Chooser_GTK.cxx \
         Fl_get_key.cxx
+        
+# These C++ files are used under condition: BUILD_WAYLAND
+WLCPPFILES = \
+	drivers/Posix/Fl_Posix_System_Driver.cxx \
+	drivers/Posix/Fl_Posix_Printer_Driver.cxx \
+        Fl_Native_File_Chooser_FLTK.cxx \
+        Fl_Native_File_Chooser_GTK.cxx \
+	drivers/Wayland/Fl_get_key_wayland.cxx \
+        drivers/Wayland/Fl_Wayland_System_Driver.cxx \
+	drivers/Wayland/Fl_Wayland_Screen_Driver.cxx \
+ drivers/Wayland/Fl_Wayland_Window_Driver.cxx \
+ drivers/Wayland/Fl_Wayland_Image_Surface_Driver.cxx \
+   drivers/Wayland/Fl_Wayland_Copy_Surface_Driver.cxx \
+   drivers/Wayland/Fl_Wayland_Graphics_Driver.cxx \
+      drivers/Wayland/Fl_wayland.cxx
+
+      
+#   fl_dnd_x.cxx  Fl_Native_File_Chooser_GTK.cxx
 
 # This C file is used under condition: BUILD_X11
 XLIBCFILES = \
@@ -285,6 +305,15 @@ XLIBFONTFILES = \
 XLIBXFTFILES = \
 	drivers/Xlib/Fl_Xlib_Graphics_Driver_font_xft.cxx \
 	drivers/Cairo/Fl_Cairo_Graphics_Driver.cxx
+	
+# This C file is used under condition: BUILD_WAYLAND
+WLCFILES = \
+        xutf8/keysym2Ucs.c \
+        scandir_posix.c ../libdecor/build/xdg-shell-protocol.c
+
+# These C++ files are used under condition: BUILD_WAYLAND
+WLXFTFILES = \
+	drivers/Cairo/Fl_Cairo_Graphics_Driver.cxx
 
 # These C++ files are used under condition: BUILD_GDI
 GDICPPFILES = \
@@ -331,6 +360,8 @@ CPPFILES_OSX = $(QUARTZCPPFILES)
 CPPFILES_XFT = $(XLIBCPPFILES) $(XLIBXFTFILES)
 CPPFILES_X11 = $(XLIBCPPFILES) $(XLIBFONTFILES)
 
+CPPFILES_WAYLAND = $(WLCPPFILES) $(WLXFTFILES)
+
 CPPFILES_WIN = $(GDICPPFILES)
 
 CPPFILES += $(CPPFILES_$(BUILD))
@@ -339,6 +370,8 @@ CPPFILES += $(CPPFILES_$(BUILD))
 CFILES_X11 = $(XLIBCFILES) $(XLIBXCFILES)
 CFILES_XFT = $(XLIBCFILES)
 
+CFILES_WAYLAND = $(WLCFILES)
+
 CFILES_WIN = $(GDICFILES)
 
 CFILES += $(CFILES_$(BUILD))
@@ -614,6 +647,7 @@ clean:
 	-$(RM)	drivers/WinAPI/*.o
 	-$(RM)	drivers/X11/*.o
 	-$(RM)	drivers/Xlib/*.o
+	-$(RM)	drivers/Wayland/*.o
 	-$(RM)	$(DSONAME) $(FLDSONAME) $(GLDSONAME) $(IMGDSONAME) \
 		$(LIBNAME) $(FLLIBNAME) $(GLLIBNAME) \
 		$(IMGLIBNAME) \
diff --git src/drivers/Wayland/Fl_Font.H src/drivers/Wayland/Fl_Font.H
new file mode 100644
index 0000000..eca2f32
--- /dev/null
+++ src/drivers/Wayland/Fl_Font.H
@@ -0,0 +1,33 @@
+//
+// Font definitions for the Fast Light Tool Kit (FLTK).
+//
+// Copyright 2021 by Bill Spitzak and others.
+//
+// This library is free software. Distribution and use rights are outlined in
+// the file "COPYING" which should have been included with this file.  If this
+// file is missing or damaged, see the license at:
+//
+//     https://www.fltk.org/COPYING.php
+//
+// Please see the following page on how to report bugs and issues:
+//
+//     https://www.fltk.org/bugs.php
+//
+
+#ifndef FL_FONT_
+#define FL_FONT_
+
+#include <config.h>
+#include "Fl_Wayland_Graphics_Driver.H"
+
+class Fl_Wayland_Font_Descriptor : public Fl_Font_Descriptor {
+public:
+  Fl_Wayland_Font_Descriptor(const char* fontname, Fl_Fontsize size);
+  FL_EXPORT ~Fl_Wayland_Font_Descriptor();
+  PangoFontDescription *fontref;
+  int **width; // array of arrays of character widths
+};
+
+extern FL_EXPORT Fl_Fontdesc *fl_fonts; // the table
+
+#endif // FL_FONT_
diff --git src/drivers/Wayland/Fl_Wayland_Copy_Surface_Driver.cxx src/drivers/Wayland/Fl_Wayland_Copy_Surface_Driver.cxx
new file mode 100644
index 0000000..04d3b2d
--- /dev/null
+++ src/drivers/Wayland/Fl_Wayland_Copy_Surface_Driver.cxx
@@ -0,0 +1,72 @@
+//
+// Copy-to-clipboard code for the Fast Light Tool Kit (FLTK).
+//
+// Copyright 1998-2021 by Bill Spitzak and others.
+//
+// This library is free software. Distribution and use rights are outlined in
+// the file "COPYING" which should have been included with this file.  If this
+// file is missing or damaged, see the license at:
+//
+//     https://www.fltk.org/COPYING.php
+//
+// Please see the following page on how to report bugs and issues:
+//
+//     https://www.fltk.org/bugs.php
+//
+
+#include <config.h>
+#include <FL/Fl_Copy_Surface.H>
+#include <FL/Fl_Image_Surface.H>
+#include "Fl_Wayland_Graphics_Driver.H"
+#include "Fl_Wayland_Screen_Driver.H"
+#include "Fl_Wayland_Window_Driver.H"
+#include <FL/platform.H>
+
+class Fl_Wayland_Copy_Surface_Driver : public Fl_Copy_Surface_Driver {
+  friend class Fl_Copy_Surface_Driver;
+  Fl_Image_Surface *img_surf;
+protected:
+  Fl_Wayland_Copy_Surface_Driver(int w, int h);
+  ~Fl_Wayland_Copy_Surface_Driver();
+  void set_current();
+  void translate(int x, int y);
+  void untranslate();
+};
+
+
+Fl_Copy_Surface_Driver *Fl_Copy_Surface_Driver::newCopySurfaceDriver(int w, int h)
+{
+  return new Fl_Wayland_Copy_Surface_Driver(w, h);
+}
+
+
+Fl_Wayland_Copy_Surface_Driver::Fl_Wayland_Copy_Surface_Driver(int w, int h) : Fl_Copy_Surface_Driver(w, h) {
+  img_surf = new Fl_Image_Surface(w, h);
+  driver(img_surf->driver());
+}
+
+
+Fl_Wayland_Copy_Surface_Driver::~Fl_Wayland_Copy_Surface_Driver() {
+  Fl_RGB_Image *rgb = img_surf->image();
+  Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver();
+  scr_driver->copy_image(rgb->array, rgb->data_w(), rgb->data_h());
+  delete rgb;
+  delete img_surf;
+  driver(NULL);
+}
+
+
+void Fl_Wayland_Copy_Surface_Driver::set_current() {
+  Fl_Surface_Device::set_current();
+  ((Fl_Wayland_Graphics_Driver*)driver())->activate(img_surf->offscreen(), fl_window?fl_window->scale:1);
+}
+
+
+void Fl_Wayland_Copy_Surface_Driver::translate(int x, int y) {
+  ((Fl_Wayland_Graphics_Driver*)driver())->ps_translate(x, y);
+}
+
+
+void Fl_Wayland_Copy_Surface_Driver::untranslate() {
+  ((Fl_Wayland_Graphics_Driver*)driver())->ps_untranslate();
+}
diff --git src/drivers/Wayland/Fl_Wayland_Gl_Window_Driver.cxx src/drivers/Wayland/Fl_Wayland_Gl_Window_Driver.cxx
new file mode 100644
index 0000000..3c5c693
--- /dev/null
+++ src/drivers/Wayland/Fl_Wayland_Gl_Window_Driver.cxx
@@ -0,0 +1,559 @@
+//
+// Class Fl_Wayland_Gl_Window_Driver for the Fast Light Tool Kit (FLTK).
+//
+// Copyright 2021 by Bill Spitzak and others.
+//
+// This library is free software. Distribution and use rights are outlined in
+// the file "COPYING" which should have been included with this file.  If this
+// file is missing or damaged, see the license at:
+//
+//     https://www.fltk.org/COPYING.php
+//
+// Please see the following page on how to report bugs and issues:
+//
+//     https://www.fltk.org/bugs.php
+//
+
+#include <config.h>
+#if HAVE_GL
+#include <FL/platform.H>
+#include <FL/Fl_Image_Surface.H>
+#include "../../Fl_Gl_Choice.H"
+#include "../../Fl_Screen_Driver.H"
+#include "Fl_Wayland_Window_Driver.H"
+#include "Fl_Wayland_Graphics_Driver.H"
+#include "../../Fl_Gl_Window_Driver.H"
+#include <wayland-egl.h>
+#include <EGL/egl.h>
+#include <FL/gl.h>
+
+
+class Fl_Wayland_Gl_Window_Driver : public Fl_Gl_Window_Driver {
+  friend class Fl_Gl_Window_Driver;
+protected:
+  Fl_Wayland_Gl_Window_Driver(Fl_Gl_Window *win);
+  virtual float pixels_per_unit();
+  virtual void make_current_before();
+  virtual int mode_(int m, const int *a);
+  virtual void swap_buffers();
+  virtual void resize(int is_a_resize, int w, int h);
+  virtual char swap_type();
+  virtual Fl_Gl_Choice *find(int m, const int *alistp);
+  virtual GLContext create_gl_context(Fl_Window* window, const Fl_Gl_Choice* g, int layer = 0);
+  virtual void set_gl_context(Fl_Window* w, GLContext context);
+  virtual void delete_gl_context(GLContext);
+  virtual void make_overlay_current();
+  virtual void redraw_overlay();
+  virtual void waitGL();
+  virtual void gl_visual(Fl_Gl_Choice*); // support for Fl::gl_visual()
+  virtual void gl_start();
+  virtual Fl_RGB_Image* capture_gl_rectangle(int x, int y, int w, int h);
+  char *alpha_mask_for_string(const char *str, int n, int w, int h, Fl_Fontsize fs);
+  virtual int flush_begin(char& valid_f_);
+public:
+  //static GLContext create_gl_context(XVisualInfo* vis);
+  static EGLDisplay egl_display;
+  static EGLConfig egl_conf;
+  void init();
+  struct wl_egl_window *egl_window;
+  EGLSurface egl_surface;
+};
+
+// Describes crap needed to create a GLContext.
+class Fl_Wayland_Gl_Choice : public Fl_Gl_Choice {
+  friend class Fl_Wayland_Gl_Window_Driver;
+private:
+  /*XVisualInfo *vis;
+  Colormap colormap;
+  GLXFBConfig best_fb;*/
+public:
+  Fl_Wayland_Gl_Choice(int m, const int *alistp, Fl_Gl_Choice *n) : Fl_Gl_Choice(m, alistp, n) {
+    /*vis = NULL;
+    colormap = 0;
+    best_fb = NULL;*/
+  }
+};
+
+EGLDisplay Fl_Wayland_Gl_Window_Driver::egl_display = EGL_NO_DISPLAY;
+EGLConfig Fl_Wayland_Gl_Window_Driver::egl_conf = 0;
+
+Fl_Wayland_Gl_Window_Driver::Fl_Wayland_Gl_Window_Driver(Fl_Gl_Window *win) : Fl_Gl_Window_Driver(win) {
+  if (egl_display == EGL_NO_DISPLAY) init();
+  egl_window = NULL;
+  egl_surface = NULL;
+}
+
+
+void Fl_Wayland_Gl_Window_Driver::init() {
+  EGLint major, minor, count, n, size;
+  EGLConfig *configs;
+  int i;
+  EGLint config_attribs[] = {
+    EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+    EGL_RED_SIZE, 8,
+    EGL_GREEN_SIZE, 8,
+    EGL_BLUE_SIZE, 8,
+    EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
+    EGL_NONE
+  };
+  
+  /*static const EGLint context_attribs[] = {
+    EGL_CONTEXT_MAJOR_VERSION, 2,
+    EGL_CONTEXT_OPENGL_PROFILE_MASK,  EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT,//for OpenGL 1 et 2
+    EGL_NONE
+  };*/
+  
+  if (!fl_display) Fl::screen_driver()->open_display();
+  egl_display = eglGetDisplay((EGLNativeDisplayType) fl_display);
+  if (egl_display == EGL_NO_DISPLAY) {
+    fprintf(stderr, "Can't create egl display\n");
+    exit(1);
+  } else {
+    //fprintf(stderr, "Created egl display\n");
+  }
+  
+  if (eglInitialize(egl_display, &major, &minor) != EGL_TRUE) {
+    fprintf(stderr, "Can't initialise egl display\n");
+    exit(1);
+  }
+  printf("EGL major: %d, minor %d\n", major, minor);
+  
+  eglGetConfigs(egl_display, NULL, 0, &count);
+  printf("EGL has %d configs\n", count);
+  
+  configs = (void**)calloc(count, sizeof *configs);
+  
+  eglChooseConfig(egl_display, config_attribs,
+                  configs, count, &n);
+  if (n == 0) {
+    fprintf(stderr, "failed to choose an EGL config\n");
+    exit(1);
+  }
+  eglBindAPI(EGL_OPENGL_API); //necessary
+  for (i = 0; i < n; i++) {
+    eglGetConfigAttrib(egl_display,
+                       configs[i], EGL_BUFFER_SIZE, &size);
+    printf("Buffer size for config %d is %d\n", i, size);
+    eglGetConfigAttrib(egl_display,
+                       configs[i], EGL_RED_SIZE, &size);
+    printf("Red size for config %d is %d\n", i, size);
+    
+    // just choose the first one
+    egl_conf = configs[i];
+    break;
+  }
+}
+
+
+char *Fl_Wayland_Gl_Window_Driver::alpha_mask_for_string(const char *str, int n, int w, int h, Fl_Fontsize fs)
+{
+  // write str to a bitmap just big enough
+  Window save_win = fl_window;
+  fl_window = NULL;
+  Fl_Image_Surface *surf = new Fl_Image_Surface(w, h);
+  fl_window = save_win;
+  Fl_Font f=fl_font();
+  Fl_Surface_Device::push_current(surf);
+  fl_color(FL_BLACK);
+  fl_rectf(0, 0, w, h);
+  fl_color(FL_WHITE);
+  fl_font(f, fs);
+  fl_draw(str, n, 0, fl_height() - fl_descent());
+  // get the R channel only of the bitmap
+  char *alpha_buf = new char[w*h], *r = alpha_buf, *q;
+  for (int i = 0; i < h; i++) {
+    q = (char*)surf->offscreen()->draw_buffer + i * surf->offscreen()->stride;
+    for (int j = 0; j < w; j++) {
+      *r++ = *q;
+      q += 4;
+    }
+  }
+  Fl_Surface_Device::pop_current();
+  delete surf;
+  return alpha_buf;
+}
+
+
+/*static XVisualInfo *gl3_getvisual(const int *blist, GLXFBConfig *pbestFB)
+{
+  int glx_major, glx_minor;
+
+  // FBConfigs were added in GLX version 1.3.
+  if ( !glXQueryVersion(fl_display, &glx_major, &glx_minor) ||
+      ( ( glx_major == 1 ) && ( glx_minor < 3 ) ) || ( glx_major < 1 ) ) {
+    return NULL;
+  }
+
+  //printf( "Getting matching framebuffer configs\n" );
+  int fbcount;
+  GLXFBConfig* fbc = glXChooseFBConfig(fl_display, DefaultScreen(fl_display), blist, &fbcount);
+  if (!fbc) {
+    //printf( "Failed to retrieve a framebuffer config\n" );
+    return NULL;
+  }
+  //printf( "Found %d matching FB configs.\n", fbcount );
+
+  // Pick the FB config/visual with the most samples per pixel
+  int best_fbc = -1, worst_fbc = -1, best_num_samp = -1, worst_num_samp = 999;
+  for (int i = 0; i < fbcount; ++i)
+  {
+    XVisualInfo *vi = glXGetVisualFromFBConfig( fl_display, fbc[i] );
+    if (vi) {
+      int samp_buf, samples;
+      glXGetFBConfigAttrib(fl_display, fbc[i], GLX_SAMPLE_BUFFERS, &samp_buf);
+      glXGetFBConfigAttrib(fl_display, fbc[i], GLX_SAMPLES       , &samples );
+      if ( best_fbc < 0 || (samp_buf && samples > best_num_samp) )
+        best_fbc = i, best_num_samp = samples;
+      if ( worst_fbc < 0 || !samp_buf || samples < worst_num_samp )
+        worst_fbc = i, worst_num_samp = samples;
+    }
+    XFree(vi);
+  }
+
+  GLXFBConfig bestFbc = fbc[ best_fbc ];
+  // Be sure to free the FBConfig list allocated by glXChooseFBConfig()
+  XFree(fbc);
+  // Get a visual
+  XVisualInfo *vi = glXGetVisualFromFBConfig(fl_display, bestFbc);
+  *pbestFB = bestFbc;
+  return vi;
+}*/
+
+Fl_Gl_Choice *Fl_Wayland_Gl_Window_Driver::find(int m, const int *alistp)
+{
+  Fl_Wayland_Gl_Choice *g = (Fl_Wayland_Gl_Choice*)Fl_Gl_Window_Driver::find_begin(m, alistp);
+  if (g) return g;
+
+/*  const int *blist;
+  int list[32];
+
+  if (alistp)
+    blist = alistp;
+  else {
+    int n = 0;
+    if (m & FL_INDEX) {
+      list[n++] = GLX_BUFFER_SIZE;
+      list[n++] = 8; // glut tries many sizes, but this should work...
+    } else {
+      list[n++] = GLX_RGBA;
+      list[n++] = GLX_GREEN_SIZE;
+      list[n++] = (m & FL_RGB8) ? 8 : 1;
+      if (m & FL_ALPHA) {
+        list[n++] = GLX_ALPHA_SIZE;
+        list[n++] = (m & FL_RGB8) ? 8 : 1;
+      }
+      if (m & FL_ACCUM) {
+        list[n++] = GLX_ACCUM_GREEN_SIZE;
+        list[n++] = 1;
+        if (m & FL_ALPHA) {
+          list[n++] = GLX_ACCUM_ALPHA_SIZE;
+          list[n++] = 1;
+        }
+      }
+    }
+    if (m & FL_DOUBLE) {
+      list[n++] = GLX_DOUBLEBUFFER;
+    }
+    if (m & FL_DEPTH) {
+      list[n++] = GLX_DEPTH_SIZE; list[n++] = 1;
+    }
+    if (m & FL_STENCIL) {
+      list[n++] = GLX_STENCIL_SIZE; list[n++] = 1;
+    }
+    if (m & FL_STEREO) {
+      list[n++] = GLX_STEREO;
+    }
+#    if defined(GLX_VERSION_1_1) && defined(GLX_SGIS_multisample)
+    if (m & FL_MULTISAMPLE) {
+      list[n++] = GLX_SAMPLES_SGIS;
+      list[n++] = 4; // value Glut uses
+    }
+#    endif
+    list[n] = 0;
+    blist = list;
+  }
+
+  fl_open_display();
+  XVisualInfo *visp = NULL;
+  GLXFBConfig best_fb = NULL;
+  if (m & FL_OPENGL3) {
+    visp = gl3_getvisual((const int *)blist, &best_fb);
+  }
+  if (!visp) {
+    visp = glXChooseVisual(fl_display, fl_screen, (int *)blist);
+    if (!visp) {
+#     if defined(GLX_VERSION_1_1) && defined(GLX_SGIS_multisample)
+        if (m&FL_MULTISAMPLE) return find(m&~FL_MULTISAMPLE, 0);
+#     endif
+      return 0;
+    }
+  }*/
+
+  g = new Fl_Wayland_Gl_Choice(m, alistp, first);
+  first = g;
+
+/*  g->vis = visp;
+  g->best_fb = best_fb;
+
+  if (visp->visualid == fl_visual->visualid &&
+      !fl_getenv("MESA_PRIVATE_CMAP"))
+    g->colormap = fl_colormap;
+  else
+    g->colormap = XCreateColormap(fl_display, RootWindow(fl_display,fl_screen),
+                                  visp->visual, AllocNone);*/
+  return g;
+}
+
+
+GLContext Fl_Wayland_Gl_Window_Driver::create_gl_context(Fl_Window* window, const Fl_Gl_Choice* g, int layer) {
+  GLContext shared_ctx = 0;
+  if (context_list && nContext) shared_ctx = context_list[0];
+
+  static const EGLint context_attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
+  GLContext ctx = (GLContext)eglCreateContext(egl_display, egl_conf, shared_ctx?shared_ctx:EGL_NO_CONTEXT, context_attribs);
+//fprintf(stderr, "eglCreateContext=%p shared_ctx=%p\n", ctx, shared_ctx);
+  if (ctx)
+    add_context(ctx);
+  return ctx;
+}
+
+/*GLContext Fl_Wayland_Gl_Window_Driver::create_gl_context(XVisualInfo *vis) {
+  GLContext shared_ctx = 0;
+  if (context_list && nContext) shared_ctx = context_list[0];
+  GLContext context = glXCreateContext(fl_display, vis, shared_ctx, 1);
+  if (context)
+    add_context(context);
+  return context;
+}*/
+
+
+void Fl_Wayland_Gl_Window_Driver::set_gl_context(Fl_Window* w, GLContext context) {
+  struct wld_window *win = fl_xid(w);
+  if (!win || !egl_surface) return;
+  if (context != cached_context || w != cached_window) {
+    cached_context = context;
+    cached_window = w;
+    if (eglMakeCurrent(egl_display, egl_surface, egl_surface, (EGLContext)context)) {
+//fprintf(stderr, "EGLContext %p made current\n", context);
+    } else {
+      fprintf(stderr, "Made current failed\n");
+    }
+  }
+}
+
+void Fl_Wayland_Gl_Window_Driver::delete_gl_context(GLContext context) {
+  if (cached_context == context) {
+    cached_context = 0;
+    cached_window = 0;
+  }
+  EGLBoolean b = eglDestroyContext(egl_display, context);
+fprintf(stderr,"EGL context %p destroyed %s\n", context, b==EGL_TRUE?"successfully":"w/ error");
+  b = eglDestroySurface(egl_display, egl_surface);
+fprintf(stderr,"EGLSurface %p destroyed %s\n", egl_surface, b==EGL_TRUE?"successfully":"w/ error");
+  egl_surface = NULL;
+  wl_egl_window_destroy(egl_window);
+  egl_window = NULL;
+  del_context(context);
+}
+
+
+void Fl_Wayland_Gl_Window_Driver::make_overlay_current() {
+//fprintf(stderr, "make_overlay_current\n");
+  glDrawBuffer(GL_FRONT);
+}
+
+void Fl_Wayland_Gl_Window_Driver::redraw_overlay() {
+//fprintf(stderr, "redraw_overlay\n");
+  pWindow->redraw();
+}
+
+
+Fl_Gl_Window_Driver *Fl_Gl_Window_Driver::newGlWindowDriver(Fl_Gl_Window *w)
+{
+  return new Fl_Wayland_Gl_Window_Driver(w);
+}
+
+
+void Fl_Wayland_Gl_Window_Driver::make_current_before() {
+  if (!egl_window) {
+    struct wld_window *win = fl_xid(pWindow);
+    struct wl_surface *surface = pWindow->parent() ? win->wl_surface : win->gl_wl_surface;
+    egl_window = wl_egl_window_create(surface, pWindow->pixel_w(), pWindow->pixel_h());
+    if (egl_window == EGL_NO_SURFACE) {
+      fprintf(stderr, "Can't create egl window\n");
+      exit(1);
+    } else {
+      //fprintf(stderr, "Created egl window=%p\n", egl_window);
+    }
+    egl_surface = eglCreateWindowSurface(egl_display, egl_conf, egl_window, NULL);
+//fprintf(stderr, "Created egl surface=%p at scale=%d\n", egl_surface, win->scale);
+    wl_surface_set_buffer_scale(surface, win->scale);
+    wl_surface_commit(surface);
+    wl_display_roundtrip(fl_display);
+  }
+}
+
+float Fl_Wayland_Gl_Window_Driver::pixels_per_unit()
+{
+  int ns = Fl_Window_Driver::driver(pWindow)->screen_num();
+  int wld_scale = pWindow->shown() ? fl_xid(pWindow)->scale : 1;
+  return wld_scale * Fl::screen_driver()->scale(ns);
+}
+
+
+int Fl_Wayland_Gl_Window_Driver::mode_(int m, const int *a) {
+  /*int oldmode = mode();
+  if (a) { // when the mode is set using the a array of system-dependent values, and if asking for double buffer,
+    // the FL_DOUBLE flag must be set in the mode_ member variable
+    const int *aa = a;
+    while (*aa) {
+      if (*(aa++) ==
+          GLX_DOUBLEBUFFER
+          ) { m |= FL_DOUBLE; break; }
+    }
+  }
+  Fl_Wayland_Gl_Choice* oldg = (Fl_Wayland_Gl_Choice*)g();
+  pWindow->context(0);
+  mode(m); alist(a);
+  if (pWindow->shown()) {
+    g( find(m, a) );
+    // under X, if the visual changes we must make a new X window (yuck!):
+    Fl_Wayland_Gl_Choice* g = (Fl_Wayland_Gl_Choice*)this->g();
+    if (!g || g->vis->visualid != oldg->vis->visualid || (oldmode^m)&FL_DOUBLE) {
+      pWindow->hide();
+      pWindow->show();
+    }
+  } else {
+    g(0);
+  }*/
+  return 1;
+}
+
+
+static void surface_frame_done(void *data, struct wl_callback *cb, uint32_t time) {
+  wl_callback_destroy(cb);
+  *(bool*)data = true;
+}
+
+static const struct wl_callback_listener surface_frame_listener = {
+  .done = surface_frame_done,
+};
+
+
+void Fl_Wayland_Gl_Window_Driver::swap_buffers() {
+  if (overlay()) {
+    static bool overlay_buffer = true;
+    int wo = pWindow->pixel_w(), ho = pWindow->pixel_h();
+    GLint matrixmode;
+    GLfloat pos[4];
+    glGetIntegerv(GL_MATRIX_MODE, &matrixmode);
+    glGetFloatv(GL_CURRENT_RASTER_POSITION, pos);       // save original glRasterPos
+    glMatrixMode(GL_PROJECTION);                        // save proj/model matrices
+    glPushMatrix();
+    glLoadIdentity();
+    glMatrixMode(GL_MODELVIEW);
+    glPushMatrix();
+    glLoadIdentity();
+    glScalef(2.0f/wo, 2.0f/ho, 1.0f);
+    glTranslatef(-wo/2.0f, -ho/2.0f, 0.0f);         // set transform so 0,0 is bottom/left of Gl_Window
+    glRasterPos2i(0,0);                             // set glRasterPos to bottom left corner
+    {
+      // Emulate overlay by doing copypixels
+      glReadBuffer(overlay_buffer?GL_BACK:GL_FRONT);
+      glDrawBuffer(overlay_buffer?GL_FRONT:GL_BACK);
+      overlay_buffer = ! overlay_buffer;
+      glCopyPixels(0, 0, wo, ho, GL_COLOR);
+    }
+    glPopMatrix(); // GL_MODELVIEW                  // restore model/proj matrices
+    glMatrixMode(GL_PROJECTION);
+    glPopMatrix();
+    glMatrixMode(matrixmode);
+    glRasterPos3f(pos[0], pos[1], pos[2]);              // restore original glRasterPos
+    if (!overlay_buffer) return; // don't call eglSwapBuffers until overlay has been drawn
+  }
+  if (egl_surface) {
+    // Make eglSwapBuffers non-blocking, we manage frame callbacks manually
+    eglSwapInterval(egl_display, 0);
+    // Register a frame callback to know when we can draw the next frame
+    Window xid = fl_xid(pWindow);
+    struct wl_surface *surf = xid->gl_wl_surface ? xid->gl_wl_surface : xid->wl_surface;
+    struct wl_callback *callback = wl_surface_frame(surf);
+    wl_surface_commit(surf);
+    bool done = false;
+    wl_callback_add_listener(callback, &surface_frame_listener, &done);
+    while (!done) wl_display_dispatch(fl_display); // wait for arrival of frame event
+    if (eglSwapBuffers(egl_display, egl_surface)) {
+      //fprintf(stderr, "Swapped buffers for surface=%p display=%p\n", egl_surface, egl_display);
+      cached_context = 0;
+    } else {
+      fprintf(stderr, "Swapped buffers failed\n");
+    }
+  }
+}
+
+
+class Fl_Gl_Overlay_Plugin : public Fl_Wayland_Plugin {
+public:
+  Fl_Gl_Overlay_Plugin() : Fl_Wayland_Plugin(name()) { }
+  virtual const char *name() { return "gl_overlay.wayland.fltk.org"; }
+  virtual void do_swap(Fl_Window *w) {
+    Fl_Gl_Window_Driver *gldr = Fl_Gl_Window_Driver::driver(w->as_gl_window());
+    if (gldr->overlay() == w) gldr->swap_buffers();
+  }
+};
+
+static Fl_Gl_Overlay_Plugin Gl_Overlay_Plugin;
+
+
+void Fl_Wayland_Gl_Window_Driver::resize(int is_a_resize, int W, int H) {
+  if (egl_window) {
+    struct wld_window *win = fl_xid(pWindow);
+    int wld_scale = win->scale;
+    wl_egl_window_resize(egl_window, W * wld_scale, H * wld_scale, 0, 0);
+//fprintf(stderr, "Fl_Wayland_Gl_Window_Driver::resize to %dx%d\n", W * wld_scale, H * wld_scale);
+  }
+}
+
+char Fl_Wayland_Gl_Window_Driver::swap_type() {
+  return copy;
+}
+
+void Fl_Wayland_Gl_Window_Driver::waitGL() {
+  //glXWaitGL();
+}
+
+
+void Fl_Wayland_Gl_Window_Driver::gl_visual(Fl_Gl_Choice *c) {
+  /*Fl_Gl_Window_Driver::gl_visual(c);
+  fl_visual = ((Fl_Wayland_Gl_Choice*)c)->vis;
+  fl_colormap = ((Fl_Wayland_Gl_Choice*)c)->colormap;*/
+}
+
+void Fl_Wayland_Gl_Window_Driver::gl_start() {
+  //glXWaitX();
+}
+
+
+Fl_RGB_Image* Fl_Wayland_Gl_Window_Driver::capture_gl_rectangle(int x, int y, int w, int h) {
+  Fl_Surface_Device::push_current(Fl_Display_Device::display_device());
+  Fl_RGB_Image *rgb = Fl_Gl_Window_Driver::capture_gl_rectangle(x, y, w, h);
+  Fl_Surface_Device::pop_current();
+  return rgb;
+}
+
+
+int Fl_Wayland_Gl_Window_Driver::flush_begin(char& valid_f_) {
+  if (!pWindow->parent()) {
+    Window window = fl_xid(pWindow);
+    if (!window->buffer) { // a fresh top-level GL window: give it its buffer
+      window->buffer = Fl_Wayland_Graphics_Driver::create_shm_buffer(
+                    pWindow->w() * window->scale, pWindow->h() * window->scale,
+                    WL_SHM_FORMAT_ARGB8888, window);
+      wl_surface_damage_buffer(window->wl_surface, 0, 0, pWindow->w() * window->scale, pWindow->h() * window->scale);
+      Fl_Wayland_Graphics_Driver::buffer_commit(window);
+    }
+  }
+  return 0;
+}
+
+#endif // HAVE_GL
diff --git src/drivers/Wayland/Fl_Wayland_Graphics_Driver.H src/drivers/Wayland/Fl_Wayland_Graphics_Driver.H
new file mode 100644
index 0000000..2627600
--- /dev/null
+++ src/drivers/Wayland/Fl_Wayland_Graphics_Driver.H
@@ -0,0 +1,89 @@
+//
+// Definition of class Fl_Wayland_Graphics_Driver.
+//
+// Copyright 1998-2021 by Bill Spitzak and others.
+//
+// This library is free software. Distribution and use rights are outlined in
+// the file "COPYING" which should have been included with this file.  If this
+// file is missing or damaged, see the license at:
+//
+//     https://www.fltk.org/COPYING.php
+//
+// Please see the following page on how to report bugs and issues:
+//
+//     https://www.fltk.org/bugs.php
+//
+
+/**
+ \file Fl_Wayland_Graphics_Driver.H
+ \brief Definition of Wayland graphics driver.
+ */
+
+#ifndef FL_WAYLAND_GRAPHICS_DRIVER_H
+#define FL_WAYLAND_GRAPHICS_DRIVER_H
+
+#include "../Cairo/Fl_Cairo_Graphics_Driver.H"
+#include <cairo/cairo.h>
+#include <wayland-client.h>
+typedef struct _PangoLayout  PangoLayout;
+
+struct buffer {
+  struct wl_buffer *wl_buffer;
+  void *data;
+  size_t data_size; // of wl_buffer and draw_buffer
+  int stride;
+  int width;
+  unsigned char *draw_buffer;
+  bool wl_buffer_ready;
+  bool draw_buffer_needs_commit;
+  cairo_surface_t *cairo_surface_;
+  cairo_t *cairo_;
+  PangoLayout *pango_layout_;
+};
+struct wld_window;
+
+class Fl_Wayland_Graphics_Driver : public Fl_Cairo_Graphics_Driver {
+private:
+  cairo_surface_t *cairo_surface_;
+  PangoLayout *dummy_pango_layout_; // used to measure text width before showing a window
+public:
+  Fl_Wayland_Graphics_Driver();
+  ~Fl_Wayland_Graphics_Driver();
+  void activate(struct buffer *buffer, int scale);
+  void font(Fl_Font fnum, Fl_Fontsize s);
+  Fl_Font font() { return Fl_Graphics_Driver::font(); }
+  void draw(const char* s, int nBytes, int x, int y) { draw(s, nBytes, float(x), float(y)); }
+  void draw(const char* s, int nBytes, float x, float y);
+  void draw(int angle, const char *str, int n, int x, int y);
+  int height();
+  int descent();
+  double width(const char *str, int n);
+  double width(unsigned c);
+  void text_extents(const char* txt, int n, int& dx, int& dy, int& w, int& h);
+  int not_clipped(int x, int y, int w, int h);
+  int clip_box(int x, int y, int w, int h, int &X, int &Y, int &W, int &H);
+  void restore_clip();
+  Fl_Region XRectangleRegion(int x, int y, int w, int h);
+  void add_rectangle_to_region(Fl_Region r, int X, int Y, int W, int H);
+  void XDestroyRegion(Fl_Region r);
+  void set_color(Fl_Color i, unsigned c);
+  Fl_Font set_fonts(const char* pattern_name);
+  const char *font_name(int num);
+  void font_name(int num, const char *name);
+  const char* get_font_name(Fl_Font fnum, int* ap);
+  int get_font_sizes(Fl_Font fnum, int*& sizep);
+  void point(int x, int y);
+  void copy_offscreen(int x, int y, int w, int h, Fl_Offscreen osrc, int srcx, int srcy);
+  void draw_image(const uchar *data, int ix, int iy, int iw, int ih, int D, int LD);
+  void curve(double x, double y, double x1, double y1, double x2, double y2, double x3, double y3);
+  void begin_points();
+  void end_points();
+  void transformed_vertex(double x, double y);
+  static void init_built_in_fonts();
+  static struct buffer *create_shm_buffer(int width, int height, uint32_t format, struct wld_window *window);
+  static void buffer_release(struct wld_window *window);
+  static void buffer_commit(struct wld_window *window);
+  static void cairo_init(struct buffer *buffer, int width, int height, int stride);
+};
+
+#endif // FL_WAYLAND_GRAPHICS_DRIVER_H
diff --git src/drivers/Wayland/Fl_Wayland_Graphics_Driver.cxx src/drivers/Wayland/Fl_Wayland_Graphics_Driver.cxx
new file mode 100644
index 0000000..d06b55a
--- /dev/null
+++ src/drivers/Wayland/Fl_Wayland_Graphics_Driver.cxx
@@ -0,0 +1,669 @@
+//
+// Implementation of the Wayland graphics driver.
+//
+// Copyright 2021 by Bill Spitzak and others.
+//
+// This library is free software. Distribution and use rights are outlined in
+// the file "COPYING" which should have been included with this file.  If this
+// file is missing or damaged, see the license at:
+//
+//     https://www.fltk.org/COPYING.php
+//
+// Please see the following page on how to report bugs and issues:
+//
+//     https://www.fltk.org/bugs.php
+//
+
+#include <config.h>
+#include <FL/platform.H>
+#include "Fl_Wayland_Graphics_Driver.H"
+#include "Fl_Wayland_Screen_Driver.H"
+#include "Fl_Wayland_Window_Driver.H"
+#include "Fl_Font.H"
+#include <pango/pangocairo.h>
+#if ! PANGO_VERSION_CHECK(1,22,0)
+#  error "Requires Pango 1.22 or higher"
+#endif
+#define _GNU_SOURCE 1
+#include <sys/mman.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+extern unsigned fl_cmap[256]; // defined in fl_color.cxx
+
+static int create_anonymous_file(off_t size)
+{
+  int ret;
+  int fd = memfd_create("FLTK-for-Wayland", MFD_CLOEXEC | MFD_ALLOW_SEALING);
+  if (fd < 0) return -1;
+  fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK);
+  do {
+    ret = posix_fallocate(fd, 0, size);
+  } while (ret == EINTR);
+  if (ret != 0) {
+    close(fd);
+    errno = ret;
+    return -1;
+  }
+  return fd;
+}
+
+
+static void buffer_gets_ready(void *user_data, struct wl_buffer *unused)
+{
+  struct wld_window *window = (struct wld_window *)user_data;
+  window->buffer->wl_buffer_ready = true;
+  if (window->buffer->draw_buffer_needs_commit) {
+//fprintf(stderr, "buffer_gets_ready calls buffer_commit\n");
+    Fl_Wayland_Graphics_Driver::buffer_commit(window);
+  }
+}
+
+static const struct wl_buffer_listener buffer_listener = {
+  buffer_gets_ready
+};
+
+
+struct buffer *Fl_Wayland_Graphics_Driver::create_shm_buffer(int width, int height, uint32_t format, struct wld_window *window)
+{
+  struct buffer *buffer;
+
+  int stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, width);
+  int size = stride * height;
+  int fd = create_anonymous_file(size);
+  if (fd < 0) {
+    fprintf(stderr, "creating a buffer file for %d B failed: %s\n",
+      size, strerror(errno));
+    return NULL;
+  }
+  void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+  if (data == MAP_FAILED) {
+    fprintf(stderr, "mmap failed: %s\n", strerror(errno));
+    close(fd);
+    return NULL;
+  }
+  buffer = (struct buffer*)calloc(1, sizeof *buffer);
+  buffer->stride = stride;
+  Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver();
+  struct wl_shm_pool *pool = wl_shm_create_pool(scr_driver->wl_shm, fd, size);
+  buffer->wl_buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, format);
+  wl_buffer_add_listener(buffer->wl_buffer, &buffer_listener, window);
+  wl_shm_pool_destroy(pool);
+  close(fd);
+  buffer->data = data;
+  buffer->data_size = size;
+  buffer->width = width;
+  buffer->draw_buffer = new uchar[buffer->data_size];
+  buffer->wl_buffer_ready = true;
+  buffer->draw_buffer_needs_commit = false;
+//fprintf(stderr, "create_shm_buffer: %dx%d\n", width, height);
+  cairo_init(buffer, width, height, stride);
+  return buffer;
+}
+
+
+void Fl_Wayland_Graphics_Driver::buffer_commit(struct wld_window *window) {
+  memcpy(window->buffer->data, window->buffer->draw_buffer, window->buffer->data_size);
+  wl_surface_attach(window->wl_surface, window->buffer->wl_buffer, 0, 0);
+  wl_surface_set_buffer_scale(window->wl_surface, window->scale);
+  wl_surface_commit(window->wl_surface);
+  window->buffer->draw_buffer_needs_commit = false;
+  window->buffer->wl_buffer_ready = false;
+}
+
+
+void Fl_Wayland_Graphics_Driver::cairo_init(struct buffer *buffer, int width, int height, int stride) {
+  buffer->cairo_surface_ = cairo_image_surface_create_for_data (buffer->draw_buffer, CAIRO_FORMAT_ARGB32,
+                                                        width, height, stride);
+  if (cairo_surface_status(buffer->cairo_surface_) != CAIRO_STATUS_SUCCESS) {
+    fprintf(stderr, "Can't create Cairo surface\n");
+  }
+  buffer->cairo_ = cairo_create(buffer->cairo_surface_);
+  cairo_status_t err;
+  if ((err = cairo_status(buffer->cairo_)) != CAIRO_STATUS_SUCCESS) {
+    fprintf(stderr, "Cairo error on create %s\n", cairo_status_to_string(err));
+  }
+  cairo_set_source_rgba(buffer->cairo_, 1.0, 1.0, 1.0, 0.);
+  cairo_paint(buffer->cairo_);
+  buffer->pango_layout_ = pango_cairo_create_layout(buffer->cairo_);
+  cairo_save(buffer->cairo_);
+}
+
+
+void Fl_Wayland_Graphics_Driver::buffer_release(struct wld_window *window)
+{
+  if (window->buffer) {
+    wl_buffer_destroy(window->buffer->wl_buffer);
+    munmap(window->buffer->data, window->buffer->data_size);
+    delete[] window->buffer->draw_buffer;
+    window->buffer->draw_buffer = NULL;
+    cairo_destroy(window->buffer->cairo_);
+    cairo_surface_destroy(window->buffer->cairo_surface_);
+    g_object_unref(window->buffer->pango_layout_);
+    free(window->buffer);
+    window->buffer = NULL;
+  }
+}
+
+
+Fl_Wayland_Graphics_Driver::Fl_Wayland_Graphics_Driver () : Fl_Cairo_Graphics_Driver() {
+  clip_ = NULL;
+  dummy_pango_layout_ = NULL;
+  pango_layout_ = NULL;
+}
+
+
+Fl_Wayland_Graphics_Driver::~Fl_Wayland_Graphics_Driver() {
+  if (pango_layout_) g_object_unref(pango_layout_);
+}
+
+
+void Fl_Wayland_Graphics_Driver::activate(struct buffer *buffer, int scale) {
+  if (dummy_pango_layout_) {
+    cairo_destroy(cairo_);
+    cairo_surface_destroy(cairo_surface_);
+    g_object_unref(dummy_pango_layout_);
+    dummy_pango_layout_ = NULL;
+    pango_layout_ = NULL;
+  }
+  cairo_surface_ = buffer->cairo_surface_;
+  cairo_ = buffer->cairo_;
+  if (pango_layout_ != buffer->pango_layout_) {
+    if (pango_layout_) g_object_unref(pango_layout_);
+    pango_layout_ = buffer->pango_layout_;
+    g_object_ref(pango_layout_);
+    Fl_Graphics_Driver::font(-1, -1); // signal that no font is current yet
+  }
+  cairo_restore(cairo_);
+  cairo_save(cairo_);
+  cairo_scale(cairo_, scale, scale);
+  cairo_translate(cairo_, 0.5, 0.5);
+  line_style(0);
+}
+
+
+static Fl_Fontdesc built_in_table[] = {  // Pango font names
+  {"Sans"},
+  {"Sans Bold"},
+  {"Sans Italic"},
+  {"Sans Bold Italic"},
+  {"Monospace"},
+  {"Monospace Bold"},
+  {"Monospace Italic"},
+  {"Monospace Bold Italic"},
+  {"Serif"},
+  {"Serif Bold"},
+  {"Serif Italic"},
+  {"Serif Bold Italic"},
+  {"Standard Symbols PS"}, // FL_SYMBOL
+  {"Monospace"},           // FL_SCREEN
+  {"Monospace Bold"},      // FL_SCREEN_BOLD
+  {"D050000L"},            // FL_ZAPF_DINGBATS
+};
+
+FL_EXPORT Fl_Fontdesc *fl_fonts = built_in_table;
+
+
+static Fl_Font_Descriptor* find(Fl_Font fnum, Fl_Fontsize size) {
+  Fl_Fontdesc* s = fl_fonts+fnum;
+  if (!s->name) s = fl_fonts; // use 0 if fnum undefined
+  Fl_Font_Descriptor* f;
+  for (f = s->first; f; f = f->next)
+    if (f->size == size) return f;
+  f = new Fl_Wayland_Font_Descriptor(s->name, size);
+  f->next = s->first;
+  s->first = f;
+  return f;
+}
+
+
+Fl_Wayland_Font_Descriptor::Fl_Wayland_Font_Descriptor(const char* name, Fl_Fontsize size) : Fl_Font_Descriptor(name, size) {
+  char string[70];
+  strcpy(string, name);
+  sprintf(string + strlen(string), " %d", int(size * 0.7 + 0.5) ); // why reduce size?
+  fontref = pango_font_description_from_string(string);
+  width = NULL;
+  static PangoFontMap *def_font_map = pango_cairo_font_map_get_default(); // 1.10
+  static PangoContext *pango_context = pango_font_map_create_context(def_font_map); // 1.22
+  static PangoLanguage *language = pango_language_get_default(); // 1.16
+  PangoFontset *fontset = pango_font_map_load_fontset(def_font_map, pango_context, fontref, language);
+  PangoFontMetrics *metrics = pango_fontset_get_metrics(fontset);
+  ascent = pango_font_metrics_get_ascent(metrics)/PANGO_SCALE;
+  descent = pango_font_metrics_get_descent(metrics)/PANGO_SCALE;
+  q_width = pango_font_metrics_get_approximate_char_width(metrics)/PANGO_SCALE;
+  pango_font_metrics_unref(metrics);
+  g_object_unref(fontset);
+//fprintf(stderr, "[%s](%d) ascent=%d descent=%d q_width=%d\n", name, size, ascent, descent, q_width);
+}
+
+
+Fl_Wayland_Font_Descriptor::~Fl_Wayland_Font_Descriptor() {
+  pango_font_description_free(fontref);
+  if (width) {
+    for (int i = 0; i < 64; i++) delete[] width[i];
+  }
+  delete[] width;
+}
+
+
+int Fl_Wayland_Graphics_Driver::height() {
+  return (font_descriptor()->ascent + font_descriptor()->descent)*1.1  /*1.15 scale=1*/;
+}
+
+
+int Fl_Wayland_Graphics_Driver::descent() {
+  return font_descriptor()->descent;
+}
+
+
+void Fl_Wayland_Graphics_Driver::font(Fl_Font fnum, Fl_Fontsize s) {
+  if (font() == fnum && size() == s) return;
+  if (!font_descriptor()) fl_open_display();
+  if (!pango_layout_) {
+    cairo_surface_ = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 100, 100);
+    cairo_ = cairo_create(cairo_surface_);
+    dummy_pango_layout_ = pango_cairo_create_layout(cairo_);
+    pango_layout_ = dummy_pango_layout_;
+  }
+  if (fnum == -1) {
+    Fl_Graphics_Driver::font(0, 0);
+    return;
+  }
+  Fl_Graphics_Driver::font(fnum, s);
+  font_descriptor( find(fnum, s) );
+  pango_layout_set_font_description(pango_layout_, ((Fl_Wayland_Font_Descriptor*)font_descriptor())->fontref);
+}
+
+
+static int font_name_process(const char *name, char &face) {
+  int l = strlen(name);
+  face = ' ';
+  if (!memcmp(name + l - 8, " Regular", 8)) l -= 8;
+  else if (!memcmp(name + l - 6, " Plain", 6)) l -= 6;
+  else if (!memcmp(name + l - 12, " Bold Italic", 12)) {l -= 12; face='P';}
+  else if (!memcmp(name + l - 7, " Italic", 7)) {l -= 7; face='I';}
+  else if (!memcmp(name + l - 5, " Bold", 5)) {l -= 5; face='B';}
+  return l;
+}
+
+typedef int (*sort_f_type)(const void *aa, const void *bb);
+
+
+static int font_sort(Fl_Fontdesc *fa, Fl_Fontdesc *fb) {
+  char face_a, face_b;
+  int la = font_name_process(fa->name, face_a);
+  int lb = font_name_process(fb->name, face_b);
+  int c = strncasecmp(fa->name, fb->name, la >= lb ? lb : la);
+  return (c == 0 ? face_a - face_b : c);
+}
+
+
+Fl_Font Fl_Wayland_Graphics_Driver::set_fonts(const char* pattern_name)
+{
+  fl_open_display();
+  int n_families, count = 0;
+  PangoFontFamily **families;
+  static PangoFontMap *pfmap_ = pango_cairo_font_map_get_default(); // 1.10
+  Fl_Wayland_Graphics_Driver::init_built_in_fonts();
+  pango_font_map_list_families(pfmap_, &families, &n_families);
+  for (int fam = 0; fam < n_families; fam++) {
+    PangoFontFace **faces;
+    int n_faces;
+    const char *fam_name = pango_font_family_get_name (families[fam]);
+    int l = strlen(fam_name);
+    pango_font_family_list_faces(families[fam], &faces, &n_faces);
+    for (int j = 0; j < n_faces; j++) {
+      const char *p = pango_font_face_get_face_name(faces[j]);
+      // build the font's FLTK name
+      l += strlen(p) + 2;
+      char *q = new char[l];
+      sprintf(q, "%s %s", fam_name, p);
+      Fl::set_font((Fl_Font)(count++ + FL_FREE_FONT), q);
+    }
+    /*g_*/free(faces); // glib source code shows that g_free is equivalent to free
+  }
+  /*g_*/free(families);
+  // Sort the list into alphabetic order
+  qsort(fl_fonts + FL_FREE_FONT, count, sizeof(Fl_Fontdesc), (sort_f_type)font_sort);
+  return FL_FREE_FONT + count;
+}
+
+
+void Fl_Wayland_Graphics_Driver::init_built_in_fonts() {
+  static int i = 0;
+  if (!i) {
+    while (i < FL_FREE_FONT) {
+      i++;
+      Fl::set_font((Fl_Font)i-1, built_in_table[i-1].name);
+    }
+  }
+}
+
+
+const char *Fl_Wayland_Graphics_Driver::font_name(int num) {
+  return fl_fonts[num].name;
+}
+
+
+void Fl_Wayland_Graphics_Driver::font_name(int num, const char *name) {
+  Fl_Fontdesc *s = fl_fonts + num;
+  if (s->name) {
+    if (!strcmp(s->name, name)) {s->name = name; return;}
+    for (Fl_Font_Descriptor* f = s->first; f;) {
+      Fl_Font_Descriptor* n = f->next; delete f; f = n;
+    }
+    s->first = 0;
+  }
+  s->name = name;
+  s->fontname[0] = 0;
+  s->first = 0;
+}
+
+#define ENDOFBUFFER  sizeof(fl_fonts->fontname)-1
+
+// turn a stored font name into a pretty name:
+const char* Fl_Wayland_Graphics_Driver::get_font_name(Fl_Font fnum, int* ap) {
+  Fl_Fontdesc *f = fl_fonts + fnum;
+  if (!f->fontname[0]) {
+    strcpy(f->fontname, f->name); // to check
+    const char* thisFont = f->name;
+    if (!thisFont || !*thisFont) {if (ap) *ap = 0; return "";}
+    int type = 0;
+    if (strstr(f->name, "Bold")) type |= FL_BOLD;
+    if (strstr(f->name, "Italic") || strstr(f->name, "Oblique")) type |= FL_ITALIC;
+    f->fontname[ENDOFBUFFER] = (char)type;
+  }
+  if (ap) *ap = f->fontname[ENDOFBUFFER];
+  return f->fontname;
+}
+
+
+int Fl_Wayland_Graphics_Driver::get_font_sizes(Fl_Font fnum, int*& sizep) {
+  static int array[128];
+  if (!fl_fonts) fl_fonts = calc_fl_fonts();
+  Fl_Fontdesc *s = fl_fonts+fnum;
+  if (!s->name) s = fl_fonts; // empty slot in table, use entry 0
+  int cnt = 0;
+
+  array[0] = 0;
+  sizep = array;
+  cnt = 1;
+
+  return cnt;
+}
+
+
+void Fl_Wayland_Graphics_Driver::draw(const char* str, int n, float x, float y) {
+  if (!n) return;
+  cairo_save(cairo_);
+  cairo_translate(cairo_, x, y - height() + descent() -1);
+  pango_layout_set_text(pango_layout_, str, n);
+  pango_cairo_show_layout(cairo_, pango_layout_);
+  cairo_restore(cairo_);
+  check_status();
+}
+
+
+void Fl_Wayland_Graphics_Driver::draw(int rotation, const char *str, int n, int x, int y)
+{
+  cairo_save(cairo_);
+  cairo_translate(cairo_, x, y);
+  cairo_rotate(cairo_, -rotation * M_PI / 180);
+  this->draw(str, n, 0, 0);
+  cairo_restore(cairo_);
+}
+
+
+double Fl_Wayland_Graphics_Driver::width(const char* c, int n) {
+  if (!font_descriptor()) return -1.0;
+  int i = 0, w = 0, l;
+  const char *end = c + n;
+  unsigned int ucs;
+  while (i < n) {
+    ucs = fl_utf8decode(c + i, end, &l);
+    i += l;
+    w += width(ucs);
+  }
+  return (double)w;
+}
+
+
+double Fl_Wayland_Graphics_Driver::width(unsigned int c) {
+  unsigned int r = 0;
+  Fl_Wayland_Font_Descriptor *desc = NULL;
+  if (c <= 0xFFFF) { // when inside basic multilingual plane
+    desc = (Fl_Wayland_Font_Descriptor*)font_descriptor();
+    r = (c & 0xFC00) >> 10;
+    if (!desc->width) {
+      desc->width = (int**)new int*[64];
+      memset(desc->width, 0, 64*sizeof(int*));
+    }
+    if (!desc->width[r]) {
+      desc->width[r] = (int*)new int[0x0400];
+      for (int i = 0; i < 0x0400; i++) desc->width[r][i] = -1;
+    } else {
+      if ( desc->width[r][c & 0x03FF] >= 0 ) { // already cached
+        return (double) desc->width[r][c & 0x03FF];
+      }
+    }
+  }
+  char buf[4];
+  int n = fl_utf8encode(c, buf);
+  pango_layout_set_text(pango_layout_, buf, n);
+  int  W = 0, H;
+  pango_layout_get_pixel_size(pango_layout_, &W, &H);
+  if (c <= 0xFFFF) desc->width[r][c & 0x03FF] = W;
+  return (double)W;
+}
+
+
+void Fl_Wayland_Graphics_Driver::text_extents(const char* txt, int n, int& dx, int& dy, int& w, int& h) {
+  pango_layout_set_text(pango_layout_, txt, n);
+  PangoRectangle ink_rect;
+  pango_layout_get_pixel_extents(pango_layout_, &ink_rect, NULL);
+  dx = ink_rect.x;
+  dy = ink_rect.y - height() + descent();
+  w = ink_rect.width;
+  h = ink_rect.height;
+}
+
+
+int Fl_Wayland_Graphics_Driver::not_clipped(int x, int y, int w, int h) {
+  if (!clip_) return 1;
+  if (clip_->w < 0) return 1;
+  int X = 0, Y = 0, W = 0, H = 0;
+  clip_box(x, y, w, h, X, Y, W, H);
+  if (W) return 1;
+  return 0;
+}
+
+int Fl_Wayland_Graphics_Driver::clip_box(int x, int y, int w, int h, int &X, int &Y, int &W, int &H) {
+  if (!clip_) {
+    X = x; Y = y; W = w; H = h;
+    return 0;
+  }
+  if (clip_->w < 0) {
+    X = x; Y = y; W = w; H = h;
+    return 1;
+  }
+  int ret = 0;
+  if (x > (X=clip_->x)) {X=x; ret=1;}
+  if (y > (Y=clip_->y)) {Y=y; ret=1;}
+  if ((x+w) < (clip_->x+clip_->w)) {
+    W=x+w-X;
+
+    ret=1;
+
+  }else
+    W = clip_->x + clip_->w - X;
+  if(W<0){
+    W=0;
+    return 1;
+  }
+  if ((y+h) < (clip_->y+clip_->h)) {
+    H=y+h-Y;
+    ret=1;
+  }else
+    H = clip_->y + clip_->h - Y;
+  if(H<0){
+    W=0;
+    H=0;
+    return 1;
+  }
+  return ret;
+}
+
+void Fl_Wayland_Graphics_Driver::restore_clip() {
+  //TODO
+}
+
+
+Fl_Region Fl_Wayland_Graphics_Driver::XRectangleRegion(int x, int y, int w, int h) {
+  Fl_Region R = (Fl_Region)malloc(sizeof(*R));
+  R->count = 1;
+  R->rects = (cairo_rectangle_t *)malloc(sizeof(cairo_rectangle_t));
+  R->rects->x=x, R->rects->y=y, R->rects->width=w; R->rects->height=h;
+  return R;
+}
+
+
+// r1 â?? r2
+static bool CairoRectContainsRect(cairo_rectangle_t *r1, cairo_rectangle_t *r2) {
+  return r1->x >= r2->x && r1->y >= r2->y && r1->x+r1->width <= r2->x+r2->width &&
+    r1->y+r1->height <= r2->y+r2->height;
+}
+
+
+void Fl_Wayland_Graphics_Driver::add_rectangle_to_region(Fl_Region r, int X, int Y, int W, int H) {
+  cairo_rectangle_t arg = {double(X), double(Y), double(W), double(H)};
+  int j; // don't add a rectangle totally inside the Fl_Region
+  for (j = 0; j < r->count; j++) {
+    if (CairoRectContainsRect(&arg, &(r->rects[j]))) break;
+  }
+  if (j >= r->count) {
+    r->rects = (cairo_rectangle_t*)realloc(r->rects, (++(r->count)) * sizeof(cairo_rectangle_t));
+    r->rects[r->count - 1] = arg;
+  }
+}
+
+
+void Fl_Wayland_Graphics_Driver::XDestroyRegion(Fl_Region r) {
+  if (r) {
+    free(r->rects);
+    free(r);
+  }
+}
+
+
+void Fl_Wayland_Graphics_Driver::set_color(Fl_Color i, unsigned c) {
+  if (fl_cmap[i] != c) {
+    fl_cmap[i] = c;
+  }
+}
+
+
+void Fl_Wayland_Graphics_Driver::point(int x, int y) {
+  rectf(x, y, 1, 1);
+}
+
+
+void Fl_Wayland_Graphics_Driver::copy_offscreen(int x, int y, int w, int h, Fl_Offscreen osrc, int srcx, int srcy) {
+  // draw portion srcx,srcy,w,h of osrc to position x,y (top-left) of the graphics driver's surface
+  int height = osrc->data_size / osrc->stride;
+  cairo_matrix_t matrix;
+  cairo_get_matrix(cairo_, &matrix);
+  double s = matrix.xx;
+  cairo_save(cairo_);
+  cairo_rectangle(cairo_, x, y, w, h);
+  cairo_clip(cairo_);
+  cairo_surface_t *surf = cairo_image_surface_create_for_data(osrc->draw_buffer, CAIRO_FORMAT_ARGB32, osrc->width, height, osrc->stride);
+  cairo_pattern_t *pat = cairo_pattern_create_for_surface(surf);
+  cairo_set_source(cairo_, pat);
+  cairo_matrix_init_scale(&matrix, s, s);
+  cairo_matrix_translate(&matrix, -(x - srcx), -(y - srcy));
+  cairo_pattern_set_matrix(pat, &matrix);
+  cairo_mask(cairo_, pat);
+  cairo_pattern_destroy(pat);
+  cairo_surface_destroy(surf);
+  cairo_restore(cairo_);
+}
+
+
+struct callback_data {
+  const uchar *data;
+  int D, LD;
+};
+
+
+static void draw_image_cb(void *data, int x, int y, int w, uchar *buf) {
+  struct callback_data *cb_data;
+  const uchar *curdata;
+
+  cb_data = (struct callback_data*)data;
+  int last = x+w;
+  const size_t aD = abs(cb_data->D);
+  curdata = cb_data->data + x*cb_data->D + y*cb_data->LD;
+  for (; x<last; x++) {
+    memcpy(buf, curdata, aD);
+    buf += aD;
+    curdata += cb_data->D;
+  }
+}
+
+
+void Fl_Wayland_Graphics_Driver::draw_image(const uchar *data, int ix, int iy, int iw, int ih, int D, int LD) {
+  if (abs(D)<3){ //mono
+    draw_image_mono(data, ix, iy, iw, ih, D, LD);
+    return;
+  }
+  struct callback_data cb_data;
+  if (!LD) LD = iw*abs(D);
+  if (D<0) data += iw*abs(D);
+  cb_data.data = data;
+  cb_data.D = D;
+  cb_data.LD = LD;
+  Fl_Cairo_Graphics_Driver::draw_image(draw_image_cb, &cb_data, ix, iy, iw, ih, abs(D));
+}
+
+
+void Fl_Wayland_Graphics_Driver::curve(double x, double y, double x1, double y1, double x2, double y2, double x3, double y3) {
+  if (shape_ == POINTS) Fl_Graphics_Driver::curve(x, y, x1, y1, x2, y2, x3, y3);
+  else Fl_Cairo_Graphics_Driver::curve(x, y, x1, y1, x2, y2, x3, y3);
+}
+
+
+void Fl_Wayland_Graphics_Driver::begin_points() {
+  cairo_save(cairo_);
+  gap_=1;
+  shape_=POINTS;
+}
+
+
+void Fl_Wayland_Graphics_Driver::end_points() {
+  cairo_restore(cairo_);
+}
+
+
+void Fl_Wayland_Graphics_Driver::transformed_vertex(double x, double y) {
+  if (shape_ == POINTS){
+    cairo_move_to(cairo_, x, y);
+    point(x, y);
+    gap_ = 1;
+  } else {
+    Fl_Cairo_Graphics_Driver::transformed_vertex(x, y);
+  }
+}
+
+
+Fl_Graphics_Driver *Fl_Graphics_Driver::newMainGraphicsDriver()
+{
+  fl_graphics_driver = new Fl_Wayland_Graphics_Driver();
+  return fl_graphics_driver;
+}
+
+
+void fl_rectf(int x, int y, int w, int h, uchar r, uchar g, uchar b) {
+  fl_color(r,g,b);
+  fl_rectf(x,y,w,h);
+}
diff --git src/drivers/Wayland/Fl_Wayland_Image_Surface_Driver.cxx src/drivers/Wayland/Fl_Wayland_Image_Surface_Driver.cxx
new file mode 100644
index 0000000..c1b2624
--- /dev/null
+++ src/drivers/Wayland/Fl_Wayland_Image_Surface_Driver.cxx
@@ -0,0 +1,105 @@
+//
+// Draw-to-image code for the Fast Light Tool Kit (FLTK).
+//
+// Copyright 1998-2021 by Bill Spitzak and others.
+//
+// This library is free software. Distribution and use rights are outlined in
+// the file "COPYING" which should have been included with this file.  If this
+// file is missing or damaged, see the license at:
+//
+//     https://www.fltk.org/COPYING.php
+//
+// Please see the following page on how to report bugs and issues:
+//
+//     https://www.fltk.org/bugs.php
+//
+
+#include <config.h>
+#include <FL/platform.H>
+#include "Fl_Wayland_Graphics_Driver.H"
+#include "Fl_Wayland_Window_Driver.H"
+#include <FL/Fl_Image_Surface.H>
+
+class Fl_Wayland_Image_Surface_Driver : public Fl_Image_Surface_Driver {
+  int wld_scale;
+public:
+  Fl_Wayland_Image_Surface_Driver(int w, int h, int high_res, Fl_Offscreen off);
+  ~Fl_Wayland_Image_Surface_Driver();
+  void set_current();
+  void translate(int x, int y);
+  void untranslate();
+  Fl_RGB_Image *image();
+};
+
+Fl_Image_Surface_Driver *Fl_Image_Surface_Driver::newImageSurfaceDriver(int w, int h, int high_res, Fl_Offscreen off)
+{
+  return new Fl_Wayland_Image_Surface_Driver(w, h, high_res, off);
+}
+
+Fl_Wayland_Image_Surface_Driver::Fl_Wayland_Image_Surface_Driver(int w, int h, int high_res, Fl_Offscreen off) : Fl_Image_Surface_Driver(w, h, high_res, off) {
+  float d = 1;
+  wld_scale = 1;
+  if (!off) {
+    fl_open_display();
+    if (fl_window) {
+      wld_scale = fl_window->scale; //TODO: take it from the display
+      w *= wld_scale;
+      h *= wld_scale;
+    }
+    d = fl_graphics_driver->scale();
+    if (d != 1 && high_res) {
+      w = int(w*d);
+      h = int(h*d);
+    }
+    offscreen = (struct buffer*)calloc(1, sizeof(struct buffer));
+    offscreen->stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, w);
+    offscreen->data_size = offscreen->stride * h;
+    offscreen->draw_buffer = (uchar*)malloc(offscreen->data_size);
+    offscreen->width = w;
+    Fl_Wayland_Graphics_Driver::cairo_init(offscreen, w, h, offscreen->stride);
+  }
+  driver(new Fl_Wayland_Graphics_Driver());
+  if (d != 1 && high_res) ((Fl_Wayland_Graphics_Driver*)driver())->scale(d);
+}
+
+
+Fl_Wayland_Image_Surface_Driver::~Fl_Wayland_Image_Surface_Driver() {
+  if (offscreen && !external_offscreen) {
+    free(offscreen->draw_buffer);
+    free(offscreen);
+  }
+  delete driver();
+}
+
+void Fl_Wayland_Image_Surface_Driver::set_current() {
+  Fl_Surface_Device::set_current();
+  ((Fl_Wayland_Graphics_Driver*)fl_graphics_driver)->activate(offscreen, wld_scale);
+}
+
+void Fl_Wayland_Image_Surface_Driver::translate(int x, int y) {
+  ((Fl_Wayland_Graphics_Driver*)driver())->ps_translate(x, y);
+}
+
+void Fl_Wayland_Image_Surface_Driver::untranslate() {
+  ((Fl_Wayland_Graphics_Driver*)driver())->ps_untranslate();
+}
+
+Fl_RGB_Image* Fl_Wayland_Image_Surface_Driver::image() {
+  // Convert depth-4 image in draw_buffer to a depth-3 image while exchanging R and B colors
+  int height = offscreen->data_size / offscreen->stride;
+  uchar *rgb = new uchar[offscreen->width * height * 3];
+  uchar *p = rgb;
+  uchar *q;
+  for (int j = 0; j < height; j++) {
+    q = offscreen->draw_buffer + j*offscreen->stride;
+    for (int i = 0; i < offscreen->width; i++) { // exchange R and B colors, transmit G
+      *p = *(q+2);
+      *(p+1) = *(q+1);
+      *(p+2) = *q;
+      p += 3; q += 4;
+    }
+  }
+  Fl_RGB_Image *image = new Fl_RGB_Image(rgb, offscreen->width, height, 3);
+  image->alloc_array = 1;
+  return image;
+}
diff --git src/drivers/Wayland/Fl_Wayland_Screen_Driver.H src/drivers/Wayland/Fl_Wayland_Screen_Driver.H
new file mode 100644
index 0000000..689d3b4
--- /dev/null
+++ src/drivers/Wayland/Fl_Wayland_Screen_Driver.H
@@ -0,0 +1,175 @@
+//
+// Definition of X11 Screen interface
+// for the Fast Light Tool Kit (FLTK).
+//
+// Copyright 2010-2021 by Bill Spitzak and others.
+//
+// This library is free software. Distribution and use rights are outlined in
+// the file "COPYING" which should have been included with this file.  If this
+// file is missing or damaged, see the license at:
+//
+//     https://www.fltk.org/COPYING.php
+//
+// Please see the following page on how to report bugs and issues:
+//
+//     https://www.fltk.org/bugs.php
+//
+
+/**
+ \file Fl_Wayland_Screen_Driver.H
+ \brief Definition of Wayland Screen interface
+ */
+
+#ifndef FL_WAYLAND_SCREEN_DRIVER_H
+#define FL_WAYLAND_SCREEN_DRIVER_H
+
+#include "../../Fl_Screen_Driver.H"
+#include <wayland-cursor.h>
+#include <wayland-client.h>
+
+
+class Fl_Window;
+
+struct seat {
+  struct wl_seat *wl_seat;
+  struct wl_pointer *wl_pointer;
+  struct wl_keyboard *wl_keyboard;
+  uint32_t keyboard_enter_serial;
+  struct wl_surface *keyboard_surface;
+  struct wl_list link;
+  struct wl_list pointer_outputs;
+  struct wl_cursor_theme *cursor_theme;
+  struct wl_cursor *default_cursor;
+  struct wl_surface *cursor_surface;
+  struct wl_surface *pointer_focus;
+  int pointer_scale;
+  uint32_t serial;
+  struct wl_data_device_manager *data_device_manager;
+  struct wl_data_device *data_device;
+  struct wl_data_source *data_source;
+  struct xkb_state *xkb_state;
+  struct xkb_context *xkb_context;
+  struct xkb_keymap *xkb_keymap;
+  struct xkb_compose_state *xkb_compose_state;
+  char *name;
+};
+
+class FL_EXPORT Fl_Wayland_Screen_Driver : public Fl_Screen_Driver
+{
+  friend class Fl_Screen_Driver;
+public:
+  typedef struct {
+    short x_org;
+    short y_org;
+    short width;
+    short height;
+    float scale;
+  } FLScreenInfo;
+  FLScreenInfo screens[MAX_SCREENS];
+  float dpi[MAX_SCREENS][2];
+  int poll_or_select();
+  int poll_or_select_with_delay(double time_to_wait);
+  int get_mouse_unscaled(int &xx, int &yy);
+  void screen_count(int count) {num_screens = count;}
+  
+  void reset_cursor();
+  struct wl_cursor *xc_arrow;
+  struct wl_cursor *xc_ns;
+  struct wl_cursor *xc_wait;
+  struct wl_cursor *xc_insert;
+  struct wl_cursor *xc_hand;
+  struct wl_cursor *xc_help;
+  struct wl_cursor *xc_cross;
+  struct wl_cursor *xc_move;
+  struct wl_cursor *xc_north;
+  struct wl_cursor *xc_south;
+  struct wl_cursor *xc_west;
+  struct wl_cursor *xc_east;
+  struct wl_cursor *xc_we;
+  struct wl_cursor *xc_nesw;
+  struct wl_cursor *xc_nwse;
+  struct wl_cursor *xc_sw;
+  struct wl_cursor *xc_se;
+  struct wl_cursor *xc_ne;
+  struct wl_cursor *xc_nw;
+  static const struct wl_data_device_listener *p_data_device_listener;
+
+public:
+  struct wl_compositor *wl_compositor;
+  struct wl_subcompositor *wl_subcompositor;
+  struct wl_shm *wl_shm;
+  struct wl_list seats;
+  struct seat *seat;
+  struct wl_list outputs;
+  struct output {
+    uint32_t id;
+    struct wl_output *wl_output;
+    int scale;
+    struct wl_list link;
+  };
+  struct libdecor *libdecor_context;
+  struct xdg_wm_base *xdg_wm_base;
+  
+  Fl_Wayland_Screen_Driver();
+  virtual APP_SCALING_CAPABILITY rescalable() { return PER_SCREEN_APP_SCALING; }
+  virtual float scale(int n) {return screens[n].scale;}
+  virtual void scale(int n, float f) { screens[n].scale = f;}
+  virtual void desktop_scale_factor();
+  int screen_num_unscaled(int x, int y);
+
+  void copy_image(const unsigned char* data, int W, int H);
+  // --- screen configuration
+  void init_workarea();
+  virtual void init();
+  virtual int x();
+  virtual int y();
+  virtual int w();
+  virtual int h();
+  virtual void screen_xywh(int &X, int &Y, int &W, int &H, int n);
+  virtual void screen_dpi(float &h, float &v, int n=0);
+  virtual void screen_work_area(int &X, int &Y, int &W, int &H, int n);
+  // --- audible output
+  virtual void beep(int type);
+  // --- global events
+  virtual void flush();
+  virtual double wait(double time_to_wait);
+  virtual int ready();
+  virtual void grab(Fl_Window* win);
+  // --- global colors
+  virtual void get_system_colors();
+  virtual const char *get_system_scheme();
+  // --- global timers
+  virtual void add_timeout(double time, Fl_Timeout_Handler cb, void *argp);
+  virtual void repeat_timeout(double time, Fl_Timeout_Handler cb, void *argp);
+  virtual int has_timeout(Fl_Timeout_Handler cb, void *argp);
+  virtual void remove_timeout(Fl_Timeout_Handler cb, void *argp);
+  virtual int dnd(int unused);
+  virtual int compose(int &del);
+  virtual void compose_reset();
+  virtual int text_display_can_leak();
+  virtual Fl_RGB_Image *read_win_rectangle(int X, int Y, int w, int h, Fl_Window *win, bool may_capture_subwins, bool *did_capture_subwins);
+  virtual int get_mouse(int &x, int &y);
+  virtual void enable_im();
+  virtual void disable_im();
+  virtual void open_display_platform();
+  virtual void close_display();
+  // --- compute dimensions of an Fl_Offscreen
+  virtual void offscreen_size(Fl_Offscreen o, int &width, int &height);
+  virtual void default_icons(const Fl_RGB_Image *icons[], int count);
+  virtual int has_marked_text();
+  virtual void reset_marked_text();
+  static int next_marked_length; // next length of marked text after current marked text will have been replaced
+  // --- Wayland-special
+  void set_cursor();
+  struct wl_cursor *default_cursor();
+  void default_cursor(struct wl_cursor *cursor);
+  struct wl_cursor *cache_cursor(const char *cursor_name);
+uint32_t get_serial();
+struct wl_seat *get_wl_seat();
+//char  *get_seat_name();
+  struct xkb_keymap *get_xkb_keymap();
+  static bool own_output(struct wl_output *output);
+};
+
+
+#endif // FL_WAYLAND_SCREEN_DRIVER_H
diff --git src/drivers/Wayland/Fl_Wayland_Screen_Driver.cxx src/drivers/Wayland/Fl_Wayland_Screen_Driver.cxx
new file mode 100755
index 0000000..d0d18c6
--- /dev/null
+++ src/drivers/Wayland/Fl_Wayland_Screen_Driver.cxx
@@ -0,0 +1,1395 @@
+//
+// Implementation of Wayland Screen interface
+//
+// Copyright 1998-2021 by Bill Spitzak and others.
+//
+// This library is free software. Distribution and use rights are outlined in
+// the file "COPYING" which should have been included with this file.  If this
+// file is missing or damaged, see the license at:
+//
+//     https://www.fltk.org/COPYING.php
+//
+// Please see the following page on how to report bugs and issues:
+//
+//     https://www.fltk.org/bugs.php
+//
+
+#include <config.h>
+#include "Fl_Wayland_Screen_Driver.H"
+#include "Fl_Wayland_Window_Driver.H"
+#include "Fl_Wayland_System_Driver.H"
+#include "Fl_Wayland_Graphics_Driver.H"
+#include "../../../libdecor/src/libdecor.h"
+#include "../../../libdecor/build/xdg-shell-client-protocol.h"
+#include "../Posix/Fl_Posix_System_Driver.H"
+#include <FL/Fl.H>
+#include <FL/platform.H>
+#include <FL/fl_ask.H>
+#include <FL/Fl_Box.H>
+#include <FL/Fl_Tooltip.H>
+#include <FL/filename.H>
+#include <dlfcn.h>
+#include <sys/time.h>
+#include <linux/input.h>
+#include <xkbcommon/xkbcommon.h>
+#include <xkbcommon/xkbcommon-compose.h>
+#include <assert.h>
+#include <sys/mman.h>
+extern "C" {
+  bool libdecor_get_cursor_settings(char **theme, int *size);
+}
+
+
+#define fl_max(a,b) ((a) > (b) ? (a) : (b))
+
+struct pointer_output {
+  Fl_Wayland_Screen_Driver::output* output;
+  struct wl_list link;
+};
+
+
+/* Implementation note about screen-related information
+ 
+ struct wl_output : Wayland-defined, contains info about a screen, one such record for each screen
+
+ struct Fl_Wayland_Screen_Driver::output { // FLTK defined
+   uint32_t id; // screen identification
+   struct wl_output *wl_output;
+   int scale;
+   struct wl_list link;
+ };
+
+ struct Fl_Wayland_Window_Driver::window_output {  // FLTK defined
+   Fl_Wayland_Screen_Driver::output* output;
+   struct wl_list link;
+ }
+
+ The unique Fl_Wayland_Screen_Driver object contains a member
+   "outputs" of type struct wl_list = list of Fl_Wayland_Screen_Driver::output records
+   - this list is initialised by open-display
+   - registry_handle_global() feeds the list with 1 record for each screen
+   - registry_handle_global_remove() runs when a screen is removed. It removes
+   output records that correspond to that screen from the unique list of screens
+   (outputs member of the Fl_Wayland_Screen_Driver) and the list of struct output objects attached
+   to each window.
+
+ Each Fl_Wayland_Window_Driver object contains a member
+   "outputs" of type struct wl_list = list of Fl_Wayland_Window_Driver::window_output records
+   - this list is fed by surface_enter() (when a surface is mapped?)
+   - these records contain:
+   window_output->output = (Fl_Wayland_Screen_Driver::output*)wl_output_get_user_data(wl_output);
+   where wl_output is received from OS by surface_enter()
+   - surface_leave() removes the adequate record from the list
+   - Fl_Wayland_Window_Driver::update_scale() sets the scale info of the records for a given window
+ */
+
+static void xdg_wm_base_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial)
+{
+    xdg_wm_base_pong(xdg_wm_base, serial);
+}
+
+static const struct xdg_wm_base_listener xdg_wm_base_listener = {
+    .ping = xdg_wm_base_ping,
+};
+
+
+// these are set by Fl::args() and override any system colors: from Fl_get_system_colors.cxx
+extern const char *fl_fg;
+extern const char *fl_bg;
+extern const char *fl_bg2;
+// end of extern additions workaround
+
+//
+//  timers
+//
+
+
+////////////////////////////////////////////////////////////////////////
+// Timeouts are stored in a sorted list (*first_timeout), so only the
+// first one needs to be checked to see if any should be called.
+// Allocated, but unused (free) Timeout structs are stored in another
+// linked list (*free_timeout).
+
+struct Timeout {
+  double time;
+  void (*cb)(void*);
+  void* arg;
+  Timeout* next;
+};
+static Timeout* first_timeout, *free_timeout;
+
+// I avoid the overhead of getting the current time when we have no
+// timeouts by setting this flag instead of getting the time.
+// In this case calling elapse_timeouts() does nothing, but records
+// the current time, and the next call will actually elapse time.
+static char reset_clock = 1;
+
+static void elapse_timeouts() {
+  static struct timeval prevclock;
+  struct timeval newclock;
+  gettimeofday(&newclock, NULL);
+  double elapsed = newclock.tv_sec - prevclock.tv_sec +
+    (newclock.tv_usec - prevclock.tv_usec)/1000000.0;
+  prevclock.tv_sec = newclock.tv_sec;
+  prevclock.tv_usec = newclock.tv_usec;
+  if (reset_clock) {
+    reset_clock = 0;
+  } else if (elapsed > 0) {
+    for (Timeout* t = first_timeout; t; t = t->next) t->time -= elapsed;
+  }
+}
+
+
+// Continuously-adjusted error value, this is a number <= 0 for how late
+// we were at calling the last timeout. This appears to make repeat_timeout
+// very accurate even when processing takes a significant portion of the
+// time interval:
+static double missed_timeout_by;
+
+/**
+ Creates a driver that manages all screen and display related calls.
+
+ This function must be implemented once for every platform.
+ */
+Fl_Screen_Driver *Fl_Screen_Driver::newScreenDriver()
+{
+  Fl_Wayland_Screen_Driver *d = new Fl_Wayland_Screen_Driver();
+#if USE_XFT
+  for (int i = 0;  i < MAX_SCREENS; i++) d->screens[i].scale = 1;
+#endif
+  return d;
+}
+
+FL_EXPORT struct wl_display *fl_display = NULL;
+
+static bool has_xrgb = false;
+
+
+static void shm_format(void *data, struct wl_shm *wl_shm, uint32_t format)
+{
+  if (format == WL_SHM_FORMAT_ARGB8888)
+    has_xrgb = true;
+}
+
+static struct wl_shm_listener shm_listener = {
+  shm_format
+};
+
+static void do_set_cursor(struct seat *seat)
+{
+  struct wl_cursor *wl_cursor;
+  struct wl_cursor_image *image;
+  struct wl_buffer *buffer;
+  const int scale = seat->pointer_scale;
+
+  if (!seat->cursor_theme)
+    return;
+
+  wl_cursor = seat->default_cursor;
+  image = wl_cursor->images[0];
+  buffer = wl_cursor_image_get_buffer(image);
+  wl_pointer_set_cursor(seat->wl_pointer, seat->serial,
+            seat->cursor_surface,
+            image->hotspot_x / scale,
+            image->hotspot_y / scale);
+  wl_surface_attach(seat->cursor_surface, buffer, 0, 0);
+  wl_surface_set_buffer_scale(seat->cursor_surface, scale);
+  wl_surface_damage_buffer(seat->cursor_surface, 0, 0,
+         image->width, image->height);
+  wl_surface_commit(seat->cursor_surface);
+}
+
+static uint32_t ptime;
+FL_EXPORT uint32_t fl_event_time;
+static int px, py;
+
+
+static void set_event_xy(Fl_Window *win) {
+  // turn off is_click if enough time or mouse movement has passed:
+  if (abs(Fl::e_x_root-px)+abs(Fl::e_y_root-py) > 3 ||
+      fl_event_time >= ptime+1000) {
+    Fl::e_is_click = 0;
+//fprintf(stderr, "Fl::e_is_click = 0\n");
+  }
+}
+
+// if this is same event as last && is_click, increment click count:
+static inline void checkdouble() {
+  if (Fl::e_is_click == Fl::e_keysym) {
+    Fl::e_clicks++;
+//fprintf(stderr, "Fl::e_clicks = %d\n", Fl::e_clicks);
+  } else {
+    Fl::e_clicks = 0;
+    Fl::e_is_click = Fl::e_keysym;
+//fprintf(stderr, "Fl::e_is_click = %d\n", Fl::e_is_click);
+  }
+  px = Fl::e_x_root;
+  py = Fl::e_y_root;
+  ptime = fl_event_time;
+}
+
+
+static Fl_Window *surface_to_window(struct wl_surface *surface) {
+  Fl_X *xp = Fl_X::first;
+  while (xp) {
+    if (xp->xid->wl_surface == surface || xp->xid->gl_wl_surface == surface) return xp->w;
+    xp = xp->next;
+  }
+  return NULL;
+}
+
+
+static void pointer_enter(void *data,
+        struct wl_pointer *wl_pointer,
+        uint32_t serial,
+        struct wl_surface *surface,
+        wl_fixed_t surface_x,
+        wl_fixed_t surface_y)
+{
+  struct seat *seat = (struct seat*)data;
+  do_set_cursor(seat);
+  seat->serial = serial;
+  Fl_Window *win = surface_to_window(surface);
+  if (win) {
+    Fl::e_x = wl_fixed_to_int(surface_x);
+    Fl::e_x_root = Fl::e_x + win->x();
+    Fl::e_y = wl_fixed_to_int(surface_y);
+    Fl::e_y_root = Fl::e_y + win->y();
+    set_event_xy(win);
+    Fl::handle(FL_ENTER, win);
+//fprintf(stderr, "pointer_enter window=%p\n", win);
+  }
+  seat->pointer_focus = surface;
+}
+
+
+static void pointer_leave(void *data,
+        struct wl_pointer *wl_pointer,
+        uint32_t serial,
+        struct wl_surface *surface)
+{
+  struct seat *seat = (struct seat*)data;
+  if (seat->pointer_focus == surface) seat->pointer_focus = NULL;
+  Fl_Window *win = surface_to_window(surface);
+  if (win) {
+    Fl::belowmouse(0);
+    set_event_xy(win);
+  }
+//fprintf(stderr, "pointer_leave surface=%p window=%p\n", surface, win);
+}
+
+
+static void pointer_motion(void *data,
+         struct wl_pointer *wl_pointer,
+         uint32_t time,
+         wl_fixed_t surface_x,
+         wl_fixed_t surface_y)
+{
+  struct seat *seat = (struct seat*)data;
+  Fl_Window *win = surface_to_window(seat->pointer_focus);
+  if (!win) return;
+  Fl::e_x = wl_fixed_to_int(surface_x);
+  Fl::e_x_root = Fl::e_x + win->x();
+  // If there's an active grab() and the pointer is in a window other than the grab(),
+  // make e_x_root too large to be in any window
+  if (Fl::grab() && !Fl::grab()->menu_window() && Fl::grab() != win) {
+    Fl::e_x_root = 1000000;
+  }
+  Fl::e_y = wl_fixed_to_int(surface_y);
+  Fl::e_y_root = Fl::e_y + win->y();
+//fprintf(stderr, "FL_MOVE on win=%p to x:%dx%d root:%dx%d\n", win, Fl::e_x, Fl::e_y, Fl::e_x_root, Fl::e_y_root);
+  fl_event_time = time;
+  set_event_xy(win);
+  Fl::handle(FL_MOVE, win);
+}
+
+
+//#include <FL/names.h>
+static void pointer_button(void *data,
+         struct wl_pointer *wl_pointer,
+         uint32_t serial,
+         uint32_t time,
+         uint32_t button,
+         uint32_t state)
+{
+  struct seat *seat = (struct seat*)data;
+  seat->serial = serial;
+  int event = 0;
+  Fl_Window *win = surface_to_window(seat->pointer_focus);
+  if (!win) return;
+  fl_event_time = time;
+  if (button == BTN_LEFT && state == WL_POINTER_BUTTON_STATE_PRESSED && seat->pointer_focus == NULL &&
+      fl_xid(win)->frame) {
+    // click on titlebar
+    libdecor_frame_move(fl_xid(win)->frame, seat->wl_seat, serial);
+    return;
+  }
+  int b = 0;
+  Fl::e_state = 0;
+  if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
+    if (button == BTN_LEFT) {Fl::e_state = FL_BUTTON1; b = 1;}
+    else if (button == BTN_RIGHT) {Fl::e_state = FL_BUTTON3; b = 3;}
+    else if (button == BTN_MIDDLE) {Fl::e_state = FL_BUTTON2; b = 2;}
+  }
+  Fl::e_keysym = FL_Button + b;
+  Fl::e_dx = Fl::e_dy = 0;
+
+  if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
+    event = FL_PUSH;
+    checkdouble();
+  } else if (state == WL_POINTER_BUTTON_STATE_RELEASED) event = FL_RELEASE;
+  set_event_xy(win);
+//fprintf(stderr, "%s %s\n", fl_eventnames[event], win->label() ? win->label():"[]");
+  Fl::handle(event, win);
+}
+
+static void pointer_axis(void *data,
+       struct wl_pointer *wl_pointer,
+       uint32_t time,
+       uint32_t axis,
+       wl_fixed_t value)
+{
+  struct seat *seat = (struct seat*)data;
+  Fl_Window *win = surface_to_window(seat->pointer_focus);
+  if (!win) return;
+  fl_event_time = time;
+  int delta = wl_fixed_to_int(value) / 10;
+//fprintf(stderr, "FL_MOUSEWHEEL: %c delta=%d\n", axis==WL_POINTER_AXIS_HORIZONTAL_SCROLL?'H':'V', delta);
+  // allow both horizontal and vertical movements to be processed by the widget
+  if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) {
+    Fl::e_dx = delta;
+    Fl::e_dy = 0;
+    Fl::handle(FL_MOUSEWHEEL, win);
+  }
+  if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) {
+    Fl::e_dx = 0;
+    Fl::e_dy = delta;
+    Fl::handle(FL_MOUSEWHEEL, win);
+  }
+}
+
+static struct wl_pointer_listener pointer_listener = {
+  pointer_enter,
+  pointer_leave,
+  pointer_motion,
+  pointer_button,
+  pointer_axis
+};
+
+static const char *proxy_tag = "libdecor-client";//TODO: see what's the purpose of this
+
+bool Fl_Wayland_Screen_Driver::own_output(struct wl_output *output)
+{
+  return wl_proxy_get_tag((struct wl_proxy *)output) == &proxy_tag;
+}
+
+static void init_cursors(struct seat *seat);
+
+static void try_update_cursor(struct seat *seat)
+{
+  struct pointer_output *pointer_output;
+  int scale = 1;
+
+  wl_list_for_each(pointer_output, &seat->pointer_outputs, link) {
+    scale = fl_max(scale, pointer_output->output->scale);
+  }
+
+  if (scale != seat->pointer_scale) {
+    seat->pointer_scale = scale;
+    init_cursors(seat);
+    do_set_cursor(seat);
+  }
+}
+
+
+static void cursor_surface_enter(void *data,
+        struct wl_surface *wl_surface,
+        struct wl_output *wl_output)
+{
+  struct seat *seat = (struct seat*)data;
+  struct pointer_output *pointer_output;
+
+  if (!Fl_Wayland_Screen_Driver::own_output(wl_output))
+    return;
+
+  pointer_output = (struct pointer_output *)calloc(1, sizeof(struct pointer_output));
+  pointer_output->output = (Fl_Wayland_Screen_Driver::output *)wl_output_get_user_data(wl_output);
+//fprintf(stderr, "cursor_surface_enter: wl_output_get_user_data(%p)=%p\n", wl_output, pointer_output->output);
+  wl_list_insert(&seat->pointer_outputs, &pointer_output->link);
+  try_update_cursor(seat);
+}
+
+static void cursor_surface_leave(void *data,
+        struct wl_surface *wl_surface,
+        struct wl_output *wl_output)
+{
+  struct seat *seat = (struct seat*)data;
+  struct pointer_output *pointer_output, *tmp;
+
+  wl_list_for_each_safe(pointer_output, tmp, &seat->pointer_outputs, link) {
+    if (pointer_output->output->wl_output == wl_output) {
+      wl_list_remove(&pointer_output->link);
+      free(pointer_output);
+    }
+  }
+}
+
+static struct wl_surface_listener cursor_surface_listener = {
+  cursor_surface_enter,
+  cursor_surface_leave,
+};
+
+
+static void init_cursors(struct seat *seat)
+{
+  char *name;
+  int size;
+  struct wl_cursor_theme *theme;
+
+  if (!libdecor_get_cursor_settings(&name, &size)) {
+    name = NULL;
+    size = 24;
+  }
+  size *= seat->pointer_scale;
+  Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver();
+  theme = wl_cursor_theme_load(name, size, scr_driver->wl_shm);
+  free(name);
+  //struct wl_cursor_theme *old_theme = seat->cursor_theme;
+  if (theme != NULL) {
+    if (seat->cursor_theme) {
+     // caution to destroy theme because Fl_Wayland_Window_Driver::set_cursor(Fl_Cursor) caches used cursors
+      scr_driver->reset_cursor();
+      wl_cursor_theme_destroy(seat->cursor_theme);
+    }
+    seat->cursor_theme = theme;
+  }
+  if (seat->cursor_theme)
+    seat->default_cursor = scr_driver->xc_arrow = wl_cursor_theme_get_cursor(seat->cursor_theme, "left_ptr");
+  if (!seat->cursor_surface) {
+    seat->cursor_surface = wl_compositor_create_surface(scr_driver->wl_compositor);
+    wl_surface_add_listener(seat->cursor_surface, &cursor_surface_listener, seat);
+  }
+}
+
+
+static void wl_keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard,
+               uint32_t format, int32_t fd, uint32_t size)
+{
+  struct seat *seat = (struct seat*)data;
+  assert(format == WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1);
+  
+  char *map_shm = (char*)mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
+  assert(map_shm != MAP_FAILED);
+  
+  struct xkb_keymap *xkb_keymap = xkb_keymap_new_from_string(seat->xkb_context, map_shm,
+                                                             XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
+  munmap(map_shm, size);
+  close(fd);
+  
+  struct xkb_state *xkb_state = xkb_state_new(xkb_keymap);
+  xkb_keymap_unref(seat->xkb_keymap);
+  xkb_state_unref(seat->xkb_state);
+  seat->xkb_keymap = xkb_keymap;
+  seat->xkb_state = xkb_state;
+}
+
+static void wl_keyboard_enter(void *data, struct wl_keyboard *wl_keyboard,
+               uint32_t serial, struct wl_surface *surface,
+               struct wl_array *keys)
+{
+  struct seat *seat = (struct seat*)data;
+//fprintf(stderr, "keyboard enter fl_win=%p; keys pressed are:\n", surface_to_window(surface));
+  seat->keyboard_surface = surface;
+  seat->keyboard_enter_serial = serial;
+}
+
+struct key_repeat_data_t {
+  uint32_t time;
+  Fl_Window *window;
+};
+
+#define KEY_REPEAT_DELAY 0.5 // sec
+#define KEY_REPEAT_INTERVAL 0.05 // sec
+
+static void key_repeat_timer_cb(key_repeat_data_t *key_repeat_data) {
+  if (Fl::event() == FL_KEYDOWN && fl_event_time == key_repeat_data->time) {
+    Fl::handle(FL_KEYDOWN, key_repeat_data->window);
+    Fl::add_timeout(KEY_REPEAT_INTERVAL, (Fl_Timeout_Handler)key_repeat_timer_cb, key_repeat_data);
+  }
+  else delete key_repeat_data;
+}
+
+int Fl_Wayland_Screen_Driver::next_marked_length = 0;
+
+int Fl_Wayland_Screen_Driver::has_marked_text() {
+  return true;
+}
+
+void Fl_Wayland_Screen_Driver::reset_marked_text() {
+  Fl::compose_state = 0;
+  next_marked_length = 0;
+}
+
+int Fl_Wayland_Screen_Driver::compose(int& del) {
+  unsigned char ascii = (unsigned char)Fl::e_text[0];
+  int condition = (Fl::e_state & (FL_ALT | FL_META | FL_CTRL)) && ascii < 128 ; // letter+modifier key
+  condition |= (Fl::e_keysym >= FL_Shift_L && Fl::e_keysym <= FL_Alt_R); // pressing modifier key
+//fprintf(stderr, "compose: condition=%d e_state=%x ascii=%d\n", condition, Fl::e_state, ascii);
+  if (condition) { del = 0; return 0;}
+//fprintf(stderr, "compose: del=%d compose_state=%d next_marked_length=%d \n", del, Fl::compose_state, next_marked_length);
+  del = Fl::compose_state;
+  Fl::compose_state = next_marked_length;
+  // no-underlined-text && (ascii non-printable || ascii == delete)
+  if ( (!Fl::compose_state) && (ascii <= 31 || ascii == 127)) { del = 0; return 0; }
+  return 1;
+}
+
+void Fl_Wayland_Screen_Driver::compose_reset()
+{
+  Fl::compose_state = 0;
+  next_marked_length = 0;
+  xkb_compose_state_reset(seat->xkb_compose_state);
+}
+
+struct dead_key_struct {
+  xkb_keysym_t keysym; // the keysym obtained when hitting a dead key
+  const char *marked_text; // the temporary text to display for that dead key
+};
+
+static dead_key_struct dead_keys[] = {
+  {XKB_KEY_dead_grave, "`"},
+  {XKB_KEY_dead_acute, "´"},
+  {XKB_KEY_dead_circumflex, "^"},
+  {XKB_KEY_dead_tilde, "~"},
+  {XKB_KEY_dead_perispomeni, "~"}, /* alias for dead_tilde */
+  {XKB_KEY_dead_macron, "¯"},
+  {XKB_KEY_dead_breve, "Ë?"},
+  {XKB_KEY_dead_abovedot, "Ë?"},
+  {XKB_KEY_dead_diaeresis, "¨"},
+  {XKB_KEY_dead_abovering, "Ë?"},
+  {XKB_KEY_dead_doubleacute, "Ë?"},
+  {XKB_KEY_dead_caron, "Ë?"},
+  {XKB_KEY_dead_cedilla, "¸"},
+  {XKB_KEY_dead_ogonek, "Ë?"},
+  {XKB_KEY_dead_iota, "ι"},
+  {XKB_KEY_dead_doublegrave, " Ì?"},
+};
+
+const int dead_key_count = sizeof(dead_keys)/sizeof(struct dead_key_struct);
+
+
+static void wl_keyboard_key(void *data, struct wl_keyboard *wl_keyboard,
+               uint32_t serial, uint32_t time, uint32_t key, uint32_t state)
+{
+  struct seat *seat = (struct seat*)data;
+  seat->serial = serial;
+  static char buf[128];
+  uint32_t keycode = key + 8;
+  xkb_keysym_t sym = xkb_state_key_get_one_sym(seat->xkb_state, keycode);
+/*xkb_keysym_get_name(sym, buf, sizeof(buf));
+const char *action = (state == WL_KEYBOARD_KEY_STATE_PRESSED ? "press" : "release");
+fprintf(stderr, "key %s: sym: %-12s(%d) code:%u fl_win=%p, ", action, buf, sym, keycode, surface_to_window(seat->keyboard_surface));*/
+  xkb_state_key_get_utf8(seat->xkb_state, keycode, buf, sizeof(buf));
+//fprintf(stderr, "utf8: '%s' e_length=%d\n", buf, (int)strlen(buf));
+  Fl::e_keysym = sym;
+  // special processing for number keys == keycodes 10-19 :
+  if (keycode >= 10 && keycode <= 18) Fl::e_keysym = keycode + 39;
+  else if (keycode == 19) Fl::e_keysym = 48;
+  Fl::e_text = buf;
+  Fl::e_length = strlen(buf);
+  // Process dead keys and compose sequences :
+  enum xkb_compose_status status = XKB_COMPOSE_NOTHING;
+  Fl::compose_state = 0;
+  if (state == WL_KEYBOARD_KEY_STATE_PRESSED && !(sym >= FL_Shift_L && sym <= FL_Alt_R) &&
+      sym != XKB_KEY_ISO_Level3_Shift) {
+    xkb_compose_state_feed(seat->xkb_compose_state, sym);
+    status = xkb_compose_state_get_status(seat->xkb_compose_state);
+    if (status == XKB_COMPOSE_COMPOSING) {
+      if (Fl::e_length == 0) { // dead keys produce e_length = 0
+        int i;
+        for (i = 0; i < dead_key_count; i++) {
+          if (dead_keys[i].keysym == sym) break;
+        }
+        if (i < dead_key_count) strcpy(buf, dead_keys[i].marked_text);
+        else buf[0] = 0;
+        Fl::e_length = strlen(buf);
+        Fl::compose_state = 0;
+      }
+      Fl_Wayland_Screen_Driver::next_marked_length = Fl::e_length;
+    } else if (status == XKB_COMPOSE_COMPOSED) {
+      Fl::e_length = xkb_compose_state_get_utf8(seat->xkb_compose_state, buf, sizeof(buf));
+      Fl::compose_state = Fl_Wayland_Screen_Driver::next_marked_length;
+      Fl_Wayland_Screen_Driver::next_marked_length = 0;
+    } else if (status == XKB_COMPOSE_CANCELLED) {
+      Fl::e_length = 0;
+      Fl::compose_state = Fl_Wayland_Screen_Driver::next_marked_length;
+      Fl_Wayland_Screen_Driver::next_marked_length = 0;
+    }
+//fprintf(stderr, "xkb_compose_status=%d ctxt=%p state=%p l=%d[%s]\n", status, seat->xkb_context, seat->xkb_compose_state, Fl::e_length, buf);
+  }
+  
+  fl_event_time = time;
+  int event = (state == WL_KEYBOARD_KEY_STATE_PRESSED ? FL_KEYDOWN : FL_KEYUP);
+  // Send event to focus-containing top window as defined by FLTK,
+  // otherwise send it to Wayland-defined focus window
+  Fl_Window *win = ( Fl::focus() ? Fl::focus()->top_window() : surface_to_window(seat->keyboard_surface) );
+  set_event_xy(win);
+  Fl::e_is_click = 0;
+  Fl::handle(event, win);
+  key_repeat_data_t *key_repeat_data = new key_repeat_data_t;
+  key_repeat_data->time = time;
+  key_repeat_data->window = win;
+  if (event == FL_KEYDOWN && status == XKB_COMPOSE_NOTHING && !(sym >= FL_Shift_L && sym <= FL_Alt_R))
+    Fl::add_timeout(KEY_REPEAT_DELAY, (Fl_Timeout_Handler)key_repeat_timer_cb, key_repeat_data);
+}
+
+static void wl_keyboard_leave(void *data, struct wl_keyboard *wl_keyboard,
+               uint32_t serial, struct wl_surface *surface)
+{
+  struct seat *seat = (struct seat*)data;
+//fprintf(stderr, "keyboard leave fl_win=%p\n", surface_to_window(surface));
+  seat->keyboard_surface = NULL;
+}
+
+static void wl_keyboard_modifiers(void *data, struct wl_keyboard *wl_keyboard,
+               uint32_t serial, uint32_t mods_depressed,
+               uint32_t mods_latched, uint32_t mods_locked,
+               uint32_t group)
+{
+  struct seat *seat = (struct seat*)data;
+  xkb_state_update_mask(seat->xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, group);
+  Fl::e_state = 0;
+  if (xkb_state_mod_name_is_active(seat->xkb_state, XKB_MOD_NAME_SHIFT, XKB_STATE_MODS_DEPRESSED))
+    Fl::e_state |= FL_SHIFT;
+  if (xkb_state_mod_name_is_active(seat->xkb_state, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_DEPRESSED))
+    Fl::e_state |= FL_CTRL;
+  if (xkb_state_mod_name_is_active(seat->xkb_state, XKB_MOD_NAME_ALT, XKB_STATE_MODS_DEPRESSED))
+    Fl::e_state |= FL_ALT;
+  if (xkb_state_mod_name_is_active(seat->xkb_state, XKB_MOD_NAME_CAPS, XKB_STATE_MODS_LOCKED))
+    Fl::e_state |= FL_CAPS_LOCK;
+//fprintf(stderr, "mods_depressed=%u Fl::e_state=%X\n", mods_depressed, Fl::e_state);
+}
+
+static void wl_keyboard_repeat_info(void *data, struct wl_keyboard *wl_keyboard, int32_t rate, int32_t delay)
+{
+  // wl_keyboard is version 3 under Debian, but that event isn't sent until version 4
+}
+
+static const struct wl_keyboard_listener wl_keyboard_listener = {
+       .keymap = wl_keyboard_keymap,
+       .enter = wl_keyboard_enter,
+       .leave = wl_keyboard_leave,
+       .key = wl_keyboard_key,
+       .modifiers = wl_keyboard_modifiers,
+       .repeat_info = wl_keyboard_repeat_info,
+};
+
+                                                
+static void seat_capabilities(void *data, struct wl_seat *wl_seat, uint32_t capabilities)
+{
+  struct seat *seat = (struct seat*)data;
+  if ((capabilities & WL_SEAT_CAPABILITY_POINTER) && !seat->wl_pointer) {
+    seat->wl_pointer = wl_seat_get_pointer(wl_seat);
+    wl_pointer_add_listener(seat->wl_pointer, &pointer_listener, seat);
+    seat->pointer_scale = 1;
+    init_cursors(seat);
+  } else if (!(capabilities & WL_SEAT_CAPABILITY_POINTER) && seat->wl_pointer) {
+    wl_pointer_release(seat->wl_pointer);
+    seat->wl_pointer = NULL;
+  }
+  
+  bool have_keyboard = capabilities & WL_SEAT_CAPABILITY_KEYBOARD;
+  if (have_keyboard && seat->wl_keyboard == NULL) {
+          seat->wl_keyboard = wl_seat_get_keyboard(wl_seat);
+          wl_keyboard_add_listener(seat->wl_keyboard,
+                          &wl_keyboard_listener, seat);
+//fprintf(stderr, "wl_keyboard version=%d\n", wl_keyboard_get_version(seat->wl_keyboard));
+
+  } else if (!have_keyboard && seat->wl_keyboard != NULL) {
+          wl_keyboard_release(seat->wl_keyboard);
+          seat->wl_keyboard = NULL;
+  }
+}
+
+static void seat_name(void *data, struct wl_seat *wl_seat, const char *name) {
+  struct seat *seat = (struct seat*)data;
+  seat->name = strdup(name);
+}
+
+static struct wl_seat_listener seat_listener = {
+  seat_capabilities,
+  seat_name
+};
+
+static void output_geometry(void *data,
+    struct wl_output *wl_output,
+    int32_t x,
+    int32_t y,
+    int32_t physical_width,
+    int32_t physical_height,
+    int32_t subpixel,
+    const char *make,
+    const char *model,
+    int32_t transform)
+{
+  //fprintf(stderr, "output_geometry: x=%d y=%d physical=%dx%d\n",x,y,physical_width,physical_height);
+}
+
+static void output_mode(void *data, struct wl_output *wl_output, uint32_t flags,
+      int32_t width, int32_t height, int32_t refresh)
+{
+  Fl_Wayland_Screen_Driver::output *output;
+  bool found = false;
+  Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver();
+  wl_list_for_each(output, &(scr_driver->outputs), link) { // all screens
+    if (output->wl_output == wl_output) { // the screen involved in this call
+      for (int i = 0; i < Fl::screen_count(); i++) {
+        scr_driver->screens[i].x_org = 0;
+        scr_driver->screens[i].y_org = 0;
+        scr_driver->screens[i].width = width;
+        scr_driver->screens[i].height = height;
+        found = true;
+      }
+    }
+  }
+  if (!found) {
+    int count = Fl::screen_count();
+    if (count < 0) count = 0;
+    scr_driver->screens[count].x_org = 0;
+    scr_driver->screens[count].y_org = 0;
+    scr_driver->screens[count].width = width;
+    scr_driver->screens[count].height = height;
+    scr_driver->screens[count].scale = 1.f;
+    scr_driver->screen_count(count+1);
+//fprintf(stderr, "output_mode: screen_count()=%d width=%d,height=%d\n",Fl::screen_count(),width,height);
+  }
+}
+
+static void output_done(void *data, struct wl_output *wl_output)
+{//TODO to be verified
+  Fl_Wayland_Screen_Driver::output *output = (Fl_Wayland_Screen_Driver::output*)data;
+  Fl_Wayland_Window_Driver::window_output *window_output;
+  struct seat *seat;
+//fprintf(stderr, "output_done output=%p\n",output);
+  Fl_X *xp = Fl_X::first;
+  while (xp) { // all mapped windows
+    struct wld_window *win = xp->xid;
+    wl_list_for_each(window_output, &(win->outputs), link) { // all Fl_Wayland_Window_Driver::window_output for this window
+      if (window_output->output == output) {
+        Fl_Wayland_Window_Driver *win_driver = (Fl_Wayland_Window_Driver*)Fl_Window_Driver::driver(win->fl_win);
+        if (output->scale != win->scale) win_driver->update_scale();
+      }
+    }
+    xp = xp->next;
+  }
+
+  Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver();
+  wl_list_for_each(seat, &(scr_driver->seats), link) {
+    try_update_cursor(seat);
+  }
+  scr_driver->init_workarea();
+}
+
+
+static void output_scale(void *data,
+       struct wl_output *wl_output,
+       int32_t factor)
+{
+  Fl_Wayland_Screen_Driver::output *output = (Fl_Wayland_Screen_Driver::output*)data;
+  output->scale = factor;
+}
+
+
+static struct wl_output_listener output_listener = {
+  output_geometry,
+  output_mode,
+  output_done,
+  output_scale
+};
+
+
+static void registry_handle_global(void *user_data, struct wl_registry *wl_registry,
+           uint32_t id, const char *interface, uint32_t version) {
+//fprintf(stderr, "interface=%s\n", interface);
+  Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver();
+  if (strcmp(interface, "wl_compositor") == 0) {
+    if (version < 4) {
+      fprintf(stderr, "wl_compositor version >= 4 required");
+      exit(EXIT_FAILURE);
+    }
+    scr_driver->wl_compositor = (struct wl_compositor*)wl_registry_bind(wl_registry,
+           id, &wl_compositor_interface, 4);
+    
+  } else if (strcmp(interface, "wl_subcompositor") == 0) {
+    scr_driver->wl_subcompositor = (struct wl_subcompositor*)wl_registry_bind(wl_registry,
+           id, &wl_subcompositor_interface, 1);    
+    
+  } else if (strcmp(interface, "wl_shm") == 0) {
+    scr_driver->wl_shm = (struct wl_shm*)wl_registry_bind(wl_registry,
+            id, &wl_shm_interface, 1);
+    wl_shm_add_listener(scr_driver->wl_shm, &shm_listener, NULL);
+    
+  } else if (strcmp(interface, "wl_seat") == 0) {
+    if (version < 3) {
+      fprintf(stderr, "%s version 3 required but only version "
+          "%i is available\n", interface, version);
+      exit(EXIT_FAILURE);
+    }
+    if (!scr_driver->seat) scr_driver->seat = (struct seat*)calloc(1, sizeof(struct seat));
+//fprintf(stderr, "registry_handle_global: seat=%p\n", scr_driver->seat);
+    wl_list_init(&scr_driver->seat->pointer_outputs);
+    scr_driver->seat->wl_seat = (wl_seat*)wl_registry_bind(wl_registry, id, &wl_seat_interface, 3);
+    scr_driver->seat->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
+    const char *locale = getenv("LC_ALL");
+    if (!locale || !*locale)
+      locale = getenv("LC_CTYPE");
+    if (!locale || !*locale)
+      locale = getenv("LANG");
+    if (!locale || !*locale)
+      locale = "C";
+    struct xkb_compose_table *table = xkb_compose_table_new_from_locale(scr_driver->seat->xkb_context, locale, XKB_COMPOSE_COMPILE_NO_FLAGS);
+    scr_driver->seat->xkb_compose_state = xkb_compose_state_new(table, XKB_COMPOSE_STATE_NO_FLAGS);
+    wl_seat_add_listener(scr_driver->seat->wl_seat, &seat_listener, scr_driver->seat);
+    if (scr_driver->seat->data_device_manager) {
+      scr_driver->seat->data_device = wl_data_device_manager_get_data_device(scr_driver->seat->data_device_manager, scr_driver->seat->wl_seat);
+      wl_data_device_add_listener(scr_driver->seat->data_device, Fl_Wayland_Screen_Driver::p_data_device_listener, NULL);
+    }
+    
+  } else if (strcmp(interface, wl_data_device_manager_interface.name) == 0) {
+    if (!scr_driver->seat) scr_driver->seat = (struct seat*)calloc(1, sizeof(struct seat));
+    scr_driver->seat->data_device_manager = (struct wl_data_device_manager*)wl_registry_bind(wl_registry, id, &wl_data_device_manager_interface, 3);
+    if (scr_driver->seat->wl_seat) {scr_driver->seat->data_device = wl_data_device_manager_get_data_device(scr_driver->seat->data_device_manager, scr_driver->seat->wl_seat);
+      wl_data_device_add_listener(scr_driver->seat->data_device, Fl_Wayland_Screen_Driver::p_data_device_listener, NULL);
+    }
+//fprintf(stderr, "registry_handle_global: %s\n", interface);
+    
+  } else if (strcmp(interface, "wl_output") == 0) {
+    if (version < 2) {
+      fprintf(stderr, "%s version 3 required but only version "
+          "%i is available\n", interface, version);
+      exit(EXIT_FAILURE);
+    }
+    Fl_Wayland_Screen_Driver::output *output = (Fl_Wayland_Screen_Driver::output*)calloc(1, sizeof *output);
+    output->id = id;
+    output->scale = 1;
+    output->wl_output = (struct wl_output*)wl_registry_bind(wl_registry,
+                 id, &wl_output_interface, 2);
+//fprintf(stderr, "wl_output: id=%d wl_output=%p\n", id, output->wl_output);
+    wl_proxy_set_tag((struct wl_proxy *) output->wl_output, &proxy_tag);
+    wl_output_add_listener(output->wl_output, &output_listener, output);
+    wl_list_insert(&(scr_driver->outputs), &output->link);
+    
+  } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
+//fprintf(stderr, "registry_handle_global interface=%s\n", interface);
+    scr_driver->xdg_wm_base = (struct xdg_wm_base *)wl_registry_bind(wl_registry, id, &xdg_wm_base_interface, 1);
+      xdg_wm_base_add_listener(scr_driver->xdg_wm_base, &xdg_wm_base_listener, NULL);
+  }
+}
+
+
+static void registry_handle_global_remove(void *data, struct wl_registry *registry, uint32_t name)
+{//TODO to be tested
+  Fl_Wayland_Screen_Driver::output *output;
+  Fl_Wayland_Window_Driver::window_output *window_output;
+fprintf(stderr, "registry_handle_global_remove data=%p id=%u\n", data, name);
+  Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver();
+  wl_list_for_each(output, &(scr_driver->outputs), link) { // all screens of the system
+    if (output->id == name) { // the screen being removed
+      Fl_X *xp = Fl_X::first;
+      while (xp) { // all mapped windows
+        struct wld_window *win = xp->xid;
+        wl_list_for_each(window_output, &(win->outputs), link) { // all Fl_Wayland_Window_Driver::window_output for this window
+          if (window_output->output == output) {
+            wl_list_remove(&window_output->link);
+            free(window_output);
+          }
+        }
+        xp = xp->next;
+      }
+      wl_list_remove(&output->link);
+      wl_output_destroy(output->wl_output);
+      free(output);
+      break;
+    }
+  }
+}
+
+
+static const struct wl_registry_listener registry_listener = {
+  registry_handle_global,
+  registry_handle_global_remove
+};
+
+
+static void fd_callback(int unused, struct wl_display *display) {
+  wl_display_dispatch(display);
+}
+
+
+Fl_Wayland_Screen_Driver::Fl_Wayland_Screen_Driver() : Fl_Screen_Driver() {
+  libdecor_context = NULL;
+  seat = NULL;
+  reset_cursor();
+}
+
+void Fl_Wayland_Screen_Driver::open_display_platform() {
+  struct wl_display *wl_display;
+  struct wl_registry *wl_registry;
+  
+  static bool beenHereDoneThat = false;
+  if (beenHereDoneThat)
+    return;
+
+  beenHereDoneThat = true;
+  wl_display = wl_display_connect(NULL);
+  if (!wl_display) {
+    fprintf(stderr, "No Wayland connection\n");
+    exit(EXIT_FAILURE);
+  }
+  fl_display = wl_display;
+  wl_list_init(&seats);
+  wl_list_init(&outputs);
+
+  wl_registry = wl_display_get_registry(wl_display);
+  wl_registry_add_listener(wl_registry, &registry_listener, NULL);
+  wl_display_dispatch(wl_display);
+  wl_display_roundtrip(wl_display);
+  if (!has_xrgb) {
+    fprintf(stderr, "No WL_SHM_FORMAT_ARGB8888 shm format\n");
+    exit( EXIT_FAILURE);
+  }
+  Fl::add_fd(wl_display_get_fd(wl_display), FL_READ, (Fl_FD_Handler)fd_callback, wl_display);
+}
+
+void Fl_Wayland_Screen_Driver::close_display() {
+  Fl::remove_fd(wl_display_get_fd(fl_display));
+  wl_display_disconnect(fl_display);
+}
+
+
+static int fl_workarea_xywh[4] = { -1, -1, -1, -1 };
+
+
+void Fl_Wayland_Screen_Driver::init_workarea()
+{
+    fl_workarea_xywh[0] = 0;
+    fl_workarea_xywh[1] = 0;
+    fl_workarea_xywh[2] = screens[0].width;
+    fl_workarea_xywh[3] = screens[0].height;
+}
+
+
+int Fl_Wayland_Screen_Driver::x() {
+  if (!fl_display) open_display();
+  return fl_workarea_xywh[0]
+#if USE_XFT
+  / screens[0].scale
+#endif
+  ;
+}
+
+int Fl_Wayland_Screen_Driver::y() {
+  if (!fl_display) open_display();
+  return fl_workarea_xywh[1]
+#if USE_XFT
+  / screens[0].scale
+#endif
+  ;
+}
+
+int Fl_Wayland_Screen_Driver::w() {
+  if (!fl_display) open_display();
+  return fl_workarea_xywh[2]
+#if USE_XFT
+      / screens[0].scale
+#endif
+  ;
+}
+
+int Fl_Wayland_Screen_Driver::h() {
+  if (!fl_display) open_display();
+  return fl_workarea_xywh[3]
+#if USE_XFT
+  / screens[0].scale
+#endif
+  ;
+}
+
+
+void Fl_Wayland_Screen_Driver::init() {
+  if (!fl_display) open_display();
+}
+
+
+void Fl_Wayland_Screen_Driver::screen_work_area(int &X, int &Y, int &W, int &H, int n)
+{
+  if (num_screens < 0) init();
+  if (n < 0 || n >= num_screens) n = 0;
+  if (n == 0) { // for the main screen, these return the work area
+    X = Fl::x();
+    Y = Fl::y();
+    W = Fl::w();
+    H = Fl::h();
+  } else { // for other screens, work area is full screen,
+    screen_xywh(X, Y, W, H, n);
+  }
+}
+
+
+void Fl_Wayland_Screen_Driver::screen_xywh(int &X, int &Y, int &W, int &H, int n)
+{
+  if (num_screens < 0) init();
+
+  if ((n < 0) || (n >= num_screens))
+    n = 0;
+
+  if (num_screens > 0) {
+#if USE_XFT
+    float s = screens[n].scale;
+#else
+    float s = 1;
+#endif
+    X = screens[n].x_org / s;
+    Y = screens[n].y_org / s;
+    W = screens[n].width / s;
+    H = screens[n].height / s;
+  }
+}
+
+
+void Fl_Wayland_Screen_Driver::screen_dpi(float &h, float &v, int n)
+{
+  if (num_screens < 0) init();
+  h = v = 0.0f;
+
+  if (n >= 0 && n < num_screens) {
+    h = dpi[n][0];
+    v = dpi[n][1];
+  }
+}
+
+
+void Fl_Wayland_Screen_Driver::beep(int type)
+{ //TODO
+  switch (type) {
+    case FL_BEEP_DEFAULT :
+    case FL_BEEP_ERROR :
+      if (!fl_display) open_display();
+      //XBell(fl_display, 100);
+      break;
+    default :
+      if (!fl_display) open_display();
+      //XBell(fl_display, 50);
+      break;
+  }
+}
+
+
+void Fl_Wayland_Screen_Driver::flush()
+{
+  if (fl_display) {
+    wl_display_flush(fl_display);
+  }
+}
+
+
+double Fl_Wayland_Screen_Driver::wait(double time_to_wait)
+{
+  static char in_idle;
+
+  if (first_timeout) {
+    elapse_timeouts();
+    Timeout *t;
+    while ((t = first_timeout)) {
+      if (t->time > 0) break;
+      // The first timeout in the array has expired.
+      missed_timeout_by = t->time;
+      // We must remove timeout from array before doing the callback:
+      void (*cb)(void*) = t->cb;
+      void *argp = t->arg;
+      first_timeout = t->next;
+      t->next = free_timeout;
+      free_timeout = t;
+      // Now it is safe for the callback to do add_timeout:
+      cb(argp);
+    }
+  } else {
+    reset_clock = 1; // we are not going to check the clock
+  }
+  Fl::run_checks();
+  if (Fl::idle) {
+    if (!in_idle) {
+      in_idle = 1;
+      Fl::idle();
+      in_idle = 0;
+    }
+    // the idle function may turn off idle, we can then wait:
+    if (Fl::idle) time_to_wait = 0.0;
+  }
+  if (first_timeout && first_timeout->time < time_to_wait)
+    time_to_wait = first_timeout->time;
+//fprintf(stderr,"time_to_wait=%g\n", time_to_wait);
+  if (time_to_wait <= 0.0) {
+    // do flush second so that the results of events are visible:
+    int ret = this->poll_or_select_with_delay(0.0);
+    Fl::flush();
+    return ret;
+  } else {
+    // do flush first so that user sees the display:
+    Fl::flush();
+    if (Fl::idle && !in_idle) // 'idle' may have been set within flush()
+      time_to_wait = 0.0;
+    else if (first_timeout && first_timeout->time < time_to_wait) {
+      // another timeout may have been queued within flush(), see STR #3188
+      time_to_wait = first_timeout->time >= 0.0 ? first_timeout->time : 0.0;
+    }
+    return this->poll_or_select_with_delay(time_to_wait);
+  }
+}
+
+
+int Fl_Wayland_Screen_Driver::ready()
+{
+  if (first_timeout) {
+    elapse_timeouts();
+    if (first_timeout->time <= 0) return 1;
+  } else {
+    reset_clock = 1;
+  }
+  return this->poll_or_select();
+}
+
+
+extern void fl_fix_focus(); // in Fl.cxx
+
+
+void Fl_Wayland_Screen_Driver::grab(Fl_Window* win)
+{
+  Fl_Window *fullscreen_win = NULL;
+  for (Fl_Window *W = Fl::first_window(); W; W = Fl::next_window(W)) {
+    if (W->fullscreen_active()) {
+      fullscreen_win = W;
+      break;
+    }
+  }
+  if (win) {
+    if (!Fl::grab()) {
+    }
+    Fl::grab_ = win;    // FIXME: Fl::grab_ "should be private", but we need
+                        // a way to *set* the variable from the driver!
+  } else {
+    if (Fl::grab()) {
+      // We must keep the grab in the non-EWMH fullscreen case
+      if (!fullscreen_win ) {
+        //XUngrabKeyboard(fl_display, fl_event_time);
+      }
+      //XUngrabPointer(fl_display, fl_event_time);
+      // this flush is done in case the picked menu item goes into
+      // an infinite loop, so we don't leave the X server locked up:
+      //XFlush(fl_display);
+      Fl::grab_ = 0;    // FIXME: Fl::grab_ "should be private", but we need
+                        // a way to *set* the variable from the driver!
+      fl_fix_focus();
+    }
+  }
+}
+
+
+static void set_selection_color(uchar r, uchar g, uchar b)
+{
+  Fl::set_color(FL_SELECTION_COLOR,r,g,b);
+}
+
+static void getsyscolor(const char *key1, const char* key2, const char *arg, const char *defarg, void (*func)(uchar,uchar,uchar))
+{
+}
+
+
+void Fl_Wayland_Screen_Driver::get_system_colors()
+{
+  open_display();
+  const char* key1 = 0;
+  if (Fl::first_window()) key1 = Fl::first_window()->xclass();
+  if (!key1) key1 = "fltk";
+  if (!bg2_set)
+    getsyscolor("Text","background",    fl_bg2, "#ffffff", Fl::background2);
+  if (!fg_set)
+    getsyscolor(key1,  "foreground",    fl_fg,  "#000000", Fl::foreground);
+  if (!bg_set)
+    getsyscolor(key1,  "background",    fl_bg,  "#c0c0c0", Fl::background);
+  getsyscolor("Text", "selectBackground", 0, "#000080", set_selection_color);
+}
+
+
+const char *Fl_Wayland_Screen_Driver::get_system_scheme()
+{
+  const char *s = 0L;
+  /*if ((s = fl_getenv("FLTK_SCHEME")) == NULL) {
+    const char* key = 0;
+    if (Fl::first_window()) key = Fl::first_window()->xclass();
+    if (!key) key = "fltk";
+    open_display();
+    s = XGetDefault(fl_display, key, "scheme");
+  }*/
+  return s;
+}
+
+
+void Fl_Wayland_Screen_Driver::add_timeout(double time, Fl_Timeout_Handler cb, void *argp) {
+  elapse_timeouts();
+  missed_timeout_by = 0;
+  repeat_timeout(time, cb, argp);
+}
+
+void Fl_Wayland_Screen_Driver::repeat_timeout(double time, Fl_Timeout_Handler cb, void *argp) {
+  time += missed_timeout_by; if (time < -.05) time = 0;
+  Timeout* t = free_timeout;
+  if (t) {
+      free_timeout = t->next;
+  } else {
+      t = new Timeout;
+  }
+  t->time = time;
+  t->cb = cb;
+  t->arg = argp;
+  // insert-sort the new timeout:
+  Timeout** p = &first_timeout;
+  while (*p && (*p)->time <= time) p = &((*p)->next);
+  t->next = *p;
+  *p = t;
+}
+
+/**
+  Returns true if the timeout exists and has not been called yet.
+*/
+int Fl_Wayland_Screen_Driver::has_timeout(Fl_Timeout_Handler cb, void *argp) {
+  for (Timeout* t = first_timeout; t; t = t->next)
+    if (t->cb == cb && t->arg == argp) return 1;
+  return 0;
+}
+
+/**
+  Removes a timeout callback. It is harmless to remove a timeout
+  callback that no longer exists.
+
+  \note This version removes all matching timeouts, not just the first one.
+        This may change in the future.
+*/
+void Fl_Wayland_Screen_Driver::remove_timeout(Fl_Timeout_Handler cb, void *argp) {
+  for (Timeout** p = &first_timeout; *p;) {
+    Timeout* t = *p;
+    if (t->cb == cb && (t->arg == argp || !argp)) {
+      *p = t->next;
+      t->next = free_timeout;
+      free_timeout = t;
+    } else {
+      p = &(t->next);
+    }
+  }
+}
+
+
+int Fl_Wayland_Screen_Driver::text_display_can_leak() {
+#if USE_XFT
+  return 1;
+#else
+  return 0;
+#endif
+}
+
+
+Fl_RGB_Image *Fl_Wayland_Screen_Driver::read_win_rectangle(int X, int Y, int w, int h, Fl_Window *win,
+                                                           bool ignore, bool *p_ignore) {
+  Window xid = win ? fl_xid(win) : NULL;
+  struct buffer *buffer = win ? xid->buffer : (Fl_Offscreen)Fl_Surface_Device::surface()->driver()->gc();
+  int s = win ? xid->scale : 1; //TODO: check when win is NULL
+  if (s != 1) {
+    X *= s; Y *= s; w *= s; h *= s;
+  }
+  uchar *data = new uchar[w * h * 3];
+  uchar *p = data, *q;
+  for (int j = 0; j < h; j++) {
+    q = buffer->draw_buffer + (j+Y) * buffer->stride + 4 * X;
+    for (int i = 0; i < w; i++) {
+      *p++ = *(q+2); // R
+      *p++ = *(q+1); // G
+      *p++ = *q;     // B
+      q += 4;
+    }
+  }
+  Fl_RGB_Image *rgb = new Fl_RGB_Image(data, w, h, 3);
+  rgb->alloc_array = 1;
+  return rgb;
+}
+
+
+int Fl_Wayland_Screen_Driver::dnd(int unused) {
+  return 0;
+}
+
+void Fl_Wayland_Screen_Driver::offscreen_size(Fl_Offscreen off, int &width, int &height)
+{
+  width = off->width;
+  height = off->data_size / off->stride;
+}
+
+#if USE_XFT
+//NOTICE: returns -1 if x,y is not in any screen
+int Fl_Wayland_Screen_Driver::screen_num_unscaled(int x, int y)
+{
+  int screen = -1;
+  if (num_screens < 0) init();
+
+  for (int i = 0; i < num_screens; i ++) {
+    int sx = screens[i].x_org, sy = screens[i].y_org, sw = screens[i].width, sh = screens[i].height;
+    if ((x >= sx) && (x < (sx+sw)) && (y >= sy) && (y < (sy+sh))) {
+      screen = i;
+      break;
+    }
+  }
+  return screen;
+}
+
+
+// set the desktop's default scaling value
+void Fl_Wayland_Screen_Driver::desktop_scale_factor()
+{
+}
+
+#endif // USE_XFT
+
+void Fl_Wayland_Screen_Driver::set_cursor() {
+  do_set_cursor(seat);
+}
+
+struct wl_cursor *Fl_Wayland_Screen_Driver::default_cursor() {
+  return seat->default_cursor;
+}
+
+void Fl_Wayland_Screen_Driver::default_cursor(struct wl_cursor *cursor) {
+  seat->default_cursor = cursor;
+}
+
+struct wl_cursor *Fl_Wayland_Screen_Driver::cache_cursor(const char *cursor_name) {
+  return wl_cursor_theme_get_cursor(seat->cursor_theme, cursor_name);
+}
+
+void Fl_Wayland_Screen_Driver::reset_cursor() {
+  xc_arrow = xc_ns = xc_wait = xc_insert = xc_hand = xc_help = xc_cross = xc_move = xc_north = xc_south = xc_west = xc_east = xc_we = xc_nesw = xc_nwse = xc_sw = xc_se = xc_ne = xc_nw = NULL;
+}
+
+uint32_t Fl_Wayland_Screen_Driver::get_serial() {
+  return seat->serial;
+}
+
+struct wl_seat*Fl_Wayland_Screen_Driver::get_wl_seat() {
+  return seat->wl_seat;
+}
+
+/*char *Fl_Wayland_Screen_Driver::get_seat_name() {
+  return seat->name;
+}*/
+
+struct xkb_keymap *Fl_Wayland_Screen_Driver::get_xkb_keymap() {
+  return seat->xkb_keymap;
+}
diff --git src/drivers/Wayland/Fl_Wayland_System_Driver.H src/drivers/Wayland/Fl_Wayland_System_Driver.H
new file mode 100644
index 0000000..044e761
--- /dev/null
+++ src/drivers/Wayland/Fl_Wayland_System_Driver.H
@@ -0,0 +1,68 @@
+//
+// Definition of Wayland system driver
+// for the Fast Light Tool Kit (FLTK).
+//
+// Copyright 2010-2021 by Bill Spitzak and others.
+//
+// This library is free software. Distribution and use rights are outlined in
+// the file "COPYING" which should have been included with this file.  If this
+// file is missing or damaged, see the license at:
+//
+//     https://www.fltk.org/COPYING.php
+//
+// Please see the following page on how to report bugs and issues:
+//
+//     https://www.fltk.org/bugs.php
+//
+
+#ifndef FL_WAYLAND_SYSTEM_DRIVER_H
+#define FL_WAYLAND_SYSTEM_DRIVER_H
+
+#include <config.h>
+#include "../Posix/Fl_Posix_System_Driver.H"
+
+class Fl_Wayland_System_Driver : public Fl_Posix_System_Driver {
+public:
+  Fl_Wayland_System_Driver() : Fl_Posix_System_Driver() {
+    // Wayland system driver uses the default key table
+  }
+  virtual void display_arg(const char *arg);
+  virtual int XParseGeometry(const char*, int*, int*, unsigned int*, unsigned int*);
+  virtual int clocale_printf(FILE *output, const char *format, va_list args);
+  // these 2 are in Fl_get_key.cxx
+  virtual int event_key(int k);
+  virtual int get_key(int k);
+  virtual int filename_list(const char *d, dirent ***list,
+                            int (*sort)(struct dirent **, struct dirent **),
+                            char *errmsg=NULL, int errmsg_sz=0);
+  virtual int open_uri(const char *uri, char *msg, int msglen);
+  virtual int use_tooltip_timeout_condition() {return 1;}
+  virtual int file_browser_load_filesystem(Fl_File_Browser *browser, char *filename, int lname, Fl_File_Icon *icon);
+  virtual void newUUID(char *uuidBuffer);
+  virtual char *preference_rootnode(Fl_Preferences *prefs, Fl_Preferences::Root root, const char *vendor,
+                                    const char *application);
+  virtual int preferences_need_protection_check() {return 1;}
+  virtual int utf8locale();
+  // this one is in Fl_own_colormap.cxx
+  virtual void own_colormap();
+  // this one is in Fl_x.cxx
+  virtual const char *filename_name(const char *buf);
+  // this one is in Fl_x.cxx
+  virtual void copy(const char *stuff, int len, int clipboard, const char *type);
+  // this one is in Fl_x.cxx
+  virtual void paste(Fl_Widget &receiver, int clipboard, const char *type);
+  // this one is in Fl_wayland.cxx
+  virtual int clipboard_contains(const char *type);
+  // this one is in Fl_wayland.cxx
+  //virtual void clipboard_notify_change();
+  virtual void add_fd(int fd, int when, Fl_FD_Handler cb, void* = 0);
+  virtual void add_fd(int fd, Fl_FD_Handler cb, void* = 0);
+  virtual void remove_fd(int, int when);
+  virtual void remove_fd(int);
+  virtual void make_transient(void *ptr_gtk, void*, Fl_Window*);
+  virtual int need_menu_handle_part2() {return 0;}
+  
+  char *get_prog_name();
+};
+
+#endif /* FL_X11_SYSTEM_DRIVER_H */
diff --git src/drivers/Wayland/Fl_Wayland_System_Driver.cxx src/drivers/Wayland/Fl_Wayland_System_Driver.cxx
new file mode 100644
index 0000000..b1e74ab
--- /dev/null
+++ src/drivers/Wayland/Fl_Wayland_System_Driver.cxx
@@ -0,0 +1,523 @@
+//
+// Definition of Wayland system driver
+// for the Fast Light Tool Kit (FLTK).
+//
+// Copyright 2010-2021 by Bill Spitzak and others.
+//
+// This library is free software. Distribution and use rights are outlined in
+// the file "COPYING" which should have been included with this file.  If this
+// file is missing or damaged, see the license at:
+//
+//     https://www.fltk.org/COPYING.php
+//
+// Please see the following page on how to report bugs and issues:
+//
+//     https://www.fltk.org/bugs.php
+//
+
+#include "Fl_Wayland_System_Driver.H"
+#include <FL/Fl_File_Browser.H>
+#include <FL/fl_string.h>  // fl_strdup
+#include <FL/platform.H>
+#include "../../flstring.h"
+#include "Fl_Wayland_Screen_Driver.H"
+#include "Fl_Wayland_Window_Driver.H"
+#include "../../../libdecor/build/xdg-shell-client-protocol.h"
+
+#include <locale.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <string.h>     // strerror(errno)
+#include <errno.h>      // errno
+#if HAVE_DLSYM && HAVE_DLFCN_H
+#include <dlfcn.h>   // for dlsym
+#endif
+
+
+#ifndef HAVE_SCANDIR
+extern "C" {
+  int fl_scandir(const char *dirname, struct dirent ***namelist,
+                 int (*select)(struct dirent *),
+                 int (*compar)(struct dirent **, struct dirent **),
+                 char *errmsg, int errmsg_sz);
+}
+#endif
+
+
+/**
+ Creates a driver that manages all system related calls.
+
+ This function must be implemented once for every platform.
+ */
+Fl_System_Driver *Fl_System_Driver::newSystemDriver()
+{
+  return new Fl_Wayland_System_Driver();
+}
+
+
+int Fl_Wayland_System_Driver::clocale_printf(FILE *output, const char *format, va_list args) {
+#if defined(__linux__) && defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 700
+  static locale_t c_locale = newlocale(LC_NUMERIC_MASK, "C", duplocale(LC_GLOBAL_LOCALE));
+  locale_t previous_locale = uselocale(c_locale);
+  int retval = vfprintf(output, format, args);
+  uselocale(previous_locale);
+#else
+  char *saved_locale = setlocale(LC_NUMERIC, NULL);
+  setlocale(LC_NUMERIC, "C");
+  int retval = vfprintf(output, format, args);
+  setlocale(LC_NUMERIC, saved_locale);
+#endif
+  return retval;
+}
+
+
+// Find a program in the path...
+static char *path_find(const char *program, char *filename, int filesize) {
+  const char    *path;                  // Search path
+  char          *ptr,                   // Pointer into filename
+                *end;                   // End of filename buffer
+
+
+  if ((path = fl_getenv("PATH")) == NULL) path = "/bin:/usr/bin";
+
+  for (ptr = filename, end = filename + filesize - 1; *path; path ++) {
+    if (*path == ':') {
+      if (ptr > filename && ptr[-1] != '/' && ptr < end) *ptr++ = '/';
+
+      strlcpy(ptr, program, end - ptr + 1);
+
+      if (!access(filename, X_OK)) return filename;
+
+      ptr = filename;
+    } else if (ptr < end) *ptr++ = *path;
+  }
+
+  if (ptr > filename) {
+    if (ptr[-1] != '/' && ptr < end) *ptr++ = '/';
+
+    strlcpy(ptr, program, end - ptr + 1);
+
+    if (!access(filename, X_OK)) return filename;
+  }
+
+  return 0;
+}
+
+
+int Fl_Wayland_System_Driver::open_uri(const char *uri, char *msg, int msglen)
+{
+  // Run any of several well-known commands to open the URI.
+  //
+  // We give preference to the Portland group's xdg-utils
+  // programs which run the user's preferred web browser, etc.
+  // based on the current desktop environment in use.  We fall
+  // back on older standards and then finally test popular programs
+  // until we find one we can use.
+  //
+  // Note that we specifically do not support the MAILER and
+  // BROWSER environment variables because we have no idea whether
+  // we need to run the listed commands in a terminal program.
+  char  command[FL_PATH_MAX],           // Command to run...
+  *argv[4],                     // Command-line arguments
+  remote[1024];                 // Remote-mode command...
+  const char * const *commands;         // Array of commands to check...
+  int i;
+  static const char * const browsers[] = {
+    "xdg-open", // Portland
+    "htmlview", // Freedesktop.org
+    "firefox",
+    "mozilla",
+    "netscape",
+    "konqueror", // KDE
+    "opera",
+    "hotjava", // Solaris
+    "mosaic",
+    NULL
+  };
+  static const char * const readers[] = {
+    "xdg-email", // Portland
+    "thunderbird",
+    "mozilla",
+    "netscape",
+    "evolution", // GNOME
+    "kmailservice", // KDE
+    NULL
+  };
+  static const char * const managers[] = {
+    "xdg-open", // Portland
+    "fm", // IRIX
+    "dtaction", // CDE
+    "nautilus", // GNOME
+    "konqueror", // KDE
+    NULL
+  };
+
+  // Figure out which commands to check for...
+  if (!strncmp(uri, "file://", 7)) commands = managers;
+  else if (!strncmp(uri, "mailto:";, 7) ||
+           !strncmp(uri, "news:";, 5)) commands = readers;
+  else commands = browsers;
+
+  // Find the command to run...
+  for (i = 0; commands[i]; i ++)
+    if (path_find(commands[i], command, sizeof(command))) break;
+
+  if (!commands[i]) {
+    if (msg) {
+      snprintf(msg, msglen, "No helper application found for \"%s\"", uri);
+    }
+
+    return 0;
+  }
+
+  // Handle command-specific arguments...
+  argv[0] = (char *)commands[i];
+
+  if (!strcmp(commands[i], "firefox") ||
+      !strcmp(commands[i], "mozilla") ||
+      !strcmp(commands[i], "netscape") ||
+      !strcmp(commands[i], "thunderbird")) {
+    // program -remote openURL(uri)
+    snprintf(remote, sizeof(remote), "openURL(%s)", uri);
+
+    argv[1] = (char *)"-remote";
+    argv[2] = remote;
+    argv[3] = 0;
+  } else if (!strcmp(commands[i], "dtaction")) {
+    // dtaction open uri
+    argv[1] = (char *)"open";
+    argv[2] = (char *)uri;
+    argv[3] = 0;
+  } else {
+    // program uri
+    argv[1] = (char *)uri;
+    argv[2] = 0;
+  }
+
+  if (msg) {
+    strlcpy(msg, argv[0], msglen);
+
+    for (i = 1; argv[i]; i ++) {
+      strlcat(msg, " ", msglen);
+      strlcat(msg, argv[i], msglen);
+    }
+  }
+
+  return run_program(command, argv, msg, msglen) != 0;
+}
+
+
+int Fl_Wayland_System_Driver::file_browser_load_filesystem(Fl_File_Browser *browser, char *filename, int lname, Fl_File_Icon *icon)
+{
+  int num_files = 0;
+  //
+  // UNIX code uses /etc/fstab or similar...
+  //
+  FILE  *mtab;          // /etc/mtab or /etc/mnttab file
+  char  line[FL_PATH_MAX];      // Input line
+
+  // Every Unix has a root filesystem '/'.
+  // This ensures that the user don't get an empty
+  // window after requesting filesystem list.
+  browser->add("/", icon);
+  num_files ++;
+
+  //
+  // Open the file that contains a list of mounted filesystems...
+  //
+  // Note: this misses automounted filesystems on FreeBSD if absent from /etc/fstab
+  //
+
+  mtab = fopen("/etc/mnttab", "r");     // Fairly standard
+  if (mtab == NULL)
+    mtab = fopen("/etc/mtab", "r");     // More standard
+  if (mtab == NULL)
+    mtab = fopen("/etc/fstab", "r");    // Otherwise fallback to full list
+  if (mtab == NULL)
+    mtab = fopen("/etc/vfstab", "r");   // Alternate full list file
+
+  if (mtab != NULL)
+  {
+    while (fgets(line, sizeof(line), mtab) != NULL)
+    {
+      if (line[0] == '#' || line[0] == '\n')
+        continue;
+      if (sscanf(line, "%*s%4095s", filename) != 1)
+        continue;
+      if (strcmp("/", filename) == 0)
+        continue; // "/" was added before
+
+      // Add a trailing slash (except for the root filesystem)
+      strlcat(filename, "/", lname);
+
+      //        printf("Fl_File_Browser::load() - adding \"%s\" to list...\n", filename);
+      browser->add(filename, icon);
+      num_files ++;
+    }
+
+    fclose(mtab);
+  }
+  return num_files;
+}
+
+void Fl_Wayland_System_Driver::newUUID(char *uuidBuffer)
+{
+  unsigned char b[16];
+#if HAVE_DLSYM && HAVE_DLFCN_H
+  typedef void (*gener_f_type)(uchar*);
+  static bool looked_for_uuid_generate = false;
+  static gener_f_type uuid_generate_f = NULL;
+  if (!looked_for_uuid_generate) {
+    looked_for_uuid_generate = true;
+    uuid_generate_f = (gener_f_type)dlopen_or_dlsym("libuuid", "uuid_generate");
+  }
+  if (uuid_generate_f) {
+    uuid_generate_f(b);
+  } else
+#endif
+  {
+    time_t t = time(0);                   // first 4 byte
+    b[0] = (unsigned char)t;
+    b[1] = (unsigned char)(t>>8);
+    b[2] = (unsigned char)(t>>16);
+    b[3] = (unsigned char)(t>>24);
+    int r = rand();                       // four more bytes
+    b[4] = (unsigned char)r;
+    b[5] = (unsigned char)(r>>8);
+    b[6] = (unsigned char)(r>>16);
+    b[7] = (unsigned char)(r>>24);
+    unsigned long a = (unsigned long)&t;  // four more bytes
+    b[8] = (unsigned char)a;
+    b[9] = (unsigned char)(a>>8);
+    b[10] = (unsigned char)(a>>16);
+    b[11] = (unsigned char)(a>>24);
+    // Now we try to find 4 more "random" bytes. We extract the
+    // lower 4 bytes from the address of t - it is created on the
+    // stack so *might* be in a different place each time...
+    // This is now done via a union to make it compile OK on 64-bit systems.
+    union { void *pv; unsigned char a[sizeof(void*)]; } v;
+    v.pv = (void *)(&t);
+    // NOTE: May need to handle big- or little-endian systems here
+# if WORDS_BIGENDIAN
+    b[8] = v.a[sizeof(void*) - 1];
+    b[9] = v.a[sizeof(void*) - 2];
+    b[10] = v.a[sizeof(void*) - 3];
+    b[11] = v.a[sizeof(void*) - 4];
+# else // data ordered for a little-endian system
+    b[8] = v.a[0];
+    b[9] = v.a[1];
+    b[10] = v.a[2];
+    b[11] = v.a[3];
+# endif
+    char name[80];                        // last four bytes
+    gethostname(name, 79);
+    memcpy(b+12, name, 4);
+  }
+  sprintf(uuidBuffer, "%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X",
+          b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
+          b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]);
+}
+
+char *Fl_Wayland_System_Driver::preference_rootnode(Fl_Preferences *prefs, Fl_Preferences::Root root, const char *vendor,
+                                                const char *application)
+{
+  static char *filename = 0L;
+  if (!filename) filename = (char*)::calloc(1, FL_PATH_MAX);
+  const char *e;
+  switch (root&Fl_Preferences::ROOT_MASK) {
+    case Fl_Preferences::USER:
+      e = getenv("HOME");
+      // make sure that $HOME is set to an existing directory
+      if ( (e==0L) || (e[0]==0) || (::access(e, F_OK)==-1) ) {
+        struct passwd *pw = getpwuid(getuid());
+        e = pw->pw_dir;
+      }
+      if ( (e==0L) || (e[0]==0) || (::access(e, F_OK)==-1) ) {
+        return 0L;
+      } else {
+        strlcpy(filename, e, FL_PATH_MAX);
+        if (filename[strlen(filename)-1] != '/')
+          strlcat(filename, "/", FL_PATH_MAX);
+        strlcat(filename, ".fltk/", FL_PATH_MAX);
+      }
+      break;
+    case Fl_Preferences::SYSTEM:
+      strcpy(filename, "/etc/fltk/");
+      break;
+  }
+
+  // Make sure that the parameters are not NULL
+  if ( (vendor==0L) || (vendor[0]==0) )
+    vendor = "unknown";
+  if ( (application==0L) || (application[0]==0) )
+    application = "unknown";
+
+  snprintf(filename + strlen(filename), FL_PATH_MAX - strlen(filename),
+           "%s/%s.prefs", vendor, application);
+  return filename;
+}
+
+void Fl_Wayland_System_Driver::display_arg(const char *arg) {
+  Fl::display(arg);
+}
+
+int Fl_Wayland_System_Driver::XParseGeometry(const char* string, int* x, int* y,
+                                         unsigned int* width, unsigned int* height) {
+  return 0;//::XParseGeometry(string, x, y, width, height);
+}
+
+//
+// Needs some docs
+// Returns -1 on error, errmsg will contain OS error if non-NULL.
+//
+int Fl_Wayland_System_Driver::filename_list(const char *d,
+                                        dirent ***list,
+                                        int (*sort)(struct dirent **, struct dirent **),
+                                        char *errmsg, int errmsg_sz) {
+  int dirlen;
+  char *dirloc;
+
+  if (errmsg && errmsg_sz>0) errmsg[0] = '\0';
+
+  // Assume that locale encoding is no less dense than UTF-8
+  dirlen = strlen(d);
+  dirloc = (char *)malloc(dirlen + 1);
+  fl_utf8to_mb(d, dirlen, dirloc, dirlen + 1);
+
+#ifndef HAVE_SCANDIR
+  // This version is when we define our own scandir. Note it updates errmsg on errors.
+  int n = fl_scandir(dirloc, list, 0, sort, errmsg, errmsg_sz);
+#elif defined(HAVE_SCANDIR_POSIX)
+  // POSIX (2008) defines the comparison function like this:
+  int n = scandir(dirloc, list, 0, (int(*)(const dirent **, const dirent **))sort);
+#else
+  // The vast majority of UNIX systems want the sort function to have this
+  // prototype, most likely so that it can be passed to qsort without any
+  // changes:
+  int n = scandir(dirloc, list, 0, (int(*)(const void*,const void*))sort);
+#endif
+
+  free(dirloc);
+
+  if (n==-1) {
+    // Don't write to errmsg if FLTK's fl_scandir() already set it.
+    // If OS's scandir() was used (HAVE_SCANDIR), we return its error in errmsg here..
+#ifdef HAVE_SCANDIR
+    if (errmsg) fl_snprintf(errmsg, errmsg_sz, "%s", strerror(errno));
+#endif
+    return -1;
+  }
+
+  // convert every filename to UTF-8, and append a '/' to all
+  // filenames that are directories
+  int i;
+  char *fullname = (char*)malloc(dirlen+FL_PATH_MAX+3); // Add enough extra for two /'s and a nul
+  // Use memcpy for speed since we already know the length of the string...
+  memcpy(fullname, d, dirlen+1);
+
+  char *name = fullname + dirlen;
+  if (name!=fullname && name[-1]!='/')
+    *name++ = '/';
+
+  for (i=0; i<n; i++) {
+    int newlen;
+    dirent *de = (*list)[i];
+    int len = strlen(de->d_name);
+    newlen = fl_utf8from_mb(NULL, 0, de->d_name, len);
+    dirent *newde = (dirent*)malloc(de->d_name - (char*)de + newlen + 2); // Add space for a / and a nul
+
+    // Conversion to UTF-8
+    memcpy(newde, de, de->d_name - (char*)de);
+    fl_utf8from_mb(newde->d_name, newlen + 1, de->d_name, len);
+
+    // Check if dir (checks done on "old" name as we need to interact with
+    // the underlying OS)
+    if (de->d_name[len-1]!='/' && len<=FL_PATH_MAX) {
+      // Use memcpy for speed since we already know the length of the string...
+      memcpy(name, de->d_name, len+1);
+      if (fl_filename_isdir(fullname)) {
+        char *dst = newde->d_name + newlen;
+        *dst++ = '/';
+        *dst = 0;
+      }
+    }
+
+    free(de);
+    (*list)[i] = newde;
+  }
+  free(fullname);
+
+  return n;
+}
+
+int Fl_Wayland_System_Driver::utf8locale() {
+  static int ret = 2;
+  if (ret == 2) {
+    char* s;
+    ret = 1; /* assume UTF-8 if no locale */
+    if (((s = getenv("LC_CTYPE")) && *s) ||
+        ((s = getenv("LC_ALL"))   && *s) ||
+        ((s = getenv("LANG"))     && *s)) {
+      ret = (strstr(s,"utf") || strstr(s,"UTF"));
+    }
+  }
+  return ret;
+}
+
+
+void Fl_Wayland_System_Driver::own_colormap() {
+  fl_open_display();
+}
+
+
+void Fl_Wayland_System_Driver::make_transient(void *ptr_gtk, void *gtk_window, Fl_Window *win) {
+  //TODO
+  typedef struct _GdkDrawable GdkWindow;
+  typedef struct _GtkWidget GtkWidget;
+
+  typedef GdkWindow* (*XX_gtk_widget_get_window_type)(GtkWidget *);
+  static XX_gtk_widget_get_window_type fl_gtk_widget_get_window = NULL;
+
+  typedef struct wl_surface *(*XX_gdk_wayland_window_get_wl_surface_type)(GdkWindow *);
+  static XX_gdk_wayland_window_get_wl_surface_type fl_gdk_wayland_window_get_wl_surface = NULL;
+
+  if (!fl_gtk_widget_get_window) {
+    fl_gtk_widget_get_window = (XX_gtk_widget_get_window_type)dlsym(ptr_gtk, "gtk_widget_get_window");
+    if (!fl_gtk_widget_get_window) return;
+  }
+
+  GdkWindow* gdkw = fl_gtk_widget_get_window((GtkWidget*)gtk_window);
+
+  if (!fl_gdk_wayland_window_get_wl_surface) {
+    fl_gdk_wayland_window_get_wl_surface = (XX_gdk_wayland_window_get_wl_surface_type)dlsym(ptr_gtk, "gdk_wayland_window_get_wl_surface");
+  }
+  struct wl_surface *wld_surf = fl_gdk_wayland_window_get_wl_surface(gdkw);
+fprintf(stderr, "wld_surf=%p \n", wld_surf);
+  // but what next?
+/* not good:
+Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver();
+struct xdg_surface *xdgs = xdg_wm_base_get_xdg_surface(scr_driver->xdg_wm_base, wld_surf);
+struct xdg_toplevel *top = xdg_surface_get_toplevel(xdgs);
+fprintf(stderr, "wld_surf=%p xdgs=%p top=%p \n", wld_surf, xdgs, top);
+xdg_toplevel_set_parent(top, fl_xid(win)->xdg_toplevel);
+*/
+}
+
+
+char *Fl_Wayland_System_Driver::get_prog_name() {
+  pid_t pid = getpid();
+  char fname[100];
+  sprintf(fname, "/proc/%u/cmdline", pid);
+  FILE *in = fopen(fname, "r");
+  if (in) {
+    static char line[200];
+    fgets(line, sizeof(line), in);
+    fclose(in);
+    char *p = strrchr(line, '/'); if (!p) p = line; else p++;
+    return p;
+  }
+  return NULL;
+}
+
diff --git src/drivers/Wayland/Fl_Wayland_Window_Driver.H src/drivers/Wayland/Fl_Wayland_Window_Driver.H
new file mode 100644
index 0000000..b4e63ff
--- /dev/null
+++ src/drivers/Wayland/Fl_Wayland_Window_Driver.H
@@ -0,0 +1,168 @@
+//
+// Definition of Wayland window driver for the Fast Light Tool Kit (FLTK).
+//
+// Copyright 2010-2021 by Bill Spitzak and others.
+//
+// This library is free software. Distribution and use rights are outlined in
+// the file "COPYING" which should have been included with this file.  If this
+// file is missing or damaged, see the license at:
+//
+//     https://www.fltk.org/COPYING.php
+//
+// Please see the following page on how to report bugs and issues:
+//
+//     https://www.fltk.org/bugs.php
+//
+
+/**
+ \file Fl_Wayland_Window_Driver.H
+ \brief Definition of Wayland window driver.
+ */
+
+#ifndef FL_WAYLAND_WINDOW_DRIVER_H
+#define FL_WAYLAND_WINDOW_DRIVER_H
+
+#include "../../Fl_Window_Driver.H"
+#include <FL/Fl_Plugin.H>
+#include "Fl_Wayland_Screen_Driver.H"
+#include <wayland-client.h>
+
+class Fl_Bitmap;
+
+/*
+ Move everything here that manages the native window interface.
+
+ There is one window driver for each Fl_Window. Window drivers manage window
+ actions such as resizing, events, decoration, fullscreen modes, etc. . All
+ drawing and rendering is managed by the Surface device and the associated
+ graphics driver.
+
+ - window specific event handling
+ - window types and styles, depth, etc.
+ - decorations
+ */
+
+typedef struct _cairo_pattern cairo_pattern_t;
+
+struct Fl_Window_Driver::shape_data_type {
+  int lw_; ///<  width of shape image
+  int lh_; ///<  height of shape image
+  Fl_Image* shape_; ///<  shape image
+  cairo_pattern_t *mask_pattern_;
+};
+
+struct wld_window {
+  struct wl_surface *wl_surface;
+  struct wl_surface *gl_wl_surface;
+  struct wl_subsurface *subsurface;
+  struct buffer *buffer;
+  struct libdecor_frame *frame;
+  struct xdg_surface *xdg_surface;
+  struct xdg_toplevel *xdg_toplevel;
+  struct xdg_popup *xdg_popup;
+  int configured_width;
+  int configured_height;
+  int decorated_height;
+  struct wl_list outputs;
+  int scale;
+  Fl_Window *fl_win;
+};
+
+class Fl_Wayland_Window_Driver : public Fl_Window_Driver
+{
+  friend class Fl_X;
+private:
+  static bool in_flush; // useful for progressive window drawing
+public:
+  static bool in_handle_configure; // distinguish OS and user window resize
+  struct window_output {
+    Fl_Wayland_Screen_Driver::output* output;
+    struct wl_list link;
+  };
+
+  struct icon_data {
+    const void *legacy_icon;
+    Fl_RGB_Image **icons;
+    int count;
+  } *icon_;
+#if  USE_XFT
+  // --- support for screen-specific scaling factors
+  struct type_for_resize_window_between_screens {
+    int screen;
+    bool busy;
+  };
+  static type_for_resize_window_between_screens data_for_resize_window_between_screens_;
+  int screen_num_;
+  void screen_num(int n) { screen_num_ = n; }
+#endif // USE_XFT
+  void decorated_win_size(int &w, int &h);
+  void shape_bitmap_(Fl_Image* b);
+  void shape_alpha_(Fl_Image* img, int offset);
+  void update_scale();
+
+public:
+  Fl_Wayland_Window_Driver(Fl_Window*);
+  virtual ~Fl_Wayland_Window_Driver();
+  static void redraw(struct wld_window *window);
+
+  static inline Fl_Wayland_Window_Driver* driver(const Fl_Window *w) {return (Fl_Wayland_Window_Driver*)Fl_Window_Driver::driver(w);}
+#if  USE_XFT
+  virtual int screen_num();
+  static void resize_after_screen_change(void *data);
+#endif // USE_XFT
+
+  // --- window data
+  virtual int decorated_w();
+  virtual int decorated_h();
+  virtual const Fl_Image* shape();
+
+  // --- window management
+  virtual Fl_X *makeWindow();
+  virtual void take_focus();
+  virtual void flush();
+  virtual void flush_overlay();
+  virtual void flush_menu();
+  virtual void erase_menu();
+  virtual void draw_end();
+  virtual void make_current();
+  virtual void show();
+  virtual void show_menu();
+  virtual void resize(int X,int Y,int W,int H);
+  virtual void label(const char *name, const char *mininame);
+  virtual void destroy_double_buffer();
+  virtual void hide();
+  virtual void map();
+  virtual void unmap();
+  virtual void fullscreen_on();
+  virtual void fullscreen_off(int X, int Y, int W, int H);
+ // virtual void use_border();
+  virtual void size_range();
+  virtual void iconize();
+  virtual void decoration_sizes(int *top, int *left,  int *right, int *bottom);
+  virtual void show_with_args_begin();
+  virtual void show_with_args_end(int argc, char **argv);
+  // --- window cursor stuff
+  virtual int set_cursor(Fl_Cursor);
+  virtual int set_cursor(const Fl_RGB_Image*, int, int);
+
+  virtual void shape(const Fl_Image* img);
+  virtual void icons(const Fl_RGB_Image *icons[], int count);
+  virtual const void *icon() const;
+  virtual void icon(const void * ic);
+  virtual void free_icons();
+  void set_icons(); // driver-internal support function
+  virtual void capture_titlebar_and_borders(Fl_RGB_Image*& top, Fl_RGB_Image*& left, Fl_RGB_Image*& bottom, Fl_RGB_Image*& right);
+  virtual int can_do_overlay();
+  virtual void redraw_overlay();
+  virtual int scroll(int src_x, int src_y, int src_w, int src_h, int dest_x, int dest_y, void (*draw_area)(void*, int,int,int,int), void* data);
+};
+
+class Fl_Wayland_Plugin : public Fl_Plugin {
+public:
+  Fl_Wayland_Plugin(const char *pluginName)  : Fl_Plugin(klass(), pluginName) { }
+  virtual const char *klass() { return "fltk:wayland"; }
+  virtual const char *name() = 0;
+  virtual void do_swap(Fl_Window*) = 0;
+};
+
+#endif // FL_WAYLAND_WINDOW_DRIVER_H
diff --git src/drivers/Wayland/Fl_Wayland_Window_Driver.cxx src/drivers/Wayland/Fl_Wayland_Window_Driver.cxx
new file mode 100644
index 0000000..99b8a90
--- /dev/null
+++ src/drivers/Wayland/Fl_Wayland_Window_Driver.cxx
@@ -0,0 +1,1150 @@
+//
+// Implementation of the Wayland window driver.
+//
+// Copyright 1998-2021 by Bill Spitzak and others.
+//
+// This library is free software. Distribution and use rights are outlined in
+// the file "COPYING" which should have been included with this file.  If this
+// file is missing or damaged, see the license at:
+//
+//     https://www.fltk.org/COPYING.php
+//
+// Please see the following page on how to report bugs and issues:
+//
+//     https://www.fltk.org/bugs.php
+//
+
+#include <config.h>
+#include <FL/platform.H>
+#include "Fl_Wayland_Window_Driver.H"
+#include "Fl_Wayland_Screen_Driver.H"
+#include "Fl_Wayland_Graphics_Driver.H"
+#include "Fl_Wayland_System_Driver.H"
+#include "../../../libdecor/src/libdecor.h"
+#include "../../../libdecor/build/xdg-shell-client-protocol.h"
+#include <pango/pangocairo.h>
+#include <FL/Fl_Overlay_Window.H>
+#include <FL/Fl_Menu_Window.H>
+#include <FL/Fl_Tooltip.H>
+#include <FL/fl_draw.H>
+#include <FL/fl_ask.H>
+#include <FL/Fl.H>
+#include <FL/Fl_Image_Surface.H>
+#include <string.h>
+#include <sys/mman.h>
+
+#define fl_max(a,b) ((a) > (b) ? (a) : (b))
+
+Window fl_window;
+
+
+void Fl_Wayland_Window_Driver::destroy_double_buffer() {
+  if (pWindow->as_overlay_window()) fl_delete_offscreen(other_xid);
+  other_xid = 0;
+}
+
+Fl_Window_Driver *Fl_Window_Driver::newWindowDriver(Fl_Window *w)
+{
+  return new Fl_Wayland_Window_Driver(w);
+}
+
+
+Fl_Wayland_Window_Driver::Fl_Wayland_Window_Driver(Fl_Window *win) : Fl_Window_Driver(win)
+{
+  icon_ = new icon_data;
+  memset(icon_, 0, sizeof(icon_data));
+#if USE_XFT
+  screen_num_ = -1;
+#endif
+}
+
+
+Fl_Wayland_Window_Driver::~Fl_Wayland_Window_Driver()
+{
+  if (shape_data_) {
+    cairo_surface_t *surface;
+    cairo_pattern_get_surface(shape_data_->mask_pattern_, &surface);
+    cairo_pattern_destroy(shape_data_->mask_pattern_);
+    uchar *data = cairo_image_surface_get_data(surface);
+    cairo_surface_destroy(surface);
+    delete[] data;
+    delete shape_data_;
+  }
+  delete icon_;
+}
+
+
+// --- private
+
+void Fl_Wayland_Window_Driver::decorated_win_size(int &w, int &h)
+{
+  Fl_Window *win = pWindow;
+  w = win->w();
+  h = win->h();
+  if (!win->shown() || win->parent() || !win->border() || !win->visible()) return;
+  h = fl_xid(win)->decorated_height;
+}
+
+
+// --- window data
+
+int Fl_Wayland_Window_Driver::decorated_h()
+{
+  int w, h;
+  decorated_win_size(w, h);
+  return h;
+}
+
+int Fl_Wayland_Window_Driver::decorated_w()
+{
+  int w, h;
+  decorated_win_size(w, h);
+  return w;
+}
+
+
+void Fl_Wayland_Window_Driver::take_focus()
+{
+  Window w = fl_xid(pWindow);
+  if (w) {
+    Fl_Window *old_first = Fl::first_window();
+    Window first_xid = (old_first ? fl_xid(old_first->top_window()) : NULL);
+    if (first_xid && first_xid != w && w->xdg_toplevel) {
+      // this will move the target window to the front
+      xdg_toplevel_set_parent(w->xdg_toplevel, first_xid->xdg_toplevel);
+      // this will remove the parent-child relationship
+      old_first->wait_for_expose();
+      xdg_toplevel_set_parent(w->xdg_toplevel, NULL);
+    }
+    // this sets the first window
+    fl_find(w);
+  }
+}
+
+
+void Fl_Wayland_Window_Driver::flush_overlay()
+{
+  if (!shown()) return;
+  Fl_Overlay_Window *oWindow = pWindow->as_overlay_window();
+  int erase_overlay = (pWindow->damage()&FL_DAMAGE_OVERLAY) | (overlay() == oWindow);
+  pWindow->clear_damage((uchar)(pWindow->damage()&~FL_DAMAGE_OVERLAY));
+  pWindow->make_current();
+  if (!other_xid) {
+    other_xid = fl_create_offscreen(oWindow->w(), oWindow->h());
+    oWindow->clear_damage(FL_DAMAGE_ALL);
+  }
+  if (oWindow->damage() & ~FL_DAMAGE_EXPOSE) {
+    Fl_X *myi = Fl_X::i(pWindow);
+    fl_clip_region(myi->region); myi->region = 0;
+    fl_begin_offscreen(other_xid);
+    draw();
+    fl_end_offscreen();
+  }
+  if (erase_overlay) fl_clip_region(0);
+  if (other_xid) {
+    fl_copy_offscreen(0, 0, oWindow->w(), oWindow->h(), other_xid, 0, 0);
+  }
+  if (overlay() == oWindow) oWindow->draw_overlay();
+  Window xid = fl_xid(pWindow);
+  wl_surface_damage_buffer(xid->wl_surface, 0, 0, pWindow->w() * xid->scale, pWindow->h() * xid->scale);
+}
+
+
+const Fl_Image* Fl_Wayland_Window_Driver::shape() {
+  return shape_data_ ? shape_data_->shape_ : NULL;
+}
+
+void Fl_Wayland_Window_Driver::shape_bitmap_(Fl_Image* b) { // needs testing
+  // complement the bits of the Fl_Bitmap and control its stride too
+  int i, j, w = b->w(), h = b->h();
+  int bytesperrow = cairo_format_stride_for_width(CAIRO_FORMAT_A1, w);
+  uchar* bits = new uchar[h * bytesperrow];
+  const uchar *q = ((Fl_Bitmap*)b)->array;
+  for (i = 0; i < h; i++) {
+    uchar *p = bits + i * bytesperrow;
+    for (j = 0; j < w; j++) {
+      *p++ = ~*q++;
+    }
+  }
+  cairo_surface_t *mask_surf = cairo_image_surface_create_for_data(bits, CAIRO_FORMAT_A1, w, h, bytesperrow);
+  shape_data_->mask_pattern_ = cairo_pattern_create_for_surface(mask_surf);
+  shape_data_->shape_ = b;
+  shape_data_->lw_ = w;
+  shape_data_->lh_ = h;
+}
+
+void Fl_Wayland_Window_Driver::shape_alpha_(Fl_Image* img, int offset) {
+  int i, j, d = img->d(), w = img->w(), h = img->h();
+  int bytesperrow = cairo_format_stride_for_width(CAIRO_FORMAT_A1, w);
+  unsigned u;
+  uchar byte, onebit;
+  // build a CAIRO_FORMAT_A1 surface covering the non-fully transparent/black part of the image
+  uchar* bits = new uchar[h*bytesperrow]; // to store the surface data
+  const uchar* alpha = (const uchar*)*img->data() + offset; // points to alpha value of rgba pixels
+  for (i = 0; i < h; i++) {
+    uchar *p = (uchar*)bits + i * bytesperrow;
+    byte = 0;
+    onebit = 1;
+    for (j = 0; j < w; j++) {
+      if (d == 3) {
+        u = *alpha;
+        u += *(alpha+1);
+        u += *(alpha+2);
+      }
+      else u = *alpha;
+      if (u > 0) { // if the pixel is not fully transparent/black
+        byte |= onebit; // turn on the corresponding bit of the bitmap
+      }
+      onebit = onebit << 1; // move the single set bit one position to the left
+      if (onebit == 0 || j == w-1) {
+        onebit = 1;
+        *p++ = ~byte; // store in bitmap one pack of bits, complemented
+        byte = 0;
+      }
+      alpha += d; // point to alpha value of next img pixel
+    }
+  }
+  cairo_surface_t *mask_surf = cairo_image_surface_create_for_data(bits, CAIRO_FORMAT_A1, w, h, bytesperrow);
+  shape_data_->mask_pattern_ = cairo_pattern_create_for_surface(mask_surf);
+  shape_data_->shape_ = img;
+  shape_data_->lw_ = w;
+  shape_data_->lh_ = h;
+}
+
+void Fl_Wayland_Window_Driver::shape(const Fl_Image* img) {
+  if (shape_data_) {
+    if (shape_data_->mask_pattern_) {
+      cairo_surface_t *surface;
+      cairo_pattern_get_surface(shape_data_->mask_pattern_, &surface);
+      cairo_pattern_destroy(shape_data_->mask_pattern_);
+      uchar *data = cairo_image_surface_get_data(surface);
+      cairo_surface_destroy(surface);
+      delete[] data;
+    }
+  }
+  else {
+    shape_data_ = new shape_data_type;
+  }
+  memset(shape_data_, 0, sizeof(shape_data_type));
+  pWindow->border(false);
+  int d = img->d();
+  if (d && img->count() >= 2) {
+    shape_pixmap_((Fl_Image*)img);
+    shape_data_->shape_ = (Fl_Image*)img;
+  }
+  else if (d == 0) shape_bitmap_((Fl_Image*)img);
+  else if (d == 2 || d == 4) shape_alpha_((Fl_Image*)img, d - 1);
+  else if ((d == 1 || d == 3) && img->count() == 1) shape_alpha_((Fl_Image*)img, 0);
+}
+
+void Fl_Wayland_Window_Driver::draw_end()
+{
+  if (shape_data_ && shape_data_->mask_pattern_) {
+    Fl_Wayland_Graphics_Driver *gr_dr = (Fl_Wayland_Graphics_Driver*)fl_graphics_driver;
+    cairo_t *cr = gr_dr->cr();
+    cairo_matrix_t matrix;
+    cairo_matrix_init_scale(&matrix, double(shape_data_->lw_)/pWindow->w() , double(shape_data_->lh_)/pWindow->h());
+    cairo_pattern_set_matrix(shape_data_->mask_pattern_, &matrix);
+    cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
+    cairo_mask(cr, shape_data_->mask_pattern_);
+    cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
+  }
+}
+
+
+void Fl_Wayland_Window_Driver::icons(const Fl_RGB_Image *icons[], int count) {
+  free_icons();
+
+  if (count > 0) {
+    icon_->icons = new Fl_RGB_Image*[count];
+    icon_->count = count;
+    // FIXME: Fl_RGB_Image lacks const modifiers on methods
+    for (int i = 0;i < count;i++) {
+      icon_->icons[i] = (Fl_RGB_Image*)((Fl_RGB_Image*)icons[i])->copy();
+      icon_->icons[i]->normalize();
+    }
+  }
+
+  if (Fl_X::i(pWindow))
+    set_icons();
+}
+
+const void *Fl_Wayland_Window_Driver::icon() const {
+  return icon_->legacy_icon;
+}
+
+void Fl_Wayland_Window_Driver::icon(const void * ic) {
+  free_icons();
+  icon_->legacy_icon = ic;
+}
+
+void Fl_Wayland_Window_Driver::free_icons() {
+  int i;
+  icon_->legacy_icon = 0L;
+  if (icon_->icons) {
+    for (i = 0;i < icon_->count;i++)
+      delete icon_->icons[i];
+    delete [] icon_->icons;
+    icon_->icons = 0L;
+  }
+  icon_->count = 0;
+}
+
+
+/* Returns images of the captures of the window title-bar, and the left, bottom and right window borders
+ (or NULL if a particular border is absent).
+ Returned images can be deleted after use. Their depth and size may be platform-dependent.
+ The top and bottom images extend from left of the left border to right of the right border.
+ */
+void Fl_Wayland_Window_Driver::capture_titlebar_and_borders(Fl_RGB_Image*& top, Fl_RGB_Image*& left, Fl_RGB_Image*& bottom, Fl_RGB_Image*& right)
+{
+  top = left = bottom = right = NULL;
+  if (pWindow->decorated_h() == h()) return;
+  int htop = pWindow->decorated_h() - pWindow->h();
+// reproduce the target window's titlebar
+  Fl_Image_Surface *surf = new Fl_Image_Surface(pWindow->w(), htop, 1);
+  Fl_Surface_Device::push_current(surf);
+  fl_color(FL_BLACK);
+  fl_rectf(0, 0, pWindow->w(), htop);
+  fl_color(FL_WHITE);
+  Fl::set_font(FL_SCREEN_BOLD, "System-ui Bold");
+  fl_font(FL_SCREEN_BOLD, 17);
+  double w = fl_width(pWindow->label());
+  fl_draw(pWindow->label(), pWindow->w()/2 - w/2, htop - fl_descent() - 4);
+  int X = pWindow->w()-1.1*htop;
+  fl_line(X, htop-5, X+htop-10, 5);
+  fl_line(X,5, X+htop-10,htop-5);
+  X -= 1.2*htop;
+  if (!pWindow->resizable()) fl_color(fl_gray_ramp(4));
+  fl_rect(X, 5, htop-10,htop-10);
+  fl_color(FL_WHITE);
+  X -= 1.2*htop;
+  fl_xyline(X, htop-5, X+htop-10);
+  top = surf->image();
+  Fl_Surface_Device::pop_current();
+  delete surf;
+  top->scale(pWindow->w(), htop);
+}
+
+
+// make drawing go into this window (called by subclass flush() impl.)
+void Fl_Wayland_Window_Driver::make_current() {
+  if (!shown()) {
+    fl_alert("Fl_Window::make_current(), but window is not shown().");
+    Fl::fatal("Fl_Window::make_current(), but window is not shown().");
+  }
+  
+  struct wld_window *window = fl_xid(pWindow);
+  // to support progressive drawing
+  if ( (!Fl_Wayland_Window_Driver::in_flush) && window && window->buffer && window->buffer->draw_buffer_needs_commit) {
+    while (! window->buffer->wl_buffer_ready) {
+      wl_display_dispatch(fl_display);
+    }
+    if (window->buffer->draw_buffer_needs_commit) {
+      wl_surface_damage_buffer(window->wl_surface, 0, 0, pWindow->w() * window->scale, pWindow->h() * window->scale);
+      Fl_Wayland_Graphics_Driver::buffer_commit(window);
+    }
+  }
+
+  fl_graphics_driver->clip_region(0);
+  //((Fl_Cairo_Graphics_Driver*)fl_graphics_driver)->scale(Fl::screen_driver()->scale(screen_num()));
+  
+  fl_window = window;
+  if (!window->buffer) window->buffer = Fl_Wayland_Graphics_Driver::create_shm_buffer(
+          pWindow->w() * window->scale, pWindow->h() * window->scale, WL_SHM_FORMAT_ARGB8888, window);
+  ((Fl_Wayland_Graphics_Driver*)fl_graphics_driver)->activate(window->buffer, window->scale);
+  window->buffer->draw_buffer_needs_commit = true;
+
+#ifdef FLTK_USE_CAIRO
+  // update the cairo_t context
+  if (Fl::cairo_autolink_context()) Fl::cairo_make_current(pWindow);
+#endif
+}
+
+
+void Fl_Wayland_Window_Driver::flush() {
+  if (!pWindow->damage()) return;
+  if (pWindow->as_gl_window()) {
+    Fl_Window_Driver::flush();
+    static Fl_Wayland_Plugin *plugin = NULL;
+    if (!plugin) {
+      Fl_Plugin_Manager pm("fltk:wayland");
+      plugin = (Fl_Wayland_Plugin*)pm.plugin("gl_overlay.wayland.fltk.org");
+    }
+    if (plugin) plugin->do_swap(pWindow); // useful only for GL win with overlay
+    return;
+  }
+  struct wld_window *window = fl_xid(pWindow);
+  if (!window || !window->configured_width) return;
+  
+  Fl_X *i = Fl_X::i(pWindow);
+  Fl_Region r = i->region;
+  if (r && window->buffer && window->buffer->wl_buffer_ready) {
+    for (int i = 0; i < r->count; i++) {
+      int left = r->rects[i].x * window->scale;
+      int top = r->rects[i].y * window->scale;
+      int width = r->rects[i].width * window->scale;
+      int height = r->rects[i].height * window->scale;
+      wl_surface_damage_buffer(window->wl_surface, left, top, width, height);
+//fprintf(stderr, "damage %dx%d %dx%d\n", left, top, width, height);
+    }
+  } else {
+    wl_surface_damage_buffer(window->wl_surface, 0, 0, pWindow->w() * window->scale, pWindow->h() * window->scale);
+//fprintf(stderr, "damage 0x0 %dx%d\n", pWindow->w() * window->scale, pWindow->h() * window->scale);
+  }
+
+  Fl_Wayland_Window_Driver::in_flush = true;
+  Fl_Window_Driver::flush();
+  Fl_Wayland_Window_Driver::in_flush = false;
+  pWindow->clear_damage();
+  if (window->buffer->wl_buffer_ready) {
+    Fl_Wayland_Graphics_Driver::buffer_commit(window);
+  }
+}
+
+
+void Fl_Wayland_Window_Driver::show() {
+  if (!shown()) {
+    fl_open_display();
+    makeWindow();
+  } else {
+    //XMapRaised(fl_display, fl_xid(pWindow));
+    //TODO
+    Fl::handle(FL_SHOW, pWindow);
+  }
+}
+
+
+void Fl_Wayland_Window_Driver::show_menu()
+{
+    pWindow->Fl_Window::show();
+}
+
+
+static void popup_done(void *data, struct xdg_popup *xdg_popup);
+
+void Fl_Wayland_Window_Driver::hide() {
+  Fl_X* ip = Fl_X::i(pWindow);
+  if (hide_common()) return;
+  if (ip->region) Fl_Graphics_Driver::default_driver().XDestroyRegion(ip->region);
+#if USE_XFT
+  screen_num_ = -1;
+#endif
+  struct wld_window *wld_win = ip->xid;
+  if (wld_win) { // this test makes sure ip->xid has not been destroyed already
+    Fl_Wayland_Graphics_Driver::buffer_release(wld_win);
+//fprintf(stderr, "Before hide: sub=%p gl=%p frame=%p xdg=%p top=%p pop=%p surf=%p\n", wld_win->subsurface, wld_win->gl_wl_surface, wld_win->frame, wld_win->xdg_surface, wld_win->xdg_toplevel, wld_win->xdg_popup, wld_win->wl_surface);
+    if (wld_win->subsurface) {
+      wl_subsurface_destroy(wld_win->subsurface);
+      wld_win->subsurface = NULL;
+    }
+    if (wld_win->gl_wl_surface) {
+      wl_surface_destroy(wld_win->gl_wl_surface);
+      wld_win->gl_wl_surface = NULL;
+    }
+    if (wld_win->frame) {
+      libdecor_frame_unref(wld_win->frame);
+      wld_win->frame = NULL;
+      wld_win->xdg_surface = NULL;
+      wld_win->xdg_toplevel = NULL;
+    } else {
+      if (wld_win->xdg_popup) {
+        popup_done(wld_win, wld_win->xdg_popup);
+        wld_win->xdg_popup = NULL;
+      }
+      if (wld_win->xdg_toplevel) {
+        xdg_toplevel_destroy(wld_win->xdg_toplevel);
+        wld_win->xdg_toplevel = NULL;
+      }
+      if (wld_win->xdg_surface) {
+        xdg_surface_destroy(wld_win->xdg_surface);
+        wld_win->xdg_surface = NULL;
+      }
+    }
+    if (wld_win->wl_surface) {
+      wl_surface_destroy(wld_win->wl_surface);
+      wld_win->wl_surface = NULL;
+    }
+//fprintf(stderr, "After hide: sub=%p gl=%p frame=%p xdg=%p top=%p pop=%p surf=%p\n", wld_win->subsurface, wld_win->gl_wl_surface, wld_win->frame, wld_win->xdg_surface, wld_win->xdg_toplevel, wld_win->xdg_popup, wld_win->wl_surface);
+  }
+  delete ip;
+}
+
+
+void Fl_Wayland_Window_Driver::map() {
+  Fl_X* ip = Fl_X::i(pWindow);
+  struct wld_window *wl_win = ip->xid;
+  if (wl_win->frame) libdecor_frame_map(wl_win->frame);//needs checking
+  else if (pWindow->parent() && !wl_win->subsurface) {
+    struct wld_window *parent = fl_xid(pWindow->window());
+    if (parent) {
+      Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver();
+      wl_win->subsurface = wl_subcompositor_get_subsurface(scr_driver->wl_subcompositor, wl_win->wl_surface, parent->wl_surface);
+      wl_subsurface_set_position(wl_win->subsurface, pWindow->x(), pWindow->y());
+      wl_subsurface_set_desync(wl_win->subsurface); // important
+      wl_subsurface_place_above(wl_win->subsurface, parent->wl_surface);
+      wl_win->configured_width = pWindow->w();
+      wl_win->configured_height = pWindow->h();
+      wl_win->scale = parent->scale;
+      wait_for_expose_value = 0;
+    }
+  }
+}
+
+
+void Fl_Wayland_Window_Driver::unmap() {
+  Fl_X* ip = Fl_X::i(pWindow);
+  struct wld_window *wl_win = ip->xid;
+  if (wl_win->frame) libdecor_frame_close(wl_win->frame);//needs checking
+  else if(wl_win->subsurface) {
+    wl_surface_attach(wl_win->wl_surface, NULL, 0, 0);
+    Fl_Wayland_Graphics_Driver::buffer_release(wl_win);
+    wl_subsurface_destroy(wl_win->subsurface);
+    wl_win->subsurface = NULL;
+  }
+}
+
+
+void Fl_Wayland_Window_Driver::size_range() {
+  Fl_Window_Driver::size_range();
+  if (shown()) {
+    Fl_X* ip = Fl_X::i(pWindow);
+    struct wld_window *wl_win = ip->xid;
+    if (wl_win->frame) {
+      libdecor_frame_set_min_content_size(wl_win->frame, minw(), minh());
+      if (maxw() && maxh()) {
+        libdecor_frame_set_max_content_size(wl_win->frame, maxw(), maxh());
+        libdecor_frame_unset_capabilities(wl_win->frame, LIBDECOR_ACTION_FULLSCREEN);
+        if (minw() >= maxw() || minh() >= maxh()) {
+          libdecor_frame_unset_capabilities(wl_win->frame, LIBDECOR_ACTION_RESIZE);
+        }
+      }
+    } else if (wl_win->xdg_toplevel) {
+      xdg_toplevel_set_min_size(wl_win->xdg_toplevel, minw(), minh());
+      if (maxw() && maxh())
+          xdg_toplevel_set_max_size(wl_win->xdg_toplevel, maxw(), maxh());
+    }
+  }
+}
+
+
+void Fl_Wayland_Window_Driver::iconize() {
+  Fl_X* ip = Fl_X::i(pWindow);
+  struct wld_window *wl_win = ip->xid;
+  if (wl_win->frame) {
+    libdecor_frame_set_minimized(wl_win->frame);
+    Fl::handle(FL_HIDE, pWindow);
+  }
+  else if (wl_win->xdg_toplevel) xdg_toplevel_set_minimized(wl_win->xdg_toplevel);
+}
+
+
+void Fl_Wayland_Window_Driver::decoration_sizes(int *top, int *left,  int *right, int *bottom) {
+  // Ensure border is on screen; these values are generic enough
+  // to work with many window managers, and are based on KDE defaults.
+  *top = 20;
+  *left = 4;
+  *right = 4;
+  *bottom = 8;
+}
+
+void Fl_Wayland_Window_Driver::show_with_args_begin() {
+  // Get defaults for drag-n-drop and focus...
+  const char *key = 0;
+
+  if (Fl::first_window()) key = Fl::first_window()->xclass();
+  if (!key) key = "fltk";
+
+  /*const char *val = XGetDefault(fl_display, key, "dndTextOps");
+  if (val) Fl::dnd_text_ops(strcasecmp(val, "true") == 0 ||
+                            strcasecmp(val, "on") == 0 ||
+                            strcasecmp(val, "yes") == 0);
+
+  val = XGetDefault(fl_display, key, "tooltips");
+  if (val) Fl_Tooltip::enable(strcasecmp(val, "true") == 0 ||
+                              strcasecmp(val, "on") == 0 ||
+                              strcasecmp(val, "yes") == 0);
+
+  val = XGetDefault(fl_display, key, "visibleFocus");
+  if (val) Fl::visible_focus(strcasecmp(val, "true") == 0 ||
+                             strcasecmp(val, "on") == 0 ||
+                             strcasecmp(val, "yes") == 0);*/
+}
+
+
+void Fl_Wayland_Window_Driver::show_with_args_end(int argc, char **argv) {
+  if (argc) {
+    // set the command string, used by state-saving window managers:
+    int j;
+    int n=0; for (j=0; j<argc; j++) n += strlen(argv[j])+1;
+    char *buffer = new char[n];
+    char *p = buffer;
+    for (j=0; j<argc; j++) for (const char *q = argv[j]; (*p++ = *q++););
+    //XChangeProperty(fl_display, fl_xid(pWindow), XA_WM_COMMAND, XA_STRING, 8, 0,
+    //                (unsigned char *)buffer, p-buffer-1);
+    delete[] buffer;
+  }
+}
+
+
+int Fl_Wayland_Window_Driver::can_do_overlay() {
+  return Fl_Window_Driver::can_do_overlay();
+}
+
+void Fl_Wayland_Window_Driver::redraw_overlay() {
+  Fl_Window_Driver::redraw_overlay();
+}
+
+void Fl_Wayland_Window_Driver::flush_menu() {
+   flush_Fl_Window();
+}
+
+void Fl_Wayland_Window_Driver::erase_menu() {
+}
+
+int Fl_Wayland_Window_Driver::scroll(int src_x, int src_y, int src_w, int src_h, int dest_x, int dest_y,
+                                 void (*draw_area)(void*, int,int,int,int), void* data)
+{
+  Window xid = fl_xid(pWindow);
+  struct buffer *buffer = xid->buffer;
+  int s = xid->scale;
+  if (s != 1) {
+    src_x *= s; src_y *= s; src_w *= s; src_h *= s; dest_x *= s; dest_y *= s;
+  }
+  if (src_x == dest_x) { // vertical scroll
+    int i, to, step;
+    if (src_y > dest_y) {
+      i = 0; to = src_h; step = 1;
+    } else {
+      i = src_h - 1; to = -1; step = -1;
+    }
+    while (i != to) {
+      memcpy(buffer->draw_buffer + (dest_y + i) * buffer->stride + 4 * dest_x,
+             buffer->draw_buffer + (src_y + i) * buffer->stride + 4 * src_x, 4 * src_w);
+      i += step;
+    }
+  } else { // horizontal scroll
+    int i, to, step;
+    if (src_x > dest_x) {
+      i = 0; to = src_h; step = 1;
+    } else {
+      i = src_h - 1; to = -1; step = -1;
+    }
+    while (i != to) {
+      memmove(buffer->draw_buffer + (src_y + i) * buffer->stride + 4 * dest_x,
+             buffer->draw_buffer + (src_y + i) * buffer->stride + 4 * src_x, 4 * src_w);
+      i += step;
+    }
+  }
+  return 0;
+}
+
+
+static void handle_error(struct libdecor *libdecor_context, enum libdecor_error error, const char *message)
+{
+  fprintf(stderr, "Caught error (%d): %s\n", error, message);
+  exit(EXIT_FAILURE);
+}
+
+static struct libdecor_interface libdecor_iface = {
+  .error = handle_error,
+};
+
+static void surface_enter(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output)
+{
+  struct wld_window *window = (struct wld_window*)data;
+  Fl_Wayland_Window_Driver::window_output *window_output;
+
+  if (!Fl_Wayland_Screen_Driver::own_output(wl_output))
+    return;
+
+  Fl_Wayland_Screen_Driver::output *output = (Fl_Wayland_Screen_Driver::output*)wl_output_get_user_data(wl_output);
+  if (output == NULL)
+    return;
+
+  window_output = (Fl_Wayland_Window_Driver::window_output*)calloc(1, sizeof *window_output);
+  window_output->output = output;
+  wl_list_insert(&window->outputs, &window_output->link);
+  Fl_Wayland_Window_Driver *win_driver = (Fl_Wayland_Window_Driver*)Fl_Window_Driver::driver(window->fl_win);
+  win_driver->update_scale();
+}
+
+static void surface_leave(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output)
+{
+  struct wld_window *window = (struct wld_window*)data;
+  if (! window->wl_surface) return;
+  Fl_Wayland_Window_Driver::window_output *window_output;
+
+  wl_list_for_each(window_output, &window->outputs, link) {
+    if (window_output->output->wl_output == wl_output) {
+      wl_list_remove(&window_output->link);
+      free(window_output);
+      Fl_Wayland_Window_Driver *win_driver = (Fl_Wayland_Window_Driver*)Fl_Window_Driver::driver(window->fl_win);
+      win_driver->update_scale();
+      break;
+    }
+  }
+}
+
+static struct wl_surface_listener surface_listener = {
+  surface_enter,
+  surface_leave,
+};
+
+bool Fl_Wayland_Window_Driver::in_handle_configure = false;
+
+static void handle_configure(struct libdecor_frame *frame,
+     struct libdecor_configuration *configuration, void *user_data)
+{
+  struct wld_window *window = (struct wld_window*)user_data;
+  if (!window->wl_surface) return;
+  int width, height;
+  enum libdecor_window_state window_state;
+  struct libdecor_state *state;
+  bool first_config = false;
+  Fl_Window_Driver *driver = Fl_Window_Driver::driver(window->fl_win);
+  
+  if (!window->xdg_toplevel) window->xdg_toplevel = libdecor_frame_get_xdg_toplevel(frame);
+  if (!window->xdg_surface) window->xdg_surface = libdecor_frame_get_xdg_surface(frame);
+  if (!libdecor_configuration_get_content_size(configuration, frame, &width, &height)) {
+    width = 0;
+    height = 0;
+    first_config = true;
+    if (!window->fl_win->parent() && window->fl_win->as_gl_window())
+      driver->wait_for_expose_value = 0;
+  } else if (driver->size_range_set()) {
+    if (width < driver->minw() || height < driver->minh()) return;
+  }
+
+  int tmp;
+  if (libdecor_configuration_get_window_size(configuration, &tmp, &window->decorated_height) ) {
+    driver->wait_for_expose_value = 0;
+//    fprintf(stderr, "decorated size=%dx%d ", tmp, window->decorated_height);
+  }
+  if (width == 0) {
+    width = window->fl_win->w();
+    height = window->fl_win->h();
+  }
+  if (width < 128) width = 128; // enforce minimal size of decorated windows for libdecor
+  if (height < 56) height = 56;
+  if (first_config) window->fl_win->Fl_Group::resize(0, 0, width, height);
+  else {
+    Fl_Wayland_Window_Driver::in_handle_configure = true;
+    window->fl_win->resize(0, 0, width, height);
+    Fl_Wayland_Window_Driver::in_handle_configure = false;
+  }
+  
+  if (width != window->configured_width || height != window->configured_height) {
+    if (window->buffer) {
+      Fl_Wayland_Graphics_Driver::buffer_release(window);
+    }
+  }
+  window->configured_width = width;
+  window->configured_height = height;
+
+  if (!libdecor_configuration_get_window_state(configuration, &window_state))
+    window_state = LIBDECOR_WINDOW_STATE_NONE;
+
+//fprintf(stderr, "handle_configure fl_win=%p pos:%dx%d size:%dx%d state=%x\n", window->fl_win, window->fl_win->x(), window->fl_win->y(), width,height,window_state);
+
+/* We would like to do FL_HIDE when window is minimized but :
+ "There is no way to know if the surface is currently minimized, nor is there any way to
+ unset minimization on this surface. If you are looking to throttle redrawing when minimized,
+ please instead use the wl_surface.frame event" */
+  if (window_state == LIBDECOR_WINDOW_STATE_NONE) Fl::handle(FL_UNFOCUS, window->fl_win);
+  else if (window_state == LIBDECOR_WINDOW_STATE_ACTIVE) Fl::handle(FL_FOCUS, window->fl_win);
+
+  state = libdecor_state_new(width, height);
+  libdecor_frame_commit(frame, state, configuration);
+  libdecor_state_free(state);
+  
+  if (!window->fl_win->as_gl_window()) {
+    driver->flush();
+  } else if (window->fl_win->parent()) {
+    driver->Fl_Window_Driver::flush();
+  }
+}
+
+
+static void delayed_close(Fl_Window *win) {
+  Fl::handle(FL_CLOSE, win);
+}
+
+static void handle_close(struct libdecor_frame *frame, void *user_data)
+{
+  struct wld_window* wl_win = (struct wld_window*)user_data;
+  // This may be called while in Fl::flush() when GL windows are involved.
+  // Thus, have the FL_CLOSE event sent by a timeout to leave Fl::flush().
+  Fl::add_timeout(0.001, (Fl_Timeout_Handler)delayed_close, wl_win->fl_win);
+}
+
+
+static void handle_commit(struct libdecor_frame *frame, void *user_data)
+{
+  struct wld_window* wl_win = (struct wld_window*)user_data;
+  if (wl_win->wl_surface) wl_surface_commit(wl_win->wl_surface);
+}
+
+static void handle_dismiss_popup(struct libdecor_frame *frame, const char *seat_name, void *user_data)
+{
+}
+
+static struct libdecor_frame_interface libdecor_frame_iface = {
+  handle_configure,
+  handle_close,
+  handle_commit,
+  handle_dismiss_popup,
+};
+
+
+static void xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial)
+{
+  // runs for borderless windows and popup (menu,tooltip) windows
+  struct wld_window *window = (struct wld_window*)data;
+  xdg_surface_ack_configure(xdg_surface, serial);
+//fprintf(stderr, "xdg_surface_configure: surface=%p\n", window->wl_surface);
+  Fl_Window_Driver::driver(window->fl_win)->wait_for_expose_value = 0;
+  
+  if (window->fl_win->w() != window->configured_width || window->fl_win->h() != window->configured_height) {
+    if (window->buffer) {
+      Fl_Wayland_Graphics_Driver::buffer_release(window);
+    }
+  }
+  window->configured_width = window->fl_win->w();
+  window->configured_height = window->fl_win->h();
+  window->fl_win->redraw();
+  Fl_Window_Driver::driver(window->fl_win)->flush();
+}
+
+static const struct xdg_surface_listener xdg_surface_listener = {
+    .configure = xdg_surface_configure,
+};
+
+
+static void xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel,
+                                   int32_t width, int32_t height, struct wl_array *states)
+{
+  // runs for borderless top-level windows
+  struct wld_window *window = (struct wld_window*)data;
+//fprintf(stderr, "xdg_toplevel_configure: surface=%p size: %dx%d\n", window->wl_surface, width, height);
+  if (width == 0 || height == 0) {
+    width = window->fl_win->w();
+    height = window->fl_win->h();
+  }
+  window->fl_win->size(width, height);
+  if (window->buffer && (width != window->configured_width || height != window->configured_height)) {
+    Fl_Wayland_Graphics_Driver::buffer_release(window);
+  }
+  window->configured_width = width;
+  window->configured_height = height;
+  Fl_Window_Driver::driver(window->fl_win)->wait_for_expose_value = 0;
+  if (window->fl_win->as_gl_window()) {
+    Fl_Window_Driver::driver(window->fl_win)->Fl_Window_Driver::flush();
+  } else {
+    Fl_Window_Driver::driver(window->fl_win)->flush();
+  }
+}
+
+
+static void xdg_toplevel_close(void *data, struct xdg_toplevel *toplevel)
+{
+}
+
+static const struct xdg_toplevel_listener xdg_toplevel_listener = {
+  .configure = xdg_toplevel_configure,
+  .close = xdg_toplevel_close,
+};
+
+
+static void popup_configure(void *data, struct xdg_popup *xdg_popup, int32_t x, int32_t y, int32_t width, int32_t height) {
+}
+
+static void popup_done(void *data, struct xdg_popup *xdg_popup) {
+   //input_ungrab(menu->input); //to be tested
+//fprintf(stderr, "popup_done: popup=%p \n", xdg_popup);
+  xdg_popup_destroy(xdg_popup);
+}
+
+static const struct xdg_popup_listener popup_listener = {
+  .configure = popup_configure,
+  .popup_done = popup_done,
+};
+
+bool Fl_Wayland_Window_Driver::in_flush = false;
+
+
+Fl_X *Fl_Wayland_Window_Driver::makeWindow()
+{
+  struct wld_window *new_window;
+  Fl_Wayland_Screen_Driver::output *output;
+  wait_for_expose_value = 1;
+  
+  if (pWindow->parent() && !pWindow->window()->shown()) return NULL;
+
+  new_window = (struct wld_window *)calloc(1, sizeof *new_window);
+  new_window->fl_win = pWindow;
+  new_window->scale = 1;
+  Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver();
+  wl_list_for_each(output, &(scr_driver->outputs), link) {
+    new_window->scale = MAX(new_window->scale, output->scale);
+  }
+  wl_list_init(&new_window->outputs);
+
+  new_window->wl_surface = wl_compositor_create_surface(scr_driver->wl_compositor);
+fprintf(stderr, "makeWindow:%p wl_compositor_create_surface=%p scale=%d\n", pWindow, new_window->wl_surface, new_window->scale);
+  wl_surface_add_listener(new_window->wl_surface, &surface_listener, new_window);
+  
+  if (pWindow->menu_window() || pWindow->tooltip_window()) { // a menu window or tooltip
+    new_window->xdg_surface = xdg_wm_base_get_xdg_surface(scr_driver->xdg_wm_base, new_window->wl_surface);
+    xdg_surface_add_listener(new_window->xdg_surface, &xdg_surface_listener, new_window);
+    struct xdg_positioner *positioner = xdg_wm_base_create_positioner(scr_driver->xdg_wm_base);
+    //xdg_positioner_get_version(positioner) <== gives 1 under Debian
+    Fl_Widget *target = pWindow->tooltip_window() ? Fl_Tooltip::current() : Fl::pushed();
+    if (!target) {
+      target = Fl::belowmouse()->top_window();
+    }
+    Fl_Window *parent_win = target->top_window();
+    struct xdg_surface *parent = fl_xid(parent_win)->xdg_surface;
+    int y_offset = parent_win->decorated_h() - parent_win->h();
+//fprintf(stderr, "menu parent_win=%p pos:%dx%d size:%dx%d y_offset=%d\n", parent_win, pWindow->x(), pWindow->y(), pWindow->w(), pWindow->h(), y_offset);
+    xdg_positioner_set_anchor_rect(positioner, pWindow->x(), pWindow->y() + y_offset, 1, 1);
+    xdg_positioner_set_size(positioner, pWindow->w() , pWindow->h() );
+    xdg_positioner_set_anchor(positioner, XDG_POSITIONER_ANCHOR_TOP_LEFT);
+    xdg_positioner_set_gravity(positioner, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
+    new_window->xdg_popup = xdg_surface_get_popup(new_window->xdg_surface, parent, positioner);
+    xdg_positioner_destroy(positioner);
+    xdg_popup_add_listener(new_window->xdg_popup, &popup_listener, new_window);
+//xdg_popup_grab(new_window->xdg_popup, scr_driver->get_wl_seat(), scr_driver->get_serial());//useful?
+    wl_surface_commit(new_window->wl_surface);
+//libdecor_frame_popup_grab(fl_xid(parent_win)->frame, scr_driver->get_seat_name());//useful?
+
+  } else if ( pWindow->border() && !pWindow->parent() ) { // a decorated window
+    if (!scr_driver->libdecor_context) scr_driver->libdecor_context = libdecor_new(fl_display, &libdecor_iface);
+    new_window->frame = libdecor_decorate(scr_driver->libdecor_context, new_window->wl_surface,
+                                              &libdecor_frame_iface, new_window);
+//fprintf(stderr, "makeWindow: libdecor_decorate=%p pos:%dx%d\n", new_window->frame, pWindow->x(), pWindow->y());
+    libdecor_frame_set_app_id(new_window->frame, ((Fl_Wayland_System_Driver*)Fl::system_driver())->get_prog_name()); // appears in the Gnome desktop menu bar
+    libdecor_frame_set_title(new_window->frame, pWindow->label()?pWindow->label():"");
+    if (!pWindow->resizable()) {
+      libdecor_frame_unset_capabilities(new_window->frame, LIBDECOR_ACTION_RESIZE);
+      libdecor_frame_unset_capabilities(new_window->frame, LIBDECOR_ACTION_FULLSCREEN);
+    }
+    libdecor_frame_set_min_content_size(new_window->frame, 128, 56);// libdecor wants width â?¥ 128 & height â?¥ 56
+    libdecor_frame_map(new_window->frame);
+    if (pWindow->as_gl_window()) { // a top-level GL window: create a subsurface for the GL part
+      new_window->gl_wl_surface = wl_compositor_create_surface(scr_driver->wl_compositor);
+      new_window->subsurface = wl_subcompositor_get_subsurface(scr_driver->wl_subcompositor, new_window->gl_wl_surface, new_window->wl_surface);
+      wl_subsurface_set_position(new_window->subsurface, 0, 0);
+      wl_subsurface_set_desync(new_window->subsurface);
+      wl_subsurface_place_above(new_window->subsurface, new_window->wl_surface);
+    }
+
+  } else if (pWindow->parent()) { // for subwindows (GL or non-GL)
+    struct wld_window *parent = fl_xid(pWindow->window());
+    new_window->subsurface = wl_subcompositor_get_subsurface(scr_driver->wl_subcompositor, new_window->wl_surface, parent->wl_surface);
+fprintf(stderr, "makeWindow: subsurface=%p\n", new_window->subsurface);
+    wl_subsurface_set_position(new_window->subsurface, pWindow->x(), pWindow->y());
+    wl_subsurface_set_desync(new_window->subsurface); // important
+    wl_subsurface_place_above(new_window->subsurface, parent->wl_surface);
+    // next 3 statements ensure the subsurface will be mapped because:
+    // "A sub-surface becomes mapped, when a non-NULL wl_buffer is applied and the parent surface is mapped."
+    new_window->configured_width = pWindow->w();
+    new_window->configured_height = pWindow->h();
+    wait_for_expose_value = 0;
+    pWindow->border(0);
+
+  } else { // a window without decoration
+    new_window->xdg_surface = xdg_wm_base_get_xdg_surface(scr_driver->xdg_wm_base, new_window->wl_surface);
+fprintf(stderr, "makeWindow: xdg_wm_base_get_xdg_surface=%p\n", new_window->xdg_surface);
+    xdg_surface_add_listener(new_window->xdg_surface, &xdg_surface_listener, new_window);
+    new_window->xdg_toplevel = xdg_surface_get_toplevel(new_window->xdg_surface);
+    xdg_toplevel_add_listener(new_window->xdg_toplevel, &xdg_toplevel_listener, new_window);
+    if (pWindow->label()) xdg_toplevel_set_title(new_window->xdg_toplevel, pWindow->label());
+    wl_surface_commit(new_window->wl_surface);
+    pWindow->border(0);
+  }
+    
+  Fl_Window *old_first = Fl::first_window();
+  Window first_xid = (old_first ? fl_xid(old_first) : NULL);
+  Fl_X *xp = new Fl_X;
+  xp->xid = new_window;
+  other_xid = 0;
+  xp->w = pWindow;
+  i(xp);
+  xp->region = 0;
+  if (!pWindow->parent()) {
+    xp->next = Fl_X::first;
+    Fl_X::first = xp;
+  } else if (Fl_X::first) {
+    xp->next = Fl_X::first->next;
+    Fl_X::first->next = xp;
+  } else {
+    xp->next = NULL;
+    Fl_X::first = xp;
+  }
+  
+  if (pWindow->modal()) {
+    Fl::modal_ = pWindow; /*fl_fix_focus();*/
+    if (new_window->frame && first_xid && first_xid->frame) {
+     libdecor_frame_set_parent(new_window->frame, first_xid->frame);
+    } else if (new_window->xdg_toplevel && first_xid && first_xid->xdg_toplevel) {
+      xdg_toplevel_set_parent(new_window->xdg_toplevel, first_xid->xdg_toplevel);
+    }
+  }
+  
+  if (size_range_set()) size_range();
+  pWindow->set_visible();
+  int old_event = Fl::e_number;
+  pWindow->handle(Fl::e_number = FL_SHOW); // get child windows to appear
+  Fl::e_number = old_event;
+  pWindow->redraw();
+  
+  return xp;
+}
+
+Fl_Wayland_Window_Driver::type_for_resize_window_between_screens Fl_Wayland_Window_Driver::data_for_resize_window_between_screens_ = {0, false};
+
+void Fl_Wayland_Window_Driver::resize_after_screen_change(void *data) {
+  Fl_Window *win = (Fl_Window*)data;
+  float f = Fl::screen_driver()->scale(data_for_resize_window_between_screens_.screen);
+  Fl_Window_Driver::driver(win)->resize_after_scale_change(data_for_resize_window_between_screens_.screen, f, f);
+  data_for_resize_window_between_screens_.busy = false;
+}
+
+
+#if USE_XFT
+int Fl_Wayland_Window_Driver::screen_num() {
+  if (pWindow->parent()) {
+    screen_num_ = Fl_Window_Driver::driver(pWindow->top_window())->screen_num();
+  }
+  return screen_num_ >= 0 ? screen_num_ : 0;
+}
+#endif
+
+
+int Fl_Wayland_Window_Driver::set_cursor(Fl_Cursor c) {
+  Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver();
+
+  //cursor names seem to be the files of directory /usr/share/icons/Adwaita/cursors/
+  switch (c) {
+    case FL_CURSOR_ARROW:
+      if (!scr_driver->xc_arrow) scr_driver->xc_arrow = scr_driver->cache_cursor("left_ptr");
+      scr_driver->default_cursor(scr_driver->xc_arrow);
+      break;
+    case FL_CURSOR_NS:
+      if (!scr_driver->xc_ns) scr_driver->xc_ns = scr_driver->cache_cursor("ns-resize");
+      if (!scr_driver->xc_ns) return 0;
+      scr_driver->default_cursor(scr_driver->xc_ns);
+      break;
+    case FL_CURSOR_CROSS:
+      if (!scr_driver->xc_cross) scr_driver->xc_cross = scr_driver->cache_cursor("cross");
+      if (!scr_driver->xc_cross) return 0;
+      scr_driver->default_cursor(scr_driver->xc_cross);
+      break;
+    case FL_CURSOR_WAIT:
+      if (!scr_driver->xc_wait) scr_driver->xc_wait = scr_driver->cache_cursor("wait");
+      if (!scr_driver->xc_wait) scr_driver->xc_wait = scr_driver->cache_cursor("watch");
+      if (!scr_driver->xc_wait) return 0;
+      scr_driver->default_cursor(scr_driver->xc_wait);
+      break;
+    case FL_CURSOR_INSERT:
+      if (!scr_driver->xc_insert) scr_driver->xc_insert = scr_driver->cache_cursor("xterm");
+      if (!scr_driver->xc_insert) return 0;
+      scr_driver->default_cursor(scr_driver->xc_insert);
+      break;
+    case FL_CURSOR_HAND:
+      if (!scr_driver->xc_hand) scr_driver->xc_hand = scr_driver->cache_cursor("hand");
+      if (!scr_driver->xc_hand) return 0;
+      scr_driver->default_cursor(scr_driver->xc_hand);
+      break;
+    case FL_CURSOR_HELP:
+      if (!scr_driver->xc_help) scr_driver->xc_help = scr_driver->cache_cursor("help");
+      if (!scr_driver->xc_help) return 0;
+      scr_driver->default_cursor(scr_driver->xc_help);
+      break;
+    case FL_CURSOR_MOVE:
+      if (!scr_driver->xc_move) scr_driver->xc_move = scr_driver->cache_cursor("move");
+      if (!scr_driver->xc_move) return 0;
+      scr_driver->default_cursor(scr_driver->xc_move);
+      break;
+    case FL_CURSOR_WE:
+      if (!scr_driver->xc_we) scr_driver->xc_we = scr_driver->cache_cursor("sb_h_double_arrow");
+      if (!scr_driver->xc_we) return 0;
+      scr_driver->default_cursor(scr_driver->xc_we);
+      break;
+    case FL_CURSOR_N:
+      if (!scr_driver->xc_north) scr_driver->xc_north = scr_driver->cache_cursor("top_side");
+      if (!scr_driver->xc_north) return 0;
+      scr_driver->default_cursor(scr_driver->xc_north);
+      break;
+    case FL_CURSOR_E:
+      if (!scr_driver->xc_east) scr_driver->xc_east = scr_driver->cache_cursor("right_side");
+      if (!scr_driver->xc_east) return 0;
+      scr_driver->default_cursor(scr_driver->xc_east);
+      break;
+    case FL_CURSOR_W:
+      if (!scr_driver->xc_west) scr_driver->xc_west = scr_driver->cache_cursor("left_side");
+      if (!scr_driver->xc_west) return 0;
+      scr_driver->default_cursor(scr_driver->xc_west);
+      break;
+    case FL_CURSOR_S:
+      if (!scr_driver->xc_south) scr_driver->xc_south = scr_driver->cache_cursor("bottom_side");
+      if (!scr_driver->xc_south) return 0;
+      scr_driver->default_cursor(scr_driver->xc_south);
+      break;
+    case FL_CURSOR_NESW:
+      if (!scr_driver->xc_nesw) scr_driver->xc_nesw = scr_driver->cache_cursor("fd_double_arrow");
+      if (!scr_driver->xc_nesw) return 0;
+      scr_driver->default_cursor(scr_driver->xc_nesw);
+      break;
+    case FL_CURSOR_NWSE:
+      if (!scr_driver->xc_nwse) scr_driver->xc_nwse = scr_driver->cache_cursor("bd_double_arrow");
+      if (!scr_driver->xc_nwse) return 0;
+      scr_driver->default_cursor(scr_driver->xc_nwse);
+      break;
+    case FL_CURSOR_SW:
+      if (!scr_driver->xc_sw) scr_driver->xc_sw = scr_driver->cache_cursor("bottom_left_corner");
+      if (!scr_driver->xc_sw) return 0;
+      scr_driver->default_cursor(scr_driver->xc_sw);
+      break;
+    case FL_CURSOR_SE:
+      if (!scr_driver->xc_se) scr_driver->xc_se = scr_driver->cache_cursor("bottom_right_corner");
+      if (!scr_driver->xc_se) return 0;
+      scr_driver->default_cursor(scr_driver->xc_se);
+      break;
+    case FL_CURSOR_NE:
+      if (!scr_driver->xc_ne) scr_driver->xc_ne = scr_driver->cache_cursor("top_right_corner");
+      if (!scr_driver->xc_ne) return 0;
+      scr_driver->default_cursor(scr_driver->xc_ne);
+      break;
+    case FL_CURSOR_NW:
+      if (!scr_driver->xc_nw) scr_driver->xc_nw = scr_driver->cache_cursor("top_left_corner");
+      if (!scr_driver->xc_nw) return 0;
+      scr_driver->default_cursor(scr_driver->xc_nw);
+      break;
+
+    default:
+      return 0;
+  }
+  scr_driver->set_cursor();
+  return 1;
+}
+
+
+void Fl_Wayland_Window_Driver::update_scale()
+{
+  struct wld_window *window = fl_xid(pWindow);
+  int scale = 1;
+  Fl_Wayland_Window_Driver::window_output *window_output;
+
+  wl_list_for_each(window_output, &window->outputs, link) {
+    scale = fl_max(scale, window_output->output->scale);
+  }
+  if (scale != window->scale) {
+    window->scale = scale;
+    if (window->buffer || window->fl_win->as_gl_window()) {
+      window->fl_win->damage(FL_DAMAGE_ALL);
+      Fl_Window_Driver::driver(window->fl_win)->flush();
+    }
+  }
+}
diff --git src/drivers/Wayland/Fl_get_key_wayland.cxx src/drivers/Wayland/Fl_get_key_wayland.cxx
new file mode 100644
index 0000000..c15e3cc
--- /dev/null
+++ src/drivers/Wayland/Fl_get_key_wayland.cxx
@@ -0,0 +1,38 @@
+//
+// Keyboard state routines for the Fast Light Tool Kit (FLTK).
+//
+// Copyright 1998-2021 by Bill Spitzak and others.
+//
+// This library is free software. Distribution and use rights are outlined in
+// the file "COPYING" which should have been included with this file.  If this
+// file is missing or damaged, see the license at:
+//
+//     https://www.fltk.org/COPYING.php
+//
+// Please see the following page on how to report bugs and issues:
+//
+//     https://www.fltk.org/bugs.php
+//
+
+#include <config.h>
+#if !defined(FL_DOXYGEN)
+
+// Return the current state of a key.  This is the Wayland version.  I identify
+// keys (mostly) by the keysym.
+
+#include <FL/Fl.H>
+#include "Fl_Wayland_System_Driver.H"
+
+int Fl_Wayland_System_Driver::event_key(int k) {
+  if (k > FL_Button && k <= FL_Button+8)
+    return Fl::event_state(8<<(k-FL_Button));
+  int sym = Fl::event_key();
+  if (sym >= 'a' && sym <= 'z' ) sym -= 32;
+  return (Fl::event() == FL_KEYDOWN || Fl::event() == FL_SHORTCUT) && sym == k;
+}
+
+int Fl_Wayland_System_Driver::get_key(int k) {
+  return event_key(k);
+}
+
+#endif // FL_DOXYGEN
diff --git src/drivers/Wayland/Fl_wayland.cxx src/drivers/Wayland/Fl_wayland.cxx
new file mode 100644
index 0000000..36459a2
--- /dev/null
+++ src/drivers/Wayland/Fl_wayland.cxx
@@ -0,0 +1,930 @@
+//
+// Wayland specific code for the Fast Light Tool Kit (FLTK).
+//
+// Copyright 1998-2021 by Bill Spitzak and others.
+//
+// This library is free software. Distribution and use rights are outlined in
+// the file "COPYING" which should have been included with this file.  If this
+// file is missing or damaged, see the license at:
+//
+//     https://www.fltk.org/COPYING.php
+//
+// Please see the following page on how to report bugs and issues:
+//
+//     https://www.fltk.org/bugs.php
+//
+
+#if !defined(FL_DOXYGEN)
+
+#  define CONSOLIDATE_MOTION 1
+
+#  include <config.h>
+#  include <FL/Fl.H>
+#  include <FL/platform.H>
+#  include "../../Fl_Window_Driver.H"
+#  include <FL/Fl_Window.H>
+#  include <FL/fl_utf8.h>
+#  include <FL/Fl_Tooltip.H>
+#  include <FL/fl_draw.H>
+#  include <FL/Fl_Paged_Device.H>
+#  include <FL/Fl_Shared_Image.H>
+#  include <FL/fl_ask.H>
+#  include <FL/filename.H>
+#  include <stdio.h>
+#  include <stdlib.h>
+#  include "../../flstring.h"
+#  include "Fl_Wayland_Screen_Driver.H"
+#  include "Fl_Wayland_Window_Driver.H"
+#  include "Fl_Wayland_System_Driver.H"
+#  include "Fl_Wayland_Graphics_Driver.H"
+#  include "../../../libdecor/src/libdecor.h"
+#  include "../../../libdecor/build/xdg-shell-client-protocol.h"
+#  include <unistd.h>
+#  include <time.h>
+#  include <sys/time.h>
+#  include <math.h>
+#  include <errno.h>
+
+////////////////////////////////////////////////////////////////
+// interface to poll/select call:
+
+#  if USE_POLL
+
+#    include <poll.h>
+static pollfd *pollfds = 0;
+
+#  else
+#    if HAVE_SYS_SELECT_H
+#      include <sys/select.h>
+#    endif /* HAVE_SYS_SELECT_H */
+
+
+static fd_set fdsets[3];
+static int maxfd;
+#    define POLLIN 1
+#    define POLLOUT 4
+#    define POLLERR 8
+
+#  endif /* USE_POLL */
+
+static int nfds = 0;
+static int fd_array_size = 0;
+struct FD {
+#  if !USE_POLL
+  int fd;
+  short events;
+#  endif
+  void (*cb)(int, void*);
+  void* arg;
+};
+
+static FD *fd = 0;
+
+void Fl_Wayland_System_Driver::add_fd(int n, int events, void (*cb)(int, void*), void *v) {
+  remove_fd(n,events);
+  int i = nfds++;
+  if (i >= fd_array_size) {
+    FD *temp;
+    fd_array_size = 2*fd_array_size+1;
+
+    if (!fd) temp = (FD*)malloc(fd_array_size*sizeof(FD));
+    else temp = (FD*)realloc(fd, fd_array_size*sizeof(FD));
+
+    if (!temp) return;
+    fd = temp;
+
+#  if USE_POLL
+    pollfd *tpoll;
+
+    if (!pollfds) tpoll = (pollfd*)malloc(fd_array_size*sizeof(pollfd));
+    else tpoll = (pollfd*)realloc(pollfds, fd_array_size*sizeof(pollfd));
+
+    if (!tpoll) return;
+    pollfds = tpoll;
+#  endif
+  }
+  fd[i].cb = cb;
+  fd[i].arg = v;
+#  if USE_POLL
+  pollfds[i].fd = n;
+  pollfds[i].events = events;
+#  else
+  fd[i].fd = n;
+  fd[i].events = events;
+  if (events & POLLIN) FD_SET(n, &fdsets[0]);
+  if (events & POLLOUT) FD_SET(n, &fdsets[1]);
+  if (events & POLLERR) FD_SET(n, &fdsets[2]);
+  if (n > maxfd) maxfd = n;
+#  endif
+}
+
+void Fl_Wayland_System_Driver::add_fd(int n, void (*cb)(int, void*), void* v) {
+  add_fd(n, POLLIN, cb, v);
+}
+
+void Fl_Wayland_System_Driver::remove_fd(int n, int events) {
+  int i,j;
+# if !USE_POLL
+  maxfd = -1; // recalculate maxfd on the fly
+# endif
+  for (i=j=0; i<nfds; i++) {
+#  if USE_POLL
+    if (pollfds[i].fd == n) {
+      int e = pollfds[i].events & ~events;
+      if (!e) continue; // if no events left, delete this fd
+      pollfds[j].events = e;
+    }
+#  else
+    if (fd[i].fd == n) {
+      int e = fd[i].events & ~events;
+      if (!e) continue; // if no events left, delete this fd
+      fd[i].events = e;
+    }
+    if (fd[i].fd > maxfd) maxfd = fd[i].fd;
+#  endif
+    // move it down in the array if necessary:
+    if (j<i) {
+      fd[j] = fd[i];
+#  if USE_POLL
+      pollfds[j] = pollfds[i];
+#  endif
+    }
+    j++;
+  }
+  nfds = j;
+#  if !USE_POLL
+  if (events & POLLIN) FD_CLR(n, &fdsets[0]);
+  if (events & POLLOUT) FD_CLR(n, &fdsets[1]);
+  if (events & POLLERR) FD_CLR(n, &fdsets[2]);
+#  endif
+}
+
+void Fl_Wayland_System_Driver::remove_fd(int n) {
+  remove_fd(n, -1);
+}
+
+extern int fl_send_system_handlers(void *e);
+
+#if CONSOLIDATE_MOTION
+//static Fl_Window* send_motion;
+extern Fl_Window* fl_xmousewin;
+#endif
+//static bool in_a_window; // true if in any of our windows, even destroyed ones
+
+// these pointers are set by the Fl::lock() function:
+static void nothing() {}
+void (*fl_lock_function)() = nothing;
+void (*fl_unlock_function)() = nothing;
+
+
+// This is never called with time_to_wait < 0.0:
+// It should return negative on error, 0 if nothing happens before
+// timeout, and >0 if any callbacks were done.
+int Fl_Wayland_Screen_Driver::poll_or_select_with_delay(double time_to_wait) {
+  
+  wl_display_flush(fl_display);
+  
+#  if !USE_POLL
+    fd_set fdt[3];
+    fdt[0] = fdsets[0];
+    fdt[1] = fdsets[1];
+    fdt[2] = fdsets[2];
+#  endif
+    int n;
+    
+    fl_unlock_function();
+    
+    if (time_to_wait < 2147483.648) {
+#  if USE_POLL
+      n = ::poll(pollfds, nfds, int(time_to_wait*1000 + .5));
+#  else
+      timeval t;
+      t.tv_sec = int(time_to_wait);
+      t.tv_usec = int(1000000 * (time_to_wait-t.tv_sec));
+      n = ::select(maxfd+1,&fdt[0],&fdt[1],&fdt[2],&t);
+#  endif
+    } else {
+#  if USE_POLL
+      n = ::poll(pollfds, nfds, -1);
+#  else
+      n = ::select(maxfd+1,&fdt[0],&fdt[1],&fdt[2],0);
+#  endif
+    }
+    fl_lock_function();
+    
+    if (n > 0) {
+      for (int i=0; i<nfds; i++) {
+#  if USE_POLL
+        if (pollfds[i].revents) fd[i].cb(pollfds[i].fd, fd[i].arg);
+#  else
+        int f = fd[i].fd;
+        short revents = 0;
+        if (FD_ISSET(f,&fdt[0])) revents |= POLLIN;
+        if (FD_ISSET(f,&fdt[1])) revents |= POLLOUT;
+        if (FD_ISSET(f,&fdt[2])) revents |= POLLERR;
+        if (fd[i].events & revents) fd[i].cb(f, fd[i].arg);
+#  endif
+      }
+    }
+    return n;
+}
+
+int Fl_Wayland_Screen_Driver::poll_or_select() {
+  wl_display_flush(fl_display);
+
+  if (!nfds) return 0; // nothing to select or poll
+#  if USE_POLL
+  return ::poll(pollfds, nfds, 0);
+#  else
+  timeval t;
+  t.tv_sec = 0;
+  t.tv_usec = 0;
+  fd_set fdt[3];
+  fdt[0] = fdsets[0];
+  fdt[1] = fdsets[1];
+  fdt[2] = fdsets[2];
+  return ::select(maxfd+1,&fdt[0],&fdt[1],&fdt[2],&t);
+#  endif
+}
+
+////////////////////////////////////////////////////////////////
+
+Window fl_message_window = 0;
+int fl_screen;
+Window fl_xim_win = 0;
+char fl_is_over_the_spot = 0;
+
+
+extern char *fl_get_font_xfld(int fnum, int size);
+
+
+void fl_set_status(int x, int y, int w, int h)
+{
+ }
+
+//extern XRectangle fl_spot;
+extern int fl_spotf;
+extern int fl_spots;
+
+void Fl_Wayland_Screen_Driver::enable_im() {
+}
+
+void Fl_Wayland_Screen_Driver::disable_im() {
+}
+
+
+int Fl_Wayland_Screen_Driver::get_mouse_unscaled(int &mx, int &my) {
+  open_display();
+  mx = Fl::e_x_root; my = Fl::e_y_root;
+  int screen = screen_num_unscaled(mx, my);
+  return screen >= 0 ? screen : 0;
+}
+
+
+int Fl_Wayland_Screen_Driver::get_mouse(int &xx, int &yy) {
+  int snum = get_mouse_unscaled(xx, yy);
+  float s = scale(snum);
+  xx = xx/s;
+  yy = yy/s;
+  return snum;
+}
+
+////////////////////////////////////////////////////////////////
+// Code used for copy and paste and DnD into the program:
+//static Window fl_dnd_source_window;
+
+static char *fl_selection_buffer[2];
+static int fl_selection_length[2];
+static const char * fl_selection_type[2];
+static int fl_selection_buffer_length[2];
+static char fl_i_own_selection[2] = {0,0};
+static struct wl_data_offer *fl_selection_offer = NULL;
+static const char *fl_selection_offer_type = NULL;
+// The MIME type Wayland uses for text-containing clipboard:
+static const char wld_plain_text_clipboard[] = "text/plain;charset=utf-8";
+
+
+static void read_int(uchar *c, int& i) {
+  i = *c;
+  i |= (*(++c))<<8;
+  i |= (*(++c))<<16;
+  i |= (*(++c))<<24;
+}
+
+// turn BMP image FLTK produced by create_bmp() back to Fl_RGB_Image
+static Fl_RGB_Image *own_bmp_to_RGB(char *bmp) {
+  int w, h;
+  read_int((uchar*)bmp + 18, w);
+  read_int((uchar*)bmp + 22, h);
+  int R=((3*w+3)/4) * 4; // the number of bytes per row, rounded up to multiple of 4
+  bmp +=  54;
+  uchar *data = new uchar[w*h*3];
+  uchar *p = data;
+  for (int i = h-1; i >= 0; i--) {
+    char *s = bmp + i * R;
+    for (int j = 0; j < w; j++) {
+      *p++=s[2];
+      *p++=s[1];
+      *p++=s[0];
+      s+=3;
+    }
+  }
+  Fl_RGB_Image *img = new Fl_RGB_Image(data, w, h, 3);
+  img->alloc_array = 1;
+  return img;
+}
+
+
+int Fl_Wayland_System_Driver::clipboard_contains(const char *type)
+{
+  return fl_selection_type[1] == type;
+}
+
+
+struct data_source_write_struct {
+  size_t rest;
+  char *from;
+};
+
+void write_data_source_cb(FL_SOCKET fd, data_source_write_struct *data) {
+  while (data->rest) {
+    ssize_t n = write(fd, data->from, data->rest);
+    if (n == -1) {
+      if (errno == EAGAIN) return;
+fprintf(stderr, "write_data_source_cb: error while writing clipboard data\n");
+      break;
+    }
+    data->from += n;
+    data->rest -= n;
+  }
+  Fl::remove_fd(fd, FL_WRITE);
+  delete data;
+  close(fd);
+}
+
+static void data_source_handle_send(void *data, struct wl_data_source *source, const char *mime_type, int fd) {
+//fprintf(stderr, "data_source_handle_send: %s fd=%d l=%d\n", mime_type, fd, fl_selection_length[1]);
+  if (strcmp(mime_type, wld_plain_text_clipboard) == 0 || strcmp(mime_type, "image/bmp") == 0) {
+    data_source_write_struct *data = new data_source_write_struct;
+    data->rest = fl_selection_length[1];
+    data->from = fl_selection_buffer[1];
+    Fl::add_fd(fd, FL_WRITE, (Fl_FD_Handler)write_data_source_cb, data);
+  } else {
+fprintf(stderr, "Destination client requested unsupported MIME type: %s\n", mime_type);
+    close(fd);
+  }
+}
+
+static void data_source_handle_cancelled(void *data, struct wl_data_source *source) {
+  // An application has replaced the clipboard contents
+//fprintf(stderr, "data_source_handle_cancelled: %p\n", source);
+  wl_data_source_destroy(source);
+  fl_i_own_selection[1] = 0;
+}
+
+
+static const struct wl_data_source_listener data_source_listener = {
+  .send = data_source_handle_send,
+  .cancelled = data_source_handle_cancelled,
+};
+
+
+static void data_offer_handle_offer(void *data, struct wl_data_offer *offer, const char *mime_type) {
+  // runs when app becomes active and lists possible clipboard types
+//fprintf(stderr, "Clipboard offer=%p supports MIME type: %s\n", offer, mime_type);
+  if (strcmp(mime_type, "image/png") == 0) {
+    fl_selection_type[1] = Fl::clipboard_image;
+    fl_selection_offer_type = "image/png";
+  } else if (strcmp(mime_type, "image/bmp") == 0 && (!fl_selection_offer_type || strcmp(fl_selection_offer_type, "image/png"))) {
+    fl_selection_type[1] = Fl::clipboard_image;
+    fl_selection_offer_type = "image/bmp";
+  } else if (strcmp(mime_type, wld_plain_text_clipboard) == 0 && !fl_selection_type[1]) {
+    fl_selection_type[1] = Fl::clipboard_plain_text;
+  }
+}
+
+static const struct wl_data_offer_listener data_offer_listener = {
+  .offer = data_offer_handle_offer,
+};
+
+static void data_device_handle_data_offer(void *data, struct wl_data_device *data_device, struct wl_data_offer *offer) {
+  // An application has created a new data source
+//fprintf(stderr, "data_device_handle_data_offer offer=%p\n", offer);
+  fl_selection_type[1] = NULL;
+  fl_selection_offer_type = NULL;
+  wl_data_offer_add_listener(offer, &data_offer_listener, NULL);
+}
+
+
+static void data_device_handle_selection(void *data, struct wl_data_device *data_device, struct wl_data_offer *offer) {
+  // An application has set the clipboard contents. W
+//fprintf(stderr, "data_device_handle_selection\n");
+  if (fl_selection_offer) wl_data_offer_destroy(fl_selection_offer);
+  fl_selection_offer = offer;
+//if (offer == NULL) fprintf(stderr, "Clipboard is empty\n");
+}
+
+
+static const struct wl_data_device_listener data_device_listener = {
+  .data_offer = data_device_handle_data_offer,
+  .selection = data_device_handle_selection,
+};
+const struct wl_data_device_listener *Fl_Wayland_Screen_Driver::p_data_device_listener = &data_device_listener;
+
+
+// Gets from the system the clipboard text and puts it in fl_selection_buffer[1]
+// which is enlarged if necessary.
+static void get_clipboard_text() {
+  int fds[2];
+  pipe(fds);
+  wl_data_offer_receive(fl_selection_offer, wld_plain_text_clipboard, fds[1]);
+  close(fds[1]);
+  wl_display_roundtrip(fl_display);
+  // read in fl_selection_buffer
+  char *to = fl_selection_buffer[1];
+  ssize_t rest = fl_selection_buffer_length[1];
+  while (rest) {
+    ssize_t n = read(fds[0], to, rest);
+    if (n <= 0) {
+      close(fds[0]);
+      fl_selection_length[1] = to - fl_selection_buffer[1];
+      fl_selection_buffer[1][ fl_selection_length[1] ] = 0;
+      return;
+    }
+    to += n;
+    rest -= n;
+  }
+  // compute size of unread clipboard data
+  rest = fl_selection_buffer_length[1];
+  while (true) {
+    char buf[1000];
+    ssize_t n = read(fds[0], buf, sizeof(buf));
+    if (n <= 0) {
+      close(fds[0]);
+      break;
+    }
+    rest += n;
+  }
+//fprintf(stderr, "get_clipboard_text: size=%ld\n", rest);
+  // read full clipboard data
+  pipe(fds);
+  wl_data_offer_receive(fl_selection_offer, wld_plain_text_clipboard, fds[1]);
+  close(fds[1]);
+  wl_display_roundtrip(fl_display);
+  if (rest+1 > fl_selection_buffer_length[1]) {
+    delete[] fl_selection_buffer[1];
+    fl_selection_buffer[1] = new char[rest+1000+1];
+    fl_selection_buffer_length[1] = rest+1000;
+  }
+  char *from = fl_selection_buffer[1];
+  while (true) {
+    ssize_t n = read(fds[0], from, rest);
+    if (n <= 0) {
+      close(fds[0]);
+      break;
+    }
+    from += n;
+  }
+  fl_selection_length[1] = rest;
+  fl_selection_buffer[1][rest] = 0;
+  Fl::e_clipboard_type = Fl::clipboard_plain_text;
+}
+
+
+// Reads from the clipboard an image which can be in image/bmp or image/png MIME type.
+// Returns 0 if OK, != 0 if error.
+static int get_clipboard_image() {
+  int fds[2];
+  pipe(fds);
+  wl_data_offer_receive(fl_selection_offer, fl_selection_offer_type, fds[1]);
+  close(fds[1]);
+  wl_display_roundtrip(fl_display);
+  if (strcmp(fl_selection_offer_type, "image/png") == 0) {
+    char tmp_fname[21];
+    Fl_Shared_Image *shared = 0;
+    strcpy(tmp_fname, "/tmp/clipboardXXXXXX");
+    int fd = mkstemp(tmp_fname);
+    if (fd == -1) return 1;
+    while (true) {
+      char buf[10000];
+      ssize_t n = read(fds[0], buf, sizeof(buf));
+      if (n <= 0) {
+        close(fds[0]);
+        close(fd);
+        break;
+      }
+      write(fd, buf, n);
+    }
+    shared = Fl_Shared_Image::get(tmp_fname);
+    fl_unlink(tmp_fname);
+    if (!shared) return 1;
+    int ld = shared->ld() ? shared->ld() : shared->w() * shared->d();
+    uchar *rgb = new uchar[shared->w() * shared->h() * shared->d()];
+    memcpy(rgb, shared->data()[0], ld * shared->h() );
+    Fl_RGB_Image *image = new Fl_RGB_Image(rgb, shared->w(), shared->h(), shared->d(), shared->ld());
+    shared->release();
+    image->alloc_array = 1;
+    Fl::e_clipboard_data = (void*)image;
+  } else { // process image/bmp
+    uchar buf[54];
+    size_t rest = 1;
+    char *bmp = NULL;
+    ssize_t n = read(fds[0], buf, sizeof(buf)); // read size info of the BMP image
+    if (n == sizeof(buf)) {
+      int w, h; // size of the BMP image
+      read_int(buf + 18, w);
+      read_int(buf + 22, h);
+      int R = ((3*w+3)/4) * 4; // the number of bytes per row of BMP image, rounded up to multiple of 4
+      bmp = new char[R * h + 54];
+      memcpy(bmp, buf, 54);
+      char *from = bmp + 54;
+      rest = R * h;
+      while (rest) {
+        ssize_t n = read(fds[0], from, rest);
+        if (n <= 0) break;
+        from += n;
+        rest -= n;
+      }
+//fprintf(stderr, "get_clipboard_image: image/bmp %dx%d rest=%lu\n", w,h,rest);
+    }
+    close(fds[0]);
+    if (!rest) Fl::e_clipboard_data = own_bmp_to_RGB(bmp);
+    delete[] bmp;
+    if (rest) return 1;
+  }
+  Fl::e_clipboard_type = Fl::clipboard_image;
+  return 0;
+}
+
+
+void Fl_Wayland_System_Driver::paste(Fl_Widget &receiver, int clipboard, const char *type) {
+  if (clipboard != 1) return;
+  if (fl_i_own_selection[1]) {
+    // We already have it, do it quickly without compositor.
+    if (type == Fl::clipboard_plain_text && fl_selection_type[1] == type) {
+      Fl::e_text = fl_selection_buffer[1];
+      Fl::e_length = fl_selection_length[1];
+      if (!Fl::e_text) Fl::e_text = (char *)"";
+    } else if (type == Fl::clipboard_image && fl_selection_type[1] == type) {
+      Fl::e_clipboard_data = own_bmp_to_RGB(fl_selection_buffer[1]);
+      Fl::e_clipboard_type = Fl::clipboard_image;
+    } else return;
+    receiver.handle(FL_PASTE);
+    return;
+  }
+  // otherwise get the compositor to return it:
+  if (!fl_selection_offer) return;
+  if (type == Fl::clipboard_plain_text && clipboard_contains(Fl::clipboard_plain_text)) {
+    get_clipboard_text();
+    Fl::e_text = fl_selection_buffer[1];
+    Fl::e_length = fl_selection_length[1];
+    receiver.handle(FL_PASTE);
+  } else if (type == Fl::clipboard_image && clipboard_contains(Fl::clipboard_image)) {
+    if (get_clipboard_image()) return;
+    Window xid = fl_xid(receiver.top_window());
+    if (xid && xid->scale > 1) {
+      Fl_RGB_Image *rgb = (Fl_RGB_Image*)Fl::e_clipboard_data;
+      rgb->scale(rgb->data_w() / xid->scale, rgb->data_h() / xid->scale);
+    }
+    int done = receiver.handle(FL_PASTE);
+    Fl::e_clipboard_type = "";
+    if (done == 0) {
+      delete (Fl_RGB_Image*)Fl::e_clipboard_data;
+      Fl::e_clipboard_data = NULL;
+    }
+  }
+}
+
+
+void Fl_Wayland_System_Driver::copy(const char *stuff, int len, int clipboard, const char *type) {
+  if (!stuff || len < 0) return;
+
+  if (clipboard >= 2)
+    clipboard = 1; // Only on X11 do multiple clipboards make sense.
+
+  if (len+1 > fl_selection_buffer_length[clipboard]) {
+    delete[] fl_selection_buffer[clipboard];
+    fl_selection_buffer[clipboard] = new char[len+100];
+    fl_selection_buffer_length[clipboard] = len+100;
+  }
+  memcpy(fl_selection_buffer[clipboard], stuff, len);
+  fl_selection_buffer[clipboard][len] = 0; // needed for direct paste
+  fl_selection_length[clipboard] = len;
+  fl_i_own_selection[clipboard] = 1;
+  fl_selection_type[clipboard] = Fl::clipboard_plain_text;
+  if (clipboard == 1) {
+    Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver();
+    scr_driver->seat->data_source = wl_data_device_manager_create_data_source(scr_driver->seat->data_device_manager);
+    wl_data_source_add_listener(scr_driver->seat->data_source, &data_source_listener, NULL);
+    wl_data_source_offer(scr_driver->seat->data_source, wld_plain_text_clipboard);
+    wl_data_device_set_selection(scr_driver->seat->data_device, scr_driver->seat->data_source, scr_driver->seat->keyboard_enter_serial);
+//fprintf(stderr, "wl_data_device_set_selection len=%d to %d\n", len, clipboard);
+  }
+}
+
+
+static void write_short(unsigned char **cp, short i) {
+  unsigned char *c = *cp;
+  *c++ = i & 0xFF; i >>= 8;
+  *c++ = i & 0xFF;
+  *cp = c;
+}
+
+static void write_int(unsigned char **cp, int i) {
+  unsigned char *c = *cp;
+  *c++ = i & 0xFF; i >>= 8;
+  *c++ = i & 0xFF; i >>= 8;
+  *c++ = i & 0xFF; i >>= 8;
+  *c++ = i & 0xFF;
+  *cp = c;
+}
+
+static unsigned char *create_bmp(const unsigned char *data, int W, int H, int *return_size){
+  int R = ((3*W+3)/4) * 4; // the number of bytes per row, rounded up to multiple of 4
+  int s=H*R;
+  int fs=14+40+s;
+  unsigned char *b=new unsigned char[fs];
+  unsigned char *c=b;
+  // BMP header
+  *c++='B';
+  *c++='M';
+  write_int(&c,fs);
+  write_int(&c,0);
+  write_int(&c,14+40);
+  // DIB header:
+  write_int(&c,40);
+  write_int(&c,W);
+  write_int(&c,H);
+  write_short(&c,1);
+  write_short(&c,24);//bits ber pixel
+  write_int(&c,0);//RGB
+  write_int(&c,s);
+  write_int(&c,0);// horizontal resolution
+  write_int(&c,0);// vertical resolution
+  write_int(&c,0);//number of colors. 0 -> 1<<bits_per_pixel
+  write_int(&c,0);
+  // Pixel data
+  data+=3*W*H;
+  for (int y=0;y<H;++y){
+    data-=3*W;
+    const unsigned char *s=data;
+    unsigned char *p=c;
+    for (int x=0;x<W;++x){
+      *p++=s[2];
+      *p++=s[1];
+      *p++=s[0];
+      s+=3;
+    }
+    c+=R;
+  }
+  *return_size = fs;
+  return b;
+}
+
+
+// takes a raw RGB image and puts it in the copy/paste buffer
+void Fl_Wayland_Screen_Driver::copy_image(const unsigned char *data, int W, int H){
+  if (!data || W <= 0 || H <= 0) return;
+  delete[] fl_selection_buffer[1];
+  fl_selection_buffer[1] = (char *)create_bmp(data,W,H,&fl_selection_length[1]);
+  fl_selection_buffer_length[1] = fl_selection_length[1];
+  fl_i_own_selection[1] = 1;
+  fl_selection_type[1] = Fl::clipboard_image;
+  seat->data_source = wl_data_device_manager_create_data_source(seat->data_device_manager);
+  wl_data_source_add_listener(seat->data_source, &data_source_listener, NULL);
+  wl_data_source_offer(seat->data_source, "image/bmp");
+  wl_data_device_set_selection(seat->data_device, seat->data_source, seat->keyboard_enter_serial);
+//fprintf(stderr, "copy_image: len=%d\n", fl_selection_length[1]);
+}
+
+////////////////////////////////////////////////////////////////
+// Code for tracking clipboard changes:
+
+// is that possible with Wayland ?
+
+////////////////////////////////////////////////////////////////
+
+
+void Fl_Wayland_Window_Driver::resize(int X, int Y, int W, int H) {
+  int is_a_move = (X != x() || Y != y() || Fl_Window::is_a_rescale());
+  int is_a_resize = (W != w() || H != h() || Fl_Window::is_a_rescale());
+  if (is_a_move) force_position(1);
+  else if (!is_a_resize && !is_a_move) return;
+  if (is_a_resize) {
+    pWindow->Fl_Group::resize(X,Y,W,H);
+//fprintf(stderr, "resize: win=%p to %dx%d\n", pWindow, W, H);
+    if (shown()) {pWindow->redraw();}
+  } else {
+    if (pWindow->parent() || pWindow->menu_window() || pWindow->tooltip_window()) {
+      x(X); y(Y);
+//fprintf(stderr, "move menuwin=%p x()=%d\n", pWindow, X);
+    } else {
+      //"a deliberate design trait of Wayland makes application windows ignorant of their exact placement on screen"
+      x(0); y(0);
+    }
+  }
+  if (is_a_resize && !pWindow->resizable() && !shown()) {
+    pWindow->size_range(w(), h(), w(), h());
+  }
+    
+  if (shown()) {
+    struct wld_window *fl_win = fl_xid(pWindow);
+    if (is_a_resize) {
+      if (!pWindow->resizable()) pWindow->size_range(w(), h(), w(), h());
+      if (fl_win->frame) { // a decorated window
+        if (fl_win->buffer) {
+          Fl_Wayland_Graphics_Driver::buffer_release(fl_win);
+        }
+        fl_win->configured_width = W;
+        fl_win->configured_height = H;
+        if (!in_handle_configure) {
+          struct libdecor_state *state = libdecor_state_new(W, H);
+          libdecor_frame_commit(fl_win->frame, state, NULL); // necessary only if resize is initiated by prog
+          libdecor_state_free(state);
+        }
+      } else if (fl_win->subsurface) { // a subwindow
+        wl_subsurface_set_position(fl_win->subsurface, X, Y);
+        if (W != fl_win->configured_width || H != fl_win->configured_height) {
+          if (!pWindow->as_gl_window()) Fl_Wayland_Graphics_Driver::buffer_release(fl_win);
+        }
+        fl_win->configured_width = W;
+        fl_win->configured_height = H;
+      } else if (fl_win->xdg_surface) { // a window without border
+        if (W != fl_win->configured_width || H != fl_win->configured_height) {
+          if (!pWindow->as_gl_window()) Fl_Wayland_Graphics_Driver::buffer_release(fl_win);
+        }
+        fl_win->configured_width = W;
+        fl_win->configured_height = H;
+        xdg_surface_set_window_geometry(fl_win->xdg_surface, 0, 0, W, H);
+      }
+    } else {
+      //XMoveWindow(fl_display, fl_xid(pWindow), rint(X*s), rint(Y*s));
+      // Wayland doesn't seem to provide a reliable way for the app to set the window position on screen
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////
+
+
+extern Fl_Window *fl_xfocus;
+
+
+/* Change an existing window to fullscreen */
+void Fl_Wayland_Window_Driver::fullscreen_on() {
+    int top, bottom, left, right;
+
+    top = fullscreen_screen_top();
+    bottom = fullscreen_screen_bottom();
+    left = fullscreen_screen_left();
+    right = fullscreen_screen_right();
+
+    if ((top < 0) || (bottom < 0) || (left < 0) || (right < 0)) {
+      top = screen_num();
+      bottom = top;
+      left = top;
+      right = top;
+    }
+  if (fl_xid(pWindow)->xdg_toplevel) {
+    xdg_toplevel_set_fullscreen(fl_xid(pWindow)->xdg_toplevel, NULL);
+    pWindow->_set_fullscreen();
+    Fl::handle(FL_FULLSCREEN, pWindow);
+  }
+}
+
+void Fl_Wayland_Window_Driver::fullscreen_off(int X, int Y, int W, int H) {
+  if (!border()) pWindow->Fl_Group::resize(X, Y, W, H);
+  xdg_toplevel_unset_fullscreen(fl_xid(pWindow)->xdg_toplevel);
+  pWindow->_clear_fullscreen();
+  Fl::handle(FL_FULLSCREEN, pWindow);
+}
+
+////////////////////////////////////////////////////////////////
+
+void Fl_Wayland_Screen_Driver::default_icons(const Fl_RGB_Image *icons[], int count) {
+}
+
+void Fl_Wayland_Window_Driver::set_icons() {
+}
+
+////////////////////////////////////////////////////////////////
+
+
+int Fl_Wayland_Window_Driver::set_cursor(const Fl_RGB_Image *image, int hotx, int hoty) {
+//TODO
+  return 0;
+}
+
+////////////////////////////////////////////////////////////////
+
+// returns pointer to the filename, or null if name ends with '/'
+const char *Fl_Wayland_System_Driver::filename_name(const char *name) {
+  const char *p,*q;
+  if (!name) return (0);
+  for (p=q=name; *p;) if (*p++ == '/') q = p;
+  return q;
+}
+
+void Fl_Wayland_Window_Driver::label(const char *name, const char *iname) {
+  if (shown() && !parent()) {
+    if (!name) name = "";
+    if (!iname) iname = fl_filename_name(name);
+    libdecor_frame_set_title(fl_xid(pWindow)->frame, name);
+  }
+}
+
+
+//#define USE_PRINT_BUTTON 1
+#ifdef USE_PRINT_BUTTON
+
+// to test the Fl_Printer class creating a "Print front window" button in a separate window
+#include <FL/Fl_Printer.H>
+#include <FL/Fl_Button.H>
+
+void printFront(Fl_Widget *o, void *data)
+{
+  Fl_Printer printer;
+  o->window()->hide();
+  Fl_Window *win = Fl::first_window()->top_window();
+  if(!win) return;
+  int w, h;
+  if( printer.begin_job(1) ) { o->window()->show(); return; }
+  if( printer.begin_page() ) { o->window()->show(); return; }
+  printer.printable_rect(&w,&h);
+  // scale the printer device so that the window fits on the page
+  float scale = 1;
+  int ww = win->decorated_w();
+  int wh = win->decorated_h();
+  if (ww > w || wh > h) {
+    scale = (float)w/ww;
+    if ((float)h/wh < scale) scale = (float)h/wh;
+    printer.scale(scale, scale);
+    printer.printable_rect(&w, &h);
+  }
+
+// #define ROTATE 20.0
+#ifdef ROTATE
+  printer.scale(scale * 0.8, scale * 0.8);
+  printer.printable_rect(&w, &h);
+  printer.origin(w/2, h/2 );
+  printer.rotate(ROTATE);
+  printer.print_window( win, - win->w()/2, - win->h()/2);
+  //printer.print_window_part( win, 0,0, win->w(), win->h(), - win->w()/2, - win->h()/2 );
+#else
+  printer.origin(w/2, h/2 );
+  printer.print_window(win, -ww/2, -wh/2);
+  //printer.print_window_part( win, 0,0, win->w(), win->h(), -ww/2, -wh/2 );
+#endif
+
+  printer.end_page();
+  printer.end_job();
+  o->window()->show();
+}
+
+#include <FL/Fl_Copy_Surface.H>
+void copyFront(Fl_Widget *o, void *data)
+{
+  o->window()->hide();
+  Fl_Window *win = Fl::first_window();
+  if (!win) return;
+  Fl_Copy_Surface *surf = new Fl_Copy_Surface(win->decorated_w(), win->decorated_h());
+  Fl_Surface_Device::push_current(surf);
+  surf->draw_decorated_window(win); // draw the window content
+  Fl_Surface_Device::pop_current();
+  delete surf; // put the window on the clipboard
+  o->window()->show();
+}
+
+static int prepare_print_button() {
+  static Fl_Window w(0,0,140,60);
+  static Fl_Button bp(0,0,w.w(),30, "Print front window");
+  bp.callback(printFront);
+  static Fl_Button bc(0,30,w.w(),30, "Copy front window");
+  bc.callback(copyFront);
+  w.end();
+  w.show();
+  return 0;
+}
+
+static int unused = prepare_print_button();
+
+#endif // USE_PRINT_BUTTON
+
+#endif // !defined(FL_DOXYGEN)
+
+/* code to use a wl_callback object :
+ 
+static void sync_with_composer(void *data, struct wl_callback *wl_callback, uint32_t callback_data) {
+  *(bool*)data = true;
+}
+
+struct wl_callback_listener sync_listener = {
+  sync_with_composer
+};
+
+struct wl_callback *cb = wl_display_sync(fl_display);
+static bool done = false;
+wl_callback_add_listener(cb, &sync_listener, &done);
+while (!done) wl_display_dispatch(fl_display);
+
+*/
diff --git src/drivers/Wayland/wayland.H src/drivers/Wayland/wayland.H
new file mode 100755
index 0000000..af0851d
--- /dev/null
+++ src/drivers/Wayland/wayland.H
@@ -0,0 +1,31 @@
+//
+// Wayland platform header file for the Fast Light Tool Kit (FLTK).
+//
+// Copyright 1998-2021 by Bill Spitzak and others.
+//
+// This library is free software. Distribution and use rights are outlined in
+// the file "COPYING" which should have been included with this file.  If this
+// file is missing or damaged, see the license at:
+//
+//     https://www.fltk.org/COPYING.php
+//
+// Please see the following page on how to report bugs and issues:
+//
+//     https://www.fltk.org/bugs.php
+//
+
+#if !defined(FL_PLATFORM_H)
+#  error "Never use <FL/wayland.H> directly; include <FL/platform.H> instead."
+#endif // !FL_PLATFORM_H
+
+typedef struct wld_window *Window;
+
+extern struct wl_display *fl_display;
+
+struct flWaylandRegion {
+  int count;
+  struct _cairo_rectangle *rects;
+}; // a region is the union of a series of rectangles
+
+#include <stdint.h>
+extern FL_EXPORT uint32_t fl_event_time;
Direct Link to Message ]
 
     
Previous Message ]Next Message ]
 
 

Comments are owned by the poster. All other content is copyright 1998-2024 by Bill Spitzak and others. This project is hosted by The FLTK Team. Please report site problems to 'erco@seriss.com'.