# Adding a device `lightctl` is an in-tree device registry. The Omarchy panel renders **capabilities**, not brand names. A new peripheral is one Rust file plus one registry line. ## Checklist 1. Copy [`src/devices/dummy.rs`](../src/devices/dummy.rs). 2. Pick a stable kebab-case `ID` (`corsair-k70`, `nzxt-kraken-elite`). Never use the USB product string as the id. 3. Fill in `DeviceInfo`: - `kind`: `mouse` | `aio` | `keyboard` | `other` (icon/order hint only) - `exclusive`: `true` if two processes must not share hidraw (LCD bulk uploads) - `udev`: hidraw + USB rules for your vid/pid using `MODE="0660", GROUP="input", TAG+="uaccess"` (never `MODE="0666"`). `lightctl udev` concatenates these; commit the result as `udev/70-lightctl.rules` so the tag is set before `73-seat-late.rules`. Install that file with `sudo install -m644`, never `sudo lightctl`. 4. `detect(&HidApi) -> bool` — vid/pid (and usage page if needed). Do not `open()` here. 5. `open` — take `crate::core::lock::acquire(ID)` when `exclusive`. Implement `DeviceSession`. 6. `status()` — return `DeviceStatus::present` with the capabilities you honor. Logical LCD rotation comes from `state::lcd_rotation(ID)`, not raw HID, if firmware does not restyle a playing GIF. 7. `apply(&Action)` — implement what you listed; return a clear unsupported error otherwise. Persist last color/mode/image with `crate::core::state`. 8. Add `&YourStruct` to `devices::all()` in [`src/devices/mod.rs`](../src/devices/mod.rs). 9. `cargo test --offline --lib` and a live `lightctl apply color '#ff0033'` if you have hardware. You do **not** edit `src/main.rs` clap enums or `omarchy-plugin/Panel.qml`. ## Status JSON contract `lightctl status --json` is schema version 1: ```json { "schema": 1, "devices": [ { "id": "...", "present": true, "capabilities": ["color"], "...": "..." } ], "last": { "color": "#ff0033", "devices": { "": { "mode": "static" } } } } ``` - Missing capability ⇒ that control is hidden. - `0` is a valid duty/brightness. Use `null` for unknown. - Keep `status()` cheap. Optional probes belong on `apply` / `probe`. ## Dummy device `LIGHTCTL_DUMMY=1 lightctl status --json` registers a fake device with color, brightness, cooling, and rotate so you can exercise the panel without hardware. ## If the closed Action set is not enough Add one verb to `Action` and one capability the panel already knows how to draw (or a new capability **and** a generic QML block keyed on that capability). Do not special-case a device id in the UI.