|
| 1 | +/** |
| 2 | + * @file src/platform/windows/win_dark_mode.cpp |
| 3 | + * @brief Implementation of Windows dark mode support |
| 4 | + */ |
| 5 | + |
| 6 | +#ifdef _WIN32 |
| 7 | + |
| 8 | +#include "win_dark_mode.h" |
| 9 | + |
| 10 | +#include <windows.h> |
| 11 | +#include <dwmapi.h> |
| 12 | + |
| 13 | +#pragma comment(lib, "dwmapi.lib") |
| 14 | + |
| 15 | +namespace win_dark_mode { |
| 16 | + |
| 17 | + // Undocumented PreferredAppMode enum from uxtheme.dll |
| 18 | + enum class PreferredAppMode { |
| 19 | + Default = 0, // Use system default (usually light) |
| 20 | + AllowDark = 1, // Allow dark mode (follow system setting) |
| 21 | + ForceDark = 2, // Force dark mode regardless of system setting |
| 22 | + ForceLight = 3, // Force light mode regardless of system setting |
| 23 | + Max = 4, |
| 24 | + }; |
| 25 | + |
| 26 | + // Function pointer types for undocumented uxtheme.dll APIs |
| 27 | + using SetPreferredAppModeFn = PreferredAppMode(WINAPI *)(PreferredAppMode); |
| 28 | + using AllowDarkModeForAppFn = BOOL(WINAPI *)(BOOL); |
| 29 | + |
| 30 | + // Global function pointers (initialized once) |
| 31 | + static SetPreferredAppModeFn g_SetPreferredAppMode = nullptr; |
| 32 | + static AllowDarkModeForAppFn g_AllowDarkModeForApp = nullptr; |
| 33 | + |
| 34 | + /** |
| 35 | + * @brief Initialize the dark mode API function pointers |
| 36 | + * |
| 37 | + * This function loads uxtheme.dll and retrieves the undocumented function pointers. |
| 38 | + * It only runs once and caches the results. |
| 39 | + */ |
| 40 | + static void |
| 41 | + init_dark_mode_apis() { |
| 42 | + static bool initialized = false; |
| 43 | + if (initialized) { |
| 44 | + return; |
| 45 | + } |
| 46 | + initialized = true; |
| 47 | + |
| 48 | + // Load uxtheme.dll |
| 49 | + HMODULE hUxTheme = LoadLibraryW(L"uxtheme.dll"); |
| 50 | + if (!hUxTheme) { |
| 51 | + return; |
| 52 | + } |
| 53 | + |
| 54 | + // Try to get SetPreferredAppMode (Windows 10 1903+) |
| 55 | + // Both SetPreferredAppMode and AllowDarkModeForApp use ordinal 135, |
| 56 | + // but they have different signatures depending on Windows version: |
| 57 | + // - Win10 1903+: SetPreferredAppMode(PreferredAppMode) -> PreferredAppMode |
| 58 | + // - Win10 1809-1903: AllowDarkModeForApp(BOOL) -> BOOL |
| 59 | + // We try SetPreferredAppMode first with the newer signature |
| 60 | + g_SetPreferredAppMode = |
| 61 | + reinterpret_cast<SetPreferredAppModeFn>( |
| 62 | + GetProcAddress(hUxTheme, MAKEINTRESOURCEA(135))); |
| 63 | + |
| 64 | + // If SetPreferredAppMode signature doesn't match (older Windows), |
| 65 | + // try AllowDarkModeForApp with BOOL signature using the same ordinal |
| 66 | + if (!g_SetPreferredAppMode) { |
| 67 | + g_AllowDarkModeForApp = |
| 68 | + reinterpret_cast<AllowDarkModeForAppFn>( |
| 69 | + GetProcAddress(hUxTheme, MAKEINTRESOURCEA(135))); |
| 70 | + } |
| 71 | + |
| 72 | + // Note: We intentionally don't call FreeLibrary(hUxTheme) because we need |
| 73 | + // the function pointers to remain valid for the lifetime of the process |
| 74 | + } |
| 75 | + |
| 76 | + void |
| 77 | + enable_process_dark_mode() { |
| 78 | + // Initialize the API function pointers |
| 79 | + init_dark_mode_apis(); |
| 80 | + |
| 81 | + // Call the appropriate function based on what's available |
| 82 | + if (g_SetPreferredAppMode) { |
| 83 | + // Windows 10 1903+ supports SetPreferredAppMode |
| 84 | + // Use AllowDark to follow the system's dark/light mode preference |
| 85 | + g_SetPreferredAppMode(PreferredAppMode::AllowDark); |
| 86 | + } |
| 87 | + else if (g_AllowDarkModeForApp) { |
| 88 | + // Windows 10 1809-1903 supports AllowDarkModeForApp |
| 89 | + // TRUE means allow dark mode (follows system setting) |
| 90 | + g_AllowDarkModeForApp(TRUE); |
| 91 | + } |
| 92 | + // If neither API is available, dark mode is not supported on this Windows version |
| 93 | + // (Windows 10 < 1809 or earlier). We silently do nothing in this case. |
| 94 | + } |
| 95 | + |
| 96 | + void |
| 97 | + apply_window_dark_title_bar(HWND hwnd, bool enable) { |
| 98 | + if (!hwnd) { |
| 99 | + return; |
| 100 | + } |
| 101 | + |
| 102 | + // DWMWA_USE_IMMERSIVE_DARK_MODE is documented for Windows 11 |
| 103 | + // but also works on Windows 10 20H1+ |
| 104 | + constexpr DWORD DWMWA_USE_IMMERSIVE_DARK_MODE = 20; |
| 105 | + |
| 106 | + BOOL useDark = enable ? TRUE : FALSE; |
| 107 | + DwmSetWindowAttribute( |
| 108 | + hwnd, |
| 109 | + DWMWA_USE_IMMERSIVE_DARK_MODE, |
| 110 | + &useDark, |
| 111 | + sizeof(useDark)); |
| 112 | + } |
| 113 | + |
| 114 | +} // namespace win_dark_mode |
| 115 | + |
| 116 | +#endif // _WIN32 |
0 commit comments