f4, сайд‑проекты и помощь экосистеме Go
- воскресенье, 6 сентября 2026 г. в 00:00:08
В процессе работы над f4, современной репродукцией Far Manager на Go, мне пришлось столкнуться с кучей ограничений экосистемы и написать массу вспомогательных библиотек, добавив недостающие кусочки паззла. Встроенный mp3 плеер f4 (да, он уже есть, пусть и на стадии PoC, вызывается по Ctrl+Shft+M) на скрине не просто так — это чуть ли не единственный плеер для *nix, нормально работающий с русскими кодировками id3v1 тэгов, для чего в том числе как раз потребовалась отдельная библиотека. Обзор всех наработок f4, которыми вы можете воспроизоваться в своих Go проектах — в этой статье.

Сразу оговорюсь: в этой статье не будет обзора на мои библиотеки zip, tar и zipper, на которых в f4 построена работа с архивами. Каждая из них наполнена возможностями настолько, что тянет на отдельную статью, которые тоже когда‑нибудь обязательно будут написаны! Если совсем кратко, маниакальное распареллеливание и оптимизация + хранение любых метаданных платформы в любых форматах, вплоть до NTFS ACL в.tar.xz + индексация, дающая произвольный доступ в tar и solid архивах. А, и ещё это всё совместимо по API со штатными либами для zip и tar — возможна прозрачная замена.
Также я сознательно не включил в статью библиотеки vtinput (ввод в терминале с поддержкой любых сочетаний клавиш) и vtui (современный кроссплатформенный «Turbo Vision» на Go с рендерингом и в консоль и в графику, сделанный по стандартам UX Far Manager). Тоже тянет на отдельную статью, оставлю на будущее.
А теперь — обзор на всё остальное!
Начнём как раз с id3v1 тэгов в mp3 файлах, раз уж у нас на вводном скрине именно плеер. Одна из главных причин проблем с UX в *nix экосистеме — то, что разработчики сдаются, не найдя доступных на Windows API. Простейший пример: в Windows у вас есть понятие «системная ANSI кодировка» (win) и «системная OEM кодировка» (dos), они привязаны к текущему языку системы. А вот в Linux или на Mac таких понятий нет. Что делают программы, столкнувшись с необходимостью как‑то превратить однобайтный текст в Юникод (например, в заголовке zip архива или id3v1 тэге в mp3 файле)? Вариантов несколько: статистические эвристики (unar) или использование захардкоженной кодировки (zip/unzip), в лучшем случае — с возможностью ручного оверрайда.
В итоге люди до сих пор, в 2026 году, видят всем знакомые крякозябры. Я решил, что так дальше продолжаться не может.
Прежде, чем начать решать проблему, я задал себе вопрос: почему 7zip в Wine показывает кодировки в старых zip архивах правильно? Что там за магия, как ему это удаётся? Полез в код Wine и обнаружил, что он просто смотрит в системную локаль, и под неё подбирает по встроенной таблице конкретные ANSI и OEM кодировки, которые отдаются приложению. Воспроизвести эту логику у себя не стоило больших усилий — так появилась библиотека localecp. Она смотрит в локаль системы и возвращает вам конкретные кодировки DOS и WIN, или, если хотите, готовые энкодеры и декодеры для них.
Логичное продолжение localecp: практические применения. Первая определяет кодировку в legacy zip архивах, где она однобайтная, вторая — парсит id3 тэги, разбирая однобайтные v1 тем же способом. Вроде бы мелочь, но даже некоторые поддерживаемые версии Windows всё ещё пишут заголовки зипов в однобайтной кодировке! Так что актуальность задачи с нами ещё надолго.
go2xp
Один из самых классных инструментов. Пока ещё в разработке, но 90% готово и работает, на днях с коллегой допилим, так что решил добавить в статью и его тоже. Позволит запускать на XP+ бинарники современного Go 1.26.
Аналогов нет: есть Go 1.26 для Win7 и Go 1.24 для XP. Но мне был нужен именно 1.26 на XP, и я был совершенно не готов поддерживать свой форк компилятора.
Решение придумалось элегантное: реализовать все отсутствующие в XP вызовы прямо на Go, а потом патчить.exe, заменяя нативные вызовы на встроенные, а поле требуемой ОС в заголовке — на подходящее.
Что уже готово и работает: загрузчик XP принимает измененную таблицу импорта и заголовок NT 5.1, рантайм поднимается, f4 доходит до main.main и падает на первом ленивом импорте. Тут надо дошлифовывать уже на самой XP (я работаю с Linux Mint и писал по имеющимся референсам), коллега этим уже занимается.
И нет, это не только just for fun (хотя я обожаю такие штуки): запрос на поддержку XP неоднократно поступал от пользователей в чатах Far и f4.
Ещё одна мелочь, которая на самом деле очень важна: кроссплатформенная поддержка буфера обмена. Вам больше не нужно разбираться, иксы у пользователя, Wayland, Винда или Мак. А ещё в комплекте утилитка командной строки, 1:1 совместимая по параметрам с xclip, только работающая вообще везде.
Форк FFI‑библиотеки goffi из проекта gogpu (полный графический стек на go, сама по себе очень интересная история, я использую их для рендеринга в графику на Маках, например), доработанный под мои нужды. Ключевая особенность: не требует отдельных сборок для glibc и musl дистрибутивов, FFI будет работать в одном общем бинарнике и там и там. Идея взята из моего проекта static everywhere, позволяющего собирать полностью портативные между дистрибутивами Linux бинарники без геморроя с контейнерами и флетпаками — этот проект (таким, конечно, нельзя заниматься вне Хогвартса) тоже требует своей отдельной статьи.
И, раз уж мы коснулись темы FFI, ещё одна очень полезная библиотечка. Реализует API самой известной FFI библиотеки в Go, purego, поверх существенно более быстрого goffi. Бесплатным бонусом — возможность использования обеих FFI библиотек в одном проекте (в моём случае этого требуют зависимости), потому что сами они скажем так не очень тщательно заботятся о совместимости друг с другом. А, и ещё она в отличие от оригинала работает на Apple Silicon, который purego даже не планирует (что? да!) поддерживать. И все эти возможности — ценой одной replace директивы в go.mod.
Одна из проблем, которую мне нужно было решить — как дать доступ плагинам к API внешней системы. В самом go есть отличные библиотеки для FFI (кстати, я развиваю доработанный форк одной из них, об этом ниже), но как дать FFI, например, плагину на wasm? Для решения этой задачки я сделал небольшую библиотеку, пробрасывающую FFI куда угодно, в тот же wasm, например. Пригодится, если вашей программе нужны мощные плагины!
keytrans
А зачем нам вообще понадобились FFI в файловом менеджере? Штука в том, что f4 умеет рендериться не только в консоль, но и в графику. И вот тут начинается веселье: окей, реализация протокола иксов для Go существует (xkb‑go). А вот полноценно работающей с русским реализации libxkbcommon — нет! У меня порядочно времени ушло на то, чтобы обойти это, зато теперь иксовый бекенд отрисовки запустится на чём угодно вплоть до NetBSD и Solaris'а. keytrans — то, что позволит вам вводить там текст на русском. Используется шесть (sic!) способов добратья до нужной информации, есть варианты как с FFI, так и без. Честный graceful degradation: input methods где‑то отвалятся, но набрать «привет» вы сможете везде.
Часть философии проекта — настолько уютный UX, насколько это позволяют законы физики. В итоге в какой‑то момент я задал себе вопрос: а почему я в 2026 использую лейауты диалогов на якорях, технологию времён Turbo Vision? Почему бы не сделать на констрейнтах, как у Apple?
Результат — go‑порт библиотеки kiwi, реализации алгоритма Cassowary, на котором у Эппла как раз и работает auto layout. Но хвастать в эпоху LLM переписыванием библиотеки с TypeScript на Go было бы немного странно — задача элементарная. Фишка здесь в другом, я изобрёл для TUI интерфейсов дискретный cassowary. Такого точно никто никогда не делал.
Сложность дискретного cassowary в том же, в чём была сложность рендеринга шрифтов в эпоху 800×600 мониторов: в жёсткой сетке, в которую надо укладываться, а это не всегда получается красиво. Но зачем изобретать велосипед? Возьмём готовое у шрифтовиков, решавших ровно ту же проблему! В итоге моя kiwi‑go поддерживает аж два алгоритма хинтинга — с байткодом, как у TrueType, и на эвристиках, как во FreeType. Можно делать буквально pixel perfect TUI интерфейсы.
winescape
Среди всего прочего в мои цели входит идеальный UX f4 в ReactOS и Wine. Причин много: требование более чистого кода, удобство тестирования в CI, а также возможность таскать с собой на флешке один бинарник, который заработает даже в Haiku. Но Wine это компромисс по скорости а ещё дурацкие Z:\bin вместо /bin.
Но зачем все эти ограничения процессу, который и так живёт в POSIX? Почему бы не дёргать нативные либы системы напрямую? Окей, разное соглашение о вызовах. А мы напишем ассемблерные трамплины (точный перевод trampoline — батут, но никто так по‑русски не пишет), делающие конверсию! В результате нативный *nix опыт, вы даже не заметите, что работаете из Wine. Поддерживается весь базовый спектр API, нужных файловому менеджеру, вплоть до сигналов и сети.
Кстати, в процессе общения с сотрудником MS в одном из тикетов Windows Terminal (я регулярно таскаю им фидбек) выяснилось, что он сделал в качестве хобби проекта практически то же самое — чтобы реализовать conhost/ConPTY для Wine поверх нативных pty системы.
На этом пока всё! Надеюсь, что‑нибудь из моих наработок пригодится вам в своих проектах. Приходите обсудить в чатик по f4 — мы всегда рады новичкам!