From ff12f59e541ad36ca9a29b19ebf2620181f7d8b4 Mon Sep 17 00:00:00 2001 From: Xander Coetzee <103134465+Xander-Coetzee@users.noreply.github.com> Date: Fri, 2 Oct 2026 13:14:49 +0200 Subject: [PATCH 1/3] feat(devices): add Cooler Master MM711 support - Register CoolerMasterHidClient in controller NEEDS_OPEN list - Add Cooler Master family traits for debounce and advanced settings - Update supported devices list with MM711 (PID 0x0101) --- src/device/controller.ts | 3 ++- src/device/traits.ts | 1 + src/supported-devices-data.ts | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/device/controller.ts b/src/device/controller.ts index 5854b67..dafa26e 100644 --- a/src/device/controller.ts +++ b/src/device/controller.ts @@ -184,6 +184,7 @@ import { IncottHidClient } from "@openmouse/protocol/drivers/incott/hid"; import { BytechHidClient } from "@openmouse/protocol/drivers/bytech/hid"; import { FaterHidClient } from "@openmouse/protocol/drivers/fater/hid"; import { RapooHidClient } from "@openmouse/protocol/drivers/rapoo/hid"; +import { CoolerMasterHidClient } from "@openmouse/protocol/drivers/coolermaster/hid"; import { parsePreviewMode, previewsEnabled, type PreviewMode } from "../preview-modes"; import { sleepLabel } from "./options"; import { traitsFor } from "./traits"; @@ -258,7 +259,7 @@ function activeAs(...classes: ClientClass[]): T | null { const DM_CLASSES = [WLMouseHidClient, LamzuHidClient, LamzuAtlantisHidClient, AtkHidClient, AtkBitmouseHidClient, NinjutsoHidClient] as const; const RAZER_CLASSES = [RazerHidClient, RazerViperMiniHidClient, RazerViperHidClient, RazerCobraHidClient] as const; -const NEEDS_OPEN = [LamzuAtlantisHidClient, TeevolutionHidClient, VgnF2HidClient, KeychronNapeHidClient, Keychron8kNordicHidClient, WLMouseBeastX4kHidClient, ModdoHidClient, ZaunkoenigHidClient, CorsairHidClient, FantechHidClient, WallhackMouseHidClient, WallhackKeyboardHidClient, GloriousHidClient, GloriousClassicHidClient, MchoseHidClient, MchoseDockHidClient, MchoseA5ProMaxHidClient, MchoseV3HidClient, MicrosoftHidClient, DareuHidClient, IncottHidClient, BytechHidClient, RapooHidClient, FaterHidClient] as const; +const NEEDS_OPEN = [LamzuAtlantisHidClient, TeevolutionHidClient, VgnF2HidClient, KeychronNapeHidClient, Keychron8kNordicHidClient, WLMouseBeastX4kHidClient, ModdoHidClient, ZaunkoenigHidClient, CorsairHidClient, FantechHidClient, WallhackMouseHidClient, WallhackKeyboardHidClient, GloriousHidClient, GloriousClassicHidClient, MchoseHidClient, MchoseDockHidClient, MchoseA5ProMaxHidClient, MchoseV3HidClient, MicrosoftHidClient, DareuHidClient, IncottHidClient, BytechHidClient, RapooHidClient, FaterHidClient, CoolerMasterHidClient] as const; const PULSAR_CLASSES = [PulsarHidClient, PulsarProHidClient, PulsarXs1HidClient] as const; const logitechClient = (): LogitechHidppClient | null => activeAs(LogitechHidppClient); diff --git a/src/device/traits.ts b/src/device/traits.ts index d4b4c28..1b586bc 100644 --- a/src/device/traits.ts +++ b/src/device/traits.ts @@ -91,6 +91,7 @@ const BY_FAMILY: Readonly>> = { // no signal, sleep or debounce card exists, and the processing card is // deliberately hidden, so no advanced-section flags are needed. hyperx: {}, + "cooler master": { advancedSection: true, debounce: true }, }; const BY_BRAND: Readonly> = { diff --git a/src/supported-devices-data.ts b/src/supported-devices-data.ts index c862fd9..2e06116 100644 --- a/src/supported-devices-data.ts +++ b/src/supported-devices-data.ts @@ -316,7 +316,7 @@ export const SUPPORTED_DEVICES: readonly SupportedDeviceEntry[] = [ { brand: "Bloody", model: "Max W95", status: "driver" }, { brand: "Fantech", model: "THOR II X16", status: "driver" }, { brand: "Cooler Master", model: "MasterMouse MM720", status: "driver" }, - { brand: "Cooler Master", model: "MM711", status: "driver" }, + { brand: "Cooler Master", model: "MM711", status: "supported", pids: [0x0101] }, { brand: "K-snake", model: "X11", status: "supported", pids: [0x2255] }, { brand: "Noir Gear", model: "M2-NEX", status: "supported", pids: [0x2255] }, { brand: "Hitscan", model: "Hyperlight", status: "unknown" }, From 67402e32d01e7007f150c0e3f38a52169f8cafba Mon Sep 17 00:00:00 2001 From: Xander Coetzee <103134465+Xander-Coetzee@users.noreply.github.com> Date: Fri, 2 Oct 2026 14:11:51 +0200 Subject: [PATCH 2/3] feat(ui): add Cooler Master MM711 product artwork resolution --- public/devices/README.md | 5 +++++ src/ui/device-images.test.ts | 5 +++++ src/ui/device-images.ts | 1 + 3 files changed, 11 insertions(+) diff --git a/public/devices/README.md b/public/devices/README.md index 4f14332..8bd4cfb 100644 --- a/public/devices/README.md +++ b/public/devices/README.md @@ -264,3 +264,8 @@ package: render from Dareu's driver panel (`https://driver.dareu.com/allinone/products/1117/TM271F.png?v=1.3.24`). It is vendor product art; request licensing review before publishing it in R2. + +- `coolermaster-mm711.png` — Cooler Master MM711 wired gaming mouse (`0x2516:0x0101`). + **Needs a maintainer upload** to the `openmouse-devices` R2 bucket; the mapping in + `src/ui/device-images.ts` is in place and falls back cleanly to the placeholder until then. + diff --git a/src/ui/device-images.test.ts b/src/ui/device-images.test.ts index 1c64e21..3462a6b 100644 --- a/src/ui/device-images.test.ts +++ b/src/ui/device-images.test.ts @@ -96,6 +96,11 @@ test("Dareu A950 PRO Mg resolves by name", () => { assert.equal(deviceImage(null, "Dareu A950 PRO Mg"), CDN + "dareu-a950-pro-mg.png"); }); +test("Cooler Master MM711 resolves by name", () => { + assert.equal(deviceImage(null, "Cooler Master MM711"), CDN + "coolermaster-mm711.png"); + assert.equal(deviceImage(null, "MM711"), CDN + "coolermaster-mm711.png"); +}); + test("Delux M800 Mini and M800 family resolve by name", () => { assert.equal(deviceImage(null, "Delux M800 Mini"), "/devices/delux-m800-mini.png"); assert.equal(deviceImage(null, "Delux M800 Mini (Wireless)"), "/devices/delux-m800-mini.png"); diff --git a/src/ui/device-images.ts b/src/ui/device-images.ts index 20b042e..a141003 100644 --- a/src/ui/device-images.ts +++ b/src/ui/device-images.ts @@ -130,6 +130,7 @@ function resolveDeviceImageFilename(_device: HIDDevice | null | undefined, displ if (/\ba7\s*v3\b/i.test(displayName)) return "mchose-a7-v3.png"; if (/\ba7\s*v2\b/i.test(displayName)) return "mchose-a7-v2.png"; if (/\ba950\s*pro\s*mg\b/i.test(displayName)) return "dareu-a950-pro-mg.png"; + if (/\bmm711\b/i.test(displayName)) return "coolermaster-mm711.png"; // NinjaForce/Ninjutso family — V3 first so "Sora V3" isn't swallowed by a // looser V2 or "Ten" match. if (/\bsora\s*v3\b/i.test(displayName)) return "ninjutso-sora-v3.png"; From 269318e11b21ce7e9a11eb4c0085545f8b622fd7 Mon Sep 17 00:00:00 2001 From: Xander Coetzee <103134465+Xander-Coetzee@users.noreply.github.com> Date: Fri, 2 Oct 2026 14:31:10 +0200 Subject: [PATCH 3/3] feat(device): map cooler master family traits for debounce and advanced settings --- src/device/traits.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/device/traits.ts b/src/device/traits.ts index 1b586bc..b9c2104 100644 --- a/src/device/traits.ts +++ b/src/device/traits.ts @@ -92,6 +92,7 @@ const BY_FAMILY: Readonly>> = { // deliberately hidden, so no advanced-section flags are needed. hyperx: {}, "cooler master": { advancedSection: true, debounce: true }, + "coolermaster-mm711": { advancedSection: true, debounce: true }, }; const BY_BRAND: Readonly> = { @@ -102,6 +103,7 @@ const BY_BRAND: Readonly> = { Logitech: "logitech-hidpp", "Attack Shark": "attack-shark", IPI: "bytech", + "Cooler Master": "cooler master", }; export function familyOf(status: MouseStatus): string {