summaryrefslogtreecommitdiff
path: root/src/common/surface.c
diff options
context:
space:
mode:
authorBerke Güzel <wenekar1@gmail.com>2026-10-13 23:41:31 +0300
committerBerke Güzel <wenekar1@gmail.com>2026-10-13 23:41:31 +0300
commit76073fe5fad8799e5850805f2f090af2cb62c741 (patch)
tree9658019babe4760a3634bdab4da0731a10e42e68 /src/common/surface.c
parentdd1775a9927922c14f8679ccbc4d3ca8d5917e20 (diff)
Simplify the public event and CPU API
Applications now need far less ceremony: hello.c is a 20-line loop that handles REFRESH, CLOSE and QUIT and draws into the surface's own pixels. - Fold GEOMETRY, STATE and FOCUS into one WINDOW event carrying size, scale and state; unchanged values send nothing. - Guarantee REFRESH once a window has a size and after every resize, never at size zero. - Merge DROP_HOVER into DROP with over/leave/data actions and give drag and drop actions enums. - Keep event strings valid until the next wait and drop yasl_event_release; empty or failed waits report NONE. - Replace copy-in presentation with a lent pixel buffer: yasl_cpu_pixels and yasl_cpu_present. - Remove yasl_poll_event, yasl_cpu_read_pixels, unused event fields and comments that restated names. - Wayland: resize floating windows in request_size instead of reporting it unsupported, and stop marking new GPU surfaces as Vulkan so yasl_vulkan_create_surface no longer fails with NATIVE_WINDOW_IN_USE. Also includes the in-progress public API refactor that was uncommitted in the tree (removal of the app/loop layer, new surface, GL and Vulkan entry points). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'src/common/surface.c')
-rw-r--r--src/common/surface.c522
1 files changed, 522 insertions, 0 deletions
diff --git a/src/common/surface.c b/src/common/surface.c
new file mode 100644
index 0000000..897b95a
--- /dev/null
+++ b/src/common/surface.c
@@ -0,0 +1,522 @@
+#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 <stdlib.h>
+#include <string.h>
+
+static struct yasl_private_display *display;
+static enum yasl_backend requested_backend;
+static char *application_id;
+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. */
+static _Thread_local enum yasl_error last_error;
+static _Thread_local int native_error;
+
+void yasl_public_set_error(enum yasl_error error, int native)
+{ last_error=error; native_error=native; }
+bool yasl_public_fail(int error)
+{
+ if (!error) error=EIO;
+ enum yasl_error code=YASL_ERROR_IO;
+ if (error==EINVAL || error==EOVERFLOW || error==EILSEQ) code=YASL_ERROR_INVALID;
+ else if (error==ENOTSUP || error==ENOSYS) code=YASL_ERROR_UNSUPPORTED;
+ else if (error==ENOMEM) code=YASL_ERROR_NO_MEMORY;
+ else if (error==EBUSY) code=YASL_ERROR_BUSY;
+ else if (error==EPERM) code=YASL_ERROR_WRONG_THREAD;
+ yasl_public_set_error(code,error); errno=error; return false;
+}
+enum yasl_error yasl_get_error(const char **description)
+{
+ static const char *const messages[]={"No error","Invalid argument","Unsupported operation",
+ "Out of memory","Library not initialized","Operation is busy","Wrong thread",
+ "Input context expired","Native operation failed"};
+ if(description) *description=messages[last_error];
+ return last_error;
+}
+int yasl_get_native_error(void) { return native_error; }
+struct yasl_private_display *yasl_public_display(void) { return display; }
+bool yasl_public_owner(void)
+{
+ if(!display) { yasl_public_set_error(YASL_ERROR_NOT_INITIALIZED,0); return false; }
+ 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;
+}
+static bool initialize(void)
+{
+ if(display) return yasl_public_owner();
+ struct yasl_private_display_spec spec={.app_id=application_id,.backend=(enum yasl_private_backend)requested_backend};
+ struct yasl_private_display *d=yasl_private_display_open(NULL,&spec);
+ if(!d) return yasl_public_fail(errno);
+ pthread_mutex_lock(&lifecycle_mutex);
+ owner=pthread_self(); display=d;
+ pthread_mutex_unlock(&lifecycle_mutex);
+ return true;
+}
+bool yasl_set_app_id(const char *id)
+{
+ if(display) return yasl_public_fail(EBUSY);
+ char *copy=id ? strdup(id) : NULL;
+ if(id&&!copy) return yasl_public_fail(ENOMEM);
+ free(application_id); application_id=copy; return true;
+}
+bool yasl_set_backend(enum yasl_backend backend)
+{
+ if(display) return yasl_public_fail(EBUSY);
+ if(backend<YASL_BACKEND_AUTO || backend>YASL_BACKEND_COCOA) return yasl_public_fail(EINVAL);
+ requested_backend=backend; return true;
+}
+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_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 char *title,int width,int height,unsigned flags)
+{
+ if(!creation_valid(width,height,flags)) return NULL;
+ bool fresh=!display;
+ if(!initialize()) return NULL;
+ struct yasl_private_window_spec spec={.title=title,.size_logical={width,height},
+ .flags=flags,.external_gpu=!(flags&YASL_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); return s;
+}
+void yasl_surface_destroy(yasl_surface *s)
+{
+ if(!s) return;
+ if(!yasl_public_surface(s,true)) return;
+ 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->committed_pixels); s->committed_pixels=NULL;
+ free(s->text_surrounding); s->text_surrounding=NULL;
+ yasl_private_surface_destroy(s);
+}
+void yasl_shutdown(void)
+{
+ if(display && !yasl_public_owner()) return;
+ if(pumping || refresh_depth) { yasl_public_fail(EBUSY); return; }
+ if(display) {
+ 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;
+ free(application_id); application_id=NULL; requested_backend=YASL_BACKEND_AUTO;
+#if YASL_HAS_VULKAN
+ extern void yasl_private_vulkan_reset(void);
+ yasl_private_vulkan_reset();
+#endif
+}
+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_backend(const yasl_surface *s)
+{ return s ? (enum yasl_backend)s->display->ops->type : YASL_BACKEND_AUTO; }
+unsigned yasl_surface_features(const yasl_surface *s)
+{
+ if(!s) 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(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)
+{ 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 scale_of(s); }
+unsigned yasl_surface_state(const yasl_surface *s) { return s?s->state:0; }
+void *yasl_surface_userdata(const yasl_surface *s) { return s?s->userdata:NULL; }
+void yasl_surface_set_userdata(yasl_surface *s,void *ud) { if(yasl_public_surface(s,false)) s->userdata=ud; }
+bool yasl_surface_set_title(yasl_surface *s,const char *title)
+{
+ if(!yasl_public_surface(s,false)) return false;
+ if(!title) return yasl_public_fail(EINVAL);
+ if(!s->display->ops->surface_set_title) return yasl_public_fail(ENOTSUP);
+ s->display->ops->surface_set_title(s,title); return true;
+}
+#define REQUEST(name,priv,args,call) bool name args { if(!yasl_public_surface(s,false)) return false; errno=0; return 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))
+bool yasl_surface_set_cursor(yasl_surface *s,enum yasl_cursor cursor)
+{
+ if(!yasl_public_surface(s,false)) return false;
+ if(cursor<YASL_CURSOR_ARROW || cursor>YASL_CURSOR_HIDDEN) return yasl_public_fail(EINVAL);
+ if(!s->display->ops->surface_set_cursor) return yasl_public_fail(ENOTSUP);
+ yasl_private_surface_set_cursor(s,(enum yasl_private_cursor)cursor); return true;
+}
+void yasl_surface_set_refresh_callback(yasl_surface *s,void(*fn)(yasl_surface *,void *),void *ud)
+{ if(yasl_public_surface(s,false)) { s->refresh_callback=fn; s->refresh_userdata=ud; } }
+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_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 -1; }
+ }
+ return 0;
+}
+int yasl_wait_event(yasl_event *out,int timeout)
+{
+ if(!out) { yasl_public_fail(EINVAL);return -1; }
+ *out=(yasl_event){.type=YASL_EVENT_NONE};
+ if(!yasl_public_owner()) return -1;
+ if(pumping || refresh_depth) { yasl_public_fail(EBUSY);return -1; }
+ 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=-1; }
+ yasl_private_display_flush(display);
+ if(n>=0) n=take_event(out);
+ }
+ pumping=false;return n;
+}
+uint32_t *yasl_cpu_pixels(yasl_surface *s,int *w,int *h)
+{
+ if(w) *w=0;
+ if(h) *h=0;
+ if(!yasl_public_surface(s,false)) return NULL;
+ if(s->presenter!=YASL_PRIVATE_PRESENTER_CPU) { yasl_public_fail(ENOTSUP); return NULL; }
+ int width=s->size.x,height=s->size.y;
+ if(width<=0 || height<=0) { yasl_public_fail(EBUSY); return NULL; }
+ if(width!=s->committed_width || height!=s->committed_height || !s->committed_pixels) {
+ uint32_t *next=calloc((size_t)width*height,4);
+ if(!next) { yasl_public_fail(ENOMEM); return NULL; }
+ int keep_w=width<s->committed_width?width:s->committed_width,keep_h=height<s->committed_height?height:s->committed_height;
+ if(s->committed_pixels) for(int y=0;y<keep_h;y++)
+ memcpy(next+(size_t)y*width,s->committed_pixels+(size_t)y*s->committed_width,(size_t)keep_w*4);
+ free(s->committed_pixels); s->committed_pixels=next; s->committed_width=width; s->committed_height=height;
+ }
+ if(w) *w=width;
+ if(h) *h=height;
+ return s->committed_pixels;
+}
+bool yasl_cpu_present(yasl_surface *s)
+{
+ if(!yasl_public_surface(s,false)) return false;
+ if(s->presenter!=YASL_PRIVATE_PRESENTER_CPU) return yasl_public_fail(ENOTSUP);
+ if(!s->committed_pixels || s->committed_width!=s->size.x || s->committed_height!=s->size.y) return yasl_public_fail(EINVAL);
+ yasl_private_surface_request_frame(s); return true;
+}
+int yasl_double_click_ms(void) { return display?yasl_private_display_double_click_ms(display):400; }
+bool yasl_clipboard_set(enum yasl_selection sel,const char *text)
+{ if(!yasl_public_owner())return false;errno=0;return 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); }
+bool yasl_surface_set_text_input(yasl_surface *s,bool active,enum yasl_text_purpose purpose)
+{
+ if(!yasl_public_surface(s,false))return false;
+ if(purpose<YASL_TEXT_NORMAL||purpose>YASL_TEXT_TERMINAL)return yasl_public_fail(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 false; }return true;
+}
+bool 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 false;
+ if((!text&&len)||begin>end||end>len||len>INT32_MAX || (len&&memchr(text,0,len)))return yasl_public_fail(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_fail(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_fail(EILSEQ);
+ for(size_t j=1;j<n;j++)if(((unsigned char)text[i+j]&0xc0)!=0x80)return yasl_public_fail(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_fail(EILSEQ);
+ i+=n;
+ }
+ char *copy=copy_text(text,len);if(!copy)return yasl_public_fail(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 false;}
+ free(old);return true;
+}
+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);
+ 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;
+}
+bool yasl_surface_set_ime_caret(yasl_surface *s,double x,double y,double w,double h)
+{
+ if(!yasl_public_surface(s,false))return false;
+ struct yasl_private_rect2i r;
+ if(!logical_rect(s,x,y,w,h,&r))return false;
+ if(!s->display->ops->surface_set_ime_caret)return yasl_public_fail(ENOTSUP);
+ yasl_private_surface_set_ime_caret(s,r);return true;
+}
+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))) {
+ yasl_public_set_error(YASL_ERROR_CONTEXT_EXPIRED,0);return false;
+ }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 || (unsigned)anchor>8 || (unsigned)gravity>8 || (constraints&~63u))return yasl_public_fail(EINVAL);
+ *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,.positioner_explicit=true};
+ return logical_rect(s,x,y,aw,ah,&out->anchor);
+}
+yasl_surface *yasl_popup_create(yasl_surface *parent,int w,int h,double x,double y,double aw,double ah,enum yasl_popup_edge anchor,enum yasl_popup_edge gravity,unsigned constraints,const yasl_event *trigger)
+{
+ 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;
+}
+bool yasl_popup_reposition(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)
+{
+ if(!yasl_public_surface(s,false))return false;
+ struct yasl_private_popup_spec spec;
+ if(!s->parent)return yasl_public_fail(EINVAL);
+ if(!popup_spec(s->parent,w,h,x,y,aw,ah,anchor,gravity,constraints,&spec))return false;
+ errno=0;return yasl_private_surface_popup_reposition(s,&spec)||yasl_public_fail(errno);
+}
+bool yasl_surface_start_interactive_move(yasl_surface *s,const yasl_event *e)
+{ if(!yasl_public_surface(s,false)||!trigger_valid(s,e))return false;errno=0;return yasl_private_surface_start_interactive_move(s)||yasl_public_fail(errno); }
+bool 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 false;errno=0;return yasl_private_surface_start_interactive_resize(s,(enum yasl_private_resize_edge)edge)||yasl_public_fail(errno); }
+bool 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 false;
+ struct yasl_private_rect2i r;if(!logical_rect(s,x,y,0,0,&r))return false;errno=0;return yasl_private_surface_show_window_menu(s,(struct yasl_private_vector2i){r.x,r.y})||yasl_public_fail(errno); }
+bool yasl_drag_start(yasl_surface *s,const yasl_event *e,const uint32_t *pixels,int w,int h,int hx,int hy)
+{
+ if(!yasl_public_surface(s,false)||!trigger_valid(s,e))return false;
+ struct yasl_private_drag_image image={.pixels=pixels,.size={w,h},.hotspot={hx,hy}};
+ errno=0;return yasl_private_display_drag_start(display,s,pixels?&image:NULL,NULL,NULL)||yasl_public_fail(errno);
+}
+void yasl_drag_cancel(void) { if(yasl_public_owner())yasl_private_display_drag_cancel(display); }
+bool yasl_surface_drag_carry(yasl_surface *s,double x,double y)
+{ if(!yasl_public_surface(s,false))return false;
+ struct yasl_private_rect2i r;if(!logical_rect(s,x,y,0,0,&r))return false;errno=0;return yasl_private_surface_drag_carry(s,(struct yasl_private_vector2i){r.x,r.y})||yasl_public_fail(errno); }
+bool yasl_surface_place_under_pointer(yasl_surface *s,double x,double y)
+{ if(!yasl_public_surface(s,false))return false;
+ struct yasl_private_rect2i r;if(!logical_rect(s,x,y,0,0,&r))return false;errno=0;return 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 char *title,int w,int h,unsigned flags,const int *attrs,yasl_surface *share)
+{
+ if(!creation_valid(w,h,flags)||(flags&YASL_SURFACE_CPU)) { yasl_public_fail(EINVAL);return NULL; }
+ struct yasl_private_gl_options parsed;if(!yasl_private_gl_parse_options(attrs,&parsed)){yasl_public_fail(errno);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_logical={w,h},.flags=flags,.external_gpu=true};
+ 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);return s;
+}
+bool yasl_gl_make_current(yasl_surface *s)
+{
+ if(!display){yasl_public_set_error(YASL_ERROR_NOT_INITIALIZED,0);return false;}
+ if(s && (s->display!=display||s->presenter!=YASL_PRIVATE_PRESENTER_GL))return yasl_public_fail(EINVAL);
+ if(!display->ops->gl_make_current)return yasl_public_fail(ENOTSUP);
+ errno=0;return display->ops->gl_make_current(display,s)||yasl_public_fail(errno);
+}
+yasl_gl_proc yasl_gl_get_proc_address(const char *name)
+{
+ if(!display||!name||!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;
+}
+bool yasl_gl_swap_interval(yasl_surface *s,int interval)
+{
+ if(!s||s->presenter!=YASL_PRIVATE_PRESENTER_GL||!s->display->ops->gl_swap_interval)return yasl_public_fail(EINVAL);
+ errno=0;return s->display->ops->gl_swap_interval(s->display,s,interval)||yasl_public_fail(errno);
+}
+bool yasl_gl_swap_buffers(yasl_surface *s)
+{
+ if(!s||s->presenter!=YASL_PRIVATE_PRESENTER_GL||!s->display->ops->gl_swap_buffers)return yasl_public_fail(EINVAL);
+ errno=0;return s->display->ops->gl_swap_buffers(s->display,s)||yasl_public_fail(errno);
+}