sigil-wm

A toolkit for writing Wayland window managers in Sigil, built on wlroots. The native thread owns Wayland objects, input transport and scene presentation.
Copied admissions that arrive after release, lock, or resource retirement complete with a cancelled scene-move-ended event.
Interactive scene translation uses start-scene-move! with a window, held button, pointer anchor and an explicit set of window/pixel resources (at most 256). The native owner follows pointer motion without a manager round trip or restacking. scene-move-ended reports delta-x:, delta-y:, move-token: and reason: (0 release, 1 cancellation). The manager reconciles its geometry in the next full plan; stale plans cannot rewind the move. Explicit fullscreen, tiled or hidden placements cancel translation. stop-scene-move!, lock, unmap and initiating-device removal also stop it.
Sigil chooses placement, visibility, stacking, focus and input handling.
The library namespaces are (sigil wm host), (sigil wm model), (sigil wm layout), (sigil wm scene), (sigil wm input) and (sigil wm repl). Optional (sigil wm draw) renders text and shapes to pixel buffers using sigil-cairo, sigil-harfbuzz, sigil-freetype and sigil-fontconfig. The core has no font dependencies. See the API design.
Build in the pinned environment:
guix time-machine -C channels.scm -- shell -m manifest.scm -- make(configure-trace! #t) enables bounded copied timing records. Bind current-trace-correlation in (sigil wm trace) to an action ID before sending commands, or current-trace-causes to up to 64 IDs when coalescing actions into one scene/pixel upload. Capture these bindings explicitly across queued callbacks. The host emits trace-record events with id:, stage:, monotonic-ns:, object: and commit-sequence:. Stages 0, 8, 9, 10, 11 and 12 denote native input receipt, commit, presentation, scene application, focus and copied pixels; 14 denotes a discarded presentation. latency-mark! records application stages. Commit/presentation associations retain each causal ID independently of newer input. Record storage is bounded at 8192; each output retains 64 causes and 64 commit associations. Saturation or contention increments trace-dropped without blocking input or disabling tracing. Disabled recording returns after its atomic enable check, without clock reads, allocation, locks or IO. Existing untraced submission functions retain their ABI. Trace events are diagnostics, not snapshot facts, and can be lost independently of reliable host state.
(host-output-inventory host) returns a detached dictionary with connectors:, truncated?: and errors:. DRM resource enumeration includes connectors which have never been plugged in; other backends supply their live output objects. Each connector includes connection/enabled/power state, available/current modes, scale, position, transform and backend make/model/serial where available. Unknown text is #f; an absent live output has id: 0. DRM connector IDs and device numbers disambiguate backend identities. Native output changes refresh the copied inventory on an idle callback. Limits (256 connectors, 128 modes per connector) and backend enumeration failures are reported explicitly.
Run scripts/test-trace-inventory inside the pinned environment with a glibc Sigil runtime. It checks disabled recording, 12,000 records, drops/recovery, overlapping actions, a never-connected mocked DRM connector, metadata, and a real headless pixel/plan commit/presentation/disable cycle.
The native maintenance timer is armed for the earliest outstanding input, motion, plan, scene bracket, lock grace, permission, data/output request, pointer hint or popup deadline. Admission and cancellation in any native/idle callback are reflected before the loop blocks. With no pending deadline the timer stays disarmed; backend/client timers continue normally. Expired bound keys wait for their explicit decision without periodic wakeups, and an elapsed lock grace never bypasses the presentation barrier. (host-statistic host 12) counts maintenance callbacks so idle tests distinguish those from real backend frame or client events. scripts/test-deadlines checks every deadline class, independent repeating timers, real resource expiry, lock isolation, repeated key streams, coalesced motion and zero blanked-host maintenance ticks.
Popups use their root parent's output as the constraint area, including layer surfaces and nested popups. Initial configuration, repositioning and policy configuration apply the client's flip, slide and resize rules in root-surface coordinates. scripts/test-popup-constraints checks delivered geometry against two adjacent outputs with nonzero layout and window-geometry origins.
Failed scene commits retain damage and client frame callbacks until an output commit succeeds. Ordinary scene failures retry after 16 ms through the native deadline timer; gamma failures retain their existing ownership and retry rules. scripts/test-surface-frames checks tiny redraws, callbacks and actual presented pixel phases, including one rejected commit. scripts/test-foot-blink samples cursor phases in a nested compositor, directly and through a private tmux.
Run the stacking example from a Wayland session:
guix time-machine -C channels.scm -- shell -m manifest.scm -- \
bash scripts/run-sigil -L src -L examples eval -f examples/stacking.sglexamples/tiling.sgl partitions the output. examples/tabbed.sgl shows one window at a time and draws titles with (sigil wm draw). Tab selects the next window; Escape exits. All three use the same host and explicit scene plans.
The tabbed example also needs the optional drawing packages. Development copies can be staged with scripts/stage-draw, passing the four package source roots in cairo, freetype, harfbuzz, fontconfig order. Use draw-manifest.scm for their native libraries. This does not add those libraries to the compositor core.
Set SIGIL_WM_REPL to a path in a private runtime directory to enable expression IPC in an example. scripts/msg SOCKET '(host-snapshot host)' inspects its live state. The socket permits arbitrary Sigil evaluation with the user's authority.
The real-client tests run through scripts/test-examples; they retain logs, composed-output captures and PNGs under build/receipts/.
scripts/test-input-reply-queue fills the 1024-command ring while a key, button or motion hook is returning its decision. A test-only native gate pauses consumption, and another scheduler task releases it to verify cooperative backpressure, accepted replies and subsequent input progress. It also checks replies before native readiness and after shutdown with an empty ring, and checks diagnostic rate limiting. Set SIGIL to a runtime with FFI loading support; logs are retained under build/receipts/input-reply-queue/.
scripts/test-lock-presentation uses a test-only presentation barrier to check that a blank lock owner cannot be acknowledged before actual output presentation, that ordinary pixels remain covered, and that authenticated unlock restores the scene. The barrier is absent from the production library.
build/backend-pointer.so is an optional test fixture for backend input. Set SIGIL_WM_BACKEND_POINTER_CONTROL before starting a headless test session; creating that file emits one backend pointer motion on the native event loop. The device uses the ordinary backend origin and is destroyed with its backend. This lets idle tests retain locked virtual-input refusal while exercising backend activity and power resume. It does not establish physical-device coverage and is absent from the production library.
Security-context connections are filtered before they can discover or bind privileged globals: session lock, capture, data control, output controls, layer-shell, idle notification, virtual input, foreign-toplevel management and the security-context manager. Read-only ext foreign-toplevel listing remains available, including other applications' titles and app IDs. Ordinary application rendering and focused clipboard protocols stay available. Classification uses wlroots connection context, including empty metadata, without PID or application-name matching. Native connections retain compatibility access, including locker replacement; this is not same-user process isolation. Sandbox engines must use the security-context protocol: unlabelled raw-socket connections retain native access. There is no strict option or client allowlist.