5

I frequently use Ctrl+Super+Arrow -Keys to move windows around. However, with a multiple monitor set up, the shortcuts only move the windows on the current screen.

**Example of my situation:

I have 3 monitors and I have an instance of Chrome open in my center monitor.

  1. When I hit Ctrl+Super+Arrow (left)the window splits to the left half of my center monitor.
  2. When I repeat, the window does not move.

What I'd like to see instead is that in [2.], the window moves to the right half of the left most monitor, and so on

How do I achieve this behaviour?

  • I wrote this one some time ago for two-screens setupGnome (should work on Unity as well, possibly with some minor issues) askubuntu.com/a/572052/72216 . I could rewrite the script a bit, but could you see if that' s what you like first? – Jacob Vlijm Dec 7 '15 at 20:11
  • Reading the link for "what it does" (2 monitors), if the targeted screen includes an adjacent monitor than yes. Another thing is I actually have 3 monitors total (all same resolution + horizontally oriented), looking at the script I'm not sure if it would work for > 2 monitors, ie. in my case in addition to left and right I think there may need to be a middle. Another question, to map the Ctrl - Super - Left/Right would require some binding to the script? Thanks a lot! – nivla12345 Dec 7 '15 at 21:37
3

Move your semi- maximized windows across multiple screens ("aero- snap" alike)

The script below does exactly as you describe. It can be used on any number of screens. The script is optimized for Unity, but can easily be edited to fit other window managers.

It turned out to be a bit more complicated than I thought, since the script should take into account that the user possibly set the launcher to only appear on the leftmost screen (or not). The targeted positions (and targeted window sizes) should be calculated accordingly.


enter image description here

Notes

  • The script moves the windows through all connected screens, subsequently switching through the "halves" of the screens, from left to right and vice versa.
  • The script reads the sizes of the screens and sets the window size, to half the screen size accordingly.
  • The script reads the settings on the launcher (if the launcher appears on only the first screen or on all), and calculates the targeted window sizes accordingly.
  • The script does not take into account the possibly set autohide option for the launcher. The reason is that updating the windowsize once the launcher hides or shows up would take a background script, and quite an other story to code.
  • The windows are moved and resized by a combination of xdotool and wmctrl - commands. Since these commands can have some "characteristic behaviour" in combination with Unity, there is a possiblility minor changes can be useful on your system. I tested it on two (quite different) systems however, working fine on both.

The script

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

# --- set possibly wanted marge (on the right side of the window) below
marge = 0
# --- set set launcher width below (default = 65)
default_launcherwidth = 65
# ---

move = sys.argv[1]
get = lambda cmd: subprocess.check_output(
    ["/bin/bash", "-c", cmd]).decode("utf-8")

screendata = [l for l in get("xrandr").splitlines() if " connected" in l]
# get the primary screen's position
pr = [l.split() for l in screendata if "primary" in l][0]
i = pr.index("primary"); s = pr[i+1]
primary= [int(s.split("x")[0]), int(s.split("+")[-2])]
# general screen list
screendata = [
    [s for s in l.split() if s.count("+") == 2][0] for l in screendata
    ]
screendata = [
    [int(s.split("x")[0]),int(s.split("+")[-2])] for s in screendata
    ]
screendata.sort(key=lambda x: x[1]); primary = screendata.index(primary)

def launcher_size():
    launcherset = get(
        "dconf read /org/compiz/profiles/unity/plugins/unityshell/num-launchers"
        ).strip()
    l_size_0 = 65; l_size_1 = l_size_0 if launcherset == "0" else 0
    return [l_size_0, l_size_1]

def magnets(index):
    l_size = l_sizes[0] if index == primary else l_sizes[1]
    screen = screendata[index]
    screenwidth = screen[0]; shift = screen[1]
    parts = (screenwidth - l_size)/2; virtual_mid = screenwidth - parts + shift
    left_trigger = shift + l_size
    return [left_trigger, int(virtual_mid), int(parts)]

def find_screen(x_loc):
    scr_index = len([scr for scr in screendata if x_loc >= scr[1]])-1
    scr_index = scr_index if scr_index >= 0 else 0
    screen = screendata[scr_index]
    return [scr_index, screen]

def get_active():
    active = hex(int(get("xdotool getactivewindow").strip()))
    active = active[:2]+(10-len(active))*"0"+active[2:] 
    x_pos = int([l.split()[2] for l in get("wmctrl -lG").splitlines() \
                 if l.startswith(active)][0])
    return [active, x_pos]

def decide(x_loc, active):
    current_scr = find_screen(x_loc)
    triggers = magnets(current_scr[0])
    width = triggers[2]
    index = current_scr[0]
    if move == "left":
        if x_loc <= triggers[1]:
            if x_loc == triggers[0]:
                if index != 0:
                    alter = magnets(index-1) 
                    width = alter[-1]
                    target = alter[1]
                else:
                    target = triggers[0]
            else:
                target = triggers[0]
        elif x_loc > triggers[1]:
            target = triggers[1]        
    elif move == "right":
        if x_loc >= triggers[1]:
            if index != len(screendata)-1:
                alter = magnets(index+1)
                width = alter[-1]
                target = alter[0]
            else:
                target = triggers[1]
        elif x_loc < triggers[1]:
            target = triggers[1]
    subprocess.call([
        "wmctrl", "-r", ":ACTIVE:","-b", "remove,maximized_vert,maximized_horz"
        ])
    subprocess.call(["wmctrl", "-ir", active, "-e", "0,"+str(target)+",400,"+\
                      str(int(width)-marge)+",200"])
    subprocess.call([
        "wmctrl", "-ir", active, "-b", "toggle,maximized_vert"
        ])

l_sizes = launcher_size()
w_data = get_active()
active = w_data[0]
x_loc = w_data[1]

decide(x_loc, active)

How to use

  • The scrpt needs both wmctrl and xdotool:

    sudo apt-get instal xdotool wmctrl
    
  • Copy the script into an empty file, save it as move_window.py

  • Test- run the script from a terminal window by (repeatedly) the command:

    python3 /path/to/move_window.py right
    

    which should move the window through your screens to the rignt, and:

    python3 /path/to/move_window.py left
    

    which should move the window through your screens to the left.

  • If all works fine, add both commands to shortcut keys: choose: System Settings > "Keyboard" > "Shortcuts" > "Custom Shortcuts". Click the "+" and add the commands to two shortcut keys of your choice.

Important notes

  1. The launcher width is set to 65px (corresponding to icon size 48px + 17px borders, which is the default). If the set width is different, it should be set correctly in the head of the script, as indicated in the section:

    # --- set set launcher width below (default = 65)
    default_launcherwidth = 65
    # ---
    
  2. In the headsection of the script, there is furthermore a line:

    marge = 0
    

    which sets the empty marge at the right side of the moved screen. Change it if you like to another value.

  • Hello Jacob, thanks for writing up this script! Your description is exactly what I want. When I run the script, it kinda works but not every time. For instance when I have an application in my middle monitor, "move_window.py left" remains stuck on the left side of my middle monitor and doesn't jump to my left-most monitor. "move_window.py right" works perfectly on the other hand. Thanks again! – nivla12345 Dec 15 '15 at 3:29
  • Hello Jacob, I just tested it again and there was no difference in behaviour. Unfortunately, I'm not sure on where to start debugging this issue :/ – nivla12345 Dec 16 '15 at 5:17
  • Here are the outputs: drive.google.com/… . The command: wmctrl -ir 0x0520000b -e 0,1920,400,960,200, didn't have any effect. – nivla12345 Dec 16 '15 at 6:55
  • 1
    Let us continue this discussion in chat. – nivla12345 Dec 16 '15 at 7:48
  • Hi @nivla12345 I believe this is the definitive working version. Hope you can confirm :). Please read the "Important notes". – Jacob Vlijm Dec 16 '15 at 13:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.