<!-- Source: https://medius.k4tech.net/bindings/c/streams -->
# Streams

_Read live input and logs in C_

Two live channels from [the box](/native/hardware.md): physical input ([Catch](/library/catch.md)) and device log lines ([Logs & counters](/library/diagnostics.md)). Subscribe, then pull fixed-size [POD](https://en.cppreference.com/w/cpp/named_req/PODType) events off the handle.

```
  medius_device_catch_events(dev, mask, &stream)    ──  subscribe to live input
  medius_device_logs(dev, &stream)                  ──  subscribe to log lines
          │
          ▼   a background reader thread fills a host-side queue
  medius_event_stream_recv(stream, &event)          ──  pull one (blocks)
  medius_log_stream_recv(stream, &line)             ──  the same, for logs
          │
          ▼   loop until MEDIUS_STATUS_ERR_DISCONNECTED
  medius_event_stream_free(stream)                  ──  unsubscribe
  medius_log_stream_free(stream)                    ──  the same, for logs
```

## Subscribe

_Open an event or log stream_

Both return a [`MediusStatus`](/bindings/c/types.md#errors) and write an opaque handle through an out-param. Pick the input classes with a [`MediusCatchMask`](/bindings/c/types.md#catch-mask).

```text
MediusStatus medius_device_catch_events(struct MediusDevice *dev, MediusCatchMask mask, struct MediusEventStream **out); MediusStatus medius_device_logs(struct MediusDevice *dev, struct MediusLogStream **out);
```

| Call | Does |
| --- | --- |
| `medius_device_catch_events(dev, mask, &out)` | Subscribe to physical input for the class `mask`. See [Catch](/library/catch.md). |
| `medius_device_logs(dev, &out)` | Open the device LOG channel. See [Logs](/library/diagnostics.md). |
| `medius_event_stream_clone(stream)` / `medius_log_stream_clone(stream)` | Another handle to the same subscription. Null in → null out. |
| `medius_event_stream_free(stream)` / `medius_log_stream_free(stream)` | Release a handle (unsubscribes when the last clone drops). Null is a no-op. |

#### CATCH MASK BITS

| Constant | Value | Class |
| --- | --- | --- |
| `MEDIUS_CATCH_MASK_MOTION` | `1` | cursor motion (dx / dy) |
| `MEDIUS_CATCH_MASK_WHEEL` | `2` | wheel |
| `MEDIUS_CATCH_MASK_BUTTONS` | `4` | mouse buttons |
| `MEDIUS_CATCH_MASK_KEYS` | `8` | keyboard keys |
| `MEDIUS_CATCH_MASK_MEDIA` | `16` | media keys |
| `MEDIUS_CATCH_MASK_ALL` | `31` | everything (`1 \| 2 \| 4 \| 8 \| 16`) |

## Receive

_Pull one event off the queue_

There's no iterator. Loop a receive call until it returns `MEDIUS_STATUS_ERR_DISCONNECTED` (the stream closes after a [reset](/library/admin.md) or [link loss](/library/connection.md)). Each writes one event through `*out`.

| Function | Returns | Blocks? |
| --- | --- | --- |
| `medius_event_stream_recv(stream, &out)` | `MediusStatus` (`MEDIUS_STATUS_ERR_DISCONNECTED` on close) | Yes, until the next event |
| `medius_event_stream_try_recv(stream, &out)` | `bool` (`false` if the queue is empty) | No, returns at once |
| `medius_event_stream_recv_timeout(stream, timeout_ms, &out)` | `bool` (`false` on timeout or close) | Up to `timeout_ms` |

#### LOGS MIRROR THIS

| Function | Returns |
| --- | --- |
| `medius_log_stream_recv(stream, &out)` | `MediusStatus` (`MEDIUS_STATUS_ERR_DISCONNECTED` on close) |
| `medius_log_stream_try_recv(stream, &out)` | `bool` (`false` if none queued) |
| `medius_log_stream_recv_timeout(stream, timeout_ms, &out)` | `bool` (`false` on timeout or close) |

## Event objects

_Fixed-size PODs, nothing to free per event_

`medius_event_stream_recv` fills a [`MediusCatchEvent`](/bindings/c/types.md#catch-event): a `kind` tag plus a [union](https://en.cppreference.com/w/c/language/union). Read the union arm that matches `kind`. The usage list is length-prefixed by an inline `n` count; the backing array is fixed at [`MEDIUS_MAX_USAGES`](/bindings/c/types.md#capacities) (256), so nothing truncates. It holds class-tagged [`MediusUsage`](/bindings/c/types.md#input) usages (a button, key, or media [HID usage](https://www.usb.org/document-library/hid-usage-tables-14)).

```c
typedef struct MediusCatchEvent {
    MediusCatchEventKind kind;          // MOTION=0, USAGES=1
    union MediusCatchEventData data;    // read the arm for kind
} MediusCatchEvent;

struct MediusMotionEvent { int16_t dx, dy, dz; };                     // cursor + wheel deltas
struct MediusUsageEvent  { uint16_t n; MediusUsage usages[256]; };    // class-tagged held usages

typedef struct MediusLogLine {          // from medius_log_stream_recv
    MediusLogLevel level;               // ERROR=0, WARN=1, INFO=2, DEBUG=3, VERBOSE=4
    char text[512];                     // NUL-terminated
} MediusLogLine;
```

| When `kind` is | Read | Fields |
| --- | --- | --- |
| `MEDIUS_CATCH_EVENT_KIND_MOTION` | `data.motion` | `dx`, `dy`, `dz` (cursor and wheel deltas) |
| `MEDIUS_CATCH_EVENT_KIND_USAGES` | `data.usages` | `usages[0..n]`, each a class-tagged `MediusUsage` |

#### INSPECTORS

| Helper | Does |
| --- | --- |
| `medius_usage_event_is_held(&ev.data.usages, usage)` | `bool`: true if that `MediusUsage` usage (button, key, or media) is held. |
| `medius_event_stream_dropped(stream)` | `uint64_t`: events dropped because the consumer fell behind (host-side back-pressure). |

## Consume loop

_Subscribe, drain until disconnect, free_

```c
#include <medius.h>
#include <stdio.h>

MediusEventStream *events = NULL;
if (medius_device_catch_events(dev, MEDIUS_CATCH_MASK_ALL, &events) != MEDIUS_STATUS_OK) {
    char msg[256];
    medius_last_error_message(msg, sizeof msg);
    fprintf(stderr, "subscribe failed: %s\n", msg);
    return 1;
}

MediusCatchEvent ev;
while (medius_event_stream_recv(events, &ev) == MEDIUS_STATUS_OK) {
    switch (ev.kind) {
    case MEDIUS_CATCH_EVENT_KIND_MOTION:
        printf("motion dx=%d dy=%d dz=%d\n",
               ev.data.motion.dx, ev.data.motion.dy, ev.data.motion.dz);
        break;
    case MEDIUS_CATCH_EVENT_KIND_USAGES:
        printf("held usages=%u  LMB=%d  W=%d\n", ev.data.usages.n,
               medius_usage_event_is_held(&ev.data.usages, medius_usage_button(MEDIUS_BUTTON_LEFT)),
               medius_usage_event_is_held(&ev.data.usages, medius_usage_key(MEDIUS_KEY_W)));
        break;
    }
}
/* recv returned MEDIUS_STATUS_ERR_DISCONNECTED: the box reset or the link dropped */
printf("dropped while behind: %llu\n",
       (unsigned long long)medius_event_stream_dropped(events));
medius_event_stream_free(events);
```

## No async

_Build it on the non-blocking receives_

> **Warning**
>
> The [C ABI](/bindings/c.md) is synchronous; there's no [async](/library/features/async.md) API. For a single-threaded event loop, poll with `medius_event_stream_try_recv` or block with a budget using `medius_event_stream_recv_timeout`; or run the blocking `recv` loop on its own thread. A stream handle is clonable, so each thread can hold its own.
