В настоящее время я работаю над проектом и для обучения хочу написать себе оконную систему с кодом, специфичным для платформы.
Итак, я использую препроцессор, чтобы во время компиляции узнать, на какой платформе я работаю, но я не знаю, как определить оконную систему в Linux. Мне это очень нужно, потому что приложения X11 и Wayland несовместимы; поэтому мне придется писать разные файлы.
Вот мой файл с опциями препроцессора, который я нашел на этом сайте:
// Windows OS
#if defined(_WIN32) || defined(_WIN64)
#define PLATFORM_WINDOWS 1
// Linux OS
#elif defined(__linux__)
#define PLATFORM_LINUX 1
#if defined(WAYLAND)
#define LINUX_ON_WAYLAND
#elif defined(X11)
#define LINUX_ON_X11
#endif
// Unix OS
#elif defined(__unix) || defined(__unix__)
#define PLATFORM_UNIX 1
// Mac OS
#elif defined(__APPLE__) || defined(__MACH__)
#define PLATFORM_MACOS
// Unknown OS
#else
#error "Unknown platform."
#endif
Как видите, я добавил заполнители для WAYLAND и X11, но есть ли способ заставить такую систему работать?
Откуда макрос мог знать? Они оцениваются во время компиляции, но один или другой используется во время выполнения. Обычно системы сборки работают в изолированных средах и используются в серьезных средах (например, фермах сборки дистрибутивов Linux), которые вообще не имеют доступа к графическому интерфейсу. Решения о том, какие дополнительные зависимости включить, затем принимаются с помощью таких инструментов, как autoconf; существует некоторый автоматический выбор, основанный на том, какие заголовки установлены, но помимо этого у вас есть параметры, передаваемые инструментам командной строки, где люди создают пакеты с конфигурацией, явно выбирающей параметры.
Вы можете использовать систему, подобную autoconf
, чтобы определить, доступны ли заголовки X11 или Wayland (отдельно от запуска сборки и до него). Если у вас есть доступ к C23 везде, где это актуально, возможно, вы можете использовать новый __has_include(headername)
, чтобы решить, что вы используете — возможно, тестируя основной заголовок Wayland и предполагая, что Wayland присутствует, и тестируя основной заголовок X11, предполагая X11. если он присутствует, и жалуется, если ни один из них недоступен в Linux.
То, что вы представляете себе выбор между Wayland и X11 во время сборки, означает, что вы должны планировать наличие источника, поддерживающего оба. В этом случае создайте оба и выберите между ними во время выполнения. Существует множество способов сделать это. Альтернативно, просто выберите один. Не существует закона, согласно которому ваше программное обеспечение должно поддерживать все мыслимые конфигурации системы. Особенно если это личный проект, как кажется. И если ваш выбор — X, то вы, вероятно, все равно сможете запустить его поверх Wayland, используя соответствующий X-сервер.
@Someprogrammerdude Я уже это сделал. Вдохновленный Трэвисом Вроманом и его игровым движком Kohi (ссылка), у меня есть интерфейс в шапке platform.h
, а реализация для каждой ОС имеет свою собственную подпапку. Но чтобы знать, в какой подпапке использовать определения кода для компиляции, я использую защиту заголовков, например #ifdef PLATFORM_*** [...] #endif
. Если у вас есть идеи получше, добро пожаловать.
@JohnBollinger Как объяснено в моем комментарии выше, я использую своего рода интерфейс для своей реализации. Следовательно, как я могу выбрать во время выполнения, какую реализацию? Я знаю, что есть переменная среды под названием XDG_SESSION_TYPE
, достаточно ли будет использования такой команды, как system("echo $XDG_SESSION_TYPE")
?
@JonathanLeffler Я нахожусь на Arch (кстати), так что да, сейчас я работаю с C23 (потому что я знаю, что этот проект будет хобби на долгие годы, поэтому я хочу использовать C23, потому что в долгосрочной перспективе я буду рад иметь его все эти новые функции). Это будет инструмент, работающий во время компиляции или во время выполнения? Есть ли какое-либо преимущество между выполнением этого во время компиляции или во время выполнения? И будет ли мое решение выше с использованием XDG_SESSION_TYPE
менее подвержено ошибкам?
Я не уверен, что у механизма __has_include
есть преимущество; Я еще ни для чего его не использовал, но он существует. Я склонен предпочесть какой-то этап настройки (autoconf
— самый известный — и, вероятно, самый ненавистный — но он далеко не единственный и может быть для вас излишним). Я не уверен в относительных преимуществах времени компиляции и определения того, какую оконную систему использовать во время выполнения. В переходный период (следующее десятилетие или около того), пока Wayland борется за использование X11, время выполнения может быть лучше — создайте оба, если на ваших машинах есть обе установки для разработки.
Тестирование будет проблемой — вам нужно будет каким-то образом переключаться между X11 и Wayland, либо перезапуская машину, либо имея несколько машин, некоторые с X11, а некоторые с Wayland.
Цель немного расплывчата: x11 можно использовать с разными API (привязки xlib или xcb), есть также xwayland для запуска приложений x11 под Wayland и так далее.
Думаю, я нашел два достаточно хороших решения.
Первый работает во время компиляции: просто вручную установите флаг типа -DBUILDING_WITH_X11
или -DBUILDING_WITH_WAYLAND
, а затем проверьте, что при компиляции определен только один флаг. Пример моего предыдущего кода с изменением:
[...]
// Linux OS
#elif defined(__linux__)
#define PLATFORM_LINUX 1
#if defined(BUILDING_WITH_X11) && defined(BUILDING_WITH_WAYLAND)
#error "You can't build for x11 and Wayland at the same time."
#elif !defined(BUILDING_WITH_X11) && !defined(BUILDING_WITH_WAYLAND)
#error "You need to define at least one windowing protocol to use."
#endif
[...]
Второй работает во время выполнения. Он использует команду secure_getenv()
из stdlib.h
(с расширением _GNU_SOURCE
), которая извлекает значение переменной среды в виде строки. Поэтому я могу использовать это с условиями, чтобы выбрать, какие функции я использую:
if (strcmp(secure_getenv("XDG_SESSION_TYPE"), "wayland") == 0) {
printf("We're on wayland.\n");
} else if (strcmp(secure_getenv("XDG_SESSION_TYPE"), "x11") == 0) {
printf("We're on X11.\n");
} else {
printf("wtf ?\n");
}
(Примечание: прежде чем комментировать это, я предпочитаю явно указывать значение, которое мы хотим получить, если это не просто true false, и здесь, как и во многих std-библиотеках, 0 является эквивалентом true, что раздражает)
Я действительно не знаю, какой из них лучший. Инстинктивно первый кажется самым простым и я видел большие программы (вроде glfw) имеют разные сборки для x11 и wayland (теперь, начиная с glfw3.4, нет glfw-x11 или glfw-wayland, это просто один и тот же пакет) так что мне не нужно стыдиться того, что я не сделал самую большую кроссплатформенную вещь всех времен.
В любом случае, если у людей есть замечания, я буду очень рад изменить свое мнение и, наоборот, отмечу это как свое решение.
Главное — абстракции. Поместите все, что не зависит от базовой системы отображения (X11 или Wayland), в отдельную библиотеку, все, что зависит от X11, в одну библиотеку и все, что зависит от Wayland, в третью библиотеку. Сделайте интерфейсы (API) одинаковыми для обеих библиотек. Затем вы создаете небольшую и простую связующую программу и настраиваете систему сборки для связи с библиотекой X11 или с библиотекой Wayland. При желании сделайте библиотеки системы отображения динамическими (файлы
.so
), и программа склеивания загрузит их во время выполнения. Использование подобных макросов просто превратит ваш код в беспорядок, который невозможно поддерживать.