Skip to content

Widgets

locainin edited this page Apr 17, 2026 · 9 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]])

Layout behavior

Widgets use fixed layouts in the panel:

  • Toggles render in 4 columns
  • Stats render in 2 columns
  • Cards render in 2 columns
  • The media widget uses one of four structural presets with different width budgets

This matters for width tuning:

  • panel.width is only a requested width
  • A row with large min-width, padding, margins, or long non-wrapping text can force the live panel wider than the requested width
  • When a panel looks too wide, the width driver is often the toggle row or a 2-column card/stat row rather than the panel shell itself
  • The media row now clamps itself to the panel body width, so media is less likely than before to be the first place to look unless media-specific CSS adds large sizing rules
  • noticenterctl css-check now warns when toggle/stat/card/media sizing looks likely to overrun the requested panel width

For tighter themes, reduce horizontal sizing in widgets.css first:

  • toggle/card/stat min-width
  • left/right padding
  • left/right margins
  • long labels that do not wrap

Media layouts

The media widget is configured under [media] and themed through [theme].media_css.

[media]
layout = "player" # carousel | inline | stacked | showcase | player
show_source = false
show_position = false
show_source_when_single_player = false
show_position_when_single_player = false
title_fallback = "artist" # identity | artist | empty
position_format = "fraction" # fraction | current
art_position = "auto" # auto | start | top | hidden
controls_position = "auto" # auto | inline | bottom | side | hidden
navigation_position = "auto" # auto | external | with_controls | hidden
art_size_px = 56
text_width_floor_px = 220
card_height_px = 176
content_spacing_px = 4
control_spacing_px = 8
navigation_spacing_px = 0
title_char_limit = 32

[media.source_aliases]
spotify = "Spotify"

[theme]
media_css = "media.css"

Layout presets:

  • carousel: keeps the older structure with player nav buttons outside the card
  • inline: folds player nav into the card so the whole transport model lives in one shell
  • stacked: keeps art and metadata above a lower control strip
  • showcase: uses a wider card with a dedicated action rail on the right
  • player: uses centered top art with a lower transport dock and no player-switch nav by default

Composition notes:

  • layout picks the default shell
  • art_position, controls_position, and navigation_position can override that default
  • player is the opt-in centered shell preset, so a plain art_position = "top" override does not force that centered layout onto every other preset
  • show_art, show_controls, and show_navigation remove those regions entirely
  • art_size_px, text_width_floor_px, and card_height_px tune the shell geometry
  • content_spacing_px, control_spacing_px, and navigation_spacing_px tune the shell gaps

Metadata notes:

  • show_source controls the source badge row above the title
  • show_source_when_single_player keeps that badge visible for single-player setups
  • show_position controls the player counter
  • show_position_when_single_player keeps that counter visible for single-player setups too
  • title_fallback decides what to render when a player exposes no title
  • position_format switches the counter between current/total and current
  • source_aliases can replace noisy player identities with cleaner labels
  • show_title and show_artist can hide those lanes without hiding the rest of the card

Theme notes:

  • media.css is loaded above widgets.css, so media-specific ricing can stay isolated
  • noticenterctl css-check validates media.css as an active theme file when [theme].media_css points to it

Useful selectors in media.css:

  • .unixnotis-media-card
  • .unixnotis-media-card-carousel, .unixnotis-media-card-inline, .unixnotis-media-card-stacked, .unixnotis-media-card-showcase, .unixnotis-media-card-player
  • .unixnotis-media-stack
  • .unixnotis-media-stack-player
  • .unixnotis-media-row
  • .unixnotis-media-row-player
  • .unixnotis-media-header
  • .unixnotis-media-body
  • .unixnotis-media-text
  • .unixnotis-media-main
  • .unixnotis-media-meta
  • .unixnotis-media-control-strip
  • .unixnotis-media-action-rail
  • .unixnotis-media-nav-strip
  • .unixnotis-media-nav-prev, .unixnotis-media-nav-next
  • .unixnotis-media-button-prev, .unixnotis-media-button-play, .unixnotis-media-button-next
  • .unixnotis-media-position
  • .unixnotis-media-art-top, .unixnotis-media-art-start, .unixnotis-media-art-hidden
  • .unixnotis-media-controls-inline, .unixnotis-media-controls-bottom, .unixnotis-media-controls-side, .unixnotis-media-controls-hidden
  • .unixnotis-media-nav-external, .unixnotis-media-nav-inline, .unixnotis-media-nav-bottom, .unixnotis-media-nav-side, .unixnotis-media-nav-hidden
  • .unixnotis-media-has-source, .unixnotis-media-no-source
  • .unixnotis-media-has-title, .unixnotis-media-no-title
  • .unixnotis-media-has-position, .unixnotis-media-no-position

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.
  • [widgets].toggle_tooltips = true enables GTK hover tooltips for toggle buttons. The default is false so compact themes do not get extra hover popups unless requested.

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