Skip to content

Commit a6c37e7

Browse files
authored
Feature query windows via hammerspoon (#18)
* format: auto-formatted per luaformat config. Functions folded with 'set fdm=marker' in vim * WIP: Working toward addressing #8 in stackline/query.lua, which replaces bin/yabai-get-stacks. The goal of this file is to eliminate the need to 'shell out' to yabai to query window data needed to render stackline, which would address #8. The main problem with relying on yabai is that a 0.03s sleep is required in the yabai script to ensure that the changes that triggered hammerspoon's window event subscriber are, in fact, represented in the query response from yabai. There are probably secondary downsides, such as overall performance, and specifically *yabai* performance (I've noticed that changing focus is slower when lots of yabai queries are happening simultaneously). ┌────────┐ │ Status │ └────────┘ We're not yet using any of the code in this file to actually render the indiators or query ata — all of that is still achieved via the "old" methods. However, this file IS being required by ./core.lua and runs one every window focus event, and the resulting "stack" data is printed to the hammerspoon console. The stack data structure differs from that used in ./stack.lua enough that it won't work as a drop-in replacement. I think that's fine (and it wouldn't be worth attempting to make this a non-breaking change, esp. since zero people rely on it as of 2020-08-02. ┌──────┐ │ Next │ └──────┘ - [ ] Integrate appropriate functionality in this file into the Stack module - [ ] Update key Stack module functions to have basic compatiblity with the new data structure - [ ] Simplify / refine Stack functions to leverage the benefits of having access to the hs.window module for each tracked window - [ ] Integrate appropriate functionality in this file into the Core module - [ ] … see if there's anything left and decide where it should live ┌───────────┐ │ WIP NOTES │ └───────────┘ Much of the functionality in this file should either be integrated into stack.lua or core.lua — I don't think a new file is needed. Rather than calling out to the script ../bin/yabai-get-stacks, we're using hammerspoon's mature (if complicated) hs.window.filter and hs.window modules to achieve the same goal natively within hammerspon. There might be other benefits in addition to fixing the problems that inspired tracked by stackline, which will probably make it easier to implement enhancements that we haven't even considered yet. This approach should also be easier to maintain, *and* we get to drop the jq dependency! * WIP: basic working state with lots of cruft to clean up and edge cases to fix * Bring over some changes from #13 * Bring over indicator styling improvements from #13 * Move self.colorOpts into Window:drawIndicator() so that the latter can be called in place of Window:process() to redraw a window. * A small bit of much-needed cleanup * Basic functionality working well. Parameterized display options. Indicators display on left/right edge of window based on which side of the screen the window resides. Known bug: changes broke multiple stacks on the same space. * Fixed issue where only 1 of N stacks responded to focus events. Moved all stack-is-occluded functionality from Query to StackMgr and Stack modules. * Workaround hammerspoon bug (Hammerspoon/hammerspoon#2400) to ensure indicators update when switching between windows of the same app. * Fold Window:getScreenSide() * Modifying existing indicators on focus change is MUCH faster than destroying them & rendering from scratch. * Cleanup (one of the) utils.lua files * Add a status update to readme describing recent changes * Included a bunch of cleanup/reorg changes missed in last commit * Fix path to dash shell (again) * Add hs._asm.undocumented.spaces dependency * Remove unused StackConfig:handleSignal() method (it needs to be available outside of class) * Cleanup comments & delete unused bluclass.lua * Don't use hs._asm.undocumented.spaces. Uncomment window.filter.notInCurrentSpace which depends on it.' * Fill gap left behind by hs._asm.undocumented.spaces with hs.spaces.watcher * Increase threshold in window:getSide() to fix issue w/ large yabai padding values * Cleanup comments in query.lua * Removed hs._asm.undocumented.spaces from list of dependencies * Delete unused utils, consolidate utils into single file. * Track stack focus state, indicate last-focused window in unfocused stack, and centralize indicator config settings & retrieval. * Fix misleading comment * Remove garbage characters in comment * Fix error stackline/window.lua:95: attempt to perform arithmetic on a nil value (field 'stackIdx') by checking for stackIdx > 0 * Remove completed TODO comments, try to keep TODO comments on single line to work well with leasot / rg * Enable keybindings in init.lua by returning stackConfig & stackManager instances from main stackline.lua Remove keybinding from config.lua & move to readme. * Fix #21 - Limit stack left/right edge to screen boundary so it doesn't go off screen * Update version to 0.1.50
1 parent 5b6e6cb commit a6c37e7

18 files changed

+1746
-1295
lines changed

README.md

Lines changed: 47 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,38 @@
11
![stackline-logo](assets/[email protected])
22
<p>
3-
<img alt="Version" src="https://img.shields.io/badge/version-0.1.01-blue.svg?cacheSeconds=2592000" />
3+
<img alt="Version" src="https://img.shields.io/badge/version-0.1.50-blue.svg?cacheSeconds=2592000" />
44
<a href="#" target="_blank">
55
<img alt="License: MIT" src="https://img.shields.io/badge/License-MIT-yellow.svg" />
66
</a>
77
</p>
88

99
> Visualize yabai window stacks on macOS. Works with yabai & hammerspoon.
1010
11-
## ⚠️ WARNING: THIS IS A PROOF-OF-CONCEPT
11+
## ⚠️ ~~WARNING: THIS IS A PROOF-OF-CONCEPT~~ (it's more like an 'alpha' now!)
1212

13-
Currently, [stackline](https://github.com/AdamWagner/stackline) is a proof-of-concept for visualizing the total number and status of stacked windows. Feel free to try it out and open [issues](https://github.com/AdamWagner/stackline/issues) / PRs, but using stackline full-time is not recommended (yet).
13+
My humble thanks to all who have been suffering through error-ridden setup instructions, spinning fans, flickering UI, and crashes. I'm happy to say that I _think_ this branch fixes enough of these issues that it _should_ be reasonable for actual use ;-)
1414

15-
There is much crucial fuctionality that is either missing or broken. For example, stack indicators do not refresh when:
15+
As before, if you notice something that's off, or could be better, please open an issue (or a PR!).
1616

17-
1. the tree is rotated or mirrored
18-
2. updating padding or gap values
19-
3. a stacked window is warped out of the stack
20-
4. app icons are toggled on/off
17+
---
2118

22-
## What is stackline & why do I need it?
19+
20+
## What is stackline & why would I want to use it?
2321

2422
Consider a browser window with many tabs.
2523

2624
A tabbed user interface consists of a collection of windows that occupy the same screen space. Only _one_ tabbed window may be visible at any given time, and it's the user's job to specify the 'active' window.
2725

28-
To enable this task, tabbed interfaces provide visual indicators for each tab, each occupying much less space than the tab it references, which enables all indicators to be visible at all times. Each indicator _identifies the contents of a window_ & _communicates its position relative to the active window_.
29-
30-
A 'stack' provides a generalized subset of the functionality provided by tabbed user interfaces: it enables multiple to windows to occupy the same screen space, and provides mechanisms to navigate its member windows. It also provides mechanisms to add & remove windows from the stack.
26+
Tabbed interfaces provide visual indicators for each tab. The indicators are relatively small, so they can be visible at all times. Each indicator _identifies the contents of a window_ & _communicates its position relative to the active window_.
3127

32-
Critically for stackline, a 'stack' does not provide the visual indicators necessary to identify how many windows belong to a stack or understand the relative position of the active window within the stack.
28+
A 'stack' provides a generalized subset of a tabbed UI: it enables multiple to windows to occupy the same screen space, and provides mechanisms to navigate its member windows. It also provides mechanisms to add & remove windows from the stack.
3329

3430
Stacks are a recent addition (June 2020) to the (_excellent!_) macOS tiling window manager [koekeishiya/yabai,](https://github.com/koekeishiya/yabai,) and visualization UI is not yet in-the-box.
3531

36-
Enter stackline, which adds non-obtrusive visual indicators to yabai's stacking functionality.
32+
Enter stackline, which adds non-obtrusive visual indicators to yabai'e 's stacking functionality.
3733

3834
![stackline-demo](assets/stackline-demo.gif)
3935

40-
4136
## Getting started with stackline
4237

4338
**Prerequisites**
@@ -47,11 +42,9 @@ Enter stackline, which adds non-obtrusive visual indicators to yabai's stacking
4742
3. https://github.com/stedolan/jq (`brew install jq`)
4843

4944

50-
You're free to bind yabai commands using your favorite key remapper tool
51-
(skhd, karabiner elements, and even hammerspoon are all viable options).
45+
You're free to bind yabai commands using your favorite key remapper tool (skhd, karabiner elements, and even hammerspoon are all viable options).
5246

53-
That said, you're _probably_ using https://github.com/koekeishiya/skhd. If so,
54-
now is a good time to map keys for navigating and manipulating yabai stacks.
47+
That said, you're _probably_ using https://github.com/koekeishiya/skhd. If so, now is a good time to map keys for navigating and manipulating yabai stacks.
5548

5649
```sh
5750
# Focus window up/down in stack
@@ -70,7 +63,6 @@ cmd + ctrl - right : yabai -m window east --stack $(yabai -m query --windows --w
7063

7164
1. Clone the repo into ~/.hammerspoon/stackline
7265
2. Install the hammerspoon cli tool
73-
3. Add signals to ~/.yabairc
7466

7567
#### 1. Clone the repo into ~/.hammerspoon/stackline
7668

@@ -80,7 +72,7 @@ git clone https://github.com/AdamWagner/stackline.git ~/.hammerspoon/stackline
8072

8173
# Make stackline run when hammerspoon launches
8274
cd ~/.hammerspoon
83-
echo 'require "stackline.stackline.core"' >> init.lua
75+
echo 'local stackline = require "stackline.stackline.stackline"' >> init.lua
8476
```
8577

8678
Now your `~/.hammerspoon` directory should look like this:
@@ -114,39 +106,6 @@ Confirm that `hs` is now available:
114106
/usr/local/bin/hs
115107
```
116108

117-
#### 3. Add signals to ~/.yabairc
118-
119-
Add signals to `~/.yabairc`:
120-
121-
```sh
122-
STACKLINE_EVENTS="\
123-
application_activated \
124-
application_front_switched \
125-
application_hidden \
126-
application_launched \
127-
application_terminated \
128-
application_visible \
129-
window_created \
130-
window_deminimized \
131-
window_focused \
132-
window_minimized \
133-
window_resized"
134-
135-
yabai -m signal --add \
136-
event="window_destroyed" \
137-
label="stackline_window_destroyed" \
138-
action="echo ':window_destroyed' | /usr/local/bin/hs -m stackline-events"
139-
140-
for event in $STACKLINE_EVENTS
141-
do
142-
yabai -m signal --add \
143-
event="$event" \
144-
label="stackline_$event" \
145-
app!="Hammerspoon" \
146-
action="echo ':$event' | /usr/local/bin/hs -m stackline-events"
147-
done
148-
```
149-
150109
### RETRO? GO! FIDO? GO! GUIDANCE…
151110

152111
We're almost there!
@@ -170,6 +129,7 @@ Did the terminal window expand to cover the area previously occupied by Safari?
170129
![stackline setup 01](assets/[email protected])
171130

172131
The default stack indicator style is a "pill" as seen ↑
132+
173133
To toggle icons:
174134

175135
```sh
@@ -180,33 +140,59 @@ To toggle icons:
180140

181141
Image (and feature!) courtesy of [@alin23](https://github.com/alin23).
182142

143+
144+
#### Keybindings
145+
146+
If you use `shkd`, you can bind a key combo to toggle icons `~/.skhdrc` file using the hammerspoon cli we installed earlier.
147+
148+
```sh
149+
# if this doesn't work, try using the absolute path to the hammerspoon cli: /usr/local/bin/hs
150+
shift + alt - b : echo ":toggle_icons:1" | hs -m stackline-config
151+
```
152+
153+
Alternatively, you can control stackline by accessing the instance directly via Hammerspoon.
154+
155+
For example, to bind a key combo to toggle icons, you could add the following to your `~/.hammerspoon/init.lua` file, _after_ requiring the stackline module & assigning a local variable `stackline`:
156+
157+
```lua
158+
local stackline = require "stackline.stackline.stackline" -- you should already have this line ;-)
159+
160+
-- bind alt+ctrl+t to toggle stackline icons
161+
hs.hotkey.bind({'alt', 'ctrl'}, 't', function()
162+
stackline.manager:toggleIcons()
163+
end)
164+
```
165+
183166
## Help us get to v1.0.0!
184167

185-
Give a ⭐️ if you think (a fully functional version of) stackline would be useful!
168+
Give a ⭐️ if you think (a more fully-featured version of) stackline would be useful!
186169

187170

188171
## Thanks to contributors!
189172

190173
All are welcome (actually, _please_ help us, 🤣️)! Feel free to dive in by opening an [issue](https://github.com/AdamWagner/stackline/issues/new) or submitting a PR.
191174

192-
[@AdamWagner](https://github.com/AdamWagner) wrote the initial proof-of-concept (POC) for stackline.
193175

194-
[@alin23](https://github.com/alin23), initially proposed the [concept for stackline here](https://github.com/koekeishiya/yabai/issues/203#issuecomment-652948362) and encouraged [@AdamWagner](https://github.com/AdamWagner) to share this mostly-broken POC publicly.
176+
[@alin23(https://github.com/alin23), initially proposed the [concept for stackline here](https://github.com/koekeishiya/yabai/issues/203#issuecomment-652948362) and encouraged [@AdamWagner](https://github.com/AdamWagner) to share the mostly-broken proof-of-concept publicly. Since then, [@alin23](https://github.com/alin23) dramatically improved upon the initial proof-of-concept with https://github.com/AdamWagner/stackline/pull/13, has some pretty whiz-bang functionality on deck with https://github.com/AdamWagner/stackline/pull/17, and has been a great thought partner/reviewer.
195177

196-
- After [@alin23](https://github.com/alin23)'s https://github.com/AdamWagner/stackline/pull/13, stackline sucks a lot less.
178+
[@zweck](https://github.com/zweck), who, [in the same thread](https://github.com/koekeishiya/yabai/issues/203#issuecomment-656780281), got the gears turning about how [@alin23](gh-alin23)'s idea could be implemented and _also_ urged Adam to share his POC.
197179

198-
Thanks to [@johnallen3d](https://github.com/johnallen3d) for being one the first folks to install stackline, and for identifying several mistakes & gaps in the setup instructions.
180+
[@johnallen3d](https://github.com/johnallen3d) for being one the first folks to install stackline, and for identifying several mistakes & gaps in the setup instructions.
199181

200-
[@zweck](https://github.com/zweck), who, [in the same thread](https://github.com/koekeishiya/yabai/issues/203#issuecomment-656780281), got the gears turning about how [@alin23](gh-alin23)'s idea could be implemented and _also_ urged Adam to share his POC.
182+
[@AdamWagner](https://github.com/AdamWagner) wrote the initial proof-of-concept (POC) for stackline.
201183

202184
### …on the shoulders of giants
185+
203186
Thanks to [@koekeishiya](gh-koekeishiya) without whom the _wonderful_ [yabai](https://github.com/koekeishiya/yabai) would not exist, and projects like this would have no reason to exist.
204187

205188
Similarly, thanks to [@dominiklohmann](https://github.com/dominiklohmann), who has helped _so many people_ make chunkwm/yabai "do the thing" they want and provides great feedback on new and proposed yabai features.
206189

207-
Finally, thanks to [@cmsj](https://github.com/cmsj), [@asmagill](https://github.com/asmagill), and all of the contributors to [hammerspoon](https://github.com/Hammerspoon/hammerspoon) for opening up macOS APIs to all of us!
190+
Thanks to [@cmsj](https://github.com/cmsj), [@asmagill](https://github.com/asmagill), and all of the contributors to [hammerspoon](https://github.com/Hammerspoon/hammerspoon) for making macOS APIs accessible to the rest of us!
191+
192+
Thanks to the creators & maintainers of the lua utility libaries [underscore.lua](https://github.com/mirven/underscore.lua), [lume.lua](https://github.com/rxi/lume), and [self.lua](https://github.com/M1que4s/self).
208193

209194
## License & attribution
195+
210196
stackline is licensed under the [&nearr;&nbsp;MIT&nbsp;License](stackline-license), the same license used by [yabai](https://github.com/koekeishiya/yabai/blob/master/LICENSE.txt) and [hammerspoon](https://github.com/Hammerspoon/hammerspoon/blob/master/LICENSE).
211197

212198
MIT is a simple permissive license with conditions only requiring preservation of copyright and license notices. Licensed works, modifications, and larger works may be distributed under different terms and without source code.

bin/yabai-get-stack-idx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/bin/dash
2+
3+
/usr/local/bin/yabai -m query --windows --space \
4+
| /usr/local/bin/jq --raw-output --compact-output --monochrome-output 'map({"\(.id)": .["stack-index"]}) | reduce .[] as $item ({}; . + $item)'
5+

bin/yabai-get-stacks

Lines changed: 0 additions & 57 deletions
This file was deleted.

notes.md renamed to dev-notes/general-dev-notes.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
# stackline development notes
22

33
## Inspiration
4+
[hhtwm/init.lua](https//github.com/szymonkaliski/hhtwm/blob/master/hhtwm/init.lua)
5+
6+
Probably the best source of inspo, esp. the module.cache = {…} implementation.
7+
8+
[megalithic/window.lua](https://github.com/megalithic/dotfiles/blob/master/hammerspoon/hammerspoon.symlink/ext/window.lua)
9+
10+
- [/utils/wm/window-handlers.lua](https://github.com/megalithic/dotfiles/blob/master/hammerspoon/hammerspoon.symlink/utils/wm/window-handlers.lua)
11+
- [/utils/wm/init.lua](https://github.com/megalithic/dotfiles/blob/master/hammerspoon/hammerspoon.symlink/utils/wm/init.lua)
412

513
[window_set.lua](https://github.com/macrael/panes/blob/master/Panes.spoon/window_set.lua)
14+
615
Seems similar to what I'm doing, but it didn't run w/ SpoonInstall so I haven't used it yet
716

817
See how they manage window indicators:
@@ -25,6 +34,68 @@ See how they manage window indicators:
2534
- [colorboard.lua](https://github.com/CommandPost/CommandPost/blob/develop/src/plugins/finalcutpro/touchbar/widgets/colorboard.lua): CommandPost Colorboard The opposite of above, this is *complicated*! Lots of state management, but not sure how applicable it is for me.
2635
- [statuslets.lua](https://github.com/cmsj/hammerspoon-config/blob/master/statuslets.lua): statuslets
2736

37+
38+
## Debugging
39+
40+
Debugging utils for lua
41+
42+
- https://github.com/renatomaia/loop-debugging
43+
44+
## Caching & queing
45+
46+
[pyericz/LuaWorkQueue](https://github.com/pyericz/LuaWorkQueue/tree/master/src)
47+
A work queue implementation written in Lua.
48+
49+
[darkwark/queue-lua](https://github.com/darkwark/queue-lua)
50+
Queue implementation for Lua and PICO-8
51+
Newer (2020)
52+
53+
[hewenning/Lua-Container](https://github.com/hewenning/Lua-Container/blob/master/Container.lua)
54+
🌏 Implement various containers, stack, queue, priority queue, heap, A* algorithms, etc. through Lua.
55+
56+
[KurtLoeffler/Lua_CachePool](https://github.com/KurtLoeffler/Lua_CachePool)
57+
A lua library for creating pools of cached objects.
58+
59+
60+
## Working with async in Hammerspoon
61+
62+
`hs.task` is async. There are 2 ways to deal with this:
63+
64+
1. `hs.timer.waitUntil(pollingCallback, func)`
65+
2. `hs.task.new(…):start():waitUntilUNex()`
66+
67+
The 1st polls a callback to check if the expected result of the async task has
68+
materialized.
69+
The 2nd makes `hs.task` synchronous.
70+
71+
The docs strongly discourage use of the 2nd approach, but as long as there isn't
72+
background work that could be done while waiting (there isn't in the use case
73+
I'm thinking of), then it should be slightly _faster_ than polling since the
74+
callback will fire immediately when the task completes. It also saves the cycles
75+
needed to poll in the first place.
76+
77+
```lua
78+
-- Wait until the win.stackIdx is set from async shell script
79+
hs.task.new("/usr/local/bin/dash", function(_code, stdout, stderr)
80+
callback(stdout)
81+
end, {cmd, args}):start():waitUntilExit()
82+
83+
-- NOTE: timer.waitUntil is like 'await' in javascript
84+
hs.timer.waitUntil(winIdxIsSet, function() return data end)
85+
86+
-- Checker func to confirm that win.stackIdx is set
87+
-- For hs.timer.waitUntil
88+
-- NOTE: Temporarily using hs.task:waitUntilExit() to accomplish the
89+
-- same thing
90+
function winIdxIsSet()
91+
if win.stackIdx ~= nil then
92+
return true
93+
end
94+
end
95+
```
96+
97+
98+
2899
## Using hs.canvas
29100

30101
Yet another project has a similar take:

dev-notes/luarocks-reqs.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
luarocks install moses

0 commit comments

Comments
 (0)