diff options
| author | Berke Güzel <wenekar1@gmail.com> | 2026-10-13 23:52:22 +0300 |
|---|---|---|
| committer | Berke Güzel <wenekar1@gmail.com> | 2026-10-13 23:52:22 +0300 |
| commit | 563b23a33726289387193b85d90fb569631ee05f (patch) | |
| tree | ddf438338034b5ab2690b531684906999e56df44 /tests/wayland/test_reconnect.c | |
| parent | 76073fe5fad8799e5850805f2f090af2cb62c741 (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.c | 118 |
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; +} |
