This repository has no description
6.7 kB
256 lines
1#!/usr/bin/env bash
2# Workspace Menu - rofi-based menu for workspace management
3# Provides: save layout, restore workspace, delete config, edit config
4
5set -euo pipefail
6
7WORKSPACES_DIR="${HOME}/workspaces"
8mkdir -p "$WORKSPACES_DIR"
9
10# Get current workspace name from sway
11get_current_workspace() {
12 swaymsg -r -t get_workspaces | jq -r '.[] | select(.focused == true).name'
13}
14
15# Get the sway tree for the current workspace (app_id for each window)
16capture_layout() {
17 local ws_name="$1"
18 swaymsg -r -t get_tree | jq --arg ws "$ws_name" '
19 [.nodes[].nodes[] |
20 select(.name == $ws) |
21 .. |
22 select(.type? == "con" and .app_id? != null) |
23 {app_id: .app_id}]
24 '
25}
26
27# Save current workspace layout
28action_save() {
29 local ws_name
30 ws_name=$(get_current_workspace)
31 local config_file="${WORKSPACES_DIR}/${ws_name}.json"
32
33 # Capture current windows
34 local layout
35 layout=$(capture_layout "$ws_name")
36
37 # Load existing config or start fresh
38 local existing="{}"
39 if [ -f "$config_file" ]; then
40 existing=$(cat "$config_file")
41 fi
42
43 # Merge layout into existing config
44 echo "$existing" | jq --argjson layout "$layout" '. + {layout: $layout}' > "$config_file"
45
46 notify-send "Workspace Manager" "Saved layout for workspace '${ws_name}' ($(echo "$layout" | jq 'length') windows)"
47}
48
49# Launch an app by its app_id in the given working directory
50launch_app() {
51 local app_id="$1"
52 local work_dir="$2"
53
54 case "$app_id" in
55 kitty|foot|alacritty|wezterm)
56 # Terminal emulators: open in workspace directory
57 cd "$work_dir" && "$app_id" &
58 ;;
59 *)
60 # Skip everything else (browsers handled by Firefox extension)
61 ;;
62 esac
63}
64
65# Restore workspace to its original state (kill windows + re-run on_create + reopen layout)
66action_restore() {
67 local ws_name
68 ws_name=$(get_current_workspace)
69 local config_file="${WORKSPACES_DIR}/${ws_name}.json"
70
71 if [ ! -f "$config_file" ]; then
72 notify-send "Workspace Manager" "No config found for workspace '${ws_name}'"
73 return 1
74 fi
75
76 # Confirm via rofi
77 local confirm
78 confirm=$(printf "Yes\nNo" | rofi -dmenu -p "Restore workspace '${ws_name}'? (kills all windows)")
79 if [ "$confirm" != "Yes" ]; then
80 return 0
81 fi
82
83 # Kill all windows on current workspace
84 local pids
85 pids=$(swaymsg -r -t get_tree | jq --arg ws "$ws_name" '
86 [.nodes[].nodes[] |
87 select(.name == $ws) |
88 .. |
89 select(.type? == "con" and .pid? != null) |
90 .pid] | unique | .[]
91 ')
92
93 for pid in $pids; do
94 kill "$pid" 2>/dev/null || true
95 done
96
97 # Wait a moment for windows to close
98 sleep 1
99
100 local directory
101 directory=$(jq -r '.directory // "~"' "$config_file" 2>/dev/null)
102 directory="${directory/#\~/$HOME}"
103
104 # Run on_create commands in tmux sessions
105 local on_create
106 on_create=$(jq -r '.on_create // [] | .[]' "$config_file" 2>/dev/null)
107
108 if [ -n "$on_create" ]; then
109 local i=1
110 while IFS= read -r cmd; do
111 local session_name="${ws_name}-start${i}"
112 # Kill existing tmux session if present
113 tmux kill-session -t "$session_name" 2>/dev/null || true
114 # Create new tmux session
115 tmux new-session -d -s "$session_name" -c "$directory" "$cmd"
116 # Open terminal attached to it
117 kitty -T "$session_name" tmux attach -t "$session_name" &
118 i=$((i + 1))
119 done <<< "$on_create"
120 fi
121
122 # Reopen windows from saved layout
123 local layout_count
124 layout_count=$(jq '.layout // [] | length' "$config_file" 2>/dev/null)
125
126 if [ "$layout_count" -gt 0 ]; then
127 local app_ids
128 app_ids=$(jq -r '.layout // [] | .[].app_id // empty' "$config_file" 2>/dev/null)
129
130 while IFS= read -r app_id; do
131 [ -n "$app_id" ] && launch_app "$app_id" "$directory"
132 done <<< "$app_ids"
133 fi
134
135 notify-send "Workspace Manager" "Restored workspace '${ws_name}'"
136}
137
138# Delete a workspace configuration
139action_delete() {
140 local configs
141 configs=$(find "$WORKSPACES_DIR" -name '*.json' -printf '%f\n' | sed 's/\.json$//' | sort)
142
143 if [ -z "$configs" ]; then
144 notify-send "Workspace Manager" "No workspace configs found"
145 return 0
146 fi
147
148 local selected
149 selected=$(echo "$configs" | rofi -dmenu -p "Delete workspace config")
150 if [ -z "$selected" ]; then
151 return 0
152 fi
153
154 local confirm
155 confirm=$(printf "Yes\nNo" | rofi -dmenu -p "Delete config for '${selected}'?")
156 if [ "$confirm" = "Yes" ]; then
157 rm "${WORKSPACES_DIR}/${selected}.json"
158 notify-send "Workspace Manager" "Deleted config for '${selected}'"
159 fi
160}
161
162# Edit a workspace configuration
163action_edit() {
164 local ws_name
165 ws_name=$(get_current_workspace)
166
167 # List existing configs + option for current workspace
168 local configs
169 configs=$(find "$WORKSPACES_DIR" -name '*.json' -printf '%f\n' | sed 's/\.json$//' | sort)
170
171 # Prepend current workspace if not already in list
172 if ! echo "$configs" | grep -qx "$ws_name"; then
173 configs="${ws_name}"$'\n'"${configs}"
174 fi
175
176 local selected
177 selected=$(echo "$configs" | rofi -dmenu -p "Edit workspace config")
178 if [ -z "$selected" ]; then
179 return 0
180 fi
181
182 local config_file="${WORKSPACES_DIR}/${selected}.json"
183
184 # Create default config if it doesn't exist
185 if [ ! -f "$config_file" ]; then
186 echo '{}' > "$config_file"
187 fi
188
189 # Open in terminal with $EDITOR
190 kitty "${EDITOR:-vim}" "$config_file" &
191}
192
193# Close workspace: kill all windows and remove the workspace
194action_close() {
195 local ws_name
196 ws_name=$(get_current_workspace)
197
198 local confirm
199 confirm=$(printf "Yes\nNo" | rofi -dmenu -p "Close workspace '${ws_name}'? (kills all windows)")
200 if [ "$confirm" != "Yes" ]; then
201 return 0
202 fi
203
204 # Kill all windows on current workspace
205 local pids
206 pids=$(swaymsg -r -t get_tree | jq --arg ws "$ws_name" '
207 [.nodes[].nodes[] |
208 select(.name == $ws) |
209 .. |
210 select(.type? == "con" and .pid? != null) |
211 .pid] | unique | .[]
212 ')
213
214 for pid in $pids; do
215 kill "$pid" 2>/dev/null || true
216 done
217
218 # Kill associated tmux sessions
219 local i=1
220 while tmux has-session -t "${ws_name}-start${i}" 2>/dev/null; do
221 tmux kill-session -t "${ws_name}-start${i}" 2>/dev/null || true
222 i=$((i + 1))
223 done
224
225 notify-send "Workspace Manager" "Closed workspace '${ws_name}'"
226}
227
228# Main menu
229main() {
230 local ws_name
231 ws_name=$(get_current_workspace)
232
233 local choice
234 choice=$(printf "Save layout\nRestore workspace\nClose workspace\nEdit config\nDelete config" | \
235 rofi -dmenu -p "Workspace: ${ws_name}")
236
237 case "$choice" in
238 "Save layout")
239 action_save
240 ;;
241 "Restore workspace")
242 action_restore
243 ;;
244 "Close workspace")
245 action_close
246 ;;
247 "Edit config")
248 action_edit
249 ;;
250 "Delete config")
251 action_delete
252 ;;
253 esac
254}
255
256main