This is a Hammerspoon Spoon that contains useful modules written by Akinori Musha a.k.a. @knu.
- Hammerspoon 0.9.53 or later
Put the following snippet in your ~/.hammerspoon/init.lua
:
if hs.fs.attributes("Spoons/Knu.spoon") == nil then
hs.execute("mkdir -p Spoons; curl -L https://github.com/knu/Knu.Spoon/raw/release/Spoons/Knu.spoon.zip | tar xf - -C Spoons/")
end
Alternatively, you can use SpoonInstall like so:
if hs.fs.attributes("Spoons/SpoonInstall.spoon") == nil then
hs.execute("mkdir -p Spoons; curl -L https://github.com/Hammerspoon/Spoons/raw/master/Spoons/SpoonInstall.spoon.zip | tar xf - -C Spoons/")
end
hs.loadSpoon("SpoonInstall")
spoon.SpoonInstall.repos.Knu = {
url = "https://github.com/knu/Knu.spoon",
desc = "Knu.spoon repository",
branch = "release",
}
spoon.SpoonInstall.use_syncinstall = true
spoon.SpoonInstall:andUse("Knu", { repo = "Knu" })
After that, you can load Knu.spoon.
knu = hs.loadSpoon("Knu")
-- Enable auto-restart when any of the *.lua files under ~/.hammerspoon/ is modified
knu.runtime.autorestart(true)
knu.photkey.bind({"fn"}, "l", function ()
-- Wait a second to skip an inevitable key-up event
hs.timer.doAfter(1.0, function () hs.application.launchOrFocus("ScreenSaverEngine") end)
end)
knu.photkey.bind({"leftshift", "rightshift"}, "l", function ()
-- This is invoked with both shift keys down + L
hs.eventtap.keyStrokes("LGTM!\n")
end)
knu.photkey.bind({"rightcmd", "shift"}, "q", function ()
-- This is invoked with right command + shift (left or right) + Q
hs.execute(("kill %d"):format(hs.application.frontmostApplication():pid()))
end)
function inputEmoji()
local window = hs.window.focusedWindow()
knu.emoji.chooser(function (chars)
window:focus()
if chars then
local appId = hs.application.frontmostApplication():bundleID()
if appId == "com.twitter.TweetDeck" then
-- TweetDeck does not restore focus on text field, so just copy and notify user
hs.pasteboard.setContents(chars)
hs.alert.show("Copied! " .. chars)
else
-- knu.keyboard.send uses an appropriate method for the frontmost application to send a text
-- knu.keyboard.paste pastes a string to the frontmost application, which is specified as a fallback function here
knu.keyboard.send(chars, knu.keyboard.paste)
end
end
end):show()
end
-- Speed up the first invocation
knu.emoji.preload()
-- Function to guard a given object from GC
guard = knu.runtime.guard
--- z+x+c opens the emoji chooser to input an emoji to the frontmost window
guard(knu.chord.bind({}, {"z", "x", "c"}, inputEmoji))
function withRepeat(fn)
return fn, nil, fn
end
-- Define some bindings for specific applications
local keymapForQt = knu.keymap.new(
hs.hotkey.new({"ctrl"}, "h", withRepeat(function ()
hs.eventtap.keyStroke({}, "delete", 0)
end)),
hs.hotkey.new({"ctrl"}, "k", withRepeat(function ()
hs.eventtap.keyStroke({"ctrl", "shift"}, "e", 0)
hs.eventtap.keyStroke({"cmd"}, "x", 0)
end)),
hs.hotkey.new({"ctrl"}, "n", withRepeat(function ()
hs.eventtap.keyStroke({}, "down", 0)
end)),
hs.hotkey.new({"ctrl"}, "p", withRepeat(function ()
hs.eventtap.keyStroke({}, "up", 0)
end))
)
knu.keymap.register("org.keepassx.keepassxc", keymapForQt)
knu.keymap.register("jp.naver.line.mac", keymapForQt)
-- F18 toggles IM between Japanese <-> Roman
do
local eisu = hs.hotkey.new({}, "f18", function ()
hs.eventtap.keyStroke({}, "eisu", 0)
end)
local kana = hs.hotkey.new({}, "f18", function ()
hs.eventtap.keyStroke({}, "kana", 0)
end)
knu.keyboard.onChange(function ()
knu.keyboard.showCurrentInputMode()
if knu.keyboard.isJapaneseMode() then
kana:disable()
eisu:enable()
else
kana:enable()
eisu:disable()
end
end)
end
-- knu.utils.shelljoin() is a method to build a command line from a list of arguments
-- with shell meta characters properly escaped
local extra_options = { "--exclude", ".*" }
hs.execute(knu.utils.shelljoin(
"rsync",
"-a",
extra_options, -- no need for table.unpack()
src,
dest
))
-- Switch the Karabiner-Elements profile when an external keyboard is attached or detached
knu.usb.onChange(function (device)
local name = device.productName
if name and (
name:find("PS2") or -- PS/2-USB converter
(not device.vendorName:find("^Apple") and name:find("Keyboard"))
) then
if device.eventType == "added" then
knu.keyboard.switchKarabinerProfile("External")
else
knu.keyboard.switchKarabinerProfile("Default")
end
end
end)
-- Enable hotkey for launching an app only while not running
function launchByHotkeyWhileNotRunning(mods, key, bundleID)
local hotkey = hs.hotkey.new(mods, key, function ()
hs.application.open(bundleID)
end)
-- Writing an application watcher for a specific application made
-- easy by knu.application.onChange()
knu.application.onChange(bundleID, function (name, type, app)
if type == hs.application.watcher.launched then
hotkey:disable()
elseif type == hs.application.watcher.terminated then
hotkey:enable()
end
end, true)
end
-- Suppose you have set up system-wide hotkeys for activating apps in
-- their preferences, but also want to launch them if they are not
-- running.
launchByHotkeyWhileNotRunning({"alt", "ctrl"}, "/", "com.kapeli.dashdoc")
launchByHotkeyWhileNotRunning({"alt", "ctrl"}, "t", "com.googlecode.iterm2")
-- Refresh calendars every 15 minutes
hs.timer.doEvery(15 * 60, function ()
knu.application.launchInBackground("com.apple.iCal", function (app)
if app ~= nil then
app:selectMenuItem({"View", "Refresh Calendars"})
-- Depending on your language settings...
-- app:selectMenuItem({"表示", "カレンダーを更新"})
end
end, 10, true)
end)
-- Scroll by moving the mouse while holding the middle button
knu.mouse.holdToScrollButton():enable()
url, error = knu.http.unshortenUrl(originalURL)
if error ~= nil then
logger.e("Error in unshortening a URL " .. originalURL .. ": " .. error)
end
-- Use url
-
application.lua: application change watcher and advanced application launcher functions
-
chord.lua: key chord implementation (
SimultaneousKeyPress
in Karabiner) -
emoji.lua: emoji database and chooser
-
http.lua: functions to manipulate URLs
-
keyboard.lua: functions to handle input source switching
-
keymap.lua: application/window based keymap switching
-
mouse.lua: functions to handle mouse events
-
photkey.lua: pseudo hotkeys with extended modifiers support
-
runtime.lua: functions like restart() and guard() (from GC)
-
usb.lua: wrapper for hs.usb.watcher
-
utils.lua: common utility functions
Copyright (c) 2017-2023 Akinori MUSHA
Licensed under the 2-clause BSD license. See LICENSE
for details.
Visit GitHub Repository for the latest information.