fix(tray): Linux tray icon duplicates and dead menus on minimize-to-tray toggle - #5086
Conversation
Toggling "Minimize to tray" off then on repeatedly left a dead icon in the system tray every time (waybar, KDE Plasma); each opened a menu but only the newest one worked, and the pile only cleared when Folo fully quit. setTrayConfig(false) calls tray.destroy(), which on a StatusNotifierItem host does not remove the icon: Chromium's StatusIconLinuxDbus registers the item by object path on the process-wide shared session-bus connection and, on teardown, un-exports its own objects but never tells org.kde.StatusNotifierWatcher the item is gone. The connection name outlives the tray, so the host gets no NameOwnerChanged and keeps a dead icon. Re-enabling then calls new Tray() again and Chromium registers a fresh /org/chromium/StatusNotifierItem/<n+1>. - Linux: keep the single Tray instance for the app's lifetime instead of destroying/recreating it on the toggle. registerAppTray() already guards new Tray() behind `if (tray)`, and the close handler reads getTrayConfig() live, so a tray icon that outlives a disabled setting is inert. - The icon can't be removed from a running process on Linux (Electron's Tray has no hide API), so setTrayConfig now returns whether a restart is needed; the renderer shows a toast with a "Restart now" action wired to a new app.relaunch IPC. Enabling the tray, and all macOS/Windows behavior, is unchanged and immediate. The real fix belongs upstream in Chromium/Electron (StatusIconLinuxDbus should own a per-item well-known bus name and release it on teardown); this is a workaround. Refs: #3940, #4985, #3207 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QmyAEEXr4eHiNFSWGbZk7x
|
@DIYgod 这个方便看下不~ 问题是: linux 下每次点关闭到托盘, 都会多一个托盘图标, 反复点的话会出一堆但只有最新那个可以点击. 必须关闭应用才能正常. |
@DIYgod Is this convenient? The problem is: every time you click Close to the tray under Linux, there will be an extra tray icon. If you click it repeatedly, a bunch of them will appear, but only the latest one can be clicked. You must close the application to normal. |
|
@DIYgod 需要我把 pr 拆简单一些吗, 现在除了修托盘重复创建, 还混进去弹窗确认是不是要重启, 可能不好读了. |
@DIYgod Do you need me to make it simpler to dismantle the PR? Now, in addition to repairing the tray and re-creating it, I also include a pop-up window to confirm whether to restart, which may be difficult to read. |
Review follow-ups on the `app.relaunch` IPC added for the Linux tray toggle: - AppImage: `process.execPath` points into the FUSE mount, which is unmounted once the process exits, so the relauncher could not exec it and "Restart now" just quit the app. Relaunch from `$APPIMAGE` instead (undefined elsewhere, so Electron's default is kept for .deb and every other platform). - Autostart: `app.relaunch()` inherits `process.argv`, so a relaunch from a login-item start carried `--start-in-tray` over and brought the app back hidden — with the tray just disabled, that left no window and no tray icon. Filter the flag out. - Use `app.quit()` instead of `app.exit(0)`: `exit` skips `before-quit`, which is where the window bounds are persisted and the cookie store is flushed, so restarting lost the window size and position. - Give the toast a 30s duration and a close button; the sonner default of 4s is too short for a toast whose only purpose is its action. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Description
On Linux, toggling Settings → General → "Minimize to tray" off and on again
leaves a dead icon in the system tray every time. After a few toggles the tray
shows a stack of Folo icons; each opens a context menu but only the newest one's
menu does anything. The pile only clears when Folo fully quits.
Root cause:
setTrayConfig(false)callstray.destroy(). On aStatusNotifierItem host (waybar, KDE Plasma, …) that does not remove the icon —
Chromium's
StatusIconLinuxDbusregisters the item by object path on theprocess-wide shared session-bus connection and, on teardown, un-exports its own
objects but never tells
org.kde.StatusNotifierWatcherthe item is gone. Theconnection name outlives the tray, so the host gets no
NameOwnerChangedandkeeps a dead icon. Re-enabling then calls
new Tray()again and Chromiumregisters a fresh
/org/chromium/StatusNotifierItem/<n+1>, stacking another.Fix (commit 1): on Linux, keep the single
Trayinstance for the app'slifetime instead of destroying/recreating it on the toggle.
registerAppTray()already guards
new Tray()behindif (tray), and the window close handlerreads
getTrayConfig()on every close, so a tray icon that outlives a disabledsetting is inert.
Follow-up (commit 2): the icon genuinely can't be removed from a running
process on Linux (Electron's
Trayhas no hide/visibility API), sosetTrayConfignow returns whether a restart is needed (true only whendisabling on Linux with a tray present).
settings.setMinimizeToTrayforwardsthat and the renderer shows a toast with a "Restart now" action wired to a new
app.relaunchIPC. Enabling the tray, and all behavior on macOS/Windows, isunchanged and takes effect immediately.
The real fix belongs upstream in Chromium/Electron (
StatusIconLinuxDbusshouldown a per-item well-known bus name and release it on teardown so hosts get
NameOwnerChanged); this is a workaround until then.PR Type
Screenshots (if UI change)
Demo Video (if new feature)
Linked Issues
Fixes #3940
Related: #4985, #3207
Additional context
Verified on Debian + Hyprland + waybar (Flatpak build, 1.13.0 / Electron 43.1.0).
D-Bus evidence:
RegisteredStatusNotifierItemsheld four:1.XXX/org/chromium/StatusNotifierItem/1..4entries from a single process;busctl --user treeshowed only/4actually exported;dbus-monitorshowedone
RegisterStatusNotifierItemper toggle and never an unregister; on quit,the host dropped all of them at once.
For review — the trade-off in commit 2: after disabling on Linux the icon
lingers until the app restarts (the toast's "Restart now" is optional; ignoring
it just leaves one inert icon, no stacking). Happy to switch to auto-relaunch or
drop the prompt entirely. The two new i18n strings fall back to
enin otherlocales.
Changelog
(
apps/desktop/changelog/next.md— desktop-only change)