Skip to content

Theme Architecture Codegen

Bobby Comet edited this page Aug 2, 2026 · 2 revisions

Theme Architecture & Codegen Pipeline

How a project JSON becomes a runnable Conky theme folder.

Project (nodes, edges, canvas, props)
        │
        ▼
   builder.build_project()
        │
        ├── scripts/   (family scripts + custom scripts + album-art fetchers)
        ├── images/    (resolved image props + Custom Lua asset_paths)
        ├── assets/    (same assets for Lua that expects ASSETS_DIR)
        ├── fonts/     (when collected)
        ├── render.lua (framework + per-node emission)
        ├── conky.conf (window + hooks + lua_load)
        ├── start.sh   (setsid, lock, daemon loops)
        ├── theme.json
        └── README.md

Live Preview uses the same builder path into a temp/workdir, then runs Conky — not a separate renderer.


1. Project model (source of truth)

Piece Role
NodeInstance id, type, x/y (editor only), props, z, visible, locked, click region fields
Edge src_nodedst_node + dst_prop (one wire per bindable prop)
CanvasSettings width, height, alignment, gaps, fps, stats_hz, window_type, transparency
Groups / labels Editor-only; ignored by codegen

Hidden visuals (visible: false) are skipped entirely by codegen. Unused native sources are not polled. Custom Script nodes in daemon mode are still launched by start.sh even when unwired (pure-Lua themes read their caches directly).


2. Script families & polling modes

Sources that need external scripts belong to a family (weather, gpu_stats, custom id, …).

Mode Who polls start.sh role
execi Conky (execi / equivalent in generated paths) No background loop for that family
daemon Background shell loop Initial run + while true; do sleep N; script; done under setsid
Album art Always daemon-style (writes an image file) Loop per album-art node interval

daemon_families_used(project) collects intervals from:

  • Wired sources with poll_mode == daemon
  • Every Album Art node
  • Unwired source.custom_script nodes with poll_mode == daemon (so sensors.sh / weather.sh still run after a pure-Lua import)

If several nodes share a family key, the shortest interval wins.

Custom Script build rules

Case Build behaviour
Inline script_body non-empty Written to scripts/; wins over path
Self-caching (self_caching prop or body/path matches CACHE_FILE / sensors.cache / weather.cache) Copied or written under original basename; no stdout-capture wrapper
Daemon, not self-caching Wrapper script captures stdout → custom_<id>.cache
Companion .conf Copied beside the script when path or importer recorded it
Cache path patch Known HOME/.cache/... patterns rewritten toward theme .runtime-cache at import when possible

Scripts write cache files. render.lua reads family caches with safe parsers so a missing file does not crash the draw. Custom Lua that still uses its own read_kv_cache(CACHE_DIR .. '/sensors.cache') relies on the daemon script, not on SRC[].


3. conky.conf

Generated by conky_conf_gen after session resolution:

Step Detail
window_type Canvas autodiscovery.resolve_window_type() (session-specific default, usually normal)
own_window* Undecorated / below / sticky / skip taskbar hints
size minimum_width/height from Canvas
timing update_interval from FPS
lua_load Self-locating: resolves render.lua next to the conf via debug.getinfo so the folder is relocatable
lua_draw_hook_post main_draw (draw after Conky’s own pass)
lua_mouse_hook Emitted only if the project has clickable nodes
empty conky.text All visible HUD content comes from Lua/Cairo

Optional Wayland/session warning is embedded as a conf comment when detection reports a problem.


4. render.lua structure

Rough layers:

FRAMEWORK_LUA          -- shared helpers, caches, surface, images
  + constants          -- THEME_DIR, SCRIPTS_DIR, IMAGES_DIR, CACHE_DIR, FPS
  + refresh_sources()  -- wired native / family / custom sources → SRC[]
  + draw_node_*()      -- per visible visual, including Custom Lua bodies
  + main_draw()        -- surface, refresh cadence, z-ordered draws
  + mouse_handler()    -- optional click regions

Custom Lua emission

Each visual.custom_lua becomes a draw_node_<id>(cr, W, H) that:

  1. cairo_save + translate by Offset X/Y
  2. Injects in1in12: bound prop → SRC['…'] expression; unwired → nil
  3. Runs the node’s code text (imported or hand-written)
  4. cairo_restore

Custom Lua is an escape hatch: it does not need edges to draw. Edges only matter if the author reads in1in12.

State management (in-Lua)

Store Purpose
IMAGE_CACHE PNG/SVG handles; false = known missing (warn once)
CACHE_KV Parsed script cache files, refreshed on source refresh
HIST Ring buffers for history graphs keyed by node id
SRC Per-source current values from refresh_sources()
Plugin tables e.g. smoothers when plugins are present

Logic nodes become expressions substituted into consumers. Evaluation order follows edge dependency.

Surface ownership

get_draw_surface()
  → conky_surface() if available (Conky owns → do NOT destroy)
  → else cairo_xlib_surface_create (we own → must destroy)

Wrong ownership caused historical segfaults; the framework encodes the branch explicitly.

Per-frame safety

  • pcall around the main draw and mouse handler
  • safe_number / safe_parse fallbacks
  • Missing cache → empty / previous value rather than aborting the whole HUD

5. start.sh — process model

Not OS threads inside Conky for daemon scripts; separate shell processes in one process group:

setsid
  ├── conky -c conky.conf
  ├── script_a.sh          (initial run)
  ├── (while sleep N; do script_a; done) &
  └── … same for other daemon families / custom scripts / album art
Mechanism Role
setsid Session leader; Manager can signal the whole group
PID file under $XDG_RUNTIME_DIR Single-instance lock; re-start replaces old HUD
DIR Theme directory (relocatable)
.runtime-cache Created up front for script/Lua caches

execi-only themes may have no poller loops — Conky alone is enough.

Studio’s Manager uses detached start on start.sh so themes survive quitting the editor.


6. Asset pipeline

Asset Behaviour
Image paths on visual props Copied into images/ when resolved at build
Custom Lua asset_paths Copied into assets/ and images/
Fonts Listed/copied when the build collects them
Script files Generated or copied into scripts/ with execute bits

Absolute host paths in the project JSON are a sharing hazard; the built folder is the portable artifact.


7. Live Preview vs Install

Path Output location Runner
Live Preview Temp / preview workdir Studio-controlled Conky process + log
Build to Folder User-chosen directory Manual ./start.sh
Build & Install ~/.config/conky/<name>/ Manager Start → start.sh

Same codegen; different destination and process ownership.


8. Mental model for authors

  1. Graph = what to sample and what to draw when using native nodes.
  2. Custom Lua / Custom Script = optional escape hatches; their text is the behaviour.
  3. Codegen = how Conky is told to run the graph every frame.
  4. start.sh = how long-running helpers stay alive beside Conky.
  5. Caches = async data into a sync draw loop without blocking Cairo.

There is no hidden Studio runtime on the user’s desktop after install — only Conky + shell + Lua.

Imported pure-Lua themes

  • Expect one (or few) Custom Lua nodes plus daemon Custom Scripts, not a full native vitals graph.
  • Data “works” when scripts run and paths point at .runtime-cache / assets/.
  • Wiring a Studio Bar does not change Custom Lua until you edit that Lua to use in1in12.

Clone this wiki locally