Skip to content

Widgets

locainin edited this page Feb 6, 2026 · 5 revisions

Widgets

Widgets are configured under [widgets] in config.toml. The panel renders quick controls, media, toggles, stats, and cards in that order. Widgets can be disabled by setting enabled = false or removing entries from the list.

Refresh cadence

  • refresh_interval_ms: fast polling interval (milliseconds).
  • refresh_interval_slow_ms: slower interval used during stable periods.

Widgets with watch_cmd can update based on events and reduce polling. When a watch command is missing or fails, the widget falls back to polling.

Widget order

  1. Quick controls ([widgets.volume], [widgets.brightness])
  2. Media ([media], when enabled and an active player is present)
  3. Toggles ([[widgets.toggles]])
  4. Stats ([[widgets.stats]])
  5. Cards ([[widgets.cards]])

Default widgets and backends

Default slider backends:

  • Volume: wpctl (PipeWire/WirePlumber)
  • Brightness: brightnessctl

Default toggles:

  • Wi-Fi: nmcli
  • Bluetooth: bluetoothctl (watch: dbus-monitor)
  • Airplane: rfkill (watch: udevadm)
  • Night: gammastep (Hyprland prefers hyprsunset when available)

Default stats:

  • CPU: builtin:cpu
  • RAM: builtin:memory
  • Battery: builtin:battery

Default cards:

  • Calendar: built-in GTK calendar
  • Weather: styled card with no built-in data (set cmd to populate)

Runtime backends can migrate to alternatives when a preferred command is missing.

Command execution

  • Simple commands run directly (no shell).
  • Commands containing shell syntax are run through sh -c.
  • Simple commands are faster and avoid shell overhead.

When a pipeline is required, use an explicit shell command:

cmd = "sh -c 'sensors | awk \"/Package id 0/ { print $4 }\"'"

Slider widgets

[widgets.volume] and [widgets.brightness] share the same schema:

[widgets.volume]
enabled = true
label = "Volume"
icon = "audio-volume-high-symbolic"
icon_muted = "audio-volume-muted-symbolic"
get_cmd = "wpctl get-volume @DEFAULT_AUDIO_SINK@"
set_cmd = "wpctl set-volume @DEFAULT_AUDIO_SINK@ {value}%"
toggle_cmd = "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"
# watch_cmd = "pactl subscribe"
min = 0.0
max = 100.0
step = 1.0
parse_mode = "auto" # auto | percent | ratio

Notes:

  • {value} is replaced with the slider value.
  • parse_mode = "ratio" treats 0.0..=1.0 output as a percentage.

Toggle widgets

Each toggle entry describes commands for a binary control:

[[widgets.toggles]]
kind = "bluetooth"
label = "Bluetooth"
icon = "bluetooth-active-symbolic"
state_cmd = "bluetoothctl show"
on_cmd = "bluetoothctl power on"
off_cmd = "bluetoothctl power off"
watch_cmd = "dbus-monitor --system type=signal,sender=org.bluez"

Notes:

  • kind (or id) applies runtime defaults and is the stable backend identifier.
  • watch_cmd is optional and may be disabled at runtime if unavailable.

Per-toggle CSS classes

Each toggle always has the base class:

  • .unixnotis-toggle

In addition, when kind is set, the UI adds a stable kind-specific class:

  • .unixnotis-toggle-kind-<kind>

This enables per-toggle styling (for example: Wi-Fi and Bluetooth can have different accent colors) without duplicating widget configs.

Class generation rules:

  • Lowercases ASCII letters.
  • Converts _ and non-alphanumeric characters to -.
  • Collapses repeated - and trims leading/trailing -.
  • If the result is empty, no kind-specific class is added.

Example:

.unixnotis-toggle.unixnotis-toggle-kind-wifi {
  border-color: alpha(@unixnotis-accent, 0.5);
}

.unixnotis-toggle.unixnotis-toggle-kind-bluetooth {
  border-color: alpha(@unixnotis-accent-2, 0.5);
}

Stat widgets

Stats can use built-in readers or custom commands. Built-ins read from procfs/sysfs and avoid process spawns.

Built-in tags:

  • builtin:cpu
  • builtin:memory
  • builtin:load
  • builtin:battery
  • builtin:net or builtin:net:INTERFACE

Example:

[[widgets.stats]]
label = "Network"
icon = "network-wired-symbolic"
cmd = "builtin:net:enp3s0"
min_height = 72

If cmd is a shell command, its stdout is used as the widget value.

Card widgets

Cards show multi-line content and are commonly used for calendar or custom scripts.

[[widgets.cards]]
kind = "calendar"
title = "Calendar"
subtitle = "Today"
icon = "x-office-calendar-symbolic"
cmd = "date '+%A, %B %d'"
min_height = 180
monospace = false

Notes:

  • When kind = "calendar" and cmd is omitted, a GTK calendar is rendered.
  • When kind = "weather" and cmd is omitted, the card shows the configured subtitle.

Calendar widget styling

The calendar card is a GTK GtkCalendar widget. Its CSS node tree (per GTK4 docs) looks like:

calendar.view
+-- header
|   +-- button
|   +-- stack.month
|   +-- button
|   +-- button
|   +-- label.year
|   +-- button
+-- grid
    +-- label[.day-name][.week-number][.day-number][.other-month][.today]

Useful selectors in widgets.css:

  • .unixnotis-calendar for the card root
  • .unixnotis-calendar .header for the header row
  • .unixnotis-calendar .day-name, .day-number, .other-month, .today, :selected
  • .unixnotis-calendar header > button:nth-child(1) (prev month)
  • .unixnotis-calendar header > button:nth-child(3) (next month)
  • .unixnotis-calendar header > button:nth-child(4) (prev year)
  • .unixnotis-calendar header > button:nth-child(6) (next year)

Example (icon-forced month navigation):

.unixnotis-calendar header > button:nth-child(1) {
  -gtk-icon-source: -gtk-icontheme("go-previous-symbolic");
}

.unixnotis-calendar header > button:nth-child(3) {
  -gtk-icon-source: -gtk-icontheme("go-next-symbolic");
}

Widget plugin API (v1)

Stats and cards support an external plugin block:

  • [[widgets.stats]].plugin
  • [[widgets.cards]].plugin

When plugin is set, it takes precedence over cmd.

Plugin config schema

[[widgets.stats]]
label = "VPN"
icon = "network-vpn-symbolic"
min_height = 72

[widgets.stats.plugin]
api_version = 1
command = "scripts/vpn_widget"
timeout_ms = 1500
max_output_bytes = 8192

Fields:

  • api_version: currently 1.
  • command: executable command path + args.
  • timeout_ms: command timeout in milliseconds.
  • max_output_bytes: maximum accepted stdout payload size.

Security/runtime notes:

  • Plugin commands must be simple commands (no shell metacharacters, no sh -c).
  • Invalid plugin configs are disabled at runtime with a warning.
  • Oversized payloads, invalid JSON, and version mismatches are rejected.

Stat plugin payload (v1)

Stdout must be JSON:

{
  "api_version": 1,
  "text": "42%"
}

Card plugin payload (v1)

Stdout must be JSON:

{
  "api_version": 1,
  "title": "Weather",
  "text": "72F, clear"
}

Notes:

  • title is optional; when omitted, configured card title is kept.
  • text is required and becomes the card body.

Command-driven widgets (legacy/compatible path)

Existing cmd fields remain fully supported:

[[widgets.cards]]
title = "Weather"
icon = "weather-clear-symbolic"
cmd = "scripts/weather.sh"
min_height = 180
monospace = true

Guidelines:

  • cmd stdout becomes the card body (multi-line output is supported).
  • Prefer watch-based flows where possible to reduce polling pressure.
  • Keep scripts fast and deterministic.

Clone this wiki locally