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
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
|
# 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 `<yasl/app.h>` 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 `<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.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 `<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.
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 `<yasl/desktop.h>` 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 `<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_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.
|