MODIFICATION IMPORTANTE
Ci-dessous une version réécrite du script de la première réponse (ci-dessous). Les différences:
Le scénario
#!/usr/bin/env python3
import subprocess
import sys
import time
import math
app_class = sys.argv[1]
ws_lock = [int(n)-1 for n in sys.argv[2].split(",")]
def check_wlist():
# get the current list of windows
try:
raw_list = [
l.split() for l in subprocess.check_output(
["wmctrl", "-lG"]
).decode("utf-8").splitlines()
]
ids = [l[0] for l in raw_list]
return (raw_list, ids)
except subprocess.CalledProcessError:
pass
def get_wssize():
# get workspace size
resdata = subprocess.check_output(["xrandr"]).decode("utf-8").split()
i = resdata.index("current")
return [int(n) for n in [resdata[i+1], resdata[i+3].replace(",", "")]]
def get_current(ws_size):
# vector of the current workspace to origin of the spanning desktop
dt_data = subprocess.check_output(
["wmctrl", "-d"]
).decode("utf-8").split()
curr = [int(n) for n in dt_data[5].split(",")]
return (int(curr[0]/ws_size[0]), int(curr[1]/ws_size[1]))
def get_relativewinpos(ws_size, w_data):
# vector to the application window, relative to the current workspace
xpos = int(w_data[2]); ypos = int(w_data[3])
xw = ws_size[0]; yw = ws_size[1]
return (math.ceil((xpos-xw)/xw), math.ceil((ypos-yw)/yw))
def get_abswindowpos(ws_size, w_data):
# vector from the origin to the current window's workspace (flipped y-axis)
curr_pos = get_current(ws_size)
w_pos = get_relativewinpos(ws_size, w_data)
return (curr_pos[0]+w_pos[0], curr_pos[1]+w_pos[1])
def wm_class(w_id):
# get the WM_CLASS of new windows
return subprocess.check_output(
["xprop", "-id", w_id.strip(), "WM_CLASS"]
).decode("utf-8").split("=")[-1].strip()
ws_size = get_wssize()
wlist1 = []
subprocess.Popen(["notify-send", 'workspace lock is running for '+app_class])
while True:
# check focussed window ('except' for errors during "wild" workspace change)
try:
focus = subprocess.check_output(
["xdotool", "getwindowfocus"]
).decode("utf-8")
except subprocess.CalledProcessError:
pass
time.sleep(1)
wdata = check_wlist()
if wdata != None:
# compare existing window- ids, checking for new ones
wlist2 = wdata[1]
if wlist2 != wlist1:
# if so, check the new window's class
newlist = [[w, wm_class(w)] for w in wlist2 if not w in wlist1]
valids = sum([[l for l in wdata[0] if l[0] == w[0]] \
for w in newlist if app_class in w[1]], [])
# for matching windows, check if they need to be moved (check workspace)
for w in valids:
abspos = list(get_abswindowpos(ws_size, w))
if not abspos == ws_lock:
current = get_current(ws_size)
move = (
(ws_lock[0]-current[0])*ws_size[0],
(ws_lock[1]-current[1])*ws_size[1]-56
)
new_w = "wmctrl -ir "+w[0]+" -e "+(",").join(
["0", str(int(w[2])+move[0]),
str(int(w[2])+move[1]), w[4], w[5]]
)
subprocess.call(["/bin/bash", "-c", new_w])
# re- focus on the window that was focussed
if not app_class in wm_class(focus):
subprocess.Popen(["wmctrl", "-ia", focus])
wlist1 = wlist2
Comment utiliser
Le script a besoin des deux wmctrl
et xdotool
:
sudo apt-get install wmctrl xdotool
Copiez le script ci-dessus dans un fichier vide, enregistrez-le sous lock_towspace.py
De votre application spécifique, découvrez WM_CLASS
: ouvrez votre application, exécutez dans un terminal:
xprop WM_CLASS and click on the window of the application
La sortie ressemblera (dans votre cas):
WM_CLASS: WM_CLASS(STRING) = "sun-awt-X11-XFramePeer", "MATLAB R2015a - academic use"
Utilisez la première ou la deuxième partie de la commande pour exécuter le script.
La commande pour exécuter le script est alors:
python3 /path/to/lock_towspace.py "sun-awt-X11-XFramePeer" 2,2
Dans la commande, la dernière section; 2,2
est l'espace de travail dans lequel vous souhaitez verrouiller l'application (sans espaces: (!) colonne, ligne ), au format "humain"; la première colonne / ligne est1,1
- Testez le script en l'exécutant. Pendant l'exécution, ouvrez votre application et laissez-la produire des fenêtres comme d'habitude. Toutes les fenêtres doivent apparaître dans l'espace de travail cible, comme défini dans la commande.
RÉPONSE ANCIENNE:
(deuxième) VERSION D'ESSAI
Le script ci-dessous verrouille une application spécifique sur son espace de travail initial. Si le script est démarré, il détermine sur quel espace de travail se trouve l'application. Toutes les fenêtres supplémentaires produites par l'application seront déplacées vers le même espace de travail en une fraction de seconde.
Le problème de mise au point est résolu en se recentrant automatiquement sur la fenêtre qui a été mise au point avant la production de la fenêtre supplémentaire.
Le scénario
#!/usr/bin/env python3
import subprocess
import time
import math
app_class = '"gedit", "Gedit"'
def get_wssize():
# get workspace size
resdata = subprocess.check_output(["xrandr"]).decode("utf-8").split()
i = resdata.index("current")
return [int(n) for n in [resdata[i+1], resdata[i+3].replace(",", "")]]
def get_current(ws_size):
# get vector of the current workspace to the origin of the spanning desktop (flipped y-axis)
dt_data = subprocess.check_output(["wmctrl", "-d"]).decode("utf-8").split(); curr = [int(n) for n in dt_data[5].split(",")]
return (int(curr[0]/ws_size[0]), int(curr[1]/ws_size[1]))
def get_relativewinpos(ws_size, w_data):
# vector to the application window, relative to the current workspace
xw = ws_size[0]; yw = ws_size[1]
return (math.ceil((w_data[1]-xw)/xw), math.ceil((w_data[2]-yw)/yw))
def get_abswindowpos(ws_size, w_data):
curr_pos = get_current(ws_size)
w_pos = get_relativewinpos(ws_size, w_data)
return (curr_pos[0]+w_pos[0], curr_pos[1]+w_pos[1])
def wm_class(w_id):
return subprocess.check_output(["xprop", "-id", w_id, "WM_CLASS"]).decode("utf-8").split("=")[-1].strip()
def filter_windows(app_class):
# find windows (id, x_pos, y_pos) of app_class
try:
raw_list = [l.split() for l in subprocess.check_output(["wmctrl", "-lG"]).decode("utf-8").splitlines()]
return [(l[0], int(l[2]), int(l[3]), l[4], l[5]) for l in raw_list if wm_class(l[0]) == app_class]
except subprocess.CalledProcessError:
pass
ws_size = get_wssize()
init_window = get_abswindowpos(ws_size, filter_windows(app_class)[0])
valid_windows1 = filter_windows(app_class)
while True:
focus = subprocess.check_output(["xdotool", "getwindowfocus"]).decode("utf-8")
time.sleep(1)
valid_windows2 = filter_windows(app_class)
if all([valid_windows2 != None, valid_windows2 != valid_windows1]):
for t in [t for t in valid_windows2 if not t[0] in [w[0] for w in valid_windows1]]:
absolute = get_abswindowpos(ws_size, t)
if not absolute == init_window:
current = get_current(ws_size)
move = ((init_window[0]-current[0])*ws_size[0], (init_window[1]-current[1])*ws_size[1]-56)
new_w = "wmctrl -ir "+t[0]+" -e "+(",").join(["0", str(t[1]+move[0]), str(t[2]+move[1]), t[3], t[4]])
subprocess.call(["/bin/bash", "-c", new_w])
focus = str(hex(int(focus)))
z = 10-len(focus); focus = focus[:2]+z*"0"+focus[2:]
if not wm_class(focus) == app_class:
subprocess.Popen(["wmctrl", "-ia", focus])
valid_windows1 = valid_windows2
Comment utiliser
Le script a besoin à la fois de wmctrl
etxdotool
sudo apt-get install wmctrl xdotool
Copiez le script dans un fichier vide, enregistrez-le sous keep_workspace.py
déterminez le WM_CLASS de votre application en ouvrant l'application, puis ouvrez un terminal et exécutez la commande:
xprop WM_CLASS
Cliquez ensuite sur la fenêtre de votre application. Copiez la sortie, comme "sun-awt-X11-XFramePeer", "MATLAB R2015a - academic use"
dans votre cas, et placez-la entre guillemets simples dans la section head du script, comme indiqué.
Exécutez le script avec la commande:
python3 /path/to/keep_workspace.py
Si cela fonctionne comme vous le souhaitez, je vais ajouter une fonction bascule. Bien que cela fonctionne déjà depuis quelques heures sur mon système, il pourrait cependant avoir besoin de quelques ajustements en premier.
Remarques
Bien que vous ne devriez pas le remarquer, le script fait ajouter un peu de charge du processeur au système. Sur mon système âgé, j'ai remarqué une augmentation de 3 à 10%. Si vous aimez la façon dont cela fonctionne, je vais probablement l'ajuster davantage pour réduire la charge.
Le script, en l'état, suppose que les fenêtres secondaires sont de la même classe que la fenêtre principale, comme vous l'avez indiqué dans un commentaire. Avec un changement (très) simple, les fenêtres secondaires peuvent cependant être d'une autre classe.
Explication
Bien que probablement pas très intéressant pour un lecteur moyen, le script fonctionne en calculant en vecteurs. Au démarrage, le script calcule:
- le vecteur de l'origine à l'espace de travail actuel avec la sortie de
wmctrl -d
- le vecteur à la fenêtre de l'application, par rapport à l'espace de travail courant, par la sortie de
wmctrl -lG
- À partir de ces deux, le script calcule la position absolue de la fenêtre de l'application sur le bureau s'étendant (tous les espaces de travail dans une matrice)
A partir de là, le script recherche de nouvelles fenêtres de la même application, avec la sortie de xprop WM_CLASS
, recherche leur position de la même manière que ci-dessus et les déplace vers l'espace de travail "d'origine".
Étant donné que la fenêtre nouvellement créée "a volé" le focus de la dernière fenêtre utilisée sur laquelle l'utilisateur travaillait, le focus est ensuite défini sur la fenêtre qui avait le focus auparavant.