488 lines
31 KiB
Markdown
488 lines
31 KiB
Markdown
# 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.
|
||
|
||
```text
|
||
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](development.md#recovering-from-an-event-loop-error).
|
||
|
||
`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](persistence.md).
|
||
|
||
`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:
|
||
|
||
1. `build-render-plan` derives inspectable `x11-effect` data 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.
|
||
2. `execute-render-plan` performs 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](https://specifications.freedesktop.org/xsettings/latest/).
|
||
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](https://github.com/altdesktop/playerctl#following-changes);
|
||
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](https://mpd.readthedocs.io/en/stable/protocol.html#idle)
|
||
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](attention.md) 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:
|
||
|
||
```text
|
||
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.
|