Win32: Add glfwAttachWin32Window

Related to #25.
diff --git a/README.md b/README.md
index 8568686..8525b2d 100644
--- a/README.md
+++ b/README.md
@@ -191,6 +191,7 @@
 - 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 4372cb7..f21bce4 100644
--- a/include/GLFW/glfw3native.h
+++ b/include/GLFW/glfw3native.h
@@ -172,6 +172,34 @@
  *  @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 72718ad..b3535d6 100644
--- a/src/win32_platform.h
+++ b/src/win32_platform.h
@@ -272,6 +272,7 @@
     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 d6959d0..e08c775 100644
--- a/src/win32_window.c
+++ b/src/win32_window.c
@@ -1261,7 +1261,7 @@
     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 @@
     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 a9900a0..82f8fd6 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -39,6 +39,12 @@
 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 @@
     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 0000000..f42c182
--- /dev/null
+++ b/tests/native.c
@@ -0,0 +1,116 @@
+//========================================================================
+// Win32 native handle attachment test
+// Copyright (c) Camilla Löwy <elmindreda@glfw.org>
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+//    claim that you wrote the original software. If you use this software
+//    in a product, an acknowledgment in the product documentation would
+//    be appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such, and must not
+//    be misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source
+//    distribution.
+//
+//========================================================================
+
+#define UNICODE
+
+#include <glad/glad.h>
+#include <GLFW/glfw3.h>
+#define GLFW_EXPOSE_NATIVE_WIN32
+#include <GLFW/glfw3native.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+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);
+}
+