--- name: niri-output-rotation-pinning description: "Rotate/re-arrange niri monitors (portrait transforms, positions, scale) persistently and verify — covers the output-block-resets-defaults trap and auto-layout reflow that flings unpinned outputs, plus the counter-clockwise transform convention." --- # Rotating and pinning outputs on niri Verified on blankpad, niri 26.04, 2026-09-15 (two PM1601 externals + eDP-1 laptop panel). ## 0. Ground truth first ```bash niri msg outputs # connector names, current mode, scale, transform, logical pos/size niri --version ``` Niri output **names are connector names** (`eDP-1`, `DP-2`, `HDMI-A-1`), not EDID model strings. The EDID strings printed before them in `niri msg outputs` are decoration — use the parenthesized name. On blankpad the live config is split by `include "./cfg/*.kdl"` from `~/.config/niri/config.kdl`, and `cfg/display.kdl` is a **symlink into the dotfiles repo** (`~/.omp/dotfiles/zz-blankpad/niri/.config/niri/cfg/display.kdl`). Edit the repo path, not the symlink target path — they are the same file, but the repo path is the one that gets committed. Check with `ls -ld`. ## 1. Transform direction is counter-clockwise ```bash niri msg output transform --help # "Transform to set, counter-clockwise" ``` | value | effect | |---|---| | `90` | content rotated 90° CCW ⇒ the panel's **top bezel sits on the LEFT** | | `270` | top bezel on the RIGHT | | `180` | upside down | | `flipped-90` / `flipped-270` | mirrored variants | `90` vs `270` is a physical-stand fact no tool can read (portable monitors expose no orientation in EDID/MCCS). Pick one, apply it, and give the user the one-liner to flip — don't stall on the question. ## 2. Two traps that bite when you edit the config 1. **An `output "X" { … }` block resets every property you don't restate to its default.** Pinning position on `eDP-1` without repeating `scale 1.25` silently drops the laptop panel to scale 1.0. Restate `scale` (and `mode` if you care) in every block you touch. 2. **Niri re-runs auto-layout for outputs with no pinned position.** Pinning only the externals shoved `eDP-1` from `0,0` to `3936,0` (laptop became rightmost). If you want the pre-existing left-to-right order, pin every output's position. Positions are logical pixels at each output's own scale. Original order preserved example: ```kdl output "DP-2" { scale 1 transform "90" position x=1536 y=0 } output "HDMI-A-1" { scale 1 transform "90" position x=2736 y=0 } output "eDP-1" { scale 1.25 // MUST restate: dashed-out by the block otherwise position x=0 y=0 } ``` x-offsets chain: `eDP-1` logical width 1536 → DP-2 at 1536 → +1200 (portrait width) → HDMI-A-1 at 2736. ## 3. Apply and verify ```bash niri validate -c ~/.config/niri/config.kdl # exit 0, "config is valid" niri msg action load-config-file # live reload; load-config-file, NOT reload-config sleep 1 niri msg outputs | grep -E '^Output|Transform|Logical|Scale' ``` Success looks like `Logical size: 1200x1920` (swapped) plus `Transform: 90° counter-clockwise` on each rotated panel, with positions matching the intended chain. One-off rotation without touching config (temporary, forgotten on next config change): ```bash niri msg output DP-2 transform 90 ``` ## 4. Commit Dotfiles repo is `dank/command-center` (`main`); push to the local Gitea is permitted. ```bash cd ~/.omp && git pull --ff-only git add dotfiles/zz-blankpad/niri/.config/niri/cfg/display.kdl git commit -m "blankpad(niri): " git push ``` ## Pitfalls - `reload-config` is not a niri subcommand — it's `niri msg action load-config-file`. - Never SIGUSR1 a terminal to "reload"; niri config reloads do not touch ghostty themes. - No man page for niri on this host; `niri --help` and `/usr/share/doc/niri/default-config.kdl` are the docs.