├── .gitignore ├── LICENSE ├── README.md ├── bring_container_to_current_workspace.py ├── create_new_project.py ├── focus_next_display.py ├── img ├── after_cycle_container.png ├── after_cycle_workspace_display.png ├── initial_state.png ├── new_project_after.png ├── new_project_dialog.png ├── rename_project_after.png └── rename_project_dialog.png ├── move_current_container_to_next_workspace_in_project.py ├── move_workspaces_in_project_to_next_output.py ├── necessaryFuncs.py ├── rename_project.py ├── requirements.txt ├── switch_to_next_project.py ├── switch_to_next_wk_in_project.py └── switcher.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .env 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Sainath Adapa 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ***[Demo video showing the capabilities](https://youtu.be/_IU-GckAc5I)*** 2 | 3 | At my workplace, I have a dual monitor setup, with Ubuntu, and [I3](https://i3wm.org/) as the window manager. Being a tiling window manager, I3 allows me to effectively use all the display space, and arrange the windows quickly in different shapes and sizes. 4 | 5 | Typically, I have a browser and a terminal (or multiple terminals) on one monitor, and IDE on another. I also generally have multiple such sets of workspaces. Each such set (one workspace for each monitor) represents a project that I am working on. 6 | 7 | This is how the I3 bar looks for me on a typical day: 8 | 9 | Monitor 1: 10 | `1:projectA | 3:projectB | 5:projectC` 11 | 12 | Monitor 2: 13 | `2:projectA | 4:projectB | 6:projectC` 14 | 15 | Now, to switch from working on Project A to Project B, I need to invoke three commands - 16 | 17 | (Workspace 1 is focused, Workspace 2 is in a focused inactive state on the other monitor) 18 | 19 | 1. `$mod+3` to switch to Workspace 3 from Workspace 1 on the first monitor 20 | 2. `$mod+2` to focus Workspace 2 on the second monitor 21 | 3. `$mod+4` to switch to Worspace 4 from Workspace 2 on the second monitor 22 | 23 | Three commands is an inefficient way to do such a simple & regular task. So, I wrote a script, using which I can achieve the above with one command. This was back in September 2015. Since then, I wrote more scripts to easily navigate and move windows in a multi-monitor setup with I3. And yes, it also works with more than two monitors. 24 | 25 | # How to install 26 | 1. Install `zenity`, in case if it is not present. This utility is present on most gnome based linux systems. 27 | 2. Download the scripts individually or clone the repo 28 | 3. Install the python dependencies as specified in [requirements.txt](requirements.txt) 29 | 4. Bind the scripts to shortcut keys in the I3 config file 30 | 31 | # Terminology 32 | A project is defined as a set of workspaces, one workspace for each display/monitor available. 33 | 34 | # Usage 35 | 36 | ## [create_new_project.py](create_new_project.py) 37 | 38 | To create a new project, run the `create_new_project.py` script. This will display a dialog box asking for the project name. Names of the workspaces will contain the project name. 39 | 40 | Dialog box: 41 | ![New project dialog box](img/new_project_dialog.png) 42 | 43 | After: 44 | ![New set of workspaces](img/new_project_after.png) 45 | 46 | ## [rename_project.py](rename_project.py) 47 | 48 | To rename a project, run `rename_project.py`. This will display a dialog box asking for the new project name. 49 | 50 | Dialog box: 51 | ![Rename project dialog box](img/rename_project_dialog.png) 52 | 53 | After: 54 | ![After rename](img/rename_project_after.png) 55 | 56 | ## [switch_to_next_project.py](switch_to_next_project.py) 57 | 58 | To switch to the next project, run `switchNextProject.py` 59 | 60 | ## [switch_to_next_wk_in_project.py](switch_to_next_wk_in_project.py) 61 | 62 | To cycle the focus between the workspaces in a project, run `switch_to_next_wk_in_project.py` 63 | 64 | ## [move_workspaces_in_project_to_next_output.py](move_workspaces_in_project_to_next_output.py) 65 | 66 | To shift all the workspaces in a project to each one's respective next display, run `move_workspaces_in_project_to_next_output.py`. For example, if the initial state of the workspaces is {Wksp 1 - Disp 1}, {Wksp 2 - Disp 2}, {Wksp 3 - Disp 3}, after running the script, the final state will be {Wksp 1 - Disp 2}, {Wksp 2 - Disp 3}, {Wksp 3 - Disp 1}. 67 | 68 | Initial state: 69 | ![Initial state](img/initial_state.png) 70 | 71 | After running the script: 72 | ![After workspace switch](img/after_cycle_workspace_display.png) 73 | 74 | ## [move_current_container_to_next_workspace_in_project.py](move_current_container_to_next_workspace_in_project.py) 75 | 76 | To switch the focused container to the next workspace in the project, run `move_current_container_to_next_workspace_in_project.py`. 77 | 78 | Initial state: 79 | ![Initial state](img/initial_state.png) 80 | 81 | After running the script: 82 | ![Gvim moved](img/after_cycle_container.png) 83 | 84 | # Sample I3 config 85 | To use the scripts, add these lines to your I3 config: 86 | 87 | ``` sh 88 | set $i3multipath ~/.i3/i3_wm_multi_disp_scripts 89 | 90 | # project workflow bindings 91 | bindsym $mod+Shift+p exec $i3multipath/.env/bin/python $i3multipath/create_new_project.py 92 | bindsym $mod+p exec $i3multipath/.env/bin/python $i3multipath/switch_to_next_project.py 93 | bindsym $mod+Shift+Tab exec $i3multipath/.env/bin/python $i3multipath/move_workspaces_in_project_to_next_output.py 94 | bindsym $mod+Control+Tab exec $i3multipath/.env/bin/python $i3multipath/move_current_container_to_next_workspace_in_project.py 95 | bindsym $mod+o exec $i3multipath/.env/bin/python $i3multipath/rename_project.py 96 | bindsym F8 exec $i3multipath/.env/bin/python $i3multipath/bring_container_to_current_workspace.py 97 | bindsym $mod+Tab exec $i3multipath/switcher.sh 98 | 99 | # create a initial project on startup with project name 'default' 100 | exec --no-startup-id $i3multipath/.env/bin/python $i3multipath/create_new_project.py default 101 | ``` 102 | 103 | 104 | -------------------------------------------------------------------------------- /bring_container_to_current_workspace.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import json 3 | import subprocess 4 | import sys 5 | import anytree as at 6 | import necessaryFuncs as nf 7 | 8 | 9 | def create_tree(root_json, root_node): 10 | con_name = root_json['name'] 11 | 12 | if con_name is None: 13 | con_name = 'container' 14 | 15 | if con_name in ['__i3', 'topdock', 'bottomdock']: 16 | return None 17 | else: 18 | this_node = at.AnyNode(id=con_name, 19 | parent=root_node, 20 | con_id=root_json['id'], 21 | workspace=False, 22 | container=False) 23 | 24 | if con_name == 'container': 25 | this_node.container = True 26 | 27 | for a_node in root_json['nodes']: 28 | create_tree(a_node, this_node) 29 | 30 | 31 | def fix_container_names(node): 32 | if node.id == 'container': 33 | node_name = ', '.join([x.id for x in node.children]) 34 | node_name = 'container[' + node_name + ']' 35 | node.id = node_name 36 | 37 | 38 | def rofi(options, program): 39 | '''Call dmenu with a list of options.''' 40 | cmd = subprocess.Popen(program, 41 | shell=True, 42 | stdin=subprocess.PIPE, 43 | stdout=subprocess.PIPE, 44 | stderr=subprocess.PIPE) 45 | stdout, _ = cmd.communicate('\n'.join(options).encode('utf-8')) 46 | return stdout.decode('utf-8').strip('\n') 47 | 48 | 49 | # Get I3 tree 50 | proc_out = subprocess.run(['i3-msg', '-t', 'get_tree'], stdout=subprocess.PIPE) 51 | i3tree = json.loads(proc_out.stdout.decode('utf-8')) 52 | 53 | # Create tree from the i3 tree output 54 | root = at.AnyNode(id='r') 55 | create_tree(i3tree, root) 56 | root = root.children[0] 57 | 58 | # Identify the workspaces 59 | for display in root.children: 60 | for wk in display.children[0].children: 61 | wk.workspace = True 62 | 63 | # Get the current workspace 64 | proc_out = subprocess.run(['i3-msg', '-t', 'get_workspaces'], stdout=subprocess.PIPE) 65 | wkList = json.loads(proc_out.stdout.decode('utf-8')) 66 | focWkName = nf.getFocusedWK(wkList) 67 | 68 | # Change the tree such that the workspaces are children to the root 69 | # while ignoring the current workspace 70 | root.children = [node 71 | for node in at.PostOrderIter(root, filter_=lambda x: x.workspace) 72 | if node.id != focWkName] 73 | 74 | # If workspace contains only one container, then remove that container 75 | for node in at.PostOrderIter(root, filter_=lambda x: x.workspace): 76 | if len(node.children) == 1: 77 | node.children = node.children[0].children 78 | 79 | # If containers have only one element, then remove such containers 80 | for node in at.PreOrderIter(root, filter_=lambda x: x.container): 81 | if len(node.children) == 1: 82 | node.children[0].parent = node.parent 83 | node.parent = None 84 | 85 | # Create names for containers 86 | for node in at.PreOrderIter(root, filter_=lambda x: x.container): 87 | fix_container_names(node) 88 | 89 | # Create new names for nodes for diplay in Rofi 90 | names_id_map = [[x+y.id, y.con_id] for x, _, y in at.RenderTree(root)] 91 | 92 | # Call rofi 93 | selected = rofi([x[0] for x in names_id_map[1:]], 'rofi -dmenu -i -format i') 94 | 95 | if selected == '': 96 | sys.exit(0) 97 | 98 | # Run the command 99 | selected = int(selected)+1 100 | command_to_run = ['i3-msg', 101 | '[con_id=' + str(names_id_map[selected][1]) + '] ' + 102 | 'move --no-auto-back-and-forth container to workspace ' + 103 | focWkName] 104 | # print(command_to_run) 105 | subprocess.call(command_to_run) 106 | -------------------------------------------------------------------------------- /create_new_project.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import subprocess 3 | import json 4 | import sys 5 | import necessaryFuncs as nf 6 | 7 | if (len(sys.argv) > 1): 8 | projectName = sys.argv[1] 9 | else: 10 | proc = subprocess.Popen(['zenity', '--entry', '--title=I3', 11 | "--text=Start a new project with the name:"], 12 | stdout=subprocess.PIPE) 13 | projectName = proc.stdout.read() 14 | projectName = projectName.decode('utf-8').replace('\n', '').replace('\r', '') 15 | 16 | if (projectName is None) or (len(projectName) == 0): 17 | sys.exit(0) 18 | 19 | proc_out = subprocess.run(['i3-msg', '-t', 'get_workspaces'], stdout=subprocess.PIPE) 20 | wkList = json.loads(proc_out.stdout.decode('utf-8')) 21 | 22 | for one_wk in wkList: 23 | one_wk['project'] = nf.getProjectFromWKName(one_wk['name']) 24 | 25 | allOutputs = nf.getListOfOutputs(wkList) 26 | # Outputs where this project is already present 27 | outputs_to_remove = [x['output'] for x in wkList if x['project'] == projectName] 28 | # Remaining outputs 29 | allOutputs = list(set(allOutputs).difference(set(outputs_to_remove))) 30 | 31 | newWorkspaceNums = nf.getValidWorkspaceNums(wkList, len(allOutputs)) 32 | 33 | commandToRun = '' 34 | 35 | wkNameProjectPart = ':' + projectName + ':' 36 | 37 | for i in range(1, len(allOutputs) + 1): 38 | # 1. find a workspace which is on this output 39 | # 2. switch to it if it is already not focused 40 | # 3. create the new workspace 41 | currentWKName = str(newWorkspaceNums[i-1]) + ':' + wkNameProjectPart + str(i) 42 | 43 | currentOutputWK = nf.getWorkspacesOnOutput(wkList, allOutputs[i-1])[0] 44 | 45 | if (i != 1) or (currentOutputWK != nf.getFocusedWK(wkList)): 46 | commandToRun = commandToRun + 'workspace ' + currentOutputWK + '; ' 47 | 48 | commandToRun = commandToRun + 'workspace ' + currentWKName + '; ' 49 | 50 | commandToRunArray = ['i3-msg', commandToRun] 51 | 52 | subprocess.call(commandToRunArray) 53 | -------------------------------------------------------------------------------- /focus_next_display.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import subprocess 3 | import json 4 | import necessaryFuncs as nf 5 | 6 | proc_out = subprocess.run(['i3-msg', '-t', 'get_workspaces'], stdout=subprocess.PIPE) 7 | wkList = json.loads(proc_out.stdout.decode('utf-8')) 8 | 9 | displays = list(sorted(nf.getListOfOutputs(wkList))) 10 | current_display = nf.getOutputForWK(wkList, nf.getFocusedWK(wkList)) 11 | current_display_i = [i for i, x in enumerate(displays) if x == current_display][0] 12 | 13 | if current_display_i+1 >= len(displays): 14 | next_display = displays[0] 15 | else: 16 | next_display = displays[current_display_i + 1] 17 | 18 | commandToRun = ["i3-msg", 'focus output ' + next_display] 19 | subprocess.call(commandToRun) 20 | -------------------------------------------------------------------------------- /img/after_cycle_container.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sainathadapa/i3-wm-multi-disp-scripts/0446299bcf0b83b230e7e893d6c5e65d98461cca/img/after_cycle_container.png -------------------------------------------------------------------------------- /img/after_cycle_workspace_display.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sainathadapa/i3-wm-multi-disp-scripts/0446299bcf0b83b230e7e893d6c5e65d98461cca/img/after_cycle_workspace_display.png -------------------------------------------------------------------------------- /img/initial_state.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sainathadapa/i3-wm-multi-disp-scripts/0446299bcf0b83b230e7e893d6c5e65d98461cca/img/initial_state.png -------------------------------------------------------------------------------- /img/new_project_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sainathadapa/i3-wm-multi-disp-scripts/0446299bcf0b83b230e7e893d6c5e65d98461cca/img/new_project_after.png -------------------------------------------------------------------------------- /img/new_project_dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sainathadapa/i3-wm-multi-disp-scripts/0446299bcf0b83b230e7e893d6c5e65d98461cca/img/new_project_dialog.png -------------------------------------------------------------------------------- /img/rename_project_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sainathadapa/i3-wm-multi-disp-scripts/0446299bcf0b83b230e7e893d6c5e65d98461cca/img/rename_project_after.png -------------------------------------------------------------------------------- /img/rename_project_dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sainathadapa/i3-wm-multi-disp-scripts/0446299bcf0b83b230e7e893d6c5e65d98461cca/img/rename_project_dialog.png -------------------------------------------------------------------------------- /move_current_container_to_next_workspace_in_project.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import subprocess 3 | import json 4 | import sys 5 | import necessaryFuncs as nf 6 | 7 | proc_out = subprocess.run(['i3-msg', '-t', 'get_workspaces'], stdout=subprocess.PIPE) 8 | wkList = json.loads(proc_out.stdout.decode('utf-8')) 9 | 10 | allWKNames = nf.getWKNames(wkList) 11 | 12 | currentWK = nf.getFocusedWK(wkList) 13 | 14 | currentProj = nf.getProjectFromWKName(currentWK) 15 | 16 | if currentProj is None: 17 | sys.exit(1) 18 | 19 | currentProjWKs = nf.getWKNamesFromProj(wkList, currentProj) 20 | 21 | if len(currentProjWKs) < 2: 22 | sys.exit(1) 23 | 24 | thisWKPos = currentProjWKs.index(currentWK) 25 | 26 | newWKPos = thisWKPos + 1 27 | if newWKPos == len(currentProjWKs): 28 | newWKPos = 0 29 | 30 | commandToRun = ['i3-msg', 'move container to workspace ' + currentProjWKs[newWKPos]] 31 | 32 | subprocess.call(commandToRun) 33 | -------------------------------------------------------------------------------- /move_workspaces_in_project_to_next_output.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import subprocess 3 | import json 4 | import sys 5 | import necessaryFuncs as nf 6 | 7 | proc_out = subprocess.run(['i3-msg', '-t', 'get_workspaces'], stdout=subprocess.PIPE) 8 | wkList = json.loads(proc_out.stdout.decode('utf-8')) 9 | 10 | allWKNames = nf.getWKNames(wkList) 11 | 12 | fcsWK = nf.getFocusedWK(wkList) 13 | 14 | currentProj = nf.getProjectFromWKName(fcsWK) 15 | 16 | if (currentProj is None) or (len(currentProj) == 0): 17 | sys.exit(1) 18 | 19 | currentProjWKs = nf.getWKNamesFromProj(wkList, currentProj) 20 | 21 | currentProjWKOutputs = [nf.getOutputForWK(wkList, x) for x in currentProjWKs] 22 | 23 | newOutputPos = range(1, len(currentProjWKs) + 1) 24 | 25 | 26 | def newOutputPosFn(x): 27 | if (x == len(currentProjWKOutputs)): 28 | x = 0 29 | return x 30 | 31 | 32 | newOutputPos = [newOutputPosFn(x) for x in newOutputPos] 33 | 34 | newOutputs = [currentProjWKOutputs[i] for i in newOutputPos] 35 | 36 | 37 | def mk_cmd(i, x): 38 | ans = '' 39 | if (i != 0) or (currentProjWKs[i] != nf.getFocusedWK(wkList)): 40 | ans = ans + 'workspace ' + currentProjWKs[i] + '; ' 41 | ans = ans + 'move workspace to output ' + newOutputs[i] + '; ' 42 | return ans 43 | 44 | 45 | parCommToRun = [mk_cmd(i, x) for i, x in enumerate(currentProjWKs)] 46 | 47 | commandToRun = ["i3-msg", ''.join(parCommToRun)] 48 | 49 | subprocess.call(commandToRun) 50 | -------------------------------------------------------------------------------- /necessaryFuncs.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import re 3 | 4 | 5 | def getWKNames(wkList): 6 | return [x['name'] for x in wkList] 7 | 8 | 9 | def getFocusedWK(wkList): 10 | return [x for x in wkList if x['focused']][0]['name'] 11 | 12 | 13 | def getProjectFromWKName(wkName): 14 | search_out = re.search('^\d+::(.*):\d+$', wkName) 15 | if search_out: 16 | return search_out.group(1) 17 | else: 18 | return None 19 | 20 | 21 | def getWKNamesFromProj(wkList, projName): 22 | wknames = getWKNames(wkList) 23 | return [x for x in wknames if getProjectFromWKName(x) == projName] 24 | 25 | 26 | def getOutputForWK(wkList, wkName): 27 | return [x for x in wkList if x['name'] == wkName][0]['output'] 28 | 29 | 30 | def getListOfOutputs(wkList): 31 | outputs_with_duplicates = [x['output'] for x in wkList] 32 | return list(set(outputs_with_duplicates)) 33 | 34 | 35 | def getWorkspaceNums(wkList): 36 | return [x['num'] for x in wkList] 37 | 38 | 39 | def getValidWorkspaceNums(wkList, num): 40 | wkNums = getWorkspaceNums(wkList) 41 | 42 | if len(wkNums) == 0: 43 | return None 44 | 45 | maxWKNum = max(wkNums) 46 | fullWKNums = range(0, maxWKNum + 1) 47 | goodWKNums = list(set(fullWKNums) - set(wkNums)) 48 | 49 | if num <= len(goodWKNums): 50 | return [goodWKNums[i] for i in range(0, num)] 51 | else: 52 | return goodWKNums + list(range(maxWKNum + 1, maxWKNum + 1 + num - len(goodWKNums))) 53 | 54 | 55 | def getVisibleWKs(wkList): 56 | return [x['name'] for x in wkList if x['visible']] 57 | 58 | 59 | def getWorkspacesOnOutput(wkList, outputName): 60 | return [x['name'] for x in wkList if x['output'] == outputName] 61 | 62 | 63 | def getListOfProjects(wkList): 64 | wknames = getWKNames(wkList) 65 | wknums = getWorkspaceNums(wkList) 66 | 67 | out1 = [getProjectFromWKName(x) for x in wknames] 68 | out11 = zip(out1, wknums) 69 | out2 = [x for x in out11 if x[0] is not None] 70 | listOfProjects = list(set([x[0] for x in out2])) 71 | 72 | def f(x): 73 | return min([y[1] for y in out2 if y[0] == x]) 74 | 75 | out3 = [f(x) for x in listOfProjects] 76 | sortedProjects = [x for (y, x) in sorted(zip(out3, listOfProjects))] 77 | 78 | return sortedProjects 79 | -------------------------------------------------------------------------------- /rename_project.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import subprocess 3 | import json 4 | import sys 5 | import necessaryFuncs as nf 6 | 7 | proc = subprocess.Popen(['zenity', '--entry', '--title=I3', 8 | "--text=Rename current project to:"], 9 | stdout=subprocess.PIPE) 10 | 11 | projectName = proc.stdout.read() 12 | 13 | projectName = projectName.decode('utf-8').replace('\n', '').replace('\r', '') 14 | 15 | if (projectName is None) or (len(projectName) == 0): 16 | sys.exit(0) 17 | 18 | proc_out = subprocess.run(['i3-msg', '-t', 'get_workspaces'], stdout=subprocess.PIPE) 19 | wkList = json.loads(proc_out.stdout.decode('utf-8')) 20 | 21 | allWKNames = nf.getWKNames(wkList) 22 | currentWK = nf.getFocusedWK(wkList) 23 | currentProj = nf.getProjectFromWKName(currentWK) 24 | 25 | if currentProj is None: 26 | sys.exit(1) 27 | 28 | currentProjWKs = nf.getWKNamesFromProj(wkList, currentProj) 29 | 30 | newProjWKs = [x.replace(":" + currentProj + ":", ":" + projectName + ":") for x in currentProjWKs] 31 | 32 | parCommand = ['rename workspace ' + currentProjWKs[i] + ' to ' + newProjWKs[i] + '; ' 33 | for i, x in enumerate(currentProjWKs)] 34 | 35 | commandToRun = ['i3-msg', ''.join(parCommand)] 36 | 37 | subprocess.call(commandToRun) 38 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | anytree==2.4.3 2 | six==1.11.0 3 | -------------------------------------------------------------------------------- /switch_to_next_project.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import subprocess 3 | import json 4 | import sys 5 | import necessaryFuncs as nf 6 | 7 | proc_out = subprocess.run(['i3-msg', '-t', 'get_workspaces'], stdout=subprocess.PIPE) 8 | wkList = json.loads(proc_out.stdout.decode('utf-8')) 9 | 10 | focWkName = nf.getFocusedWK(wkList) 11 | allProjectNames = nf.getListOfProjects(wkList) 12 | 13 | if (len(allProjectNames) == 0) or (allProjectNames is None): 14 | sys.exit(1) 15 | 16 | currentProjName = nf.getProjectFromWKName(focWkName) 17 | 18 | if currentProjName is None: 19 | nextProjIndex = 0 20 | else: 21 | nextProjIndex = allProjectNames.index(currentProjName) 22 | if nextProjIndex == (len(allProjectNames) - 1): 23 | nextProjIndex = 0 24 | else: 25 | nextProjIndex = nextProjIndex + 1 26 | 27 | nxtProjWks = nf.getWKNamesFromProj(wkList, allProjectNames[nextProjIndex]) 28 | 29 | visWks = nf.getVisibleWKs(wkList) 30 | 31 | wksToMakeVisible = list(set(nxtProjWks) - set(visWks)) 32 | 33 | focOutput = nf.getOutputForWK(wkList, focWkName) 34 | focOutputWks = nf.getWorkspacesOnOutput(wkList, focOutput) 35 | wkToBeFocused = list(set(focOutputWks).intersection(nxtProjWks)) 36 | 37 | parCommToRun = ['workspace ' + x for x in wksToMakeVisible] 38 | if len(wkToBeFocused) > 0 and wksToMakeVisible[-1] != wkToBeFocused[0]: 39 | parCommToRun.append('workspace ' + wkToBeFocused[0]) 40 | 41 | commandToRun = ["i3-msg", '; '.join(parCommToRun)] 42 | 43 | subprocess.call(commandToRun) 44 | -------------------------------------------------------------------------------- /switch_to_next_wk_in_project.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import subprocess 3 | import json 4 | import sys 5 | import necessaryFuncs as nf 6 | 7 | proc_out = subprocess.run(['i3-msg', '-t', 'get_workspaces'], stdout=subprocess.PIPE) 8 | wkList = json.loads(proc_out.stdout.decode('utf-8')) 9 | 10 | allWKNames = nf.getWKNames(wkList) 11 | 12 | currentWK = nf.getFocusedWK(wkList) 13 | 14 | currentProj = nf.getProjectFromWKName(currentWK) 15 | 16 | if currentProj is None: 17 | sys.exit(0) 18 | 19 | currentProjWKs = nf.getWKNamesFromProj(wkList, currentProj) 20 | 21 | if len(currentProjWKs) == 1: 22 | sys.exit(0) 23 | 24 | thisWKPos = currentProjWKs.index(currentWK) 25 | 26 | newWKPos = thisWKPos + 1 27 | 28 | if newWKPos == len(currentProjWKs): 29 | newWKPos = 0 30 | 31 | commandToRun = ['i3-msg', 'workspace ' + currentProjWKs[newWKPos]] 32 | 33 | subprocess.call(commandToRun) 34 | -------------------------------------------------------------------------------- /switcher.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | caps="$(xset -q | grep Caps | sed -E "s/ //g;s/[0-9]*//g" | cut -d ":" -f 3)" 3 | if [ "$caps" == "off" ]; then 4 | echo "caps is off" 5 | python3 ~/.i3/i3_wm_multi_disp_scripts/focus_next_display.py 6 | else 7 | echo "caps is on" 8 | python3 ~/.i3/i3_wm_multi_disp_scripts/switch_to_next_wk_in_project.py 9 | fi 10 | --------------------------------------------------------------------------------