Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add immersive dark mode on windows #33624

Merged
merged 12 commits into from Jun 14, 2022
8 changes: 0 additions & 8 deletions BUILD.gn
Expand Up @@ -726,14 +726,6 @@ source_set("electron_lib") {

sources += get_target_outputs(":electron_fuses")

if (is_win && enable_win_dark_mode_window_ui) {
sources += [
"shell/browser/win/dark_mode.cc",
"shell/browser/win/dark_mode.h",
]
libs += [ "uxtheme.lib" ]
}

if (allow_runtime_configurable_key_storage) {
defines += [ "ALLOW_RUNTIME_CONFIGURABLE_KEY_STORAGE" ]
}
Expand Down
3 changes: 2 additions & 1 deletion build/fuses/fuses.json5
Expand Up @@ -7,5 +7,6 @@
"node_options": "1",
"node_cli_inspect": "1",
"embedded_asar_integrity_validation": "0",
"only_load_app_from_asar": "0"
"only_load_app_from_asar": "0",
"windows_10_immersive_dark_mode": "0"
}
1 change: 0 additions & 1 deletion buildflags/BUILD.gn
Expand Up @@ -19,7 +19,6 @@ buildflag_header("buildflags") {
"ENABLE_ELECTRON_EXTENSIONS=$enable_electron_extensions",
"ENABLE_BUILTIN_SPELLCHECKER=$enable_builtin_spellchecker",
"ENABLE_PICTURE_IN_PICTURE=$enable_picture_in_picture",
"ENABLE_WIN_DARK_MODE_WINDOW_UI=$enable_win_dark_mode_window_ui",
"OVERRIDE_LOCATION_PROVIDER=$enable_fake_location_provider",
]
}
3 changes: 0 additions & 3 deletions buildflags/buildflags.gni
Expand Up @@ -31,7 +31,4 @@ declare_args() {

# Enable Spellchecker support
enable_builtin_spellchecker = true

# Undocumented Windows dark mode API
enable_win_dark_mode_window_ui = false
}
2 changes: 2 additions & 0 deletions filenames.gni
Expand Up @@ -105,6 +105,8 @@ filenames = {
"shell/browser/ui/win/notify_icon.h",
"shell/browser/ui/win/taskbar_host.cc",
"shell/browser/ui/win/taskbar_host.h",
"shell/browser/win/dark_mode.cc",
"shell/browser/win/dark_mode.h",
"shell/browser/win/scoped_hstring.cc",
"shell/browser/win/scoped_hstring.h",
"shell/common/api/electron_api_native_image_win.cc",
Expand Down
23 changes: 12 additions & 11 deletions shell/browser/ui/win/electron_desktop_window_tree_host_win.cc
Expand Up @@ -7,13 +7,10 @@
#include "base/win/windows_version.h"
#include "electron/buildflags/buildflags.h"
#include "shell/browser/ui/views/win_frame_view.h"
#include "shell/browser/win/dark_mode.h"
#include "ui/base/win/hwnd_metrics.h"
#include "ui/base/win/shell.h"

#if BUILDFLAG(ENABLE_WIN_DARK_MODE_WINDOW_UI)
#include "shell/browser/win/dark_mode.h"
#endif

namespace electron {

ElectronDesktopWindowTreeHostWin::ElectronDesktopWindowTreeHostWin(
Expand All @@ -29,14 +26,13 @@ bool ElectronDesktopWindowTreeHostWin::PreHandleMSG(UINT message,
WPARAM w_param,
LPARAM l_param,
LRESULT* result) {
#if BUILDFLAG(ENABLE_WIN_DARK_MODE_WINDOW_UI)
if (message == WM_NCCREATE) {
HWND const hwnd = GetAcceleratedWidget();
auto const theme_source =
ui::NativeTheme::GetInstanceForNativeUi()->theme_source();
win::SetDarkModeForWindow(hwnd, theme_source);
const bool dark_mode_supported = win::IsDarkModeSupported();
if (dark_mode_supported && message == WM_NCCREATE) {
win::SetDarkModeForWindow(GetAcceleratedWidget());
ui::NativeTheme::GetInstanceForNativeUi()->AddObserver(this);
} else if (dark_mode_supported && message == WM_DESTROY) {
ui::NativeTheme::GetInstanceForNativeUi()->RemoveObserver(this);
}
#endif

return native_window_view_->PreHandleMSG(message, w_param, l_param, result);
}
Expand Down Expand Up @@ -99,4 +95,9 @@ bool ElectronDesktopWindowTreeHostWin::GetClientAreaInsets(
return false;
}

void ElectronDesktopWindowTreeHostWin::OnNativeThemeUpdated(
ui::NativeTheme* observed_theme) {
win::SetDarkModeForWindow(GetAcceleratedWidget());
}

} // namespace electron
7 changes: 5 additions & 2 deletions shell/browser/ui/win/electron_desktop_window_tree_host_win.h
Expand Up @@ -12,8 +12,8 @@

namespace electron {

class ElectronDesktopWindowTreeHostWin
: public views::DesktopWindowTreeHostWin {
class ElectronDesktopWindowTreeHostWin : public views::DesktopWindowTreeHostWin,
public ::ui::NativeThemeObserver {
public:
ElectronDesktopWindowTreeHostWin(
NativeWindowViews* native_window_view,
Expand All @@ -37,6 +37,9 @@ class ElectronDesktopWindowTreeHostWin
bool GetClientAreaInsets(gfx::Insets* insets,
HMONITOR monitor) const override;

// ui::NativeThemeObserver:
void OnNativeThemeUpdated(ui::NativeTheme* observed_theme) override;

private:
NativeWindowViews* native_window_view_; // weak ref
};
Expand Down
197 changes: 48 additions & 149 deletions shell/browser/win/dark_mode.cc
@@ -1,178 +1,77 @@
// Copyright (c) 2020 Microsoft Inc. All rights reserved.
// Copyright (c) 2022 Microsoft Inc. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-CHROMIUM file.

#include "shell/browser/win/dark_mode.h"

#include <dwmapi.h> // DwmSetWindowAttribute()

deermichel marked this conversation as resolved.
Show resolved Hide resolved
#include "base/files/file_path.h"
#include "base/scoped_native_library.h"
#include "base/win/pe_image.h"
#include "base/win/win_util.h"
#include "base/win/windows_version.h"
#include "electron/fuses.h"

#define DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 19
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20

// This namespace contains code originally from
// https://github.com/ysc3839/win32-darkmode/
// governed by the MIT license and (c) Richard Yu
// https://github.com/microsoft/terminal
// governed by the MIT license and (c) Microsoft Corporation.
namespace {

// 1903 18362
enum PreferredAppMode { Default, AllowDark, ForceDark, ForceLight, Max };

bool g_darkModeSupported = false;
bool g_darkModeEnabled = false;
DWORD g_buildNumber = 0;

enum WINDOWCOMPOSITIONATTRIB {
WCA_USEDARKMODECOLORS = 26 // build 18875+
};
struct WINDOWCOMPOSITIONATTRIBDATA {
WINDOWCOMPOSITIONATTRIB Attrib;
PVOID pvData;
SIZE_T cbData;
};

using fnSetWindowCompositionAttribute =
BOOL(WINAPI*)(HWND hWnd, WINDOWCOMPOSITIONATTRIBDATA*);
fnSetWindowCompositionAttribute _SetWindowCompositionAttribute = nullptr;

bool IsHighContrast() {
HIGHCONTRASTW highContrast = {sizeof(highContrast)};
if (SystemParametersInfoW(SPI_GETHIGHCONTRAST, sizeof(highContrast),
&highContrast, FALSE))
return highContrast.dwFlags & HCF_HIGHCONTRASTON;
return false;
}

void RefreshTitleBarThemeColor(HWND hWnd, bool dark) {
LONG ldark = dark;
if (g_buildNumber >= 20161) {
// DWMA_USE_IMMERSIVE_DARK_MODE = 20
DwmSetWindowAttribute(hWnd, 20, &ldark, sizeof dark);
return;
}
if (g_buildNumber >= 18363) {
auto data = WINDOWCOMPOSITIONATTRIBDATA{WCA_USEDARKMODECOLORS, &ldark,
sizeof ldark};
_SetWindowCompositionAttribute(hWnd, &data);
return;
}
DwmSetWindowAttribute(hWnd, 0x13, &ldark, sizeof ldark);
}

void InitDarkMode() {
// confirm that we're running on a version of Windows
// where the Dark Mode API is known
auto* os_info = base::win::OSInfo::GetInstance();
g_buildNumber = os_info->version_number().build;
auto const version = os_info->version();
if ((version < base::win::Version::WIN10_RS5) ||
(version > base::win::Version::WIN10_20H1)) {
return;
}

// load "SetWindowCompositionAttribute", used in RefreshTitleBarThemeColor()
_SetWindowCompositionAttribute =
reinterpret_cast<decltype(_SetWindowCompositionAttribute)>(
base::win::GetUser32FunctionPointer("SetWindowCompositionAttribute"));
if (_SetWindowCompositionAttribute == nullptr) {
return;
}

// load the dark mode functions from uxtheme.dll
// * RefreshImmersiveColorPolicyState()
// * ShouldAppsUseDarkMode()
// * AllowDarkModeForApp()
// * SetPreferredAppMode()
// * AllowDarkModeForApp() (build < 18362)
// * SetPreferredAppMode() (build >= 18362)

base::NativeLibrary uxtheme =
base::PinSystemLibrary(FILE_PATH_LITERAL("uxtheme.dll"));
if (!uxtheme) {
return;
}
auto ux_pei = base::win::PEImage(uxtheme);
auto get_ux_proc_from_ordinal = [&ux_pei](int ordinal, auto* setme) {
FARPROC proc = ux_pei.GetProcAddress(reinterpret_cast<LPCSTR>(ordinal));
*setme = reinterpret_cast<decltype(*setme)>(proc);
};

// ordinal 104
using fnRefreshImmersiveColorPolicyState = VOID(WINAPI*)();
fnRefreshImmersiveColorPolicyState _RefreshImmersiveColorPolicyState = {};
get_ux_proc_from_ordinal(104, &_RefreshImmersiveColorPolicyState);

// ordinal 132
using fnShouldAppsUseDarkMode = BOOL(WINAPI*)();
fnShouldAppsUseDarkMode _ShouldAppsUseDarkMode = {};
get_ux_proc_from_ordinal(132, &_ShouldAppsUseDarkMode);

// ordinal 135, in 1809
using fnAllowDarkModeForApp = BOOL(WINAPI*)(BOOL allow);
fnAllowDarkModeForApp _AllowDarkModeForApp = {};

// ordinal 135, in 1903
typedef PreferredAppMode(WINAPI *
fnSetPreferredAppMode)(PreferredAppMode appMode);
fnSetPreferredAppMode _SetPreferredAppMode = {};

if (g_buildNumber < 18362) {
get_ux_proc_from_ordinal(135, &_AllowDarkModeForApp);
} else {
get_ux_proc_from_ordinal(135, &_SetPreferredAppMode);
// Use undocumented flags to set window theme on Windows 10
HRESULT TrySetWindowThemeOnWin10(HWND hWnd, bool dark) {
const BOOL isDarkMode = dark;
if (FAILED(DwmSetWindowAttribute(hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE,
&isDarkMode, sizeof(isDarkMode)))) {
HRESULT result =
DwmSetWindowAttribute(hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1,
&isDarkMode, sizeof(isDarkMode));
if (FAILED(result))
return result;
}

// dark mode is supported iff we found the functions
g_darkModeSupported = _RefreshImmersiveColorPolicyState &&
_ShouldAppsUseDarkMode &&
(_AllowDarkModeForApp || _SetPreferredAppMode);
if (!g_darkModeSupported) {
return;
}
// Toggle the nonclient area active state to force a redraw (Win10 workaround)
HWND activeWindow = GetActiveWindow();
SendMessage(hWnd, WM_NCACTIVATE, hWnd != activeWindow, 0);
SendMessage(hWnd, WM_NCACTIVATE, hWnd == activeWindow, 0);

// initial setup: allow dark mode to be used
if (_AllowDarkModeForApp) {
_AllowDarkModeForApp(true);
} else if (_SetPreferredAppMode) {
_SetPreferredAppMode(AllowDark);
}
_RefreshImmersiveColorPolicyState();
return S_OK;
deermichel marked this conversation as resolved.
Show resolved Hide resolved
}

// check to see if dark mode is currently enabled
g_darkModeEnabled = _ShouldAppsUseDarkMode() && !IsHighContrast();
// https://docs.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
HRESULT TrySetWindowTheme(HWND hWnd, bool dark) {
const BOOL isDarkMode = dark;
return DwmSetWindowAttribute(hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &isDarkMode,
sizeof(isDarkMode));
}

} // namespace

namespace electron {

void EnsureInitialized() {
static bool initialized = false;
if (!initialized) {
initialized = true;
::InitDarkMode();
}
}
namespace win {

bool IsDarkPreferred(ui::NativeTheme::ThemeSource theme_source) {
switch (theme_source) {
case ui::NativeTheme::ThemeSource::kForcedLight:
return false;
case ui::NativeTheme::ThemeSource::kForcedDark:
return g_darkModeSupported;
case ui::NativeTheme::ThemeSource::kSystem:
return g_darkModeEnabled;
}
bool IsDarkModeSupported() {
auto* os_info = base::win::OSInfo::GetInstance();
auto const version = os_info->version();

return version >= base::win::Version::WIN11 ||
(version >= base::win::Version::WIN10 &&
electron::fuses::IsWindows10ImmersiveDarkModeEnabled());
}

namespace win {
void SetDarkModeForWindow(HWND hWnd) {
ui::NativeTheme* theme = ui::NativeTheme::GetInstanceForNativeUi();
bool dark =
theme->ShouldUseDarkColors() && !theme->UserHasContrastPreference();

void SetDarkModeForWindow(HWND hWnd,
ui::NativeTheme::ThemeSource theme_source) {
EnsureInitialized();
RefreshTitleBarThemeColor(hWnd, IsDarkPreferred(theme_source));
auto* os_info = base::win::OSInfo::GetInstance();
auto const version = os_info->version();

if (version >= base::win::Version::WIN11) {
TrySetWindowTheme(hWnd, dark);
} else {
TrySetWindowThemeOnWin10(hWnd, dark);
}
}

} // namespace win
Expand Down
5 changes: 3 additions & 2 deletions shell/browser/win/dark_mode.h
@@ -1,4 +1,4 @@
// Copyright (c) 2020 Microsoft Inc. All rights reserved.
// Copyright (c) 2022 Microsoft Inc. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-CHROMIUM file.

Expand All @@ -19,7 +19,8 @@ namespace electron {

namespace win {

void SetDarkModeForWindow(HWND hWnd, ui::NativeTheme::ThemeSource theme_source);
bool IsDarkModeSupported();
void SetDarkModeForWindow(HWND hWnd);

} // namespace win

Expand Down
7 changes: 4 additions & 3 deletions shell/common/api/features.cc
Expand Up @@ -54,8 +54,8 @@ bool IsPictureInPictureEnabled() {
return BUILDFLAG(ENABLE_PICTURE_IN_PICTURE);
}

bool IsWinDarkModeWindowUiEnabled() {
return BUILDFLAG(ENABLE_WIN_DARK_MODE_WINDOW_UI);
bool IsWindows10ImmersiveDarkModeEnabled() {
return electron::fuses::IsWindows10ImmersiveDarkModeEnabled();
}

bool IsComponentBuild() {
Expand Down Expand Up @@ -84,7 +84,8 @@ void Initialize(v8::Local<v8::Object> exports,
dict.SetMethod("isPictureInPictureEnabled", &IsPictureInPictureEnabled);
dict.SetMethod("isComponentBuild", &IsComponentBuild);
dict.SetMethod("isExtensionsEnabled", &IsExtensionsEnabled);
dict.SetMethod("isWinDarkModeWindowUiEnabled", &IsWinDarkModeWindowUiEnabled);
dict.SetMethod("isWindows10ImmersiveDarkModeEnabled",
&IsWindows10ImmersiveDarkModeEnabled);
}

} // namespace
Expand Down
2 changes: 1 addition & 1 deletion typings/internal-ambient.d.ts
Expand Up @@ -27,7 +27,7 @@ declare namespace NodeJS {
isPictureInPictureEnabled(): boolean;
isExtensionsEnabled(): boolean;
isComponentBuild(): boolean;
isWinDarkModeWindowUiEnabled(): boolean;
isWindows10ImmersiveDarkModeEnabled(): boolean;
}

interface IpcRendererBinding {
Expand Down