From d26bd78644544151b42600c5358d0fecc84b14f1 Mon Sep 17 00:00:00 2001 From: Berke Güzel Date: Mon, 12 Oct 2026 22:43:23 +0300 Subject: feat: implement modern Approach A app runner, unified events, notepad, and game examples --- src/common/app.c | 529 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 529 insertions(+) create mode 100644 src/common/app.c (limited to 'src/common/app.c') diff --git a/src/common/app.c b/src/common/app.c new file mode 100644 index 0000000..15c2a3f --- /dev/null +++ b/src/common/app.c @@ -0,0 +1,529 @@ +#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; +}; + +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 frame_in_progress; + 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->frame_in_progress = true; + 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->pacing == YASL_PACING_CONTINUOUS && ar->running) { + yasl_surface_schedule_redraw(s); + } + + as->frame_in_progress = false; +} + +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; + } + + 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; + + 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; + + struct app_surface **curr = &ar->surfaces; + while (*curr) { + if (*curr == as) { + *curr = as->next; + break; + } + curr = &(*curr)->next; + } + + ar->surface_count--; + s->app_surface = NULL; + 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 (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_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) { + 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->pacing == YASL_PACING_CONTINUOUS) { + yasl_surface_schedule_redraw(as->surface); + } + } + } + } + + if (desc->quit) { + desc->quit(desc->userdata, d); + } + + d->app_runner = NULL; + while (runner.surfaces) { + struct app_surface *next = runner.surfaces->next; + free(runner.surfaces); + runner.surfaces = next; + } + + 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; +} -- cgit v1.2.3