A different approach is to arrange the windows form a pre- defined(customizable) grid (columns/rows)
An example:
rearranged into (cols
set to 3, rows
set to 2):
rearranged into (cols
set to 4, rows
set to 2):
The script below can be used to do that. As said, the number of columns&rows can be set, as well as the padding between the windows. The script calculates then the positions the windows should be arranged into, as well as their sizes.
Using the wmctrl command on Unity
The wmctrl
command shows some peculiarities when used to move windows to- or very nearby the launcher or the panel. Therefore the margins:
left_margin = 70; top_margin = 30
cannot be set to zero. You have to keep at least a few px distance to both the panel and the launcher. I'd suggest leaving both margins- values as they are. All other values, padding, columns and rows you can play around with and set it as you like.
The script
#!/usr/bin/env python3
import subprocess
import getpass
import sys
#--- set your preferences below: columns, rows, padding between windows, margin(s)
cols = 2; rows = 2; padding = 10; left_margin = 70; top_margin = 30
#---
get = lambda cmd: subprocess.check_output(["/bin/bash", "-c", cmd]).decode("utf-8")
def get_res():
xr = get("xrandr").split(); pos = xr.index("current")
return [int(xr[pos+1]), int(xr[pos+3].replace(",", "") )]
# get resolution
res = get_res()
# define (calculate) the area to divide
area_h = res[0] - left_margin; area_v = res[1] - top_margin
# create a list of calculated coordinates
x_coords = [int(left_margin+area_h/cols*n) for n in range(cols)]
y_coords = [int(top_margin+area_v/rows*n) for n in range(rows)]
coords = sum([[(cx, cy) for cx in x_coords] for cy in y_coords], [])
# calculate the corresponding window size, given the padding, margins, columns and rows
w_size = [str(int(area_h/cols - padding)), str(int(area_v/rows - padding))]
# find windows of the application, identified by their pid
pids = [p.split()[0] for p in get("ps -e").splitlines() if sys.argv[1] in p]
w_list = sum([[w.split()[0] for w in get("wmctrl -lp").splitlines() if p in w] for p in pids], [])
print(pids, w_list, coords)
# remove possibly maximization, move the windows
for n, w in enumerate(w_list):
data = (",").join([str(item) for item in coords[n]])+","+(",").join(w_size)
cmd1 = "wmctrl -ir "+w+" -b remove,maximized_horz"
cmd2 = "wmctrl -ir "+w+" -b remove,maximized_vert"
cmd3 = "wmctrl -ir "+w+" -e 0,"+data
for cmd in [cmd1, cmd2, cmd3]:
subprocess.Popen(["/bin/bash", "-c", cmd])
How to use
- Make sure
wmctrl
is installed :)
- Copy thye script into an empty file, save it as
rearrange_windows.py
- In the head section of the script, set your preferences
Run it by the command:
python3 /path/to/rearrange_windows.py <application>
example: to rearrange chromium
windows:
python3 /path/to/rearrange_windows.py chromium
to rearrange chrome
windows
python3 /path/to/rearrange_windows.py chrome
Note
The script can be used to put windows of any application into a grid, since the process name of the application is used as an argument.
EDIT
Dynamic version
below a dynamic version of the script, as requested in a comment. This version of the script calculates the number of columns and rows, depending on the number of windows. The proportions of the rearranged window(s) is similar to the proportions of the screen.
The setup and the use is pretty much the same as the version above, only the number of columns and rows is now set automatically.
#!/usr/bin/env python3
import subprocess
import getpass
import sys
import math
#--- set your preferences below: padding between windows, margin(s)
padding = 10; left_margin = 70; top_margin = 30
#---
get = lambda cmd: subprocess.check_output(["/bin/bash", "-c", cmd]).decode("utf-8")
def get_res():
xr = get("xrandr").split(); pos = xr.index("current")
return [int(xr[pos+1]), int(xr[pos+3].replace(",", "") )]
# find windows of the application, identified by their pid
pids = [p.split()[0] for p in get("ps -e").splitlines() if sys.argv[1] in p]
w_list = sum([[w.split()[0] for w in get("wmctrl -lp").splitlines() if p in w] for p in pids], [])
# calculate the columns/rows, depending on the number of windows
cols = math.ceil(math.sqrt(len(w_list))); rows = cols
# define (calculate) the area to divide
res = get_res()
area_h = res[0] - left_margin; area_v = res[1] - top_margin
# create a list of calculated coordinates
x_coords = [int(left_margin+area_h/cols*n) for n in range(cols)]
y_coords = [int(top_margin+area_v/rows*n) for n in range(rows)]
coords = sum([[(cx, cy) for cx in x_coords] for cy in y_coords], [])
# calculate the corresponding window size, given the padding, margins, columns and rows
if cols != 0:
w_size = [str(int(area_h/cols - padding)), str(int(area_v/rows - padding))]
# remove possibly maximization, move the windows
for n, w in enumerate(w_list):
data = (",").join([str(item) for item in coords[n]])+","+(",").join(w_size)
cmd1 = "wmctrl -ir "+w+" -b remove,maximized_horz"
cmd2 = "wmctrl -ir "+w+" -b remove,maximized_vert"
cmd3 = "wmctrl -ir "+w+" -e 0,"+data
for cmd in [cmd1, cmd2, cmd3]:
subprocess.call(["/bin/bash", "-c", cmd])
See below the examples with a varying number of opened windows:
Explanation (second script)
Finding the specific windows
The command:
wmctrl -lp
lists all windows, in the format:
0x19c00085 0 14838 jacob-System-Product-Name *Niet-opgeslagen document 1 - gedit
where the first column is the window's unique id, and the third column is the pid of the application that owns the window.
The command:
ps -e
lists all processes, in the format:
14838 ? 00:00:02 gedit
where the first column is the application's pid, the last one is the process name.
By comparing these two lists, we can find all windows (id of-) which belong to a specific application (called w_list
in the script, as the result of line 17/18 in the script):
pids = [p.split()[0] for p in get("ps -e").splitlines() if sys.argv[1] in p]
w_list = sum([[w.split()[0] for w in get("wmctrl -lp").splitlines() if p in w] for p in pids], [])
Calculating the number of rows/columns
- If we make the windows of the same proportions as the screen, that means the number of columns is equal to the number of rows.
That implies that both the number of colums and rows are equal to the rounded up square root of the number of windows to rearrange. That is done in line 20:
cols = math.ceil(math.sqrt(len(w_list))); rows = cols
Calculating the window size and position
Once we have the number of columns, all we need to do is divide the available area (screen resolution - left margin/top margin) in the columns/rows and we have the targeted window size, which is then diminished by the padding
, as set in the head of the script:
w_size = [str(int(area_h/cols - padding)), str(int(area_v/rows - padding))]
The horizontal (x) positions are the result of the product(s) of the horizontal window size (including padding) times the column number, in a range of the number of columns. for example: if I have 3 colums of 300 px, the resulting x-positions are:
[0, 300, 600]
The vertical (y) positions are calculated likewise. Both lists are then combined into a list of coordinates, in which the windows will be rearranged.
This is done in line 26-28 of the script:
x_coords = [int(left_margin+area_h/cols*n) for n in range(cols)]
y_coords = [int(top_margin+area_v/rows*n) for n in range(rows)]
coords = sum([[(cx, cy) for cx in x_coords] for cy in y_coords], [])
The actual rearranging finally (after unmaximizing possibly maximized windows) is done from line 33 and further.
Best Answer
It can be done very well, but you need some understanding on Unity/viewports. I hope the story below is understandable, if not, please leave a comment.
The script below can be used to open a window of any application on any of your viewports, on any position, if you run it with the right arguments. The script is an edited version of this one, but now prepared to place windows on the spanning virtual desktop.
1. Understanding viewports and window coordinates
Workspaces in Unity
In Unity, unlike other window managers, you actually only have one spanning workspace, which is divided into viewports. In your case, your workspace is divided into eight viewports.
How the position of the windows is defined
The window position, as the output of the command:
is described as the position, relative to the upper left corner of the current viewport:
So if you are on viewport
1
:a window on viewport 2 to could be positioned on e.g. 1700 (x-wise) x 500 (y-wise)
(my screen is 1680x1050)
However, if you are on viewport 6:
the same window would be positioned on 20 (x), -550 (y)
Using these coordinates correctly is important to run the script with the right arguments, as described below:
2. How to use the script
The script below can be used to place a new window of an application on your virtual (spanning) workspace.
Make sure
wmctrl
is installed:Copy the script below into an empty file, save it as
setwindow
(no extension) in~/bin
. Create the directory if it doesn't exist yet. Make the script executable.~/bin
, either run the commandsource ~/.profile
or log out/in to make the directory available in$PATH
.Test run the command:
e.g.
A gedit window should show up on the current viewport.
Notes:
gedit
window on my system is e.g. appr. 470 px.<x_position>
to place windows on the left of the current viewport(s)<y_position>
to place windows above the current viewport(s)To open new windows on different viewports at once, you can simply chain commands. Looking at the viewport setup in the "Long story" example, If I am on viewport 1, I can open gedit windows on viewport 1, 2, 3 and 4 with the command:
The script
EDIT: the lazy version
In case you'd prefer to just enter coordinates and size, simply as if you would open a window on the current viewport, and give the targeted viewport as an argument (without having to calculate anything), then use the version below...
If you set it up like the first version of the script, you can run it with the command:
An example: to open a
Google-Chrome
window positioned on20, 20
, size300x300
, on viewport5
:The setup is pretty much the same as the first version of the script.
Note that also this script only works correctly if the defined window (position/size) fits completely within the targeted viewport.
The script:
Opening application windows with arguments
To finish the job, answering your question completely:
If you run the script as e.g.:
it will open a default window on the targeted desktop(s).
With the latest version of the script however, you can add an additional argument to open the application window, for example a
url
:e.g.:
If the (extra) argument contains spaces, use quotes. The above example will open a
google-chrome
window on viewport 3, opening theurl
http://askubuntu.com
.You can chain commands to open multiple windows/urls on different workspaces in one command, e.g.: