Come posso bloccare un'applicazione (e tutte le sue nuove finestre) in uno spazio di lavoro specifico?


11

Eseguo una Matlabsceneggiatura in workspace 1. Questo genera diversi grafici. Nel frattempo passo a workspace 2lavorare lì. Il mio problema è che le trame stanno spuntando workspace 2. È possibile bloccare il software in un'area di lavoro. Quindi, mentre Matlabgenera i grafici workspace 1, posso lavorare workspace 2senza l'interruzione dei grafici a comparsa?


Unity, GNOME Shell o qualcos'altro?
AB

Aggiungo i tag, è Ubuntu 14.04 con Unity
OHLÁLÁ,

A quale classe appartengono le finestre della trama? (potresti controllare con il comando xprop WM_CLASS, quindi fare clic sulla finestra?) Aggiungi anche WM_CLASS di Matlab.
Jacob Vlijm,

2
Oggi pubblicherò più tardi, se nel frattempo qualcuno non pubblica un'altra soluzione geniale.
Jacob Vlijm,

1
Ciao OHLÁLÁ, in realtà l'ho fatto funzionare abbastanza bene, tutte le finestre aggiuntive dell'applicazione vengono immediatamente spostate nell'area di lavoro iniziale dell'app, ma ... in effetti la finestra corrente sull'area di lavoro corrente perde comunque attenzione. Sto ancora cercando una soluzione. Proveresti ancora la soluzione?
Jacob Vlijm,

Risposte:


8

MODIFICA IMPORTANTE

Di seguito una versione riscritta dello script dalla prima risposta (sotto). Le differenze:

  • Lo script ora ha pochissime risorse (come dovrebbe essere con gli script in background). Le azioni sono ora organizzate per agire se (e solo se) sono necessarie. Il ciclo praticamente non fa altro che verificare la comparsa di nuove finestre.
  • Bot the WM_CLASSe l'area di lavoro di destinazione sono ora argomenti per eseguire lo script. Utilizzare solo la prima o la seconda parte (identificativa) di WM_CLASS(vedere più avanti: come usare)
  • Lo script ora si concentra sulla finestra attualmente attiva (in realtà si concentra nuovamente in una frazione di secondo)
  • All'avvio dello script, mostra una notifica (esempio gedit):

    inserisci qui la descrizione dell'immagine

Il copione

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

Come usare

  1. Lo script ha bisogno di entrambi wmctrle xdotool:

    sudo apt-get install wmctrl xdotool
    
  2. Copia lo script sopra in un file vuoto, salvalo come lock_towspace.py

  3. Della tua applicazione specifica, scopri WM_CLASS: apri l'applicazione, esegui in un terminale:

    xprop WM_CLASS and click on the window of the application
    

    L'output sarà simile (nel tuo caso):

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

    Utilizzare la prima o la seconda parte nel comando per eseguire lo script.

  4. Il comando per eseguire lo script è quindi:

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

    Nel comando, l'ultima sezione; 2,2è l'area di lavoro in cui si desidera bloccare l'applicazione (senza spazi: colonna (!) , riga ), in formato "umano"; la prima colonna / riga è1,1

  5. Prova lo script eseguendolo. Durante l'esecuzione, apri l'applicazione e lascia che produca Windows come al solito. Tutte le finestre dovrebbero apparire nell'area di lavoro di destinazione, come impostato nel comando.

RISPOSTA AGGIORNATA:

(secondo) VERSIONE DI PROVA

Lo script seguente blocca un'applicazione specifica nel suo spazio di lavoro iniziale. Se lo script viene avviato, determina su quale area di lavoro risiede l'applicazione. Tutte le finestre aggiuntive prodotte dall'applicazione verranno spostate nello stesso spazio di lavoro in una frazione di secondo.

Il problema di messa a fuoco viene risolto focalizzandosi automaticamente sulla finestra focalizzata prima della produzione della finestra aggiuntiva.

Il copione

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

Come usare

  1. La sceneggiatura ha bisogno di entrambi wmctrlexdotool

    sudo apt-get install wmctrl xdotool
    
  2. Copia lo script in un file vuoto, salvalo come keep_workspace.py

  3. determinare il `WM_CLASS 'dell'applicazione aprendo l'applicazione, quindi aprire un terminale ed eseguire il comando:

    xprop WM_CLASS
    

    Quindi fare clic sulla finestra dell'applicazione. Copia l'output, come "sun-awt-X11-XFramePeer", "MATLAB R2015a - academic use"nel tuo caso, e posizionalo tra virgolette singole nella sezione head dello script, come indicato.

  4. Esegui lo script con il comando:

    python3 /path/to/keep_workspace.py
    

Se funziona come preferisci, aggiungerò una funzione di attivazione / disattivazione. Sebbene funzioni già da alcune ore sul mio sistema, potrebbe essere necessario prima apportare alcune modifiche.

Appunti

Anche se non si dovrebbe notare che, lo script non aggiungere un po 'carico del processore al sistema. Sul mio sistema anziano ho notato un aumento del 3-10%. Se ti piace come funziona, probabilmente lo modificherò ulteriormente per ridurre il carico.

Lo script, così com'è, presuppone che le finestre secondarie siano della stessa classe della finestra principale, come indicato in un commento. Con una (molto) semplice modifica, le finestre secondarie possono essere di un'altra classe.

Spiegazione

Sebbene probabilmente non sia molto interessante per un lettore medio, la sceneggiatura funziona calcolando in vettori. All'avvio, lo script calcola:

  • il vettore dall'origine allo spazio di lavoro corrente con l'output di wmctrl -d
  • il vettore alla finestra dell'applicazione, rispetto allo spazio di lavoro corrente, dall'output di wmctrl -lG
  • Da questi due, lo script calcola la posizione assoluta della finestra dell'applicazione sul desktop di spanning (tutte le aree di lavoro in una matrice)

Da quel momento in poi, lo script cerca nuove finestre della stessa applicazione, con l'output di xprop WM_CLASS, cerca la loro posizione come sopra e le sposta nell'area di lavoro "originale".

Poiché la finestra appena creata "rubava" lo stato attivo dall'ultima finestra utilizzata su cui l'utente stava lavorando, lo stato attivo viene successivamente impostato sulla finestra che aveva lo stato attivo in precedenza.


Questo è molto bello. Potrebbe essere una buona idea creare un indicatore in cui l'utente può bloccare diverse applicazioni in aree di lavoro. In questo momento ho avuto il problema con Matlab, ma lo stesso problema si verificherà con matplotlib
OHLÁLÁ

@ OHLÁLÁ come detto, trovo la domanda molto interessante e continuerò a lavorarci su. Quello che ho in mente è un file in cui l'utente può impostare applicatione workspaceimpostare. Se incontri possibili bug, per favore, menzionalo!
Jacob Vlijm,

Quale sarà il comportamento quando due Matlab vengono avviati in aree di lavoro separate?
OHLÁLÁ,

@ OHLÁLÁ, entrambi verranno bloccati nell'area di lavoro impostata nel comando. Poiché il loro WM_CLASSè identico, il secondo verrà spostato su quello impostato nel comando.
Jacob Vlijm,

Esistono altre possibilità per identificare un'applicazione, oltre a WM_CLASS?
OHLÁLÁ,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.