This repository was archived by the owner on Aug 23, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathspaces.lua
More file actions
239 lines (207 loc) · 6.42 KB
/
Copy pathspaces.lua
File metadata and controls
239 lines (207 loc) · 6.42 KB
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
local colors = require("colors")
local icon_map = require("helpers.icon_map")
-- Helper function to execute shell commands and get output
local function exec(command)
local handle = io.popen(command)
local result = handle:read("*a")
handle:close()
return result:gsub("%s+$", "")
end
-- Helper function to get lines from command output
local function exec_lines(command)
local lines = {}
for line in exec(command):gmatch("[^\r\n]+") do
table.insert(lines, line)
end
return lines
end
-- Module-level state storage (persists between update calls)
local full_workspace_template = {}
local workspace_states = {}
-- Add aerospace events
sbar.add("event", "aerospace_workspace_change")
sbar.add("event", "aerospace_monitor_change")
-- Create space items for all aerospace workspaces
local all_workspaces = exec_lines("aerospace list-workspaces --all")
for _, sid in ipairs(all_workspaces) do
-- Determine which monitor this workspace is on
local monitor_id = exec(
string.format(
"aerospace list-windows --workspace %s --format '%%{monitor-appkit-nsscreen-screens-id}' | cut -c1",
sid
)
)
if monitor_id == "" then
monitor_id = "1"
end
-- Create space item
sbar.add("item", "space." .. sid, {
position = "left",
display = monitor_id,
drawing = "off",
background = {
corner_radius = 5,
drawing = "on",
border_width = 1,
border_color = colors.TEXT_WHITE,
height = 23,
padding_right = 5,
padding_left = 5,
},
icon = {
string = sid,
shadow = { drawing = "off" },
padding_left = 10,
},
label = {
font = "sketchybar-app-font:Regular:16.0",
padding_right = 20,
padding_left = 0,
y_offset = -1,
shadow = { drawing = "off" },
},
click_script = "aerospace workspace " .. sid,
})
workspace_states[sid] = {
drawing = "off",
label_string = "",
icon_color = colors.TEXT_GREY,
}
end
-- Helper function to check if two states are equal
local function states_equal(old, new)
if (old == nil) ~= (new == nil) then
return false
end
if old == nil then
return true
end
return old.drawing == new.drawing and old.label_string == new.label_string and old.icon_color == new.icon_color
end
-- Build partial current state from aerospace (only non-empty workspaces)
local function build_current_state()
local state = {}
local focused_workspace = exec("aerospace list-workspaces --focused")
-- Only build state for non-empty workspaces
local monitors = exec_lines("aerospace list-monitors")
for i, _ in ipairs(monitors) do
local non_empty_workspaces = exec_lines("aerospace list-workspaces --monitor " .. i .. " --empty no")
for _, sid in ipairs(non_empty_workspaces) do
local apps_raw = exec(string.format("aerospace list-windows --workspace %s", sid))
local icon_strip = " "
for line in apps_raw:gmatch("[^\r\n]+") do
local app_name = line:match("|%s*(.-)%s*|")
if app_name then
icon_strip = icon_strip .. " " .. icon_map(app_name)
end
end
state[sid] = {
drawing = "on",
label_string = icon_strip,
icon_color = (sid == focused_workspace) and colors.TEXT_WHITE or colors.TEXT_GREY,
}
end
end
-- Ensure focused workspace is always visible, even if empty
if state[focused_workspace] == nil then
state[focused_workspace] = {
drawing = "on",
label_string = "",
icon_color = colors.TEXT_WHITE,
}
end
return state
end
-- Function to update all workspace states (with diff-based optimization)
local function update_all_workspaces()
sbar.begin_config()
-- Build partial state from aerospace (only non-empty workspaces)
local new_state = build_current_state()
-- Move empty workspaces to monitor 1 (side effect)
local empty_workspaces = exec_lines("aerospace list-workspaces --all --empty yes")
for _, sid in ipairs(empty_workspaces) do
-- WARN: this assumes monitor 1 is your main monitor
os.execute(string.format("aerospace move-workspace-to-monitor --workspace %s 1", sid))
end
-- Loop through all workspaces to handle both non-empty and empty cases
for _, sid in ipairs(all_workspaces) do
local old_ws_state = workspace_states[sid]
local new_ws_state = new_state[sid]
if new_ws_state == nil then
-- Workspace became or stayed empty
if old_ws_state.drawing == "on" then
-- Was visible, now hide it
sbar.set("space." .. sid, {
drawing = "off",
label = { string = "", color = colors.TEXT_GREY },
icon = { color = colors.TEXT_GREY },
background = {
color = colors.TRANSPARENT,
border_color = colors.TEXT_WHITE,
},
})
-- Reset to empty state
workspace_states[sid].drawing = "off"
workspace_states[sid].label_string = ""
workspace_states[sid].icon_color = colors.TEXT_GREY
end
else
-- Workspace is non-empty, check if state changed
if not states_equal(old_ws_state, new_ws_state) then
local is_focused = (new_ws_state.icon_color == colors.TEXT_WHITE)
sbar.set("space." .. sid, {
drawing = new_ws_state.drawing,
label = {
string = new_ws_state.label_string,
color = is_focused and colors.TEXT_WHITE or colors.TEXT_GREY,
},
icon = { color = new_ws_state.icon_color },
background = {
color = is_focused and colors.HIGHLIGHT_BACKGROUND or colors.TRANSPARENT,
border_color = is_focused and colors.TEXT_GREY or colors.TEXT_WHITE,
},
})
workspace_states[sid] = new_ws_state
end
end
end
sbar.end_config()
end
-- Load icons on startup for non-empty workspaces
update_all_workspaces()
-- Create space separator that handles events
local poop = sbar.add("item", "poop", {
position = "q",
icon = { string = "💩", padding_left = 20, padding_right = 0, margin_right = 0 },
label = { drawing = "off", padding_left = 0, margin_left = 0 },
background = { drawing = "off" },
})
-- Subscribe to aerospace events
poop:subscribe("aerospace_workspace_change", function(env)
update_all_workspaces()
end)
poop:subscribe("space_windows_change", function(env)
update_all_workspaces()
end)
poop:subscribe("front_app_switched", function(env)
update_all_workspaces()
end)
poop:subscribe("aerospace_monitor_change", function(env)
if env.FOCUSED_WORKSPACE and env.TARGET_MONITOR then
sbar.set("space." .. env.FOCUSED_WORKSPACE, {
display = env.TARGET_MONITOR,
})
end
end)
-- Create spaces bracket
local space_names = {}
for _, sid in ipairs(all_workspaces) do
table.insert(space_names, "space." .. sid)
end
sbar.add("bracket", "spaces", space_names, {
background = {
color = colors.BACKGROUND,
corner_radius = 10,
height = 30,
},
})