Skip to content

Commit 392c8e0

Browse files
committed
wayland: Add support for the session management protocol
Add session management protocol support for saving/restoring toplevel window state across runs.
1 parent b9c227e commit 392c8e0

File tree

7 files changed

+484
-3
lines changed

7 files changed

+484
-3
lines changed

docs/README-wayland.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@ encounter limitations or behavior that is different from other windowing systems
3838

3939
- Wayland does not allow toplevel windows to position themselves programmatically.
4040

41+
### How do I save and restore window layout and state between runs?
42+
43+
- To preserve the state of toplevel windows across runs, assign the window a stable ID string with the
44+
`SDL_PROP_WINDOW_CREATE_WAYLAND_WINDOW_ID_STRING` property at creation time, and serialize the global session property
45+
string `SDL_PROP_GLOBAL_VIDEO_WAYLAND_SESSION_ID_STRING` before shutting down. On subsequent runs, restore the session
46+
ID string property to the previously serialized value before window creation. This functionality requires that the
47+
compositor supports the `xdg_session_management_v1` protocol.
48+
4149
### Retrieving the global mouse cursor position when the cursor is outside a window doesn't work
4250

4351
- Wayland only provides applications with the cursor position within the borders of the application windows. Querying

include/SDL3/SDL_video.h

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,32 @@ typedef Uint32 SDL_WindowID;
102102
*/
103103
#define SDL_PROP_GLOBAL_VIDEO_WAYLAND_WL_DISPLAY_POINTER "SDL.video.wayland.wl_display"
104104

105+
/**
106+
* The session ID string used for saving and restoring window state across runs.
107+
*
108+
* To save the current state of Wayland toplevel windows, serialize this value before
109+
* shutting down. To restore the previous state, set this property to the previously
110+
* serialized value before window creation begins.
111+
*
112+
* This can be set at any time before the first call to a window creation function. Reading
113+
* should be deferred until serialization time, as compositors may not set the session
114+
* identifier string immediately.
115+
*
116+
* Setting this to an empty string ("") will cause a new session to be created.
117+
*
118+
* Setting this to null or an empty string before shutting down will cause the existing session
119+
* to be removed.
120+
*
121+
* Note that for windows to be saved/restored by the session, they also need a stable, unique
122+
* identifier string set via the `SDL_PROP_WINDOW_CREATE_WAYLAND_WINDOW_ID_STRING` property at
123+
* creation time.
124+
*
125+
* \since This property is available since SDL 3.6.0.
126+
*
127+
* \sa SDL_CreateWindowWithProperties
128+
*/
129+
#define SDL_PROP_GLOBAL_VIDEO_WAYLAND_SESSION_ID_STRING "SDL.video.wayland.session_id"
130+
105131
/**
106132
* System theme.
107133
*
@@ -1349,6 +1375,9 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_CreatePopupWindow(SDL_Window *paren
13491375
* application wants an associated `wl_egl_window` object to be created and
13501376
* attached to the window, even if the window does not have the OpenGL
13511377
* property or `SDL_WINDOW_OPENGL` flag set.
1378+
* - `SDL_PROP_WINDOW_CREATE_WAYLAND_WINDOW_ID_STRING` - a string used as
1379+
* a stable identifier for toplevel windows for the purpose of allowing
1380+
* the compositor to save/restore their state between runs.
13521381
* - `SDL_PROP_WINDOW_CREATE_WAYLAND_WL_SURFACE_POINTER` - the wl_surface
13531382
* associated with the window, if you want to wrap an existing window. See
13541383
* [README-wayland](README-wayland) for more information.
@@ -1440,6 +1469,7 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_CreateWindowWithProperties(SDL_Prop
14401469
#define SDL_PROP_WINDOW_CREATE_WINDOWSCENE_POINTER "SDL.window.create.uikit.windowscene"
14411470
#define SDL_PROP_WINDOW_CREATE_WAYLAND_SURFACE_ROLE_CUSTOM_BOOLEAN "SDL.window.create.wayland.surface_role_custom"
14421471
#define SDL_PROP_WINDOW_CREATE_WAYLAND_CREATE_EGL_WINDOW_BOOLEAN "SDL.window.create.wayland.create_egl_window"
1472+
#define SDL_PROP_WINDOW_CREATE_WAYLAND_WINDOW_ID_STRING "SDL.window.create.wayland.window_id"
14431473
#define SDL_PROP_WINDOW_CREATE_WAYLAND_WL_SURFACE_POINTER "SDL.window.create.wayland.wl_surface"
14441474
#define SDL_PROP_WINDOW_CREATE_WIN32_HWND_POINTER "SDL.window.create.win32.hwnd"
14451475
#define SDL_PROP_WINDOW_CREATE_WIN32_PIXEL_FORMAT_HWND_POINTER "SDL.window.create.win32.pixel_format_hwnd"
@@ -1597,6 +1627,11 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_GetWindowParent(SDL_Window *window)
15971627
* with the window
15981628
* - `SDL_PROP_WINDOW_WAYLAND_EGL_WINDOW_POINTER`: the wl_egl_window
15991629
* associated with the window
1630+
* - `SDL_PROP_WINDOW_WAYLAND_WINDOW_ID_STRING`: the window identification string,
1631+
* initially set with SDL_PROP_WINDOW_CREATE_WAYLAND_WINDOW_ID_STRING. and used
1632+
* as an identifier for session management. Setting this to null before
1633+
* destroying the window will cause any session information related to the
1634+
* window to be removed.
16001635
* - `SDL_PROP_WINDOW_WAYLAND_XDG_SURFACE_POINTER`: the xdg_surface associated
16011636
* with the window
16021637
* - `SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_POINTER`: the xdg_toplevel role
@@ -1663,6 +1698,7 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetWindowProperties(SDL_Window
16631698
#define SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER "SDL.window.wayland.surface"
16641699
#define SDL_PROP_WINDOW_WAYLAND_VIEWPORT_POINTER "SDL.window.wayland.viewport"
16651700
#define SDL_PROP_WINDOW_WAYLAND_EGL_WINDOW_POINTER "SDL.window.wayland.egl_window"
1701+
#define SDL_PROP_WINDOW_WAYLAND_WINDOW_ID_STRING "SDL.window.wayland.window_id"
16661702
#define SDL_PROP_WINDOW_WAYLAND_XDG_SURFACE_POINTER "SDL.window.wayland.xdg_surface"
16671703
#define SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_POINTER "SDL.window.wayland.xdg_toplevel"
16681704
#define SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_EXPORT_HANDLE_STRING "SDL.window.wayland.xdg_toplevel_export_handle"

src/video/wayland/SDL_waylandvideo.c

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
#include "pointer-warp-v1-client-protocol.h"
7272
#include "pointer-gestures-unstable-v1-client-protocol.h"
7373
#include "single-pixel-buffer-v1-client-protocol.h"
74+
#include "xdg-session-management-v1-client-protocol.h"
7475

7576
#ifdef HAVE_LIBDECOR_H
7677
#include <libdecor.h>
@@ -1326,6 +1327,60 @@ static void Wayland_InitColorManager(SDL_VideoData *d)
13261327
}
13271328
}
13281329

1330+
static void handle_xdg_session_created(void *data, struct xdg_session_v1 *xdg_session_v1, const char *id)
1331+
{
1332+
SDL_SetStringProperty(SDL_GetGlobalProperties(), SDL_PROP_GLOBAL_VIDEO_WAYLAND_SESSION_ID_STRING, id);
1333+
}
1334+
1335+
static void handle_xdg_session_restored(void *data, struct xdg_session_v1 *xdg_session_v1)
1336+
{
1337+
// NOP
1338+
}
1339+
1340+
static void handle_xdg_session_replaced(void *data, struct xdg_session_v1 *xdg_session_v1)
1341+
{
1342+
SDL_SetStringProperty(SDL_GetGlobalProperties(), SDL_PROP_GLOBAL_VIDEO_WAYLAND_SESSION_ID_STRING, NULL);
1343+
}
1344+
1345+
static const struct xdg_session_v1_listener xdg_session_listener = {
1346+
.created = handle_xdg_session_created,
1347+
.restored = handle_xdg_session_restored,
1348+
.replaced = handle_xdg_session_replaced
1349+
};
1350+
1351+
void Wayland_SessionCreate(SDL_VideoData *viddata)
1352+
{
1353+
if (!viddata->xdg_session_manager) {
1354+
return;
1355+
}
1356+
1357+
// Register a new session, if one does not yet exist.
1358+
if (!viddata->xdg_session) {
1359+
const char *session_id = SDL_GetStringProperty(SDL_GetGlobalProperties(), SDL_PROP_GLOBAL_VIDEO_WAYLAND_SESSION_ID_STRING, NULL);
1360+
if (session_id) {
1361+
if (*session_id == '\0') {
1362+
// Create a new session if the ID string is empty.
1363+
session_id = NULL;
1364+
}
1365+
1366+
viddata->xdg_session = xdg_session_manager_v1_get_session(viddata->xdg_session_manager, XDG_SESSION_MANAGER_V1_REASON_LAUNCH, session_id);
1367+
xdg_session_v1_add_listener(viddata->xdg_session, &xdg_session_listener, viddata);
1368+
}
1369+
}
1370+
}
1371+
1372+
static void Wayland_MaybeRemoveSession(SDL_VideoData *viddata)
1373+
{
1374+
// If the session string was cleared, remove the session.
1375+
if (viddata->xdg_session) {
1376+
const char *session_id = SDL_GetStringProperty(SDL_GetGlobalProperties(), SDL_PROP_GLOBAL_VIDEO_WAYLAND_SESSION_ID_STRING, NULL);
1377+
if (!session_id || *session_id == '\0') {
1378+
xdg_session_v1_remove(viddata->xdg_session);
1379+
WAYLAND_wl_display_roundtrip(viddata->display);
1380+
}
1381+
}
1382+
}
1383+
13291384
static void handle_xdg_wm_base_ping(void *data, struct xdg_wm_base *xdg, uint32_t serial)
13301385
{
13311386
xdg_wm_base_pong(xdg, serial);
@@ -1425,6 +1480,8 @@ static void handle_registry_global(void *data, struct wl_registry *registry, uin
14251480
Wayland_DisplayInitPointerGestureManager(d);
14261481
} else if (SDL_strcmp(interface, wp_single_pixel_buffer_manager_v1_interface.name) == 0) {
14271482
d->single_pixel_buffer_manager = wl_registry_bind(d->registry, id, &wp_single_pixel_buffer_manager_v1_interface, 1);
1483+
} else if (SDL_strcmp(interface, xdg_session_manager_v1_interface.name) == 0) {
1484+
d->xdg_session_manager = wl_registry_bind(d->registry, id, &xdg_session_manager_v1_interface, 1);
14281485
}
14291486
#ifdef SDL_WL_FIXES_VERSION
14301487
else if (SDL_strcmp(interface, wl_fixes_interface.name) == 0) {
@@ -1622,6 +1679,8 @@ static void Wayland_VideoCleanup(SDL_VideoDevice *_this)
16221679
SDL_VideoData *data = _this->internal;
16231680
SDL_WaylandSeat *seat, *tmp;
16241681

1682+
Wayland_MaybeRemoveSession(data);
1683+
16251684
for (int i = _this->num_displays - 1; i >= 0; --i) {
16261685
SDL_VideoDisplay *display = _this->displays[i];
16271686
Wayland_free_display(display, false);
@@ -1779,6 +1838,16 @@ static void Wayland_VideoCleanup(SDL_VideoDevice *_this)
17791838
data->single_pixel_buffer_manager = NULL;
17801839
}
17811840

1841+
if (data->xdg_session) {
1842+
xdg_session_v1_destroy(data->xdg_session);
1843+
data->xdg_session = NULL;
1844+
}
1845+
1846+
if (data->xdg_session_manager) {
1847+
xdg_session_manager_v1_destroy(data->xdg_session_manager);
1848+
data->xdg_session_manager = NULL;
1849+
}
1850+
17821851
if (data->subcompositor) {
17831852
wl_subcompositor_destroy(data->subcompositor);
17841853
data->subcompositor = NULL;

src/video/wayland/SDL_waylandvideo.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,9 @@ struct SDL_VideoData
8787
struct wl_fixes *wl_fixes;
8888
struct zwp_pointer_gestures_v1 *zwp_pointer_gestures;
8989
struct wp_single_pixel_buffer_manager_v1 *single_pixel_buffer_manager;
90+
struct xdg_session_manager_v1 *xdg_session_manager;
9091

92+
struct xdg_session_v1 *xdg_session;
9193
struct xkb_context *xkb_context;
9294

9395
struct wl_list seat_list;
@@ -103,6 +105,7 @@ struct SDL_VideoData
103105
bool display_disconnected;
104106
bool display_externally_owned;
105107
bool scale_to_display_enabled;
108+
bool session_ready;
106109
};
107110

108111
struct SDL_DisplayData
@@ -162,9 +165,12 @@ extern bool SDL_WAYLAND_own_surface(struct wl_surface *surface);
162165
extern bool SDL_WAYLAND_own_output(struct wl_output *output);
163166

164167
extern SDL_WindowData *Wayland_GetWindowDataForOwnedSurface(struct wl_surface *surface);
165-
void Wayland_AddWindowDataToExternalList(SDL_WindowData *data);
166-
void Wayland_RemoveWindowDataFromExternalList(SDL_WindowData *data);
167-
struct wl_event_queue *Wayland_DisplayCreateQueue(struct wl_display *display, const char *name);
168+
extern void Wayland_AddWindowDataToExternalList(SDL_WindowData *data);
169+
extern void Wayland_RemoveWindowDataFromExternalList(SDL_WindowData *data);
170+
171+
extern void Wayland_SessionCreate(SDL_VideoData *data);
172+
173+
extern struct wl_event_queue *Wayland_DisplayCreateQueue(struct wl_display *display, const char *name);
168174

169175
extern bool Wayland_LoadLibdecor(SDL_VideoData *data, bool ignore_xdg);
170176

src/video/wayland/SDL_waylandwindow.c

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
#include "frog-color-management-v1-client-protocol.h"
4949
#include "xdg-toplevel-icon-v1-client-protocol.h"
5050
#include "color-management-v1-client-protocol.h"
51+
#include "xdg-session-management-v1-client-protocol.h"
5152

5253
#ifdef HAVE_LIBDECOR_H
5354
#include <libdecor.h>
@@ -2018,6 +2019,38 @@ bool Wayland_SetWindowModal(SDL_VideoDevice *_this, SDL_Window *window, bool mod
20182019
return true;
20192020
}
20202021

2022+
static void Wayland_RegisterToplevelForSession(SDL_WindowData *data)
2023+
{
2024+
SDL_VideoData *viddata = data->waylandData;
2025+
2026+
if (viddata->xdg_session_manager) {
2027+
const char *id = SDL_GetStringProperty(data->sdlwindow->props, SDL_PROP_WINDOW_WAYLAND_WINDOW_ID_STRING, NULL);
2028+
if (id && *id != '\0') {
2029+
Wayland_SessionCreate(viddata);
2030+
data->xdg_toplevel_session = xdg_session_v1_restore_toplevel(viddata->xdg_session, GetToplevelForWindow(data), id);
2031+
data->session_id = SDL_strdup(id);
2032+
}
2033+
}
2034+
}
2035+
2036+
static void Wayland_DestroyToplevelSession(SDL_WindowData *data)
2037+
{
2038+
SDL_VideoData *viddata = data->waylandData;
2039+
2040+
if (data->xdg_toplevel_session) {
2041+
const char *id = SDL_GetStringProperty(data->sdlwindow->props, SDL_PROP_WINDOW_WAYLAND_WINDOW_ID_STRING, NULL);
2042+
if (!id || SDL_strcmp(data->session_id, id) != 0) {
2043+
xdg_session_v1_remove_toplevel(viddata->xdg_session, data->session_id);
2044+
}
2045+
2046+
xdg_toplevel_session_v1_destroy(data->xdg_toplevel_session);
2047+
data->xdg_toplevel_session = NULL;
2048+
2049+
SDL_free(data->session_id);
2050+
data->session_id = NULL;
2051+
}
2052+
}
2053+
20212054
static void show_hide_sync_handler(void *data, struct wl_callback *callback, uint32_t callback_data)
20222055
{
20232056
// Get the window from the ID as it may have been destroyed
@@ -2224,6 +2257,7 @@ void Wayland_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window)
22242257
}
22252258

22262259
// Restore state that was set prior to this call
2260+
Wayland_RegisterToplevelForSession(data);
22272261
Wayland_SetWindowParent(_this, window, window->parent);
22282262

22292263
if (window->flags & SDL_WINDOW_MODAL) {
@@ -2384,6 +2418,8 @@ void Wayland_HideWindow(SDL_VideoDevice *_this, SDL_Window *window)
23842418

23852419
wind->shell_surface_status = WAYLAND_SHELL_SURFACE_STATUS_HIDDEN;
23862420

2421+
Wayland_DestroyToplevelSession(wind);
2422+
23872423
if (wind->server_decoration) {
23882424
zxdg_toplevel_decoration_v1_destroy(wind->server_decoration);
23892425
wind->server_decoration = NULL;
@@ -3021,6 +3057,12 @@ bool Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Proper
30213057
SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, data->surface);
30223058
SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_VIEWPORT_POINTER, data->viewport);
30233059
SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_EGL_WINDOW_POINTER, data->egl_window);
3060+
if (data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL || data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) {
3061+
const char *window_id = SDL_GetStringProperty(create_props, SDL_PROP_WINDOW_CREATE_WAYLAND_WINDOW_ID_STRING, NULL);
3062+
if (window_id && *window_id != '\0') {
3063+
SDL_SetStringProperty(props, SDL_PROP_WINDOW_WAYLAND_WINDOW_ID_STRING, window_id);
3064+
}
3065+
}
30243066

30253067
data->hit_test_result = SDL_HITTEST_NORMAL;
30263068

@@ -3553,6 +3595,7 @@ void Wayland_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window)
35533595

35543596
SDL_free(wind->outputs);
35553597
SDL_free(wind->app_id);
3598+
SDL_free(wind->session_id);
35563599

35573600
if (wind->gles_swap_frame_callback) {
35583601
wl_callback_destroy(wind->gles_swap_frame_callback);

src/video/wayland/SDL_waylandwindow.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ struct SDL_WindowData
118118
struct xdg_toplevel_icon_v1 *xdg_toplevel_icon_v1;
119119
struct frog_color_managed_surface *frog_color_managed_surface;
120120
struct wp_color_management_surface_feedback_v1 *wp_color_management_surface_feedback;
121+
struct xdg_toplevel_session_v1 *xdg_toplevel_session;
121122

122123
struct Wayland_ColorInfoState *color_info_state;
123124

@@ -127,6 +128,7 @@ struct SDL_WindowData
127128
int num_outputs;
128129

129130
char *app_id;
131+
char *session_id;
130132
double scale_factor;
131133

132134
struct wl_buffer **icon_buffers;

0 commit comments

Comments
 (0)