The system surface is a family of small scopes — grant only the slices you render.
perch.system.stats.read() — scope system.stats
local s = perch.system.stats.read()
-- { cpu = 12.4, ram = 48.1, gpu = 31.0, cput = 54, gput = 61 }
On-demand sampling: percentages for CPU/RAM/GPU, temperatures in °C. CPU% is delta-based — the first read after a gap returns empty; the second fills. Your polling cadence owns the resolution; a 2–5 s timer is the sweet spot.
function M.on_timer(host)
local s = perch.system.stats.read()
if s.cpu then host.push_data("stats", { cpu = s.cpu, bars = history(s) }) end
end
The transient stream — scope system.transients
Events, not polls — each fires as its device changes:
| Event | Payload | Notes |
|---|---|---|
on_volume | { level, muted } | level 0–1; 0 while muted. Drag-rate — pair a bar with snap |
on_brightness | { level } | 0–1 |
on_nightlight | { on } | |
on_connect | { kind, name, connected, pct } | kind = bt usb wifi; pct = battery when known |
on_timer_done / on_timer_cancel | { name } / {} | the voice-timer bridge |
Volume and brightness also direct-feed the builtin HUD activities at native rate past the Lua melt — your handler is for reacting, not for relaying.
perch.volume.control(verb) — scope volume.control
perch.volume.control("up") -- +10 UI points
perch.volume.control("down") -- -10
perch.volume.control("set:35") -- absolute 0–100
on_power(host, p) — scope system.power
-- p = { battery = true, name = "Mouse", pct = 15 }
Low-battery events for the machine and attached peripherals; battery
distinguishes the machine’s own low-battery case.
on_download(host, d) — scope system.downloads
-- d = { name = "fedora.iso", frac = 0.62, active = true }
Progress of browser downloads. active = false closes the arc — retract or
flip to a done state. A negative frac on your bar/ring renders the
indeterminate look for size-unknown downloads.
Notifications — scope system.notifications
The mirror is opt-in machinery: nothing monitors until you turn it on.
function M.on_configure(host, s)
perch.mirror.set_config({ mirror = s.mirror }) -- your toggle drives the monitor
end
function M.on_note(host, n)
-- n = { app = "Signal", title = "Maya", body = "you up?", icon = "file://…" }
host.set_presence("sysnote", true, n)
end
Discord’s and Perch’s own notifications never enter the stream — that dedup is native and non-negotiable. Wire the toggle honestly: mirroring reads every desktop notification, and your Workshop wording should say so.