Какой термин используется для восстановления окон после завершения работы?

Вопрос или проблема

Когда я перезагружаю свой 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) — это возможность компьютера или операционной системы повторно открывать приложения и окна в том положении и состоянии, в котором они находились до завершения работы системы. Этот процесс включает:

  1. Автоматическое открытие запомненных окон: Приложения, которые были активно использованы, автоматически запускаются после перезагрузки системы.

  2. Продолжение работы терминала: В некоторых системах возможно возобновление сеансов командной строки, что позволяет пользователю продолжать работу, начатую до перезагрузки.

  3. Частичное восстановление состояния: Не все приложения могут поддерживать сохранение пользовательского состояния, поэтому часть окон может не восстановиться полностью.

Пример

В macOS данная функция реализуется с помощью автоматического восстановления сеансов, где после перезагрузки система открывает приложения, которые были запущены до выключения. Ubuntu, будучи другой операционной системой, по умолчанию не предлагает аналогичную функциональность в таком же виде, однако пользователи могут настроить такую возможность самостоятельно с помощью сторонних инструментов.

Применение

Для пользователей Ubuntu, которые хотят реализовать подобное восстановление, существует несколько способов:

  1. Использование скриптов для автозапуска: Один из подходов, как показано в предоставленном примерном коде, заключается в использовании скриптов Python. Такие скрипты могут быть настроены для автоматического открытия необходимых приложений и размещения окон в нужных частях экрана после каждого перезапуска.

  2. Инструмент xdotool: Этот утилитарный инструмент позволяет программно управлять окнами X11. Однако следует учесть, что xdotool не работает в среде Wayland, новой графической архитектуре в Ubuntu, что может осложнить выполнение аналогичных задач.

  3. Другая вспомогательная утилита: В зависимости от рабочих процессов, могут быть использованы другие инструменты и библиотеки, которые помогают в управлении сессиями или оконными менеджерами, как, например, wmctrl, которая служит для управления окнами и имеет возможность восстанавливать определенные параметры окон.

Вывод

Для реализации полноценного восстановления состояния окон после перезагрузки в Ubuntu может потребоваться сочетание нескольких подходов и инструментов. Важно понимать возможности и ограничения используемых утилит, а также готовность системы поддерживать такие процессы. Прежде чем приступать к реализации, рекомендуется оценить уровень усилий (LOE) и возможность автоматизации в зависимости от индивидуальных нужд и конфигурации вашей рабочей среды.

Для большинства пользователей полное восстановление состояния приложений и окон требует индивидуального подхода, где важна интеграция различных инструментов и знание специфики своей операционной системы.

Таким образом, грамотное использование терминов и технологий, подобных xdotool и пользовательских скриптов, позволит создать рабочую среду, аналогичную macOS, предоставляющую богатые возможности по автоматизации и управлению сессиями.

Оцените материал
Добавить комментарий

Капча загружается...