diff --git a/CHANGELOG.md b/CHANGELOG.md index 55821a6..c5c7266 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,3 +46,26 @@ - No API or ABI changes - existing Windows code remains compatible. - Linux video support currently targets **X11** only: **Wayland** is planned for future releases. - Safe upgrade from **v1.0.1** - just rebuild your project after updating. + +## [1.2.0] - 2025-10-22 + +### Features +- **Video:** Added **palGetInstance()** to retrieve the native display or instance handle. +- **Video:** Added **palAttachWindow()** for attaching **foreign windows** to PAL. +- **Video:** Added **palDetachWindow()** for detaching **foreign windows** from PAL. +- **Event:** Added **PAL_EVENT_KEYCHAR** to `PalEventType` enum. +- **Event:** Added documentation for event bits(payload) layout. + +### Naming Update +- PAL now stands for **Prime Abstraction Layer**, +reflecting its role as the primary explicit foundation for OS and graphics abstraction. +- All API remains unchanged — this is an identity update only. + +### Tests +- Added multi-threaded OpenGL example: demonstrating **Multi-Threaded OpenGL Rendering**. see **multi_thread_opengl_test.c**. +- Added attaching and detach foreign windows example. see **attach_window_test.c** +- Added key character example. see **char_event_test.c** + +### 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 diff --git a/README.md b/README.md index 24677d8..3e9b380 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# PAL (Platform Abstraction Layer) +# PAL (Prime Abstraction Layer) ![License: Zlib](https://img.shields.io/badge/License-Zlib-blue.svg) ![Language: C99](https://img.shields.io/badge/language-C99-green.svg) @@ -7,6 +7,9 @@ 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. 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. @@ -22,7 +25,8 @@ palGetWindowSize(window, &w, &h); ## Why PAL? -Other libraries like SDL or GLFW provide high-level abstractions but at the cost of overhead, implicit behavior, and limited control. **PAL is different:** +While libraries like SDL or GLFW focus on simplifying development +through high-level abstractions. **PAL is different:** - ✅ **Explicit**: You decide how memory, events, and handles are managed. - ✅ **Low Overhead**: PAL is close to raw OS calls, ensuring performance. diff --git a/include/pal/pal_event.h b/include/pal/pal_event.h index 2c374cd..ff422c0 100644 --- a/include/pal/pal_event.h +++ b/include/pal/pal_event.h @@ -111,25 +111,243 @@ typedef bool(PAL_CALL* PalPollFn)( * @ingroup pal_event */ typedef enum { - PAL_EVENT_WINDOW_CLOSE, /**< Window close button.*/ + /** + * PAL_EVENT_WINDOW_CLOSE + * + * event.data2 : window + * + * Use inline helpers: + * - palUnpackPointer() + */ + PAL_EVENT_WINDOW_CLOSE, + + /** + * PAL_EVENT_WINDOW_SIZE + * + * event.data : lower 32 bits = width, upper 32 bits = height + * + * event.data2 : window + * + * Use inline helpers: + * - palUnpackUint32() + * - palUnpackPointer() + */ PAL_EVENT_WINDOW_SIZE, + + /** + * PAL_EVENT_WINDOW_MOVE + * + * event.data : lower 32 bits = x, upper 32 bits = y + * + * event.data2 : window + * + * Use inline helpers: + * - palUnpackInt32() + * - palUnpackPointer() + */ PAL_EVENT_WINDOW_MOVE, - PAL_EVENT_WINDOW_STATE, /**< (minimized, maximized, restored).*/ - PAL_EVENT_WINDOW_FOCUS, /**< True for focus gained.*/ - PAL_EVENT_WINDOW_VISIBILITY, /**< True for visible.*/ - PAL_EVENT_WINDOW_MODAL_BEGIN, /**< WM_ENTERSIZEMOVE (Windows Only).*/ - PAL_EVENT_WINDOW_MODAL_END, /**< WM_EXITSIZEMOVE. (Windows Only).*/ + + /** + * PAL_EVENT_WINDOW_STATE + * + * event.data : state(minimized, maximized, restored). + * + * event.data2 : window + * + * Use inline helpers: + * - palUnpackPointer() + */ + PAL_EVENT_WINDOW_STATE, + + /** + * PAL_EVENT_WINDOW_FOCUS + * + * event.data : `true` for focus gained or `false` for focus lost. + * + * event.data2 : window + * + * Use inline helpers: + * - palUnpackPointer() + */ + PAL_EVENT_WINDOW_FOCUS, + + /** + * PAL_EVENT_WINDOW_VISIBILITY + * + * event.data : `true` for visible or `false` for hidden. + * + * event.data2 : window + * + * Use inline helpers: + * - palUnpackPointer() + */ + PAL_EVENT_WINDOW_VISIBILITY, + + /** + * @brief WM_ENTERSIZEMOVE (Windows Only). + * + * PAL_EVENT_WINDOW_MODAL_BEGIN + * + * event.data2 : window + */ + PAL_EVENT_WINDOW_MODAL_BEGIN, + + /** + * @brief WM_EXITSIZEMOVE (Windows Only). + * + * PAL_EVENT_WINDOW_MODAL_END + * + * event.data2 : window + */ + PAL_EVENT_WINDOW_MODAL_END, + + /** + * PAL_EVENT_MONITOR_DPI_CHANGED + * + * event.data2 : window + */ PAL_EVENT_MONITOR_DPI_CHANGED, - PAL_EVENT_MONITOR_LIST_CHANGED, /**< Monitor list changed.*/ + + /** + * @brief Monitor list changed + * + * PAL_EVENT_MONITOR_LIST_CHANGED + * + * event.data2 : window + */ + PAL_EVENT_MONITOR_LIST_CHANGED, + + /** + * PAL_EVENT_KEYDOWN + * + * event.data : lower 32 bits = keycode, upper 32 bits = scancode + * + * event.data2 : window + * + * Use inline helpers: + * - palUnpackUint32() + * - palUnpackPointer() + */ PAL_EVENT_KEYDOWN, + + /** + * PAL_EVENT_KEYREPEAT + * + * event.data : lower 32 bits = keycode, upper 32 bits = scancode + * + * event.data2 : window + * + * Use inline helpers: + * - palUnpackUint32() + * - palUnpackPointer() + */ PAL_EVENT_KEYREPEAT, + + /** + * PAL_EVENT_KEYUP + * + * event.data : lower 32 bits = keycode, upper 32 bits = scancode + * + * event.data2 : window + * + * Use inline helpers: + * - palUnpackUint32() + * - palUnpackPointer() + */ PAL_EVENT_KEYUP, + + /** + * PAL_EVENT_MOUSE_BUTTONDOWN + * + * event.data : mouse button + * + * event.data2 : window + * + * Use inline helpers: + * - palUnpackPointer() + */ PAL_EVENT_MOUSE_BUTTONDOWN, + + /** + * PAL_EVENT_MOUSE_BUTTONUP + * + * event.data : mouse button + * + * event.data2 : window + * + * Use inline helpers: + * - palUnpackPointer() + */ PAL_EVENT_MOUSE_BUTTONUP, + + /** + * PAL_EVENT_MOUSE_MOVE + * + * event.data : lower 32 bits = x, upper 32 bits = y + * + * event.data2 : window + * + * Use inline helpers: + * - palUnpackInt32() + * - palUnpackPointer() + */ PAL_EVENT_MOUSE_MOVE, - PAL_EVENT_MOUSE_DELTA, /**< Mouse movement delta.*/ + + /** + * @brief Mouse movement delta. + * + * PAL_EVENT_MOUSE_DELTA + * + * event.data : lower 32 bits = dx, upper 32 bits = dy + * + * event.data2 : window + * + * Use inline helpers: + * - palUnpackInt32() + * - palUnpackPointer() + */ + PAL_EVENT_MOUSE_DELTA, + + /** + * PAL_EVENT_MOUSE_WHEEL + * + * event.data : lower 32 bits = dx, upper 32 bits = dy + * + * event.data2 : window + * + * Use inline helpers: + * - palUnpackInt32() + * - palUnpackPointer() + */ PAL_EVENT_MOUSE_WHEEL, + + /** + * PAL_EVENT_USER + * + * event.userId : User event ID or type. + * + * Use inline helpers: + * - palPackInt32() + * - palPackUint32() + * - palPackPointer() + * - palUnpackInt32() + * - palUnpackUint32() + * - palUnpackPointer() + */ PAL_EVENT_USER, + + /** + * PAL_EVENT_USER + * + * event.data : codepoint + * + * event.data2 : window + * + * Use inline helpers: + * - palUnpackPointer() + */ + PAL_EVENT_KEYCHAR, + PAL_EVENT_MAX } PalEventType; diff --git a/include/pal/pal_video.h b/include/pal/pal_video.h index 98d6f69..87c3d01 100644 --- a/include/pal/pal_video.h +++ b/include/pal/pal_video.h @@ -958,7 +958,7 @@ PAL_API PalResult PAL_CALL palCreateWindow( * * The video system must be initialized before this call. * If the provided window is invalid or nullptr, this function returns - * silently. + * silently. This only destroys windows created by PAL. * * @param[in] window Pointer to the window to destroy. * @@ -1757,6 +1757,103 @@ PAL_API PalResult PAL_CALL palSetWindowCursor( PalWindow* window, PalCursor* cursor); +/** + * @brief Get the native application instance or display. + * + * The video system must be initialized before this call. + * + * This returns the native instance or display of the application + * PAL video was initialized in. + * + * On Linux: This is the Display associated with the connection. + + * On Windows: This is the HINSTANCE of the process. + * + * @return The instance or display on success or nullptr on failure. + * + * Thread safety: This function is thread safe. + * + * @note The returned instance or display must not be freed. + * + * @since 1.2 + * @ingroup pal_video + */ +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. + * + * This function registers the provided window with PAL video system so it + * can manage events and use its functionality/API for the provided window. + * + * PAL does not own the window, it just sends events to that window. + * Users are responsible for destroying the window when no longer needed. + * palDestroyWindow() does not destroy the foreign or native window. + * + * Use Case: + * + * PAL takes your native foreign or native window and gives you a PalWindow + * which can be used with all of PAL API. The native window must be valid + * till the PalWindow has been detached with palDetachWindow(). + * + * The window must be created with the same instance or display + * that PAL uses. see palGetInstance(). + * + * @param[in] windowHandle Pointer to the foreign or native window. + * @param[out] outWindow Pointer to a PalWindow to recieve the attached window. + * Must not be nullptr. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: This function must be called from the main thread. + * + * @since 1.2 + * @ingroup pal_video + * @sa palGetInstance + * @sa palDestroyWindow + * @sa palDetachWindow + */ +PAL_API PalResult PAL_CALL palAttachWindow( + void* windowHandle, + PalWindow** outWindow); + +/** + * @brief Detach a foreign or native window from PAL video system. + * + * The video system must be initialized before this call. + * + * This function unregisters the provided window from PAL video system. + * The window must not be owned by PAL otherwise the function fails + * and return `PAL_RESULT_INVALID_WINDOW`. + * + * Detaching the window does not destroy the window, + * therefore destroying the window is the users responsibility. + * + * Use Case: + * + * Give back the PalWindow returned at palAttachWindow() + * and get back your native window. + * + * @param[in] window Pointer to the PalWindow to detach. Must not be nullptr. + * @param[out] outWindowHandle Pointer to recieve the native window. Can be + * nullptr. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: This function must be called from the main thread. + * + * @since 1.2 + * @ingroup pal_video + * @sa palAttachWindow + */ +PAL_API PalResult PAL_CALL palDetachWindow( + PalWindow* window, + void** outWindowHandle); + /** @} */ // end of pal_video group #endif // _PAL_VIDEO_H \ No newline at end of file diff --git a/src/opengl/pal_opengl_linux.c b/src/opengl/pal_opengl_linux.c index dc38610..46953f4 100644 --- a/src/opengl/pal_opengl_linux.c +++ b/src/opengl/pal_opengl_linux.c @@ -510,11 +510,7 @@ PalResult PAL_CALL palInitGL(const PalAllocator* allocator) s_GL.glGetString = (glGetStringFn)s_GL.eglGetProcAddress("glGetString"); const char* version = (const char*)s_GL.glGetString(GL_VERSION); if (version) { -#ifdef _MSC_VER - sscanf_s(version, "%d.%d", &s_GL.info.major, &s_GL.info.minor); -#else sscanf(version, "%d.%d", &s_GL.info.major, &s_GL.info.minor); -#endif } const char* renderer = (const char*)s_GL.glGetString(GL_RENDERER); diff --git a/src/video/pal_video_linux.c b/src/video/pal_video_linux.c index 234795d..9b58165 100644 --- a/src/video/pal_video_linux.c +++ b/src/video/pal_video_linux.c @@ -117,6 +117,8 @@ typedef struct { bool skipConfigure; bool skipState; bool used; + bool isAttached; + bool skipIfAttached; int x; int y; Uint32 w; @@ -125,6 +127,7 @@ typedef struct { PalWindowState state; PalCursor* cursor; PalWindow* window; + XIC ic; // X11 only } WindowData; typedef struct { @@ -530,6 +533,11 @@ typedef XVisualInfo* (*XGetVisualInfoFn)( XVisualInfo*, int*); +typedef int (*XSelectInputFn)( + Display*, + Window, + long); + typedef Cursor (*XcursorImageLoadCursorFn)( Display*, const XcursorImage*); @@ -549,6 +557,30 @@ typedef int (*XkbSetDetectableAutoRepeatFn)( int, int*); +typedef char* (*XSetLocaleModifiersFn)(const char*); + +typedef XIM (*XOpenIMFn)( + Display*, + struct _XrmHashBucketRec*, + char*, + char*); + +typedef int (*XCloseIMFn)(XIM); + +typedef XIC (*XCreateICFn)( + XIM, + ...) _X_SENTINEL(0); + +typedef void (*XDestroyICFn)(XIC); + +typedef int (*Xutf8LookupStringFn)( + XIC, + XKeyPressedEvent*, + char*, + int, + KeySym*, + int*); + typedef struct { bool unicodeTitle; @@ -586,6 +618,7 @@ typedef struct { void* xrandr; void* glxHandle; void* libCursor; + XIM im; Display* display; Window root; XContext dataID; @@ -671,8 +704,15 @@ typedef struct { XcursorImageCreateFn cursorImageCreate; XcursorImageDestroyFn cursorImageDestroy; XLookupKeysymFn lookupKeysym; + XSelectInputFn selectInput; XkbSetDetectableAutoRepeatFn setDetectableAutoRepeat; + XSetLocaleModifiersFn setLocaleModifiers; + XOpenIMFn openIM; + XCloseIMFn closeIM; + XCreateICFn createIC; + XDestroyICFn destroyIC; + Xutf8LookupStringFn utf8LookupString; } X11; static X11 s_X11 = {0}; @@ -729,6 +769,9 @@ typedef struct { 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; @@ -1278,124 +1321,124 @@ static void xSendWMEvent( static void xCreateScancodeTable() { // Letters - s_Keyboard.scancodes[30] = PAL_SCANCODE_A; - s_Keyboard.scancodes[48] = PAL_SCANCODE_B; - s_Keyboard.scancodes[46] = PAL_SCANCODE_C; - s_Keyboard.scancodes[32] = PAL_SCANCODE_D; - s_Keyboard.scancodes[18] = PAL_SCANCODE_E; - s_Keyboard.scancodes[33] = PAL_SCANCODE_F; - s_Keyboard.scancodes[34] = PAL_SCANCODE_G; - s_Keyboard.scancodes[35] = PAL_SCANCODE_H; - s_Keyboard.scancodes[23] = PAL_SCANCODE_I; - s_Keyboard.scancodes[36] = PAL_SCANCODE_J; - s_Keyboard.scancodes[37] = PAL_SCANCODE_K; - s_Keyboard.scancodes[38] = PAL_SCANCODE_L; - s_Keyboard.scancodes[50] = PAL_SCANCODE_M; - s_Keyboard.scancodes[49] = PAL_SCANCODE_N; - s_Keyboard.scancodes[24] = PAL_SCANCODE_O; - s_Keyboard.scancodes[25] = PAL_SCANCODE_P; - s_Keyboard.scancodes[16] = PAL_SCANCODE_Q; - s_Keyboard.scancodes[19] = PAL_SCANCODE_R; - s_Keyboard.scancodes[31] = PAL_SCANCODE_S; - s_Keyboard.scancodes[20] = PAL_SCANCODE_T; - s_Keyboard.scancodes[22] = PAL_SCANCODE_U; - s_Keyboard.scancodes[47] = PAL_SCANCODE_V; - s_Keyboard.scancodes[17] = PAL_SCANCODE_W; - s_Keyboard.scancodes[45] = PAL_SCANCODE_X; - s_Keyboard.scancodes[21] = PAL_SCANCODE_Y; - s_Keyboard.scancodes[44] = PAL_SCANCODE_Z; + 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[11] = PAL_SCANCODE_0; - s_Keyboard.scancodes[2] = PAL_SCANCODE_1; - s_Keyboard.scancodes[3] = PAL_SCANCODE_2; - s_Keyboard.scancodes[4] = PAL_SCANCODE_3; - s_Keyboard.scancodes[5] = PAL_SCANCODE_4; - s_Keyboard.scancodes[6] = PAL_SCANCODE_5; - s_Keyboard.scancodes[7] = PAL_SCANCODE_6; - s_Keyboard.scancodes[8] = PAL_SCANCODE_7; - s_Keyboard.scancodes[9] = PAL_SCANCODE_8; - s_Keyboard.scancodes[10] = PAL_SCANCODE_9; + 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; // Function - s_Keyboard.scancodes[59] = PAL_SCANCODE_F1; - s_Keyboard.scancodes[60] = PAL_SCANCODE_F2; - s_Keyboard.scancodes[61] = PAL_SCANCODE_F3; - s_Keyboard.scancodes[62] = PAL_SCANCODE_F4; - s_Keyboard.scancodes[63] = PAL_SCANCODE_F5; - s_Keyboard.scancodes[64] = PAL_SCANCODE_F6; - s_Keyboard.scancodes[65] = PAL_SCANCODE_F7; - s_Keyboard.scancodes[66] = PAL_SCANCODE_F8; - s_Keyboard.scancodes[67] = PAL_SCANCODE_F9; - s_Keyboard.scancodes[68] = PAL_SCANCODE_F10; - s_Keyboard.scancodes[87] = PAL_SCANCODE_F11; - s_Keyboard.scancodes[88] = PAL_SCANCODE_F12; + 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; // Control - s_Keyboard.scancodes[1] = PAL_SCANCODE_ESCAPE; - s_Keyboard.scancodes[28] = PAL_SCANCODE_ENTER; - s_Keyboard.scancodes[15] = PAL_SCANCODE_TAB; - s_Keyboard.scancodes[14] = PAL_SCANCODE_BACKSPACE; - s_Keyboard.scancodes[57] = PAL_SCANCODE_SPACE; - s_Keyboard.scancodes[58] = PAL_SCANCODE_CAPSLOCK; - s_Keyboard.scancodes[69] = PAL_SCANCODE_NUMLOCK; - s_Keyboard.scancodes[70] = PAL_SCANCODE_SCROLLLOCK; - s_Keyboard.scancodes[42] = PAL_SCANCODE_LSHIFT; - s_Keyboard.scancodes[54] = PAL_SCANCODE_RSHIFT; - s_Keyboard.scancodes[29] = PAL_SCANCODE_LCTRL; - s_Keyboard.scancodes[97] = PAL_SCANCODE_RCTRL; - s_Keyboard.scancodes[56] = PAL_SCANCODE_LALT; - s_Keyboard.scancodes[100] = PAL_SCANCODE_RALT; + 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; // Arrows - s_Keyboard.scancodes[105] = PAL_SCANCODE_LEFT; - s_Keyboard.scancodes[106] = PAL_SCANCODE_RIGHT; - s_Keyboard.scancodes[103] = PAL_SCANCODE_UP; - s_Keyboard.scancodes[108] = PAL_SCANCODE_DOWN; + 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; // Navigation - s_Keyboard.scancodes[110] = PAL_SCANCODE_INSERT; - s_Keyboard.scancodes[111] = PAL_SCANCODE_DELETE; - s_Keyboard.scancodes[102] = PAL_SCANCODE_HOME; - s_Keyboard.scancodes[107] = PAL_SCANCODE_END; - s_Keyboard.scancodes[104] = PAL_SCANCODE_PAGEUP; - s_Keyboard.scancodes[109] = PAL_SCANCODE_PAGEDOWN; + 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; // Keypad - s_Keyboard.scancodes[82] = PAL_SCANCODE_KP_0; - s_Keyboard.scancodes[79] = PAL_SCANCODE_KP_1; - s_Keyboard.scancodes[80] = PAL_SCANCODE_KP_2; - s_Keyboard.scancodes[81] = PAL_SCANCODE_KP_3; - s_Keyboard.scancodes[75] = PAL_SCANCODE_KP_4; - s_Keyboard.scancodes[76] = PAL_SCANCODE_KP_5; - s_Keyboard.scancodes[77] = PAL_SCANCODE_KP_6; - s_Keyboard.scancodes[71] = PAL_SCANCODE_KP_7; - s_Keyboard.scancodes[72] = PAL_SCANCODE_KP_8; - s_Keyboard.scancodes[73] = PAL_SCANCODE_KP_9; - s_Keyboard.scancodes[96] = PAL_SCANCODE_KP_ENTER; - s_Keyboard.scancodes[78] = PAL_SCANCODE_KP_ADD; - s_Keyboard.scancodes[74] = PAL_SCANCODE_KP_SUBTRACT; - s_Keyboard.scancodes[55] = PAL_SCANCODE_KP_MULTIPLY; - s_Keyboard.scancodes[98] = PAL_SCANCODE_KP_DIVIDE; - s_Keyboard.scancodes[83] = PAL_SCANCODE_KP_DECIMAL; + 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; // Misc - s_Keyboard.scancodes[99] = PAL_SCANCODE_PRINTSCREEN; - s_Keyboard.scancodes[102] = PAL_SCANCODE_PAUSE; - s_Keyboard.scancodes[127] = PAL_SCANCODE_MENU; - s_Keyboard.scancodes[40] = PAL_SCANCODE_APOSTROPHE; - s_Keyboard.scancodes[43] = PAL_SCANCODE_BACKSLASH; - s_Keyboard.scancodes[51] = PAL_SCANCODE_COMMA; - s_Keyboard.scancodes[13] = PAL_SCANCODE_EQUAL; - s_Keyboard.scancodes[41] = PAL_SCANCODE_GRAVEACCENT; - s_Keyboard.scancodes[12] = PAL_SCANCODE_SUBTRACT; - s_Keyboard.scancodes[52] = PAL_SCANCODE_PERIOD; - s_Keyboard.scancodes[39] = PAL_SCANCODE_SEMICOLON; - s_Keyboard.scancodes[53] = PAL_SCANCODE_SLASH; - s_Keyboard.scancodes[26] = PAL_SCANCODE_LBRACKET; - s_Keyboard.scancodes[27] = PAL_SCANCODE_RBRACKET; - s_Keyboard.scancodes[125] = PAL_SCANCODE_LSUPER; - s_Keyboard.scancodes[126] = PAL_SCANCODE_RSUPER; + 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; } static void xCreateKeycodeTable() @@ -1430,18 +1473,6 @@ static void xCreateKeycodeTable() s_Keyboard.keycodes[XK_y] = PAL_KEYCODE_Y; s_Keyboard.keycodes[XK_z] = PAL_KEYCODE_Z; - // Numbers (top row) - s_Keyboard.keycodes[XK_0] = PAL_KEYCODE_0; - s_Keyboard.keycodes[XK_1] = PAL_KEYCODE_1; - s_Keyboard.keycodes[XK_2] = PAL_KEYCODE_2; - s_Keyboard.keycodes[XK_3] = PAL_KEYCODE_3; - s_Keyboard.keycodes[XK_4] = PAL_KEYCODE_4; - s_Keyboard.keycodes[XK_5] = PAL_KEYCODE_5; - s_Keyboard.keycodes[XK_6] = PAL_KEYCODE_6; - s_Keyboard.keycodes[XK_7] = PAL_KEYCODE_7; - s_Keyboard.keycodes[XK_8] = PAL_KEYCODE_8; - s_Keyboard.keycodes[XK_9] = PAL_KEYCODE_9; - // Control s_Keyboard.keycodes[XK_space] = PAL_KEYCODE_SPACE; @@ -1731,6 +1762,10 @@ static PalResult xInitVideo() s_X11.handle, "XGetInputFocus"); + s_X11.selectInput = (XSelectInputFn)dlsym( + s_X11.handle, + "XSelectInput"); + // libXcursor s_X11.cursorImageLoadCursor = (XcursorImageLoadCursorFn)dlsym( s_X11.libCursor, @@ -1752,6 +1787,30 @@ static PalResult xInitVideo() 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 s_X11.display = s_X11.openDisplay(nullptr); if (!s_X11.display) { @@ -1826,6 +1885,13 @@ static PalResult xInitVideo() // 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) { + return PAL_RESULT_PLATFORM_FAILURE; + } + // clang-format on return PAL_RESULT_SUCCESS; } @@ -1836,6 +1902,11 @@ static void xShutdownVideo() s_X11.freeColormap(s_X11.display, s_X11.colormap); } + if (s_X11.hiddenCursor) { + s_X11.freeCursor(s_X11.display, s_X11.hiddenCursor); + } + + s_X11.closeIM(s_X11.im); s_X11.closeDisplay(s_X11.display); dlclose(s_X11.handle); dlclose(s_X11.xrandr); @@ -1919,6 +1990,15 @@ static void xUpdateVideo() } } + // attach windows sometimes bypass + // skipConfgure an still send an initial move event + if (data->isAttached) { + if (data->skipIfAttached) { + data->skipIfAttached = false; + return; + } + } + // check if its a move event if (data->x != event.xconfigure.x || data->y != event.xconfigure.y) { @@ -2264,6 +2344,61 @@ static void xUpdateVideo() 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; + } + + 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); + } } return; } @@ -2825,14 +2960,12 @@ static PalResult xCreateWindow( // we dont need to set any flag } - long mask = ExposureMask | StructureNotifyMask | KeyPressMask; + long mask = StructureNotifyMask | KeyPressMask; mask |= KeyReleaseMask; mask |= ButtonPressMask; mask |= ButtonReleaseMask; mask |= PointerMotionMask; mask |= FocusChangeMask; - mask |= EnterWindowMask; - mask |= LeaveWindowMask; mask |= PropertyChangeMask; XSetWindowAttributes attrs = {0}; @@ -2893,7 +3026,7 @@ static PalResult xCreateWindow( s_X11.free(hints); } - if (s_X11Atoms.unicodeTitle) { + if (s_X11Atoms.unicodeTitle && info->title) { s_X11.changeProperty( s_X11.display, window, @@ -2905,7 +3038,9 @@ static PalResult xCreateWindow( strlen(info->title)); } else { - s_X11.storeName(s_X11.display, window, info->title); + if (info->title) { + s_X11.storeName(s_X11.display, window, info->title); + } } // borderless @@ -3040,22 +3175,35 @@ static PalResult xCreateWindow( s_X11.iconifyWindow(s_X11.display, window, s_X11.screen); } - s_X11.setWMProtocols( - s_X11.display, - window, - &s_X11Atoms.WM_DELETE_WINDOW, - True); + s_X11 + .setWMProtocols(s_X11.display, window, &s_X11Atoms.WM_DELETE_WINDOW, 1); s_X11.flush(s_X11.display); // 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); + // create an input context + data->ic = s_X11.createIC( + s_X11.im, + XNInputStyle, + XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, + window, + XNFocusWindow, + window, + nullptr); + + if (!data->ic) { + return PAL_RESULT_PLATFORM_FAILURE; + } + *outWindow = TO_PAL_HANDLE(PalWindow, window); return PAL_RESULT_SUCCESS; } @@ -3065,6 +3213,13 @@ static void xDestroyWindow(PalWindow* window) Window xWin = FROM_PAL_HANDLE(Window, window); WindowData* data = nullptr; s_X11.findContext(s_X11.display, xWin, s_X11.dataID, (XPointer*)&data); + + // PAL does not destroy an attached window + if (data->isAttached) { + return; + } + + s_X11.destroyIC(data->ic); s_X11.destroyWindow(s_X11.display, xWin); data->used = false; } @@ -3872,6 +4027,95 @@ PalResult xSetWindowCursor( return PAL_RESULT_SUCCESS; } +PalResult xAttachWindow( + void* windowHandle, + PalWindow** outWindow) +{ + Window xWin = FROM_PAL_HANDLE(Window, windowHandle); + XWindowAttributes attr; + if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { + return PAL_RESULT_INVALID_WINDOW; + } + + // 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; + } + + 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 + + // 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; + + // 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 xDetachWindow( + PalWindow* window, + void** outWindowHandle) +{ + // 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; + } + + // 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; +} + static Backend s_XBackend = { .shutdownVideo = xShutdownVideo, .updateVideo = xUpdateVideo, @@ -3919,7 +4163,10 @@ static Backend s_XBackend = { .clipCursor = xClipCursor, .getCursorPos = xGetCursorPos, .setCursorPos = xSetCursorPos, - .setWindowCursor = xSetWindowCursor}; + .setWindowCursor = xSetWindowCursor, + + .attachWindow = xAttachWindow, + .detachWindow = xDetachWindow}; #pragma endregions @@ -4745,4 +4992,47 @@ PalResult PAL_CALL palSetWindowCursor( } return s_Video.backend->setWindowCursor(window, cursor); +} + +void* PAL_CALL palGetInstance() +{ + if (!s_Video.initialized) { + return nullptr; + } + + if (s_X11.display) { + // we are on X11 + return (void*)s_X11.display; + } + return nullptr; +} + +PalResult PAL_CALL palAttachWindow( + void* windowHandle, + PalWindow** outWindow) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!windowHandle) { + return PAL_RESULT_NULL_POINTER; + } + + return s_Video.backend->attachWindow(windowHandle, outWindow); +} + +PalResult PAL_CALL palDetachWindow( + PalWindow* window, + void** outWindowHandle) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!window) { + return PAL_RESULT_NULL_POINTER; + } + + return s_Video.backend->detachWindow(window, outWindowHandle); } \ No newline at end of file diff --git a/src/video/pal_video_win32.c b/src/video/pal_video_win32.c index 2fe23e0..e14bbe6 100644 --- a/src/video/pal_video_win32.c +++ b/src/video/pal_video_win32.c @@ -48,6 +48,7 @@ freely, subject to the following restrictions: // ================================================== #define PAL_VIDEO_CLASS L"PALVideoClass" +#define PAL_VIDEO_PROP L"PalVideoData" #define WIN32_DPI 0 #define WIN32_DPI_AWARE 2 #define MAX_MODE_COUNT 128 @@ -92,8 +93,10 @@ typedef BOOL(WINAPI* SetPixelFormatFn)( typedef struct { bool used; + bool isAttached; PalWindowState state; HCURSOR cursor; + LONG_PTR wndProc; } WindowData; typedef struct { @@ -138,6 +141,7 @@ typedef struct { } PendingEvent; typedef struct { + Int32 pendingHighSurrogate; bool scancodeState[PAL_SCANCODE_MAX]; bool keycodeState[PAL_KEYCODE_MAX]; int scancodes[512]; @@ -171,7 +175,7 @@ LRESULT CALLBACK videoProc( LPARAM lParam) { // check if the window has been created - WindowData* data = (void*)(LONG_PTR)GetWindowLongPtrW(hwnd, GWLP_USERDATA); + WindowData* data = (WindowData*)GetPropW(hwnd, PAL_VIDEO_PROP); if (!data) { // window has not been created yet return DefWindowProcW(hwnd, msg, wParam, lParam); @@ -581,72 +585,17 @@ LRESULT CALLBACK videoProc( scancode = s_Keyboard.scancodes[index]; } - // special keycode handling - if (extended && win32Keycode == VK_RETURN) { - keycode = PAL_KEYCODE_KP_ENTER; - - } else if (extended && win32Keycode == VK_OEM_PLUS) { - keycode = PAL_KEYCODE_KP_EQUAL; - - } else if (extended && win32Keycode == VK_CONTROL) { - keycode = PAL_KEYCODE_RCTRL; - - } else if (extended && win32Keycode == VK_MENU) { - keycode = PAL_KEYCODE_RALT; - - } else if (win32Keycode == VK_SHIFT) { - if (scancode == PAL_SCANCODE_LSHIFT) { - keycode = PAL_KEYCODE_LSHIFT; - - } else if (scancode == PAL_SCANCODE_RSHIFT) { - keycode = PAL_KEYCODE_RSHIFT; - } - - } else if (!extended && win32Keycode == VK_INSERT) { - // numpad 0 - keycode = PAL_KEYCODE_KP_0; - - } else if (!extended && win32Keycode == VK_END) { - // numpad 1 - keycode = PAL_KEYCODE_KP_1; - - } else if (!extended && win32Keycode == VK_DOWN) { - // numpad 2 - keycode = PAL_KEYCODE_KP_2; - - } else if (!extended && win32Keycode == VK_NEXT) { - // numpad 3 - keycode = PAL_KEYCODE_KP_3; - - } else if (!extended && win32Keycode == VK_LEFT) { - // numpad 4 - keycode = PAL_KEYCODE_KP_4; - - } else if (!extended && win32Keycode == VK_CLEAR) { - // numpad 5 - keycode = PAL_KEYCODE_KP_5; - - } else if (!extended && win32Keycode == VK_RIGHT) { - // numpad 6 - keycode = PAL_KEYCODE_KP_6; - - } else if (!extended && win32Keycode == VK_HOME) { - // numpad 7 - keycode = PAL_KEYCODE_KP_7; - - } else if (!extended && win32Keycode == VK_UP) { - // numpad 8 - keycode = PAL_KEYCODE_KP_8; - - } else if (!extended && win32Keycode == VK_PRIOR) { - // numpad 9 - keycode = PAL_KEYCODE_KP_9; - - } else if (!extended && win32Keycode == VK_DELETE) { - // numpad decimal - keycode = PAL_KEYCODE_KP_DECIMAL; + keycode = s_Keyboard.keycodes[win32Keycode]; + if (keycode == PAL_KEYCODE_UNKNOWN) { + // we didnt get any printable key + // we use the scancode + // 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; + } - } else if (win32Keycode == VK_SNAPSHOT) { + if (win32Keycode == VK_SNAPSHOT) { // printscreen since the platform does not get us a keydown, we // do that ourselves if (s_Video.eventDriver) { @@ -662,9 +611,6 @@ LRESULT CALLBACK videoProc( } s_Keyboard.keycodeState[keycode] = true; } - - } else { - keycode = s_Keyboard.keycodes[win32Keycode]; } // check before updating state @@ -704,7 +650,7 @@ LRESULT CALLBACK videoProc( case WM_SETCURSOR: { if (LOWORD(lParam) == HTCLIENT) { - if (data) { + if (data && data->cursor) { SetCursor(data->cursor); return TRUE; } @@ -713,6 +659,44 @@ LRESULT CALLBACK videoProc( break; } + + case WM_CHAR: { + PalEventType type = PAL_EVENT_KEYCHAR; + Uint32 codepoint = 0; + if (s_Video.eventDriver) { + PalEventDriver* driver = s_Video.eventDriver; + mode = palGetEventDispatchMode(driver, type); + if (mode == PAL_DISPATCH_NONE) { + break; + } + } + // Most characters comes as two WM_CHAR messags or event + // we store the first one and combine with the second if we got any + Uint16 character = (Uint16)wParam; + if (character >= 0xD800 && character <= 0xDBFF) { + // high surrogate + s_Keyboard.pendingHighSurrogate = character; + } else if (character >= 0xDC00 && character <= 0xDFFF) { + if (s_Keyboard.pendingHighSurrogate) { + // low surrogate we combine both + Uint32 high = s_Keyboard.pendingHighSurrogate - 0xD800; + Uint32 low = character - 0xDC00; + codepoint = 0x10000 + ((high << 10) | low); + s_Keyboard.pendingHighSurrogate = 0; + } + + } else { + // normal character (A-Z) + codepoint = character; + } + + // push an event + PalEvent event = {0}; + event.type = type; + event.data = codepoint; + event.data2 = palPackPointer((PalWindow*)hwnd); + palPushEvent(s_Video.eventDriver, &event); + } } return DefWindowProcW(hwnd, msg, wParam, lParam); @@ -858,6 +842,8 @@ static inline void addMonitorMode( static void createKeycodeTable() { + // Tis is for only printable and text input keys + // Letters s_Keyboard.keycodes['A'] = PAL_KEYCODE_A; s_Keyboard.keycodes['B'] = PAL_KEYCODE_B; @@ -886,84 +872,10 @@ static void createKeycodeTable() s_Keyboard.keycodes['Y'] = PAL_KEYCODE_Y; s_Keyboard.keycodes['Z'] = PAL_KEYCODE_Z; - // Numbers (top row) - s_Keyboard.keycodes['0'] = PAL_KEYCODE_0; - s_Keyboard.keycodes['1'] = PAL_KEYCODE_1; - s_Keyboard.keycodes['2'] = PAL_KEYCODE_2; - s_Keyboard.keycodes['3'] = PAL_KEYCODE_3; - s_Keyboard.keycodes['4'] = PAL_KEYCODE_4; - s_Keyboard.keycodes['5'] = PAL_KEYCODE_5; - s_Keyboard.keycodes['6'] = PAL_KEYCODE_6; - s_Keyboard.keycodes['7'] = PAL_KEYCODE_7; - s_Keyboard.keycodes['8'] = PAL_KEYCODE_8; - s_Keyboard.keycodes['9'] = PAL_KEYCODE_9; - - // Function - s_Keyboard.keycodes[VK_F1] = PAL_KEYCODE_F1; - s_Keyboard.keycodes[VK_F2] = PAL_KEYCODE_F2; - s_Keyboard.keycodes[VK_F3] = PAL_KEYCODE_F3; - s_Keyboard.keycodes[VK_F4] = PAL_KEYCODE_F4; - s_Keyboard.keycodes[VK_F5] = PAL_KEYCODE_F5; - s_Keyboard.keycodes[VK_F6] = PAL_KEYCODE_F6; - s_Keyboard.keycodes[VK_F7] = PAL_KEYCODE_F7; - s_Keyboard.keycodes[VK_F8] = PAL_KEYCODE_F8; - s_Keyboard.keycodes[VK_F9] = PAL_KEYCODE_F9; - s_Keyboard.keycodes[VK_F10] = PAL_KEYCODE_F10; - s_Keyboard.keycodes[VK_F11] = PAL_KEYCODE_F11; - s_Keyboard.keycodes[VK_F12] = PAL_KEYCODE_F12; - // Control - s_Keyboard.keycodes[VK_ESCAPE] = PAL_KEYCODE_ESCAPE; - s_Keyboard.keycodes[VK_RETURN] = PAL_KEYCODE_ENTER; - s_Keyboard.keycodes[VK_TAB] = PAL_KEYCODE_TAB; - s_Keyboard.keycodes[VK_BACK] = PAL_KEYCODE_BACKSPACE; s_Keyboard.keycodes[VK_SPACE] = PAL_KEYCODE_SPACE; - s_Keyboard.keycodes[VK_CAPITAL] = PAL_KEYCODE_CAPSLOCK; - s_Keyboard.keycodes[VK_NUMLOCK] = PAL_KEYCODE_NUMLOCK; - s_Keyboard.keycodes[VK_SCROLL] = PAL_KEYCODE_SCROLLLOCK; - s_Keyboard.keycodes[VK_SHIFT] = PAL_KEYCODE_LSHIFT; - s_Keyboard.keycodes[VK_RSHIFT] = PAL_KEYCODE_RSHIFT; - s_Keyboard.keycodes[VK_CONTROL] = PAL_KEYCODE_LCTRL; - s_Keyboard.keycodes[VK_RCONTROL] = PAL_KEYCODE_RCTRL; - s_Keyboard.keycodes[VK_MENU] = PAL_KEYCODE_LALT; - s_Keyboard.keycodes[VK_RMENU] = PAL_KEYCODE_RALT; - - // Arrows - s_Keyboard.keycodes[VK_LEFT] = PAL_KEYCODE_LEFT; - s_Keyboard.keycodes[VK_RIGHT] = PAL_KEYCODE_RIGHT; - s_Keyboard.keycodes[VK_UP] = PAL_KEYCODE_UP; - s_Keyboard.keycodes[VK_DOWN] = PAL_KEYCODE_DOWN; - - // Navigation - s_Keyboard.keycodes[VK_INSERT] = PAL_KEYCODE_INSERT; - s_Keyboard.keycodes[VK_DELETE] = PAL_KEYCODE_DELETE; - s_Keyboard.keycodes[VK_HOME] = PAL_KEYCODE_HOME; - s_Keyboard.keycodes[VK_END] = PAL_KEYCODE_END; - s_Keyboard.keycodes[VK_PRIOR] = PAL_KEYCODE_PAGEUP; - s_Keyboard.keycodes[VK_NEXT] = PAL_KEYCODE_PAGEDOWN; - - // Keypad - s_Keyboard.keycodes[VK_NUMPAD0] = PAL_KEYCODE_KP_0; - s_Keyboard.keycodes[VK_NUMPAD1] = PAL_KEYCODE_KP_1; - s_Keyboard.keycodes[VK_NUMPAD2] = PAL_KEYCODE_KP_2; - s_Keyboard.keycodes[VK_NUMPAD3] = PAL_KEYCODE_KP_3; - s_Keyboard.keycodes[VK_NUMPAD4] = PAL_KEYCODE_KP_4; - s_Keyboard.keycodes[VK_NUMPAD5] = PAL_KEYCODE_KP_5; - s_Keyboard.keycodes[VK_NUMPAD6] = PAL_KEYCODE_KP_6; - s_Keyboard.keycodes[VK_NUMPAD7] = PAL_KEYCODE_KP_7; - s_Keyboard.keycodes[VK_NUMPAD8] = PAL_KEYCODE_KP_8; - s_Keyboard.keycodes[VK_NUMPAD9] = PAL_KEYCODE_KP_9; - - s_Keyboard.keycodes[VK_ADD] = PAL_KEYCODE_KP_ADD; - s_Keyboard.keycodes[VK_SUBTRACT] = PAL_KEYCODE_KP_SUBTRACT; - s_Keyboard.keycodes[VK_MULTIPLY] = PAL_KEYCODE_KP_MULTIPLY; - s_Keyboard.keycodes[VK_DIVIDE] = PAL_KEYCODE_KP_DIVIDE; - s_Keyboard.keycodes[VK_DECIMAL] = PAL_KEYCODE_KP_DECIMAL; // Misc - s_Keyboard.keycodes[VK_SNAPSHOT] = PAL_KEYCODE_PRINTSCREEN; - s_Keyboard.keycodes[VK_PAUSE] = PAL_KEYCODE_PAUSE; - s_Keyboard.keycodes[VK_APPS] = PAL_KEYCODE_MENU; s_Keyboard.keycodes[VK_OEM_7] = PAL_KEYCODE_APOSTROPHE; s_Keyboard.keycodes[VK_OEM_5] = PAL_KEYCODE_BACKSLASH; s_Keyboard.keycodes[VK_OEM_COMMA] = PAL_KEYCODE_COMMA; @@ -975,8 +887,6 @@ static void createKeycodeTable() s_Keyboard.keycodes[VK_OEM_2] = PAL_KEYCODE_SLASH; s_Keyboard.keycodes[VK_OEM_4] = PAL_KEYCODE_LBRACKET; s_Keyboard.keycodes[VK_OEM_6] = PAL_KEYCODE_RBRACKET; - s_Keyboard.keycodes[VK_LWIN] = PAL_KEYCODE_LSUPER; - s_Keyboard.keycodes[VK_RWIN] = PAL_KEYCODE_RSUPER; } static void createScancodeTable() @@ -1199,7 +1109,7 @@ PalResult PAL_CALL palInitVideo( } // set a flag to check if the window has been created - SetWindowLongPtrW(s_Video.hiddenWindow, GWLP_USERDATA, (LONG_PTR)&s_Event); + SetPropW(s_Video.hiddenWindow, PAL_VIDEO_PROP, &s_Event); // register raw input for mice to get delta RAWINPUTDEVICE rid = {0}; @@ -1903,7 +1813,10 @@ PalResult PAL_CALL palCreateWindow( SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); } - SetWindowLongPtrW(handle, GWLP_USERDATA, (LONG_PTR)data); + data->isAttached = false; + data->cursor = nullptr; + data->wndProc = (LONG_PTR)videoProc; + SetPropW(handle, PAL_VIDEO_PROP, data); *outWindow = (PalWindow*)handle; return PAL_RESULT_SUCCESS; } @@ -1911,6 +1824,11 @@ PalResult PAL_CALL palCreateWindow( void PAL_CALL palDestroyWindow(PalWindow* window) { if (s_Video.initialized && window) { + WindowData* data = (WindowData*)GetPropW((HWND)window, PAL_VIDEO_PROP); + // destroy only PAL created window + if (data->isAttached) { + return; + } DestroyWindow((HWND)window); } } @@ -2253,7 +2171,7 @@ PalResult PAL_CALL palGetWindowState( } else if (wp.showCmd == SW_MAXIMIZE) { *outState = PAL_WINDOW_STATE_MAXIMIZED; - } else if (wp.showCmd == SW_RESTORE) { + } else if (wp.showCmd == SW_RESTORE || wp.showCmd == SW_NORMAL) { *outState = PAL_WINDOW_STATE_RESTORED; } @@ -2643,7 +2561,7 @@ PalResult PAL_CALL palCreateIcon( // convert RGBA to BGRA Uint8* pixels = (Uint8*)dibPixels; - for (int i = 0; i < info->width * info->height; i++) { + for (Uint32 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 @@ -2760,7 +2678,7 @@ PalResult PAL_CALL palCreateCursor( // convert RGBA to BGRA Uint8* pixels = (Uint8*)dibPixels; - for (int i = 0; i < info->width * info->height; i++) { + for (Uint32 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 @@ -2960,8 +2878,10 @@ PalResult PAL_CALL palSetWindowCursor( { if (window) { SetLastError(0); - WindowData* data = - (WindowData*)GetWindowLongPtrW((HWND)window, GWLP_USERDATA); + WindowData* data = (WindowData*)GetPropW((HWND)window, PAL_VIDEO_PROP); + if (!data) { + return PAL_RESULT_INVALID_WINDOW; + } data->cursor = (HCURSOR)cursor; DWORD error = GetLastError(); @@ -2978,4 +2898,81 @@ PalResult PAL_CALL palSetWindowCursor( } else { return PAL_RESULT_NULL_POINTER; } +} + +void* PAL_CALL palGetInstance() +{ + if (!s_Video.initialized) { + return nullptr; + } + + return (void*)s_Video.instance; +} + +PalResult PAL_CALL palAttachWindow( + void* windowHandle, + PalWindow** outWindow) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!windowHandle || !outWindow) { + return PAL_RESULT_NULL_POINTER; + } + + WindowData* data = getFreeWindowData(); + if (!data) { + return PAL_RESULT_OUT_OF_MEMORY; + } + + PalWindow* window = (PalWindow*)windowHandle; + data->isAttached = true; + data->wndProc = SetWindowLongPtrW( + (HWND)windowHandle, + GWLP_WNDPROC, + (LONG_PTR)videoProc); + + // use default PAL video cursor + // there is no way to get the cursor set on the native window + data->cursor = nullptr; + + // get state + palGetWindowState(window, &data->state); + SetPropW((HWND)window, PAL_VIDEO_PROP, data); + + *outWindow = window; + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL palDetachWindow( + PalWindow* window, + void** outWindowHandle) +{ + if (!s_Video.initialized) { + return PAL_RESULT_VIDEO_NOT_INITIALIZED; + } + + if (!window) { + return PAL_RESULT_NULL_POINTER; + } + + WindowData* data = nullptr; + data = (WindowData*)GetPropW((HWND)window, PAL_VIDEO_PROP); + if (!data) { + return PAL_RESULT_INVALID_WINDOW; + } + + if (data->isAttached == false) { + // window is owned by PAL + return PAL_RESULT_INVALID_WINDOW; + } + + data->used = false; + SetWindowLongPtrW((HWND)window, GWLP_WNDPROC, data->wndProc); + if (outWindowHandle) { + *outWindowHandle = (void*)window; + } + + return PAL_RESULT_SUCCESS; } \ No newline at end of file diff --git a/tests/attach_window_test.c b/tests/attach_window_test.c new file mode 100644 index 0000000..ae18294 --- /dev/null +++ b/tests/attach_window_test.c @@ -0,0 +1,365 @@ + +#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 + +#define CLASS_NAME L"NATIVE" +static HINSTANCE s_Instance; + +#elif defined(__linux__) +#include +#include + +typedef Window (*XCreateSimpleWindowFn)( + Display*, + Window, + int, + int, + unsigned int, + unsigned int, + unsigned int, + unsigned long, + unsigned long); + +typedef int (*XSyncFn)( + Display*, + Bool); + +typedef int (*XMapRaisedFn)( + Display*, + Window); + +typedef int (*XDestroyWindowFn)( + Display*, + Window); + +static void* s_X11Lib; +static XCreateSimpleWindowFn s_XCreateWindow; +static XSyncFn s_XSync; +static XMapRaisedFn s_XMapRaised; +static XDestroyWindowFn s_XDestroyWindow; + +#endif // _WIN32 + +#define WINDOW_POSX 100 +#define WINDOW_POSY 100 +#define WINDOW_WIDTH 640 +#define WINDOW_HEIGHT 480 +#define WINDOW_TITLE "PAL Attach Window Test" + +static void* createX11Window() +{ +#ifdef __linux__ + // load the procs + s_X11Lib = dlopen("libX11.so", RTLD_LAZY); + if (!s_X11Lib) { + return nullptr; + } + + // clang-format off + s_XCreateWindow = (XCreateSimpleWindowFn)dlsym( + s_X11Lib, + "XCreateSimpleWindow"); + + s_XSync = (XSyncFn)dlsym( + s_X11Lib, + "XSync"); + + s_XMapRaised = (XMapRaisedFn)dlsym( + s_X11Lib, + "XMapRaised"); + + s_XDestroyWindow = (XDestroyWindowFn)dlsym( + s_X11Lib, + "XDestroyWindow"); + + if (!s_XCreateWindow || !s_XSync || !s_XMapRaised || !s_XDestroyWindow) { + return nullptr; + } + + Display* display = (Display*)palGetInstance(); + if (!display) { + return nullptr; + } + + // we create a simple window with no event mask + int screen = DefaultScreen(display); + Window root = RootWindow(display, screen); + + Window window = s_XCreateWindow( + display, + root, + WINDOW_POSX, + WINDOW_POSX, + WINDOW_WIDTH, + WINDOW_HEIGHT, + 1, + BlackPixel(display, screen), + WhitePixel(display, screen)); + + if (!window) { + return nullptr; + } + + // make sure the window is mapped + s_XMapRaised(display, window); + s_XSync(display, False); + // clang-format on + + return (void*)(UintPtr)window; +#endif // __linux__ + return nullptr; +} + +static void* createWin32Window() +{ +#ifdef _WIN32 + // create a window class + s_Instance = (HINSTANCE)palGetInstance(); + WNDCLASSEXW wc = {0}; + wc.cbSize = sizeof(WNDCLASSEXW); + wc.hCursor = LoadCursorW(NULL, IDC_ARROW); + wc.hIcon = LoadIconW(NULL, IDI_APPLICATION); + wc.hIconSm = LoadIconW(NULL, IDI_APPLICATION); + wc.hInstance = s_Instance; + wc.lpfnWndProc = DefWindowProcW; + wc.lpszClassName = CLASS_NAME; + wc.style = CS_OWNDC; + if (!RegisterClassExW(&wc)) { + return nullptr; + } + + // create a simple window + HWND window = CreateWindowExW( + WS_EX_APPWINDOW, + CLASS_NAME, + L"", + WS_OVERLAPPEDWINDOW, + WINDOW_POSX, + WINDOW_POSY, + WINDOW_WIDTH, + WINDOW_HEIGHT, + nullptr, + nullptr, + s_Instance, + nullptr); + + if (!window) { + return nullptr; + } + + ShowWindow(window, SW_SHOW); + UpdateWindow(window); + return (void*)window; +#endif // _WIN32 + return nullptr; +} + +static void destroyWin32Window(void* windowHandle) +{ +#ifdef _WIN32 + DestroyWindow((HWND)windowHandle); + UnregisterClassW(CLASS_NAME, s_Instance); +#endif // _WIN32 +} + +static void destroyX11Window(void* windowHandle) +{ +#ifdef __linux__ + Display* display = palGetInstance(); + s_XDestroyWindow(display, (Window)(UintPtr)windowHandle); + dlclose(s_X11Lib); // we loaded dynamically +#endif +} + +static void* createPlatformWindow() +{ +#ifdef _WIN32 + return createWin32Window(); +#elif defined(__linux__) + return createX11Window(); +#endif // _WIN32 +} + +static void destroyPlatformWindow(void* windowHandle) +{ +#ifdef _WIN32 + destroyWin32Window(windowHandle); +#elif defined(__linux__) + destroyX11Window(windowHandle); +#endif // _WIN32 +} + +bool attachWindowTest() +{ + palLog(nullptr, ""); + palLog(nullptr, "==========================================="); + palLog(nullptr, "Attach Window Test"); + palLog(nullptr, "Press A to attach and D to detach window"); + 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; + } + + // we are interested in move and close events + palSetEventDispatchMode( + eventDriver, + PAL_EVENT_WINDOW_CLOSE, + PAL_DISPATCH_POLL); + + palSetEventDispatchMode( + eventDriver, + PAL_EVENT_WINDOW_MOVE, + PAL_DISPATCH_POLL); + + // we listen for key release events to attach and detach the window + palSetEventDispatchMode(eventDriver, PAL_EVENT_KEYUP, PAL_DISPATCH_POLL); + + // PAL allows users create any kind of window not currently or will not + // be supported by PAL and just attach the window. + // After attaching, the window acts as though it was created by PAL + // Attached window can be detached as well + // create your desired window with your all the styles you want + void* platformWindow = createPlatformWindow(); + if (!platformWindow) { + palLog(nullptr, "Failed to create platform window"); + return false; + } + + // attach the created window to PAL video system + // PAL can only attach windows that we created in the same processs + // and the same instance. Get instance with palGetInstance() + // destroying the window is the users responsibility + PalWindow* myWindow = nullptr; + result = palAttachWindow(platformWindow, &myWindow); + if (result != PAL_RESULT_SUCCESS) { + const char* error = palFormatResult(result); + palLog(nullptr, "Failed to attach window: %s", error); + return false; + } + + // 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); + palLog(nullptr, "Failed to attach window: %s", error); + return false; + } + + bool running = true; + bool detached = false; + Int32 counter = 0; + 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: { + // we check if its really our attached window + PalWindow* window = palUnpackPointer(event.data2); + if (window == myWindow) { + running = false; + } + break; + } + + case PAL_EVENT_WINDOW_MOVE: { + // this will be triggered for our attached window + Int32 x, y; // x == low, y == high + palUnpackInt32(event.data, &x, &y); + palLog(nullptr, "Window Moved: (%d, %d)", x, y); + break; + } + + case PAL_EVENT_KEYUP: { + // we detach the window after keyup + // since if the window is detached, + // we wont recieve the key up event + // keycode == low, scancode == high + Uint32 keycode; + palUnpackUint32(event.data, &keycode, nullptr); + if (keycode == PAL_KEYCODE_D) { + palDetachWindow(myWindow, nullptr); + palLog(nullptr, "window detached"); + palLog(nullptr, "not recieving events anymore"); + detached = true; + counter = 0; + } + break; + } + } + } + + if (detached) { + counter++; + } + + if (counter >= 2000000) { // we need a good delay + palAttachWindow(platformWindow, &myWindow); + palLog(nullptr, "window attached"); + palLog(nullptr, "recieving events"); + counter = 0; // reset it + detached = false; + } + } + + void* myPlatformWindow = nullptr; + // myPlatformWindow will be equal our platformWindow + result = palDetachWindow(myWindow, &myPlatformWindow); + if (result != PAL_RESULT_SUCCESS) { + const char* error = palFormatResult(result); + palLog(nullptr, "Failed to attach window: %s", error); + return false; + } + + // We need to destroy the platform window before we shutdown + // PAL video since the window was created with PAL video instance + destroyPlatformWindow(platformWindow); + + // shutdown PAL video + palShutdownVideo(); + palDestroyEventDriver(eventDriver); + + return true; +} \ No newline at end of file diff --git a/tests/char_event_test.c b/tests/char_event_test.c new file mode 100644 index 0000000..3f2a106 --- /dev/null +++ b/tests/char_event_test.c @@ -0,0 +1,107 @@ + +#include "pal/pal_video.h" +#include "tests.h" + +bool charEventTest() +{ + palLog(nullptr, ""); + palLog(nullptr, "==========================================="); + palLog(nullptr, "Character Event Test"); + palLog(nullptr, "==========================================="); + palLog(nullptr, ""); + + PalResult result; + PalWindow* window = nullptr; + PalWindowCreateInfo createInfo = {0}; + + // 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; + } + + // fill the create info struct + createInfo.monitor = nullptr; // use primary monitor + createInfo.height = 480; + createInfo.width = 640; + createInfo.show = true; + createInfo.title = "PAL Character Window"; + + // create the window with the create info struct + result = palCreateWindow(&createInfo, &window); + if (result != PAL_RESULT_SUCCESS) { + const char* error = palFormatResult(result); + palLog(nullptr, "Failed to create window: %s", error); + return false; + } + + // we only neeed PAL_EVENT_KEYCHAR and PAL_EVENT_WINDOW_CLOSE + palSetEventDispatchMode( + eventDriver, + PAL_EVENT_WINDOW_CLOSE, + PAL_DISPATCH_POLL); + + palSetEventDispatchMode(eventDriver, PAL_EVENT_KEYCHAR, 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_KEYCHAR: { + Uint32 codepoint = (Uint32)event.data; + PalWindow* window = palUnpackPointer(event.data2); + + // we log the codepoint + // and also log if its A or a charracter + palLog(nullptr, "U+%04X", codepoint); + + if (codepoint == 0x0041) { + palLog(nullptr, "A Character"); + } else if (codepoint == 0x0061) { + // small a + palLog(nullptr, "a Character"); + } + break; + } + } + } + + // update + } + + // 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/multi_thread_opengl_test.c b/tests/multi_thread_opengl_test.c new file mode 100644 index 0000000..b375b53 --- /dev/null +++ b/tests/multi_thread_opengl_test.c @@ -0,0 +1,395 @@ + +#include "pal/pal_opengl.h" +#include "pal/pal_thread.h" +#include "pal/pal_video.h" +#include "tests.h" + +// opengl typedefs +typedef void(PAL_GL_APIENTRY* PFNGLCLEARCOLORPROC)( + float red, + float green, + float blue, + float alpha); + +typedef void(PAL_GL_APIENTRY* PFNGLCLEARPROC)( + Uint32 mask); // use GL typedefs if needed + +typedef void (*glFlushFn)(); +typedef void (*glBeginFn)(unsigned int); +typedef void (*glEndFn)(); +typedef unsigned int (*glGetErrorFn)(); + +typedef void (*glVertex2fFn)( + float, + float); + +typedef void (*glColor3fFn)( + float, + float, + float); + +typedef void (*glViewportFn)( + int, + int, + int, + int); + +typedef struct { + bool driverCreated; + bool running; + PalEventDriver* videoEventDriver; + PalEventDriver* openglEventDriver; + PalGLContext* context; + PalGLWindow window; +} SharedState; + +static void* PAL_CALL eventDriverWorker(void* arg) +{ + PalResult result; + SharedState* shared = (SharedState*)arg; + if (!shared) { + palLog(nullptr, "Failed to get thread arg"); + return nullptr; + } + + PalEventDriverCreateInfo createInfo = {0}; + result = palCreateEventDriver(&createInfo, &shared->videoEventDriver); + if (result != PAL_RESULT_SUCCESS) { + const char* error = palFormatResult(result); + palLog(nullptr, "Failed to create event driver: %s", error); + return nullptr; + } + + // create the opengl driver as well + result = palCreateEventDriver(&createInfo, &shared->openglEventDriver); + if (result != PAL_RESULT_SUCCESS) { + const char* error = palFormatResult(result); + palLog(nullptr, "Failed to create event driver: %s", error); + return nullptr; + } + + // set dispatch modes. opengl needs only window resize + palSetEventDispatchMode( + shared->openglEventDriver, + PAL_EVENT_WINDOW_SIZE, + PAL_DISPATCH_POLL); + + // video needs window close and resize + palSetEventDispatchMode( + shared->videoEventDriver, + PAL_EVENT_WINDOW_CLOSE, + PAL_DISPATCH_POLL); + + palSetEventDispatchMode( + shared->videoEventDriver, + PAL_EVENT_WINDOW_SIZE, + PAL_DISPATCH_POLL); + + // we are done + shared->driverCreated = true; + return nullptr; +} + +static void* PAL_CALL rendererWorkder(void* arg) +{ + PalResult result; + SharedState* shared = (SharedState*)arg; + if (!shared) { + palLog(nullptr, "Failed to get thread arg"); + return nullptr; + } + + // make the context current on the renderer thread + result = palMakeContextCurrent(&shared->window, shared->context); + if (result != PAL_RESULT_SUCCESS) { + const char* error = palFormatResult(result); + palLog(nullptr, "Failed to make opengl context current: %s", error); + return nullptr; + } + + // load function procs + PFNGLCLEARCOLORPROC glClearColor = nullptr; + PFNGLCLEARPROC glClear = nullptr; + glBeginFn glBegin; + glEndFn glEnd; + glVertex2fFn glVertex2f; + glColor3fFn glColor3f; + glFlushFn glFlush; + glGetErrorFn glGetError; + glViewportFn glViewport; + + glClearColor = (PFNGLCLEARCOLORPROC)palGLGetProcAddress("glClearColor"); + glClear = (PFNGLCLEARPROC)palGLGetProcAddress("glClear"); + glBegin = (glBeginFn)palGLGetProcAddress("glBegin"); + glEnd = (glEndFn)palGLGetProcAddress("glEnd"); + glVertex2f = (glVertex2fFn)palGLGetProcAddress("glVertex2f"); + glColor3f = (glColor3fFn)palGLGetProcAddress("glColor3f"); + glFlush = (glFlushFn)palGLGetProcAddress("glFlush"); + + glGetError = (glGetErrorFn)palGLGetProcAddress("glGetError"); + glViewport = (glViewportFn)palGLGetProcAddress("glViewport"); + + // set clear color + glViewport(0, 0, 640, 480); + glClearColor(.2f, .2f, .2f, .2f); + + // run our while loop over there + while (shared->running) { + PalEvent event; + while (palPollEvent(shared->openglEventDriver, &event)) { + switch (event.type) { + case PAL_EVENT_WINDOW_SIZE: { + Uint32 width, height; + palUnpackUint32(event.data, &width, &height); + palLog( + nullptr, + "Video driver sent a resize event (%d, %d)", + width, + height); + + glViewport(0, 0, width, height); + // we can optionally send back a user event + // to let the video driver know we have recieved the event + break; + } + } + } + + // clear the buffer + 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); + + glColor3f(0.0, 1.0, 0.0); + glVertex2f(0.5f, -0.5f); + + glColor3f(0.0, 0.0, 1.0); + glVertex2f(0.0f, 0.5f); + + 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); + return nullptr; + } + } + + return nullptr; +} + +bool multiThreadOpenGlTest() +{ + palLog(nullptr, ""); + palLog(nullptr, "==========================================="); + palLog(nullptr, "Multi Thread OpenGL Test"); + palLog(nullptr, "==========================================="); + palLog(nullptr, ""); + + PalResult result; + PalThread* eventDriverThread = nullptr; + + SharedState* shared = nullptr; + shared = palAllocate(nullptr, sizeof(SharedState), 0); + if (!shared) { + palLog(nullptr, "Failed to allocate shared state"); + return false; + } + + // create a thread that creates two event drivers + PalThreadCreateInfo threadCreateInfo = {0}; + threadCreateInfo.entry = eventDriverWorker; + threadCreateInfo.arg = (void*)shared; + result = palCreateThread(&threadCreateInfo, &eventDriverThread); + if (result != PAL_RESULT_SUCCESS) { + const char* error = palFormatResult(result); + palLog(nullptr, "Failed to create thread: %s", error); + return false; + } + + // initialize video and opengl systems + // we need to initialize these on the main thread + result = palInitGL(nullptr); + if (result != PAL_RESULT_SUCCESS) { + const char* error = palFormatResult(result); + palLog(nullptr, "Failed to initialize opengl: %s", error); + return false; + } + + // get all FBConfigs and select one + Int32 fbCount = 0; + result = palEnumerateGLFBConfigs(nullptr, &fbCount, nullptr); + if (result != PAL_RESULT_SUCCESS) { + const char* error = palFormatResult(result); + palLog(nullptr, "Failed to query GL FBConfigs: %s", error); + return false; + } + + if (fbCount == 0) { + palLog(nullptr, "No supported FBConfig found"); + return false; + } + + PalGLFBConfig* fbConfigs = nullptr; + fbConfigs = palAllocate(nullptr, sizeof(PalGLFBConfig) * fbCount, 0); + if (!fbConfigs) { + palLog(nullptr, "Failed to allocate memory"); + return false; + } + + result = palEnumerateGLFBConfigs(nullptr, &fbCount, fbConfigs); + if (result != PAL_RESULT_SUCCESS) { + const char* error = palFormatResult(result); + palLog(nullptr, "Failed to query GL FBConfigs: %s", error); + palFree(nullptr, fbConfigs); + return false; + } + + // we get our desired FBConfig + PalGLFBConfig desired = {0}; + desired.redBits = 8; + desired.greenBits = 8; + desired.blueBits = 8; + desired.alphaBits = 8; + desired.alphaBits = 8; + desired.depthBits = 24; + desired.stencilBits = 8; + desired.samples = 2; + desired.stereo = false; // not widely supported + desired.sRGB = true; + desired.doubleBuffer = true; + + 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); + if (result != PAL_RESULT_SUCCESS) { + const char* error = palFormatResult(result); + palLog(nullptr, "Failed to set FBConfig: %s", error); + return false; + } + + // all windows also needs to be created on the main thread + PalWindow* window = nullptr; + PalWindowCreateInfo windowCreateInfo = {0}; + windowCreateInfo.width = 640; + windowCreateInfo.height = 480; + windowCreateInfo.show = true; + windowCreateInfo.style = PAL_WINDOW_STYLE_RESIZABLE; + windowCreateInfo.title = "Multi Thread OpenGL Window"; + result = palCreateWindow(&windowCreateInfo, &window); + if (result != PAL_RESULT_SUCCESS) { + const char* error = palFormatResult(result); + palLog(nullptr, "Failed to create window: %s", error); + return false; + } + + // GL context needs to be created on the main thread + 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; + + PalGLContextCreateInfo contextCreateInfo = {0}; + contextCreateInfo.debug = true; + contextCreateInfo.fbConfig = closest; + contextCreateInfo.major = glInfo->major; + contextCreateInfo.minor = glInfo->minor; + contextCreateInfo.window = &shared->window; + + // 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 + if (glInfo->extensions & PAL_GL_EXTENSION_CONTEXT_PROFILE) { + contextCreateInfo.profile = PAL_GL_PROFILE_COMPATIBILITY; + } + + result = palCreateGLContext(&contextCreateInfo, &shared->context); + if (result != PAL_RESULT_SUCCESS) { + const char* error = palFormatResult(result); + palLog(nullptr, "Failed to create opengl context: %s", error); + palFree(nullptr, fbConfigs); + return false; + } + + shared->running = true; + + // we create a renderer thread for opengl + // we dont wait for the renderer thread since it has its own while loop + // we just passed events between the two event drivers + // create a thread that creates two event drivers + PalThread* rendererThread = nullptr; + threadCreateInfo.entry = rendererWorkder; + threadCreateInfo.arg = (void*)shared; + result = palCreateThread(&threadCreateInfo, &rendererThread); + if (result != PAL_RESULT_SUCCESS) { + const char* error = palFormatResult(result); + palLog(nullptr, "Failed to create thread: %s", error); + return false; + } + + // we run the video while loop here + while (shared->running) { + // update the video system to push video events + palUpdateVideo(); + + PalEvent event; + while (palPollEvent(shared->videoEventDriver, &event)) { + switch (event.type) { + case PAL_EVENT_WINDOW_CLOSE: { + shared->running = false; + break; + } + + case PAL_EVENT_WINDOW_SIZE: { + // tell the opengl driver about our size change + palPushEvent(shared->openglEventDriver, &event); + break; + } + } + } + } + + // 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 + // but we destroy them here for simplicity + palDestroyEventDriver(shared->videoEventDriver); + palDestroyEventDriver(shared->openglEventDriver); + palFree(nullptr, fbConfigs); + + // destroy the renderer thread + palDetachThread(rendererThread); + + return true; +} \ No newline at end of file diff --git a/tests/opengl_context_test.c b/tests/opengl_context_test.c index c5fbed0..8d0e568 100644 --- a/tests/opengl_context_test.c +++ b/tests/opengl_context_test.c @@ -144,7 +144,7 @@ bool openglContextTest() 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; } diff --git a/tests/tests.h b/tests/tests.h index d4ca572..867fbee 100644 --- a/tests/tests.h +++ b/tests/tests.h @@ -36,6 +36,8 @@ bool iconTest(); bool cursorTest(); bool inputWindowTest(); bool systemCursorTest(); +bool attachWindowTest(); +bool charEventTest(); // opengl test bool openglTest(); @@ -45,4 +47,7 @@ bool openglFBConfigTest(); bool openglContextTest(); bool openglMultiContextTest(); +// opengl, video and thread +bool multiThreadOpenGlTest(); + #endif // _TESTS_H \ No newline at end of file diff --git a/tests/tests.lua b/tests/tests.lua index 5af61d0..608f5f6 100644 --- a/tests/tests.lua +++ b/tests/tests.lua @@ -39,7 +39,9 @@ project "tests" "icon_test.c", "cursor_test.c", "input_window_test.c", - "system_cursor_test.c" + "system_cursor_test.c", + "attach_window_test.c", + "char_event_test.c" } end @@ -57,5 +59,11 @@ project "tests" } end + if (PAL_BUILD_OPENGL and PAL_BUILD_VIDEO and PAL_BUILD_THREAD) then + files { + "multi_thread_opengl_test.c" + } + end + includedirs { "%{wks.location}/include" } links { "PAL" } \ No newline at end of file diff --git a/tests/tests_main.c b/tests/tests_main.c index a972fa1..3a22feb 100644 --- a/tests/tests_main.c +++ b/tests/tests_main.c @@ -34,6 +34,8 @@ int main(int argc, char** argv) registerTest("Cursor Test", cursorTest); registerTest("Input Window Test", inputWindowTest); registerTest("System Cursor Test", systemCursorTest); + registerTest("Attach Window Test", attachWindowTest); + registerTest("Character Event Test", charEventTest); #endif // PAL_HAS_VIDEO #if PAL_HAS_OPENGL @@ -48,6 +50,10 @@ int main(int argc, char** argv) registerTest("Opengl Multi Context Test", openglMultiContextTest); #endif // PAL_HAS_OPENGL +#if PAL_HAS_OPENGL && PAL_HAS_VIDEO && PAL_HAS_THREAD + registerTest("Multi Thread OpenGL Test", multiThreadOpenGlTest); +#endif // + runTests(); return 0; } \ No newline at end of file diff --git a/tests/window_test.c b/tests/window_test.c index f5f89c7..eb80724 100644 --- a/tests/window_test.c +++ b/tests/window_test.c @@ -144,6 +144,9 @@ static void PAL_CALL onEvent( } else if (event->type == PAL_EVENT_MONITOR_LIST_CHANGED) { onMonitorList(event); + + } else { + return; } }