diff options
| author | Berke Güzel <wenekar1@gmail.com> | 2026-10-13 23:41:31 +0300 |
|---|---|---|
| committer | Berke Güzel <wenekar1@gmail.com> | 2026-10-13 23:41:31 +0300 |
| commit | 76073fe5fad8799e5850805f2f090af2cb62c741 (patch) | |
| tree | 9658019babe4760a3634bdab4da0731a10e42e68 /docs | |
| parent | dd1775a9927922c14f8679ccbc4d3ca8d5917e20 (diff) | |
Simplify the public event and CPU API
Applications now need far less ceremony: hello.c is a 20-line loop that
handles REFRESH, CLOSE and QUIT and draws into the surface's own pixels.
- Fold GEOMETRY, STATE and FOCUS into one WINDOW event carrying size,
scale and state; unchanged values send nothing.
- Guarantee REFRESH once a window has a size and after every resize,
never at size zero.
- Merge DROP_HOVER into DROP with over/leave/data actions and give drag
and drop actions enums.
- Keep event strings valid until the next wait and drop
yasl_event_release; empty or failed waits report NONE.
- Replace copy-in presentation with a lent pixel buffer:
yasl_cpu_pixels and yasl_cpu_present.
- Remove yasl_poll_event, yasl_cpu_read_pixels, unused event fields and
comments that restated names.
- Wayland: resize floating windows in request_size instead of reporting
it unsupported, and stop marking new GPU surfaces as Vulkan so
yasl_vulkan_create_surface no longer fails with NATIVE_WINDOW_IN_USE.
Also includes the in-progress public API refactor that was uncommitted
in the tree (removal of the app/loop layer, new surface, GL and Vulkan
entry points).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/api-policy.md | 48 | ||||
| -rw-r--r-- | docs/api-redesign.md | 147 | ||||
| -rw-r--r-- | docs/getting-started.md | 138 | ||||
| -rw-r--r-- | docs/guide.md | 617 | ||||
| -rw-r--r-- | docs/migrating.md | 132 | ||||
| -rw-r--r-- | docs/testing.md | 166 |
6 files changed, 226 insertions, 1022 deletions
diff --git a/docs/api-policy.md b/docs/api-policy.md index 3e510f3..f2f4d92 100644 --- a/docs/api-policy.md +++ b/docs/api-policy.md @@ -1,38 +1,18 @@ # Public API policy -YASL 0.1 is pre-release. The headers under `include/yasl/` are the public API; implementation -headers under `src/` are private even when their symbols appear in a static -archive. The shared library exports only declarations from public headers. -Native backend accessors are exported only for built backends. Installation -copies both libraries, public headers, enabled native headers, and pkg-config metadata. +YASL is pre-release 0.1. Source and ABI compatibility are not guaranteed. Rebuild +consumers against matching installed headers/library; do not serialize public events +or assume their layout is a stable file/network format. -There is currently no stable ABI promise and no versioned shared-library SONAME. -Rebuild consumers against the exact headers/library pair they ship. Source and -binary compatibility can change before 1.0. Do not assume struct layout is stable -across releases, mix headers and binaries from different revisions, or persist -public structs as a file/network format. The release version is declared in -`meson.build`. +Only installed include/yasl headers are public. The surface library exposes an +opaque surface and one tagged event record, with direct C arguments and ordinary +pointer/integer results. Native backend headers, geometry records, callbacks, loops, +and drawing targets under src are private even when a static archive contains them. +The shared library exports exactly the installed public declarations. -See [migration notes](migrating.md) for window creation, portable keyboard, and -fractional scroll API changes. - -API changes should keep ordinary CPU drawing usable with only `<yasl/yasl.h>` -and the C standard library. Prefer an explicit capability and an ordinary -unsupported result over backend names in portable application logic. Native -rendering and handles belong in the extension headers. Avoid adding engine, -widget, renderer, or application policy to the surface layer. - -Every public addition or behavior change needs: - -- A header contract describing arguments, result, units, lifetime, threading, - callback timing, and unsupported behavior where applicable. -- An update to the guide and its backend limits when observable behavior changes. -- A runnable example when the new workflow cannot be understood from an isolated - call, and meaningful regression coverage for ownership or behavior changes. -- A check that shared exports and consumers of the installed library still work. - -Functions documenting errno set it on failure. Other booleans report acceptance -or availability without a guaranteed errno; void setters are best effort. -Callbacks and successful returns do not imply confirmed screen presentation. -See `yasl_timing.source` for provenance. Future changes that strengthen error -reporting must preserve these distinctions in documentation. +Do not restore removed runner/frame/display APIs or compatibility aliases. Keep +rendering bridges separate from the portable surface/input core and preserve the +application's ownership of GPU resources and its loop. Public additions need clear +units, ownership, thread/callback timing, errors and unsupported behavior, useful +examples, and behavior coverage. Update the guide and test installed shared/static +consumers and exports. Changes affecting Cocoa need a native macOS build/test. diff --git a/docs/api-redesign.md b/docs/api-redesign.md deleted file mode 100644 index a111a92..0000000 --- a/docs/api-redesign.md +++ /dev/null @@ -1,147 +0,0 @@ -# Public API redesign - -This is the implementation contract for the pre-release API revision. The -portable layer owns event ordering, queued payloads, surface identities, and -frame validation. Backends translate native behavior into that contract. - -## One event stream - -`yasl_display_poll_event(display, &event)` consumes one display-wide FIFO. Input, -window resize/close/state, drag hover/drop, composition, clipboard results, and -display quit/reopen/loss use the same `yasl_event`. Events have an increasing -`sequence` assigned when YASL queues them, and a monotonic `time_ns`. Sequence -orders capture by YASL, not unknowable ordering between OS threads or devices. -The runner consumes this same queue; it does not drain separate queues by window. - -Event strings are copied on enqueue and borrowed until the next successful -display poll or display close, even if the recipient is destroyed. Destroying a -surface removes its undelivered events. A callback may destroy its recipient; -it must stop using that surface. Draws remain synchronous to satisfy native -live-resize rules; the runner drains queued events before invoking a frame. - -Legacy surface polling consumes input from the same queue and discards pending -non-input notifications, which were already delivered through legacy callbacks. -Applications must choose one polling style; discarding prevents idle spin and -unbounded retention in old manual loops. Legacy window -callbacks remain for migration, but applications should choose queue delivery -and a draw callback. Backend notifications must queue before calling a legacy -callback, and must not access a surface after a callback that can destroy it. - -## One frame contract - -Both manual drawing and `yasl_app_desc.frame` receive `struct yasl_frame`. -Its `target` describes CPU pixels (NULL for native rendering), device-pixel size, -and row stride. The frame also carries frame index, scale, and an estimated -animation timestamp. Native drawables are queried through `<yasl/native.h>`; -there is no untyped GPU layer or public internal pointer in the portable frame. - -`yasl_frame_present(frame, rects, count)` submits the exact active frame once. -NULL with zero count means full damage; a non-NULL array with zero count means -no damage. NULL with nonzero count and negative extents fail with EINVAL. -More than the backend rectangle limit falls back to full damage after validating -every rectangle; damage must never be silently lost. Targets expire at callback -return. Pixels must always describe a complete frame. - -`yasl_surface_request_frame` is the normal coalescing redraw operation. -`yasl_surface_set_pacing` returns success/failure and works with either loop -style. Continuous drawing uses native pacing where available and a refresh-based -timer otherwise. On-demand surfaces require explicit invalidation. - -## Identities, posting, and shutdown - -`yasl_surface_id(surface)` returns a nonzero identity unique within its display. -`yasl_display_post(display, &message)` is thread-safe; messages target an identity -(zero means display-wide), never a borrowed surface pointer. The display must -remain alive during posting, and producer threads must stop before display close. -After a successful post the optional release callback runs exactly once on the -display thread, after consumption or cancellation (including surface destruction -and shutdown). A failed post leaves payload ownership with the caller. Posting -to a retired identity fails. The display queue owns only its copy of the message; -the release callback defines payload cleanup. Release may post, but must not -poll, dispatch, or destroy YASL objects. Legacy `yasl_post_event` adapts to -this mechanism, with the caller responsible for its borrowed target lifetime. - -Application callbacks may return CONTINUE, QUIT, or ERROR. ERROR makes -`yasl_run` return failure after cleanup. `yasl_app_exit(display, result)` permits -timers, fd callbacks, and frames to request the same orderly termination. -The optional initial window is available through `yasl_app_window(display)`. -Surface userdata is independent of legacy callback userdata and accessible -through `yasl_surface_set_userdata` / `yasl_surface_userdata`. - -## Window requests and units - -New code spells toplevel creation size `size_logical`; `size` remains an alias -for migration. Framebuffer geometry, pointer coordinates, popup placement, -damage, IME carets, and drag-image coordinates are device pixels as documented -on their structures. Conversion helpers remain explicit. - -`yasl_surface_set_size(surface, logical_size)`, -`yasl_surface_set_fullscreen(surface, enabled)`, -`yasl_surface_set_maximized(surface, enabled)`, and -`yasl_surface_minimize(surface)` return whether the native request was issued. -They set errno on failure: EINVAL for invalid input/non-toplevel surfaces, -ENOTSUP for unsupported requests, or an OS error. Success is acceptance of a -request, not a promise that the window manager honored it. Resulting size/state -is reported through events. No portable absolute window-position setter exists. - -## Clipboard and composition - -`yasl_clipboard_set(display, selection, text)` returns whether a copied UTF-8 -offer was issued, with errno on failure. `yasl_clipboard_request(surface, -selection)` returns a nonzero request ID, or zero with errno on failure. -The backend operation `display_request_paste(d, selection, surface, id)` returns -true only after accepting responsibility for one completion. It completes via -`yasl_surface_complete_paste(surface, id, status, text, len)`; status is -YASL_TRANSFER_OK, UNAVAILABLE, or FAILED. Results use YASL_EVENT_PASTE and its -`.paste` payload (data/len/request_id/status); legacy `.text` aliases data/len. -Surface destruction cancels delivery and native transfers. A backend must -complete errors as well as success, including empty text. Request IDs are -monotonic per display. Legacy paste uses ID zero. External Linux transfers time out after 30 seconds. - -YASL_EVENT_COMPOSITION carries copied UTF-8 preedit text, byte selection offsets -(both -1 if unavailable), and an active flag. Each native change queues a -snapshot, including composition ending; committed text remains YASL_EVENT_TEXT. -The query functions remain migration helpers. - -## Backend boundary - -Common helpers declared in `common/backend.h`: - -* `yasl_surface_notify_configure(s, geometry)` -* `yasl_surface_notify_close(s)` -* `yasl_surface_notify_state(s, state)` -* `yasl_surface_notify_drag(s, event, position_px)` -* `yasl_surface_notify_drop(s, event, position_px)` -* `yasl_surface_notify_composition(s, text, begin, end)` (NULL/empty ends it) -* `yasl_surface_draw(s, frame)` (fills frame metadata and invokes draw) -* `yasl_display_notify_event(d, YASL_EVENT_DISPLAY_QUIT/REOPEN/LOST)` -* `yasl_surface_complete_paste(s, request_id, status, text, length)` - -Backends keep using their existing input_queue; a new `surface_input_queue` -operation exposes that queue to common code for attaching its event publisher. -Backends must replace direct lifecycle/draw/drag callbacks with these helpers, -without weakening their native destruction guards. They implement optional -surface_set_size/fullscreen/maximized/minimize and display_copy_text / -display_request_paste operations. Common wrappers validate arguments and -report unsupported operations uniformly. - -Linux backends are compiled and tested independently and together. Cocoa changes -can be inspected here but require macOS compilation/runtime verification. - -## Validation - -The combined Linux configuration passes all 26 Meson tests, including isolated -Weston/Xwayland integration, runner error/cleanup paths, event ordering, -clipboard outcomes, composition, and destruction during callbacks. Separate -Wayland-only and X11-only builds pass their unit suites and the final public -export, C++11, and event-contract checks. - -AddressSanitizer and UndefinedBehaviorSanitizer pass the common contract tests -and the isolated backend integration suite. Leak detection was disabled because -LeakSanitizer is unavailable in this execution environment. Doxygen builds with -warnings treated as errors. A staged install links both shared and static -consumers using only the installed headers and pkg-config metadata. - -Cocoa code and native regression tests were updated and reviewed, but were not -compiled or executed: macOS validation remains required. Existing X11 INCR and -Wayland arbitrary-size limitations are reported explicitly rather than hidden. diff --git a/docs/getting-started.md b/docs/getting-started.md index d1f284d..bb7f095 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -1,113 +1,71 @@ # Getting started -YASL gives a C application desktop windows with CPU pixel targets, scale-aware -geometry, queued input, clipboard text, and optional frame pacing. It fits -software-rendered tools such as editors, terminals, drawing applications, and -custom interfaces that already have their own renderer. A window needs neither -an OpenGL context nor a graphics device supplied by the application. +YASL creates desktop surfaces and delivers queued input. You own the event loop, +renderer, graphics device/context use, and application lifetime. -The core headers expose no window system types. Native handles and Metal drawing -are separate opt-in extensions. YASL currently supports Linux (Wayland and X11) -and macOS (Cocoa); it does not provide widgets, font shaping, audio, an OpenGL -context API, or a Vulkan surface API. It is pre-release software; see the -[API policy](api-policy.md) before distributing a library that depends on its ABI. - -## Build and run - -Use a C23 compiler, Meson 1.6 or newer, and Ninja. GCC and Clang can build the library. On Linux install either backend's development dependencies: - -| Backend | pkg-config dependencies | -| --- | --- | -| Wayland | `wayland-client`, `wayland-cursor`, `xkbcommon` | -| X11 | `x11`, `xi`, `xkbcommon`, `xext`; optional `xrandr` | -| Linux desktop services | Optional `dbus-1` for notifications and appearance | -| Cocoa | macOS 14+ SDK with AppKit, Metal, QuartzCore, and UserNotifications; Metal-capable device | +Build with a C23 compiler, Meson 1.6+, and Ninja. Linux backend dependencies are +wayland-client, wayland-cursor, xkbcommon, or x11, xi, xext, xkbcommon (optional +xrandr). Optional egl and wayland-egl enable GL contexts; Vulkan SDK development +files enable the Vulkan bridge. Optional dbus-1 enables Linux desktop services. +Cocoa requires macOS 14+ and the AppKit, Metal, QuartzCore, OpenGL, and +UserNotifications system frameworks. Optional Vulkan SDK enables MoltenVK WSI. ```sh meson setup build meson compile -C build ./build/yasl-hello +./build/yasl-game meson test -C build --print-errorlogs ``` -Each Linux backend defaults to `auto`, meaning build it when its dependencies -are available. Require one with `-Dwayland=enabled` or `-Dx11=enabled`; omit one -with `-Dwayland=disabled` or `-Dx11=disabled`. At least one backend is required. -`-Ddbus` works the same way for the optional D-Bus desktop services. -Cocoa is selected on macOS and requires no extra third-party dependencies. -Its minimum deployment target is macOS 14. Set `MACOSX_DEPLOYMENT_TARGET=14.0` -(or newer) before configuring if your toolchain defaults to an older target. -CPU pixel targets are presented through the system's Metal device; applications -do not need to create or manage that device themselves. +Backend options `-Dwayland=enabled|disabled|auto`, `-Dx11=...`, and `-Ddbus=...` +control dependency selection. At least one window backend must be built. Runtime +AUTO selects the native session and falls back to another built Linux backend. +For an explicit choice call `yasl_set_backend` before creating any surface. -The complete beginner example is [hello.c](../examples/hello.c). It lets -`yasl_run()` create its window from `yasl_app_desc.window`, fills every pixel -with an RGB gradient, and presents inside a frame callback. -`yasl_run()` handles input delivery, display notifications, and cleanup. The -[interactive window](../examples/simple_window.c) adds pointer input; the -[tablet example](../examples/tablet.c) demonstrates retained application content, -pressure, erasers, resizing, and scale changes. +## A window -## Link an application +```c +#include <yasl/yasl.h> + +int main(void) +{ + yasl_surface *s = yasl_surface_create("Example", 800, 600, 0); + if (!s) return 1; + /* Attach a GPU renderer here and draw on each REFRESH. */ + yasl_event event; + while (yasl_wait_event(&event, -1) >= 0 && + event.type != YASL_EVENT_CLOSE && event.type != YASL_EVENT_QUIT) { + } + /* Destroy GPU objects before shutting down. */ + yasl_shutdown(); + return 0; +} +``` -Install the libraries, public headers, and pkg-config metadata into your prefix: +The first creation initializes a shared backend. More windows use that backend; +there is no public display/context object. Closing the final window does not +implicitly terminate the application or reset configuration. `yasl_shutdown` +releases shared resources and restores defaults. + +[hello.c](../examples/hello.c) draws into a CPU surface's pixel buffer on every REFRESH. [game.c](../examples/game.c) creates a GL context, binds it, +loads procedures, draws, and swaps. [tablet.c](../examples/tablet.c) retains a canvas +and draws pressure-sensitive pen/eraser strokes. [notepad.c](../examples/notepad.c) +uses a worker queue, wakeups, clipboard, IME, and its own blink deadline. + +## Link and install ```sh meson setup build --prefix=/usr/local --libdir=lib meson compile -C build meson install -C build cc -std=c23 examples/hello.c -o hello $(pkg-config --cflags --libs yasl) -./hello -``` - -The prefix must be writable. For a custom prefix, add its pkg-config directory -to `PKG_CONFIG_PATH` and its library directory to the runtime loader path. -Use `pkg-config --static --libs yasl` for static consumers so backend dependencies -are included. Installed native accessor headers match the built backends. - -A Meson application can instead vendor the source under `subprojects/yasl`: - -```meson -project('my-app', 'c', default_options: ['c_std=c23']) -yasl = subproject('yasl').get_variable('yasl_dep') -executable('my-app', 'main.c', dependencies: yasl) -``` - -The subproject dependency includes headers and backend link dependencies. -Public headers can be included from C++11 or newer; the library itself builds -as C23. The libraries, public headers, and pkg-config metadata are installed together. - -## The application cycle - -1. Include `<yasl/app.h>` and initialize a `yasl_app_desc` with your userdata and - `init`, `event`, and `frame` callbacks. Call `yasl_run(&desc)`. -2. In `init`, create a toplevel with a positive **logical-pixel** size and call - `yasl_surface_request_frame(surface)` for the first redraw. -3. In `event`, handle the union member selected by `event->type`. Return - `YASL_APP_QUIT` for a close request, `YASL_APP_ERROR` for failure, or - `YASL_APP_CONTINUE` to keep running. -4. In `frame`, fill every pixel in `frame->target.pixels` with premultiplied - `0xAARRGGBB` and present once with `yasl_frame_present(frame, NULL, 0)`. -5. Let the runner close remaining surfaces and the display. Release your own - resources in the optional `quit` callback or after `yasl_run()` returns. - -```c -struct yasl_app_desc desc = { - .app_id = "org.example.app", - .userdata = &app, - .init = init, - .event = event, - .frame = draw, -}; -return yasl_run(&desc); ``` -The default pacing mode sleeps until input, resize, or a requested redraw. -Use `YASL_PACING_CONTINUOUS` for an animation or game. The core API still supports -manual dispatch/poll/flush when an application owns its event loop; see the guide. +Use a writable prefix. For a custom prefix configure PKG_CONFIG_PATH and the +runtime library search path. Static consumers use `pkg-config --static --libs yasl`. +Public headers also compile as C++11. Native headers and the Vulkan header are +installed only when their respective support is built. -See the [application guide](guide.md) for drawing, input, popups, timers, and -platform limits, or the comments in the [public header](../include/yasl/yasl.h) -for each function's contract. To build the searchable HTML reference, install -Doxygen and run `doxygen Doxyfile` from the repository root. Open -`build-docs/html/index.html`; Doxygen is not needed to build the library. +See the [guide](guide.md) for geometry, event lifetime, GPU ownership, input, and +backend limits. `doxygen Doxyfile` builds the reference in build-docs/html. diff --git a/docs/guide.md b/docs/guide.md index 1b64880..d0db071 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -1,476 +1,141 @@ -# Application guide - -The contracts in [yasl.h](../include/yasl/yasl.h), [loop.h](../include/yasl/loop.h), -[timing.h](../include/yasl/timing.h), [desktop.h](../include/yasl/desktop.h), and -[native.h](../include/yasl/native.h) are the reference for individual calls. All examples here use the display's opening -thread, which must be the main thread on macOS. Userdata stays owned by the -application. Copy borrowed data before retaining it beyond its documented life. - -## Backend selection and capabilities - -Zero-initialize `yasl_display_spec` or pass NULL to select automatically. On -Linux, `WAYLAND_DISPLAY`, `WAYLAND_SOCKET`, or `XDG_SESSION_TYPE=wayland` makes -YASL try Wayland first; otherwise it tries X11 first. It falls back to the other -built backend if opening fails. On macOS it uses Cocoa. Explicit -`YASL_BACKEND_WAYLAND`, `YASL_BACKEND_X11`, or `YASL_BACKEND_COCOA` requests never -fall back. Query the result with `yasl_display_backend()`. - -Use `yasl_display_caps()` for optional behavior. A bit means backend/server -support, not that a device is attached or that the current input state allows -an operation. Always check the operation's return value too. - -| Capability | Meaning | Wayland | X11 | Cocoa | -| --- | --- | --- | --- | --- | -| `YASL_CAP_POPUPS` | Child popup surfaces | Yes, input grab required | Yes | Yes | -| `YASL_CAP_DRAG` | Surface-drag source and target events | Data device v3 | Yes | Yes | -| `YASL_CAP_DRAG_CARRY` | Carry a toplevel with a surface drag | Optional toplevel-drag protocol | Yes | Yes | -| `YASL_CAP_PLACE_WINDOW` | Place a window under the pointer | No | Yes | Yes | -| `YASL_CAP_PRIMARY_SELECTION` | Separate primary text selection | Optional primary-selection protocol | Yes | Named pasteboard emulation | -| `YASL_CAP_CSD` | Backend advertises client decoration support | Yes | No | Yes | -| `YASL_CAP_NATIVE_GRAPHICS` | Application-managed native drawables | No | No | Metal | -| `YASL_CAP_FRAME_PACING` | Display/compositor-driven drawing | Yes | No | Yes | -| `YASL_CAP_TABLET` | Tablet event translation | Optional tablet-v2 protocol | XInput 2 | Yes | - -This table describes the implementation, not universal operating-system -promises. Wayland protocols and XInput availability depend on the running -server. A capability does not guarantee every related action: Cocoa currently -returns false for interactive resize and window-menu requests even though -it advertises CSD. Check each result and provide an application fallback. - -## Window creation and callbacks - -All examples include `<yasl/app.h>` and call `yasl_run()` with a -`yasl_app_desc`. Set `window` to a `yasl_window_spec` for a single toplevel, -which the runner creates before `init` with its first frame requested; -`yasl_run()` returns an error if creation fails. Create further surfaces in the -optional `init`, handle `YASL_EVENT_*` notifications in `event`, and render into -`frame->target.pixels` in `frame`. Present with `yasl_frame_present(frame, NULL, -0)` and return `YASL_APP_QUIT` from an event handler to exit. Without an `event` -callback, closing any window stops the runner. The runner handles dispatch, queued event delivery, resize redraws, -and remaining surface/display cleanup. Display quit stops the runner; connection -loss and dispatch failures return an error after cleanup. The runner delivers -the same display-wide FIFO consumed by `yasl_display_poll_event()`, including -window state, drag hover, composition, clipboard completion, and display lifecycle -events. `event.sequence` increases in enqueue order across all windows; it is -YASL capture order, not a claim about ordering between native producers. -Return `YASL_APP_ERROR` on application failure, or call -`yasl_app_exit(display, YASL_APP_ERROR)` from a frame, timer, or fd callback. -Cleanup still runs, and `yasl_run()` returns failure. During `init`, -`yasl_app_window(display)` exposes the optional initial window. Application data -can be supplied in `window.userdata` or attached with -`yasl_surface_set_userdata()`; it is independent of callback userdata. - -Unified pointer button and motion events retain `device` and `pen` axes, -including erasers and pressure-only motion. Cancel held input or active strokes -on `YASL_EVENT_INPUT_RESET`, pointer leave, or focus loss. Reset also starts the -next motion delta from zero. [hello.c](../examples/hello.c) shows the minimal -lifecycle; [tablet.c](../examples/tablet.c) demonstrates device-aware drawing. - -For applications managing their own event loop, use the core API below. - -Open the display with application-wide options and notifications in -`yasl_display_spec`. Create each toplevel with `yasl_window_spec`, including its -title, `size_logical`, optional `yasl_window_cbs` table in `cbs`, and userdata in -`ud`. One display can own multiple windows with separate callback tables. - -The window copies its callbacks during creation. Its callbacks cannot run before -creation returns, so the application can store the returned surface first. -Userdata stays borrowed until callbacks are replaced or the surface is destroyed. -Read initial geometry with `yasl_surface_geometry()`; creation does not guarantee -an initial `configure` notification. Request the first redraw once application -state is ready. `yasl_surface_set_callbacks()` replaces callbacks later and also -installs popup callbacks; the setter does not invoke the callbacks. - -When using `yasl_run()`, its runner installs callbacks for windows on its display. -Use `yasl_app_desc.event` and `.frame` rather than `yasl_window_spec.cbs` and `.ud`. -Replacing callbacks on a runner-owned surface fails with `EBUSY`. -With a manual loop, install just the draw callback and drain the display FIFO -after dispatch; lifecycle and input share the same event handler logic. - -Read window state with `yasl_surface_state()`. Its `YASL_STATE_*` flags can -coexist, and the `state` callback receives the same current bitmask. Wayland -also reports `YASL_STATE_TILED` when any window edge is tiled; X11 and Cocoa -currently leave that flag unset. Unsupported flags are absent. On Wayland, an -ordinary floating toplevel has fullscreen, maximized, and tiled all absent. -`YASL_STATE_SUSPENDED` describes repaint suspension rather than minimization; -there is no portable minimized flag. - -## Geometry and scaling - -| Value | Units | -| --- | --- | -| Toplevel `yasl_window_spec.size` | Logical pixels | -| `yasl_geometry.size`, framebuffer size | Device pixels | -| Pointer position, caret, window-menu position | Device pixels, relative to the surface | -| Damage rectangles | Device pixels, relative to the draw target | -| All popup fields, including `size` | Device pixels; anchor is relative to the parent | -| Drag image size, hotspot, carry/place offsets | Device pixels | -| `scale_120` | 120 = 1x, 180 = 1.5x, 240 = 2x | - -The origin is the top left, x increases right, and y increases down. Use -`yasl_surface_geometry()` for the current size and scale, and update cached -layout on `configure`. The native system may choose a different size from the -request. X11 currently reports unit scale; automatic output DPI scaling is not -implemented there. - -```c -struct yasl_geometry geometry = yasl_surface_geometry(surface); -int padding_px = yasl_scale_to_device(8.0, geometry.scale_120); -int logical_width = yasl_scale_to_logical(geometry.size.x, geometry.scale_120); -``` - -Scale conversion rounds to the nearest integer, with ties away from zero, -saturates out-of-range finite results, and returns zero for a zero scale. -`yasl_scale_to_device()` also returns zero for non-finite logical coordinates. - -## Drawing and pacing - -`yasl_surface_request_frame()` coalesces requests. `yasl_display_flush()` -services them when the backend can draw. During a native resize, dispatch can -also draw synchronously. Avoid assuming callbacks occur only in flush. - -The CPU target is tightly packed premultiplied `0xAARRGGBB`. Row stride is -`target.size.x * sizeof(uint32_t)`. For example, opaque red is `0xffff0000`, -50% transparent red is approximately `0x80800000`, and transparent black is -`0x00000000`. The numeric format describes a 32-bit value, not a portable byte -sequence. Keep your application's durable content separately: a new target may -contain no previous frame contents. - -```c -static void draw(struct yasl_surface *surface, struct yasl_frame *frame, void *ud) -{ - (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; - if (!yasl_frame_present(frame, NULL, 0)) { - /* Arrange application error handling after returning. */ - } -} -``` - -Present the exact active frame at most once during its callback. Do not mutate -the target identity or retain frame/pixel pointers after return. NULL rectangles -with count zero mean full damage; a non-NULL array with zero count means no damage. -All supplied extents must be nonnegative. Rectangles are clipped, and more than -`YASL_DAMAGE_MAX` rectangles fall back to full damage after all are validated. -Damage may reduce uploads, but every target must contain complete valid pixels. -Native rendering uses the same frame, with a drawable queried in the extension. - -`yasl_surface_set_pacing()` works in either loop style. ON_DEMAND coalesces -explicit requests and sleeps when idle. CONTINUOUS uses native display signals -where available and a refresh-based timer otherwise, with about 60 Hz as a -fallback estimate. It reports allocation/argument failures instead of silently -ignoring surfaces outside the runner. Suspension can suppress actual drawing. -Legacy `yasl_surface_present()` and `yasl_surface_schedule_redraw()` remain -migration adapters; new code uses the frame presentation and request functions. - -`yasl_surface_read_pixels()` copies the last presented CPU frame, for example -for a screenshot or a debugging tool. Call it outside draw callbacks. A frame -is available from a successful present until the next draw starts, and again -once that draw presents. Call it with no buffer first to learn the extent. The -copy is YASL's frame, not the composited screen, and native rendering has none. - -`yasl_surface_timing()` reports estimates and their provenance. All timestamps -are monotonic nanoseconds. `YASL_TIMING_SUBMISSION` is CPU submission time, -`YASL_TIMING_FRAME_CALLBACK` is a compositor pacing signal, and only -`YASL_TIMING_PRESENTATION` confirms compositor presentation. `UNKNOWN` means no -sample. `refresh_ns` and `next_present_ns` remain estimates. Use -`yasl_surface_frame_time_ns(surface, yasl_loop_now_ns())` as a stable animation -clock; it does not establish actual scanout time. - -## Input, text, and payload lifetime - -Dispatch receives native input; `yasl_display_poll_event()` drains the display -FIFO without waiting. Drain it once for the whole display, including multiple -windows and popup input. `event.surface` identifies the live recipient and -`surface_id` is its stable identity. Destruction cancels undelivered recipient -events. `yasl_surface_poll_event()` remains an input-only migration adapter and -discards pending non-input notifications, which legacy callbacks already -delivered. Do not mix the two polling styles. - -Read the union member selected by `event.type`. `time_ns` is monotonic and -`sequence` orders capture across the display. TEXT and DROP use `.text`, PASTE -uses `.paste`, and COMPOSITION uses `.composition`. Each string is copied when -queued, NUL-terminated, and borrowed until the next successful display/surface -poll or display close. Dispatch, unsuccessful polls, and recipient destruction -do not invalidate the last polled string. Copy it to retain it longer. - -Use `YASL_EVENT_TEXT` for document text insertion, including committed IME text. -It contains UTF-8 and can contain several characters. Key actions are for -commands and held-key state; releases and repeats have separate tags. A text -commit need not correspond to one key event, and a key need not produce text. - -`key.key` is a portable physical identity from -[keycodes.h](../include/yasl/keycodes.h), included by `<yasl/yasl.h>`. Names follow -US keyboard positions: `YASL_KEY_A` identifies the same position on a French -layout even though it types Q. Modifiers do not change this identity. Keypad -keys and left/right modifiers have distinct names. Unknown native keys report -`YASL_KEY_UNKNOWN`; `native_code` preserves their backend-specific identity -(Wayland evdev code, X11 server keycode, or Cocoa virtual keycode). - -`codepoint` and `shifted` are optional Unicode scalar labels for the active -layout without modifiers and with Shift. Zero means no single printable label, -including function keys, unavailable translations, and mappings that produce -multiple characters. Dead keys may have a spacing label or zero. These fields -are useful for displaying shortcuts or -choosing layout-sensitive commands; they are never document text. For example: - -```c -if (event.type == YASL_EVENT_KEY && event.key.action == YASL_KEY_PRESS && - event.key.key == YASL_KEY_ESCAPE) { - running = false; -} -/* Insert event.text.data[0..event.text.len) only for YASL_EVENT_TEXT. */ -``` - -Wayland uses the supplied XKB layout and locale compose rules for ordinary text -and dead keys, with text-input-v3 for IME commits. Compose rules use `LC_ALL`, -`LC_CTYPE`, then `LANG`, falling back to `C.UTF-8`. X11 uses the X input method -when available; Cocoa uses AppKit text services. X11 input methods use the -application's `LC_CTYPE` locale. Initialize it before opening a display when -using locale-specific XIM, for example with `setlocale(LC_CTYPE, "")`; YASL does -not change the process locale. IME availability and system configuration still -determine which compositions work. Physical key events -remain available for commands; input methods may consume keys while composing. - -Pointer events use the `YASL_EVENT_POINTER_*` types and share `event.pointer`, -with `device` distinguishing mouse, touch, pen, and eraser. `POINTER_DOWN` and -`POINTER_UP` carry a `YASL_BUTTON_*` value: `PRIMARY` is the left button, pen tip, -or touch contact. Other types leave `button` meaningless. Every pointer event -also carries `buttons`, a `YASL_BUTTON_BIT()` mask of the buttons held after -that event, tracked from the queue's down and up events. Use it to tell a drag -from a hover. Count clicks against `yasl_display_double_click_ms()`, the -system interval: XSETTINGS on X11, the KDE or GNOME setting through the -Settings portal on Wayland, and the system value on Cocoa. It falls back to -`YASL_DOUBLE_CLICK_MS_DEFAULT` (400 ms). The query can wait briefly on Linux, so -read it when a window gains focus. `YASL_EVENT_INPUT_RESET` clears the mask. On Wayland, -`POINTER_LEAVE` also clears it, because the compositor sends leave during a -press only when it takes the pointer away. X11 and Cocoa keep the press across -leave and still deliver the release. `POINTER_MOTION` reports `delta`, the device-pixel movement since -the previous sample, which restarts from zero after enter, leave, or reset. -`yasl_surface_set_pointer_mode()` with `YASL_POINTER_RELATIVE` hides and locks the -cursor while the surface is focused; motion then carries unbounded `delta` and -`pos` stays at the lock position. Wayland implements it with the -relative-pointer and pointer-constraints protocols, reporting the compositor's -accelerated motion in device pixels. The compositor decides when the lock -engages; until then, and while it is released on focus loss, motion stays -absolute with a computed `delta`. X11, Cocoa, and compositors without those -protocols return false and keep the current mode. Pen -pressure is normalized to 0..65535, signed tilt is in degrees, and missing axes -are zero. Pressure-only changes can produce motion. Zero pressure alone does -not mean release. This interface does not expose simultaneous touch contacts. - -For `YASL_EVENT_POINTER_SCROLL`, read `pointer.scroll.x` and `.y`: fractional line offsets, -positive right and down, after system scroll preferences. Both axes may be -nonzero in one event. Preserve fractions and choose your application's own -pixels per line; one wheel detent normally gives one line. `button` is unused -for scroll. Wayland high-resolution wheel values take precedence over duplicate -discrete/raw descriptions. Smooth Wayland offsets and precise Cocoa offsets -use ten native units per line. X11 currently reports whole wheel steps, so it -does not provide the same smooth-scroll precision as the other backends. - -Cancel held keys, buttons, active strokes, and composition on `YASL_EVENT_INPUT_RESET`, -focus loss, or pointer leave as appropriate to your application. RESET has no -union payload. It is emitted after input-queue allocation failure and certain -device interruptions; ignoring it can leave application input stuck. - -## Clipboard, file drops, and IME - -`yasl_clipboard_set(display, selection, text)` copies a NUL-terminated UTF-8 -offer and reports failure through its return and errno. Wayland may require a -recent input serial. `yasl_clipboard_request(surface, selection)` returns a -nonzero request ID, or zero with errno when rejected. Each accepted request -produces one `YASL_EVENT_PASTE` carrying `.paste.request_id`, `.status`, `.data`, -and `.len`, unless its target/display is destroyed. OK includes empty text; -UNAVAILABLE means no text offer, and FAILED means transfer/decoding failure. -External Linux transfers have a 30-second timeout. X11 does not implement INCR -and reports unsupported large transfers as FAILED. Legacy STRING uses Latin-1 -conversion. Malformed UTF-8 completes as failure. - -Unsupported primary selection reports ENOTSUP and never aliases the clipboard. -Cocoa uses a YASL named pasteboard for primary selection. Legacy copy/paste calls -remain adapters; legacy paste completions carry request ID zero. - -External file drag hover uses `YASL_EVENT_DROP_HOVER`; actual file URLs -arrive as `YASL_EVENT_DROP`. `text.data` is a `text/uri-list` string separated by -CRLF, not a list of native paths. `yasl_drop_next_path()` walks it and decodes -each local `file:` URL into a path. It skips comments, other schemes, remote -hosts, and malformed lines: - -```c -const char *cursor = event.text.data; -char path[4096]; -size_t len; -while ((len = yasl_drop_next_path(&cursor, path, sizeof(path))) != 0) { - if (len >= sizeof(path)) break; /* Too long; grow the buffer instead. */ - /* Open path. */ -} -``` - -Decoded paths are raw bytes and may contain any character the file system -allows, including newlines. Quote them before pasting into a shell. - -Handle `YASL_EVENT_COMPOSITION` for immutable UTF-8 preedit snapshots, byte -selection offsets, and composition ending. Missing selection information uses --1 offsets. Insert only committed `YASL_EVENT_TEXT` into the document. Set the -candidate-window caret with `yasl_surface_set_ime_caret()` in device pixels. -X11 uses XIM preedit callbacks when offered; fallback input methods may provide -no inline composition. The old preedit getters remain migration helpers. - -## Popups and surface drags - -Create popups from a live parent, normally in response to input. All fields in -`yasl_popup_spec` use device pixels. `size` includes transparent margins; -`content_offset` and `content_size` describe the actual content rectangle used -for positioning. A zero content-size axis extends to the corresponding size -edge. `beside = true` prefers the anchor's right edge; false prefers below. -Placement may flip or slide to stay on screen. Wayland requires a recent input -serial to grab a popup, and older protocols may reject repositioning. Destroy -children before parents. The close callback requests application destruction; -it does not automatically free the popup. - -`YASL_EVENT_DRAG` handles a YASL surface drag; `YASL_EVENT_DROP_HOVER` handles -external data hover. Legacy callbacks remain migration adapters. `yasl_display_drag_start()` is intended for window/tab tear-off -workflows, not arbitrary MIME data sources. Keep transferred application state -in your own registry. Start from a recent pointer press, and check the boolean -result even when `YASL_CAP_DRAG` is present. Supply an optional copied image and -completion listener. One active drag is supported per display. A successful -start calls a non-NULL `end` exactly once, including explicit cancel or display -close; a failed start never does. `DROPPED` does not prove that another -application accepted your application object. - -Where `YASL_CAP_DRAG_CARRY` is present, `yasl_surface_drag_carry()` can attach a -toplevel to the active drag. Otherwise an image can represent it. With -`YASL_CAP_PLACE_WINDOW`, `yasl_surface_place_under_pointer()` requests placement -relative to the pointer. These calls may fail due to current input/drag state. -Completion can run synchronously during cancel/close; keep listener userdata -alive until then and do not destroy the display recursively. - -## Timers and file descriptors - -Include `<yasl/loop.h>`. Use `yasl_display_loop(display)` for application timers -so they participate in display dispatch. Intervals are nanoseconds; the -`YASL_MS_TO_NS(ms)` macro converts a nonnegative integer millisecond value. - -```c -static uint64_t tick(void *ud, yasl_timer_id id, uint64_t interval_ns) -{ - (void)id; - yasl_surface_schedule_redraw(ud); - return interval_ns; /* Return zero for a one-shot timer. */ -} -/* Check zero for failure. This timer is canceled with the surface. */ -yasl_timer_id timer = yasl_surface_add_timer(surface, YASL_MS_TO_NS(16), tick, surface); -``` - -A timer callback receives the scheduled interval, not actual elapsed time. A -nonzero return repeats/reschedules; zero retires it. Missed periods coalesce. -Timers can add/delete timers, including themselves. Loop timers end when retired, -deleted, or the loop is destroyed; surface timers also end on surface destruction. -Userdata remains application-owned. Delete surface timers with -`yasl_surface_del_timer()` and ordinary timers with `yasl_loop_del_timer()`. - -File watches use `<poll.h>` masks. `yasl_loop_add_fd()` returns a positive watch -ID or -1. Its callback receives `revents`, including error/hangup flags; its -integer return is ignored. Consume readiness or disable/delete the watch. -The loop never closes application file descriptors: delete the watch before -closing its descriptor. Ready callbacks may add or remove watches. Do not call -`yasl_loop_step()` or `yasl_loop_run()` recursively. - -Every backend's dispatch wakes when a loop descriptor becomes ready. Cocoa -waits for native events and mirrors the loop's descriptors in a kqueue while it -waits. If kqueue rejects a descriptor, Cocoa bounds the wait to 10 ms instead. - -Loop wake and post calls work from any thread. `yasl_loop_kick()` only wakes the loop, and it -is also safe in a signal handler. `yasl_loop_post()` queues a function to run -on the owning thread during a later step or dispatch. Use it to hand results -from worker threads to the loop: - -```c -static void results_ready(void *ud) -{ - worker_drain(ud); /* Runs on the loop thread. */ -} -/* On the worker thread; false means allocation failed. */ -yasl_loop_post(loop, results_ready, worker); -``` - -`yasl_loop_watch_signals()` delivers POSIX signals to a callback on the loop -thread. It installs a process-wide handler for each signal, with `SA_RESTART`, -and restores the previous action on unwatch. YASL leaves thread signal masks -alone: a watched signal must stay unblocked in at least one thread. Only one -loop can watch a given signal at a time. - -## Worker messages and window requests - -Capture `yasl_surface_id(surface)` on the display thread and pass that value to -workers. `yasl_display_post(display, &message)` accepts an identity instead of a -borrowed surface pointer. Zero targets the display. On success, optional -`message.release(payload)` runs exactly once on the display thread after delivery -or cancellation, including shutdown. It may post messages but must not poll, -dispatch, or destroy YASL objects. Failed posting leaves ownership with the caller. -Stop and join producers before closing the display. The same API works with a -manual loop and with the runner; `yasl_post_event()` is a legacy adapter. - -Toplevel requests `yasl_surface_set_size(surface, logical_size)`, -`yasl_surface_set_fullscreen`, `yasl_surface_set_maximized`, and -`yasl_surface_minimize` return acceptance with errno on failure. Confirm actual -geometry/state through queued events. Popup targets are invalid. Wayland cannot -request an arbitrary live size and returns ENOTSUP for that operation; fullscreen, -maximize, and minimize use xdg-shell requests. X11 and Cocoa implement all four. - -## Desktop services - -Include `<yasl/desktop.h>` for services that sit next to windowing. Display and -surface calls use the display thread; path and locale calls work from any -thread. - -| Call | Linux | Cocoa | -| --- | --- | --- | -| `yasl_display_open_uri()` | `xdg-open` from `PATH` | NSWorkspace | -| `yasl_display_notify()` | Notifications service over D-Bus | User notifications; app bundle required | -| `yasl_display_appearance()` | Settings portal over D-Bus, then `GTK_THEME` | Effective appearance and increase-contrast setting | -| `yasl_surface_bell()` | Wayland xdg-system-bell; X11 XKB bell | `NSBeep` | -| `yasl_executable_path()` | `/proc/self/exe` | Executable path, resolved | -| `yasl_resource_dir()` | Always zero | App bundle resources | -| `yasl_default_locale()` | `LC_ALL`, `LC_CTYPE`, `LANG`, then `C.UTF-8` | Current language and region | - -`yasl_display_open_uri()` checks only that the URI has a scheme. Decide which -schemes and hosts your application trusts before calling it, for example -before opening a link from untrusted text. On Linux the opener starts in its -own session with default signal handling and none of the application's -descriptors above stderr. D-Bus services need YASL built with `dbus-1`; without -it, notifications return false and appearance falls back to `GTK_THEME`. -Appearance changes send no notification, so query again when a window gains -focus. - -Functions that fill a buffer return the full length, excluding the NUL. A -result less than the buffer size means the string was written. Otherwise the -buffer is unchanged, so call again with a larger one: - -```c -char path[4096]; -size_t len = yasl_executable_path(path, sizeof(path)); -if (len == 0 || len >= sizeof(path)) { - /* No path, or a longer buffer is needed. */ -} -``` - -## Native interoperability - -Portable CPU drawing needs no native headers. Opt into `<yasl/native.h>` and -`<yasl/native/wayland.h>`, `<yasl/native/x11.h>`, or `<yasl/native/cocoa.h>` when -integrating a platform API. Typed native functions are exported only for -built backends. Accessors return borrowed handles and zero/NULL for the wrong -backend. Do not pass freed objects, destroy borrowed handles, independently -dispatch native connections, or change surface roles and buffer ownership. - -Application-managed native drawing currently supports Cocoa/Metal. Use -`yasl_surface_create_native()` with a non-NULL Metal device, which YASL retains. -During draw, query `yasl_surface_get_native_frame()` for a borrowed drawable, -encode and present with your renderer, then call `yasl_frame_present()` to -record submission. The drawable expires on successful present, callback return, -or surface destruction. Do not infer GPU completion from the boolean result. -Other backends return NULL with `errno = ENOTSUP` for native creation; having a -native window handle does not imply this rendering path is supported. +# Surface and input guide + +## Lifetime, threads, and errors + +The first successful `yasl_surface_create` or `yasl_gl_surface_create` initializes +shared native state. Subsequent surfaces share one backend. Optional `yasl_set_app_id` +and `yasl_set_backend` configure the next initialization; they fail after initialization. +Destroying the final surface preserves shared state. `yasl_shutdown` destroys remaining +surfaces, releases the backend, and restores defaults. Release application-owned GPU +objects before their surface or shutdown. Parent destruction destroys popup descendants. + +Creation, window operations, event pumping, and shutdown use the first creation's +thread, which must be the main thread on Cocoa. GL context binding/drawing/swapping +may use a rendering thread; detach its context before destroying the window. The +application synchronizes renderer state/geometry and joins workers before shutdown. +`yasl_wake` is thread-safe and does nothing before initialization or after shutdown. + +Constructors return NULL on failure; fallible mutations return bool. Event reads +return 1 for an event, 0 for empty/timeout/wake, -1 for failure. `yasl_get_error` +returns a stable per-thread error and static description; `yasl_get_native_error` +returns supplementary native diagnostics. Success does not clear a preceding error. +Valid-object size/scale/userdata getters return ordinary values. Destroyed pointers +are invalid. Strings passed to creation and setters are copied; userdata is borrowed. + +## Events and coordinates + +`yasl_poll_event` and `yasl_wait_event` process native events and consume one shared +FIFO. No manual dispatch/flush cycle. A failed/empty read leaves output unchanged. +Wait timeout zero polls, negative waits for work, positive milliseconds bound waiting. +A wake can return zero. Polling before initialization returns zero; waiting reports +NOT_INITIALIZED. Close and quit are requests; the application decides how to respond. +Display loss is a terminal native failure; destroy resources and shut down. + +Read the union member selected by type. Events own their text through +`yasl_event_release`, independently of later polls and source destruction. Release +every successful event exactly once; numeric events need no allocation to release. +Do not duplicate event ownership by shallow copying. Event surface pointers remain +borrowed and become invalid when their surface is destroyed. Undelivered events for +a destroyed surface are canceled. Sequence orders capture by YASL, not OS causality. +Timestamps use monotonic nanoseconds; native_time states whether normalized native +sample timing was available or capture time was used. + +Creation dimensions, pointer/pen coordinates, popup placement, caret, and CSD actions +use logical content units. Fractional positions use doubles. Image dimensions, +byte stride, framebuffer size, and damage use device pixels. Query actual framebuffer +extent for swapchain/viewport sizing; scale alone need not determine integer rounding. +Request functions report acceptance, while geometry/state events report actual results. +There is no portable absolute window-position promise. State's known mask distinguishes +reported false from unavailable state fields. + +## Rendering + +GPU creation (flags zero) does not create a device, context, or software framebuffer. +One presenter attaches to each surface. Incompatible second attachments fail. + +- Vulkan: inject vkGetInstanceProcAddr, query instance extensions and flags through + the live GPU surface, create the instance, then `yasl_vulkan_create_surface`. + The application owns VkInstance/VkDevice/queues/swapchains/VkSurfaceKHR, handles + image acquisition, synchronization and recreation, and presents with Vulkan. +- Metal: `yasl_metal_layer` returns a borrowed CAMetalLayer on Cocoa. YASL owns + the native view/layer and updates its dimensions. The app sets device/pixel format, + obtains drawables, and submits/presents commands. No additional YASL present call. +- GL: `yasl_gl_surface_create` accepts title, dimensions, flags, a per-call int + key/value attribute list terminated by YASL_GL_END, and an optional sharing surface. + NULL attributes default to GL3.2 core, RGBA8, depth24/stencil8. The surface owns + its context. Bind, load procedures, set swap interval, and swap explicitly. + GL API support depends on the native driver; Cocoa rejects OpenGL ES/debug contexts + and supports its native legacy/core profiles. +- CPU: create with YASL_SURFACE_CPU and call `yasl_cpu_present` with premultiplied + numeric 0xAARRGGBB, width/height, byte stride, and optional damage rows containing + x/y/width/height. Extent must match current framebuffer. NULL/zero damage means full; + explicit lists must be nonempty. Initial submission and changed extent require full + images. Later patches update the retained submitted image. Source bytes are copied + before return, so the app may reuse/free them. Native uploads may copy the entire + retained image, but never unsubmitted source changes. `yasl_cpu_read_pixels` reads + that submitted CPU image, not the composited screen. + +GPU creation/presentation uses native requests directly; YASL acknowledges Wayland +configuration before external GPU submission. Native refresh events can be handled +in the ordinary loop. An optional refresh callback permits drawing while the native +window system remains in a modal resize path. It runs only during pumping and never +recursively; it may query geometry and render, but must not pump, create/destroy +surfaces, or shut down. Geometry events may not yet have been consumed, so query +current geometry there. Without a hook, CPU exposes reuse committed pixels and GPU +redraw waits for the application's loop to resume. + +## Keyboard, pen, and scrolling + +Physical YASL_KEY names identify US keyboard positions independent of layout. +Layout labels support shortcuts; committed UTF-8 TEXT is for document insertion. +Keys and text need not correspond one-to-one. Input RESET/focus loss cancels held +application input. Keep pointer and pen streams separate to avoid duplicate strokes. + +PEN events carry connection-local device/tool IDs, tool type, proximity/contact, +button transitions, coherent position and axes. Pressure is 0..1; zero does not end +contact. Tilt uses degrees -90..90; rotation uses clockwise degrees; distance is +normalized, not a physical unit. Axis validity distinguishes unavailable from zero. +Changed masks indicate native updates. Tools/axes depend on hardware and backend; +IDs do not guarantee persistent hardware serials. Pen-generated mouse duplicates +are suppressed where recognized; do not treat tool class as a multi-contact API. + +SCROLL preserves wheel-line and precise-pixel values with validity, source, phase, +and momentum metadata where supplied. Applications choose layout pixels per wheel +line. X11 supplies native wheel/smooth valuator data where available. Missing native +metadata stays explicitly unavailable instead of being inferred. + +## Text input, clipboard, popups, and drags + +Activate text input for a text control with `yasl_surface_set_text_input`. Set purpose, +caret, and copied UTF-8 surrounding text/selection/revision through direct setters. +Snapshot offsets and TEXT_EDIT replacement ranges are UTF-8 bytes; COMPOSITION is +uncommitted preedit. Apply a matching-revision edit once, update your snapshot, and +ignore/rebase stale edits. Activation generation distinguishes controls. Ordinary key +text remains TEXT. Never insert preedit as committed document text. Wayland requires +text-input-v3 for native IME; absence is reported as unsupported. Cocoa translates +native UTF-16 replacement/deletion to snapshot UTF-8 ranges. X11 XIM supplies ordinary +text/preedit but cannot promise surrounding-text replacement support. + +Clipboard set copies UTF-8; read returns a nonzero request ID and later CLIPBOARD +completion with OK, UNAVAILABLE, or FAILED. Empty successful text is distinct from +failure. Destroying the target cancels transfer and delivery. Primary selection stays +independent and reports unsupported where unavailable. Linux external transfers time +out after 30 seconds; X11 currently lacks INCR for large transfers. File drops carry +URI-list text; `yasl_drop_next_path` decodes supported local paths into caller buffers. + +Popup arguments use logical parent anchor coordinates, anchor/gravity directions, +and flip/slide/resize constraint bits. Popup size includes its content. Destroying a +parent retires descendants too. Native grabs, CSD moves/resizes, menus, and surface +drags require a live triggering press event; unsupported/expired input fails explicitly. +Surface drags support application tab/window carry; they are not an arbitrary MIME +source API. Check operation results even when feature bits advertise support. + +## Backend limits + +Wayland support varies by compositor protocols; no portable absolute placement or +minimized-state query is assumed. X11 currently reports unit scale and lacks an +implemented relative-pointer lock. Cocoa requires macOS 14+; native Metal/GL and +Objective-C paths require validation on macOS. EGL/wayland-egl and Vulkan SDK support +are independently optional at build time. Feature bits describe implementation/ +server support, not guaranteed permission or attached hardware. The current work was +compiled and tested on Linux; macOS validation remains a separate required check. diff --git a/docs/migrating.md b/docs/migrating.md deleted file mode 100644 index 912ea4d..0000000 --- a/docs/migrating.md +++ /dev/null @@ -1,132 +0,0 @@ -# Migrating pre-release applications - -YASL 0.1 has no stable source or binary compatibility promise. Rebuild all -consumers against matching public headers and the library after updating. -See the [application guide](guide.md) for the full window and input workflow. - -## Display queue, frames, and application failures - -The runner now consumes the display-wide FIFO directly. Manual loops should -replace per-surface polling with `yasl_display_poll_event(display, &event)`. -Surface polling remains an input-only adapter that discards pending non-input -notifications delivered through legacy callbacks. Do not mix polling styles. Strings from either poll -now survive recipient destruction and expire at the next successful poll on -that display, or display close. Queue allocation failure is reported by dispatch -as an error instead of silently losing a lifecycle notification. - -Replace `struct yasl_frame_context` with `struct yasl_frame`, `frame->cpu.pixels` -with `frame->target.pixels`, and `frame->size` with `frame->target.size`. -`target.stride_bytes` replaces `cpu.stride_bytes`. Native drawing uses -`yasl_surface_get_native_frame(surface, &frame->target, &native)`. -`yasl_frame_present(frame, NULL, 0)` replaces the CPU presentation wrapper. -A non-NULL damage array with count zero now explicitly means no damaged pixels. -Lists larger than the backend limit become full damage instead of truncating. - -`YASL_APP_ERROR` and `yasl_app_exit(display, result)` report application failures -through `yasl_run`. `yasl_app_window(display)` gives init access to desc.window. -Use `yasl_surface_set_userdata` / `yasl_surface_userdata` for per-window state, -or `window.userdata` during creation. `yasl_surface_set_callbacks` now returns a -boolean and rejects runner-owned surfaces with EBUSY. - -Spell toplevel creation sizes `size_logical`; `size` remains an alias. -New window request functions report acceptance with errno. Pacing now works -outside the runner too, and returns a boolean instead of silently ignoring calls. - -Replace clipboard copy/paste with `yasl_clipboard_set` / `yasl_clipboard_request`. -Check the returned request ID and `event.paste.status` on completion. Empty text -is successful; missing data and transfer errors now have explicit outcomes. -Preedit snapshots arrive through `YASL_EVENT_COMPOSITION`; state and hover use -`YASL_EVENT_WINDOW_STATE`, `YASL_EVENT_DRAG`, and `YASL_EVENT_DROP_HOVER`. - -For worker messages, capture a surface ID on the display thread and use -`yasl_display_post` with an optional release callback. Do not also free the -payload in the event handler when release owns cleanup. Stop producer threads -before display shutdown; retired IDs reject posting with ENOENT. - -## Application callbacks - -All examples now use `<yasl/app.h>` and `yasl_run()`. Describe a single window -in `yasl_app_desc.window`, or create windows in `.init`; move input handling to -`.event` and drawing to `.frame`. -Replace manual dispatch/poll/flush loops with the runner and request redraws with -`yasl_surface_request_frame()`. Present the frame context with -`yasl_frame_present()`; the runner destroys remaining surfaces on exit. - -Window state remains a `YASL_STATE_*` bitmask; `YASL_STATE_TILED` is reported -for tiled Wayland toplevels. - -## Events - -The core API and the runner now share one `struct yasl_event`, declared in -`<yasl/yasl.h>`. `yasl_display_poll_event()` returns the same events that -`yasl_app_desc.event` receives. Legacy surface polling returns only input types. - -| Previous use | Replacement | -| --- | --- | -| `yasl_surface_input_poll()`, `struct yasl_input_event` | `yasl_surface_poll_event()`, `struct yasl_event` | -| `event.kind`, `YASL_INPUT_*` | `event.type`, `YASL_EVENT_*` | -| `YASL_INPUT_MOUSE` with `mouse.kind` | `YASL_EVENT_POINTER_DOWN`, `_UP`, `_MOTION`, `_SCROLL`, `_LEAVE` with `event.pointer` | -| Runner `YASL_EVENT_MOUSE_BUTTON` with `pressed` | `YASL_EVENT_POINTER_DOWN` / `_UP` | -| Integer `button` 0/1/2 | `YASL_BUTTON_PRIMARY`, `_MIDDLE`, `_SECONDARY`, `_BACK`, `_FORWARD` | -| `paste.text`, `drop_files.files`, `text.text` | `text.data` for `PASTE`, `DROP`, and `TEXT` | -| `YASL_INPUT_DROP_FILES` / `YASL_EVENT_DROP_FILES` | `YASL_EVENT_DROP` | -| `YASL_EVENT_WINDOW_FOCUS` | `YASL_EVENT_FOCUS` | -| `YASL_EVENT_WINDOW_SCALE_CHANGED` | `YASL_EVENT_WINDOW_RESIZE` with `resize.scale_changed` | -| `YASL_EVENT_WINDOW_CLOSE_REQUEST` | `YASL_EVENT_WINDOW_CLOSE` | -| `user.type` | `user.code` | -| `key.repeat` | `key.action == YASL_KEY_REPEAT` | -| `yasl_surface_set_mouse_mode()` | `yasl_surface_set_pointer_mode()` | - -Every event carries a monotonic `time_ns`. Pointer motion carries `delta` on -both paths. Handle `YASL_EVENT_INPUT_RESET` by clearing held input and active -strokes. - -## Window creation - -Rename `struct yasl_surface_spec` to `struct yasl_window_spec` and -`struct yasl_surface_cbs` to `struct yasl_window_cbs`. Surface handles and -functions retain their names. Supply the window's callback table in `spec.cbs` -and its userdata in `spec.ud` when calling `yasl_surface_create_toplevel()` or -`yasl_surface_create_native()`. Callback tables are copied, and userdata is -borrowed. The new window's callbacks cannot run before creation returns; read -initial geometry with `yasl_surface_geometry()` and request the first redraw -after initializing application state. - -`yasl_surface_set_callbacks()` remains available to replace callbacks after -creation and to configure popups. A NULL `spec.cbs` creates a window without -callbacks. Displays owned by `yasl_run()` use the runner's callbacks instead; -provide application notifications through `yasl_app_desc.event` and `.frame`. - -## Keyboard events - -| Previous use | Replacement | -| --- | --- | -| Linux keysym or Cocoa virtual keycode in `key.key` | Portable physical `YASL_KEY_*` identity | -| `key.base` | `key.native_code`, with explicit backend-specific encoding | -| `key.keypad` | Distinct `YASL_KEY_KP_*` identities | -| Advisory `key.text` character | `YASL_EVENT_TEXT` UTF-8 payload for insertion | -| Layout label from a keysym | Optional `key.codepoint` and `key.shifted` Unicode scalars | - -Replace magic numbers and X11 symbols with named keys. For example, a quit -command checks `key.key == YASL_KEY_ESCAPE` on a press. `YASL_KEY_A` means the -US A position on every layout; use layout labels when a command should follow -the printed letter instead. Do not cast key identities to characters. Keypad -Enter and ordinary Enter now have distinct identities without a separate flag. - -Text comes only from `YASL_EVENT_TEXT`. Handle its byte length, copy it when -retaining it after the next successful poll, and allow multiple characters per -commit. Cocoa sends key events through its text input manager; Wayland emits -ordinary XKB text and locale compose results in addition to IME commits. - -## Scroll events - -Replace `scroll_lines`, `scroll_delta`, and scroll button numbers with -`pointer.scroll.x` and `.y` for `YASL_EVENT_POINTER_SCROLL`. These double -values are fractional line offsets, positive right/down on every backend. -Do not infer scroll direction from `button`. One event may include both axes. -Remove backend-specific sign inversions and whole-line casts; multiply the -offset by your application's preferred pixels per line instead. - -X11 still supplies whole wheel steps. Wayland high-resolution wheels and smooth -offsets, and Cocoa precise scrolling, preserve fractions. Device acceleration -and system preferences still affect the native offsets. diff --git a/docs/testing.md b/docs/testing.md index bd3f2f0..ce2082f 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -1,156 +1,36 @@ -# Testing and examples - -Configure isolated Linux display-server integration tests with Weston and -Xwayland installed: - -```sh -meson setup build-test -Dintegration_tests=true -meson test -C build-test --print-errorlogs -``` - -The harness starts and cleans up its own servers. It checks explicit requests, -automatic selection, fallback, both backends in one process, frame lifetimes, -callback copying, clipboard ownership, and timers. It also runs the beginner -and tablet examples on both backends. On macOS the option enables native window, -input ownership, keyboard/text/scroll, cursor, tablet, and example tests; these -require a desktop session. Linux results do not verify Cocoa behavior. - -For memory/undefined-behavior checks use a separate build: - -```sh -meson setup build-sanitize -Db_sanitize=address,undefined -Dintegration_tests=true -meson test -C build-sanitize --print-errorlogs -``` - -Default tests need no desktop connection. C++ header coverage is built when a -C++ compiler is available. On Linux and macOS, the shared-export test ensures -only public API declarations are exported. Test disabled-backend configurations separately with -`-Dwayland=disabled` or `-Dx11=disabled`; the combined integration harness needs -both backends. - -Keyboard tests check physical identities, distinct keypad/modifier keys, layout -labels, Shift/Caps Lock, shortcut text suppression, AltGr, dead-key composition, -repeat, and composition reset. The X11 integration test changes and restores -the isolated server's keymap to verify that labels change while physical keys -retain their identity. The X11 text fallback is also checked without an input -method, including Latin-1, Cyrillic, and non-BMP characters encoded as UTF-8. -Scroll tests cover both axes, fractional Wayland offsets, -duplicate protocol representations, and X11 core/XInput wheel directions. -Cocoa integration tests cover UTF-16 labels, modifier-only events, native text -commit routing, and diagonal precise scrolling. Native IME interaction and -hardware gestures still need testing on the target desktop. -Clipboard tests cover Latin-1 interoperability, empty transfers, and rejection -of malformed UTF-8 on Linux. - -## Cocoa validation - -On macOS 14 or newer, use a logged-in desktop session with a Metal-capable -device. Configure `-Dintegration_tests=true`, compile, and run the full suite. -Cross-compiling with an Apple SDK checks compilation and linking; run -`meson test -C build-macos public-exports` to check the dylib's exports on the -build machine. A cross file should provide an `nm` that can read Mach-O, such -as `llvm-nm`. These checks do not execute the native window and input tests. - -To move an already compiled, unsanitized Cocoa suite to another Mac without -rebuilding it: - -```sh -python3 tests/helpers/package_cocoa_tests.py build-macos cocoa-tests.tar.gz -``` - -On the matching Mac, extract the archive and run: - -```sh -tar -xzf cocoa-tests.tar.gz -python3 yasl-cocoa-tests/run.py -``` - -The bundle includes the dylib, every executable test registered by Meson, and -the license. The build-machine export check is separate. The runner checks file -hashes, enforces each test's timeout, saves output under `logs/`, and writes -`results.json`. A nonzero exit means validation failed. `--list` lists the -bundled tests without running them. Use Python 3.8 or newer and a bundle matching -its architecture; Apple Silicon uses `aarch64`, Intel uses `x86_64`. - -## Tablet drawing example - -Build and run the same C application on Wayland, X11, or Cocoa: +# Tests ```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 +meson test -C build --print-errorlogs ``` -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 `<yasl/app.h>` and the C -standard library. It uses `yasl_run()`, frame callbacks, and unified input events -on every backend. Tablet button and motion events retain `device`, which is -`YASL_PTR_DEV_PEN` or `YASL_PTR_DEV_ERASER`; their `pen` member 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_EVENT_INPUT_RESET` cancels a stroke. -Stationary pressure changes arrive as motion events and update the brush too. - -The example uses portable physical keys: C and Space clear, Escape exits. -Letter names describe US positions, so the clear key follows the C position -when the user selects another layout. +Tests cover public static/shared C and C++11 consumers, exact shared exports, +queue ownership/order/destruction, UTF-8, GL attributes, private native event loops, +clipboard, IME editing, pointer/pen coherence, cursor, and popup positioning. Native +unit fixtures deliberately use private implementation headers; applications must use +installed public headers. -`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: +For private Linux display servers and actual GPU presentation, install Weston, +Xwayland, EGL/wayland-egl, and Vulkan/Mesa software drivers, then: ```sh -meson configure build -Dx11_tablet_tests=true -meson test -C build tablet-x11-native --print-errorlogs +meson configure build -Dintegration_tests=true +meson compile -C build +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 +The integration harness starts its own headless GL Weston and Xwayland, runs public +surface ownership/CPU damage/wakeup/refresh checks, GL drawing/readback/context-sharing/ +resize, Vulkan swapchain clear/present, and example smoke tests. It selects Mesa +software rendering and Lavapipe when available. It needs permission to create local +Unix sockets. A sandbox blocking those sockets is not a backend test result. -`yasl-benchmark` measures queue, timer, and surface-operation costs. Results -depend on the build, backend, and display server; they do not measure confirmed -screen presentation latency. - -```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 -``` +`-Dx11_tablet_tests=true` additionally uses Xorg dummy/inputtest drivers for pen input +without physical hardware. These are separate from the compositor-free native unit +fixtures. Cocoa integration tests run only on macOS and need the native SDK/display. +Linux results do not validate Cocoa compilation or runtime. +For memory/undefined-behavior checks use a separate build with `-Db_sanitize=address,undefined` +and run the same suites. Keep sanitizer builds isolated from the normal build. |
