-
Notifications
You must be signed in to change notification settings - Fork 47
Feature fuzzy stacked win detection
https://github.com/AdamWagner/stackline/issues/32 turned out to be a lot trickier than expected.
I attempted to rewrite the window grouping logic to be more forgiving than simply rounding the window frame points before stringifying the frame and grouping windows into stacks.
The grouping logic below produces worse results, and (still) has tricky bugs, such as non-deterministic results when simply refreshing Hammerspoon. I think these bugs are due to a combination of the way lua tables can have two table keys that have the same data, and yet are distinct instance plus the fact that table order is not guaranteed. The first issue requires tables to be hashed as strings before being used as unique table keys, and the second issue makes it difficult to produce a consistent hash from a table.
At any rate, I experimented with much larger frameFuzz
values, and did not observe negative side effects with values up to 200. Further, a value of 200 was sufficient to mask the minor differences in window size for apps that constrain dimensions such as iTerm2.
function sumTable(xs)
local sum = function(x, y) return x + y end
return hs.fnutils.reduce(xs, sum)
end
function hash(frame)
local f = frame:floor().table
local hash = table.concat({f.x, f.y, f.w, f.h}, '|'),
return hash
end -- }}}
function unhash(s) -- {{{
local ks = {'x', 'y', 'w', 'h'}
local vs = u.map(u.split(s, '|'), tonumber)
local unhashed = u.zip(ks,vs)
return unhashed
end -- }}}
function compareFrames(a, b) -- {{{
local delta = {}
if a.table then
print('a is a frame')
a = a.table
else
a = unhash(a)
end
for k, _v in pairs(b.table) do
delta[k] = math.abs(a[k] - b.table[k])
end
local values = u.values(delta)
local max = math.max(table.unpack(values))
local avg = sumTable(values) / #values
local frameDiff = {
delta = delta,
max = max,
avg = avg,
}
if frameDiff.max < 50 and frameDiff.avg < 10 then
-- I didn't simply return ↑ b/c I was logging debug info here
return true
else
return false
end
end
function getAggregation(a, b)
if a.frame then
local groups = {}
groups[hash(a.frame)] = { a }
return groups
else
return a
end
end
-- The first iteration of the reducer will call fn with the first and second
-- elements of the table. The second iteration will call fn with the result of
-- the first iteration, and the third element. This repeats until there is only
-- one element left
function fuzzyGroupByFrame(a, b)
local groups = getAggregation(a, b)
for frame, _wins in pairs(groups) do
if compareFrames(frame, b.frame) then
groups[frame] = u.concat(groups[frame], {b})
else
groups[hash(b.frame)] = { b }
end
end
return groups
end