Tag Archives: Lua

Hammerspoon: one Swiss Army Knife on macOS

I may believe that most computers contain unique customizations, which may be why most people hate reinstalling the operating systems or setting up a new computer.

Such as my laptop, there are a bunch of tools that improve my experience using a computer. Here is a non-exhaustive list:

AppUsage
SizeUpCan easily attach the windows to the sides or corners of the screen. If the screen is big enough, you may put the documentation on the left and IDE on the right.
Pap.erIt can periodically and automatically set a beautiful sightseeing image as your wallpaper.
StretchlyTimely covers your screen to make you have a rest.
Lexico.comThe official site of the Oxford dictionary.
CheatsheetIt can show you the list of the selected application’s hotkeys.
New Terminal HereOpen a terminal with the current folder path.
One non-exhaustive list of my favorite tools

Besides improving my experience in using a computer, these tools also cause a few inconveniences, especially when I need to reinstall the OS and set up a new laptop. I have to look for an alternative because sometimes one is no longer free of charge, or sometimes one no longer supports the latest OS. This didn’t bother me much until I changed my Macbook Pro thrice in half a year. So I tried to find a way to minimize the number of tools I relied on, and I found Hammerspoon. I can say this is really a Swiss Army Knife on macOS.

Actually, there are some similar tools, but Hammerspoon finds the best balance between usage simplicity and functionality complexity. The users only need a little knowledge of Lua before customizing with Hammerspoon. Under the situation of no experience in Lua, I quickly finished the customizing based on the official documentation.

We can customize with the official spoons, such as windows manipulation.

-- We can quickly implement this with the WinWin spoon hs.loadSpoon("WinWin") -- Check whether WinWin is loaded and bind hotkeys to different directions. if spoon.WinWin then -- Supporting symbol characters in the message of Hammerspoon is quite helpful. We will talk about this later. I will talk about this later. -- Side hs.hotkey.bind({"cmd", "alt", "ctrl"}, "left", "Window ⬅", function() spoon.WinWin:moveAndResize("halfleft") end) hs.hotkey.bind({"cmd", "alt", "ctrl"}, "right", "Window ➡", function() spoon.WinWin:moveAndResize("halfright") end) hs.hotkey.bind({"cmd", "alt", "ctrl"}, "up", "Window ⬆", function() spoon.WinWin:moveAndResize("halfup") end) hs.hotkey.bind({"cmd", "alt", "ctrl"}, "down", "Window ⬇", function() spoon.WinWin:moveAndResize("halfdown") end) -- Corner hs.hotkey.bind({"shift", "alt", "ctrl"}, "left", "Window ↖", function() spoon.WinWin:moveAndResize("cornerNW") end) hs.hotkey.bind({"shift", "alt", "ctrl"}, "right", "Window ↘", function() spoon.WinWin:moveAndResize("cornerSE") end) hs.hotkey.bind({"shift", "alt", "ctrl"}, "up", "Window ↗", function() spoon.WinWin:moveAndResize("cornerNE") end) hs.hotkey.bind({"shift", "alt", "ctrl"}, "down", "Window ↙", function() spoon.WinWin:moveAndResize("cornerSW") end) -- Stretch hs.hotkey.bind({"cmd", "alt", "ctrl"}, "C", "Window Center", function() spoon.WinWin:moveAndResize("center") end) hs.hotkey.bind({"cmd", "alt", "ctrl"}, "M", "Window ↕↔", function() spoon.WinWin:moveAndResize("maximize") end) -- Screen hs.hotkey.bind({"alt", "ctrl"}, "right", "Window ➡ 🖥", function() spoon.WinWin:moveToScreen("next") end) -- Other hs.hotkey.bind({"cmd", "alt", "ctrl"}, "/", "Window Undo", function() spoon.WinWin:undo() end) end
Code language: PHP (php)

It is pretty easy, right? Only several hotkey bindings to functionality are enough.

We can also create spoons with the API, such as BreakTime.

To implement this, we need to periodically use a page or image to cover the whole screen to make the guys in front of the PC take a rest.

Firstly, we can quickly start a timer with hs.timer:

obj.Timer = hs.timer.new(60, refresh) obj.Timer:start()
Code language: PHP (php)

Then, we can create a page covering the whole screen with hs.webview. Using a transparent image to make the page translucent is also not bad.

function makeBrowserOfBreakTime () local screen = require"hs.screen" local webview = require"hs.webview" local mainScreenFrame = screen:primaryScreen():frame() browserFrame = { x = mainScreenFrame.x, y = mainScreenFrame.y, h = mainScreenFrame.h, w = mainScreenFrame.w } local options = { developerExtrasEnabled = true, } -- local browser = webview.new(browserFrame, options):windowStyle(1+2+4+8) local browser = webview.new(browserFrame, options):windowStyle(1+2+128) :closeOnEscape(true) :deleteOnClose(true) :bringToFront(true) :allowTextEntry(true) :transparent(true) return browser end
Code language: PHP (php)

Next, we can show and remove the cover with browser:show() and browser:delete() periodically with the timer.

function refresh() obj.curTime = obj.curTime + 1 if obj.curTime > obj.microbreakInterval then obj.curMicrobreakCount = obj.curMicrobreakCount + 1 if obj.curMicrobreakCount > obj.microbreakCount then hs.alert.show(obj.breakTime .. " minute break starts") local browser = makeBrowserOfBreakTime(); browser:url("file://" .. hs.spoons.scriptPath() .. "BreakTime.html?time=" .. (obj.breakTime * 60 - 1)):show() hs.timer.doAfter(obj.breakTime * 60, function() if browser ~= nil then browser:delete(); end end) obj.curMicrobreakCount = 0 else hs.alert.show(obj.microbreakTime .. " second microbreak starts") local browser = makeBrowserOfBreakTime(); browser:url("file://" .. hs.spoons.scriptPath() .. "BreakTime.html?time=" .. (obj.microbreakTime - 1)):show() hs.timer.doAfter(obj.microbreakTime, function() if browser ~= nil then browser:delete(); end end) end obj.curTime = obj.curTime - obj.microbreakInterval end end
Code language: JavaScript (javascript)

Load the BreakTime spoon in the same way to load the WinWin spoon, and start it. Done!

We can also customize the taskbar menu to improve the user experience.

Take BreakTime as an example. We can show the timeline of the next break.

BreakTime's Menu
BreakTime’s Menu

Besides this, we can also show all the hotkeys defined with Hammerspoon.

Menu showing all the hotkeys
Menu showing all the hotkeys

Hammerspoon supports symbol characters quite well, so users can make the menu items more attractive. Isn’t this amazing? 😄

obj.menubar:setTitle("⌨️") obj.menubar:setTooltip("Hot Key Info") local hotkeys = hs.hotkey.getHotkeys() local menuItem = {} for key, value in pairs(hotkeys) do local item = { title = value["msg"] } table.insert(menuItem, item) end obj.menubar:setMenu(menuItem)
Code language: JavaScript (javascript)

Besides all the above, Hammerspoon still has much that needs going through. I am also keeping going through to make my customization more additional. Here’s mine.

BTW, not only reinstalling these tools, but I also need to reinstall all of the common applications such as VS Code, Plex, Sublime Text, VIM, etc. This is also terrible. Now I am maintaining a tap list containing the applications with homebrew. 😂