From 94c5e361fbb229b0cd6853e2102a7d95f1f358b3 Mon Sep 17 00:00:00 2001 From: Berke Güzel Date: Mon, 12 Oct 2026 22:12:02 +0300 Subject: better public interface and doxygen --- README.md | 363 ++++++++++---------------------------------------------------- 1 file changed, 58 insertions(+), 305 deletions(-) (limited to 'README.md') diff --git a/README.md b/README.md index 0cb2eea..8300b74 100644 --- a/README.md +++ b/README.md @@ -1,321 +1,74 @@ -# 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. - -Call `yasl_surface_set_cursor(surface, YASL_CURSOR_HIDDEN)` to hide the system -mouse and tablet cursor over a surface while continuing to receive pointer -input. The choice persists across motion and re-entry; select another cursor, -such as `YASL_CURSOR_ARROW`, to show it again. `YASL_CURSOR_NONE` is a compatible -alias for `YASL_CURSOR_HIDDEN`. - -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. -Tablet pressure, tilt, and erasers use XInput 2 when the server exposes it. -It supports the primary selection, window placement, and drag operations. -It does not yet support YASL popups 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. - -Type-safe native handles are declared in ``, -``, and ``: -- Wayland: `wl_display *` and `wl_surface *` via `yasl_wayland_display_get_wl_display()` and `yasl_wayland_surface_get_wl_surface()`. -- X11: `Display *` and `uintptr_t` window ID via `yasl_x11_display_get_display()` and `yasl_x11_surface_get_window()`. -- Cocoa: `NSApplication *`, `NSWindow *`, and `NSView *` via `yasl_cocoa_display_get_application()`, `yasl_cocoa_surface_get_window()`, and `yasl_cocoa_surface_get_view()`. -Generic untyped borrowed handles (`void *`) remain accessible via ``. All handles expire on object 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. X11 also sends a reset when input devices are - removed or disabled. 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`, `xi` (libXi), 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. - -## Tablet drawing example - -Build and run the same C application on Wayland, X11, or Cocoa: +# YASL — Yet Another Surface Library + +YASL is a C desktop surface library for applications that own their renderer. +It creates windows and supplies CPU pixel targets, scale-aware geometry, queued +input, clipboard text, popups, surface drags, and optional frame pacing. +It runs on Wayland, X11, and macOS Cocoa. + +A software-rendered editor, terminal, drawing tool, or custom interface can open +a window and paint pixels without creating an OpenGL context or adopting a +widget toolkit. YASL also exposes tablet pressure/tilt and IME composition. +It does not provide fonts, widgets, audio, or a graphics engine. Native handles +and Cocoa/Metal rendering are separate extensions. + +**Status:** pre-release 0.1; source and ABI compatibility are not yet guaranteed. +Physical keys have portable names, committed text arrives as UTF-8, and scroll +offsets use fractional X/Y line units. Read the [application guide](docs/guide.md) +for platform capabilities and limits. + +## Start here + +- [Getting started](docs/getting-started.md): build, install, link, and run a window. +- [Application guide](docs/guide.md): geometry, drawing, input, ownership, and backend limits. +- [Public reference](include/yasl/yasl.h), [loop](include/yasl/loop.h), + [timing](include/yasl/timing.h), and [native extensions](include/yasl/native.h). + Run `doxygen Doxyfile` for a searchable HTML reference in `build-docs/html`. +- [API policy](docs/api-policy.md), [migration notes](docs/migrating.md), and + [contributing](CONTRIBUTING.md). +- [Tests, tablet example, and benchmarks](docs/testing.md). + +## Build a window + +Use a C23 compiler, Meson 1.6 or newer, and Ninja. Linux needs the development +packages for Wayland (`wayland-client`, `wayland-cursor`, `xkbcommon`) or X11 +(`x11`, `xi`, `xkbcommon`, `xext`; optional `xrandr`). Cocoa requires macOS 14 or +newer and the system SDK, with a Metal-capable device for window drawing. ```sh meson setup build meson compile -C build -./build/yasl-tablet -./build/yasl-tablet --backend wayland -./build/yasl-tablet --backend x11 -# On macOS: -./build/yasl-tablet --backend cocoa -``` - -Draw with the pen tip: pressure controls brush width. Flip to the eraser to -remove ink. The window shows the selected backend, tool, contact state, raw -pressure (0..65535), percentage, and tilt in degrees, with a live pressure bar, -brush outline, and tilt vector. The system cursor is hidden so the brush overlay -serves as the drawing cursor. Mouse drawing uses a fixed width. Press C or -Space to clear and Escape to quit. Resizing and display scale changes preserve -the drawing. `--smoke-test` presents one frame and exits for automated checks. - -[`examples/tablet.c`](examples/tablet.c) includes only `` and the C -standard library. It uses CPU framebuffers and `yasl_surface_input_poll()` on -every backend. Tablet events arrive as `YASL_INPUT_MOUSE`, distinguished by -`mouse.device == YASL_PTR_DEV_PEN` or `YASL_PTR_DEV_ERASER`; `mouse.pen` contains -pressure and signed tilt. Button 0 is tip contact on every backend. Track tip -press/release events: zero pressure alone does not mean release. Hover and side -buttons do not paint, and focus loss, leave, or `YASL_INPUT_RESET` cancels a stroke. -Stationary pressure changes arrive as motion events and update the brush too. - -`YASL_CAP_TABLET` indicates backend/protocol support, not whether a tablet is -plugged in or which axes it has. Missing axes are zero. Wayland requires the -compositor's `zwp_tablet_manager_v2`; X11 requires XInput 2 and driver-provided -`Abs Pressure`/`Abs Tilt X`/`Abs Tilt Y` valuators. Cocoa handles both tablet mouse -subtypes and native tablet point/proximity events. X11 identifies erasers by -the Wacom tool property or the driver's device name. Hardware and driver -support determine which readings are available. The example displays a message -when the server has no tablet protocol and still allows mouse drawing. - -The test suite checks synthetic Wayland/XInput samples and the drawing behavior -without hardware, including pressure-only changes, sparse axes, eraser input, -hover, release, reset, and scale changes. Linux integration tests launch the -example on isolated Wayland and X11 servers; macOS integration tests also check -Cocoa tablet event translation. Physical tablet testing is still needed to -validate a particular device/driver combination. - -For an end-to-end X11 pressure test without hardware, install Xorg's `dummy` -video driver and `inputtest` input driver, then run: - -```sh -meson configure build -Dx11_tablet_tests=true -meson test -C build tablet-x11-native --print-errorlogs +./build/yasl-hello +meson test -C build --print-errorlogs ``` -This starts an isolated Xorg server with virtual pen and eraser devices, sends -real driver samples, checks YASL's shared events through the example, and verifies -its presented window pixels. It checks stationary pressure changes, release -without valuators, and eraser identification through the driver's tool property. -The test disables automatic physical input/GPU attachment and cleans up its -server. Set `YASL_XORG_MODULE_PATH` to a comma-separated module search path if -the test drivers are outside Xorg's default directories. - -## Benchmarks - -YASL includes a dedicated performance microbenchmark target (`yasl-benchmark`) evaluating CPU efficiency, memory footprint, cache line pressure, data structure operations, and window lifecycle operations across core hot paths (surface lookups, multi-output refresh rate scans, input queue expansions, ring buffer throughput, timer arm/disarm cycles, cold/warm window creation, single and bulk window teardowns, titlebar property updates, and multi-resolution dynamic resizing): +Linux builds automatically include backends with available dependencies. Require +one with `-Dwayland=enabled` or `-Dx11=enabled`; omit one with `disabled`. Runtime +selection chooses the native session and allows fallback, or you can request an +explicit backend with `yasl_display_spec.backend`. -```sh -# Run via meson test runner -meson test -C build --benchmark --verbose - -# Or invoke the executable directly -ninja -C build tests/yasl-benchmark -./build/tests/yasl-benchmark -``` - -## Quick Example +Include `` for the core. The complete [hello.c](examples/hello.c) +handles creation failures, drawing, dispatch, input draining, and cleanup. +Its drawing callback fills every pixel before presenting: ```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) +static void draw(struct yasl_surface *surface, struct yasl_frame *frame, void *ud) { - 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); + (void)ud; + size_t count = (size_t)frame->target.size.x * frame->target.size.y; + for (size_t i = 0; i < count; i++) + frame->target.pixels[i] = 0xff306090; + struct yasl_damage_list damage = {.n = -1}; + if (!yasl_surface_present(surface, &frame->target, &damage)) { + /* Arrange application error handling after returning. */ } - - yasl_surface_destroy(s); - yasl_display_close(d); - return 0; } ``` +Toplevel creation sizes are logical pixels; drawing and input use device pixels. +Targets are borrowed for one draw callback. Keep durable content in application +memory, dispatch events, drain input queues, then flush scheduled drawing. These +rules are explained with examples in the application guide. + ## License -MIT License. +[MIT](LICENSE). -- cgit v1.2.3