From 41ce04e2f0a6f23ac89ee3a50abfdcd8f02a9015 Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 27 Oct 2026 03:22:21 +0100 Subject: [PATCH] Document desktop instance activation and lock lifetime --- tools/chatgui/src/single_instance.h | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/tools/chatgui/src/single_instance.h b/tools/chatgui/src/single_instance.h index 11a857f7..bd04600e 100644 --- a/tools/chatgui/src/single_instance.h +++ b/tools/chatgui/src/single_instance.h @@ -1,6 +1,10 @@ /* Один GUI на каталог профиля: QLockFile защищает данные, локальный канал передаёт * запрос показа владельцу. start вызывается один раз до чтения конфига/запуска ядра. - * Объект живёт до уничтожения окон; все вызовы — GUI-поток. */ + * Объект живёт до уничтожения окон; все вызовы — GUI-поток с QApplication. + * Имя канала — хеш homePath и канонического пути профиля (на Windows без учёта регистра), + * QLocalServer настроен на UserAccessOption. Только владелец lock удаляет старый endpoint. + * Команда ACTIVATE + LF, ответ OK + LF: ACK означает принятие запроса, в том числе до создания окна; + * подтверждение не гарантирует разрешения ОС на foreground. Ошибочные команды не активируют окно. */ #pragma once #include @@ -11,24 +15,30 @@ class QWidget; -/* Показать окно, сохранив maximized/fullscreen, и активировать его модальный диалог, если он есть. */ +/* Заимствованный window показать без потери maximized/fullscreen; активный модальный диалог + * QApplication имеет приоритет. NULL даёт WARN; ОС может отказать в активации/foreground. */ void showAndActivateWindow(QWidget* window); class SingleInstance : public QObject { public: - enum Result { Error = -1, Primary, Activated }; + enum Result { Error = -1, Primary, Activated }; // ошибка / владелец lock+server / запрос принят другим владельцем /* profileDirectory — существующий каталог конфига; parent владеет объектом по правилам QObject. */ explicit SingleInstance(const QString& profileDirectory, QObject* parent = nullptr); ~SingleInstance() override; - /* Получить блокировку и открыть канал либо дождаться ACK владельца (до 5 секунд). - * Primary: продолжить запуск; Activated: завершить второй процесс с 0; Error: errorString(). */ + /* Получить блокировку и открыть канал либо ждать ACK через вложенный Qt event loop до 5 секунд. + * Отсутствующий пока канал перепроверяется каждые 50 мс; уже подключённый владелец обязан дать ACK. + * Primary: продолжить запуск; Activated: завершить второй процесс с 0; Error: errorString(). + * При Error прекратить запуск и уничтожить объект: взятый lock сохраняется до деструктора. + * Повторный вызов даёт Error, включая вызов после неудачного первого start. */ Result start(); - /* Текущее окно: мастер настройки или MainWindow. nullptr откладывает показ до следующего setWindow. */ + /* Заимствованное текущее окно: мастер настройки или MainWindow; уничтожение обнуляет QPointer. + * nullptr откладывает запросы показа. Ожидающий запрос применяется через singleShot(0) + * после setWindow, чтобы перекрыть начальное showMinimized; несколько запросов объединяются. */ void setWindow(QWidget* window); - QString errorString() const { return m_error; } - QString serverName() const { return m_name; } + QString errorString() const { return m_error; } // причина Error, включая ошибку пути в конструкторе + QString serverName() const { return m_name; } // вычислено в конструкторе; пусто при неверном пути private: Result fail(const QString& error); @@ -39,6 +49,6 @@ private: QLocalServer m_server; // Только текущий пользователь; имя связано с каноническим путём профиля. QString m_name, m_error; QPointer m_window; // Заимствованное окно; обнуляется при уничтожении мастера/GUI. - bool m_started = false; + bool m_started = false; // start уже вызывался независимо от его результата bool m_pending = false; // Запрос принят до создания окна; повторные запросы объединяются. };