Skip to content

Instantly share code, notes, and snippets.

@johnlindquist
Created September 11, 2025 15:32
Show Gist options
  • Save johnlindquist/647b1f55ea92fa19b55381bb82a07958 to your computer and use it in GitHub Desktop.
Save johnlindquist/647b1f55ea92fa19b55381bb82a07958 to your computer and use it in GitHub Desktop.
Yabai window management script - move window right to next space

Move Window Right - Yabai Window Management Script

Overview

This Bash script is part of a macOS window management system using Yabai. It moves the currently focused window to the next space to the right, automatically creating a new space if the window is already at the rightmost space.

Purpose

  • Primary function: Move the active window one space to the right
  • Edge case handling: Creates a new space when at the rightmost position
  • Focus management: Ensures the moved window remains focused after the operation

Key Features

1. Intelligent Space Navigation

  • Queries the current space and finds the next available space to the right
  • Uses jq to parse Yabai's JSON output for accurate space information
  • Handles edge cases when there's no space to the right

2. Automatic Space Creation

  • When at the rightmost space, automatically creates a new space
  • Moves the window to the newly created space
  • Maintains user focus on the moved window

3. Comprehensive Logging

  • Logs all operations through a centralized log_helper.sh script
  • Tracks before/after states for debugging
  • Records window IDs, space indices, and display information

4. Error Handling

  • Validates window ID retrieval before attempting operations
  • Exits gracefully with error logging if no window is focused
  • Uses proper null checks for JSON parsing

Technical Details

Dependencies

  • /opt/homebrew/bin/yabai - Yabai window manager
  • /opt/homebrew/bin/jq - JSON processor for parsing Yabai queries
  • log_helper.sh - Centralized logging utility (same directory)

Operation Flow

  1. Initial State Capture: Logs current window, space, and display
  2. Window Identification: Gets the focused window's ID
  3. Space Detection: Finds the next space to the right
  4. Movement Logic:
    • If next space exists: Move window directly
    • If at rightmost space: Create new space, then move
  5. Focus Restoration: Ensures the moved window stays focused
  6. Final State Logging: Records the end state for verification

Key Functions

  • log_state(): Captures and logs the current window management state
  • focus_window(): Refocuses a specific window by ID with verification

Implementation Notes

  • Uses 0.1-second delays after space changes to ensure Yabai completes operations
  • String comparison for empty space detection uses literal '""' check
  • Window focus is explicitly restored after movement to handle Yabai quirks

Usage

~/.config/scripts/move_window_right.sh

Typically bound to a keyboard shortcut via Karabiner-Elements or similar tools.

Integration

Part of a larger window management system with complementary scripts:

  • move_window_left.sh - Move window to previous space
  • space_right.sh - Focus next space without moving windows
  • remove_empty_spaces.sh - Clean up unused spaces

Logging

Logs are written via log_helper.sh with structured format:

  • START/END: Script lifecycle events
  • INFO: Informational messages
  • ACTION: Yabai commands being executed
  • ERROR: Error conditions
  • DEBUG: Detailed state information
#!/usr/bin/env bash
# ---------- move_window_right.sh ----------
# Moves the current window to the next space, creating one if necessary.
# --- Script Setup ---
SCRIPT_NAME="move_window_right"
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
LOGGER_SCRIPT_PATH="$SCRIPT_DIR/log_helper.sh"
# Log the start of the script
"$LOGGER_SCRIPT_PATH" "$SCRIPT_NAME" "START" "Script execution started"
# --- Script Logic ---
y=/opt/homebrew/bin/yabai
jq=/opt/homebrew/bin/jq
# --- Helper Functions ---
log_state() {
local state_type=$1
local current_window=$($y -m query --windows --window 2>/dev/null | $jq -r '.id' 2>/dev/null || echo "null")
local current_space=$($y -m query --spaces --space 2>/dev/null | $jq -r '.index' 2>/dev/null || echo "null")
local current_display=$($y -m query --displays --display 2>/dev/null | $jq -r '.index' 2>/dev/null || echo "null")
"$LOGGER_SCRIPT_PATH" "$SCRIPT_NAME" "${state_type}_STATE" \
"window:$current_window space:$current_space display:$current_display"
}
focus_window() {
local window_id="$1"
if [[ -n "$window_id" ]]; then
"$LOGGER_SCRIPT_PATH" "$SCRIPT_NAME" "ACTION" "Refocusing window $window_id"
$y -m window --focus "$window_id"
# Check what window is actually focused after the command
local actual_focused=$($y -m query --windows --window | $jq '.id')
"$LOGGER_SCRIPT_PATH" "$SCRIPT_NAME" "DEBUG" "Actually focused window after refocus attempt: $actual_focused"
fi
}
# Log before state
log_state "BEFORE"
"$LOGGER_SCRIPT_PATH" "$SCRIPT_NAME" "INFO" "Getting focused window ID"
window_id=$($y -m query --windows --window | $jq '.id')
if [[ -z "$window_id" || "$window_id" == "null" ]]; then
"$LOGGER_SCRIPT_PATH" "$SCRIPT_NAME" "ERROR" "Could not get focused window ID"
"$LOGGER_SCRIPT_PATH" "$SCRIPT_NAME" "END" "Script execution finished with error"
exit 1
fi
"$LOGGER_SCRIPT_PATH" "$SCRIPT_NAME" "INFO" "Focused window ID: $window_id"
"$LOGGER_SCRIPT_PATH" "$SCRIPT_NAME" "INFO" "Querying current space index"
cur=$($y -m query --spaces --space | $jq '.index')
"$LOGGER_SCRIPT_PATH" "$SCRIPT_NAME" "INFO" "Current space index: $cur"
"$LOGGER_SCRIPT_PATH" "$SCRIPT_NAME" "INFO" "Querying next space index"
next=$($y -m query --spaces --display | $jq --argjson cur "$cur" '
map(select(.index > $cur)) # only spaces to the right
| sort_by(.index) | .[0].index // "" # pick the closest, or ""')
"$LOGGER_SCRIPT_PATH" "$SCRIPT_NAME" "INFO" "Next space index: '$next'"
# Use literal string comparison for the jq fallback ""
if [[ "$next" == '""' ]]; then # Check if next IS literally ""
# Edge -> create space, then move window
"$LOGGER_SCRIPT_PATH" "$SCRIPT_NAME" "INFO" "Edge detected (next is \"\"), creating new space"
$y -m space --create
"$LOGGER_SCRIPT_PATH" "$SCRIPT_NAME" "ACTION" "Created new space"
"$LOGGER_SCRIPT_PATH" "$SCRIPT_NAME" "INFO" "Querying last space index"
last=$($y -m query --spaces --display | $jq '.[-1].index')
"$LOGGER_SCRIPT_PATH" "$SCRIPT_NAME" "INFO" "Last space index: $last"
"$LOGGER_SCRIPT_PATH" "$SCRIPT_NAME" "ACTION" "Moving window $window_id to space $last"
$y -m window "$window_id" --space "$last"
"$LOGGER_SCRIPT_PATH" "$SCRIPT_NAME" "ACTION" "Focusing space $last"
$y -m space --focus "$last"
# Small delay to ensure yabai has completed the space switch
sleep 0.1
focus_window "$window_id"
else # Next is NOT ""
# Neighbour exists -> move window there
"$LOGGER_SCRIPT_PATH" "$SCRIPT_NAME" "INFO" "Neighbour found ('$next'), moving window $window_id to space $next"
"$LOGGER_SCRIPT_PATH" "$SCRIPT_NAME" "ACTION" "Moving window $window_id to space $next"
$y -m window "$window_id" --space "$next"
"$LOGGER_SCRIPT_PATH" "$SCRIPT_NAME" "ACTION" "Focusing space $next"
$y -m space --focus "$next"
# Small delay to ensure yabai has completed the space switch
sleep 0.1
focus_window "$window_id"
fi
# Log after state
log_state "AFTER"
"$LOGGER_SCRIPT_PATH" "$SCRIPT_NAME" "END" "Script execution finished"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment