diff options
| author | Berke Güzel <wenekar1@gmail.com> | 2026-10-15 20:38:14 +0300 |
|---|---|---|
| committer | Berke Güzel <wenekar1@gmail.com> | 2026-10-15 20:38:14 +0300 |
| commit | c2832a63547e6328afde25bb3b8145267eadef47 (patch) | |
| tree | bf03ace79f01df10ddaffd70bea74678ab4ca219 /src/platform/x11/window.c | |
| parent | 3d012d707d0242780cd386868ab511846dc9b0af (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/platform/x11/window.c')
| -rw-r--r-- | src/platform/x11/window.c | 117 |
1 files changed, 56 insertions, 61 deletions
diff --git a/src/platform/x11/window.c b/src/platform/x11/window.c index 630eb91..2751c91 100644 --- a/src/platform/x11/window.c +++ b/src/platform/x11/window.c @@ -143,7 +143,7 @@ struct x11_display { struct x11_display *next_open; Display *native; int screen; - struct yasl_private_loop *loop; + struct yasl_loop *loop; int watch; struct x11_surface *surfaces, *dead; int depth; @@ -354,7 +354,8 @@ static int yasl_private_x11_error_handler(Display *native, XErrorEvent *event) } /* Marks the display lost once and queues DISPLAY_LOST. The poll watch is removed - * by ready(), on the thread that owns the loop, because this can run on a GL thread. */ + * by prepare() or check(), on the thread that owns the loop, because this can run + * on a GL thread. */ static void display_lost(struct x11_display *d) { if (d->lost) return; @@ -1025,7 +1026,7 @@ static void cancel_pastes(struct x11_display *d, struct x11_surface *target) } else if (!target || r->target == target) { /* An INCR transfer in progress is abandoned; its owner times out. */ *p = r->next; - if (r->timeout) yasl_private_loop_del_timer(d->loop, r->timeout); + if (r->timeout) yasl_loop_del_timer(d->loop, r->timeout); if (!d->lost) XDestroyWindow(d->native, r->window); free(r->buf); free(r); @@ -1058,8 +1059,8 @@ static uint64_t paste_timeout(void *ud, yasl_private_timer_id id, uint64_t inter static void paste_rearm(struct paste_request *r) { struct x11_display *d = r->display; - if (r->timeout) yasl_private_loop_del_timer(d->loop, r->timeout); - r->timeout = yasl_private_loop_add_timer(d->loop, PASTE_TIMEOUT_NS, paste_timeout, r); + if (r->timeout) yasl_loop_del_timer(d->loop, r->timeout); + r->timeout = yasl_loop_add_timer(d->loop, PASTE_TIMEOUT_NS, paste_timeout, r); } /* Unlink a paste request at *link and report its outcome to the surface, if alive. */ @@ -1068,7 +1069,7 @@ static void paste_complete(struct x11_display *d, struct paste_request **link, { struct paste_request *r = *link; *link = r->next; - if (r->timeout) yasl_private_loop_del_timer(d->loop, r->timeout); + if (r->timeout) yasl_loop_del_timer(d->loop, r->timeout); XDestroyWindow(d->native, r->window); if (r->target && !r->target->destroyed) yasl_private_surface_complete_paste(&r->target->base, r->id, status, text, len); @@ -2027,7 +2028,7 @@ static void incr_send_retire(struct x11_display *d, struct incr_send *st, bool c *p = st->next; break; } - if (cancel_timer && st->timeout) yasl_private_loop_del_timer(d->loop, st->timeout); + if (cancel_timer && st->timeout) yasl_loop_del_timer(d->loop, st->timeout); free(st->data); free(st); } @@ -2045,8 +2046,8 @@ static uint64_t incr_send_timeout(void *ud, yasl_private_timer_id id, uint64_t i static void incr_send_arm(struct incr_send *st) { struct x11_display *d = st->display; - if (st->timeout) yasl_private_loop_del_timer(d->loop, st->timeout); - st->timeout = yasl_private_loop_add_timer(d->loop, PASTE_TIMEOUT_NS, incr_send_timeout, st); + if (st->timeout) yasl_loop_del_timer(d->loop, st->timeout); + st->timeout = yasl_loop_add_timer(d->loop, PASTE_TIMEOUT_NS, incr_send_timeout, st); } /* Start an INCR send: publish the total size, then wait for the requestor to @@ -2062,7 +2063,7 @@ static bool incr_send_start(struct x11_display *d, Window requestor, Atom proper st->target = target; st->data = data; st->length = length; - st->timeout = yasl_private_loop_add_timer(d->loop, PASTE_TIMEOUT_NS, incr_send_timeout, st); + st->timeout = yasl_loop_add_timer(d->loop, PASTE_TIMEOUT_NS, incr_send_timeout, st); if (!st->timeout) { free(st); return false; @@ -2325,14 +2326,14 @@ static void selection_notify(struct x11_display *d, XSelectionEvent *event) /* Surface destruction detached this accepted request. The reply means * the owner has finished writing the initial property; retire safely. */ *p = r->next; - if (r->timeout) yasl_private_loop_del_timer(d->loop, r->timeout); + if (r->timeout) yasl_loop_del_timer(d->loop, r->timeout); XDestroyWindow(d->native, r->window); free(r); return; } if (event->property == None) { *p = r->next; - if (r->timeout) yasl_private_loop_del_timer(d->loop, r->timeout); + if (r->timeout) yasl_loop_del_timer(d->loop, r->timeout); enum yasl_private_transfer_status status = XGetSelectionOwner(d->native, r->selection) == None ? YASL_PRIVATE_TRANSFER_UNAVAILABLE : YASL_PRIVATE_TRANSFER_FAILED; @@ -2361,7 +2362,7 @@ static void selection_notify(struct x11_display *d, XSelectionEvent *event) return; } *p = r->next; - if (r->timeout) yasl_private_loop_del_timer(d->loop, r->timeout); + if (r->timeout) yasl_loop_del_timer(d->loop, r->timeout); Atom actual = None; int format = 0; unsigned long count = 0, left = 0; @@ -2938,56 +2939,49 @@ static void flush(struct yasl_private_display *base) leave(d); } -static int ready(int fd, short events, void *ud) +/* The connection is read in check(), before any callback. */ +static void ready(void *ud, int fd, short events) +{ + (void)ud; (void)fd; (void)events; +} + +/* A hung-up socket stays readable; stop watching it so the loop does not spin. */ +static void drop_watch(struct x11_display *d) +{ + if (d->watch > 0) yasl_loop_del_fd(d->loop, d->watch); + d->watch = 0; +} + +/* Before the wait: dispatch what Xlib already holds, draw due surfaces, and send + * requests. Loss is final, so a lost display only drops its watch. */ +static bool prepare(struct yasl_private_display *base) { - (void)fd; - struct yasl_private_display *base = ud; struct x11_display *d = (struct x11_display *)base; - if (events & (POLLERR | POLLHUP)) display_lost(d); - if (d->lost) { - /* A hung-up socket stays readable; stop watching it so the loop does not spin. */ - if (d->watch > 0) yasl_private_loop_del_fd(d->loop, d->watch); - d->watch = 0; - return 0; - } - if (events & POLLIN) { + if (!d->lost) { dispatch(base); flush(base); } - return 0; + if (d->lost) { + drop_watch(d); + return false; + } + /* Replies awaited while drawing can read events into Xlib's queue. */ + return XEventsQueued(d->native, QueuedAlready) > 0; } -static int dispatch_timeout(struct yasl_private_display *base, int timeout_ms) +static void check(struct yasl_private_display *base) { struct x11_display *d = (struct x11_display *)base; - if (unlikely(!d || !d->native)) { errno = ENODEV; return -1; } - /* The DISPLAY_LOST event is delivered by the wait that found the loss. */ - if (d->lost) { errno = ENOTCONN; return -1; } - dispatch(base); - if (d->lost) return 0; - for (struct x11_surface *s = d->surfaces; s; s = s->next) { - if (s->want_draw || s->input.count || s->input.reset_pending) { - timeout_ms = 0; - break; - } - } - if (d->loop) { - XFlush(d->native); - if (d->lost) return 0; - if (XEventsQueued(d->native, QueuedAlready) > 0) - timeout_ms = 0; - if (!XPending(d->native) && timeout_ms != 0) { - yasl_private_loop_step(d->loop, timeout_ms); - dispatch(base); - } else { - yasl_private_loop_step(d->loop, 0); - dispatch(base); - } + short revents = d->watch > 0 ? yasl_private_loop_revents(d->loop, d->watch) : 0; + if (revents & (POLLERR | POLLHUP)) display_lost(d); + if (d->lost) { + drop_watch(d); + return; } - return 0; + if (revents & POLLIN) dispatch(base); } -static struct yasl_private_display *display_open(struct yasl_private_loop *loop, +static struct yasl_private_display *display_open(struct yasl_loop *loop, const struct yasl_private_display_spec *spec) { struct x11_display *d = calloc(1, sizeof(*d)); @@ -3046,7 +3040,7 @@ static struct yasl_private_display *display_open(struct yasl_private_loop *loop, XISelectEvents(d->native, RootWindow(d->native, d->screen), &mask, 1); } if (!loop) { - d->loop = yasl_private_loop_create(); + d->loop = yasl_loop_create(); d->owns_loop = true; } else { d->loop = loop; @@ -3055,7 +3049,7 @@ static struct yasl_private_display *display_open(struct yasl_private_loop *loop, d->app_id = strdup(spec && spec->app_id ? spec->app_id : "YASL"); d->base.app_id = d->app_id; if (!d->loop || !d->app_id) { - if (d->owns_loop) yasl_private_loop_destroy(d->loop); + if (d->owns_loop) yasl_loop_destroy(d->loop); free(d->app_id); XCloseDisplay(d->native); display_unregister(d); @@ -3095,14 +3089,14 @@ static struct yasl_private_display *display_open(struct yasl_private_loop *loop, ATOM(uri_list, "text/uri-list"); #undef ATOM if (d->loop) { - d->watch = yasl_private_loop_add_fd(d->loop, ConnectionNumber(d->native), POLLIN, ready, &d->base); + d->watch = yasl_loop_add_fd(d->loop, ConnectionNumber(d->native), POLLIN, ready, &d->base); if (d->watch < 0) { if (d->im) XCloseIM(d->im); XDestroyWindow(d->native, d->selection_window); XCloseDisplay(d->native); display_unregister(d); free(d->app_id); - if (d->owns_loop) yasl_private_loop_destroy(d->loop); + if (d->owns_loop) yasl_loop_destroy(d->loop); free(d); return NULL; } @@ -3118,9 +3112,9 @@ static void display_close(struct yasl_private_display *base) cancel_pastes(d, NULL); incr_cancel_all(d); d->pending_drop.active = false; - if (d->loop && d->watch > 0) yasl_private_loop_del_fd(d->loop, d->watch); + if (d->loop && d->watch > 0) yasl_loop_del_fd(d->loop, d->watch); if (d->owns_loop && d->loop) { - yasl_private_loop_destroy(d->loop); + yasl_loop_destroy(d->loop); d->loop = NULL; d->base.loop = NULL; } @@ -3208,7 +3202,7 @@ static bool request_paste(struct yasl_private_display *base, enum yasl_private_s r->selection = selection; r->id = id; errno = 0; - r->timeout = yasl_private_loop_add_timer(d->loop, PASTE_TIMEOUT_NS, + r->timeout = yasl_loop_add_timer(d->loop, PASTE_TIMEOUT_NS, paste_timeout, r); if (!r->timeout) { XDestroyWindow(d->native, r->window); @@ -3236,8 +3230,8 @@ static void schedule_redraw(struct yasl_surface *base) if (!s->destroyed) { s->want_draw = true; struct x11_display *d = (struct x11_display *)base->display; - if (d && d->loop) - yasl_private_loop_kick(d->loop); + if (d) + yasl_private_loop_wake(d->loop); } } @@ -3668,7 +3662,8 @@ const struct yasl_private_backend_ops yasl_private_x11_backend = { .display_open = display_open, .display_close = display_close, .display_caps = caps, - .display_dispatch_timeout = dispatch_timeout, + .display_prepare = prepare, + .display_check = check, .display_flush = flush, .display_modifiers = mods, .display_copy_text = copy_text, |
