TUI client for another.im (C + ncurses)
  • C 91.1%
  • Python 8.6%
  • Makefile 0.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-10-18 08:19:19 +00:00
mock Переезд метки Removed+New без задвоения файла, мета у нижней строки, copy_media 0/1/2 2026-10-18 07:49:00 +00:00
src Переезд метки Removed+New без задвоения файла, мета у нижней строки, copy_media 0/1/2 2026-10-18 07:49:00 +00:00
test Переезд метки Removed+New без задвоения файла, мета у нижней строки, copy_media 0/1/2 2026-10-18 07:49:00 +00:00
.gitignore Initial commit: another-im TUI client MVP 2026-10-18 02:25:55 +00:00
Makefile Клики по ростеру, F12-переключатель мыши, /omemo, бейдж «печатает…», ?join-JID комнат 2026-10-18 03:02:57 +00:00
README.md README: скриншот живого клиента 2026-10-18 08:19:19 +00:00
tui18oct26.png README: скриншот живого клиента 2026-10-18 08:19:19 +00:00

another-im-tui

Терминальный XMPP-клиент на libtacky: весь протокол, хранилище, OMEMO и переподключения живут в backend'е dist/tackyd-json, который клиент запускает подпроцессом и говорит с ним JSON-кадрами (<байты>\n<json>, запрос ["module","method",{args},token]). Здесь — только ncurses-интерфейс и состояние экрана, на C11.

Работает по ssh: ничего, кроме обычного TTY, не нужно.

another-im-tui в деле

Сборка и запуск

Нужны: gcc, ncursesw (libncursesw-dev), python3 для тестов, и собранный backend (make tackyd-json MEDIA_BACKEND=none в чекауте libtacky — собирается в его docker-образе).

make                 # bin/another-im-tui
make test            # json/wire-тесты + смоуки UI (мок и настоящий backend)
make run             # с backend'ом из ../libtacky/dist/tackyd-json
./bin/another-im-tui /путь/к/tackyd-json     # или так; или TACKY_BACKEND=...
./bin/another-im-tui mock/mockd.py           # мок вместо backend'а, для разработки

Профиль (журнал backend'а, база, ключи): ~/.local/share|.cache|.config/another-im-tui, либо всё в одном месте через AIT_HOME=/путь.

Клавиши и команды

↑ / ↓, колесо мыши прокрутка ленты сообщений
PgUp / PgDn, клик мышью по ростеру переход по чатам — открывает сразу; черновик ввода остаётся за чатом
Ctrl+Tab / Ctrl+Shift+Tab то же листание чатов — в терминалах, отличающих их от Tab (kitty, foot, WezTerm, xterm с modifyOtherKeys); в прочих Ctrl+Tab приходит как обычный Tab
Enter (пустой ввод) открыть выбранный чат (после /open)
Enter compose-режим (умолчание): перенос строки, два Enter подряд — отправить; send-режим: отправить (меняется в /conf). Команды /… выполняются первым Enter всегда. С курсором на сообщении — ответ цитатой
Alt+Enter, Ctrl+J перенос строки всегда
↑ / ↓ при многострочном вводе курсор по строкам текста (чат листается колесом)
Ctrl+V картинка из буфера обмена: экран превью ~70% терминала, Enter — отправить, Esc — отмена
Ctrl+↑ / Ctrl+↓ курсор по сообщениям (для цитаты и копии)
Shift+↑ / Shift+↓ растянуть выделение сообщений
Ctrl+Space отметить/снять сообщение (Shift+Space терминал не отличает от пробела)
Ctrl+C скопировать выделенное: локально wl-copy/xclip/xsel, параллельно OSC 52 (сквозь ssh и tmux); в копию входят цитата реплая (> автор: …) и описание файла; уровень copy_media в /conf: 0 — только описание, 1 — с хешем (умолчание), 2 — ещё и секстантная мозаика картинки; без выделения: дважды подряд — выход
Ctrl+Q, /quit выход
← / → · Home / End (Ctrl-A/E) курсор в строке ввода
Ctrl+← / Ctrl+→ на слово влево/вправо
Ctrl+Backspace (Ctrl-W, Alt+Backspace) стереть слово
Delete удалить символ под курсором
Esc снять выделение; затем — очистить ввод
Ctrl+S сменить свой статус (подставляет /status [away|dnd|xa|chat|online] текст; статус сохраняется и переприменяется при reconnect)
Ctrl+B / Ctrl+Y / Ctrl+U маркеры разметки XEP-0393: *жирный* / _курсив_ / ~зачёркнутый~ (подчёркивания в XMPP-разметке нет; Ctrl+I не используется — в терминале это Tab)
Ctrl+K стереть весь ввод
F12 отдать/забрать мышь: выкл — родное выделение терминала без Shift (как в bash), вкл — наши клики и колесо

Ростер — три строки на чат: имя (+непрочитанные), превью последнего сообщения, время/дата у правого края с галками доставки справа от них. Слева аватарка 6×3 клетки: настоящая картинка контакта (avatar metadata/data, PNG/JPEG через stb_image) секстантами 12×9 «пикселей» — truecolor или 256 цветов; без аватарки — квадрат цвета XEP-0392 с инициалом. Работает в любом юникодном терминале, включая ssh.

Шапка открытого чата — тоже три строки с той же аватаркой: имя; состояние связи и N devices (1:1, из presence resources) или N participants (конференция, из muc occupants); третья строка — по приоритету: «печатает…» (XEP-0085, в комнате — с никами), статусный текст собеседника, last seen M minutes ago (idle по XEP-0319). Свои «печатает…» клиент шлёт с троттлингом 4 с, пустеющий ввод отзывает их.

Команды: /account add jid@сервер пароль — добавить аккаунт, /account list; /add jid — заявка в друзья (ростер); /open jid — открыть чат; /conf — экран настроек: свои (высота превью, режим Enter; tui.conf) и все настройки backend'а (модуль setting: числа крутятся ←/→, строки правятся через Enter→/set ключ значение); /omemo — свой device id и отпечаток; /quit. Поле ввода многострочное, 2–8 строк, растёт с текстом.

Сообщения: исходящие прижаты вправо (мета 🔒 02:37 ✓✓ у нижней строки текста, галки зелёные только у прочитанных — backend шлёт это как remote_status: "read"), входящие слева. Картинки-вложения показываются секстантным превью (высота настраивается в /conf), под ними строка ⤓ файл — клик или Ctrl+O открывает (xdg-open; AIT_OPENER переопределяет), Ctrl+P копирует локальный путь в буфер — удобно по ssh.

OMEMO при этом уже работает: libtacky включает его сам, когда у собеседника есть ключи; зашифрованные строки помечены 🔒. «Прочитано» синхронизируется на другие устройства (XEP-0490/0333) при открытии чата.

Устройство

src/json.c   разбор/сборка JSON под wire-протокол (без внешних библиотек)
src/wire.c   подпроцесс backend'а + кадрирование
src/main.c   состояние (аккаунт, чаты, лента) + ncurses + event loop (poll)
mock/mockd.py        мок backend'а: история, эхо, события — для разработки без сборки libtacky
test/test_wire.c     json + кадрирование против мока
test/ui_smoke.py     клиент в pty против мока: /add → список → история → отправка → эхо
test/backend_smoke.py, test/real_ui_smoke.py   то же с настоящим tackyd-json

Под сообщением видны реакции («👍2», своя — в скобках) и пометка ✎ edited; прокрутка к верху ленты подгружает старые страницы истории; сообщения в чужих чатах дают звонок терминала и notify-send (режим в /conf); открытый чат объявляется backend'у через chats show/hide, так что по нему уведомлений нет. Замок — текстовый 🔒︎ (U+FE0E).

Дальше по плану: поиск (message search), переход по цитате (goto_reply), день-разделители, доверие OMEMO (trust_list/accept_identity), экран аккаунтов и мультиаккаунт; затем kitty/sixel-уровни картинок и аудиозвонки (сборка backend'а с MEDIA_BACKEND=datachannel).