#define _POSIX_C_SOURCE 200809L #include "public_internal.h" #include "yasl/cpu.h" #include "yasl/gl.h" #include "common/utf8.h" #include #include #include #include #include #include static struct yasl_private_display *display; static pthread_t owner; static pthread_mutex_t lifecycle_mutex = PTHREAD_MUTEX_INITIALIZER; static bool pumping; static unsigned refresh_depth; static char *delivered_text; /* Strings of the last returned event; freed by the next poll/wait. */ bool yasl_public_fail(int error) { errno = error > 0 ? error : EIO; return false; } int yasl_public_error(int error) { yasl_public_fail(error); return -errno; } int yasl_public_status(bool success) { return success ? 0 : yasl_public_error(errno); } struct yasl_private_display *yasl_public_display(void) { return display; } bool yasl_public_owner(void) { if (!display) return yasl_public_fail(ENODEV); if (!pthread_equal(owner, pthread_self())) return yasl_public_fail(EPERM); return true; } bool yasl_public_surface(yasl_surface *s, bool mutation) { if (!yasl_public_owner()) return false; if (!s || s->display != display) return yasl_public_fail(EINVAL); if (mutation && refresh_depth) return yasl_public_fail(EBUSY); return true; } int yasl_init(const struct yasl_options *options) { if (display) { if (!yasl_public_owner()) return -errno; return yasl_public_error(EALREADY); } struct yasl_options defaults = {0}; if (!options) options = &defaults; if ((unsigned)options->backend > YASL_BACKEND_COCOA) return yasl_public_error(EINVAL); struct yasl_private_display_spec spec = { .app_id = options->app_id, .backend = (enum yasl_private_backend)options->backend, }; errno = 0; struct yasl_private_display *d = yasl_private_display_open(NULL, &spec); if (!d) return yasl_public_error(errno); pthread_mutex_lock(&lifecycle_mutex); owner = pthread_self(); display = d; pthread_mutex_unlock(&lifecycle_mutex); return 0; } static bool initialize(void) { return display ? yasl_public_owner() : yasl_init(NULL) == 0; } static bool creation_valid(int width, int height, unsigned flags) { if (width <= 0 || height <= 0 || width > 32767 || height > 32767 || (unsigned long long)width * (unsigned)height > INT32_MAX / 4 || (flags & ~15u)) return yasl_public_fail(EINVAL); if (pumping || refresh_depth) return yasl_public_fail(EBUSY); return true; } static void draw_committed(yasl_surface *s, struct yasl_private_frame *frame, void *ud) { (void)ud; if (s->refresh_callback && pumping && !refresh_depth) { ++refresh_depth; s->refresh_callback(s, s->refresh_userdata); --refresh_depth; } if (s->presenter != YASL_PRIVATE_PRESENTER_CPU || !frame->target.pixels) return; int w = frame->target.size.x, h = frame->target.size.y; if (w == s->committed_width && h == s->committed_height && s->committed_pixels) memcpy(frame->target.pixels, s->committed_pixels, (size_t)w * h * 4); else memset(frame->target.pixels, 0, (size_t)w * h * 4); struct yasl_private_damage_list damage = {.n = -1}; yasl_private_surface_present(s, &frame->target, &damage); } void yasl_public_register_surface(yasl_surface *s, unsigned flags, yasl_surface *parent) { s->creation_flags = flags; s->parent = parent; if (flags & YASL_PRIVATE_SURFACE_CPU) s->presenter = YASL_PRIVATE_PRESENTER_CPU; struct yasl_private_window_cbs cb = {.draw = draw_committed}; yasl_private_surface_set_callbacks(s, &cb, NULL); s->reported_state = s->state; struct yasl_private_geometry geometry = yasl_private_surface_geometry(s); yasl_private_surface_notify_configure(s, geometry); } yasl_surface *yasl_surface_create(const struct yasl_surface_options *options) { if (!options || (unsigned)options->type > YASL_SURFACE_CPU || (options->flags & ~14u)) { yasl_public_fail(EINVAL); return NULL; } const char *title = options->title; int width = options->width, height = options->height; unsigned flags = options->flags | (options->type == YASL_SURFACE_CPU ? YASL_PRIVATE_SURFACE_CPU : 0u); if (!creation_valid(width, height, flags)) return NULL; bool fresh = !display; if (!initialize()) return NULL; struct yasl_private_window_spec spec = {.title = title, .size = {width, height}, .flags = flags, .external_gpu = !(flags & YASL_PRIVATE_SURFACE_CPU)}; yasl_surface *s = yasl_private_surface_create_toplevel(display, &spec); if (!s) { int error = errno; if (fresh) { pthread_mutex_lock(&lifecycle_mutex); struct yasl_private_display *d = display; display = NULL; pthread_mutex_unlock(&lifecycle_mutex); yasl_private_display_close(d); } yasl_public_fail(error); return NULL; } yasl_public_register_surface(s, flags, NULL); s->userdata = options->userdata; return s; } int yasl_surface_destroy(yasl_surface *s) { if (!s) return 0; if (!yasl_public_surface(s, true)) return -errno; for (yasl_surface *p = display->surfaces; p; p = p->event_next) { for (yasl_surface *ancestor = p; ancestor; ancestor = ancestor->parent) if (ancestor == s && p->vulkan) return yasl_public_error(EBUSY); } for (;;) { yasl_surface *child = NULL; for (yasl_surface *p = display->surfaces; p; p = p->event_next) if (p->parent == s) { child = p; break; } if (!child) break; yasl_surface_destroy(child); } free(s->cpu_pixels); s->cpu_pixels = NULL; free(s->committed_pixels); s->committed_pixels = NULL; free(s->text_surrounding); s->text_surrounding = NULL; yasl_private_surface_destroy(s); return 0; } int yasl_shutdown(void) { if (display && !yasl_public_owner()) return -errno; if (pumping || refresh_depth) { yasl_public_fail(EBUSY); return -errno; } if (display) { for (yasl_surface *s = display->surfaces; s; s = s->event_next) if (s->vulkan) return yasl_public_error(EBUSY); while (display->surfaces) yasl_surface_destroy(display->surfaces); pthread_mutex_lock(&lifecycle_mutex); struct yasl_private_display *d = display; display = NULL; pthread_mutex_unlock(&lifecycle_mutex); yasl_private_display_close(d); } free(delivered_text); delivered_text = NULL; #if YASL_HAS_VULKAN extern void yasl_private_vulkan_reset(void); yasl_private_vulkan_reset(); #endif return 0; } void yasl_wake(void) { pthread_mutex_lock(&lifecycle_mutex); if (display && display->loop) yasl_private_loop_kick(display->loop); pthread_mutex_unlock(&lifecycle_mutex); } uint64_t yasl_time_ns(void) { return yasl_private_loop_now_ns(); } static double scale_of(const yasl_surface *s) { return s && s->scale_120 ? (double)s->scale_120 / 120.0 : 1.0; } static int logical(int pixels, double scale) { return (int)lround(pixels / scale); } enum yasl_backend yasl_surface_get_backend(const yasl_surface *s) { return s && yasl_public_surface((yasl_surface *)s, false) ? (enum yasl_backend)s->display->ops->type : YASL_BACKEND_AUTO; } unsigned yasl_surface_get_features(const yasl_surface *s) { if (!s || !yasl_public_surface((yasl_surface *)s, false)) return 0; unsigned caps = yasl_private_display_caps(s->display); if (s->display->ops->surface_create_gl) caps |= YASL_CAP_OPENGL; #if YASL_HAS_VULKAN caps |= YASL_CAP_VULKAN; #endif return caps; } void yasl_surface_get_framebuffer_size(const yasl_surface *s, int *w, int *h) { if (s && !yasl_public_surface((yasl_surface *)s, false)) s = NULL; if (w) *w = s ? s->size.x : 0; if (h) *h = s ? s->size.y : 0; } void yasl_surface_get_size(const yasl_surface *s, int *w, int *h) { if (s && !yasl_public_surface((yasl_surface *)s, false)) s = NULL; double scale = scale_of(s); if (w) *w = s ? logical(s->size.x, scale) : 0; if (h) *h = s ? logical(s->size.y, scale) : 0; } double yasl_surface_get_scale(const yasl_surface *s) { return s && yasl_public_surface((yasl_surface *)s, false) ? scale_of(s) : 1.0; } unsigned yasl_surface_get_state(const yasl_surface *s) { return s && yasl_public_surface((yasl_surface *)s, false) ? s->state : 0; } void *yasl_surface_get_userdata(const yasl_surface *s) { return s && yasl_public_surface((yasl_surface *)s, false) ? s->userdata : NULL; } int yasl_surface_set_userdata(yasl_surface *s, void *ud) { if (!yasl_public_surface(s, false)) return -errno; s->userdata = ud; return 0; } int yasl_surface_set_title(yasl_surface *s, const char *title) { if (!yasl_public_surface(s, false)) return -errno; if (!title) return yasl_public_error(EINVAL); if (!s->display->ops->surface_set_title) return yasl_public_error(ENOTSUP); s->display->ops->surface_set_title(s, title); return 0; } #define REQUEST(name, priv, args, call) \ int name args \ { \ if (!yasl_public_surface(s, false)) \ return -errno; \ errno = 0; \ return yasl_public_status(priv call || yasl_public_fail(errno)); \ } REQUEST(yasl_surface_request_size, yasl_private_surface_set_size, (yasl_surface * s, int w, int h), (s, ((struct yasl_private_vector2i){w, h}))) REQUEST(yasl_surface_request_fullscreen, yasl_private_surface_set_fullscreen, (yasl_surface * s, bool v), (s, v)) REQUEST(yasl_surface_request_maximized, yasl_private_surface_set_maximized, (yasl_surface * s, bool v), (s, v)) REQUEST(yasl_surface_request_minimize, yasl_private_surface_minimize, (yasl_surface * s), (s)) REQUEST(yasl_surface_set_pointer_mode, yasl_private_surface_set_pointer_mode, (yasl_surface * s, enum yasl_pointer_mode m), (s, (enum yasl_private_pointer_mode)m)) int yasl_surface_set_cursor(yasl_surface *s, enum yasl_cursor cursor) { if (!yasl_public_surface(s, false)) return -errno; if (cursor < YASL_CURSOR_ARROW || cursor > YASL_CURSOR_HIDDEN) return yasl_public_error(EINVAL); if (!s->display->ops->surface_set_cursor) return yasl_public_error(ENOTSUP); yasl_private_surface_set_cursor(s, (enum yasl_private_cursor)cursor); return 0; } int yasl_surface_set_refresh_callback(yasl_surface *s, void (*fn)(yasl_surface *, void *), void *ud) { if (!yasl_public_surface(s, false)) return -errno; s->refresh_callback = fn; s->refresh_userdata = ud; return 0; } void yasl_private_surface_notify_refresh(yasl_surface *s) { if (!s || !s->display) return; struct yasl_private_event ev = {.type = YASL_PRIVATE_EVENT_REFRESH, .surface = s}; yasl_private_events_push(s->display, s, &ev); if (s->refresh_callback && pumping && !refresh_depth) { ++refresh_depth; s->refresh_callback(s, s->refresh_userdata); --refresh_depth; } } static char *copy_text(const char *data, size_t length) { if (length == SIZE_MAX || (!data && length)) { errno = EINVAL; return NULL; } char *p = malloc(length + 1); if (!p) return NULL; if (length) memcpy(p, data, length); p[length] = 0; return p; } static const char *deliver_text(const char *data, size_t length) { free(delivered_text); delivered_text = copy_text(data, length); return delivered_text; } /* Folds native size, state and focus changes into one WINDOW snapshot; unchanged values report * nothing. */ static bool window_event(yasl_surface *s, struct yasl_private_vector2i size, uint32_t scale_120, uint32_t state, yasl_event *e) { bool resized = size.x != s->reported_size.x || size.y != s->reported_size.y || scale_120 != s->reported_scale_120; if (!resized && state == s->reported_state) return false; s->reported_size = size; s->reported_scale_120 = scale_120; s->reported_state = state; if (resized) s->refresh_owed = true; double scale = scale_120 ? scale_120 / 120.0 : 1.0; e->type = YASL_EVENT_WINDOW; e->window.pixel_width = size.x; e->window.pixel_height = size.y; e->window.scale = scale; e->window.width = logical(size.x, scale); e->window.height = logical(size.y, scale); e->window.state = state; return true; } static bool refresh_ready(yasl_surface *s) { return s->reported_size.x > 0 && s->reported_size.y > 0; } static bool convert_event(const struct yasl_private_event *p, yasl_event *e) { *e = (yasl_event){.surface = p->surface, .time_ns = p->time_ns}; double scale = scale_of(p->surface); switch (p->type) { case YASL_PRIVATE_EVENT_WINDOW_CLOSE: e->type = YASL_EVENT_CLOSE; break; case YASL_PRIVATE_EVENT_DISPLAY_QUIT: e->type = YASL_EVENT_QUIT; break; case YASL_PRIVATE_EVENT_DISPLAY_REOPEN: e->type = YASL_EVENT_REOPEN; break; case YASL_PRIVATE_EVENT_DISPLAY_LOST: e->type = YASL_EVENT_DISPLAY_LOST; break; case YASL_PRIVATE_EVENT_DISPLAY_RESTORED: e->type = YASL_EVENT_DISPLAY_RESTORED; break; case YASL_PRIVATE_EVENT_REFRESH: if (!p->surface) return false; if (!refresh_ready(p->surface)) { p->surface->refresh_owed = true; return false; } p->surface->refresh_owed = false; e->type = YASL_EVENT_REFRESH; break; case YASL_PRIVATE_EVENT_INPUT_RESET: e->type = YASL_EVENT_INPUT_RESET; break; case YASL_PRIVATE_EVENT_FOCUS: { yasl_surface *s = p->surface; if (!s) return false; uint32_t state = p->focus.focused ? s->reported_state | YASL_STATE_ACTIVATED : s->reported_state & ~(uint32_t)YASL_STATE_ACTIVATED; return window_event(s, s->reported_size, s->reported_scale_120, state, e); } case YASL_PRIVATE_EVENT_WINDOW_STATE: if (!p->surface) return false; return window_event(p->surface, p->surface->reported_size, p->surface->reported_scale_120, p->state.flags, e); case YASL_PRIVATE_EVENT_WINDOW_RESIZE: if (!p->surface) return false; return window_event(p->surface, p->resize.geometry.size, p->resize.geometry.scale_120, p->surface->reported_state, e); case YASL_PRIVATE_EVENT_KEY: e->type = YASL_EVENT_KEY; e->key.key = p->key.key; e->key.action = (enum yasl_key_action)p->key.action; e->key.mods = p->key.mods; e->key.codepoint = p->key.codepoint; e->key.shifted = p->key.shifted; e->key.native_code = p->key.native_code; break; case YASL_PRIVATE_EVENT_TEXT: e->type = YASL_EVENT_TEXT; e->text.length = p->text.len; e->text.data = deliver_text(p->text.data, p->text.len); return e->text.data != NULL; case YASL_PRIVATE_EVENT_DROP: e->type = YASL_EVENT_DROP; e->drop.action = YASL_DROP_DATA; e->drop.length = p->text.len; e->drop.data = deliver_text(p->text.data, p->text.len); return e->drop.data != NULL; case YASL_PRIVATE_EVENT_PASTE: e->type = YASL_EVENT_CLIPBOARD; e->clipboard.length = p->paste.len; e->clipboard.request_id = p->paste.request_id; e->clipboard.status = (enum yasl_transfer_status)p->paste.status; e->clipboard.data = deliver_text(p->paste.data, p->paste.len); return e->clipboard.data != NULL; case YASL_PRIVATE_EVENT_COMPOSITION: e->type = YASL_EVENT_COMPOSITION; e->composition.length = p->composition.len; e->composition.begin = p->composition.begin; e->composition.end = p->composition.end; e->composition.active = p->composition.active; e->composition.revision = p->composition.revision; e->composition.generation = p->composition.generation; e->composition.data = deliver_text(p->composition.data, p->composition.len); return e->composition.data != NULL; case YASL_PRIVATE_EVENT_TEXT_EDIT: e->type = YASL_EVENT_TEXT_EDIT; e->text_edit.length = p->text_edit.len; e->text_edit.begin = p->text_edit.begin; e->text_edit.end = p->text_edit.end; e->text_edit.revision = p->text_edit.revision; e->text_edit.generation = p->text_edit.generation; e->text_edit.data = deliver_text(p->text_edit.data, p->text_edit.len); return e->text_edit.data != NULL; case YASL_PRIVATE_EVENT_PEN: e->type = YASL_EVENT_PEN; #define PEN(field) e->pen.field = p->pen.field PEN(device_id); PEN(tool_id); PEN(x); PEN(y); PEN(pressure); PEN(tilt_x); PEN(tilt_y); PEN(rotation); PEN(distance); PEN(tangential); PEN(wheel_degrees); PEN(axes_valid); PEN(axes_changed); PEN(buttons); PEN(changed_button); PEN(tool); PEN(action); PEN(in_contact); #undef PEN break; case YASL_PRIVATE_EVENT_POINTER_SCROLL: e->type = YASL_EVENT_SCROLL; e->scroll.x = p->pointer.pos.x / scale; e->scroll.y = p->pointer.pos.y / scale; e->scroll.lines_x = p->pointer.scroll.x; e->scroll.lines_y = p->pointer.scroll.y; e->scroll.pixels_x = p->pointer.pixel_scroll.x / scale; e->scroll.pixels_y = p->pointer.pixel_scroll.y / scale; e->scroll.valid = p->pointer.scroll_valid ? p->pointer.scroll_valid : YASL_SCROLL_LINES; e->scroll.source = p->pointer.scroll_source; e->scroll.phase = p->pointer.scroll_phase; e->scroll.momentum = p->pointer.scroll_momentum; e->scroll.mods = p->pointer.mods; break; case YASL_PRIVATE_EVENT_POINTER_DOWN: case YASL_PRIVATE_EVENT_POINTER_UP: case YASL_PRIVATE_EVENT_POINTER_MOTION: case YASL_PRIVATE_EVENT_POINTER_LEAVE: e->type = YASL_EVENT_POINTER; e->pointer.x = (p->pointer.precise_position ? p->pointer.precise_x : p->pointer.pos.x) / scale; e->pointer.y = (p->pointer.precise_position ? p->pointer.precise_y : p->pointer.pos.y) / scale; e->pointer.dx = p->pointer.delta.x / scale; e->pointer.dy = p->pointer.delta.y / scale; e->pointer.action = p->type == YASL_PRIVATE_EVENT_POINTER_DOWN ? YASL_POINTER_DOWN : p->type == YASL_PRIVATE_EVENT_POINTER_UP ? YASL_POINTER_UP : p->type == YASL_PRIVATE_EVENT_POINTER_LEAVE ? YASL_POINTER_LEAVE : YASL_POINTER_MOVE; e->pointer.button = p->pointer.button; e->pointer.buttons = p->pointer.buttons; e->pointer.mods = p->pointer.mods; break; case YASL_PRIVATE_EVENT_DRAG: e->type = YASL_EVENT_DRAG; e->drag.action = (enum yasl_drag_action)p->drag.action; e->drag.x = p->drag.pos.x / scale; e->drag.y = p->drag.pos.y / scale; break; case YASL_PRIVATE_EVENT_DROP_HOVER: e->type = YASL_EVENT_DROP; e->drop.action = p->drop.action == YASL_PRIVATE_DROP_LEAVE ? YASL_DROP_LEAVE : YASL_DROP_OVER; e->drop.x = p->drop.pos.x / scale; e->drop.y = p->drop.pos.y / scale; break; default: return false; } return true; } static int take_event(yasl_event *out) { for (yasl_surface *s = display->surfaces; s; s = s->event_next) if (s->refresh_owed && refresh_ready(s)) { s->refresh_owed = false; *out = (yasl_event){.type = YASL_EVENT_REFRESH, .surface = s, .time_ns = yasl_time_ns()}; return 1; } struct yasl_private_event p; while (yasl_private_display_poll_event(display, &p)) { if (p.surface && ((p.type == YASL_PRIVATE_EVENT_COMPOSITION && p.composition.generation != p.surface->text_generation) || (p.type == YASL_PRIVATE_EVENT_TEXT_EDIT && p.text_edit.generation != p.surface->text_generation))) continue; yasl_event event; errno = 0; if (convert_event(&p, &event)) { *out = event; return 1; } if (errno) { yasl_public_fail(errno); return -errno; } } return 0; } int yasl_wait_event(yasl_event *out, int timeout) { if (!out) { yasl_public_fail(EINVAL); return -errno; } *out = (yasl_event){.type = YASL_EVENT_NONE}; if (!yasl_public_owner()) return -errno; if (pumping || refresh_depth) { yasl_public_fail(EBUSY); return -errno; } for (yasl_surface *s = display->surfaces; s; s = s->event_next) s->cpu_acquired = false; free(delivered_text); delivered_text = NULL; int n = take_event(out); if (n) return n; pumping = true; yasl_private_display_flush(display); n = take_event(out); if (!n) { int work = yasl_private_display_dispatch(display, timeout); if (work < 0) { yasl_public_fail(errno); n = -errno; } yasl_private_display_flush(display); if (n >= 0) n = take_event(out); } pumping = false; return n; } static uint64_t cpu_sequence; int yasl_cpu_acquire(yasl_surface *s, struct yasl_cpu_buffer *out) { if (!out) return yasl_public_error(EINVAL); *out = (struct yasl_cpu_buffer){0}; if (!yasl_public_surface(s, false)) return -errno; if (s->presenter != YASL_PRIVATE_PRESENTER_CPU) return yasl_public_error(ENOTSUP); if (s->display->offline) return yasl_public_error(ENOTCONN); if (s->cpu_acquired) return yasl_public_error(EBUSY); int w = s->size.x, h = s->size.y; if (w <= 0 || h <= 0) return yasl_public_error(EAGAIN); if ((size_t)w > SIZE_MAX / sizeof(uint32_t) / (size_t)h) return yasl_public_error(EOVERFLOW); if (s->cpu_width != w || s->cpu_height != h || !s->cpu_pixels) { uint32_t *pixels = malloc((size_t)w * h * sizeof(*pixels)); if (!pixels) return yasl_public_error(ENOMEM); free(s->cpu_pixels); s->cpu_pixels = pixels; s->cpu_width = w; s->cpu_height = h; } memset(s->cpu_pixels, 0, (size_t)w * h * sizeof(uint32_t)); int keep_w = w < s->committed_width ? w : s->committed_width; int keep_h = h < s->committed_height ? h : s->committed_height; if (s->committed_pixels) for (int y = 0; y < keep_h; y++) memcpy(s->cpu_pixels + (size_t)y * w, s->committed_pixels + (size_t)y * s->committed_width, (size_t)keep_w * sizeof(uint32_t)); if (++cpu_sequence == 0) ++cpu_sequence; s->cpu_token = cpu_sequence; s->cpu_acquired = true; *out = (struct yasl_cpu_buffer){s->cpu_pixels, w, h, (size_t)w * sizeof(uint32_t), s->cpu_token}; return 0; } static int cpu_validate(yasl_surface *s, const struct yasl_cpu_buffer *buffer) { if (!yasl_public_surface(s, false)) return -errno; if (!buffer) return yasl_public_error(EINVAL); if (s->presenter != YASL_PRIVATE_PRESENTER_CPU) return yasl_public_error(ENOTSUP); if (!s->cpu_acquired || !buffer->token || buffer->token != s->cpu_token || s->cpu_width != s->size.x || s->cpu_height != s->size.y) return yasl_public_error(ESTALE); if (buffer->pixels != s->cpu_pixels || buffer->width != s->cpu_width || buffer->height != s->cpu_height || buffer->stride_bytes != (size_t)s->cpu_width * sizeof(uint32_t)) return yasl_public_error(EINVAL); return 0; } int yasl_cpu_present(yasl_surface *s, const struct yasl_cpu_buffer *buffer) { int error = cpu_validate(s, buffer); if (error) return error; if (s->display->offline) return yasl_public_error(ENOTCONN); size_t bytes = (size_t)s->cpu_width * s->cpu_height * sizeof(uint32_t); if (s->committed_width != s->cpu_width || s->committed_height != s->cpu_height || !s->committed_pixels) { uint32_t *pixels = malloc(bytes); if (!pixels) return yasl_public_error(ENOMEM); free(s->committed_pixels); s->committed_pixels = pixels; s->committed_width = s->cpu_width; s->committed_height = s->cpu_height; } memcpy(s->committed_pixels, s->cpu_pixels, bytes); s->cpu_acquired = false; yasl_private_surface_request_frame(s); return 0; } int yasl_cpu_discard(yasl_surface *s, const struct yasl_cpu_buffer *buffer) { int error = cpu_validate(s, buffer); if (error) return error; s->cpu_acquired = false; return 0; } int yasl_get_double_click_ms(void) { return display && yasl_public_owner() ? yasl_private_display_double_click_ms(display) : 400; } int yasl_clipboard_set(enum yasl_selection sel, const char *text) { if (!yasl_public_owner()) return -errno; errno = 0; return yasl_public_status( yasl_private_clipboard_set(display, (enum yasl_private_selection)sel, text) || yasl_public_fail(errno)); } uint64_t yasl_clipboard_request(yasl_surface *s, enum yasl_selection sel) { if (!yasl_public_surface(s, false)) return 0; errno = 0; uint64_t id = yasl_private_clipboard_request(s, (enum yasl_private_selection)sel); if (!id) yasl_public_fail(errno); return id; } static bool text_update(yasl_surface *s) { if (!s->display->ops->surface_update_text_input) return true; errno = 0; return s->display->ops->surface_update_text_input(s) || yasl_public_fail(errno); } int yasl_surface_set_text_input(yasl_surface *s, bool active, enum yasl_text_purpose purpose) { if (!yasl_public_surface(s, false)) return -errno; if (purpose < YASL_TEXT_NORMAL || purpose > YASL_TEXT_TERMINAL) return yasl_public_error(EINVAL); bool old = s->text_input_active; unsigned old_purpose = s->text_purpose; uint64_t generation = s->text_generation; if (old != active || old_purpose != (unsigned)purpose) ++s->text_generation; s->text_input_active = active; s->text_purpose = purpose; if (!text_update(s)) { s->text_input_active = old; s->text_purpose = old_purpose; s->text_generation = generation; return -errno; } return 0; } int yasl_surface_set_surrounding_text(yasl_surface *s, const char *text, size_t len, size_t begin, size_t end, uint64_t revision) { if (!yasl_public_surface(s, false)) return -errno; if ((!text && len) || begin > end || end > len || len > INT32_MAX || (len && memchr(text, 0, len))) return yasl_public_error(EINVAL); /* Validate UTF-8 and require selection boundaries between code points. */ if ((begin < len && ((unsigned char)text[begin] & 0xc0) == 0x80) || (end < len && ((unsigned char)text[end] & 0xc0) == 0x80)) return yasl_public_error(EINVAL); for (size_t i = 0; i < len;) { unsigned char c = (unsigned char)text[i]; size_t n = c < 0x80 ? 1 : c >= 0xc2 && c <= 0xdf ? 2 : c >= 0xe0 && c <= 0xef ? 3 : c >= 0xf0 && c <= 0xf4 ? 4 : 0; if (!n || i + n > len) return yasl_public_error(EILSEQ); for (size_t j = 1; j < n; j++) if (((unsigned char)text[i + j] & 0xc0) != 0x80) return yasl_public_error(EILSEQ); if (n >= 3 && ((c == 0xe0 && (unsigned char)text[i + 1] < 0xa0) || (c == 0xed && (unsigned char)text[i + 1] >= 0xa0) || (c == 0xf0 && (unsigned char)text[i + 1] < 0x90) || (c == 0xf4 && (unsigned char)text[i + 1] >= 0x90))) return yasl_public_error(EILSEQ); i += n; } char *copy = copy_text(text, len); if (!copy) return yasl_public_error(errno); char *old = s->text_surrounding; size_t old_len = s->text_length, old_begin = s->text_begin, old_end = s->text_end; uint64_t old_rev = s->text_revision; s->text_surrounding = copy; s->text_length = len; s->text_begin = begin; s->text_end = end; s->text_revision = revision; if (!text_update(s)) { free(copy); s->text_surrounding = old; s->text_length = old_len; s->text_begin = old_begin; s->text_end = old_end; s->text_revision = old_rev; return -errno; } free(old); return 0; } static bool logical_rect(yasl_surface *s, double x, double y, double w, double h, struct yasl_private_rect2i *r) { if (!isfinite(x) || !isfinite(y) || !isfinite(w) || !isfinite(h) || w < 0 || h < 0) return yasl_public_fail(EINVAL); double scale = scale_of(s); if (fabs(x) * scale > INT_MAX || fabs(y) * scale > INT_MAX || w * scale > INT_MAX || h * scale > INT_MAX) return yasl_public_fail(EOVERFLOW); r->x = yasl_private_scale_to_device(x, s->scale_120); r->y = yasl_private_scale_to_device(y, s->scale_120); r->width = yasl_private_scale_to_device(w, s->scale_120); r->height = yasl_private_scale_to_device(h, s->scale_120); return true; } int yasl_surface_set_ime_caret(yasl_surface *s, double x, double y, double w, double h) { if (!yasl_public_surface(s, false)) return -errno; struct yasl_private_rect2i r; if (!logical_rect(s, x, y, w, h, &r)) return -errno; if (!s->display->ops->surface_set_ime_caret) return yasl_public_error(ENOTSUP); yasl_private_surface_set_ime_caret(s, r); return 0; } static bool trigger_valid(yasl_surface *s, const yasl_event *e) { if (!e || e->surface != s || !e->time_ns || yasl_time_ns() - e->time_ns > 5000000000ull || !((e->type == YASL_EVENT_POINTER && e->pointer.action == YASL_POINTER_DOWN) || (e->type == YASL_EVENT_PEN && e->pen.action == YASL_PEN_DOWN))) { return yasl_public_fail(ESTALE); } return true; } static bool popup_spec(yasl_surface *s, int w, int h, double x, double y, double aw, double ah, enum yasl_popup_edge anchor, enum yasl_popup_edge gravity, unsigned constraints, struct yasl_private_popup_spec *out) { if (w <= 0 || h <= 0 || w > 32767 || h > 32767 || (unsigned)anchor > 8 || (unsigned)gravity > 8 || (constraints & ~63u)) return yasl_public_fail(EINVAL); if (w * scale_of(s) > 32767 || h * scale_of(s) > 32767) return yasl_public_fail(EOVERFLOW); *out = (struct yasl_private_popup_spec){.size = {yasl_private_scale_to_device(w, s->scale_120), yasl_private_scale_to_device(h, s->scale_120)}, .anchor_edges = anchor, .gravity = gravity, .constraints = constraints}; return logical_rect(s, x, y, aw, ah, &out->anchor); } yasl_surface *yasl_popup_create(yasl_surface *parent, const struct yasl_popup_options *options, const yasl_event *trigger) { if (!options) { yasl_public_fail(EINVAL); return NULL; } int w = options->width, h = options->height; double x = options->x, y = options->y, aw = options->anchor_width, ah = options->anchor_height; enum yasl_popup_edge anchor = options->anchor, gravity = options->gravity; unsigned constraints = options->constraints; if (!yasl_public_surface(parent, true) || !trigger_valid(parent, trigger)) return NULL; struct yasl_private_popup_spec spec; if (!popup_spec(parent, w, h, x, y, aw, ah, anchor, gravity, constraints, &spec)) return NULL; yasl_surface *s = yasl_private_surface_create_popup(parent, &spec); if (!s) { yasl_public_fail(errno); return NULL; } yasl_public_register_surface(s, parent->creation_flags, parent); return s; } int yasl_popup_reposition(yasl_surface *s, const struct yasl_popup_options *options) { if (!options) return yasl_public_error(EINVAL); int w = options->width, h = options->height; double x = options->x, y = options->y, aw = options->anchor_width, ah = options->anchor_height; enum yasl_popup_edge anchor = options->anchor, gravity = options->gravity; unsigned constraints = options->constraints; if (!yasl_public_surface(s, false)) return -errno; struct yasl_private_popup_spec spec; if (!s->parent) return yasl_public_error(EINVAL); if (!popup_spec(s->parent, w, h, x, y, aw, ah, anchor, gravity, constraints, &spec)) return -errno; errno = 0; return yasl_public_status(yasl_private_surface_popup_reposition(s, &spec) || yasl_public_fail(errno)); } int yasl_surface_start_interactive_move(yasl_surface *s, const yasl_event *e) { if (!yasl_public_surface(s, false) || !trigger_valid(s, e)) return -errno; errno = 0; return yasl_public_status(yasl_private_surface_start_interactive_move(s) || yasl_public_fail(errno)); } int yasl_surface_start_interactive_resize(yasl_surface *s, enum yasl_resize_edge edge, const yasl_event *e) { if (!yasl_public_surface(s, false) || !trigger_valid(s, e)) return -errno; errno = 0; return yasl_public_status( yasl_private_surface_start_interactive_resize(s, (enum yasl_private_resize_edge)edge) || yasl_public_fail(errno)); } int yasl_surface_show_window_menu(yasl_surface *s, double x, double y, const yasl_event *e) { if (!yasl_public_surface(s, false) || !trigger_valid(s, e)) return -errno; struct yasl_private_rect2i r; if (!logical_rect(s, x, y, 0, 0, &r)) return -errno; errno = 0; return yasl_public_status( yasl_private_surface_show_window_menu(s, (struct yasl_private_vector2i){r.x, r.y}) || yasl_public_fail(errno)); } int yasl_drag_start(yasl_surface *s, const yasl_event *e, const struct yasl_drag_image *source) { if (!yasl_public_surface(s, false) || !trigger_valid(s, e)) return -errno; struct yasl_private_drag_image image = {0}; if (source) image = (struct yasl_private_drag_image){.pixels = source->pixels, .size = {source->width, source->height}, .hotspot = {source->hotspot_x, source->hotspot_y}}; errno = 0; return yasl_public_status( yasl_private_display_drag_start(display, s, source ? &image : NULL, NULL, NULL) || yasl_public_fail(errno)); } int yasl_drag_cancel(void) { if (!yasl_public_owner()) return -errno; yasl_private_display_drag_cancel(display); return 0; } int yasl_surface_drag_carry(yasl_surface *s, double x, double y) { if (!yasl_public_surface(s, false)) return -errno; struct yasl_private_rect2i r; if (!logical_rect(s, x, y, 0, 0, &r)) return -errno; errno = 0; return yasl_public_status( yasl_private_surface_drag_carry(s, (struct yasl_private_vector2i){r.x, r.y}) || yasl_public_fail(errno)); } int yasl_surface_place_under_pointer(yasl_surface *s, double x, double y) { if (!yasl_public_surface(s, false)) return -errno; struct yasl_private_rect2i r; if (!logical_rect(s, x, y, 0, 0, &r)) return -errno; errno = 0; return yasl_public_status( yasl_private_surface_place_under_pointer(s, (struct yasl_private_vector2i){r.x, r.y}) || yasl_public_fail(errno)); } size_t yasl_drop_next_path(const char **cursor, char *out, size_t cap) { return yasl_private_drop_next_path(cursor, out, cap); } yasl_surface *yasl_gl_surface_create(const struct yasl_surface_options *options, const struct yasl_gl_options *gl) { if (!options || options->type != YASL_SURFACE_NATIVE || (options->flags & ~14u)) { yasl_public_fail(EINVAL); return NULL; } const struct yasl_gl_options defaults = YASL_GL_OPTIONS_INIT; if (!gl) gl = &defaults; const int attrs[] = { YASL_PRIVATE_GL_MAJOR, gl->major, YASL_PRIVATE_GL_MINOR, gl->minor, YASL_PRIVATE_GL_PROFILE, gl->profile, YASL_PRIVATE_GL_API, gl->api, YASL_PRIVATE_GL_RED_BITS, gl->red_bits, YASL_PRIVATE_GL_GREEN_BITS, gl->green_bits, YASL_PRIVATE_GL_BLUE_BITS, gl->blue_bits, YASL_PRIVATE_GL_ALPHA_BITS, gl->alpha_bits, YASL_PRIVATE_GL_DEPTH_BITS, gl->depth_bits, YASL_PRIVATE_GL_STENCIL_BITS, gl->stencil_bits, YASL_PRIVATE_GL_SAMPLES, gl->samples, YASL_PRIVATE_GL_DEBUG, gl->debug, YASL_PRIVATE_GL_FORWARD_COMPAT, gl->forward_compat, YASL_PRIVATE_GL_SRGB, gl->srgb, YASL_PRIVATE_GL_END, }; const char *title = options->title; int w = options->width, h = options->height; unsigned flags = options->flags; yasl_surface *share = gl->share; if (!creation_valid(w, h, flags)) { return NULL; } struct yasl_private_gl_options parsed; if (!yasl_private_gl_parse_options(attrs, &parsed)) { yasl_public_fail(errno); return NULL; } if (share && !display) { yasl_public_fail(EINVAL); return NULL; } bool fresh = !display; if (!initialize()) return NULL; if (share && (share->display != display || share->presenter != YASL_PRIVATE_PRESENTER_GL)) { yasl_public_fail(EINVAL); return NULL; } if (!display->ops->surface_create_gl) { if (fresh) yasl_shutdown(); yasl_public_fail(ENOTSUP); return NULL; } struct yasl_private_window_spec spec = { .title = title, .size = {w, h}, .flags = flags, .external_gpu = true}; errno = 0; yasl_surface *s = display->ops->surface_create_gl(display, &spec, attrs, share); if (!s) { int error = errno; if (fresh) yasl_shutdown(); yasl_public_fail(error); return NULL; } if (!yasl_private_events_register(s)) { int error = errno; display->ops->surface_destroy(s); if (fresh) yasl_shutdown(); yasl_public_fail(error); return NULL; } s->presenter = YASL_PRIVATE_PRESENTER_GL; yasl_public_register_surface(s, flags, NULL); s->userdata = options->userdata; return s; } int yasl_gl_make_current(yasl_surface *s) { if (!display) { return yasl_public_error(ENODEV); } if (s && (s->display != display || s->presenter != YASL_PRIVATE_PRESENTER_GL)) return yasl_public_error(EINVAL); if (display->offline) return yasl_public_error(ENOTCONN); if (!display->ops->gl_make_current) return yasl_public_error(ENOTSUP); errno = 0; return yasl_public_status(display->ops->gl_make_current(display, s) || yasl_public_fail(errno)); } yasl_gl_proc yasl_gl_get_proc_address(const char *name) { if (!display) { yasl_public_fail(ENODEV); return NULL; } if (!name || !*name) { yasl_public_fail(EINVAL); return NULL; } if (display->offline) { yasl_public_fail(ENOTCONN); return NULL; } if (!display->ops->gl_get_proc_address) { yasl_public_fail(ENOTSUP); return NULL; } yasl_gl_proc p = display->ops->gl_get_proc_address(display, name); if (!p) yasl_public_fail(ENOENT); return p; } int yasl_gl_set_swap_interval(yasl_surface *s, int interval) { if (!display) return yasl_public_error(ENODEV); if (display->offline) return yasl_public_error(ENOTCONN); if (!s || s->display != display || s->presenter != YASL_PRIVATE_PRESENTER_GL || !s->display->ops->gl_swap_interval) return yasl_public_error(EINVAL); errno = 0; return yasl_public_status(s->display->ops->gl_swap_interval(s->display, s, interval) || yasl_public_fail(errno)); } int yasl_gl_present(yasl_surface *s) { if (!display) return yasl_public_error(ENODEV); if (display->offline) return yasl_public_error(ENOTCONN); if (!s || s->display != display || s->presenter != YASL_PRIVATE_PRESENTER_GL || !s->display->ops->gl_swap_buffers) return yasl_public_error(EINVAL); errno = 0; return yasl_public_status(s->display->ops->gl_swap_buffers(s->display, s) || yasl_public_fail(errno)); }