· 7 years ago · Jan 10, 2019, 05:48 PM
1-----------------------------------------------
2-- Old Setup (obsolete now):
3-- Using Seil: Remap capslock to F19
4-- Using Karabiner: Remap F19 to hyper (cmd+alt+ctrl+shift)
5
6-- New Setup:
7 -- Download Karabiner Elements
8 -- Enable complex modification for caps lock to hyper (cmd+alt+ctrl+shift)
9-----------------------------------------------
10
11-----------------------------------------------
12-- Base variables and settings
13-----------------------------------------------
14
15-- keyboard modifiers for bindings
16local mod = {
17 cc = { 'cmd', 'ctrl' },
18 ca = { 'cmd', 'alt' },
19 cac = { 'cmd', 'alt', 'ctrl' },
20 cas = { 'cmd', 'alt', 'shift' },
21 cacs = { 'cmd', 'alt', 'ctrl', 'shift' }, -- hyper
22}
23
24-- extensions, available in hammerspoon console
25ext = {
26 frame = {},
27 win = {},
28 app = {},
29 utils = {},
30 cache = {},
31 watchers = {}
32}
33
34-- extension settings
35ext.win.animationDuration = 0.15
36ext.win.margin = 6
37ext.win.fixEnabled = false
38ext.win.fullFrame = true
39
40-- hs settings
41hs.window.animationDuration = ext.win.animationDuration
42
43-----------------------------------------------
44-- Launch or focus on applications
45-----------------------------------------------
46
47-- force focus
48function ext.app.forceLaunchOrFocus(appName)
49 -- first focus with hammerspoon
50 hs.application.launchOrFocus(appName)
51
52 -- clear timer if exists
53 if ext.cache.launchTimer then ext.cache.launchTimer:stop() end
54
55 -- wait 500ms for window to appear and try hard to show the window
56 ext.cache.launchTimer = hs.timer.doAfter(0.5, function()
57 local frontmostApp = hs.application.frontmostApplication()
58 local frontmostWindows = hs.fnutils.filter(frontmostApp:allWindows(), function(win) return win:isStandard() end)
59
60 -- break if this app is not frontmost (when/why?)
61 if frontmostApp:title() ~= appName then
62 print('Expected app in front: ' .. appName .. ' got: ' .. frontmostApp:title())
63 return
64 end
65
66 if #frontmostWindows == 0 then
67 -- check if there's app name in window menu (Calendar, Messages, etc...)
68 if frontmostApp:findMenuItem({ 'Window', appName }) then
69 -- select it, usually moves to space with this window
70 frontmostApp:selectMenuItem({ 'Window', appName })
71 else
72 -- otherwise send cmd-n to create new window
73 hs.eventtap.keyStroke({ 'cmd' }, 'n')
74 end
75 end
76 end)
77end
78
79-- smart app launch or focus or cycle windows
80function ext.app.smartLaunchOrFocus(launchApps)
81 local frontmostWindow = hs.window.frontmostWindow()
82 local runningApps = hs.application.runningApplications()
83 local runningWindows = {}
84
85 -- filter running applications by apps array
86 local runningApps = hs.fnutils.map(launchApps, function(launchApp)
87 return hs.application.get(launchApp)
88 end)
89
90 -- create table of sorted windows per application
91 hs.fnutils.each(runningApps, function(runningApp)
92 local standardWindows = hs.fnutils.filter(runningApp:allWindows(), function(win)
93 return win:isStandard()
94 end)
95
96 table.sort(standardWindows, function(a, b) return a:id() < b:id() end)
97
98 runningWindows = standardWindows
99 end)
100
101 if #runningApps == 0 then
102 -- if no apps are running then launch first one in list
103 ext.app.forceLaunchOrFocus(launchApps[1])
104 elseif #runningWindows == 0 then
105 -- if some apps are running, but no windows - force create one
106 ext.app.forceLaunchOrFocus(runningApps[1]:title())
107 else
108 -- check if one of windows is already focused
109 local currentIndex = hs.fnutils.indexOf(runningWindows, frontmostWindow)
110
111 if not currentIndex then
112 -- if none of them is selected focus the first one
113 runningWindows[1]:focus()
114 else
115 -- otherwise cycle through all the windows
116 local newIndex = currentIndex + 1
117 if newIndex > #runningWindows then newIndex = 1 end
118
119 runningWindows[newIndex]:focus()
120 end
121 end
122end
123
124-- bind shortcuts
125hs.fnutils.each({
126 -- { key = '`', apps = { 'Hyper' } },
127 { key = '`', apps = { 'iTerm' } },
128
129 { key = 'r', apps = { 'Google Chrome' } }, -- b(r)owser, review, ch(r)ome
130 { key = 'e', apps = { 'Visual Studio Code' } }, -- editor
131 { key = 'f', apps = { 'Finder' } }, -- find
132 { key = 'c', apps = { 'Microsoft Teams' } }, -- chat
133 { key = 'o', apps = { 'Microsoft Outlook' } }, -- outlook
134 { key = 't', apps = { 'Wunderlist' } }, -- todos
135
136 { key = 'd', apps = { 'Sketch' } }, -- design
137 -- { key = 'd', apps = { 'Preview' } }, -- design
138 --{ key = 'd', apps = { 'Adobe Photoshop CC 2015.app', } }, -- design
139 --{ key = 'd', apps = { 'Adobe InDesign CC 2015.app', } }, -- design
140 --{ key = 'd', apps = { 'Microsoft Word' } }, -- document
141
142 { key = 'g', apps = { 'GitHub Desktop' } }, -- git
143 -- { key = 'g', apps = { 'GitKraken' } }, -- git
144 -- { key = 'g', apps = { 'SmartGit' } }, -- git
145
146 { key = 'v', apps = { 'VirtualBoxVM' } }, -- vm
147 { key = 'u', apps = { 'Transmit' } }, -- upload
148 { key = '0', apps = { 'Insomnia' } }, -- undefined
149 { key = '9', apps = { 'Postico' } }, -- undefined
150
151 { key = 'h', apps = { 'Helium' } }, -- helium
152 { key = 'm', apps = { 'Spotify' } }, -- music
153 { key = 'p', apps = { 'Digital Color Meter' } }, -- picker
154}, function(object)
155 hs.hotkey.bind(mod.cacs, object.key, function() ext.app.smartLaunchOrFocus(object.apps) end)
156end)
157
158-- window hints on tab
159hs.hotkey.bind(mod.cacs, 'tab', function()
160 hs.hints.windowHints()
161end)
162
163
164-----------------------------------------------
165-- Window management: fullscreen, half size, move across screens (west and east only)
166-----------------------------------------------
167
168-- hs.hotkey.bind(mod.cacs, 'LEFT', function()
169-- local win = hs.window.focusedWindow()
170-- local f = win:frame()
171-- local screen = win:screen()
172-- local max = screen:frame()
173
174-- if f.x == max.x and f.w == max.w / 2 then
175-- win:moveOneScreenNorth()
176-- else
177-- f.x = max.x
178-- f.y = max.y
179-- f.w = max.w / 2
180-- f.h = max.h
181-- win:setSize(f, 0)
182-- win:setFrame(f, 0)
183-- end
184-- end)
185
186-- hs.hotkey.bind(mod.cacs, 'RIGHT', function()
187-- local win = hs.window.focusedWindow()
188-- local f = win:frame()
189-- local screen = win:screen()
190-- local max = screen:frame()
191
192-- if f.x == max.x + (max.w / 2) and f.w == max.w / 2 then
193-- win:moveOneScreenSouth()
194-- else
195-- f.x = max.x + (max.w / 2)
196-- f.y = max.y
197-- f.w = max.w / 2
198-- f.h = max.h
199-- win:setSize(f, 0)
200-- win:setFrame(f, 0)
201-- end
202-- end)
203
204
205-- hs.hotkey.bind(mod.cacs, 'UP', function()
206-- local win = hs.window.focusedWindow()
207-- local f = win:frame()
208-- local screen = win:screen()
209-- local max = screen:frame()
210
211-- f.x = max.x
212-- f.y = max.y
213-- f.w = max.w
214-- f.h = max.h
215
216-- win:maximize(0)
217-- win:setFrame(f, 0)
218-- end)
219
220-- hs.hotkey.bind(mod.cacs, 'DOWN', function()
221-- local win = hs.window.focusedWindow()
222-- local f = win:frame()
223-- local screen = win:screen()
224-- local max = screen:frame()
225
226-- f.w = max.w * 0.8
227-- f.h = max.h * 0.8
228
229-- local winSize = win:size()
230
231-- win:setSize(f, 0)
232-- win:centerOnScreen()
233-- end)
234
235-- hs.hotkey.bind(mod.cacs, '-', function()
236-- local windows = hs.window.orderedWindows()
237-- hs.fnutils.ieach(windows, function (win)
238-- win:minimize(0.1)
239-- end)
240-- end)
241
242-- hs.hotkey.bind(mod.cacs, '=', function()
243-- local windows = hs.window.orderedWindows()
244-- hs.fnutils.ieach(windows, function (win)
245-- win:maximize(0.1)
246-- end)
247-- end)
248
249
250-----------------------------------------------
251-- Reload config on write
252-----------------------------------------------
253
254function reload_config(files)
255 hs.reload()
256end
257hs.pathwatcher.new(os.getenv('HOME') .. '/.hammerspoon/', reload_config):start()
258hs.alert.show('Config loaded')