From e2d9df3c9f7cc21c3915e3a3396162537d09dde7 Mon Sep 17 00:00:00 2001 From: Onyx Date: Mon, 26 Oct 2026 02:19:51 +0100 Subject: [PATCH] feat(gearhub): add Attack Shark X3 Pro (device id 1660) Identified from a user diagnostic on 0x3151:0x402D: firmware v3.00, six-stage DPI table, polling up to 8000 Hz. Previously fell back to the generic GearHub V5 profile. --- src/drivers/gearhub/hid.test.ts | 14 ++++++++++++++ src/gearhub/index.test.ts | 2 +- src/gearhub/index.ts | 19 +++++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/drivers/gearhub/hid.test.ts b/src/drivers/gearhub/hid.test.ts index 245c24aa..559ba067 100644 --- a/src/drivers/gearhub/hid.test.ts +++ b/src/drivers/gearhub/hid.test.ts @@ -630,6 +630,20 @@ describe("GearHubHidClient", () => { assert.equal(client.getDpiOptions().at(-1), 26000, "PAW3395 DPI ceiling"); }); + it("identifies the Attack Shark X3 Pro (device id 1660, PAW3395)", async () => { + const { device } = fakeReceiver({ + deviceId: 1660, + replies: { + [CMD.GET_DPI]: dpiReply([1800, 26000], 1), + [CMD.GET_OPTIONPARAM0]: opt0Reply({ rate: 129, lod: 0 }), + }, + }); + const status = await new GearHubHidClient(device).readStatus(); + + assert.equal(status.name, "Attack Shark X3 Pro"); + assert.ok(status.firmware.includes("PixArt PAW3395")); + }); + it("maps the PAW3950 R3 revision (device id 3310) to the 42000 DPI profile", async () => { const { device } = fakeReceiver({ deviceId: 3310, diff --git a/src/gearhub/index.test.ts b/src/gearhub/index.test.ts index b9a3721b..895d14af 100644 --- a/src/gearhub/index.test.ts +++ b/src/gearhub/index.test.ts @@ -30,7 +30,7 @@ it("keeps AJ179 model quirks in its profile without changing sibling button layo assert.equal(AJAZZ_AJ179_PRO_PROFILE.buttons?.find((button) => button.name === "Forward")?.slot, 3); assert.equal(AJAZZ_AJ179_PRO_PROFILE.dpiStageCountIsCapacity, true); assert.equal(AJAZZ_AJ179_PRO_PROFILE.batteryPercentageOnly, true); - for (const id of [2285, 1893, 1643, 3310, 0]) { + for (const id of [2285, 1893, 1643, 3310, 1660, 0]) { const profile = gearHubProfileFor(id); assert.equal(profile.buttons, undefined); assert.equal(profile.dpiStageCountIsCapacity, undefined); diff --git a/src/gearhub/index.ts b/src/gearhub/index.ts index d7143b8d..a10ac489 100644 --- a/src/gearhub/index.ts +++ b/src/gearhub/index.ts @@ -345,6 +345,24 @@ export const ATTACK_SHARK_R3_3950_PROFILE: GearHubProfile = { maxDpi: 42000, }; +/** + * Attack Shark X3 Pro. Same GearHub-V5 platform, VID:PID and block protocol as + * the R2 / R3. Device id 1660 (0x067C) confirmed from a user diagnostic on + * 0x3151:0x402D (2.4 GHz receiver) - firmware v3.00, six-stage DPI table, + * polling up to 8000 Hz, every relay read answering. The PAW3395 sensor and + * its 26000 DPI ceiling are from the vendor spec, not read from the device. + */ +export const ATTACK_SHARK_X3_PRO_PROFILE: GearHubProfile = { + deviceId: 1660, + brand: "Attack Shark", + model: "X3 Pro", + sensor: "PixArt PAW3395", + maxPollingHz: 8000, + minDpi: 50, + maxDpi: 26000, + dpiStep: 50, +}; + /** AJ179 PRO, device id 1851. Verified on receiver, USB cable and Bluetooth. * Vendor AJAZZ Driver (R) also maps id 1851 to its PAW3395 AJ179 PRO profile. * See docs/ajazz-aj179-pro.md for measured capabilities and limitations. */ @@ -379,6 +397,7 @@ export const GEARHUB_DEVICE_PROFILES: ReadonlyMap = new [3485, ATTACK_SHARK_R3_PROFILE], [ATTACK_SHARK_R3_3950_PROFILE.deviceId, ATTACK_SHARK_R3_3950_PROFILE], [3310, ATTACK_SHARK_R3_3950_PROFILE], + [ATTACK_SHARK_X3_PRO_PROFILE.deviceId, ATTACK_SHARK_X3_PRO_PROFILE], ]); /**