diff --git a/BUILD.gn b/BUILD.gn index 60ab4cac8106c..796f1fb27a090 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -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" ] } diff --git a/buildflags/BUILD.gn b/buildflags/BUILD.gn index 9cbb8227f5f52..a37d752dba360 100644 --- a/buildflags/BUILD.gn +++ b/buildflags/BUILD.gn @@ -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", ] } diff --git a/buildflags/buildflags.gni b/buildflags/buildflags.gni index 5adc739ef7bb1..6a6a7d59fefe3 100644 --- a/buildflags/buildflags.gni +++ b/buildflags/buildflags.gni @@ -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 } diff --git a/filenames.gni b/filenames.gni index f7c6ce5dff438..6b75da667a0c9 100644 --- a/filenames.gni +++ b/filenames.gni @@ -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", 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 696798d9111aa..d978ce64f59a6 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 @@ -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( @@ -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); } @@ -99,4 +95,9 @@ bool ElectronDesktopWindowTreeHostWin::GetClientAreaInsets( return false; } +void ElectronDesktopWindowTreeHostWin::OnNativeThemeUpdated( + ui::NativeTheme* observed_theme) { + win::SetDarkModeForWindow(GetAcceleratedWidget()); +} + } // namespace electron diff --git a/shell/browser/ui/win/electron_desktop_window_tree_host_win.h b/shell/browser/ui/win/electron_desktop_window_tree_host_win.h index 3fd831aae1ed0..1b53f6f8e5313 100644 --- a/shell/browser/ui/win/electron_desktop_window_tree_host_win.h +++ b/shell/browser/ui/win/electron_desktop_window_tree_host_win.h @@ -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, @@ -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 }; diff --git a/shell/browser/win/dark_mode.cc b/shell/browser/win/dark_mode.cc index c85bf18786c20..200ba8c1f13a5 100644 --- a/shell/browser/win/dark_mode.cc +++ b/shell/browser/win/dark_mode.cc @@ -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. @@ -6,173 +6,57 @@ #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 flag works since Win10 20H1 but is not documented until Windows 11 +#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; -} +// https://docs.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +HRESULT TrySetWindowTheme(HWND hWnd, bool dark) { + const BOOL isDarkMode = dark; + HRESULT result = DwmSetWindowAttribute(hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, + &isDarkMode, sizeof(isDarkMode)); -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); -} + if (FAILED(result)) + return result; -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; + // Toggle the nonclient area active state to force a redraw (Win10 workaround) + if (version < base::win::Version::WIN11) { + 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(); - - // check to see if dark mode is currently enabled - g_darkModeEnabled = _ShouldAppsUseDarkMode() && !IsHighContrast(); + return S_OK; } } // 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::WIN10_20H1; } -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)); + TrySetWindowTheme(hWnd, dark); } } // namespace win diff --git a/shell/browser/win/dark_mode.h b/shell/browser/win/dark_mode.h index c6d14f9bfa7e7..12f6adb1dade9 100644 --- a/shell/browser/win/dark_mode.h +++ b/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. @@ -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 diff --git a/shell/common/api/features.cc b/shell/common/api/features.cc index 92208cfba036d..48d733f16a031 100644 --- a/shell/common/api/features.cc +++ b/shell/common/api/features.cc @@ -54,10 +54,6 @@ 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; @@ -84,7 +80,6 @@ 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 0eb2f3d493a61..2ac177b263154 100644 --- a/typings/internal-ambient.d.ts +++ b/typings/internal-ambient.d.ts @@ -27,7 +27,6 @@ declare namespace NodeJS { isPictureInPictureEnabled(): boolean; isExtensionsEnabled(): boolean; isComponentBuild(): boolean; - isWinDarkModeWindowUiEnabled(): boolean; } interface IpcRendererBinding {