|
| 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 <dwmapi.h> |
| 11 | +#include <mutex> |
| 12 | + |
| 13 | +namespace win_dark_mode { |
| 14 | + |
| 15 | + // Undocumented PreferredAppMode enum from uxtheme.dll |
| 16 | + enum class PreferredAppMode { |
| 17 | + Default = 0, // Use system default (usually light) |
| 18 | + AllowDark = 1, // Allow dark mode (follow system setting) |
| 19 | + ForceDark = 2, // Force dark mode regardless of system setting |
| 20 | + ForceLight = 3, // Force light mode regardless of system setting |
| 21 | + Max = 4, |
| 22 | + }; |
| 23 | + |
| 24 | + // Function pointer types for undocumented uxtheme.dll APIs |
| 25 | + // Windows 10 1903+ uses SetPreferredAppMode (ordinal 135) |
| 26 | + // Windows 10 1809-1903 uses AllowDarkModeForApp (ordinal 135) - same ordinal, different signature |
| 27 | + // We use SetPreferredAppMode as it works on all modern Windows versions |
| 28 | + using SetPreferredAppModeFn = PreferredAppMode(WINAPI *)(PreferredAppMode); |
| 29 | + |
| 30 | + // Global function pointer (initialized once) |
| 31 | + static SetPreferredAppModeFn g_SetPreferredAppMode = nullptr; |
| 32 | + static std::once_flag g_init_flag; |
| 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 using std::call_once for thread safety. |
| 39 | + */ |
| 40 | + static void |
| 41 | + init_dark_mode_apis() { |
| 42 | + // Load uxtheme.dll |
| 43 | + HMODULE hUxTheme = LoadLibraryW(L"uxtheme.dll"); |
| 44 | + if (!hUxTheme) { |
| 45 | + return; |
| 46 | + } |
| 47 | + |
| 48 | + // Get SetPreferredAppMode (ordinal 135) |
| 49 | + // This works on Windows 10 1809+ (build 17763+) |
| 50 | + // On older versions, the function will exist but may not have the expected effect |
| 51 | + g_SetPreferredAppMode = |
| 52 | + reinterpret_cast<SetPreferredAppModeFn>( |
| 53 | + GetProcAddress(hUxTheme, MAKEINTRESOURCEA(135))); |
| 54 | + |
| 55 | + // Note: We intentionally don't call FreeLibrary(hUxTheme) because we need |
| 56 | + // the function pointers to remain valid for the lifetime of the process |
| 57 | + } |
| 58 | + |
| 59 | + void |
| 60 | + enable_process_dark_mode() { |
| 61 | + // Thread-safe one-time initialization |
| 62 | + std::call_once(g_init_flag, init_dark_mode_apis); |
| 63 | + |
| 64 | + // Call the function if available |
| 65 | + if (g_SetPreferredAppMode) { |
| 66 | + // Use AllowDark to follow the system's dark/light mode preference |
| 67 | + g_SetPreferredAppMode(PreferredAppMode::AllowDark); |
| 68 | + } |
| 69 | + // If API is not available, dark mode is not supported on this Windows version |
| 70 | + // (Windows 10 < 1809 or earlier). We silently do nothing in this case. |
| 71 | + } |
| 72 | + |
| 73 | + void |
| 74 | + apply_window_dark_title_bar(HWND hwnd, bool enable) { |
| 75 | + if (!hwnd) { |
| 76 | + return; |
| 77 | + } |
| 78 | + |
| 79 | + // DWMWA_USE_IMMERSIVE_DARK_MODE is documented for Windows 11 |
| 80 | + // but also works on Windows 10 20H1+ |
| 81 | + constexpr DWORD DWMWA_USE_IMMERSIVE_DARK_MODE = 20; |
| 82 | + |
| 83 | + BOOL useDark = enable ? TRUE : FALSE; |
| 84 | + DwmSetWindowAttribute( |
| 85 | + hwnd, |
| 86 | + DWMWA_USE_IMMERSIVE_DARK_MODE, |
| 87 | + &useDark, |
| 88 | + sizeof(useDark)); |
| 89 | + } |
| 90 | + |
| 91 | +} // namespace win_dark_mode |
| 92 | + |
| 93 | +#endif // _WIN32 |
0 commit comments