Event reference
Every event topic a widget can subscribe to, with payload shapes, permission gating, and delivery rules.
Widgets subscribe to events through the SDK's on* methods (dd.time.onTick,
dd.system.onVitals, ...); every one returns an unsubscribe function. Events
deliver only while the widget is mounted and holds the gating permission;
hidden widgets unmount entirely and receive nothing. A peek (every widget
concealed by the user) keeps widgets mounted, so events keep flowing.
The topics
| Topic | Needs | Payload | Delivery |
|---|---|---|---|
time.tick | time | { epochMs } | second-aligned wall clock |
settings.changed | nothing | effective settings object | on every edit, including live previews from the settings dialog |
theme.tokens | nothing | full --dd-* token map | on every theme change and every per-widget style edit (previews and the revert included), to every widget; the SDK re-applies it to :root |
cursor.hover | nothing | { inside } | enter and leave over the widget-plus-pane footprint |
cursor.move | nothing | { x, y } viewport fractions | roughly 30 Hz while the cursor is inside |
desktop.changed | desktop | {} ping | desktop item set changed; re-list |
folders.changed | folders | {} ping | a granted folder changed; only to affected instances; debounced ~400 ms |
system.vitals | system | SystemVitals | 1 Hz sampler snapshot |
media.nowPlaying | media | NowPlaying | change-driven, not fixed-rate |
audio.spectrum | audio | { bins: number[] } | ~30 Hz while media plays (20 Hz low-spec) |
widgets.changed | control | {} ping | some approved instance's state changed; re-list |
popout.changed | manifest popout | { active, side? } | open, reshape, close, including host force-closes; both documents |
windows.changed | taskbar + kind bar | { windows }, the full dd.windows.list snapshot | change-driven: a window opened or closed, focus moved, a title changed, minimize state flipped, a window changed monitors; the driver for a taskbar's focus indicator |
pins.changed | taskbar + kind bar | { pins }, the full dd.pins.list snapshot | change-driven: a pin made or removed, or the pinned order moved |
bar.strip.changed | taskbar + kind bar | { items }, the full dd.bar.strip.list snapshot | change-driven: a row added or dropped, a pin made or removed, a move on any bar; the render order for a taskbar, to the bar document and its pane |
bar.flyout.changed | taskbar + kind bar | { open, key } | the preview flyout's real state, on every open, switch and close, whoever caused it; the bar document only |
systray.changed | taskbar + kind bar | { count, open } | the notification area's state, on every tray change and every open or close of the host list; the driver for a caret's pressed look |
popout.message | manifest popout | whatever the other document sent | main and pane relay, up to 8 KB per message |
peers.drag | peers | { active, self } | drag lifecycle, to same-type frames |
peers.drop | peers | { data, x, y } | targeted: only the drop target sees the payload |
peers.end | peers | { accepted, outside } | targeted to the drag's source |
Payload notes
settings.changed carries the effective settings. While the user tries
values in the settings dialog it fires with live-previewed (unsaved) values,
and once more with the stored values if they dismiss; treat every event the
same and re-apply. dd.settings.get() always returns the persisted state.
theme.tokens is delivered to every widget, subscribed or not, and the
SDK re-applies the map to :root automatically, so plain CSS needs nothing.
Per-widget style edits (the Style tab of your widget's settings window)
arrive the same way, live previews included. Subscribe (via
dd.theme.onChange) only when you resolve token values in JS; never cache
across it.
cursor.hover and cursor.move work for non-interactive widgets too:
the window stays click-through, you get the signal without capturing input.
While a popout is open the footprint is the bounding box of widget plus pane
(crossing the gap never flickers a leave), both documents receive hover,
and move goes to the document the cursor is actually over. For simple
element hover, skip the subscription: mark elements data-dd-hover and the
SDK toggles a dd-hover class.
The {} pings (desktop.changed, folders.changed, widgets.changed)
carry no data on purpose: they tell you your last listing is stale, and the
matching list call is the read path.
system.vitals: the payload shape and its nullability contract are on
the system page. Every field
fails independently; render nulls as absent, not zero.
media.nowPlaying: shape, the session selection policy, and the local
position interpolation recipe are on
the media page. Seed
with dd.media.status().
audio.spectrum: 500 loudness bands, the silence marker frame, the
demand-driven capture rule, and the render-loop contract are on
the audio section.
popout.changed and popout.message: lifecycle causes and the 8 KB
relay cap are on
the popouts page.
peers.*: the full drag lifecycle, including what accepted and
outside mean, is on
the peers section.
The taskbar streams (windows.changed, pins.changed,
bar.strip.changed, bar.flyout.changed, systray.changed) are the whole
truth for a bar: never move a focus indicator, a pressed caret or a row
from your own click, follow the stream. dd.bar.store() does that for
you; the shapes and the seed calls are on
the bar widgets page.