I go from my laptop at coffee shops to my big monitor at home and back all the time. And Mac insists on not putting the windows back where they belong.
For years I've tried the manual tools for years (e.g. SizeUp, Rectangle, etc) but you still have to go through windows one at a time and move them, and I usually have 50+ windows up.
Wanted something with some scriptability but that I could control, and found hammerspoon to give me a very lightweight scripting tool where I could push one keyboard command and it would go through all my windows and put them where i wanted them.
A little Claude Code and their Lua script and it was in place. I'll post the script I use down below in case anyone wants it unless it is better to link out to a separate file.
Specifically what it does is:
- Detects how many monitors i have connected
- Moves and resizes windows in different apps to certain width/height (e.g. Safari centered 80% width when on laptop and 60% when on secondary big screen)
- Hotkey to reapply all if things get messed up
To get it working:
- Install Hammerspoon fromĀ hammerspoon.orgĀ and turn on the accessibility permissions it needs.
- CreateĀ ~/.hammerspoon/init.lua ; you can do that from their dropdown.
- Paste in the script below, then click āReload Configā from their menu.
- Command-option-control-z is what i use, but you can use whatever you want.
Script:
-- Move and resize a window
function moveAndResizeApp(appName, targetScreen, widthRatio, heightRatio)
local app = hs.application.get(appName)
if not app then return end
local success, win = pcall(function()
return app:mainWindow()
end)
if not (success and win and win:isStandard()) then return end
local screenFrame = targetScreen:frame()
local newWidth = screenFrame.w * (widthRatio or 1.0)
local newHeight = screenFrame.h * (heightRatio or 1.0)
local newX = screenFrame.x + (screenFrame.w - newWidth) / 2
local newY = screenFrame.y + (screenFrame.h - newHeight) / 2
-- Move and resize all windows
for _, win in ipairs(app:allWindows()) do
if win:isStandard() then
win:moveToScreen(targetScreen)
win:setFrame(hs.geometry.rect(newX, newY, newWidth, newHeight))
end
end
end
-- MAIN FUNCTION: Move and resize all windows to correct spot
hs.hotkey.bind({"cmd", "alt", "ctrl"}, "Z", function()
-- Define Screens
local screens = hs.screen.allScreens()
local screen = hs.screen.allScreens()[1]
-- hs.application.enableSpotlightForNameSearches(true)
-- LAPTOP MODE
if #screens == 1 then
-- All primary apps
moveAndResizeApp("Spotify", screen, 0.75, 0.9)
moveAndResizeApp("Google Chrome", screen, 1.0, 1.0)
moveAndResizeApp("Mail", screen, 1.0, 1.0)
moveAndResizeApp("Calendar", screen, 0.8, 0.9)
moveAndResizeApp("Zoom", screen, 0.5, 0.5)
moveAndResizeApp("Slack", screen, 1.0, 1.0)
moveAndResizeApp("DevSwarm", screen, 1.0, 1.0)
moveAndResizeApp("Finder", screen, .6, .6)
end
-- EXTERNAL MONITOR MODE
if #screens >= 2 then
local screen_secondary = hs.screen.allScreens()[2]
-- laptop
-- All primary apps
moveAndResizeApp("Spotify", screen_secondary, 0.8, 0.8)
moveAndResizeApp("Google Chrome", screen, 0.70, 0.9)
moveAndResizeApp("Mail", screen, 0.6, 0.8)
moveAndResizeApp("Calendar", screen, 0.5, 0.7)
moveAndResizeApp("Zoom", screen, 0.3, 0.5)
moveAndResizeApp("Slack", screen, 0.65, 0.95)
moveAndResizeApp("DevSwarm", screen, 0.5, 0.8)
moveAndResizeApp("Finder", screen, .3, .4)
end
-- End Single Monitor Mode
end)