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
|
# Public API redesign
This is the implementation contract for the pre-release API revision. The
portable layer owns event ordering, queued payloads, surface identities, and
frame validation. Backends translate native behavior into that contract.
## One event stream
`yasl_display_poll_event(display, &event)` consumes one display-wide FIFO. Input,
window resize/close/state, drag hover/drop, composition, clipboard results, and
display quit/reopen/loss use the same `yasl_event`. Events have an increasing
`sequence` assigned when YASL queues them, and a monotonic `time_ns`. Sequence
orders capture by YASL, not unknowable ordering between OS threads or devices.
The runner consumes this same queue; it does not drain separate queues by window.
Event strings are copied on enqueue and borrowed until the next successful
display poll or display close, even if the recipient is destroyed. Destroying a
surface removes its undelivered events. A callback may destroy its recipient;
it must stop using that surface. Draws remain synchronous to satisfy native
live-resize rules; the runner drains queued events before invoking a frame.
Legacy surface polling consumes input from the same queue and discards pending
non-input notifications, which were already delivered through legacy callbacks.
Applications must choose one polling style; discarding prevents idle spin and
unbounded retention in old manual loops. Legacy window
callbacks remain for migration, but applications should choose queue delivery
and a draw callback. Backend notifications must queue before calling a legacy
callback, and must not access a surface after a callback that can destroy it.
## One frame contract
Both manual drawing and `yasl_app_desc.frame` receive `struct yasl_frame`.
Its `target` describes CPU pixels (NULL for native rendering), device-pixel size,
and row stride. The frame also carries frame index, scale, and an estimated
animation timestamp. Native drawables are queried through `<yasl/native.h>`;
there is no untyped GPU layer or public internal pointer in the portable frame.
`yasl_frame_present(frame, rects, count)` submits the exact active frame once.
NULL with zero count means full damage; a non-NULL array with zero count means
no damage. NULL with nonzero count and negative extents fail with EINVAL.
More than the backend rectangle limit falls back to full damage after validating
every rectangle; damage must never be silently lost. Targets expire at callback
return. Pixels must always describe a complete frame.
`yasl_surface_request_frame` is the normal coalescing redraw operation.
`yasl_surface_set_pacing` returns success/failure and works with either loop
style. Continuous drawing uses native pacing where available and a refresh-based
timer otherwise. On-demand surfaces require explicit invalidation.
## Identities, posting, and shutdown
`yasl_surface_id(surface)` returns a nonzero identity unique within its display.
`yasl_display_post(display, &message)` is thread-safe; messages target an identity
(zero means display-wide), never a borrowed surface pointer. The display must
remain alive during posting, and producer threads must stop before display close.
After a successful post the optional release callback runs exactly once on the
display thread, after consumption or cancellation (including surface destruction
and shutdown). A failed post leaves payload ownership with the caller. Posting
to a retired identity fails. The display queue owns only its copy of the message;
the release callback defines payload cleanup. Release may post, but must not
poll, dispatch, or destroy YASL objects. Legacy `yasl_post_event` adapts to
this mechanism, with the caller responsible for its borrowed target lifetime.
Application callbacks may return CONTINUE, QUIT, or ERROR. ERROR makes
`yasl_run` return failure after cleanup. `yasl_app_exit(display, result)` permits
timers, fd callbacks, and frames to request the same orderly termination.
The optional initial window is available through `yasl_app_window(display)`.
Surface userdata is independent of legacy callback userdata and accessible
through `yasl_surface_set_userdata` / `yasl_surface_userdata`.
## Window requests and units
New code spells toplevel creation size `size_logical`; `size` remains an alias
for migration. Framebuffer geometry, pointer coordinates, popup placement,
damage, IME carets, and drag-image coordinates are device pixels as documented
on their structures. Conversion helpers remain explicit.
`yasl_surface_set_size(surface, logical_size)`,
`yasl_surface_set_fullscreen(surface, enabled)`,
`yasl_surface_set_maximized(surface, enabled)`, and
`yasl_surface_minimize(surface)` return whether the native request was issued.
They set errno on failure: EINVAL for invalid input/non-toplevel surfaces,
ENOTSUP for unsupported requests, or an OS error. Success is acceptance of a
request, not a promise that the window manager honored it. Resulting size/state
is reported through events. No portable absolute window-position setter exists.
## Clipboard and composition
`yasl_clipboard_set(display, selection, text)` returns whether a copied UTF-8
offer was issued, with errno on failure. `yasl_clipboard_request(surface,
selection)` returns a nonzero request ID, or zero with errno on failure.
The backend operation `display_request_paste(d, selection, surface, id)` returns
true only after accepting responsibility for one completion. It completes via
`yasl_surface_complete_paste(surface, id, status, text, len)`; status is
YASL_TRANSFER_OK, UNAVAILABLE, or FAILED. Results use YASL_EVENT_PASTE and its
`.paste` payload (data/len/request_id/status); legacy `.text` aliases data/len.
Surface destruction cancels delivery and native transfers. A backend must
complete errors as well as success, including empty text. Request IDs are
monotonic per display. Legacy paste uses ID zero. External Linux transfers time out after 30 seconds.
YASL_EVENT_COMPOSITION carries copied UTF-8 preedit text, byte selection offsets
(both -1 if unavailable), and an active flag. Each native change queues a
snapshot, including composition ending; committed text remains YASL_EVENT_TEXT.
The query functions remain migration helpers.
## Backend boundary
Common helpers declared in `common/backend.h`:
* `yasl_surface_notify_configure(s, geometry)`
* `yasl_surface_notify_close(s)`
* `yasl_surface_notify_state(s, state)`
* `yasl_surface_notify_drag(s, event, position_px)`
* `yasl_surface_notify_drop(s, event, position_px)`
* `yasl_surface_notify_composition(s, text, begin, end)` (NULL/empty ends it)
* `yasl_surface_draw(s, frame)` (fills frame metadata and invokes draw)
* `yasl_display_notify_event(d, YASL_EVENT_DISPLAY_QUIT/REOPEN/LOST)`
* `yasl_surface_complete_paste(s, request_id, status, text, length)`
Backends keep using their existing input_queue; a new `surface_input_queue`
operation exposes that queue to common code for attaching its event publisher.
Backends must replace direct lifecycle/draw/drag callbacks with these helpers,
without weakening their native destruction guards. They implement optional
surface_set_size/fullscreen/maximized/minimize and display_copy_text /
display_request_paste operations. Common wrappers validate arguments and
report unsupported operations uniformly.
Linux backends are compiled and tested independently and together. Cocoa changes
can be inspected here but require macOS compilation/runtime verification.
## Validation
The combined Linux configuration passes all 26 Meson tests, including isolated
Weston/Xwayland integration, runner error/cleanup paths, event ordering,
clipboard outcomes, composition, and destruction during callbacks. Separate
Wayland-only and X11-only builds pass their unit suites and the final public
export, C++11, and event-contract checks.
AddressSanitizer and UndefinedBehaviorSanitizer pass the common contract tests
and the isolated backend integration suite. Leak detection was disabled because
LeakSanitizer is unavailable in this execution environment. Doxygen builds with
warnings treated as errors. A staged install links both shared and static
consumers using only the installed headers and pkg-config metadata.
Cocoa code and native regression tests were updated and reviewed, but were not
compiled or executed: macOS validation remains required. Existing X11 INCR and
Wayland arbitrary-size limitations are reported explicitly rather than hidden.
|