# YASL (Yet Another Surface Library) YASL is a minimal, modern, and high-performance desktop window surface library written in C. It provides window creation, frame pacing, high-DPI scaling, and input routing without bundling audio, physics, or game engine components. ## Features - **Lean Core**: No audio, no font rendering, and no widget toolkit overhead. - **Wayland Protocol Coverage**: - `xdg-shell` toplevels and hierarchical popups (`xdg_popup` + `xdg_positioner`). - Fractional scaling via `wp_fractional_scale_v1` and `wp_viewporter`. - Presentation feedback and refresh rate timing via `wp_presentation` and `wp_fifo_v1`. - Cursor shapes via `wp_cursor_shape_device_v1`. - Graphics tablets and stylus input via `zwp_tablet_v2` (pressure, tilt, eraser). - Text Input Method (IME) composition via `zwp_text_input_v3`. - Primary selection and clipboard transfers via `wl_data_device` and `zwp_primary_selection_v1`. - Interactive client-side window move and resize triggers (`xdg_toplevel_move`, `xdg_toplevel_resize`). - **macOS Cocoa & Metal**: - Native `NSWindow` and `CAMetalLayer` presentation. - Vsync pacing via `CADisplayLink`. - **Zero Framework Leaks**: - Self-contained UTF-8 validation. - 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. Leave `yasl_display_spec.backend` at zero to use this mode. `YASL_BACKEND_AUTO` has the same effect. You can also pass NULL as the display spec. On Linux, YASL tries Wayland first in a Wayland session. It uses `WAYLAND_DISPLAY`, `WAYLAND_SOCKET`, and `XDG_SESSION_TYPE` to detect that session. In other sessions, it tries X11 first. If the first backend cannot open, it tries the other built backend. On macOS, it uses Cocoa. Set `backend` to request one backend: ```c struct yasl_display_spec spec = { .app_id = "org.example.app", .backend = YASL_BACKEND_X11, }; struct yasl_display *display = yasl_display_open(loop, &spec); ``` Use `YASL_BACKEND_WAYLAND` to request Wayland. Use `YASL_BACKEND_COCOA` to request Cocoa. An explicit request never falls back to another backend. Open returns NULL if the requested backend cannot open. It sets `errno` to `ENOSYS` for a backend that was not built. It sets `errno` to `EINVAL` for an invalid backend value. 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. It supports the primary selection and window placement. It does not yet support YASL popups, drag operations, or IME preedit text. 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 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 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 - Calls and callbacks use the thread that opened the display (the main thread on Cocoa). Supplied event loops are borrowed. Destroy popups before parents, surfaces before displays, and displays before supplied loops. Surface destruction inside surface callbacks is supported; display or loop destruction during callbacks/dispatch is not. - Callback tables, drag listeners, input strings, titles, clipboard text, and drag pixels are copied. Userdata is borrowed. Cocoa retains a supplied Metal device. Native handles are borrowed, not transferred to the caller. - Text, paste, and file-drop event payloads remain valid until the next **successful** input poll on that surface or surface destruction. Dispatch, flush, selection replacement, and failed polls do not invalidate them. Copy a payload if it must live longer. - Input queues grow rather than silently dropping events at a fixed limit. If allocation fails, pending input is discarded and `YASL_INPUT_RESET` is delivered before later events. Apps must clear held keys/buttons and input composition state on this event. - Clipboard requests retain their original target. Destroying that target cancels delivery, rather than redirecting the paste to a focused window. Unsupported primary selections are no-ops, never aliases for the clipboard. - CPU pixels are tightly packed premultiplied `0xAARRGGBB`. Creation sizes are logical pixels; framebuffer sizes, pointer coordinates, caret and damage are device pixels. Every draw target needs complete valid pixel contents; do not assume a recycled framebuffer contains the previous frame. - 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. - `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 next-presentation times are estimates, not universal vsync guarantees. These are pre-release API changes. Applications must handle the new reset input kind and should inspect timing provenance instead of assuming all timestamps confirm scanout. See `include/yasl/yasl.h` for the complete contracts. ## Building Use a C23 compiler and Meson 1.6 or later. Build with Meson and Ninja: ```sh meson setup build ninja -C build ninja -C build test ``` Linux builds include each backend whose dependencies are installed. Wayland needs `wayland-client`, `wayland-cursor`, and `xkbcommon`. X11 needs `x11` and `xkbcommon`. Use `-Dwayland=enabled` or `-Dx11=enabled` to require a backend. Use `-Dwayland=disabled` or `-Dx11=disabled` to omit a backend. At least one backend must be built. To run the Linux window tests, install Weston and Xwayland. Then run: ```sh meson setup build-test -Dintegration_tests=true meson test -C build-test --print-errorlogs ``` The tests start isolated display servers. They test explicit requests, automatic selection, and fallback. They also test both backends in one process, callback-table copying, input payload ownership, queue growth, concurrent clipboard targets, and consistent frame/damage validation. Wayland IME ownership tests need no display server. For memory and undefined-behavior checks, configure a separate build with `-Db_sanitize=address,undefined`. On macOS, the same option runs native Cocoa window tests. These tests need a desktop session. ## Quick Example ```c #include static bool running = true; static void on_draw(struct yasl_surface *s, struct yasl_frame *frame, void *ud) { /* Present frame target */ struct yasl_damage_list dmg = { .n = -1 }; yasl_surface_present(s, &frame->target, &dmg); } static void on_close(struct yasl_surface *s, void *ud) { (void)s; (void)ud; running = false; } int main(void) { struct yasl_display_spec dspec = { .app_id = "org.example.app" }; struct yasl_display *d = yasl_display_open(NULL, &dspec); struct yasl_surface_spec sspec = { .title = "YASL Application", .size = { .x = 800, .y = 600 }, }; struct yasl_surface *s = yasl_surface_create_toplevel(d, &sspec); struct yasl_surface_cbs cbs = { .draw = on_draw, .close = on_close, }; yasl_surface_set_callbacks(s, &cbs, NULL); yasl_surface_schedule_redraw(s); while (running) { 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) { if (ev.key.key == 0xff1b) /* Escape */ running = false; } } yasl_display_flush(d); } yasl_surface_destroy(s); yasl_display_close(d); return 0; } ``` ## License MIT License.