diff --git a/src/meson.build b/src/meson.build
index 49f3c4fe3..bfa8306d2 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -709,8 +709,12 @@ if have_wayland
'wayland/meta-wayland-xdg-foreign.c',
'wayland/meta-wayland-xdg-foreign.h',
'wayland/meta-wayland-xdg-foreign-private.h',
+ 'wayland/meta-wayland-xdg-session-manager.c',
+ 'wayland/meta-wayland-xdg-session-manager.h',
'wayland/meta-wayland-xdg-session-state.c',
'wayland/meta-wayland-xdg-session-state.h',
+ 'wayland/meta-wayland-xdg-session.c',
+ 'wayland/meta-wayland-xdg-session.h',
'wayland/meta-wayland-xdg-shell.c',
'wayland/meta-wayland-xdg-shell.h',
'wayland/meta-wayland-xdg-dialog.c',
diff --git a/src/wayland/meta-wayland-private.h b/src/wayland/meta-wayland-private.h
index 9cf193677..5016f7465 100644
--- a/src/wayland/meta-wayland-private.h
+++ b/src/wayland/meta-wayland-private.h
@@ -103,6 +103,7 @@ struct _MetaWaylandCompositor
MetaWaylandTabletManager *tablet_manager;
MetaWaylandActivation *activation;
MetaWaylandXdgForeign *foreign;
+ MetaWaylandXdgSessionManager *session_manager;
GHashTable *scheduled_surface_associations;
diff --git a/src/wayland/meta-wayland-versions.h b/src/wayland/meta-wayland-versions.h
index 897bd3cc3..f0d035506 100644
--- a/src/wayland/meta-wayland-versions.h
+++ b/src/wayland/meta-wayland-versions.h
@@ -60,3 +60,4 @@
#define META_XX_COLOR_MANAGEMENT_VERSION 1
#define META_XDG_DIALOG_VERSION 1
#define META_WP_DRM_LEASE_DEVICE_V1_VERSION 1
+#define META_XDG_SESSION_MANAGER_V1_VERSION 1
diff --git a/src/wayland/meta-wayland-xdg-session-manager.c b/src/wayland/meta-wayland-xdg-session-manager.c
new file mode 100644
index 000000000..6eee0b0e1
--- /dev/null
+++ b/src/wayland/meta-wayland-xdg-session-manager.c
@@ -0,0 +1,397 @@
+/*
+ * Copyright (C) 2024 Red Hat
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see .
+ *
+ */
+
+#include "config.h"
+
+#include "wayland/meta-wayland-xdg-session-manager.h"
+
+#include
+
+#include "wayland/meta-wayland-xdg-shell.h"
+#include "wayland/meta-wayland-xdg-session.h"
+#include "wayland/meta-wayland-private.h"
+#include "core/meta-debug-control-private.h"
+#include "core/meta-session-manager.h"
+
+#include "session-management-v1-server-protocol.h"
+
+typedef struct _MetaWaylandXdgSessionManager
+{
+ MetaWaylandCompositor *compositor;
+ struct wl_global *global;
+
+ GHashTable *sessions;
+ GHashTable *session_states;
+} MetaWaylandXdgSessionManager;
+
+static void xdg_session_manager_remove_session (MetaWaylandXdgSessionManager *session_manager,
+ MetaWaylandXdgSession *session);
+
+static void
+xdg_session_manager_destroy (struct wl_client *client,
+ struct wl_resource *resource)
+{
+ wl_resource_destroy (resource);
+}
+
+static gboolean
+on_restore_toplevel (MetaWaylandXdgSession *session,
+ MetaWaylandXdgToplevel *xdg_toplevel,
+ const char *name,
+ MetaWaylandXdgSessionManager *xdg_session_manager)
+{
+ MetaContext *context =
+ meta_wayland_compositor_get_context (xdg_session_manager->compositor);
+ MetaSessionManager *session_manager =
+ meta_context_get_session_manager (context);
+ MetaSessionState *session_state;
+ MetaWaylandSurface *surface;
+ MetaWindow *window;
+
+ session_state =
+ meta_session_manager_get_session (META_SESSION_MANAGER (session_manager),
+ META_TYPE_WAYLAND_XDG_SESSION_STATE,
+ meta_wayland_xdg_session_get_id (session));
+
+ surface =
+ meta_wayland_surface_role_get_surface (META_WAYLAND_SURFACE_ROLE (xdg_toplevel));
+ if (!surface)
+ return FALSE;
+
+ window = meta_wayland_surface_get_toplevel_window (surface);
+ if (!window)
+ return FALSE;
+
+ if (!meta_session_state_restore_window (session_state, name, window))
+ return FALSE;
+
+ meta_wayland_xdg_toplevel_set_hint_restored (xdg_toplevel);
+
+ return TRUE;
+}
+
+static void
+on_save_toplevel (MetaWaylandXdgSession *session,
+ MetaWaylandXdgToplevel *xdg_toplevel,
+ const char *name,
+ MetaWindow *window,
+ MetaWaylandXdgSessionManager *xdg_session_manager)
+{
+ MetaContext *context =
+ meta_wayland_compositor_get_context (xdg_session_manager->compositor);
+ MetaSessionManager *session_manager =
+ meta_context_get_session_manager (context);
+ MetaSessionState *session_state;
+
+ session_state =
+ meta_session_manager_get_session (META_SESSION_MANAGER (session_manager),
+ META_TYPE_WAYLAND_XDG_SESSION_STATE,
+ meta_wayland_xdg_session_get_id (session));
+
+ meta_session_state_save_window (session_state, name, window);
+}
+
+static void
+on_remove_toplevel (MetaWaylandXdgSession *session,
+ const char *name,
+ MetaWaylandXdgSessionManager *xdg_session_manager)
+{
+ MetaContext *context =
+ meta_wayland_compositor_get_context (xdg_session_manager->compositor);
+ MetaSessionManager *session_manager =
+ meta_context_get_session_manager (context);
+ MetaSessionState *session_state;
+
+ session_state =
+ meta_session_manager_get_session (session_manager,
+ META_TYPE_WAYLAND_XDG_SESSION_STATE,
+ meta_wayland_xdg_session_get_id (session));
+
+ meta_session_state_remove_window (session_state, name);
+}
+
+static void
+on_session_destroyed (MetaWaylandXdgSession *session,
+ MetaWaylandXdgSessionManager *session_manager)
+{
+ xdg_session_manager_remove_session (session_manager, session);
+}
+
+static void
+on_session_delete (MetaWaylandXdgSession *xdg_session,
+ MetaWaylandXdgSessionManager *xdg_session_manager)
+{
+ MetaContext *context =
+ meta_wayland_compositor_get_context (xdg_session_manager->compositor);
+ MetaSessionManager *session_manager =
+ meta_context_get_session_manager (context);
+ const char *session_id = meta_wayland_xdg_session_get_id (xdg_session);
+
+ g_hash_table_remove (xdg_session_manager->session_states, session_id);
+ meta_session_manager_delete_session (session_manager, session_id);
+}
+
+static void
+xdg_session_manager_remove_session (MetaWaylandXdgSessionManager *session_manager,
+ MetaWaylandXdgSession *session)
+{
+ const char *session_id = meta_wayland_xdg_session_get_id (session);
+
+ g_signal_handlers_disconnect_by_func (session,
+ on_session_destroyed,
+ session_manager);
+ g_signal_handlers_disconnect_by_func (session,
+ on_restore_toplevel,
+ session_manager);
+ g_signal_handlers_disconnect_by_func (session,
+ on_save_toplevel,
+ session_manager);
+ g_signal_handlers_disconnect_by_func (session,
+ on_remove_toplevel,
+ session_manager);
+ g_signal_handlers_disconnect_by_func (session,
+ on_session_delete,
+ session_manager);
+
+ g_hash_table_remove (session_manager->sessions, session_id);
+}
+
+static char *
+generate_session_id (MetaWaylandXdgSessionManager *session_manager)
+{
+ while (TRUE)
+ {
+ g_autofree char *id = NULL;
+
+ id = g_uuid_string_random ();
+ if (!g_hash_table_lookup (session_manager->sessions, id))
+ return g_steal_pointer (&id);
+ }
+}
+
+static void
+xdg_session_manager_get_session (struct wl_client *client,
+ struct wl_resource *resource,
+ uint32_t id,
+ uint32_t reason_value,
+ const char *session_id)
+{
+ MetaWaylandXdgSessionManager *xdg_session_manager =
+ wl_resource_get_user_data (resource);
+ MetaContext *context =
+ meta_wayland_compositor_get_context (xdg_session_manager->compositor);
+ MetaSessionManager *session_manager =
+ meta_context_get_session_manager (context);
+ g_autoptr (MetaSessionState) session_state = NULL;
+ g_autoptr (MetaWaylandXdgSession) session = NULL;
+ g_autofree char *name = NULL, *stolen_name = NULL;
+ gboolean created = FALSE;
+
+ /* Unknown session ID is the same as NULL */
+ if (session_id &&
+ !meta_session_manager_get_session_exists (session_manager, session_id))
+ session_id = NULL;
+
+ if (session_id)
+ {
+ MetaWaylandXdgSession *prev_session;
+
+ prev_session =
+ g_hash_table_lookup (xdg_session_manager->sessions, session_id);
+
+ if (prev_session)
+ {
+ if (meta_wayland_xdg_session_is_same_client (prev_session, client))
+ {
+ wl_resource_post_error (resource,
+ XX_SESSION_MANAGER_V1_ERROR_IN_USE,
+ "Session %s already in use",
+ session_id);
+ return;
+ }
+
+ /* Replace existing session */
+ meta_wayland_xdg_session_emit_replaced (prev_session);
+ xdg_session_manager_remove_session (xdg_session_manager,
+ prev_session);
+ }
+
+ name = g_strdup (session_id);
+ }
+ else
+ {
+ name = generate_session_id (xdg_session_manager);
+ created = TRUE;
+ }
+
+ if (!g_hash_table_steal_extended (xdg_session_manager->session_states,
+ name,
+ (gpointer *) &stolen_name,
+ (gpointer *) &session_state))
+ {
+ session_state =
+ meta_session_manager_get_session (session_manager,
+ META_TYPE_WAYLAND_XDG_SESSION_STATE,
+ name);
+ }
+
+ session = meta_wayland_xdg_session_new (META_WAYLAND_XDG_SESSION_STATE (session_state),
+ client,
+ wl_resource_get_version (resource),
+ id);
+ g_signal_connect (session, "destroyed",
+ G_CALLBACK (on_session_destroyed), xdg_session_manager);
+ g_signal_connect (session, "restore-toplevel",
+ G_CALLBACK (on_restore_toplevel), xdg_session_manager);
+ g_signal_connect (session, "save-toplevel",
+ G_CALLBACK (on_save_toplevel), xdg_session_manager);
+ g_signal_connect (session, "remove-toplevel",
+ G_CALLBACK (on_remove_toplevel), xdg_session_manager);
+ g_signal_connect (session, "delete",
+ G_CALLBACK (on_session_delete), xdg_session_manager);
+
+ if (created)
+ meta_wayland_xdg_session_emit_created (session);
+ else
+ meta_wayland_xdg_session_emit_restored (session);
+
+ g_hash_table_insert (xdg_session_manager->sessions,
+ g_strdup (name),
+ session);
+
+ g_hash_table_insert (xdg_session_manager->session_states,
+ g_strdup (name),
+ g_steal_pointer (&session_state));
+}
+
+static const struct xx_session_manager_v1_interface meta_xdg_session_manager_interface = {
+ xdg_session_manager_destroy,
+ xdg_session_manager_get_session,
+};
+
+static void
+bind_session_manager (struct wl_client *client,
+ void *data,
+ uint32_t version,
+ uint32_t id)
+{
+ MetaWaylandXdgSessionManager *session_manager = data;
+ struct wl_resource *resource;
+
+ resource = wl_resource_create (client, &xx_session_manager_v1_interface,
+ version, id);
+ wl_resource_set_implementation (resource, &meta_xdg_session_manager_interface,
+ session_manager, NULL);
+}
+
+static void
+update_enabled (MetaWaylandXdgSessionManager *session_manager)
+{
+ MetaWaylandCompositor *compositor = session_manager->compositor;
+ MetaDebugControl *debug_control =
+ meta_context_get_debug_control (compositor->context);
+ gboolean is_enabled;
+
+ is_enabled =
+ meta_debug_control_is_session_management_protocol_enabled (debug_control);
+
+ if (is_enabled && !session_manager->global)
+ {
+ struct wl_display *wayland_display;
+
+ wayland_display =
+ meta_wayland_compositor_get_wayland_display (compositor);
+
+ session_manager->global =
+ wl_global_create (wayland_display,
+ &xx_session_manager_v1_interface,
+ META_XDG_SESSION_MANAGER_V1_VERSION,
+ session_manager, bind_session_manager);
+ if (!session_manager->global)
+ g_error ("Could not create session manager global");
+ }
+ else if (!is_enabled)
+ {
+ g_clear_pointer (&session_manager->global, wl_global_destroy);
+ }
+}
+
+static MetaWaylandXdgSessionManager *
+meta_wayland_session_manager_new (MetaWaylandCompositor *compositor)
+{
+ MetaWaylandXdgSessionManager *session_manager;
+
+ session_manager = g_new0 (MetaWaylandXdgSessionManager, 1);
+ session_manager->compositor = compositor;
+
+ session_manager->sessions =
+ g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, NULL);
+ session_manager->session_states =
+ g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, g_object_unref);
+
+ return session_manager;
+}
+
+static void
+meta_wayland_session_manager_free (MetaWaylandXdgSessionManager *session_manager)
+{
+ g_clear_pointer (&session_manager->sessions, g_hash_table_unref);
+ g_clear_pointer (&session_manager->session_states, g_hash_table_unref);
+ g_free (session_manager);
+}
+
+static void
+on_protocol_enabled_changed (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ MetaWaylandXdgSessionManager *session_manager = user_data;
+
+ update_enabled (session_manager);
+}
+
+void
+meta_wayland_xdg_session_management_init (MetaWaylandCompositor *compositor)
+{
+ MetaDebugControl *debug_control =
+ meta_context_get_debug_control (compositor->context);
+
+ compositor->session_manager = meta_wayland_session_manager_new (compositor);
+
+ g_signal_connect (debug_control, "notify::session-management-protocol",
+ G_CALLBACK (on_protocol_enabled_changed),
+ compositor->session_manager);
+
+ update_enabled (compositor->session_manager);
+}
+
+void
+meta_wayland_xdg_session_management_finalize (MetaWaylandCompositor *compositor)
+{
+ MetaDebugControl *debug_control =
+ meta_context_get_debug_control (compositor->context);
+
+ g_signal_handlers_disconnect_by_func (debug_control,
+ on_protocol_enabled_changed,
+ compositor->session_manager);
+
+ g_clear_pointer (&compositor->session_manager,
+ meta_wayland_session_manager_free);
+}
diff --git a/src/wayland/meta-wayland-xdg-session-manager.h b/src/wayland/meta-wayland-xdg-session-manager.h
new file mode 100644
index 000000000..2b73f6972
--- /dev/null
+++ b/src/wayland/meta-wayland-xdg-session-manager.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 Red Hat
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see .
+ *
+ */
+
+#pragma once
+
+#include "wayland/meta-wayland-types.h"
+
+void meta_wayland_xdg_session_management_init (MetaWaylandCompositor *compositor);
+
+void meta_wayland_xdg_session_management_finalize (MetaWaylandCompositor *compositor);
diff --git a/src/wayland/meta-wayland-xdg-session.c b/src/wayland/meta-wayland-xdg-session.c
new file mode 100644
index 000000000..0863f463d
--- /dev/null
+++ b/src/wayland/meta-wayland-xdg-session.c
@@ -0,0 +1,457 @@
+/*
+ * Copyright (C) 2024 Red Hat
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see .
+ *
+ */
+
+#include "config.h"
+
+#include "wayland/meta-wayland-xdg-session.h"
+
+#include
+
+#include "wayland/meta-wayland-xdg-session-state.h"
+#include "wayland/meta-wayland-xdg-shell.h"
+
+#include "session-management-v1-server-protocol.h"
+
+
+typedef struct _MetaWaylandXdgToplevelSession
+{
+ grefcount ref_count;
+ MetaWaylandSurface *surface;
+ struct wl_resource *resource;
+ MetaWaylandXdgSession *session;
+ char *name;
+} MetaWaylandXdgToplevelSession;
+
+enum
+{
+ DESTROYED,
+ RESTORE_TOPLEVEL,
+ SAVE_TOPLEVEL,
+ REMOVE_TOPLEVEL,
+ DELETE,
+
+ N_SIGNALS
+};
+
+static guint signals[N_SIGNALS];
+
+struct _MetaWaylandXdgSession
+{
+ GObject parent;
+
+ char *id;
+ struct wl_resource *resource;
+ GHashTable *toplevels; /* name -> MetaWaylandXdgToplevelSession */
+};
+
+G_DEFINE_FINAL_TYPE (MetaWaylandXdgSession,
+ meta_wayland_xdg_session,
+ G_TYPE_OBJECT)
+
+static void on_window_unmanaging (MetaWindow *window,
+ MetaWaylandXdgToplevelSession *toplevel_session);
+
+static MetaWaylandXdgToplevelSession *
+meta_wayland_xdg_toplevel_session_ref (MetaWaylandXdgToplevelSession *toplevel_session)
+{
+ g_ref_count_inc (&toplevel_session->ref_count);
+ return toplevel_session;
+}
+
+static void
+meta_wayland_xdg_toplevel_session_unref (MetaWaylandXdgToplevelSession *toplevel_session)
+{
+ if (g_ref_count_dec (&toplevel_session->ref_count))
+ {
+ MetaWindow *window = NULL;
+ MetaWaylandSurface *surface = toplevel_session->surface;
+
+ if (surface)
+ window = meta_wayland_surface_get_toplevel_window (surface);
+
+ if (window)
+ {
+ g_signal_handlers_disconnect_by_func (window,
+ on_window_unmanaging,
+ toplevel_session);
+ }
+
+ g_free (toplevel_session->name);
+ g_free (toplevel_session);
+ }
+}
+
+static void
+xdg_toplevel_session_destroy (struct wl_client *wl_client,
+ struct wl_resource *resource)
+{
+ wl_resource_destroy (resource);
+}
+
+static void
+xdg_toplevel_session_remove (struct wl_client *wl_client,
+ struct wl_resource *resource)
+{
+ MetaWaylandXdgToplevelSession *toplevel_session =
+ wl_resource_get_user_data (resource);
+ MetaWaylandXdgSession *session = toplevel_session->session;
+
+ if (session)
+ {
+ g_signal_emit (session, signals[REMOVE_TOPLEVEL], 0, toplevel_session->name);
+ g_hash_table_remove (session->toplevels, toplevel_session->name);
+ }
+
+ wl_resource_destroy (resource);
+}
+
+static const struct xx_toplevel_session_v1_interface meta_xdg_toplevel_session_interface = {
+ xdg_toplevel_session_destroy,
+ xdg_toplevel_session_remove,
+};
+
+static void
+xdg_toplevel_session_destructor (struct wl_resource *resource)
+{
+ MetaWaylandXdgToplevelSession *toplevel_session =
+ wl_resource_get_user_data (resource);
+
+ meta_wayland_xdg_toplevel_session_unref (toplevel_session);
+}
+
+static MetaWaylandXdgToplevelSession *
+meta_wayland_xdg_toplevel_session_new (MetaWaylandXdgSession *xdg_session,
+ MetaWaylandSurface *surface,
+ const char *name,
+ struct wl_client *wl_client,
+ uint32_t version,
+ uint32_t id)
+{
+ MetaWaylandXdgToplevelSession *toplevel_session;
+
+ toplevel_session = g_new0 (MetaWaylandXdgToplevelSession, 1);
+ g_ref_count_init (&toplevel_session->ref_count);
+ toplevel_session->surface = surface;
+ toplevel_session->session = xdg_session;
+ toplevel_session->name = g_strdup (name);
+ toplevel_session->resource =
+ wl_resource_create (wl_client,
+ &xx_toplevel_session_v1_interface,
+ version, id);
+ wl_resource_set_implementation (toplevel_session->resource,
+ &meta_xdg_toplevel_session_interface,
+ meta_wayland_xdg_toplevel_session_ref (toplevel_session),
+ xdg_toplevel_session_destructor);
+
+ return toplevel_session;
+}
+
+static void
+meta_wayland_xdg_toplevel_session_emit_restored (MetaWaylandXdgToplevelSession *toplevel_session)
+{
+ MetaWaylandXdgToplevel *xdg_toplevel =
+ META_WAYLAND_XDG_TOPLEVEL (toplevel_session->surface->role);
+ struct wl_resource *xdg_toplevel_resource =
+ meta_wayland_xdg_toplevel_get_resource (xdg_toplevel);
+
+ xx_toplevel_session_v1_send_restored (toplevel_session->resource,
+ xdg_toplevel_resource);
+}
+
+static void
+meta_wayland_xdg_session_dispose (GObject *object)
+{
+ MetaWaylandXdgSession *session = META_WAYLAND_XDG_SESSION (object);
+
+ g_clear_pointer (&session->id, g_free);
+ g_clear_pointer (&session->toplevels, g_hash_table_unref);
+
+ G_OBJECT_CLASS (meta_wayland_xdg_session_parent_class)->dispose (object);
+}
+
+static void
+meta_wayland_xdg_session_class_init (MetaWaylandXdgSessionClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = meta_wayland_xdg_session_dispose;
+
+ signals[DESTROYED] =
+ g_signal_new ("destroyed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 0);
+
+ signals[RESTORE_TOPLEVEL] =
+ g_signal_new ("restore-toplevel",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ g_signal_accumulator_true_handled,
+ NULL,
+ NULL,
+ G_TYPE_BOOLEAN, 2,
+ META_TYPE_WAYLAND_XDG_TOPLEVEL,
+ G_TYPE_STRING);
+ signals[SAVE_TOPLEVEL] =
+ g_signal_new ("save-toplevel",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 3,
+ META_TYPE_WAYLAND_XDG_TOPLEVEL,
+ G_TYPE_STRING,
+ META_TYPE_WINDOW);
+ signals[REMOVE_TOPLEVEL] =
+ g_signal_new ("remove-toplevel",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ G_TYPE_STRING);
+ signals[DELETE] =
+ g_signal_new ("delete",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+}
+
+static void
+meta_wayland_xdg_session_init (MetaWaylandXdgSession *session)
+{
+}
+
+static void
+xdg_session_destroy (struct wl_client *wl_client,
+ struct wl_resource *resource)
+{
+ wl_resource_destroy (resource);
+}
+
+static void
+xdg_session_remove (struct wl_client *wl_client,
+ struct wl_resource *resource)
+{
+ MetaWaylandXdgSession *session =
+ META_WAYLAND_XDG_SESSION (wl_resource_get_user_data (resource));
+
+ g_signal_emit (session, signals[DELETE], 0);
+
+ wl_resource_destroy (resource);
+}
+
+static void
+on_window_unmanaging (MetaWindow *window,
+ MetaWaylandXdgToplevelSession *toplevel_session)
+{
+ MetaWaylandXdgSession *session = toplevel_session->session;
+
+ if (session)
+ {
+ MetaWaylandXdgToplevel *xdg_toplevel =
+ META_WAYLAND_XDG_TOPLEVEL (toplevel_session->surface->role);
+
+ g_signal_emit (session, signals[SAVE_TOPLEVEL], 0,
+ xdg_toplevel, toplevel_session->name, window);
+ }
+
+ toplevel_session->surface = NULL;
+}
+
+static void
+xdg_session_add_toplevel (struct wl_client *wl_client,
+ struct wl_resource *resource,
+ uint32_t id,
+ struct wl_resource *toplevel_resource,
+ const char *name)
+{
+ MetaWaylandXdgSession *session =
+ META_WAYLAND_XDG_SESSION (wl_resource_get_user_data (resource));
+ MetaWaylandXdgToplevel *xdg_toplevel =
+ wl_resource_get_user_data (toplevel_resource);
+ MetaWaylandSurfaceRole *surface_role;
+ MetaWaylandSurface *surface;
+ MetaWaylandXdgToplevelSession *toplevel_session;
+ MetaWindow *window;
+
+ if (g_hash_table_lookup (session->toplevels, name))
+ {
+ wl_resource_post_error (resource, XX_SESSION_V1_ERROR_NAME_IN_USE,
+ "Name of toplevel was already in use");
+ return;
+ }
+
+ surface_role = META_WAYLAND_SURFACE_ROLE (xdg_toplevel);
+ surface = meta_wayland_surface_role_get_surface (surface_role);
+ toplevel_session =
+ meta_wayland_xdg_toplevel_session_new (session, surface, name,
+ wl_client,
+ wl_resource_get_version (resource),
+ id);
+ g_hash_table_insert (session->toplevels, g_strdup (name), toplevel_session);
+
+ window = meta_wayland_surface_get_toplevel_window (surface);
+ if (window)
+ {
+ g_signal_connect (window, "unmanaging",
+ G_CALLBACK (on_window_unmanaging), toplevel_session);
+ }
+}
+
+static void
+xdg_session_restore_toplevel (struct wl_client *wl_client,
+ struct wl_resource *resource,
+ uint32_t id,
+ struct wl_resource *toplevel_resource,
+ const char *name)
+{
+ MetaWaylandXdgSession *session =
+ META_WAYLAND_XDG_SESSION (wl_resource_get_user_data (resource));
+ MetaWaylandXdgToplevel *xdg_toplevel =
+ wl_resource_get_user_data (toplevel_resource);
+ MetaWaylandSurfaceRole *surface_role;
+ MetaWaylandSurface *surface;
+ MetaWaylandXdgToplevelSession *toplevel_session;
+ MetaWindow *window;
+ gboolean restored = FALSE;
+
+ if (g_hash_table_lookup (session->toplevels, name))
+ {
+ wl_resource_post_error (resource, XX_SESSION_V1_ERROR_NAME_IN_USE,
+ "Name of toplevel was already in use");
+ return;
+ }
+
+ surface_role = META_WAYLAND_SURFACE_ROLE (xdg_toplevel);
+ surface = meta_wayland_surface_role_get_surface (surface_role);
+ if (meta_wayland_surface_has_initial_commit (surface))
+ {
+ wl_resource_post_error (resource, XX_SESSION_V1_ERROR_ALREADY_MAPPED,
+ "Tried to restore an already mapped toplevel");
+ return;
+ }
+
+ toplevel_session =
+ meta_wayland_xdg_toplevel_session_new (session, surface, name,
+ wl_client,
+ wl_resource_get_version (resource),
+ id);
+ g_hash_table_insert (session->toplevels, g_strdup (name), toplevel_session);
+
+ window = meta_wayland_surface_get_toplevel_window (surface);
+ if (window)
+ {
+ g_signal_connect (window, "unmanaging",
+ G_CALLBACK (on_window_unmanaging), toplevel_session);
+ }
+
+ g_signal_emit (session,
+ signals[RESTORE_TOPLEVEL], 0,
+ xdg_toplevel, name, &restored);
+
+ if (restored)
+ meta_wayland_xdg_toplevel_session_emit_restored (toplevel_session);
+}
+
+static const struct xx_session_v1_interface meta_xdg_session_interface = {
+ xdg_session_destroy,
+ xdg_session_remove,
+ xdg_session_add_toplevel,
+ xdg_session_restore_toplevel,
+};
+
+static void
+xdg_session_destructor (struct wl_resource *resource)
+{
+ MetaWaylandXdgSession *session =
+ META_WAYLAND_XDG_SESSION (wl_resource_get_user_data (resource));
+ GHashTableIter iter;
+ gpointer value;
+
+ g_signal_emit (session, signals[DESTROYED], 0);
+
+ g_hash_table_iter_init (&iter, session->toplevels);
+ while (g_hash_table_iter_next (&iter, NULL, &value))
+ {
+ MetaWaylandXdgToplevelSession *toplevel_session = value;
+
+ toplevel_session->session = NULL;
+ }
+
+ g_object_unref (session);
+}
+
+MetaWaylandXdgSession *
+meta_wayland_xdg_session_new (MetaWaylandXdgSessionState *session_state,
+ struct wl_client *wl_client,
+ uint32_t version,
+ uint32_t id)
+{
+ g_autoptr (MetaWaylandXdgSession) session = NULL;
+
+ session = g_object_new (META_TYPE_WAYLAND_XDG_SESSION, NULL);
+ session->id =
+ g_strdup (meta_session_state_get_name (META_SESSION_STATE (session_state)));
+ session->toplevels =
+ g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free,
+ (GDestroyNotify) meta_wayland_xdg_toplevel_session_unref);
+ session->resource = wl_resource_create (wl_client,
+ &xx_session_v1_interface,
+ version, id);
+ wl_resource_set_implementation (session->resource,
+ &meta_xdg_session_interface,
+ g_object_ref (session),
+ xdg_session_destructor);
+
+ return g_steal_pointer (&session);
+}
+
+const char *
+meta_wayland_xdg_session_get_id (MetaWaylandXdgSession *session)
+{
+ return session->id;
+}
+
+void
+meta_wayland_xdg_session_emit_created (MetaWaylandXdgSession *session)
+{
+ xx_session_v1_send_created (session->resource, session->id);
+}
+
+void
+meta_wayland_xdg_session_emit_replaced (MetaWaylandXdgSession *session)
+{
+ xx_session_v1_send_replaced (session->resource);
+}
+
+void
+meta_wayland_xdg_session_emit_restored (MetaWaylandXdgSession *session)
+{
+ xx_session_v1_send_restored (session->resource);
+}
+
+gboolean
+meta_wayland_xdg_session_is_same_client (MetaWaylandXdgSession *session,
+ struct wl_client *client)
+{
+ return wl_resource_get_client (session->resource) == client;
+}
diff --git a/src/wayland/meta-wayland-xdg-session.h b/src/wayland/meta-wayland-xdg-session.h
new file mode 100644
index 000000000..f94a7a9b7
--- /dev/null
+++ b/src/wayland/meta-wayland-xdg-session.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 Red Hat
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see .
+ *
+ */
+
+#pragma once
+
+#include
+
+#include "wayland/meta-wayland-types.h"
+#include "wayland/meta-wayland-xdg-session-state.h"
+
+#define META_TYPE_WAYLAND_XDG_SESSION (meta_wayland_xdg_session_get_type ())
+G_DECLARE_FINAL_TYPE (MetaWaylandXdgSession,
+ meta_wayland_xdg_session,
+ META, WAYLAND_XDG_SESSION,
+ GObject)
+
+MetaWaylandXdgSession * meta_wayland_xdg_session_new (MetaWaylandXdgSessionState *session_state,
+ struct wl_client *wl_client,
+ uint32_t version,
+ uint32_t id);
+
+const char * meta_wayland_xdg_session_get_id (MetaWaylandXdgSession *session);
+
+void meta_wayland_xdg_session_emit_created (MetaWaylandXdgSession *session);
+
+void meta_wayland_xdg_session_emit_replaced (MetaWaylandXdgSession *session);
+
+void meta_wayland_xdg_session_emit_restored (MetaWaylandXdgSession *session);
+
+gboolean meta_wayland_xdg_session_is_same_client (MetaWaylandXdgSession *session,
+ struct wl_client *client);
diff --git a/src/wayland/meta-wayland.c b/src/wayland/meta-wayland.c
index d38efd178..fd7b6e473 100644
--- a/src/wayland/meta-wayland.c
+++ b/src/wayland/meta-wayland.c
@@ -48,6 +48,7 @@
#include "wayland/meta-wayland-inhibit-shortcuts-dialog.h"
#include "wayland/meta-wayland-inhibit-shortcuts.h"
#include "wayland/meta-wayland-legacy-xdg-foreign.h"
+#include "wayland/meta-wayland-linux-drm-syncobj.h"
#include "wayland/meta-wayland-outputs.h"
#include "wayland/meta-wayland-presentation-time-private.h"
#include "wayland/meta-wayland-private.h"
@@ -58,7 +59,7 @@
#include "wayland/meta-wayland-transaction.h"
#include "wayland/meta-wayland-xdg-dialog.h"
#include "wayland/meta-wayland-xdg-foreign.h"
-#include "wayland/meta-wayland-linux-drm-syncobj.h"
+#include "wayland/meta-wayland-xdg-session-manager.h"
#ifdef HAVE_XWAYLAND
#include "wayland/meta-wayland-x11-interop.h"
@@ -690,6 +691,7 @@ meta_wayland_compositor_finalize (GObject *object)
MetaBackend *backend = meta_context_get_backend (compositor->context);
ClutterActor *stage = meta_backend_get_stage (backend);
+ meta_wayland_xdg_session_management_finalize (compositor);
meta_wayland_activation_finalize (compositor);
meta_wayland_outputs_finalize (compositor);
meta_wayland_presentation_time_finalize (compositor);
@@ -892,6 +894,7 @@ meta_wayland_compositor_new (MetaContext *context)
meta_wayland_drm_syncobj_init (compositor);
meta_wayland_init_xdg_wm_dialog (compositor);
meta_wayland_init_color_management (compositor);
+ meta_wayland_xdg_session_management_init (compositor);
#ifdef HAVE_NATIVE_BACKEND
meta_wayland_drm_lease_manager_init (compositor);