Skip to content

Commit f489e30

Browse files
authored
feat: honor nativeTheme.themeSource = 'dark' before creating BrowserWindow on Windows (electron#25373)
* fix: support 'dark' theme before creating windows.
1 parent d3f32c7 commit f489e30

8 files changed

Lines changed: 241 additions & 0 deletions

File tree

BUILD.gn

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -673,6 +673,14 @@ source_set("electron_lib") {
673673
}
674674

675675
sources += get_target_outputs(":electron_fuses")
676+
677+
if (is_win && enable_win_dark_mode_window_ui) {
678+
sources += [
679+
"shell/browser/win/dark_mode.cc",
680+
"shell/browser/win/dark_mode.h",
681+
]
682+
libs += [ "uxtheme.lib" ]
683+
}
676684
}
677685

678686
electron_paks("packed_resources") {

buildflags/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ buildflag_header("buildflags") {
2020
"ENABLE_ELECTRON_EXTENSIONS=$enable_electron_extensions",
2121
"ENABLE_BUILTIN_SPELLCHECKER=$enable_builtin_spellchecker",
2222
"ENABLE_PICTURE_IN_PICTURE=$enable_picture_in_picture",
23+
"ENABLE_WIN_DARK_MODE_WINDOW_UI=$enable_win_dark_mode_window_ui",
2324
"OVERRIDE_LOCATION_PROVIDER=$enable_fake_location_provider",
2425
]
2526
}

buildflags/buildflags.gni

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,7 @@ declare_args() {
3333

3434
# Enable Spellchecker support
3535
enable_builtin_spellchecker = true
36+
37+
# Undocumented Windows dark mode API
38+
enable_win_dark_mode_window_ui = false
3639
}

shell/browser/ui/win/electron_desktop_window_tree_host_win.cc

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,15 @@
55
#include "shell/browser/ui/win/electron_desktop_window_tree_host_win.h"
66

77
#include "base/win/windows_version.h"
8+
#include "electron/buildflags/buildflags.h"
89
#include "shell/browser/ui/views/win_frame_view.h"
10+
#include "ui/base/win/hwnd_metrics.h"
911
#include "ui/base/win/shell.h"
1012

13+
#if BUILDFLAG(ENABLE_WIN_DARK_MODE_WINDOW_UI)
14+
#include "shell/browser/win/dark_mode.h"
15+
#endif
16+
1117
namespace electron {
1218

1319
ElectronDesktopWindowTreeHostWin::ElectronDesktopWindowTreeHostWin(
@@ -23,6 +29,15 @@ bool ElectronDesktopWindowTreeHostWin::PreHandleMSG(UINT message,
2329
WPARAM w_param,
2430
LPARAM l_param,
2531
LRESULT* result) {
32+
#if BUILDFLAG(ENABLE_WIN_DARK_MODE_WINDOW_UI)
33+
if (message == WM_NCCREATE) {
34+
HWND const hwnd = GetAcceleratedWidget();
35+
auto const theme_source =
36+
ui::NativeTheme::GetInstanceForNativeUi()->theme_source();
37+
win::SetDarkModeForWindow(hwnd, theme_source);
38+
}
39+
#endif
40+
2641
return native_window_view_->PreHandleMSG(message, w_param, l_param, result);
2742
}
2843

shell/browser/win/dark_mode.cc

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
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

shell/browser/win/dark_mode.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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+
#ifndef SHELL_BROWSER_WIN_DARK_MODE_H_
6+
#define SHELL_BROWSER_WIN_DARK_MODE_H_
7+
8+
#ifdef WIN32_LEAN_AND_MEAN
9+
#include <Windows.h>
10+
#else
11+
#define WIN32_LEAN_AND_MEAN
12+
#include <Windows.h>
13+
#undef WIN32_LEAN_AND_MEAN
14+
#endif
15+
16+
#include "ui/native_theme/native_theme.h"
17+
18+
namespace electron {
19+
20+
namespace win {
21+
22+
void SetDarkModeForWindow(HWND hWnd, ui::NativeTheme::ThemeSource theme_source);
23+
24+
} // namespace win
25+
26+
} // namespace electron
27+
28+
#endif // SHELL_BROWSER_WIN_DARK_MODE_H_

shell/common/api/features.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ bool IsPictureInPictureEnabled() {
5858
return BUILDFLAG(ENABLE_PICTURE_IN_PICTURE);
5959
}
6060

61+
bool IsWinDarkModeWindowUiEnabled() {
62+
return BUILDFLAG(ENABLE_WIN_DARK_MODE_WINDOW_UI);
63+
}
64+
6165
bool IsComponentBuild() {
6266
#if defined(COMPONENT_BUILD)
6367
return true;
@@ -85,6 +89,7 @@ void Initialize(v8::Local<v8::Object> exports,
8589
dict.SetMethod("isPictureInPictureEnabled", &IsPictureInPictureEnabled);
8690
dict.SetMethod("isComponentBuild", &IsComponentBuild);
8791
dict.SetMethod("isExtensionsEnabled", &IsExtensionsEnabled);
92+
dict.SetMethod("isWinDarkModeWindowUiEnabled", &IsWinDarkModeWindowUiEnabled);
8893
}
8994

9095
} // namespace

typings/internal-ambient.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ declare namespace NodeJS {
2424
isPictureInPictureEnabled(): boolean;
2525
isExtensionsEnabled(): boolean;
2626
isComponentBuild(): boolean;
27+
isWinDarkModeWindowUiEnabled(): boolean;
2728
}
2829

2930
interface IpcRendererBinding {

0 commit comments

Comments
 (0)