mesh/merge-gate checking again: the gate refused `unknown` because the controller judged with a node-engine validator pinned before mesh-host #117; mesh-co…
mesh/repo-check checking again: the gate refused `unknown` because the controller judged with a node-engine validator pinned before mesh-host #117; mesh-co…
mesh/delivery-group group feat/owned-paths checking: 0 of 2 member(s) ready
mesh/delivery superseded: a newer head of the same pull request
Every directory and archive resource carries `unknown`: move-out for a home folder whose program writes nothing there (the node-engine, delivered in mesh-host #117, moves the rest to ~/.local/state/mesh-moved/<module>/ with a list), module for ~/.ssh and ~/.claude, program elsewhere; `knows` keeps what a program or the operator relies on (powerlevel10k's compiled files, i3's scripts and the asus/g14 drop-ins, dunst's slack rule, betterbird's records, node-env's forge credential, issue 1033). Two shared folders are no longer declared. Each module's README says it. checks/owned holds every manifest to it; checks/sshdir stays while main's merge check runs it (issue 1035). Tests that pin a manifest follow the new key only. Reviewed in three rounds.
172 lines
12 KiB
Markdown
172 lines
12 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 (`move-out`) | what the module declares, and `scripts/` (named in `knows`: the screenshot binding in the main file runs `scripts/screenshot.sh`, kept until its owner exists); any other file is moved out by the node-engine to `~/.local/state/mesh-moved/i3/` with a list, never deleted (ADR 0422 §4) |
|
|
| `~/.config/i3/config.d/` | owned directory (`move-out`) | what the module declares, and `10-asus.conf` and `20-g14.conf` (named in `knows`, kept until the laptop's hardware module exists); any other file is moved out by the node-engine to `~/.local/state/mesh-moved/i3/` with a list, never deleted (ADR 0422 §4) |
|
|
| `~/.config/i3/config` | owned (the found file kept once) | the main configuration, from [`files/config`](files/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.** `~/.config/i3/config.d/` holds what this module declares and the files named in `knows`; a
|
|
file of yours put there is moved out (ADR 0422). 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.
|
|
|
|
## 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 peripherals' tray (`exec … polychromatic-tray-applet`) | `polychromatic`'s contribution to node-display-session |
|
|
|
|
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 operator's own scripts: volume, games
|
|
volume, the sessions launcher and the screenshot binding. Each leaves when the module that owns it is
|
|
written. The peripherals' tray applet left it for the `polychromatic` module.
|
|
|
|
## What it keeps, and what it moves out
|
|
|
|
Kept, because they work for the operator today: `~/.config/i3/scripts/` (the screenshot binding runs
|
|
`scripts/screenshot.sh`), and `config.d/10-asus.conf` and `20-g14.conf`, each until its owner exists. Both are
|
|
named in `knows`. Anything else in `~/.config/i3/` (such as `unlock-keyring.sh`) or in `config.d/` is moved
|
|
out by the node-engine to `~/.local/state/mesh-moved/i3/` with a list, never deleted (ADR 0422 §4). Left
|
|
alone, outside the folders: 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. They
|
|
are named in `knows`, so they stay until it exists. The desktop no longer carries them.
|
|
- `50-slack.conf` is not an i3 file: the Slack rule is dunst's (`dunstrc.d`, see `dunst`).
|
|
- `99-tmp-wine-focus.conf` (the desktop), a test of a predecessor change by its own comment, is moved
|
|
out at the first look with a list; nothing is lost.
|
|
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.
|
|
|
|
## Other modules' lines are contributions (changed 2026-10-05, novox/hq ADR 0212)
|
|
|
|
`node-display-session` receives `config` contributions, and this module places them with
|
|
`${contribution:node-display-session:config}` near the end of its configuration, each module's
|
|
under a line naming it. `~/.config/i3/config.d/` holds only what is declared and named in `knows`: the include at the very
|
|
end reads it after the mesh's lines. The catalogue-wide test composes every module's contribution as
|
|
the controller does and checks the result with `i3 -C`.
|
|
|
|
## Files it does not know
|
|
|
|
Each directory or archive this module declares says how it treats a file it did not write (ADR 0422, ADR 0416).
|
|
|
|
- `~/.config/i3`: holds only what this module declares and `scripts/` (the main file's screenshot binding runs `scripts/screenshot.sh`; kept until its owner exists); anything else is moved out by the node-engine to `~/.local/state/mesh-moved/i3/` with a list, never deleted (ADR 0422 §4).
|
|
- `~/.config/i3/config.d`: holds only what this module declares and `10-asus.conf`, `20-g14.conf` (the laptop's, kept until its hardware module exists); anything else is moved out by the node-engine to `~/.local/state/mesh-moved/i3/` with a list, never deleted (ADR 0422 §4).
|