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 +++++++++++++++++++++++++++++------ src/platform/wayland/buffer.c | 75 ++++++++++++++++++++++++++++++------ src/platform/wayland/drag.c | 37 ++++++++++++++---- src/platform/wayland/internal.h | 13 +++++++ src/platform/wayland/keyboard.c | 12 +++--- src/platform/wayland/pointer.c | 85 ++++++++++++++++++++++++++++++----------- src/platform/wayland/popup.c | 13 ++++++- src/platform/wayland/shell.c | 4 +- src/platform/wayland/surface.c | 67 +++++++++++++++++++++++--------- 9 files changed, 301 insertions(+), 79 deletions(-) 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; diff --git a/src/platform/wayland/buffer.c b/src/platform/wayland/buffer.c index 029b684..b68621d 100644 --- a/src/platform/wayland/buffer.c +++ b/src/platform/wayland/buffer.c @@ -1,8 +1,14 @@ #include "internal.h" #include "common/utf8.h" #include +#include #include +/* Largest device side a window may use, and the largest pixel count whose + * 4-byte rows still fit an INT32_MAX buffer. */ +#define DEVICE_SIDE_MAX 32767 +#define DEVICE_AREA_MAX (INT32_MAX / 4) + static long minor_faults(void) { struct rusage usage; @@ -28,6 +34,28 @@ int device_px(int logical, uint32_t scale_120) return px > 0 && px <= INT_MAX ? (int)px : -1; } +/* Unclamped device pixels of a logical extent; zero when it is not positive. */ +static int64_t device_extent(int logical, uint32_t scale_120) +{ + if (logical <= 0 || !scale_120) return 0; + return ((int64_t)logical * scale_120 + YASL_PRIVATE_SCALE_UNIT / 2) / YASL_PRIVATE_SCALE_UNIT; +} + +/* Shrinks a device size to fit the limits, keeping its aspect ratio. */ +static void device_size_fit(int64_t *width, int64_t *height) +{ + double w = (double)*width, h = (double)*height; + double k = 1.0; + if (*width > DEVICE_SIDE_MAX) k = DEVICE_SIDE_MAX / w; + if (*height > DEVICE_SIDE_MAX) k = fmin(k, DEVICE_SIDE_MAX / h); + if (w * h > DEVICE_AREA_MAX) k = fmin(k, sqrt(DEVICE_AREA_MAX / (w * h))); + if (k >= 1.0) return; + *width = (int64_t)floor(w * k); + *height = (int64_t)floor(h * k); + if (*width < 1) *width = 1; + if (*height < 1) *height = 1; +} + bool buffer_size(int width, int height, size_t *bytes) { if (width <= 0 || height <= 0 || width > INT32_MAX / 4 || @@ -140,26 +168,35 @@ int apply_pending_geometry(struct wayland_surface *w) (YASL_PRIVATE_STATE_MAXIMIZED | YASL_PRIVATE_STATE_FULLSCREEN | YASL_PRIVATE_STATE_TILED)); int free_w = floating && w->floating_width > 0 ? w->floating_width : w->logical_width; int free_h = floating && w->floating_height > 0 ? w->floating_height : w->logical_height; - int logical_w = !w->parent && w->req_width > 0 ? w->req_width : free_w; - int logical_h = !w->parent && w->req_height > 0 ? w->req_height : free_h; - if (!w->parent) { - if (logical_w < YASL_PRIVATE_MIN_LOGICAL_PX) logical_w = YASL_PRIVATE_MIN_LOGICAL_PX; - if (logical_h < YASL_PRIVATE_MIN_LOGICAL_PX) logical_h = YASL_PRIVATE_MIN_LOGICAL_PX; - } + /* Compositor-suggested sizes are raised to the minimum; application sizes are used as given. */ + bool suggested_w = !w->parent && w->req_width > 0; + bool suggested_h = !w->parent && w->req_height > 0; + int logical_w = suggested_w ? w->req_width : free_w; + int logical_h = suggested_h ? w->req_height : free_h; + if (suggested_w && logical_w < YASL_PRIVATE_MIN_LOGICAL_PX) logical_w = YASL_PRIVATE_MIN_LOGICAL_PX; + if (suggested_h && logical_h < YASL_PRIVATE_MIN_LOGICAL_PX) logical_h = YASL_PRIVATE_MIN_LOGICAL_PX; if (!(w->base.state & YASL_PRIVATE_STATE_FULLSCREEN)) { if (w->bound_width > 0 && logical_w > w->bound_width) logical_w = w->bound_width; if (w->bound_height > 0 && logical_h > w->bound_height) logical_h = w->bound_height; } - int width = device_px(logical_w, w->pending_scale_120); - int height = device_px(logical_h, w->pending_scale_120); - size_t bytes; - if (width <= 0 || height <= 0 || - (w->base.presenter == YASL_PRIVATE_PRESENTER_CPU && - !buffer_size(width, height, &bytes))) { + /* An oversized window is shrunk to what a buffer holds rather than closed. */ + int64_t device_w = device_extent(logical_w, w->pending_scale_120); + int64_t device_h = device_extent(logical_h, w->pending_scale_120); + if (device_w <= 0 || device_h <= 0) return -1; + if (device_w > DEVICE_SIDE_MAX || device_h > DEVICE_SIDE_MAX || + (double)device_w * (double)device_h > DEVICE_AREA_MAX) { + device_size_fit(&device_w, &device_h); + logical_w = yasl_private_scale_to_logical((int)device_w, w->pending_scale_120); + logical_h = yasl_private_scale_to_logical((int)device_h, w->pending_scale_120); } + int width = (int)device_w; + int height = (int)device_h; + size_t bytes; + if (w->base.presenter == YASL_PRIVATE_PRESENTER_CPU && !buffer_size(width, height, &bytes)) + return -1; bool rescaled = w->scale_120 != w->pending_scale_120; bool resized = width != w->width || height != w->height; w->size_pending = false; @@ -424,6 +461,20 @@ void surface_request_presentation_feedback(struct wayland_surface *w) wp_presentation_feedback_add_listener(fb, &feedback_listener, feedback); } +/* A swap on a rendering thread only sets frame_deferred. The owner arms the frame + * callback and feedback here, then commits so that the callback can fire without + * waiting for the application's next swap. */ +void surface_arm_deferred(struct wayland_surface *w) +{ + if (!atomic_exchange(&w->frame_deferred, false) || w->closed) + return; + bool armed = !w->frame_cb; + wayland_surface_arm_frame(w); + surface_request_presentation_feedback(w); + if (armed && w->frame_cb && !w->closed) + wl_surface_commit(w->surface); +} + static void commit_buffer(struct wayland_surface *w, int idx) { wl_surface_attach(w->surface, w->buffers[idx].proxy, 0, 0); diff --git a/src/platform/wayland/drag.c b/src/platform/wayland/drag.c index 07e31b5..4317d0d 100644 --- a/src/platform/wayland/drag.c +++ b/src/platform/wayland/drag.c @@ -136,15 +136,16 @@ static const struct wl_data_source_listener source_listener = { .action = source_action, }; +/* A seat whose press is still held over the origin window; its serial starts the drag. */ static struct seat *drag_seat(struct wayland_display *c, struct wayland_surface *origin) { struct wayland_surface *root = surface_root(origin); struct seat *active = c->active_seat ? c->active_seat : c->seats; - if (active && active->press_serial && + if (active && seat_held_press_serial(active) && surface_root(active->press_surface) == root) return active; for (struct seat *s = c->seats; s; s = s->next) - if (s->press_serial && surface_root(s->press_surface) == root) + if (seat_held_press_serial(s) && surface_root(s->press_surface) == root) return s; return NULL; } @@ -153,20 +154,33 @@ bool wayland_display_drag_start(struct wayland_display *c, struct wayland_surfac const struct yasl_private_drag_image *image, const struct yasl_private_drag_listener *l, void *ud) { - if (!c || !origin || origin->conn != c || !l || c->drag.source) + if (!c || !origin || origin->conn != c || !l) { + errno = EINVAL; return false; + } + if (c->drag.source) { + errno = EBUSY; + return false; + } struct seat *seat = drag_seat(c, origin); - if (!seat) + if (!seat) { + errno = ESTALE; return false; + } struct wl_data_device *device = clipboard_data_device(seat->clipboard); - if (!device || wl_data_device_get_version(device) < 3) + if (!device || wl_data_device_get_version(device) < 3) { + errno = ENOTSUP; return false; + } c->drag.scale_120 = origin->scale_120; - if (image && !icon_create(c, image)) + if (image && !icon_create(c, image)) { + errno = ENOMEM; return false; + } struct wl_data_source *s = wl_data_device_manager_create_data_source(c->data_manager); if (!s) { icon_clear(&c->drag.icon); + errno = ENOMEM; return false; } c->drag.source = s; @@ -180,7 +194,7 @@ bool wayland_display_drag_start(struct wayland_display *c, struct wayland_surfac c->drag.toplevel = xdg_toplevel_drag_manager_v1_get_xdg_toplevel_drag( c->toplevel_drag_mgr, s); wl_data_device_start_drag(device, s, seat->press_surface->surface, - c->drag.icon.surface, seat->press_serial); + c->drag.icon.surface, seat_held_press_serial(seat)); if (image) icon_show(c, image); wl_display_flush(c->wl); @@ -196,8 +210,14 @@ void wayland_display_drag_cancel(struct wayland_display *c) bool wayland_surface_drag_carry(struct wayland_surface *w, struct yasl_private_vector2i offset) { struct wayland_display *c = w ? w->conn : NULL; - if (!c || !c->drag.source || !c->drag.toplevel || !w->toplevel) + if (!c || !c->drag.source || !w->toplevel) { + errno = EINVAL; + return false; + } + if (!c->drag.toplevel) { + errno = ENOTSUP; return false; + } uint32_t scale = c->drag.scale_120; xdg_toplevel_drag_v1_attach(c->drag.toplevel, w->toplevel, yasl_private_scale_to_logical(offset.x, scale), @@ -210,6 +230,7 @@ bool wayland_surface_place_under_pointer(struct wayland_surface *w, struct yasl_ { (void)w; (void)at; + errno = ENOTSUP; return false; } diff --git a/src/platform/wayland/internal.h b/src/platform/wayland/internal.h index ff1b18f..d5b7de1 100644 --- a/src/platform/wayland/internal.h +++ b/src/platform/wayland/internal.h @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -79,6 +80,7 @@ struct wl_conn_output { struct pending_press { struct wayland_surface *surface; struct yasl_private_vector2i pos; + struct yasl_private_vector2d precise; int button; bool release; }; @@ -179,6 +181,8 @@ struct seat { struct yasl_private_vector2i pointer_pos; struct yasl_private_vector2i ptr_left_pos; struct yasl_private_vector2i tool_pos; + /* Unrounded device-pixel positions behind the rounded ones above. */ + struct yasl_private_vector2d pointer_precise, ptr_left_precise, tool_precise; struct touch_point touch_points[SEAT_TOUCH_POINTS]; struct press_queue ptr_presses; struct press_queue tool_presses; @@ -212,6 +216,8 @@ struct wayland_display { struct yasl_private_display base; /* 8-byte pointers & 64-bit masks */ struct wl_display *wl; + /* The thread that opened the display: the only one that arms frame callbacks. */ + pthread_t owner; #if YASL_HAS_EGL EGLDisplay egl_display; EGLint egl_api; @@ -363,6 +369,8 @@ struct wayland_surface { bool relative; bool pointer_locked; bool ime_delete_pending; + /* Set by a swap on a rendering thread; the owner arms the frame request at its next flush. */ + atomic_bool frame_deferred; }; @@ -389,6 +397,7 @@ static inline void wl_damage_list(struct wl_surface *s, const struct yasl_privat void surface_update_opaque_region(struct wayland_surface *w); int surface_init_scaling(struct wayland_surface *w); +void surface_arm_deferred(struct wayland_surface *w); uint64_t conn_output_mask(const struct wayland_display *c, const struct wl_output *proxy); uint32_t conn_output_scale_120(const struct wayland_display *c, uint64_t outputs); @@ -425,6 +434,10 @@ void seat_bind(struct wayland_display *c, struct wl_registry *registry, bool seat_remove(struct wayland_display *c, uint32_t name); void seat_destroy(struct seat *s); void seat_note_input(struct seat *s, uint32_t serial); +/* Serial of the latest press while a pointer button or tablet tip is still down, else 0. */ +uint32_t seat_held_press_serial(const struct seat *s); +/* Locks the pointer through the seat that last delivered input, or any seat with a pointer. */ +void surface_lock_preferred(struct wayland_surface *w); void seat_ensure_text_input(struct seat *s); void seat_forget_surface(struct seat *s, struct wayland_surface *w); void seat_clipboard_ensure(struct seat *s); diff --git a/src/platform/wayland/keyboard.c b/src/platform/wayland/keyboard.c index 9637341..cf6d56c 100644 --- a/src/platform/wayland/keyboard.c +++ b/src/platform/wayland/keyboard.c @@ -144,7 +144,7 @@ static void push_focus(struct wayland_surface *w, bool focused) static void keyboard_enter(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) { - (void)keyboard; (void)serial; (void)keys; + (void)keyboard; (void)keys; struct seat *s = data; struct wayland_surface *w = surface_root(surface ? (struct wayland_surface *)wl_surface_get_user_data(surface) : NULL); @@ -155,6 +155,8 @@ static void keyboard_enter(void *data, struct wl_keyboard *keyboard, s->keyboard_focus = NULL; return; } + /* The enter serial is a valid input serial, e.g. for set_selection. */ + seat_note_input(s, serial); bool focused = conn_keyboard_on(s->conn, w, s); s->keyboard_focus = w; keyboard_reset_keys(s, w); @@ -433,12 +435,12 @@ bool wayland_display_copy_text(struct wayland_display *c, enum yasl_private_sele errno = EINVAL; return false; } - if (!s || !s->clipboard) { - errno = ENOTSUP; + if (!s || !s->keyboard_focus || !s->input_serial) { + errno = ESTALE; return false; } - if (!s->input_serial) { - errno = EAGAIN; + if (!s->clipboard) { + errno = ENOTSUP; return false; } errno = 0; diff --git a/src/platform/wayland/pointer.c b/src/platform/wayland/pointer.c index 0cd8c5b..9f8ca49 100644 --- a/src/platform/wayland/pointer.c +++ b/src/platform/wayland/pointer.c @@ -35,21 +35,33 @@ struct tablet_pad { struct zwp_tablet_pad_v2 *proxy; }; -static struct yasl_private_vector2i device_from_fixed(const struct wayland_surface *w, - wl_fixed_t x, wl_fixed_t y) +static struct yasl_private_vector2d device_precise(const struct wayland_surface *w, + wl_fixed_t x, wl_fixed_t y) { - return (struct yasl_private_vector2i){ - .x = (int)lrint(wl_fixed_to_double(x) * w->scale_factor), - .y = (int)lrint(wl_fixed_to_double(y) * w->scale_factor), + return (struct yasl_private_vector2d){ + .x = wl_fixed_to_double(x) * w->scale_factor, + .y = wl_fixed_to_double(y) * w->scale_factor, }; } +static struct yasl_private_vector2i device_round(struct yasl_private_vector2d p) +{ + return (struct yasl_private_vector2i){ .x = (int)lrint(p.x), .y = (int)lrint(p.y) }; +} + void seat_note_input(struct seat *s, uint32_t serial) { s->input_serial = serial; s->conn->active_seat = s; } +uint32_t seat_held_press_serial(const struct seat *s) +{ + if (!s) return 0; + bool held = s->pointer_buttons != 0 || (s->tool && s->tool->in_contact); + return held ? s->press_serial : 0; +} + static void push_pointer(struct seat *s, struct wayland_surface *w, enum yasl_private_event_type type, struct yasl_private_pointer_event ev) { @@ -220,6 +232,9 @@ static void press_queue_flush(struct seat *s, struct press_queue *q, *buttons = (*buttons & ~mask) | ((uint64_t)(!p.release) << p.button); struct yasl_private_pointer_event ev = like; ev.pos = p.pos; + ev.precise_x = p.precise.x; + ev.precise_y = p.precise.y; + ev.precise_position = true; ev.button = (enum yasl_private_pointer_button)p.button; enum yasl_private_event_type type = p.release ? YASL_PRIVATE_EVENT_POINTER_UP : YASL_PRIVATE_EVENT_POINTER_DOWN; push_pointer(s, p.surface, type, ev); @@ -229,14 +244,15 @@ static void press_queue_flush(struct seat *s, struct press_queue *q, } static void press_queue_push(struct press_queue *q, struct wayland_surface *w, - struct yasl_private_vector2i pos, int button, bool release) + struct yasl_private_vector2i pos, struct yasl_private_vector2d precise, + int button, bool release) { if (q->n == 8) { q->head = (q->head + 1) & 7; q->n--; } q->ev[(q->head + q->n) & 7] = (struct pending_press){ - .surface = w, .pos = pos, .button = button, .release = release, + .surface = w, .pos = pos, .precise = precise, .button = button, .release = release, }; q->n++; } @@ -282,6 +298,9 @@ static void flush_pending_motion(struct seat *s, struct wayland_surface *w) s->motion_pending = false; push_pointer(s, w, YASL_PRIVATE_EVENT_POINTER_MOTION, (struct yasl_private_pointer_event){ .pos = s->pointer_pos, + .precise_x = s->pointer_precise.x, + .precise_y = s->pointer_precise.y, + .precise_position = true, }); } @@ -295,7 +314,8 @@ static void pointer_enter(void *data, struct wl_pointer *pointer, (struct wayland_surface *)wl_surface_get_user_data(surface) : NULL; s->pointer_focus = w; if (!w) return; - s->pointer_pos = device_from_fixed(w, surface_x, surface_y); + s->pointer_precise = device_precise(w, surface_x, surface_y); + s->pointer_pos = device_round(s->pointer_precise); s->pointer_serial = serial; s->cursor_shape = -1; seat_set_cursor(s, w); @@ -315,6 +335,7 @@ static void pointer_leave(void *data, struct wl_pointer *pointer, pointer_frame(s, s->pointer); s->ptr_left = w; s->ptr_left_pos = s->pointer_pos; + s->ptr_left_precise = s->pointer_precise; s->ptr_left_motion = s->motion_pending; s->motion_pending = false; pointer_event_done(s); @@ -327,7 +348,8 @@ static void pointer_motion(void *data, struct wl_pointer *pointer, struct seat *s = data; struct wayland_surface *w = s->pointer_focus; if (!w) return; - s->pointer_pos = device_from_fixed(w, surface_x, surface_y); + s->pointer_precise = device_precise(w, surface_x, surface_y); + s->pointer_pos = device_round(s->pointer_precise); s->motion_pending = true; pointer_event_done(s); } @@ -360,7 +382,7 @@ static void pointer_button(void *data, struct wl_pointer *pointer, s->press_serial = serial; s->press_surface = w; } - press_queue_push(&s->ptr_presses, w, s->pointer_pos, b, release); + press_queue_push(&s->ptr_presses, w, s->pointer_pos, s->pointer_precise, b, release); pointer_event_done(s); } @@ -464,17 +486,22 @@ static void pointer_frame(void *data, struct wl_pointer *pointer) if (s->ptr_left_motion) push_pointer(s, left, YASL_PRIVATE_EVENT_POINTER_MOTION, (struct yasl_private_pointer_event){ .pos = s->ptr_left_pos, + .precise_x = s->ptr_left_precise.x, + .precise_y = s->ptr_left_precise.y, + .precise_position = true, }); press_queue_flush(s, &s->ptr_presses, left, &s->pointer_buttons, (struct yasl_private_pointer_event){0}); push_pointer(s, left, YASL_PRIVATE_EVENT_POINTER_LEAVE, (struct yasl_private_pointer_event){ .pos = s->ptr_left_pos, }); struct yasl_private_vector2i pos = s->pointer_pos; + struct yasl_private_vector2d precise = s->pointer_precise; bool motion = s->motion_pending; uint32_t serial = s->pointer_focus ? s->pointer_serial : 0; int shape = s->pointer_focus ? s->cursor_shape : -1; seat_pointer_reset(s); s->pointer_pos = pos; + s->pointer_precise = precise; s->motion_pending = motion; s->pointer_serial = serial; s->cursor_shape = shape; @@ -508,6 +535,9 @@ static void relative_motion(void *data, struct zwp_relative_pointer_v1 *pointer, flush_pending_motion(s, w); push_pointer(s, w, YASL_PRIVATE_EVENT_POINTER_MOTION, (struct yasl_private_pointer_event){ .pos = s->pointer_pos, + .precise_x = s->pointer_precise.x, + .precise_y = s->pointer_precise.y, + .precise_position = true, .delta = {wl_fixed_to_double(dx) * w->scale_factor, wl_fixed_to_double(dy) * w->scale_factor}, }); @@ -571,6 +601,17 @@ static void seat_lock_surfaces(struct seat *s) surface_lock_pointer(w, s); } +void surface_lock_preferred(struct wayland_surface *w) +{ + struct wayland_display *c = w->conn; + /* Prefer the seat that last delivered input; others lock once it goes away. */ + struct seat *seat = c->active_seat && c->active_seat->pointer ? c->active_seat : NULL; + for (struct seat *s = c->seats; s && !seat; s = s->next) + if (s->pointer) seat = s; + if (seat) + surface_lock_pointer(w, seat); +} + bool wayland_surface_set_pointer_mode(struct wayland_surface *w, enum yasl_private_pointer_mode mode) { if (!w || !w->conn || w->destroyed) @@ -581,12 +622,7 @@ bool wayland_surface_set_pointer_mode(struct wayland_surface *w, enum yasl_priva return false; w->relative = relative; if (relative) { - /* Prefer the seat that last delivered input; others lock once it goes away. */ - struct seat *seat = c->active_seat && c->active_seat->pointer ? c->active_seat : NULL; - for (struct seat *s = c->seats; s && !seat; s = s->next) - if (s->pointer) seat = s; - if (seat) - surface_lock_pointer(w, seat); + surface_lock_preferred(w); } else { surface_unlock_pointer(w); } @@ -859,6 +895,7 @@ static void tablet_tool_proximity_in(void *data, if (!w) return; t->x = s->tool_pos.x; t->y = s->tool_pos.y; + s->tool_precise = (struct yasl_private_vector2d){ s->tool_pos.x, s->tool_pos.y }; if (s->conn->cursor_shape_mgr && !t->shape_dev) t->shape_dev = wp_cursor_shape_manager_v1_get_tablet_tool_v2( s->conn->cursor_shape_mgr, t->proxy); @@ -903,7 +940,7 @@ static void tablet_tool_down(void *data, struct zwp_tablet_tool_v2 *tool, uint32 seat_note_input(s, serial); s->press_serial = serial; s->press_surface = w; - press_queue_push(&s->tool_presses, w, s->tool_pos, 0, false); + press_queue_push(&s->tool_presses, w, s->tool_pos, s->tool_precise, 0, false); t->in_contact = true; pen_action_push(t, YASL_PRIVATE_PEN_DOWN, 0); } @@ -916,7 +953,7 @@ static void tablet_tool_up(void *data, struct zwp_tablet_tool_v2 *tool) struct wayland_surface *w = s->tablet_focus; if (!w) return; s->pen.pressure = 0; - press_queue_push(&s->tool_presses, w, s->tool_pos, 0, true); + press_queue_push(&s->tool_presses, w, s->tool_pos, s->tool_precise, 0, true); t->pressure = 0; t->in_contact = false; pen_action_push(t, YASL_PRIVATE_PEN_UP, 0); @@ -929,10 +966,11 @@ static void tablet_tool_motion(void *data, struct zwp_tablet_tool_v2 *tool, wl_f struct seat *s = ((struct tablet_tool *)data)->seat; struct wayland_surface *w = s->tablet_focus; if (!w) return; - s->tool_pos = device_from_fixed(w, x, y); + s->tool_precise = device_precise(w, x, y); + s->tool_pos = device_round(s->tool_precise); struct tablet_tool *owned = data; - owned->x = wl_fixed_to_double(x) * w->scale_factor; - owned->y = wl_fixed_to_double(y) * w->scale_factor; + owned->x = s->tool_precise.x; + owned->y = s->tool_precise.y; s->tool_motion = true; } @@ -953,7 +991,7 @@ static void tablet_tool_button(void *data, struct zwp_tablet_tool_v2 *tool, default: return; } bool release = state == ZWP_TABLET_TOOL_V2_BUTTON_STATE_RELEASED; - press_queue_push(&s->tool_presses, w, s->tool_pos, b, release); + press_queue_push(&s->tool_presses, w, s->tool_pos, s->tool_precise, b, release); pen_action_push(t, YASL_PRIVATE_PEN_BUTTON, (unsigned)b); } @@ -1032,6 +1070,9 @@ static void tool_frame(void *d, struct zwp_tablet_tool_v2 *t, uint32_t time) if (s->tool_motion) { struct yasl_private_pointer_event ev = like; ev.pos = s->tool_pos; + ev.precise_x = s->tool_precise.x; + ev.precise_y = s->tool_precise.y; + ev.precise_position = true; push_pointer(s, w, YASL_PRIVATE_EVENT_POINTER_MOTION, ev); s->tool_motion = false; } diff --git a/src/platform/wayland/popup.c b/src/platform/wayland/popup.c index 1ae4805..f66e155 100644 --- a/src/platform/wayland/popup.c +++ b/src/platform/wayland/popup.c @@ -136,9 +136,18 @@ struct wayland_surface *wayland_surface_create_popup(struct wayland_surface *par { struct wayland_display *c = parent ? parent->conn : NULL; struct seat *seat = c ? (c->active_seat ? c->active_seat : c->seats) : NULL; - if (!c || c->lost || parent->closed || !parent->xdg_surface || !seat || - !seat->input_serial || !popup_spec_valid(parent, spec)) + if (!c || !parent->xdg_surface || parent->closed || !popup_spec_valid(parent, spec)) { + errno = EINVAL; return NULL; + } + if (c->lost) { + errno = ENOTCONN; + return NULL; + } + if (!seat || !seat->input_serial) { + errno = ESTALE; + return NULL; + } struct wayland_surface *w = calloc(1, sizeof(*w)); if (!w) diff --git a/src/platform/wayland/shell.c b/src/platform/wayland/shell.c index c2578e7..8e6b67e 100644 --- a/src/platform/wayland/shell.c +++ b/src/platform/wayland/shell.c @@ -399,9 +399,9 @@ int surface_init_shell(struct wayland_surface *w, const char *title) if (!w->toplevel) return -1; xdg_toplevel_add_listener(w->toplevel, &toplevel_listener, w); - wayland_surface_set_title(w, title ? title : "yasl_private_app"); + wayland_surface_set_title(w, title ? title : "YASL"); - const char *app_id = w->conn->app_id ? w->conn->app_id : "yasl_private_app"; + const char *app_id = w->conn->app_id ? w->conn->app_id : "YASL"; xdg_toplevel_set_app_id(w->toplevel, app_id); xdg_toplevel_set_min_size(w->toplevel, YASL_PRIVATE_MIN_LOGICAL_PX, YASL_PRIVATE_MIN_LOGICAL_PX); diff --git a/src/platform/wayland/surface.c b/src/platform/wayland/surface.c index 2b72317..cc0c715 100644 --- a/src/platform/wayland/surface.c +++ b/src/platform/wayland/surface.c @@ -45,7 +45,7 @@ static void display_lose(struct wayland_display *c) return; c->lost = true; c->stale = true; - c->base.offline = true; + atomic_store(&c->base.offline, true); report_display_error(c); display_cancel_read(c); if (c->loop && c->watch > 0) @@ -195,6 +195,7 @@ void wayland_display_flush(struct wayland_display *c) c->depth++; for (struct wayland_surface *w = c->surfaces, *next; w; w = next) { next = w->conn_next; + surface_arm_deferred(w); surface_draw_frame(w); } if (!c->lost && c->wl) { @@ -290,6 +291,10 @@ static void display_rebuild(struct wayland_display *c) if (w->base.presenter == YASL_PRIVATE_PRESENTER_GL && !w->closed && !w->gl_built && !surface_build_gl(w)) surface_fail(w); #endif + /* Seats announced their pointers before these surfaces existed, so lock them now. */ + for (struct wayland_surface *w = c->surfaces; w && !c->lost; w = w->conn_next) + if (!w->closed && w->base.pointer_mode == YASL_PRIVATE_POINTER_RELATIVE) + surface_lock_preferred(w); for (struct wayland_surface *w = c->surfaces; w && !c->lost; w = w->conn_next) if (!w->closed) yasl_private_surface_notify_refresh(&w->base); } @@ -312,9 +317,11 @@ static uint64_t reconnect_tick(void *ud, yasl_private_timer_id id, uint64_t inte } 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); + /* Windows exist again only after the rebuild; a loss during it stays offline. */ + if (!c->lost) + atomic_store(&c->base.offline, false); display_leave(c); return 0; } @@ -337,6 +344,7 @@ struct wayland_display *wayland_display_open(struct yasl_private_loop *loop, con { struct wayland_display *c = calloc(1, sizeof(*c)); if (!c) return NULL; + c->owner = pthread_self(); c->base.ops = &yasl_private_wayland_backend; if (!loop) { c->loop = yasl_private_loop_create(); @@ -499,6 +507,7 @@ void surface_release_native(struct wayland_surface *w) w->base.presenter = YASL_PRIVATE_PRESENTER_NONE; w->configured = w->has_pending_serial = w->has_shown = false; w->frame_active = w->want_draw = false; + atomic_store(&w->frame_deferred, false); w->outputs = 0; } @@ -520,10 +529,10 @@ 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"); + wayland_surface_set_title(w, spec->title ? spec->title : "YASL"); w->cursor = YASL_PRIVATE_CURSOR_TEXT; - w->req_width = width; - w->req_height = height; + w->floating_width = width; + w->floating_height = height; w->logical_width = width; w->logical_height = height; w->scale_120 = YASL_PRIVATE_SCALE_UNIT; @@ -613,31 +622,55 @@ void wayland_surface_set_background_color(struct wayland_surface *w, uint32_t bg (void)w; (void)bg; } +/* The serial of the press a window request cites; it must still be held. */ +static uint32_t held_press_serial(struct wayland_surface *s, struct seat **seat) +{ + *seat = s->conn->active_seat ? s->conn->active_seat : s->conn->seats; + uint32_t serial = seat_held_press_serial(*seat); + if (!serial || !(*seat)->proxy) { + errno = ESTALE; + return 0; + } + return serial; +} + bool wayland_surface_start_interactive_move(struct wayland_surface *s) { - if (!s || !s->toplevel || !s->conn) return false; - struct seat *seat = s->conn->active_seat ? s->conn->active_seat : s->conn->seats; - if (!seat || !seat->proxy || !seat->press_serial) return false; - xdg_toplevel_move(s->toplevel, seat->proxy, seat->press_serial); + if (!s || !s->toplevel || !s->conn) { + errno = EINVAL; + return false; + } + struct seat *seat; + uint32_t serial = held_press_serial(s, &seat); + if (!serial) return false; + xdg_toplevel_move(s->toplevel, seat->proxy, serial); return true; } bool wayland_surface_start_interactive_resize(struct wayland_surface *s, enum yasl_private_resize_edge edge) { - if (!s || !s->toplevel || !s->conn || edge == YASL_PRIVATE_EDGE_NONE) return false; - struct seat *seat = s->conn->active_seat ? s->conn->active_seat : s->conn->seats; - if (!seat || !seat->proxy || !seat->press_serial) return false; - xdg_toplevel_resize(s->toplevel, seat->proxy, seat->press_serial, (uint32_t)edge); + if (!s || !s->toplevel || !s->conn || edge == YASL_PRIVATE_EDGE_NONE) { + errno = EINVAL; + return false; + } + struct seat *seat; + uint32_t serial = held_press_serial(s, &seat); + if (!serial) return false; + xdg_toplevel_resize(s->toplevel, seat->proxy, serial, (uint32_t)edge); return true; } bool wayland_surface_show_window_menu(struct wayland_surface *s, struct yasl_private_vector2i pos) { - if (!s || !s->toplevel || !s->conn) return false; - struct seat *seat = s->conn->active_seat ? s->conn->active_seat : s->conn->seats; - if (!seat || !seat->proxy || !seat->press_serial) return false; + if (!s || !s->toplevel || !s->conn) { + errno = EINVAL; + return false; + } + struct seat *seat; + uint32_t serial = held_press_serial(s, &seat); + if (!serial) return false; uint32_t scale = s->scale_120 ? s->scale_120 : YASL_PRIVATE_SCALE_UNIT; - xdg_toplevel_show_window_menu(s->toplevel, seat->proxy, seat->press_serial, + xdg_toplevel_show_window_menu(s->toplevel, seat->proxy, serial, yasl_private_scale_to_logical(pos.x, scale), yasl_private_scale_to_logical(pos.y, scale)); return true; -- cgit v1.2.3