# medius bindings
`medius-capi` wraps the Rust library, [`medius`](../README.md), in a C API. Its
header compiles as C and C++, and the Python package loads it.
```
medius (safe Rust crate)
└── medius-capi extern "C" + cbindgen → include/medius.h, libmedius_capi.{so,a}
├── C / C++ include the header, link the library
└── Python bindings/python ctypes package (pip install)
```
## The C ABI
Build the library and use the generated header:
```sh
cargo build -p medius-capi --release # target/release/libmedius_capi.{so,a}
cargo build -p medius-capi --release --features mock # the mock box, for tests
```
The header is `medius-capi/include/medius.h`, generated by `tools/gen-header.sh`
(cbindgen) and committed. It compiles as C99, C23 and C++. The mock functions are
under `#if defined(MEDIUS_FEATURE_MOCK)`; define it for a library built with the
`mock` feature.
| Errors | Every fallible call returns a `MediusStatus` (`MEDIUS_STATUS_OK` is 0) and writes its result through an out-param |
| Error text | `medius_last_error_message()`: the last failure on the calling thread |
| Handles | Opaque pointers, each freed with its `*_free` |
| Threads | The header's first comment lists the handles that take calls from several threads. A stream's `*_close` ends a receive blocked on it in another thread |
| Events | Catch events and log lines are fixed-size structs, with nothing to free |
At start-up, compare `medius_abi_version()` (the library) with `MEDIUS_ABI_VERSION`
(the header). On a mismatch, call nothing else and rebuild against the header that
came with the library.
```c
#include <medius.h>
if (medius_abi_version() != MEDIUS_ABI_VERSION) { /* rebuild against the library's header */ }
MediusDevice *dev = NULL;
if (medius_device_find(&dev) != MEDIUS_STATUS_OK) { /* medius_last_error_message(...) */ }
MediusVersion v;
medius_device_query_version(dev, &v);
medius_device_move_rel(dev, 100, -50);
medius_device_press(dev, medius_usage_button(MEDIUS_BUTTON_LEFT));
medius_device_reset(dev);
medius_device_free(dev);
```
C++ uses the same header and library; there is no C++ wrapper.
## Python
A ctypes package with no runtime dependencies. See [`bindings/python`](python/).
```sh
pip install medius # a prebuilt wheel, library included
pip install ./bindings/python # from this checkout, which needs a Rust toolchain
```
```python
import medius
with medius.Device.find() as dev:
print(dev.query_version())
dev.move_rel(100, -50)
dev.press(medius.Usage.button(medius.Button.LEFT))
with dev.input_events(medius.CatchFilter.all_input()) as events:
for event in events:
...
```
To load a local build, set `MEDIUS_LIB` to it (for example
`target/debug/libmedius_capi.so`; the tests need one built with `--features mock`).
`import medius` raises `ImportError` when the library's ABI version differs from the
package's.
## Differences from the Rust API
The bindings cover the whole API. They differ from Rust in three places:
| Errors | `Result` | a `MediusStatus`, detail in `medius_last_error_message()` | raises `MediusError` |
| Usages | any `impl Into<Usage>`: `press(Button::LEFT)` | a built value: `medius_usage_button(...)` | a built value: `Usage.button(...)` |
| Async | `AsyncDevice` (feature `async`) | none: use a thread, or `try_recv` / `recv_timeout` | none: `asyncio.to_thread`, or `try_recv` / `recv_timeout` |
The direct verbs (`press`, `force_release`, `move_rel`, …) keep their names, except Rust's `release`,
which is `soft_release` in C and Python.
## Packages
A release is a new version in `Cargo.toml`, `medius-capi/Cargo.toml` (and its pin on
`medius`) and `bindings/python/pyproject.toml`, pushed to master. CI fails if the
four disagree, and publishes only a version crates.io doesn't have yet.
| `medius`, `medius-capi` | crates.io | The `publish` job, `medius` first. A Rust library that exports a C API depends on `medius-capi` and hands a `medius::Device` to C with `MediusDevice::into_raw` |
| `medius` (Python) | PyPI | Wheels and sdist, through trusted publishing (no token): the project trusts `ci.yml` in `K4HVH/medius`, environment `pypi` |
| `medius-capi-<target>.tar.gz` | GitHub release | One per platform: `include/medius.h` and `libmedius_capi`, shared and static |