# 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_`, `peers_`, `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`.