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 | |
| 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>
| -rw-r--r-- | conky.conf | 36 | ||||
| -rw-r--r-- | dashboard.lua | 154 |
2 files changed, 190 insertions, 0 deletions
diff --git a/conky.conf b/conky.conf new file mode 100644 index 0000000..7bddb64 --- /dev/null +++ b/conky.conf @@ -0,0 +1,36 @@ +-- Development config with literal colours. Task 5 replaces this with the +-- UDT-rendered conky.conf.in. Not the file the installed dashboard uses. +conky.config = { + out_to_x = false, + out_to_wayland = true, + own_window = true, + own_window_type = 'normal', + own_window_class = 'conky-dash', + own_window_argb_visual = true, + own_window_argb_value = 200, + minimum_width = 1200, + minimum_height = 700, + double_buffer = true, + update_interval = 2, + total_run_times = 0, + draw_borders = false, + draw_shades = false, + override_utf8_locale = true, + lua_load = './dashboard.lua', + lua_draw_hook_post = 'main', + -- Literal dev palette, Catppuccin Macchiato values. + color1 = '#8aadf4', -- heading + color2 = '#a5adcb', -- label + color3 = '#494d64', -- rule / border + color4 = '#8bd5ca', -- value + color5 = '#c6a0f6', -- highlight + color6 = '#a6da95', -- ok + color7 = '#ed8796', -- critical + default_color = '#cad3f5', -- body + -- The card fill. dashboard.lua reads this for colors.surface; without it the + -- hex() fallback paints every card magenta. + default_shade_color = '#1e2030', -- body shade +} + +-- Empty: Cairo output covers conky.text entirely, verified by experiment. +conky.text = [[]] 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 |
