summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBerke Güzel <wenekar1@gmail.com>2026-10-12 08:41:51 +0300
committerBerke Güzel <wenekar1@gmail.com>2026-10-12 08:41:51 +0300
commit3b8af02be94a510e385f44d58c35c86bb89424a6 (patch)
tree2573fb5ac41700d458109ee1cea4bfd875ecf1a2
parentf7fa0930cb46046f995fa1ab927aef62466e995e (diff)
refactor: simplify public API and isolate native extensions
Separate loop/timer and native interoperability APIs from the portable surface header. Unify dispatch, geometry queries, and timer callbacks; keep framebuffer bookkeeping and frame clocks private, and hide implementation symbols. Update backends, documentation, examples, and API/integration coverage. Verify exports, strict C/C++ installed consumers, and normal plus ASan/UBSan suites.
-rw-r--r--README.md41
-rw-r--r--examples/simple_window.c2
-rw-r--r--include/yasl/loop.h46
-rw-r--r--include/yasl/native.h44
-rw-r--r--include/yasl/timing.h23
-rw-r--r--include/yasl/yasl.h110
-rw-r--r--meson.build5
-rw-r--r--src/cocoa/api.h7
-rw-r--r--src/cocoa/window.m99
-rw-r--r--src/common/backend.c103
-rw-r--r--src/common/backend.h13
-rw-r--r--src/common/loop.c59
-rw-r--r--src/common/loop.h8
-rw-r--r--src/common/meson.build2
-rw-r--r--src/common/native.c35
-rw-r--r--src/common/timing.c7
-rw-r--r--src/common/timing.h28
-rw-r--r--src/wayland/api.h7
-rw-r--r--src/wayland/backend.c23
-rw-r--r--src/wayland/buffer.c8
-rw-r--r--src/wayland/internal.h2
-rw-r--r--src/wayland/popup.c17
-rw-r--r--src/wayland/surface.c27
-rw-r--r--src/x11/window.c39
-rw-r--r--tests/cocoa/test_native.c12
-rw-r--r--tests/cocoa/test_ownership.m4
-rw-r--r--tests/common/test_backends.c20
-rw-r--r--tests/common/test_contracts.c78
-rw-r--r--tests/common/test_timers.c29
-rw-r--r--tests/helpers/check_exports.py42
-rw-r--r--tests/helpers/run_backends.py9
-rw-r--r--tests/meson.build51
-rw-r--r--tests/test_api.cpp27
-rw-r--r--tests/test_lifecycle.c22
-rw-r--r--tests/wayland/test_input.c25
35 files changed, 620 insertions, 454 deletions
diff --git a/README.md b/README.md
index 6fc8848..4b961ef 100644
--- a/README.md
+++ b/README.md
@@ -24,6 +24,25 @@ It provides window creation, frame pacing, high-DPI scaling, and input routing w
- Self-contained event queue.
- No terminal-specific telemetry or hardcoded application names.
+## Public API
+
+Include `<yasl/yasl.h>` for surface creation, drawing, input, and display dispatch.
+`yasl_surface_geometry()` returns device-pixel size and scale together.
+`yasl_display_dispatch(display, timeout_ms)` polls with zero, waits indefinitely
+with a negative timeout, or waits up to a positive number of milliseconds.
+Pending input or redraw work returns immediately; call `yasl_display_flush()`
+after processing input and scheduling redraws.
+
+Include `<yasl/loop.h>` to use the optional event loop, file watches, or timers.
+Timers use one callback model and nanoseconds throughout; use `YASL_MS_TO_NS(5)`
+for a five-millisecond interval. Add application timers to
+`yasl_display_loop(display)`. Add surface timers with `yasl_surface_add_timer()`
+when they should be canceled automatically on surface destruction.
+
+Include `<yasl/native.h>` for platform handles and application-managed native
+rendering. These extensions are separate from the portable CPU drawing API.
+`<yasl/timing.h>` contains the public timing result types.
+
## Backend selection
YASL selects the native window backend at runtime.
@@ -58,6 +77,8 @@ Call `yasl_display_backend(display)` to read the selected backend.
All surfaces on a display use that backend.
Open separate displays to use Wayland and X11 in one process.
The public API needs no window system headers.
+Shared libraries export only the functions declared in the installed YASL headers.
+Input queues, frame clocks, backend entry points, and protocol helpers are private implementation details.
Use `yasl_display_caps()` to check optional features.
X11 supports CPU pixel buffers, keyboard and pointer input, and clipboard transfer.
@@ -67,14 +88,18 @@ Those features return their normal unsupported result.
The surface spec needs no native graphics device for CPU drawing.
On Cocoa, YASL creates its own Metal device for this mode.
-An app that renders with Metal can supply its device through `graphics_device`.
-That app submits and presents the native drawable.
+An app that renders with Metal includes `<yasl/native.h>` and calls
+`yasl_surface_create_native(display, &spec, device)`. During its draw callback,
+`yasl_surface_get_native_frame()` returns the current Metal drawable. The app
+submits and presents it, then calls `yasl_surface_present()` to record submission.
The native display handle is a `wl_display *` on Wayland.
It is a `Display *` on X11.
The native surface handle is a `wl_surface *` on Wayland.
On X11, convert it to `Window` through `uintptr_t`.
-On Cocoa, the native surface handle is the content `NSView *`.
+On Cocoa, the native display handle is `NSApplication *` and the surface handle
+is `NSWindow *`. Access its view through `window.contentView`. Native getters
+are declared only in `<yasl/native.h>`; handles are borrowed until destruction.
## Ownership and backend contracts
@@ -104,10 +129,10 @@ On Cocoa, the native surface handle is the content `NSView *`.
- Present only the current draw callback's target, at most once. Targets expire
when the callback returns; preserve the opaque `generation` when copying a
target. Damage is required, clipped to the target, and validated uniformly. Backends may upload the whole frame despite damage hints.
-- A non-NULL `graphics_device` requires `YASL_CAP_NATIVE_GRAPHICS` (currently
- Cocoa); other backends reject it with `ENOTSUP`. In native Metal mode the app
- submits and presents the drawable, then calls `yasl_surface_present()` to
- record that submission.
+- `yasl_surface_create_native()` requires `YASL_CAP_NATIVE_GRAPHICS` (currently
+ Cocoa) and a non-NULL graphics device; other backends return `ENOTSUP`. Native
+ drawable queries are valid only for the current draw target and expire on
+ successful present, callback return, or surface destruction.
- `YASL_CAP_FRAME_PACING` distinguishes compositor/display-driven draws from
X11's unpaced flush-driven draws. `yasl_timing.source` distinguishes submission
estimates, frame callbacks, and confirmed presentation feedback. Refresh and
@@ -191,7 +216,7 @@ int main(void)
yasl_surface_schedule_redraw(s);
while (running) {
- yasl_display_dispatch(d);
+ yasl_display_dispatch(d, -1);
struct yasl_input_event ev;
while (yasl_surface_input_poll(s, &ev)) {
if (ev.kind == YASL_INPUT_KEY && ev.key.action == YASL_KEY_PRESS) {
diff --git a/examples/simple_window.c b/examples/simple_window.c
index a6819f9..40a7c25 100644
--- a/examples/simple_window.c
+++ b/examples/simple_window.c
@@ -146,7 +146,7 @@ int main(void)
printf(" - Escape: exit\n\n");
while (running) {
- yasl_display_dispatch(d);
+ yasl_display_dispatch(d, -1);
struct yasl_input_event ev;
while (yasl_surface_input_poll(s, &ev)) {
diff --git a/include/yasl/loop.h b/include/yasl/loop.h
new file mode 100644
index 0000000..a65fa12
--- /dev/null
+++ b/include/yasl/loop.h
@@ -0,0 +1,46 @@
+#ifndef YASL_LOOP_H
+#define YASL_LOOP_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. */
+YASL_API struct yasl_loop *yasl_loop_create(void);
+YASL_API void yasl_loop_destroy(struct yasl_loop *l);
+YASL_API int yasl_loop_run(struct yasl_loop *l);
+YASL_API int yasl_loop_step(struct yasl_loop *l, int timeout_ms);
+YASL_API void yasl_loop_quit(struct yasl_loop *l);
+YASL_API void yasl_loop_kick(struct yasl_loop *l);
+YASL_API uint64_t yasl_loop_now_ns(void);
+YASL_API struct yasl_loop *yasl_display_loop(struct yasl_display *d);
+YASL_API int yasl_loop_add_fd(struct yasl_loop *l, int fd, short events,
+ int (*fn)(int fd, short revents, void *ud), void *ud);
+YASL_API int yasl_loop_del_fd(struct yasl_loop *l, int id);
+YASL_API int yasl_loop_set_fd_events(struct yasl_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. */
+typedef uint32_t yasl_timer_id;
+typedef uint64_t (*yasl_timer_cb)(void *ud, yasl_timer_id id, uint64_t interval_ns);
+
+#define YASL_MS_TO_NS(ms) ((uint64_t)(ms) * 1000000ull)
+
+YASL_API yasl_timer_id yasl_loop_add_timer(struct yasl_loop *l, uint64_t interval_ns,
+ yasl_timer_cb cb, void *ud);
+YASL_API bool yasl_loop_del_timer(struct yasl_loop *l, yasl_timer_id id);
+YASL_API yasl_timer_id yasl_surface_add_timer(struct yasl_surface *s, uint64_t interval_ns,
+ yasl_timer_cb cb, void *ud);
+YASL_API bool yasl_surface_del_timer(struct yasl_surface *s, yasl_timer_id id);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* YASL_LOOP_H */
diff --git a/include/yasl/native.h b/include/yasl/native.h
new file mode 100644
index 0000000..f7e3020
--- /dev/null
+++ b/include/yasl/native.h
@@ -0,0 +1,44 @@
+#ifndef YASL_NATIVE_H
+#define YASL_NATIVE_H
+
+#include "yasl/yasl.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Optional platform interoperability. All handles are borrowed and expire on
+ * object destruction. Check yasl_display_backend() before casting:
+ *
+ * display surface
+ * Wayland struct wl_display * struct wl_surface *
+ * X11 Display * Window (cast through uintptr_t)
+ * Cocoa NSApplication * NSWindow * (view: window.contentView)
+ */
+YASL_API void *yasl_display_get_native_handle(struct yasl_display *d);
+YASL_API void *yasl_surface_get_native_handle(struct yasl_surface *s);
+
+/* Create a surface for application-managed native rendering. graphics_device
+ * must be non-NULL; currently an id<MTLDevice> on Cocoa, retained by the surface.
+ * Other backends return NULL with errno=ENOTSUP. CPU rendering uses the ordinary
+ * yasl_surface_create_toplevel() and requires no native device. */
+YASL_API struct yasl_surface *yasl_surface_create_native(struct yasl_display *d,
+ const struct yasl_surface_spec *spec, void *graphics_device);
+
+struct yasl_native_frame {
+ void *drawable; /* id<CAMetalDrawable> on Cocoa */
+ bool transaction; /* CAMetalLayer.presentsWithTransaction */
+};
+
+/* Query the current draw target's native drawable. Returns false for unsupported
+ * backends, stale targets, or outside the draw callback. The result expires on
+ * successful present, callback return, or surface destruction. Native renderers
+ * submit/present the drawable, then call yasl_surface_present() to record it. */
+YASL_API bool yasl_surface_get_native_frame(struct yasl_surface *s,
+ const struct yasl_framebuffer *target, struct yasl_native_frame *out);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* YASL_NATIVE_H */
diff --git a/include/yasl/timing.h b/include/yasl/timing.h
index 85f305d..d443bf6 100644
--- a/include/yasl/timing.h
+++ b/include/yasl/timing.h
@@ -1,7 +1,6 @@
#ifndef YASL_TIMING_H
#define YASL_TIMING_H
-#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
@@ -32,28 +31,6 @@ struct yasl_timing {
struct yasl_resize_stats resize;
};
-#define YASL_REFRESH_DEFAULT_NS 16666666.0
-
-struct yasl_frame_clock {
- uint64_t last_present_ns;
- uint64_t resize_count;
- uint64_t resize_sum_ns;
- uint64_t resize_max_ns;
- long resize_minflt;
- uint64_t frame_time_ns;
- double refresh_ns;
- enum yasl_timing_source source;
-};
-
-uint64_t yasl_clock_extend_ms(uint32_t ms, uint64_t now_ns);
-void yasl_clock_presented(struct yasl_frame_clock *c, uint64_t now, bool sample);
-void yasl_clock_note_resize(struct yasl_frame_clock *c, uint64_t dt_ns);
-void yasl_clock_resize_stats(const struct yasl_frame_clock *c,
- struct yasl_resize_stats *out);
-double yasl_clock_refresh_ms(const struct yasl_frame_clock *c);
-uint64_t yasl_clock_next_present_ns(const struct yasl_frame_clock *c);
-uint64_t yasl_clock_frame_timestamp_ns(struct yasl_frame_clock *c, uint64_t now);
-
#ifdef __cplusplus
}
#endif
diff --git a/include/yasl/yasl.h b/include/yasl/yasl.h
index cde7f34..30bf591 100644
--- a/include/yasl/yasl.h
+++ b/include/yasl/yasl.h
@@ -136,11 +136,6 @@ struct yasl_framebuffer {
uint64_t generation; /* Opaque draw identity; preserve when copying target. */
uint32_t *pixels;
struct yasl_vector2i size;
- int buffer;
- bool cleared;
- struct yasl_damage_list *damage;
- void *native_drawable;
- bool transaction;
};
struct yasl_frame {
@@ -250,6 +245,25 @@ enum yasl_input_kind {
YASL_INPUT_RESET,
};
+struct yasl_focus_event {
+ bool focused;
+};
+
+struct yasl_text_event {
+ const char *text;
+ size_t len;
+};
+
+struct yasl_paste_event {
+ const char *text;
+ size_t len;
+};
+
+struct yasl_drop_files_event {
+ const char *files;
+ size_t len;
+};
+
struct yasl_input_event {
struct yasl_surface *surface;
enum yasl_input_kind kind;
@@ -257,10 +271,10 @@ struct yasl_input_event {
union {
struct yasl_key_event key;
struct yasl_mouse_event mouse;
- struct { bool focused; } focus;
- struct { const char *text; size_t len; } text;
- struct { const char *text; size_t len; } paste;
- struct { const char *files; size_t len; } drop_files;
+ struct yasl_focus_event focus;
+ struct yasl_text_event text;
+ struct yasl_paste_event paste;
+ struct yasl_drop_files_event drop_files;
};
};
@@ -270,7 +284,6 @@ struct yasl_input_event {
* popups before parents, and displays before their supplied loop.
* Specs, strings, callback tables and drag listeners are copied during calls.
* Userdata is borrowed until callbacks are removed or the object/drag ends.
- * Native handles are borrowed and expire when their owning object is destroyed.
* Surface destruction is allowed from its callbacks; display/loop destruction
* during dispatch or callbacks is not supported.
*/
@@ -315,8 +328,9 @@ YASL_API struct yasl_display *yasl_display_open(struct yasl_loop *loop,
YASL_API enum yasl_backend yasl_display_backend(const struct yasl_display *d);
YASL_API void yasl_display_close(struct yasl_display *d);
YASL_API uint32_t yasl_display_caps(const struct yasl_display *d);
-YASL_API void yasl_display_dispatch(struct yasl_display *d);
-YASL_API int yasl_display_dispatch_timeout(struct yasl_display *d, int timeout_ms);
+/* Process events, waiting at most timeout_ms: zero polls, negative waits.
+ * Pending input or redraw work returns immediately. Returns -1 on failure. */
+YASL_API int yasl_display_dispatch(struct yasl_display *d, int timeout_ms);
YASL_API void yasl_display_flush(struct yasl_display *d);
YASL_API int yasl_display_modifiers(const struct yasl_display *d);
/* Unsupported/invalid selections are no-ops, never clipboard aliases. */
@@ -336,10 +350,6 @@ struct yasl_surface_spec {
struct yasl_vector2i size;
bool fullscreen;
bool tearing;
- /* Optional native graphics device (retained on Cocoa). Requires
- * YASL_CAP_NATIVE_GRAPHICS; other backends reject it with ENOTSUP.
- * NULL selects CPU pixel presentation. */
- void *graphics_device;
};
struct yasl_popup_spec {
@@ -364,12 +374,10 @@ YASL_API void yasl_surface_set_callbacks(struct yasl_surface *s,
YASL_API void yasl_surface_set_title(struct yasl_surface *s, const char *title);
YASL_API void yasl_surface_schedule_redraw(struct yasl_surface *s);
/* Present exactly the target supplied to the current draw callback, at most
- * once. Targets and native drawables expire when that callback returns or
- * the surface is destroyed.
+ * once. Targets expire when that callback returns or the surface is destroyed.
* damage is required: n must be -1 or 0..YASL_DAMAGE_MAX. Rectangles must have
* nonnegative extents; they are clipped to the target. Invalid input returns
- * false without consuming the frame. Native renderers submit/present their
- * drawable themselves, then call this function to record the submission. */
+ * false without consuming the frame. See yasl/native.h for native rendering. */
YASL_API bool yasl_surface_present(struct yasl_surface *s, const struct yasl_framebuffer *fb,
const struct yasl_damage_list *damage);
/* Event payload strings are library-owned, NUL-terminated, and valid until
@@ -377,8 +385,8 @@ YASL_API bool yasl_surface_present(struct yasl_surface *s, const struct yasl_fra
* flush and unsuccessful polls do not invalidate them. Copy to keep longer. */
YASL_API bool yasl_surface_input_poll(struct yasl_surface *s, struct yasl_input_event *ev);
-YASL_API struct yasl_vector2i yasl_surface_size(const struct yasl_surface *s);
-YASL_API uint32_t yasl_surface_scale_120(const struct yasl_surface *s);
+/* Current device-pixel size and scale, matching the configure callback. */
+YASL_API struct yasl_geometry yasl_surface_geometry(const struct yasl_surface *s);
YASL_API uint32_t yasl_surface_state(const struct yasl_surface *s);
/* Timing uses monotonic nanoseconds. Check source before treating timestamps
* as presentation confirmation. refresh/next_present are estimates. Without
@@ -403,64 +411,6 @@ YASL_API bool yasl_surface_show_window_menu(struct yasl_surface *s, struct yasl_
YASL_API bool yasl_surface_drag_carry(struct yasl_surface *s, struct yasl_vector2i offset);
YASL_API bool yasl_surface_place_under_pointer(struct yasl_surface *s, struct yasl_vector2i at);
-/* Native Interoperability Handles */
-YASL_API void *yasl_display_get_native_handle(struct yasl_display *d);
-YASL_API void *yasl_surface_get_native_handle(struct yasl_surface *s);
-
-/* Integrated Event Loop */
-YASL_API struct yasl_loop *yasl_loop_create(void);
-YASL_API void yasl_loop_destroy(struct yasl_loop *l);
-YASL_API int yasl_loop_run(struct yasl_loop *l);
-YASL_API int yasl_loop_step(struct yasl_loop *l, int timeout_ms);
-YASL_API void yasl_loop_quit(struct yasl_loop *l);
-YASL_API bool yasl_loop_is_running(const struct yasl_loop *l);
-YASL_API void yasl_loop_kick(struct yasl_loop *l);
-YASL_API uint64_t yasl_loop_now_ns(void);
-YASL_API int yasl_loop_add_fd(struct yasl_loop *l, int fd, short events,
- int (*fn)(int fd, short revents, void *ud), void *ud);
-YASL_API int yasl_loop_del_fd(struct yasl_loop *l, int id);
-YASL_API int yasl_loop_set_fd_events(struct yasl_loop *l, int id, short events);
-YASL_API int yasl_loop_next_timeout_ms(const struct yasl_loop *l);
-
-/* Timers (SDL3-compatible callback model)
- * Timer callbacks return the next interval in nanoseconds (or milliseconds for _ms),
- * or 0 to disarm and remove the timer. Returning the same interval repeats the timer;
- * returning a different non-zero interval reschedules it with the new interval.
- *
- * All timers fire on the display's opening thread during event dispatch/step.
- * Surface timers are bound to a specific surface and are automatically canceled
- * if that surface is destroyed. */
-typedef uint32_t yasl_timer_id;
-
-typedef uint64_t (*yasl_timer_cb)(void *ud, yasl_timer_id id, uint64_t interval_ns);
-typedef uint32_t (*yasl_timer_ms_cb)(void *ud, yasl_timer_id id, uint32_t interval_ms);
-
-#define YASL_MS_TO_NS(ms) ((uint64_t)(ms) * 1000000ull)
-#define YASL_NS_TO_MS(ns) ((uint64_t)(ns) / 1000000ull)
-
-/* Surface-bound Timers */
-YASL_API yasl_timer_id yasl_surface_add_timer(struct yasl_surface *s, uint64_t interval_ns,
- yasl_timer_cb cb, void *ud);
-YASL_API yasl_timer_id yasl_surface_add_timer_ms(struct yasl_surface *s, uint32_t interval_ms,
- yasl_timer_ms_cb cb, void *ud);
-YASL_API bool yasl_surface_del_timer(struct yasl_surface *s, yasl_timer_id id);
-
-/* Display-bound Timers */
-YASL_API yasl_timer_id yasl_display_add_timer(struct yasl_display *d, uint64_t interval_ns,
- yasl_timer_cb cb, void *ud);
-YASL_API yasl_timer_id yasl_display_add_timer_ms(struct yasl_display *d, uint32_t interval_ms,
- yasl_timer_ms_cb cb, void *ud);
-YASL_API bool yasl_display_del_timer(struct yasl_display *d, yasl_timer_id id);
-YASL_API struct yasl_loop *yasl_display_loop(struct yasl_display *d);
-YASL_API struct yasl_loop *yasl_surface_loop(struct yasl_surface *s);
-
-/* Loop Timers */
-YASL_API yasl_timer_id yasl_loop_add_timer(struct yasl_loop *l, uint64_t interval_ns,
- yasl_timer_cb cb, void *ud);
-YASL_API yasl_timer_id yasl_loop_add_timer_ms(struct yasl_loop *l, uint32_t interval_ms,
- yasl_timer_ms_cb cb, void *ud);
-YASL_API bool yasl_loop_del_timer(struct yasl_loop *l, yasl_timer_id id);
-
#ifdef __cplusplus
}
#endif
diff --git a/meson.build b/meson.build
index e599acf..2bcad87 100644
--- a/meson.build
+++ b/meson.build
@@ -70,12 +70,15 @@ yasl_lib = both_libraries(
sources,
include_directories: inc,
dependencies: deps,
+ gnu_symbol_visibility: 'hidden',
install: true,
)
install_headers(
'include/yasl/yasl.h',
'include/yasl/timing.h',
+ 'include/yasl/loop.h',
+ 'include/yasl/native.h',
subdir: 'yasl',
)
@@ -85,7 +88,7 @@ pkg.generate(
name: 'yasl',
description: 'Yet Another Surface Library - minimal, modern desktop window surface abstraction',
version: meson.project_version(),
- subdirs: 'yasl',
+ subdirs: '.',
)
yasl_dep = declare_dependency(
diff --git a/src/cocoa/api.h b/src/cocoa/api.h
index 2fea415..59372cf 100644
--- a/src/cocoa/api.h
+++ b/src/cocoa/api.h
@@ -9,7 +9,6 @@ struct cocoa_surface;
struct cocoa_display *cocoa_display_open(struct yasl_loop *loop, const struct yasl_display_spec *spec);
void cocoa_display_close(struct cocoa_display *d);
uint32_t cocoa_display_caps(const struct cocoa_display *d);
-void cocoa_display_dispatch(struct cocoa_display *d);
int cocoa_display_dispatch_timeout(struct cocoa_display *d, int timeout_ms);
void cocoa_display_flush(struct cocoa_display *d);
int cocoa_display_modifiers(const struct cocoa_display *d);
@@ -17,7 +16,8 @@ void cocoa_display_copy(struct cocoa_display *d, enum yasl_selection sel, const
void cocoa_display_paste(struct cocoa_display *d, enum yasl_selection sel, struct cocoa_surface *target);
bool cocoa_display_drag_start(struct cocoa_display *d, struct cocoa_surface *origin, const struct yasl_drag_image *image, const struct yasl_drag_listener *l, void *ud);
void cocoa_display_drag_cancel(struct cocoa_display *d);
-struct cocoa_surface *cocoa_surface_create_toplevel(struct cocoa_display *d, const struct yasl_surface_spec *spec);
+struct cocoa_surface *cocoa_surface_create_toplevel(struct cocoa_display *d,
+ const struct yasl_surface_spec *spec, void *graphics_device);
struct cocoa_surface *cocoa_surface_create_popup(struct cocoa_surface *parent, const struct yasl_popup_spec *spec);
bool cocoa_surface_popup_reposition(struct cocoa_surface *popup, const struct yasl_popup_spec *spec);
void cocoa_surface_destroy(struct cocoa_surface *s);
@@ -27,8 +27,7 @@ void cocoa_surface_set_title(struct cocoa_surface *s, const char *title);
void cocoa_surface_schedule_redraw(struct cocoa_surface *s);
bool cocoa_surface_present(struct cocoa_surface *s, const struct yasl_framebuffer *fb, const struct yasl_damage_list *damage);
bool cocoa_surface_input_poll(struct cocoa_surface *s, struct yasl_input_event *ev);
-struct yasl_vector2i cocoa_surface_size(const struct cocoa_surface *s);
-uint32_t cocoa_surface_scale_120(const struct cocoa_surface *s);
+struct yasl_geometry cocoa_surface_geometry(const struct cocoa_surface *s);
uint32_t cocoa_surface_state(const struct cocoa_surface *s);
struct yasl_timing cocoa_surface_timing(const struct cocoa_surface *s);
uint64_t cocoa_surface_frame_time_ns(struct cocoa_surface *s, uint64_t now);
diff --git a/src/cocoa/window.m b/src/cocoa/window.m
index fc69412..6e1e457 100644
--- a/src/cocoa/window.m
+++ b/src/cocoa/window.m
@@ -3,7 +3,8 @@
#import <QuartzCore/QuartzCore.h>
#include "cocoa/api.h"
-#include "yasl/timing.h"
+#include "common/timing.h"
+#include "common/loop.h"
#include "cocoa/keycodes.h"
#include <assert.h>
@@ -70,6 +71,8 @@ struct cocoa_surface {
uint32_t *pixels;
size_t pixel_capacity;
+ id<CAMetalDrawable> drawable;
+ bool transaction;
struct yasl_frame frame;
struct yasl_damage_list damage;
bool did_present;
@@ -775,9 +778,7 @@ bool cocoa_surface_present(struct cocoa_surface *w, const struct yasl_framebuffe
{
if (!w || w->closed || !w->rendering || w->did_present || !fb || !damage ||
fb->pixels != w->frame.target.pixels || fb->size.x != w->size.x ||
- fb->size.y != w->size.y || fb->buffer != w->frame.target.buffer ||
- fb->transaction != w->frame.target.transaction ||
- !fb->native_drawable || fb->native_drawable != w->frame.target.native_drawable)
+ fb->size.y != w->size.y || !w->drawable)
return false;
if (w->software_queue) {
if (fb->pixels != w->pixels || fb->size.x != w->size.x || fb->size.y != w->size.y)
@@ -793,7 +794,7 @@ bool cocoa_surface_present(struct cocoa_surface *w, const struct yasl_framebuffe
id<MTLCommandBuffer> command = [w->software_queue commandBuffer];
id<MTLBlitCommandEncoder> blit = [command blitCommandEncoder];
if (!blit) { [buffer release]; return false; }
- id<CAMetalDrawable> drawable = (id<CAMetalDrawable>)fb->native_drawable;
+ id<CAMetalDrawable> drawable = w->drawable;
[blit copyFromBuffer:buffer sourceOffset:0 sourceBytesPerRow:row_bytes
sourceBytesPerImage:length sourceSize:MTLSizeMake(w->size.x, w->size.y, 1)
toTexture:drawable.texture destinationSlice:0 destinationLevel:0
@@ -827,6 +828,8 @@ static void render_and_commit(struct cocoa_surface *w)
w->frame_due = false;
w->did_present = false;
id<CAMetalDrawable> drawable = [w->layer nextDrawable];
+ w->drawable = drawable;
+ w->transaction = w->layer.presentsWithTransaction;
w->damage = (struct yasl_damage_list){0};
w->frame = (struct yasl_frame){
.surface = &w->base,
@@ -834,9 +837,6 @@ static void render_and_commit(struct cocoa_surface *w)
.generation = yasl_frame_begin(&w->base),
.pixels = w->software_queue ? w->pixels : NULL,
.size = w->size,
- .damage = &w->damage,
- .native_drawable = drawable,
- .transaction = w->layer.presentsWithTransaction,
},
};
if (drawable)
@@ -845,7 +845,7 @@ static void render_and_commit(struct cocoa_surface *w)
w->want_draw = true;
w->frames_skipped++;
}
- w->frame.target.native_drawable = NULL;
+ w->drawable = nil;
w->rendering = false;
darwin_ctx.depth--;
}
@@ -864,6 +864,14 @@ int cocoa_display_dispatch_timeout(struct cocoa_display *d, int timeout_ms)
assert([NSThread isMainThread]);
if (!d || d->pumping)
return -1;
+ 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;
+ }
+ }
d->pumping = true;
d->depth++;
int count = 0;
@@ -914,21 +922,6 @@ int cocoa_display_dispatch_timeout(struct cocoa_display *d, int timeout_ms)
return count;
}
-void cocoa_display_dispatch(struct cocoa_display *d)
-{
- if (!d) return;
- bool immediate = false;
- for (int i = 0; i < d->count; i++) {
- struct cocoa_surface *w = d->windows[i];
- if (w->input.count > 0 || w->input.reset_pending ||
- (w->want_draw && (!w->link || w->frame_due || w->unpaced))) {
- immediate = true;
- break;
- }
- }
- cocoa_display_dispatch_timeout(d, immediate ? 0 : -1);
-}
-
void cocoa_display_flush(struct cocoa_display *d)
{
assert([NSThread isMainThread]);
@@ -1061,7 +1054,7 @@ uint32_t cocoa_display_caps(const struct cocoa_display *d)
}
struct cocoa_surface *cocoa_surface_create_toplevel(struct cocoa_display *d,
- const struct yasl_surface_spec *spec)
+ const struct yasl_surface_spec *spec, void *graphics_device)
{
assert([NSThread isMainThread]);
if (!d || !d->open || !spec)
@@ -1104,8 +1097,8 @@ struct cocoa_surface *cocoa_surface_create_toplevel(struct cocoa_display *d,
w->view = [[YaslView alloc] initWithFrame:w->window.contentView.bounds];
w->window.contentView = w->view;
w->layer = (CAMetalLayer *)w->view.layer;
- if (spec->graphics_device) {
- w->layer.device = (id<MTLDevice>)spec->graphics_device;
+ if (graphics_device) {
+ w->layer.device = (id<MTLDevice>)graphics_device;
} else {
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
w->layer.device = device;
@@ -1311,14 +1304,12 @@ int cocoa_display_modifiers(const struct cocoa_display *d)
return (int)cocoa_mods(NSEvent.modifierFlags);
}
-struct yasl_vector2i cocoa_surface_size(const struct cocoa_surface *w)
+struct yasl_geometry cocoa_surface_geometry(const struct cocoa_surface *w)
{
- return w ? w->size : (struct yasl_vector2i){0};
-}
-
-uint32_t cocoa_surface_scale_120(const struct cocoa_surface *w)
-{
- return w ? w->scale_120 : YASL_SCALE_UNIT;
+ return (struct yasl_geometry){
+ .size = w ? w->size : (struct yasl_vector2i){0},
+ .scale_120 = w ? w->scale_120 : YASL_SCALE_UNIT,
+ };
}
struct cocoa_surface *cocoa_surface_create_popup(struct cocoa_surface *parent,
@@ -1367,12 +1358,12 @@ bool cocoa_surface_show_window_menu(struct cocoa_surface *s, struct yasl_vector2
void *cocoa_display_get_native_handle(struct cocoa_display *d)
{
- return d ? (void *)d : NULL;
+ return d ? (void *)NSApp : NULL;
}
void *cocoa_surface_get_native_handle(struct cocoa_surface *s)
{
- return s ? (void *)s->view : NULL;
+ return s ? (void *)s->window : NULL;
}
@@ -1431,11 +1422,6 @@ static uint32_t impl_display_caps(const struct yasl_display *d)
return cocoa_display_caps((const struct cocoa_display *)d);
}
-static void impl_display_dispatch(struct yasl_display *d)
-{
- cocoa_display_dispatch((struct cocoa_display *)d);
-}
-
static int impl_display_dispatch_timeout(struct yasl_display *d, int timeout_ms)
{
return cocoa_display_dispatch_timeout((struct cocoa_display *)d, timeout_ms);
@@ -1471,9 +1457,10 @@ static void impl_display_drag_cancel(struct yasl_display *d)
cocoa_display_drag_cancel((struct cocoa_display *)d);
}
-static struct yasl_surface *impl_surface_create_toplevel(struct yasl_display *d, const struct yasl_surface_spec *spec)
+static struct yasl_surface *impl_surface_create_toplevel(struct yasl_display *d,
+ const struct yasl_surface_spec *spec, void *graphics_device)
{
- struct cocoa_surface *result = cocoa_surface_create_toplevel((struct cocoa_display *)d, spec);
+ struct cocoa_surface *result = cocoa_surface_create_toplevel((struct cocoa_display *)d, spec, graphics_device);
return result ? &result->base : NULL;
}
@@ -1524,14 +1511,9 @@ static bool impl_surface_input_poll(struct yasl_surface *s, struct yasl_input_ev
return cocoa_surface_input_poll((struct cocoa_surface *)s, ev);
}
-static struct yasl_vector2i impl_surface_size(const struct yasl_surface *s)
+static struct yasl_geometry impl_surface_geometry(const struct yasl_surface *s)
{
- return cocoa_surface_size((const struct cocoa_surface *)s);
-}
-
-static uint32_t impl_surface_scale_120(const struct yasl_surface *s)
-{
- return cocoa_surface_scale_120((const struct cocoa_surface *)s);
+ return cocoa_surface_geometry((const struct cocoa_surface *)s);
}
static uint32_t impl_surface_state(const struct yasl_surface *s)
@@ -1609,12 +1591,23 @@ static void *impl_surface_get_native_handle(struct yasl_surface *s)
return cocoa_surface_get_native_handle((struct cocoa_surface *)s);
}
+static bool impl_surface_get_native_frame(struct yasl_surface *s,
+ const struct yasl_framebuffer *target, struct yasl_native_frame *out)
+{
+ struct cocoa_surface *w = (struct cocoa_surface *)s;
+ if (w->closed || !w->rendering || w->did_present || !w->drawable ||
+ target->pixels != w->frame.target.pixels ||
+ target->size.x != w->size.x || target->size.y != w->size.y)
+ return false;
+ *out = (struct yasl_native_frame){(void *)w->drawable, w->transaction};
+ return true;
+}
+
const struct yasl_backend_ops yasl_cocoa_backend = {
.type = YASL_BACKEND_COCOA,
.display_open = impl_display_open,
.display_close = impl_display_close,
.display_caps = impl_display_caps,
- .display_dispatch = impl_display_dispatch,
.display_dispatch_timeout = impl_display_dispatch_timeout,
.display_flush = impl_display_flush,
.display_modifiers = impl_display_modifiers,
@@ -1632,8 +1625,7 @@ const struct yasl_backend_ops yasl_cocoa_backend = {
.surface_schedule_redraw = impl_surface_schedule_redraw,
.surface_present = impl_surface_present,
.surface_input_poll = impl_surface_input_poll,
- .surface_size = impl_surface_size,
- .surface_scale_120 = impl_surface_scale_120,
+ .surface_geometry = impl_surface_geometry,
.surface_state = impl_surface_state,
.surface_timing = impl_surface_timing,
.surface_frame_time_ns = impl_surface_frame_time_ns,
@@ -1649,4 +1641,5 @@ const struct yasl_backend_ops yasl_cocoa_backend = {
.surface_place_under_pointer = impl_surface_place_under_pointer,
.display_get_native_handle = impl_display_get_native_handle,
.surface_get_native_handle = impl_surface_get_native_handle,
+ .surface_get_native_frame = impl_surface_get_native_frame,
};
diff --git a/src/common/backend.c b/src/common/backend.c
index 27ab297..ad4d908 100644
--- a/src/common/backend.c
+++ b/src/common/backend.c
@@ -78,22 +78,11 @@ uint32_t yasl_display_caps(const struct yasl_display *d)
return d->ops->display_caps(d);
}
-void yasl_display_dispatch(struct yasl_display *d)
-{
- if (!d || !d->ops->display_dispatch)
- return;
- d->ops->display_dispatch(d);
-}
-
-int yasl_display_dispatch_timeout(struct yasl_display *d, int timeout_ms)
+int yasl_display_dispatch(struct yasl_display *d, int timeout_ms)
{
if (!d) return -1;
if (d->ops->display_dispatch_timeout)
return d->ops->display_dispatch_timeout(d, timeout_ms);
- if (d->ops->display_dispatch) {
- d->ops->display_dispatch(d);
- return 0;
- }
return -1;
}
@@ -147,7 +136,8 @@ void yasl_display_drag_cancel(struct yasl_display *d)
d->ops->display_drag_cancel(d);
}
-struct yasl_surface *yasl_surface_create_toplevel(struct yasl_display *d, const struct yasl_surface_spec *spec)
+struct yasl_surface *yasl_create_toplevel(struct yasl_display *d,
+ const struct yasl_surface_spec *spec, void *graphics_device)
{
if (!d || !d->ops->surface_create_toplevel || !spec ||
spec->size.x <= 0 || spec->size.y <= 0 ||
@@ -156,11 +146,17 @@ struct yasl_surface *yasl_surface_create_toplevel(struct yasl_display *d, const
errno = EINVAL;
return NULL;
}
- if (spec->graphics_device && !(yasl_display_caps(d) & YASL_CAP_NATIVE_GRAPHICS)) {
+ if (graphics_device && !(yasl_display_caps(d) & YASL_CAP_NATIVE_GRAPHICS)) {
errno = ENOTSUP;
return NULL;
}
- return d->ops->surface_create_toplevel(d, spec);
+ return d->ops->surface_create_toplevel(d, spec, graphics_device);
+}
+
+struct yasl_surface *yasl_surface_create_toplevel(struct yasl_display *d,
+ const struct yasl_surface_spec *spec)
+{
+ return yasl_create_toplevel(d, spec, NULL);
}
struct yasl_surface *yasl_surface_create_popup(struct yasl_surface *parent, const struct yasl_popup_spec *spec)
@@ -247,18 +243,11 @@ bool yasl_surface_input_poll(struct yasl_surface *s, struct yasl_input_event *ev
return s->display->ops->surface_input_poll(s, ev);
}
-struct yasl_vector2i yasl_surface_size(const struct yasl_surface *s)
-{
- if (!s || !s->display->ops->surface_size)
- return (struct yasl_vector2i){0};
- return s->display->ops->surface_size(s);
-}
-
-uint32_t yasl_surface_scale_120(const struct yasl_surface *s)
+struct yasl_geometry yasl_surface_geometry(const struct yasl_surface *s)
{
- if (!s || !s->display->ops->surface_scale_120)
- return YASL_SCALE_UNIT;
- return s->display->ops->surface_scale_120(s);
+ if (!s || !s->display->ops->surface_geometry)
+ return (struct yasl_geometry){.scale_120 = YASL_SCALE_UNIT};
+ return s->display->ops->surface_geometry(s);
}
uint32_t yasl_surface_state(const struct yasl_surface *s)
@@ -356,20 +345,6 @@ bool yasl_surface_place_under_pointer(struct yasl_surface *s, struct yasl_vector
return s->display->ops->surface_place_under_pointer(s, at);
}
-void *yasl_display_get_native_handle(struct yasl_display *d)
-{
- if (!d || !d->ops->display_get_native_handle)
- return 0;
- return d->ops->display_get_native_handle(d);
-}
-
-void *yasl_surface_get_native_handle(struct yasl_surface *s)
-{
- if (!s || !s->display->ops->surface_get_native_handle)
- return 0;
- return s->display->ops->surface_get_native_handle(s);
-}
-
void yasl_surface_clear_timers(struct yasl_surface *s)
{
if (!s || !s->timers) return;
@@ -408,29 +383,6 @@ yasl_timer_id yasl_surface_add_timer(struct yasl_surface *s, uint64_t interval_n
return id;
}
-yasl_timer_id yasl_surface_add_timer_ms(struct yasl_surface *s, uint32_t interval_ms,
- yasl_timer_ms_cb cb, void *ud)
-{
- if (!s || !s->display || !s->display->loop || !cb || interval_ms == 0)
- return 0;
-
- yasl_timer_id id = yasl_loop_add_timer_ms(s->display->loop, interval_ms, cb, ud);
- if (!id) return 0;
-
- if (s->timer_count >= s->timer_capacity) {
- int new_cap = s->timer_capacity ? s->timer_capacity * 2 : 4;
- yasl_timer_id *nt = realloc(s->timers, (size_t)new_cap * sizeof(*nt));
- if (!nt) {
- yasl_loop_del_timer(s->display->loop, id);
- return 0;
- }
- s->timers = nt;
- s->timer_capacity = new_cap;
- }
- s->timers[s->timer_count++] = id;
- return id;
-}
-
bool yasl_surface_del_timer(struct yasl_surface *s, yasl_timer_id id)
{
if (!s || id == 0) return false;
@@ -445,32 +397,7 @@ bool yasl_surface_del_timer(struct yasl_surface *s, yasl_timer_id id)
return false;
}
-yasl_timer_id yasl_display_add_timer(struct yasl_display *d, uint64_t interval_ns,
- yasl_timer_cb cb, void *ud)
-{
- if (!d || !d->loop) return 0;
- return yasl_loop_add_timer(d->loop, interval_ns, cb, ud);
-}
-
-yasl_timer_id yasl_display_add_timer_ms(struct yasl_display *d, uint32_t interval_ms,
- yasl_timer_ms_cb cb, void *ud)
-{
- if (!d || !d->loop) return 0;
- return yasl_loop_add_timer_ms(d->loop, interval_ms, cb, ud);
-}
-
-bool yasl_display_del_timer(struct yasl_display *d, yasl_timer_id id)
-{
- if (!d || !d->loop) return false;
- return yasl_loop_del_timer(d->loop, id);
-}
-
struct yasl_loop *yasl_display_loop(struct yasl_display *d)
{
return d ? d->loop : NULL;
}
-
-struct yasl_loop *yasl_surface_loop(struct yasl_surface *s)
-{
- return (s && s->display) ? s->display->loop : NULL;
-}
diff --git a/src/common/backend.h b/src/common/backend.h
index b52a147..6ab7248 100644
--- a/src/common/backend.h
+++ b/src/common/backend.h
@@ -2,6 +2,8 @@
#define YASL_BACKEND_H
#include "yasl/yasl.h"
+#include "yasl/loop.h"
+#include "yasl/native.h"
struct yasl_backend_ops;
@@ -21,6 +23,8 @@ struct yasl_surface {
};
void yasl_surface_clear_timers(struct yasl_surface *s);
+struct yasl_surface *yasl_create_toplevel(struct yasl_display *d,
+ const struct yasl_surface_spec *spec, void *graphics_device);
static inline uint64_t yasl_frame_begin(struct yasl_surface *s)
{
@@ -33,7 +37,6 @@ struct yasl_backend_ops {
struct yasl_display *(*display_open)(struct yasl_loop *loop, const struct yasl_display_spec *spec);
void (*display_close)(struct yasl_display *d);
uint32_t (*display_caps)(const struct yasl_display *d);
- void (*display_dispatch)(struct yasl_display *d);
int (*display_dispatch_timeout)(struct yasl_display *d, int timeout_ms);
void (*display_flush)(struct yasl_display *d);
int (*display_modifiers)(const struct yasl_display *d);
@@ -41,7 +44,8 @@ struct yasl_backend_ops {
void (*display_paste)(struct yasl_display *d, enum yasl_selection sel, struct yasl_surface *target);
bool (*display_drag_start)(struct yasl_display *d, struct yasl_surface *origin, const struct yasl_drag_image *image, const struct yasl_drag_listener *l, void *ud);
void (*display_drag_cancel)(struct yasl_display *d);
- struct yasl_surface *(*surface_create_toplevel)(struct yasl_display *d, const struct yasl_surface_spec *spec);
+ struct yasl_surface *(*surface_create_toplevel)(struct yasl_display *d,
+ const struct yasl_surface_spec *spec, void *graphics_device);
struct yasl_surface *(*surface_create_popup)(struct yasl_surface *parent, const struct yasl_popup_spec *spec);
bool (*surface_popup_reposition)(struct yasl_surface *popup, const struct yasl_popup_spec *spec);
void (*surface_destroy)(struct yasl_surface *s);
@@ -51,8 +55,7 @@ struct yasl_backend_ops {
void (*surface_schedule_redraw)(struct yasl_surface *s);
bool (*surface_present)(struct yasl_surface *s, const struct yasl_framebuffer *fb, const struct yasl_damage_list *damage);
bool (*surface_input_poll)(struct yasl_surface *s, struct yasl_input_event *ev);
- struct yasl_vector2i (*surface_size)(const struct yasl_surface *s);
- uint32_t (*surface_scale_120)(const struct yasl_surface *s);
+ struct yasl_geometry (*surface_geometry)(const struct yasl_surface *s);
uint32_t (*surface_state)(const struct yasl_surface *s);
struct yasl_timing (*surface_timing)(const struct yasl_surface *s);
uint64_t (*surface_frame_time_ns)(struct yasl_surface *s, uint64_t now);
@@ -68,6 +71,8 @@ struct yasl_backend_ops {
bool (*surface_place_under_pointer)(struct yasl_surface *s, struct yasl_vector2i at);
void *(*display_get_native_handle)(struct yasl_display *d);
void *(*surface_get_native_handle)(struct yasl_surface *s);
+ bool (*surface_get_native_frame)(struct yasl_surface *s,
+ const struct yasl_framebuffer *target, struct yasl_native_frame *out);
};
#if YASL_HAS_WAYLAND
diff --git a/src/common/loop.c b/src/common/loop.c
index a1f2434..3815e91 100644
--- a/src/common/loop.c
+++ b/src/common/loop.c
@@ -1,6 +1,6 @@
#define _GNU_SOURCE
#define _POSIX_C_SOURCE 200809L
-#include "yasl/yasl.h"
+#include "loop.h"
#include <errno.h>
#include <fcntl.h>
@@ -23,16 +23,11 @@ struct yasl_fd_source {
};
struct yasl_timer_source {
- union {
- yasl_timer_cb ns;
- yasl_timer_ms_cb ms;
- void *raw;
- } fn;
+ yasl_timer_cb fn;
void *ud;
yasl_timer_id id;
uint64_t interval_ns;
uint64_t deadline_ns;
- bool ms_mode;
bool deleted;
};
@@ -251,44 +246,11 @@ yasl_timer_id yasl_loop_add_timer(struct yasl_loop *l, uint64_t interval_ns,
uint64_t now = yasl_loop_now_ns();
l->timers[l->timer_count++] = (struct yasl_timer_source){
- .fn.ns = cb,
+ .fn = cb,
.ud = ud,
.id = id,
.interval_ns = interval_ns,
.deadline_ns = now + interval_ns,
- .ms_mode = false,
- .deleted = false,
- };
-
- yasl_loop_kick(l);
- return id;
-}
-
-yasl_timer_id yasl_loop_add_timer_ms(struct yasl_loop *l, uint32_t interval_ms,
- yasl_timer_ms_cb cb, void *ud)
-{
- if (!l || !cb || interval_ms == 0) return 0;
-
- if (l->timer_count >= l->timer_capacity) {
- int new_cap = l->timer_capacity ? l->timer_capacity * 2 : 16;
- struct yasl_timer_source *new_timers = realloc(l->timers, (size_t)new_cap * sizeof(*new_timers));
- if (!new_timers) 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_loop_now_ns();
- uint64_t interval_ns = (uint64_t)interval_ms * 1000000ull;
- l->timers[l->timer_count++] = (struct yasl_timer_source){
- .fn.ms = cb,
- .ud = ud,
- .id = id,
- .interval_ns = interval_ns,
- .deadline_ns = now + interval_ns,
- .ms_mode = true,
.deleted = false,
};
@@ -390,18 +352,10 @@ int yasl_loop_step(struct yasl_loop *l, int timeout_ms)
yasl_timer_id tid = l->timers[i].id;
uint64_t interval = l->timers[i].interval_ns;
- bool is_ms = l->timers[i].ms_mode;
void *ud = l->timers[i].ud;
struct yasl_timer_source timer_src = l->timers[i];
- uint64_t next_interval = 0;
- if (is_ms) {
- uint32_t old_ms = (uint32_t)(interval / 1000000ull);
- uint32_t res_ms = timer_src.fn.ms(ud, tid, old_ms);
- next_interval = (uint64_t)res_ms * 1000000ull;
- } else {
- next_interval = timer_src.fn.ns(ud, tid, interval);
- }
+ uint64_t next_interval = timer_src.fn(ud, tid, interval);
handled++;
for (int j = 0; j < l->timer_count; j++) {
@@ -443,8 +397,3 @@ void yasl_loop_quit(struct yasl_loop *l)
{
if (l) l->running = false;
}
-
-bool yasl_loop_is_running(const struct yasl_loop *l)
-{
- return l ? l->running : false;
-}
diff --git a/src/common/loop.h b/src/common/loop.h
new file mode 100644
index 0000000..579fa2c
--- /dev/null
+++ b/src/common/loop.h
@@ -0,0 +1,8 @@
+#ifndef YASL_COMMON_LOOP_H
+#define YASL_COMMON_LOOP_H
+
+#include "yasl/loop.h"
+
+int yasl_loop_next_timeout_ms(const struct yasl_loop *l);
+
+#endif /* YASL_COMMON_LOOP_H */
diff --git a/src/common/meson.build b/src/common/meson.build
index 5cfb187..ddd0194 100644
--- a/src/common/meson.build
+++ b/src/common/meson.build
@@ -1 +1 @@
-sources += files('backend.c', 'input.c', 'loop.c', 'timing.c', 'utf8.c')
+sources += files('backend.c', 'input.c', 'loop.c', 'native.c', 'timing.c', 'utf8.c')
diff --git a/src/common/native.c b/src/common/native.c
new file mode 100644
index 0000000..2d9c06f
--- /dev/null
+++ b/src/common/native.c
@@ -0,0 +1,35 @@
+#include "backend.h"
+
+#include <errno.h>
+
+void *yasl_display_get_native_handle(struct yasl_display *d)
+{
+ return d && d->ops->display_get_native_handle ?
+ d->ops->display_get_native_handle(d) : NULL;
+}
+
+void *yasl_surface_get_native_handle(struct yasl_surface *s)
+{
+ return s && s->display->ops->surface_get_native_handle ?
+ s->display->ops->surface_get_native_handle(s) : NULL;
+}
+
+struct yasl_surface *yasl_surface_create_native(struct yasl_display *d,
+ const struct yasl_surface_spec *spec, void *graphics_device)
+{
+ if (!graphics_device) {
+ errno = EINVAL;
+ return NULL;
+ }
+ return yasl_create_toplevel(d, spec, graphics_device);
+}
+
+bool yasl_surface_get_native_frame(struct yasl_surface *s,
+ const struct yasl_framebuffer *target, struct yasl_native_frame *out)
+{
+ if (!s || !target || !out || !target->generation ||
+ target->generation != s->frame_generation ||
+ !s->display->ops->surface_get_native_frame)
+ return false;
+ return s->display->ops->surface_get_native_frame(s, target, out);
+}
diff --git a/src/common/timing.c b/src/common/timing.c
index 17f71b5..7a91327 100644
--- a/src/common/timing.c
+++ b/src/common/timing.c
@@ -1,4 +1,4 @@
-#include "yasl/timing.h"
+#include "timing.h"
uint64_t yasl_clock_extend_ms(uint32_t ms, uint64_t now_ns)
{
@@ -39,11 +39,6 @@ void yasl_clock_resize_stats(const struct yasl_frame_clock *c,
out->minflt = c->resize_minflt;
}
-double yasl_clock_refresh_ms(const struct yasl_frame_clock *c)
-{
- return c->refresh_ns / 1e6;
-}
-
uint64_t yasl_clock_next_present_ns(const struct yasl_frame_clock *c)
{
uint64_t next = c->last_present_ns + (uint64_t)c->refresh_ns;
diff --git a/src/common/timing.h b/src/common/timing.h
new file mode 100644
index 0000000..a3c3c88
--- /dev/null
+++ b/src/common/timing.h
@@ -0,0 +1,28 @@
+#ifndef YASL_COMMON_TIMING_H
+#define YASL_COMMON_TIMING_H
+
+#include "yasl/timing.h"
+#include <stdbool.h>
+
+#define YASL_REFRESH_DEFAULT_NS 16666666.0
+
+struct yasl_frame_clock {
+ uint64_t last_present_ns;
+ uint64_t resize_count;
+ uint64_t resize_sum_ns;
+ uint64_t resize_max_ns;
+ long resize_minflt;
+ uint64_t frame_time_ns;
+ double refresh_ns;
+ enum yasl_timing_source source;
+};
+
+uint64_t yasl_clock_extend_ms(uint32_t ms, uint64_t now_ns);
+void yasl_clock_presented(struct yasl_frame_clock *c, uint64_t now, bool sample);
+void yasl_clock_note_resize(struct yasl_frame_clock *c, uint64_t dt_ns);
+void yasl_clock_resize_stats(const struct yasl_frame_clock *c,
+ struct yasl_resize_stats *out);
+uint64_t yasl_clock_next_present_ns(const struct yasl_frame_clock *c);
+uint64_t yasl_clock_frame_timestamp_ns(struct yasl_frame_clock *c, uint64_t now);
+
+#endif /* YASL_COMMON_TIMING_H */
diff --git a/src/wayland/api.h b/src/wayland/api.h
index 8252041..87e896d 100644
--- a/src/wayland/api.h
+++ b/src/wayland/api.h
@@ -9,7 +9,6 @@ struct wayland_surface;
struct wayland_display *wayland_display_open(struct yasl_loop *loop, const struct yasl_display_spec *spec);
void wayland_display_close(struct wayland_display *d);
uint32_t wayland_display_caps(const struct wayland_display *d);
-void wayland_display_dispatch(struct wayland_display *d);
int wayland_display_dispatch_timeout(struct wayland_display *d, int timeout_ms);
void wayland_display_flush(struct wayland_display *d);
int wayland_display_modifiers(const struct wayland_display *d);
@@ -17,7 +16,8 @@ void wayland_display_copy(struct wayland_display *d, enum yasl_selection sel, co
void wayland_display_paste(struct wayland_display *d, enum yasl_selection sel, struct wayland_surface *target);
bool wayland_display_drag_start(struct wayland_display *d, struct wayland_surface *origin, const struct yasl_drag_image *image, const struct yasl_drag_listener *l, void *ud);
void wayland_display_drag_cancel(struct wayland_display *d);
-struct wayland_surface *wayland_surface_create_toplevel(struct wayland_display *d, const struct yasl_surface_spec *spec);
+struct wayland_surface *wayland_surface_create_toplevel(struct wayland_display *d,
+ const struct yasl_surface_spec *spec, void *graphics_device);
struct wayland_surface *wayland_surface_create_popup(struct wayland_surface *parent, const struct yasl_popup_spec *spec);
bool wayland_surface_popup_reposition(struct wayland_surface *popup, const struct yasl_popup_spec *spec);
void wayland_surface_destroy(struct wayland_surface *s);
@@ -27,8 +27,7 @@ void wayland_surface_set_title(struct wayland_surface *s, const char *title);
void wayland_surface_schedule_redraw(struct wayland_surface *s);
bool wayland_surface_present(struct wayland_surface *s, const struct yasl_framebuffer *fb, const struct yasl_damage_list *damage);
bool wayland_surface_input_poll(struct wayland_surface *s, struct yasl_input_event *ev);
-struct yasl_vector2i wayland_surface_size(const struct wayland_surface *s);
-uint32_t wayland_surface_scale_120(const struct wayland_surface *s);
+struct yasl_geometry wayland_surface_geometry(const struct wayland_surface *s);
uint32_t wayland_surface_state(const struct wayland_surface *s);
struct yasl_timing wayland_surface_timing(const struct wayland_surface *s);
uint64_t wayland_surface_frame_time_ns(struct wayland_surface *s, uint64_t now);
diff --git a/src/wayland/backend.c b/src/wayland/backend.c
index 31e412f..5c34def 100644
--- a/src/wayland/backend.c
+++ b/src/wayland/backend.c
@@ -16,11 +16,6 @@ static uint32_t impl_display_caps(const struct yasl_display *d)
return wayland_display_caps((const struct wayland_display *)d);
}
-static void impl_display_dispatch(struct yasl_display *d)
-{
- wayland_display_dispatch((struct wayland_display *)d);
-}
-
static int impl_display_dispatch_timeout(struct yasl_display *d, int timeout_ms)
{
return wayland_display_dispatch_timeout((struct wayland_display *)d, timeout_ms);
@@ -56,9 +51,10 @@ static void impl_display_drag_cancel(struct yasl_display *d)
wayland_display_drag_cancel((struct wayland_display *)d);
}
-static struct yasl_surface *impl_surface_create_toplevel(struct yasl_display *d, const struct yasl_surface_spec *spec)
+static struct yasl_surface *impl_surface_create_toplevel(struct yasl_display *d,
+ const struct yasl_surface_spec *spec, void *graphics_device)
{
- struct wayland_surface *result = wayland_surface_create_toplevel((struct wayland_display *)d, spec);
+ struct wayland_surface *result = wayland_surface_create_toplevel((struct wayland_display *)d, spec, graphics_device);
return result ? &result->base : NULL;
}
@@ -109,14 +105,9 @@ static bool impl_surface_input_poll(struct yasl_surface *s, struct yasl_input_ev
return wayland_surface_input_poll((struct wayland_surface *)s, ev);
}
-static struct yasl_vector2i impl_surface_size(const struct yasl_surface *s)
-{
- return wayland_surface_size((const struct wayland_surface *)s);
-}
-
-static uint32_t impl_surface_scale_120(const struct yasl_surface *s)
+static struct yasl_geometry impl_surface_geometry(const struct yasl_surface *s)
{
- return wayland_surface_scale_120((const struct wayland_surface *)s);
+ return wayland_surface_geometry((const struct wayland_surface *)s);
}
static uint32_t impl_surface_state(const struct yasl_surface *s)
@@ -199,7 +190,6 @@ const struct yasl_backend_ops yasl_wayland_backend = {
.display_open = impl_display_open,
.display_close = impl_display_close,
.display_caps = impl_display_caps,
- .display_dispatch = impl_display_dispatch,
.display_dispatch_timeout = impl_display_dispatch_timeout,
.display_flush = impl_display_flush,
.display_modifiers = impl_display_modifiers,
@@ -217,8 +207,7 @@ const struct yasl_backend_ops yasl_wayland_backend = {
.surface_schedule_redraw = impl_surface_schedule_redraw,
.surface_present = impl_surface_present,
.surface_input_poll = impl_surface_input_poll,
- .surface_size = impl_surface_size,
- .surface_scale_120 = impl_surface_scale_120,
+ .surface_geometry = impl_surface_geometry,
.surface_state = impl_surface_state,
.surface_timing = impl_surface_timing,
.surface_frame_time_ns = impl_surface_frame_time_ns,
diff --git a/src/wayland/buffer.c b/src/wayland/buffer.c
index 3f9cf84..a1486fa 100644
--- a/src/wayland/buffer.c
+++ b/src/wayland/buffer.c
@@ -446,8 +446,7 @@ bool wayland_surface_present(struct wayland_surface *w, const struct yasl_frameb
{
if (!w || w->closed || !w->frame_active || w->frame_presented || !fb ||
!damage || fb->pixels != w->frame.target.pixels ||
- fb->size.x != w->width || fb->size.y != w->height ||
- fb->buffer != w->cur || fb->native_drawable || fb->transaction)
+ fb->size.x != w->width || fb->size.y != w->height)
return false;
w->damage = *damage;
@@ -491,11 +490,6 @@ void surface_draw_frame(struct wayland_surface *w)
.generation = yasl_frame_begin(&w->base),
.pixels = w->buffers[idx].pixels,
.size = { .x = w->width, .y = w->height },
- .buffer = idx,
- .cleared = w->parent != NULL,
- .damage = &w->damage,
- .native_drawable = NULL,
- .transaction = false,
},
};
w->frame_active = true;
diff --git a/src/wayland/internal.h b/src/wayland/internal.h
index ccfd2dc..249fc8d 100644
--- a/src/wayland/internal.h
+++ b/src/wayland/internal.h
@@ -4,7 +4,7 @@
#define _GNU_SOURCE
#include "api.h"
-#include "yasl/timing.h"
+#include "common/timing.h"
#include "clipboard.h"
#include <wayland-client.h>
diff --git a/src/wayland/popup.c b/src/wayland/popup.c
index f0ef305..63bcba6 100644
--- a/src/wayland/popup.c
+++ b/src/wayland/popup.c
@@ -23,13 +23,18 @@ static const struct xdg_popup_listener popup_listener = {
.repositioned = popup_repositioned,
};
-static bool popup_spec_valid(const struct yasl_popup_spec *spec)
+static bool popup_spec_valid(const struct wayland_surface *parent,
+ const struct yasl_popup_spec *spec)
{
- return spec && spec->size.x > 0 && spec->size.y > 0 &&
+ if (!parent || !spec) return false;
+ int64_t x = (int64_t)spec->anchor.x - parent->content.x;
+ int64_t y = (int64_t)spec->anchor.y - parent->content.y;
+ return x >= INT_MIN && x <= INT_MAX && y >= INT_MIN && y <= INT_MAX &&
+ spec->size.x > 0 && spec->size.y > 0 &&
spec->content_offset.x >= 0 && spec->content_offset.y >= 0 &&
spec->content_size.x >= 0 && spec->content_size.y >= 0 &&
- spec->content_offset.x + spec->content_size.x <= spec->size.x &&
- spec->content_offset.y + spec->content_size.y <= spec->size.y;
+ (int64_t)spec->content_offset.x + spec->content_size.x <= spec->size.x &&
+ (int64_t)spec->content_offset.y + spec->content_size.y <= spec->size.y;
}
static bool popup_apply_spec(struct wayland_surface *w, const struct yasl_popup_spec *spec)
@@ -125,7 +130,7 @@ struct wayland_surface *wayland_surface_create_popup(struct wayland_surface *par
struct wayland_display *c = parent ? parent->conn : NULL;
struct seat *seat = c ? conn_active_seat(c) : NULL;
if (!c || c->lost || parent->closed || !parent->xdg_surface || !seat ||
- !seat->input_serial || !popup_spec_valid(spec))
+ !seat->input_serial || !popup_spec_valid(parent, spec))
return NULL;
struct wayland_surface *w = calloc(1, sizeof(*w));
@@ -170,7 +175,7 @@ err:
bool wayland_surface_popup_reposition(struct wayland_surface *w, const struct yasl_popup_spec *spec)
{
if (!w || !w->popup || w->closed || !w->conn || w->conn->lost ||
- !popup_spec_valid(spec) ||
+ !popup_spec_valid(w->parent, spec) ||
xdg_popup_get_version(w->popup) < XDG_POPUP_REPOSITION_SINCE_VERSION)
return false;
struct yasl_popup_spec old = {
diff --git a/src/wayland/surface.c b/src/wayland/surface.c
index 4f064e2..f291190 100644
--- a/src/wayland/surface.c
+++ b/src/wayland/surface.c
@@ -148,13 +148,13 @@ int wayland_display_dispatch_timeout(struct wayland_display *c, int timeout_ms)
return -1;
}
- bool had_pending = display_arm(c);
+ display_arm(c);
if (c->lost) {
display_leave(c);
return -1;
}
- if (had_pending && timeout_ms != 0 && display_has_immediate_work(c)) {
+ if (timeout_ms != 0 && display_has_immediate_work(c)) {
timeout_ms = 0;
}
@@ -177,14 +177,6 @@ int wayland_display_dispatch_timeout(struct wayland_display *c, int timeout_ms)
return n;
}
-void wayland_display_dispatch(struct wayland_display *c)
-{
- if (unlikely(!c || c->lost))
- return;
- int timeout = (display_has_immediate_work(c) || yasl_loop_is_running(c->loop)) ? 0 : -1;
- wayland_display_dispatch_timeout(c, timeout);
-}
-
void wayland_display_flush(struct wayland_display *c)
{
if (!c || c->lost)
@@ -342,8 +334,9 @@ static bool spec_valid(const struct yasl_surface_spec *spec)
}
struct wayland_surface *wayland_surface_create_toplevel(struct wayland_display *c,
- const struct yasl_surface_spec *spec)
+ const struct yasl_surface_spec *spec, void *graphics_device)
{
+ (void)graphics_device;
if (!c || c->lost || !spec_valid(spec))
return NULL;
int width = spec->size.x, height = spec->size.y;
@@ -457,14 +450,12 @@ void wayland_surface_schedule_redraw(struct wayland_surface *w)
yasl_loop_kick(w->conn->loop);
}
-struct yasl_vector2i wayland_surface_size(const struct wayland_surface *w)
+struct yasl_geometry wayland_surface_geometry(const struct wayland_surface *w)
{
- return w ? (struct yasl_vector2i){ .x = w->width, .y = w->height } : (struct yasl_vector2i){0};
-}
-
-uint32_t wayland_surface_scale_120(const struct wayland_surface *w)
-{
- return w ? w->scale_120 : YASL_SCALE_UNIT;
+ return (struct yasl_geometry){
+ .size = w ? (struct yasl_vector2i){w->width, w->height} : (struct yasl_vector2i){0},
+ .scale_120 = w ? w->scale_120 : YASL_SCALE_UNIT,
+ };
}
void wayland_surface_set_background_color(struct wayland_surface *w, uint32_t bg)
diff --git a/src/x11/window.c b/src/x11/window.c
index 2d9aff3..67276cf 100644
--- a/src/x11/window.c
+++ b/src/x11/window.c
@@ -1,6 +1,7 @@
#define _POSIX_C_SOURCE 200809L
#include "common/backend.h"
#include "common/input.h"
+#include "common/timing.h"
#include <X11/Xatom.h>
#include <X11/XKBlib.h>
@@ -218,8 +219,9 @@ static void net_message(struct x11_surface *s, Atom type,
}
static struct yasl_surface *create_toplevel(struct yasl_display *base,
- const struct yasl_surface_spec *spec)
+ const struct yasl_surface_spec *spec, void *graphics_device)
{
+ (void)graphics_device;
if (!spec) return NULL;
struct x11_display *d = (struct x11_display *)base;
struct x11_surface *s = calloc(1, sizeof(*s));
@@ -481,7 +483,6 @@ static bool present(struct yasl_surface *base, const struct yasl_framebuffer *fb
struct x11_surface *s = (struct x11_surface *)base;
if (s->destroyed || !s->drawing || s->presented || !fb || !damage ||
fb->pixels != s->pixels || fb->size.x != s->size.x || fb->size.y != s->size.y ||
- fb->buffer != s->frame.target.buffer || fb->native_drawable || fb->transaction ||
damage->n < -1 || damage->n > YASL_DAMAGE_MAX) return false;
struct x11_display *d = display_of(base);
XImage *im = s->image;
@@ -527,7 +528,7 @@ static void flush(struct yasl_display *base)
s->frame = (struct yasl_frame){
.surface = &s->base,
.target = {.generation = yasl_frame_begin(&s->base),
- .pixels = s->pixels, .size = s->size, .damage = &s->damage},
+ .pixels = s->pixels, .size = s->size},
};
s->cbs->draw(&s->base, &s->frame, s->ud);
s->drawing = false;
@@ -549,6 +550,12 @@ static int dispatch_timeout(struct yasl_display *base, int timeout_ms)
struct x11_display *d = (struct x11_display *)base;
if (unlikely(!d || !d->native)) return -1;
dispatch(base);
+ 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) {
if (!XPending(d->native) && timeout_ms != 0) {
yasl_loop_step(d->loop, timeout_ms);
@@ -696,8 +703,10 @@ static void schedule_redraw(struct yasl_surface *base)
}
static struct yasl_display *surface_display(const struct yasl_surface *s) { return s->display; }
-static struct yasl_vector2i size(const struct yasl_surface *s) { return ((const struct x11_surface *)s)->size; }
-static uint32_t scale(const struct yasl_surface *s) { (void)s; return YASL_SCALE_UNIT; }
+static struct yasl_geometry geometry(const struct yasl_surface *s)
+{
+ return (struct yasl_geometry){((const struct x11_surface *)s)->size, YASL_SCALE_UNIT};
+}
static uint32_t state(const struct yasl_surface *s) { return ((const struct x11_surface *)s)->state; }
static int mods(const struct yasl_display *d) { return (int)((const struct x11_display *)d)->mods; }
static void *native_display(struct yasl_display *d) { return ((struct x11_display *)d)->native; }
@@ -766,28 +775,11 @@ static bool interactive_move(struct yasl_surface *base)
return true;
}
-static void display_dispatch(struct yasl_display *base)
-{
- struct x11_display *d = (struct x11_display *)base;
- if (unlikely(!d || !d->native)) return;
- bool immediate = false;
- for (struct x11_surface *s = d->surfaces; s; s = s->next) {
- if (s->want_draw || s->input.count > 0 || s->input.reset_pending) {
- immediate = true;
- break;
- }
- }
- if (d->loop && yasl_loop_is_running(d->loop))
- immediate = true;
- dispatch_timeout(base, immediate ? 0 : -1);
-}
-
const struct yasl_backend_ops yasl_x11_backend = {
.type = YASL_BACKEND_X11,
.display_open = display_open,
.display_close = display_close,
.display_caps = caps,
- .display_dispatch = display_dispatch,
.display_dispatch_timeout = dispatch_timeout,
.display_flush = flush,
.display_modifiers = mods,
@@ -801,8 +793,7 @@ const struct yasl_backend_ops yasl_x11_backend = {
.surface_schedule_redraw = schedule_redraw,
.surface_present = present,
.surface_input_poll = input_poll,
- .surface_size = size,
- .surface_scale_120 = scale,
+ .surface_geometry = geometry,
.surface_state = state,
.surface_timing = timing,
.surface_frame_time_ns = frame_time,
diff --git a/tests/cocoa/test_native.c b/tests/cocoa/test_native.c
index 41df79a..0c101dd 100644
--- a/tests/cocoa/test_native.c
+++ b/tests/cocoa/test_native.c
@@ -1,4 +1,6 @@
#define _POSIX_C_SOURCE 200809L
+#include "yasl/native.h"
+#include "yasl/loop.h"
#include "yasl/yasl.h"
#include <assert.h>
@@ -16,20 +18,24 @@ static void draw(struct yasl_surface *surface, struct yasl_frame *frame, void *u
assert(surface == state->surface && frame->surface == surface);
assert(yasl_surface_display(surface) == state->display);
assert(frame->target.pixels);
- struct yasl_vector2i size = yasl_surface_size(surface);
+ struct yasl_native_frame native;
+ assert(yasl_surface_get_native_frame(surface, &frame->target, &native));
+ assert(native.drawable);
+ struct yasl_vector2i size = yasl_surface_geometry(surface).size;
assert(size.x == frame->target.size.x && size.y == frame->target.size.y);
for (int i = 0; i < size.x * size.y; i++) frame->target.pixels[i] = 0xff4080c0;
struct yasl_damage_list damage = {.n = YASL_DAMAGE_MAX + 1};
assert(!yasl_surface_present(surface, &frame->target, &damage));
damage.n = -1;
struct yasl_framebuffer invalid = frame->target;
- invalid.buffer++;
+ invalid.size.x++;
assert(!yasl_surface_present(surface, &invalid, &damage));
invalid = frame->target;
invalid.generation--;
assert(!yasl_surface_present(surface, &invalid, &damage));
assert(!yasl_surface_present(surface, &frame->target, NULL));
assert(yasl_surface_present(surface, &frame->target, &damage));
+ assert(!yasl_surface_get_native_frame(surface, &frame->target, &native));
assert(!yasl_surface_present(surface, &frame->target, &damage));
state->draws++;
yasl_surface_destroy(surface);
@@ -51,7 +57,7 @@ static void test_window(enum yasl_backend request, bool null_spec)
yasl_surface_schedule_redraw(state.surface);
uint64_t deadline = yasl_loop_now_ns() + 3000000000ull;
while (!state.draws && yasl_loop_now_ns() < deadline) {
- yasl_display_dispatch(state.display);
+ yasl_display_dispatch(state.display, 0);
yasl_display_flush(state.display);
nanosleep(&(struct timespec){.tv_nsec = 1000000}, NULL);
}
diff --git a/tests/cocoa/test_ownership.m b/tests/cocoa/test_ownership.m
index aa75b1c..168a47b 100644
--- a/tests/cocoa/test_ownership.m
+++ b/tests/cocoa/test_ownership.m
@@ -1,3 +1,4 @@
+#include "yasl/native.h"
#import <AppKit/AppKit.h>
#include "yasl/yasl.h"
#include <assert.h>
@@ -11,6 +12,9 @@ int main(void)
struct yasl_surface_spec spec = {.title = "Ownership test", .size = {160, 100}};
struct yasl_surface *s = yasl_surface_create_toplevel(d, &spec);
assert(s);
+ assert(yasl_display_get_native_handle(d) == (void *)NSApp);
+ NSWindow *window = (NSWindow *)yasl_surface_get_native_handle(s);
+ assert([window isKindOfClass:[NSWindow class]] && window.contentView);
struct yasl_input_event event;
while (yasl_surface_input_poll(s, &event)) {}
@autoreleasepool {
diff --git a/tests/common/test_backends.c b/tests/common/test_backends.c
index 3372290..4f49765 100644
--- a/tests/common/test_backends.c
+++ b/tests/common/test_backends.c
@@ -1,4 +1,6 @@
#define _POSIX_C_SOURCE 200809L
+#include "yasl/native.h"
+#include "yasl/loop.h"
#include "yasl/yasl.h"
#include <X11/Xatom.h>
@@ -35,7 +37,7 @@ static int tick(int fd, short events, void *ud)
struct pump *p = ud;
for (int i = 0; i < 2; i++) {
if (p->displays[i]) {
- yasl_display_dispatch(p->displays[i]);
+ yasl_display_dispatch(p->displays[i], 0);
yasl_display_flush(p->displays[i]);
}
}
@@ -63,9 +65,11 @@ static void draw(struct yasl_surface *s, struct yasl_frame *frame, void *ud)
assert(s == w->surface);
assert(frame->surface == s);
assert(yasl_surface_display(s) == w->display);
- struct yasl_vector2i size = yasl_surface_size(s);
+ struct yasl_vector2i size = yasl_surface_geometry(s).size;
assert(size.x == frame->target.size.x && size.y == frame->target.size.y);
assert(frame->target.pixels);
+ struct yasl_native_frame native = {0};
+ assert(!yasl_surface_get_native_frame(s, &frame->target, &native));
for (int i = 0; i < size.x * size.y; i++) frame->target.pixels[i] = 0xff4080c0;
struct yasl_damage_list damage = {.n = YASL_DAMAGE_MAX + 1};
assert(!yasl_surface_present(s, &frame->target, &damage));
@@ -76,13 +80,13 @@ static void draw(struct yasl_surface *s, struct yasl_frame *frame, void *ud)
assert(!yasl_surface_present(s, &frame->target, &damage));
damage.n = -1;
struct yasl_framebuffer invalid = frame->target;
- invalid.buffer++;
+ invalid.size.x++;
assert(!yasl_surface_present(s, &invalid, &damage));
invalid = frame->target;
invalid.generation--;
assert(!yasl_surface_present(s, &invalid, &damage));
invalid = frame->target;
- invalid.native_drawable = (void *)(uintptr_t)1;
+ invalid.pixels = (uint32_t *)(uintptr_t)1;
assert(!yasl_surface_present(s, &invalid, &damage));
assert(!yasl_surface_present(s, &frame->target, NULL));
assert(yasl_surface_present(s, &frame->target, &damage));
@@ -95,9 +99,9 @@ static void configure(struct yasl_surface *s, struct yasl_geometry geometry, voi
{
struct window *w = ud;
assert(w->surface == s);
- struct yasl_vector2i size = yasl_surface_size(s);
+ struct yasl_vector2i size = yasl_surface_geometry(s).size;
assert(geometry.size.x == size.x && geometry.size.y == size.y);
- assert(geometry.scale_120 == yasl_surface_scale_120(s));
+ assert(geometry.scale_120 == yasl_surface_geometry(s).scale_120);
w->configures++;
if (w->destroy_on_configure) { yasl_surface_destroy(s); w->surface = NULL; }
}
@@ -129,6 +133,8 @@ static struct window create(struct yasl_display *d)
struct yasl_surface_spec spec = {.title = "Backend test", .size = {160, 100}};
struct yasl_surface *s = yasl_surface_create_toplevel(d, &spec);
assert(s);
+ assert(yasl_display_get_native_handle(d));
+ assert(yasl_surface_get_native_handle(s));
assert(yasl_surface_display(s) == d);
assert(yasl_surface_timing(s).source == YASL_TIMING_UNKNOWN);
return (struct window){.surface = s, .display = d};
@@ -152,7 +158,7 @@ static void x11_behavior(struct yasl_loop *loop, struct yasl_display *d, struct
XFlush(native);
pump(loop, d, NULL);
assert(w->configures > 0);
- struct yasl_vector2i size = yasl_surface_size(w->surface);
+ struct yasl_vector2i size = yasl_surface_geometry(w->surface).size;
assert(size.x == 200 && size.y == 120);
XEvent key = {0};
key.xkey = (XKeyEvent){.type = KeyPress, .display = native, .window = window,
diff --git a/tests/common/test_contracts.c b/tests/common/test_contracts.c
index 65eebcd..4e22de1 100644
--- a/tests/common/test_contracts.c
+++ b/tests/common/test_contracts.c
@@ -1,5 +1,6 @@
#include "common/backend.h"
#include "common/input.h"
+#include "common/timing.h"
#include <assert.h>
#include <errno.h>
@@ -7,6 +8,23 @@
#include <stdlib.h>
#include <string.h>
+static void test_timing_clock(void)
+{
+ struct yasl_frame_clock clock = {.refresh_ns = YASL_REFRESH_DEFAULT_NS};
+ yasl_clock_presented(&clock, 1000000000, true);
+ assert(clock.last_present_ns == 1000000000);
+ uint64_t next = yasl_clock_next_present_ns(&clock);
+ assert(next > 1000000000);
+ assert(yasl_clock_extend_ms(1000, 1000000000) == 1000000000);
+ assert(yasl_clock_frame_timestamp_ns(&clock, next) == next);
+
+ yasl_clock_note_resize(&clock, 500000);
+ struct yasl_resize_stats stats;
+ yasl_clock_resize_stats(&clock, &stats);
+ assert(stats.count == 1);
+ assert(stats.max_us == 500.0);
+}
+
static void test_queue(void)
{
struct input_queue q = {0};
@@ -96,8 +114,56 @@ static void copy(struct yasl_display *d, enum yasl_selection sel, const char *te
{ (void)d; (void)sel; (void)text; copies++; }
static void paste(struct yasl_display *d, enum yasl_selection sel, struct yasl_surface *s)
{ (void)d; (void)sel; (void)s; pastes++; }
-static struct yasl_surface *create(struct yasl_display *d, const struct yasl_surface_spec *spec)
-{ (void)d; (void)spec; creates++; return NULL; }
+static struct yasl_surface *create(struct yasl_display *d, const struct yasl_surface_spec *spec, void *graphics_device)
+{ (void)d; (void)spec; (void)graphics_device; creates++; return NULL; }
+
+static void *native_device;
+static int native_queries;
+static struct yasl_surface native_surface;
+static uint32_t native_caps(const struct yasl_display *d)
+{ (void)d; return YASL_CAP_NATIVE_GRAPHICS; }
+static struct yasl_surface *native_create(struct yasl_display *d,
+ const struct yasl_surface_spec *spec, void *device)
+{
+ (void)spec;
+ native_device = device;
+ native_surface = (struct yasl_surface){.display = d, .frame_generation = 1};
+ return &native_surface;
+}
+static bool native_frame(struct yasl_surface *s, const struct yasl_framebuffer *fb,
+ struct yasl_native_frame *out)
+{
+ (void)s; (void)fb;
+ native_queries++;
+ *out = (struct yasl_native_frame){.drawable = native_device};
+ return true;
+}
+
+static void test_native(void)
+{
+ const struct yasl_backend_ops ops = {.display_caps = native_caps,
+ .surface_create_toplevel = native_create, .surface_get_native_frame = native_frame};
+ struct yasl_display d = {.ops = &ops};
+ struct yasl_surface_spec spec = {.size = {100, 80}};
+ assert(yasl_surface_create_toplevel(&d, &spec) == &native_surface);
+ assert(!native_device);
+ assert(yasl_surface_create_native(&d, &spec, &d) == &native_surface);
+ assert(native_device == &d);
+
+ struct yasl_framebuffer fb = {.generation = 1, .size = {100, 80}};
+ struct yasl_native_frame out = {0};
+ assert(!yasl_surface_get_native_frame(NULL, &fb, &out));
+ assert(!yasl_surface_get_native_frame(&native_surface, NULL, &out));
+ assert(!yasl_surface_get_native_frame(&native_surface, &fb, NULL));
+ fb.generation = 0;
+ assert(!yasl_surface_get_native_frame(&native_surface, &fb, &out));
+ fb.generation = 2;
+ assert(!yasl_surface_get_native_frame(&native_surface, &fb, &out));
+ assert(!native_queries && !out.drawable);
+ fb.generation = 1;
+ assert(yasl_surface_get_native_frame(&native_surface, &fb, &out));
+ assert(native_queries == 1 && out.drawable == &d);
+}
static void test_validation(void)
{
@@ -143,9 +209,9 @@ static void test_validation(void)
yasl_display_copy(&d, YASL_CLIPBOARD, "yes");
yasl_display_paste(&d, YASL_CLIPBOARD, &s);
assert(copies == 1 && pastes == 1);
- struct yasl_surface_spec spec = {.size = {100, 80}, .graphics_device = &d};
- assert(!yasl_surface_create_toplevel(&d, &spec) && errno == ENOTSUP);
- spec.graphics_device = NULL;
+ struct yasl_surface_spec spec = {.size = {100, 80}};
+ assert(!yasl_surface_create_native(&d, &spec, &d) && errno == ENOTSUP);
+ assert(!yasl_surface_create_native(&d, &spec, NULL) && errno == EINVAL);
spec.size.x = 0;
assert(!yasl_surface_create_toplevel(&d, &spec) && errno == EINVAL);
assert(!creates);
@@ -168,8 +234,10 @@ static void test_scale(void)
int main(void)
{
+ test_timing_clock();
test_queue();
test_validation();
+ test_native();
test_scale();
return 0;
}
diff --git a/tests/common/test_timers.c b/tests/common/test_timers.c
index 9b6499d..d02bfe9 100644
--- a/tests/common/test_timers.c
+++ b/tests/common/test_timers.c
@@ -1,4 +1,5 @@
#define _POSIX_C_SOURCE 200809L
+#include "common/loop.h"
#include "yasl/yasl.h"
#include <assert.h>
@@ -95,18 +96,18 @@ static uint64_t killer_timer_cb(void *ud, yasl_timer_id id, uint64_t interval_ns
return 0;
}
-/* 6. Millisecond variant timer. */
-static uint32_t ms_timer_cb(void *ud, yasl_timer_id id, uint32_t interval_ms)
+/* 6. Millisecond convenience conversion uses the same timer callback. */
+static uint64_t ms_timer_cb(void *ud, yasl_timer_id id, uint64_t interval_ns)
{
struct test_ctx *ctx = ud;
assert(id == ctx->id);
- assert(interval_ms == 5);
+ assert(interval_ns == YASL_MS_TO_NS(5));
ctx->count++;
if (ctx->count >= 3) {
ctx->done = true;
return 0;
}
- return interval_ms;
+ return interval_ns;
}
static void test_loop_timers(void)
@@ -185,9 +186,9 @@ static void test_loop_timers(void)
assert(ctx6.count == 1);
yasl_loop_step(l, 20);
- /* Test 7: Millisecond API */
+ /* Test 7: Millisecond conversion */
struct test_ctx ctx7 = { .loop = l };
- ctx7.id = yasl_loop_add_timer_ms(l, 5, ms_timer_cb, &ctx7);
+ ctx7.id = yasl_loop_add_timer(l, YASL_MS_TO_NS(5), ms_timer_cb, &ctx7);
assert(ctx7.id > 0);
while (!ctx7.done) {
yasl_loop_step(l, 50);
@@ -239,14 +240,14 @@ static void test_surface_timers(void)
};
struct yasl_surface *s = yasl_surface_create_toplevel(d, &sspec);
assert(s);
- assert(yasl_surface_loop(s) == loop);
+ assert(yasl_display_loop(yasl_surface_display(s)) == loop);
- /* Display timers */
+ /* Application timers use the display's loop. */
struct test_ctx dctx = {0};
- dctx.id = yasl_display_add_timer(d, 2000000ull, oneshot_cb, &dctx);
+ dctx.id = yasl_loop_add_timer(yasl_display_loop(d), 2000000ull, oneshot_cb, &dctx);
assert(dctx.id > 0);
while (dctx.count == 0) {
- yasl_display_dispatch_timeout(d, 50);
+ yasl_display_dispatch(d, 50);
}
assert(dctx.count == 1);
@@ -255,12 +256,12 @@ static void test_surface_timers(void)
sctx.id = yasl_surface_add_timer(s, 2000000ull, surface_timer_repeat_cb, &sctx);
assert(sctx.id > 0);
while (sctx.count < 3) {
- yasl_display_dispatch_timeout(d, 50);
+ yasl_display_dispatch(d, 50);
}
assert(sctx.count >= 3);
assert(yasl_surface_del_timer(s, sctx.id));
int count_before = sctx.count;
- yasl_display_dispatch_timeout(d, 10);
+ yasl_display_dispatch(d, 10);
assert(sctx.count == count_before);
/* Surface timer auto-cleanup on surface destruction */
@@ -269,7 +270,7 @@ static void test_surface_timers(void)
assert(sctx2.id > 0);
yasl_surface_destroy(s);
/* Step display/loop; timer must not fire on dead surface */
- yasl_display_dispatch_timeout(d, 10);
+ yasl_display_dispatch(d, 10);
assert(sctx2.count == 0);
/* Surface suicide from timer callback */
@@ -279,7 +280,7 @@ static void test_surface_timers(void)
sctx3.id = yasl_surface_add_timer(s, 2000000ull, surface_suicide_cb, &sctx3);
assert(sctx3.id > 0);
while (sctx3.count == 0) {
- yasl_display_dispatch_timeout(d, 50);
+ yasl_display_dispatch(d, 50);
}
assert(sctx3.count == 1);
assert(sctx3.surface == NULL);
diff --git a/tests/helpers/check_exports.py b/tests/helpers/check_exports.py
new file mode 100644
index 0000000..0d10014
--- /dev/null
+++ b/tests/helpers/check_exports.py
@@ -0,0 +1,42 @@
+"""Require the shared library to export exactly the public header functions."""
+
+import argparse
+from pathlib import Path
+import re
+import subprocess
+
+
+def main():
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('--allow-export', action='append', default=[])
+ parser.add_argument('nm')
+ parser.add_argument('library')
+ parser.add_argument('headers', nargs='+')
+ args = parser.parse_args()
+ declarations = re.compile(
+ r"^[^\n;{}]*\b(yasl_\w+)\s*\([^;{}]*\);", re.MULTILINE
+ )
+ expected = set()
+ for header in args.headers:
+ source = re.sub(r"/\*.*?\*/|//[^\n]*", "", Path(header).read_text(), flags=re.DOTALL)
+ expected.update(declarations.findall(source))
+ if not expected:
+ raise SystemExit("No public function declarations found")
+
+ output = subprocess.check_output(
+ [args.nm, "-D", "--defined-only", "--format=posix", args.library], text=True
+ )
+ exported = {line.split()[0] for line in output.splitlines() if line.strip()}
+ missing = expected - exported
+ leaked = exported - expected - set(args.allow_export)
+ if missing or leaked:
+ if missing:
+ print("Missing public exports:", ", ".join(sorted(missing)))
+ if leaked:
+ print("Unexpected exports:", ", ".join(sorted(leaked)))
+ raise SystemExit(1)
+ print(f"Verified {len(expected)} public exports")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tests/helpers/run_backends.py b/tests/helpers/run_backends.py
index 9ad9b0d..8c5428a 100644
--- a/tests/helpers/run_backends.py
+++ b/tests/helpers/run_backends.py
@@ -21,7 +21,7 @@ def wait_for(process, ready):
def main():
- weston, xwayland, test = sys.argv[1:]
+ weston, xwayland, test, timers = sys.argv[1:]
servers = []
with tempfile.TemporaryDirectory(prefix="yasl-backends-") as directory:
root = Path(directory)
@@ -73,6 +73,13 @@ def main():
run("failure", 2, DISPLAY=":59999")
run("failure", 0, WAYLAND_DISPLAY="missing-socket", DISPLAY=":59999")
run("mixed")
+ print("Test: Wayland surface timers", flush=True)
+ subprocess.run([timers], env=env, check=True, timeout=8)
+ x_env = env.copy()
+ x_env.pop("WAYLAND_DISPLAY", None)
+ x_env["XDG_SESSION_TYPE"] = "x11"
+ print("Test: X11 surface timers", flush=True)
+ subprocess.run([timers], env=x_env, check=True, timeout=8)
except BaseException:
for log in (wl_log, x_log):
log.flush()
diff --git a/tests/meson.build b/tests/meson.build
index f2d5e79..0f28c39 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -12,6 +12,47 @@ add_test_setup('msan')
test('basic', test_exe)
+shared_dep = declare_dependency(
+ link_with: yasl_lib.get_shared_lib(),
+ include_directories: include_directories('../include'),
+)
+
+shared_api_test = executable(
+ 'yasl-shared-api-test',
+ 'test_lifecycle.c',
+ dependencies: shared_dep,
+)
+test('shared-api', shared_api_test)
+
+if add_languages('cpp', native: false, required: false)
+ cpp = meson.get_compiler('cpp')
+ cpp_api_test = executable(
+ 'yasl-cpp-api-test',
+ 'test_api.cpp',
+ dependencies: shared_dep,
+ cpp_args: cpp.get_supported_arguments('-pedantic-errors'),
+ override_options: ['cpp_std=c++11'],
+ )
+ test('cpp-api', cpp_api_test)
+endif
+
+python = find_program('python3')
+if system == 'linux'
+ nm = find_program('nm')
+ export_args = []
+ if get_option('b_sanitize').contains('address')
+ # Clang's ASan instrumentation exports these linker-generated markers.
+ export_args += ['--allow-export=__start_asan_globals',
+ '--allow-export=__stop_asan_globals']
+ endif
+ test('public-exports', python,
+ args: [files('helpers/check_exports.py')] + export_args +
+ [nm, yasl_lib.get_shared_lib(),
+ files('../include/yasl/yasl.h', '../include/yasl/timing.h',
+ '../include/yasl/loop.h', '../include/yasl/native.h')],
+ )
+endif
+
contracts_test = executable(
'yasl-contracts-test',
'common/test_contracts.c',
@@ -54,11 +95,11 @@ endif
if get_option('integration_tests')
if has_cocoa
native_test = executable('yasl-native-test', 'cocoa/test_native.c',
- dependencies: yasl_dep,
+ dependencies: shared_dep,
)
test('native', native_test, timeout: 20)
ownership_test = executable('yasl-ownership-test', 'cocoa/test_ownership.m',
- dependencies: yasl_dep,
+ dependencies: [shared_dep, deps],
)
test('native-ownership', ownership_test, timeout: 20)
else
@@ -68,13 +109,13 @@ if get_option('integration_tests')
backend_test = executable(
'yasl-backend-test',
'common/test_backends.c',
- dependencies: yasl_dep,
+ dependencies: [shared_dep, deps],
)
- python = find_program('python3')
weston = find_program('weston')
xwayland = find_program('Xwayland')
test('backends', python,
- args: [files('helpers/run_backends.py'), weston, xwayland, backend_test],
+ args: [files('helpers/run_backends.py'), weston, xwayland,
+ backend_test, timer_test],
timeout: 60,
)
endif
diff --git a/tests/test_api.cpp b/tests/test_api.cpp
new file mode 100644
index 0000000..70c4ff7
--- /dev/null
+++ b/tests/test_api.cpp
@@ -0,0 +1,27 @@
+#include "yasl/native.h"
+#include "yasl/loop.h"
+#include <yasl/timing.h>
+#include <yasl/yasl.h>
+#include <cassert>
+
+int main()
+{
+ yasl_timing timing = yasl_surface_timing(nullptr);
+ assert(timing.source == YASL_TIMING_UNKNOWN);
+ assert(yasl_scale_to_device(10.0, 240) == 20);
+ yasl_geometry geometry = yasl_surface_geometry(nullptr);
+ assert(geometry.scale_120 == YASL_SCALE_UNIT);
+ assert(!yasl_display_get_native_handle(nullptr));
+ assert(!yasl_surface_get_native_handle(nullptr));
+ yasl_native_frame native{};
+ assert(!yasl_surface_get_native_frame(nullptr, nullptr, &native));
+
+ yasl_loop *loop = yasl_loop_create();
+ assert(loop);
+ assert(yasl_loop_step(loop, 0) >= 0);
+ yasl_loop_destroy(loop);
+
+ yasl_input_event event{};
+ assert(!yasl_surface_input_poll(nullptr, &event));
+ return 0;
+}
diff --git a/tests/test_lifecycle.c b/tests/test_lifecycle.c
index 681e5fc..d04662f 100644
--- a/tests/test_lifecycle.c
+++ b/tests/test_lifecycle.c
@@ -1,3 +1,5 @@
+#include "yasl/loop.h"
+#include "yasl/timing.h"
#include "yasl/yasl.h"
#include <assert.h>
#include <poll.h>
@@ -11,21 +13,6 @@ static void test_scaling(void)
assert(yasl_scale_to_logical(20, 240) == 10);
}
-static void test_timing_clock(void)
-{
- struct yasl_frame_clock clock = { .refresh_ns = 16666666.0 };
- yasl_clock_presented(&clock, 1000000000, true);
- assert(clock.last_present_ns == 1000000000);
- uint64_t next = yasl_clock_next_present_ns(&clock);
- assert(next > 1000000000);
-
- yasl_clock_note_resize(&clock, 500000);
- struct yasl_resize_stats stats;
- yasl_clock_resize_stats(&clock, &stats);
- assert(stats.count == 1);
- assert(stats.max_us == 500.0);
-}
-
static int dummy_cb(int fd, short revents, void *ud)
{
(void)fd; (void)revents; (void)ud;
@@ -68,12 +55,12 @@ static void test_display_lifecycle_standalone(void)
assert(s != NULL);
assert(yasl_surface_display(s) == d);
- struct yasl_vector2i sz = yasl_surface_size(s);
+ struct yasl_vector2i sz = yasl_surface_geometry(s).size;
assert(sz.x > 0 && sz.y > 0);
yasl_surface_schedule_redraw(s);
yasl_display_flush(d);
- yasl_display_dispatch_timeout(d, 0);
+ yasl_display_dispatch(d, 0);
yasl_surface_destroy(s);
yasl_display_close(d);
@@ -82,7 +69,6 @@ static void test_display_lifecycle_standalone(void)
int main(void)
{
test_scaling();
- test_timing_clock();
test_loop();
test_display_lifecycle_standalone();
printf("All YASL basic tests passed.\n");
diff --git a/tests/wayland/test_input.c b/tests/wayland/test_input.c
index f421e6c..7b91a9d 100644
--- a/tests/wayland/test_input.c
+++ b/tests/wayland/test_input.c
@@ -1,8 +1,33 @@
#include "wayland/internal.h"
#include <assert.h>
+static void test_popup_bounds(void)
+{
+ struct seat seat = {.input_serial = 1};
+ struct wayland_display d = {.base.ops = &yasl_wayland_backend, .seats = &seat};
+ struct wayland_surface w = {.base.display = &d.base, .conn = &d};
+ w.parent = &w;
+ w.content.x = w.content.y = 1;
+ /* Invalid bounds must be rejected before accessing any protocol object. */
+ w.xdg_surface = (struct xdg_surface *)&w;
+ w.popup = (struct xdg_popup *)&w;
+ struct yasl_popup_spec specs[] = {
+ {.size = {100, 100}, .content_offset = {INT_MAX, 0}, .content_size = {1, 0}},
+ {.size = {100, 100}, .content_offset = {0, INT_MAX}, .content_size = {0, 1}},
+ {.size = {100, 100}, .content_offset = {1, 0}, .content_size = {INT_MAX, 0}},
+ {.size = {100, 100}, .content_offset = {0, 1}, .content_size = {0, INT_MAX}},
+ {.size = {100, 100}, .anchor = {INT_MIN, 0, 1, 1}},
+ {.size = {100, 100}, .anchor = {0, INT_MIN, 1, 1}},
+ };
+ for (size_t i = 0; i < sizeof(specs) / sizeof(specs[0]); i++) {
+ assert(!yasl_surface_create_popup(&w.base, &specs[i]));
+ assert(!yasl_surface_popup_reposition(&w.base, &specs[i]));
+ }
+}
+
int main(void)
{
+ test_popup_bounds();
struct wayland_surface w = {0};
struct seat seat = {.ime_focus = &w};
wayland_surface_set_callbacks(&w, NULL, NULL);