From 805cc4cec440525629758af918d50a850209ec0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Ankarstr=C3=B6m?= 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(wParam); - /* Look up DPI. */ if (auto lib = Library::Maybe(L"User32.dll"); auto GetDpiForWindow = lib->GetProcAddress("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(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(wParam); + return 0; + } else if (nCode == HCBT_ACTIVATE + && reinterpret_cast(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(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 #include +/* 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 auto Metric = GetSystemMetrics(I); -- cgit v1.2.3