medius 3.4.6

Rust library for Medius, replacement firmware for the MAKCU box
Documentation
# 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.

| Item | Rule |
|---|---|
| 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:

| | Rust | C | Python |
|---|---|---|---|
| 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.

| Package | Where | How |
|---|---|---|
| `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 |