Browse Source

fix: `nativeTheme.themeSource = 'dark'` on windows (#26237)

Manually backports #25373.
Charles Kerr 4 years ago
parent
commit
ffd1e9bad2

+ 8 - 0
BUILD.gn

@@ -675,6 +675,14 @@ source_set("electron_lib") {
       "shell/browser/electron_pdf_web_contents_helper_client.h",
     ]
   }
+
+  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") {

+ 1 - 0
buildflags/BUILD.gn

@@ -21,6 +21,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",
   ]
 }

+ 3 - 0
buildflags/buildflags.gni

@@ -36,4 +36,7 @@ declare_args() {
 
   # Enable Spellchecker support
   enable_builtin_spellchecker = true
+
+  # Undocumented Windows dark mode API
+  enable_win_dark_mode_window_ui = false
 }

+ 15 - 0
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);
 }
 

+ 180 - 0
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 <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 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<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;
+  }
+
+  // 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

+ 28 - 0
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 <Windows.h>
+#else
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+#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_

+ 5 - 0
shell/common/api/features.cc

@@ -57,6 +57,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;
@@ -84,6 +88,7 @@ void Initialize(v8::Local<v8::Object> exports,
   dict.SetMethod("isPictureInPictureEnabled", &IsPictureInPictureEnabled);
   dict.SetMethod("isComponentBuild", &IsComponentBuild);
   dict.SetMethod("isExtensionsEnabled", &IsExtensionsEnabled);
+  dict.SetMethod("isWinDarkModeWindowUiEnabled", &IsWinDarkModeWindowUiEnabled);
 }
 
 }  // namespace

+ 1 - 0
typings/internal-ambient.d.ts

@@ -21,6 +21,7 @@ declare namespace NodeJS {
     isPictureInPictureEnabled(): boolean;
     isExtensionsEnabled(): boolean;
     isComponentBuild(): boolean;
+    isWinDarkModeWindowUiEnabled(): boolean;
   }
 
   interface IpcRendererBinding {