Skip to content

Instantly share code, notes, and snippets.

@mattmattmatt
Last active May 7, 2018 07:24
Show Gist options
  • Save mattmattmatt/21f28d998f10f2135cda83a8feac0813 to your computer and use it in GitHub Desktop.
Save mattmattmatt/21f28d998f10f2135cda83a8feac0813 to your computer and use it in GitHub Desktop.
Hammerspoon automation configuration
hs.alert.show('Hammerspoon config loaded')
hotkeyF3 = hs.hotkey.modal.new('', 'f3')
hotkeyF4 = hs.hotkey.modal.new('', 'f4')
hs.window.animationDuration = 0.1
cachedNumScreens = #hs.screen.allScreens()
hs.dockicon.hide()
kodihost = 'http://192.168.1.140'
-------------------------------
-- Auto-reload functionality --
-------------------------------
function reloadConfig(files)
doReload = false
for _,file in pairs(files) do
if file:sub(-4) == '.lua' then
doReload = true
end
end
if doReload then
hs.reload()
end
end
hs.pathwatcher.new(os.getenv('HOME') .. '/.hammerspoon/', reloadConfig):start()
-------------------
-- Menu bar icon --
-------------------
function setMenuIcon(state, title)
if state then
menuIcon = hs.menubar.new()
menuIcon:setTitle(title)
menuIcon:setTooltip('Hammerspoon Hotkey Mode Active')
else
if menuIcon then
menuIcon:delete()
end
end
end
setMenuIcon(false)
-------------------------------
-- Window size constants --
-------------------------------
local size1 = hs.geometry(0.0, 0.0, 0.5, 0.8)
local size2 = hs.geometry(0.5, 0.0, 0.5, 0.8)
local size3 = hs.geometry(0.0, 0.0, 0.5, 0.7)
local size4 = hs.geometry(0.5, 0.0, 0.5, 0.7)
local size5 = hs.geometry(0.0, 0.0, 0.6, 1.0)
local size6 = hs.geometry(0.4, 0.0, 0.6, 1.0)
local size7 = hs.geometry(0.0, 0.1, 0.5, 0.7)
local size8 = hs.geometry(0.5, 0.1, 0.5, 0.7)
local size9 = hs.geometry(0.45, 0.1, 0.55, 0.7)
local sizeCenter = hs.geometry(0.15, 0.15, 0.7, 0.7)
local function sizeHotkeyUnit(unit, k)
local win = hs.window.focusedWindow()
win:moveToUnit(unit)
if k then
k:exit()
end
end
-------------------------
-- Normal key bindings --
-------------------------
hs.hotkey.bind({'shift', 'ctrl'}, 'f12', function() hs.caffeinate.systemSleep() end)
hs.hotkey.bind({'shift', 'ctrl'}, 'left', function() sizeHotkeyUnit(hs.geometry(0, 0, 0.5, 1)) end)
hs.hotkey.bind({'shift', 'ctrl'}, 'right', function() sizeHotkeyUnit(hs.geometry(0.5, 0, 0.5, 1)) end)
hs.hotkey.bind({'shift', 'ctrl'}, 'up', function() sizeHotkeyUnit(hs.geometry(0, 0, 1, 1)) end)
hs.hotkey.bind({'shift', 'ctrl'}, 'down', function() sizeHotkeyUnit(sizeCenter) end)
hs.hotkey.bind({'alt', 'ctrl'}, 'left', function()
hs.alert.show('Left', 0.5)
local win = hs.window.focusedWindow()
win:moveOneScreenWest(false, true, 0)
end)
hs.hotkey.bind({'alt', 'ctrl'}, 'right', function()
hs.alert.show('Right', 0.5)
local win = hs.window.focusedWindow()
win:moveOneScreenEast(false, true, 0)
end)
for key, name in pairs({
f1 = 'Finder',
f2 = 'Google Chrome',
f3 = 'Atom',
f4 = 'Terminal',
f5 = 'Slack',
f6 = 'Spotify',
f7 = 'System Preferences',
}) do
hs.hotkey.bind({'cmd'}, key, function()
local app = hs.application.get(name)
if not app then
hs.alert.closeAll()
hs.alert.show(name, 0.5)
hs.application.launchOrFocus(name)
elseif not app:isFrontmost() then
hs.alert.closeAll()
hs.alert.show(name, 0.5)
app:activate()
end
end)
end
--[[
hs.hotkey.bind({'alt', 'ctrl'}, 'up', function()
hs.alert.show('Up', 0.5)
local win = hs.window.focusedWindow()
win:moveOneScreenNorth(false, true, 0)
end)
hs.hotkey.bind({'alt', 'ctrl'}, 'down', function()
hs.alert.show('Down', 0.5)
local win = hs.window.focusedWindow()
win:moveOneScreenSouth(false, true, 0)
end)
--]]
------------------
-- Kodi Hotkeys --
------------------
function sendKodiCommand(name, method, params)
hs.alert.show('Kodi ' .. name, 0.6)
local data = [[{
"jsonrpc": "2.0",
"method": "]] .. method .. [[",
"params": ]] .. params .. [[,
"id": 1
}]]
local headers = {
["Content-Type"] = 'application/json'
}
print(hs.http.post(kodihost .. '/jsonrpc', data, headers))
end
function unbindKodiKeys()
hs.hotkey.deleteAll({'ctrl'}, 'f8');
hs.hotkey.deleteAll({'ctrl'}, 'f7');
hs.hotkey.deleteAll({'ctrl'}, 'f9');
hs.hotkey.deleteAll({'ctrl'}, 'f10');
hs.hotkey.deleteAll({'ctrl'}, 'f11');
hs.hotkey.deleteAll({'ctrl'}, 'f12');
hs.hotkey.deleteAll({'ctrl'}, 'left');
hs.hotkey.deleteAll({'ctrl'}, 'right');
hs.hotkey.deleteAll({'ctrl'}, 'up');
hs.hotkey.deleteAll({'ctrl'}, 'down');
hs.hotkey.deleteAll({'ctrl'}, 'return');
hs.hotkey.deleteAll({'ctrl'}, 'delete');
hs.hotkey.deleteAll({'ctrl'}, 'c');
end
function bindKodiKeys()
hs.hotkey.bind({'ctrl'}, 'f8', function() sendKodiCommand('Play/Pause', 'Player.PlayPause', '{"playerid": 0}') end)
hs.hotkey.bind({'ctrl'}, 'f7', function() sendKodiCommand('Previous', 'Player.GoTo', '{"playerid": 0, "to": "previous"}') end)
hs.hotkey.bind({'ctrl'}, 'f9', function() sendKodiCommand('Next', 'Player.GoTo', '{"playerid": 0, "to": "next"}') end)
hs.hotkey.bind({'ctrl'}, 'f10', function() sendKodiCommand('Mute/Unmute', 'Input.ExecuteAction', '{"action": "mute"}') end)
hs.hotkey.bind({'ctrl'}, 'f11', function() sendKodiCommand('Volume Down', 'Input.ExecuteAction', '{"action": "volumedown"}') end, nil, function() sendKodiCommand('Volume Down', 'Input.ExecuteAction', '{"action": "volumedown"}') end)
hs.hotkey.bind({'ctrl'}, 'f12', function() sendKodiCommand('Volume Up', 'Input.ExecuteAction', '{"action": "volumeup"}') end, nil, function() sendKodiCommand('Volume Up', 'Input.ExecuteAction', '{"action": "volumeup"}') end)
hs.hotkey.bind({'ctrl'}, 'left', function() sendKodiCommand('Left', 'Input.ExecuteAction', '{"action": "left"}') end)
hs.hotkey.bind({'ctrl'}, 'right', function() sendKodiCommand('Right', 'Input.ExecuteAction', '{"action": "right"}') end)
hs.hotkey.bind({'ctrl'}, 'up', function() sendKodiCommand('Up', 'Input.ExecuteAction', '{"action": "up"}') end)
hs.hotkey.bind({'ctrl'}, 'down', function() sendKodiCommand('Down', 'Input.ExecuteAction', '{"action": "down"}') end)
hs.hotkey.bind({'ctrl'}, 'return', function() sendKodiCommand('Select', 'Input.ExecuteAction', '{"action": "select"}') end)
hs.hotkey.bind({'ctrl'}, 'delete', function() sendKodiCommand('Back', 'Input.ExecuteAction', '{"action": "back"}') end)
hs.hotkey.bind({'ctrl'}, '+', function() sendKodiCommand('Context', 'Input.ExecuteAction', '{"action": "contextmenu"}') end)
hs.hotkey.bind({'ctrl'}, '´', function() hs.urlevent.openURL(kodihost .. '/#music') end)
end
-- Only bind Kodi keys after Hammerspoon loads if we are at home
if hs.wifi.currentNetwork('en0') == 'Do a flip!' then
bindKodiKeys()
end
-- Bind and unbind Kodi keys depending on wifi network
hs.wifi.watcher.new(function(watcher, message, interface)
print('Connected to wifi:')
print(hs.wifi.currentNetwork(interface))
if hs.wifi.currentNetwork(interface) == 'Do a flip!' then
bindKodiKeys()
else
unbindKodiKeys()
end
end):start()
------------------------------
-- F4 Modal Hotkey for Kodi --
------------------------------
function hotkeyF4:entered()
setMenuIcon(true, 'Kodi')
hs.alert.show('Kodi Active', 1)
end
function hotkeyF4:exited()
setMenuIcon(false)
hs.alert.closeAll()
end
hotkeyF4:bind('', 'escape', function() hotkeyF4:exit() end)
hotkeyF4:bind('', 'f3', function() hotkeyF4:exit() hotkeyF3:enter() end)
hotkeyF4:bind('', 'f4', function() hotkeyF4:exit() end)
hotkeyF4:bind('', 'f8', function() sendKodiCommand('Play/Pause', 'Player.PlayPause', '{"playerid": 0}') end)
hotkeyF4:bind('', 'f7', function() sendKodiCommand('Previous', 'Player.GoTo', '{"playerid": 0, "to": "previous"}') end)
hotkeyF4:bind('', 'f9', function() sendKodiCommand('Next', 'Player.GoTo', '{"playerid": 0, "to": "next"}') end)
hotkeyF4:bind('', 'f10', function() sendKodiCommand('Mute/Unmute', 'Input.ExecuteAction', '{"action": "mute"}') end)
hotkeyF4:bind('', 'f11', function() sendKodiCommand('Volume Down', 'Input.ExecuteAction', '{"action": "volumedown"}') end, nil, function() sendKodiCommand('Volume Down', 'Input.ExecuteAction', '{"action": "volumedown"}') end)
hotkeyF4:bind('', 'f12', function() sendKodiCommand('Volume Up', 'Input.ExecuteAction', '{"action": "volumeup"}') end, nil, function() sendKodiCommand('Volume Up', 'Input.ExecuteAction', '{"action": "volumeup"}') end)
hotkeyF4:bind('', 'left', function() sendKodiCommand('Left', 'Input.ExecuteAction', '{"action": "left"}') end)
hotkeyF4:bind('', 'right', function() sendKodiCommand('Right', 'Input.ExecuteAction', '{"action": "right"}') end)
hotkeyF4:bind('', 'up', function() sendKodiCommand('Up', 'Input.ExecuteAction', '{"action": "up"}') end)
hotkeyF4:bind('', 'down', function() sendKodiCommand('Down', 'Input.ExecuteAction', '{"action": "down"}') end)
hotkeyF4:bind('', 'return', function() sendKodiCommand('Select', 'Input.ExecuteAction', '{"action": "select"}') end)
hotkeyF4:bind('', 'delete', function() sendKodiCommand('Back', 'Input.ExecuteAction', '{"action": "back"}') end)
hotkeyF4:bind('', '+', function() sendKodiCommand('Back', 'Input.ExecuteAction', '{"action": "contextmenu"}') end)
hotkeyF4:bind('', 'c', function() sendKodiCommand('Back', 'Input.ExecuteAction', '{"action": "contextmenu"}') end)
hotkeyF4:bind('', '^', function() hs.alert.show('Toggling TV', 0.6) hs.http.get('http://pi.my.home:1880/bridge?topic=events/tv&value=2') end)
---------------------
-- F3 Modal Hotkey --
---------------------
function hotkeyF3:entered()
setMenuIcon(true, 'F3')
hs.alert.show('F3 Active', 1)
end
function hotkeyF3:exited()
setMenuIcon(false)
hs.alert.closeAll()
end
hotkeyF3:bind('', 'escape', function() hotkeyF3:exit() end)
hotkeyF3:bind('', 'f3', function() hotkeyF3:exit() end)
hotkeyF3:bind('', 'f4', function() hotkeyF3:exit() hotkeyF4:enter() end)
hotkeyF3:bind('', 'pad1', function() sizeHotkeyUnit(size1, hotkeyF3) end)
hotkeyF3:bind('', 'pad2', function() sizeHotkeyUnit(size2, hotkeyF3) end)
hotkeyF3:bind('', 'pad3', function() sizeHotkeyUnit(size3, hotkeyF3) end)
hotkeyF3:bind('', 'pad4', function() sizeHotkeyUnit(size4, hotkeyF3) end)
hotkeyF3:bind('', 'pad5', function() sizeHotkeyUnit(size5, hotkeyF3) end)
hotkeyF3:bind('', 'pad6', function() sizeHotkeyUnit(size6, hotkeyF3) end)
hotkeyF3:bind('', 'pad7', function() sizeHotkeyUnit(size7, hotkeyF3) end)
hotkeyF3:bind('', 'pad8', function() sizeHotkeyUnit(size8, hotkeyF3) end)
hotkeyF3:bind('', 'pad9', function() sizeHotkeyUnit(size9, hotkeyF3) end)
hotkeyF3:bind('', '1', function() sizeHotkeyUnit(size1, hotkeyF3) end)
hotkeyF3:bind('', '2', function() sizeHotkeyUnit(size2, hotkeyF3) end)
hotkeyF3:bind('', '3', function() sizeHotkeyUnit(size3, hotkeyF3) end)
hotkeyF3:bind('', '4', function() sizeHotkeyUnit(size4, hotkeyF3) end)
hotkeyF3:bind('', '5', function() sizeHotkeyUnit(size5, hotkeyF3) end)
hotkeyF3:bind('', '6', function() sizeHotkeyUnit(size6, hotkeyF3) end)
hotkeyF3:bind('', '7', function() sizeHotkeyUnit(size7, hotkeyF3) end)
hotkeyF3:bind('', '8', function() sizeHotkeyUnit(size8, hotkeyF3) end)
hotkeyF3:bind('', '9', function() sizeHotkeyUnit(size9, hotkeyF3) end)
-----------------------------------
-- Display Layout Configurations --
-----------------------------------
local laptopScreen = 'Color LCD'
local externalScreen = 'Thunderbolt Display'
local doubleLayout = {
{'Atom', nil, externalScreen, size5, nil, nil},
{'Brackets', nil, externalScreen, sizeCenter, nil, nil},
{nil, 'Inbox – mb@issuu.com', externalScreen, size8, nil, nil},
{nil, 'Issuu.com.*Calendar.*', externalScreen, size4, nil, nil},
{'Slack', nil, externalScreen, size3, nil, nil},
{'Spotify', nil, externalScreen, size9, nil, nil},
{'Terminal', nil, laptopScreen, hs.layout.maximized, nil, nil},
{'Google Chrome', nil, externalScreen, size1, nil, nil},
{nil, '.*Developer.*Tools.*', externalScreen, hs.layout.right50, nil, nil},
}
local laptopLayout = {
{nil, 'Issuu.com.*Calendar.*', laptopScreen, hs.layout.left50, nil, nil},
{'Atom', nil, laptopScreen, hs.layout.left75, nil, nil},
{'Google Chrome', nil, laptopScreen, hs.layout.maximized, nil, nil},
{nil, '.*Developer.*Tools.*', laptopScreen, hs.layout.right70, nil, nil},
{'Terminal', nil, laptopScreen, hs.layout.right50, nil, nil},
{'Brackets', nil, externalScreen, sizeCenter, nil, nil},
{'Slack', nil, externalScreen, sizeCenter, nil, nil},
}
local function windowTitleComparator(windowTitle, searchPattern)
-- print(string.match(windowTitle, searchPattern) ~= nil, windowTitle, searchPattern)
return string.match(windowTitle, searchPattern) ~= nil
end
function onScreenChanged(forceRelayout)
hs.alert.show('Screen change detected')
-- avoid relayout if the number of screens didn't change (e.g. wakeup from sleep)
print(cachedNumScreens .. ' - ' .. #hs.screen.allScreens())
if true or cachedNumScreens ~= #hs.screen.allScreens() or forceRelayout then
cachedNumScreens = #hs.screen.allScreens()
if #hs.screen.allScreens() == 2 then
hs.layout.apply(doubleLayout, windowTitleComparator)
elseif #hs.screen.allScreens() == 1 then
hs.layout.apply(laptopLayout, windowTitleComparator)
end
end
end
-- local screenWatcher = hs.screen.watcher.new(onScreenChanged)
-- screenWatcher:start()
hs.hotkey.bind({'ctrl'}, 'f3', function() onScreenChanged(true) end)
hs.hotkey.bind({'cmd'}, 'f9', function()
print('\n\n-- Applications --')
hs.fnutils.each(hs.application.runningApplications(), function(app) print(app:title()) end)
print('\n\n-- Window Titles --')
hs.fnutils.each(hs.window.visibleWindows(), function(app) print(app:title()) end)
end)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment