AtlatestRenderedmarkdown
Readme

sigil-wm API design

Proposed API for review. sigil-wm is a Wayland window-management library built on wlroots. C owns protocol objects, scene presentation and input transport; Sigil chooses all window-management behavior. The core has no implicit layout, focus, keybindings, decoration style or output arrangement.

Model and transactions

(sigil wm host) provides start-host!, stop-host!, on-event!, remove-handler! and host-snapshot. A host owns outputs, seats, devices and surfaces. Window, layer and popup roles expose copied metadata, parent relations, size hints and protocol requests. Handles contain a host identity, kind and monotonic ID; they never contain borrowed native pointers. Events carry a sequence number and relevant object/request generation.

(sigil wm model) supplies immutable records and explicit tree operations:

TypeMeaning
rectLogical x, y, width and height in global output-layout coordinates
wm-stateOutputs, workspaces, nodes, seat focus and application-owned data
workspaceStable ID and ordered roots; visibility is an explicit choice
containerStable ID, ordered children and opaque layout/state data
viewReference to a window handle and application-owned data
placementSurface, client rect, clip, visibility, stack position and explicit protocol state
scene-planPlacements, pixel nodes and explicit keyboard/pointer focus targets

make-wm-state, make-workspace, make-container, make-view, attach-node, detach-node, reparent-node, order-children and set-seat-focus return new state. Detached snapshots contain ordinary Sigil data and a model revision. Removing a node does not choose focus, collapse containers or migrate workspaces. Those operations belong to the manager, including its response to output loss. The model can retain disconnected views while no output exists.

(sigil wm layout) defines arrange: a manager-supplied procedure receives a container, logical rect, snapshot and trigger, returning placements and next layout state. Nested containers compose by calling their child arrangers. make-scene-plan and validate-plan assemble/check the result; the manager chooses which workspaces, layers and decorations to include. Desired placement is distinct from acknowledged configuration and committed client geometry.

(sigil wm scene) provides submit-plan!, returning a transaction token or a queue-full result. A plan explicitly supplies stacking, visibility, client boxes, activation/fullscreen/tiled states and seat focus (including no focus). Decorations are pixel nodes; Sigil computes their geometry separately from client boxes. C validates the complete plan before accepting it, sends protocol configures and applies the scene atomically after acknowledgements or the caller-specified bounded deadline. It reports accepted, applied or rejected, with a serial and reason. Until application the previous scene remains active; destroyed or lock-ineligible targets cannot acquire focus. Cross-output moves belong to one transaction. Buffer retention and configure bookkeeping stay in C.

Other decisions use typed requests and explicit commands in (sigil wm host): configure-output!, configure-device!, set-decoration-mode!, set-pointer-route!, set-cursor!, set-idle-state! and reply-request! (accept/reject plus request-specific values). Sigil selects output mode/scale/position, keymap/repeat, device settings, layer assignment and exclusive zones, popup placement, activation, selection/drag admission and pointer-constraint eligibility. It also selects protocol exposure and request deadlines at startup. Unanswered requests remain pending until their declared deadline, then fail closed. Backend tests, rollback, serial checks, constraint enforcement and authenticated session-lock isolation remain native invariants. There is no policy command to unlock a session.

The examples will demonstrate three uses of the same model:

  • Stacking: an ordered root contains views with remembered overlapping rectangles. Sigil chooses raise/focus and registers motion handling for drags.
  • Tiling: recursive containers partition a rect; Sigil chooses splits, proportions, gaps and neighbor traversal, then submits every child box.
  • Tabbed: a container retains all children; Sigil shows the selected view, hides its peers and draws clickable title headers using (sigil wm draw).

No container kind triggers special native behavior. Focus history, floating membership, tab selection and fullscreen restoration are Sigil state.

Input hooks and emulation

(sigil wm input) provides set-key-hook!, set-button-hook! and set-motion-hook!. An unrestricted key hook receives every press and release, including modifier keys, as a copied key-event: token, seat/device, time, keycode, keysyms, modifiers, focus/lock generation and origin. It returns (forward event), (replace event) or (drop). A hook can dispatch a Sigil command and drop its triggering key. Replacement may change the keycode and modifier state; routing identity, time ordering and lock generation are immutable. The manager can publish resolved eligibility rules with set-key-intercepts! and set-button-intercepts!. Nonmatching input is delivered natively without waiting for the scheduler. The cache contains chord and target predicates; commands, binding resolution and action selection remain in Sigil.

C queues a token and continues its owner loop. Sigil runs the hook outside native callbacks and replies asynchronously. C delivers replies in seat order, tracks forwarded/replaced press identities and balances their releases. Release hooks observe the event but cannot emit an unmatched or differently remapped release: delivered presses always receive their paired release, even if its hook drops or fails; dropped presses suppress their releases. Buttons use the same pairing rules. Physical and client-visible modifier state are tracked separately. A hook exception drops a new press and reports an error. Replies carrying stale focus/lock generations are rejected, with held-state cleanup.

Decision replies share the 1024-entry command ring with configuration, scene and focus commands. When it fills, Sigil retains the computed reply and sleeps cooperatively for 1 ms between enqueue attempts, without repeating the hook. Keeping the reply in this FIFO preserves earlier hook commands, including focus authorization. A reserved queue or priority lane could overtake those commands. Replies also wait while the native receiver is starting. Retry ends when the reply is accepted or the receiver has shut down; shutdown cannot leave an active intercepted input waiting for that reply. wm_send retains its boolean contract; wm_input_reply distinguishes accepted, full, starting and stopped results. INPUT_REPLY diagnostics include the case, wait/drop action, queue depth, input kind and native state. Repeated failures are limited to one line per second for each case and kind, with the suppressed attempt count included on the next line.

Intercepted decisions are observed after 32 ms, with at most one 10 ms timer interval of granularity. An unanswered intercepted press is logged and keeps waiting for policy, without exposing a manager chord to a client. Ordinary nonmatching keys never enter this queue or lose input to its budget. Delivered presses retain balanced releases. Hooked motion expires to native transport. Queued motion from the same device and generation is coalesced without crossing key/button ordering barriers. Input queue exhaustion reports a recoverable fault and cancels queued intercepted tokens, balancing their releases while leaving ordinary native input untouched. Transport resync also cancels tokens whose copied events were discarded, so a lost token cannot wait indefinitely. While locked, mapped lock surfaces acquire native keyboard focus and physical ordinary keys reach the lock owner without a policy round trip. Explicitly cached lock-eligible chords retain their manager hook. Virtual input remains refused, including its idle-activity notifications.

Without a motion hook, motion stays entirely in C: cursor transport, hit testing, explicitly configured routing, relative-pointer delivery and admitted constraint enforcement. Motion never chooses keyboard focus, raises a window or computes interactive window geometry. set-motion-hook! takes a procedure and copied device/region/rate filters; only matching events cross to Sigil. Absolute positions and accumulated relative deltas preserve movement when samples are coalesced. Registration/removal is acknowledged with a generation; stale replies are rejected. The manager can intercept/replace/drop motion and submit new drag geometry. Thus a custom drag or focus-on-hover rule deliberately pays for Sigil execution; ordinary pointer motion has no Sigil callback.

emulate-key!, emulate-motion!, emulate-button! and emulate-axis! synthesize events in-process for a chosen seat through the same ordered hook/routing path. They return completion tokens, use distinct synthetic device identities and mark origin as emulated. Hook-generated emulation is queued after the current decision, avoiding recursive dispatch. Seat and session-lock checks still apply. These functions are available to Sigil code and the REPL. Client-facing virtual-keyboard and virtual-pointer protocols use separate admission requests and permission-checked device handles; they cannot obtain internal emulation authority by setting an origin field.

Pixels and optional drawing

The core scene primitive is present-pixels!: node ID, copied premultiplied native-endian ARGB32 pixels, pixel dimensions/stride, logical rect, scale, clip and stack position. remove-pixels! retires a node. C validates sizes and copies storage before returning queue acceptance, so callers may then release their source images. Accepted transactions retain native buffers until replacement or removal completes. Placement and scale may change without re-uploading pixels; unchanged buffers are reused across frames. C neither interprets text nor chooses fonts, shapes, colors or decoration geometry.

Optional (sigil wm draw) depends on sigil-cairo, sigil-harfbuzz, sigil-freetype and sigil-fontconfig. Port Tessera's feat/text-layer (tessera text) implementation: open-text-font, shape-text, fit-text, render-text, destroy-text-image! and close-text-font!, including cluster-aware ellipsis, scaled rasterization and explicit image/font lifetimes. Add make-canvas, fill-rect!, stroke-path! and canvas-image over cairo for shapes. Images expose the same pixel descriptor consumed by the core. Generalize the existing Sigil image cache; title selection, styles and refresh triggers stay with the manager. Re-render when text/style/geometry/scale changes, not each frame. Importing the core does not load these drawing/font libraries.

REPL and execution boundary

(sigil wm repl) exports start-repl! (host, Unix-socket path, environment) and stop-repl!. Use a user-owned runtime directory and an owner-only socket; refuse to replace an existing endpoint. Requests contain length-prefixed UTF-8 Sigil expressions; replies contain the printed result or a structured error. IPC is evaluation in the manager's live environment, so inspection, layout changes and input emulation use the same public functions. The evaluator runs on the Sigil scheduler and never on the native owner thread.

CrossingFrequency and ownership
Object facts, metadata and protocol request tokens: C → SigilCreation/change/request; copied IDs/data, with ordered lifecycle events
Plan/config/request reply: Sigil → CPer explicit decision; copied bounded transaction, completion returned asynchronously
Intercepted key/button event and disposition: both directionsEligible presses and their releases; ordered tokens, no native-to-Sigil calls
Unmatched button observation: C → SigilAsynchronous copied fact; client delivery proceeds immediately
Motion event and disposition: both directionsOnly while a matching hook is registered; otherwise zero crossings
Emulated input: Sigil → C → Sigil hookPer synthesized event; normal seat ordering and checks
Pixels: Sigil → COnly on image changes; copy once, native reuse per frame
Frame, client buffer commit, hit test, native lifetime workNative execution; only changed facts/completions cross, no per-frame policy call
REPL expressions: socket → SigilPer request; only resulting public commands cross into C

One native owner thread alone touches wlroots. Bounded queues carry copied records and owned payloads; native callbacks never wait for Sigil. Commands reject stale IDs, generations and unsafe bounds. Event overflow is counted and triggers a coherent replay of copied live facts before subsequent deltas. Text updates coalesce by object and field; one bounded copy replaces chunk traffic. The scheduler publishes the rebuilt snapshot and lifecycle differences together, then emits snapshot-resynced. Managers invalidate outstanding reply tokens and resubmit complete plans. A bounded journal retains transient replies where possible; unanswered protocol requests still expire closed. Sigil owns replaceable decisions and persistent model state; C owns resource lifetimes and unconditional protocol/lock safety.