diff --git a/src/drivers/endgame/egg-op1-hid.ts b/src/drivers/endgame/egg-op1-hid.ts index 9f2365e..5c3081d 100644 --- a/src/drivers/endgame/egg-op1-hid.ts +++ b/src/drivers/endgame/egg-op1-hid.ts @@ -99,7 +99,7 @@ export class EggOp1HidClient { constructor(device: HIDDevice) { this.device = device; - this.profile = eggProfileForPid(device.productId, device.productName); + this.profile = eggProfileForPid(device.productId); } static isSupported(device: HIDDevice): boolean { diff --git a/src/drivers/endgame/egg-op1-protocol.test.ts b/src/drivers/endgame/egg-op1-protocol.test.ts index 7e6de3c..8f0c75d 100644 --- a/src/drivers/endgame/egg-op1-protocol.test.ts +++ b/src/drivers/endgame/egg-op1-protocol.test.ts @@ -41,13 +41,14 @@ test("OP1w 4K v2 wireless models are capped at 4000 Hz while wired 8K models kee assert.equal(EGG_DEVICE_PROFILES.get(0x1970)!.maxPollingHz, 4000); }); -test("the shared 4K v2 dongle PIDs resolve to XM2w by the mouse's own reported name, not OP1w by default", () => { - assert.equal(eggProfileForPid(0x1970).name, "Endgame Gear OP1w 4K v2"); - assert.equal(eggProfileForPid(0x1984).name, "Endgame Gear OP1w 4K v2"); - assert.equal(eggProfileForPid(0x1970, "Endgame Gear XM2w 4K v2").name, "Endgame Gear XM2w 4K v2"); - assert.equal(eggProfileForPid(0x1984, "Endgame Gear XM2w 4K v2").name, "Endgame Gear XM2w 4K v2"); - // A wired-model PID is never reinterpreted, even if a name happened to mention "xm2". - assert.equal(eggProfileForPid(0x1978, "xm2").name, "Endgame Gear OP1 8K v2"); +test("the shared 4K v2 dongle PIDs report a neutral OP1w/XM2w name, since WebHID has no way to tell them apart", () => { + // Confirmed on real hardware: an XM2w 4K v2 reports device.productName as + // "Endgame Gear OP1we" — the receiver's fixed USB descriptor string, the + // same regardless of which mouse is actually paired. There is no signal + // available to resolve this to one specific model, so the name says both + // rather than confidently claiming the wrong one. + assert.equal(eggProfileForPid(0x1970).name, "Endgame Gear OP1w/XM2w 4K v2"); + assert.equal(eggProfileForPid(0x1984).name, "Endgame Gear OP1w/XM2w 4K v2"); }); test("CPI ranges and quantization follow each sensor generation", () => { diff --git a/src/endgame-gear/op1.ts b/src/endgame-gear/op1.ts index 61c58e4..6f7259f 100644 --- a/src/endgame-gear/op1.ts +++ b/src/endgame-gear/op1.ts @@ -113,17 +113,21 @@ export const EGG_DEVICE_PROFILES: ReadonlyMap = new Ma motionSyncAt8k: true, maxPollingHz: 8000, }], - // OP1w 4K v2: first wireless model on the OP1-8K v2 config protocol. The - // dongle's own USB PID (0x1970) is reused from the older, unrelated OP1we - // (see egg-we-hid.ts) — descriptor-based detection there keeps the two - // drivers from both claiming it. See issue #107. That same dongle PID (and - // its 0x1984 successor) is also reused across the OP1w and XM2w 4K v2 - // mice — the receiver hardware doesn't distinguish them, only the mouse's - // own reported name does, so `eggProfileForPid` takes the device name and - // resolves to `EGG_XM2W_4K_V2_PIDS` below when it mentions "xm2". + // OP1w/XM2w 4K v2: first wireless models on the OP1-8K v2 config protocol. + // The dongle's own USB PID (0x1970) is reused from the older, unrelated + // OP1we (see egg-we-hid.ts) — descriptor-based detection there keeps the + // two drivers from both claiming it. See issue #107. That same dongle PID + // (and its 0x1984 successor) is ALSO shared between the OP1w and XM2w 4K v2 + // mice themselves — confirmed on real hardware (an XM2w 4K v2 reports as + // "Endgame Gear OP1we", the receiver's fixed USB descriptor string, with no + // "xm2" anywhere in it). WebHID has no way to tell them apart: the + // descriptor name is generic and fixed regardless of the paired mouse, and + // nothing in the config/firmware read protocol carries a model id. Rather + // than confidently claim the wrong specific model, this profile's name + // says both, until a real distinguishing signal turns up. [0x1984, { pid: 0x1984, - name: "Endgame Gear OP1w 4K v2", + name: "Endgame Gear OP1w/XM2w 4K v2", configFamily: "v2", sensorFamily: "paw3950", cpiMin: 10, @@ -137,7 +141,7 @@ export const EGG_DEVICE_PROFILES: ReadonlyMap = new Ma }], [0x1970, { pid: 0x1970, - name: "Endgame Gear OP1w 4K v2", + name: "Endgame Gear OP1w/XM2w 4K v2", configFamily: "v2", sensorFamily: "paw3950", cpiMin: 10, @@ -151,14 +155,6 @@ export const EGG_DEVICE_PROFILES: ReadonlyMap = new Ma }], ]); -/** PIDs whose dongle is shared between an OP1w and an XM2w 4K v2 mouse. */ -const EGG_XM2W_4K_V2_PIDS = new Set([0x1970, 0x1984]); - -const EGG_XM2W_4K_V2_PROFILE: EggDeviceProfile = { - ...EGG_DEVICE_PROFILES.get(0x1984)!, - name: "Endgame Gear XM2w 4K v2", -}; - export const EGG_REPORT = { config: 0xa0, command: 0xa1, @@ -233,8 +229,7 @@ export function eggNormalizeFeatureReport( return result; } -export function eggProfileForPid(pid: number, productName = ""): EggDeviceProfile { - if (EGG_XM2W_4K_V2_PIDS.has(pid) && productName.toLowerCase().includes("xm2")) return EGG_XM2W_4K_V2_PROFILE; +export function eggProfileForPid(pid: number): EggDeviceProfile { const profile = EGG_DEVICE_PROFILES.get(pid); if (!profile) throw new Error(`Unsupported Endgame Gear product 0x${pid.toString(16)}.`); return profile;