Add Cat plugin (#59)
An animated cat bar widget whose pace reflects CPU usage: sleeps idle, walks under light load, runs under heavy load. Color follows the theme by default or a pinned custom color. Cat artwork traced from the MIT-licensed CatWalk plasmoid into a custom icon font.
This commit is contained in:
@@ -0,0 +1,38 @@
|
|||||||
|
# Cat
|
||||||
|
|
||||||
|
An animated cat that lives in your bar. It sleeps when your CPU is idle,
|
||||||
|
walks as load picks up, and breaks into a full sprint under heavy load —
|
||||||
|
colored to match your theme, or any color you pick.
|
||||||
|
|
||||||
|
## Plugin
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
| --- | --- |
|
||||||
|
| ID | `dotnetrob/cat` |
|
||||||
|
| Entries | Bar widget: `cat` |
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Add the "Cat" widget to any bar from Settings → Bar → Add Widget. Click the
|
||||||
|
widget to show a notification with the current CPU percentage.
|
||||||
|
|
||||||
|
## Settings
|
||||||
|
|
||||||
|
| Setting | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `cat_size` | `int` | `24` | Sprite size in the bar, in pixels (12–48). |
|
||||||
|
| `show_cpu_percent` | `bool` | `false` | Display the CPU percentage next to the cat. |
|
||||||
|
| `walk_threshold` | `int` | `15` | CPU percentage at which the cat wakes up and starts walking. |
|
||||||
|
| `run_threshold` | `int` | `60` | CPU percentage at which the cat breaks into a run. |
|
||||||
|
| `poll_interval` | `int` | `2` | How often to sample CPU usage, in seconds. |
|
||||||
|
| `cat_color_mode` | `select` | `theme` | `theme` colors the cat with the palette's `secondary` role and tracks theme changes; `custom` uses the color below. |
|
||||||
|
| `cat_color` | `color` | `#E8A24C` | Used when `cat_color_mode` is `custom`. |
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
Every `poll_interval` seconds the widget reads `/proc/stat` to compute CPU
|
||||||
|
usage — no other files are read or written, nothing is downloaded, and no
|
||||||
|
processes are spawned. The cat's shape comes from a small custom icon font
|
||||||
|
(`fonts/catwalk2.otf`) traced from the MIT-licensed
|
||||||
|
[CatWalk](https://store.kde.org/p/2055225) plasmoid by Driglu4it, which lets
|
||||||
|
it be recolored like normal bar text instead of a fixed-color image.
|
||||||
+159
@@ -0,0 +1,159 @@
|
|||||||
|
--!nonstrict
|
||||||
|
-- Cat: an animated running cat bar widget. Pace reflects CPU usage, sampled
|
||||||
|
-- from /proc/stat, with separately configurable walk/run thresholds. Color
|
||||||
|
-- follows the theme's secondary role by default, or a pinned custom color.
|
||||||
|
--
|
||||||
|
-- The cat shape is a custom pictographic font (fonts/catwalk2.otf, traced
|
||||||
|
-- from the MIT-licensed CatWalk plasmoid by Driglu4it) so it can be colored
|
||||||
|
-- like any other bar text: "a" = idle/asleep, "b".."f" = the 5-frame run
|
||||||
|
-- cycle. Each glyph's advance width is trimmed to the shared ink extent
|
||||||
|
-- (non-negative left bearing, no dead trailing space) so bar layout doesn't
|
||||||
|
-- overlap the previous widget or leave a large gap before the next one.
|
||||||
|
--
|
||||||
|
-- Font registration is process-global and keyed by file path/family, so this
|
||||||
|
-- filename must change (not just its contents) whenever the glyph set changes
|
||||||
|
-- during development, or the shell keeps rendering whatever it cached the
|
||||||
|
-- first time it saw this path.
|
||||||
|
|
||||||
|
local catFont = noctalia.loadFont("fonts/catwalk2.otf")
|
||||||
|
|
||||||
|
local GLYPH_IDLE = "a"
|
||||||
|
local RUN_GLYPHS = { "b", "c", "d", "e", "f" }
|
||||||
|
|
||||||
|
local TICK_MS = 80
|
||||||
|
local WALK_MAX_MS = 380
|
||||||
|
local WALK_MIN_MS = 180
|
||||||
|
local RUN_MAX_MS = 160
|
||||||
|
local RUN_MIN_MS = 55
|
||||||
|
|
||||||
|
local catSize = noctalia.getConfig("cat_size")
|
||||||
|
local showCpuPercent = noctalia.getConfig("show_cpu_percent")
|
||||||
|
local walkThreshold = noctalia.getConfig("walk_threshold")
|
||||||
|
local runThreshold = noctalia.getConfig("run_threshold")
|
||||||
|
local pollIntervalMs = noctalia.getConfig("poll_interval") * 1000
|
||||||
|
local colorMode = noctalia.getConfig("cat_color_mode")
|
||||||
|
local customColor = noctalia.getConfig("cat_color")
|
||||||
|
|
||||||
|
local frameIndex = 1
|
||||||
|
local frameElapsed = 0
|
||||||
|
local frameDurationMs = WALK_MAX_MS
|
||||||
|
|
||||||
|
local sampleElapsed = pollIntervalMs -- sample immediately on first tick
|
||||||
|
local cpuPercent = 0
|
||||||
|
local prevTotal, prevIdle = nil, nil
|
||||||
|
|
||||||
|
local function sampleCpu()
|
||||||
|
local stat = noctalia.readFile("/proc/stat")
|
||||||
|
if not stat then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local line = stat:match("^cpu%s+(.-)\n") or stat:match("^cpu%s+(.-)$")
|
||||||
|
if not line then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local fields = {}
|
||||||
|
for n in line:gmatch("%d+") do
|
||||||
|
table.insert(fields, tonumber(n))
|
||||||
|
end
|
||||||
|
|
||||||
|
local idle = (fields[4] or 0) + (fields[5] or 0)
|
||||||
|
local total = 0
|
||||||
|
for _, v in fields do
|
||||||
|
total += v
|
||||||
|
end
|
||||||
|
|
||||||
|
if prevTotal then
|
||||||
|
local totalDelta = total - prevTotal
|
||||||
|
local idleDelta = idle - prevIdle
|
||||||
|
if totalDelta > 0 then
|
||||||
|
cpuPercent = math.clamp((totalDelta - idleDelta) / totalDelta * 100, 0, 100)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
prevTotal = total
|
||||||
|
prevIdle = idle
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Returns ("idle" | "walk" | "run", frame-duration-ms-for-that-pace).
|
||||||
|
local function paceFor(cpu)
|
||||||
|
local runFloor = math.max(runThreshold, walkThreshold + 1)
|
||||||
|
|
||||||
|
if cpu < walkThreshold then
|
||||||
|
return "idle", WALK_MAX_MS
|
||||||
|
elseif cpu < runFloor then
|
||||||
|
local range = math.max(runFloor - walkThreshold, 1)
|
||||||
|
local t = math.clamp((cpu - walkThreshold) / range, 0, 1)
|
||||||
|
return "walk", WALK_MAX_MS - t * (WALK_MAX_MS - WALK_MIN_MS)
|
||||||
|
else
|
||||||
|
local range = math.max(100 - runFloor, 1)
|
||||||
|
local t = math.clamp((cpu - runFloor) / range, 0, 1)
|
||||||
|
return "run", RUN_MAX_MS - t * (RUN_MAX_MS - RUN_MIN_MS)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function resolveColor()
|
||||||
|
if colorMode == "custom" and customColor and customColor ~= "" then
|
||||||
|
return customColor
|
||||||
|
end
|
||||||
|
return "secondary"
|
||||||
|
end
|
||||||
|
|
||||||
|
local function render(pace)
|
||||||
|
local glyph = pace == "idle" and GLYPH_IDLE or RUN_GLYPHS[frameIndex]
|
||||||
|
local color = resolveColor()
|
||||||
|
|
||||||
|
local children = {
|
||||||
|
ui.label({ text = glyph, fontFamily = catFont, baseline = "inkCentered", fontSize = catSize, color = color }),
|
||||||
|
}
|
||||||
|
|
||||||
|
if showCpuPercent then
|
||||||
|
table.insert(children, ui.label({ text = `{math.floor(cpuPercent)}%`, fontSize = 11, color = color }))
|
||||||
|
end
|
||||||
|
|
||||||
|
local container = barWidget.isVertical() and ui.column or ui.row
|
||||||
|
barWidget.render(container({ gap = 4, align = "center" }, children))
|
||||||
|
barWidget.setTooltip(`Cat — CPU {math.floor(cpuPercent)}% ({pace})`)
|
||||||
|
end
|
||||||
|
|
||||||
|
noctalia.setUpdateInterval(TICK_MS)
|
||||||
|
|
||||||
|
function update()
|
||||||
|
sampleElapsed += TICK_MS
|
||||||
|
if sampleElapsed >= pollIntervalMs then
|
||||||
|
sampleElapsed = 0
|
||||||
|
sampleCpu()
|
||||||
|
end
|
||||||
|
|
||||||
|
local pace, duration = paceFor(cpuPercent)
|
||||||
|
frameDurationMs = duration
|
||||||
|
|
||||||
|
if pace == "idle" then
|
||||||
|
frameElapsed = 0
|
||||||
|
frameIndex = 1
|
||||||
|
else
|
||||||
|
frameElapsed += TICK_MS
|
||||||
|
if frameElapsed >= frameDurationMs then
|
||||||
|
frameElapsed = 0
|
||||||
|
frameIndex = frameIndex % #RUN_GLYPHS + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
render(pace)
|
||||||
|
end
|
||||||
|
|
||||||
|
function onClick()
|
||||||
|
noctalia.notify(noctalia.tr("title"), `CPU {math.floor(cpuPercent)}%`)
|
||||||
|
end
|
||||||
|
|
||||||
|
function onConfigChanged()
|
||||||
|
catSize = noctalia.getConfig("cat_size")
|
||||||
|
showCpuPercent = noctalia.getConfig("show_cpu_percent")
|
||||||
|
walkThreshold = noctalia.getConfig("walk_threshold")
|
||||||
|
runThreshold = noctalia.getConfig("run_threshold")
|
||||||
|
pollIntervalMs = noctalia.getConfig("poll_interval") * 1000
|
||||||
|
colorMode = noctalia.getConfig("cat_color_mode")
|
||||||
|
customColor = noctalia.getConfig("cat_color")
|
||||||
|
render((paceFor(cpuPercent)))
|
||||||
|
end
|
||||||
Binary file not shown.
@@ -0,0 +1,76 @@
|
|||||||
|
id = "dotnetrob/cat"
|
||||||
|
name = "Cat"
|
||||||
|
version = "1.1.2"
|
||||||
|
plugin_api = 3
|
||||||
|
author = "DotNetRob"
|
||||||
|
license = "MIT"
|
||||||
|
dependencies = []
|
||||||
|
icon = "paw"
|
||||||
|
description = "An animated running cat bar widget whose speed reflects CPU usage."
|
||||||
|
tags = ["bar", "animation", "system", "fun"]
|
||||||
|
|
||||||
|
[[widget]]
|
||||||
|
id = "cat"
|
||||||
|
entry = "cat.luau"
|
||||||
|
|
||||||
|
[[widget.setting]]
|
||||||
|
key = "cat_size"
|
||||||
|
type = "int"
|
||||||
|
label_key = "settings.cat_size.label"
|
||||||
|
description_key = "settings.cat_size.description"
|
||||||
|
default = 24
|
||||||
|
min = 12
|
||||||
|
max = 48
|
||||||
|
|
||||||
|
[[widget.setting]]
|
||||||
|
key = "show_cpu_percent"
|
||||||
|
type = "bool"
|
||||||
|
label_key = "settings.show_cpu_percent.label"
|
||||||
|
description_key = "settings.show_cpu_percent.description"
|
||||||
|
default = false
|
||||||
|
|
||||||
|
[[widget.setting]]
|
||||||
|
key = "walk_threshold"
|
||||||
|
type = "int"
|
||||||
|
label_key = "settings.walk_threshold.label"
|
||||||
|
description_key = "settings.walk_threshold.description"
|
||||||
|
default = 15
|
||||||
|
min = 0
|
||||||
|
max = 100
|
||||||
|
|
||||||
|
[[widget.setting]]
|
||||||
|
key = "run_threshold"
|
||||||
|
type = "int"
|
||||||
|
label_key = "settings.run_threshold.label"
|
||||||
|
description_key = "settings.run_threshold.description"
|
||||||
|
default = 60
|
||||||
|
min = 0
|
||||||
|
max = 100
|
||||||
|
|
||||||
|
[[widget.setting]]
|
||||||
|
key = "poll_interval"
|
||||||
|
type = "int"
|
||||||
|
label_key = "settings.poll_interval.label"
|
||||||
|
description_key = "settings.poll_interval.description"
|
||||||
|
default = 2
|
||||||
|
min = 1
|
||||||
|
max = 10
|
||||||
|
|
||||||
|
[[widget.setting]]
|
||||||
|
key = "cat_color_mode"
|
||||||
|
type = "select"
|
||||||
|
label_key = "settings.cat_color_mode.label"
|
||||||
|
description_key = "settings.cat_color_mode.description"
|
||||||
|
default = "theme"
|
||||||
|
options = [
|
||||||
|
{ value = "theme", label_key = "settings.cat_color_mode.option_theme" },
|
||||||
|
{ value = "custom", label_key = "settings.cat_color_mode.option_custom" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[widget.setting]]
|
||||||
|
key = "cat_color"
|
||||||
|
type = "color"
|
||||||
|
label_key = "settings.cat_color.label"
|
||||||
|
description_key = "settings.cat_color.description"
|
||||||
|
default = "#E8A24C"
|
||||||
|
visible_when = { key = "cat_color_mode", values = ["custom"] }
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"title": "Cat",
|
||||||
|
"settings": {
|
||||||
|
"cat_size": {
|
||||||
|
"label": "Cat Size",
|
||||||
|
"description": "Sprite size in the bar, in pixels."
|
||||||
|
},
|
||||||
|
"show_cpu_percent": {
|
||||||
|
"label": "Show CPU Percentage",
|
||||||
|
"description": "Display the CPU percentage next to the cat."
|
||||||
|
},
|
||||||
|
"walk_threshold": {
|
||||||
|
"label": "Walk Threshold",
|
||||||
|
"description": "CPU percentage at which the cat wakes up and starts walking."
|
||||||
|
},
|
||||||
|
"run_threshold": {
|
||||||
|
"label": "Run Threshold",
|
||||||
|
"description": "CPU percentage at which the cat breaks into a run."
|
||||||
|
},
|
||||||
|
"poll_interval": {
|
||||||
|
"label": "CPU Poll Interval",
|
||||||
|
"description": "How often to sample CPU usage, in seconds."
|
||||||
|
},
|
||||||
|
"cat_color_mode": {
|
||||||
|
"label": "Cat Color",
|
||||||
|
"description": "Use the current theme color, or pick a custom color below.",
|
||||||
|
"option_theme": "Match Theme",
|
||||||
|
"option_custom": "Custom"
|
||||||
|
},
|
||||||
|
"cat_color": {
|
||||||
|
"label": "Custom Color",
|
||||||
|
"description": "Used when Cat Color is set to Custom."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user