diff --git a/roles/packages/tasks/macosx/main.yml b/roles/packages/tasks/macosx/main.yml index af43bce..26ba564 100644 --- a/roles/packages/tasks/macosx/main.yml +++ b/roles/packages/tasks/macosx/main.yml @@ -61,6 +61,7 @@ 'docker', 'gimp', 'gitkraken', + 'hammerspoon', 'inkscape', 'mark-text', 'mattermost', diff --git a/roles/yabai/files/hammerspoon/init.lua b/roles/yabai/files/hammerspoon/init.lua new file mode 100644 index 0000000..47bfaa9 --- /dev/null +++ b/roles/yabai/files/hammerspoon/init.lua @@ -0,0 +1,9 @@ +local configDir = os.getenv("HOME") .. "/.hammerspoon" + +for file in hs.fs.dir(configDir) do + if file:match("%.lua$") and file ~= "init.lua" then + local module = file:gsub("%.lua$", "") + require(module) + end +end + diff --git a/roles/yabai/files/hammerspoon/yabai.lua b/roles/yabai/files/hammerspoon/yabai.lua new file mode 100644 index 0000000..922e03a --- /dev/null +++ b/roles/yabai/files/hammerspoon/yabai.lua @@ -0,0 +1,33 @@ +local outputFile = os.getenv("HOME") .. "/.cache/display_state.json" + +local function handleDisplayChange() + local screens = hs.screen.allScreens() + local screenInfo = {} + + for _, screen in ipairs(screens) do + table.insert(screenInfo, { + name = screen:name(), + uuid = screen:getUUID(), + isPrimary = screen == hs.screen.primaryScreen(), + frame = { + x = screen:frame().x, + y = screen:frame().y, + w = screen:frame().w, + h = screen:frame().h + } + }) + end + + -- Write JSON to file + local file = io.open(outputFile, "w") + file:write(hs.json.encode(screenInfo, true)) -- true = pretty print + file:write("\n") + file:close() + + -- Trigger yabai update + hs.execute(os.getenv("HOME") .. "/.config/yabai/on_display_state_changed.sh " .. outputFile) +end + +screenWatcher = hs.screen.watcher.new(handleDisplayChange) +screenWatcher:start() + diff --git a/roles/yabai/files/on_display_state_changed.sh b/roles/yabai/files/on_display_state_changed.sh new file mode 100755 index 0000000..dadd79f --- /dev/null +++ b/roles/yabai/files/on_display_state_changed.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +set -euo pipefail + +STATE_FILE="$1" +LABELS=("browser" "dev" "cli" "comm" "org" "float" "free" "private" "vcs") +CONF_LABEL="conf" +BUILTIN_NAME="Built-in Retina Display" + +builtin_uuid=$(jq -r --arg name "$BUILTIN_NAME" '.[] | select(.name == $name) | .uuid' "$STATE_FILE") +all_uuids=($(jq -r '.[].uuid' "$STATE_FILE")) +num_screens=${#all_uuids[@]} + +echo "Found ${num_screens} screens:" +for uuid in "${all_uuids[@]}"; do + echo " - $uuid" +done + +# Get display index mapping +uuid_to_index="" +while IFS= read -r line; do + uuid=$(echo "$line" | jq -r '.uuid') + index=$(echo "$line" | jq -r '.index') + if [[ "$uuid" == "$builtin_uuid" ]]; then + builtin_index="$index" + fi + for u in "${all_uuids[@]}"; do + if [[ "$uuid" == "$u" ]]; then + last_match_index="$index" + fi + done +done < <(yabai -m query --displays | jq -c '.[]') + +echo "Primary screen: index=${builtin_index}, uuid=${builtin_uuid}" + +# Handle scenarios +if [[ "$num_screens" -eq 1 ]]; then + for label in "${LABELS[@]}"; do + echo "Moving space labeled ${label} to display ${last_match_index}" + yabai -m space --label "$label" --display "$last_match_index" || true + done + +elif [[ "$num_screens" -eq 2 ]]; then + # determine secondary index + for uuid in "${all_uuids[@]}"; do + if [[ "$uuid" != "$builtin_uuid" ]]; then + secondary_uuid="$uuid" + fi + done + + secondary_index=$(yabai -m query --displays | jq -r --arg uuid "$secondary_uuid" '.[] | select(.uuid == $uuid) | .index') + echo "Secondary screen: index=${secondary_index}, uuid=${secondary_uuid}" + + for label in "${LABELS[@]}"; do + echo "Moving space labeled ${label} to display ${secondary_index}" + yabai -m space --label "$label" --display "$secondary_index" || true + done + + # Create conf space if it doesn't exist + conf_exists=$(yabai -m query --spaces | jq -e '.[] | select(.label == "conf")' > /dev/null 2>&1 && echo yes || echo no) + + if [[ "$conf_exists" == "no" ]]; then + yabai -m display --focus "$secondary_index" + yabai -m space --create + last_space=$(yabai -m query --spaces | jq '.[].index' | sort -n | tail -n1) + yabai -m space "$last_space" --label "$CONF_LABEL" + fi +else + echo "Unsupported number of screens: $num_screens" + exit 1 +fi diff --git a/roles/yabai/files/skhdrc.sh b/roles/yabai/files/skhdrc.sh index e82fb44..06b9375 100644 --- a/roles/yabai/files/skhdrc.sh +++ b/roles/yabai/files/skhdrc.sh @@ -15,27 +15,29 @@ alt + shift - k : yabai -m window --warp north alt + shift - l : yabai -m window --warp east # send window to a space -alt + shift - 1 : yabai -m window --space browser -alt + shift - 2 : yabai -m window --space dev -alt + shift - 3 : yabai -m window --space cli -alt + shift - 4 : yabai -m window --space comm -alt + shift - 5 : yabai -m window --space org -alt + shift - 6 : yabai -m window --space float -alt + shift - 7 : yabai -m window --space free -alt + shift - 8 : yabai -m window --space private -alt + shift - 9 : yabai -m window --space vcs +alt + shift - 1 : yabai -m window --space 1 # browser +alt + shift - 2 : yabai -m window --space 2 # dev +alt + shift - 3 : yabai -m window --space 3 # cli +alt + shift - 4 : yabai -m window --space 4 # comm +alt + shift - 5 : yabai -m window --space 5 # org +alt + shift - 6 : yabai -m window --space 6 # float +alt + shift - 7 : yabai -m window --space 7 # free +alt + shift - 8 : yabai -m window --space 8 # private +alt + shift - 9 : yabai -m window --space 9 # vcs +alt + shift - s : yabai -m window --space 11 # conf # space 0 is reserved for balancing window sizes # moves focus between spaces -alt - 1 : yabai -m space --focus browser -alt - 2 : yabai -m space --focus dev -alt - 3 : yabai -m space --focus cli -alt - 4 : yabai -m space --focus comm -alt - 5 : yabai -m space --focus org -alt - 6 : yabai -m space --focus float -alt - 7 : yabai -m space --focus free -alt - 8 : yabai -m space --focus private -alt - 9 : yabai -m space --focus vcs +alt - 1 : yabai -m space --focus 1 # browser +alt - 2 : yabai -m space --focus 2 # dev +alt - 3 : yabai -m space --focus 3 # cli +alt - 4 : yabai -m space --focus 4 # comm +alt - 5 : yabai -m space --focus 5 # org +alt - 6 : yabai -m space --focus 6 # float +alt - 7 : yabai -m space --focus 7 # free +alt - 8 : yabai -m space --focus 8 # private +alt - 9 : yabai -m space --focus 9 # vcs +alt - s : yabai -m space --focus 11 # conf # space 0 is reserved for balancing window sizes # toggle window split type diff --git a/roles/yabai/files/yabairc.sh b/roles/yabai/files/yabairc.sh index 478db79..e0f82bb 100755 --- a/roles/yabai/files/yabairc.sh +++ b/roles/yabai/files/yabairc.sh @@ -8,15 +8,16 @@ sudo yabai --load-sa yabai -m config layout bsp # Assign space names -yabai -m space 1 --label browser -yabai -m space 2 --label dev -yabai -m space 3 --label cli -yabai -m space 4 --label comm -yabai -m space 5 --label org -yabai -m space 6 --label float -yabai -m space 7 --label free -yabai -m space 8 --label private -yabai -m space 9 --label vcs +yabai -m space 1 --label browser +yabai -m space 2 --label dev +yabai -m space 3 --label cli +yabai -m space 4 --label comm +yabai -m space 5 --label org +yabai -m space 6 --label float +yabai -m space 7 --label free +yabai -m space 8 --label private +yabai -m space 9 --label vcs +yabai -m space 10 --label conf # Space 6 has all windows floating yabai -m config --space float layout float