Jak mogę zablokować aplikację (i wszystkie jej nowe okna) w określonym obszarze roboczym?

11

Uruchamiam Matlabskrypt w workspace 1. To generuje kilka wykresów. W międzyczasie przełączam się workspace 2i tam pracuję. Mój problem polega na tym, że pojawiają się wątki workspace 2. Czy można zablokować oprogramowanie w obszarze roboczym. Więc chociaż Matlabgeneruje wykresy workspace 1, mogę pracować workspace 2bez zakłóceń pojawiających się wykresów?

O LA LA
źródło
Jedność, GNOME Shell czy coś innego?
AB
Dodam tagi, to Ubuntu 14.04 z Unity
OHLÁLÁ
Do jakiej klasy należą okna działki? (czy możesz sprawdzić za pomocą polecenia xprop WM_CLASS, a następnie kliknąć okno?) Proszę również dodać WM_CLASS Matlaba.
Jacob Vlijm
2
Wyślę później dzisiaj, jeśli w międzyczasie ktoś nie opublikuje innego genialnego rozwiązania.
Jacob Vlijm
1
Cześć OHLÁLÁ, Właściwie to działa całkiem dobrze, wszystkie dodatkowe okna aplikacji są natychmiast przenoszone do początkowego obszaru roboczego aplikacji, ale .... w rzeczywistości obecne okno w bieżącym obszarze roboczym traci jednak ostrość. Nadal zastanawiam się nad rozwiązaniem. Czy nadal spróbowałbyś rozwiązania?
Jacob Vlijm

Odpowiedzi:

8

WAŻNA EDYCJA

Poniżej przepisana wersja skryptu z pierwszej odpowiedzi (poniżej). Różnice:

  • Skrypt ma teraz bardzo mało zasobów (tak jak powinien być ze skryptami w tle). Akcje są teraz ustawione tak, aby działały, jeśli (i tylko wtedy), gdy są potrzebne. Pętla praktycznie nie robi nic poza sprawdzaniem, czy pojawiają się nowe okna.
  • Bot WM_CLASSi docelowy obszar roboczy są teraz argumentami do uruchomienia skryptu. Używaj tylko pierwszej lub drugiej (identyfikującej) części WM_CLASS(patrz poniżej: jak używać)
  • Skrypt skupia się teraz na aktualnie aktywnym oknie (w rzeczywistości uaktywnia się ponownie w ułamku sekundy)
  • Po uruchomieniu skryptu wyświetla powiadomienie (przykład gedit):

    wprowadź opis zdjęcia tutaj

Scenariusz

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

Jak używać

  1. Skrypt potrzebuje zarówno wmctrla xdotool:

    sudo apt-get install wmctrl xdotool
    
  2. Skopiuj powyższy skrypt do pustego pliku i zapisz go jako lock_towspace.py

  3. O konkretnej aplikacji dowiedz się WM_CLASS: otwórz aplikację, uruchom w terminalu:

    xprop WM_CLASS and click on the window of the application
    

    Wynik będzie wyglądał następująco (w twoim przypadku):

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

    Użyj pierwszej lub drugiej części polecenia, aby uruchomić skrypt.

  4. Polecenie uruchomienia skryptu to:

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

    W poleceniu ostatnia sekcja; 2,2to obszar roboczy, w którym chcesz zablokować aplikację (bez spacji: (!) kolumna, wiersz ), w formacie „ludzkim”; pierwsza kolumna / wiersz to1,1

  5. Przetestuj skrypt, uruchamiając go. Podczas pracy otwórz aplikację i pozwól, aby generowała okna jak zwykle. Wszystkie okna powinny pojawić się w docelowym obszarze roboczym, zgodnie z ustawieniem w poleceniu.

NIEAKTUALIZOWANA ODPOWIEDŹ:

(drugi) WERSJA TESTOWA

Poniższy skrypt blokuje określoną aplikację w jej początkowym obszarze roboczym. Jeśli skrypt zostanie uruchomiony, określa, w którym obszarze roboczym znajduje się aplikacja. Wszystkie dodatkowe okna tworzone przez aplikację zostaną przeniesione do tego samego obszaru roboczego w ułamku sekundy.

Problem fokusu jest rozwiązany przez automatyczne ponowne ustawienie ostrości na oknie, które było fokusowane przed utworzeniem dodatkowego okna.

Scenariusz

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

Jak używać

  1. Skrypt potrzebuje zarówno, jak wmctrlixdotool

    sudo apt-get install wmctrl xdotool
    
  2. Skopiuj skrypt do pustego pliku i zapisz go jako keep_workspace.py

  3. określ `WM_CLASS 'swojej aplikacji, otwierając aplikację, a następnie otwórz terminal i uruchom polecenie:

    xprop WM_CLASS
    

    Następnie kliknij okno aplikacji. Skopiuj wynik, wyglądając jak "sun-awt-X11-XFramePeer", "MATLAB R2015a - academic use"w twoim przypadku, i umieść go między pojedynczymi cudzysłowami w sekcji head skryptu, jak wskazano.

  4. Uruchom skrypt za pomocą polecenia:

    python3 /path/to/keep_workspace.py
    

Jeśli działa tak, jak chcesz, dodam funkcję przełączania. Chociaż działa w moim systemie już od kilku godzin, jednak najpierw może wymagać drobnych poprawek.

Notatki

Chociaż nie należy zauważyć, skrypt robi dodać trochę obciążenia procesora w systemie. W moim starszym systemie zauważyłem wzrost o 3-10%. Jeśli podoba ci się, jak to działa, prawdopodobnie poprawię go jeszcze bardziej, aby zmniejszyć obciążenie.

Skrypt, jak to jest, zakłada, że ​​dodatkowe okna są tej samej klasy co główne okno, jak wskazano w komentarzu. Z (bardzo) prostą zmianą okna pomocnicze mogą jednak należeć do innej klasy.

Wyjaśnienie

Chociaż prawdopodobnie nie jest zbyt interesujący dla przeciętnego czytelnika, skrypt działa poprzez obliczanie wektorów. Podczas uruchamiania skrypt oblicza:

  • wektor od początku do bieżącego obszaru roboczego z wynikiem wmctrl -d
  • wektor do okna aplikacji, względem bieżącego obszaru roboczego, według danych wyjściowych wmctrl -lG
  • Na podstawie tych dwóch skrypt oblicza bezwzględną pozycję okna aplikacji na łączącym pulpicie (wszystkie obszary robocze w jednej macierzy)

Od tego momentu skrypt szuka nowych okien tej samej aplikacji, z wyjściem xprop WM_CLASS, sprawdza ich pozycję w taki sam sposób jak powyżej i przenosi je do „oryginalnego” obszaru roboczego.

Ponieważ nowo utworzone okno „ukradło” fokus z ostatnio używanego okna, nad którym pracował użytkownik, fokus jest następnie ustawiany na okno, które miało fokus wcześniej.

Jacob Vlijm
źródło
To jest bardzo niesamowite. Dobrym pomysłem może być utworzenie wskaźnika, w którym użytkownik może zablokować różne aplikacje w obszarach roboczych. W tej chwili miałem problem z Matlabem, ale ten sam problem wystąpi z matplotlib
OHLÁLÁ
@ OHLÁLÁ, jak wspomniano, uważam to pytanie za bardzo interesujące i będę dalej nad nim pracował. Mam na myśli plik, w którym użytkownik może ustawić applicationi workspaceustawić. Jeśli napotkasz możliwe błędy, proszę o tym wspomnieć!
Jacob Vlijm
Jakie będzie zachowanie, gdy dwa Matlab zostaną uruchomione w oddzielnych obszarach roboczych?
OHLÁLÁ,
@ OHLÁLÁ, wówczas oba zostaną zablokowane w obszarze roboczym ustawionym w poleceniu. Ponieważ WM_CLASSsą identyczne, drugi zostanie przeniesiony do tego, który ustawiłeś w poleceniu.
Jacob Vlijm,
Czy istnieją inne możliwości identyfikacji aplikacji, inne niż WM_CLASS?
OHLÁLÁ,