aboutsummaryrefslogtreecommitdiff
path: root/dot_config/hypr/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'dot_config/hypr/scripts')
-rw-r--r--dot_config/hypr/scripts/executable_herdr-smart-nav143
-rw-r--r--dot_config/hypr/scripts/executable_tmux-smart-nav105
2 files changed, 248 insertions, 0 deletions
diff --git a/dot_config/hypr/scripts/executable_herdr-smart-nav b/dot_config/hypr/scripts/executable_herdr-smart-nav
new file mode 100644
index 0000000..36d27aa
--- /dev/null
+++ b/dot_config/hypr/scripts/executable_herdr-smart-nav
@@ -0,0 +1,143 @@
+#!/usr/bin/env bash
+
+set -u
+
+direction="${1:-}"
+terminal_pid="${2:-}"
+
+case "$direction" in
+ left|right|up|down)
+ ;;
+ *)
+ printf 'usage: %s <left|right|up|down> <terminal-pid>\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
diff --git a/dot_config/hypr/scripts/executable_tmux-smart-nav b/dot_config/hypr/scripts/executable_tmux-smart-nav
new file mode 100644
index 0000000..4275462
--- /dev/null
+++ b/dot_config/hypr/scripts/executable_tmux-smart-nav
@@ -0,0 +1,105 @@
+#!/usr/bin/env bash
+
+set -u
+
+direction="${1:-}"
+terminal_pid="${2:-}"
+
+case "$direction" in
+ left)
+ tmux_key="C-h"
+ ;;
+ right)
+ tmux_key="C-l"
+ ;;
+ up)
+ tmux_key="C-k"
+ ;;
+ down)
+ tmux_key="C-j"
+ ;;
+ *)
+ printf 'usage: %s <left|right|up|down> <terminal-pid>\n' "$0" >&2
+ exit 2
+ ;;
+esac
+
+if [[ ! "$terminal_pid" =~ ^[0-9]+$ ]] || ! command -v tmux >/dev/null 2>&1; then
+ printf 'hyprland\n'
+ exit
+fi
+
+# A tmux client is a descendant of the terminal process that owns it. This
+# avoids acting on a client in another Ghostty window.
+is_descendant_of() {
+ local pid="$1"
+ local parent
+
+ while [[ "$pid" =~ ^[0-9]+$ && "$pid" -gt 1 ]]; do
+ if [[ "$pid" == "$terminal_pid" ]]; then
+ return 0
+ fi
+
+ parent="$(ps -o ppid= -p "$pid" 2>/dev/null | tr -d '[:space:]')"
+ [[ -n "$parent" && "$parent" != "$pid" ]] || return 1
+ pid="$parent"
+ done
+
+ return 1
+}
+
+client_tty=""
+while read -r client_pid tty; do
+ if is_descendant_of "$client_pid"; then
+ client_tty="$tty"
+ break
+ fi
+done < <(tmux list-clients -F '#{client_pid} #{client_tty}' 2>/dev/null)
+
+if [[ -z "$client_tty" ]]; then
+ printf 'hyprland\n'
+ exit
+fi
+
+pane_id="$(tmux display-message -p -c "$client_tty" '#{pane_id}' 2>/dev/null)"
+pane_command="$(tmux display-message -p -c "$client_tty" '#{pane_current_command}' 2>/dev/null)"
+
+# Ghostty has one title for the whole tmux window, so it cannot identify the
+# focused pane's process. Ask Neovim through a pane-specific socket only when
+# tmux says this exact pane is running it.
+case "${pane_command,,}" in
+ nvim|vim)
+ safe_pane_id="${pane_id//[^a-zA-Z0-9_.-]/_}"
+ socket_path="${XDG_RUNTIME_DIR:-/tmp}/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
+ printf 'handled\n'
+ exit
+ fi
+ ;;
+esac
+
+# Send the directional key through the matching client so tmux resolves it
+# against that client's own active pane. Its existing C-h/j/k/l bindings also
+# retain the configured Neovim handoff.
+at_edge="$(tmux display-message -p -c "$client_tty" "#{pane_at_${direction}}" 2>/dev/null)"
+
+if [[ -z "$pane_id" || "$at_edge" == "1" ]]; then
+ printf 'hyprland\n'
+ exit
+fi
+
+if tmux send-keys -c "$client_tty" -K "$tmux_key" >/dev/null 2>&1; then
+ selected_pane_id="$(tmux display-message -p -c "$client_tty" '#{pane_id}' 2>/dev/null)"
+
+ if [[ "$selected_pane_id" != "$pane_id" ]]; then
+ printf 'handled\n'
+ exit
+ fi
+fi
+
+printf 'hyprland\n'