Scripted widgets
Scripted Widgets
Section titled “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.
Configuration
Section titled “Configuration”[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"| Setting | Type | Default | Description |
|---|---|---|---|
script | string | "" | 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_reload | bool | false | Watch the script file for changes and reload automatically (inotify). Intended for development. |
Lua API
Section titled “Lua API”noctalia.* — general utilities
Section titled “noctalia.* — general utilities”| Function | Returns | Description |
|---|---|---|
noctalia.log(msg) | — | Log a message to shell debug output |
noctalia.runAsync(cmd) | boolean | Execute a shell command asynchronously (fire-and-forget). Returns true if the process was launched. |
noctalia.runSync(cmd) | exitCode, stdout, stderr | Execute a shell command synchronously. Returns exit code (number), stdout (string), stderr (string). |
noctalia.commandExists(name) | boolean | Check 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. |
barWidget.* — widget display
Section titled “barWidget.* — widget display”| Function | Returns | Description |
|---|---|---|
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() | boolean | Whether the bar is in vertical orientation |
barWidget.getConfig(key, default?) | any | Read 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.
Lua callbacks
Section titled “Lua callbacks”| Callback | Called 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:
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.
| Target | Description |
|---|---|
focused | Dispatch to one matching widget on the preferred interactive output |
| monitor selector | Dispatch to one matching widget on a matching connector, such as DP-1 |
all | Broadcast 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 -- ... endendpayload is the remaining command text after the event, or an empty string when omitted.
Scene graph
Section titled “Scene graph”Every scripted widget has the following node tree:
InputArea (accepts left / right / middle clicks) └─ Flex (horizontal, center-aligned) ├─ Glyph (hidden until setGlyph is called) └─ LabelsetVisible(false) hides the entire InputArea root.
Example: GPU screen recorder
Section titled “Example: GPU screen recorder”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
- IPC —
start,stop,toggle,replay-start,replay-stop,replay-toggle, andreplay-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 = 60color_range = "limited"resolution = "original"show_cursor = truecopy_to_clipboard = falserestore_portal = falsereplay_enabled = falsereplay_duration = 30replay_storage = "ram"hide_inactive = falsenoctalia msg scripted-widget screen_recorder focused startnoctalia msg scripted-widget screen_recorder focused stopnoctalia msg scripted-widget screen_recorder DP-1 togglenoctalia msg scripted-widget screen_recorder focused:default start| Setting | Type | Default | Description |
|---|---|---|---|
directory | string | "" | Output directory (empty = ~/Videos) |
filename_pattern | string | "recording_%Y%m%d_%H%M%S" | strftime pattern for filenames |
video_source | string | "portal" | portal, screen, or focused-monitor (Hyprland) |
video_codec | string | "h264" | h264, hevc, av1, vp8, vp9 |
audio_codec | string | "opus" | opus or aac |
audio_source | string | "default_output" | none, default_output, default_input, both |
quality | string | "very_high" | medium, high, very_high, ultra |
frame_rate | number | 60 | Recording frame rate |
color_range | string | "limited" | limited or full |
resolution | string | "original" | original, 1920x1080, 2560x1440, etc. |
show_cursor | bool | true | Include mouse cursor in recording |
copy_to_clipboard | bool | false | Copy file URI to clipboard after recording |
restore_portal | bool | false | Restore previous XDG portal session |
replay_enabled | bool | false | Enable replay buffer support |
replay_duration | number | 30 | Replay buffer duration in seconds |
replay_storage | string | "ram" | ram or disk |
hide_inactive | bool | false | Hide widget when not recording or replaying |