Files
mesh-catalog/modules/i3/README.md
T
jochen 34829e39fe i3: the window manager holds node-display-session; its config improved and a checked reload watcher (hq ADR 0208)
It requires x11-display and contributes exec i3 to xinitrc's last slot, and
XDG_CURRENT_DESKTOP/XDG_SESSION_DESKTOP to the environment. It owns
~/.config/i3/config, ending with the config.d include where other modules drop
their files, and /etc/lemurs/wms/i3, which runs the session's start.

Over today's identical config it drops the dead lxpolkit, the D-Bus-activated
portal, the xrdb merge xorg now does, and the execs that XDG autostart already
started. It sets JetBrainsMono Nerd Font, runs i3-sensible-terminal, and declares
dex, which the desktop lacked.

The tools speak i3's IPC: the seat's reload, workspaces and windows, and focus,
move, layout save/restore, exec, kill, bindings, config check, marks and the
scratchpad. A watcher in the bundle (ADR 0198) replaces the predecessor's inotify
script and user unit. It reloads only a configuration i3 -C accepts, and at its
start whatever i3 has not loaded.
2026-10-04 13:21:47 +02:00

153 lines
9.6 KiB
Markdown

# i3
The window manager as a module (novox/hq ADR 0208, research 026, to-be 42 phase 2, step 4).
- **Claims `node-display-session`** and serves its verbs `reload`, `workspaces` and `windows`.
- **Requires `x11-display`**, which only `xorg` on the same machine provides.
- **Packages:** `i3-wm`, and `dex`, which the configuration has always run for XDG autostart. `dex`
was missing on the desktop, so its autostart entries never started there.
- **Environment** (ADR 0203): `XDG_CURRENT_DESKTOP=i3` and `XDG_SESSION_DESKTOP=i3`. They reach
shells, the session (through `xorg`'s block) and the user manager (`environment.d`). The portal
keys off the first.
- **Session code** (ADR 0208 §4): `exec i3 --shmlog-size=26214400` in the `xinitrc` slot `last`.
That is the end of `xorg`'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`](config/config) |
| `/etc/lemurs/wms/i3` | owned | the login manager's session entry: `exec /bin/sh "$HOME/.xinitrc"` |
**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 `config` and `config.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's `rice_set` comment;
- **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-applet` and `nextcloud` execs. Each also has an XDG autostart
entry that `dex` starts, and both machines carry those entries;
- **font:** `JetBrainsMono Nerd Font 11` for titles, replacing Hack (research 026/04);
- **terminal:** `$mod+Return` runs `i3-sensible-terminal`, which starts whatever `$TERMINAL` names
(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)
1. **Before the push that assigns `i3`:** do `xorg`'s migration (its README). From that push on, your
lines below `xorg`'s block in `~/.xinitrc` no longer run.
2. **Disable the predecessor's watcher:** `systemctl --user disable --now i3-reload-watcher.service`.
Then remove `~/.config/systemd/user/i3-reload-watcher.service` and `~/scripts/i3-reload-watcher`.
One thing reloads i3 now. Kept running, it would also `i3-msg restart` on every change, without
checking first. **Keep `i3-bar-watchdog.service` as it is**: the bar is `i3status-rust`'s, and that
module decides.
3. **Delete `/etc/lemurs/wms/i3wm`** (see `lemurs`).
4. **`config.d/` fragments:**
- `10-asus.conf` and `20-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 future `slack` module'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.
5. **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
- **`fonts` first** (to-be 42 orders it first). Without `ttf-jetbrains-mono-nerd`, i3's titles and
`i3status-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 assigns `i3`, or changes its
configuration, is therefore reloaded although it also restarted the watcher.