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.

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
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
# ---
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.
Best Answer
Install compizconfig-settings-manager
. Search for "compiz" in the Unity Dash, or start with
ccsm
from the commandline.Find the Grid plugin:
Click on the key binding you want to change, click on Grab key combination, and press the keys you'd like to use.