Window 'grouping'?
I was just wondering, is there any way of 'grouping' windows? I mean, is there a way of joining the edges of two or more windows together so that when one is moved, the other moves with it, acting as one large window? Or at least something similar where moving one window moves the other in the same way? I am running Ubuntu GNOME 15.10 with GNOME 3.18.
Solution 1:
Unfinished answer; looking for input
While at first sight it seems very well doable, using wmctrl
, as always, reality is (much) more complicated than theory.
I am hesitating to post this as an answer, since it is only an experimental, conceptual answer, not a ready-to-use solution (yet) due to some bugs. I am posting it nevertheless in the hope to get some input on solving issues in the current version. The question is interesting enough for further development (IMO) to see if a smooth solution can be created.
Language: None
Although I wrote the script in Python
, the language is irrelevant to the issues I am running into; mostly related to peculiarities in the use of wmctrl
.
I could use xdotool
to position windows, but since OP mentions moving a set of windows to another workspace, wmctrl
has some advantages, especially on using Gnome, where workspaces are arranged different from Unity.
An example how it currently works, moving a group of windows
moving windows as a group
- screen cast
The example in the screen cast above was made on Unity. It should however work similarly on Gnome
(apart from the deviation, see further below about the script).
- In the current setup, a group of windows can be created by adding the frontmost window to the group by calling the script below with the argument:
a
. In the screen cast, I added the command to a (Unity) launcher, onGnome
, it could be done with a shortcut key. - Subsequently, if the script is called with the argument
r
after one of the grouped windows is moved, the script moves all windows from the group likewise, restoring the windows, relative to each other:
How it is done
- Run with the option
a
, the script further below adds the currently active window, its position and size (as in the corresponding line in the output ofwmctrl -lG
), to a file,wgroup_data.txt
(in~
). - Run with the option
r
, the script reads the file, looks for the window that changed position, calculates the vector between the old- and the new position and moves the other windows accordingly. - If a window is closed, it is automatically removed from the group list by the script.
So far no problem.
Issues
However, if one of the windows does not fully fit inside the current workspace's borders, the script fails. Actually, the wmctrl
command fails, since it also fails "outside" the script, run as a single command.
- see when it fails
The script
#!/usr/bin/env python3
import subprocess
import os
import sys
arg = sys.argv[1]
# vertical deviation for Unity (use 0 for Gnome)
deviation = 28
fdata = os.environ["HOME"]+"/wgroup_data.txt"
def get_wmctrl():
# try because of buggy wmctrl...
try:
return subprocess.check_output(["wmctrl", "-lG"]).decode("utf-8")
except subprocess.CalledProcessError:
pass
def remove_window(window):
data = open(fdata).readlines()
[data.remove(l) for l in data if l.startswith(window)]
open(fdata, "wt").write(("").join(data))
def addwindow():
relevant = get_wmctrl()
frontmost = hex(int((subprocess.check_output(["xdotool", "getactivewindow"]).decode("utf-8").strip())))
frontmost = frontmost[:2]+str((10-len(frontmost))*"0")+frontmost[2:]
open(fdata, "+a").write([l+("\n") for l in get_wmctrl().splitlines() if frontmost in l][0])
print(frontmost)
def rearrange():
wlist = get_wmctrl()
if wlist != None:
group = [(l.strip(), l.split()) for l in open(fdata).read().splitlines() if not l in ("", "\n")]
try:
changed = [w for w in group if (w[0] in wlist, w[1][0] in wlist) == (False, True)][0] #
# only proceed if one of the grouped windows moved (give priority to a light loop if not):
follow = []
for w in group:
if not w == changed:
test = (w[0] in wlist, w[1][0] in wlist)
if test == (True, True):
follow.append(w)
elif test == (False, False):
# remove closed window from list
remove_window(w[1][0])
# only proceed if there are windows to move:
if follow:
# find match of the moved window (new coords)
wlines = wlist.splitlines()
match = [l.split() for l in wlines if changed[1][0] in l][0]
# calculate the move vector
x_move = int(match[2])-(int(changed[1][2])); y_move = int(match[3])-(int(changed[1][3]))
for w in follow:
# should be changed to try?
w[1][2] = str(int(w[1][2]) + x_move); w[1][3] = str(int(w[1][3]) + y_move - deviation)
subprocess.Popen([
"wmctrl", "-ir", w[1][0], "-e",
(",").join([w[1][1], w[1][2], w[1][3], w[1][4], w[1][5]])
])
# update grouplist
while True:
try:
newlines = sum([[l for l in get_wmctrl().splitlines() if w in l] for w in [match[0]]+[item[1][0] for item in follow]], [])
open(fdata, "wt").write(("\n").join(newlines))
break
except AttributeError:
pass
except IndexError:
print("nothing changed")
if arg == "a":
addwindow()
elif arg == "r":
rearrange()
How to use
-
The script needs both
wmctrl
andxdotool
sudo apt-get install xdotool wmctrl
Copy the script into an empty file, save it as
group_windows.py
-
If you are on Gnome:
In the head section of the script, change the line:deviation = 28
into
deviation = 0
-
Add two commands to different shortcuts:
python3 /path/to/group_windows.py a
to add windows to a group, and
python3 /path/to/group_windows.py r
to rearrange windows, as shown in the screen cast
Test the script by adding some windows to a group, move them around and restore their relative position, as shown in the screen cast.
Further development
The issue could be solved, code- wise, by simply refusing to move the windows in case any of the windows is expected to get out of the current workspace. In that case even the just moved window should be returned to its initial position to keep the relative positions alive.
This would however need extensive calculating (nothing for the computer, but complicated to code), and it would be more elegant to make partial positioning outside the current workspace possible; it is no problem when a window is positioned "on or over the edge" manually.
Any suggestions on solving the issue is more than welcome.