Skip to content

Scripted widgets

Custom bar widgets driven by Luau scripts. A script controls a widget’s glyph, label, and colors, and can run shell commands, send notifications, and read config settings.

[widget.my_widget]
type = "scripted"
script = "~/.config/noctalia/scripts/my_widget.lua"
# any extra keys are readable from Lua via barWidget.getConfig()
my_setting = "value"
SettingTypeDefaultDescription
scriptstring""Path to the Luau script file. Absolute paths and ~ paths are used directly; relative paths are resolved from the assets bundle (e.g. scripts/screen_recorder.lua).
hot_reloadboolfalseWatch the script file for changes and reload automatically (inotify). Intended for development.

FunctionReturnsDescription
noctalia.log(msg)Log a message to shell debug output
noctalia.runAsync(cmd)booleanExecute a shell command asynchronously (fire-and-forget). Returns true if the process was launched.
noctalia.runSync(cmd)exitCode, stdout, stderrExecute a shell command synchronously. Returns exit code (number), stdout (string), stderr (string).
noctalia.commandExists(name)booleanCheck if a command exists in PATH
noctalia.notify(title, body?)Show an internal notification (normal urgency)
noctalia.notifyError(title, body?)Show an internal notification (critical urgency)
noctalia.getenv(name)string?Read an environment variable. Returns nil if unset.
FunctionReturnsDescription
barWidget.setText(text)Set the label text
barWidget.setGlyph(name)Set the icon by Noctalia alias, Tabler icon name, or U+ / 0x codepoint string
barWidget.setColor(role, mode?)Set the label color by color role
barWidget.setGlyphColor(role, mode?)Set the glyph color by color role
barWidget.setVisible(visible)Show or hide the entire widget
barWidget.setUpdateInterval(ms)Set the update() tick interval in milliseconds (default 250, minimum 16)
barWidget.isVertical()booleanWhether the bar is in vertical orientation
barWidget.getConfig(key, default?)anyRead a TOML config setting. Returns the typed value or default/nil.

Color roles: primary, on_primary, secondary, on_secondary, tertiary, on_tertiary, error, on_error, surface, on_surface, surface_variant, on_surface_variant, outline, shadow, hover, on_hover.

Color mode is optional. The default "auto" mode lets Settings styling apply to neutral content: unset colors and "on_surface" use the widget foreground resolver, including color and capsule_foreground. Other roles such as "error" or "primary" are treated as script state colors. Pass "script" as the second argument to force the script role even for "on_surface"; Settings color still has final precedence.

CallbackCalled when
update()Every ~250ms while the widget is visible
onClick()Left mouse button clicked
onRightClick()Right mouse button clicked
onMiddleClick()Middle mouse button clicked when [shell].middle_click_opens_widget_settings = false
onHover(isHovering)Mouse enters (true) or leaves (false) the widget
onIpc(event, payload)noctalia msg scripted-widget ... dispatches an event to the widget

Scripted widgets can receive named events from Noctalia IPC:

Terminal window
noctalia msg scripted-widget <widget-name> <target[:bar-name]> <event> [payload]

<widget-name> is the [widget.<name>] config key. <target> is required so side-effectful scripts do not accidentally run once per monitor or once per bar. If the target matches more than one live scripted widget instance, the command fails instead of picking one arbitrarily.

TargetDescription
focusedDispatch to one matching widget on the preferred interactive output
monitor selectorDispatch to one matching widget on a matching connector, such as DP-1
allBroadcast to every live matching instance
<target>:<bar-name>Narrow any target to a named bar, such as focused:top or DP-1:default

The script receives the event through:

function onIpc(event, payload)
if event == "toggle" then
-- ...
end
end

payload is the remaining command text after the event, or an empty string when omitted.


Every scripted widget has the following node tree:

InputArea (accepts left / right / middle clicks)
└─ Flex (horizontal, center-aligned)
├─ Glyph (hidden until setGlyph is called)
└─ Label

setVisible(false) hides the entire InputArea root.


A screen_recorder.lua script is bundled in assets/scripts/ that wraps gpu-screen-recorder with recording and replay buffer support.

  • Left click — toggle recording
  • Right click — toggle replay buffer (if enabled) or save replay (if active)
  • Middle click — save replay buffer
  • IPCstart, stop, toggle, replay-start, replay-stop, replay-toggle, and replay-save
[widget.screen_recorder]
type = "scripted"
script = "scripts/screen_recorder.lua"
directory = ""
filename_pattern = "recording_%Y%m%d_%H%M%S"
video_source = "portal"
video_codec = "h264"
audio_codec = "opus"
audio_source = "default_output"
quality = "very_high"
frame_rate = 60
color_range = "limited"
resolution = "original"
show_cursor = true
copy_to_clipboard = false
restore_portal = false
replay_enabled = false
replay_duration = 30
replay_storage = "ram"
hide_inactive = false
Terminal window
noctalia msg scripted-widget screen_recorder focused start
noctalia msg scripted-widget screen_recorder focused stop
noctalia msg scripted-widget screen_recorder DP-1 toggle
noctalia msg scripted-widget screen_recorder focused:default start
SettingTypeDefaultDescription
directorystring""Output directory (empty = ~/Videos)
filename_patternstring"recording_%Y%m%d_%H%M%S"strftime pattern for filenames
video_sourcestring"portal"portal, screen, or focused-monitor (Hyprland)
video_codecstring"h264"h264, hevc, av1, vp8, vp9
audio_codecstring"opus"opus or aac
audio_sourcestring"default_output"none, default_output, default_input, both
qualitystring"very_high"medium, high, very_high, ultra
frame_ratenumber60Recording frame rate
color_rangestring"limited"limited or full
resolutionstring"original"original, 1920x1080, 2560x1440, etc.
show_cursorbooltrueInclude mouse cursor in recording
copy_to_clipboardboolfalseCopy file URI to clipboard after recording
restore_portalboolfalseRestore previous XDG portal session
replay_enabledboolfalseEnable replay buffer support
replay_durationnumber30Replay buffer duration in seconds
replay_storagestring"ram"ram or disk
hide_inactiveboolfalseHide widget when not recording or replaying