aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/superpowers/plans/2026-09-16-conky-lua-dashboard.md64
-rw-r--r--lib/card.lua19
2 files changed, 67 insertions, 16 deletions
diff --git a/docs/superpowers/plans/2026-09-16-conky-lua-dashboard.md b/docs/superpowers/plans/2026-09-16-conky-lua-dashboard.md
index fee49b4..2055224 100644
--- a/docs/superpowers/plans/2026-09-16-conky-lua-dashboard.md
+++ b/docs/superpowers/plans/2026-09-16-conky-lua-dashboard.md
@@ -26,6 +26,8 @@ These were verified experimentally on this machine. Each one invalidates an appr
**6. `print()` from Lua reaches Conky's stdout.** Run Conky in the foreground to see it. This is the only debugging channel.
+**7. Lua cannot read the palette through `conky_parse`.** `conky_parse('${color3}')` returns an **empty string**: colour variables emit escape codes into the text renderer rather than evaluating to hex. `${default_shade_color}` is not a variable at all and comes back as literal text. `conky.config` is not exposed either (`_G.conky` is `nil`), and `conky_info` holds only `cpu_count` and `update_interval`. What Lua does get is **`conky_config`**, the config file's path, so `dashboard.lua` reads that file and parses the hex out of it. All four behaviours were checked experimentally.
+
---
## File structure
@@ -574,21 +576,51 @@ local function hex(s, fallback)
return { tonumber(r, 16) / 255, tonumber(g, 16) / 255, tonumber(b, 16) / 255 }
end
--- Palette, read once per draw from the Conky colour slots the template filled.
+-- The colours, read out of the config file itself.
+--
+-- NOT via conky_parse('${color3}'): that returns an EMPTY STRING. Conky's
+-- colour variables emit renderer escape codes into conky.text, they do not
+-- evaluate to a hex string, and ${default_shade_color} is not a variable at all
+-- (conky_parse hands the literal text straight back). Verified experimentally;
+-- both were checked before this approach was chosen.
+--
+-- conky.config is also not exposed to Lua (_G.conky is nil) and conky_info
+-- carries only cpu_count and update_interval. What Lua does get is
+-- conky_config, the path of the config file, so the colours are parsed out of
+-- the rendered file. UDT has already substituted real hex into it by then.
+--
+-- Read once at load, not per frame: the file cannot change without a conky
+-- restart, since conky never rereads its config.
+local function config_colors()
+ local f = io.open(conky_config, 'r')
+ if not f then return {} end
+ local src = f:read('*a')
+ f:close()
+ local c = {}
+ -- [%w_]+ not %w+: default_color and default_shade_color carry underscores.
+ for k, v in src:gmatch("([%w_]+)%s*=%s*'(#%x%x%x%x%x%x)'") do c[k] = v end
+ return c
+end
+
+local CFG = config_colors()
+
local function palette()
return {
- heading = hex(conky_parse('${color1}')),
- label = hex(conky_parse('${color2}')),
- border = hex(conky_parse('${color3}')),
- rule = hex(conky_parse('${color3}')),
- value = hex(conky_parse('${color4}')),
- highlight = hex(conky_parse('${color5}')),
- ok = hex(conky_parse('${color6}')),
- critical = hex(conky_parse('${color7}')),
- body = hex(conky_parse('${color}')),
- -- The card fill. Derived from the body background rather than given its own
- -- role, so a scheme switch cannot leave the cards mismatched.
- surface = hex(conky_parse('${color3}')),
+ heading = hex(CFG.color1),
+ label = hex(CFG.color2),
+ border = hex(CFG.color3),
+ rule = hex(CFG.color3),
+ value = hex(CFG.color4),
+ highlight = hex(CFG.color5),
+ ok = hex(CFG.color6),
+ critical = hex(CFG.color7),
+ body = hex(CFG.default_color),
+ -- The card fill, from default_shade_color (@BODY_SHADE@, `mantle` in
+ -- Macchiato). It must NOT reuse color3: that is the border, and filling and
+ -- stroking a card in one hue makes the hairline invisible, so the cards
+ -- read as blobs instead of the mockup's thin outlines. Every scheme already
+ -- defines body_shade, so this needs no palette change.
+ surface = hex(CFG.default_shade_color),
}
end
@@ -1027,6 +1059,12 @@ package.preload['cairo'] = function() return {} end
package.preload['lib.card'] = function() return {} end
conky_window = nil
function conky_parse(s) return '#000000' end
+-- dashboard.lua parses its colours out of the config file at load time, so
+-- conky_config must point at something readable. The rendered conky.conf is
+-- gitignored and may not exist, so aim the stub at the template: it parses to
+-- no colours (its values are still @PLACEHOLDER@), which is fine here because
+-- this test only exercises the grid maths.
+conky_config = 'conky.conf.in'
dofile('dashboard.lua')
local G = conky_dashboard_internal
diff --git a/lib/card.lua b/lib/card.lua
index 320d3af..474228d 100644
--- a/lib/card.lua
+++ b/lib/card.lua
@@ -30,10 +30,23 @@ function M.text(cr, x, y, s)
end
-- Measured width and height of a string under the current font.
+-- One reused extents struct for every measurement, allocated at load.
+--
+-- Not per call: cairo_text_extents_t:create() leaks. 5000 allocations grow
+-- Lua's heap by ~182KB that collectgarbage() never reclaims, and calling
+-- :destroy() on each one does NOT help (measured: same 182KB either way).
+-- Reusing a single struct costs 0KB. In a draw hook running every 2s with
+-- several measured strings per frame, the per-call version bleeds memory for as
+-- long as conky is up, which is exactly the kind of fault a screenshot cannot
+-- show.
+--
+-- Safe because the draw hook is single-threaded and each measure() consumes the
+-- values before the next call overwrites them.
+local extents = cairo_text_extents_t:create()
+
function M.measure(cr, s)
- local e = cairo_text_extents_t:create()
- cairo_text_extents(cr, s, e)
- return e.width, e.height
+ cairo_text_extents(cr, s, extents)
+ return extents.width, extents.height
end
-- Right-aligned text: x is the RIGHT edge.