Skip to content

Commit

Permalink
Feature/fuzzy stacked win detection (#37)
Browse files Browse the repository at this point in the history
* Initial experiments with multi-monitor support. This is going to be hard!

* Remove stackIdx set in Window:new, as it was always null anyway

* Clarify why windows not on current space are filtered in Query:groupWindows()

* Clarify experimental change in yabai-get-stack-idx script

* Add note on status of multi-monitor support below feature list in readme.

* General cleanup in preparation for multi-mon support

* Add utils.look() to see tables with metatable details

* Minimally-viable multi-monitor support

  - stackline/stackline.lua is now a proper module storing fields and methods in a table
  - Move global variables into stackline module
  - On every windowFocused event, check to see if screen has changed. If so, refresh stack indicators.

  The last point is what provides an MVP for multi-monitors support:

    - `stackline` only renders on the monitor that contains the focused window
    - Stacks on screens that do not contain the focused window do not have indicators
    - Because refreshing (query all windows, re-render all stack indicators) is kind of slow (500ms), rapidly switching screens is not a great experience

I still think the "proper" solution will require updating the data model to track screens (at least) and potentially spaces. This will enable stackline to render on all screens. If spaces are modeled, it will additionally speed up the rendering of stack indicators when switching between spaces with stacks.

* Actually-pretty-good multi-monitor support

* Cleaning up

* Add note on status of multi-monitor support below feature list in readme.

* Basic 'click to focus window' feature

* Delete clicks on indicators to prevent focusing hammerspoon

* Restore missing stacks def

* fixes & tweaks

* Support iTerm by rounding window frames to (configurable) fuzzFactor before equality comparison.

* Simplify Window:isFocused()

* After a failed attempt to rework how windows are grouped into stacks to account for apps that constrain window size, got better results from simply increasing the frameFuzz factor all the way to 200 (without negative side effects)

* update .gitignore

* update .gitignore
  • Loading branch information
AdamWagner committed Oct 10, 2020
1 parent 6b1798e commit 43b37b5
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 19 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@

luacov.stats.out
lib/figlet.lua
stackline/sratch.md
tmp-notes.txt
37 changes: 37 additions & 0 deletions lib/utils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,20 @@ function utils.keyBind(hyper, keyFuncTable) -- {{{
end
end -- }}}

function utils.concat(obj, obj2) -- {{{
local output = {}

for i=1, #obj do
table.insert(output, obj[i])
end

for i=1, #obj2 do
table.insert(output, obj2[i])
end

return output
end -- }}}

utils.length = function(t) -- {{{
local count = 0
for _ in pairs(t) do
Expand Down Expand Up @@ -404,6 +418,10 @@ function utils.greaterThan(n) -- {{{
end
end -- }}}

function utils.roundToNearest(roundTo, numToRound) -- {{{
return numToRound - numToRound % roundTo
end -- }}}

function utils.getFields(t, fields) -- {{{
-- FROM: https://stackoverflow.com/questions/41417971/a-better-way-to-assign-multiple-return-values-to-table-keys-in-lua
-- WHEN: 2020-08-09
Expand Down Expand Up @@ -460,4 +478,23 @@ function utils.setFields(tab, fields, ...) -- {{{
return tab
end -- }}}

function utils.split(str, sep, max) -- {{{
sep = '^(.-)' .. sep
local t, n, p, q, r, s = {}, 1, 1, str:find(sep)
while q and n ~= max do
t[n], n, p = s, n + 1, r + 1
q, r, s = str:find(sep, p)
end
t[n] = str:sub(p)
return t
end -- }}}

function utils.zip(obj1, obj2) -- {{{
local res = {}
for k, v in pairs(obj1) do
res[v] = obj2[k]
end
return res
end -- }}}

return utils
1 change: 0 additions & 1 deletion stackline/query.lua
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ function Query:groupWindows(ws) -- {{{
return Window:new(w)
end)

-- See 'stackId' def @ /window.lua:233
byStack = u.filter(u.groupBy(windows, 'stackId'), u.greaterThan(1)) -- stacks have >1 window, so ignore 'groups' of 1

if u.length(byStack) > 0 then
Expand Down
16 changes: 13 additions & 3 deletions stackline/stackline.lua
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@ stackline.wf = wf.new():setOverrideFilter{ -- {{{
allowRoles = 'AXStandardWindow',
} -- }}}


local click = hs.eventtap.event.types['leftMouseDown'] -- print hs.eventtap.event.types to see all event types
stackline.clickTracker = hs.eventtap.new({click}, -- {{{
stackline.clickTracker = hs.eventtap.new({click}, -- {{{
function(e)
-- Listen for left mouse click events
-- if indicator containing the clickAt position can be found, focus that indicator's window
Expand All @@ -39,7 +38,18 @@ end -- }}}

function stackline.start(userPrefs) -- {{{
u.pheader('starting stackline')
local defaultUserPrefs = {showIcons = true, enableTmpFixForHsBug = true}
local defaultUserPrefs = {
showIcons = true,
enableTmpFixForHsBug = true,

-- ## frameFuzz:
-- Window frames rounded to frameFuzz before their stringified form is
-- used as a key to group windows into stacks. Windows with equal
-- stringified frames will belong to the same stack.
-- ~50 to 200 is recommended to support stacks w/ win. that quantize
-- their size, like iTerm.
frameFuzz = 200,
}
local prefs = userPrefs or defaultUserPrefs
stackline.config = StackConfig:new():setEach(prefs):registerWatchers()
stackline.manager = require('stackline.stackline.stackmanager'):new()
Expand Down
34 changes: 19 additions & 15 deletions stackline/window.lua
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
-- TODO: Click on indicator to activate target window (like tabs) https://github.com/AdamWagner/stackline/issues/19
local u = require 'stackline.lib.utils'
local Window = {}

function Window:new(hsWin) -- {{{
local frameAttrs = self:makeStackId(hsWin)
local ws = {
title = hsWin:title(), -- window title
app = hsWin:application():name(), -- app name (string)
id = hsWin:id(), -- window id (string) NOTE: the ID is the same as yabai! So we could interopt if we need to
frame = hsWin:frame(), -- x,y,w,h of window (table)
stackId = self:makeStackId(hsWin).stackId, -- "{{x}|{y}|{w}|{h}" e.g., "35|63|1185|741" (string)
topLeft = self:makeStackId(hsWin).topLeft, -- "{{x}|{y}" e.g., "35|63" (string)
stackId = frameAttrs.stackId, -- "{{x}|{y}|{w}|{h}" e.g., "35|63|1185|741" (string)
topLeft = frameAttrs.topLeft, -- "{{x}|{y}" e.g., "35|63" (string)
_win = hsWin, -- hs.window object (table)
screen = hsWin:screen():id(),
indicator = nil, -- the canvas element (table)
Expand All @@ -21,11 +21,7 @@ end -- }}}

function Window:isFocused() -- {{{
local focusedWin = hs.window.focusedWindow()
if focusedWin == nil then
return false
end
local isFocused = self.id == focusedWin:id()
return isFocused
return focusedWin and (self.id == focusedWin:id())
end -- }}}

function Window:isStackFocused() -- {{{
Expand Down Expand Up @@ -357,14 +353,22 @@ function Window:iconFromAppName() -- {{{
end -- }}}

function Window:makeStackId(hsWin) -- {{{
local frame = hsWin:frame():floor()
local x = frame.x
local y = frame.y
local w = frame.w
local h = frame.h
-- NOTE: it might be better to use frame:getstring() -> 1106,58/635x504

local fuzzFactor = stackline.config:get('frameFuzz')
local roundToFuzzFactor = u.partial(u.roundToNearest, fuzzFactor)

local f = hsWin:frame():floor():gettable()
local ff = u.map(f, roundToFuzzFactor)

-- print(hsWin:title(), 'round to neartest 5:', u.roundToNearest(5, 33))
-- u.p(f)
-- u.p(ff)

-- use fuzzy frame (rounded to nearest)
return {
topLeft = table.concat({x, y}, '|'),
stackId = table.concat({x, y, w, h}, '|'),
topLeft = table.concat({f.x, f.y}, '|'),
stackId = table.concat({ff.x, ff.y, ff.w, ff.h}, '|'),
}
end -- }}}

Expand Down

0 comments on commit 43b37b5

Please sign in to comment.