¿Cómo puedo agrupar ventanas para que se levanten como una?

10

Tengo dos ventanas, A y B. ¿Es posible vincular de alguna manera dos ventanas, de modo que cambiar a A también genere B o cambiar a B también genere A?

Entiendo que usar múltiples espacios de trabajo es una opción alternativa, pero me preguntaba si esto también es posible.

Simon Tong
fuente
El orden z no es súper importante, pero si es posible, sería genial
Simon Tong
Creo que múltiples lugares de trabajo es, con mucho, la solución más simple. ¿Conoces las combinaciones de teclas para cambiar entre ellas?
thomasrutter
1
Eres un aceptador rápido :) Sin embargo, agradecería si hicieras algún comentario sobre mi respuesta.
Jacob Vlijm
55
Posible duplicado de la ventana 'agrupación'?

Respuestas:

9

Introducción

El siguiente script permite seleccionar dos ventanas, y aunque ambas ventanas están abiertas, las abrirá cuando el usuario enfoque cualquiera de ellas. Por ejemplo, si uno vincula a las viudas A y B, las brujas con A o B harán que ambas se eleven por encima de otras viudas.

Para detener el script, puede usarlo killall link_windows.pyen la terminal, o cerrar y volver a abrir una de las ventanas. También puede cancelar la ejecución presionando el botón Cerrar Xen cualquiera de los cuadros de diálogo emergentes de selección de ventana.

Ajustes potenciales:

  • Se pueden usar varias instancias del script para agrupar pares de dos ventanas. Por ejemplo, si tenemos ventanas A, B, C y D, podemos vincular A y B, y vincular C y D.
  • Se pueden agrupar varias ventanas en una sola ventana. Por ejemplo, si enlace la ventana B a A, C a A y D a A, eso significa que si siempre cambio a A, puedo subir las 4 ventanas al mismo tiempo.

Uso

Ejecute el script como:

python link_windows.py

El script es compatible con Python 3, por lo que también puede ejecutarse como

python3 link_windows.py

Hay dos opciones de línea de comando:

  • --quieto -q, permite silenciar las ventanas de la GUI. Con esta opción, puede hacer clic con el mouse en cualquiera de las dos ventanas y el script comenzará a vincularlas.
  • --helpo -himprime la información de uso y descripción.

La -hopción produce la siguiente información:

$ 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.

Se puede ver información técnica adicional a través de pydoc ./link_windows.py, donde ./significa que debe estar en el mismo directorio que el script.

Proceso de uso simple para dos ventanas:

  1. Aparecerá una ventana emergente pidiéndole que seleccione una ventana # 1, presione OKo presione Enter. El puntero del mouse se convertirá en una cruz. Haga clic en una de las ventanas que desea vincular.

  2. Aparecerá una segunda ventana emergente pidiéndole que seleccione la ventana # 2, presione OKo presione Enter. Nuevamente, el puntero del mouse se convertirá en una cruz. Haga clic en la otra ventana que desea vincular. Después de esa ejecución comenzará.

  3. Cada vez que enfoca cualquiera de las ventanas, el script elevará la otra ventana, pero volverá a enfocarse en la seleccionada originalmente (nota: con un cuarto de segundo de retraso para obtener el mejor rendimiento), creando así la sensación de que las ventanas están unidas.

Si selecciona la misma ventana las dos veces, el script se cerrará. Si en cualquier momento hace clic en el botón Cerrar del cuadro de diálogo emergente, el script se cerrará.

Fuente de script

También disponible 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 :

Sergiy Kolodyazhnyy
fuente
Saludos, estoy realmente impresionado. El time.sleepbit entre cambios, ¿puedo poner eso a cero? ¿Hay alguna razón para la demora?
Simon Tong
1
@simontong puedes intentar comentar esa línea como # time.sleep(0.25)y no se ejecutará. La razón de esto es garantizar que cada ventana se levante correctamente. En mi experiencia en el pasado, necesitaba tener retrasos para operar en Windows. Creo que un cuarto de segundo de retraso no es tanto. En realidad, permítanme agregar solo una línea más al script, que podría acelerarlo. DE ACUERDO ?
Sergiy Kolodyazhnyy
@simontong OK, he actualizado el script. Pruebalo ahora. Debería tener un cambio más rápido
Sergiy Kolodyazhnyy
@simontong Actualizaré el script con algunas adiciones menores para agregar algunas características adicionales. Te lo haré saber una vez que esté listo, házmelo saber lo que piensas
Sergiy Kolodyazhnyy
@simontong agregó opciones adicionales al script, por favor revise
Sergiy Kolodyazhnyy
6

Elevar un número arbitrario de ventanas como una

La solución a continuación le permitirá elegir cualquier combinación de dos, tres o más ventanas para combinar y elevar como una con un atajo de teclado.

El script hace su trabajo con tres argumentos:

add

para agregar la ventana activa al grupo

raise

para elevar el grupo establecido

clear

para borrar el grupo, listo para definir un nuevo grupo

La secuencia de comandos

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

Cómo utilizar

  1. El script necesita wmctrly xdotool:

    sudo apt-get install wmctrl xdotool
  2. Copie el script anterior en un archivo vacío, guárdelo como groupwindows.py
  3. Prueba: ejecuta el script: abre dos ventanas de terminal, ejecuta el comando:

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

    en los dos. Cúbralos con otras ventanas (o minimícelas). Abra una tercera ventana de terminal, ejecute el comando:

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

    Las dos primeras ventanas se levantarán como una.

  4. Si todo funciona bien, cree tres teclas de acceso directo personalizadas: Elija: Configuración del sistema> "Teclado"> "Accesos directos"> "Accesos directos personalizados". Haga clic en "+" y agregue los siguientes comandos a tres accesos directos separados:

    en mi sistema, usé:

    Alt+ A, ejecutando el comando:

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

    ... para agregar una ventana al grupo.

    Alt+ R, ejecutando el comando:

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

    ... para levantar el grupo.

    Alt+ C, ejecutando el comando:

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

    ... para limpiar el grupo

Explicación

El guión funciona de manera bastante simple:

  • Cuando se ejecuta con el argumento add, el script almacena / agrega el id de la ventana de la ventana activa en un archivo oculto~/.windowlist
  • Cuando se ejecuta con el argumento raise, el script lee el archivo, levanta las ventanas de la lista con el comando:

    wmctrl -ia <window_id>
  • Cuando se ejecuta con el argumento clear, el script elimina el archivo oculto ~/.windowlist.

Notas

  • El script también funcionará en ventanas minimizadas, minimizará las ventanas posiblemente minimizadas
  • Si el conjunto de ventanas está en otra ventana gráfica, el script cambiará a la ventana gráfica correspondiente
  • El conjunto es flexibel, siempre puede agregar otras ventanas al conjunto actual.

¿Más flexibilidad?

Como se mencionó, el script anterior permite agregar ventanas en cualquier momento a las ventanas agrupadas. La versión siguiente también permite eliminar cualquiera de las ventanas (en cualquier momento) de la 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)

El argumento adicional para ejecutar el script es delete, entonces:

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

elimina la ventana activa de las ventanas agrupadas. Para ejecutar este comando, en mi sistema, configuro Alt+ Dcomo acceso directo.

Jacob Vlijm
fuente