AtlatestRepositorysigil-wm
1# sigil-wm API design
2
3Proposed API for review. sigil-wm is a Wayland window-management library built
4on wlroots. C owns protocol objects, scene presentation and input transport;
5Sigil chooses all window-management behavior. The core has no implicit layout,
6focus, keybindings, decoration style or output arrangement.
7
8## Model and transactions
9
10`(sigil wm host)` provides `start-host!`, `stop-host!`, `on-event!`,
11`remove-handler!` and `host-snapshot`. A host owns outputs, seats, devices and
12surfaces. Window, layer and popup roles expose copied metadata, parent relations,
13size hints and protocol requests. Handles contain a host identity, kind and
14monotonic ID; they never contain borrowed native pointers. Events carry a
15sequence 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 new
31state. Detached snapshots contain ordinary Sigil data and a model revision.
32Removing a node does not choose focus, collapse containers or migrate workspaces.
33Those operations belong to the manager, including its response to output loss.
34The model can retain disconnected views while no output exists.
36`(sigil wm layout)` defines `arrange`: a manager-supplied procedure receives a
37container, logical rect, snapshot and trigger, returning placements and next
38layout state. Nested containers compose by calling their child arrangers.
39`make-scene-plan` and `validate-plan` assemble/check the result; the manager
40chooses which workspaces, layers and decorations to include. Desired placement
41is distinct from acknowledged configuration and committed client geometry.
43`(sigil wm scene)` provides `submit-plan!`, returning a transaction token or a
44queue-full result. A plan explicitly supplies stacking, visibility, client boxes,
45activation/fullscreen/tiled states and seat focus (including no focus).
46Decorations are pixel nodes; Sigil computes their geometry separately from
47client boxes. C validates the complete plan before accepting it, sends protocol
48configures and applies the scene atomically after acknowledgements or the
49caller-specified bounded deadline. It reports accepted, applied or rejected,
50with a serial and reason. Until application the previous scene remains active;
51destroyed or lock-ineligible targets cannot acquire focus. Cross-output moves
52belong to one transaction. Buffer retention and configure bookkeeping stay in C.
54Other 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!` and
57`reply-request!` (accept/reject plus request-specific values). Sigil selects
58output mode/scale/position, keymap/repeat, device settings, layer assignment and
59exclusive zones, popup placement, activation, selection/drag admission and
60pointer-constraint eligibility. It also selects protocol exposure and request
61deadlines at startup. Unanswered requests remain pending until their declared
62deadline, then fail closed. Backend tests, rollback, serial checks, constraint
63enforcement and authenticated session-lock isolation remain native invariants.
64There is no policy command to unlock a session.
66The examples will demonstrate three uses of the same model:
68- **Stacking:** an ordered root contains views with remembered overlapping
69 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)`.
75No container kind triggers special native behavior. Focus history, floating
76membership, tab selection and fullscreen restoration are Sigil state.
78## Input hooks and emulation
80`(sigil wm input)` provides `set-key-hook!`, `set-button-hook!` and
81`set-motion-hook!`. An unrestricted key hook receives every press and release,
82including modifier keys, as a copied `key-event`: token, seat/device, time, keycode,
83keysyms, modifiers, focus/lock generation and origin. It returns `(forward event)`,
84`(replace event)` or `(drop)`. A hook can dispatch a Sigil command and drop its
85triggering key. Replacement may change the keycode and modifier state; routing
86identity, time ordering and lock generation are immutable. The manager can
87publish resolved eligibility rules with `set-key-intercepts!` and
88`set-button-intercepts!`. Nonmatching input is delivered natively without
89waiting for the scheduler. The cache contains chord and target predicates;
90commands, binding resolution and action selection remain in Sigil.
92C queues a token and continues its owner loop. Sigil runs the hook outside
93native callbacks and replies asynchronously. C delivers replies in seat order,
94tracks forwarded/replaced press identities and balances their releases. Release
95hooks observe the event but cannot emit an unmatched or differently remapped
96release: delivered presses always receive their paired release, even if its
97hook drops or fails; dropped presses suppress their releases. Buttons use the
98same pairing rules. Physical and client-visible modifier state are tracked
99separately. A hook exception drops a new press and reports an error. Replies
100carrying stale focus/lock generations are rejected, with held-state cleanup.
102Decision replies share the 1024-entry command ring with configuration, scene
103and focus commands. When it fills, Sigil retains the computed reply and sleeps
104cooperatively for 1 ms between enqueue attempts, without repeating the hook.
105Keeping the reply in this FIFO preserves earlier hook commands, including focus
106authorization. A reserved queue or priority lane could overtake those commands.
107Replies also wait while the native receiver is starting. Retry ends when the
108reply is accepted or the receiver has shut down; shutdown cannot leave an active
109intercepted 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, input
112kind and native state. Repeated failures are limited to one line per second for
113each case and kind, with the suppressed attempt count included on the next line.
115Intercepted decisions are observed after 32 ms, with at most one 10 ms timer
116interval of granularity. An unanswered intercepted press is logged and keeps
117waiting for policy, without exposing a manager chord to a client. Ordinary
118nonmatching keys never enter this queue or lose input to its budget. Delivered
119presses retain balanced releases. Hooked motion expires to native transport.
120Queued motion from the same device and generation is coalesced without crossing
121key/button ordering barriers. Input queue exhaustion reports a recoverable
122fault and cancels queued intercepted tokens, balancing their releases while
123leaving ordinary native input untouched. Transport resync also cancels tokens
124whose copied events were discarded, so a lost token cannot wait indefinitely.
125While locked, mapped lock surfaces acquire native keyboard focus and physical
126ordinary keys reach the lock owner without a policy round trip. Explicitly
127cached lock-eligible chords retain their manager hook. Virtual input remains
128refused, including its idle-activity notifications.
130Without a motion hook, motion stays entirely in C: cursor transport, hit testing,
131explicitly configured routing, relative-pointer delivery and admitted constraint
132enforcement. Motion never chooses keyboard focus, raises a window or computes
133interactive window geometry. `set-motion-hook!` takes a procedure and copied
134device/region/rate filters; only matching events cross to Sigil. Absolute
135positions and accumulated relative deltas preserve movement when samples are
136coalesced. Registration/removal is acknowledged with a generation; stale
137replies are rejected. The manager can intercept/replace/drop motion and submit
138new drag geometry. Thus a custom drag or focus-on-hover rule deliberately pays
139for Sigil execution; ordinary pointer motion has no Sigil callback.
141`emulate-key!`, `emulate-motion!`, `emulate-button!` and `emulate-axis!` synthesize
142events in-process for a chosen seat through the same ordered hook/routing path.
143They return completion tokens, use distinct synthetic device identities and
144mark `origin` as `emulated`. Hook-generated emulation is queued after the current
145decision, avoiding recursive dispatch. Seat and session-lock checks still apply.
146These functions are available to Sigil code and the REPL. Client-facing
147virtual-keyboard and virtual-pointer protocols use separate admission requests
148and permission-checked device handles; they cannot obtain internal emulation
149authority by setting an origin field.
151## Pixels and optional drawing
153The core scene primitive is `present-pixels!`: node ID, copied premultiplied
154native-endian ARGB32 pixels, pixel dimensions/stride, logical rect, scale, clip
155and stack position. `remove-pixels!` retires a node. C validates sizes and copies
156storage before returning queue acceptance, so callers may then release their
157source images. Accepted transactions retain native buffers until replacement
158or removal completes. Placement and scale may change without re-uploading
159pixels; unchanged buffers are reused across frames. C neither interprets text
160nor chooses fonts, shapes, colors or decoration geometry.
162Optional `(sigil wm draw)` depends on sigil-cairo, sigil-harfbuzz,
163sigil-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-aware
166ellipsis, scaled rasterization and explicit image/font lifetimes. Add
167`make-canvas`, `fill-rect!`, `stroke-path!` and `canvas-image` over cairo for
168shapes. Images expose the same pixel descriptor consumed by the core.
169Generalize the existing Sigil image cache; title selection, styles and refresh
170triggers stay with the manager. Re-render when text/style/geometry/scale changes,
171not each frame. Importing the core does not load these drawing/font libraries.
173## REPL and execution boundary
175`(sigil wm repl)` exports `start-repl!` (host, Unix-socket path, environment) and
176`stop-repl!`. Use a user-owned runtime directory and an owner-only socket;
177refuse to replace an existing endpoint. Requests contain length-prefixed UTF-8
178Sigil expressions; replies contain the printed result or a structured error.
179IPC is evaluation in the manager's live environment, so inspection, layout
180changes and input emulation use the same public functions. The evaluator runs
181on 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 |
195One native owner thread alone touches wlroots. Bounded queues carry copied
196records and owned payloads; native callbacks never wait for Sigil. Commands
197reject stale IDs, generations and unsafe bounds. Event overflow is counted and
198triggers a coherent replay of copied live facts before subsequent deltas. Text
199updates coalesce by object and field; one bounded copy replaces chunk traffic.
200The scheduler publishes the rebuilt snapshot and lifecycle differences together,
201then emits snapshot-resynced. Managers invalidate outstanding reply tokens and
202resubmit complete plans. A bounded journal retains transient replies where
203possible; unanswered protocol requests still expire closed. Sigil owns replaceable decisions and persistent model
204state; C owns resource lifetimes and unconditional protocol/lock safety.