Skip to content

Repository files navigation

castty

castty

Configure the Mionix Castor on Linux.

Lighting, DPI, polling rate, button mapping, macros and the surface analyzer — all of it, natively, with no Wine and no Windows.

status licence protocol platform


Why this exists

I bought a Mionix Castor about ten years ago. It is still the best mouse I have ever used, and it has been my daily driver that entire time.

When I moved from Windows to Linux, I lost control of it. Mionix never shipped Linux software, the company is gone, the mouse has been out of production for years, and no popular tool supports it — not OpenRGB, not libratbag, not anything I could find. The mouse kept working, but whatever settings it had were frozen in whatever state Windows last left them.

So I took the protocol apart. I ran the original Mionix Castor software under Wine, recorded every function as I used it, read the traffic, and mapped it byte by byte until the whole format was understood. AI was a real help in the grind of it — diffing captures, spotting patterns, writing the Rust — but the mouse on the desk and the bytes on the wire are what decided every question.

Then I built this app on top of it.

All of it is here. The protocol, the raw captures, the capture rig, the decoder — in research/, documented, and free for anyone to use however they like.

And I'm not stopping at this app: I'll be working on getting Castor support into other open source tools — OpenRGB and libratbag in particular — soon. A mouse that only one program can configure is still half-orphaned. The point of documenting the protocol this thoroughly was to make that possible for anyone, and I intend to be one of the people who does it.


What works

Everything the original Windows software could do, verified against real hardware:

🎨 Lighting Per-LED colour — scroll wheel and logo set independently
✨ Effects Solid, blinking, pulsating, breathing — plus rainbow, combinable with any of them
🎯 DPI Three steps, independent X and Y
⚡ Polling rate 125 / 250 / 500 / 1000 Hz
🖱️ Sensor Angle snapping, angle tuning, lift-off distance
🔘 Buttons Full remapping, including profile switching and single-key assignment
📼 Macros Record, edit, per-event timing, and hold mode
👤 Profiles All five, with editable names
📊 Surface analyzer Mionix S.Q.A.T., same scoring as the vendor tool

The GUI and a scriptable CLI are the same binary.


Screens

Lighting page — colour picker, effect and live mouse preview

Lighting — the preview lights the wheel and logo as you pick, before anything is written

Buttons page
Buttons — numbered to match the callouts on the mouse
Sensor page
Sensor — DPI steps, polling rate, angle tuning, lift-off
Macros page
Macros — record, retime, or hold
Profiles page
Profiles — all five, with editable names
About page
About — five themes and nine accents, none of them borrowed from your desktop

The interface is drawn with iced and uses none of the desktop's theming, so it looks and behaves the same on every distribution and on both Wayland and X11. It ships its own palettes and accents, switchable in About.

Those images are generated, not staged: cargo test --lib layout_tests builds every page's real widget tree in a headless renderer and writes it to target/ui-snapshots/ — no display, and no mouse plugged in.


Requirements

  • A Mionix Castor, USB ID 22d4:1316
  • Linux with hidraw (any modern kernel), x86-64
  • A Wayland or X11 session — GPU rendering via Vulkan or OpenGL where a driver exists, software rendering where it doesn't
  • Rust 1.88 or newer, only if building from source

The Castor PRO (22d4:1320/1321) is a different device and is not supported. See Contributing if you have one.


Install

Every route installs the same four things: the binary, a udev rule that makes the mouse reachable without root, a desktop entry and the icons. The udev rule is the one that is not optional — without it /dev/hidraw* stays root:root 0600 and castty finds no device.

Arch Linux (and Manjaro, EndeavourOS, CachyOS)
git clone https://github.com/fawaz7/castty
cd castty/packaging/arch
makepkg -si
Debian and Ubuntu (and Mint, Pop!_OS)

Grab castty_1.1.1-1_amd64.deb from the latest release:

sudo apt install ./castty_1.1.1-1_amd64.deb

apt install ./file.deb pulls in the dependencies; dpkg -i does not. Requires Debian 12+ or Ubuntu 22.04+.

Any distribution — prebuilt binary, no compiler needed
tar xzf castty-1.1.1-x86_64-linux.tar.gz
cd castty-1.1.1-x86_64-linux
./install.sh --no-build

From the latest release. The binary links only against libc, so it runs on any glibc 2.35 or newer.

Any distribution — from source

Needs Rust 1.88+ (rustup, or your package manager):

git clone https://github.com/fawaz7/castty
cd castty
./install.sh

The script builds, installs to /usr/local, sets up the udev rule and refreshes the desktop caches. It asks for sudo only for the steps that genuinely need root.

./install.sh --prefix ~/.local installs for your user alone — root is then needed only for the udev rule. ./install.sh --help lists the rest.

Then

castty info

Expect your firmware and current profile. If it says no device was found, replug the mouse once — udev rules apply when a device connects. Then launch castty, or find it in your application menu.

Uninstall

sudo pacman -R castty          # Arch
sudo apt remove castty         # Debian/Ubuntu
./install.sh --uninstall       # install.sh

Your settings in ~/.config/castty/ are always left alone. Your actual configuration lives on the mouse; that directory is a fallback copy for when the mouse isn't there to be asked. Delete it by hand if you want no trace.


Command line

Launching with no arguments opens the GUI. With arguments it's a CLI, which is handy for scripting and for startup services:

castty info                      # device identity and the profile on the mouse
castty led ff6600                # set both LEDs
castty mode breathing rainbow    # effect, optionally with rainbow
castty dpi 1 1600                # set a DPI step
castty surface                   # run the surface analyzer
castty reset                     # restore factory defaults
castty -p 2 led 00ff00           # act on profile 2
castty help                      # full usage

Two things worth knowing

castty shows what's on the mouse

The Castor can be read back. The Windows software never does it, and this project believed for a while that no such command existed — but the firmware has one, and every profile comes out of flash byte-exactly. castty reads all five when it connects, so what you see is what the mouse is actually holding, even if it was configured from another machine or from the Windows software.

One wrinkle worth knowing about: for the first few seconds after the mouse is plugged in, it answers a read with an empty profile and says the read succeeded. castty checks the shape of every reply and refuses anything that isn't plainly a real profile, rather than risk writing an empty one back. If a read is refused, or if no mouse is attached, castty falls back to the copy in ~/.config/castty/ and shows what was last written instead.

LED colours are approximate

The mouse's blue emitter is much dimmer than its red, so a colour with even a little red in it looks markedly warmer on the LED than in the picker, and full blue reads as a dark navy.

This is ordinary RGB LED behaviour, and the vendor software behaves identically. castty writes the value you picked, unchanged — a correction curve would mean sending different bytes than the colour you chose, and would make these frames diverge from the vendor's for no real gain.

Settings are written on an explicit Apply, never live as you drag a colour: writes go to the mouse's flash, and a write per drag event would be wear for nothing.


The protocol

research/ is the reverse-engineering half of this project, kept deliberately separate so it can be used without the app:

  • research/PROTOCOL.md — the full wire format. Every report, every offset, every decoded field with its confidence level, and the things that were tested and ruled out so nobody repeats them.
  • research/captures/ — the actual frames the vendor software sent. Every claim in the protocol document is checkable against the bytes it came from, and the test suite asserts against them directly.
  • research/tools/ — the capture rig: the LD_PRELOAD ioctl shim, the Wine launcher, the capture decoder, and an 88-line standalone Python LED writer.

research/README.md explains the method end to end and how to reproduce a capture yourself.

Coming next: I'm planning to upstream this into OpenRGB and libratbag so the Castor works with the tools people already use, rather than only with a program written for it. If you want to help, or you have already started, open an issue — better to do it once, together, than three times apart.


Contributing

I only own a Castor. That's the honest limit of this project — I can't test what I can't hold.

If you have another Mionix mouse — a Naos, an Avior, a Castor PRO — and you want it supported, the capture rig in research/ is exactly what you need and it is documented for that purpose. The protocol is likely a close relative across the range; the Castor PRO in particular shares a software lineage. Adding a device should mostly be a matter of new captures and a byte map.

Also welcome:

  • Packaging — Flatpak and Nix (Arch and Debian are covered; see packaging/)
  • Testing on other distributions and compositors
  • The few remaining unknowns in research/PROTOCOL.md
  • Porting the protocol into OpenRGB, libratbag or a kernel driver — genuinely, please

Working on it

cargo test                       # 103 tests, no hardware or display needed
cargo clippy --all-targets       # lint
cargo test --lib layout_tests    # renders every page to target/ui-snapshots/
cargo run                        # launch the GUI
cargo run -- info                # CLI, needs the mouse

The test suite runs entirely against the captured frames, so you can work on the protocol layer without a mouse plugged in. Layout tests render the real widget tree headlessly, so a page that renders nothing fails a test rather than waiting for someone to notice.


Changelog

1.1.1

The portable tarball's documented install actually works now.

install.sh --no-build looked for the binary at target/release/castty, which is where a source checkout builds it — but the release tarball ships it at the top level, with no source to build from. So the install route the README recommends for anyone without a compiler failed on the first command, and had been failing since 1.0.0. The script now finds the binary in either layout, and when there genuinely is no binary it says something useful about which of the two situations you are in rather than suggesting you build source that is not there.

install.sh --uninstall run under sudo also pointed at /root/.config/castty when telling you where your settings were left, because $HOME under sudo is root's. It names your own directory now.

No change to the application itself.

1.1.0

castty now shows what is on the mouse.

Until this release it showed what it had last written. The project believed the device could not be queried, and said so in the protocol document: across 1260 GET_FEATURE calls the vendor software never reads a profile back, and that absence was taken as proof no such command existed. It was not. The firmware implements one anyway — 0x60/0x07, with the profile index in byte [5] and the reply read on report 0x61 — and all five profiles come out of flash byte-exact. No capture could ever have shown this; only probing the command space directly found it.

So the device is the source of truth now. The GUI reads all five profiles whenever it connects, including a mouse plugged in mid-session, and the CLI reads the profile it is about to act on. The config file remains as the fallback for when no mouse is attached or a read is refused, which is also what keeps the test suite running with no hardware.

The hazard this is built around is worth stating plainly, because it is the kind that destroys data quietly: for a few seconds after the mouse enumerates a read returns 1041 zero bytes and reports success. The ioctl is fine; only the contents are wrong. Patching a field into that buffer and writing it back would erase the profile's DPI, button mapping and the unmapped 880-byte macro region, on hardware that is out of production. Nothing becomes state that the validator has not passed, and decoding a device reply runs that validator itself, so there is no path to a profile from a read with the check skipped.

Two corrections to research/PROTOCOL.md came out of the same work. The claim that no read path existed is retracted and replaced with the command's full documentation. And the claim that the vendor software keeps the four inert colour records in step with the wheel colour is withdrawn: the captures in this repository show it does not — between profile1-red.bin and profile1-green.bin the only bytes that differ are the wheel and the logo. castty mirrors them and the vendor does not, so our frames were never byte-identical to theirs as the document claimed.

The five profiles read off the device are included as capture fixtures, so the new behaviour is checkable against real bytes like everything else here.

1.0.0

First public release. The protocol reverse-engineered from scratch and published in research/, and a GUI and CLI built on top of it: per-LED colour, effects and rainbow, DPI, polling rate, angle snapping and tuning, lift-off distance, button mapping, macros, five profiles, and the surface analyzer.


Licence

GPL-3.0. This is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. See LICENSE.

Two notes, both in NOTICE.md:

  • The mouse render and the Mionix logo shown inside the app are not covered by the GPLv3 grant; all rights in those images remain with Mionix.
  • The licence lets you fork, modify and redistribute freely, and nothing narrows that. But if you build something derivative from this — especially a derivative app, or significant reuse of the GUI or hardware layer — please keep a visible credit to Fawaz Alghzawi and a link to this repo in your README, About screen or credits. It's a small ask that goes a long way for an independent, unpaid side project.

Not affiliated with or endorsed by Mionix. The vendor's software is not redistributed here.


Built by Fawaz Alghzawi · for a ten-year-old mouse that deserved better

About

Configure the Mionix Castor mouse on Linux — lighting, DPI, buttons, macros. Protocol reverse engineered from scratch and fully documented.

Topics

Resources

Contributing

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages