|
| 1 | +// Copyright (c) 2020 Microsoft Inc. All rights reserved. |
| 2 | +// Use of this source code is governed by a BSD-style license that can be |
| 3 | +// found in the LICENSE-CHROMIUM file. |
| 4 | + |
| 5 | +#include "shell/browser/win/dark_mode.h" |
| 6 | + |
| 7 | +#include <dwmapi.h> // DwmSetWindowAttribute() |
| 8 | + |
| 9 | +#include "base/files/file_path.h" |
| 10 | +#include "base/scoped_native_library.h" |
| 11 | +#include "base/win/pe_image.h" |
| 12 | +#include "base/win/win_util.h" |
| 13 | +#include "base/win/windows_version.h" |
| 14 | + |
| 15 | +// This namespace contains code originally from |
| 16 | +// https://github.com/ysc3839/win32-darkmode/ |
| 17 | +// governed by the MIT license and (c) Richard Yu |
| 18 | +namespace { |
| 19 | + |
| 20 | +// 1903 18362 |
| 21 | +enum PreferredAppMode { Default, AllowDark, ForceDark, ForceLight, Max }; |
| 22 | + |
| 23 | +bool g_darkModeSupported = false; |
| 24 | +bool g_darkModeEnabled = false; |
| 25 | +DWORD g_buildNumber = 0; |
| 26 | + |
| 27 | +enum WINDOWCOMPOSITIONATTRIB { |
| 28 | + WCA_USEDARKMODECOLORS = 26 // build 18875+ |
| 29 | +}; |
| 30 | +struct WINDOWCOMPOSITIONATTRIBDATA { |
| 31 | + WINDOWCOMPOSITIONATTRIB Attrib; |
| 32 | + PVOID pvData; |
| 33 | + SIZE_T cbData; |
| 34 | +}; |
| 35 | + |
| 36 | +using fnSetWindowCompositionAttribute = |
| 37 | + BOOL(WINAPI*)(HWND hWnd, WINDOWCOMPOSITIONATTRIBDATA*); |
| 38 | +fnSetWindowCompositionAttribute _SetWindowCompositionAttribute = nullptr; |
| 39 | + |
| 40 | +bool IsHighContrast() { |
| 41 | + HIGHCONTRASTW highContrast = {sizeof(highContrast)}; |
| 42 | + if (SystemParametersInfoW(SPI_GETHIGHCONTRAST, sizeof(highContrast), |
| 43 | + &highContrast, FALSE)) |
| 44 | + return highContrast.dwFlags & HCF_HIGHCONTRASTON; |
| 45 | + return false; |
| 46 | +} |
| 47 | + |
| 48 | +void RefreshTitleBarThemeColor(HWND hWnd, bool dark) { |
| 49 | + LONG ldark = dark; |
| 50 | + if (g_buildNumber >= 20161) { |
| 51 | + // DWMA_USE_IMMERSIVE_DARK_MODE = 20 |
| 52 | + DwmSetWindowAttribute(hWnd, 20, &ldark, sizeof dark); |
| 53 | + return; |
| 54 | + } |
| 55 | + if (g_buildNumber >= 18363) { |
| 56 | + auto data = WINDOWCOMPOSITIONATTRIBDATA{WCA_USEDARKMODECOLORS, &ldark, |
| 57 | + sizeof ldark}; |
| 58 | + _SetWindowCompositionAttribute(hWnd, &data); |
| 59 | + return; |
| 60 | + } |
| 61 | + DwmSetWindowAttribute(hWnd, 0x13, &ldark, sizeof ldark); |
| 62 | +} |
| 63 | + |
| 64 | +void InitDarkMode() { |
| 65 | + // confirm that we're running on a version of Windows |
| 66 | + // where the Dark Mode API is known |
| 67 | + auto* os_info = base::win::OSInfo::GetInstance(); |
| 68 | + g_buildNumber = os_info->version_number().build; |
| 69 | + auto const version = os_info->version(); |
| 70 | + if ((version < base::win::Version::WIN10_RS5) || |
| 71 | + (version > base::win::Version::WIN10_20H1)) { |
| 72 | + return; |
| 73 | + } |
| 74 | + |
| 75 | + // load "SetWindowCompositionAttribute", used in RefreshTitleBarThemeColor() |
| 76 | + _SetWindowCompositionAttribute = |
| 77 | + reinterpret_cast<decltype(_SetWindowCompositionAttribute)>( |
| 78 | + base::win::GetUser32FunctionPointer("SetWindowCompositionAttribute")); |
| 79 | + if (_SetWindowCompositionAttribute == nullptr) { |
| 80 | + return; |
| 81 | + } |
| 82 | + |
| 83 | + // load the dark mode functions from uxtheme.dll |
| 84 | + // * RefreshImmersiveColorPolicyState() |
| 85 | + // * ShouldAppsUseDarkMode() |
| 86 | + // * AllowDarkModeForApp() |
| 87 | + // * SetPreferredAppMode() |
| 88 | + // * AllowDarkModeForApp() (build < 18362) |
| 89 | + // * SetPreferredAppMode() (build >= 18362) |
| 90 | + |
| 91 | + base::NativeLibrary uxtheme = |
| 92 | + base::PinSystemLibrary(FILE_PATH_LITERAL("uxtheme.dll")); |
| 93 | + if (!uxtheme) { |
| 94 | + return; |
| 95 | + } |
| 96 | + auto ux_pei = base::win::PEImage(uxtheme); |
| 97 | + auto get_ux_proc_from_ordinal = [&ux_pei](int ordinal, auto* setme) { |
| 98 | + FARPROC proc = ux_pei.GetProcAddress(reinterpret_cast<LPCSTR>(ordinal)); |
| 99 | + *setme = reinterpret_cast<decltype(*setme)>(proc); |
| 100 | + }; |
| 101 | + |
| 102 | + // ordinal 104 |
| 103 | + using fnRefreshImmersiveColorPolicyState = VOID(WINAPI*)(); |
| 104 | + fnRefreshImmersiveColorPolicyState _RefreshImmersiveColorPolicyState = {}; |
| 105 | + get_ux_proc_from_ordinal(104, &_RefreshImmersiveColorPolicyState); |
| 106 | + |
| 107 | + // ordinal 132 |
| 108 | + using fnShouldAppsUseDarkMode = BOOL(WINAPI*)(); |
| 109 | + fnShouldAppsUseDarkMode _ShouldAppsUseDarkMode = {}; |
| 110 | + get_ux_proc_from_ordinal(132, &_ShouldAppsUseDarkMode); |
| 111 | + |
| 112 | + // ordinal 135, in 1809 |
| 113 | + using fnAllowDarkModeForApp = BOOL(WINAPI*)(BOOL allow); |
| 114 | + fnAllowDarkModeForApp _AllowDarkModeForApp = {}; |
| 115 | + |
| 116 | + // ordinal 135, in 1903 |
| 117 | + typedef PreferredAppMode(WINAPI * |
| 118 | + fnSetPreferredAppMode)(PreferredAppMode appMode); |
| 119 | + fnSetPreferredAppMode _SetPreferredAppMode = {}; |
| 120 | + |
| 121 | + if (g_buildNumber < 18362) { |
| 122 | + get_ux_proc_from_ordinal(135, &_AllowDarkModeForApp); |
| 123 | + } else { |
| 124 | + get_ux_proc_from_ordinal(135, &_SetPreferredAppMode); |
| 125 | + } |
| 126 | + |
| 127 | + // dark mode is supported iff we found the functions |
| 128 | + g_darkModeSupported = _RefreshImmersiveColorPolicyState && |
| 129 | + _ShouldAppsUseDarkMode && |
| 130 | + (_AllowDarkModeForApp || _SetPreferredAppMode); |
| 131 | + if (!g_darkModeSupported) { |
| 132 | + return; |
| 133 | + } |
| 134 | + |
| 135 | + // initial setup: allow dark mode to be used |
| 136 | + if (_AllowDarkModeForApp) { |
| 137 | + _AllowDarkModeForApp(true); |
| 138 | + } else if (_SetPreferredAppMode) { |
| 139 | + _SetPreferredAppMode(AllowDark); |
| 140 | + } |
| 141 | + _RefreshImmersiveColorPolicyState(); |
| 142 | + |
| 143 | + // check to see if dark mode is currently enabled |
| 144 | + g_darkModeEnabled = _ShouldAppsUseDarkMode() && !IsHighContrast(); |
| 145 | +} |
| 146 | + |
| 147 | +} // namespace |
| 148 | + |
| 149 | +namespace electron { |
| 150 | + |
| 151 | +void EnsureInitialized() { |
| 152 | + static bool initialized = false; |
| 153 | + if (!initialized) { |
| 154 | + initialized = true; |
| 155 | + ::InitDarkMode(); |
| 156 | + } |
| 157 | +} |
| 158 | + |
| 159 | +bool IsDarkPreferred(ui::NativeTheme::ThemeSource theme_source) { |
| 160 | + switch (theme_source) { |
| 161 | + case ui::NativeTheme::ThemeSource::kForcedLight: |
| 162 | + return false; |
| 163 | + case ui::NativeTheme::ThemeSource::kForcedDark: |
| 164 | + return g_darkModeSupported; |
| 165 | + case ui::NativeTheme::ThemeSource::kSystem: |
| 166 | + return g_darkModeEnabled; |
| 167 | + } |
| 168 | +} |
| 169 | + |
| 170 | +namespace win { |
| 171 | + |
| 172 | +void SetDarkModeForWindow(HWND hWnd, |
| 173 | + ui::NativeTheme::ThemeSource theme_source) { |
| 174 | + EnsureInitialized(); |
| 175 | + RefreshTitleBarThemeColor(hWnd, IsDarkPreferred(theme_source)); |
| 176 | +} |
| 177 | + |
| 178 | +} // namespace win |
| 179 | + |
| 180 | +} // namespace electron |
0 commit comments