Powerful Hammerspoon
I have used hammerspoon for a long time, recently I found that the new version of hammerspoon supported Lua plugins called “Spoons”.
So I deceided to rewrite my hammerspoon config file with spoons.
I hope this post can help beginners to get started with hammerspoon (and spoons).
Let’s getting started.
First, install hammerspoon, you can easily install it using homebrew:
brew cask install hammerspoon
And make sure these files and folders exists:
~/.hammerspoon
├── Spoons
├── modules
├── init.lua
Let’s download our first spoon http://www.hammerspoon.org/Spoons/ReloadConfiguration.html.
Download it and double click to unzip and install it, it will be installed in the Spoons
folder.
Then launch the hammerspoon console and input
hs.loadSpoon("AClock")
spoon.AClock:toggleShow()
Bingo! We just finish the hammerspoon version of “hello world” :)
You can see the clock display on the screen.
Now let’s start your first config script, edit the init.lua
file:
hs.loadSpoon("AClock")
hyper = {'cmd', 'alt'}
hs.hotkey.bind(hyper, 't', function() spoon.AClock:toggleShow() end)
save it and reload config. Then try to press cmd+option+t
, the clock should appear again.
Windows manager
Now we can dive into a more complex task: windows manager.
Let’s first define our hyper keys.
In modules/config.lua
:
hyper = {'cmd', 'alt'}
hyperShift = {'alt', 'cmd', 'shift'}
Don’t forget to download and install the winwin
plugin: http://www.hammerspoon.org/Spoons/WinWin.html.
In init.lua
we should load spoons and require other config files:
hs.loadSpoon("AClock")
hs.loadSpoon("WinWin")
require "modules/config"
require "modules/window"
and let’s create a file named winodw.lua
in modules
folder to save the config about windows management.
In modules/window.lua
spoon.WindowScreenLeftAndRight:bindHotkeys({
screen_left = { hyperShift, "Left" },
screen_right= { hyperShift, "Right" },
})
spoon.WindowHalfsAndThirds:bindHotkeys({
left_half = { hyper, "Left" },
right_half = { hyper, "Right" },
top_half = { hyper, "Up" },
bottom_half = { hyper, "Down" },
})
Thanks to these two awesome spoons, it’s really easy to conifg our window movement.
Press cmd+opiton+[arrowkey]
or cmd+option+shift+[arrowkey]
to move windows in one minitor or around monitors.
I think these exmples are enough for beginners to use hammerspoon, but till now it seems that hammerspoon is just a windows management tool.
So next I will talk about layout, which makes hammerspoon more powerful than other window management tools.
Layouts
Chances are that you are using a MacBookPro with at least one external monitor.
I have two, so let’s first find out their names.
In hammerspoon, insert these script:
> hs.screen.allScreens()[1]:name()
Color LCD
> hs.screen.allScreens()[2].name()
2340
> hs.screen.allScreens()[3].name()
LG ULTRAWIDE
Now we get monitors’ names. We can modify modules/config.lua
file to save them.
-- other config
macbook_monitor = "Color LCD"
main_monitor = "LG ULTRAWIDE"
second_monitor = "2340"
Let’s come back to layout, so what is layout?
When you want to keep serveral apps open all the time, and have their windows arranged in a particular way, you can use the hs.layout extension
With multi-window layouts, you can easily setup your environments.
Let’s say I place my three monitors(including the macbook screen) horizontally: Color LCD on the left, 2340 on the right, and LG ULTRAWIDE in the middle.
And I need three layouts for difference tasks.
Reading layout
There are so many ways to access to information, I want to open them at one time.
left: Emacs middle: Chrome and iBooks right: Email and Telegram
In modules/layout.lua
file:
local reading_layout= {
{"Emacs", nil, macbook_monitor, hs.layout.maximized, nil, nil},
{"Google Chrome", nil, main_monitor, hs.layout.right50, nil, nil},
{"iBooks", nil, main_monitor, hs.layout.left50, nil, nil},
{"Telegram", nil, second_monitor, hs.layout.left50, nil, nil},
{"Mail", nil, second_monitor, hs.layout.right50, nil, nil},
}
hs.hotkey.bind(hyper, '1', function()
hs.application.launchOrFocus('Emacs')
hs.application.launchOrFocus('Google Chrome')
hs.application.launchOrFocus('iBooks')
hs.application.launchOrFocus('Telegram')
hs.application.launchOrFocus('Mail')
hs.layout.apply(reading_layout)
end)
Reload config and press cmd+option+1
, Woo, every window is on it’s place.
Coding layout
When I am working, I need terminal, browser, editor, and database GUI client. Of course I need to communicate with my colleagues (I work remotely).
left: Terminal middle: Emacs and Chrome right: Station(IM) and TablePlus(Database GUI client)
local coding_layout= {
{"Terminal", nil, macbook_monitor, hs.layout.maximized, nil, nil},
{"Google Chrome", nil, main_monitor, hs.layout.left50, nil, nil},
{"Emacs", nil, main_monitor, hs.layout.right50, nil, nil},
{"Station", nil, second_monitor, hs.layout.left50, nil, nil},
{"TablePlus", nil, second_monitor, hs.layout.right50, nil, nil},
}
hs.hotkey.bind(hyper, '2', function()
hs.application.launchOrFocus('Terminal')
hs.application.launchOrFocus('Google Chrome')
hs.application.launchOrFocus('Emacs')
hs.application.launchOrFocus('Station')
hs.application.launchOrFocus('TablePlus')
hs.layout.apply(coding_layout)
end)
Writing layout
Writing posts is difference from writing code, I don’t want to be interrupted and I need music.
left: IINA(Media Player) middle: Emacs right: Chrome
local writing_layout= {
{"Emacs", nil, main_monitor, hs.layout.maximized, nil, nil},
{"Google Chrome", nil, second_monitor, hs.layout.maximized, nil, nil},
{"IINA", nil, macbook_monitor, hs.layout.maximized, nil, nil},
}
hs.hotkey.bind(hyper, '3', function()
hs.application.launchOrFocus('Google Chrome')
hs.application.launchOrFocus('Emacs')
hs.application.launchOrFocus('IINA')
hs.layout.apply(writing_layout)
end)
Reacting events
These part of functions has nothing to do with windows management, they are very useful.
bring all the window front
Application like Finder may have multiple windows on every secreen. With this simple config, when you active any Finder window, all the other windows will be actived too.
function applicationWatcher(appName, eventType, appObject)
if (eventType == hs.application.watcher.activated) then
if (appName == "Finder") then
-- Bring all Finder windows forward when one gets activated
appObject:selectMenuItem({"Window", "Bring All to Front"})
end
end
end
appWatcher = hs.application.watcher.new(applicationWatcher)
appWatcher:start()
connect wifi
I have two routers in my house. In some rooms, the signal of router A is stronger, while in other rooms, router B is better. So I can config like this to make sure I can connect to the stronger one.
bedroomSSID = "MyBedroomNetwork"
studySSID = "MyStudyNetwork"
SSID = hs.wifi.currentNetwork()
hs.hotkey.bind(hyperShift, '9', function()
if SSID ~= bedroomSSID then
hs.wifi.associate(bedroomSSID, "myPassPhrase")
end
end)
hs.hotkey.bind(hyperShift, '8', function()
if SSID ~= studySSID then
hs.wifi.associate(studySSID, "myPassPhrase")
end
end)
caffeinate on the menu bar
I used to type caffeinate -t 99999
to make OS awake. But now I can create a menubar by the following config:
caffeine = hs.menubar.new()
function setCaffeineDisplay(state)
if state then
caffeine:setTitle("AWAKE")
else
caffeine:setTitle("SLEEPY")
end
end
function caffeineClicked()
setCaffeineDisplay(hs.caffeinate.toggle("displayIdle"))
end
if caffeine then
caffeine:setClickCallback(caffeineClicked)
setCaffeineDisplay(hs.caffeinate.get("displayIdle"))
end
Conclusion
Hammerspoon is a powerful tool allowing you to have powerful effects on your system by writing Lua scripts. By the way, you can learn Lua by reading other’s hammerspoon config files.
The more you know about hammerspoon, the more you can control your MacOS.
I have used hammerspoon for a long time, recently I found that the new version of hammerspoon supported Lua plugins called “Spoons”.
So I deceided to rewrite my hammerspoon config file with spoons.
I hope this post can help beginners to get started with hammerspoon (and spoons).