From 805cc4cec440525629758af918d50a850209ec0b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?John=20Ankarstr=C3=B6m?= <john@ankarstrom.se>
Date: Tue, 9 Aug 2022 21:49:18 +0200
Subject: Add WithNextWindow function.

I'm not sure if this clarifies or complicates the control flow. My
hope is the former.
---
 c/main.cpp |  30 +++++++-------
 c/win.cpp  | 130 +++++++++++++++++++++++++++++++++++++++----------------------
 c/win.h    |   4 +-
 3 files changed, 100 insertions(+), 64 deletions(-)

diff --git a/c/main.cpp b/c/main.cpp
index 758889f..e51e0f9 100644
--- a/c/main.cpp
+++ b/c/main.cpp
@@ -52,7 +52,7 @@ BOOL (*IsThemeActive)();
 BOOL (*SetWindowTheme)(HWND, const wchar_t*, const wchar_t*);
 
 /* Initialize important global state on parent window creation. */
-static LRESULT CALLBACK CBTProc(int, WPARAM, LPARAM);
+static void InitializeMainWindow(HWND);
 /* Process parent window commands. */
 static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
 /* Process main menu commands. */
@@ -66,7 +66,7 @@ static INT_PTR CALLBACK AboutDlgProc(HWND, UINT, WPARAM, LPARAM);
 /* Try to style application according to current Windows theme. */
 static void UpdateTheme();
 
-void OnTerminate() noexcept
+static void OnTerminate() noexcept
 {
 	const wchar_t* what = L"an exception";
 	WcharPtr why;
@@ -128,10 +128,10 @@ int WINAPI WinMain(const HINSTANCE hInstance, const HINSTANCE, char* const, cons
 	wc.hIconSm	 = LoadIcon(nullptr, IDI_APPLICATION);
 	Require(RegisterClassEx(&wc));
 
-	/* Create window. A CBT hook is used to initialize important
-	 * global variables before any messages are sent to the new
-	 * window. It is vital that the hook is set up correctly. */
-	const HHOOK hHook = Require(SetWindowsHookEx(WH_CBT, CBTProc, nullptr, GetCurrentThreadId()));
+	/* InitializeMainWindow is called before the first message is
+	 * sent to WndProc. This is important, as it initializes
+	 * global state on which WndProc relies. */
+	WithNextWindow(InitializeMainWindow);
 	const HWND hWnd = Require(CreateWindowEx(
 	    0,
 	    L"Episode Browser",
@@ -139,7 +139,6 @@ int WINAPI WinMain(const HINSTANCE hInstance, const HINSTANCE, char* const, cons
 	    WS_OVERLAPPEDWINDOW|WS_CLIPCHILDREN,
 	    CW_USEDEFAULT, CW_USEDEFAULT, 0, 0,
 	    nullptr, nullptr, hInstance, nullptr));
-	Require(UnhookWindowsHookEx(hHook));
 
 	g_hWndStatus = Require(CreateWindowEx(
 	    0,
@@ -167,21 +166,16 @@ int WINAPI WinMain(const HINSTANCE hInstance, const HINSTANCE, char* const, cons
 	return 0;
 }
 
-static LRESULT CALLBACK CBTProc(const int nCode, const WPARAM wParam, const LPARAM lParam)
+void InitializeMainWindow(const HWND hWnd)
 {
-	if (nCode < 0 || nCode != HCBT_CREATEWND || g_hWnd)
-		return CallNextHookEx(0, nCode, wParam, lParam);
-
 	/* This code is run ONCE, at the creation of the top-level
 	 * window -- before WndProc! This is important, as it
 	 * initializes global variables that are used by WndProc. */
 
-	g_hWnd = reinterpret_cast<HWND>(wParam);
-
 	/* Look up DPI. */
 	if (auto lib = Library::Maybe(L"User32.dll");
 	    auto GetDpiForWindow = lib->GetProcAddress<UINT(HWND)>("GetDpiForWindow"))
-		g_dpi = GetDpiForWindow(g_hWnd);
+		g_dpi = GetDpiForWindow(hWnd);
 
 	/* Load normal font. */
 	if (auto lib = Library::Maybe(L"User32.dll");
@@ -205,8 +199,8 @@ static LRESULT CALLBACK CBTProc(const int nCode, const WPARAM wParam, const LPAR
 	}
 
 	/* Create child windows. */
-	g_dlv = new DataListView(g_hWnd);
-	g_elv = new EpisodeListView(g_hWnd);
+	g_dlv = new DataListView(hWnd);
+	g_elv = new EpisodeListView(hWnd);
 
 	/* Get saved view settings. */
 	Pl("cfg","get_view_watched",&g_bViewWatched);
@@ -221,7 +215,9 @@ static LRESULT CALLBACK CBTProc(const int nCode, const WPARAM wParam, const LPAR
 	Pl("cfg","get_dlv_height",&dlvHeight);
 	g_dlv->SetHeight(dlvHeight);
 
-	return 0;
+	/* The global main window handle must only be set AFTER
+	 * successful initialization. */
+	g_hWnd = hWnd;
 }
 
 LRESULT CALLBACK WndProc(const HWND hWnd, const UINT uMsg, const WPARAM wParam, const LPARAM lParam)
diff --git a/c/win.cpp b/c/win.cpp
index 593b340..a68d5b3 100644
--- a/c/win.cpp
+++ b/c/win.cpp
@@ -3,6 +3,90 @@
 
 #include "win.h"
 
+void WithNextWindow(void (*proc)(HWND))
+{
+	/* WithNextWindow uses a CBT hook to call an arbitrary
+	 * procedure just before the creation of the next window. The
+	 * hook is removed as soon as HCBT_CREATEWND is received.
+	 * Thus, proc is guaranteed to be executed only once. Note
+	 * that static storage must be used, as SetWindowHookEx cannot
+	 * accept a capturing lambda as the hook procedure. */
+
+	static __thread auto procNext = proc;
+	static __thread HHOOK hHook = Require(SetWindowsHookEx(WH_CBT, [](const int nCode,
+	    const WPARAM wParam, const LPARAM lParam) -> LRESULT CALLBACK
+	{
+		if (nCode == HCBT_CREATEWND) {
+			Require(UnhookWindowsHookEx(hHook));
+			procNext(reinterpret_cast<HWND>(wParam));
+			return 0;
+		} else
+			return CallNextHookEx(0, nCode, wParam, lParam);
+	}, nullptr, GetCurrentThreadId()));
+}
+
+/* Display next created window in the center of given window. */
+static void CenterNextWindow(HWND hWnd)
+{
+	if (!hWnd)
+		return;
+
+	/* CenterNextWindow employs a trick similar to that above.
+	 * Note, however, that repositioning does not work unless it
+	 * is delayed until the new window is activated. This
+	 * complicates the code somewhat. */
+
+	static __thread HWND hWndParent = hWnd;
+	static __thread HWND hWndNext = nullptr;
+	static __thread HHOOK hHook = Require(SetWindowsHookEx(WH_CBT, [](const int nCode,
+	    const WPARAM wParam, const LPARAM lParam) noexcept -> LRESULT CALLBACK
+	{
+		if (!hWndNext && nCode == HCBT_CREATEWND) {
+			hWndNext = reinterpret_cast<HWND>(wParam);
+			return 0;
+		} else if (nCode == HCBT_ACTIVATE
+		    && reinterpret_cast<HWND>(wParam) == hWndNext) {
+			Require(UnhookWindowsHookEx(hHook));
+
+			long lStyle = GetWindowLong(hWndNext, GWL_STYLE);
+			if (!(lStyle & WS_POPUP)) return 0;
+
+			RECT rcMain, rcMsg;
+			GetWindowRect(hWndParent, &rcMain);
+			GetWindowRect(hWndNext, &rcMsg);
+			SetWindowPos(hWndNext, nullptr,
+			    rcMain.left+(rcMain.right-rcMain.left)/2-(rcMsg.right-rcMsg.left)/2,
+			    rcMain.top+(rcMain.bottom-rcMain.top)/2-(rcMsg.bottom-rcMsg.top)/2,
+			    -1, -1,
+			    SWP_NOZORDER|SWP_NOSIZE|SWP_NOACTIVATE);
+
+			return 0;
+		} else
+			return CallNextHookEx(0, nCode, wParam, lParam);
+	}, nullptr, GetCurrentThreadId()));
+}
+
+int EBMessageBox(const wchar_t* const wszText, const wchar_t* const wszCaption, const UINT uType)
+{
+	extern HWND g_hWnd;
+	CenterNextWindow(g_hWnd);
+	return MessageBox(g_hWnd, wszText, wszCaption, uType);
+}
+
+int GetRelativeCursorPos(const HWND hWnd, POINT* const pt) noexcept
+{
+	RECT rc;
+	if (!GetClientRect(hWnd, &rc)) return 0;
+	SetLastError(ERROR_SUCCESS);
+	if (!MapWindowPoints(hWnd, nullptr, (POINT*)&rc, 2)) return 0;
+
+	POINT ptMouse;
+	if (!GetCursorPos(&ptMouse)) return 0;
+	pt->x = ptMouse.x-rc.left;
+	pt->y = ptMouse.y-rc.top;
+	return 1;
+}
+
 Win32Error::Win32Error() noexcept
 	: code(GetLastError()) {}
 
@@ -64,49 +148,3 @@ Library::~Library()
 {
 	FreeLibrary(m_hModule);
 }
-
-int EBMessageBox(const wchar_t* const wszText, const wchar_t* const wszCaption, const UINT uType)
-{
-	extern HWND g_hWnd;
-
-	auto proc = [](const int nCode, const WPARAM wParam, const LPARAM lParam) noexcept
-		-> LRESULT CALLBACK
-	{
-		if (!g_hWnd || nCode < 0 || nCode != HCBT_ACTIVATE)
-			return CallNextHookEx(0, nCode, wParam, lParam);
-
-		HWND hWnd = reinterpret_cast<HWND>(wParam);
-		long lStyle = GetWindowLong(hWnd, GWL_STYLE);
-		if (!(lStyle & WS_POPUP)) return 0;
-
-		RECT rcMain, rcMsg;
-		GetWindowRect(g_hWnd, &rcMain);
-		GetWindowRect(hWnd, &rcMsg);
-		SetWindowPos(hWnd, nullptr,
-		    rcMain.left+(rcMain.right-rcMain.left)/2-(rcMsg.right-rcMsg.left)/2,
-		    rcMain.top+(rcMain.bottom-rcMain.top)/2-(rcMsg.bottom-rcMsg.top)/2,
-		    -1, -1,
-		    SWP_NOZORDER|SWP_NOSIZE|SWP_NOACTIVATE);
-
-		return 0;
-	};
-
-	HHOOK hHook = Require(SetWindowsHookEx(WH_CBT, proc, nullptr, GetCurrentThreadId()));
-	int r = MessageBox(g_hWnd, wszText, wszCaption, uType);
-	Require(UnhookWindowsHookEx(hHook));
-	return r;
-}
-
-int GetRelativeCursorPos(const HWND hWnd, POINT* const pt)
-{
-	RECT rc;
-	if (!GetClientRect(hWnd, &rc)) return 0;
-	SetLastError(ERROR_SUCCESS);
-	if (!MapWindowPoints(hWnd, nullptr, (POINT*)&rc, 2)) return 0;
-
-	POINT ptMouse;
-	if (!GetCursorPos(&ptMouse)) return 0;
-	pt->x = ptMouse.x-rc.left;
-	pt->y = ptMouse.y-rc.top;
-	return 1;
-}
diff --git a/c/win.h b/c/win.h
index ff38ce0..30d3cc5 100644
--- a/c/win.h
+++ b/c/win.h
@@ -4,10 +4,12 @@
 #include <optional>
 #include <windows.h>
 
+/* Run given procedure at creation of next window. */
+void WithNextWindow(void (*proc)(HWND));
 /* Display message box centered in main window. */
 int EBMessageBox(const wchar_t* wszText, const wchar_t* wszCaption, UINT uType);
 /* Retrieve mouse position relative to given window's client area. */
-int GetRelativeCursorPos(HWND hWnd, POINT* pt);
+int GetRelativeCursorPos(HWND hWnd, POINT* pt) noexcept;
 /* Cached values from GetSystemMetrics. */
 template <int I> auto Metric = GetSystemMetrics(I);
 
-- 
cgit v1.2.3