├── bin └── sshb ├── gifify ├── Dockerfile ├── README.md └── gifify.sh └── hammerspoon └── init.lua /bin/sshb: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # SSH with host name and IP address in background (only in iTerm.app) 4 | 5 | # First, check to see if we have the correct terminal! 6 | PARENT_COMMAND=$(ps $PPID | tail -n 1) 7 | if [ "$(tty)" == 'not a tty' ] || [ "$TERM_PROGRAM" != "iTerm.app" ] || [[ "$PARENT_COMMAND" == *"/bin/mosh"* ]] ; then 8 | /usr/bin/ssh "$@" 9 | exit $? 10 | fi 11 | 12 | function __calculate_iterm_window_dimensions { 13 | local size=( $(osascript -e "tell application \"iTerm2\" 14 | get bounds of the first window 15 | end tell" | tr ',' ' ') ) 16 | 17 | local x1=${size[0]} y1=${size[1]} x2=${size[2]} y2=${size[3]} 18 | # 15px - scrollbar width 19 | local w=$(( $x2 - $x1 - 15 )) 20 | # 44px - titlebar + tabs height 21 | local h=$(( $y2 - $y1 - 44)) 22 | echo "${w}x${h}" 23 | } 24 | 25 | function __get_sess { 26 | osascript -e " 27 | tell application \"iTerm2\" 28 | set sessid to unique id of current session of current tab of current window 29 | do shell script \"echo \" & sessid 30 | end tell" 31 | } 32 | 33 | # Unique ID of session; so we don't lose track if user changes tab soon. 34 | SESS=$(__get_sess) 35 | 36 | # Console dimensions 37 | DIMENSIONS=$(__calculate_iterm_window_dimensions) 38 | 39 | BG_COLOR="#000000" # Background color 40 | FG_COLOR="#FFFFFF" # Foreground color 41 | GRAVITY="NorthEast" # Text gravity (NorthWest, North, NorthEast, 42 | # West, Center, East, SouthWest, South, SouthEast) 43 | OFFSET1="30,10" # Text offset 44 | OFFSET2="30,40" # Text offset 45 | FONT_SIZE="20" # Font size in points 46 | FONT_STYLE="Normal" # Font style (Any, Italic, Normal, Oblique) 47 | 48 | HOSTNAME=$(echo "$@" | sed -e "s/.*@//" -e "s/ .*//") 49 | 50 | # Set tab title 51 | printf "\e]1;%s\a" "$HOSTNAME" 52 | 53 | # RESOLVED_HOSTNAME=`nslookup $HOSTNAME|tail -n +4|grep '^Name:'|cut -f2 -d $'\t'` 54 | # RESOLVED_IP=`nslookup $HOSTNAME|tail -n +4|grep '^Address:'|cut -f2 -d $':'|tail -c +2` 55 | # output=`dscacheutil -q host -a name $HOSTNAME` 56 | # RESOLVED_HOSTNAME=`echo -e "$output"|grep '^name:'|awk '{print $2}'` 57 | # RESOLVED_IP=`echo -e "$output"|grep '^ip_address:'|awk '{print $2}'` 58 | RESOLVED_HOSTNAME=$HOSTNAME 59 | RESOLVED_IP=$(/usr/bin/ssh -o 'ProxyCommand echo %h' "$HOSTNAME" -v 2>&1 | grep 'debug1: Executing proxy' | rev | cut -d" " -f1 | rev) 60 | 61 | function set_bg { 62 | osascript -e " 63 | tell application \"iTerm2\" 64 | tell current window 65 | repeat with aTab in tabs 66 | tell aTab 67 | repeat with aSession in sessions 68 | if unique id of aSession is \"$1\" then 69 | tell aSession 70 | try 71 | set background image to \"$2\" 72 | on error errmesg number errn 73 | display dialog errmesg 74 | end try 75 | end tell 76 | end if 77 | end repeat 78 | end tell 79 | end repeat 80 | end tell 81 | end tell" 82 | } 83 | 84 | on_exit () { 85 | set_bg "$SESS" "" 86 | rm "/tmp/iTermBG.$$.png" 87 | } 88 | trap on_exit EXIT 89 | 90 | convert \ 91 | -size "$DIMENSIONS" xc:"$BG_COLOR" -gravity "$GRAVITY" -fill "$FG_COLOR" -style "$FONT_STYLE" -pointsize "$FONT_SIZE" -antialias -draw "text $OFFSET1 '${RESOLVED_HOSTNAME:-$HOSTNAME}'" \ 92 | -pointsize "$FONT_SIZE" -draw "text $OFFSET2 '${RESOLVED_IP:-}'" -alpha Off \ 93 | "/tmp/iTermBG.$$.png" 94 | set_bg "$SESS" "/tmp/iTermBG.$$.png" 95 | 96 | /usr/bin/ssh "$@" 97 | -------------------------------------------------------------------------------- /gifify/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:8 2 | 3 | RUN echo "deb http://ftp.uk.debian.org/debian jessie-backports main" >> /etc/apt/sources.list 4 | RUN set -ex && \ 5 | apt-get update && \ 6 | apt-get -y install ffmpeg unzip imagemagick curl 7 | 8 | # install fork of gifsicle with better lossless gif support 9 | RUN set -ex && \ 10 | curl -OJL https://github.com/pornel/giflossy/releases/download/lossy%2F1.82.1/gifsicle-1.82.1-lossy.zip && \ 11 | unzip gifsicle-1.82.1-lossy.zip -d gifsicle && \ 12 | mv gifsicle/linux/gifsicle-debian6 /usr/local/bin/gifsicle 13 | 14 | # install gifify 15 | RUN npm i -g gifify 16 | 17 | # run gifify in /data (requires user to mount their source folder in /data) 18 | WORKDIR /data 19 | 20 | ENTRYPOINT ["gifify"] 21 | -------------------------------------------------------------------------------- /gifify/README.md: -------------------------------------------------------------------------------- 1 | # Gifify 2 | 3 | A simple utility for creating an optimized gif from a Quicktime movie. 4 | 5 | Based off of the [gifsicle-lossy fork](https://github.com/kornelski/giflossy). 6 | -------------------------------------------------------------------------------- /gifify/gifify.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | if ! docker images | grep -q "gifify"; then 4 | echo "Gifify docker image not found, building..." 5 | docker build -t gifify:v1 . 6 | fi 7 | 8 | [ $# -lt 1 ] && { 9 | echo "This is a utility for converting .mov files to optimized gifs." 10 | echo "Usage: $0 "; 11 | exit 1; 12 | } 13 | 14 | FILENAME="${1%.*}" 15 | echo "Creating $FILENAME.gif..." 16 | docker run -it --rm -v "$(pwd):/data" gifify:v1 "$FILENAME.mov" -o "$FILENAME.gif" 17 | -------------------------------------------------------------------------------- /hammerspoon/init.lua: -------------------------------------------------------------------------------- 1 | -- Load Extensions 2 | local application = require "hs.application" 3 | local window = require "hs.window" 4 | local hotkey = require "hs.hotkey" 5 | local keycodes = require "hs.keycodes" 6 | local fnutils = require "hs.fnutils" 7 | local alert = require "hs.alert" 8 | local screen = require "hs.screen" 9 | local grid = require "hs.grid" 10 | local hints = require "hs.hints" 11 | local appfinder = require "hs.appfinder" 12 | local tabs = require "hs.tabs" 13 | 14 | local definitions = nil 15 | -- A global variable for the Hyper Mode 16 | local hyperKey = nil 17 | local hyperKeyStub = nil 18 | 19 | local watchers = {} 20 | 21 | 22 | -- Allow saving focus, then restoring it later. 23 | auxWin = nil 24 | function saveFocus() 25 | auxWin = window.focusedWindow() 26 | alert.show("Window '" .. auxWin:title() .. "' saved.") 27 | end 28 | function focusSaved() 29 | if auxWin then 30 | auxWin:focus() 31 | end 32 | end 33 | 34 | -- 35 | -- Hotkeys 36 | -- 37 | local hotkeys = {} 38 | function createHotkeys() 39 | bindHyperKeyStub() 40 | 41 | for key, fun in pairs(definitions) do 42 | if key == "." then alert.show("Key bound to `.`, which is an internal OS X shortcut for sysdiagnose.") end 43 | local mod = hyper 44 | -- Any definitions ending with c are cmd defs 45 | if string.len(key) == 2 and string.sub(key,2,2) == "c" then 46 | key = string.sub(key,1,1) 47 | mod = {"cmd"} 48 | -- Ending with l are ctrl 49 | elseif string.len(key) == 2 and string.sub(key,2,2) == "l" then 50 | key = string.sub(key,1,1) 51 | mod = {"ctrl"} 52 | elseif string.len(key) == 4 and string.sub(key,2,4) == "raw" then 53 | key = string.sub(key,1,1) 54 | mod = {} 55 | end 56 | 57 | -- Sierra hack 58 | if mod == hyper then 59 | -- Bind to the existing F17 stub 60 | hyperKeyStub:bind({}, key, nil, fun) 61 | else 62 | local hk = hotkey.new(mod, key, fun) 63 | table.insert(hotkeys, hk) 64 | hk:enable() 65 | end 66 | end 67 | end 68 | 69 | function rebindHotkeys() 70 | for i, hk in ipairs(hotkeys) do 71 | hk:disable() 72 | end 73 | hyperKey:delete() 74 | hyperKeyStub:delete() 75 | 76 | hotkeys = {} 77 | createHotkeys() 78 | alert.show("Rebound Hotkeys") 79 | end 80 | 81 | function bindHyperKeyStub() 82 | -- Create a new F17 global 83 | hyperKeyStub = hs.hotkey.modal.new({}, "F17") 84 | 85 | -- Enter Hyper Mode when F18 (Hyper/Capslock) is pressed 86 | pressedF18 = function() 87 | hyperKeyStub.triggered = false 88 | hyperKeyStub:enter() 89 | end 90 | 91 | -- Leave Hyper Mode when F18 (Hyper/Capslock) is pressed, 92 | -- send ESCAPE if no other keys are pressed. 93 | releasedF18 = function() 94 | hyperKeyStub:exit() 95 | if not hyperKeyStub.triggered then 96 | hs.eventtap.keyStroke({}, 'ESCAPE') 97 | end 98 | end 99 | 100 | -- Bind the Hyper key 101 | hyperKey = hs.hotkey.bind({}, 'F18', pressedF18, releasedF18) 102 | end 103 | 104 | -- 105 | -- Grid 106 | -- 107 | 108 | -- HO function for automating moving a window to a predefined position. 109 | local gridset = function(frame) 110 | return function() 111 | local win = window.focusedWindow() 112 | if win then 113 | grid.set(win, frame, win:screen()) 114 | else 115 | alert.show("No focused window.") 116 | end 117 | end 118 | end 119 | 120 | function applyPlace(win, place) 121 | local scrs = screen.allScreens() 122 | local scr = scrs[place[1]] 123 | grid.set(win, place[2], scr) 124 | end 125 | 126 | function applyLayout(layout) 127 | return function() 128 | alert.show("Applying Layout.") 129 | -- Sort table, table keys last so they are sorted independently of the main app 130 | table.sort(layout, function (op1, op2) 131 | local type1, type2 = type(op1), type(op2) 132 | if type1 ~= type2 then 133 | return type1 < type2 134 | else 135 | return op1 < op2 136 | end 137 | end) 138 | 139 | for appName, place in pairs(layout) do 140 | -- Two types we allow: table, which is {appName, windowTitle}, or just the app itself 141 | if type(appName) == 'table' then 142 | local parentAppName = appName[1] 143 | local windowPattern = appName[2] 144 | alert(windowPattern) 145 | local window = appfinder.windowFromWindowTitlePattern(windowPattern) 146 | if window then 147 | applyPlace(window, place) 148 | end 149 | else 150 | alert(appName) 151 | local app = appfinder.appFromName(appName) 152 | if app then 153 | for i, win in ipairs(app:allWindows()) do 154 | applyPlace(win, place) 155 | end 156 | end 157 | end 158 | end 159 | end 160 | end 161 | 162 | -- 163 | -- Conf 164 | -- 165 | 166 | -- 167 | -- Utility 168 | -- 169 | 170 | function reloadConfig(files) 171 | for _,file in pairs(files) do 172 | if file:sub(-4) == ".lua" then 173 | hs.reload() 174 | return 175 | end 176 | end 177 | end 178 | 179 | -- Prints out a table like a JSON object. Utility 180 | function serializeTable(val, name, skipnewlines, depth) 181 | skipnewlines = skipnewlines or false 182 | depth = depth or 0 183 | 184 | local tmp = string.rep(" ", depth) 185 | 186 | if name then tmp = tmp .. name .. " = " end 187 | 188 | if type(val) == "table" then 189 | tmp = tmp .. "{" .. (not skipnewlines and "\n" or "") 190 | 191 | for k, v in pairs(val) do 192 | tmp = tmp .. serializeTable(v, k, skipnewlines, depth + 1) .. "," .. (not skipnewlines and "\n" or "") 193 | end 194 | 195 | tmp = tmp .. string.rep(" ", depth) .. "}" 196 | elseif type(val) == "number" then 197 | tmp = tmp .. tostring(val) 198 | elseif type(val) == "string" then 199 | tmp = tmp .. string.format("%q", val) 200 | elseif type(val) == "boolean" then 201 | tmp = tmp .. (val and "true" or "false") 202 | else 203 | tmp = tmp .. "\"[inserializeable datatype:" .. type(val) .. "]\"" 204 | end 205 | 206 | return tmp 207 | end 208 | 209 | function string.starts(str,Start) 210 | return string.sub(str,1,string.len(Start))==Start 211 | end 212 | 213 | -- 214 | -- WiFi 215 | -- 216 | 217 | -- local home = {["Lemonparty"] = TRUE, ["Lemonparty 5GHz"] = TRUE} 218 | -- local lastSSID = hs.wifi.currentNetwork() 219 | 220 | -- function ssidChangedCallback() 221 | -- newSSID = hs.wifi.currentNetwork() 222 | 223 | -- if newSSID == lastSSID then return end 224 | 225 | -- if home[newSSID] and not home[lastSSID] then 226 | -- -- We just joined our home WiFi network 227 | -- hs.audiodevice.defaultOutputDevice():setVolume(25) 228 | -- elseif not home[newSSID] and home[lastSSID] then 229 | -- -- We just departed our home WiFi network 230 | -- hs.audiodevice.defaultOutputDevice():setVolume(0) 231 | -- end 232 | 233 | -- local messages = appfinder.appFromName('Messages') 234 | -- messages:selectMenuItem("Log In") 235 | 236 | -- lastSSID = newSSID 237 | -- end 238 | 239 | -- local wifiWatcher = hs.wifi.watcher.new(ssidChangedCallback) 240 | -- wifiWatcher:start() 241 | 242 | -- 243 | -- Sound 244 | -- 245 | 246 | -- Mute on jack in/out 247 | function audioCallback(uid, eventName, eventScope, channelIdx) 248 | if eventName == 'jack' then 249 | alert("Jack changed, muting.", 1) 250 | hs.audiodevice.defaultOutputDevice():setVolume(0) 251 | end 252 | end 253 | 254 | 255 | -- Watch device; mute when headphones unplugged. 256 | local defaultDevice = hs.audiodevice.defaultOutputDevice() 257 | defaultDevice:watcherCallback(audioCallback); 258 | defaultDevice:watcherStart(); 259 | 260 | -- 261 | -- Battery / Power 262 | -- 263 | 264 | -- Disable spotlight indexing while on battery. 265 | -- 266 | -- In order for this to work, add this to a file in `/etc/sudoers.d/`: 267 | -- ALL=(root) NOPASSWD: /usr/bin/mdutil -i on / 268 | -- ALL=(root) NOPASSWD: /usr/bin/mdutil -i off / 269 | -- Verify with `sudo -l` 270 | -- 271 | -- 272 | -- Removing this for now, appears to just churn CPU 273 | -- 274 | -- local currentPowerSource = nil; 275 | -- function batteryWatchUnplugged() 276 | -- newPowerSource = hs.battery.powerSource(); 277 | -- if newPowerSource ~= currentPowerSource then 278 | -- alert(string.format("New power source: %s", newPowerSource), 1); 279 | 280 | -- function taskCb(code, stdout, stderr) 281 | -- if code ~= 0 then 282 | -- alert("Failed, check console."); 283 | -- end 284 | -- print("stdout: "..stdout); 285 | -- print("stderr: "..stderr) 286 | -- end 287 | -- if newPowerSource == 'Battery Power' then 288 | -- alert("Disabling spotlight.", 1); 289 | -- hs.task.new("/usr/bin/sudo", taskCb, {"/usr/bin/mdutil", "-i", "off", "/"}):start() 290 | -- else 291 | -- alert("Enabling spotlight.", 1); 292 | -- hs.task.new("/usr/bin/sudo", taskCb, {"/usr/bin/mdutil", "-i", "on", "/"}):start() 293 | -- end 294 | -- currentPowerSource = newPowerSource; 295 | -- end 296 | -- end 297 | -- local batteryWatcher = hs.battery.watcher.new(batteryWatchUnplugged):start(); 298 | -- batteryWatchUnplugged(); 299 | 300 | -- 301 | -- Application overrides 302 | -- 303 | 304 | 305 | -- 306 | -- Fix Slack's channel switching. 307 | -- This rebinds ctrl-tab and ctrl-shift-tab back to switching channels, 308 | -- which is what they did before the Teams update. 309 | -- 310 | -- Slack only provides alt+up/down for switching channels, (and the cmd-t switcher, 311 | -- which is buggy) and have 3 (!) shortcuts for switching teams, most of which are 312 | -- the usual tab switching shortcuts in every other app. 313 | -- 314 | -- This basically turns the tab switching shortcuts into LimeChat shortcuts, which very smartly 315 | -- uses the brackets to switch any channel, and ctrl-(shift)-tab to switch unreads. 316 | local slackKeybinds = { 317 | hotkey.new({"ctrl"}, "tab", function() 318 | hs.eventtap.keyStroke({"alt", "shift"}, "Down") 319 | end), 320 | hotkey.new({"ctrl", "shift"}, "tab", function() 321 | hs.eventtap.keyStroke({"alt", "shift"}, "Up") 322 | end), 323 | hotkey.new({"cmd", "shift"}, "[", function() 324 | hs.eventtap.keyStroke({"alt"}, "Up") 325 | end), 326 | hotkey.new({"cmd", "shift"}, "]", function() 327 | hs.eventtap.keyStroke({"alt"}, "Down") 328 | end), 329 | -- Disables cmd-w entirely, which is so annoying on slack 330 | hotkey.new({"cmd"}, "w", function() return end) 331 | } 332 | local slackWatcher = hs.application.watcher.new(function(name, eventType, app) 333 | if eventType ~= hs.application.watcher.activated then return end 334 | local fnName = name == "Slack" and "enable" or "disable" 335 | for i, keybind in ipairs(slackKeybinds) do 336 | -- Remember that lua is weird, so this is the same as keybind.enable() in JS, `this` is first param 337 | keybind[fnName](keybind) 338 | end 339 | end) 340 | slackWatcher:start() 341 | 342 | -- 343 | -- Fix Skype's channel switching. 344 | -- 345 | local skypeKeybinds = { 346 | hotkey.new({"ctrl"}, "tab", function() 347 | hs.eventtap.keyStroke({"alt", "cmd"}, "Right") 348 | end), 349 | hotkey.new({"ctrl", "shift"}, "tab", function() 350 | hs.eventtap.keyStroke({"alt", "cmd"}, "Left") 351 | end) 352 | } 353 | local skypeWatcher = hs.application.watcher.new(function(name, eventType, app) 354 | if eventType ~= hs.application.watcher.activated then return end 355 | local fnName = name == "Skype" and "enable" or "disable" 356 | for i, keybind in ipairs(skypeKeybinds) do 357 | -- Remember that lua is weird, so this is the same as keybind.enable() in JS, `this` is first param 358 | keybind[fnName](keybind) 359 | end 360 | end) 361 | skypeWatcher:start() 362 | 363 | -- 364 | -- INIT! 365 | -- 366 | 367 | function init() 368 | -- Bind hotkeys. 369 | createHotkeys() 370 | -- If we hook up a keyboard, rebind. 371 | keycodes.inputSourceChanged(rebindHotkeys) 372 | -- Automatically reload config when it changes. 373 | hs.pathwatcher.new(os.getenv("HOME") .. "/.hammerspoon/", reloadConfig):start() 374 | -- Doesn't work with symlinks... so go straight to the git repo. 375 | hs.pathwatcher.new(os.getenv("HOME") .. "/git/oss/init/hammerspoon/", reloadConfig):start() 376 | 377 | -- Prevent system sleep, only if connected to AC power. (sleepType, shouldPrevent, batteryToo) 378 | hs.caffeinate.set('system', true, false) 379 | 380 | alert.show("Reloaded.", 1) 381 | end 382 | 383 | -- Grid config ================================= 384 | 385 | hs.window.animationDuration = 0.2; 386 | -- hints.style = "vimperator" 387 | -- Set grid size. 388 | has4k = hs.screen('3840x2160') 389 | 390 | grid.GRIDWIDTH = has4k and 32 or 4 391 | grid.GRIDHEIGHT = has4k and 18 or 4 392 | grid.MARGINX = 0 393 | grid.MARGINY = 0 394 | local gw = grid.GRIDWIDTH 395 | local gh = grid.GRIDHEIGHT 396 | 397 | local goMiddle = {x = gw/4, y = gh/4, w = gw/2, h = gh/2} 398 | local goLeft = {x = 0, y = 0, w = gw/2, h = gh} 399 | local goRight = {x = gw/2, y = 0, w = gw/2, h = gh} 400 | local goTopLeft = {x = 0, y = 0, w = gw/2, h = gh/2} 401 | local goTopRight = {x = gw/2, y = 0, w = gw/2, h = gh/2} 402 | local goBottomLeft = {x = 0, y = gh/2, w = gw/2, h = gh/2} 403 | local goBottomRight = {x = gw/2, y = gh/2, w = gw/2, h = gh/2} 404 | local gobig = {x = 0, y = 0, w = gw, h = gh} 405 | 406 | -- Saved layout. TODO 407 | local layout2 = { 408 | ["Sublime Text"] = {1, {x = 0, y = 0, h = 12, w = 11}}, 409 | LimeChat = {1, {x = 0, y = 12, h = 6, w = 5}}, 410 | ["Google Chrome"] = {1, {x = 11, y = 7, h = 11, w = 13}}, 411 | [{"Google Chrome", "Developer Tools.*"}] = {1, {x = 24, y = 7, h = 11, w = 8}}, 412 | Slack = {1, {x = 24, y = 0, h = 9, w = 8}}, 413 | Postbox = {1, {x = 24, y = 9, h = 9, w = 8}}, 414 | Skype = {1, {x = 6, y = 12, h = 6, w = 5}}, 415 | Telegram = {1, {x = 6, y = 12, h = 6, w = 5}}, 416 | iTerm2 = {1, {x = 11, y = 0, h = 7, w = 13}}, 417 | Messages = {1, {x = 26, y = 12, w = 6, h = 6}}, 418 | Finder = {1, {x = 22, y = 6, w = 10, h = 6}}, 419 | Postico = {1, {x = 0, y = 12, w = 6, h = 6}}, 420 | } 421 | 422 | -- Watch out, cmd-opt-ctrl-shift-period is an actual OS X shortcut for running sysdiagose 423 | definitions = { 424 | r = hs.reload, 425 | -- Not using 426 | -- [";"] = saveFocus, 427 | -- a = focusSaved, 428 | 429 | -- h = gridset(godMiddle), 430 | Left = gridset(goLeft), 431 | Up = grid.maximizeWindow, 432 | Right = gridset(goRight), 433 | 434 | ['1'] = gridset(goTopLeft), 435 | ['3'] = gridset(goTopRight), 436 | ['5'] = gridset(goMiddle), 437 | ['7'] = gridset(goBottomLeft), 438 | ['9'] = gridset(goBottomRight), 439 | 440 | -- ["'"] = function() alert.show(serializeTable(grid.get(window.focusedWindow())), 30) end, 441 | g = has4k and applyLayout(layout2) or nil, 442 | 443 | ["'"] = grid.pushWindowPrevScreen, 444 | [";"] = grid.pushWindowNextScreen, 445 | ["\\"] = grid.show, -- way too fucked with our grid sizes 446 | -- q = function() hs.application.find("Hammerspoon"):kill() end, 447 | 448 | -- Shows all sublime windows 449 | -- e = function() hints.windowHints(hs.application.find("Sublime Text"):allWindows()) end, 450 | -- Focuses these apps 451 | q = function() hs.application.find("Sublime Text"):mainWindow():focus() end, 452 | w = function() hs.application.find("iTerm2"):mainWindow():focus() end, 453 | c = function() hs.application.find("Google Chrome"):mainWindow():focus() end, 454 | s = function() hs.application.find("Slack"):mainWindow():focus() end, 455 | -- Show hints for all window 456 | f = function() hints.windowHints(nil) end, 457 | -- Shows all windows for current app 458 | v = function() hints.windowHints(window.focusedWindow():application():allWindows()) end, 459 | 460 | o = function() hs.execute(os.getenv("HOME") .. "/bin/subl ".. os.getenv("HOME") .."/.hammerspoon/init.lua") end, 461 | -- 462 | -- GRID 463 | -- 464 | 465 | -- move windows 466 | h = grid.pushWindowLeft, 467 | j = grid.pushWindowDown, 468 | l = grid.pushWindowRight, 469 | [";"] = grid.pushWindowUp, 470 | 471 | -- resize windows 472 | ["="] = grid.resizeWindowTaller, 473 | ["-"] = grid.resizeWindowShorter, 474 | ["["] = grid.resizeWindowThinner, 475 | ["]"] = grid.resizeWindowWider, 476 | 477 | m = grid.maximizeWindow, 478 | n = function() grid.snap(window.focusedWindow()) end, 479 | 480 | -- cmd+\ should run cmd-tab (keyboard symmetry) 481 | ["\\c"] = function() hs.eventtap.keyStroke({"cmd"},"tab") end 482 | } 483 | 484 | 485 | -- 486 | -- TABS 487 | -- Currently crashes on sublime text. 488 | -- 489 | -- for i=1,6 do 490 | -- definitions[tostring(i)] = function() 491 | -- local app = application.frontmostApplication() 492 | -- tabs.focusTab(app,i) 493 | -- end 494 | -- end 495 | 496 | init() 497 | --------------------------------------------------------------------------------