- Rust 96.2%
- Just 3.3%
- Nushell 0.5%
|
All checks were successful
Check / fmt + clippy + build + tests (push) Successful in 32s
Reviewed-on: #24 |
||
|---|---|---|
| .forgejo/workflows | ||
| .idea | ||
| crates | ||
| install_files/udev | ||
| oci-build | ||
| .dockerignore | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| CLAUDE.md | ||
| justfile | ||
| LICENSE.md | ||
| README.md | ||
umbra
Standalone, cross-platform Rust tools for configuring Razer mice. No kernel modules, no daemon, no D-Bus. Talks to the device directly over HID feature reports using the OS's built-in HID stack (hidraw on Linux, IOKit on macOS, HID.dll on Windows).
The wire protocol is ported from the OpenRazer kernel driver, which is the de-facto reference for Razer's USB control protocol.
Supported devices
| Device | VID:PID | DPI max | Polling | Lighting | Battery |
|---|---|---|---|---|---|
| Razer Cobra | 1532:00A3 | 8500 | yes | Logo (1 zone) | n/a |
| Razer Cobra Pro (Wired) | 1532:00AF | 30,000 | yes | Logo + Scroll, static/spectrum | yes |
| Razer Cobra Pro (Wireless) | 1532:00B0 | 30,000 | yes | Logo + Scroll, static/spectrum | yes |
| Razer DeathAdder 2013 | 1532:0037 | n/a | yes | none | n/a |
| Razer DeathAdder 1800 | 1532:0038 | n/a | yes | none | n/a |
| Razer DeathAdder Chroma | 1532:0043 | 10,000 | yes | none | n/a |
| Razer DeathAdder 2000 | 1532:004F | 2000 | yes | none | n/a |
| Razer DeathAdder 3500 | 1532:0054 | 3500 | yes | none | n/a |
| Razer DeathAdder Elite | 1532:005C | 16,000 | yes | Logo + Scroll (2 zones) | n/a |
| Razer Mamba Elite | 1532:006C | 16,000 | yes | Logo + Scroll + L + R (4 zones) | n/a |
| Razer Lancehead (Wired) | 1532:0059 | 16,000 | yes | Logo + Scroll + L + R (4 zones) | yes |
| Razer Lancehead (Wireless) | 1532:005A | 16,000 | yes | Logo + Scroll + L + R (4 zones) | yes |
| Razer Lancehead Tournament Edition | 1532:0060 | 16,000 | yes | Logo + Scroll + L + R (4 zones) | n/a |
| Razer Lancehead Wireless 2018 (Receiver) | 1532:006F | 16,000 | yes | Logo + Scroll + L + R (4 zones) | yes |
| Razer Lancehead Wireless 2018 (Wired) | 1532:0070 | 16,000 | yes | Logo + Scroll + L + R (4 zones) | yes |
| Razer Mamba 2012 (Wired) | 1532:0024 | 6400 | yes | none | yes |
| Razer Mamba 2012 (Wireless) | 1532:0025 | 6400 | yes | none | yes |
| Razer Mamba 2015 (Wired) | 1532:0044 | 16,000 | yes | none | yes |
| Razer Mamba 2015 (Wireless) | 1532:0045 | 16,000 | yes | none | yes |
| Razer Mamba Tournament Edition | 1532:0046 | 16,000 | no | none | n/a |
| Razer Mamba Wireless 2018 (Receiver) | 1532:0072 | 16,000 | yes | Logo + Scroll (2 zones) | yes |
| Razer Mamba Wireless 2018 (Wired) | 1532:0073 | 16,000 | yes | Logo + Scroll (2 zones) | yes |
| Razer DeathAdder Essential | 1532:006E | 6400 | yes | Logo + Scroll, limited effects | n/a |
| Razer DeathAdder Essential (White) | 1532:0071 | 6400 | yes | Logo + Scroll, limited effects | n/a |
| Razer DeathAdder Essential (2021) | 1532:0098 | 6400 | yes | Logo + Scroll, limited effects | n/a |
| Razer DeathAdder V2 Pro (Wired) | 1532:007C | 20,000 | yes | Logo (1 zone) | yes |
| Razer DeathAdder V2 Pro (Wireless) | 1532:007D | 20,000 | yes | Logo (1 zone) | yes |
| Razer DeathAdder V2 | 1532:0084 | 20,000 | yes | Logo + Scroll (2 zones) | n/a |
| Razer DeathAdder V2 Mini | 1532:008C | 8500 | yes | Logo (1 zone) | n/a |
| Razer DeathAdder V2 X HyperSpeed | 1532:009C | 14,000 | yes | none | yes |
| Razer DeathAdder V2 Lite | 1532:00A1 | 8500 | yes | Logo (1 zone) | n/a |
| Razer DeathAdder V3 | 1532:00B2 | 30,000 | no | none | n/a |
| Razer DeathAdder V3 Pro (Wired) | 1532:00B6 | 30,000 | yes | none | yes |
| Razer DeathAdder V3 Pro (Wireless) | 1532:00B7 | 30,000 | yes | none | yes |
| Razer DeathAdder V3 Pro (Wired, alt PID) | 1532:00C2 | 30,000 | yes | none | yes |
| Razer DeathAdder V3 Pro (Wireless, alt PID) | 1532:00C3 | 30,000 | yes | none | yes |
| Razer DeathAdder V3 HyperSpeed (Wired) | 1532:00C4 | 30,000 | yes | none | yes |
| Razer DeathAdder V3 HyperSpeed (Wireless) | 1532:00C5 | 30,000 | yes | none | yes |
| Razer DeathAdder V4 Pro (Wired) | 1532:00BE | 45,000 | no | none | yes |
| Razer DeathAdder V4 Pro (Wireless) | 1532:00BF | 45,000 | no | none | yes |
| Razer Viper Mini | 1532:008A | 8500 | yes | Logo (1 zone) | n/a |
| Razer Basilisk | 1532:0064 | 16,000 | yes | Logo + Scroll (2 zones) | n/a |
| Razer Basilisk Essential | 1532:0065 | 6400 | yes | Logo (1 zone) | n/a |
| Razer Basilisk X HyperSpeed | 1532:0083 | 16,000 | yes | none | yes |
| Razer Basilisk V2 | 1532:0085 | 26,000 | yes | Logo + Scroll (2 zones) | n/a |
| Razer Basilisk Ultimate (Wired) | 1532:0086 | 26,000 | yes | Logo + Scroll + L + R (4 zones) | yes |
| Razer Basilisk Ultimate (Receiver) | 1532:0088 | 26,000 | yes | Logo + Scroll + L + R (4 zones) | yes |
| Razer Basilisk V3 | 1532:0099 | 26,000 | yes | Logo + Scroll, static/spectrum | n/a |
| Razer Basilisk V3 Pro (Wired) | 1532:00AA | 30,000 | yes | Logo + Scroll, static/spectrum | yes |
| Razer Basilisk V3 Pro (Wireless) | 1532:00AB | 30,000 | yes | Logo + Scroll, static/spectrum | yes |
| Razer Basilisk V3 X HyperSpeed | 1532:00B9 | 18,000 | yes | Scroll (1 zone) | yes |
| Razer Basilisk V3 35K | 1532:00CB | 35,000 | yes | Logo + Scroll, static/spectrum | n/a |
| Razer Basilisk V3 Pro 35K (Wired) | 1532:00CC | 35,000 | yes | Logo + Scroll, static/spectrum | yes |
| Razer Basilisk V3 Pro 35K (Wirelss) | 1532:00CD | 35,000 | yes | Logo + Scroll, static/spectrum | yes |
| Razer Basilisk Mobile (Wired) | 1532:00D3 | 18,000 | yes | none | yes |
| Razer Basilisk Mobile (Receiver) | 1532:00D4 | 18,000 | yes | none | yes |
| Razer Basilisk V3 Pro 35K Phantom Green (Wired) | 1532:00D6 | 35,000 | yes | Scroll (1 zone) | yes |
| Razer Basilisk V3 Pro 35K Phantom Green (Wireless) | 1532:00D7 | 35,000 | yes | Scroll (1 zone) | yes |
| Razer Viper | 1532:0078 | 20,000 | yes | Logo (1 zone) | n/a |
| Razer Viper Ultimate (Wired) | 1532:007A | 20,000 | yes | Logo (1 zone) | yes |
| Razer Viper Ultimate (Wireless) | 1532:007B | 20,000 | yes | Logo (1 zone) | yes |
| Razer Viper 8KHz | 1532:0091 | 20,000 | no | Logo (1 zone) | n/a |
| Razer Viper Mini SE (Wired) | 1532:009E | 30,000 | no | none | yes |
| Razer Viper Mini SE (Wireless) | 1532:009F | 30,000 | no | none | yes |
| Razer Viper V2 Pro (Wired) | 1532:00A5 | 30,000 | yes | none | yes |
| Razer Viper V2 Pro (Wireless) | 1532:00A6 | 30,000 | yes | none | yes |
| Razer Viper V3 HyperSpeed | 1532:00B8 | 30,000 | yes | none | yes |
| Razer Viper V3 Pro (Wired) | 1532:00C0 | 35,000 | yes | none | yes |
| Razer Viper V3 Pro (Wireless) | 1532:00C1 | 35,000 | no | none | yes |
| Razer Naga | 1532:0015 | n/a | yes | none | n/a |
| Razer Naga Epic | 1532:001F | n/a | yes | none | yes |
| Razer Naga 2012 | 1532:002E | n/a | yes | none | n/a |
| Razer Naga Hex (Red) | 1532:0036 | n/a | yes | none | n/a |
| Razer Naga Epic Chroma | 1532:003E | 8200 | yes | none | yes |
| Razer Naga Epic Chroma Dock | 1532:003F | 8200 | yes | none | yes |
| Razer Naga 2014 | 1532:0040 | 8200 | yes | none | n/a |
| Razer Naga Hex | 1532:0041 | n/a | yes | none | n/a |
| Razer Naga Hex V2 | 1532:0050 | 16,000 | yes | none | n/a |
| Razer Naga Chroma | 1532:0053 | 16,000 | yes | none | n/a |
| Razer Naga Trinity | 1532:0067 | 16,000 | yes | Matrix (1 zone), static | n/a |
| Razer Naga Left-Handed Ed. 2020 | 1532:008D | 16,000 | yes | Logo + Scroll + R (3 zones) | n/a |
| Razer Naga Pro (Wired) | 1532:008F | 20,000 | yes | Logo + Scroll (2 zones) | yes |
| Razer Naga Pro (Wireless) | 1532:0090 | 20,000 | yes | Logo + Scroll (2 zones) | yes |
| Razer Naga X | 1532:0096 | 18,000 | yes | Scroll + Left (2 zones) | n/a |
| Razer Naga V2 Pro (Wired) | 1532:00A7 | 30,000 | yes | Logo (1 zone) | yes |
| Razer Naga V2 Pro (Wireless) | 1532:00A8 | 30,000 | yes | Logo (1 zone) | yes |
| Razer Naga V2 HyperSpeed (Recv.) | 1532:00B4 | 30,000 | yes | none | yes |
| Razer Abyssus 1800 | 1532:0020 | n/a | yes | none | n/a |
| Razer Abyssus 2014 | 1532:0042 | n/a | yes | none | n/a |
| Razer Abyssus V2 | 1532:005B | 5000 | yes | none | n/a |
| Razer Abyssus 2000 | 1532:005E | 2000 | yes | none | n/a |
| Razer Abyssus Elite (D.Va Edition) | 1532:006A | 7200 | yes | Logo (1 zone) | n/a |
| Razer Abyssus Essential | 1532:006B | 7200 | yes | Logo (1 zone) | n/a |
| Razer Imperator 2012 | 1532:002F | 6400 | yes | none | n/a |
| Razer Ouroboros 2012 | 1532:0032 | 8200 | yes | none | yes |
| Razer Taipan | 1532:0034 | 8200 | yes | none | n/a |
| Razer Diamondback Chroma | 1532:004C | 16,000 | no | none | n/a |
| Razer Atheris (Receiver) | 1532:0062 | 7200 | yes | none | yes |
| Razer HyperPolling Wireless Dongle | 1532:00B3 | 30,000 | no | none | yes |
| Razer Pro Click (Receiver) | 1532:0077 | 16,000 | yes | none | yes |
| Razer Pro Click (Wired) | 1532:0080 | 16,000 | yes | none | yes |
| Razer Pro Click Mini (Receiver) | 1532:009A | 12,000 | yes | none | yes |
| Razer Pro Click V2 Vertical Ed. (Wired) | 1532:00C7 | 30,000 | yes | Matrix (1 zone), static/spectrum | yes |
| Razer Pro Click V2 Vertical Ed. (Wireless) | 1532:00C8 | 30,000 | yes | Matrix (1 zone), static/spectrum | yes |
| Razer Pro Click V2 (Wired) | 1532:00D0 | 30,000 | yes | Matrix (1 zone), static/spectrum | yes |
| Razer Pro Click V2 (Wireless) | 1532:00D1 | 30,000 | yes | Matrix (1 zone), static/spectrum | yes |
| Razer Orochi 2011 | 1532:0013 | n/a | no | none | n/a |
| Razer Orochi 2013 | 1532:0039 | 6400 | yes | none | n/a |
| Razer Orochi Chroma | 1532:0048 | 8200 | yes | none | n/a |
| Razer Orochi V2 (Receiver) | 1532:0094 | 18,000 | yes | none | yes |
| Razer Orochi V2 (Bluetooth) | 1532:0095 | 18,000 | yes | none | yes |
Polling rates supported on every model: 125 / 500 / 1000 Hz. The 4 / 8 kHz rates available on some Pro models use a different command path and are not wired up yet.
Some older Naga models list n/a DPI / none lighting because their pre-Chroma protocol (1-byte DPI, on/off LEDs) or non-extended matrix path (Naga Chroma, Naga Hex V2, Epic Chroma) needs command builders umbra does not ship yet; those PIDs are still recognized by umbra info, and the missing builders are tracked as follow-up work. DPI ceilings for the Naga line are sensor-derived (the OpenRazer driver does not clamp DPI) and noted as such in each model file.
Adding more devices is a matter of adding a DeviceSpec to crates/umbra-hid/src/models/, mirroring the per-PID
branches in OpenRazer's razermouse_driver.c.
Build prerequisites
Linux
The hidapi crate links against libudev for HID device enumeration:
- openSUSE:
sudo zypper install systemd-devel libusb-1_0-devel pkg-config - Debian/Ubuntu:
sudo apt install libudev-dev libusb-1.0-0-dev pkg-config - Fedora:
sudo dnf install systemd-devel libusbx-devel pkgconf-pkg-config
macOS
No extra dependencies. The hidapi crate uses the IOKit framework that ships with the OS. Build natively:
cd rust
cargo build --release
./target/release/umbra info
Windows
No extra dependencies. The hidapi crate uses Win32's HID API. Build natively under PowerShell or cmd:
cd rust
cargo build --release
.\target\release\umbra.exe info
Cross-compilation: not provided
This repo only ships native build paths for each OS. Cross-compiling the GUI to macOS or Windows from Linux is
technically possible but pulls in non-redistributable Apple SDKs or a fragile mingw + winit toolchain. CI verifies all
three OS targets per push (.github/workflows/rust.yml).
Build and run (native)
cd rust
cargo build --release
# CLI: install the udev rule once, then run as your user.
./target/release/umbra udev install
./target/release/umbra info
./target/release/umbra dpi 1600
./target/release/umbra poll 1000
./target/release/umbra effect spectrum
./target/release/umbra effect static '#ff0000'
./target/release/umbra --device 1532:00a3 -vv info # target a specific PID, verbose log
# GUI: hot-reconnects automatically when the device is replugged.
./target/release/umbra-gui
On macOS and Windows, sudo is not required.
Why no daemon and no D-Bus?
D-Bus is just IPC. Something at the bottom of the stack still has to do USB I/O. OpenRazer puts that work in a kernel module that the daemon talks to over sysfs:
client -> D-Bus -> openrazer-daemon -> sysfs -> razermouse.ko -> USB controller -> mouse
We do the USB I/O ourselves, in our process, using the HID layer the OS already ships:
us -> hidapi -> hidraw (Linux) | IOKit (macOS) | HID.dll (Windows) -> mouse
No out-of-tree kernel module, no service, no IPC.
Linux permissions
By default /dev/hidraw* is root-owned, so the CLI and GUI need sudo. The bundled udev rule grants the active
local-seat user access via uaccess (no group membership needed). Install it through the CLI:
./umbra udev install # re-execs via sudo if not already root
./umbra udev show # print the rule body and installed status
./umbra udev uninstall # remove and reload
The rule body lives at install_files/udev/99-umbra-userspace.rules (also embedded in the binary via include_str!).
It tags every hidraw* and usb node belonging to vendor 0x1532 with uaccess, which systemd-logind translates
into ACLs for the currently active session. Replug the mouse (or reboot) after install. After that, ./umbra info and
./umbra-gui work without sudo.
Wayland note: running GUI apps with sudo on Wayland fails because the compositor socket lives in the user's
$XDG_RUNTIME_DIR and X authority lives in $XAUTHORITY. The udev rule is the right fix; running as root is not.