#include "yasl/app.h" #include "common/app_internal.h" #include "common/backend.h" #include "platform/sync.h" #include #include #include 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; }; /* Deliver one event to the application; a QUIT result stops the runner. */ static void emit(struct app_runner *ar, const struct yasl_event *ev) { 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); } static void app_runner_surface_configure(struct yasl_surface *s, struct yasl_geometry geom, void *ud) { 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); } static void app_runner_surface_close(struct yasl_surface *s, void *ud) { struct app_surface *as = ud; struct app_runner *ar = as ? as->runner : NULL; if (!ar || !ar->desc || !ar->desc->event) return; 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) { 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); } } bool yasl_frame_present_cpu(struct yasl_frame_context *ctx, const struct yasl_rect2i *dirty_rects, int count) { 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); } void yasl_surface_set_pacing(struct yasl_surface *s, yasl_pacing_mode mode) { 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 yasl_surface_request_frame(struct yasl_surface *s) { if (!s) return; yasl_surface_schedule_redraw(s); if (s->display && s->display->loop) { yasl_loop_kick(s->display->loop); } } 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; } int yasl_app_watch_fd(struct yasl_display *d, int fd, short poll_events, int (*cb)(int fd, short revents, void *ud), void *ud) { if (!d || !d->loop) return -1; return yasl_loop_add_fd(d->loop, fd, poll_events, cb, ud); } void yasl_app_unwatch_fd(struct yasl_display *d, int watch_id) { if (!d || !d->loop) return; yasl_loop_del_fd(d->loop, watch_id); } yasl_timer_id yasl_app_add_timer(struct yasl_display *d, uint64_t interval_ns, yasl_timer_cb cb, void *ud) { if (!d || !d->loop) return 0; return yasl_loop_add_timer(d->loop, interval_ns, 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; } int yasl_run(const struct yasl_app_desc *desc) { if (!desc || !desc->init) 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; } d->app_runner = &runner; if (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; 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) { runner.failed = true; 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); 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); return runner.failed ? -1 : 0; }