Last active
March 22, 2024 23:29
-
-
Save dewaka/2c160dbe4f67dbe65da5f69388516a2b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- 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