Files
mesh-catalog/modules/xorg/README.md
T
jochen b2c170acda xorg: the display server holds node-display-server and writes the session's start (hq ADR 0208)
The X server, its start and its tools as one module. It provides x11-display with
the machine's reach, gated by the host's seat capability. It writes a block at the
start of ~/.xinitrc: the account's environment, an explicit import into the user
manager, the mesh's X resources merged without cpp, autorandr, the xinitrc slots,
~/.xinitrc.local, and the session's exec from the last slot.

Its Go tools serve the seat's displays and layout (autorandr profiles keyed by
EDID), and set-mode, primary, dpi, input devices and settings, keyboard, a
screenshot (xwd decoded in Go) and the server's log. internal/desktop is how
every desktop tool finds the operator's session from the runtime, which has none:
from the session's own processes, reading only its words, confirmed with logind.
2026-10-04 13:21:47 +02:00

190 lines
12 KiB
Markdown

# xorg
The X display server as a module (novox/hq ADR 0208, research 026, to-be 42 phase 2 step 2).
- **Claims `node-display-server`** and serves its verbs `displays` and `layout`.
- **Provides `x11-display` with the machine's reach**: i3, xterm, picom and the X lock screen require
it, and a requirement for it is met only by this module on the same machine. It is never pulled in
for whatever asked.
- **Gated by the capability `seat`**, which the host reports for a machine with a graphics device
and a connected display. See *Blockers* for why not `graphical-session`.
- **Packages:** `xorg-server`, `xorg-xinit`, `xorg-xrandr`, `xorg-xset`, `xorg-xrdb`, `xorg-xinput`,
`xorg-setxkbmap`, `xorg-xwd` and `autorandr`. The GPU's driver is not here. It follows the machine,
so it belongs to that machine model's hardware module.
## What it owns
| path | class (ADR 0182) | what |
|---|---|---|
| `~/.xinitrc`, a block at the start | written into (`block`, `at: start`) | the session's start, below |
| `~/.config/xorg/` | owned directory | |
| `~/.config/xorg/xresources` | owned | the mesh's X resources: font rendering (`Xft.*`, DPI 96) and the three `xresources` slots |
**The session's start**, in ADR 0208 §5's order:
1. It sources `~/.config/mesh/environment.sh` (ADR 0203). If pam did not hand over a session bus, it
takes the user manager's socket, never a second bus.
2. It imports an explicit list of the session's words into the user manager and D-Bus activation,
only those that are set. The list is `DISPLAY`, `XAUTHORITY`, `XDG_SESSION_TYPE`,
`XDG_CURRENT_DESKTOP`, `XDG_SESSION_DESKTOP`, `XDG_CONFIG_HOME`, `XDG_DATA_DIRS`, `GTK_THEME`,
`GTK2_RC_FILES`, `QT_QPA_PLATFORMTHEME`, `QT_STYLE_OVERRIDE`, `QT_SELECT`, `XCURSOR_THEME`,
`XCURSOR_SIZE` and `TERMINAL`. It never uses `--all`.
3. It merges `~/.config/xorg/xresources` with `xrdb -nocpp`, then `~/.Xresources` if you keep one,
so yours win.
4. It runs `autorandr --change`, which applies the saved profile matching the connected monitors.
5. It runs the `xinitrc` slots `first` and `normal`, then sources **`~/.xinitrc.local`** if it exists.
6. It runs the `last` slot, where the holder of `node-display-session` starts the session (`exec i3`).
**Who contributes where**, among the desktop's modules:
| slot | module | what |
|---|---|---|
| `first` | `gnome-keyring` | `SSH_AUTH_SOCK` |
| `normal` | `adwaita` | its GSettings keys |
| `normal` | `clipmenu` | `clipmenud &` |
| `normal` | `feh` | the wallpaper |
| `normal` | `i3status-rust` | the bar watchdog |
| `normal` | `screen-lock` | the timeouts and the `xss-lock` loop |
| `last` | `i3` | `exec i3` |
**Slot `last` is the session's.** A module contributing session lines uses `first` or `normal`.
Code in `last` that sorts after the session holder's name would come after its `exec` and never run.
**Why `~/.xinitrc.local`.** The block ends by starting the session, so no line below it runs. Your own
session lines go into `~/.xinitrc.local`. Like `~/.zshrc.local`, it is yours: found, never written.
This is the one step this module adds to ADR 0208 §5. Without it, a line no module carries yet (a
`DOTNET_ROOT`, a wallpaper) would have nowhere to run.
**Resources without the preprocessor.** `xrdb -nocpp` needs no C compiler on the machine, and it
skips a line starting with `#`. The controller precedes each contribution with a `# <module>` line,
which is a cpp directive error under plain `xrdb -merge` but is skipped here.
## Tools
| tool | | what |
|---|---|---|
| `node-display-server.displays` | r | the screen and every output: position, rotation, size, DPI, the monitor's EDID identity (manufacturer, product, serial, name, fingerprint), current and preferred mode, every mode and rate, the autorandr profile in force |
| `node-display-server.layout` | r/a | autorandr profiles: `list` (each with the monitors it is for, which match now, which is current), `save` (`replace` to overwrite), `apply` |
| `xorg_set_mode` | a | one output's mode, rate, rotation, scale, position (x/y or beside another), off, primary; `dry_run` |
| `xorg_primary` | r/a | which output is primary; set it |
| `xorg_dpi` | r/a | `Xft.dpi` beside each monitor's physical DPI; set it for the running session |
| `xorg_input_devices` | r | the input devices, and each pointer's libinput settings |
| `xorg_input_set` | a | tap, tap drag, natural scroll, disable while typing, left handed, middle emulation, enabled, acceleration, by device id or name |
| `xorg_keyboard` | r/a | the XKB map; set layout, variant, model, options |
| `xorg_screenshot` | d | a PNG of the screen, one output or one window, inside the home (default `~/Pictures/Screenshots/`) |
| `xorg_x_log` | r | the X server's log for the running session: version, start, errors (and warnings) |
All answers are structured. Each tool that changes the running server says how long the change lasts.
**How a tool reaches the session** (`internal/desktop`, the same copy in every desktop module). The
runtime is a system service running as the operator account. It has no `DISPLAY`, no `XAUTHORITY` and
no session bus. The tool reads them from the session's own processes:
1. It looks at the account's processes, preferring the window manager.
2. It reads only a fixed list of words, never the rest. A session's environment held secrets on these
machines.
3. It asks logind whether that session is active and local.
4. It checks that the display's socket still exists.
The bus it hands on is the user manager's (`unix:path=/run/user/<uid>/bus`). With no session, a tool
answers `{"error": "no-graphical-session", "reason": …, "looked": […]}`.
The screenshot needs no screenshot program. `xwd` dumps the screen, and the module turns the dump
into a PNG itself.
## What it improves
- **One environment.** The session sources the shells' environment file. It no longer exports a
hand-kept second copy, and never sources the predecessor's secrets file.
- **No compiler needed for the mesh's resources**, and contributions are placed in order rather than
`#include`d.
- **Monitor layouts by the monitors' identity** (autorandr, research 026/04), not scripts with port
names baked in. The package's own udev rule and service apply the matching profile on hotplug.
- **No second bus.** The `dbus-launch` fallback that once ran the whole session on a private bus is
gone. That stale session can still be seen today on one workstation: `session_bus` in a tool's
answer shows it.
## What it leaves found
`~/.xprofile`, `~/.Xresources`, `~/.Xresources.d/`, `~/.screenlayout/`, the arandr scripts, every
line of yours below the block, and `/etc/X11/xinit/xinitrc.d/`.
## The one-off migration (ADR 0182)
**Assign the desktop's modules together** (`xorg`, `lemurs`, `i3`, `xterm`, `adwaita` and the
companions above) and do this migration first. **Until `i3` is assigned, nothing changes for you.** The block's `last` slot is empty, so the block
runs and falls through to your own lines below it, which still end in `exec i3`. The environment is
sourced twice and the import runs twice, which is harmless.
**Once `i3` is assigned, nothing below the block runs.** Before that push, sort today's
`~/.xinitrc` lines (both workstations hold the same file):
| today's line | where it goes |
|---|---|
| the `/etc/X11/xinit/xinitrc.d/?*.sh` loop | **delete**: the block imports `DISPLAY` and `XAUTHORITY` itself, and the login manager's X setup runs that directory too |
| `eval $(gnome-keyring-daemon --start …)` and `export SSH_AUTH_SOCK` | **delete** once `gnome-keyring` is assigned. PAM starts and unlocks the keyring, and that module names the ssh agent's socket in the `xinitrc` slot `first`. Until then, `~/.xinitrc.local` |
| `export PATH=…` (nine entries) | `~/.local/bin`, `~/scripts` and `~/scripts/bin` come from `zsh`'s environment already. **Delete** `~/scripts/i3-sessions/commands` and `~/scripts/mediahuis`: neither exists on either workstation. Move `~/.cargo/bin`, `~/.config/rofi/scripts`, `~/.dotnet` and `~/.dotnet/tools` to `~/.xinitrc.local` until a module carries them |
| `DOTNET_ROOT`, `DOTNET_CLI_TELEMETRY_OPTOUT` | `~/.xinitrc.local` |
| `XDG_CONFIG_HOME` | **delete**: `zsh` contributes it |
| `XDG_DATA_DIRS` with the flatpak directories | `~/.xinitrc.local` until the `flatpak` module |
| `QT_QPA_PLATFORMTHEME`, `GTK_THEME`, `QT_STYLE_OVERRIDE`, `GTK2_RC_FILES`, `QT_SELECT` | **delete** once `adwaita` is assigned (its environment contributions) |
| `XDG_SESSION_DESKTOP`, `XDG_CURRENT_DESKTOP` and their comment | **delete** once `i3` is assigned |
| `dbus-update-activation-environment --systemd …` and its comment | **delete**: the block's step 2 |
| `~/scripts/xdg-appearance \|\| true` | **delete** once `adwaita` is assigned |
| `MY_KV_PATH`, `MY_LIB_PATH`, `MY_STREAMING_PATH` | `~/.xinitrc.local` (they are yours) |
| `[ -f "$HOME/.config/hal/env" ] && . …` (the secrets file) | **delete** (ADR 0208 §5, research 027 Q2). Whatever in the session needed one of those tokens gets it the way research 027 settles |
| `xset s 1800`, `xset dpms 1800 1800 3600` | **delete** once `screen-lock` is assigned (its `normal` slot line). Until then, `~/.xinitrc.local` |
| `~/.fehbg &` | **delete** once `feh` is assigned (its `normal` slot line). Until then, `~/.xinitrc.local` |
| the `xss-lock` respawn loop | **delete** once `screen-lock` is assigned. Until then, `~/.xinitrc.local` |
| `exec i3 --shmlog-size=26214400` | **delete**: `i3` contributes `exec i3` to the `last` slot |
Then **delete everything below the block**. The old `#!/bin/sh` line now sits below the block too and
means nothing there. The file is run with `sh` (by the login manager's entry and by `startx`), never
executed by its first line.
**`~/.xprofile`.** The login manager's X setup sources it, and it sources `~/.xinitrc`. Once `i3` and
`lemurs` are assigned, the session entry `/etc/lemurs/wms/i3` runs `~/.xinitrc` itself, so **delete
`~/.xprofile`**. Its bus logic is the block's now. If you keep it, delete its `. ~/.xinitrc` line, or
the session starts from `.xprofile` and the login manager's entry is never reached. Either way works
once, but only one should.
**`~/.Xresources`** holds `#include ".Xresources.d/xterm"`, `#include ".Xresources.d/xft"` and the
two `Xcursor` lines:
- The `xft` include and `~/.Xresources.d/xft` are **deleted** now. This module's file carries the
same five values.
- The `xterm` include and `~/.Xresources.d/xterm` are **deleted** once `xterm` is assigned. Kept,
they win over the module's font and colours.
- The `Xcursor` lines are **deleted** once `adwaita` is assigned.
- A file left empty is deleted.
**Monitor layouts.** For each place you use, arrange the monitors (`xorg_set_mode`, or arandr once
more), then `layout` `save` it under a name. Once every place has a profile, these retire: the
`~/.screenlayout/` and `~/scripts/.screenlayouts/` scripts, the laptop's hotplug rule, and its i3
bindings. Those bindings belong to that machine's hardware module, not here.
## What changes when it is assigned
| | g14 (laptop) | shanks (desktop) |
|---|---|---|
| packages | `autorandr` installed; the rest are there already | the same |
| files | `~/.config/xorg/xresources` new; a block added at the start of `~/.xinitrc` | the same |
| running session | nothing: everything takes effect at the next login | nothing |
| next login, before `i3` is assigned | the block runs, then the old file below it. Fonts unchanged (96 DPI); `autorandr --change` does nothing with no profiles | the same. Its session moves to the user manager's bus at the next login, as it should have |
| next login, after `i3` is assigned | only the block: what the table above did not move is gone | the same |
## Blockers
- **The capability.** ADR 0208 §3 says `graphical-session` gates the display server. The host
reports that capability from its own environment. It is a root daemon with no `DISPLAY`, so the
capability is **no on both workstations** (`node show`, 2026-10-04). Gated on it, `xorg` could
never be assigned. This manifest uses **`seat`** instead: graphics hardware with a connected
display, yes on both. The ADR's wording needs a progressive insight, or the host needs a probe
that finds the session the way `internal/desktop` does.
- **Unassigning removes the packages.** The host takes a package away when its declaration goes,
and `xorg-server` is among them. Do not unassign `xorg` from a workstation you are sitting at
without another display server assigned.
- **The `# <module>` naming lines in the `xresources` slots** are safe only because this module
merges with `-nocpp`. If the controller rendered `!` for `xresources`, that would be the
format's own comment.