Skip to content

Repository files navigation

Nimo RGB

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.

Introduction

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.

Screenshots

Nimo RGB running on the N161 with emdoor-wmi v0.3 bound:

Main UI view

UI showing available modes

UI showing zone options

UI showing right sidebar RGB disabled

Features

Per-Zone Static Control

  • 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 #FF8800 or FF8800 and 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

Firmware Animations

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)

Controller and Mouse Support

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.

State Persistence

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.

Diagnostics

  • force_load=1 module 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 O mode sysfs, so failure modes are visible in journalctl -u plugin_loader

Compatibility

Supported Devices

  • Nimo Axis N161

Adding New Hardware

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.

Operating System Requirements

  • SteamOS 3.0 or later (Decky Loader required)
  • Linux kernel with multicolor LED class support (5.14+)
  • The emdoor-wmi kernel module loaded and bound to your device

Plugin / Kernel Module Versioning

The mode dropdown writes to a sysfs attribute that lives on a different path depending on which version of emdoor-wmi is loaded:

  • emdoor-wmi v0.3+ exposes mode at /sys/class/leds/emdoor:rgb:mode/mode, owned by deck:deck (SteamOS's 70-steam-jupiter-leds.rules auto-chowns it). The plugin writes here first.
  • emdoor-wmi v0.2 exposes mode at the WMI bus path, owned by root:root. The plugin falls back to that path if the LED class device is absent, but writes will fail with EACCES unless 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.

System Requirements

Hardware Requirements

  • A supported Nimo, Xuanpai, or MetaMech laptop
  • At least 5 MB available storage space

Software Dependencies

  • Decky Loader plugin system
  • Python 3.8 or later
  • emdoor-wmi kernel module (out-of-tree, see Installation)
  • Linux kernel with CONFIG_LEDS_CLASS_MULTICOLOR=y

Security Requirements

  • Root access required (plugin operates with elevated privileges; runs as root via Decky Loader)
  • The emdoor-wmi driver does not require Secure Boot to be disabled on the platforms it currently supports

Installation

Quick Install

curl -L https://raw.githubusercontent.com/fewtarius/nimo-rgb/main/install.sh | sh
sudo systemctl restart plugin_loader

The 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 sudo internally 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.json so 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.

Manual Installation

  1. Download the latest release:

    wget https://github.com/fewtarius/nimo-rgb/releases/latest/download/nimo-rgb.zip
  2. 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
  3. 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
  4. Restart Decky Loader:

    sudo systemctl restart plugin_loader
  5. Reboot to ensure the LED class devices are present at boot.

Verification

  1. 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
  2. Open the Decky Loader overlay and select Nimo RGB. The panel should show six zones and a Mode dropdown.

  3. Pick a color on any zone. The change should appear on the keyboard or chassis within a few hundred milliseconds.

  4. 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)"

Use

Lighting Modes

Two modes of operation, switched by the Mode dropdown:

Static

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

Firmware Modes

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

About the EC Fallback

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.

Build from Source

pnpm install
pnpm run build

Build 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.zip

To 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_loader

Versioning

The 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 release
sudo 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_loader

Troubleshooting

Plugin 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 != 2 is 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.json exists 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_loader for 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

Uninstallation

  1. Stop the plugin loader:

    sudo systemctl stop plugin_loader
  2. Remove the plugin files:

    sudo rm -rf ~/homebrew/plugins/nimo-rgb
  3. Remove persistent state (optional):

    rm -rf ~/.local/share/decky/plugins/nimo-rgb
  4. 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
  5. 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.

License

This project is licensed under the GNU General Public License v3.0 or later. See the LICENSE file for complete terms.

Related

  • emdoor-wmi - the kernel module this plugin talks to
  • PowerDeck - per-game TDP and power profile management for the same laptop class

About

Decky plugin for managing the RGB on a Nimo Axis and other compatible devices.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages