Files
FlClash/plugins/tray/README.md
T
chen08209 1e97716636 feat(platform): move desktop windows to plugins/window and rework the tray
plugins/window replaces the window_manager fork on every desktop.

Changelog: Tray hotkeys, node delays and a delay test
2026-09-30 11:39:58 +08:00

78 lines
3.1 KiB
Markdown

# 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.
```dart
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();
```
- `show` creates the tray on first call and reconciles it afterwards. Re-sending a structurally
identical `TraySpec` performs no platform call, so callbacks may be rebuilt freely.
- `setTitle` is the incremental path for high-frequency text. It is a no-op where
`capabilities.title` is false, and while no tray is visible.
- `hide` is idempotent and returns native state to "`show` was never called", so a later `show`
rebuilds the tray from scratch.
- `openMenu` is a no-op where `capabilities.menuControl` is 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.