Skip to content

corsair: add read-only NIGHTSWORD RGB driver (VID 0x1b1c, PID 0x1b5c) - #63

Closed
kfrancis wants to merge 2 commits into
OpenMouse-Project:mainfrom
kfrancis:feat/corsair-nightsword
Closed

corsair: add read-only NIGHTSWORD RGB driver (VID 0x1b1c, PID 0x1b5c)#63
kfrancis wants to merge 2 commits into
OpenMouse-Project:mainfrom
kfrancis:feat/corsair-nightsword

Conversation

@kfrancis

Copy link
Copy Markdown
Contributor

Summary

New vendor: Corsair, starting with the NIGHTSWORD RGB (VID 0x1b1c, PID 0x1b5c). Read-only phase 1: the driver identifies the mouse and reports firmware, bootloader, polling rate, the enabled-stage mask, the current DPI stage, every enabled stage with its colour, lift-off height, and angle snapping. ui.settingsReady is false, there are no setters, and getDpiOptions() is empty.

  • src/corsair/index.ts — pure codec (constants, product table, corsairEncode.*, corsairDecode.*, corsairIsEcho).
  • src/drivers/corsair/hid.tsCorsairHidClient (serialised feature-report request queue, best-effort DPI reads that degrade to identity-only).
  • Registry wiring: DEVICE_DRIVERS, SupportedClient, VENDOR_ID.corsair, SUPPORTED_HID_FILTERS (with usage: 4), the brand union, the registry probe matrix, ./corsair subpath export, tsconfig path, root namespace.
  • captures/corsair-nightsword/ — protocol write-up, hex fixtures, and a README listing them.

Device, firmware, connection

  • Corsair NIGHTSWORD RGB, wired USB only (the mouse has no wireless mode).
  • Firmware 3.41, bootloader 3.08.
  • Config channel: the interface whose collection is usage page 0xffc2, usage 4, 64-byte feature reports on report id 0. MI_00 also exposes an 0xffc2 usage-3 collection with no feature report; isSupported() rejects it and the picker filter pins usage 4.

Tested on hardware

Windows 11, Chrome 152, OpenMouse control-panel with this package linked in:

  • Picker shows one entry; the driver claims only the usage-4 interface.
  • Full status read succeeds and matches the fixtures: ident, mask 0x0f, current stage, d0d3 with colours, lift 5, snap 0. Background refreshes over a 130 s session all succeeded.
  • With iCUE (app + service) running and actively applying its profile, reads keep working. The earlier "NotAllowedError when iCUE is running" observation was reproduced from the same page and traced to the usage-3 collection instead: sendFeatureReport on it throws exactly NotAllowedError: Failed to write the feature report., while the usage-4 interface answers. The error is still mapped to guidance that leads with re-picking the interface and falls back to closing iCUE.
  • Slot d0 is iCUE's Sniper stage; iCUE's numbered Stage 1–3 are d1d3. Confirmed against the iCUE DPI panel. The driver reports the Sniper slot separately and numbers dpiStages / activeDpiStage the way iCUE does.

Protocol evidence

ckb-next's NXP protocol (nxp_proto.h, dpi.c, device_mouse.c) with two corrections found on hardware, both covered by explicit tests:

  • GET replies carry DPI X/Y big-endian; SET payloads take them little-endian. Identity fields are little-endian.
  • SET commands produce no reply; the feature buffer keeps the previous GET. Writes must be confirmed with the matching GET.

Other facts honoured: profile flag byte 3 must be 0 (1 returns zeros on this device); a MOUSE_DPIPROF write without RGB bytes 9–11 clears the stage colour, so setStageDpi requires the triple; live writes are volatile across a power cycle.

Unknowns and assumptions

  • Lift-off height is reported raw (Lift-off height 5 in the firmware lines) and liftOffDistance stays null: the scale has not been mapped against iCUE's Surface Calibration settings.
  • MOUSE_SNAP SET carries a trailing 0x05 in ckb-next; unverified (encoded and tested, never sent).
  • Ident bytes 17–22 and 39/46/47 change between sessions and are not decoded.
  • Phase-2 SET encoders (setStage, setStageDpi, setDpiMask, setLift, setSnap, setPollMs) are in the codec with tests but the driver does not use them yet.

Checks

  • npm run check passes (1060 tests).
  • OpenMouse control-panel builds and its suite passes (114 tests) with this package linked; the matching app PR adds the NEEDS_OPEN entry, the supported-devices row, and a device-image fallback.

🤖 Generated with Claude Code

New vendor. Pure codec in src/corsair and a WebHID client in
src/drivers/corsair that reads identity, firmware, the enabled-stage
mask, the current stage, every enabled DPI stage with its colour,
lift-off height and angle snapping over 64-byte feature reports on the
0xffc2 / usage 4 config interface. Read-only for now: settingsReady is
false, no setters, getDpiOptions() is empty.
Protocol facts honoured, all verified on fw 3.41 / bl 3.08:
- GET = sendFeatureReport → ~20 ms → receiveFeatureReport, reply must
echo request bytes 0–3 (retried on a stale buffer); SET has no reply.
- Byte 3 must be 0 (live profile); 1 returns zeros on this device.
- DPI is big-endian in GET replies and little-endian in SET payloads;
identity fields are little-endian. Tested explicitly.
- A MOUSE_DPIPROF write without RGB bytes clears the stage colour, so
setStageDpi requires the triple.
- Slot d0 is iCUE's Sniper stage; numbered stages are d1–d5.
- isSupported() requires usage 4 with a feature report on id 0 so the
MI_00 usage-3 collection is never claimed; the picker filter includes
usage: 4.
The "NotAllowedError when iCUE is running" note from the original probe
turned out to be the wrong picker entry: the usage-3 collection has no
feature report and throws exactly that error, while the usage-4
interface keeps answering with iCUE active. The error is still mapped
to guidance, leading with the interface choice.
Registry wiring: DEVICE_DRIVERS, SupportedClient, VENDOR_ID.corsair,
SUPPORTED_HID_FILTERS, the brand union, the registry probe matrix
(0xffc2 page and usage 4), the ./corsair subpath export, tsconfig path
and root namespace. Fixtures and the protocol write-up live in
captures/corsair-nightsword.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@kfrancis

Copy link
Copy Markdown
ContributorAuthor

Matching app PR: OpenMouse-Project/openmouse#187 (NEEDS_OPEN entry, supported-devices row, device-image fallback).

…range)
Run against the NIGHTSWORD RGB on fw 3.41 with iCUE running: selecting
a stage with 07 13 02 00 <n> takes effect immediately (cursor speed
changes and the GET reads the new slot), angle snap accepts the write
with or without ckb-next's trailing 0x05, and lift height reads back
every value from 1 to 5. Raw log in captures/corsair-nightsword/
write-probe.txt; PROTOCOL.md's write table and open-questions list
updated. No driver behaviour changes.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@snekxssnekxs closed this in #66 Sep 6, 2026
@kfrancis
kfrancis deleted the feat/corsair-nightsword branch September 7, 2026 01:25
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@kfrancis