Created
May 4, 2017 02:37
-
-
Save manugill/754422145794a75c29dabf3e37ba98d3 to your computer and use it in GitHub Desktop.
.hammerspoon
This file contains hidden or 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
| ----------------------------------------------- | |
| -- Setup: | |
| -- Using Seil: Remap capslock to F19 | |
| -- Using Karabiner: Remap F19 to hyper (cmd+alt+ctrl+shift) | |
| ----------------------------------------------- | |
| ----------------------------------------------- | |
| -- Base variables and settings | |
| ----------------------------------------------- | |
| -- keyboard modifiers for bindings | |
| local mod = { | |
| cc = { 'cmd', 'ctrl' }, | |
| ca = { 'cmd', 'alt' }, | |
| cac = { 'cmd', 'alt', 'ctrl' }, | |
| cas = { 'cmd', 'alt', 'shift' }, | |
| cacs = { 'cmd', 'alt', 'ctrl', 'shift' }, -- hyper | |
| } | |
| -- extensions, available in hammerspoon console | |
| ext = { | |
| frame = {}, | |
| win = {}, | |
| app = {}, | |
| utils = {}, | |
| cache = {}, | |
| watchers = {} | |
| } | |
| -- extension settings | |
| ext.win.animationDuration = 0.15 | |
| ext.win.margin = 6 | |
| ext.win.fixEnabled = false | |
| ext.win.fullFrame = true | |
| -- hs settings | |
| hs.window.animationDuration = ext.win.animationDuration | |
| ----------------------------------------------- | |
| -- Launch or focus on applications | |
| ----------------------------------------------- | |
| -- force focus | |
| function ext.app.forceLaunchOrFocus(appName) | |
| -- first focus with hammerspoon | |
| hs.application.launchOrFocus(appName) | |
| -- clear timer if exists | |
| if ext.cache.launchTimer then ext.cache.launchTimer:stop() end | |
| -- wait 500ms for window to appear and try hard to show the window | |
| ext.cache.launchTimer = hs.timer.doAfter(0.5, function() | |
| local frontmostApp = hs.application.frontmostApplication() | |
| local frontmostWindows = hs.fnutils.filter(frontmostApp:allWindows(), function(win) return win:isStandard() end) | |
| -- break if this app is not frontmost (when/why?) | |
| if frontmostApp:title() ~= appName then | |
| print('Expected app in front: ' .. appName .. ' got: ' .. frontmostApp:title()) | |
| return | |
| end | |
| if #frontmostWindows == 0 then | |
| -- check if there's app name in window menu (Calendar, Messages, etc...) | |
| if frontmostApp:findMenuItem({ 'Window', appName }) then | |
| -- select it, usually moves to space with this window | |
| frontmostApp:selectMenuItem({ 'Window', appName }) | |
| else | |
| -- otherwise send cmd-n to create new window | |
| hs.eventtap.keyStroke({ 'cmd' }, 'n') | |
| end | |
| end | |
| end) | |
| end | |
| -- smart app launch or focus or cycle windows | |
| function ext.app.smartLaunchOrFocus(launchApps) | |
| local frontmostWindow = hs.window.frontmostWindow() | |
| local runningApps = hs.application.runningApplications() | |
| local runningWindows = {} | |
| -- filter running applications by apps array | |
| local runningApps = hs.fnutils.map(launchApps, function(launchApp) | |
| return hs.application.get(launchApp) | |
| end) | |
| -- create table of sorted windows per application | |
| hs.fnutils.each(runningApps, function(runningApp) | |
| local standardWindows = hs.fnutils.filter(runningApp:allWindows(), function(win) | |
| return win:isStandard() | |
| end) | |
| table.sort(standardWindows, function(a, b) return a:id() < b:id() end) | |
| runningWindows = standardWindows | |
| end) | |
| if #runningApps == 0 then | |
| -- if no apps are running then launch first one in list | |
| ext.app.forceLaunchOrFocus(launchApps[1]) | |
| elseif #runningWindows == 0 then | |
| -- if some apps are running, but no windows - force create one | |
| ext.app.forceLaunchOrFocus(runningApps[1]:title()) | |
| else | |
| -- check if one of windows is already focused | |
| local currentIndex = hs.fnutils.indexOf(runningWindows, frontmostWindow) | |
| if not currentIndex then | |
| -- if none of them is selected focus the first one | |
| runningWindows[1]:focus() | |
| else | |
| -- otherwise cycle through all the windows | |
| local newIndex = currentIndex + 1 | |
| if newIndex > #runningWindows then newIndex = 1 end | |
| runningWindows[newIndex]:focus() | |
| end | |
| end | |
| end | |
| -- bind shortcuts | |
| hs.fnutils.each({ | |
| { key = '`', apps = { 'Hyper' } }, | |
| -- { key = '`', apps = { 'iTerm' } }, | |
| { key = 'r', apps = { 'Google Chrome' } }, -- b(r)owser, review, ch(r)ome | |
| { key = 't', apps = { 'FirefoxDeveloperEdition' } }, -- web | |
| { key = 'e', apps = { 'Visual Studio Code' } }, -- editor | |
| { key = 'f', apps = { 'Finder' } }, -- find | |
| { key = 'c', apps = { 'Slack' } }, -- chat | |
| { key = 'o', apps = { 'Microsoft Outlook' } }, -- outlook | |
| { key = 'd', apps = { 'Sketch' } }, -- design | |
| -- { key = 'd', apps = { 'Preview' } }, -- design | |
| --{ key = 'd', apps = { 'Adobe Photoshop CC 2015.app', } }, -- design | |
| --{ key = 'd', apps = { 'Adobe InDesign CC 2015.app', } }, -- design | |
| --{ key = 'd', apps = { 'Microsoft Word' } }, -- document | |
| -- { key = 'g', apps = { 'GitKraken' } }, -- git | |
| { key = 'g', apps = { 'SmartGit' } }, -- git | |
| { key = 'v', apps = { 'VirtualBoxVM' } }, -- vm | |
| { key = 'u', apps = { 'Transmit' } }, -- upload | |
| --{ key = '0', apps = { 'MAMP PRO' } }, -- undefined | |
| { key = 'h', apps = { 'Helium' } }, -- helium | |
| { key = 'm', apps = { 'Spotify' } }, -- music | |
| { key = 'p', apps = { 'Digital Color Meter' } }, -- picker | |
| }, function(object) | |
| hs.hotkey.bind(mod.cacs, object.key, function() ext.app.smartLaunchOrFocus(object.apps) end) | |
| end) | |
| -- window hints on tab | |
| hs.hotkey.bind(mod.cacs, 'tab', function() | |
| hs.hints.windowHints() | |
| end) | |
| ----------------------------------------------- | |
| -- Window management: fullscreen, half size, move across screens (west and east only) | |
| ----------------------------------------------- | |
| hs.hotkey.bind(mod.cacs, 'LEFT', function() | |
| local win = hs.window.focusedWindow() | |
| local f = win:frame() | |
| local screen = win:screen() | |
| local max = screen:frame() | |
| if f.x == max.x and f.w == max.w / 2 then | |
| win:moveOneScreenNorth() | |
| else | |
| f.x = max.x | |
| f.y = max.y | |
| f.w = max.w / 2 | |
| f.h = max.h | |
| win:setSize(f, 0) | |
| win:setFrame(f, 0) | |
| end | |
| end) | |
| hs.hotkey.bind(mod.cacs, 'RIGHT', function() | |
| local win = hs.window.focusedWindow() | |
| local f = win:frame() | |
| local screen = win:screen() | |
| local max = screen:frame() | |
| if f.x == max.x + (max.w / 2) and f.w == max.w / 2 then | |
| win:moveOneScreenSouth() | |
| else | |
| f.x = max.x + (max.w / 2) | |
| f.y = max.y | |
| f.w = max.w / 2 | |
| f.h = max.h | |
| win:setSize(f, 0) | |
| win:setFrame(f, 0) | |
| end | |
| end) | |
| hs.hotkey.bind(mod.cacs, 'UP', function() | |
| local win = hs.window.focusedWindow() | |
| local f = win:frame() | |
| local screen = win:screen() | |
| local max = screen:frame() | |
| f.x = max.x | |
| f.y = max.y | |
| f.w = max.w | |
| f.h = max.h | |
| win:maximize(0) | |
| win:setFrame(f, 0) | |
| end) | |
| hs.hotkey.bind(mod.cacs, 'DOWN', function() | |
| local win = hs.window.focusedWindow() | |
| local f = win:frame() | |
| local screen = win:screen() | |
| local max = screen:frame() | |
| f.w = max.w * 0.8 | |
| f.h = max.h * 0.8 | |
| local winSize = win:size() | |
| win:setSize(f, 0) | |
| win:centerOnScreen() | |
| end) | |
| hs.hotkey.bind(mod.cacs, '-', function() | |
| local windows = hs.window.orderedWindows() | |
| hs.fnutils.ieach(windows, function (win) | |
| win:minimize(0.1) | |
| end) | |
| end) | |
| hs.hotkey.bind(mod.cacs, '=', function() | |
| local windows = hs.window.orderedWindows() | |
| hs.fnutils.ieach(windows, function (win) | |
| win:maximize(0.1) | |
| end) | |
| end) | |
| ----------------------------------------------- | |
| -- Reload config on write | |
| ----------------------------------------------- | |
| function reload_config(files) | |
| hs.reload() | |
| end | |
| hs.pathwatcher.new(os.getenv('HOME') .. '/.hammerspoon/', reload_config):start() | |
| hs.alert.show('Config loaded') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment