├── LICENSE.txt ├── README.md ├── application.lua ├── config.json ├── config.lua ├── config_default.lua ├── docs.md ├── docs.org ├── examples └── config.json ├── init.lua ├── menubar.lua ├── spaces.lua ├── utilities.lua └── window.lua /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Rafi Khan 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 | # zwm 2 | macOS window manager running on Hammerspoon 3 | 4 | This project aims to work with macOS rather than fight it. 5 | 6 | Screenshot of the spaces bar. 7 | 8 | ![Screenshot](https://i.imgur.com/T1P2NkQ.png) 9 | 10 | ### Instructions 11 | 1. Install Hammerspoon 12 | 2. Download latest release 13 | 3. Open zwm.spoon 14 | 4. Install [asm.spaces](https://github.com/asmagill/hs._asm.undocumented.spaces/tree/eedc00c6796e4734e6dc161a614cb34a64f4abec) from the instructions on the page 15 | 5. Start hammerspoon 16 | 17 | ### Features 18 | **Spaces** 19 | - [x] Spaces menubar with current space indicator 20 | - [x] Switch to spaces from keyboard 21 | - [x] Send applications to spaces 22 | - [ ] Start application in designated spaces 23 | 24 | 25 | **Window Management** 26 | - [x] Move around windows (bit buggy) 27 | - [x] Monocle tiling 28 | - [] Split tiling 29 | 30 | [Trello Board](https://trello.com/b/Q8RqZ6CI) 31 | -------------------------------------------------------------------------------- /application.lua: -------------------------------------------------------------------------------- 1 | -- applications 2 | -- deals with assigning applications to the correct space 3 | dofile(zwm.spoonPath.."utilities.lua") 4 | 5 | local function application_deactivated(app, name) 6 | -- TODO: Application has been deactivated 7 | end 8 | local function application_activated(app, name) 9 | -- TODO: Application has been deactivated 10 | end 11 | 12 | local function application_hidden(app, name) 13 | -- TODO: Application has been hidden 14 | end 15 | 16 | local function space(app, space) 17 | return function() 18 | return send_to_space(app, space) 19 | end 20 | end 21 | 22 | local function application_launched(app, name) 23 | -- TODO: Application has been launched 24 | local mappings = config["applications"] 25 | local match = match_item(name, mappings) 26 | 27 | -- TODO: error checking workspaces 28 | 29 | if match ~= nil then 30 | print(name) 31 | local t = hs.timer.doAfter(0.1, space(app, match.f)) 32 | -- send_to_space(app, match.f) 33 | end 34 | end 35 | 36 | local function application_launching(app, name) 37 | -- TODO: Application has been launching 38 | 39 | end 40 | 41 | local function application_terminated(app, name) 42 | -- TODO: Application has been terminated 43 | end 44 | 45 | local function application_unhidden(app, name) 46 | -- TODO: Application has been unhidden 47 | end 48 | 49 | -- calback 50 | local function application_event(name, event, app) 51 | if event == hs.application.watcher.activated then 52 | application_activated(app, name) 53 | 54 | elseif event == hs.application.watcher.deactivated then 55 | application_deactivated(app, name) 56 | 57 | elseif event == hs.application.watcher.hidden then 58 | application_hidden(app, name) 59 | elseif event == hs.application.watcher.launched then 60 | application_launched(app, name) 61 | elseif event == hs.application.watcher.launching then 62 | application_launching(app, name) 63 | elseif event == hs.application.watcher.terminated then 64 | application_terminated(app, name) 65 | elseif event == hs.application.watcher.unhidden then 66 | application_unhidden(app, name) 67 | end 68 | end 69 | 70 | zwm.application_watcher = hs.application.watcher.new(application_event):start() 71 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "autoReload" : true, 3 | "mods": { 4 | "mod1": "alt", 5 | "mod2": "shift" 6 | }, 7 | "silent" : false, 8 | "spaces": { 9 | "1": "", 10 | "2": "" 11 | }, 12 | "applications": { 13 | "SourceTree" : 3 14 | }, 15 | "key_bindings": { 16 | "terminal" : { 17 | "key": "return", 18 | "type": "application", 19 | "action": "/Applications/iTerm.app" 20 | }, 21 | "window_left" : { 22 | "key" : "h", 23 | "type" : "window_management", 24 | "action": "window_left" 25 | }, 26 | "window_right" : { 27 | "key" : "l", 28 | "type" : "window_management", 29 | "action": "window_right" 30 | }, 31 | "window_up" : { 32 | "key" : "k", 33 | "type" : "window_management", 34 | "action": "window_up" 35 | }, 36 | "window_down" : { 37 | "key" : "j", 38 | "type" : "window_management", 39 | "action": "window_down" 40 | }, 41 | "application_next" : { 42 | "key" : "]", 43 | "type" : "window_management", 44 | "action": "application_next" 45 | }, 46 | "application_previous" : { 47 | "key" : "[", 48 | "type" : "window_management", 49 | "action": "application_previous" 50 | }, 51 | "window_next" : { 52 | "key" : "mod1-mod2-]", 53 | "type" : "window_management", 54 | "action": "window_next" 55 | }, 56 | "window_previous" : { 57 | "key" : "mod1-mod2-[", 58 | "type" : "window_management", 59 | "action": "window_previous" 60 | }, 61 | "window_tiling_mode" : { 62 | "key" : "t", 63 | "type": "window_management", 64 | "action": "window_tiling_toggle" 65 | }, 66 | "space_next" : { 67 | "key" : "mod1-mod2-]", 68 | "type": "space", 69 | "action": "space_next" 70 | }, 71 | "space_previous" : { 72 | "key" : "mod1-mod2-[", 73 | "type": "space", 74 | "action": "space_previous" 75 | }, 76 | "application_quit" : { 77 | "key": "q", 78 | "type": "application", 79 | "action":"application_quit" 80 | }, 81 | "space_change" : { 82 | "key": "mod1", 83 | "type": "space", 84 | "action": "space_change" 85 | }, 86 | "space_move" : { 87 | "key": "mod1-mod2", 88 | "type": "space", 89 | "action": "space_move" 90 | } 91 | }, 92 | "window_management" : { 93 | "mode" : "monocle", 94 | "gap" : "10", 95 | "move_amount" : 30 96 | }, 97 | "bar": { 98 | "size": 15, 99 | "color_active" : "#FFAA00", 100 | "color_inactive" : "#ffffff", 101 | "separator" : " | " 102 | } 103 | } -------------------------------------------------------------------------------- /config.lua: -------------------------------------------------------------------------------- 1 | dofile(zwm.spoonPath.."utilities.lua") 2 | 3 | -- Variables 4 | config_file_path = os.getenv("HOME").. "/.zwm" 5 | 6 | -- Parse configuration 7 | local function parse(config) 8 | 9 | if config ~= nil then 10 | 11 | print("Configuration:") 12 | print(hs.inspect(config)) 13 | 14 | local c = config 15 | 16 | -- Key bindings 17 | -- load mod keys 18 | 19 | if c.keybindings ~= nil then 20 | 21 | local k = c.keybindings 22 | 23 | if k.mods ~= nil then 24 | for index = 1, #k.mods do 25 | if exact_in_table(k.mods[index], hs.keycodes.map) == nil then 26 | log("mod: '".. k.mods[index] .."' not supported") 27 | end 28 | end 29 | 30 | else 31 | log("no mods found") 32 | end 33 | 34 | end 35 | 36 | -- Spaces 37 | 38 | 39 | -- reloads on config file change 40 | if c.autoReload ~= nil and c.autoReload == true then 41 | local config_watcher = hs.pathwatcher.new(config_file_path, load_config):start() 42 | end 43 | 44 | -- TODO: error checking 45 | zwm.config = config 46 | 47 | end 48 | 49 | 50 | end 51 | 52 | function load_config(config) 53 | parse(config) 54 | hs.alert.show("loaded config") 55 | end 56 | 57 | -------------------------------------------------------------------------------- /config_default.lua: -------------------------------------------------------------------------------- 1 | -- default configuration for zwm 2 | -- serves as an api documentation for now 3 | 4 | dofile(zwm.spoonPath.."utilities.lua") 5 | 6 | local c = {} 7 | 8 | -- General 9 | c.autoReload = true -- automatically reloads the configuration file 10 | 11 | -- Window management 12 | c.wm = {} 13 | 14 | -- Key bindings 15 | -- mod key 16 | local keybindings = {} 17 | 18 | keybindings.mods = {} 19 | keybindings.mods[1] = 'alt' 20 | 21 | c.keybindings = keybindings 22 | 23 | -- Spaces 24 | local spaces = {} 25 | spaces.tags = {} 26 | spaces[1] = '1' 27 | spaces[2] = '2' 28 | 29 | c.spaces = spaces 30 | 31 | 32 | -- UI 33 | local ui = {} 34 | 35 | ui.enabled = true 36 | 37 | ui.bar = {} 38 | ui.bar.color_active = "FFAA00" 39 | ui.bar.color_inactive = "#ffffff" 40 | ui.bar.separator = " | " 41 | ui.bar.fontsize = 15 42 | 43 | c.ui = ui 44 | 45 | 46 | 47 | zwm.config_default = c 48 | 49 | function get_default() 50 | return deepcopy(zwm.config_default) 51 | end 52 | -------------------------------------------------------------------------------- /docs.md: -------------------------------------------------------------------------------- 1 | ## Docs 2 | 3 | Note: the docuemntation is a WIP 4 | 5 | ### Configuration 6 | 7 | ####autoReload#### 8 | Will reload zwm on change to configuration if set to true. 9 | 10 | ####mods#### 11 | A list of modifier keys to be used in defining bindings for zwm. 12 | 13 | // TODO: This should be an array? 14 | 15 | To create a mod key simply suffix "mod" with a number of your choosing and then the corresponding key. 16 | 17 | ```json 18 | ... 19 | "mods" : { 20 | "mod1" : "alt" // Defines the first modifier to be alt 21 | } 22 | ... 23 | ``` 24 | 25 | This can then be used when defining short cuts 26 | 27 | ```json 28 | ... 29 | "key_bindings" : { 30 | "terminal" : { 31 | ... 32 | "key" : "mod1-enter" // Alt-enter will open the terminal 33 | ... 34 | } 35 | } 36 | ... 37 | 38 | ``` 39 | 40 | ###key_bindings### 41 | 42 | TODO: This should be an array? 43 | 44 | You can create different types of keybindings which perform different tasks such as opening an application or interacting with windows. 45 | 46 | A key binding is defined with a name, a key, a type and an action. The action could be different depending on the type. 47 | 48 | ```json 49 | "key_bindings" : { 50 | "terminal" : { 51 | "key" : "mod1-enter", // Alt-enter will open the terminal at the path provided 52 | "type" : "application", 53 | "action" : "/Applications/iTerm.app" 54 | } 55 | } 56 | ``` 57 | 58 | 59 | -------------------------------------------------------------------------------- /docs.org: -------------------------------------------------------------------------------- 1 | ** API 2 | *** Keybindings 3 | Add key bindings that can perform actions 4 | 5 | - Modifier keys 6 | 7 | **** Actions 8 | - Application 9 | - Execute command 10 | - Window management 11 | 12 | *** Spaces 13 | - Each space can get assigned a string 14 | -------------------------------------------------------------------------------- /examples/config.json: -------------------------------------------------------------------------------- 1 | /Users/z3t0/.zwm/config.json -------------------------------------------------------------------------------- /init.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | zwm 3 | 4 | Copyright (C) 2017 Rafi Khan 5 | Licensed under the MIT License 6 | 7 | --]] 8 | 9 | -- Spoon information 10 | zwm = {} 11 | zwm.name = "zwm" 12 | zwm.version = "0.0.1" 13 | zwm.author = "Rafi Khan" 14 | zwm.license = "MIT" 15 | zwm.homepage = "https://github.com/z3t0/zwm" 16 | 17 | -- Internal function used to find our location, so we know where to load files from 18 | local function script_path() 19 | local str = debug.getinfo(2, "S").source:sub(2) 20 | return str:match("(.*/)") 21 | end 22 | 23 | zwm.spoonPath = script_path() 24 | 25 | -- Load other files 26 | dofile(zwm.spoonPath.."utilities.lua") 27 | dofile(zwm.spoonPath.."window.lua") 28 | dofile(zwm.spoonPath.."spaces.lua") 29 | dofile(zwm.spoonPath.."menubar.lua") 30 | dofile(zwm.spoonPath.."application.lua") 31 | dofile(zwm.spoonPath.."config_default.lua") 32 | dofile(zwm.spoonPath.."config.lua") 33 | 34 | -- zwm 35 | function zwm:init() 36 | local config = dofile(os.getenv("HOME").."/.zwm/config.lua") 37 | if config ~= nil then 38 | load_config(config) 39 | end 40 | end 41 | 42 | -- applications = get_applications() 43 | 44 | 45 | -- hammerspoon config 46 | -- reload this file on change. 47 | function reload_hammerspoon(files) 48 | doReload = false 49 | for _,file in pairs(files) do 50 | if file:sub(-4) == ".lua" then 51 | doReload = true 52 | end 53 | end 54 | if doReload then 55 | hs.reload() 56 | end 57 | end 58 | 59 | local myWatcher = hs.pathwatcher.new(zwm.spoonPath, reload_hammerspoon):start() 60 | 61 | return zwm 62 | -------------------------------------------------------------------------------- /menubar.lua: -------------------------------------------------------------------------------- 1 | -- menubar 2 | 3 | dofile(zwm.spoonPath.."utilities.lua") 4 | 5 | menubar = hs.menubar.new() 6 | 7 | function set_workspaces() 8 | local c = zwm.config 9 | local all_spaces = get_spaces() 10 | local current = spaces.activeSpace() 11 | local inactive_before_text = "" 12 | local inactive_after_text = "" 13 | local separator = "" 14 | local active_text = "" 15 | local color_active = hex_to_rgb(c.ui.bar.color_active) 16 | local color_inactive = hex_to_rgb(c.ui.bar.color_inactive) 17 | 18 | if c.ui.bar.separator then 19 | separator = c.ui.bar.separator 20 | end 21 | 22 | local i = 0 23 | 24 | for i in ipairs(all_spaces) do 25 | t = replace_identifier(tostring(i)) 26 | if all_spaces[i] == current then 27 | active_text = t .. separator 28 | 29 | else 30 | if active_text:len() == 0 then 31 | inactive_before_text = inactive_before_text .. t .. separator 32 | else 33 | inactive_after_text = inactive_after_text .. t .. separator 34 | end 35 | end 36 | end 37 | 38 | -- fontawesome 39 | local font = {name = "FontAwesome", size = c.ui.bar.fontsize} 40 | 41 | local active = hs.styledtext.new(active_text, {font = font, color = { red = color_active.r, green = color_active.g, blue = color_active.b}}) 42 | local inactive_before = hs.styledtext.new(inactive_before_text, {font = font, color = { red = color_inactive.r, green = color_inactive.g, blue = color_inactive.b}}) 43 | local inactive_after = hs.styledtext.new(inactive_after_text, {font = font, color = { red = color_inactive.r, green = color_inactive.g, blue = color_inactive.b}}) 44 | 45 | local g_str = inactive_before .. active .. inactive_after 46 | 47 | menubar:setTitle(g_str) 48 | end 49 | 50 | function replace_identifier(index) 51 | local c = zwm.config.spaces 52 | local match = match_item(index, c.tags) 53 | if match == nil then 54 | return index 55 | else 56 | return tostring(match.f) 57 | end 58 | 59 | end 60 | 61 | 62 | -- change menubar on normal space change 63 | local watcher = hs.spaces.watcher.new(set_workspaces) 64 | watcher:start() 65 | -------------------------------------------------------------------------------- /spaces.lua: -------------------------------------------------------------------------------- 1 | -- spaces 2 | 3 | spaces = require("hs._asm.undocumented.spaces") 4 | dofile(zwm.spoonPath.."utilities.lua") 5 | 6 | -- returns spaces in order, but its best to not move spaces around 7 | -- FIXME: This only addresses the primary screen (https://github.com/z3t0/zwm/issues/15) 8 | function get_spaces() 9 | local all_spaces = spaces.layout() 10 | local inner = nil 11 | 12 | for k, v in pairs(all_spaces) do 13 | if v ~= nil and type(v) == "table" then 14 | inner = v 15 | break 16 | end 17 | end 18 | 19 | if inner ~= nil then 20 | return inner 21 | end 22 | 23 | end 24 | 25 | function change_to_space(space) 26 | local new_space = get_spaces()[space] 27 | 28 | if new_space == spaces.activeSpace() then 29 | return 30 | elseif new_space ~= nil then 31 | spaces.changeToSpace(new_space, false) 32 | else 33 | error("space does not exist: " .. tostring(space)) 34 | end 35 | 36 | set_workspaces() 37 | end 38 | 39 | function func_change_to_space(index) 40 | return function() 41 | return change_to_space(index) 42 | end 43 | end 44 | 45 | function move_to_space(space) 46 | local win = hs.window.focusedWindow() 47 | local new_space = get_spaces()[space] 48 | 49 | if new_space == spaces.activeSpace() then 50 | return 51 | elseif new_space ~= nil then 52 | win:spacesMoveTo(new_space) 53 | else 54 | error("space does not exist: " .. tostring(space)) 55 | end 56 | end 57 | 58 | -- sends an application and all of its windows to a space 59 | function send_to_space(app, space) 60 | ba = app 61 | windows = app:allWindows() 62 | print(hs.inspect(windows)) 63 | local new_space = get_spaces()[space] 64 | 65 | if new_space == spaces.activeSpace() then 66 | return 67 | 68 | elseif new_space ~= nil then 69 | for k, v in pairs(windows) do 70 | v:spacesMoveTo(new_space) 71 | end 72 | end 73 | end 74 | 75 | function func_move_to_space(index) 76 | return function() 77 | return move_to_space(index) 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /utilities.lua: -------------------------------------------------------------------------------- 1 | -- utilities 2 | 3 | function log(err) 4 | -- TODO: log file 5 | error(err) 6 | end 7 | 8 | -- return the number of items in a table 9 | function table_count(t) 10 | local count = 0 11 | for _ in pairs(t) do count = count + 1 end 12 | return count 13 | end 14 | 15 | -- recursively prints table 16 | function print_r ( t ) 17 | local print_r_cache={} 18 | local function sub_print_r(t,indent) 19 | if (print_r_cache[tostring(t)]) then 20 | print(indent.."*"..tostring(t)) 21 | else 22 | print_r_cache[tostring(t)]=true 23 | if (type(t)=="table") then 24 | for pos,val in pairs(t) do 25 | if (type(val)=="table") then 26 | print(indent.."["..pos.."] => "..tostring(t).." {") 27 | sub_print_r(val,indent..string.rep(" ",string.len(pos)+8)) 28 | print(indent..string.rep(" ",string.len(pos)+6).."}") 29 | elseif (type(val)=="string") then 30 | print(indent.."["..pos..'] => "'..val..'"') 31 | else 32 | print(indent.."["..pos.."] => "..tostring(val)) 33 | end 34 | end 35 | else 36 | print(indent..tostring(t)) 37 | end 38 | end 39 | end 40 | if (type(t)=="table") then 41 | print(tostring(t).." {") 42 | sub_print_r(t," ") 43 | print("}") 44 | else 45 | sub_print_r(t," ") 46 | end 47 | print() 48 | end 49 | 50 | -- prints an error to the console 51 | -- TODO: proper error handling 52 | function error(msg) 53 | print("error: \n" .. msg) 54 | end 55 | 56 | -- Alert in center of the screen 57 | function alert_simple(msg) 58 | hs.alert(msg) 59 | end 60 | 61 | -- Alert from notifications center 62 | function alert(msg, title) 63 | if title == nil then 64 | title = "zwm" 65 | end 66 | 67 | if config["silent"] then 68 | print(msg) 69 | else 70 | hs.notify.new({title="zwm", informativeText=msg}):send() 71 | end 72 | end 73 | 74 | -- if string matches options from table 75 | function string_match(str, table) 76 | for k, v in pairs(table) do 77 | if string.match(str, v) then 78 | return true 79 | end 80 | end 81 | return false 82 | end 83 | 84 | -- if item matches a key in the table, return that pair 85 | function match_item(item, table) 86 | for k, v in pairs(table) do 87 | if string.match(item, k) then 88 | return {i = k, f = v} 89 | end 90 | end 91 | 92 | return nil 93 | end 94 | 95 | function exact_in_table(item, table) 96 | for k, v in pairs(table) do 97 | if item == k then 98 | return {i = k, f = v} 99 | end 100 | end 101 | 102 | return nil 103 | end 104 | 105 | -- hex to rgb 106 | function hex_to_rgb(hex) 107 | local hex = hex:gsub("#","") 108 | local rgb = {r =tonumber("0x"..hex:sub(1,2))/255, g = tonumber("0x"..hex:sub(3,4))/255, b = tonumber("0x"..hex:sub(5,6))/255} 109 | 110 | return rgb 111 | end 112 | 113 | -- converts text to ansi color 114 | function ansi_color(c) 115 | if c == "black" then 116 | return 30 117 | elseif c == "red" then 118 | return 31 119 | elseif c == "green" then 120 | return 32 121 | elseif c == "yellow" then 122 | return 33 123 | elseif c == "blue" then 124 | return 34 125 | elseif c == "purple" then 126 | return 35 127 | elseif c == "cyan" then 128 | return 36 129 | elseif c == "white" then 130 | return 37 131 | end 132 | 133 | end 134 | 135 | function deepcopy(orig) 136 | local orig_type = type(orig) 137 | local copy 138 | if orig_type == 'table' then 139 | copy = {} 140 | for orig_key, orig_value in next, orig, nil do 141 | copy[deepcopy(orig_key)] = deepcopy(orig_value) 142 | end 143 | setmetatable(copy, deepcopy(getmetatable(orig))) 144 | else -- number, string, boolean, etc 145 | copy = orig 146 | end 147 | return copy 148 | end 149 | -------------------------------------------------------------------------------- /window.lua: -------------------------------------------------------------------------------- 1 | dofile (zwm.spoonPath.."utilities.lua") 2 | 3 | -- Returns a table of all the applications, minus corner cases 4 | function get_applications() 5 | local applications = hs.application.runningApplications() 6 | local not_applications = {} 7 | 8 | local n=#applications 9 | 10 | -- Certain apps show up after the filter but should not. 11 | local edge_cases = {"Notification Center", "Finder", "Übersicht", "Hammerspoon"} 12 | -- Finder glitching 13 | 14 | for k, v in pairs(applications) do 15 | local win = v:allWindows() 16 | 17 | if table_count(win) < 1 then 18 | not_applications[k] = true 19 | elseif string_match(v:name(), edge_cases) then 20 | not_applications[k] = true 21 | end 22 | end 23 | 24 | for i=1,n do 25 | if not_applications[i] then 26 | applications[i]=nil 27 | end 28 | end 29 | 30 | local ordered = {} 31 | local i = 1 32 | for k, v in pairs(applications) do 33 | ordered [i] = v 34 | i = i+1 35 | end 36 | 37 | return ordered 38 | end 39 | 40 | -- Tiles all windows according to the tiling setting 41 | function window_tile() 42 | local tile = config["window_management"]["mode"] 43 | 44 | local win = hs.window.focusedWindow() 45 | local f = win:frame() 46 | local gap = config["window_management"]["gap"] 47 | local screen = hs.screen.mainScreen():frame() 48 | local bar_offset = config["bar"]["height"] 49 | bar_offset = 0 50 | 51 | if tile == "monocle" then 52 | local new_frame = hs.geometry.rect(screen._x + gap, screen._y + gap + bar_offset, screen._w - (2 * gap), screen._h - (2 * gap) - bar_offset) 53 | if new_frame ~= f then 54 | win:setFrame(new_frame, 0) 55 | end 56 | end 57 | end 58 | 59 | -- Switches to the next application and then tiles if necessary 60 | function application_next(front) 61 | -- monocle 62 | current_app = hs.application.frontmostApplication() 63 | local next = {} 64 | 65 | local applications = get_applications() 66 | for i, v in ipairs(applications) do 67 | if v:name() == current_app:name() then 68 | if i == table_count(applications) then 69 | if front then 70 | next = applications[1] 71 | break 72 | else 73 | next = applications[i - 1] 74 | break 75 | end 76 | elseif i == 1 then 77 | if front then 78 | next = applications[i + 1] 79 | break 80 | else 81 | next = applications[table_count(applications)] 82 | break 83 | end 84 | 85 | else 86 | if front then 87 | next = applications[i + 1] 88 | break 89 | else 90 | next = applications[i - 1] 91 | break 92 | end 93 | end 94 | end 95 | end 96 | 97 | next:activate() 98 | window_tile() 99 | end 100 | 101 | -- Switches to the next window for the frontMost application 102 | -- TODO: next window of any application, duplicate windows alt-tab 103 | function window_next(front) 104 | current_app = hs.application.frontmostApplication() 105 | local win = current_app:focusedWindow() 106 | local current_windows = current_app:allWindows() 107 | local new_win = nil 108 | local desktop = hs.window.desktop() 109 | 110 | if current_app:name() == "Finder" then 111 | -- remove desktop from list of windows 112 | for i in ipairs(current_windows) do 113 | if current_windows[i] == desktop then 114 | current_windows[i] = nil 115 | print("removed") 116 | end 117 | end 118 | end 119 | 120 | local count = table_count(current_windows) 121 | 122 | for i in ipairs(current_windows) do 123 | if win == current_windows[i] then 124 | if i == count then 125 | if front then 126 | new_win = current_windows[1] 127 | else 128 | new_win = current_windows[i - 1] 129 | end 130 | elseif i == 1 then 131 | if front then 132 | new_win = current_windows[i + 1] 133 | else 134 | new_win = current_windows[count] 135 | end 136 | else 137 | if front then 138 | new_win = current_windows[i + 1] 139 | else 140 | new_win = current_windows[i - 1] 141 | end 142 | end 143 | end 144 | end 145 | 146 | new_win:focus() 147 | window_tile() 148 | end 149 | --------------------------------------------------------------------------------