From 76073fe5fad8799e5850805f2f090af2cb62c741 Mon Sep 17 00:00:00 2001 From: Berke Güzel Date: Tue, 13 Oct 2026 23:41:31 +0300 Subject: 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 --- docs/guide.md | 617 ++++++++++++++-------------------------------------------- 1 file changed, 141 insertions(+), 476 deletions(-) (limited to 'docs/guide.md') 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 `` 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 ``. 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 ``. 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. - -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 `` 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 `` 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_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. -- cgit v1.2.3