Calls & errors
What each call does is in the Rust Library and Native API sections. The API index lists every call; Types & errors every struct and enum.
Fire-and-forget vs blocking Call-kind badges
Both kinds return a MediusStatus; the API index badges each call.
| Badge | Behaviour | Fails with |
|---|---|---|
| Fire-and-forget | Returns once the frame is queued, without awaiting a reply. Move, inject, lock, LED, options, clip playback. See fire-and-forget. | MEDIUS_STATUS_ERR_IO / MEDIUS_STATUS_ERR_DISCONNECTED if the link is down. |
| Blocks | Sends a QUERY and waits for the box's RESP, up to the query timeout. The medius_device_query_* reads and the open/handshake calls. See Requests. | MEDIUS_STATUS_ERR_NO_REPLY / MEDIUS_STATUS_ERR_QUERY_TIMEOUT. |
MEDIUS_STATUS_OK from a Fire-and-forget call means the writer took the frame, not that the box acted on it. Default reply wait: medius_default_query_timeout_ms(); held-override keepalive cadence: medius_default_keepalive_cadence_ms() (see keepalive).
ErrorsMediusStatus + a thread-local last error
Every fallible call returns a MediusStatus and writes its result through an out-param. MEDIUS_STATUS_OK is 0; on anything else the out-param is untouched. Types & errors lists every enumerator; Errors is the canonical mapping.
call ──▶ MediusStatus
│
├─ == OK ──▶ the out-param is valid, carry on
└─ != OK ──▶ medius_last_error_message(buf, cap) text (this thread)
medius_last_error_proto_ver() byte (BadProtoVer only)
medius_last_error_update(&out) op + status (ErrUpdate only)uintptr_t medius_last_error_message(char *buf, uintptr_t cap); uint8_t medius_last_error_proto_ver(void); bool medius_last_error_update(MediusUpdateError *out);
MediusDevice *dev = NULL;
if (medius_device_find(&dev) != MEDIUS_STATUS_OK) {
char buf[256];
medius_last_error_message(buf, sizeof buf); /* NUL-terminated, truncated to cap */
fprintf(stderr, "open failed: %s\n", buf);
return 1;
}medius_last_error_message returns the full message length in bytes (excluding the NUL); retry with a larger buffer on truncation. medius_last_error_proto_ver returns the offending version byte after a BadProtoVer, else 0. medius_last_error_update writes the refused update op and status (MediusUpdateError) after MEDIUS_STATUS_ERR_UPDATE, else returns false.
The last error is thread-local and overwritten by the next medius_* call on that thread; read it right after the failing call.
A device call returns MEDIUS_STATUS_ERR_DISCONNECTED once the link drops. A stream's blocking recv returns it when the stream closes (after a reset, link loss or medius_*_stream_close). Recover with medius_device_reconnect or by re-opening (see Lifecycle).
LifecycleOpaque pointers, manual free, no RAII or GC
Handles are opaque, reference-counted pointers with no destructor or RAII. medius_*_clone adds an owner of the same object; call the matching medius_*_free on every handle. See Connection and Lifecycle.
medius_device_open / _find ──▶ MediusDevice * (you own it)
│ medius_device_clone
▼
MediusDevice * (2nd owner, same link)
medius_device_free(handle) ──▶ drop one owner (NULL = no-op)
free the last owner ──▶ joins the reader + keepalive threads| Handle | Create | Clone | Free |
|---|---|---|---|
MediusDevice | medius_device_open · _find · _with_mock | medius_device_clone | medius_device_free |
MediusEventStream | medius_device_catch_events | medius_event_stream_clone | medius_event_stream_free |
MediusInputStream | medius_device_input_events | - | medius_input_stream_free |
MediusTimeline | medius_timeline_new | - | medius_timeline_free |
MediusLogStream | medius_device_logs | medius_log_stream_clone | medius_log_stream_free |
MediusMockBox | medius_mock_new | medius_mock_clone | medius_mock_free |
MediusClipBuilder | medius_clip_builder_new | - | medius_clip_builder_free |
MediusClipFrame | medius_clip_frame_new | - | medius_clip_frame_free |
MediusClip | medius_device_clip | - | medius_clip_free |
MediusDevice *dev = NULL;
if (medius_device_find(&dev) != MEDIUS_STATUS_OK) { return 1; }
MediusDevice *worker = medius_device_clone(dev); /* same link, ref-counted */
/* ... use either handle from either thread ... */
medius_device_free(worker); /* drop one owner */
medius_device_free(dev); /* last owner -> joins the background threads */A clip builder, clip frame or timeline takes calls from one thread at a time. Every other handle takes calls from several threads at once. Free a handle only when no other thread is using it. To stop a thread blocked in a receive: close the stream, let the receive return, then free the stream.
clone(NULL) returns NULL and every *_free(NULL) is a no-op, so cleanup needs no null checks. A stream unsubscribes when its last handle is freed, or at once on medius_*_stream_close.
Catch events and log lines are fixed-size structs written into your buffer, with nothing to free. A MediusTrafficEvent holds captured bytes inline (bytes[MEDIUS_MAX_TRAFFIC_BYTES] plus len), so a copy owns nothing and outlives its stream.
Building targets Usage, Motion, LockTarget, CatchFilter for the generic verbs
Rust's generic inject / move_axis / lock targets are structs in C (MediusUsage, MediusMotion, MediusLockTarget, plus MediusCatchFilter), each with helper constructors.
A MediusUsage holds a button id, keycode, or Consumer usage; one value serves inject, lock and catch.
| Builder | Returns | For |
|---|---|---|
medius_usage_button(MediusButton) | MediusUsage | inject / injection model |
medius_usage_key(MediusKey) | MediusUsage | |
medius_usage_media(MediusMediaKey) | MediusUsage | |
medius_motion_cursor(dx, dy) | MediusMotion | move / MOVE |
medius_motion_wheel(delta) | MediusMotion | |
medius_lock_target_axis(MediusLockTargetKind) | MediusLockTarget | lock / LOCK |
medius_lock_target_usage(MediusUsage) | MediusLockTarget | |
medius_catch_filter_watch(MediusUsage) | MediusCatchFilter | catch / all helpers |
medius_catch_filter_watch_axis(MediusAxis) | MediusCatchFilter | |
medius_catch_filter_traffic(MediusCatchClass, uint16_t) | MediusCatchFilter |
/* inject: build a usage, then apply an Action */
MediusUsage lmb = medius_usage_button(MEDIUS_BUTTON_LEFT);
medius_device_inject(dev, lmb, MEDIUS_ACTION_PRESS);
medius_device_press(dev, medius_usage_key(MEDIUS_KEY_W)); /* keys and media inject the same way */
/* move: build a motion arm */
MediusMotion m = medius_motion_cursor(100, -50);
medius_device_move_axis(dev, m, MEDIUS_MOVE_TIMING_RIDE, MEDIUS_PENDING_MOTION_KEEP);
/* lock: an axis, or any usage */
MediusLockTarget x = medius_lock_target_axis(MEDIUS_LOCK_TARGET_KIND_X);
medius_device_lock(dev, x, MEDIUS_DIRECTION_BOTH);
MediusLockTarget side = medius_lock_target_usage(medius_usage_button(MEDIUS_BUTTON_SIDE1));
medius_device_lock(dev, side, MEDIUS_DIRECTION_BOTH);Buttons, keys and media lock the same way: medius_lock_target_usage(medius_usage_key(...)) locks a key, medius_lock_target_axis(...) an axis or the wheel. Fields: Types & errors.
A catch filter starts from a base that names what to observe; each modifier returns a narrowed copy, so one blanket can seed several entries. Fields: Types & errors.
/* every key edge, whole report */
MediusCatchFilter keys = medius_catch_filter_watch_class(MEDIUS_CLASS_KEY);
/* one vendor endpoint's IN packets, first 16 bytes each */
MediusCatchFilter ep = medius_catch_filter_with_capture(
medius_catch_filter_inbound(
medius_catch_filter_traffic(MEDIUS_CATCH_CLASS_VENDOR_INTERRUPT, 0x83)),
16);
MediusCatchFilter filters[2] = { keys, ep };
MediusEventStream *events = NULL;
medius_device_catch_events(dev, filters, 2, &events); /* the array is not retained */