Jak mogę grupować okna, które mają być podniesione jako jedno?

10

Mam dwa okna, A i B. Czy można w jakiś sposób połączyć ze sobą dwa okna, tak że przejście na A również podnosi B, czy przejście na B również podnosi A?

Rozumiem, że używanie wielu obszarów roboczych jest alternatywną opcją, ale zastanawiałem się, czy jest to również możliwe?

Simon Tong
źródło
kolejność z nie jest bardzo ważna, ale jeśli to możliwe, byłoby świetnie
Simon Tong
Myślę, że wiele miejsc pracy jest zdecydowanie najprostszym rozwiązaniem. Znasz kombinacje klawiszy do przełączania się między nimi?
thomasrutter
1
Jesteś szybkim akceptantem :) Niemniej byłbym wdzięczny, gdybyś skomentował moją odpowiedź.
Jacob Vlijm
5
Możliwy duplikat „grupowania” okien?

Odpowiedzi:

9

Wprowadzenie

Poniższy skrypt pozwala wybrać dwa okna, a gdy oba okna są otwarte, podniesie oba okna, gdy użytkownik skupi jedno z nich. Na przykład, jeśli łączy się wdowy A i B, wiedźma z A lub B spowoduje, że oba podniosą się ponad inne wdowy.

Aby zatrzymać skrypt, możesz użyć go killall link_windows.pyw terminalu lub zamknąć i ponownie otworzyć jedno z okien. Można także anulować wykonanie, naciskając przycisk zamykania Xw dowolnym oknie podręcznym wyboru okna.

Potencjalne poprawki:

  • wiele wystąpień skryptu można wykorzystać do grupowania par dwóch okien. Na przykład, jeśli mamy okna A, B, C i D, możemy połączyć A i B razem oraz połączyć C i D razem.
  • wiele okien można zgrupować w jednym oknie. Na przykład, jeśli połączę okno B z A, C z A i D z A, oznacza to, że jeśli zawsze przełączę się na A, mogę podnieść wszystkie 4 okna jednocześnie.

Stosowanie

Uruchom skrypt jako:

python link_windows.py

Skrypt jest zgodny z Python 3, więc można go również uruchomić jako

python3 link_windows.py

Istnieją dwie opcje wiersza poleceń:

  • --quietlub -q, pozwala wyciszyć okna GUI. Dzięki tej opcji możesz po prostu kliknąć myszką na dowolne dwa okna, a skrypt zacznie je łączyć.
  • --helplub -hdrukuje informacje o użytkowaniu i opisie.

Ta -hopcja wyświetla następujące informacje:

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

Dodatkowe informacje techniczne można wyświetlić za pośrednictwem pydoc ./link_windows.py, gdzie ./oznacza, że ​​musisz znajdować się w tym samym katalogu co skrypt.

Prosty proces użytkowania dwóch okien:

  1. Pojawi się wyskakujące okienko z prośbą o wybranie okna nr 1, naciśnięcie OKlub naciśnięcie Enter. Wskaźnik myszy zmieni się w krzyż. Kliknij jedno z okien, które chcesz połączyć.

  2. Pojawi się drugie okienko z prośbą o wybranie okna nr 2, naciśnięcie OKlub wciśnięcie Enter. Ponownie wskaźnik myszy zmieni się w krzyżyk. Kliknij drugie okno, które chcesz połączyć. Po tym rozpocznie się egzekucja.

  3. Ilekroć skupisz jedno z okien, skrypt podniesie drugie okno w górę, ale przywróci fokus do pierwotnie wybranego (uwaga - z ćwierć sekundy opóźnienia dla najlepszej wydajności), tworząc w ten sposób wrażenie, że okna są ze sobą połączone.

Jeśli wybierzesz to samo okno za każdym razem, skrypt zostanie zamknięty. Jeśli w dowolnym momencie klikniesz przycisk Zamknij wyskakujące okno dialogowe, skrypt zostanie zamknięty.

Źródło skryptu

Dostępny również jako GitHub Gist

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Author: Sergiy Kolodyazhnyy
Date:  August 2nd, 2016
Written for: https://askubuntu.com/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()

Uwagi :

  • Podczas uruchamiania z wiersza poleceń wyskakujące okna dialogowe wyświetlają następujący komunikat: Gtk-Message: GtkDialog mapped without a transient parent. This is discouraged.Można je zignorować.
  • Skonsultuj Jak ręcznie edytować / tworzyć nowe elementy uruchamiające w Unity? do utworzenia programu uruchamiającego lub skrótu na pulpicie dla tego skryptu, jeśli chcesz uruchomić go podwójnym kliknięciem
  • Aby połączyć ten skrypt ze skrótem klawiaturowym dla łatwego dostępu, zobacz Jak dodać skróty klawiaturowe?
Sergiy Kolodyazhnyy
źródło
Na zdrowie, jestem pod wrażeniem. time.sleepNieco między przełączania, czy jestem w stanie umieścić że do zera? czy jest powód opóźnienia?
Simon Tong,
1
@ Simontong możesz spróbować skomentować tę linię jak # time.sleep(0.25)i nie będzie ona wykonywana. Powodem tego jest prawidłowe podniesienie każdego okna. Z mojego doświadczenia w przeszłości potrzebowałem opóźnień w działaniu systemu Windows. Myślę, że kwadransowe opóźnienie nie jest aż tak duże. Właściwie, pozwól mi dodać jeszcze jedną linię do skryptu, która mogłaby go przyspieszyć. OK ?
Sergiy Kolodyazhnyy
@ Simontong OK, zaktualizowałem skrypt. Spróbuj teraz. Powinien mieć szybsze przełączanie
Sergiy Kolodyazhnyy
@ Simontong Będę aktualizować skrypt z kilkoma drobnymi dodatkami, aby dodać kilka dodatkowych funkcji. Dam ci znać, gdy będzie gotowy, daj mi znać, co myślisz
Sergiy Kolodyazhnyy
@simontong dodał dodatkowe opcje do skryptu, proszę przejrzeć
Sergiy Kolodyazhnyy
6

Podnieś dowolną liczbę okien jako jedno

Poniższe rozwiązanie pozwoli Ci wybrać dowolną kombinację dwóch, trzech lub więcej okien do połączenia i podniesienia jako jedno za pomocą skrótu klawiaturowego.

Skrypt działa z trzema argumentami:

add

aby dodać aktywne okno do grupy

raise

podnieść ustaloną grupę

clear

wyczyścić grupę, gotowy do zdefiniowania nowej grupy

Scenariusz

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

Jak używać

  1. Skrypt potrzebuje wmctrli xdotool:

    sudo apt-get install wmctrl xdotool
  2. Skopiuj powyższy skrypt do pustego pliku i zapisz go jako groupwindows.py
  3. Testuj - uruchom skrypt: otwórz dwa okna terminala, uruchom polecenie:

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

    w obu z nich. Zakryj je innymi oknami (lub zminimalizuj je). Otwórz trzecie okno terminala, uruchom polecenie:

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

    Pierwsze dwa okna zostaną podniesione jako jedno.

  4. Jeśli wszystko działa poprawnie, utwórz trzy niestandardowe klawisze skrótów: Wybierz: Ustawienia systemu> „Klawiatura”> „Skróty”> „Skróty niestandardowe”. Kliknij „+” i dodaj poniższe polecenia do trzech osobnych skrótów:

    w moim systemie użyłem:

    Alt+ A, uruchamiając polecenie:

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

    ... aby dodać okno do grupy.

    Alt+ R, uruchamiając polecenie:

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

    ... aby podnieść grupę.

    Alt+ C, uruchamiając polecenie:

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

    ... aby wyczyścić grupę

Wyjaśnienie

Skrypt działa po prostu:

  • Po uruchomieniu z argumentem addskrypt przechowuje / dodaje identyfikator okna aktywnego okna do ukrytego pliku~/.windowlist
  • Po uruchomieniu z argumentem raiseskrypt czyta plik, podnosi okna na liście za pomocą polecenia:

    wmctrl -ia <window_id>
  • Po uruchomieniu z argumentem clearskrypt usuwa ukryty plik ~/.windowlist.

Notatki

  • Skrypt będzie działał również na zminimalizowanych oknach, odminimalizuje możliwie zminimalizowane okna
  • Jeśli zestaw okien znajduje się w innej rzutni, skrypt przełączy się na odpowiednią rzutnię
  • Zestaw jest elastyczny, zawsze możesz dodać inne okna do bieżącego zestawu.

Większa elastyczność?

Jak wspomniano, powyższy skrypt umożliwia dodawanie okien w dowolnym momencie do zgrupowanych okien. Poniższa wersja umożliwia również usunięcie dowolnego okna (w dowolnym momencie) z pogrupowanej listy:

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

Dodatkowym argumentem do uruchomienia skryptu jest delete:

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

usuwa aktywne okno z zgrupowanych okien. Aby uruchomić to polecenie, w moim systemie ustawiłem Alt+ Djako skrót.

Jacob Vlijm
źródło