Medius - Rust LibraryStructs

Structs

Values the box reports back

Plain value types you get back from queries and discovery. Their fields are public.

Version

Firmware identity and box id

Firmware identity from query_version(). Display prints fw M.m.p and omits proto_ver; read it from the field.

FieldTypeMeaning
proto_veru8Wire-protocol version the firmware speaks (3 here).
fw_majoru8Firmware major version.
fw_minoru8Firmware minor version.
fw_patchu8Firmware patch version.
mac[u8; 6]The device chip's base MAC, a stable per-box id.
nameStringThe box's human-readable name (a synthesized default when unset), set via set_name.
MethodReturnsMeaning
mac_hex()StringThe MAC as 12 lowercase hex digits, the id used by open_by_id.
EXAMPLE
use medius::Version;

let v = Version { proto_ver: 3, fw_major: 3, fw_minor: 0, fw_patch: 0, mac: [0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc], name: "Loki".into() };
assert_eq!(v.to_string(), "fw 3.0.0"); // Display omits proto_ver
assert_eq!(v.mac_hex(), "123456789abc");
println!("{v} (protocol {}, box {}, name {})", v.proto_ver, v.mac_hex(), v.name);

Health

Box readiness flags

Box readiness from query_health(), one bool per bit. from_flags(u8) and to_flags() convert the byte.

FieldTypeTrue when
link_upboolThe link to the host chip is up.
mouse_attachedboolA real mouse is plugged in.
clone_configuredboolThe PC has set up the cloned mouse.
injection_activeboolThe box is holding at least one injected button or move.
rate_confidentboolThe native-rate estimator window is full, so Rate is trustworthy.
lock_onboolAt least one input lock is active.
catch_onboolA catch subscription is streaming physical-input events.
kbd_attachedboolA keyboard is attached on the host chip, cloned and injectable.
EXAMPLE
use medius::Health;

let h = Health::from_flags(0b0000_0011); // link_up | mouse_attached
assert!(h.link_up && h.mouse_attached);
assert!(!h.clone_configured);
assert_eq!(h.to_flags(), 0b0000_0011); // round-trips to the same byte

DeviceInfo

The cloned device's USB identity, kind, and product

USB identity from device_info(). Every field is zero/empty when nothing is cloned. Display prints VVVV:PPPP product.

FieldTypeMeaning
vidu16USB vendor id (idVendor).
pidu16USB product id (idProduct).
bcd_deviceu16Device release (bcdDevice).
bcd_usbu16USB version (bcdUSB), e.g. 0x0200.
has_serialboolThe clone serves a serial string.
has_bosboolThe clone serves a BOS descriptor.
kindDeviceKindThe device's primary kind, from its Boot-interface protocol.
productStringThe product string the device serves (empty when it serves none).
EXAMPLE
use medius::{DeviceInfo, DeviceKind};

let d = DeviceInfo {
    vid: 0x046D, pid: 0xC08B, bcd_device: 0, bcd_usb: 0x0201,
    has_serial: true, has_bos: true, kind: DeviceKind::Mouse, product: "G502".into(),
};
assert_eq!(d.to_string(), "046D:C08B G502"); // Display is VVVV:PPPP product

Caps

The whole device, mouse and keyboard

One caps() query, returned as one struct: a MouseCaps half and a KbdCaps half, plus the per-class change-driven flags. has_mouse() / has_keyboard() tell you which are bound; is_composite() is true when the device has more than one HID interface.

FieldTypeMeaning
mouseMouseCapsThe mouse half (all-zero when no mouse is bound).
keyboardKbdCapsThe keyboard half (all-zero when no keyboard is bound).
mouse_change_drivenboolAlways false: mouse motion is continuous, so its Rate has a learned cadence.
kbd_change_drivenboolTrue when a keyboard is bound: it reports only on a key change, so its rate has no continuous cadence.
EXAMPLE
let caps = device.caps()?;
if caps.has_keyboard() && caps.keyboard.has_consumer {
    // media injection is real on this board
}
println!("{} mouse buttons", caps.mouse.n_buttons);

MouseCaps

What the cloned mouse can do

Semantic capabilities from caps(). Every field is zero when no relative-axis mouse interface is bound. is_composite() is true when n_hid > 1.

FieldTypeMeaning
n_buttonsu8Buttons the mouse report carries.
has_xboolThe report carries an X axis.
has_yboolThe report carries a Y axis.
has_wheelboolThe report carries a wheel.
has_report_idboolThe mouse report sits behind a HID report ID.
n_hidu8Cloned HID interfaces; >1 = composite.
EXAMPLE
use medius::MouseCaps;

let c = MouseCaps { n_buttons: 5, has_x: true, has_y: true, has_wheel: true, has_report_id: false, n_hid: 1 };
assert!(!c.is_composite()); // single HID interface

Rate

The native report rate the box tracks

Live rate from query_rate(). native_hz() converts the period to a frequency, returning None while native_period_us is still 0. The rate is class-aware: a change-driven input (a keyboard or media device) has no continuous cadence, so it sets change_driven and leaves native_period_us at 0, with poll_period_us the honest figure.

FieldTypeMeaning
native_period_usu16Realised native report period in µs; 0 = not learned, or change-driven.
poll_period_usu16Cloned inject-endpoint poll period in µs.
confidentboolThe estimator window is full and the value is trustworthy.
change_drivenboolThe active input is event-driven (keyboard / media), so there is no continuous cadence.
EXAMPLE
use medius::Rate;

let r = Rate { native_period_us: 1000, poll_period_us: 1000, confident: true, change_driven: false };
assert_eq!(r.native_hz(), Some(1000.0));

Stats

Delivery and telemetry counters

Delivery counters from query_stats(). A nonzero tx_drops or tx_wedges means delivery degraded under load. The narrowed fields saturate instead of wrapping.

FieldTypeMeaning
inject_emitsu32Pure-injection reports emitted.
tx_dropsu16Reports dropped on TX-queue overflow (should stay 0).
tx_mergesu16Backed-up reports merged instead of queued.
tx_maxdepthu8Deepest the TX queue has reached.
tx_wedgesu8Wedged-endpoint recoveries.
wakeupsu16Remote-wakeups issued.
reset_countu16USB bus resets seen.
config_countu16SET_CONFIGURATION events (re-enumerations).

Locks

The active input locks

Active locks from query_locks(), a list of LockEntry across every class, so mouse, key, and media locks read the same way. is_locked(target, dir) tests one lock; entries() is the whole list. See the native LOCKS reply for the wire format.

MethodReturnsMeaning
entries()&[LockEntry]Every active lock, one entry per locked target or whole-class blanket.
is_locked(target, dir)boolWhether that target and direction is locked, by a specific entry or a covering whole-class blanket; target is any impl Into<LockTarget>.
from_entries(Vec<LockEntry>)LocksBuild one from entries, for tests and the MockBox.
EXAMPLE
use medius::{Axis, Button, LockDirection};

let locks = device.query_locks()?;
if locks.is_locked(Axis::X, LockDirection::Positive) {
    // the real mouse can't move right
}
if locks.is_locked(Button::Left, LockDirection::Negative) {
    // a left-click is latched down: the hand can't release it
}
println!("{} locks active", locks.entries().len());

LockEntry

One entry in a Locks list
struct LockEntry { scope: LockScope, positive: bool, negative: bool }

One active lock in a Locks list: what is locked (its LockScope) and which edges.

FieldTypeMeaning
scopeLockScopeA specific axis or usage, or a whole-class blanket.
positiveboolThe positive/press edge is locked.
negativeboolThe negative/release edge is locked.

CatchMask

Which physical reports raise an event

A bitflags newtype you hand to catch_events(). It gates which reports raise a CatchEvent; the payload is always the full snapshot. Combine the consts with | (BitOr), e.g. CatchMask::MOTION | CatchMask::BUTTONS.

ConstBitTriggers on
MOTION0x01The mouse moved (dx/dy).
WHEEL0x02The wheel turned.
BUTTONS0x04A button changed.
KEYS0x08A keyboard key or modifier changed.
MEDIA0x10A media (Consumer) usage changed.
MethodReturnsMeaning
empty()CatchMaskNo bits set (unsubscribe).
all()CatchMaskEvery class set (0x1F).
bits()u8The raw mask byte.
is_empty()boolNo bits set.
contains(other)boolEvery bit in other is set here.
union(other)CatchMaskThe two masks ORed together.
EXAMPLE
use medius::CatchMask;

let mask = CatchMask::MOTION | CatchMask::BUTTONS;
assert!(mask.contains(CatchMask::BUTTONS));
assert_eq!(mask.bits(), 0x05);
let stream = device.catch_events(mask)?;

MotionEvent

One physical relative-axis event

The payload of a CatchEvent::Motion, read off an EventStream. The real hand motion at the merge point, before lock suppression or injection, so a locked or injected axis still reports the true delta.

FieldTypeMeaning
dxi16X movement this report (right positive).
dyi16Y movement this report (down positive).
dzi16Wheel movement this report (up positive).
EXAMPLE
use medius::{CatchMask, CatchEvent};

let stream = device.catch_events(CatchMask::MOTION | CatchMask::WHEEL)?;
if let CatchEvent::Motion(m) = stream.recv()? {
    println!("moved {} {}, wheel {}", m.dx, m.dy, m.dz);
}

UsageSnapshot

One physical held-usage snapshot

The payload of a CatchEvent::Usages: every held Usage of one class (buttons, keys, or media, all one shape), captured before injection. Diff successive snapshots for press/release edges, or test one with is_held; a dropped frame self-corrects on the next.

FieldTypeMeaning
usagesVec<Usage>The currently-held usages, all of one class per event.
MethodReturnsMeaning
is_held(usage)boolWhether usage is held; takes any impl Into<Usage>.
class()Option<Class>The class of this snapshot, from its first usage, or None when empty.
EXAMPLE
use medius::{Button, CatchMask, CatchEvent};

let stream = device.catch_events(CatchMask::BUTTONS)?;
if let CatchEvent::Usages(s) = stream.recv()? {
    if s.is_held(Button::Left) {
        println!("left button held");
    }
}

Key

A HID keyboard keycode

A newtype over a HID keyboard/keypad usage. It converts Into<Usage>, so you pass one straight to inject or press. Named consts cover the common keys (Key::A, Key::ENTER, Key::LEFT_SHIFT); build any other with Key::new(u8). Modifiers are the usages 0xE0-0xE7.

ItemReturnsMeaning
Key::A .. Key::LEFT_SHIFTKeyNamed consts for common keycodes and modifiers.
new(u8)KeyWrap any raw HID keycode.
usage()u8The raw keycode byte.
EXAMPLE
use medius::Key;

let a = Key::A;            // 0x04
let custom = Key::new(0x04);
assert_eq!(a.usage(), custom.usage());

MediaKey

A 16-bit Consumer usage

A newtype over a 16-bit Consumer usage. It converts Into<Usage>, so you pass one straight to inject or press. Named consts cover the common media keys (MediaKey::VOLUME_UP, MediaKey::PLAY_PAUSE, MediaKey::MUTE); build any other with MediaKey::new(u16).

ItemReturnsMeaning
MediaKey::VOLUME_UP .. MediaKey::MUTEMediaKeyNamed consts for common media usages.
new(u16)MediaKeyWrap any raw Consumer usage.
usage()u16The raw Consumer usage.
EXAMPLE
use medius::MediaKey;

let vol_up = MediaKey::VOLUME_UP;   // 0x00E9
let custom = MediaKey::new(0xE9);
assert_eq!(vol_up.usage(), custom.usage());

KbdCaps

What the cloned keyboard can do

Semantic capabilities from caps(). Every field is zero when no keyboard is bound. has_consumer gates media injection.

FieldTypeMeaning
n_keysu8Keycode-array slots, or 0xFF for an NKRO bitmap.
nkroboolThe keyboard reports an NKRO bitmap.
has_consumerboolA Consumer collection is present (media keys injectable).
has_systemboolA system-control collection is present (passthrough-only).
has_report_idboolThe keyboard report sits behind a HID report ID.

CatchState

The active catch subscription

The current subscription from query_catch(). A nonzero dropped means the box shed events under back-pressure.

FieldTypeMeaning
maskCatchMaskWhich reports are subscribed; empty = none.
droppedu32Box-side events dropped under back-pressure.

ImperfectStatus

The imperfect-clone state

The imperfect-clone state from query_imperfect().

FieldTypeMeaning
allowedboolThe opt-in toggle; cloning an over-capacity device is allowed.
over_capacityboolThe attached device needs an interrupt-IN endpoint the box can't service.
clone_imperfectboolThe live clone is over-capacity and was cloned anyway, so one interface is dead.

EmitPaceStatus

The emit-rate pacing state

The emit-rate pacing state from query_emit_pace().

FieldTypeMeaning
modeEmitPaceThe selected mode; Fixed carries the requested rate.
resolved_hzu16The ceiling in effect (Hz); 0 = learnt/adaptive, or no device yet in Interval.

LogLine

One line from the LOG stream

One line from the box's LOG stream, read off a LogStream.

FieldTypeMeaning
levelLogLevelSeverity tag.
textStringThe decoded message.

PortInfo

A discovered serial port

A serial port that looks like a Medius box, from find_medius(). serial is the CH343 adapter's serial string, part of the box identity.

FieldTypeMeaning
pathStringSerial port path.
vidu16USB vendor id (0x1A86).
pidu16USB product id (0x55D3).
serialOption<String>The CH343 adapter's serial string, when it serves one.

CountersSnapshot

Link statistics snapshot

Four running link totals from counters().

FieldTypeMeaning
frames_txu64Frames sent to the box.
frames_rxu64Frames received from the box.
crc_dropsu64Inbound frames dropped on a bad checksum.
reconnectsu64Times the library reopened the port.

LogStream

Receiver for the device LOG stream

Receives the box's LOG frames as LogLine values off a local channel, from device.logs(). Pull lines with recv / try_recv / recv_timeout / try_iter / recv_async (or for line in stream); none touch the wire, so cloning shares the queue. The methods and an example are on Logs & counters.

ClipSettings

A clip's persistent config, read back

A clip's configuration from ClipHandle::query_config(): the auto-lock set, the loop and retain flags, whether it's finalized, and the bound ClipTrigger list. You set these with the handle setters (set_autolock, set_loop, set_retain, finalize, bind); this is the readback.

FieldTypeMeaning
autolockVec<Blanket>The Blanket groups auto-locked while playing (clip-owned, released on stop); empty = no auto-lock.
loop_boolPlayback restarts from the top instead of stopping at the end.
retainboolThe buffered content survives a stop, so a restart replays it instead of needing a fresh append.
finalizedboolThe clip is sealed: no more appends, ready to replay as a fixed sequence.
triggersVec<ClipTrigger>The bound input triggers (up to 8), each firing a playback action on a physical edge.
EXAMPLE
let cfg = handle.query_config()?;
if cfg.loop_ && cfg.finalized {
    println!("sealed looping clip, {} triggers", cfg.triggers.len());
}

ClipTrigger

One input binding that drives a clip

One physical-input binding for a clip: on a given Usage and Edge, run a ClipAction. You hand these to ClipHandle::bind; the box keeps up to 8, keyed by usage and edge. consume hides the triggering input from the PC.

Build one with ClipTrigger::new(usage, edge, action) (consume defaults false), then chain .consume() to swallow the input:

fn new(on: impl Into<Usage>, edge: Edge, action: ClipAction) -> ClipTrigger
FieldTypeMeaning
onUsageThe button, key, or media usage that fires the trigger.
edgeEdgeWhich edge fires it: Press, Release, or Both.
actionClipActionThe playback action to run (Start, Stop, Toggle, ...).
consumeboolSwallow the triggering input so the PC never sees it; the .consume() builder sets it true.
EXAMPLE
use medius::{Button, ClipAction, ClipTrigger, Edge};

// Toggle the clip on a Side1 press, and hide that press from the PC.
let trig = ClipTrigger::new(Button::Side1, Edge::Press, ClipAction::Toggle).consume();
handle.bind(trig)?;

ClipStatus

The buffered-clip ring and playback state

The clip ring depth and playback counters from ClipHandle::query_status(). Pace top-ups off free; a ClipState::Faulted state means re-sync (stop, then rebuild).

FieldTypeMeaning
stateClipStateThe lifecycle state (idle / playing / paused / faulted).
freeu32Free bytes in the ring, the headroom for the next append.
totalu32The retained clip size in bytes; while streaming, the buffered-but-undrained bytes.
playedu32Bytes played from the clip start (retained progress; ~0 while streaming).
ticksu32Content frames drained since the last start (gap runs are not counted).
underrunsu16Underrun episodes (the ring ran dry mid-playback).
overrunsu16Appends dropped because the ring was full.
seq_gapsu16Append-sequence gaps seen (a dropped append frame).
heldVec<Usage>The usages the clip is holding down, buttons, keys, and media in one list like a UsageSnapshot; test one with is_held(usage).