Is there a way to prevent your windows from moving when an external monitor is connected?

So, I'm using Ubuntu 14.10 on my laptop and occasionally plug it into my TV for a second screen. My TV is to the left of my desk. When I enable it as an external monitor to the left of my laptop screen all of the windows that were in my laptop screen move over to the TV screen. I can move them back, but it's really annoying to have to do so every time, especially when there are several windows open.

TV on Left

The windows do not move, however, if I set my TV screen to the right of my laptop screen (virtually). But this is understandably confusing to use since it's the opposite of the physical setup. Also, I don't want to move my desk.

TV on Right

It seems like Ubuntu or the display server simply assumes that the leftmost monitor is the main one and where all the windows should be. Is there a way to disable this behavior?

I've been checking these forums but haven't really seen anyone post about this. The closest thread I found was this one, though it's not quite the same issue.

Get Ubuntu to NOT move windows when turning off one of multiple monitors

Anyone have any ideas? Please let me know if you do. Thanks!


I did not find a "secret" setting to change the behaviour of the, as it seems, designed behaviour. It looks indeed as if the left screen is assumed to be the "base" screen.

It is however very well possible to create a workaround, with essentially the same result. You can create a script that, on the occasion of connecting a second screen, lists all windows. Subsequently, all windows that are moved initially to the left screen, are shifted back to the screen on the right, within a second or two. The size of all windows will be preserved.
That is what the script below does.

Two versions

You can restore your arranged windows in two ways:

  • Occasionally, with a shortcut key to run after the second screen is connected.
  • Automatically, running the script in the background, waiting for your screen to be connected.

How to use

preparations

  • Install wmctrl

    sudo apt-get install wmctrl

  • Look up your two screens' names with the help of xrandr, the names of the screens will be just before the word "connected".

  • Copy Either one of the scripts below, in the head section, replace in these two lines the screen names by the correct ones:

    screen_1 = "LVDS1"     # your main screen (laptop)
    screen_2 = "VGA1"      # secundary screen (on the left)
    

    save the script as move_windows.py

  • Make sure in display settings your secondary screen is on the left. The top lines of the two screens need to be in line (like in the first image of your question).

Run the script
- If you use the one to run occasionally, run it after your second screen is connected.

    python3 /path/to/move_windows.py

You might want to add it to a keyboard shortcut if you think it does what it should do, Choose: System Settings > "Keyboard" > "Shortcuts" > "Custom Shortcuts". Click the "+" and add the command:

  • If you use the one to run in the background, also run it by the command:

    python3 /path/to/move_windows.py
    

    If it acts as you intended, add it to your start-up applications: Dash > Startup Applications > Add

I tested the script with my laptop (on the right) and two different screens (on the left). The result was the same.

laptop screen

enter image description here

connecting without script

enter image description here

connecting with the script running

enter image description here

After the script did its job, windows will be "left alone" (of course), and you can arrange your windows your way.

The script(s)

1. "Manual" version, to run after the screen is connected

#!/usr/bin/env python3
import subprocess
import time

#--
screen_2 = "LVDS1"       # replace with your internal screen (right)
screen_2 = "VGA1"        # replace with your external screen (left)
#--

def get(cmd):
    return subprocess.check_output(["/bin/bash", "-c",  cmd]).decode("utf-8")

def get_shift(xr_output):
    lines = [l for l in xr_output.splitlines() if " connected" in l][0].split()
    return int([it for it in lines if "x" in it][0].split("x")[0])

def shift_windows(shift):
    w_data = [l.split() for l in get("wmctrl -lG").splitlines()]
    relevant = []
    for w in w_data:
        props = get("xprop -id "+w[0])
        if (int(w[2]) < shift, "_TYPE_NORMAL" in props, "TYPE_DIALOG" in props).count(True) == 2:
            command = "wmctrl -ir "+w[0]+" -e 0,"+(",").join([str(int(w[2])+shift), w[3], w[4], w[5]])
            subprocess.Popen(["/bin/bash", "-c", command])

shift_windows(get_shift(get("xrandr")))

2. Automatic version, to run in the background

#!/usr/bin/env python3
import subprocess
import time

#--
screen_2 = "LVDS1"       # replace with your internal screen (right)
screen_2 = "VGA1"        # replace with your external screen (left)
#--

def get(cmd):
    return subprocess.check_output(["/bin/bash", "-c",  cmd]).decode("utf-8")

def get_shift(xr_output):
    lines = [l for l in xr_output.splitlines() if " connected" in l][0].split()
    return int([it for it in lines if "x" in it][0].split("x")[0])

def shift_windows(shift):
    w_data = [l.split() for l in get("wmctrl -lG").splitlines()]
    relevant = []
    for w in w_data:
        props = get("xprop -id "+w[0])
        if (int(w[2]) < shift, "_TYPE_NORMAL" in props, "TYPE_DIALOG" in props).count(True) == 2:
            command = "wmctrl -ir "+w[0]+" -e 0,"+(",").join([str(int(w[2])+shift), w[3], w[4], w[5]])
            subprocess.Popen(["/bin/bash", "-c", command])

while True:
    try:
        screen_info1 = get("xrandr")
        time.sleep(5)
        screen_info2 = get("xrandr")
        check = screen_2+" connected"
        if (check in screen_info1, check in screen_info2) == (False, True):
            time.sleep(5)
            shift_windows(get_shift(screen_info2))
    except:
        pass