allow_imperfect_clonesClone anyway, and enable the advanced control layer

fn allow_imperfect_clones(&self, allow: bool) -> Result<()>

Fire-and-forget

By default the box refuses a real device it can't clone faithfully. true clones it anyway, as an imperfect clone. If such a device is attached, or a forced rate is pending, a change reboots the device chip to re-clone. Otherwise the clone re-presents only if the change alters the patch set it serves: turning the opt-in on with a stored set the box hasn't refused, or off while it serves a set.

ParameterTypeDescription
allowboolClone anyway, or stay faithful-only.
GATES
FeatureOpt-in off
a real device the box can't clone faithfullyRefused: no clone appears.
a forced rate, set_emit_pace's force_hzNot applied.
rewrite rulesset_rewrite returns ImperfectRequired. clear_rewrite still runs, and turning the opt-in off clears the table.
descriptor patchesStored, not applied. apply_patch returns ImperfectRequired.
control transfersReplied Refused.
raw reports, and a clip's raw and transfer itemsDropped by the box. Turning the opt-in off drops queued clip transfers.
a clip packet trigger that consumesRefused by the box. Turning the opt-in off removes held ones.

The box can't clone a real device faithfully if it: has >4 IN endpoints live at once or >6 HID interfaces; runs at high speed; has configurations the box didn't capture; has a vendor bulk or isochronous endpoint; has a setting carrying more endpoint descriptors than it declares (what fits is cloned, the rest left undriven); or has a report descriptor truncated or never captured.

A change that reboots the device chip or re-presents the clone drops the box's session state and a loaded clip. The library re-sends what it holds once the new clone is up (session recovery).

EXAMPLE
use medius::Device;

let device = Device::find()?;
device.allow_imperfect_clones(true)?;   // re-clones if the attached device needs the opt-in

set_movement_ridingInject motion only on a native move

fn set_movement_riding(&self, window: Option<Duration>) -> Result<()>

Fire-and-forget

Some(window) turns riding on. Injected cursor and wheel motion then ride a native cursor-motion report received within window, with no synthetic motion frame. Motion still unridden after the window is dropped, not emitted on the next move. None (the default) is off.

The window rounds to whole milliseconds and clamps to 65535 ms. A non-zero Some is ≥1 ms.

While riding is on, idle injection (motion while the mouse is still) needs move_rel_now. Button, key and media injection are unaffected.

A change drops motion held for a ride and clears the standing bearing, so every With / Against scale stops applying at once.

ParameterTypeDescription
windowOption<Duration>Some with the ride window, or None to turn it off.
EXAMPLE
use std::time::Duration;
use medius::Device;

let device = Device::find()?;
device.set_movement_riding(Some(Duration::from_millis(20)))?;  // ride native moves
device.set_movement_riding(None)?;                             // back to gapless fill

set_emit_paceInjected-motion pace and clone rate

fn set_emit_pace(&self, pace: EmitPace, force_hz: Option<u16>) -> Result<()>

Fire-and-forget

force_hz writes a bInterval onto every HID interrupt-IN endpoint the clone serves, and the box polls the real device at that interval. A mouse declaring 125 Hz can then run at 1 kHz. The rate resolves to a power-of-two bInterval: 1000/force_hz ms rounded to the nearest millisecond, then down to a power of two (600 Hz gives 500 Hz). A low-speed clone floors at bInterval 16 (62 Hz).

A forced rate applies only with allow_imperfect_clones on, because the descriptor stops matching the real device. Changing the resolved interval re-clones the box: the device chip reboots, and queries time out for a few seconds.

ParameterTypeDescription
paceEmitPaceRate ceiling for injected motion.
force_hzOption<u16>The rate the clone advertises and the box polls the real device at. None keeps the native bInterval.
EXAMPLE
use medius::{Device, EmitPace};

let device = Device::find()?;
device.set_emit_pace(EmitPace::Fixed(1000), None)?;   // a fixed 1 kHz ceiling
device.set_emit_pace(EmitPace::Learned, None)?;       // back to the default

set_nameHuman-readable box name

fn set_name(&self, name: &str) -> Result<()>

Fire-and-forget

Sets the box's name, the readable partner to its MAC. The firmware keeps the leading printable-ASCII run, capped at 32 bytes. An empty string clears the name. It reads back on Version::name.

ParameterTypeDescription
name&strThe new name, 1-32 printable ASCII characters.
EXAMPLE
use medius::Device;

let device = Device::find()?;
device.set_name("Loki")?;              // stored in NVS, reported on Version
let name = device.query_version()?.name;  // read it back off Version

clear_nameBack to the synthesised default

fn clear_name(&self) -> Result<()>

Fire-and-forget

Clears the custom name. The box reverts to the Medius-XXXX default derived from its MAC.

EXAMPLE
use medius::Device;

let device = Device::find()?;
device.clear_name()?;                  // back to "Medius-XXXX"

set_bearingWhat With and Against are measured against

fn set_bearing(&self, window: Option<Duration>, mode: BearingMode) -> Result<()>

Fire-and-forget

Sets the bearing: the direction the box is injecting. Direction::With and Direction::Against in scale weigh against it. Each axis holds the direction of its last injected delta until window passes with no injected motion still owed. Then the axis has no bearing, and both relative directions stop applying.

ParameterTypeDescription
windowOption<Duration>Some with the hold window, or None to turn the bearing off. With it off, the relative directions do nothing, whatever their scale.
modeBearingModePerAxis or Vector.

BEARING_WINDOW_DEFAULT (20 ms) in PerAxis is the factory setting.

Vector weighs a report twice. If Y negative is blocked while the injection runs diagonally, a purely horizontal flick can come out with its vertical share removed.

A change to either field drops the standing bearing and the box's banked fractions. While a relative scale is live, this shows as a visible step.

EXAMPLE
use std::time::Duration;
use medius::{Axis, BearingMode, Device, Direction};

let device = Device::find()?;
device.set_bearing(Some(Duration::from_millis(20)), BearingMode::PerAxis)?;
device.scale(Axis::X, Direction::Against, 40)?;  // motion opposing the injection, at 40%
device.set_bearing(None, BearingMode::PerAxis)?; // and off again

set_renderMotion texture

fn set_render(&self, mode: RenderMode, full: bool) -> Result<()>

Fire-and-forget

Off is the paced fill: one frame per emit-rate tick while injection is pending. The other three render motion through an ABCurves model (MIT) fitted live from native reports. They differ only in the smoother that feeds the model.

full picks what the model renders: injected motion alone, or injected and native cursor motion as one stream (the native delta is taken out of the relayed report). Buttons, the wheel and every other field stay relayed either way.

Rendering adds the smoother's group delay: 4 ms Stock, 2.7 ms Despiked, none Unsmoothed. With full on, native motion gets that delay +1 ms. move_rel_now, flush_motion and discard_motion skip the model and take the paced path. With full on, the rendered stream ignores movement riding.

The renderer stays out of the path until a profile arms, and while mode is Off. query_render reports both.

ParameterTypeDescription
modeRenderModeRendering texture. The box boots at Despiked.
fullboolRender native motion instead of relaying it. The box boots with it off.
EXAMPLE
use medius::{Device, RenderMode};

let device = Device::find()?;
device.set_render(RenderMode::Despiked, false)?;   // the default
device.set_render(RenderMode::Despiked, true)?;    // render native motion too
device.set_render(RenderMode::Off, false)?;        // renderer out of the path, the paced fill

set_spreadInjected-delta spread in time

fn set_spread(&self, percent: u16) -> Result<()>

Fire-and-forget

A command loop slower than the native report rate sends deltas worth several native reports. percent is the share of the command interval that the box spreads each delta across. 0 emits the whole delta on the next report. 100 spreads it evenly over one interval. Above 100 carries a standing backlog.

ParameterTypeDescription
percentu16Share of the command interval, in percent. The box boots at 100.

On average, spreading adds half the command interval of latency (~4 ms on a 125 Hz loop). The delivered total never changes. A loop at the native report rate keeps each command whole, alone on one report.

move_rel_now, flush_motion, discard_motion and wheel motion are never spread.

EXAMPLE
use medius::Device;

let device = Device::find()?;
device.set_spread(100)?;   // the default: one whole command interval
device.set_spread(50)?;    // half the interval, for half the added latency
device.set_spread(0)?;     // off: the whole delta on the next report

query_imperfectImperfect-clone state

fn query_imperfect(&self) -> Result<ImperfectStatus>

Blocks

Returns an ImperfectStatus: the opt-in toggle, whether the attached real device is over-capacity, and whether the live clone is imperfect.

EXAMPLE
use medius::Device;

let device = Device::find()?;
let status = device.query_imperfect()?;
if status.over_capacity && !status.allowed {
    // the device was refused; opt in to clone it imperfectly
    device.allow_imperfect_clones(true)?;
}

query_movement_ridingRide window

fn query_movement_riding(&self) -> Result<Option<Duration>>

Blocks

Returns the current ride window as a Duration, or None when movement riding is off.

EXAMPLE
use medius::Device;

let device = Device::find()?;
match device.query_movement_riding()? {
    Some(window) => println!("riding, window {window:?}"),
    None => println!("off"),
}

query_emit_pacePacing mode and clone rate

fn query_emit_pace(&self) -> Result<EmitPaceStatus>

Blocks

Returns an EmitPaceStatus with the pace and the rates. advertised_hz is the clone's current rate: the native rate while nothing is forced, otherwise the forced rate. It keeps no record of the native rate from before the force.

EXAMPLE
use medius::{Device, EmitPace};

let device = Device::find()?;
let status = device.query_emit_pace()?;
if let EmitPace::Fixed(hz) = status.mode {
    println!("fixed {hz} Hz, emitting at {} Hz", status.resolved_hz);
}
println!("the clone advertises {} Hz", status.advertised_hz);

query_bearingBearing window and geometry

fn query_bearing(&self) -> Result<Bearing>

Blocks

Returns the configured Bearing: the window and the mode.

EXAMPLE
use medius::Device;

let device = Device::find()?;
let bearing = device.query_bearing()?;
if bearing.is_live() {
    println!("{:?} over {:?}", bearing.mode, bearing.window);
}

query_renderTexture, and whether a profile armed

fn query_render(&self) -> Result<RenderStatus>

Blocks

Returns a RenderStatus. ready is false until a profile arms, and while the box's two chips haven't yet agreed which one holds the motion. Until then, motion is relayed and injection takes the paced fill, whatever mode says.

EXAMPLE
use medius::Device;

let device = Device::find()?;
let status = device.query_render()?;
println!("{:?}, native motion rendered: {}", status.mode, status.full);
if !status.ready {
    println!("move the mouse: nothing is rendered until a profile arms");
}

query_spreadPercent and interval in effect

fn query_spread(&self) -> Result<SpreadStatus>

Blocks

Returns a SpreadStatus. span_us is 0 in three cases: while percent is 0, until the box has learned the command interval, and while the box's two chips haven't yet agreed which one holds the motion. In each case the whole delta is emitted on the next report.

EXAMPLE
use medius::Device;

let device = Device::find()?;
let status = device.query_spread()?;
println!("{}% over {} us", status.percent, status.span_us);
if status.span_us == 0 {
    println!("send a few moves: the command interval is not learned yet");
}

On AsyncDevicesetters fire, queries await

AsyncDevice keeps the setters fire-and-forget (no await) and makes query_imperfect, query_movement_riding, query_bearing, query_emit_pace, query_render, and query_spread futures.

EXAMPLE
use std::time::Duration;
use medius::Device;

let device = Device::find()?.into_async();
device.set_movement_riding(Some(Duration::from_millis(20)))?;  // sync, no await
let window = device.query_movement_riding().await?;            // awaits