aboutsummaryrefslogtreecommitdiffstats
path: root/widgets/disks.lua
blob: 3b0f2210a22dce92547b33f68263c6e362546fa9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
-- 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

  -- 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
        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

      -- 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 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