#!/usr/bin/env bash # ~/.config/hypr/2d-nav.sh # 2D workspace navigation for Hyprland # Workspaces are named "X" (base row) or "X-Y" (sub-row, Y >= 1) # Usage: 2d-nav.sh [left|right|up|down|move-left|move-right|move-up|move-down] set -euo pipefail ACTION="${1:-}" # --- Parse current workspace --- # Returns e.g. "3" or "3-2" get_current_ws() { hyprctl activeworkspace -j | jq -r '.name' } # Split "X-Y" into X and Y parts parse_ws() { local name="$1" if [[ "$name" =~ ^([0-9]+)-([0-9]+)$ ]]; then echo "${BASH_REMATCH[1]} ${BASH_REMATCH[2]}" elif [[ "$name" =~ ^([0-9]+)$ ]]; then echo "${BASH_REMATCH[1]} 0" else # Named/special workspace we don't manage — bail echo "0 0" fi } make_ws_name() { local x="$1" y="$2" if [[ "$y" -eq 0 ]]; then echo "$x" else echo "${x}-${y}" fi } # Check if a named workspace currently exists (has at least one window OR is active) ws_exists() { local name="$1" hyprctl workspaces -j | jq -e --arg n "$name" '.[] | select(.name == $n)' > /dev/null 2>&1 } # Navigate to workspace (creates it on demand if going up; smart-back if going down) go_to_ws() { local name="$1" hyprctl dispatch workspace "name:${name}" } move_window_to_ws() { local name="$1" hyprctl dispatch movetoworkspace "name:${name}" } # --- Main logic --- current=$(get_current_ws) read -r cx cy <<< "$(parse_ws "$current")" # Min/max for X axis (your config has 1-5 on DP-2, 6-10 on HDMI-A-1) # But for 2D nav we treat X as free-range 1..10 X_MIN=1 X_MAX=10 Y_MIN=0 Y_MAX=9 # Reasonable cap for sub-workspaces case "$ACTION" in right) nx=$(( cx < X_MAX ? cx + 1 : cx )) # When moving right, land on base row of target X (Y=0) target=$(make_ws_name "$nx" 0) go_to_ws "$target" ;; left) nx=$(( cx > X_MIN ? cx - 1 : cx )) target=$(make_ws_name "$nx" 0) go_to_ws "$target" ;; up) ny=$(( cy < Y_MAX ? cy + 1 : cy )) target=$(make_ws_name "$cx" "$ny") go_to_ws "$target" ;; down) if [[ "$cy" -gt 0 ]]; then ny=$(( cy - 1 )) # If the workspace below doesn't exist, fall back to base (Y=0), not Y=(cy-1) target=$(make_ws_name "$cx" "$ny") if [[ "$ny" -gt 0 ]] && ! ws_exists "$target"; then target=$(make_ws_name "$cx" 0) fi else target=$(make_ws_name "$cx" 0) fi go_to_ws "$target" ;; # --- Move window variants --- move-right) nx=$(( cx < X_MAX ? cx + 1 : cx )) target=$(make_ws_name "$nx" 0) move_window_to_ws "$target" ;; move-left) nx=$(( cx > X_MIN ? cx - 1 : cx )) target=$(make_ws_name "$nx" 0) move_window_to_ws "$target" ;; move-up) ny=$(( cy < Y_MAX ? cy + 1 : cy )) target=$(make_ws_name "$cx" "$ny") move_window_to_ws "$target" ;; move-down) if [[ "$cy" -gt 0 ]]; then ny=$(( cy - 1 )) target=$(make_ws_name "$cx" "$ny") if [[ "$ny" -gt 0 ]] && ! ws_exists "$target"; then target=$(make_ws_name "$cx" 0) fi else target=$(make_ws_name "$cx" 0) fi move_window_to_ws "$target" ;; *) echo "Usage: $0 [left|right|up|down|move-left|move-right|move-up|move-down]" >&2 exit 1 ;; esac