I've been running Linux as my daily driver for about three years now, and the one thing that's never quite worked right is my Logitech MX Master 3S. On macOS or Windows you get Options+: an account-gated, Electron-flavored app that talks to the mouse and lets you remap the thumb buttons, tune DPI, and set up per-application profiles. On Linux, Logitech doesn't ship anything at all. You either live with default button behavior or you go hunting for a third-party tool that speaks the mouse's actual protocol.
For years the answer was Solaar, and it's a genuinely solid piece of software. But I wanted something closer to what Options+ actually does on the desktop I use for work: per-app profile switching, a proper GUI for building bindings, and a config file I could version-control instead of clicking through a settings panel every time I reinstalled. That's how I ended up trying OpenLogi, a Rust rewrite of the "local-first Options+ replacement" idea that's been picking up momentum this year. This post is about what it actually took to get it running as a real background service on Linux, not just clicking through the installer.
None of this is Logitech-sanctioned, and OpenLogi says so plainly on its own site: it's an independent project, not affiliated with Logitech, and the trademarks belong to Logitech International S.A. That matters because the whole reason this class of tool exists is that Logitech decided a mouse profile needs an account and a cloud sync layer, and a chunk of the Linux and privacy-conscious desktop crowd disagreed.
Why "just use Solaar" wasn't the whole answer
Solaar has been the default Linux answer to Logitech peripherals for a long time, and it's the reason any of this is even possible — it's the most complete open-source implementation of Logitech's HID++ protocol, and OpenLogi's own project credits it directly as their protocol reference. If you just need to see battery level and toggle a couple of buttons, Solaar is fine and you should probably start there before reaching for anything newer.
What pushed me to look elsewhere was the workflow, not the protocol coverage. I wanted the thumb buttons on my MX Master to mean one thing in my terminal and another thing in my browser, switching automatically the moment focus changed, and I wanted that config sitting in a file I could drop into my dotfiles repo instead of re-clicking through a settings panel on every fresh install. That's a narrower, more opinionated feature set than a general-purpose HID++ manager tries to cover, and it's exactly the angle OpenLogi is built around: a GUI-first configurator with per-app overlays, backed by a plain-text config, written from scratch in Rust rather than Python.
"Does a single mouse setting really need an account, telemetry, and a heavy resident process?" — BrainDetox, on the motivation behind OpenLogi
That's a fair summary of the itch. Options+ wants a login, phones home, and runs a persistent background agent for the privilege of remembering that your middle click opens Mission Control. Whether you care about that on principle or just don't want another daemon eating memory, it's a legitimate complaint, and it's the one OpenLogi is explicitly built to answer.
What's actually happening when you remap a button
HID++ is Logitech's own extension layered on top of the standard USB HID protocol, and it's how basically every modern Logitech mouse, keyboard, and receiver talks to a host machine underneath whatever official software you're running. It's not a public standard Logitech maintains documentation for — everything the open-source community knows about it comes from years of the Solaar and libratbag projects reverse-engineering it by watching USB traffic byte by byte.
The protocol is organized around numbered "features," and each capability the device exposes gets its own feature code. DPI control lives behind feature 0x2201, and SmartShift — the setting that flips your scroll wheel between a ratcheted click-per-notch mode and a free-spinning mode — lives behind feature 0x2111. When OpenLogi's configurator changes your DPI or flips SmartShift, it isn't storing that as a local-only preference and faking the behavior in software; it's writing an HID++ command straight to the sensor, over whatever transport your device happens to be connected through.
Button remaps work differently from DPI and SmartShift, and this is where I initially expected the wrong mechanism. I assumed the mouse itself would be reprogrammed to emit different scancodes, the way you'd flash a mechanical keyboard's firmware. It's closer to the opposite: OpenLogi intercepts the button event on the host and injects the mapped action through the OS's own input layer, which is why remaps can be app-aware in a way that firmware-level remapping never could be — the device itself doesn't need to know or care which application currently has focus.
The config file is the actual product
The part of OpenLogi I got the most attached to fastest wasn't the GUI, it was the fact that every binding you set ends up as a line in a plain TOML file under your config directory. The GUI is a nice way to discover what's possible the first time, but once you know what you want, editing the file directly is faster, and it's the kind of thing that survives a fresh install if you just copy it back into place.
Here's roughly what a working config looks like for an MX Master 4 with a handful of bindings set:
schema_version = 2
selected_device = "2b042"
[devices.2b042.bindings]
MiddleClick = "MissionControl"
DpiToggle = "CycleDpiPresets"
Thumbwheel = "VolumeUp"
Forward = "BrowserForward"
Back = "BrowserBack"
GestureButton = "AppExpose"
The device is keyed by an id rather than a friendly name, the bindings map physical button names to one of the built-in actions (there are 44 of them shipped, everything from window management to launching an app to firing a custom keyboard shortcut), and the schema is versioned so future releases can migrate your file instead of just breaking it outright. Per-app profiles layer on top of this as overlays — you define a base binding set and then override specific entries when a given application has focus, and the switch happens automatically the moment your window manager reports a focus change.
I'll be honest that I'd have liked a slightly richer templating story here — right now every per-app override is its own explicit block, so if you use the same four-button layout across a dozen apps with one tweak each, you're repeating yourself a lot. It's a minor gripe against a tool that's still pre-1.0, but if you're the type who likes DRY config files, expect some duplication until the schema grows a way to express shared defaults.
The gotcha that cost me twenty minutes: only one process owns the receiver
The first time I installed OpenLogi, button remaps just didn't take. No error, no crash — I'd set a binding in the GUI, click apply, and the mouse would keep doing whatever it was already doing before I touched anything. I spent a good chunk of time assuming I'd misconfigured the TOML file, double-checking action names against the docs and restarting the GUI more than once.
The actual problem was something dumber than a config typo: Solaar was still running in the background from before I'd even heard of OpenLogi, quietly holding onto the same receiver.
This isn't a bug so much as a fact about how the underlying hardware channel works, and it's the same limitation you'd hit switching between any two HID++ tools, not something specific to OpenLogi's implementation. It's just not obvious the first time, because nothing tells you the write failed — the command goes out, the receiver ignores it because another process already has the pipe open, and OpenLogi has no way to distinguish "ignored" from "applied" without asking the device directly.
Making it survive a reboot on Linux, without running as root
Getting the GUI working was the easy part. What I actually wanted was for my per-app profile switching to be alive the moment I log in, without needing to manually launch anything or run the whole thing as root just to get raw HID access. This is the part of the Linux port that's genuinely backend-adjacent work rather than desktop-app polish: udev rules to grant an unprivileged user permission to talk to the HID++ device node, and a systemd user unit to keep the background process alive across logins.
The packaged .deb and .rpm builds install both of these for you automatically, so in day-to-day use you don't hand-write either file yourself. But it's worth understanding the shape of what they're doing, because it's the same pattern you'd reach for writing any Linux service that needs unprivileged access to a USB HID device. A udev rule granting group access to the right device class looks something like this:
# /etc/udev/rules.d/99-openlogi.rules
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="046d", MODE="0660", GROUP="plugdev", TAG+="uaccess"
SUBSYSTEM=="usb", ATTRS{idVendor}=="046d", MODE="0660", GROUP="plugdev"
That vendor id, 046d, is Logitech's USB vendor code, and the rule's whole job is narrow: hand off ownership of matching device nodes to a regular user session instead of leaving them root-only, which is what stock udev defaults would otherwise do for a raw HID device. Without something like this, you'd need to run the daemon as root just to open the device file, which defeats the point of a lightweight background service.
The systemd side is a user-scoped unit rather than a system one, which matters because it ties the daemon's lifecycle to your login session instead of the whole machine's boot sequence:
# ~/.config/systemd/user/openlogi.service
[Unit]
Description=OpenLogi HID++ background daemon
After=graphical-session.target
[Service]
ExecStart=/usr/bin/openlogi --daemon
Restart=on-failure
[Install]
WantedBy=default.target
Running it as a user unit rather than a system-wide one is the right call here, and it's the same decision I'd make writing this from scratch: a HID++ daemon has no business running with root privileges just to remap a mouse button, and tying its lifecycle to the graphical session means it starts and stops cleanly with your desktop instead of lingering as an orphaned process after you log out.
Why the CLI ended up mattering more than the GUI
OpenLogi actually ships as two binaries, a GUI and a separate CLI, and once my bindings were set the way I wanted, the CLI became the tool I reached for far more often. The GUI is where you discover and build a config; the CLI is where you check that things are actually working without opening a window at all — which matters if, like me, you sometimes SSH into your own desktop from a laptop and want to sanity-check the mouse setup remotely.
The CLI's headless functionality covers a small, focused set of jobs rather than trying to mirror everything the GUI can do:
- Device inventory — listing every paired device, its transport, and connection state, useful for confirming a receiver actually enumerated after a reboot.
- Asset syncing — pulling down the device diagrams the GUI uses, separate from the config itself, so you can pre-fetch them once instead of on every launch.
- On-device diagnostics — a way to check the health of the HID++ connection without going through the GUI, which is exactly what I want when I'm debugging a "why didn't my binding apply" problem over SSH.
That last one is what saved me the second time I hit the receiver-ownership problem — instead of guessing, I could run a diagnostic check and get a straight answer about whether OpenLogi could actually see and write to the device, before I went hunting for a stray Solaar process again in a terminal I'd already forgotten I had open.
Where I've landed with it
I've been running OpenLogi as my daily driver for a few weeks now, and it's replaced Solaar entirely on my main desktop, even though Solaar is still the more mature and battle-tested option for anyone who just needs basic control and doesn't care about per-app profiles. That's the actual tradeoff here: you're picking a newer, less-proven Rust project with a narrower but more opinionated feature set over an older, broader Python one, and the project itself says plainly that it's still under active development and not yet stable.
If you're on Linux and just want battery percentage and a couple of button remaps, I'd genuinely start with Solaar first — it's had more years of real-world testing against the weirder edge cases in HID++ device enumeration across older receivers and devices. But if what you actually want is Options+-style per-app profile switching without an account, a config file you can commit to a dotfiles repo, and you don't mind a project that might still change its config schema out from under you between releases, OpenLogi is the closest thing I've found to what Logitech should have shipped for Linux in the first place. Check the receiver-ownership gotcha before you file a bug report — it's saved me from writing at least one confused GitHub issue already.

Comments
No comments yet — be the first to share your thoughts.