mirror of
https://github.com/libsdl-org/SDL.git
synced 2026-10-07 02:52:56 +08:00
wayland: Add support for the session management protocol
Add session management protocol support for saving/restoring toplevel window state across runs.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user