diff options
| author | Danilo M. <danix@danix.xyz> | 2026-09-16 19:31:42 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-09-16 19:31:42 +0200 |
| commit | 0192bc5f17cf4c8868aea81ca93318130246b4d5 (patch) | |
| tree | a95fd08b8d508bda1f12c7e84585b5a586f3c827 /dashboard.lua | |
| parent | e10bfebb1a908230244e33e970f4750d84ff1b35 (diff) | |
| download | conky-theme-udt-0192bc5f17cf4c8868aea81ca93318130246b4d5.tar.gz conky-theme-udt-0192bc5f17cf4c8868aea81ca93318130246b4d5.zip | |
feat: draw to a Wayland Cairo surface with an error overlay
Entry point, grid maths, palette parsing and widget dispatch.
Uses conky_surface(), not cairo_xlib_surface_create: under
out_to_wayland the Xlib drawable and visual are both nil, so the idiom
every Conky-Lua tutorial uses cannot work here.
The pcall overlay lands before any widget exists, because Conky reports
a Lua fault as a blank screen with no message on any stream. Verified
by injecting a fault and seeing it reported. A missing widget names
itself in its own cell rather than leaving the cell empty.
conky.conf here is a development config with literal colours; the
templated one arrives with the UDT wiring.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'dashboard.lua')
| -rw-r--r-- | dashboard.lua | 154 |
1 files changed, 154 insertions, 0 deletions
diff --git a/dashboard.lua b/dashboard.lua new file mode 100644 index 0000000..b814be7 --- /dev/null +++ b/dashboard.lua @@ -0,0 +1,154 @@ +-- Conky Lua dashboard: entry point. +-- +-- Drawn entirely with Cairo in lua_draw_hook_post; conky.text is empty because +-- Cairo output covers it. + +-- Resolve requires relative to this file's directory, since Conky's working +-- directory is wherever it was launched from, not the config's location. +local here = debug.getinfo(1, 'S').source:match('^@(.*/)') or './' +package.path = here .. '?.lua;' .. package.path + +require 'cairo' + +local card = require 'lib.card' + +-- === Layout =============================================================== +-- Order and position are yours: edit this table, nothing else. +-- col/row are grid cells, w/h span cells. Cell size is derived from the +-- surface, so the same table works on a 2560x1080 and a 1920x1080 screen. +local COLS, ROWS = 4, 2 + +local layout = { + { widget = 'clock', col = 1, row = 1, w = 1, h = 2 }, +} +-- ========================================================================== + +local GAP = 16 -- gap between cards, px +local MARGIN = 28 -- outer margin, px + +-- #rrggbb -> {r, g, b} as 0-1 floats, which is what Cairo wants. +-- UDT's gen_conky emits hex6, so the conversion lives here rather than in the +-- other repo's renderer. +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 -- magenta: visibly wrong + return { tonumber(r, 16) / 255, tonumber(g, 16) / 255, tonumber(b, 16) / 255 } +end + +-- The colours, read out of the config file itself. +-- +-- NOT via conky_parse('${color3}'): that returns an EMPTY STRING. Conky's +-- colour variables emit renderer escape codes into conky.text, they do not +-- evaluate to a hex string, and ${default_shade_color} is not a variable at all +-- (conky_parse hands the literal text straight back). Verified experimentally; +-- both were checked before this approach was chosen. +-- +-- conky.config is also not exposed to Lua (_G.conky is nil) and conky_info +-- carries only cpu_count and update_interval. What Lua does get is +-- conky_config, the path of the config file, so the colours are parsed out of +-- the rendered file. UDT has already substituted real hex into it by then. +-- +-- Read once at load, not per frame: the file cannot change without a conky +-- restart, since conky never rereads its config. +local function config_colors() + local f = io.open(conky_config, 'r') + if not f then return {} end + local src = f:read('*a') + f:close() + local c = {} + -- [%w_]+ not %w+: default_color and default_shade_color carry underscores. + 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 palette() + return { + 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), + body = hex(CFG.default_color), + -- The card fill, from default_shade_color (@BODY_SHADE@, `mantle` in + -- Macchiato). It must NOT reuse color3: that is the border, and filling and + -- stroking a card in one hue makes the hairline invisible, so the cards + -- read as blobs instead of the mockup's thin outlines. Every scheme already + -- defines body_shade, so this needs no palette change. + surface = hex(CFG.default_shade_color), + } +end + +-- Cell rect for a layout entry. 1-indexed cols and rows, as the table reads. +local function rect_for(entry, sw, sh) + local cw = (sw - MARGIN * 2 - GAP * (COLS - 1)) / COLS + local ch = (sh - MARGIN * 2 - GAP * (ROWS - 1)) / ROWS + return { + x = MARGIN + (entry.col - 1) * (cw + GAP), + y = MARGIN + (entry.row - 1) * (ch + GAP), + w = cw * (entry.w or 1) + GAP * ((entry.w or 1) - 1), + h = ch * (entry.h or 1) + GAP * ((entry.h or 1) - 1), + } +end + +-- Widget modules, loaded once and cached. A widget that fails to load must not +-- take the frame down with it, so the require is wrapped. +local widgets = {} +local function widget(name) + if widgets[name] == nil then + local ok, mod = pcall(require, 'widgets.' .. name) + widgets[name] = ok and mod or false + if not ok then print('dashboard: cannot load widget ' .. name .. ': ' .. tostring(mod)) end + end + return widgets[name] or nil +end + +-- Draw an error where the dashboard should be. +-- +-- Conky reports a Lua fault as a blank screen with no message on any stream, so +-- without this every mistake looks identical to "nothing ran". +local function draw_error(cr, msg, sw) + cairo_select_font_face(cr, 'Inconsolata Nerd Font', CAIRO_FONT_SLANT_NORMAL, + CAIRO_FONT_WEIGHT_BOLD) + cairo_set_font_size(cr, 16) + cairo_set_source_rgba(cr, 0.93, 0.53, 0.59, 1) -- literal: the palette may be what failed + local y = 40 + for line in tostring(msg):gmatch('[^\n]+') do + cairo_move_to(cr, 24, y) + cairo_show_text(cr, line) + y = y + 20 + if y > 400 then break end + end + print('dashboard error: ' .. tostring(msg)) +end + +local function draw(cr, sw, sh, colors) + for _, entry in ipairs(layout) do + local w = widget(entry.widget) + if w then + w.draw(cr, rect_for(entry, sw, sh), colors) + else + -- Name the missing widget in place, rather than leaving a blank cell. + local r = rect_for(entry, sw, sh) + local inner = card.card(cr, r, colors) + card.label(cr, inner.x, inner.y + 16, 'missing: ' .. entry.widget, colors) + end + end +end + +function conky_main() + if conky_window == nil then return end + local s = conky_surface() + if s == nil then return end + local cr = cairo_create(s) + local sw, sh = conky_window.width, conky_window.height + local ok, err = pcall(function() + draw(cr, sw, sh, palette()) + end) + if not ok then draw_error(cr, err, sw) end + cairo_destroy(cr) +end |
