15 KiB
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.