diff --git a/build/check-bundle-size.ts b/build/check-bundle-size.ts index 24828223..aed1db35 100644 --- a/build/check-bundle-size.ts +++ b/build/check-bundle-size.ts @@ -6,7 +6,9 @@ import { join } from "node:path"; // measured 3_171_143 bytes with it. const BUDGET_BYTES: Record = { ".css": 165_000, - ".js": 3_200_000, + // Raised from 3_200_000 for the Amkette XS driver and its shared 0x04F3 codec + // in @openmouse/protocol: +18,624 bytes measured against mouse-protocol main. + ".js": 3_220_000, }; const ASSETS = join("dist", "assets"); diff --git a/src/device/traits.ts b/src/device/traits.ts index 15052eea..ac5d5dc7 100644 --- a/src/device/traits.ts +++ b/src/device/traits.ts @@ -83,6 +83,9 @@ const BY_FAMILY: Readonly>> = { // it publishes its own sleep and debounce option lists. "mchose-v3": { advancedSection: true, sleep: true, debounce: true }, dareu: { advancedSection: true, sleep: true }, + // Amkette's XS firmware stores auto sleep as whole minutes and publishes its + // own list through getSleepOptions. + "amkette-xs": { advancedSection: true, sleep: true }, // The Model O2 Pro 4K/8K writes a debounce block and a motion sync flag; it // has no sleep command wired in, so no sleep flag. "glorious-core2": { advancedSection: true, debounce: true }, diff --git a/src/shared/device-images.test.ts b/src/shared/device-images.test.ts index e22b4b04..1a4016f3 100644 --- a/src/shared/device-images.test.ts +++ b/src/shared/device-images.test.ts @@ -40,6 +40,16 @@ test("PRO X3 SUPERSTRIKE gets its own render, not the X2's", () => { assert.equal(deviceImage(null, "Attack Shark X3"), CDN + "attackshark-x3.png"); }); +test("Amkette XS Flow 2 Speed resolves by model name, never from the shared ids", () => { + assert.equal(deviceImage(dev(0x04f3, 0x026e), "XS Flow 2 Speed"), CDN + "amkette-xs-flow-2-speed.png"); + assert.equal(deviceImage(dev(0x04f3, 0x026d), "XS Flow 2 Speed"), CDN + "amkette-xs-flow-2-speed.png"); + assert.equal(deviceImage(null, "Amkette XS Flow 2 Speed"), CDN + "amkette-xs-flow-2-speed.png"); + // Without a model name the mouse could be any XS model, or share ids with a + // Ryunix Kyu Pro MX1, so it keeps the placeholder. + assert.equal(deviceImage(dev(0x04f3, 0x026e), "XS series"), CDN + "unknown-device.png"); + assert.equal(deviceImage(dev(0x04f3, 0x026e), "Kyu Pro MX1"), CDN + "unknown-device.png"); +}); + test("Razer Orochi V2 resolves by name", () => { assert.equal(deviceImage(dev(0x1532, 0x0094), "Razer Orochi V2"), CDN + "razer-orochi-v2.png"); }); diff --git a/src/shared/device-images.ts b/src/shared/device-images.ts index 131d5be3..1253dad9 100644 --- a/src/shared/device-images.ts +++ b/src/shared/device-images.ts @@ -65,6 +65,11 @@ function computeDeviceImageFilename(displayName: string): string { } if (/\br5\s*ultra\b/i.test(displayName)) return "attackshark-r5-ultra.png"; if (/\bfloat\s*88\b/i.test(displayName)) return "ipi-float-88.png"; + // Amkette XS Flow 2 Speed: the driver reports this model name only once the + // mouse names itself ("XS Speed" over USB, "XS speed N" over Bluetooth). Its + // nameless fallback, "XS series", could be any XS model, so it keeps the + // placeholder rather than guess. + if (/\bxs\s*flow\s*2\s*speed\b/i.test(displayName)) return "amkette-xs-flow-2-speed.png"; if (/\batk\s*zero\b/i.test(displayName)) return "atk-zero.png"; // R2 shares PID 0x402D with the Lingbao M5 Pro, so it can only be told apart // by the name the gearhub driver reads back from the device id.