Как я могу сгруппировать окна, которые будут подняты как одно?
У меня есть два окна, A и B. Можно ли как-то связать два окна вместе, так что переключение на A также поднимает B, или переключение на B также поднимает A?
Я понимаю, что использование нескольких рабочих пространств - это альтернативный вариант, но мне было интересно, возможно ли это?
2 ответа
Вступление
Следующий скрипт позволяет выбрать два окна, и, пока оба окна открыты, он поднимает оба окна, когда пользователь фокусируется на одном из них. Например, если кто-то связывает вдов А и В, то ведьма или А или В заставит обоих подняться выше других вдов.
Чтобы остановить скрипт, вы можете использовать killall link_windows.py
в терминале или закройте и снова откройте одно из окон. Вы также можете отменить выполнение, нажав кнопку закрытия X в любом из всплывающих диалоговых окон выбора окна.
Потенциальные настройки:
- несколько экземпляров скрипта могут быть использованы для группировки пар из двух окон. Например, если у нас есть окна A,B,C и D, мы можем связать A и B вместе и связать C и D вместе.
- несколько окон могут быть сгруппированы под одним окном. Например, если я связываю окно B с A, C с A и D с A, это означает, что, если я всегда переключаюсь на A, я могу поднять все 4 окна одновременно.
использование
Запустите скрипт как:
python link_windows.py
Скрипт совместим с Python 3, поэтому он также может работать как
python3 link_windows.py
Есть две опции командной строки:
--quiet
или же-q
, позволяет заглушить окна GUI. С помощью этой опции вы можете просто щелкнуть мышью на любых двух окнах, и скрипт начнет их связывать.--help
или же-h
, печатает информацию об использовании и описании.
-h
Опция выдает следующую информацию:
$ 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.
Дополнительную техническую информацию можно посмотреть через pydoc ./link_windows.py
, где ./
означает, что вы должны находиться в том же каталоге, что и скрипт.
Простой процесс использования для двух окон:
Появится всплывающее окно с просьбой выбрать окно № 1, нажмите OK или нажмите Enter. Указатель мыши превратится в крест. Нажмите на одно из окон, которое вы хотите связать.
Появится второе всплывающее окно с просьбой выбрать окно № 2, нажать OK или нажать Enter. Опять же, указатель мыши превратится в крестик. Нажмите на другое окно, которое вы хотите связать. После этого начнется казнь.
Всякий раз, когда вы фокусируете любое окно, скрипт поднимает другое окно вверх, но возвращает фокус к первоначально выбранному (обратите внимание - с задержкой в четверть секунды для лучшей производительности), создавая ощущение, что окна связаны друг с другом.
Если вы выберете одно и то же окно оба раза, скрипт закроется. Если в любой момент вы нажмете кнопку закрытия всплывающего диалога, сценарий закроется.
Источник скрипта
Также доступно как GitHub Gist
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Author: Sergiy Kolodyazhnyy
Date: August 2nd, 2016
Written for: https://Ask-ubuntu.ru/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()
Заметки:
- При запуске из командной строки всплывающие диалоги выдают следующее сообщение:
Gtk-Message: GtkDialog mapped without a transient parent. This is discouraged.
Это можно игнорировать. - Консультируйтесь Как я могу редактировать / создавать новые элементы запуска в Unity вручную? для создания панели запуска или ярлыка на рабочем столе для этого скрипта, если вы хотите запустить его двойным щелчком мыши
- Чтобы связать этот сценарий с сочетанием клавиш для быстрого доступа, обратитесь к разделу Как добавить сочетания клавиш?
Поднимите произвольное количество окон как одно
Приведенное ниже решение позволит вам выбрать любую комбинацию из двух, трех или более окон, которые будут объединены и подняты как одно с помощью сочетания клавиш.
Скрипт работает с тремя аргументами:
add
добавить активное окно в группу
raise
поднять заданную группу
clear
очистить группу, готов определить новую группу
Сценарий
#!/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)
Как пользоваться
Скрипт нужен
wmctrl
а такжеxdotool
:sudo apt-get install wmctrl xdotool
- Скопируйте приведенный выше скрипт в пустой файл, сохраните его как
groupwindows.py
Тест - запустите скрипт: откройте два окна терминала, выполните команду:
python3 /absolute/path/to/groupwindows.py add
в них обоих. Накройте их другими окнами (или сверните их). Откройте третье окно терминала, выполните команду:
python3 /absolute/path/to/groupwindows.py raise
Первые два окна будут подняты как одно.
Если все работает нормально, создайте три пользовательских сочетания клавиш: Выберите: "Системные настройки" > "Клавиатура" > "Ярлыки" > "Пользовательские сочетания клавиш". Нажмите "+" и добавьте команды ниже к трем отдельным ярлыкам:
в моей системе я использовал:
Alt+A, запустив команду:
python3 /absolute/path/to/groupwindows.py add
... добавить окно в группу.
Alt+R, запустив команду:
python3 /absolute/path/to/groupwindows.py raise
... чтобы поднять группу.
Alt+C, запустив команду:
python3 /absolute/path/to/groupwindows.py clear
... очистить группу
объяснение
Скрипт работает довольно просто:
- Когда запустить с аргументом
add
скрипт сохраняет / добавляет идентификатор окна активного окна в скрытый файл~/.windowlist
Когда запустить с аргументом
raise
, скрипт читает файл, поднимает окна в списке с помощью команды:wmctrl -ia <window_id>
- Когда запустить с аргументом
clear
скрипт удаляет скрытый файл~/.windowlist
,
Заметки
- Скрипт также будет работать на свернутых окнах, он минимизирует возможно свернутые окна
- Если набор окон находится в другом окне просмотра, скрипт переключится на соответствующий вид
- Набор является гибким, вы всегда можете добавить другие окна к текущему набору.
Больше гибкости?
Как уже упоминалось, приведенный выше скрипт позволяет в любое время добавлять окна в сгруппированные окна. Версия ниже также позволяет удалить любое из окон (в любое время) из сгруппированного списка:
#!/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)
Дополнительный аргумент для запуска скрипта delete
, так:
python3 /absolute/path/to/groupwindows.py delete
удаляет активное окно из сгруппированных окон. Для запуска этой команды в моей системе я установил сочетание клавиш Alt+D.