Skip to Content
PlatformsTTY backend

TTY backend (ftui-tty)

ftui-tty is the native Unix implementation of the ftui-backend traits. It owns raw-mode lifecycle, reads bytes from the tty file descriptor, drives the ftui-core InputParser, and emits escape sequences through a TtyPresenter. It is what Program::with_native_backend uses when the native-backend cargo feature is on.

File: crates/ftui-tty/src/lib.rs.

What it is (and isn’t)

ftui-tty replaces Crossterm as FrankenTUI’s I/O layer on Unix-like systems (Linux, macOS, BSDs). It is:

  • The only crate that calls tcsetattr, writes to stdout, or installs SIGWINCH handlers.
  • A three-trait implementation: BackendClock, BackendEventSource, BackendPresenter.
  • A hard RAII boundary — drop always restores the terminal, even on panic, unless you compile with panic = "abort".

It is not:

  • A cross-platform shim. Windows is currently deferred; the Crossterm compat layer (features = ["crossterm-compat"]) remains available.
  • A source of events for widgets. The runtime consumes Events produced here and dispatches them into Model::update.

Structure

crates/ftui-tty/src/lib.rs
pub struct TtyBackend { clock: TtyClock, events: TtyEventSource, presenter: TtyPresenter, alt_screen_active: bool, raw_mode: Option<RawModeGuard>, // Some == live, None == headless } impl TtyBackend { pub fn new(width: u16, height: u16) -> Self; // headless pub fn with_capabilities(w: u16, h: u16, caps: TerminalCapabilities) -> Self; #[cfg(unix)] pub fn open(w: u16, h: u16, options: TtySessionOptions) -> io::Result<Self>; pub fn is_live(&self) -> bool; }

TtySessionOptions controls session startup:

crates/ftui-tty/src/lib.rs
pub struct TtySessionOptions { pub alternate_screen: bool, pub features: BackendFeatures, // mouse, bracketed paste, focus, kitty pub intercept_signals: bool, // SIGINT / SIGTERM / SIGHUP cleanup }

Drop order

The TtyBackend drop sequence is deliberate: disable features, show the cursor, exit the alt screen, flush, then restore termios. Any other order leaves the terminal in a half-configured state when a panic occurs.

Usage

examples/live_app.rs
use ftui::prelude::*; use ftui_runtime::TtyBackend; use ftui_backend::BackendFeatures; use ftui_tty::TtySessionOptions; fn main() -> std::io::Result<()> { let backend = TtyBackend::open( 80, 24, TtySessionOptions { alternate_screen: true, features: BackendFeatures { mouse_capture: true, bracketed_paste: true, focus_events: true, kitty_keyboard: false, }, intercept_signals: true, }, )?; let cfg = ProgramConfig::fullscreen(); Program::with_native_backend(MyModel::new(), cfg)?.run() }

In most applications the constructor you reach for is Program::with_native_backend(model, config), which opens a TtyBackend internally using the features implied by your ProgramConfig.

Escape sequence reference

Every capability is a DEC or xterm sequence; the exact bytes are compiled into ftui-tty so the backend is self-contained. The table below mirrors the one at the top of crates/ftui-tty/src/lib.rs.

FeatureEnableDisable
Alternate screenCSI ? 1049 hCSI ? 1049 l
Mouse (SGR)CSI ? 1000;1002;1006 h (with split-form compatibility)CSI ? 1000;1002;1006 l (+ legacy reset hygiene)
Bracketed pasteCSI ? 2004 hCSI ? 2004 l
Focus eventsCSI ? 1004 hCSI ? 1004 l
Kitty keyboardCSI > 15 uCSI < u
Cursor show / hideCSI ? 25 hCSI ? 25 l
Synchronized outputCSI ? 2026 hCSI ? 2026 l

Notes on mouse hygiene (see MOUSE_ENABLE / MOUSE_DISABLE in lib.rs:56–61):

  • SGR format (1006) is set before the mode bits so terminals that key off last-mode-set order negotiate correctly.
  • Legacy encodings (1001, 1003, 1005, 1015, 1016) are explicitly disabled on entry so they cannot linger from a previous session.
  • DECSET 1003 (any-motion mouse) is not enabled. The high-rate motion stream destabilises some multiplexers.
  • A MOUSE_ENABLE_MUX_SAFE variant is used when a multiplexer is detected.

Capabilities and feature union

BackendFeatures is a plain struct of bools:

crates/ftui-backend/src/lib.rs
pub struct BackendFeatures { pub mouse_capture: bool, pub bracketed_paste: bool, pub focus_events: bool, pub kitty_keyboard: bool, }

TtyEventSource keeps track of the current feature set and emits only the delta when set_features is called (see write_feature_delta at lib.rs:1126). Requests are sanitised against the detected TerminalCapabilities:

  • Requesting kitty_keyboard on a terminal that doesn’t advertise it is quietly dropped.
  • Requesting features inside a multiplexer falls back to the multiplexer-safe escape variants.

SIGWINCH handling

On Unix, a dedicated signal thread listens for SIGWINCH and writes a byte to a Unix socket pair. The event loop’s poll(2) blocks on both the tty fd and the wake socket, so resize events have bounded-latency delivery without polling winsize each frame. See ResizeSignalGuard at lib.rs:468.

SIGINT, SIGTERM, SIGHUP, and SIGQUIT are handled by an optional signal-interception guard (when TtySessionOptions.intercept_signals == true) that triggers the runtime’s shutdown path so Drop cleanup runs.

Layout within the workspace

    • src/lib.rs escape sequences, backend, signal guard
    • Cargo.toml unix-only by default

The runtime’s native-backend feature re-exports TtyBackend so application code can write use ftui::prelude::*; and get the whole stack. Without the feature the runtime still compiles — it just won’t expose Program::with_native_backend.

Pitfalls

Never println! inside a live session. Raw mode and synchronized output make a stray write a corrupted frame. All diagnostic output must go through Cmd::Log (inline-safe) or tracing (stderr) — not through stdout directly.

Don’t compile with panic = "abort" if you value your terminal. Abort panics skip Drop, so the session never restores termios and the user’s shell ends up in raw mode. Stick to panic = "unwind" (the default) or wrap your fn main in a panic hook that resets the terminal.

Headless mode is not the same as a file backend. TtyBackend::new(w, h) creates a headless instance — no I/O, no escape sequences, useful for tests. If you want to render into a file or pty, use the ftui-pty crate and the appropriate presenter.

Cross-references