Skip to content

Theme Architecture Codegen

Bobby Comet edited this page Jul 31, 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/          (families used by the graph)
        ├── images/ fonts/    (copied assets)
        ├── 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 sources are not polled.


2. Script families & polling modes

Sources that need external scripts belong to a family (weather, sensors, 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 while true; do sleep N; script; done under setsid
Album art Always treated as daemon-style (writes an image file) Loop per album-art node interval

daemon_families_used(project) collects intervals from wired sources (and album art). If several nodes share a family, the shortest interval wins so data is never staler than requested.

Scripts write cache files (often key/value or single values). render.lua reads caches with safe parsers so a missing file (script not run yet) does not crash the draw.


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
  + plugin lua_helpers -- once per plugin type used
  + logic expressions  -- bound props as Lua expressions
  + visual draw bodies -- per visible visual node, z-order
  + main_draw()        -- refresh sources, create cr, draw all, destroy if needed
  + mouse_handler()    -- optional click regions

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
Plugin tables e.g. _cs_plugin_smooth[slot] for EMA / rate-limit / peak-hold

Logic nodes become expressions (or small IIFEs) substituted into consumers. Evaluation order follows edge dependency so inputs exist before use.

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. Works for X11 and Wayland-with-layer-shell builds that expose conky_surface().

Per-frame safety

  • pcall around parse/draw where appropriate
  • safe_number / safe_parse fallbacks
  • Missing cache → previous/zero 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_family_a.sh          (initial run)
  ├── (while sleep N; do script_family_a; done) &
  └── … same for other daemon families
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)

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

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


6. Asset pipeline

Asset Behaviour
Image paths in props Copied into images/ / assets when resolved at build
Fonts Listed/copied when the build collects them
Script files Generated or copied into scripts/ with mode 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.
  2. Codegen = how Conky is told to do it every frame.
  3. start.sh = how long-running helpers stay alive beside Conky.
  4. 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.

Clone this wiki locally