Вопрос или проблема
Когда я перезагружаю свой Mac, он “запоминает”, какие окна были открыты, и может:
- автоматически открывать запомненные окна
- окон терминала: продолжают с того места, где я остановился
- состояние других окон не обязательно запоминается
Я ищу эту функцию после перезагрузки (не гибернация).
Я понимаю, что Ubuntu не является MacOS, но было бы неплохо, если бы я знал/понимал терминологию/жаргон, чтобы определить уровень усилий (LOE), необходимых для настройки данной возможности на моем замечательном устройстве с версией 22.04 и наслаждаться этим, как на моем Mac.
Я не знаю, как это называется универсально, но я использую этот скрипт на Python для открытия приложений и перемещения их на один из трех мониторов при загрузке.
Примечания
- Требуется
xdotool
для перемещения окон, который недоступен под Wayland - Не запоминает приложения, вы указываете, какие приложения открыть
скрипт автозапуска
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Автор: pippim.com
Лицензия: GNU GPLv3. (c) 2022-2025
Источник: Этот репозиторий
Описание: автозапуск - открывает все GUI-приложения и располагает их на рабочем столе
"""
from __future__ import print_function # Должно быть первым импортом
from __future__ import with_statement # Обработка ошибок при открытии файлов
# from __future__ import unicode_literals # Не требуется.
import warnings # 'warnings' сообщает, какие команды не поддерживаются
warnings.simplefilter('default') # в будущих версиях Python.
# 04 августа 2023 - начальная версия для публикации на www.pippim.com
# 12 ноября 2024 - добавлен HomA (Домашняя автоматизация)
# 01 февраля 2025 - ответ на вопрос: https://askubuntu.com/q/1540243/307523
SUDO_PASSWORD = "XXXXXXX" # Необходим только для eyesome
import os
import time
OTHERS_TIME = 15 # pycharm открывает три проекта, 60K+ строк
SERVER_TIME = 1.5 # время загрузки gnome-terminal-server
BASHRC_TIME = 1.5 # секунды на загрузку ~/.bashrc
WINDOW_TIME = .5 # секунды для появления окна
ACTIVE_WINDOW = "" # Активное окно получит нажатия клавиш xdotool
commands = ['sleep ' + str(OTHERS_TIME), # время загрузки pycharm snap
'gnome-terminal &', # Запуск терминала в фоновом режиме
'sleep ' + str(SERVER_TIME), # время для запуска сервер терминала
'move 2261 1225', # настройка тройного монитора
'title "Python 1"', # Заголовок окна (экранирование пробелов)
'xdotool type "cd ~/python"', # каталог разработки Python
'xdotool key Return', # клавиша Enter
'xdotool key Control_L+Shift_L+T', # открыть новую вкладку терминала
'sleep ' + str(BASHRC_TIME), # команда Bash, подождите секунду
'title "Python 2"', # Заголовок окна (экранирование пробелов)
'xdotool key Control_L+Shift_L+T', # открыть новую вкладку терминала
'sleep ' + str(BASHRC_TIME), # команда Bash, подождите секунду
'title "Python 3"', # Заголовок окна (экранирование пробелов)
# ~/.config/autostart/cursor.desktop вызывает ~/python/cursor.sh слишком рано
'~/python/cursor.sh', # большой курсор для HDPI мониторов
'nautilus &', # менеджер файлов в фоновом режиме
'sleep ' + str(BASHRC_TIME * 2), # команда Bash, подождите секунду
'move 3849 2190', # настройка тройного монитора
'gnome-terminal &', # Запуск терминала в фоновом режиме
'sleep ' + str(BASHRC_TIME), # команда Bash, подождите секунду
'move 2261 308', # настройка тройного монитора
'title Dimmer', # Заголовок окна (экранирование пробелов)
'xdotool type "sudo /home/rick/eyesome/movie.sh"',
'xdotool key Return', # клавиша Enter
'sleep .25', # задержка до запроса пароля
'xdotool type "' + SUDO_PASSWORD + '"', # не появится в логах
'xdotool key Return', # клавиша Enter
'xdotool key Control_L+Shift_L+T', # открыть новую вкладку терминала
'sleep ' + str(BASHRC_TIME), # команда Bash, подождите секунду
'xdotool type "cd ~/python"', # каталог разработки Python
'xdotool key Return', # клавиша Enter
'title mserve', # Заголовок окна (экранирование пробелов)
# Переключает фокус и нажатия клавиш идут в неверное окно
'push', # сохранить активное окно
'xdotool type "~/python/mmm &"', # международный менеджер мониторов
'xdotool key Return', # клавиша Enter
'sleep ' + str(BASHRC_TIME * 2), # команда Bash, две секунды
'move 2080 281', # настройка тройного монитора
'pop', # восстановить активное окно
'push', # сохранить активное окно
'xdotool type "~/homa/homa.py -f &"', # HomA - Автоматизация дома (быстрый старт)
'xdotool key Return', # клавиша Enter
'sleep ' + str(BASHRC_TIME), # команда Bash, подождите секунду
'move 3900 2350', # настройка тройного монитора
'pop', # восстановить активное окно
'xdotool type "~/python/m"', # музыкальный проигрыватель на переднем плане
'xdotool key Return']
""" ПРИМЕЧАНИЕ: чтобы определить координаты окна, используйте 'wmctrl -lG'. Например:
$ wmctrl -lG
0x02a00002 -1 5355 24 410 1392 alien conky (alien)
0x03000003 0 4023 2317 1742 884 alien Mozilla Firefox
0x03c0000a 0 0 0 5790 3240 alien Рабочий стол
0x03000022 0 0 0 1920 1080 alien The New Atlas LIVE: The Duran
0x03e0004a 0 4377 1264 1377 855 alien сайт – mserve.md
0x03e00055 0 2858 620 1518 1495 alien mserve – ~/python/autostart
0x0540000a 0 2261 1253 1300 874 alien Python3
0x03c00c65 0 3870 2294 1280 736 alien Дом
0x054000fe 0 2261 336 1300 874 alien mserve
0x06400030 0 1968 309 1447 810 N/A Менеджер множественных мониторов - mmm
0x06800030 0 2889 78 1455 840 N/A SD-карта SanDisk 128GB
0x06800e6e 0 3735 926 1724 813 N/A Играет Избранное - mserve
0x03000043 0 2563 62 900 767 alien Tim-ta запуск проекта Laundry — Mozilla Firefox
"""
def process_commands(command_list):
""" Обработка команд в command_list """
for command in command_list:
if command.endswith('&'):
# Запуск в фоновом режиме и получение ID окна
active_pid, active_win = launch_command(command)
if active_pid == 0:
print("Ошибка при запуске", command,
"прерывание скрипта 'autostart'")
exit()
elif command.startswith('move'):
move_window(command)
elif command.startswith('title'):
terminal_title(command)
elif command.startswith('gedit'):
gedit()
elif command == 'push':
push()
elif command == 'pop':
pop()
else:
run_and_wait(command)
def launch_command(ext_name):
""" Запуск внешней команды в фоновом режиме и возврат PID родителю.
Используйте для программ, требующих БОЛЕЕ 0.2 секунды для работы. """
all_pids = get_pids(ext_name) # Снимок текущего списка PID
all_wins = get_wins(all_pids) # Снимок открытых окон
new_pids = all_pids # Цикл до изменений
sleep_count = 0 # счетчик, чтобы предотвратить бесконечные циклы
''' Запустите команду Linux '''
os.popen(ext_name) # выполнение команды в фоновом режиме
''' Ожидание появления команды в списке процессов (PID) '''
while new_pids == all_pids: # Цикл до получения нового PID
new_pids = get_pids(ext_name) # Снимок текущего списка PID
if sleep_count > 0: # Не ждите в первый раз
time.sleep(.005) # сон 5 миллисекунд
sleep_count += 1
if sleep_count == 1000: # тайм-аут 5 секунд
print('Ошибка launch_ext_command(): достигнут тайм-аут 5 секунд')
print('Имя внешней команды:', ext_name)
return 0, 0
''' Список новых PID, появившихся после выполнения os.popen() команды '''
pid_list = list(set(new_pids) - set(all_pids))
if not len(pid_list) == 1:
print('Ошибка launch_command(): новый PID не найден')
return 0, 0
''' Список новых окон после выполнения os.popen() команды '''
time.sleep(WINDOW_TIME) # Дайте время для появления окна
new_wins = get_wins(all_pids) # Снимок открытых окон
win_list = list(set(new_wins) - set(all_wins))
if not len(win_list) == 1:
''' Запущенный процесс PID найден, но он не открыл окно'''
return int(pid_list[0]), 0
else:
''' Возврат PID и Window ID программы, запущенной в фоне '''
return int(pid_list[0]), int(win_list[0])
def run_and_wait(ext_name):
""" Запуск внешней команды и ожидание ее завершения.
Используйте для программ, требующих МЕНЕЕ 0.2 секунды для работы. """
result = os.popen(ext_name).read().strip()
return result
def get_pids(ext_name):
""" Возврат списка PID для имени программы и аргументов
Пробельный вывод сжат до одного пробела """
# Просто grep до первого пробела в командной строке. Он не срабатывал на!
prog_name = ext_name.split(' ', 1)[0]
all_lines = os.popen("ps aux | grep -v grep | grep " +
"'" + prog_name + "'").read().strip().splitlines
PID = []
first_line = True
for line in all_lines(): # Прочитать все строки PID, выведенные командой 'ps'
if first_line: # Иногда первая строка - это заголовок.
first_line = False # Найдена первая строка
continue # Пропустить заголовок, или пропустить PID #1, что не имеет значения
single=" ".join(line.split()) # Сжать пробелы в один пробел
PID.append(int(single.split(' ', 2)[1])) # Добавить номер PID в список
return PID
def get_wins(all_pids):
""" Возвращает список всех открытых окон под список PID
В настоящее время не требуется, так как мы работаем с активным окном """
windows = []
for pid in all_pids:
all_lines = os.popen('xdotool search --pid ' + str(pid)). \
read().strip().splitlines
for line in all_lines():
windows.append(int(line))
return windows
def move_window(line):
""" Перемещение окна по координатам x y на рабочем столе
Если передается буква x или y, это измерение остается неизменным. Например:
xdotool getactivewindow windowmove 100 100 # Перемещается на 100,100
xdotool getactivewindow windowmove x 100 # Перемещается в x,100
xdotool getactivewindow windowmove 100 y # Перемещается на 100,y """
line=" ".join(line.split()) # Сжать пробелы в один
x = line.split(' ')[-2]
y = line.split(' ')[-1]
# Нет необходимости передавать ID окна, так как последнее активное окно по умолчанию
all_lines = os.popen('xdotool getactivewindow windowmove ' + x + ' ' + y). \
read().strip().splitlines
for line in all_lines():
print(line)
def push():
""" Сохранить номер активного окна """
global ACTIVE_WINDOW
ACTIVE_WINDOW = os.popen('xdotool getactivewindow').read().strip()
def pop():
""" Восстановить фокус на сохраненное окно """
# noinspection SpellCheckingInspection
os.popen('xdotool windowfocus ' + ACTIVE_WINDOW).read().strip()
# noinspection SpellCheckingInspection
def terminal_title(new_title):
""" Довольно неудобно вызывать xdotool, который задыхается от двойных кавычек и командной оболочки
через python, который задыхается от обратных косых черт.
Основано на функции командной оболочки:
функция title() { PS1="${PS1/\\u@\\h: \\w/$@}"; }
Ссылка на коды клавиш xdotool:
https://gitlab.com/cunidev/gestures/-/wikis/xdotool-list-of-key-codes """
title = new_title.split(' ', 1)[1] # Удалить начальный токен "title"
run_and_wait('xdotool type PS1=')
run_and_wait('xdotool key quotedbl')
run_and_wait('xdotool type $')
run_and_wait('xdotool key braceleft')
run_and_wait('xdotool type PS1/')
run_and_wait('xdotool key backslash')
run_and_wait('xdotool key backslash')
run_and_wait('xdotool type u@')
run_and_wait('xdotool key backslash')
run_and_wait('xdotool key backslash')
run_and_wait('xdotool type "h: "')
run_and_wait('xdotool key backslash')
run_and_wait('xdotool key backslash')
run_and_wait('xdotool type "w/"')
command = 'xdotool type "' + title + '"'
run_and_wait(command)
run_and_wait('xdotool key braceright')
run_and_wait('xdotool key quotedbl')
run_and_wait('xdotool key Return')
def gedit():
""" Загружает gedit с пятью последними файлами.
Больше не используется в пользу pycharm. """
last_modified_files = gedit_recent_files()
command = 'gedit '
for f in last_modified_files:
# Добавить каждое название файла в список параметров, переданных `gedit`
command += '"' + f + '" '
# Открыть gedit с последними пятью измененными файлами. '&' = запуск в фоновом режиме
command = command + ' &'
active_pid, active_win = launch_command(command)
if active_pid == 0:
print("Ошибка при запуске", command,
"прерывание скрипта 'autostart'")
exit()
def gedit_recent_files():
# noinspection SpellCheckingInspection
""" Получить список 5 последних файлов gedit:
grep --no-group-separator -B5 'group>gedit' ~/.local/share/recently-used.xbel
| sed -n 1~6p | sed 's# <bookmark href="file:///#/#g' | sed 's/"//g'
/home/rick/python/mmm added=2020-05-02T15:34:55Z modified=2020-11-19T00:43:45Z visited=2020-05-02T15:34:56Z>
/home/rick/python/mserve added=2020-07-26T16:36:09Z modified=2020-11-28T01:57:19Z visited=2020-07-26T16:36:09Z>
"""
# noinspection SpellCheckingInspection
command = "grep --no-group-separator -B5 'group>gedit' " + \
"~/.local/share/recently-used.xbel | " + \
"sed -n 1~6p | sed 's# <bookmark href=" + '"' + \
"file:///#/#g' | " + "sed 's/" + '"' + "//g'"
recent_files = []
times = []
all_lines = os.popen(command).read().strip().splitlines
unique = 1 # gedit может дать всем открытым файлам одно и то же время
for line in all_lines():
fname = line.split(' added=', 1)[0]
trailing = line.split(' added=', 1)[1]
modified = trailing.split(' modified=', 1)[1]
modified = modified.split('Z', 1)[0]
d = time.strptime(modified, '%Y-%m-%dT%H:%M:%S')
epoch = time.mktime(d)
epoch = int(epoch)
recent_files.append(fname)
try:
times.index(epoch)
# gedit дал нескольким файлам одинаковое время изменения
epoch += unique
unique += 1
except ValueError:
pass # Не дублирует время
times.append(epoch)
N = 5
top_files = []
if N > len(times):
# В списке менее 5 последних файлов
N = len(times)
if N == 0:
# В списке нет последних файлов
return top_files # возврат пустого списка
# Сохраняем список в tmp для получения индекса
tmp = list(times)
# Сортировка списка так, что самые большие элементы справа
times.sort()
#print ('5 последних из списков и индексов')
for i in range(1, N + 1):
top_files.append(recent_files[tmp.index(times[-i])])
return top_files
if __name__ == "__main__":
process_commands(commands)
# конец автозапуска
Ответ или решение
Восстановление состояния окон после перезагрузки компьютера — задача, которую многие пользователи операционных систем стремятся решить для повышения эффективности работы. Этот процесс может быть особенно полезным для тех, кто работает на нескольких мониторах и использует множество приложений одновременно. В данной статье мы рассмотрим, как это можно реализовать в Ubuntu и какие термины используются для описания подобных сценариев.
Теория
Восстановление состояния системы (System State Restoration) — это возможность компьютера или операционной системы повторно открывать приложения и окна в том положении и состоянии, в котором они находились до завершения работы системы. Этот процесс включает:
-
Автоматическое открытие запомненных окон: Приложения, которые были активно использованы, автоматически запускаются после перезагрузки системы.
-
Продолжение работы терминала: В некоторых системах возможно возобновление сеансов командной строки, что позволяет пользователю продолжать работу, начатую до перезагрузки.
-
Частичное восстановление состояния: Не все приложения могут поддерживать сохранение пользовательского состояния, поэтому часть окон может не восстановиться полностью.
Пример
В macOS данная функция реализуется с помощью автоматического восстановления сеансов, где после перезагрузки система открывает приложения, которые были запущены до выключения. Ubuntu, будучи другой операционной системой, по умолчанию не предлагает аналогичную функциональность в таком же виде, однако пользователи могут настроить такую возможность самостоятельно с помощью сторонних инструментов.
Применение
Для пользователей Ubuntu, которые хотят реализовать подобное восстановление, существует несколько способов:
-
Использование скриптов для автозапуска: Один из подходов, как показано в предоставленном примерном коде, заключается в использовании скриптов Python. Такие скрипты могут быть настроены для автоматического открытия необходимых приложений и размещения окон в нужных частях экрана после каждого перезапуска.
-
Инструмент xdotool: Этот утилитарный инструмент позволяет программно управлять окнами X11. Однако следует учесть, что xdotool не работает в среде Wayland, новой графической архитектуре в Ubuntu, что может осложнить выполнение аналогичных задач.
-
Другая вспомогательная утилита: В зависимости от рабочих процессов, могут быть использованы другие инструменты и библиотеки, которые помогают в управлении сессиями или оконными менеджерами, как, например, wmctrl, которая служит для управления окнами и имеет возможность восстанавливать определенные параметры окон.
Вывод
Для реализации полноценного восстановления состояния окон после перезагрузки в Ubuntu может потребоваться сочетание нескольких подходов и инструментов. Важно понимать возможности и ограничения используемых утилит, а также готовность системы поддерживать такие процессы. Прежде чем приступать к реализации, рекомендуется оценить уровень усилий (LOE) и возможность автоматизации в зависимости от индивидуальных нужд и конфигурации вашей рабочей среды.
Для большинства пользователей полное восстановление состояния приложений и окон требует индивидуального подхода, где важна интеграция различных инструментов и знание специфики своей операционной системы.
Таким образом, грамотное использование терминов и технологий, подобных xdotool и пользовательских скриптов, позволит создать рабочую среду, аналогичную macOS, предоставляющую богатые возможности по автоматизации и управлению сессиями.