summaryrefslogtreecommitdiff
path: root/tests/wayland/test_reconnect.c
diff options
context:
space:
mode:
authorBerke Güzel <wenekar1@gmail.com>2026-10-13 23:52:22 +0300
committerBerke Güzel <wenekar1@gmail.com>2026-10-13 23:52:22 +0300
commit563b23a33726289387193b85d90fb569631ee05f (patch)
treeddf438338034b5ab2690b531684906999e56df44 /tests/wayland/test_reconnect.c
parent76073fe5fad8799e5850805f2f090af2cb62c741 (diff)
Reconnect to a restarted Wayland compositor
Windows now outlive the compositor. On connection loss YASL sends DISPLAY_LOST instead of closing every window, retries the connection on a loop timer, rebuilds each toplevel with its title, size, state and GL context, and sends DISPLAY_RESTORED. CPU windows keep their pixels; GPU renderers drop Vulkan surfaces and GL objects on loss and rebuild them on restore. - Free the dead connection's objects on the next wait, after the application has released GPU objects built on them. - Report a lost connection as an event rather than a failed wait. - Refuse window and Vulkan surface creation while offline with the new YASL_ERROR_DISCONNECTED. - Close popups, cancel drags and fail pending clipboard requests on loss. - Print only protocol errors, not ordinary disconnects. - Examples survive a restart; game.c rebinds its GL context. - Add an end-to-end test that kills Weston under CPU, GL and Vulkan windows and requires them to recover. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'tests/wayland/test_reconnect.c')
-rw-r--r--tests/wayland/test_reconnect.c118
1 files changed, 118 insertions, 0 deletions
diff --git a/tests/wayland/test_reconnect.c b/tests/wayland/test_reconnect.c
new file mode 100644
index 0000000..0c42043
--- /dev/null
+++ b/tests/wayland/test_reconnect.c
@@ -0,0 +1,118 @@
+/* Compositor restart: run by helpers/run_reconnect.py, which kills the compositor
+ * after "ready" and starts a new one after "lost". Windows must survive, keep
+ * their pixels, and accept new GL and Vulkan objects after DISPLAY_RESTORED. */
+#include <yasl/yasl.h>
+#include <yasl/cpu.h>
+#include <yasl/gl.h>
+#if TEST_VULKAN
+#include <yasl/vulkan.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define CHECK(cond, what) do { if (!(cond)) { const char *why; yasl_get_error(&why); \
+ fprintf(stderr, "reconnect: %s failed at line %d (%s)\n", what, __LINE__, why); exit(1); } } while (0)
+
+typedef void (*clear_color_fn)(float, float, float, float);
+typedef void (*clear_fn)(unsigned);
+
+static yasl_surface *cpu, *gl;
+
+/* Waits up to timeout_ms for an event of the given type, failing on any CLOSE. */
+static yasl_event expect(enum yasl_event_type type, yasl_surface *surface, int timeout_ms)
+{
+ yasl_event e;
+ for (int waited = 0; waited < timeout_ms; waited += 50) {
+ CHECK(yasl_wait_event(&e, 50) >= 0, "wait");
+ CHECK(e.type != YASL_EVENT_CLOSE, "keeping windows open");
+ if (e.type == type && (!surface || e.surface == surface)) return e;
+ }
+ fprintf(stderr, "reconnect: timed out waiting for event %d\n", type);
+ exit(1);
+}
+
+static void draw_gl(float red)
+{
+ CHECK(yasl_gl_make_current(gl), "binding GL");
+ clear_color_fn clear_color = (clear_color_fn)yasl_gl_get_proc_address("glClearColor");
+ clear_fn clear = (clear_fn)yasl_gl_get_proc_address("glClear");
+ CHECK(clear_color && clear, "loading GL procedures");
+ clear_color(red, 0.2f, 0.3f, 1);
+ clear(0x00004000u);
+ CHECK(yasl_gl_swap_buffers(gl), "swapping GL buffers");
+}
+
+#if TEST_VULKAN
+static VkInstance instance;
+static yasl_surface *vk;
+
+static VkSurfaceKHR vulkan_surface(void)
+{
+ VkSurfaceKHR out = VK_NULL_HANDLE;
+ CHECK(yasl_vulkan_create_surface(vk, instance, NULL, &out) == VK_SUCCESS, "creating Vulkan surface");
+ return out;
+}
+#endif
+
+int main(void)
+{
+ CHECK(yasl_set_backend(YASL_BACKEND_WAYLAND), "selecting Wayland");
+ cpu = yasl_surface_create("reconnect CPU", 160, 120, YASL_SURFACE_CPU);
+ CHECK(cpu, "creating CPU window");
+ gl = yasl_gl_surface_create("reconnect GL", 160, 120, 0, NULL, NULL);
+ CHECK(gl, "creating GL window");
+
+ expect(YASL_EVENT_REFRESH, cpu, 5000);
+ int w, h;
+ uint32_t *pixels = yasl_cpu_pixels(cpu, &w, &h);
+ CHECK(pixels, "getting pixels");
+ for (size_t i = 0; i < (size_t)w * h; i++) pixels[i] = 0xff336699u;
+ CHECK(yasl_cpu_present(cpu), "presenting");
+ draw_gl(0.8f);
+
+#if TEST_VULKAN
+ CHECK(yasl_vulkan_init(vkGetInstanceProcAddr), "loading Vulkan");
+ vk = yasl_surface_create("reconnect Vulkan", 160, 120, 0);
+ CHECK(vk, "creating Vulkan window");
+ uint32_t count = 0;
+ const char *const *extensions = yasl_vulkan_instance_extensions(vk, &count);
+ VkInstanceCreateInfo info = {.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
+ .enabledExtensionCount = count, .ppEnabledExtensionNames = extensions};
+ CHECK(vkCreateInstance(&info, NULL, &instance) == VK_SUCCESS, "creating Vulkan instance");
+ VkSurfaceKHR surface = vulkan_surface();
+#endif
+
+ puts("ready");
+ fflush(stdout);
+
+ expect(YASL_EVENT_DISPLAY_LOST, NULL, 10000);
+#if TEST_VULKAN
+ vkDestroySurfaceKHR(instance, surface, NULL);
+#endif
+ CHECK(!yasl_surface_create("offline", 64, 64, YASL_SURFACE_CPU), "refusing windows while offline");
+ CHECK(yasl_get_error(NULL) == YASL_ERROR_DISCONNECTED, "reporting the disconnect");
+ puts("lost");
+ fflush(stdout);
+
+ expect(YASL_EVENT_DISPLAY_RESTORED, NULL, 15000);
+ expect(YASL_EVENT_REFRESH, cpu, 5000);
+ int rw, rh;
+ pixels = yasl_cpu_pixels(cpu, &rw, &rh);
+ CHECK(pixels && rw == w && rh == h, "keeping the window size");
+ CHECK(pixels[0] == 0xff336699u && pixels[(size_t)w * h - 1] == 0xff336699u, "keeping the pixels");
+ CHECK(yasl_cpu_present(cpu), "presenting after reconnect");
+ draw_gl(0.1f);
+#if TEST_VULKAN
+ surface = vulkan_surface();
+ vkDestroySurfaceKHR(instance, surface, NULL);
+ vkDestroyInstance(instance, NULL);
+#endif
+
+ /* A second window proves the new connection accepts creation. */
+ CHECK(yasl_surface_create("after reconnect", 64, 64, YASL_SURFACE_CPU), "creating a window after reconnect");
+ yasl_gl_make_current(NULL);
+ yasl_shutdown();
+ puts("restored");
+ return 0;
+}