diff options
| -rw-r--r-- | docs/superpowers/plans/2026-09-16-conky-lua-dashboard.md | 64 | ||||
| -rw-r--r-- | lib/card.lua | 19 |
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. |
