diff --git a/src/tests/meson.build b/src/tests/meson.build
index 59385dd8d..889c3b019 100644
--- a/src/tests/meson.build
+++ b/src/tests/meson.build
@@ -630,6 +630,8 @@ wayland_test_cases = [
test_client_executables.get('xdg-apply-limits'),
test_client_executables.get('xdg-foreign'),
test_client_executables.get('xdg-toplevel-bounds'),
+ test_client_executables.get('xdg-session-management'),
+ test_client_executables.get('xdg-session-management-replace'),
test_client_executables.get('ycbcr'),
],
},
diff --git a/src/tests/wayland-test-clients/meson.build b/src/tests/wayland-test-clients/meson.build
index 7a1846ea3..708c541df 100644
--- a/src/tests/wayland-test-clients/meson.build
+++ b/src/tests/wayland-test-clients/meson.build
@@ -81,6 +81,12 @@ wayland_test_clients = [
{
'name': 'xdg-foreign',
},
+ {
+ 'name': 'xdg-session-management',
+ },
+ {
+ 'name': 'xdg-session-management-replace',
+ },
{
'name': 'xdg-toplevel-bounds',
},
diff --git a/src/tests/wayland-test-clients/xdg-session-management-replace.c b/src/tests/wayland-test-clients/xdg-session-management-replace.c
new file mode 100644
index 000000000..89cd87d1a
--- /dev/null
+++ b/src/tests/wayland-test-clients/xdg-session-management-replace.c
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2024 Red Hat Inc.
+ *
+ * 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
+
+#include "wayland-test-client-utils.h"
+
+#include "session-management-v1-client-protocol.h"
+
+
+typedef enum _TestState
+{
+ TEST_STATE_INIT = 0,
+ TEST_STATE_RECEIVE_REPLACED = 1,
+ TEST_STATE_ASSERT_RESTORED = 2,
+} TestState;
+
+typedef struct _TestDisplayState
+{
+ struct xx_session_manager_v1 *session_manager;
+ TestState state;
+} TestDisplayState;
+
+static void
+handle_registry_global (void *user_data,
+ struct wl_registry *registry,
+ uint32_t id,
+ const char *interface,
+ uint32_t version)
+{
+ WaylandDisplay *display = user_data;
+ TestDisplayState *test_state = display->test_state;
+
+ if (strcmp (interface, "xx_session_manager_v1") == 0)
+ {
+ test_state->session_manager =
+ wl_registry_bind (registry, id, &xx_session_manager_v1_interface, 1);
+ }
+}
+
+static void
+handle_registry_global_remove (void *user_data,
+ struct wl_registry *registry,
+ uint32_t name)
+{
+}
+
+static const struct wl_registry_listener registry_listener = {
+ handle_registry_global,
+ handle_registry_global_remove
+};
+
+typedef struct
+{
+ gboolean received_created;
+ gboolean received_restored;
+ gboolean received_replaced;
+ char *id;
+} TestCreateState;
+
+typedef struct
+{
+ gboolean configured;
+ gboolean restored;
+} ToplevelSessionState;
+
+static void
+test_create_created (void *user_data,
+ struct xx_session_v1 *xdg_session_v1,
+ const char *id)
+{
+ TestCreateState *state = user_data;
+
+ state->received_created = TRUE;
+ state->id = g_strdup (id);
+}
+
+static void
+test_create_restored (void *user_data,
+ struct xx_session_v1 *xdg_session_v1)
+{
+ TestCreateState *state = user_data;
+
+ state->received_restored = TRUE;
+}
+
+static void
+test_create_replaced (void *user_data,
+ struct xx_session_v1 *xdg_session_v1)
+{
+ TestCreateState *state = user_data;
+
+ state->received_replaced = TRUE;
+}
+
+static struct xx_session_v1_listener test_create_session_listener = {
+ test_create_created,
+ test_create_restored,
+ test_create_replaced,
+};
+
+static void
+toplevel_restored (void *user_data,
+ struct xx_toplevel_session_v1 *toplevel_session,
+ struct xdg_toplevel *toplevel)
+{
+ ToplevelSessionState *toplevel_state = user_data;
+
+ toplevel_state->restored = TRUE;
+}
+
+static struct xx_toplevel_session_v1_listener toplevel_session_listener = {
+ toplevel_restored,
+};
+
+static void
+on_toplevel_configured (WaylandSurface *surface,
+ ToplevelSessionState *toplevel_state)
+{
+ toplevel_state->configured = TRUE;
+}
+
+int
+main (int argc,
+ char **argv)
+{
+ g_autoptr (WaylandDisplay) display1 = NULL, display2 = NULL;
+ struct wl_registry *registry1, *registry2;
+ TestDisplayState *test_state1, *test_state2;
+ g_autoptr (WaylandSurface) toplevel1 = NULL, toplevel2 = NULL;
+ struct xx_session_v1 *session1, *session2;
+ struct xx_toplevel_session_v1 *toplevel_session1, *toplevel_session2;
+ TestCreateState state1 = {};
+ TestCreateState state2 = {};
+ ToplevelSessionState toplevel_state1 = {};
+ ToplevelSessionState toplevel_state2 = {};
+
+ display1 = wayland_display_new (WAYLAND_DISPLAY_CAPABILITY_TEST_DRIVER);
+ test_state1 = g_new0 (TestDisplayState, 1);
+ display1->test_state = test_state1;
+ display1->destroy_test_state = g_free;
+
+ registry1 = wl_display_get_registry (display1->display);
+ wl_registry_add_listener (registry1, ®istry_listener, display1);
+ wl_display_roundtrip (display1->display);
+
+ g_assert_nonnull (test_state1->session_manager);
+
+ toplevel1 = wayland_surface_new (display1, "toplevel",
+ 100, 100, 0xff50ff50);
+ g_signal_connect (toplevel1, "configure",
+ G_CALLBACK (on_toplevel_configured),
+ &toplevel_state1);
+
+ session1 =
+ xx_session_manager_v1_get_session (test_state1->session_manager,
+ XX_SESSION_MANAGER_V1_REASON_LAUNCH,
+ NULL);
+ xx_session_v1_add_listener (session1, &test_create_session_listener, &state1);
+
+ while (!state1.received_created)
+ wayland_display_dispatch (display1);
+ g_assert_nonnull (state1.id);
+
+ /* Test add before committing initial state. */
+ toplevel_session1 = xx_session_v1_add_toplevel (session1,
+ toplevel1->xdg_toplevel,
+ "toplevel");
+ xx_toplevel_session_v1_add_listener (toplevel_session1,
+ &toplevel_session_listener,
+ &toplevel_state1);
+ wl_surface_commit (toplevel1->wl_surface);
+
+ while (!toplevel_state1.configured)
+ wayland_display_dispatch (display1);
+ g_assert_false (toplevel_state1.restored);
+
+ display2 = wayland_display_new (WAYLAND_DISPLAY_CAPABILITY_TEST_DRIVER);
+ test_state2 = g_new0 (TestDisplayState, 1);
+ display2->test_state = test_state2;
+ display2->destroy_test_state = g_free;
+
+ registry2 = wl_display_get_registry (display2->display);
+ wl_registry_add_listener (registry2, ®istry_listener, display2);
+ wl_display_roundtrip (display2->display);
+
+ g_assert_nonnull (test_state2->session_manager);
+
+ toplevel2 = wayland_surface_new (display2, "toplevel",
+ 100, 100, 0xff50ff50);
+ g_signal_connect (toplevel2, "configure",
+ G_CALLBACK (on_toplevel_configured),
+ &toplevel_state2);
+
+ session2 =
+ xx_session_manager_v1_get_session (test_state2->session_manager,
+ XX_SESSION_MANAGER_V1_REASON_LAUNCH,
+ state1.id);
+ xx_session_v1_add_listener (session2, &test_create_session_listener, &state2);
+
+ while (!state2.received_restored)
+ wayland_display_dispatch (display2);
+
+ /* Test add before committing initial state. */
+ toplevel_session2 = xx_session_v1_restore_toplevel (session2,
+ toplevel2->xdg_toplevel,
+ "toplevel");
+ xx_toplevel_session_v1_add_listener (toplevel_session2,
+ &toplevel_session_listener,
+ &toplevel_state2);
+ wl_surface_commit (toplevel2->wl_surface);
+
+ while (!toplevel_state2.configured)
+ wayland_display_dispatch (display2);
+
+ /* check that the first client received the replaced event */
+ while (!state1.received_replaced)
+ wayland_display_dispatch (display1);
+
+ /* TODO: check that client1 is now inert */
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/tests/wayland-test-clients/xdg-session-management.c b/src/tests/wayland-test-clients/xdg-session-management.c
new file mode 100644
index 000000000..9da519755
--- /dev/null
+++ b/src/tests/wayland-test-clients/xdg-session-management.c
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2024 Red Hat Inc.
+ *
+ * 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
+
+#include "wayland-test-client-utils.h"
+
+#include "session-management-v1-client-protocol.h"
+
+typedef struct _TestDisplayState
+{
+ struct xx_session_manager_v1 *session_manager;
+} TestDisplayState;
+
+static void
+handle_registry_global (void *user_data,
+ struct wl_registry *registry,
+ uint32_t id,
+ const char *interface,
+ uint32_t version)
+{
+ WaylandDisplay *display = user_data;
+ TestDisplayState *test_state = display->test_state;
+
+ if (strcmp (interface, "xx_session_manager_v1") == 0)
+ {
+ test_state->session_manager =
+ wl_registry_bind (registry, id, &xx_session_manager_v1_interface, 1);
+ }
+}
+
+static void
+handle_registry_global_remove (void *user_data,
+ struct wl_registry *registry,
+ uint32_t name)
+{
+}
+
+static const struct wl_registry_listener registry_listener = {
+ handle_registry_global,
+ handle_registry_global_remove
+};
+
+typedef struct
+{
+ gboolean received_created;
+} TestCreateState;
+
+typedef struct
+{
+ gboolean configured;
+ gboolean restored;
+} ToplevelSessionState;
+
+static void
+test_create_created (void *user_data,
+ struct xx_session_v1 *xdg_session_v1,
+ const char *id)
+{
+ TestCreateState *state = user_data;
+
+ state->received_created = TRUE;
+}
+
+static void
+test_create_restored (void *user_data,
+ struct xx_session_v1 *xdg_session_v1)
+{
+}
+
+static void
+test_create_replaced (void *user_data,
+ struct xx_session_v1 *xdg_session_v1)
+{
+}
+
+static struct xx_session_v1_listener test_create_session_listener = {
+ test_create_created,
+ test_create_restored,
+ test_create_replaced,
+};
+
+static void
+toplevel_restored (void *user_data,
+ struct xx_toplevel_session_v1 *toplevel_session,
+ struct xdg_toplevel *toplevel)
+{
+ ToplevelSessionState *toplevel_state = user_data;
+
+ toplevel_state->restored = TRUE;
+}
+
+static struct xx_toplevel_session_v1_listener toplevel_session_listener = {
+ toplevel_restored,
+};
+
+static void
+on_toplevel_configured (WaylandSurface *surface,
+ ToplevelSessionState *toplevel_state)
+{
+ toplevel_state->configured = TRUE;
+}
+
+static void
+basic (WaylandDisplay *display)
+{
+ TestDisplayState *test_state = display->test_state;
+ g_autoptr (WaylandSurface) toplevel1 = NULL;
+ g_autoptr (WaylandSurface) toplevel2 = NULL;
+ struct xx_session_v1 *session;
+ struct xx_toplevel_session_v1 *toplevel_session1;
+ struct xx_toplevel_session_v1 *toplevel_session2;
+ TestCreateState state = {};
+ ToplevelSessionState toplevel_state1 = {};
+ ToplevelSessionState toplevel_state2 = {};
+
+ toplevel1 = wayland_surface_new (display, "toplevel1",
+ 100, 100, 0xff50ff50);
+ g_signal_connect (toplevel1, "configure",
+ G_CALLBACK (on_toplevel_configured),
+ &toplevel_state1);
+
+ session =
+ xx_session_manager_v1_get_session (test_state->session_manager,
+ XX_SESSION_MANAGER_V1_REASON_LAUNCH,
+ NULL);
+ xx_session_v1_add_listener (session, &test_create_session_listener, &state);
+
+ while (!state.received_created)
+ wayland_display_dispatch (display);
+
+ /* Test add before committing initial state. */
+ toplevel_session1 = xx_session_v1_add_toplevel (session,
+ toplevel1->xdg_toplevel,
+ "toplevel1");
+ xx_toplevel_session_v1_add_listener (toplevel_session1,
+ &toplevel_session_listener,
+ &toplevel_state1);
+ wl_surface_commit (toplevel1->wl_surface);
+
+ while (!toplevel_state1.configured)
+ wayland_display_dispatch (display);
+ g_assert_false (toplevel_state1.restored);
+
+ /* Test add after committing initial state. */
+ toplevel2 = wayland_surface_new (display, "toplevel2",
+ 100, 100, 0xff0000ff);
+ g_signal_connect (toplevel1, "configure",
+ G_CALLBACK (on_toplevel_configured),
+ &toplevel_state2);
+ wl_surface_commit (toplevel1->wl_surface);
+
+ toplevel_session2 = xx_session_v1_add_toplevel (session,
+ toplevel2->xdg_toplevel,
+ "toplevel2");
+ xx_toplevel_session_v1_add_listener (toplevel_session2,
+ &toplevel_session_listener,
+ &toplevel_state2);
+
+ while (!toplevel_state2.configured)
+ wayland_display_dispatch (display);
+ g_assert_false (toplevel_state2.restored);
+
+ xx_toplevel_session_v1_destroy (toplevel_session1);
+ xx_toplevel_session_v1_destroy (toplevel_session2);
+ xx_session_v1_destroy (session);
+}
+
+static void
+toplevel_inert (WaylandDisplay *display)
+{
+ TestDisplayState *test_state = display->test_state;
+ g_autoptr (WaylandSurface) toplevel = NULL;
+ struct xx_session_v1 *session;
+ struct xx_toplevel_session_v1 *toplevel_session;
+ TestCreateState state = {};
+ ToplevelSessionState toplevel_state = {};
+
+ toplevel = wayland_surface_new (display, "toplevel",
+ 100, 100, 0xff50ff50);
+ g_signal_connect (toplevel, "configure",
+ G_CALLBACK (on_toplevel_configured),
+ &toplevel_state);
+
+ session =
+ xx_session_manager_v1_get_session (test_state->session_manager,
+ XX_SESSION_MANAGER_V1_REASON_LAUNCH,
+ NULL);
+ xx_session_v1_add_listener (session, &test_create_session_listener, &state);
+
+ while (!state.received_created)
+ wayland_display_dispatch (display);
+
+ /* Test add before committing initial state. */
+ toplevel_session = xx_session_v1_add_toplevel (session,
+ toplevel->xdg_toplevel,
+ "toplevel");
+ xx_toplevel_session_v1_add_listener (toplevel_session,
+ &toplevel_session_listener,
+ &toplevel_state);
+ wl_surface_commit (toplevel->wl_surface);
+
+ while (!toplevel_state.configured)
+ wayland_display_dispatch (display);
+ g_assert_false (toplevel_state.restored);
+
+ /* destroy the xdg_toplevel */
+ g_clear_object (&toplevel);
+
+ /* toplevel_session should be inert now and remove should have no effect */
+ xx_toplevel_session_v1_remove (toplevel_session);
+
+ xx_session_v1_destroy (session);
+}
+
+int
+main (int argc,
+ char **argv)
+{
+ g_autoptr (WaylandDisplay) display = NULL;
+ struct wl_registry *registry;
+ TestDisplayState *test_state;
+
+ display = wayland_display_new (WAYLAND_DISPLAY_CAPABILITY_TEST_DRIVER);
+ test_state = g_new0 (TestDisplayState, 1);
+ display->test_state = test_state;
+ display->destroy_test_state = g_free;
+
+ registry = wl_display_get_registry (display->display);
+ wl_registry_add_listener (registry, ®istry_listener, display);
+ wl_display_roundtrip (display->display);
+
+ g_assert_nonnull (test_state->session_manager);
+
+ basic (display);
+
+ toplevel_inert (display);
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/tests/wayland-unit-tests.c b/src/tests/wayland-unit-tests.c
index df87bbce2..9c6feee68 100644
--- a/src/tests/wayland-unit-tests.c
+++ b/src/tests/wayland-unit-tests.c
@@ -572,6 +572,26 @@ toplevel_reuse_surface (void)
meta_wayland_test_client_finish (wayland_test_client);
}
+static void
+toplevel_sessions (void)
+{
+ MetaWaylandTestClient *wayland_test_client;
+
+ wayland_test_client =
+ meta_wayland_test_client_new (test_context, "xdg-session-management");
+ meta_wayland_test_client_finish (wayland_test_client);
+}
+
+static void
+toplevel_sessions_replace (void)
+{
+ MetaWaylandTestClient *wayland_test_client;
+
+ wayland_test_client =
+ meta_wayland_test_client_new (test_context, "xdg-session-management-replace");
+ meta_wayland_test_client_finish (wayland_test_client);
+}
+
static gboolean
mark_later_as_done (gpointer user_data)
{
@@ -1040,6 +1060,10 @@ init_tests (void)
toplevel_apply_limits);
g_test_add_func ("/wayland/toplevel/activation",
toplevel_activation);
+ g_test_add_func ("/wayland/toplevel/sessions",
+ toplevel_sessions);
+ g_test_add_func ("/wayland/toplevel/sessions-replace",
+ toplevel_sessions_replace);
#ifdef MUTTER_PRIVILEGED_TEST
(void)(toplevel_bounds_struts);
(void)(toplevel_bounds_monitors);
@@ -1066,6 +1090,8 @@ main (int argc,
g_autoptr (MetaContext) context = NULL;
MetaTestRunFlags test_run_flags;
+ g_setenv ("MUTTER_DEBUG_SESSION_MANAGEMENT_PROTOCOL", "1", TRUE);
+
#ifdef MUTTER_PRIVILEGED_TEST
context = meta_create_test_context (META_CONTEXT_TEST_TYPE_VKMS,
META_CONTEXT_TEST_FLAG_NO_X11);