Official Rust SDK for CopperlineOS (Phase‑0).
Typed clients and utilities for talking to CopperlineOS services over message ports (Unix domain sockets). Includes high‑level APIs for copperd (timeline), compositord (Vulkan/KMS compositor), blitterd (2D ops), and audiomixerd (audio graph), plus helpers for DMABUFs, events, and errors.
TL;DR: add one crate, get typed clients for
copperd
,compositord
,blitterd
, andaudiomixerd
—async or blocking—so you can script timelines, move pixels, and wire audio with minimal code.
- Targets Phase‑0 (Linux‑hosted) JSON protocol v0.
- Async and blocking clients available.
- License: MIT OR Apache‑2.0.
sdk-rs/
├─ copperline-sdk/ # main umbrella crate (re-exports sub-crates)
├─ crates/
│ ├─ ports/ # low-level port client (framing, JSON, FD passing)
│ ├─ copper/ # copperd client (load/start/stop, events)
│ ├─ compositor/ # compositord client (layers, regs, dmabuf bind)
│ ├─ blitter/ # blitterd client (rect_fill/copy/convert/batch)
│ └─ audio/ # audiomixerd client (nodes, connect, start/stop)
└─ examples/
├─ overlay_sprite.rs # layer + moving sprite via copper
├─ rect_fill.rs # use blitter to fill/copy
└─ tone_to_device.rs # audio tone → device graph
The copperline-sdk
crate re‑exports the most common types so users typically depend on just one crate.
Until published on crates.io, use a path dependency:
# Cargo.toml
[dependencies]
copperline-sdk = { path = "../sdk-rs/copperline-sdk", features = ["async"] }
# or for blocking clients:
# copperline-sdk = { path = "../sdk-rs/copperline-sdk", default-features = false, features = ["blocking"] }
When published on crates.io:
[dependencies]
copperline-sdk = { version = "0.1", features = ["async"] }
Runtime: async
uses tokio. blocking
has no async requirement.
async
(default) — async clients built on tokio.blocking
— blocking clients (no async runtime).dmabuf
— helpers for DMABUF FDs (format/stride/modifier helpers vianix
/libdrm
bindings).serde
— derive helpers for message payloads (enabled by default).
(async
and blocking
are mutually exclusive.)
Create a layer, bind an image, then animate it using Copper:
use copperline_sdk::{
compositor::AsyncCompositorClient,
copper::AsyncCopperClient,
};
use serde_json::json;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Connect to services
let mut comp = AsyncCompositorClient::connect("/run/copperline/compositord.sock").await?;
let mut cop = AsyncCopperClient::connect("/run/copperline/copperd.sock").await?;
// Create a layer and bind a debug image (raw RGBA8 128x128)
let layer = comp.create_layer().await?.id;
comp.bind_image(layer, "/tmp/sprite.rgba", 128, 128, "RGBA8").await?;
comp.set(layer)
.x(100).y(360).alpha(1.0).z(10).visible(true)
.apply().await?;
// Minimal Copper program: move right 4 px per vsync
let program = json!({
"version": 1,
"program": [
{ "op":"MOVE", "reg":"layer[1].x", "value":100 },
{ "op":"MOVE", "reg":"layer[1].y", "value":360 },
{ "op":"LOOP", "count":-1, "label":"loop" },
{ "label":"loop" },
{ "op":"WAIT", "vsync":true },
{ "op":"ADD", "reg":"layer[1].x", "delta":4 },
{ "op":"IRQ", "tag":"tick" },
{ "op":"JUMP", "label":"loop" }
]
});
let id = cop.load(program).await?.id;
cop.start(id).await?;
// Subscribe to IRQ events (optional)
let mut sub = cop.subscribe(&["irq"]).await?;
while let Some(ev) = sub.next().await {
println!("event: {:?}", ev?);
// break if you want to exit after N frames
break;
}
Ok(())
}
Run:
cargo run --example overlay_sprite
use copperline_sdk::compositor::BlockingCompositorClient as Compositor;
use copperline_sdk::copper::BlockingCopperClient as Copper;
use serde_json::json;
fn main() -> anyhow::Result<()> {
let mut comp = Compositor::connect("/run/copperline/compositord.sock")?;
let mut cop = Copper::connect("/run/copperline/copperd.sock")?;
let layer = comp.create_layer()?.id;
comp.bind_image(layer, "/tmp/sprite.rgba", 128, 128, "RGBA8")?;
comp.set(layer).x(100).y(360).alpha(1.0).z(10).visible(true).apply()?;
let program = json!({
"version":1,
"program":[ {"op":"WAIT","vsync":true} ]
});
let id = cop.load(program)?.id;
cop.start(id)?;
Ok(())
}
Build with:
cargo run --no-default-features --features blocking --example overlay_sprite
If you enable the dmabuf
feature, the SDK exposes small helpers to work with DMABUFs:
use copperline_sdk::dmabuf::{Format, SurfaceDesc};
let desc = SurfaceDesc::rgba8(1920, 1080).linear(); // stride, modifier helpers
You can pass the FD + SurfaceDesc
to compositor/blitter calls.
- All clients return
Result<T, Error>
whereError
wraps protocol errors and IO. - Idempotent calls (e.g.,
ping
,status
) may be retried by the helper methods with jittered backoff. - Subscriptions yield
Stream<Item=Result<Event>>
in async mode; read continuously to avoid backpressure drops.
The SDK enforces a protocol matrix at compile time:
SDK crate | Protocol |
---|---|
copperline-sdk 0.1.x |
ports protocol 0 (JSON/NDJSON) |
copperline-sdk 0.2.x |
planned: binary framing + JSON fallback |
Services report their protocol via {"cmd":"ping"}
; the SDK warns on mismatches.
rustup default stable
rustup component add rustfmt clippy
cargo fmt --all
cargo clippy --all-targets -- -D warnings
cargo test
Minimum Rust: 1.78+.
overlay_sprite.rs
– create a layer, move a sprite via copper.rect_fill.rs
– allocate/bind a buffer and fill/copy via blitter.tone_to_device.rs
– build a tiny audio graph and start playback.
Run all examples:
for e in overlay_sprite rect_fill tone_to_device; do
cargo run --example $e
done
- Open issues for API ergonomics or protocol coverage gaps.
- Keep examples compiling with both
async
andblocking
. - Public APIs use semver; avoid breaking changes in
0.x
without a deprecation path.
See CONTRIBUTING.md
and CODE_OF_CONDUCT.md
.
Dual-licensed under Apache‑2.0 OR MIT.
ports
: protocol spec + low-level clientscopperd
·compositord
·blitterd
·audiomixerd
examples