stackline
stackline copied to clipboard
Adjust space padding when no stacked windows present?
I'm wondering if there is any advice on how to configure .yabairc or .skhdrc i.e. with signals or keybindings to detect when a space has a window stack and open up the padding to accommodate stack icons and then go back to tighter padding when there are no stacks?
@jzelner I've definitely had this same thought!
I'm still working on refactoring for testability. I don't quite see the light at the end of the tunnel yet, but hope to be able to wrap this up shortly after the new year.
In parallel, I've been day-dreaming about rewriting stackline using a state-machine-esque component model:
- Add a base
componentclass that- has a
registerHandlermethod that accepts anonChangefunction to be called with the old/new state whenever the component's state changes - uses lua's metamethod magic to automatically call all registered
onChangehandlers whenever the component's state changes (inspired by https://github.com/Phrogz/notifitable, but I was able to implement the desired behavior in a much simpler way)
- has a
- Rewrite
stackmanagerstack,window, andindicatorclasses as subclasses ofcomponent - Simplify the scarily complicated logic here using rules inspired by state machines. (Of all the ideas so far, this has the highest risk of failing to meet expectations. I'm kind of assuming / hoping that reliable/omnipresent
onChangehandlers will make it possible to simplify that mess) - Expose a public API for end-users to easily register
onSomethingChangehandlers that perform an action when a)stacklinestate changes or b) key window management events (such as switching the active space) occur.
Let's tie that ramble together with your idea:
I think that the changes above would make it trivial to implement your suggestion:
- On space change:
- if stack length is zero, execute
yabaicommand to set left & right padding to X - otherwise, execute
yabaicommand to set left & right padding tostackling.config.appearance.size + X
- if stack length is zero, execute
Since waiting is no fun, below is a snippet that you can place in your ~/.hammerspoon/init.lua file to get an idea how this might work.
The result is pretty janky, but it should give you an idea.
If you're constantly switching spaces (like I am), the window jitter this causes isn't worth the extra real estate on spaces that don't have any stacked windows. You might be able to minimize the jank by independently maintaining state (e.g., {spaceHasStacks = { 1 = false, 2 = true, 3 = false}) that's only updated when necessary. I'm not even sure that would help much: I don't think yabai supports per-space padding, so you'll still need to call out to yabai when changing to a space with hasStack value that differs from the previous space.
local function yabai(cmd)
local output
-- NOTE: requires string:split() defined at /stackline/lib/utils.lua:16
local args = cmd:split(' ')
hs.task.new(
stackline.config:get('paths.yabai'),
function(_, stdOut) output = stdOut end,
args
):start():waitUntilExit()
return output
end
local function sidePadding(val)
yabai(string.format('-m config left_padding %s', val))
yabai(string.format('-m config right_padding %s', val))
end
-- See https://www.hammerspoon.org/docs/hs.spaces.watcher.html
hs.spaces.watcher.new(function()
-- Set space padding for each case
-- 'big' is dynamically determined based on current size of stack indicators
-- NOTE: Assumes `showIcons = true`. Should probably factor out function
-- that dynamically determines indicator width:/stackline/stackline/window.lua:45
local small = 10
local big = stackline.config:get('appearance.size') + small
if stackline.manager then -- ensure stackline is initiated
-- Hack: we need to wait ~0.5s before updating padding to ensure that
-- stackline state has finished updating. Otherwise, we'll set the new
-- space's padding based on whether the *previous* space had stacks ☹️
-- The appropriate delay will depend on your personal system's speed.
hs.timer.doAfter(0.4, function()
-- Get number of stacks on current space
-- Remember that running this on space change without a delay gives us outdated data
local stackCount = #stackline.manager:get()
-- DEBUG: uncomment to show notification w/ # of stacks on space change
-- local msg = string.format('This space has %s stacks', stackCount)
-- hs.notify.show('stackline', msg, '')
-- Set the appropriate padding
sidePadding(stackCount > 0 and big or small)
end)
end
end):start()
There might be a better way. Would love to hear any ideas you might have, @jzelner
FYI Yabai does support per-space padding so you can adjust the sidePadding function of your script to:
local function sidePadding(val)
local space = hs.json.decode(yabai('-m query --spaces --space'))
local space_index = space['index']
yabai(string.format('-m config --space %s left_padding %s', space_index, val))
yabai(string.format('-m config --space %s right_padding %s', space_index, val))
end
This gets pretty good results, only downside I see is the required half a second delay.
I am using following scripts to manage spaces between windows:
- when stacked, add left padding 40px
- When not stacked, use 8px padding
yabairc
yabai -m signal --add event=window_created action="~/.config/yabai/refresh.sh"
yabai -m signal --add event=window_destroyed action="~/.config/yabai/refresh.sh"
refresh.sh
#!/usr/bin/env bash
number_of_windows=$(yabai -m query --windows --space | ~/.nix-profile/bin/jq 'length')
number_of_stacked=$(yabai -m query --windows --space | ~/.nix-profile/bin/jq -c 'map(select(."stack-index" != 0)) | length')
padding=8
lpadding=40
[[ "$number_of_windows" -eq 1 ]] && padding=0
[[ "$number_of_stacked" = 0 ]] && lpadding=$padding
yabai -m config --space mouse top_padding $padding
yabai -m config --space mouse bottom_padding $padding
yabai -m config --space mouse left_padding $lpadding
yabai -m config --space mouse right_padding $padding
yabai -m config --space mouse window_gap $padding
this works also for the current focused window – independent of where the mouse is at the point:
#!/usr/bin/env sh
number_of_windows=$(yabai -m query --windows --space | /usr/local/bin/jq 'length')
number_of_stacked=$(yabai -m query --windows --space | /usr/local/bin/jq -c 'map(select(."stack-index" != 0)) | length')
currspace=$(yabai -m query --spaces --space | /usr/local/bin/jq '.index')
padding=8
spadding=40
[[ "$number_of_windows" -eq 1 ]] && padding=0
[[ "$number_of_stacked" = 0 ]] && spadding=$padding
yabai -m config --space "$currspace" top_padding $padding
yabai -m config --space "$currspace" bottom_padding $padding
yabai -m config --space "$currspace" left_padding $spadding
yabai -m config --space "$currspace" right_padding $spadding
yabai -m config --space "$currspace" window_gap $padding
@jzelner I've definitely had this same thought!
I'm still working on refactoring for testability. I don't quite see the light at the end of the tunnel yet, but hope to be able to wrap this up shortly after the new year.
In parallel, I've been day-dreaming about rewriting
stacklineusing a state-machine-esque component model:
Add a base
componentclass that
- has a
registerHandlermethod that accepts anonChangefunction to be called with the old/new state whenever the component's state changes- uses lua's metamethod magic to automatically call all registered
onChangehandlers whenever the component's state changes (inspired by https://github.com/Phrogz/notifitable, but I was able to implement the desired behavior in a much simpler way)Rewrite
stackmanagerstack,window, andindicatorclasses as subclasses ofcomponentSimplify the scarily complicated logic here using rules inspired by state machines. (Of all the ideas so far, this has the highest risk of failing to meet expectations. I'm kind of assuming / hoping that reliable/omnipresent
onChangehandlers will make it possible to simplify that mess)Expose a public API for end-users to easily register
onSomethingChangehandlers that perform an action when a)stacklinestate changes or b) key window management events (such as switching the active space) occur.Let's tie that ramble together with your idea:
I think that the changes above would make it trivial to implement your suggestion:
On space change:
- if stack length is zero, execute
yabaicommand to set left & right padding to X- otherwise, execute
yabaicommand to set left & right padding tostackling.config.appearance.size + XSince waiting is no fun, below is a snippet that you can place in your
~/.hammerspoon/init.luafile to get an idea how this might work.The result is pretty janky, but it should give you an idea.
If you're constantly switching spaces (like I am), the window jitter this causes isn't worth the extra real estate on spaces that don't have any stacked windows. You might be able to minimize the jank by independently maintaining state (e.g.,
{spaceHasStacks = { 1 = false, 2 = true, 3 = false}) that's only updated when necessary. I'm not even sure that would help much: I don't thinkyabaisupports per-space padding, so you'll still need to call out toyabaiwhen changing to a space withhasStackvalue that differs from the previous space.local function yabai(cmd) local output -- NOTE: requires string:split() defined at /stackline/lib/utils.lua:16 local args = cmd:split(' ') hs.task.new( stackline.config:get('paths.yabai'), function(_, stdOut) output = stdOut end, args ):start():waitUntilExit() return output end local function sidePadding(val) yabai(string.format('-m config left_padding %s', val)) yabai(string.format('-m config right_padding %s', val)) end -- See https://www.hammerspoon.org/docs/hs.spaces.watcher.html hs.spaces.watcher.new(function() -- Set space padding for each case -- 'big' is dynamically determined based on current size of stack indicators -- NOTE: Assumes `showIcons = true`. Should probably factor out function -- that dynamically determines indicator width:/stackline/stackline/window.lua:45 local small = 10 local big = stackline.config:get('appearance.size') + small if stackline.manager then -- ensure stackline is initiated -- Hack: we need to wait ~0.5s before updating padding to ensure that -- stackline state has finished updating. Otherwise, we'll set the new -- space's padding based on whether the *previous* space had stacks ☹️ -- The appropriate delay will depend on your personal system's speed. hs.timer.doAfter(0.4, function() -- Get number of stacks on current space -- Remember that running this on space change without a delay gives us outdated data local stackCount = #stackline.manager:get() -- DEBUG: uncomment to show notification w/ # of stacks on space change -- local msg = string.format('This space has %s stacks', stackCount) -- hs.notify.show('stackline', msg, '') -- Set the appropriate padding sidePadding(stackCount > 0 and big or small) end) end end):start()There might be a better way. Would love to hear any ideas you might have, @jzelner
Hey, I was wondering if there was a way to just add a top padding in the stack column, as most of the time it's just the window control buttons being blocked. I don't really want a big left padding as my screen is not very big.
Thanks for this cool project btw!!