Files
utun2/tools/chatgui/chatgui_doc.md
T

160 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Desktop chatgui
## Запуск и сборка
Приложение называется `vibechat`: Qt Widgets UI и общее C-ядро в одном процессе.
Обычная desktop-сборка использует Qt Multimedia, FFmpeg и встроенный голосовой стек.
CMake требует C++20 для GUI и C99 для ядра; Qt 6 предпочтителен, предусмотрен Qt 5.15 fallback.
Зависимости и платформенные настройки находятся в `CMakeLists.txt` и `libutun/CMakeLists.txt`.
Из корня репозитория:
```sh
cmake -S tools/chatgui -B tools/chatgui/build
cmake --build tools/chatgui/build --target vibechat -j4
./tools/chatgui/build/vibechat
```
Корневой `build.sh` также собирает GUI при наличии `tools/chatgui/build/CMakeCache.txt`.
Скрипты desktop-сборки: `build.sh`, `build_nix.sh`, `build_nix_full.sh`, `build.bat` внутри этого каталога.
Native API и протоколы находятся в общем `../../src`; копий chat_core/chat_sync в `transport/` нет.
## Владельцы и потоки
- GUI-поток владеет QApplication, виджетами, моделями, Qt-таймерами и desktop media engines.
- `UtunNode` создаёт uasync и UTUN_INSTANCE в worker, явно запускает core/chat и медиапривязки.
`[gui] utun_enabled=yes` дополнительно запускает UTUN; создание TUN управляется отдельно.
- Сетевые операции, подписанные записи, чат и изменения данных SQLite выполняет uasync.
Декодеры, FFmpeg, media_async и audio callbacks работают в своих workers и не обращаются к QWidget.
- `start()` возвращает подтверждённую готовность либо причину отказа с откатом ресурсов.
Идентичность, NTP и фактический db_path читаются в GUI через защищённую копию состояния.
- `requestStop()` просит выход, `stop()` выполняется в GUI и присоединяет worker.
Worker перед destroy ждёт подтверждения `stopping` в GUI. В этом сигнале GUI снимает SQLite handle;
слоты не должны вызывать stop/join. Синхронный stop сам выполняет отсоединение до join.
- Instance уничтожает worker; uasync освобождается после join. Старый queued `stopped` отсеивается по поколению запуска.
Штатный выход MainWindow ставит stop после принятых записей геометрии/read/scroll.
Закрытие крестиком сохраняет геометрию и скрывает окно в tray.
Перед native teardown очищаются выбранный target, ожидания auto-play и заимствованный handle БД.
## Мост GUI ↔ ядро
`transport/gui_bridge.h` описывает контракты; номера и форматы событий определяет `src/chat/chat_event.h`.
Ядро отправляет `gui_bridge_post(type, data, len)`: байты копируются синхронно,
обработка и зарегистрированные callbacks выполняются через Qt queued connection в GUI.
Данные callback заимствованы только до возврата; строка с длиной не обязана иметь NUL.
GUI использует API с владением запросами:
- `gui_bridge_post_setting` копирует key/value в ui_state либо live chat_setting.
- `gui_bridge_submit_message` и `gui_bridge_submit_dm_message` фиксируют composer request_id.
Результат локальной записи возвращается MSG_SUBMITTED; отмена до исполнения даёт отказ.
- `gui_bridge_submit_attachment` передаёт подготовку PCM/файла; отмена освобождает PCM owner,
compressor и собственный временный source. При отказе постановки caller вызывает `attachment_send_discard`.
- `gui_bridge_download_attachment` обслуживает delegate, menu и auto-play.
- Обычные звонки, приглашения в рацию, редактирование участников, member-files и JOIN имеют специализированные API.
- `gui_bridge_post_bound_fn` предназначен для оставшихся простых native-команд:
один u_malloc-блок без вложенных ресурсов и адрес его поля inst.
Проверка running, привязка к ядру и постановка выполняются под mutex.
При -1 caller освобождает блок; при принятии его освобождает trampoline либо мост при отмене.
- `gui_bridge_post_uasync_fn` оставляет arg заимствованным; он подходит только для данных
с отдельно гарантированным временем жизни. Чтение `gui_bridge_get_inst()` не резервирует ядро.
Detach закрывает приём и отменяет ожидающие команды до освобождения native-ресурсов.
Приём команды подтверждает enqueue, а не успех её последующего исполнения.
## SQLite и профиль
Ядро владеет общей SQLite: `db_path/chats.db`, без db_path — in-memory.
Соединение открывается с FULLMUTEX, WAL и foreign_keys.
`DbManager` заимствует handle; обычные запросы GUI только читают и возвращают Qt-копии.
Каждый statement закрывается до возврата; ошибки bind/step/finalize не выдаются за частичные успешные снимки.
Низкоуровневый `prepareOrNull` отклоняет write statements, caller обязан завершить read statement до следующего GUI-события.
WAL не продлевает время жизни handle и не заменяет барьер stopping.
Основные данные: `channels`, `msg_<channel>`, `peers_<channel>`, `nodes`, `node_addresses`,
`accounts`, `ui_state`, DM history/media и attachment keys.
Сообщение имеет подписанный 32-байтный message_id, версию и reply_to.
Локальный rowid применяется для read/scroll; timestamp/message_id определяют порядок истории.
История PM имеет generation: старый снимок или результат удаления не применяется к новой истории.
В DbManager нет устаревшего sync-протокола, долгоживущих sync-курсоров и GUI-удаления peers.
Синхронизация принадлежит общему ядру: `src/chat`, `src/routing_layer`.
Подробные wire-контракты находятся в заголовках этих модулей и документах `../../doc`.
PRAGMA quick_check/integrity_check проверяют текстовый ответ SQLite, optimize оценивает SQL-успех
без требования строки результата. Обслуживание синхронно; handle сохраняется живым до возврата.
Оно не меняет подписанные сообщения/членство.
Фактический каталог профиля публикует UtunNode после применения override.
GUI использует его для media; пустой native db_path означает текущий каталог.
Идентичность INI нормализует общий config_updater, сохраняя корректный private key.
NodeConfig хранит GUI-проекцию, сетевые секции проверяет общий parser в worker.
## Модель и отрисовка сообщений
`MainWindow` связывает ChannelList, MessageList, AccountList, tray, media windows и bridge.
`MessageList` владеет моделью, composer и панелями; db/recorder заимствованы.
Основная история ограничена 200 строками. Навигация по reply загружает окно вокруг оригинала;
пока это окно открыто, новые сообщения не вытесняют его и не двигают read watermark свежей истории.
`MessageDelegate` использует один layout для sizeHint, paint и hit-test.
Аватар имеет размер 54px; узкий режим убирает его. Поддерживаются текст, цитаты, реакции,
опросы, voice, файлы, изображения, видео и состояния encrypted-вложений.
Медиа открывается левым кликом по телу; drag или release без соответствующего press не запускает действие.
QPersistentModelIndex и QPointer защищают от замены модели и уничтожения UI в модальном цикле.
Опросы и реакции реализованы в `src/messagefeedback.cpp`; канал диалога фиксируется до exec.
PCM-позиция voice берётся из SoundManager. Таймер обновляет состояние вне paint,
останавливается после затухания/паузы и не удерживает уничтоженный view.
VoicePlayer отменяет устаревшую подготовку; загруженные PCM-сессии принадлежат SoundManager.
Auto-play не реагирует на initial sync/собственные сообщения, подавляет повторы и различает (channel,rowid).
Encrypted voice распознаётся по проверенным metadata/plaintext после decrypt, неизвестный MIME не считается voice.
Превью видео создаёт ограниченный QThreadPool; paint не запускает декодеры.
Сетевые загрузки и подготовка вложений принадлежат native media_delivery/media_async.
Обычный phone call и адресное приглашение в групповую рацию используют разные сервисы;
FINISHED приглашения в рацию завершает приглашение, дальнейшая рация живёт обычным циклом.
## Анимации, уведомления и язык
AnimTimer управляет общими LottieIcon; регистрация выполняется до EmojiPanel, shutdown — до очистки иконок.
TGS декомпрессируется zlib и рендерится rlottie. Unicode emoji и ресурсы перечислены в `src/emoji.cpp` и `resources/chatgui.qrc`.
Окно нового сообщения показывает MessageNotification, активность пользователя отслеживает UserActivityMonitor.
Нотификация не показывается для своего сообщения, initial sync и активного главного окна.
Ограничения размещения/показа определяет compositor; детали — в заголовках этих компонентов.
Язык выбирается через `[gui] language=system|en|ru`; смена применяется после перезапуска.
Русский и английский каталоги компилирует lrelease, инструменты Linguist обязательны.
Региональные форматы дат/чисел остаются системными.
Новые UI-строки обновляются из `tools/chatgui`:
```sh
lupdate src -no-obsolete -locations relative -ts translations/chatgui_en.ts translations/chatgui_ru.ts
```
## Проверки
Состав целей и окружение тестов определяет CMakeLists.txt. Основные команды:
```sh
cmake --build tools/chatgui/build --target vibechat test_desktop_commands test_utun_node -j4
ctest --test-dir tools/chatgui/build -R 'test_(desktop_commands|utun_node|message_events|channel_replies|database_maintenance)' --output-on-failure
```
Linux-тесты команд используют allocation hooks и null audio backend с обычной Multimedia-сборкой.
Это позволяет проверить очередь, native trampolines, SQLite и QWidget без физических audio devices.
Категории диагностики определяются `lib/debug_config.h`; используйте уровни config/chat/dm/media/radio/sys
для ошибок, при проверке состояний повышайте нужную категорию до debug.
Desktop Linux/Windows хранит текущий лог и четыре предыдущих файла (`chatgui.log.1`–`.4`;
для собственного `debug_file` используются такие же суффиксы). При запуске приложения
предыдущий непустой лог переименовывается; внутренний restart ядра и reopen не создают поколение.
Во время работы ротация происходит примерно через 10 MiB, без разрыва строк, с удалением самого старого файла.
Сжатия нет. Размер читается только при открытии, затем учитываются записанные байты.
Запись/ротация сериализованы между потоками. На Windows используются Unicode-пути.
При ошибке переименования продолжается append, повтор — через следующие 10 MiB; причина пишется в лог/stderr.
Проверка: `ctest --test-dir tools/chatgui/build -R '^test_log_rotation$' --output-on-failure`.