¿Cómo puedo bloquear una aplicación (y todas sus ventanas nuevas) en un espacio de trabajo específico?

11

Ejecuto un Matlabscript en el workspace 1. Esto genera varias parcelas. Mientras tanto me cambio workspace 2y trabajo allí. Mi problema es que las tramas están apareciendo workspace 2. ¿Es posible bloquear el software en un espacio de trabajo? Entonces, mientras Matlabgenera las parcelas workspace 1, ¿puedo trabajar workspace 2sin la interrupción de las parcelas emergentes?

OH LA LA
fuente
¿Unidad, GNOME Shell o algo más?
AB
Agrego las etiquetas, es Ubuntu 14.04 con Unity
OHLÁLÁ
¿A qué clase pertenecen las ventanas de la trama? (¿podría verificar con el comando xprop WM_CLASSy luego hacer clic en la ventana?) Agregue también el WM_CLASS de Matlab.
Jacob Vlijm
2
Publicaré más tarde hoy, si no alguien publica otra solución brillante mientras tanto.
Jacob Vlijm
1
Hola OHLÁLÁ, en realidad lo hice funcionar bastante bien, todas las ventanas adicionales de la aplicación se mueven instantáneamente al espacio de trabajo inicial de la aplicación, pero ... de hecho, la ventana actual en el espacio de trabajo actual pierde el foco. Todavía estoy pensando en una solución. ¿Seguirías intentando la solución?
Jacob Vlijm

Respuestas:

8

EDICION IMPORTANTE

A continuación, una versión reescrita del guión de la primera respuesta (a continuación). Las diferencias:

  • El script ahora es extremadamente bajo en recursos (como debería ser con scripts de fondo). Las acciones ahora están organizadas para actuar si (y solo si) son necesarias. El bucle prácticamente no hace más que verificar que aparezcan nuevas ventanas.
  • Bot WM_CLASSy el espacio de trabajo objetivo ahora son argumentos para ejecutar el script. Solo use la primera o la segunda parte (identificación) de la WM_CLASS(vea más abajo: cómo usar)
  • La secuencia de comandos ahora mantiene el foco en la ventana activa actualmente (en realidad se reenfoca en una fracción de segundo)
  • Cuando se inicia el script, muestra una notificación (ejemplo gedit):

    ingrese la descripción de la imagen aquí

La secuencia de comandos

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

Cómo utilizar

  1. El script necesita ambos wmctrly xdotool:

    sudo apt-get install wmctrl xdotool
    
  2. Copie el script anterior en un archivo vacío, guárdelo como lock_towspace.py

  3. De su aplicación específica, descubra WM_CLASS: abra su aplicación, ejecute en una terminal:

    xprop WM_CLASS and click on the window of the application
    

    La salida se verá así (en su caso):

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

    Utilice la primera o la segunda parte del comando para ejecutar el script.

  4. El comando para ejecutar el script es:

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

    En el comando, la última sección; 2,2es el espacio de trabajo donde desea bloquear la aplicación (sin espacios: (!) columna, fila ), en formato "humano"; la primera columna / fila es1,1

  5. Pruebe el script ejecutándolo. Mientras se ejecuta, abra su aplicación y deje que produzca ventanas como de costumbre. Todas las ventanas deben aparecer en el espacio de trabajo de destino, como se establece en el comando.

RESPUESTA ACTUALIZADA:

(segundo) VERSIÓN DE PRUEBA

El siguiente script bloquea una aplicación específica en su espacio de trabajo inicial. Si se inicia el script, determina en qué espacio de trabajo reside la aplicación. Todas las ventanas adicionales que produce la aplicación se moverán al mismo espacio de trabajo en una fracción de segundo.

El problema de enfoque se resuelve reenfocando automáticamente la ventana que se enfocó antes de que se produjera la ventana adicional.

La secuencia de comandos

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

Cómo utilizar

  1. El script necesita ambos wmctrlyxdotool

    sudo apt-get install wmctrl xdotool
    
  2. Copie el script en un archivo vacío, guárdelo como keep_workspace.py

  3. determine el `WM_CLASS 'de su aplicación abriendo la aplicación, luego abra una terminal y ejecute el comando:

    xprop WM_CLASS
    

    Luego haga clic en la ventana de su aplicación. Copie el resultado, como "sun-awt-X11-XFramePeer", "MATLAB R2015a - academic use"en su caso, y colóquelo entre comillas simples en la sección principal del script, como se indica.

  4. Ejecute el script con el comando:

    python3 /path/to/keep_workspace.py
    

Si funciona como desea, agregaré una función de alternar. Aunque ya funciona durante algunas horas en mi sistema, sin embargo, podría necesitar algunos ajustes primero.

Notas

Aunque no se debe notar que, el guión hace añadir un poco de carga del procesador al sistema. En mi sistema de ancianos noté un aumento del 3-10%. Si te gusta cómo funciona, probablemente lo ajustaré aún más para reducir la carga.

El script, tal como está, supone que las ventanas secundarias son de la misma clase que la ventana principal, como indicó en un comentario. Sin embargo, con un cambio (muy) simple, las ventanas secundarias pueden ser de otra clase.

Explicación

Aunque probablemente no sea muy interesante para un lector promedio, el script funciona calculando en vectores. En el inicio, el script calcula:

  • el vector desde el origen hasta el espacio de trabajo actual con la salida de wmctrl -d
  • el vector a la ventana de la aplicación, relativo al espacio de trabajo actual, por la salida de wmctrl -lG
  • A partir de estos dos, el script calcula la posición absoluta de la ventana de la aplicación en el escritorio de expansión (todos los espacios de trabajo en una matriz)

A partir de ese momento, el script busca nuevas ventanas de la misma aplicación, con la salida de xprop WM_CLASS, busca su posición de la misma manera que antes y las mueve al espacio de trabajo "original".

Dado que la ventana recién creada "robó" el foco de la última ventana utilizada en la que estaba trabajando el usuario, el foco se establece posteriormente en la ventana que tenía foco anteriormente.

Jacob Vlijm
fuente
Esto es muy asombroso. Podría ser una buena idea crear un indicador donde el usuario pueda bloquear diferentes aplicaciones en espacios de trabajo. En este momento tuve el problema con Matlab, pero el mismo problema ocurrirá con matplotlib
OHLÁLÁ
@ OHLÁLÁ como se mencionó, la pregunta me parece muy interesante y seguiré trabajando en ello. Lo que tengo en mente es un archivo en el que el usuario puede establecer applicationy establecer workspace. Si se encuentra con posibles errores, ¡menciónelo!
Jacob Vlijm
¿Cuál será el comportamiento cuando dos Matlab se inicien en espacios de trabajo separados?
OHLÁLÁ
@ OHLÁLÁ, entonces ambos estarán bloqueados en el espacio de trabajo que establezca en el comando. Como WM_CLASSes idéntico, el segundo se moverá al que configuró en el comando.
Jacob Vlijm
¿Hay otras posibilidades para identificar una aplicación, además de WM_CLASS?
OHLÁLÁ