Skip to content

Commit 5c98e36

Browse files
feat: add {get|set}AccentColor on Windows (#47741)
* feat: add setAccentColor on Windows * refactor: unify GetSystemAccentColor * refactor: remove redundant parsing * chore: fixup documentation * Update docs/api/browser-window.md Co-authored-by: Will Anderson <andersonw@dropbox.com> * Update docs/api/base-window.md Co-authored-by: Will Anderson <andersonw@dropbox.com> --------- Co-authored-by: Will Anderson <andersonw@dropbox.com>
1 parent 2cfccac commit 5c98e36

13 files changed

+265
-24
lines changed

docs/api/base-window.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1260,6 +1260,43 @@ Sets the properties for the window's taskbar button.
12601260
> `relaunchCommand` and `relaunchDisplayName` must always be set
12611261
> together. If one of those properties is not set, then neither will be used.
12621262
1263+
#### `win.setAccentColor(accentColor)` _Windows_
1264+
1265+
* `accentColor` boolean | string - The accent color for the window. By default, follows user preference in System Settings.
1266+
1267+
Sets the system accent color and highlighting of active window border.
1268+
1269+
The `accentColor` parameter accepts the following values:
1270+
1271+
* **Color string** - Sets a custom accent color using standard CSS color formats (Hex, RGB, RGBA, HSL, HSLA, or named colors). Alpha values in RGBA/HSLA formats are ignored and the color is treated as fully opaque.
1272+
* **`true`** - Uses the system's default accent color from user preferences in System Settings.
1273+
* **`false`** - Explicitly disables accent color highlighting for the window.
1274+
1275+
Examples:
1276+
1277+
```js
1278+
const win = new BrowserWindow({ frame: false })
1279+
1280+
// Set red accent color.
1281+
win.setAccentColor('#ff0000')
1282+
1283+
// RGB format (alpha ignored if present).
1284+
win.setAccentColor('rgba(255,0,0,0.5)')
1285+
1286+
// Use system accent color.
1287+
win.setAccentColor(true)
1288+
1289+
// Disable accent color.
1290+
win.setAccentColor(false)
1291+
```
1292+
1293+
#### `win.getAccentColor()` _Windows_
1294+
1295+
Returns `string | boolean` - the system accent color and highlighting of active window border in Hex RGB format.
1296+
1297+
If a color has been set for the window that differs from the system accent color, the window accent color will
1298+
be returned. Otherwise, a boolean will be returned, with `true` indicating that the window uses the global system accent color, and `false` indicating that accent color highlighting is disabled for this window.
1299+
12631300
#### `win.setIcon(icon)` _Windows_ _Linux_
12641301

12651302
* `icon` [NativeImage](native-image.md) | string

docs/api/browser-window.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1440,6 +1440,43 @@ Sets the properties for the window's taskbar button.
14401440
> `relaunchCommand` and `relaunchDisplayName` must always be set
14411441
> together. If one of those properties is not set, then neither will be used.
14421442
1443+
#### `win.setAccentColor(accentColor)` _Windows_
1444+
1445+
* `accentColor` boolean | string - The accent color for the window. By default, follows user preference in System Settings.
1446+
1447+
Sets the system accent color and highlighting of active window border.
1448+
1449+
The `accentColor` parameter accepts the following values:
1450+
1451+
* **Color string** - Sets a custom accent color using standard CSS color formats (Hex, RGB, RGBA, HSL, HSLA, or named colors). Alpha values in RGBA/HSLA formats are ignored and the color is treated as fully opaque.
1452+
* **`true`** - Uses the system's default accent color from user preferences in System Settings.
1453+
* **`false`** - Explicitly disables accent color highlighting for the window.
1454+
1455+
Examples:
1456+
1457+
```js
1458+
const win = new BrowserWindow({ frame: false })
1459+
1460+
// Set red accent color.
1461+
win.setAccentColor('#ff0000')
1462+
1463+
// RGB format (alpha ignored if present).
1464+
win.setAccentColor('rgba(255,0,0,0.5)')
1465+
1466+
// Use system accent color.
1467+
win.setAccentColor(true)
1468+
1469+
// Disable accent color.
1470+
win.setAccentColor(false)
1471+
```
1472+
1473+
#### `win.getAccentColor()` _Windows_
1474+
1475+
Returns `string | boolean` - the system accent color and highlighting of active window border in Hex RGB format.
1476+
1477+
If a color has been set for the window that differs from the system accent color, the window accent color will
1478+
be returned. Otherwise, a boolean will be returned, with `true` indicating that the window uses the global system accent color, and `false` indicating that accent color highlighting is disabled for this window.
1479+
14431480
#### `win.showDefinitionForSelection()` _macOS_
14441481

14451482
Same as `webContents.showDefinitionForSelection()`.

shell/browser/api/electron_api_base_window.cc

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,6 +1089,33 @@ void BaseWindow::SetAppDetails(const gin_helper::Dictionary& options) {
10891089
bool BaseWindow::IsSnapped() const {
10901090
return window_->IsSnapped();
10911091
}
1092+
1093+
void BaseWindow::SetAccentColor(gin_helper::Arguments* args) {
1094+
bool accent_color = false;
1095+
std::string accent_color_string;
1096+
if (args->GetNext(&accent_color_string)) {
1097+
std::optional<SkColor> maybe_color = ParseCSSColor(accent_color_string);
1098+
if (maybe_color.has_value()) {
1099+
window_->SetAccentColor(maybe_color.value());
1100+
window_->UpdateWindowAccentColor(window_->IsActive());
1101+
}
1102+
} else if (args->GetNext(&accent_color)) {
1103+
window_->SetAccentColor(accent_color);
1104+
window_->UpdateWindowAccentColor(window_->IsActive());
1105+
} else {
1106+
args->ThrowError(
1107+
"Invalid accent color value - must be a string or boolean");
1108+
}
1109+
}
1110+
1111+
v8::Local<v8::Value> BaseWindow::GetAccentColor() const {
1112+
v8::Isolate* isolate = v8::Isolate::GetCurrent();
1113+
auto accent_color = window_->GetAccentColor();
1114+
1115+
if (std::holds_alternative<bool>(accent_color))
1116+
return v8::Boolean::New(isolate, std::get<bool>(accent_color));
1117+
return gin::StringToV8(isolate, std::get<std::string>(accent_color));
1118+
}
10921119
#endif
10931120

10941121
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX)
@@ -1277,6 +1304,8 @@ void BaseWindow::BuildPrototype(v8::Isolate* isolate,
12771304
#if BUILDFLAG(IS_WIN)
12781305
.SetMethod("isSnapped", &BaseWindow::IsSnapped)
12791306
.SetProperty("snapped", &BaseWindow::IsSnapped)
1307+
.SetMethod("setAccentColor", &BaseWindow::SetAccentColor)
1308+
.SetMethod("getAccentColor", &BaseWindow::GetAccentColor)
12801309
.SetMethod("hookWindowMessage", &BaseWindow::HookWindowMessage)
12811310
.SetMethod("isWindowMessageHooked", &BaseWindow::IsWindowMessageHooked)
12821311
.SetMethod("unhookWindowMessage", &BaseWindow::UnhookWindowMessage)

shell/browser/api/electron_api_base_window.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,8 @@ class BaseWindow : public gin_helper::TrackableObject<BaseWindow>,
255255
bool SetThumbnailToolTip(const std::string& tooltip);
256256
void SetAppDetails(const gin_helper::Dictionary& options);
257257
bool IsSnapped() const;
258+
void SetAccentColor(gin_helper::Arguments* args);
259+
v8::Local<v8::Value> GetAccentColor() const;
258260
#endif
259261

260262
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX)

shell/browser/api/electron_api_system_preferences.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class SystemPreferences final
5656
const char* GetTypeName() override;
5757

5858
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
59-
std::string GetAccentColor();
59+
static std::string GetAccentColor();
6060
std::string GetColor(gin_helper::ErrorThrower thrower,
6161
const std::string& color);
6262
std::string GetMediaAccessStatus(gin_helper::ErrorThrower thrower,

shell/browser/api/electron_api_system_preferences_win.cc

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
#include <iomanip>
66
#include <string_view>
77

8-
#include <dwmapi.h>
98
#include <windows.devices.enumeration.h>
109
#include <wrl/client.h>
1110

@@ -84,14 +83,12 @@ std::string hexColorDWORDToRGBA(DWORD color) {
8483
}
8584

8685
std::string SystemPreferences::GetAccentColor() {
87-
DWORD color = 0;
88-
BOOL opaque = FALSE;
86+
std::optional<DWORD> color = GetSystemAccentColor();
8987

90-
if (FAILED(DwmGetColorizationColor(&color, &opaque))) {
88+
if (!color.has_value())
9189
return "";
92-
}
9390

94-
return hexColorDWORDToRGBA(color);
91+
return hexColorDWORDToRGBA(color.value());
9592
}
9693

9794
std::string SystemPreferences::GetColor(gin_helper::ErrorThrower thrower,

shell/browser/native_window.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,10 @@ class NativeWindow : public base::SupportsUserData,
347347

348348
#if BUILDFLAG(IS_WIN)
349349
void NotifyWindowMessage(UINT message, WPARAM w_param, LPARAM l_param);
350+
virtual void SetAccentColor(
351+
std::variant<std::monostate, bool, SkColor> accent_color) = 0;
352+
virtual std::variant<bool, std::string> GetAccentColor() const = 0;
353+
virtual void UpdateWindowAccentColor(bool active) = 0;
350354
#endif
351355

352356
void AddObserver(NativeWindowObserver* obs) { observers_.AddObserver(obs); }

shell/browser/native_window_views.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "base/strings/utf_string_conversions.h"
2727
#include "content/public/browser/desktop_media_id.h"
2828
#include "content/public/common/color_parser.h"
29+
#include "shell/browser/api/electron_api_system_preferences.h"
2930
#include "shell/browser/api/electron_api_web_contents.h"
3031
#include "shell/browser/ui/inspectable_web_contents_view.h"
3132
#include "shell/browser/ui/views/root_view.h"

shell/browser/native_window_views.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,10 @@ class NativeWindowViews : public NativeWindow,
176176
#endif
177177

178178
#if BUILDFLAG(IS_WIN)
179+
void SetAccentColor(
180+
std::variant<std::monostate, bool, SkColor> accent_color) override;
181+
std::variant<bool, std::string> GetAccentColor() const override;
182+
void UpdateWindowAccentColor(bool active) override;
179183
TaskbarHost& taskbar_host() { return taskbar_host_; }
180184
void UpdateThickFrame();
181185
void SetLayered();
@@ -223,7 +227,6 @@ class NativeWindowViews : public NativeWindow,
223227
void ResetWindowControls();
224228
void SetRoundedCorners(bool rounded);
225229
void SetForwardMouseMessages(bool forward);
226-
void UpdateWindowAccentColor(bool active);
227230
static LRESULT CALLBACK SubclassProc(HWND hwnd,
228231
UINT msg,
229232
WPARAM w_param,

shell/browser/native_window_views_win.cc

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
#include "shell/browser/native_window_views.h"
1717
#include "shell/browser/ui/views/root_view.h"
1818
#include "shell/browser/ui/views/win_frame_view.h"
19+
#include "shell/common/color_util.h"
1920
#include "shell/common/electron_constants.h"
21+
#include "skia/ext/skia_utils_win.h"
2022
#include "ui/display/display.h"
2123
#include "ui/display/screen.h"
2224
#include "ui/gfx/geometry/resize_utils.h"
@@ -46,21 +48,6 @@ void SetWindowBorderAndCaptionColor(HWND hwnd, COLORREF color) {
4648
LOG(WARNING) << "Failed to set border color";
4749
}
4850

49-
std::optional<DWORD> GetAccentColor() {
50-
base::win::RegKey key;
51-
if (key.Open(HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft\\Windows\\DWM",
52-
KEY_READ) != ERROR_SUCCESS) {
53-
return std::nullopt;
54-
}
55-
56-
DWORD accent_color = 0;
57-
if (key.ReadValueDW(L"AccentColor", &accent_color) != ERROR_SUCCESS) {
58-
return std::nullopt;
59-
}
60-
61-
return accent_color;
62-
}
63-
6451
bool IsAccentColorOnTitleBarsEnabled() {
6552
base::win::RegKey key;
6653
if (key.Open(HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft\\Windows\\DWM",
@@ -594,7 +581,7 @@ void NativeWindowViews::UpdateWindowAccentColor(bool active) {
594581

595582
// Use system accent color as fallback if no explicit color was set.
596583
if (!border_color.has_value() && should_apply_accent) {
597-
std::optional<DWORD> system_accent_color = GetAccentColor();
584+
std::optional<DWORD> system_accent_color = GetSystemAccentColor();
598585
if (system_accent_color.has_value()) {
599586
border_color = RGB(GetRValue(system_accent_color.value()),
600587
GetGValue(system_accent_color.value()),
@@ -606,6 +593,39 @@ void NativeWindowViews::UpdateWindowAccentColor(bool active) {
606593
SetWindowBorderAndCaptionColor(GetAcceleratedWidget(), final_color);
607594
}
608595

596+
void NativeWindowViews::SetAccentColor(
597+
std::variant<std::monostate, bool, SkColor> accent_color) {
598+
accent_color_ = accent_color;
599+
}
600+
601+
/*
602+
* Returns the window's accent color, per the following heuristic:
603+
*
604+
* - If |accent_color_| is an SkColor, return that color as a hex string.
605+
* - If |accent_color_| is true, return the system accent color as a hex string.
606+
* - If |accent_color_| is false, return false.
607+
* - Otherwise, return the system accent color as a hex string.
608+
*/
609+
std::variant<bool, std::string> NativeWindowViews::GetAccentColor() const {
610+
std::optional<DWORD> system_color = GetSystemAccentColor();
611+
612+
if (std::holds_alternative<SkColor>(accent_color_)) {
613+
return ToRGBHex(std::get<SkColor>(accent_color_));
614+
} else if (std::holds_alternative<bool>(accent_color_)) {
615+
if (std::get<bool>(accent_color_)) {
616+
if (!system_color.has_value())
617+
return false;
618+
return ToRGBHex(skia::COLORREFToSkColor(system_color.value()));
619+
} else {
620+
return false;
621+
}
622+
} else {
623+
if (!system_color.has_value())
624+
return false;
625+
return ToRGBHex(skia::COLORREFToSkColor(system_color.value()));
626+
}
627+
}
628+
609629
void NativeWindowViews::ResetWindowControls() {
610630
// If a given window was minimized and has since been
611631
// unminimized (restored/maximized), ensure the WCO buttons

0 commit comments

Comments
 (0)