Como posso bloquear um aplicativo (e todas as suas novas janelas) em um espaço de trabalho específico?

11

Eu corro um script Matlab no workspace 1 . Isso gera vários gráficos. Nesse meio tempo, mudo para workspace 2 e trabalho lá. Meu problema é que os gráficos estão aparecendo em workspace 2 . É possível bloquear o software em um espaço de trabalho. Então, enquanto Matlab gera os gráficos em workspace 1 , posso trabalhar em workspace 2 sem a interrupção dos gráficos pop-up?

    
por OHLÁLÁ 27.08.2015 / 11:09

1 resposta

8

EDIÇÃO IMPORTANTE

Abaixo de uma versão reescrita do script da primeira resposta (abaixo). As diferenças:

  • O script agora possui recursos extremamente baixos (como deveria ser com scripts de segundo plano). As ações estão agora organizadas para agir se (e somente se) elas forem necessárias. O loop não faz praticamente nada além de verificar se novas janelas aparecem.
  • Bot o WM_CLASS e o espaço de trabalho de destino agora são argumentos para executar o script. Use apenas a primeira ou a segunda parte (identificadora) do WM_CLASS (veja mais abaixo: como usar)
  • O script agora mantém o foco na janela ativa no momento (na verdade, se concentra em uma fração de segundo)
  • Quando o script é iniciado, ele mostra uma notificação (exemplo gedit ):

O script

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

Como usar

  1. O script precisa dos dois wmctrl e xdotool :

    sudo apt-get install wmctrl xdotool
    
  2. Copie o script acima em um arquivo vazio, salve-o como lock_towspace.py

  3. De seu aplicativo específico, descubra o WM_CLASS : abra seu aplicativo, execute em um terminal:

    xprop WM_CLASS and click on the window of the application
    

    A saída será parecida com (no seu caso):

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

    Use a primeira ou a segunda parte do comando para executar o script.

  4. O comando para executar o script é:

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

    No comando, a última seção; 2,2 é o espaço de trabalho onde você deseja bloquear o aplicativo (sem espaços: (!) coluna, linha ), no formato "humano"; a primeira coluna / linha é 1,1

  5. Teste o script executando-o. Durante a execução, abra seu aplicativo e deixe-o produzir janelas como de costume. Todas as janelas devem aparecer na área de trabalho de destino, conforme definido no comando.

RESPOSTA INDICADA:

(segunda) VERSÃO DE TESTE

O script abaixo bloqueia uma aplicação específica em sua área de trabalho inicial. Se o script for iniciado, ele determinará em qual espaço de trabalho o aplicativo reside. Todas as janelas adicionais que o aplicativo produz serão movidas para o mesmo espaço de trabalho em uma fração de segundo.

A questão do foco é solucionada ao se focalizar automaticamente na janela que foi focada antes que a janela adicional fosse produzida.

O script

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

Como usar

  1. O script precisa dos dois wmctrl e xdotool

    sudo apt-get install wmctrl xdotool
    
  2. Copie o script em um arquivo vazio, salve-o como keep_workspace.py

  3. determine o 'WM_CLASS' do seu aplicativo abrindo o aplicativo, abra um terminal e execute o comando:

    xprop WM_CLASS
    

    Em seguida, clique na janela do seu aplicativo. Copie a saída, parecendo "sun-awt-X11-XFramePeer", "MATLAB R2015a - academic use" no seu caso, e coloque entre as cotações simples na seção head do script, conforme indicado.

  4. Execute o script com o comando:

    python3 /path/to/keep_workspace.py
    

Se funcionar como quiser, adicionarei uma função de alternância. Embora já funcione por algumas horas no meu sistema, bu pode precisar de alguns ajustes antes.

Notas

Embora você não deva notar, o script faz adicionar alguma carga de processador ao sistema. No meu sistema de idosos, notei um aumento de 3-10%. Se você gosta de como funciona, provavelmente vou ajustá-lo para reduzir a carga.

O script, como é, presume que as janelas secundárias são da mesma classe que a janela principal, como você indicou em um comentário. Com uma alteração (muito) simples, as janelas secundárias podem ser de outra classe, no entanto.

Explicação

Embora provavelmente não seja muito interessante para um leitor médio, o script funciona calculando em vetores. Na inicialização, o script calcula:

  • o vetor da origem até a área de trabalho atual com a saída de wmctrl -d
  • o vetor para a janela do aplicativo, relativo à área de trabalho atual, pela saída de wmctrl -lG
  • A partir desses dois, o script calcula a posição absoluta da janela do aplicativo na área de trabalho de abrangência (todas as áreas de trabalho em uma matriz)

A partir de então, o script procura por novas janelas do mesmo aplicativo, com a saída de xprop WM_CLASS , procura sua posição da mesma maneira que acima e as move para a área de trabalho "original".

Como a janela recém-criada "roubou" o foco da última janela usada em que o usuário estava trabalhando, o foco é posteriormente definido para a janela que tinha o foco antes.

    
por Jacob Vlijm 28.08.2015 / 09:49