summaryrefslogtreecommitdiff
path: root/raveos-theme/hyprland/theme-data/hypr/scripts/alt-tab.sh
blob: 0a40f9d1cfb09681db36681ece310b97f06edbb9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
#!/bin/bash

STATE_DIR="/tmp/hypr-alt-tab"
STATE_FILE="$STATE_DIR/state"
TIME_FILE="$STATE_DIR/last_press"
CURRENT_INDEX_FILE="$STATE_DIR/current_index"
WINDOW_LIST_FILE="$STATE_DIR/window_list"

# Cooldown in seconds (time within which presses count as "continuous")
COOLDOWN=0.8

# Create state directory if it doesn't exist
mkdir -p "$STATE_DIR"

# Parse arguments
same_type=false
reverse=false
visible_only=false

for arg in "$@"; do
    case $arg in
        --same) same_type=true ;;
        --reverse) reverse=true ;;
        --visible) visible_only=true ;;
    esac
done

# Function to get window class
get_window_class() {
    local window="$1"
    hyprctl clients -j | jq -r --arg addr "$window" '.[] | select(.address == $addr) | .class'
}

# Function to get window title
get_window_title() {
    local window="$1"
    hyprctl clients -j | jq -r --arg addr "$window" '.[] | select(.address == $addr) | .title'
}

# Function to get window workspace ID
get_window_workspace() {
    local window="$1"
    hyprctl clients -j | jq -r --arg addr "$window" '.[] | select(.address == $addr) | .workspace.id'
}

# Function to get active workspaces (visible on monitors)
get_active_workspaces() {
    hyprctl monitors -j | jq -r '.[].activeWorkspace.id'
}

# Function to get all windows sorted by focus history (most recent first)
get_windows_by_focus() {
    hyprctl clients -j | jq -r 'sort_by(-.focusHistoryID) | .[].address'
}

# Function to get visible windows (on active workspaces)
get_visible_windows() {
    # Get all active workspace IDs
    mapfile -t active_workspaces < <(get_active_workspaces)
    
    if [[ ${#active_workspaces[@]} -eq 0 ]]; then
        return
    fi
    
    # Convert active workspaces to JSON array for jq query
    local workspaces_json="["
    for ws in "${active_workspaces[@]}"; do
        workspaces_json+="$ws,"
    done
    workspaces_json="${workspaces_json%,}]"
    
    # Get windows in active workspaces, sorted by focus history
    hyprctl clients -j | jq -r --argjson workspaces "$workspaces_json" '
        [.[] | select(.workspace.id as $ws | $workspaces | index($ws))] 
        | sort_by(-.focusHistoryID) 
        | .[].address
    '
}

# Function to get windows filtered by type (same class or title pattern)
get_same_type_windows() {
    local current_window="$1"
    local current_class=$(get_window_class "$current_window")
    local current_title=$(get_window_title "$current_window")
    
    # Get all windows with same class
    hyprctl clients -j | jq -r --arg class "$current_class" \
        '.[] | select(.class == $class) | .address'
}

# Function to get currently focused window
get_focused_window() {
    hyprctl activewindow -j | jq -r '.address'
}

# Function to check if window exists
window_exists() {
    local window="$1"
    [[ -n "$window" ]] && hyprctl clients -j | jq -r '.[].address' | grep -q "^${window}$"
}

# Function to validate and refresh window list
validate_window_list() {
    local windows=("$@")
    local valid_windows=()
    
    for window in "${windows[@]}"; do
        if window_exists "$window"; then
            valid_windows+=("$window")
        fi
    done
    
    printf '%s\n' "${valid_windows[@]}"
}

# Get currently focused window
current_focused=$(get_focused_window)

# Get appropriate window list based on mode
if [[ "$visible_only" == "true" ]]; then
    # Get only windows in active workspaces (visible on monitors)
    echo "Switching between visible windows only" >&2
    mapfile -t windows < <(get_visible_windows)
    
    # If no visible windows, fall back to all windows
    if [[ ${#windows[@]} -eq 0 ]]; then
        echo "No visible windows found, falling back to all windows" >&2
        mapfile -t windows < <(get_windows_by_focus)
    fi
    
    # Apply same-type filter if requested
    if [[ "$same_type" == "true" ]] && [[ -n "$current_focused" ]]; then
        # Get windows of the same type as current (from visible windows)
        mapfile -t same_type_windows < <(get_same_type_windows "$current_focused")
        
        # Filter visible windows to only include same-type windows
        local filtered_windows=()
        for visible_window in "${windows[@]}"; do
            for same_window in "${same_type_windows[@]}"; do
                if [[ "$visible_window" == "$same_window" ]]; then
                    filtered_windows+=("$visible_window")
                    break
                fi
            done
        done
        
        # If filtered list has windows, use it
        if [[ ${#filtered_windows[@]} -gt 0 ]]; then
            windows=("${filtered_windows[@]}")
        else
            echo "No visible windows of same type, using all visible windows" >&2
        fi
    fi
elif [[ "$same_type" == "true" ]] && [[ -n "$current_focused" ]]; then
    # Get windows of the same type as current (from all windows)
    mapfile -t windows < <(get_same_type_windows "$current_focused")
    
    # If no same-type windows or only current window, fall back to all windows
    if [[ ${#windows[@]} -le 1 ]]; then
        echo "No other windows of same type found, switching to all windows" >&2
        mapfile -t windows < <(get_windows_by_focus)
    fi
else
    # Get all windows
    mapfile -t windows < <(get_windows_by_focus)
fi

# Validate windows still exist
mapfile -t windows < <(validate_window_list "${windows[@]}")

# Exit if no windows or only one window
if [[ ${#windows[@]} -le 1 ]]; then
    echo "Not enough windows to switch" >&2
    exit 0
fi

# Get current time
current_time=$(date +%s.%N)

# Read last press time
last_press=0
[[ -f "$TIME_FILE" ]] && last_press=$(<"$TIME_FILE")

# Calculate time difference
time_diff=$(echo "$current_time - $last_press" | bc -l 2>/dev/null || echo "1")

# Check if we're continuing the Alt+Tab session
continuing_session=false
if (( $(echo "$time_diff <= $COOLDOWN" | bc -l 2>/dev/null || echo "0") )); then
    continuing_session=true
fi

# Find current window index in the list
current_index=-1
for i in "${!windows[@]}"; do
    if [[ "${windows[$i]}" == "$current_focused" ]]; then
        current_index=$i
        break
    fi
done

# If current window not in filtered list, start from beginning
if [[ $current_index -eq -1 ]]; then
    current_index=0
fi

# Calculate target index
if [[ "$continuing_session" == "true" ]] && [[ -f "$CURRENT_INDEX_FILE" ]] && [[ -f "$WINDOW_LIST_FILE" ]]; then
    # Read saved state
    saved_index=$(<"$CURRENT_INDEX_FILE")
    mapfile -t saved_windows < "$WINDOW_LIST_FILE"
    
    # Check if saved windows match current windows
    same_list=true
    if [[ ${#windows[@]} -ne ${#saved_windows[@]} ]]; then
        same_list=false
    else
        for i in "${!windows[@]}"; do
            if [[ "${windows[$i]}" != "${saved_windows[$i]}" ]]; then
                same_list=false
                break
            fi
        done
    fi
    
    if [[ "$same_list" == "true" ]]; then
        if [[ "$reverse" == "true" ]]; then
            target_index=$(( (saved_index - 1 + ${#windows[@]}) % ${#windows[@]} ))
        else
            target_index=$(( (saved_index + 1) % ${#windows[@]} ))
        fi
    else
        # List changed, recalculate from current
        if [[ "$reverse" == "true" ]]; then
            target_index=$(( (current_index - 1 + ${#windows[@]}) % ${#windows[@]} ))
        else
            target_index=$(( (current_index + 1) % ${#windows[@]} ))
        fi
    fi
else
    # Start new session - move from current window
    if [[ "$reverse" == "true" ]]; then
        target_index=$(( (current_index - 1 + ${#windows[@]}) % ${#windows[@]} ))
    else
        target_index=$(( (current_index + 1) % ${#windows[@]} ))
    fi
fi

# Save state
echo "$current_time" > "$TIME_FILE"
echo "$target_index" > "$CURRENT_INDEX_FILE"
printf '%s\n' "${windows[@]}" > "$WINDOW_LIST_FILE"

# Focus the target window
target_window="${windows[$target_index]}"
if window_exists "$target_window"; then
    echo "Switching to window: $target_window (Class: $(get_window_class "$target_window"), Workspace: $(get_window_workspace "$target_window"))" >&2
    hyprctl dispatch focuswindow "address:$target_window"
    hyprctl dispatch bringactivetotop
fi

# Visual feedback: dim inactive windows temporarily
hyprctl keyword decoration:dim_inactive true 2>/dev/null

# Clear any existing reset timer
[[ -f "$STATE_DIR/dim_timer" ]] && kill "$(<"$STATE_DIR/dim_timer")" 2>/dev/null || true

# Start timer to reset dimming
(
    sleep 1.2
    hyprctl keyword decoration:dim_inactive false 2>/dev/null
) &
echo $! > "$STATE_DIR/dim_timer"