diff --git a/README.md b/README.md index 8568686a..8525b2df 100644 --- a/README.md +++ b/README.md @@ -191,6 +191,7 @@ information on what to include when reporting a bug. - Bugfix: Invalid library paths were used in test and example CMake files (#930) - Bugfix: The scancode for synthetic key release events was always zero - Bugfix: The generated Doxyfile did not handle paths with spaces (#1081) +- [Win32] Added `glfwAttachWin32Window` for wrapping an existing `HWND` (#25) - [Win32] Added system error strings to relevant GLFW error descriptions (#733) - [Win32] Moved to `WM_INPUT` for disabled cursor mode motion input (#125) - [Win32] Removed XInput circular deadzone from joystick axis data (#1045) diff --git a/include/GLFW/glfw3native.h b/include/GLFW/glfw3native.h index 4372cb76..f21bce41 100644 --- a/include/GLFW/glfw3native.h +++ b/include/GLFW/glfw3native.h @@ -172,6 +172,34 @@ GLFWAPI const char* glfwGetWin32Monitor(GLFWmonitor* monitor); * @ingroup native */ GLFWAPI HWND glfwGetWin32Window(GLFWwindow* window); + +/*! @brief Wraps an existing `HWND` in a new GLFW window object. + * + * This function creates a GLFW window object and its associated OpenGL or + * OpenGL ES context for an existing `HWND`. The `HWND` is not destroyed by + * GLFW. + * + * @param[in] handle The `HWND` to attach to the window object. + * @param[in] share The window whose context to share resources with, or `NULL` + * to not share resources. + * @return The handle of the created window, or `NULL` if an + * [error](@ref error_handling) occurred. + * + * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref + * GLFW_INVALID_ENUM, @ref GLFW_INVALID_VALUE, @ref GLFW_API_UNAVAILABLE, @ref + * GLFW_VERSION_UNAVAILABLE, @ref GLFW_FORMAT_UNAVAILABLE and @ref + * GLFW_PLATFORM_ERROR. + * + * @thread_safety This function may be called from any thread. + * + * @sa @ref window_creation + * @sa @ref glfwCreateWindow + * + * @since Added in version 3.3. + * + * @ingroup native + */ +GLFWAPI GLFWwindow* glfwAttachWin32Window(HWND handle, GLFWwindow* share); #endif #if defined(GLFW_EXPOSE_NATIVE_WGL) diff --git a/src/win32_platform.h b/src/win32_platform.h index 72718ada..b3535d69 100644 --- a/src/win32_platform.h +++ b/src/win32_platform.h @@ -272,6 +272,7 @@ typedef struct _GLFWwindowWin32 GLFWbool maximized; // Whether to enable framebuffer transparency on DWM GLFWbool transparent; + GLFWbool external; // The last received cursor position, regardless of source int lastCursorPosX, lastCursorPosY; diff --git a/src/win32_window.c b/src/win32_window.c index d6959d07..e08c7752 100644 --- a/src/win32_window.c +++ b/src/win32_window.c @@ -1261,7 +1261,7 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window) if (_glfw.win32.disabledCursorWindow == window) _glfw.win32.disabledCursorWindow = NULL; - if (window->win32.handle) + if (window->win32.handle && !window->win32.external) { RemovePropW(window->win32.handle, L"GLFW"); DestroyWindow(window->win32.handle); @@ -1998,3 +1998,102 @@ GLFWAPI HWND glfwGetWin32Window(GLFWwindow* handle) return window->win32.handle; } +GLFWAPI GLFWwindow* glfwAttachWin32Window(HWND handle, GLFWwindow* share) +{ + _GLFWfbconfig fbconfig; + _GLFWctxconfig ctxconfig; + _GLFWwndconfig wndconfig; + _GLFWwindow* window; + + _GLFW_REQUIRE_INIT_OR_RETURN(NULL); + + fbconfig = _glfw.hints.framebuffer; + ctxconfig = _glfw.hints.context; + wndconfig = _glfw.hints.window; + + ctxconfig.share = (_GLFWwindow*) share; + if (ctxconfig.share) + { + if (ctxconfig.client == GLFW_NO_API || + ctxconfig.share->context.client == GLFW_NO_API) + { + _glfwInputError(GLFW_NO_WINDOW_CONTEXT, NULL); + return NULL; + } + } + + if (!_glfwIsValidContextConfig(&ctxconfig)) + return NULL; + + window = calloc(1, sizeof(_GLFWwindow)); + window->next = _glfw.windowListHead; + _glfw.windowListHead = window; + + window->autoIconify = wndconfig.autoIconify; + window->cursorMode = GLFW_CURSOR_NORMAL; + + window->minwidth = GLFW_DONT_CARE; + window->minheight = GLFW_DONT_CARE; + window->maxwidth = GLFW_DONT_CARE; + window->maxheight = GLFW_DONT_CARE; + window->numer = GLFW_DONT_CARE; + window->denom = GLFW_DONT_CARE; + + window->win32.handle = handle; + window->win32.external = GLFW_TRUE; + + SetPropW(window->win32.handle, L"GLFW", window); + SetWindowLongPtrW(window->win32.handle, GWLP_WNDPROC, (LONG_PTR) windowProc); + + { + const DWORD style = GetWindowLongW(window->win32.handle, GWL_STYLE); + const DWORD exStyle = GetWindowLongW(window->win32.handle, GWL_EXSTYLE); + + if (style & WS_THICKFRAME) + window->resizable = GLFW_TRUE; + if (style & (WS_BORDER | WS_THICKFRAME)) + window->decorated = GLFW_TRUE; + if (exStyle & WS_EX_TOPMOST) + window->floating = GLFW_TRUE; + + window->win32.maximized = IsZoomed(window->win32.handle); + window->win32.iconified = IsIconic(window->win32.handle); + } + + if (ctxconfig.client != GLFW_NO_API) + { + if (ctxconfig.source == GLFW_NATIVE_CONTEXT_API) + { + if (!_glfwInitWGL()) + return GLFW_FALSE; + if (!_glfwCreateContextWGL(window, &ctxconfig, &fbconfig)) + return GLFW_FALSE; + } + else if (ctxconfig.source == GLFW_EGL_CONTEXT_API) + { + if (!_glfwInitEGL()) + return GLFW_FALSE; + if (!_glfwCreateContextEGL(window, &ctxconfig, &fbconfig)) + return GLFW_FALSE; + } + else if (ctxconfig.source == GLFW_OSMESA_CONTEXT_API) + { + if (!_glfwInitOSMesa()) + return GLFW_FALSE; + if (!_glfwCreateContextOSMesa(window, &ctxconfig, &fbconfig)) + return GLFW_FALSE; + } + } + + if (ctxconfig.client != GLFW_NO_API) + { + if (!_glfwRefreshContextAttribs(window, &ctxconfig)) + { + glfwDestroyWindow((GLFWwindow*) window); + return NULL; + } + } + + return (GLFWwindow*) window; +} + diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a9900a0d..82f8fd63 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -39,6 +39,12 @@ add_executable(timeout WIN32 MACOSX_BUNDLE timeout.c ${GLAD}) add_executable(title WIN32 MACOSX_BUNDLE title.c ${GLAD}) add_executable(windows WIN32 MACOSX_BUNDLE windows.c ${GETOPT} ${GLAD}) +if (WIN32) + add_executable(native WIN32 native.c ${GLAD}) +else() + message(FATAL_ERROR "This branch only makes sense on Win32 at the moment") +endif() + target_link_libraries(empty "${CMAKE_THREAD_LIBS_INIT}") target_link_libraries(threads "${CMAKE_THREAD_LIBS_INIT}") if (RT_LIBRARY) @@ -46,7 +52,7 @@ if (RT_LIBRARY) target_link_libraries(threads "${RT_LIBRARY}") endif() -set(WINDOWS_BINARIES empty gamma icon inputlag joysticks opacity tearing +set(WINDOWS_BINARIES empty gamma icon inputlag joysticks native opacity tearing threads timeout title windows) set(CONSOLE_BINARIES clipboard events msaa glfwinfo iconify monitors reopen cursor) diff --git a/tests/native.c b/tests/native.c new file mode 100644 index 00000000..f42c182f --- /dev/null +++ b/tests/native.c @@ -0,0 +1,116 @@ +//======================================================================== +// Win32 native handle attachment test +// Copyright (c) Camilla Löwy +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would +// be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, and must not +// be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source +// distribution. +// +//======================================================================== + +#define UNICODE + +#include +#include +#define GLFW_EXPOSE_NATIVE_WIN32 +#include + +#include +#include + +static void error_callback(int error, const char* description) +{ + fprintf(stderr, "Error: %s\n", description); +} + +static void framebuffer_size_callback(GLFWwindow* window, int width, int height) +{ + glViewport(0, 0, width, height); +} + +static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + // This will only be used until glfwAttachWin32Window + return DefWindowProcW(hWnd, uMsg, wParam, lParam); +} + +int main(void) +{ + GLFWwindow* window; + WNDCLASSEX wc; + HWND handle; + + ZeroMemory(&wc, sizeof(wc)); + wc.cbSize = sizeof(wc); + wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; + wc.lpfnWndProc = (WNDPROC) windowProc; + wc.hInstance = GetModuleHandleW(NULL); + wc.hCursor = LoadCursorW(NULL, IDC_ARROW); + wc.lpszClassName = L"SomeKindOfWindowClassName"; + + if (!RegisterClassExW(&wc)) + exit(EXIT_FAILURE); + + handle = CreateWindowExW(WS_EX_APPWINDOW, + L"SomeKindOfWindowClassName", + L"HWND attachment test", + WS_OVERLAPPEDWINDOW | WS_VISIBLE, + CW_USEDEFAULT, CW_USEDEFAULT, + 600, 400, + NULL, + NULL, + GetModuleHandleW(NULL), + NULL); + + if (!handle) + exit(EXIT_FAILURE); + + glfwSetErrorCallback(error_callback); + + if (!glfwInit()) + { + DestroyWindow(handle); + exit(EXIT_FAILURE); + } + + window = glfwAttachWin32Window(handle, NULL); + if (!window) + { + glfwTerminate(); + DestroyWindow(handle); + exit(EXIT_FAILURE); + } + + glfwMakeContextCurrent(window); + gladLoadGLLoader((GLADloadproc) glfwGetProcAddress); + glfwSwapInterval(1); + + glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); + + while (!glfwWindowShouldClose(window)) + { + glClear(GL_COLOR_BUFFER_BIT); + glfwSwapBuffers(window); + glfwWaitEvents(); + } + + glfwTerminate(); + DestroyWindow(handle); + exit(EXIT_SUCCESS); +} +