Being a big fan of using the keyboard for everything and not touching the mouse more than necessary, I went through different OSX helper applications for helping to move windows around, aligning them on screen, etc. Lately I've been using Magnet (formerly known as Window Magnet, App Store, $4.99), which is pretty nice but I felt it is not using the concept of moving stuff around with the keyboard to the full extent: While you can surely put windows in one quadrant of the screen or something, there's no option to e.g. nudge a window a bit to the right.
Enter Hammerspoon: First and foremost, Hammerspoon provides you with scriptable access to OSX's accessibility API -- and whatever you do with that, you can bind to a hotkey. Not only can you access that single API, but also check for USB devices, Wifi names, attached screens and so on - all controlable with small Lua scripts. Just some examples of what you can do with Hammerspoon:
- switch sound on or off, depending on the available wifi networks
- unmount USB devices upon switching to battery power
- watch the name and number of screens connected and position windows accordingly (e.g. if Thunderbolt Display connected, put Xcode on that one and Mail on the internal one)
- move windows on a grid on the screen
- move windows in general
- ...
Phrased differently:
Configuring Hammerspoon
The following code snippets have been heavily inspired (read: copied) by philipalexander's, tstirrat's and cmsj's Hammerspoon configs on Github and also by Tristan Hume's post on configuring Mjolnir, of which Hammerspoon is a fork.
The configuration (it basically comes with no defaults) can be a bit daunting in the beginning - in fact, I had stumbled upon Hammerspoon already some time ago but didn't invest the effort back then.
However, if you have about 30min at hand, you can already hammer out a nice configuration for the things that are most important for you to automate or hotkey. For me, these are arranging windows grid-like on screen and have e.g. the screen locked with a keypress. So let's have a look at that:
First, Hammerspoon's config resides in the file ~/.hammerspoon/init.lua
. I use a set of modifier keys for all of the hotkeys, let's call that set hyper. I also define that I want a window grid size of 2x2, with no margins:
local hyper = {"⌘", "⌥", "⌃", "⇧"}
-- definitions
hs.grid.MARGINX = 0
hs.grid.MARGINY = 0
hs.grid.GRIDWIDTH = 2
hs.grid.GRIDHEIGHT = 2
This combination of keys might also be mapped to a single key by virtue of Karabiner, a cool program allowing arbitrary key remappings and much much. How to achieve that thou is left as an exercise for the interested reader ;-)
Easy bits first -- application launching
I want to automate a few things via hotkeys. Stuff I regularly want to do is get an iTerm open or locking the screen. These two actions can be bound to keys via:
-- locking
hs.hotkey.bind(hyper, 'x', function()
os.execute("/System/Library/CoreServices/Menu\\ Extras/User.menu/Contents/Resources/CGSession -suspend")
end)
-- Applications
hs.hotkey.bind(hyper, 'i', function()
os.execute("open /Applications/iTerm.app")
end)
Configuring Window Movement Hotkeys
-- a helper function that returns another function that resizes the current window
-- to a certain grid size.
local gridset = function(x, y, w, h)
return function()
cur_window = hs.window.focusedWindow()
hs.grid.set(
cur_window,
{x=x, y=y, w=w, h=h},
cur_window:screen()
)
end
end
-- function to move window one screen left or back if it's already on the
-- leftmost
local toNextScreen = function()
return function()
currentWindow = hs.window.focusedWindow()
s = hs.screen{x=1,y=0}
if s == currentWindow:screen() then
s = hs.screen{x=0,y=0}
currentWindow:moveToScreen(s)
else
currentWindow:moveToScreen(s)
end
end
end
-- movement keys
hs.hotkey.bind(hyper, 'j', toNextScreen())
hs.hotkey.bind(hyper, 'h', gridset(0, 0, 1, 2)) -- left half
hs.hotkey.bind(hyper, 'k', hs.grid.maximizeWindow)
hs.hotkey.bind(hyper, 'l', gridset(1, 0, 1, 2)) -- right half
Auto-reloading of config
Hammerspoon also supports reloading its configuration files upon detecting changes. Just add the following at the very end of init.lua
:
-- watch config for changes and reload when they occur
function reloadConfig(files)
doReload = false
for _,file in pairs(files) do
if file:sub(-4) == ".lua" then
doReload = true
end
end
if doReload then
hs.reload()
end
end
hs.pathwatcher.new(os.getenv("HOME") .. "/.hammerspoon/", reloadConfig):start()
hs.alert.show("Config reloaded")
Hammerspoon is free & open source software (MIT licensed) - the source code is on Github: https://github.com/Hammerspoon/hammerspoon
My full Hammerspoon config is also available on Github, have a look at https://github.com/skalarproduktraum/hammerspoon-config/