docs(DE): full redesign sweep — desk profiles, keybinds, noctalia v5, handy fleet wiring, incident log

- niri: index/config-tree rework, displays.md desk-profile rewrite,
  tips/action-reference/default-bindings refresh (26.04),
  generated keybindings.md (119 bindings)
- noctalia-shell.md: settings.toml lives in ~/.local/state, taskbar
  widget, v5 keybind table (Mod+D/Mod+V/Alt+Tab)
- handy.md: NEW; live nested store shape, fleet endpoint, known
  thinking-model empty-content breaker + verified cure
- log.md: DE redesign entry + pam_faillock sudo-lockout incident
This commit is contained in:
Frank committed 2026-10-09 14:42:08 -07:00
1 parent 37554b0fb0
commit b441cfda56
10 files changed
+974 -166

No files matched your search

+91 -82
View File
@@ -1,126 +1,135 @@
---
type: reference
title: Display Configuration
description: Niri output configuration for a multi-monitor desk rig — portrait transforms, position pinning, scale, and iGPU display ownership.
description: Niri output configuration on blankpad — EDID-identity-keyed desk profiles, hotplug auto-switching, portrait transforms, position pinning, and GPU notes (AMD Cezanne now; Intel Arc render-node pin is dankware-legacy).
tags:
- niri
- display
- monitors
- rotation
verified: 2026-08-28
- desk-profiles
verified: 2026-10-09
---
# Display Configuration
How a multi-monitor desk rig is configured in niri: portrait external panels
flanking a laptop panel, positions pinned by hand, and the integrated GPU kept
as the display owner.
Niri output config on **blankpad** is a **desk-profile system**: the laptop
travels between desks, connector names (`DP-2`, `HDMI-A-1`) reshuffle per
dock, so panels are configured by **EDID identity** (manufacturer model
serial), and *positions* live in per-desk files. A hotplug watcher and a
manual picker keep the live layout matched to reality.
**Config:** `~/.config/niri/cfg/display.kdl`
**Probe:** `wlr-randr` (installed by CachyOS) reports what the compositor
actually committed — connectors, preferred modes, transforms, scale, positions.
**Mechanism:** `~/.config/niri/cfg/display.kdl` is a **symlink** to the
active profile (`desk-1-home.kdl`, `desk-2-portable.kdl`, …).
`config.kdl` includes it with `include optional=true` so a missing link is a
warning, not a parse failure.
**Probe:** `niri msg outputs` is authoritative (connector, full EDID long
name, modes, transform, scale, position, VRR). `wlr-randr` remains available.
---
## The rig
Three outputs in a row: two external monitors rotated to portrait, one
internal laptop panel in landscape at the right.
## The desk-1 rig (home)
```
┌──────────────┬──────────────┬──────────────┐
│ │ │ │
│ DP-1 │ DP-3 │ eDP-1 │
│ external │ external │ internal │
│ 1920x1200 │ 1920x1200 │ 2560x1600 │
│ Portrait │ Portrait │ Landscape │
│ 90° rotate │ 270° rotate │ 1.5x scale │
│ │ │ │
└──────────────┴──────────────┴──────────────┘
x=0,0 x=1200,0 auto-placed
┌──────────────┬──────────────────────────────┬──────────┐
│ PM1601 │ Sceptre M34 ultrawide │ eDP-1 │
│ portrait │ 3440x1440 @ 239.999 │ 1920x1080│
│ 1200x1920 │ landscape, scale 1 │ scale 1 │
│ rot 90 │ position 1581,-600 │ 5021,-240│
└──────────────┴──────────────────────────────┴──────────┘
HDMI-A-1 DP-2 (focus-at-startup) eDP-1 (fallback focus)
```
| Output | Role | Transform | Position | Scale |
|--------|------|-----------|----------|-------|
| `DP-1` | External, left | `90` (portrait) | `x=0 y=0` | 1.0 |
| `DP-3` | External, center | `270` (portrait) | `x=1200 y=0` | 1.0 |
| `eDP-1` | Internal panel | none (landscape) | auto-placed | 1.5 |
| Panel | EDID long name | Keyed by | Notes |
|-------|----------------|----------|-------|
| Sceptre M34 ultrawide | `Sceptre Tech Inc Sceptre M34 Unknown` | identity | `mode "3440x1440@239.999"`, `focus-at-startup` |
| PM1601 portrait | *not captured* | connector `HDMI-A-1` | dock-dependent; run `desk.sh capture` when plugged in, then re-key |
| eDP-1 laptop panel | `Lenovo Group Limited 0x40A9 Unknown` | identity | scale **1.0** (deliberate; Ghostty grid at 1.25 rejected 2026-10-09), second `focus-at-startup` |
The two portrait panels are 1200 px wide after rotation, which is why the
second one starts at `x=1200`. The internal panel declares no position at
all — niri auto-places it to the right of the explicitly positioned outputs.
That is the only reliable way to keep a third panel from landing on top of
the rig when a cable is re-seated.
VRR: DP-2 supports it; **left disabled by decision** (2026-10-09). Add
`variable-refresh-rate` per output if that ever changes.
---
`focus-at-startup` on both M34 and eDP-1: niri focuses in config order, so
the ultrawide wins when present and the laptop panel is the fallback.
## Desk switching
```mermaid
graph LR
P[plug / unplug] --> E[niri event-stream<br/>OutputsChanged]
E --> M[desk-autoswitch.sh<br/>debounced matcher]
M --> R{profile requires<br/>subset of connected?}
R -->|best match| A[desk.sh apply:<br/>validate - relink - reload - notify]
R -->|none| K[keep current + notify]
D[Mod+Shift+D] --> C[desk.sh cycle] --> A
```
- **Matcher:** each profile declares `// requires: <long name>` (all must be
connected) and `// optional:` comment lines; the most-specific eligible
profile wins.
- **Apply order is load-config-safe:** `niri validate` the *current* config
first, relink `display.kdl`, then `niri msg action load-config-file`,
then a noctalia notification names the active desk. A profile that fails
validation can never go live.
- **Manual override:** `Mod+Shift+D` cycles profiles (wrong auto-pick, or
same panels, new layout).
- **New desk:** `desk.sh capture` prints connected long names → copy into a
new `desk-N-<name>.kdl` (see `desk-2-portable.kdl` skeleton, portable
monitor EDID pending first capture).
- Unit: `blankpad-desk-autoswitch.service` (systemd user,
`ConditionHost=blankpad`).
## The config pattern
```kdl
output "DP-1" {
transform "90"
position x=0 y=0
}
output "DP-3" {
transform "270"
position x=1200 y=0
}
output "eDP-1" {
// no position — auto-placed to the right of the DP-* outputs
scale 1.5
// desk-1-home.kdl — one block per panel, keyed by identity
output "Sceptre Tech Inc Sceptre M34 Unknown" {
mode "3440x1440@239.999"
scale 1
transform "normal"
position x=1581 y=-600
focus-at-startup
}
```
Three rules learned the hard way:
Four rules learned the hard way:
1. **One output, one block.** A second block keyed by the make/model string
(`output "Samsung Electric Company …"`) that also sets `transform` fights
the connector-keyed block. Use the make/model form only to pin a `mode`
for an output whose connector name is unstable across boots, and never
set a transform in both places.
2. **Positions are absolute, not relational.** Niri does not reflow the rig
when one panel is disconnected — a missing output leaves a hole at its
coordinates. Verify with `wlr-randr` after any cable or dock change.
3. **Re-assert rotation after a config reload, then confirm.** A transform
silently reverting to landscape is the usual symptom of a duplicate
output block, not of a failed reload.
Modes must match what the panel reports. Check `wlr-randr` for the preferred
mode before writing a `mode` line; a `mode` the panel cannot drive leaves the
output dark or drops it.
---
1. **One output, one block.** Two blocks matching the same output (connector
name *and* long name) fight over properties — transform reverts are the
classic symptom. Desk profiles are self-contained: a panel's identity,
mode, transform, **and** position live in exactly one block per file.
2. **Key by EDID identity, not connector.** Connector names swap between
docks; the EDID long name travels with the panel. Serials on these panels
read `Unknown`, so the full long name (manufacturer model serial) is the
match string — copy it verbatim from `niri msg outputs`.
3. **Positions are absolute, not relational.** A disconnected panel leaves
a hole; that is exactly why positions belong to the *desk profile*, not a
global file.
4. **Modes must match what the panel reports** (`niri msg outputs` →
`Mode: current …`). A mode the panel cannot drive leaves the output dark.
## GPU assignment
The integrated GPU drives all display output; the discrete GPU is available
for compute but must not take the outputs. Pin the render node explicitly in
`~/.config/niri/cfg/misc.kdl`:
blankpad is a **single AMD Cezanne iGPU** (`/dev/dri/renderD128`, PCI
`07:00.0`). No render-node pin exists in `misc.kdl` — and must not: the
blankpad override file deliberately deletes the pin.
```kdl
environment {
render-node "/dev/dri/renderD129"
}
```
Without the pin, the session's render node follows probe order and can land
on the discrete GPU — which costs display-linked copies and, on hybrid
graphics laptops, is a common trigger of the suspend/resume deadlock covered
in the sleep-wake investigation report.
---
The `environment { render-node "/dev/dri/renderD129" }` pin found in the
*base* `misc.kdl` is **dankware-legacy** (Intel Arc iGPU + discrete GPU
hybrid; pin kept the iGPU as display owner and sidesteps the suspend/resume
deadlock documented in the sleep-wake investigation). Deploying that base
file on blankpad pins a render node that does not exist — this is why
blankpad runs the `zz-blankpad` override layer (see [[index|the per-host
symlink matrix]]).
## Reload
`niri msg action load-config-file` reloads live. Never `SIGHUP` the process
— see [[tips]].
---
## See Also
- [Niri](/tools/niri/index.md) — compositor overview and config layout
- [[tips|tips]] — reload path and other gotchas
- [[tips|tips]] — reload path, `niri msg layers/casts`, includes
- [[noctalia-shell|noctalia-shell]] — notification shown on desk switch
- [[cachyos|cachyos]] — operating system, including `wlr-randr`