summaryrefslogtreecommitdiff
path: root/src/platform/wayland
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 /src/platform/wayland
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 'src/platform/wayland')
-rw-r--r--src/platform/wayland/backend.c82
-rw-r--r--src/platform/wayland/buffer.c8
-rw-r--r--src/platform/wayland/internal.h15
-rw-r--r--src/platform/wayland/pointer.c2
-rw-r--r--src/platform/wayland/surface.c347
5 files changed, 301 insertions, 153 deletions
diff --git a/src/platform/wayland/backend.c b/src/platform/wayland/backend.c
index 02aae59..b3337cc 100644
--- a/src/platform/wayland/backend.c
+++ b/src/platform/wayland/backend.c
@@ -125,42 +125,36 @@ static bool egl_configure(struct wayland_display *d, const struct yasl_private_g
return eglBindAPI(api) == EGL_TRUE;
}
-static struct yasl_surface *impl_surface_create_gl(struct yasl_private_display *display,
- const struct yasl_private_window_spec *spec, const int *attributes, struct yasl_surface *share)
-{
- struct wayland_display *d = (struct wayland_display *)display;
- struct yasl_private_gl_options o;
- if (!spec || !yasl_private_gl_parse_options(attributes, &o) || !d || !d->egl_initialized)
- return NULL;
- struct yasl_private_window_spec gl_spec = *spec;
- gl_spec.external_gpu = true;
- gl_spec.flags &= ~YASL_PRIVATE_SURFACE_CPU;
- struct wayland_surface *w = wayland_surface_create_toplevel(d, &gl_spec, NULL);
- if (!w) return NULL;
- if (!egl_configure(d, &o, &w->egl_config)) goto fail;
- EGLContext share_context = share && share->display == display
- ? ((struct wayland_surface *)share)->egl_context : EGL_NO_CONTEXT;
- if (share && share_context == EGL_NO_CONTEXT) goto fail;
+bool surface_build_gl(struct wayland_surface *w)
+{
+ struct wayland_display *d = w->conn;
+ const struct yasl_private_gl_options *o = &w->gl_options;
+ if (!egl_configure(d, o, &w->egl_config)) return false;
+ EGLContext share_context = EGL_NO_CONTEXT;
+ if (w->gl_share) {
+ if (!w->gl_share->gl_built && !surface_build_gl(w->gl_share)) return false;
+ share_context = w->gl_share->egl_context;
+ }
w->egl_window = wl_egl_window_create(w->surface, w->width, w->height);
- if (!w->egl_window) goto fail;
+ if (!w->egl_window) return false;
EGLint context_attrs[12];
int n = 0;
- if (o.api == YASL_PRIVATE_GL_OPENGL_ES) {
+ if (o->api == YASL_PRIVATE_GL_OPENGL_ES) {
context_attrs[n++] = EGL_CONTEXT_CLIENT_VERSION;
- context_attrs[n++] = o.major;
+ context_attrs[n++] = o->major;
} else {
context_attrs[n++] = EGL_CONTEXT_MAJOR_VERSION_KHR;
- context_attrs[n++] = o.major;
+ context_attrs[n++] = o->major;
context_attrs[n++] = EGL_CONTEXT_MINOR_VERSION_KHR;
- context_attrs[n++] = o.minor;
- if (o.profile != YASL_PRIVATE_GL_ANY) {
+ context_attrs[n++] = o->minor;
+ if (o->profile != YASL_PRIVATE_GL_ANY) {
context_attrs[n++] = EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR;
- context_attrs[n++] = o.profile == YASL_PRIVATE_GL_CORE
+ context_attrs[n++] = o->profile == YASL_PRIVATE_GL_CORE
? EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR
: EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR;
}
- EGLint flags = (o.debug ? EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR : 0) |
- (o.forward_compat ? EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR : 0);
+ EGLint flags = (o->debug ? EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR : 0) |
+ (o->forward_compat ? EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR : 0);
if (flags) {
context_attrs[n++] = EGL_CONTEXT_FLAGS_KHR;
context_attrs[n++] = flags;
@@ -168,21 +162,42 @@ static struct yasl_surface *impl_surface_create_gl(struct yasl_private_display *
}
context_attrs[n] = EGL_NONE;
w->egl_context = eglCreateContext(d->egl_display, w->egl_config, share_context, context_attrs);
- if (w->egl_context == EGL_NO_CONTEXT) goto fail;
+ if (w->egl_context == EGL_NO_CONTEXT) { w->egl_context = 0; return false; }
EGLint surface_attrs[] = {
#ifdef EGL_GL_COLORSPACE_KHR
- EGL_GL_COLORSPACE_KHR, o.srgb ? EGL_GL_COLORSPACE_SRGB_KHR : EGL_GL_COLORSPACE_LINEAR_KHR,
+ EGL_GL_COLORSPACE_KHR, o->srgb ? EGL_GL_COLORSPACE_SRGB_KHR : EGL_GL_COLORSPACE_LINEAR_KHR,
#endif
EGL_NONE,
};
w->egl_surface = eglCreateWindowSurface(d->egl_display, w->egl_config,
(EGLNativeWindowType)w->egl_window, surface_attrs);
- if (w->egl_surface == EGL_NO_SURFACE) { w->egl_surface = 0; goto fail; }
+ if (w->egl_surface == EGL_NO_SURFACE) { w->egl_surface = 0; return false; }
+ w->gl_built = true;
+ return true;
+}
+
+static struct yasl_surface *impl_surface_create_gl(struct yasl_private_display *display,
+ const struct yasl_private_window_spec *spec, const int *attributes, struct yasl_surface *share)
+{
+ struct wayland_display *d = (struct wayland_display *)display;
+ struct yasl_private_gl_options o;
+ if (!spec || !yasl_private_gl_parse_options(attributes, &o) || !d || !d->egl_initialized)
+ return NULL;
+ if (share && (share->display != display || !((struct wayland_surface *)share)->egl_context))
+ return NULL;
+ struct yasl_private_window_spec gl_spec = *spec;
+ gl_spec.external_gpu = true;
+ gl_spec.flags &= ~YASL_PRIVATE_SURFACE_CPU;
+ struct wayland_surface *w = wayland_surface_create_toplevel(d, &gl_spec, NULL);
+ if (!w) return NULL;
+ w->gl_options = o;
+ w->gl_share = (struct wayland_surface *)share;
+ if (!surface_build_gl(w)) {
+ wayland_surface_destroy(w);
+ return NULL;
+ }
w->base.presenter = YASL_PRIVATE_PRESENTER_GL;
return &w->base;
-fail:
- wayland_surface_destroy(w);
- return NULL;
}
static bool impl_gl_make_current(struct yasl_private_display *display, struct yasl_surface *surface)
@@ -250,7 +265,7 @@ static struct input_queue *impl_surface_input_queue(struct yasl_surface *s)
static bool impl_surface_set_size(struct yasl_surface *s, struct yasl_private_vector2i size)
{
struct wayland_surface *w = (struct wayland_surface *)s;
- if (!w || w->closed || w->destroyed || !w->toplevel ||
+ if (!w || w->closed || w->destroyed || (!w->toplevel && !w->conn->lost) ||
size.x <= 0 || size.y <= 0) {
errno = EINVAL;
return false;
@@ -258,7 +273,8 @@ static bool impl_surface_set_size(struct yasl_surface *s, struct yasl_private_ve
/* Floating windows choose their own size; constrained states keep it for later. */
w->floating_width = size.x;
w->floating_height = size.y;
- if (w->base.state & (YASL_PRIVATE_STATE_MAXIMIZED | YASL_PRIVATE_STATE_FULLSCREEN | YASL_PRIVATE_STATE_TILED))
+ if (w->conn->lost ||
+ (w->base.state & (YASL_PRIVATE_STATE_MAXIMIZED | YASL_PRIVATE_STATE_FULLSCREEN | YASL_PRIVATE_STATE_TILED)))
return true;
w->req_width = w->req_height = 0;
w->size_pending = true;
diff --git a/src/platform/wayland/buffer.c b/src/platform/wayland/buffer.c
index bbc055d..029b684 100644
--- a/src/platform/wayland/buffer.c
+++ b/src/platform/wayland/buffer.c
@@ -211,6 +211,7 @@ static const struct wp_fractional_scale_v1_listener frac_scale_listener = {
void surface_follow_outputs(struct wayland_surface *w)
{
+ if (!w->surface) return; /* Not rebuilt yet after a reconnect. */
uint32_t refresh = 0;
int32_t max_scale = 0;
bool need_scale = !w->frac_scale &&
@@ -563,11 +564,12 @@ int surface_init_scaling(struct wayland_surface *w)
void wayland_surface_set_title(struct wayland_surface *w, const char *title)
{
- if (!w || !w->toplevel || !title) return;
+ if (!w || !title) return;
char *copy = yasl_private_utf8_sanitize_dup(title);
if (!copy) return;
- xdg_toplevel_set_title(w->toplevel, copy);
- free(copy);
+ free(w->title);
+ w->title = copy;
+ if (w->toplevel) xdg_toplevel_set_title(w->toplevel, copy);
}
struct yasl_private_timing wayland_surface_timing(const struct wayland_surface *w)
diff --git a/src/platform/wayland/internal.h b/src/platform/wayland/internal.h
index a479e2b..92108c1 100644
--- a/src/platform/wayland/internal.h
+++ b/src/platform/wayland/internal.h
@@ -251,6 +251,10 @@ struct wayland_display {
struct conn_drag drag;
struct wayland_surface *surfaces;
struct wayland_surface *dead;
+ /* While lost: retries a connection; stale marks objects of the dead one. */
+ yasl_private_timer_id reconnect_timer;
+ uint64_t reconnect_delay_ns;
+ bool stale;
/* 4-byte integers */
int cursor_size;
@@ -287,7 +291,12 @@ struct wayland_surface {
EGLSurface egl_surface;
EGLContext egl_context;
EGLConfig egl_config;
+ struct yasl_private_gl_options gl_options;
+ struct wayland_surface *gl_share;
+ bool gl_built;
#endif
+ char *title;
+ bool tearing;
struct xdg_surface *xdg_surface;
struct xdg_toplevel *toplevel;
struct xdg_popup *popup;
@@ -393,6 +402,12 @@ void surface_draw_frame(struct wayland_surface *w);
void wayland_surface_arm_frame(struct wayland_surface *w);
void surface_fail(struct wayland_surface *w);
void display_leave(struct wayland_display *c);
+/* Native objects of a toplevel: built at creation and again after a reconnect. */
+int surface_build_toplevel(struct wayland_surface *w, bool fullscreen);
+void surface_release_native(struct wayland_surface *w);
+#if YASL_HAS_EGL
+bool surface_build_gl(struct wayland_surface *w);
+#endif
static inline struct wayland_surface *surface_root(struct wayland_surface *w)
{
diff --git a/src/platform/wayland/pointer.c b/src/platform/wayland/pointer.c
index 5f486ae..0cd8c5b 100644
--- a/src/platform/wayland/pointer.c
+++ b/src/platform/wayland/pointer.c
@@ -542,7 +542,7 @@ static const struct zwp_locked_pointer_v1_listener locked_pointer_listener = {
static void surface_lock_pointer(struct wayland_surface *w, struct seat *s)
{
struct wayland_display *c = w->conn;
- if (!w->relative || w->pointer_lock || w->destroyed || !s->pointer)
+ if (!w->relative || w->pointer_lock || w->destroyed || !w->surface || !s->pointer)
return;
w->pointer_lock = zwp_pointer_constraints_v1_lock_pointer(c->pointer_constraints,
w->surface, s->pointer, NULL, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
diff --git a/src/platform/wayland/surface.c b/src/platform/wayland/surface.c
index 31096f4..e04a594 100644
--- a/src/platform/wayland/surface.c
+++ b/src/platform/wayland/surface.c
@@ -3,6 +3,7 @@
#include <errno.h>
static void display_free(struct wayland_display *c);
+static uint64_t reconnect_tick(void *ud, yasl_private_timer_id id, uint64_t interval_ns);
static void display_cancel_read(struct wayland_display *c)
{
@@ -12,19 +13,16 @@ static void display_cancel_read(struct wayland_display *c)
c->reading = false;
}
+/* A compositor going away is expected; a protocol error is a YASL bug worth reporting. */
static void report_display_error(struct wayland_display *c)
{
- int err = wl_display_get_error(c->wl);
- if (!err)
+ if (wl_display_get_error(c->wl) != EPROTO)
return;
uint32_t object_id = 0;
const struct wl_interface *iface = NULL;
uint32_t protocol_err = wl_display_get_protocol_error(c->wl, &iface, &object_id);
- if (protocol_err)
- fprintf(stderr, "[yasl] protocol error %u on %s (object %u)\n",
- protocol_err, iface ? iface->name : "?", object_id);
- else
- fprintf(stderr, "[yasl] display error %d\n", err);
+ fprintf(stderr, "[yasl] protocol error %u on %s (object %u)\n",
+ protocol_err, iface ? iface->name : "?", object_id);
}
void display_leave(struct wayland_display *c)
@@ -39,20 +37,32 @@ void display_leave(struct wayland_display *c)
}
}
+/* Windows outlive the connection. Its objects stay until the next dispatch, after
+ * the application has seen DISPLAY_LOST and released GPU objects built on them. */
static void display_lose(struct wayland_display *c)
{
if (c->lost)
return;
c->lost = true;
+ c->stale = true;
+ c->base.offline = true;
report_display_error(c);
display_cancel_read(c);
+ if (c->loop && c->watch > 0)
+ yasl_private_loop_del_fd(c->loop, c->watch);
+ c->watch = 0;
c->depth++;
+ conn_drag_end(c);
for (struct wayland_surface *w = c->surfaces, *next; w; w = next) {
next = w->conn_next;
- surface_fail(w);
+ if (w->parent)
+ surface_fail(w);
}
display_leave(c);
yasl_private_display_notify_event(&c->base, YASL_PRIVATE_EVENT_DISPLAY_LOST);
+ c->reconnect_delay_ns = YASL_PRIVATE_MS_TO_NS(100);
+ if (c->loop && !c->reconnect_timer)
+ c->reconnect_timer = yasl_private_loop_add_timer(c->loop, c->reconnect_delay_ns, reconnect_tick, c);
}
void surface_fail(struct wayland_surface *w)
@@ -131,21 +141,28 @@ static inline bool display_has_immediate_work(const struct wayland_display *c)
int wayland_display_dispatch_timeout(struct wayland_display *c, int timeout_ms)
{
- if (!c || c->lost)
+ if (!c)
return -1;
c->depth++;
+ if (c->lost) {
+ /* Reconnect attempts run as a loop timer. */
+ int n = c->loop ? yasl_private_loop_step(c->loop, timeout_ms) : 0;
+ display_leave(c);
+ return n;
+ }
+ /* Losing the connection is reported as DISPLAY_LOST, not as a failed wait. */
if (wl_display_dispatch_pending(c->wl) < 0) {
display_lose(c);
display_leave(c);
- return -1;
+ return 0;
}
display_arm(c);
if (c->lost) {
display_leave(c);
- return -1;
+ return 0;
}
if (timeout_ms != 0 && display_has_immediate_work(c)) {
@@ -161,10 +178,10 @@ int wayland_display_dispatch_timeout(struct wayland_display *c, int timeout_ms)
display_cancel_read(c);
}
- if (wl_display_dispatch_pending(c->wl) < 0) {
+ if (c->wl && wl_display_dispatch_pending(c->wl) < 0) {
display_lose(c);
display_leave(c);
- return -1;
+ return 0;
}
display_leave(c);
@@ -187,40 +204,126 @@ void wayland_display_flush(struct wayland_display *c)
display_leave(c);
}
-static void display_free(struct wayland_display *c)
+#define RELEASE(field, destroy) do { if (c->field) { destroy(c->field); c->field = NULL; } } while (0)
+
+static void connection_release(struct wayland_display *c)
{
+ for (struct wayland_surface *w = c->surfaces; w; w = w->conn_next)
+ surface_release_native(w);
conn_drag_end(c);
if (c->loop && c->watch > 0) yasl_private_loop_del_fd(c->loop, c->watch);
+ c->watch = 0;
if (c->wl) display_cancel_read(c);
while (c->seats)
seat_destroy(c->seats);
- if (c->toplevel_drag_mgr) xdg_toplevel_drag_manager_v1_destroy(c->toplevel_drag_mgr);
- if (c->primary_selection_mgr) zwp_primary_selection_device_manager_v1_destroy(c->primary_selection_mgr);
- if (c->data_manager) wl_data_device_manager_destroy(c->data_manager);
- if (c->cursor_shape_mgr) wp_cursor_shape_manager_v1_destroy(c->cursor_shape_mgr);
- if (c->relative_pointer_mgr) zwp_relative_pointer_manager_v1_destroy(c->relative_pointer_mgr);
- if (c->pointer_constraints) zwp_pointer_constraints_v1_destroy(c->pointer_constraints);
- if (c->system_bell) xdg_system_bell_v1_destroy(c->system_bell);
- if (c->cursor_theme) wl_cursor_theme_destroy(c->cursor_theme);
- if (c->tablet_mgr) zwp_tablet_manager_v2_destroy(c->tablet_mgr);
- if (c->text_input_mgr) zwp_text_input_manager_v3_destroy(c->text_input_mgr);
- if (c->tearing_mgr) wp_tearing_control_manager_v1_destroy(c->tearing_mgr);
- if (c->fifo_mgr) wp_fifo_manager_v1_destroy(c->fifo_mgr);
- if (c->commit_timing_mgr) wp_commit_timing_manager_v1_destroy(c->commit_timing_mgr);
- if (c->presentation) wp_presentation_destroy(c->presentation);
- if (c->xkb_ctx) xkb_context_unref(c->xkb_ctx);
- if (c->scale_mgr) wp_fractional_scale_manager_v1_destroy(c->scale_mgr);
- if (c->viewporter) wp_viewporter_destroy(c->viewporter);
- if (c->decor_mgr) zxdg_decoration_manager_v1_destroy(c->decor_mgr);
+ RELEASE(toplevel_drag_mgr, xdg_toplevel_drag_manager_v1_destroy);
+ RELEASE(primary_selection_mgr, zwp_primary_selection_device_manager_v1_destroy);
+ RELEASE(data_manager, wl_data_device_manager_destroy);
+ RELEASE(cursor_shape_mgr, wp_cursor_shape_manager_v1_destroy);
+ RELEASE(relative_pointer_mgr, zwp_relative_pointer_manager_v1_destroy);
+ RELEASE(pointer_constraints, zwp_pointer_constraints_v1_destroy);
+ RELEASE(system_bell, xdg_system_bell_v1_destroy);
+ RELEASE(cursor_theme, wl_cursor_theme_destroy);
+ RELEASE(tablet_mgr, zwp_tablet_manager_v2_destroy);
+ RELEASE(text_input_mgr, zwp_text_input_manager_v3_destroy);
+ RELEASE(tearing_mgr, wp_tearing_control_manager_v1_destroy);
+ RELEASE(fifo_mgr, wp_fifo_manager_v1_destroy);
+ RELEASE(commit_timing_mgr, wp_commit_timing_manager_v1_destroy);
+ RELEASE(presentation, wp_presentation_destroy);
+ RELEASE(scale_mgr, wp_fractional_scale_manager_v1_destroy);
+ RELEASE(viewporter, wp_viewporter_destroy);
+ RELEASE(decor_mgr, zxdg_decoration_manager_v1_destroy);
conn_destroy_outputs(c);
- if (c->wm_base) xdg_wm_base_destroy(c->wm_base);
- if (c->shm) wl_shm_destroy(c->shm);
- if (c->compositor) wl_compositor_destroy(c->compositor);
- if (c->registry) wl_registry_destroy(c->registry);
+ RELEASE(wm_base, xdg_wm_base_destroy);
+ RELEASE(shm, wl_shm_destroy);
+ RELEASE(compositor, wl_compositor_destroy);
+ RELEASE(registry, wl_registry_destroy);
#if YASL_HAS_EGL
if (c->egl_initialized) eglTerminate(c->egl_display);
+ c->egl_initialized = false;
+ c->egl_display = EGL_NO_DISPLAY;
+ c->egl_api = 0;
#endif
- if (c->wl) wl_display_disconnect(c->wl);
+ RELEASE(wl, wl_display_disconnect);
+ c->base.native_handle = NULL;
+ c->cursor_theme_scale = 0;
+ c->pres_clock_known = false;
+ c->active_seat = NULL;
+}
+
+static bool connection_open(struct wayland_display *c)
+{
+ c->wl = wl_display_connect(NULL);
+ if (!c->wl) return false;
+ c->base.native_handle = c->wl;
+#if YASL_HAS_EGL
+ c->egl_display = eglGetDisplay((EGLNativeDisplayType)c->wl);
+ if (c->egl_display != EGL_NO_DISPLAY) {
+ EGLint major = 0, minor = 0;
+ c->egl_initialized = eglInitialize(c->egl_display, &major, &minor) == EGL_TRUE;
+ }
+#endif
+ c->registry = wl_display_get_registry(c->wl);
+ if (!c->registry) return false;
+ wl_registry_add_listener(c->registry, &registry_listener, c);
+ if (wl_display_roundtrip(c->wl) < 0 || !c->compositor || !c->shm || !c->wm_base)
+ return false;
+ surface_init_cursor(c);
+ if (c->loop) {
+ c->watch = yasl_private_loop_add_fd(c->loop, wl_display_get_fd(c->wl), POLLIN, display_ready, c);
+ if (c->watch <= 0) { c->watch = 0; return false; }
+ }
+ return display_input_init(c) == 0;
+}
+
+/* Recreates every window on a new connection; one that cannot be rebuilt is closed. */
+static void display_rebuild(struct wayland_display *c)
+{
+ for (struct wayland_surface *w = c->surfaces; w && !c->lost; w = w->conn_next) {
+ if (w->parent || w->closed || w->destroyed) continue;
+ w->req_width = w->req_height = 0;
+ if (surface_build_toplevel(w, w->base.state & YASL_PRIVATE_STATE_FULLSCREEN) < 0 && !c->lost)
+ surface_fail(w);
+ }
+#if YASL_HAS_EGL
+ for (struct wayland_surface *w = c->surfaces; w && !c->lost; w = w->conn_next)
+ if (w->base.presenter == YASL_PRIVATE_PRESENTER_GL && !w->closed && !w->gl_built && !surface_build_gl(w))
+ surface_fail(w);
+#endif
+ for (struct wayland_surface *w = c->surfaces; w && !c->lost; w = w->conn_next)
+ if (!w->closed) yasl_private_surface_notify_refresh(&w->base);
+}
+
+static uint64_t reconnect_tick(void *ud, yasl_private_timer_id id, uint64_t interval_ns)
+{
+ (void)id; (void)interval_ns;
+ struct wayland_display *c = ud;
+ c->depth++;
+ if (c->stale) {
+ connection_release(c);
+ c->stale = false;
+ }
+ if (!connection_open(c)) {
+ connection_release(c);
+ display_leave(c);
+ uint64_t limit = YASL_PRIVATE_MS_TO_NS(2000);
+ c->reconnect_delay_ns = c->reconnect_delay_ns * 2 < limit ? c->reconnect_delay_ns * 2 : limit;
+ return c->reconnect_delay_ns;
+ }
+ c->reconnect_timer = 0;
+ c->lost = false;
+ c->base.offline = false;
+ yasl_private_display_notify_event(&c->base, YASL_PRIVATE_EVENT_DISPLAY_RESTORED);
+ display_rebuild(c);
+ display_leave(c);
+ return 0;
+}
+
+static void display_free(struct wayland_display *c)
+{
+ connection_release(c);
+ if (c->loop && c->reconnect_timer) yasl_private_loop_del_timer(c->loop, c->reconnect_timer);
+ if (c->xkb_ctx) xkb_context_unref(c->xkb_ctx);
if (c->owns_loop && c->loop) {
yasl_private_loop_destroy(c->loop);
c->loop = NULL;
@@ -251,29 +354,7 @@ struct wayland_display *wayland_display_open(struct yasl_private_loop *loop, con
c->base.app_id = c->app_id;
}
c->xkb_ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
- c->wl = c->xkb_ctx ? wl_display_connect(NULL) : NULL;
- if (!c->wl) goto err;
- c->base.native_handle = c->wl;
-#if YASL_HAS_EGL
- c->egl_display = eglGetDisplay((EGLNativeDisplayType)c->wl);
- if (c->egl_display != EGL_NO_DISPLAY) {
- EGLint major = 0, minor = 0;
- c->egl_initialized = eglInitialize(c->egl_display, &major, &minor) == EGL_TRUE;
- }
-#endif
-
- c->registry = wl_display_get_registry(c->wl);
- if (!c->registry) goto err;
- wl_registry_add_listener(c->registry, &registry_listener, c);
- if (wl_display_roundtrip(c->wl) < 0 || !c->compositor || !c->shm || !c->wm_base)
- goto err;
-
- surface_init_cursor(c);
- if (c->loop) {
- c->watch = yasl_private_loop_add_fd(c->loop, wl_display_get_fd(c->wl), POLLIN, display_ready, c);
- if (c->watch <= 0) { c->watch = 0; goto err; }
- }
- if (display_input_init(c) < 0)
+ if (!c->xkb_ctx || !connection_open(c))
goto err;
return c;
@@ -343,11 +424,91 @@ static bool spec_valid(const struct yasl_private_window_spec *spec)
: buffer_size(spec->size.x, spec->size.y, &bytes);
}
+int surface_build_toplevel(struct wayland_surface *w, bool fullscreen)
+{
+ struct wayland_display *c = w->conn;
+ w->surface = wl_compositor_create_surface(c->compositor);
+ if (!w->surface) return -1;
+ w->base.native_handle = w->surface;
+ wl_surface_set_user_data(w->surface, w);
+ if (surface_init_scaling(w) < 0) return -1;
+ if (surface_init_shell(w, w->title) < 0) return -1;
+ if (w->base.creation_flags & YASL_PRIVATE_SURFACE_FIXED_SIZE) {
+ xdg_toplevel_set_min_size(w->toplevel, w->logical_width, w->logical_height);
+ xdg_toplevel_set_max_size(w->toplevel, w->logical_width, w->logical_height);
+ }
+ if (fullscreen)
+ xdg_toplevel_set_fullscreen(w->toplevel, NULL);
+ else if (w->base.state & YASL_PRIVATE_STATE_MAXIMIZED)
+ xdg_toplevel_set_maximized(w->toplevel);
+ if (w->tearing && c->tearing_mgr) {
+ w->tearing_control = wp_tearing_control_manager_v1_get_tearing_control(
+ c->tearing_mgr, w->surface);
+ if (w->tearing_control)
+ wp_tearing_control_v1_set_presentation_hint(w->tearing_control,
+ WP_TEARING_CONTROL_V1_PRESENTATION_HINT_ASYNC);
+ }
+ w->size_pending = true;
+ wl_surface_commit(w->surface);
+ if (wl_display_roundtrip(c->wl) < 0) {
+ display_lose(c);
+ return -1;
+ }
+ if (apply_pending_geometry(w) < 0 || w->closed) return -1;
+ return 0;
+}
+
+void surface_release_native(struct wayland_surface *w)
+{
+ surface_unlock_pointer(w);
+ w->ime_seat = NULL;
+#define DROP(field, destroy) do { if (w->field) { destroy(w->field); w->field = NULL; } } while (0)
+ DROP(tearing_control, wp_tearing_control_v1_destroy);
+#if YASL_HAS_EGL
+ struct wayland_display *c = w->conn;
+ if (w->egl_context) {
+ if (eglGetCurrentContext() == w->egl_context)
+ eglMakeCurrent(c->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+ eglDestroyContext(c->egl_display, w->egl_context);
+ w->egl_context = 0;
+ }
+ if (w->egl_surface) eglDestroySurface(c->egl_display, w->egl_surface);
+ w->egl_surface = 0;
+ DROP(egl_window, wl_egl_window_destroy);
+ w->gl_built = false;
+#endif
+ DROP(fifo, wp_fifo_v1_destroy);
+ DROP(commit_timer, wp_commit_timer_v1_destroy);
+ surface_feedback_destroy(w);
+ DROP(frame_cb, wl_callback_destroy);
+ for (int i = 0; i < NBUF; i++) buffer_destroy(&w->buffers[i]);
+ DROP(frac_scale, wp_fractional_scale_v1_destroy);
+ DROP(viewport, wp_viewport_destroy);
+ DROP(toplevel_decor, zxdg_toplevel_decoration_v1_destroy);
+ DROP(toplevel, xdg_toplevel_destroy);
+ DROP(popup, xdg_popup_destroy);
+ DROP(xdg_surface, xdg_surface_destroy);
+ if (w->surface) {
+ wl_surface_set_user_data(w->surface, NULL);
+ wl_surface_destroy(w->surface);
+ w->surface = NULL;
+ }
+#undef DROP
+ w->base.native_handle = NULL;
+ /* A Vulkan surface built on the old wl_surface is gone; allow a new one. */
+ if (w->base.presenter == YASL_PRIVATE_PRESENTER_VULKAN)
+ w->base.presenter = YASL_PRIVATE_PRESENTER_NONE;
+ w->configured = w->has_pending_serial = w->has_shown = false;
+ w->frame_active = w->want_draw = false;
+ w->outputs = 0;
+}
+
struct wayland_surface *wayland_surface_create_toplevel(struct wayland_display *c,
const struct yasl_private_window_spec *spec, void *graphics_device)
{
(void)graphics_device;
- if (!c || c->lost || !spec_valid(spec))
+ if (c && c->lost) { errno = ENOTCONN; return NULL; }
+ if (!c || !spec_valid(spec))
return NULL;
int width = spec->size.x, height = spec->size.y;
display_cancel_read(c);
@@ -360,6 +521,7 @@ struct wayland_surface *wayland_surface_create_toplevel(struct wayland_display *
w->base.presenter = (spec->external_gpu && !(spec->flags & YASL_PRIVATE_SURFACE_CPU))
? YASL_PRIVATE_PRESENTER_NONE : YASL_PRIVATE_PRESENTER_CPU;
wayland_surface_set_callbacks(w, NULL, NULL);
+ wayland_surface_set_title(w, spec->title ? spec->title : "yasl_private_app");
w->cursor = YASL_PRIVATE_CURSOR_TEXT;
w->req_width = width;
w->req_height = height;
@@ -368,40 +530,13 @@ struct wayland_surface *wayland_surface_create_toplevel(struct wayland_display *
w->scale_120 = YASL_PRIVATE_SCALE_UNIT;
w->pending_scale_120 = YASL_PRIVATE_SCALE_UNIT;
w->scale_factor = 1.0;
- w->size_pending = true;
+ w->tearing = spec->tearing;
+ w->opaque = spec->fullscreen;
w->clock.refresh_ns = c->outputs && c->outputs->refresh_ns
? c->outputs->refresh_ns : YASL_PRIVATE_REFRESH_DEFAULT_NS;
-
- w->surface = wl_compositor_create_surface(c->compositor);
- if (!w->surface) goto err;
- w->base.native_handle = w->surface;
w->base.size = (struct yasl_private_vector2i){width, height};
w->base.scale_120 = YASL_PRIVATE_SCALE_UNIT;
- wl_surface_set_user_data(w->surface, w);
- if (surface_init_scaling(w) < 0) goto err;
- if (surface_init_shell(w, spec->title) < 0) goto err;
- if (spec->flags & YASL_PRIVATE_SURFACE_FIXED_SIZE) {
- xdg_toplevel_set_min_size(w->toplevel, width, height);
- xdg_toplevel_set_max_size(w->toplevel, width, height);
- }
- w->opaque = spec->fullscreen;
- if (spec->fullscreen)
- xdg_toplevel_set_fullscreen(w->toplevel, NULL);
- if (spec->tearing && c->tearing_mgr) {
- w->tearing_control = wp_tearing_control_manager_v1_get_tearing_control(
- c->tearing_mgr, w->surface);
- if (w->tearing_control)
- wp_tearing_control_v1_set_presentation_hint(w->tearing_control,
- WP_TEARING_CONTROL_V1_PRESENTATION_HINT_ASYNC);
- }
-
- wl_surface_commit(w->surface);
- if (wl_display_roundtrip(c->wl) < 0) {
- display_lose(c);
- goto err;
- }
- if (apply_pending_geometry(w) < 0) goto err;
- if (w->closed) goto err;
+ if (surface_build_toplevel(w, spec->fullscreen) < 0) goto err;
display_leave(c);
return w;
@@ -426,37 +561,17 @@ void wayland_surface_destroy(struct wayland_surface *w)
if (!w || w->destroyed) return;
for (struct wayland_surface *o = w->conn ? w->conn->surfaces : NULL; o; o = o->conn_next)
assert(o->parent != w && "destroy a popup before its parent");
- surface_unlock_pointer(w);
+ surface_release_native(w);
input_queue_clear(&w->input);
free(w->ime_preedit);
free(w->ime_commit);
free(w->ime_shown);
- if (w->tearing_control) wp_tearing_control_v1_destroy(w->tearing_control);
+ free(w->title);
+ struct wayland_display *c = w->conn;
#if YASL_HAS_EGL
- if (w->egl_context) {
- if (eglGetCurrentContext() == w->egl_context)
- eglMakeCurrent(w->conn->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
- eglDestroyContext(w->conn->egl_display, w->egl_context);
- }
- if (w->egl_surface) eglDestroySurface(w->conn->egl_display, w->egl_surface);
- if (w->egl_window) wl_egl_window_destroy(w->egl_window);
+ for (struct wayland_surface *o = c ? c->surfaces : NULL; o; o = o->conn_next)
+ if (o->gl_share == w) o->gl_share = NULL;
#endif
- if (w->fifo) wp_fifo_v1_destroy(w->fifo);
- if (w->commit_timer) wp_commit_timer_v1_destroy(w->commit_timer);
- surface_feedback_destroy(w);
- if (w->frame_cb) wl_callback_destroy(w->frame_cb);
- for (int i = 0; i < NBUF; i++) buffer_destroy(&w->buffers[i]);
- if (w->frac_scale) wp_fractional_scale_v1_destroy(w->frac_scale);
- if (w->viewport) wp_viewport_destroy(w->viewport);
- if (w->toplevel_decor) zxdg_toplevel_decoration_v1_destroy(w->toplevel_decor);
- if (w->toplevel) xdg_toplevel_destroy(w->toplevel);
- if (w->popup) xdg_popup_destroy(w->popup);
- if (w->xdg_surface) xdg_surface_destroy(w->xdg_surface);
- if (w->surface) {
- wl_surface_set_user_data(w->surface, NULL);
- wl_surface_destroy(w->surface);
- }
- struct wayland_display *c = w->conn;
conn_unlink(w);
static const struct yasl_private_window_cbs no_callbacks = {0};
*w = (struct wayland_surface){