diff foosdk/sdk/libPPUI/DarkMode.cpp @ 1:20d02a178406 default tip

*: check in everything else yay
author Paper <paper@tflc.us>
date Mon, 05 Jan 2026 02:15:46 -0500
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/foosdk/sdk/libPPUI/DarkMode.cpp	Mon Jan 05 02:15:46 2026 -0500
@@ -0,0 +1,1931 @@
+#include "stdafx.h"
+#include "DarkMode.h"
+#include "DarkModeEx.h"
+#include "win32_utility.h"
+#include "win32_op.h"
+#include "PaintUtils.h"
+#include "ImplementOnFinalMessage.h"
+#include <vsstyle.h>
+#include "GDIUtils.h"
+#include "CListControl.h"
+#include "CListControl-Subst.h"
+#include "ReStyleWnd.h"
+#include <map>
+
+// Allow scary undocumented ordinal-dll-export functions?
+#define DARKMODE_ALLOW_HAX 1
+
+#define DARKMODE_DEBUG 0
+
+#if DARKMODE_DEBUG
+#define DARKMODE_DEBUG_PRINT(...) PFC_DEBUG_PRINT("DarkMode: ", __VA_ARGS__)
+#else
+#define DARKMODE_DEBUG_PRINT(...)
+#endif
+
+
+#include <dwmapi.h>
+#pragma comment(lib, "dwmapi.lib")
+
+/*
+
+==== DARK MODE KNOWLEDGE BASE ====
+
+== Generic window ==
+UpdateTitleBar() to set title bar to black
+
+== Dialog box ==
+Use WM_CTLCOLORDLG, background of 0x383838
+UpdateTitleBar() to set title bar to black
+
+== Edit box ==
+Use WM_CTLCOLOREDIT, background of 0x383838
+ApplyDarkThemeCtrl() with "Explorer"
+
+== Drop list combo ==
+Method #1: ::SetWindowTheme(wnd, L"DarkMode_CFD", nullptr);
+Method #2: ::SetWindowTheme(wnd, L"", L""); to obey WM_CTLCOLOR* but leaves oldstyle classic-colors button and breaks comboboxex
+
+== Button ==
+Use WM_CTLCOLORBTN, background of 0x383838
+ApplyDarkThemeCtrl() with "Explorer"
+
+== Scroll bar ==
+ApplyDarkThemeCtrl() with "Explorer"
+^^ on either scrollbar or the window that implicitly creates scrollbars
+
+== Header ==
+ApplyDarkThemeCtrl() with "ItemsView"
+Handle custom draw, override text color
+
+== Tree view ==
+ApplyDarkThemeCtrl() with "Explorer"
+Set text/bk colors explicitly
+
+Text color: 0xdedede
+Background: 0x191919
+
+Label-editing:
+Pass WM_CTLCOLOR* to parent, shim TVM_EDITLABEL to pass theme to the editbox (not really necessary tho)
+
+
+== Rebar ==
+Can be beaten into working to some extent with a combination of:
+* ::SetWindowTheme(toolbar, L"", L""); or else RB_SETTEXTCOLOR & REBARBANDINFO colors are disregarded
+* Use RB_SETTEXTCOLOR / SetTextColor() + RB_SETBKCOLOR / SetBkColor() to override text/background colors
+* Override WM_ERASEBKGND to draw band frames, RB_SETBKCOLOR doesn't seem to be thorough
+* NM_CUSTOMDRAW on parent window to paint band labels & grippers without annoying glitches
+NM_CUSTOMDRAW is buggy, doesn't hand you band indexes to query text, have to use hit tests to know what text to render
+
+Solution: full custom draw
+
+
+== Toolbar ==
+Source: https://stackoverflow.com/questions/61271578/winapi-toolbar-text-color
+Respects background color of its parent
+Override text:
+::SetWindowTheme(toolbar, L"", L""); or else NM_CUSTOMDRAW color is disregarded
+NM_CUSTOMDRAW handler: 
+switch (cd->nmcd.dwDrawStage) {
+	case CDDS_PREPAINT: return CDRF_NOTIFYITEMDRAW;
+	case CDDS_ITEMPREPAINT: cd->clrText = DarkMode::GetSysColor(COLOR_WINDOWTEXT); return CDRF_DODEFAULT;
+}
+
+== Tab control ==
+Full custom draw, see CTabsHook
+
+== List View ==
+Dark scrollbars are shown only if using "Explorer" theme, but other stuff needs "ItemsView" theme???
+Other projects shim Windows functions to bypass the above.
+Avoid using List View, use libPPUI CListControl instead.
+
+== List Box ==
+Use WM_CTLCOLOR*
+
+== Status Bar ==
+Full custom draw
+
+== Check box, radio button ==
+SetWindowTheme(wnd, L"", L""); works but not 100% pretty, disabled text ugly in particular
+Full custom draw preferred
+
+== Group box ===
+SetWindowTheme(wnd, L"", L""); works but not 100% pretty, disabled text ugly in particular
+Full custom draw preferred (we don't do this).
+Avoid disabling groupboxes / use something else.
+
+==== NOTES ====
+AllowDarkModeForWindow() needs SetPreferredAppMode() to take effect, hence we implicitly call it
+AllowDarkModeForWindow() must be called BEFORE SetWindowTheme() to take effect
+
+It seems it is interchangeable to do:
+AllowDarkModeForWindow(wnd, true); SetWindowTheme(wnd, L"foo", nullptr);
+vs
+SetWindowTheme(wnd, L"DarkMode_foo", nullptr)
+But the latter doesn't require undocumented function calls and doesn't infect all menus with dark mode
+*/
+
+namespace {
+	enum class PreferredAppMode
+	{
+		Default,
+		AllowDark,
+		ForceDark,
+		ForceLight,
+		Max
+	};
+
+	enum WINDOWCOMPOSITIONATTRIB
+	{
+		WCA_UNDEFINED = 0,
+		WCA_NCRENDERING_ENABLED = 1,
+		WCA_NCRENDERING_POLICY = 2,
+		WCA_TRANSITIONS_FORCEDISABLED = 3,
+		WCA_ALLOW_NCPAINT = 4,
+		WCA_CAPTION_BUTTON_BOUNDS = 5,
+		WCA_NONCLIENT_RTL_LAYOUT = 6,
+		WCA_FORCE_ICONIC_REPRESENTATION = 7,
+		WCA_EXTENDED_FRAME_BOUNDS = 8,
+		WCA_HAS_ICONIC_BITMAP = 9,
+		WCA_THEME_ATTRIBUTES = 10,
+		WCA_NCRENDERING_EXILED = 11,
+		WCA_NCADORNMENTINFO = 12,
+		WCA_EXCLUDED_FROM_LIVEPREVIEW = 13,
+		WCA_VIDEO_OVERLAY_ACTIVE = 14,
+		WCA_FORCE_ACTIVEWINDOW_APPEARANCE = 15,
+		WCA_DISALLOW_PEEK = 16,
+		WCA_CLOAK = 17,
+		WCA_CLOAKED = 18,
+		WCA_ACCENT_POLICY = 19,
+		WCA_FREEZE_REPRESENTATION = 20,
+		WCA_EVER_UNCLOAKED = 21,
+		WCA_VISUAL_OWNER = 22,
+		WCA_HOLOGRAPHIC = 23,
+		WCA_EXCLUDED_FROM_DDA = 24,
+		WCA_PASSIVEUPDATEMODE = 25,
+		WCA_USEDARKMODECOLORS = 26,
+		WCA_LAST = 27
+	};
+
+	struct WINDOWCOMPOSITIONATTRIBDATA
+	{
+		WINDOWCOMPOSITIONATTRIB Attrib;
+		PVOID pvData;
+		SIZE_T cbData;
+	};
+#if DARKMODE_ALLOW_HAX
+	using fnAllowDarkModeForWindow = bool (WINAPI*)(HWND hWnd, bool allow); // ordinal 133
+	using fnSetPreferredAppMode = PreferredAppMode(WINAPI*)(PreferredAppMode appMode); // ordinal 135, since 1809
+	using fnFlushMenuThemes = void (WINAPI*)(); // ordinal 136
+	fnAllowDarkModeForWindow _AllowDarkModeForWindow = nullptr;
+	fnSetPreferredAppMode _SetPreferredAppMode = nullptr;
+	fnFlushMenuThemes _FlushMenuThemes = nullptr;
+
+	bool ImportsInited = false;
+
+	void InitImports() {
+		if (ImportsInited) return;
+		if (DarkMode::IsSupportedSystem()) {
+			HMODULE hUxtheme = LoadLibraryEx(L"uxtheme.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
+			if (hUxtheme) {
+				_AllowDarkModeForWindow = reinterpret_cast<fnAllowDarkModeForWindow>(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(133)));
+				_SetPreferredAppMode = reinterpret_cast<fnSetPreferredAppMode>(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(135)));
+				_FlushMenuThemes = reinterpret_cast<fnFlushMenuThemes>(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(136)));
+			}
+		}
+		ImportsInited = true;
+	}
+#endif
+}
+
+
+
+namespace DarkMode {
+	UINT msgSetDarkMode() {
+		// No need to threadguard this, should be main thread only, not much harm even if it's not
+		static UINT val = 0;
+		if (val == 0) val = RegisterWindowMessage(L"libPPUI:msgSetDarkMode");
+		return val;
+	}
+	bool IsSupportedSystem() {
+		return Win10BuildNumber() >= 17763 && !IsWine(); // require at least Win10 1809 / Server 2019
+	}
+	bool IsWindows11() {
+		return Win10BuildNumber() >= 22000;
+	}
+	bool QueryUserOption() {
+		DWORD v = 0;
+		DWORD cb = sizeof(v);
+		DWORD type = 0;
+		if (RegGetValue(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", L"AppsUseLightTheme", RRF_RT_REG_DWORD, &type, &v, &cb) == 0) {
+			if (type == REG_DWORD) {
+				return v == 0;
+			}
+		}
+		return false;
+	}
+	void UpdateTitleBar(HWND hWnd, bool bDark) {
+		if (!IsSupportedSystem()) return;
+
+		CWindow wnd(hWnd);
+		DWORD style = wnd.GetStyle();
+		if ((style & WS_CAPTION) != WS_CAPTION) return;
+
+#if 0
+		// Some apps do this - no idea why, doesn't work
+		// Kept for future reference
+		AllowDarkModeForWindow(hWnd, bDark);
+		SetProp(hWnd, L"UseImmersiveDarkModeColors", (HANDLE)(INT_PTR)(bDark ? TRUE : FALSE));
+#endif
+
+		if (IsWindows11()) {
+			// DwmSetWindowAttribute()
+			// Windows 11 : works
+			// Windows 10 @ late 2021 : doesn't work
+			// Server 2019 : as good as SetWindowCompositionAttribute(), needs ModifyStyle() hack for full effect
+			BOOL arg = !!bDark;
+			DwmSetWindowAttribute(hWnd, 19 /*DWMWA_USE_IMMERSIVE_DARK_MODE*/, &arg, sizeof(arg));
+		} else {
+			// Windows 10 mode
+			using fnSetWindowCompositionAttribute = BOOL(WINAPI*)(HWND hWnd, WINDOWCOMPOSITIONATTRIBDATA*);
+			static fnSetWindowCompositionAttribute _SetWindowCompositionAttribute = reinterpret_cast<fnSetWindowCompositionAttribute>(GetProcAddress(GetModuleHandleW(L"user32.dll"), "SetWindowCompositionAttribute"));
+			if (_SetWindowCompositionAttribute != nullptr) {
+				BOOL dark = !!bDark;
+				WINDOWCOMPOSITIONATTRIBDATA data = { WCA_USEDARKMODECOLORS, &dark, sizeof(dark) };
+				_SetWindowCompositionAttribute(hWnd, &data);
+
+				// Neither of these fixes stuck titlebar (kept in here for future reference)
+				// ::RedrawWindow(hWnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE | RDW_FRAME | RDW_UPDATENOW);
+				// ::SetWindowPos(hWnd, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_DRAWFRAME);
+
+				// Apparently the least painful way to reliably fix stuck titlebar
+				// 2x SWP_FRAMECHANGED needed with actual style changes
+
+				if (style & WS_VISIBLE) { // Only do this if visible
+					wnd.ModifyStyle(WS_BORDER, 0, SWP_FRAMECHANGED);
+					wnd.ModifyStyle(0, WS_BORDER, SWP_FRAMECHANGED);
+				}
+
+			}
+		}
+	}
+
+	void ApplyDarkThemeCtrl2(HWND ctrl, bool bDark, const wchar_t* ThemeID_light, const wchar_t * ThemeID_dark) {
+		if (ctrl == NULL) return;
+		AllowDarkModeForWindow(ctrl, bDark);
+		if (bDark && IsSupportedSystem()) {
+			::SetWindowTheme(ctrl, ThemeID_dark, NULL);
+		} else {
+			::SetWindowTheme(ctrl, ThemeID_light, NULL);
+		}
+	}
+
+	void ApplyDarkThemeCtrl(HWND ctrl, bool bDark, const wchar_t* ThemeID) {
+		if ( ctrl == NULL ) return;
+		AllowDarkModeForWindow(ctrl, bDark);
+		if (bDark && IsSupportedSystem()) {
+			std::wstring temp = L"DarkMode_"; temp += ThemeID;
+			::SetWindowTheme(ctrl, temp.c_str(), NULL);
+		} else {
+			::SetWindowTheme(ctrl, ThemeID, NULL);
+		}
+	}
+
+	void DarkenEditLite(HWND ctrl) {
+		if (IsSupportedSystem()) {
+			::SetWindowTheme(ctrl, L"DarkMode_Explorer", NULL);
+		}
+	}
+
+	void DarkenComboLite(HWND ctrl) {
+		if (IsSupportedSystem()) {
+			::SetWindowTheme(ctrl, L"DarkMode_CFD", NULL);
+		}
+	}
+
+	bool IsDCDark(HDC dc_) {
+		CDCHandle dc(dc_);
+		return IsThemeDark(dc.GetTextColor(), dc.GetBkColor());
+	}
+	bool IsDialogDark(HWND dlg, UINT msgSend) {
+		CWindowDC dc(dlg);
+		dc.SetTextColor(0x000000);
+		dc.SetBkColor(0xFFFFFF);
+		::SendMessage(dlg, msgSend, (WPARAM)dc.m_hDC, (LPARAM)dlg);
+		return IsDCDark(dc);
+	}
+
+	COLORREF GetSysColor(int idx, bool bDark) {
+		if (!bDark) return ::GetSysColor(idx);
+		switch (idx) {
+		case COLOR_MENU:
+		case COLOR_BTNFACE:
+		case COLOR_WINDOW:
+		case COLOR_MENUBAR:
+			// Explorer:
+			// return 0x383838;
+			return 0x202020;
+		case COLOR_BTNSHADOW:
+			return 0;
+		case COLOR_WINDOWTEXT:
+		case COLOR_MENUTEXT:
+		case COLOR_BTNTEXT:
+		case COLOR_CAPTIONTEXT:
+			// Explorer:
+			// return 0xdedede;
+			return 0xC0C0C0;
+		case COLOR_BTNHIGHLIGHT:
+		case COLOR_MENUHILIGHT:
+			return 0x383838;
+		case COLOR_HIGHLIGHT:
+			return 0x777777;
+		case COLOR_HIGHLIGHTTEXT:
+			return 0x101010;
+		case COLOR_GRAYTEXT:
+			return 0x777777;
+		case COLOR_HOTLIGHT:
+			return 0xd69c56;
+		default:
+			return ::GetSysColor(idx);
+		}
+	}
+#if DARKMODE_ALLOW_HAX
+	void SetAppDarkMode(bool bDark) {
+		InitImports();
+
+		if (_SetPreferredAppMode != nullptr) {
+			static PreferredAppMode lastMode = PreferredAppMode::Default;
+			PreferredAppMode wantMode = bDark ? PreferredAppMode::ForceDark : PreferredAppMode::ForceLight;
+			if (lastMode != wantMode) {
+				_SetPreferredAppMode(wantMode);
+				lastMode = wantMode;
+				if (_FlushMenuThemes) _FlushMenuThemes();
+			}
+		}
+	}
+#else
+	void SetAppDarkMode(bool) {}
+#endif
+#if DARKMODE_ALLOW_HAX
+	void AllowDarkModeForWindow(HWND wnd, bool bDark) {
+		InitImports();
+
+		if (_AllowDarkModeForWindow) _AllowDarkModeForWindow(wnd, bDark);
+	}
+#else
+	void AllowDarkModeForWindow(HWND, bool) {}
+#endif
+
+	bool IsThemeDark(COLORREF text, COLORREF background) {
+		if (!IsSupportedSystem() || IsHighContrast()) return false;
+		auto l_text = PaintUtils::Luminance(text);
+		auto l_bk = PaintUtils::Luminance(background);
+		if (l_text > l_bk) {
+			if (l_bk <= PaintUtils::Luminance(GetSysColor(COLOR_BTNFACE))) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	bool IsHighContrast() {
+		HIGHCONTRASTW highContrast = { sizeof(highContrast) };
+		if (SystemParametersInfoW(SPI_GETHIGHCONTRAST, sizeof(highContrast), &highContrast, FALSE))
+			return (highContrast.dwFlags & HCF_HIGHCONTRASTON) != 0;
+		return false;
+	}
+
+	static void DrawTab(CTabCtrl& tabs, CDCHandle dc, int iTab, bool selected, bool focused, const RECT * rcPaint) {
+		(void)focused;
+		PFC_ASSERT((tabs.GetStyle() & TCS_VERTICAL) == 0);
+
+		CRect rc;
+		if (!tabs.GetItemRect(iTab, rc)) return;
+
+		if ( rcPaint != nullptr ) {
+			CRect foo;
+			if (!foo.IntersectRect(rc, rcPaint)) return;
+		}
+		const int edgeCX = MulDiv(1, QueryScreenDPI_X(tabs), 120); // note: MulDiv() rounds up from +0.5, this will
+		const auto colorBackground = GetSysColor(selected ? COLOR_HIGHLIGHT : COLOR_BTNFACE);
+		const auto colorFrame = GetSysColor(COLOR_WINDOWFRAME);
+		dc.SetDCBrushColor(colorBackground);
+		dc.FillSolidRect(rc, colorBackground);
+
+		{
+			CPen pen;
+			WIN32_OP_D(pen.CreatePen(PS_SOLID, edgeCX, colorFrame));
+			SelectObjectScope scope(dc, pen);
+			dc.MoveTo(rc.left, rc.bottom);
+			dc.LineTo(rc.left, rc.top);
+			dc.LineTo(rc.right, rc.top);
+			dc.LineTo(rc.right, rc.bottom);
+		}
+
+		wchar_t text[512] = {};
+		TCITEM item = {};
+		item.mask = TCIF_TEXT;
+		item.pszText = text;
+		item.cchTextMax = (int)(std::size(text) - 1);
+		if (tabs.GetItem(iTab, &item)) {
+			SelectObjectScope fontScope(dc, tabs.GetFont());
+			dc.SetBkMode(TRANSPARENT);
+			dc.SetTextColor(GetSysColor(selected ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT));
+			dc.DrawText(text, (int)wcslen(text), rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
+		}
+	}
+
+	void PaintTabs(CTabCtrl tabs, CDCHandle dc, const RECT * rcPaint) {
+		CRect rcClient; tabs.GetClientRect(rcClient); 
+		CRect rcArea = rcClient; tabs.AdjustRect(FALSE, rcArea);
+		int dx = rcClient.bottom - rcArea.bottom;
+		int dy = rcClient.right - rcArea.right;
+		CRect rcFrame = rcArea; rcFrame.InflateRect(dx/2, dy/2);
+		dc.SetDCBrushColor(GetSysColor(COLOR_WINDOWFRAME));
+		dc.FrameRect(rcFrame, (HBRUSH)GetStockObject(DC_BRUSH));
+		const int tabCount = tabs.GetItemCount();
+		const int tabSelected = tabs.GetCurSel();
+		const int tabFocused = tabs.GetCurFocus();
+		for (int iTab = 0; iTab < tabCount; ++iTab) {
+			if (iTab != tabSelected) DrawTab(tabs, dc, iTab, false, iTab == tabFocused, rcPaint);
+		}
+		if (tabSelected >= 0) DrawTab(tabs, dc, tabSelected, true, tabSelected == tabFocused, rcPaint);
+	}
+
+	void PaintTabsErase(CTabCtrl tabs, CDCHandle dc) {
+		CRect rcClient; WIN32_OP_D(tabs.GetClientRect(rcClient));
+		dc.FillSolidRect(&rcClient, GetSysColor(COLOR_BTNFACE));
+	}
+
+
+	// =================================================
+	// NM_CUSTOMDRAW handlers
+	// =================================================
+
+	// We keep a global list of HWNDs that require dark rendering, so dialogs can call DarkMode::OnCustomDraw() which deals with this nonsense behind the scenes
+	// This way there's no need to subclass parent windows at random
+
+	enum class whichDark_t {
+		none, toolbar
+	};
+
+	// readWriteLock used in case someone uses off-main-thread UI, though it should not really happen in real life
+	static pfc::readWriteLock lstDarkGuard;
+	static std::map<HWND, whichDark_t> lstDark;
+	static whichDark_t lstDark_query(HWND w) {
+		PFC_INSYNC_READ(lstDarkGuard);
+		auto iter = lstDark.find(w);
+		if (iter == lstDark.end()) return whichDark_t::none;
+		return iter->second;
+	}
+	static void lstDark_set(HWND w, whichDark_t which) {
+		PFC_INSYNC_WRITE(lstDarkGuard);
+		lstDark[w] = which;
+	}
+	static void lstDark_clear(HWND w) {
+		PFC_INSYNC_WRITE(lstDarkGuard);
+		lstDark.erase(w);
+	}
+
+	LRESULT CustomDrawToolbar(NMHDR* hdr) {
+		LPNMTBCUSTOMDRAW cd = reinterpret_cast<LPNMTBCUSTOMDRAW>(hdr);
+		switch (cd->nmcd.dwDrawStage) {
+		case CDDS_PREPAINT: return CDRF_NOTIFYITEMDRAW;
+		case CDDS_ITEMPREPAINT:
+			cd->clrText = DarkMode::GetSysColor(COLOR_WINDOWTEXT);
+			cd->clrBtnFace = DarkMode::GetSysColor(COLOR_BTNFACE);
+			cd->clrBtnHighlight = DarkMode::GetSysColor(COLOR_BTNHIGHLIGHT);
+			return CDRF_DODEFAULT;
+		default:
+			return CDRF_DODEFAULT;
+		}
+	}
+
+	LRESULT OnCustomDraw(int,NMHDR* hdr, BOOL& bHandled) {
+		switch (lstDark_query(hdr->hwndFrom)) {
+		case whichDark_t::toolbar:
+			bHandled = TRUE;
+			return CustomDrawToolbar(hdr);
+		default:
+			bHandled = FALSE; return 0;
+		}
+	}
+
+	namespace {
+
+		class CToolbarHook {
+			bool m_dark = false;
+			const bool m_explorerTheme;
+			CToolBarCtrl m_wnd;
+		public:
+			CToolbarHook(HWND wnd, bool initial, bool bExplorerTheme) : m_wnd(wnd), m_explorerTheme(bExplorerTheme) {
+				SetDark(initial);
+			}
+			
+			void SetDark(bool v) {
+				if (m_dark == v) return;
+				m_dark = v;
+				if (v) {
+					lstDark_set(m_wnd, whichDark_t::toolbar);
+					if (m_explorerTheme) ::SetWindowTheme(m_wnd, L"", L""); // if we don't do this, NM_CUSTOMDRAW color overrides get disregarded
+				} else {
+					lstDark_clear(m_wnd);
+					if (m_explorerTheme) ::SetWindowTheme(m_wnd, L"Explorer", NULL);
+				}
+				m_wnd.Invalidate();
+
+				ApplyDarkThemeCtrl(m_wnd.GetToolTips(), v);
+			}
+			~CToolbarHook() {
+				if (m_dark) lstDark_clear(m_wnd);
+			}
+
+		};
+
+		class CTabsHook : public CWindowImpl<CTabsHook, CTabCtrl> {
+		public:
+			CTabsHook(bool bDark = false) : m_dark(bDark) {}
+			BEGIN_MSG_MAP_EX(CTabsHook)
+				MSG_WM_PAINT(OnPaint)
+				MSG_WM_ERASEBKGND(OnEraseBkgnd)
+				MESSAGE_HANDLER_EX(msgSetDarkMode(), OnSetDarkMode)
+			END_MSG_MAP()
+
+			void SetDark(bool v = false);
+		private:
+			void OnPaint(CDCHandle);
+			BOOL OnEraseBkgnd(CDCHandle);
+
+			LRESULT OnSetDarkMode(UINT, WPARAM wp, LPARAM) {
+				switch (wp) {
+				case 0: SetDark(false); break;
+				case 1: SetDark(true); break;
+				}
+				return 1;
+			}
+
+			bool m_dark = false;
+		};
+
+		void CTabsHook::OnPaint(CDCHandle target) {
+			if (!m_dark) { SetMsgHandled(FALSE); return; }
+			if (target) {
+				PaintTabs(m_hWnd, target);
+			} else {
+				CPaintDC dc(*this);
+				PaintTabs(m_hWnd, dc.m_hDC, &dc.m_ps.rcPaint);
+			}
+		}
+		BOOL CTabsHook::OnEraseBkgnd(CDCHandle dc) {
+			if (m_dark) {
+				PaintTabsErase(*this, dc);
+				return TRUE;
+			}
+			SetMsgHandled(FALSE);
+			return FALSE;
+		}
+
+		void CTabsHook::SetDark(bool v) {
+			m_dark = v;
+			if (m_hWnd != NULL) Invalidate();
+
+			ApplyDarkThemeCtrl(GetToolTips(), v);
+		}
+
+		class CTreeViewHook : public CWindowImpl<CTreeViewHook, CTreeViewCtrl> {
+			bool m_dark;
+		public:
+			CTreeViewHook(bool v) : m_dark(v) {}
+
+			BEGIN_MSG_MAP_EX(CTreeViewHook)
+				MESSAGE_RANGE_HANDLER_EX(WM_CTLCOLORMSGBOX, WM_CTLCOLORSTATIC, OnCtlColor)
+				MESSAGE_HANDLER_EX(msgSetDarkMode(), OnSetDarkMode)
+				MESSAGE_HANDLER_EX(TVM_EDITLABEL, OnEditLabel)
+			END_MSG_MAP()
+
+			LRESULT OnCtlColor(UINT uMsg, WPARAM wParam, LPARAM lParam) {
+				return GetParent().SendMessage(uMsg, wParam, lParam);
+			}
+			LRESULT OnEditLabel(UINT, WPARAM, LPARAM) {
+				LRESULT ret = DefWindowProc();
+				if (ret != 0) {
+					HWND edit = (HWND) ret;
+					PFC_ASSERT( ::IsWindow(edit) );
+					ApplyDarkThemeCtrl( edit, m_dark );
+				}
+				return ret;
+			}
+			void SetDark(bool v) { 
+				if (m_dark == v) return;
+				m_dark = v;
+				ApplyDark();
+			}
+			void ApplyDark() {
+				ApplyDarkThemeCtrl(m_hWnd, m_dark);
+				COLORREF bk = m_dark ? GetSysColor(COLOR_WINDOW) : (COLORREF)(-1);
+				COLORREF tx = m_dark ? GetSysColor(COLOR_WINDOWTEXT) : (COLORREF)(-1);
+				this->SetTextColor(tx); this->SetLineColor(tx);
+				this->SetBkColor(bk);
+
+				ApplyDarkThemeCtrl(GetToolTips(), m_dark);
+			}
+
+			void SubclassWindow(HWND wnd) {
+				WIN32_OP_D( __super::SubclassWindow(wnd) );
+				this->ApplyDark();
+			}
+
+			LRESULT OnSetDarkMode(UINT, WPARAM wp, LPARAM) {
+				switch (wp) {
+				case 0: SetDark(false); break;
+				case 1: SetDark(true); break;
+				}
+				return 1;
+			}
+		};
+
+		class CDialogHook : public CWindowImpl<CDialogHook> {
+			bool m_enabled;
+		public:
+			CDialogHook(bool v) : m_enabled(v) {}
+
+			void SetDark(bool v) { 
+				if (m_enabled == v) return;
+
+				// Important: PostMessage()'ing this caused bugs
+				SendMessage(WM_THEMECHANGED); 
+				
+				m_enabled = v; 
+				
+				// Ensure menu bar redraw with RDW_FRAME
+				RedrawWindow(NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_FRAME);
+			}
+
+			// Undocumented Windows menu drawing API
+			// Source: https://github.com/adzm/win32-custom-menubar-aero-theme
+
+			static constexpr unsigned WM_UAHDRAWMENU = 0x0091;
+			static constexpr unsigned WM_UAHDRAWMENUITEM = 0x0092;
+
+			typedef union tagUAHMENUITEMMETRICS
+			{
+				struct {
+					DWORD cx;
+					DWORD cy;
+				} rgsizeBar[2];
+				struct {
+					DWORD cx;
+					DWORD cy;
+				} rgsizePopup[4];
+			} UAHMENUITEMMETRICS;
+
+			typedef struct tagUAHMENUPOPUPMETRICS
+			{
+				DWORD rgcx[4];
+				DWORD fUpdateMaxWidths : 2;
+			} UAHMENUPOPUPMETRICS;
+
+			typedef struct tagUAHMENU
+			{
+				HMENU hmenu;
+				HDC hdc;
+				DWORD dwFlags;
+			} UAHMENU;
+
+			typedef struct tagUAHMENUITEM
+			{
+				int iPosition;
+				UAHMENUITEMMETRICS umim;
+				UAHMENUPOPUPMETRICS umpm;
+			} UAHMENUITEM;
+
+			typedef struct UAHDRAWMENUITEM
+			{
+				DRAWITEMSTRUCT dis;
+				UAHMENU um;
+				UAHMENUITEM umi;
+			} UAHDRAWMENUITEM;
+
+
+			BEGIN_MSG_MAP_EX(CDialogHook)
+				MSG_WM_CTLCOLORDLG(ctlColorCommon)
+				MSG_WM_CTLCOLORSTATIC(ctlColorCommon)
+				MSG_WM_CTLCOLOREDIT(ctlColorCommon)
+				MSG_WM_CTLCOLORBTN(ctlColorCommon)
+				MSG_WM_CTLCOLORLISTBOX(ctlColorCommon)
+				MSG_WM_CTLCOLORSCROLLBAR(ctlColorCommon)
+				NOTIFY_CODE_HANDLER(NM_CUSTOMDRAW, DarkMode::OnCustomDraw)
+				MESSAGE_HANDLER_EX(WM_UAHDRAWMENU, Handle_WM_UAHDRAWMENU)
+				MESSAGE_HANDLER_EX(WM_UAHDRAWMENUITEM, Handle_WM_UAHDRAWMENUITEM)
+				MESSAGE_HANDLER_EX(msgSetDarkMode(), OnSetDarkMode)
+			END_MSG_MAP()
+
+			LRESULT OnSetDarkMode(UINT, WPARAM wp, LPARAM) {
+				switch (wp) {
+				case 0: SetDark(false); break;
+				case 1: SetDark(true); break;
+				}
+				return 1;
+			}
+
+			static COLORREF GetBkColor() { return DarkMode::GetSysColor(COLOR_WINDOW); }
+			static COLORREF GetTextColor() { return DarkMode::GetSysColor(COLOR_WINDOWTEXT); }
+
+			HBRUSH ctlColorDlg(CDCHandle dc, CWindow wnd) {
+				if (m_enabled && ::IsThemeDialogTextureEnabled(*this)) {
+					auto bkColor = DarkMode::GetSysColor(COLOR_HIGHLIGHT);
+					auto txColor = GetTextColor();
+
+					dc.SetTextColor(txColor);
+					dc.SetBkColor(bkColor);
+					dc.SetDCBrushColor(bkColor);
+					return (HBRUSH)GetStockObject(DC_BRUSH);
+				}
+				return ctlColorCommon(dc, wnd);
+			}
+
+			HBRUSH ctlColorCommon(CDCHandle dc, CWindow wnd) {
+				(void)wnd;
+				if (m_enabled) {
+					auto bkColor = GetBkColor();
+					auto txColor = GetTextColor();
+
+					dc.SetTextColor(txColor);
+					dc.SetBkColor(bkColor);
+					dc.SetDCBrushColor(bkColor);
+					return (HBRUSH)GetStockObject(DC_BRUSH);
+				}
+				SetMsgHandled(FALSE);
+				return NULL;
+			}
+			LRESULT Handle_WM_UAHDRAWMENU(UINT, WPARAM wParam, LPARAM lParam) {
+				if (!m_enabled) {
+					SetMsgHandled(FALSE);
+					return 0;
+				}
+				UAHMENU* pUDM = (UAHMENU*)lParam;
+				CRect rc;
+
+				MENUBARINFO mbi = { sizeof(mbi) };
+				WIN32_OP_D(GetMenuBarInfo(m_hWnd, OBJID_MENU, 0, &mbi));
+
+				CRect rcWindow;
+				WIN32_OP_D(GetWindowRect(rcWindow));
+
+				rc = mbi.rcBar;
+				OffsetRect(&rc, -rcWindow.left, -rcWindow.top);
+
+				rc.top -= 1;
+
+				CDCHandle dc(pUDM->hdc);
+				dc.FillSolidRect(rc, DarkMode::GetSysColor(COLOR_MENUBAR));
+				return 0;
+			}
+			LRESULT Handle_WM_UAHDRAWMENUITEM(UINT, WPARAM wParam, LPARAM lParam) {
+				if (!m_enabled) {
+					SetMsgHandled(FALSE);
+					return 0;
+				}
+
+				UAHDRAWMENUITEM* pUDMI = (UAHDRAWMENUITEM*)lParam;
+				CMenuHandle hMenu = pUDMI->um.hmenu;
+
+				CString menuString;
+				WIN32_OP_D(hMenu.GetMenuString(pUDMI->umi.iPosition, menuString, MF_BYPOSITION) > 0);
+
+				DWORD drawTextFlags = DT_CENTER | DT_SINGLELINE | DT_VCENTER;
+
+				int iTextStateID = MPI_NORMAL;
+				int iBackgroundStateID = MPI_NORMAL;
+				if ((pUDMI->dis.itemState & ODS_INACTIVE) | (pUDMI->dis.itemState & ODS_DEFAULT)) {
+					iTextStateID = MPI_NORMAL;
+					iBackgroundStateID = MPI_NORMAL;
+				}
+				if (pUDMI->dis.itemState & (ODS_HOTLIGHT|ODS_SELECTED)) {
+					iTextStateID = MPI_HOT;
+					iBackgroundStateID = MPI_HOT;
+				}
+				if (pUDMI->dis.itemState & (ODS_GRAYED|ODS_DISABLED)) {
+					iTextStateID = MPI_DISABLED;
+					iBackgroundStateID = MPI_DISABLED;
+				}
+				if (pUDMI->dis.itemState & ODS_NOACCEL) {
+					drawTextFlags |= DT_HIDEPREFIX;
+				}
+
+				if (m_menuTheme == NULL) {
+					m_menuTheme.OpenThemeData(m_hWnd, L"Menu");
+				}
+
+				CDCHandle dc(pUDMI->um.hdc);
+				switch (iBackgroundStateID) {
+				case MPI_NORMAL:
+				case MPI_DISABLED:
+					dc.FillSolidRect(&pUDMI->dis.rcItem, DarkMode::GetSysColor(COLOR_MENUBAR));
+					break;
+				case MPI_HOT:
+				case MPI_DISABLEDHOT:
+					dc.FillSolidRect(&pUDMI->dis.rcItem, DarkMode::GetSysColor(COLOR_MENUHILIGHT));
+					break;
+				default:
+					DrawThemeBackground(m_menuTheme, pUDMI->um.hdc, MENU_POPUPITEM, iBackgroundStateID, &pUDMI->dis.rcItem, nullptr);
+					break;
+				}
+				DTTOPTS dttopts = { sizeof(dttopts) };
+				if (iTextStateID == MPI_NORMAL || iTextStateID == MPI_HOT)
+				{
+					dttopts.dwFlags |= DTT_TEXTCOLOR;
+					dttopts.crText = DarkMode::GetSysColor(COLOR_WINDOWTEXT);
+				}
+				DrawThemeTextEx(m_menuTheme, dc, MENU_POPUPITEM, iTextStateID, menuString, menuString.GetLength(), drawTextFlags, &pUDMI->dis.rcItem, &dttopts);
+
+				return 0;
+			}
+			CTheme m_menuTheme;
+		};
+
+
+		class CStatusBarHook : public CWindowImpl<CStatusBarHook, CStatusBarCtrl> {
+		public:
+			CStatusBarHook(bool v = false) : m_dark(v) {}
+
+			BEGIN_MSG_MAP_EX(CStatusBarHook)
+				MSG_WM_ERASEBKGND(OnEraseBkgnd)
+				MSG_WM_PAINT(OnPaint)
+				MESSAGE_HANDLER_EX(SB_SETTEXT, OnSetText)
+				MESSAGE_HANDLER_EX(SB_SETICON, OnSetIcon)
+				MESSAGE_HANDLER_EX(msgSetDarkMode(), OnSetDarkMode)
+			END_MSG_MAP()
+
+			LRESULT OnSetDarkMode(UINT, WPARAM wp, LPARAM) {
+				switch (wp) {
+				case 0: SetDark(false); break;
+				case 1: SetDark(true); break;
+				}
+				return 1;
+			}
+
+			void SetDark(bool v = true) {
+				if (m_dark != v) {
+					m_dark = v;
+					Invalidate();
+					ApplyDarkThemeCtrl(m_hWnd, v);
+				}
+			}
+
+			void SubclassWindow(HWND wnd) {
+				WIN32_OP_D(__super::SubclassWindow(wnd));
+				Invalidate();
+				ApplyDarkThemeCtrl(m_hWnd, m_dark);
+			}
+			LRESULT OnSetIcon(UINT, WPARAM wp, LPARAM lp) {
+				unsigned idx = (unsigned)wp;
+				if (idx < 32) {
+					CSize sz;
+					if (lp != 0) sz = GetIconSize((HICON)lp);
+					m_iconSizeCache[idx] = sz;
+				}
+				SetMsgHandled(FALSE);
+				return 0;
+			}
+			LRESULT OnSetText(UINT, WPARAM wp, LPARAM) {
+				// Status bar won't tell us about ownerdraw from GetText()
+				// Have to listen to relevant messages to know
+				unsigned idx = (unsigned)(wp & 0xFF);
+				if (idx < 32) {
+					uint32_t flag = 1 << idx;
+					if (wp & SBT_OWNERDRAW) {
+						m_ownerDrawMask |= flag;
+					} else {
+						m_ownerDrawMask &= ~flag;
+					}
+				}
+
+				SetMsgHandled(FALSE);
+				return 0;
+			}
+
+			void Paint(CDCHandle dc) {
+				CRect rcClient; WIN32_OP_D(GetClientRect(rcClient));
+				dc.FillSolidRect(rcClient, GetSysColor(COLOR_BTNFACE)); // Wine seems to not call our WM_ERASEBKGND handler, fill the background here too
+
+				dc.SelectFont(GetFont());
+				dc.SetBkMode(TRANSPARENT);
+				dc.SetTextColor(GetSysColor(COLOR_WINDOWTEXT));
+				CPen pen; pen.CreatePen(PS_SOLID, 1, GetSysColor(COLOR_BTNHIGHLIGHT));
+				dc.SelectPen(pen);
+				int count = this->GetParts(0, nullptr);
+				for (int iPart = 0; iPart < count; ++iPart) {
+					CRect rcPart;
+					this->GetRect(iPart, rcPart);
+					if (rcPart.left > 0) {
+						dc.MoveTo(rcPart.left, rcPart.top);
+						dc.LineTo(rcPart.left, rcPart.bottom);
+					}
+					int type = 0;
+					CString text;
+					this->GetText(iPart, text, &type);
+
+					HICON icon = this->GetIcon(iPart);
+					int iconMargin = 0;
+					if (icon != NULL && (unsigned)iPart < std::size(m_iconSizeCache)) {
+
+						auto size = m_iconSizeCache[iPart];
+
+						dc.DrawIconEx(rcPart.left + size.cx / 4, (rcPart.top + rcPart.bottom) / 2 - size.cy / 2, icon, size.cx, size.cy);
+						iconMargin = MulDiv(size.cx, 3, 2);
+					}
+
+					if (m_ownerDrawMask & (1 << iPart)) { // statusbar won't tell us about ownerdraw from GetText()
+						DRAWITEMSTRUCT ds = {};
+						ds.CtlType = ODT_STATIC;
+						ds.CtlID = this->GetDlgCtrlID();
+						ds.itemID = iPart;
+						ds.hwndItem = m_hWnd;
+						ds.hDC = dc;
+						ds.rcItem = rcPart;
+
+						DCStateScope scope(dc);
+						GetParent().SendMessage(WM_DRAWITEM, GetDlgCtrlID(), (LPARAM)&ds);
+					} else {
+						CRect rcText = rcPart;
+						int defMargin = rcText.Height() / 4;
+						int l = iconMargin > 0 ? iconMargin : defMargin;
+						int r = defMargin;
+						rcText.DeflateRect(l, 0, r, 0);
+						dc.DrawText(text, text.GetLength(), rcText, DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS | DT_NOPREFIX);
+					}
+
+					if (GetStyle() & SBARS_SIZEGRIP) {
+						CSize size;
+						auto theme = OpenThemeData(*this, L"status");
+						PFC_ASSERT(theme != NULL);
+						GetThemePartSize(theme, dc, SP_GRIPPER, 0, &rcClient, TS_DRAW, &size);
+						auto rc = rcClient;
+						rc.left = rc.right - size.cx;
+						rc.top = rc.bottom - size.cy;
+						DrawThemeBackground(theme, dc, SP_GRIPPER, 0, &rc, nullptr);
+						CloseThemeData(theme);
+					}
+				}
+			}
+
+			void OnPaint(CDCHandle target) {
+				if (!m_dark) { SetMsgHandled(FALSE); return; }
+				if (target) {
+					Paint(target);
+				} else {
+					CPaintDC dc(*this);
+					Paint(dc.m_hDC);
+				}
+			}
+
+			BOOL OnEraseBkgnd(CDCHandle dc) {
+				if (m_dark) {
+					CRect rc; WIN32_OP_D(GetClientRect(rc)); dc.FillSolidRect(rc, DarkMode::GetSysColor(COLOR_BTNFACE)); return TRUE;
+				}
+				SetMsgHandled(FALSE); return FALSE;			
+			}
+
+			bool m_dark = false;
+
+			uint32_t m_ownerDrawMask = 0;
+			CSize m_iconSizeCache[32];
+		};
+
+		class CCheckBoxHook : public CWindowImpl<CCheckBoxHook, CButton> {
+		public:
+			CCheckBoxHook(bool v = false) : m_dark(v) {}
+
+			BEGIN_MSG_MAP_EX(CCheckBoxHook)
+				MSG_WM_PAINT(OnPaint)
+				MSG_WM_PRINTCLIENT(OnPaint)
+				MSG_WM_ERASEBKGND(OnEraseBkgnd)
+				MSG_WM_UPDATEUISTATE(OnUpdateUIState)
+
+				// Note that checkbox implementation likes to paint on its own in response to events 
+				// instead of invalidating and handling WM_PAINT
+				// We have to specifically trigger WM_PAINT to override their rendering with ours
+				MESSAGE_HANDLER_EX(WM_SETFOCUS, OnMsgRedraw)
+				MESSAGE_HANDLER_EX(WM_KILLFOCUS, OnMsgRedraw)
+				MESSAGE_HANDLER_EX(WM_ENABLE, OnMsgRedraw)
+				MESSAGE_HANDLER_EX(WM_SETTEXT, OnMsgRedraw)
+
+				MESSAGE_HANDLER_EX(msgSetDarkMode(), OnSetDarkMode)
+			END_MSG_MAP()
+
+			LRESULT OnSetDarkMode(UINT, WPARAM wp, LPARAM) {
+				switch (wp) {
+				case 0: SetDark(false); break;
+				case 1: SetDark(true); break;
+				}
+				return 1;
+			}
+
+			LRESULT OnMsgRedraw(UINT, WPARAM, LPARAM) {
+				if ( m_dark ) {
+					// PROBLEM: 
+					// Can't invalidate prior to their handling of the message
+					// Causes bugs with specific chains of events - EnableWindow() followed immediately SetWindowText()
+					LRESULT ret = DefWindowProc();
+					Invalidate();
+					return ret;
+				}
+				SetMsgHandled(FALSE); return 0;
+			}
+
+			void OnUpdateUIState(WORD nAction, WORD nState) {
+				(void)nAction;
+				if ( m_dark && (nState & (UISF_HIDEACCEL | UISF_HIDEFOCUS)) != 0) {
+					// PROBLEM: 
+					// Can't invalidate prior to their handling of the message
+					// Causes bugs with specific chains of events - EnableWindow() followed immediately SetWindowText()
+					DefWindowProc();
+					Invalidate();
+					return;
+				}
+				SetMsgHandled(FALSE);
+			}
+			void PaintHandler(CDCHandle dc) {
+				CRect rcClient; WIN32_OP_D(GetClientRect(rcClient));
+
+				const bool bDisabled = !this->IsWindowEnabled();
+
+				dc.SetTextColor(DarkMode::GetSysColor(COLOR_BTNTEXT));
+				dc.SetBkColor(DarkMode::GetSysColor(COLOR_BTNFACE));
+				dc.SetBkMode(OPAQUE);
+				dc.SelectFont(GetFont());
+				GetParent().SendMessage(WM_CTLCOLORBTN, (WPARAM)dc.m_hDC, (LPARAM)m_hWnd);
+				if (bDisabled) dc.SetTextColor(DarkMode::GetSysColor(COLOR_GRAYTEXT)); // override WM_CTLCOLORBTN
+
+				const DWORD btnStyle = GetStyle();
+				const DWORD btnType = btnStyle & BS_TYPEMASK;
+				const bool bRadio = (btnType == BS_RADIOBUTTON || btnType == BS_AUTORADIOBUTTON);
+				const int part = bRadio ? BP_RADIOBUTTON : BP_CHECKBOX;
+
+				const auto ctrlState = GetState();
+				const DWORD uiState = (DWORD)SendMessage(WM_QUERYUISTATE);
+
+				
+				const bool bChecked = (ctrlState & BST_CHECKED) != 0;
+				const bool bMixed = (ctrlState & BST_INDETERMINATE) != 0;
+				const bool bHot = (ctrlState & BST_HOT) != 0;
+				const bool bFocus = (ctrlState & BST_FOCUS) != 0 && (uiState & UISF_HIDEFOCUS) == 0;
+
+				HTHEME theme = OpenThemeData(m_hWnd, L"button");
+
+				CRect rcCheckBox = rcClient;
+				bool bDrawn = false;
+				int margin = 0;
+				if (theme != NULL && IsThemePartDefined(theme, part, 0)) {
+					int state = 0;
+					if (bDisabled) {
+						if ( bChecked ) state = CBS_CHECKEDDISABLED;
+						else if ( bMixed ) state = CBS_MIXEDDISABLED;
+						else state = CBS_UNCHECKEDDISABLED;
+					} else if (bHot) {
+						if ( bChecked ) state = CBS_CHECKEDHOT;
+						else if ( bMixed ) state = CBS_MIXEDHOT;
+						else state = CBS_UNCHECKEDNORMAL;
+					} else {
+						if ( bChecked ) state = CBS_CHECKEDNORMAL;
+						else if ( bMixed ) state = CBS_MIXEDNORMAL;
+						else state = CBS_UNCHECKEDNORMAL;
+					}
+
+					CSize size;
+					if (SUCCEEDED(GetThemePartSize(theme, dc, part, state, rcCheckBox, TS_TRUE, &size))) {
+						if (size.cx <= rcCheckBox.Width() && size.cy <= rcCheckBox.Height()) {
+							CRect rc = rcCheckBox;
+							margin = MulDiv(size.cx, 5, 4);
+							// rc.left += (rc.Width() - size.cx) / 2;
+							rc.top += (rc.Height() - size.cy) / 2;
+							rc.right = rc.left + size.cx;
+							rc.bottom = rc.top + size.cy;
+							DrawThemeBackground(theme, dc, part, state, rc, &rc);
+							bDrawn = true;
+						}
+					}
+				}
+				if (theme != NULL) CloseThemeData(theme);
+				if (!bDrawn) {
+					int stateEx = bRadio ? DFCS_BUTTONRADIO : DFCS_BUTTONCHECK;
+					if (bChecked) stateEx |= DFCS_CHECKED;
+					// FIX ME bMixed ?
+					if (bDisabled) stateEx |= DFCS_INACTIVE;
+					else if (bHot) stateEx |= DFCS_HOT;
+
+					const int dpi = GetDeviceCaps(dc, LOGPIXELSX);
+					int w = MulDiv(16, dpi, 96);
+
+					CRect rc = rcCheckBox; 
+					if (rc.Width() > w) rc.right = rc.left + w;;
+
+					DrawFrameControl(dc, rc, DFC_BUTTON, stateEx);
+					margin = MulDiv(20, dpi, 96);
+				}
+
+				CString text;
+				if (margin < rcClient.Width()) GetWindowText(text);
+				if (!text.IsEmpty()) {
+					CRect rcText = rcClient;
+					rcText.left += margin;
+					UINT dtFlags = DT_VCENTER;
+					if (btnStyle & BS_MULTILINE) {
+						dtFlags |= DT_WORDBREAK;
+					} else {
+						dtFlags |= DT_END_ELLIPSIS | DT_SINGLELINE;
+					}
+					dc.DrawText(text, text.GetLength(), rcText, dtFlags);
+					if (bFocus) {
+						dc.DrawText(text, text.GetLength(), rcText, DT_CALCRECT | dtFlags);
+						dc.DrawFocusRect(rcText);
+					}
+				} else if (bFocus) {
+					dc.DrawFocusRect(rcClient);
+				}
+			}
+			void OnPaint(CDCHandle userDC, UINT flags = 0) {
+				(void)flags;
+				if (!m_dark) { SetMsgHandled(FALSE); return; }
+				if (userDC) {
+					PaintHandler(userDC);
+				} else {
+					CPaintDC dc(*this);
+					PaintHandler(dc.m_hDC);
+				}
+			}
+			BOOL OnEraseBkgnd(CDCHandle dc) {
+				if (m_dark) {
+					CRect rc; WIN32_OP_D(GetClientRect(rc));
+
+					dc.SetTextColor(DarkMode::GetSysColor(COLOR_BTNTEXT));
+					dc.SetBkColor(DarkMode::GetSysColor(COLOR_BTNFACE));
+					auto br = (HBRUSH)GetParent().SendMessage(WM_CTLCOLORSTATIC, (WPARAM)dc.m_hDC, (LPARAM)m_hWnd);
+					if (br != NULL) {
+						dc.FillRect(rc, br);
+					} else {
+						dc.FillSolidRect(rc, DarkMode::GetSysColor(COLOR_BTNFACE));
+					}
+					return TRUE;
+				}
+				SetMsgHandled(FALSE); return FALSE;
+			}
+
+			void SetDark(bool v = true) {
+				if (v != m_dark) {
+					m_dark = v;
+					Invalidate();
+					ApplyDarkThemeCtrl(m_hWnd, m_dark);
+				}
+			}
+
+			void SubclassWindow(HWND wnd) {
+				WIN32_OP_D(__super::SubclassWindow(wnd));
+				ApplyDarkThemeCtrl(m_hWnd, m_dark);
+			}
+
+			bool m_dark = false;
+		};
+
+		class CGripperHook : public CWindowImpl<CGripperHook> {
+		public:
+			CGripperHook(bool v) : m_dark(v) {}
+
+			BEGIN_MSG_MAP_EX(CGRipperHook)
+				MSG_WM_ERASEBKGND(OnEraseBkgnd)
+				MSG_WM_PAINT(OnPaint)
+				MESSAGE_HANDLER_EX(msgSetDarkMode(), OnSetDarkMode)
+			END_MSG_MAP()
+
+			LRESULT OnSetDarkMode(UINT, WPARAM wp, LPARAM) {
+				switch (wp) {
+				case 0: SetDark(false); break;
+				case 1: SetDark(true); break;
+				}
+				return 1;
+			}
+
+			void SetDark(bool v) {
+				if (v != m_dark) {
+					m_dark = v;
+					ApplyDarkThemeCtrl(*this, m_dark);
+				}
+			}
+
+			void SubclassWindow(HWND wnd) {
+				WIN32_OP_D(__super::SubclassWindow(wnd));
+				ApplyDarkThemeCtrl(m_hWnd, m_dark);
+			}
+
+			void PaintGripper(CDCHandle dc) {
+				CRect rcClient; WIN32_OP_D(GetClientRect(rcClient));
+				CSize size;
+				auto theme = OpenThemeData(*this, L"status");
+				PFC_ASSERT(theme != NULL);
+				GetThemePartSize(theme, dc, SP_GRIPPER, 0, &rcClient, TS_DRAW, &size);
+				auto rc = rcClient;
+				rc.left = rc.right - size.cx;
+				rc.top = rc.bottom - size.cy;
+				DrawThemeBackground(theme, dc, SP_GRIPPER, 0, &rc, nullptr);
+				CloseThemeData(theme);
+			}
+
+			void OnPaint(CDCHandle dc) {
+				if (!m_dark) { SetMsgHandled(FALSE); return; }
+				if (dc) PaintGripper(dc);
+				else {CPaintDC pdc(*this); PaintGripper(pdc.m_hDC);}
+			}
+
+			BOOL OnEraseBkgnd(CDCHandle dc) {
+				if (m_dark) {
+					CRect rc; GetClientRect(rc);
+					dc.FillSolidRect(rc, GetSysColor(COLOR_WINDOW));
+					return TRUE;
+				}
+				SetMsgHandled(FALSE); return FALSE;
+			}
+			bool m_dark = false;
+		};
+
+		class CReBarHook : public CWindowImpl<CReBarHook, CReBarCtrl> {
+			bool m_dark;
+		public:
+			CReBarHook(bool v) : m_dark(v) {}
+			BEGIN_MSG_MAP_EX(CReBarHook)
+				MSG_WM_ERASEBKGND(OnEraseBkgnd)
+				MSG_WM_DESTROY(OnDestroy)
+				MSG_WM_PAINT(OnPaint)
+				MSG_WM_PRINTCLIENT(OnPaint)
+				NOTIFY_CODE_HANDLER(NM_CUSTOMDRAW, DarkMode::OnCustomDraw)
+				MESSAGE_HANDLER_EX(msgSetDarkMode(), OnSetDarkMode)
+			END_MSG_MAP()
+
+			LRESULT OnSetDarkMode(UINT, WPARAM wp, LPARAM) {
+				switch (wp) {
+				case 0: SetDark(false); break;
+				case 1: SetDark(true); break;
+				}
+				return 1;
+			}
+
+			void OnPaint(CDCHandle target, unsigned flags = 0) {
+				if (!m_dark) { SetMsgHandled(FALSE); return; }
+				(void)flags;
+				if (target) {
+					HandlePaint(target);
+				} else {
+					CPaintDC pdc(*this);
+					HandlePaint(pdc.m_hDC);
+				}
+			}
+
+			void HandlePaint(CDCHandle dc) {
+				const int total = this->GetBandCount();
+				for (int iBand = 0; iBand < total; ++iBand) {
+					CRect rc;
+					WIN32_OP_D(this->GetRect(iBand, rc));
+
+					wchar_t buffer[256] = {};
+					REBARBANDINFO info = { sizeof(info) };
+					info.fMask = RBBIM_TEXT | RBBIM_CHILD | RBBIM_STYLE;
+					info.lpText = buffer; info.cch = (UINT)std::size(buffer);
+					WIN32_OP_D(this->GetBandInfo(iBand, &info));
+
+					HFONT useFont;
+					// Sadly overriding the font breaks the layout
+					// MS implementation disregards fonts too
+					// useFont = this->GetFont();
+					useFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
+					SelectObjectScope fontScope(dc, useFont);
+					dc.SetTextColor(DarkMode::GetSysColor(COLOR_BTNTEXT));
+					dc.SetBkMode(TRANSPARENT);
+
+					CRect rcText = rc;
+					if ((info.fStyle & RBBS_NOGRIPPER) == 0) {
+						auto color = PaintUtils::BlendColor(DarkMode::GetSysColor(COLOR_WINDOWFRAME), DarkMode::GetSysColor(COLOR_BTNFACE));
+						dc.SetDCPenColor(color);
+						SelectObjectScope penScope(dc, GetStockObject(DC_PEN));
+						dc.MoveTo(rcText.TopLeft());
+						dc.LineTo(CPoint(rcText.left, rcText.bottom));
+						rcText.left += 6; // this should be DPI-scaled, only it's not because rebar layout isn't either
+					} else {
+						rcText.left += 2;
+					}
+					dc.DrawText(buffer, (int)wcslen(buffer), &rcText, DT_VCENTER | DT_LEFT | DT_SINGLELINE | DT_NOPREFIX);
+				}
+			}
+
+
+			void OnDestroy() {
+				SetMsgHandled(FALSE);
+			}
+			BOOL OnEraseBkgnd(CDCHandle dc) {
+				if (m_dark) {
+					CRect rc;
+					WIN32_OP_D(GetClientRect(rc));
+					dc.FillSolidRect(rc, DarkMode::GetSysColor(COLOR_BTNFACE));
+					return TRUE;
+				}
+				SetMsgHandled(FALSE); return FALSE;
+			}
+			void SetDark(bool v) {
+				if (v != m_dark) {
+					m_dark = v; Apply();
+				}
+			}
+			void Apply() {
+				if (m_dark) {
+					this->SetTextColor(DarkMode::GetSysColor(COLOR_WINDOWTEXT));
+					this->SetBkColor(DarkMode::GetSysColor(COLOR_BTNFACE));
+				} else {
+					this->SetTextColor((COLORREF)-1);
+					this->SetBkColor((COLORREF)-1);
+				}
+				Invalidate();
+			}
+			void SubclassWindow(HWND wnd) {
+				WIN32_OP_D(__super::SubclassWindow(wnd));
+				Apply();
+			}
+		};
+
+		class CStaticHook : public CWindowImpl<CStaticHook, CStatic> {
+			bool m_dark;
+		public:
+			CStaticHook(bool v) : m_dark(v) {}
+
+			BEGIN_MSG_MAP_EX(CStaticHook)
+				MSG_WM_PAINT(OnPaint)
+				MESSAGE_HANDLER_EX(WM_ENABLE, OnMsgRedraw)
+				MESSAGE_HANDLER_EX(msgSetDarkMode(), OnSetDarkMode)
+			END_MSG_MAP()
+
+			LRESULT OnSetDarkMode(UINT, WPARAM wp, LPARAM) {
+				switch (wp) {
+				case 0: SetDark(false); break;
+				case 1: SetDark(true); break;
+				}
+				return 1;
+			}
+			
+			LRESULT OnMsgRedraw(UINT, WPARAM, LPARAM) {
+				Invalidate();
+				SetMsgHandled(FALSE);
+				return 0;
+			}
+
+			void SetDark(bool v) {
+				if (m_dark != v) {
+					m_dark = v;
+					Invalidate();
+				}
+			}
+
+			void OnPaint(CDCHandle dc) {
+				// ONLY override the dark+disabled or dark+icon behavior
+				if (!m_dark || (this->IsWindowEnabled() && this->GetIcon() == NULL) ) {
+					SetMsgHandled(FALSE); return;
+				}
+
+				if (dc) HandlePaint(dc);
+				else {
+					CPaintDC pdc(*this); HandlePaint(pdc.m_hDC);
+				}
+			}
+			void HandlePaint(CDCHandle dc) {
+				CString str;
+				CIconHandle icon = this->GetIcon();
+				this->GetWindowTextW(str);
+
+				CRect rcClient;
+				WIN32_OP_D(GetClientRect(rcClient));
+				
+				const DWORD style = this->GetStyle();
+				
+				dc.SelectFont(GetFont());
+
+				HBRUSH br = (HBRUSH) GetParent().SendMessage(WM_CTLCOLORSTATIC, (WPARAM)dc.m_hDC, (LPARAM)m_hWnd);;
+				if (br == NULL) {
+					dc.FillSolidRect(rcClient, DarkMode::GetSysColor(COLOR_WINDOW));
+				} else {
+					WIN32_OP_D(dc.FillRect(rcClient, br));
+				}
+
+				if (icon != NULL) {
+					dc.DrawIcon(0, 0, icon);
+				} else {
+					DWORD flags = 0;
+					if (style & SS_SIMPLE) flags |= DT_SINGLELINE | DT_WORD_ELLIPSIS;
+					else flags |= DT_WORDBREAK;
+					if (style & SS_RIGHT) flags |= DT_RIGHT;
+					else if (style & SS_CENTER) flags |= DT_CENTER;
+
+					dc.SetTextColor(DarkMode::GetSysColor(COLOR_GRAYTEXT));
+					dc.SetBkMode(TRANSPARENT);
+					dc.DrawText(str, str.GetLength(), rcClient, flags);
+				}
+			}
+		};
+
+		class CUpDownHook : public CWindowImpl<CUpDownHook, CUpDownCtrl> {
+			bool m_dark;
+		public:
+			CUpDownHook(bool v) : m_dark(v) {}
+
+			void SetDark(bool v) {
+				if (v != m_dark) {
+					m_dark = v; Invalidate();
+				}
+			}
+
+			BEGIN_MSG_MAP_EX(CUpDownHook)
+				MSG_WM_PAINT(OnPaint)
+				MSG_WM_PRINTCLIENT(OnPaint)
+				MSG_WM_MOUSEMOVE(OnMouseMove)
+				MSG_WM_LBUTTONDOWN(OnMouseBtn)
+				MSG_WM_LBUTTONUP(OnMouseBtn)
+				MSG_WM_MOUSELEAVE(OnMouseLeave)
+				MESSAGE_HANDLER_EX(msgSetDarkMode(), OnSetDarkMode)
+			END_MSG_MAP()
+
+		private:
+			LRESULT OnSetDarkMode(UINT, WPARAM wp, LPARAM) {
+				switch (wp) {
+				case 0: SetDark(false); break;
+				case 1: SetDark(true); break;
+				}
+				return 1;
+			}
+
+			struct layout_t {
+				CRect whole, upper, lower;
+				int yCenter;
+			};
+			layout_t Layout(CRect const & rcClient) {
+				CRect rc = rcClient;
+				rc.DeflateRect(1, 1);
+				int yCenter = (rc.top + rc.bottom) / 2;
+				layout_t ret;
+				ret.yCenter = yCenter;
+				ret.whole = rc;
+				ret.upper = rc;
+				ret.upper.bottom = yCenter;
+				ret.lower = rc;
+				ret.lower.top = yCenter;
+				return ret;
+			}
+			layout_t Layout() {
+				CRect rcClient; WIN32_OP_D(GetClientRect(rcClient)); return Layout(rcClient);
+			}
+			int m_hot = 0;
+			bool m_btnDown = false;
+			void SetHot(int iHot) {
+				if (iHot != m_hot) {
+					m_hot = iHot; Invalidate();
+				}
+			}
+			void OnMouseLeave() {
+				SetHot(0);
+				SetMsgHandled(FALSE);
+			}
+			int HitTest(CPoint pt) {
+				auto layout = Layout();
+				if (layout.upper.PtInRect(pt)) return 1;
+				else if (layout.lower.PtInRect(pt)) return 2;
+				else return 0;
+			}
+			void OnMouseBtn(UINT flags, CPoint) {
+				bool bDown = (flags & MK_LBUTTON) != 0;
+				if (bDown != m_btnDown) {
+					m_btnDown = bDown;
+					Invalidate();
+				}
+				SetMsgHandled(FALSE);
+			}
+			void OnMouseMove(UINT, CPoint pt) {
+				SetHot(HitTest(pt));
+				SetMsgHandled(FALSE);
+			}
+			void OnPaint(CDCHandle target, unsigned flags = 0) {
+				if (!m_dark) { SetMsgHandled(FALSE); return; }
+				(void)flags;
+				if (target) {
+					HandlePaint(target);
+				} else {
+					CPaintDC pdc(*this);
+					HandlePaint(pdc.m_hDC);
+				}
+			}
+			void HandlePaint(CDCHandle dc) {
+				// no corresponding getsyscolor values for this, hardcoded values taken from actual button control
+				// + frame trying to fit edit control frame
+				const COLORREF colorText = 0xFFFFFF;
+				const COLORREF colorFrame = 0xFFFFFF;
+				const COLORREF colorBk = 0x333333;
+				const COLORREF colorHot = 0x454545;
+				const COLORREF colorPressed = 0x666666;
+
+				CRect rcClient; WIN32_OP_D(GetClientRect(rcClient));
+				auto layout = Layout(rcClient);
+				dc.FillRect(rcClient, MakeTempBrush(dc, colorBk));
+
+				if (m_hot != 0) {
+					auto color = m_btnDown ? colorPressed : colorHot;
+					switch (m_hot) {
+					case 1:
+						dc.FillSolidRect(layout.upper, color);
+						break;
+					case 2:
+						dc.FillSolidRect(layout.lower, color);
+						break;
+					}
+				}
+
+				dc.FrameRect(layout.whole, MakeTempBrush(dc, colorFrame));
+				dc.SetDCPenColor(colorFrame);
+				dc.SelectPen((HPEN)GetStockObject(DC_PEN));
+				dc.MoveTo(layout.whole.left, layout.yCenter);
+				dc.LineTo(layout.whole.right, layout.yCenter);
+				
+				CFontHandle f = GetFont();
+				if (f == NULL) {
+					auto buddy = this->GetBuddy();
+					if ( buddy ) f = buddy.GetFont();
+				}
+				if (f == NULL) f = (HFONT) GetStockObject(DEFAULT_GUI_FONT);
+				PFC_ASSERT(f != NULL);
+				CFont font2;
+				CreateScaledFont(font2, f, 0.5);
+				SelectObjectScope selectFont(dc, font2);
+				dc.SetBkMode(TRANSPARENT);
+				dc.SetTextColor(colorText);
+				dc.DrawText(L"˄", 1, layout.upper, DT_SINGLELINE | DT_VCENTER | DT_CENTER | DT_NOPREFIX);
+				dc.DrawText(L"˅", 1, layout.lower, DT_SINGLELINE | DT_VCENTER | DT_CENTER | DT_NOPREFIX);
+			}
+		};
+
+		class CNCFrameHook : public CWindowImpl<CNCFrameHook, CWindow> { 
+		public:
+			CNCFrameHook(bool dark) : m_dark(dark) {}
+
+			BEGIN_MSG_MAP_EX(CNCFrameHook)
+				MESSAGE_HANDLER_EX(msgSetDarkMode(), OnSetDarkMode)
+				MSG_WM_NCPAINT(OnNCPaint)
+			END_MSG_MAP()
+
+			void SetDark(bool v) {
+				if (v != m_dark) {
+					m_dark = v; ApplyDark();
+				}
+			}
+			BOOL SubclassWindow(HWND wnd) {
+				auto rv = __super::SubclassWindow(wnd);
+				if (rv) {
+					ApplyDark();
+				}
+				return rv;
+			}
+		private:
+			void OnNCPaint(HRGN rgn) {
+				if (m_dark) {
+					NCPaintDarkFrame(m_hWnd, rgn);
+					return;
+				}
+				SetMsgHandled(FALSE);
+			}
+			void ApplyDark() {
+				ApplyDarkThemeCtrl(m_hWnd, m_dark);
+				Invalidate();
+			}
+			LRESULT OnSetDarkMode(UINT, WPARAM wp, LPARAM) {
+				switch (wp) {
+				case 0: SetDark(false); break;
+				case 1: SetDark(true); break;
+				}
+				return 1;
+			}
+
+			bool m_dark;
+		};
+	}
+
+	void CHooks::AddPopup(HWND wnd) {
+		addOp( [wnd, this] {
+			UpdateTitleBar(wnd, m_dark);
+		} );
+	}
+
+	void CHooks::AddDialogWithControls(HWND wnd) {
+		AddDialog(wnd); AddControls(wnd);
+	}
+
+	void CHooks::AddDialog(HWND wnd) {
+
+		{
+			CWindow w(wnd);
+			if ((w.GetStyle() & WS_CHILD) == 0) {
+				AddPopup(wnd);
+			}
+		}
+
+		auto hook = new ImplementOnFinalMessage< CDialogHook > (m_dark);
+		hook->SubclassWindow(wnd);
+		AddCtrlMsg(wnd);
+	}
+	void CHooks::AddTabCtrl(HWND wnd) {
+		auto hook = new ImplementOnFinalMessage<CTabsHook>(m_dark);
+		hook->SubclassWindow(wnd);
+		AddCtrlMsg(wnd);
+	}
+	void CHooks::AddComboBox(HWND wnd) {
+		{
+			CComboBox combo = wnd;
+			COMBOBOXINFO info = {sizeof(info)};
+			WIN32_OP_D( combo.GetComboBoxInfo(&info) );
+			if (info.hwndList != NULL) {
+				AddListBox( info.hwndList );
+			}
+		}
+
+		addOp([wnd, this] { 
+			SetWindowTheme(wnd, m_dark ? L"DarkMode_CFD" : L"Explorer", NULL);
+		});
+	}
+	void CHooks::AddComboBoxEx(HWND wnd) {
+		this->AddControls(wnd); // recurse to add the combo box
+	}
+	void CHooks::AddEditBox(HWND wnd) { 
+#if 0 // Experimental
+		auto hook = new ImplementOnFinalMessage<CNCFrameHook>(m_dark);
+		hook->SubclassWindow( wnd );
+		AddCtrlMsg( wnd );
+#else
+		AddGeneric(wnd); 
+#endif
+	}
+	void CHooks::AddButton(HWND wnd) { 
+		CButton btn(wnd);
+		auto style = btn.GetButtonStyle();
+		auto type = style & BS_TYPEMASK;
+		if ((type == BS_CHECKBOX || type == BS_AUTOCHECKBOX || type == BS_RADIOBUTTON || type == BS_AUTORADIOBUTTON || type == BS_3STATE || type == BS_AUTO3STATE) && (style & BS_PUSHLIKE) == 0) {
+			// MS checkbox implementation is terminally retarded and won't draw text in correct color
+			// Subclass it and draw our own content
+			// Other button types seem OK
+			auto hook = new ImplementOnFinalMessage<CCheckBoxHook>(m_dark);
+			hook->SubclassWindow(wnd);
+			AddCtrlMsg(wnd);
+		} else if (type == BS_GROUPBOX) {
+			SetWindowTheme(wnd, L"", L"");
+			// SNAFU: re-creation of other controls such as list boxes causes repaint bugs due to overlapping
+			// Even though this is not a fault of the groupbox, fix it here - by defer-pushing all group boxes to the back
+			// Can't move to back right away, breaks window enumeration
+			m_lstMoveToBack.push_back(wnd);
+		} else {
+			AddGeneric(wnd);
+		}
+		
+	}
+	void CHooks::AddGeneric(HWND wnd, const wchar_t * name) {
+		this->addOp([wnd, this, name] {ApplyDarkThemeCtrl(wnd, m_dark, name); });
+	}
+	void CHooks::AddClassic(HWND wnd, const wchar_t* normalTheme) {
+		this->addOp([wnd, this, normalTheme] {
+			if (m_dark) ::SetWindowTheme(wnd, L"", L"");
+			else ::SetWindowTheme(wnd, normalTheme, nullptr);
+		});
+	}
+	void CHooks::AddStatusBar(HWND wnd) {
+		auto hook = new ImplementOnFinalMessage<CStatusBarHook>(m_dark);
+		hook->SubclassWindow(wnd);
+		this->AddCtrlMsg(wnd);
+	}
+	void CHooks::AddScrollBar(HWND wnd) {
+		CWindow w(wnd);
+		if (w.GetStyle() & SBS_SIZEGRIP) {
+			auto hook = new ImplementOnFinalMessage<CGripperHook>(m_dark);
+			hook->SubclassWindow(wnd);
+			this->AddCtrlMsg(wnd);
+		} else {
+			AddGeneric(wnd);
+		}
+	}
+
+	void CHooks::AddReBar(HWND wnd) {
+		auto hook = new ImplementOnFinalMessage<CReBarHook>(m_dark);
+		hook->SubclassWindow(wnd);
+		this->AddCtrlMsg(wnd);
+	}
+
+	void CHooks::AddToolBar(HWND wnd, bool bExplorerTheme) {
+		// Not a subclass
+		addObj(new CToolbarHook(wnd, m_dark, bExplorerTheme));
+	}
+
+	void CHooks::AddStatic(HWND wnd) {
+		auto hook = new ImplementOnFinalMessage<CStaticHook>(m_dark);
+		hook->SubclassWindow(wnd);
+		this->AddCtrlMsg(wnd);
+	}
+
+	void CHooks::AddUpDown(HWND wnd) {
+		auto hook = new ImplementOnFinalMessage<CUpDownHook>(m_dark);
+		hook->SubclassWindow(wnd);
+		this->AddCtrlMsg(wnd);
+	}
+
+	void CHooks::AddTreeView(HWND wnd) {
+		auto hook = new ImplementOnFinalMessage<CTreeViewHook>(m_dark);
+		hook->SubclassWindow(wnd);
+		this->AddCtrlMsg(wnd);
+	}
+	void CHooks::AddListBox(HWND wnd) {
+		this->AddGeneric( wnd );
+#if 0
+		auto subst = CListControl_ReplaceListBox(wnd);
+		if (subst) AddPPListControl(subst);
+#endif
+	}
+
+	void CHooks::AddListView(HWND wnd) {
+		auto subst = CListControl_ReplaceListView(wnd);
+		if (subst) AddPPListControl(subst);
+	}
+
+	void CHooks::AddPPListControl(HWND wnd) {
+		this->AddCtrlMsg(wnd);
+		// this->addOp([this, wnd] { CListControl::wndSetDarkMode(wnd, m_dark); });
+	}
+
+	void CHooks::SetDark(bool v) {
+		// Important: some handlers to ugly things if told to apply when no state change occurred - UpdateTitleBar() stuff in particular
+		if (m_dark != v) {
+			m_dark = v;
+			for (auto& f : m_apply) f();
+		}
+	}
+	void CHooks::flushMoveToBack() {
+		for (auto w : m_lstMoveToBack) {
+			::SetWindowPos(w, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
+		}
+		m_lstMoveToBack.clear();
+	}
+	void CHooks::AddControls(HWND wndParent) {
+		for (HWND walk = GetWindow(wndParent, GW_CHILD); walk != NULL; ) {
+			HWND next = GetWindow(walk, GW_HWNDNEXT);
+			AddCtrlAuto(walk);
+			walk = next;
+		}
+		this->flushMoveToBack();
+		// EnumChildWindows(wndParent, [this](HWND ctrl) {this->AddCtrlAuto(ctrl);});
+	}
+
+	void CHooks::AddCtrlMsg(HWND w) {
+		this->addOp([this, w] {
+			::SendMessage(w, msgSetDarkMode(), this->m_dark ? 1 : 0, 0);
+		});
+	}
+
+	void CHooks::AddCtrlAuto(HWND wnd) {
+
+		if (::SendMessage(wnd, msgSetDarkMode(), -1, -1)) {
+			AddCtrlMsg(wnd); return;
+		}
+
+		wchar_t buffer[128] = {};
+
+		::GetClassName(wnd, buffer, (int)(std::size(buffer) - 1));
+
+		const wchar_t* cls = buffer;
+		if (_wcsnicmp(cls, L"ATL:", 4) == 0) cls += 4;
+
+
+		if (_wcsicmp(cls, CButton::GetWndClassName()) == 0) {
+			AddButton(wnd);
+		} else if (_wcsicmp(cls, CComboBox::GetWndClassName()) == 0) {
+			AddComboBox(wnd);
+		} else if (_wcsicmp(cls, CComboBoxEx::GetWndClassName()) == 0 ) {
+			AddComboBoxEx(wnd);
+		} else if (_wcsicmp(cls, CTabCtrl::GetWndClassName()) == 0) {
+			AddTabCtrl(wnd);
+		} else if (_wcsicmp(cls, CStatusBarCtrl::GetWndClassName()) == 0) {
+			AddStatusBar(wnd);
+		} else if (_wcsicmp(cls, CEdit::GetWndClassName()) == 0) {
+			AddEditBox(wnd);
+		} else if (_wcsicmp(cls, WC_SCROLLBAR) == 0) {
+			AddScrollBar(wnd);
+		} else if (_wcsicmp(cls, CToolBarCtrl::GetWndClassName()) == 0) {
+			AddToolBar(wnd);
+		} else if (_wcsicmp(cls, CTrackBarCtrl::GetWndClassName()) == 0) {
+			AddGeneric(wnd);
+		} else if (_wcsicmp(cls, CTreeViewCtrl::GetWndClassName()) == 0) {
+			AddTreeView(wnd);
+		} else if (_wcsicmp(cls, CStatic::GetWndClassName()) == 0) {
+			AddStatic(wnd);
+		} else if (_wcsicmp(cls, CUpDownCtrl::GetWndClassName()) == 0) {
+			AddUpDown(wnd);
+		} else if (_wcsicmp(cls, CListViewCtrl::GetWndClassName()) == 0) {
+			AddListView(wnd);
+		} else if (_wcsicmp(cls, CListBox::GetWndClassName()) == 0) {
+			AddListBox(wnd);
+		} else if (_wcsicmp(cls, CReBarCtrl::GetWndClassName()) == 0) {
+			 AddReBar(wnd);
+		} else {
+#if PFC_DEBUG
+			pfc::outputDebugLine(pfc::format("DarkMode: unknown class - ", buffer));
+#endif
+		}
+	}
+
+	void CHooks::clear() {
+		m_apply.clear();
+		for (auto v : m_cleanup) v();
+		m_cleanup.clear();
+	}
+
+	void CHooks::AddApp() {
+		addOp([this] {
+			SetAppDarkMode(this->m_dark);
+		});
+	}
+
+	void NCPaintDarkFrame(HWND wnd_, HRGN rgn_) {
+		// rgn is in SCREEN COORDINATES, possibly (HRGN)1 to indicate no clipping / whole nonclient area redraw
+		// we're working with SCREEN COORDINATES until actual DC painting
+		CWindow wnd = wnd_;
+
+		CRect rcWindow, rcClient;
+		WIN32_OP_D( wnd.GetWindowRect(rcWindow) );
+		WIN32_OP_D( wnd.GetClientRect(rcClient) );
+		WIN32_OP_D( wnd.ClientToScreen( rcClient ) ); // transform all to same coordinate system
+
+		CRgn rgnClip;
+		WIN32_OP_D( rgnClip.CreateRectRgnIndirect(rcWindow) != NULL );
+		if (rgn_ != NULL && rgn_ != (HRGN)1) {
+			// we have a valid HRGN from caller?
+			if (rgnClip.CombineRgn(rgn_, RGN_AND) == NULLREGION) return; // nothing to draw, exit early
+		}
+
+		{
+			// Have scroll bars? Have DefWindowProc() them then exclude from our rgnClip.
+			SCROLLBARINFO si = { sizeof(si) };
+			if (::GetScrollBarInfo(wnd, OBJID_VSCROLL, &si) && (si.rgstate[0] & STATE_SYSTEM_INVISIBLE) == 0 && si.rcScrollBar.left < si.rcScrollBar.right) {
+				CRect rc = si.rcScrollBar;
+				// rcClient.right = rc.right;
+				CRgn rgn; WIN32_OP_D( rgn.CreateRectRgnIndirect(rc) );
+				int status = SIMPLEREGION;
+				if (rgnClip) {
+					status = rgn.CombineRgn(rgn, rgnClip, RGN_AND);
+				}
+				if (status != NULLREGION) {
+					DefWindowProc(wnd, WM_NCPAINT, (WPARAM)rgn.m_hRgn, 0);
+					rgnClip.CombineRgn(rgn, RGN_DIFF); // exclude from further drawing
+				}
+			}
+			if (::GetScrollBarInfo(wnd, OBJID_HSCROLL, &si) && (si.rgstate[0] & STATE_SYSTEM_INVISIBLE) == 0 && si.rcScrollBar.top < si.rcScrollBar.bottom) {
+				CRect rc = si.rcScrollBar;
+				// rcClient.bottom = rc.bottom;
+				CRgn rgn; WIN32_OP_D(rgn.CreateRectRgnIndirect(rc));
+				int status = SIMPLEREGION;
+				if (rgnClip) {
+					status = rgn.CombineRgn(rgn, rgnClip, RGN_AND);
+				}
+				if (status != NULLREGION) {
+					DefWindowProc(wnd, WM_NCPAINT, (WPARAM)rgn.m_hRgn, 0);
+					rgnClip.CombineRgn(rgn, RGN_DIFF); // exclude from further drawing
+				}
+			}
+		}
+
+		const auto colorLight = DarkMode::GetSysColor(COLOR_BTNHIGHLIGHT);
+		const auto colorDark = DarkMode::GetSysColor(COLOR_BTNSHADOW);
+
+		CWindowDC dc( wnd );
+		if (dc.IsNull()) {
+			PFC_ASSERT(!"???");
+			return;
+		}
+
+
+		// Window DC has (0,0) in upper-left corner of our window (not screen, not client)
+		// Turn rcWindow to (0,0), (winWidth, winHeight)
+		CPoint origin = rcWindow.TopLeft();
+		rcWindow.OffsetRect(-origin);
+		rcClient.OffsetRect(-origin);
+
+		if (!rgnClip.IsNull()) {
+			// rgnClip is still in screen coordinates, fix this here
+			rgnClip.OffsetRgn(-origin);
+			dc.SelectClipRgn(rgnClip);
+		}
+
+		// bottom
+		dc.FillSolidRect(CRect(rcClient.left, rcClient.bottom, rcWindow.right, rcWindow.bottom), colorLight);
+		// right
+		dc.FillSolidRect(CRect(rcClient.right, rcWindow.top, rcWindow.right, rcClient.bottom), colorLight);
+		// top
+		dc.FillSolidRect(CRect(rcWindow.left, rcWindow.top, rcWindow.right, rcClient.top), colorDark);
+		// left
+		dc.FillSolidRect(CRect(rcWindow.left, rcClient.top, rcClient.left, rcWindow.bottom), colorDark);
+	}
+}