lemurs leaves the session a stdout nobody reads, so a program writing to it dies of EPIPE. i3lock-color provides i3lock, so with it still installed the host saw i3lock as present, skipped the install and then removed the only locker; removing it first lets the same apply install i3lock.
9.7 KiB
i3
The window manager as a module (novox/hq ADR 0208, research 026, to-be 42 phase 2, step 4).
- Claims
node-display-sessionand serves its verbsreload,workspacesandwindows. - Requires
x11-display, which onlyxorgon the same machine provides. - Packages:
i3-wm, anddex, which the configuration has always run for XDG autostart.dexwas missing on the desktop, so its autostart entries never started there. - Environment (ADR 0203):
XDG_CURRENT_DESKTOP=i3andXDG_SESSION_DESKTOP=i3. They reach shells, the session (throughxorg's block) and the user manager (environment.d). The portal keys off the first. - Session code (ADR 0208 §4):
exec i3 --shmlog-size=26214400in thexinitrcslotlast. That is the end ofxorg's block in~/.xinitrc.
What it owns
| path | class (ADR 0182) | what |
|---|---|---|
~/.config/i3/ |
owned directory | its contents other than config are found and kept |
~/.config/i3/config.d/ |
owned directory | other modules' drop-ins, and yours |
~/.config/i3/config |
owned (the found file kept once) | the main configuration, from config/config |
/etc/lemurs/wms/i3 |
owned | the login manager's session entry: exec systemd-cat -t x-session /bin/sh "$HOME/.xinitrc", the session's output in the journal (journalctl -t x-session) because the login manager's pipe has no reader |
Drop-ins. Another module adds to i3 with its own ~/.config/i3/config.d/<NN>-<module>.conf. The
include is the last line of the main file, so every variable set there ($mod, $ws1…$ws10) is
in scope. Files are read in name order. A file of yours there is yours.
The reload watcher
The bundle runs the watcher for as long as the runtime runs it (ADR 0198). It replaces the
predecessor's i3-reload-watcher script and its user unit, so this needs no user-scoped unit:
- every 2 s it looks at
configandconfig.d/*.conf(size and time); - at its start, if the files differ from what the running i3 loaded, that counts as a change;
- after a change, once the files have been still for one more interval, it checks the result with
i3 -C; - it reloads only a configuration without errors. Otherwise it keeps the running one and records the errors.
What it has done is in the answers of reload and i3_config_check. It polls rather than using
inotify, so a file replaced by rename and a directory created later are seen without a fresh watch.
It reloads i3 only. Re-running the bar, the compositor and the notifier is each of those modules'
own business.
Tools
| tool | what | |
|---|---|---|
node-display-session.reload |
a | checks, then reloads, keeping windows. Refused with the errors when the check fails; force reloads anyway |
node-display-session.workspaces |
r | number, name, output, visible, focused, urgent |
node-display-session.windows |
r | container id, X id, class, instance, role, title, workspace, output, focused, urgent, floating, fullscreen, scratchpad, marks; narrowed to one workspace |
i3_focus |
d | a window by criteria, or a workspace |
i3_move |
d | windows to a workspace or output; a workspace to an output |
i3_layout_save |
d | a workspace's arrangement as a named layout of placeholders (class, instance, role), in ~/.local/state/mesh/i3/layouts/ |
i3_layout_restore |
d | lays a saved layout back; name: list lists them |
i3_exec |
d | starts a program as i3's child, optionally on a workspace |
i3_kill |
d | closes the matching windows, or the focused one when asked; with no arguments it closes nothing |
i3_bindings |
r | every binding by mode, with its command and file, from what i3 loaded |
i3_config_check |
r | i3 -C on the files on disk (errors with file and line), the files i3 loaded, the watcher's record |
i3_marks |
r | each mark and its window |
i3_scratchpad |
r/d | list, show or move into the scratchpad |
The tools speak i3's IPC on its socket in the account's runtime directory, and need no display. Every
value a caller gives is quoted before it reaches an i3 command. With no i3 running, the answer is
{"error": "no-graphical-session", …}. The bundle's binary is i3-tools, so nothing that looks for
i3 by name finds it.
What it improves over today's configuration
Both workstations ran the same configuration. These changes are against it:
- dead:
exec lxpolkit(installed on neither machine), the unused$refresh_i3status, and the predecessor'srice_setcomment; - duplicates:
exec xdg-desktop-portal(it is D-Bus activated);exec xrdb -merge ~/.Xresources(xorg's session start merges it);- the
picom,nm-applet,blueman-appletandnextcloudexecs. Each also has an XDG autostart entry thatdexstarts, and both machines carry those entries;
- font:
JetBrainsMono Nerd Font 11for titles, replacing Hack (research 026/04); - terminal:
$mod+Returnrunsi3-sensible-terminal, which starts whatever$TERMINALnames (the terminal module's contribution), instead of naming xterm; - reloads: the watcher never reloads into a broken configuration.
Moved to their own modules' drop-ins (the companion modules of research 026). These leave this file because each module carries them now:
| lines | now in |
|---|---|
the launcher bindings ($mod+d, $mod+t, $mod+Shift+t) and the power menu ($mod+Escape) |
rofi's 50-rofi.conf, which also adds $mod+Shift+w |
the greenclip daemon and its menu ($mod+period) |
clipmenu's 50-clipmenu.conf and its session line |
the wallpaper key ($mod+Shift+b) |
feh's 50-feh.conf |
| both bars | i3status-rust's 60-i3status-rust.conf |
the keyring prompt (unlock-keyring.sh) |
gone: gnome-keyring unlocks the keyring through PAM at login |
The theme picker ($mod+Shift+d) goes too. It was the predecessor's tool for its theme variables,
and settings take its place once issue 168 closes. $mod+Delete (loginctl lock-session) stays here,
because screen-lock relies on it. The test TestTheMainFileAndEveryModulesDropInLoadTogether
loads this file with every catalogue module's drop-in through i3 -C, so no two of them bind one
key.
Kept until their owners exist. A marked section holds the peripherals' tray applet and the operator's own scripts: volume, games volume, the sessions launcher and the screenshot binding. Each leaves when the module that owns it is written.
What it leaves found
~/.config/i3/scripts/, ~/.config/i3/unlock-keyring.sh, every file in config.d/, the
/usr/share/xsessions entries (the package's, no longer offered by lemurs), and i3's restart
state and logs.
The one-off migration (ADR 0182)
- Before the push that assigns
i3: doxorg's migration (its README). From that push on, your lines belowxorg's block in~/.xinitrcno longer run. - Disable the predecessor's watcher:
systemctl --user disable --now i3-reload-watcher.service. Then remove~/.config/systemd/user/i3-reload-watcher.serviceand~/scripts/i3-reload-watcher. One thing reloads i3 now. Kept running, it would alsoi3-msg restarton every change, without checking first. Keepi3-bar-watchdog.serviceas it is: the bar isi3status-rust's, and that module decides. - Delete
/etc/lemurs/wms/i3wm(seelemurs). config.d/fragments:10-asus.confand20-g14.conf(the laptop) belong to that machine model's hardware module. Keep them until it exists. The desktop no longer carries them.50-slack.conf(both) is yours, or a futureslackmodule's. Keep it.99-tmp-wine-focus.conf(the desktop), a test of a predecessor change by its own comment: delete it, or keep it as yours.
- The main file's predecessor copy is kept once by the host and written over. Nothing to do.
What changes when it is assigned
| g14 | shanks | |
|---|---|---|
| packages | none (i3-wm and dex present) |
dex installed |
~/.config/i3/config |
written: the improved configuration | the same |
| running i3 | the watcher reloads it once the file changes. i3 keeps every window, and the title font becomes JetBrains Mono. Assigned without rofi, clipmenu, feh and i3status-rust, the reload takes away the bars and those keys until they are assigned too, so assign them in the same push. The dropped duplicate execs end nothing that runs |
the same |
/etc/lemurs/wms/i3 |
new: offered as i3 at the next boot |
the same |
~/.xinitrc |
xorg's block now ends in exec i3: the lines below it stop running at the next login |
the same |
~/.config/environment.d/50-mesh.conf, environment.sh |
gain XDG_CURRENT_DESKTOP=i3, XDG_SESSION_DESKTOP=i3 |
the same. Its user manager lacked them, and gets them at the next login |
| next login | XDG autostart as before | XDG autostart runs for the first time: Slack, JetBrains Toolbox, Nextcloud, the FortiClient tray, the print applet, the geoclue demo agent and snap's user daemon start from their entries |
One reload on assignment is the one live change. To avoid it, assign during a session you are about to end, or accept it: a reload keeps every window and workspace.
Blockers
fontsfirst (to-be 42 orders it first). Withoutttf-jetbrains-mono-nerd, i3's titles andi3status-rust's bars fall back to pango's default face. On 2026-10-04 the laptop had the package, and the desktop only a hand-copied file of the face.- None for the watcher. The runtime restarts with every push that changes it, after the push has
written the files. So at its start the watcher compares the files on disk with what the running i3
loaded (
GET_CONFIG), and reloads when they differ. The push that assignsi3, or changes its configuration, is therefore reloaded although it also restarted the watcher.