summaryrefslogtreecommitdiff
path: root/src/common/surface.c
diff options
context:
space:
mode:
authorBerke Güzel <wenekar1@gmail.com>2026-10-15 20:38:14 +0300
committerBerke Güzel <wenekar1@gmail.com>2026-10-15 20:38:14 +0300
commitc2832a63547e6328afde25bb3b8145267eadef47 (patch)
treebf03ace79f01df10ddaffd70bea74678ab4ca219 /src/common/surface.c
parent3d012d707d0242780cd386868ab511846dc9b0af (diff)
Make the loop public and let the display drive it
An application that has its own descriptors and timers needed a second loop next to yasl's, and could not see yasl's key-repeat timer, clipboard pipes or reconnect timer. Now there is one wait: the application owns a yasl_loop, and yasl_init can run the display on it. - Public yasl_loop_*: descriptors, timers and signals. Descriptor callbacks return void. Timer IDs are 64 bits and never reused; a zero delay runs in the next step. A nested step or run fails with EBUSY, a step off the loop's thread with EPERM, and a quit made before a run ends it at once. An interrupted wait keeps its deadline. Creating a loop fails when it cannot make its wake pipe. - The display drives the loop through prepare, wait and check hooks instead of wrapping its step. Wayland finishes its read before any callback runs, so loop callbacks may create surfaces and do roundtrips. Readiness (queued events, an owed REFRESH) is derived in the step, so the wait never blocks over pending work. A draw that cannot proceed for want of a buffer is no longer counted as work. - yasl_init takes struct yasl_options. With loop and on_event, each step delivers events to on_event and yasl_wait_event fails with EBUSY. EALREADY only for the same options, EBUSY for others. - yasl_surface_request_frame: a REFRESH once the window can show a new frame, for pacing animation. - DISPLAY_LOST reports whether the display reconnects. Pull waits still fail with ENOTCONN after a final loss. - Events pushed from another thread wake the loop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'src/common/surface.c')
-rw-r--r--src/common/surface.c122
1 files changed, 97 insertions, 25 deletions
diff --git a/src/common/surface.c b/src/common/surface.c
index aeca3d8..5c29a10 100644
--- a/src/common/surface.c
+++ b/src/common/surface.c
@@ -13,9 +13,12 @@ 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; /* Text of the last returned event; freed by the next wait. */
+/* The options of the open display; on_event set means callback mode. */
+static struct yasl_options active;
+static char *active_app_id;
+static bool queue_failure_reported;
bool yasl_public_fail(int error)
{
@@ -62,20 +65,74 @@ bool yasl_public_connected(yasl_surface *s)
/* 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)
+static bool refresh_ready(yasl_surface *s);
+static int take_event(yasl_event *out);
+/* An owed REFRESH is an event the queue does not hold. */
+static bool public_pending(struct yasl_private_display *d)
+{
+ for (yasl_surface *s = d->surfaces; s; s = s->event_next)
+ if (s->refresh_owed && refresh_ready(s))
+ return true;
+ return false;
+}
+/* Callback mode: runs in each loop step, after the display is read and before
+ * descriptor callbacks. Bounded, so a handler that keeps making events cannot
+ * starve the loop's other sources; the rest go in the next step, which does
+ * not wait while they are pending. */
+static void deliver_events(struct yasl_private_display *d)
{
- if (display)
- return yasl_public_owner() ? yasl_public_error(EALREADY) : -errno;
- if ((unsigned)backend > YASL_BACKEND_COCOA)
+ if (yasl_private_events_error(d) && !queue_failure_reported) {
+ queue_failure_reported = true;
+ active.on_event(active.userdata, &(yasl_event){
+ .type = YASL_EVENT_DISPLAY_LOST, .action = YASL_LOST_FINAL, .time_ns = yasl_time_ns()});
+ }
+ for (int i = 0; i < 256; i++) {
+ yasl_event event;
+ if (take_event(&event) <= 0)
+ break;
+ active.on_event(active.userdata, &event);
+ free(delivered_text);
+ delivered_text = NULL;
+ }
+}
+static bool same_options(const struct yasl_options *o)
+{
+ const char *app_id = o->app_id ? o->app_id : "";
+ return o->backend == active.backend && o->loop == active.loop &&
+ o->on_event == active.on_event && o->userdata == active.userdata &&
+ !strcmp(app_id, active_app_id ? active_app_id : "");
+}
+int yasl_init(const struct yasl_options *options)
+{
+ struct yasl_options o = options ? *options : (struct yasl_options){0};
+ if (display) {
+ if (!yasl_public_owner())
+ return -errno;
+ return yasl_public_error(same_options(&o) ? EALREADY : EBUSY);
+ }
+ if ((unsigned)o.backend > YASL_BACKEND_COCOA || !o.loop != !o.on_event)
return yasl_public_error(EINVAL);
+ if (o.loop && !yasl_private_loop_owned(o.loop))
+ return yasl_public_error(EPERM);
+ char *app_id = o.app_id ? strdup(o.app_id) : NULL;
+ if (o.app_id && !app_id)
+ return yasl_public_error(ENOMEM);
struct yasl_private_display_spec spec = {
- .app_id = app_id,
- .backend = (enum yasl_private_backend)backend,
+ .app_id = o.app_id,
+ .backend = (enum yasl_private_backend)o.backend,
};
errno = 0;
- struct yasl_private_display *d = yasl_private_display_open(NULL, &spec);
- if (!d)
+ struct yasl_private_display *d = yasl_private_display_open(o.loop, &spec);
+ if (!d) {
+ free(app_id);
return yasl_public_error(errno);
+ }
+ d->pending = public_pending;
+ d->deliver = o.on_event ? deliver_events : NULL;
+ active = o;
+ active.app_id = NULL;
+ active_app_id = app_id;
+ queue_failure_reported = false;
pthread_mutex_lock(&lifecycle_mutex);
owner = pthread_self();
display = d;
@@ -84,7 +141,7 @@ int yasl_init(enum yasl_backend backend, const char *app_id)
}
bool yasl_public_initialize(void)
{
- return display ? yasl_public_owner() : yasl_init(YASL_BACKEND_AUTO, NULL) == 0;
+ return display ? yasl_public_owner() : yasl_init(NULL) == 0;
}
static void close_display(void)
{
@@ -98,7 +155,7 @@ int yasl_shutdown(void)
{
if (display && !yasl_public_owner())
return -errno;
- if (pumping || refresh_depth)
+ if (refresh_depth || (display && yasl_private_loop_stepping(display->loop)))
return yasl_public_error(EBUSY);
if (display) {
for (yasl_surface *s = display->surfaces; s; s = s->event_next)
@@ -110,6 +167,9 @@ int yasl_shutdown(void)
}
free(delivered_text);
delivered_text = NULL;
+ free(active_app_id);
+ active_app_id = NULL;
+ active = (struct yasl_options){0};
atomic_store(&wake_requested, false);
#if YASL_HAS_VULKAN
extern void yasl_private_vulkan_reset(void);
@@ -260,7 +320,7 @@ 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)
+ if (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) |
@@ -387,6 +447,12 @@ int yasl_surface_request_size(yasl_surface *s, int w, int h)
return yasl_public_error(EINVAL);
return STATUS(yasl_private_surface_set_size(s, (struct yasl_private_vector2i){w, h}));
}
+int yasl_surface_request_frame(yasl_surface *s)
+{
+ if (!yasl_public_connected(s))
+ return -errno;
+ return STATUS(yasl_private_surface_request_shown_frame(s));
+}
int yasl_surface_set_cursor(yasl_surface *s, enum yasl_cursor cursor)
{
if (!yasl_public_surface(s, false))
@@ -409,7 +475,8 @@ void yasl_private_surface_notify_refresh(yasl_surface *s)
{
if (!s || !s->display)
return;
- if (s->refresh && s->display->modal && pumping && !refresh_depth) {
+ /* modal holds only inside the system's own loop, within the wait. */
+ if (s->refresh && s->display->modal && !refresh_depth) {
++refresh_depth;
s->refresh(s);
--refresh_depth;
@@ -703,6 +770,7 @@ static bool convert_event(const struct yasl_private_event *p, yasl_event *e)
return true;
case YASL_PRIVATE_EVENT_DISPLAY_LOST:
e->type = YASL_EVENT_DISPLAY_LOST;
+ e->action = display->reconnects ? YASL_LOST_RECONNECTING : YASL_LOST_FINAL;
return true;
case YASL_PRIVATE_EVENT_DISPLAY_RESTORED:
e->type = YASL_EVENT_DISPLAY_RESTORED;
@@ -869,11 +937,14 @@ static int pump(yasl_event *out, int timeout)
int n = take_event(out);
if (n)
return n;
- yasl_private_display_flush(display);
- if ((n = take_event(out)))
- return n;
+ int error = yasl_private_events_error(display);
+ if (error)
+ return yasl_public_error(error);
if (atomic_exchange(&wake_requested, false))
return 0;
+ /* A loss that does not reconnect ends the waits once its event is out. */
+ if (atomic_load(&display->offline) && !display->reconnects)
+ return yasl_public_error(ENOTCONN);
int wait = timeout;
if (timeout > 0) {
uint64_t now = yasl_time_ns();
@@ -881,11 +952,11 @@ static int pump(yasl_event *out, int timeout)
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 = take_event(out)) || timeout == 0)
- return n;
+ int stepped = yasl_loop_step(display->loop, wait);
+ if (stepped < 0)
+ return yasl_public_error(-stepped);
+ if (timeout == 0)
+ return take_event(out);
}
}
int yasl_wait_event(yasl_event *out, int timeout)
@@ -895,13 +966,11 @@ int yasl_wait_event(yasl_event *out, int timeout)
*out = (yasl_event){.type = YASL_EVENT_NONE};
if (!yasl_public_owner())
return -errno;
- if (pumping || refresh_depth)
+ if (active.on_event || refresh_depth || yasl_private_loop_stepping(display->loop))
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;
@@ -1014,5 +1083,8 @@ int yasl_gl_present(yasl_surface *s)
return -errno;
if (!display->ops->gl_swap_buffers)
return yasl_public_error(ENOTSUP);
- return STATUS(display->ops->gl_swap_buffers(display, s));
+ int status = STATUS(display->ops->gl_swap_buffers(display, s));
+ if (status == 0)
+ atomic_store(&s->presented_ns, yasl_private_loop_now_ns());
+ return status;
}