From 75cda9e16bcd0a7271385eb5a971cd9f760f15d4 Mon Sep 17 00:00:00 2001 From: "trop[bot]" <37223003+trop[bot]@users.noreply.github.com> Date: Wed, 25 Jun 2025 20:41:42 +0200 Subject: [PATCH] feat: support customizing window accent color on Windows (#47537) * fix: support window accent color in frameless windows Co-authored-by: Shelley Vohr * refactor: allow customization Co-authored-by: Shelley Vohr * Update docs/api/structures/base-window-options.md Co-authored-by: Will Anderson Co-authored-by: Shelley Vohr --------- Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com> Co-authored-by: Shelley Vohr --- docs/api/structures/base-window-options.md | 1 + shell/browser/native_window_views.cc | 11 +++ shell/browser/native_window_views.h | 3 + shell/browser/native_window_views_win.cc | 90 ++++++++++++++++++++++ shell/common/options_switches.h | 2 + 5 files changed, 107 insertions(+) diff --git a/docs/api/structures/base-window-options.md b/docs/api/structures/base-window-options.md index 0b451c7733a0..375c6adc3b59 100644 --- a/docs/api/structures/base-window-options.md +++ b/docs/api/structures/base-window-options.md @@ -95,6 +95,7 @@ * `color` String (optional) _Windows_ _Linux_ - The CSS color of the Window Controls Overlay when enabled. Default is the system color. * `symbolColor` String (optional) _Windows_ _Linux_ - The CSS color of the symbols on the Window Controls Overlay when enabled. Default is the system color. * `height` Integer (optional) - The height of the title bar and Window Controls Overlay in pixels. Default is system height. +* `accentColor` boolean | string (optional) _Windows_ - The accent color for the window. By default, follows user preference in System Settings. Set to `false` to explicitly disable, or set the color in Hex, RGB, RGBA, HSL, HSLA or named CSS color format. Alpha values will be ignored. * `trafficLightPosition` [Point](point.md) (optional) _macOS_ - Set a custom position for the traffic light buttons in frameless windows. * `roundedCorners` boolean (optional) _macOS_ _Windows_ - Whether frameless window diff --git a/shell/browser/native_window_views.cc b/shell/browser/native_window_views.cc index 77250ef9e727..fff2c9dd9a88 100644 --- a/shell/browser/native_window_views.cc +++ b/shell/browser/native_window_views.cc @@ -79,6 +79,7 @@ #include "base/win/windows_version.h" #include "shell/browser/ui/views/win_frame_view.h" #include "shell/browser/ui/win/electron_desktop_native_widget_aura.h" +#include "shell/common/color_util.h" #include "skia/ext/skia_utils_win.h" #include "ui/display/win/screen_win.h" #include "ui/gfx/color_utils.h" @@ -214,6 +215,14 @@ NativeWindowViews::NativeWindowViews(const gin_helper::Dictionary& options, overlay_button_color_ = color_utils::GetSysSkColor(COLOR_BTNFACE); overlay_symbol_color_ = color_utils::GetSysSkColor(COLOR_BTNTEXT); + + bool accent_color = true; + std::string accent_color_string; + if (options.Get(options::kAccentColor, &accent_color_string)) { + accent_color_ = ParseCSSColor(accent_color_string); + } else if (options.Get(options::kAccentColor, &accent_color)) { + accent_color_ = accent_color; + } #endif v8::Local titlebar_overlay; @@ -430,6 +439,8 @@ NativeWindowViews::NativeWindowViews(const gin_helper::Dictionary& options, last_window_state_ = ui::mojom::WindowShowState::kFullscreen; else last_window_state_ = ui::mojom::WindowShowState::kNormal; + + UpdateWindowAccentColor(); #endif // Listen to mouse events. diff --git a/shell/browser/native_window_views.h b/shell/browser/native_window_views.h index b65221bcdba2..5554b8676e16 100644 --- a/shell/browser/native_window_views.h +++ b/shell/browser/native_window_views.h @@ -209,6 +209,7 @@ class NativeWindowViews : public NativeWindow, void ResetWindowControls(); void SetRoundedCorners(bool rounded); void SetForwardMouseMessages(bool forward); + void UpdateWindowAccentColor(); static LRESULT CALLBACK SubclassProc(HWND hwnd, UINT msg, WPARAM w_param, @@ -305,6 +306,8 @@ class NativeWindowViews : public NativeWindow, // Whether the window is currently being moved. bool is_moving_ = false; + std::variant accent_color_ = true; + std::optional pending_bounds_change_; // The message ID of the "TaskbarCreated" message, sent to us when we need to diff --git a/shell/browser/native_window_views_win.cc b/shell/browser/native_window_views_win.cc index 7202464f608d..8a2876d4f69b 100644 --- a/shell/browser/native_window_views_win.cc +++ b/shell/browser/native_window_views_win.cc @@ -7,6 +7,7 @@ #include #include "base/win/atl.h" // Must be before UIAutomationCore.h +#include "base/win/registry.h" #include "base/win/scoped_handle.h" #include "base/win/windows_version.h" #include "content/public/browser/browser_accessibility_state.h" @@ -28,6 +29,53 @@ namespace electron { namespace { +void SetWindowBorderAndCaptionColor(HWND hwnd, COLORREF color) { + if (base::win::GetVersion() < base::win::Version::WIN11) + return; + + HRESULT result = + DwmSetWindowAttribute(hwnd, DWMWA_CAPTION_COLOR, &color, sizeof(color)); + + if (FAILED(result)) + LOG(WARNING) << "Failed to set caption color"; + + result = + DwmSetWindowAttribute(hwnd, DWMWA_BORDER_COLOR, &color, sizeof(color)); + + if (FAILED(result)) + LOG(WARNING) << "Failed to set border color"; +} + +std::optional GetAccentColor() { + base::win::RegKey key; + if (key.Open(HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft\\Windows\\DWM", + KEY_READ) != ERROR_SUCCESS) { + return std::nullopt; + } + + DWORD accent_color = 0; + if (key.ReadValueDW(L"AccentColor", &accent_color) != ERROR_SUCCESS) { + return std::nullopt; + } + + return accent_color; +} + +bool IsAccentColorOnTitleBarsEnabled() { + base::win::RegKey key; + if (key.Open(HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft\\Windows\\DWM", + KEY_READ) != ERROR_SUCCESS) { + return false; + } + + DWORD enabled = 0; + if (key.ReadValueDW(L"ColorPrevalence", &enabled) != ERROR_SUCCESS) { + return false; + } + + return enabled != 0; +} + // Convert Win32 WM_QUERYENDSESSIONS to strings. const std::vector EndSessionToStringVec(LPARAM end_session_id) { std::vector params; @@ -450,6 +498,19 @@ bool NativeWindowViews::PreHandleMSG(UINT message, } return false; } + case WM_DWMCOLORIZATIONCOLORCHANGED: { + UpdateWindowAccentColor(); + return false; + } + case WM_SETTINGCHANGE: { + if (l_param) { + const wchar_t* setting_name = reinterpret_cast(l_param); + std::wstring setting_str(setting_name); + if (setting_str == L"ImmersiveColorSet") + UpdateWindowAccentColor(); + } + return false; + } default: { return false; } @@ -509,6 +570,35 @@ void NativeWindowViews::HandleSizeEvent(WPARAM w_param, LPARAM l_param) { } } +void NativeWindowViews::UpdateWindowAccentColor() { + if (base::win::GetVersion() < base::win::Version::WIN11) + return; + + if (!IsAccentColorOnTitleBarsEnabled()) + return; + + COLORREF border_color; + if (std::holds_alternative(accent_color_)) { + // Don't set accent color if the user has disabled it. + if (!std::get(accent_color_)) + return; + + std::optional accent_color = GetAccentColor(); + if (!accent_color.has_value()) + return; + + border_color = + RGB(GetRValue(accent_color.value()), GetGValue(accent_color.value()), + GetBValue(accent_color.value())); + } else { + SkColor color = std::get(accent_color_); + border_color = + RGB(SkColorGetR(color), SkColorGetG(color), SkColorGetB(color)); + } + + SetWindowBorderAndCaptionColor(GetAcceleratedWidget(), border_color); +} + void NativeWindowViews::ResetWindowControls() { // If a given window was minimized and has since been // unminimized (restored/maximized), ensure the WCO buttons diff --git a/shell/common/options_switches.h b/shell/common/options_switches.h index 7b86b734e5a2..1e2aad82727f 100644 --- a/shell/common/options_switches.h +++ b/shell/common/options_switches.h @@ -123,6 +123,8 @@ inline constexpr std::string_view kRoundedCorners = "roundedCorners"; inline constexpr std::string_view ktitleBarOverlay = "titleBarOverlay"; +inline constexpr std::string_view kAccentColor = "accentColor"; + // The color to use as the theme and symbol colors respectively for Window // Controls Overlay if enabled on Windows. inline constexpr std::string_view kOverlayButtonColor = "color";