Skip to content

Commit 6edc81b

Browse files
committed
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!
1 parent ba70ea8 commit 6edc81b

File tree

5 files changed

+466
-291
lines changed

5 files changed

+466
-291
lines changed

stackline/WIP-query.lua

+167
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
local _ = require 'stackline.utils.utils'
2+
local spaces = require("hs._asm.undocumented.spaces")
3+
local screen = require 'hs.screen'
4+
local u = require 'stackline.utils.underscore'
5+
local fnutils = require("hs.fnutils")
6+
7+
--[[
8+
The goal of this file is to eliminate the need to 'shell out' to yabai to query
9+
window data needed to render stackline, which would address
10+
https://github.com/AdamWagner/stackline/issues/8. The main problem with relying
11+
on yabai is that a 0.03s sleep is required in the yabai script to ensure that
12+
the changes that triggered hammerspoon's window event subscriber are, in fact,
13+
represented in the query response from yabai. There are probably secondary
14+
downsides, such as overall performance, and specifically *yabai* performance
15+
(I've noticed that changing focus is slower when lots of yabai queries are
16+
happening simultaneously).
17+
18+
┌────────┐
19+
│ Status │
20+
└────────┘
21+
We're not yet using any of the code in this file to actually render the
22+
indiators or query ata — all of that is still achieved via the "old" methods.
23+
24+
However, this file IS being required by ./core.lua and runs one every window focus
25+
event, and the resulting "stack" data is printed to the hammerspoon console.
26+
27+
The stack data structure differs from that used in ./stack.lua enough that it
28+
won't work as a drop-in replacement. I think that's fine (and it wouldn't be
29+
worth attempting to make this a non-breaking change, esp. since zero people rely
30+
on it as of 2020-08-02.
31+
32+
┌──────┐
33+
│ Next │
34+
└──────┘
35+
- [ ] Integrate appropriate functionality in this file into the Stack module
36+
- [ ] Update key Stack module functions to have basic compatiblity with the new data structure
37+
- [ ] Simplify / refine Stack functions to leverage the benefits of having access to the hs.window module for each tracked window
38+
- [ ] Integrate appropriate functionality in this file into the Core module
39+
- [ ] … see if there's anything left and decide where it should live
40+
41+
┌───────────┐
42+
│ WIP NOTES │
43+
└───────────┘
44+
Much of the functionality in this file should either be integrated into
45+
stack.lua or core.lua — I don't think a new file is needed.
46+
47+
Rather than calling out to the script ../bin/yabai-get-stacks, we're using
48+
hammerspoon's mature (if complicated) hs.window.filter and hs.window modules to
49+
achieve the same goal natively within hammerspon.
50+
51+
There might be other benefits in addition to fixing the problems that inspired
52+
#8: We get "free" access to the *hammerspoon* window module in the window data
53+
tracked by stackline, which will probably make it easier to implement
54+
enhancements that we haven't even considered yet. This approach should also be
55+
easier to maintain, *and* we get to drop the jq dependency!
56+
57+
--]]
58+
59+
local wfd = hs.window.filter.new():setOverrideFilter{ -- {{{
60+
visible = true, -- (i.e. not hidden and not minimized)
61+
fullscreen = false,
62+
currentSpace = true,
63+
allowRoles = 'AXStandardWindow',
64+
} -- }}}
65+
66+
function getSpaces() -- {{{
67+
return fnutils.mapCat(screen.allScreens(), function(s)
68+
return spaces.layout()[s:spacesUUID()]
69+
end)
70+
end -- }}}
71+
72+
function getActiveSpaceIndex() -- {{{
73+
local s = getSpaces()
74+
local activeSpace = spaces.activeSpace()
75+
return _.indexOf(s, activeSpace)
76+
end -- }}}
77+
78+
function makeStackId(win, winSpaceId) -- {{{
79+
-- generate stackId from spaceId & frame values
80+
-- example: "302|35|63|1185|741"
81+
local frame = win:frame():floor()
82+
local x = frame.x
83+
local y = frame.y
84+
local w = frame.w
85+
local h = frame.h
86+
return table.concat({winSpaceId, x, y, w, h}, '|')
87+
end -- }}}
88+
89+
function mapWin(hsWindow) -- {{{
90+
return {
91+
stackId = makeStackId(hsWindow, hsWindow:spaces()[1]),
92+
id = hsWindow:id(),
93+
x = hsWindow:frame().x,
94+
y = hsWindow:frame().y,
95+
app = hsWindow:application():name(),
96+
title = hsWindow:title(),
97+
frame = hsWindow:frame(),
98+
_win = hsWindow,
99+
}
100+
end -- }}}
101+
102+
function lenGreaterThanOne(t)
103+
return #t > 1
104+
end
105+
106+
function makeStacksFromWindows(ws) -- {{{
107+
local windows = u.map(ws, mapWin)
108+
local groupedWindows = _.groupBy(windows, 'stackId')
109+
-- stacks contain more than one window,
110+
-- so ignore groups with only 1 window
111+
local stacks = hs.fnutils.filter(groupedWindows, lenGreaterThanOne)
112+
return stacks
113+
end -- }}}
114+
115+
function winToHs(win)
116+
return win._win
117+
end
118+
119+
function stackOccluded(stack) -- {{{
120+
-- FIXES: When a stack that has "zoom-parent": 1 occludes another stack, the
121+
-- occluded stack's indicators shouldn't be displaed
122+
-- https://github.com/AdamWagner/stackline/issues/11
123+
124+
-- Returns true if any non-stack window occludes the stack's frame.
125+
-- This can occur when an unstacked window is zoomed to cover a stack.
126+
-- In this situation, we want to *hide* the occluded stack's indicators
127+
-- TODO: Convert to Stack instance method (wouldn't need to pass in the 'stack' arg)
128+
129+
function notInStack(hsWindow)
130+
local stackWindowsHs = u.map(u.values(stack), winToHs)
131+
local isInStack = u.include(stackWindowsHs, hsWindow)
132+
return not isInStack
133+
end
134+
135+
-- NOTE: under.filter works with tables
136+
-- _.filter only works with "list-like" tables
137+
local nonStackWindows = u.filter(wfd:getWindows(), notInStack)
138+
139+
function isStackInside(nonStackWindow)
140+
local stackFrame = stack[1]._win:frame()
141+
return stackFrame:inside(nonStackWindow:frame())
142+
end
143+
144+
return u.any(_.map(nonStackWindows, isStackInside))
145+
end -- }}}
146+
147+
-- luacheck: ignore
148+
function stacksOccluded(stacks) -- {{{
149+
-- NOTE: This *could* be a simple one-liner
150+
local occludedStacks = _.map(stacks, stackOccluded)
151+
_.pheader('occluded stacks:')
152+
_.p(occludedStacks)
153+
return occludedStacks
154+
end -- }}}
155+
156+
function windowsCurrentSpace() -- {{{
157+
local ws = wfd:getWindows()
158+
local stacks = makeStacksFromWindows(ws)
159+
_.pheader('STACKS!')
160+
_.p(stacks, 3)
161+
stacksOccluded(stacks)
162+
end -- }}}
163+
164+
wfd:subscribe(hs.window.filter.windowFocused, windowsCurrentSpace)
165+
166+
windowsCurrentSpace()
167+

stackline/core.lua

+2
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,5 @@ wf:subscribe(win_removed, (function(_win, _app, event)
4545
wsi:update(true)
4646
end))
4747

48+
require 'stackline.stackline.query'
49+

stackline/window.lua

-28
Original file line numberDiff line numberDiff line change
@@ -53,34 +53,6 @@ function Window.__eq(a, b) -- {{{
5353
return isEqual
5454
end -- }}}
5555

56-
-- metatable testing {{{
57-
local Test = {}
58-
function Test:new(name, age)
59-
local test = {name = name, age = age}
60-
61-
local mmt = {
62-
__add = function(a, b)
63-
return a.age + b.age
64-
end,
65-
__eq = function(a, b)
66-
return a.age == b.age
67-
end,
68-
}
69-
setmetatable(test, mmt)
70-
return test
71-
end
72-
73-
local amy = Test:new('amy', 18)
74-
local adam = Test:new('adam', 33)
75-
local carl = Test:new('carl', 18)
76-
77-
print('amy equals adam?', amy == adam)
78-
print('amy equals carl?', amy == carl)
79-
print('amy plus adam?', (amy + adam))
80-
print('amy plus carl?', (amy + carl))
81-
print('amy plus amy?', (amy + amy))
82-
-- }}}
83-
8456
-- TODO: ↑ Convert to .__eq metatable
8557
function Window:setNeedsUpdated(extant) -- {{{
8658
local isEqual = _.isEqual(existComp, currComp)

0 commit comments

Comments
 (0)