Files
command-center/agent/managed-skills/niri-multi-monitor-screenshot-capture/SKILL.md
T

3.0 KiB

name, description
name description
niri-multi-monitor-screenshot-capture Diagnose flameshot screenshot failures on niri with 2+ outputs (portal single-output limit) and capture all monitors via niri-native per-output actions

Niri multi-monitor screenshot capture

Flameshot works on a single niri output, dies the moment a second output connects. Diagnose via compositor journal, capture full desk with niri-native per-output actions — not grim, not portal.

Diagnose (one pass)

  1. journalctl --user -u niri --since today | grep -i screenshot
    • WARN niri::niri: error taking a screenshot: Condition failed: outputs.len() == 1 (2 vs 1) → flameshot's portal path (xdg-desktop-portal-gnome → niri GNOME-Screenshot D-Bus → screenshot_all_outputs ensure!(outputs.len()==1)). Upstream niri#4313, unfixed in 26.04. Symptoms: error bubbles appearing after a delay (portal round-trip), count = number of hotkey presses.
  2. niri msg outputs — count active outputs. 1 output = flameshot fine; the bug is dock-state-dependent (portable works, docked breaks).
  3. Per-mode status: flameshot screen (focused output) works docked; flameshot gui region works (mixed-res scaling bug = flameshot#4449); flameshot full dead on 2+.

Capture all monitors (niri-native)

niri 26.04 has NO all-output capture: screenshot UI is single-output by design (screenshot_ui.rs: "allows only single-output selections for now"); D-Bus path hard-limited. Script pattern:

prev=$(niri msg --json focused-output | jq -r .name)
while IFS= read -r mon; do
  [ "$mon" != "$prev" ] && niri msg action focus-monitor "$mon"
  sleep 0.15                       # let compositor render a frame after focus move
  niri msg action screenshot-screen --path "$out/Screenshot from $ts $mon.png"
done < <(niri msg --json outputs | jq -r '.[].name')
# restore focus: niri msg action focus-monitor "$prev"

CLI facts (niri msg action screenshot-screen --help): --path must be ABSOLUTE; -d/--write-to-disk default true (also saves per screenshot-path); -p/--show-pointer default true.

KDL gotchas (bit me twice; bats niri validate catches them)

  • KDL line comments are //, NEVER # — # = parse storm "identifiers cannot be used as arguments".
  • screenshot-path is a ROOT-level config node (config.screenshot_path in config.rs); do NOT nest it in output {} — root-level bare node validates and works; output { ... } without a name/match arg fails: "additional argument name is required".
  • Root-level prefer-no-csd is a silent no-op (output-section property) — root unknown nodes only warn.

Verify

  • Run the script live; file each PNG (valid dimensions per monitor); niri msg --json focused-output equals pre-run monitor.
  • bats tests/ incl. niri-config assembly validate on every desk profile before commit.
  • Deploy: symlink script into ~/.local/bin (repo scripts/ convention, like desk.sh); bind spawn-sh "name" in keybinds.kdl; niri hot-reloads config on file change.