# Xephyr demo `lime/x11` is Lime's first deliberately small adapter from the pure Activity/View/Presentation/Lease model to real X11 operations. It exists to make the model visible and testable early. It is not a complete X11 window manager yet. ## Run it safely Use a clean nested X server. Do not run the prototype on the same root window as an existing window manager: X11 allows only one client to select `SubstructureRedirect` on a root window. ```sh Xephyr :99 -ac -screen 1000x700 -br -nolisten tcp ``` In a second terminal, in the repository root, start Lime in a Lisp image with Quicklisp, CLX, Babel, and CFFI available. Install the native Pango/Cairo libraries (including PangoCairo) and your desired fonts first: ```lisp (ql:quickload '(:clx :babel :cffi)) (asdf:load-system "lime/x11") (lime.x11:run-demo :display-name ":99") ``` For a user-owned configuration file instead of the starter map, load the same system and start through `lime.config:start-lime`: ```lisp (lime.config:start-lime :display-name ":99" :config-file #P"/home/adan/.config/lime/config.lisp") ``` The configuration file interface, command registry, reload behavior, and initial floating rules are documented in [Configuration](configuration.md). In a third terminal, create some clients: ```sh DISPLAY=:99 xterm DISPLAY=:99 xterm ``` For two clients the fallback layout divides the 1000px-wide server into two 500px columns below Lime's default 22px Head bar. When either client exits, the other immediately occupies the full usable width. This was exercised against Xephyr during implementation. ## Read the first Head bar Every Head owns a small persistent top bar. Its initial left-to-right shape is roughly: ```text Lime | chill work code | []= focused window title music | volume | clock ``` The local Activity is highlighted. Saved Views are checkpoints recalled from the menu, not nested workspaces, so there is no View/Draft/dirty suffix. `[]=` is master/stack, `|M|` is the retained manual tree, and `[N]` is monocle with `N` tiled clients in its stack, including the visible one. Hidden Activity clients and clients leased elsewhere are excluded. Individual floats do not change that token; Lime has no fake all-floating layout. The expanding, left-aligned title section has quiet one-pixel edges on both sides. Its background and text colors identify the focused Head, without a center underline or a redundant "left Head" or "right Head" label. The fields use actual font measurements rather than fixed-width columns. The left fields use measured, contrasting ` | ` separators; long names, titles, and status text are shortened to fit their pixel budgets. Click targets follow the displayed text, excluding status separators and ellipses. The right-hand music/volume/clock above are illustrative providers, not built-in integrations. Bar colors, including optional title edges, live in `(:bar ...)`. Titles prefer UTF-8 `_NET_WM_NAME`, then Latin-1 `WM_NAME`, then the client class/name or "Untitled window". Property notifications refresh the bar immediately. The shared Pango/Cairo renderer preserves Unicode, shapes text, and uses system font fallback, including color emoji when installed. Configure both the bar and dropdown with `(:font :family "monospace" :size 14 :weight :normal)`; size is in pixels. Long text is shortened at grapheme boundaries, not in the middle of an accented character or joined emoji. See [font configuration](configuration.md#fonts) for family lists and live reload. Normal tiles and floats use the client work area below the bar. A real EWMH fullscreen client takes the whole physical Head and hides its bar. Click `Lime` to open the same Activity/View palette as `Super-y`; click the layout token to cycle master/stack and monocle. Manual remains available through Lisp and saved Views, but is hidden from the everyday cycle and layout menu. The palette is a dropdown below the bar, so the current Activity stays visible while searching. The right side has no built-in shell commands. Configure optional status providers in Lisp instead: ```lisp (lime.x11:backend-set-head-bar-status-providers *lime* (list (lambda (backend head-id) (declare (ignore backend head-id)) (lime.x11:make-head-bar-segment :id :clock :text "12:34")))) ``` Each provider receives `(backend head-id)` and may return one segment or a list. A segment can also carry a `:click-action` function of those same two arguments. ## Exercise Activities and Views from the REPL The first control surface is intentionally Lisp-first. Start the backend in the REPL and give its event loop a background SBCL thread: ```lisp (defparameter *lime* (lime.x11:start-backend :display-name ":99")) (defparameter *lime-event-thread* (sb-thread:make-thread (lambda () (lime.x11:run-event-loop *lime*)))) ``` Now the initial task workflow is directly usable: ```lisp ;; 1. A fresh named task with a blank unsaved live draft. (lime.x11:backend-start-activity *lime* "wm" "WM project") ;; 2. Launch clients with DISPLAY=:99; they enter the WM project draft. ;; 3. Capture the current arrangement as a checkpoint. (lime.x11:backend-save-view *lime* "wm-code" "Code") ;; 4. Arrange through the model, then capture a second checkpoint. (lime.x11:backend-set-layout-root *lime* my-debug-layout) (lime.x11:backend-save-view *lime* "wm-debug" "Debug") ;; 5. Recall either View. The selected policy is explicit. (lime.x11:backend-activate-view *lime* "wm-code" :policy :strict) (lime.x11:backend-activate-view *lime* "wm-debug" :policy :partial) ;; 6. Inspect the active Head without digging through structures. (lime.x11:backend-status *lime*) ``` `backend-update-view` deliberately overwrites an existing checkpoint from the live draft. `backend-resume-presentation` restores a cached draft by id; both accept the same `:strict`, `:partial`, and `:take` collision policies where they apply. Every transition applies the resulting Presentation to X11 before returning. If a REPL or future IPC client only has a human-facing name, these helpers make the same palette workflow available without hand-writing an id: ```lisp (lime.x11:backend-start-named-activity *lime* "WM project") (lime.x11:backend-save-named-view *lime* "Code") ``` They derive readable unique ids and make the saved View current, so the normal `backend-update-current-view` command follows the new checkpoint. ## Capture and recall a whole-desk Viewset A Viewset is the explicit multi-Head companion to a single-Head View. It saves the current `Head -> saved View` mapping, then later recalls every named target as one atomic transition: ```lisp ;; Every selected Head must currently be mounted from a saved View. By default ;; this captures all configured Heads in display order. (lime.x11:backend-save-named-viewset *lime* "Work desk") ;; Later, recall every mapped Head together. (lime.x11:backend-activate-viewset *lime* "work-desk" :policy :strict) ``` `backend-save-viewset` accepts an explicit id/name and optional `:head-ids` when you deliberately want a subset. It never serializes the live draft; save the relevant View first if a Head has unsaved changes. Viewset recall uses the same `:strict`, `:partial`, and `:take` vocabulary as View recall, but checks the selected Heads as one outgoing group. That is what makes a saved left/right swap work without briefly duplicating a View or lease. A client held by an unselected Head can be left there (`:partial`) or deliberately taken (`:take`). Two target Views that request the same client are rejected as an ambiguous Viewset—Lime will not pick one based on ordering. `bind-viewset-key` is the corresponding opt-in Lisp configuration helper: ```lisp (lime.x11:bind-viewset-key *lime* #\w "work-desk") ``` The focused client is also saved on the live Presentation. Focus a particular visible client or cycle its leased clients without exposing raw CLX calls: ```lisp (lime.x11:backend-focus-client *lime* "x11-...") (lime.x11:backend-focus-next-client *lime*) (lime.x11:backend-focus-previous-client *lime*) ``` `backend-status` includes `:focus-client-id`. The renderer sends X input focus to that registered visible client after it maps all windows; a blank selected Head focuses the root window instead. ## Use built-in layouts and manual tree edits Layout policy belongs to the active Head-local Presentation, so it is an unsaved draft change until `backend-save-view` or `backend-update-view` is called. The first built-ins are master/stack and monocle: ```lisp ;; First tiled client is the 60% left-hand master; the rest share the stack. (lime.x11:backend-use-master-stack-layout *lime* :master-count 1 :master-ratio 3/5 :orientation :horizontal) ;; Promote the selected Head's focused tiled client to the master slot. (lime.x11:backend-promote-focused-client *lime*) ;; Full-Head focus mode. Other tiled clients remain leased but are unmapped. (lime.x11:backend-use-monocle-layout *lime*) (lime.x11:backend-focus-next-client *lime*) ; shows the next client in monocle ;; Return to the retained binary tree, then edit the focused client's nearest ;; split. These actions deliberately make the draft :MANUAL. (lime.x11:backend-use-manual-layout *lime*) (lime.x11:backend-orient-focused-split *lime* :vertical) (lime.x11:backend-resize-focused-split *lime* 1/20) (lime.x11:backend-swap-focused-client *lime* :direction :next) ``` `backend-status` exposes `:layout-mode` and `:layout-options` for UI/IPC frontends. The optional starter map below covers everyday commands; the named helpers add task and physical-output shortcuts without hard-coding them. ## Float a client and use Super-drag Floating is placement state, not a second kind of client or lease. Toggle the focused client, then optionally set an exact Head-relative rectangle: ```lisp (lime.x11:backend-toggle-focused-client-floating *lime*) ;; All values are normalized fractions of the selected Head. (lime.x11:backend-set-client-floating-geometry *lime* "x11-..." :x 1/5 :y 1/6 :width 3/5 :height 2/3) ;; The same generic operation is useful to a future menu or IPC client. (lime.x11:backend-toggle-client-floating *lime* "x11-...") ``` The renderer places visible floats above tiled content. `backend-status` includes `:floating-client-ids`; save or update the active View to make a float checkpoint durable. On a floating client, hold Super (`Mod4`) and use Button1 to move or Button3 to resize. Super+Button2 toggles the clicked client's floating placement, the same way as Super+Space. The adapter converts each root-pixel drag motion into the same normalized geometry command above. Super-drag only claims floating clients; on a tiled client it simply focuses the window and changes no geometry. ## Click to focus An unmodified Button1 anywhere in a managed client's window tree selects the Head leasing it and focuses that client, then the press is replayed to the application. Button1 on a blank part of the root selects the Head under the pointer and focuses root, while retaining that Presentation's saved client-focus target for later keyboard cycling or another client click. A background click is thus not a View edit. The initial pointer policy reserves Super+Button1/2/3 for float move/toggle/ resize. Menus, focus-follows-pointer, button rebinding, and robust Caps/Num- Lock modifier normalization remain separate future policies. ## Inspect ICCCM client facts or close a client `x11/icccm.lisp` tracks the conventional title, resource name/class, `WM_PROTOCOLS`, InputHint, transient parent, and normal size hints for every managed client. This data stays out of the Activity/View model and is available to a future UI: ```lisp (let ((client-id (first (getf (lime.x11:backend-status *lime*) :client-ids)))) (lime.x11:backend-client-icccm-info *lime* client-id)) ;; Sends WM_DELETE_WINDOW when supported; only legacy clients use XKillClient. (lime.x11:backend-close-focused-client *lime*) ``` Focus now honors ICCCM's InputHint and sends `WM_TAKE_FOCUS` when a client advertises it. When Lime denies a tiled client's ConfigureRequest, it restores the model geometry, adjusted to its cached normal hints, and sends that client a synthetic ConfigureNotify response. When a lease makes a client visible Lime publishes `WM_STATE` `NormalState`; when a View hides it, Lime publishes `IconicState` while retaining the client in its Activity. Those renderer-originated unmaps are tracked explicitly. If the application later unmaps itself, Lime recognizes the distinct ICCCM withdrawal, removes `WM_STATE`, and retires that client from its Views. ## Fullscreen and small EWMH compatibility `Super-f` follows your dwm binding and toggles true fullscreen for the focused client. It also responds to a client's `_NET_WM_STATE_FULLSCREEN` request, including the initial state a game publishes before mapping. The fullscreen window receives the entire rectangle of the Head holding its exclusive Lease, is raised above the other windows on that Head, and becomes the input focus. Normal size hints intentionally do not constrain that fullscreen rectangle. Fullscreen is temporary EWMH adapter state, not a saved View edit. Switching to a different View hides the game through its normal Lease; returning to the View restores fullscreen on whichever Head legally holds that Lease. A second fullscreen request on the same visible Head replaces the first. Lime also advertises and handles `_NET_ACTIVE_WINDOW` for already visible clients and `_NET_CLOSE_WINDOW`; it publishes `_NET_ACTIVE_WINDOW` while it controls focus. Lime also creates the required `_NET_SUPPORTING_WM_CHECK` window and keeps `_NET_CLIENT_LIST` plus `_NET_CLIENT_LIST_STACKING` current, so ordinary desktop tools can identify the WM and enumerate its clients. It deliberately does **not** implement fake numbered EWMH desktops. `_NET_WM_WINDOW_TYPE_DIALOG`, `UTILITY`, and `SPLASH` receive the same centered floating/focus behavior as a transient dialog when no `WM_TRANSIENT_FOR` parent exists. `DOCK` and `DESKTOP` windows are mapped at their application-supplied geometry but never become Activity/View task clients. External dock struts are intentionally a later work-area pass. ## Dialogs and normal size hints When a newly managed window declares `WM_TRANSIENT_FOR` for a client Lime is already managing, it follows that parent’s active Presentation and Head rather than the most recently selected Head. Lime adds it as a float, centers it over the parent, and focuses it. The dialog is still a normal placement and lease, so it disappears/restores with the same View rules as every other client. Before every X11 configure, Lime applies the client’s `WM_NORMAL_HINTS` minimum/maximum size, base-size, and resize increments. This includes tiling, direct float geometry commands, and every Super-resize motion. The requested Lime rectangle remains model state; the final client-compatible pixel rectangle is adapter-local, so client constraints never mutate a saved View. If a client demands more than the whole Head, Head containment wins. ## Exercise two independent Heads On a normal X11 session, `start-backend` uses RandR automatically. Every active CRTC becomes a Lime Head named after its representative output (for example, `DP-1` and `DP-3`). Mirrored outputs sharing one CRTC deliberately become one Head, so Lime never renders the same task rectangle twice. Inspect the live hardware snapshot with: ```lisp (lime.x11:backend-randr-status *lime*) ``` Xephyr normally exposes one virtual output, so explicit rectangles remain a useful way to exercise two independent Heads in the nested demo. This 1000×700 server becomes a left and right 500×700 Head: ```lisp (defparameter *lime* (lime.x11:start-backend :display-name ":99" :heads (list (lime.x11:make-x11-head :head-id "left" :x 0 :y 0 :width 500 :height 700) (lime.x11:make-x11-head :head-id "right" :x 500 :y 0 :width 500 :height 700)))) ``` Each configured rectangle receives its own active Presentation: initially an unsaved, blank `Scratch` draft rather than a fabricated empty View. A saved View is never mounted on two Heads, while two Head-local drafts may show the same Activity when their exclusive window leases do not collide. Set the temporary routing target before opening a window for a different Head: ```lisp ;; Keep the existing Scratch task on the left. (lime.x11:backend-start-activity *lime* "game" "Game" :head-id "right") (lime.x11:backend-select-head *lime* "right") ;; A subsequent DISPLAY=:99 client joins Game on the right. (lime.x11:backend-status *lime* :head-id "left") (lime.x11:backend-status *lime* :head-id "right") ``` `backend-select-head` selects the routing target and applies that Head's saved focus target as real X input focus. Head rectangles must be positive, inside the X root, and non-overlapping. Explicit `:heads` always takes precedence over discovery; pass `:head-discovery :none` to force the old single-root fallback instead. Lime subscribes to RandR screen/CRTC/output notifications for a discovered startup. An event only refreshes the observed topology and marks it pending: it will never silently decide where an Activity, View, or Lease should go when a monitor is unplugged. `backend-refresh-randr-heads` safely applies a resolution/position change when the same Head ids remain; an added or removed Head returns `:reason :topology-changed`. For that case, keep observation, choice, and mutation separate: ```lisp ;; Read-only data for a future palette, IPC client, or REPL inspection. (defparameter *randr-change* (lime.x11:backend-randr-topology-report *lime*)) (getf *randr-change* :removed-active-sources) ;; Example after DP-3 disappears: keep its exact live draft on DP-1. ;; Omit a removed source from :MOVES to suspend its draft and release leases. (defparameter *randr-plan* (lime.x11:backend-build-randr-topology-plan *lime* :moves (list (lime.core:make-head-topology-move :from-head-id "DP-3" :to-head-id "DP-1")))) ;; Rereads RandR, rejects a physically stale plan, then renders the result. (lime.x11:backend-apply-randr-topology-plan *lime* *randr-plan*) ``` An added Head without an incoming move is blank. Select it, then use the ordinary palette or `backend-start-named-activity` / `backend-activate-view` commands to put the task you actually want there. ## Watch a cross-Head lease collision resolve With two clients on the left Head, save two overlapping checkpoints from that Head and ask to mount one on the right: ```lisp (lime.x11:backend-save-view *lime* "code" "Code" :head-id "left") (lime.x11:backend-save-view *lime* "docs" "Docs" :head-id "left") ;; Read-only: returns an ACTIVATION-REPORT with :CLIENT-LEASED conflicts. (lime.x11:backend-preflight-view-activation *lime* "docs" :head-id "right") ;; "Open what is available": left stays visible; right records holes. (lime.x11:backend-activate-view-partially *lime* "docs" :head-id "right") ;; "Move windows here": explicit transfer; the old left draft becomes partial. (lime.x11:backend-activate-view-taking *lime* "docs" :head-id "right") ``` `backend-activate-view-strictly` is the default “open only if complete” command: it signals `activation-blocked` rather than changing either Head if any lease conflicts. The preflight function does no mutation; a future menu can inspect its report, offer the complete, partial, and move-here commands, or cancel. The adapter applies all configured Heads after a transition, so an explicit take unmaps/reconfigures the source and target in one visual update. ## Install the starter keymap and name your own task shortcuts `start-backend` (and therefore `run-demo`) installs a Super (`Mod4`) keymap for ordinary window operations. It has no Activity, View, or physical Head names, so it does not guess at your workflow. Reinstall it after clearing or changing bindings, or opt out at startup for a fully custom map: ```lisp ;; Reinstall with a different primary modifier. (lime.x11:install-default-keybindings *lime* :modifiers '(:mod-1)) ;; Or start with no defaults and bind everything yourself. (lime.x11:start-backend :display-name ":99" :install-default-keybindings-p nil) ``` | Binding | Command | | --- | --- | | Super-t / Super-h | Focus next / previous visible client | | Super-Shift-t / Super-Shift-h | Move focused tiled client forward / backward | | Super-Return / Super-z | Promote focused client / focus primary tiled client | | Super-Tab / Super-Shift-Tab | Cycle master-stack ↔ monocle forward / backward | | Super-d / Super-n | Shrink / grow master area (master-stack only) | | Super-Up / Super-Down | Add / remove a master client (master-stack only; minimum one) | | Super-Space | Toggle focused client floating | | Super-f | Toggle focused client fullscreen | | Super-i / Super-Shift-i | Show a hidden/shared window / hide the focused window | | Super-Shift-c | Close focused client | | Super-Shift-q | Quit Lime cleanly | | Super-Shift-s | Update the mounted saved View from its live draft | | Super-p | Open fuzzy PATH-program picker | | Super-y | Open the Activity/View command palette | | Super-e / Super-Shift-e | Select next Head / send focused client to next Head | `Super-Shift-i` parks the focused window only from the current Head-local draft: it does not close, minimize, or remove the client from its Activity. `Super-i` opens a fuzzy list of parked Activity windows plus shared windows and reveals the chosen one in the current draft. If the window is presently visible on a different Head, Lime asks for an explicit **Move here** confirmation rather than showing one X11 window twice. Saving or updating a View records its current shown/parked set, so this is the everyday way to keep a focused monocle or tiled context uncluttered without inventing another workspace. Under `Super-y → w` (Windows), `m` moves the focused window to an existing Activity. Create one first through `a → n` if needed. The move keeps you here and queues the window in the destination's working draft for this Head; opening that Activity shows it. A private window is removed from its old checkpoints and cached drafts, so recalling them cannot pull it back across Activities. Another Head already displaying the destination Activity is not rearranged. `Super-y → w → s` makes the focused window shared. It keeps one home Activity, but becomes available through `Super-i` in every Activity. It does not appear everywhere automatically. If another Head leases it, the picker offers an explicit **Move here**; shared slots taken away can be summoned back using the same picker. Views may save shared placements, subject to normal lease conflicts on recall. On a shared window, `w → s` instead offers **Make private to Activity**: the Activity where you're using it becomes its home. The window stays on this Head, with the same position and focus. Saved views in this Activity keep it; references in every other Activity (including its old home) are removed. The confirmation names the Activity and defaults to Cancel; choose the named action with arrows and Enter, or click it. Changing the desk while the prompt is open invalidates the choice rather than acting on a different window or Activity. `Super-Shift-e` (or `Super-y → h → s`) sends the focused window to the next Head. Same-Activity and shared-window transfers happen immediately, without changing home membership. For a private window crossing Activities, the dropdown names both Activities and offers **Cancel** (default) or **Move to destination**. Choose with arrows and Enter, or click; Escape cancels. The warning explains that moving removes the private window from its old saved views. Changing the desk while this confirmation is open invalidates it. Neither Head switches Activities; keyboard focus follows the window automatically, so you don't need a second `Super-e`. `Super-e` still switches monitors without moving a window. The destination selects the arriving window in its arrangement, so it is visible even in monocle. A destination View or draft that already contains the client controls the placement there; otherwise Lime creates an unsaved destination placement matching the source tile/float kind. The old Head loses the client and focuses a surviving window. The low-level `backend-transfer-focused-client-*` functions remain strict, non-prompting lease transfers; `backend-rehome-client-to-head` is the separate explicit rehome API. Lime does not yet have dwm's all-floating layout; floating remains per-client. The retained manual tree is accessible through Lisp and existing Views, not through the everyday layout cycle. For names that only you can choose, use the configuration helpers. The strict View policy below is safe by default; a future picker can use the same preflight result to offer partial or take alternatives: ```lisp ;; Super is normally Mod4 on X11. (lime.x11:bind-head-key *lime* #\1 "left") (lime.x11:bind-head-key *lime* #\2 "right") ;; A View binding chooses an explicit collision policy. (lime.x11:bind-view-key *lime* #\d "docs" :policy :strict) ;; A Viewset binding atomically applies its saved Head -> View mapping. (lime.x11:bind-viewset-key *lime* #\w "work-desk" :policy :strict) ;; Without :VIEW-ID this resumes the Activity's unsaved Head-local draft, ;; creating a blank draft on that Head when needed. (lime.x11:bind-activity-key *lime* #\g "game" :view-id "play") (lime.x11:list-keybindings *lime*) (lime.x11:clear-keybindings *lime*) ``` Handlers that signal an error—such as a strict activation blocked by a lease—do not kill the event loop. Inspect `backend-last-command-error` from the REPL, and use `clear-keybindings` during reconfiguration. `bind-key` remains available for arbitrary commands. The first pass matches modifier state exactly, so Caps Lock/Num Lock tolerance and an actual focus policy remain later input-layer work. ## Open the first command palette Press Super-y to open a which-key-style command map below the bar. It shows only the current prefix's keys in a compact grid; `+` labels are submenus. Press a shown key to enter a group or run a command. Space or Tab opens direct search; type to filter, use Up/Down to select, and Enter to run. Tab in search returns to the root map. Escape closes in every mode. Super-p remains the separate fuzzy program launcher. This is a real X11 overlay with a temporary keyboard grab, not a managed client, so it is local to the currently focused Head and does not affect client leases by itself. The launcher reuses its executable catalog while PATH directories are unchanged; rapid typing immediately after the shortcut is queued for the picker, not sent to the previously focused application. The isolated `test/x11-launcher-smoke.lisp` checks complete shortcut-plus-query-plus-Enter bursts and keyboard error cleanup. The direct-search catalog includes `New named Activity...`, `Save current arrangement as View...`, and—when every configured Head has a saved source View—`Save current Heads as Viewset...`. Press Enter on either to enter a one-line name prompt; Enter confirms it and Esc cancels it. A named Activity opens a blank unsaved live draft; it owns no permanent View until you save a meaningful checkpoint. Saving a View checkpoints the live draft and mounts that new checkpoint, so Super-Shift-s subsequently updates the View you just named. Lime derives readable durable ids (`WM Project` becomes `wm-project`; `Code` within it becomes `wm-project/code`) and adds a numeric suffix if needed, while keeping your original title as the display name. If RandR reports an added or removed Head, the searchable catalog also contains `Reconcile changed displays...`. Selecting it asks once for every live Presentation on a removed Head: move it to one of the discovered Heads, or `Suspend safely`. The last screen summarizes every move and suspension; `Apply display plan` is the only action that calls the topology command. Thus an unplug event never grabs attention or moves a task until you deliberately open the palette and confirm the plan. The map shows a breadcrumb instead of a search input. Backspace moves up one level; PageUp/PageDown reveal additional keys if a large custom tree or narrow Head requires pages. The panel sizes itself to the current entries and follows the configured font and bar colors. The first level is deliberately small: | Key | Menu | | --- | --- | | a | Activity (`a n` creates a named Activity; `a o` searches) | | v | View (`v s` saves; `v u` updates; `v o` searches) | | x | Viewset (`x s` saves current Heads; `x o` searches) | | l | Layout (master/stack, monocle, cycle) | | w | Window (focus, float, close) | | h | Head (select, send a client, or `h t` reconcile changed displays) | | r | Launch (terminal, browser, explicit shell command) | | q | System (`q q` requests a graceful quit) | | u | User (when the config supplies commands with `:key`) | | Space | Search all commands | For example, `Super-y`, `v`, `s` reaches the same new-View name prompt without typing a search query. The key tree only calls the existing command layer, so it carries no independent Activity, layout, or lease policy. When a display change is pending, `Super-y`, `h`, `t` reaches exactly the same reconciliation flow as the direct-search candidate. `Super-y → a → d` opens **Delete activity...** (also available in direct search). First move or close every window owned by the Activity, including hidden/shared ones, and switch all Heads away from it. The picker explains either blocker. Choose the Activity, then explicitly select **Delete Activity: name**; the confirmation defaults to Cancel. This removes its saved Views and cached drafts, prunes their Viewset entries, and deletes any now-empty Viewsets. No app closes and no Head switches. There is no undo; a changed desk invalidates the confirmation and requires reviewing it again. Deletion is saved by ordinary session persistence. Selecting a ready View uses the normal strict activation command. Selecting a View whose windows are leased on another Head replaces the result list with three explicit options: | Choice | Command it runs | | --- | --- | | Recall without busy windows | `backend-activate-view-partially` | | Move windows here | `backend-activate-view-taking` | | Cancel | No model transition | The palette includes `Update saved view...` when the current Activity has saved checkpoints, even if the live arrangement has no source View. Choose a target and confirm its name before replacing it; Cancel is the default choice. `Recall view...` explicitly restores a checkpoint, including the last one used. Named-entity pickers, lease-conflict choices, and name prompts remain separate transitions inside the same overlay; the command map does not force those choices into a giant tree. Returning to the map and searching again restores the full catalog, not the last scoped picker. ## What the adapter does `start-backend` creates one Head for each configured rectangle and an initial `inbox` Activity/View for each, claims the root window, and adopts already-viewable normal clients into the selected Head. Each normal `MapRequest` then: 1. creates a Lime client in the active Presentation's Activity if necessary; 2. adds it only to the active Presentation's live draft; 3. gives it a lease when free; and 4. resolves the Head-local layout tree into X11 work-area rectangles before mapping the leased clients; and 5. focuses the selected Head's preferred visible client (or root if blank). `apply-active-presentation` is the narrow convenience boundary around a declarative render plan and its X11 executor. `build-render-plan` produces inspectable configure/map/unmap/raise/focus effects for every configured Head; `execute-render-plan` flushes them as one CLX batch and refuses a stale world revision. `DestroyNotify` lifecycle handling retires a permanently gone client from the model before the remaining geometry is reapplied. See [the architecture document](architecture.md) for the module contracts. ## Intentional omissions This initial bridge is purposefully not a complete ICCCM/EWMH-compliant daily-driver. It currently has no per-client decorations, theme system, custom/dynamic key-path palettes, focus-follows-pointer policy, aspect-ratio hints, general floating rules or stacking controls, session persistence, or complete View/Viewset editing UI. It implements a small ICCCM slice: basic client facts, `WM_DELETE_WINDOW`, InputHint/`WM_TAKE_FOCUS`, transient-dialog placement, min/max/base/increment normal hints, and synthetic ConfigureNotify for managed ConfigureRequests. Its small EWMH slice advertises `_NET_SUPPORTED`, handles fullscreen/activate/close requests, identifies itself, publishes client and stacking lists, and maintains `_NET_ACTIVE_WINDOW`; it does not provide fake `_NET_WM_DESKTOP` state, external dock work areas, or the many other EWMH states expected of a mature desktop WM. Those are separate layers. The value of this adapter is that the core model can now be observed controlling real windows while those layers are added without smuggling policy into lease resolution.