Как я создал свой GoGui для установщика SalixEDA

🔧 Для разработчиков
Эта статья — техническое путешествие в мир создания GUI на Go. Если вы просто пользователь SalixEDA и хотите узнать о новых возможностях программы — возможно, эта статья покажется вам слишком глубокой. Но если вы разработчик или просто интересуетесь «как это сделано» — добро пожаловать!

Как я создал свой GoGui для установщика SalixEDA

В процессе ребрендинга программа SalixEDA получила не только новое имя и новый сайт. Я задумал полностью заменить систему установки.

Спойлер: в итоге я написал свой собственный GUI-фреймворк на Go с нуля, используя C-биндинги и шрифт, вшитый в бинарник. Но обо всём по порядку.

Почему старая система установки перестала устраивать

Прежняя система была построена на прекрасном QtInstallerFramework. У неё масса достоинств:

  • Кроссплатформенность (Windows и Linux «из коробки»)
  • Мультиязычность (язык установщика зависит от настроек пользователя)
  • Поддержка офлайн и онлайн установки
  • Автоматическая программа удаления

Система выше всяких похвал! Но есть одно «но».

В QtInstallerFramework нет встроенной проверки обновлений. Подразумевается, что этим должна заниматься сама установленная программа, которая при необходимости вызывает программу обновления.

Мне же хотелось, чтобы проверкой обновлений занимался именно установщик. Вот какая схема родилась:

  1. Основная программа никогда не запускается самостоятельно.
  2. Всегда запускается установщик-обновлятор.
  3. Он периодически проверяет сервер на наличие обновлений.
  4. Если обновление есть — предлагает пользователю установить его.
  5. После обновления — запускает актуальную версию программы.
  6. Если пользователь отказывается — запускает текущую версию.

Помимо проверки обновлений, у этого подхода есть ещё один плюс: для Linux требуется установить переменные окружения, необходимые для работы основной программы. Эту задачу тоже удобно возложить на установщик-обновлятор-запускальщик.

А ещё туда же можно добавить функцию удаления — чтобы не городить отдельную программу с GUI. Установщик и так прекрасно осведомлён обо всех установленных файлах.

Почему Go?

Примерно в это же время у меня случился очередной подход к языку Go. И он мне очень зашёл в качестве решения сервисных задач. На нём уже были сделаны глобальная библиотека компонентов и приватные облака библиотек. А кроме того у него есть прекрасная функция строить приложения для разных ОС.

Я подумал: «А что если сделать этот установщик-обновлятор-запускальщик на Go?» И оперативно взялся за реализацию.

Первая попытка: Fyne

Для GUI я предварительно выбрал Fyne — он по сути единственная кроссплатформенная GUI-библиотека для Go (хотя и с оговорками: для Linux требуется установка библиотек поддержки OpenGL, которые с высокой долей вероятности уже установлены у пользователя).

С помощью вайб-кодинга быстро написал ядро и интерфейс, проверил на Linux. Всё работало. Дело двигалось к завершению и я решил проверить как это все выглядит на windows. И тут началось... Сначала танцы с бубном, чтобы откомпилировать в принципе. Это победить удалось, но пришла другая беда: приложение падало при запуске. И здесь я завис на долгие семь дней. Ничего не помогало, а самое плохое, что диагностика не помогала - приложение падало еще до какой-либо диагностики.

Отладка и неожиданный поворот

Тогда я решил попробовать наипростейший вариант — создать пустое окно Windows только системными средствами (WinAPI). Здесь тоже ждала неудача — приложение также падало при запуске. Однако появилась возможность диагностики.

Быстро нашёл причину падения — и окно заработало.

У меня есть определённый опыт работы с QML, и я решил попробовать построить интерфейс «ручным» способом в этом простейшем окне. Тем более что для установщика интерфейс не очень сложный.

Неожиданно оказалось, что вся система получается:

  • Компактной
  • Без внешних зависимостей
  • Простой и понятной

Красота, конечно, пострадала, но для данного применения красота вторична. Всё-таки это не основное приложение, а вспомогательное. Кроме того, если потратить больше времени, можно будет добиться и красоты.

Самое печальное, что пришлось отказаться от диалогового выбора каталога для установки. Причина - в отсутствии простого доступа к нативному диалогу. И, если в windows все это решаем, то в Linux все плохо. Конечно, без проблем можно сделать этот диалоговый выбор руками, но я пока не стал этого делать, посчитав, что альтернативный каталог для установки выбирают очень не многие.

Linux: первый опыт X11

Если с окнами Windows мне всё было понятно (есть опыт программирования под WinAPI), то с Linux всё было впервые. Я никогда не программировал окно Linux (X11) только с помощью системных вызовов.

Здесь на помощь опять пришёл вайб-кодинг. Оказалось, что всё не так страшно.

Архитектура получившейся GoGui

В результате родилась «библиотека» GUI для Go:

  • Системно-зависимые части — два маленьких C-файла, которые подключаются к сборке Go-программы в зависимости от целевой системы.
  • Низкоуровневая графика — тоже на C, но уже общая для Windows и Linux.
  • Иерархия объектов (как в QML) — реализована на Go, а для непосредственного рисования вызываются C-функции.

Самое сложное: текст и шрифты

Самым «тухлым» с точки зрения рисования оказался текст. Требовалась поддержка юникода для мультиязычного интерфейса.

Быстро обнаружилось, что системы шрифтов в Windows и Linux не очень похожи. Чтобы избавиться от необходимости требовать установки сторонних программ или библиотек, я решил внедрить шрифт прямо в установщик.

Взял TTF-файл готового шрифта и вставил его непосредственно в код GUI. Оставалась необходимость парсить этот файл и рисовать знаки.

Здесь на выручку пришла stb_truetype.h от Sean Barrett, за что ему огромное человеческое СПАСИБО! Без этой библиотеки проект не удалось бы завершить.

Шлифовка и финальный результат

После этого была выполнена некоторая шлифовка:

  • Нивелированы различия между операционными системами
  • Устранены артефакты рисования
  • Добавлена функция удаления (чтобы не городить отдельную программу с GUI)

В настоящее время эта GoGui используется для программы установщик-обновлятор-запускальщик-удалятор. Всё в одном флаконе!

Мысли о будущем

Была даже мысль выложить эту GUI-библиотеку как простое решение для Go-программ. Пока решил не возиться, хотя возможность построить на Linux приложения и для Linux, и для Windows (а в перспективе и для Android) — впечатляет.

Возможно, когда-нибудь я вернусь к этой идее. А пока библиотека живёт внутри установщика SalixEDA и отлично справляется со своей задачей.

Итог: что я вынес из этого опыта

  • Не бойтесь писать «свои велосипеды». Иногда это быстрее, чем разбираться с чужими багами.
  • Вайб-кодинг работает. Особенно когда у вас есть чёткое понимание, что нужно сделать.
  • stb_truetype.h — это магия. Огромное спасибо Sean Barrett.
  • Go + C — отличная связка для системного программирования с высокоуровневой логикой.

Исходный код — открыт

Программа установщика (и вся GoGui вместе с ней) находится в основном репозитории SalixEDA на GitHub:

https://github.com/SalixEDA/SalixEDA.git

Исходный код установщика можно найти в поддиректории installer/. Приглашаю к изучению, форкам и улучшениям!