diff options
| author | Berke Güzel <wenekar1@gmail.com> | 2026-10-12 22:12:02 +0300 |
|---|---|---|
| committer | Berke Güzel <wenekar1@gmail.com> | 2026-10-12 22:12:02 +0300 |
| commit | 94c5e361fbb229b0cd6853e2102a7d95f1f358b3 (patch) | |
| tree | 3c0f11af9dcb84dac8067a31a6979acd6dd56fbf /docs/guide.md | |
| parent | 08dc9047098d6d96791024ac7d8f5837633673ef (diff) | |
better public interface and doxygen
Diffstat (limited to 'docs/guide.md')
| -rw-r--r-- | docs/guide.md | 298 |
1 files changed, 298 insertions, 0 deletions
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 `<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.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 `<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. + +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 `<yasl/native.h>` and +`<yasl/native/wayland.h>`, `<yasl/native/x11.h>`, or `<yasl/native/cocoa.h>` 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. |
