diff options
Diffstat (limited to 'src/platform')
| -rw-r--r-- | src/platform/cocoa/api.h | 5 | ||||
| -rw-r--r-- | src/platform/cocoa/window.m | 79 | ||||
| -rw-r--r-- | src/platform/posix/loop.c | 646 | ||||
| -rw-r--r-- | src/platform/wayland/api.h | 5 | ||||
| -rw-r--r-- | src/platform/wayland/backend.c | 20 | ||||
| -rw-r--r-- | src/platform/wayland/buffer.c | 8 | ||||
| -rw-r--r-- | src/platform/wayland/clipboard.c | 25 | ||||
| -rw-r--r-- | src/platform/wayland/clipboard.h | 2 | ||||
| -rw-r--r-- | src/platform/wayland/internal.h | 4 | ||||
| -rw-r--r-- | src/platform/wayland/keyboard.c | 9 | ||||
| -rw-r--r-- | src/platform/wayland/pointer.c | 2 | ||||
| -rw-r--r-- | src/platform/wayland/surface.c | 147 | ||||
| -rw-r--r-- | src/platform/x11/window.c | 117 |
13 files changed, 519 insertions, 550 deletions
diff --git a/src/platform/cocoa/api.h b/src/platform/cocoa/api.h index 006954c..0660ffd 100644 --- a/src/platform/cocoa/api.h +++ b/src/platform/cocoa/api.h @@ -6,10 +6,11 @@ struct cocoa_display; struct cocoa_surface; -struct cocoa_display *cocoa_display_open(struct yasl_private_loop *loop, const struct yasl_private_display_spec *spec); +struct cocoa_display *cocoa_display_open(struct yasl_loop *loop, const struct yasl_private_display_spec *spec); void cocoa_display_close(struct cocoa_display *d); uint32_t cocoa_display_caps(const struct cocoa_display *d); -int cocoa_display_dispatch_timeout(struct cocoa_display *d, int timeout_ms); +bool cocoa_display_prepare(struct cocoa_display *d); +int cocoa_display_wait(struct cocoa_display *d, int timeout_ms); void cocoa_display_flush(struct cocoa_display *d); int cocoa_display_modifiers(const struct cocoa_display *d); bool cocoa_display_drag_start(struct cocoa_display *d, struct cocoa_surface *origin, const struct yasl_private_drag_image *image, const struct yasl_private_drag_listener *l, void *ud); diff --git a/src/platform/cocoa/window.m b/src/platform/cocoa/window.m index a78c2ce..c5022dd 100644 --- a/src/platform/cocoa/window.m +++ b/src/platform/cocoa/window.m @@ -129,7 +129,7 @@ struct cocoa_display { int count; int capacity; char *app_id; - struct yasl_private_loop *loop; + struct yasl_loop *loop; id key_up_monitor; NSCursor *hidden_cursor; YaslAppDelegate *delegate; @@ -1413,10 +1413,18 @@ bool cocoa_surface_present(struct cocoa_surface *w, const struct yasl_private_fr return true; } +/* render_and_commit draws only when this holds; readiness uses it too, so a + * window that cannot draw never keeps the loop from waiting. */ +static bool draw_possible(const struct cocoa_surface *w) +{ + return w->base.presenter == YASL_PRIVATE_PRESENTER_CPU && !w->destroyed && !w->closed && + w->cbs->draw && !w->rendering && w->size.x > 0 && w->size.y > 0 && + !(w->base.state & YASL_PRIVATE_STATE_SUSPENDED); +} + static void render_and_commit(struct cocoa_surface *w) { - if (w->base.presenter != YASL_PRIVATE_PRESENTER_CPU || w->destroyed || w->closed || !w->cbs->draw || w->rendering || w->size.x <= 0 || w->size.y <= 0 || - (w->base.state & YASL_PRIVATE_STATE_SUSPENDED)) + if (!draw_possible(w)) return; size_t count = (size_t)w->size.x * (size_t)w->size.y; @@ -1482,7 +1490,7 @@ static void loop_ready(CFFileDescriptorRef fd, CFOptionFlags flags, void *info) /* A kqueue mirroring the loop's poll set, or -1. complete is false when some * descriptor could not be registered. */ -static int loop_kqueue(struct yasl_private_loop *loop, bool *complete) +static int loop_kqueue(struct yasl_loop *loop, bool *complete) { *complete = true; int kq = kqueue(); @@ -1511,30 +1519,30 @@ static bool is_loop_wake(NSEvent *ev) return ev.type == NSEventTypeApplicationDefined && ev.subtype == LOOP_WAKE_SUBTYPE; } -int cocoa_display_dispatch_timeout(struct cocoa_display *d, int timeout_ms) +/* Before the wait: draw due windows. True when a window can draw now. */ +bool cocoa_display_prepare(struct cocoa_display *d) { assert([NSThread isMainThread]); - if (!d || d->pumping) - return -1; + cocoa_display_flush(d); for (int i = 0; i < d->count; i++) { struct cocoa_surface *w = d->windows[i]; - if (w->input.count || w->input.reset_pending || - (w->want_draw && (!w->link || w->frame_due || w->unpaced))) { - timeout_ms = 0; - break; - } + if (w->want_draw && (!w->link || w->frame_due || w->unpaced) && draw_possible(w)) + return true; } + return false; +} + +/* The loop's wait. The loop has already shortened timeout_ms to its next + * timer, and reads descriptor readiness after this returns. */ +int cocoa_display_wait(struct cocoa_display *d, int timeout_ms) +{ + assert([NSThread isMainThread]); + if (d->pumping) + return -EBUSY; d->pumping = true; d->depth++; int count = 0; @try { - if (d->loop) { - int timer_to = yasl_private_loop_next_timeout_ms(d->loop); - if (timer_to >= 0 && (timeout_ms < 0 || timer_to < timeout_ms)) { - timeout_ms = timer_to; - } - } - /* AppKit waits on Mach ports, not on loop descriptors. While waiting, * a run loop source on a kqueue of the loop's descriptors posts a wake * event, so kicks, posts, signals, and watches end the wait. */ @@ -1592,10 +1600,6 @@ int cocoa_display_dispatch_timeout(struct cocoa_display *d, int timeout_ms) dequeue:YES]; } } - - if (d->loop) { - yasl_private_loop_step(d->loop, 0); - } } @finally { d->depth--; d->pumping = false; @@ -1632,8 +1636,7 @@ void cocoa_surface_schedule_redraw(struct cocoa_surface *w) w->frame_due = true; w->link.paused = NO; } - if (darwin_ctx.loop) - yasl_private_loop_kick(darwin_ctx.loop); + yasl_private_loop_wake(darwin_ctx.loop); } static void app_init(const char *app_name) @@ -1688,7 +1691,7 @@ static void place_window(NSWindow *window) darwin_ctx.cascade = [window cascadeTopLeftFromPoint:darwin_ctx.cascade]; } -struct cocoa_display *cocoa_display_open(struct yasl_private_loop *loop, const struct yasl_private_display_spec *spec) +struct cocoa_display *cocoa_display_open(struct yasl_loop *loop, const struct yasl_private_display_spec *spec) { if (![NSThread isMainThread]) { errno = EPERM; @@ -1701,7 +1704,7 @@ struct cocoa_display *cocoa_display_open(struct yasl_private_loop *loop, const s darwin_ctx.base.native_handle = (__bridge void *)[NSApplication sharedApplication]; darwin_ctx.open = true; if (!loop) { - darwin_ctx.loop = yasl_private_loop_create(); + darwin_ctx.loop = yasl_loop_create(); darwin_ctx.owns_loop = true; } else { darwin_ctx.loop = loop; @@ -1726,7 +1729,7 @@ void cocoa_display_close(struct cocoa_display *d) d->key_up_monitor = nil; } if (d->owns_loop && d->loop) { - yasl_private_loop_destroy(d->loop); + yasl_loop_destroy(d->loop); d->loop = NULL; d->base.loop = NULL; } @@ -2538,7 +2541,7 @@ struct yasl_private_timing cocoa_surface_timing(const struct cocoa_surface *w) return timing; } -static struct yasl_private_display *impl_display_open(struct yasl_private_loop *loop, const struct yasl_private_display_spec *spec) +static struct yasl_private_display *impl_display_open(struct yasl_loop *loop, const struct yasl_private_display_spec *spec) { struct cocoa_display *result = cocoa_display_open(loop, spec); return result ? &result->base : NULL; @@ -2554,9 +2557,19 @@ static uint32_t impl_display_caps(const struct yasl_private_display *d) return cocoa_display_caps((const struct cocoa_display *)d); } -static int impl_display_dispatch_timeout(struct yasl_private_display *d, int timeout_ms) +static bool impl_display_prepare(struct yasl_private_display *d) +{ + return cocoa_display_prepare((struct cocoa_display *)d); +} + +static int impl_display_wait(struct yasl_private_display *d, int timeout_ms) +{ + return cocoa_display_wait((struct cocoa_display *)d, timeout_ms); +} + +static void impl_display_check(struct yasl_private_display *d) { - return cocoa_display_dispatch_timeout((struct cocoa_display *)d, timeout_ms); + (void)d; } static void impl_display_flush(struct yasl_private_display *d) @@ -2787,7 +2800,9 @@ const struct yasl_private_backend_ops yasl_private_cocoa_backend = { .display_open = impl_display_open, .display_close = impl_display_close, .display_caps = impl_display_caps, - .display_dispatch_timeout = impl_display_dispatch_timeout, + .display_prepare = impl_display_prepare, + .display_wait = impl_display_wait, + .display_check = impl_display_check, .display_flush = impl_display_flush, .display_modifiers = impl_display_modifiers, .display_double_click_ms = impl_display_double_click_ms, diff --git a/src/platform/posix/loop.c b/src/platform/posix/loop.c index 49132f8..6624575 100644 --- a/src/platform/posix/loop.c +++ b/src/platform/posix/loop.c @@ -6,7 +6,9 @@ #include <errno.h> #include <fcntl.h> +#include <limits.h> #include <poll.h> +#include <pthread.h> #include <signal.h> #include <stdatomic.h> #include <stdlib.h> @@ -16,33 +18,33 @@ #define YASL_PRIVATE_LOOP_INIT_CAP 32 -typedef int (*yasl_private_loop_fd_fn)(int fd, short revents, void *ud); +typedef void (*fd_fn)(void *ud, int fd, short revents); -struct yasl_private_fd_source { - yasl_private_loop_fd_fn fn; +struct fd_source { + fd_fn fn; void *ud; int id; int fd; short events; }; -struct yasl_private_timer_source { +struct timer_source { yasl_private_timer_cb fn; void *ud; uint64_t interval_ns; uint64_t deadline_ns; - yasl_private_timer_id id; + uint64_t id; bool deleted; }; -struct yasl_private_post { +struct post { void (*fn)(void *ud); void *ud; - struct yasl_private_post *next; + struct post *next; }; /* Signals this loop watches; the previous actions are restored on unwatch. */ -struct yasl_private_signal_watch { +struct signal_watch { void (*cb)(void *ud, int signo); void *ud; int count; @@ -50,28 +52,34 @@ struct yasl_private_signal_watch { struct sigaction old[NSIG]; }; -struct yasl_private_loop { - struct yasl_private_fd_source *fds; +struct yasl_loop { + struct fd_source *fds; struct pollfd *pollfds; int count; int capacity; int next_id; - bool running; + pthread_t owner; + /* Set by yasl_loop_quit; run clears it when it returns. */ + bool quit; + bool stepping; int wake_rfd; int wake_wfd; int wake_watch; - struct yasl_private_timer_source *timers; + struct timer_source *timers; int timer_count; int timer_capacity; - uint32_t next_timer_id; + uint64_t next_timer_id; int timer_firing_depth; yasl_private_mutex post_mutex; - struct yasl_private_post *post_head; - struct yasl_private_post *post_tail; + struct post *post_head; + struct post *post_tail; - struct yasl_private_signal_watch *signals; + struct signal_watch *signals; + + struct yasl_private_loop_driver driver; + bool has_driver; }; /* Handlers run on any thread, so they only touch lock-free atomics: they mark @@ -79,7 +87,7 @@ struct yasl_private_loop { * stored as fd + 1 so that the zero initializer means no pipe. */ static atomic_int signal_wake_fd[NSIG]; static atomic_bool signal_pending[NSIG]; -static struct yasl_private_loop *signal_owner[NSIG]; +static struct yasl_loop *signal_owner[NSIG]; uint64_t yasl_private_loop_now_ns(void) { @@ -88,25 +96,25 @@ uint64_t yasl_private_loop_now_ns(void) return (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec; } -static void run_posts(struct yasl_private_loop *l) +static void run_posts(struct yasl_loop *l) { yasl_private_mutex_lock(&l->post_mutex); - struct yasl_private_post *p = l->post_head; + struct post *p = l->post_head; l->post_head = l->post_tail = NULL; yasl_private_mutex_unlock(&l->post_mutex); while (p) { - struct yasl_private_post *next = p->next; + struct post *next = p->next; p->fn(p->ud); free(p); p = next; } } -static void run_signals(struct yasl_private_loop *l) +static void run_signals(struct yasl_loop *l) { /* A callback may unwatch, so re-check the watch before each delivery. */ for (int signo = 1; signo < NSIG; signo++) { - struct yasl_private_signal_watch *w = l->signals; + struct signal_watch *w = l->signals; if (!w) return; if (signal_owner[signo] != l || !atomic_exchange(&signal_pending[signo], false)) continue; @@ -114,148 +122,143 @@ static void run_signals(struct yasl_private_loop *l) } } -static int loop_wake_cb(int fd, short revents, void *ud) +static void loop_wake_cb(void *ud, int fd, short revents) { (void)revents; char buf[128]; ssize_t n; while ((n = read(fd, buf, sizeof(buf))) > 0 || (n < 0 && errno == EINTR)) {} - struct yasl_private_loop *l = ud; + struct yasl_loop *l = ud; run_posts(l); run_signals(l); - return 0; } -struct yasl_private_loop *yasl_private_loop_create(void) +static bool open_wake_pipe(int p[2]) { - struct yasl_private_loop *l = calloc(1, sizeof(*l)); - if (!l) return NULL; +#if defined(__linux__) && defined(O_CLOEXEC) + return pipe2(p, O_CLOEXEC | O_NONBLOCK) == 0; +#else + if (pipe(p) != 0) return false; + for (int i = 0; i < 2; i++) { + int flags = fcntl(p[i], F_GETFL, 0); + if (flags < 0 || fcntl(p[i], F_SETFL, flags | O_NONBLOCK) < 0 || + fcntl(p[i], F_SETFD, FD_CLOEXEC) < 0) { + int err = errno; + close(p[0]); + close(p[1]); + errno = err; + return false; + } + } + return true; +#endif +} +yasl_loop *yasl_loop_create(void) +{ + struct yasl_loop *l = calloc(1, sizeof(*l)); + if (!l) return NULL; l->capacity = YASL_PRIVATE_LOOP_INIT_CAP; l->fds = malloc((size_t)l->capacity * sizeof(*l->fds)); l->pollfds = malloc((size_t)l->capacity * sizeof(*l->pollfds)); - if (!l->fds || !l->pollfds) { - free(l->fds); - free(l->pollfds); - free(l); - return NULL; + int p[2]; + int err = 0; + if (!l->fds || !l->pollfds) + err = ENOMEM; + else if ((err = yasl_private_mutex_init(&l->post_mutex)) == 0 && !open_wake_pipe(p)) { + err = errno; + yasl_private_mutex_destroy(&l->post_mutex); } - - if (yasl_private_mutex_init(&l->post_mutex) != 0) { + if (err) { free(l->fds); free(l->pollfds); free(l); + errno = err; return NULL; } - + l->owner = pthread_self(); l->next_id = 1; l->next_timer_id = 1; - l->wake_rfd = -1; - l->wake_wfd = -1; - - int p[2]; -#if defined(__linux__) && defined(O_CLOEXEC) - if (pipe2(p, O_CLOEXEC | O_NONBLOCK) == 0) { - l->wake_rfd = p[0]; - l->wake_wfd = p[1]; - } -#else - if (pipe(p) == 0) { - int f0 = fcntl(p[0], F_GETFL, 0); - int f1 = fcntl(p[1], F_GETFL, 0); - if (f0 >= 0) fcntl(p[0], F_SETFL, f0 | O_NONBLOCK); - if (f1 >= 0) fcntl(p[1], F_SETFL, f1 | O_NONBLOCK); - fcntl(p[0], F_SETFD, FD_CLOEXEC); - fcntl(p[1], F_SETFD, FD_CLOEXEC); - l->wake_rfd = p[0]; - l->wake_wfd = p[1]; - } -#endif - - if (l->wake_rfd >= 0) { - l->wake_watch = yasl_private_loop_add_fd(l, l->wake_rfd, POLLIN, loop_wake_cb, l); - } + l->wake_rfd = p[0]; + l->wake_wfd = p[1]; + /* The first watch fits the initial capacity, so it cannot fail. */ + l->wake_watch = yasl_loop_add_fd(l, l->wake_rfd, POLLIN, loop_wake_cb, l); return l; } -void yasl_private_loop_destroy(struct yasl_private_loop *l) +int yasl_loop_destroy(yasl_loop *l) { - if (!l) return; - yasl_private_loop_unwatch_signals(l); - for (struct yasl_private_post *p = l->post_head; p;) { - struct yasl_private_post *next = p->next; + if (!l) return 0; + if (l->stepping || l->has_driver) return -EBUSY; + yasl_loop_unwatch_signals(l); + for (struct post *p = l->post_head; p;) { + struct post *next = p->next; free(p); p = next; } yasl_private_mutex_destroy(&l->post_mutex); - if (l->wake_watch > 0) - yasl_private_loop_del_fd(l, l->wake_watch); - if (l->wake_rfd >= 0) - close(l->wake_rfd); - if (l->wake_wfd >= 0) - close(l->wake_wfd); + close(l->wake_rfd); + close(l->wake_wfd); free(l->fds); free(l->pollfds); free(l->timers); free(l); + return 0; } -int yasl_private_loop_add_fd(struct yasl_private_loop *l, int fd, short events, - int (*fn)(int fd, short revents, void *ud), void *ud) +int yasl_loop_add_fd(yasl_loop *l, int fd, short events, + void (*fn)(void *ud, int fd, short revents), void *ud) { - if (!l || fd < 0) - return -1; - + if (!l || fd < 0 || !fn) return -EINVAL; if (l->count >= l->capacity) { - int new_cap = l->capacity ? l->capacity * 2 : YASL_PRIVATE_LOOP_INIT_CAP; - struct yasl_private_fd_source *new_fds = realloc(l->fds, (size_t)new_cap * sizeof(*new_fds)); - if (!new_fds) return -1; + int new_cap = l->capacity * 2; + struct fd_source *new_fds = realloc(l->fds, (size_t)new_cap * sizeof(*new_fds)); + if (!new_fds) return -ENOMEM; l->fds = new_fds; - struct pollfd *new_pollfds = realloc(l->pollfds, (size_t)new_cap * sizeof(*new_pollfds)); - if (!new_pollfds) return -1; + if (!new_pollfds) return -ENOMEM; l->pollfds = new_pollfds; l->capacity = new_cap; } - int id = l->next_id++; if (l->next_id <= 0) l->next_id = 1; - - l->fds[l->count] = (struct yasl_private_fd_source){ - .fn = fn, .ud = ud, .id = id, .fd = fd, .events = events, - }; - l->pollfds[l->count] = (struct pollfd){ - .fd = fd, .events = events, .revents = 0, - }; + l->fds[l->count] = (struct fd_source){.fn = fn, .ud = ud, .id = id, .fd = fd, .events = events}; + l->pollfds[l->count] = (struct pollfd){.fd = fd, .events = events}; l->count++; return id; } -int yasl_private_loop_del_fd(struct yasl_private_loop *l, int id) +static int fd_index(const struct yasl_loop *l, int id) { - if (!l || id <= 0) return -1; - for (int i = 0; i < l->count; i++) { - if (l->fds[i].id == id) { - l->count--; - l->fds[i] = l->fds[l->count]; - l->pollfds[i] = l->pollfds[l->count]; - return 0; - } - } + for (int i = 0; l && id > 0 && i < l->count; i++) + if (l->fds[i].id == id) + return i; return -1; } -int yasl_private_loop_set_fd_events(struct yasl_private_loop *l, int id, short events) +int yasl_loop_del_fd(yasl_loop *l, int id) { - if (!l || id <= 0) return -1; - for (int i = 0; i < l->count; i++) { - if (l->fds[i].id == id) { - l->fds[i].events = events; - l->pollfds[i].events = events; - return 0; - } - } - return -1; + int i = fd_index(l, id); + if (i < 0) return -ENOENT; + l->count--; + l->fds[i] = l->fds[l->count]; + l->pollfds[i] = l->pollfds[l->count]; + return 0; +} + +int yasl_loop_set_fd_events(yasl_loop *l, int id, short events) +{ + int i = fd_index(l, id); + if (i < 0) return -ENOENT; + l->fds[i].events = events; + l->pollfds[i].events = events; + return 0; +} + +short yasl_private_loop_revents(const yasl_loop *l, int id) +{ + int i = fd_index(l, id); + return i < 0 ? 0 : l->pollfds[i].revents; } static void wake_write(int fd) @@ -270,18 +273,23 @@ static void wake_write(int fd) errno = saved; } -void yasl_private_loop_kick(struct yasl_private_loop *l) +void yasl_private_loop_kick(yasl_loop *l) { - if (!l || l->wake_wfd < 0) return; - wake_write(l->wake_wfd); + if (l) wake_write(l->wake_wfd); } -bool yasl_private_loop_post(struct yasl_private_loop *l, void (*fn)(void *ud), void *ud) +void yasl_private_loop_wake(yasl_loop *l) { - if (!l || !fn || l->wake_wfd < 0) return false; - struct yasl_private_post *p = malloc(sizeof(*p)); + if (l && !pthread_equal(l->owner, pthread_self())) + wake_write(l->wake_wfd); +} + +bool yasl_private_loop_post(yasl_loop *l, void (*fn)(void *ud), void *ud) +{ + if (!l || !fn) return false; + struct post *p = malloc(sizeof(*p)); if (!p) return false; - *p = (struct yasl_private_post){.fn = fn, .ud = ud}; + *p = (struct post){.fn = fn, .ud = ud}; yasl_private_mutex_lock(&l->post_mutex); if (l->post_tail) l->post_tail->next = p; @@ -305,85 +313,49 @@ static bool signal_valid(int signo) return signo > 0 && signo < NSIG && signo != SIGKILL && signo != SIGSTOP; } -bool yasl_private_loop_watch_signals(struct yasl_private_loop *l, const int *signals, int n, - void (*cb)(void *ud, int signo), void *ud) +int yasl_loop_watch_signals(yasl_loop *l, const int *signals, int n, + void (*cb)(void *ud, int signo), void *ud) { - if (!l || !signals || n <= 0 || !cb || l->wake_wfd < 0) { - errno = EINVAL; - return false; - } + if (!l || !signals || n <= 0 || !cb) return -EINVAL; + if (l->signals) return -EBUSY; bool wanted[NSIG] = {0}; for (int i = 0; i < n; i++) { - if (!signal_valid(signals[i])) { - errno = EINVAL; - return false; - } - if (signal_owner[signals[i]] && signal_owner[signals[i]] != l) { - errno = EBUSY; - return false; - } + if (!signal_valid(signals[i])) return -EINVAL; + if (signal_owner[signals[i]]) return -EBUSY; wanted[signals[i]] = true; } - struct yasl_private_signal_watch *w = calloc(1, sizeof(*w)); - if (!w) return false; + struct signal_watch *w = calloc(1, sizeof(*w)); + if (!w) return -ENOMEM; w->cb = cb; w->ud = ud; - - /* Keep the old watch's saved actions for signals that stay watched, so - * that unwatch restores what the application had before any watch. */ - struct yasl_private_signal_watch *old = l->signals; struct sigaction sa = {.sa_handler = signal_handler, .sa_flags = SA_RESTART}; sigemptyset(&sa.sa_mask); for (int signo = 1; signo < NSIG; signo++) { if (!wanted[signo]) continue; - int prev = -1; - for (int i = 0; old && i < old->count; i++) - if (old->signals[i] == signo) prev = i; - if (prev >= 0) { - w->old[w->count] = old->old[prev]; - } else { - atomic_store(&signal_pending[signo], false); - atomic_store(&signal_wake_fd[signo], l->wake_wfd + 1); - if (sigaction(signo, &sa, &w->old[w->count]) != 0) { - int err = errno; - atomic_store(&signal_wake_fd[signo], 0); - for (int i = 0; i < w->count; i++) { - int done = w->signals[i]; - bool kept = false; - for (int j = 0; old && j < old->count; j++) - if (old->signals[j] == done) kept = true; - if (kept) continue; - sigaction(done, &w->old[i], NULL); - atomic_store(&signal_wake_fd[done], 0); - } - free(w); - errno = err; - return false; + atomic_store(&signal_pending[signo], false); + atomic_store(&signal_wake_fd[signo], l->wake_wfd + 1); + if (sigaction(signo, &sa, &w->old[w->count]) != 0) { + int err = errno; + atomic_store(&signal_wake_fd[signo], 0); + for (int i = 0; i < w->count; i++) { + sigaction(w->signals[i], &w->old[i], NULL); + atomic_store(&signal_wake_fd[w->signals[i]], 0); } + free(w); + return -err; } w->signals[w->count++] = signo; } - - /* Restore signals the old watch had that the new one drops. */ - for (int i = 0; old && i < old->count; i++) { - int signo = old->signals[i]; - if (wanted[signo]) continue; - sigaction(signo, &old->old[i], NULL); - atomic_store(&signal_wake_fd[signo], 0); - atomic_store(&signal_pending[signo], false); - signal_owner[signo] = NULL; - } for (int i = 0; i < w->count; i++) signal_owner[w->signals[i]] = l; - free(old); l->signals = w; - return true; + return 0; } -void yasl_private_loop_unwatch_signals(struct yasl_private_loop *l) +void yasl_loop_unwatch_signals(yasl_loop *l) { if (!l || !l->signals) return; - struct yasl_private_signal_watch *w = l->signals; + struct signal_watch *w = l->signals; l->signals = NULL; for (int i = 0; i < w->count; i++) { int signo = w->signals[i]; @@ -395,19 +367,19 @@ void yasl_private_loop_unwatch_signals(struct yasl_private_loop *l) free(w); } -static void timer_cleanup_deleted(struct yasl_private_loop *l) +static void timer_cleanup_deleted(struct yasl_loop *l) { - if (!l || l->timer_firing_depth > 0) return; + if (l->timer_firing_depth > 0) return; int dest = 0; for (int src = 0; src < l->timer_count; src++) { - struct yasl_private_timer_source t = l->timers[src]; + struct timer_source t = l->timers[src]; l->timers[dest] = t; dest += !t.deleted; } l->timer_count = dest; } -bool yasl_private_loop_timer_armed(const struct yasl_private_loop *l, yasl_private_timer_id id) +bool yasl_private_loop_timer_armed(const yasl_loop *l, yasl_private_timer_id id) { if (!l || id == 0) return false; for (int i = 0; i < l->timer_count; i++) @@ -416,7 +388,7 @@ bool yasl_private_loop_timer_armed(const struct yasl_private_loop *l, yasl_priva return false; } -int yasl_private_loop_poll_set(const struct yasl_private_loop *l, const struct pollfd **fds) +int yasl_private_loop_poll_set(const yasl_loop *l, const struct pollfd **fds) { if (!l) { *fds = NULL; @@ -426,215 +398,213 @@ int yasl_private_loop_poll_set(const struct yasl_private_loop *l, const struct p return l->count; } -int yasl_private_loop_next_timeout_ms(const struct yasl_private_loop *l) +int yasl_private_loop_next_timeout_ms(const yasl_loop *l) { - if (!l || l->timer_count <= 0) return -1; + if (!l) return -1; uint64_t now = yasl_private_loop_now_ns(); uint64_t min_deadline = UINT64_MAX; - bool has_active = false; - for (int i = 0; i < l->timer_count; i++) { - if (!l->timers[i].deleted) { - has_active = true; - if (l->timers[i].deadline_ns < min_deadline) - min_deadline = l->timers[i].deadline_ns; - } - } - if (!has_active) return -1; + for (int i = 0; i < l->timer_count; i++) + if (!l->timers[i].deleted && l->timers[i].deadline_ns < min_deadline) + min_deadline = l->timers[i].deadline_ns; + if (min_deadline == UINT64_MAX) return -1; if (min_deadline <= now) return 0; - uint64_t diff = min_deadline - now; - uint64_t ms = (diff + 999999ull) / 1000000ull; - return (ms > (uint64_t)INT_MAX) ? INT_MAX : (int)ms; + uint64_t ms = (min_deadline - now + 999999ull) / 1000000ull; + return ms > (uint64_t)INT_MAX ? INT_MAX : (int)ms; } -yasl_private_timer_id yasl_private_loop_add_timer(struct yasl_private_loop *l, uint64_t interval_ns, - yasl_private_timer_cb cb, void *ud) +uint64_t yasl_loop_add_timer(yasl_loop *l, uint64_t interval_ns, + yasl_private_timer_cb cb, void *ud) { - if (!l || !cb || interval_ns == 0) return 0; - + if (!l || !cb) { + errno = EINVAL; + return 0; + } if (l->timer_count >= l->timer_capacity) { int new_cap = l->timer_capacity ? l->timer_capacity * 2 : 16; - struct yasl_private_timer_source *new_timers = realloc(l->timers, (size_t)new_cap * sizeof(*new_timers)); - if (!new_timers) return 0; + struct timer_source *new_timers = realloc(l->timers, (size_t)new_cap * sizeof(*new_timers)); + if (!new_timers) { + errno = ENOMEM; + return 0; + } l->timers = new_timers; l->timer_capacity = new_cap; } - - uint32_t id = l->next_timer_id++; - if (l->next_timer_id == 0) l->next_timer_id = 1; - - uint64_t now = yasl_private_loop_now_ns(); - l->timers[l->timer_count++] = (struct yasl_private_timer_source){ + /* 64 bits never wrap in practice, so an ID never names a newer timer. */ + uint64_t id = l->next_timer_id++; + l->timers[l->timer_count++] = (struct timer_source){ .fn = cb, .ud = ud, .id = id, .interval_ns = interval_ns, - .deadline_ns = now + interval_ns, - .deleted = false, + .deadline_ns = yasl_private_loop_now_ns() + interval_ns, }; - - yasl_private_loop_kick(l); + /* Owner-thread only, and the next wait computes its timeout from the + * timers, so no wake is needed. */ return id; } -bool yasl_private_loop_del_timer(struct yasl_private_loop *l, yasl_private_timer_id id) +bool yasl_loop_del_timer(yasl_loop *l, uint64_t id) { if (!l || id == 0) return false; for (int i = l->timer_count - 1; i >= 0; i--) { if (l->timers[i].id == id && !l->timers[i].deleted) { l->timers[i].deleted = true; - if (l->timer_firing_depth == 0) { - if (i == l->timer_count - 1) { - while (l->timer_count > 0 && l->timers[l->timer_count - 1].deleted) - l->timer_count--; - } else { - timer_cleanup_deleted(l); - } - } + timer_cleanup_deleted(l); return true; } } return false; } -int yasl_private_loop_step(struct yasl_private_loop *l, int timeout_ms) +bool yasl_private_loop_set_driver(yasl_loop *l, const struct yasl_private_loop_driver *driver) { - if (!l) return -1; - - int timer_to = yasl_private_loop_next_timeout_ms(l); - int effective_timeout = timeout_ms; - if (timer_to >= 0) { - if (effective_timeout < 0 || timer_to < effective_timeout) - effective_timeout = timer_to; - } - - if (l->count <= 0) { - if (effective_timeout > 0) { - struct timespec req = { - .tv_sec = effective_timeout / 1000, - .tv_nsec = (long)(effective_timeout % 1000) * 1000000L, - }; - nanosleep(&req, NULL); - } - } else { - int n; - do { - n = poll(l->pollfds, (nfds_t)l->count, effective_timeout); - } while (n < 0 && errno == EINTR); - - if (n < 0) return -1; + if (!l) return false; + if (driver && l->has_driver) { + errno = EBUSY; + return false; } + l->driver = driver ? *driver : (struct yasl_private_loop_driver){0}; + l->has_driver = driver != NULL; + return true; +} - int handled = 0; +bool yasl_private_loop_owned(const yasl_loop *l) +{ + return l && pthread_equal(l->owner, pthread_self()); +} - if (l->count > 0) { - int count = l->count; - struct yasl_private_fd_source sstack[32]; - struct pollfd pstack[32]; - struct yasl_private_fd_source *scopy = (count <= 32) ? sstack : malloc((size_t)count * sizeof(*scopy)); - struct pollfd *pcopy = (count <= 32) ? pstack : malloc((size_t)count * sizeof(*pcopy)); - if (!scopy || !pcopy) { - if (scopy && scopy != sstack) free(scopy); - if (pcopy && pcopy != pstack) free(pcopy); - return -1; - } +bool yasl_private_loop_stepping(const yasl_loop *l) +{ + return l && l->stepping; +} - memcpy(scopy, l->fds, (size_t)count * sizeof(*scopy)); - memcpy(pcopy, l->pollfds, (size_t)count * sizeof(*pcopy)); - - for (int i = 0; i < count; i++) { - if (pcopy[i].revents) { - short revents = pcopy[i].revents; - // Fast-path: check if source is still at index i before linear scan - bool valid = false; - if (i < l->count && l->fds[i].id == scopy[i].id) { - l->pollfds[i].revents = 0; - valid = true; - } else { - for (int j = 0; j < l->count; j++) { - if (l->fds[j].id == scopy[i].id) { - l->pollfds[j].revents = 0; - valid = true; - break; - } - } - } - if (valid && scopy[i].fn) { - scopy[i].fn(scopy[i].fd, revents, scopy[i].ud); - handled++; - } - } +/* poll with an EINTR retry that keeps the deadline. */ +static int poll_all(struct yasl_loop *l, int timeout_ms) +{ + uint64_t deadline = timeout_ms > 0 ? yasl_private_loop_now_ns() + YASL_PRIVATE_MS_TO_NS(timeout_ms) : 0; + for (;;) { + if (poll(l->pollfds, (nfds_t)l->count, timeout_ms) >= 0) return 0; + if (errno != EINTR) return -errno; + if (timeout_ms > 0) { + uint64_t now = yasl_private_loop_now_ns(); + timeout_ms = now >= deadline ? 0 : (int)((deadline - now + 999999ull) / 1000000ull); } + } +} +static int dispatch_fds(struct yasl_loop *l) +{ + int handled = 0; + int count = l->count; + struct fd_source sstack[32]; + struct pollfd pstack[32]; + struct fd_source *scopy = count <= 32 ? sstack : malloc((size_t)count * sizeof(*scopy)); + struct pollfd *pcopy = count <= 32 ? pstack : malloc((size_t)count * sizeof(*pcopy)); + if (!scopy || !pcopy) { if (scopy != sstack) free(scopy); if (pcopy != pstack) free(pcopy); + return -ENOMEM; + } + memcpy(scopy, l->fds, (size_t)count * sizeof(*scopy)); + memcpy(pcopy, l->pollfds, (size_t)count * sizeof(*pcopy)); + for (int i = 0; i < count; i++) { + if (!pcopy[i].revents) continue; + /* A callback may have removed or moved this watch. */ + int j = i < l->count && l->fds[i].id == scopy[i].id ? i : fd_index(l, scopy[i].id); + if (j < 0) continue; + l->pollfds[j].revents = 0; + scopy[i].fn(scopy[i].ud, scopy[i].fd, pcopy[i].revents); + handled++; } + if (scopy != sstack) free(scopy); + if (pcopy != pstack) free(pcopy); + return handled; +} - if (l->timer_count > 0) { - uint64_t now = yasl_private_loop_now_ns(); - l->timer_firing_depth++; - int initial_count = l->timer_count; - for (int i = 0; i < initial_count; i++) { - if (i >= l->timer_count) break; - if (l->timers[i].deleted || l->timers[i].deadline_ns > now) - continue; - - yasl_private_timer_id tid = l->timers[i].id; - uint64_t interval = l->timers[i].interval_ns; - void *ud = l->timers[i].ud; - struct yasl_private_timer_source timer_src = l->timers[i]; - - uint64_t next_interval = timer_src.fn(ud, tid, interval); - handled++; - - // Fast-path: timer is usually still at index i unless an earlier timer was deleted - if (i < l->timer_count && l->timers[i].id == tid) { - if (next_interval == 0 || l->timers[i].deleted) { - l->timers[i].deleted = true; - } else { - l->timers[i].interval_ns = next_interval; - if (l->timers[i].deadline_ns + next_interval > now) - l->timers[i].deadline_ns += next_interval; - else - l->timers[i].deadline_ns = now + next_interval; - } - } else { - for (int j = 0; j < l->timer_count; j++) { - if (l->timers[j].id == tid) { - if (next_interval == 0 || l->timers[j].deleted) { - l->timers[j].deleted = true; - } else { - l->timers[j].interval_ns = next_interval; - if (l->timers[j].deadline_ns + next_interval > now) - l->timers[j].deadline_ns += next_interval; - else - l->timers[j].deadline_ns = now + next_interval; - } - break; - } - } - } - } - l->timer_firing_depth--; - if (l->timer_firing_depth == 0) { - timer_cleanup_deleted(l); - } +static void reschedule(struct timer_source *t, uint64_t next, uint64_t now) +{ + if (next == 0 || t->deleted) { + t->deleted = true; + return; } + t->interval_ns = next; + t->deadline_ns = t->deadline_ns + next > now ? t->deadline_ns + next : now + next; +} +static int fire_timers(struct yasl_loop *l) +{ + int handled = 0; + uint64_t now = yasl_private_loop_now_ns(); + l->timer_firing_depth++; + /* Timers added by a callback wait for the next step. */ + int initial_count = l->timer_count; + for (int i = 0; i < initial_count && i < l->timer_count; i++) { + if (l->timers[i].deleted || l->timers[i].deadline_ns > now) + continue; + struct timer_source t = l->timers[i]; + uint64_t next = t.fn(t.ud, t.id, t.interval_ns); + handled++; + /* Compaction waits for depth zero, so index i still holds this timer. */ + reschedule(&l->timers[i], next, now); + } + l->timer_firing_depth--; + timer_cleanup_deleted(l); return handled; } -int yasl_private_loop_run(struct yasl_private_loop *l) +static int step(struct yasl_loop *l, int timeout_ms) { - if (!l) return -1; - l->running = true; - while (l->running) { - if (yasl_private_loop_step(l, -1) < 0) - return -1; + /* A driver cannot change during a step: its display cannot close then. */ + const struct yasl_private_loop_driver *d = l->has_driver ? &l->driver : NULL; + if (d && d->prepare(d->ud)) + timeout_ms = 0; + int timer_to = yasl_private_loop_next_timeout_ms(l); + if (timer_to >= 0 && (timeout_ms < 0 || timer_to < timeout_ms)) + timeout_ms = timer_to; + int r; + if (d && d->wait) { + r = d->wait(d->ud, timeout_ms); + /* The platform wait does not fill revents; read them now. */ + if (r >= 0) r = poll_all(l, 0); + } else { + r = poll_all(l, timeout_ms); } - return 0; + /* check closes what prepare opened, whatever the wait returned. */ + if (d) d->check(d->ud); + if (r < 0) return r; + if (d && d->deliver) d->deliver(d->ud); + int fds = dispatch_fds(l); + if (fds < 0) return fds; + return fds + fire_timers(l); +} + +int yasl_loop_step(yasl_loop *l, int timeout_ms) +{ + if (!l) return -EINVAL; + if (!pthread_equal(l->owner, pthread_self())) return -EPERM; + if (l->stepping) return -EBUSY; + l->stepping = true; + int r = step(l, timeout_ms); + l->stepping = false; + return r; +} + +int yasl_loop_run(yasl_loop *l) +{ + if (!l) return -EINVAL; + if (!pthread_equal(l->owner, pthread_self())) return -EPERM; + /* A nested run must not consume the outer run's quit. */ + if (l->stepping) return -EBUSY; + int r = 0; + while (!l->quit) { + r = yasl_loop_step(l, -1); + if (r < 0) break; + } + l->quit = false; + return r < 0 ? r : 0; } -void yasl_private_loop_quit(struct yasl_private_loop *l) +void yasl_loop_quit(yasl_loop *l) { - if (l) l->running = false; + if (l) l->quit = true; } diff --git a/src/platform/wayland/api.h b/src/platform/wayland/api.h index 69f61b1..1fb6d5c 100644 --- a/src/platform/wayland/api.h +++ b/src/platform/wayland/api.h @@ -6,10 +6,11 @@ struct wayland_display; struct wayland_surface; -struct wayland_display *wayland_display_open(struct yasl_private_loop *loop, const struct yasl_private_display_spec *spec); +struct wayland_display *wayland_display_open(struct yasl_loop *loop, const struct yasl_private_display_spec *spec); void wayland_display_close(struct wayland_display *d); uint32_t wayland_display_caps(const struct wayland_display *d); -int wayland_display_dispatch_timeout(struct wayland_display *d, int timeout_ms); +bool wayland_display_prepare(struct wayland_display *d); +void wayland_display_check(struct wayland_display *d); void wayland_display_flush(struct wayland_display *d); int wayland_display_modifiers(const struct wayland_display *d); bool wayland_display_copy_text(struct wayland_display *d, enum yasl_private_selection sel, const char *text); diff --git a/src/platform/wayland/backend.c b/src/platform/wayland/backend.c index 3ff696c..f6207ed 100644 --- a/src/platform/wayland/backend.c +++ b/src/platform/wayland/backend.c @@ -2,7 +2,7 @@ #include "platform/freedesktop/dbus.h" #include <errno.h> -static struct yasl_private_display *impl_display_open(struct yasl_private_loop *loop, const struct yasl_private_display_spec *spec) +static struct yasl_private_display *impl_display_open(struct yasl_loop *loop, const struct yasl_private_display_spec *spec) { struct wayland_display *result = wayland_display_open(loop, spec); return result ? &result->base : NULL; @@ -18,9 +18,19 @@ static uint32_t impl_display_caps(const struct yasl_private_display *d) return wayland_display_caps((const struct wayland_display *)d); } -static int impl_display_dispatch_timeout(struct yasl_private_display *d, int timeout_ms) +static bool impl_display_prepare(struct yasl_private_display *d) { - return wayland_display_dispatch_timeout((struct wayland_display *)d, timeout_ms); + return wayland_display_prepare((struct wayland_display *)d); +} + +static void impl_display_check(struct yasl_private_display *d) +{ + wayland_display_check((struct wayland_display *)d); +} + +static bool impl_surface_frame_in_flight(const struct yasl_surface *s) +{ + return ((const struct wayland_surface *)s)->frame_cb != NULL; } static void impl_display_flush(struct yasl_private_display *d) @@ -497,7 +507,9 @@ const struct yasl_private_backend_ops yasl_private_wayland_backend = { .display_open = impl_display_open, .display_close = impl_display_close, .display_caps = impl_display_caps, - .display_dispatch_timeout = impl_display_dispatch_timeout, + .display_prepare = impl_display_prepare, + .display_check = impl_display_check, + .surface_frame_in_flight = impl_surface_frame_in_flight, .display_flush = impl_display_flush, .display_modifiers = impl_display_modifiers, .display_double_click_ms = impl_display_double_click_ms, diff --git a/src/platform/wayland/buffer.c b/src/platform/wayland/buffer.c index 7ff4bd0..4f42b60 100644 --- a/src/platform/wayland/buffer.c +++ b/src/platform/wayland/buffer.c @@ -335,6 +335,7 @@ static void frame_done(void *data, struct wl_callback *cb, uint32_t ms) w->clock.source = YASL_PRIVATE_TIMING_FRAME_CALLBACK; } + yasl_private_surface_notify_frame_shown(&w->base); if (w->base.presenter != YASL_PRIVATE_PRESENTER_CPU) yasl_private_surface_notify_refresh(&w->base); else if (w->want_draw) @@ -550,6 +551,13 @@ bool wayland_surface_read_pixels(struct wayland_surface *w, uint32_t *out, size_ return yasl_private_copy_frame(b->pixels, (struct yasl_private_vector2i){b->width, b->height}, out, cap, size); } +bool surface_draw_ready(const struct wayland_surface *w) +{ + return w->want_draw && w->configured && !w->closed && + w->base.presenter == YASL_PRIVATE_PRESENTER_CPU && + buffer_pick_free_index(w->buffers, w->cur) >= 0; +} + void surface_draw_frame(struct wayland_surface *w) { if (w->closed || !w->configured || w->base.presenter != YASL_PRIVATE_PRESENTER_CPU) diff --git a/src/platform/wayland/clipboard.c b/src/platform/wayland/clipboard.c index 9a75953..9c019a1 100644 --- a/src/platform/wayland/clipboard.c +++ b/src/platform/wayland/clipboard.c @@ -48,7 +48,7 @@ struct clipboard { struct offer *drag_offer; struct primary_offer *primary_selection; struct primary_offer *primary_pending_offers; - struct yasl_private_loop *loop; + struct yasl_loop *loop; struct transfer *transfers; char *text; char *primary_text; @@ -106,9 +106,9 @@ static void transfer_end(struct transfer *t, bool complete) struct transfer **p = &owner->transfers; while (*p && *p != t) p = &(*p)->next; if (*p) *p = t->next; - yasl_private_loop_del_fd(owner->loop, t->id); + yasl_loop_del_fd(owner->loop, t->id); if (t->timeout) - yasl_private_loop_del_timer(owner->loop, t->timeout); + yasl_loop_del_timer(owner->loop, t->timeout); close(t->fd); if (t->drop) { if (complete && wl_data_offer_get_version(t->drop->proxy) >= @@ -133,10 +133,10 @@ static void transfer_end(struct transfer *t, bool complete) free(t); } -static int transfer_io(int fd, short events, void *ud) +static void transfer_io(void *ud, int fd, short events) { struct transfer *t = ud; - if (events & (POLLERR | POLLNVAL)) { transfer_end(t, false); return 0; } + if (events & (POLLERR | POLLNVAL)) { transfer_end(t, false); return; } if (t->sending) { ssize_t n = write(fd, t->text + t->offset, t->len - t->offset); if (n > 0) t->offset += (size_t)n; @@ -146,9 +146,9 @@ static int transfer_io(int fd, short events, void *ud) if (t->capacity - t->len < 4096) { size_t capacity = t->capacity ? t->capacity * 2 : 8192; if (capacity > CLIPBOARD_TEXT_MAX) capacity = CLIPBOARD_TEXT_MAX; - if (capacity <= t->len) { transfer_end(t, false); return 0; } + if (capacity <= t->len) { transfer_end(t, false); return; } char *text = realloc(t->text, capacity); - if (!text) { transfer_end(t, false); return 0; } + if (!text) { transfer_end(t, false); return; } t->text = text; t->capacity = capacity; } @@ -160,7 +160,6 @@ static int transfer_io(int fd, short events, void *ud) transfer_end(t, valid); } else if (errno != EAGAIN && errno != EINTR) transfer_end(t, false); } - return 0; } static struct transfer *start_transfer_text(struct clipboard *c, int fd, bool sending, @@ -176,12 +175,12 @@ static struct transfer *start_transfer_text(struct clipboard *c, int fd, bool se if (!t->text) { close(fd); free(t); return NULL; } t->len = strlen(t->text); } - t->id = yasl_private_loop_add_fd(c->loop, fd, sending ? POLLOUT : POLLIN, transfer_io, t); - if (t->id < 0) { close(fd); free(t->text); free(t); return NULL; } - t->timeout = yasl_private_loop_add_timer(c->loop, CLIPBOARD_TRANSFER_TIMEOUT_NS, + t->id = yasl_loop_add_fd(c->loop, fd, sending ? POLLOUT : POLLIN, transfer_io, t); + if (t->id <= 0) { close(fd); free(t->text); free(t); return NULL; } + t->timeout = yasl_loop_add_timer(c->loop, CLIPBOARD_TRANSFER_TIMEOUT_NS, transfer_timeout, t); if (!t->timeout) { - yasl_private_loop_del_fd(c->loop, t->id); + yasl_loop_del_fd(c->loop, t->id); close(fd); free(t->text); free(t); @@ -522,7 +521,7 @@ struct wl_data_device *clipboard_data_device(const struct clipboard *c) } struct clipboard *clipboard_create(struct wl_display *display, struct wl_data_device_manager *manager, - struct wl_seat *seat, struct yasl_private_loop *loop, + struct wl_seat *seat, struct yasl_loop *loop, clipboard_receive_fn receive, void *ud) { if (!manager || !seat) return NULL; diff --git a/src/platform/wayland/clipboard.h b/src/platform/wayland/clipboard.h index 7ab1719..738d32e 100644 --- a/src/platform/wayland/clipboard.h +++ b/src/platform/wayland/clipboard.h @@ -19,7 +19,7 @@ typedef void (*clipboard_receive_fn)(const char *, size_t, bool uri_list, enum yasl_private_transfer_status, void *); struct clipboard *clipboard_create(struct wl_display *, struct wl_data_device_manager *, - struct wl_seat *, struct yasl_private_loop *, + struct wl_seat *, struct yasl_loop *, clipboard_receive_fn, void *); void clipboard_destroy(struct clipboard *); bool clipboard_set_seat(struct clipboard *, struct wl_seat *); diff --git a/src/platform/wayland/internal.h b/src/platform/wayland/internal.h index d5b7de1..a580e71 100644 --- a/src/platform/wayland/internal.h +++ b/src/platform/wayland/internal.h @@ -249,7 +249,7 @@ struct wayland_display { struct wp_fifo_manager_v1 *fifo_mgr; struct wp_commit_timing_manager_v1 *commit_timing_mgr; struct xkb_context *xkb_ctx; - struct yasl_private_loop *loop; + struct yasl_loop *loop; char *app_id; struct xdg_toplevel_drag_manager_v1 *toplevel_drag_mgr; struct conn_drag drag; @@ -405,6 +405,8 @@ void conn_destroy_outputs(struct wayland_display *c); void surface_follow_outputs(struct wayland_surface *w); uint64_t conn_pres_to_mono(const struct wayland_display *c, uint64_t ns); uint64_t conn_mono_to_pres(const struct wayland_display *c, uint64_t ns); +/* True when surface_draw_frame would draw now. */ +bool surface_draw_ready(const struct wayland_surface *w); void surface_draw_frame(struct wayland_surface *w); void wayland_surface_arm_frame(struct wayland_surface *w); void surface_fail(struct wayland_surface *w); diff --git a/src/platform/wayland/keyboard.c b/src/platform/wayland/keyboard.c index cf6d56c..294f553 100644 --- a/src/platform/wayland/keyboard.c +++ b/src/platform/wayland/keyboard.c @@ -336,23 +336,22 @@ static void keyboard_key(void *data, struct wl_keyboard *keyboard, } } -static int keyboard_repeat_ready(int fd, short revents, void *data) +static void keyboard_repeat_ready(void *data, int fd, short revents) { struct seat *s = data; uint64_t count; - if (!(revents & POLLIN) || read(fd, &count, sizeof(count)) != sizeof(count)) return 0; + if (!(revents & POLLIN) || read(fd, &count, sizeof(count)) != sizeof(count)) return; struct wayland_surface *w = s->keyboard_focus; if (!w) { keyboard_repeat_stop(s); - return 0; + return; } if (count > 32) count = 32; while (count-- && s->repeat_held && s->repeat_rate > 0) { if (!keyboard_dispatch_key(s, w, s->repeat_key, YASL_KEY_REPEAT)) keyboard_repeat_stop(s); } - return 0; } static void keyboard_modifiers(void *data, struct wl_keyboard *keyboard, @@ -391,7 +390,7 @@ int keyboard_repeat_init(struct seat *s) if (s->repeat_fd >= 0) return 0; s->repeat_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); if (s->repeat_fd < 0) return -1; - s->repeat_watch = yasl_private_loop_add_fd(s->conn->loop, s->repeat_fd, POLLIN, + s->repeat_watch = yasl_loop_add_fd(s->conn->loop, s->repeat_fd, POLLIN, keyboard_repeat_ready, s); if (s->repeat_watch <= 0) { s->repeat_watch = 0; diff --git a/src/platform/wayland/pointer.c b/src/platform/wayland/pointer.c index 571bb12..6cf2710 100644 --- a/src/platform/wayland/pointer.c +++ b/src/platform/wayland/pointer.c @@ -1552,7 +1552,7 @@ void seat_destroy(struct seat *s) } seat_drop_touch(s); clipboard_destroy(s->clipboard); - if (s->repeat_watch > 0) yasl_private_loop_del_fd(c->loop, s->repeat_watch); + if (s->repeat_watch > 0) yasl_loop_del_fd(c->loop, s->repeat_watch); if (s->repeat_fd >= 0) close(s->repeat_fd); if (s->cursor_viewport) wp_viewport_destroy(s->cursor_viewport); if (s->cursor_surface) wl_surface_destroy(s->cursor_surface); diff --git a/src/platform/wayland/surface.c b/src/platform/wayland/surface.c index cc0c715..bc37c10 100644 --- a/src/platform/wayland/surface.c +++ b/src/platform/wayland/surface.c @@ -49,7 +49,7 @@ static void display_lose(struct wayland_display *c) report_display_error(c); display_cancel_read(c); if (c->loop && c->watch > 0) - yasl_private_loop_del_fd(c->loop, c->watch); + yasl_loop_del_fd(c->loop, c->watch); c->watch = 0; c->depth++; conn_drag_end(c); @@ -62,7 +62,7 @@ static void display_lose(struct wayland_display *c) yasl_private_display_notify_event(&c->base, YASL_PRIVATE_EVENT_DISPLAY_LOST); c->reconnect_delay_ns = YASL_PRIVATE_MS_TO_NS(100); if (c->loop && !c->reconnect_timer) - c->reconnect_timer = yasl_private_loop_add_timer(c->loop, c->reconnect_delay_ns, reconnect_tick, c); + c->reconnect_timer = yasl_loop_add_timer(c->loop, c->reconnect_delay_ns, reconnect_tick, c); } void surface_fail(struct wayland_surface *w) @@ -73,43 +73,18 @@ void surface_fail(struct wayland_surface *w) yasl_private_surface_notify_close(&w->base); } -static int display_ready(int fd, short revents, void *ud) +/* The display fd is read in wayland_display_check, before any callback. */ +static void display_ready(void *ud, int fd, short revents) { - (void)fd; - struct wayland_display *c = ud; - if (c->lost) - return 0; - c->depth++; - if (c->reading) { - c->reading = false; - if (!(revents & POLLIN)) - wl_display_cancel_read(c->wl); - else if (wl_display_read_events(c->wl) < 0) - revents |= POLLERR; - } - if (revents & POLLOUT) { - if (wl_display_flush(c->wl) >= 0) { - if (c->loop && c->watch > 0) - yasl_private_loop_set_fd_events(c->loop, c->watch, POLLIN); - } else if (errno != EAGAIN) { - display_lose(c); - } - } - if (wl_display_dispatch_pending(c->wl) < 0 || - (revents & (POLLERR | POLLHUP | POLLNVAL))) - display_lose(c); - display_leave(c); - return 0; + (void)ud; (void)fd; (void)revents; } -static bool display_arm(struct wayland_display *c) +static void display_arm(struct wayland_display *c) { - bool had_pending = false; while (!c->reading && wl_display_prepare_read(c->wl) != 0) { - had_pending = true; if (wl_display_dispatch_pending(c->wl) < 0) { display_lose(c); - return false; + return; } } c->reading = true; @@ -118,74 +93,65 @@ static bool display_arm(struct wayland_display *c) if (errno != EAGAIN) { display_cancel_read(c); display_lose(c); - return false; + return; } events |= POLLOUT; } - if (c->loop && c->watch > 0) - yasl_private_loop_set_fd_events(c->loop, c->watch, events); - return had_pending; + yasl_loop_set_fd_events(c->loop, c->watch, events); } -static inline bool display_has_immediate_work(const struct wayland_display *c) +/* A draw that cannot proceed now, for want of a buffer or a configure, is not + * work: the release or configure that unblocks it schedules it again. */ +static bool display_has_immediate_work(const struct wayland_display *c) { - if (unlikely(!c || c->lost)) return false; - for (struct wayland_surface *w = c->surfaces; w; w = w->conn_next) { - bool ready_draw = w->want_draw & (w->frame_cb == NULL) & w->configured; - bool has_input = (w->input.count > 0 || w->input.reset_pending); - if (unlikely((ready_draw | has_input) & !w->closed)) + for (const struct wayland_surface *w = c->surfaces; w; w = w->conn_next) + if (surface_draw_ready(w)) return true; - } return false; } -int wayland_display_dispatch_timeout(struct wayland_display *c, int timeout_ms) +/* Before the wait: dispatch what libwayland already holds, draw due surfaces, + * and arm the read. While lost, only the reconnect timer runs. */ +bool wayland_display_prepare(struct wayland_display *c) { - if (!c) - return -1; - + if (c->lost) + return false; c->depth++; - if (c->lost) { - /* Reconnect attempts run as a loop timer. */ - int n = c->loop ? yasl_private_loop_step(c->loop, timeout_ms) : 0; - display_leave(c); - return n; - } - /* Losing the connection is reported as DISPLAY_LOST, not as a failed wait. */ - if (wl_display_dispatch_pending(c->wl) < 0) { + if (wl_display_dispatch_pending(c->wl) < 0) display_lose(c); - display_leave(c); - return 0; - } - - display_arm(c); - if (c->lost) { - display_leave(c); - return 0; - } - - if (timeout_ms != 0 && display_has_immediate_work(c)) { - timeout_ms = 0; - } - - int n = 0; - if (c->loop) { - n = yasl_private_loop_step(c->loop, timeout_ms); - } + if (!c->lost) + wayland_display_flush(c); + if (!c->lost) + display_arm(c); + bool ready = !c->lost && display_has_immediate_work(c); + display_leave(c); + return ready; +} - if (c->reading) { - display_cancel_read(c); +/* After the wait, before any callback: finish the read prepare armed, so + * callbacks may do roundtrips, and dispatch what it brought. */ +void wayland_display_check(struct wayland_display *c) +{ + if (!c->reading) + return; + c->depth++; + short revents = yasl_private_loop_revents(c->loop, c->watch); + c->reading = false; + if (!(revents & POLLIN)) + wl_display_cancel_read(c->wl); + else if (wl_display_read_events(c->wl) < 0) + revents |= POLLERR; + if (revents & POLLOUT) { + if (wl_display_flush(c->wl) >= 0) + yasl_loop_set_fd_events(c->loop, c->watch, POLLIN); + else if (errno != EAGAIN) + display_lose(c); } - - if (c->wl && wl_display_dispatch_pending(c->wl) < 0) { + if (!c->lost && (wl_display_dispatch_pending(c->wl) < 0 || + (revents & (POLLERR | POLLHUP | POLLNVAL)))) display_lose(c); - display_leave(c); - return 0; - } - display_leave(c); - return n; } void wayland_display_flush(struct wayland_display *c) @@ -212,7 +178,7 @@ static void connection_release(struct wayland_display *c) for (struct wayland_surface *w = c->surfaces; w; w = w->conn_next) surface_release_native(w); conn_drag_end(c); - if (c->loop && c->watch > 0) yasl_private_loop_del_fd(c->loop, c->watch); + if (c->loop && c->watch > 0) yasl_loop_del_fd(c->loop, c->watch); c->watch = 0; if (c->wl) display_cancel_read(c); while (c->seats) @@ -271,7 +237,7 @@ static bool connection_open(struct wayland_display *c) return false; surface_init_cursor(c); if (c->loop) { - c->watch = yasl_private_loop_add_fd(c->loop, wl_display_get_fd(c->wl), POLLIN, display_ready, c); + c->watch = yasl_loop_add_fd(c->loop, wl_display_get_fd(c->wl), POLLIN, display_ready, c); if (c->watch <= 0) { c->watch = 0; return false; } } return display_input_init(c) == 0; @@ -329,10 +295,10 @@ static uint64_t reconnect_tick(void *ud, yasl_private_timer_id id, uint64_t inte static void display_free(struct wayland_display *c) { connection_release(c); - if (c->loop && c->reconnect_timer) yasl_private_loop_del_timer(c->loop, c->reconnect_timer); + if (c->loop && c->reconnect_timer) yasl_loop_del_timer(c->loop, c->reconnect_timer); if (c->xkb_ctx) xkb_context_unref(c->xkb_ctx); if (c->owns_loop && c->loop) { - yasl_private_loop_destroy(c->loop); + yasl_loop_destroy(c->loop); c->loop = NULL; c->base.loop = NULL; } @@ -340,14 +306,15 @@ static void display_free(struct wayland_display *c) free(c); } -struct wayland_display *wayland_display_open(struct yasl_private_loop *loop, const struct yasl_private_display_spec *spec) +struct wayland_display *wayland_display_open(struct yasl_loop *loop, const struct yasl_private_display_spec *spec) { struct wayland_display *c = calloc(1, sizeof(*c)); if (!c) return NULL; c->owner = pthread_self(); c->base.ops = &yasl_private_wayland_backend; + c->base.reconnects = true; if (!loop) { - c->loop = yasl_private_loop_create(); + c->loop = yasl_loop_create(); c->owns_loop = true; } else { c->loop = loop; @@ -605,8 +572,8 @@ void wayland_surface_schedule_redraw(struct wayland_surface *w) return; } w->want_draw = true; - if (w->conn && w->conn->loop) - yasl_private_loop_kick(w->conn->loop); + if (w->conn) + yasl_private_loop_wake(w->conn->loop); } struct yasl_private_geometry wayland_surface_geometry(const struct wayland_surface *w) 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, |
