Enums
Command and status enumerationsCommand and status enums, each tied to a wire byte. Conversion helpers are listed with each.
DeviceKind
The cloned device's primary kindenum DeviceKind { Unknown, Keyboard, Mouse }The kind field of a DeviceInfo, read from the cloned device's USB Boot-interface bInterfaceProtocol. It also drives find_mouse_box and find_keyboard_box. Display prints the lowercase name.
| Variant | Byte | Meaning |
|---|---|---|
Unknown | 0 | Neither a Boot keyboard nor a Boot mouse. |
Keyboard | 1 | The device is a keyboard. |
Mouse | 2 | The device is a mouse. |
Action
The shared press / release tri-stateenum Action { SoftRelease, Press, ForceRelease }The shared override action for an inject call, on any Usage class (button, key, or media). The discriminant is the wire byte. Convert with as_u8() and from_u8(u8) -> Option<Action>.
| Variant | Byte | Meaning |
|---|---|---|
SoftRelease | 0 | Drop the box's override, press or force; a physical hold stays down. |
Press | 1 | Force the input down. |
ForceRelease | 2 | Force the input up, masking a physical hold. |
The two releases differ only when the user physically holds the same input:
| Variant | User holds nothing | User is holding it |
|---|---|---|
Press | down | down |
SoftRelease | up | down (the physical bit stands) |
ForceRelease | up | up (masks physical) |
Class
The class of a momentary usageenum Class { Button, Key, Media }The class byte of a Usage, shared by INJECT, LOCK, and CATCH. Convert with as_u8() and from_u8(u8) -> Option<Class>.
| Variant | Byte | Meaning |
|---|---|---|
Button | 0 | A mouse button; id is a Button id (0=Left .. 4=Side2). |
Key | 1 | A keyboard key; id is a HID keycode (0xE0 .. 0xE7 is a modifier). |
Media | 2 | A media usage; id is a 16-bit Consumer usage. |
CatchClass
What a catch subscription addressesenum CatchClass { Button, Key, Media, Axis, HidIn, HidOut, VendorInterrupt, VendorBulk, Control, Emit, Bus }The address space a CatchFilter picks from, and the class of a TrafficEvent. Convert with as_u8() and from_u8(u8) -> Option<CatchClass>; split it with is_input() and is_traffic().
The first four are LOCK's own classes at the same byte values. The other seven address USB traffic and have no lock counterpart; TrafficClass is that half on its own.
| Variant | Byte | id is | Blanket covers |
|---|---|---|---|
Button | 0 | a Button id (0 = Left .. 4 = Side2). | every mouse button. |
Key | 1 | a HID keycode (0xE0 .. 0xE7 is a modifier). | every key and modifier. |
Media | 2 | a 16-bit Consumer usage. | every media usage. |
Axis | 3 | an Axis: X, Y, or the wheel. | every axis. |
HidIn | 4 | an interface number on the real device. | every HID interface. |
HidOut | 5 | an endpoint address. | every interrupt-OUT endpoint. |
VendorInterrupt | 6 | an endpoint address. | every vendor interrupt endpoint. |
VendorBulk | 7 | an endpoint address. | every vendor bulk endpoint. |
Control | 8 | an endpoint number (0 = EP0). | every control endpoint. |
Emit | 9 | an endpoint address on the clone. | every emitting endpoint. |
Bus | 10 | unused; a bus event has no id. | every bus event. |
A blanket is one table entry, not an expansion into one per id. The wire sentinels never appear in Rust: CatchFilter::watch_class(c) and traffic_class(c) are the per-class blankets, and CatchFilter::everything() is the wildcard over all eleven, not a CatchClass variant.
The input classes are captured at the emission merge point, before lock suppression and injection, so a locked input still reports. Emit is the far end of the same path: what the clone actually put on the wire.
real device --> [ merge point ] --> locks --> injection --> [ clone emits ] --> game PC
| |
Button / Key / Media / Axis EmitThe traffic classes tap the pipes themselves, each on whichever chip owns that pipe. That split is what each event's ClockDomain records.
use medius::{Button, Capture, CatchFilter, Direction, TrafficClass};
// The same target, once as a lock and once as a catch.
device.lock(Button::Side1, Direction::Both)?; // suppressed in the emitted report
let stream = device.catch_events([
CatchFilter::watch(Button::Side1).on_press(), // the tap is before suppression
])?;
// A byte-oriented class instead: one vendor interrupt endpoint, IN only, 16 bytes a packet.
let trace = device.catch_events([
CatchFilter::traffic(TrafficClass::VendorInterrupt, 0x83)
.inbound()
.with_capture(Capture::First(16)),
])?;TrafficClass
The byte-oriented half of the address spaceenum TrafficClass { HidIn, HidOut, VendorInterrupt, VendorBulk, Control, Emit, Bus }The seven classes that carry packets, at the same byte values as their CatchClass counterparts. A separate enum so CatchFilter::traffic cannot be handed an input class. TrafficClass::ALL lists them; From and TryFrom convert both ways.
Capture
How much of each packet to keepenum Capture { Whole, First(u8) }Traffic classes only. An input class carries no packet, so naming one together with a capture is refused rather than ignored.
A ceiling request, not a guarantee: the box holds one entry per address and cuts once, so another subscriber naming the same address more widely raises yours too. First(0) is Whole; bytes() returns Option<u8> and widest() resolves two.
Input
One decoded input edgeenum Input { Press(Usage), Release(Usage), Motion { dx: i16, dy: i16, dz: i16 } }What input_events yields, wrapped in an InputEvent with its timestamp. usage(), is_press(), is_release(), direction() and axes() read it without a match.
Usage
A momentary input: (class, id)struct Usage { class: Class, id: u16 }What inject drives and LockTarget locks. A Button, Key, and MediaKey each impl Into<Usage>, so you pass one straight to any verb; build one by hand with Usage::new(class, id).
| Field | Type | Meaning |
|---|---|---|
class | Class | The input class (button, key, or media). |
id | u16 | The class-specific id: a button id, a HID keycode, or a Consumer usage. |
use medius::{Button, Class, Key, Usage};
let from_button: Usage = Button::Left.into(); // Class::Button, id 0
let from_key: Usage = Key::A.into(); // Class::Key, id 0x04
let by_hand = Usage::new(Class::Media, 0x00E9); // volume up
device.press(from_button)?; // press takes any impl Into<Usage>MoveTiming
When a delta reaches the game PCenum MoveTiming { Ride, Now }The move_axis timing argument, against movement riding. Defaults to Ride.
| Variant | Byte | Meaning |
|---|---|---|
Ride | 0x00 | Wait for a real cursor move to carry this delta, as movement riding asks. |
Now | 0x01 | Emit on the box's own clock, whatever movement riding is set to. |
PendingMotion
What a move does to held motionenum PendingMotion { Keep, Flush, Discard }The move_axis pending argument: what happens to motion the box is already holding for a real move. Defaults to Keep.
| Variant | Byte | Meaning |
|---|---|---|
Keep | 0x00 | Leave it held. |
Flush | 0x02 | Emit it now, ignoring the ride window (flush_motion). |
Discard | 0x04 | Drop it (discard_motion). |
Axis
A single relative axisenum Axis { X, Y, Wheel }One relative axis. A lock_axis or a LockTarget::Axis names one, with the sign given by a Direction. Convert with as_u16().
| Variant | id | Meaning |
|---|---|---|
X | 0 | The X cursor axis. |
Y | 1 | The Y cursor axis. |
Wheel | 2 | The wheel. |
RebootTarget
Which chip to restart, and howenum RebootTarget { DeviceDownload, HostDownload, DeviceRun, HostRun }Which chip a REBOOT restarts, and into what mode. Convert with as_u8() and from_u8(u8) -> Option<RebootTarget>.
| Variant | Byte | Meaning |
|---|---|---|
DeviceDownload | 0 | Device chip into ROM download mode, ready to flash over the serial link. |
HostDownload | 1 | Host chip into ROM download mode, ready to flash over its own USB. |
DeviceRun | 2 | Restart the device chip and run its firmware. |
HostRun | 3 | Restart the host chip and run its firmware. |
EmitPace
What paces injected motionenum EmitPace { Learned, Interval, Fixed(u16) }What sets the emit-rate ceiling for injected motion, passed to set_emit_pace and returned in EmitPaceStatus. It raises the ceiling only, so idle stays idle.
| Variant | Meaning |
|---|---|
Learned | Pace to the mouse's learnt native report rate (the default). |
Interval | Pace to the cloned mouse's declared poll rate (its bInterval). |
Fixed(u16) | Pace to a fixed rate in Hz; snaps to 1000/n and caps at 1 kHz. |
LedTarget
Which chip's status LED to driveenum LedTarget { Device, Host, Both }Which chip's LED a LED command drives. The discriminant is the wire target byte. Convert with as_u8() and from_u8(u8) -> Option<LedTarget>.
| Variant | Byte | Meaning |
|---|---|---|
Device | 0 | The device chip's own LED. |
Host | 1 | The host chip's LED, relayed over the inter-chip link. |
Both | 2 | Both LEDs at once. |
LedMode
What to drive the LED toenum LedMode { Auto, Off, Solid, Blink }What a LED command drives the LED to; Auto restores the box's status display. The discriminant is the wire mode byte. Convert with as_u8() and from_u8(u8) -> Option<LedMode>.
| Variant | Byte | Meaning |
|---|---|---|
Auto | 0 | Restore the chip's own status display. |
Off | 1 | LED dark. |
Solid | 2 | Lit steadily at the command's level. |
Blink | 3 | Blinks at the command's level. |
LockTarget
What a lock acts onenum LockTarget { Axis(Axis), Usage(Usage) }What a LOCK command blocks. An Axis and any impl Into<Usage> each convert Into<LockTarget>, so you pass one straight to lock. A button locks exactly like a key.
| Variant | Payload | Locked by |
|---|---|---|
Axis | Axis | The sign, a Direction of positive, negative or both, or the bearing-relative With / Against. |
Usage | Usage | The press or release edge, a Direction. |
LockScope
What a reported lock coversenum LockScope { Target(LockTarget), Blanket(Class) }What a LockEntry in a query_locks reply covers.
| Variant | Payload | Covers |
|---|---|---|
Target | LockTarget | A specific axis or usage. |
Blanket | Class | Every button, key, or media usage of the class. |
Direction
Which way, which edge, or which transfer directionenum Direction { Both, Positive, Negative, With, Against }The one byte LOCK, CLIP and CATCH all carry. The variants are named for the axis reading; which of the three applies is decided by the class, and no class carries two. Convert with as_u8() and from_u8(u8) -> Option<Direction>.
| Variant | Byte | On an axis | On a button or key | On a traffic class |
|---|---|---|---|---|
Both | 0 | both signs; on a scale, a full pass to the relative pair | press and release | IN and OUT |
Positive | 1 | + | press | IN: device to PC |
Negative | 2 | - | release | OUT: PC to device |
With | 3 | the sign the box is injecting | refused | no meaning |
Against | 4 | the sign opposing it | refused | no meaning |
A media usage has no edges. An edge named on one goes out as Both, which is what query_locks reports it as.
With and Against are measured against the bearing rather than a fixed sign, so the sign they cover follows the injection; is_relative() tells them apart, and a lock or catch call on any class but an axis refuses one with Error::RelativeDirection. See set_bearing.
The other two readings get their own names: Direction::PRESS and RELEASE for a usage, Direction::IN and OUT for traffic. admits() tests one against another, and of_delta() reads the sign of a movement.
BearingMode
How the box reads the direction it is injectingenum BearingMode { PerAxis, Vector }What set_bearing chooses, and what Direction::With and Against are resolved by. Convert with as_u8() and from_u8(u8) -> Option<BearingMode>.
| Variant | Byte | Meaning |
|---|---|---|
PerAxis | 0 | Each axis compares its own sign against its own bearing, independently. The default. |
Vector | 1 | The physical delta is projected onto the injected XY vector, and the relative scale weighs only the part along it. |
In Vector the relative pair addresses X and Y as one vector: the box takes the lower of X's and Y's scale and applies it to both axes, so address them together with scale_all. What Locks reports back is there.
The projection is the first of two stages. Each axis's Positive / Negative scale then applies to what the projection left on that axis, so it can weigh motion the projection moved there. See set_bearing.
Blanket
A whole-group lock selectorenum Blanket { Aim, Wheel, Buttons, Keys, Media }A whole input group: which one scale_all / lock_all weigh in one call, and the members of a clip's ClipSettings auto-lock.
| Variant | Meaning | What direction picks |
|---|---|---|
Aim | The X and Y cursor axes. | A sign on each axis, or the relative pair, which is how Vector mode is addressed. |
Wheel | The wheel. | A sign. |
Buttons | Every mouse button. | An edge, on each button. |
Keys | Every keyboard key and modifier. | An edge: Positive blocks presses, Negative releases, Both both. |
Media | Every media (Consumer) usage. | Nothing. Media has no edges. |
LogLevel
Severity tag on a log lineenum LogLevel { Error, Warn, Info, Debug, Verbose }The severity tag on a LogLine. from_u8(u8) is infallible: an unknown byte falls back to Info.
| Variant | Byte | Meaning |
|---|---|---|
Error | 0 | A failure the box could not recover from. |
Warn | 1 | Something off that the box handled. |
Info | 2 | Normal operational notices. |
Debug | 3 | Detail for diagnosing a problem. |
Verbose | 4 | The finest-grained trace output. |
CatchEvent
One caught event off the streamenum CatchEvent { Motion(MotionEvent), Usages(UsageSnapshot), Traffic(TrafficEvent) }What an EventStream yields, one variant per event frame the box pushes.
| Variant | Payload | Raised by |
|---|---|---|
Motion | MotionEvent | A cursor or wheel change, from a CatchClass::Axis filter. |
Usages | UsageSnapshot | A button, key, or media change, from a Button / Key / Media filter. |
Traffic | TrafficEvent | Bytes off a pipe, from any TrafficClass filter. |
All three carry ts_us and a ClockDomain: a stamp compares only to another from the same domain, until you apply the ClockEstimate.
One rolling seq counter covers all three frame types. It counts what the box sent, not what it saw, so a gap is not how you detect loss. CatchState is.
use medius::{CatchEvent, CatchFilter};
let stream = device.catch_events([CatchFilter::everything()])?;
match stream.recv()? {
CatchEvent::Motion(m) => println!("{} {} {}", m.dx, m.dy, m.dz),
CatchEvent::Usages(s) => println!("{} held", s.usages.len()),
CatchEvent::Traffic(t) => println!("{:?} 0x{:04X}: {} bytes", t.class, t.id, t.true_len),
}ClockDomain
Which chip stamped the eventenum ClockDomain { HostChip, DeviceChip }The clock field beside every event's ts_us. The box runs two microsecond timers and nothing relates them; stamping happens on whichever chip saw the event. Convert with as_u8() and from_u8(u8) -> Option<ClockDomain>.
| Variant | Byte | Stamped |
|---|---|---|
HostChip | 0 | On the host chip, in USB interrupt context, when the real device's transfer completed. |
DeviceChip | 1 | On the device chip, at the tap, when the clone's own traffic passed it. |
| Domain | Classes stamped there |
|---|---|
HostChip | the input classes (raising Motion and Usages), HidIn, and the IN direction of the vendor classes. |
DeviceChip | HidOut, the OUT direction of both vendor classes, and Control / Emit / Bus. |
Both timers are box-local, unrelated to any clock on this machine. They wrap every ~71.6 minutes (a u32 of microseconds) and restart at zero when their chip reboots.
A stamp below the one before it is a wrap, a reboot, or a domain change, and only the third is visible in the value itself. To put both domains on one timeline, apply the ClockEstimate from query_catch.
ControlStatus
How a proxied control transfer endedenum ControlStatus { Ok, Stalled, Naked, Other(u8) }Read it with TrafficEvent::control_status(), which returns None for any class other than Control.
| Variant | flags | Meaning |
|---|---|---|
Ok | 0x00 | The transfer completed. |
Stalled | 0xFD | The device STALLed the request. |
Naked | 0xFE | The device NAKed until the transfer timed out. |
Other(u8) | anything else | A status byte with no variant in this build, carried verbatim. |
BusEvent
What happened on the USB busenum BusEvent { Reset, Suspend, Resume, Configured(u8), Deconfigured,
SetInterface { interface: u8, alt: u8 },
DeviceAttached, DeviceDetached, CloneUp, CloneDown }The kind of a CatchClass::Bus event. Read it with TrafficEvent::bus_event(), which returns Option<BusEvent>, which is None for a kind byte with no variant in this build. The two kinds that carry operands parse them into their own fields.
| Variant | Byte | bytes | Meaning |
|---|---|---|---|
Reset | 0 | - | The game PC reset the clone's bus. |
Suspend | 1 | - | The bus went idle and the PC suspended the clone. |
Resume | 2 | - | The bus came back. |
Configured | 3 | a = configuration index | The PC selected a configuration; the clone is live. |
Deconfigured | 4 | - | The PC dropped the clone back to configuration 0. |
SetInterface | 5 | a = interface, b = alternate setting | The PC switched an interface's alternate setting. |
DeviceAttached | 6 | - | The real device appeared on the host chip. |
DeviceDetached | 7 | - | The real device went away. |
CloneUp | 8 | - | The box started presenting the clone to the game PC. |
CloneDown | 9 | - | The box stopped presenting it. |
DeviceAttached and DeviceDetached are the real device on USB3; the other eight are the clone's own USB1 bus, which the control PC is not on.
use medius::{BusEvent, CatchEvent, CatchFilter, TrafficClass};
let stream = device.catch_events([CatchFilter::traffic_class(TrafficClass::Bus)])?;
if let CatchEvent::Traffic(t) = stream.recv()? {
match t.bus_event() {
Some(BusEvent::Configured(n)) => println!("configuration {n}"),
Some(BusEvent::SetInterface { interface, alt }) => println!("iface {interface} alt {alt}"),
Some(kind) => println!("{kind:?}"),
None => {}
}
}ClipState
The buffered-clip lifecycle stateenum ClipState { Idle, Playing, Paused, Faulted }The device-side clip state on ClipStatus::state, from ClipHandle::query_status().
| Variant | Byte | Meaning |
|---|---|---|
Idle | 0 | No clip playing (empty, or a loaded clip parked at its start). |
Playing | 1 | Draining the ring, one entry per native frame. |
Paused | 2 | Held mid-clip, keeping the cursor and any held input; resumes from the same spot. |
Faulted | 3 | An append was dropped or the ring overflowed; recover with clear. |
Edge
Which edge fires a clip triggerenum Edge { Both, Press, Release }Which edge of a bound usage fires a ClipTrigger: its press, its release, or either. It shares wire values with Direction.
| Variant | Byte | Meaning |
|---|---|---|
Both | 0 | Fire on either edge. |
Press | 1 | Fire on the press edge. |
Release | 2 | Fire on the release edge. |
ClipAction
What a fired clip trigger doesenum ClipAction { Start, Stop, Pause, Resume, Restart, Toggle }What a bound ClipTrigger does to the clip when its edge fires. The discriminant doubles as the CLIP_CTRL op byte for the same action.
| Variant | Byte | Meaning |
|---|---|---|
Start | 0 | Play from the ring's head, or resume a pause. |
Stop | 1 | Stop playback and rewind to the head. |
Pause | 2 | Hold playback mid-clip. |
Resume | 3 | Continue a paused clip from where it stopped. |
Restart | 4 | Rewind to the head and play from the start. |
Toggle | 5 | Play if idle or paused, stop if playing. |
UpdateTarget
Which chip an update op addresses| Variant | Wire | Means |
|---|---|---|
Device | 0 | The PC-facing chip, written directly over the control port. |
Host | 1 | The chip that reads the real device, relayed over the inter-chip link. |
ImageState
Where a booted image is in its probationThe bootloader's own record for a slot. See rollback.
| Variant | Wire | Means |
|---|---|---|
New | 0 | Selected but not yet booted. |
PendingVerify | 1 | Booted and on probation; the window rollback lives in. |
Valid | 2 | Confirmed by the image itself. |
Invalid | 3 | The image asked to be rolled back. |
Aborted | 4 | Booted once and never confirmed. |
Unknown | 0xFF | No entry for this slot. |