aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-09-16 19:31:42 +0200
committerDanilo M. <danix@danix.xyz>2026-09-16 19:31:42 +0200
commit0192bc5f17cf4c8868aea81ca93318130246b4d5 (patch)
treea95fd08b8d508bda1f12c7e84585b5a586f3c827
parente10bfebb1a908230244e33e970f4750d84ff1b35 (diff)
downloadconky-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.conf36
-rw-r--r--dashboard.lua154
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