Multiple configurations for different monitor setups in i3wm


Screenshot of my workspace configuration in i3wm
Figure 1: Anyone that recognize this image might find this “guide” useful!

I use i3 window manager (i3wm, on my ThinkPad X2xx series Ubuntu Linux work laptop. At work I have a docking bay for the laptop and an external screen connected to it, at home I have just a HDMI cable that I plug in. I really like how i3 makes everything quick and easy, it just takes effort to learn how to set things up, like most Linux things.




The problem

In i3, to set up workspaces on specific monitors, you have to have `workspace X output OUTPUT` in your i3 configuration file (usually at ~/.config/i3/config, but changes with version), where X is workspace number 1 through 10, and OUTPUT is the name of the display output. This means that the workspace output is defined in the i3 configuration file, when i3 is loaded! Put in another way, the variable OUTPUT_E as you see in Figure 1 above, is hard coded in the i3 config file at start. You might want to define this variable different depending on your situation. You can use some suggested solutions to this, see e.g. answers to these questions:

Both of these solutions are less than ideal, as they don’t change the i3 configuration file to assign workspaces.

To get a list of your outputs, you can run

    xrandr | grep connect | cut -d’ ‘ -f1

in a terminal, assuming you have grep, coreutils, xrandr.
For me this list is

    eDP1, DP1, DP2, HDMI1, HDMI2, VIRTUAL1.

At work one of the DPX outputs is used, and at home the normal HDMI output is HDMI2, the standard internal monitor is eDP1.

The solution

I have set up a rather bulky, but working solution which is a script that checks which external monitor is connects, and if any, open the i3 configuration file and change the definition of the variable OUTPUT_E. This is less then ideal, but OK. This means that when I start my computer, or if I move it and plug in an external monitor, I first run my sensing script which finds any connected extra monitor and redefines the OUTPUT_E to that (or my internal one if none is connected), and then I can choose my self to reload i3 ($MOD+SHIFT+R). I also have a separate i3 mode to configure the monitor layout, see Figure 2.

My monitor setup mode in i3wm.

    Figure 2: My monitor setup/mode section of i3 configuration file. A bit messy at the moment, but does what I want.

The sensing and changing script

So first I have a (Python 3) script to do the sensing and editing of the configuration file:

import subprocess
import fileinput
import sys
from shutil import copy2
import os
cwd = os.getcwd()

# Path to the i3 config file
CONFIG_FILE = 'config'

# FIRST get the connected displays

# alternative 1
outputs = subprocess.Popen('xrandr | grep connected | grep -v disconnected | cut -d' ' -f1',shell=True, stdout=subprocess.PIPE).communicate()[0].split()
# "eDP1" is the internal monitor
outputs = [i.decode('utf-8') for i in outputs if INTERNAL_DISPLAY not in i.decode('utf-8')]

def change_external(line):
    # function that changes one thing in a file
    # returns each line and returns the change when
    # a match is passed to it.
    if line.startswith('set $OUTPUT_E'):
        if not outputs: # if outputs is empty, only internal display is there
            return '{0} {1}n'.format(' '.join(line.split()[:-1]), INTERNAL_DISPLAY)
        elif outputs:
            return '{0} {1}n'.format(' '.join(line.split()[:-1]),outputs[0])
    else: # if it doesn't contain it
        return line

# copy the file
    os.path.join(cwd,'{0}.bak'.format(CONFIG_FILE)) )

# open inplace for editing. Grabs the stout and writes
for line in fileinput.input(CONFIG_FILE, inplace=True):

This script just goes through the i3wm config file line by line until it finds on that starts with set $OUTPUT_E  and writes the external monitor connected, or the default internal one if none is found. This of course assumes you have a similar section in your configuration file as shown in Figure 1 above.

File locations

As you can see from the second half, this script assumes that it is run IN THE FOLDER where the i3 configuration file (config) is. This means that this script can be anywhere on your machine, and then in your /usr/bin you have a runable script (let’s call it monitor_setup) with the following contents:

(cd $HOME/.config/i3 && python /PATH/TO/


Where you of course change /PATH/TO/ to whatever is applicable for you. This only works for the user that has access to the script. So it might be better to just have the script in the /usr/bin folder directly and do some Python scripting to run it from the $HOME/.config/i3 directly there. So, with this I just hit $MOD+d, type in monitor_setup and hit Enter, then $MOD+SHIFT+r to get my workspace binding the way I want, wherever I am. I think it should be possible to do this in bash with sed, but Python is just quicker for me.