Skip to content

Instantly share code, notes, and snippets.

@maidis
Created February 15, 2026 19:35
Show Gist options
  • Select an option

  • Save maidis/368af226d08b1a3a3565ecd9beb8237d to your computer and use it in GitHub Desktop.

Select an option

Save maidis/368af226d08b1a3a3565ecd9beb8237d to your computer and use it in GitHub Desktop.
SFML on Haiku
--- /Users/anilozbek/.gemini/antigravity/scratch/sfml_port/temp_extract/SFML/extlibs/headers/miniaudio/miniaudio.h 2026-02-12 01:36:54
+++ /Users/anilozbek/.gemini/antigravity/scratch/sfml_port/SFML/extlibs/headers/miniaudio/miniaudio.h 2026-02-15 20:05:17
@@ -6547,6 +6547,9 @@
#define MA_SUPPORT_JACK /* JACK is technically supported on Windows, but I don't know how many people use it in practice... */
#endif
#endif
+#endif
+#if defined(MA_HAIKU)
+ #define MA_SUPPORT_HAIKU
#endif
#if defined(MA_UNIX) && !defined(MA_ORBIS) && !defined(MA_PROSPERO)
#if defined(MA_LINUX)
@@ -6612,6 +6615,9 @@
#if defined(MA_SUPPORT_SNDIO) && !defined(MA_NO_SNDIO) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_SNDIO))
#define MA_HAS_SNDIO
#endif
+#if defined(MA_SUPPORT_HAIKU) && !defined(MA_NO_HAIKU) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_HAIKU))
+ #define MA_HAS_HAIKU
+#endif
#if defined(MA_SUPPORT_AUDIO4) && !defined(MA_NO_AUDIO4) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_AUDIO4))
#define MA_HAS_AUDIO4
#endif
@@ -6665,6 +6671,7 @@
ma_backend_coreaudio,
ma_backend_sndio,
ma_backend_audio4,
+ ma_backend_haiku,
ma_backend_oss,
ma_backend_pulseaudio,
ma_backend_alsa,
@@ -7597,6 +7604,12 @@
{
int _unused;
} audio4;
+#endif
+#ifdef MA_SUPPORT_HAIKU
+ struct
+ {
+ int _unused;
+ } haiku;
#endif
#ifdef MA_SUPPORT_OSS
struct
@@ -7929,6 +7942,12 @@
int fdPlayback;
int fdCapture;
} audio4;
+#endif
+#ifdef MA_SUPPORT_HAIKU
+ struct
+ {
+ void* pPlayer;
+ } haiku;
#endif
#ifdef MA_SUPPORT_OSS
struct
@@ -17971,6 +17990,7 @@
{ma_backend_coreaudio, "Core Audio"},
{ma_backend_sndio, "sndio"},
{ma_backend_audio4, "audio(4)"},
+ {ma_backend_haiku, "Haiku"},
{ma_backend_oss, "OSS"},
{ma_backend_pulseaudio, "PulseAudio"},
{ma_backend_alsa, "ALSA"},
@@ -18053,6 +18073,12 @@
#endif
case ma_backend_audio4:
#if defined(MA_HAS_AUDIO4)
+ return MA_TRUE;
+ #else
+ return MA_FALSE;
+ #endif
+ case ma_backend_haiku:
+ #if defined(MA_HAS_HAIKU)
return MA_TRUE;
#else
return MA_FALSE;
@@ -28587,8 +28613,322 @@
}
#endif /* ALSA */
+
+/******************************************************************************
+Haiku Backend
+******************************************************************************/
+#ifdef MA_HAS_HAIKU
+#ifdef __cplusplus
+#include <SoundPlayer.h>
+#include <media/MediaDefs.h>
+#include <signal.h>
+#include <OS.h>
+
+static void ma_haiku_callback(void* pCookie, void* pBuffer, size_t size, const media_raw_audio_format& format)
+{
+ ma_device* pDevice = (ma_device*)pCookie;
+
+ if (pDevice == NULL || pBuffer == NULL) {
+ return;
+ }
+
+ /*
+ BSoundPlayer starts its thread immediately, which means this callback can be fired
+ before ma_device_start() has been called. We MUST guard against this to avoid
+ accessing uninitialized state or firing data callbacks too early.
+ */
+ if (ma_device_get_state(pDevice) != ma_device_state_started) {
+ memset(pBuffer, 0, size);
+ return;
+ }
+
+ /* Boost priority on first callback to ensure stable RT performance */
+ #ifndef B_AUDIO_PLAYBACK_PRIORITY
+ #define B_AUDIO_PLAYBACK_PRIORITY 120
+ #endif
+ static thread_id lastCheckedThread = -1;
+ thread_id currentThread = find_thread(NULL);
+ if (currentThread != lastCheckedThread) {
+ set_thread_priority(currentThread, B_AUDIO_PLAYBACK_PRIORITY);
+ lastCheckedThread = currentThread;
+ }
+
+ ma_uint32 targetChannels = format.channel_count;
+ ma_format targetFormat;
+ switch (format.format) {
+ case media_raw_audio_format::B_AUDIO_FLOAT: targetFormat = ma_format_f32; break;
+ case media_raw_audio_format::B_AUDIO_INT: targetFormat = ma_format_s32; break;
+ case media_raw_audio_format::B_AUDIO_SHORT: targetFormat = ma_format_s16; break;
+ case media_raw_audio_format::B_AUDIO_UCHAR: targetFormat = ma_format_u8; break;
+ default: targetFormat = ma_format_unknown; break;
+ }
+
+ if (targetFormat == ma_format_unknown) {
+ memset(pBuffer, 0, size);
+ return;
+ }
+
+ ma_uint32 bytesPerFrame = ma_get_bytes_per_frame(targetFormat, targetChannels);
+ if (bytesPerFrame > 0) {
+ ma_uint32 frameCount = (ma_uint32)(size / bytesPerFrame);
+
+ /*
+ We must ensure that the device format matches targetFormat.
+ If not, we risk buffer overflow or garbage audio.
+ */
+ if (pDevice->playback.format != targetFormat || pDevice->playback.channels != targetChannels) {
+ memset(pBuffer, 0, size);
+ } else {
+ ma_device__handle_data_callback(pDevice, pBuffer, NULL, frameCount);
+ }
+ }
+}
+#endif
+
+static ma_result ma_device_init__haiku(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture)
+{
+ MA_ASSERT(pDevice != NULL);
+ MA_ASSERT(pConfig != NULL);
+
+ (void)pDescriptorCapture;
+
+#ifdef __cplusplus
+ ma_uint32 targetSampleRate = pConfig->sampleRate;
+ if (targetSampleRate == 0) {
+ targetSampleRate = 44100;
+ }
+
+ ma_uint32 targetChannels = pConfig->playback.channels;
+ if (targetChannels == 0) {
+ targetChannels = 2;
+ }
+
+ ma_log_postf(ma_context_get_log(pDevice->pContext), MA_LOG_LEVEL_DEBUG, "[Haiku] Requesting sample rate %u, channels %u\n", targetSampleRate, targetChannels);
+
+ media_raw_audio_format format;
+ MA_ZERO_OBJECT(&format);
+ format.frame_rate = (float)targetSampleRate;
+ format.channel_count = (ma_uint32)targetChannels;
+
+ ma_uint32 bytesPerSample = 2;
+ if (pConfig->playback.format == ma_format_f32) {
+ format.format = media_raw_audio_format::B_AUDIO_FLOAT;
+ bytesPerSample = 4;
+ } else if (pConfig->playback.format == ma_format_s32) {
+ format.format = media_raw_audio_format::B_AUDIO_INT;
+ bytesPerSample = 4;
+ } else if (pConfig->playback.format == ma_format_u8) {
+ format.format = media_raw_audio_format::B_AUDIO_UCHAR;
+ bytesPerSample = 1;
+ } else {
+ format.format = media_raw_audio_format::B_AUDIO_SHORT;
+ bytesPerSample = 2;
+ }
+
+ format.byte_order = B_MEDIA_HOST_ENDIAN;
+
+ if (pConfig->periodSizeInFrames > 0) {
+ format.buffer_size = pConfig->periodSizeInFrames * bytesPerSample * targetChannels;
+ } else {
+ /* Default to 16k frames for maximal safety on virtual machines */
+ format.buffer_size = 16384 * bytesPerSample * targetChannels;
+ }
+
+ /*
+ Mask signals to prevent the audio thread from being interrupted by the OS.
+ This pattern is used by SDL's Haiku backend and is critical for stability.
+ */
+ sigset_t omask, mask;
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGHUP);
+ sigaddset(&mask, SIGINT);
+ sigaddset(&mask, SIGQUIT);
+ sigaddset(&mask, SIGPIPE);
+ sigaddset(&mask, SIGALRM);
+ sigaddset(&mask, SIGTERM);
+ sigaddset(&mask, SIGWINCH);
+ sigprocmask(SIG_BLOCK, &mask, &omask);
+
+ BSoundPlayer* pPlayer = new BSoundPlayer(&format, "miniaudio", ma_haiku_callback, NULL, (void*)pDevice);
+
+ // Restore signal mask
+ sigprocmask(SIG_SETMASK, &omask, NULL);
+
+ if (pPlayer == NULL || pPlayer->InitCheck() != B_OK) {
+ if (pPlayer != NULL) {
+ delete pPlayer;
+ }
+ return MA_ERROR;
+ }
+
+ pDevice->haiku.pPlayer = (void*)pPlayer;
+
+ /* Haiku might have adjusted the format. Inform miniaudio. */
+ const media_raw_audio_format& actualFormat = pPlayer->Format();
+
+ pDescriptorPlayback->channels = actualFormat.channel_count;
+ pDescriptorPlayback->sampleRate = (ma_uint32)actualFormat.frame_rate;
+
+ switch (actualFormat.format) {
+ case media_raw_audio_format::B_AUDIO_FLOAT: pDescriptorPlayback->format = ma_format_f32; break;
+ case media_raw_audio_format::B_AUDIO_INT: pDescriptorPlayback->format = ma_format_s32; break;
+ case media_raw_audio_format::B_AUDIO_SHORT: pDescriptorPlayback->format = ma_format_s16; break;
+ case media_raw_audio_format::B_AUDIO_UCHAR: pDescriptorPlayback->format = ma_format_u8; break;
+ default:
+ if (pPlayer != NULL) {
+ delete pPlayer;
+ }
+ return MA_INVALID_ARGS;
+ }
+
+ ma_uint32 actualBytesPerFrame = ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels);
+ if (actualBytesPerFrame > 0) {
+ pDescriptorPlayback->periodSizeInFrames = actualFormat.buffer_size / actualBytesPerFrame;
+ }
+
+ return MA_SUCCESS;
+#else
+ (void)pDevice;
+ (void)pConfig;
+ (void)pDescriptorPlayback;
+ return MA_NOT_IMPLEMENTED;
+#endif
+}
+
+static ma_result ma_device_uninit__haiku(ma_device* pDevice)
+{
+ MA_ASSERT(pDevice != NULL);
+
+#ifdef __cplusplus
+ BSoundPlayer* pPlayer = (BSoundPlayer*)pDevice->haiku.pPlayer;
+ if (pPlayer != NULL) {
+ pPlayer->SetHasData(false);
+ pPlayer->Stop();
+
+ /*
+ Mask signals during deletion because BSoundPlayer's destructor joins the audio thread.
+ */
+ sigset_t omask, mask;
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGHUP);
+ sigaddset(&mask, SIGINT);
+ sigaddset(&mask, SIGQUIT);
+ sigaddset(&mask, SIGPIPE);
+ sigaddset(&mask, SIGALRM);
+ sigaddset(&mask, SIGTERM);
+ sigaddset(&mask, SIGWINCH);
+ sigprocmask(SIG_BLOCK, &mask, &omask);
+
+ delete pPlayer;
+
+ sigprocmask(SIG_SETMASK, &omask, NULL);
+ }
+ pDevice->haiku.pPlayer = NULL;
+ return MA_SUCCESS;
+#else
+ (void)pDevice;
+ return MA_NOT_IMPLEMENTED;
+#endif
+}
+
+static ma_result ma_device_start__haiku(ma_device* pDevice)
+{
+ MA_ASSERT(pDevice != NULL);
+
+#ifdef __cplusplus
+ BSoundPlayer* pPlayer = (BSoundPlayer*)pDevice->haiku.pPlayer;
+ if (pPlayer != NULL) {
+ pPlayer->Start();
+ pPlayer->SetHasData(true);
+ }
+ return MA_SUCCESS;
+#else
+ (void)pDevice;
+ return MA_NOT_IMPLEMENTED;
+#endif
+}
+
+static ma_result ma_device_stop__haiku(ma_device* pDevice)
+{
+ MA_ASSERT(pDevice != NULL);
+
+#ifdef __cplusplus
+ BSoundPlayer* pPlayer = (BSoundPlayer*)pDevice->haiku.pPlayer;
+ if (pPlayer != NULL) {
+ pPlayer->SetHasData(false);
+ pPlayer->Stop();
+ }
+ return MA_SUCCESS;
+#else
+ (void)pDevice;
+ return MA_NOT_IMPLEMENTED;
+#endif
+}
+
+static ma_result ma_context_enumerate_devices__haiku(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData)
+{
+ ma_device_info info;
+
+ MA_ASSERT(pContext != NULL);
+ MA_ASSERT(callback != NULL);
+
+ MA_ZERO_OBJECT(&info);
+ ma_strncpy_s(info.name, sizeof(info.name), "Haiku Media Kit", MA_MAX_DEVICE_NAME_LENGTH);
+
+ callback(pContext, ma_device_type_playback, &info, pUserData);
+
+ return MA_SUCCESS;
+}
+
+static ma_result ma_context_get_device_info__haiku(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pID, ma_device_info* pInfo)
+{
+ MA_ASSERT(pContext != NULL);
+ MA_ASSERT(pInfo != NULL);
+
+ (void)pID;
+
+ if (deviceType == ma_device_type_capture) {
+ return MA_NO_DEVICE;
+ }
+
+ MA_ZERO_OBJECT(pInfo);
+ ma_strncpy_s(pInfo->name, sizeof(pInfo->name), "Haiku Media Kit", MA_MAX_DEVICE_NAME_LENGTH);
+ pInfo->isDefault = MA_TRUE;
+
+ return MA_SUCCESS;
+}
+
+static ma_result ma_context_uninit__haiku(ma_context* pContext)
+{
+ MA_ASSERT(pContext != NULL);
+ (void)pContext;
+ return MA_SUCCESS;
+}
+
+static ma_result ma_context_init__haiku(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks)
+{
+ MA_ASSERT(pContext != NULL);
+ MA_ASSERT(pCallbacks != NULL);
+
+ (void)pConfig;
+
+ pCallbacks->onContextInit = ma_context_init__haiku;
+ pCallbacks->onContextUninit = ma_context_uninit__haiku;
+ pCallbacks->onContextEnumerateDevices = ma_context_enumerate_devices__haiku;
+ pCallbacks->onContextGetDeviceInfo = ma_context_get_device_info__haiku;
+ pCallbacks->onDeviceInit = ma_device_init__haiku;
+ pCallbacks->onDeviceUninit = ma_device_uninit__haiku;
+ pCallbacks->onDeviceStart = ma_device_start__haiku;
+ pCallbacks->onDeviceStop = ma_device_stop__haiku;
+ pCallbacks->onDeviceRead = NULL;
+ pCallbacks->onDeviceWrite = NULL;
+ pCallbacks->onDeviceDataLoop = NULL;
+
+ return MA_SUCCESS;
+}
+#endif
/******************************************************************************
PulseAudio Backend
@@ -30050,7 +30390,7 @@
}
g_StreamCounter += 1;
- return ((ma_pa_stream_new_proc)pDevice->pContext->pulse.pa_stream_new)((ma_pa_context*)pDevice->pulse.pPulseContext, actualStreamName, ss, cmap);
+ return ((ma_pa_stream_new_proc)pDevice->pContext->pulse.pa_stream_new)((ma_pa_context*)pDevice->pContext->pulse.pPulseContext, actualStreamName, ss, cmap);
}
@@ -30292,7 +30632,7 @@
int error = 0;
const char* devPlayback = NULL;
const char* devCapture = NULL;
- ma_format format = ma_format_unknown;
+ ma_format targetFormat = ma_format_s16;
ma_uint32 channels = 0;
ma_uint32 sampleRate = 0;
ma_pa_sink_info sinkInfo;
@@ -36735,7 +37075,11 @@
#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
+#if defined(__HAIKU__)
+#include <private/audio/soundcard.h>
+#else
#include <sys/soundcard.h>
+#endif
#ifndef SNDCTL_DSP_HALT
#define SNDCTL_DSP_HALT SNDCTL_DSP_RESET
@@ -37354,10 +37698,7 @@
return MA_SUCCESS;
}
-#endif /* OSS */
-
-
-
+#endif
/******************************************************************************
@@ -41311,6 +41652,12 @@
case ma_backend_audio4:
{
pContext->callbacks.onContextInit = ma_context_init__audio4;
+ } break;
+ #endif
+ #ifdef MA_HAS_HAIKU
+ case ma_backend_haiku:
+ {
+ pContext->callbacks.onContextInit = ma_context_init__haiku;
} break;
#endif
#ifdef MA_HAS_OSS
SUMMARY="Simple and Fast Multimedia Library for Haiku"
DESCRIPTION="SFML is a free multimedia C++ API that provides you low and high \
level access to graphics, input, audio, etc."
HOMEPAGE="https://www.sfml-dev.org/"
COPYRIGHT="2007-2024 Laurent Gomila"
LICENSE="Zlib"
REVISION="1"
SOURCE_URI="https://github.com/SFML/SFML/archive/3.0.2.tar.gz"
CHECKSUM_SHA256="4cf7da2af6ad71c77f88414fd53d6110f0011b9ba7144e554b4138e6475739c9"
SOURCE_DIR="SFML-3.0.2"
PATCHES="sfml-only-haiku.patch sfml-miniaudio-haiku.patch"
ARCHITECTURES="all !x86_gcc2"
SECONDARY_ARCHITECTURES="x86"
PROVIDES="
sfml$secondaryArchSuffix = $portVersion
lib:libsfml_audio$secondaryArchSuffix = $portVersion
lib:libsfml_graphics$secondaryArchSuffix = $portVersion
lib:libsfml_network$secondaryArchSuffix = $portVersion
lib:libsfml_system$secondaryArchSuffix = $portVersion
lib:libsfml_window$secondaryArchSuffix = $portVersion
"
REQUIRES="
haiku$secondaryArchSuffix
lib:libFLAC$secondaryArchSuffix
lib:libfreetype$secondaryArchSuffix
lib:libGL$secondaryArchSuffix
lib:libogg$secondaryArchSuffix
lib:libvorbis$secondaryArchSuffix
"
PROVIDES_devel="
sfml${secondaryArchSuffix}_devel = $portVersion
devel:libsfml_audio$secondaryArchSuffix = $portVersion
devel:libsfml_graphics$secondaryArchSuffix = $portVersion
devel:libsfml_network$secondaryArchSuffix = $portVersion
devel:libsfml_system$secondaryArchSuffix = $portVersion
devel:libsfml_window$secondaryArchSuffix = $portVersion
"
REQUIRES_devel="
sfml$secondaryArchSuffix == $portVersion
"
BUILD_REQUIRES="
haiku${secondaryArchSuffix}_devel
devel:libFLAC$secondaryArchSuffix
devel:libfreetype$secondaryArchSuffix
devel:libGL$secondaryArchSuffix
devel:libogg$secondaryArchSuffix
devel:libvorbis$secondaryArchSuffix
"
BUILD_PREREQUIRES="
cmd:cmake
cmd:gcc$secondaryArchSuffix
cmd:make
"
BUILD()
{
cmake -Bbuild -S. -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=$prefix \
-DSFML_BUILD_EXAMPLES=ON
make -C build $jobArgs
}
INSTALL()
{
make -C build install
prepareInstalledDevelLibs \
libsfml-audio \
libsfml-graphics \
libsfml-network \
libsfml-system \
libsfml-window
packageEntries devel \
$developDir
}
diff --git a/CMakeLists.txt b/CMakeLists.txt
index f013d24a..adc2e9f4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -98,6 +98,8 @@ elseif(SFML_OS_MACOS)
set(DEFAULT_INSTALL_MISC_DIR /usr/local/share/SFML)
elseif(SFML_OS_ANDROID)
set(DEFAULT_INSTALL_MISC_DIR ${CMAKE_ANDROID_NDK}/sources/third_party/sfml)
+elseif(SFML_OS_HAIKU)
+ set(DEFAULT_INSTALL_MISC_DIR share/SFML)
endif()
# force building sfml-window, if sfml-graphics module is built
diff --git a/cmake/Config.cmake b/cmake/Config.cmake
index 0d3153a7..ab262499 100644
--- a/cmake/Config.cmake
+++ b/cmake/Config.cmake
@@ -61,6 +61,13 @@ elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Android")
# use the OpenGL ES implementation on Android
set(OPENGL_ES 1)
+elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Haiku")
+ set(SFML_OS_HAIKU 1)
+ set(SFML_OS_UNIX 1)
+
+ # don't use the OpenGL ES implementation on Haiku
+ set(OPENGL_ES 0)
+
# comparing CMAKE_SYSTEM_NAME with "CYGWIN" generates a false warning depending on the CMake version
# let's avoid it so the actual error is more visible
elseif(${CYGWIN})
diff --git a/examples/event_handling/EventHandling.cpp b/examples/event_handling/EventHandling.cpp
index 577dbb64..ada48a4e 100644
--- a/examples/event_handling/EventHandling.cpp
+++ b/examples/event_handling/EventHandling.cpp
@@ -331,8 +331,16 @@ private:
/// \return Application exit code
///
////////////////////////////////////////////////////////////
+#ifdef __HAIKU__
+#include <Application.h>
+#endif
+
int main()
{
+#ifdef __HAIKU__
+ BApplication app("application/x-vnd.sfml-event_handling");
+#endif
+
Application application;
application.run();
}
diff --git a/examples/island/Island.cpp b/examples/island/Island.cpp
index 628c17c5..372da3d2 100644
--- a/examples/island/Island.cpp
+++ b/examples/island/Island.cpp
@@ -424,8 +424,16 @@ void generateTerrain(sf::Vertex* buffer)
/// \return Application exit code
///
////////////////////////////////////////////////////////////
+#ifdef __HAIKU__
+#include <Application.h>
+#endif
+
int main()
{
+#ifdef __HAIKU__
+ BApplication app("application/x-vnd.sfml-island");
+#endif
+
// Create the window of the application
sf::RenderWindow window(sf::VideoMode(windowSize), "SFML Island", sf::Style::Titlebar | sf::Style::Close);
window.setVerticalSyncEnabled(true);
diff --git a/examples/joystick/Joystick.cpp b/examples/joystick/Joystick.cpp
index bca3396d..9236b2b4 100644
--- a/examples/joystick/Joystick.cpp
+++ b/examples/joystick/Joystick.cpp
@@ -87,8 +87,16 @@ void updateValues(unsigned int index)
/// \return Application exit code
///
////////////////////////////////////////////////////////////
+#ifdef __HAIKU__
+#include <Application.h>
+#endif
+
int main()
{
+#ifdef __HAIKU__
+ BApplication app("application/x-vnd.sfml-joystick");
+#endif
+
// Create the window of the application
sf::RenderWindow window(sf::VideoMode({400, 775}), "Joystick", sf::Style::Close);
window.setVerticalSyncEnabled(true);
diff --git a/examples/keyboard/Keyboard.cpp b/examples/keyboard/Keyboard.cpp
index a1155e60..3f7fa098 100644
--- a/examples/keyboard/Keyboard.cpp
+++ b/examples/keyboard/Keyboard.cpp
@@ -749,8 +749,16 @@ sf::String textEventDescription(const sf::Event::TextEntered& textEntered)
/// \return Application exit code
///
////////////////////////////////////////////////////////////
+#ifdef __HAIKU__
+#include <Application.h>
+#endif
+
int main()
{
+#ifdef __HAIKU__
+ BApplication app("application/x-vnd.sfml-keyboard");
+#endif
+
// Create the main window
sf::RenderWindow window(sf::VideoMode({1280, 720}), "Keyboard", sf::Style::Titlebar | sf::Style::Close);
window.setFramerateLimit(25);
diff --git a/examples/opengl/OpenGL.cpp b/examples/opengl/OpenGL.cpp
index 538d19d1..fafb3789 100644
--- a/examples/opengl/OpenGL.cpp
+++ b/examples/opengl/OpenGL.cpp
@@ -38,8 +38,16 @@ std::filesystem::path resourcesDir()
/// \return Application exit code
///
////////////////////////////////////////////////////////////
+#ifdef __HAIKU__
+#include <Application.h>
+#endif
+
int main()
{
+#ifdef __HAIKU__
+ BApplication app("application/x-vnd.sfml-opengl");
+#endif
+
bool exit = false;
bool sRgb = false;
diff --git a/examples/shader/Shader.cpp b/examples/shader/Shader.cpp
index 14761218..41f5016f 100644
--- a/examples/shader/Shader.cpp
+++ b/examples/shader/Shader.cpp
@@ -375,8 +375,16 @@ std::optional<Geometry> tryLoadGeometry()
/// \return Application exit code
///
////////////////////////////////////////////////////////////
+#ifdef __HAIKU__
+#include <Application.h>
+#endif
+
int main()
{
+#ifdef __HAIKU__
+ BApplication app("application/x-vnd.sfml-shader");
+#endif
+
// Exit early if shaders are not available
if (!sf::Shader::isAvailable())
{
diff --git a/examples/stencil/Stencil.cpp b/examples/stencil/Stencil.cpp
index 91917034..cf5e956d 100644
--- a/examples/stencil/Stencil.cpp
+++ b/examples/stencil/Stencil.cpp
@@ -12,8 +12,16 @@
/// \return Application exit code
///
////////////////////////////////////////////////////////////
+#ifdef __HAIKU__
+#include <Application.h>
+#endif
+
int main()
{
+#ifdef __HAIKU__
+ BApplication app("application/x-vnd.sfml-stencil");
+#endif
+
// Create the window of the application with a stencil buffer
sf::RenderWindow window(sf::VideoMode({600, 600}),
"SFML Stencil",
diff --git a/examples/tennis/Tennis.cpp b/examples/tennis/Tennis.cpp
index a808597a..08187f32 100644
--- a/examples/tennis/Tennis.cpp
+++ b/examples/tennis/Tennis.cpp
@@ -34,8 +34,16 @@ std::filesystem::path resourcesDir()
/// \return Application exit code
///
////////////////////////////////////////////////////////////
+#ifdef __HAIKU__
+#include <Application.h>
+#endif
+
int main()
{
+#ifdef __HAIKU__
+ BApplication app("application/x-vnd.sfml-tennis");
+#endif
+
std::random_device rd;
std::mt19937 rng(rd());
diff --git a/examples/vulkan/Vulkan.cpp b/examples/vulkan/Vulkan.cpp
index 100ae283..66210734 100644
--- a/examples/vulkan/Vulkan.cpp
+++ b/examples/vulkan/Vulkan.cpp
@@ -2624,8 +2624,16 @@ private:
/// \return Application exit code
///
////////////////////////////////////////////////////////////
+#ifdef __HAIKU__
+#include <Application.h>
+#endif
+
int main()
{
+#ifdef __HAIKU__
+ BApplication app("application/x-vnd.sfml-vulkan");
+#endif
+
VulkanExample example;
example.run();
diff --git a/examples/window/Window.cpp b/examples/window/Window.cpp
index 9f46a9af..e53fd4d6 100644
--- a/examples/window/Window.cpp
+++ b/examples/window/Window.cpp
@@ -23,8 +23,15 @@
/// \return Application exit code
///
////////////////////////////////////////////////////////////
+#ifdef __HAIKU__
+#include <Application.h>
+#endif
+
int main()
{
+#ifdef __HAIKU__
+ BApplication app("application/x-vnd.sfml-window");
+#endif
// Request a 24-bits depth buffer when creating the window
sf::ContextSettings contextSettings;
contextSettings.depthBits = 24;
diff --git a/include/SFML/Config.hpp b/include/SFML/Config.hpp
index 36a8b1a3..81ebb877 100644
--- a/include/SFML/Config.hpp
+++ b/include/SFML/Config.hpp
@@ -68,6 +68,11 @@
#endif
+#elif defined(__HAIKU__)
+
+// Haiku
+#define SFML_SYSTEM_HAIKU
+
#elif defined(__unix__)
// UNIX system, see which one it is
diff --git a/include/SFML/Window/WindowHandle.hpp b/include/SFML/Window/WindowHandle.hpp
index c15ce07b..3fbd30d1 100644
--- a/include/SFML/Window/WindowHandle.hpp
+++ b/include/SFML/Window/WindowHandle.hpp
@@ -57,7 +57,7 @@ using WindowHandle = void*;
// Window handle is UIWindow (void*) on iOS - UIKit
using WindowHandle = void*;
-#elif defined(SFML_SYSTEM_ANDROID)
+#elif defined(SFML_SYSTEM_ANDROID) || defined(SFML_SYSTEM_HAIKU)
// Window handle is ANativeWindow* (void*) on Android
using WindowHandle = void*;
diff --git a/src/SFML/Audio/CMakeLists.txt b/src/SFML/Audio/CMakeLists.txt
index 7567205c..aea78269 100644
--- a/src/SFML/Audio/CMakeLists.txt
+++ b/src/SFML/Audio/CMakeLists.txt
@@ -198,4 +198,6 @@ endif()
if(SFML_OS_LINUX)
target_link_libraries(sfml-audio PRIVATE dl)
+elseif(SFML_OS_HAIKU)
+ target_link_libraries(sfml-audio PRIVATE media)
endif()
diff --git a/src/SFML/Network/CMakeLists.txt b/src/SFML/Network/CMakeLists.txt
index 872c3a47..143fdf33 100644
--- a/src/SFML/Network/CMakeLists.txt
+++ b/src/SFML/Network/CMakeLists.txt
@@ -47,4 +47,6 @@ sfml_add_library(Network
target_link_libraries(sfml-network PUBLIC SFML::System)
if(SFML_OS_WINDOWS)
target_link_libraries(sfml-network PRIVATE ws2_32)
+elseif(SFML_OS_HAIKU)
+ target_link_libraries(sfml-network PRIVATE network)
endif()
diff --git a/src/SFML/System/CMakeLists.txt b/src/SFML/System/CMakeLists.txt
index 3b7543a4..590060fc 100644
--- a/src/SFML/System/CMakeLists.txt
+++ b/src/SFML/System/CMakeLists.txt
@@ -92,4 +92,6 @@ elseif(SFML_OS_WINDOWS)
target_link_libraries(sfml-system PRIVATE winmm)
elseif(SFML_OS_ANDROID)
target_link_libraries(sfml-system PRIVATE android log)
+elseif(SFML_OS_HAIKU)
+ target_link_libraries(sfml-system PRIVATE root)
endif()
diff --git a/src/SFML/Window/CMakeLists.txt b/src/SFML/Window/CMakeLists.txt
index ae9d90ec..8b5508fe 100644
--- a/src/SFML/Window/CMakeLists.txt
+++ b/src/SFML/Window/CMakeLists.txt
@@ -263,6 +263,26 @@ elseif(SFML_OS_ANDROID)
${SRCROOT}/Android/SensorImpl.cpp
)
source_group("android" FILES ${PLATFORM_SRC})
+elseif(SFML_OS_HAIKU)
+ set(PLATFORM_SRC
+ ${SRCROOT}/Haiku/WindowImplHaiku.hpp
+ ${SRCROOT}/Haiku/WindowImplHaiku.cpp
+ ${SRCROOT}/Haiku/GlContext.hpp
+ ${SRCROOT}/Haiku/GlContext.cpp
+ ${SRCROOT}/Haiku/Utils.hpp
+ ${SRCROOT}/Haiku/Utils.cpp
+ ${SRCROOT}/Haiku/CursorImpl.hpp
+ ${SRCROOT}/Haiku/CursorImpl.cpp
+ ${SRCROOT}/Haiku/ClipboardImpl.hpp
+ ${SRCROOT}/Haiku/ClipboardImpl.cpp
+ ${SRCROOT}/Haiku/InputImpl.cpp
+ ${SRCROOT}/Haiku/JoystickImpl.hpp
+ ${SRCROOT}/Haiku/JoystickImpl.cpp
+ ${SRCROOT}/Haiku/SensorImpl.hpp
+ ${SRCROOT}/Haiku/SensorImpl.cpp
+ ${SRCROOT}/Haiku/VideoModeImpl.cpp
+ )
+ source_group("haiku" FILES ${PLATFORM_SRC})
endif()
# define the sfml-window target
@@ -333,6 +353,8 @@ elseif(SFML_OS_IOS)
target_link_libraries(sfml-window PUBLIC "-framework Foundation" "-framework UIKit" "-framework CoreGraphics" "-framework QuartzCore" "-framework CoreMotion")
elseif(SFML_OS_ANDROID)
target_link_libraries(sfml-window PRIVATE android)
+elseif(SFML_OS_HAIKU)
+ target_link_libraries(sfml-window PRIVATE be game GL device)
endif()
# on some platforms (e.g. Raspberry Pi 3 armhf), GCC requires linking libatomic to use <atomic> features
diff --git a/src/SFML/Window/ClipboardImpl.hpp b/src/SFML/Window/ClipboardImpl.hpp
index b0ed7339..c1fa2e0f 100644
--- a/src/SFML/Window/ClipboardImpl.hpp
+++ b/src/SFML/Window/ClipboardImpl.hpp
@@ -44,4 +44,6 @@
#include <SFML/Window/iOS/ClipboardImpl.hpp>
#elif defined(SFML_SYSTEM_ANDROID)
#include <SFML/Window/Android/ClipboardImpl.hpp>
+#elif defined(SFML_SYSTEM_HAIKU)
+#include <SFML/Window/Haiku/ClipboardImpl.hpp>
#endif
diff --git a/src/SFML/Window/CursorImpl.hpp b/src/SFML/Window/CursorImpl.hpp
index e6a87756..9b195cd6 100644
--- a/src/SFML/Window/CursorImpl.hpp
+++ b/src/SFML/Window/CursorImpl.hpp
@@ -44,4 +44,6 @@
#include <SFML/Window/iOS/CursorImpl.hpp>
#elif defined(SFML_SYSTEM_ANDROID)
#include <SFML/Window/Android/CursorImpl.hpp>
+#elif defined(SFML_SYSTEM_HAIKU)
+#include <SFML/Window/Haiku/CursorImpl.hpp>
#endif
diff --git a/src/SFML/Window/GlContext.cpp b/src/SFML/Window/GlContext.cpp
index 7a9c5cdd..c503deb0 100644
--- a/src/SFML/Window/GlContext.cpp
+++ b/src/SFML/Window/GlContext.cpp
@@ -100,6 +100,11 @@ using ContextType = sf::priv::EaglContext;
#include <SFML/Window/EglContext.hpp>
using ContextType = sf::priv::EglContext;
+#elif defined(SFML_SYSTEM_HAIKU)
+
+#include <SFML/Window/Haiku/GlContext.hpp>
+using ContextType = sf::priv::HaikuContext;
+
#endif
#if defined(SFML_SYSTEM_WINDOWS)
@@ -490,7 +495,8 @@ void GlContext::unregisterUnsharedGlObject(std::shared_ptr<void> object)
// in unshared objects should be the only one existing
const auto iter = std::find_if(unsharedGlObjects->begin(),
unsharedGlObjects->end(),
- [&object](const Impl::UnsharedGlObject& obj) {
+ [&object](const Impl::UnsharedGlObject& obj)
+ {
return (obj.object == object) &&
(obj.contextId == GlContextImpl::CurrentContext::get().id);
});
diff --git a/src/SFML/Window/Haiku/ClipboardImpl.cpp b/src/SFML/Window/Haiku/ClipboardImpl.cpp
new file mode 100644
index 00000000..cbce1359
--- /dev/null
+++ b/src/SFML/Window/Haiku/ClipboardImpl.cpp
@@ -0,0 +1,77 @@
+////////////////////////////////////////////////////////////
+//
+// SFML - Simple and Fast Multimedia Library
+// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org)
+//
+// This software is provided 'as-is', without any express or implied warranty.
+// In no event will the authors be held liable for any damages arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it freely,
+// subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented;
+// you must not claim that you wrote the original software.
+// If you use this software in a product, an acknowledgment
+// in the product documentation would be appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such,
+// and must not be misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source distribution.
+//
+////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////
+// Headers
+////////////////////////////////////////////////////////////
+#include <SFML/Window/Haiku/ClipboardImpl.hpp>
+
+#include <Application.h>
+
+#include <Clipboard.h>
+#include <cstring>
+
+namespace sf
+{
+namespace priv
+{
+
+////////////////////////////////////////////////////////////
+String ClipboardImpl::getString()
+{
+ if (be_clipboard->Lock())
+ {
+ BMessage* clip = be_clipboard->Data();
+ const char* text = NULL;
+ ssize_t textLen = 0;
+
+ if (clip->FindData("text/plain", B_MIME_TYPE, (const void**)&text, &textLen) == B_OK)
+ {
+ be_clipboard->Unlock();
+ return String::fromUtf8(text, text + textLen);
+ }
+ be_clipboard->Unlock();
+ }
+ return "";
+}
+
+
+////////////////////////////////////////////////////////////
+void ClipboardImpl::setString(const String& text)
+{
+ if (be_clipboard->Lock())
+ {
+ be_clipboard->Clear();
+ BMessage* clip = be_clipboard->Data();
+
+ std::string utf8 = text.toUtf8();
+ clip->AddData("text/plain", B_MIME_TYPE, utf8.c_str(), utf8.length());
+
+ be_clipboard->Commit();
+ be_clipboard->Unlock();
+ }
+}
+
+} // namespace priv
+} // namespace sf
diff --git a/src/SFML/Window/Haiku/ClipboardImpl.hpp b/src/SFML/Window/Haiku/ClipboardImpl.hpp
new file mode 100644
index 00000000..04e26f92
--- /dev/null
+++ b/src/SFML/Window/Haiku/ClipboardImpl.hpp
@@ -0,0 +1,62 @@
+////////////////////////////////////////////////////////////
+//
+// SFML - Simple and Fast Multimedia Library
+// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org)
+//
+// This software is provided 'as-is', without any express or implied warranty.
+// In no event will the authors be held liable for any damages arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it freely,
+// subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented;
+// you must not claim that you wrote the original software.
+// If you use this software in a product, an acknowledgment
+// in the product documentation would be appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such,
+// and must not be misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source distribution.
+//
+////////////////////////////////////////////////////////////
+
+#pragma once
+
+////////////////////////////////////////////////////////////
+// Headers
+////////////////////////////////////////////////////////////
+#include <SFML/System/String.hpp>
+
+
+namespace sf
+{
+namespace priv
+{
+////////////////////////////////////////////////////////////
+/// \brief Haiku implementation of clipboard
+///
+////////////////////////////////////////////////////////////
+class ClipboardImpl
+{
+public:
+ ////////////////////////////////////////////////////////////
+ /// \brief Get the content of the clipboard as string data
+ ///
+ /// \return Clipboard contents as a string
+ ///
+ ////////////////////////////////////////////////////////////
+ static String getString();
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Set the content of the clipboard as string data
+ ///
+ /// \param text Clipboard contents
+ ///
+ ////////////////////////////////////////////////////////////
+ static void setString(const String& text);
+};
+
+} // namespace priv
+} // namespace sf
diff --git a/src/SFML/Window/Haiku/CursorImpl.cpp b/src/SFML/Window/Haiku/CursorImpl.cpp
new file mode 100644
index 00000000..6f0a62ef
--- /dev/null
+++ b/src/SFML/Window/Haiku/CursorImpl.cpp
@@ -0,0 +1,167 @@
+////////////////////////////////////////////////////////////
+//
+// SFML - Simple and Fast Multimedia Library
+// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org)
+//
+// This software is provided 'as-is', without any express or implied warranty.
+// In no event will the authors be held liable for any damages arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it freely,
+// subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented;
+// you must not claim that you wrote the original software.
+// If you use this software in a product, an acknowledgment
+// in the product documentation would be appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such,
+// and must not be misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source distribution.
+//
+////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////
+// Headers
+////////////////////////////////////////////////////////////
+#include <SFML/Window/Haiku/CursorImpl.hpp>
+
+#include <SFML/System/Err.hpp>
+
+#include <Bitmap.h>
+
+#include <Cursor.h>
+#include <cstring>
+
+namespace sf
+{
+namespace priv
+{
+
+////////////////////////////////////////////////////////////
+CursorImpl::CursorImpl() : m_cursor(NULL)
+{
+}
+
+
+////////////////////////////////////////////////////////////
+CursorImpl::~CursorImpl()
+{
+ delete m_cursor;
+}
+
+
+////////////////////////////////////////////////////////////
+bool CursorImpl::loadFromPixels(const std::uint8_t* pixels, Vector2u size, Vector2u hotspot)
+{
+ // BCursor expects a BBitmap.
+ // Create a BBitmap from pixels (RGBA).
+ // BBitmap accepts B_RGBA32.
+
+ // Note: older Haiku/BeOS versions used specific cursor format.
+ // Modern Haiku supports creating cursor from bitmap.
+
+ BRect rect(0, 0, (float)size.x - 1, (float)size.y - 1);
+ BBitmap* bitmap = new BBitmap(rect, B_RGBA32);
+
+ if (bitmap->InitCheck() != B_OK)
+ {
+ delete bitmap;
+ return false;
+ }
+
+ // Copy pixels. SFML uses RGBA. Haiku uses B_RGBA32 (BGRA usually on little endian, but let's check).
+ // B_RGBA32 structure: B, G, R, A.
+ // SFML: R, G, B, A.
+
+ std::uint8_t* bits = (std::uint8_t*)bitmap->Bits();
+ int32 bpr = bitmap->BytesPerRow();
+
+ for (unsigned int y = 0; y < size.y; ++y)
+ {
+ for (unsigned int x = 0; x < size.x; ++x)
+ {
+ const std::uint8_t* src = pixels + (x + y * size.x) * 4;
+ std::uint8_t* dst = bits + y * bpr + x * 4;
+
+ dst[0] = src[2]; // B
+ dst[1] = src[1]; // G
+ dst[2] = src[0]; // R
+ dst[3] = src[3]; // A
+ }
+ }
+
+ delete m_cursor;
+ // BCursor(const BBitmap* bitmap, BPoint hotspot)
+ m_cursor = new BCursor(bitmap, BPoint((float)hotspot.x, (float)hotspot.y));
+
+ delete bitmap; // BCursor copies the data? Yes, usually.
+
+ return true;
+}
+
+
+////////////////////////////////////////////////////////////
+bool CursorImpl::loadFromSystem(Cursor::Type type)
+{
+ BCursorID id = B_CURSOR_ID_SYSTEM_DEFAULT;
+
+ switch (type)
+ {
+ case Cursor::Type::Arrow:
+ id = B_CURSOR_ID_SYSTEM_DEFAULT;
+ break;
+ case Cursor::Type::ArrowWait:
+ id = B_CURSOR_ID_PROGRESS;
+ break;
+ case Cursor::Type::Wait:
+ id = B_CURSOR_ID_PROGRESS;
+ break; // Or B_CURSOR_ID_WAIT? (check headers)
+ case Cursor::Type::Text:
+ id = B_CURSOR_ID_I_BEAM;
+ break;
+ case Cursor::Type::Hand:
+ id = B_CURSOR_ID_FOLLOW_LINK;
+ break; // Context menu? Hand usually means link.
+ case Cursor::Type::SizeHorizontal:
+ id = B_CURSOR_ID_RESIZE_EAST_WEST;
+ break;
+ case Cursor::Type::SizeVertical:
+ id = B_CURSOR_ID_RESIZE_NORTH_SOUTH;
+ break;
+ case Cursor::Type::SizeTopLeftBottomRight:
+ id = B_CURSOR_ID_RESIZE_NORTH_WEST_SOUTH_EAST;
+ break;
+ case Cursor::Type::SizeBottomLeftTopRight:
+ id = B_CURSOR_ID_RESIZE_NORTH_EAST_SOUTH_WEST;
+ break;
+ case Cursor::Type::SizeAll:
+ id = B_CURSOR_ID_MOVE;
+ break;
+ case Cursor::Type::Cross:
+ id = B_CURSOR_ID_CROSS_HAIR;
+ break;
+ case Cursor::Type::Help:
+ id = B_CURSOR_ID_HELP;
+ break;
+ case Cursor::Type::NotAllowed:
+ id = B_CURSOR_ID_NOT_ALLOWED;
+ break;
+ }
+
+ delete m_cursor;
+ m_cursor = new BCursor(id);
+
+ return true;
+}
+
+
+////////////////////////////////////////////////////////////
+const BCursor* CursorImpl::getCursor() const
+{
+ return m_cursor;
+}
+
+} // namespace priv
+} // namespace sf
diff --git a/src/SFML/Window/Haiku/CursorImpl.hpp b/src/SFML/Window/Haiku/CursorImpl.hpp
new file mode 100644
index 00000000..c0f91f41
--- /dev/null
+++ b/src/SFML/Window/Haiku/CursorImpl.hpp
@@ -0,0 +1,101 @@
+////////////////////////////////////////////////////////////
+//
+// SFML - Simple and Fast Multimedia Library
+// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org)
+//
+// This software is provided 'as-is', without any express or implied warranty.
+// In no event will the authors be held liable for any damages arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it freely,
+// subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented;
+// you must not claim that you wrote the original software.
+// If you use this software in a product, an acknowledgment
+// in the product documentation would be appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such,
+// and must not be misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source distribution.
+//
+////////////////////////////////////////////////////////////
+
+#pragma once
+
+////////////////////////////////////////////////////////////
+// Headers
+////////////////////////////////////////////////////////////
+#include <SFML/Window/Cursor.hpp>
+#include <SFML/Window/CursorImpl.hpp>
+
+#include <SFML/System/NonCopyable.hpp>
+#include <SFML/System/Vector2.hpp>
+
+#include <InterfaceDefs.h>
+
+class BCursor;
+
+namespace sf
+{
+namespace priv
+{
+////////////////////////////////////////////////////////////
+/// \brief Haiku implementation of cursor
+///
+////////////////////////////////////////////////////////////
+class CursorImpl : NonCopyable
+{
+public:
+ ////////////////////////////////////////////////////////////
+ /// \brief Default constructor
+ ///
+ ////////////////////////////////////////////////////////////
+ CursorImpl();
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Destructor
+ ///
+ ////////////////////////////////////////////////////////////
+ ~CursorImpl();
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Create a cursor with the provided image
+ ///
+ /// \param pixels Array of pixels of the image
+ /// \param size Width and height of the image
+ /// \param hotspot (x,y) location of the hotspot
+ ///
+ /// \return true if the cursor was successfully loaded; false otherwise
+ ///
+ ////////////////////////////////////////////////////////////
+ bool loadFromPixels(const std::uint8_t* pixels, Vector2u size, Vector2u hotspot);
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Create a native system cursor
+ ///
+ /// \param type System cursor type
+ ///
+ /// \return true if the cursor was successfully loaded; false otherwise
+ ///
+ ////////////////////////////////////////////////////////////
+ bool loadFromSystem(Cursor::Type type);
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Get the underlying Haiku cursor
+ ///
+ /// \return Pointer to the BCursor
+ ///
+ ////////////////////////////////////////////////////////////
+ const BCursor* getCursor() const;
+
+private:
+ ////////////////////////////////////////////////////////////
+ // Member data
+ ////////////////////////////////////////////////////////////
+ BCursor* m_cursor; //!< Haiku cursor
+};
+
+} // namespace priv
+} // namespace sf
diff --git a/src/SFML/Window/Haiku/GlContext.cpp b/src/SFML/Window/Haiku/GlContext.cpp
new file mode 100644
index 00000000..3e2a581d
--- /dev/null
+++ b/src/SFML/Window/Haiku/GlContext.cpp
@@ -0,0 +1,258 @@
+////////////////////////////////////////////////////////////
+//
+// SFML - Simple and Fast Multimedia Library
+// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org)
+//
+// This software is provided 'as-is', without any express or implied warranty.
+// In no event will the authors be held liable for any damages arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it freely,
+// subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented;
+// you must not claim that you wrote the original software.
+// If you use this software in a product, an acknowledgment
+// in the product documentation would be appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such,
+// and must not be misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source distribution.
+//
+////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////
+// Headers
+////////////////////////////////////////////////////////////
+#include <SFML/Window/Haiku/GlContext.hpp>
+#include <SFML/Window/Haiku/WindowImplHaiku.hpp>
+
+#include <SFML/System/Err.hpp>
+
+#include <GLView.h>
+
+#include <Window.h>
+#include <dlfcn.h>
+#include <mutex>
+
+namespace sf
+{
+namespace priv
+{
+
+////////////////////////////////////////////////////////////
+namespace
+{
+void ensureGlInit()
+{
+ // Ensure GL lib is loaded explicitly?
+ // Usually linked.
+}
+} // namespace
+
+
+////////////////////////////////////////////////////////////
+HaikuContext::HaikuContext(const ContextSettings& settings) : m_view(NULL)
+{
+ // Create a dummy window for the context
+ // We can't have a view without a window in BGLView (mostly).
+ // Or we can, but it won't be valid for drawing until attached.
+ // SFML assumes context can be active.
+
+ // Create a hidden window
+ createContext(NULL, 32);
+}
+
+
+////////////////////////////////////////////////////////////
+HaikuContext::HaikuContext(const ContextSettings& settings, const WindowImpl* owner, unsigned int bitsPerPixel) :
+m_view(NULL)
+{
+ createContext(owner, bitsPerPixel);
+}
+
+
+////////////////////////////////////////////////////////////
+HaikuContext::HaikuContext(const ContextSettings& settings, const GlContext* shared) : m_view(NULL)
+{
+ // Sharing not easily supported in BGLView legacy API without specific options.
+ // BGLView constructor has Options...
+ // But we normally just create a context.
+ // If shared is provided, we might need to assume it works via shared resources globally or specific BGLView capability.
+ // Standard BGLView doesn't expose "share with" argument explicitly in constructor.
+ // We'll ignore sharing for now or check if Haiku GL supports it automatically.
+ // Note: Mesa usually shares lists if in same address space.
+
+ createContext(NULL, 32);
+}
+
+
+////////////////////////////////////////////////////////////
+HaikuContext::~HaikuContext()
+{
+ if (m_view)
+ {
+ // View must be removed from window?
+ BWindow* win = m_view->Window();
+ if (win)
+ {
+ win->Lock();
+ m_view->RemoveSelf();
+ delete m_view; // Deleting view inside Lock? Allowed.
+ win->Unlock();
+
+ // If we created a dummy window, we should destroy it.
+ // But how do we know if we created a dummy window?
+ // GlContext::createContext needs to store that.
+ // But WindowImplHaiku manages its own window.
+ // If we are ownerless, we need a dummy window.
+ // Let's store "m_window" if we own it.
+ // But GlContext class doesn't have m_window member in generic interface?
+ // We can add it to our private class if needed, or check if owner was null.
+ // Actually, we can attach the dummy window to m_view->Window() and see if its name is "SFML_Dummy".
+
+ if (win && strcmp(win->Title(), "SFML_Dummy") == 0)
+ {
+ win->Lock();
+ win->Quit();
+ }
+ }
+ }
+}
+
+
+////////////////////////////////////////////////////////////
+GlFunctionPointer HaikuContext::getFunction(const char* name)
+{
+ static void* handle = NULL;
+ static std::mutex mutex;
+
+ std::lock_guard<std::mutex> lock(mutex);
+ if (!handle)
+ {
+ handle = dlopen("libGL.so", RTLD_LAZY | RTLD_GLOBAL);
+ }
+
+ if (handle)
+ {
+ return (GlFunctionPointer)dlsym(handle, name);
+ }
+
+ return 0;
+}
+
+
+////////////////////////////////////////////////////////////
+bool HaikuContext::makeCurrent(bool current)
+{
+ if (m_view)
+ {
+ if (current)
+ {
+ m_view->LockGL();
+ // Also need BWindow lock?
+ // BGLView::LockGL() locks the window if needed?
+ // Docs say: "Blocks until the view's window is locked and the context is current."
+ // So yes.
+ }
+ else
+ {
+ m_view->UnlockGL();
+ }
+ return true;
+ }
+ return false;
+}
+
+
+////////////////////////////////////////////////////////////
+void HaikuContext::display()
+{
+ if (m_view)
+ {
+ m_view->SwapBuffers();
+ }
+}
+
+
+////////////////////////////////////////////////////////////
+void HaikuContext::setVerticalSyncEnabled(bool enabled)
+{
+ // BGLView doesn't offer direct VSync control via API without extensions.
+ // Try WGL_EXT_swap_control equivalent?
+ // Use glCheck?
+ // Or BGLView::SetVerticalSync? (doesn't exist).
+ // Haiku R1/Beta might support GL_swap_control via extension.
+}
+
+
+////////////////////////////////////////////////////////////
+void HaikuContext::createContext(const WindowImpl* owner, unsigned int bitsPerPixel)
+{
+ // Flags
+ ulong flags = BGL_RGB | BGL_DOUBLE | BGL_DEPTH;
+ // Stencil? BGL_STENCIL
+ if (m_settings.stencilBits > 0)
+ flags |= BGL_STENCIL;
+ if (m_settings.antiAliasingLevel > 0)
+ {
+ // BGLView doesn't have explicit AA flag in legacy API.
+ // MSAA might be requested differently.
+ }
+
+ // Rect
+ BRect frame(0, 0, 100, 100);
+ BWindow* win = NULL;
+
+ if (owner)
+ {
+ // Attach to owner window
+ // But owner window already has a view?
+ // WindowImplHaiku creates a SFView.
+ // We can create a BGLView as child of that view or properly integrated?
+ // Actually, for OpenGL, the BGLView *should be* the view handling the drawing.
+ // WindowImplHaiku created a SFView (BView).
+ // We probably want to replace it or add BGLView on top?
+ // Or make WindowImplHaiku create BGLView if needed?
+ // But WindowImpl creates window before Context.
+
+ // Solution: Add BGLView to the Window.
+ WindowHandle handle = owner->getNativeHandle();
+ win = (BWindow*)handle;
+ if (win)
+ {
+ if (win->Lock())
+ {
+ frame = win->Bounds();
+ // Create View
+ m_view = new BGLView(frame, "SFML_GL", B_FOLLOW_ALL_SIDES, 0, flags);
+ win->AddChild(m_view); // Add to window (on top of existing view?)
+ // If we have SFView, it handles inputs. BGLView handles drawing.
+ // BGLView might steal input?
+ // We should ensure m_view is behind? Or simple:
+ // BGLView is just for GL context.
+ win->Unlock();
+ }
+ }
+ }
+ else
+ {
+ // Dummy window
+ win = new BWindow(frame, "SFML_Dummy", B_TITLED_WINDOW, B_NOT_RESIZABLE | B_NOT_MOVABLE);
+ m_view = new BGLView(frame, "SFML_GL", B_FOLLOW_ALL_SIDES, 0, flags);
+ win->AddChild(m_view);
+ // Don't show the window
+ }
+
+ if (m_view)
+ {
+ // Update settings
+ m_settings.majorVersion = 1; // Default
+ m_settings.minorVersion = 1;
+ // Query actual values if possible?
+ }
+}
+
+} // namespace priv
+} // namespace sf
diff --git a/src/SFML/Window/Haiku/GlContext.hpp b/src/SFML/Window/Haiku/GlContext.hpp
new file mode 100644
index 00000000..b77165e1
--- /dev/null
+++ b/src/SFML/Window/Haiku/GlContext.hpp
@@ -0,0 +1,133 @@
+////////////////////////////////////////////////////////////
+//
+// SFML - Simple and Fast Multimedia Library
+// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org)
+//
+// This software is provided 'as-is', without any express or implied warranty.
+// In no event will the authors be held liable for any damages arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it freely,
+// subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented;
+// you must not claim that you wrote the original software.
+// If you use this software in a product, an acknowledgment
+// in the product documentation would be appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such,
+// and must not be misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source distribution.
+//
+////////////////////////////////////////////////////////////
+
+#pragma once
+
+////////////////////////////////////////////////////////////
+// Headers
+////////////////////////////////////////////////////////////
+#include <SFML/Window/ContextSettings.hpp>
+#include <SFML/Window/GlContext.hpp>
+#include <SFML/Window/Haiku/WindowImplHaiku.hpp>
+#include <SFML/Window/WindowImpl.hpp>
+
+class BGLView;
+
+namespace sf
+{
+namespace priv
+{
+////////////////////////////////////////////////////////////
+/// \brief Haiku implementation of OpenGL Context
+///
+////////////////////////////////////////////////////////////
+class HaikuContext : public sf::priv::GlContext
+{
+public:
+ ////////////////////////////////////////////////////////////
+ /// \brief Create a new context, not associated to a window
+ ///
+ /// \param settings Creation settings
+ ///
+ ////////////////////////////////////////////////////////////
+ HaikuContext(const ContextSettings& settings);
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Create a new context attached to a window
+ ///
+ /// \param settings Creation settings
+ /// \param owner Pointer to the owner window
+ /// \param bitsPerPixel Bits per pixel
+ ///
+ ////////////////////////////////////////////////////////////
+ HaikuContext(const ContextSettings& settings, const WindowImpl* owner, unsigned int bitsPerPixel);
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Create a new context that shares its settings with another
+ ///
+ /// \param settings Creation settings
+ /// \param shared Context to share the settings with
+ ///
+ ////////////////////////////////////////////////////////////
+ HaikuContext(const ContextSettings& settings, const GlContext* shared);
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Destructor
+ ///
+ ////////////////////////////////////////////////////////////
+ ~HaikuContext();
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Get the address of an OpenGL function
+ ///
+ /// \param name Name of the function to get the address of
+ ///
+ /// \return Address of the OpenGL function, 0 on failure
+ ///
+ ////////////////////////////////////////////////////////////
+ static GlFunctionPointer getFunction(const char* name);
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Activate or deactivate the context as the current target
+ /// for rendering
+ ///
+ /// \param current True to activate, false to deactivate
+ ///
+ /// \return True on success, false on failure
+ ///
+ ////////////////////////////////////////////////////////////
+ bool makeCurrent(bool current) override;
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Display what has been rendered to the context so far
+ ///
+ ////////////////////////////////////////////////////////////
+ void display() override;
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Enable or disable vertical synchronization
+ ///
+ /// \param enabled: True to enable v-sync, false to disable
+ ///
+ ////////////////////////////////////////////////////////////
+ void setVerticalSyncEnabled(bool enabled) override;
+
+private:
+ ////////////////////////////////////////////////////////////
+ /// \brief Create the context
+ ///
+ /// \param owner Window to attach the context to
+ /// \param bitsPerPixel BPP
+ ///
+ ////////////////////////////////////////////////////////////
+ void createContext(const WindowImpl* owner, unsigned int bitsPerPixel);
+
+ ////////////////////////////////////////////////////////////
+ // Member data
+ ////////////////////////////////////////////////////////////
+ BGLView* m_view; //!< The BGLView
+};
+
+} // namespace priv
+} // namespace sf
diff --git a/src/SFML/Window/Haiku/InputImpl.cpp b/src/SFML/Window/Haiku/InputImpl.cpp
new file mode 100644
index 00000000..928f5137
--- /dev/null
+++ b/src/SFML/Window/Haiku/InputImpl.cpp
@@ -0,0 +1,165 @@
+////////////////////////////////////////////////////////////
+//
+// SFML - Simple and Fast Multimedia Library
+// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org)
+//
+// This software is provided 'as-is', without any express or implied warranty.
+// In no event will the authors be held liable for any damages arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it freely,
+// subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented;
+// you must not claim that you wrote the original software.
+// If you use this software in a product, an acknowledgment
+// in the product documentation would be appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such,
+// and must not be misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source distribution.
+//
+////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////
+// Headers
+////////////////////////////////////////////////////////////
+#include <SFML/Window/Haiku/Utils.hpp>
+#include <SFML/Window/InputImpl.hpp>
+#include <SFML/Window/WindowBase.hpp>
+#include <SFML/Window/WindowImpl.hpp>
+
+#include <SFML/System/String.hpp>
+#include <SFML/System/Vector2.hpp>
+
+#include <InterfaceDefs.h>
+#include <Screen.h>
+#include <View.h>
+#include <Window.h>
+
+namespace sf
+{
+namespace priv
+{
+namespace InputImpl
+{
+////////////////////////////////////////////////////////////
+bool isKeyPressed(Keyboard::Key key)
+{
+ key_info info;
+ if (get_key_info(&info) == B_OK)
+ {
+ std::uint32_t haikuKey = keyToHaiku(key);
+ if (haikuKey != 0)
+ {
+ return (info.key_states[haikuKey / 8] & (1 << (7 - (haikuKey % 8)))) != 0;
+ }
+ }
+ return false;
+}
+
+////////////////////////////////////////////////////////////
+bool isKeyPressed(Keyboard::Scancode code)
+{
+ // Scancode support requires delocalizing or specific Haiku mapping
+ return isKeyPressed(localize(code));
+}
+
+////////////////////////////////////////////////////////////
+Keyboard::Key localize(Keyboard::Scancode code)
+{
+ // Stub
+ return Keyboard::Key::Unknown;
+}
+
+////////////////////////////////////////////////////////////
+Keyboard::Scancode delocalize(Keyboard::Key key)
+{
+ // Stub
+ return Keyboard::Scancode::Unknown;
+}
+
+////////////////////////////////////////////////////////////
+String getDescription(Keyboard::Scancode code)
+{
+ return "";
+}
+
+////////////////////////////////////////////////////////////
+bool isMouseButtonPressed(Mouse::Button button)
+{
+ return false; // TODO: Implement global mouse check
+}
+
+////////////////////////////////////////////////////////////
+Vector2i getMousePosition()
+{
+ return Vector2i(0, 0);
+}
+
+////////////////////////////////////////////////////////////
+Vector2i getMousePosition(const WindowBase& relativeTo)
+{
+ // WindowBase handle is a BWindow pointer
+ BWindow* win = reinterpret_cast<BWindow*>(relativeTo.getNativeHandle());
+ if (win && win->Lock())
+ {
+ BPoint p;
+ uint32 b;
+ BView* view = win->ChildAt(0);
+ if (view)
+ {
+ view->GetMouse(&p, &b, false);
+ win->Unlock();
+ return Vector2i(static_cast<int>(p.x), static_cast<int>(p.y));
+ }
+ win->Unlock();
+ }
+ return Vector2i(0, 0);
+}
+
+////////////////////////////////////////////////////////////
+void setMousePosition(Vector2i position)
+{
+ set_mouse_position(static_cast<int32>(position.x), static_cast<int32>(position.y));
+}
+
+////////////////////////////////////////////////////////////
+void setMousePosition(Vector2i position, const WindowBase& relativeTo)
+{
+ BWindow* win = reinterpret_cast<BWindow*>(relativeTo.getNativeHandle());
+ if (win && win->Lock())
+ {
+ BPoint winPos = win->Frame().LeftTop();
+ win->Unlock();
+ set_mouse_position(static_cast<int32>(winPos.x + position.x), static_cast<int32>(winPos.y + position.y));
+ }
+}
+
+////////////////////////////////////////////////////////////
+bool isTouchDown(unsigned int finger)
+{
+ return false;
+}
+
+////////////////////////////////////////////////////////////
+Vector2i getTouchPosition(unsigned int finger)
+{
+ return Vector2i(0, 0);
+}
+
+////////////////////////////////////////////////////////////
+Vector2i getTouchPosition(unsigned int finger, const WindowBase& relativeTo)
+{
+ return Vector2i(0, 0);
+}
+
+////////////////////////////////////////////////////////////
+void setVirtualKeyboardVisible(bool visible)
+{
+}
+
+} // namespace InputImpl
+} // namespace priv
+} // namespace sf
diff --git a/src/SFML/Window/Haiku/JoystickImpl.cpp b/src/SFML/Window/Haiku/JoystickImpl.cpp
new file mode 100644
index 00000000..4ea5159e
--- /dev/null
+++ b/src/SFML/Window/Haiku/JoystickImpl.cpp
@@ -0,0 +1,166 @@
+////////////////////////////////////////////////////////////
+//
+// SFML - Simple and Fast Multimedia Library
+// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org)
+//
+// This software is provided 'as-is', without any express or implied warranty.
+// In no event will the authors be held liable for any damages arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it freely,
+// subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented;
+// you must not claim that you wrote the original software.
+// If you use this software in a product, an acknowledgment
+// in the product documentation would be appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such,
+// and must not be misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source distribution.
+//
+////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////
+// Headers
+////////////////////////////////////////////////////////////
+#include <SFML/Window/Haiku/JoystickImpl.hpp>
+
+#include <SFML/System/Err.hpp>
+
+#include <Storage/Directory.h>
+#include <Storage/Entry.h>
+#include <Storage/Path.h>
+
+#include <cstring>
+
+namespace sf
+{
+namespace priv
+{
+////////////////////////////////////////////////////////////
+void JoystickImpl::initialize()
+{
+ // Nothing to do
+}
+
+
+////////////////////////////////////////////////////////////
+void JoystickImpl::cleanup()
+{
+ // Nothing to do
+}
+
+
+////////////////////////////////////////////////////////////
+bool JoystickImpl::isConnected(unsigned int index)
+{
+ // Use BJoystick to check presence
+ BJoystick joystick;
+ char name[B_OS_NAME_LENGTH];
+
+ // We need to iterate over devices to find the index-th one.
+ // Haiku joysticks are usually under /dev/joystick/usb/0, etc.
+ // BJoystick::CountDevices() returns the number of devices.
+
+ int count = joystick.CountDevices();
+ if (index < (unsigned int)count)
+ {
+ return true;
+ }
+ return false;
+}
+
+
+////////////////////////////////////////////////////////////
+bool JoystickImpl::open(unsigned int index)
+{
+ int count = m_joystick.CountDevices();
+ if (index >= (unsigned int)count)
+ return false;
+
+ // Get the name of the device at index
+ if (m_joystick.GetDeviceName(index, m_name) == B_OK)
+ {
+ if (m_joystick.Open(m_name) == B_OK)
+ {
+ m_index = index;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+////////////////////////////////////////////////////////////
+void JoystickImpl::close()
+{
+ m_joystick.Close();
+}
+
+
+////////////////////////////////////////////////////////////
+JoystickCaps JoystickImpl::getCapabilities() const
+{
+ JoystickCaps caps;
+
+ caps.buttonCount = static_cast<unsigned int>(m_joystick.CountButtons());
+ if (caps.buttonCount > Joystick::ButtonCount)
+ caps.buttonCount = Joystick::ButtonCount;
+
+ int axisCount = m_joystick.CountAxes();
+ for (int i = 0; i < axisCount && i < static_cast<int>(Joystick::AxisCount); ++i)
+ {
+ caps.axes[static_cast<Joystick::Axis>(i)] = true;
+ }
+
+ return caps;
+}
+
+
+////////////////////////////////////////////////////////////
+Joystick::Identification JoystickImpl::getIdentification() const
+{
+ Joystick::Identification id;
+ id.name = String(m_name);
+
+ id.vendorId = 0;
+ id.productId = 0;
+
+ return id;
+}
+
+
+////////////////////////////////////////////////////////////
+JoystickState JoystickImpl::update()
+{
+ JoystickState state;
+ state.connected = true;
+
+ if (m_joystick.Update() == B_OK)
+ {
+ // Axes
+ int axisCount = m_joystick.CountAxes();
+ for (int i = 0; i < axisCount && i < static_cast<int>(Joystick::AxisCount); ++i)
+ {
+ // Haiku returns -32768 to 32767
+ // SFML expects -100 to 100
+ int16 raw = m_joystick.axis_values[i];
+ state.axes[static_cast<Joystick::Axis>(i)] = static_cast<float>(raw) * 100.f / 32767.f;
+ }
+
+ // Buttons
+ uint32 buttons = m_joystick.button_values;
+ for (int i = 0; i < static_cast<int>(Joystick::ButtonCount); ++i)
+ {
+ state.buttons[i] = (buttons & (1 << i)) != 0;
+ }
+ }
+
+ return state;
+}
+
+} // namespace priv
+} // namespace sf
diff --git a/src/SFML/Window/Haiku/JoystickImpl.hpp b/src/SFML/Window/Haiku/JoystickImpl.hpp
new file mode 100644
index 00000000..6d5ea338
--- /dev/null
+++ b/src/SFML/Window/Haiku/JoystickImpl.hpp
@@ -0,0 +1,120 @@
+////////////////////////////////////////////////////////////
+//
+// SFML - Simple and Fast Multimedia Library
+// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org)
+//
+// This software is provided 'as-is', without any express or implied warranty.
+// In no event will the authors be held liable for any damages arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it freely,
+// subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented;
+// you must not claim that you wrote the original software.
+// If you use this software in a product, an acknowledgment
+// in the product documentation would be appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such,
+// and must not be misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source distribution.
+//
+////////////////////////////////////////////////////////////
+
+#pragma once
+
+////////////////////////////////////////////////////////////
+// Headers
+////////////////////////////////////////////////////////////
+#include <SFML/Window/Joystick.hpp>
+#include <SFML/Window/JoystickImpl.hpp>
+
+#include <SFML/System/String.hpp>
+
+#include <Device/Joystick.h>
+
+namespace sf
+{
+namespace priv
+{
+////////////////////////////////////////////////////////////
+/// \brief Haiku implementation of joysticks
+///
+////////////////////////////////////////////////////////////
+class JoystickImpl
+{
+public:
+ ////////////////////////////////////////////////////////////
+ /// \brief Perform the global initialization of the joystick module
+ ///
+ ////////////////////////////////////////////////////////////
+ static void initialize();
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Perform the global cleanup of the joystick module
+ ///
+ ////////////////////////////////////////////////////////////
+ static void cleanup();
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Check if a joystick is currently connected
+ ///
+ /// \param index Index of the joystick to check
+ ///
+ /// \return True if the joystick is connected, false otherwise
+ ///
+ ////////////////////////////////////////////////////////////
+ static bool isConnected(unsigned int index);
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Open the joystick
+ ///
+ /// \param index Index of the joystick to open
+ ///
+ /// \return True on success, false on failure
+ ///
+ ////////////////////////////////////////////////////////////
+ bool open(unsigned int index);
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Close the joystick
+ ///
+ ////////////////////////////////////////////////////////////
+ void close();
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Get the joystick capabilities
+ ///
+ /// \return Joystick capabilities
+ ///
+ ////////////////////////////////////////////////////////////
+ JoystickCaps getCapabilities() const;
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Get the joystick identification
+ ///
+ /// \return Joystick identification
+ ///
+ ////////////////////////////////////////////////////////////
+ Joystick::Identification getIdentification() const;
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Update the joystick and get its new state
+ ///
+ /// \return Joystick state
+ ///
+ ////////////////////////////////////////////////////////////
+ JoystickState update();
+
+private:
+ ////////////////////////////////////////////////////////////
+ // Member data
+ ////////////////////////////////////////////////////////////
+ BJoystick m_joystick; //!< Haiku joystick object
+ char m_name[B_OS_NAME_LENGTH]; //!< Joystick name
+ int m_index; //!< Index of the joystick
+};
+
+} // namespace priv
+} // namespace sf
diff --git a/src/SFML/Window/Haiku/SensorImpl.cpp b/src/SFML/Window/Haiku/SensorImpl.cpp
new file mode 100644
index 00000000..f8cf6b7e
--- /dev/null
+++ b/src/SFML/Window/Haiku/SensorImpl.cpp
@@ -0,0 +1,85 @@
+////////////////////////////////////////////////////////////
+//
+// SFML - Simple and Fast Multimedia Library
+// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org)
+//
+// This software is provided 'as-is', without any express or implied warranty.
+// In no event will the authors be held liable for any damages arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it freely,
+// subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented;
+// you must not claim that you wrote the original software.
+// If you use this software in a product, an acknowledgment
+// in the product documentation would be appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such,
+// and must not be misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source distribution.
+//
+////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////
+// Headers
+////////////////////////////////////////////////////////////
+#include <SFML/Window/Haiku/SensorImpl.hpp>
+
+
+namespace sf
+{
+namespace priv
+{
+////////////////////////////////////////////////////////////
+void SensorImpl::initialize()
+{
+ // Nothing to do
+}
+
+
+////////////////////////////////////////////////////////////
+void SensorImpl::cleanup()
+{
+ // Nothing to do
+}
+
+
+////////////////////////////////////////////////////////////
+bool SensorImpl::isAvailable(Sensor::Type)
+{
+ // No standard sensor support on Haiku
+ return false;
+}
+
+
+////////////////////////////////////////////////////////////
+bool SensorImpl::open(Sensor::Type)
+{
+ return false;
+}
+
+
+////////////////////////////////////////////////////////////
+void SensorImpl::close()
+{
+ // Nothing to do
+}
+
+
+////////////////////////////////////////////////////////////
+Vector3f SensorImpl::update()
+{
+ return Vector3f(0, 0, 0);
+}
+
+
+////////////////////////////////////////////////////////////
+void SensorImpl::setEnabled(bool)
+{
+ // Nothing to do
+}
+
+} // namespace priv
+} // namespace sf
diff --git a/src/SFML/Window/Haiku/SensorImpl.hpp b/src/SFML/Window/Haiku/SensorImpl.hpp
new file mode 100644
index 00000000..d4f1f7c2
--- /dev/null
+++ b/src/SFML/Window/Haiku/SensorImpl.hpp
@@ -0,0 +1,101 @@
+////////////////////////////////////////////////////////////
+//
+// SFML - Simple and Fast Multimedia Library
+// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org)
+//
+// This software is provided 'as-is', without any express or implied warranty.
+// In no event will the authors be held liable for any damages arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it freely,
+// subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented;
+// you must not claim that you wrote the original software.
+// If you use this software in a product, an acknowledgment
+// in the product documentation would be appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such,
+// and must not be misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source distribution.
+//
+////////////////////////////////////////////////////////////
+
+#pragma once
+
+////////////////////////////////////////////////////////////
+// Headers
+////////////////////////////////////////////////////////////
+#include <SFML/Window/Sensor.hpp>
+#include <SFML/Window/SensorImpl.hpp>
+
+
+namespace sf
+{
+namespace priv
+{
+////////////////////////////////////////////////////////////
+/// \brief Haiku implementation of sensors
+///
+////////////////////////////////////////////////////////////
+class SensorImpl
+{
+public:
+ ////////////////////////////////////////////////////////////
+ /// \brief Perform the global initialization of the sensor module
+ ///
+ ////////////////////////////////////////////////////////////
+ static void initialize();
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Perform the global cleanup of the sensor module
+ ///
+ ////////////////////////////////////////////////////////////
+ static void cleanup();
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Check if a sensor is available
+ ///
+ /// \param sensor Sensor to check
+ ///
+ /// \return True if the sensor is available, false otherwise
+ ///
+ ////////////////////////////////////////////////////////////
+ static bool isAvailable(Sensor::Type sensor);
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Open the sensor
+ ///
+ /// \param sensor Sensor to open
+ ///
+ /// \return True on success, false on failure
+ ///
+ ////////////////////////////////////////////////////////////
+ bool open(Sensor::Type sensor);
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Close the sensor
+ ///
+ ////////////////////////////////////////////////////////////
+ void close();
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Update the sensor and get its new value
+ ///
+ /// \return Sensor value
+ ///
+ ////////////////////////////////////////////////////////////
+ Vector3f update();
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Enable or disable the sensor
+ ///
+ /// \param enabled True to enable, false to disable
+ ///
+ ////////////////////////////////////////////////////////////
+ void setEnabled(bool enabled);
+};
+
+} // namespace priv
+} // namespace sf
diff --git a/src/SFML/Window/Haiku/Utils.cpp b/src/SFML/Window/Haiku/Utils.cpp
new file mode 100644
index 00000000..6b71c49d
--- /dev/null
+++ b/src/SFML/Window/Haiku/Utils.cpp
@@ -0,0 +1,416 @@
+////////////////////////////////////////////////////////////
+//
+// SFML - Simple and Fast Multimedia Library
+// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org)
+//
+// This software is provided 'as-is', without any express or implied warranty.
+// In no event will the authors be held liable for any damages arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it freely,
+// subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented;
+// you must not claim that you wrote the original software.
+// If you use this software in a product, an acknowledgment
+// in the product documentation would be appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such,
+// and must not be misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source distribution.
+//
+////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////
+// Headers
+////////////////////////////////////////////////////////////
+#include <SFML/Window/Haiku/Utils.hpp>
+
+#include <InterfaceDefs.h>
+
+namespace sf
+{
+namespace priv
+{
+////////////////////////////////////////////////////////////
+Keyboard::Key keyToSf(std::uint32_t key)
+{
+ // Key codes from Haiku's InterfaceDefs.h
+ switch (key)
+ {
+ case B_ESCAPE:
+ return Keyboard::Key::Escape;
+ case B_F1_KEY:
+ return Keyboard::Key::F1;
+ case B_F2_KEY:
+ return Keyboard::Key::F2;
+ case B_F3_KEY:
+ return Keyboard::Key::F3;
+ case B_F4_KEY:
+ return Keyboard::Key::F4;
+ case B_F5_KEY:
+ return Keyboard::Key::F5;
+ case B_F6_KEY:
+ return Keyboard::Key::F6;
+ case B_F7_KEY:
+ return Keyboard::Key::F7;
+ case B_F8_KEY:
+ return Keyboard::Key::F8;
+ case B_F9_KEY:
+ return Keyboard::Key::F9;
+ case B_F10_KEY:
+ return Keyboard::Key::F10;
+ case B_F11_KEY:
+ return Keyboard::Key::F11;
+ case B_F12_KEY:
+ return Keyboard::Key::F12;
+ case 0x11:
+ return Keyboard::Key::Grave; // Tilde
+ case 0x12:
+ return Keyboard::Key::Num1;
+ case 0x13:
+ return Keyboard::Key::Num2;
+ case 0x14:
+ return Keyboard::Key::Num3;
+ case 0x15:
+ return Keyboard::Key::Num4;
+ case 0x16:
+ return Keyboard::Key::Num5;
+ case 0x17:
+ return Keyboard::Key::Num6;
+ case 0x18:
+ return Keyboard::Key::Num7;
+ case 0x19:
+ return Keyboard::Key::Num8;
+ case 0x1a:
+ return Keyboard::Key::Num9;
+ case 0x1b:
+ return Keyboard::Key::Num0;
+ case 0x1c:
+ return Keyboard::Key::Hyphen;
+ case 0x1d:
+ return Keyboard::Key::Equal;
+ case 0x1e:
+ return Keyboard::Key::Backspace;
+ case 0x26:
+ return Keyboard::Key::Tab;
+ case 0x27:
+ return Keyboard::Key::Q;
+ case 0x28:
+ return Keyboard::Key::W;
+ case 0x29:
+ return Keyboard::Key::E;
+ case 0x2a:
+ return Keyboard::Key::R;
+ case 0x2b:
+ return Keyboard::Key::T;
+ case 0x2c:
+ return Keyboard::Key::Y;
+ case 0x2d:
+ return Keyboard::Key::U;
+ case 0x2e:
+ return Keyboard::Key::I;
+ case 0x2f:
+ return Keyboard::Key::O;
+ case 0x30:
+ return Keyboard::Key::P;
+ case 0x31:
+ return Keyboard::Key::LBracket;
+ case 0x32:
+ return Keyboard::Key::RBracket;
+ case 0x33:
+ return Keyboard::Key::Backslash;
+ case 0x3b:
+ return Keyboard::Key::CapsLock;
+ case 0x3c:
+ return Keyboard::Key::A;
+ case 0x3d:
+ return Keyboard::Key::S;
+ case 0x3e:
+ return Keyboard::Key::D;
+ case 0x3f:
+ return Keyboard::Key::F;
+ case 0x40:
+ return Keyboard::Key::G;
+ case 0x41:
+ return Keyboard::Key::H;
+ case 0x42:
+ return Keyboard::Key::J;
+ case 0x43:
+ return Keyboard::Key::K;
+ case 0x44:
+ return Keyboard::Key::L;
+ case 0x45:
+ return Keyboard::Key::Semicolon;
+ case 0x46:
+ return Keyboard::Key::Apostrophe;
+ case 0x47:
+ return Keyboard::Key::Enter;
+ case 0x4b:
+ return Keyboard::Key::LShift;
+ case 0x4c:
+ return Keyboard::Key::Z;
+ case 0x4d:
+ return Keyboard::Key::X;
+ case 0x4e:
+ return Keyboard::Key::C;
+ case 0x4f:
+ return Keyboard::Key::V;
+ case 0x50:
+ return Keyboard::Key::B;
+ case 0x51:
+ return Keyboard::Key::N;
+ case 0x52:
+ return Keyboard::Key::M;
+ case 0x53:
+ return Keyboard::Key::Comma;
+ case 0x54:
+ return Keyboard::Key::Period;
+ case 0x55:
+ return Keyboard::Key::Slash;
+ case 0x56:
+ return Keyboard::Key::RShift;
+ case 0x5c:
+ return Keyboard::Key::LControl;
+ case 0x66:
+ return Keyboard::Key::LSystem; // Command/Option
+ case 0x5d:
+ return Keyboard::Key::LAlt;
+ case 0x5e:
+ return Keyboard::Key::Space;
+ case 0x5f:
+ return Keyboard::Key::RAlt;
+ case 0x67:
+ return Keyboard::Key::RSystem; // Command/Option
+ // case 0x60: return Keyboard::Key::RControl; // No specific RControl in keymap usually, handled as Control
+ case B_PRINT_KEY:
+ return Keyboard::Key::PrintScreen;
+ case B_SCROLL_KEY:
+ return Keyboard::Key::ScrollLock;
+ case B_PAUSE_KEY:
+ return Keyboard::Key::Pause;
+ case B_INSERT:
+ return Keyboard::Key::Insert;
+ case B_HOME:
+ return Keyboard::Key::Home;
+ case B_PAGE_UP:
+ return Keyboard::Key::PageUp;
+ case 0x34:
+ return Keyboard::Key::Delete;
+ case B_END:
+ return Keyboard::Key::End;
+ case B_PAGE_DOWN:
+ return Keyboard::Key::PageDown;
+ case B_UP_ARROW:
+ return Keyboard::Key::Up;
+ case B_LEFT_ARROW:
+ return Keyboard::Key::Left;
+ case B_DOWN_ARROW:
+ return Keyboard::Key::Down;
+ case B_RIGHT_ARROW:
+ return Keyboard::Key::Right;
+ case 0x37:
+ return Keyboard::Key::NumpadDivide;
+ case 0x38:
+ return Keyboard::Key::NumpadMultiply;
+ case 0x39:
+ return Keyboard::Key::NumpadMinus;
+ case 0x25:
+ return Keyboard::Key::NumpadPlus;
+ case 0x64:
+ return Keyboard::Key::NumpadEnter;
+ case 0x65:
+ return Keyboard::Key::NumpadDecimal; // Period
+ case 0x3a:
+ return Keyboard::Key::Numpad1; // BeOS numeric keypad?
+ case 0x58:
+ return Keyboard::Key::Numpad0;
+ // ... incomplete numpad map, using best guess standards for PC keyboards on Haiku
+ default:
+ return Keyboard::Key::Unknown;
+ }
+}
+
+////////////////////////////////////////////////////////////
+std::uint32_t keyToHaiku(Keyboard::Key key)
+{
+ switch (key)
+ {
+ case Keyboard::Key::Escape:
+ return B_ESCAPE;
+ case Keyboard::Key::F1:
+ return B_F1_KEY;
+ case Keyboard::Key::F2:
+ return B_F2_KEY;
+ case Keyboard::Key::F3:
+ return B_F3_KEY;
+ case Keyboard::Key::F4:
+ return B_F4_KEY;
+ case Keyboard::Key::F5:
+ return B_F5_KEY;
+ case Keyboard::Key::F6:
+ return B_F6_KEY;
+ case Keyboard::Key::F7:
+ return B_F7_KEY;
+ case Keyboard::Key::F8:
+ return B_F8_KEY;
+ case Keyboard::Key::F9:
+ return B_F9_KEY;
+ case Keyboard::Key::F10:
+ return B_F10_KEY;
+ case Keyboard::Key::F11:
+ return B_F11_KEY;
+ case Keyboard::Key::F12:
+ return B_F12_KEY;
+ case Keyboard::Key::Grave:
+ return 0x11;
+ case Keyboard::Key::Num1:
+ return 0x12;
+ case Keyboard::Key::Num2:
+ return 0x13;
+ case Keyboard::Key::Num3:
+ return 0x14;
+ case Keyboard::Key::Num4:
+ return 0x15;
+ case Keyboard::Key::Num5:
+ return 0x16;
+ case Keyboard::Key::Num6:
+ return 0x17;
+ case Keyboard::Key::Num7:
+ return 0x18;
+ case Keyboard::Key::Num8:
+ return 0x19;
+ case Keyboard::Key::Num9:
+ return 0x1a;
+ case Keyboard::Key::Num0:
+ return 0x1b;
+ case Keyboard::Key::Hyphen:
+ return 0x1c;
+ case Keyboard::Key::Equal:
+ return 0x1d;
+ case Keyboard::Key::Backspace:
+ return 0x1e;
+ case Keyboard::Key::Tab:
+ return 0x26;
+ case Keyboard::Key::Q:
+ return 0x27;
+ case Keyboard::Key::W:
+ return 0x28;
+ case Keyboard::Key::E:
+ return 0x29;
+ case Keyboard::Key::R:
+ return 0x2a;
+ case Keyboard::Key::T:
+ return 0x2b;
+ case Keyboard::Key::Y:
+ return 0x2c;
+ case Keyboard::Key::U:
+ return 0x2d;
+ case Keyboard::Key::I:
+ return 0x2e;
+ case Keyboard::Key::O:
+ return 0x2f;
+ case Keyboard::Key::P:
+ return 0x30;
+ case Keyboard::Key::LBracket:
+ return 0x31;
+ case Keyboard::Key::RBracket:
+ return 0x32;
+ case Keyboard::Key::Backslash:
+ return 0x33;
+ case Keyboard::Key::CapsLock:
+ return 0x3b;
+ case Keyboard::Key::A:
+ return 0x3c;
+ case Keyboard::Key::S:
+ return 0x3d;
+ case Keyboard::Key::D:
+ return 0x3e;
+ case Keyboard::Key::F:
+ return 0x3f;
+ case Keyboard::Key::G:
+ return 0x40;
+ case Keyboard::Key::H:
+ return 0x41;
+ case Keyboard::Key::J:
+ return 0x42;
+ case Keyboard::Key::K:
+ return 0x43;
+ case Keyboard::Key::L:
+ return 0x44;
+ case Keyboard::Key::Semicolon:
+ return 0x45;
+ case Keyboard::Key::Apostrophe:
+ return 0x46;
+ case Keyboard::Key::Enter:
+ return 0x47;
+ case Keyboard::Key::LShift:
+ return 0x4b;
+ case Keyboard::Key::Z:
+ return 0x4c;
+ case Keyboard::Key::X:
+ return 0x4d;
+ case Keyboard::Key::C:
+ return 0x4e;
+ case Keyboard::Key::V:
+ return 0x4f;
+ case Keyboard::Key::B:
+ return 0x50;
+ case Keyboard::Key::N:
+ return 0x51;
+ case Keyboard::Key::M:
+ return 0x52;
+ case Keyboard::Key::Comma:
+ return 0x53;
+ case Keyboard::Key::Period:
+ return 0x54;
+ case Keyboard::Key::Slash:
+ return 0x55;
+ case Keyboard::Key::RShift:
+ return 0x56;
+ case Keyboard::Key::LControl:
+ return 0x5c;
+ case Keyboard::Key::LSystem:
+ return 0x66;
+ case Keyboard::Key::LAlt:
+ return 0x5d;
+ case Keyboard::Key::Space:
+ return 0x5e;
+ case Keyboard::Key::RAlt:
+ return 0x5f;
+ case Keyboard::Key::RSystem:
+ return 0x67;
+ case Keyboard::Key::PrintScreen:
+ return B_PRINT_KEY;
+ case Keyboard::Key::ScrollLock:
+ return B_SCROLL_KEY;
+ case Keyboard::Key::Pause:
+ return B_PAUSE_KEY;
+ case Keyboard::Key::Insert:
+ return B_INSERT;
+ case Keyboard::Key::Home:
+ return B_HOME;
+ case Keyboard::Key::PageUp:
+ return B_PAGE_UP;
+ case Keyboard::Key::Delete:
+ return 0x34;
+ case Keyboard::Key::End:
+ return B_END;
+ case Keyboard::Key::PageDown:
+ return B_PAGE_DOWN;
+ case Keyboard::Key::Up:
+ return B_UP_ARROW;
+ case Keyboard::Key::Left:
+ return B_LEFT_ARROW;
+ case Keyboard::Key::Down:
+ return B_DOWN_ARROW;
+ case Keyboard::Key::Right:
+ return B_RIGHT_ARROW;
+ default:
+ return 0;
+ }
+}
+
+} // namespace priv
+} // namespace sf
diff --git a/src/SFML/Window/Haiku/Utils.hpp b/src/SFML/Window/Haiku/Utils.hpp
new file mode 100644
index 00000000..12e7db08
--- /dev/null
+++ b/src/SFML/Window/Haiku/Utils.hpp
@@ -0,0 +1,59 @@
+////////////////////////////////////////////////////////////
+//
+// SFML - Simple and Fast Multimedia Library
+// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org)
+//
+// This software is provided 'as-is', without any express or implied warranty.
+// In no event will the authors be held liable for any damages arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it freely,
+// subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented;
+// you must not claim that you wrote the original software.
+// If you use this software in a product, an acknowledgment
+// in the product documentation would be appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such,
+// and must not be misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source distribution.
+//
+////////////////////////////////////////////////////////////
+
+#pragma once
+
+////////////////////////////////////////////////////////////
+// Headers
+////////////////////////////////////////////////////////////
+#include <SFML/Window/Keyboard.hpp>
+
+#include <cstdint>
+
+namespace sf
+{
+namespace priv
+{
+////////////////////////////////////////////////////////////
+/// \brief Convert a Haiku key code to a SFML key code
+///
+/// \param key Haiku key code
+///
+/// \return SFML key code
+///
+////////////////////////////////////////////////////////////
+Keyboard::Key keyToSf(std::uint32_t key);
+
+////////////////////////////////////////////////////////////
+/// \brief Convert a SFML key code to a Haiku key code
+///
+/// \param key SFML key code
+///
+/// \return Haiku key code
+///
+////////////////////////////////////////////////////////////
+std::uint32_t keyToHaiku(Keyboard::Key key);
+
+} // namespace priv
+} // namespace sf
diff --git a/src/SFML/Window/Haiku/VideoModeImpl.cpp b/src/SFML/Window/Haiku/VideoModeImpl.cpp
new file mode 100644
index 00000000..3bdea9e9
--- /dev/null
+++ b/src/SFML/Window/Haiku/VideoModeImpl.cpp
@@ -0,0 +1,171 @@
+////////////////////////////////////////////////////////////
+//
+// SFML - Simple and Fast Multimedia Library
+// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org)
+//
+// This software is provided 'as-is', without any express or implied warranty.
+// In no event will the authors be held liable for any damages arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it freely,
+// subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented;
+// you must not claim that you wrote the original software.
+// If you use this software in a product, an acknowledgment
+// in the product documentation would be appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such,
+// and must not be misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source distribution.
+//
+////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////
+// Headers
+////////////////////////////////////////////////////////////
+#include <SFML/Window/VideoModeImpl.hpp>
+
+#include <SFML/System/Err.hpp>
+
+#include <Screen.h>
+
+
+namespace sf
+{
+namespace priv
+{
+////////////////////////////////////////////////////////////
+std::vector<VideoMode> VideoModeImpl::getFullscreenModes()
+{
+ std::vector<VideoMode> modes;
+
+ BScreen screen;
+ if (!screen.IsValid())
+ {
+ err() << "Failed to retrieve fullscreen modes: Invalid BScreen" << std::endl;
+ return modes;
+ }
+
+ display_mode mode;
+ // Iterate over all modes
+ // GetModeList(display_mode* list, uint32 count)
+
+ // Easier way:
+ // count_modes, get_mode_list not available in BScreen class directly?
+ // It is `BScreen::GetModeList(display_mode**, uint32*)`.
+
+ display_mode* modeList = NULL;
+ uint32 count = 0;
+
+ if (screen.GetModeList(&modeList, &count) == B_OK)
+ {
+ for (uint32 i = 0; i < count; ++i)
+ {
+ // Convert to VideoMode
+ // Logic to check bits per pixel?
+ // B_RGB32, B_RGB16, etc.
+
+ unsigned int bpp = 0;
+ switch (modeList[i].space)
+ {
+ case B_RGB32:
+ bpp = 32;
+ break;
+ case B_RGBA32:
+ bpp = 32;
+ break;
+ case B_RGB24:
+ bpp = 24;
+ break;
+ case B_RGB15:
+ bpp = 15;
+ break; // 15 or 16? usually 15 + 1 alpha bit or just 15
+ case B_RGBA15:
+ bpp = 16;
+ break;
+ case B_RGB16:
+ bpp = 16;
+ break;
+ case B_CMAP8:
+ bpp = 8;
+ break;
+ default:
+ break;
+ }
+
+ if (bpp > 0)
+ {
+ VideoMode vm({static_cast<unsigned int>(modeList[i].virtual_width),
+ static_cast<unsigned int>(modeList[i].virtual_height)},
+ bpp);
+ // Check for duplicates
+ bool found = false;
+ for (const auto& existing : modes)
+ {
+ if (existing == vm)
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ modes.push_back(vm);
+ }
+ }
+
+ free(modeList); // Haiku BScreen::GetModeList allocates with malloc
+ }
+
+ return modes;
+}
+
+
+////////////////////////////////////////////////////////////
+VideoMode VideoModeImpl::getDesktopMode()
+{
+ BScreen screen;
+ if (screen.IsValid())
+ {
+ display_mode mode;
+ if (screen.GetMode(&mode) == B_OK)
+ {
+ unsigned int bpp = 0;
+ switch (mode.space)
+ {
+ case B_RGB32:
+ bpp = 32;
+ break;
+ case B_RGBA32:
+ bpp = 32;
+ break;
+ case B_RGB24:
+ bpp = 24;
+ break;
+ case B_RGB15:
+ bpp = 15;
+ break;
+ case B_RGBA15:
+ bpp = 16;
+ break;
+ case B_RGB16:
+ bpp = 16;
+ break;
+ case B_CMAP8:
+ bpp = 8;
+ break;
+ default:
+ break;
+ }
+ return VideoMode({static_cast<unsigned int>(mode.virtual_width), static_cast<unsigned int>(mode.virtual_height)},
+ bpp);
+ }
+ }
+
+ return VideoMode({0, 0}, 0);
+}
+
+} // namespace priv
+} // namespace sf
diff --git a/src/SFML/Window/Haiku/WindowImplHaiku.cpp b/src/SFML/Window/Haiku/WindowImplHaiku.cpp
new file mode 100644
index 00000000..ac333806
--- /dev/null
+++ b/src/SFML/Window/Haiku/WindowImplHaiku.cpp
@@ -0,0 +1,689 @@
+////////////////////////////////////////////////////////////
+//
+// SFML - Simple and Fast Multimedia Library
+// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org)
+//
+// This software is provided 'as-is', without any express or implied warranty.
+// In no event will the authors be held liable for any damages arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it freely,
+// subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented;
+// you must not claim that you wrote the original software.
+// If you use this software in a product, an acknowledgment
+// in the product documentation would be appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such,
+// and must not be misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source distribution.
+//
+////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////
+// Headers
+////////////////////////////////////////////////////////////
+#include <SFML/Window/Event.hpp>
+#include <SFML/Window/Haiku/Utils.hpp>
+#include <SFML/Window/Haiku/WindowImplHaiku.hpp>
+
+#include <SFML/System/Err.hpp>
+
+#include <Application.h>
+#include <Bitmap.h>
+#include <DirectWindow.h>
+#include <Message.h>
+#include <Screen.h>
+#include <View.h>
+#include <Window.h>
+#include <mutex>
+#include <queue>
+
+#include <cstring>
+
+// Define a custom BWindow subclass to handle events
+class SFWindow : public BDirectWindow
+{
+public:
+ SFWindow(BRect frame, const char* title, uint32 flags) :
+ BDirectWindow(frame, title, B_TITLED_WINDOW, flags),
+ m_sfmlWindow(NULL)
+ {
+ }
+
+ void SetSFMLWindow(sf::priv::WindowImplHaiku* window)
+ {
+ m_sfmlWindow = window;
+ }
+
+ // Event handling
+ bool QuitRequested() override
+ {
+ if (m_sfmlWindow)
+ {
+ sf::Event event;
+ event.type = sf::Event::Closed;
+ PushEvent(event);
+ return false; // Don't close automatically, let SFML handle it
+ // Actually, SFML expects the window to close when requested or when user manages it.
+ // BWindow's QuitRequested closes the window if true.
+ // If we return false, we must handle closing ourselves via window.close().
+ // Correct for SFML is to send Closed event and NOT close immediately.
+ }
+ return true;
+ }
+
+ void WindowActivated(bool active) override
+ {
+ if (m_sfmlWindow)
+ {
+ sf::Event event;
+ event.type = active ? sf::Event::GainedFocus : sf::Event::LostFocus;
+ PushEvent(event);
+ }
+ }
+
+ void FrameMoved(BPoint origin) override
+ {
+ if (m_sfmlWindow)
+ {
+ sf::Event event;
+ event.type = sf::Event::Moved;
+ event.move.x = (int)origin.x;
+ event.move.y = (int)origin.y;
+ PushEvent(event);
+ }
+ }
+
+ void FrameResized(float width, float height) override
+ {
+ if (m_sfmlWindow)
+ {
+ sf::Event event;
+ event.type = sf::Event::Resized;
+ event.size.width = (unsigned int)(width + 1); // width is right - left, so +1? BRect definition: right - left.
+ // Wait, BRect(0,0,10,10) has width 10? No, 10.
+ // FrameResized params are width and height delta? No, new width and height (content area).
+ event.size.height = (unsigned int)(height + 1);
+ PushEvent(event);
+ }
+ }
+
+ void MessageReceived(BMessage* message) override
+ {
+ if (!m_sfmlWindow)
+ {
+ BDirectWindow::MessageReceived(message);
+ return;
+ }
+
+ switch (message->what)
+ {
+ case B_KEY_DOWN:
+ case B_UNMAPPED_KEY_DOWN:
+ {
+ int32 key;
+ if (message->FindInt32("key", &key) == B_OK) // raw key code
+ {
+ // Convert to SFML key
+ // B_KEY_DOWN raw key code is "key".
+ // But "raw_char" or "byte" might be needed?
+ // "key" is the scancode usually? unique code.
+
+ // Actually, keyToSf expects the raw key code (which is what we mapped in Utils.cpp).
+ // Wait, Utils.cpp mapped B_ESCAPE (0x01) etc. which are ASCII often? No, B_ESCAPE is defined in InterfaceDefs.h.
+ // Let's check Utils.cpp again.
+ // It maps things like 0x11, 0x12... these correspond to Haiku raw key codes (scancodes).
+ // So "key" field from B_KEY_DOWN is correct.
+
+ sf::Event event;
+ event.type = sf::Event::KeyPressed;
+ event.key.code = sf::priv::keyToSf(key);
+ event.key.alt = (modifiers() & B_OPTION_KEY) != 0; // Haiku Option is Alt? Or Command?
+ // LAlt/RAlt map to B_OPTION_KEY in Haiku?
+ // B_COMMAND_KEY (Alt on PC usually, Command on Mac).
+ // SFML Alt usually means the Alt key.
+ // On Haiku (PC keyboard), Alt is command?
+ // Let's stick to B_COMMAND_KEY = Alt logic for compatibility with typical SFML apps?
+ // Or B_OPTION_KEY = Alt?
+ // Haiku: Alt key is Command usually. Option is Windows key? No.
+ // Standard PC: Alt -> Command (B_COMMAND_KEY). Windows -> Option (B_OPTION_KEY). Control -> Control (B_CONTROL_KEY).
+
+ event.key.alt = (modifiers() & B_COMMAND_KEY) != 0;
+ event.key.control = (modifiers() & B_CONTROL_KEY) != 0;
+ event.key.shift = (modifiers() & B_SHIFT_KEY) != 0;
+ event.key.system = (modifiers() & B_OPTION_KEY) != 0; // Windows key
+
+ PushEvent(event);
+
+ // TextEntered
+ const char* bytes;
+ if (message->FindString("bytes", &bytes) == B_OK)
+ {
+ // Handle UTF-8 bytes provided by Haiku
+ sf::String str = sf::String::fromUtf8(bytes, bytes + strlen(bytes));
+ for (const auto& curChar : str)
+ {
+ sf::Event textEvent;
+ textEvent.type = sf::Event::TextEntered;
+ textEvent.text.unicode = curChar;
+ PushEvent(textEvent);
+ }
+ }
+ }
+ break;
+ }
+
+ case B_KEY_UP:
+ case B_UNMAPPED_KEY_UP:
+ {
+ int32 key;
+ if (message->FindInt32("key", &key) == B_OK)
+ {
+ sf::Event event;
+ event.type = sf::Event::KeyReleased;
+ event.key.code = sf::priv::keyToSf(key);
+ event.key.alt = (modifiers() & B_COMMAND_KEY) != 0;
+ event.key.control = (modifiers() & B_CONTROL_KEY) != 0;
+ event.key.shift = (modifiers() & B_SHIFT_KEY) != 0;
+ event.key.system = (modifiers() & B_OPTION_KEY) != 0;
+ PushEvent(event);
+ }
+ break;
+ }
+
+ case B_MOUSE_DOWN:
+ {
+ sf::Event event;
+ event.type = sf::Event::MouseButtonPressed;
+ int32 buttons;
+ if (message->FindInt32("buttons", &buttons) == B_OK)
+ {
+ if (buttons & B_PRIMARY_MOUSE_BUTTON)
+ event.mouseButton.button = sf::Mouse::Left;
+ else if (buttons & B_SECONDARY_MOUSE_BUTTON)
+ event.mouseButton.button = sf::Mouse::Right;
+ else if (buttons & B_TERTIARY_MOUSE_BUTTON)
+ event.mouseButton.button = sf::Mouse::Middle;
+ else if (buttons & B_MOUSE_BUTTON(4))
+ event.mouseButton.button = sf::Mouse::XButton1;
+ else if (buttons & B_MOUSE_BUTTON(5))
+ event.mouseButton.button = sf::Mouse::XButton2;
+ else
+ return; // Unknown button
+
+ BPoint where;
+ if (message->FindPoint("where", &where) == B_OK)
+ {
+ // Convert where (screen? window?)
+ // BWindow message 'where' is usually in window coordinates if passed to a view,
+ // but BWindow receives it from App Server. docs say: "coordinate space is undefined" for BWindow?
+ // Actually, we usually add a BView to catch mouse events properly.
+ // Mouse events go to the view under cursor.
+ // If we don't have a view covering content, BWindow might not see it correctly or it's raw.
+ // BUT, WindowImplHaiku creates a BView?
+ // Let's ensure we forward events from the View to here, OR we just let the window handle it if we make the view a simple pass-through.
+ // Better: The SFWindow should have a generic "InputView" that fills the window and catches messages.
+ // This class SFWindow handles window messages.
+ // Let's create `SFView` inside this file too or just use `BView`.
+ // MouseDown is sent to the BView.
+ event.mouseButton.x = (int)where.x;
+ event.mouseButton.y = (int)where.y;
+ PushEvent(event);
+ }
+ }
+ break;
+ }
+
+ case B_MOUSE_UP:
+ {
+ sf::Event event;
+ event.type = sf::Event::MouseButtonReleased;
+ int32 buttons;
+ // B_MOUSE_UP doesn't always contain "buttons" field with the button released, it contains *current* buttons (which might be 0).
+ // We might need to track state or assume.
+ // Actually, "buttons" in Message is state after event?
+ // Let's assume Left for now if not present or try to deduce?
+ // Wait, `B_MOUSE_UP` is usually sent to the view.
+ // Let's handle it in the `SFView` class logic if possible.
+ // For now, assume we get it.
+ event.mouseButton.button = sf::Mouse::Left; // Fallback
+ BPoint where;
+ if (message->FindPoint("where", &where) == B_OK)
+ {
+ event.mouseButton.x = (int)where.x;
+ event.mouseButton.y = (int)where.y;
+ }
+ PushEvent(event);
+ break;
+ }
+
+ case B_MOUSE_MOVED:
+ {
+ sf::Event event;
+ event.type = sf::Event::MouseMoved;
+ BPoint where;
+ if (message->FindPoint("where", &where) == B_OK)
+ {
+ event.mouseMove.x = (int)where.x;
+ event.mouseMove.y = (int)where.y;
+ PushEvent(event);
+ }
+ break;
+ }
+
+ case B_MOUSE_WHEEL_CHANGED:
+ {
+ sf::Event event;
+ event.type = sf::Event::MouseWheelScrolled;
+ float deltaY;
+ if (message->FindFloat("be:wheel_delta_y", &deltaY) == B_OK)
+ {
+ event.mouseWheelScroll.wheel = sf::Mouse::VerticalWheel;
+ event.mouseWheelScroll.delta = -deltaY; // Invert? Haiku down is positive?
+ event.mouseWheelScroll.x = 0; // Need mouse pos... check "where"?
+ event.mouseWheelScroll.y = 0;
+ PushEvent(event);
+ }
+ float deltaX;
+ if (message->FindFloat("be:wheel_delta_x", &deltaX) == B_OK)
+ {
+ event.mouseWheelScroll.wheel = sf::Mouse::HorizontalWheel;
+ event.mouseWheelScroll.delta = deltaX;
+ event.mouseWheelScroll.x = 0;
+ event.mouseWheelScroll.y = 0;
+ PushEvent(event);
+ }
+ break;
+ }
+
+ default:
+ BDirectWindow::MessageReceived(message);
+ break;
+ }
+ }
+
+ void PushEvent(const sf::Event& event)
+ {
+ // Add to queue safely
+ m_mutex.lock();
+ m_events.push(event);
+ m_mutex.unlock();
+ }
+
+ // Helper to retrieve events from the window thread
+ bool PopEvent(sf::Event& event)
+ {
+ std::lock_guard<std::mutex> lock(m_mutex);
+ if (m_events.empty())
+ return false;
+ event = m_events.front();
+ m_events.pop();
+ return true;
+ }
+
+private:
+ sf::priv::WindowImplHaiku* m_sfmlWindow;
+ std::queue<sf::Event> m_events;
+ std::mutex m_mutex;
+};
+
+
+// SFView Implementation (to catch mouse events properly)
+class SFView : public BView
+{
+public:
+ SFView(BRect frame, SFWindow* window) :
+ BView(frame, "SFView", B_FOLLOW_ALL_SIDES, B_WILL_DRAW | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE),
+ m_window(window)
+ {
+ }
+
+ void MouseDown(BPoint where) override
+ {
+ BMessage* msg = Window()->CurrentMessage();
+ if (msg)
+ {
+ // Forward to window or handle here
+ // We can manually construct a message or just invoke base logic which might send to window?
+ // Actually, we can just call m_window->MessageReceived(msg);
+ // But we shouldn't steal it if BWindow doesn't expect it?
+ // BWindow usually dispatches to View.
+ // So we should call m_window->MessageReceived(msg) carefully.
+ // Or better: Just process it here and push to window's queue.
+ m_window->MessageReceived(msg);
+ }
+ }
+
+ void MouseUp(BPoint where) override
+ {
+ // BView doesn't always receive MouseUp unless it captures mouse.
+ // But MouseDown + SetMouseEventMask is typical.
+ BMessage* msg = Window()->CurrentMessage();
+ if (msg)
+ m_window->MessageReceived(msg);
+ }
+
+ void MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage) override
+ {
+ BMessage* msg = Window()->CurrentMessage();
+ if (msg)
+ m_window->MessageReceived((BMessage*)msg);
+ }
+
+ void KeyDown(const char* bytes, int32 numBytes) override
+ {
+ BMessage* msg = Window()->CurrentMessage();
+ if (msg)
+ m_window->MessageReceived(msg);
+ }
+
+ void KeyUp(const char* bytes, int32 numBytes) override
+ {
+ BMessage* msg = Window()->CurrentMessage();
+ if (msg)
+ m_window->MessageReceived(msg);
+ }
+
+private:
+ SFWindow* m_window;
+};
+
+
+namespace sf
+{
+namespace priv
+{
+
+////////////////////////////////////////////////////////////
+WindowImplHaiku::WindowImplHaiku(WindowHandle handle) :
+m_window(NULL),
+m_view(NULL),
+m_ownsWindow(false),
+m_keyRepeatEnabled(true)
+{
+ // Adopt existing window
+ if (handle)
+ {
+ m_window = (BWindow*)handle;
+ // Check if we can/should attach a view?
+ m_ownsWindow = false;
+ }
+}
+
+
+////////////////////////////////////////////////////////////
+WindowImplHaiku::WindowImplHaiku(VideoMode mode, const String& title, std::uint32_t style, const ContextSettings& settings) :
+m_window(NULL),
+m_view(NULL),
+m_ownsWindow(true),
+m_keyRepeatEnabled(true)
+{
+ // Create BWindow
+ BRect frame(0, 0, (float)mode.size.x - 1, (float)mode.size.y - 1);
+
+ // Map style
+ uint32 flags = 0;
+ if (style & Style::Titlebar)
+ {
+ } // Default
+ if (!(style & Style::Resize))
+ flags |= B_NOT_RESIZABLE;
+ if (!(style & Style::Close))
+ flags |= B_NOT_CLOSABLE;
+ if (style & Style::Fullscreen)
+ {
+ // Fullscreen logic
+ // ...
+ }
+
+ // Initialize application if needed (BApplication must exist)
+ if (!be_app)
+ {
+ // We cannot create BApplication here easily if the user didn't.
+ // SFML generally requires main() to create the app loop context,
+ // but often libraries hide this.
+ // Haiku REQUIRES a BApplication for windowing messages.
+ // If we are a library, we might need a "DummyApplication".
+ // But `new BApplication` converts the current thread into the app thread when `Run()` is called.
+ // We can't do that inside `WindowImplHaiku` constructor.
+ // User MUST have created a BApplication.
+ // We will output an error if not.
+ err() << "SFML warning: No BApplication found. Ensure you have created a BApplication instance in your main()."
+ << std::endl;
+ }
+
+ SFWindow* window = new SFWindow(frame, title.toAnsiString().c_str(), flags);
+ m_window = window;
+ window->SetSFMLWindow(this);
+
+ // Create View
+ m_view = new SFView(window->Bounds(), window);
+ window->AddChild(m_view);
+
+ // Center on screen
+ window->CenterOnScreen();
+
+ if (style & Style::Fullscreen)
+ {
+ // implement SetFullScreen
+ }
+
+ window->Show();
+}
+
+
+////////////////////////////////////////////////////////////
+WindowImplHaiku::~WindowImplHaiku()
+{
+ if (m_ownsWindow && m_window)
+ {
+ m_window->Lock();
+ m_window->Quit(); // Closes and deletes
+ }
+}
+
+
+////////////////////////////////////////////////////////////
+WindowHandle WindowImplHaiku::getNativeHandle() const
+{
+ return (WindowHandle)m_window;
+}
+
+
+////////////////////////////////////////////////////////////
+Vector2i WindowImplHaiku::getPosition() const
+{
+ if (m_window)
+ {
+ // Check synchronization? BWindow access usually requires Lock, but Frame() might be cached.
+ // Safer to Lock.
+ if (m_window->Lock())
+ {
+ BRect frame = m_window->Frame();
+ m_window->Unlock();
+ return Vector2i((int)frame.left, (int)frame.top);
+ }
+ }
+ return Vector2i(0, 0);
+}
+
+
+////////////////////////////////////////////////////////////
+void WindowImplHaiku::setPosition(Vector2i position)
+{
+ if (m_window)
+ {
+ if (m_window->Lock())
+ {
+ m_window->MoveTo((float)position.x, (float)position.y);
+ m_window->Unlock();
+ }
+ }
+}
+
+
+////////////////////////////////////////////////////////////
+Vector2u WindowImplHaiku::getSize() const
+{
+ if (m_window)
+ {
+ if (m_window->Lock())
+ {
+ BRect bounds = m_window->Bounds();
+ m_window->Unlock();
+ return Vector2u((unsigned int)bounds.Width() + 1, (unsigned int)bounds.Height() + 1);
+ }
+ }
+ return Vector2u(0, 0);
+}
+
+
+////////////////////////////////////////////////////////////
+void WindowImplHaiku::setSize(Vector2u size)
+{
+ if (m_window)
+ {
+ if (m_window->Lock())
+ {
+ m_window->ResizeTo((float)size.x - 1, (float)size.y - 1);
+ m_window->Unlock();
+ }
+ }
+}
+
+
+////////////////////////////////////////////////////////////
+void WindowImplHaiku::setTitle(const String& title)
+{
+ if (m_window)
+ {
+ if (m_window->Lock())
+ {
+ m_window->SetTitle(title.toAnsiString().c_str());
+ m_window->Unlock();
+ }
+ }
+}
+
+
+////////////////////////////////////////////////////////////
+void WindowImplHaiku::setIcon(Vector2u size, const std::uint8_t* pixels)
+{
+ // Not directly supported on BWindow to set Icon from pixels easily?
+ // R1/Beta supports SetIcon with BBitmap?
+ // Actually typically app icon is resource based.
+ // There isn't a simple SetIcon API on BWindow for arbitrary bitmaps in all versions.
+ // Use BApplication::SetAppInfo? No that's global.
+ // Pass for now or implement if API found.
+}
+
+
+////////////////////////////////////////////////////////////
+void WindowImplHaiku::setVisible(bool visible)
+{
+ if (m_window)
+ {
+ if (m_window->Lock())
+ {
+ if (visible && m_window->IsHidden())
+ m_window->Show();
+ else if (!visible && !m_window->IsHidden())
+ m_window->Hide();
+ m_window->Unlock();
+ }
+ }
+}
+
+
+////////////////////////////////////////////////////////////
+void WindowImplHaiku::setMouseCursorVisible(bool visible)
+{
+ if (be_app)
+ {
+ if (visible)
+ be_app->ShowCursor();
+ else
+ be_app->HideCursor();
+ }
+}
+
+
+////////////////////////////////////////////////////////////
+void WindowImplHaiku::setMouseCursorGrabbed(bool grabbed)
+{
+ // Can restrict mouse to window bounds?
+ // Not standard BWindow API.
+}
+
+
+////////////////////////////////////////////////////////////
+void WindowImplHaiku::setMouseCursor(const CursorImpl& cursor)
+{
+ if (be_app)
+ {
+ if (cursor.getCursor())
+ be_app->SetCursor(cursor.getCursor());
+ }
+}
+
+
+////////////////////////////////////////////////////////////
+void WindowImplHaiku::setKeyRepeatEnabled(bool enabled)
+{
+ m_keyRepeatEnabled = enabled;
+}
+
+
+////////////////////////////////////////////////////////////
+void WindowImplHaiku::requestFocus()
+{
+ if (m_window)
+ {
+ if (m_window->Lock())
+ {
+ m_window->Activate(true);
+ m_window->Unlock();
+ }
+ }
+}
+
+
+////////////////////////////////////////////////////////////
+bool WindowImplHaiku::hasFocus() const
+{
+ if (m_window)
+ {
+ if (m_window->Lock())
+ {
+ bool active = m_window->IsActive();
+ m_window->Unlock();
+ return active;
+ }
+ }
+ return false;
+}
+
+
+////////////////////////////////////////////////////////////
+void WindowImplHaiku::processEvents()
+{
+ // Pull events from the window queue
+ if (m_window)
+ {
+ SFWindow* win = dynamic_cast<SFWindow*>(m_window);
+ if (win)
+ {
+ sf::Event event;
+ while (win->PopEvent(event))
+ {
+ pushEvent(event);
+ }
+ }
+ }
+}
+
+} // namespace priv
+} // namespace sf
diff --git a/src/SFML/Window/Haiku/WindowImplHaiku.hpp b/src/SFML/Window/Haiku/WindowImplHaiku.hpp
new file mode 100644
index 00000000..ef194ca1
--- /dev/null
+++ b/src/SFML/Window/Haiku/WindowImplHaiku.hpp
@@ -0,0 +1,212 @@
+////////////////////////////////////////////////////////////
+//
+// SFML - Simple and Fast Multimedia Library
+// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org)
+//
+// This software is provided 'as-is', without any express or implied warranty.
+// In no event will the authors be held liable for any damages arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it freely,
+// subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented;
+// you must not claim that you wrote the original software.
+// If you use this software in a product, an acknowledgment
+// in the product documentation would be appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such,
+// and must not be misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source distribution.
+//
+////////////////////////////////////////////////////////////
+
+#pragma once
+
+////////////////////////////////////////////////////////////
+// Headers
+////////////////////////////////////////////////////////////
+#include <SFML/Window/Event.hpp>
+#include <SFML/Window/Haiku/CursorImpl.hpp>
+#include <SFML/Window/WindowImpl.hpp>
+
+#include <SFML/System/String.hpp>
+
+#include <vector>
+
+class BWindow;
+class BView;
+
+namespace sf
+{
+namespace priv
+{
+////////////////////////////////////////////////////////////
+/// \brief Haiku implementation of WindowImpl
+///
+////////////////////////////////////////////////////////////
+class WindowImplHaiku : public WindowImpl
+{
+public:
+ ////////////////////////////////////////////////////////////
+ /// \brief Construct the window implementation from an existing control
+ ///
+ /// \param handle Platform-specific handle of the control
+ ///
+ ////////////////////////////////////////////////////////////
+ WindowImplHaiku(WindowHandle handle);
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Create the window implementation
+ ///
+ /// \param mode Video mode to use
+ /// \param title Title of the window
+ /// \param style Window style
+ /// \param settings Additional settings for the underlying OpenGL context
+ ///
+ ////////////////////////////////////////////////////////////
+ WindowImplHaiku(VideoMode mode, const String& title, std::uint32_t style, const ContextSettings& settings);
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Destructor
+ ///
+ ////////////////////////////////////////////////////////////
+ ~WindowImplHaiku();
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Get the OS-specific handle of the window
+ ///
+ /// \return Handle of the window
+ ///
+ ////////////////////////////////////////////////////////////
+ WindowHandle getNativeHandle() const override;
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Get the position of the window
+ ///
+ /// \return Position of the window, in pixels
+ ///
+ ////////////////////////////////////////////////////////////
+ Vector2i getPosition() const override;
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Change the position of the window on screen
+ ///
+ /// \param position New position of the window, in pixels
+ ///
+ ////////////////////////////////////////////////////////////
+ void setPosition(Vector2i position) override;
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Get the client size of the window
+ ///
+ /// \return Size of the window, in pixels
+ ///
+ ////////////////////////////////////////////////////////////
+ Vector2u getSize() const override;
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Change the size of the rendering region of the window
+ ///
+ /// \param size New size, in pixels
+ ///
+ ////////////////////////////////////////////////////////////
+ void setSize(Vector2u size) override;
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Change the title of the window
+ ///
+ /// \param title New title
+ ///
+ ////////////////////////////////////////////////////////////
+ void setTitle(const String& title) override;
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Change the window's icon
+ ///
+ /// \param size Icon's width and height, in pixels
+ /// \param pixels Pointer to the pixels in memory, format must be RGBA 32 bits
+ ///
+ ////////////////////////////////////////////////////////////
+ void setIcon(Vector2u size, const std::uint8_t* pixels) override;
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Show or hide the window
+ ///
+ /// \param visible True to show, false to hide
+ ///
+ ////////////////////////////////////////////////////////////
+ void setVisible(bool visible) override;
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Show or hide the mouse cursor
+ ///
+ /// \param visible True to show, false to hide
+ ///
+ ////////////////////////////////////////////////////////////
+ void setMouseCursorVisible(bool visible) override;
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Grab or release the mouse cursor and keeps it from leaving
+ ///
+ /// \param grabbed True to enable, false to disable
+ ///
+ ////////////////////////////////////////////////////////////
+ void setMouseCursorGrabbed(bool grabbed) override;
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Set the displayed cursor to a native system cursor
+ ///
+ /// \param cursor Native system cursor type to display
+ ///
+ ////////////////////////////////////////////////////////////
+ void setMouseCursor(const CursorImpl& cursor) override;
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Enable or disable automatic key-repeat
+ ///
+ /// \param enabled True to enable, false to disable
+ ///
+ ////////////////////////////////////////////////////////////
+ void setKeyRepeatEnabled(bool enabled) override;
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Request the current window to be made the active
+ /// foreground window
+ ///
+ ////////////////////////////////////////////////////////////
+ void requestFocus() override;
+
+ ////////////////////////////////////////////////////////////
+ /// \brief Check whether the window has the input focus
+ ///
+ /// \return True if window has focus, false otherwise
+ ///
+ ////////////////////////////////////////////////////////////
+ bool hasFocus() const override;
+
+protected:
+ ////////////////////////////////////////////////////////////
+ /// \brief Process incoming events from the operating system
+ ///
+ ////////////////////////////////////////////////////////////
+ void processEvents() override;
+
+private:
+ ////////////////////////////////////////////////////////////
+ // Member data
+ ////////////////////////////////////////////////////////////
+ BWindow* m_window; //!< Pointer to the Haiku window
+ BView* m_view; //!< View for key/mouse event capture
+ bool m_ownsWindow; //!< Do we own the window?
+ bool m_keyRepeatEnabled; //!< Is key repeat enabled?
+ CursorImpl m_cursor; //!< Current cursor implementation
+ std::vector<Event> m_eventQueue; //!< Local event queue to store events from BWindow thread
+ // Thread safety needed? Yes, BWindow runs in separate thread.
+ // We need a way to pass events safely.
+ // Semaphores or ports are standard.
+};
+
+} // namespace priv
+} // namespace sf
diff --git a/src/SFML/Window/JoystickImpl.hpp b/src/SFML/Window/JoystickImpl.hpp
index 65ec74d5..79b07e68 100644
--- a/src/SFML/Window/JoystickImpl.hpp
+++ b/src/SFML/Window/JoystickImpl.hpp
@@ -93,4 +93,8 @@ struct JoystickState
#include <SFML/Window/Android/JoystickImpl.hpp>
+#elif defined(SFML_SYSTEM_HAIKU)
+
+#include <SFML/Window/Haiku/JoystickImpl.hpp>
+
#endif
diff --git a/src/SFML/Window/SensorImpl.hpp b/src/SFML/Window/SensorImpl.hpp
index c27acf14..96c4c4c1 100644
--- a/src/SFML/Window/SensorImpl.hpp
+++ b/src/SFML/Window/SensorImpl.hpp
@@ -52,4 +52,8 @@
#include <SFML/Window/Android/SensorImpl.hpp>
+#elif defined(SFML_SYSTEM_HAIKU)
+
+#include <SFML/Window/Haiku/SensorImpl.hpp>
+
#endif
diff --git a/src/SFML/Window/WindowImpl.cpp b/src/SFML/Window/WindowImpl.cpp
index 2b162b47..ead4deb4 100644
--- a/src/SFML/Window/WindowImpl.cpp
+++ b/src/SFML/Window/WindowImpl.cpp
@@ -89,6 +89,13 @@ using WindowImplType = sf::priv::WindowImplAndroid;
#define SFML_VULKAN_IMPLEMENTATION_NOT_AVAILABLE
+#elif defined(SFML_SYSTEM_HAIKU)
+
+#include <SFML/Window/Haiku/WindowImplHaiku.hpp>
+using WindowImplType = sf::priv::WindowImplHaiku;
+
+#define SFML_VULKAN_IMPLEMENTATION_NOT_AVAILABLE
+
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment