Changelog: Windows and Linux desktop fixes
tray
System tray integration for FlClash on Linux, macOS and Windows.
API
The plugin owns call ordering, idempotency, call serialization and unchanged-payload suppression. Callers declare the desired tray state; they never sequence platform calls themselves.
await Tray.instance.show(
TraySpec(
icon: TrayIcon.asset('assets/images/tray/unix/status_1.png', isTemplate: true),
toolTip: 'FlClash',
menu: [
TrayMenuAction(label: 'Show', onSelected: showWindow),
const TrayMenuSeparator(),
TrayMenuCheckbox(label: 'TUN', checked: true, onSelected: toggleTun),
TrayMenuSubmenu(label: 'Proxy', items: proxyItems),
],
),
);
await Tray.instance.setTitle('↑ 1.2 MB/s');
await Tray.instance.hide();
showcreates the tray on first call and reconciles it afterwards. Re-sending a structurally identicalTraySpecperforms no platform call, so callbacks may be rebuilt freely.setTitleis the incremental path for high-frequency text. It is a no-op wherecapabilities.titleis false, and while no tray is visible.hideis idempotent and returns native state to "showwas never called", so a latershowrebuilds the tray from scratch.openMenuis a no-op wherecapabilities.menuControlis false.
TrayIcon.asset names a bundled PNG and follows Flutter's resolution-aware layout: every
2.0x/, 3.0x/, 4.0x/ sibling that exists is loaded too. macOS receives them all as
representations of one size-point image; Linux is handed the largest raster on disk and lets the
indicator scale it; Windows loads the path as-is, so point it at a multi-size .ico instead.
Menu item ids are assigned by pre-order position, so an unchanged menu serializes identically across rebuilds and click dispatch stays stable while a menu is open.
Actions, checkboxes and submenus take an optional detail, secondary text shown after the label, such as
a shortcut or a measured latency:
| macOS | Windows | Linux |
|---|---|---|
| right-aligned column in the color of a native key equivalent | the menu's accelerator column | appended in parentheses |
AppIndicator menus travel over dbusmenu, which carries no column for free text, hence the Linux fallback.
Events
Tray.instance.events is a broadcast stream of TrayIconActivated, TrayMenuRequested and
TrayMenuItemSelected. Per-item onSelected callbacks fire before the corresponding stream event.
On Windows a left click is TrayIconActivated and a right click TrayMenuRequested; macOS opens the
menu from either button, as status items do there, so it only sends TrayMenuRequested.
Capabilities
Tray.instance.capabilities reports what the current platform can do, so callers branch on ability
rather than on Platform.isX.
| macOS | Windows | Linux | |
|---|---|---|---|
title |
yes | no | yes |
toolTip |
yes | yes | yes |
iconEvents |
yes | yes | no |
menuControl |
yes | yes | no |
Linux runs on AppIndicator/StatusNotifierItem, where the desktop shell owns the menu; the application cannot receive icon clicks or open the menu itself.
Linux requirements
libayatana-appindicator3-dev, or libappindicator3-dev as a fallback.