Blazing-fast cross-platform monitor casting for iced.
iced_live_cast is the crate you reach for when you want a live preview in an
iced app without routing frames through your application's message loop.
It gives you three layers:
CastViewfor actively rendering a live handle insideicedCastHandlefor callers that already own frames and want to push themMonitorCapturefor OS-backed monitor capture on macOS and Windows
The hot path stays small:
- sources push frames into a shared handle
- the widget owns redraw scheduling
- the renderer reuses one GPU texture per handle instead of rebuilding per frame
If you already have frames, use CastHandle.
If you want the OS to produce frames for you, use MonitorCapture.
If you just want something on screen, use CastView.
That is the whole pitch.
- an active
icedwidget with redraw scheduling - a reusable GPU texture per live handle
- typed source markers and typed runtime errors
- a BGRA upload path tuned for monitor capture
- macOS support through
screencapturekit - Windows support through
windows-capture
use iced::widget::container;
use iced::{Element, Length};
use iced_live_cast::{CastView, Monitor, MonitorCapture};
fn preview() -> Result<Element<'static, ()>, Box<dyn std::error::Error>> {
let monitor = Monitor::all()?
.into_iter()
.next()
.ok_or("no monitors are currently available")?;
let capture = MonitorCapture::start(monitor)?;
Ok(container(
CastView::new(&capture)
.width(Length::Fill)
.height(400),
)
.into())
}Add the crate normally:
iced_live_cast = "0.1.0"For the full story, run:
cargo run --example basicBy default, the example uses the first available monitor.
If you want, you can still pass an argument and the example will treat it as either a real monitor id or a one-based monitor index:
cargo run --example basic
cargo run --example basic -- 2That example uses the built-in monitor source.
If your frames come from somewhere other than the built-in monitor source, use
CastHandle directly:
use iced_live_cast::{CastHandle, Frame};
let handle = CastHandle::new();
let frame = Frame::from_bgra(1280, 720, vec![0; 1280 * 720 * 4])?;
handle.present(frame);You can then render the same handle with CastView::new(&handle).
Frame::to_handle() is available by default as a bridge into
iced::widget::image.
If you want a full running example for that path, run:
cargo run --example manual_pushAnd here is that manual source path running for real:
Quick local numbers from a short Criterion run on Apple silicon with 1080p frames:
| Benchmark | Size | Result |
|---|---|---|
frame_construction/from_rgba/packed |
1080p | about 1.52 ms |
frame_construction/from_bgra/packed |
1080p | about 475 µs |
frame_processing/rgba_pixels/packed_bgra |
1080p | about 328 µs |
frame_handles/frame_to_handle/full_frame |
1080p | about 370 µs |
cast_handle_updates/present_frame/prebuilt_bgra |
1080p | about 19 ns |
cast_handle_updates/construct_and_present/bgra |
1080p | about 469 µs |
The GPU upload bench is included too, but treat it as a renderer baseline rather
than a full end-to-end crate benchmark. It measures the same wgpu upload shape
the renderer uses, not the whole widget pipeline.
- macOS requires Screen Recording permission from the OS before monitor capture can start.
- The crate uses
screencapturekiton macOS andwindows-captureon Windows. - Linux support is not implemented yet.
Useful checks from the workspace root:
cargo check --lib --examples --benches
cargo test --lib
cargo bench --no-run