#!/usr/bin/env bash set -u direction="${1:-}" terminal_pid="${2:-}" case "$direction" in left|right|up|down) ;; *) printf 'usage: %s \n' "$0" >&2 exit 2 ;; esac # Window titles can change with the workspace. Find a foreground Herdr # process belonging to this terminal instead of relying on its title. is_in_terminal() { local pid="$1" local parent while [[ "$pid" =~ ^[0-9]+$ && "$pid" -gt 1 ]]; do [[ "$pid" == "$terminal_pid" ]] && return 0 parent="$(ps -o ppid= -p "$pid" 2>/dev/null | tr -d '[:space:]')" [[ -n "$parent" && "$parent" != "$pid" ]] || return 1 pid="$parent" done return 1 } herdr_found=false if [[ "$terminal_pid" =~ ^[0-9]+$ ]]; then while read -r pid process_group foreground_group; do # The server has no foreground terminal; a suspended client is not active. if [[ "$foreground_group" -gt 0 && "$process_group" == "$foreground_group" ]] && is_in_terminal "$pid"; then herdr_found=true break fi done < <(ps -C herdr -o pid=,pgid=,tpgid= 2>/dev/null) fi if [[ "$herdr_found" != true ]]; then printf 'not-herdr\n' exit fi herdr_bin="${HERDR_BIN_PATH:-$HOME/.local/share/mise/installs/herdr/latest/herdr}" if [[ ! -x "$herdr_bin" ]]; then printf 'hyprland\n' exit fi handled() { printf 'handled\n' exit } fall_through() { printf 'hyprland\n' exit } current="$("$herdr_bin" pane current --current 2>/dev/null)" || { fall_through } pane_id="$(jq -r '.result.pane.pane_id // empty' <<<"$current")" if [[ -z "$pane_id" ]]; then fall_through fi process_info="$( "$herdr_bin" pane process-info --pane "$pane_id" 2>/dev/null || true )" nvim_pid="$( jq -r ' [ (.result.process_info.foreground_processes // [])[] | select( ((.name // "") | ascii_downcase) == "nvim" or ((.name // "") | ascii_downcase) == "vim" ) | .pid ][0] // empty ' <<<"$process_info" 2>/dev/null )" if [[ -n "$nvim_pid" ]]; then safe_pane_id="${pane_id//[^a-zA-Z0-9_.-]/_}" runtime_dir="${XDG_RUNTIME_DIR:-/tmp}" socket_path="$runtime_dir/nvim-hypr-nav-pane-$safe_pane_id.sock" response="$( printf 'nav:%s\n' "$direction" | socat -T 0.25 -,ignoreeof "UNIX-CONNECT:$socket_path" 2>/dev/null || true )" if [[ "$response" == *success* ]]; then handled fi fi neighbor="$( "$herdr_bin" pane neighbor \ --pane "$pane_id" \ --direction "$direction" \ 2>/dev/null )" || { fall_through } # A zoomed pane deliberately falls through to Hyprland. Do not expose or # navigate to another Herdr pane. if jq -e '.result.neighbor.layout.zoomed == true' \ >/dev/null 2>&1 <<<"$neighbor"; then fall_through fi neighbor_id="$( jq -r '.result.neighbor.neighbor_pane_id // empty' <<<"$neighbor" )" # Herdr 0.7.5 returns the current pane itself when there is no neighbour. if [[ -z "$neighbor_id" || "$neighbor_id" == "$pane_id" ]]; then fall_through fi if "$herdr_bin" pane focus \ --pane "$pane_id" \ --direction "$direction" \ >/dev/null 2>&1; then handled else fall_through fi