Pull to refresh

Операционная система Haiku: портирование приложений и создание пакетов

Reading time44 min
Views24K
Осенью этого года, спустя 6 лет разработки, вышла первая бета-версия «R1/beta1» операционной системы Haiku. Я давно слежу за этим интересным проектом, который нацелен на воссоздание и последующее развитие существовавшей в 1994-2000 годах системы BeOS. Поэтому, как только на новостных IT-сайтах я увидел новость о выходе бета-версии Haiku, я незамедлительно решил посмотреть, что же было добавлено в этот долгожданный релиз. После установки системы в виртуальную машину VirtualBox и небольшого ознакомления с её основной функциональностью, я подумал, что было бы неплохо немного помочь OpenSource-сообществу, которое сегодня развивает эту операционную систему. Начать я решил с того, в чём у меня накопился небольшой опыт: с портирования некоторых игровых проектов.


Рабочий стол операционной системы Haiku.

Позже я попытался доработать некоторые уже существующие приложения и библиотеки. Именно этой моей небольшой деятельности в различных репозиториях с открытым исходным кодом и будет посвящена эта статья. В ней я последовательно опишу те проблемы, с которыми столкнулся и расскажу про методы их решения. Большинство патчей, которые были сделаны в процессе этой работы, я попытался отправить в upstream существующих проектов, дабы обеспечить в них поддержку Haiku и заинтересовать их разработчиков существованием альтернативных операционных систем.

Операционная система Haiku использует гибридное ядро, которое представляет собой реализацию микроядерной архитектуры с возможностью динамической подгрузки необходимых модулей. Оно базируется на форке ядра NewOS, которое было разработано бывшим инженером Be Inc., Travis'ом Geiselbrecht'ом. Сегодня этот разработчик работает в Google над ядром, которое называется Zircon, для новой операционной системы Google Fuchsia, но это уже другая история. Итак, поскольку разработчики Haiku декларируют бинарную совместимость с BeOS, то они вынуждены поддерживать не две привычных всем архитектурных ветки, а три: x86_64, x86 и x86_gcc2. Последняя архитектура представляет собой груз совместимости с компилятором старой версии GCC 2.95. Именно благодаря ей имеется возможность запуска приложений, написанных для оригинальной операционной системы BeOS. К сожалению, из-за этого груза совместимости, разработчики Haiku не могут использовать современные возможности языка программирования C++ в системных API. Тем не менее, установочные образы они подготавливают только для двух архитектур: x86_64 и x86. Всё дело в том, что дистрибутив Haiku для x86 является гибридным: несмотря на то, что все системные компоненты собраны под x86_gcc2 для обеспечения бинарной совместимости, пользователю предоставляется возможность установки или сборки любых современных приложений, которые были сделаны с расчётом на современные компиляторы и архитектуру x86. Дистрибутив Haiku для архитектуры x86_64 является полностью 64-битным и не имеет возможности запуска 32-битных приложений BeOS и Haiku. Однако, совместимость на уровне API имеется, поэтому если у вас есть на руках исходный код приложения под BeOS или Haiku x86, вы без проблем сможете скомпилировать его под Haiku x86_64 и всё должно работать. Образ операционной системы под архитектуру x86_64 рекомендуется для установки на реальное железо, если вам не требуется поддержка каких-либо специфичных приложений BeOS или 32-битных приложений Haiku.

Стоит сказать, что в этой операционной системе имеется частичная поддержка стандарта POSIX. Этот фундамент делает её родственной UNIX-like системам и позволяет легко переносить их программное обеспечение. Основным языком программирования является C++, он активно используется, поскольку публичные API у Haiku в основном преследуют объектно-ориентированную парадигму программирования. Тем не менее, никто не запрещает использовать и язык программирования C, только для большинства случаев придётся писать соответствующие прослойки совместимости. Программный интерфейс операционной системы сгруппирован в отдельные системные фреймворки, которые отвечают за ту или иную возможность, например, за интерфейс или поддержку сети. Это немного похоже на то, что имеется в macOS или во фреймворке Qt. Обязательно нужно отметить, что эта операционная система является однопользовательской, хотя некоторые подвижки в сторону обеспечения многопользовательского режима работы у разработчиков Haiku имеются.

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


Продвинутое управление окнами в операционной системе Haiku: поддержка тайлинга и вкладок.

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

Я не буду писать в этой статье полный обзор всех особенностей и возможностей Haiku, так как те, кому это интересно, легко смогут самостоятельно найти нужную информацию в интернете.

Содержание:


1. Пакеты и репозитории в Haiku
2. Первые шаги: портирование игры Adamant Armor Affection Adventure
3. Доработка существующего порта NXEngine (Cave Story)
4. Портирование игры Gish
5. Проект BeGameLauncher, позволяющий быстро создавать лаунчеры для игр
6. Портирование Xash3D: легендарная игра Half-Life и официальные дополнения
7. Портирование двух частей игры Serious Sam: The First Encounter и The Second Encounter
8. Портирование игры Вангеры (Vangers)
9. Реализация диалогов в библиотеке SDL2 для Haiku
10. Портирование моего форка программы Cool Reader
11. Доработка программы KeymapSwitcher
12. Заключение

1. Пакеты и репозитории в Haiku


По сравнению с оригинальной BeOS, в Haiku появилось значимое нововведение: система управления пакетами, которая включает в себя различные инструменты для получения и установки программного обеспечения из различных источников. Такими источниками могут служить официальные репозитории Haiku и HaikuPorts, неофициальные репозитории и просто отдельные и специально подготовленные HPKG-пакеты. Подобные возможности по установке и обновлению ПО давно известны в мире Unix-like операционных систем, теперь же вся их мощь и удобство успешно добрались и до Haiku, что не может не радовать рядовых пользователей этой операционной системы. Благодаря выстроенной вокруг пакетного менеджера инфраструктуры, теперь любой разработчик может легко портировать новое или доработать уже существующее приложение с открытым исходным кодом, затем добавить результаты своего труда в репозиторий портов программного обеспечения HaikuPorts, после чего они станут доступны всем пользователям Haiku. В итоге получившаяся экосистема напоминает таковую у операционных систем macOS с их Homebrew, FreeBSD с их портами, Windows с MSYS2 или Arch Linux c его AUR'ом.

Инструмент для сборки пакетов и портирования программного обеспечения, называемый HaikuPorter, поставляется отдельно от операционной системы и устанавливается по небольшому мануалу, расположенному в репозитории на GitHub. После установки этой утилиты с того же GitHub'а скачивается всё дерево рецептов, над которым и работает разработчик. Рецепт представляет собой обычный Shell-скрипт с инструкциями, по которым HaikuPorter и будет собирать требуемый HPKG-пакет. Примечательно, что сам инструмент написан на языке программирования Python 2, тесно взаимодействует со существующей системой управления пакетами, а для фиксации изменений исходного кода ПО и генерации набора патчей, внутри себя использует стандартный инструмент — Git. Именно благодаря подобному стеку технологий делать рецепты для сборки HPKG-пакетов и наборы патчей к ПО в виде patchset-файлов очень удобно и просто. В большинстве случаев мне пришлось использовать всего три команды при работе с HaikuPorter'ом:

alias hp="haikuporter -S -j4 --get-dependencies --no-source-packages"

hp libsdl2
hp libsdl2 -c
hp libsdl2 -e

Первая команда просто собирает выбранный пакет, вторая команда очищает директорию сборки, а третья создаёт или обновляет набор патчей в соответствии с вашими изменениями, которые были зафиксированы в Git-репозитории рабочей директории посредством коммитов.

Таким образом, чтобы опубликовать какой-либо пакет в репозиторий HaikuPorts и сделать его доступным для всех пользователей Haiku, разработчик должен установить у себя HaikuPorter, развернуть дерево рецептов, локально собрать HPKG-пакет и протестировать его, затем сделать коммит в свой форк дерева рецептов, после чего оформить Pull request на GitHub'е. Опубликованную работу должны рассмотреть разработчики Haiku, после чего они принимают решение влить ваши изменения в репозиторий или же отправить их на доработку. Если изменения приняты, то такой же HaikuPorter, установленный на сборочном сервере, удалённо соберёт пакет и автоматически опубликует его в репозиторий.

В бета-версию «R1/beta1» операционной системы Haiku была добавлена специальная программа HaikuDepot, которая позволяет работать с пакетами и репозиториями через графический интерфейс пользователя, а не через консольные команды в терминале.


Программа HaikuDepot, запущенная в операционной системе Haiku.

Благодаря этому инструменту неискушённые и начинающие пользователи Haiku могут удобно управлять своей пакетной базой. Стоит отметить, что это приложение не просто является GUI-оболочкой над существующим пакетным менеджером, но и реализует дополнительную функциональность. Например, авторизированные пользователи могут выставлять оценки и писать отзывы к доступным для установки пакетам. Кроме того, у HaikuDepot имеется специальный сайт Haiku Depot Web, позволяющий просматривать изменения пакетной базы в интернете или скачивать отдельные HPKG-пакеты.

<< Перейти к содержанию

2. Первые шаги: портирование игры Adamant Armor Affection Adventure


После того, как я ознакомился с функциональностью операционной системы в виртуальной машине VirtualBox, я решил оценить работу библиотеки SDL2 в ней и портировать на Haiku игру Adamant Armor Affection Adventure, о переносе которой на платформу Android я писал ранее. Сборка программы не потребовала каких-либо изменений исходного кода, я просто установил из репозитория все нужные инструменты, библиотеки, их заголовочные файлы и выполнил следующее:

cmake -DCMAKE_BUILD_TYPE=Release -DGLES=off -DANDROID=off -DCMAKE_C_FLAGS="-D__linux__" -DSDL2_INCLUDE_DIR=`finddir B_SYSTEM_HEADERS_DIRECTORY` -DSDL2_MIXER_INCLUDE_DIR=`finddir B_SYSTEM_HEADERS_DIRECTORY` ../aaaa/src/main/cpp
cmake --build .

Поскольку в Haiku имеется POSIX, то дефайны -D__linux__ или -D__unix__ разрешают многие проблемы, связанные с определением платформы. Однако, стоит заметить, что лучше всего отказаться от их использования и реализовывать поддержку Haiku в исходном коде проекта, если существуют подобные проблемы со сборкой. Вызов системной утилиты finddir с определённым аргументом позволяет получить корректный путь к заголовочным файлам для различных архитектур.

Итак, выполнив команды выше, я скомпилировал исполняемый файл, который прекрасно запускался, а игра отлично работала. Я подумал, что было бы классно подготовить самодостаточный HPKG-пакет с игрой и для этого углубился в интернет на поиски необходимой мне информации. Тогда я не знал ни о каких удобных инструментах для портирования программного обеспечения, вроде HaikuPorter'а, о котором я написал в разделе выше, поэтому для осуществления своей цели я решил схитрить и разобрать какой-нибудь системный пакет, чтобы посмотреть как он устроен внутри и сделать по аналогии.

На просторах интернета я нашёл желаемую информацию, после чего распаковал случайный системный пакет с помощью встроенного в местный файловый менеджер архиватора Expander, нашёл файл .PackageInfo, отредактировал его и, в соответствии со структурой своего приложения, подменил файлы. Затем я просто выполнил команды для сборки HPKG-пакета и его установки в систему:

package create -C AAAA/ aaaa.pkg
pkgman install aaaa.pkg

К сожалению, запуск игры из меню «Applications» не увенчался успехом. Запустив исполняемый файл в терминале, я получил ошибку, говорившую о невозможности найти файлы данных, которые были необходимы для запуска и работы приложения. При этом, если в терминале перейти в директорию пакета приложения, то всё запускалось нормально. Это натолкнуло меня на мысль о том, что при запуске игры из меню нужно делать принудительное изменение директории приложения. Подобное можно сделать либо Shell-скриптом, либо изменением исходников игры. Я выбрал второй вариант и добавил нечто похожее на этот код:

#ifdef __HAIKU__
    // To make it able to start from Deskbar
    chdir(dirname(argv[0]));
#endif

В самое начало стартовой функции main(), что полностью решило данную проблему и пакет получился работоспособным. В комментариях к новости про релиз бета-версии Haiku на сайте Linux.org.ru я скинул ссылку на мой собранный пакет и попросил кого-нибудь направить меня в какие-нибудь активные сообщества пользователей этой операционной системы, после чего лёг спать.


Порт игры Adamant Armor Affection Adventure, запущенный в операционной системе Haiku.

На утро мне на e-mail написал человек, использующий ник 3dEyes. Как позже оказалось, за этим именем скрывался Герасим Троеглазов — один из активных разработчиков Haiku и автор порта фреймворка Qt для этой операционной системы. Он показал мне репозиторий HaikuPorts и рассказал как пользоваться утилитой HaikuPorter. Кроме того, он написал рецепт для сборки HPKG-пакета игры Adamant Armor Affection Adventure и добавил её в HaikuDepot.

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

Кроме того, я узнал, что в Haiku нет аппаратного ускорения 3D-графики и тот же OpenGL отрисовывается программно с помощью мощностей CPU. Для тяжёлых графических приложений это, конечно, никуда не годится, но для старых игр этого более чем достаточно. Я даже решил специально проверить пакет игры и установил Haiku на свой старый ноутбук, то есть на реальное железо. На моё удивление, картинка Adamant Armor Affection Adventure рендерилась настолько быстро, что если бы мне не сказали про отсутствие аппаратного ускорения, я бы и не заметил того, что рендеринг осуществляется моим процессором.

Исходный код проекта: https://github.com/EXL/AdamantArmorAffectionAdventure

Ручное создание HPKG-пакетов я отложил до лучших времён и полностью перешёл на использование инструмента HaikuPorter и написание рецептов. Но иногда бывают ситуации, когда требуется ручная пересборка пакета. Например, если HaikuPorter выставил в файле .PackageInfo слишком высокую «ночную» версию Haiku, а пакет нужно протестировать на релизной версии операционной системы. Стоит отметить, что именно благодаря отзывчивости и опыту Герасима я смог разобраться во многих тонкостях создания пакетов для операционной системы Haiku и продолжил свою работу далее.

<< Перейти к содержанию

3. Доработка существующего порта NXEngine (Cave Story)


Я был несказанно удивлён, обнаружив в репозитории HaikuPorts рецепт, который ссылался на мой форк движка NXEngine для игры Cave Story, который я очень давно разбирал в своём блоге. Рецепт и патчи подготовил разработчик по имени Zoltán Mizsei, использующий ник extrowerk и являющийся активным мейнтейнером множества пакетов для Haiku.

Поверхностный анализ, установка пакета и запуск приложения выявил те же проблемы, которые я описывал в предыдущем разделе этой статьи: сохранения игры не работали, настройки тоже не сохранялись, кроме того у пакета не было оригинальной иконки. Я решил исправить эти недочёты и начал работать над патчем, сперва интегрировав все наработки extrowerk'а. Я написал оригинальный Makefile для операционной системы Haiku и поправил запись и сохранение различных пользовательских данных.


Порт игры Cave Story на основе движка NXEngine, запущенный в операционной системе Haiku.

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

#!/bin/bash
if [[ `locale -l` == ru* ]] ;
then
    EXE="`finddir B_SYSTEM_APPS_DIRECTORY`/NXEngine/RUS/Cave Story"
else
    EXE="`finddir B_SYSTEM_APPS_DIRECTORY`/NXEngine/ENG/Cave Story"
fi
"$EXE" $@

Этот скрипт запускается при выборе пункта игры в меню «Applications» и определяет текущую системную локаль. В том случае, если пользователь выбрал русский язык в качестве системного, запустится русская версия игры, а во всех остальных случаях — английская.

А вот с созданием оригинальной иконки для приложения пришлось изрядно повозиться. Дело в том, что в операционной системе Haiku разрешены только векторные иконки специального формата HVIF, которые устанавливаются в качестве атрибутов файловой системы Be File System. В официальной документации существует два больших мануала, посвящённых созданию собственных иконок для приложений: первый мануал описывает стилистику рисовки и дизайн, а второй мануал подробно рассказывает как пользоваться системной программой Icon-O-Matic, предназначенной для создания иконок.

Icon-O-Matic позволяет импортировать простейшие SVG-файлы и экспортировать получившуюся иконку в необходимый для HaikuPorter'а формат, называемый HVIF RDef и представляющий собой тот же HVIF, но преобразованный в текстовый вид. RDef-файлы могут содержать не только изображения, но и дополнительную информацию, например, версию приложения и его описание. Чем-то эти файлы напоминают RES-файлы, используемые в Windows. Следующие команды в рецепте компилируют RDef-файлы и устанавливают получившийся результат в специальные атрибуты:

rc nxengine-launcher.rdef
resattr -o "$appsDir/NXEngine/Cave Story" nxengine-launcher.rsrc

addResourcesToBinaries $sourceDir/build/nxengine-rus.rdef "$appsDir/NXEngine/RUS/Cave Story"

Кроме того, в рецептах определена функция addResourcesToBinaries, позволяющая автоматизировать эту работу. Проблема с программой Icon-O-Matic имеется одна, но очень серьёзная: те SVG-файлы, которые сохраняет популярный векторный редактор Inkscape, либо не открываются, либо импортируются без поддержки некоторых необходимых возможностей, например, градиентов. Поэтому приключенческий квест с конвертированием растровых изображений в векторные через использование различных платных и бесплатных online- и offline-конверторов, а потом открытием получившихся SVG-файлов в программе Icon-O-Matic, я с треском провалил. Позже я решил проблему открытия SVG-файлов и нашёл обходной путь, но об этом я напишу ниже. А пока я решил воспользоваться стандартными возможностями программы Icon-O-Matic и нарисовать иконку самостоятельно. Спустя полчаса работы по усердному копированию пикселей у меня получилось следующее художество:


Стандартная программа Icon-O-Matic в операционной системе Haiku.

Да, я использовал векторный редактор для создания изображения в жанре Pixel Art. На мой дилетантский взгляд человека, который слабо разбирается в искусстве, получилось вполне неплохо. Я сохранил эту иконку в нужном формате, подготовил все изменения, обновил рецепт и отправил всё в репозиторий HaikuPorts.

Исходный код проекта: https://github.com/EXL/NXEngine

Получившиеся пакеты я отправил на всякий случай и на фанатский сайт игры Cave Story (Doukutsu Monogatari), администрация которого добавила операционную систему Haiku в раздел загрузок.

<< Перейти к содержанию

4. Портирование игры Gish


Следующим проектом, который я решил перенести на Haiku, стала игра Gish, которую ранее я уже переносил на Android. В репозитории HaikuPorts был рецепт для недоделанной свободной реализации игры под названием Freegish, поэтому я решил добавить туда ещё и оригинальную игру, но без файлов данных, так как они, в отличие от движка, поставляются отдельно и вовсе не бесплатны.


Порт игры Gish, запущенный в операционной системе Haiku.

Никаких особых проблем с портированием этой игры у меня не возникло. Исполняемый файл собрался сразу же после выполнения следующих команд сборки:

cmake gish/src/main/cpp/ \
    -DGLES=0 \
    -DANDROID=0 \
    -DSDL2_INCLUDE_DIR=`finddir B_SYSTEM_HEADERS_DIRECTORY` \
    -DCMAKE_C_FLAGS="`sdl2-config --cflags` -D__linux__" \
    -DCMAKE_BUILD_TYPE=Release
cmake --build .

Далее я реализовал возможность запуска игры из меню «Applications» и обеспечил поддержку сохранения пользовательских данных в доступную для записи и предназначенную для этого директорию:

char* getHaikuSettingsPath()
{
    char path[PATH_MAX];
    find_directory(B_USER_SETTINGS_DIRECTORY, -1, false, path, sizeof(path));
    strcat(path, "/Gish/");
    return strdup(path);
}

Функция getHaikuSettingsPath() с помощью функции find_directory() из Haiku API формирует полный путь до необходимой мне директории.

Исходный код проекта: https://github.com/EXL/Gish

Оставалось решить следующий вопрос: каким образом пользователь должен выбирать директорию с оригинальными файлами игры Gish? Проблему можно было попытаться решить с помощью Shell-скриптов и системной утилиты alert, но я решил подойти к этой проблеме более основательно и реализовать удобный GUI-лаунчер, используя Haiku API и фреймворк Interface Kit.

<< Перейти к содержанию

5. Проект BeGameLauncher, позволяющий быстро создавать лаунчеры для игр


Мой проект BeGameLauncher было решено писать на языке C++ старого стандарта 1998 года, используя родные средства операционной системы для создания приложений с графическим интерфейсом пользователя. Так как названия многих программ для Haiku и BeOS начинаются с перефикса «Be», мной было тоже решено выбрать именно такое название для проекта. Начать я решил с ознакомления с фреймворком Interface Kit, который входит в состав Haiku API. Кроме достаточно подробной документации на официальном сайте Haiku, я нашёл два просто отличных курса уроков от DarkWyrm, которые позволяют быстро понять начинающему разработчику как работают те или иные системные классы. Первый курс называется Learning to Program with Haiku и в самом начале затрагивает основы языка программирования C++, что будет очень полезно начинающим программистам. Второй курс называется Programming With Haiku и предназначен для тех, кто уже знаком с C++ и имеет базовые знания этого языка. Оба курса рассказывают о самых различных аспектах Haiku API и поэтому будут очень полезны любому человеку, который хочет начать создавать приложения для этой операционной системы.

Прочитав по диагонали этот отличный материал, я составил общее впечатление о Haiku API и начал обдумывать свои дальнейшие действия. У меня уже имелся небольшой опыт разработки прикладных приложений с использованием фреймворка Qt, который тоже написан на языке программирования C++ и использует объектно-ориентированную парадигму построения программ. Так вот, Haiku API очень сильно на него похож, за исключением отсутствия системы сигналов и слотов, поэтому я буду часто проводить некоторые параллели и сравнения с Qt. Кроме того, стоит отметить распространённое в Haiku API использование принципа Event-driven programming, который позволяет взаимодействовать различным сущностям между собой посредством передачи событий или сообщений. Аналогом класса QEvent здесь является класс BMessage, вокруг которого и построена система взаимодействия объектов. Экземпляр класса BMessage в общем случае получает уникальное число, которое позволяет идентифицировать отправителя и его действие в общем фильтре событий.

Для моего проекта нужно было выбрать подходящие классы Haiku API, которые позволяли бы реализовать задуманную функциональность. Во-первых, для запуска внешнего приложения, нужно было найти аналог класса QProcess или POSIX-функции execve(), которая, к слову, тоже отлично работает в операционной системе Haiku, однако я решил, что использовать родные средства будет предпочтительнее, но на всякий случай оставил возможность запуска приложений и через POSIX-функцию. Класс BRoster, занимающийся межпроцессным взаимодействием, отлично подходил для этой цели. В нём нашёлся подходящий метод Launch(), позволяющий задать путь до исполняемого файла и передать ему аргументы. Поскольку лаунчер должен иметь возможность сохранения некоторых параметров, например, выбранной пользователем директории с файлами данных игры, мне нужен был класс, который занимается всем этим. В Qt такой класс имеет название QSettings, а в Haiku API, как мне подсказал Герасим, имеется уже знакомый мне класс BMessage, который имеет очень полезную особенность. Всё дело в том, что информацию этого класса можно легко сериализовать и, например, сохранить на диск. Это очень удобно и часто используется для записи каких-либо пользовательских данных в программах, поэтому именно этот класс я и выбрал для сохранения настроек в своём проекте реализации лаунчеров. К сожалению, в Haiku API не нашлось аналога класса QDebug, поэтому требуемый мне отладочный вывод в процессе разработки я просто отправлял в stderr, средствами функции fprintf() из стандартного языка программирования C:

// .h
#if __cplusplus >= 201103L
#define BeDebug(...) fprintf(stderr, __VA_ARGS__)
#else
extern void BeDebug(const char *format, ...);
#endif // __cplusplus == 201103L

// .cpp
#if __cplusplus < 201103L
#include <cstdarg>

void
BeDebug(const char *format, ...)
{
    va_list args;
    va_start(args, format);
    vfprintf(stderr, format, args);
    va_end(args);
}
#endif

Эту функцию я обернул в удобную мне сущность BeDebug(), которая в зависимости от выбранного стандарта языка является либо макросом, либо тоже функцией. Так было сделано из-за того, что C++98 не поддерживает макросы с переменным количеством аргументов.

Ещё во фреймворке Qt имеется полезный класс QMessageBox, через который можно создать модальный диалог с какой-либо информацией, на которую должен обратить внимание пользователь, например, на ошибку или предупреждение. В Haiku API для этих целей имеется класс BAlert, реализация которого несколько отличается от того, что доступно в Qt. Например, объект этого класса обязательно должен быть создан в куче, а не на стеке, поскольку после какого-либо действия пользователя он должен удалить сам себя. Что же касается других классов графического интерфейса, то здесь у меня не возникло абсолютно никаких трудностей и всё необходимое я нашёл без каких-либо проблем.

Теперь мне следовало подумать о простенькой архитектуре проекта. Я решил остановиться на создании статической библиотеки, в которой бы имелось два класса, предназначенных для унаследования от них собственных производных классов. Первый и самый важный класс, BeLauncherBase, отвечает за создание главного окна лаунчера, передачу всех пользовательских параметров и предоставляет возможность добавления собственных GUI-элементов. Второй класс, BeAboutWindow, просто отвечает за открытие диалога «О программе...» с информацией, которая показывается в отдельном окне. Таким образом, программисту для создания своего лаунчера, например, для игры Gish, требуется сделать два простых действия:

class GishAboutWindow : public BeAboutWindow
{
    ...
};

class GishLauncher : public BeLauncherBase
{
    ...
};

int
main(void)
{
    BeApp *beApp = new BeApp(SIGNATURE);
    GishLauncher *gishLauncher = new GishLauncher(BeUtils::GetPathToHomeDir());
    beApp->SetMainWindow(gishLauncher);
    beApp->Run();
    delete beApp;
    beApp = NULL;
    return 0;
}


Во-первых, создать подходящую стартовую функцию main(), а во-вторых просто унаследоваться от двух вышеперечисленных классов и реализовать в них необходимые методы. После этого компилируем полученный C++-файл с линковкой к моей статической библиотеке и наш лаунчер для игры Gish готов.


Диалог «О программе...» в лаунчере порта игры Gish.

Далее я задумался о том, каким образом мне передавать из своего лаунчера параметры в сам движок или в исполняемый файл игры. Я увидел только два пути решения этой проблемы. Первый путь заключался в изменении переменных окружения. На практике лаунчер после нажатия на кнопку «Run» просто помещает все параметры в переменные окружения посредством вызовов функции setenv(), а движок игры потом читает эти параметры с помощью функции getenv(), что выглядит достаточно просто. Единственная проблема, которая могла здесь возникнуть, находилась в классе BRoster и его методе Launch(): я не знал, унаследует ли запускаемое с помощью этого класса приложение все те переменные окружения, которые были выставлены в лаунчере. После небольшого эксперимента наследование переменных окружения подтвердилось и этот способ я полностью реализовал в своём проекте. Второй путь решения проблемы заключался в задании специальных параметров командной строки. На практике лаунчер просто складывал все настройки в соответствующие аргументы и вызывал с ними исполняемый файл приложения. А вот движок игры уже должен был самостоятельно их обработать, что создавало некоторые сложности. Например, если игра не предполагала возможность задания пути до игровых файлов через параметры командной строки, то нужно было модифицировать парсер аргументов в самом движке. Несмотря на эти проблемы, я реализовал и этот способ взаимодействия и в итоге получил отличную возможность совмещать всё вместе. Это позволило мне создавать в некоторых лаунчерах строку задания пользовательских аргументов.

Когда всё было спроектировано, я решил выбрать сборочную систему для своего проекта. Рассматривалось только два варианта: Makefile «на стероидах» и CMake. В первом случае, разработчики операционной системы Haiku подготовили удобный пакет makefile-engine, в котором собрали все необходимые возможности, с которыми столкнулся бы разработчик, начав писать приложение на Haiku API, например, автоматическую генерацию переводов и компиляцию ресурсов приложения. Но я не из тех, кто ищет лёгкие пути, поэтому я выбрал CMake и перенёс в него некоторые наработки из пакета makefile-engine. В итоге получившийся монструозный сборочный скрипт вы можете посмотреть в репозитории проекта, ссылку на который я оставлю ниже.


Скриншот лаунчера порта игры Gish для Haiku.

Хотелось бы написать пару слов о локализации приложений. Во фреймворке Qt для этого имеется удобная функция-обёртка tr(), две вспомогательные утилиты lrelease и lupdate, которые занимаются генерацией файлов перевода. В комплекте с фреймворком доступна даже специальная программа Qt Linguist с удобным графическим интерфейсом пользователя, предназначенная для переводчиков. В Haiku API инструменты для локализации приложений менее удобные и более архаичные. Строки, которые нужно перевести, предлагается оборачивать в специальный макрос B_TRANSLATE(), а в исходный файл добавлять определение B_TRANSLATION_CONTEXT, которое отделяет одну группу переводимых строк от другой. После этого требуется выполнить очень странную вещь: натравить препроцессор компилятора с флагом -DB_COLLECTING_CATKEYS на абсолютно все исходные файлы проекта, сделать какую-то магию с помощью утилиты grep и в итоге получить PRE-файл громадного размера. Именно с этим файлом и будет работать утилита collectcatkeys, которая уже создаст человекочитаемые и удобные для редактирования переводчику CATKEYS-файлы. После локализации строк необходимо воспользоваться утилитой linkcatkeys, которая добавляет переводы в ресурсы исполняемого файла. Таким образом, при выборе определённого системного языка приложение отображает переведённые строки. Странно, но в документации Haiku API о локализации приложений содержится очень мало информации. Однако, на официальном сайте я нашёл отличную статью Localizing an application, в которой подробно рассмотрены многие аспекты переводов приложений для этой операционной системы. Как я понял, в оригинальном BeOS не было фреймворка Locale Kit и он был добавлен уже только в Haiku.

Следующим моим шагом стал выбор среды для разработки приложений на языке программирования C++. Благодаря тому, что на Haiku был портирован фреймворк Qt, в репозитории HaikuPorts доступны такие IDE, как Qt Creator и KDevelop. Кроме того, имеется порт JVM, что позволяет использовать IDE, написанные на языке программирования Java, например, NetBeans или IntelliJ IDEA. Я остановил свой выбор на среде разработки Qt Creator, тем более в её последних версиях имеется качественный разбор кода с помощью парсера LibClang, который работает на порядок точнее и быстрее стандартного парсера.


Интегрированная среда разработки Qt Creator, запущенная в операционной системе Haiku.

В плане общеизвестных и кроссплатформенных IDE в Haiku всё хорошо. Но что насчёт эксклюзивных решений? Я не могу не упомянуть очень интересный проект, автором которого является DarkWyrm и который в настоящее время поддерживает Adam Fowler, он называется Paladin. Эта программа превращает доступный в дистрибутиве операционной системы продвинутый текстовый редактор Pe практически в настоящую IDE.


Интегрированная среда разработки Paladin для Haiku, установленная из репозитория HaikuPorts.

С помощью встроенного в оконную систему Haiku тайлинга можно прикрепить окно Paladin сбоку редактора Pe и добавить терминал. Ещё в репозитории HaikuPorts имеется удобный редактор текста Koder, напоминающий собой популярную программу Notepad++ для Windows и так же базирующийся на наработках проекта Scintilla. Для своего приложения я создал проектный PLD-файл и теперь любой разработчик, который пользуется Paladin IDE, может без проблем открыть в этой программе мой проект.

Когда среда разработки Qt Creator была настроена и готова к работе, я начал реализовывать все задуманные возможности. Первая проблема, с которой я столкнулся, была связана с масштабированием контролов при изменении размера системного шрифта. Изначально в BeOS весь код размещения GUI-элементов задавался явно в координатах. Это было очень неудобно, многословно и создавало огромный ворох проблем, например, при том же изменении размера шрифта вся форма приложения разъезжалась и становилась непригодной к использованию. К счастью, в Haiku попытались решить эту проблему и добавили программный интерфейс Layout API, который является частью фреймворка Interface Kit.


Благодаря использованию Layout API лаунчеры корректно реагируют на изменение размера системного шрифта в Haiku.

Это нововведение полностью решало мою проблему с позиционированием контролов и я переписал приложение с использованием Layout API, что серьёзно сократило длину кода в некоторых местах. На официальном сайте Haiku я нашёл цикл интересных статей Laying It All Out, в которых как раз рассказываются причины того, почему был создан этот программный интерфейс и показаны примеры его использования.

Ещё одну проблему обозначил Герасим, когда попробовал воспользоваться моей библиотекой для создания лаунчера к игре, которую он портировал. Дело было в том, что я часто обращался к исходному коду самой операционной системы Haiku для реализации различной функциональности. В частности, пример использования метода Launch() у объекта класса BRoster я обнаружил именно там. Проблема проявлялась в том, что этот пример оказался некорректным и движок игры, которую портировал Герасим, не мог правильно распарсить заданные лаунчером аргументы. Глубже изучив исходный код Haiku мне удалось выяснить, что первый аргумент, который должен содержать полный путь до исполняемого файла, в случае с методом Launch() не требуется задавать явно, так как он будет задан автоматически.

// Error
const char* args[] = { "/bin/open", fURL.String(), NULL };
be_roster->Launch(&ref, 2, args);

// Good
const char* args[] = { fURL.String(), NULL };
be_roster->Launch(&ref, 1, args);

// See "src/kits/app/Roster.cpp", BRoster::ArgVector::Init() method:
if (error == B_OK) {
    fArgs[0] = fAppPath.Path(); // <= Here
    if (argc > 0 && args != NULL) {
        for (int i = 0; i < argc; i++)
            fArgs[i + 1] = args[i];
        if (hasDocArg)
            fArgs[fArgc - 1] = fDocPath.Path();
    }
    // NULL terminate (e.g. required by load_image())
    fArgs[fArgc] = NULL;
}

В документации на метод Launch() ничего не сказано про то, что первый аргумент задавать не требуется, наверное именно поэтому разработчик написал этот код некорректно. Я исправил эту ошибку в своём проекте и проблема Герасима разрешилась сама собой. Но что насчёт этой небольшой ошибки в самой операционной системе Haiku? Я решил исправить и её. К счастью, это оказалось сделать ну очень просто! Нужно авторизоваться с помощью GitHub на Gerrit-ресурсе Haiku Code Review, добавить свой публичный SSH-ключ, форкнуть исходный код Haiku, создать коммит с исправлением и отправить получившийся патч на Code review привилегированным разработчикам:

git clone ssh://EXL@git.haiku-os.org/haiku --depth=1 -b master && cd haiku
git commit
git push origin master:refs/for/master

Если нужно обновить уже отправленные патчи, то перед отправкой изменённых или новых коммитов обязательно добавляем в конец commit-сообщения тот ID, который выдал нам сервис Haiku Code Review. После того, как патч отправлен, разработчики Haiku должны его подтвердить, отклонить или отправить на доработку. В моём случае исправление было принято сразу и этот небольшой недочёт теперь устранён везде. Если вам требуется протестировать ваши патчи перед отправкой в репозиторий, то вы можете попробовать с помощью утилиты jam, которая является форком сборочной системы Perforce Jam и используется для сборки всей кодовой базы операционной системы Haiku, скомпилировать отдельное приложение. В репозитории исходного кода имеется файл ReadMe.Compiling.md, который поможет вам разобраться со всеми премудростями компиляции.

Дорабатывая свой проект, я нашёл причину по которой программа Icon-O-Matic не открывает SVG-файлы, созданные с помощью векторного редактора Inkscape. Всё дело в том, что Icon-O-Matic не умеет обрабатывать атрибут viewBox, однако, если найти простой SVG-файл без этого атрибута, отредактировать его с помощью Inkscape и сохранить как Plain SVG file, то он откроется и в программе Icon-O-Matic. Поэтому я положил в свой репозиторий такой специально подготовленный SVG-файл, который можно редактировать и который будет открываться в Icon-O-Matic без проблем. Дополнительно я добавил в ReadMe-файл проекта небольшую инструкцию о том, как создавать иконки для своих лаунчеров с помощью Inkscape.

Код своего проекта я решил проверить самыми различными статическими анализаторами, но никаких серьёзных проблем они не нашли. А я вот позже нашёл одну проблему, которую не смогли обнаружить они. Дело в том, что статический метод GetBitmap() класса BTranslationUtils мог вернуть NULL:

// Somewhere
fBitmap = BTranslationUtils::GetBitmap(B_PNG_FORMAT, fIndex);

void
BeImageView::Draw(BRect rect)
{
    // Fail
    const BRect bitmapRect = fBitmap->Bounds();
    ...
}

И в методе Draw() я по невнимательности забыл проверить поле класса fBitmap на валидность. Поэтому приложение ожидаемо падало, если не находило определённую картинку, а по плану должно было нарисовать красный квадрат вместо этого. Эту историю я рассказал к тому, что статические анализаторы далеко не панацея и внимательность при работе с кодом на языке программирования C++ требуется в любом случае.

Исходный код проекта BeGameLauncher и все свои наработки я выкладываю в репозиторий на GitHub. Надеюсь, эта программа окажется кому-нибудь полезной и может быть станет неким учебным пособием в качестве простого приложения для Haiku:

Исходный код проекта: https://github.com/EXL/BeGameLauncher

Небольшой совет для тех, кто будет использовать мой лаунчер в своих рецептах для репозитория HaikuPorts. Если вы хотите скрыть исполняемый файл игры из списка приложений Haiku, которые читают некоторые программы, и оставить там только лаунчер, вы можете воспользоваться следующим трюком:

settype -t application/x-vnd.Be-elfexecutable $appsDir/Gish/engine/Gish
rc $portDir/additional-files/gish.rdef -o gish.rsrc
resattr -o $appsDir/Gish/engine/Gish gish.rsrc

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

<< Перейти к содержанию

6. Портирование Xash3D: легендарная игра Half-Life и официальные дополнения


Проект Xash3D представляет собой свободную реализацию движка GoldSrc, который используется в игре Half-Life и в её официальных дополнениях. За разработкой Xash3D стоит отечественный программист Дядя Миша, который до сих пор координирует его развитие и улучшение. Чуть позже к проекту присоединились другие разработчики, которые сделали форк FWGS Xash3D, с поддержкой огромного количества операционных систем, отличных от Windows. Сегодня ключевыми программистами проекта FWGS Xash3D являются mittorn и a1batross (libpony), последний человек был активным участником некогда популярного форума MotoFan.Ru, который я до сих пор администрирую в своё свободное время.

Я задался вопросом: почему бы не портировать этот движок на Haiku, добавив в проект Xash3D поддержку такой интересной операционной системы, а пользователям Haiku дать возможность поиграть в легендарный Half-Life, игру всех времён и народов? Дело оставалось за малым — требовалось незамедлительно начать работу по портированию и в случае успеха опубликовать результаты этой работы.

Потратив несколько часов на изучение структуры проекта и тех частей кода, которые отвечают за поддержку различных платформ, я начал вносить изменения в движок Xash3D, чтобы обеспечить возможность поддержки операционной системы Haiku. По-старинке, я задефайнил компилятору -D__linux__ и попытался собрать исполняемый файл и кучу библиотек. На удивление дело пошло достаточно быстро и уже к вечеру, пробросив файлы данных для игры, у меня получилось запустить Half-Life и доехать на поезде до начальной станции в Black Mesa.


Процесс портирования движка Xash3D на Haiku в интегрированной среде разработки Qt Creator.

Благодаря тому, что проект использует кроссплатформенную библиотеку SDL2, портирование движка очень сильно упрощается, так как не нужно писать каких-либо кусков кода, которые зависят от платформы, например: вывод звука, создание окна с OpenGL-контекстом, или обработку событий ввода. Всё это уже реализовано в библиотеке SDL2 и готово к использованию. Небольшая проблема возникла с поддержкой сети, потому что в Haiku имеется отдельная библиотека, реализующая сетевой стек, соответственно, её требовалось прилинковать к движку.

Проект по созданию лаунчеров, про который я написал чуть выше, мне очень сильно пригодился. С помощью наследования C++-классов я серьёзно расширил его функциональность и реализовал возможность выбора различных дополнений игры:


Скриншот лаунчера порта движка Xash3D для Haiku.

Идея была следующей: определить три переменных окружения, которые бы позволили гибко настраивать движок игры на запуск определённого дополнения. При этом было бы полезно дать пользователю поиграться с различными аргументами исполняемого файла и оставить возможность портативного запуска движка, когда он лежит просто в директории с требуемыми файлами данных. Итак, первая переменная окружения XASH3D_BASEDIR отвечает за директорию с файлами игры, которую выбирает пользователь из лаунчера. Вторая переменная XASH3D_GAME отвечает за то, какое дополнение выбрал для запуска пользователь в лаунчере. А вот третья переменная XASH3D_MIRRORDIR, пригодится лишь продвинутым пользователям. Она позволяет зеркалировать системную директорию Xash3D в любое доступное для записи пользователю место на диске. Таким образом, человеку, который хочет выпустить свою игру-дополнение на движке Xash3D под Haiku требуется просто собрать из исходного кода своего проекта несколько динамических библиотек для разных архитектур:

• ./cl_dlls/libclient-haiku.so
• ./dlls/libserver-haiku.so
• ./cl_dlls/libclient-haiku64.so
• ./dlls/libserver-haiku64.so

И затем положить их в соответствующие директории своего дополнения. Для своего порта Xash3D я решил предкомпилировать библиотеки популярных дополнений к игре Half-Life, а именно Blue Shift и Opposing Force, что позволит пользователям просто скачать их файлы данных, выбрать директорию и начать игру без каких-либо компилирований библиотек.

В процессе портирования движка Xash3D я столкнулся с некоторыми забавными проблемами. Оказывается, для определения длины сообщения справки по аргументам исполняемого файла, которая генерируется при передаче параметра --help, в движке был использован предустановленный размер константы MAX_SYSPATH, которая является псевдонимом другой константы MAX_PATH, значение которой уже берётся из Haiku API. Так вот, я долго не мог понять, почему же эта справка выдаётся неполной и обрезается в самом интересном месте. Сначала я грешил на то, что каким-то странным образом к стандартному потоку вывода ошибок stderr подключилась буферизация и даже пытался принудительно её отключить. Спустя какое-то время я вспомнил, что меня удивил очень маленький размер константы MAX_PATH в операционной системе Haiku. Эта константа предполагает размер пути всего в 1024 байт. Моя догадка полностью оправдала себя, как только я увеличил размер сообщения до стандартных 4096 байт, проблема разрешилась. Из этой забавной истории следует сделать следующий вывод: не стоит использовать константу MAX_PATH в массивах символов, которые никак не связаны с файловыми путями.


Коллаж из скриншотов игры Half-Life, а также её официальных дополнений Blue Shift и Opposing Force, запущенных с помощью движка Xash3D в операционной системе Haiku (превью, увеличение по ссылке).

Ещё одной проблемой был вылет при использовании функциональности самого движка для выбора дополнения игры. Оказалось, что при выставленном дефайне XASH_INTERNAL_GAMELIBS клиентская библиотека загружалась не один, а два раза. Что и повлекло за собой подобную проблему. Как мне разъяснил a1batross, так было сделано для того, чтобы была возможность статически прилинковать библиотеку OpenVGUI к клиентской библиотеке. В моём порте Xash3D на Haiku эта библиотека никак не используется, поэтому я просто ушёл от использования дефайна XASH_INTERNAL_GAMELIBS и зарепортил этот баг разработчикам движка.

Далее я наткнулся на невозможность открытия встроенного в Haiku браузера WebPositive при нажатии на ссылки внутри запущенной в Xash3D игры. При этом проблема была действительно странной, так как при запуске движка из терминала браузер открывался, а вот при запуске с помощью лаунчера он отказывался это делать. Немного изучив код я нашёл там вызов execve(), который я попробовал заменить на system(), после чего браузер стал открываться без каких-либо проблем.

Движок Xash3D при возникновении ошибок активно использует вызовы функций SDL_ShowSimpleMessageBox() и SDL_ShowMessageBox(), вот только текущий порт библиотеки SDL2 для Haiku не поддерживает создание этих диалогов. В нашей версии библиотеки просто отсутствует эта функциональность. Но об исправлении этой проблемы я расскажу ниже.


Порт движка Xash3D, опубликованный в репозиторий Haiku Depot.

Стоит ещё отметить, что перед моим переносом движка Xash3D на Haiku, Герасим Троеглазов реализовал в SDL2 захват курсора мыши; до этого играть в 3D-игры было практически невозможно. Чуть позже он же исправил хитрый баг, при котором перемещение игрока в пространстве постепенно замедлялось, а игра начинала жутко тормозить. Оказывается, дело было в том, что по умолчанию события курсора мыши передавались со всей его историей передвижения по экрану. Соответственно, эта история в процессе игры быстро раздувалась и всё начинало сильно тормозить. Отключение подобной возможности в порте SDL2 на Haiku решило эту проблему и в Half-Life теперь можно играть без особых проблем. Хотя, отсутствие 3D-ускорения на слабом железе даёт о себе знать. И если игра прилично работает в окне и вообще не тормозит, то в полноэкранном режиме значительно снижается FPS. Но тут поможет лишь добавление в видеодрайвера операционной системы аппаратного ускорения хотя бы для тех GPU, которые встроены в популярные процессоры Intel.

Исходный код проекта: https://github.com/FWGS/xash3d

Все изменения исходного кода я отправил разработчикам проекта FWGS Xash3D, которые приняли их в репозиторий, а пакеты с этим движком уже давно доступны в HaikuPorts и в программе HaikuDepot для любого пользователя Haiku.

<< Перейти к содержанию

7. Портирование двух частей игры Serious Sam: The First Encounter и The Second Encounter


Недавно разработчики из компании Croteam выложили исходный код движка Serious Engine, который используется в играх серии Serious Sam: The First Encounter и The Second Encounter. Я решил заняться его портированием на операционную систему Haiku, скачал исходный код и начал работу.


Скриншот лаунчера порта движка Serious Engine для Haiku.

Сборка исполняемого файла после внесённых изменений обошлась без каких-либо проблем, а вот запустить игру так просто не удалось из-за того, что ошибки сыпались в диалоги SDL2, реализация которых отсутствует в версии этой библиотеки для Haiku. Поэтому пришлось брать в руки проверенный временем стандартный поток вывода ошибок stderr и потихоньку разбираться в проблемах, которые оказались в основном в отсутствии требуемых файлов данных игры.


Скриншот игры Serious Sam: The Second Encounter, запущенной с помощью порта движка Serious Engine для операционной системы Haiku.

Разложив скачанные файлы по требуемым директориям я без проблем смог запустить вторую часть этой замечательной игры и даже немного побегал по красивейшим джунглям. Несмотря на отсутствие 3D-ускорения, процессор вытягивает графические прелести игры, если запускать её в окне, а не в полноэкранном режиме. Работает этот движок, конечно, куда с меньшим FPS, чем движок Xash3D, про который я писал выше, но и графика здесь современнее и лучше. После небольших манипуляций удалось запустить и первую часть игры, которая требует другой исполняемый файл и другой набор динамических библиотек. На удивление, она заработала немного быстрее, видимо графика в ней не такая требовательная. Полазив по настройкам движка, я обнаружил огромное количество графических параметров, позволяющих значительно снизить нагрузку на процессор, что в случае с Haiku оказалось очень полезным.


Скриншот игры Serious Sam: The First Encounter, запущенной с помощью порта движка Serious Engine для операционной системы Haiku.

Я решил сделать один пакет сразу для двух частей игры, переключение между которыми будет осуществляется просто выбором директории с соответствующим набором файлов данных. Например, если пользователь в лаунчере выбирает директорию с файлами игры Serious Sam: The First Encounter, то запускается соответствующий исполняемый файл и подгружается соответствующий набор динамических библиотек. А если он выберет каталог с файлами игры Serious Sam: The Second Encounter, то лаунчер соответственно запустит уже другой исполняемый файл, который подгрузит свой набор разделяемых библиотек.

К сожалению, без проблем не обошлось. Повторное изменение разрешения видеорежима в игре приводило к падению всего движка. При этом в моём дистрибутиве Linux этого вылета не было. Я потратил очень много времени на локализацию проблемы и на её устранение. Оказалось, всё дело было в том, что при каждом изменении разрешения разрушалось и снова создавалось окно SDL_Window, при этом OpenGL-рендерер вовремя не мог переключиться и пытался что-то там рисовать в разрушенном окне. Такие выкрутасы порт библиотеки SDL2 на Haiku не позволял проворачивать. Все простые попытки решения этой проблемы не помогали и мне пришлось серьёзно влезть в логику и изменить поведение таким образом, чтобы окно при смене разрешения не разрушалось, а просто изменялись его параметры. Это помогло убрать вылет, но добавило дополнительное ограничение: теперь, чтобы активировать полноэкранный режим, требуется перезапустить движок.

Ещё одной проблемой было отсутствие музыки в игре. При этом на Linux, опять же, эта проблема не проявлялась. Исследуя исходный код движка, я обнаружил что воспроизведение музыки зависит от библиотеки libvorbisfile, но сам движок при этом не линкуется с ней, а использует системную функцию dlopen(), чтобы скормить этой библиотеке поток OGG-аудиофайла. Проблема заключалась в том, что на Haiku эта библиотеку движок не мог найти, так как симлинка на файл библиотеки без обозначения версии не было.

void CUnixDynamicLoader::DoOpen(const char *lib)
{
    // Small HACK for Haiku OS (:
#ifdef __HAIKU__
    static int vorbis_cnt = 3;
    char path[PATH_MAX];
    char libpath[PATH_MAX];
    find_directory(B_SYSTEM_LIB_DIRECTORY, -1, false, libpath, PATH_MAX);
    if (strstr(lib, "vorbis")) {
        snprintf(path, sizeof(path), "%s/libvorbisfile.so.%c", libpath, char(vorbis_cnt + '0'));
        vorbis_cnt++;
        lib = path;
    }
#endif
    // fprintf(stderr, "dlopen => %s\n", lib);
    module = ::dlopen(lib, RTLD_LAZY | RTLD_GLOBAL);
    SetError();
}

Небольшой трюк, который подставлял в функцию полный путь до требуемой библиотеки, оказался вполне рабочим решением. А поскольку библиотека при её отсутствии ищется движком несколько раз, я на будущее оставил возможность подгрузки следующей мажорной версии. Надеюсь, они не сломают в ней API.

Следующая проблема, с которой я столкнулся, заключалась в невозможности определения частоты процессора на архитектуре x86, хотя на x86_64 всё работало нормально. При запуске на x86 движок просил выставить переменную окружения с именем SERIOUS_MHZ и задать в ней соответствующую частоту, что меня очень сильно удивило. Я попробовал это сделать и игра действительно запустилась, но почему-то работала слишком медленно. Полазив по исходному коду игры, я долго не мог найти источник проблемы и даже написал кусочек кода, который с помощью Haiku API получает правильную частоту процессора и подставляет её в движок игры, вот так он выглядел:

#include <kernel/OS.h>
#include <stdio.h>

...

uint64 cpuFreq = 0;
uint32 count = 0;
get_cpu_topology_info(NULL, &count);
if (count != 0) {
    cpu_topology_node_info *topology = new cpu_topology_node_info[count];
    get_cpu_topology_info(topology, &count);
    for (uint32 i = 0; i < count; ++i) {
        if(topology[i].type == B_TOPOLOGY_CORE) {
            cpuFreq = topology[i].data.core.default_frequency;
        }
    }
    delete[] topology;
}
fprintf(stderr, "%llu\n", cpuFreq);

Но это не помогало. Тогда я проверил логи движка на x86_64 и увидел, что там у CPU частота вообще определяется в 1 MHz, но всё прекрасно работает. Продолжив исследовать код дальше, я наткнулся на отрицание дефайна __GNU_INLINE_X86_32__, который автоматически выставляется тогда, когда приложение собирается под архитектуру x86, но не под x86_64. Ниже за этим дефайном как раз и скрывался флажок, который говорил использовать таймеры SDL2, вместо получения частоты процессора с помощью различной магии вроде inline-ассемблера и инструкции rdtsc или чтения файла /proc/cpuinfo, поэтому я сделал так, чтобы этот флаг был активирован и для x86, что решило мою проблему.

Последний недочёт был связан с моей невнимательностью. Я пропустил в сборочном файле CMakeLists.txt установку флага -march=native, который буквально говорит компилятору: при генерации блоков машинного кода используй все навороченные и современные инструкции, которые доступны в процессоре твоего компьютера.

if(NOT PANDORA AND NOT HAIKU)
    message("Warning: arch-native will be used!")
    add_compile_options(-march=native)
endif()
if(HAIKU)
    if(CMAKE_SIZEOF_VOID_P EQUAL 4) # 32-bit
        message("Warning: Will building 32-bit executable with MMX, SSE, SSE2 support.")
        add_compile_options(-mmmx -msse -msse2)
    else()                          # 64-bit
        message("Warning: Will building 64-bit executable.")
    endif()
endif()

Из-за этого пакеты в репозитории собрались эксклюзивно под мощнейший build-сервер и отказывались запускаться на компьютерах простых смертных людей, ругаясь на неправильные инструкции и опкоды. Отключение этого флага и ручное добавление поддержки инструкций MMX, SSE и SSE2 не только решило эту проблему, но и позволило скомпилировать огромную кучу inline-ассемблера в этом проекте, которая отвалилась после того, как был убран этот флаг.

К моему большому сожалению, разработчики Croteam не принимают какие-либо патчи в репозиторий движка, поэтому я сделал форк и выложил все свои наработки там:

Исходный код проекта: https://github.com/EXLMOTODEV/Serious-Engine

Готовые к установке пакеты, позволяющие запустить игры серии Serious Sam, уже доступны в репозитории HaikuPorts. Только не забудьте скачать файлы данных игры.

<< Перейти к содержанию

8. Портирование игры Вангеры (Vangers)


Скажу честно, до недавнего времени я был совсем незнаком с этой игрой, которую в далёких 90-ых годах сделала отечественная студия разработчиков K-D Lab. Но участники конференции в Telegram IM, которая посвящёна обсуждению операционной системы Haiku, попросили меня портировать Вангеров и дали мне ссылку на GitHub-репозиторий, в котором находились исходники этой игры.


Скриншот лаунчера порта игры Вангеры для Haiku.

Стянув исходники в Haiku я попытался их скомпилировать и у меня это удалось без каких-либо особых проблем. Немного пришлось повозиться с отсутствием некоторых заголовочных файлов и с путями к библиотекам FFmpeg, которые используются движком этой игры. Я сразу начал подготавливать исходный код к пакетированию, поэтому добавил переменную окружения VANGERS_DATA и перенёс лог движка в пользовательскую директорию, доступную для записи.


Процесс портирования игры Вангеры на Haiku в интегрированной среде разработки Qt Creator.

Я запустил саму игру и спустя некоторое время по достоинству оценил всю ту атмосферу, которую удалось создать ребятам из K-D Lab. Через некоторое время я начал беспечно возить «Нимбус» в «Инкубатор» и «Флегму» в «Подиш», после чего мне даже удалось привезти «Элика» третьим. Вдоволь наигравшись, я начал подготавливать лаунчер для этой игры на основе своей библиотеки, про которую я написал выше.


Порт игры Вангеры, запущенный на операционной системе Haiku.

Первая проблема, с которой я столкнулся, заключалась в том, что файлы данных игры, которые могли быть официально получены с помощью сервисов цифровой дистрибуции GOG.com и Steam, не хотели работать с движком. Мне пришлось связаться с человеком, который использует ник stalkerg и который занимался портированием Вангеров на Linux. Он рассказал мне какие именно файлы требуется подменить, чтобы всё запустилось и начало работать. Я последовал его рекомендациям и получил то, что мне требовалось.

Как и в случае с портом NXEngine (Cave Story), о котором я писал выше, русская и английская версия различаются между собой разными исполняемыми файлами, а вот директория с файлами данных у них общая, отличия имеются лишь в скриптах. По подсказке stalkerg я попробовал скомпилировать движок игры с опцией -DBINARY_SCRIPT=Off, которая активировала динамическую компиляцию этих скриптов во время исполнения, в том случае, если они имеются в каталоге файлов данных игры. Всё это позволило мне создать лаунчер, в котором имеется возможность переключения языка. Идея такая: предварительно проверяется директория игры, и если в ней нет необходимых скриптов, то они копируются из внутренностей пакета, после чего уже запускается исполняемый файл русской или английской версии.


Порт игры Вангеры, опубликованный в репозиторий Haiku Depot.

При портировании Вангеров я задействовал одну интересную особенность, связанную с разделяемыми библиотеками, которая мне нравится в Haiku. Движок игры зависит от динамической библиотеки libclunk.so, которая отвечает за генерацию бинауральных звуков в реальном времени. И если в Linux, я должен ломать пальцы, подставляя в переменную окружения LD_LIBRARY_PATH путь до этой библиотеки, таким образом, чтобы и то что было в этой переменной до этого, тоже было сохранено, то в Haiku это сделано удобно, как и в Windows. Достаточно положить разделяемую библиотеку рядышком с исполняемым файлом и она будет подхвачена, с тем лишь отличием, что в случае с Haiku библиотеку необходимо положить в директорию ./lib/, что на мой взгляд позволяет сильно сэкономить время и нервы. Поэтому статическую компиляцию этой библиотеки я решил не рассматривать.

Исходный код проекта: https://github.com/KranX/Vangers

Разработчики Вангеров приняли мои изменения в движок своей игры, а готовые к установке пакеты доступны для скачивания из репозитория HaikuPorts или программы HaikuDepot, несмотря на недавний факап в инфраструктуре репозиториев, случившийся после обновления Linux-дистрибутива Fedora на новую версию.

<< Перейти к содержанию

9. Реализация диалогов в библиотеке SDL2 для Haiku


При портировании движков Xash3D и Serious Engine, про которые я писал выше, я наткнулся в местном порте библиотеки SDL2 на полное отсутствие реализации диалогов. Диалоги вызываются двумя функциями SDL_ShowSimpleMessageBox() и SDL_ShowMessageBox(), которые позволяют проинформировать пользователя о какой-либо важной информации, например, об ошибке. Реализация этих диалогов доступна на многих платформах и операционных системах: Windows, macOS, iOS, X11 и Android, но почему-то отсутствует в Haiku. Я решил исправить это упущение и добавить эту функциональность в порт библиотеки SDL2.

В Haiku API, а точнее во фреймворке Interface Kit, имеется прекрасный класс BAlert, который отлично подходит для реализации подобных диалогов. Я решил выбрать его в качестве базового. Единственное, что меня смущало, это то, что я не был уверен в том, что в диалоге, который конструирует BAlert, можно разместить более трёх кнопок. Ещё я помнил про особенности управления памятью в этом классе, о которых я писал выше: его объекты можно создавать только в куче, и нельзя создавать на стеке, так как после вызова метода Go() и последующего действия пользователя он удаляет сам себя. Проведя некоторые эксперименты, я развеял все свои сомнения, унаследовался от этого класса и начал писать реализацию.


Реализация диалогов в библиотеке SDL2 для операционной системы Haiku.

Первая трудность, с которой я столкнулся, состояла в том, что при использовании любого объекта класса BAlert или его наследников, необходимо было обязательно создать экземпляр системного класса BApplication, видимо чтобы зарегистрировать приложение в app_server для возможности взаимодействия с ним. Я создал экземпляр этого класса, но при вызове диалога BAlert из другого процесса или из созданного окна я получил другую ошибку, связанную с тем, что приложение не может иметь два объекта класса BApplication, к счастью я нашёл решение и этой проблемы. В Haiku API имеется глобальный указатель на текущий экземпляр класса BApplication, который называется be_app, его аналогом во фреймворке Qt является специальный макрос qApp, тоже определяющий указатель на текущий объект приложения. Так вот, достаточно просто проверять указатель be_app на NULL, и в том случае, если проверка завершилось успешно, создавать требуемый объект. Таким образом все эти проблемы были решены.

Стоит обязательно отметить то, что библиотека SDL2 написана на языке программирования C, а в Haiku API, как известно, используют язык программирования C++. Из-за этого некоторые части кода следует обязательно обмазать соглашениями о связывании extern "C", чтобы не было никаких проблем с разрешением символов в процессе линковки. Кроме того, вместо new следует использовать оператор new(std::nothrow), чтобы иметь возможность проверять выделенную память по NULL, вместо выброса исключения, обработку которых SDL2, конечно же, не поддерживает.

В остальном ничего сложного не было. Пишем несколько функций, которые конвертируют сущности и представления SDL2 таким образом, чтобы они были совместимы с Haiku API и проверяем их корректную работу. Для различных проверок мной был расширен небольшой тест, который я периодически запускал на разных операционных системах, анализировал полученные результаты и оценивал свою работу. В конце-концов я так увлёкся, что сделал даже поддержку кастомизации, вроде задания разных цветов кнопкам и фону диалога. Это поддерживается в API библиотеки SDL2, но изначально я не планировал реализовывать такие вещи.

Если программист решит выплюнуть в этот диалог очень-очень длинную строку, то у объекта класса BTextView, который используется внутри объекта класса BAlert, требуется вызвать метод SetWordWrap() с аргументом true, чтобы ударить такого программиста по рукам и сделать так, чтобы диалог мог поместиться на экран. Казалось бы, нет ничего проще: проверяем длину строки с помощью функции strlen() и делаем нужное. Вот только проблема в том, что SDL2 работает так же и с UTF-8, а это значит, что функция strlen() будет возвращать количество байт, а не количество символов. На помощь приходит Haiku API и класс строк BString, в котором имеется метод CountChars(), позволяющий узнать длину строки в символах, а не в байтах:

bool
CheckLongLines(const char *aMessage)
{
    int final = 0;

    // This UTF-8 friendly. P.S. G_MAX_STRING_LENGTH = 120
    BString message = aMessage;
    int32 length = message.CountChars();

    for (int i = 0, c = 0; i < length; ++i)
    {
        c++;
        if (*(message.CharAt(i)) == '\n')
        {
            c = 0;
        }
        if (c > final)
        {
            final = c;
        }
    }

    return (final > G_MAX_STRING_LENGTH);
}

Эта функция проверяет текст сообщения на строки длинной более 120-ти символов и если такие имеются возвращает истину. Насчёт UTF-8 обнаружился ещё такой момент, что в некоторых системных шрифтах Haiku отсутствует поддержка китайских иероглифов. Поэтому, к примеру, установить какую-нибудь китайскую надпись в заголовок окна, нельзя. А вот текст на русском языке устанавливается без проблем.

При подготовке пакета я столкнулся с ошибкой сборки под архитектуру x86_gcc2, которая активирована в рецепте библиотеки SDL2. Оказалось, что древнейший компилятор GCC 2.95 не может догадаться, что закомментированный код эквивалентен тому, что находится ниже:

rgb_color
ConvertColorType(const SDL_MessageBoxColor *aColor) const
{
    // return { aColor->r, aColor->g, aColor->b, 255 };
    rgb_color color = { aColor->r, aColor->g, aColor->b, color.alpha = 255 };
    return color;
}

Поэтому мне пришлось переписать этот фрагмент в старом стиле и ещё убрать инициализацию некоторых констант в классе прямо в их объявлениях, это тоже не понравилось старому компилятору.

Я отправил патчи реализации диалогов SDL2 в репозиторий HaikuPorts, благодаря чему теперь движки Xash3D и Serious Engine могут корректно выдавать пользователю какую-либо информацию, например, об ошибках. А вот с разработчиками SDL2 я пока не связывался, но было бы прекрасно перенести все патчи из репозитория HaikuPorts в upstream библиотеки SDL2. Хотя работа по переносу наших патчей немного усложнилась из-за недавнего переименования префиксов функций с BE_* на HAIKU_*, но это не является такой уж серьёзной проблемой.

<< Перейти к содержанию

10. Портирование моего форка программы Cool Reader


Я уже давно развиваю форк программы Cool Reader, которую написал Вадим Лопатин (Buggins), соответствующая статья про это имеется на моём сайте. В комментариях к той статье постоянно отписываются читатели моего блога, которые либо хотят увидеть какую-нибудь новую возможность в своём любимом приложении для чтения электронных книг, либо хотят исправить ошибки и недочёты в уже реализованных функциях программы.


Мой форк программы Cool Reader, запущенный в операционной системе Haiku.

В репозитории HaikuPorts я обнаружил рецепт для сборки оригинальной программы Cool Reader, однако из-за каких-то постоянных изменений, происходящих с ресурсом SourceForge, этот рецепт оказался нерабочим, поскольку исходный код приложения стал недоступен для скачивания. Тогда я решил перенести свой форк в репозиторий HaikuPorts, в качестве новой версии программы Cool Reader. Я наложил все патчи Герасима на код, поправил некоторые недочёты в рецепте и на его основе создал новый пакет, который уже доступен всем пользователям Haiku. Исходный код моего форка программы Cool Reader вы сможете найти в этом GitHub-репозитории:

Исходный код проекта: https://github.com/EXLMOTODEV/coolreader

Единственной проблемой, с которой я столкнулся, были неточности переноса патчей Герасима. Кроме дефайна __HAIKU__, где-то в системе сборки выставлялся ещё и дефайн _LINUX и, поскольку в большинстве случаев последний в листинге исходного кода стоял первым, условная компиляция подвела меня. В соответствии с правилами приоритета препроцессора, для Haiku компилировались именно те куски кода, которые были обрамлены дефайном _LINUX, хотя мне нужно было совсем другое. Но даже несмотря на это программа запускалась и работала, вот только сохраняла свои настройки не там, где это требовалось. Я правильно расставил приоритеты, пересобрал пакет и проблема полностью разрешилась.

<< Перейти к содержанию

11. Доработка программы KeymapSwitcher


В последнее время многие популярные операционные системы перешли на новое сочетание клавиш Meta/Opt/Cmd/Win+Space для переключения раскладки клавиатуры. Мне оно показалось очень удобным тем, что теперь не нужно ничего менять и настраивать. Садишься за любой компьютер под управлением macOS, Windows или Linux с оболочкой GNOME 3 и эта удобная комбинация смены языка ввода просто везде работает. Даже в мобильной операционной системе Android имеется её аналог. В общем, я давно полностью перешёл на эту клавиатурную комбинацию и сильно привык к ней.

К моему большому сожалению, программа KeymapSwitcher, которая поставляется с Haiku, не позволяла задать такое удобное сочетание клавиш для переключения раскладок, из-за чего я постоянно испытывал неудобство при работе с текстом в этой операционной системе. Поэтому я решил немного доработать это приложение и занялся поисками его исходного кода. Оказалось, что эта программа хоть и входит в дистрибутив Haiku, но поставляется отдельно от исходного кода самой операционной системы. Кроме того, приложение доступно в репозитории HaikuPorts и обновляется оно тоже через него. Как мне сообщили, KeymapSwitcher не включили в состав Haiku, потому что планируется реализовать специальное API для смены раскладок клавиатуры и когда-нибудь надобность в этой программе полностью отпадёт.


Программа KeymapSwitcher в операционной системе Haiku с популярной комбинацией клавиш для переключения раскладки клавиатуры.

Несмотря на то, что меня пугали сложностью кода KeymapSwitcher, я довольно быстро нашёл нужное место благодаря комментариям и внедрил в код программы небольшой патч, который очень сильно облегчил мне набор каких-либо текстов в Haiku. Единственный небольшой недочёт, который я так и не смог побороть, заключается в том, что клавишу Opt требуется отпускать для переключения языка. То есть, зажать Opt и пробелом переключаться между выбранными языками не получится. Но это абсолютно никак не мешает переключению языков во время набора текста, поэтому я отправил патч в репозиторий программы и обновил пакет приложения в HaikuPorts, после чего новая версия KeymapSwitcher стала доступна для установки всем пользователям Haiku.

Исходный код проекта: https://github.com/HaikuArchives/KeymapSwitcher

Надеюсь, я не единственный пользователь этого сочетания клавиш для переключения раскладок клавиатуры.

<< Перейти к содержанию

12. Заключение


Изучение Haiku API, а также разрешение различных экзотических проблем, возникших вследствие портирования новых и доработки существующих приложений для этой операционной системы, принесли мне огромное количество ценного опыта и удовольствия. Я смог продвинуть патчи поддержки Haiku в репозитории исходного кода некоторых крупных проектов и познакомился с новыми интересными людьми, так или иначе связанными с этой прекрасной операционной системой.


Различные приложения, запущенные в операционной системе Haiku.

Я искренне надеюсь, что в будущем все сегодняшние проблемы вроде отсутствия аппаратного 3D-ускорения и популярных браузеров, а также слабой поддержки современного железа, будут успешно решены и Haiku получит приток новой крови разработчиков и пользователей, которые по достоинству оценят её уникальные возможности и самобытный дизайн. К счастью, разработка далеко не стоит на месте и уже сегодня на местном форуме этой операционной системы поднимаются горячие темы про 3D-ускорение и про портирование библиотеки GTK+3, а в репозиториях HaikuPorts обсуждается возможность переноса компонента QtWebEngine. Порт GTK+3 может повлечь за собой возможность запуска и работы популярных браузеров Firefox и Chromium, а QtWebEngine позволит использовать движок Blink в современных браузерах, основанных на фреймворке Qt, таких как Otter Browser или Falkon.

Уже сейчас я могу порекомендовать эту операционную систему тем, у кого имеются старые и слабые ноутбуки или нетбуки, например, вместо дистрибутива Lubuntu или Windows XP. Вы будете поражены тем, насколько быстро и отзывчиво она работает. Да, придётся немного ограничить себя в просмотре некоторых сайтов из-за старых браузеров и кучи глюков, которые связаны с ними, однако для большинства случаев на старом железе это ограничение не является сколько бы то ни было значимым.

Все мои порты и доработки уже опубликованы и доступны для установки всем пользователям Haiku. Все изменения исходного кода доступны в соответствующих репозиториях под их оригинальными лицензиями. В этой работе я использовал огромное количество материалов, основные из них я выделю в полезных ссылках ниже. Огромное спасибо ресурсам stackoverflow.com и google.com за то, что они есть.

1. Официальный сайт операционной системы Haiku.
2. Официальный форум операционной системы Haiku.
3. Официальная документация для пользователей Haiku.
4. Официальная документация для разработчиков Haiku.
5. Описание возможностей графического интерфейса пользователя Haiku.
6. Рекомендации по созданию иконок для приложений Haiku.
7. Описание программы Icon-O-Matic и советы по её использованию.
8. Описание формата векторных иконок HVIF.
9. Официальная документация по фреймворку Interface Kit.
10. Официальная документация по фреймворку Locale Kit.
11. Статья, посвящённая аспектам локализации приложений для Haiku.
12. Официальная документация по программному интерфейсу Layout API.
13. Цикл статей, рассказывающий про внедрение в Haiku программного интерфейса Layout API.
14. GitHub-репозиторий исходного кода операционной системы Haiku.
15. GitHub-репозиторий дерева рецептов HaikuPorts.
16. Интернет версия репозитория готовых HPKG-пакетов Haiku Depot Web.
17. Интересная статья «Haiku: ламповая гик-ОС» в блоге разработчика INSTEAD, Петра Косых.
18. Статья «Haiku: погружение» в блоге разработчика INSTEAD, Петра Косых.
19. Курс уроков программирования «Learning to Program with Haiku» от DarkWyrm.
20. Курс уроков программирования «Programming With Haiku» от DarkWyrm.
21. Публикация «Есть ли жизнь на Haiku?» на ресурсе Linux.org.ru, от меня.
22. Конференция в Telegram IM, посвящённая обсуждению операционной системы Haiku.

Поздравляю всех пользователей ресурса habr с наступающим Новым Годом и желаю им счастливых рождественских праздников! Добра вам, ребята, в новом 2019 году!

<< Перейти к содержанию
Tags:
Hubs:
Total votes 75: ↑71 and ↓4+67
Comments16

Articles