documentation/toolbar-plugins.md
@seelen/fancy-toolbarThis is one concrete example of the generic Plugin mechanism described in plugin guidelines:
@seelen/fancy-toolbar is the target widget, and this page documents its schema for plugin, and its rules for
parsing and executing that data. None of this is special-cased in Seelen UI's core — the toolbar widget owns all of it.
plugin Payload — ToolbarItemid: "@seelen/tb-notifications"
target: "@seelen/fancy-toolbar"
plugin:
scopes:
- Notifications
template: >-
return [
dndActive ? icon("TbZzz") : null,
count > 0 ? icon("MdNotificationsActive") : icon("MdOutlineNotifications"),
]
badge: "return count > 0 ? count : null"
tooltip: 'return [t("placeholder.notifications"), ": ", count]'
onClickV2: |-
trigger("@seelen/notifications");
The full field set (scopes, template, tooltip, badge, onClick/onClickV2, onWheelUp, onWheelDown,
style, remoteData) is shown in the sections below.
Every field except scopes and template is optional. template, tooltip, badge, onClick, onWheelUp, and
onWheelDown are all JS function bodies written as plain strings (typically pulled in with !include from a .js
file) — not JSON, not a declarative object. The toolbar widget compiles and runs each one in a sandbox.
Legacy alias:
onClickV2is accepted as an alternate key foronClick— several bundled plugins use it, both work identically.
scopes — What Data Gets InjectedEach entry in scopes (case-insensitive) makes a set of fields available inside template/tooltip/badge/ onClick
as top-level variables — no scope. prefix needed.
| Scope | Injected variables | Backing command(s) |
|---|---|---|
Date | date (formatted string) | local reactive clock |
Notifications | count, dndActive | GetNotifications, GetNotificationsMode |
Media | defaultOutputDevice, defaultInputDevice, volume, isMuted, inputVolume, inputIsMuted, mediaSession | GetMediaSessions, GetMediaDevices |
Network | online, interfaces (NetworkAdapter[]), usingInterface | GetNetworkInternetConnection, GetNetworkAdapters, GetNetworkDefaultLocalIp |
Keyboard | activeLang, activeKeyboard, activeLangPrefix, activeKeyboardPrefix, languages, imeState | SystemGetLanguages, SystemGetImeState |
User | user (includes computed displayName) | GetUser |
Bluetooth | devices, getIconNameForBTDevice(device) | GetBluetoothDevices |
Power | power (PowerStatus), powerMode, batteries (Battery[]) | GetPowerStatus, GetPowerMode, GetBatteries |
FocusedApp | focusedApp | GetFocusedApp |
Workspaces | workspaces, activeWorkspace | StateGetVirtualDesktops |
Disk | disks (Disk[]) | GetSystemDisks |
NetworkStatistics | networkStatistics | GetSystemNetwork |
Memory | memory (Memory) | GetSystemMemory |
Cpu | cores (Core[]) | GetSystemCores |
Tray | trayIcons | GetSystemTrayIcons |
TrashBin | trashBinInfo (TrashBinInfo) | GetTrashBinInfo |
Shapes of the more structured values (generated TS, libs/core/gen/types/):
type Memory = { total: number; free: number; swapTotal: number; swapFree: number };
type Core = { name: string; brand: string; usage: number; frequency: number };
type Disk = {
name: string;
fileSystem: string;
totalSpace: number;
availableSpace: number;
mountPoint: string;
isRemovable: boolean;
readBytes: number;
writtenBytes: number;
};
type PowerStatus = {
acLineStatus: string;
batteryFlag: string;
batteryLifePercent: number;
systemStatusFlag: string;
batteryLifeTime: number;
batteryFullLifeTime: number;
};
type Battery = {
vendor: string;
model: string;
serialNumber: string;
technology: string;
state: string;
capacity: number;
temperature: number;
percentage: number;
cycleCount: number;
smartCharging: boolean;
energy: number;
energyFull: number;
energyFullDesign: number;
energyRate: number;
voltage: number;
timeToFull: number;
timeToEmpty: number;
};
type NetworkAdapter = {
name: string;
description: string;
status: string;
dnsSuffix: string;
type: string;
ipv6: string[];
ipv4: string[];
gateway: string | null;
mac: string | null;
};
type TrashBinInfo = { itemCount: number; sizeInBytes: number };
remoteData — Fetching External Data Into ScoperemoteData:
weather:
url: "https://api.example.com/weather"
requestInit: null
updateIntervalSeconds: 600
For each key, the toolbar fetch()es url (with the given requestInit, if any), parses the response as JSON or text
depending on Content-Type, and injects the result under that key into the script scope — so the example above makes a
weather variable available inside template. If updateIntervalSeconds is set, the fetch repeats on that interval.
All scripts run inside a JS sandbox (@nyariv/sandboxjs), not raw eval. There are two categories of script with two
different scopes:
template, tooltip, badgeRun with { ...resolvedScopes, ...resolvedRemoteData, t }, where t(key, args) is the i18n lookup function. You must
return a value — the return value becomes the rendered content:
icon(name, size) / Icon(...) — an icon from the bundled icon setAppIcon(...) — an application iconImage(...) — an image elementButton(...) — a clickable button elementGroup(...) — a container grouping other elementsconst totalUsage = cores.reduce((total, core) => total + core.usage, 0);
const used = totalUsage / cores.length;
return [icon("LuCpu"), " ", used.toFixed(0) + "%"];
onClick, onWheelUp, onWheelDownRun with { ...resolvedScopes, ...resolvedRemoteData, invoke, open, trigger }. Return value is ignored — these are
fire-and-forget handlers.
invoke(command, args) — whitelisted: only a small allow-list of SeelenCommands can be called this way (e.g.
SwitchWorkspace, SetVolumeLevel, OpenFile). This is not the full command surface — it exists so toolbar items
can trigger a handful of safe system actions without a full IPC bridge.open(path) — opens a file, URL, or ms-settings: shell URI with the OS default handler.trigger(widgetId) — pops up another widget (typically a Popup-preset widget like @seelen/bluetooth-popup)
anchored at this toolbar item's screen position. This is how toolbar items open their associated popups.plugin:
scopes: [Bluetooth]
tooltip: |-
return "Bluetooth";
template: !include plugin.js
onClickV2: |-
trigger("@seelen/bluetooth-popup");
Using open():
plugin:
scopes: [Power]
tooltip: !include plugin/tooltip.js
template: !include plugin/template.js
onClickV2: open("ms-settings:powersleep")
styleA plain object merged onto the item's container as inline styles, keyed like a React style prop
({ "margin-left": "4px" }, numbers allowed for unitless properties).
In the Fancy Toolbar's own settings, a toolbar item slot accepts either an inline ToolbarItem object (as shown above)
or a plain string — the id of an already-installed Plugin resource targeting @seelen/fancy-toolbar. Both forms
resolve to the exact same ToolbarItem shape by the time it reaches the execution engine described in section 4.