makcu
A Rust library for controlling MAKCU USB HID interceptor devices.
MAKCU devices are small USB dongles that sit between a mouse and a host PC, allowing programmatic mouse control — cursor movement, button presses, scroll, input locking, and more — via serial commands over USB.
Quick start
use ;
Features
The base crate exposes only firmware-native commands. Optional features add higher-level functionality:
| Feature | Description |
|---|---|
async |
AsyncDevice with full async parity (requires tokio) |
batch |
BatchBuilder — fluent command sequencing with coalesced writes |
extras |
Software-implemented click, smooth movement, drag, and event callbacks |
profile |
Per-command timing profiler (zero overhead when disabled) |
mock |
In-process mock transport for testing without hardware |
[]
= { = "0.1", = ["batch", "extras"] }
API overview
Connection
// Auto-detect by USB VID/PID
let device = connect?;
// Specific port
let device = connect_port?;
// Custom config
let device = with_config?;
Mouse control
device.move_xy?; // relative move
device.silent_move?; // left-down → move → left-up (two HID frames)
device.wheel?; // scroll up
device.button_down?; // press
device.button_up?; // release
device.button_up_force?; // force-release (unstick)
device.button_state?; // query: true/false
Input locks
device.set_lock?; // lock X axis
device.lock_state?; // query lock state
device.lock_states_all?; // all 7 locks at once
Device info
device.version?; // firmware version string
device.serial?; // current serial number
device.set_serial?; // spoof serial
device.reset_serial?; // restore factory serial
Fire-and-forget
By default, every command waits for the device's >>> response prompt (~1ms round trip). For maximum throughput, use fire-and-forget:
let ff = device.ff;
ff.move_xy?; // returns immediately after serial write
ff.wheel?;
Button stream
device.enable_button_stream?;
let rx = device.button_events;
// rx.try_recv() returns ButtonMask with per-button accessors
if let Ok = rx.try_recv
device.disable_button_stream?;
Batch (feature = batch)
Coalesces multiple commands into a single write_all() call:
device.batch
.move_xy
.move_xy
.button_down
.button_up
.wheel
.execute?;
The firmware processes commands sequentially from its serial buffer — batching doesn't skip or merge commands. It just eliminates the inter-command gap on the host side by delivering all the bytes in one write, so the firmware always has the next command ready to read immediately.
Extras (feature = extras)
Software-implemented operations with timing control:
use Duration;
device.click?;
device.click_sequence?;
device.move_smooth?;
device.drag?;
device.move_pattern?;
Event callbacks:
let _handle = device.on_button_press;
// Callback unregisters when handle is dropped
Async (feature = async)
Full async parity — every sync method has an async equivalent:
let device = connect.await?;
device.move_xy.await?;
device.click.await?;
device.batch.move_xy.wheel.execute.await?;
Profiler (feature = profile)
Zero-cost when disabled. Records timing for every command:
use profiler;
device.move_xy?;
device.move_xy?;
for in stats
reset;
Mock (feature = mock)
Test without hardware:
let = mock;
// Register responses for query commands
mock.on_command;
let version = device.version?;
assert_eq!;
// Inspect what was sent
assert!;
Raw commands
Escape hatch for firmware commands the library doesn't wrap:
let response = device.send_raw?;
Examples
# Basic usage with real hardware
# All features demonstrated
# Mock transport (no hardware)
# Performance benchmark
Architecture
The library uses a multi-threaded transport layer:
- Writer thread coalesces pending commands into single
write_all()calls - Reader thread runs a
StreamParserstate machine that routes responses and fans out button events - Monitor thread handles automatic reconnection with exponential backoff
All Device methods take &self — I/O goes through channels. Device is Send + Sync and can be shared via Arc.
Communication is at 4 Mbaud over USB-serial (CH340/CH343 chip). The library auto-detects devices by USB VID/PID and handles the baud rate upgrade sequence automatically.
Performance
All numbers are averages of 3 runs on the same device (Linux, CH340 USB-serial).
| Metric | makcu | makcu-cpp | makcu-rs |
|---|---|---|---|
| Baud rate | 4 Mbaud | 4 Mbaud | 115,200 |
| What is measured | Real serial I/O | Serial write+flush | Channel enqueue* |
| Confirmed round-trip (move) | 999 us | N/A | N/A |
| 100 rapid F&F moves | 1333 us total | 4647 us total | 27 us total* |
| Batch 10 cmds | 16 us total | 470 us total | 3 us total* |
| Batch 50 moves | 12 us total | 2635 us total | 6 us total* |
*makcu-rs measures channel enqueue time, not serial I/O — the timer stops before bytes reach the serial port, producing sub-microsecond figures that don't reflect actual device latency.
Run cargo run --example benchmark --release --features "batch,extras" to reproduce.
License
MIT