# 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. ## Window creation and callbacks All examples include `` 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->cpu.pixels` in `frame`. Present with `yasl_frame_present_cpu(frame, NULL, 0)` and return `YASL_APP_QUIT` from an event handler to exit. The runner handles dispatch, input polling, 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 input exactly as `yasl_surface_poll_event()` returns it, plus `YASL_EVENT_WINDOW_RESIZE`, `YASL_EVENT_WINDOW_CLOSE`, and `YASL_EVENT_USER`. 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, logical size, 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`. 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_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_poll_event()` 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.type`; `time_ns` is a monotonic timestamp on the `yasl_loop_now_ns()` clock. Polling returns only input types, `YASL_EVENT_KEY` through `YASL_EVENT_INPUT_RESET`. Text, paste, and file-drop payloads share `event.text`: 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_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 ``. 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. Track those transitions; other types leave `button` meaningless. `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_display_copy()` copies a NUL-terminated UTF-8 string during the call. `yasl_display_paste()` requests delivery to the original target as `YASL_EVENT_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_EVENT_DROP`. `text.data` 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 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_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.