Files
mesh-catalog/modules/i3
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
..

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
/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.