List windows, choose one to move to the current workspace
When the script below is called, it will list all windows on all workspaces. Pick one and press OK to move the window to the current workspace and raise it. By default, it moves the window to position 100
(x), 100
(y)
The script is relatively simple as a result of the use of both wmctrl
and xdotool
. While wmctrl
is used to list all windows, xdotool
simply moves them to a predefined position on the current workspace "without asking questions" on the window's size (unlike wmctrl
) and relative positions of both workspaces to each other.
A more precise positioning of the window, according to the position on its original workspace, would very well be possible, but would also multiply the needed code (like e.g. here). I assume in most situations, this will do.
An example:
I am on workspace 8, while I have a gedit
window on workspace 1. Calling the script lists the windows:
picking the gedit window will move it to the current workspace:
The script
#!/usr/bin/env python3
import subprocess
import socket
import time
def get(command):
return subprocess.check_output(["/bin/bash", "-c", command]).decode("utf-8")
def check_window(w_id):
w_type = get("xprop -id "+w_id)
if " _NET_WM_WINDOW_TYPE_NORMAL" in w_type:
return True
else:
return False
# split wmctrl output by machine name
wlist = [l.split(socket.gethostname()) for l in get("wmctrl -l").splitlines()]
# extract window -id from first section
wlist = [[wlist[i][0].split()[0], wlist[i][-1].strip()] for i, l in enumerate(wlist)]
# filter only "real, normal" windows
wlist = [w for w in wlist if check_window(w[0]) == True]
# create columns for zenity list
cols = (" ").join(['"'+w[1]+'" '+'"'+w[0]+'"' for w in wlist])
# calculate height and width for the zenity window, according to window names and list length
h = str(140+(len(wlist)*23))
w = str((max([len(w[-1]) for w in wlist])*8))
# define the zenity window
cmd = "zenity --list --hide-column=2 --print-column=2 "+\
"--title='Window list' --column='Current windowlist' "+\
"--column='wid' --height="+h+" --width="+w+" "+cols
try:
# call the window
w_id = get(cmd).split("|")[-1].strip()
# move the selected window to the current workspace
subprocess.Popen(["xdotool", "windowmove", "--sync", w_id, "100", "100"])
# raise it (the command below alone should do the job, but sometimes fails
# on firefox windows without first moving the window).
subprocess.Popen(["wmctrl", "-iR", w_id])
except subprocess.CalledProcessError:
pass
How to use
The script needs both wmctrl
and xdotool
sudo apt-get install wmctrl xdotool
copy the script into an empty file, svae it as move_windows.py
Test-run it by the command:
python3 /path/to/move_windows.py
a window should appear, listing currently opened windows:
pick one to see if it is moved to the current workspace and raised correctly.
If all works fine, ad it to a shortcut key: choose: System Settings > "Keyboard" > "Shortcuts" > "Custom Shortcuts". Click the "+" and add the command:
python3 /path/to/move_windows.py
Note
The size of the zenity
window, listing current windows, is set automatically. The script looks for the longest window name and the number of rows (windows) and sets the size accordingly.
EDIT
As requested in a comment, below a version in which the zenity
list- window includes more information: de current workspace of the targeted window(s) and the application it belongs to.
As mentioned above, the information on the relative/absolute workspace positions leads to a more "substantial" amount of code, but luckily I could use this earlier answer as a base.
How to use
The use is pretty much the same as the first version of the script (above), but the command needs to include the preferred sorting option. Run it by one of the commands:
python3 /path/to/move_windows.py -app
to sort the list by application,
python3 /path/to/move_windows.py -ws
to sort the list by workspace, and
python3 /path/to/move_windows.py -win
to sort the list by window name.
The script:
#!/usr/bin/env python3
import subprocess
import socket
import sys
arg = sys.argv[1]
# list (column) header titles and their (data) position in the produced window data list
cols = [["Workspace", -1], ["Application name", -2] , ["Window name", -3]]
# rearrange columns, depending on the chosen option
if arg == "-app":
cols = [cols[1], cols[2], cols[0]]
elif arg == "-ws":
cols = [cols[0], cols[2], cols[1]]
elif arg == "-win":
cols = [cols[2], cols[1], cols[0]]
# extract headers, list positions, to be used in the zenity list
col1 = cols[0][0]; i1 = cols[0][1]
col2 = cols[1][0]; i2 = cols[1][1]
col3 = cols[2][0]; i3 = cols[2][1]
# just a helper function
get = lambda cmd: subprocess.check_output([
"/bin/bash", "-c", cmd
]).decode("utf-8")
# analyse viewport data, to be able to calculate relative/absolute position of windows
# and current viewport
def get_spandata():
xr = get("xrandr").split(); pos = xr.index("current")
res = [int(xr[pos+1]), int(xr[pos+3].replace(",", "") )]
spandata = get("wmctrl -d").split()
span = [int(n) for n in spandata[3].split("x")]
cols = int(span[0]/res[0]); rows = int(span[1]/res[1])
curr_vector = [int(n) for n in spandata[5].split(",")]
curr_viewport = int((curr_vector[1]/res[1])*cols + (curr_vector[0]/res[0])+1)
return {"resolution": res, "n_columns": cols, "vector": curr_vector, "current_viewport": curr_viewport}
posdata = get_spandata()
vector = posdata["vector"]; cols = posdata["n_columns"]
res = posdata["resolution"]; currvp = posdata["current_viewport"]
# function to distinguish "normal" windows from other types (like the desktop etc)
def check_window(w_id):
w_type = get("xprop -id "+w_id)
if " _NET_WM_WINDOW_TYPE_NORMAL" in w_type:
return True
else:
return False
# split windowdata by machine name
mach_name = socket.gethostname()
wlist = [[l.strip() for l in w.split(mach_name)] for w in get("wmctrl -lpG").splitlines()]
# split first section of window data
for i, w in enumerate(wlist):
wlist[i][0] = wlist[i][0].split()
# filter only "real" windows
real_wlist = [w for w in wlist if check_window(w[0][0]) == True]
# adding the viewport to the window's data
for w in real_wlist:
w.append(get("ps -p "+w[0][2]+" -o comm=").strip())
loc_rel = [int(n) for n in w[0][3:5]]
loc_abs = [loc_rel[0]+vector[0], loc_rel[1]+vector[1]]
abs_viewport = int((loc_abs[1]/res[1])*cols + (loc_abs[0]/res[0])+1)
abs_viewport = str(abs_viewport)+"*" if abs_viewport == currvp else str(abs_viewport)
w.append(abs_viewport)
# set sorting rules
if arg == "-app":
real_wlist.sort(key=lambda x: x[-2])
elif arg == "-ws":
real_wlist.sort(key=lambda x: x[-1])
elif arg == "-win":
real_wlist.sort(key=lambda x: x[-3])
# calculate width and height of the zenity window:
# height = 140px + 23px per line
h = str(140+(len(real_wlist)*23))
# width = 250px + 8px per character (of the longest window title)
w = str(250+(max([len(w[-3]) for w in real_wlist])*8))
# define the zenity window's content
cmd = "zenity --list --hide-column=4 --print-column=4 --title='Window list' "\
"--width="+w+" --height="+h+" --column='"+col1+"' --column='"+col2+"' --column='"+col3+\
"' --column='w_id' "+(" ").join([(" ").join([
'"'+w[i1]+'"','"'+w[i2]+'"','"'+w[i3]+'"','"'+w[0][0]+'"'
]) for w in real_wlist])
# finally, call the window list
try:
w_id = subprocess.check_output(["/bin/bash", "-c", cmd]).decode("utf-8").split("|")[0]
subprocess.Popen(["xdotool", "windowmove", "--sync", w_id, "100", "100"])
subprocess.Popen(["wmctrl", "-iR", w_id])
except subprocess.CalledProcessError:
pass
EDIT 2: 15.04 specific
The output of the used ps
command seems to have changed for gnome-terminal
in 15.04. Therefore, in 15.04, the application name of gnome-terminal
was not displayed correctly in the script above. The version below derives the application name from the WM_CLASS
, as in the output of the xprop
command:
The usage is exactly the same as in the (second) script above:
#!/usr/bin/env python3
import subprocess
import socket
import sys
arg = sys.argv[1]
# list (column) header titles and their (data) position in the produced window data list
cols = [["Workspace", -1], ["Application name", -2] , ["Window name", -3]]
# rearrange columns, depending on the chosen option
if arg == "-app":
cols = [cols[1], cols[2], cols[0]]
elif arg == "-ws":
cols = [cols[0], cols[2], cols[1]]
elif arg == "-win":
cols = [cols[2], cols[1], cols[0]]
# extract headers, list positions, to be used in the zenity list
col1 = cols[0][0]; i1 = cols[0][1]
col2 = cols[1][0]; i2 = cols[1][1]
col3 = cols[2][0]; i3 = cols[2][1]
# just a helper function
get = lambda cmd: subprocess.check_output([
"/bin/bash", "-c", cmd
]).decode("utf-8")
# analyse viewport data, to be able to calculate relative/absolute position of windows
# and current viewport
def get_spandata():
xr = get("xrandr").split(); pos = xr.index("current")
res = [int(xr[pos+1]), int(xr[pos+3].replace(",", "") )]
spandata = get("wmctrl -d").split()
span = [int(n) for n in spandata[3].split("x")]
cols = int(span[0]/res[0]); rows = int(span[1]/res[1])
curr_vector = [int(n) for n in spandata[5].split(",")]
curr_viewport = int((curr_vector[1]/res[1])*cols + (curr_vector[0]/res[0])+1)
return {"resolution": res, "n_columns": cols, "vector": curr_vector, "current_viewport": curr_viewport}
posdata = get_spandata()
vector = posdata["vector"]; cols = posdata["n_columns"]
res = posdata["resolution"]; currvp = posdata["current_viewport"]
# function to distinguish "normal" windows from other types (like the desktop etc)
def check_window(w_id):
w_type = get("xprop -id "+w_id)
if " _NET_WM_WINDOW_TYPE_NORMAL" in w_type:
cl = [l.replace('"', '').split(",")[-1].strip()\
for l in w_type.splitlines() if "WM_CLASS(STRING)" in l][0]
return (True, cl)
else:
return (False, "")
# split windowdata by machine name
mach_name = socket.gethostname()
wlist = [[l.strip() for l in w.split(mach_name)] for w in get("wmctrl -lpG").splitlines()]
# split first section of window data
for i, w in enumerate(wlist):
wlist[i][0] = wlist[i][0].split()
# filter only "real" windows
real_wlist = [w+[check_window(w[0][0])[1]] for w in wlist if check_window(w[0][0])[0] == True]
# adding the viewport to the window's data
for w in real_wlist:
loc_rel = [int(n) for n in w[0][3:5]]
loc_abs = [loc_rel[0]+vector[0], loc_rel[1]+vector[1]]
abs_viewport = int((loc_abs[1]/res[1])*cols + (loc_abs[0]/res[0])+1)
abs_viewport = str(abs_viewport)+"*" if abs_viewport == currvp else str(abs_viewport)
w.append(abs_viewport)
# set sorting rules
if arg == "-app":
real_wlist.sort(key=lambda x: x[-2])
elif arg == "-ws":
real_wlist.sort(key=lambda x: x[-1])
elif arg == "-win":
real_wlist.sort(key=lambda x: x[-3])
# calculate width and height of the zenity window:
# height = 140px + 23px per line
h = str(140+(len(real_wlist)*23))
# width = 250px + 8px per character (of the longest window title)
w = str(250+(max([len(w[-3]) for w in real_wlist])*8))
# define the zenity window's content
cmd = "zenity --list --hide-column=4 --print-column=4 --title='Window list' "\
"--width="+w+" --height="+h+" --column='"+col1+"' --column='"+col2+"' --column='"+col3+\
"' --column='w_id' "+(" ").join([(" ").join([
'"'+w[i1]+'"','"'+w[i2]+'"','"'+w[i3]+'"','"'+w[0][0]+'"'
]) for w in real_wlist])
# finally, call the window list
try:
w_id = subprocess.check_output(["/bin/bash", "-c", cmd]).decode("utf-8").split("|")[0]
subprocess.Popen(["xdotool", "windowmove", "--sync", w_id, "100", "100"])
subprocess.Popen(["wmctrl", "-iR", w_id])
except subprocess.CalledProcessError:
pass
Best Answer
What you ask is actually to allow a specific application's window to only appear either on first- or last position, z-wise.
When the gedit window (in this example) loses focus, it is sent to the last poition (z-wise, below the semi-transparent terminal window) instead of descending only one position:
Z- position of a window
While it can be done, we still have to overcome some serious complications; When the window is sent to the very last position, you will want to keep the z-order of all other windows. However, currently, there are no tools that can give us this z-order of windows. Both
xdotool
andwmctrl
give us no information on this whatsoever.What we can do however is to keep track of the focus history of (all) windows. Since a window descends one position if another window gets focus, we can conclude the z-order of windows if we run a background script to watch the focus history of windows.
The solution two small background scripts
The solution below exists of two small background scripts, to run simultaneously.
focus_history.py
set_z.py
Script 1
focus-history.py
Script 2
How to set up
The script uses both
wmctrl
andxdotool
Copy script 1 into an empty file, save it (exactly!) as
focus_history.py
Copy script 2 into an empty file, save it as
set_z.py
in the exact same directory as script 1.In the head section of the script, in the line:
replace
"gedit"
by the process name of your application (between quotes)Test- run the script: Before opening any (additional) windows, start script 1 by the command:
[The script will recognize windows that were focussed at least once. That will be the case if the script is run on log in]
As mentioned, the scripts should be in one and the same directory, on the same level.
Now start opening windows and see how it behaves. Your application should move to the (very) background if it loses focus.
If all works fine, add it to Startup Applications: Dash > Startup Applications > Add. Add the command:
Notes
Alternatively
Alternatively, you could set a shortcut key to raise a specific applications's window if it exists, as explained here.
That would require however to have another shortcut to go back to the first application's window,
Unless...,
You would setup one shortcut to toggle between two applications. That would however be out of the scope of this question...