Files
utun2/tools/chatgui/chatgui_doc.md
T

15 KiB
Raw Blame History

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.

Из корня репозитория:

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:

lupdate src -no-obsolete -locations relative -ts translations/chatgui_en.ts translations/chatgui_ru.ts

Проверки

Состав целей и окружение тестов определяет CMakeLists.txt. Основные команды:

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.