#include "yasl/app.h" #include "common/app_internal.h" #include "common/backend.h" #include #include #include struct user_event_node { struct yasl_surface *surface; uint32_t type; 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; yasl_mouse_mode mouse_mode; struct yasl_vector2i last_mouse_pos; bool has_last_mouse_pos; struct yasl_vector2i last_size; 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 had_surfaces; int surface_count; struct app_surface *surfaces; pthread_mutex_t user_event_mutex; struct user_event_node *user_event_head; struct user_event_node *user_event_tail; }; 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; bool scale_changed = (as->last_scale_120 != geom.scale_120 && as->last_scale_120 != 0); as->last_scale_120 = geom.scale_120; as->last_size = geom.size; if (scale_changed) { struct yasl_event ev = { .surface = s, .type = YASL_EVENT_WINDOW_SCALE_CHANGED, .scale = { .scale_120 = geom.scale_120, .size = geom.size, }, }; if (ar->desc->event(ar->desc->userdata, &ev) == YASL_APP_QUIT) ar->running = false; if (!as->surface) return; } struct yasl_event ev = { .surface = s, .type = YASL_EVENT_WINDOW_RESIZE, .resize = { .size = geom.size, .scale_120 = geom.scale_120, }, }; if (ar->desc->event(ar->desc->userdata, &ev) == YASL_APP_QUIT) ar->running = false; 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; struct yasl_event ev = { .surface = s, .type = YASL_EVENT_WINDOW_CLOSE_REQUEST, }; if (ar->desc->event(ar->desc->userdata, &ev) == YASL_APP_QUIT) { ar->running = false; } } 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->mouse_mode = YASL_MOUSE_NORMAL; as->last_scale_120 = s->scale_120; as->last_size = s->size; s->app_surface = as; as->next = ar->surfaces; ar->surfaces = as; ar->surface_count++; ar->had_surfaces = true; struct yasl_surface_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); } } void yasl_surface_set_mouse_mode(struct yasl_surface *s, yasl_mouse_mode mode) { if (!s || !s->app_surface) return; struct app_surface *as = s->app_surface; as->mouse_mode = mode; if (mode == YASL_MOUSE_NORMAL) { yasl_surface_set_cursor(s, YASL_CURSOR_ARROW); } else { yasl_surface_set_cursor(s, YASL_CURSOR_HIDDEN); } } bool yasl_post_event(struct yasl_display *d, struct yasl_surface *s, uint32_t user_type, 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->type = user_type; node->payload = payload; node->size = size; node->next = NULL; pthread_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; } pthread_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) { pthread_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; pthread_mutex_unlock(&ar->user_event_mutex); while (node && ar->running) { struct user_event_node *next = node->next; if (ar->desc->event) { struct yasl_event ev = { .surface = node->surface, .type = YASL_EVENT_USER, .user = { .type = node->type, .payload = node->payload, .size = node->size, }, }; if (ar->desc->event(ar->desc->userdata, &ev) == YASL_APP_QUIT) ar->running = false; } 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_input_event ie; while (as->surface && yasl_surface_input_poll(as->surface, &ie) && ar->running) { if (!ar->desc->event) continue; struct yasl_event ev = {0}; ev.surface = ie.surface ? ie.surface : as->surface; switch (ie.kind) { case YASL_INPUT_KEY: ev.type = YASL_EVENT_KEY; ev.key.key = ie.key.key; ev.key.action = ie.key.action; ev.key.mods = ie.key.mods; ev.key.codepoint = ie.key.codepoint; ev.key.shifted = ie.key.shifted; ev.key.native_code = ie.key.native_code; ev.key.repeat = (ie.key.action == YASL_KEY_REPEAT); if (ar->desc->event(ar->desc->userdata, &ev) == YASL_APP_QUIT) ar->running = false; break; case YASL_INPUT_TEXT: ev.type = YASL_EVENT_TEXT; ev.text.text = ie.text.text; ev.text.len = ie.text.len; if (ar->desc->event(ar->desc->userdata, &ev) == YASL_APP_QUIT) ar->running = false; break; case YASL_INPUT_MOUSE: if (ie.mouse.kind == YASL_PTR_PRESS || ie.mouse.kind == YASL_PTR_RELEASE) { ev.type = YASL_EVENT_MOUSE_BUTTON; ev.mouse_button.pos = ie.mouse.pos; ev.mouse_button.button = ie.mouse.button; ev.mouse_button.pressed = (ie.mouse.kind == YASL_PTR_PRESS); ev.mouse_button.mods = ie.mouse.mods; ev.mouse_button.pen = ie.mouse.pen; as->last_mouse_pos = ie.mouse.pos; as->has_last_mouse_pos = true; if (ar->desc->event(ar->desc->userdata, &ev) == YASL_APP_QUIT) ar->running = false; } else if (ie.mouse.kind == YASL_PTR_MOTION) { ev.type = YASL_EVENT_MOUSE_MOTION; ev.mouse_motion.pos = ie.mouse.pos; if (as->has_last_mouse_pos) { ev.mouse_motion.delta.x = ie.mouse.pos.x - as->last_mouse_pos.x; ev.mouse_motion.delta.y = ie.mouse.pos.y - as->last_mouse_pos.y; } else { ev.mouse_motion.delta = (struct yasl_vector2i){0, 0}; } ev.mouse_motion.mods = ie.mouse.mods; ev.mouse_motion.pen = ie.mouse.pen; as->last_mouse_pos = ie.mouse.pos; as->has_last_mouse_pos = true; if (ar->desc->event(ar->desc->userdata, &ev) == YASL_APP_QUIT) ar->running = false; } else if (ie.mouse.kind == YASL_PTR_SCROLL) { ev.type = YASL_EVENT_MOUSE_SCROLL; ev.mouse_scroll.pos = ie.mouse.pos; ev.mouse_scroll.scroll = ie.mouse.scroll; ev.mouse_scroll.mods = ie.mouse.mods; if (ar->desc->event(ar->desc->userdata, &ev) == YASL_APP_QUIT) ar->running = false; } else if (ie.mouse.kind == YASL_PTR_LEAVE) { ev.type = YASL_EVENT_MOUSE_LEAVE; as->has_last_mouse_pos = false; if (ar->desc->event(ar->desc->userdata, &ev) == YASL_APP_QUIT) ar->running = false; } break; case YASL_INPUT_FOCUS: ev.type = YASL_EVENT_WINDOW_FOCUS; ev.focus.focused = ie.focus.focused; if (ar->desc->event(ar->desc->userdata, &ev) == YASL_APP_QUIT) ar->running = false; break; case YASL_INPUT_PASTE: ev.type = YASL_EVENT_PASTE; ev.paste.text = ie.paste.text; ev.paste.len = ie.paste.len; if (ar->desc->event(ar->desc->userdata, &ev) == YASL_APP_QUIT) ar->running = false; break; case YASL_INPUT_DROP_FILES: ev.type = YASL_EVENT_DROP_FILES; ev.drop_files.files = ie.drop_files.files; ev.drop_files.len = ie.drop_files.len; if (ar->desc->event(ar->desc->userdata, &ev) == YASL_APP_QUIT) ar->running = false; break; case YASL_INPUT_RESET: as->has_last_mouse_pos = false; break; default: break; } } } } int yasl_run(const struct yasl_app_desc *desc) { if (!desc || !desc->init) return -1; struct yasl_display_spec dspec = { .app_id = desc->app_id, .backend = desc->backend, }; struct yasl_display *d = yasl_display_open(NULL, &dspec); if (!d) return -1; struct app_runner runner = { .display = d, .desc = desc, .running = true, .had_surfaces = false, .surface_count = 0, .surfaces = NULL, }; pthread_mutex_init(&runner.user_event_mutex, NULL); 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) { 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; pthread_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; } pthread_mutex_unlock(&runner.user_event_mutex); pthread_mutex_destroy(&runner.user_event_mutex); yasl_display_close(d); return 0; }