diff options
| author | Danilo M. <danix@danix.xyz> | 2026-09-16 19:29:10 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-09-16 19:29:10 +0200 |
| commit | e10bfebb1a908230244e33e970f4750d84ff1b35 (patch) | |
| tree | f94bf19541613b0a773b3f545985084355c91486 | |
| parent | 9c8157beb67018f5ecc94917d067b5c96aa8c328 (diff) | |
| download | conky-theme-udt-e10bfebb1a908230244e33e970f4750d84ff1b35.tar.gz conky-theme-udt-e10bfebb1a908230244e33e970f4750d84ff1b35.zip | |
fix: read the palette from the config file, and stop leaking extents
Two defects, both found by review of the Cairo work and both confirmed
by experiment rather than reasoning.
The palette mechanism could not have worked. conky_parse('${color3}')
returns an empty string: conky's colour variables emit renderer escape
codes into conky.text, they never evaluate to a hex string, and
${default_shade_color} is not a variable at all, so conky_parse hands
the literal text back. conky.config is not exposed to Lua either
(_G.conky is nil) and conky_info carries only cpu_count and
update_interval. What Lua does get is conky_config, the config file's
path, so the colours are now parsed out of that file, where UDT has
already substituted real hex. The pattern needs [%w_]+ rather than %w+
or it misses default_color and default_shade_color.
The card fill also duplicated the border colour, both being color3, so
filling at 0.55 alpha and stroking the hairline at 0.9 in one hue made
the border invisible and the cards read as blobs rather than the
mockup's thin outlines. The fill is default_shade_color now, a role
every scheme already defines.
card.measure allocated a cairo_text_extents_t per call, which leaks:
5000 allocations grow Lua's heap by 182KB that GC never reclaims, and
:destroy() on each does not help, measured at the same 182KB. One
reused module-level struct costs 0KB. A draw hook running every 2s
would have bled memory for as long as conky stayed up, which no
screenshot would ever reveal.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
| -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. |
