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

11

Eu corro um Matlabscript no workspace 1. Isso gera várias parcelas. Enquanto isso, mudo para workspace 2e trabalhando lá. Meu problema é que as parcelas estão surgindo workspace 2. É possível bloquear o software em um espaço de trabalho. Então, enquanto Matlabgera as parcelas workspace 1, posso trabalhar workspace 2sem a interrupção das parcelas que aparecem?

OHLÁLÁ
fonte
Unity, GNOME Shell ou algo mais?
AB
Eu adiciono as tags, é o Ubuntu 14.04 com Unity
OHLÁLÁ
A que classe pertencem as janelas da trama? (você pode verificar com o comando xprop WM_CLASSe clicar na janela?) Adicione também o WM_CLASS do Matlab.
Jacob Vlijm 27/08/2015
2
Vou postar hoje mais tarde, se não alguém postar outra solução brilhante nesse meio tempo.
Jacob Vlijm
1
Olá, OHLÁLÁ. Na verdade, eu o fiz funcionar muito bem, todas as janelas adicionais do aplicativo são instantaneamente movidas para o espaço de trabalho inicial do aplicativo, mas .... de fato, a janela atual no espaço de trabalho atual perde o foco. Ainda pensando em uma solução. Você ainda tentaria a solução?
Jacob Vlijm

Respostas:

8

EDIÇÃO IMPORTANTE

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

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

    insira a descrição da imagem aqui

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 de ambos wmctrle xdotool:

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

  3. De sua aplicação específica, descubra WM_CLASS: abra sua aplicação, execute em um terminal:

    xprop WM_CLASS and click on the window of the application
    

    A saída será semelhante (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 em que 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 o aplicativo e deixe-o produzir janelas como de costume. Todas as janelas devem aparecer no espaço de trabalho de destino, conforme definido no comando.

RESPOSTA ATUALIZADA:

(segundo) VERSÃO DE TESTE

O script abaixo bloqueia um aplicativo específico em seu espaço de trabalho inicial. Se o script for iniciado, ele determinará em qual espaço de trabalho o aplicativo reside. Todas as janelas adicionais produzidas pelo aplicativo serão movidas para o mesmo espaço de trabalho em uma fração de segundo.

O problema do foco é resolvido ao se focar novamente automaticamente na janela que foi focada antes da produção da janela adicional.

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 de wmctrlexdotool

    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, como "sun-awt-X11-XFramePeer", "MATLAB R2015a - academic use"no seu caso, e coloque-a entre aspas simples na seção principal do script, conforme indicado.

  4. Execute o script com o comando:

    python3 /path/to/keep_workspace.py
    

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

Notas

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

O script, como é, assume 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.

Explicação

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

  • o vetor da origem ao espaço de trabalho atual com a saída de wmctrl -d
  • o vetor para a janela do aplicativo, em relação ao espaço 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 (todos os espaços de trabalho em uma matriz)

A partir de então, o script procura 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 anteriormente.

Jacob Vlijm
fonte
Isso é muito impressionante. Pode ser uma boa ideia criar um indicador em que o usuário possa bloquear diferentes aplicativos nos espaços de trabalho. Agora eu tive o problema com o Matlab, mas o mesmo problema ocorrerá com matplotlib
OhLaLa
@ OHLÁLÁ, como mencionado, acho a pergunta muito interessante e continuarei trabalhando nela. O que tenho em mente é um arquivo no qual o usuário pode definir applicatione workspaceconfigurar. Se você encontrar possíveis erros, mencione!
Jacob Vlijm
Qual será o comportamento quando dois Matlab forem iniciados em áreas de trabalho separadas?
OHLÁLÁ 30/08/2015
@ OHLÁLÁ, então os dois serão bloqueados no espaço de trabalho que você definiu no comando. Como os WM_CLASSmesmos são idênticos, o segundo será movido para o que você definiu no comando.
Jacob Vlijm
Existem outras possibilidades para identificar um aplicativo, além de WM_CLASS?
OHLÁLÁ 30/08