Skip to main content
otter-bar and similar daemons use widget structs, not Surface Description trees. Each widget is a Zig struct with draw, setArea, getWidth, and optional input hooks. Layout code lines them up in a row and calls those methods directly. Stick to this path for config-driven status bar rows. New windows and forms should use Surface Description.

Widget shell

Every bar widget embeds ui.Widget:
pub const Clock = struct {
    widget: ui.Widget,
    // widget-specific fields

    pub fn draw(self: *Clock, frame: anytype, clip: Rect) !void { ... }
    pub fn setArea(self: *Clock, area: Rect) void { self.widget.area = area; }
    pub fn getWidth(self: *Clock) u31 { ... }
    // optional: motion, leave, click, scroll, getPopupSupport
};
Widget tracks area and pointer motion state. No vtable on the hot path for bar specs (see below).

Widget registries

FieldRegistry(Owner, specs)

Maps layout string names ("clock", "volume") to optional struct fields on the bar owner:
const specs = [_]ui.widget_registry.FieldSpec{
    .{ .name = "clock", .field = "clock_widget" },
    .{ .name = "volume", .field = "volume_widget" },
};
const registry = ui.widget_registry.FieldRegistry(Bar, &specs);
Registry methods: lookup by name, getWidth, setArea, hit test, popup enumeration, damage collection, drawByName.

NamedWidgetList(Owner, spec)

Same dispatch for dynamic lists like button_menu:
const spec = ui.widget_registry.NamedListSpec{
    .list_field = "custom_buttons",
    .widget_field = "widget",
    .layout_prefix = "button_menu",
};

Draw specs (widgets/specs/)

Bar widgets render through draw specs under widgets/specs/ (for example widgets/specs/clock.zig). Specs are comptime structs with draw(frame, clip, ...) that call drawing primitives or emit Surface Description subtrees for complex popups. Registries invoke specs plus widget hooks for:
  • Input (click, scroll, motion)
  • Popups (getPopupSupport, volume/MPRIS/network menus)
  • Damage (collectDamage, markFullRedraw)

Available bar widgets

WidgetConfig nameNotes
ClockclockTime/date, optional calendar command
BatterybatteryUPower D-Bus
BrightnessbrightnessBacklight sysfs, scroll to adjust
Buttonbutton_menu, customRuns shell command
Workspacesworkspacesotter-tag / ext-workspace / dwl IPC
ActiveWindowtitleFocused window title
PowerProfilespower_profilespower-profiles-daemon
CpuLoadcpu_load/proc CPU percent
CpuTempcpu_tempThermal zones
MemorymemoryRAM percent
NetworknetworkNetworkManager stats and menu
WeatherweatherReads otter-weather cache file
FalcondfalcondSCX scheduler status
VolumevolumePipeWire, scroll, device popup
MprismprisMedia player controls
SystemTraysystem_trayStatusNotifierItem row
Enable flags in otter-bar.conf (workspaces_enabled, falcond_enabled, etc.). See otter-bar.

D-Bus and PipeWire dependencies

Several widgets need runtime services:
WidgetRequires
Battery, PowerProfiles, MPRIS, SystemTray, NetworkD-Bus (-Denable_dbus=true)
VolumePipeWire (-Denable_pipewire=true)
Build with -Denable_dbus=false or -Denable_pipewire=false to drop widgets at compile time.

Popups and tooltips

Widgets implementing getPopupSupport can open PopupMenu or custom layer surfaces (volume slider, network Wi-Fi list). HoverState drives tooltip delay. Popups render in a separate draw pass with damage tracking.

Damage and redraw

Bar polls D-Bus, timers, and config inotify. Each widget reports damage via the registry so DamageTracker gets minimal rects. Full redraw on theme or config reload.

Adding a custom bar widget

  1. Create widgets/my_widget.zig struct with draw, setArea, getWidth.
  2. Add widgets/specs/my_widget.zig draw spec.
  3. Register field in otter-bar config schema and layout parser.
  4. Add FieldSpec entry to the bar registry.
For one-off launcher buttons, use NamedWidgetList and button_menu config entries instead of a new widget type.

Surface Description in bar popups

Some widgets (volume popup, MPRIS expanded) build a small UiState internally for the popup panel while the bar strip stays on the widget path. Hybrid pattern: bar strip = widget spec; popup = Surface Description.

Next