Explorar el Código

wayland: Add support for the session management protocol

Add session management protocol support for saving/restoring toplevel window state across runs.
Frank Praznik hace 3 meses
padre
commit
d8971fa00a

+ 13 - 0
docs/README-wayland.md

@@ -38,6 +38,19 @@ encounter limitations or behavior that is different from other windowing systems
 
 - Wayland does not allow toplevel windows to position themselves programmatically.
 
+### How do I save and restore window layout and state between runs?
+
+- To preserve the state of toplevel windows across runs, the `xdg-session-management-v1` protocol is required:
+  - Create a session by setting the `SDL_PROP_GLOBAL_VIDEO_WAYLAND_SESSION_ID_STRING` property to a non-null value
+    before creating a window.
+  - Assign the windows you want managed by the session a unique, stable ID string with the
+    `SDL_PROP_WINDOW_CREATE_WAYLAND_WINDOW_ID_STRING` property at creation time. This string should be human-readable,
+    but not translated.
+  - Serialize the session ID property `SDL_PROP_GLOBAL_VIDEO_WAYLAND_SESSION_ID_STRING` before shutting down.
+  - On subsequent runs, restore the session ID string property to the previously serialized value before window
+    creation. This property may change at some arbitrary point during runtime, so serialization should be deferred as
+    late as possible.
+
 ### Retrieving the global mouse cursor position when the cursor is outside a window doesn't work
 
 - Wayland only provides applications with the cursor position within the borders of the application windows. Querying

+ 43 - 0
include/SDL3/SDL_video.h

@@ -102,6 +102,37 @@ typedef Uint32 SDL_WindowID;
  */
 #define SDL_PROP_GLOBAL_VIDEO_WAYLAND_WL_DISPLAY_POINTER "SDL.video.wayland.wl_display"
 
+/**
+ * The session ID string used for saving and restoring window state across runs.
+ *
+ * This requires that the compositor supports the `xdg_session_management_v1` protocol.
+ *
+ * To save and restore the current state of Wayland toplevel windows, set this to a non-null
+ * value before creating a window, and serialize this value before shutting down. To restore
+ * the previous state on subsequent runs, set this property to the previously serialized
+ * value before window creation.
+ *
+ * This can be set at any time before the first call to a window creation function. Reading
+ * should be deferred until serialization time, as compositors may not set the session
+ * identifier string immediately, and the identifier string may change during runtime, so
+ * it should not be cached.
+ *
+ * Setting this to an empty string ("") before creating a window will cause a new session with
+ * an automatically generated identifier string to be created.
+ *
+ * Setting this to null or an empty string before shutting down the video subsystem will cause
+ * the existing session to be removed.
+ *
+ * Note that for windows to be saved/restored by the session, they also need a stable, unique
+ * identifier string set via the `SDL_PROP_WINDOW_CREATE_WAYLAND_WINDOW_ID_STRING` property at
+ * creation time.
+ *
+ * \since This property is available since SDL 3.6.0.
+ *
+ * \sa SDL_CreateWindowWithProperties
+ */
+#define SDL_PROP_GLOBAL_VIDEO_WAYLAND_SESSION_ID_STRING "SDL.video.wayland.session_id"
+
 /**
  * System theme.
  *
@@ -1349,6 +1380,11 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_CreatePopupWindow(SDL_Window *paren
  *   application wants an associated `wl_egl_window` object to be created and
  *   attached to the window, even if the window does not have the OpenGL
  *   property or `SDL_WINDOW_OPENGL` flag set.
+ * - `SDL_PROP_WINDOW_CREATE_WAYLAND_WINDOW_ID_STRING` - a string used as
+ *   a stable identifier for toplevel windows for the purpose of allowing
+ *   the compositor to save/restore their state between runs. This should
+ *   be human readable, but not translated, and must be unique for each
+ *   individual window.
  * - `SDL_PROP_WINDOW_CREATE_WAYLAND_WL_SURFACE_POINTER` - the wl_surface
  *   associated with the window, if you want to wrap an existing window. See
  *   [README-wayland](README-wayland) for more information.
@@ -1450,6 +1486,7 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_CreateWindowWithProperties(SDL_Prop
 #define SDL_PROP_WINDOW_CREATE_WINDOWSCENE_POINTER                 "SDL.window.create.uikit.windowscene"
 #define SDL_PROP_WINDOW_CREATE_WAYLAND_SURFACE_ROLE_CUSTOM_BOOLEAN "SDL.window.create.wayland.surface_role_custom"
 #define SDL_PROP_WINDOW_CREATE_WAYLAND_CREATE_EGL_WINDOW_BOOLEAN   "SDL.window.create.wayland.create_egl_window"
+#define SDL_PROP_WINDOW_CREATE_WAYLAND_WINDOW_ID_STRING            "SDL.window.create.wayland.window_id"
 #define SDL_PROP_WINDOW_CREATE_WAYLAND_WL_SURFACE_POINTER          "SDL.window.create.wayland.wl_surface"
 #define SDL_PROP_WINDOW_CREATE_WIN32_HWND_POINTER                  "SDL.window.create.win32.hwnd"
 #define SDL_PROP_WINDOW_CREATE_WIN32_PIXEL_FORMAT_HWND_POINTER     "SDL.window.create.win32.pixel_format_hwnd"
@@ -1608,6 +1645,11 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_GetWindowParent(SDL_Window *window)
  *   with the window
  * - `SDL_PROP_WINDOW_WAYLAND_EGL_WINDOW_POINTER`: the wl_egl_window
  *   associated with the window
+ * - `SDL_PROP_WINDOW_WAYLAND_WINDOW_ID_STRING`: the window identification string,
+ *   initially set with SDL_PROP_WINDOW_CREATE_WAYLAND_WINDOW_ID_STRING, and used
+ *   as an identifier for session management. Setting this to null or an empty
+ *   string ("") before hiding or destroying the window will cause any session
+ *   information associated with the window to be removed
  * - `SDL_PROP_WINDOW_WAYLAND_XDG_SURFACE_POINTER`: the xdg_surface associated
  *   with the window
  * - `SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_POINTER`: the xdg_toplevel role
@@ -1680,6 +1722,7 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetWindowProperties(SDL_Window
 #define SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER                     "SDL.window.wayland.surface"
 #define SDL_PROP_WINDOW_WAYLAND_VIEWPORT_POINTER                    "SDL.window.wayland.viewport"
 #define SDL_PROP_WINDOW_WAYLAND_EGL_WINDOW_POINTER                  "SDL.window.wayland.egl_window"
+#define SDL_PROP_WINDOW_WAYLAND_WINDOW_ID_STRING                    "SDL.window.wayland.window_id"
 #define SDL_PROP_WINDOW_WAYLAND_XDG_SURFACE_POINTER                 "SDL.window.wayland.xdg_surface"
 #define SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_POINTER                "SDL.window.wayland.xdg_toplevel"
 #define SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_EXPORT_HANDLE_STRING   "SDL.window.wayland.xdg_toplevel_export_handle"

+ 93 - 0
src/video/wayland/SDL_waylandvideo.c

@@ -70,6 +70,7 @@
 #include "pointer-warp-v1-client-protocol.h"
 #include "pointer-gestures-unstable-v1-client-protocol.h"
 #include "single-pixel-buffer-v1-client-protocol.h"
+#include "xdg-session-management-v1-client-protocol.h"
 
 #ifdef HAVE_LIBDECOR_H
 #include <libdecor.h>
@@ -1326,6 +1327,89 @@ static void Wayland_InitColorManager(SDL_VideoData *d)
     }
 }
 
+static void handle_xdg_session_created(void *data, struct xdg_session_v1 *xdg_session_v1, const char *id)
+{
+    SDL_SetStringProperty(SDL_GetGlobalProperties(), SDL_PROP_GLOBAL_VIDEO_WAYLAND_SESSION_ID_STRING, id);
+}
+
+static void handle_xdg_session_restored(void *data, struct xdg_session_v1 *xdg_session_v1)
+{
+    // NOP
+}
+
+static void handle_xdg_session_replaced(void *data, struct xdg_session_v1 *xdg_session_v1)
+{
+    SDL_VideoDevice *viddev = SDL_GetVideoDevice();
+    SDL_VideoData *viddata = data;
+
+    // Clean up all session objects, as they have become inert, and should be destroyed.
+    SDL_SetStringProperty(SDL_GetGlobalProperties(), SDL_PROP_GLOBAL_VIDEO_WAYLAND_SESSION_ID_STRING, NULL);
+
+    for (SDL_Window *w = viddev->windows; w; w = w->next) {
+        SDL_WindowData *d = w->internal;
+
+        if (d->xdg_toplevel_session) {
+            xdg_toplevel_session_v1_destroy(d->xdg_toplevel_session);
+            d->xdg_toplevel_session = NULL;
+
+            SDL_free(d->session_id);
+            d->session_id = NULL;
+        }
+    }
+
+    if (viddata->xdg_session) {
+        xdg_session_v1_destroy(viddata->xdg_session);
+        viddata->xdg_session = NULL;
+    }
+}
+
+static const struct xdg_session_v1_listener xdg_session_listener = {
+    .created  = handle_xdg_session_created,
+    .restored = handle_xdg_session_restored,
+    .replaced = handle_xdg_session_replaced
+};
+
+void Wayland_CreateSession(SDL_VideoData *viddata)
+{
+    if (!viddata->xdg_session_manager) {
+        // Set the ID string to null if session management is not available.
+        SDL_SetStringProperty(SDL_GetGlobalProperties(), SDL_PROP_GLOBAL_VIDEO_WAYLAND_SESSION_ID_STRING, NULL);
+        return;
+    }
+
+    // Register a new session, if one does not yet exist.
+    if (!viddata->xdg_session) {
+        const char *session_id = SDL_GetStringProperty(SDL_GetGlobalProperties(), SDL_PROP_GLOBAL_VIDEO_WAYLAND_SESSION_ID_STRING, NULL);
+        if (session_id) {
+            if (*session_id == '\0') {
+                // Create a new session if the ID string is empty.
+                session_id = NULL;
+            }
+
+            const enum xdg_session_manager_v1_reason reason = session_id ? XDG_SESSION_MANAGER_V1_REASON_SESSION_RESTORE : XDG_SESSION_MANAGER_V1_REASON_LAUNCH;
+            viddata->xdg_session = xdg_session_manager_v1_get_session(viddata->xdg_session_manager, reason, session_id);
+            xdg_session_v1_add_listener(viddata->xdg_session, &xdg_session_listener, viddata);
+        }
+    }
+}
+
+static void Wayland_SessionDestroy(SDL_VideoData *viddata)
+{
+    // If the session string was cleared, remove the session.
+    if (viddata->xdg_session) {
+        const char *session_id = SDL_GetStringProperty(SDL_GetGlobalProperties(), SDL_PROP_GLOBAL_VIDEO_WAYLAND_SESSION_ID_STRING, NULL);
+        if (!session_id || *session_id == '\0') {
+            xdg_session_v1_remove(viddata->xdg_session);
+
+            WAYLAND_wl_display_roundtrip(viddata->display);
+        } else {
+            xdg_session_v1_destroy(viddata->xdg_session);
+        }
+
+        viddata->xdg_session = NULL;
+    }
+}
+
 static void handle_xdg_wm_base_ping(void *data, struct xdg_wm_base *xdg, uint32_t serial)
 {
     xdg_wm_base_pong(xdg, serial);
@@ -1425,6 +1509,8 @@ static void handle_registry_global(void *data, struct wl_registry *registry, uin
         Wayland_DisplayInitPointerGestureManager(d);
     } else if (SDL_strcmp(interface, wp_single_pixel_buffer_manager_v1_interface.name) == 0) {
         d->single_pixel_buffer_manager = wl_registry_bind(d->registry, id, &wp_single_pixel_buffer_manager_v1_interface, 1);
+    } else if (SDL_strcmp(interface, xdg_session_manager_v1_interface.name) == 0) {
+        d->xdg_session_manager = wl_registry_bind(d->registry, id, &xdg_session_manager_v1_interface, 1);
     }
 #ifdef SDL_WL_FIXES_VERSION
     else if (SDL_strcmp(interface, wl_fixes_interface.name) == 0) {
@@ -1685,6 +1771,8 @@ static void Wayland_VideoCleanup(SDL_VideoDevice *_this)
     SDL_VideoData *data = _this->internal;
     SDL_WaylandSeat *seat, *tmp;
 
+    Wayland_SessionDestroy(data);
+
     for (int i = _this->num_displays - 1; i >= 0; --i) {
         SDL_VideoDisplay *display = _this->displays[i];
         Wayland_free_display(display, false);
@@ -1842,6 +1930,11 @@ static void Wayland_VideoCleanup(SDL_VideoDevice *_this)
         data->single_pixel_buffer_manager = NULL;
     }
 
+    if (data->xdg_session_manager) {
+        xdg_session_manager_v1_destroy(data->xdg_session_manager);
+        data->xdg_session_manager = NULL;
+    }
+
     if (data->subcompositor) {
         wl_subcompositor_destroy(data->subcompositor);
         data->subcompositor = NULL;

+ 6 - 3
src/video/wayland/SDL_waylandvideo.h

@@ -87,7 +87,9 @@ struct SDL_VideoData
     struct wl_fixes *wl_fixes;
     struct zwp_pointer_gestures_v1 *zwp_pointer_gestures;
     struct wp_single_pixel_buffer_manager_v1 *single_pixel_buffer_manager;
+    struct xdg_session_manager_v1 *xdg_session_manager;
 
+    struct xdg_session_v1 *xdg_session;
     struct xkb_context *xkb_context;
 
     struct wl_list seat_list;
@@ -162,9 +164,10 @@ extern bool SDL_WAYLAND_own_surface(struct wl_surface *surface);
 extern bool SDL_WAYLAND_own_output(struct wl_output *output);
 
 extern SDL_WindowData *Wayland_GetWindowDataForOwnedSurface(struct wl_surface *surface);
-void Wayland_AddWindowDataToExternalList(SDL_WindowData *data);
-void Wayland_RemoveWindowDataFromExternalList(SDL_WindowData *data);
-struct wl_event_queue *Wayland_DisplayCreateQueue(struct wl_display *display, const char *name);
+extern void Wayland_AddWindowDataToExternalList(SDL_WindowData *data);
+extern void Wayland_RemoveWindowDataFromExternalList(SDL_WindowData *data);
+extern struct wl_event_queue *Wayland_DisplayCreateQueue(struct wl_display *display, const char *name);
+extern void Wayland_CreateSession(SDL_VideoData *data);
 
 extern bool Wayland_LoadLibdecor(SDL_VideoData *data, bool ignore_xdg);
 

+ 65 - 0
src/video/wayland/SDL_waylandwindow.c

@@ -49,6 +49,7 @@
 #include "frog-color-management-v1-client-protocol.h"
 #include "xdg-toplevel-icon-v1-client-protocol.h"
 #include "color-management-v1-client-protocol.h"
+#include "xdg-session-management-v1-client-protocol.h"
 
 #ifdef HAVE_LIBDECOR_H
 #include <libdecor.h>
@@ -2050,6 +2051,60 @@ bool Wayland_SetWindowModal(SDL_VideoDevice *_this, SDL_Window *window, bool mod
     return true;
 }
 
+static void Wayland_RegisterToplevelForSession(SDL_WindowData *data)
+{
+    SDL_VideoDevice *viddev = SDL_GetVideoDevice();
+    SDL_VideoData *viddata = viddev->internal;
+
+    struct xdg_toplevel *toplevel = GetToplevelForWindow(data);
+    if (!toplevel) {
+        return;
+    }
+
+    if (viddata->xdg_session_manager) {
+        const char *id = SDL_GetStringProperty(data->sdlwindow->props, SDL_PROP_WINDOW_WAYLAND_WINDOW_ID_STRING, NULL);
+        if (id && *id != '\0') {
+            Wayland_CreateSession(viddata);
+
+            if (viddata->xdg_session) {
+                CHECK_PARAM (true) {
+                    // Windows must not have a duplicate ID string, or a protocol error will result.
+                    for (SDL_Window *w = viddev->windows; w; w = w->next) {
+                        SDL_WindowData *d = w->internal;
+                        if (d->session_id && SDL_strcmp(d->session_id, id) == 0) {
+                            SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Duplicate window ID string %s found; window will not be added to session", id);
+                            return;
+                        }
+                    }
+                }
+
+                data->xdg_toplevel_session = xdg_session_v1_restore_toplevel(viddata->xdg_session, toplevel, id);
+                data->session_id = SDL_strdup(id);
+            }
+        }
+    }
+}
+
+static void Wayland_DestroyToplevelSession(SDL_WindowData *data)
+{
+    SDL_VideoData *viddata = data->waylandData;
+
+    if (data->xdg_toplevel_session) {
+        const char *id = SDL_GetStringProperty(data->sdlwindow->props, SDL_PROP_WINDOW_WAYLAND_WINDOW_ID_STRING, NULL);
+
+        // If the ID string was cleared, remove the window from the session.
+        if (!id || *id == '\0') {
+            xdg_session_v1_remove_toplevel(viddata->xdg_session, data->session_id);
+        }
+
+        xdg_toplevel_session_v1_destroy(data->xdg_toplevel_session);
+        data->xdg_toplevel_session = NULL;
+
+        SDL_free(data->session_id);
+        data->session_id = NULL;
+    }
+}
+
 static void show_hide_sync_handler(void *data, struct wl_callback *callback, uint32_t callback_data)
 {
     // Get the window from the ID as it may have been destroyed
@@ -2256,6 +2311,7 @@ void Wayland_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window)
     }
 
     // Restore state that was set prior to this call
+    Wayland_RegisterToplevelForSession(data);
     Wayland_SetWindowParent(_this, window, window->parent);
 
     if (window->flags & SDL_WINDOW_MODAL) {
@@ -2476,6 +2532,9 @@ void Wayland_HideWindow(SDL_VideoDevice *_this, SDL_Window *window)
     wl_surface_attach(wind->surface, NULL, 0, 0);
     wl_surface_commit(wind->surface);
 
+    // Need to destroy the session object after unmapping the window, or the state may not be saved.
+    Wayland_DestroyToplevelSession(wind);
+
     SDL_zero(wind->shell_surface);
     wind->show_hide_sync_required = true;
     struct wl_callback *cb = wl_display_sync(_this->internal->display);
@@ -3108,6 +3167,12 @@ bool Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Proper
     SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, data->surface);
     SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_VIEWPORT_POINTER, data->viewport);
     SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_EGL_WINDOW_POINTER, data->egl_window);
+    if (data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL || data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) {
+        const char *window_id = SDL_GetStringProperty(create_props, SDL_PROP_WINDOW_CREATE_WAYLAND_WINDOW_ID_STRING, NULL);
+        if (window_id && *window_id != '\0') {
+            SDL_SetStringProperty(props, SDL_PROP_WINDOW_WAYLAND_WINDOW_ID_STRING, window_id);
+        }
+    }
 
     data->hit_test_result = SDL_HITTEST_NORMAL;
 

+ 2 - 0
src/video/wayland/SDL_waylandwindow.h

@@ -118,6 +118,7 @@ struct SDL_WindowData
     struct xdg_toplevel_icon_v1 *xdg_toplevel_icon_v1;
     struct frog_color_managed_surface *frog_color_managed_surface;
     struct wp_color_management_surface_feedback_v1 *wp_color_management_surface_feedback;
+    struct xdg_toplevel_session_v1 *xdg_toplevel_session;
 
     struct Wayland_ColorInfoState *color_info_state;
 
@@ -127,6 +128,7 @@ struct SDL_WindowData
     int num_outputs;
 
     char *app_id;
+    char *session_id;
     double scale_factor;
 
     struct wl_buffer **icon_buffers;

+ 333 - 0
wayland-protocols/xdg-session-management-v1.xml

@@ -0,0 +1,333 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="xdg_session_management_v1">
+  <copyright>
+    Copyright 2018 Mike Blumenkrantz
+    Copyright 2018 Samsung Electronics Co., Ltd
+    Copyright 2018 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.
+  </copyright>
+
+  <description summary="Protocol for managing application sessions">
+    This description provides a high-level overview of the interplay between
+    the interfaces defined this protocol. For details, see the protocol
+    specification.
+
+    The xdg_session_manager protocol declares interfaces necessary to
+    allow clients to restore toplevel state from previous executions. The
+    xdg_session_manager_v1.get_session request can be used to obtain a
+    xdg_session_v1 resource representing the state of a set of toplevels.
+
+    Clients may obtain the session string to use in future calls through
+    the xdg_session_v1.created event. Compositors will use this string
+    as an identifiable token for future runs, possibly storing data about
+    the related toplevels in persistent storage. Clients that wish to
+    track sessions in multiple environments may use the $XDG_CURRENT_DESKTOP
+    environment variable.
+
+    Toplevels are managed through the xdg_session_v1.add_toplevel and
+    xdg_session_v1.remove_toplevel pair of requests. Clients will explicitly
+    request a toplevel to be restored according to prior state through the
+    xdg_session_v1.restore_toplevel request before the toplevel is mapped.
+
+    Compositors may store session information up to any arbitrary level, and
+    apply any limits and policies to the amount of data stored and its lifetime.
+    Clients must account for missing sessions and partial session restoration.
+
+    Warning! The protocol described in this file is currently in the testing
+    phase. Backward compatible changes may be added together with the
+    corresponding interface version bump. Backward incompatible changes can
+    only be done by creating a new major version of the extension.
+  </description>
+
+  <interface name="xdg_session_manager_v1" version="1">
+    <description summary="manage sessions for applications">
+      The xdg_session_manager_v1 interface defines base requests for creating and
+      managing a session for an application. Sessions persist across application
+      and compositor restarts unless explicitly destroyed. A session is created
+      for the purpose of maintaining an application's xdg_toplevel surfaces
+      across compositor or application restarts. The compositor should remember
+      as many states as possible for surfaces in a given session, but there is
+      no requirement for which states must be remembered.
+
+      Policies such as cache eviction are declared an implementation detail of
+      the compositor. Clients should account for no longer existing sessions.
+    </description>
+
+    <enum name="error">
+      <entry name="in_use" summary="a requested session is already in use"
+             value="1"/>
+      <entry name="invalid_session_id" summary="invalid session identifier"
+             value="2"/>
+      <entry name="invalid_reason" summary="invalid reason" value="3"/>
+    </enum>
+
+    <enum name="reason">
+      <description summary="reason for getting a session">
+        The reason may determine in what way a session restores the window
+        management state of associated toplevels.
+
+        For example newly launched applications might be launched on the active
+        workspace with restored size and position, while a recovered
+        application might restore additional state such as active workspace and
+        stacking order.
+      </description>
+      <entry name="launch" value="1">
+        <description summary="an app is newly launched">
+          A new app instance is launched, for example from an app launcher.
+        </description>
+      </entry>
+      <entry name="recover" value="2">
+        <description summary="an app recovered">
+          An app instance is recovering from for example a compositor or app crash.
+        </description>
+      </entry>
+      <entry name="session_restore" value="3">
+        <description summary="an app restored">
+          An app instance is restored, for example part of a restored session, or
+          restored from having been temporarily terminated due to resource
+          constraints.
+        </description>
+      </entry>
+    </enum>
+
+    <request name="destroy" type="destructor">
+      <description summary="Destroy this object">
+        Destroy the manager object. The existing session objects will be
+        unaffected.
+      </description>
+    </request>
+
+    <request name="get_session">
+      <description summary="create or restore a session">
+        Create a session object corresponding to either an existing session
+        identified by the given session identifier string or a new session.
+        While the session object exists, the session is considered to be "in
+        use".
+
+        If an identifier string represents a session that is currently actively
+        in use by the the same client, an 'in_use' error is raised. If some
+        other client is currently using the same session, the new session will
+        replace managing the associated state.
+
+        If the reason is not a valid enum entry, the 'invalid_reason' protocol
+        error is raised.
+
+        NULL is passed to initiate a new session. If a session_id is passed
+        which does not represent a valid session, the compositor treats it as if
+        NULL had been passed.
+
+        The session id string must be UTF-8 encoded. It is also limited by the
+        maximum length of wayland messages (around 4KB). The 'invalid_session_id'
+        protocol error will be raised if an invalid string is provided.
+
+        A client is allowed to have any number of in use sessions at the same
+        time.
+      </description>
+      <arg name="id" type="new_id" interface="xdg_session_v1"/>
+      <arg name="reason" type="uint" enum="reason"
+           summary="reason for session"/>
+      <arg name="session_id" type="string"
+           summary="the session to restore"
+           allow-null="true"/>
+    </request>
+  </interface>
+
+  <interface name="xdg_session_v1" version="1">
+    <description summary="A session for an application">
+      A xdg_session_v1 object represents a session for an application. While the
+      object exists, all surfaces which have been added to the session will
+      have states stored by the compositor which can be reapplied at a later
+      time. Two sessions cannot exist for the same identifier string.
+
+      States for surfaces added to a session are automatically updated by the
+      compositor when they are changed.
+    </description>
+
+    <enum name="error">
+      <entry name="name_in_use"
+             summary="toplevel name is already in use"
+             value="1"/>
+      <entry name="already_mapped"
+             summary="toplevel was already mapped when restored"
+             value="2"/>
+      <entry name="invalid_name"
+             summary="provided toplevel name is invalid"
+             value="3"/>
+      <entry name="already_added"
+             summary="toplevel already added"
+             value="4"/>
+    </enum>
+
+    <request name="destroy" type="destructor">
+      <description summary="Destroy the session">
+        Destroy a session object, preserving the current state but not continuing
+        to make further updates if state changes occur. This makes the associated
+        xdg_toplevel_session_v1 objects inert.
+      </description>
+    </request>
+
+    <request name="remove" type="destructor">
+      <description summary="Remove the session">
+        Remove the session, making it no longer available for restoration. A
+        compositor should in response to this request remove the data related to
+        this session from its storage.
+      </description>
+    </request>
+
+    <request name="add_toplevel">
+      <description summary="add a new surface to the session">
+        Attempt to add a given surface to the session. The passed name is used
+        to identify what window is being restored, and may be used to store
+        window specific state within the session.
+
+        The name given to the toplevel must not correspond to any previously
+        existing toplevel names in the session. If the name matches an already
+        known toplevel name in the session, a 'name_in_use' protocol error will
+        be raised.
+
+        The toplevel object must not be added more than once to any session
+        created by the client, otherwise the 'already_added' protocol error
+        will be raised.
+
+        This request will return a xdg_toplevel_session_v1 for later
+        manipulation. As this resource is created from an empty initial state,
+        compositors must not emit a xdg_toplevel_session_v1.restored event for
+        resources created through this request.
+
+        The name string must be UTF-8 encoded. It is also limited by the maximum
+        length of wayland messages (around 4KB). The 'invalid_name' protocol
+        error will be raised if an invalid string is provided.
+      </description>
+      <arg name="id" type="new_id" interface="xdg_toplevel_session_v1"/>
+      <arg name="toplevel" type="object" interface="xdg_toplevel"/>
+      <arg name="name" type="string" summary="name identifying the toplevel"/>
+    </request>
+
+    <request name="restore_toplevel">
+      <description summary="restore a surface state">
+        Inform the compositor that the toplevel associated with the passed name
+        should have its window management state restored.
+
+        If the toplevel name was previously granted to another xdg_toplevel,
+        the 'name_in_use' protocol error will be raised.
+
+        The toplevel object must not be added more than once to any session
+        created by the client, otherwise the 'already_added' protocol error
+        will be raised.
+
+        This request must be called prior to the first commit on the associated
+        wl_surface after creating the toplevel, otherwise an 'already_mapped'
+        error is raised.
+
+        As part of the initial configure sequence, if the toplevel was
+        successfully restored, a xdg_toplevel_session_v1.restored event is
+        emitted. If the toplevel name was not known in the session, this request
+        will be equivalent to the xdg_toplevel_session_v1.add_toplevel request,
+        and no such event will be emitted. See the xdg_toplevel_session_v1.restored
+        event for further details.
+
+        The name string must be UTF-8 encoded. It is also limited by the maximum
+        length of wayland messages (around 4KB). The 'invalid_name' protocol
+        error will be raised if an invalid string is provided.
+      </description>
+      <arg name="id" type="new_id" interface="xdg_toplevel_session_v1"/>
+      <arg name="toplevel" type="object" interface="xdg_toplevel"/>
+      <arg name="name" type="string" summary="name identifying the toplevel"/>
+    </request>
+
+    <request name="remove_toplevel">
+      <description summary="remove a surface from the session">
+        Remove a specified surface from the session and render any related
+        xdg_toplevel_session_v1 object inert. The compositor should remove any
+        data related to the toplevel in the corresponding session from its internal
+        storage.
+
+        The window is specified by its name in the session. The name string
+        must be encoded in UTF-8, and it is limited in size by the maximum
+        length of wayland messages (around 4KB).
+      </description>
+      <arg name="name" type="string" summary="name identifying the toplevel"/>
+    </request>
+
+    <event name="created">
+      <description summary="newly-created session id">
+        Emitted at most once some time after getting a new session object. It
+        means that no previous state was restored, and a new session was created.
+        The passed id can be persistently stored and used to restore previous
+        sessions.
+      </description>
+      <arg name="session_id" type="string"/>
+    </event>
+
+    <event name="restored">
+      <description summary="the session has been restored">
+        Emitted at most once some time after getting a new session object. It
+        means that previous state was at least partially restored. The same id
+        can again be used to restore previous sessions.
+      </description>
+    </event>
+
+    <event name="replaced">
+      <description summary="the session has been replaced">
+        Emitted at most once, if the session was taken over by some other
+        client. When this happens, the session and all its toplevel session
+        objects become inert, and should be destroyed.
+      </description>
+    </event>
+  </interface>
+
+  <interface name="xdg_toplevel_session_v1" version="1">
+    <description summary="A session for an application">
+      A xdg_toplevel_session_v1 resource acts as a handle for the given
+      toplevel in the session. It allows for receiving events after a
+      toplevel state was restored, and has the requests to manage them.
+    </description>
+
+    <request name="destroy" type="destructor">
+      <description summary="Destroy the object">
+        Destroy the object. This has no effect over window management of the
+        associated toplevel.
+      </description>
+    </request>
+
+    <request name="rename">
+      <description summary="change the name of toplevel session">
+        Renames the toplevel session. The new name can be used in subsequent requests
+        to identify this session object. The state associated with this toplevel
+        session will be preserved.
+
+        If the xdg_session_v1 already contains a toplevel with the specified name,
+        the 'name_in_use' protocol error will be raised.
+      </description>
+      <arg name="name" type="string" summary="new name to identify the toplevel"/>
+    </request>
+
+    <event name="restored">
+      <description summary="a toplevel's session has been restored">
+        The "restored" event is emitted prior to the first
+        xdg_toplevel.configure for the toplevel. It will only be emitted after
+        xdg_session_v1.restore_toplevel, and the initial empty surface state has
+        been applied, and it indicates that the surface's session is being
+        restored with this configure event.
+      </description>
+    </event>
+  </interface>
+</protocol>