web-dev-qa-db-fra.com

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

Je lance un script Matlab dans le workspace 1. Cela génère plusieurs parcelles. En attendant, je passe à workspace 2 et y travaille. Mon problème est que les parcelles apparaissent dans workspace 2. Est-il possible de verrouiller un logiciel dans un espace de travail? Ainsi, alors que Matlab génère les tracés dans workspace 1, je peux travailler dans workspace 2 sans perturber les tracés qui s'affichent?

11
OHLÁLÁ

IMPORTANT EDIT

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 pauvre en ressources (comme cela devrait être le cas 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 que vérifier que de nouvelles fenêtres apparaissent.
  • Bot WM_CLASS et l'espace de travail ciblé sont désormais des arguments permettant d'exécuter le script. N'utilisez que la première ou la deuxième partie (identifiant) du WM_CLASS (voir plus bas: comment utiliser)
  • Le script garde maintenant le focus sur la fenêtre actuellement active (se re-concentre en une fraction de seconde)
  • Lorsque le script démarre, il affiche une notification (exemple gedit):

    enter image description here

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 nécessite à la fois wmctrl et xdotool:

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

  3. Dans votre application spécifique, trouvez le WM_CLASS: ouvrez votre application, exécutez-le 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,2 est l'espace de travail sur lequel vous souhaitez verrouiller l'application (sans espaces: (!) , colonne ), en "humain" format; la première colonne/ligne est 1,1

  5. Testez le script en l'exécutant. En cours d’exécution, ouvrez votre application et laissez-la produire les fenêtres comme d’habitude. Toutes les fenêtres doivent apparaître sur l'espace de travail cible, comme défini dans la commande.

Réponse périmée:

(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 réside l'application. Toutes les fenêtres supplémentaires générées par l'application seront déplacées vers le même espace de travail en une fraction de seconde.

Le problème de focalisation est résolu en recentrant automatiquement sur la fenêtre qui était focalisée 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 nécessite à la fois wmctrlet xdotool

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

  3. déterminez le `WM_CLASS 'de votre application en l'ouvrant, puis ouvrez un terminal et exécutez la commande suivante:

    xprop WM_CLASS
    

    Cliquez ensuite sur la fenêtre de votre application. Copiez la sortie sous la forme "Sun-awt-X11-XFramePeer", "MATLAB R2015a - academic use" dans votre cas et placez-la entre guillemets simples dans la section d'en-tête 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 de basculement. Bien que cela fonctionne déjà depuis quelques heures sur mon système, il pourrait toutefois nécessiter quelques ajustements d’abord.

Remarques

Bien que vous ne deviez pas le remarquer, le script ajoute une charge de processeur au système. Sur mon système de personnes âgées, j'ai constaté une augmentation de 3 à 10%. Si vous aimez la façon dont cela fonctionne, je vais probablement l'améliorer davantage pour réduire la charge.

Le script, dans son état actuel, suppose que les fenêtres secondaires appartiennent à 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 appartenir à une autre classe.

Explication

Bien que probablement peu 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 dans la fenêtre de l'application, par rapport à l'espace de travail actuel, par la sortie de wmctrl -lG
  • À partir de ces deux éléments, le script calcule la position absolue de la fenêtre de l'application sur le bureau recouvrant (tous les espaces de travail d'une matrice).

À partir de ce moment, le script recherche les 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 par l'utilisateur, le focus est par la suite défini sur la fenêtre précédemment active.

8
Jacob Vlijm