summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBerke Güzel <wenekar1@gmail.com>2026-10-12 00:09:38 +0300
committerBerke Güzel <wenekar1@gmail.com>2026-10-12 00:09:38 +0300
commit624d1371304a5ae4b3429ad5e4267aac7cd03dfd (patch)
tree32de069f6eec83686521fc4bf39cc9874b123e93 /src
parente813252e685d761235ad45e170d97c9375f51958 (diff)
missing impls and hardening
Diffstat (limited to 'src')
-rw-r--r--src/cocoa/window.m67
-rw-r--r--src/common/backend.c57
-rw-r--r--src/common/backend.h9
-rw-r--r--src/common/input.c103
-rw-r--r--src/common/input.h19
-rw-r--r--src/common/meson.build2
-rw-r--r--src/common/timing.c1
-rw-r--r--src/wayland/buffer.c9
-rw-r--r--src/wayland/clipboard.c32
-rw-r--r--src/wayland/clipboard.h5
-rw-r--r--src/wayland/drag.c12
-rw-r--r--src/wayland/internal.h34
-rw-r--r--src/wayland/keyboard.c6
-rw-r--r--src/wayland/pointer.c8
-rw-r--r--src/wayland/surface.c13
-rw-r--r--src/x11/window.c109
16 files changed, 319 insertions, 167 deletions
diff --git a/src/cocoa/window.m b/src/cocoa/window.m
index 8712ce8..4454be3 100644
--- a/src/cocoa/window.m
+++ b/src/cocoa/window.m
@@ -42,35 +42,7 @@ enum {
@property (nonatomic, assign) BOOL fullscreenTransition;
@end
-#define YASL_QUEUE_CAP 256
-struct input_queue {
- struct yasl_input_event events[YASL_QUEUE_CAP];
- int head;
- int tail;
- int count;
-};
-
-static inline void input_queue_push(struct input_queue *q, const struct yasl_input_event *ev)
-{
- if (q->count >= YASL_QUEUE_CAP) return;
- q->events[q->tail] = *ev;
- q->tail = (q->tail + 1) % YASL_QUEUE_CAP;
- q->count++;
-}
-
-static inline bool input_queue_pop(struct input_queue *q, struct yasl_input_event *ev)
-{
- if (q->count <= 0) return false;
- *ev = q->events[q->head];
- q->head = (q->head + 1) % YASL_QUEUE_CAP;
- q->count--;
- return true;
-}
-
-static inline void input_queue_clear(struct input_queue *q)
-{
- q->head = q->tail = q->count = 0;
-}
+#include "common/input.h"
struct cocoa_surface {
struct yasl_surface base;
@@ -145,15 +117,6 @@ static struct cocoa_display darwin_ctx;
static void render_and_commit(struct cocoa_surface *w);
static void reap_dead(struct cocoa_display *d);
-static uint64_t media_to_loop_ns(CFTimeInterval t)
-{
- double delta = (t - CACurrentMediaTime()) * 1e9;
- uint64_t now = yasl_loop_now_ns();
- if (delta < 0.0 && -delta >= (double)now)
- return 0;
- return (uint64_t)((double)now + delta);
-}
-
static void set_state(struct cocoa_surface *w, uint32_t bit, bool on)
{
if (!w || w->closed)
@@ -502,7 +465,6 @@ static NSCursor *cocoa_cursor(enum yasl_cursor cursor)
CFTimeInterval period = link.targetTimestamp - link.timestamp;
if (period > 0.0 && period < 0.25)
w->clock.refresh_ns = period * 1e9;
- w->clock.last_present_ns = media_to_loop_ns(link.timestamp);
w->frame_due = true;
if (!w->want_draw || (w->state & YASL_STATE_SUSPENDED)) {
if (++w->idle_frames >= LINK_IDLE_FRAMES)
@@ -810,8 +772,10 @@ static NSString *plain_string(id string)
bool cocoa_surface_present(struct cocoa_surface *w, const struct yasl_framebuffer *fb,
const struct yasl_damage_list *damage)
{
- (void)damage;
- if (!w || w->closed || !w->rendering || w->did_present || !fb ||
+ if (!w || w->closed || !w->rendering || w->did_present || !fb || !damage ||
+ fb->pixels != w->frame.target.pixels || fb->size.x != w->size.x ||
+ fb->size.y != w->size.y || fb->buffer != w->frame.target.buffer ||
+ fb->transaction != w->frame.target.transaction ||
!fb->native_drawable || fb->native_drawable != w->frame.target.native_drawable)
return false;
if (w->software_queue) {
@@ -850,7 +814,7 @@ static void render_and_commit(struct cocoa_surface *w)
return;
size_t count = (size_t)w->size.x * (size_t)w->size.y;
- if (count > w->pixel_capacity) {
+ if (w->software_queue && count > w->pixel_capacity) {
uint32_t *pixels = realloc(w->pixels, count * sizeof(*pixels));
if (!pixels) return;
w->pixels = pixels;
@@ -866,7 +830,8 @@ static void render_and_commit(struct cocoa_surface *w)
w->frame = (struct yasl_frame){
.surface = &w->base,
.target = {
- .pixels = w->pixels,
+ .generation = yasl_frame_begin(&w->base),
+ .pixels = w->software_queue ? w->pixels : NULL,
.size = w->size,
.damage = &w->damage,
.native_drawable = drawable,
@@ -943,7 +908,8 @@ void cocoa_display_dispatch(struct cocoa_display *d)
bool immediate = false;
for (int i = 0; i < d->count; i++) {
struct cocoa_surface *w = d->windows[i];
- if (w->input.count > 0 || (w->want_draw && (!w->link || w->frame_due || w->unpaced))) {
+ if (w->input.count > 0 || w->input.reset_pending ||
+ (w->want_draw && (!w->link || w->frame_due || w->unpaced))) {
immediate = true;
break;
}
@@ -1039,7 +1005,8 @@ struct cocoa_display *cocoa_display_open(struct yasl_loop *loop, const struct ya
darwin_ctx.open = true;
darwin_ctx.loop = loop;
if (spec) {
- darwin_ctx.cbs = spec->cbs;
+ darwin_ctx.base.callbacks = spec->cbs ? *spec->cbs : (struct yasl_display_cbs){0};
+ darwin_ctx.cbs = &darwin_ctx.base.callbacks;
darwin_ctx.ud = spec->ud;
if (spec->app_id) darwin_ctx.app_id = strdup(spec->app_id);
}
@@ -1065,7 +1032,8 @@ void cocoa_display_close(struct cocoa_display *d)
uint32_t cocoa_display_caps(const struct cocoa_display *d)
{
- return d ? (YASL_CAP_PLACE_WINDOW | YASL_CAP_CSD) : 0;
+ return d ? (YASL_CAP_PLACE_WINDOW | YASL_CAP_CSD | YASL_CAP_NATIVE_GRAPHICS |
+ YASL_CAP_FRAME_PACING) : 0;
}
struct cocoa_surface *cocoa_surface_create_toplevel(struct cocoa_display *d,
@@ -1302,7 +1270,7 @@ void cocoa_display_paste(struct cocoa_display *d, enum yasl_selection sel, struc
(void)d; (void)sel;
if (!target) return;
NSString *s = [[NSPasteboard generalPasteboard] stringForType:NSPasteboardTypeString];
- const char *utf8 = s.length ? s.UTF8String : NULL;
+ const char *utf8 = s ? s.UTF8String : NULL;
if (utf8) {
struct yasl_input_event ev = {
.kind = YASL_INPUT_PASTE,
@@ -1388,9 +1356,9 @@ void *cocoa_surface_get_native_handle(struct cocoa_surface *s)
void cocoa_surface_set_callbacks(struct cocoa_surface *w,
const struct yasl_surface_cbs *cbs, void *ud)
{
- static const struct yasl_surface_cbs empty = {0};
if (!w || w->closed) return;
- w->cbs = cbs ? cbs : &empty;
+ w->base.callbacks = cbs ? *cbs : (struct yasl_surface_cbs){0};
+ w->cbs = &w->base.callbacks;
w->ud = ud;
}
@@ -1412,6 +1380,7 @@ struct yasl_timing cocoa_surface_timing(const struct cocoa_surface *w)
timing.last_present_ns = w->clock.last_present_ns;
timing.next_present_ns = yasl_clock_next_present_ns(&w->clock);
timing.frames_skipped = w->frames_skipped;
+ timing.source = w->clock.source;
yasl_clock_resize_stats(&w->clock, &timing.resize);
}
return timing;
diff --git a/src/common/backend.c b/src/common/backend.c
index 2220f1f..214854b 100644
--- a/src/common/backend.c
+++ b/src/common/backend.c
@@ -113,14 +113,18 @@ int yasl_display_modifiers(const struct yasl_display *d)
void yasl_display_copy(struct yasl_display *d, enum yasl_selection sel, const char *text)
{
- if (!d || !d->ops->display_copy)
+ if (!d || !d->ops->display_copy || !text ||
+ (sel != YASL_CLIPBOARD && sel != YASL_PRIMARY) ||
+ (sel == YASL_PRIMARY && !(yasl_display_caps(d) & YASL_CAP_PRIMARY_SELECTION)))
return;
d->ops->display_copy(d, sel, text);
}
void yasl_display_paste(struct yasl_display *d, enum yasl_selection sel, struct yasl_surface *target)
{
- if (!d || !d->ops->display_paste)
+ if (!d || !d->ops->display_paste ||
+ (sel != YASL_CLIPBOARD && sel != YASL_PRIMARY) ||
+ (sel == YASL_PRIMARY && !(yasl_display_caps(d) & YASL_CAP_PRIMARY_SELECTION)))
return;
if (!target || target->display != d)
return;
@@ -145,8 +149,17 @@ void yasl_display_drag_cancel(struct yasl_display *d)
struct yasl_surface *yasl_surface_create_toplevel(struct yasl_display *d, const struct yasl_surface_spec *spec)
{
- if (!d || !d->ops->surface_create_toplevel)
- return 0;
+ if (!d || !d->ops->surface_create_toplevel || !spec ||
+ spec->size.x <= 0 || spec->size.y <= 0 ||
+ spec->size.x > 32767 || spec->size.y > 32767 ||
+ (int64_t)spec->size.x * spec->size.y * 4 > INT32_MAX) {
+ errno = EINVAL;
+ return NULL;
+ }
+ if (spec->graphics_device && !(yasl_display_caps(d) & YASL_CAP_NATIVE_GRAPHICS)) {
+ errno = ENOTSUP;
+ return NULL;
+ }
return d->ops->surface_create_toplevel(d, spec);
}
@@ -201,14 +214,34 @@ void yasl_surface_schedule_redraw(struct yasl_surface *s)
bool yasl_surface_present(struct yasl_surface *s, const struct yasl_framebuffer *fb, const struct yasl_damage_list *damage)
{
- if (!s || !s->display->ops->surface_present)
- return 0;
- return s->display->ops->surface_present(s, fb, damage);
+ if (!s || !s->display->ops->surface_present || !fb || !damage ||
+ !fb->generation || fb->generation != s->frame_generation ||
+ fb->size.x <= 0 || fb->size.y <= 0 ||
+ damage->n < -1 || damage->n > YASL_DAMAGE_MAX)
+ return false;
+ struct yasl_damage_list clipped = {.n = damage->n};
+ if (damage->n >= 0) {
+ clipped.n = 0;
+ for (int i = 0; i < damage->n; i++) {
+ struct yasl_rect2i r = damage->rects[i];
+ if (r.width < 0 || r.height < 0) return false;
+ int64_t right = (int64_t)r.x + r.width;
+ int64_t bottom = (int64_t)r.y + r.height;
+ int64_t x = r.x > 0 ? r.x : 0, y = r.y > 0 ? r.y : 0;
+ if (right > fb->size.x) right = fb->size.x;
+ if (bottom > fb->size.y) bottom = fb->size.y;
+ if (right > x && bottom > y)
+ clipped.rects[clipped.n++] = (struct yasl_rect2i){
+ (int)x, (int)y, (int)(right - x), (int)(bottom - y),
+ };
+ }
+ }
+ return s->display->ops->surface_present(s, fb, &clipped);
}
bool yasl_surface_input_poll(struct yasl_surface *s, struct yasl_input_event *ev)
{
- if (!s || !s->display->ops->surface_input_poll)
+ if (!s || !ev || !s->display->ops->surface_input_poll)
return 0;
return s->display->ops->surface_input_poll(s, ev);
}
@@ -271,7 +304,7 @@ const char *yasl_surface_ime_preedit(const struct yasl_surface *s)
bool yasl_surface_ime_preedit_cursor(const struct yasl_surface *s, int *begin, int *end)
{
- if (!s || !s->display->ops->surface_ime_preedit_cursor)
+ if (!s || !begin || !end || !s->display->ops->surface_ime_preedit_cursor)
return 0;
return s->display->ops->surface_ime_preedit_cursor(s, begin, end);
}
@@ -292,7 +325,11 @@ bool yasl_surface_start_interactive_move(struct yasl_surface *s)
bool yasl_surface_start_interactive_resize(struct yasl_surface *s, enum yasl_resize_edge edge)
{
- if (!s || !s->display->ops->surface_start_interactive_resize)
+ if (!s || !s->display->ops->surface_start_interactive_resize ||
+ (edge != YASL_EDGE_TOP && edge != YASL_EDGE_BOTTOM &&
+ edge != YASL_EDGE_LEFT && edge != YASL_EDGE_RIGHT &&
+ edge != YASL_EDGE_TOP_LEFT && edge != YASL_EDGE_TOP_RIGHT &&
+ edge != YASL_EDGE_BOTTOM_LEFT && edge != YASL_EDGE_BOTTOM_RIGHT))
return 0;
return s->display->ops->surface_start_interactive_resize(s, edge);
}
diff --git a/src/common/backend.h b/src/common/backend.h
index c1d878b..c6691eb 100644
--- a/src/common/backend.h
+++ b/src/common/backend.h
@@ -7,12 +7,21 @@ struct yasl_backend_ops;
struct yasl_display {
const struct yasl_backend_ops *ops;
+ struct yasl_display_cbs callbacks;
};
struct yasl_surface {
struct yasl_display *display;
+ struct yasl_surface_cbs callbacks;
+ uint64_t frame_generation;
};
+static inline uint64_t yasl_frame_begin(struct yasl_surface *s)
+{
+ if (++s->frame_generation == 0) ++s->frame_generation;
+ return s->frame_generation;
+}
+
struct yasl_backend_ops {
enum yasl_backend type;
struct yasl_display *(*display_open)(struct yasl_loop *loop, const struct yasl_display_spec *spec);
diff --git a/src/common/input.c b/src/common/input.c
new file mode 100644
index 0000000..eac4d1a
--- /dev/null
+++ b/src/common/input.c
@@ -0,0 +1,103 @@
+#include "input.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+static const char *event_text(const struct yasl_input_event *ev, size_t *len)
+{
+ switch (ev->kind) {
+ case YASL_INPUT_TEXT: *len = ev->text.len; return ev->text.text;
+ case YASL_INPUT_PASTE: *len = ev->paste.len; return ev->paste.text;
+ case YASL_INPUT_DROP_FILES: *len = ev->drop_files.len; return ev->drop_files.files;
+ default: *len = 0; return NULL;
+ }
+}
+
+static void set_text(struct yasl_input_event *ev, const char *text)
+{
+ switch (ev->kind) {
+ case YASL_INPUT_TEXT: ev->text.text = text; break;
+ case YASL_INPUT_PASTE: ev->paste.text = text; break;
+ case YASL_INPUT_DROP_FILES: ev->drop_files.files = text; break;
+ default: break;
+ }
+}
+
+static void discard_pending(struct input_queue *q)
+{
+ for (size_t i = 0; i < q->count; i++) {
+ size_t len;
+ free((void *)event_text(&q->events[(q->head + i) % q->capacity], &len));
+ }
+ q->head = q->count = 0;
+}
+
+bool input_queue_push(struct input_queue *q, const struct yasl_input_event *ev)
+{
+ struct yasl_input_event copy = *ev;
+ size_t len;
+ const char *text = event_text(ev, &len);
+ char *owned = NULL;
+ if (ev->kind == YASL_INPUT_TEXT || ev->kind == YASL_INPUT_PASTE ||
+ ev->kind == YASL_INPUT_DROP_FILES) {
+ if ((!text && len) || len == SIZE_MAX) goto failed;
+ owned = malloc(len + 1);
+ if (!owned) goto failed;
+ if (len) memcpy(owned, text, len);
+ owned[len] = '\0';
+ set_text(&copy, owned);
+ }
+ if (q->count == q->capacity) {
+ size_t capacity = q->capacity ? q->capacity * 2 : 32;
+ if (capacity < q->capacity || capacity > SIZE_MAX / sizeof(*q->events))
+ goto failed;
+ struct yasl_input_event *events = malloc(capacity * sizeof(*events));
+ if (!events) goto failed;
+ for (size_t i = 0; i < q->count; i++)
+ events[i] = q->events[(q->head + i) % q->capacity];
+ free(q->events);
+ q->events = events;
+ q->capacity = capacity;
+ q->head = 0;
+ }
+ q->events[(q->head + q->count) % q->capacity] = copy;
+ q->count++;
+ return true;
+
+failed:
+ free(owned);
+ /* A missing release must never silently leave app input state stuck.
+ * Discard the old stream and deliver an allocation-free reset first. */
+ discard_pending(q);
+ q->reset_surface = ev->surface;
+ q->reset_pending = true;
+ return false;
+}
+
+bool input_queue_pop(struct input_queue *q, struct yasl_input_event *ev)
+{
+ if (!ev || (!q->count && !q->reset_pending)) return false;
+ free(q->last_text);
+ q->last_text = NULL;
+ if (q->reset_pending) {
+ *ev = (struct yasl_input_event){
+ .surface = q->reset_surface, .kind = YASL_INPUT_RESET,
+ };
+ q->reset_pending = false;
+ return true;
+ }
+ *ev = q->events[q->head];
+ size_t len;
+ q->last_text = (char *)event_text(ev, &len);
+ q->head = (q->head + 1) % q->capacity;
+ q->count--;
+ return true;
+}
+
+void input_queue_clear(struct input_queue *q)
+{
+ discard_pending(q);
+ free(q->last_text);
+ free(q->events);
+ *q = (struct input_queue){0};
+}
diff --git a/src/common/input.h b/src/common/input.h
new file mode 100644
index 0000000..d7264f6
--- /dev/null
+++ b/src/common/input.h
@@ -0,0 +1,19 @@
+#ifndef YASL_INPUT_H
+#define YASL_INPUT_H
+
+#include "yasl/yasl.h"
+
+/* Each queued string and the last successfully polled string are owned here. */
+struct input_queue {
+ struct yasl_input_event *events;
+ size_t head, count, capacity;
+ char *last_text;
+ struct yasl_surface *reset_surface;
+ bool reset_pending;
+};
+
+bool input_queue_push(struct input_queue *q, const struct yasl_input_event *ev);
+bool input_queue_pop(struct input_queue *q, struct yasl_input_event *ev);
+void input_queue_clear(struct input_queue *q);
+
+#endif
diff --git a/src/common/meson.build b/src/common/meson.build
index 61653df..5cfb187 100644
--- a/src/common/meson.build
+++ b/src/common/meson.build
@@ -1 +1 @@
-sources += files('backend.c', 'loop.c', 'timing.c', 'utf8.c')
+sources += files('backend.c', 'input.c', 'loop.c', 'timing.c', 'utf8.c')
diff --git a/src/common/timing.c b/src/common/timing.c
index 3e3a705..17f71b5 100644
--- a/src/common/timing.c
+++ b/src/common/timing.c
@@ -17,6 +17,7 @@ void yasl_clock_presented(struct yasl_frame_clock *c, uint64_t now, bool sample)
c->refresh_ns += 0.125 * (dt - c->refresh_ns);
}
c->last_present_ns = now;
+ c->source = YASL_TIMING_SUBMISSION;
}
void yasl_clock_note_resize(struct yasl_frame_clock *c, uint64_t dt_ns)
diff --git a/src/wayland/buffer.c b/src/wayland/buffer.c
index 76f6c0e..3f9cf84 100644
--- a/src/wayland/buffer.c
+++ b/src/wayland/buffer.c
@@ -278,8 +278,10 @@ static void frame_done(void *data, struct wl_callback *cb, uint32_t ms)
w->frame_cb = NULL;
uint64_t now = yasl_loop_now_ns();
- if (!w->conn->pres_clock_known)
+ if (!w->conn->pres_clock_known) {
yasl_clock_presented(&w->clock, yasl_clock_extend_ms(ms, now), w->sample_refresh);
+ w->clock.source = YASL_TIMING_FRAME_CALLBACK;
+ }
if (w->want_draw)
wayland_surface_schedule_redraw(w);
@@ -358,6 +360,7 @@ static void presented(void *data, struct wp_presentation_feedback *fb,
(((uint64_t)tv_sec_hi << 32) | tv_sec_lo) * 1000000000ull + tv_nsec);
if (refresh) w->clock.refresh_ns = refresh;
w->clock.last_present_ns = ns;
+ w->clock.source = YASL_TIMING_PRESENTATION;
}
(void)fb;
feedback_destroy(feedback);
@@ -444,7 +447,7 @@ bool wayland_surface_present(struct wayland_surface *w, const struct yasl_frameb
if (!w || w->closed || !w->frame_active || w->frame_presented || !fb ||
!damage || fb->pixels != w->frame.target.pixels ||
fb->size.x != w->width || fb->size.y != w->height ||
- fb->buffer != w->cur)
+ fb->buffer != w->cur || fb->native_drawable || fb->transaction)
return false;
w->damage = *damage;
@@ -485,6 +488,7 @@ void surface_draw_frame(struct wayland_surface *w)
w->frame = (struct yasl_frame){
.surface = &w->base,
.target = {
+ .generation = yasl_frame_begin(&w->base),
.pixels = w->buffers[idx].pixels,
.size = { .x = w->width, .y = w->height },
.buffer = idx,
@@ -544,6 +548,7 @@ struct yasl_timing wayland_surface_timing(const struct wayland_surface *w)
.last_present_ns = w->clock.last_present_ns,
.next_present_ns = yasl_clock_next_present_ns(&w->clock),
.frames_skipped = w->frames_skipped,
+ .source = w->clock.source,
};
yasl_clock_resize_stats(&w->clock, &t.resize);
return t;
diff --git a/src/wayland/clipboard.c b/src/wayland/clipboard.c
index 0e6ce33..7df1a70 100644
--- a/src/wayland/clipboard.c
+++ b/src/wayland/clipboard.c
@@ -134,10 +134,8 @@ static int transfer_io(int fd, short events, void *ud)
memcpy(t->text + t->len, buf, (size_t)n);
t->len += n;
} else if (n == 0) {
- if (t->len) {
- t->owner->receive(t->text, t->len, t->uri_list,
- t->drop_surface, t->owner->ud);
- }
+ t->owner->receive(t->text ? t->text : "", t->len, t->uri_list,
+ t->drop_surface, t->owner->ud);
transfer_end(t, true);
} else if (errno != EAGAIN && errno != EINTR) transfer_destroy(t);
}
@@ -556,24 +554,22 @@ void clipboard_copy(struct clipboard *c, const char *text, uint32_t serial)
wl_data_device_set_selection(c->device, c->source, serial);
}
-void clipboard_paste(struct clipboard *c)
+void clipboard_paste(struct clipboard *c, struct wl_surface *target)
{
if (!c || !c->device || !c->selection || !c->selection->mime) return;
int fds[2];
if (pipe2(fds, O_CLOEXEC) < 0) return;
wl_data_offer_receive(c->selection->proxy, c->selection->mime, fds[1]);
close(fds[1]);
- start_transfer(c, fds[0], false, false);
+ struct transfer *t = start_transfer(c, fds[0], false, false);
+ if (t) t->drop_surface = target;
wl_display_flush(c->display);
}
void clipboard_copy_primary(struct clipboard *c, const char *text, uint32_t serial)
{
if (!c || !text || !serial) return;
- if (!c->primary_device || !c->primary_manager) {
- clipboard_copy(c, text, serial);
- return;
- }
+ if (!c->primary_device || !c->primary_manager) return;
char *copy = strdup(text);
if (!copy) return;
if (c->primary_source) zwp_primary_selection_source_v1_destroy(c->primary_source);
@@ -589,7 +585,7 @@ void clipboard_copy_primary(struct clipboard *c, const char *text, uint32_t seri
zwp_primary_selection_device_v1_set_selection(c->primary_device, c->primary_source, serial);
}
-void clipboard_paste_primary(struct clipboard *c)
+void clipboard_paste_primary(struct clipboard *c, struct wl_surface *target)
{
if (!c) return;
if (c->primary_device && c->primary_selection && c->primary_selection->mime) {
@@ -598,11 +594,21 @@ void clipboard_paste_primary(struct clipboard *c)
zwp_primary_selection_offer_v1_receive(c->primary_selection->proxy,
c->primary_selection->mime, fds[1]);
close(fds[1]);
- start_transfer(c, fds[0], false, false);
+ struct transfer *t = start_transfer(c, fds[0], false, false);
+ if (t) t->drop_surface = target;
wl_display_flush(c->display);
return;
}
- clipboard_paste(c);
+}
+
+void clipboard_forget_surface(struct clipboard *c, struct wl_surface *surface)
+{
+ if (!c || !surface) return;
+ for (struct transfer *t = c->transfers, *next; t; t = next) {
+ next = t->next;
+ if (t->drop_surface == surface) transfer_destroy(t);
+ }
+ if (c->drag_surface == surface) c->drag_surface = NULL;
}
void clipboard_destroy(struct clipboard *c)
diff --git a/src/wayland/clipboard.h b/src/wayland/clipboard.h
index 6ea75c6..9fefa8e 100644
--- a/src/wayland/clipboard.h
+++ b/src/wayland/clipboard.h
@@ -24,9 +24,10 @@ void clipboard_destroy(struct clipboard *);
bool clipboard_set_seat(struct clipboard *, struct wl_seat *);
void clipboard_set_primary_manager(struct clipboard *, struct zwp_primary_selection_device_manager_v1 *);
void clipboard_copy(struct clipboard *, const char *, uint32_t);
-void clipboard_paste(struct clipboard *);
+void clipboard_paste(struct clipboard *, struct wl_surface *target);
+void clipboard_forget_surface(struct clipboard *, struct wl_surface *);
void clipboard_copy_primary(struct clipboard *, const char *, uint32_t);
-void clipboard_paste_primary(struct clipboard *);
+void clipboard_paste_primary(struct clipboard *, struct wl_surface *target);
typedef void (*clipboard_drag_fn)(enum yasl_drag_event, struct wl_surface *on,
double x, double y, void *);
diff --git a/src/wayland/drag.c b/src/wayland/drag.c
index 7c15002..989dbb6 100644
--- a/src/wayland/drag.c
+++ b/src/wayland/drag.c
@@ -90,11 +90,11 @@ static void drag_clear(struct wayland_display *c, bool ended)
static void drag_finish(struct wayland_display *c, enum yasl_drag_end how, bool ended)
{
- const struct yasl_drag_listener *l = c->drag.listener;
+ struct yasl_drag_listener l = c->drag.listener;
void *ud = c->drag.ud;
drag_clear(c, ended);
- if (l && l->end)
- l->end(ud, how);
+ if (l.end)
+ l.end(ud, how);
}
void conn_drag_end(struct wayland_display *c)
@@ -111,7 +111,7 @@ static void source_cancelled(void *ud, struct wl_data_source *s)
{
(void)s;
struct wayland_display *c = ud;
- bool released = c->drag.dropped || !c->drag.carrying;
+ bool released = c->drag.dropped;
drag_finish(c, released ? YASL_DRAG_DROPPED : YASL_DRAG_CANCELLED, true);
}
@@ -171,7 +171,7 @@ bool wayland_display_drag_start(struct wayland_display *c, struct wayland_surfac
}
c->drag.source = s;
c->drag.seat = seat;
- c->drag.listener = l;
+ c->drag.listener = *l;
c->drag.ud = ud;
wl_data_source_add_listener(s, &source_listener, c);
wl_data_source_offer(s, clipboard_drag_mime());
@@ -190,7 +190,7 @@ bool wayland_display_drag_start(struct wayland_display *c, struct wayland_surfac
void wayland_display_drag_cancel(struct wayland_display *c)
{
if (c && c->drag.source)
- drag_clear(c, false);
+ drag_finish(c, YASL_DRAG_CANCELLED, false);
}
bool wayland_surface_drag_carry(struct wayland_surface *w, struct yasl_vector2i offset)
diff --git a/src/wayland/internal.h b/src/wayland/internal.h
index cba5096..ccfd2dc 100644
--- a/src/wayland/internal.h
+++ b/src/wayland/internal.h
@@ -100,7 +100,7 @@ struct conn_drag {
struct wl_data_source *source;
struct xdg_toplevel_drag_v1 *toplevel;
struct drag_icon icon;
- const struct yasl_drag_listener *listener;
+ struct yasl_drag_listener listener;
void *ud;
struct seat *seat;
uint32_t scale_120;
@@ -192,36 +192,7 @@ struct seat {
bool touch_scrolling;
};
-#define YASL_QUEUE_CAP 256
-#define YASL_QUEUE_MASK (YASL_QUEUE_CAP - 1)
-struct input_queue {
- struct yasl_input_event events[YASL_QUEUE_CAP];
- int head;
- int tail;
- int count;
-};
-
-static inline void input_queue_push(struct input_queue *q, const struct yasl_input_event *ev)
-{
- if (unlikely(q->count >= YASL_QUEUE_CAP)) return;
- q->events[q->tail] = *ev;
- q->tail = (q->tail + 1) & YASL_QUEUE_MASK;
- q->count++;
-}
-
-static inline bool input_queue_pop(struct input_queue *q, struct yasl_input_event *ev)
-{
- if (unlikely(q->count <= 0)) return false;
- *ev = q->events[q->head];
- q->head = (q->head + 1) & YASL_QUEUE_MASK;
- q->count--;
- return true;
-}
-
-static inline void input_queue_clear(struct input_queue *q)
-{
- q->head = q->tail = q->count = 0;
-}
+#include "common/input.h"
struct wayland_display {
struct yasl_display base;
@@ -257,7 +228,6 @@ struct wayland_display {
struct xdg_toplevel_drag_manager_v1 *toplevel_drag_mgr;
struct conn_drag drag;
struct wayland_surface *surfaces;
- struct wayland_surface *paste_target;
struct wayland_surface *dead;
/* 4-byte integers */
diff --git a/src/wayland/keyboard.c b/src/wayland/keyboard.c
index 6bdd58c..f7e2691 100644
--- a/src/wayland/keyboard.c
+++ b/src/wayland/keyboard.c
@@ -374,11 +374,11 @@ void wayland_display_paste(struct wayland_display *c, enum yasl_selection sel, s
struct seat *s = c ? conn_active_seat(c) : NULL;
if (!s || !s->clipboard)
return;
- c->paste_target = target;
+ if (!target || target->closed) return;
if (sel == YASL_PRIMARY)
- clipboard_paste_primary(s->clipboard);
+ clipboard_paste_primary(s->clipboard, target->surface);
else
- clipboard_paste(s->clipboard);
+ clipboard_paste(s->clipboard, target->surface);
}
bool wayland_surface_input_poll(struct wayland_surface *w, struct yasl_input_event *ev)
diff --git a/src/wayland/pointer.c b/src/wayland/pointer.c
index d434047..3b3ad73 100644
--- a/src/wayland/pointer.c
+++ b/src/wayland/pointer.c
@@ -1032,10 +1032,9 @@ static void clipboard_received(const char *text, size_t len, bool uri_list,
{
struct seat *s = ud;
struct wayland_display *c = s->conn;
- struct wayland_surface *w = on ? surface_root(conn_surface_for(c, on)) : NULL;
- if (!w)
- w = c->paste_target ? c->paste_target : s->keyboard_focus;
- if (!len || !w) return;
+ struct wayland_surface *w = conn_surface_for(c, on);
+ if (uri_list) w = surface_root(w);
+ if (!w || w->closed) return;
struct yasl_input_event ev = {
.surface = &w->base,
.kind = uri_list ? YASL_INPUT_DROP_FILES : YASL_INPUT_PASTE,
@@ -1216,6 +1215,7 @@ void seat_bind(struct wayland_display *c, struct wl_registry *registry,
void seat_forget_surface(struct seat *s, struct wayland_surface *w)
{
+ clipboard_forget_surface(s->clipboard, w->surface);
struct wayland_surface **refs[] = {
&s->pointer_focus, &s->keyboard_focus,
&s->tablet_focus, &s->ime_focus, &s->focus_leaving, &s->ptr_left,
diff --git a/src/wayland/surface.c b/src/wayland/surface.c
index 8de4992..ef355ab 100644
--- a/src/wayland/surface.c
+++ b/src/wayland/surface.c
@@ -128,7 +128,7 @@ static inline bool display_has_immediate_work(const struct wayland_display *c)
if (unlikely(!c || c->lost)) return false;
for (struct wayland_surface *w = c->surfaces; w; w = w->conn_next) {
bool ready_draw = w->want_draw & (w->frame_cb == NULL) & w->configured;
- bool has_input = (w->input.count > 0);
+ bool has_input = (w->input.count > 0 || w->input.reset_pending);
if (unlikely((ready_draw | has_input) & !w->closed))
return true;
}
@@ -250,7 +250,8 @@ struct wayland_display *wayland_display_open(struct yasl_loop *loop, const struc
}
if (!c->loop) goto err;
if (spec) {
- c->cbs = spec->cbs;
+ c->base.callbacks = spec->cbs ? *spec->cbs : (struct yasl_display_cbs){0};
+ c->cbs = &c->base.callbacks;
c->ud = spec->ud;
if (spec->app_id) c->app_id = strdup(spec->app_id);
}
@@ -288,7 +289,7 @@ void wayland_display_close(struct wayland_display *c)
uint32_t wayland_display_caps(const struct wayland_display *c)
{
if (!c) return 0;
- uint32_t caps = YASL_CAP_POPUPS | YASL_CAP_CSD;
+ uint32_t caps = YASL_CAP_POPUPS | YASL_CAP_CSD | YASL_CAP_FRAME_PACING;
struct seat *s = conn_active_seat(c);
const struct wl_data_device *device = s ? clipboard_data_device(s->clipboard) : NULL;
if (device && wl_data_device_get_version((struct wl_data_device *)device) >= 3)
@@ -318,8 +319,6 @@ static void conn_unlink(struct wayland_surface *w)
break;
}
}
- if (c->paste_target == w)
- c->paste_target = NULL;
for (struct seat *s = c->seats; s; s = s->next)
seat_forget_surface(s, w);
w->conn = NULL;
@@ -397,8 +396,8 @@ err:
void wayland_surface_set_callbacks(struct wayland_surface *w, const struct yasl_surface_cbs *cbs, void *ud)
{
if (w && !w->closed) {
- static const struct yasl_surface_cbs no_callbacks = {0};
- w->cbs = cbs ? cbs : &no_callbacks;
+ w->base.callbacks = cbs ? *cbs : (struct yasl_surface_cbs){0};
+ w->cbs = &w->base.callbacks;
w->ud = ud;
}
}
diff --git a/src/x11/window.c b/src/x11/window.c
index 4680d3f..e40db6e 100644
--- a/src/x11/window.c
+++ b/src/x11/window.c
@@ -1,5 +1,6 @@
#define _POSIX_C_SOURCE 200809L
#include "common/backend.h"
+#include "common/input.h"
#include <X11/Xatom.h>
#include <X11/XKBlib.h>
@@ -16,7 +17,6 @@
#include <stdlib.h>
#include <string.h>
-#define INPUT_CAP 256
#define TEXT_LIMIT (16 * 1024 * 1024)
#ifndef likely
@@ -38,10 +38,7 @@ struct x11_surface {
struct yasl_vector2i size;
const struct yasl_surface_cbs *cbs;
void *ud;
- struct yasl_input_event events[INPUT_CAP];
- char *event_text[INPUT_CAP];
- char *last_text;
- unsigned head, count;
+ struct input_queue input;
struct yasl_frame frame;
struct yasl_damage_list damage;
struct yasl_frame_clock clock;
@@ -49,6 +46,12 @@ struct x11_surface {
bool want_draw, drawing, presented, destroyed;
};
+struct paste_request {
+ struct paste_request *next;
+ struct x11_surface *target;
+ Window window;
+};
+
struct x11_display {
struct yasl_display base;
Display *native;
@@ -62,7 +65,7 @@ struct x11_display {
Atom protocols, delete_window, utf8, clipboard, targets, paste_property;
Atom net_name, net_state, fullscreen, maximized_h, maximized_v;
char *selection[2];
- struct x11_surface *paste_target;
+ struct paste_request *pastes;
uint32_t mods;
char *app_id;
bool owns_loop;
@@ -86,25 +89,18 @@ static void leave(struct x11_display *d)
static void push(struct x11_surface *s, struct yasl_input_event event,
const char *text, size_t len)
{
- if (s->destroyed || s->count == INPUT_CAP) return;
- unsigned slot = (s->head + s->count) % INPUT_CAP;
+ if (s->destroyed) return;
if (text) {
- char *copy = malloc(len + 1);
- if (!copy) return;
- memcpy(copy, text, len);
- copy[len] = 0;
- s->event_text[slot] = copy;
if (event.kind == YASL_INPUT_TEXT) {
- event.text.text = copy;
+ event.text.text = text;
event.text.len = len;
} else {
- event.paste.text = copy;
+ event.paste.text = text;
event.paste.len = len;
}
}
event.surface = &s->base;
- s->events[slot] = event;
- s->count++;
+ input_queue_push(&s->input, &event);
}
static uint32_t modifiers(unsigned state)
@@ -149,6 +145,23 @@ static bool resize(struct x11_surface *s, struct yasl_vector2i size)
return true;
}
+static void cancel_pastes(struct x11_display *d, struct x11_surface *target)
+{
+ for (struct paste_request **p = &d->pastes; *p;) {
+ struct paste_request *r = *p;
+ if (!target) {
+ *p = r->next;
+ XDestroyWindow(d->native, r->window);
+ free(r);
+ } else {
+ /* Keep the requestor alive until SelectionNotify: the owner may
+ * still be writing to its property. Never retain a dead target. */
+ if (r->target == target) r->target = NULL;
+ p = &r->next;
+ }
+ }
+}
+
static void surface_destroy(struct yasl_surface *base)
{
struct x11_surface *s = (struct x11_surface *)base;
@@ -158,14 +171,13 @@ static void surface_destroy(struct yasl_surface *base)
for (struct x11_surface **p = &d->surfaces; *p; p = &(*p)->next) {
if (*p == s) { *p = s->next; break; }
}
- if (d->paste_target == s) d->paste_target = NULL;
+ cancel_pastes(d, s);
if (s->ic) XDestroyIC(s->ic);
if (s->image) XDestroyImage(s->image);
free(s->pixels);
if (s->gc) XFreeGC(d->native, s->gc);
if (s->window) XDestroyWindow(d->native, s->window);
- for (unsigned i = 0; i < INPUT_CAP; i++) free(s->event_text[i]);
- free(s->last_text);
+ input_queue_clear(&s->input);
if (d->depth) {
s->dead_next = d->dead;
d->dead = s;
@@ -177,7 +189,8 @@ static void set_callbacks(struct yasl_surface *base,
{
struct x11_surface *s = (struct x11_surface *)base;
if (s->destroyed) return;
- s->cbs = cbs;
+ s->base.callbacks = cbs ? *cbs : (struct yasl_surface_cbs){0};
+ s->cbs = &s->base.callbacks;
s->ud = ud;
}
@@ -295,14 +308,22 @@ static void selection_request(struct x11_display *d, XSelectionRequestEvent *req
static void selection_notify(struct x11_display *d, XSelectionEvent *event)
{
- struct x11_surface *s = d->paste_target;
- d->paste_target = NULL;
- if (!s || event->property == None) return;
+ struct paste_request **p = &d->pastes;
+ while (*p && (*p)->window != event->requestor) p = &(*p)->next;
+ if (!*p) return;
+ struct paste_request *r = *p;
+ *p = r->next;
+ struct x11_surface *s = r->target;
+ if (!s || event->property == None) {
+ XDestroyWindow(d->native, r->window);
+ free(r);
+ return;
+ }
Atom actual;
int format;
unsigned long count, left;
unsigned char *data = NULL;
- if (XGetWindowProperty(d->native, d->selection_window, event->property,
+ if (XGetWindowProperty(d->native, r->window, event->property,
0, TEXT_LIMIT / 4, True, AnyPropertyType,
&actual, &format, &count, &left, &data) == Success &&
(actual == d->utf8 || actual == XA_STRING) && format == 8 && !left) {
@@ -310,6 +331,8 @@ static void selection_notify(struct x11_display *d, XSelectionEvent *event)
(const char *)data, count);
}
if (data) XFree(data);
+ XDestroyWindow(d->native, r->window);
+ free(r);
}
static void key_event(struct x11_surface *s, XKeyEvent *key, bool repeat)
@@ -458,6 +481,7 @@ static bool present(struct yasl_surface *base, const struct yasl_framebuffer *fb
struct x11_surface *s = (struct x11_surface *)base;
if (s->destroyed || !s->drawing || s->presented || !fb || !damage ||
fb->pixels != s->pixels || fb->size.x != s->size.x || fb->size.y != s->size.y ||
+ fb->buffer != s->frame.target.buffer || fb->native_drawable || fb->transaction ||
damage->n < -1 || damage->n > YASL_DAMAGE_MAX) return false;
struct x11_display *d = display_of(base);
XImage *im = s->image;
@@ -502,7 +526,8 @@ static void flush(struct yasl_display *base)
s->damage = (struct yasl_damage_list){0};
s->frame = (struct yasl_frame){
.surface = &s->base,
- .target = {.pixels = s->pixels, .size = s->size, .damage = &s->damage},
+ .target = {.generation = yasl_frame_begin(&s->base),
+ .pixels = s->pixels, .size = s->size, .damage = &s->damage},
};
s->cbs->draw(&s->base, &s->frame, s->ud);
s->drawing = false;
@@ -537,6 +562,7 @@ static struct yasl_display *display_open(struct yasl_loop *loop,
struct x11_display *d = calloc(1, sizeof(*d));
if (!d) return NULL;
d->base.ops = &yasl_x11_backend;
+ d->base.callbacks = spec && spec->cbs ? *spec->cbs : (struct yasl_display_cbs){0};
d->native = XOpenDisplay(NULL);
if (!d->native) { free(d); errno = ENODEV; return NULL; }
d->screen = DefaultScreen(d->native);
@@ -547,7 +573,13 @@ static struct yasl_display *display_open(struct yasl_loop *loop,
d->loop = loop;
}
d->app_id = strdup(spec && spec->app_id ? spec->app_id : "YASL");
- if (!d->app_id) { XCloseDisplay(d->native); free(d); return NULL; }
+ if (!d->loop || !d->app_id) {
+ if (d->owns_loop) yasl_loop_destroy(d->loop);
+ free(d->app_id);
+ XCloseDisplay(d->native);
+ free(d);
+ return NULL;
+ }
d->im = XOpenIM(d->native, NULL, NULL, NULL);
d->selection_window = XCreateSimpleWindow(d->native,
RootWindow(d->native, d->screen), 0, 0, 1, 1, 0, 0, 0);
@@ -583,6 +615,7 @@ static void display_close(struct yasl_display *base)
{
struct x11_display *d = (struct x11_display *)base;
assert(!d->surfaces && "destroy every surface before its display");
+ cancel_pastes(d, NULL);
if (d->loop && d->watch > 0) yasl_loop_del_fd(d->loop, d->watch);
if (d->owns_loop && d->loop) yasl_loop_destroy(d->loop);
if (d->im) XCloseIM(d->im);
@@ -623,9 +656,15 @@ static void paste(struct yasl_display *base, enum yasl_selection sel, struct yas
const char *text = d->selection[sel];
if (text) push(s, (struct yasl_input_event){.kind = YASL_INPUT_PASTE}, text, strlen(text));
} else {
- d->paste_target = s;
+ struct paste_request *r = calloc(1, sizeof(*r));
+ if (!r) return;
+ r->window = XCreateSimpleWindow(d->native, RootWindow(d->native, d->screen),
+ 0, 0, 1, 1, 0, 0, 0);
+ r->target = s;
+ r->next = d->pastes;
+ d->pastes = r;
XConvertSelection(d->native, selection, d->utf8, d->paste_property,
- d->selection_window, CurrentTime);
+ r->window, CurrentTime);
XFlush(d->native);
}
}
@@ -633,14 +672,7 @@ static void paste(struct yasl_display *base, enum yasl_selection sel, struct yas
static bool input_poll(struct yasl_surface *base, struct yasl_input_event *event)
{
struct x11_surface *s = (struct x11_surface *)base;
- if (!event || s->destroyed || !s->count) return false;
- free(s->last_text);
- s->last_text = s->event_text[s->head];
- s->event_text[s->head] = NULL;
- *event = s->events[s->head];
- s->head = (s->head + 1) % INPUT_CAP;
- s->count--;
- return true;
+ return !s->destroyed && input_queue_pop(&s->input, event);
}
static void schedule_redraw(struct yasl_surface *base)
@@ -667,7 +699,8 @@ static struct yasl_timing timing(const struct yasl_surface *base)
const struct x11_surface *s = (const struct x11_surface *)base;
struct yasl_timing t = {.refresh_ns = s->clock.refresh_ns,
.last_present_ns = s->clock.last_present_ns,
- .next_present_ns = yasl_clock_next_present_ns(&s->clock)};
+ .next_present_ns = yasl_clock_next_present_ns(&s->clock),
+ .source = s->clock.source};
yasl_clock_resize_stats(&s->clock, &t.resize);
return t;
}