local appBindings = { ["1"] = "Slack", ["2"] = "Discord", ["3"] = "Messages", ["4"] = "Mail", ["7"] = "Spotify", ["8"] = "Firefox", ["9"] = "Ghostty", ["0"] = "Obsidian", } for key, app in pairs(appBindings) do hs.hotkey.bind({ "cmd", "ctrl" }, key, function() hs.application.launchOrFocus(app) end) end hs.hotkey.bind({ "cmd", "ctrl" }, "r", function() hs.reload() end) -- Window management hs.window.animationDuration = 0 local function moveWindow(fn) local win = hs.window.focusedWindow() if not win then return end local screen = win:screen():frame() fn(win, screen) end local windowBindings = { h = function(w, s) w:setFrame({ x = s.x, y = s.y, w = s.w / 2, h = s.h }) end, l = function(w, s) w:setFrame({ x = s.x + s.w / 2, y = s.y, w = s.w / 2, h = s.h }) end, Return = function(w, s) w:setFrame(s) end, u = function(w, s) w:setFrame({ x = s.x, y = s.y, w = s.w / 2, h = s.h / 2 }) end, i = function(w, s) w:setFrame({ x = s.x + s.w / 2, y = s.y, w = s.w / 2, h = s.h / 2 }) end, j = function(w, s) w:setFrame({ x = s.x, y = s.y + s.h / 2, w = s.w / 2, h = s.h / 2 }) end, k = function(w, s) w:setFrame({ x = s.x + s.w / 2, y = s.y + s.h / 2, w = s.w / 2, h = s.h / 2 }) end, } for key, fn in pairs(windowBindings) do hs.hotkey.bind({ "ctrl", "alt" }, key, function() moveWindow(fn) end) end -- Cycling window positions (thirds and 2/3rds) local cycleState = { g = 0, e = 0 } -- g: 1/3 width, cycles right → middle → left hs.hotkey.bind({ "ctrl", "alt" }, "g", function() local win = hs.window.focusedWindow() if not win then return end local s = win:screen():frame() local positions = { { x = s.x + s.w * 2 / 3, y = s.y, w = s.w / 3, h = s.h }, { x = s.x + s.w / 3, y = s.y, w = s.w / 3, h = s.h }, { x = s.x, y = s.y, w = s.w / 3, h = s.h }, } cycleState.g = (cycleState.g % #positions) + 1 win:setFrame(positions[cycleState.g]) end) -- e: 2/3 width, cycles left → right hs.hotkey.bind({ "ctrl", "alt" }, "e", function() local win = hs.window.focusedWindow() if not win then return end local s = win:screen():frame() local positions = { { x = s.x, y = s.y, w = s.w * 2 / 3, h = s.h }, { x = s.x + s.w / 3, y = s.y, w = s.w * 2 / 3, h = s.h }, } cycleState.e = (cycleState.e % #positions) + 1 win:setFrame(positions[cycleState.e]) end) -- Volume control local volumeStep = 5 local volumeMods = { "ctrl", "shift", "cmd" } local volumeAlertId = nil local function showVolumeAlert(vol) if volumeAlertId then hs.alert.closeSpecific(volumeAlertId) end volumeAlertId = hs.alert.show("Volume: " .. math.floor(vol) .. "%") end hs.hotkey.bind(volumeMods, "=", function() local device = hs.audiodevice.defaultOutputDevice() device:setVolume(math.min(100, device:volume() + volumeStep)) showVolumeAlert(device:volume()) end) hs.hotkey.bind(volumeMods, "-", function() local device = hs.audiodevice.defaultOutputDevice() device:setVolume(math.max(0, device:volume() - volumeStep)) showVolumeAlert(device:volume()) end) hs.alert.show("Config loaded")