Files
command-center/omp-docs/tools/niri/displays.md
T
Frank 334e5d0ee0 docs(niri): the ultrawide's refresh ceiling was a typo, not a limit
The page asserted the Sceptre M34's EDID tops out at 180 Hz. Measured on blankpad
after the merge, `niri msg outputs` lists 3440x1440@239.998 — the claim was false
in main, and it was load-bearing: it is the reason nobody questioned the profile
while the panel ran at 60 Hz for weeks. The real defect was one character: the
profile asked for 239.999, the panel offers 239.998, and niri matches mode strings
verbatim, falling back to *preferred* without failing.

Corrected to the measured mode list, and the deployed 180 is now documented as a
deliberate choice (the link is trained at x8 32.0 GT/s against a max of x16 and the
display engine throws EINVAL), so the next reader neither "restores 240" blind nor
re-pins 180 as a hardware ceiling. The kdl profile comment carries the same facts at
the point of edit, because that is where the next change to this mode gets typed.

Refs #60
2026-10-21 01:06:04 -07:00

10 KiB

type, title, description, tags, verified
type title description tags verified
reference Display Configuration Niri output configuration on blankpad — EDID-identity-keyed desk profiles, hotplug auto-switching, portrait transforms, position pinning, the phantom-mode fallback trap, and GPU notes (AMD Cezanne now; Intel Arc render-node pin is dankware-legacy).
niri
display
monitors
rotation
desk-profiles
2026-10-21

Display Configuration

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.

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). Per-output changes go live with niri msg output <name> <mode|scale|transform|position|off|on> and are temporary — never written to the config file. wlr-randr is not installed on blankpad (pacman -Qs wlr matches nothing, verified 2026-10-21); pages that list it as an installed tool are stale.


The desk-1 rig (home)

┌──────────────┬──────────────────────────────┬──────────┐
│  PM1601      │   Sceptre M34 ultrawide      │  eDP-1   │
│  portrait    │   3440x1440 @ 180.000        │ 1920x1080│
│  1200x1920   │   landscape, scale 1         │ scale 1  │
│  rot 90      │   position 1581,-600         │ 5021,-240│
└──────────────┴──────────────────────────────┴──────────┘
  HDMI-A-1            DP-1 (focus-at-startup)     eDP-1 (fallback focus)
Connector names are today's (`niri msg outputs`, 2026-10-21). They reshuffle with
the dock and the cable, which is why the config keys on the EDID long name instead.
Panel EDID long name Keyed by Notes
Sceptre M34 ultrawide Sceptre Tech Inc Sceptre M34 Unknown identity mode "3440x1440@180.000", deliberate — the panel also offers 3440x1440@239.998, 144.001 and 120.000; 180 is driven while the link is degraded (see the law below) — 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

VRR: the M34 supports it (its connector today is DP-1); 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

graph LR
    P[plug / unplug] --> E[niri event-stream<br/>WorkspacesChanged → output-set diff]
    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

// desk-1-home.kdl — one block per panel, keyed by identity
output "Sceptre Tech Inc Sceptre M34 Unknown" {
    mode "3440x1440@180.000"
    scale 1
    transform "normal"
    position x=1581 y=-600
    focus-at-startup
}

Four rules learned the hard way:

  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. desk-1-home.kdl still carries legacy connector-keyed DP-1 / DP-2 blocks next to the identity block. Their modes were aligned to 180 Hz on 2026-10-21 so a connector-named reconnect cannot drop the panel to 120, but they belong folded into the identity block.
  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. Ask only for modes the panel reports, and copy the string exactly. The M34's Available modes (niri msg outputs, 2026-10-21) are 3440x1440@239.998, 3440x1440@180.000, 144.001, 120.000, … with 59.998 marked preferred. niri matches mode strings verbatim and does not round, so 239.999 — one digit from the real mode, which is what this file and the config both carried for weeks — is not 239.998. A mode it cannot find does not leave the output dark: niri logs configured mode … could not be found, falling back to preferred and quietly drives the panel at its EDID preferred mode — 60 Hz. Nothing looks broken except the refresh rate, and every reconnect repeats the fallback. Check Hz, not just resolution, after any "monitor is acting up".
  5. 180 Hz here is a decision, not a ceiling. The panel offers 239.998 and is driven at 180.000 deliberately: the link trained at x8 32.0 GT/s against a max of x16, and the display engine is throwing DMCUB errors with EINVAL page flips after s2idle, so the 240-class mode is not driven until a reboot clears that state. Before changing the mode, re-read niri msg outputs and the link sysfs; treat "restore 240" as a change to verify on a healthy link, never as the obvious fix. Refs #60.

"The monitors are broken" — three layers, three signatures

A display complaint belongs to one of three layers. Sort it before editing config, because the config is usually innocent — and when it is guilty, the guilt may have been copied from this page.

Layer Log signature Reality Move
Config configured mode … could not be found, falling back to preferred a mode/scale the panel never offered re-read the mode list with niri msg outputs and correct the file; do not trust a documented mode
Link disconnecting connector: "DP-1" followed seconds later by new connector: DP-1, recurring the DP link itself dropping — cable, dock, the monitor's DP-version/DSC setting, or panel power saving every drop re-runs mode selection, so a config error is re-enacted in public; swap the cable before blaming the compositor
Driver Page flip commit failed … (Invalid argument) plus kernel *ERROR* … DMCUB error amdgpu's display engine wedged, typically after an s2idle resume survives until a reboot; niri keeps running while the panel keeps dropping. No config edit clears it

Probes, in this order:

niri msg outputs                    # connector, EDID long name, real mode list, current mode
journalctl --user -b | grep -E 'disconnecting connector|Page flip commit failed|falling back to preferred'
journalctl -k -b  | grep -iE 'amdgpu.*(ERROR|DMCUB)|PM: suspend exit'

Correlate the drop timestamps against PM: suspend exit first. A resume a minute before the first drop is the cause; the config file is not.

To fix a refresh rate without disturbing the session, use the temporary per-output path — it touches one output, applies no config, reloads nothing:

niri msg output "Sceptre Tech Inc Sceptre M34 Unknown" mode "3440x1440@180.000"

It is temporary by design, so correct the profile too and let the next reconnect or load-config-file make it permanent. With the display engine already throwing EINVAL, prefer the per-output path over a full config reload; on a healthy machine either is fine.

GPU assignment

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.

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 — compositor overview and config layout
  • tips — reload path, niri msg layers/casts, includes
  • noctalia-shell — notification shown on desk switch
  • cachyos — operating system