160 lines
15 KiB
Markdown
160 lines
15 KiB
Markdown
# 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`.
|