fix(hammerspoon): resize ghostty windows as a pair

This commit is contained in:
AquaMorph committed 2026-10-30 07:46:56 -04:00
1 parent 0b97d73083
commit e39b5fc3b0
1 file changed
+40 -4
+40 -4
View File
@@ -34,6 +34,21 @@ local rules = {
local applied, pending = {}, {} local applied, pending = {}, {}
local filter local filter
local function ghosttyWindows()
local windows = {}
for _, window in ipairs(filter:getWindows()) do
local app = window:application()
if app and app:bundleID() == "com.mitchellh.ghostty"
and window:isStandard() and not window:isMinimized()
and not window:isFullScreen() and window:id() then
table.insert(windows, window)
end
end
-- Window IDs keep the older window on the left, regardless of focus order.
table.sort(windows, function(a, b) return a:id() < b:id() end)
return windows
end
local function spotOccupied(window, rule) local function spotOccupied(window, rule)
if not rule.singleOccupancy or not filter then return false end if not rule.singleOccupancy or not filter then return false end
for _, other in ipairs(filter:getWindows()) do for _, other in ipairs(filter:getWindows()) do
@@ -79,7 +94,17 @@ end
local function place(window, force) local function place(window, force)
local rule, index = ruleFor(window) local rule, index = ruleFor(window)
local id = window:id() local id = window:id()
if not rule or not id or (not force and applied[id] == index) then return end if not rule or not id then return end
local frame, placement = rule.frame, index
if rule.app == "com.mitchellh.ghostty" then
local windows = ghosttyWindows()
if #windows == 2 then
local left = windows[1]:id() == id
frame = {left and 0 or 0.5, frame[2], 0.5, frame[4]}
placement = tostring(index) .. (left and ":left" or ":right")
end
end
if not force and applied[id] == placement then return end
local screen local screen
for _, candidate in ipairs(hs.screen.allScreens()) do for _, candidate in ipairs(hs.screen.allScreens()) do
if candidate:getUUID() == monitors[rule.monitor] then screen = candidate; break end if candidate:getUUID() == monitors[rule.monitor] then screen = candidate; break end
@@ -101,7 +126,7 @@ local function place(window, force)
return return
end end
end end
local bounds, frame = screen:fullFrame(), rule.frame local bounds = screen:fullFrame()
window:setFrame(hs.geometry.rect( window:setFrame(hs.geometry.rect(
bounds.x + frame[1] * bounds.w, bounds.y + frame[2] * bounds.h, bounds.x + frame[1] * bounds.w, bounds.y + frame[2] * bounds.h,
frame[3] * bounds.w, frame[4] * bounds.h frame[3] * bounds.w, frame[4] * bounds.h
@@ -113,7 +138,7 @@ local function place(window, force)
.. " (target Space " .. tostring(target) .. ")") .. " (target Space " .. tostring(target) .. ")")
return return
end end
applied[id] = index applied[id] = placement
end end
filter = hs.window.filter.new(true):setOverrideFilter({ filter = hs.window.filter.new(true):setOverrideFilter({
@@ -131,7 +156,13 @@ filter:subscribe({hs.window.filter.windowCreated, hs.window.filter.windowTitleCh
-- Wait for apps to finish their own initial resize and browser page loading. -- Wait for apps to finish their own initial resize and browser page loading.
pending[id] = hs.timer.doAfter(1, function() pending[id] = hs.timer.doAfter(1, function()
pending[id] = nil pending[id] = nil
if window:id() then place(window, false) end if window:id() then
place(window, false)
local app = window:application()
if app and app:bundleID() == "com.mitchellh.ghostty" then
for _, other in ipairs(ghosttyWindows()) do place(other, false) end
end
end
end) end)
end) end)
filter:subscribe(hs.window.filter.windowDestroyed, function(window) filter:subscribe(hs.window.filter.windowDestroyed, function(window)
@@ -140,6 +171,11 @@ filter:subscribe(hs.window.filter.windowDestroyed, function(window)
applied[id] = nil applied[id] = nil
if pending[id] then pending[id]:stop(); pending[id] = nil end if pending[id] then pending[id]:stop(); pending[id] = nil end
end end
-- Let the filter remove the closed window before checking the remaining one.
hs.timer.doAfter(0.2, function()
local windows = ghosttyWindows()
if #windows == 1 then place(windows[1], false) end
end)
end) end)
-- Retain the filter, watchers and menu for the lifetime of the configuration. -- Retain the filter, watchers and menu for the lifetime of the configuration.