fix(electron): crisp, flicker-free Linux tray icon (#4905) (#8484)

Linux StatusNotifierItem hosts redraw the whole tray item on every
Tray.setImage(), so cycling the 16 progress-animation frames made the
icon flicker between a full and a partial ring. Show a single static
running icon on Linux; Windows/macOS keep the animation.

Also re-render the static stopped/running icons (light+dark, @2x) from
vector sources at 24/48px so GNOME no longer upscales a 16px source.
This commit is contained in:
Johannes Millan 2026-06-19 12:43:00 +02:00 committed by GitHub
parent b2a68eddee
commit f7278a43ec
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 42 additions and 2 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 253 B

After

Width:  |  Height:  |  Size: 635 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 458 B

After

Width:  |  Height:  |  Size: 1.2 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 240 B

After

Width:  |  Height:  |  Size: 614 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 441 B

After

Width:  |  Height:  |  Size: 1.2 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 307 B

After

Width:  |  Height:  |  Size: 650 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 564 B

After

Width:  |  Height:  |  Size: 1.3 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 276 B

After

Width:  |  Height:  |  Size: 627 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 530 B

After

Width:  |  Height:  |  Size: 1.3 KiB

Before After
Before After

View file

@ -251,15 +251,50 @@ test('initIndicator uses NativeImage for Linux tray creation and updates', () =>
undefined,
);
// On Linux the running icon stays static (no progress-animation frames) to
// avoid StatusNotifierItem flicker (#4905).
assert.equal(traySetImageCalls.at(-1).kind, 'native-image');
assert.match(
traySetImageCalls.at(-1).iconPath,
/\/icons\/indicator\/running-anim-d\/3\.png$/,
/\/icons\/indicator\/running-d\.png$/,
);
beforeQuitHandler();
});
test('non-Linux platforms keep the running progress animation', () => {
Object.defineProperty(process, 'platform', {
configurable: true,
value: 'darwin',
});
const { initIndicator } = loadIndicatorModule();
initIndicator({
showApp: () => {},
quitApp: () => {},
ICONS_FOLDER: '/icons/',
forceDarkTray: false,
app: { on: () => {} },
});
const currentTaskUpdated = ipcHandlers.get('CURRENT_TASK_UPDATED');
currentTaskUpdated(
{},
{ id: 'T1', title: 'Task', timeSpent: 5 * 60000, timeEstimate: 25 * 60000 },
false,
0,
false,
0,
undefined,
);
// macOS uses the black (`-l`) template icon and still animates progress.
assert.match(
traySetImageCalls.at(-1).iconPath,
/\/icons\/indicator\/running-anim-l\/3\.png$/,
);
});
test('initIndicator falls back to icon path if NativeImage creation is empty', () => {
nextNativeImageIsEmpty = true;
const { initIndicator } = loadIndicatorModule();

View file

@ -559,7 +559,12 @@ function createContextMenu(msg?: string): Menu {
// eslint-disable-next-line prefer-arrow/prefer-arrow-functions
function getRunningIconPath(progress?: number): string {
const suf = shouldUseDarkColors ? '-d' : '-l';
if (typeof progress === 'number' && progress > 0 && isFinite(progress)) {
// Linux StatusNotifierItem hosts redraw the whole tray item on every
// Tray.setImage(); cycling through the 16 progress-animation frames makes the
// icon visibly flicker between a full and a partial ring (#4905). Show a single
// static running icon there instead — elapsed/remaining time still shows in the
// tray context-menu label (and the title/OS taskbar where the host supports it).
if (!IS_LINUX && typeof progress === 'number' && progress > 0 && isFinite(progress)) {
const f = Math.min(Math.round(progress * 15), 15);
return DIR + `running-anim${suf}/${f || 0}.png`;
}