|
1 | | -// Copyright (c) 2020 Microsoft Inc. All rights reserved. |
| 1 | +// Copyright (c) 2022 Microsoft Inc. All rights reserved. |
2 | 2 | // Use of this source code is governed by a BSD-style license that can be |
3 | 3 | // found in the LICENSE-CHROMIUM file. |
4 | 4 |
|
5 | 5 | #include "shell/browser/win/dark_mode.h" |
6 | 6 |
|
7 | 7 | #include <dwmapi.h> // DwmSetWindowAttribute() |
8 | 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 | 9 | #include "base/win/windows_version.h" |
14 | 10 |
|
| 11 | +// This flag works since Win10 20H1 but is not documented until Windows 11 |
| 12 | +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 |
| 13 | + |
15 | 14 | // This namespace contains code originally from |
16 | | -// https://github.com/ysc3839/win32-darkmode/ |
17 | | -// governed by the MIT license and (c) Richard Yu |
| 15 | +// https://github.com/microsoft/terminal |
| 16 | +// governed by the MIT license and (c) Microsoft Corporation. |
18 | 17 | namespace { |
19 | 18 |
|
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 | | -} |
| 19 | +// https://docs.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute |
| 20 | +HRESULT TrySetWindowTheme(HWND hWnd, bool dark) { |
| 21 | + const BOOL isDarkMode = dark; |
| 22 | + HRESULT result = DwmSetWindowAttribute(hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, |
| 23 | + &isDarkMode, sizeof(isDarkMode)); |
47 | 24 |
|
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 | | -} |
| 25 | + if (FAILED(result)) |
| 26 | + return result; |
63 | 27 |
|
64 | | -void InitDarkMode() { |
65 | | - // confirm that we're running on a version of Windows |
66 | | - // where the Dark Mode API is known |
67 | 28 | auto* os_info = base::win::OSInfo::GetInstance(); |
68 | | - g_buildNumber = os_info->version_number().build; |
69 | 29 | 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 | 30 |
|
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; |
| 31 | + // Toggle the nonclient area active state to force a redraw (Win10 workaround) |
| 32 | + if (version < base::win::Version::WIN11) { |
| 33 | + HWND activeWindow = GetActiveWindow(); |
| 34 | + SendMessage(hWnd, WM_NCACTIVATE, hWnd != activeWindow, 0); |
| 35 | + SendMessage(hWnd, WM_NCACTIVATE, hWnd == activeWindow, 0); |
133 | 36 | } |
134 | 37 |
|
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(); |
| 38 | + return S_OK; |
145 | 39 | } |
146 | 40 |
|
147 | 41 | } // namespace |
148 | 42 |
|
149 | 43 | namespace electron { |
150 | 44 |
|
151 | | -void EnsureInitialized() { |
152 | | - static bool initialized = false; |
153 | | - if (!initialized) { |
154 | | - initialized = true; |
155 | | - ::InitDarkMode(); |
156 | | - } |
157 | | -} |
| 45 | +namespace win { |
158 | 46 |
|
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 | | - } |
| 47 | +bool IsDarkModeSupported() { |
| 48 | + auto* os_info = base::win::OSInfo::GetInstance(); |
| 49 | + auto const version = os_info->version(); |
| 50 | + |
| 51 | + return version >= base::win::Version::WIN10_20H1; |
168 | 52 | } |
169 | 53 |
|
170 | | -namespace win { |
| 54 | +void SetDarkModeForWindow(HWND hWnd) { |
| 55 | + ui::NativeTheme* theme = ui::NativeTheme::GetInstanceForNativeUi(); |
| 56 | + bool dark = |
| 57 | + theme->ShouldUseDarkColors() && !theme->UserHasContrastPreference(); |
171 | 58 |
|
172 | | -void SetDarkModeForWindow(HWND hWnd, |
173 | | - ui::NativeTheme::ThemeSource theme_source) { |
174 | | - EnsureInitialized(); |
175 | | - RefreshTitleBarThemeColor(hWnd, IsDarkPreferred(theme_source)); |
| 59 | + TrySetWindowTheme(hWnd, dark); |
176 | 60 | } |
177 | 61 |
|
178 | 62 | } // namespace win |
|
0 commit comments