Wie kann ich eine Anwendung (und alle neuen Fenster) in einem bestimmten Arbeitsbereich sperren?

11

Ich führe ein MatlabSkript in der workspace 1. Dies erzeugt mehrere Diagramme. In der Zwischenzeit wechsle ich workspace 2dorthin und arbeite dort. Mein Problem ist, dass die Handlungen auftauchen workspace 2. Ist es möglich, Software in einem Arbeitsbereich zu sperren? Also, während ich Matlabdie Plots in generiere workspace 1, kann ich workspace 2ohne Unterbrechung der Popup-Plots arbeiten?

OHLÁLÁ
quelle
Einheit, GNOME Shell oder etwas anderes?
AB
Ich füge die Tags hinzu, es ist Ubuntu 14.04 mit Unity
OHLÁLÁ
Zu welcher Klasse gehören die Handlungsfenster? ( xprop WM_CLASSKönnen Sie dies mit dem Befehl überprüfen und dann auf das Fenster klicken?) Fügen Sie auch die WM_CLASS von Matlab hinzu.
Jacob Vlijm
2
Ich werde heute später posten, wenn nicht jemand in der Zwischenzeit eine andere brillante Lösung veröffentlicht.
Jacob Vlijm
1
Hallo OHLÁLÁ, ich habe es tatsächlich ganz gut zum Laufen gebracht, alle zusätzlichen Fenster der Anwendung werden sofort in den ursprünglichen Arbeitsbereich der App verschoben, aber ... tatsächlich verliert das aktuelle Fenster im aktuellen Arbeitsbereich trotzdem den Fokus. Ich denke immer noch an eine Lösung. Würden Sie die Lösung trotzdem ausprobieren?
Jacob Vlijm

Antworten:

8

WICHTIGE BEARBEITUNG

Unten eine umgeschriebene Version des Skripts aus der ersten Antwort (unten). Die Unterschiede:

  • Das Skript ist jetzt extrem ressourcenschonend (wie es bei Hintergrundskripten sein sollte). Die Aktionen sind jetzt so angeordnet, dass sie nur dann (und nur dann) ausgeführt werden, wenn sie benötigt werden. Die Schleife überprüft praktisch nur, ob neue Fenster angezeigt werden.
  • Bot the WM_CLASSund der Zielarbeitsbereich sind jetzt Argumente zum Ausführen des Skripts. Verwenden Sie nur entweder den ersten oder den zweiten (identifizierenden) Teil des WM_CLASS(siehe weiter unten: Verwendung)
  • Das Skript konzentriert sich jetzt auf das aktuell aktive Fenster (fokussiert in Sekundenbruchteilen)
  • Wenn das Skript gestartet wird, wird eine Benachrichtigung angezeigt (Beispiel gedit):

    Geben Sie hier die Bildbeschreibung ein

Das Skript

#!/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

Wie benutzt man

  1. Das Skript benötigt beides wmctrlund xdotool:

    sudo apt-get install wmctrl xdotool
    
  2. Kopieren Sie das obige Skript in eine leere Datei und speichern Sie es unter lock_towspace.py

  3. Finden Sie unter Ihrer spezifischen Anwendung Folgendes heraus WM_CLASS: Öffnen Sie Ihre Anwendung, führen Sie sie in einem Terminal aus:

    xprop WM_CLASS and click on the window of the application
    

    Die Ausgabe sieht wie folgt aus (in Ihrem Fall):

    WM_CLASS: WM_CLASS(STRING) = "sun-awt-X11-XFramePeer", "MATLAB R2015a - academic use"
    

    Verwenden Sie entweder den ersten oder den zweiten Teil des Befehls, um das Skript auszuführen.

  4. Der Befehl zum Ausführen des Skripts lautet dann:

    python3 /path/to/lock_towspace.py "sun-awt-X11-XFramePeer" 2,2
    

    Im Befehl der letzte Abschnitt; 2,2ist der Arbeitsbereich, in dem Sie die Anwendung (ohne Leerzeichen: (!) Spalte, Zeile ) im "menschlichen" Format sperren möchten ; Die erste Spalte / Zeile ist1,1

  5. Testen Sie das Skript, indem Sie es ausführen. Öffnen Sie während der Ausführung Ihre Anwendung und lassen Sie sie wie gewohnt Fenster erstellen. Alle Fenster sollten im Zielarbeitsbereich angezeigt werden, wie im Befehl festgelegt.

Veraltete Antwort:

(zweite) TESTVERSION

Das folgende Skript sperrt eine bestimmte Anwendung an ihren ursprünglichen Arbeitsbereich. Wenn das Skript gestartet wird, bestimmt es, auf welchem ​​Arbeitsbereich sich die Anwendung befindet. Alle zusätzlichen Fenster, die die Anwendung erstellt, werden in Sekundenbruchteilen in denselben Arbeitsbereich verschoben.

Das Fokusproblem wird gelöst, indem automatisch auf das Fenster fokussiert wird, das vor der Erstellung des zusätzlichen Fensters fokussiert wurde.

Das Skript

#!/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

Wie benutzt man

  1. Das Skript benötigt sowohl wmctrlundxdotool

    sudo apt-get install wmctrl xdotool
    
  2. Kopieren Sie das Skript in eine leere Datei und speichern Sie es unter keep_workspace.py

  3. Bestimmen Sie die WM_CLASS Ihrer Anwendung, indem Sie die Anwendung öffnen, dann ein Terminal öffnen und den folgenden Befehl ausführen:

    xprop WM_CLASS
    

    Klicken Sie dann auf das Fenster Ihrer Anwendung. Kopieren Sie die Ausgabe wie "sun-awt-X11-XFramePeer", "MATLAB R2015a - academic use"in Ihrem Fall und platzieren Sie sie wie angegeben zwischen einfachen Anführungszeichen im Kopfbereich des Skripts.

  4. Führen Sie das Skript mit dem folgenden Befehl aus:

    python3 /path/to/keep_workspace.py
    

Wenn es so funktioniert, wie Sie möchten, füge ich eine Umschaltfunktion hinzu. Obwohl es auf meinem System bereits einige Stunden funktioniert, muss es möglicherweise zuerst angepasst werden.

Anmerkungen

Auch wenn Sie es nicht bemerken sollte, wird das Skript tut etwas Prozessorlast zum System hinzuzufügen. Bei meinem älteren System bemerkte ich einen Anstieg von 3-10%. Wenn Ihnen die Funktionsweise gefällt, werde ich sie wahrscheinlich weiter optimieren, um die Last zu reduzieren.

Das Skript geht davon aus, dass die sekundären Fenster zur selben Klasse gehören wie das Hauptfenster, wie Sie in einem Kommentar angegeben haben. Mit einem (sehr) einfache Änderung, die sekundären Fenster können jedoch einer anderen Klasse sein.

Erläuterung

Obwohl das Skript für einen durchschnittlichen Leser wahrscheinlich nicht sehr interessant ist, berechnet es in Vektoren. Beim Start berechnet das Skript:

  • der Vektor vom Ursprung zum aktuellen Arbeitsbereich mit der Ausgabe von wmctrl -d
  • der Vektor zum Fenster der Anwendung, relativ zum aktuellen Arbeitsbereich, durch die Ausgabe von wmctrl -lG
  • Aus diesen beiden berechnet das Skript die absolute Position des Anwendungsfensters auf dem übergreifenden Desktop (alle Arbeitsbereiche in einer Matrix).

Von da an sucht das Skript nach neuen Fenstern derselben Anwendung mit der Ausgabe von xprop WM_CLASS, sucht ihre Position auf die gleiche Weise wie oben und verschiebt sie in den "ursprünglichen" Arbeitsbereich.

Da das neu erstellte Fenster den Fokus aus dem zuletzt verwendeten Fenster "gestohlen" hat, an dem der Benutzer gearbeitet hat, wird der Fokus anschließend auf das Fenster gesetzt, das zuvor den Fokus hatte.

Jacob Vlijm
quelle
Das ist sehr beeindruckend. Es kann eine gute Idee sein, einen Indikator zu erstellen, in dem der Benutzer verschiedene Anwendungen in Arbeitsbereichen sperren kann. Im Moment hatte ich das Problem mit Matlab, aber das gleiche Problem wird mit Matplotlib auftreten
OHLÁLÁ
@ OHLÁLÁ wie gesagt, ich finde die Frage sehr interessant und werde weiter daran arbeiten. Was ich vorhabe, ist eine Datei, in der der Benutzer setzen applicationund workspacesetzen kann. Wenn Sie auf mögliche Fehler stoßen, erwähnen Sie diese bitte!
Jacob Vlijm
Wie wird sich verhalten, wenn zwei Matlab in getrennten Arbeitsbereichen gestartet werden?
OHLÁLÁ
@ OHLÁLÁ dann werden beide an den Arbeitsbereich gebunden, den Sie im Befehl festgelegt haben. Da sie WM_CLASSidentisch sind, wird die zweite zu der im Befehl festgelegten verschoben.
Jacob Vlijm
Gibt es andere Möglichkeiten zur Identifizierung einer Anwendung als WM_CLASS?
OHLÁLÁ