diff --git a/package-lock.json b/package-lock.json index e3188ceb..84121ede 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "2.0.0", "license": "AGPL-3.0-or-later", "dependencies": { - "@openmouse/protocol": "0.16.0", + "@openmouse/protocol": "0.17.0", "lucide-react": "^1.43.0", "preact": "^10.29.8" }, @@ -481,9 +481,9 @@ } }, "node_modules/@openmouse/protocol": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@openmouse/protocol/-/protocol-0.16.0.tgz", - "integrity": "sha512-AZ760P++wljy4bezo0ov7okjf1ARfI2/uvK+N+Uih3AoXcf1hCdOLwM85sk+60lTD8RwHnmRKWL8aTqxTNIdgw==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@openmouse/protocol/-/protocol-0.17.0.tgz", + "integrity": "sha512-IsHTnBz+Gt05TYpEz2+1Wovf1Z102CO7Ttve9dTr7TWtD5J4T16D1YV3q07XZSSPIqq2RJN7wZukQJt4JpnAng==", "engines": { "node": ">=20" } diff --git a/package.json b/package.json index cf7c5d1b..9ba4a952 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "preview": "vite preview" }, "dependencies": { - "@openmouse/protocol": "0.16.0", + "@openmouse/protocol": "0.17.0", "lucide-react": "^1.43.0", "preact": "^10.29.8" }, diff --git a/src/control-devices.css b/src/control-devices.css index acf57aac..fe7c18e8 100644 --- a/src/control-devices.css +++ b/src/control-devices.css @@ -1,9 +1,9 @@ #pulsar-advanced { margin-top: .65rem; grid-template-columns: repeat(auto-fit, minmax(145px, 1fr)); gap: .65rem; padding-bottom: .4rem; } #pulsar-advanced > .setting-card { min-height: 0; padding: .8rem; } -#pulsar-advanced input[type="number"], #pulsar-advanced select { width: 100%; box-sizing: border-box; margin-top: .2rem; padding: .48rem .55rem; border: 1px solid var(--line-strong); border-radius: 6px; outline: none; background: var(--surface-input); color: var(--text-input); font: inherit; color-scheme: dark; } -#pulsar-advanced input[type="number"]:focus, #pulsar-advanced select:focus { border-color: var(--faint); box-shadow: 0 0 0 2px rgb(255 255 255 / 4%); } -#pulsar-advanced input:disabled, #pulsar-advanced select:disabled { cursor: not-allowed; opacity: .45; } +#pulsar-advanced input[type="number"], #pulsar-advanced select, #debounce-settings select { width: 100%; box-sizing: border-box; margin-top: .2rem; padding: .48rem .55rem; border: 1px solid var(--line-strong); border-radius: 6px; outline: none; background: var(--surface-input); color: var(--text-input); font: inherit; color-scheme: dark; } +#pulsar-advanced input[type="number"]:focus, #pulsar-advanced select:focus, #debounce-settings select:focus { border-color: var(--faint); box-shadow: 0 0 0 2px rgb(255 255 255 / 4%); } +#pulsar-advanced input:disabled, #pulsar-advanced select:disabled, #debounce-settings select:disabled { cursor: not-allowed; opacity: .45; } #pulsar-advanced input[type="checkbox"] { width: .85rem; height: .85rem; margin: 0; accent-color: var(--ui-accent); } #pulsar-advanced .sleep-time-picker { display: flex; align-items: start; gap: .35rem; margin-top: .35rem; } #pulsar-advanced .sleep-time-picker label { display: grid; gap: .2rem; min-width: 0; flex: 1 1 0; color: var(--faint); font-size: .58rem; text-align: center; } diff --git a/src/device/controller.ts b/src/device/controller.ts index f12b3aa8..054d21ed 100644 --- a/src/device/controller.ts +++ b/src/device/controller.ts @@ -166,6 +166,7 @@ import { WLMouseHidClient } from "@openmouse/protocol/drivers/wlmouse/hid"; import { MicrosoftHidClient } from "@openmouse/protocol/drivers/microsoft/hid"; import { DareuHidClient } from "@openmouse/protocol/drivers/dareu/hid"; import { IncottHidClient } from "@openmouse/protocol/drivers/incott/hid"; +import { BytechHidClient } from "@openmouse/protocol/drivers/bytech/hid"; import { parsePreviewMode, previewsEnabled, type PreviewMode } from "../preview-modes"; import { sleepLabel } from "./options"; import { traitsFor } from "./traits"; @@ -240,7 +241,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, KeychronM6HidClient, ModdoHidClient, ZaunkoenigHidClient, CorsairHidClient, FantechHidClient, WallhackMouseHidClient, WallhackKeyboardHidClient, GloriousHidClient, GloriousClassicHidClient, MchoseHidClient, MchoseDockHidClient, MchoseA5ProMaxHidClient, MchoseV3HidClient, MicrosoftHidClient, DareuHidClient, IncottHidClient] as const; +const NEEDS_OPEN = [LamzuAtlantisHidClient, TeevolutionHidClient, VgnF2HidClient, KeychronNapeHidClient, KeychronM6HidClient, ModdoHidClient, ZaunkoenigHidClient, CorsairHidClient, FantechHidClient, WallhackMouseHidClient, WallhackKeyboardHidClient, GloriousHidClient, GloriousClassicHidClient, MchoseHidClient, MchoseDockHidClient, MchoseA5ProMaxHidClient, MchoseV3HidClient, MicrosoftHidClient, DareuHidClient, IncottHidClient, BytechHidClient] as const; const PULSAR_CLASSES = [PulsarHidClient, PulsarProHidClient, PulsarXs1HidClient] as const; const logitechClient = (): LogitechHidppClient | null => activeAs(LogitechHidppClient); @@ -3844,10 +3845,12 @@ export function applyPulsarToggle(setting: PulsarToggleSetting, enabled: boolean export function applyPulsarValue(setting: "debounce" | "sleep", value: number): void { const client = setting === "sleep" ? activeSettingsClient() - : pulsarClient() ?? dmClient() ?? orbitalClient() ?? razerClient() - ?? viperClient() ?? teevolutionClient() ?? vgnClient() ?? keychronNapeClient() ?? keychronM6Client() ?? keychron4kClient() ?? wallhackMouseClient() - ?? incottClient(); - if (!client || (setting === "sleep" && !("setSleepTimeout" in client))) return; + : (activeSettingsClient() && "setDebounceTime" in (activeSettingsClient() ?? {})) + ? activeSettingsClient() + : pulsarClient() ?? dmClient() ?? orbitalClient() ?? razerClient() + ?? viperClient() ?? teevolutionClient() ?? vgnClient() ?? keychronNapeClient() ?? keychronM6Client() ?? keychron4kClient() ?? wallhackMouseClient() + ?? incottClient(); + if (!client || (setting === "sleep" && !("setSleepTimeout" in client)) || (setting === "debounce" && !("setDebounceTime" in client))) return; const asleep = value !== WLMOUSE_SLEEP_NEVER; stageChange({ key: setting, diff --git a/src/device/traits.ts b/src/device/traits.ts index c34bf5e8..354f90a0 100644 --- a/src/device/traits.ts +++ b/src/device/traits.ts @@ -42,6 +42,7 @@ const BY_FAMILY: Readonly>> = { wlmouse: DIRECT_MODE, lamzu: DIRECT_MODE, "attack-shark": DIRECT_MODE, + bytech: DIRECT_MODE, crdrako: DIRECT_MODE, atk: DIRECT_MODE, "atk-bitmouse": DIRECT_MODE, @@ -87,6 +88,7 @@ const BY_BRAND: Readonly> = { VGN: "vgn", Logitech: "logitech-hidpp", "Attack Shark": "attack-shark", + IPI: "bytech", }; export function familyOf(status: MouseStatus): string { diff --git a/src/hardware-brand-checks.ts b/src/hardware-brand-checks.ts index e0b1c683..59418440 100644 --- a/src/hardware-brand-checks.ts +++ b/src/hardware-brand-checks.ts @@ -35,6 +35,7 @@ const WIRELESS_BATTERY_BRANDS = new Set([ "moddoMOUSE", "WALLHACK", "Attack Shark", + "IPI", ]); /** diff --git a/src/supported-devices-data.ts b/src/supported-devices-data.ts index 1c505fa3..8ec17541 100644 --- a/src/supported-devices-data.ts +++ b/src/supported-devices-data.ts @@ -97,6 +97,7 @@ export const SUPPORTED_DEVICES: readonly SupportedDeviceEntry[] = [ { brand: "Attack Shark", model: "X3", status: "bridge" }, { brand: "Attack Shark", model: "X11", status: "bridge" }, { brand: "Attack Shark", model: "R5 Ultra", status: "supported", pids: [0x46, 0x47] }, + { brand: "IPI", model: "Float 88", status: "supported", pids: [0x1014, 0x1015] }, { brand: "Attack Shark", model: "X6", status: "bridge" }, { brand: "Attack Shark", model: "R3", status: "likely", pids: [0x402d, 0x4026] }, { brand: "Attack Shark", model: "R1", status: "bridge" }, diff --git a/src/ui/device-images.test.ts b/src/ui/device-images.test.ts index e04b4b87..d4a175ec 100644 --- a/src/ui/device-images.test.ts +++ b/src/ui/device-images.test.ts @@ -83,6 +83,11 @@ test("Attack Shark R5 Ultra resolves by name", () => { assert.equal(deviceImage(null, "Attack Shark R5 Ultra"), CDN + "attackshark-r5-ultra.png"); }); +test("IPI Float 88 resolves by name", () => { + assert.equal(deviceImage(null, "IPI Float 88 (Wired)"), "/devices/ipi-float-88.png"); + assert.equal(deviceImage(null, "IPI Float 88 (Wireless)"), "/devices/ipi-float-88.png"); +}); + test("ATK ZERO resolves by name", () => { assert.equal(deviceImage(null, "ATK ZERO"), CDN + "atk-zero.png"); }); diff --git a/src/ui/device-images.ts b/src/ui/device-images.ts index 3dd24078..afa8846d 100644 --- a/src/ui/device-images.ts +++ b/src/ui/device-images.ts @@ -55,6 +55,7 @@ function resolveDeviceImageFilename(_device: HIDDevice | null | undefined, displ return "vxe-r1-series.png"; } 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"; 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. @@ -189,6 +190,7 @@ const DEVICE_IMAGE_BASE_URL = "https://img.openmouse.app/"; */ const LOCAL_OVERRIDES: Readonly> = { "attackshark-r2.png": "/devices/attackshark-r2.png", + "ipi-float-88.png": "/devices/ipi-float-88.png", }; export function deviceImage(device: HIDDevice | null | undefined, displayName = ""): string {