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 --- docs/api-policy.md | 37 ++++++ docs/doxygen_filter.py | 24 ++++ docs/examples.dox | 12 ++ docs/getting-started.md | 118 +++++++++++++++++++ docs/guide.md | 298 ++++++++++++++++++++++++++++++++++++++++++++++++ docs/migrating.md | 40 +++++++ docs/testing.md | 156 +++++++++++++++++++++++++ 7 files changed, 685 insertions(+) create mode 100644 docs/api-policy.md create mode 100644 docs/doxygen_filter.py create mode 100644 docs/examples.dox create mode 100644 docs/getting-started.md create mode 100644 docs/guide.md create mode 100644 docs/migrating.md create mode 100644 docs/testing.md (limited to 'docs') diff --git a/docs/api-policy.md b/docs/api-policy.md new file mode 100644 index 0000000..d5c7159 --- /dev/null +++ b/docs/api-policy.md @@ -0,0 +1,37 @@ +# Public API policy + +YASL 0.1 is pre-release. The installed headers 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 installed headers. +Native backend headers and accessors are available only for built backends. + +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 package version is available through +`pkg-config --modversion yasl`. + +See [migration notes](migrating.md) for the portable keyboard and fractional +scroll API changes. + +API changes should keep ordinary CPU drawing usable with only `` +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 installed-package consumers 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. diff --git a/docs/doxygen_filter.py b/docs/doxygen_filter.py new file mode 100644 index 0000000..ed6e6e8 --- /dev/null +++ b/docs/doxygen_filter.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +"""Resolve repository source links to Doxygen reference/example pages. + +Markdown keeps ordinary relative links for repository and installed-document +readers. Doxygen gets @ref links so its standalone HTML has no source-tree URLs. +""" + +from pathlib import Path +import re +import sys + +text = Path(sys.argv[1]).read_text(encoding="utf-8") + + +def reference(match): + label, target = match.groups() + if target.startswith("../include/yasl/") and target.endswith(".h"): + return f"[{label}](@ref {Path(target).name})" + if target.startswith("../examples/") and target.endswith(".c"): + return f"[{label}](@ref {Path(target).name})" + return match.group(0) + + +sys.stdout.write(re.sub(r"\[([^\]]+)\]\(([^)]+)\)", reference, text)) diff --git a/docs/examples.dox b/docs/examples.dox new file mode 100644 index 0000000..8e9d06a --- /dev/null +++ b/docs/examples.dox @@ -0,0 +1,12 @@ +/** @example hello.c + * A complete beginner window using CPU pixels, checked creation/presentation, + * and ordered shutdown. Run with --smoke-test to present once and exit. + */ + +/** @example simple_window.c + * Interactive pointer and tablet samples over a CPU-rendered gradient. + */ + +/** @example tablet.c + * Retained drawing content with pressure, tilt, eraser input, and scale changes. + */ diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000..ff40a9b --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,118 @@ +# 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. + +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` | +| Cocoa | macOS 14+ SDK with AppKit, Metal, and QuartzCore; Metal-capable device | + +```sh +meson setup build +meson compile -C build +./build/yasl-hello +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. +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. + +The complete beginner example is [hello.c](../examples/hello.c). It checks +construction failures, fills every pixel, presents inside `draw`, drains input, +responds to quit/close/lost, and destroys resources in order. 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. + +## Link an application + +Install the library and public headers into your chosen prefix: + +```sh +meson setup build --prefix=/usr/local +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, and your compiler, pkg-config, and runtime loader +must search it. For a custom prefix, set `PKG_CONFIG_PATH` to its pkg-config +directory and configure the loader for its library directory. Static linking +uses `pkg-config --static --libs yasl` to include backend dependencies. + +A Meson consumer uses the installed dependency: + +```meson +project('my-app', 'c', default_options: ['c_std=c23']) +yasl = dependency('yasl', version: '>=0.1.0') +executable('my-app', 'main.c', dependencies: yasl) +``` + +YASL also declares `meson.override_dependency('yasl', ...)`, so the same consumer +can use a vendored Meson subproject through a fallback dependency. Public headers +can be included from C++11 or newer; the library itself builds as C23. + +## The application cycle + +1. Open a display with `yasl_display_open(NULL, &spec)`. NULL creates a loop owned + by the display. Supply your own loop only when you need shared loop ownership. +2. Create a toplevel with a positive **logical-pixel** size and install callbacks. + Zero-initialize every option and callback struct. Callback tables are copied; + userdata must remain alive. Registration can call back synchronously. +3. Schedule the initial redraw. `draw` receives a borrowed target. Fill every + pixel with premultiplied `0xAARRGGBB`, then present that exact target, once. +4. Dispatch, drain input for every live surface queue, update application state, + and flush. Flush runs scheduled drawing and sends native requests. Dispatch + may also invoke callbacks, including drawing during native live resize. +5. Destroy popups before parents, surfaces before displays, and displays before + any supplied loop. NULL destroy/close calls do nothing; freed pointers are + invalid for every operation. + +```c +while (running) { + if (yasl_display_dispatch(display, -1) < 0) { + perror("yasl_display_dispatch"); + break; + } + struct yasl_input_event event; + while (yasl_surface_input_poll(surface, &event)) { + /* Read the union member selected by event.kind. */ + } + yasl_display_flush(display); +} +``` + +Zero timeout polls, positive timeout bounds the wait in milliseconds, and a +negative timeout waits for work. Undrained input makes dispatch return without +waiting. Use the return value only to detect failure, not to count input events. +If callbacks destroy surfaces, update your application registry immediately and +only poll surfaces that are still alive. Arrange display/loop cleanup after the +cycle ends; destroying them inside a callback is unsupported. + +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. diff --git a/docs/guide.md b/docs/guide.md new file mode 100644 index 0000000..43a7a1e --- /dev/null +++ b/docs/guide.md @@ -0,0 +1,298 @@ +# Application guide + +The contracts in [yasl.h](../include/yasl/yasl.h), [loop.h](../include/yasl/loop.h), +[timing.h](../include/yasl/timing.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. + +## Geometry and scaling + +| Value | Units | +| --- | --- | +| Toplevel `yasl_surface_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_schedule_redraw()` 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; + struct yasl_damage_list damage = {.n = -1}; + if (!yasl_surface_present(surface, &frame->target, &damage)) { + /* Arrange application error handling after returning. */ + } +} +``` + +Present that exact target at most once during draw. Do not change its pointer, +size, or generation, free it, or use it after callback return or surface +destruction. A successful present consumes it. Invalid input returns false +without consuming it. `damage.n = -1` means full target; `0` means no damaged +rectangles; positive `n` uses the first `n` entries in `rects`. Extents must be +nonnegative, and the core clips them to the target. `bounds` is a reserved hint +and can be zero. Damage may reduce uploads, but every target must still contain +complete valid content; backends may upload all pixels. + +To animate, schedule again from draw. With `YASL_CAP_FRAME_PACING`, the backend +uses display/compositor signals to limit draws; tearing can bypass this. X11 +is unpaced: scheduling continuously will use CPU continuously. Use an application +timer to limit redraws there. Suspended/hidden windows need not draw at each +requested interval. + +`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_surface_input_poll()` only drains it and +never waits. Poll every live surface queue until empty. Wayland routes popup +pointer and keyboard events through the root parent's queue; inspect +`event.surface` to identify the recipient. Do not assume it equals the object +whose queue you polled. + +Read the union member selected by `event.kind`. The `reserved` field is internal +and must not be interpreted. Text, paste, and file-drop payloads are UTF-8 byte +strings, NUL-terminated; `len` excludes the NUL. They remain valid until the +next **successful** poll on the queue or queue destruction. Dispatch, flush, +and unsuccessful polls do not invalidate them. Copy data to keep it longer. + +Use `YASL_INPUT_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 ``. 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.kind == YASL_INPUT_KEY && event.key.action == YASL_KEY_PRESS && + event.key.key == YASL_KEY_ESCAPE) { + running = false; +} +/* Insert event.text.text[0..event.text.len) only for YASL_INPUT_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 `YASL_INPUT_MOUSE`, with `device` distinguishing mouse, touch, +pen, and eraser. Press/release button 0 is primary/tip, 1 middle, and 2 secondary. +Track those transitions; motion/leave button values are informational. 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_PTR_SCROLL`, read `mouse.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_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_display_copy()` copies a NUL-terminated UTF-8 string during the call. +`yasl_display_paste()` requests delivery to the original target as +`YASL_INPUT_PASTE`; destroying the target cancels delivery. There is no transfer +ID or error callback, so unavailable or failed transfers may produce no event. +Wayland selection offers can require a recent input serial. X11 currently does +not implement incremental (`INCR`) selection transfers and may reject large +transfers. X11 converts legacy `STRING` data between Latin-1 and UTF-8; it only +offers that legacy format when the copied text fits Latin-1. Malformed UTF-8 +clipboard transfers are discarded. Unsupported primary selection does nothing +and never aliases the clipboard. Cocoa uses a YASL named pasteboard to emulate primary selection; +this is not a desktop-wide macOS select-to-copy convention. + +External file drag hover uses the surface `drop` callback; actual file URLs +arrive as `YASL_INPUT_DROP_FILES`. `files` is a `text/uri-list` string separated by +CRLF, not a list of native paths. Parse URL encoding, skip blank/comment lines, +and decide which schemes/hosts your application accepts before opening files. + +Use `yasl_surface_ime_preedit()` to draw the uncommitted composition. The borrowed +pointer expires on the next dispatch or surface destruction; copy to keep it. +Set the candidate-window caret with `yasl_surface_set_ime_caret()` in device +pixels. X11 has no inline preedit. Cocoa supplies preedit text but currently no +cursor range. On Wayland, `yasl_surface_ime_preedit_cursor()` returns UTF-8 byte +offsets when a range is available. A false result leaves outputs unchanged. + +## 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. + +The surface `drag` callback handles a YASL surface drag; `drop` handles external +data hover. `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 ``. 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 `` 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. + +Cocoa dispatch waits for native events and checks the loop nonblockingly +afterward. An application FD alone does not currently wake an indefinite Cocoa +dispatch; use a bounded timeout when integrating such sources. `loop_kick()` +signals the loop pipe but is not a portable cross-thread event-posting API. + +## Native interoperability + +Portable CPU drawing needs no native headers. Opt into `` and +``, ``, or `` when +integrating a platform API. Typed headers and functions are installed 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_surface_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. diff --git a/docs/migrating.md b/docs/migrating.md new file mode 100644 index 0000000..d224cd4 --- /dev/null +++ b/docs/migrating.md @@ -0,0 +1,40 @@ +# Migrating pre-release applications + +YASL 0.1 has no stable source or binary compatibility promise. Rebuild all +consumers against the matching installed headers and library after updating. +The following changes replace backend-specific input fields with a common +contract. See the [application guide](guide.md) for the full input workflow. + +## 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_INPUT_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_INPUT_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 +`mouse.scroll.x` and `.y` when `mouse.kind == YASL_PTR_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 new file mode 100644 index 0000000..d01b82c --- /dev/null +++ b/docs/testing.md @@ -0,0 +1,156 @@ +# 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 installed 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: + +```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. + +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. + +`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 +``` + +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-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 +``` + -- cgit v1.2.3