DeviceKindClone's primary kind

enum DeviceKind { Unknown, Keyboard, Mouse }

The kind field of a DeviceInfo, read from the native HID report descriptors and Boot declarations. A device with both kinds reads keyboard if it has a full key bitmap (unless it declares Boot for both) or declares Boot for its keyboard only. Otherwise it reads mouse. It also drives find_mouse_box and find_keyboard_box. Display prints the lowercase name.

VariantByteMeaning
Unknown0Neither a mouse nor a keyboard (a device with no HID interface reads this), or nothing cloned: DeviceInfo::is_cloned tells them apart.
Keyboard1A keyboard.
Mouse2A mouse.

ActionPress / release tri-state

enum Action { SoftRelease, Press, ForceRelease }

The 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>.

VariantByteMeaning
SoftRelease0Drop the box's override, press or force. A hold on the real device stays down.
Press1Force the input down.
ForceRelease2Force the input up, masking a hold on the real device.

The two releases differ only under a hold on the real device:

VariantNot heldHeld on the real device
Pressdowndown
SoftReleaseupdown (the native bit stands)
ForceReleaseupup (masks native)

ClassClass of a momentary usage

enum 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>.

VariantByteMeaning
Button0A mouse button; id is a Button id (0 = LEFT .. 4 = SIDE2).
Key1A keyboard key; id is a HID keycode (0xE0 .. 0xE7 is a modifier).
Media2A media usage; id is a 16-bit Consumer usage.

CatchClassWhat a catch subscription addresses

enum CatchClass { Button, Key, Media, Axis, HidIn, HidOut, VendorInterrupt, VendorBulk, Control, Emit, Bus, ClipTransfer }

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 classes at the same byte values. The other eight address USB traffic. TrafficClass is that half alone.

VariantByteidBlanket
Button0a Button id (0 = LEFT .. 4 = SIDE2).every mouse button.
Key1a HID keycode (0xE0 .. 0xE7 is a modifier).every key and modifier.
Media2a 16-bit Consumer usage.every media usage.
Axis3an Axis: X, Y, the wheel, or pan.every axis.
HidIn4an interface number on the real device.every HID interface.
HidOut5an endpoint number.every interrupt-OUT endpoint.
VendorInterrupt6an endpoint number.every vendor interrupt endpoint.
VendorBulk7an endpoint number.every vendor bulk endpoint.
Control8an endpoint number (0 = EP0; on EP0, the requests the clone relays to the real device).every control endpoint.
Emit9an endpoint number on the clone.every emitting endpoint.
Bus10unused; a bus event has no id.every bus event.
ClipTransfer11the endpoint number (0 = EP0) a clip's transfer ran on.every control endpoint.

A blanket is one table entry, not one per id. CatchFilter::watch_class(c) and traffic_class(c) are the per-class blankets, and CatchFilter::everything() is the wildcard over all twelve. The wire sentinels never appear in Rust.

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 emitted.

real device --> [ merge point ] --> locks --> injection --> [ clone emits ] --> game PC
                       |                                           |
          Button / Key / Media / Axis                            Emit

The traffic classes tap each pipe on the chip that owns it. Each event's ClockDomain records which.

EXAMPLE
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)),
])?;

TrafficClassByte-oriented half of the address space

enum TrafficClass { HidIn, HidOut, VendorInterrupt, VendorBulk, Control, Emit, Bus, ClipTransfer }

The eight packet-carrying classes, at their CatchClass byte values. It's a separate enum, so CatchFilter::traffic can't take an input class. TrafficClass::ALL lists them, and From and TryFrom convert both ways.

CaptureHow much of each packet to keep

enum Capture { Whole, First(u8) }

Traffic classes only: a capture on an input class, which carries no packet, is refused.

It's a ceiling. The box holds one entry per address and cuts once, so another subscriber naming the same address with a wider capture raises yours too. First(0) is Whole. bytes() returns Option<u8>, and widest() resolves two captures.

InputOne decoded input edge

enum Input { Press(Usage), Release(Usage), Motion { dx: i16, dy: i16, dz: i16, pan: 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.

MotionRelative axis for move_axis

enum Motion { Cursor { dx: i16, dy: i16 }, Wheel(i16), Pan(i16) }

What move_axis drives. Every delta spans the full i16 range. A lock names a single Axis instead.

VariantPayloadMeaning
Cursor{ dx: i16, dy: i16 }Relative pointer motion.
Wheeli16Relative scroll.
Pani16Relative AC Pan (horizontal scroll), a full peer of the wheel.

MoveTimingWhen a delta reaches the game PC

enum MoveTiming { Ride, Now }

The move_axis timing argument, against movement riding. Defaults to Ride.

VariantByteMeaning
Ride0x00When movement riding is on, wait for a native cursor-motion report to carry this delta.
Now0x01Emit on the next mouse report, native or not, whatever movement riding is set to.

PendingMotionWhat a move does to held motion

enum PendingMotion { Keep, Flush, Discard }

The move_axis pending argument, for motion already held for a ride. Defaults to Keep.

VariantByteMeaning
Keep0x00Leave held motion for its ride.
Flush0x02Emit held motion now, ignoring the ride window (flush_motion).
Discard0x04Drop held motion (discard_motion).

AxisSingle relative axis

enum Axis { X, Y, Wheel, Pan }

A lock_axis or LockTarget::Axis names one, with a Direction for the sign. Convert with as_u16() and from_u16().

VariantidMeaning
X0The X cursor axis.
Y1The Y cursor axis.
Wheel2The wheel.
Pan3AC Pan (horizontal scroll).

RebootTargetWhich chip to restart, and how

enum RebootTarget { DeviceDownload, HostDownload, DeviceRun, HostRun }

Which chip a REBOOT restarts, and into what mode. Convert with as_u8() and from_u8(u8) -> Option<RebootTarget>.

VariantByteMeaning
DeviceDownload0Device chip into ROM download mode, ready to flash over the serial link.
HostDownload1Host chip into ROM download mode, ready to flash over USB3.
DeviceRun2Restart the device chip and run its firmware.
HostRun3Restart the host chip and run its firmware.

EmitPaceWhat paces injected motion

enum 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: the box still emits only while motion is pending.

VariantMeaning
LearnedPace to the learnt native report rate (the default).
IntervalPace 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.

RenderModeTexture the box renders motion with

enum RenderMode { Off, Stock, Despiked, Unsmoothed }

Passed to set_render and returned in RenderStatus. The box boots at Despiked, which is also this enum's Default. The model is ABCurves (MIT).

VariantMeaning
OffEven fill at the paced rate, no model.
StockRender with the bit-exact triangular smoother.
DespikedRender with the smoother's onset ramped rather than stepped (the default).
UnsmoothedRender with no smoother. The model receives the raw injection.

LedTargetWhich chip's status LED to drive

enum 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>.

VariantByteMeaning
Device0The device chip's LED.
Host1The host chip's LED, relayed over the inter-chip link.
Both2Both LEDs at once.

LedModeWhat to drive the LED to

enum 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>.

VariantByteMeaning
Auto0Restore the chip's status display.
Off1LED dark.
Solid2Lit steadily at the command's level.
Blink3Blinks at the command's level.

LockTargetOne addressable input field: an axis or a usage

enum LockTarget { Axis(Axis), Usage(Usage) }

What a lock weighs and a Transform reads and writes. The box addresses a field the same way everywhere. An Axis and any impl Into<Usage> convert Into<LockTarget>, so either passes straight in. A button locks exactly like a key.

VariantPayloadLocked by
AxisAxis: X, Y, the wheel, or panThe sign, a Direction of positive, negative or both, or the bearing-relative With / Against.
UsageUsage: a button, key, or media usageThe press or release edge, a Direction.
MethodReturnsMeaning
.class_id()(u8, u16)The wire (class, id) this field encodes to.
LockTarget::from_class_id(class, id)Option<LockTarget>The inverse. None for a class no field names, or an axis id past the declared axes.
.as_axis()Option<Axis>The axis this field names, or None for a momentary usage.

LockScopeWhat a reported lock covers

enum LockScope { Target(LockTarget), Blanket(Class) }

What a LockEntry in a query_locks reply covers.

VariantPayloadCovers
TargetLockTargetA specific axis or usage.
BlanketClassEvery button, key, or media usage of the class.

DirectionWhich way, which edge, or which transfer direction

enum Direction { Both, Positive, Negative, With, Against }

The one byte LOCK, CLIP and CATCH all carry. The variants are named for the axis reading. The class picks which of the three readings applies, and no class carries two. Convert with as_u8() and from_u8(u8) -> Option<Direction>.

VariantByteAxisButton or keyTraffic class
Both0both signs; on a scale, a full pass to the relative pairpress and releaseIN and OUT
Positive1+pressIN: device to PC
Negative2-releaseOUT: PC to device
With3the sign the box is injectingrefusedno meaning
Against4the sign opposing itrefusedno meaning

A media usage has no edges. An edge on one is sent as Both, and reads back as Both in query_locks.

With and Against are measured against the bearing, not a fixed sign, so the sign they cover follows the injection. is_relative() tells them apart. On any class but an axis, a lock or catch call refuses them with Error::RelativeDirection.

The other two readings have separate 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 delta.

BearingModeHow the box reads the injected direction

enum BearingMode { PerAxis, Vector }

Set by set_bearing. It resolves Direction::With and Against. Convert with as_u8() and from_u8(u8) -> Option<BearingMode>.

VariantByteMeaning
PerAxis0Each axis compares its sign against its bearing. The default.
Vector1The native 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 applies the lower of X's and Y's scale to both axes, so address them together with scale_all. Locks documents the readback.

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.

BlanketWhole-group lock selector

enum Blanket { Aim, Wheel, Buttons, Keys, Media }

A whole input group. scale_all / lock_all weigh one in a single call, and a clip's ClipSettings auto-lock is a list of them.

VariantMeaningDirection
AimThe X and Y cursor axes.A sign on each axis, or the relative pair, which is how Vector mode is addressed.
WheelThe wheel.A sign.
ButtonsEvery mouse button.An edge, on each button.
KeysEvery keyboard key and modifier.An edge: Positive blocks presses, Negative releases, Both both.
MediaEvery media (Consumer) usage.Nothing; media has no edges.

LogLevelSeverity tag on a log line

enum LogLevel { Error, Warn, Info, Debug, Verbose }

The severity tag on a LogLine. from_u8(u8) is infallible: an unknown byte falls back to Info.

VariantByteMeaning
Error0A failure the box could not recover from.
Warn1Something off that the box handled.
Info2Normal operational notices.
Debug3Detail for diagnosing a problem.
Verbose4The finest-grained trace output.

CatchEventOne caught event off the stream

enum CatchEvent { Motion(MotionEvent), Usages(UsageSnapshot), Traffic(TrafficEvent) }

What an EventStream yields, one variant per event frame the box sends.

VariantPayloadRaised by
MotionMotionEventA cursor or wheel change, from a CatchClass::Axis filter.
UsagesUsageSnapshotA button, key, or media change, from a Button / Key / Media filter.
TrafficTrafficEventBytes off a pipe, from any TrafficClass filter.

All three carry ts_us and a ClockDomain. Until the ClockEstimate is applied, a stamp compares only with another from the same domain.

One rolling seq counter covers all three frame types. It counts what the box sent, not what it captured, so loss shows in CatchState, not as a seq gap.

EXAMPLE
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),
}

ClockDomainWhich chip stamped the event

enum ClockDomain { HostChip, DeviceChip }

The clock field beside every event's ts_us. The box runs two unsynchronised microsecond timers. The chip that captured the event stamps it. Convert with as_u8() and from_u8(u8) -> Option<ClockDomain>.

VariantByteStamped
HostChip0On the host chip, in USB interrupt context, when the real device's transfer completed.
DeviceChip1On the device chip, at the tap.
DomainClasses stamped there
HostChipthe input classes (raising Motion and Usages), HidIn, and native IN traffic on the vendor classes.
DeviceChipHidOut, the OUT direction of both vendor classes, a raw packet on a vendor IN endpoint, and Control / Emit / Bus / ClipTransfer.

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 lower than the one before it means a wrap, a reboot, or a domain change. Only the domain change shows in the value itself. To put both domains on one timeline, apply the ClockEstimate from query_catch.

ControlStatusHandshake the game PC received

enum ControlStatus { Ok, Stalled, Naked, Other(u8) }

Read it with TrafficEvent::control_status(), which returns None for any class other than Control. It is flags bits 0-1. Bit 7 is rule_acted().

Variantflags b0-b1Meaning
Ok0The transaction completed.
Stalled1The PC got a STALL: from the real device, from a rule that refused the request, or, above endpoint 0, for a request that failed.
Naked2NAKed until the host gave up, on endpoint 0 only: the real device never replied, or a Nak rule.
Other(u8)3A handshake value with no variant in this build, carried verbatim.

BusEventWhat happened on the USB bus

enum 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>: None for a kind byte this build does not name. The two kinds with operands parse them into named fields.

VariantByteOperandsMeaning
Reset0-The game PC reset the clone's bus.
Suspend1-The bus went idle and the PC suspended the clone. The box suspends the real device with it.
Resume2-The bus came back, and the box resumes the real device with it.
Configured3a = configuration indexThe PC selected a configuration. The clone is live.
Deconfigured4-The PC dropped the clone back to configuration 0.
SetInterface5a = interface, b = alternate settingThe PC switched an interface's alternate setting.
DeviceAttached6-The real device appeared on the host chip.
DeviceDetached7-The real device went away.
CloneUp8-The box started presenting the clone to the game PC.
CloneDown9-The box stopped presenting it.

DeviceAttached and DeviceDetached are the real device on USB3. The other eight are on the clone's USB1 bus, which the control PC isn't on.

EXAMPLE
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                                       => {}
    }
}

TransformOpWhat a transform does to its fields

enum TransformOp { Remap, Swap }

How a Transform's source and destination relate. as_u8() and from_u8() convert the wire byte. admits(source, dest) checks a pair, as the crate does before sending.

VariantByteFields it admitsEffect
Remap0axis→axis, button→button, button→key, button→mediaMove the source's value into the destination, clearing the source. A button to key or media remap holds the destination through that interface's report for as long as the button is down.
Swap1two axesRead both, then write both, so it is not two remaps.

admits refuses a pair whose source and destination are the same field. To weigh a field, or reverse it, use scale.

ClipStateClip lifecycle state

enum ClipState { Idle, Playing, Paused, Faulted }

The box's clip state on ClipStatus::state, from ClipHandle::query_status().

VariantByteMeaning
Idle0No clip playing (empty, or a loaded clip parked at its start).
Playing1Draining the ring, one entry per native frame.
Paused2Held mid-clip, keeping the cursor and any held input. Resumes from the same spot.
Faulted3An append was dropped or the ring overflowed. Recover with clear.

EdgeWhich edge fires a clip trigger

enum Edge { Both, Press, Release }

Which edge of a bound usage fires a ClipTrigger. Shares wire values with Direction.

VariantByteMeaning
Both0Fire on either edge.
Press1Fire on the press edge.
Release2Fire on the release edge.

ClipActionWhat a fired clip trigger does

enum ClipAction { Start, Stop, Pause, Resume, Restart, Toggle }

What a bound ClipTrigger or ClipPacketTrigger does to the clip when it fires. The discriminant doubles as the CLIP_CTRL op byte for the same action.

VariantByteMeaning
Start0Play from the ring's head, or resume a pause.
Stop1Stop playback and rewind to the head.
Pause2Hold playback mid-clip.
Resume3Continue a paused clip from where it stopped.
Restart4Rewind to the head and play from the start.
Toggle5Play if idle or paused, stop if playing.

UpdateTargetWhich chip an update op addresses

enum UpdateTarget { Device, Host }
VariantByteMeaning
Device0The PC-facing chip, written directly over the control port.
Host1The chip that reads the real device, relayed over the inter-chip link.

ImageStateWhere a booted image is in its probation

enum ImageState { New, PendingVerify, Valid, Invalid, Aborted, Unknown }

The bootloader's record for a slot. See rollback.

VariantByteMeaning
New0Selected but not yet booted.
PendingVerify1Booted and on probation. Rollback is still possible.
Valid2Confirmed by the image itself.
Invalid3The image asked to be rolled back.
Aborted4Booted once and never confirmed.
Unknown0xFFNo entry for this slot.

TransferStatusHow a control transfer ended

enum TransferStatus { Ok, Refused, Stall, Nak, NoDevice, Other(u8) }

The status byte of a TransferOutcome, and of a ClipTransfer event through TrafficEvent::transfer_status(). The codes sit at the top of the byte range so they never collide with a length. An unknown byte is carried through as Other.

VariantByteMeaning
Ok0x00The transfer completed; any IN data is in data.
Refused0xFCThe box refused before reaching the real device: the opt-in is off, the request was malformed, wLength was >504 or the OUT data shorter than it, or the host chip's control queue was full.
Stall0xFDThe real device STALLed the request.
Nak0xFEThe real device didn't finish within 500 ms, the transfer failed on the bus, the endpoint is undeclared, no reply crossed the link within 800 ms, or the game PC has the clone (and so the real device) suspended.
NoDevice0xFFNo real device is attached on the host chip.
Other(u8)any otherA status byte this build does not name.

Refused comes from the box, before the real device. Stall comes from the real device. Nak can be either: a real device that did not finish, or a request with no reply (native TRANSFER_RESP).

EXAMPLE
use medius::TransferStatus;

match reply.status {
    TransferStatus::Ok => println!("{} bytes", reply.data().len()),
    TransferStatus::Stall => println!("device rejected the request"),
    TransferStatus::NoDevice => println!("nothing attached on the host chip"),
    TransferStatus::Refused => println!("box refused it: opt-in off, malformed, too large, or queue full"),
    other => println!("other status: {other:?}"),
}

RewriteClassWhich traffic a rewrite rule addresses

enum RewriteClass { HidIn, HidOut, VendorInterrupt, VendorBulk, Control, Emit, Any }

The class picks the traffic a RewriteRule addresses, and the id picks the address within it. Together they are the writable half of catch's address space.

VariantByteTrafficid
HidIn4A native HID report, as it arrived; see motion.the interface number
HidOut5A report the PC writes to the real device.the endpoint number
VendorInterrupt6Interrupt traffic on a vendor interface.the endpoint number
VendorBulk7Bulk traffic on a vendor interface.the endpoint number
Control8A proxied control transfer, and the only class that may answer or rewrite the native reply. On EP0, it covers the requests the clone relays to the real device (class and vendor ones, and a few standard ones). A control endpoint above 0 carries every request.the endpoint number (0 = EP0)
Emit9What the clone emits, after the renderer: injected and rendered frames as well as relayed ones.the endpoint number
Any0xFFEvery surface a packet crosses, so a report meets the rule at HidIn and again at Emit. Pass, Patch and Replace only, ranked below every other class.ignored

The parsed-input and bus classes are not rewritable. as_u8() and from_u8() convert the wire byte.

RewriteActionWhat the top-ranked rewrite rule does

enum RewriteAction { Pass, Drop, Patch, Replace, Answer, Stall, Nak, ReplyPatch, ReplyReplace }

The top-ranked rule's action sets what happens to a matched packet. The class column says which RewriteClass accepts the action. A report-only action on a control class, or the reverse, returns Error::RewriteActionClass.

VariantByteClassPayloadEffect
Pass0anynoMatched, but left untouched: a shadow over a broader rule.
Drop1reportnoNot delivered. An Emit drop emits nothing; a HidIn drop removes the native report while injection still emits.
Patch2anyyesOverwrite the payload bytes at offset, length preserved. On Control, an OUT request's data stage only.
Replace3anyyesThe packet becomes the payload. On Control, the payload overwrites the start of an OUT request's data stage and wLength is kept.
Answer4controlyesAnswer from the payload without forwarding the request to the real device.
Stall5controlnoProtocol STALL.
Nak6controlnoEP0 NAKs until the host times out. A control endpoint above 0 STALLs.
ReplyPatch7controlyesOverwrite the native reply at offset.
ReplyReplace8controlyesReplace the native reply with the payload.

PatchSectionWhich descriptor a patch overwrites

enum PatchSection { Device, Config, Report, String, Bos }

The section names the descriptor a Patch overwrites, and that patch's cfg and index address within it.

VariantByteOverwritescfg / index
Device0The 18-byte device descriptor.ignored
Config1A configuration descriptor.cfg is the configuration index, counting from 0
Report2An interface's report descriptor.cfg is that index, index the interface number
String3A string descriptor, replaced whole: ≤126 bytes, one UTF-16 code unit per byte. Index 0, the language list, is not patched.index is the string index; offset ignored
Bos4The BOS descriptor.ignored