diff options
Diffstat (limited to 'widgets')
| -rw-r--r-- | widgets/disks.lua | 150 | ||||
| -rw-r--r-- | widgets/gpu.lua | 121 | ||||
| -rw-r--r-- | widgets/network.lua | 181 | ||||
| -rw-r--r-- | widgets/system.lua | 198 |
4 files changed, 0 insertions, 650 deletions
diff --git a/widgets/disks.lua b/widgets/disks.lua deleted file mode 100644 index 3922188..0000000 --- a/widgets/disks.lua +++ /dev/null @@ -1,150 +0,0 @@ --- Disks: one ring per filesystem, after idea2.png. --- --- Reads a cache file written by bin/disks-sample.sh and never calls statfs --- itself: an unreachable NFS server blocks that call, and blocking the draw --- freezes the whole dashboard. - -local card = require 'lib.card' -local data = require 'lib.data' - -local M = {} - -local CACHE = (os.getenv('XDG_CACHE_HOME') or (os.getenv('HOME') .. '/.cache')) - .. '/udt/disks.txt' - --- mount -> centre glyph and the small text under it. --- --- The icon IS the label: no mount name is drawn, because a Slackware S or a --- house says which filesystem this is faster than the word does. Every --- codepoint here was taken from the old conky config and re-rendered to --- confirm it, rather than guessed: an earlier guess at the Slackware mark --- (F83C) turned out to draw a stomach, and a wrong-but-present codepoint --- fails plausibly rather than visibly. --- --- The two NFS shares are told apart by their server's address, which is read --- from the mount's device field at runtime rather than written here. The --- addresses belong on the screen, not in a public repo, and deriving them also --- means the card keeps working if the LAN is renumbered. It fits: at a 50px --- radius the usable inner width is about 75px and an address measures 58px at --- 10px type. --- --- A mount not listed still draws, with a generic disk glyph and the last path --- segment, so adding one to the sampler needs no change here. -local GLYPH = { - ['/'] = '\u{F318}', -- Slackware - ['/home'] = '\u{F015}', -- house - ['/data'] = '\u{F02CA}', -- hard disk -} -local NFS_GLYPH = '\u{F06F3}' -- network share -local FALLBACK_GLYPH = '\u{F02CA}' - -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, 10, 16) - - local fs = data.df_parse(data.slurp(CACHE) or '') - if #fs == 0 then - card.notice(cr, inner, colors, 'DISKS', 'no disk data', 'bin/disks-sample.sh') - return - end - - card.font(cr, card.FONT_MONO, label_size, false) - card.title(cr, inner.x, inner.y + label_size, card.ICON.disks, 'DISKS', colors) - - local top = inner.y + label_size * 2.2 - local avail_h = (inner.y + inner.h) - top - - -- All rings share one row: the cell width shrinks as mounts are added and - -- the ring radius follows it, so the layout never wraps. Below the radius - -- where a ring reads, fall back to labelled bars rather than drawing a row - -- of illegible dots. - local n = #fs - local per_row = n - local cell_w = inner.w / per_row - -- 0.27 rather than 0.30: two rows sit under each ring now, not one, and the - -- radius is what gives up the height. - local r = math.min(cell_w * 0.34, avail_h * 0.27) - - if r >= 18 then - for i, e in ipairs(fs) do - -- host is non-nil exactly for a network mount, since a local device is - -- a path and carries no 'server:' prefix. - local glyph = GLYPH[e.mount] or (e.host and NFS_GLYPH) or FALLBACK_GLYPH - local sub = e.host - if not sub and not GLYPH[e.mount] then - sub = (e.mount:match('([^/]+)/?$') or e.mount):upper() - end - local cx = inner.x + cell_w * (i - 0.5) - local cy = top + r + 6 - local colour = card.threshold(e.pct, 25, 75, colors) - - card.ring(cr, cx, cy, r, e.pct / 100, colour, colors) - - -- Glyph at the centre. Drawn at 0.55 of the radius rather than 0.7: the - -- network-share glyph carries a wide horizontal connector that runs past - -- the ring at larger sizes, which reads as a line joining the rings - -- together. - local gsize = r * 0.55 - card.font(cr, card.FONT_MONO, gsize, false) - card.rgba(cr, colors.label) - local gw = card.measure(cr, glyph) - -- Raised when there is text under it, centred when there is not. - card.text(cr, cx - gw / 2, cy + (sub and gsize * 0.10 or gsize * 0.38), glyph) - - -- The address, inside the ring beneath the glyph. Only the NFS shares - -- have one; the local mounts are identified by their icon alone. - if sub then - -- Tied to the card's own label scale rather than a fixed px floor, so - -- every piece of small mono text on the card tracks the same size. - card.font(cr, card.FONT_MONO, clamp(r * 0.20, label_size * 0.7, label_size), false) - card.rgba(cr, colors.label) - local sw_ = card.measure(cr, sub) - card.text(cr, cx - sw_ / 2, cy + r * 0.55, sub) - end - - -- Free on top, total beneath, both centred under the ring. - -- - -- The percentage that used to sit here is gone: the ring already draws - -- it as an angle, and a number repeating it costs a row in a column - -- about 75px wide. Free carries the threshold colour because it is the - -- measured quantity; the total is fixed, so it stays in `value`. One - -- coloured number per column, as the rest of the board reads. - local free_size = label_size - local total_size = label_size * 0.88 - card.font(cr, card.FONT_MONO, free_size, false) - card.rgba(cr, colour) - local free_txt = card.human(e.avail) .. ' free' - local fw = card.measure(cr, free_txt) - card.text(cr, cx - fw / 2, cy + r + free_size * 1.7, free_txt) - - card.font(cr, card.FONT_MONO, total_size, false) - card.rgba(cr, colors.value) - local total_txt = card.human(e.size) - local tw = card.measure(cr, total_txt) - card.text(cr, cx - tw / 2, cy + r + free_size * 1.7 + total_size * 1.4, total_txt) - end - else - local step = label_size * 2.6 - local bar_h = clamp(label_size * 0.5, 4, 8) - local ey = top + label_size - for _, e in ipairs(fs) do - if ey + step > inner.y + inner.h then break end - -- The bar fallback has no room for a ring, so it shows the glyph inline - -- followed by the address or the mount name. - local glyph = GLYPH[e.mount] or (e.host and NFS_GLYPH) or FALLBACK_GLYPH - local name = e.host or (e.mount:match('([^/]+)/?$') or e.mount):upper() - local colour = card.threshold(e.pct, 25, 75, colors) - card.font(cr, card.FONT_MONO, label_size, false) - card.rgba(cr, colors.label) - card.text(cr, inner.x, ey, glyph .. ' ' .. name) - card.rgba(cr, colour) - card.text_right(cr, inner.x + inner.w, ey, - string.format('%s / %s', card.human(e.used), card.human(e.size))) - card.bar(cr, inner.x, ey + label_size * 0.5, inner.w, bar_h, e.pct / 100, colour, colors) - ey = ey + step - end - end -end - -return M diff --git a/widgets/gpu.lua b/widgets/gpu.lua deleted file mode 100644 index abac856..0000000 --- a/widgets/gpu.lua +++ /dev/null @@ -1,121 +0,0 @@ --- GPU: Arc B580 temperatures, VRAM in use and fan speed. --- --- No utilisation figure, deliberately. The xe driver exposes no --- gpu_busy_percent and intel_gpu_top refuses the device outright ("Detected Xe --- device which is not supported"). VRAM comes from DRM fdinfo, the per-client --- counters gputop itself reads (see data.drm_vram). --- --- The integrated AMD GPU does expose gpu_busy_percent and is deliberately not --- shown: the discrete card is the one in use. - -local card = require 'lib.card' -local data = require 'lib.data' - -local M = {} - --- The B580's VRAM. fdinfo reports what clients hold, not the card's size, and --- xe puts no capacity in sysfs. ponytail: hardcoded like the 'ARC B580' label. -local VRAM_TOTAL = 12 * 1024 ^ 3 - --- fdinfo is a popen over every fd on the system, so sample it every ~2s, not --- every frame. -local gate = data.new_gate(1.9) -local vram - --- chip, file, label, warn, crit. Package (temp2) is the headline above, so it --- is not repeated here; these are the rows drawn under it. -local TEMPS = { - { 'xe', 'temp3_input', 'VRAM', 80, 90 }, -} - -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 - - if gate() then vram = data.drm_vram() end - - local pkg = data.sensor('xe', 'temp2_input') - local big_txt = pkg and string.format('%d\u{00B0}', pkg) or '--' - - -- Fluid type. One base size S drives every element. The cell's height sets - -- how large the content grows; the measured width fit pulls S back down - -- only where the header or a row would overrun the card. So a tall cell - -- gets larger type instead of empty space, and nothing ever collides. - local LABEL_F, ROW_F = 0.32, 0.34 - local row_n = #TEMPS + 2 -- VRAM + MEM + FAN - local S_h = inner.h * 0.94 / (2.088 + row_n * 0.646 + 0.3) -- + MEM bar - local groups = { - { { card.title_str(card.ICON.gpu, 'GPU'), card.FONT_MONO, LABEL_F }, - { big_txt, card.FONT_HEAVY, 1.0 } }, - { { 'ARC B580', card.FONT_MONO, LABEL_F } }, - } - for _, t in ipairs(TEMPS) do - groups[#groups + 1] = { { t[3], card.FONT_MONO, ROW_F }, - { '888\u{00B0}', card.FONT_MONO, ROW_F } } - end - groups[#groups + 1] = { { 'MEM', card.FONT_MONO, ROW_F }, - { '100%', card.FONT_MONO, ROW_F } } - groups[#groups + 1] = { { 'FAN', card.FONT_MONO, ROW_F }, - { '8888 RPM', card.FONT_MONO, ROW_F } } - local S = clamp(math.min(S_h, card.fit_unit(cr, inner.w * 0.96, groups, 100)), 10, 72) - local label_size = S * LABEL_F - local row_size = S * ROW_F - - local y = inner.y - - -- The package temperature is the headline, since there is no load to show. - -- It shares the header line with the label, set on the same baseline and - -- right-aligned, so the card's width carries the number rather than leaving - -- the right half empty under a left-set value. - card.font(cr, card.FONT_HEAVY, S, false) - card.rgba(cr, pkg and card.threshold(pkg, 75, 85, colors) or colors.label) - card.text_right(cr, inner.x + inner.w, y + S, big_txt) - - card.font(cr, card.FONT_MONO, label_size, false) - card.title(cr, inner.x, y + label_size, card.ICON.gpu, 'GPU', colors) - - card.rgba(cr, colors.value) - card.text(cr, inner.x, y + S + label_size * 1.7, 'ARC B580') - - local ey = y + S + label_size * 3.4 - local step = row_size * 1.9 - - card.font(cr, card.FONT_MONO, row_size, false) - for _, t in ipairs(TEMPS) do - if ey + step > inner.y + inner.h then break end - 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 - - -- VRAM as a fraction of the card's, a bar under the row like RAM in system. - local bar_h = S * 0.12 - if ey + step + bar_h + S * 0.1 <= inner.y + inner.h then - local frac = vram and vram / VRAM_TOTAL - local colour = frac and card.threshold(frac * 100, 25, 75, colors) or colors.label - card.rgba(cr, colors.label) - card.text(cr, inner.x, ey, 'MEM') - card.rgba(cr, colour) - card.text_right(cr, inner.x + inner.w, ey, - frac and string.format('%d%%', math.floor(frac * 100 + 0.5)) or '--') - card.bar(cr, inner.x, ey + row_size * 0.6, inner.w, bar_h, frac, colour, colors) - ey = ey + step + bar_h + S * 0.1 - end - - -- Fan RPM is not a temperature, so it carries no threshold colour: a fast - -- fan is the cooling working, not a fault. - if ey + step <= inner.y + inner.h then - local rpm = data.sensor_raw('xe', 'fan1_input') - card.rgba(cr, colors.label) - card.text(cr, inner.x, ey, 'FAN') - card.rgba(cr, colors.value) - card.text_right(cr, inner.x + inner.w, ey, - rpm and string.format('%d RPM', rpm) or '--') - end -end - -return M diff --git a/widgets/network.lua b/widgets/network.lua deleted file mode 100644 index 20b0573..0000000 --- a/widgets/network.lua +++ /dev/null @@ -1,181 +0,0 @@ --- Network: throughput as two lines on one chart, with the LAN and public --- addresses. --- --- The rates come from the interface's own byte counters, read every draw: two --- file reads, no subprocess. The public address comes from a cache file, --- because a network call in the draw hook would freeze the dashboard when it --- hung. - -local card = require 'lib.card' -local data = require 'lib.data' - -local M = {} - --- The interface to graph. br0 is this host's bridge, matching the old text --- config. --- --- Note what this means: a bridge carries VM-to-host traffic that never reaches --- the router, so a local copy spikes the graph. That was true of the old --- config too and is accepted; this constant is the one edit that changes it. -local IFACE = 'br0' - -local CACHE = (os.getenv('XDG_CACHE_HOME') or (os.getenv('HOME') .. '/.cache')) - .. '/udt/pubip.txt' - --- A public address older than this is not shown. Eight sampling intervals: by --- then the sampler has failed repeatedly, and an address that may no longer be --- yours displayed with confidence is worse than '--'. -local STALE_AFTER = 4 * 3600 - -local STAT = '/sys/class/net/' .. IFACE .. '/statistics/' - --- Module state: the history outlives the frame, which is the whole point. --- It does not outlive a conky restart, so the chart starts empty and fills --- left to right. -local rx_counter, tx_counter -local rx_hist, tx_hist = {}, {} -local hist_len = 0 -local lan_addr = nil - --- Sampled every ~2s whatever the draw rate, so the chart keeps one point per --- sample rather than per draw. 1.9 not 2: draws jitter by a few ms. -local gate = data.new_gate(1.9) -local down, up = nil, nil - --- Append to a fixed-length history, dropping the oldest. A plain array with --- table.remove(1) rather than a circular buffer with an index: the lengths --- here are a few hundred at most and the draw is every two seconds, so the --- O(n) shift is free and the array is already in draw order. -local function push(hist, v, len) - hist[#hist + 1] = v - while #hist > len do table.remove(hist, 1) end -end - -local function read_counter(file) - local s = data.slurp(STAT .. file) - if not s then return nil end - return tonumber(s:match('^%s*(%d+)')) -end - --- Bytes per second to a short string. Deliberately not card.human: a rate --- wants a '/s' and one decimal at most, and reusing card.human would put a --- suffix meant for capacity onto a speed. -local function rate_str(bps) - if not bps then return '--' end - local units = { 'B', 'K', 'M', 'G' } - local n, i = bps, 1 - while n >= 1024 and i < #units do n = n / 1024; i = i + 1 end - if i == 1 then return string.format('%d%s/s', math.floor(n), units[i]) end - if n >= 100 then return string.format('%.0f%s/s', n, units[i]) end - return string.format('%.1f%s/s', n, units[i]) -end - -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 interval = (conky_info and conky_info.update_interval) or 2 - rx_counter = rx_counter or data.new_rate_counter(interval) - tx_counter = tx_counter or data.new_rate_counter(interval) - - local sampled, now = gate() - if sampled then - down = rx_counter:sample(read_counter('rx_bytes'), now) - up = tx_counter:sample(read_counter('tx_bytes'), now) - end - - -- Fluid type, as every other card does: one base size S drives everything, - -- taken as the smaller of a height budget and a measured width fit. - local LABEL_F, BIG_F, ROW_F = 0.30, 1.0, 0.30 - local groups = { - { { card.title_str(card.ICON.network, 'NET'), card.FONT_MONO, LABEL_F }, - { '888.8M/s', card.FONT_HEAVY, BIG_F } }, - { { '\u{F0318} 192.000.000.000', card.FONT_MONO, ROW_F } }, - { { '\u{F01DA} 888.8M/s', card.FONT_MONO, ROW_F }, - { '\u{F0552} 888.8M/s', card.FONT_MONO, ROW_F } }, - } - local fixed_units = 1.0 + ROW_F * 2.2 * 3 - local S = clamp(math.min(inner.h * 0.96 / (fixed_units + 1.2), - card.fit_unit(cr, inner.w * 0.96, groups, 100)), 10, 72) - local label_size = S * LABEL_F - local row_size = S * ROW_F - - -- Header: the label left, the download rate as the big value at the right. - -- Download is the headline because it is the number that moves. - card.font(cr, card.FONT_HEAVY, S * BIG_F, false) - card.rgba(cr, colors.body) - card.text_right(cr, inner.x + inner.w, inner.y + S * BIG_F, rate_str(down)) - - card.font(cr, card.FONT_MONO, label_size, false) - card.title(cr, inner.x, inner.y + label_size, card.ICON.network, 'NET', colors) - - local ey = inner.y + S * BIG_F + row_size * 1.4 - local step = row_size * 2.2 - - -- The addresses. LAN is memoized on first success and retried while nil: a - -- bridge may not be up when conky starts. - lan_addr = lan_addr or data.iface_addr(IFACE) - card.font(cr, card.FONT_MONO, row_size, false) - card.rgba(cr, colors.label) - card.text(cr, inner.x, ey, '\u{F0318}') -- LAN - card.rgba(cr, colors.value) - card.text_right(cr, inner.x + inner.w, ey, lan_addr or '--') - ey = ey + step - - local pub = data.kv_parse(data.slurp(CACHE) or '') - local fetched = tonumber(pub.fetched) - local pub_txt = '--' - if pub.ip and fetched and (os.time() - fetched) < STALE_AFTER then - pub_txt = pub.ip - end - card.rgba(cr, colors.label) - card.text(cr, inner.x, ey, '\u{F059F}') -- globe - card.rgba(cr, pub_txt == '--' and colors.label or colors.value) - card.text_right(cr, inner.x + inner.w, ey, pub_txt) - ey = ey + step * 1.1 - - -- The chart takes the room left between the addresses and the rate row. - local rate_row_h = row_size * 2.4 - local chart_top = ey - local chart_h = (inner.y + inner.h) - chart_top - rate_row_h - if chart_h > 12 then - -- One sample per pixel column, so the chart never interpolates. The - -- buffer is resized when the card is, keeping what it can: a moved card - -- should not clear the history. - local want = math.max(8, math.floor(inner.w)) - if want ~= hist_len then - hist_len = want - while #rx_hist > hist_len do table.remove(rx_hist, 1) end - while #tx_hist > hist_len do table.remove(tx_hist, 1) end - end - if sampled and down then push(rx_hist, down, hist_len) end - if sampled and up then push(tx_hist, up, hist_len) end - - -- One ceiling for both series. Scaled independently they would lie about - -- their relative size, which is the entire reason to draw them together. - local peak = 0 - for _, v in ipairs(rx_hist) do if v > peak then peak = v end end - for _, v in ipairs(tx_hist) do if v > peak then peak = v end end - - card.plot(cr, inner.x, chart_top, inner.w, chart_h, { - { values = rx_hist, colour = colors.ok }, - { values = tx_hist, colour = colors.highlight }, - }, peak, colors) - - -- The peak, so a full-height line means something in absolute terms. - card.font(cr, card.FONT_MONO, row_size * 0.85, false) - card.rgba(cr, colors.label) - card.text(cr, inner.x, chart_top + row_size * 0.85, rate_str(peak)) - end - - -- The live rates, each in its series colour so the line and the number are - -- unmistakably the same thing. - local ry = inner.y + inner.h - row_size * 0.4 - card.font(cr, card.FONT_MONO, row_size, false) - card.rgba(cr, colors.ok) - card.text(cr, inner.x, ry, '\u{F01DA} ' .. rate_str(down)) - card.rgba(cr, colors.highlight) - card.text_right(cr, inner.x + inner.w, ry, '\u{F0552} ' .. rate_str(up)) -end - -return M diff --git a/widgets/system.lua b/widgets/system.lua deleted file mode 100644 index c1cdf67..0000000 --- a/widgets/system.lua +++ /dev/null @@ -1,198 +0,0 @@ --- 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() - --- Sampled every ~2s whatever the draw rate; the values between samples are --- the last ones read. 1.9 not 2: draws jitter by a few ms either way. -local gate = data.new_gate(1.9) -local load, pcts = nil, {} - --- Read once, not per frame. Neither can change without a reboot, and --- re-reading /proc/cpuinfo every two seconds for a constant is waste: the file --- repeats its model-name line once per thread. --- --- `false` is the cached miss, distinct from nil meaning "not looked up yet", --- so an absent DMI table is not re-read on every draw. -local cpu_name, board = nil, nil - -local function hardware() - if cpu_name == nil then - cpu_name = data.cpu_model(data.slurp('/proc/cpuinfo') or '') or false - end - if board == nil then - local dmi = '/sys/devices/virtual/dmi/id/' - board = data.board_name(data.slurp(dmi .. 'board_vendor'), - data.slurp(dmi .. 'board_name')) or false - end - return cpu_name or nil, board or nil -end - -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 - - if gate() then - local stat = data.slurp('/proc/stat') or '' - load = total_counter:sample(data.cpu_times(stat)) - for i, c in ipairs(data.per_cpu_times(stat)) do - counters[i] = counters[i] or data.new_cpu_counter() - pcts[i] = counters[i]:sample(c.total, c.idle) or 0 - end - end - local load_txt = load and string.format('%d%%', math.floor(load + 0.5)) or '--' - local temp_n = #TEMPS - local cpu_txt, board_txt = hardware() - - -- Fluid type. One base size S drives the label, headline, RAM and every - -- temperature row; the equaliser then absorbs the vertical room left over, - -- so the card fills its cell and S shrinks only where a row would overrun - -- the width. card.fit_unit supplies the width side of that. - local LABEL_F, BIG_F, ROW_F = 0.30, 1.0, 0.32 - local groups = { - { { card.title_str(card.ICON.system, 'CPU'), card.FONT_MONO, LABEL_F }, - { load_txt, card.FONT_HEAVY, BIG_F } }, - { { cpu_txt or '', card.FONT_MONO, ROW_F } }, - { { board_txt or '', card.FONT_MONO, ROW_F } }, - { { 'RAM', card.FONT_MONO, ROW_F }, { '999.9G / 999.9G', card.FONT_MONO, ROW_F } }, - } - for _, t in ipairs(TEMPS) do - groups[#groups + 1] = { { t[3], card.FONT_MONO, ROW_F }, - { '888\u{00B0}', card.FONT_MONO, ROW_F } } - end - -- Height the non-equaliser content needs, in units of S, plus a reserve for - -- the equaliser band. The smaller of that budget and the width fit is S. - -- The two identification rows come out of the equaliser's budget: it is the - -- card's designated slack absorber, so it is what gives up the height. - local id_rows = (cpu_txt and 1 or 0) + (board_txt and 1 or 0) - local fixed_units = 1.00 + 1.13 + temp_n * ROW_F * 1.8 + 0.10 - + id_rows * ROW_F * 1.5 - local S = clamp(math.min(inner.h * 0.96 / (fixed_units + 1.7), - card.fit_unit(cr, inner.w * 0.96, groups, 100)), 10, 72) - local label_size = S * LABEL_F - local big_size = S * BIG_F - local row_size = S * ROW_F - - local y = inner.y - - -- Aggregate load, the headline number. It takes the header line, right - -- aligned like the big value on every other card, with the label top-left. - 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_right(cr, inner.x + inner.w, y + big_size, load_txt) - - card.font(cr, card.FONT_MONO, label_size, false) - card.title(cr, inner.x, y + label_size, card.ICON.system, 'CPU', colors) - - -- The hardware this card is about, under the header. Truncated by character, - -- never by byte: a model string can carry a multi-byte character and half of - -- one draws as a replacement box. - -- Two accents rather than one label colour: the rows name two different - -- pieces of hardware, and giving each its own hue separates them at a glance - -- without a prefix like 'CPU:' eating the width. `heading` and `highlight` - -- are the two roles that carry no state meaning, so they can stand for an - -- identity; ok/warning/critical could not, since those say how something is - -- doing and these strings never change. - local id_y = y + big_size + row_size * 0.2 - if cpu_txt or board_txt then - card.font(cr, card.FONT_MONO, row_size, false) - local id_limit = math.max(8, math.floor(inner.w / (row_size * 0.55))) - if cpu_txt then - card.rgba(cr, colors.heading) - card.text(cr, inner.x, id_y, card.truncate(cpu_txt, id_limit)) - id_y = id_y + row_size * 1.5 - end - if board_txt then - card.rgba(cr, colors.highlight) - card.text(cr, inner.x, id_y, card.truncate(board_txt, id_limit)) - id_y = id_y + row_size * 1.5 - end - end - - -- The equaliser: one bar per core, rising from a common baseline. It takes - -- the room left between the header and the RAM/temperature block, so it is - -- the card's slack absorber. - -- Below the identification rows when there are any, otherwise where it was. - local eq_top = (cpu_txt or board_txt) and (id_y + S * 0.10) or (y + big_size + S * 0.30) - local bar_h = S * 0.18 - local step = row_size * 1.8 - -- The floor was a flat 18px. With the identification rows above it the - -- equaliser can genuinely run out of room, and a fixed floor means it keeps - -- height it does not have and the temperature rows draw over it. Zero is a - -- legitimate outcome: the guard below skips the band when it has no height. - local eh = clamp((inner.y + inner.h) - eq_top - (S * 1.13 + temp_n * step), - 0, math.huge) - local n = #pcts - if n > 0 and eh >= 8 then - local gap = math.max(2, inner.w * 0.006) - local bw = (inner.w - gap * (n - 1)) / n - -- 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, pct in ipairs(pcts) do - card.vbar(cr, inner.x + (i - 1) * (bw + gap), eq_top + eh, bw, eh, - pct / 100, card.threshold(pct, 25, 75, colors), colors) - end - else - card.bar(cr, inner.x, eq_top + eh - bar_h, inner.w, bar_h, (load or 0) / 100, - card.threshold(load or 0, 25, 75, colors), colors) - end - end - local ey = eq_top + eh + S * 0.30 - - -- 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 + S * 0.30 - card.bar(cr, inner.x, ey, inner.w, bar_h, frac, - card.threshold(frac * 100, 25, 75, colors), colors) - ey = ey + bar_h + S * 0.35 - end - - -- Temperatures, each against its own ceiling. - card.font(cr, card.FONT_MONO, row_size, false) - 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 |
