Medius - BindingsAPI index

API index

Every Python call, linked to what it does

The full Device surface, grouped. What each call does lives in the Rust Library and Native API. Types and enums are on Types & errors; streams on Streams.

Most calls are fire-and-forget. They return once the frame is queued. The query calls, plus Device.open / find, block for the box's reply. Any call raises a MediusError on failure.

Connecting & lifecycle

Open, share, and release the link

See Connection and Lifecycle.

CallDoes
Device.open(path)Open a serial path and handshake.
Device.find()Open the first box found, or raise NotFoundError.
dev.clone()Another handle to the same link; the connection is shared.
dev.close()Free the handle. Called automatically by a with block and on GC.
with Device.find() as dev:Context manager that closes the link on block exit.

Discovery

Enumerate boxes and open one by identity

Pick a box out of several by a stable identity (device MAC or CH343 serial), or by the kind of device it clones. See Discovery.

CallDoes
medius.list_boxes(cap=16)Enumerate every connected box as a BoxInfo (opens, handshakes, and reads each one's version + device info).
Device.open_by_id(id)Open the box whose identity matches id (device MAC hex or CH343 serial) and handshake.
Device.find_mouse_box()Open the first box whose clone is a mouse.
Device.find_keyboard_box()Open the first box whose clone is a keyboard.

Movement

Relative cursor and wheel

See Move. +x right, +y down.

CallDoes
dev.move_rel(dx, dy)Nudge the cursor by a signed 16-bit delta.
dev.wheel(delta)Scroll the wheel.
dev.move_rel_now(dx, dy)The same, bypassing movement riding.
dev.wheel_now(delta)Scroll, bypassing movement riding.
dev.flush_motion()Emit the motion riding is holding, now.
dev.discard_motion()Drop the motion riding is holding.
dev.move_axis(motion, timing, pending)Drive one axis from a Motion.cursor(dx, dy) or Motion.wheel(delta), with a MoveTiming and a PendingMotion.

Inject

Press and release any usage: button, key, or media

See Inject and the injection model (press / soft-release / force-release). One usage vocabulary drives every verb; build a Usage with Usage.button / key / media. Ids are on Usage IDs.

CallDoes
dev.inject(input, action)Apply an Action to a built Usage (button, key, or media usage).
dev.press(input)Hold a usage down (Action.PRESS).
dev.soft_release(input)Release, unless the user is physically holding it.
dev.force_release(input)Release even against a physical hold.

Every verb takes a Usage, so a button, key, and media usage inject the same way: dev.press(Usage.button(Button.LEFT)), dev.press(Usage.key(Key.W)), dev.press(Usage.media(MediaKey.MUTE)).

Locks

Weigh the user's own input

See Lock. Build axis/usage targets with LockTarget.x/y/wheel/usage (or the button/key/media shortcuts); a Direction picks a direction, and scale is the percent of the physical value kept (LOCK_SCALE_BLOCK 0, LOCK_SCALE_PASS 100, LOCK_SCALE_MAX 255).

CallDoes
dev.scale(target, direction, scale)Keep scale percent of an axis or usage (e.g. LockTarget.x(), LockTarget.key(Key.W)).
dev.scale_all(what, direction, scale)The same over a Blanket class (buttons, keys, media, aim, wheel).
dev.lock(target, direction)Block an axis or usage: scale 0.
dev.unlock(target, direction)Back to passing untouched: scale 100.
dev.lock_all(what, direction) / unlock_allBlanket block / release a whole class.

A scale auto-clears; it isn't permanent. The keepalive holds it for you. See Lock. Direction.WITH and Direction.AGAINST are measured against the bearing and need a live one; set one with dev.set_bearing(window_ms, mode); the refusal rules are on Direction.

LED, admin & options

Status light, resets, persistent settings
CallDoes
dev.led(target, mode, level)Drive the status LED. See LED.
dev.reset()Clear all overrides. See Admin.
dev.reapply()Re-send the active settings.
dev.reconnect()Force a reconnect to the mouse.
dev.reboot(target)Reboot a chip to run or download mode.
dev.allow_imperfect_clones(allow)Opt in to cloning over-capacity devices. See Options.
dev.set_movement_riding(window_ms)Set the riding window in ms, or None to turn it off.
dev.set_bearing(window_ms, mode)Set what Direction.WITH / AGAINST are measured against; None turns it off. mode is a BearingMode.
dev.set_emit_pace(pace, force_hz=None)Pick what paces injected motion (EmitPace.learned() / .interval() / .fixed(hz)) and what rate the clone advertises (force_hz, None = the device's own). See Options.
dev.set_name(name)Set the box's human-readable name (1 to 32 printable ASCII). See Name.
dev.clear_name()Clear the name, back to the synthesized default. Read it back on Version.name.

Queries

Read box state, each blocks for one reply

See Requests. Each blocks for the box's reply and returns a dataclass documented on Types & errors.

CallReturns
dev.query_version()Version: protocol + firmware version.
dev.query_health()Health: link, mouse, clone, injection flags.
dev.device_info()DeviceInfo: the cloned device's USB identity, kind, and product.
dev.caps()Caps: mouse/keyboard capabilities.
dev.query_rate()Rate: native report rate and poll period.
dev.query_stats()Stats: box-side telemetry.
dev.query_locks()Locks: every weighed direction (.entries, .scale_of(...), .is_locked(...)).
dev.query_bearing()Bearing: the bearing window and geometry.
dev.query_catch()CatchState: the live filter table (.entries, .table_full), drop counts, and the two chips' ClockEstimate.
dev.query_imperfect()ImperfectStatus: imperfect-clone state.
dev.query_movement_riding()int ms, or None when off.
dev.query_emit_pace()EmitPaceStatus: pacing mode, rate in effect, and the rate the clone advertises.
dev.firmware_info()FirmwareInfo: both chips' versions, slots, and what is staged.
dev.counters()Counters: host-side wire counters.

Firmware update

Write either chip over the open connection

See Firmware update. Staging blocks for the whole transfer and calls progress(sent, total) as windows are acknowledged; a refusal raises UpdateError.

CallDoes
dev.stage_firmware(target, image, progress=None)Write one image into that chip's spare slot, without booting it. target is an UpdateTarget.
dev.activate_firmware()Commit everything staged and boot into it, host chip first.
dev.abort_update(target)Throw a staged or in-flight transfer away.
dev.update_firmware(target, image, progress=None)Stage one image and activate it in a single call.

Streams

Subscribe to live input and logs

Consuming events is covered on Streams; the catch feature itself on Catch and Logs & counters.

CallReturns
dev.catch_events(filters)EventStream of the subscribed traffic: input, raw HID, vendor endpoints, control transactions, bus events.
dev.input_events(filters)InputStream of decoded press and release edges, and motion. Every filter must name an input class and cover both edges.
dev.logs()LogStream of device log lines.

filters takes one CatchFilter or an iterable of them, each naming a CatchClass and an id inside it, with an optional Direction and Capture.

The box's own refusals get no reply, so check what it actually holds with dev.query_catch().

Buffered clip playback

Preload a per-frame stream, box-clocked

Build a stream with ClipBuilder, then drive it with the ClipHandle from dev.clip(). Concept on Clip.

CLIPBUILDER
CallAppends
ClipBuilder() / .clear()A new builder (chainable); reset for reuse.
.gap(frames)A gap run (0 = no-op).
.move(dx, dy) / .wheel(dz)A cursor / wheel motion frame.
.press(usage) / .release(usage) / .force_release(usage)A one-edge press / soft-release / force-release frame; usage is a Usage (button, key, or media).
.edge(usage, action)A one-edge frame for any Usage with an explicit Action (default press).
.frame(dx, dy, wheel, edges)A motion delta plus up to 8 Usage / Action edges on one frame.
MOVE AND CLICK ON ONE FRAME
from medius import Action, Button, ClipBuilder, Usage

b = ClipBuilder()

# move (+10, -4) AND press Left on the same frame
b.frame(10, -4, 0, [(Usage.button(Button.LEFT), Action.PRESS)])
CLIPHANDLE
CallEffect
dev.clip()A ClipHandle (owns the append-seq counter).
clip.append(builder)Append the builder's entries to the ring.
clip.set_autolock(blankets)Set the auto-lock scope: a list of Blanket classes locked while the clip plays.
clip.set_loop(on) / clip.set_retain(on)Loop the ring on completion; retain entries after playback instead of flushing.
clip.set_ride(on)Make the clip's motion wait for a real move under movement riding (off = the box's own clock, the default).
clip.finalize()Fix a retained clip's end so it can replay and loop.
clip.bind(trigger)Bind a ClipTrigger: a physical Usage + Edge fires a ClipAction (up to 8).
clip.unbind(usage, edge) / clip.clear_triggers()Remove one bound trigger by usage + edge; drop all triggers.
clip.start() / clip.stop()Begin playback; stop and flush the ring, releasing the auto-lock.
clip.pause() / clip.resume()Halt playback in place; carry on from where it paused.
clip.restart() / clip.toggle()Replay from the first frame; start if idle else stop.
clip.clear()Drop the ring's entries.
clip.query_status()ClipStatus: ring depth, playback state, held usages, counters.
clip.query_config()ClipSettings: auto-lock, loop, retain, finalized, bound triggers.

Module functions

Top-level helpers on medius
CallDoes
medius.find_ports(cap=16)List present medius ports as PortInfo (now including the CH343 serial).
medius.list_boxes(cap=16)Enumerate every connected box as a BoxInfo. See Discovery.
medius.default_query_timeout_ms()The default query reply wait (1000 ms).
medius.default_keepalive_cadence_ms()The default keepalive interval (500 ms).
medius.abi_version()The C ABI version the library exposes.
medius.version_string()The library version string.