So I am using i3 an Linux window manager to manage my windows. In addition this is run on a laptop that is frequently mounted to several output-displays. This is handled by the following lines in my i3 config file
set $firstMonitor DP-2-1 set $secondMonitor DP-1-2 set $laptop eDP-1 workspace 1 output $firstMonitor $laptop workspace 2 output $firstMonitor $laptop workspace 3 output $firstMonitor $laptop workspace 4 output $firstMonitor $laptop workspace 5 output $secondMonitor $laptop workspace 6 output $secondMonitor $laptop workspace 7 output $secondMonitor $laptop workspace 8 output $secondMonitor $laptop workspace 9 output $secondMonitor $laptop workspace 10 output $laptop $laptop
For convience we can define variables in the config file using set and prefix them with $. The lines above tells my window manager I would like workspace 1
to 4
on $firstMonitor
if it exists. Otherwise fall back to $laptop
.
I am using this for various scripts and therefore need to extract this information from my config file, and store it somewhere. For instance it could look like this
[ { 'DP-2-1': ['1', '2', '3', '4'], 'DP-1-2': ['5', '6', '7', '8', '9'], 'eDP-1': ['10'] }, { 'eDP-1': ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10' } ]
parsed. To do so I wrote the following Python code to extract which output each workspace is assigned to. Note that the i3 file is picky about whitespaces, meaning the line set$firstMonitor DP-2-1
will not compile.
from pathlib import Path import collections CONFIG = Path.home() / ".config" / "i3" / "config" def read_config(config=CONFIG): with open(config, "r") as f: return f.readlines() def read_workspace_outputs(lines=read_config()): """Reads an i3 config, returns which output each workspaces is assigned to Example: set $firstMonitor DP-2-1 set $secondMonitor DP-1-2 set $laptop eDP-1 set $browser 1 workspace $browser output $firstMonitor $laptop workspace 2 output $firstMonitor $laptop workspace 3 output $firstMonitor $laptop workspace 4 output $firstMonitor $laptop workspace 5 output $secondMonitor $laptop workspace 6 output $secondMonitor $laptop workspace 7 output $secondMonitor $laptop workspace 8 output $secondMonitor $laptop workspace 9 output $secondMonitor $laptop workspace 10 output $laptop $laptop Will return [ { 'DP-2-1': ['1', '2', '3', '4'], 'DP-1-2': ['5', '6', '7', '8', '9'], 'eDP-1': ['10'] }, { 'eDP-1': ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10' } ] >>> read_workspace_outputs(lines=['workspace 1 output eDP-1']) [defaultdict(<class 'list'>, {'eDP-1': ['1']})] >>> read_workspace_outputs(lines=['set $laptop eDP-1','workspace 1 output eDP-1']) [defaultdict(<class 'list'>, {'eDP-1': ['1']})] >>> read_workspace_outputs(lines=['set $browser 1','workspace $browser output eDP-1']) [defaultdict(<class 'list'>, {'eDP-1': ['1']})] >>> read_workspace_outputs(lines=[ ... "set $firstMonitor DP-2-1", ... "set $secondMonitor DP-1-2", ... "set $laptop eDP-1", ... "", ... "workspace 1 output $firstMonitor $laptop", ... "workspace 3 output $secondMonitor $laptop", ... "workspace 5 output $laptop $laptop", ... ]) [defaultdict(<class 'list'>, {'DP-2-1': ['1'], 'DP-1-2': ['3'], 'eDP-1': ['5']}), defaultdict(<class 'list'>, {'eDP-1': ['1', '3', '5']})] """ # Extract workspaces and variables [set $name command] from file def get_workspaces_and_variables(lines): workspaces = [] variables_2_commands = dict() for line in lines: if line.startswith("workspace"): workspaces.append(line.strip()) elif line.startswith("set"): _, variable, *command = line.split() variables_2_commands[variable] = " ".join(command) return workspaces, variables_2_commands # Convert back $name to command for outputs and workspaces def workspaces_without_variables(workspaces, variables): workspace_outputs = [] for workspace in workspaces: workspace_str, output_str = workspace.split("output") workspace, variable = workspace_str.split() workspace_number = ( variables[variable] if variable.startswith("$") else variable ) outputs = [ variables[output] if output.startswith("$") else output for output in output_str.split() ] workspace_outputs.append([workspace_number, outputs]) return workspace_outputs # Currently things are stored as workspaces = [[output1, output 2], ...] # This flips the order and stores it as a dict with outputs as keys and values workspaces def workspaces_2_outputs(workspaces): output_workspaces = [ collections.defaultdict(list) for _ in range(len(max((x[1] for x in workspaces), key=len))) ] for (workspace_number, outputs) in workspaces: for j, output in enumerate(outputs): output_workspaces[j][output].append(workspace_number) return output_workspaces workspaces_w_variables, variables = get_workspaces_and_variables(lines) variable_free_workspaces = workspaces_without_variables( workspaces_w_variables, variables ) return workspaces_2_outputs(variable_free_workspaces) if __name__ == "__main__": import doctest import yaml from yaml.representer import Representer doctest.testmod() OUTPUT_WORKSPACE_CONFIG = ( Path.home() / ".config" / "i3" / "oisov-scripts" / "i3-output-workspace-2.yaml" ) yaml.add_representer(collections.defaultdict, Representer.represent_dict) with open(OUTPUT_WORKSPACE_CONFIG, "w") as file: yaml.dump(read_workspace_outputs(), file)
The output.yaml
file looks like this
- DP-1-2: - '5' - '6' - '7' - '8' - '9' DP-2-1: - '1' - '2' - '3' - '4' eDP-1: - '10' - eDP-1: - '1' - '2' - '3' - '4' - '5' - '6' - '7' - '8' - '9' - '10'
I will add some typing hints in the future, but for now I was wondering if this is the best approach to extract this information. The code feels a bit clunky, even if it does that I want it to.
For testing one can use the first code block in this example. A longer i3 example file can for instance be https://github.com/sainathadapa/i3-wm-config/blob/master/i3-default-config-backup, due note that this does not set the workspaces to specific outputs.