libtmux
Drive tmux from Rust: typed, async control over servers, sessions, windows, and panes.
use Server;
# async
Every accessor that reaches tmux is async; everything that reads an
already-taken snapshot is not. Commands run without a shell, and results keep
stdout and stderr as raw bytes, so decoding stays the caller's decision.
Requirements and installation
Rust 1.85 or newer, tmux 3.2a or newer, and a Unix target. Native Windows is unsupported because tmux is unavailable there; WSL works.
[]
= "0.1.0-alpha.3"
= { = "1", = ["macros", "rt-multi-thread"] }
Async operations need an entered Tokio runtime. The default executable is
tmux, resolved through the PATH captured when the Server is built; use
ServerBuilder::tmux_executable to select another.
Minimum supported Rust version. 1.85, the first compiler to ship Edition 2024. It is checked in CI against the whole test suite, not just a build. A raise is a minor version bump and is called out in the release notes, so a patch release never moves it.
Features
None are on by default.
| Feature | What it adds |
|---|---|
control-mode |
One persistent tmux connection, so the server reports changes as they happen rather than being polled |
derive |
#[derive(Filterable)], for filtering your own structs with the same expressions |
serde |
Versioned serialization for FilterExpr<T>, for sending expressions over a wire |
blocking |
A runtime for calling from code that is not async |
tracing |
Sanitized command instrumentation |
test-support |
The real-tmux test guard, for your own tests |
Walking the hierarchy
Server::hierarchy gathers the whole tree in three tmux commands, rather than
one per object:
# async
Listings come in pairs. The plain form returns an empty Vec when the
underlying tmux command fails, which suits a status line; the try_ form keeps
the reason, which suits anything that must not guess.
Filtering
Typed field handles build an expression without accepting an untyped field name or value, so a comparison that has no meaning for a field does not compile:
use ;
# async
A question about what a session contains needs the shape that holds its
windows, so SessionTree and WindowTree carry relations:
use ;
use ;
# async
With serde, an expression lowers to a versioned JSON envelope, so a CLI, an
MCP server, or a config file can carry one.
Text from tmux is bytes
tmux permits names, titles, and pane contents that are not valid UTF-8, so they
arrive as TmuxText rather than String. There is no implicit conversion:
let text = from;
assert_eq!;
assert_eq!;
assert_eq!;
Failures say what to do about them
Error has a variant per failure mode; Error::kind reduces those to the
decision a caller makes. Error::is_object_gone is the branch most programs
write, because an object disappearing is an ordinary race rather than a failed
request.
# async
Cancellation and shutdown
Each command runs in an isolated process group with a supervised deadline,
30 seconds by default and configurable through ServerBuilder::default_timeout.
Dropping the command future, reaching its timeout, or shutting the server down
signals the group and waits for the direct child while the runtime is alive.
Server::shutdown() is shared by all clones: it cancels active work, rejects
later commands, and is safe to call concurrently or repeatedly. Await it, or
await your commands, before tearing the runtime down — runtime destruction
signals best-effort but cannot promise that child reaping finished.
Testing against real tmux
test-support exports libtmux::test::TestServer, the same guard this crate's
own suite uses:
[]
= { = "0.1.0-alpha.3", = ["test-support"] }
Each guard owns a tmux child on a private socket with an empty config, so tests
cannot reach your real server or each other. shutdown().await closes escaped
clients, waits the daemon, and reports cleanup failures; Drop forces
best-effort cleanup even after the runtime has ended. On Linux, cleanup also
sweeps processes by an exact environment marker through pidfds, so PID reuse
cannot redirect a signal.
The guarantees and their limits are set out in docs/design.md, which ships
with the crate.
Compatibility
Every supported tmux release is built from source in CI and runs the whole workspace: 3.2a, 3.4, 3.5a, 3.6, and 3.7b. The floor and the ceiling matter equally — 3.4 and 3.5a are the releases that wrap command output differently, which is why the format codec carries a second dialect.
Documentation
API documentation covers the public surface. Two longer documents ship inside the crate, next to the source:
docs/design.md— why the crate is shaped the way it is: the transport, the snapshot and format boundary, the query grammar, the test guard, and the compatibility lanes.docs/parity.md— the capability ledger against Python libtmux, naming each Rust symbol and the test that exercises it.
License
MIT.