Skip to content

Commit 9daba33

Browse files
committed
feat: add setAccentColor on Windows
1 parent 4a89068 commit 9daba33

10 files changed

+180
-3
lines changed

docs/api/base-window.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1260,6 +1260,19 @@ 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. 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.
1266+
1267+
Sets the system accent color and highlighting of active window border.
1268+
1269+
#### `win.getAccentColor()` _Windows_
1270+
1271+
Returns `string | null` - the system accent color and highlighting of active window border in RGB format.
1272+
1273+
If a color has been set for the window that differs from the system accent color, the window accent color will
1274+
be returned. Otherwise, the system accent color will be returned, if one is enabled.
1275+
12631276
#### `win.setIcon(icon)` _Windows_ _Linux_
12641277

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

docs/api/browser-window.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1440,6 +1440,19 @@ 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. 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.
1446+
1447+
Sets the system accent color and highlighting of active window border.
1448+
1449+
#### `win.getAccentColor()` _Windows_
1450+
1451+
Returns `string | null` - the system accent color and highlighting of active window border in RGB format.
1452+
1453+
If a color has been set for the window that differs from the system accent color, the window accent color will
1454+
be returned. Otherwise, the system accent color will be returned, if one is enabled.
1455+
14431456
#### `win.showDefinitionForSelection()` _macOS_
14441457

14451458
Same as `webContents.showDefinitionForSelection()`.

shell/browser/api/electron_api_base_window.cc

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,6 +1089,32 @@ 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+
window_->SetAccentColor(accent_color_string);
1098+
} else if (args->GetNext(&accent_color)) {
1099+
window_->SetAccentColor(accent_color);
1100+
} else {
1101+
args->ThrowError(
1102+
"Invalid accent color value - must be a string or boolean");
1103+
}
1104+
}
1105+
1106+
v8::Local<v8::Value> BaseWindow::GetAccentColor() const {
1107+
v8::Isolate* isolate = v8::Isolate::GetCurrent();
1108+
auto accent_color = window_->GetAccentColor();
1109+
1110+
if (std::holds_alternative<bool>(accent_color)) {
1111+
return v8::Boolean::New(isolate, std::get<bool>(accent_color));
1112+
} else if (std::holds_alternative<std::string>(accent_color)) {
1113+
return gin::StringToV8(isolate, std::get<std::string>(accent_color));
1114+
}
1115+
1116+
return v8::Null(isolate);
1117+
}
10921118
#endif
10931119

10941120
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX)
@@ -1277,6 +1303,8 @@ void BaseWindow::BuildPrototype(v8::Isolate* isolate,
12771303
#if BUILDFLAG(IS_WIN)
12781304
.SetMethod("isSnapped", &BaseWindow::IsSnapped)
12791305
.SetProperty("snapped", &BaseWindow::IsSnapped)
1306+
.SetMethod("setAccentColor", &BaseWindow::SetAccentColor)
1307+
.SetMethod("getAccentColor", &BaseWindow::GetAccentColor)
12801308
.SetMethod("hookWindowMessage", &BaseWindow::HookWindowMessage)
12811309
.SetMethod("isWindowMessageHooked", &BaseWindow::IsWindowMessageHooked)
12821310
.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/native_window.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,8 @@ class NativeWindow : public base::SupportsUserData,
343343

344344
#if BUILDFLAG(IS_WIN)
345345
void NotifyWindowMessage(UINT message, WPARAM w_param, LPARAM l_param);
346+
virtual void SetAccentColor(std::variant<bool, std::string> accent_color) = 0;
347+
virtual std::variant<bool, std::string> GetAccentColor() const = 0;
346348
#endif
347349

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

shell/browser/native_window_views.cc

Lines changed: 31 additions & 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"
@@ -1705,6 +1706,36 @@ void NativeWindowViews::SetIcon(const gfx::ImageSkia& icon) {
17051706
#endif
17061707

17071708
#if BUILDFLAG(IS_WIN)
1709+
void NativeWindowViews::SetAccentColor(
1710+
std::variant<bool, std::string> accent_color) {
1711+
if (std::holds_alternative<std::string>(accent_color)) {
1712+
std::optional<SkColor> maybe_color =
1713+
ParseCSSColor(std::get<std::string>(accent_color));
1714+
if (maybe_color.has_value())
1715+
accent_color_ = maybe_color.value();
1716+
} else if (std::holds_alternative<bool>(accent_color)) {
1717+
accent_color_ = std::get<bool>(accent_color);
1718+
}
1719+
1720+
UpdateWindowAccentColor();
1721+
}
1722+
1723+
std::variant<bool, std::string> NativeWindowViews::GetAccentColor() const {
1724+
if (std::holds_alternative<SkColor>(accent_color_)) {
1725+
return ToRGBHex(std::get<SkColor>(accent_color_));
1726+
} else if (std::holds_alternative<bool>(accent_color_)) {
1727+
return std::get<bool>(accent_color_);
1728+
}
1729+
1730+
std::string accent_color = electron::api::SystemPreferences::GetAccentColor();
1731+
if (accent_color.empty()) {
1732+
return false;
1733+
} else {
1734+
// Return color in RGB hex format.
1735+
return accent_color.substr(0, accent_color.length() - 2);
1736+
}
1737+
}
1738+
17081739
void NativeWindowViews::UpdateThickFrame() {
17091740
if (!thick_frame_)
17101741
return;

shell/browser/native_window_views.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,8 @@ class NativeWindowViews : public NativeWindow,
176176
#endif
177177

178178
#if BUILDFLAG(IS_WIN)
179+
void SetAccentColor(std::variant<bool, std::string> accent_color) override;
180+
std::variant<bool, std::string> GetAccentColor() const override;
179181
TaskbarHost& taskbar_host() { return taskbar_host_; }
180182
void UpdateThickFrame();
181183
#endif

shell/browser/native_window_views_win.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ void SetWindowBorderAndCaptionColor(HWND hwnd, COLORREF color) {
4646
LOG(WARNING) << "Failed to set border color";
4747
}
4848

49-
std::optional<DWORD> GetAccentColor() {
49+
std::optional<DWORD> GetSystemAccentColor() {
5050
base::win::RegKey key;
5151
if (key.Open(HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft\\Windows\\DWM",
5252
KEY_READ) != ERROR_SUCCESS) {
@@ -594,7 +594,7 @@ void NativeWindowViews::UpdateWindowAccentColor() {
594594

595595
// Use system accent color as fallback if no explicit color was set.
596596
if (!border_color.has_value() && should_apply_accent) {
597-
std::optional<DWORD> system_accent_color = GetAccentColor();
597+
std::optional<DWORD> system_accent_color = GetSystemAccentColor();
598598
if (system_accent_color.has_value()) {
599599
border_color = RGB(GetRValue(system_accent_color.value()),
600600
GetGValue(system_accent_color.value()),

spec/api-browser-window-spec.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2555,6 +2555,92 @@ describe('BrowserWindow module', () => {
25552555
});
25562556
});
25572557

2558+
ifdescribe(process.platform === 'win32')('BrowserWindow.{get|set}AccentColor', () => {
2559+
afterEach(closeAllWindows);
2560+
2561+
it('throws if called with an invalid parameter', () => {
2562+
const w = new BrowserWindow({ show: false });
2563+
expect(() => {
2564+
// @ts-ignore this is wrong on purpose.
2565+
w.setAccentColor([1, 2, 3]);
2566+
}).to.throw('Invalid accent color value - must be a string or boolean');
2567+
});
2568+
2569+
it('returns the accent color after setting it to a string', () => {
2570+
const w = new BrowserWindow({ show: false });
2571+
const testColor = '#FF0000';
2572+
w.setAccentColor(testColor);
2573+
const accentColor = w.getAccentColor();
2574+
expect(accentColor).to.be.a('string');
2575+
expect(accentColor).to.equal(testColor);
2576+
});
2577+
2578+
it('returns the accent color after setting it to false', () => {
2579+
const w = new BrowserWindow({ show: false });
2580+
w.setAccentColor(false);
2581+
const accentColor = w.getAccentColor();
2582+
expect(accentColor).to.be.a('boolean');
2583+
expect(accentColor).to.equal(false);
2584+
});
2585+
2586+
it('returns a system color when set to true', () => {
2587+
const w = new BrowserWindow({ show: false });
2588+
w.setAccentColor(true);
2589+
const accentColor = w.getAccentColor();
2590+
expect(accentColor).to.be.a('string');
2591+
expect(accentColor).to.match(/^#[0-9A-F]{6}$/i);
2592+
});
2593+
2594+
it('returns the correct accent color after multiple changes', () => {
2595+
const w = new BrowserWindow({ show: false });
2596+
2597+
const testColor1 = '#00FF00';
2598+
w.setAccentColor(testColor1);
2599+
expect(w.getAccentColor()).to.equal(testColor1);
2600+
2601+
w.setAccentColor(false);
2602+
expect(w.getAccentColor()).to.equal(false);
2603+
2604+
const testColor2 = '#0000FF';
2605+
w.setAccentColor(testColor2);
2606+
expect(w.getAccentColor()).to.equal(testColor2);
2607+
2608+
w.setAccentColor(true);
2609+
const systemColor = w.getAccentColor();
2610+
expect(systemColor).to.be.a('string');
2611+
expect(systemColor).to.match(/^#[0-9A-F]{6}$/i);
2612+
});
2613+
2614+
it('handles CSS color names correctly', () => {
2615+
const w = new BrowserWindow({ show: false });
2616+
const testColor = 'red';
2617+
w.setAccentColor(testColor);
2618+
const accentColor = w.getAccentColor();
2619+
expect(accentColor).to.be.a('string');
2620+
expect(accentColor).to.equal('#FF0000');
2621+
});
2622+
2623+
it('handles RGB color values correctly', () => {
2624+
const w = new BrowserWindow({ show: false });
2625+
const testColor = 'rgb(255, 128, 0)';
2626+
w.setAccentColor(testColor);
2627+
const accentColor = w.getAccentColor();
2628+
expect(accentColor).to.be.a('string');
2629+
expect(accentColor).to.equal('#FF8000');
2630+
});
2631+
2632+
it('persists accent color across window operations', () => {
2633+
const w = new BrowserWindow({ show: false });
2634+
const testColor = '#ABCDEF';
2635+
w.setAccentColor(testColor);
2636+
2637+
w.show();
2638+
w.hide();
2639+
2640+
expect(w.getAccentColor()).to.equal(testColor);
2641+
});
2642+
});
2643+
25582644
describe('BrowserWindow.setAlwaysOnTop(flag, level)', () => {
25592645
let w: BrowserWindow;
25602646

0 commit comments

Comments
 (0)