#!/usr/bin/env bash # Cascade focus: window → occupied workspace → monitor (cycles at edges) # Skips the trailing empty workspace — jumps to the monitor instead. # usage: niri-focus.sh up|down set -euo pipefail dir="${1:?usage: niri-focus.sh up|down}" opp=$([ "$dir" = down ] && echo up || echo down) out() { niri msg --json focused-output | jq -r '.name // "none"'; } win() { niri msg --json focused-window | jq -r '.id // "none"'; } # 1) window within the column w0=$(win); niri msg action "focus-window-$dir" >/dev/null 2>&1 || true [ "$(win)" != "$w0" ] && exit 0 # 2) occupied workspace in that direction (skip the empty trailing one) wss=$(niri msg --json workspaces) wins=$(niri msg --json windows) cwsid=$(jq -r 'map(select(.is_focused)) | .[0].id' <<<"$wss") real_neighbor=$(jq -n --argjson wss "$wss" --argjson wins "$wins" \ --argjson cwsid "$cwsid" --arg dir "$dir" ' ($wss[] | select(.id==$cwsid)) as $c | [ $wss[] | select(.output==$c.output) | select(if $dir=="down" then .idx>$c.idx else .idx<$c.idx end) | .id as $wid | select([$wins[] | select(.workspace_id==$wid)] | length > 0) ] | length > 0') if [ "$real_neighbor" = "true" ]; then niri msg action "focus-workspace-$dir" >/dev/null 2>&1 || true exit 0 fi # 3) adjacent monitor… or wrap to the far-end monitor o0=$(out); niri msg action "focus-monitor-$dir" >/dev/null 2>&1 || true [ "$(out)" != "$o0" ] && exit 0 while :; do prev=$(out) niri msg action "focus-monitor-$opp" >/dev/null 2>&1 || true [ "$(out)" = "$prev" ] && break done