Skip to content

Instantly share code, notes, and snippets.

@dewaka
Last active March 22, 2024 23:29
Show Gist options
  • Save dewaka/2c160dbe4f67dbe65da5f69388516a2b to your computer and use it in GitHub Desktop.
Save dewaka/2c160dbe4f67dbe65da5f69388516a2b to your computer and use it in GitHub Desktop.
-- Hammerspoon configuration, heavily influenced by sdegutis default configuration
-- https://github.com/Hammerspoon/hammerspoon/wiki/ztomer's-init.lua
-- init grid
hs.grid.MARGINX = 0
hs.grid.MARGINY = 0
hs.grid.GRIDWIDTH = 16
hs.grid.GRIDHEIGHT = 10
-- disable animation
hs.window.animationDuration = 0
-- hotkey mash
local mash = {"ctrl", "alt"}
local mash_app = {"cmd", "alt", "ctrl"}
local mash_shift = {"ctrl", "alt", "shift"}
local app_mash = {"cmd", "ctrl"}
local quick_mash = {"cmd"}
local app_mash_shift = {"cmd", "ctrl", "shift"}
local win_focus_mash = {"cmd", "ctrl"}
local win_resize_mash = {"cmd", "alt", "shift"}
local win_move_mash = {"cmd", "ctrl", "alt"}
-- There is a bug in this function which affects Emacs
-- May be borrow from https://github.com/bobziuchkovski/dotfiles/blob/master/.hammerspoon/init.lua
local function toggleViewApp(app_name)
local app = hs.application.find(app_name)
if app == nil then
hs.application.launchOrFocus(app_name)
elseif app:isFrontmost() then
app:hide()
else
app:activate()
end
end
local function toggleViewApps(apps)
for _, current in pairs(apps) do
local app = hs.application.find(current)
if app then
if app:isFrontmost() then
app:hide()
else
app:activate()
end
return
end
end
if apps then
-- If no app is currently running to focus/unfocus, then launch the first
-- app in the list
hs.application.launchOrFocus(apps[1])
end
end
local function toggleShowHide(app_name)
local app = hs.application.find(app_name)
-- We are not going to do anything if the application is not already running
if app ~= nil then
if app:isFrontmost() then
app:hide()
else
app:activate()
end
end
end
local function toggleShowHideFirstFound(apps)
for i = 1, #apps do
local app_name = apps[i]
local app = hs.application.find(app_name)
-- We are not going to do anything if the application is not already running
if app ~= nil then
if app:isFrontmost() then
app:hide()
else
app:activate()
end
return
end
end
end
-- This is the usual Finder File Search shortcut, but I don't use that at all
-- given I'm used to Alfred for that task. Also I use the same combination in
-- XMonad, so the following is the faster combination for me.
-- Previous combination was: {"cmd", "shift"}
-- I've swapped them now - above combination is now for Spotlight search
hs.hotkey.bind({"cmd", "alt"}, 'space', function () toggleViewApp("Emacs") end)
hs.hotkey.bind("alt", 'space', function () toggleViewApp("Terminal") end)
-- Launch applications
local defaultBrowser = "Google Chrome"
hs.hotkey.bind({"cmd", "control"}, 'space', function () toggleViewApp(defaultBrowser) end)
hs.hotkey.bind(app_mash, 'G', function () toggleViewApp("Google Chrome") end)
hs.hotkey.bind({"cmd", "alt"}, 'F', function () toggleViewApp("Firefox") end)
hs.hotkey.bind(app_mash, 'T', function () toggleViewApp("Terminal") end)
hs.hotkey.bind(app_mash, 'C', function () toggleViewApp("Terminal") end)
hs.hotkey.bind(app_mash, 'E', function () toggleViewApp("Emacs") end)
hs.hotkey.bind(app_mash, 'P', function () toggleViewApps({"Spotify"}) end)
hs.hotkey.bind(app_mash, 'M', function () toggleViewApp("Outlook") end)
hs.hotkey.bind(app_mash, 'S', function () toggleViewApp("Slack") end)
-- hs.hotkey.bind(app_mash, 'K', function () toggleViewApp("Keybase") end)
hs.hotkey.bind(app_mash, 'V', function () toggleViewApp("MacVim") end)
hs.hotkey.bind({"cmd", "alt"}, 'O', function () toggleViewApp("Preview") end)
-- Easy access keys for IntelliJ IDEA
hs.hotkey.bind(app_mash, 'I', function () toggleViewApp("IntelliJ IDEA") end)
hs.hotkey.bind({"cmd", "shift"}, 'space', function () toggleShowHideFirstFound({"IntelliJ IDEA", "CLion"}) end)
hs.hotkey.bind(app_mash, 'D', function () hs.application.launchOrFocus("Dictionary") end)
hs.hotkey.bind(app_mash, 'X', function () hs.application.launchOrFocus("Activity Monitor") end)
-- change focus
hs.hotkey.bind(win_focus_mash, 'H', function() hs.window.focusedWindow():focusWindowWest() end)
hs.hotkey.bind(win_focus_mash, 'L', function() hs.window.focusedWindow():focusWindowEast() end)
hs.hotkey.bind(win_focus_mash, 'K', function() hs.window.focusedWindow():focusWindowNorth() end)
hs.hotkey.bind(win_focus_mash, 'J', function() hs.window.focusedWindow():focusWindowSouth() end)
-- Window Hints - this purely brilliant!
hs.hotkey.bind(win_focus_mash, ';', hs.hints.windowHints)
-- Amphetamine activation
local onOrOffAmphetamine = [[
tell application "Amphetamine"
set sessionActiveTest to session is active
if sessionActiveTest is false then
start new session
else
end session
end if
end tell
]]
-- hs.hotkey.bind(app_mash_shift, 'C', function () hs.osascript.applescript(onOrOffAmphetamine) end)
-- Reload configuration
hs.hotkey.bind(app_mash_shift,
'R',
function()
hs.alert.show("Reloading configuration", 1)
hs.reload()
end)
-- global operations
hs.hotkey.bind(mash, ';', function() hs.grid.snap(hs.window.focusedWindow()) end)
hs.hotkey.bind(mash, "'", function() hs.fnutil.map(hs.window.visibleWindows(), hs.grid.snap) end)
-- adjust grid size
hs.hotkey.bind(mash, '=', function() hs.grid.adjustWidth( 1) end)
hs.hotkey.bind(mash, '-', function() hs.grid.adjustWidth(-1) end)
hs.hotkey.bind(mash, ']', function() hs.grid.adjustHeight( 1) end)
hs.hotkey.bind(mash, '[', function() hs.grid.adjustHeight(-1) end)
-- hs.hotkey.bind(mash, 'M', hs.grid.maximizeWindow)
-- multi monitor
-- hs.hotkey.bind(mash, 'N', hs.grid.pushWindowNextScreen)
-- hs.hotkey.bind(mash, 'P', hs.grid.pushWindowPrevScreen)
-- move windows
hs.hotkey.bind(win_move_mash, 'H', hs.grid.pushWindowLeft)
hs.hotkey.bind(win_move_mash, 'J', hs.grid.pushWindowDown)
hs.hotkey.bind(win_move_mash, 'K', hs.grid.pushWindowUp)
hs.hotkey.bind(win_move_mash, 'L', hs.grid.pushWindowRight)
-- resize windows
hs.hotkey.bind(win_resize_mash, 'H', hs.grid.resizeWindowThinner)
hs.hotkey.bind(win_resize_mash, 'K', hs.grid.resizeWindowShorter)
hs.hotkey.bind(win_resize_mash, 'J', hs.grid.resizeWindowTaller)
hs.hotkey.bind(win_resize_mash, 'L', hs.grid.resizeWindowWider)
-- Volume control
-- hs.hotkey.bind({win_resize_mash}, , hs.audio.decVolume)
-- hs.hotkey.bind({win_resize_mash}, , hs.audio.incVolume)
-- TODO: Replace Caffeine functionality
-- API Guide - http://www.hammerspoon.org/docs/hs.caffeinate.html
-- Example - https://github.com/BrianGilbert/.hammerspoon/blob/master/init.lua
-- Non-modal Spotify functionality
-- local spotify_mash = {"cmd", "alt"}
-- hs.hotkey.bind(spotify_mash, 'N', hs.spotify.next)
-- hs.hotkey.bind(spotify_mash, 'P', hs.spotify.previous)
-- hs.hotkey.bind(spotify_mash, 'J', hs.spotify.playpause)
-- hs.hotkey.bind(spotify_mash, 'K', hs.spotify.pause)
-- Spotify/iTunes Modal stuff
music_playerctrl_mode = hs.hotkey.modal.new('cmd-alt', 'n')
function music_playerctrl_mode:entered()
-- hs.alert'Entered Spotify mode'
end
function music_playerctrl_mode:exited()
-- hs.alert'Exited Spotify mode'
end
-- Simple Esc or Space to exit mode
local function exit_music_playerctrl_mode()
music_playerctrl_mode:exit()
end
music_playerctrl_mode:bind('', 'escape', exit_music_playerctrl_mode);
music_playerctrl_mode:bind('', 'return', exit_music_playerctrl_mode);
music_playerctrl_mode:bind('', 'space', exit_music_playerctrl_mode);
music_playerctrl_mode:bind('', 'J', nil,
function()
if hs.spotify.isRunning() then
hs.spotify.playpause()
elseif hs.itunes.isRunning() then
hs.itunes.playpause()
end
end)
music_playerctrl_mode:bind('', 'K', nil,
function()
if hs.spotify.isRunning() then
hs.spotify.pause()
elseif hs.itunes.isRunning() then
hs.itunes.pause()
end
end)
music_playerctrl_mode:bind('', 'N', nil,
function()
if hs.spotify.isRunning() then
hs.spotify.next()
elseif hs.itunes.isRunning() then
hs.itunes.next()
end
end)
music_playerctrl_mode:bind('', 'P', nil,
function()
if hs.spotify.isRunning() then
hs.spotify.previous()
elseif hs.itunes.isRunning() then
hs.itunes.previous()
end
end)
-- Spotify volume control
music_playerctrl_mode:bind('', 'H', nil,
function()
if hs.spotify.isRunning() then
hs.spotify.volumeDown()
elseif hs.itunes.isRunning() then
hs.itunes.volumeDown()
end
end)
music_playerctrl_mode:bind('', 'L', nil,
function()
if hs.spotify.isRunning() then
hs.spotify.volumeUp()
elseif hs.itunes.isRunning() then
hs.itunes.volumeUp()
end
end)
-- Display current track info
music_playerctrl_mode:bind('', 'D', nil,
function()
if hs.spotify.isRunning() then
hs.spotify.displayCurrentTrack()
elseif hs.itunes.isRunning() then
hs.itunes.displayCurrentTrack()
end
end)
-- System volume control
music_playerctrl_mode:bind('', '[', nil,
function()
local vol = hs.audiodevice.current().volume - 5
if vol < 0 then vol = 0 end
hs.audiodevice.defaultOutputDevice():setVolume(vol)
end)
music_playerctrl_mode:bind('', ']', nil,
function()
local vol = hs.audiodevice.current().volume + 5
if vol > 100 then vol = 100 end
hs.audiodevice.defaultOutputDevice():setVolume(vol)
end)
--[[
I would like to have modal commands with two options
- sticky modal where the mode would keep accepting modal commands till special keys
are pressed to explicitly exit the mode
- one-off modal where any single operation would exit the modal after carrying out
the command
I would like to find out a nice way to implement this feature though.
]]
-- Replacing Caffeine
--[[
local caffeine = hs.menubar.new()
function setCaffeineDisplay(state)
local result
if state then
result = caffeine:setIcon("caffeine-on.pdf")
else
result = caffeine:setIcon("caffeine-off.pdf")
end
end
function caffeineClicked()
setCaffeineDisplay(hs.caffeinate.toggle("displayIdle"))
end
]]
-- Application Layout Modal stuff
applayout_mode = hs.hotkey.modal.new({"cmd", "alt"}, 'L')
function applayout_mode:entered()
-- hs.alert'Entered Applayout mode'
end
function applayout_mode:exited()
-- hs.alert'Exited Applayout mode'
end
-- Simple Esc or Space to exit mode
local function exit_applayout_mode()
applayout_mode:exit()
end
applayout_mode:bind('', 'escape', exit_applayout_mode);
applayout_mode:bind('', 'space', exit_applayout_mode);
local rightCode = hs.geometry.rect(0.55, 0.03, 0.45, 0.97)
local left80 = hs.geometry.rect(0, 0, 0.8, 1)
-- NOTE: There might be a better, more elegant way of doing the following things
-- But I wanted to keep things seperate as possible following a simple
-- convention For maximising stuff use Capital mnemonic letter. For normal size
-- just the simple letter. Also keep applications separate. Emacs maximised does
-- not necessarily mean Chrome has to be put down to normal size etc. and so on.
-- If I had multiple monitors then I can really do some cool stuff by placing
-- things on different monitors, but that's not the case yet.
local codeMaxLayout = {
{"Emacs", nil, "Color LCD", hs.layout.maximized, nil, nil}
}
local codeNormalLayout = {
{"Emacs", nil, "Color LCD", rightCode, nil, nil}
}
local webMaxLayout = {
{"Google Chrome", nil, "Color LCD", hs.layout.maximized, nil, nil}
}
local webNormalLayout = {
{"Google Chrome", nil, "Color LCD", hs.geometry.rect(0, 0, 0.78, 0.9), nil, nil}
}
local termMaxLayout = {
{"Terminal", nil, "Color LCD", hs.layout.maximized, nil, nil}
}
local termNormalLayout = {
{"Terminal", nil, "Color LCD", hs.geometry.rect(0, 0.32, 0.62, 0.68), nil, nil}
}
applayout_mode:bind('shift', 'E', nil,
function()
hs.layout.apply(codeMaxLayout)
exit_applayout_mode()
end)
applayout_mode:bind('', 'E', nil,
function()
hs.layout.apply(codeNormalLayout)
exit_applayout_mode()
end)
applayout_mode:bind('shift', 'G', nil,
function()
hs.layout.apply(webMaxLayout)
exit_applayout_mode()
end)
applayout_mode:bind('', 'G', nil,
function()
hs.layout.apply(webNormalLayout)
exit_applayout_mode()
end)
applayout_mode:bind('shift', 'T', nil,
function()
hs.layout.apply(termMaxLayout)
exit_applayout_mode()
end)
applayout_mode:bind('', 'T', nil,
function()
hs.layout.apply(termNormalLayout)
exit_applayout_mode()
end)
local function setFocusedWindowSize(wsize)
if hs.window.focusedWindow() then
local win = hs.window.focusedWindow()
local f = win:frame()
local screen = win:screen()
local max = screen:frame()
if wsize == 'left half' then
f.x = max.x
f.y = max.y
f.w = max.w / 2
f.h = max.h
elseif wsize == 'right half' then
f.x = max.x + (max.w / 2)
f.y = max.y
f.w = max.w / 2
f.h = max.h
elseif wsize == 'full' then
f.x = max.x
f.y = max.y
f.w = max.w
f.h = max.h
elseif wsize == 'right third' then
f.x = max.x + (max.w / 4)
f.y = max.y
f.w = 3 * max.w / 4
f.h = max.h
elseif wsize == 'left third' then
f.x = max.x
f.y = max.y
f.w = 3 * max.w / 4
f.h = max.h
elseif wsize == 'right 2/3' then
f.x = max.x + (max.w / 3)
f.y = max.y
f.w = 2 * max.w / 3
f.h = max.h
elseif wsize == 'left 2/3' then
f.x = max.x
f.y = max.y
f.w = 2 * max.w / 3
f.h = max.h
elseif wsize == 'web' then
-- 0, 0, 0.8, 0.9
f.x = max.x
f.y = max.y
f.w = max.w * 0.78
f.h = max.h * 0.9
elseif wsize == 'web large' then
-- 0, 0, 0.8, 0.9
f.x = max.x
f.y = max.y
f.w = max.w * 0.9
f.h = max.h * 0.97
elseif wsize == 'right 3/5' then
f.x = max.x + (2 * max.w / 5)
f.y = max.y
f.w = 3 * max.w / 5
f.h = max.h
elseif wsize == 'left 3/5' then
f.x = max.x
f.y = max.y
f.w = 3 * max.w / 5
f.h = max.h
end
win:setFrame(f)
else
hs.alert.show("No active window")
end
end
applayout_mode:bind('', 'L', nil,
function()
setFocusedWindowSize('right half')
exit_applayout_mode()
end)
applayout_mode:bind('', 'H', nil,
function()
setFocusedWindowSize('left half')
exit_applayout_mode()
end)
applayout_mode:bind('', 'F', nil,
function()
setFocusedWindowSize('full')
exit_applayout_mode()
end)
applayout_mode:bind('', '3', nil,
function()
setFocusedWindowSize('right third')
exit_applayout_mode()
end)
applayout_mode:bind('shift', '3', nil,
function()
setFocusedWindowSize('left third')
exit_applayout_mode()
end)
applayout_mode:bind('', '2', nil,
function()
setFocusedWindowSize('right 2/3')
exit_applayout_mode()
end)
applayout_mode:bind('shift', '2', nil,
function()
setFocusedWindowSize('left 2/3')
exit_applayout_mode()
end)
applayout_mode:bind('', '1', nil,
function()
setFocusedWindowSize('right 3/5')
exit_applayout_mode()
end)
applayout_mode:bind('shift', '1', nil,
function()
setFocusedWindowSize('left 3/5')
exit_applayout_mode()
end)
applayout_mode:bind('', 'w', nil,
function()
setFocusedWindowSize('web')
exit_applayout_mode()
end)
applayout_mode:bind('shift', 'w', nil,
function()
setFocusedWindowSize('web large')
exit_applayout_mode()
end)
-- Window sizing --
hs.window.animationDuration = 0
previousFrameSizes = {}
modificationKeys = {"cmd"}
function isAlmostEqualToCurWinFrame(geo)
local epsilon = 5
local curWin = hs.window.focusedWindow()
local curWinFrame = curWin:frame()
if math.abs(curWinFrame.x - geo.x) < epsilon and
math.abs(curWinFrame.y - geo.y) < epsilon and
math.abs(curWinFrame.w - geo.w) < epsilon and
math.abs(curWinFrame.h - geo.h) < epsilon then
return true
else
return false
end
end
function getMaxWinFrame()
local curWin = hs.window.focusedWindow()
return curWin:screen():frame()
end
function getFillLeftWinFrame()
local curWin = hs.window.focusedWindow()
local curWinFrame = curWin:frame()
local maxFrame = curWin:screen():frame()
curWinFrame.x = maxFrame.x
curWinFrame.y = maxFrame.y
curWinFrame.w = maxFrame.w / 2
curWinFrame.h = maxFrame.h
return curWinFrame
end
function getFillRightWinFrame()
local curWin = hs.window.focusedWindow()
local curWinFrame = curWin:frame()
local maxFrame = curWin:screen():frame()
curWinFrame.x = maxFrame.x + maxFrame.w / 2
curWinFrame.y = maxFrame.y
curWinFrame.w = maxFrame.w / 2
curWinFrame.h = maxFrame.h
return curWinFrame
end
function isPredefinedWinFrameSize()
if isAlmostEqualToCurWinFrame(getMaxWinFrame()) or
isAlmostEqualToCurWinFrame(getFillLeftWinFrame()) or
isAlmostEqualToCurWinFrame(getFillRightWinFrame()) then
return true
else
return false
end
end
function bindResizeAndRestoreToKeys(key, resize_frame_fn)
hs.hotkey.bind(modificationKeys, key, function()
local curWin = hs.window.focusedWindow()
local curWinFrame = curWin:frame()
local targetFrame = resize_frame_fn()
if isPredefinedWinFrameSize() and not isAlmostEqualToCurWinFrame(targetFrame) then
curWin:setFrame(targetFrame)
elseif previousFrameSizes[curWin:id()] then
curWin:setFrame(previousFrameSizes[curWin:id()])
previousFrameSizes[curWin:id()] = nil
else
previousFrameSizes[curWin:id()] = curWinFrame
curWin:setFrame(targetFrame)
end
end)
end
bindResizeAndRestoreToKeys("up", getMaxWinFrame)
bindResizeAndRestoreToKeys("left", getFillLeftWinFrame)
bindResizeAndRestoreToKeys("right", getFillRightWinFrame)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment