31 KiB
Architecture
Lime is arranged so that task/workflow semantics stay independent of X11, and so that X11 protocol work can grow without becoming one giant event handler.
key / menu / IPC / pointer MapRequest / DestroyNotify
| |
v v
x11/input, x11/ui x11/lifecycle
| |
+--------------> x11/commands <---------------+
|
v
lime.core
|
v
x11/render: plan + CLX executor
Core model
src/model.lisp, src/layout.lisp, src/operations.lisp, and
src/topology.lisp make no X11 calls. They own Activities, Views, Heads,
Presentations, Leases, layout resolution, collision planning, topology-plan
reconciliation, and model focus state. Every core transition must preserve the
one-lease invariant and be testable without an X server.
src/membership.lisp owns explicit home-Activity moves, shared eligibility,
and guarded Activity deletion (empty and unmounted only, with reference cleanup).
It prepares affected View/draft copies before mutation, then commits ownership,
layout/focus cleanup and lease release together. The adapter chooses destination
draft and layout policy; neither menus nor X events implement membership rules.
make-client-private-in-presentation atomically adopts the current Activity as
home and revokes sharing. It prunes other Activities' saved/live references
without replacing the current lease or changing placement, focus, or dirty state.
The lower-level, context-free set-client-shared retains its existing-home
revocation semantics for callers deliberately operating without a live desk.
src/topology.lisp compares a desired list of logical Head ids with the
current model and builds an explicit move/suspend plan. It knows nothing about
physical outputs: a removed live Presentation either moves whole to a caller's
chosen destination or becomes an unbound, lease-free cached draft. This keeps
hotplug routing reusable from an X11 menu, IPC, or test fixture.
src/layout-operations.lisp is the focused, Head-local layout command layer:
it selects manual/master-stack/monocle policy, promotes a master, and edits a
focused split or visible tiled slots. It changes only a live Presentation;
View persistence remains an explicit operation in operations.lisp.
src/floating-operations.lisp is the equally small Head-local floating layer:
it toggles an existing placement between tile and float, validates normalized
rectangles, and keeps the tiling tree consistent. It neither knows pointer
events nor grants leases, so the same commands work from a menu, IPC, or REPL.
The core should receive an intention such as “activate this View partially” or “focus this visible leased client,” never an X event or a pixel rectangle.
X11 adapter
src/x11/backend.lisp owns the CLX connection, root selection, configured
Head rectangles, backend-owned window lookup tables, and the event loop. It
does not decide layouts or issue rendering effects directly.
src/x11/resilience.lisp owns the narrow X error policy, bounded diagnostic
reports, independent input-grab cleanup, and explicit recovery reconciliation.
Only request-specific vanished external-client/unviewable-focus races are
tolerated; BadAccess and Lime-owned resource failures remain errors. Failed
synchronous client queries unwind to an event/adoption boundary rather than
waiting for a reply that cannot arrive. Dispatch consumes each current event
before mutation (CLX otherwise retains it on an unwind), and returns after one
event so a busy client cannot starve developer control requests.
The separate lime/dev runtime catches unexpected dispatch errors with a
pre-unwind backtrace, pauses without replacing the world, releases active grabs,
and continues servicing the owner-thread mailbox and Slynk. Explicit resume
validates model/adapter consistency and reconciles live X windows before render.
It is not a rollback, automatic retry, or persistence system. Explicit backend
restart keeps the supervisor and REPL alive through success or failure, and
constructs a replacement world from session storage when enabled. A separate lifecycle lock serializes REPL
and supervisor stop/restart operations without blocking mailbox bookkeeping.
Shutdown attempts each cleanup independently
and always attempts to close CLX. See recovery behavior.
lime/persistence is separate from X11. src/persistence/model.lisp encodes a
versioned plain-data schema and reconstructs a fresh validated world, including
cached drafts; it derives leases from active selections/unavailable slots.
src/persistence/store.lisp owns bounded non-evaluating reads, a lifetime writer
lock, private atomic replacements, and file/directory synchronization.
src/x11/persistence.lisp supplies verified server/window tokens, physical-Head
reconciliation, restored-client adoption, debounced owner-thread snapshots, and
one off-thread disk writer. The worker never sees the live world or CLX. A
restore error blocks autosave rather than replacing a rejected file with a
blank desk. Configuration remains policy, not generated task state. See
persistence boundaries.
src/x11/randr.lisp discovers active physical outputs at startup and maps one
active CRTC to one logical Head; cloned outputs share a CRTC and therefore a
Head. RandR events update only an adapter-owned topology snapshot. A refresh
may safely replace rectangles when the same Head ids still exist. For added or
removed Heads, the adapter exposes a report -> build plan -> apply plan command
sequence; it rereads RandR immediately before apply and rejects a plan if the
physical Head-id set changed while a frontend was presenting choices.
src/x11/lifecycle.lisp turns narrow client lifecycle events into core
commands: adopt a normal new client into the selected live Presentation,
retire a destroyed or application-withdrawn client, or ask the renderer to
restore a tiled geometry. Its pending-unmap token boundary distinguishes a
renderer-driven View hide from an application's real ICCCM withdrawal.
src/x11/icccm.lisp supplies the protocol details lifecycle and rendering
need: cached title/class/capability facts, WM_STATE normal/iconic updates,
property refreshes, graceful close requests, protocol-aware focus, and
synthetic ConfigureNotify replies. Lime removes WM_STATE on a real client
withdrawal. It does not change Activities or lease planning.
src/x11/placement-policy.lisp consumes a managed client's transient-parent
fact only at adoption: a WM_TRANSIENT_FOR child follows its parent’s active
Presentation and Head, starts as a centered float, and receives focus.
Dialog-like EWMH clients float on the selected Head. Ordinary clients whose
WM_NORMAL_HINTS declare equal positive minimum/maximum sizes on both axes
default to centered floats too, unless a matching configuration rule requests
tiling. This is initial-placement policy only: later hint changes do not undo
manual floating/tiling choices or saved arrangements. Resizable main windows
remain tiled even when their fixed-size startup windows float. The adjacent
src/x11/geometry.lisp adapts resolved pixel rectangles to cached
WM_NORMAL_HINTS min/max/base/increment constraints inside the target Head.
Both modules are adapter policy; they never write View or Lease state.
src/x11/appearance.lisp owns configurable client borders and inner/outer
tile gaps. It insets resolved slots without editing the layout tree, contains
the complete bordered frame, and applies normal hints within each tile's
remaining space. Floats bypass gaps but retain borders. The render planner
counts leased tiles with resolved geometry separately on each Head; a sole
visible tile bypasses gaps and borders in every layout. This also covers
monocle without a layout-name special case. Normal hints and the bar's work
area still apply. Fullscreen
bypasses all decoration, normal hints, and the bar reservation.
The adapter caches allocated border colors and original application border
widths, restoring widths on withdrawal or shutdown. Configuration reloads
repaint the same draft rather than turning theme choices into saved View data.
src/x11/render.lisp has two separate jobs:
build-render-planderives inspectablex11-effectdata from active Presentations—currently configure, map, unmap, float raise, and one input-focus effect. Configure effects include content geometry, border width, and the color chosen for global client focus.execute-render-planperforms that plan through CLX and flushes it as one batch. It rejects a plan if the core world revision changed after planning.
The temporary apply-active-presentation name remains as a small convenience
wrapper around those two operations. It then redraws the adapter-owned Head
bars; that UI state is deliberately outside the model render plan. Normal
layouts and floats resolve against x11-head-work-area (below a configured
top-bar reservation), while EWMH fullscreen still resolves against the whole
physical Head. New policy-aware callers can inspect a plan before executing
it.
src/x11/commands.lisp is the frontend-neutral imperative API. A keybinding,
menu, REPL, or future IPC service calls it to choose an already-tested core
transition and request one reconciliation. It also owns Head-relative
selection and focused-client transfer: the target draft retains any existing
placement, otherwise the source tile/float placement is copied, and the core
performs the exclusive lease move. Private clients require the same Activity;
shared clients may transfer across Activities without changing their home.
The interactive backend-send-focused-client-* entry points live in ui.lisp:
eligible transfers run immediately, while a private cross-Activity send offers
a named confirmation with Cancel selected. It captures the window, both live
arrangements, focused Head, and world revision, rejecting stale choices.
Confirmation calls the separate backend-rehome-client-to-head command and
core move-client-to-presentation transaction: membership, old-checkpoint
pruning, new placement, and the single lease commit together after validation.
Neither monitor switches Activities. After a successful send, keyboard focus
and command/new-window routing follow the moved window to the destination Head;
the source remembers a surviving client for when the user returns. Cancelled or
rejected sends do not change focus.
backend-move-client-to-activity explicitly rehomes a visible client and queues
it in the destination Activity's draft for the same Head without switching.
backend-set-client-shared enables sharing without moving the window; NIL calls
backend-make-client-private, which keeps it private to the current Activity.
That explicit command also accepts a Head and expected revision. The menu names
the Activity, defaults to Cancel, and rejects changed focus/desk/revision before
calling it. The hidden-window picker includes shared guests and asks before
transferring an existing lease.
src/x11/input.lisp only turns a
passive X key grab into such a command; it contains no View names or lease
rules. Passive grabs synchronously queue subsequent keyboard input until the
handler either opens an asynchronous palette grab or finishes; an unwind-protect
always thaws input, including failed or missing handlers. This closes the gap
where releasing the shortcut before a slow menu opened sent typed letters to
the previously focused application. The pointer remains asynchronous.
src/x11/default-bindings.lisp supplies the default, replaceable
dwm-shaped starter map plus named Activity/View/Head configuration helpers; it
owns neither X events nor model policy. src/x11/pointer.lisp does the
equivalent minimal routing for unmodified Button1 plus Super+Button1/2/3
float move/toggle/resize gestures. It uses passive grabs so
a click in an application's child window reaches the managed client boundary;
an ordinary managed-client click resolves to its leased Head, focuses that
client, then replays the click to the application. A Super gesture focuses a
floating client and turns its root-pixel motion into a normalized geometry
command. Consecutive queued motions from the same gesture are coalesced in
bounded batches; key/button/lifecycle events are never skipped. Release applies
its final coordinates, and a gesture is cancelled if its original Presentation
or floating lease is no longer current. Geometry-only commands configure just
that client through the usual layout/border/normal-hint calculations, without
reapplying focus, stacking, EWMH inventories, or other clients' rectangles.
Only the clean-to-dirty transition redraws bars. Blank root space selects that
Head and deliberately focuses root.
The adapter owns reusable left_ptr and hand2 cursors. Bars and dropdowns
inherit the root arrow except over their measured clickable regions, where
only that UI window selects the hand. Application-specific cursors are
untouched. Enter/motion/leave events update the cursor; redraws also recheck
the pointer so changed widgets, filtered lists, and resized menus cannot
leave a stale hand or highlight under a stationary mouse. This is event-driven,
not a pointer polling timer. Root selection is confirmed before installing
the arrow, and shutdown releases both cursor resources.
src/x11/cursor.lisp owns cursor configuration, assets, and resource lifetime;
pointer.lisp only routes gestures and selects arrow/hand roles. Optional
libXcursor loads theme images into memory, including inherited/default themes.
CLX uploads their ARGB pixels through X Render on the existing WM connection,
independent of root visual depth. There is no additional Xlib connection or
GTK dependency. Temporary images, pixmaps, GCs, pictures, and animation frames
are released after upload; the installed cursor set lives until replacement
or shutdown. Theme/size reload is transactional with the rest of configuration:
old cursors remain live until commit, and rollback rebinds the original XIDs,
including on hovered UI. Unsupported/missing theme facilities use core cursors
with diagnostics available through backend-cursor-report.
src/x11/session-settings.lisp is the application-facing settings boundary,
separate from cursor rendering. Explicit cursor config publishes only
Gtk/CursorThemeName / Gtk/CursorThemeSize using the
XSETTINGS protocol.
It claims _XSETTINGS_Sn only when vacant, announces a complete initial property
with a real server timestamp, preserves unrelated queued events, and destroys
its owner window on SelectionClear without stealing ownership back. Existing
managers remain authoritative for their toolkit clients.
The same layer owns reversible cursor-only RESOURCE_MANAGER edits and a child launch-environment overlay. It preserves unrelated resource records and later external edits; resolving NIL defaults excludes Lime's own publication. No desktop config files, process-global environment, or activation environment are mutated. Configuration failure restores prior publication; shutdown releases it before closing X. Runtime ownership lives in a backend-keyed table, not the pure model or session persistence. Clients may cache settings; protocol publication cannot force an application to replace its own cursor resources.
src/x11/config.lisp is a user-facing adapter-policy layer, exposed as
lime.config. Its DEFCONFIGURATION macro produces ordinary data:
first-class commands, bindings to those commands, ordered initial window
rules, window appearance, optional bar settings, and startup hooks. The configuration is held by
the backend, not the core world. A named command appears in direct palette
search and, when it has a key letter, under the palette’s User branch as well
as being eligible for a physical binding. Reload validates all data before it
touches X11 and rolls back binding/bar/appearance settings after an installation error; it
never recreates Activities, Views, Presentations, leases, or Viewsets. Current
rules style only ordinary new clients (float/center) and intentionally cannot
make a class match mount a View or move a client between task contexts.
src/x11/navigation.lisp owns contiguous Activity positions, stable Activity-local View slots,
and last-used Presentation ids per (Head, Activity). A separately owned,
backend-keyed runtime avoids changing core identities or the live backend's
structure; shutdown releases it (SBCL also uses weak keys). Rendering records
active arrangements once per model revision. Slot reassignment changes only
navigation metadata, whose own generation participates in session autosave.
src/x11/activity-navigation.lisp owns persistent portable/extended navigation
intent and the portable arrangement id. New named Activities are portable;
the bootstrap Scratch desk starts with its existing per-Head blank portions.
src/presentation-navigation.lisp supplies the pure atomic bring/swap operation:
it preserves active lease identity, transposes displaced cached portions, and
preflights inactive resumes without replaying missing checkpoint slots.
Portable checkpoint recall previews the compound move/recall on a detached
world before any live mutation, so a lease conflict or cancellation never
swaps monitors as a side effect. Explicit multi-Head Viewsets and direct
multi-portion model edits remain capable of extending an Activity.
The footprint sidecar has its own autosave generation; it is not a slot added
to live backend or model structures. activity-navigation-ui.lisp exposes
extension and return-to-portable commands through the normal dropdown, with
cancel-first choices and stale-context guards.
Activity deletion closes gaps while preserving surviving bar order; new
Activities append. Persisted sparse Activity slots are validated before being
compacted. Explicit Activity slot assignment swaps within the existing count,
whereas View assignments retain their stable, gap-filling policy.
The versioned X11 session envelope stores these indexes separately from the
pure world. The core's unique (Activity, Head) arrangement is authoritative;
last-used ids remain migration metadata, not a separate navigation target.
Activity navigation resumes current locations without a collision menu.
Explicit checkpoint recall still uses lease preflight/choice commands. :navigation-keys expands
to ordinary configuration commands/bindings rather than hardcoded key policy.
src/viewset.lisp adds the corresponding grouped core planner for named
whole-desk shortcuts. It records only Head -> View entries, preflights every
target Head as one outgoing group, and commits one atomic multi-Head lease
transition. src/x11/commands.lisp exposes save/preflight/strict/partial/take
commands; no X11 frontend is allowed to recreate that grouped collision logic.
UI boundary
src/ui/session.lisp is a renderer-neutral direct-search, one-string-prompt,
and key-tree session. It owns a candidate list, filtering, selection,
minibuffer text, a key-path stack/page, and declarative results (:close,
:keep-open, :replace, or :prompt). Candidate actions and key nodes are
supplied by a frontend, which makes a collision prompt an ordinary replacement
candidate set, a name prompt an ordinary session transition, and a which-key
menu a data tree rather than special modes embedded in a View-switch command.
MAKE-UI-SESSION explicitly selects :search (the default for standalone
pickers) or :key-path (the Super-y menu). A separate root candidate catalog
survives scoped/collision pickers: menu → search always restores the complete
catalog rather than accidentally searching the last replacement list.
src/x11/bar.lisp supplies the first reusable persistent UI surface: one
override-redirect bar per Head. It renders Activity names (not checkpoint or dirty
state), the Head-local layout token, and a focused-client title section whose
colors identify Head focus. Its optional right-side providers return HEAD-BAR-SEGMENT values
and may give each segment a click action; music, volume, and clock integration
therefore remain configuration rather than model state. The Lime segment
opens the palette and the layout segment cycles the local layout. The Activity
strip is slot-ordered on every Head; that Head's active entry uses the accent
foreground. Saved Views live in the recall/save/update menu; updates choose a
named target and confirm replacement rather than infer it from the last recall.
Each independently shaped run
owns its click region, reusing ordinary Activity resume and lease prompts.
Budgeted overflow retains the active entry and nearby slots when possible,
with a dedicated … target opening an Activity-only picker. Neither clipping
nor clicking invents new layouts, changes membership, or takes leases silently.
src/x11/status.lisp owns opt-in managed provider lifecycle and cached data,
separate from bar.lisp painting and status-desktop.lisp source protocols.
Each managed provider has one cancellable worker per backend, not per Head.
Workers publish changed data snapshots under a lock and send one coalesced
_LIME_STATUS_CHANGED event through a separate, serialized CLX notification
connection to an unmapped adapter-owned window. They never use the WM's X
connection or access model state. The ordinary X event loop consumes the wake
and redraws bars on their owning thread—even when there has been no keyboard,
mouse, or client event. No status refresh timer is attached to bar rendering.
Music publishes playback/title/source facts, volume publishes percentage/mute/
sink facts, and the clock publishes local date fields. A configuration-supplied
:formatter consumes a defensive copy on the X thread, outside worker locks,
and returns text, NIL, or a HEAD-BAR-SEGMENT with optional foreground, width,
and click overrides. status-format.lisp contains optional, parameterized
presentation helpers, not subscription logic. Config therefore chooses icons,
spacing, date style, visibility, and colors without editing protocol code.
Legacy generic providers may still publish strings through an identity
formatter. Formatting failures hide one widget without stopping its source or
other widgets. The bar's overall left/title/right geometry remains renderer
policy; this seam does not claim an arbitrary bar-layout language.
MPD snapshots and idle use the same native connection, retaining events
between query and subscription. MPRIS uses the documented
playerctl follow interface;
audio uses a persistent pactl subscribe process and bounded one-shot reads.
The clock alone schedules a wall-time deadline, recomputed for each minute.
See the MPD idle protocol
for the accumulated-event guarantee. Reconnection timers use 1–30 second
backoff only after service failure; they are not a healthy-state polling loop.
Configuration validation accepts managed sources alongside legacy pure
provider functions. Reconciliation keeps identical provider objects, cancels
removed workers, and starts replacements; rollback reinstalls the previous
set. Shutdown wakes sleeping workers, shuts down owned MPD sockets, and kills
and reaps only owned child helpers. Provider failures are retained in
backend-status-provider-report, not allowed to kill the X loop or display
stale cached data. Public worker functions must cooperate with cancellation.
Bar text is shaped and measured by the shared Pango/Cairo font layer, with a
bounded per-font metrics cache. A server-free text-layout helper computes pixel-budgeted truncation and
positions, shared by drawing and click regions. Identity and layout fields
use measured | separators; the title is left-aligned inside an expanding
section with two muted one-pixel edges, and status text is right-aligned.
Validated :padding-x controls matching outer text margins and title insets;
the separator's own space supplies the gap after Lime, without duplicate
logo padding. The same layout data positions paint and click targets. A font-derived optical
baseline centers a stable letter-body reference instead of unused line leading.
Every bar field uses that baseline, independent of its current text or fallback
font's ascent, avoiding title-content-dependent vertical jumps.
HEAD-BAR-STYLE is validated configuration, separate from window appearance
and layout. Each bar caches its own colormap allocations and releases unused
colors on redraw/configuration rollback and shutdown. Oversized status text is truncated while
preserving room for the layout control and a small Activity navigation area.
Ordinary separators and status truncation dots do
not acquire status click actions. Each provider is shaped independently so
bidirectional text cannot reorder adjacent widgets; click regions use the
shaped source text's visual ranges, not an assumed character advance.
src/x11/attention.lisp owns a backend-only, two-source request cache for
ICCCM urgency and EWMH demands-attention. Property/client-message events update
it without touching the world revision. The renderer's global input target and
normal FocusIn events acknowledge only a selected visible client with verified
X focus; another Head's local focus, root focus, or a palette grab is not enough.
The cache is not serialized: adoption rereads surviving clients' protocol state.
The bar renders a configurable global count using its existing status-segment
and click machinery; the picker consumes cached data and calls
backend-attend-client. Visiting an Activity resumes its current arrangement
without restoring stale claims for unrelated windows.
See attention for the protocol and interaction boundaries.
The cached client title prefers EWMH _NET_WM_NAME decoded with Babel over
ICCCM WM_NAME. Title/class property events repaint bars without touching
geometry, leases, or draft dirty state. src/x11/text-native.lisp contains
the narrow CFFI boundary to Pango, Cairo, and GLib. src/x11/text.lisp owns
font descriptions, shaping/measurement, grapheme-safe fitting, clipped
painting, and pixel upload for both bars and the palette. Pango handles
Fontconfig fallback and shaping, including color emoji with installed fonts.
Cairo paints a complete opaque RGB frame in memory; CLX uploads it on the
existing WM connection. The font renderer needs no extra Xlib connection or
toolkit event loop (the optional status notifier described above is separate).
The adapter requires a TrueColor root visual and converts RGB pixels to its
actual masks (including 16-bit displays). The pure model and lime/ui
remain independent of these native libraries.
Each UI surface owns a font/layout and a resize-reused canvas, operated only
by the backend thread. Font replacement allocates before freeing the old
resource; close, Head removal, resize, rollback, and shutdown release native
resources explicitly. UI-FONT-STYLE is validated configuration, shared by
both surfaces. A font reload repaints/resizes an open palette without replacing
its search session. This font-rendering boundary does not add an IME.
src/x11/ui.lisp builds the command catalog/tree and owns keyboard-grab/input
routing. src/x11/ui-render.lisp owns the temporary X11 surface, fonts,
canvases, measured geometry, and painting. Its server-free grid projection
chooses up to four columns, fits labels in pixel budgets, and pages large
trees within the Head-local dropdown. The renderer clamps the session's
requested page when font/Head dimensions change. Height follows the current
menu or picker instead of reserving a fixed ten-row block. Keys, submenu
labels, breadcrumb, and footer have distinct visual roles; detailed candidate
descriptions appear only for the selected search result. The dropdown reuses
the configured bar colors through the adapter's style seam, rather than
hard-coding black/white. It draws directly below the persistent bar. Its Activity and
View choices call the command layer. A collision first performs the normal
read-only preflight, then offers the already-public strict, partial, and take
commands explicitly. The overlay is frontend state only: no X11 window,
keyboard grab, search query, or visual styling leaks into lime.core.
The same replacement-candidate mechanism drives RandR reconciliation. A
pending added/removed-Head snapshot contributes one direct-search candidate
and a Head -> topology key-path entry. The frontend collects one move or
suspend choice for every removed live Presentation, shows a final Apply choice,
then calls the separate RandR build/apply commands. It never reacts directly
to an output event or invents a second modal UI protocol.
Super-y and the bar logo open the key root; Super-p remains the separate fuzzy program picker. Executable discovery is cached in the launcher adapter, keyed by PATH, current directory, and PATH-directory modification dates. A five-minute maximum cache age catches permission-only/coarse-timestamp changes on the next open; no timer polls while idle. Name deduplication uses a hash table rather than quadratic list comparisons. Each palette gets fresh candidates/actions; the cache holds only executable names, not a stale backend or UI session. Space (unless explicitly bound in a custom tree) or Tab opens global search; Tab returns to the root map. Prompts retain literal spaces and cannot switch modes with Tab. Backspace goes up a prefix and Escape dismisses the entire overlay without dispatching another command. Key cells and search results are also clickable, with hover feedback and small Back/Search/Menu/ Confirm/Close controls as appropriate. Wheel input navigates menu pages or search selection; paged grids also expose previous/next buttons. Geometry is shared by painting and clipped hit regions, rebuilt each frame. Mouse events on the overlay are consumed before client focus/drag routing, including clicks in blank space. No global pointer grab is needed. Both input paths share the same session transitions and error boundary; candidate clicks carry the actual candidate object and reject stale choices instead of reusing a list index.
Richer text input, dynamic workflow-specific trees, themes, and decorations can reuse the same session protocol or add another renderer without changing Activity/View/Lease rules.
Direction of dependencies
Dependencies should point inward toward the core, never the reverse:
input / pointer / menu / IPC -> commands -> lime.core
user configuration -> commands + input + bar + lifecycle policy
ui session -> commands -> lime.core
x11 bar / UI renderer -> ui session + CLX + adapter snapshots
lifecycle -> lime.core
icccm -> CLX
ewmh -> commands + CLX + active Lease queries
randr -> CLX + backend Head rectangles + core topology plans
render -> lime.core + ICCCM/EWMH + CLX
backend -> CLX + module interfaces
That leaves room for an IPC frontend, richer Viewset editing, an interactive RandR topology picker, remaining ICCCM/EWMH work, richer pointer policy, and alternate layout engines without forcing the Activity/View/Lease model to know about physical output APIs.