diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/cocoa/window.m | 67 | ||||
| -rw-r--r-- | src/common/backend.c | 57 | ||||
| -rw-r--r-- | src/common/backend.h | 9 | ||||
| -rw-r--r-- | src/common/input.c | 103 | ||||
| -rw-r--r-- | src/common/input.h | 19 | ||||
| -rw-r--r-- | src/common/meson.build | 2 | ||||
| -rw-r--r-- | src/common/timing.c | 1 | ||||
| -rw-r--r-- | src/wayland/buffer.c | 9 | ||||
| -rw-r--r-- | src/wayland/clipboard.c | 32 | ||||
| -rw-r--r-- | src/wayland/clipboard.h | 5 | ||||
| -rw-r--r-- | src/wayland/drag.c | 12 | ||||
| -rw-r--r-- | src/wayland/internal.h | 34 | ||||
| -rw-r--r-- | src/wayland/keyboard.c | 6 | ||||
| -rw-r--r-- | src/wayland/pointer.c | 8 | ||||
| -rw-r--r-- | src/wayland/surface.c | 13 | ||||
| -rw-r--r-- | src/x11/window.c | 109 |
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 : ∅ + 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(©, 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; } |
