aboutsummaryrefslogtreecommitdiffstats
path: root/widgets/system.lua
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-09-17 12:23:06 +0200
committerDanilo M. <danix@danix.xyz>2026-09-17 12:23:06 +0200
commit59211a1bc2912a3bf39b848e48fc2dfc4c1ecd8e (patch)
treebd7d631de1543843ff9448365b87a8d7cb2685d1 /widgets/system.lua
parenta2a06d5d3e5b4b2352b0b327aa5d558b48174ab9 (diff)
downloadconky-theme-udt-59211a1bc2912a3bf39b848e48fc2dfc4c1ecd8e.tar.gz
conky-theme-udt-59211a1bc2912a3bf39b848e48fc2dfc4c1ecd8e.zip
feat: add the system card with a per-core equaliser
Diffstat (limited to 'widgets/system.lua')
-rw-r--r--widgets/system.lua111
1 files changed, 111 insertions, 0 deletions
diff --git a/widgets/system.lua b/widgets/system.lua
new file mode 100644
index 0000000..c76b5b4
--- /dev/null
+++ b/widgets/system.lua
@@ -0,0 +1,111 @@
+-- System: CPU load as a per-core equaliser, memory, and every temperature.
+--
+-- The hwmon bindings and temperature ceilings below are host-specific, which
+-- is why they live here rather than in lib/data.lua: that file is the part
+-- that ports to another machine, this is the part that does not.
+
+local card = require 'lib.card'
+local data = require 'lib.data'
+
+local M = {}
+
+-- chip, file, label, warn, crit.
+--
+-- Ceilings are per sensor because a single pair cannot serve both: 70C is
+-- unremarkable on a CPU package and alarming on an NVMe. Starting values,
+-- easy to retune once real numbers under load are known.
+local TEMPS = {
+ { 'k10temp', 'temp1_input', 'CPU', 75, 90 },
+ { 'k10temp', 'temp3_input', 'CCD1', 75, 90 },
+ { 'nvme', 'temp1_input', 'NVME', 60, 70 },
+ { 'gigabyte_wmi', 'temp2_input', 'BOARD', 60, 70 },
+ { 'gigabyte_wmi', 'temp3_input', 'BOARD', 60, 70 },
+}
+
+-- One counter per core, created on first draw and kept for the process's life:
+-- load is a delta between samples, so the state has to outlive the frame.
+local counters = {}
+local total_counter = data.new_cpu_counter()
+
+function M.draw(cr, rect, colors)
+ local inner = card.card(cr, rect, colors)
+ local function clamp(v, lo, hi) return math.max(lo, math.min(hi, v)) end
+
+ local label_size = clamp(inner.w * 0.030, 11, 18)
+ local big_size = clamp(inner.w * 0.085, 26, 56)
+ local row_size = clamp(inner.w * 0.030, 11, 17)
+
+ local stat = data.slurp('/proc/stat')
+ local agg_total, agg_idle = data.cpu_times(stat or '')
+ local load = total_counter:sample(agg_total, agg_idle)
+
+ local y = inner.y
+
+ -- Aggregate load, the headline number.
+ card.font(cr, card.FONT_MONO, label_size, false)
+ card.rgba(cr, colors.label)
+ card.text(cr, inner.x, y + label_size, 'CPU')
+
+ card.font(cr, card.FONT_HEAVY, big_size, false)
+ card.rgba(cr, load and card.threshold(load, 25, 75, colors) or colors.label)
+ card.text(cr, inner.x, y + label_size + big_size,
+ load and string.format('%d%%', math.floor(load + 0.5)) or '--')
+
+ local ey = y + label_size + big_size + 14
+
+ -- The equaliser: one bar per core, rising from a common baseline.
+ local cores = data.per_cpu_times(stat or '')
+ local n = #cores
+ if n > 0 then
+ local gap = math.max(2, inner.w * 0.006)
+ local bw = (inner.w - gap * (n - 1)) / n
+ local eh = clamp(inner.h * 0.16, 24, 80)
+ -- Below about 3px a row of sixteen bars is an illegible smear; drop to the
+ -- aggregate bar instead of drawing one.
+ if bw >= 3 then
+ for i, c in ipairs(cores) do
+ counters[i] = counters[i] or data.new_cpu_counter()
+ local pct = counters[i]:sample(c.total, c.idle) or 0
+ card.vbar(cr, inner.x + (i - 1) * (bw + gap), ey + eh, bw, eh,
+ pct / 100, card.threshold(pct, 25, 75, colors), colors)
+ end
+ else
+ card.bar(cr, inner.x, ey + eh - 8, inner.w, 8, (load or 0) / 100,
+ card.threshold(load or 0, 25, 75, colors), colors)
+ end
+ ey = ey + eh + 16
+ end
+
+ -- Memory. MemAvailable, not free: it already excludes reclaimable cache, so
+ -- it is the figure a person recognises as "used".
+ local mem = data.mem_info(data.slurp('/proc/meminfo') or '')
+ if mem then
+ local frac = mem.used / mem.total
+ card.font(cr, card.FONT_MONO, row_size, false)
+ card.rgba(cr, colors.label)
+ card.text(cr, inner.x, ey, 'RAM')
+ card.rgba(cr, colors.value)
+ card.text_right(cr, inner.x + inner.w, ey,
+ string.format('%s / %s', card.human(mem.used * 1024), card.human(mem.total * 1024)))
+ ey = ey + 8
+ card.bar(cr, inner.x, ey, inner.w, 8, frac,
+ card.threshold(frac * 100, 25, 75, colors), colors)
+ ey = ey + 22
+ end
+
+ -- Temperatures, each against its own ceiling.
+ card.font(cr, card.FONT_MONO, row_size, false)
+ local step = row_size * 1.8
+ for _, t in ipairs(TEMPS) do
+ if ey + step > inner.y + inner.h then break end -- out of room: stop
+ local v = data.sensor(t[1], t[2])
+ card.rgba(cr, colors.label)
+ card.text(cr, inner.x, ey, t[3])
+ card.rgba(cr, v and card.threshold(v, t[4], t[5], colors) or colors.label)
+ card.text_right(cr, inner.x + inner.w, ey,
+ v and string.format('%d\u{00B0}', v) or '--')
+ ey = ey + step
+ end
+end
+
+return M