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, correlationEngine,Layout,decode_output, the fullNotificationset) and a usableClientare in place, with three runtime drivers (blocking,tokio,smol) over the one core. Tested by unit tests, transcript replay of a realtmux -Ccapture, 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 ;
let mut client = spawn?;
// Notifications stream on their own channel; drain on a thread.
let events = client.events.unwrap;
spawn;
// Commands block until tmux's reply is correlated.
let windows = client.command?;
client.send_keys?;
client.resize?;
# Ok::
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 ;
let mut engine = new;
for incoming in engine.feed
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
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.