From bbd49197d66af2c3e366583932a48b3efd0d914a Mon Sep 17 00:00:00 2001 From: NoxLoveYa <83467468+NoxLoveYa@users.noreply.github.com> Date: Tue, 22 Sep 2026 22:57:58 +0200 Subject: [PATCH 01/16] feat(atk): add F1 Ultimate extras card (sensor mode, anti-mistouch, dongle light) --- src/app/OverviewPage.tsx | 3 +- src/app/cards/AtkCards.tsx | 81 ++++++++++++++++++++++++++++++++++- src/app/cards/availability.ts | 5 +++ src/device/controller.ts | 50 +++++++++++++++++++++ 4 files changed, 137 insertions(+), 2 deletions(-) diff --git a/src/app/OverviewPage.tsx b/src/app/OverviewPage.tsx index 232a71a6..7ba1cf6a 100644 --- a/src/app/OverviewPage.tsx +++ b/src/app/OverviewPage.tsx @@ -24,7 +24,7 @@ import { DpiCard } from "./cards/DpiCard"; import { LightforceCard, PollingCard, SensorCard } from "./cards/PerformanceCards"; import { LightingCard } from "./cards/LightingCard"; import { MxMasterButtonsCard, MxMasterCards } from "./cards/MxMasterCards"; -import { AtkButtonCard, AtkProfileCard, AtkReceiverCard } from "./cards/AtkCards"; +import { AtkButtonCard, AtkF1ExtrasCard, AtkProfileCard, AtkReceiverCard } from "./cards/AtkCards"; import { DebounceCard, EggButtonCard, @@ -462,6 +462,7 @@ export function Workspace({ show(has.incott, ["advanced"]) ? : null, show(has.atkProfile, ["profiles"]) ? : null, device && show(has.atkReceiver, ["advanced"]) ? : null, + show(has.atkF1Extras, ["advanced"]) ? : null, show(has.onboardProfiles && !snapshot.traits.teevolution, ["profiles"]) ? : null, show(has.pulsarPro, ["profiles"]) ? : null, diff --git a/src/app/cards/AtkCards.tsx b/src/app/cards/AtkCards.tsx index 11dc2188..7fbf88c3 100644 --- a/src/app/cards/AtkCards.tsx +++ b/src/app/cards/AtkCards.tsx @@ -1,7 +1,7 @@ import { useEffect, useRef, useState, type ReactNode } from "react"; import * as control from "../../device/controller"; import type { ControlSnapshot } from "../../device/types"; -import { Segmented } from "../ui"; +import { Segmented, SwitchRow } from "../ui"; export function AtkProfileCard({ snapshot }: { snapshot: ControlSnapshot }): ReactNode { const status = snapshot.status; @@ -115,3 +115,82 @@ export function AtkReceiverCard({ snapshot }: { snapshot: ControlSnapshot }): Re ); } + +const ATK_SENSOR_MODE_LABELS = ["Basic", "Shard", "MAX"] as const; +const ATK_DONGLE_LIGHT_LABELS = ["Off", "Polling", "Battery", "Low battery"] as const; + +/** + * F1 Ultimate extras: sensor sampling mode, scroll anti-mistouch, and the + * receiver dongle LED. Each control renders only when the driver actually + * reported the field; all three were verified on the F1 Ultimate 2.0 + * (CID 01, MID 08 over 373B:11D9). + */ +export function AtkF1ExtrasCard({ snapshot }: { snapshot: ControlSnapshot }): ReactNode { + const status = snapshot.status; + const sensorMode = status?.atkSensorMode ?? null; + const antiMistouchMs = status?.atkAntiMistouchMs ?? null; + const dongleLight = status?.atkDongleLight; + if (sensorMode == null && antiMistouchMs == null && dongleLight == null) return null; + const busy = snapshot.settingInProgress; + return ( +
+
+

ATK

F1 Ultimate extras

+
+ {sensorMode != null ? ( +
+ Sensor mode + ({ value, label }))} + value={sensorMode} + disabled={busy} + onChange={(mode) => void control.selectAtkSensorMode(mode)} + /> + + Shard is the stock firmware; MAX raises the sensor scan rate. + +
+ ) : null} + {antiMistouchMs != null ? ( +
+ 0} + disabled={busy} + onChange={(next) => void control.applyAtkAntiMistouch(next ? 100 : 0)} + /> + void control.applyAtkAntiMistouch(ms)} + /> + + First scroll tick is ignored unless repeated inside the window. + +
+ ) : null} + {dongleLight != null ? ( +
+ Dongle light + ({ value, label }))} + value={dongleLight} + disabled={busy} + onChange={(mode) => void control.selectAtkDongleLight(mode)} + /> + + Effect on the 8K receiver; write-only, confirmed on the LED. + +
+ ) : null} +
+ ); +} diff --git a/src/app/cards/availability.ts b/src/app/cards/availability.ts index db6c717c..18f81a33 100644 --- a/src/app/cards/availability.ts +++ b/src/app/cards/availability.ts @@ -29,6 +29,7 @@ export interface CardAvailability { atkButtons: boolean; atkProfile: boolean; atkReceiver: boolean; + atkF1Extras: boolean; mxMasterButtons: boolean; pulsarPro: boolean; onboardProfiles: boolean; @@ -67,6 +68,7 @@ const NOTHING: CardAvailability = { atkButtons: false, atkProfile: false, atkReceiver: false, + atkF1Extras: false, mxMasterButtons: false, pulsarPro: false, onboardProfiles: false, @@ -173,6 +175,9 @@ export function cardAvailability(snapshot: ControlSnapshot): CardAvailability { atkButtons: (status.atkButtonMappings?.length ?? 0) > 0, atkProfile: status.atkProfileCount !== undefined && status.activeProfile !== null, atkReceiver: status.atkReceiver !== undefined, + atkF1Extras: status.atkSensorMode != null + || status.atkAntiMistouchMs != null + || status.atkDongleLight != null, mxMasterButtons: traits.logitech && (snapshot.buttons?.length ?? 0) > 0, pulsarPro: host && isPulsarProProtocol(status), }; diff --git a/src/device/controller.ts b/src/device/controller.ts index bef043cd..d515033e 100644 --- a/src/device/controller.ts +++ b/src/device/controller.ts @@ -1302,6 +1302,56 @@ export async function selectAtkR1Profile(profile: number): Promise { } } +/** + * Immediate ATK F1 Ultimate writes (sensor mode, anti-mistouch, dongle + * light). Unlike staged slider changes these apply at once with a read-back, + * following selectAtkR1Profile above. + */ +async function writeAtkF1Setting( + label: string, + apply: (client: AtkHidClient) => Promise, +): Promise { + const client = activeAs(AtkHidClient); + if (!client || refreshInProgress || settingInProgress) return; + if (hasPendingChanges()) { + setReadStatus(st("ctl.atkPending")); + emit(); + return; + } + const device = activeDevice; + settingInProgress = true; + setReadStatus(label); + emit(); + recordDiagnosticCommand(label); + try { + await apply(client); + if (active !== client || activeDevice !== device) return; + const status = await statusAfterWrite(client); + if (active !== client || activeDevice !== device) return; + applyStatus(status); + setReadStatus(label); + } catch (error) { + if (active !== client || activeDevice !== device) return; + recordDiagnosticError(error, label); + setReadStatus(error instanceof Error ? error.message : label); + } finally { + settingInProgress = false; + emit(); + } +} + +export async function selectAtkSensorMode(mode: number): Promise { + await writeAtkF1Setting(`Set ATK sensor mode to ${mode}`, (client) => client.setAtkSensorMode(mode)); +} + +export async function applyAtkAntiMistouch(milliseconds: number): Promise { + await writeAtkF1Setting(`Set ATK anti-mistouch to ${milliseconds} ms`, (client) => client.setAntiMistouchMs(milliseconds)); +} + +export async function selectAtkDongleLight(mode: number): Promise { + await writeAtkF1Setting(`Set ATK dongle light to ${mode}`, (client) => client.setDongleLight(mode)); +} + export async function pairAtkR1SePlusReceiver(): Promise { if (blockedByGameProfileDraft()) return; const client = activeAs(AtkHidClient); From 4852fd10e17b38185e49dc3296bfee1462c5cb8d Mon Sep 17 00:00:00 2001 From: NoxLoveYa <83467468+NoxLoveYa@users.noreply.github.com> Date: Tue, 22 Sep 2026 23:34:48 +0200 Subject: [PATCH 02/16] feat(atk): mirror ATK HUB labels and descriptions in F1 extras card --- src/app/cards/AtkCards.tsx | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/app/cards/AtkCards.tsx b/src/app/cards/AtkCards.tsx index 7fbf88c3..d627c333 100644 --- a/src/app/cards/AtkCards.tsx +++ b/src/app/cards/AtkCards.tsx @@ -116,8 +116,8 @@ export function AtkReceiverCard({ snapshot }: { snapshot: ControlSnapshot }): Re ); } -const ATK_SENSOR_MODE_LABELS = ["Basic", "Shard", "MAX"] as const; -const ATK_DONGLE_LIGHT_LABELS = ["Off", "Polling", "Battery", "Low battery"] as const; +const ATK_SENSOR_MODE_LABELS = ["Basic Mode", "ATK Shard Competitive Firmware", "ATK Shard Competitive Firmware MAX"] as const; +const ATK_DONGLE_LIGHT_LABELS = ["Close", "Polling Rate Effect", "Battery Gradient Light Effect", "Low Battery Effect"] as const; /** * F1 Ultimate extras: sensor sampling mode, scroll anti-mistouch, and the @@ -139,7 +139,7 @@ export function AtkF1ExtrasCard({ snapshot }: { snapshot: ControlSnapshot }): Re {sensorMode != null ? (
- Sensor mode + Sensor Sampling Rate ({ value, label }))} @@ -148,22 +148,29 @@ export function AtkF1ExtrasCard({ snapshot }: { snapshot: ControlSnapshot }): Re onChange={(mode) => void control.selectAtkSensorMode(mode)} /> - Shard is the stock firmware; MAX raises the sensor scan rate. + {sensorMode === 2 + ? "In this mode, the mouse sensor is in high performance state, high scanning frequency, more responsive control." + : sensorMode === 1 + ? "ATK Shard competitive firmware: balanced scan rate for match play." + : "Basic sensor mode for daily work and maximum battery life."}
) : null} {antiMistouchMs != null ? (
0} disabled={busy} onChange={(next) => void control.applyAtkAntiMistouch(next ? 100 : 0)} /> + + Helps prevent accidental scroll wheel input during gaming. When enabled, the first scroll wheel trigger is ignored by default, and the scroll function will only activate if triggered again within the set time. + void control.applyAtkAntiMistouch(ms)} /> - - First scroll tick is ignored unless repeated inside the window. -
) : null} {dongleLight != null ? (
- Dongle light + Dongle Light Effect + Lighting Effect Mode ({ value, label }))} From 328e505934da75333c6adfa2dc3d4112940d6207 Mon Sep 17 00:00:00 2001 From: NoxLoveYa <83467468+NoxLoveYa@users.noreply.github.com> Date: Tue, 22 Sep 2026 23:38:15 +0200 Subject: [PATCH 03/16] feat(atk): group F1 controls Hub-style (sensor in Performance, rest in Advanced) --- src/app/OverviewPage.tsx | 6 +- src/app/cards/AtkCards.tsx | 157 ++++++++++++++++++---------------- src/app/cards/availability.ts | 14 +-- 3 files changed, 98 insertions(+), 79 deletions(-) diff --git a/src/app/OverviewPage.tsx b/src/app/OverviewPage.tsx index 7ba1cf6a..72af3a59 100644 --- a/src/app/OverviewPage.tsx +++ b/src/app/OverviewPage.tsx @@ -24,7 +24,7 @@ import { DpiCard } from "./cards/DpiCard"; import { LightforceCard, PollingCard, SensorCard } from "./cards/PerformanceCards"; import { LightingCard } from "./cards/LightingCard"; import { MxMasterButtonsCard, MxMasterCards } from "./cards/MxMasterCards"; -import { AtkButtonCard, AtkF1ExtrasCard, AtkProfileCard, AtkReceiverCard } from "./cards/AtkCards"; +import { AtkAntiMistouchCard, AtkButtonCard, AtkDongleCard, AtkProfileCard, AtkReceiverCard, AtkSensorCard } from "./cards/AtkCards"; import { DebounceCard, EggButtonCard, @@ -430,6 +430,7 @@ export function Workspace({ show(has.dpi, ["performance"]) ? : null, show(has.polling, ["performance"]) ? : null, show(has.sensor, ["performance"]) ? : null, + show(has.atkF1Sensor, ["performance"]) ? : null, show(has.lightforce, ["buttons"]) ? : null, show(has.ninjutsoSensor, ["performance"]) ? : null, @@ -462,7 +463,8 @@ export function Workspace({ show(has.incott, ["advanced"]) ? : null, show(has.atkProfile, ["profiles"]) ? : null, device && show(has.atkReceiver, ["advanced"]) ? : null, - show(has.atkF1Extras, ["advanced"]) ? : null, + show(has.atkF1AntiMistouch, ["advanced"]) ? : null, + show(has.atkF1Dongle, ["advanced"]) ? : null, show(has.onboardProfiles && !snapshot.traits.teevolution, ["profiles"]) ? : null, show(has.pulsarPro, ["profiles"]) ? : null, diff --git a/src/app/cards/AtkCards.tsx b/src/app/cards/AtkCards.tsx index d627c333..4753e13e 100644 --- a/src/app/cards/AtkCards.tsx +++ b/src/app/cards/AtkCards.tsx @@ -120,82 +120,95 @@ const ATK_SENSOR_MODE_LABELS = ["Basic Mode", "ATK Shard Competitive Firmware", const ATK_DONGLE_LIGHT_LABELS = ["Close", "Polling Rate Effect", "Battery Gradient Light Effect", "Low Battery Effect"] as const; /** - * F1 Ultimate extras: sensor sampling mode, scroll anti-mistouch, and the - * receiver dongle LED. Each control renders only when the driver actually - * reported the field; all three were verified on the F1 Ultimate 2.0 - * (CID 01, MID 08 over 373B:11D9). + * F1 Ultimate extras, grouped like the ATK HUB pages: sensor sampling rate + * lives with polling under Performance, while scroll anti-mistouch and the + * receiver dongle LED live under Advanced (HUB Parameter page). Each control + * renders only when the driver actually reported the field; all three were + * verified on the F1 Ultimate 2.0 (CID 01, MID 08 over 373B:11D9). */ -export function AtkF1ExtrasCard({ snapshot }: { snapshot: ControlSnapshot }): ReactNode { - const status = snapshot.status; - const sensorMode = status?.atkSensorMode ?? null; - const antiMistouchMs = status?.atkAntiMistouchMs ?? null; - const dongleLight = status?.atkDongleLight; - if (sensorMode == null && antiMistouchMs == null && dongleLight == null) return null; - const busy = snapshot.settingInProgress; +export function AtkSensorCard({ snapshot }: { snapshot: ControlSnapshot }): ReactNode { + const sensorMode = snapshot.status?.atkSensorMode ?? null; + if (sensorMode == null) return null; + return ( +
+
+

PERFORMANCE

Sensor Sampling Rate

+
+
+ ({ value, label }))} + value={sensorMode} + disabled={snapshot.settingInProgress} + onChange={(mode) => void control.selectAtkSensorMode(mode)} + /> + + {sensorMode === 2 + ? "In this mode, the mouse sensor is in high performance state, high scanning frequency, more responsive control." + : sensorMode === 1 + ? "ATK Shard competitive firmware: balanced scan rate for match play." + : "Basic sensor mode for daily work and maximum battery life."} + +
+
+ ); +} + +export function AtkAntiMistouchCard({ snapshot }: { snapshot: ControlSnapshot }): ReactNode { + const antiMistouchMs = snapshot.status?.atkAntiMistouchMs ?? null; + if (antiMistouchMs == null) return null; return ( -
+
-

ATK

F1 Ultimate extras

+

PARAMETER

Scroll Wheel Anti-Mistouch Mode

+
+
+ 0} + disabled={snapshot.settingInProgress} + onChange={(next) => void control.applyAtkAntiMistouch(next ? 100 : 0)} + /> + + Helps prevent accidental scroll wheel input during gaming. When enabled, the first scroll wheel trigger is ignored by default, and the scroll function will only activate if triggered again within the set time. + + void control.applyAtkAntiMistouch(ms)} + /> +
+
+ ); +} + +export function AtkDongleCard({ snapshot }: { snapshot: ControlSnapshot }): ReactNode { + const dongleLight = snapshot.status?.atkDongleLight; + if (dongleLight == null) return null; + return ( +
+
+

PARAMETER

Dongle Light Effect

+
+
+ Lighting Effect Mode + ({ value, label }))} + value={dongleLight} + disabled={snapshot.settingInProgress} + onChange={(mode) => void control.selectAtkDongleLight(mode)} + /> + + Effect on the 8K receiver; write-only, confirmed on the LED. +
- {sensorMode != null ? ( -
- Sensor Sampling Rate - ({ value, label }))} - value={sensorMode} - disabled={busy} - onChange={(mode) => void control.selectAtkSensorMode(mode)} - /> - - {sensorMode === 2 - ? "In this mode, the mouse sensor is in high performance state, high scanning frequency, more responsive control." - : sensorMode === 1 - ? "ATK Shard competitive firmware: balanced scan rate for match play." - : "Basic sensor mode for daily work and maximum battery life."} - -
- ) : null} - {antiMistouchMs != null ? ( -
- 0} - disabled={busy} - onChange={(next) => void control.applyAtkAntiMistouch(next ? 100 : 0)} - /> - - Helps prevent accidental scroll wheel input during gaming. When enabled, the first scroll wheel trigger is ignored by default, and the scroll function will only activate if triggered again within the set time. - - void control.applyAtkAntiMistouch(ms)} - /> -
- ) : null} - {dongleLight != null ? ( -
- Dongle Light Effect - Lighting Effect Mode - ({ value, label }))} - value={dongleLight} - disabled={busy} - onChange={(mode) => void control.selectAtkDongleLight(mode)} - /> - - Effect on the 8K receiver; write-only, confirmed on the LED. - -
- ) : null}
); } diff --git a/src/app/cards/availability.ts b/src/app/cards/availability.ts index 18f81a33..ec9f5f56 100644 --- a/src/app/cards/availability.ts +++ b/src/app/cards/availability.ts @@ -29,7 +29,9 @@ export interface CardAvailability { atkButtons: boolean; atkProfile: boolean; atkReceiver: boolean; - atkF1Extras: boolean; + atkF1Sensor: boolean; + atkF1AntiMistouch: boolean; + atkF1Dongle: boolean; mxMasterButtons: boolean; pulsarPro: boolean; onboardProfiles: boolean; @@ -68,7 +70,9 @@ const NOTHING: CardAvailability = { atkButtons: false, atkProfile: false, atkReceiver: false, - atkF1Extras: false, + atkF1Sensor: false, + atkF1AntiMistouch: false, + atkF1Dongle: false, mxMasterButtons: false, pulsarPro: false, onboardProfiles: false, @@ -175,9 +179,9 @@ export function cardAvailability(snapshot: ControlSnapshot): CardAvailability { atkButtons: (status.atkButtonMappings?.length ?? 0) > 0, atkProfile: status.atkProfileCount !== undefined && status.activeProfile !== null, atkReceiver: status.atkReceiver !== undefined, - atkF1Extras: status.atkSensorMode != null - || status.atkAntiMistouchMs != null - || status.atkDongleLight != null, + atkF1Sensor: status.atkSensorMode != null, + atkF1AntiMistouch: status.atkAntiMistouchMs != null, + atkF1Dongle: status.atkDongleLight != null, mxMasterButtons: traits.logitech && (snapshot.buttons?.length ?? 0) > 0, pulsarPro: host && isPulsarProProtocol(status), }; From e48f086b355f49a5a528d4d498c8bece576791d1 Mon Sep 17 00:00:00 2001 From: NoxLoveYa <83467468+NoxLoveYa@users.noreply.github.com> Date: Tue, 22 Sep 2026 23:39:08 +0200 Subject: [PATCH 04/16] feat(atk): move anti-mistouch card to Performance --- src/app/OverviewPage.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/OverviewPage.tsx b/src/app/OverviewPage.tsx index 72af3a59..16f3963e 100644 --- a/src/app/OverviewPage.tsx +++ b/src/app/OverviewPage.tsx @@ -431,6 +431,7 @@ export function Workspace({ show(has.polling, ["performance"]) ? : null, show(has.sensor, ["performance"]) ? : null, show(has.atkF1Sensor, ["performance"]) ? : null, + show(has.atkF1AntiMistouch, ["performance"]) ? : null, show(has.lightforce, ["buttons"]) ? : null, show(has.ninjutsoSensor, ["performance"]) ? : null, @@ -463,7 +464,7 @@ export function Workspace({ show(has.incott, ["advanced"]) ? : null, show(has.atkProfile, ["profiles"]) ? : null, device && show(has.atkReceiver, ["advanced"]) ? : null, - show(has.atkF1AntiMistouch, ["advanced"]) ? : null, + show(has.atkF1Dongle, ["advanced"]) ? : null, show(has.atkF1Dongle, ["advanced"]) ? : null, show(has.onboardProfiles && !snapshot.traits.teevolution, ["profiles"]) ? : null, From 4f39492de0f137b15fce98dbeb6c882c48d563e0 Mon Sep 17 00:00:00 2001 From: NoxLoveYa <83467468+NoxLoveYa@users.noreply.github.com> Date: Tue, 22 Sep 2026 23:43:13 +0200 Subject: [PATCH 05/16] feat(atk): integrate anti-mistouch into Processing card with 100-1000ms slider --- src/app/OverviewPage.tsx | 3 +- src/app/cards/AdvancedCards.tsx | 59 ++++++++++++++++++++++++++++++++- src/app/cards/AtkCards.tsx | 36 +------------------- src/app/cards/availability.ts | 3 -- 4 files changed, 60 insertions(+), 41 deletions(-) diff --git a/src/app/OverviewPage.tsx b/src/app/OverviewPage.tsx index 16f3963e..78fc217f 100644 --- a/src/app/OverviewPage.tsx +++ b/src/app/OverviewPage.tsx @@ -24,7 +24,7 @@ import { DpiCard } from "./cards/DpiCard"; import { LightforceCard, PollingCard, SensorCard } from "./cards/PerformanceCards"; import { LightingCard } from "./cards/LightingCard"; import { MxMasterButtonsCard, MxMasterCards } from "./cards/MxMasterCards"; -import { AtkAntiMistouchCard, AtkButtonCard, AtkDongleCard, AtkProfileCard, AtkReceiverCard, AtkSensorCard } from "./cards/AtkCards"; +import { AtkButtonCard, AtkDongleCard, AtkProfileCard, AtkReceiverCard, AtkSensorCard } from "./cards/AtkCards"; import { DebounceCard, EggButtonCard, @@ -431,7 +431,6 @@ export function Workspace({ show(has.polling, ["performance"]) ? : null, show(has.sensor, ["performance"]) ? : null, show(has.atkF1Sensor, ["performance"]) ? : null, - show(has.atkF1AntiMistouch, ["performance"]) ? : null, show(has.lightforce, ["buttons"]) ? : null, show(has.ninjutsoSensor, ["performance"]) ? : null, diff --git a/src/app/cards/AdvancedCards.tsx b/src/app/cards/AdvancedCards.tsx index 63bfe955..2e6fd908 100644 --- a/src/app/cards/AdvancedCards.tsx +++ b/src/app/cards/AdvancedCards.tsx @@ -358,6 +358,12 @@ export function ProcessingCard({ snapshot }: { snapshot: ControlSnapshot }): Rea hidden={ui?.hideRippleControl === true || traits.finalmouse} onChange={(next) => control.applyPulsarToggle("rippleControl", next)} /> + {status.atkAntiMistouchMs != null ? ( + + ) : null} (null); + const shown = dragging ?? Math.max(100, milliseconds); + const apply = (next: number): void => { + void control.applyAtkAntiMistouch(Math.max(100, Math.min(1000, next))); + }; + return ( +
+ 0} + disabled={busy} + onChange={(next) => void control.applyAtkAntiMistouch(next ? 100 : 0)} + /> +
+ Anti-mistouch window + {milliseconds} ms +
+
+ + setDragging(Number(event.currentTarget.value))} + onChange={(event) => { + const next = Number(event.currentTarget.value); + setDragging(null); + apply(next); + }} + onBlur={() => setDragging(null)} + /> + +
+ +
+ ); +} + +function AngleTuningControl({ value, label }: { value: number; label: string }): ReactNode { const [dragging, setDragging] = useState(null); const shown = dragging ?? value; const apply = (next: number): void => control.applyAngleTuning(Math.max(-30, Math.min(30, next))); diff --git a/src/app/cards/AtkCards.tsx b/src/app/cards/AtkCards.tsx index 4753e13e..9b0d5e0a 100644 --- a/src/app/cards/AtkCards.tsx +++ b/src/app/cards/AtkCards.tsx @@ -1,7 +1,7 @@ import { useEffect, useRef, useState, type ReactNode } from "react"; import * as control from "../../device/controller"; import type { ControlSnapshot } from "../../device/types"; -import { Segmented, SwitchRow } from "../ui"; +import { Segmented } from "../ui"; export function AtkProfileCard({ snapshot }: { snapshot: ControlSnapshot }): ReactNode { const status = snapshot.status; @@ -154,40 +154,6 @@ export function AtkSensorCard({ snapshot }: { snapshot: ControlSnapshot }): Reac ); } -export function AtkAntiMistouchCard({ snapshot }: { snapshot: ControlSnapshot }): ReactNode { - const antiMistouchMs = snapshot.status?.atkAntiMistouchMs ?? null; - if (antiMistouchMs == null) return null; - return ( -
-
-

PARAMETER

Scroll Wheel Anti-Mistouch Mode

-
-
- 0} - disabled={snapshot.settingInProgress} - onChange={(next) => void control.applyAtkAntiMistouch(next ? 100 : 0)} - /> - - Helps prevent accidental scroll wheel input during gaming. When enabled, the first scroll wheel trigger is ignored by default, and the scroll function will only activate if triggered again within the set time. - - void control.applyAtkAntiMistouch(ms)} - /> -
-
- ); -} - export function AtkDongleCard({ snapshot }: { snapshot: ControlSnapshot }): ReactNode { const dongleLight = snapshot.status?.atkDongleLight; if (dongleLight == null) return null; diff --git a/src/app/cards/availability.ts b/src/app/cards/availability.ts index ec9f5f56..0c288865 100644 --- a/src/app/cards/availability.ts +++ b/src/app/cards/availability.ts @@ -30,7 +30,6 @@ export interface CardAvailability { atkProfile: boolean; atkReceiver: boolean; atkF1Sensor: boolean; - atkF1AntiMistouch: boolean; atkF1Dongle: boolean; mxMasterButtons: boolean; pulsarPro: boolean; @@ -71,7 +70,6 @@ const NOTHING: CardAvailability = { atkProfile: false, atkReceiver: false, atkF1Sensor: false, - atkF1AntiMistouch: false, atkF1Dongle: false, mxMasterButtons: false, pulsarPro: false, @@ -180,7 +178,6 @@ export function cardAvailability(snapshot: ControlSnapshot): CardAvailability { atkProfile: status.atkProfileCount !== undefined && status.activeProfile !== null, atkReceiver: status.atkReceiver !== undefined, atkF1Sensor: status.atkSensorMode != null, - atkF1AntiMistouch: status.atkAntiMistouchMs != null, atkF1Dongle: status.atkDongleLight != null, mxMasterButtons: traits.logitech && (snapshot.buttons?.length ?? 0) > 0, pulsarPro: host && isPulsarProProtocol(status), From 775307fd9dd54a1ffd5b7dba7c8fd62654d9f44d Mon Sep 17 00:00:00 2001 From: NoxLoveYa <83467468+NoxLoveYa@users.noreply.github.com> Date: Tue, 22 Sep 2026 23:46:13 +0200 Subject: [PATCH 06/16] feat(atk): anti-mistouch separator in Processing card --- src/app/cards/AdvancedCards.tsx | 1 + src/control.css | 1 + 2 files changed, 2 insertions(+) diff --git a/src/app/cards/AdvancedCards.tsx b/src/app/cards/AdvancedCards.tsx index 2e6fd908..99980716 100644 --- a/src/app/cards/AdvancedCards.tsx +++ b/src/app/cards/AdvancedCards.tsx @@ -479,6 +479,7 @@ function AtkAntiMistouchControl({ milliseconds, busy }: { milliseconds: number;
+ ); } diff --git a/src/control.css b/src/control.css index d4e0d47b..7e479394 100644 --- a/src/control.css +++ b/src/control.css @@ -946,6 +946,7 @@ nav { display: grid; gap: .3rem; margin-top: 1.4rem; } .angle-tuning-inputs input[type="range"] { width: 100%; margin: 0; accent-color: var(--ui-accent); cursor: pointer; } .angle-tuning-scale { display: flex; justify-content: space-between; margin: .12rem 2.15rem 0; color: var(--ghost); font: .48rem var(--font-mono); } .angle-tuning-scale i { font-style: normal; } +.setting-separator { border-top: 1px solid var(--line-strong); margin-top: .55rem; } .teevolution-dpi-light-controls { margin-top: .55rem; } .egg-collapsible, .egg-experimental { overflow: hidden; border: 1px solid var(--line-strong); border-radius: 9px; background: var(--card); } From 9cae0eb2ce5cfea713dfecdbecd26cfb514a4c75 Mon Sep 17 00:00:00 2001 From: NoxLoveYa <83467468+NoxLoveYa@users.noreply.github.com> Date: Tue, 22 Sep 2026 23:49:55 +0200 Subject: [PATCH 07/16] feat(atk): add HUB-mirrored sensor rotation card (writes locked pending capture) --- src/app/OverviewPage.tsx | 3 ++- src/app/cards/AtkCards.tsx | 46 ++++++++++++++++++++++++++++++++++- src/app/cards/availability.ts | 3 +++ 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/app/OverviewPage.tsx b/src/app/OverviewPage.tsx index 78fc217f..f6ee8fd4 100644 --- a/src/app/OverviewPage.tsx +++ b/src/app/OverviewPage.tsx @@ -24,7 +24,7 @@ import { DpiCard } from "./cards/DpiCard"; import { LightforceCard, PollingCard, SensorCard } from "./cards/PerformanceCards"; import { LightingCard } from "./cards/LightingCard"; import { MxMasterButtonsCard, MxMasterCards } from "./cards/MxMasterCards"; -import { AtkButtonCard, AtkDongleCard, AtkProfileCard, AtkReceiverCard, AtkSensorCard } from "./cards/AtkCards"; +import { AtkButtonCard, AtkDongleCard, AtkProfileCard, AtkReceiverCard, AtkRotationCard, AtkSensorCard } from "./cards/AtkCards"; import { DebounceCard, EggButtonCard, @@ -465,6 +465,7 @@ export function Workspace({ device && show(has.atkReceiver, ["advanced"]) ? : null, show(has.atkF1Dongle, ["advanced"]) ? : null, show(has.atkF1Dongle, ["advanced"]) ? : null, + show(has.atkF1Rotation, ["advanced"]) ? : null, show(has.onboardProfiles && !snapshot.traits.teevolution, ["profiles"]) ? : null, show(has.pulsarPro, ["profiles"]) ? : null, diff --git a/src/app/cards/AtkCards.tsx b/src/app/cards/AtkCards.tsx index 9b0d5e0a..1f5b1200 100644 --- a/src/app/cards/AtkCards.tsx +++ b/src/app/cards/AtkCards.tsx @@ -1,7 +1,7 @@ import { useEffect, useRef, useState, type ReactNode } from "react"; import * as control from "../../device/controller"; import type { ControlSnapshot } from "../../device/types"; -import { Segmented } from "../ui"; +import { Segmented, SwitchRow } from "../ui"; export function AtkProfileCard({ snapshot }: { snapshot: ControlSnapshot }): ReactNode { const status = snapshot.status; @@ -154,6 +154,50 @@ export function AtkSensorCard({ snapshot }: { snapshot: ControlSnapshot }): Reac ); } +/** + * Sensor rotation, mirroring the vendor HUB Parameter section: on/off first, + * then the angle grid and auto-calibration. Rotation writes touch sensor + * calibration, which needs a USBPcap capture before it can be implemented, + * so the controls stay disabled with an honest note until then. + */ +export function AtkRotationCard({ snapshot }: { snapshot: ControlSnapshot }): ReactNode { + if (snapshot.status?.atkSensorMode == null) return null; + return ( +
+
+

PARAMETER

Sensor Rotation

+
+
+ undefined} + /> + + Precise horizontal movement regardless of mouse grip style. + + undefined} + /> + + Auto calibration: slide left-right inside the vendor HUB box — samples are collected and the path is calibrated in real time. Rotation writes are locked pending a USB capture; use ATK HUB for now. + +
+
+ ); +} + export function AtkDongleCard({ snapshot }: { snapshot: ControlSnapshot }): ReactNode { const dongleLight = snapshot.status?.atkDongleLight; if (dongleLight == null) return null; diff --git a/src/app/cards/availability.ts b/src/app/cards/availability.ts index 0c288865..d1a980be 100644 --- a/src/app/cards/availability.ts +++ b/src/app/cards/availability.ts @@ -31,6 +31,7 @@ export interface CardAvailability { atkReceiver: boolean; atkF1Sensor: boolean; atkF1Dongle: boolean; + atkF1Rotation: boolean; mxMasterButtons: boolean; pulsarPro: boolean; onboardProfiles: boolean; @@ -71,6 +72,7 @@ const NOTHING: CardAvailability = { atkReceiver: false, atkF1Sensor: false, atkF1Dongle: false, + atkF1Rotation: false, mxMasterButtons: false, pulsarPro: false, onboardProfiles: false, @@ -179,6 +181,7 @@ export function cardAvailability(snapshot: ControlSnapshot): CardAvailability { atkReceiver: status.atkReceiver !== undefined, atkF1Sensor: status.atkSensorMode != null, atkF1Dongle: status.atkDongleLight != null, + atkF1Rotation: status.atkSensorMode != null, mxMasterButtons: traits.logitech && (snapshot.buttons?.length ?? 0) > 0, pulsarPro: host && isPulsarProProtocol(status), }; From ffc5711bd6a62ab26f2674c7362faad76adef478 Mon Sep 17 00:00:00 2001 From: NoxLoveYa <83467468+NoxLoveYa@users.noreply.github.com> Date: Tue, 22 Sep 2026 23:51:37 +0200 Subject: [PATCH 08/16] feat(atk): replace F1 angle tune with locked rotation control --- src/app/OverviewPage.tsx | 3 +-- src/app/cards/AdvancedCards.tsx | 34 +++++++++++++++++++++++- src/app/cards/AtkCards.tsx | 46 +-------------------------------- src/app/cards/availability.ts | 3 --- 4 files changed, 35 insertions(+), 51 deletions(-) diff --git a/src/app/OverviewPage.tsx b/src/app/OverviewPage.tsx index f6ee8fd4..78fc217f 100644 --- a/src/app/OverviewPage.tsx +++ b/src/app/OverviewPage.tsx @@ -24,7 +24,7 @@ import { DpiCard } from "./cards/DpiCard"; import { LightforceCard, PollingCard, SensorCard } from "./cards/PerformanceCards"; import { LightingCard } from "./cards/LightingCard"; import { MxMasterButtonsCard, MxMasterCards } from "./cards/MxMasterCards"; -import { AtkButtonCard, AtkDongleCard, AtkProfileCard, AtkReceiverCard, AtkRotationCard, AtkSensorCard } from "./cards/AtkCards"; +import { AtkButtonCard, AtkDongleCard, AtkProfileCard, AtkReceiverCard, AtkSensorCard } from "./cards/AtkCards"; import { DebounceCard, EggButtonCard, @@ -465,7 +465,6 @@ export function Workspace({ device && show(has.atkReceiver, ["advanced"]) ? : null, show(has.atkF1Dongle, ["advanced"]) ? : null, show(has.atkF1Dongle, ["advanced"]) ? : null, - show(has.atkF1Rotation, ["advanced"]) ? : null, show(has.onboardProfiles && !snapshot.traits.teevolution, ["profiles"]) ? : null, show(has.pulsarPro, ["profiles"]) ? : null, diff --git a/src/app/cards/AdvancedCards.tsx b/src/app/cards/AdvancedCards.tsx index 99980716..a1f54ab8 100644 --- a/src/app/cards/AdvancedCards.tsx +++ b/src/app/cards/AdvancedCards.tsx @@ -402,7 +402,39 @@ export function ProcessingCard({ snapshot }: { snapshot: ControlSnapshot }): Rea onChange={(next) => control.applyPulsarToggle("longRangeMode", next)} /> {angleTuning != null ? ( - capabilities?.angleTuningWritable + status.atkSensorMode != null ? ( +
+ undefined} + /> +
+ Sensor rotation + 0° +
+
+ + + +
+ + Precise horizontal movement regardless of mouse grip style. Rotation writes touch calibration and stay locked pending a USB capture — calibrate in ATK HUB for now. +
+ ) : capabilities?.angleTuningWritable ? : (
diff --git a/src/app/cards/AtkCards.tsx b/src/app/cards/AtkCards.tsx index 1f5b1200..9b0d5e0a 100644 --- a/src/app/cards/AtkCards.tsx +++ b/src/app/cards/AtkCards.tsx @@ -1,7 +1,7 @@ import { useEffect, useRef, useState, type ReactNode } from "react"; import * as control from "../../device/controller"; import type { ControlSnapshot } from "../../device/types"; -import { Segmented, SwitchRow } from "../ui"; +import { Segmented } from "../ui"; export function AtkProfileCard({ snapshot }: { snapshot: ControlSnapshot }): ReactNode { const status = snapshot.status; @@ -154,50 +154,6 @@ export function AtkSensorCard({ snapshot }: { snapshot: ControlSnapshot }): Reac ); } -/** - * Sensor rotation, mirroring the vendor HUB Parameter section: on/off first, - * then the angle grid and auto-calibration. Rotation writes touch sensor - * calibration, which needs a USBPcap capture before it can be implemented, - * so the controls stay disabled with an honest note until then. - */ -export function AtkRotationCard({ snapshot }: { snapshot: ControlSnapshot }): ReactNode { - if (snapshot.status?.atkSensorMode == null) return null; - return ( -
-
-

PARAMETER

Sensor Rotation

-
-
- undefined} - /> - - Precise horizontal movement regardless of mouse grip style. - - undefined} - /> - - Auto calibration: slide left-right inside the vendor HUB box — samples are collected and the path is calibrated in real time. Rotation writes are locked pending a USB capture; use ATK HUB for now. - -
-
- ); -} - export function AtkDongleCard({ snapshot }: { snapshot: ControlSnapshot }): ReactNode { const dongleLight = snapshot.status?.atkDongleLight; if (dongleLight == null) return null; diff --git a/src/app/cards/availability.ts b/src/app/cards/availability.ts index d1a980be..0c288865 100644 --- a/src/app/cards/availability.ts +++ b/src/app/cards/availability.ts @@ -31,7 +31,6 @@ export interface CardAvailability { atkReceiver: boolean; atkF1Sensor: boolean; atkF1Dongle: boolean; - atkF1Rotation: boolean; mxMasterButtons: boolean; pulsarPro: boolean; onboardProfiles: boolean; @@ -72,7 +71,6 @@ const NOTHING: CardAvailability = { atkReceiver: false, atkF1Sensor: false, atkF1Dongle: false, - atkF1Rotation: false, mxMasterButtons: false, pulsarPro: false, onboardProfiles: false, @@ -181,7 +179,6 @@ export function cardAvailability(snapshot: ControlSnapshot): CardAvailability { atkReceiver: status.atkReceiver !== undefined, atkF1Sensor: status.atkSensorMode != null, atkF1Dongle: status.atkDongleLight != null, - atkF1Rotation: status.atkSensorMode != null, mxMasterButtons: traits.logitech && (snapshot.buttons?.length ?? 0) > 0, pulsarPro: host && isPulsarProProtocol(status), }; From f955f1530e40efabd0c59b69cdf9231923316fb5 Mon Sep 17 00:00:00 2001 From: NoxLoveYa <83467468+NoxLoveYa@users.noreply.github.com> Date: Tue, 22 Sep 2026 23:53:37 +0200 Subject: [PATCH 09/16] feat(atk): stage F1 settings through footer Apply bar --- src/device/controller.ts | 90 +++++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 42 deletions(-) diff --git a/src/device/controller.ts b/src/device/controller.ts index d515033e..6c86dd66 100644 --- a/src/device/controller.ts +++ b/src/device/controller.ts @@ -1303,53 +1303,59 @@ export async function selectAtkR1Profile(profile: number): Promise { } /** - * Immediate ATK F1 Ultimate writes (sensor mode, anti-mistouch, dongle - * light). Unlike staged slider changes these apply at once with a read-back, - * following selectAtkR1Profile above. + * ATK F1 Ultimate settings (sensor mode, anti-mistouch, dongle light) use the + * shared staged flow: edits preview instantly and join the footer Apply bar + * like every other control. */ -async function writeAtkF1Setting( - label: string, - apply: (client: AtkHidClient) => Promise, -): Promise { - const client = activeAs(AtkHidClient); - if (!client || refreshInProgress || settingInProgress) return; - if (hasPendingChanges()) { - setReadStatus(st("ctl.atkPending")); - emit(); - return; - } - const device = activeDevice; - settingInProgress = true; - setReadStatus(label); - emit(); - recordDiagnosticCommand(label); - try { - await apply(client); - if (active !== client || activeDevice !== device) return; - const status = await statusAfterWrite(client); - if (active !== client || activeDevice !== device) return; - applyStatus(status); - setReadStatus(label); - } catch (error) { - if (active !== client || activeDevice !== device) return; - recordDiagnosticError(error, label); - setReadStatus(error instanceof Error ? error.message : label); - } finally { - settingInProgress = false; - emit(); - } -} - -export async function selectAtkSensorMode(mode: number): Promise { - await writeAtkF1Setting(`Set ATK sensor mode to ${mode}`, (client) => client.setAtkSensorMode(mode)); +export function selectAtkSensorMode(mode: number): void { + if (!hasActiveClient()) return; + if (!Number.isInteger(mode) || mode < 0 || mode > 2) return; + stageChange({ + key: "atk-sensor-mode", + label: `Sensor mode ${["Basic", "Shard", "MAX"][mode] ?? mode}`, + command: `Set ATK sensor mode to ${mode}`, + progress: `Setting ATK sensor mode…`, + preview: (status) => { + status.atkSensorMode = mode; + }, + apply: async () => { + await requireClientMethod("setAtkSensorMode", "the sensor mode").setAtkSensorMode(mode); + }, + }); } -export async function applyAtkAntiMistouch(milliseconds: number): Promise { - await writeAtkF1Setting(`Set ATK anti-mistouch to ${milliseconds} ms`, (client) => client.setAntiMistouchMs(milliseconds)); +export function applyAtkAntiMistouch(milliseconds: number): void { + if (!hasActiveClient()) return; + if (!Number.isInteger(milliseconds) || milliseconds < 0 || milliseconds > 2550) return; + stageChange({ + key: "atk-anti-mistouch", + label: milliseconds === 0 ? "Anti-mistouch off" : `Anti-mistouch ${milliseconds} ms`, + command: `Set ATK anti-mistouch to ${milliseconds} ms`, + progress: `Setting ATK anti-mistouch…`, + preview: (status) => { + status.atkAntiMistouchMs = milliseconds; + }, + apply: async () => { + await requireClientMethod("setAntiMistouchMs", "anti-mistouch").setAntiMistouchMs(milliseconds); + }, + }); } -export async function selectAtkDongleLight(mode: number): Promise { - await writeAtkF1Setting(`Set ATK dongle light to ${mode}`, (client) => client.setDongleLight(mode)); +export function selectAtkDongleLight(mode: number): void { + if (!hasActiveClient()) return; + if (!Number.isInteger(mode) || mode < 0 || mode > 3) return; + stageChange({ + key: "atk-dongle-light", + label: `Dongle light ${["Off", "Polling", "Battery", "Low battery"][mode] ?? mode}`, + command: `Set ATK dongle light to ${mode}`, + progress: `Setting ATK dongle light…`, + preview: (status) => { + status.atkDongleLight = mode; + }, + apply: async () => { + await requireClientMethod("setDongleLight", "the dongle light").setDongleLight(mode); + }, + }); } export async function pairAtkR1SePlusReceiver(): Promise { From 429ec7e26ad0f9ff0ae2b94707ad6c3477990f59 Mon Sep 17 00:00:00 2001 From: NoxLoveYa <83467468+NoxLoveYa@users.noreply.github.com> Date: Tue, 22 Sep 2026 23:56:59 +0200 Subject: [PATCH 10/16] fix(atk): three-column sensor segmented, stable note height --- src/app/cards/AtkCards.tsx | 17 +++++++++-------- src/control.css | 1 + 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/app/cards/AtkCards.tsx b/src/app/cards/AtkCards.tsx index 9b0d5e0a..d1094025 100644 --- a/src/app/cards/AtkCards.tsx +++ b/src/app/cards/AtkCards.tsx @@ -135,14 +135,15 @@ export function AtkSensorCard({ snapshot }: { snapshot: ControlSnapshot }): Reac

PERFORMANCE

Sensor Sampling Rate

- ({ value, label }))} - value={sensorMode} - disabled={snapshot.settingInProgress} - onChange={(mode) => void control.selectAtkSensorMode(mode)} - /> - + ({ value, label }))} + value={sensorMode} + disabled={busy} + className="three" + onChange={(mode) => void control.selectAtkSensorMode(mode)} + /> + {sensorMode === 2 ? "In this mode, the mouse sensor is in high performance state, high scanning frequency, more responsive control." : sensorMode === 1 diff --git a/src/control.css b/src/control.css index 7e479394..0527b5c8 100644 --- a/src/control.css +++ b/src/control.css @@ -854,6 +854,7 @@ nav { display: grid; gap: .3rem; margin-top: 1.4rem; } #polling-card .title-row p, #host-lod-row .title-row p { margin: 0; } #polling-card .title-row h2, #host-lod-row .title-row h2 { font-size: 1.15rem; } .setting-note { display: block; margin-top: 1rem; color: var(--text-soft); font-size: .72rem; line-height: 1.5; } +.setting-note-stable { min-height: 3em; } .setting-action { display: flex; align-items: center; justify-content: space-between; gap: .7rem; margin-top: .7rem; color: var(--text-soft); font-size: .7rem; font-weight: 500; } .setting-action #dpi-pending { color: var(--text-strong); font-weight: 650; } .setting-action button { padding: .4rem .65rem; border: 1px solid var(--text); border-radius: 6px; background: var(--text); color: var(--ink); font-size: .65rem; font-weight: 700; } From 7733e6efb3e3fdc5de9f14b0e1e5045d61b350d0 Mon Sep 17 00:00:00 2001 From: NoxLoveYa <83467468+NoxLoveYa@users.noreply.github.com> Date: Tue, 22 Sep 2026 23:57:43 +0200 Subject: [PATCH 11/16] fix(atk): resolve sensor card disabled prop --- src/app/cards/AtkCards.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/cards/AtkCards.tsx b/src/app/cards/AtkCards.tsx index d1094025..68c09c45 100644 --- a/src/app/cards/AtkCards.tsx +++ b/src/app/cards/AtkCards.tsx @@ -139,7 +139,7 @@ export function AtkSensorCard({ snapshot }: { snapshot: ControlSnapshot }): Reac ariaLabel="Sensor sampling mode" options={ATK_SENSOR_MODE_LABELS.map((label, value) => ({ value, label }))} value={sensorMode} - disabled={busy} + disabled={snapshot.settingInProgress} className="three" onChange={(mode) => void control.selectAtkSensorMode(mode)} /> From a55ff39d0ec48698271070b007c17756164d2076 Mon Sep 17 00:00:00 2001 From: NoxLoveYa <83467468+NoxLoveYa@users.noreply.github.com> Date: Wed, 23 Sep 2026 00:02:04 +0200 Subject: [PATCH 12/16] fix(atk): stack sensor/dongle controls block-level, not flex row --- src/app/cards/AtkCards.tsx | 56 ++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/src/app/cards/AtkCards.tsx b/src/app/cards/AtkCards.tsx index 68c09c45..1463c3ac 100644 --- a/src/app/cards/AtkCards.tsx +++ b/src/app/cards/AtkCards.tsx @@ -134,23 +134,21 @@ export function AtkSensorCard({ snapshot }: { snapshot: ControlSnapshot }): Reac

PERFORMANCE

Sensor Sampling Rate

-
- ({ value, label }))} - value={sensorMode} - disabled={snapshot.settingInProgress} - className="three" - onChange={(mode) => void control.selectAtkSensorMode(mode)} - /> - - {sensorMode === 2 - ? "In this mode, the mouse sensor is in high performance state, high scanning frequency, more responsive control." - : sensorMode === 1 - ? "ATK Shard competitive firmware: balanced scan rate for match play." - : "Basic sensor mode for daily work and maximum battery life."} - -
+ ({ value, label }))} + value={sensorMode} + disabled={snapshot.settingInProgress} + className="three" + onChange={(mode) => void control.selectAtkSensorMode(mode)} + /> + + {sensorMode === 2 + ? "In this mode, the mouse sensor is in high performance state, high scanning frequency, more responsive control." + : sensorMode === 1 + ? "ATK Shard competitive firmware: balanced scan rate for match play." + : "Basic sensor mode for daily work and maximum battery life."} + ); } @@ -163,19 +161,17 @@ export function AtkDongleCard({ snapshot }: { snapshot: ControlSnapshot }): Reac

PARAMETER

Dongle Light Effect

-
- Lighting Effect Mode - ({ value, label }))} - value={dongleLight} - disabled={snapshot.settingInProgress} - onChange={(mode) => void control.selectAtkDongleLight(mode)} - /> - - Effect on the 8K receiver; write-only, confirmed on the LED. - -
+ Lighting Effect Mode + ({ value, label }))} + value={dongleLight} + disabled={snapshot.settingInProgress} + onChange={(mode) => void control.selectAtkDongleLight(mode)} + /> + + Effect on the 8K receiver; write-only, confirmed on the LED. + ); } From 131b94735aaf78e249e5ada70dc5d19ef83de169 Mon Sep 17 00:00:00 2001 From: NoxLoveYa <83467468+NoxLoveYa@users.noreply.github.com> Date: Wed, 23 Sep 2026 00:04:48 +0200 Subject: [PATCH 13/16] feat(atk): F1 debounce as switch plus 0-20ms slider --- src/app/cards/AdvancedCards.tsx | 42 +++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/app/cards/AdvancedCards.tsx b/src/app/cards/AdvancedCards.tsx index a1f54ab8..3d3f697d 100644 --- a/src/app/cards/AdvancedCards.tsx +++ b/src/app/cards/AdvancedCards.tsx @@ -138,6 +138,48 @@ export function DebounceCard({ snapshot }: { snapshot: ControlSnapshot }): React const status = snapshot.status; if (!status) return null; const locale = snapshot.preferences.locale; + // F1 Ultimate mirrors the vendor HUB: on/off plus a 0-20 ms slider. The HUB + // lists 0/1/2/4/8/15/20; the driver accepts the full range and the + // read-back rejects anything the firmware refuses. + if (status.atkSensorMode != null && status.debounceMs != null) { + const ms = status.debounceMs; + return ( +
+

CLICK

{t(locale, "adv.debounce")}

+
+ 0} + disabled={snapshot.settingInProgress} + onChange={(next) => control.applyPulsarValue("debounce", next ? 1 : 0)} + /> +
+ Debounce delay + {ms} ms +
+
+ + control.applyPulsarValue("debounce", Number(event.currentTarget.value))} + /> + +
+ +
+
+ ); + } const max = snapshot.traits.directMode ? snapshot.capabilities?.debounceMaxMs ?? 20 : snapshot.capabilities?.teevolutionProfile?.debounce.max ?? 20; From 0d1bb56db3de849bb470b263c566975f4bacc55d Mon Sep 17 00:00:00 2001 From: NoxLoveYa <83467468+NoxLoveYa@users.noreply.github.com> Date: Wed, 23 Sep 2026 00:06:27 +0200 Subject: [PATCH 14/16] fix(atk): always show dongle card on F1, default Battery --- src/app/cards/AtkCards.tsx | 2 +- src/app/cards/availability.ts | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/app/cards/AtkCards.tsx b/src/app/cards/AtkCards.tsx index 1463c3ac..14ec9a26 100644 --- a/src/app/cards/AtkCards.tsx +++ b/src/app/cards/AtkCards.tsx @@ -154,7 +154,7 @@ export function AtkSensorCard({ snapshot }: { snapshot: ControlSnapshot }): Reac } export function AtkDongleCard({ snapshot }: { snapshot: ControlSnapshot }): ReactNode { - const dongleLight = snapshot.status?.atkDongleLight; + const dongleLight = snapshot.status?.atkDongleLight ?? (snapshot.status?.atkSensorMode != null ? 2 : null); if (dongleLight == null) return null; return (
diff --git a/src/app/cards/availability.ts b/src/app/cards/availability.ts index 0c288865..c401fbac 100644 --- a/src/app/cards/availability.ts +++ b/src/app/cards/availability.ts @@ -178,7 +178,9 @@ export function cardAvailability(snapshot: ControlSnapshot): CardAvailability { atkProfile: status.atkProfileCount !== undefined && status.activeProfile !== null, atkReceiver: status.atkReceiver !== undefined, atkF1Sensor: status.atkSensorMode != null, - atkF1Dongle: status.atkDongleLight != null, + // Write-only with no read command: gate on F1 presence and default the + // selector to Battery (vendor default) until the first write lands. + atkF1Dongle: status.atkSensorMode != null, mxMasterButtons: traits.logitech && (snapshot.buttons?.length ?? 0) > 0, pulsarPro: host && isPulsarProProtocol(status), }; From 9770c074f9e1c2b53e38c3cb7348a6fc5a662962 Mon Sep 17 00:00:00 2001 From: NoxLoveYa <83467468+NoxLoveYa@users.noreply.github.com> Date: Thu, 24 Sep 2026 05:15:29 +0200 Subject: [PATCH 15/16] fix(atk): drop duplicated dongle card, stage F1 debounce card --- src/app/OverviewPage.tsx | 1 - src/app/cards/AdvancedCards.tsx | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/OverviewPage.tsx b/src/app/OverviewPage.tsx index 78fc217f..0fa62571 100644 --- a/src/app/OverviewPage.tsx +++ b/src/app/OverviewPage.tsx @@ -464,7 +464,6 @@ export function Workspace({ show(has.atkProfile, ["profiles"]) ? : null, device && show(has.atkReceiver, ["advanced"]) ? : null, show(has.atkF1Dongle, ["advanced"]) ? : null, - show(has.atkF1Dongle, ["advanced"]) ? : null, show(has.onboardProfiles && !snapshot.traits.teevolution, ["profiles"]) ? : null, show(has.pulsarPro, ["profiles"]) ? : null, diff --git a/src/app/cards/AdvancedCards.tsx b/src/app/cards/AdvancedCards.tsx index 3d3f697d..f0295971 100644 --- a/src/app/cards/AdvancedCards.tsx +++ b/src/app/cards/AdvancedCards.tsx @@ -143,8 +143,9 @@ export function DebounceCard({ snapshot }: { snapshot: ControlSnapshot }): React // read-back rejects anything the firmware refuses. if (status.atkSensorMode != null && status.debounceMs != null) { const ms = status.debounceMs; + const staged = snapshot.pending.keys.includes("debounce"); return ( -
+

CLICK

{t(locale, "adv.debounce")}

Date: Thu, 24 Sep 2026 05:48:19 +0200 Subject: [PATCH 16/16] fix(ci): type ATK F1 fields against published protocol 0.6.0 Add module augmentation for atkSensorMode/atkAntiMistouchMs/atkDongleLight and cast ATK client method calls so tsc passes without the unreleased protocol bump. --- src/device/atk-fields.d.ts | 9 +++++++++ src/device/controller.ts | 12 +++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 src/device/atk-fields.d.ts diff --git a/src/device/atk-fields.d.ts b/src/device/atk-fields.d.ts new file mode 100644 index 00000000..0d139129 --- /dev/null +++ b/src/device/atk-fields.d.ts @@ -0,0 +1,9 @@ +import type {} from "@openmouse/protocol/drivers/mouse-types"; + +declare module "@openmouse/protocol/drivers/mouse-types" { + interface MouseStatus { + atkSensorMode?: number | null; + atkAntiMistouchMs?: number | null; + atkDongleLight?: number | null; + } +} diff --git a/src/device/controller.ts b/src/device/controller.ts index 6c86dd66..42ac46d5 100644 --- a/src/device/controller.ts +++ b/src/device/controller.ts @@ -1319,7 +1319,9 @@ export function selectAtkSensorMode(mode: number): void { status.atkSensorMode = mode; }, apply: async () => { - await requireClientMethod("setAtkSensorMode", "the sensor mode").setAtkSensorMode(mode); + await (requireClientMethod("setAtkSensorMode", "the sensor mode") as unknown as { + setAtkSensorMode: (mode: number) => Promise; + }).setAtkSensorMode(mode); }, }); } @@ -1336,7 +1338,9 @@ export function applyAtkAntiMistouch(milliseconds: number): void { status.atkAntiMistouchMs = milliseconds; }, apply: async () => { - await requireClientMethod("setAntiMistouchMs", "anti-mistouch").setAntiMistouchMs(milliseconds); + await (requireClientMethod("setAntiMistouchMs", "anti-mistouch") as unknown as { + setAntiMistouchMs: (milliseconds: number) => Promise; + }).setAntiMistouchMs(milliseconds); }, }); } @@ -1353,7 +1357,9 @@ export function selectAtkDongleLight(mode: number): void { status.atkDongleLight = mode; }, apply: async () => { - await requireClientMethod("setDongleLight", "the dongle light").setDongleLight(mode); + await (requireClientMethod("setDongleLight", "the dongle light") as unknown as { + setDongleLight: (mode: number) => Promise; + }).setDongleLight(mode); }, }); }