diff options
| author | Berke Güzel <wenekar1@gmail.com> | 2026-10-14 13:56:48 +0300 |
|---|---|---|
| committer | Berke Güzel <wenekar1@gmail.com> | 2026-10-14 13:56:48 +0300 |
| commit | 87279f6903343e621c0446b20d1e7d6a495f1125 (patch) | |
| tree | 999b5fb5fb76702efb02f856f082147304f0859b /src/common/surface.c | |
| parent | 1ab3e5a123109704789c2768e11a4f9af515821d (diff) | |
Simplify the public API into one dense header
yasl.h now holds everything except Vulkan: 45 functions instead of 62,
and three installed headers instead of ten.
- One constructor: yasl_surface_create takes CPU, GL or NATIVE, with
zero-initialized GL options meaning 3.2 core, RGBA8, D24S8.
- Events carry action, mods, x, y and text at the top level; pen
positions are now logical like pointer positions.
- yasl_cpu_pixels/yasl_cpu_present draw into one persistent canvas,
cutting three full-frame copies per frame.
- yasl_surface_get_info, yasl_surface_request, YASL_CURSOR_LOCKED,
yasl_surface_set_text_input, yasl_surface_begin_move_resize and
yasl_surface_get_native replace groups of narrower calls.
- Interactive calls drop the decorative trigger event argument.
- yasl_wait_event returns 0 only on timeout or yasl_wake, the refresh
callback runs only in native modal loops, focus loss sends
INPUT_RESET, and a lost event no longer fails every later wait.
- Unsupported operations and bad arguments report ENOTSUP and EINVAL
instead of EIO; the keycodes header no longer needs C23.
- The shared library gets a versioned SONAME and version macros.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'src/common/surface.c')
| -rw-r--r-- | src/common/surface.c | 1508 |
1 files changed, 671 insertions, 837 deletions
diff --git a/src/common/surface.c b/src/common/surface.c index de9e7b3..b9231ea 100644 --- a/src/common/surface.c +++ b/src/common/surface.c @@ -1,21 +1,22 @@ #define _POSIX_C_SOURCE 200809L #include "public_internal.h" -#include "yasl/cpu.h" -#include "yasl/gl.h" #include "common/utf8.h" #include <errno.h> #include <limits.h> #include <math.h> #include <pthread.h> +#include <stdatomic.h> #include <stdlib.h> #include <string.h> static struct yasl_private_display *display; static pthread_t owner; static pthread_mutex_t lifecycle_mutex = PTHREAD_MUTEX_INITIALIZER; +static atomic_bool wake_requested; static bool pumping; static unsigned refresh_depth; -static char *delivered_text; /* Strings of the last returned event; freed by the next poll/wait. */ +static char *delivered_text; /* Text of the last returned event; freed by the next wait. */ + bool yasl_public_fail(int error) { errno = error > 0 ? error : EIO; @@ -52,21 +53,24 @@ bool yasl_public_surface(yasl_surface *s, bool mutation) return yasl_public_fail(EBUSY); return true; } -int yasl_init(const struct yasl_options *options) +bool yasl_public_connected(yasl_surface *s) { - 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) + if (!yasl_public_surface(s, false)) + return false; + return !atomic_load(&display->offline) || yasl_public_fail(ENOTCONN); +} +/* Runs a private call that reports failure through errno. */ +#define STATUS(call) (errno = 0, yasl_public_status((call) || yasl_public_fail(errno))) + +int yasl_init(enum yasl_backend backend, const char *app_id) +{ + if (display) + return yasl_public_owner() ? yasl_public_error(EALREADY) : -errno; + if ((unsigned)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, + .app_id = app_id, + .backend = (enum yasl_private_backend)backend, }; errno = 0; struct yasl_private_display *d = yasl_private_display_open(NULL, &spec); @@ -78,83 +82,214 @@ int yasl_init(const struct yasl_options *options) pthread_mutex_unlock(&lifecycle_mutex); return 0; } -static bool initialize(void) +bool yasl_public_initialize(void) { - return display ? yasl_public_owner() : yasl_init(NULL) == 0; + return display ? yasl_public_owner() : yasl_init(YASL_BACKEND_AUTO, NULL) == 0; } -static bool creation_valid(int width, int height, unsigned flags) +static void close_display(void) { - 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); + pthread_mutex_lock(&lifecycle_mutex); + struct yasl_private_display *d = display; + display = NULL; + pthread_mutex_unlock(&lifecycle_mutex); + yasl_private_display_close(d); +} +int yasl_shutdown(void) +{ + if (display && !yasl_public_owner()) + return -errno; if (pumping || refresh_depth) - return yasl_public_fail(EBUSY); + return yasl_public_error(EBUSY); + 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); + close_display(); + } + free(delivered_text); + delivered_text = NULL; + atomic_store(&wake_requested, false); +#if YASL_HAS_VULKAN + extern void yasl_private_vulkan_reset(void); + yasl_private_vulkan_reset(); +#endif + return 0; +} +enum yasl_backend yasl_get_backend(void) +{ + return display && yasl_public_owner() ? (enum yasl_backend)display->ops->type + : YASL_BACKEND_AUTO; +} +unsigned yasl_get_features(void) +{ + if (!display || !yasl_public_owner()) + return 0; + static const struct { + uint32_t cap; + unsigned feature; + } map[] = { + {YASL_PRIVATE_CAP_POPUPS, YASL_FEATURE_POPUPS}, + {YASL_PRIVATE_CAP_DRAG, YASL_FEATURE_DRAG}, + {YASL_PRIVATE_CAP_DRAG_CARRY, YASL_FEATURE_DRAG_CARRY}, + {YASL_PRIVATE_CAP_PLACE_WINDOW, YASL_FEATURE_PLACE_WINDOW}, + {YASL_PRIVATE_CAP_PRIMARY_SELECTION, YASL_FEATURE_PRIMARY_SELECTION}, + {YASL_PRIVATE_CAP_TABLET, YASL_FEATURE_TABLET}, + {YASL_PRIVATE_CAP_TEXT_INPUT, YASL_FEATURE_TEXT_INPUT}, + {YASL_PRIVATE_CAP_RELATIVE_POINTER, YASL_FEATURE_LOCKED_CURSOR}, + }; + uint32_t caps = yasl_private_display_caps(display); + unsigned features = display->ops->surface_create_gl ? YASL_FEATURE_OPENGL : 0; +#if YASL_HAS_VULKAN + features |= YASL_FEATURE_VULKAN; +#endif + for (size_t i = 0; i < sizeof(map) / sizeof(map[0]); i++) + if (caps & map[i].cap) + features |= map[i].feature; + return features; +} + +/* Surfaces */ +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); +} +static bool size_valid(int width, int height) +{ + return width > 0 && height > 0 && width <= 32767 && height <= 32767 && + (unsigned long long)width * (unsigned)height <= INT32_MAX / 4; +} +/* Keeps the overlap of the old canvas and zeroes the rest. */ +static bool canvas_fit(yasl_surface *s, int w, int h) +{ + if (s->canvas && s->canvas_width == w && s->canvas_height == h) + return true; + if (w <= 0 || h <= 0) + return yasl_public_fail(EAGAIN); + uint32_t *pixels = calloc((size_t)w * (size_t)h, sizeof(*pixels)); + if (!pixels) + return yasl_public_fail(ENOMEM); + int keep_w = w < s->canvas_width ? w : s->canvas_width; + int keep_h = h < s->canvas_height ? h : s->canvas_height; + for (int y = 0; s->canvas && y < keep_h; y++) + memcpy(pixels + (size_t)y * w, s->canvas + (size_t)y * s->canvas_width, + (size_t)keep_w * sizeof(*pixels)); + free(s->canvas); + s->canvas = pixels; + s->canvas_width = w; + s->canvas_height = h; return true; } -static void draw_committed(yasl_surface *s, struct yasl_private_frame *frame, void *ud) +/* CPU presenter: every drawn frame shows the canvas; before the first present there is none. */ +static void draw_canvas(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) + if (s->presenter != YASL_PRIVATE_PRESENTER_CPU || !frame->target.pixels || !s->canvas) 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); + if (!canvas_fit(s, w, h)) + return; + memcpy(frame->target.pixels, s->canvas, (size_t)w * (size_t)h * sizeof(uint32_t)); 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) +static void register_surface(yasl_surface *s, unsigned flags, yasl_surface *parent, void *userdata) { s->creation_flags = flags; s->parent = parent; + s->userdata = userdata; if (flags & YASL_PRIVATE_SURFACE_CPU) s->presenter = YASL_PRIVATE_PRESENTER_CPU; - struct yasl_private_window_cbs cb = {.draw = draw_committed}; + struct yasl_private_window_cbs cb = {.draw = draw_canvas}; 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_private_surface_notify_configure(s, yasl_private_surface_geometry(s)); +} +/* Translates zero-means-default GL options into the private attribute list. */ +static bool gl_attributes(const struct yasl_gl_options *gl, int attrs[static 31]) +{ + int major = gl->major, minor = gl->minor; + if (!major && !minor) { + major = 3; + minor = gl->es ? 0 : 2; + } + bool profiles = !gl->es && (major > 3 || (major == 3 && minor >= 2)); + int profile = !profiles ? YASL_PRIVATE_GL_ANY + : gl->compatibility ? YASL_PRIVATE_GL_COMPATIBILITY + : YASL_PRIVATE_GL_CORE; + int depth = gl->no_depth_stencil ? 0 : 24, stencil = gl->no_depth_stencil ? 0 : 8; + const int list[] = { + YASL_PRIVATE_GL_MAJOR, major, YASL_PRIVATE_GL_MINOR, minor, + YASL_PRIVATE_GL_PROFILE, profile, + YASL_PRIVATE_GL_API, gl->es ? YASL_PRIVATE_GL_OPENGL_ES : YASL_PRIVATE_GL_OPENGL, + YASL_PRIVATE_GL_RED_BITS, 8, YASL_PRIVATE_GL_GREEN_BITS, 8, + YASL_PRIVATE_GL_BLUE_BITS, 8, YASL_PRIVATE_GL_ALPHA_BITS, 8, + YASL_PRIVATE_GL_DEPTH_BITS, depth, YASL_PRIVATE_GL_STENCIL_BITS, stencil, + YASL_PRIVATE_GL_SAMPLES, gl->samples, YASL_PRIVATE_GL_DEBUG, gl->debug, + YASL_PRIVATE_GL_FORWARD_COMPAT, 0, YASL_PRIVATE_GL_SRGB, gl->srgb, + YASL_PRIVATE_GL_END, + }; + memcpy(attrs, list, sizeof(list)); + struct yasl_private_gl_options parsed; + return yasl_private_gl_parse_options(attrs, &parsed) || yasl_public_fail(EINVAL); } -yasl_surface *yasl_surface_create(const struct yasl_surface_options *options) +static yasl_surface *create_gl(const struct yasl_private_window_spec *spec, const int *attrs, + yasl_surface *share) { - if (!options || (unsigned)options->type > YASL_SURFACE_CPU || (options->flags & ~14u)) { - yasl_public_fail(EINVAL); - return NULL; + if (!display->ops->surface_create_gl) + return yasl_public_fail(ENOTSUP), NULL; + errno = 0; + yasl_surface *s = display->ops->surface_create_gl(display, spec, attrs, share); + if (!s) + return yasl_public_fail(errno), NULL; + if (!yasl_private_events_register(s)) { + int error = errno; + display->ops->surface_destroy(s); + return yasl_public_fail(error), 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)) + s->presenter = YASL_PRIVATE_PRESENTER_GL; + return s; +} +yasl_surface *yasl_surface_create(const struct yasl_surface_options *o) +{ + if (!o || (unsigned)o->type > YASL_SURFACE_NATIVE || !size_valid(o->width, o->height)) + return yasl_public_fail(EINVAL), NULL; + if (pumping || refresh_depth) + return yasl_public_fail(EBUSY), NULL; + unsigned flags = (o->type == YASL_SURFACE_CPU ? YASL_PRIVATE_SURFACE_CPU : 0u) | + (o->undecorated ? YASL_PRIVATE_SURFACE_UNDECORATED : 0u) | + (o->fixed_size ? YASL_PRIVATE_SURFACE_FIXED_SIZE : 0u); + int attrs[31]; + yasl_surface *share = o->type == YASL_SURFACE_GL ? o->gl.share : NULL; + if (o->type == YASL_SURFACE_GL && !gl_attributes(&o->gl, attrs)) return NULL; + if (share && (!display || share->display != display || + share->presenter != YASL_PRIVATE_PRESENTER_GL)) + return yasl_public_fail(EINVAL), NULL; bool fresh = !display; - if (!initialize()) + if (!yasl_public_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); + struct yasl_private_window_spec spec = { + .title = o->title, + .size = {o->width, o->height}, + .flags = flags, + .external_gpu = o->type != YASL_SURFACE_CPU, + }; + yasl_surface *s = o->type == YASL_SURFACE_GL ? create_gl(&spec, attrs, share) + : 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; + if (fresh) + close_display(); + return yasl_public_fail(error), NULL; } - yasl_public_register_surface(s, flags, NULL); - s->userdata = options->userdata; + register_surface(s, flags, NULL, o->userdata); + s->refresh = o->refresh; return s; } int yasl_surface_destroy(yasl_surface *s) @@ -163,122 +298,39 @@ int yasl_surface_destroy(yasl_surface *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 *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) { + for (yasl_surface *p = display->surfaces; p && !child; 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->canvas); + s->canvas = 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) +struct yasl_surface_info yasl_surface_get_info(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; + return (struct yasl_surface_info){.scale = 1.0}; 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; + return (struct yasl_surface_info){ + .width = logical(s->size.x, scale), + .height = logical(s->size.y, scale), + .pixel_width = s->size.x, + .pixel_height = s->size.y, + .scale = scale, + .state = s->state, + }; } void *yasl_surface_get_userdata(const yasl_surface *s) { @@ -302,54 +354,69 @@ int yasl_surface_set_title(yasl_surface *s, const char *title) 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)); \ +int yasl_surface_request(yasl_surface *s, enum yasl_request request) +{ + if (!yasl_public_connected(s)) + return -errno; + switch (request) { + case YASL_REQUEST_RESTORE: { + uint32_t state = s->state; + errno = 0; + bool ok = !(state & YASL_PRIVATE_STATE_FULLSCREEN) || yasl_private_surface_set_fullscreen(s, false); + if (ok && (state & YASL_PRIVATE_STATE_MAXIMIZED || !(state & YASL_PRIVATE_STATE_FULLSCREEN))) + ok = yasl_private_surface_set_maximized(s, false); + return yasl_public_status(ok || yasl_public_fail(errno)); + } + case YASL_REQUEST_FULLSCREEN: + return STATUS(yasl_private_surface_set_fullscreen(s, true)); + case YASL_REQUEST_MAXIMIZE: + return STATUS(yasl_private_surface_set_maximized(s, true)); + case YASL_REQUEST_MINIMIZE: + return STATUS(yasl_private_surface_minimize(s)); } -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)) + return yasl_public_error(EINVAL); +} +int yasl_surface_request_size(yasl_surface *s, int w, int h) +{ + if (!yasl_public_connected(s)) + return -errno; + double scale = scale_of(s); + if (!size_valid(w, h) || !size_valid((int)ceil(w * scale), (int)ceil(h * scale))) + return yasl_public_error(EINVAL); + return STATUS(yasl_private_surface_set_size(s, (struct yasl_private_vector2i){w, h})); +} 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) + if ((unsigned)cursor > YASL_CURSOR_LOCKED) return yasl_public_error(EINVAL); + bool locked = cursor == YASL_CURSOR_LOCKED; + if (!yasl_private_surface_set_pointer_mode(s, locked ? YASL_PRIVATE_POINTER_RELATIVE + : YASL_PRIVATE_POINTER_NORMAL)) + return yasl_public_error(errno); + if (locked) + return 0; 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) { + if (s->refresh && s->display->modal && pumping && !refresh_depth) { ++refresh_depth; - s->refresh_callback(s, s->refresh_userdata); + s->refresh(s); --refresh_depth; + return; } + struct yasl_private_event ev = {.type = YASL_PRIVATE_EVENT_REFRESH, .surface = s}; + yasl_private_events_push(s->display, s, &ev); } + +/* Text input */ static char *copy_text(const char *data, size_t length) { if (length == SIZE_MAX || (!data && length)) { @@ -357,20 +424,226 @@ static char *copy_text(const char *data, size_t length) return NULL; } char *p = malloc(length + 1); - if (!p) + if (!p) { + errno = ENOMEM; return NULL; + } if (length) memcpy(p, data, length); p[length] = 0; return p; } +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 = (struct yasl_private_rect2i){ + yasl_private_scale_to_device(x, s->scale_120), yasl_private_scale_to_device(y, s->scale_120), + yasl_private_scale_to_device(w, s->scale_120), yasl_private_scale_to_device(h, s->scale_120)}; + return true; +} +static bool utf8_boundary(const char *text, size_t length, size_t at) +{ + return at >= length || ((unsigned char)text[at] & 0xc0) != 0x80; +} +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, const struct yasl_text_input *in) +{ + if (!yasl_public_surface(s, false)) + return -errno; + struct yasl_private_rect2i caret = {0}; + char *copy = NULL; + if (in) { + if ((unsigned)in->purpose > YASL_TEXT_TERMINAL || (!in->text && in->length) || + in->selection_begin > in->selection_end || in->selection_end > in->length || + in->length > INT32_MAX || (in->length && memchr(in->text, 0, in->length))) + return yasl_public_error(EINVAL); + if (!yasl_private_utf8_validate(in->text ? in->text : "", in->length) || + !utf8_boundary(in->text, in->length, in->selection_begin) || + !utf8_boundary(in->text, in->length, in->selection_end)) + return yasl_public_error(EILSEQ); + if (!logical_rect(s, in->caret_x, in->caret_y, in->caret_width, in->caret_height, &caret)) + return -errno; + if (!(copy = copy_text(in->text, in->length))) + return yasl_public_error(errno); + } + struct text_state { + bool active; + unsigned purpose; + char *surrounding; + size_t length, begin, end; + uint64_t revision, generation; + } old = {s->text_input_active, s->text_purpose, s->text_surrounding, s->text_length, + s->text_begin, s->text_end, s->text_revision, s->text_generation}; + bool active = in != NULL; + unsigned purpose = in ? (unsigned)in->purpose : s->text_purpose; + if (active != old.active || purpose != old.purpose) + ++s->text_generation; + s->text_input_active = active; + s->text_purpose = purpose; + s->text_surrounding = copy; + s->text_length = in ? in->length : 0; + s->text_begin = in ? in->selection_begin : 0; + s->text_end = in ? in->selection_end : 0; + s->text_revision = in ? in->revision : old.revision; + if (!text_update(s)) { + int error = errno; + free(copy); + s->text_input_active = old.active; + s->text_purpose = old.purpose; + s->text_surrounding = old.surrounding; + s->text_length = old.length; + s->text_begin = old.begin; + s->text_end = old.end; + s->text_revision = old.revision; + s->text_generation = old.generation; + return yasl_public_error(error); + } + free(old.surrounding); + if (active && s->display->ops->surface_set_ime_caret) + yasl_private_surface_set_ime_caret(s, caret); + return 0; +} + +/* Window management */ +int yasl_surface_begin_move_resize(yasl_surface *s, unsigned edges) +{ + if (!yasl_public_connected(s)) + return -errno; + if ((edges & ~15u) || (edges & 3u) == 3u || (edges & 12u) == 12u) + return yasl_public_error(EINVAL); + if (!edges) + return STATUS(yasl_private_surface_start_interactive_move(s)); + return STATUS(yasl_private_surface_start_interactive_resize(s, (enum yasl_private_resize_edge)edges)); +} +int yasl_surface_show_window_menu(yasl_surface *s, double x, double y) +{ + struct yasl_private_rect2i r; + if (!yasl_public_connected(s) || !logical_rect(s, x, y, 0, 0, &r)) + return -errno; + return STATUS(yasl_private_surface_show_window_menu(s, (struct yasl_private_vector2i){r.x, r.y})); +} + +/* Popups */ +static bool popup_spec(yasl_surface *parent, const struct yasl_popup_options *o, + struct yasl_private_popup_spec *out) +{ + /* YASL_EDGE_* bits to xdg_positioner anchor/gravity values; -1 rejects opposite edges. */ + static const signed char placement[16] = {0, 1, 2, -1, 3, 5, 6, -1, 4, 7, 8, -1, -1, -1, -1, -1}; + if (!o || !size_valid(o->width, o->height) || o->anchor > 15 || o->gravity > 15 || + placement[o->anchor] < 0 || placement[o->gravity] < 0 || (o->adjust & ~63u)) + return yasl_public_fail(EINVAL); + double scale = scale_of(parent); + if (!size_valid((int)ceil(o->width * scale), (int)ceil(o->height * scale))) + return yasl_public_fail(EINVAL); + *out = (struct yasl_private_popup_spec){ + .size = {yasl_private_scale_to_device(o->width, parent->scale_120), + yasl_private_scale_to_device(o->height, parent->scale_120)}, + .anchor_edges = (unsigned)placement[o->anchor], + .gravity = (unsigned)placement[o->gravity], + .constraints = o->adjust ? o->adjust + : YASL_ADJUST_FLIP_X | YASL_ADJUST_FLIP_Y | YASL_ADJUST_SLIDE_X | + YASL_ADJUST_SLIDE_Y, + }; + return logical_rect(parent, o->x, o->y, o->anchor_width, o->anchor_height, &out->anchor); +} +yasl_surface *yasl_popup_create(yasl_surface *parent, const struct yasl_popup_options *o) +{ + struct yasl_private_popup_spec spec; + if (!yasl_public_connected(parent) || !yasl_public_surface(parent, true) || + !popup_spec(parent, o, &spec)) + return NULL; + errno = 0; + yasl_surface *s = yasl_private_surface_create_popup(parent, &spec); + if (!s) + return yasl_public_fail(errno), NULL; + register_surface(s, YASL_PRIVATE_SURFACE_CPU, parent, o->userdata); + return s; +} +int yasl_popup_reposition(yasl_surface *s, const struct yasl_popup_options *o) +{ + struct yasl_private_popup_spec spec; + if (!yasl_public_connected(s)) + return -errno; + if (!s->parent) + return yasl_public_error(EINVAL); + if (!popup_spec(s->parent, o, &spec)) + return -errno; + return STATUS(yasl_private_surface_popup_reposition(s, &spec)); +} + +/* Clipboard, drag and drop */ +int yasl_clipboard_set(enum yasl_selection selection, const char *text) +{ + if (!yasl_public_owner()) + return -errno; + if (atomic_load(&display->offline)) + return yasl_public_error(ENOTCONN); + return STATUS(yasl_private_clipboard_set(display, (enum yasl_private_selection)selection, text)); +} +int yasl_clipboard_request(yasl_surface *s, enum yasl_selection selection) +{ + if (!yasl_public_connected(s)) + return -errno; + return STATUS(yasl_private_clipboard_request(s, (enum yasl_private_selection)selection) != 0); +} +int yasl_drag_start(yasl_surface *s, const struct yasl_drag_image *source) +{ + if (!yasl_public_connected(s)) + return -errno; + struct yasl_private_drag_image image = {0}; + if (source) + image = (struct yasl_private_drag_image){ + .pixels = source->pixels, + .size = {source->pixel_width, source->pixel_height}, + .hotspot = {source->hotspot_x, source->hotspot_y}}; + return STATUS(yasl_private_display_drag_start(display, s, source ? &image : NULL, NULL, NULL)); +} +int yasl_drag_cancel(void) +{ + if (!yasl_public_owner()) + return -errno; + yasl_private_display_drag_cancel(display); + return 0; +} +int yasl_drag_carry(yasl_surface *s, double x, double y) +{ + struct yasl_private_rect2i r; + if (!yasl_public_connected(s) || !logical_rect(s, x, y, 0, 0, &r)) + return -errno; + return STATUS(yasl_private_surface_drag_carry(s, (struct yasl_private_vector2i){r.x, r.y})); +} +int yasl_surface_place_under_pointer(yasl_surface *s, double x, double y) +{ + struct yasl_private_rect2i r; + if (!yasl_public_connected(s) || !logical_rect(s, x, y, 0, 0, &r)) + return -errno; + return STATUS(yasl_private_surface_place_under_pointer(s, (struct yasl_private_vector2i){r.x, r.y})); +} +size_t yasl_drop_next_path(const char **cursor, char *out, size_t cap) +{ + return yasl_private_drop_next_path(cursor, out, cap); +} + +/* Events */ 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 +/* Folds native size, scale and state 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) @@ -386,18 +659,31 @@ static bool window_event(yasl_surface *s, struct yasl_private_vector2i size, uin 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; + e->window = (struct yasl_surface_info){ + .width = logical(size.x, scale), + .height = logical(size.y, scale), + .pixel_width = size.x, + .pixel_height = size.y, + .scale = scale, + .state = state, + }; return true; } static bool refresh_ready(yasl_surface *s) { return s->reported_size.x > 0 && s->reported_size.y > 0; } +static bool with_text(yasl_event *e, const char *data, size_t length) +{ + e->length = length; + e->text = deliver_text(data, length); + return e->text != NULL; +} +/* IME offsets: -1 (unknown) places the cursor at the end of the text. */ +static size_t ime_offset(int offset, size_t length) +{ + return offset < 0 || (size_t)offset > length ? length : (size_t)offset; +} static bool convert_event(const struct yasl_private_event *p, yasl_event *e) { *e = (yasl_event){.surface = p->surface, .time_ns = p->time_ns}; @@ -405,19 +691,19 @@ static bool convert_event(const struct yasl_private_event *p, yasl_event *e) switch (p->type) { case YASL_PRIVATE_EVENT_WINDOW_CLOSE: e->type = YASL_EVENT_CLOSE; - break; + return true; case YASL_PRIVATE_EVENT_DISPLAY_QUIT: e->type = YASL_EVENT_QUIT; - break; + return true; case YASL_PRIVATE_EVENT_DISPLAY_REOPEN: e->type = YASL_EVENT_REOPEN; - break; + return true; case YASL_PRIVATE_EVENT_DISPLAY_LOST: e->type = YASL_EVENT_DISPLAY_LOST; - break; + return true; case YASL_PRIVATE_EVENT_DISPLAY_RESTORED: e->type = YASL_EVENT_DISPLAY_RESTORED; - break; + return true; case YASL_PRIVATE_EVENT_REFRESH: if (!p->surface) return false; @@ -427,159 +713,134 @@ static bool convert_event(const struct yasl_private_event *p, yasl_event *e) } p->surface->refresh_owed = false; e->type = YASL_EVENT_REFRESH; - break; + return true; 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 true; + case YASL_PRIVATE_EVENT_FOCUS: + /* Activation comes from the window state; losing focus releases held input. */ + if (p->focus.focused) 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); - } + e->type = YASL_EVENT_INPUT_RESET; + return true; 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); + return p->surface && 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); + return p->surface && 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; + e->action = (unsigned)p->key.action; + e->mods = p->key.mods; + e->key = (struct yasl_key_event){p->key.key, p->key.codepoint, p->key.shifted, + p->key.native_code}; + return true; 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; + return with_text(e, p->text.data, p->text.len); 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; + e->action = YASL_DROP_RELEASE; + return with_text(e, p->text.data, p->text.len); 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; + e->action = (unsigned)p->paste.status; + return with_text(e, p->paste.data, p->paste.len); 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; + e->ime = (struct yasl_ime_event){ime_offset(p->composition.begin, p->composition.len), + ime_offset(p->composition.end, p->composition.len), + p->composition.revision}; + return with_text(e, p->composition.data, p->composition.len); 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; + e->ime = (struct yasl_ime_event){p->text_edit.begin, p->text_edit.end, p->text_edit.revision}; + return with_text(e, p->text_edit.data, p->text_edit.len); 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->action = p->pen.action; + e->x = p->pen.x / scale; + e->y = p->pen.y / scale; + e->pen = (struct yasl_pen_event){ + .device_id = p->pen.device_id, + .tool_id = p->pen.tool_id, + .pressure = p->pen.pressure, + .tilt_x = p->pen.tilt_x, + .tilt_y = p->pen.tilt_y, + .rotation = p->pen.rotation, + .distance = p->pen.distance, + .tangential = p->pen.tangential, + .wheel_degrees = p->pen.wheel_degrees, + .tool = (enum yasl_pen_tool)p->pen.tool, + .axes = p->pen.axes_valid, + .buttons = p->pen.buttons, + .button = p->pen.changed_button, + .in_contact = p->pen.in_contact, + }; + return true; + case YASL_PRIVATE_EVENT_POINTER_SCROLL: { + unsigned valid = p->pointer.scroll_valid ? p->pointer.scroll_valid : YASL_PRIVATE_SCROLL_LINES; + bool lines = valid & YASL_PRIVATE_SCROLL_LINES, pixels = valid & YASL_PRIVATE_SCROLL_PIXELS; 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; + e->mods = p->pointer.mods; + e->x = p->pointer.pos.x / scale; + e->y = p->pointer.pos.y / scale; + e->scroll = (struct yasl_scroll_event){ + .lines_x = lines ? p->pointer.scroll.x : 0, + .lines_y = lines ? p->pointer.scroll.y : 0, + .pixels_x = pixels ? p->pointer.pixel_scroll.x / scale : 0, + .pixels_y = pixels ? p->pointer.pixel_scroll.y / scale : 0, + .source = (enum yasl_scroll_source)p->pointer.scroll_source, + .phase = (enum yasl_scroll_phase)p->pointer.scroll_phase, + .momentum = p->pointer.scroll_momentum, + }; + return true; + } 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; + e->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->mods = p->pointer.mods; + e->x = (p->pointer.precise_position ? p->pointer.precise_x : p->pointer.pos.x) / scale; + e->y = (p->pointer.precise_position ? p->pointer.precise_y : p->pointer.pos.y) / scale; + e->pointer = (struct yasl_pointer_event){p->pointer.delta.x / scale, p->pointer.delta.y / scale, + p->pointer.button, p->pointer.buttons}; + return true; 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; + e->action = p->drag.action == YASL_PRIVATE_DRAG_OVER ? YASL_DROP_OVER + : p->drag.action == YASL_PRIVATE_DRAG_LEAVE ? YASL_DROP_LEAVE + : YASL_DROP_RELEASE; + e->x = p->drag.pos.x / scale; + e->y = p->drag.pos.y / scale; + return true; 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; + e->action = p->drop.action == YASL_PRIVATE_DROP_LEAVE ? YASL_DROP_LEAVE : YASL_DROP_OVER; + e->x = p->drop.pos.x / scale; + e->y = p->drop.pos.y / scale; + return true; 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()}; + *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)) { + /* Input method events from an older text field are stale. */ if (p.surface && ((p.type == YASL_PRIVATE_EVENT_COMPOSITION && p.composition.generation != p.surface->text_generation) || (p.type == YASL_PRIVATE_EVENT_TEXT_EDIT && @@ -591,547 +852,130 @@ static int take_event(yasl_event *out) *out = event; return 1; } - if (errno) { - yasl_public_fail(errno); - return -errno; - } + if (errno) + return yasl_public_error(errno); } return 0; } -int yasl_wait_event(yasl_event *out, int timeout) +/* Internal wakeups, such as a redraw request, keep waiting; only an event, the deadline or + * yasl_wake end the wait. */ +static int pump(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; + uint64_t deadline = timeout > 0 ? yasl_time_ns() + (uint64_t)timeout * 1000000u : 0; + for (;;) { + int n = take_event(out); + if (n) + return n; + yasl_private_display_flush(display); + if ((n = take_event(out))) + return n; + if (atomic_exchange(&wake_requested, false)) + return 0; + int wait = timeout; + if (timeout > 0) { + uint64_t now = yasl_time_ns(); + if (now >= deadline) + return 0; + wait = (int)((deadline - now + 999999u) / 1000000u); } + if (yasl_private_display_dispatch(display, wait) < 0) + return yasl_public_error(errno); yasl_private_display_flush(display); - if (n >= 0) - n = take_event(out); + if ((n = take_event(out)) || timeout == 0) + return n; } - pumping = false; - return n; } -static uint64_t cpu_sequence; -int yasl_cpu_acquire(yasl_surface *s, struct yasl_cpu_buffer *out) +int yasl_wait_event(yasl_event *out, int timeout) { 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) -{ + *out = (yasl_event){.type = YASL_EVENT_NONE}; 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; + if (pumping || refresh_depth) + return yasl_public_error(EBUSY); + free(delivered_text); + delivered_text = NULL; + pumping = true; + int n = pump(out, timeout); + pumping = false; + if (n < 0) + *out = (yasl_event){.type = YASL_EVENT_NONE}; + return n; } -int yasl_surface_set_surrounding_text(yasl_surface *s, const char *text, size_t len, size_t begin, - size_t end, uint64_t revision) +void yasl_wake(void) { - 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; + atomic_store(&wake_requested, true); + pthread_mutex_lock(&lifecycle_mutex); + if (display && display->loop) + yasl_private_loop_kick(display->loop); + pthread_mutex_unlock(&lifecycle_mutex); } -static bool logical_rect(yasl_surface *s, double x, double y, double w, double h, - struct yasl_private_rect2i *r) +uint64_t yasl_time_ns(void) { - 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; + return yasl_private_loop_now_ns(); } -int yasl_surface_set_ime_caret(yasl_surface *s, double x, double y, double w, double h) + +/* CPU rendering */ +uint32_t *yasl_cpu_pixels(yasl_surface *s, int *pw, int *ph) { + if (pw) + *pw = 0; + if (ph) + *ph = 0; 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); + if (s->presenter != YASL_PRIVATE_PRESENTER_CPU) + return yasl_public_fail(ENOTSUP), NULL; + if (!canvas_fit(s, s->size.x, s->size.y)) return NULL; - } - yasl_public_register_surface(s, parent->creation_flags, parent); - return s; + if (pw) + *pw = s->canvas_width; + if (ph) + *ph = s->canvas_height; + return s->canvas; } -int yasl_popup_reposition(yasl_surface *s, const struct yasl_popup_options *options) +int yasl_cpu_present(yasl_surface *s) { - 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)) + if (!yasl_public_connected(s)) 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()) + if (s->presenter != YASL_PRIVATE_PRESENTER_CPU) + return yasl_public_error(ENOTSUP); + if (!canvas_fit(s, s->size.x, s->size.y)) return -errno; - yasl_private_display_drag_cancel(display); + yasl_private_surface_request_frame(s); 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) +/* OpenGL; these may run on a rendering thread, so they read only immutable or atomic state. */ +static bool gl_surface(yasl_surface *s) { - 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; + if (!display) + return yasl_public_fail(ENODEV); + if (!s || s->display != display || s->presenter != YASL_PRIVATE_PRESENTER_GL) + return yasl_public_fail(EINVAL); + return !atomic_load(&display->offline) || yasl_public_fail(ENOTCONN); } 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 (!s && (!display || !display->ops->gl_make_current)) + return 0; /* Nothing can be current. */ + if (s && !gl_surface(s)) + return -errno; 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)); + return STATUS(display->ops->gl_make_current(display, s)); } 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; - } + if (!display) + return yasl_public_fail(ENODEV), NULL; + if (!name || !*name) + return yasl_public_fail(EINVAL), NULL; + if (atomic_load(&display->offline)) + return yasl_public_fail(ENOTCONN), NULL; + if (!display->ops->gl_get_proc_address) + return yasl_public_fail(ENOTSUP), NULL; yasl_gl_proc p = display->ops->gl_get_proc_address(display, name); if (!p) yasl_public_fail(ENOENT); @@ -1139,27 +983,17 @@ yasl_gl_proc yasl_gl_get_proc_address(const char *name) } 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)); + if (!gl_surface(s)) + return -errno; + if (!display->ops->gl_swap_interval) + return yasl_public_error(ENOTSUP); + return STATUS(display->ops->gl_swap_interval(display, s, interval)); } 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)); + if (!gl_surface(s)) + return -errno; + if (!display->ops->gl_swap_buffers) + return yasl_public_error(ENOTSUP); + return STATUS(display->ops->gl_swap_buffers(display, s)); } |
