summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--examples/notepad.c2
-rw-r--r--examples/tablet.c5
-rw-r--r--include/yasl/yasl.h88
-rw-r--r--src/common/backend.c136
-rw-r--r--src/common/backend.h28
-rw-r--r--src/common/events.c2
-rw-r--r--src/common/loop.h6
-rw-r--r--src/common/private_loop.h225
-rw-r--r--src/common/surface.c122
-rw-r--r--src/common/types.h25
-rw-r--r--src/platform/cocoa/api.h5
-rw-r--r--src/platform/cocoa/window.m79
-rw-r--r--src/platform/posix/loop.c646
-rw-r--r--src/platform/wayland/api.h5
-rw-r--r--src/platform/wayland/backend.c20
-rw-r--r--src/platform/wayland/buffer.c8
-rw-r--r--src/platform/wayland/clipboard.c25
-rw-r--r--src/platform/wayland/clipboard.h2
-rw-r--r--src/platform/wayland/internal.h4
-rw-r--r--src/platform/wayland/keyboard.c9
-rw-r--r--src/platform/wayland/pointer.c2
-rw-r--r--src/platform/wayland/surface.c147
-rw-r--r--src/platform/x11/window.c117
-rw-r--r--tests/benchmark.c8
-rw-r--r--tests/cocoa/test_native.c2
-rw-r--r--tests/common/test_contracts.c10
-rw-r--r--tests/common/test_gl.c4
-rw-r--r--tests/common/test_loop.c204
-rw-r--r--tests/common/test_surface_integration.c110
-rw-r--r--tests/common/test_timers.c128
-rw-r--r--tests/common/test_vulkan.c2
-rw-r--r--tests/test_lifecycle.c14
-rw-r--r--tests/wayland/test_clipboard.c8
-rw-r--r--tests/wayland/test_reconnect.c5
-rw-r--r--tests/x11/test_input.c8
-rw-r--r--tests/x11/test_tablet_native.c2
36 files changed, 1280 insertions, 933 deletions
diff --git a/examples/notepad.c b/examples/notepad.c
index d0b38b9..470d976 100644
--- a/examples/notepad.c
+++ b/examples/notepad.c
@@ -1696,7 +1696,7 @@ int main(int argc, char **argv)
}
}
- if (yasl_init(YASL_BACKEND_AUTO, "org.yasl.notepad") < 0 || !editor_init(&ed)) {
+ if (yasl_init(&(struct yasl_options){.app_id = "org.yasl.notepad"}) < 0 || !editor_init(&ed)) {
fprintf(stderr, "Could not initialize YASL Notepad\n");
editor_shutdown(&ed);
yasl_shutdown();
diff --git a/examples/tablet.c b/examples/tablet.c
index 8c62fc0..bbe242c 100644
--- a/examples/tablet.c
+++ b/examples/tablet.c
@@ -42,8 +42,9 @@ int main(int argc, char **argv)
if (!strcmp(argv[i], "--smoke-test")) smoke = true;
else if (!strcmp(argv[i], "--backend") && i + 1 < argc) {
const char *b = argv[++i];
- if (yasl_init(!strcmp(b, "wayland") ? YASL_BACKEND_WAYLAND :
- !strcmp(b, "x11") ? YASL_BACKEND_X11 : YASL_BACKEND_COCOA, NULL) < 0) return 1;
+ enum yasl_backend backend = !strcmp(b, "wayland") ? YASL_BACKEND_WAYLAND :
+ !strcmp(b, "x11") ? YASL_BACKEND_X11 : YASL_BACKEND_COCOA;
+ if (yasl_init(&(struct yasl_options){.backend = backend}) < 0) return 1;
} else return 1;
}
yasl_surface *s = yasl_surface_create(&(struct yasl_surface_options){.title="YASL pen canvas — C clears, Escape exits", .width=800, .height=600});
diff --git a/include/yasl/yasl.h b/include/yasl/yasl.h
index 15bbe16..dab1091 100644
--- a/include/yasl/yasl.h
+++ b/include/yasl/yasl.h
@@ -27,6 +27,7 @@ extern "C" {
#endif
typedef struct yasl_surface yasl_surface;
typedef struct yasl_event yasl_event;
+typedef struct yasl_loop yasl_loop;
/* Lifecycle */
enum yasl_backend { YASL_BACKEND_AUTO, YASL_BACKEND_WAYLAND, YASL_BACKEND_X11, YASL_BACKEND_COCOA };
@@ -36,11 +37,25 @@ enum yasl_feature {
YASL_FEATURE_TEXT_INPUT = 64, YASL_FEATURE_LOCKED_CURSOR = 128, YASL_FEATURE_OPENGL = 256,
YASL_FEATURE_VULKAN = 512
};
-/** Optional: the first surface initializes with AUTO and no app ID. app_id is a
- * reverse-DNS name for window grouping and notifications. EALREADY once
- * initialized; a failed initialization may be retried. */
-YASL_API int yasl_init(enum yasl_backend backend, const char *app_id);
-/** Destroys every surface. EBUSY while a Vulkan surface exists. */
+/** Zero is the default: AUTO, no app ID, and yasl_wait_event delivers events.
+ * app_id is a reverse-DNS name for window grouping and notifications. Set loop
+ * and on_event together to run the display on an application loop: each
+ * yasl_loop_step then delivers events to on_event, and yasl_wait_event fails
+ * with EBUSY. */
+struct yasl_options {
+ enum yasl_backend backend;
+ const char *app_id;
+ yasl_loop *loop;
+ void (*on_event)(void *userdata, const yasl_event *event);
+ void *userdata;
+};
+/** Optional: the first surface initializes with defaults; NULL options too.
+ * EINVAL when only one of loop and on_event is set; EPERM off the loop's
+ * thread. EALREADY once initialized with the same options, EBUSY with others;
+ * a failed initialization may be retried. */
+YASL_API int yasl_init(const struct yasl_options *options);
+/** Destroys every surface; on_event is not called again. EBUSY inside a loop
+ * step or while a Vulkan surface exists. */
YASL_API int yasl_shutdown(void);
/** AUTO and 0 before initialization. */
YASL_API enum yasl_backend yasl_get_backend(void);
@@ -99,6 +114,10 @@ YASL_API int yasl_surface_set_title(yasl_surface *surface, const char *title);
enum yasl_request { YASL_REQUEST_RESTORE, YASL_REQUEST_FULLSCREEN, YASL_REQUEST_MAXIMIZE, YASL_REQUEST_MINIMIZE };
YASL_API int yasl_surface_request(yasl_surface *surface, enum yasl_request request);
YASL_API int yasl_surface_request_size(yasl_surface *surface, int width, int height);
+/** Asks for a REFRESH when the window can show a new frame: at once when none
+ * is in flight, otherwise once the system shows the current one. Requests
+ * coalesce. Use it to pace animation; draw at once for input. */
+YASL_API int yasl_surface_request_frame(yasl_surface *surface);
enum yasl_cursor {
YASL_CURSOR_ARROW, YASL_CURSOR_TEXT, YASL_CURSOR_LINK, YASL_CURSOR_CROSSHAIR, YASL_CURSOR_MOVE,
@@ -196,10 +215,11 @@ enum yasl_event_type {
YASL_EVENT_DROP, /**< External data: x, y; action is a yasl_drop_action;
* text on RELEASE is a text/uri-list. */
YASL_EVENT_DRAG, /**< A yasl_drag_start drag: x, y; action is a yasl_drop_action. */
- /** The display server went away. Before the next wait, destroy Vulkan
- * surfaces and detach GL contexts on every thread; popups close. Wayland
- * reconnects and sends DISPLAY_RESTORED: recreate them (GL contexts are new).
- * Elsewhere loss is final and waits fail with ENOTCONN. */
+ /** The display server went away; action is a yasl_lost. Before the next
+ * wait, or before returning to the loop, destroy Vulkan surfaces and detach
+ * GL contexts on every thread; popups close. On RECONNECTING, DISPLAY_RESTORED
+ * follows: recreate them (GL contexts are new). After FINAL, yasl_wait_event
+ * fails with ENOTCONN once no event is left. */
YASL_EVENT_DISPLAY_LOST,
YASL_EVENT_DISPLAY_RESTORED
};
@@ -218,6 +238,7 @@ enum yasl_scroll_source { YASL_SCROLL_UNKNOWN, YASL_SCROLL_WHEEL, YASL_SCROLL_FI
enum yasl_scroll_phase { YASL_SCROLL_PHASE_NONE, YASL_SCROLL_BEGIN, YASL_SCROLL_UPDATE, YASL_SCROLL_END };
enum yasl_transfer { YASL_TRANSFER_OK, YASL_TRANSFER_UNAVAILABLE, YASL_TRANSFER_FAILED };
enum yasl_drop_action { YASL_DROP_OVER, YASL_DROP_LEAVE, YASL_DROP_RELEASE };
+enum yasl_lost { YASL_LOST_RECONNECTING, YASL_LOST_FINAL };
/** Physical key, layout labels (codepoint unshifted, shifted; 0 if none) and the
* native code (evdev, X11 keycode or macOS virtual key). Type with TEXT, not this. */
@@ -256,7 +277,8 @@ struct yasl_event {
uint64_t time_ns;
unsigned action, mods; /**< mods: YASL_MOD_* for KEY, POINTER and SCROLL. */
double x, y;
- const char *text; /**< NUL-terminated UTF-8 until the next wait, or NULL. */
+ const char *text; /**< NUL-terminated UTF-8 until the next wait or until
+ * on_event returns, or NULL. */
size_t length;
union {
struct yasl_surface_info window;
@@ -268,18 +290,58 @@ struct yasl_event {
};
};
/** 1 with an event, 0 once timeout_ms passes or after yasl_wake. 0 polls and -1
- * waits forever. On failure out->type is NONE. */
+ * waits forever. On failure out->type is NONE. EBUSY with on_event. */
YASL_API int yasl_wait_event(yasl_event *out, int timeout_ms);
/** Makes a waiting, or the next, yasl_wait_event return 0 once no event is
- * pending. Thread-safe. */
+ * pending. With on_event, wakes the loop. Thread-safe. */
YASL_API void yasl_wake(void);
/** Monotonic nanoseconds, the clock of event times. Thread-safe. */
YASL_API uint64_t yasl_time_ns(void);
+/* Loop: one wait for the whole application. Give it to yasl_init to run the
+ * display on it; it also runs descriptors, timers and signals without one.
+ * Call it and get its callbacks on the thread that created it. */
+/** NULL with errno on failure. */
+YASL_API yasl_loop *yasl_loop_create(void);
+/** EBUSY while a step runs or the display runs on it. Leaves descriptors open. */
+YASL_API int yasl_loop_destroy(yasl_loop *loop);
+/** Waits at most timeout_ms (0 polls, -1 waits) and runs what is ready:
+ * display events, then descriptors, then due timers. Returns how many
+ * descriptor and timer callbacks ran. EBUSY inside a step; EPERM off the
+ * loop's thread. */
+YASL_API int yasl_loop_step(yasl_loop *loop, int timeout_ms);
+/** Steps until yasl_loop_quit. A quit made before the run ends it at once. */
+YASL_API int yasl_loop_run(yasl_loop *loop);
+YASL_API void yasl_loop_quit(yasl_loop *loop);
+/** Calls callback with the poll(2) revents, errors and hangup included; it
+ * must consume them or change the watch. Returns an ID above zero. */
+YASL_API int yasl_loop_add_fd(yasl_loop *loop, int fd, short events,
+ void (*callback)(void *userdata, int fd, short revents),
+ void *userdata);
+/** ENOENT for an unknown ID. Leaves the descriptor open. */
+YASL_API int yasl_loop_del_fd(yasl_loop *loop, int id);
+YASL_API int yasl_loop_set_fd_events(yasl_loop *loop, int id, short events);
+/** Calls callback after delay_ns, 0 meaning the next step. It returns the next
+ * delay, or 0 to stop. Returns an ID that is never reused, or 0 with errno. */
+YASL_API uint64_t yasl_loop_add_timer(yasl_loop *loop, uint64_t delay_ns,
+ uint64_t (*callback)(void *userdata, uint64_t id,
+ uint64_t delay_ns),
+ void *userdata);
+/** True when it cancels an armed timer, also from its own callback. */
+YASL_API bool yasl_loop_del_timer(yasl_loop *loop, uint64_t id);
+/** Calls callback on the loop's thread for each signal; repeats before the
+ * call coalesce. EBUSY when this loop already watches signals or another loop
+ * watches one of them. Unwatching restores the previous actions. */
+YASL_API int yasl_loop_watch_signals(yasl_loop *loop, const int *signals, int count,
+ void (*callback)(void *userdata, int signo),
+ void *userdata);
+YASL_API void yasl_loop_unwatch_signals(yasl_loop *loop);
+
/* Rendering */
/** CPU surfaces: the window's pixel_width * pixel_height premultiplied
* 0xAARRGGBB pixels, rows tightly packed. They persist, and a resize keeps the
- * overlap and zeroes the rest. Valid until the next wait. Present to show them;
+ * overlap and zeroes the rest. Valid until the next wait, the return of the
+ * callback that got them, or the next yasl_cpu_ call. Present to show them;
* the system may also show them when it redraws the window. */
struct yasl_damage {
int x, y, width, height;
diff --git a/src/common/backend.c b/src/common/backend.c
index ad9e218..9910e08 100644
--- a/src/common/backend.c
+++ b/src/common/backend.c
@@ -22,8 +22,38 @@ static const struct yasl_private_backend_ops *backend_ops(enum yasl_private_back
}
}
+/* The display drives the loop it runs on; see struct yasl_private_loop_driver. */
+static bool driver_prepare(void *ud)
+{
+ struct yasl_private_display *d = ud;
+ bool ready = d->ops->display_prepare(d);
+ /* Readiness the backends cannot see: events already queued, an error to
+ * report, or an owed REFRESH. */
+ return ready || yasl_private_events_pending(d) || yasl_private_events_error(d) ||
+ (d->pending && d->pending(d));
+}
+
+static int driver_wait(void *ud, int timeout_ms)
+{
+ struct yasl_private_display *d = ud;
+ return d->ops->display_wait(d, timeout_ms);
+}
+
+static void driver_check(void *ud)
+{
+ struct yasl_private_display *d = ud;
+ d->ops->display_check(d);
+}
+
+static void driver_deliver(void *ud)
+{
+ struct yasl_private_display *d = ud;
+ if (d->deliver)
+ d->deliver(d);
+}
+
static struct yasl_private_display *open_backend(enum yasl_private_backend backend,
- struct yasl_private_loop *loop, const struct yasl_private_display_spec *spec)
+ struct yasl_loop *loop, const struct yasl_private_display_spec *spec)
{
const struct yasl_private_backend_ops *ops = backend_ops(backend);
if (!ops) {
@@ -33,18 +63,26 @@ static struct yasl_private_display *open_backend(enum yasl_private_backend backe
errno = 0;
struct yasl_private_display *d = ops->display_open(loop, spec);
if (!d && !errno) errno = ENODEV;
- if (d) {
- if (!yasl_private_events_open(d)) {
- int error = errno;
- ops->display_close(d);
- errno = error;
- return NULL;
- }
+ if (!d)
+ return NULL;
+ struct yasl_private_loop_driver driver = {
+ .prepare = driver_prepare,
+ .wait = ops->display_wait ? driver_wait : NULL,
+ .check = driver_check,
+ .deliver = driver_deliver,
+ .ud = d,
+ };
+ if (!yasl_private_events_open(d) || !yasl_private_loop_set_driver(d->loop, &driver)) {
+ int error = errno;
+ yasl_private_events_close(d);
+ ops->display_close(d);
+ errno = error;
+ return NULL;
}
return d;
}
-struct yasl_private_display *yasl_private_display_open(struct yasl_private_loop *loop,
+struct yasl_private_display *yasl_private_display_open(struct yasl_loop *loop,
const struct yasl_private_display_spec *spec)
{
enum yasl_private_backend backend = spec ? spec->backend : YASL_PRIVATE_BACKEND_AUTO;
@@ -78,6 +116,7 @@ void yasl_private_display_close(struct yasl_private_display *d)
{
if (!d || !d->ops->display_close)
return;
+ yasl_private_loop_set_driver(d->loop, NULL);
yasl_private_events_close(d);
d->ops->display_close(d);
}
@@ -89,27 +128,6 @@ uint32_t yasl_private_display_caps(const struct yasl_private_display *d)
return d->ops->display_caps(d);
}
-int yasl_private_display_dispatch(struct yasl_private_display *d, int timeout_ms)
-{
- if (!d) {
- errno = EINVAL;
- return -1;
- }
- if (!d->ops->display_dispatch_timeout) {
- errno = ENOTSUP;
- return -1;
- }
- int queue_error = yasl_private_events_error(d);
- if (queue_error) { errno = queue_error; return -1; }
- if (yasl_private_events_pending(d)) timeout_ms = 0;
- errno = 0;
- int result = d->ops->display_dispatch_timeout(d, timeout_ms);
- queue_error = yasl_private_events_error(d);
- if (queue_error) { errno = queue_error; return -1; }
- if (result < 0 && !errno) errno = EIO;
- return result;
-}
-
void yasl_private_display_flush(struct yasl_private_display *d)
{
if (!d || !d->ops->display_flush)
@@ -317,7 +335,10 @@ bool yasl_private_surface_present(struct yasl_surface *s, const struct yasl_priv
};
}
}
- return s->display->ops->surface_present(s, fb, &clipped);
+ if (!s->display->ops->surface_present(s, fb, &clipped))
+ return false;
+ atomic_store(&s->presented_ns, yasl_private_loop_now_ns());
+ return true;
}
bool yasl_private_surface_read_pixels(struct yasl_surface *s, uint32_t *out, size_t cap,
@@ -473,10 +494,10 @@ bool yasl_private_surface_place_under_pointer(struct yasl_surface *s, struct yas
void yasl_private_surface_clear_timers(struct yasl_surface *s)
{
if (!s || !s->timers) return;
- struct yasl_private_loop *loop = s->display ? s->display->loop : NULL;
+ struct yasl_loop *loop = s->display ? s->display->loop : NULL;
for (int i = 0; i < s->timer_count; i++) {
if (loop && s->timers[i]) {
- yasl_private_loop_del_timer(loop, s->timers[i]);
+ yasl_loop_del_timer(loop, s->timers[i]);
}
}
free(s->timers);
@@ -491,7 +512,7 @@ yasl_private_timer_id yasl_private_surface_add_timer(struct yasl_surface *s, uin
if (!s || !s->display || !s->display->loop || !cb || interval_ns == 0)
return 0;
- yasl_private_timer_id id = yasl_private_loop_add_timer(s->display->loop, interval_ns, cb, ud);
+ yasl_private_timer_id id = yasl_loop_add_timer(s->display->loop, interval_ns, cb, ud);
if (!id) return 0;
/* A one-shot timer retires itself in the loop. Drop retired ids before
@@ -507,7 +528,7 @@ yasl_private_timer_id yasl_private_surface_add_timer(struct yasl_surface *s, uin
int new_cap = s->timer_capacity ? s->timer_capacity * 2 : 4;
yasl_private_timer_id *nt = realloc(s->timers, (size_t)new_cap * sizeof(*nt));
if (!nt) {
- yasl_private_loop_del_timer(s->display->loop, id);
+ yasl_loop_del_timer(s->display->loop, id);
return 0;
}
s->timers = nt;
@@ -523,13 +544,13 @@ bool yasl_private_surface_del_timer(struct yasl_surface *s, yasl_private_timer_i
for (int i = 0; i < s->timer_count; i++) {
if (s->timers[i] == id) {
s->timers[i] = s->timers[--s->timer_count];
- return s->display && yasl_private_loop_del_timer(s->display->loop, id);
+ return s->display && yasl_loop_del_timer(s->display->loop, id);
}
}
return false;
}
-struct yasl_private_loop *yasl_private_display_loop(struct yasl_private_display *d)
+struct yasl_loop *yasl_private_display_loop(struct yasl_private_display *d)
{
return d ? d->loop : NULL;
}
@@ -560,7 +581,46 @@ void yasl_private_surface_request_frame(struct yasl_surface *s)
{
if (!s) return;
yasl_private_surface_schedule_redraw(s);
- if (s->display->loop) yasl_private_loop_kick(s->display->loop);
+ yasl_private_loop_wake(s->display->loop);
+}
+
+void yasl_private_surface_notify_frame_shown(struct yasl_surface *s)
+{
+ if (!s || !s->frame_requested)
+ return;
+ s->frame_requested = false;
+ s->refresh_owed = true;
+}
+
+static uint64_t frame_interval_done(void *ud, yasl_private_timer_id id, uint64_t interval_ns)
+{
+ (void)id; (void)interval_ns;
+ yasl_private_surface_notify_frame_shown(ud);
+ return 0;
+}
+
+bool yasl_private_surface_request_shown_frame(struct yasl_surface *s)
+{
+ s->frame_requested = true;
+ const struct yasl_private_backend_ops *ops = s->display->ops;
+ if (ops->surface_frame_in_flight) {
+ if (!ops->surface_frame_in_flight(s))
+ yasl_private_surface_notify_frame_shown(s);
+ return true;
+ }
+ /* No frame signal: one frame per refresh interval since the last present. */
+ if (yasl_private_loop_timer_armed(s->display->loop, s->frame_timer))
+ return true;
+ double refresh = yasl_private_surface_timing(s).refresh_ns;
+ uint64_t interval = refresh >= 1000000.0 && refresh <= 1000000000.0 ? (uint64_t)refresh : 16666667;
+ uint64_t presented = atomic_load(&s->presented_ns);
+ uint64_t since = yasl_private_loop_now_ns() - presented;
+ if (!presented || since >= interval) {
+ yasl_private_surface_notify_frame_shown(s);
+ return true;
+ }
+ s->frame_timer = yasl_private_surface_add_timer(s, interval - since, frame_interval_done, s);
+ return s->frame_timer != 0;
}
static uint64_t pacing_tick(void *ud, yasl_private_timer_id id, uint64_t interval)
diff --git a/src/common/backend.h b/src/common/backend.h
index b111d3e..7633609 100644
--- a/src/common/backend.h
+++ b/src/common/backend.h
@@ -23,11 +23,18 @@ struct yasl_private_display {
/* Set by the backend while a native modal loop (macOS live resize) blocks the
* event loop; refresh callbacks run only then. */
bool modal;
- struct yasl_private_loop *loop;
+ struct yasl_loop *loop;
/* Backend's copy of yasl_private_display_spec.app_id, or NULL; for desktop services. */
const char *app_id;
struct yasl_private_event_state *events;
struct yasl_surface *surfaces;
+ /* Set by the backend: a lost connection comes back (Wayland). */
+ bool reconnects;
+ /* Set by the public layer, or NULL. pending: an event is ready that the
+ * queue does not hold, such as an owed REFRESH. deliver: hand queued events
+ * to the application during the loop step. */
+ bool (*pending)(struct yasl_private_display *d);
+ void (*deliver)(struct yasl_private_display *d);
};
struct yasl_surface {
@@ -43,6 +50,11 @@ struct yasl_surface {
struct yasl_private_vector2i reported_size;
uint32_t reported_scale_120, reported_state;
bool refresh_owed;
+ /* yasl_surface_request_frame waits for the frame in flight. */
+ bool frame_requested;
+ /* Written by GL presents on a rendering thread. */
+ _Atomic uint64_t presented_ns;
+ yasl_private_timer_id frame_timer;
void (*refresh)(struct yasl_surface *);
bool text_input_active;
unsigned text_purpose;
@@ -89,6 +101,8 @@ bool yasl_private_events_push(struct yasl_private_display *d, struct yasl_surfac
const struct yasl_private_event *event);
void yasl_private_surface_notify_configure(struct yasl_surface *s, struct yasl_private_geometry geometry);
void yasl_private_surface_notify_refresh(struct yasl_surface *s);
+/* The system showed the surface's last frame; answers a pending frame request. */
+void yasl_private_surface_notify_frame_shown(struct yasl_surface *s);
void yasl_private_surface_notify_close(struct yasl_surface *s);
void yasl_private_surface_notify_state(struct yasl_surface *s, uint32_t state);
void yasl_private_surface_notify_drag(struct yasl_surface *s, enum yasl_private_drag_event ev, struct yasl_private_vector2i pos);
@@ -132,10 +146,15 @@ struct yasl_private_backend_ops {
bool (*surface_update_text_input)(struct yasl_surface *);
enum yasl_private_backend type;
- struct yasl_private_display *(*display_open)(struct yasl_private_loop *loop, const struct yasl_private_display_spec *spec);
+ struct yasl_private_display *(*display_open)(struct yasl_loop *loop, const struct yasl_private_display_spec *spec);
void (*display_close)(struct yasl_private_display *d);
uint32_t (*display_caps)(const struct yasl_private_display *d);
- int (*display_dispatch_timeout)(struct yasl_private_display *d, int timeout_ms);
+ /* The loop driver hooks; see struct yasl_private_loop_driver. prepare and
+ * check are required. wait is optional, for platforms whose events do not
+ * arrive on a descriptor. */
+ bool (*display_prepare)(struct yasl_private_display *d);
+ int (*display_wait)(struct yasl_private_display *d, int timeout_ms);
+ void (*display_check)(struct yasl_private_display *d);
void (*display_flush)(struct yasl_private_display *d);
int (*display_modifiers)(const struct yasl_private_display *d);
/* Optional; returns zero or less when the system reports no interval. */
@@ -161,6 +180,9 @@ struct yasl_private_backend_ops {
void (*surface_set_callbacks)(struct yasl_surface *s, const struct yasl_private_window_cbs *cbs, void *ud);
void (*surface_set_title)(struct yasl_surface *s, const char *title);
void (*surface_schedule_redraw)(struct yasl_surface *s);
+ /* Optional; true while the system has not yet shown the last presented
+ * frame. Without it, frame requests are paced by the refresh interval. */
+ bool (*surface_frame_in_flight)(const struct yasl_surface *s);
bool (*surface_present)(struct yasl_surface *s, const struct yasl_private_framebuffer *fb, const struct yasl_private_damage_list *damage);
/* Optional; NULL means only YASL_PRIVATE_POINTER_NORMAL is supported. Backends that
* implement RELATIVE push POINTER_MOTION with delta set and pos frozen. */
diff --git a/src/common/events.c b/src/common/events.c
index b318d0c..83baf11 100644
--- a/src/common/events.c
+++ b/src/common/events.c
@@ -200,6 +200,8 @@ bool yasl_private_events_push(struct yasl_private_display *d, struct yasl_surfac
if (!ok) d->events->error = errno;
yasl_private_mutex_unlock(&d->events->mutex);
if (!ok) dispose(node);
+ /* The owner's next step sees the queue; another thread must wake it. */
+ yasl_private_loop_wake(d->loop);
return ok;
failed:
yasl_private_mutex_lock(&d->events->mutex);
diff --git a/src/common/loop.h b/src/common/loop.h
index 3a1c542..437466b 100644
--- a/src/common/loop.h
+++ b/src/common/loop.h
@@ -5,11 +5,9 @@
struct pollfd;
-int yasl_private_loop_next_timeout_ms(const struct yasl_private_loop *l);
+int yasl_private_loop_next_timeout_ms(const yasl_loop *l);
/* The loop's current poll set, valid until the next watch change. Backends
* that wait on native events use it to wake on loop readiness. */
-int yasl_private_loop_poll_set(const struct yasl_private_loop *l, const struct pollfd **fds);
-/* Whether id is armed: added, not deleted, and not retired by its callback. */
-bool yasl_private_loop_timer_armed(const struct yasl_private_loop *l, yasl_private_timer_id id);
+int yasl_private_loop_poll_set(const yasl_loop *l, const struct pollfd **fds);
#endif /* YASL_PRIVATE_COMMON_LOOP_H */
diff --git a/src/common/private_loop.h b/src/common/private_loop.h
index 432bbc3..a70997a 100644
--- a/src/common/private_loop.h
+++ b/src/common/private_loop.h
@@ -1,168 +1,22 @@
/**
* @file
- * Optional event loop, file watches, and timers.
- *
- * Use the loop's owning thread for all calls and callbacks, except
- * yasl_private_loop_now_ns(), yasl_private_loop_kick(), and yasl_private_loop_post(), which any thread
- * may call. A display borrows a supplied loop. For window applications,
- * drive yasl_private_display_dispatch(), drain input, then yasl_private_display_flush(); the
- * standalone loop_run() function does not replace this cycle. Timer intervals are
- * nanoseconds; timeout_ms arguments are milliseconds. Do not recursively step/run
- * or destroy a loop from a callback.
+ * The internal parts of the loop. The loop itself is public: see yasl_loop_* in
+ * yasl.h. Use the loop's owning thread for all calls and callbacks, except
+ * yasl_private_loop_now_ns(), yasl_private_loop_kick(), yasl_private_loop_wake()
+ * and yasl_private_loop_post(), which any thread may call.
*/
#ifndef YASL_PRIVATE_LOOP_H
#define YASL_PRIVATE_LOOP_H
#include "common/types.h"
+#include "yasl/yasl.h"
#ifdef __cplusplus
extern "C" {
#endif
-/* Optional event loop. Use the display's loop when adding application timers.
- * A step waits at most timeout_ms: zero polls, a negative value waits indefinitely.
- * Calls and callbacks use the loop's owning thread. */
-/**
- * Create an owned standalone file-descriptor and timer loop.
- * @return A loop to destroy with yasl_private_loop_destroy(), or NULL on allocation failure.
- * Use on one owning thread. Passing it to yasl_private_display_open() only lends it to
- * the display; the caller retains ownership.
- */
-struct yasl_private_loop *yasl_private_loop_create(void);
-/**
- * Release a loop, its watches, its timers, and its signal watch.
- * @param l Loop, or NULL for no action.
- * Close displays borrowing this loop first. Must not run during a loop callback
- * or an active step/run. Watched file descriptors are not closed. Posted
- * functions that have not run are dropped; no thread may still post or kick.
- */
-void yasl_private_loop_destroy(struct yasl_private_loop *l);
-/**
- * Step a standalone loop until yasl_private_loop_quit() is called.
- * @param l Loop.
- * @return Zero after quit, or -1 on failure.
- * For a window application, use a dispatch, input-poll, flush cycle instead;
- * loop_run alone does not perform that full cycle on every backend.
- */
-int yasl_private_loop_run(struct yasl_private_loop *l);
-/**
- * Wait for file-descriptor readiness or a timer deadline and run callbacks.
- * @param l Loop.
- * @param timeout_ms Maximum wait: zero polls, negative waits indefinitely.
- * @return Number of callbacks handled, or -1 on failure.
- * Timer deadlines shorten the wait. Callbacks can add/delete sources, including
- * themselves. Do not recursively step/run this loop.
- */
-int yasl_private_loop_step(struct yasl_private_loop *l, int timeout_ms);
-/**
- * Ask the currently running yasl_private_loop_run() to return after this step.
- * @param l Loop, or NULL for no action.
- * Call on the owning thread, usually from a callback.
- */
-void yasl_private_loop_quit(struct yasl_private_loop *l);
-/**
- * Wake a loop that is waiting, without running any callback.
- * @param l Loop, or NULL for no action.
- * May be called from any thread and from a signal handler. The loop must stay
- * alive until the call returns. A waiting yasl_private_loop_step(), yasl_private_loop_run(), or
- * yasl_private_display_dispatch() on this loop returns early; a loop that is not
- * waiting returns early from its next wait instead. Kicks coalesce. This does
- * not schedule a redraw or enqueue an event; use yasl_private_loop_post() to run code
- * on the owning thread.
- */
-void yasl_private_loop_kick(struct yasl_private_loop *l);
-/**
- * Run a function on the loop's owning thread.
- * @param l Loop.
- * @param fn Required function, called once with ud during a later step.
- * @param ud Borrowed userdata until fn runs.
- * @return true if queued; false for NULL arguments, a loop without a wake
- * pipe, or allocation failure.
- * May be called from any thread, but not from a signal handler. Wakes the loop
- * like yasl_private_loop_kick(). Functions run in posting order, during a
- * yasl_private_loop_step() or display dispatch on the owning thread. They may post
- * again; a function posted while posts are running waits for a later step.
- * Stop posting before yasl_private_loop_destroy(), which drops functions that have
- * not run.
- */
-bool yasl_private_loop_post(struct yasl_private_loop *l, void (*fn)(void *ud), void *ud);
-/**
- * Deliver POSIX signals to a callback on the loop's owning thread.
- * @param l Loop.
- * @param signals Signal numbers to watch; the array is read during the call.
- * @param n Number of entries, at least one.
- * @param cb Required callback, given ud and one signal number per delivery.
- * @param ud Borrowed userdata until the watch is removed.
- * @return true if every signal is watched; false with errno set and no
- * dispositions changed. EINVAL means a NULL or invalid argument, including
- * SIGKILL and SIGSTOP. EBUSY means another loop watches one of the signals.
- * Replaces this loop's previous watch, if any. Installs one process-wide
- * handler per signal with SA_RESTART and an empty handler mask, and saves the
- * previous action. The handler only records the signal and wakes the loop,
- * so it may run on any thread. YASL does not change any thread's signal mask:
- * a signal blocked in every thread stays pending and is not delivered. Caught
- * signals reset to their default action across exec, so spawned programs
- * do not inherit the handler. Repeated deliveries of one signal before the
- * callback runs coalesce into one callback. SIGCHLD reports stopped and
- * continued children as well as exits. Callbacks run during yasl_private_loop_step()
- * or display dispatch, and may remove the watch.
- */
-bool yasl_private_loop_watch_signals(struct yasl_private_loop *l, const int *signals, int n,
- void (*cb)(void *ud, int signo), void *ud);
-/**
- * Stop watching signals and restore their previous actions.
- * @param l Loop, or NULL for no action.
- * Does nothing if the loop watches no signals. Signals that arrived but have
- * not reached the callback are dropped. yasl_private_loop_destroy() also unwatches.
- */
-void yasl_private_loop_unwatch_signals(struct yasl_private_loop *l);
-/**
- * Read the monotonic clock used by YASL timers and timing results.
- * @return Nanoseconds from CLOCK_MONOTONIC, unrelated to wall-clock time.
- * May be called from any thread.
- */
-uint64_t yasl_private_loop_now_ns(void);
-/**
- * Get the loop used for a display's file watches and application timers.
- * @param d Display, or NULL.
- * @return Borrowed loop, or NULL. Do not destroy a display-owned loop yourself.
- */
-struct yasl_private_loop *yasl_private_display_loop(struct yasl_private_display *d);
-/**
- * Register a borrowed file descriptor for poll-style readiness.
- * @param l Loop.
- * @param fd Open file descriptor; it remains owned by the caller.
- * @param events Bitmask from <poll.h>, for example POLLIN or POLLOUT.
- * @param fn Callback receiving poll revents (including errors/hangup); return value is ignored.
- * @param ud Borrowed userdata until watch removal or loop destruction.
- * @return Positive watch ID, or -1 on failure.
- * Delete the watch before closing fd. The callback must consume readiness or
- * change/remove its watch to avoid repeated immediate wakeups.
- */
-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);
-/**
- * Remove a file watch without closing its descriptor.
- * @param l Loop.
- * @param id Watch ID returned by yasl_private_loop_add_fd().
- * @return Zero if removed, or -1 for an invalid/missing watch.
- */
-int yasl_private_loop_del_fd(struct yasl_private_loop *l, int id);
-/**
- * Replace a watch's poll event mask.
- * @param l Loop.
- * @param id Watch ID.
- * @param events New <poll.h> mask; zero disables requested readiness (errors may still arrive).
- * @return Zero if updated, or -1 for an invalid/missing watch.
- */
-int yasl_private_loop_set_fd_events(struct yasl_private_loop *l, int id, short events);
-
-/* Timers use nanoseconds. Return the next interval to repeat/reschedule, or zero
- * to remove the timer. A zero ID indicates failure. Loop timers live until they
- * stop, are deleted, or the loop is destroyed. Surface timers are also canceled
- * when their surface is destroyed. */
-/** Timer identity scoped to its loop. Zero is invalid; do not reuse a retired ID. */
-typedef uint32_t yasl_private_timer_id;
+/** Timer identity scoped to its loop. Zero is invalid; IDs are never reissued. */
+typedef uint64_t yasl_private_timer_id;
/** Timer callback. interval_ns is the last scheduled delay; return the next delay or zero to stop. */
typedef uint64_t (*yasl_private_timer_cb)(void *ud, yasl_private_timer_id id, uint64_t interval_ns);
@@ -170,29 +24,60 @@ typedef uint64_t (*yasl_private_timer_cb)(void *ud, yasl_private_timer_id id, ui
#define YASL_PRIVATE_MS_TO_NS(ms) ((uint64_t)(ms) * 1000000ull)
/**
- * Schedule a timer on a loop.
- * @param l Loop.
- * @param interval_ns Nonzero delay in monotonic nanoseconds before first firing.
- * @param cb Required callback; returns a new nonzero interval to repeat, or zero to stop.
- * @param ud Borrowed userdata until the timer stops, is deleted, or the loop is destroyed.
- * @return Nonzero timer ID, or zero for invalid arguments or allocation failure.
- * Repeating timers coalesce missed deadlines; do not use them to count elapsed
- * wall time. The callback receives the last scheduled interval, not elapsed time.
+ * How a display drives the loop it runs on. Each step runs prepare, the wait,
+ * check, deliver, then descriptor callbacks and timers.
+ * prepare: dispatch what the backend already holds, draw due surfaces, flush
+ * requests, and arm the read. True when progress is possible now, so the
+ * wait must not block.
+ * wait: replaces poll(2) for platforms whose events do not arrive on a
+ * descriptor; NULL uses poll. The loop reads descriptor readiness after it.
+ * check: runs after every wait, failed or not, before any callback; it closes
+ * what prepare opened.
+ * deliver: optional; hands queued events to the application.
+ */
+struct yasl_private_loop_driver {
+ bool (*prepare)(void *ud);
+ int (*wait)(void *ud, int timeout_ms);
+ void (*check)(void *ud);
+ void (*deliver)(void *ud);
+ void *ud;
+};
+/** Install a driver, or remove it with NULL. False with EBUSY when one is set. */
+bool yasl_private_loop_set_driver(yasl_loop *l, const struct yasl_private_loop_driver *driver);
+/** True on the thread that created the loop. */
+bool yasl_private_loop_owned(const yasl_loop *l);
+/** True while a step runs. */
+bool yasl_private_loop_stepping(const yasl_loop *l);
+/** The revents the last wait reported for watch id, until its callback runs. */
+short yasl_private_loop_revents(const yasl_loop *l, int id);
+
+/** Wake a waiting loop from any thread or a signal handler. Kicks coalesce. */
+void yasl_private_loop_kick(yasl_loop *l);
+/** Kick, but only from another thread: the owner's next step sees its own work. */
+void yasl_private_loop_wake(yasl_loop *l);
+/**
+ * Run fn on the loop's owning thread during a later step. Any thread, not a
+ * signal handler. False for NULL arguments or allocation failure. Functions run
+ * in posting order; one posted while posts run waits for a later step.
*/
-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);
+bool yasl_private_loop_post(yasl_loop *l, void (*fn)(void *ud), void *ud);
+/** CLOCK_MONOTONIC nanoseconds. Any thread. */
+uint64_t yasl_private_loop_now_ns(void);
+
/**
- * Cancel a loop timer, including from its own callback.
- * @param l Loop.
- * @param id Timer ID.
- * @return true if an armed timer was removed, false if invalid or already retired.
+ * Get the loop used for a display's file watches and application timers.
+ * @param d Display, or NULL.
+ * @return Borrowed loop, or NULL. Do not destroy a display-owned loop yourself.
*/
-bool yasl_private_loop_del_timer(struct yasl_private_loop *l, yasl_private_timer_id id);
+yasl_loop *yasl_private_display_loop(struct yasl_private_display *d);
+
+/** Whether id is armed: added, not deleted, and not retired by its callback. */
+bool yasl_private_loop_timer_armed(const yasl_loop *l, yasl_private_timer_id id);
/**
* Schedule a timer canceled automatically on surface destruction.
* @param s Surface whose display loop will run the timer.
- * @param interval_ns Nonzero initial delay in nanoseconds.
- * @param cb Required timer callback, with the same rules as yasl_private_loop_add_timer().
+ * @param interval_ns Initial delay in nanoseconds.
+ * @param cb Required timer callback, with the same rules as yasl_loop_add_timer().
* @param ud Borrowed userdata until the timer ends or the surface is destroyed.
* @return Nonzero timer ID, or zero on failure.
* The surface does not own userdata. Cancel with yasl_private_surface_del_timer().
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;
}
diff --git a/src/common/types.h b/src/common/types.h
index 3fffd47..8d00d0b 100644
--- a/src/common/types.h
+++ b/src/common/types.h
@@ -4,8 +4,9 @@
*
* All calls and callbacks use the display's opening thread (the main thread on
* Cocoa), unless explicitly documented otherwise. Userdata is borrowed; callback
- * tables and creation specs are copied. Do not recursively dispatch/flush or
- * destroy a display/loop while dispatch or a callback is active. Destroy child
+ * tables and creation specs are copied. The display drives its loop: each
+ * yasl_loop_step dispatches it. Do not recursively step or flush, or destroy a
+ * display/loop while a step or a callback is active. Destroy child
* popups before parents, surfaces before displays, displays before borrowed loops.
* Surface destruction from its own callback is supported; stop using it immediately.
*
@@ -43,7 +44,7 @@ extern "C" {
struct yasl_private_display;
struct yasl_surface;
-struct yasl_private_loop;
+struct yasl_loop;
/* Geometry */
/** A two-dimensional integer size, position, or offset; units depend on the caller. */
@@ -569,7 +570,7 @@ struct yasl_private_display_spec {
* opening fails without a more specific platform error. Explicit choices never
* fall back. Close with yasl_private_display_close() after destroying its surfaces.
*/
-struct yasl_private_display *yasl_private_display_open(struct yasl_private_loop *loop,
+struct yasl_private_display *yasl_private_display_open(struct yasl_loop *loop,
const struct yasl_private_display_spec *spec);
/**
* Read the selected backend.
@@ -592,19 +593,6 @@ void yasl_private_display_close(struct yasl_private_display *d);
* permission to perform an operation at any arbitrary moment.
*/
uint32_t yasl_private_display_caps(const struct yasl_private_display *d);
-/* Process events, waiting at most timeout_ms: zero polls, negative waits.
- * Pending input or redraw work returns immediately. Returns -1 on failure. */
-/**
- * Receive events and run ready loop sources.
- * @param d Display to dispatch.
- * @param timeout_ms Maximum wait in milliseconds: zero polls, negative waits indefinitely.
- * @return A nonnegative backend-specific work count on success, or -1 with errno
- * set on failure (EINVAL for NULL, EIO if no specific backend error is available).
- * Do not interpret the count as the number of queued input events. Pending input
- * or runnable redraw work prevents waiting. Drain input and call
- * yasl_private_display_flush() after updating application state. Do not dispatch recursively.
- */
-int yasl_private_display_dispatch(struct yasl_private_display *d, int timeout_ms);
/**
* Draw scheduled surfaces and send pending display requests.
* @param d Display, or NULL for no action.
@@ -886,6 +874,9 @@ bool yasl_private_frame_present(struct yasl_private_frame *frame,
const struct yasl_private_rect2i *rects, size_t count);
/** Coalesce a redraw request; use on the display thread. NULL is ignored. */
void yasl_private_surface_request_frame(struct yasl_surface *s);
+/** Owe s a REFRESH once the system shows its frame in flight, or at once when
+ * none is. False with errno when the fallback timer cannot start. */
+bool yasl_private_surface_request_shown_frame(struct yasl_surface *s);
/** Set drawing policy for either loop style. Returns false with errno on failure. */
bool yasl_private_surface_set_pacing(struct yasl_surface *s, yasl_private_pacing_mode mode);
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,
diff --git a/tests/benchmark.c b/tests/benchmark.c
index 8fa8066..53c24f4 100644
--- a/tests/benchmark.c
+++ b/tests/benchmark.c
@@ -188,15 +188,15 @@ static uint64_t timer_dummy_cb(void *ud, yasl_private_timer_id id, uint64_t inte
static void bench_timers(void) {
const int iters = 2000000;
- struct yasl_private_loop *loop = yasl_private_loop_create();
+ struct yasl_loop *loop = yasl_loop_create();
uint64_t t0 = get_time_ns();
for (int i = 0; i < iters; i++) {
- yasl_private_timer_id id = yasl_private_loop_add_timer(loop, 1000000, timer_dummy_cb, NULL);
- yasl_private_loop_del_timer(loop, id);
+ yasl_private_timer_id id = yasl_loop_add_timer(loop, 1000000, timer_dummy_cb, NULL);
+ yasl_loop_del_timer(loop, id);
}
uint64_t t1 = get_time_ns();
- yasl_private_loop_destroy(loop);
+ yasl_loop_destroy(loop);
double ms = (double)(t1 - t0) / 1e6;
double per_cycle = (double)(t1 - t0) / iters;
char details[128];
diff --git a/tests/cocoa/test_native.c b/tests/cocoa/test_native.c
index d26a89d..7ff4426 100644
--- a/tests/cocoa/test_native.c
+++ b/tests/cocoa/test_native.c
@@ -61,7 +61,7 @@ static void test_window(enum yasl_private_backend request, bool null_spec)
yasl_private_surface_schedule_redraw(state.surface);
uint64_t deadline = yasl_private_loop_now_ns() + 3000000000ull;
while (!state.draws && yasl_private_loop_now_ns() < deadline) {
- yasl_private_display_dispatch(state.display, 0);
+ yasl_loop_step(yasl_private_display_loop(state.display), 0);
yasl_private_display_flush(state.display);
nanosleep(&(struct timespec){.tv_nsec = 1000000}, NULL);
}
diff --git a/tests/common/test_contracts.c b/tests/common/test_contracts.c
index f9f427f..004cd64 100644
--- a/tests/common/test_contracts.c
+++ b/tests/common/test_contracts.c
@@ -347,9 +347,6 @@ static bool popup_reposition(struct yasl_surface *popup,
static uint32_t popup_caps(const struct yasl_private_display *d)
{ (void)d; return YASL_PRIVATE_CAP_POPUPS; }
-static int dispatch_failure(struct yasl_private_display *d, int timeout)
-{ (void)d; (void)timeout; return -1; }
-
static struct yasl_surface *create_failure(struct yasl_private_display *d,
const struct yasl_private_window_spec *spec, void *device)
{ (void)d; (void)spec; (void)device; return NULL; }
@@ -372,8 +369,7 @@ static void test_errors(void)
.surface_create_toplevel = create_failure,
.surface_create_popup = popup_create,
.surface_popup_reposition = popup_reposition,
- .display_drag_start = drag_without_listener,
- .display_dispatch_timeout = dispatch_failure};
+ .display_drag_start = drag_without_listener};
struct yasl_private_display d = {.ops = &ops};
struct yasl_surface s = {.display = &d};
struct yasl_private_popup_spec spec = {.size = {120, 80}, .anchor = {0, 0, 1, 1}};
@@ -399,11 +395,7 @@ static void test_errors(void)
d.ops = &unsupported;
assert(!yasl_private_surface_create_popup(&s, &spec) && errno == ENOTSUP);
assert(!yasl_private_surface_popup_reposition(&s, &spec) && errno == ENOTSUP);
- assert(yasl_private_display_dispatch(&d, 0) == -1 && errno == ENOTSUP);
- assert(yasl_private_display_dispatch(NULL, 0) == -1 && errno == EINVAL);
d.ops = &ops;
- errno = ERANGE;
- assert(yasl_private_display_dispatch(&d, 0) == -1 && errno == EIO);
struct yasl_private_window_spec toplevel = {.size = {120, 80}};
errno = ERANGE;
assert(!yasl_private_surface_create_toplevel(&d, &toplevel) && errno == ENODEV);
diff --git a/tests/common/test_gl.c b/tests/common/test_gl.c
index 2bf3d8c..1bb5bec 100644
--- a/tests/common/test_gl.c
+++ b/tests/common/test_gl.c
@@ -77,8 +77,8 @@ static void wait_for_size(yasl_surface *surface, int width, int height)
int main(int argc, char **argv)
{
REQUIRE(argc == 2, "expected backend argument: wayland or x11");
- if (!strcmp(argv[1], "wayland")) REQUIRE(yasl_init(YASL_BACKEND_WAYLAND, NULL) == 0, "select Wayland backend");
- else if (!strcmp(argv[1], "x11")) REQUIRE(yasl_init(YASL_BACKEND_X11, NULL) == 0, "select X11 backend");
+ if (!strcmp(argv[1], "wayland")) REQUIRE(yasl_init(&(struct yasl_options){.backend = YASL_BACKEND_WAYLAND}) == 0, "select Wayland backend");
+ else if (!strcmp(argv[1], "x11")) REQUIRE(yasl_init(&(struct yasl_options){.backend = YASL_BACKEND_X11}) == 0, "select X11 backend");
else REQUIRE(false, "backend must be wayland or x11");
primary = yasl_surface_create(&(struct yasl_surface_options){.title="YASL GL test", .width=320, .height=240, .type=YASL_SURFACE_GL});
diff --git a/tests/common/test_loop.c b/tests/common/test_loop.c
index 5c49691..bdc33b7 100644
--- a/tests/common/test_loop.c
+++ b/tests/common/test_loop.c
@@ -4,7 +4,9 @@
#include <assert.h>
#include <errno.h>
+#include <poll.h>
#include <pthread.h>
+#include <stdint.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
@@ -29,7 +31,7 @@ static void on_alarm(int signo)
}
struct post_log {
- struct yasl_private_loop *loop;
+ struct yasl_loop *loop;
char order[8];
int count;
};
@@ -57,7 +59,7 @@ static void post_b(void *ud)
static void test_post_order(void)
{
- struct yasl_private_loop *l = yasl_private_loop_create();
+ struct yasl_loop *l = yasl_loop_create();
assert(l);
assert(!yasl_private_loop_post(NULL, post_b, NULL));
assert(!yasl_private_loop_post(l, NULL, NULL));
@@ -65,20 +67,20 @@ static void test_post_order(void)
struct post_log log = {.loop = l};
assert(yasl_private_loop_post(l, post_a, &log));
assert(yasl_private_loop_post(l, post_b, &log));
- assert(yasl_private_loop_step(l, 0) >= 1);
+ assert(yasl_loop_step(l, 0) >= 1);
/* A post made while posts run waits for a later step. */
assert(log.count == 2 && memcmp(log.order, "ab", 2) == 0);
- assert(yasl_private_loop_step(l, 0) >= 1);
+ assert(yasl_loop_step(l, 0) >= 1);
assert(log.count == 3 && log.order[2] == 'c');
/* Destroy drops posts that never ran. */
assert(yasl_private_loop_post(l, post_b, &log));
- yasl_private_loop_destroy(l);
+ yasl_loop_destroy(l);
assert(log.count == 3);
}
struct thread_ctx {
- struct yasl_private_loop *loop;
+ struct yasl_loop *loop;
bool kick;
int signo;
_Atomic bool ran;
@@ -105,27 +107,27 @@ static void *wake_thread(void *ud)
static void test_cross_thread(void)
{
- struct yasl_private_loop *l = yasl_private_loop_create();
+ struct yasl_loop *l = yasl_loop_create();
assert(l);
struct thread_ctx post = {.loop = l};
pthread_t t;
assert(pthread_create(&t, NULL, wake_thread, &post) == 0);
while (!post.ran)
- assert(yasl_private_loop_step(l, -1) >= 0);
+ assert(yasl_loop_step(l, -1) >= 0);
pthread_join(t, NULL);
/* A kick ends an indefinite wait without running anything. */
struct thread_ctx kick = {.loop = l, .kick = true};
assert(pthread_create(&t, NULL, wake_thread, &kick) == 0);
- assert(yasl_private_loop_step(l, -1) >= 1);
+ assert(yasl_loop_step(l, -1) >= 1);
pthread_join(t, NULL);
- yasl_private_loop_destroy(l);
+ yasl_loop_destroy(l);
}
struct signal_log {
- struct yasl_private_loop *loop;
+ struct yasl_loop *loop;
int seen[NSIG];
int total;
bool unwatch;
@@ -138,7 +140,7 @@ static void on_signal(void *ud, int signo)
log->seen[signo]++;
log->total++;
if (log->unwatch)
- yasl_private_loop_unwatch_signals(log->loop);
+ yasl_loop_unwatch_signals(log->loop);
}
static volatile sig_atomic_t previous_handler_hits;
@@ -149,10 +151,10 @@ static void previous_handler(int signo)
previous_handler_hits++;
}
-static void step_until(struct yasl_private_loop *l, const int *counter, int want)
+static void step_until(struct yasl_loop *l, const int *counter, int want)
{
while (*counter < want)
- assert(yasl_private_loop_step(l, -1) >= 0);
+ assert(yasl_loop_step(l, -1) >= 0);
}
static void test_signals(void)
@@ -161,30 +163,26 @@ static void test_signals(void)
sigemptyset(&prev.sa_mask);
assert(sigaction(SIGUSR2, &prev, NULL) == 0);
- struct yasl_private_loop *l = yasl_private_loop_create();
- struct yasl_private_loop *other = yasl_private_loop_create();
+ struct yasl_loop *l = yasl_loop_create();
+ struct yasl_loop *other = yasl_loop_create();
assert(l && other);
struct signal_log log = {.loop = l};
const int bad[] = {SIGKILL};
- errno = 0;
- assert(!yasl_private_loop_watch_signals(l, bad, 1, on_signal, &log) && errno == EINVAL);
+ assert(yasl_loop_watch_signals(l, bad, 1, on_signal, &log) == -EINVAL);
const int sigs[] = {SIGUSR1, SIGUSR2, SIGCHLD};
- errno = 0;
- assert(!yasl_private_loop_watch_signals(l, sigs, 0, on_signal, &log) && errno == EINVAL);
- errno = 0;
- assert(!yasl_private_loop_watch_signals(l, sigs, 3, NULL, &log) && errno == EINVAL);
- assert(yasl_private_loop_watch_signals(l, sigs, 3, on_signal, &log));
+ assert(yasl_loop_watch_signals(l, sigs, 0, on_signal, &log) == -EINVAL);
+ assert(yasl_loop_watch_signals(l, sigs, 3, NULL, &log) == -EINVAL);
+ assert(yasl_loop_watch_signals(l, sigs, 3, on_signal, &log) == 0);
/* One signal belongs to one loop at a time. */
- errno = 0;
- assert(!yasl_private_loop_watch_signals(other, sigs, 1, on_signal, &log) && errno == EBUSY);
+ assert(yasl_loop_watch_signals(other, sigs, 1, on_signal, &log) == -EBUSY);
/* Deliveries before the callback runs coalesce per signal. */
raise(SIGUSR1);
raise(SIGUSR1);
raise(SIGUSR2);
- assert(yasl_private_loop_step(l, 0) >= 1);
+ assert(yasl_loop_step(l, 0) >= 1);
assert(log.seen[SIGUSR1] == 1 && log.seen[SIGUSR2] == 1);
assert(previous_handler_hits == 0);
@@ -203,13 +201,16 @@ static void test_signals(void)
int status;
assert(waitpid(child, &status, 0) == child);
- /* Replacing the watch restores the signals it drops. */
+ /* A loop has one watch, so a second cannot silently drop the first one's
+ * signals. Unwatching restores the previous actions. */
const int only_usr1[] = {SIGUSR1};
- assert(yasl_private_loop_watch_signals(l, only_usr1, 1, on_signal, &log));
+ assert(yasl_loop_watch_signals(l, only_usr1, 1, on_signal, &log) == -EBUSY);
+ yasl_loop_unwatch_signals(l);
+ assert(yasl_loop_watch_signals(l, only_usr1, 1, on_signal, &log) == 0);
raise(SIGUSR2);
assert(previous_handler_hits == 1);
- assert(yasl_private_loop_watch_signals(other, (const int[]){SIGUSR2}, 1, on_signal, &log));
- yasl_private_loop_unwatch_signals(other);
+ assert(yasl_loop_watch_signals(other, (const int[]){SIGUSR2}, 1, on_signal, &log) == 0);
+ yasl_loop_unwatch_signals(other);
assert(previous_handler_hits == 1);
raise(SIGUSR2);
assert(previous_handler_hits == 2);
@@ -221,21 +222,153 @@ static void test_signals(void)
struct sigaction now;
assert(sigaction(SIGUSR1, NULL, &now) == 0);
assert(now.sa_handler == SIG_DFL);
- yasl_private_loop_unwatch_signals(l);
+ yasl_loop_unwatch_signals(l);
/* Destroy unwatches. */
log.unwatch = false;
- assert(yasl_private_loop_watch_signals(l, only_usr1, 1, on_signal, &log));
- yasl_private_loop_destroy(l);
+ assert(yasl_loop_watch_signals(l, only_usr1, 1, on_signal, &log) == 0);
+ assert(yasl_loop_destroy(l) == 0);
assert(sigaction(SIGUSR1, NULL, &now) == 0);
assert(now.sa_handler == SIG_DFL);
- yasl_private_loop_destroy(other);
+ yasl_loop_destroy(other);
struct sigaction dfl = {.sa_handler = SIG_DFL};
sigemptyset(&dfl.sa_mask);
sigaction(SIGUSR2, &dfl, NULL);
}
+struct fd_log {
+ int calls;
+ short revents;
+};
+
+static void on_readable(void *ud, int fd, short revents)
+{
+ struct fd_log *log = ud;
+ char b;
+ if (revents & POLLIN)
+ assert(read(fd, &b, 1) == 1);
+ log->calls++;
+ log->revents = revents;
+}
+
+static void test_fds(void)
+{
+ yasl_loop *l = yasl_loop_create();
+ assert(l);
+ int p[2];
+ assert(pipe(p) == 0);
+ struct fd_log log = {0};
+ assert(yasl_loop_add_fd(l, -1, POLLIN, on_readable, &log) == -EINVAL);
+ assert(yasl_loop_add_fd(l, p[0], POLLIN, NULL, &log) == -EINVAL);
+ int id = yasl_loop_add_fd(l, p[0], POLLIN, on_readable, &log);
+ assert(id > 0);
+ assert(yasl_loop_step(l, 0) == 0 && log.calls == 0);
+ assert(write(p[1], "x", 1) == 1);
+ assert(yasl_loop_step(l, -1) == 1 && log.calls == 1 && (log.revents & POLLIN));
+ /* No requested events: a ready descriptor stays quiet. */
+ assert(yasl_loop_set_fd_events(l, id, 0) == 0);
+ assert(write(p[1], "x", 1) == 1);
+ assert(yasl_loop_step(l, 0) == 0 && log.calls == 1);
+ assert(yasl_loop_set_fd_events(l, id, POLLIN) == 0);
+ assert(yasl_loop_step(l, 0) == 1 && log.calls == 2);
+ assert(yasl_loop_del_fd(l, id) == 0);
+ assert(yasl_loop_del_fd(l, id) == -ENOENT);
+ assert(yasl_loop_set_fd_events(l, id, POLLIN) == -ENOENT);
+ assert(write(p[1], "x", 1) == 1);
+ assert(yasl_loop_step(l, 0) == 0 && log.calls == 2);
+ close(p[0]);
+ close(p[1]);
+ assert(yasl_loop_destroy(l) == 0);
+}
+
+struct reentry {
+ yasl_loop *loop;
+ int step, run;
+ bool quit;
+};
+
+static uint64_t reenter(void *ud, uint64_t id, uint64_t interval_ns)
+{
+ (void)id; (void)interval_ns;
+ struct reentry *r = ud;
+ r->step = yasl_loop_step(r->loop, 0);
+ r->run = yasl_loop_run(r->loop);
+ if (r->quit)
+ yasl_loop_quit(r->loop);
+ return 0;
+}
+
+static void *step_elsewhere(void *ud)
+{
+ return (void *)(intptr_t)yasl_loop_step(ud, 0);
+}
+
+static void test_reentry_and_quit(void)
+{
+ yasl_loop *l = yasl_loop_create();
+ assert(l);
+ /* A callback cannot step its own loop; the nested run keeps the outer quit. */
+ struct reentry r = {.loop = l, .quit = true};
+ assert(yasl_loop_add_timer(l, 0, reenter, &r));
+ assert(yasl_loop_run(l) == 0);
+ assert(r.step == -EBUSY && r.run == -EBUSY);
+
+ /* A quit before the run ends it at once, and the run clears it. */
+ yasl_loop_quit(l);
+ assert(yasl_loop_run(l) == 0);
+ r = (struct reentry){.loop = l, .quit = true};
+ assert(yasl_loop_add_timer(l, YASL_PRIVATE_MS_TO_NS(5), reenter, &r));
+ assert(yasl_loop_run(l) == 0 && r.step == -EBUSY);
+
+ /* Only the creating thread may step. */
+ pthread_t t;
+ void *result;
+ assert(pthread_create(&t, NULL, step_elsewhere, l) == 0);
+ pthread_join(t, &result);
+ assert((intptr_t)result == -EPERM);
+ assert(yasl_loop_destroy(l) == 0);
+}
+
+static void on_interrupt(int signo)
+{
+ (void)signo;
+}
+
+struct interrupter {
+ pthread_t target;
+ int delay_ms;
+};
+
+static void *interrupt_thread(void *ud)
+{
+ struct interrupter *i = ud;
+ sleep_ms(i->delay_ms);
+ pthread_kill(i->target, SIGUSR2);
+ return NULL;
+}
+
+/* An interrupted wait resumes with the time left, not the whole timeout. */
+static void test_interrupted_wait(void)
+{
+ struct sigaction sa = {.sa_handler = on_interrupt};
+ sigemptyset(&sa.sa_mask);
+ struct sigaction old;
+ assert(sigaction(SIGUSR2, &sa, &old) == 0);
+ yasl_loop *l = yasl_loop_create();
+ assert(l);
+ struct interrupter i = {.target = pthread_self(), .delay_ms = 60};
+ pthread_t t;
+ uint64_t start = yasl_time_ns();
+ assert(pthread_create(&t, NULL, interrupt_thread, &i) == 0);
+ assert(yasl_loop_step(l, 100) == 0);
+ uint64_t elapsed_ms = (yasl_time_ns() - start) / 1000000u;
+ pthread_join(t, NULL);
+ assert(elapsed_ms >= 90 && elapsed_ms < 140);
+ assert(yasl_loop_destroy(l) == 0);
+ sigaction(SIGUSR2, &old, NULL);
+}
+
int main(void)
{
signal(SIGALRM, on_alarm);
@@ -243,6 +376,9 @@ int main(void)
test_post_order();
test_cross_thread();
test_signals();
+ test_fds();
+ test_reentry_and_quit();
+ test_interrupted_wait();
printf("Loop tests passed.\n");
return 0;
}
diff --git a/tests/common/test_surface_integration.c b/tests/common/test_surface_integration.c
index f33e35b..7862bfc 100644
--- a/tests/common/test_surface_integration.c
+++ b/tests/common/test_surface_integration.c
@@ -34,6 +34,102 @@ static void *wrong_thread(void *data)
assert(yasl_surface_get_native(data, &native) == -EPERM && !native.wl_display && !native.x11_display);
return NULL;
}
+struct callback_state {
+ yasl_loop *loop;
+ yasl_surface *window;
+ int refreshes;
+ int wait_result, shutdown_result;
+ yasl_surface *made;
+ int made_destroy;
+};
+static void on_event(void *userdata, const yasl_event *e)
+{
+ struct callback_state *st = userdata;
+ if (e->type == YASL_EVENT_REFRESH && e->surface == st->window) {
+ st->refreshes++;
+ int w = 0, h = 0;
+ uint32_t *pixels = yasl_cpu_pixels(e->surface, &w, &h);
+ assert(pixels && w > 0 && h > 0);
+ pixels[0] = 0xff336699u;
+ assert(yasl_cpu_present(e->surface) == 0);
+ }
+ yasl_event inner;
+ st->wait_result = yasl_wait_event(&inner, 0);
+ st->shutdown_result = yasl_shutdown();
+}
+/* A loop callback runs with the display idle, so surface creation and its
+ * roundtrips are allowed there. */
+static uint64_t create_in_timer(void *userdata, uint64_t id, uint64_t delay_ns)
+{
+ (void)id; (void)delay_ns;
+ struct callback_state *st = userdata;
+ st->made = yasl_surface_create(&(struct yasl_surface_options){
+ .title = "made by a timer", .width = 64, .height = 64});
+ assert(st->made);
+ st->made_destroy = yasl_surface_destroy(st->made);
+ return 0;
+}
+static uint64_t quit_in_timer(void *userdata, uint64_t id, uint64_t delay_ns)
+{
+ (void)id; (void)delay_ns;
+ yasl_loop_quit(userdata);
+ return 0;
+}
+static void step_until(struct callback_state *st, const int *counter, int want)
+{
+ uint64_t deadline = yasl_time_ns() + 5000000000ull;
+ while (*counter < want && yasl_time_ns() < deadline)
+ assert(yasl_loop_step(st->loop, 50) >= 0);
+ assert(*counter >= want);
+}
+static void test_callback_mode(enum yasl_backend backend)
+{
+ struct callback_state st = {.loop = yasl_loop_create()};
+ assert(st.loop);
+ const struct yasl_options options = {
+ .backend = backend, .loop = st.loop, .on_event = on_event, .userdata = &st};
+ assert(yasl_init(&options) == 0);
+ assert(yasl_init(&options) == -EALREADY);
+ assert(yasl_loop_destroy(st.loop) == -EBUSY);
+ yasl_event e;
+ assert(yasl_wait_event(&e, 0) == -EBUSY);
+ st.window = yasl_surface_create(&(struct yasl_surface_options){
+ .title = "callback mode", .width = 96, .height = 64});
+ assert(st.window);
+
+ /* Events reach on_event while the application only steps its loop. */
+ step_until(&st, &st.refreshes, 1);
+ assert(st.wait_result == -EBUSY && st.shutdown_result == -EBUSY);
+
+ /* Frame requests coalesce into one REFRESH, sent once the window can
+ * show a frame. */
+ int before = st.refreshes;
+ assert(yasl_surface_request_frame(st.window) == 0);
+ assert(yasl_surface_request_frame(st.window) == 0);
+ step_until(&st, &st.refreshes, before + 1);
+ for (int i = 0; i < 4; i++)
+ assert(yasl_loop_step(st.loop, 25) >= 0);
+ assert(st.refreshes == before + 1);
+
+ /* An idle loop waits instead of spinning. */
+ int steps = 0;
+ uint64_t end = yasl_time_ns() + 300000000ull;
+ while (yasl_time_ns() < end) {
+ assert(yasl_loop_step(st.loop, 100) >= 0);
+ steps++;
+ }
+ assert(steps < 30);
+
+ assert(yasl_loop_add_timer(st.loop, 0, create_in_timer, &st));
+ while (!st.made)
+ assert(yasl_loop_step(st.loop, 50) >= 0);
+ assert(st.made_destroy == 0);
+
+ assert(yasl_loop_add_timer(st.loop, YASL_PRIVATE_MS_TO_NS(10), quit_in_timer, st.loop));
+ assert(yasl_loop_run(st.loop) == 0);
+ assert(yasl_shutdown() == 0);
+ assert(yasl_loop_destroy(st.loop) == 0);
+}
static void push(yasl_surface *s, struct yasl_private_event event)
{
event.surface = s;
@@ -68,8 +164,11 @@ int main(int argc, char **argv)
enum yasl_backend backend = !strcmp(argv[1], "wayland") ? YASL_BACKEND_WAYLAND
: !strcmp(argv[1], "x11") ? YASL_BACKEND_X11
: YASL_BACKEND_COCOA;
- assert(yasl_init(backend, "org.yasl.surface-test") == 0);
- assert(yasl_init(backend, NULL) == -EALREADY);
+ const struct yasl_options options = {.backend = backend, .app_id = "org.yasl.surface-test"};
+ assert(yasl_init(&options) == 0);
+ assert(yasl_init(&options) == -EALREADY);
+ /* Other options would need another display. */
+ assert(yasl_init(&(struct yasl_options){.backend = backend}) == -EBUSY);
assert(yasl_get_backend() == backend);
unsigned features = yasl_get_features();
yasl_surface *s = yasl_surface_create(
@@ -329,16 +428,17 @@ int main(int argc, char **argv)
assert(yasl_surface_destroy(s) == 0);
if (popup)
assert(!listed(display, popup_id));
- assert(yasl_init(backend, NULL) == -EALREADY);
+ assert(yasl_init(&options) == -EALREADY);
s = yasl_surface_create(&(struct yasl_surface_options){.title = "shared backend remains",
.width = 64, .height = 64});
assert(s);
yasl_shutdown();
- assert(yasl_init(backend, NULL) == 0);
+ assert(yasl_init(&(struct yasl_options){.backend = backend}) == 0);
s = yasl_surface_create(
&(struct yasl_surface_options){.title = "reinitialized", .width = 64, .height = 64});
assert(s);
yasl_shutdown();
- puts("public surface lifecycle, pixels, ownership, input, wake and refresh passed");
+ test_callback_mode(backend);
+ puts("public surface lifecycle, pixels, ownership, input, wake, refresh and callback mode passed");
return 0;
}
diff --git a/tests/common/test_timers.c b/tests/common/test_timers.c
index 2c3542f..3a35819 100644
--- a/tests/common/test_timers.c
+++ b/tests/common/test_timers.c
@@ -3,15 +3,17 @@
#include "common/types.h"
#include <assert.h>
+#include <signal.h>
#include <stdio.h>
#include <string.h>
+#include <unistd.h>
struct test_ctx {
int count;
uint64_t last_time;
yasl_private_timer_id id;
yasl_private_timer_id other_id;
- struct yasl_private_loop *loop;
+ struct yasl_loop *loop;
struct yasl_surface *surface;
bool done;
};
@@ -21,11 +23,32 @@ static uint64_t oneshot_cb(void *ud, yasl_private_timer_id id, uint64_t interval
{
struct test_ctx *ctx = ud;
assert(id == ctx->id);
- assert(interval_ns > 0);
+ (void)interval_ns;
ctx->count++;
return 0; /* Disarm */
}
+static uint64_t count_cb(void *ud, yasl_private_timer_id id, uint64_t interval_ns)
+{
+ (void)id; (void)interval_ns;
+ (*(int *)ud)++;
+ return 0;
+}
+
+/* Adds enough timers to move the timer array, then repeats itself. */
+static uint64_t grow_cb(void *ud, yasl_private_timer_id id, uint64_t interval_ns)
+{
+ struct test_ctx *ctx = ud;
+ assert(id == ctx->id);
+ if (ctx->count++ == 0) {
+ for (int i = 0; i < 64; i++)
+ assert(yasl_loop_add_timer(ctx->loop, YASL_PRIVATE_MS_TO_NS(60000), count_cb, NULL));
+ return YASL_PRIVATE_MS_TO_NS(2);
+ }
+ assert(interval_ns == YASL_PRIVATE_MS_TO_NS(2));
+ return 0;
+}
+
/* 2. Repeating timer: ticks N times, then returns 0. */
static uint64_t repeat_cb(void *ud, yasl_private_timer_id id, uint64_t interval_ns)
{
@@ -73,7 +96,7 @@ static uint64_t parent_timer_cb(void *ud, yasl_private_timer_id id, uint64_t int
struct test_ctx *ctx = ud;
assert(id == ctx->id);
ctx->count += 1;
- ctx->other_id = yasl_private_loop_add_timer(ctx->loop, 1000000ull, child_timer_cb, ctx);
+ ctx->other_id = yasl_loop_add_timer(ctx->loop, 1000000ull, child_timer_cb, ctx);
assert(ctx->other_id > 0);
return 0;
}
@@ -91,7 +114,7 @@ static uint64_t killer_timer_cb(void *ud, yasl_private_timer_id id, uint64_t int
(void)id; (void)interval_ns;
struct test_ctx *ctx = ud;
ctx->count++;
- assert(yasl_private_loop_del_timer(ctx->loop, ctx->other_id));
+ assert(yasl_loop_del_timer(ctx->loop, ctx->other_id));
return 0;
}
@@ -111,90 +134,109 @@ static uint64_t ms_timer_cb(void *ud, yasl_private_timer_id id, uint64_t interva
static void test_loop_timers(void)
{
- struct yasl_private_loop *l = yasl_private_loop_create();
+ struct yasl_loop *l = yasl_loop_create();
assert(l);
/* Invalid arguments */
- assert(yasl_private_loop_add_timer(NULL, 1000, oneshot_cb, NULL) == 0);
- assert(yasl_private_loop_add_timer(l, 0, oneshot_cb, NULL) == 0);
- assert(yasl_private_loop_add_timer(l, 1000, NULL, NULL) == 0);
- assert(!yasl_private_loop_del_timer(NULL, 1));
- assert(!yasl_private_loop_del_timer(l, 0));
- assert(!yasl_private_loop_del_timer(l, 99999));
+ assert(yasl_loop_add_timer(NULL, 1000, oneshot_cb, NULL) == 0);
+ assert(yasl_loop_add_timer(l, 1000, NULL, NULL) == 0);
+ assert(!yasl_loop_del_timer(NULL, 1));
+ assert(!yasl_loop_del_timer(l, 0));
+ assert(!yasl_loop_del_timer(l, 99999));
assert(yasl_private_loop_next_timeout_ms(l) == -1);
/* Test 1: Single-shot */
struct test_ctx ctx1 = { .loop = l };
- ctx1.id = yasl_private_loop_add_timer(l, 5000000ull, oneshot_cb, &ctx1);
+ ctx1.id = yasl_loop_add_timer(l, 5000000ull, oneshot_cb, &ctx1);
assert(ctx1.id > 0);
assert(yasl_private_loop_next_timeout_ms(l) >= 0);
while (ctx1.count == 0) {
- int handled = yasl_private_loop_step(l, 50);
+ int handled = yasl_loop_step(l, 50);
assert(handled >= 0);
}
assert(ctx1.count == 1);
/* Should be disarmed and removed */
- assert(!yasl_private_loop_del_timer(l, ctx1.id));
+ assert(!yasl_loop_del_timer(l, ctx1.id));
assert(yasl_private_loop_next_timeout_ms(l) == -1);
/* Test 2: Repeating */
struct test_ctx ctx2 = { .loop = l };
- ctx2.id = yasl_private_loop_add_timer(l, 2000000ull, repeat_cb, &ctx2);
+ ctx2.id = yasl_loop_add_timer(l, 2000000ull, repeat_cb, &ctx2);
assert(ctx2.id > 0);
while (!ctx2.done) {
- yasl_private_loop_step(l, 50);
+ yasl_loop_step(l, 50);
}
assert(ctx2.count == 5);
- assert(!yasl_private_loop_del_timer(l, ctx2.id));
+ assert(!yasl_loop_del_timer(l, ctx2.id));
/* Test 3: Reschedule dynamically */
struct test_ctx ctx3 = { .loop = l };
- ctx3.id = yasl_private_loop_add_timer(l, 2000000ull, reschedule_cb, &ctx3);
+ ctx3.id = yasl_loop_add_timer(l, 2000000ull, reschedule_cb, &ctx3);
assert(ctx3.id > 0);
while (!ctx3.done) {
- yasl_private_loop_step(l, 50);
+ yasl_loop_step(l, 50);
}
assert(ctx3.count == 2);
/* Test 4: Timer cancellation before firing */
struct test_ctx ctx4 = { .loop = l };
- yasl_private_timer_id cancel_id = yasl_private_loop_add_timer(l, 5000000ull, oneshot_cb, &ctx4);
+ yasl_private_timer_id cancel_id = yasl_loop_add_timer(l, 5000000ull, oneshot_cb, &ctx4);
assert(cancel_id > 0);
- assert(yasl_private_loop_del_timer(l, cancel_id));
- assert(!yasl_private_loop_del_timer(l, cancel_id));
+ assert(yasl_loop_del_timer(l, cancel_id));
+ assert(!yasl_loop_del_timer(l, cancel_id));
/* Step for 10ms; timer should not fire */
- yasl_private_loop_step(l, 10);
+ yasl_loop_step(l, 10);
assert(ctx4.count == 0);
/* Test 5: Child timer added inside callback */
struct test_ctx ctx5 = { .loop = l };
- ctx5.id = yasl_private_loop_add_timer(l, 2000000ull, parent_timer_cb, &ctx5);
+ ctx5.id = yasl_loop_add_timer(l, 2000000ull, parent_timer_cb, &ctx5);
while (ctx5.count < 11) {
- yasl_private_loop_step(l, 50);
+ yasl_loop_step(l, 50);
}
assert(ctx5.count == 11);
/* Test 6: Killer timer deletes victim timer before victim fires */
struct test_ctx ctx6 = { .loop = l };
- ctx6.id = yasl_private_loop_add_timer(l, 1000000ull, killer_timer_cb, &ctx6);
- ctx6.other_id = yasl_private_loop_add_timer(l, 10000000ull, victim_timer_cb, &ctx6);
+ ctx6.id = yasl_loop_add_timer(l, 1000000ull, killer_timer_cb, &ctx6);
+ ctx6.other_id = yasl_loop_add_timer(l, 10000000ull, victim_timer_cb, &ctx6);
while (ctx6.count == 0) {
- yasl_private_loop_step(l, 50);
+ yasl_loop_step(l, 50);
}
assert(ctx6.count == 1);
- yasl_private_loop_step(l, 20);
+ yasl_loop_step(l, 20);
+
+ /* Test 8: Zero delay runs in the next step without waiting, and IDs are
+ * never handed out again. */
+ int zero_runs = 0;
+ yasl_private_timer_id zero = yasl_loop_add_timer(l, 0, count_cb, &zero_runs);
+ assert(zero > 0 && yasl_private_loop_next_timeout_ms(l) == 0);
+ assert(yasl_loop_step(l, -1) == 1 && zero_runs == 1);
+ yasl_private_timer_id next = yasl_loop_add_timer(l, 0, count_cb, &zero_runs);
+ assert(next > zero);
+ assert(!yasl_loop_del_timer(l, zero) && yasl_loop_del_timer(l, next));
+
+ /* Test 9: A callback that grows the timer array still reschedules itself. */
+ struct yasl_loop *grown = yasl_loop_create();
+ assert(grown);
+ struct test_ctx ctx9 = { .loop = grown };
+ ctx9.id = yasl_loop_add_timer(grown, 0, grow_cb, &ctx9);
+ while (ctx9.count < 2)
+ assert(yasl_loop_step(grown, 50) >= 0);
+ assert(!yasl_private_loop_timer_armed(grown, ctx9.id));
+ assert(yasl_loop_destroy(grown) == 0);
/* Test 7: Millisecond conversion */
struct test_ctx ctx7 = { .loop = l };
- ctx7.id = yasl_private_loop_add_timer(l, YASL_PRIVATE_MS_TO_NS(5), ms_timer_cb, &ctx7);
+ ctx7.id = yasl_loop_add_timer(l, YASL_PRIVATE_MS_TO_NS(5), ms_timer_cb, &ctx7);
assert(ctx7.id > 0);
while (!ctx7.done) {
- yasl_private_loop_step(l, 50);
+ yasl_loop_step(l, 50);
}
assert(ctx7.count == 3);
- yasl_private_loop_destroy(l);
+ yasl_loop_destroy(l);
}
/* Surface timer tests */
@@ -220,14 +262,14 @@ static uint64_t surface_suicide_cb(void *ud, yasl_private_timer_id id, uint64_t
static void test_surface_timers(void)
{
- struct yasl_private_loop *loop = yasl_private_loop_create();
+ struct yasl_loop *loop = yasl_loop_create();
assert(loop);
struct yasl_private_display_spec spec = { .app_id = "org.yasl.timer_test" };
struct yasl_private_display *d = yasl_private_display_open(loop, &spec);
if (!d) {
/* Headless without display server */
- yasl_private_loop_destroy(loop);
+ yasl_loop_destroy(loop);
return;
}
@@ -243,10 +285,10 @@ static void test_surface_timers(void)
/* Application timers use the display's loop. */
struct test_ctx dctx = {0};
- dctx.id = yasl_private_loop_add_timer(yasl_private_display_loop(d), 2000000ull, oneshot_cb, &dctx);
+ dctx.id = yasl_loop_add_timer(yasl_private_display_loop(d), 2000000ull, oneshot_cb, &dctx);
assert(dctx.id > 0);
while (dctx.count == 0) {
- yasl_private_display_dispatch(d, 50);
+ yasl_loop_step(loop, 50);
}
assert(dctx.count == 1);
@@ -255,12 +297,12 @@ static void test_surface_timers(void)
sctx.id = yasl_private_surface_add_timer(s, 2000000ull, surface_timer_repeat_cb, &sctx);
assert(sctx.id > 0);
while (sctx.count < 3) {
- yasl_private_display_dispatch(d, 50);
+ yasl_loop_step(loop, 50);
}
assert(sctx.count >= 3);
assert(yasl_private_surface_del_timer(s, sctx.id));
int count_before = sctx.count;
- yasl_private_display_dispatch(d, 10);
+ yasl_loop_step(loop, 10);
assert(sctx.count == count_before);
/* A fired one-shot surface timer is retired, as a loop timer is. Many
@@ -270,7 +312,7 @@ static void test_surface_timers(void)
octx.id = yasl_private_surface_add_timer(s, 1000000ull, oneshot_cb, &octx);
assert(octx.id > 0);
while (octx.count == 0)
- yasl_private_display_dispatch(d, 50);
+ yasl_loop_step(loop, 50);
assert(!yasl_private_surface_del_timer(s, octx.id));
}
@@ -280,7 +322,7 @@ static void test_surface_timers(void)
assert(sctx2.id > 0);
yasl_private_surface_destroy(s);
/* Step display/loop; timer must not fire on dead surface */
- yasl_private_display_dispatch(d, 10);
+ yasl_loop_step(loop, 10);
assert(sctx2.count == 0);
/* Surface suicide from timer callback */
@@ -290,17 +332,19 @@ static void test_surface_timers(void)
sctx3.id = yasl_private_surface_add_timer(s, 2000000ull, surface_suicide_cb, &sctx3);
assert(sctx3.id > 0);
while (sctx3.count == 0) {
- yasl_private_display_dispatch(d, 50);
+ yasl_loop_step(loop, 50);
}
assert(sctx3.count == 1);
assert(sctx3.surface == NULL);
yasl_private_display_close(d);
- yasl_private_loop_destroy(loop);
+ yasl_loop_destroy(loop);
}
int main(void)
{
+ /* A wait that never ends fails here, not at the suite timeout. */
+ alarm(10);
test_loop_timers();
test_surface_timers();
printf("Timer tests passed successfully.\n");
diff --git a/tests/common/test_vulkan.c b/tests/common/test_vulkan.c
index 4633d55..80f9cbe 100644
--- a/tests/common/test_vulkan.c
+++ b/tests/common/test_vulkan.c
@@ -41,7 +41,7 @@ static int run_test(enum yasl_backend backend)
VkSemaphore acquired = VK_NULL_HANDLE, rendered = VK_NULL_HANDLE;
VkCommandBuffer command = VK_NULL_HANDLE;
- if (yasl_init(backend, NULL) < 0) {
+ if (yasl_init(&(struct yasl_options){.backend = backend}) < 0) {
fprintf(stderr, "vulkan integration: cannot select requested backend %d (YASL error %d)\n",
backend, errno);
goto cleanup;
diff --git a/tests/test_lifecycle.c b/tests/test_lifecycle.c
index 41dafe6..7ba3f89 100644
--- a/tests/test_lifecycle.c
+++ b/tests/test_lifecycle.c
@@ -1,6 +1,11 @@
#include <yasl/yasl.h>
#include <assert.h>
#include <string.h>
+static void on_event(void *userdata, const yasl_event *event)
+{
+ (void)userdata;
+ (void)event;
+}
int main(void)
{
assert(yasl_shutdown() == 0);
@@ -8,7 +13,14 @@ int main(void)
yasl_event event = {.type = YASL_EVENT_QUIT};
assert(yasl_wait_event(&event, 0) == -ENODEV && event.type == YASL_EVENT_NONE);
assert(yasl_wait_event(NULL, 0) == -EINVAL);
- assert(yasl_init((enum yasl_backend)999, NULL) == -EINVAL);
+ assert(yasl_init(&(struct yasl_options){.backend = (enum yasl_backend)999}) == -EINVAL);
+ /* The loop and its event callback come together or not at all. */
+ yasl_loop *loop = yasl_loop_create();
+ assert(loop);
+ assert(yasl_init(&(struct yasl_options){.loop = loop}) == -EINVAL);
+ assert(yasl_init(&(struct yasl_options){.on_event = on_event}) == -EINVAL);
+ assert(yasl_loop_destroy(loop) == 0);
+ assert(yasl_surface_request_frame(NULL) == -ENODEV);
assert(yasl_get_backend() == YASL_BACKEND_AUTO && yasl_get_features() == 0);
assert(!yasl_surface_create(NULL) && errno == EINVAL);
struct yasl_surface_options options = {.width = 0, .height = 64};
diff --git a/tests/wayland/test_clipboard.c b/tests/wayland/test_clipboard.c
index 0ee74b6..f1427ab 100644
--- a/tests/wayland/test_clipboard.c
+++ b/tests/wayland/test_clipboard.c
@@ -39,7 +39,7 @@ static void receive(const char *text, size_t len, bool uri_list,
int main(void)
{
struct received received = {0};
- struct clipboard c = {.loop = yasl_private_loop_create(), .receive = receive, .ud = &received};
+ struct clipboard c = {.loop = yasl_loop_create(), .receive = receive, .ud = &received};
received.clipboard = &c;
assert(c.loop);
const struct { const char *bytes; size_t len; bool valid; } cases[] = {
@@ -62,7 +62,7 @@ int main(void)
assert(write(fds[1], cases[i].bytes, cases[i].len) == (ssize_t)cases[i].len);
close(fds[1]);
for (int steps = 0; c.transfers && steps < 10; steps++)
- assert(yasl_private_loop_step(c.loop, 0) >= 0);
+ assert(yasl_loop_step(c.loop, 0) >= 0);
assert(!c.transfers);
assert(!yasl_private_loop_timer_armed(c.loop, timeout_id));
assert(received.count == 1);
@@ -100,11 +100,11 @@ int main(void)
struct wl_surface *timeout_target = (struct wl_surface *)(uintptr_t)2;
timed->drop_surface = timeout_target;
yasl_private_timer_id timeout_id = timed->timeout;
- assert(yasl_private_loop_del_timer(c.loop, timeout_id));
+ assert(yasl_loop_del_timer(c.loop, timeout_id));
(void)transfer_timeout(timed, timeout_id, CLIPBOARD_TRANSFER_TIMEOUT_NS);
close(timeout_fds[1]);
assert(!c.transfers && received.count == 1);
assert(received.request_id == 100 && received.status == YASL_PRIVATE_TRANSFER_FAILED);
- yasl_private_loop_destroy(c.loop);
+ yasl_loop_destroy(c.loop);
return 0;
}
diff --git a/tests/wayland/test_reconnect.c b/tests/wayland/test_reconnect.c
index 891d98b..6bd6967 100644
--- a/tests/wayland/test_reconnect.c
+++ b/tests/wayland/test_reconnect.c
@@ -55,7 +55,7 @@ static VkSurfaceKHR vulkan_surface(void)
int main(void)
{
- CHECK(yasl_init(YASL_BACKEND_WAYLAND, NULL) == 0, "selecting Wayland");
+ CHECK(yasl_init(&(struct yasl_options){.backend = YASL_BACKEND_WAYLAND}) == 0, "selecting Wayland");
cpu = yasl_surface_create(&(struct yasl_surface_options){.title="reconnect CPU", .width=160, .height=120});
CHECK(cpu, "creating CPU window");
gl = yasl_surface_create(&(struct yasl_surface_options){.title="reconnect GL", .width=160, .height=120, .type=YASL_SURFACE_GL});
@@ -84,7 +84,8 @@ int main(void)
puts("ready");
fflush(stdout);
- expect(YASL_EVENT_DISPLAY_LOST, NULL, 10000);
+ yasl_event lost = expect(YASL_EVENT_DISPLAY_LOST, NULL, 10000);
+ CHECK(lost.action == YASL_LOST_RECONNECTING, "reporting a loss that reconnects");
struct yasl_native native;
CHECK(yasl_surface_get_native(cpu, &native) == -ENOTCONN && !native.wl_display && !native.wl_surface,
"rejecting native handles while offline");
diff --git a/tests/x11/test_input.c b/tests/x11/test_input.c
index aa534ca..b2b79d7 100644
--- a/tests/x11/test_input.c
+++ b/tests/x11/test_input.c
@@ -62,7 +62,7 @@ static void check_selection_encoding(struct x11_display *d, struct x11_surface *
0, 0, 1, 1, 0, 0, 0);
pending->target = s;
pending->id = (uint64_t)i + 1;
- pending->timeout = yasl_private_loop_add_timer(d->loop, PASTE_TIMEOUT_NS,
+ pending->timeout = yasl_loop_add_timer(d->loop, PASTE_TIMEOUT_NS,
paste_timeout, pending);
assert(pending->timeout);
pending->next = d->pastes;
@@ -89,7 +89,7 @@ static void check_selection_encoding(struct x11_display *d, struct x11_surface *
incr->window = XCreateSimpleWindow(d->native, DefaultRootWindow(d->native), 0, 0, 1, 1, 0, 0, 0);
incr->target = s;
incr->id = 9;
- incr->timeout = yasl_private_loop_add_timer(d->loop, PASTE_TIMEOUT_NS, paste_timeout, incr);
+ incr->timeout = yasl_loop_add_timer(d->loop, PASTE_TIMEOUT_NS, paste_timeout, incr);
assert(incr->timeout);
incr->next = d->pastes;
d->pastes = incr;
@@ -193,7 +193,7 @@ static void check_request_completion(struct x11_display *d, struct x11_surface *
assert(request_paste(&d->base, YASL_PRIVATE_CLIPBOARD, &s->base, 103));
struct paste_request *timed = d->pastes;
assert(timed && timed->id == 103 && timed->timeout);
- assert(yasl_private_loop_del_timer(d->loop, timed->timeout));
+ assert(yasl_loop_del_timer(d->loop, timed->timeout));
timed->timeout = 0;
paste_timeout(timed, 0, PASTE_TIMEOUT_NS);
event = wait_paste(&d->base, 103);
@@ -207,7 +207,7 @@ static void check_request_completion(struct x11_display *d, struct x11_surface *
yasl_private_timer_id timeout = d->pastes->timeout;
yasl_private_surface_destroy(cancel_target);
assert(d->pastes && !d->pastes->target && d->pastes->id == 0);
- assert(yasl_private_loop_del_timer(d->loop, timeout));
+ assert(yasl_loop_del_timer(d->loop, timeout));
d->pastes->timeout = 0;
XSelectionEvent late_reply = {
.requestor = d->pastes->window,
diff --git a/tests/x11/test_tablet_native.c b/tests/x11/test_tablet_native.c
index bbe39f2..8d44232 100644
--- a/tests/x11/test_tablet_native.c
+++ b/tests/x11/test_tablet_native.c
@@ -140,7 +140,7 @@ int main(int argc, char **argv)
{
assert(argc == 3); /* pen socket, eraser socket */
struct canvas_state app = {0};
- assert(yasl_init(YASL_BACKEND_X11, NULL) == 0);
+ assert(yasl_init(&(struct yasl_options){.backend = YASL_BACKEND_X11}) == 0);
yasl_surface *surface = yasl_surface_create(&(struct yasl_surface_options){.title="YASL native pressure test", .width=500, .height=400});
assert(surface && (yasl_get_features() & YASL_FEATURE_TABLET));
struct yasl_native native;