diff --git a/package-lock.json b/package-lock.json index 94ab9ae..5beed48 100644 --- a/package-lock.json +++ b/package-lock.json @@ -481,7 +481,7 @@ }, "node_modules/@openmouse/protocol": { "version": "0.1.0", - "resolved": "git+ssh://git@github.com/OpenMouse-Project/mouse-protocol.git#badc8d35b00af881f6ebf9e9a812ffd903b97b76", + "resolved": "git+ssh://git@github.com/OpenMouse-Project/mouse-protocol.git#541f39eeb5b2cf5761bb1239e8d825a7052004fe", "engines": { "node": ">=20" } diff --git a/src/device/controller.ts b/src/device/controller.ts index af8f51f..1e74c07 100644 --- a/src/device/controller.ts +++ b/src/device/controller.ts @@ -118,6 +118,7 @@ import { FinalmouseHidClient } from "@openmouse/protocol/drivers/finalmouse/hid" import { ModdoHidClient } from "@openmouse/protocol/drivers/moddo/hid"; import { NinjutsoHidClient } from "@openmouse/protocol/drivers/ninjutso/hid"; import { ZaunkoenigHidClient } from "@openmouse/protocol/drivers/zaunkoenig/hid"; +import { CorsairHidClient } from "@openmouse/protocol/drivers/corsair/hid"; import { TeevolutionHidClient } from "@openmouse/protocol/drivers/teevolution/hid"; import { teevolutionProfileForCid } from "@openmouse/protocol/teevolution"; import { VgnF2HidClient } from "@openmouse/protocol/drivers/vgn/hid"; @@ -197,7 +198,7 @@ function activeAs(...classes: ClientClass[]): T | null { const DM_CLASSES = [WLMouseHidClient, LamzuHidClient, AtkHidClient, AtkBitmouseHidClient, NinjutsoHidClient] as const; const RAZER_CLASSES = [RazerHidClient, RazerViperMiniHidClient, RazerViperHidClient, RazerCobraHidClient] as const; -const NEEDS_OPEN = [TeevolutionHidClient, VgnF2HidClient, KeychronNapeHidClient, KeychronM6HidClient, ModdoHidClient, ZaunkoenigHidClient, FantechHidClient, WallhackMouseHidClient, WallhackKeyboardHidClient, GloriousHidClient, GloriousClassicHidClient, MchoseHidClient, MchoseDockHidClient, MicrosoftHidClient] as const; +const NEEDS_OPEN = [TeevolutionHidClient, VgnF2HidClient, KeychronNapeHidClient, KeychronM6HidClient, ModdoHidClient, ZaunkoenigHidClient, CorsairHidClient, FantechHidClient, WallhackMouseHidClient, WallhackKeyboardHidClient, GloriousHidClient, GloriousClassicHidClient, MchoseHidClient, MchoseDockHidClient, MicrosoftHidClient] as const; const PULSAR_CLASSES = [PulsarHidClient, PulsarProHidClient, PulsarXs1HidClient] as const; const logitechClient = (): LogitechHidppClient | null => activeAs(LogitechHidppClient); diff --git a/src/supported-mice.test.ts b/src/supported-mice.test.ts index ed16676..d178d65 100644 --- a/src/supported-mice.test.ts +++ b/src/supported-mice.test.ts @@ -25,6 +25,7 @@ import { PULSAR_XS1_PRODUCT_IDS } from "@openmouse/protocol/pulsar"; import { RAZER_PRODUCTS } from "@openmouse/protocol/razer-devices"; import { TEEVOLUTION_PRODUCT_IDS } from "@openmouse/protocol/teevolution"; import { ZAUNKOENIG_PRODUCT_IDS } from "@openmouse/protocol/zaunkoenig"; +import { CORSAIR_PRODUCT_IDS } from "@openmouse/protocol/corsair"; import { MICE, STATUS, type Mouse, type Status } from "./supported-mice.ts"; @@ -97,6 +98,7 @@ const PID_UNIVERSE = new Set([ ...KEYCHRON_NAPE_PRODUCTS.keys(), ...TEEVOLUTION_PRODUCT_IDS, ...ZAUNKOENIG_PRODUCT_IDS, + ...CORSAIR_PRODUCT_IDS, ...NINJUTSO_LEGACY_MOUSE_PRODUCT_IDS, ...NINJUTSO_MOUSE_PRODUCT_IDS, ...NINJUTSO_LEGACY_RECEIVER_PRODUCT_IDS, diff --git a/src/supported-mice.ts b/src/supported-mice.ts index 3364c69..cdd3c3e 100644 --- a/src/supported-mice.ts +++ b/src/supported-mice.ts @@ -346,6 +346,9 @@ export const MICE: Mouse[] = [ note: "DPI presets, polling, LED color, sleep/dim timers, default lighting, and buttons are write-only; battery percentage/charging is read live (no firmware-query command in this protocol). PID 0x184a (wired mode) and 0x1848 (2.4 GHz mode) in SteelSeries Prime Mini Wireless driver" }, // CORSAIR ───────────────────────────────────────────────────────────── + { brand: "Corsair", model: "NIGHTSWORD RGB", status: "supported", req: 1, + pids: [0x1b5c], + note: "Read-only: identity, firmware, live DPI stages, polling rate, lift-off height, and angle snapping over the 0xffc2/usage-4 feature-report interface (fw 3.41). Close iCUE first — it holds the control interface. PID 0x1b5c in Corsair driver" }, { brand: "Corsair", model: "Harpoon RGB Pro", status: "driver", req: 5, note: "iCUE protocol — not implemented" }, { brand: "Corsair", model: "Katar Pro", status: "driver", req: 4, diff --git a/src/ui/device-images.test.ts b/src/ui/device-images.test.ts index 8890709..0ea7907 100644 --- a/src/ui/device-images.test.ts +++ b/src/ui/device-images.test.ts @@ -36,6 +36,11 @@ test("Razer Orochi V2 uses its own render over its Atheris receiver", () => { assert.equal(deviceImage({ vendorId: 0x1532, productId: 0x0094 } as HIDDevice), CDN + "razer-orochi-v2.png"); }); +test("Corsair NIGHTSWORD RGB falls back to the placeholder until art exists", () => { + assert.equal(deviceImage({ vendorId: 0x1b1c, productId: 0x1b5c } as HIDDevice, "Corsair NIGHTSWORD RGB"), CDN + "unknown-device.png"); + assert.equal(deviceImage(null, "CORSAIR NIGHTSWORD RGB Gaming Mouse"), CDN + "unknown-device.png"); +}); + test("fixture previews resolve product art without a HID device", () => { assert.equal(deviceImage(null, "CRDRAKO KO-ONE"), CDN + "crdrako-ko-one.png"); assert.equal(deviceImage(null, "Zaunkoenig M3K"), CDN + "zaunkoenig-m3k.png"); diff --git a/src/ui/device-images.ts b/src/ui/device-images.ts index f5434ab..4dc50ad 100644 --- a/src/ui/device-images.ts +++ b/src/ui/device-images.ts @@ -180,6 +180,9 @@ function resolveDeviceImageFilename(device: HIDDevice | null | undefined, displa if (/\bterra\s*pro\b/i.test(displayName)) return "teevolution-terra-pro.png"; if (/\bm-001\b/i.test(displayName)) return "wallhack-m-001.png"; if (/\bk-001\b/i.test(displayName)) return "wallhack-k-001.png"; + // Corsair NIGHTSWORD RGB has no product render yet; resolves to the generic + // placeholder until art is uploaded (then add ["1b1c:1b5c", ...] above). + if (/\bnightsword\b/i.test(displayName)) return "unknown-device.png"; // Newer supported-model artwork resolved from the reported product name. These // run after the shared-receiver checks above but before the Pulsar/unknown // catch-alls. Test-needed (likely) models are deliberately left out.