Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion public/devices/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,11 @@ package:
- `logitech-mx-anywhere-3.png` — Logitech MX Anywhere 3 top-view render
- `logitech-mx-ergo-s.png` — Logitech MX Ergo S top-view render
- `razer-deathadder-v2.png` — Razer DeathAdder V2 (V2 / V2 Pro / Essential share the shell)
- `razer-deathadder-v3.png` — Razer DeathAdder V3 render
- `razer-deathadder-v3.png` — Razer DeathAdder V3 render. The DeathAdder V3 Pro
shares the shell and resolves to this file too (a dedicated cable-free V3 Pro
render would be an improvement). **Not in the bucket as of September 2026**:
`img.openmouse.app/razer-deathadder-v3.png` returns 404, so both models show
the placeholder (and the showcase entry 404s) until a maintainer uploads it.
- `razer-deathadder-v4-pro.png` — Razer DeathAdder V4 Pro (Carbon Fiber SKU shares the shell)
- `razer-viper-v3-hyperspeed.png` — Razer Viper V3 HyperSpeed render
- `razer-viper-v4-pro.png` — Razer Viper V4 Pro render
Expand Down
11 changes: 7 additions & 4 deletions src/ui/device-images.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import assert from "node:assert/strict";
import test from "node:test";

import { deviceImage } from "./device-images.ts";
import { deviceImage, isUnknownDevice } from "./device-images.ts";

const CDN = "https://img.openmouse.app/";

Expand Down Expand Up @@ -141,13 +141,16 @@ test("DeathAdder V2 family shares the V2 render; V4 Pro gets its own", () => {
assert.equal(deviceImage(null, "DeathAdder Essential"), CDN + "razer-deathadder-v2.png");
assert.equal(deviceImage(null, "DeathAdder V4 Pro"), CDN + "razer-deathadder-v4-pro.png");
assert.equal(deviceImage(null, "DeathAdder V4 Pro Carbon Fiber Edition"), CDN + "razer-deathadder-v4-pro.png");
// Test-needed V3 Pro and V2 X HyperSpeed must NOT pick up V3/V2 artwork.
assert.equal(deviceImage(null, "DeathAdder V3 Pro"), CDN + "unknown-device.png");
// Test-needed V2 X HyperSpeed must NOT pick up V2 artwork.
assert.equal(deviceImage(null, "DeathAdder V2 X HyperSpeed"), CDN + "unknown-device.png");
});

test("DeathAdder V3 wired resolves by name", () => {
test("DeathAdder V3 and V3 Pro share the V3 render", () => {
assert.equal(deviceImage(null, "DeathAdder V3"), CDN + "razer-deathadder-v3.png");
// The name the Razer driver reports for the verified 0x00b7 receiver.
assert.equal(deviceImage(null, "Razer DeathAdder V3 Pro"), CDN + "razer-deathadder-v3.png");
assert.equal(deviceImage(null, "DeathAdder V3 Pro (Wired)"), CDN + "razer-deathadder-v3.png");
assert.equal(isUnknownDevice(null, "DeathAdder V3 Pro"), false);
});

test("Viper family resolves each generation to its own render", () => {
Expand Down
6 changes: 5 additions & 1 deletion src/ui/device-images.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,11 @@ function resolveDeviceImageFilename(_device: HIDDevice | null | undefined, displ
if (/\bmx\s*anywhere\s*3\b/i.test(displayName)) return "logitech-mx-anywhere-3.png";
if (/\bmx\s*ergo\b/i.test(displayName)) return "logitech-mx-ergo-s.png";
if (/\bdeathadder\s*v4\b/i.test(displayName)) return "razer-deathadder-v4-pro.png";
if (/\bdeathadder\s*v3\b(?!\s*pro\b)/i.test(displayName)) return "razer-deathadder-v3.png";
// V3 and V3 Pro are one shell (the Pro drops the cable), so the Pro shares
// the V3 render the same way the V2 family shares one below. The Pro used
// to be excluded here while it was still test-needed; it has since been
// verified on hardware (mouse-protocol `0x00b7`).
if (/\bdeathadder\s*v3\b/i.test(displayName)) return "razer-deathadder-v3.png";
if (/\bdeathadder\s*v2\b(?!\s*x\s*hyperspeed\b)/i.test(displayName)) return "razer-deathadder-v2.png";
if (/\bdeathadder\s*essential\b/i.test(displayName)) return "razer-deathadder-v2.png";
if (/\bviper\s*v3\s*hyperspeed\b/i.test(displayName)) return "razer-viper-v3-hyperspeed.png";
Expand Down