remove promise
This commit is contained in:
parent
572d3bfa80
commit
cc55237709
4 changed files with 222 additions and 159 deletions
7
config/sketchybar/.luarc.json
Normal file
7
config/sketchybar/.luarc.json
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
|
||||||
|
"runtime.version": "Lua 5.4",
|
||||||
|
"diagnostics.global": [
|
||||||
|
"sbar"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -1,25 +1,28 @@
|
||||||
local Promise = require 'promise'
|
|
||||||
local colors = require 'colors'
|
local colors = require 'colors'
|
||||||
local utils = require 'utils'
|
local utils = require 'utils'
|
||||||
local settings = require 'settings'
|
local settings = require 'settings'
|
||||||
local app_icons = require 'app_icons'
|
local app_icons = require 'app_icons'
|
||||||
|
|
||||||
|
local async = utils.async
|
||||||
|
local await = utils.await
|
||||||
|
local awaitAll = utils.awaitAll
|
||||||
|
local exec = utils.exec
|
||||||
|
|
||||||
local aerospace_cmd = '/opt/homebrew/bin/aerospace'
|
local aerospace_cmd = '/opt/homebrew/bin/aerospace'
|
||||||
|
|
||||||
|
-- Aerospace commands
|
||||||
local function getAllWorkspaces()
|
local function getAllWorkspaces()
|
||||||
return utils.sbarExecP(
|
return exec(aerospace_cmd .. " list-workspaces --all --format '%{workspace}%{monitor-appkit-nsscreen-screens-id}%{monitor-id}%{monitor-name}' --json")
|
||||||
aerospace_cmd .. " list-workspaces --all --format '%{workspace}%{monitor-appkit-nsscreen-screens-id}%{monitor-id}%{monitor-name}' --json"
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function getVisibleWorkspaces()
|
local function getVisibleWorkspaces()
|
||||||
return utils.sbarExecP(
|
return exec(
|
||||||
aerospace_cmd .. " list-workspaces --visible --monitor all --format '%{workspace}%{monitor-appkit-nsscreen-screens-id}%{monitor-id}%{monitor-name}' --json"
|
aerospace_cmd .. " list-workspaces --visible --monitor all --format '%{workspace}%{monitor-appkit-nsscreen-screens-id}%{monitor-id}%{monitor-name}' --json"
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function getAllWindows()
|
local function getAllWindows()
|
||||||
return utils.sbarExecP(
|
return exec(
|
||||||
aerospace_cmd
|
aerospace_cmd
|
||||||
.. " list-windows --all --format '%{app-name}%{window-title}%{workspace}%{monitor-id}%{monitor-appkit-nsscreen-screens-id}%{monitor-name}' --json"
|
.. " list-windows --all --format '%{app-name}%{window-title}%{workspace}%{monitor-id}%{monitor-appkit-nsscreen-screens-id}%{monitor-name}' --json"
|
||||||
)
|
)
|
||||||
|
|
@ -37,6 +40,7 @@ local function getMonitorId(obj)
|
||||||
return tonumber(obj['monitor-id']) or 1
|
return tonumber(obj['monitor-id']) or 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- State
|
||||||
local spaces = {}
|
local spaces = {}
|
||||||
local space_paddings = {}
|
local space_paddings = {}
|
||||||
local state = {
|
local state = {
|
||||||
|
|
@ -44,10 +48,40 @@ local state = {
|
||||||
updating = false,
|
updating = false,
|
||||||
}
|
}
|
||||||
|
|
||||||
local function getState()
|
-- Build app icons string from app list
|
||||||
|
local function buildAppIcons(apps)
|
||||||
|
local appkeys = {}
|
||||||
|
for app in pairs(apps) do
|
||||||
|
table.insert(appkeys, app)
|
||||||
|
end
|
||||||
|
table.sort(appkeys)
|
||||||
|
|
||||||
|
if #appkeys == 0 then return '' end
|
||||||
|
|
||||||
|
local icons = ''
|
||||||
|
for _, app in ipairs(appkeys) do
|
||||||
|
local lookup = app_icons[app]
|
||||||
|
local icon = lookup or app_icons['Default']
|
||||||
|
icons = icons .. ' ' .. icon
|
||||||
|
end
|
||||||
|
return icons
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Fetch and build workspace state
|
||||||
|
local fetchState = async(function()
|
||||||
|
if state.updating then return end
|
||||||
|
state.updating = true
|
||||||
|
|
||||||
|
local ok, err = pcall(function()
|
||||||
|
local all, visible, windows = table.unpack(awaitAll {
|
||||||
|
getAllWorkspaces(),
|
||||||
|
getVisibleWorkspaces(),
|
||||||
|
getAllWindows(),
|
||||||
|
})
|
||||||
|
|
||||||
local newstate = { workspaces = {} }
|
local newstate = { workspaces = {} }
|
||||||
|
|
||||||
-- Pre-initialize all workspaces from the spaces table
|
-- Initialize all workspaces
|
||||||
for workspaceid, _ in pairs(spaces) do
|
for workspaceid, _ in pairs(spaces) do
|
||||||
newstate.workspaces[workspaceid] = {
|
newstate.workspaces[workspaceid] = {
|
||||||
id = workspaceid,
|
id = workspaceid,
|
||||||
|
|
@ -55,122 +89,94 @@ local function getState()
|
||||||
active = false,
|
active = false,
|
||||||
empty = true,
|
empty = true,
|
||||||
apps = {},
|
apps = {},
|
||||||
appicons = '',
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
return Promise.all({ getAllWorkspaces(), getVisibleWorkspaces(), getAllWindows() }):thenCall(function(values)
|
-- Set monitor IDs
|
||||||
local all, visible, apps = values[1], values[2], values[3]
|
for _, ws in ipairs(all) do
|
||||||
|
local id = ws['workspace']
|
||||||
for _, workspace in ipairs(all) do
|
if newstate.workspaces[id] then newstate.workspaces[id].monitor = getMonitorId(ws) end
|
||||||
local workspaceid = workspace['workspace']
|
|
||||||
if newstate.workspaces[workspaceid] then newstate.workspaces[workspaceid]['monitor'] = getMonitorId(workspace) end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, workspace in ipairs(visible) do
|
-- Mark visible workspaces as active
|
||||||
local workspaceid = workspace['workspace']
|
for _, ws in ipairs(visible) do
|
||||||
if newstate.workspaces[workspaceid] then newstate.workspaces[workspaceid]['active'] = true end
|
local id = ws['workspace']
|
||||||
|
if newstate.workspaces[id] then newstate.workspaces[id].active = true end
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, window in ipairs(apps) do
|
-- Collect apps per workspace
|
||||||
local workspaceid = window['workspace']
|
for _, win in ipairs(windows) do
|
||||||
local appname = window['app-name']
|
local id = win['workspace']
|
||||||
if newstate.workspaces[workspaceid] then
|
local app = win['app-name']
|
||||||
newstate.workspaces[workspaceid]['apps'][appname] = true
|
if newstate.workspaces[id] then
|
||||||
newstate.workspaces[workspaceid]['empty'] = false
|
newstate.workspaces[id].apps[app] = true
|
||||||
|
newstate.workspaces[id].empty = false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
for workspaceid, workspacestate in pairs(newstate.workspaces) do
|
-- Build app icons
|
||||||
local appkeys = {}
|
for _, ws in pairs(newstate.workspaces) do
|
||||||
for app in pairs(workspacestate['apps']) do
|
ws.appicons = buildAppIcons(ws.apps)
|
||||||
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
|
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.workspaces = newstate.workspaces
|
||||||
state.updating = false
|
|
||||||
end)
|
end)
|
||||||
:catch(function(err)
|
|
||||||
state.updating = false
|
|
||||||
print('Error updating state: ' .. tostring(err))
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
return Promise.resolve()
|
|
||||||
end
|
|
||||||
|
|
||||||
|
state.updating = false
|
||||||
|
if not ok then print('Error fetching state: ' .. tostring(err)) end
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Sync UI with state
|
||||||
local function syncState()
|
local function syncState()
|
||||||
sbar.animate('tanh', 10, function()
|
sbar.animate('tanh', 10, function()
|
||||||
for workspaceid, workspacestate in pairs(state.workspaces) do
|
for id, ws in pairs(state.workspaces) do
|
||||||
if not workspacestate['empty'] or workspacestate['active'] then
|
local shouldShow = not ws.empty or ws.active
|
||||||
spaces[workspaceid]:set {
|
|
||||||
drawing = true,
|
spaces[id]:set {
|
||||||
display = workspacestate['monitor'],
|
drawing = shouldShow,
|
||||||
|
display = ws.monitor,
|
||||||
label = {
|
label = {
|
||||||
string = workspacestate['appicons'],
|
string = ws.appicons,
|
||||||
highlight = workspacestate['active'],
|
highlight = ws.active,
|
||||||
},
|
},
|
||||||
icon = {
|
icon = {
|
||||||
highlight = workspacestate['active'],
|
highlight = ws.active,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
space_paddings[workspaceid]:set { drawing = true }
|
space_paddings[id]:set { drawing = shouldShow }
|
||||||
else
|
|
||||||
spaces[workspaceid]:set {
|
|
||||||
drawing = false,
|
|
||||||
display = workspacestate['monitor'],
|
|
||||||
label = workspacestate['appicons'],
|
|
||||||
}
|
|
||||||
space_paddings[workspaceid]:set { drawing = false }
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function updateStateAndSync() return updateState():thenCall(syncState) end
|
local function updateStateAndSync()
|
||||||
|
fetchState()
|
||||||
|
-- Use a small delay to let fetchState complete before syncing
|
||||||
|
sbar.exec('sleep 0.1', syncState)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Handle workspace change event
|
||||||
local function onActiveSpaceChange(env)
|
local function onActiveSpaceChange(env)
|
||||||
local focused_workspace = env.FOCUSED_WORKSPACE
|
local focused = env.FOCUSED_WORKSPACE
|
||||||
local prev_workspace = env.PREV_WORKSPACE
|
local prev = env.PREV_WORKSPACE
|
||||||
|
|
||||||
-- Immediately show the focused workspace with animation
|
-- Immediately update UI for responsiveness
|
||||||
if spaces[focused_workspace] then
|
if spaces[focused] then
|
||||||
sbar.animate('tanh', 10, function()
|
sbar.animate('tanh', 10, function()
|
||||||
spaces[focused_workspace]:set {
|
spaces[focused]:set {
|
||||||
drawing = true,
|
drawing = true,
|
||||||
icon = { highlight = true },
|
icon = { highlight = true },
|
||||||
label = { highlight = true },
|
label = { highlight = true },
|
||||||
}
|
}
|
||||||
space_paddings[focused_workspace]:set { drawing = true }
|
space_paddings[focused]:set { drawing = true }
|
||||||
|
|
||||||
if spaces[prev_workspace] then
|
if spaces[prev] then
|
||||||
spaces[prev_workspace]:set {
|
spaces[prev]:set {
|
||||||
icon = { highlight = false },
|
icon = { highlight = false },
|
||||||
label = { highlight = false },
|
label = { highlight = false },
|
||||||
}
|
}
|
||||||
-- Hide previous workspace if it's empty
|
if state.workspaces[prev] and state.workspaces[prev].empty then
|
||||||
if state.workspaces[prev_workspace] and state.workspaces[prev_workspace]['empty'] then
|
spaces[prev]:set { drawing = false }
|
||||||
spaces[prev_workspace]:set { drawing = false }
|
space_paddings[prev]:set { drawing = false }
|
||||||
space_paddings[prev_workspace]:set { drawing = false }
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
@ -179,19 +185,21 @@ local function onActiveSpaceChange(env)
|
||||||
updateStateAndSync()
|
updateStateAndSync()
|
||||||
end
|
end
|
||||||
|
|
||||||
local function setup()
|
-- Setup
|
||||||
getAllWorkspaces()
|
local setup = async(function()
|
||||||
:thenCall(function(workspaces)
|
local workspaces = await(getAllWorkspaces())
|
||||||
for _, workspace in ipairs(workspaces) do
|
|
||||||
local workspaceid = workspace['workspace']
|
|
||||||
local display = getMonitorId(workspace)
|
|
||||||
|
|
||||||
local space = sbar.add('item', 'space.' .. workspaceid, {
|
-- Create space items
|
||||||
|
for _, ws in ipairs(workspaces) do
|
||||||
|
local id = ws['workspace']
|
||||||
|
local display = getMonitorId(ws)
|
||||||
|
|
||||||
|
local space = sbar.add('item', 'space.' .. id, {
|
||||||
drawing = false,
|
drawing = false,
|
||||||
updates = 'when_shown',
|
updates = 'when_shown',
|
||||||
display = display,
|
display = display,
|
||||||
icon = {
|
icon = {
|
||||||
string = workspaceid,
|
string = id,
|
||||||
color = colors.fg,
|
color = colors.fg,
|
||||||
highlight_color = colors.red,
|
highlight_color = colors.red,
|
||||||
},
|
},
|
||||||
|
|
@ -204,10 +212,9 @@ local function setup()
|
||||||
},
|
},
|
||||||
padding_left = 1,
|
padding_left = 1,
|
||||||
padding_right = 1,
|
padding_right = 1,
|
||||||
click_script = aerospace_cmd .. ' workspace ' .. workspaceid,
|
click_script = aerospace_cmd .. ' workspace ' .. id,
|
||||||
})
|
})
|
||||||
|
spaces[id] = space
|
||||||
spaces[workspaceid] = space
|
|
||||||
|
|
||||||
local padding = sbar.add('space', 'space.padding.' .. space.name, {
|
local padding = sbar.add('space', 'space.padding.' .. space.name, {
|
||||||
drawing = false,
|
drawing = false,
|
||||||
|
|
@ -216,23 +223,24 @@ local function setup()
|
||||||
script = '',
|
script = '',
|
||||||
width = settings.space_paddings,
|
width = settings.space_paddings,
|
||||||
})
|
})
|
||||||
space_paddings[workspaceid] = padding
|
space_paddings[id] = padding
|
||||||
end
|
end
|
||||||
end)
|
|
||||||
:thenCall(function()
|
-- Register events
|
||||||
sbar.add('event', 'aerospace_workspace_change')
|
sbar.add('event', 'aerospace_workspace_change')
|
||||||
|
|
||||||
local space_window_observer = sbar.add('item', {
|
local observer = sbar.add('item', {
|
||||||
drawing = false,
|
drawing = false,
|
||||||
updates = true,
|
updates = true,
|
||||||
})
|
})
|
||||||
|
|
||||||
space_window_observer:subscribe('aerospace_workspace_change', onActiveSpaceChange)
|
observer:subscribe('aerospace_workspace_change', onActiveSpaceChange)
|
||||||
space_window_observer:subscribe('space_windows_change', updateStateAndSync)
|
observer:subscribe('space_windows_change', updateStateAndSync)
|
||||||
space_window_observer:subscribe('system_woke', updateStateAndSync)
|
observer:subscribe('system_woke', updateStateAndSync)
|
||||||
space_window_observer:subscribe('front_app_switched', updateStateAndSync)
|
observer:subscribe('front_app_switched', updateStateAndSync)
|
||||||
end)
|
|
||||||
:thenCall(updateStateAndSync)
|
-- Initial state sync
|
||||||
end
|
updateStateAndSync()
|
||||||
|
end)
|
||||||
|
|
||||||
setup()
|
setup()
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
local Promise = require 'promise'
|
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
function M.dump(o)
|
function M.dump(o)
|
||||||
|
|
@ -15,19 +13,68 @@ function M.dump(o)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function onErrorP(reason) print('Error found: ' .. (reason and M.dump(reason) or 'unknown')) end
|
-- Coroutine-based async/await helpers
|
||||||
|
function M.await(callback_fn)
|
||||||
|
local co = coroutine.running()
|
||||||
|
if not co then error 'await must be called inside async' end
|
||||||
|
|
||||||
-- https://github.com/Tnixc/nix-config/blob/main/home/programs/aerospace-sketchybar/sbar-config-libs/items/aerospaces.lua
|
local result, err
|
||||||
function M.sbarExecP(cmd)
|
local done = false
|
||||||
return Promise.new(function(resolve, failfunc)
|
|
||||||
|
callback_fn(function(res, e)
|
||||||
|
result = res
|
||||||
|
err = e
|
||||||
|
done = true
|
||||||
|
if coroutine.status(co) == 'suspended' then coroutine.resume(co) end
|
||||||
|
end)
|
||||||
|
|
||||||
|
if not done then coroutine.yield() end
|
||||||
|
if err then error(err) end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.awaitAll(callback_fns)
|
||||||
|
local co = coroutine.running()
|
||||||
|
if not co then error 'awaitAll must be called inside async' end
|
||||||
|
|
||||||
|
local results = {}
|
||||||
|
local pending = #callback_fns
|
||||||
|
local err = nil
|
||||||
|
|
||||||
|
for i, fn in ipairs(callback_fns) do
|
||||||
|
fn(function(res, e)
|
||||||
|
if e then err = e end
|
||||||
|
results[i] = res
|
||||||
|
pending = pending - 1
|
||||||
|
if pending == 0 and coroutine.status(co) == 'suspended' then coroutine.resume(co) end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
if pending > 0 then coroutine.yield() end
|
||||||
|
if err then error(err) end
|
||||||
|
return results
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.async(fn)
|
||||||
|
return function(...)
|
||||||
|
local args = { ... }
|
||||||
|
local co = coroutine.create(function() fn(table.unpack(args)) end)
|
||||||
|
local ok, err = coroutine.resume(co)
|
||||||
|
if not ok then print('Async error: ' .. tostring(err)) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Execute a shell command, returns a callback function for use with await
|
||||||
|
function M.exec(cmd)
|
||||||
|
return function(resolve)
|
||||||
sbar.exec(cmd, function(result, exit_code)
|
sbar.exec(cmd, function(result, exit_code)
|
||||||
if exit_code ~= 0 then
|
if exit_code ~= 0 then
|
||||||
if failfunc ~= nil then failfunc(string.format('Exit Code: %s Message: %s', tostring(exit_code), M.dump(result))) end
|
resolve(nil, string.format('Exit Code: %s Message: %s', tostring(exit_code), M.dump(result)))
|
||||||
else
|
else
|
||||||
if resolve ~= nil then resolve(result) end
|
resolve(result, nil)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end):catch(onErrorP)
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ in
|
||||||
|
|
||||||
hm.home.packages = with pkgs; [
|
hm.home.packages = with pkgs; [
|
||||||
sketchybar-app-font
|
sketchybar-app-font
|
||||||
|
custom.sk-utils
|
||||||
];
|
];
|
||||||
|
|
||||||
hm.xdg.configFile = {
|
hm.xdg.configFile = {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue