AtlatestRepositorysigil-wm
1
# sigil-wm API design3
Proposed API for review. sigil-wm is a Wayland window-management library built4
on wlroots. C owns protocol objects, scene presentation and input transport;5
Sigil chooses all window-management behavior. The core has no implicit layout,6
focus, keybindings, decoration style or output arrangement.8
## Model and transactions10
`(sigil wm host)` provides `start-host!`, `stop-host!`, `on-event!`,11
`remove-handler!` and `host-snapshot`. A host owns outputs, seats, devices and12
surfaces. Window, layer and popup roles expose copied metadata, parent relations,13
size hints and protocol requests. Handles contain a host identity, kind and14
monotonic ID; they never contain borrowed native pointers. Events carry a15
sequence number and relevant object/request generation.17
`(sigil wm model)` supplies immutable records and explicit tree operations:19
| Type | Meaning |20
| --- | --- |21
| `rect` | Logical x, y, width and height in global output-layout coordinates |22
| `wm-state` | Outputs, workspaces, nodes, seat focus and application-owned data |23
| `workspace` | Stable ID and ordered roots; visibility is an explicit choice |24
| `container` | Stable ID, ordered children and opaque layout/state data |25
| `view` | Reference to a window handle and application-owned data |26
| `placement` | Surface, client rect, clip, visibility, stack position and explicit protocol state |27
| `scene-plan` | Placements, pixel nodes and explicit keyboard/pointer focus targets |29
`make-wm-state`, `make-workspace`, `make-container`, `make-view`, `attach-node`,30
`detach-node`, `reparent-node`, `order-children` and `set-seat-focus` return new31
state. Detached snapshots contain ordinary Sigil data and a model revision.32
Removing a node does not choose focus, collapse containers or migrate workspaces.33
Those operations belong to the manager, including its response to output loss.34
The model can retain disconnected views while no output exists.36
`(sigil wm layout)` defines `arrange`: a manager-supplied procedure receives a37
container, logical rect, snapshot and trigger, returning placements and next38
layout state. Nested containers compose by calling their child arrangers.39
`make-scene-plan` and `validate-plan` assemble/check the result; the manager40
chooses which workspaces, layers and decorations to include. Desired placement41
is distinct from acknowledged configuration and committed client geometry.43
`(sigil wm scene)` provides `submit-plan!`, returning a transaction token or a44
queue-full result. A plan explicitly supplies stacking, visibility, client boxes,45
activation/fullscreen/tiled states and seat focus (including no focus).46
Decorations are pixel nodes; Sigil computes their geometry separately from47
client boxes. C validates the complete plan before accepting it, sends protocol48
configures and applies the scene atomically after acknowledgements or the49
caller-specified bounded deadline. It reports accepted, applied or rejected,50
with a serial and reason. Until application the previous scene remains active;51
destroyed or lock-ineligible targets cannot acquire focus. Cross-output moves52
belong to one transaction. Buffer retention and configure bookkeeping stay in C.54
Other decisions use typed requests and explicit commands in `(sigil wm host)`:55
`configure-output!`, `configure-device!`, `set-decoration-mode!`,56
`set-pointer-route!`, `set-cursor!`, `set-idle-state!` and57
`reply-request!` (accept/reject plus request-specific values). Sigil selects58
output mode/scale/position, keymap/repeat, device settings, layer assignment and59
exclusive zones, popup placement, activation, selection/drag admission and60
pointer-constraint eligibility. It also selects protocol exposure and request61
deadlines at startup. Unanswered requests remain pending until their declared62
deadline, then fail closed. Backend tests, rollback, serial checks, constraint63
enforcement and authenticated session-lock isolation remain native invariants.64
There is no policy command to unlock a session.66
The examples will demonstrate three uses of the same model:68
- **Stacking:** an ordered root contains views with remembered overlapping69
rectangles. Sigil chooses raise/focus and registers motion handling for drags.70
- **Tiling:** recursive containers partition a rect; Sigil chooses splits,71
proportions, gaps and neighbor traversal, then submits every child box.72
- **Tabbed:** a container retains all children; Sigil shows the selected view,73
hides its peers and draws clickable title headers using `(sigil wm draw)`.75
No container kind triggers special native behavior. Focus history, floating76
membership, tab selection and fullscreen restoration are Sigil state.78
## Input hooks and emulation80
`(sigil wm input)` provides `set-key-hook!`, `set-button-hook!` and81
`set-motion-hook!`. An unrestricted key hook receives every press and release,82
including modifier keys, as a copied `key-event`: token, seat/device, time, keycode,83
keysyms, modifiers, focus/lock generation and origin. It returns `(forward event)`,84
`(replace event)` or `(drop)`. A hook can dispatch a Sigil command and drop its85
triggering key. Replacement may change the keycode and modifier state; routing86
identity, time ordering and lock generation are immutable. The manager can87
publish resolved eligibility rules with `set-key-intercepts!` and88
`set-button-intercepts!`. Nonmatching input is delivered natively without89
waiting for the scheduler. The cache contains chord and target predicates;90
commands, binding resolution and action selection remain in Sigil.92
C queues a token and continues its owner loop. Sigil runs the hook outside93
native callbacks and replies asynchronously. C delivers replies in seat order,94
tracks forwarded/replaced press identities and balances their releases. Release95
hooks observe the event but cannot emit an unmatched or differently remapped96
release: delivered presses always receive their paired release, even if its97
hook drops or fails; dropped presses suppress their releases. Buttons use the98
same pairing rules. Physical and client-visible modifier state are tracked99
separately. A hook exception drops a new press and reports an error. Replies100
carrying stale focus/lock generations are rejected, with held-state cleanup.102
Decision replies share the 1024-entry command ring with configuration, scene103
and focus commands. When it fills, Sigil retains the computed reply and sleeps104
cooperatively for 1 ms between enqueue attempts, without repeating the hook.105
Keeping the reply in this FIFO preserves earlier hook commands, including focus106
authorization. A reserved queue or priority lane could overtake those commands.107
Replies also wait while the native receiver is starting. Retry ends when the108
reply is accepted or the receiver has shut down; shutdown cannot leave an active109
intercepted input waiting for that reply. `wm_send` retains its boolean contract;110
`wm_input_reply` distinguishes accepted, full, starting and stopped results.111
`INPUT_REPLY` diagnostics include the case, wait/drop action, queue depth, input112
kind and native state. Repeated failures are limited to one line per second for113
each case and kind, with the suppressed attempt count included on the next line.115
Intercepted decisions are observed after 32 ms, with at most one 10 ms timer116
interval of granularity. An unanswered intercepted press is logged and keeps117
waiting for policy, without exposing a manager chord to a client. Ordinary118
nonmatching keys never enter this queue or lose input to its budget. Delivered119
presses retain balanced releases. Hooked motion expires to native transport.120
Queued motion from the same device and generation is coalesced without crossing121
key/button ordering barriers. Input queue exhaustion reports a recoverable122
fault and cancels queued intercepted tokens, balancing their releases while123
leaving ordinary native input untouched. Transport resync also cancels tokens124
whose copied events were discarded, so a lost token cannot wait indefinitely.125
While locked, mapped lock surfaces acquire native keyboard focus and physical126
ordinary keys reach the lock owner without a policy round trip. Explicitly127
cached lock-eligible chords retain their manager hook. Virtual input remains128
refused, including its idle-activity notifications.130
Without a motion hook, motion stays entirely in C: cursor transport, hit testing,131
explicitly configured routing, relative-pointer delivery and admitted constraint132
enforcement. Motion never chooses keyboard focus, raises a window or computes133
interactive window geometry. `set-motion-hook!` takes a procedure and copied134
device/region/rate filters; only matching events cross to Sigil. Absolute135
positions and accumulated relative deltas preserve movement when samples are136
coalesced. Registration/removal is acknowledged with a generation; stale137
replies are rejected. The manager can intercept/replace/drop motion and submit138
new drag geometry. Thus a custom drag or focus-on-hover rule deliberately pays139
for Sigil execution; ordinary pointer motion has no Sigil callback.141
`emulate-key!`, `emulate-motion!`, `emulate-button!` and `emulate-axis!` synthesize142
events in-process for a chosen seat through the same ordered hook/routing path.143
They return completion tokens, use distinct synthetic device identities and144
mark `origin` as `emulated`. Hook-generated emulation is queued after the current145
decision, avoiding recursive dispatch. Seat and session-lock checks still apply.146
These functions are available to Sigil code and the REPL. Client-facing147
virtual-keyboard and virtual-pointer protocols use separate admission requests148
and permission-checked device handles; they cannot obtain internal emulation149
authority by setting an origin field.151
## Pixels and optional drawing153
The core scene primitive is `present-pixels!`: node ID, copied premultiplied154
native-endian ARGB32 pixels, pixel dimensions/stride, logical rect, scale, clip155
and stack position. `remove-pixels!` retires a node. C validates sizes and copies156
storage before returning queue acceptance, so callers may then release their157
source images. Accepted transactions retain native buffers until replacement158
or removal completes. Placement and scale may change without re-uploading159
pixels; unchanged buffers are reused across frames. C neither interprets text160
nor chooses fonts, shapes, colors or decoration geometry.162
Optional `(sigil wm draw)` depends on sigil-cairo, sigil-harfbuzz,163
sigil-freetype and sigil-fontconfig. Port Tessera's `feat/text-layer`164
`(tessera text)` implementation: `open-text-font`, `shape-text`, `fit-text`,165
`render-text`, `destroy-text-image!` and `close-text-font!`, including cluster-aware166
ellipsis, scaled rasterization and explicit image/font lifetimes. Add167
`make-canvas`, `fill-rect!`, `stroke-path!` and `canvas-image` over cairo for168
shapes. Images expose the same pixel descriptor consumed by the core.169
Generalize the existing Sigil image cache; title selection, styles and refresh170
triggers stay with the manager. Re-render when text/style/geometry/scale changes,171
not each frame. Importing the core does not load these drawing/font libraries.173
## REPL and execution boundary175
`(sigil wm repl)` exports `start-repl!` (host, Unix-socket path, environment) and176
`stop-repl!`. Use a user-owned runtime directory and an owner-only socket;177
refuse to replace an existing endpoint. Requests contain length-prefixed UTF-8178
Sigil expressions; replies contain the printed result or a structured error.179
IPC is evaluation in the manager's live environment, so inspection, layout180
changes and input emulation use the same public functions. The evaluator runs181
on the Sigil scheduler and never on the native owner thread.183
| Crossing | Frequency and ownership |184
| --- | --- |185
| Object facts, metadata and protocol request tokens: C → Sigil | Creation/change/request; copied IDs/data, with ordered lifecycle events |186
| Plan/config/request reply: Sigil → C | Per explicit decision; copied bounded transaction, completion returned asynchronously |187
| Intercepted key/button event and disposition: both directions | Eligible presses and their releases; ordered tokens, no native-to-Sigil calls |188
| Unmatched button observation: C → Sigil | Asynchronous copied fact; client delivery proceeds immediately |189
| Motion event and disposition: both directions | Only while a matching hook is registered; otherwise zero crossings |190
| Emulated input: Sigil → C → Sigil hook | Per synthesized event; normal seat ordering and checks |191
| Pixels: Sigil → C | Only on image changes; copy once, native reuse per frame |192
| Frame, client buffer commit, hit test, native lifetime work | Native execution; only changed facts/completions cross, no per-frame policy call |193
| REPL expressions: socket → Sigil | Per request; only resulting public commands cross into C |195
One native owner thread alone touches wlroots. Bounded queues carry copied196
records and owned payloads; native callbacks never wait for Sigil. Commands197
reject stale IDs, generations and unsafe bounds. Event overflow is counted and198
triggers a coherent replay of copied live facts before subsequent deltas. Text199
updates coalesce by object and field; one bounded copy replaces chunk traffic.200
The scheduler publishes the rebuilt snapshot and lifecycle differences together,201
then emits snapshot-resynced. Managers invalidate outstanding reply tokens and202
resubmit complete plans. A bounded journal retains transient replies where203
possible; unanswered protocol requests still expire closed. Sigil owns replaceable decisions and persistent model204
state; C owns resource lifetimes and unconditional protocol/lock safety.