Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
527e5d9
HITS tuning: flash at once when instant flash is on
onyxhq-dev Oct 8, 2026
23fa4e6
HITS tuning: hide the Apply buttons while instant flash is on
onyxhq-dev Oct 8, 2026
fcbde37
HITS card: first on the Buttons tab, with Bunny Hop inside it
onyxhq-dev Oct 8, 2026
4c6106e
HITS card: keep its own card chrome beside the mouse panel
onyxhq-dev Oct 8, 2026
61db374
Show battery time left in the device status pill, with a rated-life f…
onyxhq-dev Oct 8, 2026
43eaace
Battery estimate: follow the polling rate on the PRO X 3
onyxhq-dev Oct 8, 2026
d3c6fa9
battery-history: the 135 h is Logitech's figure at 1000 Hz, not an as…
onyxhq-dev Oct 8, 2026
56a67ed
Show battery time left in the Overview status pill too
onyxhq-dev Oct 8, 2026
a4df4c5
HITS presets: save setups in the browser and share them as a code
onyxhq-dev Oct 8, 2026
bba9d0a
HITS presets: import and delete use modals, not browser prompts
onyxhq-dev Oct 8, 2026
69147bc
Button assignments as a picture, like G HUB's Zuweisungen (PRO X 2 / …
onyxhq-dev Oct 8, 2026
f704ea2
Button map: one mouse, full-width cards, status in the header
onyxhq-dev Oct 8, 2026
9e41e31
Buttons tab: the button map first, then the HITS card
onyxhq-dev Oct 8, 2026
cb66f9e
Button map: a more compact card and a themed scrollbar
onyxhq-dev Oct 8, 2026
4702ed9
Buttons tab: button map left, HITS card right
onyxhq-dev Oct 8, 2026
bc6ae56
Button map: same card design as HITS Tuning
onyxhq-dev Oct 8, 2026
81a2b28
Buttons tab: line the HITS card up with the button map
onyxhq-dev Oct 8, 2026
8b4be0a
Button map: keep the command search visible while scrolling
onyxhq-dev Oct 8, 2026
b9d7814
Merge branch 'feat/battery-rated-hours' into feat/button-map
onyxhq-dev Oct 8, 2026
7299f11
Button map: show the battery hours in the header
onyxhq-dev Oct 8, 2026
a9d69f2
Button map: record any key or shortcut for a button
onyxhq-dev Oct 8, 2026
27e9c9d
Button map: reset a button or all buttons to their defaults
onyxhq-dev Oct 8, 2026
c2d845c
Button map: stay visible while a flash is running
onyxhq-dev Oct 8, 2026
7825a8d
Bunny Hop: stay visible while a flash is running
onyxhq-dev Oct 8, 2026
27f7985
Button map: a shorter card that follows the window height
onyxhq-dev Oct 8, 2026
29bef5f
Button map: keep the commands beside the mouse at half width, so the …
onyxhq-dev Oct 8, 2026
6742c18
Button map: fill the window height, bigger mouse
onyxhq-dev Oct 8, 2026
1669424
Button map: search and buttons stay fixed above a scrolling command list
onyxhq-dev Oct 8, 2026
7d3c961
Button map: the command list scrolls down to the card border
onyxhq-dev Oct 8, 2026
37d2286
HITS card: built-in presets and a Reset button
onyxhq-dev Oct 8, 2026
d045524
HITS card: built-in presets, Reset, and the G HUB pro player profiles
onyxhq-dev Oct 8, 2026
8a4551f
HITS card: a Default preset (5, rapid trigger off, 3) that Reset loads
onyxhq-dev Oct 8, 2026
f51e4fb
HITS card: a save-preset modal instead of the browser prompt
onyxhq-dev Oct 8, 2026
b84fb3c
HITS card: a speed test that times your presses against a conventiona…
onyxhq-dev Oct 8, 2026
41f47b3
HITS test: G HUB style result with the time per step
onyxhq-dev Oct 8, 2026
51cb9bd
HITS test: one click per run, like G HUB
onyxhq-dev Oct 8, 2026
2365ae4
HITS presets: a roomier panel with the select on its own row and even…
onyxhq-dev Oct 8, 2026
c9c09ee
HITS card: presets above the press meter
onyxhq-dev Oct 8, 2026
a1ff857
Bunny Hop: its own card under HITS Tuning, not part of it
onyxhq-dev Oct 8, 2026
b404d0a
Buttons tab: no gap between HITS Tuning and Bunny Hop
onyxhq-dev Oct 8, 2026
1752e8c
HITS card: last-press graph, and copy/swap between the two buttons
onyxhq-dev Oct 8, 2026
4667c50
Bunny Hop: a bounce check that suggests a filter time
onyxhq-dev Oct 8, 2026
3a3cf9d
HITS card: remove the last-press graph
onyxhq-dev Oct 8, 2026
e4cfda7
Polling rate buttons: show the hours a full charge lasts at each rate
onyxhq-dev Oct 8, 2026
1602e17
Share setup: one code for HITS, Bunny Hop, buttons and DPI stages
onyxhq-dev Oct 8, 2026
a7b65d0
HITS card: drop Copy code and Import code, Share setup covers them
onyxhq-dev Oct 8, 2026
5eb5b00
Button map: stay on the Host profile with a notice and a way to open …
onyxhq-dev Oct 8, 2026
52af351
Button map: record key-sequence macros, and warn about duplicate or m…
onyxhq-dev Oct 8, 2026
a02dae3
Button map: no macro recorder on the Superstrike mice, where G HUB lo…
onyxhq-dev Oct 8, 2026
f968a17
Revert "Button map: no macro recorder on the Superstrike mice, where …
onyxhq-dev Oct 8, 2026
74e41a3
Button map: add mouse clicks to the macro recorder
onyxhq-dev Oct 8, 2026
75795bf
Revert "Button map: add mouse clicks to the macro recorder"
onyxhq-dev Oct 8, 2026
cae6f16
Conflict hints: a macro or custom record counts as something set
onyxhq-dev Oct 8, 2026
be96cad
Conflict hints: drop the Nothing is set to Back/Forward warning
onyxhq-dev Oct 8, 2026
d16c602
Reapply "Button map: no macro recorder on the Superstrike mice, where…
onyxhq-dev Oct 8, 2026
eb12c33
HITS presets: the dropdown follows the values on the mouse, so it sur…
onyxhq-dev Oct 8, 2026
a9607a4
Button map: stacked layout drops the fixed height, so the list stays …
onyxhq-dev Oct 8, 2026
a2e926d
Button map: remove the macro recorder, the X3 does not take onboard m…
onyxhq-dev Oct 8, 2026
1a3adeb
Merge branch 'main' into feat/button-map
onyxhq-dev Oct 8, 2026
43a4252
build: raise the CSS budget for the Superstrike Buttons tab
onyxhq-dev Oct 8, 2026
009e1f2
build: raise the JS budget for the Superstrike Buttons tab and protoc…
onyxhq-dev Oct 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions build/check-bundle-size.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ const BUDGET_BYTES: Record<string, number> = {
// Raised to 210 kB after the protocol 0.26.0 device batch left the measured
// CSS aggregate at 194.2 kB — 843 bytes under the old 195 kB — so the next
// card or theme change would have tripped the check for no reviewable reason.
".css": 210_000,
// Raised to 225 kB for the Logitech Superstrike Buttons tab: the button map
// card (callouts, command list, scrollbar), the HITS presets/test/share dialogs
// and the Bunny Hop card add ~15 kB of scoped styles; the measured CSS is
// 210.6 kB, which was 631 bytes over the old 210 kB budget.
".css": 225_000,
// Raised from 510 kB for Bridge discovery, profile editing, automatic
// reconnection, and recent device support, which have since grown further
// with the supported-device page and MX Master remap controls. Preview
Expand Down Expand Up @@ -183,7 +187,10 @@ const BUDGET_BYTES: Record<string, number> = {
// arrive through bridge-hid's SUPPORTED_HID_FILTERS as well as the
// controllers. The measured aggregate is 2,084.2 kB, which left only 801
// bytes of headroom under the old 2,085 kB.
".js": 2_150_000,
// Raised to 2.2 MB for the Superstrike Buttons tab (button map, HITS presets,
// speed test, setup codes, bounce check) merged with protocol 0.28.0: the
// measured aggregate is 2,150.4 kB, 350 bytes over the old 2.15 MB budget.
".js": 2_200_000,
};

const ASSETS = join("dist", "assets");
Expand Down
120 changes: 120 additions & 0 deletions src/app/BounceCheckDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { useEffect, useRef, useState, type ReactNode } from "react";
import { BUNNY_HOP_LIMITS, clampBunnyHopMs } from "@openmouse/protocol/drivers/logitech/onboard-profiles";
import * as control from "../device/controller";
import { t } from "../i18n";
import type { InterfaceLocale } from "../interface-preferences";
import { BOUNCE_GAP_MS, bounceReport } from "../hits-test";

/**
* Click a button as fast as you can for a few seconds. A switch that bounces
* registers two presses a few milliseconds apart, which no finger can do; the
* longest such gap tells how long Bunny Hop needs to be to filter them.
*/
export function BounceCheckDialog({
open,
locale,
bunnyHopMs,
canApply,
onClose,
}: {
open: boolean;
locale: InterfaceLocale;
/** The Bunny Hop time now on the profile, 0 when it is off. */
bunnyHopMs: number;
canApply: boolean;
onClose: () => void;
}): ReactNode {
const dialog = useRef<HTMLDialogElement>(null);
// Press times in ms, per mouse button (0 left, 2 right).
const [presses, setPresses] = useState<Record<number, number[]>>({});

useEffect(() => {
const element = dialog.current;
if (!element) return;
if (open && !element.open) element.showModal();
if (!open && element.open) element.close();
if (!open) setPresses({});
}, [open]);

const reports = [
{ button: 0, label: "Left button" },
{ button: 2, label: "Right button" },
].map((entry) => ({ ...entry, report: bounceReport(presses[entry.button] ?? []) }));
const longest = Math.max(0, ...reports.map((entry) => entry.report.longestGap ?? 0));
const anyBounce = reports.some((entry) => entry.report.bounces > 0);
// Room above the longest bounce seen, snapped to what the mouse accepts.
const suggested = clampBunnyHopMs(Math.ceil(longest * 2));

return (
<dialog
ref={dialog}
className="support-dialog share-profile-dialog"
aria-labelledby="bounce-check-title"
onClose={onClose}
onClick={(event) => { if (event.target === dialog.current) onClose(); }}
>
<div className="support-dialog-inner share-profile-dialog-inner">
<header>
<div>
<p className="overline">Bunny hop</p>
<h2 id="bounce-check-title">Bounce check</h2>
</div>
<button type="button" onClick={onClose} aria-label={t(locale, "common.close")}>×</button>
</header>
<div className="profile-key-fields">
<small className="setting-description">
Click a button as fast as you can in the field below for a few seconds. A switch that bounces registers a
second press within {BOUNCE_GAP_MS} ms, which a finger cannot do, so those are counted and Bunny Hop is
suggested to cover them.
</small>
{bunnyHopMs > 0 ? (
<small className="setting-description" role="status">
Bunny Hop is on at {bunnyHopMs} ms, so the mouse is already hiding bounces. Turn it off to see them.
</small>
) : null}
<button
type="button"
className="bounce-check-area"
onMouseDown={(event) => {
event.preventDefault();
const time = event.timeStamp;
setPresses((previous) => ({ ...previous, [event.button]: [...(previous[event.button] ?? []), time] }));
}}
onContextMenu={(event) => event.preventDefault()}
>
Click here as fast as you can
</button>
<ul className="bounce-check-results">
{reports.map((entry) => (
<li key={entry.button}>
{entry.label}: {entry.report.clicks} clicks, {entry.report.bounces} bounced
{entry.report.longestGap !== null ? `, longest gap ${Math.round(entry.report.longestGap)} ms` : ""}
</li>
))}
</ul>
{reports.some((entry) => entry.report.clicks >= 10) ? (
<small className="setting-description" role="status">
{anyBounce
? `Bounce found. A Bunny Hop of ${suggested} ms (range ${BUNNY_HOP_LIMITS.minMs} to ${BUNNY_HOP_LIMITS.maxMs}) should filter it.`
: "No bounce found in these clicks."}
</small>
) : null}
<div className="import-hits-actions">
{anyBounce ? (
<button
type="button"
className="connect-button"
disabled={!canApply}
onClick={() => { control.applyBunnyHopMs(suggested); onClose(); }}
>
Set Bunny Hop to {suggested} ms
</button>
) : null}
<button type="button" className="connect-button" onClick={() => setPresses({})}>Clear</button>
<button type="button" className="connect-button" onClick={onClose}>{t(locale, "common.close")}</button>
</div>
</div>
</div>
</dialog>
);
}
Loading
Loading