nix/config/sketchybar/items/aerospace.lua

238 lines
7.2 KiB
Lua

local Promise = require 'promise'
local colors = require 'colors'
local utils = require 'utils'
local settings = require 'settings'
local app_icons = require 'app_icons'
local aerospace_cmd = '/opt/homebrew/bin/aerospace'
local function getAllWorkspaces()
return utils.sbarExecP(
aerospace_cmd .. " list-workspaces --all --format '%{workspace}%{monitor-appkit-nsscreen-screens-id}%{monitor-id}%{monitor-name}' --json"
)
end
local function getVisibleWorkspaces()
return utils.sbarExecP(
aerospace_cmd .. " list-workspaces --visible --monitor all --format '%{workspace}%{monitor-appkit-nsscreen-screens-id}%{monitor-id}%{monitor-name}' --json"
)
end
local function getAllWindows()
return utils.sbarExecP(
aerospace_cmd
.. " list-windows --all --format '%{app-name}%{window-title}%{workspace}%{monitor-id}%{monitor-appkit-nsscreen-screens-id}%{monitor-name}' --json"
)
end
local function getMonitorId(obj)
if obj['monitor-name'] then
if obj['monitor-name'] == 'ZOWIE XL LCD' then
return 2
elseif obj['monitor-name'] == 'LG ULTRAWIDE' then
return 1
end
end
if obj['monitor-appkit-nsscreen-screens-id'] then return tonumber(obj['monitor-appkit-nsscreen-screens-id']) or 1 end
return tonumber(obj['monitor-id']) or 1
end
local spaces = {}
local space_paddings = {}
local state = {
workspaces = {},
updating = false,
}
local function getState()
local newstate = { workspaces = {} }
-- Pre-initialize all workspaces from the spaces table
for workspaceid, _ in pairs(spaces) do
newstate.workspaces[workspaceid] = {
id = workspaceid,
monitor = 1,
active = false,
empty = true,
apps = {},
appicons = '',
}
end
return Promise.all({ getAllWorkspaces(), getVisibleWorkspaces(), getAllWindows() }):thenCall(function(values)
local all, visible, apps = values[1], values[2], values[3]
for _, workspace in ipairs(all) do
local workspaceid = workspace['workspace']
if newstate.workspaces[workspaceid] then newstate.workspaces[workspaceid]['monitor'] = getMonitorId(workspace) end
end
for _, workspace in ipairs(visible) do
local workspaceid = workspace['workspace']
if newstate.workspaces[workspaceid] then newstate.workspaces[workspaceid]['active'] = true end
end
for _, window in ipairs(apps) do
local workspaceid = window['workspace']
local appname = window['app-name']
if newstate.workspaces[workspaceid] then
newstate.workspaces[workspaceid]['apps'][appname] = true
newstate.workspaces[workspaceid]['empty'] = false
end
end
for workspaceid, workspacestate in pairs(newstate.workspaces) do
local appkeys = {}
for app in pairs(workspacestate['apps']) do
table.insert(appkeys, app)
end
table.sort(appkeys)
if #appkeys > 0 then
for _, app in ipairs(appkeys) do
local lookup = app_icons[app]
local icon = ((lookup == nil) and app_icons['Default'] or lookup)
workspacestate['appicons'] = workspacestate['appicons'] .. ' ' .. icon
end
else
workspacestate['appicons'] = ''
end
end
return newstate
end)
end
local function updateState()
if not state.updating then
state.updating = true
return getState()
:thenCall(function(newstate)
state.workspaces = newstate.workspaces
state.updating = false
end)
:catch(function(err)
state.updating = false
print('Error updating state: ' .. tostring(err))
end)
end
return Promise.resolve()
end
local function syncState()
sbar.animate('tanh', 10, function()
for workspaceid, workspacestate in pairs(state.workspaces) do
if not workspacestate['empty'] or workspacestate['active'] then
spaces[workspaceid]:set {
drawing = true,
display = workspacestate['monitor'],
label = {
string = workspacestate['appicons'],
highlight = workspacestate['active'],
},
icon = {
highlight = workspacestate['active'],
},
}
space_paddings[workspaceid]:set { drawing = true }
else
spaces[workspaceid]:set {
drawing = false,
display = workspacestate['monitor'],
label = workspacestate['appicons'],
}
space_paddings[workspaceid]:set { drawing = false }
end
end
end)
end
local function updateStateAndSync() return updateState():thenCall(syncState) end
local function onActiveSpaceChange(env)
local focused_workspace = env.FOCUSED_WORKSPACE
local prev_workspace = env.PREV_WORKSPACE
-- Immediately show the focused workspace with animation
if spaces[focused_workspace] then
sbar.animate('tanh', 10, function()
spaces[focused_workspace]:set {
drawing = true,
icon = { highlight = true },
label = { highlight = true },
}
space_paddings[focused_workspace]:set { drawing = true }
if spaces[prev_workspace] then
spaces[prev_workspace]:set {
icon = { highlight = false },
label = { highlight = false },
}
-- Hide previous workspace if it's empty
if state.workspaces[prev_workspace] and state.workspaces[prev_workspace]['empty'] then
spaces[prev_workspace]:set { drawing = false }
space_paddings[prev_workspace]:set { drawing = false }
end
end
end)
end
updateStateAndSync()
end
local function setup()
getAllWorkspaces()
:thenCall(function(workspaces)
for _, workspace in ipairs(workspaces) do
local workspaceid = workspace['workspace']
local display = getMonitorId(workspace)
local space = sbar.add('item', 'space.' .. workspaceid, {
drawing = false,
updates = 'when_shown',
display = display,
icon = {
string = workspaceid,
color = colors.fg,
highlight_color = colors.red,
},
label = {
padding_right = 12,
color = colors.fg,
highlight_color = colors.blue,
font = 'sketchybar-app-font:Regular:14.0',
y_offset = -1,
},
padding_left = 1,
padding_right = 1,
click_script = aerospace_cmd .. ' workspace ' .. workspaceid,
})
spaces[workspaceid] = space
local padding = sbar.add('space', 'space.padding.' .. space.name, {
drawing = false,
updates = 'when_shown',
display = display,
script = '',
width = settings.space_paddings,
})
space_paddings[workspaceid] = padding
end
end)
:thenCall(function()
sbar.add('event', 'aerospace_workspace_change')
local space_window_observer = sbar.add('item', {
drawing = false,
updates = true,
})
space_window_observer:subscribe('aerospace_workspace_change', onActiveSpaceChange)
space_window_observer:subscribe('space_windows_change', updateStateAndSync)
space_window_observer:subscribe('system_woke', updateStateAndSync)
space_window_observer:subscribe('front_app_switched', updateStateAndSync)
end)
:thenCall(updateStateAndSync)
end
setup()