#include "backend.h" #include "loop.h" #include #include #include static const struct yasl_backend_ops *backend_ops(enum yasl_backend backend) { switch (backend) { #if YASL_HAS_WAYLAND case YASL_BACKEND_WAYLAND: return &yasl_wayland_backend; #endif #if YASL_HAS_X11 case YASL_BACKEND_X11: return &yasl_x11_backend; #endif #if YASL_HAS_COCOA case YASL_BACKEND_COCOA: return &yasl_cocoa_backend; #endif default: return NULL; } } static struct yasl_display *open_backend(enum yasl_backend backend, struct yasl_loop *loop, const struct yasl_display_spec *spec) { const struct yasl_backend_ops *ops = backend_ops(backend); if (!ops) { errno = ENOSYS; return NULL; } errno = 0; struct yasl_display *d = ops->display_open(loop, spec); if (!d && !errno) errno = ENODEV; return d; } struct yasl_display *yasl_display_open(struct yasl_loop *loop, const struct yasl_display_spec *spec) { enum yasl_backend backend = spec ? spec->backend : YASL_BACKEND_AUTO; if (backend < YASL_BACKEND_AUTO || backend > YASL_BACKEND_COCOA) { errno = EINVAL; return NULL; } if (backend != YASL_BACKEND_AUTO) return open_backend(backend, loop, spec); #if YASL_HAS_COCOA return open_backend(YASL_BACKEND_COCOA, loop, spec); #else const char *session = getenv("XDG_SESSION_TYPE"); bool wayland = getenv("WAYLAND_DISPLAY") || getenv("WAYLAND_SOCKET") || (session && strcmp(session, "wayland") == 0); enum yasl_backend first = wayland ? YASL_BACKEND_WAYLAND : YASL_BACKEND_X11; enum yasl_backend second = wayland ? YASL_BACKEND_X11 : YASL_BACKEND_WAYLAND; struct yasl_display *d = NULL; if (backend_ops(first)) d = open_backend(first, loop, spec); if (!d && backend_ops(second)) d = open_backend(second, loop, spec); return d; #endif } enum yasl_backend yasl_display_backend(const struct yasl_display *d) { return d ? d->ops->type : YASL_BACKEND_AUTO; } void yasl_display_close(struct yasl_display *d) { if (!d || !d->ops->display_close) return; d->ops->display_close(d); } uint32_t yasl_display_caps(const struct yasl_display *d) { if (!d || !d->ops->display_caps) return 0; return d->ops->display_caps(d); } int yasl_display_dispatch(struct yasl_display *d, int timeout_ms) { if (!d) { errno = EINVAL; return -1; } if (!d->ops->display_dispatch_timeout) { errno = ENOTSUP; return -1; } errno = 0; int result = d->ops->display_dispatch_timeout(d, timeout_ms); if (result < 0 && !errno) errno = EIO; return result; } void yasl_display_flush(struct yasl_display *d) { if (!d || !d->ops->display_flush) return; d->ops->display_flush(d); } int yasl_display_modifiers(const struct yasl_display *d) { if (!d || !d->ops->display_modifiers) return 0; return d->ops->display_modifiers(d); } void yasl_display_copy(struct yasl_display *d, enum yasl_selection sel, const char *text) { 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 || (sel != YASL_CLIPBOARD && sel != YASL_PRIMARY) || (sel == YASL_PRIMARY && !(yasl_display_caps(d) & YASL_CAP_PRIMARY_SELECTION))) return; if (!target || target->display != d) return; d->ops->display_paste(d, sel, target); } bool yasl_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) { if (!d || !d->ops->display_drag_start) return 0; if (!origin || origin->display != d) return false; if (image && (!image->pixels || image->size.x <= 0 || image->size.y <= 0 || image->size.x > 32767 || image->size.y > 32767 || (int64_t)image->size.x * image->size.y * 4 > INT32_MAX)) return false; const struct yasl_drag_listener empty = {0}; return d->ops->display_drag_start(d, origin, image, l ? l : &empty, ud); } void yasl_display_drag_cancel(struct yasl_display *d) { if (!d || !d->ops->display_drag_cancel) return; d->ops->display_drag_cancel(d); } struct yasl_surface *yasl_create_toplevel(struct yasl_display *d, const struct yasl_surface_spec *spec, void *graphics_device) { 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 (graphics_device && !(yasl_display_caps(d) & YASL_CAP_NATIVE_GRAPHICS)) { errno = ENOTSUP; return NULL; } errno = 0; struct yasl_surface *s = d->ops->surface_create_toplevel(d, spec, graphics_device); if (!s && !errno) errno = ENODEV; return s; } struct yasl_surface *yasl_surface_create_toplevel(struct yasl_display *d, const struct yasl_surface_spec *spec) { return yasl_create_toplevel(d, spec, NULL); } static bool popup_spec_valid(const struct yasl_popup_spec *spec) { return 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 && spec->anchor.width >= 0 && spec->anchor.height >= 0 && spec->content_offset.x >= 0 && spec->content_offset.y >= 0 && spec->content_size.x >= 0 && spec->content_size.y >= 0 && (int64_t)spec->content_offset.x + spec->content_size.x <= spec->size.x && (int64_t)spec->content_offset.y + spec->content_size.y <= spec->size.y; } struct yasl_surface *yasl_surface_create_popup(struct yasl_surface *parent, const struct yasl_popup_spec *spec) { if (!parent || !popup_spec_valid(spec)) { errno = EINVAL; return NULL; } if (!(yasl_display_caps(parent->display) & YASL_CAP_POPUPS) || !parent->display->ops->surface_create_popup) { errno = ENOTSUP; return NULL; } errno = 0; struct yasl_surface *s = parent->display->ops->surface_create_popup(parent, spec); if (!s && !errno) errno = ENODEV; return s; } bool yasl_surface_popup_reposition(struct yasl_surface *popup, const struct yasl_popup_spec *spec) { if (!popup || !popup_spec_valid(spec)) { errno = EINVAL; return false; } if (!(yasl_display_caps(popup->display) & YASL_CAP_POPUPS) || !popup->display->ops->surface_popup_reposition) { errno = ENOTSUP; return false; } errno = 0; bool result = popup->display->ops->surface_popup_reposition(popup, spec); if (!result && !errno) errno = ENODEV; return result; } void yasl_surface_destroy(struct yasl_surface *s) { if (!s || !s->display->ops->surface_destroy) return; yasl_surface_clear_timers(s); input_queue_clear(&s->input); s->display->ops->surface_destroy(s); } 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_surface_cbs *cbs, void *ud) { if (!s) return; s->callbacks = cbs ? *cbs : (struct yasl_surface_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); } void yasl_surface_set_title(struct yasl_surface *s, const char *title) { if (!s || !s->display->ops->surface_set_title) return; s->display->ops->surface_set_title(s, title); } void yasl_surface_schedule_redraw(struct yasl_surface *s) { if (!s || !s->display->ops->surface_schedule_redraw) return; s->display->ops->surface_schedule_redraw(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 || !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; clipped.n = damage->n; if (damage->n < 0) { clipped.bounds = damage->bounds; } else { clipped.n = 0; clipped.bounds = (struct yasl_rect2i){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 || !ev) return false; if (s->display && s->display->ops && s->display->ops->surface_input_poll) return s->display->ops->surface_input_poll(s, ev); return input_queue_pop(&s->input, ev); } struct yasl_geometry yasl_surface_geometry(const struct yasl_surface *s) { if (!s) return (struct yasl_geometry){.scale_120 = YASL_SCALE_UNIT}; if (s->scale_120) return (struct yasl_geometry){s->size, s->scale_120}; if (s->display && s->display->ops && s->display->ops->surface_geometry) return s->display->ops->surface_geometry(s); return (struct yasl_geometry){s->size, YASL_SCALE_UNIT}; } uint32_t yasl_surface_state(const struct yasl_surface *s) { if (!s || !s->display->ops->surface_state) return 0; return s->display->ops->surface_state(s); } struct yasl_timing yasl_surface_timing(const struct yasl_surface *s) { if (!s) return (struct yasl_timing){0}; if (s->display && s->display->ops && s->display->ops->surface_timing) return s->display->ops->surface_timing(s); 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), .frames_skipped = s->frames_skipped, .source = s->clock.source, }; yasl_clock_resize_stats(&s->clock, &t.resize); return t; } uint64_t yasl_surface_frame_time_ns(struct yasl_surface *s, uint64_t now) { if (!s) return 0; if (s->display && s->display->ops && s->display->ops->surface_frame_time_ns) return s->display->ops->surface_frame_time_ns(s, now); return yasl_clock_frame_timestamp_ns(&s->clock, now); } void yasl_surface_set_cursor(struct yasl_surface *s, enum yasl_cursor cursor) { if (!s || !s->display->ops->surface_set_cursor) return; s->display->ops->surface_set_cursor(s, cursor); } void yasl_surface_set_background_color(struct yasl_surface *s, uint32_t bg) { if (!s || !s->display->ops->surface_set_background_color) return; s->display->ops->surface_set_background_color(s, bg); } const char *yasl_surface_ime_preedit(const struct yasl_surface *s) { if (!s || !s->display->ops->surface_ime_preedit) return 0; return s->display->ops->surface_ime_preedit(s); } bool yasl_surface_ime_preedit_cursor(const struct yasl_surface *s, int *begin, int *end) { if (!s || !begin || !end || !s->display->ops->surface_ime_preedit_cursor) return 0; return s->display->ops->surface_ime_preedit_cursor(s, begin, end); } void yasl_surface_set_ime_caret(struct yasl_surface *s, struct yasl_rect2i caret) { if (!s || !s->display->ops->surface_set_ime_caret) return; s->display->ops->surface_set_ime_caret(s, caret); } bool yasl_surface_start_interactive_move(struct yasl_surface *s) { if (!s || !s->display->ops->surface_start_interactive_move) return 0; return s->display->ops->surface_start_interactive_move(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 || (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); } bool yasl_surface_show_window_menu(struct yasl_surface *s, struct yasl_vector2i pos) { if (!s || !s->display->ops->surface_show_window_menu) return 0; return s->display->ops->surface_show_window_menu(s, pos); } bool yasl_surface_drag_carry(struct yasl_surface *s, struct yasl_vector2i offset) { if (!s || !s->display->ops->surface_drag_carry) return 0; return s->display->ops->surface_drag_carry(s, offset); } bool yasl_surface_place_under_pointer(struct yasl_surface *s, struct yasl_vector2i at) { if (!s || !s->display->ops->surface_place_under_pointer) return 0; return s->display->ops->surface_place_under_pointer(s, at); } void yasl_surface_clear_timers(struct yasl_surface *s) { if (!s || !s->timers) return; struct yasl_loop *loop = s->display ? s->display->loop : NULL; for (int i = 0; i < s->timer_count; i++) { if (loop && s->timers[i]) { yasl_loop_del_timer(loop, s->timers[i]); } } free(s->timers); s->timers = NULL; s->timer_count = 0; s->timer_capacity = 0; } yasl_timer_id yasl_surface_add_timer(struct yasl_surface *s, uint64_t interval_ns, yasl_timer_cb cb, void *ud) { if (!s || !s->display || !s->display->loop || !cb || interval_ns == 0) return 0; yasl_timer_id id = yasl_loop_add_timer(s->display->loop, interval_ns, cb, ud); if (!id) return 0; /* A one-shot timer retires itself in the loop. Drop retired ids before * the list grows, so the list holds only armed timers. */ if (s->timer_count >= s->timer_capacity) { int kept = 0; for (int i = 0; i < s->timer_count; i++) if (yasl_loop_timer_armed(s->display->loop, s->timers[i])) s->timers[kept++] = s->timers[i]; s->timer_count = kept; } if (s->timer_count >= s->timer_capacity) { int new_cap = s->timer_capacity ? s->timer_capacity * 2 : 4; yasl_timer_id *nt = realloc(s->timers, (size_t)new_cap * sizeof(*nt)); if (!nt) { yasl_loop_del_timer(s->display->loop, id); return 0; } s->timers = nt; s->timer_capacity = new_cap; } s->timers[s->timer_count++] = id; return id; } bool yasl_surface_del_timer(struct yasl_surface *s, yasl_timer_id id) { if (!s || id == 0) return false; for (int i = 0; i < s->timer_count; i++) { if (s->timers[i] == id) { s->timers[i] = s->timers[--s->timer_count]; return s->display && yasl_loop_del_timer(s->display->loop, id); } } return false; } struct yasl_loop *yasl_display_loop(struct yasl_display *d) { return d ? d->loop : NULL; }