Injection model
A set of fields, two verbs, added on top of the user's inputA connected device is a set of fields: each is an Axis (relative motion: X, Y, wheel) or a Usage (a momentary button, key, or media control). MOVE drives an Axis, INJECT drives a Usage, and a Usage is one (class, id) shape for buttons, keys, and media alike.
| Device | Axes (MOVE) | Momentary (INJECT) |
|---|---|---|
| mouse | cursor X/Y, wheel | buttons |
| keyboard | none | keys, modifiers |
| media | none | volume, play/pause, ... |
Whatever you send is added on top of the user's own input, never replacing it:
physical input (real device) --+
+--> one combined report --> game PC
injected input (your program) --+| You send | The PC sees |
|---|---|
a MOVE while the real mouse moves | The sum of both. |
an INJECT press while the user holds nothing | The injected press. |
| nothing | Only the real device. |
Fire-and-forget
No per-command acknowledgementCommand frames get no echo and no acknowledgement, so you can stream input fast (up to about one command per millisecond). The exception is QUERY, which returns a RESP.
Correctness comes from three places, not per-command tracking:
| Mechanism | What it does |
|---|---|
| frame checksum | Drops corrupted frames. |
| safety rules | Keep a dropped command from leaving the box stuck. |
HEALTH | Reads the box's actual state. |
A lost movement frame costs one millisecond of motion; the next frame carries on.
What the box tracks
Pending motion and held usages| State | What it holds |
|---|---|
| accumulator | A running total of sent motion and scroll not yet delivered to the PC. Each MOVE, cursor or wheel, adds in; the box drains it into outgoing reports. |
| usage override | Per usage (button, key, or media), whether the box forces it active, forces it inactive, or leaves it to the real device. Set by the INJECT actions: press forces active, force-release forces inactive, soft-release hands it back. |
A report can only carry a limited movement size. A large injected move sends what fits and keeps the remainder in the accumulator for the next report. Nothing is clipped (total seen = total sent), just spread over as many reports as it takes.
When the box sends a report
At the mouse's own report rate, only on activity| When… | The box sends |
|---|---|
| the real mouse reported | The real movement plus whatever's drained from the accumulator, with buttons combining the physical state and your overrides. |
| the real mouse was still, but you have motion pending | A report carrying just the drained accumulator, paced to the mouse's own report rate (not one every millisecond). |
an INJECT or RESET changed a usage | One report reflecting the new state. |
Otherwise the box sends nothing, like a real mouse sitting idle. A held usage is a single report (the edge), then silence until it changes.
Safety
Injected state can't trap the real deviceA force-release always wins: it clears an injected hold and masks a physical press, so any held input can always be forced back to inactive.
The box also clears all injection if your program goes quiet, dropping every override and pending move and returning to plain passthrough. Any of these resets it:
| Trigger | What happens |
|---|---|
| silence timeout | No valid frame arrives within the timeout (default 1000 ms), so a crash while holding a button releases it a second later. |
| link drop | The link to the host chip drops. |
| mouse unplugged | The real mouse is detached, so there's nothing left to inject into. |
RESET | You send the reset command explicitly. |
To hold an injected button deliberately, keep the link busy: any valid frame resets the timer, so a periodic QUERY(HEALTH) suffices.
The medius library automates this: it sends keepalives while you hold something, and reconnects and re-applies your state if the link drops.