Como agrupar janelas para serem levantadas como uma?

10

Eu tenho duas janelas, A e B. É possível, de alguma forma, vincular duas janelas, de forma que a mudança para A também aumenta B ou a mudança para B também aumenta A?

Entendo que o uso de vários espaços de trabalho é uma opção alternativa, mas queria saber se isso também é possível?

Simon Tong
fonte
z-ordem não é super importante, mas, se possível, que seria ótimo
Simon Tong
Eu acho que vários locais de trabalho são de longe a solução mais simples. Você conhece as combinações de teclas para alternar entre elas?
thomasrutter
1
Você é um aceitador rápido :) No entanto, agradeceria se você desse algum comentário sobre minha resposta.
Jacob Vlijm
5
Possível duplicata do 'agrupamento'

Respostas:

9

Introdução

O script a seguir permite selecionar duas janelas e, enquanto as duas estiverem abertas, ele aumentará as duas janelas quando o usuário focalizar uma delas. Por exemplo, se alguém vincula as viúvas A e B, passar para A ou B fará com que ambos se elevem acima das outras viúvas.

Para interromper o script, você pode usar killall link_windows.pyno terminal ou fechar e reabrir uma das janelas. Você também pode cancelar a execução pressionando o botão Fechar Xem qualquer uma das caixas de diálogo pop-up de seleção de janela.

Potenciais ajustes:

  • várias instâncias do script podem ser usadas para agrupar pares de duas janelas. Por exemplo, se tivermos as janelas A, B, C e D, podemos vincular A e B juntos e C e D juntos.
  • várias janelas podem ser agrupadas em uma única janela. Por exemplo, se eu vincular a janela B a A, C a A e D a A, isso significa que, se eu sempre alternar para A, posso levantar todas as 4 janelas ao mesmo tempo.

Uso

Execute o script como:

python link_windows.py

O script é compatível com o Python 3, portanto, também pode ser executado como

python3 link_windows.py

Existem duas opções de linha de comando:

  • --quietou -q, permite silenciar as janelas da GUI. Com esta opção, você pode simplesmente clicar com o mouse em duas janelas, e o script começará a vinculá-las.
  • --helpou -h, imprime as informações de uso e descrição.

A -hopção produz as seguintes informações:

$ python3 link_windows.py  -h                                                                                            
usage: link_windows.py [-h] [--quiet]

Linker for two X11 windows.Allows raising two user selected windows together

optional arguments:
  -h, --help  show this help message and exit
  -q, --quiet  Blocks GUI dialogs.

Informações técnicas adicionais podem ser visualizadas via pydoc ./link_windows.py, onde ./significa que você deve estar no mesmo diretório que o script.

Processo de uso simples para duas janelas:

  1. Um pop-up aparecerá pedindo para você selecionar uma janela nº 1, pressionar OKou pressionar Enter. O ponteiro do mouse se transformará em uma cruz. Clique em uma das janelas que você deseja vincular.

  2. Um segundo pop-up será exibido, solicitando que você selecione a janela nº 2, pressione OKou aperte Enter. Novamente, o ponteiro do mouse se transformará em uma cruz. Clique na outra janela que você deseja vincular. Depois que a execução começará.

  3. Sempre que você focalizar uma das janelas, o script aumentará a outra janela, mas retornará o foco para a selecionada originalmente (observe - com um atraso de um quarto de segundo para obter o melhor desempenho), criando a sensação de que as janelas estão ligadas.

Se você selecionar a mesma janela as duas vezes, o script será encerrado. Se a qualquer momento você clicar no botão Fechar da caixa de diálogo pop-up, o script será encerrado.

Origem do script

Também disponível como GitHub Gist

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Author: Sergiy Kolodyazhnyy
Date:  August 2nd, 2016
Written for: /ubuntu//q/805515/295286
Tested on Ubuntu 16.04 LTS
"""
import gi
gi.require_version('Gdk', '3.0')
gi.require_version('Gtk', '3.0')
from gi.repository import Gdk, Gtk
import time
import subprocess
import sys
import argparse


def run_cmd(cmdlist):
    """ Reusable function for running shell commands"""
    try:
        stdout = subprocess.check_output(cmdlist)
    except subprocess.CalledProcessError:
        sys.exit(1)
    else:
        if stdout:
            return stdout


def focus_windows_in_order(first, second, scr):
    """Raise two user-defined windows above others.
       Takes two XID integers and screen object.
       Window with first XID will have the focus"""

    first_obj = None
    second_obj = None

    for window in scr.get_window_stack():
        if window.get_xid() == first:
            first_obj = window
        if window.get_xid() == second:
            second_obj = window

    # When this  function is called first_obj is alread
    # raised. Therefore we must raise second one, and switch
    # back to first
    second_obj.focus(int(time.time()))
    second_obj.get_update_area()
    # time.sleep(0.25)
    first_obj.focus(int(time.time()))
    first_obj.get_update_area()


def get_user_window():
    """Select two windows via mouse. Returns integer value of window's id"""
    window_id = None
    while not window_id:
        for line in run_cmd(['xwininfo', '-int']).decode().split('\n'):
            if 'Window id:' in line:
                window_id = line.split()[3]
    return int(window_id)


def main():
    """ Main function. This is where polling for window stack is done"""

    # Parse command line arguments
    arg_parser = argparse.ArgumentParser(
        description="""Linker for two X11 windows.Allows raising """ +
                    """two user selected windows together""")
    arg_parser.add_argument(
                '-q','--quiet', action='store_true',
                help='Blocks GUI dialogs.',
                required=False)
    args = arg_parser.parse_args()

    # Obtain list of two user windows
    user_windows = [None, None]
    if not args.quiet:
        run_cmd(['zenity', '--info', '--text="select first window"'])
    user_windows[0] = get_user_window()
    if not args.quiet:
        run_cmd(['zenity', '--info', '--text="select second window"'])
    user_windows[1] = get_user_window()

    if user_windows[0] == user_windows[1]:
        run_cmd(
            ['zenity', '--error', '--text="Same window selected. Exiting"'])
        sys.exit(1)

    screen = Gdk.Screen.get_default()
    flag = False

    # begin watching for changes in window stack
    while True:

        window_stack = [window.get_xid()
                        for window in screen.get_window_stack()]

        if user_windows[0] in window_stack and user_windows[1] in window_stack:

            active_xid = screen.get_active_window().get_xid()
            if active_xid not in user_windows:
                flag = True

            if flag and active_xid == user_windows[0]:
                focus_windows_in_order(
                    user_windows[0], user_windows[1], screen)
                flag = False

            elif flag and active_xid == user_windows[1]:
                focus_windows_in_order(
                    user_windows[1], user_windows[0], screen)
                flag = False

        else:
            break

        time.sleep(0.15)


if __name__ == "__main__":
    main()

Notas:

  • Ao executar a partir da linha de comando, as caixas de diálogo pop-up produzem a seguinte mensagem: Gtk-Message: GtkDialog mapped without a transient parent. This is discouraged.Elas podem ser ignoradas.
  • Consulte Como editar / criar novos itens do iniciador no Unity manualmente? para criar um iniciador ou atalho na área de trabalho para esse script, se você deseja iniciá-lo com um clique duplo
  • Para vincular esse script a um atalho de teclado para facilitar o acesso, consulte Como adicionar atalhos de teclado?
Sergiy Kolodyazhnyy
fonte
Saúde, estou realmente impressionado. O time.sleepintervalo entre a troca, eu sou capaz de colocar isso em zero? existe uma razão para o atraso?
Simon Tong
1
@ simontong você pode tentar comentar essa linha como # time.sleep(0.25)e ela não será executada. A razão para isso é garantir que cada janela seja levantada corretamente. Na minha experiência no passado, eu precisava ter atrasos para operar no Windows. Eu acho que o atraso de um quarto de segundo não é tanto assim. Na verdade, deixe-me adicionar apenas mais uma linha ao script, que pode acelerar. OK ?
Sergiy Kolodyazhnyy 02/08
@ simontong OK, eu atualizei o script. Tente agora. Deveria ter uma mudança mais rápida #
Sergiy Kolodyazhnyy 02/08
@simontong Vou atualizar o script com algumas pequenas adições para adicionar alguns recursos extras. Eu vou deixar você saber quando ele estiver pronto, por favor deixe-me saber o que você pensa
Sergiy Kolodyazhnyy
@simontong adicionou opções extras ao script, por favor, revise #
Sergiy Kolodyazhnyy
6

Aumente um número arbitrário de janelas como uma

A solução abaixo permitirá que você escolha qualquer combinação de duas, três ou mais janelas a serem combinadas e elevadas como uma só com um atalho de teclado.

O script faz seu trabalho com três argumentos:

add

para adicionar a janela ativa ao grupo

raise

aumentar o grupo definido

clear

para limpar o grupo, pronto para definir um novo grupo

O script

#!/usr/bin/env python3
import sys
import os
import subprocess

wlist = os.path.join(os.environ["HOME"], ".windowlist")

arg = sys.argv[1]

if arg == "add":
    active = subprocess.check_output([
        "xdotool", "getactivewindow"
        ]).decode("utf-8").strip()
    try:
        currlist = open(wlist).read()
    except FileNotFoundError:
        currlist = []
    if not active in currlist:
        open(wlist, "a").write(active + "\n")
elif arg == "raise":
    group = [w.strip() for w in open(wlist).readlines()]
    [subprocess.call(["wmctrl", "-ia", w]) for w in group]
elif arg == "clear":
    os.remove(wlist)

Como usar

  1. O script precisa wmctrle xdotool:

    sudo apt-get install wmctrl xdotool
  2. Copie o script acima em um arquivo vazio, salve-o como groupwindows.py
  3. Teste - execute o script: abra duas janelas de terminal, execute o comando:

    python3 /absolute/path/to/groupwindows.py add

    em ambos. Cubra-os com outras janelas (ou minimize-os). Abra uma terceira janela do terminal, execute o comando:

    python3 /absolute/path/to/groupwindows.py raise

    As duas primeiras janelas serão levantadas como uma.

  4. Se tudo funcionar bem, crie três teclas de atalho personalizadas: Escolha: Configurações do sistema> "Teclado"> "Atalhos"> "Atalhos personalizados". Clique no "+" e adicione os comandos abaixo a três atalhos separados:

    no meu sistema, eu usei:

    Alt+ A, executando o comando:

    python3 /absolute/path/to/groupwindows.py add

    ... para adicionar uma janela ao grupo.

    Alt+ R, executando o comando:

    python3 /absolute/path/to/groupwindows.py raise

    ... para criar o grupo.

    Alt+ C, executando o comando:

    python3 /absolute/path/to/groupwindows.py clear

    ... para limpar o grupo

Explicação

O script funciona de maneira bem simples:

  • Quando executado com o argumento add, o script armazena / adiciona o ID da janela da janela ativa em um arquivo oculto~/.windowlist
  • Quando executado com o argumento raise, o script lê o arquivo, eleva as janelas da lista com o comando:

    wmctrl -ia <window_id>
  • Quando executado com o argumento clear, o script remove o arquivo oculto ~/.windowlist.

Notas

  • O script também funcionará em janelas minimizadas, minimizando as janelas possivelmente minimizadas
  • Se o conjunto de janelas estiver em outra viewport, o script mudará para a viewport correspondente
  • O conjunto é flexível, você sempre pode adicionar outras janelas ao conjunto atual.

Mais flexibilidade?

Como mencionado, o script acima permite adicionar janelas a qualquer momento às janelas agrupadas. A versão abaixo também permite remover qualquer uma das janelas (a qualquer momento) da lista agrupada:

#!/usr/bin/env python3
import sys
import os
import subprocess

wlist = os.path.join(os.environ["HOME"], ".windowlist")
arg = sys.argv[1]
# add windows to the group
if arg == "add":
    active = subprocess.check_output([
        "xdotool", "getactivewindow"
        ]).decode("utf-8").strip()
    try:
        currlist = open(wlist).read()
    except FileNotFoundError:
        currlist = []
    if not active in currlist:
        open(wlist, "a").write(active + "\n")
# delete window from the group
if arg == "delete":
    try:
        currlist = [w.strip() for w in open(wlist).readlines()]
    except FileNotFoundError:
        pass
    else:
        currlist.remove(subprocess.check_output([
            "xdotool", "getactivewindow"]).decode("utf-8").strip())      
        open(wlist, "w").write("\n".join(currlist)+"\n")
# raise the grouped windows
elif arg == "raise":
    group = [w.strip() for w in open(wlist).readlines()]
    [subprocess.call(["wmctrl", "-ia", w]) for w in group]
# clear the grouped window list
elif arg == "clear":
    os.remove(wlist)

O argumento adicional para executar o script é delete:

python3 /absolute/path/to/groupwindows.py delete

exclui a janela ativa das janelas agrupadas. Para executar este comando, no meu sistema, defino Alt+ Dcomo um atalho.

Jacob Vlijm
fonte