diff options
Diffstat (limited to 'src/common')
| -rw-r--r-- | src/common/app.c | 468 | ||||
| -rw-r--r-- | src/common/backend.c | 222 | ||||
| -rw-r--r-- | src/common/backend.h | 45 | ||||
| -rw-r--r-- | src/common/events.c | 372 | ||||
| -rw-r--r-- | src/common/input.c | 13 | ||||
| -rw-r--r-- | src/common/input.h | 4 | ||||
| -rw-r--r-- | src/common/meson.build | 2 |
7 files changed, 735 insertions, 391 deletions
diff --git a/src/common/app.c b/src/common/app.c index 435d020..63cf2ce 100644 --- a/src/common/app.c +++ b/src/common/app.c @@ -1,451 +1,153 @@ #include "yasl/app.h" #include "common/app_internal.h" #include "common/backend.h" -#include "platform/sync.h" #include <errno.h> -#include <stdlib.h> -#include <string.h> - -struct user_event_node { - struct yasl_surface *surface; - uint32_t code; - void *payload; - size_t size; - struct user_event_node *next; -}; - -/* `surface` is NULL once the surface is destroyed. A callback may still - * hold the entry then, so only app_runner_free_dead() frees it. */ -struct app_surface { - struct yasl_surface *surface; - struct app_runner *runner; - yasl_pacing_mode pacing; - uint32_t last_scale_120; - uint64_t frame_index; - bool presented; - struct app_surface *next; -}; struct app_runner { struct yasl_display *display; const struct yasl_app_desc *desc; - bool running; - bool failed; - bool had_surfaces; - int surface_count; - struct app_surface *surfaces; - - yasl_mutex user_event_mutex; - struct user_event_node *user_event_head; - struct user_event_node *user_event_tail; + uint64_t initial_window; + bool running, failed, had_surfaces; + int error; }; -/* Deliver one event to the application; a QUIT result stops the runner. */ -static void emit(struct app_runner *ar, const struct yasl_event *ev) +void yasl_app_exit(struct yasl_display *d, enum yasl_app_result result) { - if (ar->desc->event && ar->desc->event(ar->desc->userdata, ev) == YASL_APP_QUIT) - ar->running = false; -} - -static void app_runner_surface_draw(struct yasl_surface *s, struct yasl_frame *frame, void *ud) -{ - struct app_surface *as = ud; - struct app_runner *ar = as ? as->runner : NULL; - if (!ar || !ar->desc || !ar->desc->frame) - return; - - as->presented = false; - - struct yasl_frame_context ctx = { - .surface = s, - .frame_index = as->frame_index++, - .target_time_ns = yasl_surface_frame_time_ns(s, yasl_loop_now_ns()), - .size = frame->target.size, - .scale_120 = s->scale_120, - .cpu = { - .pixels = frame->target.pixels, - .stride_bytes = frame->target.size.x * (int)sizeof(uint32_t), - }, - .gpu = { - .native_layer = NULL, - }, - .internal_handle = frame, - }; - - struct yasl_native_frame nf; - if (yasl_surface_get_native_frame(s, &frame->target, &nf)) { - ctx.gpu.native_layer = nf.drawable; - } - - ar->desc->frame(ar->desc->userdata, s, &ctx); - - if (as->surface && as->pacing == YASL_PACING_CONTINUOUS && ar->running) - yasl_surface_schedule_redraw(s); + if (!d || !d->app_runner || result == YASL_APP_CONTINUE) return; + struct app_runner *runner = d->app_runner; + runner->running = false; + if (result == YASL_APP_ERROR) { runner->failed = true; runner->error = EIO; } } -static void app_runner_surface_configure(struct yasl_surface *s, struct yasl_geometry geom, void *ud) +struct yasl_surface *yasl_app_window(struct yasl_display *d) { - struct app_surface *as = ud; - struct app_runner *ar = as ? as->runner : NULL; - if (!ar || !ar->desc || !ar->desc->event) - return; - - struct yasl_event ev = { - .type = YASL_EVENT_WINDOW_RESIZE, - .surface = s, - .time_ns = yasl_loop_now_ns(), - .resize = { - .geometry = geom, - .scale_changed = as->last_scale_120 && as->last_scale_120 != geom.scale_120, - }, - }; - as->last_scale_120 = geom.scale_120; - emit(ar, &ev); - - if (as->surface) - yasl_surface_schedule_redraw(s); + if (!d || !d->app_runner) return NULL; + struct app_runner *runner = d->app_runner; + return runner->initial_window ? yasl_events_find(d, runner->initial_window) : NULL; } -static void app_runner_surface_close(struct yasl_surface *s, void *ud) +static void dispatch_events(struct app_runner *runner) { - struct app_surface *as = ud; - struct app_runner *ar = as ? as->runner : NULL; - if (!ar || !ar->desc) - return; - /* Without an event callback nothing could answer the request, so quit. */ - if (!ar->desc->event) { - ar->running = false; - return; + struct yasl_event event; + while (runner->running && yasl_display_poll_event(runner->display, &event)) { + if (runner->desc->event) + yasl_app_exit(runner->display, runner->desc->event(runner->desc->userdata, &event)); + else if (event.type == YASL_EVENT_WINDOW_CLOSE) + yasl_app_exit(runner->display, YASL_APP_QUIT); + if (event.type == YASL_EVENT_DISPLAY_QUIT) + yasl_app_exit(runner->display, YASL_APP_QUIT); + if (event.type == YASL_EVENT_DISPLAY_LOST) + yasl_app_exit(runner->display, YASL_APP_ERROR); } - - emit(ar, &(struct yasl_event){ - .type = YASL_EVENT_WINDOW_CLOSE, - .surface = s, - .time_ns = yasl_loop_now_ns(), - }); -} - -void app_runner_register_surface(void *runner, struct yasl_surface *s) -{ - if (!runner || !s) - return; - struct app_runner *ar = runner; - struct app_surface *as = calloc(1, sizeof(*as)); - if (!as) - return; - - as->surface = s; - as->runner = ar; - as->pacing = YASL_PACING_ON_DEMAND; - as->last_scale_120 = s->scale_120; - s->app_surface = as; - - as->next = ar->surfaces; - ar->surfaces = as; - ar->surface_count++; - ar->had_surfaces = true; - - struct yasl_window_cbs cbs = { - .draw = app_runner_surface_draw, - .configure = app_runner_surface_configure, - .close = app_runner_surface_close, - }; - yasl_surface_set_callbacks(s, &cbs, as); } -void app_runner_unregister_surface(void *runner, struct yasl_surface *s) +static void draw(struct yasl_surface *s, struct yasl_frame *frame, void *ud) { - if (!runner || !s || !s->app_surface) - return; - struct app_runner *ar = runner; - struct app_surface *as = s->app_surface; - as->surface = NULL; - s->app_surface = NULL; - ar->surface_count--; -} - -/* Call only when no callback runs. */ -static void app_runner_free_dead(struct app_runner *ar) -{ - for (struct app_surface **p = &ar->surfaces; *p;) { - struct app_surface *as = *p; - if (as->surface) { - p = &as->next; - continue; - } - *p = as->next; - free(as); - } + struct app_runner *runner = ud; + uint64_t id = s->id; + dispatch_events(runner); + /* Draining events can destroy the surface whose native draw is active. */ + struct yasl_surface *live = yasl_events_find(runner->display, id); + if (live && runner->running && runner->desc->frame) + runner->desc->frame(runner->desc->userdata, live, frame); } -bool yasl_frame_present_cpu(struct yasl_frame_context *ctx, - const struct yasl_rect2i *dirty_rects, - int count) +void app_runner_register_surface(void *data, struct yasl_surface *s) { - if (!ctx || !ctx->surface || !ctx->internal_handle) - return false; - - struct yasl_frame *frame = (struct yasl_frame *)ctx->internal_handle; - struct yasl_damage_list damage; - if (!dirty_rects || count <= 0) { - damage.n = -1; - damage.bounds = (struct yasl_rect2i){0}; - } else { - damage.n = count > YASL_DAMAGE_MAX ? YASL_DAMAGE_MAX : count; - damage.bounds = (struct yasl_rect2i){0}; - for (int i = 0; i < damage.n; i++) { - damage.rects[i] = dirty_rects[i]; - } - } - - return yasl_surface_present(ctx->surface, &frame->target, &damage); + struct app_runner *runner = data; + struct yasl_window_cbs callbacks = {.draw = draw}; + yasl_surface_set_callbacks(s, &callbacks, runner); + s->app_surface = runner; + runner->had_surfaces = true; } -void yasl_surface_set_pacing(struct yasl_surface *s, yasl_pacing_mode mode) +void app_runner_unregister_surface(void *data, struct yasl_surface *s) { - if (!s || !s->app_surface) - return; - struct app_surface *as = s->app_surface; - as->pacing = mode; - if (mode == YASL_PACING_CONTINUOUS) { - yasl_surface_schedule_redraw(s); - } + (void)data; + s->app_surface = NULL; } -void yasl_surface_request_frame(struct yasl_surface *s) +bool yasl_frame_present_cpu(struct yasl_frame *frame, const struct yasl_rect2i *rects, int count) { - if (!s) - return; - yasl_surface_schedule_redraw(s); - if (s->display && s->display->loop) { - yasl_loop_kick(s->display->loop); - } + /* Preserve the migration adapter's old NULL/nonpositive full-damage rule. */ + return yasl_frame_present(frame, count <= 0 ? NULL : rects, count <= 0 ? 0 : (size_t)count); } bool yasl_post_event(struct yasl_display *d, struct yasl_surface *s, uint32_t code, void *payload, size_t size) { - if (!d || !d->app_runner) - return false; - struct app_runner *ar = d->app_runner; - - struct user_event_node *node = malloc(sizeof(*node)); - if (!node) - return false; - node->surface = s; - node->code = code; - node->payload = payload; - node->size = size; - node->next = NULL; - - yasl_mutex_lock(&ar->user_event_mutex); - if (ar->user_event_tail) { - ar->user_event_tail->next = node; - ar->user_event_tail = node; - } else { - ar->user_event_head = node; - ar->user_event_tail = node; - } - yasl_mutex_unlock(&ar->user_event_mutex); - - if (d->loop) { - yasl_loop_kick(d->loop); - } - return true; + if (!d || !d->app_runner) { errno = EINVAL; return false; } + /* Legacy callers must keep s alive through this call. New code passes an ID. */ + return yasl_display_post(d, &(struct yasl_message){ + .target_id = s ? yasl_surface_id(s) : 0, .code = code, .payload = payload, .size = size, + }); } -int yasl_app_watch_fd(struct yasl_display *d, int fd, short poll_events, - int (*cb)(int fd, short revents, void *ud), void *ud) +int yasl_app_watch_fd(struct yasl_display *d, int fd, short events, + int (*cb)(int, short, void *), void *ud) { - if (!d || !d->loop) - return -1; - return yasl_loop_add_fd(d->loop, fd, poll_events, cb, ud); + if (!d || !d->loop) { errno = EINVAL; return -1; } + return yasl_loop_add_fd(d->loop, fd, events, cb, ud); } - -void yasl_app_unwatch_fd(struct yasl_display *d, int watch_id) +void yasl_app_unwatch_fd(struct yasl_display *d, int id) { - if (!d || !d->loop) - return; - yasl_loop_del_fd(d->loop, watch_id); + if (d && d->loop) yasl_loop_del_fd(d->loop, id); } - -yasl_timer_id yasl_app_add_timer(struct yasl_display *d, uint64_t interval_ns, - yasl_timer_cb cb, void *ud) +yasl_timer_id yasl_app_add_timer(struct yasl_display *d, uint64_t interval, yasl_timer_cb cb, void *ud) { - if (!d || !d->loop) - return 0; - return yasl_loop_add_timer(d->loop, interval_ns, cb, ud); + if (!d || !d->loop) return 0; + return yasl_loop_add_timer(d->loop, interval, cb, ud); } - void yasl_app_del_timer(struct yasl_display *d, yasl_timer_id id) { - if (!d || !d->loop) - return; - yasl_loop_del_timer(d->loop, id); -} - -static void app_runner_dispatch_user_events(struct app_runner *ar) -{ - yasl_mutex_lock(&ar->user_event_mutex); - struct user_event_node *node = ar->user_event_head; - ar->user_event_head = NULL; - ar->user_event_tail = NULL; - yasl_mutex_unlock(&ar->user_event_mutex); - - while (node && ar->running) { - struct user_event_node *next = node->next; - emit(ar, &(struct yasl_event){ - .type = YASL_EVENT_USER, - .surface = node->surface, - .time_ns = yasl_loop_now_ns(), - .user = {.code = node->code, .payload = node->payload, .size = node->size}, - }); - free(node); - node = next; - } - while (node) { - struct user_event_node *next = node->next; - free(node); - node = next; - } -} - -static void app_runner_dispatch_input_events(struct app_runner *ar) -{ - for (struct app_surface *as = ar->surfaces; as && ar->running; as = as->next) { - struct yasl_event ev; - while (ar->running && as->surface && yasl_surface_poll_event(as->surface, &ev)) { - if (!ev.surface) - ev.surface = as->surface; - emit(ar, &ev); - } - } -} - -static void app_runner_quit(void *ud) -{ - ((struct app_runner *)ud)->running = false; -} - -static void app_runner_lost(void *ud) -{ - struct app_runner *runner = ud; - runner->failed = true; - runner->running = false; + if (d && d->loop) yasl_loop_del_timer(d->loop, id); } int yasl_run(const struct yasl_app_desc *desc) { - if (!desc) - return -1; - - struct app_runner runner = {.desc = desc, .running = true}; - const struct yasl_display_cbs callbacks = { - .quit = app_runner_quit, .lost = app_runner_lost, - }; - struct yasl_display_spec dspec = { - .app_id = desc->app_id, - .backend = desc->backend, - .cbs = &callbacks, - .ud = &runner, - }; - struct yasl_display *d = yasl_display_open(NULL, &dspec); - if (!d) - return -1; - - runner.display = d; - int sync_error = yasl_mutex_init(&runner.user_event_mutex); - if (sync_error) { - yasl_display_close(d); - errno = sync_error; - return -1; - } + if (!desc) { errno = EINVAL; return -1; } + struct yasl_display *d = yasl_display_open(NULL, &(struct yasl_display_spec){ + .app_id = desc->app_id, .backend = desc->backend, + }); + if (!d) return -1; + struct app_runner runner = {.display = d, .desc = desc, .running = true}; d->app_runner = &runner; - if (desc->window) { struct yasl_surface *s = yasl_surface_create_toplevel(d, desc->window); if (!s) { - int create_error = errno; + int error = errno; d->app_runner = NULL; - yasl_mutex_destroy(&runner.user_event_mutex); yasl_display_close(d); - errno = create_error; + errno = error; return -1; } + runner.initial_window = s->id; yasl_surface_request_frame(s); } - - if (desc->init && desc->init(desc->userdata, d) == YASL_APP_QUIT) { - runner.running = false; - } - - uint32_t caps = yasl_display_caps(d); - bool has_frame_pacing = (caps & YASL_CAP_FRAME_PACING) != 0; - + if (desc->init) yasl_app_exit(d, desc->init(desc->userdata, d)); while (runner.running) { - app_runner_free_dead(&runner); - if (runner.had_surfaces && runner.surface_count == 0) { - runner.running = false; - break; - } - - bool any_continuous = false; - for (struct app_surface *as = runner.surfaces; as; as = as->next) { - if (as->pacing == YASL_PACING_CONTINUOUS) - any_continuous = true; - } - - int timeout_ms = -1; - if (any_continuous) { - timeout_ms = has_frame_pacing ? -1 : 16; - } - - int res = yasl_display_dispatch(d, timeout_ms); - if (res < 0) { + dispatch_events(&runner); + if (!runner.running || (runner.had_surfaces && !d->surfaces)) break; + if (yasl_display_dispatch(d, -1) < 0) { + int dispatch_error = errno; + dispatch_events(&runner); runner.failed = true; + runner.error = dispatch_error; break; } - - app_runner_dispatch_user_events(&runner); - app_runner_dispatch_input_events(&runner); - - if (runner.running) { - yasl_display_flush(d); - } - - if (!has_frame_pacing && any_continuous && runner.running) { - for (struct app_surface *as = runner.surfaces; as; as = as->next) { - if (as->surface && as->pacing == YASL_PACING_CONTINUOUS) { - yasl_surface_schedule_redraw(as->surface); - } - } - } - } - - if (desc->quit) { - desc->quit(desc->userdata, d); - } - - /* Destroy any surfaces that were not explicitly destroyed by the application */ - for (struct app_surface *as = runner.surfaces; as; as = as->next) { - if (as->surface) - yasl_surface_destroy(as->surface); - } - app_runner_free_dead(&runner); - + dispatch_events(&runner); + if (runner.running) yasl_display_flush(d); + } + int queue_error = yasl_events_error(d); + if (queue_error) { runner.failed = true; runner.error = queue_error; } + int error = runner.error; + if (desc->quit) desc->quit(desc->userdata, d); + /* Backend registries create children after parents: reverse creation order + * ensures child popups are destroyed before their parent. */ + while (d->surfaces) yasl_surface_destroy(d->surfaces); d->app_runner = NULL; - - yasl_mutex_lock(&runner.user_event_mutex); - struct user_event_node *node = runner.user_event_head; - while (node) { - struct user_event_node *next = node->next; - free(node); - node = next; - } - yasl_mutex_unlock(&runner.user_event_mutex); - yasl_mutex_destroy(&runner.user_event_mutex); - yasl_display_close(d); + if (runner.failed) errno = error; return runner.failed ? -1 : 0; } diff --git a/src/common/backend.c b/src/common/backend.c index bb5ae33..a81e1e7 100644 --- a/src/common/backend.c +++ b/src/common/backend.c @@ -1,6 +1,7 @@ #include "backend.h" #include "loop.h" #include "common/app_internal.h" +#include "common/utf8.h" #include <errno.h> #include <stdlib.h> @@ -33,6 +34,15 @@ static struct yasl_display *open_backend(enum yasl_backend backend, errno = 0; struct yasl_display *d = ops->display_open(loop, spec); if (!d && !errno) errno = ENODEV; + if (d) { + d->callback_ud = spec ? spec->ud : NULL; + if (!yasl_events_open(d)) { + int error = errno; + ops->display_close(d); + errno = error; + return NULL; + } + } return d; } @@ -70,6 +80,7 @@ void yasl_display_close(struct yasl_display *d) { if (!d || !d->ops->display_close) return; + yasl_events_close(d); d->ops->display_close(d); } @@ -90,8 +101,13 @@ int yasl_display_dispatch(struct yasl_display *d, int timeout_ms) errno = ENOTSUP; return -1; } + int queue_error = yasl_events_error(d); + if (queue_error) { errno = queue_error; return -1; } + if (yasl_events_pending(d)) timeout_ms = 0; errno = 0; int result = d->ops->display_dispatch_timeout(d, timeout_ms); + queue_error = yasl_events_error(d); + if (queue_error) { errno = queue_error; return -1; } if (result < 0 && !errno) errno = EIO; return result; } @@ -175,7 +191,14 @@ struct yasl_surface *yasl_create_toplevel(struct yasl_display *d, errno = 0; struct yasl_surface *s = d->ops->surface_create_toplevel(d, spec, graphics_device); if (!s && !errno) errno = ENODEV; + if (s && !yasl_events_register(s)) { + int error = errno; + d->ops->surface_destroy(s); + errno = error; + return NULL; + } if (s) { + s->userdata = spec->userdata; /* Native creation may dispatch events. Attach this window's callbacks * only after it finishes, so callers can store the returned surface. */ if (d->app_runner) @@ -218,8 +241,16 @@ struct yasl_surface *yasl_surface_create_popup(struct yasl_surface *parent, cons errno = 0; struct yasl_surface *s = parent->display->ops->surface_create_popup(parent, spec); if (!s && !errno) errno = ENODEV; - if (s && parent->display->app_runner) { - app_runner_register_surface(parent->display->app_runner, s); + if (s) { + s->popup = true; + if (!yasl_events_register(s)) { + int error = errno; + parent->display->ops->surface_destroy(s); + errno = error; + return NULL; + } + if (parent->display->app_runner) + app_runner_register_surface(parent->display->app_runner, s); } return s; } @@ -248,6 +279,7 @@ void yasl_surface_destroy(struct yasl_surface *s) if (s->app_surface && s->display->app_runner) { app_runner_unregister_surface(s->display->app_runner, s); } + yasl_events_unregister(s); yasl_surface_clear_timers(s); input_queue_clear(&s->input); s->display->ops->surface_destroy(s); @@ -258,14 +290,18 @@ struct yasl_display *yasl_surface_display(const struct yasl_surface *s) return s ? s->display : NULL; } -void yasl_surface_set_callbacks(struct yasl_surface *s, const struct yasl_window_cbs *cbs, void *ud) +bool yasl_surface_set_callbacks(struct yasl_surface *s, const struct yasl_window_cbs *cbs, void *ud) { - if (!s) - return; + if (!s) { errno = EINVAL; return false; } + if (s->display && s->display->app_runner && s->app_surface) { + errno = EBUSY; + return false; + } s->callbacks = cbs ? *cbs : (struct yasl_window_cbs){0}; s->ud = ud; if (s->display && s->display->ops && s->display->ops->surface_set_callbacks) s->display->ops->surface_set_callbacks(s, cbs, ud); + return true; } void yasl_surface_set_title(struct yasl_surface *s, const char *title) @@ -317,6 +353,8 @@ bool yasl_surface_poll_event(struct yasl_surface *s, struct yasl_event *ev) { if (!s || !ev) return false; + if (s->display && s->display->events) + return yasl_events_poll_surface(s, ev); if (s->display && s->display->ops && s->display->ops->surface_poll_event) return s->display->ops->surface_poll_event(s, ev); return input_queue_pop(&s->input, ev); @@ -525,3 +563,177 @@ struct yasl_loop *yasl_display_loop(struct yasl_display *d) { return d ? d->loop : NULL; } + +bool yasl_frame_present(struct yasl_frame *frame, const struct yasl_rect2i *rects, size_t count) +{ + if (!frame || !frame->surface || (!rects && count)) { errno = EINVAL; return false; } + struct yasl_surface *s = frame->surface; + if (!s->drawing || s->active_frame != frame) { errno = EINVAL; return false; } + if (s->frame_presented) { errno = EALREADY; return false; } + for (size_t i = 0; i < count; i++) { + if (rects[i].width < 0 || rects[i].height < 0) { errno = EINVAL; return false; } + } + struct yasl_damage_list damage = {0}; + if (!rects || count > YASL_DAMAGE_MAX) damage.n = -1; + else { + damage.n = (int)count; + for (size_t i = 0; i < count; i++) damage.rects[i] = rects[i]; + } + errno = 0; + bool ok = yasl_surface_present(s, &frame->target, &damage); + if (ok) s->frame_presented = true; + if (!ok && !errno) errno = EIO; + return ok; +} + +void yasl_surface_request_frame(struct yasl_surface *s) +{ + if (!s) return; + yasl_surface_schedule_redraw(s); + if (s->display->loop) yasl_loop_kick(s->display->loop); +} + +static uint64_t pacing_tick(void *ud, yasl_timer_id id, uint64_t interval) +{ + (void)id; (void)interval; + struct yasl_surface *s = ud; + yasl_surface_request_frame(s); + double refresh = yasl_surface_timing(s).refresh_ns; + return refresh >= 1000000.0 && refresh <= 1000000000.0 + ? (uint64_t)refresh : 16666667; +} + +bool yasl_surface_set_pacing(struct yasl_surface *s, yasl_pacing_mode mode) +{ + if (!s || (mode != YASL_PACING_ON_DEMAND && mode != YASL_PACING_CONTINUOUS)) { + errno = EINVAL; + return false; + } + if (s->pacing_timer) { + yasl_surface_del_timer(s, s->pacing_timer); + s->pacing_timer = 0; + } + if (mode == YASL_PACING_CONTINUOUS && !(yasl_display_caps(s->display) & YASL_CAP_FRAME_PACING)) { + s->pacing_timer = yasl_surface_add_timer(s, 16666667, pacing_tick, s); + if (!s->pacing_timer) { + s->pacing = YASL_PACING_ON_DEMAND; + errno = ENOMEM; + return false; + } + } + s->pacing = mode; + if (mode == YASL_PACING_CONTINUOUS) yasl_surface_request_frame(s); + return true; +} + +void yasl_surface_draw(struct yasl_surface *s, struct yasl_frame *frame) +{ + if (!s || !frame || !s->callbacks.draw) return; + struct yasl_display *d = s->display; + uint64_t id = s->id; + frame->surface = s; + frame->target.stride_bytes = frame->target.size.x * (int)sizeof(uint32_t); + frame->scale_120 = s->scale_120; + frame->frame_index = s->draw_index++; + frame->target_time_ns = yasl_surface_frame_time_ns(s, yasl_loop_now_ns()); + s->drawing = true; + s->frame_presented = false; + s->active_frame = frame; + s->callbacks.draw(s, frame, s->ud); + /* The callback may destroy s. Lookup by identity before accessing it. */ + struct yasl_surface *live = id ? yasl_events_find(d, id) : NULL; + if (live) { + live->drawing = false; + live->active_frame = NULL; + if (live->pacing == YASL_PACING_CONTINUOUS && + (yasl_display_caps(d) & YASL_CAP_FRAME_PACING)) + yasl_surface_schedule_redraw(live); + } +} + +static bool window_valid(struct yasl_surface *s) +{ + if (!s || s->popup) { errno = EINVAL; return false; } + return true; +} + +bool yasl_surface_set_size(struct yasl_surface *s, struct yasl_vector2i size) +{ + if (!window_valid(s)) return false; + if (size.x <= 0 || size.y <= 0 || size.x > 32767 || size.y > 32767 || + (int64_t)size.x * size.y * 4 > INT32_MAX) { errno = EINVAL; return false; } + if (!s->display->ops->surface_set_size) { errno = ENOTSUP; return false; } + errno = 0; + bool ok = s->display->ops->surface_set_size(s, size); + if (!ok && !errno) errno = EIO; + return ok; +} + +bool yasl_surface_set_fullscreen(struct yasl_surface *s, bool enabled) +{ + if (!window_valid(s)) return false; + if (!s->display->ops->surface_set_fullscreen) { errno = ENOTSUP; return false; } + errno = 0; + bool ok = s->display->ops->surface_set_fullscreen(s, enabled); + if (!ok && !errno) errno = EIO; + return ok; +} + +bool yasl_surface_set_maximized(struct yasl_surface *s, bool enabled) +{ + if (!window_valid(s)) return false; + if (!s->display->ops->surface_set_maximized) { errno = ENOTSUP; return false; } + errno = 0; + bool ok = s->display->ops->surface_set_maximized(s, enabled); + if (!ok && !errno) errno = EIO; + return ok; +} + +bool yasl_surface_minimize(struct yasl_surface *s) +{ + if (!window_valid(s)) return false; + if (!s->display->ops->surface_minimize) { errno = ENOTSUP; return false; } + errno = 0; + bool ok = s->display->ops->surface_minimize(s); + if (!ok && !errno) errno = EIO; + return ok; +} + +static bool selection_valid(struct yasl_display *d, enum yasl_selection selection) +{ + if (!d || (selection != YASL_CLIPBOARD && selection != YASL_PRIMARY)) { + errno = EINVAL; return false; + } + if (selection == YASL_PRIMARY && !(yasl_display_caps(d) & YASL_CAP_PRIMARY_SELECTION)) { + errno = ENOTSUP; return false; + } + return true; +} + +bool yasl_clipboard_set(struct yasl_display *d, enum yasl_selection selection, const char *text) +{ + if (!selection_valid(d, selection)) return false; + if (!text) { errno = EINVAL; return false; } + if (!yasl_utf8_validate(text, strlen(text))) { errno = EILSEQ; return false; } + if (!d->ops->display_copy_text) { errno = ENOTSUP; return false; } + errno = 0; + bool ok = d->ops->display_copy_text(d, selection, text); + if (!ok && !errno) errno = EIO; + return ok; +} + +uint64_t yasl_clipboard_request(struct yasl_surface *s, enum yasl_selection selection) +{ + if (!s) { errno = EINVAL; return 0; } + struct yasl_display *d = s->display; + if (!selection_valid(d, selection)) return 0; + if (!d->ops->display_request_paste) { errno = ENOTSUP; return 0; } + uint64_t id = yasl_events_request_id(d); + if (!id) return 0; + errno = 0; + if (!d->ops->display_request_paste(d, selection, s, id)) { + if (!errno) errno = EIO; + return 0; + } + return id; +} diff --git a/src/common/backend.h b/src/common/backend.h index 13ce9b5..876a4ca 100644 --- a/src/common/backend.h +++ b/src/common/backend.h @@ -10,6 +10,7 @@ #include <string.h> struct yasl_backend_ops; +struct yasl_event_state; struct yasl_display { const struct yasl_backend_ops *ops; @@ -19,6 +20,9 @@ struct yasl_display { void *app_runner; /* Backend's copy of yasl_display_spec.app_id, or NULL; for desktop services. */ const char *app_id; + void *callback_ud; + struct yasl_event_state *events; + struct yasl_surface *surfaces; }; struct yasl_surface { @@ -39,8 +43,41 @@ struct yasl_surface { int timer_count; int timer_capacity; void *app_surface; + struct yasl_surface *event_next; + uint64_t id; + void *userdata; + bool popup; + bool drawing; + bool frame_presented; + uint32_t reported_scale; + uint64_t draw_index; + struct yasl_frame *active_frame; + yasl_pacing_mode pacing; + yasl_timer_id pacing_timer; }; +bool yasl_events_open(struct yasl_display *d); +void yasl_events_close(struct yasl_display *d); +int yasl_events_error(struct yasl_display *d); +bool yasl_events_pending(struct yasl_display *d); +struct yasl_surface *yasl_events_find(struct yasl_display *d, uint64_t id); +bool yasl_events_register(struct yasl_surface *s); +void yasl_events_unregister(struct yasl_surface *s); +bool yasl_events_push(struct yasl_display *d, struct yasl_surface *queue_owner, + const struct yasl_event *event); +bool yasl_events_poll_surface(struct yasl_surface *s, struct yasl_event *event); +void yasl_surface_notify_configure(struct yasl_surface *s, struct yasl_geometry geometry); +void yasl_surface_notify_close(struct yasl_surface *s); +void yasl_surface_notify_state(struct yasl_surface *s, uint32_t state); +void yasl_surface_notify_drag(struct yasl_surface *s, enum yasl_drag_event ev, struct yasl_vector2i pos); +void yasl_surface_notify_drop(struct yasl_surface *s, enum yasl_drop_event ev, struct yasl_vector2i pos); +void yasl_surface_notify_composition(struct yasl_surface *s, const char *text, int begin, int end); +void yasl_surface_draw(struct yasl_surface *s, struct yasl_frame *frame); +void yasl_display_notify_event(struct yasl_display *d, enum yasl_event_type type); +void yasl_surface_complete_paste(struct yasl_surface *s, uint64_t request_id, + enum yasl_transfer_status status, const char *text, size_t len); +uint64_t yasl_events_request_id(struct yasl_display *d); + void yasl_surface_clear_timers(struct yasl_surface *s); struct yasl_surface *yasl_create_toplevel(struct yasl_display *d, const struct yasl_window_spec *spec, void *graphics_device); @@ -74,6 +111,9 @@ struct yasl_backend_ops { int (*display_double_click_ms)(const struct yasl_display *d); void (*display_copy)(struct yasl_display *d, enum yasl_selection sel, const char *text); void (*display_paste)(struct yasl_display *d, enum yasl_selection sel, struct yasl_surface *target); + bool (*display_copy_text)(struct yasl_display *d, enum yasl_selection sel, const char *text); + bool (*display_request_paste)(struct yasl_display *d, enum yasl_selection sel, + struct yasl_surface *target, uint64_t request_id); bool (*display_drag_start)(struct yasl_display *d, struct yasl_surface *origin, const struct yasl_drag_image *image, const struct yasl_drag_listener *l, void *ud); void (*display_drag_cancel)(struct yasl_display *d); /* Create with callbacks disabled; common code installs them after return. */ @@ -82,6 +122,11 @@ struct yasl_backend_ops { struct yasl_surface *(*surface_create_popup)(struct yasl_surface *parent, const struct yasl_popup_spec *spec); bool (*surface_popup_reposition)(struct yasl_surface *popup, const struct yasl_popup_spec *spec); void (*surface_destroy)(struct yasl_surface *s); + struct input_queue *(*surface_input_queue)(struct yasl_surface *s); + bool (*surface_set_size)(struct yasl_surface *s, struct yasl_vector2i size_logical); + bool (*surface_set_fullscreen)(struct yasl_surface *s, bool enabled); + bool (*surface_set_maximized)(struct yasl_surface *s, bool enabled); + bool (*surface_minimize)(struct yasl_surface *s); struct yasl_display *(*surface_display)(const struct yasl_surface *s); /* Copy only; must not invoke application callbacks. */ void (*surface_set_callbacks)(struct yasl_surface *s, const struct yasl_window_cbs *cbs, void *ud); diff --git a/src/common/events.c b/src/common/events.c new file mode 100644 index 0000000..a9bbcaf --- /dev/null +++ b/src/common/events.c @@ -0,0 +1,372 @@ +#include "backend.h" +#include "platform/sync.h" + +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +struct event_node { + struct yasl_event event; + uint64_t queue_id; + char *text; + void (*release)(void *); + struct event_node *next; +}; + +struct yasl_event_state { + yasl_mutex mutex; + struct event_node *head, *tail, *last; + uint64_t sequence, identity, request; + int error; +}; + +static void dispose(struct event_node *node) +{ + if (!node) return; + if (node->release) node->release(node->event.user.payload); + free(node->text); + free(node); +} + +static void dispose_list(struct event_node *nodes) +{ + while (nodes) { + struct event_node *next = nodes->next; + dispose(nodes); + nodes = next; + } +} + +bool yasl_events_open(struct yasl_display *d) +{ + struct yasl_event_state *state = calloc(1, sizeof(*state)); + if (!state) { errno = ENOMEM; return false; } + int error = yasl_mutex_init(&state->mutex); + if (error) { free(state); errno = error; return false; } + d->events = state; + return true; +} + +void yasl_events_close(struct yasl_display *d) +{ + struct yasl_event_state *state = d->events; + if (!state) return; + /* Producers must already have stopped. Detach before callbacks so cleanup + * can make harmless failed posts without touching a dying queue. */ + d->events = NULL; + dispose_list(state->head); + dispose(state->last); + yasl_mutex_destroy(&state->mutex); + free(state); +} + +int yasl_events_error(struct yasl_display *d) +{ + if (!d->events) return 0; + yasl_mutex_lock(&d->events->mutex); + int error = d->events->error; + yasl_mutex_unlock(&d->events->mutex); + return error; +} + +bool yasl_events_pending(struct yasl_display *d) +{ + if (!d->events) return false; + yasl_mutex_lock(&d->events->mutex); + bool pending = d->events->head != NULL; + yasl_mutex_unlock(&d->events->mutex); + return pending; +} + +struct yasl_surface *yasl_events_find(struct yasl_display *d, uint64_t id) +{ + for (struct yasl_surface *s = d->surfaces; s; s = s->event_next) + if (s->id == id) return s; + return NULL; +} + +static bool publish_input(const struct yasl_event *event, void *ud) +{ + struct yasl_surface *owner = ud; + return yasl_events_push(owner->display, owner, event); +} + +bool yasl_events_register(struct yasl_surface *s) +{ + struct yasl_display *d = s->display; + if (!d->events) return true; /* Internal test doubles need no queue. */ + yasl_mutex_lock(&d->events->mutex); + if (d->events->identity == UINT64_MAX) { + yasl_mutex_unlock(&d->events->mutex); + errno = EOVERFLOW; + return false; + } + s->reported_scale = s->scale_120; + s->id = ++d->events->identity; + s->event_next = d->surfaces; + d->surfaces = s; + yasl_mutex_unlock(&d->events->mutex); + struct input_queue *q = d->ops->surface_input_queue + ? d->ops->surface_input_queue(s) : &s->input; + if (q) { + /* Preserve input generated during native creation. */ + struct yasl_event event; + while (input_queue_pop(q, &event)) + yasl_events_push(d, s, &event); + q->publish = publish_input; + q->publish_ud = s; + } + return true; +} + +void yasl_events_unregister(struct yasl_surface *s) +{ + struct yasl_display *d = s->display; + if (!d->events) return; + struct event_node *discarded = NULL; + yasl_mutex_lock(&d->events->mutex); + struct yasl_surface **surface = &d->surfaces; + while (*surface && *surface != s) surface = &(*surface)->event_next; + if (*surface) *surface = s->event_next; + struct event_node **entry = &d->events->head; + d->events->tail = NULL; + while (*entry) { + struct event_node *node = *entry; + if (node->event.surface_id == s->id || node->queue_id == s->id) { + *entry = node->next; + node->next = discarded; + discarded = node; + } else { + d->events->tail = node; + entry = &node->next; + } + } + s->event_next = NULL; + yasl_mutex_unlock(&d->events->mutex); + dispose_list(discarded); +} + +static bool append(struct yasl_display *d, struct event_node *node) +{ + struct yasl_event_state *state = d->events; + if (state->sequence == UINT64_MAX) { errno = EOVERFLOW; return false; } + node->event.sequence = ++state->sequence; + if (!node->event.time_ns) node->event.time_ns = yasl_loop_now_ns(); + if (state->tail) state->tail->next = node; + else state->head = node; + state->tail = node; + return true; +} + +static bool string_event(enum yasl_event_type type) +{ + return type == YASL_EVENT_TEXT || type == YASL_EVENT_PASTE || + type == YASL_EVENT_DROP || type == YASL_EVENT_COMPOSITION; +} + +bool yasl_events_push(struct yasl_display *d, struct yasl_surface *queue_owner, + const struct yasl_event *event) +{ + if (!d || !d->events || (event->surface && !event->surface->id)) return true; + struct event_node *node = calloc(1, sizeof(*node)); + if (!node) goto failed; + node->event = *event; + node->event.surface_id = event->surface ? event->surface->id : 0; + node->queue_id = queue_owner ? queue_owner->id : node->event.surface_id; + if (string_event(event->type)) { + if (event->text.len == SIZE_MAX || (!event->text.data && event->text.len)) { + free(node); + goto failed; + } + node->text = malloc(event->text.len + 1); + if (!node->text) { free(node); goto failed; } + if (event->text.len) memcpy(node->text, event->text.data, event->text.len); + node->text[event->text.len] = '\0'; + if (event->type == YASL_EVENT_PASTE) node->event.paste.data = node->text; + else if (event->type == YASL_EVENT_COMPOSITION) node->event.composition.data = node->text; + else node->event.text.data = node->text; + } + yasl_mutex_lock(&d->events->mutex); + if ((node->event.surface_id && !yasl_events_find(d, node->event.surface_id)) || + (node->queue_id && !yasl_events_find(d, node->queue_id))) { + yasl_mutex_unlock(&d->events->mutex); + dispose(node); + return true; /* A native teardown notification for a canceled recipient. */ + } + bool ok = append(d, node); + if (!ok) d->events->error = errno; + yasl_mutex_unlock(&d->events->mutex); + if (!ok) dispose(node); + return ok; +failed: + yasl_mutex_lock(&d->events->mutex); + d->events->error = ENOMEM; + yasl_mutex_unlock(&d->events->mutex); + errno = ENOMEM; + return false; +} + +bool yasl_display_post(struct yasl_display *d, const struct yasl_message *message) +{ + if (!d || !d->events || !message) { errno = EINVAL; return false; } + struct event_node *node = calloc(1, sizeof(*node)); + if (!node) { errno = ENOMEM; return false; } + node->event = (struct yasl_event){ + .type = YASL_EVENT_USER, .surface_id = message->target_id, + .user = {.code = message->code, .payload = message->payload, .size = message->size}, + }; + node->queue_id = message->target_id; + node->release = message->release; + yasl_mutex_lock(&d->events->mutex); + bool ok = !message->target_id || yasl_events_find(d, message->target_id); + if (!ok) errno = ENOENT; + else ok = append(d, node); + yasl_mutex_unlock(&d->events->mutex); + if (!ok) { free(node); return false; } + if (d->loop) yasl_loop_kick(d->loop); + return true; +} + +static bool poll_event(struct yasl_display *d, uint64_t queue_id, struct yasl_event *event) +{ + if (!d || !d->events || !event) return false; + struct yasl_event_state *state = d->events; + yasl_mutex_lock(&state->mutex); + struct event_node *discarded = NULL; + struct event_node **entry = &state->head; + struct event_node *previous = NULL; + if (queue_id) { + /* The input-only migration adapter uses legacy callbacks for lifecycle. + * Retaining those notifications would keep dispatch permanently ready. + * Mixing this adapter with display polling is deliberately unsupported. */ + while (*entry) { + struct event_node *candidate = *entry; + if (candidate->event.type < YASL_EVENT_KEY || + candidate->event.type > YASL_EVENT_INPUT_RESET) { + *entry = candidate->next; + if (state->tail == candidate) state->tail = previous; + candidate->next = discarded; + discarded = candidate; + } else { + previous = candidate; + entry = &candidate->next; + } + } + entry = &state->head; + previous = NULL; + while (*entry && (*entry)->queue_id != queue_id) { + previous = *entry; + entry = &(*entry)->next; + } + } + struct event_node *node = *entry; + if (!node) { + yasl_mutex_unlock(&state->mutex); + dispose_list(discarded); + return false; + } + *entry = node->next; + if (state->tail == node) state->tail = previous; + node->next = NULL; + if (node->event.surface_id) + node->event.surface = yasl_events_find(d, node->event.surface_id); + struct event_node *last = state->last; + state->last = node; + *event = node->event; + yasl_mutex_unlock(&state->mutex); + dispose(last); + dispose_list(discarded); + return true; +} + +bool yasl_display_poll_event(struct yasl_display *d, struct yasl_event *event) +{ + return poll_event(d, 0, event); +} + +bool yasl_events_poll_surface(struct yasl_surface *s, struct yasl_event *event) +{ + return s && s->id ? poll_event(s->display, s->id, event) : false; +} + +uint64_t yasl_events_request_id(struct yasl_display *d) +{ + if (!d->events || d->events->request == UINT64_MAX) { errno = EOVERFLOW; return 0; } + return ++d->events->request; +} + +uint64_t yasl_surface_id(const struct yasl_surface *s) { return s ? s->id : 0; } +void yasl_surface_set_userdata(struct yasl_surface *s, void *ud) { if (s) s->userdata = ud; } +void *yasl_surface_userdata(const struct yasl_surface *s) { return s ? s->userdata : NULL; } + +void yasl_surface_notify_configure(struct yasl_surface *s, struct yasl_geometry geometry) +{ + uint32_t previous_scale = s->reported_scale; + s->reported_scale = geometry.scale_120; + s->size = geometry.size; + s->scale_120 = geometry.scale_120; + struct yasl_event event = {.type = YASL_EVENT_WINDOW_RESIZE, .surface = s, + .resize = {.geometry = geometry, .scale_changed = previous_scale != geometry.scale_120}}; + yasl_events_push(s->display, s, &event); + if (s->display) yasl_surface_request_frame(s); + if (s->callbacks.configure) s->callbacks.configure(s, geometry, s->ud); +} + +void yasl_surface_notify_close(struct yasl_surface *s) +{ + struct yasl_event event = {.type = YASL_EVENT_WINDOW_CLOSE, .surface = s}; + yasl_events_push(s->display, s, &event); + if (s->callbacks.close) s->callbacks.close(s, s->ud); +} + +void yasl_surface_notify_state(struct yasl_surface *s, uint32_t flags) +{ + s->state = flags; + struct yasl_event event = {.type = YASL_EVENT_WINDOW_STATE, .surface = s, .state.flags = flags}; + yasl_events_push(s->display, s, &event); + if (s->callbacks.state) s->callbacks.state(s, flags, s->ud); +} + +void yasl_surface_notify_drag(struct yasl_surface *s, enum yasl_drag_event action, struct yasl_vector2i pos) +{ + struct yasl_event event = {.type = YASL_EVENT_DRAG, .surface = s, .drag = {action, pos}}; + yasl_events_push(s->display, s, &event); + if (s->callbacks.drag) s->callbacks.drag(s, action, pos, s->ud); +} + +void yasl_surface_notify_drop(struct yasl_surface *s, enum yasl_drop_event action, struct yasl_vector2i pos) +{ + struct yasl_event event = {.type = YASL_EVENT_DROP_HOVER, .surface = s, .drop = {action, pos}}; + yasl_events_push(s->display, s, &event); + if (s->callbacks.drop) s->callbacks.drop(s, action, pos, s->ud); +} + +void yasl_surface_notify_composition(struct yasl_surface *s, const char *text, int begin, int end) +{ + struct yasl_event event = {.type = YASL_EVENT_COMPOSITION, .surface = s, + .composition = {.data = text ? text : "", .len = text ? strlen(text) : 0, + .begin = begin, .end = end, .active = text && text[0]}}; + yasl_events_push(s->display, s, &event); +} + +void yasl_surface_complete_paste(struct yasl_surface *s, uint64_t id, + enum yasl_transfer_status status, const char *text, size_t len) +{ + struct yasl_event event = {.type = YASL_EVENT_PASTE, .surface = s, + .paste = {.data = text ? text : "", .len = text ? len : 0, .request_id = id, .status = status}}; + if (!s->display->events) { + struct input_queue *q = s->display->ops->surface_input_queue + ? s->display->ops->surface_input_queue(s) : &s->input; + input_queue_push(q, &event); + } else yasl_events_push(s->display, s, &event); +} + +void yasl_display_notify_event(struct yasl_display *d, enum yasl_event_type type) +{ + struct yasl_event event = {.type = type}; + yasl_events_push(d, NULL, &event); + void (*callback)(void *) = type == YASL_EVENT_DISPLAY_QUIT ? d->callbacks.quit + : type == YASL_EVENT_DISPLAY_REOPEN ? d->callbacks.reopen + : type == YASL_EVENT_DISPLAY_LOST ? d->callbacks.lost : NULL; + if (callback) callback(d->callback_ud); +} diff --git a/src/common/input.c b/src/common/input.c index ce3219c..511cd0a 100644 --- a/src/common/input.c +++ b/src/common/input.c @@ -24,14 +24,15 @@ bool input_queue_push(struct input_queue *q, const struct yasl_event *ev) struct yasl_event copy = *ev; char *owned = NULL; if (!copy.time_ns) copy.time_ns = yasl_loop_now_ns(); - if (has_text(ev)) { + if (has_text(ev) && !q->publish) { size_t len = ev->text.len; if ((!ev->text.data && len) || len == SIZE_MAX) goto failed; owned = malloc(len + 1); if (!owned) goto failed; if (len) memcpy(owned, ev->text.data, len); owned[len] = '\0'; - copy.text.data = owned; + if (ev->type == YASL_EVENT_PASTE) copy.paste.data = owned; + else copy.text.data = owned; } uint32_t buttons = q->buttons; switch (ev->type) { @@ -67,6 +68,10 @@ bool input_queue_push(struct input_queue *q, const struct yasl_event *ev) } if (ev->type >= YASL_EVENT_POINTER_DOWN && ev->type <= YASL_EVENT_POINTER_LEAVE) copy.pointer.buttons = buttons; + if (q->publish) { + q->buttons = buttons; + return q->publish(©, q->publish_ud); + } if (q->count == q->capacity) { size_t capacity = q->capacity ? q->capacity * 2 : 32; if (capacity < q->capacity || capacity > SIZE_MAX / sizeof(*q->events)) @@ -127,8 +132,12 @@ bool input_queue_pop(struct input_queue *q, struct yasl_event *ev) void input_queue_clear(struct input_queue *q) { + bool (*publish)(const struct yasl_event *, void *) = q->publish; + void *publish_ud = q->publish_ud; discard_pending(q); free(q->last_text); free(q->events); *q = (struct input_queue){0}; + q->publish = publish; + q->publish_ud = publish_ud; } diff --git a/src/common/input.h b/src/common/input.h index aad5c9a..8980c55 100644 --- a/src/common/input.h +++ b/src/common/input.h @@ -19,6 +19,10 @@ struct input_queue { uint32_t buttons; /* Set by backends whose pointer leave ends any press: no release follows. */ bool leave_ends_press; + /* Installed by common code for display-wide publication. Standalone queues + * retain their normal storage/pop semantics when publish is NULL. */ + bool (*publish)(const struct yasl_event *ev, void *ud); + void *publish_ud; }; /* Copies text payloads and stamps time_ns when zero. Unless q->relative is set, diff --git a/src/common/meson.build b/src/common/meson.build index 9a30f52..1577132 100644 --- a/src/common/meson.build +++ b/src/common/meson.build @@ -1 +1 @@ -sources += files('app.c', 'backend.c', 'desktop.c', 'input.c', 'native.c', 'timing.c', 'uri.c', 'utf8.c') +sources += files('app.c', 'backend.c', 'desktop.c', 'events.c', 'input.c', 'native.c', 'timing.c', 'uri.c', 'utf8.c') |
