aboutsummaryrefslogtreecommitdiffstats
path: root/docs/superpowers
diff options
context:
space:
mode:
Diffstat (limited to 'docs/superpowers')
-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.