Skip to content
Merged
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
2 changes: 1 addition & 1 deletion src/drivers/endgame/egg-op1-hid.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -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 {
Expand Down
15 changes: 8 additions & 7 deletions src/drivers/endgame/egg-op1-protocol.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -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", () => {
Expand Down
35 changes: 15 additions & 20 deletions src/endgame-gear/op1.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -113,17 +113,21 @@ export const EGG_DEVICE_PROFILES: ReadonlyMap<number, EggDeviceProfile> = 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,
Expand All@@ -137,7 +141,7 @@ export const EGG_DEVICE_PROFILES: ReadonlyMap<number, EggDeviceProfile> = new Ma
}],
[0x1970, {
pid: 0x1970,
name: "Endgame Gear OP1w 4K v2",
name: "Endgame Gear OP1w/XM2w 4K v2",
configFamily: "v2",
sensorFamily: "paw3950",
cpiMin: 10,
Expand All@@ -151,14 +155,6 @@ export const EGG_DEVICE_PROFILES: ReadonlyMap<number, EggDeviceProfile> = 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,
Expand DownExpand Up@@ -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;
Expand Down