5

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.

  • Interesting question. Could be done, but the question is how to set the window group in such a way that "feels" logical. Do you have an incidental (single time use) set in mind, or a set, going along together for "longer time". – Jacob Vlijm Oct 04 '15 at 11:24
  • @JacobVlijm: Well, firstly it would be very useful when wanting to move multiple windows to another workspace, and secondly if I have lots of system monitoring tools running in different Terminal windows I would like to have an easy way of making those windows one so that I can easily move and minimize them. –  Oct 04 '15 at 11:46
  • You can use Terminator to solve your terminal window grouping problem. – TheWanderer Oct 04 '15 at 11:48
  • @Zacharee1: If I suggested this as a feature to my window manager, could they implement it? –  Oct 04 '15 at 16:56
  • I wouldn't know, but it doesn't seem like it's extremely hard. Terminator and GIMP both have it. – TheWanderer Oct 04 '15 at 17:05
  • I see, should be very well possible, but I might need some time. Things are quite busy at the moment, but if no brilliant solution pops up, I will get back to it. – Jacob Vlijm Oct 04 '15 at 18:42
  • Your question seems to exist of actually three sections: 1] move to another workspace, 2] move around as a group on the current workspace, 3] (un-) minimize as a group. All can be done, but making it as one comprehensive solution is too comprehensive, a lot of work (for now). Which of the three would you consider as first priority to make an acceptable solution (if at all)? – Jacob Vlijm Oct 05 '15 at 11:15
  • @JacobVlijm: Move around as a group probably. –  Oct 05 '15 at 15:30
  • Working on it, might take a few days... – Jacob Vlijm Oct 06 '15 at 14:26
  • ...or a week; I managed to succesfully move around a group of windows. Updating the information on the new windowposition is problematic at the moment however. As always, practice is more complicated than theory :) – Jacob Vlijm Oct 07 '15 at 17:55
  • KDE has a window tabbing feature, i heard. You may want to look it up – Sergiy Kolodyazhnyy Oct 12 '15 at 20:46
  • Hi P., I still think it is very well possible. Unfortunately, these weeks are the busiest ones of the year for me. The good news is that I actually got it working and a lot changed since my unfinished answer. In the current state, the "leading" moved window simply bounces back if one of the grouped windows would get out of the screen's area. It needs to be rewritten and tested on gnome to make it a real answer however. That will happen in a few weeks if no other solution pops up. Nice question! – Jacob Vlijm Oct 19 '15 at 13:03
  • Minor update: I had an unpolished version, which could move around grouped windows, but due to limitations of both wmctrl and xdotool, windows cannot be partially outside the workspace. The script then returns the moved window to its "unmoved" position. Would that be acceptable? – Jacob Vlijm Nov 20 '15 at 17:44
  • @JacobVlijm: Yes, that would be acceptable. –  Nov 20 '15 at 20:12

1 Answers1

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

enter image description here

moving windows as a group

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, on Gnome, 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 of wmctrl -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.

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

  1. The script needs both wmctrl and xdotool

    sudo apt-get install xdotool wmctrl
    
  2. Copy the script into an empty file, save it as group_windows.py

  3. If you are on Gnome:
    In the head section of the script, change the line:

    deviation = 28
    

    into

    deviation = 0 
    
  4. 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

  5. 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.

Jacob Vlijm
  • 83,767