From 7e8bf8d2a81f37303e0c9ae430f1acbff106da22 Mon Sep 17 00:00:00 2001 From: Berke Güzel Date: Wed, 14 Oct 2026 14:16:42 +0300 Subject: Wayland: fix GL thread races, reconnect ordering and sizing bugs - GL swaps on a rendering thread no longer touch frame callbacks or presentation feedback. They set a per-surface flag and wake the loop; the owner thread arms both at its next flush and commits, so the frame callback fires without waiting for the application's next swap. - Clear the offline flag only after the reconnect rebuild succeeds, and not when the connection was lost again during it. - make_current fails with ENOTCONN for a window without a context. Detaching is a no-op when nothing is current or the EGL display is gone. - Window move, resize, window menu and drag start need a pointer button or tablet tip that is still held; otherwise they fail with ESTALE and use that press's serial. Popups fail with ESTALE without an input serial and with ENOTCONN while the display is lost. - An oversized device size shrinks to the buffer limits, keeping the aspect ratio, instead of closing the window. - FIXED_SIZE windows pin xdg min and max on every set_size. The 64px minimum applies only to compositor-suggested sizes, not to application sizes. - The keyboard-enter serial becomes the input serial. Clipboard copy with no keyboard focus fails with ESTALE. - Relative-pointer windows relock after a reconnect rebuild. - errno: EBUSY for an active drag, ENOTSUP for place_under_pointer and for GL without EGL, EINVAL for drag carry without a drag. - Default window title and app ID are "YASL". - Pointer motion and button events carry unrounded device-pixel positions in precise_x/precise_y with precise_position set. Co-Authored-By: Claude Haiku 5.5 --- src/platform/wayland/backend.c | 74 +++++++++++++++++++++++++++++++++++------- 1 file changed, 63 insertions(+), 11 deletions(-) (limited to 'src/platform/wayland/backend.c') diff --git a/src/platform/wayland/backend.c b/src/platform/wayland/backend.c index 6b7758f..3ff696c 100644 --- a/src/platform/wayland/backend.c +++ b/src/platform/wayland/backend.c @@ -171,10 +171,18 @@ static struct yasl_surface *impl_surface_create_gl(struct yasl_private_display * { 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) + if (!spec || !d || !yasl_private_gl_parse_options(attributes, &o)) { + errno = EINVAL; return NULL; - if (share && (share->display != display || !((struct wayland_surface *)share)->egl_context)) + } + if (!d->egl_initialized) { + errno = ENOTSUP; return NULL; + } + if (share && (share->display != display || !((struct wayland_surface *)share)->egl_context)) { + errno = EINVAL; + return NULL; + } struct yasl_private_window_spec gl_spec = *spec; gl_spec.external_gpu = true; gl_spec.flags &= ~YASL_PRIVATE_SURFACE_CPU; @@ -190,15 +198,44 @@ static struct yasl_surface *impl_surface_create_gl(struct yasl_private_display * return &w->base; } +/* An EGL call failed: a lost context means the display is gone, anything else is a bad argument. */ +static int egl_failure_errno(void) +{ + return eglGetError() == EGL_CONTEXT_LOST ? ENOTCONN : EINVAL; +} + static bool impl_gl_make_current(struct yasl_private_display *display, struct yasl_surface *surface) { struct wayland_display *d = (struct wayland_display *)display; struct wayland_surface *w = (struct wayland_surface *)surface; - if (!d) return false; - if (!w) - return eglMakeCurrent(d->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT) == EGL_TRUE; - return w->conn == d && w->base.presenter == YASL_PRIVATE_PRESENTER_GL && - eglMakeCurrent(d->egl_display, w->egl_surface, w->egl_surface, w->egl_context) == EGL_TRUE; + if (!d) { + errno = EINVAL; + return false; + } + if (!w) { + /* Nothing to detach when no context is current or the display is gone. */ + if (eglGetCurrentContext() == EGL_NO_CONTEXT || d->egl_display == EGL_NO_DISPLAY) + return true; + if (eglMakeCurrent(d->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT) != EGL_TRUE) { + errno = egl_failure_errno(); + return false; + } + return true; + } + if (w->conn != d || w->base.presenter != YASL_PRIVATE_PRESENTER_GL) { + errno = EINVAL; + return false; + } + /* A window whose rebuild failed has no context and binds nothing. */ + if (!w->egl_context) { + errno = ENOTCONN; + return false; + } + if (eglMakeCurrent(d->egl_display, w->egl_surface, w->egl_surface, w->egl_context) != EGL_TRUE) { + errno = egl_failure_errno(); + return false; + } + return true; } static yasl_private_gl_proc impl_gl_get_proc_address(struct yasl_private_display *display, const char *name) @@ -223,10 +260,20 @@ static bool impl_gl_swap_buffers(struct yasl_private_display *display, struct ya w->base.presenter != YASL_PRIVATE_PRESENTER_GL) return false; if (!impl_gl_make_current(display, surface)) return false; - wayland_surface_arm_frame(w); - surface_request_presentation_feedback(w); - if (!eglSwapBuffers(d->egl_display, w->egl_surface)) return false; - return true; + /* Frame callbacks and feedback belong to the owner thread. Another thread only + * flags the surface and wakes the loop; the owner arms them at its next flush. */ + bool owner = pthread_equal(pthread_self(), d->owner); + if (owner) { + wayland_surface_arm_frame(w); + surface_request_presentation_feedback(w); + } + bool swapped = eglSwapBuffers(d->egl_display, w->egl_surface) == EGL_TRUE; + if (!swapped) errno = egl_failure_errno(); + if (!owner) { + atomic_store(&w->frame_deferred, true); + if (d->loop) yasl_private_loop_kick(d->loop); + } + return swapped; } #endif @@ -263,6 +310,11 @@ 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; + /* A fixed-size window is pinned at its size, so the compositor must not stretch it. */ + if ((w->base.creation_flags & YASL_PRIVATE_SURFACE_FIXED_SIZE) && w->toplevel && !w->conn->lost) { + xdg_toplevel_set_min_size(w->toplevel, size.x, size.y); + xdg_toplevel_set_max_size(w->toplevel, size.x, size.y); + } if (w->conn->lost || (w->base.state & (YASL_PRIVATE_STATE_MAXIMIZED | YASL_PRIVATE_STATE_FULLSCREEN | YASL_PRIVATE_STATE_TILED))) return true; -- cgit v1.2.3