675 lines
34 KiB
Markdown
675 lines
34 KiB
Markdown
# Configuration
|
||
|
||
Lime’s configuration is a user-owned Common Lisp file—not a source-tree fork
|
||
and not a collection of patches. Load `lime/x11`, then start through
|
||
`lime.config:start-lime` with the file path you want to own, for example
|
||
`~/.config/lime/config.lisp`.
|
||
|
||
```lisp
|
||
;;;; ~/.config/lime/config.lisp
|
||
(in-package #:cl-user)
|
||
|
||
(lime.config:defconfiguration my-lime
|
||
(:options :default-keybindings t
|
||
:default-modifiers '(:mod-4)
|
||
:default-layout :master-stack
|
||
;; New tiles append at the bottom of master/stack, but receive
|
||
;; focus; closing one returns focus toward master.
|
||
:new-client-focus t
|
||
:new-tiled-client-position :back
|
||
:close-focus-direction :previous)
|
||
|
||
(:command terminal
|
||
:label "Terminal"
|
||
:description "launch Alacritty"
|
||
:category "Launch"
|
||
:key #\t
|
||
:action (lambda (backend)
|
||
(declare (ignore backend))
|
||
(uiop:launch-program '("alacritty") :output :interactive)))
|
||
(:command reload-config
|
||
:label "Reload configuration"
|
||
:description "reload this file without restarting Lime"
|
||
:key #\r
|
||
:action #'lime.config:reload-configuration)
|
||
(:command palette
|
||
:label "Command palette"
|
||
:description "open Lime's command picker"
|
||
:action #'lime.x11:backend-open-command-palette)
|
||
|
||
;; This replaces the starter map's Super-t binding with TERMINAL.
|
||
(:bind #\t terminal :modifiers '(:mod-4))
|
||
(:bind #\y palette :modifiers '(:mod-4))
|
||
(:bind #\r reload-config :modifiers '(:mod-4 :shift))
|
||
|
||
;; Rules are ordered. The first match styles an ordinary newly mapped
|
||
;; client; strings are case-insensitive exact ICCCM WM_CLASS matches.
|
||
(:rule :class "mpv" :float t :center t)
|
||
(:rule :class "Pavucontrol" :float t :center t)
|
||
|
||
(:font :family "monospace" :size 14 :weight :normal)
|
||
(:bar :height 22
|
||
:status-providers (list (lime.x11:make-clock-status-provider)))
|
||
(:appearance :border-width 3
|
||
:focused-border "#ffac54" :unfocused-border "#121f25"
|
||
:inner-gap 5 :outer-gap 5))
|
||
```
|
||
|
||
```lisp
|
||
(asdf:load-system "lime/x11")
|
||
(lime.config:start-lime
|
||
:display-name ":99"
|
||
:config-file #P"/home/adan/.config/lime/config.lisp")
|
||
```
|
||
|
||
`Super-y`, the clickable `Lime` bar item, and a command’s optional
|
||
`:key` all meet in the same command registry. `Super-p` is deliberately kept
|
||
for the immediate dmenu_run-like PATH-program picker. `:key` places a command under the
|
||
palette’s `User` which-key branch; it does not make an X key grab by itself.
|
||
Use `:bind` for that. Commands are ordinary functions, so anything beyond this
|
||
small DSL is just Common Lisp calling Lime's public command functions.
|
||
|
||
The program picker caches PATH executable names. It refreshes when PATH or a
|
||
directory modification date changes, or after five minutes on the next open
|
||
(covering executable-permission edits). For immediate rediscovery from the REPL,
|
||
use `(lime.x11::%path-program-names :refresh-p t)`. Opening shortcuts queue rapid
|
||
follow-up keystrokes safely until the menu takes over, including cold launches.
|
||
|
||
## Numbered navigation
|
||
|
||
The starter map binds `Super-1` through `Super-9`, then `Super-0`, to Activity
|
||
positions 1–10 in the Activity bar order. Add Shift to select View slots 1–10
|
||
**inside the current Activity**.
|
||
For a complete personal keymap with starter bindings disabled, opt in with:
|
||
|
||
```lisp
|
||
(:navigation-keys)
|
||
;; Or use another modifier family:
|
||
;; (:navigation-keys :modifiers '(:mod-1))
|
||
```
|
||
|
||
This clause expands into ordinary named commands and bindings. It inherits
|
||
`:default-modifiers` unless explicitly overridden. For individual custom keys,
|
||
commands can call `backend-select-activity-slot` or `backend-select-view-slot`.
|
||
The clause uses command ids `:navigate-activity-1` … `:navigate-activity-10`
|
||
and `:navigate-view-1` … `:navigate-view-10`.
|
||
|
||
Numbers are durable navigation metadata, not identities or a ten-Activity
|
||
limit. The bar shows Activity names only, such as `Work`; the picker retains each
|
||
Activity/View's number for reference. The bar lists Activities in slot order.
|
||
Saved Views are arrangements to recall, not subworkspaces: no View name,
|
||
Draft label, or dirty marker appears in the bar. Active text
|
||
uses the configured `:accent-color` (falling back to `:focused-foreground`);
|
||
inactive text uses `:foreground`, without changing background colors.
|
||
Click a name to use the same portable/extended navigation as the number keys.
|
||
Clicking the already active name restores client
|
||
focus without resetting its arrangement. Long lists retain the active tab
|
||
where space permits and use a clickable `…` Activity picker for overflow;
|
||
title and status areas remain bounded. Custom commands can open the same
|
||
picker with `backend-open-activity-picker`.
|
||
|
||
Activity numbers are contiguous positions: deleting an Activity shifts later
|
||
ones left, and new Activities append to the end. Existing order is otherwise
|
||
preserved, including across restarts; older sparse assignments are compacted
|
||
in their existing bar order. No Activity identity, window, View, or lease changes.
|
||
On narrow bars, the `…` picker shows the full order, including clipped tabs.
|
||
|
||
Drafts have no View number. View slots remain stable and local: new Views take
|
||
the lowest free slot and deleting one does not renumber the others. Previously
|
||
unnumbered sessions start with Scratch on 1, then Activities alphabetically;
|
||
existing Views use library order. Beyond position/slot 10, use name search or
|
||
reassign a shortcut. Empty shortcuts do nothing. Session persistence saves
|
||
navigation metadata across restarts; without it, the order lasts for this runtime.
|
||
|
||
New Activities are portable: selection brings their current arrangement here,
|
||
including unsaved edits. If already displayed elsewhere, the two monitors swap
|
||
arrangements; an inactive Activity changes only the selected monitor. Numbered selection of the current Activity
|
||
or View is a no-op, not a reset. Selecting another View recalls its checkpoint.
|
||
The menu's explicit `Recall view...` restores a checkpoint even when it was the
|
||
last one recalled. For portable Activities, recalling a checkpoint on another
|
||
monitor first brings that Activity's arrangement there; lease conflicts are
|
||
checked before either step, so cancelling does not swap monitors. A View still
|
||
cannot be mounted twice. Extended Activities retain independent Head-local
|
||
recall and explicit partial/take lease choices.
|
||
Both `backend-activate-activity` and `backend-switch-to-activity` use the same
|
||
current working arrangement. Activity navigation never reloads a checkpoint
|
||
or retries an old window claim. Moving a window updates its previous live
|
||
location even while that Activity is undisplayed. Hidden windows remain hidden
|
||
when an arrangement travels; ordinary navigation never gathers the whole pool.
|
||
|
||
`Super-y → a → e` chooses a monitor to **extend** the current Activity onto.
|
||
Its existing portion stays put; a new portion is empty, or an existing portion
|
||
resumes. That Activity then navigates independently per Head, even when one
|
||
portion becomes empty. `Super-y → a → p` makes the current portion portable
|
||
again once other portions are empty and inactive; it preserves saved Views
|
||
and hidden windows. Both actions are also searchable commands.
|
||
Noninteractive callers use `backend-extend-activity-to-head` and
|
||
`backend-make-activity-portable`; `backend-activity-navigation-mode` reports
|
||
`:portable` or `:extended`. The startup Scratch desk retains its initial blank
|
||
per-Head portions; newly created named Activities default to portable.
|
||
|
||
Sending a window to a portable Activity targets its real arrangement, even on
|
||
another monitor. It appears there if that arrangement is active, otherwise it
|
||
is queued there; focus stays with the sending monitor. Extended destinations
|
||
still use the sending monitor's portion.
|
||
|
||
`Super-y → v` contains saved-arrangement actions: `s` saves a new named
|
||
checkpoint, `o` recalls one, and `u` chooses a saved view in the current Activity
|
||
to replace. Updating requires a named confirmation (Cancel is selected first)
|
||
and rejects stale choices if the desk changes. The starter `Super-Shift-s`
|
||
binding opens this same picker via `backend-open-view-update-picker`. Advanced
|
||
Lisp callers can still explicitly use `backend-update-view` or
|
||
`backend-update-current-view`; the UI no longer silently targets the last recall.
|
||
|
||
For interactive monitor sends, bind a command calling
|
||
`(lime.x11:backend-send-focused-client-relatively backend :next)` (the starter
|
||
`Super-Shift-e` binding). It transfers same-Activity/shared windows immediately
|
||
and opens a Cancel-first confirmation for a private cross-Activity move.
|
||
Successful sends follow the window with keyboard focus and route subsequent
|
||
commands/new windows to its destination Head. Cancelling leaves focus unchanged.
|
||
The same entry point works inside configured menu commands. Strict non-prompting
|
||
`backend-transfer-focused-client-*` APIs still reject private cross-Activity
|
||
transfers; they never silently change home membership.
|
||
|
||
Shortcuts can be reassigned through the REPL; occupied assignments swap.
|
||
Activity positions must be within 1 through the Activity count, so reordering
|
||
cannot create a gap. View slots may still be any positive integer:
|
||
|
||
```lisp
|
||
(lime.dev:with-development-backend (backend)
|
||
(lime.x11:backend-set-activity-slot backend "work" 2)
|
||
(lime.x11:backend-set-view-slot backend "work/docs" 3)
|
||
(lime.x11:backend-navigation-slots backend))
|
||
```
|
||
|
||
## Browser launch profiles
|
||
|
||
The built-in Waterfox command and the PATH picker use the browser's normal
|
||
profile selection, like running `waterfox` in a terminal. Testing alongside
|
||
another graphical session can explicitly opt into an isolated profile:
|
||
|
||
```lisp
|
||
(lime.x11:backend-launch-browser backend :test-profile-p t)
|
||
```
|
||
|
||
That test profile lives in Lime's XDG state directory (`lime/waterfox-profile/`)
|
||
and intentionally has separate tabs, settings, and extensions. It is never
|
||
selected implicitly for normal desktop use.
|
||
|
||
Super-y opens the compact key map first: `w → +Windows` enters the Window
|
||
group, for example. Space or Tab opens command search, Backspace goes up a
|
||
menu level, and Escape closes. Tab in search returns to the root map. Large
|
||
User groups can be paged with PgUp/PgDn. The dropdown uses the bar's configured
|
||
background, foreground, focus, separator, and edge colors, plus the shared
|
||
font below; it no longer has a separate hard-coded black/white appearance.
|
||
|
||
## Configuration versus session state
|
||
|
||
Your configuration defines policy and commands. Activities, saved Views,
|
||
Viewsets, and drafts are automatically stored separately when using `lime/dev`.
|
||
Choose `:session-file :default`, a pathname, or `nil` on the session launcher;
|
||
the lower-level `lime.config:start-lime` defaults to no session persistence.
|
||
Reloading this configuration does not reload or replace the desk. See the
|
||
[persistence guide](persistence.md), including the distinction between restarting
|
||
Lime and relaunching applications after logout.
|
||
|
||
## Status widgets
|
||
|
||
Opt into managed providers in your own configuration; no music/audio services
|
||
are started by Lime's starter configuration:
|
||
|
||
```lisp
|
||
(:bar :height 22
|
||
:status-providers
|
||
(list (lime.x11:make-music-status-provider
|
||
:source :mpd :sources '(:mpd "spotify")
|
||
:mpd-host "127.0.0.1" :mpd-port 6600 :maximum-width 320
|
||
:formatter (lambda (data)
|
||
(lime.x11:format-music-status data
|
||
:playing-icon "" :paused-icon "" :icon-gap " ")))
|
||
(lime.x11:make-volume-status-provider
|
||
:foreground nil ; inherit the bar's color, or set "#RRGGBB"
|
||
:formatter (lambda (data)
|
||
(lime.x11:format-volume-status data
|
||
:icon "" :muted-icon "" :icon-gap " ")))
|
||
(lime.x11:make-clock-status-provider
|
||
:formatter (lambda (data)
|
||
(lime.x11:format-clock-status data
|
||
:icon "" :icon-gap " " :twenty-four-hour-p nil)))))
|
||
```
|
||
|
||
Subscriptions publish **data**, not display strings. The configuration owns
|
||
the ordered provider list, formatters, icons, gaps, optional per-widget colors,
|
||
width limits, and click actions. Rendering still owns the overall left/title/
|
||
right bar geometry; this is not yet an arbitrary bar-layout DSL.
|
||
|
||
Each `:formatter` is a fast function of one cached data plist:
|
||
|
||
| Provider | Snapshot fields |
|
||
| --- | --- |
|
||
| Music | `:state` (`:playing`, `:paused`, `:stopped`, `:unknown`), `:title` (string/NIL), `:source` (`:mpd` or MPRIS player name) |
|
||
| Volume | `:volume` (first-channel integer percentage), `:muted-p`, `:sink` (output name) |
|
||
| Clock | `:universal-time`, `:minute`, `:hour` (0–23), `:day`, `:month`, `:year`, `:weekday` (Monday=0), `:daylight-p`, `:timezone` (CL hours west of GMT) |
|
||
|
||
The clock publishes at minute resolution, even if a custom formatter decodes
|
||
seconds from the initial timestamp. NIL source data means unavailable: the
|
||
widget is hidden and its formatter is not called until data arrives.
|
||
|
||
A formatter may return a string, NIL to hide available data, or a
|
||
`head-bar-segment`. For example, this configuration can color muted volume
|
||
differently without making the source know anything about themes:
|
||
|
||
```lisp
|
||
:formatter
|
||
(lambda (data)
|
||
(lime.x11:make-head-bar-segment
|
||
:text (lime.x11:format-volume-status data :icon-gap " ")
|
||
:foreground (if (getf data :muted-p) "#9f9584" "#d8c9ad")))
|
||
```
|
||
|
||
Segment foreground/width/click overrides take precedence over the provider's
|
||
defaults; NIL fields inherit those defaults. Provider `:foreground nil`
|
||
inherits the bar foreground. Colors must be `#RRGGBB`. Invalid formatter
|
||
output or a formatter error hides only that provider and records a diagnostic;
|
||
other widgets and subscriptions keep running. Formatters run on the X thread,
|
||
outside worker locks, on a defensive copy. They must not block or do I/O.
|
||
|
||
The `format-*-status` functions are optional presentation helpers in
|
||
`src/x11/status-format.lisp`, not part of the subscription protocols. Supply
|
||
your own formatter for a different date format, stopped-track display, labels,
|
||
or icons (NIL/empty icons omit both icon and gap). Helpers default to one space
|
||
for music and two for volume/date. `:twenty-four-hour-p` now belongs to
|
||
`format-clock-status`, not `make-clock-status-provider`.
|
||
|
||
Pass `:icon-foreground "#76929f"` to any of these helpers for a colored icon
|
||
with an independently colored label. With this option the helper returns a
|
||
`head-bar-segment` instead of a string; the label still inherits the provider
|
||
or bar foreground. Without it, the existing string/NIL behavior is unchanged.
|
||
For example:
|
||
|
||
```lisp
|
||
:formatter (lambda (data)
|
||
(lime.x11:format-volume-status data
|
||
:icon-foreground "#76929f" :icon-gap " "))
|
||
```
|
||
|
||
Custom segments can use `:prefix-length` (Lisp character count, not UTF-8
|
||
bytes) and `:prefix-foreground` for the same effect. The icon and label remain
|
||
one shaped item and one click target; fitting never colors truncation dots as
|
||
part of the icon. Text is still plain text, not markup.
|
||
|
||
Music uses a native MPD connection with `idle player playlist`, or a long-lived
|
||
`playerctl --follow` process for an MPRIS player name such as `"spotify"` or
|
||
`"brave"`. Default formatting hides stopped players; a custom formatter can
|
||
show their retained data. Disconnected players disappear. Paused tracks retain
|
||
their pause indicator. The song's pixel budget truncates at grapheme boundaries,
|
||
leaving room for the following widgets. Volume uses `pactl subscribe` and
|
||
queries the default output only initially and on relevant events. It follows
|
||
mute, volume, output changes, and hotplug; the first channel percentage matches
|
||
the old dwm script. Queued bursts are coalesced without a debounce delay.
|
||
The clock shows local date/time and recomputes the next wall-clock minute
|
||
boundary after every wake, not a repeating sleep measured from startup.
|
||
It is a date/time widget, not a calendar/appointment application.
|
||
|
||
The external event bridges need `pactl` (PulseAudio or PipeWire-Pulse) and
|
||
`playerctl` (only when using MPRIS); MPD controls use `mpc`. The built-in desktop
|
||
sources currently target SBCL/Linux. MPD host/port/password are explicit
|
||
constructor options, not inferred by reading a shell script. MPRIS and audio
|
||
inherit the session's D-Bus/audio environment; a nested Xephyr session will
|
||
normally share the host user's player and volume. No controls are invoked
|
||
merely by displaying the widgets.
|
||
|
||
Click music to play/pause; click volume to toggle mute. Each constructor accepts
|
||
an optional `:click-action` function of `(backend head-id)` to customize this.
|
||
The clock has no default click action. Bind these commands through your usual
|
||
configuration command registry:
|
||
|
||
```lisp
|
||
(lime.x11:backend-cycle-music-source backend) ; :mpd -> "spotify" -> :mpd
|
||
(lime.x11:backend-music-command backend :toggle) ; also :next / :previous
|
||
```
|
||
|
||
The widget and controls share the same selected source. An optional `:id`
|
||
supports multiple music providers; commands accept the corresponding `:id`.
|
||
Reloading a newly evaluated music constructor resets its source to the
|
||
configuration's `:source`. Your dwm scripts remain independent.
|
||
|
||
Managed providers share one cached snapshot across Heads. Subscription failures
|
||
clear stale data and reconnect with bounded 1–30 second backoff. Queries have
|
||
timeouts, and reload/removal/quit cancel and join old workers, kill/reap their
|
||
owned helpers, and close sockets. Set `:status-providers nil` explicitly to
|
||
remove all providers; omitting the option preserves the previous setting.
|
||
To inspect connection errors through SLY:
|
||
|
||
```lisp
|
||
(lime.dev:with-development-backend (backend)
|
||
(lime.x11:backend-status-provider-report backend))
|
||
```
|
||
|
||
Fast plain `(backend head-id)` provider functions returning `head-bar-segment`
|
||
values still work. They run during drawing, so must not perform blocking I/O
|
||
and do not schedule their own refresh. For a Lisp-only scheduled source,
|
||
`make-status-provider` accepts `:id`, `:run`, `:click-action`, and optional
|
||
`:formatter`, `:foreground`, and `:maximum-width`. Its worker function uses
|
||
`status-publish` (scalar/string/list data, or NIL for unavailable),
|
||
`status-wait` (interruptible deadline), and `status-cancelled-p`; it must honor
|
||
cancellation, never mutate the WM/backend, and never draw from its thread.
|
||
The built-in I/O sources additionally register short resource-cancellation
|
||
callbacks through the internal runtime boundary.
|
||
|
||
Publication and formatting copy data trees, so mutating a producer's input or
|
||
a formatter's argument cannot change the worker cache. Formatters are never
|
||
called by the publisher. Generic providers default to the identity formatter,
|
||
so existing string-publishing providers continue to work. Reports expose both
|
||
`:value` (raw data) and `:text` (formatted output), plus source/formatter errors.
|
||
Changing a formatter on an existing provider can redraw the cache without
|
||
restarting its worker; evaluating new constructor objects during config reload
|
||
still replaces those providers using the normal lifecycle.
|
||
|
||
## Fonts
|
||
|
||
The bar and dropdown share one font setting, independent of window layouts:
|
||
|
||
```lisp
|
||
(:font :family "monospace" :size 14 :weight :normal)
|
||
```
|
||
|
||
These are the defaults. `:size` is a pixel size (1–96), not points; `:weight`
|
||
is `:normal` or `:bold`. Family names are installed Fontconfig font families,
|
||
not X11 core-font names or Xft patterns such as `Dina:size=11`.
|
||
Comma-separated families give a preferred fallback order, for example:
|
||
|
||
```lisp
|
||
(:font :family "Dina, Fira Code Nerd Font, Noto Color Emoji"
|
||
:size 15 :weight :bold)
|
||
```
|
||
|
||
Pango/Cairo supplies antialiasing, Unicode shaping, bidirectional text, and
|
||
system font fallback. Japanese, emoji (including color emoji), and Nerd Font
|
||
icons work when suitable fonts are installed. A missing family falls back
|
||
to a system font; a character missing from every installed font may still
|
||
show a missing-glyph box. Lime no longer converts Unicode to `?`.
|
||
|
||
Font changes reload live, including an already-open dropdown. Its rows resize
|
||
to the font; the bar keeps its explicitly configured `:height`, so increase
|
||
that too for larger fonts. Fitting uses measured glyphs and truncates only at
|
||
grapheme boundaries, preserving combining accents and joined emoji. This is
|
||
rendering support, not an input-method/IME implementation.
|
||
|
||
The first upgrade from the old core-font renderer changes native resources
|
||
and structure definitions: restart the Lisp/Xephyr test session once. Later
|
||
font **configuration** changes use the ordinary configuration reload.
|
||
|
||
## Cursors
|
||
|
||
The desktop arrow, clickable bar/menu hand, and cooperating applications share
|
||
a configurable Xcursor theme:
|
||
|
||
```lisp
|
||
(:cursor :theme nil :size nil) ; defaults: follow the system
|
||
;; Or choose an installed theme and nominal pixel size explicitly:
|
||
(:cursor :theme "Adwaita" :size 24)
|
||
```
|
||
|
||
Use just one `:cursor` clause. `:theme` is a theme directory name, not a font
|
||
or file path; `:size` is an integer from 8 to 256, or NIL for automatic sizing.
|
||
Explicit config wins over `XCURSOR_THEME` / `XCURSOR_SIZE`, which win over the
|
||
current X screen's `Xcursor.theme` / `Xcursor.size` Xresources. No environment
|
||
variables are required. Without those, the theme is Xcursor's `default`
|
||
(including `default/index.theme` inheritance); size uses `Xft.dpi` when
|
||
available, otherwise 24px. The theme supplies its nearest available size.
|
||
|
||
Installed cursor directories, `XCURSOR_PATH`, and theme inheritance are resolved
|
||
by libXcursor. A missing asset/theme may fall back to the system default theme.
|
||
If libXcursor, ARGB-capable X Render, or usable cursor images are unavailable,
|
||
Lime falls back to built-in X cursors. `:theme :core` requests those explicitly.
|
||
Inspect the selected settings and each role's source/fallback reason with
|
||
`(lime.x11:backend-cursor-report backend)`; the reported theme is the requested
|
||
search theme, not proof that an asset came from that directory.
|
||
|
||
Theme images retain alpha and hotspots. Animated assets use X Render's server
|
||
animation support (or their first frame on older servers), not a WM timer.
|
||
|
||
Explicit `:theme` / `:size` values also become **session-local application
|
||
defaults**. Lime publishes cursor-only XSETTINGS for GTK and compatible toolkits,
|
||
updates `Xcursor.theme` / `Xcursor.size` in the current X screen's resources,
|
||
and passes `XCURSOR_THEME` / `XCURSOR_SIZE` to programs launched through Lime.
|
||
You still configure these in Lisp; no environment setup is required. Lime does
|
||
not rewrite GTK/Qt files, dconf, the global launch environment, or another X
|
||
session. Application-specific cursors (text, resize, drag, etc.) retain their
|
||
roles; applications that deliberately override the theme can still do so.
|
||
|
||
Lime never replaces an existing XSETTINGS manager and yields if one takes over.
|
||
The `:session` entry in `backend-cursor-report` reports `:published`,
|
||
`:external-manager`, or `:following-system` under `:xsettings`. With an external
|
||
manager, configure its cursor theme too if its toolkit settings disagree.
|
||
|
||
Only explicit values are published: a NIL field follows its ordinary defaults.
|
||
Returning both fields to NIL, choosing `:theme :core`, or exiting Lime releases
|
||
its manager and restores the Xresources it still owns, preserving intervening
|
||
external edits. Some applications cache cursor settings and need reopening;
|
||
in particular, GTK 3 can retain a removed setting until restart even though
|
||
changes between explicit themes/sizes update live.
|
||
|
||
After the initial structural upgrade/restart, theme/size changes reload live,
|
||
including a hovered widget or an open dropdown. New cursor resources are
|
||
prepared first; a later config failure restores the old resources, while a
|
||
successful reload releases them. Session publication participates in the same
|
||
rollback. No activity or view is recreated.
|
||
|
||
## Bar appearance
|
||
|
||
The bar has three regions: `Lime | activities | layout` on the left,
|
||
a left-aligned expanding title section, and optional right-aligned status
|
||
providers. Only the title section changes colors with Head focus. Its two
|
||
one-pixel edges are deliberately quieter than the colored text separators.
|
||
|
||
Bar colors and padding can be reloaded without restarting or changing a saved View:
|
||
|
||
```lisp
|
||
(:bar :height 22 :padding-x 10
|
||
:background "#060a0c" :foreground "#ffdbb0"
|
||
:focused-background "#121f25" :focused-foreground "#ffac54"
|
||
:unfocused-background "#0b1216" :unfocused-foreground "#a8adb0"
|
||
:separator-color "#b88755"
|
||
:attention-color "#ffac54"
|
||
:attention-marker "!" :attention-indicator-p t
|
||
:title-edge-color "#40535d")
|
||
```
|
||
|
||
These are the defaults. Use `:title-edge-color nil` for the edge-free variant.
|
||
Use `:logo-color "#768565"` to color the clickable `Lime` label independently
|
||
of the Activity and layout labels. NIL (the default) inherits `:foreground`.
|
||
Optional `:accent-color` separates menu key hints, hover/selection markers, and
|
||
which-key headings from the focused title's foreground. `:menu-label-color`
|
||
separates submenu labels from the bar's separators. Both default to NIL to
|
||
preserve the old colors (`:focused-foreground` and `:separator-color`,
|
||
respectively; headings retain `:foreground`). For cream titles and labels
|
||
with yellow hints, add:
|
||
|
||
```lisp
|
||
:focused-foreground "#dbc9a6"
|
||
:accent-color "#ffb054"
|
||
:menu-label-color "#dbc9a6"
|
||
```
|
||
|
||
These new roles and separately colored status prefixes require one fresh Lime
|
||
process when upgrading from the older renderer. Subsequent color-only config
|
||
edits can be reloaded normally.
|
||
|
||
The attention indicator appears as `! N` ahead of the optional status widgets
|
||
only while windows request attention. It counts windows across **all** Activities
|
||
and appears on every visible Head bar, including when their Activity is parked.
|
||
Click it, or use `Super-y → w → a`, to search requesting windows by Activity
|
||
and current title. Activity-search details and hidden-window entries also mark
|
||
requests. No new global keybinding is installed.
|
||
|
||
`:attention-color` and `:attention-marker` control that badge; the marker is
|
||
1–16 printable characters. `:attention-indicator-p nil` hides the bar badge,
|
||
not the protocol handling or menu. The public cached-data queries
|
||
`backend-attention-client-ids` (optional `:activity-id`) and
|
||
`backend-client-needs-attention-p` are available to custom Lisp UI/providers.
|
||
`backend-open-attention-picker` opens the picker, and `backend-attend-client`
|
||
performs the same visit without a menu (`:policy :strict` by default).
|
||
See [attention behavior](attention.md) for acknowledgement and lease rules.
|
||
|
||
`:padding-x` is horizontal text padding in pixels (0–128): the outer left and
|
||
right margins and the title section's inset
|
||
inside its hairlines. The bar vertically centers a stable sample of the font's
|
||
letter body, rather than its invisible line spacing. All fields share a
|
||
baseline, so changing titles or adding descenders does not make text jump.
|
||
Accents and descenders retain their normal positions relative to that baseline;
|
||
increase `:height` if the chosen font or fallback glyphs need more room.
|
||
|
||
Colors must be `#RRGGBB`; invalid settings fail before changing the running
|
||
configuration. A failed installation restores the old bar appearance along
|
||
with bindings and window styling. Lisp callers can use `MAKE-HEAD-BAR-STYLE`
|
||
and the configuration's `BAR-STYLE` slot directly.
|
||
|
||
Status providers still supply their own text and optional click actions.
|
||
The bar draws contrasting ` | ` delimiters between their items; those
|
||
delimiters and truncation dots are not clickable. Appearance settings alone
|
||
do not enable providers; opt into the widgets above separately. Providers may use text,
|
||
Unicode symbols, or icons covered by the configured/fallback fonts.
|
||
|
||
## Default layout
|
||
|
||
`:options :default-layout` chooses `:master-stack` (the default), `:monocle`,
|
||
or `:manual` for a **new** working arrangement. This includes the initial
|
||
Scratch drafts, new Activities, and newly extended portions. Bringing or resuming
|
||
an existing draft or recalling a saved View keeps its layout. Reloading this
|
||
option changes future drafts only; it never rearranges your current work.
|
||
|
||
The configuration is read before startup drafts are created. Without a config
|
||
file, `start-lime`/`start-backend` also accept `:default-layout`; with a file,
|
||
the file's setting wins.
|
||
|
||
## New-window and close policy
|
||
|
||
The `:options` clause also keeps client-flow preference out of the layout
|
||
engine. `:new-client-focus` controls whether ordinary newly managed windows
|
||
take focus. `:new-tiled-client-position` is `:back` (append after the existing
|
||
stack) or `:front` (become master-most). `:close-focus-direction` is
|
||
`:previous` (toward master in placement order) or `:next` (farther down the
|
||
stack); reaching either edge falls back to the closest client on the other
|
||
side. These are the fallback defaults for manual and master/stack: `t`,
|
||
`:back`, and `:previous`.
|
||
|
||
Each layout can override only the flow choices it needs:
|
||
|
||
```lisp
|
||
(:layout-flow :monocle
|
||
:new-tiled-client-position :front
|
||
:close-focus-direction :next)
|
||
```
|
||
|
||
That is Lime's built-in monocle policy already: each new tiled client becomes
|
||
the visible one, and closing it advances to the next underlying client. A
|
||
`:layout-flow` clause is useful when you want a different monocle cycle, or a
|
||
separate policy for `:manual` or `:master-stack`.
|
||
|
||
## Resizing a window within a column
|
||
|
||
Master/stack gives each tiled placement a relative weight, initially `1`.
|
||
Two windows weighted `2` and `1` take two-thirds and one-third of their column.
|
||
The master area and stack normalize separately, leaving their shared divider
|
||
unchanged. This also works with several master windows, and along the other
|
||
axis when the layout is rotated.
|
||
|
||
Configure the commands and step size in your own keymap, for example:
|
||
|
||
```lisp
|
||
(:command grow-tile
|
||
:label "Grow window in column"
|
||
:action (lambda (backend)
|
||
(lime.x11:backend-adjust-focused-tile-weight backend 1/4)))
|
||
(:command shrink-tile
|
||
:label "Shrink window in column"
|
||
:action (lambda (backend)
|
||
(lime.x11:backend-adjust-focused-tile-weight backend -1/4)))
|
||
(:command reset-tile-size
|
||
:label "Reset window column share"
|
||
:action (lambda (backend)
|
||
(lime.x11:backend-set-focused-tile-weight backend 1)))
|
||
(:bind #\d grow-tile :modifiers '(:mod-4 :shift))
|
||
(:bind #\n shrink-tile :modifiers '(:mod-4 :shift))
|
||
(:bind #\o reset-tile-size :modifiers '(:mod-4 :shift))
|
||
```
|
||
|
||
The adjust command defaults to weights between `1/4` and `4`; callers can
|
||
override `:minimum-weight` and `:maximum-weight`. Reset affects the focused
|
||
window only. A sole window still fills its column.
|
||
|
||
Weights belong to placements in the current draft and are checkpointed by
|
||
normal View save/update operations. They follow windows through tile moves,
|
||
float/unfloat, and layout changes. Monocle ignores the weights. Hiding removes
|
||
the placement; showing the window again creates a default-weight placement
|
||
unless a saved View restores its previous arrangement.
|
||
|
||
## Borders and gaps
|
||
|
||
The `:appearance` clause configures window styling independently of layout
|
||
policy. Sizes are non-negative integer pixels and colors are `#RRGGBB` strings.
|
||
`:inner-gap` is the space **between** tiled outer frames, not a margin on each
|
||
window; `:outer-gap` is the space between tiles and the Head's work-area edge
|
||
(below the bar). Border widths are included inside the allotted rectangle,
|
||
so they cannot make neighboring tiles overlap.
|
||
|
||
The implementation lives in `src/x11/appearance.lisp`; the render planner
|
||
combines it with ICCCM hints, while your configuration owns the actual values.
|
||
|
||
Only the globally focused client gets `:focused-border`; windows on other
|
||
Heads use `:unfocused-border`. Floating windows have borders but no tiling
|
||
gaps. Smart gaps remove both gaps and borders whenever a Head displays just
|
||
one tiled window, in manual, master/stack, or monocle. Floats above it keep
|
||
their borders and do not count as tiles; explicitly hidden clients and tiles
|
||
leased to other Heads do not count either. A second visible tile restores the
|
||
configured borders and gaps automatically. Monocle always resolves just one
|
||
tile, so it remains undecorated even with several clients in its cycle.
|
||
These tiles still respect the bar's work area and normal size hints. True
|
||
fullscreen bypasses decoration, the bar reservation, and size hints. Size hints may
|
||
leave additional unused space, but cannot enlarge a tile beyond its slot.
|
||
On very small tiles, excessive gaps/borders shrink to leave usable content.
|
||
|
||
Changing these settings and reloading configuration repaints existing windows
|
||
without changing layout weights, saved Views, leases, or draft dirty state.
|
||
Setting a size to `0` disables that component. Without an `:appearance` clause,
|
||
the adapter retains its borderless/gapless defaults; default border colors
|
||
are white (`#ffffff`) and gray (`#444444`) if only a width is specified.
|
||
|
||
## Reloading
|
||
|
||
`lime.config:reload-configuration` rereads the source file selected at start.
|
||
It validates commands, bindings, rules, appearance, and bar inputs before changing the
|
||
running backend. If a subsequent installation operation fails, Lime restores
|
||
the previous binding set, appearance, and bar settings; Activities, Views, Presentations,
|
||
leases, and Viewsets are never re-created or changed by a reload. `:startup`
|
||
hooks run only at initial `start-lime`, never on reload.
|
||
|
||
Because a config file is real Lisp, arbitrary top-level side effects are still
|
||
the author’s responsibility. Keep top-level work to helper definitions and
|
||
`defconfiguration`; place deliberate launches in a `(:startup (lambda
|
||
(backend) ...))` clause when that behavior is wanted.
|
||
|
||
## Rules, deliberately for styling first
|
||
|
||
`(:rule ...)` supports `:name`, `:class`, and `:title` matchers (a string or a
|
||
one-argument predicate), plus a full `:predicate` function of `(backend
|
||
client-id icccm-info)`. Its current actions are `:float t/nil` and `:center t`.
|
||
Centering requires floating. The first matching rule wins.
|
||
|
||
Ordinary clients default to tiled, except fixed-size windows (equal positive
|
||
minimum and maximum width **and** height in `WM_NORMAL_HINTS`), which start
|
||
centered and floating. This handles startup/updater windows without floating
|
||
the application's resizable main window. A matching `:float nil` rule can
|
||
override that default. These defaults apply only when a client is first
|
||
managed; later hint updates do not reclassify existing placements. The usual
|
||
`:new-client-focus` setting applies to fixed-size ordinary clients too.
|
||
|
||
Rules apply only to ordinary new clients. ICCCM transient dialogs keep their
|
||
parent’s Head, Activity, and float behavior; dialog-like EWMH clients retain
|
||
their floating policy too. Rules intentionally do **not**
|
||
mount a View, change an Activity, or transfer a lease: matching an application
|
||
class must not silently alter Lime’s task model. Activity-aware launch/routing
|
||
can grow later as a separate policy with explicit collision semantics.
|