Files
dotfiles/.hammerspoon/init.lua

288 lines
7.8 KiB
Lua

-- init.lua
-- Richard Zhao <richardz@andrew.cmu.edu>
-- modified by Aaron Gutierrez
local log = hs.logger.new('init', 'debug')
log.i('initializing')
--------------------------------------------------------------------------------
-- General ---------------------------------------------------------------------
local hyper = {'cmd', 'shift'}
local hotkey = require 'hs.hotkey'
hs.alert.defaultStyle.radius = 2
hs.alert.defaultStyle.textSize = 22
hs.alert.defaultStyle.textFont = 'Menlo'
hs.alert.defaultStyle.textColor = { white = 1, alpha = 0.95 }
hs.alert.defaultStyle.strokeColor = { white = 1, alpha = 0 }
hs.alert.defaultStyle.fillColor = { white = 0.2, alpha = 0.9 }
-- Configuration reload
hotkey.bind(hyper, 'r', function ()
hs.reload()
end)
hs.alert.show('Config loaded')
--------------------------------------------------------------------------------
-- Scripts ---------------------------------------------------------------------
function newTerm()
local iterm = [[ tell application "iTerm"
create window with default profile
end tell
]]
hs.osascript.applescript(iterm)
end
-- Open a new iTerm window
hotkey.bind(hyper, 't', function () newTerm() end)
local screen = require 'hs.screen'
local window = require 'hs.window'
local geom = require 'hs.geometry'
function box ()
return screen.primaryScreen():currentMode()
end
--------------------------------------------------------------------------------
-- Drawing ---------------------------------------------------------------------
local drawing = require 'hs.drawing'
local timer = require 'hs.timer'
local hints = {
font = 'Helvetica Neue',
size = 36,
width = 200,
duration = 2,
fadeIn = 0.2,
fadeOut = 0.3,
}
local colors = {
orange = { red = 214 / 255, green = 93 / 255, blue = 14 / 255, alpha = 0.9 },
green = { red = 152 / 255, green = 151 / 255, blue = 26 / 255, alpha = 0.9 },
blue = { red = 69 / 255, green = 133 / 255, blue = 136 / 255, alpha = 0.9 },
grey = { red = 0, green = 0, blue = 0, alpha = 0.7 }
}
-- Create a circle centered at (x, y) with radius r.
function hints.circle (x, y, r, color)
local c = drawing.circle(geom(x - r, y - r, 2 * r, 2 * r))
c:setStroke(false)
c:setFillColor(color)
return c
end
function hints.background ()
local b = box ()
local rect = drawing.rectangle(geom(0, 0, b.w, b.h))
:setStroke(false)
:setFillColor(colors.grey)
return {
show = function ()
rect:show(hints.fadeIn)
end,
hide = function ()
rect:hide(hints.fadeOut)
end
}
end
function hints.hint (x, y, key, msg, color)
local radius = hints.size
local circle = hints.circle(x + radius, y + radius, radius, color)
local keyBox = hs.drawing.getTextDrawingSize(key, {
font = hints.font, size = hints.size, alignment = 'center',
})
keyBox.w = keyBox.w + 3 -- known fudge factor (see getTextDrawingSize docs)
local left = x + radius - keyBox.w / 2
local top = y + radius - keyBox.h / 2
local keyText = hs.drawing.text(geom(left, top, keyBox.w, keyBox.h), key)
:setTextFont(hints.font)
:setTextSize(hints.size)
local msgBox = hs.drawing.getTextDrawingSize(msg, {
font = hints.font, size = hints.size, alignment = 'left',
})
msgBox.w = msgBox.w + hints.size
if msgBox.w > hints.width then
log.i('width ' .. msgBox.w .. ' of hint ' .. msg .. ' too large')
end
local msgText = hs.drawing.text(geom(left + hints.size * 2, top, hints.width, msgBox.h), msg)
:setTextFont(hints.font)
:setTextSize(hints.size)
return {
show = function ()
circle:show(hints.fadeIn)
keyText:show(hints.fadeIn)
msgText:show(hints.fadeIn)
end,
hide = function ()
circle:hide(hints.fadeOut)
keyText:hide(hints.fadeOut)
msgText:hide(hints.fadeOut)
end
}
end
function webApp(app)
local web = [[
do shell script "/Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome --app=\"APP\""
]]
hs.osascript.applescript(string.gsub(web, "APP", app))
end
--------------------------------------------------------------------------------
-- Hydra -----------------------------------------------------------------------
local hydraExitKey = 'escape'
local hydraDefinitions = {
{
mods = hyper,
key = 'space',
hint = 'head',
master = true,
actions = {
{ mod = '', key = 'A', hint = 'Asana', target = function() webApp('https://app.asana.com') end, exit = true },
{ mod = '', key = 'M', hint = 'Messenger', target = function() webApp('https://messenger.com') end, exit = true },
{ mod = '', key = 'S', hint = 'Slack', target = function() hs.application.launchOrFocus('Slack') end, exit = true },
{ mod = '', key = 'T', hint = 'Terminal', target = function() newTerm() end, exit = true },
},
hydras = {}
}
}
function tableLen(T)
local count = 0
for k, v in pairs(T) do
count = count + 1
end
return count
end
function hydraGenerateHints (hydra)
local b = box ()
local hintTable = {}
local numHints = tableLen(hydra.hydras) + tableLen(hydra.actions)
local height = hints.size * 2 + hints.size / 2
local totalHeight = numHints * height
local left = b.w / 2 - hints.width / 2
local top = b.h / 2 - totalHeight / 2
local y = top
-- generate child hydras first
for _, child in pairs(hydra.hydras) do
local hint = hints.hint(left, y, child.key, child.hint, colors.orange)
table.insert(hintTable, hint)
y = y + height
end
-- and then actions
for _, child in pairs(hydra.actions) do
local hint
if child.exit then
hint = hints.hint(left, y, child.key, child.hint, colors.blue)
else
hint = hints.hint(left, y, child.key, child.hint, colors.green)
end
table.insert(hintTable, hint)
y = y + height
end
return {
show = function ()
for _, hint in pairs(hintTable) do
hint.show()
end
end,
hide = function ()
for _, hint in pairs(hintTable) do
hint.hide()
end
end
}
end
function hydraExitAllAndHideHints ()
recurse = function (hydras)
for _, hydra in pairs(hydras) do
if hydra.master ~= nil and hydra.master == true then
hydra.background.hide()
end
hydra.modal:exit()
hydra.hints.hide()
if next(hydra.hydras) ~= nil then
recurse(hydra.hydras)
end
end
end
recurse(hydraDefinitions)
end
function hydraInit (parentHydra, hydras)
if next(hydras) == nil then return end
-- add modals to each body and bind all actions
for _, hydra in pairs(hydras) do
-- generate a new modal and put it under its parent
hydra.modal = hotkey.modal.new()
hydra.modal:bind('', hydraExitKey, function() hydraExitAllAndHideHints () end)
-- global bind of master
if hydra.master ~= nil and hydra.master == true then
hydra.background = hints.background ()
hotkey.bind(hydra.mods, hydra.key, function ()
hydra.background.show()
hydra.modal:enter()
end)
else
parentHydra.modal:bind(hydra.mods, hydra.key, function()
parentHydra.modal:exit()
hydra.modal:enter()
end)
end
-- add the hint drawing objects
hydra.hints = hydraGenerateHints(hydra)
-- for debugging
function hydra.modal:entered()
hydra.hints.show()
log.i("hydra [" .. hydra.hint .. "] entered")
end
function hydra.modal:exited()
hydra.hints.hide()
log.i("hydra [" .. hydra.hint .. "] exited")
end
-- add actions
for _, action in ipairs(hydra.actions) do
hydra.modal:bind(action.mod, action.key, function ()
action.target ()
if action.exit then hydraExitAllAndHideHints () end
end)
log.i("hydra [" .. hydra.hint .. "] binding action " .. action.hint)
end
-- recurse on children
hydraInit (hydra, hydra.hydras)
end
end
-- Create all hydras under the head
hydraInit (nil, hydraDefinitions)