tmuxctl 0.1.0

A bidirectional tmux control-mode (tmux -C) client: typed notification stream, command reply correlation, pane-output decoding, and layout-tree parsing.
Documentation
  • Coverage
  • 63.06%
    70 out of 111 items documented0 out of 10 items with examples
  • Size
  • Source code size: 214.77 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 1.68 MB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 5s Average build duration of successful builds.
  • all releases: 5s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • ace-rs/tmuxctl
    0 0 0
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • chakrit

tmuxctl

A bidirectional tmux control-mode (tmux -C) client for Rust: spawn a control session, parse the %-prefixed notification stream into typed events, correlate command replies by command-number, octal-decode pane output to raw bytes, and model tmux's layout tree.

It is a protocol layer only — no terminal emulation, no rendering, no UI. Those belong to the consumer (a VT emulator like vt100/avt, a renderer like ratatui). The crate exists because a 2026 survey found no reusable Rust control-mode client with reply correlation and output unescaping; tmuxctl fills that gap on its own release cadence.

Status: working, pre-1.0. The sans-IO core (line Parser, correlation Engine, Layout, decode_output, the full Notification set) and a usable Client are in place, with three runtime drivers (blocking, tokio, smol) over the one core. Tested by unit tests, transcript replay of a real tmux -C capture, and live integration against tmux. Not yet published to crates.io.

Scope

In: spawn/supervise tmux -C; line-framed parser → typed Notification stream; command send with %begin/%end/%error reply correlation; octal-decode of %output/%extended-output; layout-string parse + render + checksum; typed helpers for send-keys and refresh-client resize; tolerate-and-log unknown %-lines. Versioning is lock-step to one pinned tmux (no per-version gating) — see the ADRs.

Out (the consumer's job): VT emulation of decoded bytes, rendering and layout placement, keybindings, spawning pane processes (tmux does that), anything app-specific.

Usage

Three runtime drivers wrap one sans-IO core; pick by Cargo feature.

Feature Driver Notes
blocking Client std threads, no extra deps. Default.
tokio TokioClient async; opt-in (pulls tokio).
smol SmolClient async; opt-in (pulls smol).

Blocking client — spawn a control session, run a command, react to notifications:

use tmuxctl::{Client, Notification, PaneId, SpawnOpts};

let mut client = Client::spawn(SpawnOpts::new().session("work"))?;

// Notifications stream on their own channel; drain on a thread.
let events = client.events().unwrap();
std::thread::spawn(move || {
    for note in events {
        if let Notification::Output { pane, bytes } = note {
            // feed `bytes` (already octal-decoded) to your VT emulator
        }
    }
});

// Commands block until tmux's reply is correlated.
let windows = client.command("list-windows -F '#{window_id}'")?;
client.send_keys(PaneId(0), b"echo hi\r")?;
client.resize(120, 40)?;
# Ok::<(), tmuxctl::CommandError>(())

The async drivers mirror this with async fn and an mpsc/channel events receiver.

Sans-IO core — drive the protocol yourself over any transport (this is what the drivers wrap, and how the parser is tested without a process):

use tmuxctl::{Engine, Incoming};

let mut engine = Engine::new();
for incoming in engine.feed(&raw_tmux_bytes) {
    match incoming {
        Incoming::Notification(note) => { /* async event */ }
        Incoming::Reply { id, result } => { /* a command you registered completed */ }
    }
}

Documentation

  • docs/spec/overview.md — the protocol contract: transport, reply framing, output escaping, the notification set, layout strings, the API sketch.
  • docs/reference/tmux-source-map.md — a navigable map of the tmux C source (control mode, layout, escaping) that backs each wire detail.
  • docs/decisions/ — dated ADRs (crate name/license, sans-IO core + feature-gated drivers, lock-step tmux versioning, the container test strategy).
  • docs/roadmap.md — what's done and what's next.

Development

cargo test                                       # units + transcript replay (fast, no tmux)
cargo test --all-features                         # also the tokio + smol drivers
cargo clippy --all-targets --all-features        # done-gate, must be clean
cargo fmt
./scripts/integration.sh                          # live tmux round-trip (#[ignore]d otherwise)

Warnings are errors (#![deny(warnings)]); clippy is a separate done-gate. No CI service — release and integration scripts live under scripts/ and run locally. The live integration suite is keyed off TMUXCTL_TMUX_BIN.

Relationship to hangar

tmuxctl is developed standalone but consumed by hangar, a local-first terminal multiplexer that puts tmux's server in the engine role. hangar depends on this crate by path during co-development, then by version once published. The crate has no dependency on hangar.

License

Dual-licensed under either of MIT or Apache-2.0 at your option.