feat: support customizing window accent color on Windows (#47537)

* fix: support window accent color in frameless windows

Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>

* refactor: allow customization

Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>

* Update docs/api/structures/base-window-options.md

Co-authored-by: Will Anderson <andersonw@dropbox.com>

Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
This commit is contained in:
trop[bot] 2025-06-25 20:41:42 +02:00 committed by GitHub
parent be53277b54
commit 75cda9e16b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 107 additions and 0 deletions

View file

@ -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

View file

@ -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<v8::Value> 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.

View file

@ -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<bool, SkColor> accent_color_ = true;
std::optional<gfx::Rect> pending_bounds_change_;
// The message ID of the "TaskbarCreated" message, sent to us when we need to

View file

@ -7,6 +7,7 @@
#include <wrl/client.h>
#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<DWORD> 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<std::string> EndSessionToStringVec(LPARAM end_session_id) {
std::vector<std::string> 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<const wchar_t*>(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<bool>(accent_color_)) {
// Don't set accent color if the user has disabled it.
if (!std::get<bool>(accent_color_))
return;
std::optional<DWORD> 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<SkColor>(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

View file

@ -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";