diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..6bb0457 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,41 @@ +# Contributing to PAL + +Everyone is welcome to contribute to PAL by submitting bug reports, +bug fixes, improving documentation, adding tests examples, telling others about PAL, +giving PAL a star, adding a new backend, etc. + +### Reporting Bugs + +Make sure the bug is not an API usage issue and you have the lastest version of PAL. +If the above is not the case, report the bug on our [GitHub Issue Tracker](https://github.com/nichcode/PAL/issues) using the bug report template. Please make sure you write a good bug report. + +## Requesting Features + +Requesting a feature which does not align with the goals of PAL (explicit, low-level) will likey +not be merge into PAL. This approach keeps project focused and consistent. + +Request features on our [GitHub Issue Tracker](https://github.com/nichcode/PAL/issues) +using the feature report template. Please explain into detail why your feature will work and if +possibly usages of it in use. + +## Coding Convention + +- **C99** for C source. + +- Naming convention: + - `lowerCamelCase` for functions and function parameters. (e.g. `palCreateWindow`, `windowHandle`). + - `PascalCase` for public types (e.g. `PalResult`). + - static internal variables uses 's_' prefix. (eg. `s_InternalData`). + - `snakeCase` for public and internal macros. (e.g. `PAL_DEFAULT_ALIGNMENT`). + + +## Contributing code + +**PAL is released under the Zlib License and every +code contributed to PAL must agree to Zlib licensing terms.** + +Pull request checklist: + +- Ensure it compiles on **all** supported platforms. +- Ensure the PR is focused and changes are relevant to the feature or bug fix. +- Ensure your code is formatted with clang-format using the `clang-format` file in the repo. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..1291251 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,28 @@ +--- +name: Bug report +about: Create a report to help us improve +labels: bug +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Operating system (please complete the following information)**: +- OS: [e.g. windows] +- Version: [e.g. 11] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/config.yaml b/.github/ISSUE_TEMPLATE/config.yaml new file mode 100644 index 0000000..ec4bb38 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yaml @@ -0,0 +1 @@ +blank_issues_enabled: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..31b18c9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,17 @@ +--- +name: Feature request +about: Suggest an idea for this project +labels: enhancement +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/CHANGELOG.md b/CHANGELOG.md index c5c7266..a8c99ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# Changelog +# CHANGELOG ## [1.0.0] - 2025-09-27 - Initial stable release of PAL. @@ -68,4 +68,56 @@ reflecting its role as the primary explicit foundation for OS and graphics abstr ### Notes - No API or ABI changes - existing code remains compatible. -- Safe upgrade from **v1.1.0** - just rebuild your project after updating. \ No newline at end of file +- Safe upgrade from **v1.1.0** - just rebuild your project after updating. + +## [1.3.0] - 2025-11-21 + +### Features +- **Video:** Added Wayland backend support +- **Video:** Added **palGetVideoFeaturesEx()** to check old and extended supported features. +- **Video:** Added **palGetWindowHandleInfoEx()** to get extended window handles. +- **Video:** Added **palGetRawMouseWheelDelta()** to get raw mouse wheel delta. +- **Video:** Added **PAL_CONFIG_BACKEND_GLES** to `PalFBConfigBackend` enum. +- **Video:** Added **palSetPreferredInstance()** to set the native instance or display PAL video should use rather than creating a new one. + +- **Core:** Added **palPackFloat()** to combine two floats into a single Int64 integer. +- **Core:** Added **palUnpackFloat()** to retreive two floats from a single Int64 integer. + +- **OpenGL:** Added **palGLSetInstance()** to set the native instance or display PAL opengl should use. This must be set before calling **palInitGL()**. +- **OpenGL:** Added **palGLGetBackend()** to get the opengl backend. + +- **Event:** Added **PAL_EVENT_WINDOW_DECORATION_MODE** to `PalEventType` enum. +- **Event:** Added **PalDecorationMode** enum. + +### Tests +- Added native integration example: demonstrating **Native API Integration with PAL API**. see +**native_integration_test.c**. + +- Added native instance example: demonstrating **Native Instance or Display Integration with PAL API**. +see **native_instance_test.c**. + +- Added custom decoration example: demonstrating **Custom Window Decoration**. +see **custom_decoration_test.c**. + +### Notes +- **No ABI changes** - existing code remains compatible. + +- **OpenGL tests may fail** - The opengl system now needs to call **palGLSetInstance()** +to set the instance or display before initializing. Failure to do this fails. +This is a runtime behavior change. The tests are updated in the repo. + +- **Pal mouse button event** - The `event.data` now packs both the button and +wayland seat serial (If on wayland). Existing code that reads as a single value +without unpacking will see different values. +This is a runtime behavior change. The **input_window_test.c** has been updated in the repo. + +- **palJoinThread()** - ABI remains unchanged but now takes the address +of a pointer variable for the return value of the thread. +PAL internally reinterpreted into a pointer-to-pointer. This is for ABI stability. + +Example – Join thread and get the return value +```c +void* retval; +palJoinThread(thread, &retval); +``` + diff --git a/LICENSE.txt b/LICENSE.txt index ee6114c..ba74348 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,7 +1,7 @@ zlib License -Copyright (C) 2025 Nicholas Agbo +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 diff --git a/README.md b/README.md index 3e9b380..6438f82 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,12 @@ 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. -Originally named as **Platform Abstraction Layer**, -PAL has evolved into **Prime Abstraction Layer** — the **first** and most **direct** layer between your engine or software and the operating system. - 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. +The goal is very simple, write low-level cross-platform code without having per platform files +all over the place. Example: `renderer_vulkan`, `renderer_d3d12`, `window_win32`, etc. +PAL makes it possible to safely mix native API with its API in a very straight forward way. This is one of the main reasons why PAL exists. + 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 @@ -93,9 +94,9 @@ For more detailed examples, see the [tests folder](./tests) tests folder, which ## Supported Platforms - Windows (Vista+) - Linux (X11) +- Linux (Wayland) ## Planned Platforms -- Linux (Wayland) - macOS (Cocoa) - Android - iOS diff --git a/include/pal/pal_core.h b/include/pal/pal_core.h index ce82697..a21c117 100644 --- a/include/pal/pal_core.h +++ b/include/pal/pal_core.h @@ -1,7 +1,7 @@ /** -Copyright (C) 2025 Nicholas Agbo +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 @@ -33,6 +33,7 @@ freely, subject to the following restrictions: #define _PAL_CORE_H #include +#include #ifdef __cplusplus #define PAL_EXTERN_C extern "C" @@ -75,7 +76,14 @@ typedef _Bool bool; #define PAL_API PAL_EXTERN_C #endif // _PAL_BUILD_DLL +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#define PAL_BIG_ENDIAN 1 +#else +#define PAL_BIG_ENDIAN 0 +#endif // __ORDER_BIG_ENDIAN__ + #define PAL_BIT(x) 1 << x +#define PAL_BIT64(x) 1ULL << x /** * @brief A signed 8-bit integer @@ -462,6 +470,33 @@ static inline Int64 PAL_CALL palPackPointer(void* ptr) return (Int64)(UintPtr)ptr; } +/** + * @brief Combine two floats into a single 64-bit signed integer. + * + * @return The combined 64-bit signed integer. + * + * Thread safety: This function is thread safe. + * + * @since 1.3 + * @ingroup pal_core + * @sa palUnpackFloat + */ +static inline Int64 PAL_CALL palPackFloat( + float low, + float high) +{ + Int64 combined = 0; +#if PAL_BIG_ENDIAN + memcpy(&((Uint32*)&combined)[0], &high, sizeof(float)); + memcpy(&((Uint32*)&combined)[1], &low, sizeof(float)); +#else + memcpy(&((Uint32*)&combined)[0], &low, sizeof(float)); + memcpy(&((Uint32*)&combined)[1], &high, sizeof(float)); +#endif // PAL_BIG_ENDIAN + + return combined; +} + /** * @brief Retrieve two 32-bit unsigned integers from a 64-bit signed integer. * @@ -531,6 +566,44 @@ static inline void* PAL_CALL palUnpackPointer(Int64 data) return (void*)(UintPtr)data; } +/** + * @brief Retrieve two floats from a 64-bit signed integer. + * + * @param[out] outLow Low value of the 64-bit signed integer. + * @param[out] outHigh High value of the 64-bit signed integer. + * + * Thread safety: This function is thread-safe if `outLow` and `outHigh` are + * thread local. + * + * @since 1.3 + * @ingroup pal_core + * @sa palPackFloat + */ +static inline void PAL_CALL palUnpackFloat( + Int64 data, + float* low, + float* high) +{ +#if PAL_BIG_ENDIAN + if (low) { + memcpy(low, &((Uint32*)&data)[1], sizeof(float)); + } + + if (high) { + memcpy(high, &((Uint32*)&data)[0], sizeof(float)); + } +#else + if (low) { + memcpy(low, &((Uint32*)&data)[0], sizeof(float)); + } + + if (high) { + memcpy(high, &((Uint32*)&data)[1], sizeof(float)); + } + +#endif // PAL_BIG_ENDIAN +} + /** @} */ // end of pal_core group #endif // _PAL_CORE_H \ No newline at end of file diff --git a/include/pal/pal_event.h b/include/pal/pal_event.h index ff422c0..f85a7b0 100644 --- a/include/pal/pal_event.h +++ b/include/pal/pal_event.h @@ -1,7 +1,7 @@ /** -Copyright (C) 2025 Nicholas Agbo +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 @@ -100,6 +100,21 @@ typedef bool(PAL_CALL* PalPollFn)( void* userData, PalEvent* outEvent); +/** + * @enum PalDecorationMode + * @brief Decoration types. This is not a bitmask enum. + * + * All decoration types follow the format `PAL_DECORATION_MODE_**` for + * consistency and API use. + * + * @since 1.3 + * @ingroup pal_event + */ +typedef enum { + PAL_DECORATION_MODE_CLIENT_SIDE, + PAL_DECORATION_MODE_SERVER_SIDE +} PalDecorationMode; + /** * @enum PalEventType * @brief Event types. This is not a bitmask enum. @@ -259,7 +274,7 @@ typedef enum { /** * PAL_EVENT_MOUSE_BUTTONDOWN * - * event.data : mouse button + * event.data : lower 32 bits = button, upper 32 bits = serial * * event.data2 : window * @@ -271,7 +286,7 @@ typedef enum { /** * PAL_EVENT_MOUSE_BUTTONUP * - * event.data : mouse button + * event.data : lower 32 bits = button, upper 32 bits = serial * * event.data2 : window * @@ -348,6 +363,18 @@ typedef enum { */ PAL_EVENT_KEYCHAR, + /** + * PAL_EVENT_WINDOW_DECORATION_MODE + * + * event.data : negotiated decorations mode + * + * event.data2 : window + * + * Use inline helpers: + * - palUnpackPointer() + */ + PAL_EVENT_WINDOW_DECORATION_MODE, + PAL_EVENT_MAX } PalEventType; diff --git a/include/pal/pal_opengl.h b/include/pal/pal_opengl.h index e31ca92..a54a5e7 100644 --- a/include/pal/pal_opengl.h +++ b/include/pal/pal_opengl.h @@ -1,7 +1,7 @@ /** -Copyright (C) 2025 Nicholas Agbo +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 @@ -169,7 +169,7 @@ typedef struct { */ typedef struct { void* display; /**< Can be nullptr depending on platform (eg. Windows).*/ - void* window; /**< Must not be nullptr.*/ + void* window; /**< Must not be nullptr. (egl_wl_window on Wayland)*/ } PalGLWindow; /** @@ -215,6 +215,7 @@ typedef struct { * @since 1.0 * @ingroup pal_opengl * @sa palShutdownGL + * @sa palGLSetInstance */ PAL_API PalResult PAL_CALL palInitGL(const PalAllocator* allocator); @@ -317,6 +318,9 @@ PAL_API const PalGLFBConfig* PAL_CALL palGetClosestGLFBConfig( * window. Once set, it cannot be changed. To change it, you must destroy the * window and recreate it. * + * On Wayland: PalGLContextCreateInfo::PalGLWindow::window is the wl_egl_window + * not the wl_surface. + * * @param[in] info Pointer to a PalGLContextCreateInfo struct that specifies * paramters. Must not be nullptr. * @param[out] outContext Pointer to a PalGLContext to recieve the created @@ -363,6 +367,10 @@ PAL_API void PAL_CALL palDestroyGLContext(PalGLContext* context); * used to create the context, this function fails and returns * `PAL_RESULT_INVALID_GL_WINDOW`. * + * If the window was created with a different display other than the one + * passed to the opengl system, this function fails and returns + * `PAL_RESULT_INVALID_GL_WINDOW`. see palGLSetInstance() + * * @param[in] glWindow Pointer to the opengl window. * @param[in] context Pointer to the context to make current. * @@ -441,6 +449,40 @@ PAL_API PalResult PAL_CALL palSwapBuffers( */ PAL_API PalResult PAL_CALL palSetSwapInterval(Int32 interval); +/** + * @brief Set the native application instance or display for the opengl system. + * + * This must be called before palInitGL() is called. if palInitGL() is called + * before this function, + * it fails and returns `PAL_RESULT_PLATFORM_FAILURE` will be returned. + * + * On Linux: This is the Display associated with the connection. + + * On Windows: This is the HINSTANCE of the process. + * + * Thread safety: This function is thread safe. + * + * @note The provided instance will not be freed by the opengl system. + * + * @since 1.3 + * @ingroup pal_opengl + * @sa palInitGL + */ +PAL_API void PAL_CALL palGLSetInstance(void* instance); + +/** + * @brief Get the backend of the opengl system. + * + * The opengl system must be initialized before this call. + * Possible values are `wgl`, `glx`, `gles`, `egl`. + * + * Thread safety: This function is thread safe. + * + * @since 1.3 + * @ingroup pal_opengl + */ +PAL_API const char* PAL_CALL palGLGetBackend(); + /** @} */ // end of pal_opengl group #endif // _PAL_OPENGL_H \ No newline at end of file diff --git a/include/pal/pal_system.h b/include/pal/pal_system.h index c8d71bf..9e67153 100644 --- a/include/pal/pal_system.h +++ b/include/pal/pal_system.h @@ -1,6 +1,6 @@ /** -Copyright (C) 2025 Nicholas Agbo +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 diff --git a/include/pal/pal_thread.h b/include/pal/pal_thread.h index c18d933..666f27e 100644 --- a/include/pal/pal_thread.h +++ b/include/pal/pal_thread.h @@ -1,7 +1,7 @@ /** -Copyright (C) 2025 Nicholas Agbo +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 @@ -179,8 +179,13 @@ PAL_API PalResult PAL_CALL palCreateThread( /** * @brief Wait for the provided thread to finish executing. * + * After the thread is done executing, it is freed automatically and + * must not be used anymore nor detached. + * * @param[in] thread Pointer to the thread. * @param[out] retval Optionally pointer to get the threads exit value. + * Pass the address of the pointer. Internally it will be reinterpreted into a + * pointer-to-pointer. This is for ABI stability. * * @return `PAL_RESULT_SUCCESS` on success or a result code on * failure. Call palFormatResult() for more information. @@ -201,6 +206,8 @@ PAL_API PalResult PAL_CALL palJoinThread( * After this call, the thread cannot be attached or used anymore. * If the thread is invalid or nullptr, this function returns silently. * + * This must not be called on a thread that has been attached. + * * @param[in] thread Pointer to the thread to detach. * * Thread safety: This function is thread safe. diff --git a/include/pal/pal_video.h b/include/pal/pal_video.h index 87c3d01..d06c968 100644 --- a/include/pal/pal_video.h +++ b/include/pal/pal_video.h @@ -1,7 +1,7 @@ /** -Copyright (C) 2025 Nicholas Agbo +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 @@ -111,9 +111,62 @@ 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), + PAL_VIDEO_FEATURE_WINDOW_SET_ICON = PAL_BIT(31) } PalVideoFeatures; +/** + * @enum PalVideoFeatures64 + * @brief Extended Video system features. + * + * All extended video features follow the format `PAL_VIDEO_FEATURE64_**` for + * consistency and API use. + * + * @since 1.3 + * @ingroup pal_video + */ +typedef enum { + PAL_VIDEO_FEATURE64_HIGH_DPI = PAL_BIT64(0), + PAL_VIDEO_FEATURE64_MONITOR_SET_ORIENTATION = PAL_BIT64(1), + PAL_VIDEO_FEATURE64_MONITOR_GET_ORIENTATION = PAL_BIT64(2), + PAL_VIDEO_FEATURE64_BORDERLESS_WINDOW = PAL_BIT64(3), + PAL_VIDEO_FEATURE64_TRANSPARENT_WINDOW = PAL_BIT64(4), + PAL_VIDEO_FEATURE64_TOOL_WINDOW = PAL_BIT64(5), + PAL_VIDEO_FEATURE64_MONITOR_SET_MODE = PAL_BIT64(6), + PAL_VIDEO_FEATURE64_MONITOR_GET_MODE = PAL_BIT64(7), + PAL_VIDEO_FEATURE64_MULTI_MONITORS = PAL_BIT64(8), + PAL_VIDEO_FEATURE64_WINDOW_SET_SIZE = PAL_BIT64(9), + PAL_VIDEO_FEATURE64_WINDOW_GET_SIZE = PAL_BIT64(10), + PAL_VIDEO_FEATURE64_WINDOW_SET_POS = PAL_BIT64(11), + PAL_VIDEO_FEATURE64_WINDOW_GET_POS = PAL_BIT64(12), + PAL_VIDEO_FEATURE64_WINDOW_SET_STATE = PAL_BIT64(13), + PAL_VIDEO_FEATURE64_WINDOW_GET_STATE = PAL_BIT64(14), + PAL_VIDEO_FEATURE64_WINDOW_SET_VISIBILITY = PAL_BIT64(15), + PAL_VIDEO_FEATURE64_WINDOW_GET_VISIBILITY = PAL_BIT64(16), + PAL_VIDEO_FEATURE64_WINDOW_SET_TITLE = PAL_BIT64(17), + PAL_VIDEO_FEATURE64_WINDOW_GET_TITLE = PAL_BIT64(18), + PAL_VIDEO_FEATURE64_NO_MAXIMIZEBOX = PAL_BIT64(19), + PAL_VIDEO_FEATURE64_NO_MINIMIZEBOX = PAL_BIT64(20), + PAL_VIDEO_FEATURE64_CLIP_CURSOR = PAL_BIT64(21), + PAL_VIDEO_FEATURE64_WINDOW_FLASH_CAPTION = PAL_BIT64(22), + PAL_VIDEO_FEATURE64_WINDOW_FLASH_TRAY = PAL_BIT64(23), + PAL_VIDEO_FEATURE64_WINDOW_FLASH_INTERVAL = PAL_BIT64(24), + PAL_VIDEO_FEATURE64_WINDOW_SET_INPUT_FOCUS = PAL_BIT64(25), + PAL_VIDEO_FEATURE64_WINDOW_GET_INPUT_FOCUS = PAL_BIT64(26), + PAL_VIDEO_FEATURE64_WINDOW_SET_STYLE = PAL_BIT64(27), + PAL_VIDEO_FEATURE64_WINDOW_GET_STYLE = PAL_BIT64(28), + PAL_VIDEO_FEATURE64_CURSOR_SET_POS = PAL_BIT64(29), + PAL_VIDEO_FEATURE64_CURSOR_GET_POS = PAL_BIT64(30), + PAL_VIDEO_FEATURE64_WINDOW_SET_ICON = PAL_BIT64(31), + PAL_VIDEO_FEATURE64_TOPMOST_WINDOW = PAL_BIT64(32), + PAL_VIDEO_FEATURE64_DECORATED_WINDOW = PAL_BIT64(33), + PAL_VIDEO_FEATURE64_CURSOR_SET_VISIBILITY = PAL_BIT64(34), + PAL_VIDEO_FEATURE64_WINDOW_GET_MONITOR = PAL_BIT64(35), + PAL_VIDEO_FEATURE64_MONITOR_GET_PRIMARY = PAL_BIT64(36), + PAL_VIDEO_FEATURE64_FOREIGN_WINDOWS = PAL_BIT64(37), + PAL_VIDEO_FEATURE64_MONITOR_VALIDATE_MODE = PAL_BIT64(38), + PAL_VIDEO_FEATURE64_WINDOW_SET_CURSOR = PAL_BIT64(39) +} PalVideoFeatures64; + /** * @enum PalOrientation * @brief Orientation types for a monitor. @@ -201,7 +254,8 @@ typedef enum { PAL_CONFIG_BACKEND_EGL, PAL_CONFIG_BACKEND_GLX, PAL_CONFIG_BACKEND_WGL, - PAL_CONFIG_BACKEND_PAL_OPENGL /**< Use PAL opengl module backend.*/ + PAL_CONFIG_BACKEND_PAL_OPENGL, /**< Use PAL opengl module backend.*/ + PAL_CONFIG_BACKEND_GLES } PalFBConfigBackend; /** @@ -593,9 +647,24 @@ typedef struct { */ typedef struct { void* nativeDisplay; /**< The platform (OS) display.*/ - void* nativeWindow; /**< The platform (OS) handle.*/ + void* nativeWindow; /**< The window platform (OS) handle.*/ } PalWindowHandleInfo; +/** + * @struct PalWindowHandleInfoEx + * @brief Extended information about a window handle. + * + * @since 1.3 + * @ingroup pal_video + */ +typedef struct { + void* nativeDisplay; /**< The platform (OS) display.*/ + void* nativeWindow; /**< The window platform (OS) handle.*/ + void* nativeHandle1; /**< Extra window handle (xdgSurface)*/ + void* nativeHandle2; /**< Extra window handle (xdgToplevel)*/ + void* nativeHandle3; /**< Extra window handle (wl_egl_window)*/ +} PalWindowHandleInfoEx; + /** * @struct PalWindowCreateInfo * @brief Creation parameters for a window. @@ -640,6 +709,7 @@ typedef struct { * @since 1.0 * @ingroup pal_video * @sa palShutdownVideo + * @sa palSetPreferredInstance */ PAL_API PalResult PAL_CALL palInitVideo( const PalAllocator* allocator, @@ -689,6 +759,23 @@ PAL_API void PAL_CALL palUpdateVideo(); */ PAL_API PalVideoFeatures PAL_CALL palGetVideoFeatures(); +/** + * @brief Get the supported features of the video system. + * + * The video system must be initialized before this call. + * This returns the supported features from palGetVideoFeatures() + * and adds additionally supported features. + * + * @return video features on success or `0` on failure. + * + * Thread safety: This function is thread safe. + * + * @since 1.3 + * @ingroup pal_video + * @sa palInitVideo + */ +PAL_API PalVideoFeatures64 PAL_CALL palGetVideoFeaturesEx(); + /** * @brief Set the FBConfig for the video system. * @@ -762,6 +849,7 @@ PAL_API PalResult PAL_CALL palEnumerateMonitors( * @brief Get the primary connected monitor. * * The video system must be initialized before this call. + * `PAL_VIDEO_FEATURE_MONITOR_GET_PRIMARY` must be supported. * * The monitor handle must not be freed by the user, they are managed by the * platform (OS). @@ -868,6 +956,7 @@ PAL_API PalResult PAL_CALL palGetCurrentMonitorMode( * PAL only validates the monitor display mode pointer not the values. To be * safe, users must get the monitor mode from palEnumerateMonitorModes() or call * palValidateMonitorMode() to validate before switching. + * palValidateMonitorMode() is not supported on all platforms. * * If the monitor display mode submitted is invalid, this function might fail * depending on the platform (OS). @@ -892,6 +981,7 @@ PAL_API PalResult PAL_CALL palSetMonitorMode( * @brief Check if a monitor display mode is valid on the provided monitor. * * The video system must be initialized before this call. + * `PAL_VIDEO_FEATURE_MONITOR_VALIDATE_MODE` must be supported. * * @param[in] monitor The monitor. * @param[in] mode Pointer to a PalMonitorMode to validate. @@ -946,6 +1036,15 @@ PAL_API PalResult PAL_CALL palSetMonitorOrientation( * * Thread safety: This function must only be called from the main thread. * + * @note On Wayland + * + * - creating non resizable windows is not supported. + * PAL will always creating resizable windows. + * + * - Creating windows on a specific monitor is not supported. + * + * - Creating hidden window is not supported. It will be ignored. + * * @since 1.0 * @ingroup pal_video */ @@ -1026,6 +1125,8 @@ PAL_API PalResult PAL_CALL palMaximizeWindow(PalWindow* window); * * Thread safety: This function must only be called from the main thread. * + * @note Wayland does not support restoring a minimized windows. + * * @since 1.0 * @ingroup pal_video * @sa palMinimizeWindow @@ -1126,6 +1227,7 @@ PAL_API PalResult PAL_CALL palGetWindowStyle( * @brief Get the monitor the provided window is currently on. * * The video system must be initialized before this call. + * `PAL_VIDEO_FEATURE_WINDOW_GET_MONITOR` must be supported. * * @param[in] window Pointer to the window. * @param[out] outMonitor Pointer to a PalMonitor to recieve the monitor. @@ -1331,10 +1433,31 @@ PAL_API void PAL_CALL palGetMouseDelta( * @since 1.0 * @ingroup pal_video */ -void PAL_CALL palGetMouseWheelDelta( +PAL_API void PAL_CALL palGetMouseWheelDelta( Int32* dx, Int32* dy); +/** + * @brief Get the raw wheel delta of the mouse in floats. + * + * The video system must be initialized before this call. + * The wheel delta will be updated when palUpdateVideo() is called. + * + * @param[in] dx Pointer to recieve the mouse wheel delta x in floats. Can be + * nullptr. + * @param[in] dy Pointer to recieve the mouse wheel delta y in floats. Can be + * nullptr. + * + * Thread safety: This function is thread-safe if `dx` and `dy` are thread + * local. + * + * @since 1.3 + * @ingroup pal_video + */ +PAL_API void PAL_CALL palGetRawMouseWheelDelta( + float* dx, + float* dy); + /** * @brief Check if the provided window is visible. * @@ -1384,6 +1507,27 @@ PAL_API PalWindow* PAL_CALL palGetFocusWindow(); */ PAL_API PalWindowHandleInfo PAL_CALL palGetWindowHandleInfo(PalWindow* window); +/** + * @brief Get the native handles of the provided window. + * + * The video system must be initialized before this call. + * + * On Wayland: `PalWindowHandleInfoEx::nativeHandle1`, + * `PalWindowHandleInfoEx::nativeHandle2` and + * `PalWindowHandleInfoEx::nativeHandle3` are xdg_surface, xdg_toplevel + * and wl_egl_window respectively. + * + * @param[in] window Pointer to the window. + * + * @return The native handles of the window on success or nullptr on failure. + * + * Thread safety: This function is thread-safe. + * + * @since 1.3 + * @ingroup pal_video + */ +PAL_API PalWindowHandleInfoEx PAL_CALL palGetWindowHandleInfoEx(PalWindow* w); + /** * @brief Set the opacity of the provided window. * @@ -1650,6 +1794,8 @@ PAL_API void PAL_CALL palDestroyCursor(PalCursor* cursor); * @brief Show or hide the cursor. * * The video system must be initialized before this call. + * `PAL_VIDEO_FEATURE_CURSOR_SET_VISIBILITY` must be supported. + * * This affects all created cursors since the platform (OS) merges all cursors * into a single one on the screen. * @@ -1784,6 +1930,7 @@ PAL_API void* PAL_CALL palGetInstance(); * @brief Attach a foreign or native window to PAL video system. * * The video system must be initialized before this call. + * `PAL_VIDEO_FEATURE_FOREIGN_WINDOWS` must be supported. * * This function registers the provided window with PAL video system so it * can manage events and use its functionality/API for the provided window. @@ -1824,6 +1971,7 @@ PAL_API PalResult PAL_CALL palAttachWindow( * @brief Detach a foreign or native window from PAL video system. * * The video system must be initialized before this call. + * `PAL_VIDEO_FEATURE_FOREIGN_WINDOWS` must be supported. * * This function unregisters the provided window from PAL video system. * The window must not be owned by PAL otherwise the function fails @@ -1854,6 +2002,27 @@ PAL_API PalResult PAL_CALL palDetachWindow( PalWindow* window, void** outWindowHandle); +/** + * @brief Set the preferred instance the video system should use. + * + * This function must be called before palInitVideo(). This will be ignored + * if the video system is already initialized. + * If there is no preferred instance set, the video system creates one. + * + * On Linux: This is the Display associated with the connection. + + * On Windows: This is the HINSTANCE of the process. + * + * Thread safety: This function must be called from the main thread. + * + * @note The provided instance will not be freed by the video system. + * + * @since 1.3 + * @ingroup pal_video + * @sa palInitVideo + */ +PAL_API void PAL_CALL palSetPreferredInstance(void* instance); + /** @} */ // end of pal_video group #endif // _PAL_VIDEO_H \ No newline at end of file diff --git a/pal.lua b/pal.lua index ce17da0..ecf4681 100644 --- a/pal.lua +++ b/pal.lua @@ -86,13 +86,13 @@ project "PAL" files { "src/video/pal_video_linux.c" } -- check for wayland support. This is cross compiler - local paths = { + local waylandPaths = { "/usr/include/wayland-client.h", "/usr/include/x86_64-linux-gnu/wayland-client.h" } local found = false - for _, path in ipairs(paths) do + for _, path in ipairs(waylandPaths) do local file = io.open(path, "r") if file then file:close() @@ -106,6 +106,28 @@ project "PAL" else defines { "PAL_HAS_WAYLAND=0" } end + + -- -- check for X11 support. This is cross compiler + local XPaths = { + "/usr/include/X11/Xlib.h", + "/usr/include/x86_64-linux-gnu/X11/Xlib.h" + } + + found = false + for _, path in ipairs(XPaths) do + local file = io.open(path, "r") + if file then + file:close() + found = true + break + end + end + + if found then + defines { "PAL_HAS_X11=1" } + else + defines { "PAL_HAS_X11=0" } + end filter {} end diff --git a/src/opengl/pal_opengl_linux.c b/src/opengl/pal_opengl_linux.c index 46953f4..c8b3b6a 100644 --- a/src/opengl/pal_opengl_linux.c +++ b/src/opengl/pal_opengl_linux.c @@ -1,7 +1,7 @@ /** -Copyright (C) 2025 Nicholas Agbo +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 @@ -25,9 +25,12 @@ freely, subject to the following restrictions: // Includes // ================================================== +#define _GNU_SOURCE +#define _POSIX_C_SOURCE 200112L #include "pal/pal_opengl.h" #include +#include #include #include #include @@ -59,16 +62,11 @@ 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_OPENGL_ES_BIT 0x0001 +#define EGL_OPENGL_ES_API 0x30A0 #define EGL_NO_CONTEXT EGL_CAST(EGLContext, 0) #define EGL_NO_DISPLAY EGL_CAST(EGLDisplay, 0) #define EGL_NO_SURFACE EGL_CAST(EGLSurface, 0) @@ -107,6 +105,7 @@ typedef void* EGLNativeDisplayType; #define EGL_WINDOW_BIT 0x0004 #define EGL_CONTEXT_MAJOR_VERSION 0x3098 #define EGL_CONTEXT_MINOR_VERSION 0x30FB +#define EGL_CONTEXT_CLIENT_VERSION 0x3098 #define EGL_CONTEXT_MAJOR_VERSION_KHR 0x3098 #define EGL_CONTEXT_MINOR_VERSION_KHR 0x30FB @@ -127,7 +126,6 @@ typedef void* EGLNativeDisplayType; #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*); @@ -208,6 +206,13 @@ typedef EGLSurface (*eglCreateWindowSurfaceFn)( const EGLint*); typedef const GLubyte* (*glGetStringFn)(GLenum); +typedef void(PAL_GL_APIENTRY* glClearFn)(Uint32); + +typedef void(PAL_GL_APIENTRY* glClearColorFn)( + float, + float, + float, + float); typedef struct { bool used; @@ -218,6 +223,8 @@ typedef struct { typedef struct { bool initialized; Int32 maxContextData; + EGLenum apiType; + int apiTypeBit; const PalAllocator* allocator; eglGetProcAddressFn eglGetProcAddress; @@ -240,8 +247,11 @@ typedef struct { eglCreateWindowSurfaceFn eglCreateWindowSurface; glGetStringFn glGetString; + glClearColorFn glClearColor; + glClearFn glClear; void* handle; + void* platformDisplay; ContextData* contextData; EGLDisplay display; PalGLInfo info; @@ -333,6 +343,8 @@ static void freeContextData(PalGLContext* context) } } +void palSetLastPlatformError(Uint32 e); + // ================================================== // Public API // ================================================== @@ -347,6 +359,11 @@ PalResult PAL_CALL palInitGL(const PalAllocator* allocator) return PAL_RESULT_INVALID_ALLOCATOR; } + if (!s_GL.platformDisplay) { + palSetLastPlatformError(errno); + return PAL_RESULT_PLATFORM_FAILURE; + } + s_GL.maxContextData = 16; // initial size s_GL.contextData = palAllocate( s_GL.allocator, @@ -359,6 +376,7 @@ PalResult PAL_CALL palInitGL(const PalAllocator* allocator) s_GL.handle = dlopen("libEGL.so", RTLD_LAZY); if (!s_GL.handle) { + palSetLastPlatformError(errno); return PAL_RESULT_PLATFORM_FAILURE; } @@ -415,6 +433,9 @@ PalResult PAL_CALL palInitGL(const PalAllocator* allocator) s_GL.eglCreateWindowSurface = (eglCreateWindowSurfaceFn)s_GL.eglGetProcAddress( "eglCreateWindowSurface"); + s_GL.glClearColor = (glClearColorFn)s_GL.eglGetProcAddress("glClearColor"); + s_GL.glClear = (glClearFn)s_GL.eglGetProcAddress("glClear"); + if (!s_GL.eglBindAPI || !s_GL.eglChooseConfig || !s_GL.eglCreateContext || @@ -433,46 +454,91 @@ PalResult PAL_CALL palInitGL(const PalAllocator* allocator) !s_GL.eglQueryString || !s_GL.eglGetConfigs || !s_GL.eglCreateWindowSurface) { + palSetLastPlatformError(errno); return PAL_RESULT_PLATFORM_FAILURE; } - // create a dummy context - if (!s_GL.eglBindAPI(EGL_OPENGL_API)) { + // get backend type + const char* session = getenv("XDG_SESSION_TYPE"); + if (session) { + if (strcmp(session, "wayland") == 0) { + s_GL.apiType = EGL_OPENGL_ES_API; + s_GL.apiTypeBit = EGL_OPENGL_ES2_BIT; // default + + } else { + s_GL.apiType = EGL_OPENGL_API; + s_GL.apiTypeBit = EGL_OPENGL_BIT; + } + } + + if (!s_GL.eglBindAPI(s_GL.apiType)) { + palSetLastPlatformError(errno); return PAL_RESULT_PLATFORM_FAILURE; } - EGLDisplay display = s_GL.eglGetDisplay(EGL_DEFAULT_DISPLAY); + EGLDisplay display = s_GL.eglGetDisplay(s_GL.platformDisplay); + EGLDisplay * tmpDisplay = EGL_NO_DISPLAY; if (display == EGL_NO_DISPLAY) { + palSetLastPlatformError(errno); return PAL_RESULT_PLATFORM_FAILURE; } if (!s_GL.eglInitialize(display, nullptr, nullptr)) { + palSetLastPlatformError(errno); return PAL_RESULT_PLATFORM_FAILURE; } // use a simple FBConfig EGLConfig config; int numConfigs; + EGLint type; EGLint attribs[] = { EGL_RENDERABLE_TYPE, - EGL_OPENGL_BIT, + s_GL.apiTypeBit, 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) + if (!config || numConfigs == 0) { + // API bind type might not support puffer + // create a default display and use that + tmpDisplay = s_GL.eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (display == EGL_NO_DISPLAY) { + palSetLastPlatformError(errno); + return PAL_RESULT_PLATFORM_FAILURE; + } + + if (!s_GL.eglInitialize(tmpDisplay, nullptr, nullptr)) { + palSetLastPlatformError(errno); + return PAL_RESULT_PLATFORM_FAILURE; + } + } else { + // set the tmp display to our display + tmpDisplay = display; + } + + s_GL.eglChooseConfig(tmpDisplay, attribs, &config, 1, &numConfigs); + if (!config) { + palSetLastPlatformError(errno); + return PAL_RESULT_PLATFORM_FAILURE; + } + + s_GL.eglGetConfigAttrib(tmpDisplay, config, EGL_RENDERABLE_TYPE, &type); + if (!(type & s_GL.apiTypeBit)) { + // we must support the required API + palSetLastPlatformError(errno); 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; + if (s_GL.apiType == EGL_OPENGL_API) { + if (!(type & EGL_OPENGL_ES2_BIT)) { + // FIXME: create a dummy window if EGL_OPENGL_ES2_BIT + palSetLastPlatformError(errno); + return PAL_RESULT_PLATFORM_FAILURE; + } } EGLSurface surface = EGL_NO_SURFACE; @@ -482,35 +548,56 @@ PalResult PAL_CALL palInitGL(const PalAllocator* allocator) EGL_NONE}; surface = s_GL.eglCreatePbufferSurface( - display, + tmpDisplay, config, pBufferAttribs); if (surface == EGL_NO_SURFACE) { + palSetLastPlatformError(errno); 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.apiType == EGL_OPENGL_API) { + EGLint contextAttrib[] = { + EGL_CONTEXT_MAJOR_VERSION, 2, + EGL_CONTEXT_MINOR_VERSION, 1, + EGL_NONE}; + + context = s_GL.eglCreateContext( + tmpDisplay, + config, + EGL_NO_CONTEXT, + contextAttrib); - if (!s_GL.eglMakeCurrent(display, surface, surface, context)) { + } else { + EGLint contextAttrib[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE}; + + context = s_GL.eglCreateContext( + tmpDisplay, + config, + EGL_NO_CONTEXT, + contextAttrib); + } + + if (context == EGL_NO_CONTEXT) { + palSetLastPlatformError(errno); return PAL_RESULT_PLATFORM_FAILURE; } + s_GL.eglMakeCurrent(tmpDisplay, surface, surface, context); s_GL.glGetString = (glGetStringFn)s_GL.eglGetProcAddress("glGetString"); + const char* version = (const char*)s_GL.glGetString(GL_VERSION); if (version) { - sscanf(version, "%d.%d", &s_GL.info.major, &s_GL.info.minor); + if (s_GL.apiType == EGL_OPENGL_API) { + sscanf(version, "%d.%d", &s_GL.info.major, &s_GL.info.minor); + } else { + sscanf(version + 10, "%d.%d", &s_GL.info.major, &s_GL.info.minor); + } } const char* renderer = (const char*)s_GL.glGetString(GL_RENDERER); @@ -521,7 +608,7 @@ PalResult PAL_CALL palInitGL(const PalAllocator* allocator) // 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); + const char* extensions = s_GL.eglQueryString(tmpDisplay, EGL_EXTENSIONS); if (extensions) { // color space if (checkExtension("EGL_KHR_gl_colorspace", extensions)) { @@ -563,8 +650,7 @@ PalResult PAL_CALL palInitGL(const PalAllocator* allocator) s_GL.info.extensions |= PAL_GL_EXTENSION_MULTISAMPLE; if (type & EGL_OPENGL_ES_BIT || - type & EGL_OPENGL_ES2_BIT || - type & EGL_OPENGL_ES3_BIT) { + type & EGL_OPENGL_ES2_BIT) { s_GL.info.extensions |= PAL_GL_EXTENSION_CONTEXT_PROFILE_ES2; } @@ -573,19 +659,37 @@ PalResult PAL_CALL palInitGL(const PalAllocator* allocator) s_GL.info.extensions |= PAL_GL_EXTENSION_SWAP_CONTROL; } + if (s_GL.apiType != EGL_OPENGL_API) { + if (s_GL.info.extensions & PAL_GL_EXTENSION_CONTEXT_PROFILE) { + s_GL.info.extensions &= ~PAL_GL_EXTENSION_CONTEXT_PROFILE; + s_GL.info.extensions &= ~PAL_GL_EXTENSION_CONTEXT_PROFILE_ES2; + } + + if (type & EGL_OPENGL_ES3_BIT) { + s_GL.apiTypeBit = EGL_OPENGL_ES3_BIT; + } + } + s_GL.eglMakeCurrent( - display, + tmpDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); // clang-format on - s_GL.eglDestroyContext(display, context); - s_GL.eglDestroySurface(display, surface); + s_GL.eglDestroyContext(tmpDisplay, context); + s_GL.eglDestroySurface(tmpDisplay, surface); s_GL.allocator = allocator; s_GL.initialized = true; + + if (tmpDisplay != display) { + // tmpDisplay is a seperate display + // terminate it + s_GL.eglTerminate(tmpDisplay); + } + s_GL.display = display; return PAL_RESULT_SUCCESS; } @@ -597,7 +701,10 @@ void PAL_CALL palShutdownGL() } palFree(s_GL.allocator, s_GL.contextData); - s_GL.eglTerminate(s_GL.display); + if (s_GL.display) { + s_GL.eglTerminate(s_GL.display); + } + dlclose(s_GL.handle); s_GL.initialized = false; } @@ -637,6 +744,7 @@ PalResult PAL_CALL palEnumerateGLFBConfigs( // 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)) { + palSetLastPlatformError(errno); return PAL_RESULT_PLATFORM_FAILURE; } @@ -676,8 +784,22 @@ PalResult PAL_CALL palEnumerateGLFBConfigs( continue; } - if (!(renderable & EGL_OPENGL_BIT)) { - continue; + if (s_GL.apiType == EGL_OPENGL_ES3_BIT) { + // EGL_OPENGL_ES2_BIT + if (!(renderable & EGL_OPENGL_ES2_BIT) && + !(renderable & EGL_OPENGL_ES3_BIT)) { + continue; + } + + } else if (s_GL.apiType == EGL_OPENGL_ES2_BIT) { + if (!(renderable & EGL_OPENGL_ES2_BIT)) { + continue; + } + + } else { + if (!(renderable & EGL_OPENGL_BIT)) { + continue; + } } if (!(surfaceType & EGL_WINDOW_BIT)) { @@ -852,6 +974,12 @@ PalResult PAL_CALL palCreateGLContext( return PAL_RESULT_NULL_POINTER; } + // check if the window was created with the same display + // the opengl system is using + if (info->window->display != s_GL.platformDisplay) { + return PAL_RESULT_INVALID_GL_WINDOW; + } + // check support for requested features if (info->profile != PAL_GL_PROFILE_NONE) { if (!(s_GL.info.extensions & PAL_GL_EXTENSION_CONTEXT_PROFILE)) { @@ -902,6 +1030,7 @@ PalResult PAL_CALL palCreateGLContext( // 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)) { + palSetLastPlatformError(errno); return PAL_RESULT_PLATFORM_FAILURE; } @@ -917,6 +1046,8 @@ PalResult PAL_CALL palCreateGLContext( EGLContext* share = nullptr; if (info->shareContext) { share = (EGLContext)info->shareContext; + } else { + share = EGL_NO_CONTEXT; } Int32 attribs[40]; @@ -926,29 +1057,36 @@ PalResult PAL_CALL palCreateGLContext( // 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 (s_GL.apiType == EGL_OPENGL_API) { + // 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; + } - 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; } - attribs[index++] = info->profile; - } + // set forward flag + if (info->forward) { + flags |= EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR; + } - // set forward flag - if (info->forward) { - flags |= EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR; + } else { + attribs[index++] = EGL_CONTEXT_CLIENT_VERSION; + // our configs support EGL_OPENGL_ES2_BIT and EGL_OPENGL_ES3_BIT + attribs[index++] = info->major; } // set debug flag @@ -985,6 +1123,7 @@ PalResult PAL_CALL palCreateGLContext( attribs[index++] = EGL_CONTEXT_FLAGS_KHR; attribs[index++] = flags; } + attribs[index++] = EGL_NONE; // clang-format off @@ -992,7 +1131,7 @@ PalResult PAL_CALL palCreateGLContext( EGLContext context = s_GL.eglCreateContext( s_GL.display, config, - EGL_NO_CONTEXT, + share, attribs); // clang-format on @@ -1007,6 +1146,7 @@ PalResult PAL_CALL palCreateGLContext( return PAL_RESULT_INVALID_GL_VERSION; } else { + palSetLastPlatformError(errno); return PAL_RESULT_PLATFORM_FAILURE; } } @@ -1034,10 +1174,26 @@ PalResult PAL_CALL palCreateGLContext( return PAL_RESULT_INVALID_GL_FBCONFIG; } else { + palSetLastPlatformError(errno); return PAL_RESULT_PLATFORM_FAILURE; } } + // make the context current and swap for the first time + // this will make the window visible + if (s_GL.eglMakeCurrent(s_GL.display, surface, surface, context)) { + s_GL.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + s_GL.glClear(0x00004000); + s_GL.eglSwapBuffers(s_GL.display, surface); + + // revert + s_GL.eglMakeCurrent( + s_GL.display, + EGL_NO_SURFACE, + EGL_NO_SURFACE, + EGL_NO_CONTEXT); + } + palFree(s_GL.allocator, eglConfigs); data->context = (PalGLContext*)context; data->surface = surface; @@ -1051,6 +1207,13 @@ void PAL_CALL palDestroyGLContext(PalGLContext* context) if (s_GL.initialized && context) { ContextData* data = findContextData(context); if (data) { + // make it not current if it was current + s_GL.eglMakeCurrent( + s_GL.display, + EGL_NO_SURFACE, + EGL_NO_SURFACE, + EGL_NO_CONTEXT); + s_GL.eglDestroyContext(s_GL.display, (EGLContext)context); s_GL.eglDestroySurface(s_GL.display, data->surface); data->used = false; @@ -1092,6 +1255,7 @@ PalResult PAL_CALL palMakeContextCurrent( return PAL_RESULT_INVALID_GL_WINDOW; } else { + palSetLastPlatformError(errno); return PAL_RESULT_PLATFORM_FAILURE; } } @@ -1143,6 +1307,7 @@ PalResult PAL_CALL palSwapBuffers( return PAL_RESULT_INVALID_GL_WINDOW; } else { + palSetLastPlatformError(errno); return PAL_RESULT_PLATFORM_FAILURE; } } @@ -1162,4 +1327,22 @@ PalResult PAL_CALL palSetSwapInterval(Int32 interval) s_GL.eglSwapInterval(s_GL.display, interval); return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palGLSetInstance(void* instance) +{ + s_GL.platformDisplay = instance; +} + +const char* PAL_CALL palGLGetBackend() +{ + if (!s_GL.initialized) { + return nullptr; + } + + if (s_GL.apiType == EGL_OPENGL_API) { + return "egl"; + } else { + return "gles"; + } } \ No newline at end of file diff --git a/src/opengl/pal_opengl_win32.c b/src/opengl/pal_opengl_win32.c index 91e9626..0eb79f0 100644 --- a/src/opengl/pal_opengl_win32.c +++ b/src/opengl/pal_opengl_win32.c @@ -1,7 +1,7 @@ /** -Copyright (C) 2025 Nicholas Agbo +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 @@ -203,8 +203,8 @@ typedef struct { PalGLInfo info; } Wgl; -static Gdi s_Gdi; -static Wgl s_Wgl; +static Gdi s_Gdi = {0}; +static Wgl s_Wgl = {0}; // ================================================== // Internal API @@ -239,6 +239,8 @@ static inline bool checkExtension( } } +void palSetLastPlatformError(Uint32 e); + // ================================================== // Public API // ================================================== @@ -254,7 +256,9 @@ PalResult PAL_CALL palInitGL(const PalAllocator* allocator) } // get the instance - s_Wgl.instance = GetModuleHandleW(nullptr); + if (!s_Wgl.instance) { + s_Wgl.instance = GetModuleHandleW(nullptr); + } // register class WNDCLASSEXW wc = {0}; @@ -285,12 +289,16 @@ PalResult PAL_CALL palInitGL(const PalAllocator* allocator) 0); if (!s_Wgl.window) { + DWORD error = GetLastError(); + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } s_Gdi.handle = LoadLibraryA("gdi32.dll"); s_Wgl.opengl = LoadLibraryA("opengl32.dll"); if (!s_Gdi.handle || !s_Wgl.opengl) { + DWORD error = GetLastError(); + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } @@ -342,6 +350,8 @@ PalResult PAL_CALL palInitGL(const PalAllocator* allocator) !s_Gdi.describePixelFormat || !s_Gdi.swapBuffers || !s_Gdi.setPixelFormat) { + DWORD error = GetLastError(); + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } @@ -349,6 +359,8 @@ PalResult PAL_CALL palInitGL(const PalAllocator* allocator) !s_Wgl.wglCreateContext || !s_Wgl.wglDeleteContext || !s_Wgl.wglMakeCurrent) { + DWORD error = GetLastError(); + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } @@ -371,6 +383,8 @@ PalResult PAL_CALL palInitGL(const PalAllocator* allocator) s_Wgl.context = s_Wgl.wglCreateContext(s_Wgl.hdc); if (!s_Wgl.wglMakeCurrent(s_Wgl.hdc, s_Wgl.context)) { + DWORD error = GetLastError(); + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } @@ -514,12 +528,13 @@ void PAL_CALL palShutdownGL() FreeLibrary(s_Wgl.opengl); FreeLibrary(s_Gdi.handle); + + memset(&s_Wgl, 0, sizeof(Wgl)); s_Wgl.initialized = false; } const PalGLInfo* PAL_CALL palGetGLInfo() { - if (!s_Wgl.initialized) { return nullptr; } @@ -562,6 +577,8 @@ PalResult PAL_CALL palEnumerateGLFBConfigs( 1, &configAttrib, &nativeCount)) { + DWORD error = GetLastError(); + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } @@ -908,6 +925,7 @@ PalResult PAL_CALL palCreateGLContext( if (error == ERROR_INVALID_PROFILE_ARB) { return PAL_RESULT_INVALID_GL_PROFILE; } else { + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } } @@ -916,6 +934,8 @@ PalResult PAL_CALL palCreateGLContext( // create context with legacy wgl functions context = s_Wgl.wglCreateContext(hdc); if (!context) { + DWORD error = GetLastError(); + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } @@ -924,6 +944,8 @@ PalResult PAL_CALL palCreateGLContext( if (!s_Wgl.wglShareLists(share, context)) { s_Wgl.wglDeleteContext(context); ReleaseDC((HWND)info->window->window, hdc); + DWORD error = GetLastError(); + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } } @@ -967,6 +989,7 @@ PalResult PAL_CALL palMakeContextCurrent( return PAL_RESULT_INVALID_GL_CONTEXT; } else { + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } } @@ -1016,6 +1039,7 @@ PalResult PAL_CALL palSwapBuffers( return PAL_RESULT_INVALID_GL_FBCONFIG; } else { + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } } @@ -1036,4 +1060,17 @@ PalResult PAL_CALL palSetSwapInterval(Int32 interval) s_Wgl.wglSwapIntervalEXT(interval); return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palGLSetInstance(void* instance) +{ + s_Wgl.instance = instance; +} + +const char* PAL_CALL palGLGetBackend() +{ + if (!s_Wgl.initialized) { + return nullptr; + } + return "wgl"; } \ No newline at end of file diff --git a/src/pal_core.c b/src/pal_core.c index 48a3519..f9b5de8 100644 --- a/src/pal_core.c +++ b/src/pal_core.c @@ -1,7 +1,7 @@ /** -Copyright (C) 2025 Nicholas Agbo +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 @@ -26,7 +26,9 @@ freely, subject to the following restrictions: // ================================================== #ifdef __linux__ +#define _GNU_SOURCE #define _POSIX_C_SOURCE 200112L +#include #include #include #include @@ -66,9 +68,9 @@ freely, subject to the following restrictions: #define PAL_DEFAULT_ALIGNMENT 16 #define PAL_VERSION_MAJOR 1 -#define PAL_VERSION_MINOR 0 +#define PAL_VERSION_MINOR 3 #define PAL_VERSION_BUILD 0 -#define PAL_VERSION_STRING "1.0.0" +#define PAL_VERSION_STRING "1.3.0" #define PAL_LOG_MSG_SIZE 4096 #ifdef _WIN32 @@ -81,6 +83,7 @@ static pthread_once_t s_TLSCreation = PTHREAD_ONCE_INIT; typedef struct { char tmp[PAL_LOG_MSG_SIZE]; char buffer[PAL_LOG_MSG_SIZE]; + char platformResultDesc[PAL_LOG_MSG_SIZE]; wchar_t wideBuffer[PAL_LOG_MSG_SIZE]; bool isLogging; } LogTLSData; @@ -233,6 +236,36 @@ static inline void writeToConsole(LogTLSData* data) #endif // _WIN32 } +void palSetLastPlatformError(Uint32 e) +{ + LogTLSData* data = getLogTlsData(); + memset(data->platformResultDesc, 0, PAL_LOG_MSG_SIZE); + + if (e == 0) { + return; + } + +#ifdef __linux__ +#if defined(__GLIBC__) + char* ret = strerror_r(e, data->platformResultDesc, PAL_LOG_MSG_SIZE); + if (ret != data->platformResultDesc) { + snprintf(data->platformResultDesc, PAL_LOG_MSG_SIZE, "%s", ret); + } +#else + strerror_r(e, data->platformResultDesc, PAL_LOG_MSG_SIZE); +#endif // __GLIBC__ +#else + FormatMessageA( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, + e, + 0, + data->platformResultDesc, + PAL_LOG_MSG_SIZE, + nullptr); +#endif // __linux__ +} + // ================================================== // Public API // ================================================== @@ -264,8 +297,16 @@ const char* PAL_CALL palFormatResult(PalResult result) case PAL_RESULT_OUT_OF_MEMORY: return "Out of memory"; - case PAL_RESULT_PLATFORM_FAILURE: - return "Platform error"; + case PAL_RESULT_PLATFORM_FAILURE: { + LogTLSData* data = getLogTlsData(); + if (!data) { + return "Platform error"; + } else if (data && data->platformResultDesc[0] == 0) { + return "Platform error"; + } else { + return data->platformResultDesc; + } + } case PAL_RESULT_INVALID_ALLOCATOR: return "Invalif allocator"; diff --git a/src/pal_event.c b/src/pal_event.c index ddae41d..b608dff 100644 --- a/src/pal_event.c +++ b/src/pal_event.c @@ -1,7 +1,7 @@ /** -Copyright (C) 2025 Nicholas Agbo +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 diff --git a/src/system/pal_system_linux.c b/src/system/pal_system_linux.c index 6d9e731..f775c41 100644 --- a/src/system/pal_system_linux.c +++ b/src/system/pal_system_linux.c @@ -1,7 +1,7 @@ /** -Copyright (C) 2025 Nicholas Agbo +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 @@ -25,8 +25,11 @@ freely, subject to the following restrictions: // Includes // ================================================== +#define _GNU_SOURCE +#define _POSIX_C_SOURCE 200112L #include "pal/pal_system.h" +#include #include #include #include @@ -65,6 +68,8 @@ static Uint32 parseCache(const char* path) return cacheSize; } +void palSetLastPlatformError(Uint32 e); + // ================================================== // Public API // ================================================== @@ -90,6 +95,7 @@ PalResult PAL_CALL palGetPlatformInfo(PalPlatformInfo* info) FILE* file = fopen("/etc/os-release", "r"); if (!file) { + palSetLastPlatformError(errno); return PAL_RESULT_PLATFORM_FAILURE; } @@ -118,6 +124,7 @@ PalResult PAL_CALL palGetPlatformInfo(PalPlatformInfo* info) struct statvfs stats; if (statvfs("/", &stats) != 0) { + palSetLastPlatformError(errno); return PAL_RESULT_PLATFORM_FAILURE; } @@ -141,6 +148,7 @@ PalResult PAL_CALL palGetCPUInfo( FILE* file = fopen("/proc/cpuinfo", "r"); if (!file) { + palSetLastPlatformError(errno); return PAL_RESULT_PLATFORM_FAILURE; } @@ -220,6 +228,7 @@ PalResult PAL_CALL palGetCPUInfo( // get architecture struct utsname arch; if (uname(&arch) != 0) { + palSetLastPlatformError(errno); return PAL_RESULT_PLATFORM_FAILURE; } diff --git a/src/system/pal_system_win32.c b/src/system/pal_system_win32.c index 58c5a9f..bcce0a7 100644 --- a/src/system/pal_system_win32.c +++ b/src/system/pal_system_win32.c @@ -1,7 +1,7 @@ /** -Copyright (C) 2025 Nicholas Agbo +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 @@ -125,6 +125,8 @@ static inline bool isVersionWin32( return osVersion->build >= build; } +void palSetLastPlatformError(Uint32 e); + // ================================================== // Public API // ================================================== @@ -140,6 +142,8 @@ PalResult PAL_CALL palGetPlatformInfo(PalPlatformInfo* info) // get windows build, version and combine them if (!getVersionWin32(&info->version)) { + DWORD error = GetLastError(); + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } @@ -277,6 +281,8 @@ PalResult PAL_CALL palGetCPUInfo( if (!ret) { palFree(allocator, buffer); + DWORD error = GetLastError(); + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } diff --git a/src/thread/pal_thread_linux.c b/src/thread/pal_thread_linux.c index b20a669..2c5b786 100644 --- a/src/thread/pal_thread_linux.c +++ b/src/thread/pal_thread_linux.c @@ -1,7 +1,7 @@ /** -Copyright (C) 2025 Nicholas Agbo +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 @@ -57,6 +57,8 @@ struct PalCondVar { // Internal API // ================================================== +void palSetLastPlatformError(Uint32 e); + // ================================================== // Public API // ================================================== @@ -82,6 +84,7 @@ PalResult PAL_CALL palCreateThread( pthread_t thread; if (info->stackSize == 0) { if (pthread_create(&thread, nullptr, info->entry, info->arg) != 0) { + palSetLastPlatformError(errno); return PAL_RESULT_PLATFORM_FAILURE; } @@ -91,6 +94,7 @@ PalResult PAL_CALL palCreateThread( pthread_attr_setstacksize(&attr, info->stackSize); if (pthread_create(&thread, nullptr, info->entry, info->arg) != 0) { + palSetLastPlatformError(errno); return PAL_RESULT_PLATFORM_FAILURE; } pthread_attr_destroy(&attr); @@ -109,9 +113,13 @@ PalResult PAL_CALL palJoinThread( } int ret = 0; + void* value = nullptr; pthread_t _thread = FROM_PAL_HANDLE(pthread_t, thread); if (retval) { - ret = pthread_join(_thread, &retval); + ret = pthread_join(_thread, &value); + void** out = (void**)retval; + *out = value; + } else { ret = pthread_join(_thread, nullptr); } @@ -119,6 +127,7 @@ PalResult PAL_CALL palJoinThread( if (ret == 0) { return PAL_RESULT_SUCCESS; } else { + palSetLastPlatformError(errno); return PAL_RESULT_PLATFORM_FAILURE; } } @@ -433,6 +442,7 @@ PalResult PAL_CALL palWaitCondVar( } else if (ret == ETIMEDOUT) { return PAL_RESULT_TIMEOUT; } else { + palSetLastPlatformError(errno); return PAL_RESULT_PLATFORM_FAILURE; } } diff --git a/src/thread/pal_thread_win32.c b/src/thread/pal_thread_win32.c index e55a3c2..4337c63 100644 --- a/src/thread/pal_thread_win32.c +++ b/src/thread/pal_thread_win32.c @@ -1,7 +1,7 @@ /** -Copyright (C) 2025 Nicholas Agbo +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 @@ -82,6 +82,8 @@ static DWORD WINAPI threadEntryToWin32(LPVOID arg) return (DWORD)(uintptr_t)ret; } +void palSetLastPlatformError(Uint32 e); + // ================================================== // Public API // ================================================== @@ -135,6 +137,7 @@ PalResult PAL_CALL palCreateThread( return PAL_RESULT_ACCESS_DENIED; } else { + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } } @@ -151,11 +154,16 @@ PalResult PAL_CALL palJoinThread( return PAL_RESULT_NULL_POINTER; } + void* value = nullptr; DWORD wait = WaitForSingleObject((HANDLE)thread, INFINITE); if (wait == WAIT_OBJECT_0 && retval) { uintptr_t ret; GetExitCodeThread((HANDLE)thread, (LPDWORD)&ret); - retval = (void*)ret; + void** out = (void**)retval; + *out = value; + + // thread is done destroy the HANDLE + CloseHandle((HANDLE)thread); } else if (wait == WAIT_FAILED) { return PAL_RESULT_INVALID_THREAD; @@ -326,6 +334,7 @@ PalResult PAL_CALL palSetThreadPriority( return PAL_RESULT_ACCESS_DENIED; } else { + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } } @@ -350,6 +359,7 @@ PalResult PAL_CALL palSetThreadAffinity( return PAL_RESULT_INVALID_ARGUMENT; } else { + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } } @@ -396,6 +406,8 @@ PalResult PAL_CALL palSetThreadName( return PAL_RESULT_ACCESS_DENIED; } else { + DWORD error = GetLastError(); + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } } @@ -532,6 +544,7 @@ PalResult PAL_CALL palWaitCondVar( if (error == ERROR_TIMEOUT) { return PAL_RESULT_TIMEOUT; } else { + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } } @@ -559,6 +572,7 @@ PalResult PAL_CALL palWaitCondVarTimeout( if (error == ERROR_TIMEOUT) { return PAL_RESULT_TIMEOUT; } else { + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } } diff --git a/src/video/pal_video_linux.c b/src/video/pal_video_linux.c index 9b58165..ffe25d3 100644 --- a/src/video/pal_video_linux.c +++ b/src/video/pal_video_linux.c @@ -1,7 +1,7 @@ /** -Copyright (C) 2025 Nicholas Agbo +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 @@ -25,15 +25,20 @@ freely, subject to the following restrictions: // Includes // ================================================== +#define _GNU_SOURCE +#define _POSIX_C_SOURCE 200112L #include "pal/pal_video.h" #include +#include +#include #include #include #include #include // X11 headers +#if PAL_HAS_X11 #include #include #include @@ -42,10 +47,23 @@ freely, subject to the following restrictions: #include #include #include +#endif // PAL_HAS_X11 // Wayland headers #if PAL_HAS_WAYLAND #include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include #endif // PAL_HAS_WAYLAND // ================================================== @@ -54,6 +72,10 @@ freely, subject to the following restrictions: #define TO_PAL_HANDLE(type, val) ((type*)(UintPtr)(val)) #define FROM_PAL_HANDLE(type, handle) ((type)(UintPtr)(handle)) +#define MAX_SPAN_MONITORS 4 +#define NULL_BUTTON_SERIAL 0xffffffffU + +#pragma region EGL Typedefs typedef void* EGLConfig; typedef void* EGLSurface; @@ -61,15 +83,11 @@ 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_OPENGL_ES_BIT 0x0001 +#define EGL_OPENGL_ES_API 0x30A0 #define EGL_NO_CONTEXT EGL_CAST(EGLContext, 0) #define EGL_NO_DISPLAY EGL_CAST(EGLDisplay, 0) #define EGL_NO_SURFACE EGL_CAST(EGLSurface, 0) @@ -113,21 +131,43 @@ typedef EGLBoolean (*eglGetConfigsFn)( EGLint, EGLint*); +#pragma endregion + +typedef struct { + void* monitor; + int dpi; +} SpanMonitor; + typedef struct { bool skipConfigure; bool skipState; bool used; bool isAttached; bool skipIfAttached; + bool focused; + bool pushConfigureEvent; + bool pushStateEvent; int x; int y; Uint32 w; int dpi; + int monitorCount; Uint32 h; PalWindowState state; - PalCursor* cursor; PalWindow* window; - XIC ic; // X11 only + + // X11 only + void* ic; + unsigned long colormap; + + // Wayland only + void* xdgSurface; + void* xdgToplevel; + void* buffer; + void* decoration; + void* cursor; + void* eglWindow; + SpanMonitor monitors[MAX_SPAN_MONITORS]; } WindowData; typedef struct { @@ -137,24 +177,42 @@ typedef struct { int y; Uint32 w; Uint32 h; + Uint32 refreshRate; + Uint32 wlName; + PalOrientation orientation; PalMonitor* monitor; + PalMonitorMode mode; // wayland only sends current + char name[32]; } MonitorData; typedef struct { + bool pendingScroll; Int32 lastX; Int32 lastY; Int32 dx; Int32 dy; Int32 WheelX; Int32 WheelY; + float WheelXf; + float WheelYf; bool state[PAL_MOUSE_BUTTON_MAX]; + double tmpScrollX; + double tmpScrollY; + double accumScrollX; + double accumScrollY; } Mouse; typedef struct { bool scancodeState[PAL_SCANCODE_MAX]; bool keycodeState[PAL_KEYCODE_MAX]; + int repeatRate; + int repeatDelay; + int repeatKey; + int repeatScancode; int scancodes[512]; int keycodes[256]; + Uint64 timer; + Uint64 frequency; } Keyboard; typedef struct { @@ -169,15 +227,94 @@ typedef struct { eglGetConfigsFn eglGetConfigs; } EGL; +typedef struct { + // clang-format off + void (*shutdownVideo)(); + void (*updateVideo)(); + PalResult (*setFBConfig)(const int, PalFBConfigBackend); + 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 (*flashWindow)(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*); + PalWindowHandleInfoEx (*getWindowHandleInfoEx)(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*); + + PalResult (*attachWindow)(void*, PalWindow**); + PalResult (*detachWindow)(PalWindow*, void**); + // clang-format off +} Backend; + +typedef struct { + bool initialized; + Int32 maxWindowData; + Int32 maxMonitorData; + Int32 pixelFormat; + PalVideoFeatures features; + PalVideoFeatures64 features64; + const PalAllocator* allocator; + PalEventDriver* eventDriver; + const Backend* backend; + WindowData* windowData; + MonitorData* monitorData; + const char* className; + void* platformInstance; + void* display; +} VideoLinux; + +static VideoLinux s_Video = {0}; +static Mouse s_Mouse = {0}; +static Keyboard s_Keyboard = {0}; +static EGL s_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) +#if PAL_HAS_X11 +#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 @@ -609,10 +746,10 @@ typedef struct { typedef struct { bool error; bool skipScreenEvent; - bool skipNotifyEvent; int bpp; int screen; int depth; + int monitorCount; int rrEventBase; void* handle; void* xrandr; @@ -622,11 +759,7 @@ typedef struct { Display* display; Window root; XContext dataID; - Cursor hiddenCursor; - const char* className; - - Visual* visual; - Colormap colormap; + XVisualInfo* visualInfo; XOpenDisplayFn openDisplay; XCloseDisplayFn closeDisplay; @@ -718,3457 +851,6734 @@ typedef struct { static X11 s_X11 = {0}; static X11Atoms s_X11Atoms = {0}; +#endif // PAL_HAS_X11 #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); +// ================================================== +// Wayland Typedefs, enums and structs +// ================================================== - 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*); +#pragma region Wayland Typedefs +#if PAL_HAS_WAYLAND - PalResult (*createIcon)(const PalIconCreateInfo*, PalIcon**); - void (*destroyIcon)(PalIcon*); - PalResult (*setWindowIcon)(PalWindow*, PalIcon*); +typedef struct wl_display* (*wl_display_connect_fn)(const char*); +typedef void (*wl_display_disconnect_fn)(struct wl_display*); +typedef int (*wl_display_roundtrip_fn)(struct wl_display*); +typedef int (*wl_display_dispatch_fn)(struct wl_display*); +typedef void (*wl_proxy_destroy_fn)(struct wl_proxy*); + +typedef int (*wl_proxy_add_listener_fn)( + struct wl_proxy*, + void (**)(void), void*); + +typedef struct wl_proxy* (*wl_proxy_marshal_constructor_v_fn)( + struct wl_proxy*, + uint32_t, + const struct wl_interface*, + uint32_t, ...); + +typedef struct wl_proxy* (*wl_proxy_marshal_flags_fn)( + struct wl_proxy*, + uint32_t, + const struct wl_interface*, + uint32_t, + uint32_t, ...); + +typedef uint32_t (*wl_proxy_get_version_fn)(struct wl_proxy*); + +typedef int (*wl_proxy_add_listener_fn)( + struct wl_proxy*, + void (**)(void), void*); + +typedef int (*wl_display_get_error_fn)(struct wl_display*); +typedef int (*wl_display_dispatch_pending_fn)(struct wl_display*); +typedef int (*wl_display_flush_fn)(struct wl_display*); +typedef int (*wl_display_prepare_read_fn)(struct wl_display*); +typedef int (*wl_display_read_events_fn)(struct wl_display*); +typedef int (*wl_display_get_fd_fn)(struct wl_display*); +typedef void (*wl_display_cancel_read_fn)(struct wl_display*); + +// xkb +typedef void (*xkb_keymap_unref_fn)(struct xkb_keymap*); +typedef struct xkb_state* (*xkb_state_new_fn)(struct xkb_keymap*); +typedef void (*xkb_state_unref_fn)(struct xkb_state*); +typedef void (*xkb_context_unref_fn)(struct xkb_context*); +typedef struct xkb_context* (*xkb_context_new_fn)(enum xkb_context_flags); +typedef uint32_t (*xkb_keysym_to_utf32_fn)(xkb_keysym_t); + +typedef xkb_keysym_t (*xkb_state_key_get_one_sym_fn)( + struct xkb_state*, + xkb_keycode_t); + +typedef struct xkb_keymap* (*xkb_keymap_new_from_string_fn)( + struct xkb_context*, + const char*, + enum xkb_keymap_format, + enum xkb_keymap_compile_flags); + +typedef enum xkb_state_component (*xkb_state_update_mask_fn)( + struct xkb_state*, + xkb_mod_mask_t, + xkb_mod_mask_t, + xkb_mod_mask_t, + xkb_layout_index_t, + xkb_layout_index_t, + xkb_layout_index_t); + +typedef int (*xkb_keymap_key_repeats_fn)( + struct xkb_keymap*, + xkb_keycode_t); + +// wayland cursor +typedef struct wl_cursor_theme* (*wl_cursor_theme_load_fn)( + const char*, + int, + struct wl_shm*); + +typedef struct wl_cursor* (*wl_cursor_theme_get_cursor_fn)( + struct wl_cursor_theme*, + const char*); + +typedef struct wl_buffer* (*wl_cursor_image_get_buffer_fn)( + struct wl_cursor_image*); + +// egl_window +struct wl_egl_window; +struct wl_surface; + +typedef struct wl_egl_window* (*wl_egl_window_create_fn)( + struct wl_surface*, + int, + int); - 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*); +typedef void (*wl_egl_window_destroy_fn)(struct wl_egl_window*); - PalResult (*attachWindow)(void*, PalWindow**); - PalResult (*detachWindow)(PalWindow*, void**); - // clang-format off -} Backend; +typedef void (*wl_egl_window_resize_fn)( + struct wl_egl_window*, + int, + int, + int, + int); 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; + bool checkFeatures; + int monitorCount; + + void* handle; + void* xkbCommon; + void* libCursor; + void* libWaylandEgl; + struct wl_display* display; + struct wl_registry* registry; + struct xdg_wm_base* xdgBase; + struct wl_compositor* compositor; + struct wl_shm* shm; + struct wl_seat* seat; + struct wl_pointer* pointer; + struct wl_keyboard* keyboard; + struct zxdg_decoration_manager_v1* decorationManager; + struct zwp_pointer_constraints* pointerConstraints; + struct wl_surface* pointerSurface; + struct wl_surface* keyboardSurface; + struct wl_cursor_theme* cursorTheme; + + struct xkb_context* inputContext; + struct xkb_keymap* keymap; + struct xkb_state* state; + + const struct wl_interface* outputInterface; + const struct wl_interface* seatInterface; + const struct wl_interface* compositorInterface; + const struct wl_interface* registryInterface; + const struct wl_interface* surfaceInterface; + const struct wl_interface* shmInterface; + const struct wl_interface* bufferInterface; + const struct wl_interface* shmPoolInterface; + const struct wl_interface* regionInterface; + const struct wl_interface* pointerInterface; + const struct wl_interface* keyboardInterface; + + wl_display_connect_fn displayConnect; + wl_display_disconnect_fn displayDisconnect; + wl_display_roundtrip_fn displayRoundtrip; + wl_display_dispatch_fn displayDispatch; + wl_proxy_destroy_fn proxyDestroy; + wl_proxy_add_listener_fn proxyAddListener; + wl_proxy_marshal_constructor_v_fn proxyMarshalCnstructor; + wl_proxy_marshal_flags_fn proxyMarshalFlags; + wl_proxy_get_version_fn proxyGetVersion; + wl_display_get_error_fn getError; + wl_display_dispatch_pending_fn dispatchPending; + wl_display_flush_fn displayFlush; + wl_display_prepare_read_fn prepareRead; + wl_display_read_events_fn readEvents; + wl_display_get_fd_fn displayGetFd; + wl_display_cancel_read_fn cancelRead; + + xkb_keymap_unref_fn xkbKeymapUnref; + xkb_state_new_fn xkbStateNew; + xkb_state_unref_fn xkbStateUnref; + xkb_context_unref_fn xkbContextUnref; + xkb_context_new_fn xkbContextNew; + xkb_state_key_get_one_sym_fn xkbStateKeyGetOneSym; + xkb_keymap_new_from_string_fn xkbKeymapNewFromString; + xkb_keysym_to_utf32_fn xkbKeysymToUtf32; + xkb_state_update_mask_fn xkbStateUpdateMask; + xkb_keymap_key_repeats_fn xkbKeymapKeyRepeats; + + wl_cursor_theme_load_fn cursorThemeLoad; + wl_cursor_theme_get_cursor_fn cursorThemeGetCursor; + wl_cursor_image_get_buffer_fn cursorImageGetBuffer; + + EGLConfig eglFBConfig; + wl_egl_window_create_fn eglWindowCreate; + wl_egl_window_destroy_fn eglWindowDestroy; + wl_egl_window_resize_fn eglWindowResize; +} Wayland; -// ================================================== -// Internal API -// ================================================== +typedef struct { + struct wl_buffer* buffer; + struct wl_surface* surface; + int hotspotX; + int hotspotY; +} WaylandCursor; -static int compareModes( - const void* a, - const void* b) -{ - const PalMonitorMode* mode1 = (const PalMonitorMode*)a; - const PalMonitorMode* mode2 = (const PalMonitorMode*)b; +static Wayland s_Wl = {0}; - // compare fields - if (mode1->width != mode2->width) { - return mode1->width - mode2->width; - } +#endif // PAL_HAS_WAYLAND +#pragma endregion - if (mode1->height != mode2->height) { - return mode1->height - mode2->height; - } +#pragma region Wayland-Client-Protocol +#if PAL_HAS_WAYLAND - if (mode1->refreshRate != mode2->refreshRate) { - return mode1->refreshRate - mode2->refreshRate; - } +static WindowData* findWindowData(PalWindow* window); +static MonitorData* findMonitorData(PalMonitor* monitor); - if (mode1->bpp != mode2->bpp) { - return mode1->bpp - mode2->bpp; - } +static inline Uint64 getTime() +{ + Uint64 now = palGetPerformanceCounter(); + return (now * 1000) / s_Keyboard.frequency; } -static WindowData* getFreeWindowData() +static inline void* wlRegistryBind( + struct wl_registry *wl_registry, + uint32_t name, + const struct wl_interface *interface, + uint32_t version) { - 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)); + struct wl_proxy *id; + id = s_Wl.proxyMarshalFlags( + (struct wl_proxy *)wl_registry, + WL_REGISTRY_BIND, + interface, + version, + 0, + name, + interface->name, + version, + NULL); - palFree(s_Video.allocator, s_Video.windowData); - s_Video.windowData = data; - s_Video.maxWindowData = count; + return (void *)id; +} - s_Video.windowData[freeIndex].used = true; - return &s_Video.windowData[freeIndex]; - } - return nullptr; +static inline int wlRegistryAddListener( + struct wl_registry *wl_registry, + const struct wl_registry_listener *listener, + void *data) +{ + return s_Wl.proxyAddListener( + (struct wl_proxy *) wl_registry, + (void (**)(void)) listener, data); } -static void resetMonitorData() +static inline struct wl_registry* wlDisplayGetRegistry( + struct wl_display *wl_display) { - memset( - s_Video.monitorData, - 0, - s_Video.maxMonitorData * sizeof(MonitorData)); + struct wl_proxy *registry; + registry = s_Wl.proxyMarshalFlags( + (struct wl_proxy *) wl_display, + 1, // WL_DISPLAY_GET_REGISTRY + s_Wl.registryInterface, + s_Wl.proxyGetVersion( + (struct wl_proxy *) wl_display), + 0, + NULL); + + return (struct wl_registry *)registry; } -static MonitorData* getFreeMonitorData() +static inline int wlOutputAddListener( + struct wl_output *wl_output, + const struct wl_output_listener *listener, + void *data) { - 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]; - } - } + return s_Wl.proxyAddListener( + (struct wl_proxy *) wl_output, + (void (**)(void)) listener, data); +} - // 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)); +static inline struct wl_surface* wlCompositorCreateSurface( + struct wl_compositor *wl_compositor) +{ + struct wl_proxy *id; + id = s_Wl.proxyMarshalFlags( + (struct wl_proxy *) wl_compositor, + 0, // WL_COMPOSITOR_CREATE_SURFACE, + s_Wl.surfaceInterface, + s_Wl.proxyGetVersion( + (struct wl_proxy *) wl_compositor), + 0, + NULL); + + return (struct wl_surface*) id; +} - palFree(s_Video.allocator, s_Video.monitorData); - s_Video.monitorData = data; - s_Video.maxWindowData = count; +static inline void wlSurfaceCommit(struct wl_surface *wl_surface) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy *) wl_surface, + 6, // WL_SURFACE_COMMIT + NULL, + s_Wl.proxyGetVersion( + (struct wl_proxy *) wl_surface), + 0); +} - s_Video.monitorData[freeIndex].used = true; - return &s_Video.monitorData[freeIndex]; - } - return nullptr; +static inline void wlSurfaceDestroy(struct wl_surface *wl_surface) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy *) wl_surface, + 0, // WL_SURFACE_DESTROY + NULL, + s_Wl.proxyGetVersion( + (struct wl_proxy *) wl_surface), + WL_MARSHAL_FLAG_DESTROY); } -static MonitorData* findMonitorData(PalMonitor* monitor) +static inline struct wl_shm_pool* wlShmCreatePool( + struct wl_shm *wl_shm, + int32_t fd, + int32_t size) { - 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]; - } - } + struct wl_proxy *id; + id = s_Wl.proxyMarshalFlags( + (struct wl_proxy *) wl_shm, + 0, // WL_SHM_CREATE_POOL + s_Wl.shmPoolInterface, + s_Wl.proxyGetVersion( + (struct wl_proxy *) wl_shm), + 0, + NULL, + fd, + size); + + return (struct wl_shm_pool *) id; } -static void freeMonitorData(PalMonitor* monitor) +static inline void wlShmPoolDestroy(struct wl_shm_pool *wl_shm_pool) { - 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; - } - } + s_Wl.proxyMarshalFlags( + (struct wl_proxy *) wl_shm_pool, + WL_SHM_POOL_DESTROY, + NULL, + s_Wl.proxyGetVersion( + (struct wl_proxy *) wl_shm_pool), + WL_MARSHAL_FLAG_DESTROY); } -static PalResult glxBackend() +static inline struct wl_buffer* wlShmPoolCreateBuffer( + struct wl_shm_pool *wl_shm_pool, + int32_t offset, + int32_t width, + int32_t height, + int32_t stride, + uint32_t format) { - // user choose GLX FBConfig backend - if (!s_X11.glxHandle) { - // Rare - return PAL_RESULT_PLATFORM_FAILURE; - } + struct wl_proxy *id; + id = s_Wl.proxyMarshalFlags( + (struct wl_proxy *) wl_shm_pool, + WL_SHM_POOL_CREATE_BUFFER, + s_Wl.bufferInterface, + s_Wl.proxyGetVersion( + (struct wl_proxy *) wl_shm_pool), + 0, + NULL, + offset, + width, + height, + stride, + format); + + return (struct wl_buffer *) id; +} - // clang-format off +static inline void wlBufferDestroy(struct wl_buffer *wl_buffer) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy *) wl_buffer, + WL_BUFFER_DESTROY, + NULL, + s_Wl.proxyGetVersion( + (struct wl_proxy *) wl_buffer), + WL_MARSHAL_FLAG_DESTROY); +} - int count = 0; - GLXFBConfig* configs = s_X11.glxGetFBConfigs( - s_X11.display, - s_X11.screen, - &count); +static inline void wlSurfaceAttach( + struct wl_surface *wl_surface, + struct wl_buffer *buffer, + int32_t x, + int32_t y) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy *) wl_surface, + WL_SURFACE_ATTACH, + NULL, + s_Wl.proxyGetVersion( + (struct wl_proxy *) wl_surface), + 0, + buffer, + x, + y); +} - GLXFBConfig fbConfig = configs[s_Video.pixelFormat]; - if (!fbConfig) { - return PAL_RESULT_INVALID_GL_FBCONFIG; - } +static inline void wlSurfaceDamageBuffer( + struct wl_surface *wl_surface, + int32_t x, + int32_t y, + int32_t width, + int32_t height) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy *) wl_surface, + WL_SURFACE_DAMAGE_BUFFER, + NULL, + s_Wl.proxyGetVersion( + (struct wl_proxy *) wl_surface), + 0, + x, + y, + width, + height); +} - // get a matching visual - XVisualInfo* visualInfo = s_X11.glxGetVisualFromFBConfig( - s_X11.display, - fbConfig); +static inline int wlSurfaceAddListener( + struct wl_surface *wl_surface, + const struct wl_surface_listener *listener, + void *data) +{ + return s_Wl.proxyAddListener( + (struct wl_proxy *) wl_surface, + (void (**)(void)) listener, data); +} - if (!visualInfo) { - return PAL_RESULT_INVALID_GL_FBCONFIG; - } +static inline int wlSeatAddListener( + struct wl_seat *wl_seat, + const struct wl_seat_listener *listener, + void *data) +{ + return s_Wl.proxyAddListener( + (struct wl_proxy *) wl_seat, + (void (**)(void)) listener, data); +} - // clang-format on +static inline struct wl_pointer* wlSeatGetPointer(struct wl_seat *wl_seat) +{ + struct wl_proxy *id; + id = s_Wl.proxyMarshalFlags( + (struct wl_proxy *) wl_seat, + WL_SEAT_GET_POINTER, + s_Wl.pointerInterface, + s_Wl.proxyGetVersion( + (struct wl_proxy *) wl_seat), + 0, + NULL); + + return (struct wl_pointer *) id; +} - 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); +static inline struct wl_keyboard* wlSeatGetKeyboard(struct wl_seat *wl_seat) +{ + struct wl_proxy *id; + id = s_Wl.proxyMarshalFlags( + (struct wl_proxy *) wl_seat, + WL_SEAT_GET_KEYBOARD, + s_Wl.keyboardInterface, + s_Wl.proxyGetVersion( + (struct wl_proxy *) wl_seat), + 0, + NULL); + + return (struct wl_keyboard *) id; +} - if (!s_X11.colormap) { - return PAL_RESULT_INVALID_GL_FBCONFIG; - } +static inline int wlPointerAddListener( + struct wl_pointer *wl_pointer, + const struct wl_pointer_listener *listener, + void *data) +{ + return s_Wl.proxyAddListener( + (struct wl_proxy *) wl_pointer, + (void (**)(void)) listener, data); +} - return PAL_RESULT_SUCCESS; +static inline void wlPointerSetCursor( + struct wl_pointer *wl_pointer, + uint32_t serial, + struct wl_surface *surface, + int32_t hotspot_x, + int32_t hotspot_y) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy *) wl_pointer, + WL_POINTER_SET_CURSOR, + NULL, + s_Wl.proxyGetVersion( + (struct wl_proxy *) wl_pointer), + 0, + serial, + surface, + hotspot_x, + hotspot_y); } -static PalResult eglXBackend(int index) +static inline int wlKeyboardAddListener( + struct wl_keyboard *wl_keyboard, + const struct wl_keyboard_listener *listener, + void *data) { - // user choose EGL FBConfig backend - if (!s_Egl.handle) { - return PAL_RESULT_PLATFORM_FAILURE; - } + return s_Wl.proxyAddListener( + (struct wl_proxy *) wl_keyboard, + (void (**)(void)) listener, data); +} - if (!s_Egl.eglBindAPI(EGL_OPENGL_API)) { - return PAL_RESULT_PLATFORM_FAILURE; +static inline struct wl_region* wlCompositorCreateRegion( + struct wl_compositor *wl_compositor) +{ + struct wl_proxy *id; + id = s_Wl.proxyMarshalFlags( + (struct wl_proxy *) wl_compositor, + WL_COMPOSITOR_CREATE_REGION, + s_Wl.regionInterface, + s_Wl.proxyGetVersion( + (struct wl_proxy *) wl_compositor), + 0, + NULL); + + return (struct wl_region *) id; +} + +static inline void wlRegionAdd( + struct wl_region *wl_region, + int32_t x, + int32_t y, + int32_t width, + int32_t height) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy *) wl_region, + WL_REGION_ADD, + NULL, + s_Wl.proxyGetVersion( + (struct wl_proxy *) wl_region), + 0, + x, + y, + width, + height); +} + +static inline void wlSurfaceSetOpaqueRegion( + struct wl_surface *wl_surface, + struct wl_region *region) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy *) wl_surface, + WL_SURFACE_SET_OPAQUE_REGION, + NULL, + s_Wl.proxyGetVersion( + (struct wl_proxy *) wl_surface), + 0, + region); +} + +static inline void wlRegionDestroy(struct wl_region *wl_region) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy *) wl_region, + WL_REGION_DESTROY, + NULL, + s_Wl.proxyGetVersion( + (struct wl_proxy *) wl_region), + WL_MARSHAL_FLAG_DESTROY); +} + +static void surfaceHandleEnter( + void* userData, + struct wl_surface* surface, + struct wl_output* output) +{ + WindowData* data = userData; + MonitorData* monitorData = findMonitorData((PalMonitor*)output); + if (!monitorData) { + return; } - EGLDisplay display = EGL_NO_DISPLAY; - display = s_Egl.eglGetDisplay((EGLNativeDisplayType)s_X11.display); + // wayland sends multiple surface enter events + // if the surface spans multiple monitors + // we get all and return the highest dpi + // this assumes a surface can only span 4 monitors + // at the sametime but this might be wrong + // FIXME: check if we need more + + // wayland will trigger this event if the window gains focus + // so we check if the output is the same + SpanMonitor* span = nullptr; + if (data->monitorCount > 0) { + for (int i = 0; i < data->monitorCount; i++) { + if (data->monitors[i].monitor == (void*)output) { + // the monitor already exist in our array + // so we just update it + span = &data->monitors[i]; + span->dpi = monitorData->dpi; + break; + } + } + } - if (display == EGL_NO_DISPLAY) { - return PAL_RESULT_PLATFORM_FAILURE; + if (span == nullptr) { + // new entry + span = &data->monitors[data->monitorCount]; + span->monitor = output; + span->dpi = monitorData->dpi; + data->monitorCount++; } - if (!s_Egl.eglInitialize(display, nullptr, nullptr)) { - return PAL_RESULT_PLATFORM_FAILURE; + if (data->dpi == 0) { + // this is triggered when the window is created + // we cache the DPI and skip the event + data->dpi = monitorData->dpi; + return; } - EGLint numConfigs = 0; - if (!s_Egl.eglGetConfigs(display, nullptr, 0, &numConfigs)) { - return PAL_RESULT_PLATFORM_FAILURE; + // the code below should be skipped if users are not + // interested in DPI changed events + PalDispatchMode mode = PAL_DISPATCH_NONE; + PalEventType type = PAL_EVENT_MONITOR_DPI_CHANGED; + if (!s_Video.eventDriver) { + return; } - EGLint configSize = sizeof(EGLConfig) * numConfigs; - EGLConfig* eglConfigs = palAllocate(s_Video.allocator, configSize, 0); - if (!eglConfigs) { - return PAL_RESULT_OUT_OF_MEMORY; + PalEventDriver* driver = s_Video.eventDriver; + mode = palGetEventDispatchMode(driver, type); + if (mode == PAL_DISPATCH_NONE) { + return; } - s_Egl.eglGetConfigs(display, eglConfigs, numConfigs, &numConfigs); - EGLConfig config = eglConfigs[index]; + // get the highest dpi and check if the it has changed + int maxDPI = 96; // baseline + for (int i = 0; i < data->monitorCount; i++) { + if (!data->monitors[i].monitor) { + // not a valid index. continue + continue; + } - // 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; + if (data->monitors[i].dpi > maxDPI) { + // new highest + maxDPI = data->monitors[i].dpi; + } } - int numVisuals = 0; - XVisualInfo tmp; - tmp.visualid = visualID; + if (maxDPI != data->dpi) { + data->dpi = maxDPI; - // clang-format off - // get a matching visual info - XVisualInfo* visualInfo = s_X11.getVisualInfo( - s_X11.display, - VisualIDMask, - &tmp, - &numVisuals); - // clang-format on + PalEvent event = {0}; + event.type = type; + event.data = maxDPI; + event.data2 = palPackPointer((void*)data->window); + palPushEvent(driver, &event); + } +} - if (!visualInfo) { - return PAL_RESULT_INVALID_GL_FBCONFIG; +static void surfaceHandleLeave( + void* userData, + struct wl_surface* surface, + struct wl_output* output) +{ + // remove the monitor from our span monitor list + WindowData* data = userData; + MonitorData* monitorData = findMonitorData((PalMonitor*)output); + if (!monitorData) { + return; } - 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); + for (int i = 0; i < data->monitorCount; i++) { + if (data->monitors[i].monitor == (void*)output) { + // found our monitor + // we might want to pack the array but its just 4 monitors + // so we leave it like that + data->monitors[i].monitor = nullptr; + data->monitorCount--; + break; + } + } +} - if (!s_X11.colormap) { - return PAL_RESULT_INVALID_GL_FBCONFIG; +static struct wl_surface_listener surfaceListener = { + .enter = surfaceHandleEnter, + .leave = surfaceHandleLeave +}; + +static void pointerHandleEnter( + void* userData, + struct wl_pointer* pointer, + uint32_t serial, + struct wl_surface* surface, + wl_fixed_t surface_x, + wl_fixed_t surface_y) +{ + WindowData* data = findWindowData((PalWindow*)surface); + if (!data) { + return; } - s_Egl.eglTerminate(display); - palFree(s_Video.allocator, eglConfigs); - return PAL_RESULT_SUCCESS; -} + if (data->cursor) { + // our window + WaylandCursor* cursor = data->cursor; + wlPointerSetCursor( + pointer, + serial, + cursor->surface, + cursor->hotspotX, + cursor->hotspotY); + } -// ================================================== -// X11 API -// ================================================== + // cache the surface the pointer is currently on + s_Wl.pointerSurface = surface; +} -#pragma region X11 API +static void pointerHandleLeave( + void* userData, + struct wl_pointer* pointer, + uint32_t serial, + struct wl_surface* surface) +{ + if (s_Wl.pointerSurface == surface) { + s_Wl.pointerSurface == nullptr; + } +} -static RRMode xFindMode( - XRRScreenResources* resources, - const PalMonitorMode* mode) +static void pointerHandleMotion( + void* userData, + struct wl_pointer* pointer, + uint32_t time, + wl_fixed_t surface_x, + wl_fixed_t surface_y) { - for (int i = 0; i < resources->nmode; ++i) { - XRRModeInfo* info = &resources->modes[i]; + int x = wl_fixed_to_int(surface_x); + int y = wl_fixed_to_int(surface_y); + const int dx = x - s_Mouse.lastX; + const int dy = y - s_Mouse.lastY; - double tmp = (double)info->hTotal * (double)info->vTotal; - double rate = (double)info->dotClock / tmp; + PalDispatchMode mode = PAL_DISPATCH_NONE; + PalWindow* window = (PalWindow*)s_Wl.pointerSurface; + if (s_Video.eventDriver && window) { + // we only push a mouse move only if we are on a window + 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); + } - // 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; + // 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); } } - return None; + + s_Mouse.lastX = x; + s_Mouse.lastY = y; + s_Mouse.dx = dx; + s_Mouse.dy = dy; } -static void xCheckFeatures() +static void pointerHandleButton( + void* userData, + struct wl_pointer* pointer, + uint32_t serial, + uint32_t time, + uint32_t button, + uint32_t state) { - // 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); + PalWindow* window = nullptr; + if (s_Wl.pointerSurface) { + window = (PalWindow*)s_Wl.pointerSurface; - // 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); + } else { + // cannot recieve events without a focused surface + return; + } + + bool pressed = state == WL_POINTER_BUTTON_STATE_PRESSED; + PalMouseButton _button = 0; + PalEventType type = PAL_EVENT_MOUSE_BUTTONUP; - 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 (button == BTN_LEFT) { + _button = PAL_MOUSE_BUTTON_LEFT; - if (supportedAtoms[i] == s_X11Atoms._NET_WM_STATE_MAXIMIZED_HORZ) { - features |= PAL_VIDEO_FEATURE_WINDOW_SET_STATE; - features |= PAL_VIDEO_FEATURE_WINDOW_GET_STATE; - } + } else if (button == BTN_RIGHT) { + _button = PAL_MOUSE_BUTTON_RIGHT; - if (supportedAtoms[i] == s_X11Atoms._NET_WM_STATE_HIDDEN) { - features |= PAL_VIDEO_FEATURE_WINDOW_SET_STATE; - features |= PAL_VIDEO_FEATURE_WINDOW_GET_STATE; - } + } else if (button == BTN_MIDDLE) { + _button = PAL_MOUSE_BUTTON_MIDDLE; - if (supportedAtoms[i] == s_X11Atoms._NET_WM_WINDOW_TYPE_SPLASH) { - features |= PAL_VIDEO_FEATURE_BORDERLESS_WINDOW; - } + } else if (button == BTN_SIDE) { + _button = PAL_MOUSE_BUTTON_X1; - if (supportedAtoms[i] == s_X11Atoms._NET_WM_NAME) { - s_X11Atoms.unicodeTitle = true; - } + } else if (button == BTN_EXTRA) { + _button = PAL_MOUSE_BUTTON_X2; + } - if (supportedAtoms[i] == s_X11Atoms._NET_WM_WINDOW_TYPE_UTILITY) { - features |= PAL_VIDEO_FEATURE_TOOL_WINDOW; - } + if (pressed) { + type = PAL_EVENT_MOUSE_BUTTONDOWN; } - // 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; + s_Mouse.state[_button] = pressed; + if (s_Video.eventDriver) { + PalEventDriver* driver = s_Video.eventDriver; + PalDispatchMode mode = PAL_DISPATCH_NONE; + mode = palGetEventDispatchMode(driver, type); + if (mode != PAL_DISPATCH_NONE) { + PalEvent event = {0}; + event.type = type; + + // since we are not drawing decorations for users + // they need the serial in order to draw their decorations + // we put the serial at the upper 32 so ABI is preserved + event.data = palPackUint32(_button, serial); + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); } } +} - // 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; +static void pointerHandleAxis( + void* userData, + struct wl_pointer* pointer, + uint32_t time, + uint32_t axis, + wl_fixed_t value) +{ + double delta = wl_fixed_to_double(value); + if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) { + s_Mouse.tmpScrollX += delta; + s_Mouse.accumScrollX += delta; - s_Video.features = features; - s_X11.free(supportedAtoms); + } else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) { + s_Mouse.tmpScrollY += delta; + s_Mouse.accumScrollY += delta; + } + + s_Mouse.pendingScroll = true; } -static int xErrorHandler( - Display*, - XErrorEvent* e) +static void pointerHandleAxisDiscrete( + void* userData, + struct wl_pointer* pointer, + uint32_t axis, + int32_t discrete) { - // this is use for simple success and failure - s_X11.error = true; - return 0; + if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) { + s_Mouse.tmpScrollX += discrete; + s_Mouse.accumScrollX += discrete; + + } else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) { + s_Mouse.tmpScrollY += discrete; + s_Mouse.accumScrollY += discrete; + } + + s_Mouse.pendingScroll = true; } -static PalWindowState xQueryWindowState(Window xWin) +static void pointerHandleFrame( + void* userData, + struct wl_pointer* pointer) { - 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); + if (!s_Mouse.pendingScroll) { + // no wheel event + return; + } - for (unsigned int i = 0; i < count; i++) { - if (atoms[i] == s_X11Atoms._NET_WM_STATE_MAXIMIZED_HORZ) { - state = PAL_WINDOW_STATE_MAXIMIZED; - } + PalWindow* window = nullptr; + if (s_Wl.pointerSurface) { + window = (PalWindow*)s_Wl.pointerSurface; - if (atoms[i] == s_X11Atoms._NET_WM_STATE_MAXIMIZED_VERT) { - state = PAL_WINDOW_STATE_MAXIMIZED; - } + } else { + // cannot recieve events without a focused surface + return; + } - if (atoms[i] == s_X11Atoms._NET_WM_STATE_HIDDEN) { - state = PAL_WINDOW_STATE_MINIMIZED; + const int dx = (int)s_Mouse.accumScrollX; + const int dy = (int)s_Mouse.accumScrollY; + if (s_Video.eventDriver) { + PalEventType type = PAL_EVENT_MOUSE_WHEEL; + PalEventDriver* driver = s_Video.eventDriver; + PalDispatchMode mode = PAL_DISPATCH_NONE; + mode = palGetEventDispatchMode(driver, type); + if (mode != PAL_DISPATCH_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = palPackUint32(dx, dy); + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); } } - s_X11.free(atoms); - return state; + s_Mouse.WheelX = dx; + s_Mouse.WheelY = dy; + s_Mouse.accumScrollX -= dx; + s_Mouse.accumScrollY -= dy; + s_Mouse.pendingScroll = false; } -static void xCacheMonitors(bool enumerate) +static void pointerHandleAxisSource( + void* userData, + struct wl_pointer* pointer, + uint32_t axis_source) { - 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; +static void pointerHandleAxisStop( + void* userData, + struct wl_pointer* pointer, + uint32_t time, + uint32_t axis) +{ + +} - if (enumerate) { - data = getFreeMonitorData(); - if (!data) { - return; - } +static void keyboardHandleEnter( + void* userData, + struct wl_keyboard* keyboard, + uint32_t serial, + struct wl_surface* surface, + struct wl_array* keys) +{ + // cache the surface the keyboard is currently on + s_Wl.keyboardSurface = surface; +} - data->monitor = monitor; +static void keyboardHandleLeave( + void* userData, + struct wl_keyboard* keyboard, + uint32_t serial, + struct wl_surface* surface) +{ + if (s_Wl.keyboardSurface == surface) { + s_Wl.keyboardSurface == nullptr; + } +} - } else { - data = findMonitorData(monitor); - } +static void keyboardHandleRemap( + void* userData, + struct wl_keyboard* keyboard, + uint32_t format, + int32_t fd, + uint32_t size) +{ + if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { + close(fd); + return; + } - // clang-format off - XRRCrtcInfo* crtc = s_X11.getCrtcInfo(s_X11.display, resources, info->crtc); - // clang-format on + struct xkb_keymap* keymap = nullptr; + struct xkb_state* state = nullptr; - 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; + char* keymapStr = mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0); + if (keymapStr == MAP_FAILED) { + close(fd); + return; + } - s_X11.freeCrtcInfo(crtc); - } + keymap = s_Wl.xkbKeymapNewFromString( + s_Wl.inputContext, + keymapStr, + XKB_KEYMAP_FORMAT_TEXT_V1, + XKB_KEYMAP_COMPILE_NO_FLAGS); - s_X11.freeOutputInfo(info); + if (!keymap) { + return; } - s_X11.freeScreenResources(resources); + munmap(keymapStr, size); + close(fd); + + state = s_Wl.xkbStateNew(keymap); + if (!state) { + return; + } + + // check if we have old keymap and state + if (s_Wl.state) { + s_Wl.xkbStateUnref(s_Wl.state); + s_Wl.xkbKeymapUnref(s_Wl.keymap); + } + + s_Wl.state = state; + s_Wl.keymap = keymap; + s_Keyboard.frequency = palGetPerformanceFrequency(); } -static int xGetWindowMonitorDPI( - WindowData* data, - bool enumerate) +static void keyboardHandleKey( + void* userData, + struct wl_keyboard* keyboard, + uint32_t serial, + uint32_t time, + uint32_t key, + uint32_t state) { - 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; + PalWindow* window = nullptr; + if (s_Wl.keyboardSurface) { + window = (PalWindow*)s_Wl.keyboardSurface; + + } else { + // cannot recieve events without a focused surface + return; + } + + PalScancode scancode = 0; + PalKeycode keycode = 0; + bool pressed = (state == WL_KEYBOARD_KEY_STATE_PRESSED); + PalEventType type = PAL_EVENT_KEYUP; + PalDispatchMode mode = PAL_DISPATCH_NONE; + xkb_keysym_t keySym = s_Wl.xkbStateKeyGetOneSym(s_Wl.state, key + 8); + + // special handling + if (key == 119) { + scancode = PAL_SCANCODE_PAUSE; + } else if (key == 107) { + scancode = PAL_SCANCODE_END; + } else if (key == 103) { + scancode = PAL_SCANCODE_UP; + } else if (key == 102) { + scancode = PAL_SCANCODE_HOME; + + } else { + scancode = s_Keyboard.scancodes[key]; + } + + // printable and text input keys are from the range + // 32 (PAL_KEYCODE_SPACE) 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 >= XKB_KEY_space && keySym <= XKB_KEY_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; + } + + s_Keyboard.scancodeState[scancode] = pressed; + s_Keyboard.keycodeState[keycode] = pressed; + + // check for key repeats + if (pressed) { + if (s_Wl.xkbKeymapKeyRepeats(s_Wl.keymap, key + 8)) { + s_Keyboard.repeatKey = keycode; + s_Keyboard.repeatScancode = scancode; + s_Keyboard.timer = getTime() + s_Keyboard.repeatDelay; + + } else { + s_Keyboard.repeatKey = 0; } - // 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; + type = PAL_EVENT_KEYDOWN; + + } else { + // key release + if (s_Keyboard.repeatKey == keycode) { + s_Keyboard.repeatKey = 0; } } + + 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); + } + + // check for char event if enabled + type = PAL_EVENT_KEYCHAR; + mode = palGetEventDispatchMode(driver, type); + if (mode == PAL_DISPATCH_NONE) { + return; + } + + Uint32 codepoint = s_Wl.xkbKeysymToUtf32(keySym); + if (codepoint <= 0) { + return; + } + + PalEvent event = {0}; + event.type = type; + event.data = codepoint; + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); + } } -static void xSendWMEvent( - Window window, - Atom type, - long a, - long b, - long c, - long d, - bool add) +static void keyboardHandleRepeatInfo( + void* userData, + struct wl_keyboard* keyboard, + int32_t rate, + int32_t delay) { - 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 + if (s_Wl.keyboard == keyboard) { + s_Keyboard.repeatDelay = delay; + s_Keyboard.repeatRate = rate; + } else { - e.xclient.data.l[0] = 0; // _NET_WM_STATE_REMOVE + if (s_Keyboard.repeatDelay == 0) { + s_Keyboard.repeatDelay = 500; + s_Keyboard.repeatRate = 30; + } } +} - e.xclient.data.l[1] = a; - e.xclient.data.l[2] = b; - e.xclient.data.l[3] = c; - e.xclient.data.l[4] = d; +static void keyboardHandleModifiers( + void* userData, + struct wl_keyboard* keyboard, + uint32_t serial, + uint32_t mods_depressed, + uint32_t mods_latched, + uint32_t mods_locked, + uint32_t group) +{ + if (s_Wl.state) { + s_Wl.xkbStateUpdateMask( + s_Wl.state, + mods_depressed, + mods_latched, + mods_locked, + group, + 0, + 0); + } +} - s_X11.sendEvent( - s_X11.display, - s_X11.root, - False, - SubstructureNotifyMask | SubstructureRedirectMask, - &e); +static struct wl_pointer_listener pointerListener = { + .enter = pointerHandleEnter, + .leave = pointerHandleLeave, + .motion = pointerHandleMotion, + .button = pointerHandleButton, + .axis = pointerHandleAxis, + .axis_discrete = pointerHandleAxisDiscrete, + .frame = pointerHandleFrame, + .axis_source = pointerHandleAxisSource, + .axis_stop = pointerHandleAxisStop +}; + +static struct wl_keyboard_listener keyboardListener = { + .enter = keyboardHandleEnter, + .leave = keyboardHandleLeave, + .keymap = keyboardHandleRemap, + .key = keyboardHandleKey, + .repeat_info = keyboardHandleRepeatInfo, + .modifiers = keyboardHandleModifiers +}; + +static void seatHandleCapabilities( + void* userData, + struct wl_seat* seat, + enum wl_seat_capability caps) +{ + if (caps & WL_SEAT_CAPABILITY_KEYBOARD) { + s_Wl.keyboard = wlSeatGetKeyboard(seat); + wlKeyboardAddListener(s_Wl.keyboard, &keyboardListener, nullptr); + } + + if (caps & WL_SEAT_CAPABILITY_POINTER) { + s_Wl.pointer = wlSeatGetPointer(seat); + wlPointerAddListener(s_Wl.pointer, &pointerListener, nullptr); + } } -static void xCreateScancodeTable() +static void seatHandleName( + void* userData, + struct wl_seat* seat, + const char* name) { - // Letters - s_Keyboard.scancodes[0x01E] = PAL_SCANCODE_A; - s_Keyboard.scancodes[0x030] = PAL_SCANCODE_B; - s_Keyboard.scancodes[0x02E] = PAL_SCANCODE_C; - s_Keyboard.scancodes[0x020] = PAL_SCANCODE_D; - s_Keyboard.scancodes[0x012] = PAL_SCANCODE_E; - s_Keyboard.scancodes[0x021] = PAL_SCANCODE_F; - s_Keyboard.scancodes[0x022] = PAL_SCANCODE_G; - s_Keyboard.scancodes[0x023] = PAL_SCANCODE_H; - s_Keyboard.scancodes[0x017] = PAL_SCANCODE_I; - s_Keyboard.scancodes[0x024] = PAL_SCANCODE_J; - s_Keyboard.scancodes[0x025] = PAL_SCANCODE_K; - s_Keyboard.scancodes[0x026] = PAL_SCANCODE_L; - s_Keyboard.scancodes[0x032] = PAL_SCANCODE_M; - s_Keyboard.scancodes[0x031] = PAL_SCANCODE_N; - s_Keyboard.scancodes[0x018] = PAL_SCANCODE_O; - s_Keyboard.scancodes[0x019] = PAL_SCANCODE_P; - s_Keyboard.scancodes[0x010] = PAL_SCANCODE_Q; - s_Keyboard.scancodes[0x013] = PAL_SCANCODE_R; - s_Keyboard.scancodes[0x01F] = PAL_SCANCODE_S; - s_Keyboard.scancodes[0x014] = PAL_SCANCODE_T; - s_Keyboard.scancodes[0x016] = PAL_SCANCODE_U; - s_Keyboard.scancodes[0x02F] = PAL_SCANCODE_V; - s_Keyboard.scancodes[0x011] = PAL_SCANCODE_W; - s_Keyboard.scancodes[0x02D] = PAL_SCANCODE_X; - s_Keyboard.scancodes[0x015] = PAL_SCANCODE_Y; - s_Keyboard.scancodes[0x02C] = PAL_SCANCODE_Z; + +} - // Numbers (top row) - s_Keyboard.scancodes[0x00B] = PAL_SCANCODE_0; - s_Keyboard.scancodes[0x002] = PAL_SCANCODE_1; - s_Keyboard.scancodes[0x003] = PAL_SCANCODE_2; - s_Keyboard.scancodes[0x004] = PAL_SCANCODE_3; - s_Keyboard.scancodes[0x005] = PAL_SCANCODE_4; - s_Keyboard.scancodes[0x006] = PAL_SCANCODE_5; - s_Keyboard.scancodes[0x007] = PAL_SCANCODE_6; - s_Keyboard.scancodes[0x008] = PAL_SCANCODE_7; - s_Keyboard.scancodes[0x009] = PAL_SCANCODE_8; - s_Keyboard.scancodes[0x00A] = PAL_SCANCODE_9; +static struct wl_seat_listener seatListener = { + .capabilities = seatHandleCapabilities, + .name = seatHandleName +}; - // Function - s_Keyboard.scancodes[0x03B] = PAL_SCANCODE_F1; - s_Keyboard.scancodes[0x03C] = PAL_SCANCODE_F2; - s_Keyboard.scancodes[0x03D] = PAL_SCANCODE_F3; - s_Keyboard.scancodes[0x03E] = PAL_SCANCODE_F4; - s_Keyboard.scancodes[0x03F] = PAL_SCANCODE_F5; - s_Keyboard.scancodes[0x040] = PAL_SCANCODE_F6; - s_Keyboard.scancodes[0x041] = PAL_SCANCODE_F7; - s_Keyboard.scancodes[0x042] = PAL_SCANCODE_F8; - s_Keyboard.scancodes[0x043] = PAL_SCANCODE_F9; - s_Keyboard.scancodes[0x044] = PAL_SCANCODE_F10; - s_Keyboard.scancodes[0x057] = PAL_SCANCODE_F11; - s_Keyboard.scancodes[0x058] = PAL_SCANCODE_F12; +#endif // PAL_HAS_WAYLAND +#pragma endregion - // Control - s_Keyboard.scancodes[0x001] = PAL_SCANCODE_ESCAPE; - s_Keyboard.scancodes[0x01C] = PAL_SCANCODE_ENTER; - s_Keyboard.scancodes[0x00F] = PAL_SCANCODE_TAB; - s_Keyboard.scancodes[0x00E] = PAL_SCANCODE_BACKSPACE; - s_Keyboard.scancodes[0x039] = PAL_SCANCODE_SPACE; - s_Keyboard.scancodes[0x03A] = PAL_SCANCODE_CAPSLOCK; - s_Keyboard.scancodes[0x045] = PAL_SCANCODE_NUMLOCK; - s_Keyboard.scancodes[0x046] = PAL_SCANCODE_SCROLLLOCK; - s_Keyboard.scancodes[0x02A] = PAL_SCANCODE_LSHIFT; - s_Keyboard.scancodes[0x036] = PAL_SCANCODE_RSHIFT; - s_Keyboard.scancodes[0x01D] = PAL_SCANCODE_LCTRL; - s_Keyboard.scancodes[0x061] = PAL_SCANCODE_RCTRL; - s_Keyboard.scancodes[0x038] = PAL_SCANCODE_LALT; - s_Keyboard.scancodes[0x064] = PAL_SCANCODE_RALT; +#pragma region Xdg-Shell-Protocol +#if PAL_HAS_WAYLAND - // Arrows - s_Keyboard.scancodes[0x069] = PAL_SCANCODE_LEFT; - s_Keyboard.scancodes[0x06A] = PAL_SCANCODE_RIGHT; - s_Keyboard.scancodes[0x067] = PAL_SCANCODE_UP; - s_Keyboard.scancodes[0x06C] = PAL_SCANCODE_DOWN; +struct xdg_wm_base; +struct xdg_surface; +struct xdg_toplevel; - // Navigation - s_Keyboard.scancodes[0x06E] = PAL_SCANCODE_INSERT; - s_Keyboard.scancodes[0x06F] = PAL_SCANCODE_DELETE; - s_Keyboard.scancodes[0x066] = PAL_SCANCODE_HOME; - s_Keyboard.scancodes[0x067] = PAL_SCANCODE_END; - s_Keyboard.scancodes[0x068] = PAL_SCANCODE_PAGEUP; - s_Keyboard.scancodes[0x06D] = PAL_SCANCODE_PAGEDOWN; +// forward declare +static struct wl_buffer* createShmBuffer( + int width, + int height, + const Uint8* pixels, + bool cursor); - // Keypad - s_Keyboard.scancodes[0x052] = PAL_SCANCODE_KP_0; - s_Keyboard.scancodes[0x04F] = PAL_SCANCODE_KP_1; - s_Keyboard.scancodes[0x050] = PAL_SCANCODE_KP_2; - s_Keyboard.scancodes[0x051] = PAL_SCANCODE_KP_3; - s_Keyboard.scancodes[0x04B] = PAL_SCANCODE_KP_4; - s_Keyboard.scancodes[0x04C] = PAL_SCANCODE_KP_5; - s_Keyboard.scancodes[0x04D] = PAL_SCANCODE_KP_6; - s_Keyboard.scancodes[0x047] = PAL_SCANCODE_KP_7; - s_Keyboard.scancodes[0x048] = PAL_SCANCODE_KP_8; - s_Keyboard.scancodes[0x049] = PAL_SCANCODE_KP_9; - s_Keyboard.scancodes[0x060] = PAL_SCANCODE_KP_ENTER; - s_Keyboard.scancodes[0x04E] = PAL_SCANCODE_KP_ADD; - s_Keyboard.scancodes[0x04A] = PAL_SCANCODE_KP_SUBTRACT; - s_Keyboard.scancodes[0x037] = PAL_SCANCODE_KP_MULTIPLY; - s_Keyboard.scancodes[0x062] = PAL_SCANCODE_KP_DIVIDE; - s_Keyboard.scancodes[0x053] = PAL_SCANCODE_KP_DECIMAL; +const struct wl_interface xdg_popup_interface; +const struct wl_interface xdg_positioner_interface; +const struct wl_interface xdg_surface_interface; +const struct wl_interface xdg_toplevel_interface; - // Misc - s_Keyboard.scancodes[0x063] = PAL_SCANCODE_PRINTSCREEN; - s_Keyboard.scancodes[0x066] = PAL_SCANCODE_PAUSE; - s_Keyboard.scancodes[0x07F] = PAL_SCANCODE_MENU; - s_Keyboard.scancodes[0x028] = PAL_SCANCODE_APOSTROPHE; - s_Keyboard.scancodes[0x02B] = PAL_SCANCODE_BACKSLASH; - s_Keyboard.scancodes[0x033] = PAL_SCANCODE_COMMA; - s_Keyboard.scancodes[0x00D] = PAL_SCANCODE_EQUAL; - s_Keyboard.scancodes[0x029] = PAL_SCANCODE_GRAVEACCENT; - s_Keyboard.scancodes[0x00C] = PAL_SCANCODE_SUBTRACT; - s_Keyboard.scancodes[0x034] = PAL_SCANCODE_PERIOD; - s_Keyboard.scancodes[0x027] = PAL_SCANCODE_SEMICOLON; - s_Keyboard.scancodes[0x035] = PAL_SCANCODE_SLASH; - s_Keyboard.scancodes[0x01A] = PAL_SCANCODE_LBRACKET; - s_Keyboard.scancodes[0x01B] = PAL_SCANCODE_RBRACKET; - s_Keyboard.scancodes[0x07D] = PAL_SCANCODE_LSUPER; - s_Keyboard.scancodes[0x07E] = PAL_SCANCODE_RSUPER; -} +struct xdg_wm_base_listener { + void (*ping)(void*, struct xdg_wm_base*, uint32_t); +}; -static void xCreateKeycodeTable() -{ - // Tis is for only printable and text input keys +struct xdg_surface_listener { + void (*configure)(void*, struct xdg_surface*, uint32_t); +}; - // 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; +struct xdg_toplevel_listener { + // clang-format off + void (*configure)(void*, struct xdg_toplevel*, int32_t, int32_t, struct wl_array*); + void (*close)(void*, struct xdg_toplevel*); + void (*configure_bounds)(void*, struct xdg_toplevel*, int32_t, int32_t); + void (*wm_capabilities)(void*, struct xdg_toplevel*, struct wl_array*); + // clang-format on +}; - // Control - s_Keyboard.keycodes[XK_space] = PAL_KEYCODE_SPACE; +static inline void xdgWmBasePong( + struct xdg_wm_base* xdg_wm_base, + uint32_t serial) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy*)xdg_wm_base, + 3, // XDG_WM_BASE_PONG + NULL, + s_Wl.proxyGetVersion((struct wl_proxy*)xdg_wm_base), + 0, + serial); +} - // 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 inline int xdgWmBaseAddListener( + struct xdg_wm_base* xdg_wm_base, + const struct xdg_wm_base_listener* listener, + void* data) +{ + return s_Wl.proxyAddListener( + (struct wl_proxy*)xdg_wm_base, + (void (**)(void))listener, + data); } -static PalResult xInitVideo() +static inline struct xdg_surface* xdgWmBaseGetXdgSurface( + struct xdg_wm_base* xdg_wm_base, + struct wl_surface* surface) { - // load X11 library - s_X11.handle = dlopen("libX11.so", RTLD_LAZY); - if (!s_X11.handle) { - return PAL_RESULT_PLATFORM_FAILURE; - } + struct wl_proxy* id; + id = s_Wl.proxyMarshalFlags( + (struct wl_proxy*)xdg_wm_base, + 2, // XDG_WM_BASE_GET_XDG_SURFACE, + &xdg_surface_interface, + s_Wl.proxyGetVersion((struct wl_proxy*)xdg_wm_base), + 0, + NULL, + surface); - // libXCursor is needed - s_X11.libCursor = dlopen("libXcursor.so", RTLD_LAZY); - if (!s_X11.libCursor) { - return PAL_RESULT_PLATFORM_FAILURE; - } + return (struct xdg_surface*)id; +} - // Xrandr is needed - s_X11.xrandr = dlopen("libXrandr.so.2", RTLD_LAZY); - if (!s_X11.xrandr) { - s_X11.xrandr = dlopen("libXrandr.so", RTLD_LAZY); - } +static inline struct xdg_toplevel* +xdgSurfaceGetToplevel(struct xdg_surface* xdg_surface) +{ + struct wl_proxy* id; + id = s_Wl.proxyMarshalFlags( + (struct wl_proxy*)xdg_surface, + 1, // XDG_SURFACE_GET_TOPLEVEL, + &xdg_toplevel_interface, + s_Wl.proxyGetVersion((struct wl_proxy*)xdg_surface), + 0, + NULL); - // clang-format off + return (struct xdg_toplevel*)id; +} - // load procs - s_X11.openDisplay = (XOpenDisplayFn)dlsym( - s_X11.handle, - "XOpenDisplay"); +static inline void xdgSurfaceAckConfigure( + struct xdg_surface* xdg_surface, + uint32_t serial) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy*)xdg_surface, + 4, // XDG_SURFACE_ACK_CONFIGURE + NULL, + s_Wl.proxyGetVersion((struct wl_proxy*)xdg_surface), + 0, + serial); +} - s_X11.closeDisplay = (XCloseDisplayFn)dlsym( - s_X11.handle, - "XCloseDisplay"); +static void wmBaseHandlePing( + void* data, + struct xdg_wm_base* base, + uint32_t serial) +{ + xdgWmBasePong(base, serial); +} - s_X11.getWindowAttributes = (XGetWindowAttributesFn)dlsym( - s_X11.handle, - "XGetWindowAttributes"); +static void xdgSurfaceHandleConfigure( + void* data, + struct xdg_surface* surface, + uint32_t serial) +{ + WindowData* winData = (WindowData*)data; + xdgSurfaceAckConfigure(surface, serial); + + // push and resolve any pending events + if (!winData->skipConfigure) { + if (winData->pushConfigureEvent) { + if (winData->eglWindow) { + s_Wl.eglWindowResize( + winData->eglWindow, + winData->w, + winData->h, + 0, + 0); - s_X11.setCrtcConfig = (XRRSetCrtcConfigFn)dlsym( - s_X11.handle, - "XRRSetCrtcConfig"); + } else { + // create a new buffer with the new size + struct wl_buffer* buffer = nullptr; + buffer = + createShmBuffer(winData->w, winData->h, nullptr, false); + if (!buffer) { + return; + } - s_X11.getWindowProperty = (XGetWindowPropertyFn)dlsym( - s_X11.handle, - "XGetWindowProperty"); + struct wl_surface* _surface = nullptr; + _surface = (struct wl_surface*)winData->window; - s_X11.internAtom = (XInternAtomFn)dlsym( - s_X11.handle, - "XInternAtom"); + wlSurfaceAttach(_surface, buffer, 0, 0); + wlSurfaceDamageBuffer(_surface, 0, 0, winData->w, winData->h); + wlSurfaceCommit(_surface); - s_X11.getSelectionOwner = (XGetSelectionOwnerFn)dlsym( - s_X11.handle, - "XGetSelectionOwner"); + // destroy old buffer + wlBufferDestroy(winData->buffer); + winData->buffer = buffer; + } - s_X11.freeColormap = (XFreeColormapFn)dlsym( - s_X11.handle, - "XFreeColormap"); + // push a window resize event + if (s_Video.eventDriver) { + PalEventType type = PAL_EVENT_WINDOW_SIZE; + PalDispatchMode mode = PAL_DISPATCH_NONE; + mode = palGetEventDispatchMode(s_Video.eventDriver, type); + if (mode != PAL_DISPATCH_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = palPackUint32(winData->w, winData->h); + event.data2 = palPackPointer(winData->window); + palPushEvent(s_Video.eventDriver, &event); + } + } - s_X11.storeName = (XStoreNameFn)dlsym( - s_X11.handle, - "XStoreName"); + winData->pushConfigureEvent = false; + } + } - s_X11.changeProperty = (XChangePropertyFn)dlsym( - s_X11.handle, - "XChangeProperty"); + // pending state + if (!winData->skipState) { + if (!winData->pushStateEvent) { + return; + } - s_X11.flush = (XFlushFn)dlsym( - s_X11.handle, - "XFlush"); + // push a window state event + // we dont recreate buffers over here + // since we already create the buffer with the new size + if (s_Video.eventDriver) { + PalEventType type = PAL_EVENT_WINDOW_STATE; + PalDispatchMode mode = PAL_DISPATCH_NONE; + mode = palGetEventDispatchMode(s_Video.eventDriver, type); + if (mode != PAL_DISPATCH_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = winData->state; + event.data2 = palPackPointer(winData->window); + palPushEvent(s_Video.eventDriver, &event); + } + } - s_X11.createColormap = (XCreateColormapFn)dlsym( - s_X11.handle, - "XCreateColormap"); + winData->pushStateEvent = false; + } +} - s_X11.mapWindow = (XMapWindowFn)dlsym( - s_X11.handle, - "XMapWindow"); +static void xdgToplevelHandleConfigure( + void* data, + struct xdg_toplevel* toplevel, + int32_t width, + int32_t height, + struct wl_array* states) +{ + WindowData* winData = (WindowData*)data; + uint32_t* state; + bool activated = false; + wl_array_for_each(state, states) + { + // we need only maximized + if (*state == 1) { // XDG_TOPLEVEL_STATE_MAXIMIZED + if (winData->state != PAL_WINDOW_STATE_MAXIMIZED) { + winData->state = PAL_WINDOW_STATE_MAXIMIZED; + winData->pushStateEvent = true; + } - s_X11.unmapWindow = (XUnmapWindowFn)dlsym( - s_X11.handle, - "XUnmapWindow"); + } else if (*state == 4) { // XDG_TOPLEVEL_STATE_ACTIVATED + activated = true; + } + } - s_X11.createWindow = (XCreateWindowFn)dlsym( - s_X11.handle, - "XCreateWindow"); - - s_X11.destroyWindow = (XDestroyWindowFn)dlsym( - s_X11.handle, - "XDestroyWindow"); + if (width > 0 && height > 0) { + if (width != winData->w || height != winData->h) { + // size change + winData->pushConfigureEvent = true; + } - s_X11.matchVisualInfo = (XMatchVisualInfoFn)dlsym( - s_X11.handle, - "XMatchVisualInfo"); + winData->w = width; + winData->h = height; + } - s_X11.pending = (XPendingFn)dlsym( - s_X11.handle, - "XPending"); + if (activated && !winData->focused) { + // focus gained + winData->focused = true; - s_X11.setWMProtocols = (XSetWMProtocolsFn)dlsym( - s_X11.handle, - "XSetWMProtocols"); + } else if (!activated && winData->focused) { + // focus lost + winData->focused = false; - s_X11.nextEvent = (XNextEventFn)dlsym( - s_X11.handle, - "XNextEvent"); + } else { + // discard double focus gained and double focus lost + return; + } - s_X11.setWMNormalHints = (XSetWMNormalHintsFn)dlsym( - s_X11.handle, - "XSetWMNormalHints"); + if (s_Video.eventDriver) { + PalEventDriver* driver = s_Video.eventDriver; + PalDispatchMode mode = PAL_DISPATCH_NONE; + PalEventType type = PAL_EVENT_WINDOW_FOCUS; + mode = palGetEventDispatchMode(driver, type); - s_X11.getWMNormalHints = (XGetWMNormalHintsFn)dlsym( - s_X11.handle, - "XGetWMNormalHints"); + if (mode != PAL_DISPATCH_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = winData->focused; + event.data2 = palPackPointer(winData->window); + palPushEvent(driver, &event); + } + } +} - s_X11.sendEvent = (XSendEventFn)dlsym( - s_X11.handle, - "XSendEvent"); +static void xdgToplevelHandleClose( + void* data, + struct xdg_toplevel* toplevel) +{ + WindowData* winData = (WindowData*)data; + if (s_Video.eventDriver) { + PalEventType type = PAL_EVENT_WINDOW_CLOSE; + PalDispatchMode mode = PAL_DISPATCH_NONE; + mode = palGetEventDispatchMode(s_Video.eventDriver, type); + if (mode != PAL_DISPATCH_NONE) { + PalEvent event = {0}; + event.type = type; + event.data2 = palPackPointer(winData->window); + palPushEvent(s_Video.eventDriver, &event); + } + } +} - s_X11.moveWindow = (XMoveWindowFn)dlsym( - s_X11.handle, - "XMoveWindow"); +static inline int xdgSurfaceAddListener( + struct xdg_surface* xdg_surface, + const struct xdg_surface_listener* listener, + void* data) +{ + return s_Wl.proxyAddListener( + (struct wl_proxy*)xdg_surface, + (void (**)(void))listener, + data); +} - s_X11.resizeWindow = (XResizeWindowFn)dlsym( - s_X11.handle, - "XResizeWindow"); +static inline void xdgSurfaceDestroy(struct xdg_surface* xdg_surface) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy*)xdg_surface, + 0, // XDG_SURFACE_DESTROY + NULL, + s_Wl.proxyGetVersion((struct wl_proxy*)xdg_surface), + WL_MARSHAL_FLAG_DESTROY); +} - s_X11.iconifyWindow = (XIconifyWindowFn)dlsym( - s_X11.handle, - "XIconifyWindow"); +static inline void xdgToplevelDestroy(struct xdg_toplevel* xdg_toplevel) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy*)xdg_toplevel, + 0, // XDG_TOPLEVEL_DESTROY + NULL, + s_Wl.proxyGetVersion((struct wl_proxy*)xdg_toplevel), + WL_MARSHAL_FLAG_DESTROY); +} - s_X11.setErrorHandler = (XSetErrorHandlerFn)dlsym( - s_X11.handle, - "XSetErrorHandler"); +static inline void xdgToplevelSetTitle( + struct xdg_toplevel* xdg_toplevel, + const char* title) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy*)xdg_toplevel, + 2, // XDG_TOPLEVEL_SET_TITLE + NULL, + s_Wl.proxyGetVersion((struct wl_proxy*)xdg_toplevel), + 0, + title); +} - s_X11.sync = (XSyncFn)dlsym( - s_X11.handle, - "XSync"); +static inline void xdgToplevelSetMaximized(struct xdg_toplevel* xdg_toplevel) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy*)xdg_toplevel, + 9, // XDG_TOPLEVEL_SET_MAXIMIZED + NULL, + s_Wl.proxyGetVersion((struct wl_proxy*)xdg_toplevel), + 0); +} - s_X11.saveContext = (XSaveContextFn)dlsym( - s_X11.handle, - "XSaveContext"); +static inline void xdgToplevelSetMinimized(struct xdg_toplevel* xdg_toplevel) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy*)xdg_toplevel, + 13, // XDG_TOPLEVEL_SET_MINIMIZED + NULL, + s_Wl.proxyGetVersion((struct wl_proxy*)xdg_toplevel), + 0); +} - s_X11.findContext = (XFindContextFn)dlsym( - s_X11.handle, - "XFindContext"); +static inline int xdgToplevelAddListener( + struct xdg_toplevel* xdg_toplevel, + const struct xdg_toplevel_listener* listener, + void* data) +{ + return s_Wl.proxyAddListener( + (struct wl_proxy*)xdg_toplevel, + (void (**)(void))listener, + data); +} - s_X11.uniqueContext = (XrmUniqueQuarkFn)dlsym( - s_X11.handle, - "XrmUniqueQuark"); +static inline void xdgToplevelSetMinSize( + struct xdg_toplevel* xdg_toplevel, + int32_t width, + int32_t height) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy*)xdg_toplevel, + 8, // XDG_TOPLEVEL_SET_MIN_SIZE + NULL, + s_Wl.proxyGetVersion((struct wl_proxy*)xdg_toplevel), + 0, + width, + height); +} - // load Xrandr functions - s_X11.getScreenResources = (XRRGetScreenResourcesFn)dlsym( - s_X11.xrandr, - "XRRGetScreenResources"); +static inline void xdgToplevelSetMaxSize( + struct xdg_toplevel* xdg_toplevel, + int32_t width, + int32_t height) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy*)xdg_toplevel, + 7, // XDG_TOPLEVEL_SET_MAX_SIZE + NULL, + s_Wl.proxyGetVersion((struct wl_proxy*)xdg_toplevel), + 0, + width, + height); +} - s_X11.getOutputPrimary = (XRRGetOutputPrimaryFn)dlsym( - s_X11.xrandr, - "XRRGetOutputPrimary"); +static inline void xdgToplevelSetAppId( + struct xdg_toplevel* xdg_toplevel, + const char* app_id) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy*)xdg_toplevel, + 3, // XDG_TOPLEVEL_SET_APP_ID + NULL, + s_Wl.proxyGetVersion((struct wl_proxy*)xdg_toplevel), + 0, + app_id); +} - 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"); +static inline void xdgToplevelUnsetMaximized(struct xdg_toplevel* xdg_toplevel) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy*)xdg_toplevel, + 10, // XDG_TOPLEVEL_UNSET_MAXIMIZED + NULL, + s_Wl.proxyGetVersion((struct wl_proxy*)xdg_toplevel), + 0); +} - s_X11.selectRRInput = (XRRSelectInputFn)dlsym( - s_X11.xrandr, - "XRRSelectInput"); +static const struct wl_interface* xdg_shell_types[26]; + +static const struct wl_message xdg_wm_base_requests[] = { + {"destroy", "", xdg_shell_types + 0}, + {"create_positioner", "n", xdg_shell_types + 4}, + {"get_xdg_surface", "no", xdg_shell_types + 5}, + {"pong", "u", xdg_shell_types + 0}, +}; + +static const struct wl_message xdg_wm_base_events[] = { + {"ping", "u", xdg_shell_types + 0}, +}; + +const struct wl_interface xdg_wm_base_interface = { + "xdg_wm_base", + 6, + 4, + xdg_wm_base_requests, + 1, + xdg_wm_base_events, +}; + +static const struct wl_message xdg_positioner_requests[] = { + {"destroy", "", xdg_shell_types + 0}, + {"set_size", "ii", xdg_shell_types + 0}, + {"set_anchor_rect", "iiii", xdg_shell_types + 0}, + {"set_anchor", "u", xdg_shell_types + 0}, + {"set_gravity", "u", xdg_shell_types + 0}, + {"set_constraint_adjustment", "u", xdg_shell_types + 0}, + {"set_offset", "ii", xdg_shell_types + 0}, + {"set_reactive", "3", xdg_shell_types + 0}, + {"set_parent_size", "3ii", xdg_shell_types + 0}, + {"set_parent_configure", "3u", xdg_shell_types + 0}, +}; + +const struct wl_interface xdg_positioner_interface = { + "xdg_positioner", + 6, + 10, + xdg_positioner_requests, + 0, + NULL, +}; + +static const struct wl_message xdg_surface_requests[] = { + {"destroy", "", xdg_shell_types + 0}, + {"get_toplevel", "n", xdg_shell_types + 7}, + {"get_popup", "n?oo", xdg_shell_types + 8}, + {"set_window_geometry", "iiii", xdg_shell_types + 0}, + {"ack_configure", "u", xdg_shell_types + 0}, +}; + +static const struct wl_message xdg_surface_events[] = { + {"configure", "u", xdg_shell_types + 0}, +}; + +const struct wl_interface xdg_surface_interface = { + "xdg_surface", + 6, + 5, + xdg_surface_requests, + 1, + xdg_surface_events, +}; + +static const struct wl_message xdg_toplevel_requests[] = { + {"destroy", "", xdg_shell_types + 0}, + {"set_parent", "?o", xdg_shell_types + 11}, + {"set_title", "s", xdg_shell_types + 0}, + {"set_app_id", "s", xdg_shell_types + 0}, + {"show_window_menu", "ouii", xdg_shell_types + 12}, + {"move", "ou", xdg_shell_types + 16}, + {"resize", "ouu", xdg_shell_types + 18}, + {"set_max_size", "ii", xdg_shell_types + 0}, + {"set_min_size", "ii", xdg_shell_types + 0}, + {"set_maximized", "", xdg_shell_types + 0}, + {"unset_maximized", "", xdg_shell_types + 0}, + {"set_fullscreen", "?o", xdg_shell_types + 21}, + {"unset_fullscreen", "", xdg_shell_types + 0}, + {"set_minimized", "", xdg_shell_types + 0}, +}; + +static const struct wl_message xdg_toplevel_events[] = { + {"configure", "iia", xdg_shell_types + 0}, + {"close", "", xdg_shell_types + 0}, + {"configure_bounds", "4ii", xdg_shell_types + 0}, + {"wm_capabilities", "5a", xdg_shell_types + 0}, +}; + +const struct wl_interface xdg_toplevel_interface = { + "xdg_toplevel", + 6, + 14, + xdg_toplevel_requests, + 4, + xdg_toplevel_events, +}; + +static const struct wl_message xdg_popup_requests[] = { + {"destroy", "", xdg_shell_types + 0}, + {"grab", "ou", xdg_shell_types + 22}, + {"reposition", "3ou", xdg_shell_types + 24}, +}; + +static const struct wl_message xdg_popup_events[] = { + {"configure", "iiii", xdg_shell_types + 0}, + {"popup_done", "", xdg_shell_types + 0}, + {"repositioned", "3u", xdg_shell_types + 0}, +}; + +const struct wl_interface xdg_popup_interface = { + "xdg_popup", + 6, + 3, + xdg_popup_requests, + 3, + xdg_popup_events, +}; + +static void setupXdgShellProtocol() +{ + xdg_shell_types[0] = NULL; + xdg_shell_types[1] = NULL; + xdg_shell_types[2] = NULL; + xdg_shell_types[3] = NULL; + xdg_shell_types[4] = &xdg_positioner_interface; + xdg_shell_types[5] = &xdg_surface_interface; + xdg_shell_types[6] = s_Wl.surfaceInterface; + xdg_shell_types[7] = &xdg_toplevel_interface; + xdg_shell_types[8] = &xdg_popup_interface; + xdg_shell_types[9] = &xdg_surface_interface; + xdg_shell_types[10] = &xdg_positioner_interface; + xdg_shell_types[11] = &xdg_toplevel_interface; + xdg_shell_types[12] = s_Wl.seatInterface; + xdg_shell_types[13] = NULL; + xdg_shell_types[14] = NULL; + xdg_shell_types[15] = NULL; + xdg_shell_types[16] = s_Wl.seatInterface; + xdg_shell_types[17] = NULL; + xdg_shell_types[18] = s_Wl.seatInterface; + xdg_shell_types[19] = NULL; + xdg_shell_types[20] = NULL; + xdg_shell_types[21] = s_Wl.outputInterface; + xdg_shell_types[22] = s_Wl.seatInterface; + xdg_shell_types[23] = NULL; + xdg_shell_types[24] = &xdg_positioner_interface; + xdg_shell_types[25] = NULL; +} - s_X11.queryRRExtension = (XRRQueryExtensionFn)dlsym( - s_X11.xrandr, - "XRRQueryExtension"); +static const struct xdg_wm_base_listener wmBaseListener = { + .ping = wmBaseHandlePing}; - s_X11.allocClassHint = (XAllocClassHintFn)dlsym( - s_X11.handle, - "XAllocClassHint"); +static const struct xdg_surface_listener xdgSurfaceListener = { + .configure = xdgSurfaceHandleConfigure}; - s_X11.setClassHint = (XSetClassHintFn)dlsym( - s_X11.handle, - "XSetClassHint"); +static const struct xdg_toplevel_listener xdgToplevelListener = { + .configure = xdgToplevelHandleConfigure, + .close = xdgToplevelHandleClose, + .configure_bounds = nullptr, + .wm_capabilities = nullptr}; - s_X11.free = (XFreeFn)dlsym( - s_X11.handle, - "XFree"); +#endif // PAL_HAS_WAYLAND +#pragma endregion - s_X11.getVisualInfo = (XGetVisualInfoFn)dlsym( - s_X11.handle, - "XGetVisualInfo"); +#pragma region Zxdg-Decoraion_Manager-V1 +#if PAL_HAS_WAYLAND - s_X11.createFontCursor = (XCreateFontCursorFn)dlsym( - s_X11.handle, - "XCreateFontCursor"); - - s_X11.freePixmap = (XFreePixmapFn)dlsym( - s_X11.handle, - "XFreePixmap"); +struct xdg_toplevel; +struct zxdg_decoration_manager_v1; +struct zxdg_toplevel_decoration_v1; - s_X11.setWMHints = (XSetWMHintsFn)dlsym( - s_X11.handle, - "XSetWMHints"); +struct zxdg_toplevel_decoration_v1_listener { + void (*configure)( + void*, + struct zxdg_toplevel_decoration_v1*, + uint32_t); +}; - s_X11.grabPointer = (XGrabPointerFn)dlsym( - s_X11.handle, - "XGrabPointer"); +const struct wl_interface zxdg_decoration_manager_v1_interface; +const struct wl_interface zxdg_toplevel_decoration_v1_interface; - s_X11.createPixmapCursor = (XCreatePixmapCursorFn)dlsym( - s_X11.handle, - "XCreatePixmapCursor"); +static inline void zxdgDecorationManagerV1Destroy( + struct zxdg_decoration_manager_v1* zxdg_decoration_manager_v1) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy*)zxdg_decoration_manager_v1, + 0, // ZXDG_DECORATION_MANAGER_V1_DESTROY + NULL, + s_Wl.proxyGetVersion((struct wl_proxy*)zxdg_decoration_manager_v1), + WL_MARSHAL_FLAG_DESTROY); +} - s_X11.warpPointer = (XWarpPointerFn)dlsym( - s_X11.handle, - "XWarpPointer"); +static inline struct zxdg_toplevel_decoration_v1* zxdgGetToplevelDecoration( + struct zxdg_decoration_manager_v1* zxdg_decoration_manager_v1, + struct xdg_toplevel* toplevel) +{ + struct wl_proxy* id; + id = s_Wl.proxyMarshalFlags( + (struct wl_proxy*)zxdg_decoration_manager_v1, + 1, // ZXDG_DECORATION_MANAGER_V1_GET_TOPLEVEL_DECORATION, + &zxdg_toplevel_decoration_v1_interface, + s_Wl.proxyGetVersion((struct wl_proxy*)zxdg_decoration_manager_v1), + 0, + NULL, + toplevel); - s_X11.getWMName = (XGetWMNameFn)dlsym( - s_X11.handle, - "XGetWMName"); + return (struct zxdg_toplevel_decoration_v1*)id; +} - s_X11.queryPointer = (XQueryPointerFn)dlsym( - s_X11.handle, - "XQueryPointer"); +static inline int zxdgToplevelDecorationV1AddListener( + struct zxdg_toplevel_decoration_v1* zxdg_toplevel_decoration_v1, + const struct zxdg_toplevel_decoration_v1_listener* listener, + void* data) +{ + return s_Wl.proxyAddListener( + (struct wl_proxy*)zxdg_toplevel_decoration_v1, + (void (**)(void))listener, + data); +} - s_X11.ungrabPointer = (XUngrabPointerFn)dlsym( - s_X11.handle, - "XUngrabPointer"); +static inline void zxdgToplevelDecorationV1Destroy( + struct zxdg_toplevel_decoration_v1* zxdg_toplevel_decoration_v1) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy*)zxdg_toplevel_decoration_v1, + 0, // ZXDG_TOPLEVEL_DECORATION_V1_DESTROY + NULL, + s_Wl.proxyGetVersion((struct wl_proxy*)zxdg_toplevel_decoration_v1), + WL_MARSHAL_FLAG_DESTROY); +} - s_X11.allocWMHints = (XAllocWMHintsFn)dlsym( - s_X11.handle, - "XAllocWMHints"); +static inline void zxdgToplevelDecorationV1SetMode( + struct zxdg_toplevel_decoration_v1* zxdg_toplevel_decoration_v1, + uint32_t mode) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy*)zxdg_toplevel_decoration_v1, + 1, // ZXDG_TOPLEVEL_DECORATION_V1_SET_MODE, + NULL, + s_Wl.proxyGetVersion((struct wl_proxy*)zxdg_toplevel_decoration_v1), + 0, + mode); +} - s_X11.mapRaised = (XMapRaisedFn)dlsym( - s_X11.handle, - "XMapRaised"); +static const struct wl_interface* xdg_decoration_unstable_v1_types[] = { + NULL, + &zxdg_toplevel_decoration_v1_interface, + &xdg_toplevel_interface, +}; + +static const struct wl_message zxdg_decoration_manager_v1_requests[] = { + {"destroy", "", xdg_decoration_unstable_v1_types + 0}, + {"get_toplevel_decoration", "no", xdg_decoration_unstable_v1_types + 1}, +}; + +const struct wl_interface zxdg_decoration_manager_v1_interface = { + "zxdg_decoration_manager_v1", + 1, + 2, + zxdg_decoration_manager_v1_requests, + 0, + NULL, +}; + +static const struct wl_message zxdg_toplevel_decoration_v1_requests[] = { + {"destroy", "", xdg_decoration_unstable_v1_types + 0}, + {"set_mode", "u", xdg_decoration_unstable_v1_types + 0}, + {"unset_mode", "", xdg_decoration_unstable_v1_types + 0}, +}; + +static const struct wl_message zxdg_toplevel_decoration_v1_events[] = { + {"configure", "u", xdg_decoration_unstable_v1_types + 0}, +}; + +const struct wl_interface zxdg_toplevel_decoration_v1_interface = { + "zxdg_toplevel_decoration_v1", + 1, + 3, + zxdg_toplevel_decoration_v1_requests, + 1, + zxdg_toplevel_decoration_v1_events, +}; + +void zxdgDecorationHandleConfigure( + void* data, + struct zxdg_toplevel_decoration_v1* dec, + uint32_t mode) +{ + if (s_Video.eventDriver) { + PalEventDriver* driver = s_Video.eventDriver; + PalDispatchMode dispatchMode = PAL_DISPATCH_NONE; + PalEventType type = PAL_EVENT_WINDOW_DECORATION_MODE; + dispatchMode = palGetEventDispatchMode(driver, type); + + if (dispatchMode != PAL_DISPATCH_NONE) { + PalDecorationMode decorMode = PAL_DECORATION_MODE_SERVER_SIDE; + if (mode == 0 || mode == 1) { + // client side decoration + decorMode = PAL_DECORATION_MODE_CLIENT_SIDE; + } - s_X11.undefineCursor = (XUndefineCursorFn)dlsym( - s_X11.handle, - "XUndefineCursor"); + PalEvent event = {0}; + event.type = type; + event.data = decorMode; + event.data2 = palPackPointer(data); + palPushEvent(driver, &event); + } + } +} - s_X11.defineCursor = (XDefineCursorFn)dlsym( - s_X11.handle, - "XDefineCursor"); +static struct zxdg_toplevel_decoration_v1_listener decorationListener = { + .configure = zxdgDecorationHandleConfigure}; - s_X11.freeCursor = (XFreeCursorFn)dlsym( - s_X11.handle, - "XFreeCursor"); +#endif // PAL_HAS_WAYLAND +#pragma endregion - s_X11.getWMHints = (XGetWMHintsFn)dlsym( - s_X11.handle, - "XGetWMHints"); +// ================================================== +// Internal API +// ================================================== - s_X11.createPixmap = (XCreatePixmapFn)dlsym( - s_X11.handle, - "XCreatePixmap"); +static int compareModes( + const void* a, + const void* b) +{ + const PalMonitorMode* mode1 = (const PalMonitorMode*)a; + const PalMonitorMode* mode2 = (const PalMonitorMode*)b; - s_X11.setInputFocus = (XSetInputFocusFn)dlsym( - s_X11.handle, - "XSetInputFocus"); + // compare fields + if (mode1->width != mode2->width) { + return mode1->width - mode2->width; + } - s_X11.getInputFocus = (XGetInputFocusFn)dlsym( - s_X11.handle, - "XGetInputFocus"); + if (mode1->height != mode2->height) { + return mode1->height - mode2->height; + } - s_X11.selectInput = (XSelectInputFn)dlsym( - s_X11.handle, - "XSelectInput"); + if (mode1->refreshRate != mode2->refreshRate) { + return mode1->refreshRate - mode2->refreshRate; + } - // libXcursor - s_X11.cursorImageLoadCursor = (XcursorImageLoadCursorFn)dlsym( - s_X11.libCursor, - "XcursorImageLoadCursor"); + if (mode1->bpp != mode2->bpp) { + return mode1->bpp - mode2->bpp; + } +} - s_X11.cursorImageCreate = (XcursorImageCreateFn)dlsym( - s_X11.libCursor, - "XcursorImageCreate"); +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]; + } + } - s_X11.cursorImageDestroy = (XcursorImageDestroyFn)dlsym( - s_X11.libCursor, - "XcursorImageDestroy"); + // 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)); - s_X11.lookupKeysym = (XLookupKeysymFn)dlsym( - s_X11.libCursor, - "XLookupKeysym"); + palFree(s_Video.allocator, s_Video.windowData); + s_Video.windowData = data; + s_Video.maxWindowData = count; - s_X11.setDetectableAutoRepeat = (XkbSetDetectableAutoRepeatFn)dlsym( - s_X11.handle, - "XkbSetDetectableAutoRepeat"); + s_Video.windowData[freeIndex].used = true; + return &s_Video.windowData[freeIndex]; + } + return nullptr; +} - s_X11.setLocaleModifiers = (XSetLocaleModifiersFn)dlsym( - s_X11.handle, - "XSetLocaleModifiers"); +static WindowData* findWindowData(PalWindow* window) +{ + for (int i = 0; i < s_Video.maxWindowData; ++i) { + if (s_Video.windowData[i].used && + s_Video.windowData[i].window == window) { + return &s_Video.windowData[i]; + } + } + return nullptr; +} - s_X11.openIM = (XOpenIMFn)dlsym( - s_X11.handle, - "XOpenIM"); +static void resetMonitorData() +{ + memset( + s_Video.monitorData, + 0, + s_Video.maxMonitorData * sizeof(MonitorData)); +} - s_X11.closeIM = (XCloseIMFn)dlsym( - s_X11.handle, - "XCloseIM"); +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]; + } + } - s_X11.createIC = (XCreateICFn)dlsym( - s_X11.handle, - "XCreateIC"); + // 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)); - s_X11.destroyIC = (XDestroyICFn)dlsym( - s_X11.handle, - "XDestroyIC"); + palFree(s_Video.allocator, s_Video.monitorData); + s_Video.monitorData = data; + s_Video.maxWindowData = count; - s_X11.utf8LookupString = (Xutf8LookupStringFn)dlsym( - s_X11.handle, - "Xutf8LookupString"); + s_Video.monitorData[freeIndex].used = true; + return &s_Video.monitorData[freeIndex]; + } + return nullptr; +} - // X11 server - s_X11.display = s_X11.openDisplay(nullptr); - if (!s_X11.display) { - return PAL_RESULT_PLATFORM_FAILURE; +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]; + } } + return nullptr; +} - s_X11.root = DefaultRootWindow(s_X11.display); - s_X11.screen = DefaultScreen(s_X11.display); +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; + } + } +} - xCheckFeatures(); - - // subscribe for monitor events - s_X11.selectRRInput( - s_X11.display, - s_X11.root, - RRScreenChangeNotifyMask | RRNotify); +static void createScancodeTable() +{ + // Letters + s_Keyboard.scancodes[0x01E] = PAL_SCANCODE_A; + s_Keyboard.scancodes[0x030] = PAL_SCANCODE_B; + s_Keyboard.scancodes[0x02E] = PAL_SCANCODE_C; + s_Keyboard.scancodes[0x020] = PAL_SCANCODE_D; + s_Keyboard.scancodes[0x012] = PAL_SCANCODE_E; + s_Keyboard.scancodes[0x021] = PAL_SCANCODE_F; + s_Keyboard.scancodes[0x022] = PAL_SCANCODE_G; + s_Keyboard.scancodes[0x023] = PAL_SCANCODE_H; + s_Keyboard.scancodes[0x017] = PAL_SCANCODE_I; + s_Keyboard.scancodes[0x024] = PAL_SCANCODE_J; + s_Keyboard.scancodes[0x025] = PAL_SCANCODE_K; + s_Keyboard.scancodes[0x026] = PAL_SCANCODE_L; + s_Keyboard.scancodes[0x032] = PAL_SCANCODE_M; + s_Keyboard.scancodes[0x031] = PAL_SCANCODE_N; + s_Keyboard.scancodes[0x018] = PAL_SCANCODE_O; + s_Keyboard.scancodes[0x019] = PAL_SCANCODE_P; + s_Keyboard.scancodes[0x010] = PAL_SCANCODE_Q; + s_Keyboard.scancodes[0x013] = PAL_SCANCODE_R; + s_Keyboard.scancodes[0x01F] = PAL_SCANCODE_S; + s_Keyboard.scancodes[0x014] = PAL_SCANCODE_T; + s_Keyboard.scancodes[0x016] = PAL_SCANCODE_U; + s_Keyboard.scancodes[0x02F] = PAL_SCANCODE_V; + s_Keyboard.scancodes[0x011] = PAL_SCANCODE_W; + s_Keyboard.scancodes[0x02D] = PAL_SCANCODE_X; + s_Keyboard.scancodes[0x015] = PAL_SCANCODE_Y; + s_Keyboard.scancodes[0x02C] = PAL_SCANCODE_Z; - int eventBase, errorBase = 0; - s_X11.queryRRExtension(s_X11.display, &eventBase, &errorBase); - s_X11.rrEventBase = eventBase; - s_X11.skipScreenEvent = true; - s_X11.skipNotifyEvent = true; + // Numbers (top row) + s_Keyboard.scancodes[0x00B] = PAL_SCANCODE_0; + s_Keyboard.scancodes[0x002] = PAL_SCANCODE_1; + s_Keyboard.scancodes[0x003] = PAL_SCANCODE_2; + s_Keyboard.scancodes[0x004] = PAL_SCANCODE_3; + s_Keyboard.scancodes[0x005] = PAL_SCANCODE_4; + s_Keyboard.scancodes[0x006] = PAL_SCANCODE_5; + s_Keyboard.scancodes[0x007] = PAL_SCANCODE_6; + s_Keyboard.scancodes[0x008] = PAL_SCANCODE_7; + s_Keyboard.scancodes[0x009] = PAL_SCANCODE_8; + s_Keyboard.scancodes[0x00A] = PAL_SCANCODE_9; - s_X11.dataID = (XContext)s_X11.uniqueContext(); - s_X11.className = "PAL"; - resetMonitorData(); - xCacheMonitors(true); + // Function + s_Keyboard.scancodes[0x03B] = PAL_SCANCODE_F1; + s_Keyboard.scancodes[0x03C] = PAL_SCANCODE_F2; + s_Keyboard.scancodes[0x03D] = PAL_SCANCODE_F3; + s_Keyboard.scancodes[0x03E] = PAL_SCANCODE_F4; + s_Keyboard.scancodes[0x03F] = PAL_SCANCODE_F5; + s_Keyboard.scancodes[0x040] = PAL_SCANCODE_F6; + s_Keyboard.scancodes[0x041] = PAL_SCANCODE_F7; + s_Keyboard.scancodes[0x042] = PAL_SCANCODE_F8; + s_Keyboard.scancodes[0x043] = PAL_SCANCODE_F9; + s_Keyboard.scancodes[0x044] = PAL_SCANCODE_F10; + s_Keyboard.scancodes[0x057] = PAL_SCANCODE_F11; + s_Keyboard.scancodes[0x058] = PAL_SCANCODE_F12; - // since X11 supports both EGL and GLX - // we try to load them and resolve the needed functions + // Control + s_Keyboard.scancodes[0x001] = PAL_SCANCODE_ESCAPE; + s_Keyboard.scancodes[0x01C] = PAL_SCANCODE_ENTER; + s_Keyboard.scancodes[0x00F] = PAL_SCANCODE_TAB; + s_Keyboard.scancodes[0x00E] = PAL_SCANCODE_BACKSPACE; + s_Keyboard.scancodes[0x039] = PAL_SCANCODE_SPACE; + s_Keyboard.scancodes[0x03A] = PAL_SCANCODE_CAPSLOCK; + s_Keyboard.scancodes[0x045] = PAL_SCANCODE_NUMLOCK; + s_Keyboard.scancodes[0x046] = PAL_SCANCODE_SCROLLLOCK; + s_Keyboard.scancodes[0x02A] = PAL_SCANCODE_LSHIFT; + s_Keyboard.scancodes[0x036] = PAL_SCANCODE_RSHIFT; + s_Keyboard.scancodes[0x01D] = PAL_SCANCODE_LCTRL; + s_Keyboard.scancodes[0x061] = PAL_SCANCODE_RCTRL; + s_Keyboard.scancodes[0x038] = PAL_SCANCODE_LALT; + s_Keyboard.scancodes[0x064] = PAL_SCANCODE_RALT; - // we load GLX - s_X11.glxHandle = dlopen("libGL.so.1", RTLD_LAZY); - if (s_X11.glxHandle) { + // Arrows + s_Keyboard.scancodes[0x069] = PAL_SCANCODE_LEFT; + s_Keyboard.scancodes[0x06A] = PAL_SCANCODE_RIGHT; + s_Keyboard.scancodes[0x067] = PAL_SCANCODE_UP; + s_Keyboard.scancodes[0x06C] = PAL_SCANCODE_DOWN; - GLXGetProcAddressFn load = nullptr; - load = (GLXGetProcAddressFn)dlsym( - s_X11.glxHandle, - "glXGetProcAddress"); + // Navigation + s_Keyboard.scancodes[0x06E] = PAL_SCANCODE_INSERT; + s_Keyboard.scancodes[0x06F] = PAL_SCANCODE_DELETE; + s_Keyboard.scancodes[0x066] = PAL_SCANCODE_HOME; + s_Keyboard.scancodes[0x067] = PAL_SCANCODE_END; + s_Keyboard.scancodes[0x068] = PAL_SCANCODE_PAGEUP; + s_Keyboard.scancodes[0x06D] = PAL_SCANCODE_PAGEDOWN; - s_X11.glxGetFBConfigs = (GLXGetFBConfigsFn)load( - "glXGetFBConfigs"); + // Keypad + s_Keyboard.scancodes[0x052] = PAL_SCANCODE_KP_0; + s_Keyboard.scancodes[0x04F] = PAL_SCANCODE_KP_1; + s_Keyboard.scancodes[0x050] = PAL_SCANCODE_KP_2; + s_Keyboard.scancodes[0x051] = PAL_SCANCODE_KP_3; + s_Keyboard.scancodes[0x04B] = PAL_SCANCODE_KP_4; + s_Keyboard.scancodes[0x04C] = PAL_SCANCODE_KP_5; + s_Keyboard.scancodes[0x04D] = PAL_SCANCODE_KP_6; + s_Keyboard.scancodes[0x047] = PAL_SCANCODE_KP_7; + s_Keyboard.scancodes[0x048] = PAL_SCANCODE_KP_8; + s_Keyboard.scancodes[0x049] = PAL_SCANCODE_KP_9; + s_Keyboard.scancodes[0x060] = PAL_SCANCODE_KP_ENTER; + s_Keyboard.scancodes[0x04E] = PAL_SCANCODE_KP_ADD; + s_Keyboard.scancodes[0x04A] = PAL_SCANCODE_KP_SUBTRACT; + s_Keyboard.scancodes[0x037] = PAL_SCANCODE_KP_MULTIPLY; + s_Keyboard.scancodes[0x062] = PAL_SCANCODE_KP_DIVIDE; + s_Keyboard.scancodes[0x053] = PAL_SCANCODE_KP_DECIMAL; - s_X11.glxGetFBConfigAttrib = (GLXGetFBConfigAttribFn)load( - "glXGetFBConfigAttrib"); + // Misc + s_Keyboard.scancodes[0x063] = PAL_SCANCODE_PRINTSCREEN; + s_Keyboard.scancodes[0x066] = PAL_SCANCODE_PAUSE; + s_Keyboard.scancodes[0x07F] = PAL_SCANCODE_MENU; + s_Keyboard.scancodes[0x028] = PAL_SCANCODE_APOSTROPHE; + s_Keyboard.scancodes[0x02B] = PAL_SCANCODE_BACKSLASH; + s_Keyboard.scancodes[0x033] = PAL_SCANCODE_COMMA; + s_Keyboard.scancodes[0x00D] = PAL_SCANCODE_EQUAL; + s_Keyboard.scancodes[0x029] = PAL_SCANCODE_GRAVEACCENT; + s_Keyboard.scancodes[0x00C] = PAL_SCANCODE_SUBTRACT; + s_Keyboard.scancodes[0x034] = PAL_SCANCODE_PERIOD; + s_Keyboard.scancodes[0x027] = PAL_SCANCODE_SEMICOLON; + s_Keyboard.scancodes[0x035] = PAL_SCANCODE_SLASH; + s_Keyboard.scancodes[0x01A] = PAL_SCANCODE_LBRACKET; + s_Keyboard.scancodes[0x01B] = PAL_SCANCODE_RBRACKET; + s_Keyboard.scancodes[0x07D] = PAL_SCANCODE_LSUPER; + s_Keyboard.scancodes[0x07E] = PAL_SCANCODE_RSUPER; +} - s_X11.glxGetVisualFromFBConfig = (GLXGetVisualFromFBConfigFn)load( - "glXGetVisualFromFBConfig"); +void palSetLastPlatformError(Uint32 e); + +// ================================================== +// X11 API +// ================================================== + +#pragma region X11 API +#if PAL_HAS_X11 + +static PalResult glxBackend(const int index) +{ + // user choose GLX FBConfig backend + if (!s_X11.glxHandle) { + // Rare + palSetLastPlatformError(errno); + return PAL_RESULT_PLATFORM_FAILURE; } + // clang-format off - // 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( + int count = 0; + GLXFBConfig* configs = s_X11.glxGetFBConfigs( s_X11.display, - map, - map, - &dummy, - &dummy, - 0, - 0); - - s_X11.freePixmap(s_X11.display, map); - xCreateScancodeTable(); - xCreateKeycodeTable(); + s_X11.screen, + &count); - // disable auto key repeats - int supported; - s_X11.setDetectableAutoRepeat(s_X11.display, True, &supported); - if (!supported) { - // FIXME: fallback to manual key repeat detection + GLXFBConfig fbConfig = configs[index]; + if (!fbConfig) { + return PAL_RESULT_INVALID_GL_FBCONFIG; } - // create an input method - s_X11.setLocaleModifiers(""); - s_X11.im = s_X11.openIM(s_X11.display, nullptr, nullptr, nullptr); - if (s_X11.im == None) { - return PAL_RESULT_PLATFORM_FAILURE; + // 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.visualInfo = visualInfo; return PAL_RESULT_SUCCESS; } -static void xShutdownVideo() -{ - if (s_X11.colormap) { - s_X11.freeColormap(s_X11.display, s_X11.colormap); +static PalResult eglXBackend(int index) +{ + // user choose EGL FBConfig backend + if (!s_Egl.handle) { + palSetLastPlatformError(errno); + return PAL_RESULT_PLATFORM_FAILURE; } - if (s_X11.hiddenCursor) { - s_X11.freeCursor(s_X11.display, s_X11.hiddenCursor); + EGLDisplay display = EGL_NO_DISPLAY; + display = s_Egl.eglGetDisplay((EGLNativeDisplayType)s_X11.display); + if (display == EGL_NO_DISPLAY) { + palSetLastPlatformError(errno); + return PAL_RESULT_PLATFORM_FAILURE; } - s_X11.closeIM(s_X11.im); - s_X11.closeDisplay(s_X11.display); - dlclose(s_X11.handle); - dlclose(s_X11.xrandr); - dlclose(s_X11.libCursor); + EGLint numConfigs = 0; + if (!s_Egl.eglGetConfigs(display, nullptr, 0, &numConfigs)) { + palSetLastPlatformError(errno); + return PAL_RESULT_PLATFORM_FAILURE; + } - if (s_X11.glxHandle) { - dlclose(s_X11.glxHandle); + EGLint configSize = sizeof(EGLConfig) * numConfigs; + EGLConfig* eglConfigs = palAllocate(s_Video.allocator, configSize, 0); + if (!eglConfigs) { + return PAL_RESULT_OUT_OF_MEMORY; } -} -static void xUpdateVideo() -{ - XEvent event; - PalDispatchMode mode = PAL_DISPATCH_NONE; - while (s_X11.pending(s_X11.display)) { - s_X11.nextEvent(s_X11.display, &event); + s_Egl.eglGetConfigs(display, eglConfigs, numConfigs, &numConfigs); + EGLConfig config = eglConfigs[index]; - 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); + // we get a visual info 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.visualInfo = visualInfo; + palFree(s_Video.allocator, eglConfigs); + return PAL_RESULT_SUCCESS; +} + +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_STATE_HIDDEN); + 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; + PalVideoFeatures64 features64 = 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; + + // extended features + // old features + features64 |= PAL_VIDEO_FEATURE64_MULTI_MONITORS; + features64 |= PAL_VIDEO_FEATURE64_MONITOR_GET_ORIENTATION; + features64 |= PAL_VIDEO_FEATURE64_MONITOR_SET_MODE; + features64 |= PAL_VIDEO_FEATURE64_MONITOR_GET_MODE; + features64 |= PAL_VIDEO_FEATURE64_WINDOW_SET_SIZE; + features64 |= PAL_VIDEO_FEATURE64_WINDOW_GET_SIZE; + features64 |= PAL_VIDEO_FEATURE64_WINDOW_SET_VISIBILITY; + features64 |= PAL_VIDEO_FEATURE64_WINDOW_GET_VISIBILITY; + + features64 |= PAL_VIDEO_FEATURE64_CLIP_CURSOR; + features64 |= PAL_VIDEO_FEATURE64_WINDOW_SET_INPUT_FOCUS; + features64 |= PAL_VIDEO_FEATURE64_WINDOW_GET_INPUT_FOCUS; + features64 |= PAL_VIDEO_FEATURE64_CURSOR_SET_POS; + features64 |= PAL_VIDEO_FEATURE64_CURSOR_GET_POS; + features64 |= PAL_VIDEO_FEATURE64_WINDOW_SET_TITLE; + features64 |= PAL_VIDEO_FEATURE64_WINDOW_GET_TITLE; + features64 |= PAL_VIDEO_FEATURE64_WINDOW_FLASH_TRAY; + + features64 |= PAL_VIDEO_FEATURE64_WINDOW_SET_ICON; + features64 |= PAL_VIDEO_FEATURE64_TOPMOST_WINDOW; + features64 |= PAL_VIDEO_FEATURE64_DECORATED_WINDOW; + features64 |= PAL_VIDEO_FEATURE64_MONITOR_GET_PRIMARY; + features64 |= PAL_VIDEO_FEATURE64_FOREIGN_WINDOWS; + features64 |= PAL_VIDEO_FEATURE64_WINDOW_SET_CURSOR; + + s_Video.features = features; + s_Video.features64 = features64; + 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() +{ + resetMonitorData(); + + 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; + data = getFreeMonitorData(); + if (!data) { + return; + } + + data->monitor = monitor; + + // clang-format off + XRRCrtcInfo* crtc = s_X11.getCrtcInfo(s_X11.display, resources, info->crtc); + // clang-format on + + // get DPI + float raw = crtc->width / 1920.0f; + float steps[] = {1.0f, 1.2f, 1.5f, 1.75, 2.0f}; + float closest = steps[0]; + float minDiff = fabsf(raw - steps[0]); + + for (int i = 1; i < sizeof(steps) / sizeof(steps[0]); i++) { + float diff = fabsf(raw - steps[i]); + if (diff < minDiff) { + minDiff = diff; + closest = steps[i]; + } + } + + data->dpi = (Uint32)(closest * 96.0f); + data->w = crtc->width; + data->h = crtc->height; + data->x = crtc->x; + data->y = crtc->y; + + s_X11.freeCrtcInfo(crtc); + s_X11.monitorCount++; + } + + s_X11.freeOutputInfo(info); + } + + s_X11.freeScreenResources(resources); +} + +static int xGetWindowMonitorDPI(WindowData* data) +{ + 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 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; + + // 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) { + palSetLastPlatformError(errno); + return PAL_RESULT_PLATFORM_FAILURE; + } + + // libXCursor is needed + s_X11.libCursor = dlopen("libXcursor.so", RTLD_LAZY); + if (!s_X11.libCursor) { + palSetLastPlatformError(errno); + 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"); + + s_X11.selectInput = (XSelectInputFn)dlsym( + s_X11.handle, + "XSelectInput"); + + // 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"); + + s_X11.setLocaleModifiers = (XSetLocaleModifiersFn)dlsym( + s_X11.handle, + "XSetLocaleModifiers"); + + s_X11.openIM = (XOpenIMFn)dlsym( + s_X11.handle, + "XOpenIM"); + + s_X11.closeIM = (XCloseIMFn)dlsym( + s_X11.handle, + "XCloseIM"); + + s_X11.createIC = (XCreateICFn)dlsym( + s_X11.handle, + "XCreateIC"); + + s_X11.destroyIC = (XDestroyICFn)dlsym( + s_X11.handle, + "XDestroyIC"); + + s_X11.utf8LookupString = (Xutf8LookupStringFn)dlsym( + s_X11.handle, + "Xutf8LookupString"); + + // X11 server + if (s_Video.platformInstance) { + s_X11.display = (Display*)s_Video.platformInstance; + + } else { + s_X11.display = s_X11.openDisplay(nullptr); + s_Video.platformInstance = nullptr; + } + + if (!s_X11.display) { + palSetLastPlatformError(errno); + 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.dataID = (XContext)s_X11.uniqueContext(); + resetMonitorData(); + + s_X11.monitorCount = 0; + xCacheMonitors(); + + // 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"); + } + + xCreateKeycodeTable(); + + // disable auto key repeats + int supported; + s_X11.setDetectableAutoRepeat(s_X11.display, True, &supported); + if (!supported) { + // FIXME: fallback to manual key repeat detection + } + + // create an input method + s_X11.setLocaleModifiers(""); + s_X11.im = s_X11.openIM(s_X11.display, nullptr, nullptr, nullptr); + if (s_X11.im == None) { + palSetLastPlatformError(errno); + return PAL_RESULT_PLATFORM_FAILURE; + } + + s_Video.display = (void*)s_X11.display; + return PAL_RESULT_SUCCESS; +} + +static void xShutdownVideo() +{ + s_X11.closeIM(s_X11.im); + if (!s_Video.platformInstance) { + // opened by PAL + 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); + } + memset(&s_X11, 0, sizeof(X11)); + memset(&s_X11Atoms, 0, sizeof(X11Atoms)); +} + +PalResult xSetFBConfig( + const int index, + PalFBConfigBackend backend) +{ + if (backend == PAL_CONFIG_BACKEND_GLX) { + return glxBackend(index); + + } else if (backend == PAL_CONFIG_BACKEND_EGL || + backend == PAL_CONFIG_BACKEND_PAL_OPENGL) { + return eglXBackend(index); + + } else { + return PAL_RESULT_INVALID_FBCONFIG_BACKEND; + } +} + +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); - } + 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); + } + } + } + break; + } + + 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; + break; + } + + // 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); + } + } + + // attach windows sometimes bypass + // skipConfgure an still send an initial move event + if (data->isAttached) { + if (data->skipIfAttached) { + data->skipIfAttached = false; + break; + } + } + + // 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); + 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); + } + } + } + } + break; + } + + 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 + 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 = true; + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); + } + } + break; + } + + 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); + } + } + break; + } + + 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; + break; + } + + // 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); + } + } + } + break; + } + + case RANDR_SCREEN_CHANGE_EVENT: { + // skip the first event + if (s_X11.skipScreenEvent) { + s_X11.skipScreenEvent = false; + break; + } + + // store old monitor count + int oldCount = s_X11.monitorCount; + s_X11.monitorCount = 0; + xCacheMonitors(); + + if (oldCount != s_X11.monitorCount) { + // a monitor has been added or removed + 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); + } + } + } + break; + } + + 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; + break; + } + + 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 = palPackUint32(button, NULL_BUTTON_SERIAL); + 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); + } + } + break; + } + + 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 + // 32 (PAL_KEYCODE_SPACE) 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_space && 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); + } + + // check for char event if enabled + type = PAL_EVENT_KEYCHAR; + mode = palGetEventDispatchMode(driver, type); + if (mode == PAL_DISPATCH_NONE) { + break; + } + + int status; + char buffer[32]; + KeySym keySym; + int len = s_X11.utf8LookupString( + data->ic, + &event.xkey, + buffer, + sizeof(buffer), + &keySym, + &status); + + Uint32 codepoint = 0; + if (status == XLookupChars || status == XLookupBoth) { + // decode to Unicode codepoint + unsigned char ch = buffer[0]; + if (ch < 0x80) { + // 1 byte (A-Z) + codepoint = ch; + + } else if ((ch >> 5) == 0x6 && len >= 2) { + // 2 byte + codepoint = ((ch & 0x1F) << 6) | buffer[1] & 0x3F; + + } else if ((ch >> 4) == 0xE && len >= 3) { + // 3 byte + // clang-format off + codepoint = ((ch & 0x0F) << 12) | + ((buffer[1] & 0x3F) << 6) | + (buffer[2] & 0x3F); + // clang-format on + + } else if ((ch >> 3) == 0x1E && len >= 4) { + // 4 byte + // clang-format off + codepoint = ((ch & 0x07) << 18) | + ((buffer[1] & 0x3F) << 12) | + ((buffer[2] & 0x3F) << 6) | + (buffer[3] & 0x3F); + // clang-format on + } + + PalEvent event = {0}; + event.type = type; + event.data = codepoint; + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); + } + } + break; + } + } + } + + 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) { + *outMonitor = TO_PAL_HANDLE(PalMonitor, primary); + return PAL_RESULT_SUCCESS; + } + + palSetLastPlatformError(errno); + 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; + } else { + info->primary = false; + } + + // 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 + float raw = crtc->width / 1920.0f; + float steps[] = {1.0f, 1.2f, 1.5f, 1.75, 2.0f}; + float closest = steps[0]; + float minDiff = fabsf(raw - steps[0]); + + for (int i = 1; i < sizeof(steps) / sizeof(steps[0]); i++) { + float diff = fabsf(raw - steps[i]); + if (diff < minDiff) { + minDiff = diff; + closest = steps[i]; + } + } + + info->dpi = (Uint32)(closest * 96.0f); + + 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; } } - return; + 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) { + palSetLastPlatformError(errno); + return PAL_RESULT_PLATFORM_FAILURE; + } + + return PAL_RESULT_SUCCESS; +} + +static PalResult xValidateMonitorMode( + PalMonitor* monitor, + PalMonitorMode* mode) +{ + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; +} + +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 ConfigureNotify: { - // window resize or move + case PAL_ORIENTATION_PORTRAIT: { + rotation = RR_Rotate_90; + break; + } - // 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; - } + case PAL_ORIENTATION_LANDSCAPE_FLIPPED: { + rotation = RR_Rotate_180; + break; + } - // 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; + case PAL_ORIENTATION_PORTRAIT_FLIPPED: { + rotation = RR_Rotate_270; + break; + } - // push a resize event - PalEventDriver* driver = s_Video.eventDriver; - PalEventType type = PAL_EVENT_WINDOW_SIZE; - mode = palGetEventDispatchMode(driver, type); + default: { + return PAL_RESULT_INVALID_ORIENTATION; + } + } - 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); - } - } + if (!(crtc->rotations & rotation)) { + return PAL_RESULT_INVALID_ORIENTATION; + } - // attach windows sometimes bypass - // skipConfgure an still send an initial move event - if (data->isAttached) { - if (data->skipIfAttached) { - data->skipIfAttached = false; - return; - } - } + 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); - // 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; + s_X11.freeCrtcInfo(crtc); + s_X11.freeOutputInfo(outputInfo); + s_X11.freeScreenResources(resources); - // push a move event - PalEventDriver* driver = s_Video.eventDriver; - PalEventType type = PAL_EVENT_WINDOW_MOVE; - mode = palGetEventDispatchMode(driver, type); + if (ret != Success) { + palSetLastPlatformError(errno); + return PAL_RESULT_PLATFORM_FAILURE; + } - 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); - } + return PAL_RESULT_SUCCESS; +} - /** 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; +static PalResult xCreateWindow( + const PalWindowCreateInfo* info, + PalWindow** outWindow) +{ + Window window = None; + PalMonitor* monitor = nullptr; + PalMonitorInfo monitorInfo; - // push a DPI event - type = PAL_EVENT_MONITOR_DPI_CHANGED; - mode = palGetEventDispatchMode(driver, type); + WindowData* data = getFreeWindowData(); + if (!data) { + return PAL_RESULT_OUT_OF_MEMORY; + } - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = type; - event.data = monitorDPI; - event.data2 = palPackPointer(window); - palPushEvent(driver, &event); - } - } - } - } - return; - } + Visual* visual = nullptr; + int depth = 0; + Colormap colormap = None; + unsigned long bgPixel = 0; + unsigned long borderPixel = 0; - 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; - } + if (s_X11.visualInfo) { + visual = s_X11.visualInfo->visual; + depth = s_X11.visualInfo->depth; + bgPixel = 0; + borderPixel = 0; - 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; - } + // clang-format off + + colormap = s_X11.createColormap( + s_X11.display, + s_X11.root, + visual, + AllocNone); + // clang-format on - 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; - } + if (!colormap) { + palSetLastPlatformError(errno); + return PAL_RESULT_PLATFORM_FAILURE; + } - 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; - } + data->colormap = colormap; - 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; + } 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); + data->colormap = None; + } - // skip the first state event - if (data->skipState) { - data->skipState = false; - return; - } + // get monitor + int monitorX = 0; + int monitorY = 0; + Uint32 monitorW = 0; + Uint32 monitorH = 0; + int dpi = 0; + if (info->monitor) { + monitor = info->monitor; - // push event - PalEventDriver* driver = s_Video.eventDriver; - PalEventType type = PAL_EVENT_WINDOW_STATE; - mode = palGetEventDispatchMode(driver, type); + } else { + // get primary monitor + xGetPrimaryMonitor(&monitor); + } - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = type; - event.data = data->state; - event.data2 = palPackPointer(window); - palPushEvent(driver, &event); - } - } - } - return; - } + if (monitor) { + // get monitor info + PalResult result = palGetMonitorInfo(monitor, &monitorInfo); + if (result != PAL_RESULT_SUCCESS) { + return result; + } - case RANDR_SCREEN_CHANGE_EVENT: { - // skip the first event - if (s_X11.skipScreenEvent) { - s_X11.skipScreenEvent = false; - return; - } + monitorX = monitorInfo.x; + monitorY = monitorInfo.y; + monitorW = monitorInfo.width; + monitorH = monitorInfo.height; + dpi = monitorInfo.dpi; - // something change on pre existing monitor - // cache the information - xCacheMonitors(false); - return; + } else { + // primary monitor is not set + 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]; + XRROutputInfo* outputInfo = s_X11.getOutputInfo( + s_X11.display, + resources, + FROM_PAL_HANDLE(RROutput, monitor)); + + // check if its a monitor + if (outputInfo->connection != RR_Connected || + outputInfo->crtc == None) { + s_X11.freeOutputInfo(outputInfo); + continue; } - case RANDR_NOTIFY_EVENT: { - // skip the first event - if (s_X11.skipNotifyEvent) { - s_X11.skipNotifyEvent = false; - return; + // clang-format off + XRRCrtcInfo* crtc = s_X11.getCrtcInfo( + s_X11.display, + resources, + outputInfo->crtc); + // clang-format on + + monitorX = crtc->x; + monitorY = crtc->y; + monitorW = crtc->width; + monitorH = crtc->height; + + // get DPI + float raw = crtc->width / 1920.0f; + float steps[] = {1.0f, 1.2f, 1.5f, 1.75, 2.0f}; + float closest = steps[0]; + float minDiff = fabsf(raw - steps[0]); + + for (int i = 1; i < sizeof(steps) / sizeof(steps[0]); i++) { + float diff = fabsf(raw - steps[i]); + if (diff < minDiff) { + minDiff = diff; + closest = steps[i]; } + } - 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); - } - } + dpi = (Uint32)(closest * 96.0f); + + s_X11.freeCrtcInfo(crtc); + s_X11.freeOutputInfo(outputInfo); + break; + } + s_X11.freeScreenResources(resources); + } + + Int32 x, y = 0; + // the position and size must be scaled with the dpi before this call + if (info->center) { + x = monitorX + (monitorW - info->width) / 2; + y = monitorY + (monitorH - info->height) / 2; - /** 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; - } - } - } + } else { + // we set 100 for each axix + x = monitorX + 100; + y = monitorY + 100; + } - 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; + // 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; + } - 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); - } + // we dont need to set any flag + } - // 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); - } - } + long mask = StructureNotifyMask | KeyPressMask; + mask |= KeyReleaseMask; + mask |= ButtonPressMask; + mask |= ButtonReleaseMask; + mask |= PointerMotionMask; + mask |= FocusChangeMask; + mask |= PropertyChangeMask; - s_Mouse.lastX = x; - s_Mouse.lastY = y; - s_Mouse.dx = dx; - s_Mouse.dy = dy; - return; - } + XSetWindowAttributes attrs = {0}; + attrs.colormap = colormap; + attrs.event_mask = mask; + attrs.background_pixel = bgPixel; + attrs.border_pixel = borderPixel; + attrs.override_redirect = False; - case ButtonPress: - case ButtonRelease: { - int xButton = event.xbutton.button; - bool pressed = (event.xbutton.type == ButtonPress); - PalMouseButton button = 0; - PalEventType type; + // 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 (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; - } + if (window == None) { + palSetLastPlatformError(errno); + return PAL_RESULT_PLATFORM_FAILURE; + } - 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; - } + // 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); - 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); - } - } + // set class property + XClassHint* hints = s_X11.allocClassHint(); + if (hints) { + const char* resName = getenv("RESOURCE_NAME"); + const char* resClass = getenv("RESOURCE_CLASS"); - 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; - } + if (!resName || strlen(resName) == 0) { + resName = info->title; + } - 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 (!resClass || strlen(resClass) == 0) { + resClass = s_Video.className; + } - 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; - } + hints->res_name = (char*)resName; + hints->res_class = (char*)resClass; + s_X11.setClassHint(s_X11.display, window, hints); + s_X11.free(hints); + } - 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); + if (s_X11Atoms.unicodeTitle && info->title) { + 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)); - // special handling for Pause/break with Home - if (xScancode == 110) { - if (keySym == XK_Pause) { - scancode = PAL_SCANCODE_PAUSE; - } else { - scancode = PAL_SCANCODE_HOME; - } + } else { + if (info->title) { + s_X11.storeName(s_X11.display, window, info->title); + } + } - } else { - int index = xScancode - 8; - scancode = s_Keyboard.scancodes[index]; - } + // 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; + } - // 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]; + 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); + } - } 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; - } + // 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; + } - // 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; - } + 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); + } - // 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; + // 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); + } + } - if (pressed) { - if (repeat) { - type = PAL_EVENT_KEYREPEAT; - } else { - type = PAL_EVENT_KEYDOWN; - } - } else { - type = PAL_EVENT_KEYUP; - } + // set size hints + XSizeHints wmHints = {0}; + wmHints.flags = PPosition | PSize; + wmHints.x = x; + wmHints.y = y; + wmHints.width = info->width; + wmHints.height = info->height; - 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); - } + // 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; + } - // check for char event if enabled - type = PAL_EVENT_KEYCHAR; - mode = palGetEventDispatchMode(driver, type); - if (mode == PAL_DISPATCH_NONE) { - return; - } + // 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); + } - int status; - char buffer[32]; - KeySym keySym; - int len = s_X11.utf8LookupString( - data->ic, - &event.xkey, - buffer, - sizeof(buffer), - &keySym, - &status); + // 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; - Uint32 codepoint = 0; - if (status == XLookupChars || status == XLookupBoth) { - // decode to Unicode codepoint - unsigned char ch = buffer[0]; - if (ch < 0x80) { - // 1 byte (A-Z) - codepoint = ch; + // 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; + } - } else if ((ch >> 5) == 0x6 && len >= 2) { - // 2 byte - codepoint = ((ch & 0x1F) << 6) | buffer[1] & 0x3F; + // 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; + } + } + } - } else if ((ch >> 4) == 0xE && len >= 3) { - // 3 byte - // clang-format off - codepoint = ((ch & 0x0F) << 12) | - ((buffer[1] & 0x3F) << 6) | - (buffer[2] & 0x3F); - // clang-format on + 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 + } - } else if ((ch >> 3) == 0x1E && len >= 4) { - // 4 byte - // clang-format off - codepoint = ((ch & 0x07) << 18) | - ((buffer[1] & 0x3F) << 12) | - ((buffer[2] & 0x3F) << 6) | - (buffer[3] & 0x3F); - // clang-format on - } + // 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; + } - PalEvent event = {0}; - event.type = type; - event.data = codepoint; - event.data2 = palPackPointer(window); - palPushEvent(driver, &event); - } + // 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; } - return; } } + s_X11.iconifyWindow(s_X11.display, window, s_X11.screen); } + s_X11 + .setWMProtocols(s_X11.display, window, &s_X11Atoms.WM_DELETE_WINDOW, 1); + 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 + // attach the window data to the window + data->skipConfigure = true; + data->skipState = true; + data->isAttached = false; // true for attached windows + data->dpi = dpi; // the current window monitor + data->window = TO_PAL_HANDLE(PalWindow, window); + s_X11.saveContext(s_X11.display, window, s_X11.dataID, (XPointer)data); - 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 + // create an input context + data->ic = s_X11.createIC( + s_X11.im, + XNInputStyle, + XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, + window, + XNFocusWindow, + window, + nullptr); - if (outputInfo->connection == RR_Connected && - outputInfo->crtc != None) { - // a monitor - if (outMonitors) { - if (_count < maxCount) { - outMonitors[_count] = TO_PAL_HANDLE(PalMonitor, output); - } - } - _count++; - } + if (!data->ic) { + palSetLastPlatformError(errno); + return PAL_RESULT_PLATFORM_FAILURE; } - if (!outMonitors) { - *count = _count; - } + *outWindow = TO_PAL_HANDLE(PalWindow, window); return PAL_RESULT_SUCCESS; } -static PalResult xGetPrimaryMonitor(PalMonitor** outMonitor) +static void xDestroyWindow(PalWindow* window) { - 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 + Window xWin = FROM_PAL_HANDLE(Window, window); + WindowData* data = nullptr; + s_X11.findContext(s_X11.display, xWin, s_X11.dataID, (XPointer*)&data); - if (outputInfo->connection == RR_Connected && - outputInfo->crtc != None) { - primary = resources->outputs[i]; - break; - } - } + // PAL does not destroy an attached window + if (data->isAttached) { + return; } - // if we still did not get one, we fail - if (primary) { - *outMonitor = TO_PAL_HANDLE(PalMonitor, primary); - return PAL_RESULT_SUCCESS; + s_X11.destroyIC(data->ic); + s_X11.destroyWindow(s_X11.display, xWin); - } else { - return PAL_RESULT_PLATFORM_FAILURE; + if (data->colormap != None) { + s_X11.freeColormap(s_X11.display, data->colormap); } + + data->used = false; } -static PalResult xGetMonitorInfo( - PalMonitor* monitor, - PalMonitorInfo* info) +PalResult xMinimizeWindow(PalWindow* window) { - // 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 (!(s_Video.features & PAL_VIDEO_FEATURE_WINDOW_SET_STATE)) { + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; + } - if (!outputInfo) { - // invalid monitor - s_X11.freeScreenResources(resources); - return PAL_RESULT_INVALID_MONITOR; + Window xWin = FROM_PAL_HANDLE(Window, window); + XWindowAttributes attr; + if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { + return PAL_RESULT_INVALID_WINDOW; } - if (outputInfo->connection != RR_Connected) { - // invalid monitor - s_X11.freeScreenResources(resources); - return PAL_RESULT_INVALID_MONITOR; + 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; } - 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; + Window xWin = FROM_PAL_HANDLE(Window, window); + XWindowAttributes attr; + if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { + return PAL_RESULT_INVALID_WINDOW; } - // get monitor pos and size - // clang-format off - XRRCrtcInfo* crtc = s_X11.getCrtcInfo(s_X11.display, resources, outputInfo->crtc); - // clang-format on + 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 - info->x = crtc->x; - info->y = crtc->y; - info->width = crtc->width; - info->height = crtc->height; + return PAL_RESULT_SUCCESS; +} - // 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; - } +PalResult xRestoreWindow(PalWindow* window) +{ + if (!(s_Video.features & PAL_VIDEO_FEATURE_WINDOW_SET_STATE)) { + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; } - // orientation - switch (crtc->rotation) { - case RR_Rotate_0: { - info->orientation = PAL_ORIENTATION_LANDSCAPE; - break; - } + Window xWin = FROM_PAL_HANDLE(Window, window); + XWindowAttributes attr; + if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { + return PAL_RESULT_INVALID_WINDOW; + } - case RR_Rotate_90: { - info->orientation = PAL_ORIENTATION_PORTRAIT; - break; - } + // 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 - case RR_Rotate_180: { - info->orientation = PAL_ORIENTATION_LANDSCAPE_FLIPPED; - break; - } + s_X11.mapRaised(s_X11.display, xWin); - case RR_Rotate_270: { - info->orientation = PAL_ORIENTATION_PORTRAIT_FLIPPED; - break; - } + return PAL_RESULT_SUCCESS; +} - default: { - info->orientation = PAL_ORIENTATION_LANDSCAPE; - } +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; } - // 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); - + s_X11.mapWindow(s_X11.display, xWin); return PAL_RESULT_SUCCESS; } -static PalResult xEnumerateMonitorModes( - PalMonitor* monitor, - Int32* count, - PalMonitorMode* modes) +PalResult xHideWindow(PalWindow* window) { - Int32 modeCount = 0; - int maxModeCount = modes ? *count : 0; + Window xWin = FROM_PAL_HANDLE(Window, window); + XWindowAttributes attr; + if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { + return PAL_RESULT_INVALID_WINDOW; + } - // clang-format off - XRRScreenResources* resources = s_X11.getScreenResources(s_X11.display, s_X11.root); - // clang-format on + s_X11.unmapWindow(s_X11.display, xWin); + return PAL_RESULT_SUCCESS; +} - // get the monitor info - XRROutputInfo* outputInfo = s_X11.getOutputInfo( - s_X11.display, - resources, - FROM_PAL_HANDLE(RROutput, monitor)); +PalResult xFlashWindow( + PalWindow* window, + const PalFlashInfo* info) +{ + if (info->flags & PAL_FLASH_CAPTION) { + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; + } - if (!outputInfo) { - // invalid monitor - s_X11.freeScreenResources(resources); - return PAL_RESULT_INVALID_MONITOR; + Window xWin = FROM_PAL_HANDLE(Window, window); + XWindowAttributes attr; + if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { + return PAL_RESULT_INVALID_WINDOW; } - if (outputInfo->connection != RR_Connected) { - // invalid monitor - s_X11.freeScreenResources(resources); - return PAL_RESULT_INVALID_MONITOR; + bool add = false; + if (info->flags & PAL_FLASH_TRAY) { + add = true; } - // 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; + // 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 - // clang-format off - double tmp = (double)info->hTotal * (double)info->vTotal; - // clang-format on + } 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; + } - double rate = (double)info->dotClock / tmp; - mode->refreshRate = rate + 0.5; - } - } - modeCount++; + if (add) { + hints->flags |= XUrgencyHint; + } else { + hints->flags &= ~XUrgencyHint; } + s_X11.setWMHints(s_X11.display, xWin, hints); + s_X11.free(hints); } } - if (!modes) { - *count = modeCount; - } - - s_X11.freeOutputInfo(outputInfo); - s_X11.freeScreenResources(resources); - return PAL_RESULT_SUCCESS; } -static PalResult xGetCurrentMonitorMode( - PalMonitor* monitor, - PalMonitorMode* mode) +PalResult xGetWindowStyle( + PalWindow* window, + PalWindowStyle* outStyle) { - // clang-format off - XRRScreenResources* resources = s_X11.getScreenResources(s_X11.display, s_X11.root); - // clang-format on + // Window Manager quirks + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; +} - // get the monitor info - XRROutputInfo* outputInfo = s_X11.getOutputInfo( - s_X11.display, - resources, - FROM_PAL_HANDLE(RROutput, monitor)); +PalResult xGetWindowMonitor( + PalWindow* window, + PalMonitor** outMonitor) +{ + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; +} - if (!outputInfo) { - // invalid monitor - s_X11.freeScreenResources(resources); - return PAL_RESULT_INVALID_MONITOR; +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 (outputInfo->connection != RR_Connected) { - // invalid monitor - s_X11.freeScreenResources(resources); - return PAL_RESULT_INVALID_MONITOR; + if (!outBuffer || bufferSize <= 0) { + return PAL_RESULT_INSUFFICIENT_BUFFER; } - // get the current display mode - // clang-format off - XRRCrtcInfo* crtc = s_X11.getCrtcInfo(s_X11.display, resources, outputInfo->crtc); - // clang-format on + 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); - // 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 (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); - if (mode) { - mode->width = info->width; - mode->height = info->height; - mode->bpp = s_X11.bpp; + } else { + XTextProperty text; + if (!s_X11.getWMName(s_X11.display, xWin, &text)) { + return PAL_RESULT_INVALID_WINDOW; + } - double tmp = (double)info->hTotal * (double)info->vTotal; - double rate = (double)info->dotClock / tmp; - mode->refreshRate = rate + 0.5; + 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); } - s_X11.freeCrtcInfo(crtc); - s_X11.freeOutputInfo(outputInfo); - s_X11.freeScreenResources(resources); - return PAL_RESULT_SUCCESS; } -static PalResult xSetMonitorMode( - PalMonitor* monitor, - PalMonitorMode* mode) +PalResult xGetWindowPos( + PalWindow* window, + Int32* x, + Int32* y) { - // 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; + Window xWin = FROM_PAL_HANDLE(Window, window); + XWindowAttributes attr; + if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { + return PAL_RESULT_INVALID_WINDOW; } - if (outputInfo->connection != RR_Connected) { - // invalid monitor - s_X11.freeScreenResources(resources); - return PAL_RESULT_INVALID_MONITOR; + if (x) { + *x = attr.x; } - // 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; + if (y) { + *y = attr.y; } - // apply the display mode - // clang-format off - XRRCrtcInfo* crtc = s_X11.getCrtcInfo(s_X11.display, resources, outputInfo->crtc); - // clang-format on + return PAL_RESULT_SUCCESS; +} - 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); +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; + } - s_X11.freeCrtcInfo(crtc); - s_X11.freeOutputInfo(outputInfo); - s_X11.freeScreenResources(resources); + if (width) { + *width = attr.width; + } - if (ret != Success) { - return PAL_RESULT_PLATFORM_FAILURE; + if (height) { + *height = attr.height; } return PAL_RESULT_SUCCESS; } -static PalResult xValidateMonitorMode( - PalMonitor* monitor, - PalMonitorMode* mode) +PalResult xGetWindowState( + PalWindow* window, + PalWindowState* outState) { - // clang-format off - XRRScreenResources* resources = s_X11.getScreenResources(s_X11.display, s_X11.root); - // clang-format on + Window xWin = FROM_PAL_HANDLE(Window, window); + XWindowAttributes attr; + if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { + return PAL_RESULT_INVALID_WINDOW; + } - // get the monitor info - XRROutputInfo* outputInfo = s_X11.getOutputInfo( + Atom type; + int format; + unsigned long count, bytesAfter; + unsigned char* props = nullptr; + s_X11.getWindowProperty( s_X11.display, - resources, - FROM_PAL_HANDLE(RROutput, monitor)); + xWin, + s_X11Atoms._NET_WM_STATE, + 0, + (~0L), + False, + XA_ATOM, + &type, + &format, + &count, + &bytesAfter, + &props); - if (!outputInfo) { - // invalid monitor - s_X11.freeScreenResources(resources); - return PAL_RESULT_INVALID_MONITOR; - } + 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 (outputInfo->connection != RR_Connected) { - // invalid monitor - s_X11.freeScreenResources(resources); - return PAL_RESULT_INVALID_MONITOR; - } + if (props[i] == s_X11Atoms._NET_WM_STATE_MAXIMIZED_VERT) { + state = PAL_WINDOW_STATE_MAXIMIZED; + } - // 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; + if (props[i] == s_X11Atoms._NET_WM_STATE_HIDDEN) { + state = PAL_WINDOW_STATE_MINIMIZED; + } } - s_X11.freeOutputInfo(outputInfo); - s_X11.freeScreenResources(resources); - + s_X11.free(props); + *outState = state; return PAL_RESULT_SUCCESS; } -static PalResult xSetMonitorOrientation( - PalMonitor* monitor, - PalOrientation orientation) +bool xIsWindowVisible(PalWindow* window) { - // clang-format off - XRRScreenResources* resources = s_X11.getScreenResources(s_X11.display, s_X11.root); - // clang-format on + Window xWin = FROM_PAL_HANDLE(Window, window); + XWindowAttributes attr; + if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { + return false; + } - // get the monitor info - XRROutputInfo* outputInfo = s_X11.getOutputInfo( - s_X11.display, - resources, - FROM_PAL_HANDLE(RROutput, monitor)); + return attr.map_state == IsViewable; +} - if (!outputInfo) { - // invalid monitor - s_X11.freeScreenResources(resources); - return PAL_RESULT_INVALID_MONITOR; - } +PalWindow* xGetFocusWindow() +{ + Window window; + int tmp; + s_X11.getInputFocus(s_X11.display, &window, &tmp); + Window xWin = FROM_PAL_HANDLE(Window, window); - if (outputInfo->connection != RR_Connected) { - // invalid monitor - s_X11.freeScreenResources(resources); - return PAL_RESULT_INVALID_MONITOR; + if (xWin == s_X11.root) { + return nullptr; } + return TO_PAL_HANDLE(PalWindow, window); +} - // get the current display mode - // clang-format off - XRRCrtcInfo* crtc = s_X11.getCrtcInfo(s_X11.display, resources, outputInfo->crtc); - // clang-format on +PalWindowHandleInfo xGetWindowHandleInfo(PalWindow* window) +{ + PalWindowHandleInfo info; + info.nativeDisplay = (void*)s_X11.display; + info.nativeWindow = (void*)window; + return info; +} - // check if the new orientation is supported - Rotation rotation = 0; - switch (orientation) { - case PAL_ORIENTATION_LANDSCAPE: { - rotation = RR_Rotate_0; - break; - } +PalWindowHandleInfoEx xGetWindowHandleInfoEx(PalWindow* w) +{ + PalWindowHandleInfoEx info = {0}; + info.nativeDisplay = (void*)s_X11.display; + info.nativeWindow = (void*)w; + 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); - case PAL_ORIENTATION_PORTRAIT: { - rotation = RR_Rotate_90; - break; - } + 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; + } - case PAL_ORIENTATION_LANDSCAPE_FLIPPED: { - rotation = RR_Rotate_180; - break; - } + return PAL_RESULT_SUCCESS; +} - case PAL_ORIENTATION_PORTRAIT_FLIPPED: { - rotation = RR_Rotate_270; - break; - } +PalResult xSetWindowStyle( + PalWindow* window, + PalWindowStyle style) +{ + // Window Manager quirks + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; +} - default: { - return PAL_RESULT_INVALID_ORIENTATION; - } +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 (!(crtc->rotations & rotation)) { - return PAL_RESULT_INVALID_ORIENTATION; - } + 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)); - 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); + } else { + s_X11.storeName(s_X11.display, xWin, title); + } - s_X11.freeCrtcInfo(crtc); - s_X11.freeOutputInfo(outputInfo); - s_X11.freeScreenResources(resources); + s_X11.flush(s_X11.display); + return PAL_RESULT_SUCCESS; +} - if (ret != Success) { - return PAL_RESULT_PLATFORM_FAILURE; +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; } -static PalResult xCreateWindow( - const PalWindowCreateInfo* info, - PalWindow** outWindow) +PalResult xSetWindowSize( + PalWindow* window, + Uint32 width, + Uint32 height) { - Window window = None; - PalMonitor* monitor = nullptr; - PalMonitorInfo monitorInfo; - - WindowData* data = getFreeWindowData(); - if (!data) { - return PAL_RESULT_OUT_OF_MEMORY; + Window xWin = FROM_PAL_HANDLE(Window, window); + XWindowAttributes attr; + if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { + return PAL_RESULT_INVALID_WINDOW; } - Visual* visual = nullptr; - int depth = 0; - Colormap colormap = None; - unsigned long bgPixel = 0; - unsigned long borderPixel = 0; + // 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); - if (s_X11.colormap) { - visual = s_X11.visual; - depth = s_X11.depth; - colormap = s_X11.colormap; - bgPixel = 0; - borderPixel = 0; + // revert + hints.flags |= PMinSize; + hints.flags |= PMaxSize; + hints.min_width = hints.max_width = width; + hints.min_height = hints.max_height = height; } 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); + // window is already resizable + s_X11.resizeWindow(s_X11.display, xWin, width, height); } - // get monitor - if (info->monitor) { - monitor = info->monitor; - - } else { - // get primary monitor - xGetPrimaryMonitor(&monitor); - if (!monitor) { - return PAL_RESULT_PLATFORM_FAILURE; - } - } + s_X11.flush(s_X11.display); + return PAL_RESULT_SUCCESS; +} - // get monitor info - PalResult result = palGetMonitorInfo(monitor, &monitorInfo); - if (result != PAL_RESULT_SUCCESS) { - return result; +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; } - 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; + if (s_X11Atoms._NET_ACTIVE_WINDOW) { + xSendWMEvent( + xWin, + s_X11Atoms._NET_ACTIVE_WINDOW, + CurrentTime, + 0, + 0, + 0, + true); // 1 } else { - // we set 100 for each axix - x = monitorInfo.x + 100; - y = monitorInfo.y + 100; + s_X11.setInputFocus(s_X11.display, xWin, RevertToParent, CurrentTime); } - // 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; - } + return PAL_RESULT_SUCCESS; +} - // we dont need to set any flag +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; } - long mask = StructureNotifyMask | KeyPressMask; - mask |= KeyReleaseMask; - mask |= ButtonPressMask; - mask |= ButtonReleaseMask; - mask |= PointerMotionMask; - mask |= FocusChangeMask; - mask |= PropertyChangeMask; + // 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; - XSetWindowAttributes attrs = {0}; - attrs.colormap = colormap; - attrs.event_mask = mask; - attrs.background_pixel = bgPixel; - attrs.border_pixel = borderPixel; - attrs.override_redirect = False; + // 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 - // 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); + // clang-format off + icon[2 + i] = ((unsigned long)a << 24) | + ((unsigned long)r << 16) | + ((unsigned long)g << 8) | + ((unsigned long)b); + // clang-format on + } - if (window == None) { - return PAL_RESULT_PLATFORM_FAILURE; + *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; } - // set pid property - pid_t pid = getpid(); + unsigned long* iconData = FROM_PAL_HANDLE(unsigned long*, icon); + Uint64 totalPixels = 2 + iconData[0] * iconData[1]; s_X11.changeProperty( s_X11.display, - window, - s_X11Atoms._NET_WM_PID, + xWin, + s_X11Atoms._NET_WM_ICON, XA_CARDINAL, 32, PropModeReplace, - (unsigned char*)&pid, - 1); + (unsigned char*)iconData, + (int)totalPixels); - // set class property - XClassHint* hints = s_X11.allocClassHint(); - if (hints) { - const char* resName = getenv("RESOURCE_NAME"); - const char* resClass = getenv("RESOURCE_CLASS"); + s_X11.flush(s_X11.display); + return PAL_RESULT_SUCCESS; +} - if (!resName || strlen(resName) == 0) { - resName = info->title; - } +PalResult xCreateCursor( + const PalCursorCreateInfo* info, + PalCursor** outCursor) +{ + XcursorImage* image = s_X11.cursorImageCreate(info->width, info->height); + image->xhot = info->xHotspot; + image->yhot = info->yHotspot; - if (!resClass || strlen(resClass) == 0) { - resClass = s_X11.className; - } + // 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 - hints->res_name = (char*)resName; - hints->res_class = (char*)resClass; - s_X11.setClassHint(s_X11.display, window, hints); - s_X11.free(hints); + // clang-format off + image->pixels[i] = ((unsigned long)a << 24) | + ((unsigned long)r << 16) | + ((unsigned long)g << 8) | + ((unsigned long)b); + // clang-format on } - if (s_X11Atoms.unicodeTitle && info->title) { - 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 { - if (info->title) { - s_X11.storeName(s_X11.display, window, info->title); - } + Cursor cursor = s_X11.cursorImageLoadCursor(s_X11.display, image); + if (!cursor) { + palSetLastPlatformError(errno); + return PAL_RESULT_PLATFORM_FAILURE; } - // 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); - } + s_X11.cursorImageDestroy(image); + *outCursor = TO_PAL_HANDLE(PalCursor, cursor); + return PAL_RESULT_SUCCESS; +} - // 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; +PalResult xCreateCursorFrom( + PalCursorType type, + PalCursor** outCursor) +{ + int shape; + Cursor cursor; + switch (type) { + case PAL_CURSOR_ARROW: { + shape = XC_left_ptr; + break; } - 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); + case PAL_CURSOR_HAND: { + shape = XC_hand2; + break; } - } - - // 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; + case PAL_CURSOR_CROSS: { + shape = XC_cross; + break; } - // 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; - } - } + case PAL_CURSOR_IBEAM: { + shape = XC_xterm; + 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; + case PAL_CURSOR_WAIT: { + shape = XC_watch; + break; } - // 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; - } - } + default: { + return PAL_RESULT_INVALID_ARGUMENT; } - s_X11.iconifyWindow(s_X11.display, window, s_X11.screen); } - s_X11 - .setWMProtocols(s_X11.display, window, &s_X11Atoms.WM_DELETE_WINDOW, 1); + cursor = s_X11.createFontCursor(s_X11.display, shape); + *outCursor = TO_PAL_HANDLE(PalCursor, cursor); + return PAL_RESULT_SUCCESS; +} - s_X11.flush(s_X11.display); +void xDestroyCursor(PalCursor* cursor) +{ + s_X11.freeCursor(s_X11.display, FROM_PAL_HANDLE(Cursor, cursor)); +} - // attach the window data to the window - data->skipConfigure = true; - data->skipState = true; - data->isAttached = false; // true for attached windows - 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); +void xShowCursor(bool show) +{ + // x11 does not support Hiding and showing cursor + return; +} - // create an input context - data->ic = s_X11.createIC( - s_X11.im, - XNInputStyle, - XIMPreeditNothing | XIMStatusNothing, - XNClientWindow, - window, - XNFocusWindow, - window, - nullptr); +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); - if (!data->ic) { - return PAL_RESULT_PLATFORM_FAILURE; + } else { + s_X11.ungrabPointer(s_X11.display, CurrentTime); } - *outWindow = TO_PAL_HANDLE(PalWindow, window); return PAL_RESULT_SUCCESS; } -static void xDestroyWindow(PalWindow* window) +PalResult xGetCursorPos( + PalWindow* window, + Int32* x, + Int32* y) { Window xWin = FROM_PAL_HANDLE(Window, window); - WindowData* data = nullptr; - s_X11.findContext(s_X11.display, xWin, s_X11.dataID, (XPointer*)&data); + XWindowAttributes attr; + if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { + return PAL_RESULT_INVALID_WINDOW; + } - // PAL does not destroy an attached window - if (data->isAttached) { - return; + 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; } - s_X11.destroyIC(data->ic); - s_X11.destroyWindow(s_X11.display, xWin); - data->used = false; + if (y) { + *y = winY; + } + + return PAL_RESULT_SUCCESS; } -PalResult xMinimizeWindow(PalWindow* window) +PalResult xSetCursorPos( + PalWindow* window, + Int32 x, + Int32 y) { - 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); + 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 xMaximizeWindow(PalWindow* window) +PalResult xSetWindowCursor( + PalWindow* window, + PalCursor* cursor) { - 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 + Window xCursor = FROM_PAL_HANDLE(Cursor, cursor); + if (xCursor) { + s_X11.defineCursor(s_X11.display, xWin, xCursor); + + } else { + s_X11.undefineCursor(s_X11.display, xWin); + } + s_X11.flush(s_X11.display); return PAL_RESULT_SUCCESS; } -PalResult xRestoreWindow(PalWindow* window) +PalResult xAttachWindow( + void* windowHandle, + PalWindow** outWindow) { - if (!(s_Video.features & PAL_VIDEO_FEATURE_WINDOW_SET_STATE)) { - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; - } - - Window xWin = FROM_PAL_HANDLE(Window, window); + Window xWin = FROM_PAL_HANDLE(Window, windowHandle); 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 + // get a free slot and set the window handle to it + // we also set a flag to make sure we know this is an attached window + WindowData* data = getFreeWindowData(); + if (!data) { + return PAL_RESULT_OUT_OF_MEMORY; + } - s_X11.mapRaised(s_X11.display, xWin); + PalWindow* window = TO_PAL_HANDLE(PalWindow, xWin); + // we assume the window was just created, since there is + // no official way to get the DPI + data->isAttached = true; + data->dpi = 96; // if this is not the DPI, a dpi event will be triggered + + // If the window manager has not mapped the window yet, + // we dont need the initial Size / Move events + data->skipConfigure = true; + data->skipState = true; + data->skipIfAttached = true; + data->window = window; + data->w = attr.width; + data->h = attr.height; + data->x = attr.x; + data->y = attr.y; + // get the current window state + // we dont check the return code because we know the window is valid + xGetWindowState(window, &data->state); + + // listen to the events we support + long mask = StructureNotifyMask | KeyPressMask; + mask |= KeyReleaseMask; + mask |= ButtonPressMask; + mask |= ButtonReleaseMask; + mask |= PointerMotionMask; + mask |= FocusChangeMask; + mask |= PropertyChangeMask; + s_X11.selectInput(s_X11.display, xWin, mask); + + // listen to window close event + s_X11.setWMProtocols(s_X11.display, xWin, &s_X11Atoms.WM_DELETE_WINDOW, 1); + + s_X11.saveContext(s_X11.display, xWin, s_X11.dataID, (XPointer)data); + s_X11.flush(s_X11.display); + + *outWindow = window; return PAL_RESULT_SUCCESS; } -PalResult xShowWindow(PalWindow* window) +PalResult xDetachWindow( + PalWindow* window, + void** outWindowHandle) { + // we check is the window is really detachable Window xWin = FROM_PAL_HANDLE(Window, window); - XWindowAttributes attr; - if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { + WindowData* data = nullptr; + s_X11.findContext(s_X11.display, xWin, s_X11.dataID, (XPointer*)&data); + if (!data) { return PAL_RESULT_INVALID_WINDOW; } - s_X11.mapWindow(s_X11.display, xWin); + if (data->isAttached == false) { + // window was created by PAL + return PAL_RESULT_INVALID_WINDOW; + } + + // detach the window + data->used = false; + long mask = 0; + s_X11.selectInput(s_X11.display, xWin, mask); + s_X11.setWMProtocols(s_X11.display, xWin, nullptr, 0); + + if (outWindowHandle) { + *outWindowHandle = (void*)window; + } + return PAL_RESULT_SUCCESS; } -PalResult xHideWindow(PalWindow* window) +static Backend s_XBackend = { + .shutdownVideo = xShutdownVideo, + .updateVideo = xUpdateVideo, + .setFBConfig = xSetFBConfig, + .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, + .flashWindow = xFlashWindow, + .getWindowStyle = xGetWindowStyle, + .getWindowMonitor = xGetWindowMonitor, + .getWindowTitle = xGetWindowTitle, + .getWindowPos = xGetWindowPos, + .getWindowSize = xGetWindowSize, + .getWindowState = xGetWindowState, + .isWindowVisible = xIsWindowVisible, + .getFocusWindow = xGetFocusWindow, + .getWindowHandleInfo = xGetWindowHandleInfo, + .getWindowHandleInfoEx = xGetWindowHandleInfoEx, + .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, + + .attachWindow = xAttachWindow, + .detachWindow = xDetachWindow}; + +#endif // PAL_HAS_X11 +#pragma endregion + +// ================================================== +// Wayland API +// ================================================== + +#pragma region Wayland API +#if PAL_HAS_WAYLAND + +PalResult eglWlBackend(const int index) { - Window xWin = FROM_PAL_HANDLE(Window, window); - XWindowAttributes attr; - if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { - return PAL_RESULT_INVALID_WINDOW; + // user choose EGL FBConfig backend + if (!s_Egl.handle) { + palSetLastPlatformError(errno); + return PAL_RESULT_PLATFORM_FAILURE; + } + + EGLDisplay display = EGL_NO_DISPLAY; + display = s_Egl.eglGetDisplay((EGLNativeDisplayType)s_Wl.display); + + if (display == EGL_NO_DISPLAY) { + palSetLastPlatformError(errno); + return PAL_RESULT_PLATFORM_FAILURE; + } + + EGLint numConfigs = 0; + if (!s_Egl.eglGetConfigs(display, nullptr, 0, &numConfigs)) { + palSetLastPlatformError(errno); + 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_X11.unmapWindow(s_X11.display, xWin); + s_Egl.eglGetConfigs(display, eglConfigs, numConfigs, &numConfigs); + s_Wl.eglFBConfig = eglConfigs[index]; + return PAL_RESULT_SUCCESS; } -PalResult xFlashWindow( - PalWindow* window, - const PalFlashInfo* info) +static void wlCreateKeycodeTable() { - if (info->flags & PAL_FLASH_CAPTION) { - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; - } + // Tis is for only printable and text input keys - Window xWin = FROM_PAL_HANDLE(Window, window); - XWindowAttributes attr; - if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { - return PAL_RESULT_INVALID_WINDOW; - } + // Letters + s_Keyboard.keycodes[XKB_KEY_a] = PAL_KEYCODE_A; + s_Keyboard.keycodes[XKB_KEY_b] = PAL_KEYCODE_B; + s_Keyboard.keycodes[XKB_KEY_c] = PAL_KEYCODE_C; + s_Keyboard.keycodes[XKB_KEY_d] = PAL_KEYCODE_D; + s_Keyboard.keycodes[XKB_KEY_e] = PAL_KEYCODE_E; + s_Keyboard.keycodes[XKB_KEY_f] = PAL_KEYCODE_F; + s_Keyboard.keycodes[XKB_KEY_g] = PAL_KEYCODE_G; + s_Keyboard.keycodes[XKB_KEY_h] = PAL_KEYCODE_H; + s_Keyboard.keycodes[XKB_KEY_i] = PAL_KEYCODE_I; + s_Keyboard.keycodes[XKB_KEY_j] = PAL_KEYCODE_J; + s_Keyboard.keycodes[XKB_KEY_k] = PAL_KEYCODE_K; + s_Keyboard.keycodes[XKB_KEY_l] = PAL_KEYCODE_L; + s_Keyboard.keycodes[XKB_KEY_m] = PAL_KEYCODE_M; + s_Keyboard.keycodes[XKB_KEY_n] = PAL_KEYCODE_N; + s_Keyboard.keycodes[XKB_KEY_o] = PAL_KEYCODE_O; + s_Keyboard.keycodes[XKB_KEY_p] = PAL_KEYCODE_P; + s_Keyboard.keycodes[XKB_KEY_q] = PAL_KEYCODE_Q; + s_Keyboard.keycodes[XKB_KEY_r] = PAL_KEYCODE_R; + s_Keyboard.keycodes[XKB_KEY_s] = PAL_KEYCODE_S; + s_Keyboard.keycodes[XKB_KEY_t] = PAL_KEYCODE_T; + s_Keyboard.keycodes[XKB_KEY_u] = PAL_KEYCODE_U; + s_Keyboard.keycodes[XKB_KEY_v] = PAL_KEYCODE_V; + s_Keyboard.keycodes[XKB_KEY_w] = PAL_KEYCODE_W; + s_Keyboard.keycodes[XKB_KEY_x] = PAL_KEYCODE_X; + s_Keyboard.keycodes[XKB_KEY_y] = PAL_KEYCODE_Y; + s_Keyboard.keycodes[XKB_KEY_z] = PAL_KEYCODE_Z; - bool add = false; - if (info->flags & PAL_FLASH_TRAY) { - add = true; - } + // Control + s_Keyboard.keycodes[XKB_KEY_space] = PAL_KEYCODE_SPACE; - // 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 + // Misc + s_Keyboard.keycodes[XKB_KEY_apostrophe] = PAL_KEYCODE_APOSTROPHE; + s_Keyboard.keycodes[XKB_KEY_backslash] = PAL_KEYCODE_BACKSLASH; + s_Keyboard.keycodes[XKB_KEY_comma] = PAL_KEYCODE_COMMA; + s_Keyboard.keycodes[XKB_KEY_equal] = PAL_KEYCODE_EQUAL; + s_Keyboard.keycodes[XKB_KEY_grave] = PAL_KEYCODE_GRAVEACCENT; + s_Keyboard.keycodes[XKB_KEY_minus] = PAL_KEYCODE_SUBTRACT; + s_Keyboard.keycodes[XKB_KEY_period] = PAL_KEYCODE_PERIOD; + s_Keyboard.keycodes[XKB_KEY_semicolon] = PAL_KEYCODE_SEMICOLON; + s_Keyboard.keycodes[XKB_KEY_slash] = PAL_KEYCODE_SLASH; + s_Keyboard.keycodes[XKB_KEY_bracketleft] = PAL_KEYCODE_LBRACKET; + s_Keyboard.keycodes[XKB_KEY_bracketright] = PAL_KEYCODE_RBRACKET; +} - } 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; - } +static int createShmFile(Uint64 size) +{ + char template[] = "/tmp/pal-shm-XXXXXX"; + int fd = mkstemp(template); + if (fd < 0) { + return -1; + } - if (add) { - hints->flags |= XUrgencyHint; - } else { - hints->flags &= ~XUrgencyHint; - } - s_X11.setWMHints(s_X11.display, xWin, hints); - s_X11.free(hints); - } + unlink(template); + if (ftruncate(fd, size) < 0) { + return -1; } - return PAL_RESULT_SUCCESS; + return fd; } -PalResult xGetWindowStyle( - PalWindow* window, - PalWindowStyle* outStyle) +static struct wl_buffer* createShmBuffer( + int width, + int height, + const Uint8* pixels, + bool cursor) { - // Window Manager quirks - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; -} + int stride = width * 4; + Uint64 size = stride * height; + struct wl_buffer* buffer = nullptr; + struct wl_shm_pool* pool = nullptr; + void* data = nullptr; + + int fd = createShmFile(size); + if (fd == -1) { + return nullptr; + } -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; + data = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) { + return nullptr; } - XRRScreenResources* resources = nullptr; - resources = s_X11.getScreenResources(s_X11.display, s_X11.root); + enum wl_shm_format format = WL_SHM_FORMAT_XRGB8888; + if (cursor) { + format = WL_SHM_FORMAT_ARGB8888; + Uint32* dataPixels = (Uint32*)data; - 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 + // convert from RGBA8 to ARGB32 + for (int i = 0; i < width * height; i++) { + Uint8 r = pixels[i * 4 + 0]; // Red + Uint8 g = pixels[i * 4 + 1]; // Green + Uint8 b = pixels[i * 4 + 2]; // Blue + Uint8 a = pixels[i * 4 + 3]; // Alpha - if (info->connection == RR_Connected && info->crtc != None) { // clang-format off - XRRCrtcInfo* crtc = s_X11.getCrtcInfo(s_X11.display, resources, info->crtc); + dataPixels[i] = ((unsigned long)a << 24) | + ((unsigned long)r << 16) | + ((unsigned long)g << 8) | + ((unsigned long)b); // 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); + + } else { + memset(data, 0xFF, size); // white } - 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; + pool = wlShmCreatePool(s_Wl.shm, fd, size); + if (!pool) { + return nullptr; } - if (!outBuffer || bufferSize <= 0) { - return PAL_RESULT_INSUFFICIENT_BUFFER; + buffer = wlShmPoolCreateBuffer(pool, 0, width, height, stride, format); + + if (!buffer) { + return nullptr; } - 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); + wlShmPoolDestroy(pool); + munmap(data, size); + close(fd); + return buffer; +} - if (bufferSize >= count) { - strcpy(outBuffer, (const char*)prop); - } else { - // copy up to the limiit of the supplied buffer - strncpy(outBuffer, (const char*)prop, bufferSize); +static void wlOutputGeometry( + void* data, + struct wl_output* output, + int32_t x, + int32_t y, + int32_t, // we dont need physical size + int32_t, // we dont need physical size + int32_t, // we dont need subpixel + const char* make, + const char* model, + int32_t transform) +{ + MonitorData* monitorData = data; + monitorData->x = x; + monitorData->y = y; + + switch (transform) { + case WL_OUTPUT_TRANSFORM_NORMAL: + case WL_OUTPUT_TRANSFORM_180: { + monitorData->orientation = PAL_ORIENTATION_LANDSCAPE; + break; } - s_X11.free(prop); - } else { - XTextProperty text; - if (!s_X11.getWMName(s_X11.display, xWin, &text)) { - return PAL_RESULT_INVALID_WINDOW; + case WL_OUTPUT_TRANSFORM_90: + case WL_OUTPUT_TRANSFORM_270: { + monitorData->orientation = PAL_ORIENTATION_PORTRAIT; + break; } - 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); + case WL_OUTPUT_TRANSFORM_FLIPPED: + case WL_OUTPUT_TRANSFORM_FLIPPED_180: { + monitorData->orientation = PAL_ORIENTATION_LANDSCAPE_FLIPPED; + break; + } + + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + case WL_OUTPUT_TRANSFORM_FLIPPED_270: { + monitorData->orientation = PAL_ORIENTATION_PORTRAIT_FLIPPED; + break; } - s_X11.free(text.value); } - return PAL_RESULT_SUCCESS; + snprintf(monitorData->name, 32, "%s %s", make, model); } -PalResult xGetWindowPos( - PalWindow* window, - Int32* x, - Int32* y) +static void wlOutputMode( + void* data, + struct wl_output* output, + uint32_t flags, + int32_t width, + int32_t height, + int32_t refresh) { - 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; + MonitorData* monitorData = data; + if (flags & WL_OUTPUT_MODE_CURRENT) { + monitorData->w = width; + monitorData->h = height; + monitorData->refreshRate = (refresh + 500) / 1000; + + // wayland only sends the current mode + monitorData->mode.bpp = 0; + monitorData->mode.width = width; + monitorData->mode.height = height; + monitorData->mode.refreshRate = (refresh + 500) / 1000; } +} - if (y) { - *y = attr.y; - } +static void wlOutputScale( + void* data, + struct wl_output* output, + int32_t scale) +{ + MonitorData* monitorData = data; + float dpi = (float)scale * 96.0f; + monitorData->dpi = (Uint32)dpi; +} - return PAL_RESULT_SUCCESS; +static void wlOutputDone( + void* data, + struct wl_output* output) +{ } -PalResult xGetWindowSize( - PalWindow* window, - Uint32* width, - Uint32* height) +static const struct wl_output_listener s_OutputListener = { + .geometry = wlOutputGeometry, + .mode = wlOutputMode, + .done = wlOutputDone, + .scale = wlOutputScale}; + +static const struct wl_output_listener s_DefaultModeListener = { + .geometry = wlOutputGeometry, + .mode = wlOutputMode, + .done = wlOutputDone, + .scale = wlOutputScale}; + +static void globalHandle( + void* data, + struct wl_registry* registry, + uint32_t name, + const char* interface, + uint32_t version) { - 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_Wl.checkFeatures) { + PalVideoFeatures features = 0; + PalVideoFeatures64 features64 = 0; + features |= PAL_VIDEO_FEATURE_HIGH_DPI; + features |= PAL_VIDEO_FEATURE_MONITOR_GET_ORIENTATION; + features |= PAL_VIDEO_FEATURE_MULTI_MONITORS; + features |= PAL_VIDEO_FEATURE_MONITOR_GET_MODE; + features |= PAL_VIDEO_FEATURE_WINDOW_SET_TITLE; + features |= PAL_VIDEO_FEATURE_WINDOW_SET_SIZE; + features |= PAL_VIDEO_FEATURE_WINDOW_SET_STATE; + features |= PAL_VIDEO_FEATURE_BORDERLESS_WINDOW; + + features64 |= PAL_VIDEO_FEATURE64_HIGH_DPI; + features64 |= PAL_VIDEO_FEATURE64_MONITOR_GET_ORIENTATION; + features64 |= PAL_VIDEO_FEATURE64_MULTI_MONITORS; + features64 |= PAL_VIDEO_FEATURE64_MONITOR_GET_MODE; + features64 |= PAL_VIDEO_FEATURE64_WINDOW_SET_TITLE; + features64 |= PAL_VIDEO_FEATURE64_WINDOW_SET_SIZE; + features64 |= PAL_VIDEO_FEATURE64_WINDOW_SET_STATE; + features64 |= PAL_VIDEO_FEATURE64_BORDERLESS_WINDOW; + + features64 |= PAL_VIDEO_FEATURE64_WINDOW_SET_CURSOR; + + s_Video.features = features; + s_Video.features64 = features64; + s_Wl.checkFeatures = false; + } + + if (strcmp(interface, "wl_compositor") == 0) { + s_Wl.compositor = + wlRegistryBind(registry, name, s_Wl.compositorInterface, 4); + + } else if (strcmp(interface, "xdg_wm_base") == 0) { + s_Wl.xdgBase = + wlRegistryBind(registry, name, &xdg_wm_base_interface, 1); + + xdgWmBaseAddListener(s_Wl.xdgBase, &wmBaseListener, nullptr); + + } else if (strcmp(interface, "wl_shm") == 0) { + s_Wl.shm = wlRegistryBind(registry, name, s_Wl.shmInterface, 1); + + } else if (strcmp(interface, "wl_seat") == 0) { + s_Wl.seat = wlRegistryBind(registry, name, s_Wl.seatInterface, 5); + + wlSeatAddListener(s_Wl.seat, &seatListener, nullptr); + + } else if (strcmp(interface, "zxdg_decoration_manager_v1") == 0) { + s_Wl.decorationManager = wlRegistryBind( + registry, + name, + &zxdg_decoration_manager_v1_interface, + 1); - if (width) { - *width = attr.width; - } + s_Video.features64 |= PAL_VIDEO_FEATURE64_DECORATED_WINDOW; - if (height) { - *height = attr.height; + } else if (strcmp(interface, "wl_output") == 0) { + // wayland does not let use query monitors directly + // so we enumerate and store at init and update the + // cache when a monitor is added or removed + MonitorData* monitorData = getFreeMonitorData(); + if (!monitorData) { + return; + } + + struct wl_output* output = nullptr; + output = wlRegistryBind(s_Wl.registry, name, s_Wl.outputInterface, 3); + wlOutputAddListener(output, &s_OutputListener, monitorData); + + monitorData->wlName = name; + monitorData->monitor = (PalMonitor*)output; + s_Wl.monitorCount++; } +} - return PAL_RESULT_SUCCESS; +static void globalRemove( + void* data, + struct wl_registry* registry, + uint32_t name) +{ + for (int i = 0; i < s_Video.maxMonitorData; ++i) { + if (s_Video.monitorData[i].used && + s_Video.monitorData[i].wlName == name) { + MonitorData* data = &s_Video.monitorData[i]; + data->used = false; + s_Wl.proxyDestroy((struct wl_proxy*)data->monitor); + s_Wl.monitorCount--; + } + } } -PalResult xGetWindowState( - PalWindow* window, - PalWindowState* outState) +static const struct wl_registry_listener s_RegistryListener = { + .global = globalHandle, + .global_remove = globalRemove}; + +PalResult wlInitVideo() { - Window xWin = FROM_PAL_HANDLE(Window, window); - XWindowAttributes attr; - if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { - return PAL_RESULT_INVALID_WINDOW; + // load wayland libray + s_Wl.handle = dlopen("libwayland-client.so.0", RTLD_LAZY); + s_Wl.xkbCommon = dlopen("libxkbcommon.so", RTLD_LAZY); + s_Wl.libCursor = dlopen("libwayland-cursor.so", RTLD_LAZY); + s_Wl.libWaylandEgl = dlopen("libwayland-egl.so", RTLD_LAZY); + + // clang-format off + if (!s_Wl.handle || + !s_Wl.xkbCommon || + !s_Wl.libCursor || + !s_Wl.libWaylandEgl) { + palSetLastPlatformError(errno); + return PAL_RESULT_PLATFORM_FAILURE; } - 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); + // get the exported global variables + s_Wl.outputInterface = dlsym(s_Wl.handle, "wl_output_interface"); + s_Wl.seatInterface = dlsym(s_Wl.handle, "wl_seat_interface"); + s_Wl.compositorInterface = dlsym(s_Wl.handle, "wl_compositor_interface"); + s_Wl.surfaceInterface = dlsym(s_Wl.handle, "wl_surface_interface"); + s_Wl.registryInterface = dlsym(s_Wl.handle, "wl_registry_interface"); + s_Wl.shmInterface = dlsym(s_Wl.handle, "wl_shm_interface"); + s_Wl.bufferInterface = dlsym(s_Wl.handle, "wl_buffer_interface"); + s_Wl.shmPoolInterface = dlsym(s_Wl.handle, "wl_shm_pool_interface"); + s_Wl.regionInterface = dlsym(s_Wl.handle, "wl_region_interface"); + s_Wl.pointerInterface = dlsym(s_Wl.handle, "wl_pointer_interface"); + s_Wl.keyboardInterface = dlsym(s_Wl.handle, "wl_keyboard_interface"); + + // load function procs + s_Wl.displayConnect = (wl_display_connect_fn)dlsym( + s_Wl.handle, + "wl_display_connect"); + + s_Wl.displayDisconnect = (wl_display_disconnect_fn)dlsym( + s_Wl.handle, + "wl_display_disconnect"); + + s_Wl.displayRoundtrip = (wl_display_roundtrip_fn)dlsym( + s_Wl.handle, + "wl_display_roundtrip"); + + s_Wl.displayDispatch = (wl_display_dispatch_fn)dlsym( + s_Wl.handle, + "wl_display_dispatch"); + + s_Wl.proxyAddListener = (wl_proxy_add_listener_fn)dlsym( + s_Wl.handle, + "wl_proxy_add_listener"); + + s_Wl.proxyMarshalCnstructor = (wl_proxy_marshal_constructor_v_fn)dlsym( + s_Wl.handle, + "wl_proxy_marshal_constructor_versioned"); + + s_Wl.proxyDestroy = (wl_proxy_destroy_fn)dlsym( + s_Wl.handle, + "wl_proxy_destroy"); + + s_Wl.proxyMarshalFlags = (wl_proxy_marshal_flags_fn)dlsym( + s_Wl.handle, + "wl_proxy_marshal_flags"); + + s_Wl.proxyGetVersion = (wl_proxy_get_version_fn)dlsym( + s_Wl.handle, + "wl_proxy_get_version"); + + s_Wl.getError = (wl_display_get_error_fn)dlsym( + s_Wl.handle, + "wl_display_get_error"); + + s_Wl.dispatchPending = (wl_display_dispatch_pending_fn)dlsym( + s_Wl.handle, + "wl_display_dispatch_pending"); + + s_Wl.displayFlush = (wl_display_flush_fn)dlsym( + s_Wl.handle, + "wl_display_flush"); + + s_Wl.prepareRead = (wl_display_prepare_read_fn)dlsym( + s_Wl.handle, + "wl_display_prepare_read"); + + s_Wl.readEvents = (wl_display_read_events_fn)dlsym( + s_Wl.handle, + "wl_display_read_events"); + + s_Wl.displayGetFd = (wl_display_get_fd_fn)dlsym( + s_Wl.handle, + "wl_display_get_fd"); + + s_Wl.cancelRead = (wl_display_cancel_read_fn)dlsym( + s_Wl.handle, + "wl_display_cancel_read"); + + // load xkbcommon procs + s_Wl.xkbKeymapUnref = (xkb_keymap_unref_fn)dlsym( + s_Wl.xkbCommon, + "xkb_keymap_unref"); + + s_Wl.xkbStateKeyGetOneSym = (xkb_state_key_get_one_sym_fn)dlsym( + s_Wl.xkbCommon, + "xkb_state_key_get_one_sym"); + + s_Wl.xkbStateNew = (xkb_state_new_fn)dlsym( + s_Wl.xkbCommon, + "xkb_state_new"); + + s_Wl.xkbStateUnref = (xkb_state_unref_fn)dlsym( + s_Wl.xkbCommon, + "xkb_state_unref"); + + s_Wl.xkbContextUnref = (xkb_context_unref_fn)dlsym( + s_Wl.xkbCommon, + "xkb_context_unref"); + + s_Wl.xkbContextNew = (xkb_context_new_fn)dlsym( + s_Wl.xkbCommon, + "xkb_context_new"); + + s_Wl.xkbKeymapNewFromString = (xkb_keymap_new_from_string_fn)dlsym( + s_Wl.xkbCommon, + "xkb_keymap_new_from_string"); + + s_Wl.xkbKeysymToUtf32 = (xkb_keysym_to_utf32_fn)dlsym( + s_Wl.xkbCommon, + "xkb_keysym_to_utf32"); - 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; - } + s_Wl.xkbStateUpdateMask = (xkb_state_update_mask_fn)dlsym( + s_Wl.xkbCommon, + "xkb_state_update_mask"); - if (props[i] == s_X11Atoms._NET_WM_STATE_MAXIMIZED_VERT) { - state = PAL_WINDOW_STATE_MAXIMIZED; - } + s_Wl.xkbKeymapKeyRepeats = (xkb_keymap_key_repeats_fn)dlsym( + s_Wl.xkbCommon, + "xkb_keymap_key_repeats"); - if (props[i] == s_X11Atoms._NET_WM_STATE_HIDDEN) { - state = PAL_WINDOW_STATE_MINIMIZED; - } + // load wayland cursor procs + s_Wl.cursorImageGetBuffer = (wl_cursor_image_get_buffer_fn)dlsym( + s_Wl.libCursor, + "wl_cursor_image_get_buffer"); + + s_Wl.cursorThemeLoad = (wl_cursor_theme_load_fn)dlsym( + s_Wl.libCursor, + "wl_cursor_theme_load"); + + s_Wl.cursorThemeGetCursor = (wl_cursor_theme_get_cursor_fn)dlsym( + s_Wl.libCursor, + "wl_cursor_theme_get_cursor"); + + // wl_egl procs + s_Wl.eglWindowCreate = (wl_egl_window_create_fn)dlsym( + s_Wl.libWaylandEgl, + "wl_egl_window_create"); + + s_Wl.eglWindowDestroy = (wl_egl_window_destroy_fn)dlsym( + s_Wl.libWaylandEgl, + "wl_egl_window_destroy"); + + s_Wl.eglWindowResize = (wl_egl_window_resize_fn)dlsym( + s_Wl.libWaylandEgl, + "wl_egl_window_resize"); + + // clang-format on + + // initialize wayland + s_Wl.checkFeatures = true; + s_Wl.monitorCount = 0; + setupXdgShellProtocol(); + + // check if user supplied their own display + if (s_Video.platformInstance) { + s_Wl.display = (struct wl_display*)s_Video.platformInstance; + + } else { + s_Wl.display = s_Wl.displayConnect(nullptr); + s_Video.platformInstance = nullptr; + } + + if (!s_Wl.display) { + palSetLastPlatformError(errno); + return PAL_RESULT_PLATFORM_FAILURE; + } + + s_Video.display = (void*)s_Wl.display; + s_Wl.registry = wlDisplayGetRegistry(s_Wl.display); + wlRegistryAddListener(s_Wl.registry, &s_RegistryListener, nullptr); + s_Wl.displayRoundtrip(s_Wl.display); + + // do a roundtrip again to get remaining handles + s_Wl.displayRoundtrip(s_Wl.display); + + if (!s_Wl.compositor || !s_Wl.xdgBase || !s_Wl.shm) { + palSetLastPlatformError(errno); + return PAL_RESULT_PLATFORM_FAILURE; + } + + // create an input context + s_Wl.inputContext = s_Wl.xkbContextNew(XKB_CONTEXT_NO_FLAGS); + if (!s_Wl.inputContext) { + palSetLastPlatformError(errno); + return PAL_RESULT_PLATFORM_FAILURE; + } + + // get the current theme + s_Wl.cursorTheme = s_Wl.cursorThemeLoad(nullptr, 32, s_Wl.shm); + if (!s_Wl.cursorTheme) { + palSetLastPlatformError(errno); + return PAL_RESULT_PLATFORM_FAILURE; } - s_X11.free(props); - *outState = state; + wlCreateKeycodeTable(); + + s_Video.display = (void*)s_Wl.display; return PAL_RESULT_SUCCESS; } -bool xIsWindowVisible(PalWindow* window) +void wlShutdownVideo() { - Window xWin = FROM_PAL_HANDLE(Window, window); - XWindowAttributes attr; - if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { - return false; + if (s_Wl.state) { + s_Wl.xkbStateUnref(s_Wl.state); + s_Wl.xkbKeymapUnref(s_Wl.keymap); } - return attr.map_state == IsViewable; + s_Wl.xkbContextUnref(s_Wl.inputContext); + if (s_Wl.compositor) { + // if compositor was found, all this will be as well + // since we check all at init + s_Wl.proxyDestroy((struct wl_proxy*)s_Wl.compositor); + s_Wl.proxyDestroy((struct wl_proxy*)s_Wl.xdgBase); + s_Wl.proxyDestroy((struct wl_proxy*)s_Wl.shm); + s_Wl.proxyDestroy((struct wl_proxy*)s_Wl.seat); + } + + if (!s_Video.platformInstance) { + // opened by PAL + s_Wl.displayDisconnect(s_Wl.display); + } + + dlclose(s_Wl.libCursor); + dlclose(s_Wl.xkbCommon); + dlclose(s_Wl.libWaylandEgl); + dlclose(s_Wl.handle); + + memset(&s_Wl, 0, sizeof(Wayland)); } -PalWindow* xGetFocusWindow() +PalResult wlSetFBConfig( + const int index, + PalFBConfigBackend backend) { - Window window; - int tmp; - s_X11.getInputFocus(s_X11.display, &window, &tmp); - Window xWin = FROM_PAL_HANDLE(Window, window); + if (backend == PAL_CONFIG_BACKEND_GLES || + backend == PAL_CONFIG_BACKEND_PAL_OPENGL) { + return eglWlBackend(index); - if (xWin == s_X11.root) { - return nullptr; + } else { + return PAL_RESULT_INVALID_FBCONFIG_BACKEND; } - return TO_PAL_HANDLE(PalWindow, window); } -PalWindowHandleInfo xGetWindowHandleInfo(PalWindow* window) +void wlUpdateVideo() { - PalWindowHandleInfo info; - info.nativeDisplay = (void*)s_X11.display; - info.nativeWindow = (void*)window; - return info; + // flush pending requests + s_Mouse.tmpScrollX = 0; + s_Mouse.tmpScrollY = 0; + + // push key repeats + // we only do this if the user wants key repeat events + if (s_Keyboard.repeatKey != 0 && s_Video.eventDriver) { + PalEventDriver* driver = s_Video.eventDriver; + PalDispatchMode mode = PAL_DISPATCH_NONE; + mode = palGetEventDispatchMode(driver, PAL_EVENT_KEYREPEAT); + if (mode != PAL_DISPATCH_NONE) { + // get now time and check with the key repeat time + Uint64 now = getTime(); + if (now >= s_Keyboard.timer) { + PalWindow* window = (PalWindow*)s_Wl.keyboardSurface; + PalKeycode key = s_Keyboard.repeatKey; + PalScancode scancode = s_Keyboard.repeatScancode; + + PalEvent event = {0}; + event.type = PAL_EVENT_KEYREPEAT; + event.data = palPackUint32(key, scancode); + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); + s_Keyboard.timer += s_Keyboard.repeatRate; + } + } + } + + while (s_Wl.prepareRead(s_Wl.display) != 0) { + s_Wl.dispatchPending(s_Wl.display); + } + + s_Wl.displayFlush(s_Wl.display); + int fd = s_Wl.displayGetFd(s_Wl.display); + struct pollfd pfd = {fd, POLLIN, 0}; + if (poll(&pfd, 1, 0) > 0) { + // there are events ready to be read + s_Wl.readEvents(s_Wl.display); + + } else { + s_Wl.cancelRead(s_Wl.display); + } + + // dispatch events that were read + s_Wl.dispatchPending(s_Wl.display); } -static PalResult xSetWindowOpacity( - PalWindow* window, - float opacity) +PalResult wlEnumerateMonitors( + Int32* count, + PalMonitor** outMonitors) { - 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); + if (outMonitors) { + int index = 0; + int maxCount = s_Video.maxMonitorData; + for (int i = 0; i < maxCount && index < *count; i++) { + if (s_Video.monitorData[i].used) { + // found a monitor + PalMonitor* monitor = s_Video.monitorData[index].monitor; + outMonitors[index++] = monitor; + } + } + } - 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; + if (!outMonitors) { + *count = s_Wl.monitorCount; } return PAL_RESULT_SUCCESS; } -PalResult xSetWindowStyle( - PalWindow* window, - PalWindowStyle style) +PalResult wlGetPrimaryMonitor(PalMonitor** outMonitor) { - // Window Manager quirks return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; } -PalResult xSetWindowTitle( - PalWindow* window, - const char* title) +PalResult wlGetMonitorInfo( + PalMonitor* monitor, + PalMonitorInfo* info) { - Window xWin = FROM_PAL_HANDLE(Window, window); - XWindowAttributes attr; - if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { - return PAL_RESULT_INVALID_WINDOW; + MonitorData* monitorData = findMonitorData(monitor); + if (!monitorData) { + return PAL_RESULT_INVALID_MONITOR; } - 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)); + info->dpi = monitorData->dpi; + info->x = monitorData->x; + info->y = monitorData->y; + info->width = monitorData->w; + info->height = monitorData->h; + info->refreshRate = monitorData->refreshRate; + info->orientation = monitorData->orientation; - } else { - s_X11.storeName(s_X11.display, xWin, title); - } + info->primary = false; // no way to query + strcpy(info->name, monitorData->name); - s_X11.flush(s_X11.display); return PAL_RESULT_SUCCESS; } -PalResult xSetWindowPos( - PalWindow* window, - Int32 x, - Int32 y) +PalResult wlEnumerateMonitorModes( + PalMonitor* monitor, + Int32* count, + PalMonitorMode* modes) { - Window xWin = FROM_PAL_HANDLE(Window, window); - XWindowAttributes attr; - if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { - return PAL_RESULT_INVALID_WINDOW; + MonitorData* monitorData = findMonitorData(monitor); + if (!monitorData) { + return PAL_RESULT_INVALID_MONITOR; + } + + if (modes && *count > 0) { + PalMonitorMode* mode = &modes[0]; + mode->bpp = monitorData->mode.bpp; + mode->width = monitorData->mode.width; + mode->height = monitorData->mode.height; + mode->refreshRate = monitorData->mode.refreshRate; + } + + if (!modes) { + *count = 1; // wayland only gives the active mode } - 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) +PalResult wlGetCurrentMonitorMode( + PalMonitor* monitor, + PalMonitorMode* mode) { - Window xWin = FROM_PAL_HANDLE(Window, window); - XWindowAttributes attr; - if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { - return PAL_RESULT_INVALID_WINDOW; + MonitorData* monitorData = findMonitorData(monitor); + if (!monitorData) { + return PAL_RESULT_INVALID_MONITOR; } - // 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); + // this is the same as the current mode + mode->bpp = monitorData->mode.bpp; + mode->width = monitorData->mode.width; + mode->height = monitorData->mode.height; + mode->refreshRate = monitorData->mode.refreshRate; - // revert - hints.flags |= PMinSize; - hints.flags |= PMaxSize; - hints.min_width = hints.max_width = width; - hints.min_height = hints.max_height = height; + return PAL_RESULT_SUCCESS; +} - } else { - // window is already resizable - s_X11.resizeWindow(s_X11.display, xWin, width, height); - } +PalResult wlSetMonitorMode( + PalMonitor* monitor, + PalMonitorMode* mode) +{ + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; +} - s_X11.flush(s_X11.display); - return PAL_RESULT_SUCCESS; +PalResult wlValidateMonitorMode( + PalMonitor* monitor, + PalMonitorMode* mode) +{ + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; } -PalResult xSetFocusWindow(PalWindow* window) +PalResult wlSetMonitorOrientation( + PalMonitor* monitor, + PalOrientation orientation) { - Window xWin = FROM_PAL_HANDLE(Window, window); - XWindowAttributes attr; - if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { - return PAL_RESULT_INVALID_WINDOW; - } + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; +} - if (s_X11Atoms._NET_ACTIVE_WINDOW) { - xSendWMEvent( - xWin, - s_X11Atoms._NET_ACTIVE_WINDOW, - CurrentTime, - 0, - 0, - 0, - true); // 1 +PalResult wlCreateWindow( + const PalWindowCreateInfo* info, + PalWindow** outWindow) +{ + struct wl_surface* surface = nullptr; + struct xdg_surface* xdgSurface = nullptr; + struct xdg_toplevel* xdgToplevel = nullptr; - } else { - s_X11.setInputFocus(s_X11.display, xWin, RevertToParent, CurrentTime); + if (info->style & PAL_WINDOW_STYLE_TOPMOST) { + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; } - return PAL_RESULT_SUCCESS; -} + if (info->style & PAL_WINDOW_STYLE_TRANSPARENT) { + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; + } -PalResult xCreateIcon( - const PalIconCreateInfo* info, - PalIcon** outIcon) -{ - Uint64 totalPixels = 2 + (Uint64)(info->width * info->height); - Uint64 totalBytes = sizeof(unsigned long) * totalPixels; + if (info->style & PAL_WINDOW_STYLE_TOOL) { + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; + } - unsigned long* icon = palAllocate(s_Video.allocator, totalBytes, 0); - if (!icon) { + if (!(info->style & PAL_WINDOW_STYLE_BORDERLESS)) { + if (!s_Wl.decorationManager) { + // user wants decorated window but its not supported + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; + } + } + + WindowData* data = getFreeWindowData(); + if (!data) { 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; + memset(data, 0, sizeof(WindowData)); + data->used = true; + data->focused = false; - // 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 + // create surface + surface = wlCompositorCreateSurface(s_Wl.compositor); + if (!surface) { + palSetLastPlatformError(errno); + return PAL_RESULT_PLATFORM_FAILURE; + } + + wlSurfaceAddListener(surface, &surfaceListener, data); + xdgSurface = xdgWmBaseGetXdgSurface(s_Wl.xdgBase, surface); + if (!xdgSurface) { + palSetLastPlatformError(errno); + return PAL_RESULT_PLATFORM_FAILURE; + } + + xdgToplevel = xdgSurfaceGetToplevel(xdgSurface); + if (!xdgSurface) { + palSetLastPlatformError(errno); + return PAL_RESULT_PLATFORM_FAILURE; + } + + // set APP id + const char* appID = getenv("RESOURCE_CLASS"); + if (!appID || strlen(appID) == 0) { + appID = s_Video.className; + } + + const char* title = info->title; + if (!title) { + title = ""; + } + + xdgToplevelSetTitle(xdgToplevel, title); + xdgToplevelSetAppId(xdgToplevel, appID); + + xdgToplevelAddListener(xdgToplevel, &xdgToplevelListener, data); + xdgSurfaceAddListener(xdgSurface, &xdgSurfaceListener, data); + + // decorated window + if (!(info->style & PAL_WINDOW_STYLE_BORDERLESS)) { + struct zxdg_toplevel_decoration_v1* decoration = nullptr; + decoration = + zxdgGetToplevelDecoration(s_Wl.decorationManager, xdgToplevel); + + zxdgToplevelDecorationV1AddListener( + decoration, + &decorationListener, + surface); + + zxdgToplevelDecorationV1SetMode(decoration, 2); + data->decoration = decoration; + } + + data->skipState = true; + data->skipConfigure = true; + wlSurfaceCommit(surface); + s_Wl.displayRoundtrip(s_Wl.display); + + if (info->maximized && info->show) { + // we need the maximized size the compositor will use + // and use that to create the buffer + xdgToplevelSetMaximized(xdgToplevel); + wlSurfaceCommit(surface); + s_Wl.displayRoundtrip(s_Wl.display); + data->state = PAL_WINDOW_STATE_MAXIMIZED; + + } else { + data->w = info->width; + data->h = info->height; + } + + data->xdgSurface = xdgSurface; + data->xdgToplevel = xdgToplevel; + data->window = (PalWindow*)surface; + data->buffer = nullptr; + data->isAttached = false; + data->state = PAL_WINDOW_STATE_RESTORED; + + // minimize + // This is just a requeest, the compositor might ignore it + if (info->minimized) { + xdgToplevelSetMinimized(xdgToplevel); + wlSurfaceCommit(surface); + data->state = PAL_WINDOW_STATE_MINIMIZED; + } + + if (s_Wl.eglFBConfig) { + data->eglWindow = s_Wl.eglWindowCreate(surface, data->w, data->h); + if (!data->eglWindow) { + palSetLastPlatformError(errno); + return PAL_RESULT_PLATFORM_FAILURE; + } + + } else { + // create a white buffer for the surface + struct wl_buffer* buffer = nullptr; + buffer = createShmBuffer(data->w, data->h, nullptr, false); + if (!buffer) { + palSetLastPlatformError(errno); + return PAL_RESULT_PLATFORM_FAILURE; + } - // clang-format off - icon[2 + i] = ((unsigned long)a << 24) | - ((unsigned long)r << 16) | - ((unsigned long)g << 8) | - ((unsigned long)b); - // clang-format on + wlSurfaceAttach(surface, buffer, 0, 0); + wlSurfaceDamageBuffer(surface, 0, 0, data->w, data->h); + wlSurfaceCommit(surface); + data->buffer = buffer; + } + + struct wl_region* region = wlCompositorCreateRegion(s_Wl.compositor); + if (region) { + wlRegionAdd(region, 0, 0, data->w, data->h); + wlSurfaceSetOpaqueRegion(surface, region); + wlRegionDestroy(region); + wlSurfaceCommit(surface); + } + + s_Wl.displayRoundtrip(s_Wl.display); + if (!s_Wl.decorationManager && s_Video.eventDriver) { + PalEventDriver* driver = s_Video.eventDriver; + PalDispatchMode mode = PAL_DISPATCH_NONE; + PalEventType type = PAL_EVENT_WINDOW_DECORATION_MODE; + mode = palGetEventDispatchMode(driver, type); + + if (mode != PAL_DISPATCH_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = PAL_DECORATION_MODE_CLIENT_SIDE; + event.data2 = palPackPointer(data->window); + palPushEvent(driver, &event); + } } - *outIcon = TO_PAL_HANDLE(PalIcon, icon); + // Since wayland does not have a way to set unique data + // to a surface without taking control from users + // we might implement a simple hash map to do that + // but at the moment a linear search is fine + // FIXME: Implement a window hash map + + data->skipState = false; + data->skipConfigure = false; + *outWindow = data->window; return PAL_RESULT_SUCCESS; } -void xDestroyIcon(PalIcon* icon) +void wlDestroyWindow(PalWindow* window) { - if (icon) { - palFree(s_Video.allocator, icon); + WindowData* data = findWindowData(window); + if (!data || (data && data->isAttached)) { + return; + } + + if (data->decoration) { + zxdgToplevelDecorationV1Destroy(data->decoration); } + + if (data->eglWindow) { + s_Wl.eglWindowDestroy(data->eglWindow); + } else { + wlBufferDestroy(data->buffer); + } + + xdgToplevelDestroy(data->xdgToplevel); + xdgSurfaceDestroy(data->xdgSurface); + wlSurfaceDestroy((struct wl_surface*)window); + data->used = false; } -PalResult xSetWindowIcon( - PalWindow* window, - PalIcon* icon) +PalResult wlMinimizeWindow(PalWindow* window) { - WindowData* winData = nullptr; - Window xWin = FROM_PAL_HANDLE(Window, window); - s_X11.findContext(s_X11.display, xWin, s_X11.dataID, (XPointer*)&winData); - if (!winData) { + WindowData* data = findWindowData(window); + if (!data) { 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); + xdgToplevelSetMinimized(data->xdgToplevel); return PAL_RESULT_SUCCESS; } -PalResult xCreateCursor( - const PalCursorCreateInfo* info, - PalCursor** outCursor) +PalResult wlMaximizeWindow(PalWindow* window) { - 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 + WindowData* data = findWindowData(window); + if (!data) { + return PAL_RESULT_INVALID_WINDOW; } - Cursor cursor = s_X11.cursorImageLoadCursor(s_X11.display, image); - if (!cursor) { - return PAL_RESULT_PLATFORM_FAILURE; + xdgToplevelSetMaximized(data->xdgToplevel); + return PAL_RESULT_SUCCESS; +} + +PalResult wlRestoreWindow(PalWindow* window) +{ + WindowData* data = findWindowData(window); + if (!data) { + return PAL_RESULT_INVALID_WINDOW; } - s_X11.cursorImageDestroy(image); - *outCursor = TO_PAL_HANDLE(PalCursor, cursor); + // we can only restore from a maximized state + xdgToplevelUnsetMaximized(data->xdgToplevel); return PAL_RESULT_SUCCESS; } -PalResult xCreateCursorFrom( - PalCursorType type, - PalCursor** outCursor) +PalResult wlShowWindow(PalWindow* window) { - int shape; - Cursor cursor; - switch (type) { - case PAL_CURSOR_ARROW: { - shape = XC_left_ptr; - break; - } + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; +} - case PAL_CURSOR_HAND: { - shape = XC_hand2; - break; - } +PalResult wlHideWindow(PalWindow* window) +{ + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; +} - case PAL_CURSOR_CROSS: { - shape = XC_cross; - break; - } +PalResult wlFlashWindow( + PalWindow* window, + const PalFlashInfo* info) +{ + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; +} - case PAL_CURSOR_IBEAM: { - shape = XC_xterm; - break; - } +PalResult wlGetWindowStyle( + PalWindow* window, + PalWindowStyle* outStyle) +{ + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; +} - case PAL_CURSOR_WAIT: { - shape = XC_watch; - break; - } +PalResult wlGetWindowMonitor( + PalWindow* window, + PalMonitor** outMonitor) +{ + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; +} - default: { - return PAL_RESULT_INVALID_ARGUMENT; - } - } +PalResult wlGetWindowTitle( + PalWindow* window, + Uint64 bufferSize, + Uint64* outSize, + char* outBuffer) +{ + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; +} - cursor = s_X11.createFontCursor(s_X11.display, shape); - *outCursor = TO_PAL_HANDLE(PalCursor, cursor); - return PAL_RESULT_SUCCESS; +PalResult wlGetWindowPos( + PalWindow* window, + Int32* x, + Int32* y) +{ + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; } -void xDestroyCursor(PalCursor* cursor) +PalResult wlGetWindowSize( + PalWindow* window, + Uint32* width, + Uint32* height) { - s_X11.freeCursor(s_X11.display, FROM_PAL_HANDLE(Cursor, cursor)); + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; } -void xShowCursor(bool show) +PalResult wlGetWindowState( + PalWindow* window, + PalWindowState* outState) { - // 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); - } + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; +} - } else { - s_X11.defineCursor(s_X11.display, xWin, s_X11.hiddenCursor); - } - s_X11.flush(s_X11.display); - } +bool wlIsWindowVisible(PalWindow* window) +{ + return false; } -PalResult xClipCursor( - PalWindow* window, - bool clip) +PalWindow* wlGetFocusWindow() { - Window xWin = FROM_PAL_HANDLE(Window, window); - XWindowAttributes attr; - if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { - return PAL_RESULT_INVALID_WINDOW; - } + // Wayland does not let client query focused window + return nullptr; +} - if (clip) { - s_X11.grabPointer( - s_X11.display, - xWin, - True, - 0, - GrabModeAsync, - GrabModeAsync, - xWin, - None, - CurrentTime); +PalWindowHandleInfo wlGetWindowHandleInfo(PalWindow* window) +{ + PalWindowHandleInfo info; + info.nativeDisplay = (void*)s_Wl.display; + info.nativeWindow = (void*)window; + return info; +} - } else { - s_X11.ungrabPointer(s_X11.display, CurrentTime); +PalWindowHandleInfoEx wlGetWindowHandleInfoEx(PalWindow* window) +{ + PalWindowHandleInfoEx info = {0}; + WindowData* data = findWindowData(window); + if (data) { + info.nativeDisplay = (void*)s_Wl.display; + info.nativeWindow = (void*)window; + info.nativeHandle1 = data->xdgSurface; + info.nativeHandle2 = data->xdgToplevel; + info.nativeHandle3 = data->eglWindow; } - return PAL_RESULT_SUCCESS; + return info; } -PalResult xGetCursorPos( +PalResult wlSetWindowOpacity( PalWindow* window, - Int32* x, - Int32* y) + float opacity) { - 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); + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; +} - if (x) { - *x = winX; - } +PalResult wlSetWindowStyle( + PalWindow* window, + PalWindowStyle style) +{ + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; +} - if (y) { - *y = winY; +PalResult wlSetWindowTitle( + PalWindow* window, + const char* title) +{ + WindowData* data = findWindowData(window); + if (!data) { + return PAL_RESULT_INVALID_WINDOW; } + xdgToplevelSetTitle(data->xdgToplevel, title); + s_Wl.displayFlush(s_Wl.display); return PAL_RESULT_SUCCESS; } -PalResult xSetCursorPos( +PalResult wlSetWindowPos( 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_VIDEO_FEATURE_NOT_SUPPORTED; +} + +PalResult wlSetWindowSize( + PalWindow* window, + Uint32 width, + Uint32 height) +{ + WindowData* data = findWindowData(window); + if (!data) { 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); + xdgToplevelSetMinSize(data->xdgToplevel, width, height); + xdgToplevelSetMaxSize(data->xdgToplevel, width, height); + wlSurfaceCommit((struct wl_surface*)window); return PAL_RESULT_SUCCESS; } -PalResult xSetWindowCursor( +PalResult wlSetFocusWindow(PalWindow* window) +{ + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; +} + +PalResult wlCreateIcon( + const PalIconCreateInfo* info, + PalIcon** outIcon) +{ + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; +} + +void wlDestroyIcon(PalIcon* icon) +{ + return; +} + +PalResult wlSetWindowIcon( PalWindow* window, - PalCursor* cursor) + PalIcon* icon) { - Window xWin = FROM_PAL_HANDLE(Window, window); - XWindowAttributes attr; - if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { - return PAL_RESULT_INVALID_WINDOW; + return PAL_RESULT_THREAD_FEATURE_NOT_SUPPORTED; +} + +PalResult wlCreateCursor( + const PalCursorCreateInfo* info, + PalCursor** outCursor) +{ + WaylandCursor* cursor = nullptr; + cursor = palAllocate(s_Video.allocator, sizeof(WaylandCursor), 0); + if (!cursor) { + return PAL_RESULT_OUT_OF_MEMORY; } - 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; + cursor->surface = wlCompositorCreateSurface(s_Wl.compositor); + if (!cursor->surface) { + palSetLastPlatformError(errno); + return PAL_RESULT_PLATFORM_FAILURE; + } - } else { - s_X11.undefineCursor(s_X11.display, xWin); + cursor->buffer = + createShmBuffer(info->width, info->height, info->pixels, true); + + if (!cursor->buffer) { + palSetLastPlatformError(errno); + return PAL_RESULT_PLATFORM_FAILURE; } - s_X11.flush(s_X11.display); + wlSurfaceAttach(cursor->surface, cursor->buffer, 0, 0); + wlSurfaceCommit(cursor->surface); + cursor->hotspotX = info->xHotspot; + cursor->hotspotY = info->yHotspot; + + *outCursor = (PalCursor*)cursor; return PAL_RESULT_SUCCESS; } -PalResult xAttachWindow( - void* windowHandle, - PalWindow** outWindow) +PalResult wlCreateCursorFrom( + PalCursorType type, + PalCursor** outCursor) { - Window xWin = FROM_PAL_HANDLE(Window, windowHandle); - XWindowAttributes attr; - if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { - return PAL_RESULT_INVALID_WINDOW; + const char* cursorType = nullptr; + switch (type) { + case PAL_CURSOR_ARROW: { + cursorType = "left_ptr"; + break; + } + + case PAL_CURSOR_HAND: { + cursorType = "hand1"; + break; + } + + case PAL_CURSOR_CROSS: { + cursorType = "crosshair"; + break; + } + + case PAL_CURSOR_IBEAM: { + cursorType = "text"; + break; + } + + case PAL_CURSOR_WAIT: { + cursorType = "wait"; + break; + } } - // get a free slot and set the window handle to it - // we also set a flag to make sure we know this is an attached window - WindowData* data = getFreeWindowData(); - if (!data) { + struct wl_cursor* wlCursor = nullptr; + wlCursor = s_Wl.cursorThemeGetCursor(s_Wl.cursorTheme, cursorType); + if (!wlCursor) { + palSetLastPlatformError(errno); + return PAL_RESULT_PLATFORM_FAILURE; + } + + WaylandCursor* cursor = nullptr; + cursor = palAllocate(s_Video.allocator, sizeof(WaylandCursor), 0); + if (!cursor) { return PAL_RESULT_OUT_OF_MEMORY; } - PalWindow* window = TO_PAL_HANDLE(PalWindow, xWin); - // we assume the window was just created, since there is - // no official way to get the DPI - data->isAttached = true; - data->dpi = 100; // if this is not the DPI, a dpi event will be triggered + cursor->surface = wlCompositorCreateSurface(s_Wl.compositor); + if (!cursor->surface) { + palSetLastPlatformError(errno); + return PAL_RESULT_PLATFORM_FAILURE; + } - // If the window manager has not mapped the window yet, - // we dont need the initial Size / Move events - data->skipConfigure = true; - data->skipState = true; - data->skipIfAttached = true; - data->cursor = nullptr; - data->window = window; - data->w = attr.width; - data->h = attr.height; - data->x = attr.x; - data->y = attr.y; + cursor->buffer = s_Wl.cursorImageGetBuffer(wlCursor->images[0]); + wlSurfaceAttach(cursor->surface, cursor->buffer, 0, 0); + wlSurfaceCommit(cursor->surface); + cursor->hotspotX = wlCursor->images[0]->hotspot_x; + cursor->hotspotY = wlCursor->images[0]->hotspot_y; - // get the current window state - // we dont check the return code because we know the window is valid - xGetWindowState(window, &data->state); + *outCursor = (PalCursor*)cursor; + return PAL_RESULT_SUCCESS; +} - // listen to the events we support - long mask = StructureNotifyMask | KeyPressMask; - mask |= KeyReleaseMask; - mask |= ButtonPressMask; - mask |= ButtonReleaseMask; - mask |= PointerMotionMask; - mask |= FocusChangeMask; - mask |= PropertyChangeMask; - s_X11.selectInput(s_X11.display, xWin, mask); +void wlDestroyCursor(PalCursor* cursor) +{ + WaylandCursor* waylandCursor = (WaylandCursor*)cursor; + wlBufferDestroy(waylandCursor->buffer); + wlSurfaceDestroy(waylandCursor->surface); + palFree(s_Video.allocator, waylandCursor); +} - // listen to window close event - s_X11.setWMProtocols(s_X11.display, xWin, &s_X11Atoms.WM_DELETE_WINDOW, 1); +void wlShowCursor(bool show) +{ + // not supported + return; +} - s_X11.saveContext(s_X11.display, xWin, s_X11.dataID, (XPointer)data); - s_X11.flush(s_X11.display); +PalResult wlClipCursor( + PalWindow* window, + bool clip) +{ + if (!(s_Video.features & PAL_VIDEO_FEATURE_CLIP_CURSOR)) { + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; + } - *outWindow = window; return PAL_RESULT_SUCCESS; } -PalResult xDetachWindow( +PalResult wlGetCursorPos( PalWindow* window, - void** outWindowHandle) + Int32* x, + Int32* y) { - // we check is the window is really detachable - Window xWin = FROM_PAL_HANDLE(Window, window); - WindowData* data = nullptr; - s_X11.findContext(s_X11.display, xWin, s_X11.dataID, (XPointer*)&data); - if (!data) { - return PAL_RESULT_INVALID_WINDOW; - } - - if (data->isAttached == false) { - // window was created by PAL - return PAL_RESULT_INVALID_WINDOW; - } + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; +} - // detach the window - data->used = false; - long mask = 0; - s_X11.selectInput(s_X11.display, xWin, mask); - s_X11.setWMProtocols(s_X11.display, xWin, nullptr, 0); +PalResult wlSetCursorPos( + PalWindow* window, + Int32 x, + Int32 y) +{ + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; +} - if (outWindowHandle) { - *outWindowHandle = (void*)window; +PalResult wlSetWindowCursor( + PalWindow* window, + PalCursor* cursor) +{ + WindowData* data = findWindowData(window); + if (!data) { + return PAL_RESULT_INVALID_WINDOW; } + data->cursor = cursor; 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, +void* wlGetInstance() +{ + return (void*)s_Wl.display; +} - .createIcon = xCreateIcon, - .destroyIcon = xDestroyIcon, - .setWindowIcon = xSetWindowIcon, +PalResult wlAttachWindow( + void* windowHandle, + PalWindow** outWindow) +{ + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; +} - .createCursor = xCreateCursor, - .createCursorFrom = xCreateCursorFrom, - .destroyCursor = xDestroyCursor, - .showCursor = xShowCursor, - .clipCursor = xClipCursor, - .getCursorPos = xGetCursorPos, - .setCursorPos = xSetCursorPos, - .setWindowCursor = xSetWindowCursor, +PalResult wlDetachWindow( + PalWindow* window, + void** outWindowHandle) +{ + return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; +} - .attachWindow = xAttachWindow, - .detachWindow = xDetachWindow}; +static Backend s_wlBackend = { + .shutdownVideo = wlShutdownVideo, + .updateVideo = wlUpdateVideo, + .setFBConfig = wlSetFBConfig, + .enumerateMonitors = wlEnumerateMonitors, + .getMonitorInfo = wlGetMonitorInfo, + .getPrimaryMonitor = wlGetPrimaryMonitor, + .enumerateMonitorModes = wlEnumerateMonitorModes, + .getCurrentMonitorMode = wlGetCurrentMonitorMode, + .setMonitorMode = wlSetMonitorMode, + .validateMonitorMode = wlValidateMonitorMode, + .setMonitorOrientation = wlSetMonitorOrientation, + + .createWindow = wlCreateWindow, + .destroyWindow = wlDestroyWindow, + .maximizeWindow = wlMaximizeWindow, + .minimizeWindow = wlMinimizeWindow, + .restoreWindow = wlRestoreWindow, + .showWindow = wlShowWindow, + .hideWindow = wlHideWindow, + .flashWindow = wlFlashWindow, + .getWindowStyle = wlGetWindowStyle, + .getWindowMonitor = wlGetWindowMonitor, + .getWindowTitle = wlGetWindowTitle, + .getWindowPos = wlGetWindowPos, + .getWindowSize = wlGetWindowSize, + .getWindowState = wlGetWindowState, + .isWindowVisible = wlIsWindowVisible, + .getFocusWindow = wlGetFocusWindow, + .getWindowHandleInfo = wlGetWindowHandleInfo, + .getWindowHandleInfoEx = wlGetWindowHandleInfoEx, + .setWindowOpacity = wlSetWindowOpacity, + .setWindowStyle = wlSetWindowStyle, + .setWindowTitle = wlSetWindowTitle, + .setWindowPos = wlSetWindowPos, + .setWindowSize = wlSetWindowSize, + .setFocusWindow = wlSetFocusWindow, + + .createIcon = wlCreateIcon, + .destroyIcon = wlDestroyIcon, + .setWindowIcon = wlSetWindowIcon, + + .createCursor = wlCreateCursor, + .createCursorFrom = wlCreateCursorFrom, + .destroyCursor = wlDestroyCursor, + .showCursor = wlShowCursor, + .clipCursor = wlClipCursor, + .getCursorPos = wlGetCursorPos, + .setCursorPos = wlSetCursorPos, + .setWindowCursor = wlSetWindowCursor, + + .attachWindow = wlAttachWindow, + .detachWindow = wlDetachWindow}; -#pragma endregions +#endif // PAL_HAS_WAYLAND +#pragma endregion // ================================================== // Public API @@ -4212,14 +7622,32 @@ PalResult PAL_CALL palInitVideo( return PAL_RESULT_OUT_OF_MEMORY; } + s_Video.className = "PAL"; if (x11) { +#if PAL_HAS_X11 PalResult ret = xInitVideo(); if (ret != PAL_RESULT_SUCCESS) { return ret; } s_Video.backend = &s_XBackend; +#else + return PAL_RESULT_PLATFORM_FAILURE; +#endif // PAL_HAS_X11 + + } else { +#if PAL_HAS_WAYLAND + PalResult ret = wlInitVideo(); + if (ret != PAL_RESULT_SUCCESS) { + return ret; + } + s_Video.backend = &s_wlBackend; +#else + return PAL_RESULT_PLATFORM_FAILURE; +#endif // PAL_HAS_WAYLAND } + createScancodeTable(); + // we load EGL as well s_Egl.handle = dlopen("libEGL.so", RTLD_LAZY); if (s_Egl.handle) { @@ -4255,6 +7683,11 @@ void PAL_CALL palShutdownVideo() if (s_Egl.handle) { dlclose(s_Egl.handle); } + + s_Video.platformInstance = nullptr; + s_Video.display = nullptr; + memset(&s_Keyboard, 0, sizeof(Keyboard)); + memset(&s_Mouse, 0, sizeof(Mouse)); s_Video.initialized = false; } } @@ -4275,6 +7708,15 @@ PalVideoFeatures PAL_CALL palGetVideoFeatures() return s_Video.features; } +PalVideoFeatures64 PAL_CALL palGetVideoFeaturesEx() +{ + if (!s_Video.initialized) { + return 0; + } + + return s_Video.features64; +} + PalResult PAL_CALL palSetFBConfig( const int index, PalFBConfigBackend backend) @@ -4283,23 +7725,12 @@ PalResult PAL_CALL palSetFBConfig( return PAL_RESULT_VIDEO_NOT_INITIALIZED; } - // X11 can used GLX and EGL + // X11 and wayland can only 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); - } - } + return s_Video.backend->setFBConfig(index, backend); } PalResult PAL_CALL palEnumerateMonitors( @@ -4413,7 +7844,6 @@ PalResult PAL_CALL palValidateMonitorMode( PalMonitor* monitor, PalMonitorMode* mode) { - if (!s_Video.initialized) { return PAL_RESULT_VIDEO_NOT_INITIALIZED; } @@ -4551,7 +7981,7 @@ PalResult PAL_CALL palFlashWindow( return PAL_RESULT_NULL_POINTER; } - return s_Video.backend->xFlashWindow(window, info); + return s_Video.backend->flashWindow(window, info); } PalResult PAL_CALL palGetWindowStyle( @@ -4712,6 +8142,23 @@ void PAL_CALL palGetMouseWheelDelta( } } +void PAL_CALL palGetRawMouseWheelDelta( + float* dx, + float* dy) +{ + if (!s_Video.initialized) { + return; + } + + if (dx) { + *dx = (float)s_Mouse.tmpScrollX; + } + + if (dy) { + *dy = (float)s_Mouse.tmpScrollY; + } +} + bool PAL_CALL palIsWindowVisible(PalWindow* window) { if (!s_Video.initialized) { @@ -4741,6 +8188,13 @@ PalWindowHandleInfo PAL_CALL palGetWindowHandleInfo(PalWindow* window) } } +PalWindowHandleInfoEx PAL_CALL palGetWindowHandleInfoEx(PalWindow* w) +{ + if (s_Video.initialized) { + return s_Video.backend->getWindowHandleInfoEx(w); + } +} + PalResult PAL_CALL palSetWindowOpacity( PalWindow* window, float opacity) @@ -5000,11 +8454,7 @@ void* PAL_CALL palGetInstance() return nullptr; } - if (s_X11.display) { - // we are on X11 - return (void*)s_X11.display; - } - return nullptr; + return s_Video.display; } PalResult PAL_CALL palAttachWindow( @@ -5035,4 +8485,11 @@ PalResult PAL_CALL palDetachWindow( } return s_Video.backend->detachWindow(window, outWindowHandle); +} + +void PAL_CALL palSetPreferredInstance(void* instance) +{ + if (!s_Video.initialized && instance) { + s_Video.platformInstance = instance; + } } \ No newline at end of file diff --git a/src/video/pal_video_win32.c b/src/video/pal_video_win32.c index e14bbe6..a95412f 100644 --- a/src/video/pal_video_win32.c +++ b/src/video/pal_video_win32.c @@ -1,7 +1,7 @@ /** -Copyright (C) 2025 Nicholas Agbo +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 @@ -54,6 +54,7 @@ freely, subject to the following restrictions: #define MAX_MODE_COUNT 128 #define NULL_ORIENTATION 5 #define WINDOW_NAME_SIZE 256 +#define NULL_BUTTON_SERIAL 0xffffffffU typedef HRESULT(WINAPI* GetDpiForMonitorFn)( HMONITOR, @@ -104,6 +105,7 @@ typedef struct { Int32 pixelFormat; Int32 maxWindowData; PalVideoFeatures features; + PalVideoFeatures64 features64; const PalAllocator* allocator; PalEventDriver* eventDriver; HINSTANCE shcore; @@ -119,6 +121,7 @@ typedef struct { HINSTANCE instance; HWND hiddenWindow; + HCURSOR defaultCursor; WindowData* windowData; } VideoWin32; @@ -168,6 +171,8 @@ static Keyboard s_Keyboard = {0}; // Internal API // ================================================== +void palSetLastPlatformError(Uint32 e); + LRESULT CALLBACK videoProc( HWND hwnd, UINT msg, @@ -551,7 +556,7 @@ LRESULT CALLBACK videoProc( if (mode != PAL_DISPATCH_NONE) { PalEvent event = {0}; event.type = type; - event.data = button; + event.data = palPackUint32(button, NULL_BUTTON_SERIAL); event.data2 = palPackPointer((PalWindow*)hwnd); palPushEvent(driver, &event); } @@ -652,11 +657,12 @@ LRESULT CALLBACK videoProc( if (LOWORD(lParam) == HTCLIENT) { if (data && data->cursor) { SetCursor(data->cursor); - return TRUE; + } else { + // no cursor, use default + SetCursor(s_Video.defaultCursor); } - return FALSE; + return TRUE; } - break; } @@ -772,6 +778,7 @@ static inline PalResult setMonitorMode( return PAL_RESULT_INVALID_MONITOR; } else { + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } } @@ -1070,12 +1077,17 @@ PalResult PAL_CALL palInitVideo( 0); // get the instance - s_Video.instance = GetModuleHandleW(nullptr); + if (!s_Video.instance) { + s_Video.instance = GetModuleHandleW(nullptr); + } + + // load default cursor + s_Video.defaultCursor = LoadCursorW(NULL, IDC_ARROW); // register class WNDCLASSEXW wc = {0}; wc.cbSize = sizeof(WNDCLASSEXW); - wc.hCursor = LoadCursorW(NULL, IDC_ARROW); + wc.hCursor = s_Video.defaultCursor; wc.hIcon = LoadIconW(NULL, IDI_APPLICATION); wc.hIconSm = LoadIconW(NULL, IDI_APPLICATION); wc.hInstance = s_Video.instance; @@ -1105,6 +1117,8 @@ PalResult PAL_CALL palInitVideo( nullptr); if (!s_Video.hiddenWindow) { + DWORD error = GetLastError(); + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } @@ -1118,6 +1132,8 @@ PalResult PAL_CALL palInitVideo( rid.usUsage = 0x02; rid.usUsagePage = 0x01; if (!RegisterRawInputDevices(&rid, 1, sizeof(RAWINPUTDEVICE))) { + DWORD error = GetLastError(); + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } @@ -1201,9 +1217,52 @@ PalResult PAL_CALL palInitVideo( if (s_Video.getDpiForMonitor && s_Video.setProcessAwareness) { s_Video.features |= PAL_VIDEO_FEATURE_HIGH_DPI; + s_Video.features64 |= PAL_VIDEO_FEATURE64_HIGH_DPI; s_Video.setProcessAwareness(WIN32_DPI_AWARE); } + // extended features + s_Video.features64 |= PAL_VIDEO_FEATURE64_MONITOR_SET_ORIENTATION; + s_Video.features64 |= PAL_VIDEO_FEATURE64_MONITOR_GET_ORIENTATION; + s_Video.features64 |= PAL_VIDEO_FEATURE64_BORDERLESS_WINDOW; + s_Video.features64 |= PAL_VIDEO_FEATURE64_TRANSPARENT_WINDOW; + s_Video.features64 |= PAL_VIDEO_FEATURE64_TOOL_WINDOW; + s_Video.features64 |= PAL_VIDEO_FEATURE64_MONITOR_SET_MODE; + s_Video.features64 |= PAL_VIDEO_FEATURE64_MONITOR_GET_MODE; + s_Video.features64 |= PAL_VIDEO_FEATURE64_MULTI_MONITORS; + s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_SET_SIZE; + s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_GET_SIZE; + s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_SET_POS; + s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_GET_POS; + s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_SET_STATE; + s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_GET_STATE; + s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_SET_VISIBILITY; + s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_GET_VISIBILITY; + s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_SET_TITLE; + s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_GET_TITLE; + s_Video.features64 |= PAL_VIDEO_FEATURE64_NO_MAXIMIZEBOX; + s_Video.features64 |= PAL_VIDEO_FEATURE64_NO_MINIMIZEBOX; + s_Video.features64 |= PAL_VIDEO_FEATURE64_CLIP_CURSOR; + s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_FLASH_CAPTION; + s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_FLASH_TRAY; + s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_FLASH_INTERVAL; + s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_SET_INPUT_FOCUS; + s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_GET_INPUT_FOCUS; + s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_SET_STYLE; + s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_GET_STYLE; + s_Video.features64 |= PAL_VIDEO_FEATURE64_CURSOR_SET_POS; + s_Video.features64 |= PAL_VIDEO_FEATURE64_CURSOR_GET_POS; + s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_SET_ICON; + + s_Video.features64 |= PAL_VIDEO_FEATURE64_TOPMOST_WINDOW; + s_Video.features64 |= PAL_VIDEO_FEATURE64_DECORATED_WINDOW; + s_Video.features64 |= PAL_VIDEO_FEATURE64_CURSOR_SET_VISIBILITY; + s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_GET_MONITOR; + s_Video.features64 |= PAL_VIDEO_FEATURE64_MONITOR_GET_PRIMARY; + s_Video.features64 |= PAL_VIDEO_FEATURE64_FOREIGN_WINDOWS; + s_Video.features64 |= PAL_VIDEO_FEATURE64_MONITOR_VALIDATE_MODE; + s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_SET_CURSOR; + s_Video.initialized = true; s_Video.allocator = allocator; s_Video.eventDriver = eventDriver; @@ -1225,6 +1284,12 @@ void PAL_CALL palShutdownVideo() DestroyWindow(s_Video.hiddenWindow); UnregisterClassW(PAL_VIDEO_CLASS, s_Video.instance); palFree(s_Video.allocator, s_Video.windowData); + + memset(&s_Video, 0, sizeof(VideoWin32)); + memset(&s_Keyboard, 0, sizeof(Keyboard)); + memset(&s_Mouse, 0, sizeof(Mouse)); + + s_Video.windowData = nullptr; s_Video.initialized = false; } @@ -1281,6 +1346,15 @@ PalVideoFeatures PAL_CALL palGetVideoFeatures() return s_Video.features; } +PalVideoFeatures64 PAL_CALL palGetVideoFeaturesEx() +{ + if (!s_Video.initialized) { + return 0; + } + + return s_Video.features64; +} + PalResult PAL_CALL palSetFBConfig( const int index, PalFBConfigBackend backend) @@ -1291,6 +1365,7 @@ PalResult PAL_CALL palSetFBConfig( } if (backend == PAL_CONFIG_BACKEND_EGL || + backend == PAL_CONFIG_BACKEND_GLES || backend == PAL_CONFIG_BACKEND_GLX) { return PAL_RESULT_INVALID_FBCONFIG_BACKEND; } @@ -1347,6 +1422,8 @@ PalResult PAL_CALL palGetPrimaryMonitor(PalMonitor** outMonitor) HMONITOR monitor = nullptr; monitor = MonitorFromPoint((POINT){0, 0}, MONITOR_DEFAULTTOPRIMARY); if (!monitor) { + DWORD error = GetLastError(); + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } @@ -1374,6 +1451,7 @@ PalResult PAL_CALL palGetMonitorInfo( return PAL_RESULT_INVALID_MONITOR; } else { + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } } @@ -1454,6 +1532,7 @@ PalResult PAL_CALL palEnumerateMonitorModes( return PAL_RESULT_INVALID_MONITOR; } else { + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } } @@ -1522,6 +1601,7 @@ PalResult PAL_CALL palGetCurrentMonitorMode( return PAL_RESULT_INVALID_MONITOR; } else { + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } } @@ -1584,6 +1664,7 @@ PalResult PAL_CALL palSetMonitorOrientation( return PAL_RESULT_INVALID_MONITOR; } else { + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } } @@ -1701,6 +1782,8 @@ PalResult PAL_CALL palCreateWindow( MONITOR_DEFAULTTOPRIMARY); if (!monitor) { + DWORD error = GetLastError(); + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } } @@ -1749,6 +1832,8 @@ PalResult PAL_CALL palCreateWindow( nullptr); if (!handle) { + DWORD error = GetLastError(); + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } @@ -1829,7 +1914,9 @@ void PAL_CALL palDestroyWindow(PalWindow* window) if (data->isAttached) { return; } + DestroyWindow((HWND)window); + data->used = false; } } @@ -1958,6 +2045,7 @@ PalResult PAL_CALL palFlashWindow( return PAL_RESULT_INVALID_WINDOW; } else { + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } } @@ -2050,6 +2138,7 @@ PalResult PAL_CALL palGetWindowMonitor( return PAL_RESULT_INVALID_WINDOW; } else { + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } } @@ -2236,6 +2325,23 @@ void PAL_CALL palGetMouseWheelDelta( } } +void PAL_CALL palGetRawMouseWheelDelta( + float* dx, + float* dy) +{ + if (!s_Video.initialized) { + return; + } + + if (dx) { + *dx = (float)s_Mouse.WheelX; + } + + if (dy) { + *dy = (float)s_Mouse.WheelY; + } +} + bool PAL_CALL palIsWindowVisible(PalWindow* window) { if (!s_Video.initialized) { @@ -2267,6 +2373,16 @@ PalWindowHandleInfo PAL_CALL palGetWindowHandleInfo(PalWindow* window) return handle; } +PalWindowHandleInfoEx PAL_CALL palGetWindowHandleInfoEx(PalWindow* window) +{ + PalWindowHandleInfoEx handle = {0}; + if (s_Video.initialized && window) { + handle.nativeDisplay = nullptr; + handle.nativeWindow = (void*)window; + } + return handle; +} + PalResult PAL_CALL palSetWindowOpacity( PalWindow* window, float opacity) @@ -2303,6 +2419,7 @@ PalResult PAL_CALL palSetWindowOpacity( } else { // FIXME: check for child windows + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } } @@ -2387,6 +2504,7 @@ PalResult PAL_CALL palSetWindowStyle( return PAL_RESULT_INVALID_WINDOW; } else { + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } } @@ -2442,6 +2560,7 @@ PalResult PAL_CALL palSetWindowPos( return PAL_RESULT_INVALID_WINDOW; } else { + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } } @@ -2479,6 +2598,7 @@ PalResult PAL_CALL palSetWindowSize( return PAL_RESULT_INVALID_ARGUMENT; } else { + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } } @@ -2504,6 +2624,7 @@ PalResult PAL_CALL palSetFocusWindow(PalWindow* window) return PAL_RESULT_ACCESS_DENIED; } else { + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } } @@ -2555,6 +2676,8 @@ PalResult PAL_CALL palCreateIcon( if (!bitmap) { ReleaseDC(nullptr, hdc); + DWORD error = GetLastError(); + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } ReleaseDC(nullptr, hdc); @@ -2591,6 +2714,8 @@ PalResult PAL_CALL palCreateIcon( HICON icon = CreateIconIndirect(&iconInfo); if (!icon) { s_Video.deleteObject(bitmap); + DWORD error = GetLastError(); + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } @@ -2672,6 +2797,8 @@ PalResult PAL_CALL palCreateCursor( if (!bitmap) { ReleaseDC(nullptr, hdc); + DWORD error = GetLastError(); + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } ReleaseDC(nullptr, hdc); @@ -2710,6 +2837,8 @@ PalResult PAL_CALL palCreateCursor( HCURSOR cursor = CreateIconIndirect(&iconInfo); if (!cursor) { s_Video.deleteObject(bitmap); + DWORD error = GetLastError(); + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } @@ -2764,6 +2893,8 @@ PalResult PAL_CALL palCreateCursorFrom( } if (!cursor) { + DWORD error = GetLastError(); + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } @@ -2892,6 +3023,7 @@ PalResult PAL_CALL palSetWindowCursor( return PAL_RESULT_INVALID_WINDOW; } else { + palSetLastPlatformError(error); return PAL_RESULT_PLATFORM_FAILURE; } @@ -2975,4 +3107,11 @@ PalResult PAL_CALL palDetachWindow( } return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palSetPreferredInstance(void* instance) +{ + if (!s_Video.initialized && instance) { + s_Video.instance = instance; + } } \ No newline at end of file diff --git a/tests/attach_window_test.c b/tests/attach_window_test.c index ae18294..25e0e92 100644 --- a/tests/attach_window_test.c +++ b/tests/attach_window_test.c @@ -48,7 +48,7 @@ typedef int (*XDestroyWindowFn)( Display*, Window); -static void* s_X11Lib; +static void* s_LibX; static XCreateSimpleWindowFn s_XCreateWindow; static XSyncFn s_XSync; static XMapRaisedFn s_XMapRaised; @@ -60,32 +60,32 @@ static XDestroyWindowFn s_XDestroyWindow; #define WINDOW_POSY 100 #define WINDOW_WIDTH 640 #define WINDOW_HEIGHT 480 -#define WINDOW_TITLE "PAL Attach Window Test" +#define WINDOW_TITLE "Attach Window Test" static void* createX11Window() { #ifdef __linux__ // load the procs - s_X11Lib = dlopen("libX11.so", RTLD_LAZY); - if (!s_X11Lib) { + s_LibX = dlopen("libX11.so", RTLD_LAZY); + if (!s_LibX) { return nullptr; } // clang-format off s_XCreateWindow = (XCreateSimpleWindowFn)dlsym( - s_X11Lib, + s_LibX, "XCreateSimpleWindow"); s_XSync = (XSyncFn)dlsym( - s_X11Lib, + s_LibX, "XSync"); s_XMapRaised = (XMapRaisedFn)dlsym( - s_X11Lib, + s_LibX, "XMapRaised"); s_XDestroyWindow = (XDestroyWindowFn)dlsym( - s_X11Lib, + s_LibX, "XDestroyWindow"); if (!s_XCreateWindow || !s_XSync || !s_XMapRaised || !s_XDestroyWindow) { @@ -183,7 +183,7 @@ static void destroyX11Window(void* windowHandle) #ifdef __linux__ Display* display = palGetInstance(); s_XDestroyWindow(display, (Window)(UintPtr)windowHandle); - dlclose(s_X11Lib); // we loaded dynamically + dlclose(s_LibX); // we loaded dynamically #endif } @@ -211,6 +211,7 @@ bool attachWindowTest() palLog(nullptr, "==========================================="); palLog(nullptr, "Attach Window Test"); palLog(nullptr, "Press A to attach and D to detach window"); + palLog(nullptr, "Press Escape or click close button to close Test"); palLog(nullptr, "==========================================="); palLog(nullptr, ""); @@ -238,6 +239,18 @@ bool attachWindowTest() return false; } + // check for support + PalVideoFeatures64 features = palGetVideoFeaturesEx(); + if (!(features & PAL_VIDEO_FEATURE64_FOREIGN_WINDOWS)) { + // clang-format off + palLog(nullptr, "Attaching and detaching foreign windows feature not supported"); + // clang-format on + + palDestroyEventDriver(eventDriver); + palShutdownVideo(); + return false; + } + // we are interested in move and close events palSetEventDispatchMode( eventDriver, @@ -249,6 +262,8 @@ bool attachWindowTest() PAL_EVENT_WINDOW_MOVE, PAL_DISPATCH_POLL); + palSetEventDispatchMode(eventDriver, PAL_EVENT_KEYDOWN, PAL_DISPATCH_POLL); + // we listen for key release events to attach and detach the window palSetEventDispatchMode(eventDriver, PAL_EVENT_KEYUP, PAL_DISPATCH_POLL); @@ -277,7 +292,6 @@ bool attachWindowTest() // now that the window is attached, we can use PAL video API // to manager it - // TODO: check features before result = palSetWindowTitle(myWindow, WINDOW_TITLE); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); @@ -312,6 +326,15 @@ bool attachWindowTest() break; } + case PAL_EVENT_KEYDOWN: { + PalKeycode keycode = 0; + palUnpackUint32(event.data, &keycode, nullptr); + if (keycode == PAL_KEYCODE_ESCAPE) { + running = false; + } + break; + } + case PAL_EVENT_KEYUP: { // we detach the window after keyup // since if the window is detached, diff --git a/tests/char_event_test.c b/tests/char_event_test.c index 3f2a106..ea34b91 100644 --- a/tests/char_event_test.c +++ b/tests/char_event_test.c @@ -7,6 +7,7 @@ bool charEventTest() palLog(nullptr, ""); palLog(nullptr, "==========================================="); palLog(nullptr, "Character Event Test"); + palLog(nullptr, "Press Escape or click close button to close Test"); palLog(nullptr, "==========================================="); palLog(nullptr, ""); @@ -37,11 +38,19 @@ bool charEventTest() } // fill the create info struct - createInfo.monitor = nullptr; // use primary monitor + createInfo.monitor = nullptr; // use default monitor createInfo.height = 480; createInfo.width = 640; createInfo.show = true; - createInfo.title = "PAL Character Window"; + createInfo.title = "Character Window"; + + // check if we support decorated windows (title bar, close etc) + PalVideoFeatures64 features = palGetVideoFeaturesEx(); + if (!(features & PAL_VIDEO_FEATURE64_DECORATED_WINDOW)) { + // if we dont support, we need to create a borderless window + // and create the decorations ourselves + createInfo.style |= PAL_WINDOW_STYLE_BORDERLESS; + } // create the window with the create info struct result = palCreateWindow(&createInfo, &window); @@ -57,6 +66,8 @@ bool charEventTest() PAL_EVENT_WINDOW_CLOSE, PAL_DISPATCH_POLL); + palSetEventDispatchMode(eventDriver, PAL_EVENT_KEYDOWN, PAL_DISPATCH_POLL); + palSetEventDispatchMode(eventDriver, PAL_EVENT_KEYCHAR, PAL_DISPATCH_POLL); bool running = true; @@ -88,6 +99,15 @@ bool charEventTest() } break; } + + case PAL_EVENT_KEYDOWN: { + PalKeycode keycode = 0; + palUnpackUint32(event.data, &keycode, nullptr); + if (keycode == PAL_KEYCODE_ESCAPE) { + running = false; + } + break; + } } } diff --git a/tests/condvar_test.c b/tests/condvar_test.c index c22e356..266b707 100644 --- a/tests/condvar_test.c +++ b/tests/condvar_test.c @@ -111,6 +111,7 @@ bool condvarTest() palUnlockMutex(g_Mutex); // wait for the remaining threads + // joint threads does not need to be detached for (Int32 i = 0; i < THREAD_COUNT; i++) { palJoinThread(threads[i], nullptr); palLog(nullptr, "Thread %d finished successfully", data[i].id); diff --git a/tests/cursor_test.c b/tests/cursor_test.c index 710ce75..59620be 100644 --- a/tests/cursor_test.c +++ b/tests/cursor_test.c @@ -7,6 +7,7 @@ bool cursorTest() palLog(nullptr, ""); palLog(nullptr, "==========================================="); palLog(nullptr, "Cursor Test"); + palLog(nullptr, "Press Escape or click close button to close Test"); palLog(nullptr, "==========================================="); palLog(nullptr, ""); @@ -45,6 +46,15 @@ bool cursorTest() return false; } + // check for support + PalVideoFeatures64 features = palGetVideoFeaturesEx(); + if (!(features & PAL_VIDEO_FEATURE64_WINDOW_SET_CURSOR)) { + palLog(nullptr, "Seting cursors feature not supported"); + palDestroyEventDriver(eventDriver); + palShutdownVideo(); + return false; + } + // simple checkerboard RGBA pixel buffer // every block contains 64 pixels Uint8 pixels[32 * 32 * 4]; // size is 32 and we have 4 channles @@ -82,12 +92,19 @@ bool cursorTest() } // fill the create info struct - createInfo.monitor = nullptr; // use primary monitor + createInfo.monitor = nullptr; // use default monitor createInfo.height = 480; createInfo.width = 640; createInfo.show = true; createInfo.style = PAL_WINDOW_STYLE_RESIZABLE; - createInfo.title = "PAL cursor Window"; + createInfo.title = "Cursor Window"; + + // check if we support decorated windows (title bar, close etc) + if (!(features & PAL_VIDEO_FEATURE64_DECORATED_WINDOW)) { + // if we dont support, we need to create a borderless window + // and create the decorations ourselves + createInfo.style |= PAL_WINDOW_STYLE_BORDERLESS; + } // create the window with the create info struct result = palCreateWindow(&createInfo, &window); @@ -103,6 +120,8 @@ bool cursorTest() PAL_EVENT_WINDOW_CLOSE, PAL_DISPATCH_POLL); // polling + palSetEventDispatchMode(eventDriver, PAL_EVENT_KEYDOWN, PAL_DISPATCH_POLL); + // set the cursor palSetWindowCursor(window, cursor); @@ -118,6 +137,15 @@ bool cursorTest() running = false; break; } + + case PAL_EVENT_KEYDOWN: { + PalKeycode keycode = 0; + palUnpackUint32(event.data, &keycode, nullptr); + if (keycode == PAL_KEYCODE_ESCAPE) { + running = false; + } + break; + } } } } diff --git a/tests/custom_decoration_test.c b/tests/custom_decoration_test.c new file mode 100644 index 0000000..d0965cb --- /dev/null +++ b/tests/custom_decoration_test.c @@ -0,0 +1,1014 @@ + +#if defined(__linux__) +#define _GNU_SOURCE +#define _POSIX_C_SOURCE 200112L // for linux +#include "pal/pal_video.h" +#include "tests.h" + +#include +#include +#include +#include +#include +#include + +#define WINDOW_TITLE "Custom Decoration Test" +#define WL_MARSHAL_FLAG_DESTROY 1 << 0 +#define TITLEBAR_HEIGHT 34 +#define BUTTON_SIZE 24 +#define BUTTON_POSY 8 +#define BUTTON_OFFSET 30 + +struct wl_display; +struct wl_registry; +struct wl_proxy; +struct wl_interface; +struct wl_seat; +struct wl_compositor; +struct wl_subcompositor; +struct wl_shm; +struct wl_buffer; +struct wl_surface; +struct wl_subsurface; +struct xdg_toplevel; + +typedef struct { + int fd; + + // cache mouse position + int mouseX; + int mouseY; + + struct wl_buffer* buffer; + struct wl_surface* surface; + struct wl_subsurface* subsurface; + Uint32* pixels; + PalEventDriver* driver; // a pointer to the event driver + Uint64 size; +} WaylandDecoration; + +static struct wl_display* s_Display = nullptr; +static struct wl_registry* s_Registry = nullptr; +static struct wl_compositor* s_Compositor = nullptr; +static struct wl_subcompositor* s_Subcompositor = nullptr; +static struct wl_shm* s_Shm = nullptr; +static struct wl_surface* s_Surface = nullptr; +static struct wl_seat* s_Seat = nullptr; + +static WaylandDecoration s_Decoration = {0}; + +typedef struct wl_display* (*wl_display_connect_fn)(const char*); +typedef void (*wl_display_disconnect_fn)(struct wl_display*); +typedef uint32_t (*wl_proxy_get_version_fn)(struct wl_proxy*); +typedef int (*wl_display_roundtrip_fn)(struct wl_display*); + +typedef struct wl_proxy* (*wl_proxy_marshal_flags_fn)( + struct wl_proxy*, + uint32_t, + const struct wl_interface*, + uint32_t, + uint32_t, + ...); + +typedef int (*wl_proxy_add_listener_fn)( + struct wl_proxy*, + void (**)(void), + void*); + +typedef void (*wl_proxy_destroy_fn)(struct wl_proxy*); + +struct wl_interface { + const char* name; + int version; + int method_count; + const struct wl_message* methods; + int event_count; + const struct wl_message* events; +}; + +struct wl_registry_listener { + void (*global)( + void*, + struct wl_registry*, + uint32_t, + const char*, + uint32_t); + + void (*global_remove)( + void*, + struct wl_registry*, + uint32_t); +}; + +static void* s_LibWayland; +static wl_display_connect_fn s_wl_display_connect; +static wl_display_disconnect_fn s_wl_display_disconnect; +static wl_display_roundtrip_fn s_wl_display_roundtrip; +static wl_proxy_get_version_fn s_wl_proxy_get_version; +static wl_proxy_marshal_flags_fn s_wl_proxy_marshal_flags; +static wl_proxy_add_listener_fn s_wl_proxy_add_listener; +static wl_proxy_destroy_fn s_wl_proxy_destroy; + +static const struct wl_interface* registryInterface; +static const struct wl_interface* seatInterface; +static const struct wl_interface* compositorInterface; +static const struct wl_interface* subCompositorInterface; +static const struct wl_interface* shmInterface; +static const struct wl_interface* shmPoolInterface; +static const struct wl_interface* surfaceInterface; +static const struct wl_interface* bufferInterface; +static const struct wl_interface* subsurfaceInterface; + +static inline void* wlRegistryBind( + struct wl_registry* wl_registry, + uint32_t name, + const struct wl_interface* interface, + uint32_t version) +{ + struct wl_proxy* id; + id = s_wl_proxy_marshal_flags( + (struct wl_proxy*)wl_registry, + 0, // WL_REGISTRY_BIND + interface, + version, + 0, + name, + interface->name, + version, + NULL); + + return (void*)id; +} + +static inline int wlRegistryAddListener( + struct wl_registry* wl_registry, + const struct wl_registry_listener* listener, + void* data) +{ + return s_wl_proxy_add_listener( + (struct wl_proxy*)wl_registry, + (void (**)(void))listener, + data); +} + +static inline struct wl_registry* +wlDisplayGetRegistry(struct wl_display* wl_display) +{ + struct wl_proxy* registry; + registry = s_wl_proxy_marshal_flags( + (struct wl_proxy*)wl_display, + 1, // WL_DISPLAY_GET_REGISTRY + registryInterface, + s_wl_proxy_get_version((struct wl_proxy*)wl_display), + 0, + NULL); + + return (struct wl_registry*)registry; +} + +static inline struct wl_surface* +wlCompositorCreateSurface(struct wl_compositor* wl_compositor) +{ + struct wl_proxy* id; + id = s_wl_proxy_marshal_flags( + (struct wl_proxy*)wl_compositor, + 0, // WL_COMPOSITOR_CREATE_SURFACE, + surfaceInterface, + s_wl_proxy_get_version((struct wl_proxy*)wl_compositor), + 0, + NULL); + + return (struct wl_surface*)id; +} + +static inline void wlSurfaceCommit(struct wl_surface* wl_surface) +{ + s_wl_proxy_marshal_flags( + (struct wl_proxy*)wl_surface, + 6, // WL_SURFACE_COMMIT + NULL, + s_wl_proxy_get_version((struct wl_proxy*)wl_surface), + 0); +} + +static inline void wlSurfaceDestroy(struct wl_surface* wl_surface) +{ + s_wl_proxy_marshal_flags( + (struct wl_proxy*)wl_surface, + 0, // WL_SURFACE_DESTROY + NULL, + s_wl_proxy_get_version((struct wl_proxy*)wl_surface), + WL_MARSHAL_FLAG_DESTROY); +} + +static inline struct wl_shm_pool* wlShmCreatePool( + struct wl_shm* wl_shm, + int32_t fd, + int32_t size) +{ + struct wl_proxy* id; + id = s_wl_proxy_marshal_flags( + (struct wl_proxy*)wl_shm, + 0, // WL_SHM_CREATE_POOL + shmPoolInterface, + s_wl_proxy_get_version((struct wl_proxy*)wl_shm), + 0, + NULL, + fd, + size); + + return (struct wl_shm_pool*)id; +} + +static inline void wlShmPoolDestroy(struct wl_shm_pool* wl_shm_pool) +{ + s_wl_proxy_marshal_flags( + (struct wl_proxy*)wl_shm_pool, + 1, + NULL, + s_wl_proxy_get_version((struct wl_proxy*)wl_shm_pool), + WL_MARSHAL_FLAG_DESTROY); +} + +static inline struct wl_buffer* wlShmPoolCreateBuffer( + struct wl_shm_pool* wl_shm_pool, + int32_t offset, + int32_t width, + int32_t height, + int32_t stride, + uint32_t format) +{ + struct wl_proxy* id; + id = s_wl_proxy_marshal_flags( + (struct wl_proxy*)wl_shm_pool, + 0, + bufferInterface, + s_wl_proxy_get_version((struct wl_proxy*)wl_shm_pool), + 0, + NULL, + offset, + width, + height, + stride, + format); + + return (struct wl_buffer*)id; +} + +static inline void wlBufferDestroy(struct wl_buffer* wl_buffer) +{ + s_wl_proxy_marshal_flags( + (struct wl_proxy*)wl_buffer, + 0, + NULL, + s_wl_proxy_get_version((struct wl_proxy*)wl_buffer), + WL_MARSHAL_FLAG_DESTROY); +} + +static inline void wlSurfaceAttach( + struct wl_surface* wl_surface, + struct wl_buffer* buffer, + int32_t x, + int32_t y) +{ + s_wl_proxy_marshal_flags( + (struct wl_proxy*)wl_surface, + 1, + NULL, + s_wl_proxy_get_version((struct wl_proxy*)wl_surface), + 0, + buffer, + x, + y); +} + +static inline void wlSurfaceDamageBuffer( + struct wl_surface* wl_surface, + int32_t x, + int32_t y, + int32_t width, + int32_t height) +{ + s_wl_proxy_marshal_flags( + (struct wl_proxy*)wl_surface, + 9, + NULL, + s_wl_proxy_get_version((struct wl_proxy*)wl_surface), + 0, + x, + y, + width, + height); +} + +static inline void +wlSubcompositorDestroy(struct wl_subcompositor* wl_subcompositor) +{ + s_wl_proxy_marshal_flags( + (struct wl_proxy*)wl_subcompositor, + 0, + NULL, + s_wl_proxy_get_version((struct wl_proxy*)wl_subcompositor), + WL_MARSHAL_FLAG_DESTROY); +} + +static inline struct wl_subsurface* wlSubcompositorGetSubsurface( + struct wl_subcompositor* wl_subcompositor, + struct wl_surface* surface, + struct wl_surface* parent) +{ + struct wl_proxy* id; + id = s_wl_proxy_marshal_flags( + (struct wl_proxy*)wl_subcompositor, + 1, + subsurfaceInterface, + s_wl_proxy_get_version((struct wl_proxy*)wl_subcompositor), + 0, + NULL, + surface, + parent); + + return (struct wl_subsurface*)id; +} + +static inline void wlSubsurfaceDestroy(struct wl_subsurface* wl_subsurface) +{ + s_wl_proxy_marshal_flags( + (struct wl_proxy*)wl_subsurface, + 0, + NULL, + s_wl_proxy_get_version((struct wl_proxy*)wl_subsurface), + WL_MARSHAL_FLAG_DESTROY); +} + +static inline void wlSubsurfaceSetPosition( + struct wl_subsurface* wl_subsurface, + int32_t x, + int32_t y) +{ + s_wl_proxy_marshal_flags( + (struct wl_proxy*)wl_subsurface, + 1, + NULL, + s_wl_proxy_get_version((struct wl_proxy*)wl_subsurface), + 0, + x, + y); +} + +static inline void wlSubsurfaceSetDesync(struct wl_subsurface* wl_subsurface) +{ + s_wl_proxy_marshal_flags( + (struct wl_proxy*)wl_subsurface, + 5, + NULL, + s_wl_proxy_get_version((struct wl_proxy*)wl_subsurface), + 0); +} + +static void globalHandle( + void* data, + struct wl_registry* registry, + uint32_t name, + const char* interface, + uint32_t version) +{ + if (strcmp(interface, "wl_seat") == 0) { + s_Seat = wlRegistryBind(registry, name, seatInterface, 5); + + } else if (strcmp(interface, "wl_compositor") == 0) { + s_Compositor = wlRegistryBind(registry, name, compositorInterface, 4); + + } else if (strcmp(interface, "wl_subcompositor") == 0) { + s_Subcompositor = + wlRegistryBind(registry, name, subCompositorInterface, 1); + + } else if (strcmp(interface, "wl_shm") == 0) { + s_Shm = wlRegistryBind(registry, name, shmInterface, 1); + } +} + +static void globalRemove( + void* data, + struct wl_registry* registry, + uint32_t name) +{ +} + +static const struct wl_registry_listener s_RegistryListener = { + .global = globalHandle, + .global_remove = globalRemove}; + +// xdg-shell protocol +static inline void xdgToplevelMove( + struct xdg_toplevel* xdg_toplevel, + struct wl_seat* seat, + uint32_t serial) +{ + s_wl_proxy_marshal_flags( + (struct wl_proxy*)xdg_toplevel, + 5, + NULL, + s_wl_proxy_get_version((struct wl_proxy*)xdg_toplevel), + 0, + seat, + serial); +} + +static inline void xdgToplevelResize( + struct xdg_toplevel* xdg_toplevel, + struct wl_seat* seat, + uint32_t serial, + uint32_t edges) +{ + s_wl_proxy_marshal_flags( + (struct wl_proxy*)xdg_toplevel, + 6, + NULL, + s_wl_proxy_get_version((struct wl_proxy*)xdg_toplevel), + 0, + seat, + serial, + edges); +} + +static void openDisplayWayland() +{ + const char* session = getenv("XDG_SESSION_TYPE"); + if (session) { + if (strcmp(session, "x11") == 0) { + return; + } + } + + s_LibWayland = dlopen("libwayland-client.so.0", RTLD_LAZY); + if (!s_LibWayland) { + return; + } + + // clang-format off + s_wl_display_connect = (wl_display_connect_fn)dlsym( + s_LibWayland, + "wl_display_connect"); + + s_wl_display_disconnect = (wl_display_disconnect_fn)dlsym( + s_LibWayland, + "wl_display_disconnect"); + + s_wl_display_roundtrip = (wl_display_roundtrip_fn)dlsym( + s_LibWayland, + "wl_display_roundtrip"); + + s_wl_proxy_marshal_flags = (wl_proxy_marshal_flags_fn)dlsym( + s_LibWayland, + "wl_proxy_marshal_flags"); + + s_wl_proxy_get_version = (wl_proxy_get_version_fn)dlsym( + s_LibWayland, + "wl_proxy_get_version"); + + s_wl_proxy_add_listener = (wl_proxy_add_listener_fn)dlsym( + s_LibWayland, + "wl_proxy_add_listener"); + + s_wl_proxy_destroy = (wl_proxy_destroy_fn)dlsym( + s_LibWayland, + "wl_proxy_destroy"); + + registryInterface = dlsym(s_LibWayland, "wl_registry_interface"); + seatInterface = dlsym(s_LibWayland, "wl_seat_interface"); + compositorInterface = dlsym(s_LibWayland, "wl_compositor_interface"); + subCompositorInterface = dlsym(s_LibWayland, "wl_subcompositor_interface"); + shmInterface = dlsym(s_LibWayland, "wl_shm_interface"); + shmPoolInterface = dlsym(s_LibWayland, "wl_shm_pool_interface"); + surfaceInterface = dlsym(s_LibWayland, "wl_surface_interface"); + bufferInterface = dlsym(s_LibWayland, "wl_buffer_interface"); + subsurfaceInterface = dlsym(s_LibWayland, "wl_subsurface_interface"); + + struct wl_display* display = s_wl_display_connect(nullptr); + if (display) { + struct wl_registry* registry = wlDisplayGetRegistry(display); + wlRegistryAddListener(registry, &s_RegistryListener, nullptr); + s_wl_display_roundtrip(display); + s_Display = display; + } + + if (!s_Compositor || !s_Subcompositor || !s_Shm || !s_Seat) { + palLog(nullptr, "Failed to get globals"); + return; + } +} + +static void closeDisplayWayland() +{ + if (s_Compositor) { + s_wl_proxy_destroy((struct wl_proxy *)s_Compositor); + s_wl_proxy_destroy((struct wl_proxy *)s_Shm); + s_wl_proxy_destroy((struct wl_proxy *)s_Seat); + wlSubcompositorDestroy(s_Subcompositor); + } + + s_wl_display_disconnect((struct wl_display*)s_Display); + dlclose(s_LibWayland); +} + +static int createShmFile(Uint64 size) +{ + char template[] = "/tmp/pal-shm-XXXXXX"; + int fd = mkstemp(template); + if (fd < 0) { + return -1; + } + + unlink(template); + if (ftruncate(fd, size) < 0) { + return -1; + } + + return fd; +} + +static struct wl_buffer* createShmBuffer( + int width, + int height, + int* outFd, + Uint64* outSize, + Uint32** outPixels) +{ + int stride = width * 4; + Uint64 size = stride * height; + struct wl_buffer* buffer = nullptr; + struct wl_shm_pool* pool = nullptr; + void* data = nullptr; + + int fd = createShmFile(size); + if (fd == -1) { + return nullptr; + } + + data = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) { + return nullptr; + } + + pool = wlShmCreatePool(s_Shm, fd, size); + if (!pool) { + return nullptr; + } + + buffer = wlShmPoolCreateBuffer(pool, 0, width, height, stride, 1); + if (!buffer) { + return nullptr; + } + + wlShmPoolDestroy(pool); + + *outPixels = (Uint32*)data; + *outFd = fd; + *outSize = size; + return buffer; +} + +void fillRect( + uint32_t *pixels, + int stride, + int x, + int y, + int w, + int h, + uint32_t color) +{ + for (int j = 0; j < h; j++) { + for (int i = 0; i < w; i++) { + pixels[(y + j) * stride + (x + i)] = color; + } + } +} + +// a simple 8x8 bitmap font +// exchange with your font +// Each byte = one row of 8 pixels, MSB = leftmost pixel +static const Uint8 s_FontBasic[128][8] = { + ['A'] = {0x18,0x24,0x42,0x42,0x7E,0x42,0x42,0x42}, + ['B'] = {0x7C,0x42,0x42,0x7C,0x42,0x42,0x42,0x7C}, + ['C'] = {0x3C,0x42,0x40,0x40,0x40,0x40,0x42,0x3C}, + ['D'] = {0x78,0x44,0x42,0x42,0x42,0x42,0x44,0x78}, + ['E'] = {0x7E,0x40,0x40,0x7C,0x40,0x40,0x40,0x7E}, + ['F'] = {0x7E,0x40,0x40,0x7C,0x40,0x40,0x40,0x40}, + ['G'] = {0x3C,0x42,0x40,0x40,0x4E,0x42,0x42,0x3C}, + ['H'] = {0x42,0x42,0x42,0x7E,0x42,0x42,0x42,0x42}, + ['I'] = {0x3E,0x08,0x08,0x08,0x08,0x08,0x08,0x3E}, + ['J'] = {0x1E,0x04,0x04,0x04,0x04,0x44,0x44,0x38}, + ['K'] = {0x42,0x44,0x48,0x70,0x48,0x44,0x42,0x42}, + ['L'] = {0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x7E}, + ['M'] = {0x42,0x66,0x5A,0x5A,0x42,0x42,0x42,0x42}, + ['N'] = {0x42,0x62,0x52,0x4A,0x46,0x42,0x42,0x42}, + ['O'] = {0x3C,0x42,0x42,0x42,0x42,0x42,0x42,0x3C}, + ['P'] = {0x7C,0x42,0x42,0x7C,0x40,0x40,0x40,0x40}, + ['Q'] = {0x3C,0x42,0x42,0x42,0x42,0x4A,0x44,0x3A}, + ['R'] = {0x7C,0x42,0x42,0x7C,0x48,0x44,0x42,0x42}, + ['S'] = {0x3C,0x42,0x40,0x3C,0x02,0x02,0x42,0x3C}, + ['T'] = {0x7F,0x49,0x08,0x08,0x08,0x08,0x08,0x08}, + ['U'] = {0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x3C}, + ['V'] = {0x42,0x42,0x42,0x42,0x42,0x24,0x24,0x18}, + ['W'] = {0x42,0x42,0x42,0x5A,0x5A,0x5A,0x66,0x42}, + ['X'] = {0x42,0x42,0x24,0x18,0x18,0x24,0x42,0x42}, + ['Y'] = {0x42,0x42,0x24,0x18,0x08,0x08,0x08,0x08}, + ['Z'] = {0x7E,0x02,0x04,0x08,0x10,0x20,0x40,0x7E}, + + ['a'] = {0x00,0x00,0x3C,0x02,0x3E,0x42,0x46,0x3A}, + ['b'] = {0x40,0x40,0x5C,0x62,0x42,0x42,0x62,0x5C}, + ['c'] = {0x00,0x00,0x3C,0x42,0x40,0x40,0x42,0x3C}, + ['d'] = {0x02,0x02,0x3A,0x46,0x42,0x42,0x46,0x3A}, + ['e'] = {0x00,0x00,0x3C,0x42,0x7E,0x40,0x42,0x3C}, + ['f'] = {0x0C,0x12,0x10,0x3C,0x10,0x10,0x10,0x10}, + ['g'] = {0x00,0x00,0x3A,0x46,0x46,0x3E,0x02,0x3C}, + ['h'] = {0x40,0x40,0x5C,0x62,0x42,0x42,0x42,0x42}, + ['i'] = {0x08,0x00,0x18,0x08,0x08,0x08,0x08,0x1C}, + ['j'] = {0x04,0x00,0x0C,0x04,0x04,0x04,0x44,0x38}, + ['k'] = {0x40,0x40,0x44,0x48,0x70,0x48,0x44,0x42}, + ['l'] = {0x18,0x08,0x08,0x08,0x08,0x08,0x08,0x1C}, + ['m'] = {0x00,0x00,0x6C,0x52,0x52,0x42,0x42,0x42}, + ['n'] = {0x00,0x00,0x5C,0x62,0x42,0x42,0x42,0x42}, + ['o'] = {0x00,0x00,0x3C,0x42,0x42,0x42,0x42,0x3C}, + ['p'] = {0x00,0x00,0x5C,0x62,0x42,0x62,0x40,0x40}, + ['q'] = {0x00,0x00,0x3A,0x46,0x42,0x46,0x02,0x02}, + ['r'] = {0x00,0x00,0x5C,0x62,0x40,0x40,0x40,0x40}, + ['s'] = {0x00,0x00,0x3E,0x40,0x3C,0x02,0x42,0x3C}, + ['t'] = {0x10,0x10,0x3C,0x10,0x10,0x12,0x0C,0x00}, + ['u'] = {0x00,0x00,0x42,0x42,0x42,0x42,0x46,0x3A}, + ['v'] = {0x00,0x00,0x42,0x42,0x42,0x24,0x24,0x18}, + ['w'] = {0x00,0x00,0x42,0x42,0x42,0x52,0x52,0x2C}, + ['x'] = {0x00,0x00,0x42,0x24,0x18,0x18,0x24,0x42}, + ['y'] = {0x00,0x00,0x42,0x42,0x46,0x3A,0x02,0x3C}, + ['z'] = {0x00,0x00,0x7E,0x04,0x08,0x10,0x20,0x7E}, +}; + +void drawCharacter( + uint32_t *pixels, + int stride, + int x, + int y, + char c, + uint32_t color) +{ + if (c < 0 || c > 127) return; // not in out font + + const uint8_t *bitmap = s_FontBasic[(int)c]; + for (int row = 0; row < 8; row++) { + uint8_t bits = bitmap[row]; + for (int col = 0; col < 8; col++) { + if (bits & (1 << (7-col))) { + int px = x + col; + int py = y + row; + if (px < stride && py < TITLEBAR_HEIGHT) { + pixels[py*stride + px] = color; + } + } + } + } +} + +void drawText( + uint32_t *pixels, + int stride, + int x, + int y, + const char *text, + uint32_t color) +{ + while (*text) { + drawCharacter(pixels, stride, x, y, *text, color); + x += 8; // advance 8 pixels per character + text++; + } +} + +static PalWindowHandleInfoEx s_WinHandle; + +static void createDecoration() +{ + s_Decoration.surface = wlCompositorCreateSurface(s_Compositor); + if (!s_Decoration.surface) { + palLog(nullptr, "Failed to create wayland surface"); + return; + } + + s_Decoration.subsurface = wlSubcompositorGetSubsurface( + s_Subcompositor, + s_Decoration.surface, + (struct wl_surface*)s_WinHandle.nativeWindow + ); + + if (!s_Decoration.subsurface) { + palLog(nullptr, "Failed to create wayland subsurface"); + return; + } + + // make it soo that the decoration updates independently of the window + wlSubsurfaceSetDesync(s_Decoration.subsurface); + wlSubsurfaceSetPosition(s_Decoration.subsurface, 0, 0); + + // create decoration shm buffer + int width = 640; + s_Decoration.buffer = createShmBuffer( + width, // might be different depending on compositor + TITLEBAR_HEIGHT, + &s_Decoration.fd, + &s_Decoration.size, + &s_Decoration.pixels); + + if (!s_Decoration.buffer) { + palLog(nullptr, "Failed to create wayland buffer"); + return; + } + + // write pixels + // title bar color (dark grey) + fillRect( + s_Decoration.pixels, + width, + 0, + 0, + width, + TITLEBAR_HEIGHT, + 0x002F3030); + + // Close button + // this is just a rectangle for this simple example + // to show CSD works with PAL + fillRect( + s_Decoration.pixels, + width, + width - BUTTON_SIZE, + BUTTON_POSY, + BUTTON_SIZE, + TITLEBAR_HEIGHT / 2, // half of the size of the title bar + 0x00AA3333); + + // Maximize button + fillRect( + s_Decoration.pixels, + width, + width - (BUTTON_OFFSET + BUTTON_SIZE), + BUTTON_POSY, + BUTTON_SIZE, + TITLEBAR_HEIGHT / 2, // half of the size of the title bar + 0x0033AA33); + + // Minimize button + fillRect( + s_Decoration.pixels, + width, + width - (BUTTON_OFFSET * 2 + BUTTON_SIZE), + BUTTON_POSY, + BUTTON_SIZE, + TITLEBAR_HEIGHT / 2, // half of the size of the title bar + 0x0033AAAA); + + wlSurfaceAttach(s_Decoration.surface, s_Decoration.buffer, 0, 0); + wlSurfaceDamageBuffer(s_Decoration.surface, 0, 0, width, TITLEBAR_HEIGHT); + wlSurfaceCommit(s_Decoration.surface); + + // wayland requires the main surface to be committed as well + wlSurfaceCommit(s_WinHandle.nativeWindow); + + // draw window title + // this example does not support unicode characters + int textWidth = strlen(WINDOW_TITLE) * 8; // 8x8 font + int x = (width - textWidth) / 2; + int y = (TITLEBAR_HEIGHT - 8) / 2; + + drawText( + s_Decoration.pixels, + width, + x, + y, + WINDOW_TITLE, + 0x00FFFFFF); + + // set opaque regions for optimazation + // we wont do this in this example + // this does not include any hover effects and any shadows + // and any fancy stuff +} + +static void destroyDecoration() +{ + wlSubsurfaceDestroy(s_Decoration.subsurface); + wlSurfaceDestroy(s_Decoration.surface); + wlBufferDestroy(s_Decoration.buffer); + munmap((void*)s_Decoration.pixels, s_Decoration.size); + close(s_Decoration.fd); +} + +static void PAL_CALL onEvent( + void* userData, + const PalEvent* event) +{ + if (event->type == PAL_EVENT_MOUSE_BUTTONDOWN) { + Uint32 button, serial; + palUnpackUint32(event->data, &button, &serial); + + if (button == PAL_MOUSE_BUTTON_LEFT) { + // check if the mouse is on the title bar + // and move the window + // use the cache mouse position to check if the mouse + // is in the title bar + int x = s_Decoration.mouseX; + int y = s_Decoration.mouseY; + // width should reflect window width + if (x >= 0 && x < 640 && y >= 0 && y < TITLEBAR_HEIGHT) { + xdgToplevelMove(s_WinHandle.nativeHandle2, s_Seat, serial); + + // Optionally check for another click and maximize the window + // maybe set a bool or query mouse button state + // we will skip it for this example + } + + // we skip maximize and minimize button for simplicity + // we just deal with the close button + int buttonX = 640 - BUTTON_SIZE; + if (x >= buttonX && + x < buttonX + BUTTON_SIZE && + y >= BUTTON_POSY && + y < BUTTON_POSY + BUTTON_SIZE) { + // inside close button + // trigger a window close event + + PalEvent event = {0}; + event.data2 = palPackPointer(s_WinHandle.nativeWindow); + event.type = PAL_EVENT_WINDOW_CLOSE; + palPushEvent(s_Decoration.driver, &event); + } + } + + } else if (event->type == PAL_EVENT_MOUSE_MOVE) { + Int32 x, y; + palUnpackInt32(event->data, &x, &y); + s_Decoration.mouseX = x; + s_Decoration.mouseY = y; + + } else if (event->type == PAL_EVENT_MONITOR_DPI_CHANGED) { + palLog(nullptr, "Monitor DPI: %d", event->data); + } +} + +bool customDecorationTest() +{ + palLog(nullptr, ""); + palLog(nullptr, "==========================================="); + palLog(nullptr, "Custom Decoration Test"); + palLog(nullptr, "Press Escape or click close button to close Test"); + + palLog(nullptr, + "This only implements close and window movement for simplicity"); + + palLog(nullptr, "==========================================="); + palLog(nullptr, ""); + + openDisplayWayland(); + if (!s_Display) { + // not on wayland + palLog(nullptr, "Not on wayland platform"); + return false; + } + + PalResult result; + + // create an event driver + PalEventDriver* eventDriver = nullptr; + PalEventDriverCreateInfo eventDriverCreateInfo = {0}; + eventDriverCreateInfo.callback = onEvent; + + 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; + } + + palSetEventDispatchMode( + eventDriver, + PAL_EVENT_WINDOW_CLOSE, + PAL_DISPATCH_POLL); + + palSetEventDispatchMode( + eventDriver, + PAL_EVENT_WINDOW_DECORATION_MODE, + PAL_DISPATCH_POLL); + + palSetEventDispatchMode(eventDriver, PAL_EVENT_KEYDOWN, PAL_DISPATCH_POLL); + + // we use PAL_DISPATCH_CALLBACK for the mouse button to get + // real time events which we then use for moving and resizing + palSetEventDispatchMode( + eventDriver, + PAL_EVENT_MOUSE_BUTTONDOWN, + PAL_DISPATCH_CALLBACK); + + palSetEventDispatchMode( + eventDriver, + PAL_EVENT_MOUSE_MOVE, + PAL_DISPATCH_CALLBACK); + + palSetEventDispatchMode( + eventDriver, + PAL_EVENT_MONITOR_DPI_CHANGED, + PAL_DISPATCH_CALLBACK); + + // tell the video system to use out instance rather + // than creating a new one + palSetPreferredInstance((void*)s_Display); + + // 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; + } + + PalWindow* window = nullptr; + PalWindowCreateInfo createInfo = {0}; + createInfo.height = 480; + createInfo.width = 640; + createInfo.show = true; + createInfo.style = PAL_WINDOW_STYLE_BORDERLESS; + createInfo.title = WINDOW_TITLE; + + 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 native handles + s_WinHandle = palGetWindowHandleInfoEx(window); + s_Decoration.driver = eventDriver; + createDecoration(); + + bool 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; + } + + case PAL_EVENT_KEYDOWN: { + PalKeycode keycode = 0; + palUnpackUint32(event.data, &keycode, nullptr); + if (keycode == PAL_KEYCODE_ESCAPE) { + running = false; + } + break; + } + + case PAL_EVENT_WINDOW_DECORATION_MODE: { + if (event.data == PAL_DECORATION_MODE_CLIENT_SIDE) { + palLog(nullptr, "Window Decoration Mode: Client Side"); + + } else { + palLog(nullptr, "Window Decoration Mode: Server Side"); + } + break; + } + } + } + + } + + // destroy decoration + destroyDecoration(); + + // destroy the window + palDestroyWindow(window); + + // shutdown the video system + palShutdownVideo(); + + // destroy the event driver + palDestroyEventDriver(eventDriver); + + closeDisplayWayland(); + + return true; +} + +#else +#include "tests.h" +bool customDecorationTest() +{ + return false; +} + +#endif // __linux__ \ No newline at end of file diff --git a/tests/icon_test.c b/tests/icon_test.c index b92dc2a..4682926 100644 --- a/tests/icon_test.c +++ b/tests/icon_test.c @@ -7,6 +7,7 @@ bool iconTest() palLog(nullptr, ""); palLog(nullptr, "==========================================="); palLog(nullptr, "Icon Test"); + palLog(nullptr, "Press Escape or click close button to close Test"); palLog(nullptr, "==========================================="); palLog(nullptr, ""); @@ -45,6 +46,15 @@ bool iconTest() return false; } + // check for support + PalVideoFeatures64 features = palGetVideoFeaturesEx(); + if (!(features & PAL_VIDEO_FEATURE64_WINDOW_SET_ICON)) { + palLog(nullptr, "Setting icons feature not supported"); + palDestroyEventDriver(eventDriver); + palShutdownVideo(); + return false; + } + // simple checkerboard RGBA pixel buffer // every block contains 64 pixels Uint8 pixels[32 * 32 * 4]; // size is 32 and we have 4 channles @@ -80,12 +90,12 @@ bool iconTest() } // fill the create info struct - createInfo.monitor = nullptr; // use primary monitor + createInfo.monitor = nullptr; // use default monitor createInfo.height = 480; createInfo.width = 640; createInfo.show = true; createInfo.style = PAL_WINDOW_STYLE_RESIZABLE; - createInfo.title = "PAL Icon Window"; + createInfo.title = "Icon Window"; // create the window with the create info struct result = palCreateWindow(&createInfo, &window); @@ -101,6 +111,8 @@ bool iconTest() PAL_EVENT_WINDOW_CLOSE, PAL_DISPATCH_POLL); // polling + palSetEventDispatchMode(eventDriver, PAL_EVENT_KEYDOWN, PAL_DISPATCH_POLL); + // set the icon result = palSetWindowIcon(window, icon); if (result != PAL_RESULT_SUCCESS) { @@ -121,6 +133,15 @@ bool iconTest() running = false; break; } + + case PAL_EVENT_KEYDOWN: { + PalKeycode keycode = 0; + palUnpackUint32(event.data, &keycode, nullptr); + if (keycode == PAL_KEYCODE_ESCAPE) { + running = false; + } + break; + } } } } diff --git a/tests/input_window_test.c b/tests/input_window_test.c index ef83f9f..0abbee3 100644 --- a/tests/input_window_test.c +++ b/tests/input_window_test.c @@ -2,6 +2,8 @@ #include "pal/pal_video.h" #include "tests.h" +#define DISPATCH_MODE_POLL 0 + static const char* s_KeyNames[PAL_KEYCODE_MAX] = { [PAL_KEYCODE_UNKNOWN] = "Unknown", @@ -270,6 +272,8 @@ static const char* dispatchString = "Poll Mode"; static const char* dispatchString = "Callback Mode"; #endif // DISPATCH_MODE_POLL +static bool s_Running = false; + // inline helpers static inline void onKeydown(const PalEvent* event) { @@ -286,6 +290,10 @@ static inline void onKeydown(const PalEvent* event) dispatchString, keyName, scancodeName); + + if (keycode == PAL_KEYCODE_ESCAPE) { + s_Running = false; + } } static inline void onKeyrepeat(const PalEvent* event) @@ -324,19 +332,23 @@ static inline void onKeyup(const PalEvent* event) static inline void onMouseButtondown(const PalEvent* event) { + Uint32 button, serial; // button == low, serial == high + palUnpackUint32(event->data, &button, &serial); PalWindow* window = palUnpackPointer(event->data2); // get mouse button name - const char* name = s_MouseButtonNames[event->data]; + const char* name = s_MouseButtonNames[button]; palLog(nullptr, "%s: Mouse Button pressed: %s", dispatchString, name); } static inline void onMouseButtonup(const PalEvent* event) { + Uint32 button, serial; // button == low, serial == high + palUnpackUint32(event->data, &button, &serial); PalWindow* window = palUnpackPointer(event->data2); // get mouse button name - const char* name = s_MouseButtonNames[event->data]; + const char* name = s_MouseButtonNames[button]; palLog(nullptr, "%s: Mouse Button released: %s", dispatchString, name); } @@ -360,8 +372,19 @@ static inline void onMouseWheel(const PalEvent* event) { Int32 dx, dy; // dx == low, dy == high palUnpackInt32(event->data, &dx, &dy); + + // get the raw wheel delta (float) + float fdx, fdy; + palGetRawMouseWheelDelta(&fdx, &fdy); + PalWindow* window = palUnpackPointer(event->data2); palLog(nullptr, "%s: Mouse Wheel: (%d, %d)", dispatchString, dx, dy); + palLog( + nullptr, + "%s: Mouse Wheel Raw: (%.2f, %.2f)", + dispatchString, + fdx, + fdy); } static void PAL_CALL onEvent( @@ -399,6 +422,7 @@ bool inputWindowTest() palLog(nullptr, ""); palLog(nullptr, "==========================================="); palLog(nullptr, "Input Window Test"); + palLog(nullptr, "Press Escape or click close button to close Test"); palLog(nullptr, "==========================================="); palLog(nullptr, ""); @@ -436,12 +460,20 @@ bool inputWindowTest() } // fill the create info struct - createInfo.monitor = nullptr; // use primary monitor + createInfo.monitor = nullptr; // use default monitor createInfo.height = 480; createInfo.width = 640; createInfo.show = true; createInfo.style = PAL_WINDOW_STYLE_RESIZABLE; - createInfo.title = "PAL Input Window"; + createInfo.title = "Input Window"; + + // check if we support decorated windows (title bar, close etc) + PalVideoFeatures64 features = palGetVideoFeaturesEx(); + if (!(features & PAL_VIDEO_FEATURE64_DECORATED_WINDOW)) { + // if we dont support, we need to create a borderless window + // and create the decorations ourselves + createInfo.style |= PAL_WINDOW_STYLE_BORDERLESS; + } // create the window with the create info struct result = palCreateWindow(&createInfo, &window); @@ -469,8 +501,8 @@ bool inputWindowTest() palSetEventDispatchMode(eventDriver, e, dispatchMode); } - running = true; - while (running) { + s_Running = true; + while (s_Running) { // update the video system to push video events palUpdateVideo(); @@ -478,7 +510,7 @@ bool inputWindowTest() while (palPollEvent(eventDriver, &event)) { switch (event.type) { case PAL_EVENT_WINDOW_CLOSE: { - running = false; + s_Running = false; break; } diff --git a/tests/monitor_mode_test.c b/tests/monitor_mode_test.c index 61a1bdb..e67db81 100644 --- a/tests/monitor_mode_test.c +++ b/tests/monitor_mode_test.c @@ -121,6 +121,7 @@ bool monitorModeTest() palLog(nullptr, " Size: (%d, %d)", current.width, current.height); palLog(nullptr, " RefreshRate: %d", current.refreshRate); palLog(nullptr, " Bits Per Pixel: %d", current.bpp); + palLog(nullptr, ""); } // shutdown the video system diff --git a/tests/multi_thread_opengl_test.c b/tests/multi_thread_opengl_test.c index b375b53..0950156 100644 --- a/tests/multi_thread_opengl_test.c +++ b/tests/multi_thread_opengl_test.c @@ -37,6 +37,7 @@ typedef void (*glViewportFn)( typedef struct { bool driverCreated; bool running; + bool fixedPipeline; PalEventDriver* videoEventDriver; PalEventDriver* openglEventDriver; PalGLContext* context; @@ -85,6 +86,11 @@ static void* PAL_CALL eventDriverWorker(void* arg) PAL_EVENT_WINDOW_SIZE, PAL_DISPATCH_POLL); + palSetEventDispatchMode( + shared->videoEventDriver, + PAL_EVENT_KEYDOWN, + PAL_DISPATCH_POLL); + // we are done shared->driverCreated = true; return nullptr; @@ -131,7 +137,7 @@ static void* PAL_CALL rendererWorkder(void* arg) // set clear color glViewport(0, 0, 640, 480); - glClearColor(.2f, .2f, .2f, .2f); + glClearColor(.2f, .2f, .2f, 1.0f); // run our while loop over there while (shared->running) { @@ -159,24 +165,26 @@ static void* PAL_CALL rendererWorkder(void* arg) glClear(0x00004000); // GL_COLOR_BUFFER_BIT // draw a triangle using the fixed pipeline - glBegin(0x0004); // GL_TRIANGLES - glColor3f(1.0, 0.0, 0.0); - glVertex2f(-0.5f, -0.5f); + if (shared->fixedPipeline) { + glBegin(0x0004); // GL_TRIANGLES + glColor3f(1.0, 0.0, 0.0); + glVertex2f(-0.5f, -0.5f); - glColor3f(0.0, 1.0, 0.0); - glVertex2f(0.5f, -0.5f); + glColor3f(0.0, 1.0, 0.0); + glVertex2f(0.5f, -0.5f); - glColor3f(0.0, 0.0, 1.0); - glVertex2f(0.0f, 0.5f); + glColor3f(0.0, 0.0, 1.0); + glVertex2f(0.0f, 0.5f); - glEnd(); - glFlush(); + glEnd(); + glFlush(); + } // swap buffers result = palSwapBuffers(&shared->window, shared->context); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); - palLog(nullptr, "Failed to swap buffers: %s", error); + palLog(nullptr, "Failed to swap buffers from: %s", error); return nullptr; } } @@ -189,6 +197,7 @@ bool multiThreadOpenGlTest() palLog(nullptr, ""); palLog(nullptr, "==========================================="); palLog(nullptr, "Multi Thread OpenGL Test"); + palLog(nullptr, "Press Escape or click close button to close Test"); palLog(nullptr, "==========================================="); palLog(nullptr, ""); @@ -213,6 +222,30 @@ bool multiThreadOpenGlTest() return false; } + // check to see if the event driver thread is done creating the drivers + // if not we wait for it + if (!shared->driverCreated) { + // this will be detached automatically when done + palJoinThread(eventDriverThread, nullptr); + + } else { + palDetachThread(eventDriverThread); // we dont need it anymore + } + + // 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, shared->videoEventDriver); + if (result != PAL_RESULT_SUCCESS) { + const char* error = palFormatResult(result); + palLog(nullptr, "Failed to initialize video: %s", error); + return false; + } + + // get the instance or display handle and pass it to the opengl system + // This must be called before the opengl system is initialized + palGLSetInstance(palGetInstance()); + // initialize video and opengl systems // we need to initialize these on the main thread result = palInitGL(nullptr); @@ -268,20 +301,6 @@ bool multiThreadOpenGlTest() const PalGLFBConfig* closest = nullptr; closest = palGetClosestGLFBConfig(fbConfigs, fbCount, &desired); - // check to see if the event driver thread is done creating the drivers - // if not we wait for it - if (!shared->driverCreated) { - palJoinThread(eventDriverThread, nullptr); - } - palDetachThread(eventDriverThread); // we dont need it anymore - - result = palInitVideo(nullptr, shared->videoEventDriver); - if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to initialize video: %s", error); - return false; - } - // tell the video system to use our closest FBConfig // to create the windows result = palSetFBConfig(closest->index, PAL_CONFIG_BACKEND_PAL_OPENGL); @@ -291,6 +310,25 @@ bool multiThreadOpenGlTest() return false; } + // if not using pal_opengl with pal_video + // we get the backend string from the opengl system and + // get the backend from it + // Possible values are `wgl`, `glx`, `gles`, `egl`. + // PalFBConfigBackend backend; + // const char* glBackendString = palGLGetBackend(); + // if (strcmp(glBackendString, "wgl") == 0) { + // backend = PAL_CONFIG_BACKEND_WGL; + + // } else if (strcmp(glBackendString, "glx") == 0) { + // backend = PAL_CONFIG_BACKEND_GLX; + + // } else if (strcmp(glBackendString, "gles") == 0) { + // backend = PAL_CONFIG_BACKEND_GLES; + + // } else if (strcmp(glBackendString, "egl") == 0) { + // backend = PAL_CONFIG_BACKEND_EGL; + // } + // all windows also needs to be created on the main thread PalWindow* window = nullptr; PalWindowCreateInfo windowCreateInfo = {0}; @@ -299,6 +337,15 @@ bool multiThreadOpenGlTest() windowCreateInfo.show = true; windowCreateInfo.style = PAL_WINDOW_STYLE_RESIZABLE; windowCreateInfo.title = "Multi Thread OpenGL Window"; + + // check if we support decorated windows (title bar, close etc) + PalVideoFeatures64 features = palGetVideoFeaturesEx(); + if (!(features & PAL_VIDEO_FEATURE64_DECORATED_WINDOW)) { + // if we dont support, we need to create a borderless window + // and create the decorations ourselves + windowCreateInfo.style |= PAL_WINDOW_STYLE_BORDERLESS; + } + result = palCreateWindow(&windowCreateInfo, &window); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); @@ -310,10 +357,19 @@ bool multiThreadOpenGlTest() const PalGLInfo* glInfo = palGetGLInfo(); // get the native handles of our created window - PalWindowHandleInfo windowHandleInfo; - windowHandleInfo = palGetWindowHandleInfo(window); - shared->window.display = windowHandleInfo.nativeDisplay; - shared->window.window = windowHandleInfo.nativeWindow; + PalWindowHandleInfoEx winHandle = {0}; + winHandle = palGetWindowHandleInfoEx(window); + + shared->window.display = winHandle.nativeDisplay; + + // On Wayland the window is the wl_egl_window + if (winHandle.nativeHandle3) { + // the window has a valid wl_egl_window + shared->window.window = winHandle.nativeHandle3; + + } else { + shared->window.window = winHandle.nativeWindow; + } PalGLContextCreateInfo contextCreateInfo = {0}; contextCreateInfo.debug = true; @@ -325,8 +381,14 @@ bool multiThreadOpenGlTest() // we dont want to get into GL pipeline for this example // so we request a Compatibility profile if supported // NOTE: is its not supported, no triangle would be displayed + shared->fixedPipeline = false; if (glInfo->extensions & PAL_GL_EXTENSION_CONTEXT_PROFILE) { contextCreateInfo.profile = PAL_GL_PROFILE_COMPATIBILITY; + shared->fixedPipeline = true; + } + + if (!shared->fixedPipeline) { + palLog(nullptr, "Fixed pipeline not supported"); } result = palCreateGLContext(&contextCreateInfo, &shared->context); @@ -366,6 +428,15 @@ bool multiThreadOpenGlTest() break; } + case PAL_EVENT_KEYDOWN: { + PalKeycode keycode = 0; + palUnpackUint32(event.data, &keycode, nullptr); + if (keycode == PAL_KEYCODE_ESCAPE) { + shared->running = false; + } + break; + } + case PAL_EVENT_WINDOW_SIZE: { // tell the opengl driver about our size change palPushEvent(shared->openglEventDriver, &event); @@ -375,11 +446,16 @@ bool multiThreadOpenGlTest() } } + // we wait for the render thread to finish with + // the current frame and destroy the context + palJoinThread(rendererThread, nullptr); + + palDestroyGLContext(shared->context); + palShutdownGL(); + // shutdown video and opengl systems // we need to shutdown these on the main thread palDestroyWindow(window); - palDestroyGLContext(shared->context); - palShutdownGL(); palShutdownVideo(); // The event drivers cn be destroyed on a seperate thread @@ -388,8 +464,5 @@ bool multiThreadOpenGlTest() palDestroyEventDriver(shared->openglEventDriver); palFree(nullptr, fbConfigs); - // destroy the renderer thread - palDetachThread(rendererThread); - return true; } \ No newline at end of file diff --git a/tests/mutex_test.c b/tests/mutex_test.c index 1b7b955..048d8aa 100644 --- a/tests/mutex_test.c +++ b/tests/mutex_test.c @@ -72,14 +72,11 @@ bool mutexTest() } // join the threads to main thread + // joint threads does not need to be detached for (Int32 i = 0; i < THREAD_COUNT; i++) { palJoinThread(threads[i], nullptr); } - for (Int32 i = 0; i < THREAD_COUNT; i++) { - palDetachThread(threads[i]); - } - palDestroyMutex(data->mutex); palLog(nullptr, "Expected Counter: %d", MAX_COUNTER * THREAD_COUNT); diff --git a/tests/native_instance_test.c b/tests/native_instance_test.c new file mode 100644 index 0000000..dd4f2be --- /dev/null +++ b/tests/native_instance_test.c @@ -0,0 +1,428 @@ + +#include "pal/pal_video.h" +#include "tests.h" + +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif // WIN32_LEAN_AND_MEAN + +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX + +// set unicode +#ifndef UNICODE +#define UNICODE +#endif // UNICODE + +#include + +#elif defined(__linux__) +#include +#include +#include + +typedef Display* (*XOpenDisplayFn)(const char*); +typedef int (*XCloseDisplayFn)(Display*); + +static void* s_LibX; +static XOpenDisplayFn s_XOpenDisplay; +static XCloseDisplayFn s_XCloseDisplay; + +struct wl_display; +struct wl_registry; +struct wl_proxy; +struct wl_interface; + +typedef struct wl_display* (*wl_display_connect_fn)(const char*); +typedef void (*wl_display_disconnect_fn)(struct wl_display*); +typedef uint32_t (*wl_proxy_get_version_fn)(struct wl_proxy*); +typedef int (*wl_display_roundtrip_fn)(struct wl_display*); +typedef void (*wl_proxy_destroy_fn)(struct wl_proxy*); + +typedef struct wl_proxy* (*wl_proxy_marshal_flags_fn)( + struct wl_proxy*, + uint32_t, + const struct wl_interface*, + uint32_t, + uint32_t, + ...); + +typedef int (*wl_proxy_add_listener_fn)( + struct wl_proxy*, + void (**)(void), + void*); + +struct wl_interface { + const char* name; + int version; + int method_count; + const struct wl_message* methods; + int event_count; + const struct wl_message* events; +}; + +struct wl_registry_listener { + void (*global)( + void*, + struct wl_registry*, + uint32_t, + const char*, + uint32_t); + + void (*global_remove)( + void*, + struct wl_registry*, + uint32_t); +}; + +static void* s_LibWayland; +static wl_display_connect_fn s_wl_display_connect; +static wl_display_disconnect_fn s_wl_display_disconnect; +static wl_display_roundtrip_fn s_wl_display_roundtrip; +static wl_proxy_get_version_fn s_wl_proxy_get_version; +static wl_proxy_marshal_flags_fn s_wl_proxy_marshal_flags; +static wl_proxy_add_listener_fn s_wl_proxy_add_listener; +static wl_proxy_destroy_fn s_wl_proxy_destroy; +static struct wl_registry* s_Registry; + +static const struct wl_interface* registryInterface; + +static inline void* wlRegistryBind( + struct wl_registry* wl_registry, + uint32_t name, + const struct wl_interface* interface, + uint32_t version) +{ + struct wl_proxy* id; + id = s_wl_proxy_marshal_flags( + (struct wl_proxy*)wl_registry, + 0, // WL_REGISTRY_BIND + interface, + version, + 0, + name, + interface->name, + version, + NULL); + + return (void*)id; +} + +static inline int wlRegistryAddListener( + struct wl_registry* wl_registry, + const struct wl_registry_listener* listener, + void* data) +{ + return s_wl_proxy_add_listener( + (struct wl_proxy*)wl_registry, + (void (**)(void))listener, + data); +} + +static inline struct wl_registry* +wlDisplayGetRegistry(struct wl_display* wl_display) +{ + struct wl_proxy* registry; + registry = s_wl_proxy_marshal_flags( + (struct wl_proxy*)wl_display, + 1, // WL_DISPLAY_GET_REGISTRY + registryInterface, + s_wl_proxy_get_version((struct wl_proxy*)wl_display), + 0, + NULL); + + return (struct wl_registry*)registry; +} + +static bool s_Logged = false; +static void globalHandle( + void* data, + struct wl_registry* registry, + uint32_t name, + const char* interface, + uint32_t version) +{ + if (!s_Logged) { + palLog(nullptr, "Registry global handle working"); + s_Logged = true; + } +} + +static void globalRemove( + void* data, + struct wl_registry* registry, + uint32_t name) +{ + if (s_Logged) { + palLog(nullptr, "Registry global remove working"); + s_Logged = false; + } +} + +static const struct wl_registry_listener s_RegistryListener = { + .global = globalHandle, + .global_remove = globalRemove}; + +static bool s_OnWayland = false; + +#endif // _WIN32 + +void* openDisplayX11() +{ +#ifdef __linux__ + // load the procs + s_LibX = dlopen("libX11.so", RTLD_LAZY); + if (!s_LibX) { + return nullptr; + } + + // clang-format off + s_XOpenDisplay = (XOpenDisplayFn)dlsym( + s_LibX, + "XOpenDisplay"); + + s_XCloseDisplay = (XCloseDisplayFn)dlsym( + s_LibX, + "XCloseDisplay"); + + return s_XOpenDisplay(nullptr); +#endif // __linux__ +} + +void closeDisplayX11(void* instance) +{ +#ifdef __linux__ + s_XCloseDisplay((Display*)instance); + dlclose(s_LibX); +#endif // __linux__ +} + +void* openDisplayWayland() +{ +#ifdef __linux__ + // load the procs + s_LibWayland = dlopen("libwayland-client.so.0", RTLD_LAZY); + if (!s_LibWayland) { + return nullptr; + } + + // clang-format off + s_wl_display_connect = (wl_display_connect_fn)dlsym( + s_LibWayland, + "wl_display_connect"); + + s_wl_display_disconnect = (wl_display_disconnect_fn)dlsym( + s_LibWayland, + "wl_display_disconnect"); + + s_wl_display_roundtrip = (wl_display_roundtrip_fn)dlsym( + s_LibWayland, + "wl_display_roundtrip"); + + s_wl_proxy_marshal_flags = (wl_proxy_marshal_flags_fn)dlsym( + s_LibWayland, + "wl_proxy_marshal_flags"); + + s_wl_proxy_get_version = (wl_proxy_get_version_fn)dlsym( + s_LibWayland, + "wl_proxy_get_version"); + + s_wl_proxy_add_listener = (wl_proxy_add_listener_fn)dlsym( + s_LibWayland, + "wl_proxy_add_listener"); + + s_wl_proxy_destroy = (wl_proxy_destroy_fn)dlsym( + s_LibWayland, + "wl_proxy_destroy"); + + registryInterface = dlsym(s_LibWayland, "wl_registry_interface"); + + struct wl_display* display = s_wl_display_connect(nullptr); + if (display) { + s_Registry = wlDisplayGetRegistry(display); + wlRegistryAddListener(s_Registry, &s_RegistryListener, nullptr); + s_wl_display_roundtrip(display); + } + + return display; +#endif // __linux__ +} + +void closeDisplayWayland(void* instance) +{ +#ifdef __linux__ + s_wl_proxy_destroy((struct wl_proxy*)s_Registry); + s_wl_display_disconnect((struct wl_display*)instance); + dlclose(s_LibWayland); +#endif // __linux__ +} + +void* openDisplayWin32() +{ +#ifdef __WIN32 + return GetModuleHandleW(nullptr); +#endif // __WIN32 +} + +void closeDisplayWin32(void* instance) +{ + // this does nothing +} + +void* openInstance() +{ +#ifdef _WIN32 + return openDisplayWin32(); +#elif defined(__linux__) + // get the active session + const char* session = getenv("XDG_SESSION_TYPE"); + if (session) { + if (strcmp(session, "wayland") == 0) { + s_OnWayland = true; + } else { + s_OnWayland = false; + } + } + + if (s_OnWayland) { + return openDisplayWayland(); + } else { + return openDisplayX11(); + } +#endif // _WIN32 +} + +void closeInstance(void* instance) +{ +#ifdef _WIN32 + closeDisplayWin32(instance); +#elif defined(__linux__) + if (s_OnWayland) { + closeDisplayWayland(instance); + } else { + closeDisplayX11(instance); + } +#endif // _WIN32 +} + +bool nativeInstanceTest() +{ + palLog(nullptr, ""); + palLog(nullptr, "==========================================="); + palLog(nullptr, "Native Instance Test"); + palLog(nullptr, "Press Escape or click close button to close Test"); + palLog(nullptr, "==========================================="); + palLog(nullptr, ""); + + PalResult result; + + // event driver + PalEventDriver* eventDriver = nullptr; + PalEventDriverCreateInfo eventDriverCreateInfo = {0}; + + // 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; + } + + // open our own display or instance + void* instance = openInstance(); + if (!instance) { + palLog(nullptr, "Failed to open instance"); + return false; + } + + // tell the video system to use out instance rather + // than creating a new one + // this can be set to the opengl system as well + palSetPreferredInstance(instance); + + // 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; + } + + PalWindow* window = nullptr; + PalWindowCreateInfo createInfo = {0}; + createInfo.monitor = nullptr; // use default monitor + createInfo.height = 480; + createInfo.width = 640; + createInfo.show = true; + createInfo.style = PAL_WINDOW_STYLE_RESIZABLE; + createInfo.title = "Native Instance Test"; + + // check if we support decorated windows (title bar, close etc) + PalVideoFeatures64 features = palGetVideoFeaturesEx(); + if (!(features & PAL_VIDEO_FEATURE64_DECORATED_WINDOW)) { + // if we dont support, we need to create a borderless window + // and create the decorations ourselves + createInfo.style |= PAL_WINDOW_STYLE_BORDERLESS; + } + + 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); + + palSetEventDispatchMode( + eventDriver, + PAL_EVENT_KEYDOWN, + PAL_DISPATCH_POLL); + + bool 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; + } + + case PAL_EVENT_KEYDOWN: { + PalKeycode keycode = 0; + palUnpackUint32(event.data, &keycode, nullptr); + if (keycode == PAL_KEYCODE_ESCAPE) { + running = false; + } + break; + } + } + } + } + + // destroy the window + palDestroyWindow(window); + + // shutdown the video system + palShutdownVideo(); + + // destroy the event driver + palDestroyEventDriver(eventDriver); + + // the video system does not destroy or close the handle provided + closeInstance(instance); + + return true; +} \ No newline at end of file diff --git a/tests/native_integration_test.c b/tests/native_integration_test.c new file mode 100644 index 0000000..80eabd7 --- /dev/null +++ b/tests/native_integration_test.c @@ -0,0 +1,446 @@ + +#include "pal/pal_video.h" +#include "tests.h" + +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif // WIN32_LEAN_AND_MEAN + +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX + +// set unicode +#ifndef UNICODE +#define UNICODE +#endif // UNICODE + +#include + +#elif defined(__linux__) +#include +#include +#include +#include +#include + +// X11 typedefs +typedef Atom (*XInternAtomFn)( + Display*, + _Xconst char*, + Bool); + +typedef int (*XChangePropertyFn)( + Display*, + Window, + Atom, + Atom, + int, + int, + _Xconst unsigned char*, + int); + +typedef Status (*XGetWMNameFn)( + Display*, + Window, + XTextProperty*); + +typedef int (*XStoreNameFn)( + Display*, + Window, + _Xconst char*); + +typedef int (*XGetWindowPropertyFn)( + Display*, + Window, + Atom, + long, + long, + Bool, + Atom, + Atom*, + int*, + unsigned long*, + unsigned long*, + unsigned char**); + +typedef int (*XFlushFn)(Display*); +typedef int (*XFreeFn)(void*); + +// Wayland typedefs +struct wl_display; +struct wl_interface; +struct xdg_toplevel; + +typedef struct wl_proxy* (*wl_proxy_marshal_flags_fn)( + struct wl_proxy*, + uint32_t, + const struct wl_interface*, + uint32_t, + uint32_t, + ...); + +typedef uint32_t (*wl_proxy_get_version_fn)(struct wl_proxy*); +typedef int (*wl_display_flush_fn)(struct wl_display*); + +static wl_proxy_marshal_flags_fn s_wl_proxy_marshal_flags; +static wl_proxy_get_version_fn s_wl_proxy_get_version; +static wl_display_flush_fn s_wl_display_flush; + +static inline void xdgToplevelSetTitle( + struct xdg_toplevel* xdg_toplevel, + const char* title) +{ + s_wl_proxy_marshal_flags( + (struct wl_proxy*)xdg_toplevel, + 2, // XDG_TOPLEVEL_SET_TITLE + NULL, + s_wl_proxy_get_version((struct wl_proxy*)xdg_toplevel), + 0, + title); +} + +static XInternAtomFn s_XInternAtom; +static XChangePropertyFn s_XChangeProperty; +static XGetWMNameFn s_XGetWMName; +static XStoreNameFn s_XStoreName; +static XGetWindowPropertyFn s_XGetWindowProperty; +static XFlushFn s_XFlush; +static XFreeFn s_XFree; + +static Atom s_NET_WM_NAME; +static Atom s_UTF8_STRING; + +static bool s_OnWayland = false; +static void* s_X11Lib; + +static void* s_WaylandLib; + +#endif // _WIN32 + +static char s_TitleBuffer[32]; + +void setWindowTitleX11(PalWindowHandleInfoEx* windowInfo) +{ +#ifdef __linux__ + // load the procs + s_X11Lib = dlopen("libX11.so", RTLD_LAZY); + if (!s_X11Lib) { + return; + } + + // clang-format off + s_XInternAtom = (XInternAtomFn)dlsym( + s_X11Lib, + "XInternAtom"); + + s_XChangeProperty = (XChangePropertyFn)dlsym( + s_X11Lib, + "XChangeProperty"); + + s_XGetWMName = (XGetWMNameFn)dlsym( + s_X11Lib, + "XGetWMName"); + + s_XStoreName = (XStoreNameFn)dlsym( + s_X11Lib, + "XStoreName"); + + s_XGetWindowProperty = (XGetWindowPropertyFn)dlsym( + s_X11Lib, + "XGetWindowProperty"); + + s_XFlush = (XFlushFn)dlsym( + s_X11Lib, + "XFlush"); + + s_XFree = (XFreeFn)dlsym( + s_X11Lib, + "XFree"); + + // clang-format on + + Display* display = (Display*)windowInfo->nativeDisplay; + Window window = (Window)(UintPtr)windowInfo->nativeWindow; + + s_NET_WM_NAME = s_XInternAtom(display, "_NET_WM_NAME", False); + s_UTF8_STRING = s_XInternAtom(display, "UTF8_STRING", False); + + const char* title = "Hello from native X11 API"; + if (s_NET_WM_NAME) { + s_XChangeProperty( + display, + window, + s_NET_WM_NAME, + s_UTF8_STRING, + 8, // unsigned char + PropModeReplace, + title, + strlen(title)); + + } else { + s_XStoreName(display, window, title); + } + + s_XFlush(display); +#endif // __linux__ +} + +void getWindowTitleX11(PalWindowHandleInfoEx* windowInfo) +{ +#ifdef __linux__ + Display* display = (Display*)windowInfo->nativeDisplay; + Window window = (Window)(UintPtr)windowInfo->nativeWindow; + + if (s_NET_WM_NAME) { + Atom type; + int format; + unsigned long count, bytesAfter; + unsigned char* prop = nullptr; + s_XGetWindowProperty( + display, + window, + s_NET_WM_NAME, + 0, + (~0L), + False, + s_UTF8_STRING, + &type, + &format, + &count, + &bytesAfter, + &prop); + + strcpy(s_TitleBuffer, (const char*)prop); + s_XFree(prop); + + } else { + XTextProperty text; + s_XGetWMName(display, window, &text); + strcpy(s_TitleBuffer, (const char*)text.value); + s_XFree(text.value); + } + + // free Xlib since we loaded dynamically + dlclose(s_X11Lib); + +#endif // __linux__ +} + +void setWindowTitleWayland(PalWindowHandleInfoEx* windowInfo) +{ +#ifdef __linux__ + s_WaylandLib = dlopen("libwayland-client.so.0", RTLD_LAZY); + if (!s_WaylandLib) { + return; + } + + s_wl_proxy_marshal_flags = (wl_proxy_marshal_flags_fn)dlsym( + s_WaylandLib, + "wl_proxy_marshal_flags"); + + s_wl_proxy_get_version = + (wl_proxy_get_version_fn)dlsym(s_WaylandLib, "wl_proxy_get_version"); + + s_wl_display_flush = + (wl_display_flush_fn)dlsym(s_WaylandLib, "wl_display_flush"); + + struct xdg_toplevel* toplevel = nullptr; + struct wl_display* display = nullptr; + display = (struct wl_display*)windowInfo->nativeDisplay; + toplevel = (struct xdg_toplevel*)windowInfo->nativeHandle2; + + xdgToplevelSetTitle(toplevel, "Hello from native Wayland API"); + s_wl_display_flush(display); + +#endif // __linux__ +} + +void getWindowTitleWayland(PalWindowHandleInfoEx* windowInfo) +{ +#ifdef __linux__ + // wayland does not support getting window title + // so we just return the title we set through wayland + dlclose(s_WaylandLib); +#endif // __linux__ +} + +void setWindowTitleWin32(PalWindowHandleInfoEx* windowInfo) +{ +#ifdef _WIN32 + const char* title = "Hello from native Win32 API"; + SetWindowTextA((HWND)windowInfo->nativeWindow, title); +#endif // _WIN32 +} + +void getWindowTitleWin32(PalWindowHandleInfoEx* windowInfo) +{ +#ifdef _WIN32 + GetWindowTextA( + (HWND)windowInfo->nativeWindow, + s_TitleBuffer, + sizeof(s_TitleBuffer)); +#endif // _WIN32 +} + +void setWindowTitle(PalWindowHandleInfoEx* windowInfo) +{ +#ifdef _WIN32 + setWindowTitleWin32(windowInfo); +#elif defined(__linux__) + // get the active session + const char* session = getenv("XDG_SESSION_TYPE"); + if (session) { + if (strcmp(session, "wayland") == 0) { + s_OnWayland = true; + } else { + s_OnWayland = false; + } + } + + if (s_OnWayland) { + setWindowTitleWayland(windowInfo); + } else { + setWindowTitleX11(windowInfo); + } +#endif // _WIN32 +} + +void getWindowTitle(PalWindowHandleInfoEx* windowInfo) +{ +#ifdef _WIN32 + getWindowTitleWin32(windowInfo); +#elif defined(__linux__) + if (s_OnWayland) { + getWindowTitleWayland(windowInfo); + } else { + getWindowTitleX11(windowInfo); + } +#endif // _WIN32 +} + +bool nativeIntegrationTest() +{ + palLog(nullptr, ""); + palLog(nullptr, "==========================================="); + palLog(nullptr, "Native Integration Test"); + palLog(nullptr, "Press Escape or click close button to close Test"); + palLog(nullptr, "==========================================="); + palLog(nullptr, ""); + + PalResult result; + + // event driver + PalEventDriver* eventDriver = nullptr; + PalEventDriverCreateInfo eventDriverCreateInfo = {0}; + + // 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 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; + } + + PalWindow* window = nullptr; + PalWindowCreateInfo createInfo = {0}; + createInfo.monitor = nullptr; // use default monitor + createInfo.height = 480; + createInfo.width = 640; + createInfo.show = true; + createInfo.style = PAL_WINDOW_STYLE_RESIZABLE; + createInfo.title = "Native Integration Test"; + + // check if we support decorated windows (title bar, close etc) + PalVideoFeatures64 features = palGetVideoFeaturesEx(); + if (!(features & PAL_VIDEO_FEATURE64_DECORATED_WINDOW)) { + // if we dont support, we need to create a borderless window + // and create the decorations ourselves + createInfo.style |= PAL_WINDOW_STYLE_BORDERLESS; + } + + 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); + + palSetEventDispatchMode(eventDriver, PAL_EVENT_KEYDOWN, PAL_DISPATCH_POLL); + + // set the window title using native APIs + PalWindowHandleInfoEx windowInfo = {0}; + windowInfo = palGetWindowHandleInfoEx(window); + + palLog(nullptr, "Window title: %s", createInfo.title); + palLog(nullptr, "Setting window title with native API"); + setWindowTitle(&windowInfo); + + palLog(nullptr, "Getting window title with PAL API"); + palGetWindowTitle(window, sizeof(s_TitleBuffer), nullptr, s_TitleBuffer); + palLog(nullptr, "Window title: %s", s_TitleBuffer); + + // set the title with PAL and retreive it with the native API + palLog(nullptr, "Setting window title with PAL API"); + palSetWindowTitle(window, "Hello from PAL API"); + + palLog(nullptr, "Getting window title with native API"); + getWindowTitle(&windowInfo); + palLog(nullptr, "Window title: %s", s_TitleBuffer); + + // using native API like wl_proxy_set_user_data, XContext and + // SetWindowLongPtr(GWLP_USERDATA) can be used freely + + bool 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; + } + + case PAL_EVENT_KEYDOWN: { + PalKeycode keycode = 0; + palUnpackUint32(event.data, &keycode, nullptr); + if (keycode == PAL_KEYCODE_ESCAPE) { + running = false; + } + break; + } + } + } + } + + // destroy the window + palDestroyWindow(window); + + // shutdown the video system + palShutdownVideo(); + + // destroy the event driver + palDestroyEventDriver(eventDriver); + + return true; +} \ No newline at end of file diff --git a/tests/opengl_context_test.c b/tests/opengl_context_test.c index 8d0e568..5a1491f 100644 --- a/tests/opengl_context_test.c +++ b/tests/opengl_context_test.c @@ -20,25 +20,11 @@ bool openglContextTest() palLog(nullptr, ""); palLog(nullptr, "==========================================="); palLog(nullptr, "Opengl Context Test"); + palLog(nullptr, "Press Escape or click close button to close Test"); palLog(nullptr, "==========================================="); palLog(nullptr, ""); - // initialize the opengl system - PalResult result = palInitGL(nullptr); - if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to initialize opengl: %s", error); - return false; - } - - PalWindow* window = nullptr; - PalGLContext* context = nullptr; - PalWindowCreateInfo createInfo = {0}; - PalGLContextCreateInfo contextCreateInfo = {0}; - Int32 fbCount = 0; - bool running = false; - - // event driver + PalResult result; PalEventDriver* eventDriver = nullptr; PalEventDriverCreateInfo eventDriverCreateInfo = {0}; @@ -56,6 +42,35 @@ bool openglContextTest() 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; + } + + // get the instance or display handle and pass it to the opengl system + // This must be called before the opengl system is initialized + palGLSetInstance(palGetInstance()); + + // initialize the opengl system + result = palInitGL(nullptr); + if (result != PAL_RESULT_SUCCESS) { + const char* error = palFormatResult(result); + palLog(nullptr, "Failed to initialize opengl: %s", error); + return false; + } + + PalWindow* window = nullptr; + PalGLContext* context = nullptr; + PalWindowCreateInfo createInfo = {0}; + PalGLContextCreateInfo contextCreateInfo = {0}; + Int32 fbCount = 0; + bool running = false; + // enumerate supported opengl framebuffer configs // glWindow must be nullptr result = palEnumerateGLFBConfigs(nullptr, &fbCount, nullptr); @@ -123,16 +138,6 @@ 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. @@ -148,12 +153,39 @@ bool openglContextTest() return false; } - createInfo.monitor = nullptr; // use primary monitor + // if not using pal_opengl with pal_video + // we get the backend string from the opengl system and + // get the backend from it + // Possible values are `wgl`, `glx`, `gles`, `egl`. + // PalFBConfigBackend backend; + // const char* glBackendString = palGLGetBackend(); + // if (strcmp(glBackendString, "wgl") == 0) { + // backend = PAL_CONFIG_BACKEND_WGL; + + // } else if (strcmp(glBackendString, "glx") == 0) { + // backend = PAL_CONFIG_BACKEND_GLX; + + // } else if (strcmp(glBackendString, "gles") == 0) { + // backend = PAL_CONFIG_BACKEND_GLES; + + // } else if (strcmp(glBackendString, "egl") == 0) { + // backend = PAL_CONFIG_BACKEND_EGL; + // } + + createInfo.monitor = nullptr; // use default monitor createInfo.height = 480; createInfo.width = 640; createInfo.show = true; createInfo.style = PAL_WINDOW_STYLE_RESIZABLE; - createInfo.title = "Pal Opengl Context Window"; + createInfo.title = "Opengl Context Window"; + + // check if we support decorated windows (title bar, close etc) + PalVideoFeatures64 features = palGetVideoFeaturesEx(); + if (!(features & PAL_VIDEO_FEATURE64_DECORATED_WINDOW)) { + // if we dont support, we need to create a borderless window + // and create the decorations ourselves + createInfo.style |= PAL_WINDOW_STYLE_BORDERLESS; + } // create the window with the create info struct result = palCreateWindow(&createInfo, &window); @@ -169,17 +201,26 @@ bool openglContextTest() PAL_EVENT_WINDOW_CLOSE, PAL_DISPATCH_POLL); + palSetEventDispatchMode(eventDriver, PAL_EVENT_KEYDOWN, 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); + PalWindowHandleInfoEx winHandle = {0}; + winHandle = palGetWindowHandleInfoEx(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; + glWindow.display = winHandle.nativeDisplay; + + // On Wayland the window is the wl_egl_window + if (winHandle.nativeHandle3) { + // the window has a valid wl_egl_window + glWindow.window = winHandle.nativeHandle3; + + } else { + glWindow.window = winHandle.nativeWindow; + } // get opengl info const PalGLInfo* info = palGetGLInfo(); @@ -238,7 +279,7 @@ bool openglContextTest() glClear = (PFNGLCLEARPROC)palGLGetProcAddress("glClear"); // set clear color - glClearColor(.2f, .2f, .2f, .2f); + glClearColor(0.2f, 0.2f, 0.2f, 1.0f); running = true; while (running) { @@ -252,6 +293,15 @@ bool openglContextTest() running = false; break; } + + case PAL_EVENT_KEYDOWN: { + PalKeycode keycode = 0; + palUnpackUint32(event.data, &keycode, nullptr); + if (keycode == PAL_KEYCODE_ESCAPE) { + running = false; + } + break; + } } } @@ -268,6 +318,12 @@ bool openglContextTest() } } + // destroy the opengl context + palDestroyGLContext(context); + + // shutdown the opengl system + palShutdownGL(); + // destroy the window palDestroyWindow(window); @@ -277,12 +333,6 @@ bool openglContextTest() // destroy the event driver palDestroyEventDriver(eventDriver); - // destroy the opengl context - palDestroyGLContext(context); - - // shutdown the opengl system - palShutdownGL(); - // free the framebuffer configs palFree(nullptr, fbConfigs); diff --git a/tests/opengl_fbconfig_test.c b/tests/opengl_fbconfig_test.c index 407daee..ebd14ff 100644 --- a/tests/opengl_fbconfig_test.c +++ b/tests/opengl_fbconfig_test.c @@ -13,8 +13,20 @@ bool openglFBConfigTest() palLog(nullptr, "==========================================="); palLog(nullptr, ""); + // initialize the video system and create a window + PalResult result = palInitVideo(nullptr, nullptr); + if (result != PAL_RESULT_SUCCESS) { + const char* error = palFormatResult(result); + palLog(nullptr, "Failed to initialize video: %s", error); + return false; + } + + // get the instance or display handle and pass it to the opengl system + // This must be called before the opengl system is initialized + palGLSetInstance(palGetInstance()); + // initialize the opengl system - PalResult result = palInitGL(nullptr); + result = palInitGL(nullptr); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); palLog(nullptr, "Failed to initialize opengl: %s", error); @@ -22,7 +34,6 @@ bool openglFBConfigTest() } // enumerate supported opengl framebuffer configs - // glWindow must be nullptr for default Int32 fbCount = 0; result = palEnumerateGLFBConfigs(nullptr, &fbCount, nullptr); if (result != PAL_RESULT_SUCCESS) { @@ -127,6 +138,9 @@ bool openglFBConfigTest() // shutdown the opengl system palShutdownGL(); + // shutdown the video system + palShutdownVideo(); + // free the framebuffer configs palFree(nullptr, fbConfigs); diff --git a/tests/opengl_multi_context_test.c b/tests/opengl_multi_context_test.c index f69d31c..70c3cd1 100644 --- a/tests/opengl_multi_context_test.c +++ b/tests/opengl_multi_context_test.c @@ -20,25 +20,11 @@ bool openglMultiContextTest() palLog(nullptr, ""); palLog(nullptr, "==========================================="); palLog(nullptr, "Opengl Multi Context Test"); + palLog(nullptr, "Press Escape or click close button to close Test"); palLog(nullptr, "==========================================="); palLog(nullptr, ""); - // initialize the opengl system - PalResult result = palInitGL(nullptr); - if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to initialize opengl: %s", error); - return false; - } - - PalWindow* window = nullptr; - PalGLContext* context = nullptr; - PalWindowCreateInfo createInfo = {0}; - PalGLContextCreateInfo contextCreateInfo = {0}; - Int32 fbCount = 0; - bool running = false; - - // event driver + PalResult result; PalEventDriver* eventDriver = nullptr; PalEventDriverCreateInfo eventDriverCreateInfo = {0}; @@ -56,6 +42,35 @@ bool openglMultiContextTest() 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; + } + + // get the instance or display handle and pass it to the opengl system + // This must be called before the opengl system is initialized + palGLSetInstance(palGetInstance()); + + // initialize the opengl system + result = palInitGL(nullptr); + if (result != PAL_RESULT_SUCCESS) { + const char* error = palFormatResult(result); + palLog(nullptr, "Failed to initialize opengl: %s", error); + return false; + } + + PalWindow* window = nullptr; + PalGLContext* context = nullptr; + PalWindowCreateInfo createInfo = {0}; + PalGLContextCreateInfo contextCreateInfo = {0}; + Int32 fbCount = 0; + bool running = false; + // enumerate supported opengl framebuffer configs // glWindow must be nullptr result = palEnumerateGLFBConfigs(nullptr, &fbCount, nullptr); @@ -123,16 +138,6 @@ 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. @@ -144,16 +149,43 @@ bool openglMultiContextTest() 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); + palLog(nullptr, "Failed to set GL FBConfig: %s", error); return false; } - createInfo.monitor = nullptr; // use primary monitor + // if not using pal_opengl with pal_video + // we get the backend string from the opengl system and + // get the backend from it + // Possible values are `wgl`, `glx`, `gles`, `egl`. + // PalFBConfigBackend backend; + // const char* glBackendString = palGLGetBackend(); + // if (strcmp(glBackendString, "wgl") == 0) { + // backend = PAL_CONFIG_BACKEND_WGL; + + // } else if (strcmp(glBackendString, "glx") == 0) { + // backend = PAL_CONFIG_BACKEND_GLX; + + // } else if (strcmp(glBackendString, "gles") == 0) { + // backend = PAL_CONFIG_BACKEND_GLES; + + // } else if (strcmp(glBackendString, "egl") == 0) { + // backend = PAL_CONFIG_BACKEND_EGL; + // } + + createInfo.monitor = nullptr; // use default monitor createInfo.height = 480; createInfo.width = 640; createInfo.show = true; createInfo.style = PAL_WINDOW_STYLE_RESIZABLE; - createInfo.title = "Pal Opengl Multi Context Window"; + createInfo.title = "Opengl Multi Context Window"; + + // check if we support decorated windows (title bar, close etc) + PalVideoFeatures64 features = palGetVideoFeaturesEx(); + if (!(features & PAL_VIDEO_FEATURE64_DECORATED_WINDOW)) { + // if we dont support, we need to create a borderless window + // and create the decorations ourselves + createInfo.style |= PAL_WINDOW_STYLE_BORDERLESS; + } // create the window with the create info struct result = palCreateWindow(&createInfo, &window); @@ -169,17 +201,26 @@ bool openglMultiContextTest() PAL_EVENT_WINDOW_CLOSE, PAL_DISPATCH_POLL); + palSetEventDispatchMode(eventDriver, PAL_EVENT_KEYDOWN, 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); + PalWindowHandleInfoEx winHandle = {0}; + winHandle = palGetWindowHandleInfoEx(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; + glWindow.display = winHandle.nativeDisplay; + + // On Wayland the window is the wl_egl_window + if (winHandle.nativeHandle3) { + // the window has a valid wl_egl_window + glWindow.window = winHandle.nativeHandle3; + + } else { + glWindow.window = winHandle.nativeWindow; + } // get opengl info const PalGLInfo* info = palGetGLInfo(); @@ -217,7 +258,7 @@ bool openglMultiContextTest() return false; } - // make the context current on this thread + // make the context current and optionally set vsync if supported result = palMakeContextCurrent(&glWindow, context); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); @@ -226,7 +267,6 @@ bool openglMultiContextTest() return false; } - // check if vsync is supported. if (info->extensions & PAL_GL_EXTENSION_SWAP_CONTROL) { // vsync is supported. This is set for the current context palSetSwapInterval(1); @@ -239,7 +279,7 @@ bool openglMultiContextTest() glClear = (PFNGLCLEARPROC)palGLGetProcAddress("glClear"); // set clear color - glClearColor(.2f, .2f, .2f, .2f); + glClearColor(0.2f, 0.2f, 0.2f, 1.0f); running = true; while (running) { @@ -253,6 +293,15 @@ bool openglMultiContextTest() running = false; break; } + + case PAL_EVENT_KEYDOWN: { + PalKeycode keycode = 0; + palUnpackUint32(event.data, &keycode, nullptr); + if (keycode == PAL_KEYCODE_ESCAPE) { + running = false; + } + break; + } } } @@ -273,8 +322,7 @@ bool openglMultiContextTest() palDestroyGLContext(context); // create a new opengl context with the same window - // the window's FBConfig has already be set, so we can skip it or set it. - // Opengl system will ignore it + // the FBConfig of the new context must match the windows context = nullptr; result = palCreateGLContext(&contextCreateInfo, &context); if (result != PAL_RESULT_SUCCESS) { @@ -293,7 +341,7 @@ bool openglMultiContextTest() return false; } - glClearColor(.2f, .6f, .6f, .2f); + glClearColor(.2f, .6f, .6f, 1.0f); running = true; while (running) { palUpdateVideo(); @@ -305,6 +353,15 @@ bool openglMultiContextTest() running = false; break; } + + case PAL_EVENT_KEYDOWN: { + PalKeycode keycode = 0; + palUnpackUint32(event.data, &keycode, nullptr); + if (keycode == PAL_KEYCODE_ESCAPE) { + running = false; + } + break; + } } } @@ -318,6 +375,12 @@ bool openglMultiContextTest() } } + // destroy the opengl context + palDestroyGLContext(context); + + // shutdown the opengl system + palShutdownGL(); + // destroy the window palDestroyWindow(window); @@ -327,12 +390,6 @@ bool openglMultiContextTest() // destroy the event driver palDestroyEventDriver(eventDriver); - // destroy the opengl context. - palDestroyGLContext(context); - - // shutdown the opengl system - palShutdownGL(); - // free the framebuffer configs palFree(nullptr, fbConfigs); diff --git a/tests/opengl_test.c b/tests/opengl_test.c index 4f4f5b6..a61d7f0 100644 --- a/tests/opengl_test.c +++ b/tests/opengl_test.c @@ -1,5 +1,6 @@ #include "pal/pal_opengl.h" +#include "pal/pal_video.h" #include "tests.h" bool openglTest() @@ -10,8 +11,20 @@ bool openglTest() palLog(nullptr, "==========================================="); palLog(nullptr, ""); + // initialize the video system and create a window + PalResult result = palInitVideo(nullptr, nullptr); + if (result != PAL_RESULT_SUCCESS) { + const char* error = palFormatResult(result); + palLog(nullptr, "Failed to initialize video: %s", error); + return false; + } + + // get the instance or display handle and pass it to the opengl system + // This must be called before the opengl system is initialized + palGLSetInstance(palGetInstance()); + // initialize the opengl system. This loads the icd. - PalResult result = palInitGL(nullptr); + result = palInitGL(nullptr); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); palLog(nullptr, "Failed to initialize opengl: %s", error); @@ -73,5 +86,8 @@ bool openglTest() // shutdown the opengl system palShutdownGL(); + // shutdown the video system + palShutdownVideo(); + return true; } \ No newline at end of file diff --git a/tests/system_cursor_test.c b/tests/system_cursor_test.c index ce92fa5..a0f4ec7 100644 --- a/tests/system_cursor_test.c +++ b/tests/system_cursor_test.c @@ -7,6 +7,7 @@ bool systemCursorTest() palLog(nullptr, ""); palLog(nullptr, "==========================================="); palLog(nullptr, "System Cursor Test"); + palLog(nullptr, "Press Escape or click close button to close Test"); palLog(nullptr, "==========================================="); palLog(nullptr, ""); @@ -44,6 +45,15 @@ bool systemCursorTest() return false; } + // check for support + PalVideoFeatures64 features = palGetVideoFeaturesEx(); + if (!(features & PAL_VIDEO_FEATURE64_WINDOW_SET_CURSOR)) { + palLog(nullptr, "Seting cursors feature not supported"); + palDestroyEventDriver(eventDriver); + palShutdownVideo(); + return false; + } + // create system cursor result = palCreateCursorFrom(PAL_CURSOR_CROSS, &cursor); if (result != PAL_RESULT_SUCCESS) { @@ -53,12 +63,19 @@ bool systemCursorTest() } // fill the create info struct - createInfo.monitor = nullptr; // use primary monitor + createInfo.monitor = nullptr; // use default monitor createInfo.height = 480; createInfo.width = 640; createInfo.show = true; createInfo.style = PAL_WINDOW_STYLE_RESIZABLE; - createInfo.title = "PAL System Cursor Window - Cross"; + createInfo.title = "System Cursor Window - Cross"; + + // check if we support decorated windows (title bar, close etc) + if (!(features & PAL_VIDEO_FEATURE64_DECORATED_WINDOW)) { + // if we dont support, we need to create a borderless window + // and create the decorations ourselves + createInfo.style |= PAL_WINDOW_STYLE_BORDERLESS; + } // create the window with the create info struct result = palCreateWindow(&createInfo, &window); @@ -74,6 +91,8 @@ bool systemCursorTest() PAL_EVENT_WINDOW_CLOSE, PAL_DISPATCH_POLL); // polling + palSetEventDispatchMode(eventDriver, PAL_EVENT_KEYDOWN, PAL_DISPATCH_POLL); + // set the cursor palSetWindowCursor(window, cursor); @@ -89,6 +108,15 @@ bool systemCursorTest() running = false; break; } + + case PAL_EVENT_KEYDOWN: { + PalKeycode keycode = 0; + palUnpackUint32(event.data, &keycode, nullptr); + if (keycode == PAL_KEYCODE_ESCAPE) { + running = false; + } + break; + } } } } diff --git a/tests/tests.h b/tests/tests.h index 867fbee..65eb69c 100644 --- a/tests/tests.h +++ b/tests/tests.h @@ -38,6 +38,9 @@ bool inputWindowTest(); bool systemCursorTest(); bool attachWindowTest(); bool charEventTest(); +bool nativeIntegrationTest(); +bool nativeInstanceTest(); +bool customDecorationTest(); // opengl test bool openglTest(); diff --git a/tests/tests.lua b/tests/tests.lua index 608f5f6..1c19695 100644 --- a/tests/tests.lua +++ b/tests/tests.lua @@ -41,19 +41,17 @@ project "tests" "input_window_test.c", "system_cursor_test.c", "attach_window_test.c", - "char_event_test.c" - } - end - - if (PAL_BUILD_OPENGL) then - files { - "opengl_test.c", - "opengl_fbconfig_test.c" + "char_event_test.c", + "native_integration_test.c", + "native_instance_test.c", + "custom_decoration_test.c" } end if (PAL_BUILD_OPENGL and PAL_BUILD_VIDEO) then files { + "opengl_test.c", + "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 3a22feb..a551b67 100644 --- a/tests/tests_main.c +++ b/tests/tests_main.c @@ -36,16 +36,16 @@ int main(int argc, char** argv) registerTest("System Cursor Test", systemCursorTest); registerTest("Attach Window Test", attachWindowTest); registerTest("Character Event Test", charEventTest); + registerTest("Native Integration Test", nativeIntegrationTest); + registerTest("Native Instance Test", nativeInstanceTest); + registerTest("Custom Decoration Test", customDecorationTest); #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 Test", openglTest); + 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 f016c8f..16f0bd9 100644 --- a/tests/thread_test.c +++ b/tests/thread_test.c @@ -47,6 +47,7 @@ bool threadTest() // join threads for (Int32 i = 0; i < THREAD_COUNT; i++) { // we dont need the return value + // joint threads does not need to be detached result = palJoinThread(threads[i], nullptr); if (result != PAL_RESULT_SUCCESS) { const char* error = palFormatResult(result); @@ -56,12 +57,5 @@ bool threadTest() } palLog(nullptr, "All threads finished successfully"); - - // detach threads - for (Int32 i = 0; i < THREAD_COUNT; i++) { - palDetachThread(threads[i]); - palLog(nullptr, "Thread %d: detached", i + 1); - } - return true; } \ No newline at end of file diff --git a/tests/tls_test.c b/tests/tls_test.c index fcc0e52..fcbd3e5 100644 --- a/tests/tls_test.c +++ b/tests/tls_test.c @@ -89,11 +89,9 @@ bool tlsTest() } // join thread + // joint threads does not need to be detached palJoinThread(thread, nullptr); // wait for thread to finish - // detach thread - palDetachThread(thread); - // destroy the tls palDestroyTLS(tlsID); diff --git a/tests/video_test.c b/tests/video_test.c index 32e8861..c018c74 100644 --- a/tests/video_test.c +++ b/tests/video_test.c @@ -11,7 +11,6 @@ bool videoTest() palLog(nullptr, ""); PalResult result; - PalVideoFeatures features; // initialize the video system result = palInitVideo(nullptr, nullptr); @@ -21,133 +20,169 @@ bool videoTest() return false; } - // get supported features - features = palGetVideoFeatures(); + // get supported features. Now uses extended function + PalVideoFeatures64 features = palGetVideoFeaturesEx(); palLog(nullptr, "Supported Video Features:"); - if (features & PAL_VIDEO_FEATURE_HIGH_DPI) { + if (features & PAL_VIDEO_FEATURE64_HIGH_DPI) { palLog(nullptr, " High DPI windows"); } - if (features & PAL_VIDEO_FEATURE_MONITOR_SET_ORIENTATION) { + if (features & PAL_VIDEO_FEATURE64_MONITOR_SET_ORIENTATION) { palLog(nullptr, " Setting monitor orientation"); } - if (features & PAL_VIDEO_FEATURE_MONITOR_GET_ORIENTATION) { + if (features & PAL_VIDEO_FEATURE64_MONITOR_GET_ORIENTATION) { palLog(nullptr, " Getting monitor orientation"); } - if (features & PAL_VIDEO_FEATURE_BORDERLESS_WINDOW) { + if (features & PAL_VIDEO_FEATURE64_BORDERLESS_WINDOW) { palLog(nullptr, " Borderless windows"); } - if (features & PAL_VIDEO_FEATURE_TRANSPARENT_WINDOW) { + if (features & PAL_VIDEO_FEATURE64_TRANSPARENT_WINDOW) { palLog(nullptr, " Transparent windows"); } - if (features & PAL_VIDEO_FEATURE_TOOL_WINDOW) { + if (features & PAL_VIDEO_FEATURE64_TOOL_WINDOW) { palLog(nullptr, " Tool windows"); } - if (features & PAL_VIDEO_FEATURE_MONITOR_SET_MODE) { + if (features & PAL_VIDEO_FEATURE64_MONITOR_SET_MODE) { palLog(nullptr, " Setting monitor display mode"); } - if (features & PAL_VIDEO_FEATURE_MONITOR_GET_MODE) { + if (features & PAL_VIDEO_FEATURE64_MONITOR_GET_MODE) { palLog(nullptr, " Getting monitor display mode"); } - if (features & PAL_VIDEO_FEATURE_MULTI_MONITORS) { + if (features & PAL_VIDEO_FEATURE64_MULTI_MONITORS) { palLog(nullptr, " Multi monitors"); } - if (features & PAL_VIDEO_FEATURE_WINDOW_SET_SIZE) { + if (features & PAL_VIDEO_FEATURE64_WINDOW_SET_SIZE) { palLog(nullptr, " Setting window size"); } - if (features & PAL_VIDEO_FEATURE_WINDOW_GET_SIZE) { + if (features & PAL_VIDEO_FEATURE64_WINDOW_GET_SIZE) { palLog(nullptr, " Getting window size"); } - if (features & PAL_VIDEO_FEATURE_WINDOW_SET_POS) { + if (features & PAL_VIDEO_FEATURE64_WINDOW_SET_POS) { palLog(nullptr, " Setting window position"); } - if (features & PAL_VIDEO_FEATURE_WINDOW_GET_POS) { + if (features & PAL_VIDEO_FEATURE64_WINDOW_GET_POS) { palLog(nullptr, " Getting window position"); } - if (features & PAL_VIDEO_FEATURE_WINDOW_SET_STATE) { + if (features & PAL_VIDEO_FEATURE64_WINDOW_SET_STATE) { palLog(nullptr, " Setting window state"); } - if (features & PAL_VIDEO_FEATURE_WINDOW_GET_STATE) { + if (features & PAL_VIDEO_FEATURE64_WINDOW_GET_STATE) { palLog(nullptr, " Getting window state"); } - if (features & PAL_VIDEO_FEATURE_WINDOW_SET_VISIBILITY) { + if (features & PAL_VIDEO_FEATURE64_WINDOW_SET_VISIBILITY) { palLog(nullptr, " Setting window visibility"); } - if (features & PAL_VIDEO_FEATURE_WINDOW_GET_VISIBILITY) { + if (features & PAL_VIDEO_FEATURE64_WINDOW_GET_VISIBILITY) { palLog(nullptr, " Getting window visibility"); } - if (features & PAL_VIDEO_FEATURE_WINDOW_SET_TITLE) { + if (features & PAL_VIDEO_FEATURE64_WINDOW_SET_TITLE) { palLog(nullptr, " Setting window title"); } - if (features & PAL_VIDEO_FEATURE_WINDOW_GET_TITLE) { + if (features & PAL_VIDEO_FEATURE64_WINDOW_GET_TITLE) { palLog(nullptr, " Getting window title"); } - if (features & PAL_VIDEO_FEATURE_NO_MINIMIZEBOX) { + if (features & PAL_VIDEO_FEATURE64_NO_MINIMIZEBOX) { palLog(nullptr, " No minimize box for windows"); } - if (features & PAL_VIDEO_FEATURE_NO_MAXIMIZEBOX) { + if (features & PAL_VIDEO_FEATURE64_NO_MAXIMIZEBOX) { palLog(nullptr, " No maximize box for windows"); } - if (features & PAL_VIDEO_FEATURE_CLIP_CURSOR) { + if (features & PAL_VIDEO_FEATURE64_CLIP_CURSOR) { palLog(nullptr, " Clipping cursor (mouse)"); } - if (features & PAL_VIDEO_FEATURE_WINDOW_FLASH_CAPTION) { + if (features & PAL_VIDEO_FEATURE64_WINDOW_FLASH_CAPTION) { palLog(nullptr, " Window titlebar flashing"); } - if (features & PAL_VIDEO_FEATURE_WINDOW_FLASH_TRAY) { + if (features & PAL_VIDEO_FEATURE64_WINDOW_FLASH_TRAY) { palLog(nullptr, " Window icon on taskbar flashing"); } - if (features & PAL_VIDEO_FEATURE_WINDOW_FLASH_INTERVAL) { + if (features & PAL_VIDEO_FEATURE64_WINDOW_FLASH_INTERVAL) { palLog(nullptr, " Setting window flash interval"); } - if (features & PAL_VIDEO_FEATURE_WINDOW_SET_INPUT_FOCUS) { + if (features & PAL_VIDEO_FEATURE64_WINDOW_SET_INPUT_FOCUS) { palLog(nullptr, " Setting input window"); } - if (features & PAL_VIDEO_FEATURE_WINDOW_GET_INPUT_FOCUS) { + if (features & PAL_VIDEO_FEATURE64_WINDOW_GET_INPUT_FOCUS) { palLog(nullptr, " Getting input window"); } - if (features & PAL_VIDEO_FEATURE_WINDOW_SET_STYLE) { + if (features & PAL_VIDEO_FEATURE64_WINDOW_SET_STYLE) { palLog(nullptr, " Setting window style"); } - if (features & PAL_VIDEO_FEATURE_WINDOW_GET_STYLE) { + if (features & PAL_VIDEO_FEATURE64_WINDOW_GET_STYLE) { palLog(nullptr, " Getting window style"); } - if (features & PAL_VIDEO_FEATURE_CURSOR_SET_POS) { + if (features & PAL_VIDEO_FEATURE64_CURSOR_SET_POS) { palLog(nullptr, " Setting cursor position"); } - if (features & PAL_VIDEO_FEATURE_CURSOR_GET_POS) { + if (features & PAL_VIDEO_FEATURE64_CURSOR_GET_POS) { palLog(nullptr, " Getting cursor position"); } + if (features & PAL_VIDEO_FEATURE64_WINDOW_SET_ICON) { + palLog(nullptr, " Setting window icon"); + } + + if (features & PAL_VIDEO_FEATURE64_TOPMOST_WINDOW) { + palLog(nullptr, " Topmost windows"); + } + + if (features & PAL_VIDEO_FEATURE64_DECORATED_WINDOW) { + palLog(nullptr, " Decorated (Normal) windows"); + } + + if (features & PAL_VIDEO_FEATURE64_CURSOR_SET_VISIBILITY) { + palLog(nullptr, " Setting cursor visibility"); + } + + if (features & PAL_VIDEO_FEATURE64_WINDOW_GET_MONITOR) { + palLog(nullptr, " Getting window current monitor"); + } + + if (features & PAL_VIDEO_FEATURE64_MONITOR_GET_PRIMARY) { + palLog(nullptr, " Getting primary monitor"); + } + + if (features & PAL_VIDEO_FEATURE64_FOREIGN_WINDOWS) { + palLog(nullptr, " Attaching and detaching foreign windows"); + } + + if (features & PAL_VIDEO_FEATURE64_MONITOR_VALIDATE_MODE) { + palLog(nullptr, " Validate monitor display mode"); + } + + if (features & PAL_VIDEO_FEATURE64_WINDOW_SET_CURSOR) { + palLog(nullptr, " Setting window cursor"); + } + // shutdown the video system palShutdownVideo(); diff --git a/tests/window_test.c b/tests/window_test.c index eb80724..7210dab 100644 --- a/tests/window_test.c +++ b/tests/window_test.c @@ -102,16 +102,13 @@ static inline void onWindowModalEnd(const PalEvent* event) static inline void onMonitorDPI(const PalEvent* event) { PalWindow* window = palUnpackPointer(event->data2); - palLog(nullptr, "%s: Display DPI: %d", dispatchString, event->data); + palLog(nullptr, "%s: Monitor DPI: %d", dispatchString, event->data); } static inline void onMonitorList(const PalEvent* event) { PalWindow* window = palUnpackPointer(event->data2); - palLog( - nullptr, - "%s: Display (monitor) List has been changed", - dispatchString); + palLog(nullptr, "%s: Monitor List has been changed", dispatchString); } static void PAL_CALL onEvent( @@ -155,13 +152,13 @@ bool windowTest() palLog(nullptr, ""); palLog(nullptr, "==========================================="); palLog(nullptr, "Window Test"); + palLog(nullptr, "Press Escape or click close button to close Test"); palLog(nullptr, "==========================================="); palLog(nullptr, ""); PalResult result; PalWindow* window = nullptr; PalWindowCreateInfo createInfo = {0}; - PalVideoFeatures features; bool running = false; // event driver @@ -182,6 +179,43 @@ bool windowTest() return false; } + PalDispatchMode dispatchMode = PAL_DISPATCH_NONE; +#if DISPATCH_MODE_POLL + dispatchMode = PAL_DISPATCH_POLL; +#else + dispatchMode = PAL_DISPATCH_CALLBACK; +#endif // DISPATCH_MODE_POLL + + // set dispatch mode for all events. + for (Uint32 e = 0; e < PAL_EVENT_KEYDOWN; e++) { + palSetEventDispatchMode(eventDriver, e, dispatchMode); + } + + // we set window close to poll + palSetEventDispatchMode( + eventDriver, + PAL_EVENT_WINDOW_CLOSE, + PAL_DISPATCH_POLL); + + palSetEventDispatchMode(eventDriver, PAL_EVENT_KEYDOWN, PAL_DISPATCH_POLL); + + // we set callback mode for modal begin and end. Since we want to capture + // that instantly + palSetEventDispatchMode( + eventDriver, + PAL_EVENT_WINDOW_MODAL_BEGIN, + PAL_DISPATCH_CALLBACK); + + palSetEventDispatchMode( + eventDriver, + PAL_EVENT_WINDOW_MODAL_END, + PAL_DISPATCH_CALLBACK); + + palSetEventDispatchMode( + eventDriver, + PAL_EVENT_WINDOW_DECORATION_MODE, + PAL_DISPATCH_POLL); + // 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 @@ -192,20 +226,25 @@ bool windowTest() return false; } - // get video system features - features = palGetVideoFeatures(); - // fill the create info struct - createInfo.monitor = nullptr; // use primary monitor + createInfo.monitor = nullptr; // use default monitor createInfo.height = 480; createInfo.width = 640; createInfo.show = true; createInfo.style = PAL_WINDOW_STYLE_RESIZABLE; + // check if we support decorated windows (title bar, close etc) + PalVideoFeatures64 features = palGetVideoFeaturesEx(); + if (!(features & PAL_VIDEO_FEATURE64_DECORATED_WINDOW)) { + // if we dont support, we need to create a borderless window + // and create the decorations ourselves + createInfo.style |= PAL_WINDOW_STYLE_BORDERLESS; + } + #if UNICODE_NAME - createInfo.title = "PAL Test Window Unicode - àà"; + createInfo.title = "Test Window Unicode - àà"; #else - createInfo.title = "PAL Test Window"; + createInfo.title = "Test Window"; #endif // UNICODE_NAME #if MAKE_BORDERLESS @@ -256,35 +295,6 @@ bool windowTest() } } #endif // MAKE_TRANSPARENT - PalDispatchMode dispatchMode = PAL_DISPATCH_NONE; -#if DISPATCH_MODE_POLL - dispatchMode = PAL_DISPATCH_POLL; -#else - dispatchMode = PAL_DISPATCH_CALLBACK; -#endif // DISPATCH_MODE_POLL - - // set dispatch mode for all events. - for (Uint32 e = 0; e < PAL_EVENT_KEYDOWN; e++) { - palSetEventDispatchMode(eventDriver, e, dispatchMode); - } - - // we set window close to poll - palSetEventDispatchMode( - eventDriver, - PAL_EVENT_WINDOW_CLOSE, - PAL_DISPATCH_POLL); - - // we set callback mode for modal begin and end. Since we want to capture - // that instantly - palSetEventDispatchMode( - eventDriver, - PAL_EVENT_WINDOW_MODAL_BEGIN, - PAL_DISPATCH_CALLBACK); - - palSetEventDispatchMode( - eventDriver, - PAL_EVENT_WINDOW_MODAL_END, - PAL_DISPATCH_CALLBACK); running = true; while (running) { @@ -333,6 +343,24 @@ bool windowTest() onMonitorList(&event); break; } + + case PAL_EVENT_KEYDOWN: { + PalKeycode keycode = 0; + palUnpackUint32(event.data, &keycode, nullptr); + if (keycode == PAL_KEYCODE_ESCAPE) { + running = false; + } + break; + } + + case PAL_EVENT_WINDOW_DECORATION_MODE: { + if (event.data == PAL_DECORATION_MODE_CLIENT_SIDE) { + palLog(nullptr, "Window Decoration Mode: Client Side"); + } else { + palLog(nullptr, "Window Decoration Mode: Server Side"); + } + break; + } } }