Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/README-wayland.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ 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, assign the window a stable ID string with the
`SDL_PROP_WINDOW_CREATE_WAYLAND_WINDOW_ID_STRING` property at creation time, and serialize the global session property
string `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 functionality requires that the
compositor supports the `xdg_session_management_v1` protocol.

### 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
Expand Down
36 changes: 36 additions & 0 deletions include/SDL3/SDL_video.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,32 @@ 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.
*
* To save the current state of Wayland toplevel windows, serialize this value before
* shutting down. To restore the previous state, set this property to the previously
* serialized value before window creation begins.
*
* 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.
*
* Setting this to an empty string ("") will cause a new session to be created.
*
* Setting this to null or an empty string before shutting down 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.
*
Expand Down Expand Up @@ -1349,6 +1375,9 @@ 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.
* - `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.
Expand Down Expand Up @@ -1440,6 +1469,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"
Expand Down Expand Up @@ -1597,6 +1627,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 before
* destroying the window will cause any session information related to 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
Expand Down Expand Up @@ -1663,6 +1698,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"
Expand Down
69 changes: 69 additions & 0 deletions src/video/wayland/SDL_waylandvideo.c
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,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>
Expand Down Expand Up @@ -1326,6 +1327,60 @@ 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_SetStringProperty(SDL_GetGlobalProperties(), SDL_PROP_GLOBAL_VIDEO_WAYLAND_SESSION_ID_STRING, 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_SessionCreate(SDL_VideoData *viddata)
{
if (!viddata->xdg_session_manager) {
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;
}

viddata->xdg_session = xdg_session_manager_v1_get_session(viddata->xdg_session_manager, XDG_SESSION_MANAGER_V1_REASON_LAUNCH, session_id);
xdg_session_v1_add_listener(viddata->xdg_session, &xdg_session_listener, viddata);
}
}
}

static void Wayland_MaybeRemoveSession(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);
}
}
}

static void handle_xdg_wm_base_ping(void *data, struct xdg_wm_base *xdg, uint32_t serial)
{
xdg_wm_base_pong(xdg, serial);
Expand Down Expand Up @@ -1425,6 +1480,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) {
Expand Down Expand Up @@ -1622,6 +1679,8 @@ static void Wayland_VideoCleanup(SDL_VideoDevice *_this)
SDL_VideoData *data = _this->internal;
SDL_WaylandSeat *seat, *tmp;

Wayland_MaybeRemoveSession(data);

for (int i = _this->num_displays - 1; i >= 0; --i) {
SDL_VideoDisplay *display = _this->displays[i];
Wayland_free_display(display, false);
Expand Down Expand Up @@ -1779,6 +1838,16 @@ static void Wayland_VideoCleanup(SDL_VideoDevice *_this)
data->single_pixel_buffer_manager = NULL;
}

if (data->xdg_session) {
xdg_session_v1_destroy(data->xdg_session);
data->xdg_session = 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;
Expand Down
12 changes: 9 additions & 3 deletions src/video/wayland/SDL_waylandvideo.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -103,6 +105,7 @@ struct SDL_VideoData
bool display_disconnected;
bool display_externally_owned;
bool scale_to_display_enabled;
bool session_ready;
};

struct SDL_DisplayData
Expand Down Expand Up @@ -162,9 +165,12 @@ 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 void Wayland_SessionCreate(SDL_VideoData *data);

extern struct wl_event_queue *Wayland_DisplayCreateQueue(struct wl_display *display, const char *name);

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

Expand Down
43 changes: 43 additions & 0 deletions src/video/wayland/SDL_waylandwindow.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,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>
Expand Down Expand Up @@ -2018,6 +2019,38 @@ bool Wayland_SetWindowModal(SDL_VideoDevice *_this, SDL_Window *window, bool mod
return true;
}

static void Wayland_RegisterToplevelForSession(SDL_WindowData *data)
{
SDL_VideoData *viddata = data->waylandData;

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_SessionCreate(viddata);
data->xdg_toplevel_session = xdg_session_v1_restore_toplevel(viddata->xdg_session, GetToplevelForWindow(data), 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 (!id || SDL_strcmp(data->session_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
Expand Down Expand Up @@ -2224,6 +2257,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) {
Expand Down Expand Up @@ -2384,6 +2418,8 @@ void Wayland_HideWindow(SDL_VideoDevice *_this, SDL_Window *window)

wind->shell_surface_status = WAYLAND_SHELL_SURFACE_STATUS_HIDDEN;

Wayland_DestroyToplevelSession(wind);

if (wind->server_decoration) {
zxdg_toplevel_decoration_v1_destroy(wind->server_decoration);
wind->server_decoration = NULL;
Expand Down Expand Up @@ -3021,6 +3057,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;

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

SDL_free(wind->outputs);
SDL_free(wind->app_id);
SDL_free(wind->session_id);

if (wind->gles_swap_frame_callback) {
wl_callback_destroy(wind->gles_swap_frame_callback);
Expand Down
2 changes: 2 additions & 0 deletions src/video/wayland/SDL_waylandwindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -127,6 +128,7 @@ struct SDL_WindowData
int num_outputs;

char *app_id;
char *session_id;
double scale_factor;

struct wl_buffer **icon_buffers;
Expand Down
Loading
Loading