summaryrefslogtreecommitdiff
path: root/docs/guide.md
blob: 1bd1aef17aa1040f5e7b9639dde7cb32adeece7b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
# 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 `<yasl/app.h>` and call `yasl_run()` with a
`yasl_app_desc`. Create surfaces in `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.

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_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 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.