A minimal, OpenRGB-inspired RGB controller for Windows 11. Built to control one specific rig with the smallest footprint and least input lag possible:
| Device | Transport | Admin required |
|---|---|---|
| ASRock X870 Pro RS mobo + Thermalright AIO ARGB | USB HID (Polychrome, VID 26CE) |
no |
| ADATA XPG Lancer Blade DDR5 RAM | SMBus via PawnIO (AMD PIIX4) | yes |
| ASRock RX 9070 XT GPU | AMD GPU I2C via ADL (atiadlxx.dll) |
no (needs Adrenalin) |
The GPU is not supported by OpenRGB. Protocol is derived from SignalRGB's ASRock GPU.js,
driven here over AMD's ADL I2C interface.
- Python + CustomTkinter + pystray — modern dark-mode UI, tiny bundle, no extra runtime.
- One backend interface (
core/device.py): every device is aDevicewithZones and a singleapply(). The UI, presets, and effect engine never special-case hardware — adding a device is one backend class. - One effect engine (
core/engine.py): static mode writes once and idles at 0% CPU (key for the always-on case); animated modes run a single 30 Hz loop for all devices. - One-shot + tray hybrid:
--apply-quitapplies the boot preset and exits (near-zero idle RAM); launch the full app only to change settings.
- Python 3.11+ — python.org. During install tick "Add python.exe to PATH".
- AMD Adrenalin — required for GPU ADL I2C (
atiadlxx.dll). Install via AMD's site or auto-install driver package. - Close SignalRGB before running rgbctl — if it is open its own render loop overwrites every write to the GPU and colors will not change.
git clone <repo-url> rgbctl
cd rgbctl
pip install -r requirements.txtDDR5 RGB needs kernel-level SMBus access through PawnIO. Skip this block if you don't need RAM control — the app runs fine without it.
-
Install PawnIO (official, signed edition): pawnio.eu → Download → run
PawnIO_setup.exe. -
Download
SmbusPIIX4.binfrom PawnIO.Modules releases. -
Place
SmbusPIIX4.binin themodules/folder inside the repo. -
Pin its hash (required — the app refuses to load an unsigned/unverified module):
# run from the repo root in PowerShell: (Get-FileHash modules\SmbusPIIX4.bin -Algorithm SHA256).Hash.ToLower() ` | Out-File -Encoding ascii -NoNewline modules\SmbusPIIX4.bin.sha256
python main.pyw # full window (tray-resident)
python main.pyw --minimized # start hidden in tray
python main.pyw --apply-quit # apply startup preset, exit (boot path)Dev / cross-platform:
RGBCTL_MOCK=1 python main.pywruns with simulated devices on any OS (WSL, Linux, macOS) — no hardware or Windows DLLs required.
Produces a --onedir bundle in dist/rgbctl/ via PyInstaller. --onedir starts faster
than --onefile (no per-launch extraction) — important for the --apply-quit boot path.
pip install pyinstaller
python build.pyThe build script verifies SmbusPIIX4.bin against its pinned SHA-256 before bundling —
a tampered module aborts the build rather than shipping in a release.
To add a Windows Startup entry (apply-and-exit at login):
from rgbctl.autostart import install
install()This creates a plain non-admin Startup shortcut (--apply-quit). No scheduled task,
no UAC bypass — see Security section.
The interface uses CustomTkinter for a native dark-mode look on Windows 11.
- Sidebar: device list, preset picker (save/load), mode selector, speed slider.
- Device panel: inline HSV colour picker (wheel + brightness bar) embedded directly above the zone grid — no separate dialog. Drag the wheel to pick hue/saturation; drag the brightness bar to adjust value.
- Zone cards: each card shows the zone name, current colour swatch, an Enabled checkbox (affects engine output) and a Selected checkbox (targets colour picker). Select All button top-right of the zone grid.
- Colour picker: applies instantly to every selected zone as you drag.
- Tray icon: minimise-to-tray on window close. Right-click for preset shortcuts and quit. Consistent icon across tray, taskbar, and title bar.
On launch the app checks (in order):
startup_presetsetting → apply and optionally exit (--apply-quit).last_preset(persisted tosettings.jsonon every manual load) → apply silently.- Neither set → skip; hardware holds whatever state it was in.
No hardware write happens at launch unless a preset is found.
This app does kernel-level SMBus I/O and raw GPU I2C; mistakes can corrupt hardware. Controls are documented at each call site.
- PawnIO / signed module: the app refuses to load
SmbusPIIX4.binunless a matching*.bin.sha256sidecar is present. Missing or mismatched hash → fail closed; RAM control disabled, app still runs. - SMBus write allowlist (brick prevention): DDR5 SPD EEPROM lives at
0x50–0x57on the same bus — a stray write can permanently corrupt RAM and prevent boot.hw/pawnio_smbus.pyrejects every write outside0x70–0x77, enforced at the lowest method so no caller can bypass it. Verified bytests/test_smbus_guard.py. - Least privilege: USB HID and GPU I2C need no admin; only RAM SMBus does.
- Autostart: plain non-admin Startup shortcut (
--apply-quit). No auto-elevated scheduled task — Defender flags that pattern as an escalation vector. - GPU validation: ADL validates ASRock subvendor
0x1849and probes address0x36before any write. Save-to-Hardware (NVRAM flash) is an explicit user action only — never triggered during normal colour changes. - Presets: JSON only (never
pickle), schema-validated and value-clamped on load. - Supply chain: runtime deps are version-pinned in
requirements.txt.
Run the security test suite (no hardware needed):
python -m pytest tests/test_smbus_guard.py -q- Transport: ADL I2C via
ADL2_Display_WriteAndReadI2C, OEM line 1, address0x6C. - Primary adapter index 5 (from
ADL2_Adapter_Primary_Get) — not enum index 0. - Active zones: Top Side (channel 6), Fan (channel 7). No physical ARGB header on this card.
- Protocol: init sequence + 12-byte colour packet per zone, 20 ms between zones.
- SignalRGB must be closed — its render loop overwrites every write.
- Disconnected zones excluded at probe:
0x02(ARGB Header 1),0x07(Audio/ARGB Header 3). - ARGB header zones (0x03) use GRB wire order; all other zones use RGB.
RGSWAP_CFGregister returns 0 for all zones on this board and is not used for ordering decisions.
- SMBus via PawnIO AMD PIIX4. Write allowlist enforced:
0x70–0x77only.
rgbctl/
core/ device, color+effects, engine, registry, presets
backends/ polychrome_usb, asrock_gpu_adl, ene_dram_smbus, mock
hw/ pawnio_smbus (SMBus trust boundary), adl_i2c (GPU)
ui/ app (window), tray, color_picker
main.py entrypoint (--apply-quit / --minimized / gui)
modules/ SmbusPIIX4.bin + pinned hash (gitignored — set up manually)
tests/ test_smbus_guard.py (security gate)