Perch
Lua APIperch.system & friends

perch.system & friends

Stats, the transient stream, power, downloads, notifications, and volume control.

2 min readUpdated Jul 21, 2026

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:

EventPayloadNotes
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.

esc
Type to search
navigate open