├── .gitignore ├── LICENSE ├── README.md ├── cmdtab.png └── init.lua /.gitignore: -------------------------------------------------------------------------------- 1 | *.sw[po] 2 | *.orig 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Sasha Matijasic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | hammerspoon-alttab 2 | ================== 3 | 4 | alttab is [Hammerspoon](http://www.hammerspoon.org/) powered alternative window 5 | switcher. 6 | 7 | Why? 8 | ---- 9 | 10 | As a developer, I spend most of my time in terminal and browser, constantly 11 | switching between them. This extension has been written so I can avoid 12 | switching to Finder, somehow it always finds (hence a name) a way to annoy me. 13 | 14 | ![Finder is always there](cmdtab.png) 15 | 16 | Installation 17 | ------------ 18 | 19 | * Install [Hammerspoon](http://www.hammerspoon.org/) 20 | * git clone this repository to ~/.hammerspoon config directory: 21 | 22 | `git clone https://github.com/selectnull/hammerspoon-alttab.git ~/.hammerspoon/hammerspoon-alttab` 23 | 24 | Configuration 25 | ------------- 26 | 27 | Add these lines to your `~/.hammerspoon/init.lua` file: 28 | 29 | local alttab = require("hammerspoon-alttab") 30 | alttab.registerDefaultBindings('.') 31 | 32 | This will set the following default key bindings: 33 | 34 | * `cmd+.` to switch to next window 35 | * `cmd+alt+.` to add or remove currently focused window to cycle list 36 | * `cmd+ctrl+alt+.` to display debug info in Hammerspoon console 37 | 38 | Instead of `.` you may use any other key, just pass it to 39 | `registerDefaultBindings` method. 40 | 41 | You might also not like default modifiers used by `registerDefaultBindings`. In 42 | that case, you can register your own: 43 | 44 | local alttab = require("hammerspoon-alttab") 45 | hs.hotkey.bind({"alt"}, "tab", alttab.focusNext) 46 | hs.hotkey.bind({"alt", "shift"}, "tab", alttab.toggleWindow) 47 | hs.hotkey.bind({"alt", "ctrl", "shift"}, "tab", alttab.debug) 48 | 49 | Contribution 50 | ------------ 51 | 52 | Comments, bug reports, and pull requests are welcomed. 53 | 54 | Released under MIT license. 55 | -------------------------------------------------------------------------------- /cmdtab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/selectnull/hammerspoon-alttab/0e6841781b7242fe1c50b6834737d2356d09ca73/cmdtab.png -------------------------------------------------------------------------------- /init.lua: -------------------------------------------------------------------------------- 1 | local mod = {} 2 | 3 | -- this is local variable that keeps a list of window IDs to cycle thru 4 | local _windows = {} 5 | 6 | function focusIfExist (id, index) 7 | win = hs.window.find(id) 8 | if win then 9 | win:focus() 10 | else 11 | if index ~= nil then 12 | table.remove(_windows, index) 13 | mod.focusNext() 14 | end 15 | end 16 | end 17 | 18 | function mod.focusNext () 19 | local currentId = hs.window.focusedWindow():id() 20 | local found = false 21 | 22 | -- if there are no windows to cycle thru, don't do anything 23 | if #_windows > 0 then 24 | -- if there is only 1 window in the list, focus it 25 | if #_windows == 1 then 26 | focusIfExist(_windows[1]) 27 | else 28 | -- there are more than 1 window in the list 29 | -- loop thru the list and find the currently focused window 30 | -- and focus the next one in the line 31 | for i, v in ipairs(_windows) do 32 | -- trying to match current window 33 | if v == currentId then 34 | -- we are at the last element so we need 35 | -- to focus the first window 36 | if i == #_windows then 37 | focusIfExist(_windows[1], i) 38 | -- or we just focus the next window in the list 39 | else 40 | focusIfExist(_windows[i+1], i) 41 | end 42 | found = true 43 | end 44 | end 45 | -- currently focused window is not in the list so just 46 | -- focus the first window in the list 47 | if not found then 48 | hs.window.find(_windows[1]):focus() 49 | end 50 | end 51 | end 52 | end 53 | 54 | function mod.toggleWindow () 55 | local currentId = hs.window.focusedWindow():id() 56 | local removed = false 57 | 58 | for i, v in ipairs(_windows) do 59 | if v == currentId then 60 | table.remove(_windows, i) 61 | removed = true 62 | end 63 | end 64 | if not removed then 65 | table.insert(_windows, currentId) 66 | hs.console.printStyledtext(currentId) 67 | end 68 | end 69 | 70 | function mod.debug () 71 | for i, v in ipairs(_windows) do 72 | local win = hs.window.find(v) 73 | if win then 74 | local title = win:id() .. ' ' .. win:title() .. 75 | ' (' .. win:application():name() .. ')' 76 | hs.console.printStyledtext(title) 77 | else 78 | table.remove(_windows, i) 79 | end 80 | end 81 | end 82 | 83 | function mod.registerDefaultBindings (key) 84 | hs.hotkey.bind({"cmd"}, key, mod.focusNext) 85 | hs.hotkey.bind({"cmd", "alt"}, key, mod.toggleWindow) 86 | hs.hotkey.bind({"cmd", "ctrl", "alt"}, key, mod.debug) 87 | end 88 | 89 | return mod 90 | --------------------------------------------------------------------------------