From 5a7412e91b4735a2835fd3fcf5c22055f327772b Mon Sep 17 00:00:00 2001 From: snekxs <26660858+snekxs@users.noreply.github.com> Date: Sun, 6 Sep 2026 15:39:56 -0600 Subject: [PATCH] endgame: show a neutral OP1w/XM2w 4K v2 name instead of guessing wrong MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Yesterday's fix assumed device.productName would carry the actual paired mouse's model. Confirmed on real hardware it doesn't: an XM2w 4K v2 reports productName as "Endgame Gear OP1we" — the receiver's fixed USB descriptor string, identical regardless of which mouse is paired. There is no "xm2" substring for the check to ever find. Checked for prior art: stuffz/mouse-battery-monitor documents this same shared dongle PID (0x1970) and states there is no known way to distinguish the model over the wire. Rather than confidently claim the wrong specific model, both PID entries now report a combined "Endgame Gear OP1w/XM2w 4K v2" name until a real signal turns up. --- src/drivers/endgame/egg-op1-hid.ts | 2 +- src/drivers/endgame/egg-op1-protocol.test.ts | 15 +++++---- src/endgame-gear/op1.ts | 35 +++++++++----------- 3 files changed, 24 insertions(+), 28 deletions(-) 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;