Skip to content

Feature/ibiticu/plugin new cpu bars#48

Open
ioandev wants to merge 3 commits into
noctalia-dev:mainfrom
ioandev:feature/ibiticu/plugin-new-cpu-bars
Open

Feature/ibiticu/plugin new cpu bars#48
ioandev wants to merge 3 commits into
noctalia-dev:mainfrom
ioandev:feature/ibiticu/plugin-new-cpu-bars

Conversation

@ioandev

@ioandev ioandev commented Jul 18, 2026

Copy link
Copy Markdown

Plugin

  • Id: ioandev/cpu-bars
  • New plugin
  • Update to an existing plugin (version bumped in plugin.toml)

What it does

Depends on noctalia-dev/noctalia#3515, which adds the noctalia.systemStats() and noctalia.nowMs() bindings this plugin needs and raises the plugin API to 5. This cannot merge before that ships — until then plugin_api = 5 is too new and the plugin will not load. Happy to keep this as a Draft until #3515 lands.

Draws one thin vertical bar per logical CPU core in the bar, each filling from the bottom in proportion to that core's usage over the last second. A core at or above the warning threshold (90% by default) switches to the warning colour.

The point is what an averaged CPU percentage hides. A single pegged thread and mild uniform load across every core produce the same "8%" readout, but they mean very different things — one is a runaway process, the other is a machine working normally. Per-core bars make that visible at a glance, in roughly the width of a normal bar widget.

Idle cores show an empty track rather than a sliver, so a busy core stands out instead of blending into a solid line. Colours are theme role tokens, so they follow the active colour scheme, including light/dark switches. On a vertical bar the widget rotates: cores stack downward and each bar fills from the left.

Eight settings: bar width, gap, length, warning threshold, bar/warning/track colours, and an icon toggle.

External dependencies

None. No network calls, no spawned processes, no filesystem writes. Per-core usage comes from the host via noctalia.systemStats(), which reads /proc/stat, so the plugin is Linux-only.

Testing

Added the widget to a bar and exercised it end-to-end against a local build of noctalia#3515.

Correctness of the per-core mapping was checked against ground truth rather than eyeballed: load was pinned to known cores with taskset and the matching bars confirmed, so bar N really is core N-1. Cores 3 and 17 pinned lit exactly bars 4 and 18; repeated with cores 0-3 + 12-13, and with 6/7/15. Partial load via stress-ng --cpu-load at 100% / 75% / 55% / 25% produced proportional bar heights, and the warning colour appeared only on bars at or above the threshold, clearing when the load stopped.

All eight settings were changed and confirmed to take effect: bar width, gap and length visibly resize the widget, and lowering the warning threshold to 50 turned the mid-load bars red. The tooltip was checked for the total plus the per-core breakdown. The wallpaper was changed mid-run — the palette here is wallpaper-derived — and the bars followed the new scheme, confirming the role tokens resolve live rather than being sampled once.

Tick alignment was measured by logging noctalia.nowMs() % 1000 each update: it converges within a couple of ticks and holds at 4-16 ms past the second, one update per second with no skipped or doubled seconds.

Not exercised: the vertical-bar orientation. The code path exists — barWidget.isVertical() swaps the container and fills each bar from the left — but the test bar was horizontal throughout, so I cannot claim it works. Also untested: machines with a very low or very high core count (this is a 24-core box), and CPU hotplug.

Screenshots / Videos

To attach: the widget under mixed load — some cores pegged and red, some at partial height, the rest idle — plus an idle shot showing empty tracks.

Checklist

  • The directory name matches the part of id after the / in plugin.toml exactly.
  • It ships plugin.toml, README.md, thumbnail.webp, and translations/en.json.
  • README.md follows the README template, documents every entry id and dependency, and includes exact panel IPC commands and launcher prefixes where applicable.
  • [xI created thumbnail.webp with the thumbnail generator.
  • version follows semver and is bumped in this PR; plugin_api is the oldest API level this plugin requires.
  • Every non-English translation in this PR uses a locale supported by Noctalia core, and I can read, write, and understand that language well enough to review and maintain it (no unreviewed machine/LLM translations).
  • I did not edit catalog.toml; CI generates it.
  • This PR touches exactly one plugin directory.

This PR adds only translations/en.json. No other locales are included, so there is nothing machine-translated to review.

Code review attestation

  • The code is readable and not obfuscated, minified, or generated.
  • It does not download and execute remote code.
  • Every network call, filesystem write, and spawned process is something the description above accounts for.
  • I have the right to publish this code under the license declared in plugin.toml.

The plugin is a single 148-line Luau file with no dependencies and no I/O of any kind: it reads noctalia.systemStats() and noctalia.getConfig(), builds a ui.row of boxes, and sets a tooltip. The only non-obvious part is scheduleNextTick(), which corrects its own update interval to land on the second boundary; it is commented in place, since the reason is not guessable from the code.

ioan added 2 commits July 18, 2026 19:52
A bar widget drawing one vertical bar per logical core, filling bottom-up
from noctalia.systemStats().cpu.cores, so an unbalanced load or a single
pegged thread stays visible where an averaged percentage would hide it.

- Bars are composed from ui.column/ui.box; there is no meter primitive.
  Height is strictly proportional with no minimum, so an idle core shows an
  empty track.
- Colours are theme role tokens, so they follow the active scheme; a core at
  or above the threshold (default 90%) switches to the warning role.
- Rotates for a vertical bar: cores stack downward, each bar fills from the
  left.
- Updates once a second aligned to the top of the second. The host adds a
  fixed per-widget phase to timer restarts and the script runs a moment after
  the timer fires, so the interval is corrected each tick from an observed
  round trip via noctalia.nowMs(), biased a few ms past the boundary to stay
  on the correct side of the second.

Requires plugin API 5 for noctalia.systemStats() and noctalia.nowMs().
Adds a validator rule and its unit tests. Two failure modes that CI could
not previously see:

- Reading a key that plugin.toml does not declare. The host only resolves
  manifest-declared keys, so a typo in getConfig() yields nil rather than an
  error — the setting silently does nothing.
- Declaring a setting nothing reads, which puts a dead control in the user's
  settings UI.

The unused-setting half is skipped for plugins that call getConfig with a
non-literal argument, since a key read through a variable cannot be seen and
flagging it would be a false positive. Six plugins in this repo do that. The
undeclared-read half still applies to them.

Factors the existing Luau comment/string masking out of
obsolete_config_accessors() into mask_luau(mask_strings=...), because
reading a key literal needs comments masked but strings visible.
obsolete_config_accessors() keeps its previous behaviour.

Verified against all 24 plugins before wiring it in: no existing plugin
trips either half, so this does not break CI for anyone. Mutation-tested —
disabling the undeclared-read check, and disabling comment masking so a
commented-out getConfig counts as a read, both fail the new tests.
@ioandev

ioandev commented Jul 18, 2026

Copy link
Copy Markdown
Author

There is a problem with this plugin. Every time a change in the bar takes place, the plugin gets reloaded and it waits a second between it redraws. I will fix.

  • Fixed

The widget vanished for over a second whenever the bar reloaded. Every bar
reload recreates the widget, and update() called setVisible(false) whenever
cpu.cores came back empty, so a momentary gap in the data became a visible
disappearance rather than a stale frame.

Keep whatever is already drawn instead, and only hide when nothing has ever
been rendered — the genuine first-tick case, where there is no previous
reading to diff against.

The host half of this is fixed separately in noctalia: releasing the last
per-core reference used to clear the samples, and recreating a widget drops
the refcount to zero for a few milliseconds. Either fix alone hides the
symptom; this one also makes the widget robust against any future gap.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant