# librio `librio` is an embeddable terminal library extracted from Rio: the PTY and VT state core, usable from Swift, C, and anything that speaks C, without pulling in a windowing stack. It began as a research exercise studying two prior art decompositions, the libghostty split (a terminal-state library shipped first, renderer as a later peer) and [libvterm](https://www.leonerd.org.uk/code/libvterm/), then kept the parts that fit Rio's existing engine and dropped the rest. `librio` is a single Rust crate that wraps [`rio-vt`](../rio-vt), Rio's terminal core, and compiles to a C ABI. It is deliberately VT-only: PTY, VT state, and a host-pulled render-state API, with no renderer. ## Distribution `librio` is **not published to crates.io**. It lives in the Rio source tree and exists only to produce static artifacts: a `RioKit.xcframework` (static lib + curated header + `module.modulemap`, `module RioKit`) shipped in Rio's GitHub releases, built with `cargo build` then `lipo` and `xcodebuild -create-xcframework`. The C header is curated (cbindgen seeds it, never auto-committed). If you write Rust, depend on `rio-vt` directly; that is the published, idiomatic crate. `librio` is the C boundary for everyone else, and versions with Rio. ## Design Owns the PTY (`teletypewriter`), the VT machine (`Machine` / `performer`), and the grid (`Crosswords`). Knows nothing about pixels: the host pulls a render-state snapshot with per-row dirty flags (which the host resets) and draws it however it likes. This pull model is the permanent dogfood test: if our own renderer can be built on it, so can a third-party CPU renderer, a wasm canvas, a TUI proxy, or a test harness. The callback surface is deliberately small: `userdata` + a `wakeup_cb` + a single enum-tagged `action_cb` + clipboard and close-surface callbacks. Adding a new action never changes the ABI struct. Input is a per-surface set of entry points: key (returns a consumed bool), text, preedit (IME), mouse button/pos/scroll, set_size, set_content_scale, set_focus, set_occlusion. ### Rust API ```rust pub struct Engine { /* shared config, action fan-out */ } pub struct Surface { /* one PTY + grid; owns the IO thread */ } pub struct RenderState { /* snapshot + dirty rows */ } pub trait SurfaceDelegate: Send + Sync { fn wakeup(&self); // any thread; schedule only fn action(&self, surface_id: SurfaceId, action: Action); fn clipboard_write(&self, kind: ClipboardKind, text: &str); fn close_surface(&self, surface_id: SurfaceId); } pub enum Action { SetTitle(String), RingBell, ChildExited(i32), CursorShape(CursorShape), // grows freely } impl Surface { pub fn new(engine: &Engine, desc: SurfaceDesc) -> Result; pub fn key(&self, event: KeyEvent) -> bool; // true = consumed pub fn text(&self, s: &str); pub fn preedit(&self, s: &str); pub fn mouse_button(&self, ...); pub fn mouse_pos(&self, ...); pub fn scroll(&self, dx: f64, dy: f64); pub fn set_size(&self, cols: u16, rows: u16, px_w: u32, px_h: u32); pub fn set_content_scale(&self, scale: f64); pub fn set_focus(&self, focused: bool); pub fn set_occlusion(&self, occluded: bool); } impl RenderState { pub fn new(surface: &Surface) -> RenderState; pub fn update(&mut self); // snapshot under the terminal lock pub fn dims(&self) -> (u16, u16); pub fn row_dirty(&self, row: u16) -> bool; pub fn reset_dirty(&mut self); pub fn cell(&self, row: u16, col: u16) -> Cell; // materialized view pub fn row_cells(&self, row: u16) -> &[Cell]; pub fn cursor(&self) -> Cursor; pub fn selection(&self) -> Option; pub fn palette(&self) -> &Palette; } ``` ### C ABI ```c typedef struct rio_engine rio_engine_t; typedef struct rio_config rio_config_t; typedef struct rio_surface rio_surface_t; typedef struct rio_render_state rio_render_state_t; typedef enum { RIO_ACTION_SET_TITLE, RIO_ACTION_RING_BELL, RIO_ACTION_CHILD_EXITED, RIO_ACTION_CURSOR_SHAPE, } rio_action_tag_t; typedef struct { rio_action_tag_t tag; /* tagged union payload */ } rio_action_s; typedef struct { void *userdata; void (*wakeup_cb)(void *ud); /* any thread; schedule only */ void (*action_cb)(void *ud, rio_surface_t*, rio_action_s); void (*clipboard_write_cb)(void *ud, int kind, const char *utf8); void (*close_surface_cb)(void *ud, rio_surface_t*); } rio_runtime_config_s; rio_engine_t *rio_engine_new(const rio_runtime_config_s*, const rio_config_t*); void rio_engine_free(rio_engine_t*); rio_surface_t *rio_surface_new(rio_engine_t*, const rio_surface_config_s*); void rio_surface_free(rio_surface_t*); bool rio_surface_key(rio_surface_t*, rio_key_event_s); /* true = consumed */ void rio_surface_text(rio_surface_t*, const char*, uintptr_t); void rio_surface_preedit(rio_surface_t*, const char*, uintptr_t); void rio_surface_mouse_button(rio_surface_t*, rio_mouse_event_s); void rio_surface_mouse_pos(rio_surface_t*, double x, double y); void rio_surface_scroll(rio_surface_t*, double dx, double dy); void rio_surface_set_size(rio_surface_t*, uint16_t cols, uint16_t rows, uint32_t px_w, uint32_t px_h); void rio_surface_set_content_scale(rio_surface_t*, double); void rio_surface_set_focus(rio_surface_t*, bool); void rio_surface_set_occlusion(rio_surface_t*, bool); rio_render_state_t *rio_render_state_new(rio_surface_t*); void rio_render_state_free(rio_render_state_t*); void rio_render_state_update(rio_render_state_t*); bool rio_render_state_row_dirty(const rio_render_state_t*, uint16_t row); void rio_render_state_reset_dirty(rio_render_state_t*); void rio_render_state_dims(const rio_render_state_t*, uint16_t *cols, uint16_t *rows); const rio_cell_s *rio_render_state_row(const rio_render_state_t*, uint16_t row, uintptr_t *out_len); rio_cursor_s rio_render_state_cursor(const rio_render_state_t*); ``` Every `extern "C"` entry point wraps its body in `catch_unwind`, a Rust panic must never unwind into Swift/C (UB). Threading contract (identical to today's rioterm split): the surface owns one PTY IO thread; `wakeup_cb`/`action_cb` may fire on it and must only flag/schedule. All `render_state_*` calls and input calls are host-thread safe (input posts to the IO channel; render-state update takes the FairMutex).