diff --git a/CHANGELOG.md b/CHANGELOG.md index 878f7db..55821a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,4 +14,35 @@ ### Notes - No API or ABI changes -- Safe upgrade from **v1.0** - just rebuild your project after updating. \ No newline at end of file +- Safe upgrade from **v1.0** - just rebuild your project after updating. + +## [1.1.0] - 2025-10-17 + +### Features +- **Build:** Added Linux platform support across all modules. +- **Core:** Added Linux backend support. +- **Video:** Added X11-based backend support. +- **Thread:** Added Linux backend support. +- **Opengl:** Added Linux backend support. +- **System:** Added Linux backend support. +- **Video:** Added **palCreateCursorFrom()** to create system cursors. +- **Video:** Added **palSetFBConfig()** to select window FBConfig. +- **Video:** Added **PAL_VIDEO_FEATURE_WINDOW_SET_ICON** to `PalVideoFeatures` enum. +- **System:** Added **PAL_PLATFORM_API_COCOA** to `PalPlatformApiType` enum. +- **System:** Added **PAL_PLATFORM_API_ANDRIOD** to `PalPlatformApiType` enum. +- **System:** Added **PAL_PLATFORM_API_UIKIT** to `PalPlatformApiType` enum. +- **System:** Added **PAL_PLATFORM_API_HEADLESS** to `PalPlatformApiType` enum. +- **Core:** Added **PAL_RESULT_INVALID_FBCONFIG_BACKEND** to `PalResult` enum. + +### Changed +- **System:** `PalCPUInfo.architecture` is now determined at runtime instead of build time. +- **Opengl:** **palEnumerateGLFBConfigs()** now does not use the `glWindow` paramter. Set to `nullptr` + +### Fixed +- Fixed a bug where **enter modal mode and exit modal mode** operations triggered only one event. +- Fixed repeated window state event (**minimized**, **maximized**, **restore**). + +### Notes +- No API or ABI changes - existing Windows code remains compatible. +- Linux video support currently targets **X11** only: **Wayland** is planned for future releases. +- Safe upgrade from **v1.0.1** - just rebuild your project after updating. diff --git a/README.md b/README.md index 57c948c..24677d8 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,17 @@ PAL is a lightweight, low-level, cross-platform abstraction layer in **C**, designed to be **explicit** and as close to the **OS** as possible — similar in philosophy to Vulkan. It gives you precise control without hidden behavior, making it ideal for developers who want performance and predictability. +PAL is transparent. All queries — window size, position, monitor info, and more — reflect the current platform state. Using PAL is like working directly with the OS: it applies no hidden logic, makes no assumptions, and leaves behavior fully in your control. + +This approach gives you total control: you handle events, manage resources, and cache state explicitly. PAL provides the building blocks; how you use them — whether for simple applications or advanced frameworks — is entirely up to you. + +Example – Get Window Size +```c +// Direct query from the platform — not cached by PAL +palGetWindowSize(window, &w, &h); +``` +> Note: palGetWindowSize queries the OS directly. If your application needs continuous updates (e.g., window moves or resizes frequently), it is more efficient to listen to PAL events rather than repeatedly querying the OS. This ensures your app stays performant. + --- ## Why PAL? @@ -60,7 +71,11 @@ For more detailed examples, see the [tests folder](./tests) tests folder, which --- ## Philosophy - +- PAL is a thin layer over the OS, not a framework or library. +- Queries return the current platform state, reflecting any changes made through direct OS calls. +- Developers are responsible for state tracking, caching, and event handling. +- PAL enables cross-platform consistency while preserving full OS behavior and control. +- Advanced users can build libraries or frameworks on top of PAL. - Minimal overhead (close to raw OS calls) - Explicit API (no hidden behavior or defaults) - Event system supporting both polling and callbacks @@ -73,9 +88,10 @@ For more detailed examples, see the [tests folder](./tests) tests folder, which ## Supported Platforms - Windows (Vista+) +- Linux (X11) ## Planned Platforms -- Linux (X11/Wayland) +- Linux (Wayland) - macOS (Cocoa) - Android - iOS @@ -84,6 +100,8 @@ For more detailed examples, see the [tests folder](./tests) tests folder, which - Standard C library - Platform SDKs (Win32, X11, Cocoa, etc.) - [Make for Windows](https://www.gnu.org/software/make/) (if not using Visual Studio) +- XRandR (1.2+) for X11 +- libXcursor for X11 ## Compilers - GCC @@ -97,6 +115,7 @@ For more detailed examples, see the [tests folder](./tests) tests folder, which PAL is written in **C99** and uses **Premake** as its build system. Configure modules via [pal_config.lua](./pal_config.lua). See [pal_config.h](./include/pal/pal_config.h) to see the reflection of modules that will be built. +**Windows** ```bash premake\premake5.exe gmake2 # generate Makefiles (default: GCC) premake\premake5.exe gmake2 --compiler=clang @@ -105,6 +124,11 @@ premake\premake5.exe vs2022 # generate Visual Studio project (default: MS premake\premake5.exe vs2022 --compiler=clang ``` +**Linux** +```bash +./premake/premake5 gmake # generate Makefiles (default: GCC) +``` + Enable tests in `pal_config.lua` by setting `PAL_BUILD_TESTS = true`. --- @@ -132,7 +156,7 @@ PAL uses [Doxygen](https://www.doxygen.nl/) for generating API documentation. ```bash cd docs -make doxygen +doxygen doxyfile ``` The generated HTML docs will be available in `docs/html/`. diff --git a/include/pal/pal_core.h b/include/pal/pal_core.h index 24a2c5e..ce82697 100644 --- a/include/pal/pal_core.h +++ b/include/pal/pal_core.h @@ -236,7 +236,8 @@ typedef enum { PAL_RESULT_INVALID_GL_FBCONFIG, PAL_RESULT_INVALID_GL_VERSION, PAL_RESULT_INVALID_GL_PROFILE, - PAL_RESULT_INVALID_GL_CONTEXT + PAL_RESULT_INVALID_GL_CONTEXT, + PAL_RESULT_INVALID_FBCONFIG_BACKEND } PalResult; /** diff --git a/include/pal/pal_opengl.h b/include/pal/pal_opengl.h index a247634..e31ca92 100644 --- a/include/pal/pal_opengl.h +++ b/include/pal/pal_opengl.h @@ -261,7 +261,7 @@ PAL_API const PalGLInfo* PAL_CALL palGetGLInfo(); * If the count is 0 and the PalGLFBConfigs array is nullptr, the function fails * and returns `PAL_RESULT_INSUFFICIENT_BUFFER`. * - * @param[in] glWindow Pointer to the opengl window. + * @param[in] glWindow Set to nullptr. * @param[in] count Capacity of the PalGLFBConfig array. * @param[out] configs User allocated array of PalGLFBConfig. * @@ -313,10 +313,9 @@ PAL_API const PalGLFBConfig* PAL_CALL palGetClosestGLFBConfig( * The opengl system must be initialized before this call. The created context * will not be made current. * - * After this call, the provided PalGLFBConfig will be set to the window - * permanently and cannot be changed. To change it, you must destroy the window - * and recreate it. If the window already has a PalGLFBConfig, the opengl system - * will use that and discard the provided one. + * The provided PalGLFBConfig must be the same as the one used to create the + * window. Once set, it cannot be changed. To change it, you must destroy the + * window and recreate it. * * @param[in] info Pointer to a PalGLContextCreateInfo struct that specifies * paramters. Must not be nullptr. diff --git a/include/pal/pal_system.h b/include/pal/pal_system.h index 972524f..c8d71bf 100644 --- a/include/pal/pal_system.h +++ b/include/pal/pal_system.h @@ -119,7 +119,11 @@ typedef enum { typedef enum { PAL_PLATFORM_API_WIN32, PAL_PLATFORM_API_WAYLAND, - PAL_PLATFORM_API_X11 + PAL_PLATFORM_API_X11, + PAL_PLATFORM_API_COCOA, + PAL_PLATFORM_API_ANDRIOD, + PAL_PLATFORM_API_UIKIT, + PAL_PLATFORM_API_HEADLESS } PalPlatformApiType; /** diff --git a/include/pal/pal_video.h b/include/pal/pal_video.h index 5ecae08..98d6f69 100644 --- a/include/pal/pal_video.h +++ b/include/pal/pal_video.h @@ -111,6 +111,7 @@ typedef enum { PAL_VIDEO_FEATURE_WINDOW_GET_STYLE = PAL_BIT(28), PAL_VIDEO_FEATURE_CURSOR_SET_POS = PAL_BIT(29), PAL_VIDEO_FEATURE_CURSOR_GET_POS = PAL_BIT(30), + PAL_VIDEO_FEATURE_WINDOW_SET_ICON = PAL_BIT(31), } PalVideoFeatures; /** @@ -186,6 +187,23 @@ typedef enum { PAL_FLASH_TRAY = PAL_BIT(1) /**< Flash the icon of the window.*/ } PalFlashFlag; +/** + * @enum PalFBConfigBackend + * @brief Represents the backend of a FBConfig. + * + * All FBConfig backends follow the format `PAL_CONFIG_BACKEND**` for + * consistency and API use. + * + * @since 1.1 + * @ingroup pal_video + */ +typedef enum { + PAL_CONFIG_BACKEND_EGL, + PAL_CONFIG_BACKEND_GLX, + PAL_CONFIG_BACKEND_WGL, + PAL_CONFIG_BACKEND_PAL_OPENGL /**< Use PAL opengl module backend.*/ +} PalFBConfigBackend; + /** * @enum PalScancode * @brief scancodes (layout independent keys) of a keyboard. @@ -466,6 +484,26 @@ typedef enum { PAL_MOUSE_BUTTON_MAX } PalMouseButton; +/** + * @enum PalCursorType + * @brief System cursor types. + * + * All cursor types follow the format `PAL_CURSOR_**` for + * consistency and API use. + * + * @since 1.1 + * @ingroup pal_video + */ +typedef enum { + PAL_CURSOR_ARROW, + PAL_CURSOR_HAND, + PAL_CURSOR_CROSS, + PAL_CURSOR_IBEAM, + PAL_CURSOR_WAIT, + + PAL_CURSOR_MAX +} PalCursorType; + /** * @struct PalMonitorInfo * @brief Information about a monitor. @@ -651,6 +689,41 @@ PAL_API void PAL_CALL palUpdateVideo(); */ PAL_API PalVideoFeatures PAL_CALL palGetVideoFeatures(); +/** + * @brief Set the FBConfig for the video system. + * + * The video system must be initialized before this call. + * The provided FBConfig will be used for all created windows after this call. + * The `index` is the loop index from the drivers + * supported FBConfigs. + * + * The `backend` is used to tell the video system, the source of the index. + * Examples: PAL_CONFIG_BACKEND_EGL tells the video system, we got this loop + * index from EGL. This will enable the video system to find your FBConfig. + * + * Example Flow: + * Enumerate and select your FBConfig using any backend(EGL, GLX, WGL, etc) + * and just let the video system know which one you used. + * + * If the backend passed is not the same as the one used, + * the video system might still get a FBConfig but it will not be the + * one requested. + * + * @param[in] index The FBConfig driver index. + * @param[in] backend The FBConfig backend or source. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: This function must be called from the main thread. + * + * @since 1.1 + * @ingroup pal_video + */ +PAL_API PalResult PAL_CALL palSetFBConfig( + const int index, + PalFBConfigBackend backend); + /** * @brief Return a list of all connected monitors. * @@ -1451,6 +1524,7 @@ PAL_API PalResult PAL_CALL palSetFocusWindow(PalWindow* window); * @brief Create an icon. * * The video system must be initialized before this call. + * `PAL_VIDEO_FEATURE_WINDOW_SET_ICON` must be supported. * * @param[in] info Pointer to a PalIconCreateInfo struct that specifies * paramters. Must not be nullptr. @@ -1492,9 +1566,10 @@ PAL_API void PAL_CALL palDestroyIcon(PalIcon* icon); * @brief Set the icon for the provided window. * * The video system must be initialized before this call. + * `PAL_VIDEO_FEATURE_WINDOW_SET_ICON` must be supported. * * @param[in] window Pointer to the window. - * @param[in] icon Pointer to the icon. + * @param[in] icon Pointer to the icon. Set to nullptr to revert. * * @return `PAL_RESULT_SUCCESS` on success or a result code on * failure. Call palFormatResult() for more information. @@ -1531,6 +1606,28 @@ PAL_API PalResult PAL_CALL palCreateCursor( const PalCursorCreateInfo* info, PalCursor** outCursor); +/** + * @brief Create a system cursor. + * + * The video system must be initialized before this call. + * + * @param[in] type The system cursor type to create. Must not be nullptr. + * @param[out] outCursor Pointer to a PalCursor to recieve the created + * cursor. Must not be nullptr. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: This function must only be called from the main thread. + * + * @since 1.1 + * @ingroup pal_video + * @sa palDestroyCursor + */ +PAL_API PalResult PAL_CALL palCreateCursorFrom( + PalCursorType type, + PalCursor** outCursor); + /** * @brief Destroy the provided cursor. * @@ -1550,7 +1647,7 @@ PAL_API PalResult PAL_CALL palCreateCursor( PAL_API void PAL_CALL palDestroyCursor(PalCursor* cursor); /** - * @brief Show or hide the provided cursor. + * @brief Show or hide the cursor. * * The video system must be initialized before this call. * This affects all created cursors since the platform (OS) merges all cursors @@ -1646,7 +1743,7 @@ PAL_API PalResult PAL_CALL palSetCursorPos( * The video system must be initialized before this call. * * @param[in] window Pointer to the window. - * @param[in] cursor Pointer to the cursor. + * @param[in] cursor Pointer to the cursor. Set to nullptr to revert. * * @return `PAL_RESULT_SUCCESS` on success or a result code on * failure. Call palFormatResult() for more information. diff --git a/pal.lua b/pal.lua index 0f77413..ce17da0 100644 --- a/pal.lua +++ b/pal.lua @@ -61,25 +61,61 @@ project "PAL" if (PAL_BUILD_SYSTEM) then filter {"system:windows", "configurations:*"} - files { "src/system/pal_system_win32.c" } + files { "src/system/pal_system_win32.c" } + + filter {"system:linux", "configurations:*"} + files { "src/system/pal_system_linux.c" } filter {} end if (PAL_BUILD_THREAD) then filter {"system:windows", "configurations:*"} - files { "src/thread/pal_thread_win32.c" } + files { "src/thread/pal_thread_win32.c" } + + filter {"system:linux", "configurations:*"} + files { "src/thread/pal_thread_linux.c" } + filter {} end if (PAL_BUILD_VIDEO) then filter {"system:windows", "configurations:*"} - files { "src/video/pal_video_win32.c" } + files { "src/video/pal_video_win32.c" } + + filter {"system:linux", "configurations:*"} + files { "src/video/pal_video_linux.c" } + + -- check for wayland support. This is cross compiler + local paths = { + "/usr/include/wayland-client.h", + "/usr/include/x86_64-linux-gnu/wayland-client.h" + } + + local found = false + for _, path in ipairs(paths) do + local file = io.open(path, "r") + if file then + file:close() + found = true + break + end + end + + if found then + defines { "PAL_HAS_WAYLAND=1" } + else + defines { "PAL_HAS_WAYLAND=0" } + end + filter {} end if (PAL_BUILD_OPENGL) then filter {"system:windows", "configurations:*"} - files { "src/opengl/pal_opengl_win32.c" } + files { "src/opengl/pal_opengl_win32.c" } + + filter {"system:linux", "configurations:*"} + files { "src/opengl/pal_opengl_linux.c" } filter {} end diff --git a/premake/premake5 b/premake/premake5 new file mode 100755 index 0000000..1b601b0 Binary files /dev/null and b/premake/premake5 differ diff --git a/premake5.lua b/premake5.lua index e48d0d4..c0aa049 100644 --- a/premake5.lua +++ b/premake5.lua @@ -36,6 +36,10 @@ workspace "PAL_workspace" systemversion "latest" cdialect "C99" + filter {"system:linux", "configurations:*"} + architecture "x86_64" + cdialect "C99" + filter "configurations:Debug" symbols "on" runtime "Debug" diff --git a/src/opengl/pal_opengl_linux.c b/src/opengl/pal_opengl_linux.c new file mode 100644 index 0000000..dc38610 --- /dev/null +++ b/src/opengl/pal_opengl_linux.c @@ -0,0 +1,1169 @@ + +/** + +Copyright (C) 2025 Nicholas Agbo + +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. + + */ + +// ================================================== +// Includes +// ================================================== + +#include "pal/pal_opengl.h" + +#include +#include +#include +#include + +// ================================================== +// Typedefs, enums and structs +// ================================================== + +#ifndef GL_VENDOR +#define GL_VENDOR 0x1F00 +#define GL_RENDERER 0x1F01 +#define GL_VERSION 0x1F02 +#define GL_EXTENSIONS 0x1F03 +#endif // GL_VENDOR + +typedef unsigned int GLenum; +typedef unsigned char GLubyte; +typedef int32_t EGLint; +typedef unsigned int EGLBoolean; +typedef unsigned int EGLenum; + +typedef unsigned long int khronos_uintptr_t; +typedef khronos_uintptr_t EGLNativeWindowType; + +typedef void* EGLConfig; +typedef void* EGLSurface; +typedef void* EGLContext; +typedef void* EGLDisplay; +typedef void* EGLNativeDisplayType; + +#ifndef EGL_OPENGL_API +// EGL header is not included +/* C++ / C typecast macros for special EGL handle values */ +#if defined(__cplusplus) +#define EGL_CAST(type, value) (static_cast(value)) +#else +#define EGL_CAST(type, value) ((type)(value)) +#endif + +#define EGL_OPENGL_API 0x30A2 +#define EGL_OPENGL_BIT 0x0008 +#define EGL_NO_CONTEXT EGL_CAST(EGLContext, 0) +#define EGL_NO_DISPLAY EGL_CAST(EGLDisplay, 0) +#define EGL_NO_SURFACE EGL_CAST(EGLSurface, 0) +#define EGL_DEFAULT_DISPLAY EGL_CAST(EGLNativeDisplayType, 0) +#define EGL_GL_COLORSPACE 0x309D +#define EGL_COLORSPACE_sRGB 0x3089 +#define EGL_COLORSPACE_LINEAR 0x308A +#define EGL_COLOR_BUFFER_TYPE 0x303F +#define EGL_OPENGL_ES_BIT 0x0001 +#define EGL_OPENGL_ES_API 0x30A0 +#define EGL_RENDERABLE_TYPE 0x3040 +#define EGL_RGB_BUFFER 0x308E +#define EGL_ALPHA_SIZE 0x3021 +#define EGL_BAD_ATTRIBUTE 0x3004 +#define EGL_BAD_CONFIG 0x3005 +#define EGL_BAD_CONTEXT 0x3006 +#define EGL_BAD_SURFACE 0x300D +#define EGL_EXTENSIONS 0x3055 +#define EGL_GREEN_SIZE 0x3023 +#define EGL_MAX_PBUFFER_WIDTH 0x302C +#define EGL_NATIVE_VISUAL_ID 0x302E +#define EGL_NONE 0x3038 +#define EGL_HEIGHT 0x3056 +#define EGL_WIDTH 0x3057 +#define EGL_OPENGL_ES_BIT 0x0001 +#define EGL_OPENGL_ES2_BIT 0x0004 +#define EGL_OPENGL_ES3_BIT 0x00000040 + +#define EGL_PBUFFER_BIT 0x0001 +#define EGL_RED_SIZE 0x3024 +#define EGL_BLUE_SIZE 0x3022 +#define EGL_DEPTH_SIZE 0x3025 +#define EGL_SAMPLES 0x3031 +#define EGL_STENCIL_SIZE 0x3026 +#define EGL_SURFACE_TYPE 0x3033 +#define EGL_WINDOW_BIT 0x0004 +#define EGL_CONTEXT_MAJOR_VERSION 0x3098 +#define EGL_CONTEXT_MINOR_VERSION 0x30FB + +#define EGL_CONTEXT_MAJOR_VERSION_KHR 0x3098 +#define EGL_CONTEXT_MINOR_VERSION_KHR 0x30FB +#define EGL_CONTEXT_FLAGS_KHR 0x30FC +#define EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR 0x30FD +#define EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR 0x31BD +#define EGL_NO_RESET_NOTIFICATION_KHR 0x31BE +#define EGL_LOSE_CONTEXT_ON_RESET_KHR 0x31BF +#define EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR 0x00000001 +#define EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR 0x00000002 +#define EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR 0x00000004 +#define EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR 0x00000001 +#define EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR 0x00000002 +#define EGL_OPENGL_ES3_BIT_KHR 0x00000040 +#define EGL_CONTEXT_OPENGL_NO_ERROR_KHR 0x31B3 +#define EGL_CONTEXT_RELEASE_BEHAVIOR_KHR 0x2097 +#define EGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_KHR 0x2098 +#define EGL_GL_COLORSPACE_KHR 0x309D +#define EGL_GL_COLORSPACE_SRGB_KHR 0x3089 +#define EGL_GL_COLORSPACE_LINEAR_KHR 0x308A + +#endif // EGL header + +typedef void* (*eglGetProcAddressFn)(const char*); + +typedef EGLContext (*eglCreateContextFn)( + EGLDisplay, + EGLConfig, + EGLContext, + const EGLint*); + +typedef EGLBoolean (*eglDestroyContextFn)( + EGLDisplay, + EGLContext); + +typedef EGLBoolean (*eglMakeCurrentFn)( + EGLDisplay, + EGLSurface, + EGLSurface, + EGLContext); + +typedef EGLBoolean (*eglSwapBuffersFn)( + EGLDisplay, + EGLSurface); + +typedef EGLBoolean (*eglSwapIntervalFn)( + EGLDisplay, + EGLint); + +typedef EGLBoolean (*eglInitializeFn)( + EGLDisplay, + EGLint*, + EGLint*); + +typedef EGLBoolean (*eglTerminateFn)(EGLDisplay); + +typedef EGLDisplay (*eglGetDisplayFn)(EGLNativeDisplayType); + +typedef EGLBoolean (*eglDestroySurfaceFn)( + EGLDisplay, + EGLSurface); + +typedef EGLSurface (*eglCreatePbufferSurfaceFn)( + EGLDisplay, + EGLConfig, + const EGLint*); + +typedef EGLBoolean (*eglChooseConfigFn)( + EGLDisplay, + const EGLint*, + EGLConfig*, + EGLint, + EGLint*); + +typedef EGLBoolean (*eglGetConfigAttribFn)( + EGLDisplay, + EGLConfig, + EGLint, + EGLint*); + +typedef EGLint (*eglGetErrorFn)(void); + +typedef EGLBoolean (*eglBindAPIFn)(EGLenum); + +typedef const char* (*eglQueryStringFn)( + EGLDisplay, + EGLint); + +typedef EGLBoolean (*eglGetConfigsFn)( + EGLDisplay, + EGLConfig*, + EGLint, + EGLint*); + +typedef EGLSurface (*eglCreateWindowSurfaceFn)( + EGLDisplay, + EGLConfig, + EGLNativeWindowType, + const EGLint*); + +typedef const GLubyte* (*glGetStringFn)(GLenum); + +typedef struct { + bool used; + EGLSurface surface; + PalGLContext* context; +} ContextData; + +typedef struct { + bool initialized; + Int32 maxContextData; + const PalAllocator* allocator; + + eglGetProcAddressFn eglGetProcAddress; + eglCreateContextFn eglCreateContext; + eglDestroyContextFn eglDestroyContext; + eglMakeCurrentFn eglMakeCurrent; + eglSwapBuffersFn eglSwapBuffers; + eglSwapIntervalFn eglSwapInterval; + eglInitializeFn eglInitialize; + eglTerminateFn eglTerminate; + eglGetDisplayFn eglGetDisplay; + eglDestroySurfaceFn eglDestroySurface; + eglCreatePbufferSurfaceFn eglCreatePbufferSurface; + eglChooseConfigFn eglChooseConfig; + eglGetConfigAttribFn eglGetConfigAttrib; + eglGetErrorFn eglGetError; + eglBindAPIFn eglBindAPI; + eglQueryStringFn eglQueryString; + eglGetConfigsFn eglGetConfigs; + eglCreateWindowSurfaceFn eglCreateWindowSurface; + + glGetStringFn glGetString; + + void* handle; + ContextData* contextData; + EGLDisplay display; + PalGLInfo info; +} GLLinux; + +static GLLinux s_GL = {0}; + +// ================================================== +// Internal API +// ================================================== + +static inline bool checkExtension( + const char* extension, + const char* extensions) +{ + const char* start = extensions; + size_t extensionLen = strlen(extension); + + for (;;) { + const char* where = nullptr; + const char* terminator = nullptr; + + where = strstr(start, extension); + if (!where) { + return false; + } + + // the extension was found, we find the terminator by adding the sizeof + // the extension + terminator = where + extensionLen; + if (where == start || *(where - 1) == ' ') { + if (*terminator == ' ' || *terminator == '\0') { + return true; + } + } + + start = terminator; + } +} + +static ContextData* getFreeContextData() +{ + for (int i = 0; i < s_GL.maxContextData; ++i) { + if (!s_GL.contextData[i].used) { + s_GL.contextData[i].used = true; + return &s_GL.contextData[i]; + } + } + + // resize the data array + // this will almost not reach here since most setups are 1-4 monitors + ContextData* data = nullptr; + int count = s_GL.maxContextData * 2; // double the size + int freeIndex = s_GL.maxContextData + 1; + data = palAllocate(s_GL.allocator, sizeof(ContextData) * count, 0); + if (data) { + memcpy( + data, + s_GL.contextData, + s_GL.maxContextData * sizeof(ContextData)); + + palFree(s_GL.allocator, s_GL.contextData); + s_GL.contextData = data; + s_GL.maxContextData = count; + + s_GL.contextData[freeIndex].used = true; + return &s_GL.contextData[freeIndex]; + } + return nullptr; +} + +static ContextData* findContextData(PalGLContext* context) +{ + for (int i = 0; i < s_GL.maxContextData; ++i) { + if (s_GL.contextData[i].used && + s_GL.contextData[i].context == context) { + return &s_GL.contextData[i]; + } + } +} + +static void freeContextData(PalGLContext* context) +{ + for (int i = 0; i < s_GL.maxContextData; ++i) { + if (s_GL.contextData[i].used && + s_GL.contextData[i].context == context) { + s_GL.contextData[i].used = false; + } + } +} + +// ================================================== +// Public API +// ================================================== + +PalResult PAL_CALL palInitGL(const PalAllocator* allocator) +{ + if (s_GL.initialized) { + return PAL_RESULT_SUCCESS; + } + + if (allocator && (!allocator->allocate || !allocator->free)) { + return PAL_RESULT_INVALID_ALLOCATOR; + } + + s_GL.maxContextData = 16; // initial size + s_GL.contextData = palAllocate( + s_GL.allocator, + sizeof(ContextData) * s_GL.maxContextData, + 0); + + if (!s_GL.maxContextData) { + return PAL_RESULT_OUT_OF_MEMORY; + } + + s_GL.handle = dlopen("libEGL.so", RTLD_LAZY); + if (!s_GL.handle) { + return PAL_RESULT_PLATFORM_FAILURE; + } + + // clang-format off + + s_GL.eglGetProcAddress = (eglGetProcAddressFn)dlsym( + s_GL.handle, + "eglGetProcAddress"); + + s_GL.eglCreateContext = (eglCreateContextFn)s_GL.eglGetProcAddress( + "eglCreateContext"); + + s_GL.eglDestroyContext = (eglDestroyContextFn)s_GL.eglGetProcAddress( + "eglDestroyContext"); + + s_GL.eglDestroySurface = (eglDestroySurfaceFn)s_GL.eglGetProcAddress( + "eglDestroySurface"); + + s_GL.eglMakeCurrent = (eglMakeCurrentFn)s_GL.eglGetProcAddress( + "eglMakeCurrent"); + + s_GL.eglSwapBuffers = (eglSwapBuffersFn)s_GL.eglGetProcAddress( + "eglSwapBuffers"); + + s_GL.eglSwapInterval = (eglSwapIntervalFn)s_GL.eglGetProcAddress( + "eglSwapInterval"); + + s_GL.eglInitialize = (eglInitializeFn)s_GL.eglGetProcAddress( + "eglInitialize"); + + s_GL.eglTerminate = (eglTerminateFn)s_GL.eglGetProcAddress("eglTerminate"); + + s_GL.eglGetDisplay = (eglGetDisplayFn)s_GL.eglGetProcAddress( + "eglGetDisplay"); + + s_GL.eglCreatePbufferSurface = (eglCreatePbufferSurfaceFn)s_GL.eglGetProcAddress( + "eglCreatePbufferSurface"); + + s_GL.eglChooseConfig = (eglChooseConfigFn)s_GL.eglGetProcAddress( + "eglChooseConfig"); + + s_GL.eglGetConfigAttrib = (eglGetConfigAttribFn)s_GL.eglGetProcAddress( + "eglGetConfigAttrib"); + + s_GL.eglGetError = (eglGetErrorFn)s_GL.eglGetProcAddress("eglGetError"); + s_GL.eglBindAPI = (eglBindAPIFn)s_GL.eglGetProcAddress("eglBindAPI"); + + s_GL.eglQueryString = (eglQueryStringFn)s_GL.eglGetProcAddress( + "eglQueryString"); + + s_GL.eglGetConfigs = (eglGetConfigsFn)s_GL.eglGetProcAddress( + "eglGetConfigs"); + + s_GL.eglCreateWindowSurface = (eglCreateWindowSurfaceFn)s_GL.eglGetProcAddress( + "eglCreateWindowSurface"); + + if (!s_GL.eglBindAPI || + !s_GL.eglChooseConfig || + !s_GL.eglCreateContext || + !s_GL.eglCreatePbufferSurface || + !s_GL.eglDestroyContext || + !s_GL.eglDestroySurface || + !s_GL.eglGetConfigAttrib || + !s_GL.eglGetDisplay || + !s_GL.eglGetError || + !s_GL.eglGetProcAddress || + !s_GL.eglInitialize || + !s_GL.eglMakeCurrent || + !s_GL.eglSwapBuffers || + !s_GL.eglSwapInterval || + !s_GL.eglTerminate || + !s_GL.eglQueryString || + !s_GL.eglGetConfigs || + !s_GL.eglCreateWindowSurface) { + return PAL_RESULT_PLATFORM_FAILURE; + } + + // create a dummy context + if (!s_GL.eglBindAPI(EGL_OPENGL_API)) { + return PAL_RESULT_PLATFORM_FAILURE; + } + + EGLDisplay display = s_GL.eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (display == EGL_NO_DISPLAY) { + return PAL_RESULT_PLATFORM_FAILURE; + } + + if (!s_GL.eglInitialize(display, nullptr, nullptr)) { + return PAL_RESULT_PLATFORM_FAILURE; + } + + // use a simple FBConfig + EGLConfig config; + int numConfigs; + EGLint attribs[] = { + EGL_RENDERABLE_TYPE, + EGL_OPENGL_BIT, + EGL_SURFACE_TYPE, + EGL_PBUFFER_BIT, + EGL_NONE}; + + EGLint type; + s_GL.eglChooseConfig(display, attribs, &config, 1, &numConfigs); + s_GL.eglGetConfigAttrib(display, config, EGL_RENDERABLE_TYPE, &type); + if (!(type & EGL_OPENGL_BIT)) { + // we must support opengl API (desktop) + return PAL_RESULT_PLATFORM_FAILURE; + } + + // Since we don't want to create dummy window to get the driver info + // we use EGL_OPENGL_ES2_BIT to create without a window + if (!(type & EGL_OPENGL_ES2_BIT)) { + // FIXME: create a dummy window if EGL_OPENGL_ES2_BIT + return PAL_RESULT_PLATFORM_FAILURE; + } + + EGLSurface surface = EGL_NO_SURFACE; + EGLint pBufferAttribs[] = { + EGL_WIDTH, 1, + EGL_HEIGHT, 1, + EGL_NONE}; + + surface = s_GL.eglCreatePbufferSurface( + display, + config, + pBufferAttribs); + + if (surface == EGL_NO_SURFACE) { + return PAL_RESULT_PLATFORM_FAILURE; + } + + EGLint contextAttrib[] = { + EGL_CONTEXT_MAJOR_VERSION, 2, + EGL_CONTEXT_MINOR_VERSION, 1, + EGL_NONE}; + + // create a dummy context + EGLContext context = EGL_NO_CONTEXT; + context = s_GL.eglCreateContext( + display, + config, + EGL_NO_CONTEXT, + contextAttrib); + + if (!s_GL.eglMakeCurrent(display, surface, surface, context)) { + return PAL_RESULT_PLATFORM_FAILURE; + } + + s_GL.glGetString = (glGetStringFn)s_GL.eglGetProcAddress("glGetString"); + const char* version = (const char*)s_GL.glGetString(GL_VERSION); + if (version) { +#ifdef _MSC_VER + sscanf_s(version, "%d.%d", &s_GL.info.major, &s_GL.info.minor); +#else + sscanf(version, "%d.%d", &s_GL.info.major, &s_GL.info.minor); +#endif + } + + const char* renderer = (const char*)s_GL.glGetString(GL_RENDERER); + const char* vendor = (const char*)s_GL.glGetString(GL_VENDOR); + strcpy(s_GL.info.vendor, vendor); + strcpy(s_GL.info.version, version); + strcpy(s_GL.info.graphicsCard, renderer); + + // EGL extensions can be queried without a bound context + // we just do that over here after making the context current + const char* extensions = s_GL.eglQueryString(display, EGL_EXTENSIONS); + if (extensions) { + // color space + if (checkExtension("EGL_KHR_gl_colorspace", extensions)) { + s_GL.info.extensions |= PAL_GL_EXTENSION_COLORSPACE_SRGB; + } + + // create context + if (checkExtension("EGL_KHR_create_context", extensions)) { + s_GL.info.extensions |= PAL_GL_EXTENSION_CREATE_CONTEXT; + s_GL.info.extensions |= PAL_GL_EXTENSION_CONTEXT_PROFILE; + } + + // robustness + if (checkExtension("EGL_EXT_create_context_robustness", extensions)) { + s_GL.info.extensions |= PAL_GL_EXTENSION_ROBUSTNESS; + } + + // no error + if (checkExtension("EGL_KHR_create_context_no_error", extensions)) { + s_GL.info.extensions |= PAL_GL_EXTENSION_NO_ERROR; + } + + // flush control + if (checkExtension("EGL_KHR_context_flush_control", extensions)) { + s_GL.info.extensions |= PAL_GL_EXTENSION_FLUSH_CONTROL; + } + + // swap control + if (checkExtension("EGL_EXT_swap_control", extensions)) { + s_GL.info.extensions |= PAL_GL_EXTENSION_FLUSH_CONTROL; + } + + if (checkExtension("EGL_EXT_swap_control_tear", extensions)) { + s_GL.info.extensions |= PAL_GL_EXTENSION_FLUSH_CONTROL; + } + } + + // part of the core API + s_GL.info.extensions |= PAL_GL_EXTENSION_MULTISAMPLE; + + if (type & EGL_OPENGL_ES_BIT || + type & EGL_OPENGL_ES2_BIT || + type & EGL_OPENGL_ES3_BIT) { + s_GL.info.extensions |= PAL_GL_EXTENSION_CONTEXT_PROFILE_ES2; + } + + // check if we support the core swap interval + if (s_GL.eglSwapInterval) { + s_GL.info.extensions |= PAL_GL_EXTENSION_SWAP_CONTROL; + } + + s_GL.eglMakeCurrent( + display, + EGL_NO_SURFACE, + EGL_NO_SURFACE, + EGL_NO_CONTEXT); + + // clang-format on + + s_GL.eglDestroyContext(display, context); + s_GL.eglDestroySurface(display, surface); + + s_GL.allocator = allocator; + s_GL.initialized = true; + s_GL.display = display; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palShutdownGL() +{ + if (!s_GL.initialized) { + return; + } + + palFree(s_GL.allocator, s_GL.contextData); + s_GL.eglTerminate(s_GL.display); + dlclose(s_GL.handle); + s_GL.initialized = false; +} + +const PalGLInfo* PAL_CALL palGetGLInfo() +{ + if (!s_GL.initialized) { + return nullptr; + } + return &s_GL.info; +} + +PalResult PAL_CALL palEnumerateGLFBConfigs( + PalGLWindow* glWindow, + Int32* count, + PalGLFBConfig* configs) +{ + if (!s_GL.initialized) { + return PAL_RESULT_GL_NOT_INITIALIZED; + } + + if (!count) { + return PAL_RESULT_NULL_POINTER; + } + + if (count == 0 && configs) { + return PAL_RESULT_INSUFFICIENT_BUFFER; + } + + Int32 configCount = 0; + Int32 maxConfigCount = 0; + + if (configs) { + maxConfigCount = *count; + } + + // get the number of configs and filter the ones for opengl desktop + EGLint numConfigs = 0; + if (!s_GL.eglGetConfigs(s_GL.display, nullptr, 0, &numConfigs)) { + return PAL_RESULT_PLATFORM_FAILURE; + } + + EGLint configSize = sizeof(EGLConfig) * numConfigs; + EGLConfig* eglConfigs = palAllocate(s_GL.allocator, configSize, 0); + if (!eglConfigs) { + return PAL_RESULT_OUT_OF_MEMORY; + } + + s_GL.eglGetConfigs(s_GL.display, eglConfigs, numConfigs, &numConfigs); + for (int i = 0; i < numConfigs; i++) { + // attributes we care about + EGLint surfaceType = 0, renderable = 0; + EGLint colorType = 0; + + EGLConfig config = eglConfigs[i]; + s_GL.eglGetConfigAttrib( + s_GL.display, + config, + EGL_SURFACE_TYPE, + &surfaceType); + + s_GL.eglGetConfigAttrib( + s_GL.display, + config, + EGL_RENDERABLE_TYPE, + &renderable); + + s_GL.eglGetConfigAttrib( + s_GL.display, + config, + EGL_COLOR_BUFFER_TYPE, + &colorType); + + // we need only opengl API configs + if (colorType != EGL_RGB_BUFFER) { + continue; + } + + if (!(renderable & EGL_OPENGL_BIT)) { + continue; + } + + if (!(surfaceType & EGL_WINDOW_BIT)) { + continue; + } + + if (configs && configCount < maxConfigCount) { + // get this only if user supplied an output buffer (PalGLFBConfig*) + PalGLFBConfig* fbConfig = &configs[configCount]; + fbConfig->index = i; + + EGLint redBits, greenBits, blueBits, alphaBits; + EGLint depthBits, stencilBits, samples; + + s_GL.eglGetConfigAttrib( + s_GL.display, + config, + EGL_RED_SIZE, + &redBits); + + s_GL.eglGetConfigAttrib( + s_GL.display, + config, + EGL_GREEN_SIZE, + &greenBits); + + s_GL.eglGetConfigAttrib( + s_GL.display, + config, + EGL_BLUE_SIZE, + &blueBits); + + s_GL.eglGetConfigAttrib( + s_GL.display, + config, + EGL_ALPHA_SIZE, + &alphaBits); + + s_GL.eglGetConfigAttrib( + s_GL.display, + config, + EGL_DEPTH_SIZE, + &depthBits); + + s_GL.eglGetConfigAttrib( + s_GL.display, + config, + EGL_STENCIL_SIZE, + &stencilBits); + + s_GL.eglGetConfigAttrib( + s_GL.display, + config, + EGL_SAMPLES, + &samples); + + if (samples == 0) { + samples = 1; + } + + fbConfig->redBits = redBits; + fbConfig->greenBits = greenBits; + fbConfig->blueBits = blueBits; + fbConfig->alphaBits = alphaBits; + fbConfig->depthBits = depthBits; + fbConfig->stencilBits = stencilBits; + fbConfig->samples = samples; + + // True for EGL_WINDOW_BIT + fbConfig->doubleBuffer = true; + fbConfig->stereo = false; + + if (s_GL.info.extensions & PAL_GL_EXTENSION_COLORSPACE_SRGB) { + // since EGL does not have a bit to check SRGB support + // we check if all the color bits are greater than or equal to 8 + if (fbConfig->redBits >= 8 && fbConfig->greenBits >= 8 && + fbConfig->blueBits >= 8) { + fbConfig->sRGB = true; + } + } else { + fbConfig->sRGB = false; + } + } + configCount++; + } + + palFree(s_GL.allocator, eglConfigs); + if (!configs) { + *count = configCount; + } + + return PAL_RESULT_SUCCESS; +} + +const PalGLFBConfig* PAL_CALL palGetClosestGLFBConfig( + PalGLFBConfig* configs, + Int32 count, + const PalGLFBConfig* desired) +{ + if (!s_GL.initialized) { + return nullptr; + } + + if (!configs || !desired) { + return nullptr; + } + + if (count == 0) { + return nullptr; + } + + Int32 score = 0; + Int32 bestScore = 0x7FFFFFFF; + PalGLFBConfig* best = nullptr; + for (Int32 i = 0; i < count; i++) { + PalGLFBConfig* tmp = &configs[i]; + + // filter out hard constraints + if (desired->doubleBuffer && !tmp->doubleBuffer) { + continue; + } + + if (desired->stereo && !tmp->stereo) { + continue; + } + + score = 0; + + // score color bits + score += abs(tmp->redBits - desired->redBits); + score += abs(tmp->greenBits - desired->greenBits); + score += abs(tmp->blueBits - desired->blueBits); + score += abs(tmp->alphaBits - desired->alphaBits); + score += abs(tmp->depthBits - desired->depthBits); + score += abs(tmp->stencilBits - desired->stencilBits); + + // score soft constraints + if (desired->samples != tmp->samples) { + score += 1000; + } + + if (desired->sRGB != tmp->sRGB) { + score += 500; + } + + if (score < bestScore) { + bestScore = score; + best = &configs[i]; + } + } + + return best; +} + +// ================================================== +// Context +// ================================================== + +PalResult PAL_CALL palCreateGLContext( + const PalGLContextCreateInfo* info, + PalGLContext** outContext) +{ + if (!s_GL.initialized) { + return PAL_RESULT_GL_NOT_INITIALIZED; + } + + if (!info || !outContext) { + return PAL_RESULT_NULL_POINTER; + } + + if (!info->window || !info->fbConfig) { + return PAL_RESULT_NULL_POINTER; + } + + // check support for requested features + if (info->profile != PAL_GL_PROFILE_NONE) { + if (!(s_GL.info.extensions & PAL_GL_EXTENSION_CONTEXT_PROFILE)) { + return PAL_RESULT_GL_EXTENSION_NOT_SUPPORTED; + } + } + + if (info->forward) { + if (!(s_GL.info.extensions & PAL_GL_EXTENSION_CREATE_CONTEXT)) { + return PAL_RESULT_GL_EXTENSION_NOT_SUPPORTED; + } + } + + if (info->reset != PAL_GL_CONTEXT_RESET_NONE) { + if (!(s_GL.info.extensions & PAL_GL_EXTENSION_ROBUSTNESS)) { + return PAL_RESULT_GL_EXTENSION_NOT_SUPPORTED; + } + } + + if (info->noError) { + if (!(s_GL.info.extensions & PAL_GL_EXTENSION_NO_ERROR)) { + return PAL_RESULT_GL_EXTENSION_NOT_SUPPORTED; + } + } + + if (info->release != PAL_GL_RELEASE_BEHAVIOR_NONE) { + if (!(s_GL.info.extensions & PAL_GL_EXTENSION_FLUSH_CONTROL)) { + return PAL_RESULT_GL_EXTENSION_NOT_SUPPORTED; + } + } + + // clang-format off + + // check version + bool valid = info->major < s_GL.info.major || + (info->major == s_GL.info.major && info->minor <= s_GL.info.minor); + // clang-format on + + if (!valid) { + return PAL_RESULT_INVALID_GL_VERSION; + } + + ContextData* data = getFreeContextData(); + if (!data) { + return PAL_RESULT_OUT_OF_MEMORY; + } + + // we need to get the EGL config from the user supplied index + EGLint numConfigs = 0; + if (!s_GL.eglGetConfigs(s_GL.display, nullptr, 0, &numConfigs)) { + return PAL_RESULT_PLATFORM_FAILURE; + } + + EGLint configSize = sizeof(EGLConfig) * numConfigs; + EGLConfig* eglConfigs = palAllocate(s_GL.allocator, configSize, 0); + if (!eglConfigs) { + return PAL_RESULT_OUT_OF_MEMORY; + } + + s_GL.eglGetConfigs(s_GL.display, eglConfigs, numConfigs, &numConfigs); + EGLConfig config = eglConfigs[info->fbConfig->index]; + + EGLContext* share = nullptr; + if (info->shareContext) { + share = (EGLContext)info->shareContext; + } + + Int32 attribs[40]; + Int32 index = 0; + Int32 profile = 0; + Int32 flags = 0; + + // set context attributes + // the first element is the key and the second is the value + // set version + attribs[index++] = EGL_CONTEXT_MAJOR_VERSION_KHR; // key + attribs[index++] = info->major; // value + + attribs[index++] = EGL_CONTEXT_MINOR_VERSION_KHR; + attribs[index++] = info->minor; + + // set profile mask + if (info->profile != PAL_GL_PROFILE_NONE) { + attribs[index++] = EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR; + + if (info->profile == PAL_GL_PROFILE_COMPATIBILITY) { + profile = EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR; + } else if (info->profile == PAL_GL_PROFILE_CORE) { + profile = EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR; + } + + attribs[index++] = info->profile; + } + + // set forward flag + if (info->forward) { + flags |= EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR; + } + + // set debug flag + if (info->debug) { + flags |= EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR; + } + + // set robustness + if (info->reset != PAL_GL_CONTEXT_RESET_NONE) { + flags |= EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR; + attribs[index++] = EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR; + + if (info->reset == PAL_GL_CONTEXT_RESET_LOSE_CONTEXT) { + attribs[index++] = EGL_LOSE_CONTEXT_ON_RESET_KHR; + + } else if (info->reset == PAL_GL_CONTEXT_RESET_NO_NOTIFICATION) { + attribs[index++] = EGL_NO_RESET_NOTIFICATION_KHR; + } + } + + // set no error + if (info->noError) { + attribs[index++] = EGL_CONTEXT_OPENGL_NO_ERROR_KHR; + attribs[index++] = true; + } + + // release + if (info->release != PAL_GL_RELEASE_BEHAVIOR_NONE) { + attribs[index++] = EGL_CONTEXT_RELEASE_BEHAVIOR_KHR; + attribs[index++] = EGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_KHR; + } + + if (flags) { + attribs[index++] = EGL_CONTEXT_FLAGS_KHR; + attribs[index++] = flags; + } + attribs[index++] = EGL_NONE; + + // clang-format off + // create context + EGLContext context = s_GL.eglCreateContext( + s_GL.display, + config, + EGL_NO_CONTEXT, + attribs); + // clang-format on + + if (context == EGL_NO_CONTEXT) { + EGLint error = s_GL.eglGetError(); + if (error == EGL_BAD_CONFIG) { + return PAL_RESULT_INVALID_GL_FBCONFIG; + + } else if (error == EGL_BAD_ATTRIBUTE) { + // we just return invalid version + // it could be invalid profile + return PAL_RESULT_INVALID_GL_VERSION; + + } else { + return PAL_RESULT_PLATFORM_FAILURE; + } + } + + // create a window surface + // The only attrib we want is colorspace + EGLint surfaceAttribs[3]; + surfaceAttribs[0] = EGL_GL_COLORSPACE; + if (info->fbConfig->sRGB) { + surfaceAttribs[1] = EGL_GL_COLORSPACE_SRGB_KHR; + } else { + surfaceAttribs[1] = EGL_GL_COLORSPACE_LINEAR_KHR; + } + surfaceAttribs[2] = EGL_NONE; + + EGLSurface surface = s_GL.eglCreateWindowSurface( + s_GL.display, + config, + (EGLNativeWindowType)info->window->window, + surfaceAttribs); + + if (surface == EGL_NO_SURFACE) { + EGLint error = s_GL.eglGetError(); + if (error == EGL_BAD_CONFIG) { + return PAL_RESULT_INVALID_GL_FBCONFIG; + + } else { + return PAL_RESULT_PLATFORM_FAILURE; + } + } + + palFree(s_GL.allocator, eglConfigs); + data->context = (PalGLContext*)context; + data->surface = surface; + + *outContext = (PalGLContext*)context; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palDestroyGLContext(PalGLContext* context) +{ + if (s_GL.initialized && context) { + ContextData* data = findContextData(context); + if (data) { + s_GL.eglDestroyContext(s_GL.display, (EGLContext)context); + s_GL.eglDestroySurface(s_GL.display, data->surface); + data->used = false; + } + } +} + +PalResult PAL_CALL palMakeContextCurrent( + PalGLWindow* glWindow, + PalGLContext* context) +{ + if (!s_GL.initialized) { + return PAL_RESULT_GL_NOT_INITIALIZED; + } + + if ((!glWindow && context) || (glWindow && !context)) { + return PAL_RESULT_NULL_POINTER; + } + + if (context && glWindow) { + ContextData* data = findContextData(context); + if (!data) { + return PAL_RESULT_INVALID_GL_CONTEXT; + } + + EGLint ret = s_GL.eglMakeCurrent( + s_GL.display, + data->surface, + data->surface, + (EGLConfig)context); + + if (!ret) { + EGLint error = s_GL.eglGetError(); + if (error == EGL_BAD_CONTEXT) { + return PAL_RESULT_INVALID_GL_CONTEXT; + + } else if (error == EGL_BAD_SURFACE) { + // since we always create a window surface + return PAL_RESULT_INVALID_GL_WINDOW; + + } else { + return PAL_RESULT_PLATFORM_FAILURE; + } + } + + } else if (!context && !glWindow) { + s_GL.eglMakeCurrent( + s_GL.display, + EGL_NO_SURFACE, + EGL_NO_SURFACE, + EGL_NO_CONTEXT); + } + + return PAL_RESULT_SUCCESS; +} + +void* PAL_CALL palGLGetProcAddress(const char* name) +{ + if (!s_GL.initialized) { + return nullptr; + } + + return s_GL.eglGetProcAddress(name); +} + +PalResult PAL_CALL palSwapBuffers( + PalGLWindow* glWindow, + PalGLContext* context) +{ + if (!s_GL.initialized) { + return PAL_RESULT_GL_NOT_INITIALIZED; + } + + if (!context || !glWindow) { + return PAL_RESULT_NULL_POINTER; + } + + ContextData* data = findContextData(context); + if (!data) { + return PAL_RESULT_INVALID_GL_CONTEXT; + } + + if (!s_GL.eglSwapBuffers(s_GL.display, data->surface)) { + EGLint error = s_GL.eglGetError(); + if (error == EGL_BAD_CONTEXT) { + return PAL_RESULT_INVALID_GL_CONTEXT; + + } else if (error == EGL_BAD_SURFACE) { + // since we always create a window surface + return PAL_RESULT_INVALID_GL_WINDOW; + + } else { + return PAL_RESULT_PLATFORM_FAILURE; + } + } + + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL palSetSwapInterval(Int32 interval) +{ + if (!s_GL.initialized) { + return PAL_RESULT_GL_NOT_INITIALIZED; + } + + if (!s_GL.eglSwapInterval) { + return PAL_RESULT_GL_EXTENSION_NOT_SUPPORTED; + } + + s_GL.eglSwapInterval(s_GL.display, interval); + return PAL_RESULT_SUCCESS; +} \ No newline at end of file diff --git a/src/opengl/pal_opengl_win32.c b/src/opengl/pal_opengl_win32.c index 33a7721..91e9626 100644 --- a/src/opengl/pal_opengl_win32.c +++ b/src/opengl/pal_opengl_win32.c @@ -1,4 +1,26 @@ +/** + +Copyright (C) 2025 Nicholas Agbo + +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. + + */ + // ================================================== // Includes // ================================================== @@ -33,6 +55,7 @@ #define GL_VENDOR 0x1F00 #define GL_RENDERER 0x1F01 #define GL_VERSION 0x1F02 +#define GL_EXTENSIONS 0x1F03 #endif // GL_VENDOR #ifndef WGL_NUMBER_PIXEL_FORMATS_ARB @@ -508,12 +531,11 @@ PalResult PAL_CALL palEnumerateGLFBConfigs( Int32* count, PalGLFBConfig* configs) { - if (!s_Wgl.initialized) { return PAL_RESULT_GL_NOT_INITIALIZED; } - if (!count || !glWindow) { + if (!count) { return PAL_RESULT_NULL_POINTER; } @@ -521,11 +543,6 @@ PalResult PAL_CALL palEnumerateGLFBConfigs( return PAL_RESULT_INSUFFICIENT_BUFFER; } - HDC windowDC = GetDC((HWND)glWindow->window); - if (!windowDC) { - return PAL_RESULT_INVALID_GL_WINDOW; - } - Int32 configCount = 0; Int32 maxConfigCount = 0; Int32 nativeCount = 0; @@ -539,7 +556,7 @@ PalResult PAL_CALL palEnumerateGLFBConfigs( if (s_Wgl.wglGetPixelFormatAttribivARB) { // get framebuffer config with extensions if (!s_Wgl.wglGetPixelFormatAttribivARB( - windowDC, + s_Wgl.hdc, 0, 0, 1, @@ -568,7 +585,7 @@ PalResult PAL_CALL palEnumerateGLFBConfigs( Int32 values[sizeof(attributes) / sizeof(attributes[0])]; for (Int32 i = 1; i <= nativeCount; i++) { if (!s_Wgl.wglGetPixelFormatAttribivARB( - windowDC, + s_Wgl.hdc, i, 0, sizeof(attributes) / sizeof(attributes[0]), @@ -624,12 +641,12 @@ PalResult PAL_CALL palEnumerateGLFBConfigs( } else { // get pixel format with legacy pixel descriptor - nativeCount = s_Gdi.describePixelFormat(windowDC, 1, 0, nullptr); + nativeCount = s_Gdi.describePixelFormat(s_Wgl.hdc, 1, 0, nullptr); for (Int32 i = 1; i <= nativeCount; i++) { PIXELFORMATDESCRIPTOR pfd; if (!s_Gdi.describePixelFormat( - windowDC, + s_Wgl.hdc, i, sizeof(PIXELFORMATDESCRIPTOR), &pfd)) { @@ -674,7 +691,6 @@ PalResult PAL_CALL palEnumerateGLFBConfigs( } } - ReleaseDC((HWND)glWindow->window, windowDC); if (!configs) { *count = configCount; } @@ -804,26 +820,14 @@ PalResult PAL_CALL palCreateGLContext( return PAL_RESULT_INVALID_GL_WINDOW; } - // check if the window's pixel format has already been set - if (s_Gdi.getPixelFormat(hdc) == 0) { - // set the pixel format - Int32 pixelFormat = info->fbConfig->index; - // since we have the pixel format already - // we ask the OS (platform) to fill the pfd struct for us from that - // index - PIXELFORMATDESCRIPTOR pfd; - if (!s_Gdi.describePixelFormat( - hdc, - pixelFormat, - sizeof(PIXELFORMATDESCRIPTOR), - &pfd)) { - return PAL_RESULT_INVALID_GL_FBCONFIG; - } + // check if the provided pixel format is the same as the windows + if (s_Gdi.getPixelFormat(hdc) != info->fbConfig->index) { + return PAL_RESULT_INVALID_GL_FBCONFIG; + } - // we then set the pixel format for the hdc - if (!s_Gdi.setPixelFormat(hdc, pixelFormat, &pfd)) { - return PAL_RESULT_INVALID_GL_FBCONFIG; - } + HGLRC share = nullptr; + if (info->shareContext) { + share = (HGLRC)info->shareContext; } HGLRC context = nullptr; @@ -887,11 +891,9 @@ PalResult PAL_CALL palCreateGLContext( } // release - if (s_Wgl.info.extensions & PAL_GL_EXTENSION_FLUSH_CONTROL) { - if (info->release != PAL_GL_RELEASE_BEHAVIOR_NONE) { - attribs[index++] = WGL_CONTEXT_RELEASE_BEHAVIOR_ARB; - attribs[index++] = WGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB; - } + if (info->release != PAL_GL_RELEASE_BEHAVIOR_NONE) { + attribs[index++] = WGL_CONTEXT_RELEASE_BEHAVIOR_ARB; + attribs[index++] = WGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB; } if (flags) { @@ -900,7 +902,7 @@ PalResult PAL_CALL palCreateGLContext( } attribs[index++] = 0; - context = s_Wgl.wglCreateContextAttribsARB(hdc, nullptr, attribs); + context = s_Wgl.wglCreateContextAttribsARB(hdc, share, attribs); if (!context) { DWORD error = GetLastError(); if (error == ERROR_INVALID_PROFILE_ARB) { @@ -916,14 +918,14 @@ PalResult PAL_CALL palCreateGLContext( if (!context) { return PAL_RESULT_PLATFORM_FAILURE; } - } - // share context - if (info->shareContext) { - if (!s_Wgl.wglShareLists((HGLRC)info->shareContext, context)) { - s_Wgl.wglDeleteContext(context); - ReleaseDC((HWND)info->window->window, hdc); - return PAL_RESULT_PLATFORM_FAILURE; + // share context + if (share) { + if (!s_Wgl.wglShareLists(share, context)) { + s_Wgl.wglDeleteContext(context); + ReleaseDC((HWND)info->window->window, hdc); + return PAL_RESULT_PLATFORM_FAILURE; + } } } @@ -977,6 +979,19 @@ PalResult PAL_CALL palMakeContextCurrent( return PAL_RESULT_SUCCESS; } +void* PAL_CALL palGLGetProcAddress(const char* name) +{ + if (!s_Wgl.initialized) { + return nullptr; + } + + void* proc = s_Wgl.wglGetProcAddress(name); + if (!proc) { + proc = (void*)GetProcAddress(s_Wgl.opengl, name); + } + return proc; +} + PalResult PAL_CALL palSwapBuffers( PalGLWindow* glWindow, PalGLContext* context) @@ -1009,19 +1024,6 @@ PalResult PAL_CALL palSwapBuffers( return PAL_RESULT_SUCCESS; } -void* PAL_CALL palGLGetProcAddress(const char* name) -{ - if (!s_Wgl.initialized) { - return nullptr; - } - - void* proc = s_Wgl.wglGetProcAddress(name); - if (!proc) { - proc = (void*)GetProcAddress(s_Wgl.opengl, name); - } - return proc; -} - PalResult PAL_CALL palSetSwapInterval(Int32 interval) { if (!s_Wgl.initialized) { diff --git a/src/pal_core.c b/src/pal_core.c index 7ea5993..48a3519 100644 --- a/src/pal_core.c +++ b/src/pal_core.c @@ -25,6 +25,15 @@ freely, subject to the following restrictions: // Includes // ================================================== +#ifdef __linux__ +#define _POSIX_C_SOURCE 200112L +#include +#include +#include +#include +#include +#endif // __linux__ + #include "pal/pal_core.h" #ifdef _WIN32 @@ -62,7 +71,12 @@ freely, subject to the following restrictions: #define PAL_VERSION_STRING "1.0.0" #define PAL_LOG_MSG_SIZE 4096 +#ifdef _WIN32 static volatile LONG s_TlsID = 0; +#elif defined(__linux__) +pthread_key_t s_TLSID = 0; +static pthread_once_t s_TLSCreation = PTHREAD_ONCE_INIT; +#endif // _WIN32 typedef struct { char tmp[PAL_LOG_MSG_SIZE]; @@ -75,6 +89,18 @@ typedef struct { // Internal API // ================================================== +static void destroyTlsData(void* data); + +#ifdef __linux__ +void createTLSID() +{ + if (pthread_key_create(&s_TLSID, destroyTlsData) != 0) { + // FIXME: Use a global log buffer with a mutex + return; + } +} +#endif // __linux__ + static inline void* alignedAlloc( Uint64 size, Uint64 alignment) @@ -86,7 +112,7 @@ static inline void* alignedAlloc( return aligned_alloc(alignment, size); #else void* ptr = nullptr; - posix_memalign(&ptr, alignment); + posix_memalign(&ptr, alignment, size); return ptr; #endif // _MSC_VER } @@ -112,6 +138,8 @@ static inline LogTLSData* getLogTlsData() { #ifdef _WIN32 LogTLSData* data = FlsGetValue((DWORD)s_TlsID); +#elif defined(__linux__) + LogTLSData* data = pthread_getspecific(s_TLSID); #endif // _WIN32 if (!data) { @@ -140,6 +168,9 @@ static inline LogTLSData* getLogTlsData() } } FlsSetValue(s_TlsID, data); +#elif defined(__linux__) + pthread_once(&s_TLSCreation, createTLSID); + pthread_setspecific(s_TLSID, data); #endif // _WIN32 } return data; @@ -149,6 +180,8 @@ static inline void updateLogTlsData(LogTLSData* data) { #ifdef _WIN32 FlsSetValue(s_TlsID, data); +#elif defined(__linux__) + pthread_setspecific(s_TLSID, data); #endif // _WIN32 } @@ -181,6 +214,7 @@ static inline void format( static inline void writeToConsole(LogTLSData* data) { +#ifdef _WIN32 HANDLE console = GetStdHandle(STD_ERROR_HANDLE); int len = MultiByteToWideChar(CP_UTF8, 0, data->buffer, -1, nullptr, 0); if (!len) { @@ -193,6 +227,10 @@ static inline void writeToConsole(LogTLSData* data) } else { OutputDebugStringW(data->wideBuffer); } +#elif defined(__linux__) + fprintf(stdout, "%s", data->buffer); + fflush(stdout); +#endif // _WIN32 } // ================================================== @@ -297,6 +335,9 @@ const char* PAL_CALL palFormatResult(PalResult result) case PAL_RESULT_INVALID_GL_CONTEXT: return "Invalid opengl context"; + + case PAL_RESULT_INVALID_FBCONFIG_BACKEND: + return "Invalid FBConfg backend"; } return "Unknown"; } @@ -373,6 +414,10 @@ Uint64 PAL_CALL palGetPerformanceCounter() LARGE_INTEGER counter; QueryPerformanceCounter(&counter); return (Uint64)counter.QuadPart; +#elif defined(__linux__) + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (Uint64)ts.tv_sec * 1000000000LL + (Uint64)ts.tv_nsec; #endif // _WIN32 } @@ -382,5 +427,7 @@ Uint64 PAL_CALL palGetPerformanceFrequency() LARGE_INTEGER frequency; QueryPerformanceFrequency(&frequency); return (Uint64)frequency.QuadPart; +#elif defined(__linux__) + return 1000000000LL; #endif // _WIN32 } \ No newline at end of file diff --git a/src/system/pal_system_linux.c b/src/system/pal_system_linux.c new file mode 100644 index 0000000..6d9e731 --- /dev/null +++ b/src/system/pal_system_linux.c @@ -0,0 +1,247 @@ + +/** + +Copyright (C) 2025 Nicholas Agbo + +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. + + */ + +// ================================================== +// Includes +// ================================================== + +#include "pal/pal_system.h" + +#include +#include +#include +#include +#include +#include +#include + +// ================================================== +// Typedefs, enums and structs +// ================================================== + +// ================================================== +// Internal API +// ================================================== + +static Uint32 parseCache(const char* path) +{ + long cacheSize = 0; + FILE* file = fopen(path, "r"); + if (!file) { + return 0; + } + char cache[16]; + if (fgets(cache, sizeof(cache), file)) { + // get and convert to KB + char unit; + if (sscanf(cache, "%ld%c", &cacheSize, &unit) == 2) { + // check if size is in MB or KB + if (unit == 'M' || unit == 'm') { + cacheSize *= 1024; + } + } + } + fclose(file); + return cacheSize; +} + +// ================================================== +// Public API +// ================================================== + +PalResult PAL_CALL palGetPlatformInfo(PalPlatformInfo* info) +{ + if (!info) { + return PAL_RESULT_NULL_POINTER; + } + + info->type = PAL_PLATFORM_LINUX; + const char* session = getenv("XDG_SESSION_TYPE"); + if (session) { + if (strcmp(session, "wayland") == 0) { + info->apiType = PAL_PLATFORM_API_WAYLAND; + } else { + info->apiType = PAL_PLATFORM_API_X11; + } + } else { + // default + info->apiType = PAL_PLATFORM_API_X11; + } + + FILE* file = fopen("/etc/os-release", "r"); + if (!file) { + return PAL_RESULT_PLATFORM_FAILURE; + } + + char line[256]; + char name[15]; + char version[15]; + // parse version and name from the release file + while (fgets(line, sizeof(line), file)) { + if (strncmp(line, "NAME=", 5) == 0) { + sscanf(line, "NAME=\"%15[^\"]", name); + + } else if (strncmp(line, "VERSION_ID=", 11) == 0) { + sscanf(line, "VERSION_ID=\"%15[^\"]", version); + } + } + + // combine to get name + snprintf(info->name, PAL_PLATFORM_NAME_SIZE, "%s %s", name, version); + sscanf(version, "%d.%d", &info->version.major, &info->version.minor); + fclose(file); + + // get total memory and disk space for the root drive + struct sysinfo sysInfo; + sysinfo(&sysInfo); + info->totalRAM = (Uint32)(sysInfo.totalram / (1024 * 1024)); + + struct statvfs stats; + if (statvfs("/", &stats) != 0) { + return PAL_RESULT_PLATFORM_FAILURE; + } + + Uint64 size = (stats.f_blocks * stats.f_frsize) / (1024 * 1024 * 1024); + info->totalMemory = (Uint32)size; + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL palGetCPUInfo( + const PalAllocator* allocator, + PalCPUInfo* info) +{ + if (!info) { + return PAL_RESULT_NULL_POINTER; + } + + // check invalid allocator + if (allocator && (!allocator->allocate || !allocator->free)) { + return PAL_RESULT_INVALID_ALLOCATOR; + } + + FILE* file = fopen("/proc/cpuinfo", "r"); + if (!file) { + return PAL_RESULT_PLATFORM_FAILURE; + } + + char line[1024]; + // parse cpu information + while (fgets(line, sizeof(line), file)) { + if (strncmp(line, "vendor_id", 9) == 0) { + sscanf(line, "vendor_id : %15[^\n]", info->vendor); + + } else if (strncmp(line, "model name", 10) == 0) { + sscanf(line, "model name : %63[^\n]", info->model); + + } else if (strncmp(line, "cpu cores", 9) == 0) { + sscanf(line, "cpu cores : %d", &info->numCores); + + } else if (strncmp(line, "flags", 5) == 0) { + // extensions + char* flags = strchr(line, ':'); + if (flags) { + flags++; // skip colon + char* token = strtok(flags, " \t\n"); + while (token) { + if (strcmp(token, "sse") == 0) { + info->features |= PAL_CPU_FEATURE_SSE; + + } else if (strcmp(token, "sse2") == 0) { + info->features |= PAL_CPU_FEATURE_SSE2; + + } else if (strcmp(token, "sse3") == 0) { + info->features |= PAL_CPU_FEATURE_SSE3; + + } else if (strcmp(token, "ssse3") == 0) { + info->features |= PAL_CPU_FEATURE_SSSE3; + + } else if (strcmp(token, "sse4_1") == 0) { + info->features |= PAL_CPU_FEATURE_SSE41; + + } else if (strcmp(token, "sse4_2") == 0) { + info->features |= PAL_CPU_FEATURE_SSE42; + + } else if (strcmp(token, "avx") == 0) { + info->features |= PAL_CPU_FEATURE_AVX; + + } else if (strcmp(token, "avx2") == 0) { + info->features |= PAL_CPU_FEATURE_AVX2; + + } else if (strcmp(token, "avx512f") == 0) { + info->features |= PAL_CPU_FEATURE_AVX512F; + + } else if (strcmp(token, "fma") == 0) { + info->features |= PAL_CPU_FEATURE_FMA3; + + } else if (strcmp(token, "bmi1") == 0) { + info->features |= PAL_CPU_FEATURE_BMI1; + + } else if (strcmp(token, "bmi2") == 0) { + info->features |= PAL_CPU_FEATURE_BMI2; + } + + token = strtok(nullptr, " \t\n"); + } + } + break; // all CPUs are the same + } + } + fclose(file); + + Uint32 l1 = parseCache("/sys/devices/system/cpu/cpu0/cache/index0/size"); + Uint32 l2 = parseCache("/sys/devices/system/cpu/cpu0/cache/index2/size"); + Uint32 l3 = parseCache("/sys/devices/system/cpu/cpu0/cache/index3/size"); + + info->numLogicalProcessors = (Uint32)sysconf(_SC_NPROCESSORS_ONLN); + info->cacheL1 = l1; + info->cacheL2 = l2; + info->cacheL3 = l3; + + // get architecture + struct utsname arch; + if (uname(&arch) != 0) { + return PAL_RESULT_PLATFORM_FAILURE; + } + + if (strcmp(arch.machine, "x86_64") == 0) { + info->architecture = PAL_CPU_ARCH_X86_64; + } + + if (strcmp(arch.machine, "i386") == 0) { + info->architecture = PAL_CPU_ARCH_X86; + } + + if (strcmp(arch.machine, "i686") == 0) { + info->architecture = PAL_CPU_ARCH_X86; + } + + if (strcmp(arch.machine, "armv71") == 0) { + info->architecture = PAL_CPU_ARCH_ARM; + } + + if (strcmp(arch.machine, "aarch64") == 0) { + info->architecture = PAL_CPU_ARCH_ARM64; + } + + return PAL_RESULT_SUCCESS; +} \ No newline at end of file diff --git a/src/system/pal_system_win32.c b/src/system/pal_system_win32.c index 0613f3f..58c5a9f 100644 --- a/src/system/pal_system_win32.c +++ b/src/system/pal_system_win32.c @@ -236,6 +236,33 @@ PalResult PAL_CALL palGetCPUInfo( GetSystemInfo(&sysInfo); info->numLogicalProcessors = sysInfo.dwNumberOfProcessors; + // get architecture + switch (sysInfo.wProcessorArchitecture) { + case PROCESSOR_ARCHITECTURE_INTEL: { + info->architecture = PAL_CPU_ARCH_X86; + break; + } + + case PROCESSOR_ARCHITECTURE_AMD64: { + info->architecture = PAL_CPU_ARCH_X86_64; + break; + } + + case PROCESSOR_ARCHITECTURE_ARM: { + info->architecture = PAL_CPU_ARCH_ARM; + break; + } + + case PROCESSOR_ARCHITECTURE_ARM64: { + info->architecture = PAL_CPU_ARCH_ARM64; + break; + } + + default: { + info->architecture = PAL_CPU_ARCH_UNKNOWN; + } + } + // get cpu info DWORD len = 0; GetLogicalProcessorInformationEx(RelationAll, nullptr, &len); @@ -338,19 +365,5 @@ PalResult PAL_CALL palGetCPUInfo( } info->features = features; - - // check compile time architecture -#if defined(_M_X64) || defined(__x86_64__) - info->architecture = PAL_CPU_ARCH_X86_64; -#elif defined(_M_IX86) || defined(__I386__) - info->architecture = PAL_CPU_ARCH_X86; -#elif defined(_M_ARM64) || defined(__aarch64__) - info->architecture = PAL_CPU_ARCH_ARM64; -#elif defined(_M_ARM) || defined(__arm__) - info->architecture = PAL_CPU_ARCH_ARM; -#else - info->architecture = PAL_CPU_ARCH_UNKNOWN; -#endif // check compile time architecture - return PAL_RESULT_SUCCESS; } \ No newline at end of file diff --git a/src/thread/pal_thread_linux.c b/src/thread/pal_thread_linux.c new file mode 100644 index 0000000..b20a669 --- /dev/null +++ b/src/thread/pal_thread_linux.c @@ -0,0 +1,477 @@ + +/** + +Copyright (C) 2025 Nicholas Agbo + +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. + + */ + +// ================================================== +// Includes +// ================================================== + +#define _GNU_SOURCE +#define _POSIX_C_SOURCE 200112L +#include +#include +#include +#include +#include +#include + +#include "pal/pal_thread.h" + +// ================================================== +// Typedefs, enums and structs +// ================================================== + +#define TO_PAL_HANDLE(type, val) ((type*)(UintPtr)(val)) +#define FROM_PAL_HANDLE(type, handle) ((type)(UintPtr)(handle)) + +struct PalMutex { + const PalAllocator* allocator; + pthread_mutex_t handle; +}; + +struct PalCondVar { + const PalAllocator* allocator; + pthread_cond_t handle; +}; + +// ================================================== +// Internal API +// ================================================== + +// ================================================== +// Public API +// ================================================== + +// ================================================== +// Thread +// ================================================== + +PalResult PAL_CALL palCreateThread( + const PalThreadCreateInfo* info, + PalThread** outThread) +{ + if (!info || !outThread) { + return PAL_RESULT_NULL_POINTER; + } + + if (info->allocator) { + if (!info->allocator->allocate && !info->allocator->free) { + return PAL_RESULT_INVALID_ALLOCATOR; + } + } + + pthread_t thread; + if (info->stackSize == 0) { + if (pthread_create(&thread, nullptr, info->entry, info->arg) != 0) { + return PAL_RESULT_PLATFORM_FAILURE; + } + + } else { + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setstacksize(&attr, info->stackSize); + + if (pthread_create(&thread, nullptr, info->entry, info->arg) != 0) { + return PAL_RESULT_PLATFORM_FAILURE; + } + pthread_attr_destroy(&attr); + } + + *outThread = TO_PAL_HANDLE(PalThread, thread); + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL palJoinThread( + PalThread* thread, + void* retval) +{ + if (!thread) { + return PAL_RESULT_NULL_POINTER; + } + + int ret = 0; + pthread_t _thread = FROM_PAL_HANDLE(pthread_t, thread); + if (retval) { + ret = pthread_join(_thread, &retval); + } else { + ret = pthread_join(_thread, nullptr); + } + + if (ret == 0) { + return PAL_RESULT_SUCCESS; + } else { + return PAL_RESULT_PLATFORM_FAILURE; + } +} + +void PAL_CALL palDetachThread(PalThread* thread) +{ + if (thread) { + pthread_detach(FROM_PAL_HANDLE(pthread_t, thread)); + } +} + +void PAL_CALL palSleep(Uint64 milliseconds) +{ + usleep(milliseconds * 1000); +} + +void PAL_CALL palYield() +{ + sched_yield(); +} + +PalThread* PAL_CALL palGetCurrentThread() +{ + return TO_PAL_HANDLE(PalThread, pthread_self()); +} + +PalThreadFeatures PAL_CALL palGetThreadFeatures() +{ + PalThreadFeatures features = 0; + features |= PAL_THREAD_FEATURE_STACK_SIZE; + features |= PAL_THREAD_FEATURE_PRIORITY; + features |= PAL_THREAD_FEATURE_AFFINITY; + features |= PAL_THREAD_FEATURE_NAME; + return features; +} + +PalThreadPriority PAL_CALL palGetThreadPriority(PalThread* thread) +{ + if (!thread) { + return 0; + } + + int policy; + struct sched_param param; + pthread_t _thread = FROM_PAL_HANDLE(pthread_t, thread); + if (pthread_getschedparam(_thread, &policy, ¶m) != 0) { + return 0; + } + + // set priority + if (policy == SCHED_OTHER || policy == SCHED_RR) { + if (param.sched_priority == 10) { + return PAL_THREAD_PRIORITY_LOW; + + } else if (param.sched_priority == 0) { + return PAL_THREAD_PRIORITY_NORMAL; + } + + } else { + return PAL_THREAD_PRIORITY_HIGH; + } +} + +Uint64 PAL_CALL palGetThreadAffinity(PalThread* thread) +{ + if (!thread) { + return 0; + } + + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + pthread_t _thread = FROM_PAL_HANDLE(pthread_t, thread); + if (pthread_getaffinity_np(_thread, sizeof(cpuset), &cpuset) != 0) { + return 0; + } + + Uint64 mask = 0; + for (int i = 0; i < 64; ++i) { + if (CPU_ISSET(i, &cpuset)) { + mask |= (1ULL << 1); + } + } + return mask; +} + +PalResult PAL_CALL palGetThreadName( + PalThread* thread, + Uint64 bufferSize, + Uint64* outSize, + char* outBuffer) +{ + if (!thread) { + return PAL_RESULT_NULL_POINTER; + } + + // see if user provided a buffer and write to it + if (outBuffer && bufferSize > 0) { + pthread_t _thread = FROM_PAL_HANDLE(pthread_t, thread); + if (pthread_getname_np(_thread, outBuffer, bufferSize) != 0) { + return PAL_RESULT_INVALID_THREAD; + } + } + + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL palSetThreadPriority( + PalThread* thread, + PalThreadPriority priority) +{ + if (!thread) { + return PAL_RESULT_NULL_POINTER; + } + + switch (priority) { + case PAL_THREAD_PRIORITY_LOW: { + setpriority(PRIO_PROCESS, 0, 10); + break; + } + + case PAL_THREAD_PRIORITY_NORMAL: { + setpriority(PRIO_PROCESS, 0, 0); + break; + } + + case PAL_THREAD_PRIORITY_HIGH: { + struct sched_param param; + param.sched_priority = 10; + pthread_t _thread = FROM_PAL_HANDLE(pthread_t, thread); + int ret = pthread_setschedparam(_thread, SCHED_FIFO, ¶m); + if (ret == EPERM) { + return PAL_RESULT_ACCESS_DENIED; + } + } + } + + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL palSetThreadAffinity( + PalThread* thread, + Uint64 mask) +{ + if (!thread) { + return PAL_RESULT_NULL_POINTER; + } + + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + for (int i = 0; i < 64; ++i) { + // check for all bits to see which CPUs will be used + if (mask & (1ULL << i)) { + CPU_SET(i, &cpuset); + } + } + + pthread_t _thread = FROM_PAL_HANDLE(pthread_t, thread); + int ret = pthread_setaffinity_np(_thread, sizeof(cpuset), &cpuset); + if (ret == 0) { + return PAL_RESULT_SUCCESS; + } else { + return PAL_RESULT_INVALID_THREAD; + } +} + +PalResult PAL_CALL palSetThreadName( + PalThread* thread, + const char* name) +{ + if (!thread || !name) { + return PAL_RESULT_NULL_POINTER; + } + + pthread_t _thread = FROM_PAL_HANDLE(pthread_t, thread); + int ret = pthread_setname_np(_thread, name); + if (ret == 0) { + return PAL_RESULT_SUCCESS; + } else { + return PAL_RESULT_INVALID_THREAD; + } +} + +// ================================================== +// TLS +// ================================================== + +PalTLSId PAL_CALL palCreateTLS(PaTlsDestructorFn destructor) +{ + pthread_key_t key; + if (pthread_key_create(&key, destructor) != 0) { + return 0; + } + return (PalTLSId)key; +} + +void PAL_CALL palDestroyTLS(PalTLSId id) +{ + pthread_key_delete((pthread_key_t)id); +} + +void* PAL_CALL palGetTLS(PalTLSId id) +{ + return pthread_getspecific((pthread_key_t)id); +} + +void PAL_CALL palSetTLS( + PalTLSId id, + void* data) +{ + pthread_setspecific((pthread_key_t)id, data); +} + +// ================================================== +// Mutex +// ================================================== + +PalResult PAL_CALL palCreateMutex( + const PalAllocator* allocator, + PalMutex** outMutex) +{ + if (!outMutex) { + return PAL_RESULT_NULL_POINTER; + } + + if (allocator) { + if (!allocator->allocate && !allocator->free) { + return PAL_RESULT_INVALID_ALLOCATOR; + } + } + + PalMutex* mutex = palAllocate(allocator, sizeof(PalMutex), 0); + if (!mutex) { + return PAL_RESULT_OUT_OF_MEMORY; + } + + pthread_mutex_init(&mutex->handle, nullptr); + mutex->allocator = allocator; + *outMutex = mutex; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palDestroyMutex(PalMutex* mutex) +{ + if (mutex) { + pthread_mutex_destroy(&mutex->handle); + palFree(mutex->allocator, mutex); + } +} + +void PAL_CALL palLockMutex(PalMutex* mutex) +{ + if (mutex) { + pthread_mutex_lock(&mutex->handle); + } +} + +void PAL_CALL palUnlockMutex(PalMutex* mutex) +{ + if (mutex) { + pthread_mutex_unlock(&mutex->handle); + } +} + +// ================================================== +// Condition Variable +// ================================================== + +PalResult PAL_CALL palCreateCondVar( + const PalAllocator* allocator, + PalCondVar** outCondVar) +{ + if (!outCondVar) { + return PAL_RESULT_NULL_POINTER; + } + + if (allocator) { + if (!allocator->allocate && !allocator->free) { + return PAL_RESULT_INVALID_ALLOCATOR; + } + } + + PalCondVar* condVar = palAllocate(allocator, sizeof(PalCondVar), 0); + if (!condVar) { + return PAL_RESULT_OUT_OF_MEMORY; + } + + pthread_cond_init(&condVar->handle, nullptr); + condVar->allocator = allocator; + *outCondVar = condVar; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palDestroyCondVar(PalCondVar* condVar) +{ + if (condVar) { + pthread_cond_destroy(&condVar->handle); + palFree(condVar->allocator, condVar); + } +} + +PalResult PAL_CALL palWaitCondVar( + PalCondVar* condVar, + PalMutex* mutex) +{ + if (!condVar || !mutex) { + return PAL_RESULT_NULL_POINTER; + } + + int ret = pthread_cond_wait(&condVar->handle, &mutex->handle); + if (ret == 0) { + return PAL_RESULT_SUCCESS; + } else if (ret == ETIMEDOUT) { + return PAL_RESULT_TIMEOUT; + } else { + return PAL_RESULT_PLATFORM_FAILURE; + } +} + +PalResult PAL_CALL palWaitCondVarTimeout( + PalCondVar* condVar, + PalMutex* mutex, + Uint64 milliseconds) +{ + if (!condVar || !mutex) { + return PAL_RESULT_NULL_POINTER; + } + + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += milliseconds / 1000; + ts.tv_nsec += (milliseconds % 1000) * 1000000; + + if (ts.tv_nsec >= 1000000000) { + ts.tv_sec++; + ts.tv_nsec -= 1000000000; + } + + if (pthread_cond_timedwait(&condVar->handle, &mutex->handle, &ts) != 0) { + return PAL_RESULT_TIMEOUT; + } + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palSignalCondVar(PalCondVar* condVar) +{ + if (condVar) { + pthread_cond_signal(&condVar->handle); + } +} + +void PAL_CALL palBroadcastCondVar(PalCondVar* condVar) +{ + if (condVar) { + pthread_cond_broadcast(&condVar->handle); + } +} \ No newline at end of file diff --git a/src/thread/pal_thread_win32.c b/src/thread/pal_thread_win32.c index e9976e4..e55a3c2 100644 --- a/src/thread/pal_thread_win32.c +++ b/src/thread/pal_thread_win32.c @@ -139,7 +139,7 @@ PalResult PAL_CALL palCreateThread( } } - *outThread = thread; + *outThread = (PalThread*)thread; return PAL_RESULT_SUCCESS; } diff --git a/src/video/pal_video_linux.c b/src/video/pal_video_linux.c new file mode 100644 index 0000000..234795d --- /dev/null +++ b/src/video/pal_video_linux.c @@ -0,0 +1,4748 @@ + +/** + +Copyright (C) 2025 Nicholas Agbo + +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. + + */ + +// ================================================== +// Includes +// ================================================== + +#include "pal/pal_video.h" + +#include +#include +#include +#include +#include + +// X11 headers +#include +#include +#include +#include +#include +#include +#include +#include + +// Wayland headers +#if PAL_HAS_WAYLAND +#include +#endif // PAL_HAS_WAYLAND + +// ================================================== +// Typedefs, enums and structs +// ================================================== + +#define TO_PAL_HANDLE(type, val) ((type*)(UintPtr)(val)) +#define FROM_PAL_HANDLE(type, handle) ((type)(UintPtr)(handle)) + +typedef void* EGLConfig; +typedef void* EGLSurface; +typedef void* EGLContext; +typedef void* EGLDisplay; +typedef void* EGLNativeDisplayType; + +/* C++ / C typecast macros for special EGL handle values */ +#if defined(__cplusplus) +#define EGL_CAST(type, value) (static_cast(value)) +#else +#define EGL_CAST(type, value) ((type)(value)) +#endif + +#define EGL_OPENGL_API 0x30A2 +#define EGL_OPENGL_BIT 0x0008 +#define EGL_NO_CONTEXT EGL_CAST(EGLContext, 0) +#define EGL_NO_DISPLAY EGL_CAST(EGLDisplay, 0) +#define EGL_NO_SURFACE EGL_CAST(EGLSurface, 0) +#define EGL_NATIVE_VISUAL_ID 0x302E + +typedef int32_t EGLint; +typedef unsigned int EGLBoolean; +typedef unsigned int EGLenum; + +typedef void* (*eglGetProcAddressFn)(const char*); + +typedef EGLBoolean (*eglInitializeFn)( + EGLDisplay, + EGLint*, + EGLint*); + +typedef EGLBoolean (*eglTerminateFn)(EGLDisplay); + +typedef EGLDisplay (*eglGetDisplayFn)(void*); + +typedef EGLBoolean (*eglChooseConfigFn)( + EGLDisplay, + const EGLint*, + EGLConfig*, + EGLint, + EGLint*); + +typedef EGLBoolean (*eglGetConfigAttribFn)( + EGLDisplay, + EGLConfig, + EGLint, + EGLint*); + +typedef EGLint (*eglGetErrorFn)(void); + +typedef EGLBoolean (*eglBindAPIFn)(EGLenum); + +typedef EGLBoolean (*eglGetConfigsFn)( + EGLDisplay, + EGLConfig*, + EGLint, + EGLint*); + +typedef struct { + bool skipConfigure; + bool skipState; + bool used; + int x; + int y; + Uint32 w; + int dpi; + Uint32 h; + PalWindowState state; + PalCursor* cursor; + PalWindow* window; +} WindowData; + +typedef struct { + bool used; + int dpi; + int x; + int y; + Uint32 w; + Uint32 h; + PalMonitor* monitor; +} MonitorData; + +typedef struct { + Int32 lastX; + Int32 lastY; + Int32 dx; + Int32 dy; + Int32 WheelX; + Int32 WheelY; + bool state[PAL_MOUSE_BUTTON_MAX]; +} Mouse; + +typedef struct { + bool scancodeState[PAL_SCANCODE_MAX]; + bool keycodeState[PAL_KEYCODE_MAX]; + int scancodes[512]; + int keycodes[256]; +} Keyboard; + +typedef struct { + void* handle; + eglInitializeFn eglInitialize; + eglTerminateFn eglTerminate; + eglGetDisplayFn eglGetDisplay; + eglChooseConfigFn eglChooseConfig; + eglGetConfigAttribFn eglGetConfigAttrib; + eglGetErrorFn eglGetError; + eglBindAPIFn eglBindAPI; + eglGetConfigsFn eglGetConfigs; +} EGL; + +// ================================================== +// X11 Typedefs, enums and structs +// ================================================== + +#pragma region X11 Typedefs +#define X_INTERN(x) s_X11Atoms.x = s_X11.internAtom(s_X11.display, #x, False) + +#define RANDR_SCREEN_CHANGE_EVENT 1040 +#define RANDR_NOTIFY_EVENT 1041 + +// optionally, needed to create visual from FBConfig +#define GLX_FBCONFIG_ID 0x8012 +typedef struct __GLXFBConfigRec* GLXFBConfig; + +typedef GLXFBConfig* (*GLXGetFBConfigsFn)( + Display*, + int, + int*); + +typedef int (*GLXGetFBConfigAttribFn)( + Display*, + GLXFBConfig, + int, + int*); + +typedef XVisualInfo* (*GLXGetVisualFromFBConfigFn)( + Display*, + GLXFBConfig); + +typedef XVisualInfo* (*GLXGetProcAddressFn)(const unsigned char*); + +typedef Display* (*XOpenDisplayFn)(const char*); +typedef int (*XCloseDisplayFn)(Display*); + +typedef int (*XGetWindowAttributesFn)( + Display*, + Window, + XWindowAttributes*); + +typedef int (*XGetWindowPropertyFn)( + Display*, + Window, + Atom, + long, + long, + Bool, + Atom, + Atom*, + int*, + unsigned long*, + unsigned long*, + unsigned char**); + +typedef Atom (*XInternAtomFn)( + Display*, + _Xconst char*, + Bool); + +typedef Window (*XGetSelectionOwnerFn)( + Display*, + Atom); + +typedef Window (*XCreateWindowFn)( + Display*, + Window, + int, + int, + unsigned int, + unsigned int, + unsigned int, + int, + unsigned int, + Visual*, + unsigned long, + XSetWindowAttributes*); + +typedef int (*XChangePropertyFn)( + Display*, + Window, + Atom, + Atom, + int, + int, + _Xconst unsigned char*, + int); + +typedef int (*XFlushFn)(Display*); + +typedef Colormap (*XCreateColormapFn)( + Display*, + Window, + Visual*, + int); + +typedef int (*XFreeColormapFn)( + Display*, + Colormap); + +typedef int (*XDestroyWindowFn)( + Display*, + Window); + +typedef int (*XStoreNameFn)( + Display*, + Window, + _Xconst char*); + +typedef int (*XMapWindowFn)( + Display*, + Window); + +typedef int (*XUnmapWindowFn)( + Display*, + Window); + +typedef int (*XMatchVisualInfoFn)( + Display*, + int, + int, + int, + XVisualInfo*); + +typedef int (*XPendingFn)(Display*); + +typedef int (*XSetWMProtocolsFn)( + Display*, + Window, + Atom*, + int); + +typedef int (*XNextEventFn)( + Display*, + XEvent*); + +typedef int (*XSetWMNormalHintsFn)( + Display*, + Window, + XSizeHints*); + +typedef int (*XGetWMNormalHintsFn)( + Display*, + Window, + XSizeHints*, + long*); + +typedef int (*XSendEventFn)( + Display*, + Window, + Bool, + long, + XEvent*); + +typedef int (*XMoveWindowFn)( + Display*, + Window, + int, + int); + +typedef int (*XResizeWindowFn)( + Display*, + Window, + unsigned int, + unsigned int); + +typedef int (*XIconifyWindowFn)( + Display*, + Window, + int); + +typedef XErrorHandler (*XSetErrorHandlerFn)(XErrorHandler); + +typedef int (*XSyncFn)( + Display*, + Bool); + +typedef int (*XSaveContextFn)( + Display*, + XID, + XContext, + _Xconst char*); + +typedef int (*XFindContextFn)( + Display*, + XID, + XContext, + XPointer*); + +typedef XrmQuark (*XrmUniqueQuarkFn)(void); + +typedef int (*XRRSetCrtcConfigFn)( + Display*, + XRRScreenResources*, + RRCrtc, + Time, + int, + int, + RRMode, + Rotation, + RROutput*, + int); + +typedef XRRScreenResources* (*XRRGetScreenResourcesFn)( + Display*, + Window); + +typedef RROutput (*XRRGetOutputPrimaryFn)( + Display*, + Window); + +typedef XRROutputInfo* (*XRRGetOutputInfoFn)( + Display*, + XRRScreenResources*, + RROutput); + +typedef XRRCrtcInfo* (*XRRGetCrtcInfoFn)( + Display*, + XRRScreenResources*, + RRCrtc); + +typedef void (*XRRFreeScreenResourcesFn)(XRRScreenResources*); + +typedef void (*XRRFreeOutputInfoFn)(XRROutputInfo*); + +typedef void (*XRRFreeCrtcInfoFn)(XRRCrtcInfo*); + +typedef void (*XRRSelectInputFn)( + Display*, + Window, + int); + +typedef int (*XRRQueryExtensionFn)( + Display*, + int*, + int*); + +typedef XClassHint* (*XAllocClassHintFn)(void); + +typedef int (*XSetClassHintFn)( + Display*, + Window, + XClassHint*); + +typedef int (*XFreeFn)(void*); + +typedef Cursor (*XCreateFontCursorFn)( + Display*, + unsigned int); + +typedef int (*XFreePixmapFn)( + Display*, + Pixmap); + +typedef int (*XSetWMHintsFn)( + Display*, + Window, + XWMHints*); + +typedef int (*XGrabPointerFn)( + Display*, + Window, + Bool, + unsigned int, + int, + int, + Window, + Cursor, + Time); + +typedef Cursor (*XCreatePixmapCursorFn)( + Display*, + Pixmap, + Pixmap, + XColor*, + XColor*, + unsigned int, + unsigned int); + +typedef int (*XWarpPointerFn)( + Display*, + Window, + Window, + int, + int, + unsigned int, + unsigned int, + int, + int); + +typedef Status (*XGetWMNameFn)( + Display*, + Window, + XTextProperty*); + +typedef Bool (*XQueryPointerFn)( + Display*, + Window, + Window*, + Window*, + int*, + int*, + int*, + int*, + unsigned int*); + +typedef int (*XUngrabPointerFn)( + Display*, + Time); + +typedef XWMHints* (*XAllocWMHintsFn)(void); + +typedef int (*XMapRaisedFn)( + Display*, + Window); + +typedef int (*XUndefineCursorFn)( + Display*, + Window); + +typedef int (*XDefineCursorFn)( + Display*, + Window, + Cursor); + +typedef int (*XFreeCursorFn)( + Display*, + Cursor); + +typedef XWMHints* (*XGetWMHintsFn)( + Display*, + Window); + +typedef Cursor (*XCreatePixmapCursorFn)( + Display*, + Pixmap, + Pixmap, + XColor*, + XColor*, + unsigned int, + unsigned int); + +typedef int (*XSetInputFocusFn)( + Display*, + Window, + int, + Time); + +typedef int (*XGetInputFocusFn)( + Display*, + Window*, + int*); + +typedef Pixmap (*XCreatePixmapFn)( + Display*, + Drawable, + unsigned int, + unsigned int, + unsigned int); + +typedef XVisualInfo* (*XGetVisualInfoFn)( + Display*, + long, + XVisualInfo*, + int*); + +typedef Cursor (*XcursorImageLoadCursorFn)( + Display*, + const XcursorImage*); + +typedef XcursorImage* (*XcursorImageCreateFn)( + int, + int); + +typedef void (*XcursorImageDestroyFn)(XcursorImage*); + +typedef KeySym (*XLookupKeysymFn)( + XKeyEvent*, + int); + +typedef int (*XkbSetDetectableAutoRepeatFn)( + Display*, + int, + int*); + +typedef struct { + bool unicodeTitle; + + Atom WM_DELETE_WINDOW; + Atom _NET_SUPPORTED; + Atom _NET_WM_STATE; + Atom _NET_WM_STATE_ABOVE; + Atom _NET_WM_STATE_HIDDEN; + Atom _NET_WM_STATE_MAXIMIZED_VERT; + Atom _NET_WM_STATE_MAXIMIZED_HORZ; + Atom _NET_WM_WINDOW_TYPE_UTILITY; + Atom _NET_WM_DESKTOP; + Atom _NET_WM_STATE_DEMANDS_ATTENTIONS; + Atom _NET_WM_WINDOW_OPACITY; + + Atom _NET_WM_NAME; + Atom UTF8_STRING; + Atom _NET_WM_WINDOW_TYPE; + Atom _NET_WM_WINDOW_TYPE_SPLASH; + Atom _NET_WM_PID; + Atom _WM_CLASS; + Atom _NET_ACTIVE_WINDOW; + Atom _NET_WM_ICON; +} X11Atoms; + +typedef struct { + bool error; + bool skipScreenEvent; + bool skipNotifyEvent; + int bpp; + int screen; + int depth; + int rrEventBase; + void* handle; + void* xrandr; + void* glxHandle; + void* libCursor; + Display* display; + Window root; + XContext dataID; + Cursor hiddenCursor; + const char* className; + + Visual* visual; + Colormap colormap; + + XOpenDisplayFn openDisplay; + XCloseDisplayFn closeDisplay; + XGetWindowAttributesFn getWindowAttributes; + XGetWindowPropertyFn getWindowProperty; + XInternAtomFn internAtom; + XGetSelectionOwnerFn getSelectionOwner; + XFreeColormapFn freeColormap; + XStoreNameFn storeName; + XChangePropertyFn changeProperty; + XFlushFn flush; + XCreateColormapFn createColormap; + XMapWindowFn mapWindow; + XUnmapWindowFn unmapWindow; + + XCreateWindowFn createWindow; + XDestroyWindowFn destroyWindow; + XMatchVisualInfoFn matchVisualInfo; + XPendingFn pending; + XSetWMProtocolsFn setWMProtocols; + XNextEventFn nextEvent; + XSetWMNormalHintsFn setWMNormalHints; + XGetWMNormalHintsFn getWMNormalHints; + XSendEventFn sendEvent; + XMoveWindowFn moveWindow; + XResizeWindowFn resizeWindow; + XIconifyWindowFn iconifyWindow; + + XSetErrorHandlerFn setErrorHandler; + XSyncFn sync; + XSaveContextFn saveContext; + XFindContextFn findContext; + XrmUniqueQuarkFn uniqueContext; + + XRRSetCrtcConfigFn setCrtcConfig; + XRRGetScreenResourcesFn getScreenResources; + XRRGetOutputPrimaryFn getOutputPrimary; + XRRGetOutputInfoFn getOutputInfo; + XRRGetCrtcInfoFn getCrtcInfo; + XRRFreeScreenResourcesFn freeScreenResources; + XRRFreeOutputInfoFn freeOutputInfo; + XRRFreeCrtcInfoFn freeCrtcInfo; + XRRSelectInputFn selectRRInput; + XRRQueryExtensionFn queryRRExtension; + + XAllocClassHintFn allocClassHint; + XSetClassHintFn setClassHint; + XFreeFn free; + XGetVisualInfoFn getVisualInfo; + + // opengl + GLXGetFBConfigsFn glxGetFBConfigs; + GLXGetFBConfigAttribFn glxGetFBConfigAttrib; + GLXGetVisualFromFBConfigFn glxGetVisualFromFBConfig; + + XCreateFontCursorFn createFontCursor; + XFreePixmapFn freePixmap; + XSetWMHintsFn setWMHints; + XGrabPointerFn grabPointer; + XCreatePixmapCursorFn createPixmapCursor; + XWarpPointerFn warpPointer; + XGetWMNameFn getWMName; + XQueryPointerFn queryPointer; + XUngrabPointerFn ungrabPointer; + XAllocWMHintsFn allocWMHints; + XMapRaisedFn mapRaised; + XUndefineCursorFn undefineCursor; + XDefineCursorFn defineCursor; + XFreeCursorFn freeCursor; + XGetWMHintsFn getWMHints; + XCreatePixmapFn createPixmap; + XSetInputFocusFn setInputFocus; + XGetInputFocusFn getInputFocus; + XcursorImageLoadCursorFn cursorImageLoadCursor; + XcursorImageCreateFn cursorImageCreate; + XcursorImageDestroyFn cursorImageDestroy; + XLookupKeysymFn lookupKeysym; + + XkbSetDetectableAutoRepeatFn setDetectableAutoRepeat; +} X11; + +static X11 s_X11 = {0}; +static X11Atoms s_X11Atoms = {0}; + +#pragma endregion + +typedef struct { + // clang-format off + void (*shutdownVideo)(); + void (*updateVideo)(); + PalResult (*enumerateMonitors)(Int32*, PalMonitor**); + PalResult (*getPrimaryMonitor)(PalMonitor**); + PalResult (*getMonitorInfo)(PalMonitor*, PalMonitorInfo*); + PalResult (*enumerateMonitorModes)(PalMonitor*, Int32*, PalMonitorMode*); + PalResult (*getCurrentMonitorMode)(PalMonitor*, PalMonitorMode*); + PalResult (*setMonitorMode)(PalMonitor*, PalMonitorMode*); + PalResult (*validateMonitorMode)(PalMonitor*, PalMonitorMode*); + PalResult (*setMonitorOrientation)(PalMonitor*, PalOrientation); + + PalResult (*createWindow)(const PalWindowCreateInfo*, PalWindow**); + void (*destroyWindow)(PalWindow*); + PalResult (*maximizeWindow)(PalWindow*); + PalResult (*minimizeWindow)(PalWindow*); + PalResult (*restoreWindow)(PalWindow*); + PalResult (*showWindow)(PalWindow*); + PalResult (*hideWindow)(PalWindow*); + PalResult (*xFlashWindow)(PalWindow*, const PalFlashInfo*); + PalResult (*getWindowStyle)(PalWindow*, PalWindowStyle*); + PalResult (*getWindowMonitor)(PalWindow*, PalMonitor**); + PalResult (*getWindowTitle)(PalWindow*, Uint64, Uint64*, char*); + PalResult (*getWindowPos)(PalWindow*, Int32*, Int32*); + PalResult (*getWindowSize)(PalWindow*, Uint32*, Uint32*); + PalResult (*getWindowState)(PalWindow*, PalWindowState*); + bool (*isWindowVisible)(PalWindow*); + PalWindow* (*getFocusWindow)(); + PalWindowHandleInfo (*getWindowHandleInfo)(PalWindow*); + PalResult (*setWindowOpacity)(PalWindow*, float); + PalResult (*setWindowStyle)(PalWindow*, PalWindowStyle); + PalResult (*setWindowTitle)(PalWindow*, const char*); + PalResult (*setWindowPos)(PalWindow*, Int32, Int32); + PalResult (*setWindowSize)(PalWindow*, Uint32, Uint32); + PalResult (*setFocusWindow)(PalWindow*); + + PalResult (*createIcon)(const PalIconCreateInfo*, PalIcon**); + void (*destroyIcon)(PalIcon*); + PalResult (*setWindowIcon)(PalWindow*, PalIcon*); + + PalResult (*createCursor)(const PalCursorCreateInfo*, PalCursor**); + PalResult (*createCursorFrom)(PalCursorType, PalCursor**); + void (*destroyCursor)(PalCursor*); + void (*showCursor)(bool); + PalResult (*clipCursor)(PalWindow*, bool); + PalResult (*getCursorPos)(PalWindow*, Int32*, Int32*); + PalResult (*setCursorPos)(PalWindow*, Int32, Int32); + PalResult (*setWindowCursor)(PalWindow*, PalCursor*); + // clang-format off +} Backend; + +typedef struct { + bool initialized; + Int32 maxWindowData; + Int32 maxMonitorData; + Int32 pixelFormat; + PalVideoFeatures features; + const PalAllocator* allocator; + PalEventDriver* eventDriver; + const Backend* backend; + WindowData* windowData; + MonitorData* monitorData; +} VideoLinux; + +static VideoLinux s_Video = {0}; +static Mouse s_Mouse = {0}; +static Keyboard s_Keyboard = {0}; +static EGL s_Egl; + +// ================================================== +// Internal API +// ================================================== + +static int compareModes( + const void* a, + const void* b) +{ + const PalMonitorMode* mode1 = (const PalMonitorMode*)a; + const PalMonitorMode* mode2 = (const PalMonitorMode*)b; + + // compare fields + if (mode1->width != mode2->width) { + return mode1->width - mode2->width; + } + + if (mode1->height != mode2->height) { + return mode1->height - mode2->height; + } + + if (mode1->refreshRate != mode2->refreshRate) { + return mode1->refreshRate - mode2->refreshRate; + } + + if (mode1->bpp != mode2->bpp) { + return mode1->bpp - mode2->bpp; + } +} + +static WindowData* getFreeWindowData() +{ + for (int i = 0; i < s_Video.maxWindowData; ++i) { + if (!s_Video.windowData[i].used) { + s_Video.windowData[i].used = true; + return &s_Video.windowData[i]; + } + } + + // resize the data array + // It is rare for a user to create and manage + // 32 windows at the same time + WindowData* data = nullptr; + int count = s_Video.maxWindowData * 2; // double the size + int freeIndex = s_Video.maxWindowData + 1; + data = palAllocate(s_Video.allocator, sizeof(WindowData) * count, 0); + if (data) { + memcpy( + data, + s_Video.windowData, + s_Video.maxWindowData * sizeof(WindowData)); + + palFree(s_Video.allocator, s_Video.windowData); + s_Video.windowData = data; + s_Video.maxWindowData = count; + + s_Video.windowData[freeIndex].used = true; + return &s_Video.windowData[freeIndex]; + } + return nullptr; +} + +static void resetMonitorData() +{ + memset( + s_Video.monitorData, + 0, + s_Video.maxMonitorData * sizeof(MonitorData)); +} + +static MonitorData* getFreeMonitorData() +{ + for (int i = 0; i < s_Video.maxMonitorData; ++i) { + if (!s_Video.monitorData[i].used) { + s_Video.monitorData[i].used = true; + return &s_Video.monitorData[i]; + } + } + + // resize the data array + // this will almost not reach here since most setups are 1-4 monitors + MonitorData* data = nullptr; + int count = s_Video.maxMonitorData * 2; // double the size + int freeIndex = s_Video.maxMonitorData + 1; + data = palAllocate(s_Video.allocator, sizeof(MonitorData) * count, 0); + if (data) { + memcpy( + data, + s_Video.monitorData, + s_Video.maxMonitorData * sizeof(MonitorData)); + + palFree(s_Video.allocator, s_Video.monitorData); + s_Video.monitorData = data; + s_Video.maxWindowData = count; + + s_Video.monitorData[freeIndex].used = true; + return &s_Video.monitorData[freeIndex]; + } + return nullptr; +} + +static MonitorData* findMonitorData(PalMonitor* monitor) +{ + for (int i = 0; i < s_Video.maxMonitorData; ++i) { + if (s_Video.monitorData[i].used && + s_Video.monitorData[i].monitor == monitor) { + return &s_Video.monitorData[i]; + } + } +} + +static void freeMonitorData(PalMonitor* monitor) +{ + for (int i = 0; i < s_Video.maxMonitorData; ++i) { + if (s_Video.monitorData[i].used && + s_Video.monitorData[i].monitor == monitor) { + s_Video.monitorData[i].used = false; + } + } +} + +static PalResult glxBackend() +{ + // user choose GLX FBConfig backend + if (!s_X11.glxHandle) { + // Rare + return PAL_RESULT_PLATFORM_FAILURE; + } + + // clang-format off + + int count = 0; + GLXFBConfig* configs = s_X11.glxGetFBConfigs( + s_X11.display, + s_X11.screen, + &count); + + GLXFBConfig fbConfig = configs[s_Video.pixelFormat]; + if (!fbConfig) { + return PAL_RESULT_INVALID_GL_FBCONFIG; + } + + // get a matching visual + XVisualInfo* visualInfo = s_X11.glxGetVisualFromFBConfig( + s_X11.display, + fbConfig); + + if (!visualInfo) { + return PAL_RESULT_INVALID_GL_FBCONFIG; + } + + // clang-format on + + s_X11.visual = visualInfo->visual; + s_X11.depth = visualInfo->depth; + s_X11.colormap = s_X11.createColormap( + s_X11.display, + s_X11.root, + visualInfo->visual, + AllocNone); + + if (!s_X11.colormap) { + return PAL_RESULT_INVALID_GL_FBCONFIG; + } + + return PAL_RESULT_SUCCESS; +} + +static PalResult eglXBackend(int index) +{ + // user choose EGL FBConfig backend + if (!s_Egl.handle) { + return PAL_RESULT_PLATFORM_FAILURE; + } + + if (!s_Egl.eglBindAPI(EGL_OPENGL_API)) { + return PAL_RESULT_PLATFORM_FAILURE; + } + + EGLDisplay display = EGL_NO_DISPLAY; + display = s_Egl.eglGetDisplay((EGLNativeDisplayType)s_X11.display); + + if (display == EGL_NO_DISPLAY) { + return PAL_RESULT_PLATFORM_FAILURE; + } + + if (!s_Egl.eglInitialize(display, nullptr, nullptr)) { + return PAL_RESULT_PLATFORM_FAILURE; + } + + EGLint numConfigs = 0; + if (!s_Egl.eglGetConfigs(display, nullptr, 0, &numConfigs)) { + return PAL_RESULT_PLATFORM_FAILURE; + } + + EGLint configSize = sizeof(EGLConfig) * numConfigs; + EGLConfig* eglConfigs = palAllocate(s_Video.allocator, configSize, 0); + if (!eglConfigs) { + return PAL_RESULT_OUT_OF_MEMORY; + } + + s_Egl.eglGetConfigs(display, eglConfigs, numConfigs, &numConfigs); + EGLConfig config = eglConfigs[index]; + + // we create a visual from the config + EGLint visualID; + s_Egl.eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &visualID); + if (visualID == 0) { + return PAL_RESULT_INVALID_GL_FBCONFIG; + } + + int numVisuals = 0; + XVisualInfo tmp; + tmp.visualid = visualID; + + // clang-format off + // get a matching visual info + XVisualInfo* visualInfo = s_X11.getVisualInfo( + s_X11.display, + VisualIDMask, + &tmp, + &numVisuals); + // clang-format on + + if (!visualInfo) { + return PAL_RESULT_INVALID_GL_FBCONFIG; + } + + s_X11.visual = visualInfo->visual; + s_X11.depth = visualInfo->depth; + s_X11.colormap = s_X11.createColormap( + s_X11.display, + s_X11.root, + visualInfo->visual, + AllocNone); + + if (!s_X11.colormap) { + return PAL_RESULT_INVALID_GL_FBCONFIG; + } + + s_Egl.eglTerminate(display); + palFree(s_Video.allocator, eglConfigs); + return PAL_RESULT_SUCCESS; +} + +// ================================================== +// X11 API +// ================================================== + +#pragma region X11 API + +static RRMode xFindMode( + XRRScreenResources* resources, + const PalMonitorMode* mode) +{ + for (int i = 0; i < resources->nmode; ++i) { + XRRModeInfo* info = &resources->modes[i]; + + double tmp = (double)info->hTotal * (double)info->vTotal; + double rate = (double)info->dotClock / tmp; + + // compare with width, height and refresh rate + if (info->width == mode->width && info->height == mode->height && + (Uint32)(rate + 0.5) == mode->refreshRate) { + return info->id; + } + } + return None; +} + +static void xCheckFeatures() +{ + // cache this atoms + X_INTERN(WM_DELETE_WINDOW); + X_INTERN(_NET_SUPPORTED); + X_INTERN(_NET_WM_STATE); + X_INTERN(_NET_WM_STATE_ABOVE); + X_INTERN(_NET_WM_STATE_MAXIMIZED_VERT); + X_INTERN(_NET_WM_STATE_MAXIMIZED_HORZ); + X_INTERN(_NET_WM_NAME); + X_INTERN(UTF8_STRING); + X_INTERN(_NET_WM_WINDOW_TYPE_UTILITY); + X_INTERN(_NET_WM_DESKTOP); + X_INTERN(_NET_WM_STATE_DEMANDS_ATTENTIONS); + X_INTERN(_NET_WM_WINDOW_OPACITY); + X_INTERN(_NET_WM_WINDOW_TYPE); + X_INTERN(_NET_WM_WINDOW_TYPE_SPLASH); + X_INTERN(_NET_WM_PID); + X_INTERN(_WM_CLASS); + X_INTERN(_NET_ACTIVE_WINDOW); + X_INTERN(_NET_WM_ICON); + + // check for support from the window manager + Atom type; + int format; + unsigned long count, bytesAfters; + Atom* supportedAtoms = nullptr; + s_X11.getWindowProperty( + s_X11.display, + s_X11.root, + s_X11Atoms._NET_SUPPORTED, + 0, + (~0L), + False, + XA_ATOM, + &type, + &format, + &count, + &bytesAfters, + (unsigned char**)&supportedAtoms); + + PalVideoFeatures features = 0; + for (unsigned long i = 0; i < count; ++i) { + if (supportedAtoms[i] == s_X11Atoms._NET_WM_STATE_MAXIMIZED_VERT) { + features |= PAL_VIDEO_FEATURE_WINDOW_SET_STATE; + features |= PAL_VIDEO_FEATURE_WINDOW_GET_STATE; + } + + if (supportedAtoms[i] == s_X11Atoms._NET_WM_STATE_MAXIMIZED_HORZ) { + features |= PAL_VIDEO_FEATURE_WINDOW_SET_STATE; + features |= PAL_VIDEO_FEATURE_WINDOW_GET_STATE; + } + + if (supportedAtoms[i] == s_X11Atoms._NET_WM_STATE_HIDDEN) { + features |= PAL_VIDEO_FEATURE_WINDOW_SET_STATE; + features |= PAL_VIDEO_FEATURE_WINDOW_GET_STATE; + } + + if (supportedAtoms[i] == s_X11Atoms._NET_WM_WINDOW_TYPE_SPLASH) { + features |= PAL_VIDEO_FEATURE_BORDERLESS_WINDOW; + } + + if (supportedAtoms[i] == s_X11Atoms._NET_WM_NAME) { + s_X11Atoms.unicodeTitle = true; + } + + if (supportedAtoms[i] == s_X11Atoms._NET_WM_WINDOW_TYPE_UTILITY) { + features |= PAL_VIDEO_FEATURE_TOOL_WINDOW; + } + } + + // check for transparent windows + Atom compositor = s_X11.internAtom(s_X11.display, "_NET_WM_CM_S0", True); + if (compositor != None) { + Window owner = s_X11.getSelectionOwner(s_X11.display, compositor); + if (owner != None) { + features |= PAL_VIDEO_FEATURE_TRANSPARENT_WINDOW; + } + } + + // general features + features |= PAL_VIDEO_FEATURE_MULTI_MONITORS; + features |= PAL_VIDEO_FEATURE_MONITOR_GET_ORIENTATION; + features |= PAL_VIDEO_FEATURE_MONITOR_SET_MODE; + features |= PAL_VIDEO_FEATURE_MONITOR_GET_MODE; + features |= PAL_VIDEO_FEATURE_WINDOW_SET_SIZE; + features |= PAL_VIDEO_FEATURE_WINDOW_GET_SIZE; + features |= PAL_VIDEO_FEATURE_WINDOW_SET_VISIBILITY; + features |= PAL_VIDEO_FEATURE_WINDOW_GET_VISIBILITY; + + features |= PAL_VIDEO_FEATURE_CLIP_CURSOR; + features |= PAL_VIDEO_FEATURE_WINDOW_SET_INPUT_FOCUS; + features |= PAL_VIDEO_FEATURE_WINDOW_GET_INPUT_FOCUS; + features |= PAL_VIDEO_FEATURE_CURSOR_SET_POS; + features |= PAL_VIDEO_FEATURE_CURSOR_GET_POS; + features |= PAL_VIDEO_FEATURE_WINDOW_SET_TITLE; + features |= PAL_VIDEO_FEATURE_WINDOW_GET_TITLE; + features |= PAL_VIDEO_FEATURE_WINDOW_FLASH_TRAY; + + s_Video.features = features; + s_X11.free(supportedAtoms); +} + +static int xErrorHandler( + Display*, + XErrorEvent* e) +{ + // this is use for simple success and failure + s_X11.error = true; + return 0; +} + +static PalWindowState xQueryWindowState(Window xWin) +{ + Atom type; + int format; + unsigned long count, bytesAfter; + Atom* atoms = nullptr; + PalWindowState state = PAL_WINDOW_STATE_RESTORED; + + s_X11.getWindowProperty( + s_X11.display, + xWin, + s_X11Atoms._NET_WM_STATE, + 0, + 1024, + False, + XA_ATOM, + &type, + &format, + &count, + &bytesAfter, + (unsigned char**)&atoms); + + for (unsigned int i = 0; i < count; i++) { + if (atoms[i] == s_X11Atoms._NET_WM_STATE_MAXIMIZED_HORZ) { + state = PAL_WINDOW_STATE_MAXIMIZED; + } + + if (atoms[i] == s_X11Atoms._NET_WM_STATE_MAXIMIZED_VERT) { + state = PAL_WINDOW_STATE_MAXIMIZED; + } + + if (atoms[i] == s_X11Atoms._NET_WM_STATE_HIDDEN) { + state = PAL_WINDOW_STATE_MINIMIZED; + } + } + + s_X11.free(atoms); + return state; +} + +static void xCacheMonitors(bool enumerate) +{ + XRRScreenResources* resources = nullptr; + resources = s_X11.getScreenResources(s_X11.display, s_X11.root); + + for (int i = 0; i < resources->noutput; ++i) { + RROutput output = resources->outputs[i]; + // clang-format off + XRROutputInfo* info = s_X11.getOutputInfo(s_X11.display, resources, output); + // clang-format on + + if (info->connection == RR_Connected && info->crtc != None) { + // get monitor data and update info + PalMonitor* monitor = TO_PAL_HANDLE(PalMonitor, output); + MonitorData* data = nullptr; + + if (enumerate) { + data = getFreeMonitorData(); + if (!data) { + return; + } + + data->monitor = monitor; + + } else { + data = findMonitorData(monitor); + } + + // clang-format off + XRRCrtcInfo* crtc = s_X11.getCrtcInfo(s_X11.display, resources, info->crtc); + // clang-format on + + double dpi = (double)(crtc->width * 25.4) / (double)info->mm_width; + data->dpi = (int)dpi; + data->w = crtc->width; + data->h = crtc->height; + data->x = crtc->x; + data->y = crtc->y; + + s_X11.freeCrtcInfo(crtc); + } + + s_X11.freeOutputInfo(info); + } + + s_X11.freeScreenResources(resources); +} + +static int xGetWindowMonitorDPI( + WindowData* data, + bool enumerate) +{ + int winX = data->x + data->w / 2; + int winY = data->y + data->w / 2; + // get the DPI from our cached monitor + for (int i = 0; i < s_Video.maxMonitorData; i++) { + if (!s_Video.monitorData->used) { + continue; + } + + // we found a monitor, check the monitor bounds with the window + MonitorData* info = &s_Video.monitorData[i]; + if (winX >= info->x && winX < info->x + info->w && winY >= info->y && + winY < info->y + info->h) { + // found monitor + return info->dpi; + } + } +} + +static void xSendWMEvent( + Window window, + Atom type, + long a, + long b, + long c, + long d, + bool add) +{ + XEvent e = {0}; + e.xclient.type = ClientMessage; + e.xclient.send_event = True; + e.xclient.window = window; + e.xclient.message_type = type; + e.xclient.format = 32; + if (add) { + e.xclient.data.l[0] = 1; // _NET_WM_STATE_ADD + } else { + e.xclient.data.l[0] = 0; // _NET_WM_STATE_REMOVE + } + + e.xclient.data.l[1] = a; + e.xclient.data.l[2] = b; + e.xclient.data.l[3] = c; + e.xclient.data.l[4] = d; + + s_X11.sendEvent( + s_X11.display, + s_X11.root, + False, + SubstructureNotifyMask | SubstructureRedirectMask, + &e); +} + +static void xCreateScancodeTable() +{ + // Letters + s_Keyboard.scancodes[30] = PAL_SCANCODE_A; + s_Keyboard.scancodes[48] = PAL_SCANCODE_B; + s_Keyboard.scancodes[46] = PAL_SCANCODE_C; + s_Keyboard.scancodes[32] = PAL_SCANCODE_D; + s_Keyboard.scancodes[18] = PAL_SCANCODE_E; + s_Keyboard.scancodes[33] = PAL_SCANCODE_F; + s_Keyboard.scancodes[34] = PAL_SCANCODE_G; + s_Keyboard.scancodes[35] = PAL_SCANCODE_H; + s_Keyboard.scancodes[23] = PAL_SCANCODE_I; + s_Keyboard.scancodes[36] = PAL_SCANCODE_J; + s_Keyboard.scancodes[37] = PAL_SCANCODE_K; + s_Keyboard.scancodes[38] = PAL_SCANCODE_L; + s_Keyboard.scancodes[50] = PAL_SCANCODE_M; + s_Keyboard.scancodes[49] = PAL_SCANCODE_N; + s_Keyboard.scancodes[24] = PAL_SCANCODE_O; + s_Keyboard.scancodes[25] = PAL_SCANCODE_P; + s_Keyboard.scancodes[16] = PAL_SCANCODE_Q; + s_Keyboard.scancodes[19] = PAL_SCANCODE_R; + s_Keyboard.scancodes[31] = PAL_SCANCODE_S; + s_Keyboard.scancodes[20] = PAL_SCANCODE_T; + s_Keyboard.scancodes[22] = PAL_SCANCODE_U; + s_Keyboard.scancodes[47] = PAL_SCANCODE_V; + s_Keyboard.scancodes[17] = PAL_SCANCODE_W; + s_Keyboard.scancodes[45] = PAL_SCANCODE_X; + s_Keyboard.scancodes[21] = PAL_SCANCODE_Y; + s_Keyboard.scancodes[44] = PAL_SCANCODE_Z; + + // Numbers (top row) + s_Keyboard.scancodes[11] = PAL_SCANCODE_0; + s_Keyboard.scancodes[2] = PAL_SCANCODE_1; + s_Keyboard.scancodes[3] = PAL_SCANCODE_2; + s_Keyboard.scancodes[4] = PAL_SCANCODE_3; + s_Keyboard.scancodes[5] = PAL_SCANCODE_4; + s_Keyboard.scancodes[6] = PAL_SCANCODE_5; + s_Keyboard.scancodes[7] = PAL_SCANCODE_6; + s_Keyboard.scancodes[8] = PAL_SCANCODE_7; + s_Keyboard.scancodes[9] = PAL_SCANCODE_8; + s_Keyboard.scancodes[10] = PAL_SCANCODE_9; + + // Function + s_Keyboard.scancodes[59] = PAL_SCANCODE_F1; + s_Keyboard.scancodes[60] = PAL_SCANCODE_F2; + s_Keyboard.scancodes[61] = PAL_SCANCODE_F3; + s_Keyboard.scancodes[62] = PAL_SCANCODE_F4; + s_Keyboard.scancodes[63] = PAL_SCANCODE_F5; + s_Keyboard.scancodes[64] = PAL_SCANCODE_F6; + s_Keyboard.scancodes[65] = PAL_SCANCODE_F7; + s_Keyboard.scancodes[66] = PAL_SCANCODE_F8; + s_Keyboard.scancodes[67] = PAL_SCANCODE_F9; + s_Keyboard.scancodes[68] = PAL_SCANCODE_F10; + s_Keyboard.scancodes[87] = PAL_SCANCODE_F11; + s_Keyboard.scancodes[88] = PAL_SCANCODE_F12; + + // Control + s_Keyboard.scancodes[1] = PAL_SCANCODE_ESCAPE; + s_Keyboard.scancodes[28] = PAL_SCANCODE_ENTER; + s_Keyboard.scancodes[15] = PAL_SCANCODE_TAB; + s_Keyboard.scancodes[14] = PAL_SCANCODE_BACKSPACE; + s_Keyboard.scancodes[57] = PAL_SCANCODE_SPACE; + s_Keyboard.scancodes[58] = PAL_SCANCODE_CAPSLOCK; + s_Keyboard.scancodes[69] = PAL_SCANCODE_NUMLOCK; + s_Keyboard.scancodes[70] = PAL_SCANCODE_SCROLLLOCK; + s_Keyboard.scancodes[42] = PAL_SCANCODE_LSHIFT; + s_Keyboard.scancodes[54] = PAL_SCANCODE_RSHIFT; + s_Keyboard.scancodes[29] = PAL_SCANCODE_LCTRL; + s_Keyboard.scancodes[97] = PAL_SCANCODE_RCTRL; + s_Keyboard.scancodes[56] = PAL_SCANCODE_LALT; + s_Keyboard.scancodes[100] = PAL_SCANCODE_RALT; + + // Arrows + s_Keyboard.scancodes[105] = PAL_SCANCODE_LEFT; + s_Keyboard.scancodes[106] = PAL_SCANCODE_RIGHT; + s_Keyboard.scancodes[103] = PAL_SCANCODE_UP; + s_Keyboard.scancodes[108] = PAL_SCANCODE_DOWN; + + // Navigation + s_Keyboard.scancodes[110] = PAL_SCANCODE_INSERT; + s_Keyboard.scancodes[111] = PAL_SCANCODE_DELETE; + s_Keyboard.scancodes[102] = PAL_SCANCODE_HOME; + s_Keyboard.scancodes[107] = PAL_SCANCODE_END; + s_Keyboard.scancodes[104] = PAL_SCANCODE_PAGEUP; + s_Keyboard.scancodes[109] = PAL_SCANCODE_PAGEDOWN; + + // Keypad + s_Keyboard.scancodes[82] = PAL_SCANCODE_KP_0; + s_Keyboard.scancodes[79] = PAL_SCANCODE_KP_1; + s_Keyboard.scancodes[80] = PAL_SCANCODE_KP_2; + s_Keyboard.scancodes[81] = PAL_SCANCODE_KP_3; + s_Keyboard.scancodes[75] = PAL_SCANCODE_KP_4; + s_Keyboard.scancodes[76] = PAL_SCANCODE_KP_5; + s_Keyboard.scancodes[77] = PAL_SCANCODE_KP_6; + s_Keyboard.scancodes[71] = PAL_SCANCODE_KP_7; + s_Keyboard.scancodes[72] = PAL_SCANCODE_KP_8; + s_Keyboard.scancodes[73] = PAL_SCANCODE_KP_9; + s_Keyboard.scancodes[96] = PAL_SCANCODE_KP_ENTER; + s_Keyboard.scancodes[78] = PAL_SCANCODE_KP_ADD; + s_Keyboard.scancodes[74] = PAL_SCANCODE_KP_SUBTRACT; + s_Keyboard.scancodes[55] = PAL_SCANCODE_KP_MULTIPLY; + s_Keyboard.scancodes[98] = PAL_SCANCODE_KP_DIVIDE; + s_Keyboard.scancodes[83] = PAL_SCANCODE_KP_DECIMAL; + + // Misc + s_Keyboard.scancodes[99] = PAL_SCANCODE_PRINTSCREEN; + s_Keyboard.scancodes[102] = PAL_SCANCODE_PAUSE; + s_Keyboard.scancodes[127] = PAL_SCANCODE_MENU; + s_Keyboard.scancodes[40] = PAL_SCANCODE_APOSTROPHE; + s_Keyboard.scancodes[43] = PAL_SCANCODE_BACKSLASH; + s_Keyboard.scancodes[51] = PAL_SCANCODE_COMMA; + s_Keyboard.scancodes[13] = PAL_SCANCODE_EQUAL; + s_Keyboard.scancodes[41] = PAL_SCANCODE_GRAVEACCENT; + s_Keyboard.scancodes[12] = PAL_SCANCODE_SUBTRACT; + s_Keyboard.scancodes[52] = PAL_SCANCODE_PERIOD; + s_Keyboard.scancodes[39] = PAL_SCANCODE_SEMICOLON; + s_Keyboard.scancodes[53] = PAL_SCANCODE_SLASH; + s_Keyboard.scancodes[26] = PAL_SCANCODE_LBRACKET; + s_Keyboard.scancodes[27] = PAL_SCANCODE_RBRACKET; + s_Keyboard.scancodes[125] = PAL_SCANCODE_LSUPER; + s_Keyboard.scancodes[126] = PAL_SCANCODE_RSUPER; +} + +static void xCreateKeycodeTable() +{ + // Tis is for only printable and text input keys + + // Letters + s_Keyboard.keycodes[XK_a] = PAL_KEYCODE_A; + s_Keyboard.keycodes[XK_b] = PAL_KEYCODE_B; + s_Keyboard.keycodes[XK_c] = PAL_KEYCODE_C; + s_Keyboard.keycodes[XK_d] = PAL_KEYCODE_D; + s_Keyboard.keycodes[XK_e] = PAL_KEYCODE_E; + s_Keyboard.keycodes[XK_f] = PAL_KEYCODE_F; + s_Keyboard.keycodes[XK_g] = PAL_KEYCODE_G; + s_Keyboard.keycodes[XK_h] = PAL_KEYCODE_H; + s_Keyboard.keycodes[XK_i] = PAL_KEYCODE_I; + s_Keyboard.keycodes[XK_j] = PAL_KEYCODE_J; + s_Keyboard.keycodes[XK_k] = PAL_KEYCODE_K; + s_Keyboard.keycodes[XK_l] = PAL_KEYCODE_L; + s_Keyboard.keycodes[XK_m] = PAL_KEYCODE_M; + s_Keyboard.keycodes[XK_n] = PAL_KEYCODE_N; + s_Keyboard.keycodes[XK_o] = PAL_KEYCODE_O; + s_Keyboard.keycodes[XK_p] = PAL_KEYCODE_P; + s_Keyboard.keycodes[XK_q] = PAL_KEYCODE_Q; + s_Keyboard.keycodes[XK_r] = PAL_KEYCODE_R; + s_Keyboard.keycodes[XK_s] = PAL_KEYCODE_S; + s_Keyboard.keycodes[XK_t] = PAL_KEYCODE_T; + s_Keyboard.keycodes[XK_u] = PAL_KEYCODE_U; + s_Keyboard.keycodes[XK_v] = PAL_KEYCODE_V; + s_Keyboard.keycodes[XK_w] = PAL_KEYCODE_W; + s_Keyboard.keycodes[XK_x] = PAL_KEYCODE_X; + s_Keyboard.keycodes[XK_y] = PAL_KEYCODE_Y; + s_Keyboard.keycodes[XK_z] = PAL_KEYCODE_Z; + + // Numbers (top row) + s_Keyboard.keycodes[XK_0] = PAL_KEYCODE_0; + s_Keyboard.keycodes[XK_1] = PAL_KEYCODE_1; + s_Keyboard.keycodes[XK_2] = PAL_KEYCODE_2; + s_Keyboard.keycodes[XK_3] = PAL_KEYCODE_3; + s_Keyboard.keycodes[XK_4] = PAL_KEYCODE_4; + s_Keyboard.keycodes[XK_5] = PAL_KEYCODE_5; + s_Keyboard.keycodes[XK_6] = PAL_KEYCODE_6; + s_Keyboard.keycodes[XK_7] = PAL_KEYCODE_7; + s_Keyboard.keycodes[XK_8] = PAL_KEYCODE_8; + s_Keyboard.keycodes[XK_9] = PAL_KEYCODE_9; + + // Control + s_Keyboard.keycodes[XK_space] = PAL_KEYCODE_SPACE; + + // Misc + s_Keyboard.keycodes[XK_apostrophe] = PAL_KEYCODE_APOSTROPHE; + s_Keyboard.keycodes[XK_backslash] = PAL_KEYCODE_BACKSLASH; + s_Keyboard.keycodes[XK_comma] = PAL_KEYCODE_COMMA; + s_Keyboard.keycodes[XK_equal] = PAL_KEYCODE_EQUAL; + s_Keyboard.keycodes[XK_grave] = PAL_KEYCODE_GRAVEACCENT; + s_Keyboard.keycodes[XK_minus] = PAL_KEYCODE_SUBTRACT; + s_Keyboard.keycodes[XK_period] = PAL_KEYCODE_PERIOD; + s_Keyboard.keycodes[XK_semicolon] = PAL_KEYCODE_SEMICOLON; + s_Keyboard.keycodes[XK_slash] = PAL_KEYCODE_SLASH; + s_Keyboard.keycodes[XK_bracketleft] = PAL_KEYCODE_LBRACKET; + s_Keyboard.keycodes[XK_bracketright] = PAL_KEYCODE_RBRACKET; +} + +static PalResult xInitVideo() +{ + // load X11 library + s_X11.handle = dlopen("libX11.so", RTLD_LAZY); + if (!s_X11.handle) { + return PAL_RESULT_PLATFORM_FAILURE; + } + + // libXCursor is needed + s_X11.libCursor = dlopen("libXcursor.so", RTLD_LAZY); + if (!s_X11.libCursor) { + return PAL_RESULT_PLATFORM_FAILURE; + } + + // Xrandr is needed + s_X11.xrandr = dlopen("libXrandr.so.2", RTLD_LAZY); + if (!s_X11.xrandr) { + s_X11.xrandr = dlopen("libXrandr.so", RTLD_LAZY); + } + + // clang-format off + + // load procs + s_X11.openDisplay = (XOpenDisplayFn)dlsym( + s_X11.handle, + "XOpenDisplay"); + + s_X11.closeDisplay = (XCloseDisplayFn)dlsym( + s_X11.handle, + "XCloseDisplay"); + + s_X11.getWindowAttributes = (XGetWindowAttributesFn)dlsym( + s_X11.handle, + "XGetWindowAttributes"); + + s_X11.setCrtcConfig = (XRRSetCrtcConfigFn)dlsym( + s_X11.handle, + "XRRSetCrtcConfig"); + + s_X11.getWindowProperty = (XGetWindowPropertyFn)dlsym( + s_X11.handle, + "XGetWindowProperty"); + + s_X11.internAtom = (XInternAtomFn)dlsym( + s_X11.handle, + "XInternAtom"); + + s_X11.getSelectionOwner = (XGetSelectionOwnerFn)dlsym( + s_X11.handle, + "XGetSelectionOwner"); + + s_X11.freeColormap = (XFreeColormapFn)dlsym( + s_X11.handle, + "XFreeColormap"); + + s_X11.storeName = (XStoreNameFn)dlsym( + s_X11.handle, + "XStoreName"); + + s_X11.changeProperty = (XChangePropertyFn)dlsym( + s_X11.handle, + "XChangeProperty"); + + s_X11.flush = (XFlushFn)dlsym( + s_X11.handle, + "XFlush"); + + s_X11.createColormap = (XCreateColormapFn)dlsym( + s_X11.handle, + "XCreateColormap"); + + s_X11.mapWindow = (XMapWindowFn)dlsym( + s_X11.handle, + "XMapWindow"); + + s_X11.unmapWindow = (XUnmapWindowFn)dlsym( + s_X11.handle, + "XUnmapWindow"); + + s_X11.createWindow = (XCreateWindowFn)dlsym( + s_X11.handle, + "XCreateWindow"); + + s_X11.destroyWindow = (XDestroyWindowFn)dlsym( + s_X11.handle, + "XDestroyWindow"); + + s_X11.matchVisualInfo = (XMatchVisualInfoFn)dlsym( + s_X11.handle, + "XMatchVisualInfo"); + + s_X11.pending = (XPendingFn)dlsym( + s_X11.handle, + "XPending"); + + s_X11.setWMProtocols = (XSetWMProtocolsFn)dlsym( + s_X11.handle, + "XSetWMProtocols"); + + s_X11.nextEvent = (XNextEventFn)dlsym( + s_X11.handle, + "XNextEvent"); + + s_X11.setWMNormalHints = (XSetWMNormalHintsFn)dlsym( + s_X11.handle, + "XSetWMNormalHints"); + + s_X11.getWMNormalHints = (XGetWMNormalHintsFn)dlsym( + s_X11.handle, + "XGetWMNormalHints"); + + s_X11.sendEvent = (XSendEventFn)dlsym( + s_X11.handle, + "XSendEvent"); + + s_X11.moveWindow = (XMoveWindowFn)dlsym( + s_X11.handle, + "XMoveWindow"); + + s_X11.resizeWindow = (XResizeWindowFn)dlsym( + s_X11.handle, + "XResizeWindow"); + + s_X11.iconifyWindow = (XIconifyWindowFn)dlsym( + s_X11.handle, + "XIconifyWindow"); + + s_X11.setErrorHandler = (XSetErrorHandlerFn)dlsym( + s_X11.handle, + "XSetErrorHandler"); + + s_X11.sync = (XSyncFn)dlsym( + s_X11.handle, + "XSync"); + + s_X11.saveContext = (XSaveContextFn)dlsym( + s_X11.handle, + "XSaveContext"); + + s_X11.findContext = (XFindContextFn)dlsym( + s_X11.handle, + "XFindContext"); + + s_X11.uniqueContext = (XrmUniqueQuarkFn)dlsym( + s_X11.handle, + "XrmUniqueQuark"); + + // load Xrandr functions + s_X11.getScreenResources = (XRRGetScreenResourcesFn)dlsym( + s_X11.xrandr, + "XRRGetScreenResources"); + + s_X11.getOutputPrimary = (XRRGetOutputPrimaryFn)dlsym( + s_X11.xrandr, + "XRRGetOutputPrimary"); + + s_X11.getOutputInfo = (XRRGetOutputInfoFn)dlsym( + s_X11.xrandr, + "XRRGetOutputInfo"); + + s_X11.getCrtcInfo = (XRRGetCrtcInfoFn)dlsym( + s_X11.xrandr, + "XRRGetCrtcInfo"); + + s_X11.freeScreenResources = (XRRFreeScreenResourcesFn)dlsym( + s_X11.xrandr, + "XRRFreeScreenResources"); + + s_X11.freeOutputInfo = (XRRFreeOutputInfoFn)dlsym( + s_X11.xrandr, + "XRRFreeScreenResources"); + + s_X11.freeCrtcInfo = (XRRFreeCrtcInfoFn)dlsym( + s_X11.xrandr, + "XRRFreeCrtcInfo"); + + s_X11.selectRRInput = (XRRSelectInputFn)dlsym( + s_X11.xrandr, + "XRRSelectInput"); + + s_X11.queryRRExtension = (XRRQueryExtensionFn)dlsym( + s_X11.xrandr, + "XRRQueryExtension"); + + s_X11.allocClassHint = (XAllocClassHintFn)dlsym( + s_X11.handle, + "XAllocClassHint"); + + s_X11.setClassHint = (XSetClassHintFn)dlsym( + s_X11.handle, + "XSetClassHint"); + + s_X11.free = (XFreeFn)dlsym( + s_X11.handle, + "XFree"); + + s_X11.getVisualInfo = (XGetVisualInfoFn)dlsym( + s_X11.handle, + "XGetVisualInfo"); + + s_X11.createFontCursor = (XCreateFontCursorFn)dlsym( + s_X11.handle, + "XCreateFontCursor"); + + s_X11.freePixmap = (XFreePixmapFn)dlsym( + s_X11.handle, + "XFreePixmap"); + + s_X11.setWMHints = (XSetWMHintsFn)dlsym( + s_X11.handle, + "XSetWMHints"); + + s_X11.grabPointer = (XGrabPointerFn)dlsym( + s_X11.handle, + "XGrabPointer"); + + s_X11.createPixmapCursor = (XCreatePixmapCursorFn)dlsym( + s_X11.handle, + "XCreatePixmapCursor"); + + s_X11.warpPointer = (XWarpPointerFn)dlsym( + s_X11.handle, + "XWarpPointer"); + + s_X11.getWMName = (XGetWMNameFn)dlsym( + s_X11.handle, + "XGetWMName"); + + s_X11.queryPointer = (XQueryPointerFn)dlsym( + s_X11.handle, + "XQueryPointer"); + + s_X11.ungrabPointer = (XUngrabPointerFn)dlsym( + s_X11.handle, + "XUngrabPointer"); + + s_X11.allocWMHints = (XAllocWMHintsFn)dlsym( + s_X11.handle, + "XAllocWMHints"); + + s_X11.mapRaised = (XMapRaisedFn)dlsym( + s_X11.handle, + "XMapRaised"); + + s_X11.undefineCursor = (XUndefineCursorFn)dlsym( + s_X11.handle, + "XUndefineCursor"); + + s_X11.defineCursor = (XDefineCursorFn)dlsym( + s_X11.handle, + "XDefineCursor"); + + s_X11.freeCursor = (XFreeCursorFn)dlsym( + s_X11.handle, + "XFreeCursor"); + + s_X11.getWMHints = (XGetWMHintsFn)dlsym( + s_X11.handle, + "XGetWMHints"); + + s_X11.createPixmap = (XCreatePixmapFn)dlsym( + s_X11.handle, + "XCreatePixmap"); + + s_X11.setInputFocus = (XSetInputFocusFn)dlsym( + s_X11.handle, + "XSetInputFocus"); + + s_X11.getInputFocus = (XGetInputFocusFn)dlsym( + s_X11.handle, + "XGetInputFocus"); + + // libXcursor + s_X11.cursorImageLoadCursor = (XcursorImageLoadCursorFn)dlsym( + s_X11.libCursor, + "XcursorImageLoadCursor"); + + s_X11.cursorImageCreate = (XcursorImageCreateFn)dlsym( + s_X11.libCursor, + "XcursorImageCreate"); + + s_X11.cursorImageDestroy = (XcursorImageDestroyFn)dlsym( + s_X11.libCursor, + "XcursorImageDestroy"); + + s_X11.lookupKeysym = (XLookupKeysymFn)dlsym( + s_X11.libCursor, + "XLookupKeysym"); + + s_X11.setDetectableAutoRepeat = (XkbSetDetectableAutoRepeatFn)dlsym( + s_X11.handle, + "XkbSetDetectableAutoRepeat"); + + // X11 server + s_X11.display = s_X11.openDisplay(nullptr); + if (!s_X11.display) { + return PAL_RESULT_PLATFORM_FAILURE; + } + + s_X11.root = DefaultRootWindow(s_X11.display); + s_X11.screen = DefaultScreen(s_X11.display); + + xCheckFeatures(); + + // subscribe for monitor events + s_X11.selectRRInput( + s_X11.display, + s_X11.root, + RRScreenChangeNotifyMask | RRNotify); + + int eventBase, errorBase = 0; + s_X11.queryRRExtension(s_X11.display, &eventBase, &errorBase); + s_X11.rrEventBase = eventBase; + s_X11.skipScreenEvent = true; + s_X11.skipNotifyEvent = true; + + s_X11.dataID = (XContext)s_X11.uniqueContext(); + s_X11.className = "PAL"; + resetMonitorData(); + xCacheMonitors(true); + + // since X11 supports both EGL and GLX + // we try to load them and resolve the needed functions + + // we load GLX + s_X11.glxHandle = dlopen("libGL.so.1", RTLD_LAZY); + if (s_X11.glxHandle) { + + GLXGetProcAddressFn load = nullptr; + load = (GLXGetProcAddressFn)dlsym( + s_X11.glxHandle, + "glXGetProcAddress"); + + s_X11.glxGetFBConfigs = (GLXGetFBConfigsFn)load( + "glXGetFBConfigs"); + + s_X11.glxGetFBConfigAttrib = (GLXGetFBConfigAttribFn)load( + "glXGetFBConfigAttrib"); + + s_X11.glxGetVisualFromFBConfig = (GLXGetVisualFromFBConfigFn)load( + "glXGetVisualFromFBConfig"); + } + + // create a hidden cursor. + // This is used to simulate cursor hide and show + Pixmap map = s_X11.createPixmap(s_X11.display, s_X11.root, 1, 1, 1); + XColor dummy; + s_X11.hiddenCursor = s_X11.createPixmapCursor( + s_X11.display, + map, + map, + &dummy, + &dummy, + 0, + 0); + + s_X11.freePixmap(s_X11.display, map); + xCreateScancodeTable(); + xCreateKeycodeTable(); + + // disable auto key repeats + int supported; + s_X11.setDetectableAutoRepeat(s_X11.display, True, &supported); + if (!supported) { + // FIXME: fallback to manual key repeat detection + } + + // clang-format on + return PAL_RESULT_SUCCESS; +} + +static void xShutdownVideo() +{ + if (s_X11.colormap) { + s_X11.freeColormap(s_X11.display, s_X11.colormap); + } + + s_X11.closeDisplay(s_X11.display); + dlclose(s_X11.handle); + dlclose(s_X11.xrandr); + dlclose(s_X11.libCursor); + + if (s_X11.glxHandle) { + dlclose(s_X11.glxHandle); + } +} + +static void xUpdateVideo() +{ + XEvent event; + PalDispatchMode mode = PAL_DISPATCH_NONE; + while (s_X11.pending(s_X11.display)) { + s_X11.nextEvent(s_X11.display, &event); + + Window xWin = event.xany.window; + PalWindow* window = TO_PAL_HANDLE(PalWindow, xWin); + WindowData* data = nullptr; + s_X11.findContext(s_X11.display, xWin, s_X11.dataID, (XPointer*)&data); + + if (event.type == s_X11.rrEventBase + RRScreenChangeNotify) { + event.type = RANDR_SCREEN_CHANGE_EVENT; // for switch flow + } else if (event.type == s_X11.rrEventBase + RRNotify) { + event.type = RANDR_NOTIFY_EVENT; // for switch flow + } + + switch (event.type) { + case ClientMessage: { + // check for window close + Atom windowClose = event.xclient.data.l[0]; + if (windowClose == s_X11Atoms.WM_DELETE_WINDOW) { + if (s_Video.eventDriver) { + PalEventDriver* driver = s_Video.eventDriver; + PalEventType type = PAL_EVENT_WINDOW_CLOSE; + mode = palGetEventDispatchMode(driver, type); + if (mode != PAL_DISPATCH_NONE) { + PalEvent event = {0}; + event.type = type; + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); + } + } + } + return; + } + + case ConfigureNotify: { + // window resize or move + + // skip the first configure event + if (data->skipConfigure) { + data->skipConfigure = false; + data->w = event.xconfigure.width; + data->h = event.xconfigure.height; + data->x = event.xconfigure.x; + data->y = event.xconfigure.y; + return; + } + + // real configure event + if (s_Video.eventDriver) { + // check if its a resize event + if (data->w != event.xconfigure.width || + data->h != event.xconfigure.height) { + data->w = event.xconfigure.width; + data->h = event.xconfigure.height; + + // push a resize event + PalEventDriver* driver = s_Video.eventDriver; + PalEventType type = PAL_EVENT_WINDOW_SIZE; + mode = palGetEventDispatchMode(driver, type); + + if (mode != PAL_DISPATCH_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = palPackUint32(data->w, data->h); + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); + } + } + + // check if its a move event + if (data->x != event.xconfigure.x || + data->y != event.xconfigure.y) { + data->x = event.xconfigure.x; + data->y = event.xconfigure.y; + + // push a move event + PalEventDriver* driver = s_Video.eventDriver; + PalEventType type = PAL_EVENT_WINDOW_MOVE; + mode = palGetEventDispatchMode(driver, type); + + if (mode != PAL_DISPATCH_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = palPackInt32(data->x, data->y); + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); + } + + /** a window has to be moved + before its can change monitors + we get the monitor the moved + window is on and check if the dpi is different + from the one it was created on */ + int monitorDPI = xGetWindowMonitorDPI(data, false); + if (monitorDPI != data->dpi) { + // window is on a different monitor + data->dpi = monitorDPI; + + // push a DPI event + type = PAL_EVENT_MONITOR_DPI_CHANGED; + mode = palGetEventDispatchMode(driver, type); + + if (mode != PAL_DISPATCH_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = monitorDPI; + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); + } + } + } + } + return; + } + + case FocusIn: { + // window has gained focus + if (s_Video.eventDriver) { + int mode = event.xfocus.mode; + if (mode == NotifyGrab || mode == NotifyUngrab) { + // ignore dragging and popup focus events + return; + } + + PalEventDriver* driver = s_Video.eventDriver; + PalEventType type = PAL_EVENT_WINDOW_FOCUS; + mode = palGetEventDispatchMode(driver, type); + if (mode != PAL_DISPATCH_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = true; + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); + } + } + return; + } + + case FocusOut: { + // window has lost focus + if (s_Video.eventDriver) { + int mode = event.xfocus.mode; + if (mode == NotifyGrab || mode == NotifyUngrab) { + // ignore dragging and popup focus events + break; + } + + PalEventDriver* driver = s_Video.eventDriver; + PalEventType type = PAL_EVENT_WINDOW_FOCUS; + mode = palGetEventDispatchMode(driver, type); + if (mode != PAL_DISPATCH_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = false; + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); + } + } + return; + } + + case PropertyNotify: { + // check window state (maximize, minimize) + if (event.xproperty.atom == s_X11Atoms._NET_WM_STATE) { + PalWindowState state; + state = xQueryWindowState(event.xproperty.window); + if (state != data->state) { + data->state = state; + + // skip the first state event + if (data->skipState) { + data->skipState = false; + return; + } + + // push event + PalEventDriver* driver = s_Video.eventDriver; + PalEventType type = PAL_EVENT_WINDOW_STATE; + mode = palGetEventDispatchMode(driver, type); + + if (mode != PAL_DISPATCH_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = data->state; + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); + } + } + } + return; + } + + case RANDR_SCREEN_CHANGE_EVENT: { + // skip the first event + if (s_X11.skipScreenEvent) { + s_X11.skipScreenEvent = false; + return; + } + + // something change on pre existing monitor + // cache the information + xCacheMonitors(false); + return; + } + + case RANDR_NOTIFY_EVENT: { + // skip the first event + if (s_X11.skipNotifyEvent) { + s_X11.skipNotifyEvent = false; + return; + } + + XRRNotifyEvent* e = (XRRNotifyEvent*)&event; + switch (e->subtype) { + case RRNotify_OutputChange: { + // push a event monitor list changed event + if (s_Video.eventDriver) { + PalEventDriver* driver = s_Video.eventDriver; + PalEventType type = PAL_EVENT_MONITOR_LIST_CHANGED; + mode = palGetEventDispatchMode(driver, type); + if (mode != PAL_DISPATCH_NONE) { + PalEvent event = {0}; + event.type = type; + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); + } + } + + /** enumerate monitors and cache them + these will be used to detect DPI changed + since X11 does not have a DPI changed function */ + resetMonitorData(); + xCacheMonitors(true); + return; + } + } + } + + case MotionNotify: { + // mouse moved + const int x = event.xmotion.x; + const int y = event.xmotion.y; + const int dx = x - s_Mouse.lastX; + const int dy = y - s_Mouse.lastY; + + if (s_Video.eventDriver) { + PalEventDriver* driver = s_Video.eventDriver; + PalEventType type = PAL_EVENT_MOUSE_MOVE; + mode = palGetEventDispatchMode(driver, type); + if (mode != PAL_DISPATCH_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = palPackInt32(x, y); + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); + } + + // push a mouse delta event + type = PAL_EVENT_MOUSE_DELTA; + mode = palGetEventDispatchMode(driver, type); + if (mode != PAL_DISPATCH_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = palPackInt32(dx, dy); + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); + } + } + + s_Mouse.lastX = x; + s_Mouse.lastY = y; + s_Mouse.dx = dx; + s_Mouse.dy = dy; + return; + } + + case ButtonPress: + case ButtonRelease: { + int xButton = event.xbutton.button; + bool pressed = (event.xbutton.type == ButtonPress); + PalMouseButton button = 0; + PalEventType type; + + if (xButton == 1) { + button = PAL_MOUSE_BUTTON_LEFT; + } else if (xButton == 3) { + button = PAL_MOUSE_BUTTON_RIGHT; + } else if (xButton == 2) { + button = PAL_MOUSE_BUTTON_MIDDLE; + } + + s_Mouse.state[button] = pressed; + if (s_Video.eventDriver && button != 0) { + PalEventDriver* driver = s_Video.eventDriver; + if (pressed) { + type = PAL_EVENT_MOUSE_BUTTONDOWN; + } else { + type = PAL_EVENT_MOUSE_BUTTONUP; + } + + mode = palGetEventDispatchMode(driver, type); + if (mode != PAL_DISPATCH_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = button; + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); + } + } + + int scrollX = 0; + int scrollY = 0; + if (xButton == 4) { + // scroll up + scrollY = 1; + } else if (xButton == 5) { + // scroll down + scrollY = -1; + } else if (xButton == 6) { + // scroll left + scrollX = -1; + } else if (xButton == 7) { + // scroll right + scrollX = 1; + } + + s_Mouse.WheelX = scrollX; + s_Mouse.WheelY = scrollY; + if (s_Video.eventDriver && (scrollX || scrollY)) { + PalEventDriver* driver = s_Video.eventDriver; + // clang-format off + mode = palGetEventDispatchMode(driver, PAL_EVENT_MOUSE_WHEEL); + // clang-format on + + if (mode != PAL_DISPATCH_NONE) { + PalEvent event = {0}; + event.type = PAL_EVENT_MOUSE_WHEEL; + event.data = palPackInt32(scrollX, scrollY); + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); + } + } + return; + } + + case KeyPress: + case KeyRelease: { + int xScancode = event.xkey.keycode; + bool pressed = (event.xbutton.type == KeyPress); + PalScancode scancode = PAL_SCANCODE_UNKNOWN; + PalKeycode keycode = PAL_KEYCODE_UNKNOWN; + PalEventType type; + KeySym keySym = s_X11.lookupKeysym(&event.xkey, 0); + + // special handling for Pause/break with Home + if (xScancode == 110) { + if (keySym == XK_Pause) { + scancode = PAL_SCANCODE_PAUSE; + } else { + scancode = PAL_SCANCODE_HOME; + } + + } else { + int index = xScancode - 8; + scancode = s_Keyboard.scancodes[index]; + } + + // printable and text input keys are from the range + // 39 (PAL_KEYCODE_APOSTROPHE) and 122 (PAL_KEYCODE_Z) + // The rest are almost the same as their scancode + // Maybe there will be a layout that makes this wrong + // but for now this works + if (keySym >= XK_apostrophe && keySym <= XK_z) { + // a printable or input key + keycode = s_Keyboard.keycodes[keySym]; + + } else { + // Since PalKeycode and PalScancode have the same integers + // we can make a direct cast without a table + // Examle: PAL_KEYCODE_A(int 0) == PAL_SCANCODE_A(int 0) + keycode = (PalKeycode)(Uint32)scancode; + } + + // If we got a keySym but its not mapped into our keycode array + // we do a direct cast as well + if (keycode == PAL_KEYCODE_UNKNOWN) { + keycode = (PalKeycode)(Uint32)scancode; + } + + // update our keyboard and mouse state to handle key repeat + bool repeat = s_Keyboard.keycodeState[keycode]; + s_Keyboard.scancodeState[scancode] = pressed; + s_Keyboard.keycodeState[keycode] = pressed; + + if (pressed) { + if (repeat) { + type = PAL_EVENT_KEYREPEAT; + } else { + type = PAL_EVENT_KEYDOWN; + } + } else { + type = PAL_EVENT_KEYUP; + } + + if (s_Video.eventDriver) { + PalEventDriver* driver = s_Video.eventDriver; + mode = palGetEventDispatchMode(driver, type); + if (mode != PAL_DISPATCH_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = palPackUint32(keycode, scancode); + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); + } + } + return; + } + } + } + + s_X11.flush(s_X11.display); +} + +static PalResult xEnumerateMonitors( + Int32* count, + PalMonitor** outMonitors) +{ + int _count = 0; + int maxCount = outMonitors ? *count : 0; + // clang-format off + XRRScreenResources* resources = s_X11.getScreenResources(s_X11.display, s_X11.root); + // clang-format on + + for (int i = 0; i < resources->noutput; ++i) { + RROutput output = resources->outputs[i]; + // clang-format off + XRROutputInfo* outputInfo = s_X11.getOutputInfo(s_X11.display, resources, output); + // clang-format on + + if (outputInfo->connection == RR_Connected && + outputInfo->crtc != None) { + // a monitor + if (outMonitors) { + if (_count < maxCount) { + outMonitors[_count] = TO_PAL_HANDLE(PalMonitor, output); + } + } + _count++; + } + } + + if (!outMonitors) { + *count = _count; + } + return PAL_RESULT_SUCCESS; +} + +static PalResult xGetPrimaryMonitor(PalMonitor** outMonitor) +{ + RROutput primary = s_X11.getOutputPrimary(s_X11.display, s_X11.root); + + if (!primary) { + // primary monitor is not set, set the first one + // clang-format off + XRRScreenResources* resources = s_X11.getScreenResources(s_X11.display, s_X11.root); + // clang-format on + + for (int i = 0; i < resources->noutput; ++i) { + RROutput output = resources->outputs[i]; + // clang-format off + XRROutputInfo* outputInfo = s_X11.getOutputInfo(s_X11.display, resources, output); + // clang-format on + + if (outputInfo->connection == RR_Connected && + outputInfo->crtc != None) { + primary = resources->outputs[i]; + break; + } + } + } + + // if we still did not get one, we fail + if (primary) { + *outMonitor = TO_PAL_HANDLE(PalMonitor, primary); + return PAL_RESULT_SUCCESS; + + } else { + return PAL_RESULT_PLATFORM_FAILURE; + } +} + +static PalResult xGetMonitorInfo( + PalMonitor* monitor, + PalMonitorInfo* info) +{ + // clang-format off + XRRScreenResources* resources = s_X11.getScreenResources(s_X11.display, s_X11.root); + // clang-format on + + XRROutputInfo* outputInfo = s_X11.getOutputInfo( + s_X11.display, + resources, + FROM_PAL_HANDLE(RROutput, monitor)); + + if (!outputInfo) { + // invalid monitor + s_X11.freeScreenResources(resources); + return PAL_RESULT_INVALID_MONITOR; + } + + if (outputInfo->connection != RR_Connected) { + // invalid monitor + s_X11.freeScreenResources(resources); + return PAL_RESULT_INVALID_MONITOR; + } + + strcpy(info->name, outputInfo->name); + + // check if its primary monitor + RROutput primary = s_X11.getOutputPrimary(s_X11.display, s_X11.root); + + if (monitor == TO_PAL_HANDLE(PalMonitor, primary)) { + info->primary = true; + } + + // get monitor pos and size + // clang-format off + XRRCrtcInfo* crtc = s_X11.getCrtcInfo(s_X11.display, resources, outputInfo->crtc); + // clang-format on + + info->x = crtc->x; + info->y = crtc->y; + info->width = crtc->width; + info->height = crtc->height; + + // get refresh rate + double rate = 0; + for (int i = 0; i < resources->nmode; ++i) { + // check for our monitor + if (resources->modes[i].id == crtc->mode) { + XRRModeInfo* mode = &resources->modes[i]; + double tmp = (double)mode->hTotal * (double)mode->vTotal; + rate = (double)mode->dotClock / tmp; + info->refreshRate = rate + 0.5; + break; + } + } + + // orientation + switch (crtc->rotation) { + case RR_Rotate_0: { + info->orientation = PAL_ORIENTATION_LANDSCAPE; + break; + } + + case RR_Rotate_90: { + info->orientation = PAL_ORIENTATION_PORTRAIT; + break; + } + + case RR_Rotate_180: { + info->orientation = PAL_ORIENTATION_LANDSCAPE_FLIPPED; + break; + } + + case RR_Rotate_270: { + info->orientation = PAL_ORIENTATION_PORTRAIT_FLIPPED; + break; + } + + default: { + info->orientation = PAL_ORIENTATION_LANDSCAPE; + } + } + + // get dpi + double tmp = (double)(crtc->width * 25.4) / (double)outputInfo->mm_width; + info->dpi = (Uint32)tmp; + + s_X11.freeCrtcInfo(crtc); + s_X11.freeOutputInfo(outputInfo); + s_X11.freeScreenResources(resources); + + return PAL_RESULT_SUCCESS; +} + +static PalResult xEnumerateMonitorModes( + PalMonitor* monitor, + Int32* count, + PalMonitorMode* modes) +{ + Int32 modeCount = 0; + int maxModeCount = modes ? *count : 0; + + // clang-format off + XRRScreenResources* resources = s_X11.getScreenResources(s_X11.display, s_X11.root); + // clang-format on + + // get the monitor info + XRROutputInfo* outputInfo = s_X11.getOutputInfo( + s_X11.display, + resources, + FROM_PAL_HANDLE(RROutput, monitor)); + + if (!outputInfo) { + // invalid monitor + s_X11.freeScreenResources(resources); + return PAL_RESULT_INVALID_MONITOR; + } + + if (outputInfo->connection != RR_Connected) { + // invalid monitor + s_X11.freeScreenResources(resources); + return PAL_RESULT_INVALID_MONITOR; + } + + // get supported display modes + for (int i = 0; i < outputInfo->nmode; ++i) { + for (int j = 0; j < resources->nmode; ++j) { + // get the display mode and check if its for our monitor + XRRModeInfo* info = &resources->modes[j]; + if (info->id == outputInfo->modes[i]) { + // check if user supplied a PalMonitorMode array + if (modes) { + if (modeCount < maxModeCount) { + PalMonitorMode* mode = &modes[modeCount]; + mode->width = info->width; + mode->height = info->height; + mode->bpp = s_X11.bpp; + + // clang-format off + double tmp = (double)info->hTotal * (double)info->vTotal; + // clang-format on + + double rate = (double)info->dotClock / tmp; + mode->refreshRate = rate + 0.5; + } + } + modeCount++; + } + } + } + + if (!modes) { + *count = modeCount; + } + + s_X11.freeOutputInfo(outputInfo); + s_X11.freeScreenResources(resources); + + return PAL_RESULT_SUCCESS; +} + +static PalResult xGetCurrentMonitorMode( + PalMonitor* monitor, + PalMonitorMode* mode) +{ + // clang-format off + XRRScreenResources* resources = s_X11.getScreenResources(s_X11.display, s_X11.root); + // clang-format on + + // get the monitor info + XRROutputInfo* outputInfo = s_X11.getOutputInfo( + s_X11.display, + resources, + FROM_PAL_HANDLE(RROutput, monitor)); + + if (!outputInfo) { + // invalid monitor + s_X11.freeScreenResources(resources); + return PAL_RESULT_INVALID_MONITOR; + } + + if (outputInfo->connection != RR_Connected) { + // invalid monitor + s_X11.freeScreenResources(resources); + return PAL_RESULT_INVALID_MONITOR; + } + + // get the current display mode + // clang-format off + XRRCrtcInfo* crtc = s_X11.getCrtcInfo(s_X11.display, resources, outputInfo->crtc); + // clang-format on + + // find the display mode + XRRModeInfo* info = nullptr; + for (int i = 0; i < resources->nmode; ++i) { + if (resources->modes[i].id == crtc->mode) { + // found + info = &resources->modes[i]; + break; + } + } + + if (mode) { + mode->width = info->width; + mode->height = info->height; + mode->bpp = s_X11.bpp; + + double tmp = (double)info->hTotal * (double)info->vTotal; + double rate = (double)info->dotClock / tmp; + mode->refreshRate = rate + 0.5; + } + + s_X11.freeCrtcInfo(crtc); + s_X11.freeOutputInfo(outputInfo); + s_X11.freeScreenResources(resources); + + return PAL_RESULT_SUCCESS; +} + +static PalResult xSetMonitorMode( + PalMonitor* monitor, + PalMonitorMode* mode) +{ + // clang-format off + XRRScreenResources* resources = s_X11.getScreenResources(s_X11.display, s_X11.root); + // clang-format on + + // get the monitor info + XRROutputInfo* outputInfo = s_X11.getOutputInfo( + s_X11.display, + resources, + FROM_PAL_HANDLE(RROutput, monitor)); + + if (!outputInfo) { + // invalid monitor + s_X11.freeScreenResources(resources); + return PAL_RESULT_INVALID_MONITOR; + } + + if (outputInfo->connection != RR_Connected) { + // invalid monitor + s_X11.freeScreenResources(resources); + return PAL_RESULT_INVALID_MONITOR; + } + + // find the monitor display mode + RRMode displayMode = xFindMode(resources, mode); + if (displayMode == None) { + s_X11.freeOutputInfo(outputInfo); + s_X11.freeScreenResources(resources); + return PAL_RESULT_INVALID_MONITOR_MODE; + } + + // apply the display mode + // clang-format off + XRRCrtcInfo* crtc = s_X11.getCrtcInfo(s_X11.display, resources, outputInfo->crtc); + // clang-format on + + RROutput output = FROM_PAL_HANDLE(RROutput, monitor); + int ret = s_X11.setCrtcConfig( + s_X11.display, + resources, + outputInfo->crtc, + CurrentTime, + crtc->x, + crtc->y, + displayMode, + crtc->rotation, + &output, + 1); + + s_X11.freeCrtcInfo(crtc); + s_X11.freeOutputInfo(outputInfo); + s_X11.freeScreenResources(resources); + + if (ret != Success) { + return PAL_RESULT_PLATFORM_FAILURE; + } + + return PAL_RESULT_SUCCESS; +} + +static PalResult xValidateMonitorMode( + PalMonitor* monitor, + PalMonitorMode* mode) +{ + // clang-format off + XRRScreenResources* resources = s_X11.getScreenResources(s_X11.display, s_X11.root); + // clang-format on + + // get the monitor info + XRROutputInfo* outputInfo = s_X11.getOutputInfo( + s_X11.display, + resources, + FROM_PAL_HANDLE(RROutput, monitor)); + + if (!outputInfo) { + // invalid monitor + s_X11.freeScreenResources(resources); + return PAL_RESULT_INVALID_MONITOR; + } + + if (outputInfo->connection != RR_Connected) { + // invalid monitor + s_X11.freeScreenResources(resources); + return PAL_RESULT_INVALID_MONITOR; + } + + // find the monitor display mode + RRMode displayMode = xFindMode(resources, mode); + if (displayMode == None) { + s_X11.freeOutputInfo(outputInfo); + s_X11.freeScreenResources(resources); + return PAL_RESULT_INVALID_MONITOR_MODE; + } + + s_X11.freeOutputInfo(outputInfo); + s_X11.freeScreenResources(resources); + + return PAL_RESULT_SUCCESS; +} + +static PalResult xSetMonitorOrientation( + PalMonitor* monitor, + PalOrientation orientation) +{ + // clang-format off + XRRScreenResources* resources = s_X11.getScreenResources(s_X11.display, s_X11.root); + // clang-format on + + // get the monitor info + XRROutputInfo* outputInfo = s_X11.getOutputInfo( + s_X11.display, + resources, + FROM_PAL_HANDLE(RROutput, monitor)); + + if (!outputInfo) { + // invalid monitor + s_X11.freeScreenResources(resources); + return PAL_RESULT_INVALID_MONITOR; + } + + if (outputInfo->connection != RR_Connected) { + // invalid monitor + s_X11.freeScreenResources(resources); + return PAL_RESULT_INVALID_MONITOR; + } + + // get the current display mode + // clang-format off + XRRCrtcInfo* crtc = s_X11.getCrtcInfo(s_X11.display, resources, outputInfo->crtc); + // clang-format on + + // check if the new orientation is supported + Rotation rotation = 0; + switch (orientation) { + case PAL_ORIENTATION_LANDSCAPE: { + rotation = RR_Rotate_0; + break; + } + + case PAL_ORIENTATION_PORTRAIT: { + rotation = RR_Rotate_90; + break; + } + + case PAL_ORIENTATION_LANDSCAPE_FLIPPED: { + rotation = RR_Rotate_180; + break; + } + + case PAL_ORIENTATION_PORTRAIT_FLIPPED: { + rotation = RR_Rotate_270; + break; + } + + default: { + return PAL_RESULT_INVALID_ORIENTATION; + } + } + + if (!(crtc->rotations & rotation)) { + return PAL_RESULT_INVALID_ORIENTATION; + } + + RROutput output = FROM_PAL_HANDLE(RROutput, monitor); + int ret = s_X11.setCrtcConfig( + s_X11.display, + resources, + outputInfo->crtc, + CurrentTime, + crtc->x, + crtc->y, + crtc->mode, + rotation, + &output, + 1); + + s_X11.freeCrtcInfo(crtc); + s_X11.freeOutputInfo(outputInfo); + s_X11.freeScreenResources(resources); + + if (ret != Success) { + return PAL_RESULT_PLATFORM_FAILURE; + } + + return PAL_RESULT_SUCCESS; +} + +static PalResult xCreateWindow( + const PalWindowCreateInfo* info, + PalWindow** outWindow) +{ + Window window = None; + PalMonitor* monitor = nullptr; + PalMonitorInfo monitorInfo; + + WindowData* data = getFreeWindowData(); + if (!data) { + return PAL_RESULT_OUT_OF_MEMORY; + } + + Visual* visual = nullptr; + int depth = 0; + Colormap colormap = None; + unsigned long bgPixel = 0; + unsigned long borderPixel = 0; + + if (s_X11.colormap) { + visual = s_X11.visual; + depth = s_X11.depth; + colormap = s_X11.colormap; + bgPixel = 0; + borderPixel = 0; + + } else { + // use a default visual + visual = DefaultVisual(s_X11.display, s_X11.screen); + depth = DefaultDepth(s_X11.display, s_X11.screen); + bgPixel = WhitePixel(s_X11.display, s_X11.screen); + borderPixel = BlackPixel(s_X11.display, s_X11.screen); + colormap = DefaultColormap(s_X11.display, s_X11.screen); + } + + // get monitor + if (info->monitor) { + monitor = info->monitor; + + } else { + // get primary monitor + xGetPrimaryMonitor(&monitor); + if (!monitor) { + return PAL_RESULT_PLATFORM_FAILURE; + } + } + + // get monitor info + PalResult result = palGetMonitorInfo(monitor, &monitorInfo); + if (result != PAL_RESULT_SUCCESS) { + return result; + } + + Int32 x, y = 0; + // the position and size must be scaled with the dpi before this call + if (info->center) { + x = monitorInfo.x + (monitorInfo.width - info->width) / 2; + y = monitorInfo.y + (monitorInfo.height - info->height) / 2; + + } else { + // we set 100 for each axix + x = monitorInfo.x + 100; + y = monitorInfo.y + 100; + } + + // check and set transparency + if (info->style & PAL_WINDOW_STYLE_TRANSPARENT) { + if (!(s_Video.features & PAL_VIDEO_FEATURE_TRANSPARENT_WINDOW)) { + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; + } + + // we dont need to set any flag + } + + long mask = ExposureMask | StructureNotifyMask | KeyPressMask; + mask |= KeyReleaseMask; + mask |= ButtonPressMask; + mask |= ButtonReleaseMask; + mask |= PointerMotionMask; + mask |= FocusChangeMask; + mask |= EnterWindowMask; + mask |= LeaveWindowMask; + mask |= PropertyChangeMask; + + XSetWindowAttributes attrs = {0}; + attrs.colormap = colormap; + attrs.event_mask = mask; + attrs.background_pixel = bgPixel; + attrs.border_pixel = borderPixel; + attrs.override_redirect = False; + + // create window + window = s_X11.createWindow( + s_X11.display, + s_X11.root, + x, + y, + info->width, + info->height, + 0, // border width + depth, + InputOutput, // class + visual, + CWEventMask | CWColormap | CWBorderPixel | CWBackPixel, + &attrs); + + if (window == None) { + return PAL_RESULT_PLATFORM_FAILURE; + } + + // set pid property + pid_t pid = getpid(); + s_X11.changeProperty( + s_X11.display, + window, + s_X11Atoms._NET_WM_PID, + XA_CARDINAL, + 32, + PropModeReplace, + (unsigned char*)&pid, + 1); + + // set class property + XClassHint* hints = s_X11.allocClassHint(); + if (hints) { + const char* resName = getenv("RESOURCE_NAME"); + const char* resClass = getenv("RESOURCE_CLASS"); + + if (!resName || strlen(resName) == 0) { + resName = info->title; + } + + if (!resClass || strlen(resClass) == 0) { + resClass = s_X11.className; + } + + hints->res_name = (char*)resName; + hints->res_class = (char*)resClass; + s_X11.setClassHint(s_X11.display, window, hints); + s_X11.free(hints); + } + + if (s_X11Atoms.unicodeTitle) { + s_X11.changeProperty( + s_X11.display, + window, + s_X11Atoms._NET_WM_NAME, + s_X11Atoms.UTF8_STRING, + 8, // unsigned char + PropModeReplace, + info->title, + strlen(info->title)); + + } else { + s_X11.storeName(s_X11.display, window, info->title); + } + + // borderless + if (info->style & PAL_WINDOW_STYLE_BORDERLESS) { + if (!(s_Video.features & PAL_VIDEO_FEATURE_BORDERLESS_WINDOW)) { + s_X11.destroyWindow(s_X11.display, window); + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; + } + + s_X11.changeProperty( + s_X11.display, + window, + s_X11Atoms._NET_WM_WINDOW_TYPE, + XA_ATOM, + 32, + PropModeReplace, + (unsigned char*)&s_X11Atoms._NET_WM_WINDOW_TYPE_SPLASH, + 1); + } + + // tool window + if (info->style & PAL_WINDOW_STYLE_TOOL) { + if (!(s_Video.features & PAL_VIDEO_FEATURE_TOOL_WINDOW)) { + s_X11.destroyWindow(s_X11.display, window); + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; + } + + s_X11.changeProperty( + s_X11.display, + window, + s_X11Atoms._NET_WM_WINDOW_TYPE, + XA_ATOM, + 32, + PropModeReplace, + (unsigned char*)&s_X11Atoms._NET_WM_WINDOW_TYPE_UTILITY, + 1); + } + + // topmost + if (info->style & PAL_WINDOW_STYLE_TOPMOST) { + if (s_X11Atoms._NET_WM_STATE_ABOVE) { + s_X11.changeProperty( + s_X11.display, + window, + s_X11Atoms._NET_WM_STATE, + XA_ATOM, + 32, + PropModeAppend, + (unsigned char*)&s_X11Atoms._NET_WM_STATE_ABOVE, + 1); + } + } + + // set size hints + XSizeHints wmHints = {0}; + wmHints.flags = PPosition | PSize; + wmHints.x = x; + wmHints.y = y; + wmHints.width = info->width; + wmHints.height = info->height; + + // resizable + if (!(info->style & PAL_WINDOW_STYLE_RESIZABLE)) { + wmHints.flags |= PMinSize; + wmHints.flags |= PMaxSize; + wmHints.min_width = wmHints.max_width = info->width; + wmHints.min_height = wmHints.max_height = info->height; + } + + // show window + s_X11.setWMNormalHints(s_X11.display, window, &wmHints); + if (info->show) { + s_X11.mapWindow(s_X11.display, window); + s_X11.flush(s_X11.display); + } + + // check if the window has been mapped + // we use this to minimize or maximize + XWindowAttributes attr; + s_X11.getWindowAttributes(s_X11.display, window, &attr); + bool windowMapped = attr.map_state = IsViewable; + + // maximize + if (info->maximized) { + if (!(s_Video.features & PAL_VIDEO_FEATURE_WINDOW_SET_STATE)) { + s_X11.destroyWindow(s_X11.display, window); + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; + } + + // if the window is not mapped, we wait till its mapped + if (!windowMapped) { + // wait till the window is mapped + for (;;) { + XEvent event; + s_X11.nextEvent(s_X11.display, &event); + if (event.type == MapNotify && event.xmap.window == window) { + windowMapped = true; + break; + } + } + } + + xSendWMEvent( + window, + s_X11Atoms._NET_WM_STATE, + s_X11Atoms._NET_WM_STATE_MAXIMIZED_VERT, + s_X11Atoms._NET_WM_STATE_MAXIMIZED_HORZ, + 1, + 0, + true); // _NET_WM_STATE_ADD + } + + // minimize + if (info->minimized) { + if (!(s_Video.features & PAL_VIDEO_FEATURE_WINDOW_SET_STATE)) { + s_X11.destroyWindow(s_X11.display, window); + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; + } + + // if the window is not mapped, we wait till its mapped + if (!windowMapped) { + // wait till the window is mapped + for (;;) { + XEvent event; + s_X11.nextEvent(s_X11.display, &event); + if (event.type == MapNotify && event.xmap.window == window) { + windowMapped = true; + break; + } + } + } + s_X11.iconifyWindow(s_X11.display, window, s_X11.screen); + } + + s_X11.setWMProtocols( + s_X11.display, + window, + &s_X11Atoms.WM_DELETE_WINDOW, + True); + + s_X11.flush(s_X11.display); + + // attach the window data to the window + data->skipConfigure = true; + data->skipState = true; + data->dpi = monitorInfo.dpi; // the current window monitor + data->window = TO_PAL_HANDLE(PalWindow, window); + data->cursor = nullptr; + s_X11.saveContext(s_X11.display, window, s_X11.dataID, (XPointer)data); + + *outWindow = TO_PAL_HANDLE(PalWindow, window); + return PAL_RESULT_SUCCESS; +} + +static void xDestroyWindow(PalWindow* window) +{ + Window xWin = FROM_PAL_HANDLE(Window, window); + WindowData* data = nullptr; + s_X11.findContext(s_X11.display, xWin, s_X11.dataID, (XPointer*)&data); + s_X11.destroyWindow(s_X11.display, xWin); + data->used = false; +} + +PalResult xMinimizeWindow(PalWindow* window) +{ + if (!(s_Video.features & PAL_VIDEO_FEATURE_WINDOW_SET_STATE)) { + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; + } + + Window xWin = FROM_PAL_HANDLE(Window, window); + XWindowAttributes attr; + if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { + return PAL_RESULT_INVALID_WINDOW; + } + + s_X11.iconifyWindow(s_X11.display, xWin, s_X11.screen); + return PAL_RESULT_SUCCESS; +} + +PalResult xMaximizeWindow(PalWindow* window) +{ + if (!(s_Video.features & PAL_VIDEO_FEATURE_WINDOW_SET_STATE)) { + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; + } + + Window xWin = FROM_PAL_HANDLE(Window, window); + XWindowAttributes attr; + if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { + return PAL_RESULT_INVALID_WINDOW; + } + + xSendWMEvent( + xWin, + s_X11Atoms._NET_WM_STATE, + s_X11Atoms._NET_WM_STATE_MAXIMIZED_VERT, + s_X11Atoms._NET_WM_STATE_MAXIMIZED_HORZ, + 1, + 0, + true); // _NET_WM_STATE_ADD + + return PAL_RESULT_SUCCESS; +} + +PalResult xRestoreWindow(PalWindow* window) +{ + if (!(s_Video.features & PAL_VIDEO_FEATURE_WINDOW_SET_STATE)) { + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; + } + + Window xWin = FROM_PAL_HANDLE(Window, window); + XWindowAttributes attr; + if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { + return PAL_RESULT_INVALID_WINDOW; + } + + // since we have no fixed way to restore the window + // we just restore from minimized and maximized state + xSendWMEvent( + xWin, + s_X11Atoms._NET_WM_STATE, + s_X11Atoms._NET_WM_STATE_MAXIMIZED_VERT, + s_X11Atoms._NET_WM_STATE_MAXIMIZED_HORZ, + 1, + 0, + false); // _NET_WM_STATE_REMOVE + + s_X11.mapRaised(s_X11.display, xWin); + + return PAL_RESULT_SUCCESS; +} + +PalResult xShowWindow(PalWindow* window) +{ + Window xWin = FROM_PAL_HANDLE(Window, window); + XWindowAttributes attr; + if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { + return PAL_RESULT_INVALID_WINDOW; + } + + s_X11.mapWindow(s_X11.display, xWin); + return PAL_RESULT_SUCCESS; +} + +PalResult xHideWindow(PalWindow* window) +{ + Window xWin = FROM_PAL_HANDLE(Window, window); + XWindowAttributes attr; + if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { + return PAL_RESULT_INVALID_WINDOW; + } + + s_X11.unmapWindow(s_X11.display, xWin); + return PAL_RESULT_SUCCESS; +} + +PalResult xFlashWindow( + PalWindow* window, + const PalFlashInfo* info) +{ + if (info->flags & PAL_FLASH_CAPTION) { + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; + } + + Window xWin = FROM_PAL_HANDLE(Window, window); + XWindowAttributes attr; + if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { + return PAL_RESULT_INVALID_WINDOW; + } + + bool add = false; + if (info->flags & PAL_FLASH_TRAY) { + add = true; + } + + // check if modern flashing is supported + if (s_X11Atoms._NET_WM_STATE_DEMANDS_ATTENTIONS) { + xSendWMEvent( + xWin, + s_X11Atoms._NET_WM_STATE, + s_X11Atoms._NET_WM_STATE_DEMANDS_ATTENTIONS, + 0, + 0, + 0, + add); // _NET_WM_STATE_ADD + + } else { + // legacy mode + XWMHints* hints = s_X11.getWMHints(s_X11.display, xWin); + if (!hints) { + hints = s_X11.allocWMHints(); + if (!hints) { + return PAL_RESULT_OUT_OF_MEMORY; + } + + if (add) { + hints->flags |= XUrgencyHint; + } else { + hints->flags &= ~XUrgencyHint; + } + s_X11.setWMHints(s_X11.display, xWin, hints); + s_X11.free(hints); + } + } + + return PAL_RESULT_SUCCESS; +} + +PalResult xGetWindowStyle( + PalWindow* window, + PalWindowStyle* outStyle) +{ + // Window Manager quirks + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; +} + +PalResult xGetWindowMonitor( + PalWindow* window, + PalMonitor** outMonitor) +{ + Window xWin = FROM_PAL_HANDLE(Window, window); + XWindowAttributes attr; + if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { + return PAL_RESULT_INVALID_WINDOW; + } + + XRRScreenResources* resources = nullptr; + resources = s_X11.getScreenResources(s_X11.display, s_X11.root); + + for (int i = 0; i < resources->noutput; ++i) { + RROutput output = resources->outputs[i]; + // clang-format off + XRROutputInfo* info = s_X11.getOutputInfo(s_X11.display, resources, output); + // clang-format on + + if (info->connection == RR_Connected && info->crtc != None) { + // clang-format off + XRRCrtcInfo* crtc = s_X11.getCrtcInfo(s_X11.display, resources, info->crtc); + // clang-format on + + // check bounds to see if window is on the monitor + if (attr.x >= crtc->x && attr.x < crtc->x + crtc->width && + attr.y >= crtc->y && attr.y < crtc->y + crtc->height) { + // found monitor + *outMonitor = TO_PAL_HANDLE(PalMonitor, output); + break; + } + s_X11.freeCrtcInfo(crtc); + } + s_X11.freeOutputInfo(info); + } + s_X11.freeScreenResources(resources); + return PAL_RESULT_SUCCESS; +} + +PalResult xGetWindowTitle( + PalWindow* window, + Uint64 bufferSize, + Uint64* outSize, + char* outBuffer) +{ + Window xWin = FROM_PAL_HANDLE(Window, window); + XWindowAttributes attr; + if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { + return PAL_RESULT_INVALID_WINDOW; + } + + if (!outBuffer || bufferSize <= 0) { + return PAL_RESULT_INSUFFICIENT_BUFFER; + } + + if (s_X11Atoms.unicodeTitle) { + Atom type; + int format; + unsigned long count, bytesAfter; + unsigned char* prop = nullptr; + s_X11.getWindowProperty( + s_X11.display, + xWin, + s_X11Atoms._NET_WM_NAME, + 0, + (~0L), + False, + s_X11Atoms.UTF8_STRING, + &type, + &format, + &count, + &bytesAfter, + &prop); + + if (bufferSize >= count) { + strcpy(outBuffer, (const char*)prop); + } else { + // copy up to the limiit of the supplied buffer + strncpy(outBuffer, (const char*)prop, bufferSize); + } + s_X11.free(prop); + + } else { + XTextProperty text; + if (!s_X11.getWMName(s_X11.display, xWin, &text)) { + return PAL_RESULT_INVALID_WINDOW; + } + + if (bufferSize >= text.nitems) { + strcpy(outBuffer, (const char*)text.value); + } else { + // copy up to the limiit of the supplied buffer + strncpy(outBuffer, (const char*)text.value, bufferSize); + } + s_X11.free(text.value); + } + + return PAL_RESULT_SUCCESS; +} + +PalResult xGetWindowPos( + PalWindow* window, + Int32* x, + Int32* y) +{ + Window xWin = FROM_PAL_HANDLE(Window, window); + XWindowAttributes attr; + if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { + return PAL_RESULT_INVALID_WINDOW; + } + + if (x) { + *x = attr.x; + } + + if (y) { + *y = attr.y; + } + + return PAL_RESULT_SUCCESS; +} + +PalResult xGetWindowSize( + PalWindow* window, + Uint32* width, + Uint32* height) +{ + Window xWin = FROM_PAL_HANDLE(Window, window); + XWindowAttributes attr; + if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { + return PAL_RESULT_INVALID_WINDOW; + } + + if (width) { + *width = attr.width; + } + + if (height) { + *height = attr.height; + } + + return PAL_RESULT_SUCCESS; +} + +PalResult xGetWindowState( + PalWindow* window, + PalWindowState* outState) +{ + Window xWin = FROM_PAL_HANDLE(Window, window); + XWindowAttributes attr; + if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { + return PAL_RESULT_INVALID_WINDOW; + } + + Atom type; + int format; + unsigned long count, bytesAfter; + unsigned char* props = nullptr; + s_X11.getWindowProperty( + s_X11.display, + xWin, + s_X11Atoms._NET_WM_STATE, + 0, + (~0L), + False, + XA_ATOM, + &type, + &format, + &count, + &bytesAfter, + &props); + + PalWindowState state = PAL_WINDOW_STATE_RESTORED; + for (unsigned long i = 0; i < count; ++i) { + if (props[i] == s_X11Atoms._NET_WM_STATE_MAXIMIZED_HORZ) { + state = PAL_WINDOW_STATE_MAXIMIZED; + } + + if (props[i] == s_X11Atoms._NET_WM_STATE_MAXIMIZED_VERT) { + state = PAL_WINDOW_STATE_MAXIMIZED; + } + + if (props[i] == s_X11Atoms._NET_WM_STATE_HIDDEN) { + state = PAL_WINDOW_STATE_MINIMIZED; + } + } + + s_X11.free(props); + *outState = state; + return PAL_RESULT_SUCCESS; +} + +bool xIsWindowVisible(PalWindow* window) +{ + Window xWin = FROM_PAL_HANDLE(Window, window); + XWindowAttributes attr; + if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { + return false; + } + + return attr.map_state == IsViewable; +} + +PalWindow* xGetFocusWindow() +{ + Window window; + int tmp; + s_X11.getInputFocus(s_X11.display, &window, &tmp); + Window xWin = FROM_PAL_HANDLE(Window, window); + + if (xWin == s_X11.root) { + return nullptr; + } + return TO_PAL_HANDLE(PalWindow, window); +} + +PalWindowHandleInfo xGetWindowHandleInfo(PalWindow* window) +{ + PalWindowHandleInfo info; + info.nativeDisplay = (void*)s_X11.display; + info.nativeWindow = (void*)window; + return info; +} + +static PalResult xSetWindowOpacity( + PalWindow* window, + float opacity) +{ + XErrorHandler old = s_X11.setErrorHandler(xErrorHandler); + unsigned long value = (unsigned long)(opacity * 0xFFFFFFFFUL + 0.5f); + + s_X11.changeProperty( + s_X11.display, + FROM_PAL_HANDLE(Window, window), + s_X11Atoms._NET_WM_WINDOW_OPACITY, + XA_CARDINAL, + 32, + PropModeReplace, + (unsigned char*)&value, + 1); + + s_X11.sync(s_X11.display, False); + s_X11.setErrorHandler(old); + if (s_X11.error) { + // technically, this is the only error that can occur + return PAL_RESULT_INVALID_WINDOW; + } + + return PAL_RESULT_SUCCESS; +} + +PalResult xSetWindowStyle( + PalWindow* window, + PalWindowStyle style) +{ + // Window Manager quirks + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; +} + +PalResult xSetWindowTitle( + PalWindow* window, + const char* title) +{ + Window xWin = FROM_PAL_HANDLE(Window, window); + XWindowAttributes attr; + if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { + return PAL_RESULT_INVALID_WINDOW; + } + + if (s_X11Atoms.unicodeTitle) { + s_X11.changeProperty( + s_X11.display, + xWin, + s_X11Atoms._NET_WM_NAME, + s_X11Atoms.UTF8_STRING, + 8, // unsigned char + PropModeReplace, + title, + strlen(title)); + + } else { + s_X11.storeName(s_X11.display, xWin, title); + } + + s_X11.flush(s_X11.display); + return PAL_RESULT_SUCCESS; +} + +PalResult xSetWindowPos( + PalWindow* window, + Int32 x, + Int32 y) +{ + Window xWin = FROM_PAL_HANDLE(Window, window); + XWindowAttributes attr; + if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { + return PAL_RESULT_INVALID_WINDOW; + } + + s_X11.moveWindow(s_X11.display, xWin, x, y); + s_X11.flush(s_X11.display); + return PAL_RESULT_SUCCESS; +} + +PalResult xSetWindowSize( + PalWindow* window, + Uint32 width, + Uint32 height) +{ + Window xWin = FROM_PAL_HANDLE(Window, window); + XWindowAttributes attr; + if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { + return PAL_RESULT_INVALID_WINDOW; + } + + // X11 does not allow users resize programaticaly + // if the window is not resizable. + // so we hack it by making the window resizable and resizing + // then revert back. + XSizeHints hints; + long tmp; + s_X11.getWMNormalHints(s_X11.display, xWin, &hints, &tmp); + if ((hints.flags & PMinSize) && (hints.flags & PMaxSize)) { + hints.flags &= ~PMinSize; + hints.flags &= ~PMaxSize; + s_X11.resizeWindow(s_X11.display, xWin, width, height); + s_X11.flush(s_X11.display); + + // revert + hints.flags |= PMinSize; + hints.flags |= PMaxSize; + hints.min_width = hints.max_width = width; + hints.min_height = hints.max_height = height; + + } else { + // window is already resizable + s_X11.resizeWindow(s_X11.display, xWin, width, height); + } + + s_X11.flush(s_X11.display); + return PAL_RESULT_SUCCESS; +} + +PalResult xSetFocusWindow(PalWindow* window) +{ + Window xWin = FROM_PAL_HANDLE(Window, window); + XWindowAttributes attr; + if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { + return PAL_RESULT_INVALID_WINDOW; + } + + if (s_X11Atoms._NET_ACTIVE_WINDOW) { + xSendWMEvent( + xWin, + s_X11Atoms._NET_ACTIVE_WINDOW, + CurrentTime, + 0, + 0, + 0, + true); // 1 + + } else { + s_X11.setInputFocus(s_X11.display, xWin, RevertToParent, CurrentTime); + } + + return PAL_RESULT_SUCCESS; +} + +PalResult xCreateIcon( + const PalIconCreateInfo* info, + PalIcon** outIcon) +{ + Uint64 totalPixels = 2 + (Uint64)(info->width * info->height); + Uint64 totalBytes = sizeof(unsigned long) * totalPixels; + + unsigned long* icon = palAllocate(s_Video.allocator, totalBytes, 0); + if (!icon) { + return PAL_RESULT_OUT_OF_MEMORY; + } + + // store width and height and populate data with icon pixels + // [width][height][pixels] + icon[0] = (unsigned long)info->width; + icon[1] = (unsigned long)info->height; + + // convert from RGBA8 to ARGB32 + for (int i = 0; i < info->width * info->height; i++) { + Uint8 r = info->pixels[i * 4 + 0]; // Red + Uint8 g = info->pixels[i * 4 + 1]; // Green + Uint8 b = info->pixels[i * 4 + 2]; // Blue + Uint8 a = info->pixels[i * 4 + 3]; // Alpha + + // clang-format off + icon[2 + i] = ((unsigned long)a << 24) | + ((unsigned long)r << 16) | + ((unsigned long)g << 8) | + ((unsigned long)b); + // clang-format on + } + + *outIcon = TO_PAL_HANDLE(PalIcon, icon); + return PAL_RESULT_SUCCESS; +} + +void xDestroyIcon(PalIcon* icon) +{ + if (icon) { + palFree(s_Video.allocator, icon); + } +} + +PalResult xSetWindowIcon( + PalWindow* window, + PalIcon* icon) +{ + WindowData* winData = nullptr; + Window xWin = FROM_PAL_HANDLE(Window, window); + s_X11.findContext(s_X11.display, xWin, s_X11.dataID, (XPointer*)&winData); + if (!winData) { + return PAL_RESULT_INVALID_WINDOW; + } + + unsigned long* iconData = FROM_PAL_HANDLE(unsigned long*, icon); + Uint64 totalPixels = 2 + iconData[0] * iconData[1]; + s_X11.changeProperty( + s_X11.display, + xWin, + s_X11Atoms._NET_WM_ICON, + XA_CARDINAL, + 32, + PropModeReplace, + (unsigned char*)iconData, + (int)totalPixels); + + s_X11.flush(s_X11.display); + return PAL_RESULT_SUCCESS; +} + +PalResult xCreateCursor( + const PalCursorCreateInfo* info, + PalCursor** outCursor) +{ + XcursorImage* image = s_X11.cursorImageCreate(info->width, info->height); + image->xhot = info->xHotspot; + image->yhot = info->yHotspot; + + // convert from RGBA8 to ARGB32 + for (int i = 0; i < info->width * info->height; i++) { + Uint8 r = info->pixels[i * 4 + 0]; // Red + Uint8 g = info->pixels[i * 4 + 1]; // Green + Uint8 b = info->pixels[i * 4 + 2]; // Blue + Uint8 a = info->pixels[i * 4 + 3]; // Alpha + + // clang-format off + image->pixels[i] = ((unsigned long)a << 24) | + ((unsigned long)r << 16) | + ((unsigned long)g << 8) | + ((unsigned long)b); + // clang-format on + } + + Cursor cursor = s_X11.cursorImageLoadCursor(s_X11.display, image); + if (!cursor) { + return PAL_RESULT_PLATFORM_FAILURE; + } + + s_X11.cursorImageDestroy(image); + *outCursor = TO_PAL_HANDLE(PalCursor, cursor); + return PAL_RESULT_SUCCESS; +} + +PalResult xCreateCursorFrom( + PalCursorType type, + PalCursor** outCursor) +{ + int shape; + Cursor cursor; + switch (type) { + case PAL_CURSOR_ARROW: { + shape = XC_left_ptr; + break; + } + + case PAL_CURSOR_HAND: { + shape = XC_hand2; + break; + } + + case PAL_CURSOR_CROSS: { + shape = XC_cross; + break; + } + + case PAL_CURSOR_IBEAM: { + shape = XC_xterm; + break; + } + + case PAL_CURSOR_WAIT: { + shape = XC_watch; + break; + } + + default: { + return PAL_RESULT_INVALID_ARGUMENT; + } + } + + cursor = s_X11.createFontCursor(s_X11.display, shape); + *outCursor = TO_PAL_HANDLE(PalCursor, cursor); + return PAL_RESULT_SUCCESS; +} + +void xDestroyCursor(PalCursor* cursor) +{ + s_X11.freeCursor(s_X11.display, FROM_PAL_HANDLE(Cursor, cursor)); +} + +void xShowCursor(bool show) +{ + // X11 does not have a single function to show or hide cursor globally + // so we query on windows and set the cursor for each + // The limitation is any window not attached to PAL will not be affected + for (int i = 0; i < s_Video.maxWindowData; i++) { + WindowData* data = &s_Video.windowData[i]; + Window xWin = FROM_PAL_HANDLE(Window, data->window); + if (show) { + // we check if the window has a valid cursor + // if not we use the root windows cursor + Cursor cursor = FROM_PAL_HANDLE(Cursor, data->cursor); + if (cursor) { + s_X11.defineCursor(s_X11.display, xWin, cursor); + } else { + s_X11.undefineCursor(s_X11.display, xWin); + } + + } else { + s_X11.defineCursor(s_X11.display, xWin, s_X11.hiddenCursor); + } + s_X11.flush(s_X11.display); + } +} + +PalResult xClipCursor( + PalWindow* window, + bool clip) +{ + Window xWin = FROM_PAL_HANDLE(Window, window); + XWindowAttributes attr; + if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { + return PAL_RESULT_INVALID_WINDOW; + } + + if (clip) { + s_X11.grabPointer( + s_X11.display, + xWin, + True, + 0, + GrabModeAsync, + GrabModeAsync, + xWin, + None, + CurrentTime); + + } else { + s_X11.ungrabPointer(s_X11.display, CurrentTime); + } + + return PAL_RESULT_SUCCESS; +} + +PalResult xGetCursorPos( + PalWindow* window, + Int32* x, + Int32* y) +{ + Window xWin = FROM_PAL_HANDLE(Window, window); + XWindowAttributes attr; + if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { + return PAL_RESULT_INVALID_WINDOW; + } + + Window root, rootChild; + int rootX, rootY, winX, winY; + unsigned int mask; + s_X11.queryPointer( + s_X11.display, + xWin, + &root, + &rootChild, + &rootX, + &rootY, + &winX, + &winY, + &mask); + + if (x) { + *x = winX; + } + + if (y) { + *y = winY; + } + + return PAL_RESULT_SUCCESS; +} + +PalResult xSetCursorPos( + PalWindow* window, + Int32 x, + Int32 y) +{ + Window xWin = FROM_PAL_HANDLE(Window, window); + XWindowAttributes attr; + if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { + return PAL_RESULT_INVALID_WINDOW; + } + + s_X11.warpPointer(s_X11.display, None, xWin, 0, 0, 0, 0, x, y); + + s_X11.flush(s_X11.display); + return PAL_RESULT_SUCCESS; +} + +PalResult xSetWindowCursor( + PalWindow* window, + PalCursor* cursor) +{ + Window xWin = FROM_PAL_HANDLE(Window, window); + XWindowAttributes attr; + if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { + return PAL_RESULT_INVALID_WINDOW; + } + + Window xCursor = FROM_PAL_HANDLE(Cursor, cursor); + if (xCursor) { + s_X11.defineCursor(s_X11.display, xWin, xCursor); + // cache the cursor. Show or hide cursor needs it + WindowData* data = nullptr; + s_X11.findContext(s_X11.display, xWin, s_X11.dataID, (XPointer*)&data); + data->cursor = cursor; + + } else { + s_X11.undefineCursor(s_X11.display, xWin); + } + + s_X11.flush(s_X11.display); + return PAL_RESULT_SUCCESS; +} + +static Backend s_XBackend = { + .shutdownVideo = xShutdownVideo, + .updateVideo = xUpdateVideo, + .enumerateMonitors = xEnumerateMonitors, + .getMonitorInfo = xGetMonitorInfo, + .getPrimaryMonitor = xGetPrimaryMonitor, + .enumerateMonitorModes = xEnumerateMonitorModes, + .getCurrentMonitorMode = xGetCurrentMonitorMode, + .setMonitorMode = xSetMonitorMode, + .validateMonitorMode = xValidateMonitorMode, + .setMonitorOrientation = xSetMonitorOrientation, + + .createWindow = xCreateWindow, + .destroyWindow = xDestroyWindow, + .maximizeWindow = xMaximizeWindow, + .minimizeWindow = xMinimizeWindow, + .restoreWindow = xRestoreWindow, + .showWindow = xShowWindow, + .hideWindow = xHideWindow, + .xFlashWindow = xFlashWindow, + .getWindowStyle = xGetWindowStyle, + .getWindowMonitor = xGetWindowMonitor, + .getWindowTitle = xGetWindowTitle, + .getWindowPos = xGetWindowPos, + .getWindowSize = xGetWindowSize, + .getWindowState = xGetWindowState, + .isWindowVisible = xIsWindowVisible, + .getFocusWindow = xGetFocusWindow, + .getWindowHandleInfo = xGetWindowHandleInfo, + .setWindowOpacity = xSetWindowOpacity, + .setWindowStyle = xSetWindowStyle, + .setWindowTitle = xSetWindowTitle, + .setWindowPos = xSetWindowPos, + .setWindowSize = xSetWindowSize, + .setFocusWindow = xSetFocusWindow, + + .createIcon = xCreateIcon, + .destroyIcon = xDestroyIcon, + .setWindowIcon = xSetWindowIcon, + + .createCursor = xCreateCursor, + .createCursorFrom = xCreateCursorFrom, + .destroyCursor = xDestroyCursor, + .showCursor = xShowCursor, + .clipCursor = xClipCursor, + .getCursorPos = xGetCursorPos, + .setCursorPos = xSetCursorPos, + .setWindowCursor = xSetWindowCursor}; + +#pragma endregions + +// ================================================== +// Public API +// ================================================== + +PalResult PAL_CALL palInitVideo( + const PalAllocator* allocator, + PalEventDriver* eventDriver) +{ + if (s_Video.initialized) { + return PAL_RESULT_SUCCESS; + } + + if (allocator && (!allocator->allocate || !allocator->free)) { + return PAL_RESULT_INVALID_ALLOCATOR; + } + + // get backend type + bool x11 = true; + const char* session = getenv("XDG_SESSION_TYPE"); + if (session) { + if (strcmp(session, "wayland") == 0) { + x11 = false; + } + } + + s_Video.maxMonitorData = 16; // initial size + s_Video.maxWindowData = 32; // initial size + + s_Video.windowData = palAllocate( + s_Video.allocator, + sizeof(WindowData) * s_Video.maxWindowData, + 0); + + s_Video.monitorData = palAllocate( + s_Video.allocator, + sizeof(MonitorData) * s_Video.maxMonitorData, + 0); + + if (!s_Video.monitorData || !s_Video.windowData) { + return PAL_RESULT_OUT_OF_MEMORY; + } + + if (x11) { + PalResult ret = xInitVideo(); + if (ret != PAL_RESULT_SUCCESS) { + return ret; + } + s_Video.backend = &s_XBackend; + } + + // we load EGL as well + s_Egl.handle = dlopen("libEGL.so", RTLD_LAZY); + if (s_Egl.handle) { + eglGetProcAddressFn load = nullptr; + load = (eglGetProcAddressFn)dlsym(s_Egl.handle, "eglGetProcAddress"); + + s_Egl.eglInitialize = (eglInitializeFn)load("eglInitialize"); + s_Egl.eglTerminate = (eglTerminateFn)load("eglTerminate"); + s_Egl.eglGetDisplay = (eglGetDisplayFn)load("eglGetDisplay"); + s_Egl.eglChooseConfig = (eglChooseConfigFn)load("eglChooseConfig"); + s_Egl.eglGetError = (eglGetErrorFn)load("eglGetError"); + s_Egl.eglBindAPI = (eglBindAPIFn)load("eglBindAPI"); + s_Egl.eglGetConfigs = (eglGetConfigsFn)load("eglGetConfigs"); + + // clang-format off + s_Egl.eglGetConfigAttrib = (eglGetConfigAttribFn)load("eglGetConfigAttrib"); + // clang-format on + } + + s_Video.allocator = allocator; + s_Video.eventDriver = eventDriver; + s_Video.initialized = true; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palShutdownVideo() +{ + if (s_Video.initialized) { + s_Video.backend->shutdownVideo(); + palFree(s_Video.allocator, s_Video.windowData); + palFree(s_Video.allocator, s_Video.monitorData); + + if (s_Egl.handle) { + dlclose(s_Egl.handle); + } + s_Video.initialized = false; + } +} + +void PAL_CALL palUpdateVideo() +{ + if (s_Video.initialized) { + s_Video.backend->updateVideo(); + } +} + +PalVideoFeatures PAL_CALL palGetVideoFeatures() +{ + if (!s_Video.initialized) { + return 0; + } + + return s_Video.features; +} + +PalResult PAL_CALL palSetFBConfig( + const int index, + PalFBConfigBackend backend) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + // X11 can used GLX and EGL + if (backend == PAL_CONFIG_BACKEND_WGL) { + return PAL_RESULT_INVALID_FBCONFIG_BACKEND; + } + + // we try to create a colormap to see if the index is valid + if (backend == PAL_CONFIG_BACKEND_GLX) { + return glxBackend(); + + } else if ( + backend == PAL_CONFIG_BACKEND_EGL || + backend == PAL_CONFIG_BACKEND_PAL_OPENGL) { + if (s_X11.display) { + // we are on X11 + return eglXBackend(index); + } + } +} + +PalResult PAL_CALL palEnumerateMonitors( + Int32* count, + PalMonitor** outMonitors) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!count) { + return PAL_RESULT_NULL_POINTER; + } + + if (*count == 0 && outMonitors) { + return PAL_RESULT_INSUFFICIENT_BUFFER; + } + + return s_Video.backend->enumerateMonitors(count, outMonitors); +} + +PalResult PAL_CALL palGetPrimaryMonitor(PalMonitor** outMonitor) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!outMonitor) { + return PAL_RESULT_NULL_POINTER; + } + + return s_Video.backend->getPrimaryMonitor(outMonitor); +} + +PalResult PAL_CALL palGetMonitorInfo( + PalMonitor* monitor, + PalMonitorInfo* info) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!info) { + return PAL_RESULT_NULL_POINTER; + } + + return s_Video.backend->getMonitorInfo(monitor, info); +} + +PalResult PAL_CALL palEnumerateMonitorModes( + PalMonitor* monitor, + Int32* count, + PalMonitorMode* modes) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!monitor || !count) { + return PAL_RESULT_NULL_POINTER; + } + + if (*count == 0 && modes) { + return PAL_RESULT_INSUFFICIENT_BUFFER; + } + + // clang-format off + + PalResult ret = s_Video.backend->enumerateMonitorModes(monitor, count, modes); + // clang-format on + + if (ret == PAL_RESULT_SUCCESS && modes) { + // sort the modes so that they are lowest to highest + qsort(modes, *count, sizeof(PalMonitorMode), compareModes); + } + + return ret; +} + +PalResult PAL_CALL palGetCurrentMonitorMode( + PalMonitor* monitor, + PalMonitorMode* mode) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!monitor || !mode) { + return PAL_RESULT_NULL_POINTER; + } + + return s_Video.backend->getCurrentMonitorMode(monitor, mode); +} + +PalResult PAL_CALL palSetMonitorMode( + PalMonitor* monitor, + PalMonitorMode* mode) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!monitor || !mode) { + return PAL_RESULT_NULL_POINTER; + } + + return s_Video.backend->setMonitorMode(monitor, mode); +} + +PalResult PAL_CALL palValidateMonitorMode( + PalMonitor* monitor, + PalMonitorMode* mode) +{ + + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!monitor || !mode) { + return PAL_RESULT_NULL_POINTER; + } + + return s_Video.backend->validateMonitorMode(monitor, mode); +} + +PalResult PAL_CALL palSetMonitorOrientation( + PalMonitor* monitor, + PalOrientation orientation) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!monitor) { + return PAL_RESULT_NULL_POINTER; + } + + return s_Video.backend->setMonitorOrientation(monitor, orientation); +} + +// ================================================== +// Window +// ================================================== + +PalResult PAL_CALL palCreateWindow( + const PalWindowCreateInfo* info, + PalWindow** outWindow) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!info || !outWindow) { + return PAL_RESULT_NULL_POINTER; + } + + if (info->style & PAL_WINDOW_STYLE_NO_MINIMIZEBOX) { + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; + } + + if (info->style & PAL_WINDOW_STYLE_NO_MAXIMIZEBOX) { + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; + } + + return s_Video.backend->createWindow(info, outWindow); +} + +void PAL_CALL palDestroyWindow(PalWindow* window) +{ + if (s_Video.initialized && window) { + return s_Video.backend->destroyWindow(window); + } +} + +PalResult PAL_CALL palMinimizeWindow(PalWindow* window) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!window) { + return PAL_RESULT_NULL_POINTER; + } + + return s_Video.backend->maximizeWindow(window); +} + +PalResult PAL_CALL palMaximizeWindow(PalWindow* window) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!window) { + return PAL_RESULT_NULL_POINTER; + } + + return s_Video.backend->minimizeWindow(window); +} + +PalResult PAL_CALL palRestoreWindow(PalWindow* window) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!window) { + return PAL_RESULT_NULL_POINTER; + } + + return s_Video.backend->restoreWindow(window); +} + +PalResult PAL_CALL palShowWindow(PalWindow* window) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!window) { + return PAL_RESULT_NULL_POINTER; + } + + return s_Video.backend->showWindow(window); +} + +PalResult PAL_CALL palHideWindow(PalWindow* window) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!window) { + return PAL_RESULT_NULL_POINTER; + } + + return s_Video.backend->hideWindow(window); +} + +PalResult PAL_CALL palFlashWindow( + PalWindow* window, + const PalFlashInfo* info) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!window || !info) { + return PAL_RESULT_NULL_POINTER; + } + + return s_Video.backend->xFlashWindow(window, info); +} + +PalResult PAL_CALL palGetWindowStyle( + PalWindow* window, + PalWindowStyle* outStyle) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!window || !outStyle) { + return PAL_RESULT_NULL_POINTER; + } + + return s_Video.backend->getWindowStyle(window, outStyle); +} + +PalResult PAL_CALL palGetWindowMonitor( + PalWindow* window, + PalMonitor** outMonitor) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!window || !outMonitor) { + return PAL_RESULT_NULL_POINTER; + } + + return s_Video.backend->getWindowMonitor(window, outMonitor); +} + +PalResult PAL_CALL palGetWindowTitle( + PalWindow* window, + Uint64 bufferSize, + Uint64* outSize, + char* outBuffer) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!window || !outBuffer) { + return PAL_RESULT_NULL_POINTER; + } + + // clang-format off + return s_Video.backend->getWindowTitle( + window, + bufferSize, + outSize, + outBuffer); + // clang-format on +} + +PalResult PAL_CALL palGetWindowPos( + PalWindow* window, + Int32* x, + Int32* y) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!window) { + return PAL_RESULT_NULL_POINTER; + } + + return s_Video.backend->getWindowPos(window, x, y); +} + +PalResult PAL_CALL palGetWindowSize( + PalWindow* window, + Uint32* width, + Uint32* height) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!window) { + return PAL_RESULT_NULL_POINTER; + } + + return s_Video.backend->getWindowSize(window, width, height); +} + +PalResult PAL_CALL palGetWindowState( + PalWindow* window, + PalWindowState* outState) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!window || !outState) { + return PAL_RESULT_NULL_POINTER; + } + + return s_Video.backend->getWindowState(window, outState); +} + +const bool* PAL_CALL palGetKeycodeState() +{ + if (!s_Video.initialized) { + return nullptr; + } + return s_Keyboard.keycodeState; +} + +const bool* PAL_CALL palGetScancodeState() +{ + if (!s_Video.initialized) { + return nullptr; + } + return s_Keyboard.scancodeState; +} + +const bool* PAL_CALL palGetMouseState() +{ + if (!s_Video.initialized) { + return nullptr; + } + return s_Mouse.state; +} + +void PAL_CALL palGetMouseDelta( + Int32* dx, + Int32* dy) +{ + if (!s_Video.initialized) { + return; + } + + if (dx) { + *dx = s_Mouse.dx; + } + + if (dy) { + *dy = s_Mouse.dy; + } +} + +void PAL_CALL palGetMouseWheelDelta( + Int32* dx, + Int32* dy) +{ + if (!s_Video.initialized) { + return; + } + + if (dx) { + *dx = s_Mouse.WheelX; + } + + if (dy) { + *dy = s_Mouse.WheelY; + } +} + +bool PAL_CALL palIsWindowVisible(PalWindow* window) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!window) { + return PAL_RESULT_NULL_POINTER; + } + + return s_Video.backend->isWindowVisible(window); +} + +PalWindow* PAL_CALL palGetFocusWindow() +{ + if (!s_Video.initialized) { + return nullptr; + } + + return s_Video.backend->getFocusWindow(); +} + +PalWindowHandleInfo PAL_CALL palGetWindowHandleInfo(PalWindow* window) +{ + if (s_Video.initialized) { + return s_Video.backend->getWindowHandleInfo(window); + } +} + +PalResult PAL_CALL palSetWindowOpacity( + PalWindow* window, + float opacity) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!window) { + return PAL_RESULT_NULL_POINTER; + } + + if (!(s_Video.features & PAL_VIDEO_FEATURE_TRANSPARENT_WINDOW)) { + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; + } + + if (opacity < 0.0f) { + opacity = 0.0f; + } + + if (opacity > 1.0f) { + opacity = 1.0f; + } + + return s_Video.backend->setWindowOpacity(window, opacity); +} + +PalResult PAL_CALL palSetWindowStyle( + PalWindow* window, + PalWindowStyle style) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!window) { + return PAL_RESULT_NULL_POINTER; + } + + return s_Video.backend->setWindowStyle(window, style); +} + +PalResult PAL_CALL palSetWindowTitle( + PalWindow* window, + const char* title) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!window || !title) { + return PAL_RESULT_NULL_POINTER; + } + + return s_Video.backend->setWindowTitle(window, title); +} + +PalResult PAL_CALL palSetWindowPos( + PalWindow* window, + Int32 x, + Int32 y) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!window) { + return PAL_RESULT_NULL_POINTER; + } + + return s_Video.backend->setWindowPos(window, x, y); +} + +PalResult PAL_CALL palSetWindowSize( + PalWindow* window, + Uint32 width, + Uint32 height) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!window) { + return PAL_RESULT_NULL_POINTER; + } + + return s_Video.backend->setWindowSize(window, width, height); +} + +PalResult PAL_CALL palSetFocusWindow(PalWindow* window) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!window) { + return PAL_RESULT_NULL_POINTER; + } + + return s_Video.backend->setFocusWindow(window); +} + +// ================================================== +// Icon +// ================================================== + +PalResult PAL_CALL palCreateIcon( + const PalIconCreateInfo* info, + PalIcon** outIcon) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!info || !outIcon) { + return PAL_RESULT_NULL_POINTER; + } + + return s_Video.backend->createIcon(info, outIcon); +} + +void PAL_CALL palDestroyIcon(PalIcon* icon) +{ + if (s_Video.initialized && icon) { + s_Video.backend->destroyIcon(icon); + } +} + +PalResult PAL_CALL palSetWindowIcon( + PalWindow* window, + PalIcon* icon) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!window) { + return PAL_RESULT_NULL_POINTER; + } + + return s_Video.backend->setWindowIcon(window, icon); +} + +// ================================================== +// Cursor +// ================================================== + +PalResult PAL_CALL palCreateCursor( + const PalCursorCreateInfo* info, + PalCursor** outCursor) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!info || !outCursor) { + return PAL_RESULT_NULL_POINTER; + } + + return s_Video.backend->createCursor(info, outCursor); +} + +PalResult PAL_CALL palCreateCursorFrom( + PalCursorType type, + PalCursor** outCursor) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!outCursor) { + return PAL_RESULT_NULL_POINTER; + } + + return s_Video.backend->createCursorFrom(type, outCursor); +} + +void PAL_CALL palDestroyCursor(PalCursor* cursor) +{ + if (s_Video.initialized && cursor) { + s_Video.backend->destroyCursor(cursor); + } +} + +void PAL_CALL palShowCursor(bool show) +{ + if (s_Video.initialized) { + s_Video.backend->showCursor(show); + } +} + +PalResult PAL_CALL palClipCursor( + PalWindow* window, + bool clip) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!window) { + return PAL_RESULT_NULL_POINTER; + } + + return s_Video.backend->clipCursor(window, clip); +} + +PalResult PAL_CALL palGetCursorPos( + PalWindow* window, + Int32* x, + Int32* y) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!window) { + return PAL_RESULT_NULL_POINTER; + } + + return s_Video.backend->getCursorPos(window, x, y); +} + +PalResult PAL_CALL palSetCursorPos( + PalWindow* window, + Int32 x, + Int32 y) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!window) { + return PAL_RESULT_NULL_POINTER; + } + + return s_Video.backend->setCursorPos(window, x, y); +} + +PalResult PAL_CALL palSetWindowCursor( + PalWindow* window, + PalCursor* cursor) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!window) { + return PAL_RESULT_NULL_POINTER; + } + + return s_Video.backend->setWindowCursor(window, cursor); +} \ No newline at end of file diff --git a/src/video/pal_video_win32.c b/src/video/pal_video_win32.c index 4c8041a..2fe23e0 100644 --- a/src/video/pal_video_win32.c +++ b/src/video/pal_video_win32.c @@ -79,8 +79,27 @@ typedef HBITMAP(WINAPI* CreateBitmapFn)( typedef BOOL(WINAPI* DeleteObjectFn)(HGDIOBJ); +typedef int(WINAPI* DescribePixelFormatFn)( + HDC, + int, + UINT, + LPPIXELFORMATDESCRIPTOR); + +typedef BOOL(WINAPI* SetPixelFormatFn)( + HDC, + int, + CONST PIXELFORMATDESCRIPTOR*); + +typedef struct { + bool used; + PalWindowState state; + HCURSOR cursor; +} WindowData; + typedef struct { bool initialized; + Int32 pixelFormat; + Int32 maxWindowData; PalVideoFeatures features; const PalAllocator* allocator; PalEventDriver* eventDriver; @@ -92,9 +111,12 @@ typedef struct { CreateDIBSectionFn createDIBSection; CreateBitmapFn createBitmap; DeleteObjectFn deleteObject; + DescribePixelFormatFn describePixelFormat; + SetPixelFormatFn setPixelFormat; HINSTANCE instance; HWND hiddenWindow; + WindowData* windowData; } VideoWin32; typedef struct { @@ -106,6 +128,7 @@ typedef struct { typedef struct { bool pendingResize; bool pendingMove; + bool pendingState; Uint32 width; Uint32 height; Int32 x; @@ -117,8 +140,8 @@ typedef struct { typedef struct { bool scancodeState[PAL_SCANCODE_MAX]; bool keycodeState[PAL_KEYCODE_MAX]; - PalScancode scancodes[512]; - PalKeycode keycodes[256]; + int scancodes[512]; + int keycodes[256]; } Keyboard; typedef struct { @@ -148,7 +171,7 @@ LRESULT CALLBACK videoProc( LPARAM lParam) { // check if the window has been created - void* data = (void*)(LONG_PTR)GetWindowLongPtrW(hwnd, GWLP_USERDATA); + WindowData* data = (void*)(LONG_PTR)GetWindowLongPtrW(hwnd, GWLP_USERDATA); if (!data) { // window has not been created yet return DefWindowProcW(hwnd, msg, wParam, lParam); @@ -184,7 +207,7 @@ LRESULT CALLBACK videoProc( event.data2 = palPackPointer((PalWindow*)hwnd); palPushEvent(driver, &event); - } else { + } else if (mode == PAL_DISPATCH_POLL) { s_Event.pendingResize = true; s_Event.width = width; s_Event.height = height; @@ -210,16 +233,23 @@ LRESULT CALLBACK videoProc( } } + // if state has not changed, we discard the event + if (data->state == state) { + return 0; + } + if (mode == PAL_DISPATCH_CALLBACK) { PalEvent event = {0}; + event.type = PAL_EVENT_WINDOW_STATE; event.data = state; event.data2 = palPackPointer((PalWindow*)hwnd); - event.type = PAL_EVENT_WINDOW_STATE; palPushEvent(driver, &event); - } else { + } else if (mode == PAL_DISPATCH_POLL) { + s_Event.pendingState = true; s_Event.state = state; } + data->state = state; } return 0; @@ -316,7 +346,7 @@ LRESULT CALLBACK videoProc( s_Mouse.push = true; if (s_Video.eventDriver) { PalEventDriver* driver = s_Video.eventDriver; - PalEventType type = PAL_EVENT_WINDOW_MODAL_BEGIN; + PalEventType type = PAL_EVENT_WINDOW_MODAL_END; mode = palGetEventDispatchMode(driver, type); if (mode != PAL_DISPATCH_NONE) { PalEvent event = {0}; @@ -366,7 +396,7 @@ LRESULT CALLBACK videoProc( case WM_MOUSEHWHEEL: { Int32 delta = GET_WHEEL_DELTA_WPARAM(wParam); - s_Mouse.WheelX = delta; + s_Mouse.WheelX = delta / WHEEL_DELTA; if (s_Video.eventDriver) { PalEventDriver* driver = s_Video.eventDriver; @@ -384,7 +414,7 @@ LRESULT CALLBACK videoProc( case WM_MOUSEWHEEL: { Int32 delta = GET_WHEEL_DELTA_WPARAM(wParam); - s_Mouse.WheelY = delta; + s_Mouse.WheelY = delta / WHEEL_DELTA; if (s_Video.eventDriver) { PalEventDriver* driver = s_Video.eventDriver; @@ -462,6 +492,7 @@ LRESULT CALLBACK videoProc( case WM_MBUTTONUP: case WM_XBUTTONUP: { PalMouseButton button = PAL_MOUSE_BUTTON_UNKNOWN; + PalEventType type; bool pressed = false; if (msg == WM_LBUTTONDOWN || msg == WM_LBUTTONUP) { @@ -492,9 +523,11 @@ LRESULT CALLBACK videoProc( msg == WM_MBUTTONDOWN || msg == WM_XBUTTONDOWN) { pressed = true; + type = PAL_EVENT_MOUSE_BUTTONDOWN; } else { pressed = false; + type = PAL_EVENT_MOUSE_BUTTONUP; } // clang-format on @@ -510,27 +543,13 @@ LRESULT CALLBACK videoProc( s_Mouse.state[button] = pressed; if (s_Video.eventDriver) { PalEventDriver* driver = s_Video.eventDriver; - if (pressed) { - PalEventType type = PAL_EVENT_MOUSE_BUTTONDOWN; - mode = palGetEventDispatchMode(driver, type); - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = type; - event.data = button; - event.data2 = palPackPointer((PalWindow*)hwnd); - palPushEvent(driver, &event); - } - - } else { - PalEventType type = PAL_EVENT_MOUSE_BUTTONUP; - mode = palGetEventDispatchMode(driver, type); - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = type; - event.data = button; - event.data2 = palPackPointer((PalWindow*)hwnd); - palPushEvent(driver, &event); - } + mode = palGetEventDispatchMode(driver, type); + if (mode != PAL_DISPATCH_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = button; + event.data2 = palPackPointer((PalWindow*)hwnd); + palPushEvent(driver, &event); } } return 0; @@ -542,6 +561,7 @@ LRESULT CALLBACK videoProc( case WM_SYSKEYUP: { PalKeycode keycode = PAL_KEYCODE_UNKNOWN; PalScancode scancode = PAL_SCANCODE_UNKNOWN; + PalEventType type; Int32 win32Keycode; Int32 win32Scancode; bool pressed = false; @@ -653,49 +673,28 @@ LRESULT CALLBACK videoProc( s_Keyboard.keycodeState[keycode] = true; s_Keyboard.scancodeState[scancode] = true; + type = PAL_EVENT_KEYDOWN; + if (repeat) { + type = PAL_EVENT_KEYREPEAT; + } + } else { s_Keyboard.keycodeState[keycode] = false; s_Keyboard.scancodeState[scancode] = false; + type = PAL_EVENT_KEYUP; } if (s_Video.eventDriver) { PalEventDriver* driver = s_Video.eventDriver; - if (pressed) { - if (repeat) { - PalEventType type = PAL_EVENT_KEYREPEAT; - mode = palGetEventDispatchMode(driver, type); - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = type; - event.data = palPackUint32(keycode, scancode); - event.data2 = palPackPointer((PalWindow*)hwnd); - palPushEvent(driver, &event); - } - - } else { - PalEventType type = PAL_EVENT_KEYDOWN; - mode = palGetEventDispatchMode(driver, type); - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = type; - event.data = palPackUint32(keycode, scancode); - event.data2 = palPackPointer((PalWindow*)hwnd); - palPushEvent(driver, &event); - } - } - } else { - PalEventType type = PAL_EVENT_KEYUP; - mode = palGetEventDispatchMode(driver, type); - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = type; - event.data = palPackUint32(keycode, scancode); - event.data2 = palPackPointer((PalWindow*)hwnd); - palPushEvent(driver, &event); - } + mode = palGetEventDispatchMode(driver, type); + if (mode != PAL_DISPATCH_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = palPackUint32(keycode, scancode); + event.data2 = palPackPointer((PalWindow*)hwnd); + palPushEvent(driver, &event); } } - return 0; } @@ -706,10 +705,10 @@ LRESULT CALLBACK videoProc( case WM_SETCURSOR: { if (LOWORD(lParam) == HTCLIENT) { if (data) { - SetCursor((HCURSOR)data); + SetCursor(data->cursor); return TRUE; } - return TRUE; + return FALSE; } break; @@ -1106,6 +1105,38 @@ static void createScancodeTable() s_Keyboard.scancodes[0x15C] = PAL_SCANCODE_RSUPER; } +static WindowData* getFreeWindowData() +{ + for (int i = 0; i < s_Video.maxWindowData; ++i) { + if (!s_Video.windowData[i].used) { + s_Video.windowData[i].used = true; + return &s_Video.windowData[i]; + } + } + + // resize the data array + // It is rare for a user to create and manage + // 32 windows at the same time + WindowData* data = nullptr; + int count = s_Video.maxWindowData * 2; // double the size + int freeIndex = s_Video.maxWindowData + 1; + data = palAllocate(s_Video.allocator, sizeof(WindowData) * count, 0); + if (data) { + memcpy( + data, + s_Video.windowData, + s_Video.maxWindowData * sizeof(WindowData)); + + palFree(s_Video.allocator, s_Video.windowData); + s_Video.windowData = data; + s_Video.maxWindowData = count; + + s_Video.windowData[freeIndex].used = true; + return &s_Video.windowData[freeIndex]; + } + return nullptr; +} + // ================================================== // Public API // ================================================== @@ -1122,6 +1153,12 @@ PalResult PAL_CALL palInitVideo( return PAL_RESULT_INVALID_ALLOCATOR; } + s_Video.maxWindowData = 32; + s_Video.windowData = palAllocate( + s_Video.allocator, + sizeof(WindowData) * s_Video.maxWindowData, + 0); + // get the instance s_Video.instance = GetModuleHandleW(nullptr); @@ -1161,7 +1198,7 @@ PalResult PAL_CALL palInitVideo( return PAL_RESULT_PLATFORM_FAILURE; } - // set a flag to set if the window has been created + // set a flag to check if the window has been created SetWindowLongPtrW(s_Video.hiddenWindow, GWLP_USERDATA, (LONG_PTR)&s_Event); // register raw input for mice to get delta @@ -1207,6 +1244,14 @@ PalResult PAL_CALL palInitVideo( s_Video.deleteObject = (DeleteObjectFn)GetProcAddress( s_Video.gdi, "DeleteObject"); + + s_Video.describePixelFormat = (DescribePixelFormatFn)GetProcAddress( + s_Video.gdi, + "DescribePixelFormat"); + + s_Video.setPixelFormat = (SetPixelFormatFn)GetProcAddress( + s_Video.gdi, + "SetPixelFormat"); } // clang-format on @@ -1235,7 +1280,6 @@ PalResult PAL_CALL palInitVideo( s_Video.features |= PAL_VIDEO_FEATURE_CLIP_CURSOR; s_Video.features |= PAL_VIDEO_FEATURE_WINDOW_FLASH_CAPTION; s_Video.features |= PAL_VIDEO_FEATURE_WINDOW_FLASH_TRAY; - s_Video.features |= PAL_VIDEO_FEATURE_WINDOW_FLASH_TRAY; s_Video.features |= PAL_VIDEO_FEATURE_WINDOW_FLASH_INTERVAL; s_Video.features |= PAL_VIDEO_FEATURE_WINDOW_SET_INPUT_FOCUS; s_Video.features |= PAL_VIDEO_FEATURE_WINDOW_GET_INPUT_FOCUS; @@ -1243,6 +1287,7 @@ PalResult PAL_CALL palInitVideo( s_Video.features |= PAL_VIDEO_FEATURE_WINDOW_GET_STYLE; s_Video.features |= PAL_VIDEO_FEATURE_CURSOR_SET_POS; s_Video.features |= PAL_VIDEO_FEATURE_CURSOR_GET_POS; + s_Video.features |= PAL_VIDEO_FEATURE_WINDOW_SET_ICON; if (s_Video.getDpiForMonitor && s_Video.setProcessAwareness) { s_Video.features |= PAL_VIDEO_FEATURE_HIGH_DPI; @@ -1252,6 +1297,7 @@ PalResult PAL_CALL palInitVideo( s_Video.initialized = true; s_Video.allocator = allocator; s_Video.eventDriver = eventDriver; + s_Video.pixelFormat = 0; return PAL_RESULT_SUCCESS; } @@ -1268,6 +1314,7 @@ void PAL_CALL palShutdownVideo() FreeLibrary(s_Video.gdi); DestroyWindow(s_Video.hiddenWindow); UnregisterClassW(PAL_VIDEO_CLASS, s_Video.instance); + palFree(s_Video.allocator, s_Video.windowData); s_Video.initialized = false; } @@ -1294,10 +1341,14 @@ void PAL_CALL palUpdateVideo() event.data2 = palPackPointer(s_Event.window); palPushEvent(s_Video.eventDriver, &event); - // push a window state event - event.type = PAL_EVENT_WINDOW_STATE; - event.data = s_Event.state; - palPushEvent(s_Video.eventDriver, &event); + if (s_Event.pendingState) { + PalEvent event = {0}; + event.data = s_Event.state; + event.data2 = palPackPointer(s_Event.window); + event.type = PAL_EVENT_WINDOW_STATE; + palPushEvent(s_Video.eventDriver, &event); + s_Event.pendingState = false; + } s_Event.pendingResize = false; @@ -1320,6 +1371,27 @@ PalVideoFeatures PAL_CALL palGetVideoFeatures() return s_Video.features; } +PalResult PAL_CALL palSetFBConfig( + const int index, + PalFBConfigBackend backend) +{ + // Win32 uses only WGL and WGL index starts from 1 + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (backend == PAL_CONFIG_BACKEND_EGL || + backend == PAL_CONFIG_BACKEND_GLX) { + return PAL_RESULT_INVALID_FBCONFIG_BACKEND; + } + + if (index >= 1) { + s_Video.pixelFormat = index; + return PAL_RESULT_SUCCESS; + } + return PAL_RESULT_INVALID_GL_FBCONFIG; +} + // ================================================== // Monitor // ================================================== @@ -1336,7 +1408,7 @@ PalResult PAL_CALL palEnumerateMonitors( return PAL_RESULT_NULL_POINTER; } - if (count == 0 && outMonitors) { + if (*count == 0 && outMonitors) { return PAL_RESULT_INSUFFICIENT_BUFFER; } @@ -1456,6 +1528,10 @@ PalResult PAL_CALL palEnumerateMonitorModes( return PAL_RESULT_NULL_POINTER; } + if (*count == 0 && modes) { + return PAL_RESULT_INSUFFICIENT_BUFFER; + } + Int32 modeCount = 0; Int32 maxModes = 0; PalMonitorMode* monitorModes = nullptr; @@ -1658,6 +1734,11 @@ PalResult PAL_CALL palCreateWindow( return PAL_RESULT_NULL_POINTER; } + WindowData* data = getFreeWindowData(); + if (!data) { + return PAL_RESULT_OUT_OF_MEMORY; + } + HWND handle = nullptr; PalMonitor* monitor = nullptr; PalMonitorInfo monitorInfo; @@ -1761,16 +1842,37 @@ PalResult PAL_CALL palCreateWindow( return PAL_RESULT_PLATFORM_FAILURE; } + // set the pixel format is set + if (s_Video.pixelFormat) { + HDC hdc = GetDC(handle); + // since we have the pixel format already + // we ask the OS (platform) to fill the pfd struct for us from that + // index + PIXELFORMATDESCRIPTOR pfd; + if (!s_Video.describePixelFormat( + hdc, + s_Video.pixelFormat, + sizeof(PIXELFORMATDESCRIPTOR), + &pfd)) { + return PAL_RESULT_INVALID_GL_FBCONFIG; + } + + s_Video.setPixelFormat(hdc, s_Video.pixelFormat, &pfd); + ReleaseDC(handle, hdc); + } + // show, maximize and minimize Int32 showFlag = SW_HIDE; // maximize if (info->maximized) { showFlag = SW_MAXIMIZE; + data->state = PAL_WINDOW_STATE_MAXIMIZED; } // minimized if (info->minimized) { showFlag = SW_MINIMIZE; + data->state = PAL_WINDOW_STATE_MINIMIZED; } // shown @@ -1778,6 +1880,7 @@ PalResult PAL_CALL palCreateWindow( if (showFlag == SW_HIDE) { // change only if maximize and minimize are not set showFlag = SW_SHOW; + data->state = PAL_WINDOW_STATE_RESTORED; } } @@ -1800,16 +1903,14 @@ PalResult PAL_CALL palCreateWindow( SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); } - // set a flag to set if the window has been created - SetWindowLongPtrW(handle, GWLP_USERDATA, (LONG_PTR)&s_Event); - + SetWindowLongPtrW(handle, GWLP_USERDATA, (LONG_PTR)data); *outWindow = (PalWindow*)handle; return PAL_RESULT_SUCCESS; } void PAL_CALL palDestroyWindow(PalWindow* window) { - if (window) { + if (s_Video.initialized && window) { DestroyWindow((HWND)window); } } @@ -2260,6 +2361,14 @@ PalResult PAL_CALL palSetWindowOpacity( return PAL_RESULT_NULL_POINTER; } + if (opacity < 0.0f) { + opacity = 0.0f; + } + + if (opacity > 1.0f) { + opacity = 1.0f; + } + bool ret = SetLayeredWindowAttributes( (HWND)window, 0, @@ -2534,38 +2643,39 @@ PalResult PAL_CALL palCreateIcon( // convert RGBA to BGRA Uint8* pixels = (Uint8*)dibPixels; - for (Uint32 y = 0; y < info->height; ++y) { - for (Uint32 x = 0; x < info->width; ++x) { - int i = (y * info->width + x) * 4; - pixels[i + 0] = info->pixels[i + 2]; // Red - pixels[i + 1] = info->pixels[i + 1]; // Green - pixels[i + 2] = info->pixels[i + 0]; // Nlue - pixels[i + 3] = info->pixels[i + 3]; // Alpha + for (int i = 0; i < info->width * info->height; i++) { + Uint8 r = info->pixels[i * 4 + 0]; // Red + Uint8 g = info->pixels[i * 4 + 1]; // Green + Uint8 b = info->pixels[i * 4 + 2]; // Blue + Uint8 a = info->pixels[i * 4 + 3]; // Alpha + + // premultiply only if alpha is not 0 + if (a == 0) { + r = g = b = 0; + } else { + r = (Uint8)((r * a) / 255); + g = (Uint8)((g * a) / 255); + b = (Uint8)((b * a) / 255); } - } - // create mask - HBITMAP mask = nullptr; - mask = s_Video.createBitmap(info->width, info->height, 1, 1, nullptr); - if (!mask) { - s_Video.deleteObject(bitmap); - return PAL_RESULT_PLATFORM_FAILURE; + pixels[i * 4 + 0] = b; + pixels[i * 4 + 1] = g; + pixels[i * 4 + 2] = r; + pixels[i * 4 + 3] = a; } ICONINFO iconInfo = {0}; iconInfo.fIcon = TRUE; - iconInfo.hbmMask = mask; + iconInfo.hbmMask = bitmap; iconInfo.hbmColor = bitmap; // create the icon with the icon info HICON icon = CreateIconIndirect(&iconInfo); if (!icon) { - s_Video.deleteObject(mask); s_Video.deleteObject(bitmap); return PAL_RESULT_PLATFORM_FAILURE; } - s_Video.deleteObject(mask); s_Video.deleteObject(bitmap); *outIcon = (PalIcon*)icon; return PAL_RESULT_SUCCESS; @@ -2648,31 +2758,101 @@ PalResult PAL_CALL palCreateCursor( } ReleaseDC(nullptr, hdc); - // copy pixels and create mask - memcpy(dibPixels, info->pixels, info->width * info->height * 4); - HBITMAP mask = s_Video.createBitmap(info->width, info->height, 1, 1, NULL); + // convert RGBA to BGRA + Uint8* pixels = (Uint8*)dibPixels; + for (int i = 0; i < info->width * info->height; i++) { + Uint8 r = info->pixels[i * 4 + 0]; // Red + Uint8 g = info->pixels[i * 4 + 1]; // Green + Uint8 b = info->pixels[i * 4 + 2]; // Blue + Uint8 a = info->pixels[i * 4 + 3]; // Alpha + + // premultiply only if alpha is not 0 + if (a == 0) { + r = g = b = 0; + } else { + r = (Uint8)((r * a) / 255); + g = (Uint8)((g * a) / 255); + b = (Uint8)((b * a) / 255); + } + + pixels[i * 4 + 0] = b; + pixels[i * 4 + 1] = g; + pixels[i * 4 + 2] = r; + pixels[i * 4 + 3] = a; + } ICONINFO iconInfo = {0}; iconInfo.fIcon = false; iconInfo.hbmColor = bitmap; - iconInfo.hbmMask = mask; + iconInfo.hbmMask = bitmap; iconInfo.xHotspot = info->xHotspot; iconInfo.xHotspot = info->yHotspot; // create the cursor with the iconinfo HCURSOR cursor = CreateIconIndirect(&iconInfo); if (!cursor) { - s_Video.deleteObject(mask); s_Video.deleteObject(bitmap); return PAL_RESULT_PLATFORM_FAILURE; } - s_Video.deleteObject(mask); s_Video.deleteObject(bitmap); *outCursor = (PalCursor*)cursor; return PAL_RESULT_SUCCESS; } +PalResult PAL_CALL palCreateCursorFrom( + PalCursorType type, + PalCursor** outCursor) +{ + + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!outCursor) { + return PAL_RESULT_NULL_POINTER; + } + + HCURSOR cursor = nullptr; + switch (type) { + case PAL_CURSOR_ARROW: { + cursor = LoadCursorW(nullptr, IDC_ARROW); + break; + } + + case PAL_CURSOR_HAND: { + cursor = LoadCursorW(nullptr, IDC_HAND); + break; + } + + case PAL_CURSOR_CROSS: { + cursor = LoadCursorW(nullptr, IDC_CROSS); + break; + } + + case PAL_CURSOR_IBEAM: { + cursor = LoadCursorW(nullptr, IDC_IBEAM); + break; + } + + case PAL_CURSOR_WAIT: { + cursor = LoadCursorW(nullptr, IDC_WAIT); + break; + } + + default: { + return PAL_RESULT_INVALID_ARGUMENT; + } + } + + if (!cursor) { + return PAL_RESULT_PLATFORM_FAILURE; + } + + *outCursor = (PalCursor*)cursor; + return PAL_RESULT_SUCCESS; +} + void PAL_CALL palDestroyCursor(PalCursor* cursor) { if (s_Video.initialized && cursor) { @@ -2778,10 +2958,12 @@ PalResult PAL_CALL palSetWindowCursor( PalWindow* window, PalCursor* cursor) { - if (window || cursor) { + if (window) { SetLastError(0); - SetWindowLongPtrW((HWND)window, GWLP_USERDATA, (LONG_PTR)cursor); + WindowData* data = + (WindowData*)GetWindowLongPtrW((HWND)window, GWLP_USERDATA); + data->cursor = (HCURSOR)cursor; DWORD error = GetLastError(); if (error == 0) { return PAL_RESULT_SUCCESS; diff --git a/tests/cursor_test.c b/tests/cursor_test.c index d05565c..710ce75 100644 --- a/tests/cursor_test.c +++ b/tests/cursor_test.c @@ -31,7 +31,7 @@ bool cursorTest() result = palCreateEventDriver(&eventDriverCreateInfo, &eventDriver); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create event driver %s", error); + palLog(nullptr, "Failed to create event driver: %s", error); return false; } @@ -41,7 +41,7 @@ bool cursorTest() result = palInitVideo(nullptr, eventDriver); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to initialize video %s", error); + palLog(nullptr, "Failed to initialize video: %s", error); return false; } @@ -56,13 +56,13 @@ bool cursorTest() pixels[i + 0] = 255; // Red bit pixels[i + 1] = 0; // Green bit pixels[i + 2] = 0; // Blue bit - pixels[i + 3] = 0; // Alpha bit + pixels[i + 3] = 255; // Alpha bit } else { pixels[i + 0] = 0; // Red bit pixels[i + 1] = 0; // Green bit pixels[i + 2] = 255; // Blue bit - pixels[i + 3] = 0; // Alpha bit + pixels[i + 3] = 255; // Alpha bit } } } @@ -77,7 +77,7 @@ bool cursorTest() result = palCreateCursor(&cursorCreateInfo, &cursor); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create window cursor %s", error); + palLog(nullptr, "Failed to create window cursor: %s", error); return false; } @@ -93,7 +93,7 @@ bool cursorTest() result = palCreateWindow(&createInfo, &window); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create window %s", error); + palLog(nullptr, "Failed to create window: %s", error); return false; } diff --git a/tests/event_test.c b/tests/event_test.c index 073f082..2bd211a 100644 --- a/tests/event_test.c +++ b/tests/event_test.c @@ -38,7 +38,7 @@ static inline void eventDispatchTest(bool poll) result = palCreateEventDriver(&createInfo, &driver); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create event driver %s", error); + palLog(nullptr, "Failed to create event driver: %s", error); return; } diff --git a/tests/icon_test.c b/tests/icon_test.c index f375849..b92dc2a 100644 --- a/tests/icon_test.c +++ b/tests/icon_test.c @@ -31,7 +31,7 @@ bool iconTest() result = palCreateEventDriver(&eventDriverCreateInfo, &eventDriver); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create event driver %s", error); + palLog(nullptr, "Failed to create event driver: %s", error); return false; } @@ -41,7 +41,7 @@ bool iconTest() result = palInitVideo(nullptr, eventDriver); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to initialize video %s", error); + palLog(nullptr, "Failed to initialize video: %s", error); return false; } @@ -56,13 +56,13 @@ bool iconTest() pixels[i + 0] = 255; // Red bit pixels[i + 1] = 0; // Green bit pixels[i + 2] = 0; // Blue bit - pixels[i + 3] = 0; // Alpha bit + pixels[i + 3] = 255; // Alpha bit } else { pixels[i + 0] = 0; // Red bit pixels[i + 1] = 255; // Green bit pixels[i + 2] = 0; // Blue bit - pixels[i + 3] = 0; // Alpha bit + pixels[i + 3] = 255; // Alpha bit } } } @@ -75,7 +75,7 @@ bool iconTest() result = palCreateIcon(&iconCreateInfo, &icon); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create window icon %s", error); + palLog(nullptr, "Failed to create window icon: %s", error); return false; } @@ -91,7 +91,7 @@ bool iconTest() result = palCreateWindow(&createInfo, &window); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create window %s", error); + palLog(nullptr, "Failed to create window: %s", error); return false; } @@ -105,7 +105,7 @@ bool iconTest() result = palSetWindowIcon(window, icon); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to set window icon %s", error); + palLog(nullptr, "Failed to set window icon: %s", error); return false; } diff --git a/tests/input_window_test.c b/tests/input_window_test.c index 29f2ca1..ef83f9f 100644 --- a/tests/input_window_test.c +++ b/tests/input_window_test.c @@ -421,7 +421,7 @@ bool inputWindowTest() result = palCreateEventDriver(&eventDriverCreateInfo, &eventDriver); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create event driver %s", error); + palLog(nullptr, "Failed to create event driver: %s", error); return false; } @@ -431,7 +431,7 @@ bool inputWindowTest() result = palInitVideo(nullptr, eventDriver); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to initialize video %s", error); + palLog(nullptr, "Failed to initialize video: %s", error); return false; } @@ -447,7 +447,7 @@ bool inputWindowTest() result = palCreateWindow(&createInfo, &window); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create window %s", error); + palLog(nullptr, "Failed to create window: %s", error); return false; } diff --git a/tests/monitor_mode_test.c b/tests/monitor_mode_test.c index c3f81e2..61a1bdb 100644 --- a/tests/monitor_mode_test.c +++ b/tests/monitor_mode_test.c @@ -18,7 +18,7 @@ bool monitorModeTest() PalResult result = palInitVideo(nullptr, nullptr); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to initialize video %s", error); + palLog(nullptr, "Failed to initialize video: %s", error); return false; } @@ -26,7 +26,7 @@ bool monitorModeTest() result = palEnumerateMonitors(&count, nullptr); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to get query monitors %s", error); + palLog(nullptr, "Failed to get query monitors: %s", error); return false; } @@ -50,7 +50,7 @@ bool monitorModeTest() result = palEnumerateMonitors(&count, monitors); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to get query monitors %s", error); + palLog(nullptr, "Failed to get query monitors: %s", error); return false; } @@ -60,7 +60,7 @@ bool monitorModeTest() result = palGetMonitorInfo(monitor, &info); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to get monitor info %s", error); + palLog(nullptr, "Failed to get monitor info: %s", error); palFree(nullptr, monitors); return false; } @@ -72,7 +72,7 @@ bool monitorModeTest() result = palEnumerateMonitorModes(monitor, &modeCount, nullptr); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to get query monitor modes %s", error); + palLog(nullptr, "Failed to get query monitor modes: %s", error); return false; } @@ -91,7 +91,7 @@ bool monitorModeTest() result = palEnumerateMonitorModes(monitor, &modeCount, modes); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to get query monitor modes %s", error); + palLog(nullptr, "Failed to get query monitor modes: %s", error); return false; } @@ -112,7 +112,7 @@ bool monitorModeTest() result = palGetCurrentMonitorMode(monitor, ¤t); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to get current monitor mode %s", error); + palLog(nullptr, "Failed to get current monitor mode: %s", error); return false; } @@ -128,5 +128,6 @@ bool monitorModeTest() // free monitors array palFree(nullptr, monitors); + return true; } \ No newline at end of file diff --git a/tests/monitor_test.c b/tests/monitor_test.c index 256e53a..6d65e60 100644 --- a/tests/monitor_test.c +++ b/tests/monitor_test.c @@ -17,7 +17,7 @@ bool monitorTest() PalResult result = palInitVideo(nullptr, nullptr); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to initialize video %s", error); + palLog(nullptr, "Failed to initialize video: %s", error); return false; } @@ -25,7 +25,7 @@ bool monitorTest() result = palEnumerateMonitors(&count, nullptr); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to get query monitors %s", error); + palLog(nullptr, "Failed to get query monitors: %s", error); return false; } @@ -49,7 +49,7 @@ bool monitorTest() result = palEnumerateMonitors(&count, monitors); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to get query monitors %s", error); + palLog(nullptr, "Failed to get query monitors: %s", error); return false; } @@ -59,7 +59,7 @@ bool monitorTest() result = palGetMonitorInfo(monitor, &info); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to get monitor info %s", error); + palLog(nullptr, "Failed to get monitor info: %s", error); palFree(nullptr, monitors); return false; } @@ -106,5 +106,6 @@ bool monitorTest() // free monitors array palFree(nullptr, monitors); + return true; } \ No newline at end of file diff --git a/tests/opengl_context_test.c b/tests/opengl_context_test.c index f1753e0..c5fbed0 100644 --- a/tests/opengl_context_test.c +++ b/tests/opengl_context_test.c @@ -35,7 +35,7 @@ bool openglContextTest() PalGLContext* context = nullptr; PalWindowCreateInfo createInfo = {0}; PalGLContextCreateInfo contextCreateInfo = {0}; - Int32 fbCount; + Int32 fbCount = 0; bool running = false; // event driver @@ -52,58 +52,16 @@ bool openglContextTest() result = palCreateEventDriver(&eventDriverCreateInfo, &eventDriver); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create event driver %s", error); + palLog(nullptr, "Failed to create event driver: %s", error); return false; } - // initialize the video system. We pass the event driver to recieve video - // related events the video system does not copy the event driver, it must - // be valid till the video system is shutdown - result = palInitVideo(nullptr, eventDriver); + // enumerate supported opengl framebuffer configs + // glWindow must be nullptr + result = palEnumerateGLFBConfigs(nullptr, &fbCount, nullptr); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to initialize video: %s", error); - return false; - } - - createInfo.monitor = nullptr; // use primary monitor - createInfo.height = 480; - createInfo.width = 640; - createInfo.show = true; - createInfo.style = PAL_WINDOW_STYLE_RESIZABLE; - createInfo.title = "Pal Opengl Context Window"; - - // create the window with the create info struct - result = palCreateWindow(&createInfo, &window); - if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create window %s", error); - return false; - } - - // we set window close to poll - palSetEventDispatchMode( - eventDriver, - PAL_EVENT_WINDOW_CLOSE, - PAL_DISPATCH_POLL); - - // get window handle. You can use any window from any library - // so long as you can get the window handle and display (if on X11, wayland) - // If pal video system will not be used, there is no need to initialize it - PalWindowHandleInfo windowHandleInfo; - windowHandleInfo = palGetWindowHandleInfo(window); - - // PalGLWindow is just a struct to hold native handles - PalGLWindow glWindow = {0}; - // needed when using X11 or wayland - glWindow.display = windowHandleInfo.nativeDisplay; - glWindow.window = windowHandleInfo.nativeWindow; - - // use the gl window to query supported FBconfigs - result = palEnumerateGLFBConfigs(&glWindow, &fbCount, nullptr); - if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to query GL FBConfigs %s", error); + palLog(nullptr, "Failed to query GL FBConfigs: %s", error); return false; } @@ -120,10 +78,12 @@ bool openglContextTest() return false; } - result = palEnumerateGLFBConfigs(&glWindow, &fbCount, fbConfigs); + // enumerate supported opengl framebuffer configs + // glWindow must be nullptr + result = palEnumerateGLFBConfigs(nullptr, &fbCount, fbConfigs); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to query GL FBConfigs %s", error); + palLog(nullptr, "Failed to query GL FBConfigs: %s", error); palFree(nullptr, fbConfigs); return false; } @@ -163,6 +123,64 @@ bool openglContextTest() palLog(nullptr, " sRGB: %s", g_BoolsToSting[closest->sRGB]); palLog(nullptr, ""); + // initialize the video system. We pass the event driver to recieve video + // related events the video system does not copy the event driver, it must + // be valid till the video system is shutdown + result = palInitVideo(nullptr, eventDriver); + if (result != PAL_RESULT_SUCCESS) { + const char* error = palFormatResult(result); + palLog(nullptr, "Failed to initialize video: %s", error); + return false; + } + + // set the FBConfig that will be used by PAL video system + // to create windows. this must be set before creating a window + // for this example, we set the closest we desired. + // If pal_opengl and pal_video will be used together, + // then its recommended to use PAL_CONFIG_BACKEND_PAL_OPENGL + + // NOTE: If PAL video system will not be used, + // users need to call the direct OS call to achieve this. + result = palSetFBConfig(closest->index, PAL_CONFIG_BACKEND_PAL_OPENGL); + if (result != PAL_RESULT_SUCCESS) { + const char* error = palFormatResult(result); + palLog(nullptr, "Failed to set GL pixel format: %s", error); + return false; + } + + createInfo.monitor = nullptr; // use primary monitor + createInfo.height = 480; + createInfo.width = 640; + createInfo.show = true; + createInfo.style = PAL_WINDOW_STYLE_RESIZABLE; + createInfo.title = "Pal Opengl Context Window"; + + // create the window with the create info struct + result = palCreateWindow(&createInfo, &window); + if (result != PAL_RESULT_SUCCESS) { + const char* error = palFormatResult(result); + palLog(nullptr, "Failed to create window: %s", error); + return false; + } + + // we set window close to poll + palSetEventDispatchMode( + eventDriver, + PAL_EVENT_WINDOW_CLOSE, + PAL_DISPATCH_POLL); + + // get window handle. You can use any window from any library + // so long as you can get the window handle and display (if on X11, wayland) + // If pal video system will not be used, there is no need to initialize it + PalWindowHandleInfo windowHandleInfo; + windowHandleInfo = palGetWindowHandleInfo(window); + + // PalGLWindow is just a struct to hold native handles + PalGLWindow glWindow = {0}; + // needed when using X11 or wayland + glWindow.display = windowHandleInfo.nativeDisplay; + glWindow.window = windowHandleInfo.nativeWindow; + // get opengl info const PalGLInfo* info = palGetGLInfo(); diff --git a/tests/opengl_fbconfig_test.c b/tests/opengl_fbconfig_test.c index 6bc9176..407daee 100644 --- a/tests/opengl_fbconfig_test.c +++ b/tests/opengl_fbconfig_test.c @@ -21,49 +21,13 @@ bool openglFBConfigTest() return false; } - PalWindow* window = nullptr; - PalWindowCreateInfo createInfo = {0}; - Int32 fbCount; - - // initialize the video system. - result = palInitVideo(nullptr, nullptr); - if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to initialize video: %s", error); - return false; - } - - createInfo.monitor = nullptr; // use primary monitor - createInfo.height = 480; - createInfo.width = 640; - createInfo.show = true; - createInfo.style = PAL_WINDOW_STYLE_RESIZABLE; - - // create the window with the create info struct - result = palCreateWindow(&createInfo, &window); - if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create window %s", error); - return false; - } - - // get window handle. You can use any window from any library - // so long as you can get the window handle and display (if on X11, wayland) - // If pal video system will not be used, there is no need to initialize it - PalWindowHandleInfo windowHandleInfo; - windowHandleInfo = palGetWindowHandleInfo(window); - - // PalGLWindow is just a struct to hold native handles - PalGLWindow glWindow = {0}; - // needed when using X11 or wayland - glWindow.display = windowHandleInfo.nativeDisplay; - glWindow.window = windowHandleInfo.nativeWindow; - // enumerate supported opengl framebuffer configs - result = palEnumerateGLFBConfigs(&glWindow, &fbCount, nullptr); + // glWindow must be nullptr for default + Int32 fbCount = 0; + result = palEnumerateGLFBConfigs(nullptr, &fbCount, nullptr); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to query GL FBConfigs %s", error); + palLog(nullptr, "Failed to query GL FBConfigs: %s", error); return false; } @@ -80,10 +44,11 @@ bool openglFBConfigTest() return false; } - result = palEnumerateGLFBConfigs(&glWindow, &fbCount, fbConfigs); + // enumerate supported opengl framebuffer configs + result = palEnumerateGLFBConfigs(nullptr, &fbCount, fbConfigs); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to query GL FBConfigs %s", error); + palLog(nullptr, "Failed to query GL FBConfigs: %s", error); palFree(nullptr, fbConfigs); return false; } @@ -159,12 +124,6 @@ bool openglFBConfigTest() palLog(nullptr, " sRGB: %s", g_BoolsToSting[closest->sRGB]); palLog(nullptr, ""); - // destroy the window - palDestroyWindow(window); - - // shutdown the video system - palShutdownVideo(); - // shutdown the opengl system palShutdownGL(); diff --git a/tests/opengl_multi_context_test.c b/tests/opengl_multi_context_test.c index 3f82e26..f69d31c 100644 --- a/tests/opengl_multi_context_test.c +++ b/tests/opengl_multi_context_test.c @@ -35,7 +35,7 @@ bool openglMultiContextTest() PalGLContext* context = nullptr; PalWindowCreateInfo createInfo = {0}; PalGLContextCreateInfo contextCreateInfo = {0}; - Int32 fbCount; + Int32 fbCount = 0; bool running = false; // event driver @@ -52,58 +52,16 @@ bool openglMultiContextTest() result = palCreateEventDriver(&eventDriverCreateInfo, &eventDriver); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create event driver %s", error); + palLog(nullptr, "Failed to create event driver: %s", error); return false; } - // initialize the video system. We pass the event driver to recieve video - // related events the video system does not copy the event driver, it must - // be valid till the video system is shutdown - result = palInitVideo(nullptr, eventDriver); - if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to initialize video %s", error); - return false; - } - - createInfo.monitor = nullptr; // use primary monitor - createInfo.height = 480; - createInfo.width = 640; - createInfo.show = true; - createInfo.style = PAL_WINDOW_STYLE_RESIZABLE; - createInfo.title = "Pal Opengl Multi Context Window"; - - // create the window with the create info struct - result = palCreateWindow(&createInfo, &window); - if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create window %s", error); - return false; - } - - // we set window close to poll - palSetEventDispatchMode( - eventDriver, - PAL_EVENT_WINDOW_CLOSE, - PAL_DISPATCH_POLL); - - // get window handle. You can use any window from any library - // so long as you can get the window handle and display (if on X11, wayland) - // If pal video system will not be used, there is no need to initialize it - PalWindowHandleInfo windowHandleInfo; - windowHandleInfo = palGetWindowHandleInfo(window); - - // PalGLWindow is just a struct to hold native handles - PalGLWindow glWindow = {0}; - // needed when using X11 or wayland - glWindow.display = windowHandleInfo.nativeDisplay; - glWindow.window = windowHandleInfo.nativeWindow; - - // use the gl window to query supported FBconfigs - result = palEnumerateGLFBConfigs(&glWindow, &fbCount, nullptr); + // enumerate supported opengl framebuffer configs + // glWindow must be nullptr + result = palEnumerateGLFBConfigs(nullptr, &fbCount, nullptr); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to query GL FBConfigs %s", error); + palLog(nullptr, "Failed to query GL FBConfigs: %s", error); return false; } @@ -120,10 +78,12 @@ bool openglMultiContextTest() return false; } - result = palEnumerateGLFBConfigs(&glWindow, &fbCount, fbConfigs); + // enumerate supported opengl framebuffer configs + // glWindow must be nullptr + result = palEnumerateGLFBConfigs(nullptr, &fbCount, fbConfigs); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to query GL FBConfigs %s", error); + palLog(nullptr, "Failed to query GL FBConfigs: %s", error); palFree(nullptr, fbConfigs); return false; } @@ -163,6 +123,64 @@ bool openglMultiContextTest() palLog(nullptr, " sRGB: %s", g_BoolsToSting[closest->sRGB]); palLog(nullptr, ""); + // initialize the video system. We pass the event driver to recieve video + // related events the video system does not copy the event driver, it must + // be valid till the video system is shutdown + result = palInitVideo(nullptr, eventDriver); + if (result != PAL_RESULT_SUCCESS) { + const char* error = palFormatResult(result); + palLog(nullptr, "Failed to initialize video: %s", error); + return false; + } + + // set the FBConfig that will be used by PAL video system + // to create windows. this must be set before creating a window + // for this example, we set the closest we desired. + // If pal_opengl and pal_video will be used together, + // then its recommended to use PAL_CONFIG_BACKEND_PAL_OPENGL + + // NOTE: If PAL video system will not be used, + // users need to call the direct OS call to achieve this. + result = palSetFBConfig(closest->index, PAL_CONFIG_BACKEND_PAL_OPENGL); + if (result != PAL_RESULT_SUCCESS) { + const char* error = palFormatResult(result); + palLog(nullptr, "Failed to set GL pixel format: %s", error); + return false; + } + + createInfo.monitor = nullptr; // use primary monitor + createInfo.height = 480; + createInfo.width = 640; + createInfo.show = true; + createInfo.style = PAL_WINDOW_STYLE_RESIZABLE; + createInfo.title = "Pal Opengl Multi Context Window"; + + // create the window with the create info struct + result = palCreateWindow(&createInfo, &window); + if (result != PAL_RESULT_SUCCESS) { + const char* error = palFormatResult(result); + palLog(nullptr, "Failed to create window: %s", error); + return false; + } + + // we set window close to poll + palSetEventDispatchMode( + eventDriver, + PAL_EVENT_WINDOW_CLOSE, + PAL_DISPATCH_POLL); + + // get window handle. You can use any window from any library + // so long as you can get the window handle and display (if on X11, wayland) + // If pal video system will not be used, there is no need to initialize it + PalWindowHandleInfo windowHandleInfo; + windowHandleInfo = palGetWindowHandleInfo(window); + + // PalGLWindow is just a struct to hold native handles + PalGLWindow glWindow = {0}; + // needed when using X11 or wayland + glWindow.display = windowHandleInfo.nativeDisplay; + glWindow.window = windowHandleInfo.nativeWindow; + // get opengl info const PalGLInfo* info = palGetGLInfo(); diff --git a/tests/system_cursor_test.c b/tests/system_cursor_test.c new file mode 100644 index 0000000..ce92fa5 --- /dev/null +++ b/tests/system_cursor_test.c @@ -0,0 +1,109 @@ + +#include "pal/pal_video.h" +#include "tests.h" + +bool systemCursorTest() +{ + palLog(nullptr, ""); + palLog(nullptr, "==========================================="); + palLog(nullptr, "System Cursor Test"); + palLog(nullptr, "==========================================="); + palLog(nullptr, ""); + + PalResult result; + PalWindow* window = nullptr; + PalCursor* cursor = nullptr; + PalWindowCreateInfo createInfo = {0}; + bool running = false; + + // event driver + PalEventDriver* eventDriver = nullptr; + PalEventDriverCreateInfo eventDriverCreateInfo = {0}; + + // fill the event driver create info + eventDriverCreateInfo.allocator = nullptr; // default allocator + eventDriverCreateInfo.callback = nullptr; // for callback dispatch + eventDriverCreateInfo.queue = nullptr; // default queue + eventDriverCreateInfo.userData = nullptr; // null + + // create the event driver + result = palCreateEventDriver(&eventDriverCreateInfo, &eventDriver); + if (result != PAL_RESULT_SUCCESS) { + const char* error = palFormatResult(result); + palLog(nullptr, "Failed to create event driver: %s", error); + return false; + } + + // initialize the video system. We pass the event driver to recieve video + // related events the video does not copy this, this must be valid till the + // video system is shutdown + result = palInitVideo(nullptr, eventDriver); + if (result != PAL_RESULT_SUCCESS) { + const char* error = palFormatResult(result); + palLog(nullptr, "Failed to initialize video: %s", error); + return false; + } + + // create system cursor + result = palCreateCursorFrom(PAL_CURSOR_CROSS, &cursor); + if (result != PAL_RESULT_SUCCESS) { + const char* error = palFormatResult(result); + palLog(nullptr, "Failed to create window cursor: %s", error); + return false; + } + + // fill the create info struct + createInfo.monitor = nullptr; // use primary monitor + createInfo.height = 480; + createInfo.width = 640; + createInfo.show = true; + createInfo.style = PAL_WINDOW_STYLE_RESIZABLE; + createInfo.title = "PAL System Cursor Window - Cross"; + + // create the window with the create info struct + result = palCreateWindow(&createInfo, &window); + if (result != PAL_RESULT_SUCCESS) { + const char* error = palFormatResult(result); + palLog(nullptr, "Failed to create window: %s", error); + return false; + } + + // set the dispatch mode for window close event to recieve it + palSetEventDispatchMode( + eventDriver, + PAL_EVENT_WINDOW_CLOSE, + PAL_DISPATCH_POLL); // polling + + // set the cursor + palSetWindowCursor(window, cursor); + + running = true; + while (running) { + // update the video system to push video events + palUpdateVideo(); + + PalEvent event; + while (palPollEvent(eventDriver, &event)) { + switch (event.type) { + case PAL_EVENT_WINDOW_CLOSE: { + running = false; + break; + } + } + } + } + + // destroy the window + palDestroyWindow(window); + + // destroy cursor + palDestroyCursor(cursor); + + // shutdown the video system + palShutdownVideo(); + + // destroy the event driver + palDestroyEventDriver(eventDriver); + + return true; +} \ No newline at end of file diff --git a/tests/system_test.c b/tests/system_test.c index 794bcb8..126c085 100644 --- a/tests/system_test.c +++ b/tests/system_test.c @@ -86,7 +86,7 @@ bool systemTest() result = palGetCPUInfo(nullptr, &cpuInfo); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to get Cpu info %s", error); + palLog(nullptr, "Failed to get Cpu info: %s", error); return false; } @@ -147,11 +147,11 @@ bool systemTest() } if (cpuInfo.features & PAL_CPU_FEATURE_AVX2) { - strcat(instructionSets, "| AVX2"); + strcat(instructionSets, " | AVX2"); } if (cpuInfo.features & PAL_CPU_FEATURE_AVX512F) { - strcat(instructionSets, "| AVX-512F"); + strcat(instructionSets, " | AVX-512F"); } if (cpuInfo.features & PAL_CPU_FEATURE_FMA3) { diff --git a/tests/tests.h b/tests/tests.h index 0f3c3e4..d4ca572 100644 --- a/tests/tests.h +++ b/tests/tests.h @@ -35,6 +35,7 @@ bool windowTest(); bool iconTest(); bool cursorTest(); bool inputWindowTest(); +bool systemCursorTest(); // opengl test bool openglTest(); diff --git a/tests/tests.lua b/tests/tests.lua index 25c8109..5af61d0 100644 --- a/tests/tests.lua +++ b/tests/tests.lua @@ -38,19 +38,20 @@ project "tests" "window_test.c", "icon_test.c", "cursor_test.c", - "input_window_test.c" + "input_window_test.c", + "system_cursor_test.c" } end if (PAL_BUILD_OPENGL) then files { - "opengl_test.c" + "opengl_test.c", + "opengl_fbconfig_test.c" } end if (PAL_BUILD_OPENGL and PAL_BUILD_VIDEO) then files { - "opengl_fbconfig_test.c", "opengl_context_test.c", "opengl_multi_context_test.c" } diff --git a/tests/tests_main.c b/tests/tests_main.c index 53f6831..a972fa1 100644 --- a/tests/tests_main.c +++ b/tests/tests_main.c @@ -33,16 +33,17 @@ int main(int argc, char** argv) registerTest("Icon Test", iconTest); registerTest("Cursor Test", cursorTest); registerTest("Input Window Test", inputWindowTest); + registerTest("System Cursor Test", systemCursorTest); #endif // PAL_HAS_VIDEO #if PAL_HAS_OPENGL registerTest("Opengl Test", openglTest); + registerTest("Opengl FBConfig Test", openglFBConfigTest); #endif // PAL_HAS_OPENGL // This test can run without video system so long as your have a valid // window #if PAL_HAS_OPENGL && PAL_HAS_VIDEO - registerTest("Opengl FBConfig Test", openglFBConfigTest); registerTest("Opengl Context Test", openglContextTest); registerTest("Opengl Multi Context Test", openglMultiContextTest); #endif // PAL_HAS_OPENGL diff --git a/tests/thread_test.c b/tests/thread_test.c index f5168ab..f016c8f 100644 --- a/tests/thread_test.c +++ b/tests/thread_test.c @@ -5,14 +5,6 @@ #define THREAD_TIME 1000 #define THREAD_COUNT 4 -// clang-format off -static const char* g_ThreadNames[THREAD_COUNT] = { - "Thread1", - "Thread2", - "Thread3", - "Thread4"}; -// clang-format on - static void* PAL_CALL worker(void* arg) { // palLog is thread safe so there should'nt be any race conditions @@ -63,31 +55,6 @@ bool threadTest() } } - // check if we support setting thread names and set for each created thread - PalThreadFeatures features = palGetThreadFeatures(); - if (features & PAL_THREAD_FEATURE_NAME) { - for (Int32 i = 0; i < THREAD_COUNT; i++) { - result = palSetThreadName(threads[i], g_ThreadNames[i]); - if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to set thread name %s", error); - return false; - } - } - } - - if (features & PAL_THREAD_FEATURE_NAME) { - char buffer[64] = {0}; - for (Int32 i = 0; i < THREAD_COUNT; i++) { - palGetThreadName(threads[i], 64, nullptr, buffer); - palLog( - nullptr, - "Thread %d: (%s) finnished successfully", - i + 1, - buffer); - } - } - palLog(nullptr, "All threads finished successfully"); // detach threads diff --git a/tests/time_test.c b/tests/time_test.c index 81ff7ec..02042bd 100644 --- a/tests/time_test.c +++ b/tests/time_test.c @@ -37,11 +37,14 @@ bool timeTest() while (totalTime < 5.0) { double now = getTime(&timer); totalTime = now - lastTime; + palLog( nullptr, "Frame %d, Total Time %f seconds", - frameCount++, + frameCount, totalTime); + + frameCount++; } palLog( diff --git a/tests/user_event_test.c b/tests/user_event_test.c index dd36b09..75fb7f6 100644 --- a/tests/user_event_test.c +++ b/tests/user_event_test.c @@ -41,7 +41,7 @@ bool userEventTest() result = palCreateEventDriver(&createInfo, &eventDriver); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create event driver %s", error); + palLog(nullptr, "Failed to create event driver: %s", error); return false; } diff --git a/tests/video_test.c b/tests/video_test.c index 423ece9..32e8861 100644 --- a/tests/video_test.c +++ b/tests/video_test.c @@ -17,7 +17,7 @@ bool videoTest() result = palInitVideo(nullptr, nullptr); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to initialize video %s", error); + palLog(nullptr, "Failed to initialize video: %s", error); return false; } @@ -25,130 +25,131 @@ bool videoTest() features = palGetVideoFeatures(); palLog(nullptr, "Supported Video Features:"); if (features & PAL_VIDEO_FEATURE_HIGH_DPI) { - palLog(nullptr, "High DPI windows"); + palLog(nullptr, " High DPI windows"); } if (features & PAL_VIDEO_FEATURE_MONITOR_SET_ORIENTATION) { - palLog(nullptr, "Setting monitor orientation"); + palLog(nullptr, " Setting monitor orientation"); } if (features & PAL_VIDEO_FEATURE_MONITOR_GET_ORIENTATION) { - palLog(nullptr, "Getting monitor orientation"); + palLog(nullptr, " Getting monitor orientation"); } if (features & PAL_VIDEO_FEATURE_BORDERLESS_WINDOW) { - palLog(nullptr, "Borderless windows"); + palLog(nullptr, " Borderless windows"); } if (features & PAL_VIDEO_FEATURE_TRANSPARENT_WINDOW) { - palLog(nullptr, "Transparent windows"); + palLog(nullptr, " Transparent windows"); } if (features & PAL_VIDEO_FEATURE_TOOL_WINDOW) { - palLog(nullptr, "Tool windows"); + palLog(nullptr, " Tool windows"); } if (features & PAL_VIDEO_FEATURE_MONITOR_SET_MODE) { - palLog(nullptr, "Setting monitor display mode"); + palLog(nullptr, " Setting monitor display mode"); } if (features & PAL_VIDEO_FEATURE_MONITOR_GET_MODE) { - palLog(nullptr, "Getting monitor display mode"); + palLog(nullptr, " Getting monitor display mode"); } if (features & PAL_VIDEO_FEATURE_MULTI_MONITORS) { - palLog(nullptr, "Multi monitors"); + palLog(nullptr, " Multi monitors"); } if (features & PAL_VIDEO_FEATURE_WINDOW_SET_SIZE) { - palLog(nullptr, "Setting window size"); + palLog(nullptr, " Setting window size"); } if (features & PAL_VIDEO_FEATURE_WINDOW_GET_SIZE) { - palLog(nullptr, "Getting window size"); + palLog(nullptr, " Getting window size"); } if (features & PAL_VIDEO_FEATURE_WINDOW_SET_POS) { - palLog(nullptr, "Setting window position"); + palLog(nullptr, " Setting window position"); } if (features & PAL_VIDEO_FEATURE_WINDOW_GET_POS) { - palLog(nullptr, "Getting window position"); + palLog(nullptr, " Getting window position"); } if (features & PAL_VIDEO_FEATURE_WINDOW_SET_STATE) { - palLog(nullptr, "Setting window state"); + palLog(nullptr, " Setting window state"); } if (features & PAL_VIDEO_FEATURE_WINDOW_GET_STATE) { - palLog(nullptr, "Getting window state"); + palLog(nullptr, " Getting window state"); } if (features & PAL_VIDEO_FEATURE_WINDOW_SET_VISIBILITY) { - palLog(nullptr, "Setting window visibility"); + palLog(nullptr, " Setting window visibility"); } if (features & PAL_VIDEO_FEATURE_WINDOW_GET_VISIBILITY) { - palLog(nullptr, "Getting window visibility"); + palLog(nullptr, " Getting window visibility"); } if (features & PAL_VIDEO_FEATURE_WINDOW_SET_TITLE) { - palLog(nullptr, "Setting window title"); + palLog(nullptr, " Setting window title"); } if (features & PAL_VIDEO_FEATURE_WINDOW_GET_TITLE) { - palLog(nullptr, "Getting window title"); + palLog(nullptr, " Getting window title"); } if (features & PAL_VIDEO_FEATURE_NO_MINIMIZEBOX) { - palLog(nullptr, "No minimize box for windows"); + palLog(nullptr, " No minimize box for windows"); } if (features & PAL_VIDEO_FEATURE_NO_MAXIMIZEBOX) { - palLog(nullptr, "No maximize box for windows"); + palLog(nullptr, " No maximize box for windows"); } if (features & PAL_VIDEO_FEATURE_CLIP_CURSOR) { - palLog(nullptr, "Clipping cursor (mouse)"); + palLog(nullptr, " Clipping cursor (mouse)"); } if (features & PAL_VIDEO_FEATURE_WINDOW_FLASH_CAPTION) { - palLog(nullptr, "Window titlebar flashing"); + palLog(nullptr, " Window titlebar flashing"); } if (features & PAL_VIDEO_FEATURE_WINDOW_FLASH_TRAY) { - palLog(nullptr, "Window icon on taskbar flashing"); + palLog(nullptr, " Window icon on taskbar flashing"); } if (features & PAL_VIDEO_FEATURE_WINDOW_FLASH_INTERVAL) { - palLog(nullptr, "Setting window flash interval"); + palLog(nullptr, " Setting window flash interval"); } if (features & PAL_VIDEO_FEATURE_WINDOW_SET_INPUT_FOCUS) { - palLog(nullptr, "Setting input window"); + palLog(nullptr, " Setting input window"); } if (features & PAL_VIDEO_FEATURE_WINDOW_GET_INPUT_FOCUS) { - palLog(nullptr, "Getting input window"); + palLog(nullptr, " Getting input window"); } if (features & PAL_VIDEO_FEATURE_WINDOW_SET_STYLE) { - palLog(nullptr, "Setting window style"); + palLog(nullptr, " Setting window style"); } if (features & PAL_VIDEO_FEATURE_WINDOW_GET_STYLE) { - palLog(nullptr, "Getting window style"); + palLog(nullptr, " Getting window style"); } if (features & PAL_VIDEO_FEATURE_CURSOR_SET_POS) { - palLog(nullptr, "Setting cursor position"); + palLog(nullptr, " Setting cursor position"); } if (features & PAL_VIDEO_FEATURE_CURSOR_GET_POS) { - palLog(nullptr, "Getting cursor position"); + palLog(nullptr, " Getting cursor position"); } // shutdown the video system palShutdownVideo(); + return true; } \ No newline at end of file diff --git a/tests/window_test.c b/tests/window_test.c index 070180e..f5f89c7 100644 --- a/tests/window_test.c +++ b/tests/window_test.c @@ -7,7 +7,7 @@ // make the window transparent if supported #define MAKE_TRANSPARENT 0 -#define OPACITY 0.5 // if transparent window is supported +#define OPACITY 0.8f // if transparent window is supported // make the window a tool window if supported #define MAKE_TOOL 0 @@ -18,7 +18,7 @@ // remove the maximize box if supported #define NO_MAXIMIZEBOX 0 -#define UNICODE_NAME 0 +#define UNICODE_NAME 1 #define DISPATCH_MODE_POLL 1 // use polling dispatch mode #if DISPATCH_MODE_POLL @@ -175,7 +175,7 @@ bool windowTest() result = palCreateEventDriver(&eventDriverCreateInfo, &eventDriver); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create event driver %s", error); + palLog(nullptr, "Failed to create event driver: %s", error); return false; } @@ -185,7 +185,7 @@ bool windowTest() result = palInitVideo(nullptr, eventDriver); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to initialize video %s", error); + palLog(nullptr, "Failed to initialize video: %s", error); return false; } @@ -239,7 +239,7 @@ bool windowTest() result = palCreateWindow(&createInfo, &window); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create window %s", error); + palLog(nullptr, "Failed to create window: %s", error); return false; } @@ -248,7 +248,7 @@ bool windowTest() result = palSetWindowOpacity(window, OPACITY); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to set window opacity %s", error); + palLog(nullptr, "Failed to set window opacity: %s", error); return false; } }