-
Notifications
You must be signed in to change notification settings - Fork 0
How It Works
One DLL, five layers, no shared mutable state between the SDK code and the Rainmeter boundary. Each layer only knows about the one below it.
Plugin → Core → Modules → Resolvers → Providers
| Layer | Location | Job | Knows about |
|---|---|---|---|
| Plugin | source/Plugin/ |
Rainmeter entry points (Initialize/Reload/Update/Finalize/GetString). Reads measure options, hands them to Core. |
Rainmeter only — no hardware |
| Core | source/Core/ |
HardwareCore singleton + poller. Deduplicates the gather so each category is read once per tick; GetValue/GetString are pure cache reads. |
Modules only — no SDKs |
| Modules | source/Modules/<Cat>/ |
One per category. Owns the per-device Snapshot[] cache; answers metric queries from it. |
Its Resolver — no SDKs |
| Resolvers | source/Modules/<Cat>/ |
The vendor-aware priority chain. At init, assigns each device to the provider that wins for it. | Providers |
| Providers | source/Providers/<SDK>/ |
Leaf nodes. The only place an SDK is touched. | One SDK each |
- Rainmeter calls
Updateon a measure →HardwareCore::GetValue(handle). - Core makes sure the owning module has gathered this tick (once, shared across every
measure of that category), then returns the cached value. No SDK call happens inside
GetValue— gathering and reading are separated. - A module gathers by asking, per device, the provider the Resolver picked for it, which
fills a
Snapshot(ametricId → {value, supported}map).
supported = false is not value = 0. A metric the hardware genuinely can't report
is marked unsupported and surfaces as -1; a real zero reading stays 0. See
Quirks & Troubleshooting.
Because every reading is a normal Rainmeter measure, a Script measure can read it from Lua with the standard measure API — no plugin-specific support needed. Rainmeter ships Lua 5.1.
function Update()
local usage = SKIN:GetMeasure('MeasureGpuUsage'):GetValue() -- numeric side
local name = SKIN:GetMeasure('MeasureGpuName'):GetStringValue() -- string side
if usage == -1 then usage = 0 end -- -1 = metric unsupported on this hardware
return usage, name
endGetValue() returns the numeric side — including the -1 (unsupported) and -2 (error)
sentinels as plain numbers you can test — and GetStringValue() returns the string side
for identity metrics (Name, Vendor, VolumeLabel, …).
By default a category is gathered in step with Rainmeter's Update= tick. Set
UpdateOverride=<ms>
on a measure and that category is instead polled on a background thread at that rate,
independent of the skin — so a fast network readout can live under a slow skin, and the
gather never blocks Rainmeter's UI thread.
GPU is the only multi-provider category. NVIDIA, AMD, and Intel are enumerated simultaneously — whichever card(s) are present show up. NVIDIA and AMD have a primary provider and a backup; Intel is a single driverless API:
- NVIDIA: NVML (primary) → NVAPI (backup)
- AMD: ADLX (primary) → ADL2 (legacy backup)
-
Intel: IGCL (single provider —
ControlLib.dll, no backup)
Fallback is per metric, not per card: if the primary can't supply a given value, the
vendor's backup fills just that one (Intel has no backup, so an unsupported Intel sensor
just reports unsupported). For Name and DriverVersion there's a final
Windows DXGI/registry last resort so those are never blank. A blank cell in the
Metrics Reference support tables is a real SDK/hardware ceiling, not a
missing fallback.
The other categories (CPU, Memory, Network, Storage, Ping, Battery, System, Motherboard, Display) each have a single Windows-API provider — no vendor chain.
Every reading comes from a user-mode source: vendor SDKs (NVML/NVAPI/ADLX/ADL2/IGCL) and
Windows APIs (WMI, PDH, Win32, IP Helper, ICMP). Values that only exist behind Model-Specific
Registers or vendor SMU telemetry — real per-core CPU voltage, true Tctl/Tdie temperature —
would require a signed kernel driver and a service to load it. That's a permanent scope
boundary, not a TODO. Such metrics report -1. See
Quirks & Troubleshooting.