СофтХабрахабр

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

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

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

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

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

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

Продвинутое управление окнами в операционной системе 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'ом.

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

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

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

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

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

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

Стоит отметить, что это приложение не просто является GUI-оболочкой над существующим пакетным менеджером, но и реализует дополнительную функциональность. Благодаря этому инструменту неискушённые и начинающие пользователи Haiku могут удобно управлять своей пакетной базой. Кроме того, у 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'а, о котором я написал в разделе выше, поэтому для осуществления своей цели я решил схитрить и разобрать какой-нибудь системный пакет, чтобы посмотреть как он устроен внутри и сделать по аналогии.

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

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

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

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

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

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

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

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

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

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

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 и поэтому будут очень полезны любому человеку, который хочет начать создавать приложения для этой операционной системы.

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

Во-первых, для запуска внешнего приложения, нужно было найти аналог класса QProcess или POSIX-функции execve(), которая, к слову, тоже отлично работает в операционной системе Haiku, однако я решил, что использовать родные средства будет предпочтительнее, но на всякий случай оставил возможность запуска приложений и через POSIX-функцию. Для моего проекта нужно было выбрать подходящие классы Haiku API, которые позволяли бы реализовать задуманную функциональность. В нём нашёлся подходящий метод Launch(), позволяющий задать путь до исполняемого файла и передать ему аргументы. Класс BRoster, занимающийся межпроцессным взаимодействием, отлично подходил для этой цели. В 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 не поддерживает макросы с переменным количеством аргументов.

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

Я решил остановиться на создании статической библиотеки, в которой бы имелось два класса, предназначенных для унаследования от них собственных производных классов. Теперь мне следовало подумать о простенькой архитектуре проекта. Второй класс, BeAboutWindow, просто отвечает за открытие диалога «О программе...» с информацией, которая показывается в отдельном окне. Первый и самый важный класс, BeLauncherBase, отвечает за создание главного окна лаунчера, передачу всех пользовательских параметров и предоставляет возможность добавления собственных GUI-элементов. Таким образом, программисту для создания своего лаунчера, например, для игры 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;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// Error
const char* args[] = ;
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 не умеет обрабатывать атрибут viewBox, однако, если найти простой SVG-файл без этого атрибута, отредактировать его с помощью Inkscape и сохранить как Plain SVG file, то он откроется и в программе Icon-O-Matic. Дорабатывая свой проект, я нашёл причину по которой программа Icon-O-Matic не открывает SVG-файлы, созданные с помощью векторного редактора Inkscape. Дополнительно я добавил в ReadMe-файл проекта небольшую инструкцию о том, как создавать иконки для своих лаунчеров с помощью Inkscape. Поэтому я положил в свой репозиторий такой специально подготовленный SVG-файл, который можно редактировать и который будет открываться в Icon-O-Matic без проблем.

А я вот позже нашёл одну проблему, которую не смогли обнаружить они. Код своего проекта я решил проверить самыми различными статическими анализаторами, но никаких серьёзных проблем они не нашли. Дело в том, что статический метод 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++ требуется в любом случае.

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

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

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

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, игру всех времён и народов?

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

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

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

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

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

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

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

Коллаж из скриншотов игры 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-игры было практически невозможно. Соответственно, эта история в процессе игры быстро раздувалась и всё начинало сильно тормозить. Оказывается, дело было в том, что по умолчанию события курсора мыши передавались со всей его историей передвижения по экрану. Хотя, отсутствие 3D-ускорения на слабом железе даёт о себе знать. Отключение подобной возможности в порте SDL2 на Haiku решило эту проблему и в Half-Life теперь можно играть без особых проблем. Но тут поможет лишь добавление в видеодрайвера операционной системы аппаратного ускорения хотя бы для тех GPU, которые встроены в популярные процессоры Intel. И если игра прилично работает в окне и вообще не тормозит, то в полноэкранном режиме значительно снижается FPS.

Исходный код проекта: 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.

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

Скриншот игры 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 этого вылета не было. Такие выкрутасы порт библиотеки SDL2 на Haiku не позволял проворачивать. Оказалось, всё дело было в том, что при каждом изменении разрешения разрушалось и снова создавалось окно SDL_Window, при этом OpenGL-рендерер вовремя не мог переключиться и пытался что-то там рисовать в разрушенном окне. Это помогло убрать вылет, но добавило дополнительное ограничение: теперь, чтобы активировать полноэкранный режим, требуется перезапустить движок. Все простые попытки решения этой проблемы не помогали и мне пришлось серьёзно влезть в логику и изменить поведение таким образом, чтобы окно при смене разрешения не разрушалось, а просто изменялись его параметры.

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

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 движок просил выставить переменную окружения с именем SERIOUS_MHZ и задать в ней соответствующую частоту, что меня очень сильно удивило. Следующая проблема, с которой я столкнулся, заключалась в невозможности определения частоты процессора на архитектуре x86, хотя на x86_64 всё работало нормально. Полазив по исходному коду игры, я долго не мог найти источник проблемы и даже написал кусочек кода, который с помощью 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.

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

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

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

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

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

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

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

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

Исходный код проекта: 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, который отлично подходит для реализации подобных диалогов. Ещё я помнил про особенности управления памятью в этом классе, о которых я писал выше: его объекты можно создавать только в куче, и нельзя создавать на стеке, так как после вызова метода Go() и последующего действия пользователя он удаляет сам себя. Единственное, что меня смущало, это то, что я не был уверен в том, что в диалоге, который конструирует BAlert, можно разместить более трёх кнопок. Проведя некоторые эксперименты, я развеял все свои сомнения, унаследовался от этого класса и начал писать реализацию.

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

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

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

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

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

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 отсутствует поддержка китайских иероглифов. Поэтому, к примеру, установить какую-нибудь китайскую надпись в заголовок окна, нельзя. А вот текст на русском языке устанавливается без проблем.

Оказалось, что древнейший компилятор GCC 2. При подготовке пакета я столкнулся с ошибкой сборки под архитектуру x86_gcc2, которая активирована в рецепте библиотеки SDL2. 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 в upstream библиотеки SDL2. Я отправил патчи реализации диалогов SDL2 в репозиторий HaikuPorts, благодаря чему теперь движки Xash3D и Serious Engine могут корректно выдавать пользователю какую-либо информацию, например, об ошибках. Хотя работа по переносу наших патчей немного усложнилась из-за недавнего переименования префиксов функций с BE_* на HAIKU_*, но это не является такой уж серьёзной проблемой.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть