Skip to content

Instantly share code, notes, and snippets.

@luosky
Last active December 20, 2021 14:20
Show Gist options
  • Save luosky/61b2d14e6ce5148489a93e38dbb4a3ef to your computer and use it in GitHub Desktop.
Save luosky/61b2d14e6ce5148489a93e38dbb4a3ef to your computer and use it in GitHub Desktop.
Hammerspoon config
local alert = require("hs.alert") -- http://www.hammerspoon.org/docs/hs.alert.html
local pasteboard = require("hs.pasteboard") -- http://www.hammerspoon.org/docs/hs.pasteboard.html
local settings = require("hs.settings") -- http://www.hammerspoon.org/docs/hs.settings.html
-- http://www.hammerspoon.org/docs/hs.chooser.html
------------------------
-- default config
------------------------
-- screen name can use hs.screen.allScreens()[1]:name() to see
local laptop = "Color LCD"
local mainDesktop = "DELL U2412M"
local vDesktop = "DELL U2312HM"
local hyper = {'cmd','alt','shift','ctrl'}
hs.grid.setGrid('4x4','1920x1200')
hs.window.animationDuration = 0
-- hs.alert(hs.screen.allScreens()[3])
---------------------------
-- reload config
---------------------------
-- ofun = function()
-- hs.reload()
-- hs.alert.show("Config loaded")
-- k.triggered = true
-- end
-- k:bind({}, '/', nil, ofun)
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
local myWatcher = hs.pathwatcher.new(os.getenv("HOME") .. "/.hammerspoon/", reloadConfig):start()
hs.alert.show("HammperSpoon Config loaded")
-- hs.notify.new({
-- title='Hammerspoon',
-- informativeText='Config loaded'
-- }):send()
----------------
-- Screen Layout
----------------
function autoLayout()
local screens = hs.screen.allScreens()
local count = countArray(screens)
-- hs.alert(count)
if count == 1 then
oneScreenLayout()
elseif count == 2 then
twoScreenLayout()
elseif count == 3 then
threeScreenLayout()
end
end
function countArray(array)
count = 0
for k,v in pairs(array) do
count = count + 1
end
return count
end
local max = hs.layout.maximized
local left75 = hs.layout.left75
local left60 = {0,0,0.6,1}
local right40 = {0.6,0,0.4,1}
local right25 = hs.layout.right25
local top50 = {0,0,1,0.5}
local bottom50 = {0,0.5,1,0.5}
function oneScreenLayout()
-- local app = hs.application.get("Reeder")
-- app:selectMenuItem({"View", "Switch to Classic Layout"})
local windowLayout = {
-- max
{"Cocos Creator", nil, laptop, hs.layout.maximized, nil, nil},
{"iTunes", "iTunes", laptop, hs.layout.maximized, nil, nil},
{"iTunes", "MiniPlayer", laptop, nil, nil, hs.geometry.rect(0, -48, 400, 48)},
-- left 75
{"Google Chrome", nil, laptop, hs.layout.left75, nil, nil},
{"WorkFlowy", nil, laptop, right25, nil, nil},
{"Safari", nil, laptop, hs.layout.left75, nil, nil},
{"Sublime Text", nil, laptop, hs.layout.left75, nil, nil},
-- left 50
{"Reeder", nil, laptop, hs.layout.left50, nil, nil},
-- right 40
{"WeChat", nil, laptop, right40, nil, nil},
{"QQ", nil, laptop, right40, nil, nil},
}
hs.layout.apply(windowLayout)
hs.alert.show("One Scrren Layout")
end
function twoScreenLayout()
local app = hs.application.get("Reeder")
if app ~= nil then
app:selectMenuItem({"View", "Switch to Minimized Layout"})
end
local windowLayout = {
-- {"Cocos Creator", "Cocos Creator", mainDesktop, hs.layout.maximized, nil, nil}, -- 不起作用?
{"Xcode", nil, mainDesktop, hs.layout.maximized, nil, nil},
{"Google Chrome", nil, mainDesktop, hs.layout.left75, nil, nil},
{"Safari", nil, laptop, hs.layout.left75, nil, nil},
{"iTunes", "iTunes", laptop, hs.layout.maximized, nil, nil},
{"iTunes", "MiniPlayer", laptop, nil, nil, hs.geometry.rect(0, -48, 400, 48)},
{"Reeder", nil, laptop, hs.layout.left50, nil, nil},
{"WeChat", nil, laptop, hs.layout.right50, nil, nil},
}
function winTitleComparater(winTitle, layoutWinTitle)
-- if layoutWinTitle == "Cocos Creator" then
-- hs.alert(winTitle)
-- hs.alert(layoutWinTitle)
-- -- return true
-- end
return winTitle == layoutWinTitle
end
hs.layout.apply(windowLayout,winTitleComparater)
hs.alert.show("Two Scrren Layout")
end
function threeScreenLayout()
local app = hs.application.get("Reeder")
if app ~= nil then
app:selectMenuItem({"View", "Switch to Minimized Layout"})
end
local windowLayout = {
-- Main Desktop
-- {"Cocos Creator", nil, mainDesktop, hs.layout.maximized, nil, nil}, -- 不起作用?
{"Xcode", nil, mainDesktop, hs.layout.maximized, nil, nil},
{"Code", nil, mainDesktop, hs.layout.maximized, nil, nil},
{"Google Chrome", nil, mainDesktop, left75, nil, nil},
{"WorkFlowy", nil, mainDesktop, right25, nil, nil},
{"Sublime Text", nil, mainDesktop, hs.layout.left75, nil, nil},
-- Laptop
-- max
{"Spark", nil, laptop, hs.layout.maximized, nil, nil},
{"iTunes", "iTunes", laptop, hs.layout.maximized, nil, nil},
{"iTunes", "MiniPlayer", laptop, nil, nil, hs.geometry.rect(0, -48, 400, 48)},
-- left
{"Day One", nil, laptop, right50, nil, nil},
--right
{"Reeder", nil, laptop, hs.layout.right50, nil, nil},
-- V Desktop
-- top
{"Safari", nil, vDesktop, top50, nil, nil},
{"QQ", nil, vDesktop, top50, nil, nil},
--bottom
{"WeChat", nil, vDesktop, bottom50, nil, nil},
{"Simulator", nil, vDesktop, {0.25,0.5,0.25,0.5}, nil, nil},
}
function winTitleComparater(winTitle, layoutWinTitle)
-- if layoutWinTitle == "Cocos Creator" then
-- hs.alert(winTitle)
-- hs.alert(layoutWinTitle)
-- -- return true
-- end
return winTitle == layoutWinTitle
end
hs.layout.apply(windowLayout,winTitleComparater)
hs.alert.show("Three Scrren Layout")
end
---------------------------
-- Layout config
---------------------------
-- hs.logger:d(hs.screen.allScreens())
-- hs.alert.show(hs.screen.allScreens()[1])
autoLayout()
---------------------------
-- Launcher config
---------------------------
function focusApp(appName)
local isDebug = false
-- local result = hs.application.launchOrFocus(appName)
-- hs.alert(result)
local app = hs.application.find(appName)
if isDebug then
alert(app)
end
-- app.activate()
if app == nil then
if isDebug then
alert("nil app")
end
hs.application.launchOrFocus(string.gsub(appName,"%s+", "")) -- 不能有空格。。。
return
end
local mainWin = app:mainWindow()
if mainWin == nil then
-- alert(appName)
mainWin = hs.window.find(appName)
-- alert(mainWin)
end
-- local wins = app:allWindows()
-- local wins = hs.appfinder.appFromName(appName):allWindows()
-- hs.alert(wins)
-- app:activate()
mainWin:focus()
-- hs.fnutils.each(wins, function(win)
-- hs.alert("each")
-- hs.alert(win)
-- hs.alert(win:size())
-- -- win:focus()
-- -- hs.logger:i(win:title())
-- end)
-- for i, win in ipairs(wins) do
-- hs.alert(win:title())
-- -- hs.logger.d(win:title())
-- end
end
-- expose_app = hs.expose.new('Safari','Google Chrome') -- only windows in the current Mission Control Space
-- hs.hotkey.bind('ctrl-cmd-shift','e','App Expose',function()expose_app:toggleShow()end)
function setupCaffeine()
local caffeine = hs.menubar.new()
function setCaffeineDisplay(state)
if state then
caffeine:setTitle("AWAKE")
else
caffeine:setTitle("SLEEPY")
end
end
function caffeineClicked()
local state = hs.caffeinate.toggle("displayIdle")
setCaffeineDisplay(state)
end
if caffeine then
caffeine:setClickCallback(caffeineClicked)
setCaffeineDisplay(hs.caffeinate.get("displayIdle"))
end
end
setupCaffeine()
--[[
-----------------------------
-- Hyper Settings
-----------------------------
-- A global variable for the Hyper Mode
k = hs.hotkey.modal.new({}, "F17")
-- function k:entered()
-- hs.alert("Enter Hyper Mode")
-- end
-- function k:exited()
-- hs.alert("Exit Hyper Mode")
-- end
-- Enter Hyper Mode when F18 (Hyper/Capslock) is pressed
pressedF18 = function()
k.triggered = false
k:enter()
-- if hs.eventtap.checkKeyboardModifiers().cmd then
-- -- k.exit()
-- -- k.triggered = true
-- else
-- end
end
-- Leave Hyper Mode when F18 (Hyper/Capslock) is pressed,
-- send ESCAPE if no other keys are pressed.
releasedF18 = function()
if not k.triggered then
-- if hs.eventtap.checkKeyboardModifiers()["cmd"] then
-- hs.eventtap.keyStroke({'cmd'} , 'Tab')
-- else
hs.eventtap.keyStroke({} , 'Tab')
-- end
end
k:exit()
end
-- Bind the Hyper key
f18 = hs.hotkey.bind({}, 'F18', pressedF18, releasedF18)
-- Trigger existing hyper key shortcuts
hyperBindings = {'a','b','c','d','e','f','g','i','m','n','o','p','q','r','s','t','u','v','w','x','y','z','1','2','3','4','5','6','SPACE','h','j','k','l'}
for i,key in ipairs(hyperBindings) do
k:bind({}, key, nil, function() hs.eventtap.keyStroke(hyper, key)
k.triggered = true
end)
end
-- Alt + Tab
-- t = hs.hotkey.modal.new({'cmd'},"f18")
-- t.bind({},'F18',nil, function() t.eventtap.keyStroke({'cmd'},'Tab') end)
hs.hotkey.bind({'cmd'}, 'F18', function() hs.eventtap.keyStroke({'cmd'},"Tab");k:exit(); end)
-- hs.hotkey.bind({'cmd'}, 'ESCAPE', function() hs.eventtap.keyStroke({'cmd'},"Tab");k:exit(); end)
-- tb = hs.hotkey.modal.new({'cmd'}, "ESCAPE")
-- function tb:entered() hs.alert'Entered mode'; hs.eventtap.keyStroke({'cmd'},"Tab"); end
-- function tb:exited() hs.alert'Exited mode' end
-- tb:bind('', 'ESCAPE', function() tb:exit() end)
-- tb:bind('', 'J', 'Pressed J',function() print'let the record show that J was pressed' end)
-- tb:bind('', 'h', function() hs.eventtap.keyStroke({},"left") end)
-- tb:bind('', 'l', function() hs.eventtap.keyStroke({},"right") end)
-- tb:bind('cmd', 'ESCAPE', function() hs.eventtap.keyStroke({'cmd'},"Tab") end)
-- k:bind({}, 'w', nil, function() hs.eventtap.keyStroke({"cmd","alt","shift","ctrl"}, 'w') end)
-- k:bind({}, 'cmd', nil, function() hs.eventtap.keyStroke('cmd', 'tab') end)
-- OR build your own
-- launch = function(appname)
-- hs.application.launchOrFocus(appname)
-- k.triggered = true
-- end
-- Single keybinding for app launch
-- singleapps = {
-- {'q', 'Newton'},
-- -- {'w', 'Google Chrome'},
-- {'e', 'Sublime Text'},
-- -- {'r', 'Google Chrome'}
-- }
-- for i, app in ipairs(singleapps) do
-- k:bind({}, app[1], function() launch(app[2]); k:exit(); end)
-- end
-- Sequential keybindings, e.g. Hyper-a,f for Finder
-- a = hs.hotkey.modal.new({}, "F16")
-- apps = {
-- {'d', 'Twitter'},
-- {'f', 'Finder'},
-- {'s', 'Skype'},
-- }
-- for i, app in ipairs(apps) do
-- a:bind({}, app[1], function() launch(app[2]); a:exit(); end)
-- end
-- HYPER+L: Open news.google.com in the default browser
-- lfun = function()
-- news = "app = Application.currentApplication(); app.includeStandardAdditions = true; app.doShellScript('open http://news.google.com')"
-- hs.osascript.javascript(news)
-- k.triggered = true
-- end
-- k:bind('', 'l', nil, lfun)
-------------------
-- Launcher Mode --
-------------------
--
-- This feature assumes you have instructed Karabiner-Elements to map right_command to f19:
-- {
-- "profiles": [
-- {
-- "name": "Default profile",
-- "selected": true,
-- "simple_modifications": {
-- "right_command": "f19"
-- }
-- }
-- ]
-- }
--
-- local launcherModeKeyCode = hs.keycodes.map["f19"]
-- local launcherModeBindings = {
-- x = "Xcode",
-- r = "Simulator",
-- s = "Slack",
-- e = "Sublime Text",
-- z = "Zeplin",
-- m = "Mail",
-- c = "Chrome",
-- t = "Terminal",
-- a = "Activity Monitor",
-- i = "iTunes",
-- w = "Google Chrome",
-- }
-- local arrowKeysTable = {
-- h = "left",
-- j = "down",
-- k = "up",
-- l = "right"
-- }
-- local inLauncherMode = false
-- f19down = hs.eventtap.new({ hs.eventtap.event.types.keyDown }, function(event)
-- local keyCode = event:getKeyCode()
-- local characters = event:getCharacters()
-- local isRepeat = event:getProperty(hs.eventtap.event.properties.keyboardEventAutorepeat)
-- if keyCode == launcherModeKeyCode and isRepeat == 0 then
-- inLauncherMode = true
-- end
-- if inLauncherMode then
-- return true
-- end
-- end)
-- f19down:start()
-- rcmd_tap = hs.eventtap.new({ hs.eventtap.event.types.keyUp }, function(event)
-- local keyCode = event:getKeyCode()
-- local characters = event:getCharacters()
-- if keyCode == launcherModeKeyCode then
-- inLauncherMode = false
-- end
-- local appToLaunch = nil
-- local arrowKeyToSend = nil
-- if inLauncherMode then
-- arrowKeyToSend = arrowKeysTable[characters]
-- appToLaunch = launcherModeBindings[characters]
-- if arrowKeyToSend ~= nil then
-- -- hs.alert(arrowKeyToSend)
-- modifiers = {}
-- -- if hs.eventtap.checkKeyboardModifiers().cmd then w
-- -- table.insert(modifiers,'cmd')
-- -- end
-- -- hs.alert(modifiers)
-- hs.eventtap.keyStroke({}, "left")
-- -- return trueww
-- elseif appToLaunch ~= nil then
-- hs.application.launchOrFocus(appToLaunch)
-- hs.alert(appToLaunch)
-- end
-- end
-- -- return appToLaunch ~= nil
-- return true
-- end)
-- rcmd_tap:start()
-- flagsChange = hs.eventtap.new({ hs.eventtap.event.types.flagsChanged }, function(event)
-- local keyCode = event:getKeyCode()
-- local characters = event:getCharacters()
-- local isRepeat = event:getProperty(hs.eventtap.event.properties.keyboardEventAutorepeat)
-- hs.alert(keyCode)
-- end)
-- flagsChange:start()
]]
---------------------------
-- 将所有 Finder 窗口一起显示
---------------------------
function applicationWatcher(appName, eventType, appObject)
if (eventType == hs.application.watcher.activated) then
if (appName == "Finder") then
appObject:selectMenuItem({"Window", "Bring All to Front"})
end
end
end
local appWatcher = hs.application.watcher.new(applicationWatcher)
appWatcher:start()
--[[
---------------------------
-- 连上 usb 后自动打开 timemachine
---------------------------
local usbWatcher = nil
function usbDeviceCallback(data)
if (data["productName"] == "") then
if (data["eventType"] == "added") then
-- hs.application.launchOrFocus("Time Machine")
hs.osascript.applescript("/usr/bin/tmutil startbackup --auto --block")
end
end
end
usbWatcher = hs.usb.watcher.new(usbDeviceCallback)
usbWatcher:start()
--]]
---------------------------
-- Mute volume when leaving home Wifi network
-- Revert to 25 when arriving home
---------------------------
local wifiWatcher = nil
local homeSSID1 = "renjian"
local homeSSID2 = "lain"
local lastSSID = hs.wifi.currentNetwork()
function ssidChangedCallback()
newSSID = hs.wifi.currentNetwork()
local changedToHome = (newSSID == homeSSID1 or newSSID == homeSSID2) and lastSSID ~= homeSSID1 and lastSSID ~= homeSSID2
local changedFromHome = (newSSID ~= homeSSID1 and newSSID ~= homeSSID2) and (lastSSID == homeSSID1 and lastSSID == homeSSID2)
if changedToHome then
-- We just joined our home WiFi network
hs.audiodevice.defaultOutputDevice():setVolume(25)
elseif changedFromHome then
-- We just departed our home WiFi network
hs.audiodevice.defaultOutputDevice():setVolume(0)
end
lastSSID = newSSID
end
wifiWatcher = hs.wifi.watcher.new(ssidChangedCallback)
wifiWatcher:start()
---------------------------
-- Mouse Highlight
---------------------------
local mouseCircle = nil
local mouseCircleTimer = nil
function mouseHighlight()
-- Delete an existing highlight if it exists
if mouseCircle then
mouseCircle:delete()
if mouseCircleTimer then
mouseCircleTimer:stop()
end
end
-- Get the current co-ordinates of the mouse pointer
mousepoint = hs.mouse.getAbsolutePosition()
-- Prepare a big red circle around the mouse pointer
mouseCircle = hs.drawing.circle(hs.geometry.rect(mousepoint.x-40, mousepoint.y-40, 80, 80))
mouseCircle:setStrokeColor({["red"]=1,["blue"]=0,["green"]=0,["alpha"]=1})
mouseCircle:setFill(false)
mouseCircle:setStrokeWidth(5)
mouseCircle:show()
-- Set a timer to delete the circle after 3 seconds
mouseCircleTimer = hs.timer.doAfter(3, function() mouseCircle:delete() end)
end
--[[
---------------------------------
-- tmux 式的窗口布局
---------------------------------
hs.mjomatic.go({
"CCCCCiiiii # <-- The windowgram, it defines the shapes and positions of windows",
"CCCCCiiiii",
"SSSSSiiiii",
"SSSSSYYYYY",
"SSSSSYYYYY",
"",
"C Google Chrome # <-- window C has application():title() 'Google Chrome'",
"i iTerm2",
"Y Safari",
"S Sublime Text"}
)
--]]
-----------------------------
-- Hourly Reminder
-----------------------------
function showDoingThings ()
local wwid = "com.luosky.wwid"
local defaultDoingThings = {
{text = "Work"},
{text = "Playing"},
}
local doingThings = settings.get(wwid) or defaultDoingThings
-- local doingThings = defaultDoingThings
local lastDoingThing = doingThings[1]
function saveToDoingThings(item)
if item == nil then return end
-- Loop to enforce limit on qty of elements in history. Removes the oldest items
while (#doingThings >= 10) do
table.remove(doingThings,1)
end
table.insert(doingThings, item)
settings.set(wwid,doingThings) -- updates the saved history
end
function choiceSelected(choice)
if choice == nil then
return
end
if choice.text == lastDoingThing.text then
focusApp("Day One Classic")
return
end
saveToDoingThings(choice)
lastDoingThing = choice
local now = os.date("%I:%M %p")
pasteboard.setContents("[" .. now .. "] " .. lastDoingThing.text)
-- hs.application.launchOrFocus("Day One Classic")
focusApp("Day One Classic")
end
local chooser = hs.chooser.new(choiceSelected)
chooser:choices(reverse(doingThings))
chooser:queryChangedCallback(function ()
local query = chooser:query()
if query == nil then return end
local choices = { {text = query} }
for i = #doingThings, 1, -1 do
table.insert(choices, doingThings[i])
end
chooser:choices(choices)
end)
chooser:show()
end
timer = hs.timer.delayed.new(3600, function()
showDoingThings()
alert("最近一小时做了啥?")
timer:setDelay(3600)
timer.start()
end)
timer.start()
function reverse(tbl)
local result = {}
for i = #tbl, 1, -1 do
table.insert(result, tbl[i])
end
return result
end
-----------------------------
-- 快捷键绑定
-----------------------------
hs.hotkey.bind(hyper,"1",autoLayout)
hs.hotkey.bind(hyper, "4", function()
local win = hs.window.focusedWindow()
local fullscreenSize = hs.screen.mainScreen():frame().size
local unitRect = hs.screen.mainScreen():toUnitRect(win:frame())
if unitRect.w >= 1.0 then
win:moveToUnit({x=0.125,y=0.125,w=0.75,h=0.75})
else
win:maximize()
-- win:moveToUnit(hs.layout.maximized)
end
end)
hs.hotkey.bind(hyper, "5", function()
local win = hs.window.focusedWindow()
-- win:moveToUnit(hs.layout.maximized)
hs.grid.show()
end)
-- hs.hotkey.bind(hyper,"ESCAPE",function() focusApp("Workflowy") end)
hs.hotkey.bind(hyper,"2",function() focusApp("Workflowy") end)
hs.hotkey.bind(hyper,"y",function() focusApp("Cocos Creator") end)
hs.hotkey.bind(hyper,"s",function() focusApp("Simulator") end)
hs.hotkey.bind(hyper, "=", mouseHighlight)
hs.hotkey.bind(hyper, "`", function() showDoingThings() alert("现在打算做啥?") end)
--[[
-----------------------------
-- SpaceFN
-----------------------------
arrowModal = hs.hotkey.modal.new({}, nil)
enterSpaceFN = function()
arrowModal:enter()
arrowModal.triggered = false
end
spaceFNKeyBinding = hs.hotkey.bind({}, 'SPACE', enterSpaceFN, function()
if not arrowModal.triggered then
spaceFNKeyBinding:disable()
hs.eventtap.keyStroke({}, 'SPACE')
-- hs.eventtap.keyStrokes(' ')
end
arrowModal:exit()
spaceFNKeyBinding:enable()
end)
-- Cursor movement modifiers for line, word, selecting, etc. 
arrowKeysModifiers = {
{''},
{'cmd'},
{'ctrl'},
{'alt'},
{'shift'},
{'cmd','shift'},
{'cmd','alt'},
{'cmd','ctrl'},
{'ctrl','shift'},
{'alt','shift'},
}
arrowKeysMap = {
{'h','left'},
{'j','down'},
{'k','up'},
{'l','right'},
{'a','left'},
{'s','left'},
{'d','down'},
{'e','up'},
{'f','right'},
}
for i, map in ipairs(arrowKeysMap) do
for j,modifier in ipairs(arrowKeysModifiers) do
func = function()
hs.eventtap.keyStroke(modifier,map[2])
arrowModal.triggered = true
end
arrowModal:bind(modifier,map[1],func)
-- arrowModal:bind({}, map[1], function() hs.eventtap.keyStroke({}, map[2]) end)
-- arrowModal:bind({'shift'}, map[1], function() hs.eventtap.keyStroke({'shift'}, map[2]) end)
-- arrowModal:bind({'cmd'}, map[1], function() hs.eventtap.keyStroke({'cmd'}, map[2]) end)
-- arrowModal:bind({'alt'}, map[1], function() hs.eventtap.keyStroke({'alt'}, map[2]) end)
-- arrowModal:bind({'cmd','shift'}, map[1], function() hs.eventtap.keyStroke({'cmd','shift'}, map[2]) end)
-- arrowModal:bind({'alt','shift'}, map[1], function() hs.eventtap.keyStroke({'alt','shift'}, map[2]) end)
end
end
]]--
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment