From ca4c5c49d400ac1bbfe872343eda737c0fba43e5 Mon Sep 17 00:00:00 2001 From: snekxs <26660858+snekxs@users.noreply.github.com> Date: Tue, 13 Oct 2026 14:47:07 -0600 Subject: [PATCH] fix(razer): commit DeathAdder V3 polling with the second extended write The wired 0x00b2 acknowledges selector 0x00 and keeps the old rate. OpenRazer follows it with selector 0x01 on the same 0x1f transaction. Co-authored-by: Cursor --- src/drivers/razer/devices.test.ts | 3 +++ src/drivers/razer/hid.test.ts | 17 +++++++++++++++++ src/razer/devices.ts | 14 ++++++++++---- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/drivers/razer/devices.test.ts b/src/drivers/razer/devices.test.ts index 175b4eb9..a652c0ed 100644 --- a/src/drivers/razer/devices.test.ts +++ b/src/drivers/razer/devices.test.ts @@ -145,6 +145,9 @@ test("the wired DeathAdder V3 writes polling on the extended command", () => { const wired = RAZER_PRODUCTS.get(0x00b2); assert.equal(wired?.wireless, false); assert.equal(wired?.highRatePolling, true); + // Both writes stay on 0x1f. The dongle's commit uses 0xff; copying that + // here would address a transaction this mouse does not use for the commit. + assert.equal(wired?.extendedPollingCommitTransactionId, RAZER_TRANSACTION_ID); // The rate the hardware was found at has to survive the round trip, or the // picker offers a value the mouse is already sitting at and cannot be set to. assert.ok(wired?.pollingRates.includes(2000)); diff --git a/src/drivers/razer/hid.test.ts b/src/drivers/razer/hid.test.ts index 2d21e14b..a57fb7c3 100644 --- a/src/drivers/razer/hid.test.ts +++ b/src/drivers/razer/hid.test.ts @@ -869,6 +869,23 @@ test("Mouse Dock Pro with a HyperPolling mouse unlocks the 8 kHz ladder", async assert.deepEqual(status.supportedPollingRates, [...RATES_8K]); }); +test("the wired DeathAdder V3 commits polling with selector 0x01 on the same transaction id", async () => { + // Issue #222: selector 0x00 alone is acknowledged and the read-back stays + // at the old rate. OpenRazer follows it with selector 0x01, still on 0x1f. + const { client, sent } = fakeMouse( + { tracking: 0, liftOff: 2, landing: 1, asymmetric: false }, + { productId: 0x00b2 }, + ); + + const confirmed = await client.setPollingRate(2000); + + assert.equal(confirmed, 2000); + const writes = sent.filter((packet) => packet[6] === 0x00 && packet[7] === 0x40); + assert.equal(writes.length, 2); + assert.deepEqual([writes[0][1], writes[0][8], writes[0][9]], [0x1f, 0x00, 0x04]); + assert.deepEqual([writes[1][1], writes[1][8], writes[1][9]], [0x1f, 0x01, 0x04]); +}); + test("HyperPolling dongle commits an 8 kHz change through both selectors", async () => { const { client, sent } = fakeMouse( { tracking: 0, liftOff: 2, landing: 1, asymmetric: false }, diff --git a/src/razer/devices.ts b/src/razer/devices.ts index 1f46af7e..f6a1db85 100644 --- a/src/razer/devices.ts +++ b/src/razer/devices.ts @@ -124,10 +124,10 @@ export interface RazerProduct { */ highRatePolling: boolean; /** - * Some early HyperPolling devices commit an extended polling change with a - * second write: selector `0x01` on a different transaction id. OpenRazer - * does this for the standalone HyperPolling dongle after the ordinary - * selector-`0x00` write. Omitted for devices that need only one write. + * Commit an extended polling change with a second write, selector `0x01`, + * on this transaction id. OpenRazer sends the ordinary selector-`0x00` + * write first. The HyperPolling dongle puts the commit on `0xff`; the wired + * DeathAdder V3 keeps both writes on `0x1f`. Omitted when one write is enough. */ extendedPollingCommitTransactionId?: number; /** @@ -497,12 +497,18 @@ const PRODUCT_DEFINITIONS: ReadonlyArray<[number, Omit