Per-zone RGB keyboard and chassis lighting control for Nimo Axis N161.
Requires emdoor-wmi kernel
module and Decky Loader. You can pick static per-zone colors or hand the
chassis off to the firmware's built-in animations.
Nimo RGB is the user-facing side of the Emdoor WMI driver. The kernel module exposes six LED class devices (four keyboard zones and two chassis side bars) plus a WMI mode attribute. This plugin talks to those sysfs handles directly, so colors land on the EC in one write with no ACPI round-trip.
The plugin runs as root inside Decky Loader. Everything happens through the kernel module - no ACPI calls, no BIOS patching, no per-zone firmware quirks to work around.
Your color choices persist. The plugin writes the active state (every zone's color, on/off, and firmware mode) to a JSON file on every change, and replays it on the next load. Reload, rmmod/insmod, reboot - the keyboard comes back the way you left it.
Nimo RGB running on the N161 with emdoor-wmi v0.3 bound:
- Six independent zones: four keyboard regions and two chassis side bars
- HSV picker with hue, saturation, and brightness sliders that match the current zone color
- Hex input for exact color values - type
#FF8800orFF8800and the picker snaps to it; invalid input reverts to the current color - "Apply current color to all zones" button for a uniform chassis look
- Per-zone on/off toggle that blanks a single zone without disturbing the others
Ten built-in modes handed off to the EC. Switch back to **Static to resume manual control:
- Twinkle, Wave, Breath, Color cycle
- Reactive, Ripple
- Spiral rainbow, Rainbow ripple
- Off, Reset (firmware default)
Mouse / touch works on every control. Controller support is partial:
the panel, zone dropdown, mode dropdown, on/off toggle, hue bar,
sliders, and the "Apply to all" button are all reachable with D-pad
navigation. The two dropdowns render as native <select> elements
for visual consistency, which means Steam Input cannot navigate
their option lists - pick the zone and mode with mouse or touch.
For touch-only / laptop use, the Steam on-screen keyboard handles
hex entry.
Every color change, on/off toggle, and mode switch is written to
~/.local/share/decky/plugins/nimo-rgb/state.json. The plugin reads
this file on startup and applies it before the user sees the panel,
so the device comes up exactly as it was when the plugin last ran.
force_load=1module parameter bypasses the kernel driver's DMI whitelist for hardware not yet in the supported list- Clear log messages identify which path (WMI or EC fallback) was taken during probe
- All writes go through Python's
Omode sysfs, so failure modes are visible injournalctl -u plugin_loader
- Nimo Axis N161
The emdoor-wmi driver binds only to devices in its DMI whitelist.
For hardware not on the list, load emdoor-wmi force_load=1 for
diagnostics. Add new vendors to emd_dmi_table in
emdoor-wmi.c once the
device has been verified to expose the EmdAcpi WMxx GUIDs.
- SteamOS 3.0 or later (Decky Loader required)
- Linux kernel with multicolor LED class support (5.14+)
- The
emdoor-wmikernel module loaded and bound to your device
The mode dropdown writes to a sysfs attribute that lives on a
different path depending on which version of emdoor-wmi is
loaded:
emdoor-wmiv0.3+ exposesmodeat/sys/class/leds/emdoor:rgb:mode/mode, owned bydeck:deck(SteamOS's70-steam-jupiter-leds.rulesauto-chowns it). The plugin writes here first.emdoor-wmiv0.2 exposesmodeat the WMI bus path, owned byroot:root. The plugin falls back to that path if the LED class device is absent, but writes will fail withEACCESunless you relax the ownership yourself.
| Plugin version | Kernel module version | Result |
|---|---|---|
| 0.3.x | 0.3.x | Mode writes succeed (LED class path) |
| 0.3.x | 0.2.x | Mode writes fail (Permission denied) |
| 0.2.x | 0.3.x | Mode writes succeed via the WMI fallback path |
| 0.2.x | 0.2.x | Mode writes fail (no fallback existed yet) |
Recommendation: keep the plugin and kernel module on the same major.minor line so the LED class path is the one being used.
- A supported Nimo, Xuanpai, or MetaMech laptop
- At least 5 MB available storage space
- Decky Loader plugin system
- Python 3.8 or later
emdoor-wmikernel module (out-of-tree, see Installation)- Linux kernel with
CONFIG_LEDS_CLASS_MULTICOLOR=y
- Root access required (plugin operates with elevated privileges; runs as root via Decky Loader)
- The
emdoor-wmidriver does not require Secure Boot to be disabled on the platforms it currently supports
curl -L https://raw.githubusercontent.com/fewtarius/nimo-rgb/main/install.sh | sh
sudo systemctl restart plugin_loaderThe installer runs the systemctl restart step itself - the
sudo systemctl restart line above is only needed if you want to
restart the loader again after a kernel module change.
The installer:
- refuses to run as root (uses
sudointernally for the steps that need it) - queries the GitHub API for the latest release
- downloads
nimo-rgb.zip - extracts it under
~/homebrew/plugins/nimo-rgb/ - backs up and restores
state.jsonso colors carry over across upgrades - chowns the install to the current user (Decky Loader runs as that user too)
- restarts
plugin_loader - prints a summary
The installer does not build, install, or load the emdoor-wmi
kernel module. That is a separate step covered in Manual Installation
below, and only needs to happen once per device.
Pass --debug (or set DEBUG_INSTALL=true) for verbose trace output
when troubleshooting the installer itself.
The installer does not build, install, or load the emdoor-wmi
kernel module. That is a separate step covered in Manual Installation
below, and only needs to happen once per device.
-
Download the latest release:
wget https://github.com/fewtarius/nimo-rgb/releases/latest/download/nimo-rgb.zip
-
Extract to the plugins directory:
sudo mkdir -p ~/homebrew/plugins/nimo-rgb sudo unzip -o nimo-rgb.zip -d ~/homebrew/plugins/nimo-rgb sudo chown -R deck:deck ~/homebrew/plugins/nimo-rgb
-
Build and install the kernel module (one-time per device):
git clone https://github.com/fewtarius/emdoor-wmi cd emdoor-wmi make && sudo make install sudo modprobe emdoor-wmi echo emdoor-wmi | sudo tee /etc/modules-load.d/emdoor-wmi.conf
-
Restart Decky Loader:
sudo systemctl restart plugin_loader
-
Reboot to ensure the LED class devices are present at boot.
-
Confirm the kernel module loaded and bound:
ls /sys/class/leds/ | grep emdoor # Expected: # emdoor:multicolor:zone1 # emdoor:multicolor:zone2 # emdoor:multicolor:zone3 # emdoor:multicolor:zone4 # emdoor:multicolor:bar-left # emdoor:multicolor:bar-right # emdoor:rgb:mode # control surface for animation mode
-
Open the Decky Loader overlay and select Nimo RGB. The panel should show six zones and a Mode dropdown.
-
Pick a color on any zone. The change should appear on the keyboard or chassis within a few hundred milliseconds.
-
Tail the kernel log to confirm the probe path:
sudo dmesg -w | grep -E 'emdoor|EmdAcpi' # Healthy firmware: "EmdAcpi power mode bound (... WMI path)" # Broken firmware: "EmdAcpi power mode bound (... EC IO fallback)"
Two modes of operation, switched by the Mode dropdown:
The firmware is parked in always mode. You control every zone
individually.
- Pick a zone from the dropdown
- Adjust the hue, saturation, and brightness sliders
- Type a hex value into the
#______field for exact colors - Toggle Zone on to blank a single zone without disturbing others
- Click Apply current color to all zones for a uniform look
Zone IDs match the physical layout:
| ID | Where |
|---|---|
zone1 |
Keyboard left quadrant |
zone2 |
Keyboard left center quadrant |
zone3 |
Keyboard right center quadrant |
zone4 |
Keyboard right quadrant |
bar-left |
Left side bar |
bar-right |
Right side bar |
Pick anything other than Static and the EC takes over. The chassis animates globally. A yellow note in the plugin reminds you to switch back to Static when you want manual control.
| Mode | What it does |
|---|---|
| Twinkle | Stars twinkle randomly |
| Wave | Color wave sweeps the keyboard |
| Breath | Slow breathing pulse |
| Color cycle | Cycles through the full hue wheel |
| Reactive | Lights up on keypress |
| Ripple | Ripple from the keypress origin |
| Spiral rainbow | Rotating rainbow spiral |
| Rainbow ripple | Rainbow-coloured ripple |
| Off | Everything dark |
| Reset | Restore the firmware default |
Some Emdoor firmware declares a malformed WMBF control method with a
63-bit BufferField at offset 0x20 of an 8-byte argument buffer.
Linux ACPICA rejects this declaration before the method body runs;
Microsoft ACPI.SYS does not. The field is unreferenced dead code - the
real value lives in the EC's PWMD register at OperationRegion offset
0x7C.
The driver probes WMBF first. On AE_AML_BUFFER_LIMIT it latches a
quirk flag and reads/writes PWMD directly via EC IO for the lifetime
of the device. User-visible behavior is identical in either case; the
log line identifies which path was taken.
The EC fallback omits one thing the WMI path does: the fan-curve
re-tuning that the firmware performs after PWMD = ECPM. If you
change power modes while the EC fallback is active, expect the fans
to stay at whatever curve they were on before the write.
pnpm install
pnpm run buildBuild output lands in dist/.
To produce the same .zip artifact that the GitHub release pipeline
publishes, install the Decky Plugin CLI
and run:
./cli/decky plugin build .
# -> out/nimo-rgb.zipTo install a local build without packaging it:
sudo rm -rf ~/homebrew/plugins/nimo-rgb
sudo mkdir -p ~/homebrew/plugins/nimo-rgb
sudo cp -r dist main.py plugin.json package.json README.md LICENSE py_modules \
~/homebrew/plugins/nimo-rgb/
sudo chown -R deck:deck ~/homebrew/plugins/nimo-rgb
sudo systemctl restart plugin_loaderThe canonical version lives in VERSION at the repo root.
update-version.sh mirrors it into package.json and plugin.json.
The CI release workflow runs update-version.sh before the Decky CLI
build, so tagging v0.3.0 (with VERSION set to 0.3.0) produces
a nimo-rgb.zip whose internal plugin.json reports 0.3.0.
To cut a release locally without running CI:
./update-version.sh 0.4.0 # write 0.4.0 into every manifest
pnpm run build # rebuild the bundle with the new version
git tag v0.4.0
git push origin v0.4.0 # triggers release.yml -> GitHub releasesudo rm -rf ~/homebrew/plugins/nimo-rgb
sudo mkdir -p ~/homebrew/plugins/nimo-rgb
cp -r dist main.py plugin.json package.json README.md LICENSE py_modules \
~/homebrew/plugins/nimo-rgb/
sudo chown -R deck:deck ~/homebrew/plugins/nimo-rgb
sudo systemctl restart plugin_loaderPlugin not appearing in Decky Loader:
- Verify Decky Loader is installed and running
- Confirm files are in
~/homebrew/plugins/nimo-rgb/ - Restart the loader:
sudo systemctl restart plugin_loader - Check the loader log:
journalctl -u plugin_loader -e
No zones shown (the "No emdoor-wmi zones found" message):
- The kernel module did not bind, or the LED class devices are not present
- Confirm:
ls /sys/class/leds/ | grep emdoor - Check the kernel log:
dmesg | grep -i emdoor - If
KBTE != 2is reported, the firmware reports a non-4-zone keyboard layout; this driver only supports the 4-zone layout - Try loading with
force_load=1:sudo modprobe emdoor-wmi force_load=1
Color changes don't appear:
- Check that the lighting mode is Static. Other modes hand control to the firmware
- Check the kernel log for EC write failures
- Verify sysfs is writable:
cat /sys/class/leds/emdoor:multicolor:zone1/multi_intensity echo "255 0 0" | sudo tee /sys/class/leds/emdoor:multicolor:zone1/multi_intensity
Color doesn't persist across restarts:
- Confirm
~/.local/share/decky/plugins/nimo-rgb/state.jsonexists and contains the expected zones - The file is rewritten on every successful write. If it's empty or
stale, the kernel writes are failing silently - check
journalctl -u plugin_loaderfor OSError messages
Controller focus skips the custom controls:
- All custom controls are wrapped in Decky's
Focusable. If D-pad skips past them, restart Decky Loader - the focus chain is rebuilt on plugin mount
-
Stop the plugin loader:
sudo systemctl stop plugin_loader
-
Remove the plugin files:
sudo rm -rf ~/homebrew/plugins/nimo-rgb -
Remove persistent state (optional):
rm -rf ~/.local/share/decky/plugins/nimo-rgb -
Remove the kernel module (optional):
sudo modprobe -r emdoor-wmi sudo rm /etc/modules-load.d/emdoor-wmi.conf sudo rm /lib/modules/$(uname -r)/extra/emdoor-wmi.ko sudo depmod -a -
Restart the plugin loader:
sudo systemctl start plugin_loader
The keyboard returns to the firmware's default mode (typically the last animation that was active) after the next reboot.
This project is licensed under the GNU General Public License v3.0
or later. See the LICENSE file for complete terms.
emdoor-wmi- the kernel module this plugin talks to- PowerDeck - per-game TDP and power profile management for the same laptop class



