Skip to content

Commit 41e722c

Browse files
committed
feat: add setAccentColor on Windows
1 parent 4a89068 commit 41e722c

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: 29 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,34 @@ 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 ToRGBAHex(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 accent_color;
1735+
}
1736+
17081737
void NativeWindowViews::UpdateThickFrame() {
17091738
if (!thick_frame_)
17101739
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: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2555,6 +2555,94 @@ describe('BrowserWindow module', () => {
25552555
});
25562556
});
25572557

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

0 commit comments

Comments
 (0)