Merge branch 'xdg-session-management-v1' into 'master'

wlr_xdg_session_management_v1: new protocol implementation

See merge request wlroots/wlroots!5321
This commit is contained in:
Simon Ser 2026-04-12 10:20:11 +00:00
commit bc8f70d934
4 changed files with 580 additions and 1 deletions

View file

@ -0,0 +1,145 @@
/*
* This an unstable interface of wlroots. No guarantees are made regarding the
* future consistency of this API.
*/
#ifndef WLR_USE_UNSTABLE
#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features"
#endif
#ifndef WLR_TYPES_WLR_XDG_SESSION_MANAGEMENT_V1_H
#define WLR_TYPES_WLR_XDG_SESSION_MANAGEMENT_V1_H
#include <wayland-server-core.h>
#include <wayland-protocols/xdg-session-management-v1-enum.h>
/**
* A toplevel part of a session.
*/
struct wlr_xdg_toplevel_session_v1 {
struct wl_resource *resource;
struct wlr_xdg_session_v1 *session;
struct wlr_xdg_toplevel *toplevel;
struct wl_list link; // wlr_xdg_session_v1.toplevels
// Client-provided identifier, unique per session.
char *name;
struct {
struct wl_signal destroy;
// Toplevel session name has changed.
struct wl_signal rename;
} events;
struct {
bool restorable;
struct wl_listener toplevel_destroy;
} WLR_PRIVATE;
};
struct wlr_xdg_session_v1_remove_toplevel_event {
const char *name;
};
/**
* An application session.
*
* When a new session is created, compositors should load any saved state based
* on wlr_xdg_session_v1.id (if non-NULL), then call
* wlr_xdg_session_v1_notify_created() or wlr_xdg_session_v1_notify_restored().
*
* Compositors should save toplevel state for all toplevels added to the
* session.
*/
struct wlr_xdg_session_v1 {
struct wl_resource *resource;
struct wl_list link; // wlr_xdg_session_manager_v1.sessions
enum xdg_session_manager_v1_reason reason;
char *id;
struct wl_list toplevels; // wlr_xdg_toplevel_session_v1.link
struct {
struct wl_signal destroy;
// All session saved state should be erased.
struct wl_signal remove;
// Toplevel state should be tracked as part of the session.
struct wl_signal add_toplevel; // struct wlr_xdg_toplevel_session_v1 *
// If the toplevel state was saved, it should be restored and
// wlr_xdg_toplevel_session_v1_notify_restored() should be called. Then
// toplevel state should be tracked as part of the session.
struct wl_signal restore_toplevel; // struct wlr_xdg_toplevel_session_v1 *
// Toplevel state should no longer be tracked as part of the session.
struct wl_signal remove_toplevel; // struct wlr_xdg_session_v1_remove_toplevel_event *
} events;
struct {
bool initialized; // added or restored
} WLR_PRIVATE;
};
/**
* Session manager global.
*/
struct wlr_xdg_session_manager_v1 {
struct wl_global *global;
struct wl_list sessions; // wlr_xdg_session_v1.link
struct {
struct wl_signal destroy;
struct wl_signal new_session; // struct wlr_xdg_session_v1 *
} events;
struct {
struct wl_listener display_destroy;
} WLR_PRIVATE;
};
/**
* Create the session manager global.
*/
struct wlr_xdg_session_manager_v1 *wlr_xdg_session_manager_v1_create(
struct wl_display *display, uint32_t version);
/**
* Notify that a new session state has been created.
*
* This function should be called in response to
* wlr_xdg_session_manager_v1.events.new_session if no state could be restored.
*/
void wlr_xdg_session_v1_notify_created(struct wlr_xdg_session_v1 *session, const char *session_id);
/**
* Notify that the session has been restored.
*
* This function should be called in response to
* wlr_xdg_session_manager_v1.events.new_session if at least some state could
* be restored.
*/
void wlr_xdg_session_v1_notify_restored(struct wlr_xdg_session_v1 *session);
/**
* Notify that the session has been taken over by another client.
*
* This function destroys the session.
*/
void wlr_xdg_session_v1_notify_replaced_and_destroy(struct wlr_xdg_session_v1 *session);
/**
* Notify that the toplevel state has been restored.
*
* This event is part of a toplevel configure sequence.
*
* This function should be called in response to
* wlr_xdg_session_v1.events.restore_toplevel if the toplevel was successfully
* restored.
*/
uint32_t wlr_xdg_toplevel_session_v1_notify_restored(
struct wlr_xdg_toplevel_session_v1 *toplevel_session);
#endif

View file

@ -1,5 +1,5 @@
wayland_protos = dependency('wayland-protocols',
version: '>=1.47',
version: '>=1.48',
fallback: 'wayland-protocols',
default_options: ['tests=false'],
)
@ -44,6 +44,7 @@ protocols = {
'single-pixel-buffer-v1': wl_protocol_dir / 'staging/single-pixel-buffer/single-pixel-buffer-v1.xml',
'xdg-activation-v1': wl_protocol_dir / 'staging/xdg-activation/xdg-activation-v1.xml',
'xdg-dialog-v1': wl_protocol_dir / 'staging/xdg-dialog/xdg-dialog-v1.xml',
'xdg-session-management-v1': wl_protocol_dir / 'staging/xdg-session-management/xdg-session-management-v1.xml',
'xdg-system-bell-v1': wl_protocol_dir / 'staging/xdg-system-bell/xdg-system-bell-v1.xml',
'xdg-toplevel-icon-v1': wl_protocol_dir / 'staging/xdg-toplevel-icon/xdg-toplevel-icon-v1.xml',
'xdg-toplevel-tag-v1': wl_protocol_dir / 'staging/xdg-toplevel-tag/xdg-toplevel-tag-v1.xml',

View file

@ -105,6 +105,7 @@ wlr_files += files(
'wlr_xdg_foreign_v2.c',
'wlr_xdg_foreign_registry.c',
'wlr_xdg_output_v1.c',
'wlr_xdg_session_management_v1.c',
'wlr_xdg_system_bell_v1.c',
'wlr_xdg_toplevel_icon_v1.c',
'wlr_xdg_toplevel_tag_v1.c',

View file

@ -0,0 +1,432 @@
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <wlr/types/wlr_xdg_session_management_v1.h>
#include <wlr/types/wlr_xdg_shell.h>
#include "xdg-session-management-v1-protocol.h"
#define MANAGER_VERSION 1
static const struct xdg_toplevel_session_v1_interface toplevel_session_impl;
static const struct xdg_session_v1_interface session_impl;
static const struct xdg_session_manager_v1_interface manager_impl;
static struct wlr_xdg_toplevel_session_v1 *toplevel_session_from_resource(struct wl_resource *resource) {
assert(wl_resource_instance_of(resource, &xdg_toplevel_session_v1_interface, &toplevel_session_impl));
return wl_resource_get_user_data(resource);
}
static struct wlr_xdg_session_v1 *session_from_resource(struct wl_resource *resource) {
assert(wl_resource_instance_of(resource, &xdg_session_v1_interface, &session_impl));
return wl_resource_get_user_data(resource);
}
static struct wlr_xdg_session_manager_v1 *manager_from_resource(struct wl_resource *resource) {
assert(wl_resource_instance_of(resource, &xdg_session_manager_v1_interface, &manager_impl));
return wl_resource_get_user_data(resource);
}
static struct wlr_xdg_toplevel_session_v1 *session_find_toplevel(struct wlr_xdg_session_v1 *session,
const char *name);
static void toplevel_session_handle_rename(struct wl_client *client,
struct wl_resource *toplevel_session_resource, const char *name) {
struct wlr_xdg_toplevel_session_v1 *toplevel_session =
toplevel_session_from_resource(toplevel_session_resource);
if (toplevel_session == NULL) {
return;
}
if (strcmp(toplevel_session->name, name) == 0) {
return;
}
if (session_find_toplevel(toplevel_session->session, name) != NULL) {
wl_resource_post_error(toplevel_session_resource,
XDG_SESSION_V1_ERROR_NAME_IN_USE,
"Name already in use by another toplevel in the same session");
return;
}
char *name_copy = strdup(name);
if (name_copy == NULL) {
wl_resource_post_no_memory(toplevel_session_resource);
return;
}
free(toplevel_session->name);
toplevel_session->name = name_copy;
wl_signal_emit_mutable(&toplevel_session->events.rename, NULL);
}
static void toplevel_session_handle_destroy(struct wl_client *client,
struct wl_resource *toplevel_session_resource) {
wl_resource_destroy(toplevel_session_resource);
}
static const struct xdg_toplevel_session_v1_interface toplevel_session_impl = {
.destroy = toplevel_session_handle_destroy,
.rename = toplevel_session_handle_rename,
};
uint32_t wlr_xdg_toplevel_session_v1_notify_restored(
struct wlr_xdg_toplevel_session_v1 *toplevel_session) {
assert(toplevel_session->restorable);
toplevel_session->restorable = false;
xdg_toplevel_session_v1_send_restored(toplevel_session->resource);
return wlr_xdg_surface_schedule_configure(toplevel_session->toplevel->base);
}
static void toplevel_session_destroy(struct wlr_xdg_toplevel_session_v1 *toplevel_session) {
if (toplevel_session == NULL) {
return;
}
wl_list_remove(&toplevel_session->toplevel_destroy.link);
wl_signal_emit_mutable(&toplevel_session->events.destroy, NULL);
assert(wl_list_empty(&toplevel_session->events.destroy.listener_list));
assert(wl_list_empty(&toplevel_session->events.rename.listener_list));
wl_resource_set_user_data(toplevel_session->resource, NULL); // make inert
wl_list_remove(&toplevel_session->link);
free(toplevel_session->name);
free(toplevel_session);
}
static void toplevel_session_handle_resource_destroy(struct wl_resource *resource) {
struct wlr_xdg_toplevel_session_v1 *toplevel_session = toplevel_session_from_resource(resource);
toplevel_session_destroy(toplevel_session);
}
static void toplevel_session_handle_toplevel_destroy(struct wl_listener *listener, void *data) {
struct wlr_xdg_toplevel_session_v1 *toplevel_session =
wl_container_of(listener, toplevel_session, toplevel_destroy);
toplevel_session_destroy(toplevel_session);
}
static void session_handle_remove(struct wl_client *client, struct wl_resource *session_resource) {
struct wlr_xdg_session_v1 *session = session_from_resource(session_resource);
wl_signal_emit_mutable(&session->events.remove, NULL);
}
static struct wlr_xdg_toplevel_session_v1 *session_find_toplevel(struct wlr_xdg_session_v1 *session,
const char *name) {
struct wlr_xdg_toplevel_session_v1 *toplevel_session;
wl_list_for_each(toplevel_session, &session->toplevels, link) {
if (strcmp(toplevel_session->name, name) == 0) {
return toplevel_session;
}
}
return NULL;
}
static struct wlr_xdg_toplevel_session_v1 *session_create_toplevel(struct wl_resource *session_resource,
uint32_t id, struct wl_resource *toplevel_resource, const char *name) {
struct wlr_xdg_session_v1 *session = session_from_resource(session_resource);
struct wlr_xdg_toplevel *toplevel = wlr_xdg_toplevel_from_resource(toplevel_resource);
struct wl_client *client = wl_resource_get_client(session_resource);
uint32_t version = wl_resource_get_version(session_resource);
struct wl_resource *resource = wl_resource_create(client,
&xdg_toplevel_session_v1_interface, version, id);
if (resource == NULL) {
wl_resource_post_no_memory(session_resource);
return NULL;
}
wl_resource_set_implementation(resource, &toplevel_session_impl, NULL,
toplevel_session_handle_resource_destroy);
if (session == NULL || toplevel == NULL) {
return NULL;
}
if (session_find_toplevel(session, name) != NULL) {
wl_resource_post_error(session->resource, XDG_SESSION_V1_ERROR_NAME_IN_USE,
"Name already in use by a toplevel in the same session");
return NULL;
}
// TODO: post already_added error if toplevel was already added to *any* session
struct wlr_xdg_toplevel_session_v1 *toplevel_session;
wl_list_for_each(toplevel_session, &session->toplevels, link) {
if (toplevel_session->toplevel == toplevel) {
wl_resource_post_error(session->resource, XDG_SESSION_V1_ERROR_ALREADY_ADDED,
"Toplevel already added to session");
return NULL;
}
}
toplevel_session = calloc(1, sizeof(*toplevel_session));
if (toplevel_session == NULL) {
wl_resource_post_no_memory(session_resource);
return NULL;
}
toplevel_session->resource = resource;
toplevel_session->session = session;
toplevel_session->toplevel = toplevel;
toplevel_session->name = strdup(name);
if (toplevel_session->name == NULL) {
wl_resource_post_no_memory(session_resource);
free(toplevel_session);
return NULL;
}
toplevel_session->toplevel_destroy.notify = toplevel_session_handle_toplevel_destroy;
wl_signal_add(&toplevel->events.destroy, &toplevel_session->toplevel_destroy);
wl_resource_set_user_data(resource, toplevel_session);
wl_list_insert(&session->toplevels, &toplevel_session->link);
return toplevel_session;
}
static void session_handle_add_toplevel(struct wl_client *client, struct wl_resource *session_resource,
uint32_t id, struct wl_resource *toplevel_resource, const char *name) {
struct wlr_xdg_session_v1 *session = session_from_resource(session_resource);
struct wlr_xdg_toplevel_session_v1 *toplevel_session =
session_create_toplevel(session_resource, id, toplevel_resource, name);
if (toplevel_session == NULL) {
return;
}
wl_signal_emit_mutable(&session->events.add_toplevel, toplevel_session);
}
static void session_handle_restore_toplevel(struct wl_client *client, struct wl_resource *session_resource,
uint32_t id, struct wl_resource *toplevel_resource, const char *name) {
struct wlr_xdg_session_v1 *session = session_from_resource(session_resource);
struct wlr_xdg_toplevel *toplevel = wlr_xdg_toplevel_from_resource(toplevel_resource);
if (toplevel && toplevel->base->surface->mapped) {
wl_resource_post_error(session_resource, XDG_SESSION_V1_ERROR_ALREADY_MAPPED,
"Restored toplevel is already mapped");
return;
}
struct wlr_xdg_toplevel_session_v1 *toplevel_session =
session_create_toplevel(session_resource, id, toplevel_resource, name);
if (toplevel_session == NULL) {
return;
}
toplevel_session->restorable = true;
wl_signal_emit_mutable(&session->events.restore_toplevel, toplevel_session);
}
static void session_handle_remove_toplevel(struct wl_client *client, struct wl_resource *session_resource,
const char *name) {
struct wlr_xdg_session_v1 *session = session_from_resource(session_resource);
if (session == NULL) {
return;
}
struct wlr_xdg_session_v1_remove_toplevel_event event = {
.name = name,
};
wl_signal_emit_mutable(&session->events.remove_toplevel, &event);
struct wlr_xdg_toplevel_session_v1 *toplevel_session = session_find_toplevel(session, name);
toplevel_session_destroy(toplevel_session);
}
static void session_handle_destroy(struct wl_client *client, struct wl_resource *session_resource) {
wl_resource_destroy(session_resource);
}
static const struct xdg_session_v1_interface session_impl = {
.destroy = session_handle_destroy,
.remove = session_handle_remove,
.add_toplevel = session_handle_add_toplevel,
.restore_toplevel = session_handle_restore_toplevel,
.remove_toplevel = session_handle_remove_toplevel,
};
static void session_destroy(struct wlr_xdg_session_v1 *session) {
if (session == NULL) {
return;
}
wl_signal_emit_mutable(&session->events.destroy, NULL);
assert(wl_list_empty(&session->events.destroy.listener_list));
assert(wl_list_empty(&session->events.remove.listener_list));
assert(wl_list_empty(&session->events.add_toplevel.listener_list));
assert(wl_list_empty(&session->events.restore_toplevel.listener_list));
assert(wl_list_empty(&session->events.remove_toplevel.listener_list));
struct wlr_xdg_toplevel_session_v1 *toplevel_session, *toplevel_session_tmp;
wl_list_for_each_safe(toplevel_session, toplevel_session_tmp, &session->toplevels, link) {
toplevel_session_destroy(toplevel_session);
}
wl_resource_set_user_data(session->resource, NULL); // make inert
wl_list_remove(&session->link);
free(session->id);
free(session);
}
static void session_handle_resource_destroy(struct wl_resource *resource) {
struct wlr_xdg_session_v1 *session = session_from_resource(resource);
session_destroy(session);
}
void wlr_xdg_session_v1_notify_created(struct wlr_xdg_session_v1 *session, const char *session_id) {
assert(!session->initialized);
char *session_id_copy = strdup(session_id);
if (session_id_copy == NULL) {
wl_resource_post_no_memory(session->resource);
return;
}
free(session->id);
session->id = session_id_copy;
xdg_session_v1_send_created(session->resource, session_id);
session->initialized = true;
}
void wlr_xdg_session_v1_notify_restored(struct wlr_xdg_session_v1 *session) {
assert(!session->initialized);
assert(session->id != NULL);
xdg_session_v1_send_restored(session->resource);
session->initialized = true;
}
void wlr_xdg_session_v1_notify_replaced_and_destroy(struct wlr_xdg_session_v1 *session) {
xdg_session_v1_send_replaced(session->resource);
session_destroy(session);
}
static void manager_handle_get_session(struct wl_client *client,
struct wl_resource *manager_resource, uint32_t id, uint32_t reason,
const char *session_id) {
struct wlr_xdg_session_manager_v1 *manager = manager_from_resource(manager_resource);
uint32_t version = wl_resource_get_version(manager_resource);
if (!xdg_session_manager_v1_reason_is_valid(reason, version)) {
wl_resource_post_error(manager_resource, XDG_SESSION_MANAGER_V1_ERROR_INVALID_REASON,
"Invalid reason");
return;
}
struct wlr_xdg_session_v1 *sess;
wl_list_for_each(sess, &manager->sessions, link) {
if (wl_resource_get_client(sess->resource) == client && sess->id != NULL &&
strcmp(sess->id, session_id) == 0) {
wl_resource_post_error(manager_resource,
XDG_SESSION_MANAGER_V1_ERROR_IN_USE,
"Session ID already in use by another session object");
return;
}
}
struct wlr_xdg_session_v1 *session = calloc(1, sizeof(*session));
if (session == NULL) {
wl_client_post_no_memory(client);
return;
}
char *session_id_copy = NULL;
if (session_id != NULL) {
session_id_copy = strdup(session_id);
if (session_id_copy == NULL) {
wl_client_post_no_memory(client);
free(session);
return;
}
}
session->resource = wl_resource_create(client,
&xdg_session_v1_interface, version, id);
if (session->resource == NULL) {
wl_resource_post_no_memory(manager_resource);
free(session);
return;
}
wl_resource_set_implementation(session->resource, &session_impl, session,
session_handle_resource_destroy);
wl_signal_init(&session->events.destroy);
wl_signal_init(&session->events.remove);
wl_signal_init(&session->events.add_toplevel);
wl_signal_init(&session->events.restore_toplevel);
wl_signal_init(&session->events.remove_toplevel);
session->reason = reason;
session->id = session_id_copy;
wl_list_init(&session->toplevels);
wl_list_insert(&manager->sessions, &session->link);
wl_signal_emit_mutable(&manager->events.new_session, session);
}
static void manager_handle_destroy(struct wl_client *client, struct wl_resource *manager_resource) {
wl_resource_destroy(manager_resource);
}
static const struct xdg_session_manager_v1_interface manager_impl = {
.destroy = manager_handle_destroy,
.get_session = manager_handle_get_session,
};
static void manager_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) {
struct wlr_xdg_session_manager_v1 *manager = data;
struct wl_resource *resource = wl_resource_create(client,
&xdg_session_manager_v1_interface, version, id);
if (resource == NULL) {
wl_client_post_no_memory(client);
return;
}
wl_resource_set_implementation(resource, &manager_impl, manager, NULL);
}
static void manager_handle_display_destroy(struct wl_listener *listener, void *data) {
struct wlr_xdg_session_manager_v1 *manager = wl_container_of(listener, manager, display_destroy);
wl_signal_emit_mutable(&manager->events.destroy, NULL);
assert(wl_list_empty(&manager->events.destroy.listener_list));
assert(wl_list_empty(&manager->events.new_session.listener_list));
wl_list_remove(&manager->display_destroy.link);
wl_global_destroy(manager->global);
free(manager);
}
struct wlr_xdg_session_manager_v1 *wlr_xdg_session_manager_v1_create(
struct wl_display *display, uint32_t version) {
assert(version <= MANAGER_VERSION);
struct wlr_xdg_session_manager_v1 *manager = calloc(1, sizeof(*manager));
if (manager == NULL) {
return NULL;
}
manager->global = wl_global_create(display, &xdg_session_manager_v1_interface,
version, manager, manager_bind);
if (manager->global == NULL) {
free(manager);
return NULL;
}
wl_signal_init(&manager->events.destroy);
wl_signal_init(&manager->events.new_session);
wl_list_init(&manager->sessions);
manager->display_destroy.notify = manager_handle_display_destroy;
wl_display_add_destroy_listener(display, &manager->display_destroy);
return manager;
}