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
|
-- Render a widget to a PNG without conky.
--
-- Conky ships its Cairo bindings as a loadable module, so a widget's draw()
-- can be called against an image surface from plain Lua. That turns
-- edit-install-restart-toggle-screenshot into one command, which is how the
-- weather card's proportions were fixed.
--
-- lua test/render.lua weather 16 12 3 5 /tmp/w.png
--
-- Caveat: this crops to the single card. It says nothing about how the card
-- looks NEXT to its neighbours, which is a real failure mode (the weather
-- card's type once looked fine here and tiny beside the clock). Check the
-- live dashboard before believing anything about relative size.
package.cpath = '/usr/lib64/conky/lib?.so;' .. package.cpath
package.path = './?.lua;' .. package.path
require 'cairo'
local widget_name = arg[1] or error('usage: render.lua <widget> <cols> <rows> <w> <h> <out.png>')
local COLS = tonumber(arg[2]) or 8
local ROWS = tonumber(arg[3]) or 6
local wspan = tonumber(arg[4]) or 2
local hspan = tonumber(arg[5]) or 3
local out = arg[6] or '/tmp/widget.png'
local GAP, MARGIN = 16, 28
local sw, sh = 2560, 1080
-- The palette, read from the rendered conky.conf exactly as dashboard.lua
-- does, so the PNG uses the real scheme rather than invented colours.
local function config_colors()
local home = os.getenv('HOME')
if not home then return {} end
local path = home .. '/.config/conky/conky.conf'
local f = io.open(path, 'r')
if not f then return {} end
local src = f:read('*a')
f:close()
local c = {}
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 hex(s, fallback)
local r, g, b = tostring(s or ''):match('^#?(%x%x)(%x%x)(%x%x)$')
if not r then return fallback or { 1, 0, 1 } end
return { tonumber(r, 16) / 255, tonumber(g, 16) / 255, tonumber(b, 16) / 255 }
end
local colors = {
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),
warning = hex(CFG.color8, { 0.88, 0.69, 0.41 }),
body = hex(CFG.default_color),
surface = hex(CFG.default_shade_color),
}
local cw = (sw - MARGIN * 2 - GAP * (COLS - 1)) / COLS
local ch = (sh - MARGIN * 2 - GAP * (ROWS - 1)) / ROWS
local rect = {
x = MARGIN, y = MARGIN,
w = cw * wspan + GAP * (wspan - 1),
h = ch * hspan + GAP * (hspan - 1),
}
local surf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, rect.w + 40, rect.h + 40)
local cr = cairo_create(surf)
-- A backdrop close to the dashboard's own, so contrast reads honestly.
local bg = colors.surface
cairo_set_source_rgb(cr, bg[1] * 0.6, bg[2] * 0.6, bg[3] * 0.7)
cairo_paint(cr)
cairo_translate(cr, 20 - rect.x, 20 - rect.y)
local ok, mod = pcall(require, 'widgets.' .. widget_name)
if not ok then
print('cannot load widget ' .. widget_name .. ': ' .. tostring(mod))
os.exit(1)
end
local drew, err = pcall(mod.draw, cr, rect, colors)
if not drew then
print('DRAW ERROR: ' .. tostring(err))
os.exit(1)
end
cairo_destroy(cr)
cairo_surface_write_to_png(surf, out)
cairo_surface_destroy(surf)
print(string.format('%s at %dx%d on a %dx%d grid -> %s', widget_name, wspan, hspan, COLS, ROWS, out))
|