diff --git a/hw/xwayland/man/Xwayland.man b/hw/xwayland/man/Xwayland.man
index 4e7877730fc81e1d80b4753c2ab84d72aa8011ca..02b333be9f215bff8f2cd8a9947a812ba9dccf0d 100644
--- a/hw/xwayland/man/Xwayland.man
+++ b/hw/xwayland/man/Xwayland.man
@@ -46,6 +46,14 @@ Like all of the X servers, \fIXwayland\fP accepts the command line options
 described in the \fIXserver\fP(@miscmansuffix@) manual page.
 The following additional arguments are supported as well.
 .TP 8
+.B \-decorate
+Add decorations to the Xwayland root window when running rootful.
+
+This option has no effect when \fIXwayland\fP is built without libdecor
+support (optional).
+
+This option is not compatible with rootless mode (\fI-rootless\fP).
+.TP 8
 .B \-eglstream
 Use EGLStream backend for NVidia GPUs. If \fIXwayland\fP was compiled with
 EGLStream support, this option will instruct \fIXwayland\fP to try that
diff --git a/hw/xwayland/meson.build b/hw/xwayland/meson.build
index 40aec15879bf37ee931379343812978a689bb83d..6c04c4cf60875af6f6f4c7e61a67314d569bf90a 100644
--- a/hw/xwayland/meson.build
+++ b/hw/xwayland/meson.build
@@ -128,6 +128,10 @@ if libdrm_dep.found()
     xwayland_dep += libdrm_dep
 endif
 
+if have_libdecor
+    xwayland_dep += libdecor_dep
+endif
+
 xwayland_server = executable(
     'Xwayland',
     srcs,
@@ -158,6 +162,7 @@ xwayland_data.set('PACKAGE_VERSION', meson.project_version())
 xwayland_data.set('xwayland_path', xwayland_path)
 xwayland_data.set('have_glamor', build_glamor ? 'true' : 'false')
 xwayland_data.set('have_eglstream', build_eglstream ? 'true' : 'false')
+xwayland_data.set('have_libdecor', have_libdecor ? 'true' : 'false')
 configure_file(
     input: 'xwayland.pc.in',
     output: 'xwayland.pc',
diff --git a/hw/xwayland/xwayland-screen.c b/hw/xwayland/xwayland-screen.c
index a69fc4dc707d82aaef59e8429085a9ea5b96a59a..9b1ad0ec559970d75fec3a2fbc0627fa8dfd086d 100644
--- a/hw/xwayland/xwayland-screen.c
+++ b/hw/xwayland/xwayland-screen.c
@@ -513,6 +513,33 @@ xwl_display_pollout (struct xwl_screen *xwl_screen, int timeout)
     return xserver_poll(&poll_fd, 1, timeout);
 }
 
+#ifdef XWL_HAS_LIBDECOR
+static void
+xwl_dispatch_events_with_libdecor(struct xwl_screen *xwl_screen)
+{
+    int ret = 0;
+
+    assert(!xwl_screen->rootless);
+
+    ret = libdecor_dispatch(xwl_screen->libdecor_context, 0);
+    if (ret == -1)
+        xwl_give_up("failed to dispatch Wayland events with libdecor: %s\n",
+                    strerror(errno));
+}
+
+static void
+handle_libdecor_error(struct libdecor *context,
+                      enum libdecor_error error,
+                      const char *message)
+{
+    xwl_give_up("libdecor error (%d): %s\n", error, message);
+}
+
+static struct libdecor_interface libdecor_iface = {
+    .error = handle_libdecor_error,
+};
+#endif
+
 static void
 xwl_dispatch_events (struct xwl_screen *xwl_screen)
 {
@@ -551,6 +578,12 @@ socket_handler(int fd, int ready, void *data)
 {
     struct xwl_screen *xwl_screen = data;
 
+#ifdef XWL_HAS_LIBDECOR
+    if (xwl_screen->libdecor_context) {
+        xwl_dispatch_events_with_libdecor(xwl_screen);
+        return;
+    }
+#endif
     xwl_read_events (xwl_screen);
 }
 
@@ -565,12 +598,24 @@ block_handler(void *data, void *timeout)
     struct xwl_screen *xwl_screen = data;
 
     xwl_screen_post_damage(xwl_screen);
+#ifdef XWL_HAS_LIBDECOR
+    if (xwl_screen->libdecor_context) {
+        xwl_dispatch_events_with_libdecor(xwl_screen);
+        return;
+    }
+#endif
     xwl_dispatch_events (xwl_screen);
 }
 
 void
 xwl_sync_events (struct xwl_screen *xwl_screen)
 {
+#ifdef XWL_HAS_LIBDECOR
+    if (xwl_screen->libdecor_context) {
+        xwl_dispatch_events_with_libdecor(xwl_screen);
+        return;
+    }
+#endif
     xwl_dispatch_events (xwl_screen);
     xwl_read_events (xwl_screen);
 }
@@ -689,6 +734,13 @@ xwl_screen_init(ScreenPtr pScreen, int argc, char **argv)
             xwl_screen->host_grab = 1;
             xwl_screen->has_grab = 1;
         }
+        else if (strcmp(argv[i], "-decorate") == 0) {
+#ifdef XWL_HAS_LIBDECOR
+            xwl_screen->decorate = 1;
+#else
+            ErrorF("This build does not have libdecor support\n");
+#endif
+        }
     }
 
     if (use_fixed_size) {
@@ -745,11 +797,17 @@ xwl_screen_init(ScreenPtr pScreen, int argc, char **argv)
                              &registry_listener, xwl_screen);
     xwl_screen_roundtrip(xwl_screen);
 
+
     if (xwl_screen->fullscreen && xwl_screen->rootless) {
         ErrorF("error, cannot set fullscreen when running rootless\n");
         return FALSE;
     }
 
+    if (xwl_screen->fullscreen && xwl_screen->decorate) {
+        ErrorF("error, cannot use the decorate option when running fullscreen\n");
+        return FALSE;
+    }
+
     if (xwl_screen->fullscreen && !xwl_screen_has_viewport_support(xwl_screen)) {
         ErrorF("missing viewport support in the compositor, ignoring fullscreen\n");
         xwl_screen->fullscreen = FALSE;
@@ -796,7 +854,16 @@ xwl_screen_init(ScreenPtr pScreen, int argc, char **argv)
         return FALSE;
 #endif
 
-    xwl_screen->wayland_fd = wl_display_get_fd(xwl_screen->display);
+#ifdef XWL_HAS_LIBDECOR
+    if (xwl_screen->decorate && !xwl_screen->rootless) {
+        xwl_screen->libdecor_context = libdecor_new(xwl_screen->display, &libdecor_iface);
+        xwl_screen->wayland_fd = libdecor_get_fd(xwl_screen->libdecor_context);
+    }
+    else
+#endif
+    {
+        xwl_screen->wayland_fd = wl_display_get_fd(xwl_screen->display);
+    }
     SetNotifyFd(xwl_screen->wayland_fd, socket_handler, X_NOTIFY_READ, xwl_screen);
     RegisterBlockAndWakeupHandlers(block_handler, wakeup_handler, xwl_screen);
 
diff --git a/hw/xwayland/xwayland-screen.h b/hw/xwayland/xwayland-screen.h
index 6ddb6f7519d94d1e8cf9cde05de81c740eab8ed2..fd201cdf5f540563a57d849cf12f69f58d9089af 100644
--- a/hw/xwayland/xwayland-screen.h
+++ b/hw/xwayland/xwayland-screen.h
@@ -38,6 +38,10 @@
 #include "xwayland-glamor.h"
 #include "xwayland-drm-lease.h"
 
+#ifdef XWL_HAS_LIBDECOR
+#include <libdecor.h>
+#endif
+
 struct xwl_format {
     uint32_t format;
     int num_modifiers;
@@ -61,6 +65,7 @@ struct xwl_screen {
     int fullscreen;
     int host_grab;
     int has_grab;
+    int decorate;
 
     CreateScreenResourcesProcPtr CreateScreenResources;
     CloseScreenProcPtr CloseScreen;
@@ -123,6 +128,10 @@ struct xwl_screen {
 
     /* The preferred GLVND vendor. If NULL, "mesa" is assumed. */
     const char *glvnd_vendor;
+#ifdef XWL_HAS_LIBDECOR
+    int libdecor_fd;
+    struct libdecor *libdecor_context;
+#endif
 };
 
 /* Apps which use randr/vidmode to change the mode when going fullscreen,
diff --git a/hw/xwayland/xwayland-window.c b/hw/xwayland/xwayland-window.c
index 8b7e447afb9879177cee71ac86cf449ca2077097..0f6cacd3f4332dacb6b795a84682b88f839e35d3 100644
--- a/hw/xwayland/xwayland-window.c
+++ b/hw/xwayland/xwayland-window.c
@@ -498,6 +498,11 @@ xwl_window_rootful_update_title(struct xwl_window *xwl_window)
 
     snprintf(title, sizeof(title), "Xwayland on :%s%s", display, grab_message);
 
+#ifdef XWL_HAS_LIBDECOR
+    if (xwl_window->libdecor_frame)
+        libdecor_frame_set_title(xwl_window->libdecor_frame, title);
+    else
+#endif
     if (xwl_window->xdg_toplevel)
         xdg_toplevel_set_title(xwl_window->xdg_toplevel, title);
 }
@@ -507,10 +512,78 @@ xwl_window_rootful_set_app_id(struct xwl_window *xwl_window)
 {
     const char *app_id = "org.freedesktop.Xwayland";
 
+#ifdef XWL_HAS_LIBDECOR
+    if (xwl_window->libdecor_frame)
+        libdecor_frame_set_app_id(xwl_window->libdecor_frame, app_id);
+    else
+#endif
     if (xwl_window->xdg_toplevel)
         xdg_toplevel_set_app_id(xwl_window->xdg_toplevel, app_id);
 }
 
+#ifdef XWL_HAS_LIBDECOR
+static void
+xwl_window_update_libdecor_size(struct xwl_window *xwl_window, int width, int height)
+{
+    struct libdecor_state *state;
+
+    if (xwl_window->libdecor_frame) {
+	state = libdecor_state_new(width, height);
+	libdecor_frame_commit(xwl_window->libdecor_frame, state, NULL);
+	libdecor_state_free(state);
+    }
+}
+
+static void
+handle_libdecor_configure(struct libdecor_frame *frame,
+                          struct libdecor_configuration *configuration,
+                          void *data)
+{
+    struct xwl_window *xwl_window = data;
+    struct xwl_screen *xwl_screen = xwl_window->xwl_screen;
+    struct libdecor_state *state;
+
+    state = libdecor_state_new(xwl_screen->width, xwl_screen->height);
+    libdecor_frame_commit(frame, state, configuration);
+    libdecor_state_free(state);
+
+    if (libdecor_frame_has_capability(frame, LIBDECOR_ACTION_RESIZE))
+        libdecor_frame_unset_capabilities(frame, LIBDECOR_ACTION_RESIZE);
+    if (libdecor_frame_has_capability(frame, LIBDECOR_ACTION_FULLSCREEN))
+        libdecor_frame_unset_capabilities(frame, LIBDECOR_ACTION_FULLSCREEN);
+}
+
+static void
+handle_libdecor_close(struct libdecor_frame *frame,
+                      void *data)
+{
+    DebugF("Terminating on compositor request");
+    GiveUp(0);
+}
+
+static void
+handle_libdecor_commit(struct libdecor_frame *frame,
+                       void *data)
+{
+    struct xwl_window *xwl_window = data;
+    wl_surface_commit(xwl_window->surface);
+}
+
+static void
+handle_libdecor_dismiss_popup(struct libdecor_frame *frame,
+                              const char *seat_name,
+                              void *data)
+{
+}
+
+static struct libdecor_frame_interface libdecor_frame_iface = {
+    handle_libdecor_configure,
+    handle_libdecor_close,
+    handle_libdecor_commit,
+    handle_libdecor_dismiss_popup,
+};
+#endif
+
 static void
 xdg_surface_handle_configure(void *data,
                              struct xdg_surface *xdg_surface,
@@ -591,29 +664,43 @@ xwl_create_root_surface(struct xwl_window *xwl_window)
     WindowPtr window = xwl_window->window;
     struct wl_region *region;
 
-    xwl_window->xdg_surface =
-        xdg_wm_base_get_xdg_surface(xwl_screen->xdg_wm_base, xwl_window->surface);
-    if (xwl_window->xdg_surface == NULL) {
-        ErrorF("Failed creating xdg_wm_base xdg_surface\n");
-        goto err_surf;
-    }
 
-    xwl_window->xdg_toplevel =
-        xdg_surface_get_toplevel(xwl_window->xdg_surface);
-    if (xwl_window->xdg_surface == NULL) {
-        ErrorF("Failed creating xdg_toplevel\n");
-        goto err_surf;
+#ifdef XWL_HAS_LIBDECOR
+    if (xwl_screen->decorate) {
+        xwl_window->libdecor_frame =
+            libdecor_decorate(xwl_screen->libdecor_context,
+                              xwl_window->surface,
+                              &libdecor_frame_iface,
+                              xwl_window);
+        libdecor_frame_map(xwl_window->libdecor_frame);
     }
+    else
+#endif
+    {
+        xwl_window->xdg_surface =
+            xdg_wm_base_get_xdg_surface(xwl_screen->xdg_wm_base, xwl_window->surface);
+        if (xwl_window->xdg_surface == NULL) {
+            ErrorF("Failed creating xdg_wm_base xdg_surface\n");
+            goto err_surf;
+        }
+
+        xwl_window->xdg_toplevel =
+            xdg_surface_get_toplevel(xwl_window->xdg_surface);
+        if (xwl_window->xdg_surface == NULL) {
+            ErrorF("Failed creating xdg_toplevel\n");
+            goto err_surf;
+        }
 
-    wl_surface_add_listener(xwl_window->surface,
-                            &surface_listener, xwl_window);
+        wl_surface_add_listener(xwl_window->surface,
+                                &surface_listener, xwl_window);
 
-    xdg_surface_add_listener(xwl_window->xdg_surface,
-                             &xdg_surface_listener, xwl_window);
+        xdg_surface_add_listener(xwl_window->xdg_surface,
+                                 &xdg_surface_listener, xwl_window);
 
-    xdg_toplevel_add_listener(xwl_window->xdg_toplevel,
-                              &xdg_toplevel_listener,
-                              NULL);
+        xdg_toplevel_add_listener(xwl_window->xdg_toplevel,
+                                  &xdg_toplevel_listener,
+                                  NULL);
+    }
 
     xwl_window_rootful_update_title(xwl_window);
     xwl_window_rootful_set_app_id(xwl_window);
@@ -908,8 +995,14 @@ xwl_resize_window(WindowPtr window,
     xwl_screen->ResizeWindow = screen->ResizeWindow;
     screen->ResizeWindow = xwl_resize_window;
 
-    if (xwl_window && (xwl_window_get(window) || xwl_window_is_toplevel(window)))
-        xwl_window_check_resolution_change_emulation(xwl_window);
+    if (xwl_window) {
+        if (xwl_window_get(window) || xwl_window_is_toplevel(window))
+            xwl_window_check_resolution_change_emulation(xwl_window);
+#ifdef XWL_HAS_LIBDECOR
+        if (window == screen->root)
+            xwl_window_update_libdecor_size(xwl_window, width, height);
+#endif
+    }
 }
 
 void
diff --git a/hw/xwayland/xwayland-window.h b/hw/xwayland/xwayland-window.h
index 7fb5425930c8a7f5c5c28e2002cdb1b34449e1be..3dbfd2dc283a80e041cfbb8c8c1fe835fe2fff6e 100644
--- a/hw/xwayland/xwayland-window.h
+++ b/hw/xwayland/xwayland-window.h
@@ -58,6 +58,9 @@ struct xwl_window {
     struct xorg_list frame_callback_list;
     Bool present_flipped;
 #endif
+#ifdef XWL_HAS_LIBDECOR
+    struct libdecor_frame *libdecor_frame;
+#endif
 };
 
 struct xwl_window *xwl_window_get(WindowPtr window);
diff --git a/hw/xwayland/xwayland.c b/hw/xwayland/xwayland.c
index e4983ed1e91d8e8ad43c4f2341fab2b85ea4c7a2..51d3147f1be52a90eb84a493b0275e78d3ff8599 100644
--- a/hw/xwayland/xwayland.c
+++ b/hw/xwayland/xwayland.c
@@ -106,6 +106,9 @@ ddxUseMsg(void)
     ErrorF("-version               show the server version and exit\n");
     ErrorF("-noTouchPointerEmulation  disable touch pointer emulation\n");
     ErrorF("-force-xrandr-emulation   force non-native modes to be exposed when viewporter is not exposed by the compositor\n");
+#ifdef XWL_HAS_LIBDECOR
+    ErrorF("-decorate              add decorations to Xwayland when rootful (experimental)\n");
+#endif
 }
 
 static int init_fd = -1;
@@ -237,6 +240,9 @@ ddxProcessArgument(int argc, char *argv[], int i)
     else if (strcmp(argv[i], "-host-grab") == 0) {
         return 1;
     }
+    else if (strcmp(argv[i], "-decorate") == 0) {
+        return 1;
+    }
 
     return 0;
 }
diff --git a/hw/xwayland/xwayland.pc.in b/hw/xwayland/xwayland.pc.in
index 70db37db75919845cc41e060aa52ece1541e80bf..c62a95a0234c2797007b0af5b6829a626ba37bcb 100644
--- a/hw/xwayland/xwayland.pc.in
+++ b/hw/xwayland/xwayland.pc.in
@@ -16,3 +16,4 @@ have_force_xrandr_emulation=true
 have_geometry=true
 have_fullscreen=true
 have_host_grab=true
+have_decorate=@have_libdecor@
diff --git a/include/meson.build b/include/meson.build
index 7f1e4fd8fb5b0f5f02f6b948b42abc515798d5af..57466dc79b2208573f0ddf472a094cc765c24bbf 100644
--- a/include/meson.build
+++ b/include/meson.build
@@ -415,6 +415,7 @@ configure_file(output : 'xwin-config.h',
 xwayland_data = configuration_data()
 xwayland_data.set('XWL_HAS_GLAMOR', build_glamor and (gbm_dep.found() or build_eglstream) ? '1' : false)
 xwayland_data.set('XWL_HAS_EGLSTREAM', build_eglstream ? '1' : false)
+xwayland_data.set('XWL_HAS_LIBDECOR', have_libdecor ? '1' : false)
 
 configure_file(output : 'xwayland-config.h',
                input : 'xwayland-config.h.meson.in',
diff --git a/include/xwayland-config.h.meson.in b/include/xwayland-config.h.meson.in
index 0943ff57de60a50f78acb0453b0d9e1b947d686e..bb121e355e5e0f7c722fbcfa95edd67e848154f0 100644
--- a/include/xwayland-config.h.meson.in
+++ b/include/xwayland-config.h.meson.in
@@ -9,3 +9,6 @@
 
 /* Build eglstream support for Xwayland */
 #mesondefine XWL_HAS_EGLSTREAM
+
+/* Build Xwayland with libdecor support*/
+#mesondefine XWL_HAS_LIBDECOR
diff --git a/meson.build b/meson.build
index 0793f0e54061d0037bb3f47aa5324ab23bd686de..7bcec7d4e8da10425ef56072366dad225dbd305e 100644
--- a/meson.build
+++ b/meson.build
@@ -336,6 +336,21 @@ if build_glamor
     epoxy_dep = dependency('epoxy', required: false)
 endif
 
+if build_xwayland
+    libdecor_dep = dependency('libdecor-0', required: false)
+    libdecor_option = get_option('libdecor')
+    if libdecor_option == 'auto'
+        have_libdecor = libdecor_dep.found()
+    else
+        have_libdecor = libdecor_option == 'true'
+        if have_libdecor and not libdecor_dep.found()
+            error('libdecor support requested but not found')
+        endif
+    endif
+else
+    have_libdecor = false
+endif
+
 eglstream_option = get_option('xwayland_eglstream')
 if build_xwayland and build_glamor
     eglstream_dep = dependency('wayland-eglstream-protocols', required:false)
diff --git a/meson_options.txt b/meson_options.txt
index 3a494478e1c3b098f496d8e7621e86df133fda98..16377dcc348fa3948117ceacbb8f35d082858abf 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -135,6 +135,8 @@ option('libunwind', type: 'boolean', value: false,
         description: 'Use libunwind for backtrace reporting')
 
 option('xwayland-path', type: 'string', description: 'Directory containing Xwayland executable')
+option('libdecor', type: 'combo', choices: ['true', 'false', 'auto'], value: 'auto',
+        description: 'Whether Xwayland should use libdecor when running rootful.')
 
 option('docs', type: 'combo', choices: ['true', 'false', 'auto'], value: 'auto',
         description: 'Build documentation')