#!/usr/bin/env bash # scripts/desk-autoswitch.sh — hotplug daemon: apply the matching desk profile # when the SET OF OUTPUTS changes. # # niri has no dedicated hotplug event (verified on 26.04): # - WorkspacesChanged carries every workspace's `output`, so the sorted # unique set of outputs changes exactly on connect/disconnect. # - OutputConfigChanged is only an ACK that an IPC/config output change was # Applied (niri-ipc), NOT a hotplug signal. # # Usage: desk-autoswitch.sh watch # # Environment overrides: # DESK_AUTO_HOSTNAME only run on this hostname (default: blankpad) # DESK_SCRIPT path to desk.sh (default ~/.local/bin/desk.sh) # DESK_NIRI niri binary (default niri) # DESK_DEBOUNCE_S min seconds between applies (default 1.5) set -euo pipefail HOST="${DESK_AUTO_HOSTNAME:-blankpad}" DESK="${DESK_SCRIPT:-$HOME/.local/bin/desk.sh}" NIRI="${DESK_NIRI:-niri}" DEBOUNCE="${DESK_DEBOUNCE_S:-1.5}" usage() { sed -n '2,18p' "$0" | sed 's/^# \{0,1\}//'; } outputs_from_event() { # $1 = one JSON event line jq -r '[.WorkspacesChanged.workspaces[]?.output // empty] | unique | join(" ")' <<<"$1" 2>/dev/null || true } watch_mode() { [ "$(hostname)" = "$HOST" ] || { echo "desk-autoswitch: not on $HOST, exiting"; exit 0; } echo "desk-autoswitch: initial match" "$DESK" match --apply || true local seen="" last_seen="" last=0 now # One JSON event per line; reconnect forever (niri restart drops the stream). while "$NIRI" msg --json event-stream 2>/dev/null | while IFS= read -r line; do case "$line" in *WorkspacesChanged*) seen="$(outputs_from_event "$line")" [ -n "$seen" ] || continue [ "$seen" = "$last_seen" ] && continue last_seen="$seen" now="$(date +%s.%N)" # swallow bursts inside the debounce window if awk -v l="$last" -v n="$now" -v d="$DEBOUNCE" 'BEGIN { exit !(n - l >= d) }'; then last="$now" echo "desk-autoswitch: outputs changed [$seen]" "$DESK" match --apply || true fi ;; esac done; do :; done } case "${1:-}" in watch) watch_mode ;; -h|--help) usage ;; *) usage >&2; exit 1 ;; esac