Update(procmon-plugin): Add keyboard navigation and reduce CPU sampling (#321)

* Add procmon plugin with live process panel

New Noctalia plugin: bar widget shows CPU/RAM usage and process
count; floating panel has a sortable, searchable process table
with per-process kill. Background service samples ps and /proc
stats and publishes via plugin state.

* Throttle data-driven panel renders

Rebuild table at most every 500ms to stay within the panel update
CPU budget. User interactions still render immediately; only state
watches and the tick go through maybeRender().

* Update procmon deps and fix timestamp precision

Document head, grep, and cat as required commands. Correct refresh
interval default to 1000 ms. Sample refreshedAtMs in milliseconds.

* Add keyboard navigation and reduce CPU sampling

- Panel: arrow keys move cursor, Ctrl+D kills selected, Ctrl+F focuses
  filter
- Service: sample /proc stats every 1s, ps every 2s; 2s render floor
- Widget: handle vertical bars, truncate text, use valid theme color
- Bump version to 0.4.0, plugin_api 13
This commit is contained in:
weinguyen
2026-08-09 13:55:01 -04:00
committed by GitHub
parent 82f1cffd55
commit 44e32f58bc
6 changed files with 274 additions and 111 deletions
+36 -21
View File
@@ -1,7 +1,12 @@
--!nonstrict
-- Process Monitor — bar widget. Full mode shows "CPU% RAM% · count" text; icon-only
-- mode shows just a CPU icon colored by load, with the details in the tooltip so the
-- bar stays tiny. Clicking opens the panel.
-- Process Monitor — bar widget. Horizontal bars show "CPU% RAM%" text (truncated
-- to fit); vertical bars are too narrow for text, so they show only a CPU icon
-- colored by load. Clicking opens the panel.
-- Read the bar orientation once at load (pomodoro pattern) so the widget renders
-- consistently instead of flickering between icon and text when isVertical() is
-- polled on every update.
local isVertical = barWidget.isVertical()
local function cfg(key)
return noctalia.getConfig(key)
@@ -15,6 +20,14 @@ local function tr(key, subst)
return noctalia.tr(key, subst)
end
-- Cap the text so it fits the bar instead of spilling out the right edge.
local function trunc(s, n)
if #s <= n then
return s
end
return s:sub(1, n - 1) .. "…"
end
function update()
local stats = st("stats")
local cpu = stats and stats.cpu and stats.cpu.usagePercent
@@ -23,28 +36,30 @@ function update()
local cpuInt = math.floor(cpu or 0)
local tooltip = tr("widget.tooltip", { cpu = cpuInt, ram = math.floor(ram or 0), n = count })
barWidget.setGlyph(cfg("glyph") or "cpu")
barWidget.setTooltip(tooltip)
-- Color the icon by CPU load so a glance reads the machine state.
-- Only real theme tokens (error/secondary/on_surface) — the runtime rejects
-- unknown names like "warning" and logs a warning per glyph per frame.
local color = "on_surface"
if cpuInt >= 80 then
color = "error"
elseif cpuInt >= 50 then
color = "secondary"
end
if cfg("icon_only") then
barWidget.setText("")
-- color the icon by CPU load so a glance reads the machine state
if cpuInt >= 80 then
barWidget.setGlyphColor("error")
elseif cpuInt >= 50 then
barWidget.setGlyphColor("warning")
else
barWidget.setGlyphColor("on_surface")
local children = { ui.glyph({ name = cfg("glyph") or "cpu", size = 14, color = color }) }
-- Vertical bars are too narrow for text; show only the icon so it fits.
if not isVertical and not cfg("icon_only") then
local text = string.format("CPU %d%% RAM %d%%", cpuInt, math.floor(ram or 0))
if cfg("show_count") then
text = text .. " · " .. count
end
return
table.insert(children, ui.label({ text = text, fontSize = 11 }))
end
barWidget.setGlyphColor("on_surface")
local text = string.format("CPU %d%% RAM %d%%", cpuInt, math.floor(ram or 0))
if cfg("show_count") then
text = text .. " · " .. count
end
barWidget.setText(text)
local container = isVertical and ui.column or ui.row
barWidget.render(container({ gap = 6, align = "center" }, children))
barWidget.setTooltip(tooltip)
end
noctalia.state.watch("stats", update)