How Claude extended an open-source configuration tool to support my mouse, helped me dump and migrate my button bindings, and made a battery-status widget.
My favorite mouse is the Logitech G502, as well as its successors. It fits my hand, the build quality is excellent, and more than anything it has a lot of buttons that I have painstakingly customized. Clicking the wheel closes a browser tab, so a thumb button takes over as the real middle-click. Two other thumb buttons switch to the next and previous tab, and the two under my index finger go back and forward: page history in a browser, or the cursor position history in my code editor. I also love the free-spinning scroll wheel: flip a switch and it spins with almost no friction, so a single flick flies through a long file.
There are a few things I don’t like, though.
The first is connectivity. No G502 has Bluetooth, not even the wireless Lightspeed models, so the USB-A dongle comes with me everywhere, along with a USB-C to USB-A adapter for my laptop. The wireless version is especially unforgiving about this. Plug in a stray USB-C cable and it will charge the mouse but often send no input at all, because plenty of cables carry power but no data. It does work wired over a proper data cable, but the practical upshot is the same: to actually use the mouse you need either the dongle or a specific cable. At least the newer G502 X charges over USB-C, instead of the previous generation’s archaic micro-USB.
But my real problem is the software.
The problem with G HUB
To customize the mouse you are supposed to use Logitech G HUB, and G HUB is not good. It is bloated and slow, it nudges you to create an account, and everything has to be set up by hand through the GUI. There is no clean way to export your configuration to a file and version it. It occasionally crashes. These are not just my personal grievances: the Consumer Rights Wiki documents the account pressure and bloat, and it is a perennial complaint online. And of course there is no official Linux support, which the community has had to work around with projects like Solaar and Piper.
For all these reasons, I stopped depending on G HUB and moved my setup onto the mouse itself.
Onboard memory + Karabiner
Logitech mice have onboard memory: a small flash chip that stores profiles (DPI steps, polling rate, and button assignments) directly on the device. Once a profile is written, it works on any computer, with no software and no account, because the mouse is doing the remapping in its own firmware.
My trick is to map the extra buttons to function keys (F13 through F24, which no application uses) and then translate those into real shortcuts with Karabiner-Elements, whose configuration is a plain text file I can edit and commit. So the mouse emits F22 and Karabiner turns it into ⌘W:
{
"from": { "key_code": "f22" },
"to": [{ "key_code": "w", "modifiers": ["left_command"] }],
"type": "basic"
}
The heavy customization lives in one JSON file, and the mouse itself only ever sends boring function keys. I like this a lot.
Migrating to a new mouse
The catch: writing those onboard profiles still meant using G HUB once. And when I bought the newer G502 X, I wanted to migrate my configuration over without having to painfully set each keybinding in G HUB.
That is how I found opengcontrol, an open-source command-line alternative to G HUB written in Rust. It talks to the mouse directly over HID++ 2.0, Logitech’s feature-based control protocol layered on top of plain USB HID, with no background daemon and no account. From the terminal you can read and change the DPI steps and polling rate, inspect and remap buttons, list and switch the onboard profiles, and read the battery. There is no GUI, but there is a genuinely nice TUI dashboard that shows every setting at once, and since all of it is scriptable, it can just as easily be driven by an agent.
There was one problem: it did not support my devices, and it was missing most of the features I needed. So I sat down with Claude Code and, with surprisingly little intervention, we added them. In a single pull request:
- device definitions for the G502 LIGHTSPEED and the G502 X LIGHTSPEED;
- reading and writing onboard profiles for the “sector-addressed” memory layout these mice use, including the CRC the firmware expects (get that wrong and the write is rejected);
profile clone, which copies a profile from one mouse onto another and automatically remaps the buttons, because the two G502 models number their physical buttons differently (a naive byte copy scrambled which physical button did what: tilting the wheel suddenly fired keystrokes);- battery readout over HID++, with a voltage-curve estimate for the older mouse, which predates the modern battery feature;
- a fix for setting the polling rate on mice in onboard mode, where the rate is stored in the profile rather than set live.
Claude also chased down a genuinely annoying side quest: the G502 X Lightspeed receiver continuously spams do_not_disturb HID events, which flood Karabiner’s EventViewer and make it useless for debugging. It turns out to be a known firmware quirk of the receiver, not something I was doing wrong.
My configuration already lived on the mouse; that part was never the problem. What is new is that it is now legible. I can dump a profile to a text file, read it, edit it, and write it back, all from the terminal. That is also how the one-command migration to the new mouse works, and, more importantly, it is what makes the configuration something an agent can actually work on.
A bonus: battery in the menubar
Once the battery was readable from the command line:
$ opengcontrol battery
Battery 79%
Status discharging
I realized I could just ask Claude for a menubar indicator too. A few minutes later I had a SwiftBar plugin: a tiny script that shells out to opengcontrol battery and renders a mouse-shaped gauge that fills from the bottom and shifts colour from green to red as the battery drains. It resolves the mouse automatically (no hardcoded model) and only shows a reading when exactly one mouse is connected.
If you want it, I put it up as logitech-battery-bar, with the plugin, the icon generator, and install instructions.
How an agent supports a device it has never seen
The part I keep thinking about is how little I had to know. I never opened the HID++ specification, I did not reverse-engineer the flash format, and I wrote none of the protocol code. I described what I wanted and occasionally acted as the agent’s eyes and hands (“press the button behind the wheel”, “the LED is orange now”) while it did the actual engineering:
- It enumerated the mouse’s HID++ features and dumped the raw memory sectors off the device, then compared them until the layout fell out: where the DPI lives, where each button’s four bytes sit, where the UTF-16 profile name is stored.
- Onboard profiles are guarded by a checksum, and a write with the wrong one is silently rejected. The agent recovered the exact checksum variant (a CRC-16-CCITT) by computing candidates against the device’s own stored value until it reproduced it byte-for-byte.
- It wrote to flash defensively: back up the sector, write, then read it back and verify, with the factory ROM profiles as a fallback. That made poking at real hardware essentially risk-free, which is precisely what let it iterate at all.
- For the cross-model migration it did something genuinely clever. Instead of hardcoding which slot is which button, it read both mice’s factory default profiles and diffed them: in a default, every slot holds its button’s natural function, so the defaults themselves reveal how the two models number their buttons.
- Throughout, it cross-referenced open-source prior art like libratbag and Solaar for protocol details. I prompted it to find references and other implementations but it was able to find those repositories and explore their codebase without any help from me.
None of this is beyond a determined human with a free weekend. The point is that it now takes less than an hour and almost no expertise. A long-tail device that no vendor and few volunteers would prioritize becomes something you can simply ask for, and because it landed in an open-source project, the next person with a G502 gets it for free.
Editing my whole setup, end to end
There is a second, quieter win. My configuration has two halves, what the mouse sends and what the OS does with it, and both are now machine-readable and machine-writable:
- the mouse, through
opengcontrol’s scriptable CLI (profile list --output json,profile set-button,profile clone,battery, …); - the keyboard remaps, through Karabiner’s plain-text
karabiner.json.
That means an agent can edit the entire pipeline coherently, something a GUI like G HUB structurally cannot allow. When I noticed one of my buttons was doing the wrong thing, the fix spanned both halves at once: the agent read karabiner.json, saw that F23 maps to ⌃⇧Tab (previous tab), and set the physical button to emit F23, reasoning across the mouse and the OS layer in a single step and then reading the profile back to confirm it stuck. I can now say “make the button behind the wheel report the battery” or “swap my next-tab and previous-tab buttons”, and it lands correctly on both sides, verifiably, and reverts cleanly if I don’t like it.
My mouse used to be configured by a bloated GUI I resented opening. Now the whole thing, firmware profile and OS remaps alike, is plain text and a CLI I can hand to an agent. That, more than any single feature, is the upgrade.