Skip to content

Instantly share code, notes, and snippets.

@kidd
Last active March 24, 2025 09:23
Show Gist options
  • Save kidd/90f9ec61f72ed566408059bebb926c14 to your computer and use it in GitHub Desktop.
Save kidd/90f9ec61f72ed566408059bebb926c14 to your computer and use it in GitHub Desktop.
prefix = {
mods={"ctrl"},
key="t",
}
appNameToPreferredNumber = {
Emacs=0,
Safari=1,
Terminal=2,
Spotify=9,
}
windows = {}
prevwindow = nil
squareDrawing = nil
function notify(msg)
hs.alert.show(msg)
end
function log(msg)
print(msg)
end
function deleteSquarePointer()
squareDrawing:delete()
squareDrawing = nil
end
function drawSquarePointer()
if squareDrawing then
deleteSquarePointer()
end
-- Get the current co-ordinates of the mouse pointer
mousepoint = hs.mouse.absolutePosition()
-- Prepare a big red circle around the mouse pointer
squareDrawing = hs.drawing.rectangle(hs.geometry.rect(mousepoint.x-10, mousepoint.y-10, 20, 20))
squareDrawing:setStrokeColor({["red"]=0.478431,["green"]=0.780392,["blue"]=0.960784,["alpha"]=1})
squareDrawing:setFill(false)
squareDrawing:setStrokeWidth(5)
squareDrawing:show()
end
function reloadConfig()
hs.reload()
notify("Configuration was reloaded")
keymapselect:exit()
end
function showWindows()
s = ""
for i = 0, 9 do
window = windows[i]
if window ~= nil then
if s ~= "" then
s = s .. "\n"
end
s = s .. tostring(i) .. " - " .. window:application():name() .. " - " .. window:title()
end
end
notify(s)
keymapselect:exit()
end
function findWindow(window)
local MAXWINDOWS <const> = 9
for i = 0, MAXWINDOWS do
if windows[i] == window then
return i
end
end
return -1
end
function firstAvailableNumber()
i = 0
while windows[i] do
i = i + 1
end
return i
end
function rebind(n)
window = hs.window.focusedWindow()
-- unbind if it's already bound
i = findWindow(window)
if i ~= -1 then
windows[i] = nil
end
-- move window already at this number
if windows[n] then
windows[firstAvailableNumber()] = windows[n]
end
windows[n] = window
notify("Bound " .. window:title() .. " to " .. n)
keymaprebind:exit()
end
function selectwin(w)
focused = hs.window.focusedWindow()
if focused ~= w then
prevwindow = focused
end
w:focus()
end
function selectWinNumber(n)
if windows[n] == nil then
notify(tostring(n) .. " is not bound")
else
selectwin(windows[n])
end
keymapselect:exit()
end
function other()
if prevwindow == nil then
notify("No prev window")
else
selectwin(prevwindow)
end
keymapselect:exit()
end
function prefixAction()
keymapselect:enter()
end
function literal()
-- exit first, otherwise the keyStroke is caught by the keymap again
keymapselect:exit()
hs.hotkey.deleteAll(prefix["mods"], prefix["key"])
hs.eventtap.keyStroke(prefix["mods"], prefix["key"])
hs.hotkey.bind(prefix["mods"], prefix["key"], prefixAction)
keymapselect:bind(prefix["mods"], prefix["key"], other)
end
function enterRebind()
keymapselect:exit()
keymaprebind:enter()
end
function windowCreatedCallback(window, appName, event)
i = firstAvailableNumber()
log("created window for " .. appName .. " is number " .. i)
windows[i] = window
end
function windowDestroyedCallback(window, appName, event)
i = findWindow(window)
if i ~= -1 then
windows[i] = nil
end
log("destroyed window for " .. appName .. " was number " .. i)
end
hs.window.filter.new():subscribe({
windowCreated=windowCreatedCallback,
windowDestroyed=windowDestroyedCallback
})
function bindRunningApps()
windows = {}
for _, app in ipairs(hs.application.runningApplications()) do
for _, window in ipairs(app:allWindows()) do
appName = window:application():name()
-- Skip auto-numbering Finder window that does not exist
if not (appName == "Finder" and window:title() == "") then
number = appNameToPreferredNumber[appName]
if not number then
number = firstAvailableNumber()
end
windows[number] = window
end
end
end
keymapselect:exit()
end
-- Don't bind directly in here because it will trigger on keyup, not
-- keydown. This makes it feel laggy.
keymapselect = hs.hotkey.modal.new()
keymaprebind = hs.hotkey.modal.new()
hs.hotkey.bind(prefix["mods"], prefix["key"], prefixAction)
keymapselect:bind("", "t", literal)
keymapselect:bind(prefix["mods"], prefix["key"], other)
keymapselect:bind("", "o", other)
keymapselect:bind("ctrl", "o", other)
keymapselect:bind("", "w", showWindows)
keymapselect:bind("ctrl", "w", showWindows)
keymapselect:bind("", "r", reloadConfig)
keymapselect:bind("shift", "d", bindRunningApps)
keymapselect:bind("", "escape", function() keymapselect:exit() end)
for i = 0, 9 do
keymapselect:bind("", tostring(i), function() selectWinNumber(i) end)
keymapselect:bind("ctrl", tostring(i), function() selectWinNumber(i) end)
end
keymapselect:bind("", "n", enterRebind)
keymapselect:bind("ctrl", "n", enterRebind)
keymaprebind:bind("", "escape", function() keymaprebind:exit() end)
for i = 0, 9 do
keymaprebind:bind("", tostring(i), function() rebind(i) end)
end
bindRunningApps()
function keymapselect:entered()
drawSquarePointer()
end
function keymapselect:exited()
deleteSquarePointer()
end
function keymaprebind:entered()
drawSquarePointer()
end
function keymaprebind:exited()
deleteSquarePointer()
end
keymapselect:bind("", "s",
function()
local w = hs.window.focusedWindow()
local oldsizew=w:size().w
local oldsizeh=w:size().h
w:setSize(hs.geometry.size(w:size().w, w:size().h/2))
-- hs.window:setTopLeft(w:topLeft()+{oldsizew, oldsizeh})
keymapselect:exit()
end)
keymapselect:bind("shift", "q",
function()
local w = hs.window.focusedWindow()
w:maximize()
keymapselect:exit()
end)
keymapselect:bind("", "v",
function()
local w = hs.window.focusedWindow()
w:setSize(hs.geometry.size(w:size().w/2, w:size().h))
-- hs.window:setTopLeft(w:topLeft()+{oldsizew, oldsizeh})
keymapselect:exit()
end)
function focusnext(n)
return function()
selectWinNumber(((findWindow(hs.window.focusedWindow())+n) % (#windows+1)))
keymapselect:exit()
end
end
keymapselect:bind("", "space", focusnext(1))
keymapselect:bind("ctrl", "space", focusnext(1))
keymapselect:bind("", "n", focusnext(1))
keymapselect:bind("ctrl", "n", focusnext(1))
keymapselect:bind("", "p", focusnext(-1))
keymapselect:bind("ctrl", "p", focusnext(-1))
keymapselect:bind("ctrl", "k", function() hs.window.focusedWindow():application():kill() ; keymapselect:exit() end)
keymapselect:bind("", "k", function() hs.window.focusedWindow():application():kill() ; keymapselect:exit() end)
keymapselect:bind("", "h", function() hs.window.focusedWindow():focusWindowWest() ; keymapselect:exit() end)
keymapselect:bind("", "l", function() hs.window.focusedWindow():focusWindowEast() ; keymapselect:exit() end)
keymapselect:bind("", "-", function() hs.window.focusedWindow():minimize() ; keymapselect:exit() end)
-- hs.window.focusedWindow()
-- hs.window:setSize(size) -> window
-- hs.window:setTopLeft(point) -> window
-- hs.geometry.size(w, h) -> hs.geometry size
keymapselect:bind("", "b", function()
hs.mouse.absolutePosition({x=0,y=0})
keymapselect:exit()
end)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment