diff --git a/BUILD.gn b/BUILD.gn index b1b82ac740e8..2e5fbb6b004f 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -673,6 +673,14 @@ 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" ] + } } electron_paks("packed_resources") { diff --git a/buildflags/BUILD.gn b/buildflags/BUILD.gn index 29d4c7435045..9bd2aba0036a 100644 --- a/buildflags/BUILD.gn +++ b/buildflags/BUILD.gn @@ -20,6 +20,7 @@ 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", ] } diff --git a/buildflags/buildflags.gni b/buildflags/buildflags.gni index 26f6117b0e0c..95de99fe0a33 100644 --- a/buildflags/buildflags.gni +++ b/buildflags/buildflags.gni @@ -33,4 +33,7 @@ declare_args() { # Enable Spellchecker support enable_builtin_spellchecker = true + + # Undocumented Windows dark mode API + enable_win_dark_mode_window_ui = false } diff --git a/shell/browser/ui/win/electron_desktop_window_tree_host_win.cc b/shell/browser/ui/win/electron_desktop_window_tree_host_win.cc index 7718fd03d095..852de00a52b4 100644 --- a/shell/browser/ui/win/electron_desktop_window_tree_host_win.cc +++ b/shell/browser/ui/win/electron_desktop_window_tree_host_win.cc @@ -5,9 +5,15 @@ #include "shell/browser/ui/win/electron_desktop_window_tree_host_win.h" #include "base/win/windows_version.h" +#include "electron/buildflags/buildflags.h" #include "shell/browser/ui/views/win_frame_view.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( @@ -23,6 +29,15 @@ 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); + } +#endif + return native_window_view_->PreHandleMSG(message, w_param, l_param, result); } diff --git a/shell/browser/win/dark_mode.cc b/shell/browser/win/dark_mode.cc new file mode 100644 index 000000000000..c85bf18786c2 --- /dev/null +++ b/shell/browser/win/dark_mode.cc @@ -0,0 +1,180 @@ +// Copyright (c) 2020 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 // DwmSetWindowAttribute() + +#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" + +// This namespace contains code originally from +// https://github.com/ysc3839/win32-darkmode/ +// governed by the MIT license and (c) Richard Yu +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( + 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(ordinal)); + *setme = reinterpret_cast(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); + } + + // dark mode is supported iff we found the functions + g_darkModeSupported = _RefreshImmersiveColorPolicyState && + _ShouldAppsUseDarkMode && + (_AllowDarkModeForApp || _SetPreferredAppMode); + if (!g_darkModeSupported) { + return; + } + + // initial setup: allow dark mode to be used + if (_AllowDarkModeForApp) { + _AllowDarkModeForApp(true); + } else if (_SetPreferredAppMode) { + _SetPreferredAppMode(AllowDark); + } + _RefreshImmersiveColorPolicyState(); + + // check to see if dark mode is currently enabled + g_darkModeEnabled = _ShouldAppsUseDarkMode() && !IsHighContrast(); +} + +} // namespace + +namespace electron { + +void EnsureInitialized() { + static bool initialized = false; + if (!initialized) { + initialized = true; + ::InitDarkMode(); + } +} + +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; + } +} + +namespace win { + +void SetDarkModeForWindow(HWND hWnd, + ui::NativeTheme::ThemeSource theme_source) { + EnsureInitialized(); + RefreshTitleBarThemeColor(hWnd, IsDarkPreferred(theme_source)); +} + +} // namespace win + +} // namespace electron diff --git a/shell/browser/win/dark_mode.h b/shell/browser/win/dark_mode.h new file mode 100644 index 000000000000..9e83d54ed7e7 --- /dev/null +++ b/shell/browser/win/dark_mode.h @@ -0,0 +1,28 @@ +// Copyright (c) 2020 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. + +#ifndef SHELL_BROWSER_WIN_DARK_MODE_H_ +#define SHELL_BROWSER_WIN_DARK_MODE_H_ + +#ifdef WIN32_LEAN_AND_MEAN +#include +#else +#define WIN32_LEAN_AND_MEAN +#include +#undef WIN32_LEAN_AND_MEAN +#endif + +#include "ui/native_theme/native_theme.h" + +namespace electron { + +namespace win { + +void SetDarkModeForWindow(HWND hWnd, ui::NativeTheme::ThemeSource theme_source); + +} // namespace win + +} // namespace electron + +#endif // SHELL_BROWSER_WIN_DARK_MODE_H_ diff --git a/shell/common/api/features.cc b/shell/common/api/features.cc index d040ef3a232e..a53573f7c8f5 100644 --- a/shell/common/api/features.cc +++ b/shell/common/api/features.cc @@ -58,6 +58,10 @@ bool IsPictureInPictureEnabled() { return BUILDFLAG(ENABLE_PICTURE_IN_PICTURE); } +bool IsWinDarkModeWindowUiEnabled() { + return BUILDFLAG(ENABLE_WIN_DARK_MODE_WINDOW_UI); +} + bool IsComponentBuild() { #if defined(COMPONENT_BUILD) return true; @@ -85,6 +89,7 @@ void Initialize(v8::Local exports, dict.SetMethod("isPictureInPictureEnabled", &IsPictureInPictureEnabled); dict.SetMethod("isComponentBuild", &IsComponentBuild); dict.SetMethod("isExtensionsEnabled", &IsExtensionsEnabled); + dict.SetMethod("isWinDarkModeWindowUiEnabled", &IsWinDarkModeWindowUiEnabled); } } // namespace diff --git a/typings/internal-ambient.d.ts b/typings/internal-ambient.d.ts index ed479f0771e7..11461c22f7bd 100644 --- a/typings/internal-ambient.d.ts +++ b/typings/internal-ambient.d.ts @@ -24,6 +24,7 @@ declare namespace NodeJS { isPictureInPictureEnabled(): boolean; isExtensionsEnabled(): boolean; isComponentBuild(): boolean; + isWinDarkModeWindowUiEnabled(): boolean; } interface IpcRendererBinding {