summaryrefslogtreecommitdiff
path: root/config/hammerspoon/dotlib.lua
diff options
context:
space:
mode:
Diffstat (limited to 'config/hammerspoon/dotlib.lua')
-rw-r--r--config/hammerspoon/dotlib.lua139
1 files changed, 139 insertions, 0 deletions
diff --git a/config/hammerspoon/dotlib.lua b/config/hammerspoon/dotlib.lua
new file mode 100644
index 0000000..15fe0b2
--- /dev/null
+++ b/config/hammerspoon/dotlib.lua
@@ -0,0 +1,139 @@
+-- Helpers shared by init.lua and a machine's own ~/.config/hammerspoon/
+-- local.lua, which reach them with `require("dotlib")`.
+--
+-- They used to be locals of init.lua, which meant a local.lua -- a separate
+-- chunk -- could not see them and had to copy whatever it needed. Hammerspoon
+-- puts the config directory first on package.path (its Contents/Resources/
+-- setup.lua), and init.lua prepends it again from its own location, so this
+-- file is found wherever the config is linked.
+--
+-- link/hammerspoon.sh links this file next to init.lua. Anything added here is
+-- part of what a local.lua may call, so keep the names stable.
+
+local M = {}
+
+-- Bind a key. Argument order is the one init.lua has always used: key first,
+-- then the modifier table, then the function.
+function M.hotkey(key, mods, fn)
+ hs.hotkey.bind(mods, key, fn)
+end
+
+-- The modifier combinations these configs bind against.
+M.mods = {
+ cs = { "ctrl", "shift" },
+ ca = { "ctrl", "alt" },
+ csa = { "ctrl", "shift", "alt" },
+ cag = { "ctrl", "alt", "cmd" },
+ hyp = { "ctrl", "alt", "cmd", "shift" },
+ spc = "space",
+}
+
+-- Focus an app. Given several names, the first one installed wins, so one
+-- binding can cover machines that have different apps for the same job.
+function M.focus(...)
+ local appNames = { ... }
+ local selectedAppName = nil
+
+ -- If only one argument is provided, use it directly without searching
+ if #appNames == 1 then
+ selectedAppName = appNames[1]
+ else
+ -- For multiple arguments, find the first app that exists on disk
+ local appDirs = { "/Applications/", os.getenv("HOME") .. "/Applications/" }
+ for _, appName in ipairs(appNames) do
+ for _, dir in ipairs(appDirs) do
+ local path = dir .. appName .. ".app"
+ if hs.fs.attributes(path) then
+ selectedAppName = appName
+ break
+ end
+ end
+ if selectedAppName then break end
+ end
+ if not selectedAppName then
+ print("No apps found: " .. table.concat(appNames, ", "))
+ end
+ end
+
+ return function()
+ if selectedAppName then
+ print('Opening ' .. selectedAppName)
+ hs.application.launchOrFocus(selectedAppName)
+ else
+ print("No apps found: " .. table.concat(appNames, ", "))
+ end
+ end
+end
+
+-- Run a shell command.
+function M.execute(cmd)
+ return function()
+ hs.execute(cmd, true)
+ end
+end
+
+-- Type today's date.
+function M.type_current_date()
+ local clock = os.date('%Y-%m-%d')
+ hs.eventtap.keyStrokes(clock)
+end
+
+-- Type ids from random-id.ts, through a login shell so it is on PATH.
+function M.type_random_ids(count)
+ local cmd = string.format('/bin/bash --login -c "random-id.ts %s"', count)
+ return function()
+ local out = hs.execute(cmd, true)
+ if count == 1 then
+ out = out:gsub("%s+$", "")
+ end
+ hs.eventtap.keyStrokes(out)
+ end
+end
+
+-- Type a string. With min_delay/max_delay it is typed character by character,
+-- at a randomised pace, for a screencast; without, all at once.
+function M.type_text(text, options)
+ return function()
+ if options and (options.min_delay or options.max_delay) then
+ -- Type with delays between characters
+ local min_delay = options.min_delay or 0
+ local max_delay = options.max_delay or min_delay
+
+ local chars = {}
+ for char in text:gmatch(".") do
+ table.insert(chars, char)
+ end
+
+ local function typeNextChar(index)
+ if index > #chars then return end
+
+ -- Handle newlines specially by sending return key
+ if chars[index] == "\n" then
+ hs.eventtap.keyStroke({}, "return")
+ else
+ hs.eventtap.keyStrokes(chars[index])
+ end
+
+ if index < #chars then
+ local delay = min_delay
+ if max_delay > min_delay then
+ -- Use squared random for skewed distribution
+ -- Most delays will be near min_delay, higher delays are rarer
+ local rand_squared = math.random() ^ 2
+ delay = min_delay + rand_squared * (max_delay - min_delay)
+ end
+ hs.timer.doAfter(delay, function()
+ typeNextChar(index + 1)
+ end)
+ end
+ end
+
+ typeNextChar(1)
+ else
+ -- Fast typing without delays
+ hs.eventtap.keyStrokes(text)
+ end
+ end
+end
+
+return M