aboutsummaryrefslogtreecommitdiffstats
path: root/widgets
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-09-17 12:27:28 +0200
committerDanilo M. <danix@danix.xyz>2026-09-17 12:27:28 +0200
commit5013893b4cbb63277b2a4c92a0dc8b851816aa93 (patch)
treea0cd680dccee25b41e9f3b6a51c5ba284b7d7669 /widgets
parentd9c68ef7a62b45527cfb87d61e51866c5e2931a3 (diff)
downloadconky-theme-udt-5013893b4cbb63277b2a4c92a0dc8b851816aa93.tar.gz
conky-theme-udt-5013893b4cbb63277b2a4c92a0dc8b851816aa93.zip
feat: add the disks and cache cards
disks draws a ring per filesystem from the df sample, with the NFS server address read from the device field and a labelled-bar fallback when the rings would be too small to read. cache shows the ~/.cache total and its four largest children from the du sample. card.ring now resets the cairo path first: a preceding card.text() leaves a current point, and cairo_arc() joins to it with a straight line, so the disks rings were drawn with stray chords. The ring primitive had no caller until now, which is why it went unnoticed.
Diffstat (limited to 'widgets')
-rw-r--r--widgets/cache.lua63
-rw-r--r--widgets/disks.lua135
2 files changed, 198 insertions, 0 deletions
diff --git a/widgets/cache.lua b/widgets/cache.lua
new file mode 100644
index 0000000..a1a42e3
--- /dev/null
+++ b/widgets/cache.lua
@@ -0,0 +1,63 @@
+-- Cache: ~/.cache total and its four largest subdirectories.
+--
+-- Reads a cache file written by bin/cache-sample.sh. du walks the tree and
+-- costs about 100ms, which cannot happen on a 2-second draw.
+
+local card = require 'lib.card'
+local data = require 'lib.data'
+
+local CACHE = (os.getenv('XDG_CACHE_HOME') or (os.getenv('HOME') .. '/.cache'))
+ .. '/udt/cachesize.txt'
+
+local M = {}
+
+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 big_size = clamp(inner.w * 0.075, 22, 46)
+
+ card.font(cr, card.FONT_MONO, label_size, false)
+ card.rgba(cr, colors.label)
+ card.text(cr, inner.x, inner.y + label_size, 'CACHE')
+
+ local c = data.du_parse(data.slurp(CACHE) or '')
+ if not c then
+ card.rgba(cr, colors.label)
+ card.font(cr, card.FONT_UI, label_size * 1.2, true)
+ card.text(cr, inner.x, inner.y + label_size * 3.4, 'no cache data')
+ card.font(cr, card.FONT_MONO, label_size, false)
+ card.text(cr, inner.x, inner.y + label_size * 5, 'bin/cache-sample.sh')
+ return
+ end
+
+ card.font(cr, card.FONT_HEAVY, big_size, false)
+ card.rgba(cr, colors.body)
+ card.text(cr, inner.x, inner.y + label_size + big_size, c.total.label)
+
+ local ey = inner.y + label_size + big_size + label_size * 2
+ local step = label_size * 2.2
+
+ card.font(cr, card.FONT_MONO, label_size, false)
+ for i, e in ipairs(c.items) do
+ if ey + step > inner.y + inner.h then break end
+ -- The biggest offender is the point of the card, so it carries colour:
+ -- critical when it is over half the total, warning otherwise. The rest
+ -- stay in the ordinary value colour so the eye lands on the one to delete.
+ local colour = colors.value
+ if i == 1 then
+ local share = c.total.bytes > 0 and (e.bytes / c.total.bytes) or 0
+ colour = share > 0.5 and colors.critical or colors.warning
+ end
+ card.rgba(cr, colors.label)
+ -- Long names are truncated rather than allowed to collide with the size.
+ local name = e.name
+ if #name > 18 then name = name:sub(1, 17) .. '\u{2026}' end
+ card.text(cr, inner.x, ey, name)
+ card.rgba(cr, colour)
+ card.text_right(cr, inner.x + inner.w, ey, e.label)
+ ey = ey + step
+ end
+end
+
+return M
diff --git a/widgets/disks.lua b/widgets/disks.lua
new file mode 100644
index 0000000..aff3e5b
--- /dev/null
+++ b/widgets/disks.lua
@@ -0,0 +1,135 @@
+-- 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)
+
+ card.font(cr, card.FONT_MONO, label_size, false)
+ card.rgba(cr, colors.label)
+ card.text(cr, inner.x, inner.y + label_size, 'DISKS')
+
+ local fs = data.df_parse(data.slurp(CACHE) or '')
+ if #fs == 0 then
+ card.rgba(cr, colors.label)
+ card.font(cr, card.FONT_UI, label_size * 1.2, true)
+ card.text(cr, inner.x, inner.y + label_size * 3.4, 'no disk data')
+ card.font(cr, card.FONT_MONO, label_size, false)
+ card.text(cr, inner.x, inner.y + label_size * 5, 'bin/disks-sample.sh')
+ return
+ end
+
+ local top = inner.y + label_size * 2.2
+ local avail_h = (inner.y + inner.h) - top
+
+ -- Lay the rings out in a row, wrapping if the card is narrow. 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
+ local r = math.min(cell_w * 0.34, avail_h * 0.30)
+
+ 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
+ card.font(cr, card.FONT_MONO, clamp(r * 0.20, 9, 12), false)
+ card.rgba(cr, colors.label)
+ local sw_ = card.measure(cr, sub)
+ card.text(cr, cx - sw_ / 2, cy + r * 0.55, sub)
+ end
+
+ -- The percentage sits under the ring, in the threshold colour.
+ card.font(cr, card.FONT_MONO, label_size, false)
+ card.rgba(cr, colour)
+ local pct = string.format('%d%%', e.pct)
+ local pw = card.measure(cr, pct)
+ card.text(cr, cx - pw / 2, cy + r + label_size * 1.7, pct)
+ end
+ else
+ local step = label_size * 2.6
+ 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 + 6, inner.w, 6, e.pct / 100, colour, colors)
+ ey = ey + step
+ end
+ end
+end
+
+return M