SDL: wayland: client-side decoration

From 9e6fcbe72c3550d2007d54bcdab1178f90803eda Mon Sep 17 00:00:00 2001
From: Christian Rauch <[EMAIL REDACTED]>
Date: Thu, 11 Jun 2020 22:10:28 +0100
Subject: [PATCH] wayland: client-side decoration

---
 CMakeLists.txt                        |   8 +
 cmake/sdlchecks.cmake                 |   9 +
 configure.ac                          |  38 ++++
 include/SDL_config.h.cmake            |   1 +
 include/SDL_config.h.in               |   2 +
 src/video/wayland/SDL_waylanddyn.c    |   6 +-
 src/video/wayland/SDL_waylanddyn.h    |  44 +++++
 src/video/wayland/SDL_waylandevents.c |  40 +++++
 src/video/wayland/SDL_waylandsym.h    |  53 ++++++
 src/video/wayland/SDL_waylandvideo.c  |  50 +++++-
 src/video/wayland/SDL_waylandvideo.h  |   3 +
 src/video/wayland/SDL_waylandwindow.c | 240 +++++++++++++++++++++++++-
 src/video/wayland/SDL_waylandwindow.h |  12 ++
 13 files changed, 499 insertions(+), 7 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 665bca6e83..6df265618b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -388,6 +388,7 @@ set_option(CLOCK_GETTIME       "Use clock_gettime() instead of gettimeofday()" O
 set_option(VIDEO_X11           "Use X11 video driver" ${UNIX_SYS})
 set_option(VIDEO_WAYLAND       "Use Wayland video driver" ${UNIX_SYS})
 dep_option(WAYLAND_SHARED      "Dynamically load Wayland support" ON "VIDEO_WAYLAND" OFF)
+dep_option(WAYLAND_LIBDECOR    "Use client-side window decorations on Wayland" ON "VIDEO_WAYLAND" ON)
 dep_option(VIDEO_WAYLAND_QT_TOUCH  "QtWayland server support for Wayland video driver" ON "VIDEO_WAYLAND" OFF)
 set_option(VIDEO_RPI           "Use Raspberry Pi video driver" ${UNIX_SYS})
 dep_option(X11_SHARED          "Dynamically load X11 support" ON "VIDEO_X11" OFF)
@@ -2593,6 +2594,9 @@ if(SDL_SHARED)
     set_property(TARGET SDL2 APPEND_STRING PROPERTY COMPILE_FLAGS "-fobjc-arc")
     target_compile_definitions(SDL2 PRIVATE IOS_DYLIB=1)
   endif()
+  if(WAYLAND_LIBDECOR)
+    target_include_directories(SDL2 PRIVATE "${libdecor_INCLUDE_DIRS}")
+  endif()
 endif()
 
 if(ANDROID)
@@ -2644,6 +2648,10 @@ if(SDL_STATIC)
   if(IOS OR TVOS)
     set_property(TARGET SDL2-static APPEND_STRING PROPERTY COMPILE_FLAGS "-fobjc-arc")
   endif()
+  if(WAYLAND_LIBDECOR)
+    target_include_directories(SDL2-static PRIVATE "${libdecor_INCLUDE_DIRS}")
+    target_link_libraries(SDL2-static "${libdecor_LIBRARIES}")
+  endif()
 endif()
 
 ##### Tests #####
diff --git a/cmake/sdlchecks.cmake b/cmake/sdlchecks.cmake
index d3f6cec1f7..8df592549e 100644
--- a/cmake/sdlchecks.cmake
+++ b/cmake/sdlchecks.cmake
@@ -697,6 +697,15 @@ macro(CheckWayland)
         set(EXTRA_LIBS ${WAYLAND_LIBRARIES} ${EXTRA_LIBS})
       endif()
 
+      if(WAYLAND_LIBDECOR)
+        pkg_check_modules(libdecor REQUIRED libdecor-0)
+
+        FindLibraryAndSONAME(decor-0)
+        set(SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_LIBDECOR "\"${DECOR_0_LIB_SONAME}\"")
+
+        add_definitions(-DHAVE_LIBDECOR_H)
+      endif()
+
       set(SDL_VIDEO_DRIVER_WAYLAND 1)
     endif()
   endif()
diff --git a/configure.ac b/configure.ac
index 10879ee8db..615048e41a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2777,6 +2777,43 @@ CheckFcitx()
     fi
 }
 
+dnl See if libdecor is available
+CheckLibDecor()
+{
+    AC_ARG_ENABLE(libdecor,
+[AS_HELP_STRING([--enable-libdecor], [use libdecor for Wayland client-side decorations [default=yes]])],
+                  , enable_libdecor=yes)
+    if test x$enable_libdecor = xyes; then
+        AC_MSG_CHECKING(for libdecor support)
+        AS_IF([$PKG_CONFIG --exists libdecor-0],
+              [video_libdecor=yes],
+              [video_libdecor=no])
+        AC_MSG_RESULT($video_libdecor)
+        if test x$video_libdecor = xyes; then
+            EXTRA_CFLAGS="$EXTRA_CFLAGS `$PKG_CONFIG --cflags libdecor-0`"
+            AC_DEFINE(HAVE_LIBDECOR_H, 1, [ ])
+
+            AC_ARG_ENABLE(libdecor-shared,
+[AS_HELP_STRING([--enable-libdecor-shared], [dynamically load libdecor [default=yes]])],
+                          , enable_libdecor_shared=yes)
+
+            decor_lib=[`find_lib "libdecor-0.so.*" "" | sed 's/.*\/\(.*\)/\1/; q'`]
+
+            if test x$have_loadso != xyes && \
+               test x$enable_libdecor_shared = xyes; then
+                AC_MSG_WARN([You must have SDL_LoadObject() support for dynamic libdecor loading])
+            fi
+            if test x$have_loadso = xyes && \
+               test x$enable_libdecor_shared = xyes && test x$decor_lib != x; then
+                echo "-- dynamic libdecor -> $decor_lib"
+                AC_DEFINE_UNQUOTED(SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_LIBDECOR, "$decor_lib", [ ])
+            else
+                EXTRA_LDFLAGS="$EXTRA_LDFLAGS `$PKG_CONFIG --libs libdecor-0`"
+            fi
+        fi
+    fi
+}
+
 dnl Check to see if GameController framework support is desired
 CheckJoystickMFI()
 {
@@ -3572,6 +3609,7 @@ case "$host" in
         CheckInotify
         CheckIBus
         CheckFcitx
+        CheckLibDecor
         case $ARCH in
           linux)
               CheckInputKD
diff --git a/include/SDL_config.h.cmake b/include/SDL_config.h.cmake
index 40c5a7ed87..897d06ac8e 100644
--- a/include/SDL_config.h.cmake
+++ b/include/SDL_config.h.cmake
@@ -387,6 +387,7 @@
 #cmakedefine SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_EGL @SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_EGL@
 #cmakedefine SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_CURSOR @SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_CURSOR@
 #cmakedefine SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_XKBCOMMON @SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_XKBCOMMON@
+#cmakedefine SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_LIBDECOR @SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_LIBDECOR@
 
 #cmakedefine SDL_VIDEO_DRIVER_X11 @SDL_VIDEO_DRIVER_X11@
 #cmakedefine SDL_VIDEO_DRIVER_X11_DYNAMIC @SDL_VIDEO_DRIVER_X11_DYNAMIC@
diff --git a/include/SDL_config.h.in b/include/SDL_config.h.in
index 9ddd932e37..ea877237cd 100644
--- a/include/SDL_config.h.in
+++ b/include/SDL_config.h.in
@@ -225,6 +225,7 @@
 #undef HAVE_IMMINTRIN_H
 #undef HAVE_LIBUDEV_H
 #undef HAVE_LIBSAMPLERATE_H
+#undef HAVE_LIBDECOR_H
 
 #undef HAVE_DDRAW_H
 #undef HAVE_DINPUT_H
@@ -365,6 +366,7 @@
 #undef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_EGL
 #undef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_CURSOR
 #undef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_XKBCOMMON
+#undef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_LIBDECOR
 #undef SDL_VIDEO_DRIVER_X11
 #undef SDL_VIDEO_DRIVER_RPI
 #undef SDL_VIDEO_DRIVER_KMSDRM
diff --git a/src/video/wayland/SDL_waylanddyn.c b/src/video/wayland/SDL_waylanddyn.c
index e729861883..2bbc49efe7 100644
--- a/src/video/wayland/SDL_waylanddyn.c
+++ b/src/video/wayland/SDL_waylanddyn.c
@@ -47,12 +47,16 @@ typedef struct
 #ifndef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_XKBCOMMON
 #define SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_XKBCOMMON NULL
 #endif
+#ifndef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_LIBDECOR
+#define SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_LIBDECOR NULL
+#endif
 
 static waylanddynlib waylandlibs[] = {
     {NULL, SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC},
     {NULL, SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_EGL},
     {NULL, SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_CURSOR},
-    {NULL, SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_XKBCOMMON}
+    {NULL, SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_XKBCOMMON},
+    {NULL, SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_LIBDECOR}
 };
 
 static void *
diff --git a/src/video/wayland/SDL_waylanddyn.h b/src/video/wayland/SDL_waylanddyn.h
index 55ac1ebf7f..eef24a6dd1 100644
--- a/src/video/wayland/SDL_waylanddyn.h
+++ b/src/video/wayland/SDL_waylanddyn.h
@@ -34,6 +34,19 @@ struct wl_display;
 struct wl_surface;
 struct wl_shm;
 
+/* We also need some for libdecor */
+struct wl_seat;
+struct wl_output;
+struct libdecor;
+struct libdecor_frame;
+struct libdecor_state;
+struct libdecor_configuration;
+struct libdecor_interface;
+struct libdecor_frame_interface;
+enum libdecor_resize_edge;
+enum libdecor_capabilities;
+enum libdecor_window_state;
+
 #include <stdint.h>
 #include "wayland-cursor.h"
 #include "wayland-util.h"
@@ -82,6 +95,8 @@ void SDL_WAYLAND_UnloadSymbols(void);
 #define wl_proxy_add_listener (*WAYLAND_wl_proxy_add_listener)
 #define wl_proxy_marshal_constructor (*WAYLAND_wl_proxy_marshal_constructor)
 #define wl_proxy_marshal_constructor_versioned (*WAYLAND_wl_proxy_marshal_constructor_versioned)
+#define wl_proxy_set_tag (*WAYLAND_wl_proxy_set_tag)
+#define wl_proxy_get_tag (*WAYLAND_wl_proxy_get_tag)
 
 #define wl_seat_interface (*WAYLAND_wl_seat_interface)
 #define wl_surface_interface (*WAYLAND_wl_surface_interface)
@@ -101,6 +116,35 @@ void SDL_WAYLAND_UnloadSymbols(void);
 #define wl_data_source_interface (*WAYLAND_wl_data_source_interface)
 #define wl_data_device_manager_interface (*WAYLAND_wl_data_device_manager_interface)
 
+#ifdef HAVE_LIBDECOR_H
+/* Must be included before our defines */
+#include <libdecor.h>
+
+#define libdecor_unref (*WAYLAND_libdecor_unref)
+#define libdecor_new (*WAYLAND_libdecor_new)
+#define libdecor_decorate (*WAYLAND_libdecor_decorate)
+#define libdecor_frame_unref (*WAYLAND_libdecor_frame_unref)
+#define libdecor_frame_set_title (*WAYLAND_libdecor_frame_set_title)
+#define libdecor_frame_set_app_id (*WAYLAND_libdecor_frame_set_app_id)
+#define libdecor_frame_set_max_content_size (*WAYLAND_libdecor_frame_set_max_content_size)
+#define libdecor_frame_set_min_content_size (*WAYLAND_libdecor_frame_set_min_content_size)
+#define libdecor_frame_resize (*WAYLAND_libdecor_frame_resize)
+#define libdecor_frame_move (*WAYLAND_libdecor_frame_move)
+#define libdecor_frame_commit (*WAYLAND_libdecor_frame_commit)
+#define libdecor_frame_set_minimized (*WAYLAND_libdecor_frame_set_minimized)
+#define libdecor_frame_set_maximized (*WAYLAND_libdecor_frame_set_maximized)
+#define libdecor_frame_unset_maximized (*WAYLAND_libdecor_frame_unset_maximized)
+#define libdecor_frame_set_fullscreen (*WAYLAND_libdecor_frame_set_fullscreen)
+#define libdecor_frame_unset_fullscreen (*WAYLAND_libdecor_frame_unset_fullscreen)
+#define libdecor_frame_set_capabilities (*WAYLAND_libdecor_frame_set_capabilities)
+#define libdecor_frame_unset_capabilities (*WAYLAND_libdecor_frame_unset_capabilities)
+#define libdecor_frame_map (*WAYLAND_libdecor_frame_map)
+#define libdecor_state_new (*WAYLAND_libdecor_state_new)
+#define libdecor_state_free (*WAYLAND_libdecor_state_free)
+#define libdecor_configuration_get_content_size (*WAYLAND_libdecor_configuration_get_content_size)
+#define libdecor_configuration_get_window_state (*WAYLAND_libdecor_configuration_get_window_state)
+#endif
+
 #endif /* SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC */
 
 #include "wayland-client-protocol.h"
diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c
index b69171ab6a..d0c681173a 100644
--- a/src/video/wayland/SDL_waylandevents.c
+++ b/src/video/wayland/SDL_waylandevents.c
@@ -43,6 +43,10 @@
 #include "xdg-shell-unstable-v6-client-protocol.h"
 #include "keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h"
 
+#ifdef HAVE_LIBDECOR_H
+#include <libdecor.h>
+#endif
+
 #ifdef SDL_INPUT_LINUXEV
 #include <linux/input.h>
 #else
@@ -272,6 +276,11 @@ pointer_handle_enter(void *data, struct wl_pointer *pointer,
         return;
     }
 
+    /* check that this surface belongs to one of the SDL windows */
+    if (!SDL_WAYLAND_own_surface(surface)) {
+        return;
+    }
+
     /* This handler will be called twice in Wayland 1.4
      * Once for the window surface which has valid user data
      * and again for the mouse cursor surface which does not have valid user data
@@ -301,6 +310,10 @@ pointer_handle_leave(void *data, struct wl_pointer *pointer,
 {
     struct SDL_WaylandInput *input = data;
 
+    if (!surface || !SDL_WAYLAND_own_surface(surface)) {
+        return;
+    }
+
     if (input->pointer_focus) {
         SDL_SetMouseFocus(NULL);
         input->pointer_focus = NULL;
@@ -328,8 +341,20 @@ ProcessHitTest(struct SDL_WaylandInput *input, uint32_t serial)
            WL_SHELL_SURFACE_RESIZE_*), but the values are the same. */
         const uint32_t *directions_zxdg = directions_wl;
 
+#ifdef HAVE_LIBDECOR_H
+        /* ditto for libdecor. */
+        const uint32_t *directions_libdecor = directions_wl;
+#endif
+
         switch (rc) {
             case SDL_HITTEST_DRAGGABLE:
+#ifdef HAVE_LIBDECOR_H
+                if (input->display->shell.libdecor) {
+                    if (window_data->shell_surface.libdecor.frame) {
+                        libdecor_frame_move(window_data->shell_surface.libdecor.frame, input->seat, serial);
+                    }
+                } else
+#endif
                 if (input->display->shell.xdg) {
                     if (window_data->shell_surface.xdg.roleobj.toplevel) {
                         xdg_toplevel_move(window_data->shell_surface.xdg.roleobj.toplevel,
@@ -357,6 +382,13 @@ ProcessHitTest(struct SDL_WaylandInput *input, uint32_t serial)
             case SDL_HITTEST_RESIZE_BOTTOM:
             case SDL_HITTEST_RESIZE_BOTTOMLEFT:
             case SDL_HITTEST_RESIZE_LEFT:
+#ifdef HAVE_LIBDECOR_H
+                if (input->display->shell.libdecor) {
+                    if (window_data->shell_surface.libdecor.frame) {
+                        libdecor_frame_resize(window_data->shell_surface.libdecor.frame, input->seat, serial, directions_libdecor[rc - SDL_HITTEST_RESIZE_TOPLEFT]);
+                    }
+                } else
+#endif
                 if (input->display->shell.xdg) {
                     if (window_data->shell_surface.xdg.roleobj.toplevel) {
                         xdg_toplevel_resize(window_data->shell_surface.xdg.roleobj.toplevel,
@@ -723,6 +755,10 @@ keyboard_handle_enter(void *data, struct wl_keyboard *keyboard,
         return;
     }
 
+    if (!SDL_WAYLAND_own_surface(surface)) {
+        return;
+    }
+
     window = wl_surface_get_user_data(surface);
 
     if (window) {
@@ -741,6 +777,10 @@ keyboard_handle_leave(void *data, struct wl_keyboard *keyboard,
 {
     struct SDL_WaylandInput *input = data;
 
+    if (!surface || !SDL_WAYLAND_own_surface(surface)) {
+        return;
+    }
+
     /* Stop key repeat before clearing keyboard focus */
     keyboard_repeat_clear(&input->keyboard_repeat);
 
diff --git a/src/video/wayland/SDL_waylandsym.h b/src/video/wayland/SDL_waylandsym.h
index 278c4ff178..76b9c276f5 100644
--- a/src/video/wayland/SDL_waylandsym.h
+++ b/src/video/wayland/SDL_waylandsym.h
@@ -33,6 +33,8 @@
 #define SDL_WAYLAND_INTERFACE(iface)
 #endif
 
+#include <stdbool.h>
+
 SDL_WAYLAND_MODULE(WAYLAND_CLIENT)
 SDL_WAYLAND_SYM(void, wl_proxy_marshal, (struct wl_proxy *, uint32_t, ...))
 SDL_WAYLAND_SYM(struct wl_proxy *, wl_proxy_create, (struct wl_proxy *, const struct wl_interface *))
@@ -71,6 +73,10 @@ SDL_WAYLAND_SYM(struct wl_proxy *, wl_proxy_marshal_constructor, (struct wl_prox
 SDL_WAYLAND_MODULE(WAYLAND_CLIENT_1_10)
 SDL_WAYLAND_SYM(struct wl_proxy *, wl_proxy_marshal_constructor_versioned, (struct wl_proxy *proxy, uint32_t opcode, const struct wl_interface *interface, uint32_t version, ...))
 
+SDL_WAYLAND_MODULE(WAYLAND_CLIENT_1_18)
+SDL_WAYLAND_SYM(void, wl_proxy_set_tag, (struct wl_proxy *, const char * const *))
+SDL_WAYLAND_SYM(const char * const *, wl_proxy_get_tag, (struct wl_proxy *))
+
 SDL_WAYLAND_INTERFACE(wl_seat_interface)
 SDL_WAYLAND_INTERFACE(wl_surface_interface)
 SDL_WAYLAND_INTERFACE(wl_shm_pool_interface)
@@ -134,6 +140,53 @@ SDL_WAYLAND_SYM(int, xkb_keymap_key_get_syms_by_level, (struct xkb_keymap *,
                                                         const xkb_keysym_t **) )
 SDL_WAYLAND_SYM(uint32_t, xkb_keysym_to_utf32, (xkb_keysym_t) )
 
+#ifdef HAVE_LIBDECOR_H
+SDL_WAYLAND_MODULE(WAYLAND_LIBDECOR)
+SDL_WAYLAND_SYM(void, libdecor_unref, (struct libdecor *))
+SDL_WAYLAND_SYM(struct libdecor *, libdecor_new, (struct wl_display *, struct libdecor_interface *))
+SDL_WAYLAND_SYM(struct libdecor_frame *, libdecor_decorate, (struct libdecor *,\
+                                                             struct wl_surface *,\
+                                                             struct libdecor_frame_interface *,\
+                                                             void *))
+SDL_WAYLAND_SYM(void, libdecor_frame_unref, (struct libdecor_frame *))
+SDL_WAYLAND_SYM(void, libdecor_frame_set_title, (struct libdecor_frame *, const char *))
+SDL_WAYLAND_SYM(void, libdecor_frame_set_app_id, (struct libdecor_frame *, const char *))
+SDL_WAYLAND_SYM(void, libdecor_frame_set_max_content_size, (struct libdecor_frame *frame,\
+                                                            int content_width,\
+                                                            int content_height))
+SDL_WAYLAND_SYM(void, libdecor_frame_set_min_content_size, (struct libdecor_frame *frame,\
+                                                            int content_width,\
+                                                            int content_height))
+SDL_WAYLAND_SYM(void, libdecor_frame_resize, (struct libdecor_frame *,\
+                                              struct wl_seat *,\
+                                              uint32_t,\
+                                              enum libdecor_resize_edge))
+SDL_WAYLAND_SYM(void, libdecor_frame_move, (struct libdecor_frame *,\
+                                            struct wl_seat *,\
+                                            uint32_t))
+SDL_WAYLAND_SYM(void, libdecor_frame_commit, (struct libdecor_frame *,\
+                                              struct libdecor_state *,\
+                                              struct libdecor_configuration *))
+SDL_WAYLAND_SYM(void, libdecor_frame_set_minimized, (struct libdecor_frame *))
+SDL_WAYLAND_SYM(void, libdecor_frame_set_maximized, (struct libdecor_frame *))
+SDL_WAYLAND_SYM(void, libdecor_frame_unset_maximized, (struct libdecor_frame *))
+SDL_WAYLAND_SYM(void, libdecor_frame_set_fullscreen, (struct libdecor_frame *, struct wl_output *))
+SDL_WAYLAND_SYM(void, libdecor_frame_unset_fullscreen, (struct libdecor_frame *))
+SDL_WAYLAND_SYM(void, libdecor_frame_set_capabilities, (struct libdecor_frame *, \
+                                                        enum libdecor_capabilities))
+SDL_WAYLAND_SYM(void, libdecor_frame_unset_capabilities, (struct libdecor_frame *, \
+                                                          enum libdecor_capabilities))
+SDL_WAYLAND_SYM(void, libdecor_frame_map, (struct libdecor_frame *))
+SDL_WAYLAND_SYM(struct libdecor_state *, libdecor_state_new, (int, int))
+SDL_WAYLAND_SYM(void, libdecor_state_free, (struct libdecor_state *))
+SDL_WAYLAND_SYM(bool, libdecor_configuration_get_content_size, (struct libdecor_configuration *,\
+                                                                struct libdecor_frame *,\
+                                                                int *,\
+                                                                int *))
+SDL_WAYLAND_SYM(bool, libdecor_configuration_get_window_state, (struct libdecor_configuration *,\
+                                                                enum libdecor_window_state *))
+#endif
+
 #undef SDL_WAYLAND_MODULE
 #undef SDL_WAYLAND_SYM
 #undef SDL_WAYLAND_INTERFACE
diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c
index 44b2163619..ba5dc23762 100644
--- a/src/video/wayland/SDL_waylandvideo.c
+++ b/src/video/wayland/SDL_waylandvideo.c
@@ -53,6 +53,10 @@
 #include "idle-inhibit-unstable-v1-client-protocol.h"
 #include "xdg-activation-v1-client-protocol.h"
 
+#ifdef HAVE_LIBDECOR_H
+#include <libdecor.h>
+#endif
+
 #define WAYLANDVID_DRIVER_NAME "wayland"
 
 /* Initialization/Query functions */
@@ -433,6 +437,21 @@ static const struct xdg_wm_base_listener shell_listener_xdg = {
 };
 
 
+#ifdef HAVE_LIBDECOR_H
+static void
+libdecor_error(struct libdecor *context,
+               enum libdecor_error error,
+               const char *message)
+{
+    SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "libdecor error (%d): %s\n", error, message);
+}
+
+static struct libdecor_interface libdecor_interface = {
+    libdecor_error,
+};
+#endif
+
+
 static void
 display_handle_global(void *data, struct wl_registry *registry, uint32_t id,
                       const char *interface, uint32_t version)
@@ -447,13 +466,25 @@ display_handle_global(void *data, struct wl_registry *registry, uint32_t id,
         Wayland_add_display(d, id);
     } else if (SDL_strcmp(interface, "wl_seat") == 0) {
         Wayland_display_add_input(d, id, version);
-    } else if (SDL_strcmp(interface, "xdg_wm_base") == 0) {
+    } else if (
+#ifdef HAVE_LIBDECOR_H
+               !d->shell.libdecor &&
+#endif
+               SDL_strcmp(interface, "xdg_wm_base") == 0) {
         d->shell.xdg = wl_registry_bind(d->registry, id, &xdg_wm_base_interface, 1);
         xdg_wm_base_add_listener(d->shell.xdg, &shell_listener_xdg, NULL);
-    } else if (SDL_strcmp(interface, "zxdg_shell_v6") == 0) {
+    } else if (
+#ifdef HAVE_LIBDECOR_H
+               !d->shell.libdecor &&
+#endif
+               SDL_strcmp(interface, "zxdg_shell_v6") == 0) {
         d->shell.zxdg = wl_registry_bind(d->registry, id, &zxdg_shell_v6_interface, 1);
         zxdg_shell_v6_add_listener(d->shell.zxdg, &shell_listener_zxdg, NULL);
-    } else if (SDL_strcmp(interface, "wl_shell") == 0) {
+    } else if (
+#ifdef HAVE_LIBDECOR_H
+               !d->shell.libdecor &&
+#endif
+               SDL_strcmp(interface, "wl_shell") == 0) {
         d->shell.wl = wl_registry_bind(d->registry, id, &wl_shell_interface, 1);
     } else if (SDL_strcmp(interface, "wl_shm") == 0) {
         d->shm = wl_registry_bind(registry, id, &wl_shm_interface, 1);
@@ -510,6 +541,12 @@ Wayland_VideoInit(_THIS)
         return SDL_SetError("Failed to get the Wayland registry");
     }
 
+#ifdef HAVE_LIBDECOR_H
+    if (SDL_WAYLAND_HAVE_WAYLAND_LIBDECOR) {
+        data->shell.libdecor = libdecor_new(data->display, &libdecor_interface);
+    }
+#endif
+
     wl_registry_add_listener(data->registry, &registry_listener, data);
 
     // First roundtrip to receive all registry objects.
@@ -632,6 +669,13 @@ Wayland_VideoQuit(_THIS)
     if (data->decoration_manager)
         zxdg_decoration_manager_v1_destroy(data->decoration_manager);
 
+#ifdef HAVE_LIBDECOR_H
+    if (data->shell.libdecor) {
+        libdecor_unref(data->shell.libdecor);
+        data->shell.libdecor = NULL;
+    }
+#endif
+
     if (data->compositor)
         wl_compositor_destroy(data->compositor);
 
diff --git a/src/video/wayland/SDL_waylandvideo.h b/src/video/wayland/SDL_waylandvideo.h
index 0c6957f002..54201e2269 100644
--- a/src/video/wayland/SDL_waylandvideo.h
+++ b/src/video/wayland/SDL_waylandvideo.h
@@ -62,6 +62,9 @@ typedef struct {
         struct xdg_wm_base *xdg;
         struct zxdg_shell_v6 *zxdg;
         struct wl_shell *wl;
+#ifdef HAVE_LIBDECOR_H
+        struct libdecor *libdecor;
+#endif
     } shell;
     struct zwp_relative_pointer_manager_v1 *relative_pointer_manager;
     struct zwp_pointer_constraints_v1 *pointer_constraints;
diff --git a/src/video/wayland/SDL_waylandwindow.c b/src/video/wayland/SDL_waylandwindow.c
index c18e159e8a..cc843c7181 100644
--- a/src/video/wayland/SDL_waylandwindow.c
+++ b/src/video/wayland/SDL_waylandwindow.c
@@ -39,6 +39,20 @@
 #include "idle-inhibit-unstable-v1-client-protocol.h"
 #include "xdg-activation-v1-client-protocol.h"
 
+#ifdef HAVE_LIBDECOR_H
+#include <libdecor.h>
+#endif
+
+static const char *SDL_WAYLAND_surface_tag = "sdl-window";
+
+SDL_bool SDL_WAYLAND_own_surface(struct wl_surface *surface)
+{
+    if (SDL_WAYLAND_HAVE_WAYLAND_CLIENT_1_18) {
+        return wl_proxy_get_tag((struct wl_proxy *) surface) == &SDL_WAYLAND_surface_tag;
+    }
+    return SDL_TRUE; /* For older clients we have to assume this is us... */
+}
+
 static float get_window_scale_factor(SDL_Window *window) {
       return ((SDL_WindowData*)window->driverdata)->scale_factor;
 }
@@ -67,6 +81,19 @@ CommitMinMaxDimensions(SDL_Window *window)
         max_height = window->windowed.h;
     }
 
+#ifdef HAVE_LIBDECOR_H
+    if (data->shell.libdecor) {
+        if (wind->shell_surface.libdecor.frame == NULL) {
+            return; /* Can't do anything yet, wait for ShowWindow */
+        }
+        libdecor_frame_set_min_content_size(wind->shell_surface.libdecor.frame,
+                                            min_width,
+                                            min_height);
+        libdecor_frame_set_max_content_size(wind->shell_surface.libdecor.frame,
+                                            max_width,
+                                            max_height);
+    } else
+#endif
     if (data->shell.xdg) {
         if (wind->shell_surface.xdg.roleobj.toplevel == NULL) {
             return; /* Can't do anything yet, wait for ShowWindow */
@@ -77,6 +104,7 @@ CommitMinMaxDimensions(SDL_Window *window)
         xdg_toplevel_set_max_size(wind->shell_surface.xdg.roleobj.toplevel,
                                   max_width,
                                   max_height);
+        wl_surface_commit(wind->surface);
     } else if (data->shell.zxdg) {
         if (wind->shell_surface.zxdg.roleobj.toplevel == NULL) {
             return; /* Can't do anything yet, wait for ShowWindow */
@@ -87,9 +115,8 @@ CommitMinMaxDimensions(SDL_Window *window)
         zxdg_toplevel_v6_set_max_size(wind->shell_surface.zxdg.roleobj.toplevel,
                                       max_width,
                                       max_height);
+        wl_surface_commit(wind->surface);
     }
-
-    wl_surface_commit(wind->surface);
 }
 
 static void
@@ -103,6 +130,18 @@ SetFullscreen(SDL_Window *window, struct wl_output *output)
      */
     CommitMinMaxDimensions(window);
 
+#ifdef HAVE_LIBDECOR_H
+    if (viddata->shell.libdecor) {
+        if (wind->shell_surface.libdecor.frame == NULL) {
+            return; /* Can't do anything yet, wait for ShowWindow */
+        }
+        if (output) {
+            libdecor_frame_set_fullscreen(wind->shell_surface.libdecor.frame, output);
+        } else {
+            libdecor_frame_unset_fullscreen(wind->shell_surface.libdecor.frame);
+        }
+    } else
+#endif
     if (viddata->shell.xdg) {
         if (wind->shell_surface.xdg.roleobj.toplevel == NULL) {
             return; /* Can't do anything yet, wait for ShowWindow */
@@ -481,8 +520,97 @@ static const struct xdg_toplevel_listener toplevel_listener_xdg = {
     handle_close_xdg_toplevel
 };
 
+#ifdef HAVE_LIBDECOR_H
+static void
+decoration_frame_configure(struct libdecor_frame *frame,
+                           struct libdecor_configuration *configuration,
+                           void *user_data)
+{
+    SDL_WindowData *wind = user_data;
+    SDL_Window *window = wind->sdlwindow;
+    int width, height;
+    enum libdecor_window_state window_state;
+    struct libdecor_state *state;
+
+    /* window size */
+    if (!libdecor_configuration_get_content_size(configuration, frame, &width, &height)) {
+        width = window->w;
+        height = window->h;
+    }
+
+    wind->resize.width = width;
+    wind->resize.height = height;
+
+    wind->resize.pending = SDL_TRUE;
+    wind->resize.configure = SDL_TRUE;
+    Wayland_HandlePendingResize(window);
+    wind->shell_surface.libdecor.initial_configure_seen = SDL_TRUE;
+
+    window->w = wind->resize.width;
+    window->h = wind->resize.height;
+
+    /* window state */
+    if (!libdecor_configuration_get_window_state(configuration, &window_state)) {
+        window_state = LIBDECOR_WINDOW_STATE_NONE;
+    }
+
+    if (window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED) {
+        window->flags |= SDL_WINDOW_MAXIMIZED;
+    }
+    else {
+        window->flags &= ~SDL_WINDOW_MAXIMIZED;
+    }
+
+    if (window_state & LIBDECOR_WINDOW_STATE_ACTIVE) {
+        window->flags |= SDL_WINDOW_INPUT_FOCUS;
+    }
+    else {
+        window->flags &= ~SDL_WINDOW_INPUT_FOCUS;
+    }
+
+    /* The fullscreen flag is already set my some other entity when 'SDL_SetWindowFullscreen'
+     * is called, but we will set it here again in case the compositor requests fullscreen.
+     */
+    if (window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN) {
+        window->flags |= SDL_WINDOW_FULLSCREEN;
+    }
+    else {
+        window->flags &= ~SDL_WINDOW_FULLSCREEN;
+    }
+
+    /* commit frame state */
+    state = libdecor_state_new(width, height);
+    libdecor_frame_commit(frame, state, configuration);
+    libdecor_state_free(state);
+
+    /* Update the resize capability. Since this will change the capabilities and
+     * commit a new frame state with the last known content dimension, this has
+     * to be called after the new state has been commited and the new content
+     * dimensions were updated. */
+    Wayland_SetWindowResizable(SDL_GetVideoDevice(), window,
+                               window->flags & SDL_WINDOW_RESIZABLE);
+}
+
+static void
+decoration_frame_close(struct libdecor_frame *frame, void *user_data)
+{
+    SDL_SendWindowEvent(((SDL_WindowData *)user_data)->sdlwindow, SDL_WINDOWEVENT_CLOSE, 0, 0);
+}
+
+static void
+decoration_frame_commit(struct libdecor_frame *frame, void *user_data)
+{
+    SDL_WindowData *wind = user_data;
 
+    SDL_SendWindowEvent(wind->sdlwindow, SDL_WINDOWEVENT_EXPOSED, 0, 0);
+}
 
+static struct libdecor_frame_interface libdecor_frame_interface = {
+    decoration_frame_configure,
+    decoration_frame_close,
+    decoration_frame_commit,
+};
+#endif
 
 #ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH
 static void
@@ -706,6 +834,20 @@ void Wayland_ShowWindow(_THIS, SDL_Window *window)
     wl_surface_commit(data->surface);
 
     /* Create the shell surface and map the toplevel */
+#ifdef HAVE_LIBDECOR_H
+    if (c->shell.libdecor) {
+        data->shell_surface.libdecor.frame = libdecor_decorate(c->shell.libdecor,
+                                                               data->surface,
+                                                               &libdecor_frame_interface,
+                                                               data);
+        if (data->shell_surface.libdecor.frame == NULL) {
+            SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Failed to create libdecor frame!");
+        } else {
+            libdecor_frame_set_app_id(data->shell_surface.libdecor.frame, c->classname);
+            libdecor_frame_map(data->shell_surface.libdecor.frame);
+        }
+    } else
+#endif
     if (c->shell.xdg) {
         data->shell_surface.xdg.surface = xdg_wm_base_get_xdg_surface(c->shell.xdg, data->surface);
         xdg_surface_set_user_data(data->shell_surface.xdg.surface, data);
@@ -744,6 +886,16 @@ void Wayland_ShowWindow(_THIS, SDL_Window *window)
     /* We have to wait until the surface gets a "configure" event, or use of
      * this surface will fail. This is a new rule for xdg_shell.
      */
+#ifdef HAVE_LIBDECOR_H
+    if (c->shell.libdecor) {
+        if (data->shell_surface.libdecor.frame) {
+            while (!data->shell_surface.libdecor.initial_configure_seen) {
+                WAYLAND_wl_display_flush(c->display);
+                WAYLAND_wl_display_dispatch(c->display);
+            }
+        }
+    } else
+#endif
     if (c->shell.xdg) {
         if (data->shell_surface.xdg.surface) {
             while (!data->shell_surface.xdg.initial_configure_seen) {
@@ -801,6 +953,14 @@ void Wayland_HideWindow(_THIS, SDL_Window *window)
        wind->server_decoration = NULL;
     }
 
+#ifdef HAVE_LIBDECOR_H
+    if (data->shell.libdecor) {
+        if (wind->shell_surface.libdecor.frame) {
+            libdecor_frame_unref(wind->shell_surface.libdecor.frame);
+            wind->shell_surface.libdecor.frame = NULL;
+        }
+    } else
+#endif
     if (data->shell.x

(Patch may be truncated, please check the link at the top of this post.)