Comment verrouiller une application (et toutes ses nouvelles fenêtres) dans un espace de travail spécifique?

11

J'exécute un Matlabscript dans le workspace 1. Cela génère plusieurs tracés. En attendant, je passe workspace 2et travaille là-bas. Mon problème est que les complots apparaissent workspace 2. Est-il possible de verrouiller le logiciel dans un espace de travail. Donc, tout en Matlabgénérant les parcelles workspace 1, je peux travailler workspace 2sans perturber les parcelles pop-up?

OH LA LA
la source
Unity, GNOME Shell ou autre chose?
AB
J'ajoute les tags, c'est Ubuntu 14.04 avec Unity
OHLÁLÁ
À quelle classe appartiennent les fenêtres de tracé? (pourriez-vous vérifier avec la commande xprop WM_CLASS, puis cliquer sur la fenêtre?) Veuillez également ajouter le WM_CLASS de Matlab.
Jacob Vlijm
2
Je posterai plus tard dans la journée, sinon quelqu'un publie une autre solution brillante en attendant.
Jacob Vlijm
1
Salut OHLÁLÁ, je l'ai fait fonctionner assez bien, toutes les fenêtres supplémentaires de l'application sont instantanément déplacées vers l'espace de travail initial de l'application, mais .... en effet, la fenêtre actuelle sur l'espace de travail actuel perd néanmoins son focus. Toujours en train de trouver une solution. Souhaitez-vous toujours essayer la solution?
Jacob Vlijm

Réponses:

8

MODIFICATION IMPORTANTE

Ci-dessous une version réécrite du script de la première réponse (ci-dessous). Les différences:

  • Le script est maintenant extrêmement faible en ressources (comme il devrait l'être avec les scripts d'arrière-plan). Les actions sont maintenant organisées pour agir si (et seulement si) elles sont nécessaires. La boucle ne fait pratiquement rien d'autre que de vérifier l'apparition de nouvelles fenêtres.
  • Bot WM_CLASSet l'espace de travail ciblé sont désormais des arguments pour exécuter le script. N'utilisez que la première ou la deuxième partie (identifiante) du WM_CLASS(voir ci-dessous: comment utiliser)
  • Le script reste désormais concentré sur la fenêtre actuellement active (se recentre en fait en une fraction de seconde)
  • Lorsque le script démarre, il affiche une notification (exemple gedit):

    entrez la description de l'image ici

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

  1. Le script a besoin des deux wmctrlet xdotool:

    sudo apt-get install wmctrl xdotool
    
  2. Copiez le script ci-dessus dans un fichier vide, enregistrez-le sous lock_towspace.py

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

  4. 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,2est 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

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

  1. Le script a besoin à la fois de wmctrletxdotool

    sudo apt-get install wmctrl xdotool
    
  2. Copiez le script dans un fichier vide, enregistrez-le sous keep_workspace.py

  3. 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é.

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

Jacob Vlijm
la source
C'est vraiment génial. Il peut être judicieux de créer un indicateur dans lequel l'utilisateur peut verrouiller différentes applications dans des espaces de travail. En ce moment, j'ai eu le problème avec Matlab, mais le même problème se produira avec matplotlib
OHLÁLÁ
@ OHLÁLÁ comme mentionné, je trouve la question très intéressante et je continuerai à y travailler. Ce que je pense, c'est un fichier dans lequel l'utilisateur peut définir applicationet définir des workspaceparamètres. Si vous rencontrez des bugs possibles, merci de le mentionner!
Jacob Vlijm
Quel sera le comportement lorsque deux Matlab seront démarrés sur des espaces de travail séparés?
OHLÁLÁ
@ OHLÁLÁ, ils seront tous les deux verrouillés dans l'espace de travail que vous avez défini dans la commande. Puisque leur WM_CLASSest identique, le second sera déplacé vers celui que vous avez défini dans la commande.
Jacob Vlijm
Existe-t-il d'autres possibilités d'identifier une application, autre que WM_CLASS?
OHLÁLÁ