Initial Lime window manager

This commit is contained in:
2026-10-02 14:14:47 -05:00
commit ccd691edd0
121 changed files with 29719 additions and 0 deletions

149
docs/persistence.md Normal file
View File

@@ -0,0 +1,149 @@
# Session persistence
Lime saves the **desk**, separately from your Lisp configuration: Activities,
named Views and Viewsets, current and cached drafts, hidden/shown membership,
home Activity and shared eligibility, layout order/options/weights, floating rectangles, focus, dirty flags, partial
View state, and logical Head selections. It does not serialize CLX objects,
fonts, key bindings, callbacks, running processes, or application contents.
`lime/dev` enables this automatically. The normal location is
`$XDG_STATE_HOME/lime/sessions/`, falling back to `~/.local/state/lime/sessions/`.
Files are separate for each DISPLAY (for example `display-_3A0.sexp` for `:0`,
`display-_3A99.sexp` for `:99`). Your nested test desk does not overwrite your
normal desktop's desk. A lifetime writer lock prevents two backends from using
one explicit file simultaneously.
Select a different file or disable persistence with launcher keywords—not an
additional Lime environment variable:
```lisp
(lime.dev:run-development-session
:config-file #p"/home/adan/.config/lime/config.lisp"
:session-file #p"/home/adan/.local/state/lime/my-desk.sexp")
;; For a disposable test:
(lime.dev:start-development-session :display-name ":99" :session-file nil)
```
The lower-level `lime.x11:start-lime` / `start-backend` APIs default to no
persistence; pass `:session-file :default` or a pathname to enable it. An
explicit `:world` and a session file are mutually exclusive startup sources.
Configuration reload does not change the selected session file.
Configured startup hooks still run normally; hooks that create named Activities
should tolerate those Activities already existing in a restored desk.
`scripts/run-xephyr-dev` supports `--session-file PATH` and `--no-session`.
Without either option it uses the display-specific default.
## What a restart restores
On a **Lime-only restart within the same X server**, surviving clients reconnect
to their Activities, checkpoints, and drafts—including unmapped hidden clients.
The adapter verifies a random server token plus each client's XID and random
window token. An XID, title, WM_CLASS, or PID alone is never enough: reused ids
must not attach an unrelated application's window to an old checkpoint.
On **logout, reboot, or X-server replacement**, the names and non-client layout
settings survive, but the old windows do not. Missing clients are retired from
the saved arrangements; an empty View remains a named checkpoint. This first
version does **not** relaunch applications, restore terminal/browser contents,
reserve placeholder slots for dead windows, or guess how newly launched
windows should fill an old arrangement. Application/session-relaunch rules are
a separate feature.
Current physical Heads remain authoritative. Matching logical Head ids resume
their saved selections. Drafts from removed Heads are suspended, not moved to
an arbitrary other monitor or deleted; new Heads get ordinary blank drafts.
Viewsets retain their logical mappings even when an output is absent. Leases
are reconstructed and validated, never accepted as serialized ownership objects.
## Saving and failures
Autosave observes model/focus changes on the owner thread. After 750 ms of
quiet, or five seconds of ongoing edits, it captures a validated plain-data
snapshot. Active pointer drags postpone capture until release. A single worker
writes snapshots off the X event thread, keeping only the latest pending one.
An unchanged desk produces no disk writes. A crash can lose edits since the
last completed autosave; this is a snapshot system, not a per-operation journal.
Normal shutdown flushes the final valid desk. A normal developer restart saves
**before closing X**; if that save fails, restart aborts with the old backend
still available for repair. Restart after an event-loop fault instead uses the
last good disk snapshot, avoiding an automatic save of partially mutated state.
`resume-development-session` still uses the retained in-memory world, not disk.
For an explicit flush or inspection from SLY:
```lisp
(lime.dev:save-development-session)
(lime.dev:with-development-backend (backend)
(lime.x11:backend-session-status backend))
```
The status includes the file, restore/missing-client report, last-save time,
submitted/completed write generations, and restore/snapshot/write errors. Disk
write failures are nonfatal to ordinary operation and retry with five-second
backoff; explicit flush reports the error to its caller. Invalid in-memory
snapshots block automatic saving until repaired and explicitly saved again.
Files are mode `0600`. Writes use a private temporary file in the same
directory, file `fsync`, atomic rename, and directory `fsync` on Linux. A failed
write before rename leaves the old file untouched; readers cannot see a partial
replacement. The format is versioned, bounded plain data. Reading never calls
`load`/`eval`, disables read-time evaluation, and rejects reader dispatch,
circular structures, excessive nesting, and trailing data. Unknown versions,
invalid relationships, duplicate leases, or malformed files are rejected before
replacing the startup world.
A rejected file is **not overwritten**: Lime starts a fresh usable desk with
autosave blocked and reports the error. Inspect/back up the file first. To
deliberately replace it with the current desk:
```lisp
(lime.dev:save-development-session :replace-invalid-p t)
```
This does not override another session's file lock. Stop that other session or
choose a different path. Back up important files separately; atomic writes are
not a backup/history system. Names and window associations can be private even
though the file contains no application buffers or process contents.
## Installing and testing
Membership support adds a client structure slot and ASDF components. Relaunch
Lime in a fresh Lisp process; configuration reload alone cannot install it.
World snapshots use schema version 3. The X11 session envelope uses
version 3, with ordered Activity positions, stable local-View shortcut slots,
last-arrangement history, and portable/extended Activity navigation policy.
Version-1/2 envelopes remain readable. Old Activities with one substantive
arrangement become portable; genuinely split arrangements remain extended.
This inference happens before missing-client pruning, preserving multi-monitor
intent across reboot. Old empty drafts are retained, not recalled or deleted.
Version-1 envelopes also receive initial shortcut assignments. Migration does
not move a window or alter what the Heads display. Navigation metadata is
validated before a saved world is installed. An explicit extended choice stays
extended when all its windows close; it is never re-inferred from window count.
Version 1 worlds load with all clients private. Version 1/2 worlds consolidate
historical drafts into one current arrangement per Activity/Head. Active state
wins, then saved navigation history, then the newest generated Presentation
id. Actual leases win duplicated window locations; missing checkpoint slots
are not live locations. Windows and saved Views are retained, and old source
labels are detached where a checkpoint was recalled elsewhere. Migration is
performed on a candidate world before installation; version 3 rejects duplicate
live locations rather than silently repairing them. Keep a backup before
upgrading or downgrading; older Lime builds may not read these schema versions.
The same-X-server identity rules above still apply to moved/shared clients.
```lisp
(asdf:test-system "lime/persistence")
```
```sh
sbcl --non-interactive --load test/x11-persistence-smoke.lisp
sbcl --non-interactive --load test/x11-membership-smoke.lisp
```
The smoke test uses its own Xvfb, temporary state files, and fake client windows.
It tests graceful/abrupt restart, hidden clients, dirty checkpoints, partial
leases, changed topology/identity, corrupt files, disk failures, and the actual
developer restart path. It does not connect to your running desktop.