Files
punktfunk/clients/apple/README.md
T
enricobuehler 3ea096ace9
ci / rust (push) Has been cancelled
feat: M4 groundwork — lumen/1 client connector in the C ABI + SwiftUI client scaffold
The shared-core architecture pays off: platform clients now link ONE Rust library that
does the entire lumen/1 protocol, and only add decode/present/input on top.

lumen-core:
- client.rs (quic feature): NativeClient — QUIC handshake + UDP data plane + input
  datagrams on internal threads; embedder surface = connect / next_frame / send_input.
- abi.rs: lumen_connect / lumen_connection_next_au (borrow-until-next-call, matching
  lumen_client_poll_frame semantics) / lumen_connection_send_input / lumen_connection_mode /
  lumen_connection_close. Guarded in the generated header by LUMEN_FEATURE_QUIC (cbindgen
  [defines] mapping), so the checked-in header is stable across feature sets.
- error.rs: append-only LumenStatus additions Timeout (-9) and Closed (-10).
- TESTED end-to-end through the C ABI: in-process lumen/1 host, lumen_connect pulls 25
  byte-verified frames, sends input, closes (m3.rs::c_abi_connection_roundtrip).

Apple client (clients/apple — SCAFFOLD, written on Linux, first Xcode build pending):
- scripts/build-xcframework.sh: cargo per Apple target → universal staticlib + header
  (LUMEN_FEATURE_QUIC pre-defined) + modulemap → LumenCore.xcframework.
- Package.swift (LumenKit) + Swift sources: LumenConnection (ABI wrapper), AnnexB
  (in-band VPS/SPS/PPS → CMVideoFormatDescription, Annex-B → AVCC CMSampleBuffers with
  DisplayImmediately), StreamView (SwiftUI over AVSampleBufferDisplayLayer — stage-1
  presenter that hardware-decodes compressed HEVC itself), InputCapture (GCMouse raw
  deltas + GCKeyboard HID→VK).
- README.md is the full handoff for the next (Mac-side) agent: build steps, ABI contract,
  first-light test recipe against the Linux host, stage-2 (VT+Metal pacing) plan, and the
  known host-side gaps (single-session m3-host, no lumen/1 audio yet, gamepad kinds not
  yet routed in m3's injector, seed-stage trust).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 07:28:41 +00:00

100 lines
5.5 KiB
Markdown

# lumen Apple client (SwiftUI) — handoff
The native macOS/iOS client for **`lumen/1`** (the post-GameStream protocol). All
networking/protocol work — QUIC control plane, UDP data plane, GF(2¹⁶) FEC, AES-GCM,
input datagrams — lives in the shared Rust core and is **done and tested**; this package
is the Swift shell: decode (VideoToolbox), present (SwiftUI), input capture.
## What exists (built + tested on the Linux host)
- **The connector**: `lumen_core::client::NativeClient` (Rust) exposed over the C ABI as
`lumen_connect` / `lumen_connection_next_au` / `lumen_connection_send_input` /
`lumen_connection_mode` / `lumen_connection_close` (see `include/lumen_core.h`, guarded
by `LUMEN_FEATURE_QUIC`). **End-to-end tested through the C ABI** against an in-process
host (`crates/lumen-host/src/m3.rs::tests::c_abi_connection_roundtrip`).
- **The host to test against**: `lumen-host m3-host --source virtual --seconds 60` on the
Linux box (it creates a native virtual output at whatever mode the client requests and
streams HEVC; `LUMEN_COMPOSITOR=gamescope LUMEN_GAMESCOPE_APP=vkcube` for moving content).
- **This package (SCAFFOLD — written on Linux, never compiled in Xcode)**:
- `LumenConnection.swift` — Swift wrapper over the C ABI (AUs copied into `Data`).
- `AnnexB.swift` — in-band VPS/SPS/PPS → `CMVideoFormatDescription`; Annex-B → AVCC
`CMSampleBuffer` with `DisplayImmediately` set.
- `StreamView.swift` — SwiftUI `NSViewRepresentable` over `AVSampleBufferDisplayLayer`
(stage-1 presenter: the layer hardware-decodes compressed HEVC itself).
- `InputCapture.swift``GCMouse` raw deltas + `GCKeyboard` HID→VK mapping →
`lumen_connection_send_input`.
## Build steps (on the Mac)
```sh
rustup target add aarch64-apple-darwin x86_64-apple-darwin
bash scripts/build-xcframework.sh # → clients/apple/LumenCore.xcframework
open clients/apple/Package.swift # or add the package to an Xcode app project
```
Minimal app around it:
```swift
@main struct LumenApp: App {
var body: some Scene { WindowGroup { ContentView() } }
}
struct ContentView: View {
@State private var conn: LumenConnection?
var body: some View {
if let conn {
StreamView(connection: conn)
.onAppear { InputCapture(connection: conn).start() }
} else {
Button("Connect") {
conn = try? LumenConnection(
host: "192.168.1.70", width: 2560, height: 1440, refreshHz: 120)
}
}
}
}
```
## Handoff — what the next agent needs to know
1. **Expect small compile fixes.** Every Swift file is flagged SCAFFOLD: API-checked from
documentation, never run through Xcode. Likely friction: the imported C enum spellings
(`LUMEN_STATUS_OK` etc. — cbindgen emits `QualifiedScreamingSnakeCase`), `LumenFrame()`
zero-init, `_pad` tuple shape on `LumenInputEvent`.
2. **ABI contract** (matches `lumen_core.h` docs): `next_au`'s pointer is valid only until
the *next* call on that handle (we copy to `Data` immediately); one pump thread per
connection; `send_input` is enqueue-only and thread-safe alongside it; `close` joins the
Rust threads — never call it with a `next_au` call in flight.
3. **Decode flow**: the host opens every stream with an IDR carrying VPS/SPS/PPS in-band,
and recovery keyframes re-send them — so "wait for the first format description, refresh
it on every IDR" (already what `StreamView` does) is sufficient; there is no out-of-band
extradata, ever.
4. **First-light test**: Linux box runs
`PATH=/tmp/gamescope-src/build/src:$PATH LUMEN_COMPOSITOR=gamescope \
LUMEN_GAMESCOPE_APP=vkcube LUMEN_ZEROCOPY=1 cargo run -rp lumen-host -- m3-host
--source virtual --seconds 120`; Mac connects with the app. Success = the spinning
vkcube on glass. Then mouse/keys should appear inside the gamescope session (verify
with `LUMEN_GAMESCOPE_APP=xev` and the box-side log `/tmp/lumen-gamescope.log`).
5. **Stage 2 (after first light)**: replace `AVSampleBufferDisplayLayer` with explicit
`VTDecompressionSession` + `CAMetalLayer` for frame-pacing control (ProMotion/120 Hz),
and add glass-to-glass measurement (`tools/latency-probe` is the scaffold; the host
already stamps `pts_ns` with its capture wall clock — across machines you'll need a
clock-offset estimate from the QUIC RTT, or the probe's visual timestamp loop).
6. **Gamepads**: `GCController``GamepadButton`/`GamepadAxis` `LumenInputEvent`s. The
host does NOT yet route those kinds in `m3.rs`'s injector path (mouse/keys work; the
gamepad kinds need a `GamepadManager` hookup like the GameStream control stream has —
small host-side task).
7. **Trust model is seed-stage**: the client accepts any host certificate
(`endpoint::client_insecure`). Pairing + pinning is a planned lumen-core task; design it
alongside this client's "add host" UX.
8. **iOS**: same package (`BUILD_IOS=1` for the xcframework slice); `StreamView` needs the
`UIViewRepresentable` twin and touch→input mapping.
## Known limitations of the current host (relevant to client UX)
- `m3-host` serves **one session and exits** — fine for development; the persistent
lumen/1 listener (serve-style) is a small host-side task.
- No audio on lumen/1 yet (the GameStream path has it; porting the Opus stream onto a
second datagram flow is straightforward).
- Mid-stream renegotiation (resolution change without reconnect) is designed-for but not
implemented (the Welcome is one-shot today).