Skip to content

Instantly share code, notes, and snippets.

@ridwanyinus
Created January 17, 2026 12:19
Show Gist options
  • Select an option

  • Save ridwanyinus/75b9172a761c417a8a58ce240049f5d5 to your computer and use it in GitHub Desktop.

Select an option

Save ridwanyinus/75b9172a761c417a8a58ce240049f5d5 to your computer and use it in GitHub Desktop.
#!/usr/bin/env bash
#######################################################################################
## Raise, Cycle, or Spawn ##
## ##
## A helper script for the niri window manager that implements ##
## “raise-or-run” behavior with window cycling. ##
## ##
## Based on and improved from eXsoR65 script: ##
## https://gist.github.com/eXsoR65/6c760854ea1187cd398b89b9ef5aa479 ##
#######################################################################################
## Installation ##
## ------------ ##
## 1. Place this script somewhere in your $PATH (e.g. ~/.local/bin), ##
## make it executable, and remove the .sh extension: ##
## ##
## chmod +x raise-cycle-or-spawn ##
## ##
## Usage ##
## ----- ##
## 2. Create a bind in your niri config with spawn-sh. This version only ##
## requires ONE argument. It will use that argument to fuzzy-match the app_id ##
## and, if no window is found, it will use it as the command to spawn. ##
## ##
## Example: ##
## Mod+B { spawn-sh "raise-cycle-or-spawn zen"; } ##
## Mod+return { spawn-sh "raise-cycle-or-spawn kitty"; } ##
## ##
## Behavior ##
## -------- ##
## • If no matching window exists → spawn the application ##
## • If one matching window exists → focus (raise) it ##
## • If multiple matching windows exist → cycle focus between them ##
## • Uses case-insensitive fuzzy matching for app_ids ##
#######################################################################################
set -euo pipefail
raise_or_cycle() {
local search_term="$1"
local tmp
tmp="$(mktemp)"
trap 'rm -f "$tmp"' EXIT
# Get window list from niri
if ! niri msg --json windows >"$tmp" 2>/dev/null; then
return 1
fi
# Find IDs using case-insensitive 'contains'
# It compares the lowercase app_id against your lowercase search term
mapfile -t ids < <(jq -r --arg search "${search_term,,}" \
'.[] | select(.app_id != null) | select(.app_id | ascii_downcase | contains($search)) | .id' <"$tmp")
# If no windows found, return failure so we can spawn
((${#ids[@]})) || return 1
local focused
focused="$(jq -r '.[] | select(.is_focused) | .id' <"$tmp")"
# Find the next window to cycle to
local next_idx=0
for ((i = 0; i < ${#ids[@]}; i++)); do
if [[ "${ids[$i]}" == "$focused" ]]; then
next_idx=$(( (i + 1) % ${#ids[@]} ))
break
fi
done
niri msg action focus-window --id "${ids[$next_idx]}"
exit 0
}
# The single argument serves as both the search term and the executable
target="${1:-}"
if [[ -z "$target" ]]; then
echo "Usage: $0 <app_name>" >&2
exit 2
fi
# 1. Try to focus or cycle existing windows
if raise_or_cycle "$target"; then
exit 0
fi
# 2. If no window matched, spawn the command exactly as typed
niri msg action spawn -- "$target"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment