diff --git a/Windows/Debugger/Debugger_Disasm.cpp b/Windows/Debugger/Debugger_Disasm.cpp index 00a7b1ed5124..584533703d7f 100644 --- a/Windows/Debugger/Debugger_Disasm.cpp +++ b/Windows/Debugger/Debugger_Disasm.cpp @@ -1,5 +1,3 @@ -// NOTE: Apologies for the quality of this code, this is really from pre-opensource Dolphin - that is, 2003. - #include "Core/Config.h" #include "Core/MemMap.h" #include "Windows/resource.h" @@ -13,6 +11,7 @@ #include "Windows/Debugger/Debugger_Disasm.h" #include "Windows/Debugger/Debugger_VFPUDlg.h" #include "Windows/Debugger/DebuggerShared.h" +// #include "Windows/W32Util/DarkMode.h" #include "Windows/main.h" #include "Windows/Debugger/CtrlRegisterList.h" @@ -350,10 +349,8 @@ BOOL CDisasm::DlgProc(UINT message, WPARAM wParam, LPARAM lParam) switch(message) { case WM_INITDIALOG: - { - return TRUE; - } - break; + // DarkModeInitDialog(m_hDlg); + return TRUE; case WM_NOTIFY: switch (wParam) @@ -692,7 +689,7 @@ BOOL CDisasm::DlgProc(UINT message, WPARAM wParam, LPARAM lParam) } break; } - return FALSE; + return 0; // DarkModeDlgProc(m_hDlg, message, wParam, lParam); } void CDisasm::updateThreadLabel(bool clear) diff --git a/Windows/Debugger/Debugger_Disasm.h b/Windows/Debugger/Debugger_Disasm.h index 73367f02f458..e186e3e238c6 100644 --- a/Windows/Debugger/Debugger_Disasm.h +++ b/Windows/Debugger/Debugger_Disasm.h @@ -1,5 +1,3 @@ -// NOTE: Apologies for the quality of this code, this is really from pre-opensource Dolphin - that is, 2003. - #pragma once #include "Windows/W32Util/DialogManager.h" diff --git a/Windows/MainWindow.cpp b/Windows/MainWindow.cpp index 65e6e5385ebe..7bb751f0fb82 100644 --- a/Windows/MainWindow.cpp +++ b/Windows/MainWindow.cpp @@ -56,6 +56,8 @@ #include "Windows/GPU/WindowsGLContext.h" #include "Windows/GEDebugger/GEDebugger.h" #endif +#include "Windows/W32Util/DarkMode.h" +#include "Windows/W32Util/UAHMenuBar.h" #include "Windows/Debugger/Debugger_Disasm.h" #include "Windows/Debugger/Debugger_MemoryDlg.h" @@ -177,7 +179,7 @@ namespace MainWindow WNDCLASSEX wcdisp; memset(&wcdisp, 0, sizeof(wcdisp)); - // Display Window + // Display Window (contained in main window) wcdisp.cbSize = sizeof(WNDCLASSEX); wcdisp.style = CS_HREDRAW | CS_VREDRAW; wcdisp.lpfnWndProc = (WNDPROC)DisplayProc; @@ -728,13 +730,52 @@ namespace MainWindow return 0; } + RECT MapRectFromClientToWndCoords(HWND hwnd, const RECT & r) + { + RECT wnd_coords = r; + + // map to screen + MapWindowPoints(hwnd, NULL, reinterpret_cast(&wnd_coords), 2); + + RECT scr_coords; + GetWindowRect(hwnd, &scr_coords); + + // map to window coords by substracting the window coord origin in + // screen coords. + OffsetRect(&wnd_coords, -scr_coords.left, -scr_coords.top); + + return wnd_coords; + } + + RECT GetNonclientMenuBorderRect(HWND hwnd) + { + RECT r; + GetClientRect(hwnd, &r); + r = MapRectFromClientToWndCoords(hwnd, r); + int y = r.top - 1; + return { + r.left, + y, + r.right, + y + 1 + }; + } + LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { + LRESULT darkResult = 0; + if (UAHDarkModeWndProc(hWnd, message, wParam, lParam, &darkResult)) { + return darkResult; + } + switch (message) { case WM_CREATE: if (!DoesVersionMatchWindows(6, 0, 0, 0, true)) { // Remove the D3D11 choice on versions below XP RemoveMenu(GetMenu(hWnd), ID_OPTIONS_DIRECT3D11, MF_BYCOMMAND); } + if (g_darkModeSupported) { + SendMessageW(hWnd, WM_THEMECHANGED, 0, 0); + } break; case WM_USER_GET_BASE_POINTER: @@ -749,6 +790,26 @@ namespace MainWindow } break; + // Hack to kill the white line underneath the menubar. + // From https://stackoverflow.com/questions/57177310/how-to-paint-over-white-line-between-menu-bar-and-client-area-of-window + case WM_NCPAINT: + case WM_NCACTIVATE: + { + if (!IsDarkModeEnabled() || IsIconic(hWnd)) { + return DefWindowProc(hWnd, message, wParam, lParam); + } + + auto result = DefWindowProc(hWnd, message, wParam, lParam); + // Paint over the line with pure black. Could also try to figure out the dark theme color. + HDC hdc = GetWindowDC(hWnd); + RECT r = GetNonclientMenuBorderRect(hWnd); + HBRUSH red = CreateSolidBrush(RGB(0, 0, 0)); + FillRect(hdc, &r, red); + DeleteObject(red); + ReleaseDC(hWnd, hdc); + return result; + } + case WM_GETMINMAXINFO: { MINMAXINFO *minmax = reinterpret_cast(lParam); @@ -804,7 +865,7 @@ namespace MainWindow case WM_ERASEBKGND: // This window is always covered by DisplayWindow. No reason to erase. - return 1; + return 0; case WM_MOVE: SavePosition(); @@ -1048,6 +1109,23 @@ namespace MainWindow } return DefWindowProc(hWnd, message, wParam, lParam); } + break; + case WM_SETTINGCHANGE: + { + if (g_darkModeSupported && IsColorSchemeChangeMessage(lParam)) + SendMessageW(hWnd, WM_THEMECHANGED, 0, 0); + } + return DefWindowProc(hWnd, message, wParam, lParam); + + case WM_THEMECHANGED: + { + if (g_darkModeSupported) + { + _AllowDarkModeForWindow(hWnd, g_darkModeEnabled); + RefreshTitleBarThemeColor(hWnd); + } + return DefWindowProc(hWnd, message, wParam, lParam); + } default: return DefWindowProc(hWnd, message, wParam, lParam); diff --git a/Windows/MainWindowMenu.cpp b/Windows/MainWindowMenu.cpp index 25af2cd08ee2..0183f033c4bb 100644 --- a/Windows/MainWindowMenu.cpp +++ b/Windows/MainWindowMenu.cpp @@ -44,6 +44,7 @@ #include "Windows/W32Util/Misc.h" #include "Windows/InputBox.h" #include "Windows/main.h" +#include "Windows/W32Util/DarkMode.h" #include "Core/HLE/sceUmd.h" #include "Core/SaveState.h" @@ -65,7 +66,7 @@ namespace MainWindow { static bool menuShaderInfoLoaded = false; std::vector menuShaderInfo; - LRESULT CALLBACK About(HWND, UINT, WPARAM, LPARAM); + LRESULT CALLBACK AboutDlgProc(HWND, UINT, WPARAM, LPARAM); void SetIngameMenuItemStates(HMENU menu, const GlobalUIState state) { UINT menuEnable = state == UISTATE_INGAME || state == UISTATE_EXCEPTION ? MF_ENABLED : MF_GRAYED; @@ -942,7 +943,7 @@ namespace MainWindow { case ID_HELP_ABOUT: DialogManager::EnableAll(FALSE); - DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About); + DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)AboutDlgProc); DialogManager::EnableAll(TRUE); break; @@ -1274,24 +1275,31 @@ namespace MainWindow { } // Message handler for about box. - LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { + LRESULT CALLBACK AboutDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { - case WM_INITDIALOG: - { - W32Util::CenterWindow(hDlg); - HWND versionBox = GetDlgItem(hDlg, IDC_VERSION); - std::string windowText = System_GetPropertyBool(SYSPROP_APP_GOLD) ? "PPSSPP Gold " : "PPSSPP "; - windowText.append(PPSSPP_GIT_VERSION); - SetWindowText(versionBox, ConvertUTF8ToWString(windowText).c_str()); - } - return TRUE; - - case WM_COMMAND: - if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) { - EndDialog(hDlg, LOWORD(wParam)); + case WM_INITDIALOG: + { + W32Util::CenterWindow(hDlg); + HWND versionBox = GetDlgItem(hDlg, IDC_VERSION); + std::string windowText = System_GetPropertyBool(SYSPROP_APP_GOLD) ? "PPSSPP Gold " : "PPSSPP "; + windowText.append(PPSSPP_GIT_VERSION); + SetWindowText(versionBox, ConvertUTF8ToWString(windowText).c_str()); + DarkModeInitDialog(hDlg); return TRUE; } - break; + + case WM_COMMAND: + { + if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) { + EndDialog(hDlg, LOWORD(wParam)); + return TRUE; + } + break; + return FALSE; + } + + default: + return DarkModeDlgProc(hDlg, message, wParam, lParam); } return FALSE; } diff --git a/Windows/PPSSPP.vcxproj b/Windows/PPSSPP.vcxproj index 659056d6956b..58acec5cc90c 100644 --- a/Windows/PPSSPP.vcxproj +++ b/Windows/PPSSPP.vcxproj @@ -248,7 +248,7 @@ $(EXTERNAL_COMPILE_OPTIONS) - mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;comctl32.lib;d3d9.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) + uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;comctl32.lib;d3d9.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) ../ffmpeg/Windows/x86/lib true Windows @@ -287,7 +287,7 @@ $(EXTERNAL_COMPILE_OPTIONS) - mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;comctl32.lib;d3d9.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) + uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;comctl32.lib;d3d9.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) ../ffmpeg/Windows/x86_64/lib true $(OutDir)$(ProjectName).pdb @@ -322,7 +322,7 @@ $(EXTERNAL_COMPILE_OPTIONS) - mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;comctl32.lib;d3d9.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;oleaut32.lib;comdlg32.lib;shell32.lib;user32.lib;gdi32.lib;advapi32.lib;ole32.lib;%(AdditionalDependencies) + uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;comctl32.lib;d3d9.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;oleaut32.lib;comdlg32.lib;shell32.lib;user32.lib;gdi32.lib;advapi32.lib;ole32.lib;%(AdditionalDependencies) ../ffmpeg/Windows/aarch64/lib true $(OutDir)$(ProjectName).pdb @@ -355,7 +355,7 @@ $(EXTERNAL_COMPILE_OPTIONS) - mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;comctl32.lib;d3d9.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;oleaut32.lib;comdlg32.lib;shell32.lib;user32.lib;gdi32.lib;advapi32.lib;ole32.lib;%(AdditionalDependencies) + uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;comctl32.lib;d3d9.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;oleaut32.lib;comdlg32.lib;shell32.lib;user32.lib;gdi32.lib;advapi32.lib;ole32.lib;%(AdditionalDependencies) ../ffmpeg/Windows/arm/lib true $(OutDir)$(ProjectName).pdb @@ -392,7 +392,7 @@ $(EXTERNAL_COMPILE_OPTIONS) - mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;comctl32.lib;d3d9.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) + uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;comctl32.lib;d3d9.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) ../ffmpeg/Windows/x86/lib;%(AdditionalLibraryDirectories) $(OutDir)$(TargetName)$(TargetExt) true @@ -440,7 +440,7 @@ $(EXTERNAL_COMPILE_OPTIONS) - mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;comctl32.lib;d3d9.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) + uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;comctl32.lib;d3d9.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) ../ffmpeg/Windows/x86_64/lib;%(AdditionalLibraryDirectories) true Windows @@ -482,7 +482,7 @@ $(EXTERNAL_COMPILE_OPTIONS) - mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;comctl32.lib;d3d9.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;oleaut32.lib;comdlg32.lib;shell32.lib;user32.lib;gdi32.lib;advapi32.lib;ole32.lib;%(AdditionalDependencies) + uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;comctl32.lib;d3d9.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;oleaut32.lib;comdlg32.lib;shell32.lib;user32.lib;gdi32.lib;advapi32.lib;ole32.lib;%(AdditionalDependencies) ../ffmpeg/Windows/aarch64/lib;%(AdditionalLibraryDirectories) true Windows @@ -522,7 +522,7 @@ $(EXTERNAL_COMPILE_OPTIONS) - mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;comctl32.lib;d3d9.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;oleaut32.lib;comdlg32.lib;shell32.lib;user32.lib;gdi32.lib;advapi32.lib;ole32.lib;%(AdditionalDependencies) + uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;comctl32.lib;d3d9.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;oleaut32.lib;comdlg32.lib;shell32.lib;user32.lib;gdi32.lib;advapi32.lib;ole32.lib;%(AdditionalDependencies) ../ffmpeg/Windows/arm/lib;%(AdditionalLibraryDirectories) true Windows @@ -917,6 +917,7 @@ + $(IntDir)%(Filename)2.obj @@ -939,6 +940,7 @@ true true + @@ -1484,7 +1486,9 @@ + + @@ -1492,6 +1496,7 @@ + diff --git a/Windows/PPSSPP.vcxproj.filters b/Windows/PPSSPP.vcxproj.filters index b5f779508404..312983befe1f 100644 --- a/Windows/PPSSPP.vcxproj.filters +++ b/Windows/PPSSPP.vcxproj.filters @@ -59,6 +59,9 @@ {8f39c005-9738-41c5-a8d8-cfc05bf178e8} + + {e6f1a7f6-807b-484e-9595-bdb58ecaa2ae} + @@ -271,6 +274,12 @@ Other Platforms\libretro + + Windows\W32Util + + + Windows\W32Util + @@ -553,6 +562,15 @@ Other Platforms\iOS\iCade + + Windows\W32Util + + + Windows\W32Util + + + Windows\W32Util + diff --git a/Windows/W32Util/DarkMode.cpp b/Windows/W32Util/DarkMode.cpp new file mode 100644 index 000000000000..e22fc06629ca --- /dev/null +++ b/Windows/W32Util/DarkMode.cpp @@ -0,0 +1,229 @@ +#include "IatHook.h" +#include "DarkMode.h" + +fnSetWindowCompositionAttribute _SetWindowCompositionAttribute = nullptr; +fnShouldAppsUseDarkMode _ShouldAppsUseDarkMode = nullptr; +fnAllowDarkModeForWindow _AllowDarkModeForWindow = nullptr; +fnAllowDarkModeForApp _AllowDarkModeForApp = nullptr; +fnFlushMenuThemes _FlushMenuThemes = nullptr; +fnRefreshImmersiveColorPolicyState _RefreshImmersiveColorPolicyState = nullptr; +fnIsDarkModeAllowedForWindow _IsDarkModeAllowedForWindow = nullptr; +fnGetIsImmersiveColorUsingHighContrast _GetIsImmersiveColorUsingHighContrast = nullptr; +fnOpenNcThemeData _OpenNcThemeData = nullptr; +// 1903 18362 +fnShouldSystemUseDarkMode _ShouldSystemUseDarkMode = nullptr; +fnSetPreferredAppMode _SetPreferredAppMode = nullptr; +fnSetWindowTheme _SetWindowTheme = nullptr; + +bool g_darkModeSupported = false; +bool g_darkModeEnabled = false; +DWORD g_buildNumber = 0; + +bool AllowDarkModeForWindow(HWND hWnd, bool allow) +{ + if (g_darkModeSupported) + return _AllowDarkModeForWindow(hWnd, allow); + return false; +} + +bool IsHighContrast() +{ + HIGHCONTRASTW highContrast = { sizeof(highContrast) }; + if (SystemParametersInfoW(SPI_GETHIGHCONTRAST, sizeof(highContrast), &highContrast, FALSE)) + return highContrast.dwFlags & HCF_HIGHCONTRASTON; + return false; +} + +void RefreshTitleBarThemeColor(HWND hWnd) +{ + BOOL dark = FALSE; + if (_IsDarkModeAllowedForWindow(hWnd) && + _ShouldAppsUseDarkMode() && + !IsHighContrast()) + { + dark = TRUE; + } + if (g_buildNumber < 18362) + SetPropW(hWnd, L"UseImmersiveDarkModeColors", reinterpret_cast(static_cast(dark))); + else if (_SetWindowCompositionAttribute) + { + WINDOWCOMPOSITIONATTRIBDATA data = { WCA_USEDARKMODECOLORS, &dark, sizeof(dark) }; + _SetWindowCompositionAttribute(hWnd, &data); + } +} + +bool IsColorSchemeChangeMessage(LPARAM lParam) +{ + bool is = false; + if (lParam && CompareStringOrdinal(reinterpret_cast(lParam), -1, L"ImmersiveColorSet", -1, TRUE) == CSTR_EQUAL) + { + _RefreshImmersiveColorPolicyState(); + is = true; + } + _GetIsImmersiveColorUsingHighContrast(IHCM_REFRESH); + return is; +} + +bool IsColorSchemeChangeMessage(UINT message, LPARAM lParam) +{ + if (message == WM_SETTINGCHANGE) + return IsColorSchemeChangeMessage(lParam); + return false; +} + +void AllowDarkModeForApp(bool allow) +{ + if (_AllowDarkModeForApp) + _AllowDarkModeForApp(allow); + else if (_SetPreferredAppMode) + _SetPreferredAppMode(allow ? AllowDark : Default); +} + +void FixDarkScrollBar() +{ + // Disable this, doesn't look good. + return; + + HMODULE hComctl = LoadLibraryExW(L"comctl32.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (hComctl) + { + auto addr = FindDelayLoadThunkInModule(hComctl, "uxtheme.dll", 49); // OpenNcThemeData + if (addr) + { + DWORD oldProtect; + if (VirtualProtect(addr, sizeof(IMAGE_THUNK_DATA), PAGE_READWRITE, &oldProtect)) + { + auto MyOpenThemeData = [](HWND hWnd, LPCWSTR classList) -> HTHEME { + if (wcscmp(classList, L"ScrollBar") == 0) + { + hWnd = nullptr; + classList = L"Explorer::ScrollBar"; + } + return _OpenNcThemeData(hWnd, classList); + }; + + addr->u1.Function = reinterpret_cast(static_cast(MyOpenThemeData)); + VirtualProtect(addr, sizeof(IMAGE_THUNK_DATA), oldProtect, &oldProtect); + } + } + } +} + +void DarkModeInitDialog(HWND hDlg) { + if (g_darkModeSupported) { + _SetWindowTheme(GetDlgItem(hDlg, IDOK), L"Explorer", nullptr); + SendMessageW(hDlg, WM_THEMECHANGED, 0, 0); + } +} + +LRESULT DarkModeDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { + constexpr COLORREF darkBkColor = 0x383838; + constexpr COLORREF darkTextColor = 0xFFFFFF; + static HBRUSH hbrBkgnd = nullptr; + + switch (message) { + case WM_CTLCOLORDLG: + case WM_CTLCOLORSTATIC: + { + if (g_darkModeSupported && g_darkModeEnabled) + { + HDC hdc = reinterpret_cast(wParam); + SetTextColor(hdc, darkTextColor); + SetBkColor(hdc, darkBkColor); + if (!hbrBkgnd) + hbrBkgnd = CreateSolidBrush(darkBkColor); + return reinterpret_cast(hbrBkgnd); + } + break; + } + case WM_SETTINGCHANGE: + { + if (g_darkModeSupported && IsColorSchemeChangeMessage(lParam)) + SendMessageW(hDlg, WM_THEMECHANGED, 0, 0); + break; + } + case WM_THEMECHANGED: + { + if (g_darkModeSupported) + { + _AllowDarkModeForWindow(hDlg, g_darkModeEnabled); + RefreshTitleBarThemeColor(hDlg); + + HWND hButton = GetDlgItem(hDlg, IDOK); + _AllowDarkModeForWindow(hButton, g_darkModeEnabled); + SendMessageW(hButton, WM_THEMECHANGED, 0, 0); + + UpdateWindow(hDlg); + } + break; + } + } + return FALSE; +} + +bool IsDarkModeEnabled() { + return g_darkModeEnabled; +} + +constexpr bool CheckBuildNumber(DWORD buildNumber) +{ + // TODO: This is BS. + + return (buildNumber == 17763 || // 1809 + buildNumber == 18362 || // 1903 + buildNumber == 18363 || // 1909 + buildNumber >= 19041); // Windows 11 +} + +void InitDarkMode() +{ + auto RtlGetNtVersionNumbers = reinterpret_cast(GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "RtlGetNtVersionNumbers")); + if (RtlGetNtVersionNumbers) + { + DWORD major, minor; + RtlGetNtVersionNumbers(&major, &minor, &g_buildNumber); + g_buildNumber &= ~0xF0000000; + if (major == 10 && minor == 0 && CheckBuildNumber(g_buildNumber)) + { + HMODULE hUxtheme = LoadLibraryExW(L"uxtheme.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (hUxtheme) + { + _OpenNcThemeData = reinterpret_cast(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(49))); + _RefreshImmersiveColorPolicyState = reinterpret_cast(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(104))); + _GetIsImmersiveColorUsingHighContrast = reinterpret_cast(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(106))); + _ShouldAppsUseDarkMode = reinterpret_cast(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(132))); + _AllowDarkModeForWindow = reinterpret_cast(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(133))); + + auto ord135 = GetProcAddress(hUxtheme, MAKEINTRESOURCEA(135)); + if (g_buildNumber < 18362) + _AllowDarkModeForApp = reinterpret_cast(ord135); + else + _SetPreferredAppMode = reinterpret_cast(ord135); + + //_FlushMenuThemes = reinterpret_cast(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(136))); + _IsDarkModeAllowedForWindow = reinterpret_cast(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(137))); + + _SetWindowCompositionAttribute = reinterpret_cast(GetProcAddress(GetModuleHandleW(L"user32.dll"), "SetWindowCompositionAttribute")); + _SetWindowTheme = reinterpret_cast(GetProcAddress(hUxtheme, "SetWindowTheme")); + + if (_OpenNcThemeData && + _RefreshImmersiveColorPolicyState && + _ShouldAppsUseDarkMode && + _AllowDarkModeForWindow && + (_AllowDarkModeForApp || _SetPreferredAppMode) && + //_FlushMenuThemes && + _IsDarkModeAllowedForWindow) + { + g_darkModeSupported = true; + + AllowDarkModeForApp(true); + _RefreshImmersiveColorPolicyState(); + + g_darkModeEnabled = _ShouldAppsUseDarkMode() && !IsHighContrast(); + + FixDarkScrollBar(); + } + } + } + } +} diff --git a/Windows/W32Util/DarkMode.h b/Windows/W32Util/DarkMode.h new file mode 100644 index 000000000000..50ac9498db1c --- /dev/null +++ b/Windows/W32Util/DarkMode.h @@ -0,0 +1,110 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include +#include +#include +#include +#include + +#include "IatHook.h" + +enum IMMERSIVE_HC_CACHE_MODE +{ + IHCM_USE_CACHED_VALUE, + IHCM_REFRESH +}; + +// 1903 18362 +enum 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; +}; + +using fnRtlGetNtVersionNumbers = void (WINAPI *)(LPDWORD major, LPDWORD minor, LPDWORD build); +using fnSetWindowCompositionAttribute = BOOL (WINAPI *)(HWND hWnd, WINDOWCOMPOSITIONATTRIBDATA*); +// 1809 17763 +using fnShouldAppsUseDarkMode = bool (WINAPI *)(); // ordinal 132 +using fnAllowDarkModeForWindow = bool (WINAPI *)(HWND hWnd, bool allow); // ordinal 133 +using fnAllowDarkModeForApp = bool (WINAPI *)(bool allow); // ordinal 135, in 1809 +using fnFlushMenuThemes = void (WINAPI *)(); // ordinal 136 +using fnRefreshImmersiveColorPolicyState = void (WINAPI *)(); // ordinal 104 +using fnIsDarkModeAllowedForWindow = bool (WINAPI *)(HWND hWnd); // ordinal 137 +using fnGetIsImmersiveColorUsingHighContrast = bool (WINAPI *)(IMMERSIVE_HC_CACHE_MODE mode); // ordinal 106 +using fnOpenNcThemeData = HTHEME(WINAPI *)(HWND hWnd, LPCWSTR pszClassList); // ordinal 49 +// 1903 18362 +using fnShouldSystemUseDarkMode = bool (WINAPI *)(); // ordinal 138 +using fnSetPreferredAppMode = PreferredAppMode (WINAPI *)(PreferredAppMode appMode); // ordinal 135, in 1903 +using fnIsDarkModeAllowedForApp = bool (WINAPI *)(); // ordinal 139 +using fnSetWindowTheme = void (WINAPI*)(HWND, LPCWSTR, LPCWSTR); +//--------------------------------------------------------------------------- + +extern fnSetWindowCompositionAttribute _SetWindowCompositionAttribute; +extern fnShouldAppsUseDarkMode _ShouldAppsUseDarkMode; +extern fnAllowDarkModeForWindow _AllowDarkModeForWindow; +extern fnAllowDarkModeForApp _AllowDarkModeForApp; +extern fnFlushMenuThemes _FlushMenuThemes; +extern fnRefreshImmersiveColorPolicyState _RefreshImmersiveColorPolicyState; +extern fnIsDarkModeAllowedForWindow _IsDarkModeAllowedForWindow; +extern fnGetIsImmersiveColorUsingHighContrast _GetIsImmersiveColorUsingHighContrast; +extern fnOpenNcThemeData _OpenNcThemeData; +// 1903 18362 +extern fnShouldSystemUseDarkMode _ShouldSystemUseDarkMode; +extern fnSetPreferredAppMode _SetPreferredAppMode; +extern fnSetWindowTheme _SetWindowTheme; + +extern bool g_darkModeSupported; +extern bool g_darkModeEnabled; + +void InitDarkMode(); +bool AllowDarkModeForWindow(HWND hWnd, bool allow); +void RefreshTitleBarThemeColor(HWND hWnd); +bool IsColorSchemeChangeMessage(LPARAM lParam); +bool IsDarkModeEnabled(); + +void DarkModeInitDialog(HWND hDlg); +LRESULT DarkModeDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); diff --git a/Windows/W32Util/IatHook.h b/Windows/W32Util/IatHook.h new file mode 100644 index 000000000000..1f259a5f20c3 --- /dev/null +++ b/Windows/W32Util/IatHook.h @@ -0,0 +1,91 @@ +// This file contains code from +// https://github.com/stevemk14ebr/PolyHook_2_0/blob/master/sources/IatHook.cpp +// which is licensed under the MIT License. +// See PolyHook_2_0-LICENSE for more information. + +#pragma once + +template +constexpr T RVA2VA(T1 base, T2 rva) +{ + return reinterpret_cast(reinterpret_cast(base) + rva); +} + +template +constexpr T DataDirectoryFromModuleBase(void *moduleBase, size_t entryID) +{ + auto dosHdr = reinterpret_cast(moduleBase); + auto ntHdr = RVA2VA(moduleBase, dosHdr->e_lfanew); + auto dataDir = ntHdr->OptionalHeader.DataDirectory; + return RVA2VA(moduleBase, dataDir[entryID].VirtualAddress); +} + +inline PIMAGE_THUNK_DATA FindAddressByName(void *moduleBase, PIMAGE_THUNK_DATA impName, PIMAGE_THUNK_DATA impAddr, const char *funcName) +{ + for (; impName->u1.Ordinal; ++impName, ++impAddr) + { + if (IMAGE_SNAP_BY_ORDINAL(impName->u1.Ordinal)) + continue; + + auto import = RVA2VA(moduleBase, impName->u1.AddressOfData); + if (strcmp(import->Name, funcName) != 0) + continue; + return impAddr; + } + return nullptr; +} + +inline PIMAGE_THUNK_DATA FindAddressByOrdinal(void *moduleBase, PIMAGE_THUNK_DATA impName, PIMAGE_THUNK_DATA impAddr, uint16_t ordinal) +{ + for (; impName->u1.Ordinal; ++impName, ++impAddr) + { + if (IMAGE_SNAP_BY_ORDINAL(impName->u1.Ordinal) && IMAGE_ORDINAL(impName->u1.Ordinal) == ordinal) + return impAddr; + } + return nullptr; +} + +inline PIMAGE_THUNK_DATA FindIatThunkInModule(void *moduleBase, const char *dllName, const char *funcName) +{ + auto imports = DataDirectoryFromModuleBase(moduleBase, IMAGE_DIRECTORY_ENTRY_IMPORT); + for (; imports->Name; ++imports) + { + if (_stricmp(RVA2VA(moduleBase, imports->Name), dllName) != 0) + continue; + + auto origThunk = RVA2VA(moduleBase, imports->OriginalFirstThunk); + auto thunk = RVA2VA(moduleBase, imports->FirstThunk); + return FindAddressByName(moduleBase, origThunk, thunk, funcName); + } + return nullptr; +} + +inline PIMAGE_THUNK_DATA FindDelayLoadThunkInModule(void *moduleBase, const char *dllName, const char *funcName) +{ + auto imports = DataDirectoryFromModuleBase(moduleBase, IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT); + for (; imports->DllNameRVA; ++imports) + { + if (_stricmp(RVA2VA(moduleBase, imports->DllNameRVA), dllName) != 0) + continue; + + auto impName = RVA2VA(moduleBase, imports->ImportNameTableRVA); + auto impAddr = RVA2VA(moduleBase, imports->ImportAddressTableRVA); + return FindAddressByName(moduleBase, impName, impAddr, funcName); + } + return nullptr; +} + +inline PIMAGE_THUNK_DATA FindDelayLoadThunkInModule(void *moduleBase, const char *dllName, uint16_t ordinal) +{ + auto imports = DataDirectoryFromModuleBase(moduleBase, IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT); + for (; imports->DllNameRVA; ++imports) + { + if (_stricmp(RVA2VA(moduleBase, imports->DllNameRVA), dllName) != 0) + continue; + + auto impName = RVA2VA(moduleBase, imports->ImportNameTableRVA); + auto impAddr = RVA2VA(moduleBase, imports->ImportAddressTableRVA); + return FindAddressByOrdinal(moduleBase, impName, impAddr, ordinal); + } + return nullptr; +} diff --git a/Windows/W32Util/UAHMenuBar.cpp b/Windows/W32Util/UAHMenuBar.cpp new file mode 100644 index 000000000000..32f900358588 --- /dev/null +++ b/Windows/W32Util/UAHMenuBar.cpp @@ -0,0 +1,110 @@ +#include "Common/CommonWindows.h" + +#include +#include + +#include "Windows/W32Util/UAHMenuBar.h" + +static HTHEME g_menuTheme = nullptr; + +// processes messages related to UAH / custom menubar drawing. +// return true if handled, false to continue with normal processing in your wndproc +bool UAHDarkModeWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, LRESULT *lr) +{ + switch (message) + { + case WM_UAHDRAWMENU: + { + UAHMENU *pUDM = (UAHMENU *)lParam; + RECT rc = { 0 }; + + // get the menubar rect + { + MENUBARINFO mbi = { sizeof(mbi) }; + GetMenuBarInfo(hWnd, OBJID_MENU, 0, &mbi); + + RECT rcWindow; + GetWindowRect(hWnd, &rcWindow); + + // the rcBar is offset by the window rect + rc = mbi.rcBar; + OffsetRect(&rc, -rcWindow.left, -rcWindow.top); + + rc.top -= 1; + } + + if (!g_menuTheme) { + g_menuTheme = OpenThemeData(hWnd, L"Menu"); + } + + DrawThemeBackground(g_menuTheme, pUDM->hdc, MENU_POPUPITEM, MPI_NORMAL, &rc, nullptr); + return true; + } + case WM_UAHDRAWMENUITEM: + { + UAHDRAWMENUITEM *pUDMI = (UAHDRAWMENUITEM *)lParam; + + // get the menu item string + wchar_t menuString[256] = { 0 }; + MENUITEMINFO mii = { sizeof(mii), MIIM_STRING }; + { + mii.dwTypeData = menuString; + mii.cch = (sizeof(menuString) / 2) - 1; + + GetMenuItemInfo(pUDMI->um.hmenu, pUDMI->umi.iPosition, TRUE, &mii); + } + + // get the item state for drawing + + DWORD dwFlags = DT_CENTER | DT_SINGLELINE | DT_VCENTER; + + int iTextStateID = 0; + int iBackgroundStateID = 0; + { + if ((pUDMI->dis.itemState & ODS_INACTIVE) | (pUDMI->dis.itemState & ODS_DEFAULT)) { + // normal display + iTextStateID = MPI_NORMAL; + iBackgroundStateID = MPI_NORMAL; + } + if (pUDMI->dis.itemState & ODS_HOTLIGHT) { + // hot tracking + iTextStateID = MPI_HOT; + iBackgroundStateID = MPI_HOT; + } + if (pUDMI->dis.itemState & ODS_SELECTED) { + // clicked -- MENU_POPUPITEM has no state for this, though MENU_BARITEM does + iTextStateID = MPI_HOT; + iBackgroundStateID = MPI_HOT; + } + if ((pUDMI->dis.itemState & ODS_GRAYED) || (pUDMI->dis.itemState & ODS_DISABLED)) { + // disabled / grey text + iTextStateID = MPI_DISABLED; + iBackgroundStateID = MPI_DISABLED; + } + if (pUDMI->dis.itemState & ODS_NOACCEL) { + dwFlags |= DT_HIDEPREFIX; + } + } + + if (!g_menuTheme) { + g_menuTheme = OpenThemeData(hWnd, L"Menu"); + } + + DrawThemeBackground(g_menuTheme, pUDMI->um.hdc, MENU_POPUPITEM, iBackgroundStateID, &pUDMI->dis.rcItem, nullptr); + DrawThemeText(g_menuTheme, pUDMI->um.hdc, MENU_POPUPITEM, iTextStateID, menuString, mii.cch, dwFlags, 0, &pUDMI->dis.rcItem); + + return true; + } + case WM_THEMECHANGED: + { + if (g_menuTheme) { + CloseThemeData(g_menuTheme); + g_menuTheme = nullptr; + } + // continue processing in main wndproc + return false; + } + default: + return false; + } +} diff --git a/Windows/W32Util/UAHMenuBar.h b/Windows/W32Util/UAHMenuBar.h new file mode 100644 index 000000000000..95f9c0feb70f --- /dev/null +++ b/Windows/W32Util/UAHMenuBar.h @@ -0,0 +1,74 @@ +#pragma once + +// MIT license, see LICENSE +// Copyright(c) 2021 adzm / Adam D. Walling + +// processes messages related to UAH / custom menubar drawing. +// return true if handled, false to continue with normal processing in your wndproc +bool UAHDarkModeWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, LRESULT* lr); + +// window messages related to menu bar drawing +#define WM_UAHDESTROYWINDOW 0x0090 // handled by DefWindowProc +#define WM_UAHDRAWMENU 0x0091 // lParam is UAHMENU +#define WM_UAHDRAWMENUITEM 0x0092 // lParam is UAHDRAWMENUITEM +#define WM_UAHINITMENU 0x0093 // handled by DefWindowProc +#define WM_UAHMEASUREMENUITEM 0x0094 // lParam is UAHMEASUREMENUITEM +#define WM_UAHNCPAINTMENUPOPUP 0x0095 // handled by DefWindowProc + +// describes the sizes of the menu bar or menu item +typedef union tagUAHMENUITEMMETRICS +{ + // cx appears to be 14 / 0xE less than rcItem's width! + // cy 0x14 seems stable, i wonder if it is 4 less than rcItem's height which is always 24 atm + struct { + DWORD cx; + DWORD cy; + } rgsizeBar[2]; + struct { + DWORD cx; + DWORD cy; + } rgsizePopup[4]; +} UAHMENUITEMMETRICS; + +// not really used in our case but part of the other structures +typedef struct tagUAHMENUPOPUPMETRICS +{ + DWORD rgcx[4]; + DWORD fUpdateMaxWidths : 2; // from kernel symbols, padded to full dword +} UAHMENUPOPUPMETRICS; + +// hmenu is the main window menu; hdc is the context to draw in +typedef struct tagUAHMENU +{ + HMENU hmenu; + HDC hdc; + DWORD dwFlags; // no idea what these mean, in my testing it's either 0x00000a00 or sometimes 0x00000a10 +} UAHMENU; + +// menu items are always referred to by iPosition here +typedef struct tagUAHMENUITEM +{ + int iPosition; // 0-based position of menu item in menubar + UAHMENUITEMMETRICS umim; + UAHMENUPOPUPMETRICS umpm; +} UAHMENUITEM; + +// the DRAWITEMSTRUCT contains the states of the menu items, as well as +// the position index of the item in the menu, which is duplicated in +// the UAHMENUITEM's iPosition as well +typedef struct UAHDRAWMENUITEM +{ + DRAWITEMSTRUCT dis; // itemID looks uninitialized + UAHMENU um; + UAHMENUITEM umi; +} UAHDRAWMENUITEM; + +// the MEASUREITEMSTRUCT is intended to be filled with the size of the item +// height appears to be ignored, but width can be modified +typedef struct tagUAHMEASUREMENUITEM +{ + MEASUREITEMSTRUCT mis; + UAHMENU um; + UAHMENUITEM umi; +} UAHMEASUREMENUITEM; + diff --git a/Windows/main.cpp b/Windows/main.cpp index ebfff8b6dcf6..35cf5436e342 100644 --- a/Windows/main.cpp +++ b/Windows/main.cpp @@ -42,6 +42,7 @@ #include "Common/Thread/ThreadUtil.h" #include "Common/Data/Encoding/Utf8.h" #include "Common/Net/Resolve.h" +#include "W32Util/DarkMode.h" #include "Core/Config.h" #include "Core/ConfigValues.h" @@ -553,6 +554,8 @@ static void WinMainInit() { // FMA3 support in the 2013 CRT is broken on Vista and Windows 7 RTM (fixed in SP1). Just disable it. _set_FMA3_enable(0); #endif + + InitDarkMode(); } static void WinMainCleanup() {