aboutsummaryrefslogtreecommitdiffstats
path: root/docs
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-09-16 18:42:09 +0200
committerDanilo M. <danix@danix.xyz>2026-09-16 18:42:09 +0200
commit820b637bffd4231675bb56163991fb938bd9bc79 (patch)
treec9121cdec03ce6cf456d8d1eb1c6eca77d1fb033 /docs
downloadconky-theme-udt-820b637bffd4231675bb56163991fb938bd9bc79.tar.gz
conky-theme-udt-820b637bffd4231675bb56163991fb938bd9bc79.zip
docs: add Lua dashboard design spec
Conky dashboard drawn with Cairo from Lua, on a Hyprland special workspace. Replaces UDT's variables-based conky.conf.in. Platform facts verified before fixing the design, since each one invalidates a common approach: - Cairo on Wayland needs conky_surface(). The usual cairo_xlib_surface_create idiom gets a nil drawable and visual under out_to_wayland, so every tutorial using it is wrong here. - own_window_type='normal' makes Conky a real toplevel that a Hyprland windowrule can place; 'desktop' is a layer-surface and cannot be assigned to a workspace. - Cairo output covers conky.text, so conky.text stays empty. Layout is a table of grid cells at the top of dashboard.lua, so reordering widgets is one edit and the same table works on both monitors. The 2560x1600 mockup's vertical stack does not fit the 2560x1080 primary, which is what motivates cells over pixels. hwmon glob-by-name is carried over from the old config deliberately: fixed indices drift across kernel reorders and silently read the wrong chip. v1 is one clock card end to end. Widgets are additive after that. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'docs')
-rw-r--r--docs/superpowers/specs/2026-09-16-conky-lua-dashboard-design.md252
1 files changed, 252 insertions, 0 deletions
diff --git a/docs/superpowers/specs/2026-09-16-conky-lua-dashboard-design.md b/docs/superpowers/specs/2026-09-16-conky-lua-dashboard-design.md
new file mode 100644
index 0000000..059205b
--- /dev/null
+++ b/docs/superpowers/specs/2026-09-16-conky-lua-dashboard-design.md
@@ -0,0 +1,252 @@
+# Conky Lua dashboard: design
+
+Date: 2026-09-16
+Status: approved, pending implementation plan
+
+## What this is
+
+A fullscreen Conky dashboard for Hyprland, drawn with Cairo from Lua, living on
+a special workspace that a keybind and a waybar launcher toggle.
+
+It replaces `templates/conky.conf.in` in the `unified-desktop-theme` (UDT) repo.
+That template is a 6.3K variables-and-`${execi}` config rendering a flat text
+column on the desktop layer. This repo becomes the canonical Conky consumer of
+the UDT palette: UDT's `install.sh` keeps rendering the template and restarting
+Conky exactly as it does today, only the source of the template moves here.
+
+The old config is not thrown away. Its hardware discovery, in particular the
+hwmon glob-by-name discipline, is the one part that must not regress and is
+carried over deliberately (see "Data").
+
+## Reference material
+
+Two mockups in the repo root, both **layout and shape only, not colour**.
+Colour comes from the UDT palette in every case.
+
+`idea1.png` (2560x1600): card grid, thin-bordered rounded rectangles, monospace
+labels in small caps over larger values. Supplies the card vocabulary.
+
+`idea2.png` (307x773): the clock treatment. Very heavy numerals, hour stacked
+over minute, left-aligned, tight leading; small-caps weekday beneath; date as
+`12 / MAY / 2023` with slash separators. Also shows ring gauges and album art,
+useful for later widgets.
+
+### Deliberately not taken from the mockups
+
+- **Lumon branding and "UNITED IN SEVERANCE"** — user asked for layout only.
+- **Task list with a Sync button** — needs an application backend, not a system
+ monitor's job.
+- **"Sent to Jackie" mic button** — interactive. Conky can draw a button; it
+ cannot receive a click.
+- **`idea1.png`'s vertical right-hand stack** — does not fit the target screen.
+ See "Screen geometry".
+
+## Screen geometry
+
+The primary monitor is **2560x1080**; the secondary is 1920x1080. `idea1.png` is
+2560x1600, so its four-card vertical stack cannot be reproduced at 1080px tall.
+
+This is why the layout is expressed in **grid cells** rather than pixels: the
+cell size derives from the actual surface dimensions, so one layout table works
+on both monitors and the column count, not a set of hand-tuned offsets, decides
+whether the result reads as horizontal or dense.
+
+v1 targets the primary monitor. Per-monitor layout variants are not built (see
+"Explicitly out of scope").
+
+## Verified platform facts
+
+Established by experiment on this machine before the design was fixed, because
+each one invalidates a common approach.
+
+**Conky build:** 1.24.2-pre, with Lua bindings for Cairo and Imlib2, ARGB visual,
+and Wayland output. Lua 5.4.9.
+
+**Cairo on Wayland needs `conky_surface()`.** The idiom in essentially every
+Conky-Lua tutorial is:
+
+ cairo_xlib_surface_create(conky_window.display, conky_window.drawable, ...)
+
+Under `out_to_wayland = true` there is no Xlib drawable: `conky_window.drawable`
+and `conky_window.visual` are both **nil**, and Conky emits a deprecation
+warning pointing at `conky_surface()` instead. `conky_surface()` was tested and
+returns a live surface; Cairo fills, Nerd Font text via `cairo_show_text`, and
+`conky_parse()` all work on it. A `grim` screenshot confirmed the output reaches
+the screen, so this is not merely "the calls return without error".
+
+**Conky renders as a real toplevel.** With `own_window_type = 'normal'` the
+window appears in `hyprctl clients` with `class=conky`, floating, on a normal
+workspace, which is what makes a Hyprland `windowrule` able to place it. The
+existing desktop-layer config uses `own_window_type = 'desktop'`, which is a
+layer-surface at level 0 and cannot be assigned to a workspace at all.
+
+**Cairo output covers `conky.text`.** A `conky.text` of `hello` did not appear
+under the Cairo drawing. `conky.text` is therefore left empty.
+
+## Architecture
+
+ conky.conf.in UDT-templated config: @ROLE@ placeholders, lua_load
+ dashboard.lua entry point; owns the layout table and the grid
+ lib/card.lua Cairo primitives: rounded card, text, gauge
+ lib/data.lua /proc and /sys readers
+ widgets/<name>.lua one file per widget
+
+`conky.text` is empty; all drawing happens in `lua_draw_hook_post`.
+
+### Layout: the user owns the order
+
+A single table at the top of `dashboard.lua` is the layout. Reordering widgets
+means editing that table and nothing else.
+
+ -- Layout. Order and position are yours: edit this table, nothing else.
+ -- col/row are grid cells; w/h span cells. Grid auto-sizes to the screen.
+ local layout = {
+ { widget = 'clock', col = 1, row = 1, w = 1, h = 2 },
+ { widget = 'system', col = 2, row = 1, w = 2, h = 1 },
+ { widget = 'weather', col = 2, row = 2, w = 1, h = 1 },
+ { widget = 'network', col = 3, row = 2, w = 1, h = 1 },
+ }
+
+Consequences, and they are the point:
+
+- A widget receives a rectangle and draws inside it. It never knows where it is,
+ so moving it cannot break it.
+- Cell size is derived from the surface dimensions, so the same table works on
+ either monitor.
+- Adding a widget is a new file in `widgets/` plus one row in the table.
+
+Grid cells mean cards snap to the grid; a card cannot sit at an arbitrary pixel
+offset. This was chosen over an absolute-placement escape hatch because cells
+give alignment for free and keep realigning when the grid or the screen changes.
+
+### Widget interface
+
+Every widget exports one function:
+
+ function draw(cr, rect, colors) -- rect = {x, y, w, h}
+
+`rect` is computed by the grid. `colors` is the palette table. A widget draws
+only inside its rect; it does not read the layout, other widgets, or global
+position.
+
+### Palette
+
+No hex literals in Lua. `conky.conf.in` carries the `@HEADING@`, `@LABEL@`,
+`@VALUE@`, `@RULE@`, `@BODY@` placeholders that the existing template already
+uses, and UDT's `bin/udt-palette` fills them from `palette/roles-<scheme>.conf`.
+Lua reads the resulting values from `conky.config` once per draw cycle and hands
+them to widgets as `colors`.
+
+Scheme switching therefore keeps working unchanged: edit
+`~/.config/udt/roles.conf`, run `./install.sh`. The `[conky]` role block already
+exists in every shipped scheme, so no palette file needs touching.
+
+### Data
+
+`lib/data.lua` reads `/proc/stat`, `/proc/meminfo` and `/sys/class/hwmon/*`
+directly, replacing the `${execi ... | grep | awk}` pipelines of the old
+template. Motivation is correctness and cost: those pipelines spawn several
+processes every few seconds.
+
+**hwmon is globbed by its `name` file, never by a fixed index.** Carried over
+verbatim from the old config, where it is the hard-won part:
+
+ /sys/class/hwmon/hwmon*/name -> match exact name -> read tempN_input
+
+Fixed `${hwmon N}` indices drift across kernel and hardware reorders and then
+silently report the wrong chip. Known mapping on this host: CPU TCTL is
+`k10temp` temp1; NVMe is `nvme` temp1; motherboard is `gigabyte_wmi` temp2 and
+temp3 (that chip exposes five unlabeled temps, chosen empirically); GPU (Intel
+Arc B580) is `xe` temp2 package, temp3 vram, fan1 RPM.
+
+These bindings are host-specific and will not port to another machine.
+
+Data that genuinely needs a shell stays a shell call, kept behind a `data.lua`
+function: `playerctl` for MPRIS now-playing, and the weather fetch below.
+
+### Weather
+
+Deferred past the v1 slice, then implemented as a fetch script writing a cache
+file that `data.lua` reads. The dashboard never blocks on the network.
+
+`~/Programming/waybar-weather.py` exists and is the starting point, but it is
+not reusable as-is: it scrapes `weather.com` HTML with `pyquery`, a third-party
+dependency, and screen-scraping breaks whenever the site's markup changes. The
+replacement targets a documented endpoint (`wttr.in` JSON, or an API with a key)
+and is reimplemented rather than copied. The location id in that script is
+personal data and does not move into this repo.
+
+## v1 slice: one card, one widget, end to end
+
+The clock card, shaped after `idea2.png`, and nothing else.
+
+That single card exercises the entire pipeline: UDT renders the template from
+palette roles, Conky loads the Lua, the grid computes a rect, Cairo draws on a
+`conky_surface()`, and Hyprland places the window on the special workspace.
+
+Every widget after it is additive: one file, one table row.
+
+## Hyprland integration
+
+ own_window_class = 'conky-dash'
+
+Distinct from the existing desktop-layer instance's `Conky`, so the two can
+never match each other's rules.
+
+ windowrulev2 = workspace special:dash silent, class:^(conky-dash)$
+ windowrulev2 = fullscreen, class:^(conky-dash)$
+ windowrulev2 = noborder, class:^(conky-dash)$
+
+Toggle, bound to both a keybind and a waybar launcher:
+
+ hyprctl dispatch togglespecialworkspace dash
+
+**Lifetime: always running, started with the session, no hidden-state
+optimization.** Hiding is a workspace switch, so showing is instant and graph
+history survives. Skipping draws while the workspace is hidden was considered
+and rejected as unmeasured: Conky at a 2-3s interval is cheap, and the check
+costs a `hyprctl` call per cycle. Revisit only if idle CPU actually shows up.
+
+## Error handling
+
+**Conky reports a Lua fault as a blank screen and nothing else.** No stderr, no
+log line: a typo in a Cairo call yields an empty dashboard.
+
+So the draw body runs inside `pcall`, and on failure the error string is drawn
+onto the surface in the palette's critical colour. Three lines, and every future
+failure becomes a readable message instead of a black rectangle.
+
+This is the only error handling the dashboard needs; a widget that cannot read
+its data draws a dash, it does not abort the frame.
+
+## Verification
+
+**Parsers:** one runnable check, `assert`-based, run with `lua`. Committed
+fixture text for `/proc/stat`, `/proc/meminfo` and a hwmon `temp*_input` is
+parsed and the results asserted. Parsers are what break silently on a kernel
+change, so they are what gets a test. No framework, no fixtures directory.
+
+**Rendering:** screenshot loop, the one used to verify `conky_surface()`:
+
+ conky -c ./conky.conf &
+ grim -g "$(hyprctl clients -j | jq -r '.[]|select(.class=="conky-dash")|"\(.at[0]),\(.at[1]) \(.size[0])x\(.size[1])"')" cap.png
+
+Cairo correctness is a visual property; a screenshot is the honest check.
+
+## Explicitly out of scope
+
+Per-monitor layout variants. Absolute pixel placement. A configuration UI or
+drag-to-place. Click interaction of any kind. The task list and mic button from
+`idea1.png`. Light-scheme support, which UDT does not have either.
+
+## Open items
+
+Both deferred by design, neither blocks v1.
+
+- Weather source: `wttr.in` versus a keyed API. Decided when the widget is built.
+- Album art via Imlib2 for the media widget, as in `idea2.png`. Needs a check
+ that Imlib2 bindings work on a Wayland `conky_surface()`, which was not tested.
+
+## License
+
+GPLv2 only, matching UDT.