Repository: electron/electron (language: cpp)

Issue:
[Feature Request]: Immersive Dark mode
### Preflight Checklist

- [X] I have read the [Contributing Guidelines](https://github.com/electron/electron/blob/main/CONTRIBUTING.md) for this project.
- [X] I agree to follow the [Code of Conduct](https://github.com/electron/electron/blob/main/CODE_OF_CONDUCT.md) that this project adheres to.
- [X] I have searched the [issue tracker](https://www.github.com/electron/electron/issues) for a feature request that matches the one I want to file, without success.

### Problem Description

Developer cannot change caption bar theme. It remains white even if windows theme is set to dark.

### Proposed Solution

When themesource in electron is set to dark, use dwm window attributes or check an example here: [Notepad++ Source code](https://github.com/notepad-plus-plus/notepad-plus-plus/tree/master/PowerEditor/src/DarkMode)

### Alternatives Considered

Check out in Notepad++, Sublime Text, Windows Explorer, MSYS Console...

### Additional Information

_No response_
Windows chrome and context menu should be aware of dark mode setting
<!--  As an open source project with a dedicated but small maintainer team, it can sometimes take a long time for issues to be addressed so please be patient and we will get back to you as soon as we can.
-->

### Preflight Checklist
<!-- Please ensure you've completed the following steps by replacing [ ] with [x]-->

* [x] I have read the [Contributing Guidelines](https://github.com/electron/electron/blob/master/CONTRIBUTING.md) for this project.
* [x] I agree to follow the [Code of Conduct](https://github.com/electron/electron/blob/master/CODE_OF_CONDUCT.md) that this project adheres to.
* [x] I have searched the issue tracker for a feature request that matches the one I want to file, without success.

### Problem Description

Recent builds of Windows 10 support a "dark mode" that changes the chrome and context menus to fit better with the rest of the OS. https://www.digitaltrends.com/computing/how-to-enable-dark-mode-in-windows-10/ has a write up of how to enable this.

Here's a side-by-side of Explorer and the Quick Start app on Windows `10.0.17763.437`:

![](https://user-images.githubusercontent.com/359239/57080976-2b782b00-6cca-11e9-8c41-5097e39526d1.png)

This is complementary to #15316 but not the whole solution:

 - it'd be great if the defaults "just worked" based on this setting, so app developers and users would get this benefit without needing to manage configuration themselves
 - some apps choose to roll their own themes, but they don't have control over the main menu or context menu without also rolling their own implementations of those and hiding the default chrome

To go into more detail, here's what a context menu looks like that respects the dark mode setting:

![](https://user-images.githubusercontent.com/359239/57081074-5ebaba00-6cca-11e9-97f7-d30fc030c759.png)

And here's the equivalent main menu and context menu in the Electron Quick Start when using Electron 5:

![](https://user-images.githubusercontent.com/359239/57081076-5ebaba00-6cca-11e9-926b-9a18be540bef.png)
![](https://user-images.githubusercontent.com/359239/57081077-5ebaba00-6cca-11e9-8f3e-d3b0d61d3e72.png)

### Proposed Solution

I'm not familiar with how this works under the hood, but happy to go spelunking if this is something that interests the maintainers to gather more information.

### Alternatives Considered

As mentioned above, app developers can roll their own dark theme for menus and switch to use a chromeless `BrowserWindow`. That's fine for people who want to invest in that, but if there's lots of interest in the default chrome working better then maybe this is worth diving into?

### Additional Information
<!-- Add any other context about the problem here. -->



Discussion / hints:
@deermichel
cc @MarshallOfSound ☝️ 
I believe we have a hard dependency on Chromium enabling dark mode in these views.  This is happening _right nowish_ in C76/77 based on the work that is still happening in their codebase.

E.g. 
https://chromium-review.googlesource.com/c/chromium/src/+/1588766

Once that lands this should happen automagically
@MarshallOfSound awesome - I'm happy to keep an eye on this as the 6.0 betas evolve in case it makes it in by time that ships.

Feel free to close this out if there's no Electron-specific work to enable this on your side.
This change look like it's worth following: https://chromium-review.googlesource.com/c/chromium/src/+/1597930
Is there any progress on this?

Since this bug was opened Electron 6+ started supporting a dark mode menu bar but the window chrome is still stuck as white.

Electron v9.0.0-beta.3 looks like this:
![image](https://user-images.githubusercontent.com/13466620/75464490-2da92580-597f-11ea-9d8c-6fc7bc1f0e99.png)

C76/77 is mentioned above but v9 is now on C83 is this still a Chromium blocker or an Electron issue?
Is there progress for this white window chrome ?
Electron v10.0.0-beta.1 looks like this:
![image](https://user-images.githubusercontent.com/13466620/82687185-e1ecdc00-9c4e-11ea-88c1-47fc6e1d204e.png)

I guess that rules out C84 fixing it
Is it windows api that dont have dark windows chrome ? But windows explorer can have dark windows chrome
The latest Electron v11.0.0-beta7 (C86) still doesn't have any improvement so I have posted this as a bug on the Chromium tracker: https://bugs.chromium.org/p/chromium/issues/detail?id=1127795

I am no expert... but I *think* what is happening is that Electron is showing the native Windows 10 titlebar (which Chrome never shows anymore as it renders its own "custom-titlebar"). Hence Chromium has never had any need to respect dark mode on the native titlebar.

Luckily there was still an obscure flag for Chrome `disable-windows10-custom-titlebar` so they could replicate the issue natively and has at least got the bug report to `Status: Untriaged`.

Fix patch:
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 <dwmapi.h>  // 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<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);
-  }
 
-  // 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<v8::Object> 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 {


Test patch: