-
-
Notifications
You must be signed in to change notification settings - Fork 1
Custom lua scripts
Audience: HUD creators, theme porters, and anyone writing or extending node behaviour in Conky Studio.
Scope: How visual.custom_lua and source.custom_script work end-to-end; canvas wiring, property panel, build/codegen, Live Preview, and runtime Lua/shell, plus how to keep them reliable.
This page expands the short Custom Lua section in the Visuals node reference. It assumes you already know the three core ideas: Sources → (optional Logic) → Visuals.
- Mental model
- Custom Lua (
visual.custom_lua) - Custom Script (
source.custom_script) - Wiring patterns that work
- Making sure it runs in Studio
- Legacy import path
- Codegen & runtime contracts
- Debugging checklist
- Recipes
- Related extension points
| Piece | Category | Outputs data? | Draws? | Runs where? |
|---|---|---|---|---|
| Custom Script | Source | Yes (one value/line) | No | Shell (execi or daemon cache) → SRC[id] in Lua |
| Logic nodes | Logic | Yes | No | Pure Lua in refresh_sources()
|
| Custom Lua | Visual | No | Yes | Cairo draw function in render.lua
|
Data only flows into Custom Lua; nothing can bind from it.
If you need “Custom Lua computes X, then a Bar uses X,” you must compute X in a Logic node (or Custom Script + Logic) and feed that into both the visual and any downstream consumers. Custom Lua is a drawing sink, not a passthrough.
[Source / Custom Script]
│
▼
[Logic chain] ──optional──► other visuals (Bar, Text, …)
│
▼
[Custom Lua in1…in12] → Cairo only
- One node box in the Visuals → Advanced palette (Complex mode; not in Simple).
- No output socket on the right.
- Up to 12 input sockets on the left (
in1…in12), each bindable. - Header colour matches other visuals (violet).
Double-click / select the node → Properties dock:
| Group | Property | Notes |
|---|---|---|
| Position | Offset X, Offset Y | Applied as cairo_translate before your code. Move the whole block without editing Lua. |
| Data inputs | Input 1 … Input 12 | Each can be a constant or a wire. Accepts percent, Celsius, number, text, category. |
| (code) | Lua code | Multiline monospace editor. Body of the draw function. |
Codegen emits roughly:
local function draw_node_<id>(cr, W, H)
cairo_save(cr)
cairo_translate(cr, <Offset X>, <Offset Y>)
local in1 = <resolved expr or nil>
local in2 = ...
-- … in12 …
-- === your Property "Lua code" body is inserted here ===
cairo_restore(cr)
endThen main_draw_impl calls every visible visual’s function in ascending z-order (Layers dock order).
Already in scope inside your code (framework + injected locals):
| Name | Meaning |
|---|---|
cr |
Cairo context for this Conky window |
W, H
|
Window width/height (after canvas settings) |
in1 … in12
|
Wired values, or nil if unwired
|
clamp, lerp, rounded_rect, … |
Framework helpers |
wall_clock() |
Monotonic real time (prefer over os.clock()) |
safe_parse / safe_number
|
Safe conky_parse wrappers |
load_image_cached / draw_image_fit
|
Images under IMAGES_DIR
|
studio_draw_text, studio_heat_rgb
|
Text + heat colour helpers |
STATE, SRC, HIST, CACHE_KV
|
Shared runtime tables |
THEME_DIR, SCRIPTS_DIR, IMAGES_DIR, CACHE_DIR
|
Paths |
You do not need require 'cairo' — the framework already loads it.
Unbound slots are intentionally nil, not 0. That lets you distinguish “nothing wired” from a real zero reading.
Always write:
local cpu = tonumber(in1) or safe_number('${cpu}', 0)
local label = tostring(in2 or '')Anti-patterns:
-- BAD: crashes or misbehaves when in1 is nil
local cpu = in1 + 1
-- BAD: treats "unwired" the same as "user set constant 0" in confusing ways
local cpu = tonumber(in1) or 0 -- OK only if you truly want 0 when unboundPrefer the tonumber(inN) or safe_number(...) pattern so:
- Live Preview/Build with a wire uses the Studio graph.
- The same Lua still works if someone deletes the wire (falls back to Conky’s built-in).
Custom Lua cannot feed Logic or other visuals. If you need a shared computed value:
- Put the math in Logic (Math, Map Range, Conditional, Smooth, …).
- Wire that Logic output into Custom Lua and into any Bar/Text/Gauge that needs it.
If you need more than 12 live values into one Custom Lua node:
- Combine upstream with Logic (e.g., Math/String Join/Enum Map), or
- Split drawing across multiple Custom Lua nodes (each with its own 12 inputs).
Use the framework STATE table for animation phase, previous values, eased numbers:
local key = 'my_effect_' .. tostring(W) -- or any stable string
local st = STATE[key]
if st == nil then
st = { phase = 0, last = wall_clock() }
STATE[key] = st
end
local now = wall_clock()
local dt = now - st.last
if dt < 0 or dt > 1 then dt = 0 end
st.phase = st.phase + dt * 30 -- degrees per second
st.last = nowAvoid relying on module-level globals that disappear on rebuild; STATE is the supported hook (same pattern as Smooth/Vinyl Spinner generators).
- Prefer assets copied into the theme’s
images/(orassets/for legacy imports). - Load with:
local img = load_image_cached(IMAGES_DIR .. '/logo.png')
draw_image_fit(cr, img, 0, 0, 64, 0, 1.0)- Absolute paths from your machine will not exist on another user’s install. Always build through Studio so paths are rewritten/copied.
Custom Lua can still use the node’s Interaction fields (on_click_command + click region in canvas pixels). Clicks are handled by the generated mouse_handler, independent of your Cairo code. Region is not inferred from what you draw; set X/Y/W/H explicitly in Properties.
Draw is wrapped in pcall. A Lua error in your code prints:
[conky-studio] draw error: <message>
to Conky’s log (visible in Studio’s Live Preview log). One bad Custom Lua node should not tear down the whole HUD; other nodes still draw.
A source that runs your shell (or any executable) and exposes a single value into the graph. Use it when:
- Conky has no
${…}variable for the data. - You are porting a legacy
sensors.sh/one-liner. - You need structured polling with the same cache harness as GPU/Weather families.
Palette: Data Sources → Custom (Complex mode recommended).
| Property | Purpose |
|---|---|
| Script path | Optional file on disk; copied into scripts/ on Build. Ignored if Inline script is non-empty. |
| Inline script | Body edited in Properties (like Custom Lua). Written to scripts/ at Build. Prefer this when fixing imports; no external file is required. |
| Treat output as |
text / number / percent / celsius / category — drives socket colour and which bindable props accept the wire. |
| Polling mode |
execi (Conky ${execi N …}) or daemon (background loop + cache file). |
| Refresh every (sec) | Interval for that mode. |
Optional flags used heavily by legacy import:
-
self_caching— script already writes its own cache (e.g.,sensors.cache); Studio runs it without a stdout-capture wrapper. -
script_basename/companion conf paths — keep names Lua expects.
Stdout, one primary line (or key=value lines for family-style caches). For a normal custom script in daemon mode, the wrapper stores:
value=<first line of stdout>
In Lua, SRC['<node_id>'] becomes that value (string or number, depending on Treat output as).
Empty/failed commands should print a safe default (0 or empty string), not spam stderr every interval.
| Mode | Behaviour | When to use |
|---|---|---|
| execi | Conky runs the script on its interval; a brief block is possible when the cache expires. | Slow polls, simple scripts, decorative HUDs. |
| daemon |
start.sh runs a background loop; Lua only reads a cache file (zero stutter on the draw path). |
Network, sensors, anything that can hang; high FPS HUDs. |
Live Preview: daemon families get one synchronous prime per rebuild (no long-lived poller in the preview process). Values appear after that prime; they are not continuously refreshed until you Build & Install with start.sh.
- Drop Custom Script, set inline body or path, set Treat output as.
- Connect its output socket to:
- a Visual bindable (
value,trigger, …), and/or - Logic inputs, and/or
- Custom Lua
inN.
- a Visual bindable (
- Unused Custom Scripts are still emitted if they are daemon/self-caching (so Custom Lua that reads
sensors.cacheby path still works after legacy import).
Treat output as: number
#!/usr/bin/env bash
# Example: count open TCP connections (illustrative)
ss -tan 2>/dev/null | grep -c ESTAB || echo 0Wire → Bar or Text (with String Format).
CPU Usage ──► Custom Lua in1
RAM Usage ──► Custom Lua in2
local cpu = tonumber(in1) or 0
local ram = tonumber(in2) or 0
-- draw using cpu, ramCPU Temp ──► Smooth ──► Map Range (0–100 → 0–1)
│
├──► Needle Gauge value
└──► Custom Lua in1
Custom Script (category tokens) ──► Enum Map ──► LED Dot/Icon swap
Track Artist ──┐
├──► String Join ──► Text Label
Track Title ──┘
Or feed the joined string into Custom Lua in3 for a custom typography layout.
- Wire Custom Lua to anything (impossible, no output).
- Bind a text Custom Script into a gauge that only accepts numeric kinds (change Treat output as, or insert Logic that parses).
- Rely on absolute
/home/you/...paths in Custom Lua after Build. - Assume Live Preview runs your daemon loop forever (it primes once per rebuild).
- Studio tab — add nodes, wire sockets (drag output → input).
- Properties — set Inline script/Lua code; set kinds and intervals.
-
Live Preview → Start — real Conky + generated
render.lua. - Watch the preview log for
[build],[hint],draw error, script failures. -
Project → Build & Install to Manager when stable; use Manager to launch full
start.sh(real daemon loops).
- Rename the HUD before treating it as finished (Manager/naming notes in Getting Started).
- Use Layers: hide skips codegen; lock blocks accidental moves.
- Keep Canvas (
canvas.root) FPS and Sensor refresh (Hz) sensible: high FPS with low stats_hz is normal (draw chrome often, poll sensors less).
Tools → Hardware & Session before blaming Lua. Wayland/GNOME overlay limits, missing lua-cairo, missing playerctl / sensors, etc., show up there and in preview log hints.
-
Tools → Install Font for families used in Custom Lua
studio_draw_text/cairo_select_font_face. - Image nodes and Custom Lua assets must be present after Build under
images//assets/.
Community plugins register real NodeSpec + generators (Logic/Visual). Prefer a plugin when the behaviour is reusable. Prefer Custom Lua when the drawing is one-off, or you are pasting legacy Cairo.
Project → Import Legacy Theme… parses classic conf/Lua into a Studio project.
Typical outcomes for hand-drawn themes:
- Unrecognised Cairo bodies → Custom Lua nodes with pasted code.
- Common sensors auto-wired into early in1…inN.
- External scripts → Custom Script nodes (often
self_caching+ original basenames). - Warnings list approximations; expect to reposition and re-bind.
After import:
- Live Preview immediately.
- Open each Custom Lua — confirm
tonumber(inN) or …bridges where auto-patch applied. - Verify Custom Script Treat output as and poll mode.
- Rebuild; check Manager Start uses the generated
start.sh.
Semantic extraction is not pixel-perfect recreation — that is by design.
- Your code is the body of a local function; do not declare
function conky_…inside it unless you know you need a nested helper. - Prefer framework helpers over reimplementing clamp/lerp/text.
- Heavy work every frame (file I/O, process spawn) will stutter; push polling to Custom Script/native sources and only draw in Custom Lua.
-
Sources + Logic update in
refresh_sources()on the stats cadence (STATS_HZ/canvas). -
Visuals (including Custom Lua) run every draw frame (
FPS). - Bound
inNvalues are whatever was last written toSRC[…]on the stats tick — same as any other visual’s bound property.
Build runs assert_full_coverage(): every registered visual type must have a Lua generator. Custom Lua is a first-class registered type; your user code is data inside that generator’s template, not a separate registry entry.
All enabled windows share the same render.lua graph by default. Custom Lua draws in each process; the window size is that conf’s width/height. Pin monitors in the Windows dock when needed.
| Symptom | Checks |
|---|---|
| Blank Custom Lua | Syntax error → preview log draw error. pcall swallowed the frame. |
| Always zero/empty inputs | Wire missing; wrong socket; source not in used set (nothing reads it; wire it). Custom Script kind wrong. |
| Works in Preview, fails installed | Daemon scripts need start.sh; path permissions; missing deps (curl, sensors). |
| Stale daemon values in Preview | Expected until rebuild prime; use execi for continuous preview polling, or Build & Install. |
| Image missing | Not copied to images/; wrong basename; SVG without RSVG build. |
| Nil arithmetic | Use tonumber(inN) or default. |
| Logic order wrong | Cycles are broken arbitrarily; keep DAGs. |
| High CPU | Lower canvas FPS; move work to scripts; avoid per-frame shell. |
- Import or paste into Custom Lua.
- Wire CPU Usage →
in1. - Replace internal parse:
local cpu = tonumber(in1) or safe_number('${cpu}', 0)- Keep Offset X/Y for layout; use Layers z-order relative to other Studio visuals.
- Custom Script (daemon, number) reading your tool.
- Optional Smooth.
-
Arc Gauge / Needle Gauge on
value. - Optional second wire into Custom Lua for a matching custom bezel.
- Weather Category or Custom Script printing tokens.
-
Enum Map → index, or Weather Icon on category, or Custom Lua
ifontostring(in1).
- Title/Artist/Progress sources (or Custom Script + playerctl).
- String Join → Text; Progress → Bar.
- Custom Lua or Vinyl Spinner for decorative disc; gate with playback status.
- Draw the control in Custom Lua.
- Set On click to, e.g.,
playerctl play-pause. - Set the click region to the drawn hotspot in canvas coordinates (not only Offset-local).
For reusable behaviour (not one-off Cairo):
| Extension | Where |
|---|---|
| New logic node type |
NodeSpec + logic_generator registration (see built-in logic_* modules) |
| New visual type |
NodeSpec + visual_generator + import from nodes/__init__ /extensions bootstrap |
| Plugins | Tools → Plugins; trust boundary = Conky process |
| Framework helpers | Shared FRAMEWORK_LUA (prefer extending there only in Studio itself) |
Custom Lua remains the escape hatch when a full node type is overkill.
Custom Lua
- Visual only · 12 inputs · no output ·
nilif unbound · Offset X/Y · framework in scope · errors → log viapcall.
Custom Script
- Source · stdout →
SRC[id]· inline or path · kind + poll mode · daemon cached for zero-stutter · still built if self-caching/unwired for pure-Lua themes.
Golden rules
- Compute in Source/Logic; draw in Custom Lua.
-
tonumber(inN) or fallbackevery time. - Validate with Live Preview log, then full
start.shvia Manager. - No absolute home-machine paths in shipped themes.
Document version aligned with Conky Studio’s node model: Custom Lua as visual.custom_lua, Custom Script as source.custom_script, shared render.lua framework, and builder/start.sh, daemon families.