From 3b8af02be94a510e385f44d58c35c86bb89424a6 Mon Sep 17 00:00:00 2001 From: Berke Güzel Date: Mon, 12 Oct 2026 08:41:51 +0300 Subject: 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. --- README.md | 41 ++++++++++++--- examples/simple_window.c | 2 +- include/yasl/loop.h | 46 +++++++++++++++++ include/yasl/native.h | 44 +++++++++++++++++ include/yasl/timing.h | 23 --------- include/yasl/yasl.h | 110 +++++++++++------------------------------ meson.build | 5 +- src/cocoa/api.h | 7 ++- src/cocoa/window.m | 99 +++++++++++++++++-------------------- src/common/backend.c | 103 ++++++-------------------------------- src/common/backend.h | 13 +++-- src/common/loop.c | 59 ++-------------------- src/common/loop.h | 8 +++ src/common/meson.build | 2 +- src/common/native.c | 35 +++++++++++++ src/common/timing.c | 7 +-- src/common/timing.h | 28 +++++++++++ src/wayland/api.h | 7 ++- src/wayland/backend.c | 23 +++------ src/wayland/buffer.c | 8 +-- src/wayland/internal.h | 2 +- src/wayland/popup.c | 17 ++++--- src/wayland/surface.c | 27 ++++------ src/x11/window.c | 39 ++++++--------- tests/cocoa/test_native.c | 12 +++-- tests/cocoa/test_ownership.m | 4 ++ tests/common/test_backends.c | 20 +++++--- tests/common/test_contracts.c | 78 +++++++++++++++++++++++++++-- tests/common/test_timers.c | 29 +++++------ tests/helpers/check_exports.py | 42 ++++++++++++++++ tests/helpers/run_backends.py | 9 +++- tests/meson.build | 51 +++++++++++++++++-- tests/test_api.cpp | 27 ++++++++++ tests/test_lifecycle.c | 22 ++------- tests/wayland/test_input.c | 25 ++++++++++ 35 files changed, 620 insertions(+), 454 deletions(-) create mode 100644 include/yasl/loop.h create mode 100644 include/yasl/native.h create mode 100644 src/common/loop.h create mode 100644 src/common/native.c create mode 100644 src/common/timing.h create mode 100644 tests/helpers/check_exports.py create mode 100644 tests/test_api.cpp 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 `` 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 `` 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 `` for platform handles and application-managed native +rendering. These extensions are separate from the portable CPU drawing API. +`` 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 `` 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 ``; 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 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 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 #include #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 #include "cocoa/api.h" -#include "yasl/timing.h" +#include "common/timing.h" +#include "common/loop.h" #include "cocoa/keycodes.h" #include @@ -70,6 +71,8 @@ struct cocoa_surface { uint32_t *pixels; size_t pixel_capacity; + id 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 command = [w->software_queue commandBuffer]; id blit = [command blitCommandEncoder]; if (!blit) { [buffer release]; return false; } - id drawable = (id)fb->native_drawable; + id 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 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)spec->graphics_device; + if (graphics_device) { + w->layer.device = (id)graphics_device; } else { id 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 #include @@ -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 + +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 + +#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 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 #include @@ -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 @@ -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 #include "yasl/yasl.h" #include @@ -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 @@ -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 #include @@ -7,6 +8,23 @@ #include #include +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 @@ -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 +#include +#include + +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 #include @@ -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 +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); -- cgit v1.2.3