Conversation
Lets a supported mouse switch to a higher polling rate the instant a game launches and back to an idle rate when it closes, even with the browser tab closed. The browser can't see what process is in the foreground, so the actual switch happens in a new companion app (OpenMouseCompanion) that pairs with this page over a local WebSocket. No vendor protocol logic is duplicated in the companion app: the site records the exact HID transaction its own driver already performs for setPollingRate() (src/game-mode/hid-recorder.ts) and hands that recipe over once, so the companion just replays real bytes it never had to understand. v1 covers vendors whose rate change is a self-contained HID transaction (Razer, Teevolution, Lamzu, WLMouse, ATK, VGN, Pulsar) — see src/game-mode/support.ts. Orbital, Endgame Gear, and Logitech read-modify-write a whole settings blob or need live HID++ negotiation, so they're excluded for now rather than risk reverting unrelated settings on every rate flip.
Drops the per-device Game Mode card in favor of a single home for the whole feature: connection status, install steps, and now idle/gaming rate pickers all live on the Background Service page reachable from the sidebar. Idle defaults to 1000 Hz, gaming to 4000 Hz. Both use the same rate-slider widget as the main Polling Rate card, extracted into src/ui/rate-slider.ts so it's shared code rather than a second implementation. Also drops the "NEW" sidebar badge.
Connecting to the companion needs the browser's loopback permission, and a blocked page previously looked identical to one where the service was simply not running. Query loopback-network (falling back to Chromium's local-network-access alias), keep the PermissionStatus so a change re-renders, and show at most one of the two onboarding graphics: the Allow instructions while the decision is pending, the site-settings recovery steps once it has been denied. Neither is shown once connected.
Finalmouse was excluded from Game Mode without cause. Its setPollingRate sends one output report whose payload is a pure function of the requested rate ([len, 0x80|17, 2, rate_lo, rate_hi]), which is exactly the property the replay design needs, so it now enrolls like the other nine vendors. Rewrite the doc comment around the single rule that actually decides eligibility, and record what each remaining vendor would need, so the list does not read as arbitrary: - Endgame Gear 8K and Orbital read the whole settings blob, patch one field and write it back, so a captured payload carries a snapshot of DPI, lift-off and button mapping and replaying it would revert them. They need a read-modify-write step that records changed offsets instead of the buffer. - Endgame Gear OP1we writes only the rate, but across two vendor collections, and RecipeStep cannot say which collection a step belongs to. - Logitech resolves its HID++ feature index at runtime, so a recorded index is only valid for the firmware it was captured on.
Game Mode is the only reason the Background Service exists, so leaving it off until the user finds and clicks a toggle made the default state the useless one. A supported mouse plus a connected service now enrolls itself, once per device key, and a device the user has explicitly switched off stays off. Because enrollment can now happen without anyone choosing rates, clamp both defaults to rates the mouse actually advertises. Extract that as a pure nearestRate() next to the slider it serves, with tests: ties break downward so a default never lands on the faster rate on its own, and an empty rate list returns null rather than enrolling undefined as a polling rate. Also distinguish the two cases the status line used to conflate. "Connect a supported mouse" was shown both when no mouse was connected and when a connected mouse simply is not supported yet, which reads as a bug when your mouse is plainly connected.
The CSS budget covers the Background Service page: its own sidebar route, the install and permission onboarding steps, and the Game Mode card with two rate sliders. The JS budget covers the companion WebSocket client, the HID transcript recorder and collection fingerprinting. Both budgets are set just above the current size rather than rounded up generously, so the next unnoticed increase still trips the check. npm audit fix clears one high advisory in a transitive dev dependency (nanoid). No runtime dependencies were added; the tree is still empty.
xBambooz
marked this pull request as draft
August 8, 2026 01:51
A denied connection proves the Background Service is installed and running, so the page showing "Download and install" and "Allow access to apps on your device" told the user to fix things that were not broken. It also had no way back: ws-client sets stopped = true on pairDenied and never reconnects, on purpose, so that a denial cannot re-trigger prompts on a timer. Nothing in the UI could undo that, leaving the page dead until a manual reload. Replace the onboarding in that state with what actually happened, naming the origin that was turned away, and add a Try again button that calls connect(). Rename the badge from BLOCKED to REFUSED. "Blocked" reads as something the user did, or as a browser permission problem, when it is the service declining an origin. The status line was also still telling the user to approve the service in a tray popup that no longer exists.
The service is a Windows executable, but its sidebar entry was unconditional. WebHID works on macOS and Linux and the rest of OpenMouse is fully usable there, so the only broken thing on the page was an installer those users cannot run. Hide the sidebar entry and skip opening the WebSocket entirely when the platform is not Windows. Detection prefers navigator.userAgentData, the one source of the three that is neither frozen nor deprecated, and deliberately trusts it over the userAgent string, which Chromium still freezes a Windows token into on some platforms. An unknown navigator is treated as not Windows, so the failure mode is a hidden page rather than a broken one.
xBambooz
marked this pull request as ready for review
August 8, 2026 03:16
Member
|
This is in the roadmap. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds Game Mode: a small Windows tray app applies your idle and gaming polling rates as games start and stop, with the browser closed.
The page captures the HID bytes your mouse's own driver already sends for
setPollingRate()and hands them over once, so no vendor protocol is reimplemented natively and adding a vendor stays a one-line change here.Companion app: https://github.com/xBambooz/OpenMouseCompanion (MIT). Happy to hand the repo over or repoint the download at a release you publish, so you own the binary users install.
Why other vendors are excluded is documented in
support.ts: Endgame Gear 8K and Orbital rewrite a whole settings blob, so replaying it would revert DPI and lift-off; OP1we spans two HID collections; Logitech resolves its feature index at runtime.Three things for you to decide: the bundle budgets are raised (CSS 45 to 58 kB, JS 310 to 340 kB) with reasons in the comment,
npm audit fixclears one high advisory in a dev dependency, and the installer is currently unsigned so Windows shows a SmartScreen warning.