diff --git a/electron/assets/icons/indicator/running-d.png b/electron/assets/icons/indicator/running-d.png index 8896288f2c..6a9d2e5a31 100644 Binary files a/electron/assets/icons/indicator/running-d.png and b/electron/assets/icons/indicator/running-d.png differ diff --git a/electron/assets/icons/indicator/running-d@2x.png b/electron/assets/icons/indicator/running-d@2x.png index 122d71a124..cc8ff4c445 100644 Binary files a/electron/assets/icons/indicator/running-d@2x.png and b/electron/assets/icons/indicator/running-d@2x.png differ diff --git a/electron/assets/icons/indicator/running-l.png b/electron/assets/icons/indicator/running-l.png index 145dfde887..7d1f6ed647 100644 Binary files a/electron/assets/icons/indicator/running-l.png and b/electron/assets/icons/indicator/running-l.png differ diff --git a/electron/assets/icons/indicator/running-l@2x.png b/electron/assets/icons/indicator/running-l@2x.png index a800f16b7a..57466a439f 100644 Binary files a/electron/assets/icons/indicator/running-l@2x.png and b/electron/assets/icons/indicator/running-l@2x.png differ diff --git a/electron/assets/icons/indicator/stopped-d.png b/electron/assets/icons/indicator/stopped-d.png index a339ac8865..623ed6d764 100644 Binary files a/electron/assets/icons/indicator/stopped-d.png and b/electron/assets/icons/indicator/stopped-d.png differ diff --git a/electron/assets/icons/indicator/stopped-d@2x.png b/electron/assets/icons/indicator/stopped-d@2x.png index b4063b1d36..6b5175aa8d 100644 Binary files a/electron/assets/icons/indicator/stopped-d@2x.png and b/electron/assets/icons/indicator/stopped-d@2x.png differ diff --git a/electron/assets/icons/indicator/stopped-l.png b/electron/assets/icons/indicator/stopped-l.png index 69c0252043..07c8d3465e 100644 Binary files a/electron/assets/icons/indicator/stopped-l.png and b/electron/assets/icons/indicator/stopped-l.png differ diff --git a/electron/assets/icons/indicator/stopped-l@2x.png b/electron/assets/icons/indicator/stopped-l@2x.png index d2ba3f4ea1..71d81ab017 100644 Binary files a/electron/assets/icons/indicator/stopped-l@2x.png and b/electron/assets/icons/indicator/stopped-l@2x.png differ diff --git a/electron/indicator.test.cjs b/electron/indicator.test.cjs index 2369dada9c..46e91ac8e1 100644 --- a/electron/indicator.test.cjs +++ b/electron/indicator.test.cjs @@ -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(); diff --git a/electron/indicator.ts b/electron/indicator.ts index 9ac2ce0016..dacef9b150 100644 --- a/electron/indicator.ts +++ b/electron/indicator.ts @@ -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`; }