summaryrefslogtreecommitdiff
path: root/src/platform/wayland
diff options
context:
space:
mode:
authorBerke Güzel <wenekar1@gmail.com>2026-10-14 14:16:42 +0300
committerBerke Güzel <wenekar1@gmail.com>2026-10-14 14:17:37 +0300
commit7e8bf8d2a81f37303e0c9ae430f1acbff106da22 (patch)
tree274cc707238f930395f87e74322c7c139811fe46 /src/platform/wayland
parent26f644f51709cc9f6cdd8060e7561498f2bb465d (diff)
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 <noreply@anthropic.com>
Diffstat (limited to 'src/platform/wayland')
-rw-r--r--src/platform/wayland/backend.c74
-rw-r--r--src/platform/wayland/buffer.c75
-rw-r--r--src/platform/wayland/drag.c37
-rw-r--r--src/platform/wayland/internal.h13
-rw-r--r--src/platform/wayland/keyboard.c12
-rw-r--r--src/platform/wayland/pointer.c85
-rw-r--r--src/platform/wayland/popup.c13
-rw-r--r--src/platform/wayland/shell.c4
-rw-r--r--src/platform/wayland/surface.c67
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 <errno.h>
+#include <math.h>
#include <sys/resource.h>
+/* 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 <fcntl.h>
#include <limits.h>
#include <poll.h>
+#include <pthread.h>
#include <time.h>
#include <linux/input-event-codes.h>
#include <stdio.h>
@@ -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;