███████╗████████╗███╗ ███╗██╗ ██╗██╗ ██╗
╚══███╔╝╚══██╔══╝████╗ ████║██║ ██║╚██╗██╔╝
███╔╝ ██║ ██╔████╔██║██║ ██║ ╚███╔╝
███╔╝ ██║ ██║╚██╔╝██║██║ ██║ ██╔██╗
███████╗ ██║ ██║ ╚═╝ ██║╚██████╔╝██╔╝ ██╗
╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝
[TMUX, REWRITTEN IN RUST — DONE RIGHT]
"The world's first 100%-functional tmux in Rust — the whole multiplexer, server and client, running."
"Not a wrapper. Not control mode. The multiplexer itself."
"Ported against the C, verified against the C — byte for byte, 1107/1107 parity cases passing."
[FROM SOURCE, NOT FROM SCRATCH]
ztmux is a from-source port of tmux to Rust — the whole
program: the server, the client, the grid/screen model, the input parser, layouts, the
command language, formats, and the terminal back end. It is not a wrapper around the
tmux binary and it is not control mode (tmux -CC); it is tmux, reimplemented. The
port stands on the upstream tmux C sources, vendored under
vendor/ as a plain, read-only, SHA-pinned copy — the source of truth
every module is diffed against. Correctness is measured, not claimed — a
parity suite runs identical inputs through the real tmux and
ztmux and diffs them byte-for-byte, and an anti-drift gate fails the build if a Rust
function is added whose name has no counterpart in the tmux C source.
Docs · Port Report · Parity · ztmux-core · tmux
Table of Contents
- [0x00] Overview
- [0x01] Install
- [0x02] How the Port Is Built
- [0x03] "Done Right"
- [0x04] Parity vs System tmux
- [0x05] Anti-Drift Gate — No Fake Functions
- [0x06] Layout
- [0x07] Porting Workflow
- [0x08] Extensions
- [0xFF] License
[0x00] OVERVIEW
A terminal multiplexer keeps your shells alive: split panes, detach and reattach, script
the whole thing. tmux is the reference implementation, ~30 years of C. ztmux ports that C
to Rust one subsystem at a time, holding behavior identical to upstream at every step. It
opens its own socket namespace (ztmux-<uid>) so it never collides with a running tmux.
Status: 100% functional. The port builds, runs, and self-hosts — ztmux new-session,
splits, detach/reattach, the command language, formats, and layouts all work — and the
parity suite is green at 1107/1107 (100%) against the vendored tmux. Every bug the harness
found has been root-caused and fixed (see docs/BUGS.md).
On top of the port, ztmux ships original subcommands with no tmux counterpart — live
dashboards and JSON-emitting inspectors for the running server (ztmux --help, and [0x08]).
Distinct from
ztmux-core, a native tmux client engine that speaks the wire protocol to an existing server for GUI hosts. This repo is the whole server + client. The two pair: ztmux and ztmux-core pin the identicalPROTOCOL_VERSION = 8(src/ported/tmux_protocol_h.rshere,src/transport.rsthere), so a GUI drives this server over the same wire protocol — and because both ends are MenkeTechnologies-owned, upstream tmux's release cadence can never break that contract.
[0x01] INSTALL
Requires a C libevent (tmux's event-loop library) and a terminfo database (ncurses),
exactly like tmux.
# macOS
# Debian / Ubuntu
The binary is ztmux. On macOS the build links Homebrew's libevent automatically; set
TMUX_RS_DISABLE_HOMEBREW_LIBS=1 to skip the Homebrew search path. Linking can be forced
with the static / dynamic features.
[0x02] HOW THE PORT IS BUILT
One reference, vendored under vendor/ as a plain committed copy
(the clone is self-contained and never depends on an upstream staying alive):
| Path | Upstream | Role |
|---|---|---|
vendor/tmux/ |
tmux/tmux (C) | Source of truth. Every ported module is diffed against its C counterpart. |
src/ |
— | The port. The crate we own and evolve. Edit here. |
Cargo.toml declares its own [workspace] excluding vendor/, so Cargo never walks into
the reference. Every ported function carries a back-link comment to its C origin, e.g.:
// vendor/tmux/grid.c:320 grid_create()
[0x03] "DONE RIGHT"
The port began as a faithful but almost-entirely-unsafe mechanical transpile. "Done
right" is turning that working skeleton into good Rust without ever drifting from tmux:
- Start from a working skeleton — a running program to refactor, not a blank page.
- Shrink the
unsafesurface — replace raw-pointer intrusive lists and C-isms with safe Rust where behavior allows. - Verify against C at every step — a module isn't "ported" until it matches the C
reference (see
[0x04]). - Keep it green —
cargo buildandcargo clippystay clean as code comes over.
[0x04] PARITY VS SYSTEM tmux
ztmux is a port of tmux, so "correct" means tmux itself. The parity suite runs the same
inputs through the real tmux (reference) and ztmux (port) and compares byte-for-byte —
the same shape as the sibling ports (zshrs vs
zsh, strykelang vs perl).
Cases live in parity/cases/ as tmux FORMAT strings (#{e|+|:2,3}) or shell scenarios.
It earns its keep: it root-caused a #{l:…} server crash to a dropped pointer increment in
format_unescape, fixed even-horizontal layout rounding and #{pane_current_command} on
macOS, regex backreferences, #{!:}, named buffers, loop variables, and the last layout
divergences — each pinned to a single case and then ported correctly. It now stands at
1107/1107 cases passing (100%), byte-for-byte vs the vendored tmux, with zero known
divergences. See parity/PARITY_ROADMAP.md and the bug log
docs/BUGS.md.
[0x05] ANTI-DRIFT GATE — NO FAKE FUNCTIONS
A port can be faked by inventing Rust-only "helper" functions that don't exist in tmux,
inflating apparent completeness. tests/ported_fn_names_match_c.rs fails the build when
a free fn is added to src/ whose name has no counterpart in vendor/tmux. Pre-existing
exceptions (libc/libevent wrappers, Rust glue) are frozen in
tests/data/fake_fn_allowlist.txt — an audit trail to burn down, not a free pass. The
port report tracks C→Rust
coverage per function.
[0x06] LAYOUT
ztmux/
├── Cargo.toml # the ztmux crate (own workspace root; excludes vendor/)
├── build.rs # lalrpop (command grammar) + libevent linking
├── src/ # THE PORT — edit here
│ └── extensions/ # original ztmux subcommands (see [0x08]); not a port
├── completions/ # _ztmux zsh completion (generated by scripts/)
├── parity/ # ztmux-vs-tmux byte-for-byte suite + roadmap
├── scripts/ # gen_port_report.py, annotate_c_links.py
├── tests/ # anti-drift gate + allowlist
├── docs/ # GH Pages hub: index / report / port_report
├── vendor/
│ └── tmux/ # C source of truth (read-only reference)
└── COPYING # ISC (upstream notices)
[0x07] PORTING WORKFLOW
- Pick a subsystem (a
.rsmodule undersrc/). - Open its C counterpart in
vendor/tmux/. - Bring the Rust toward correct, idiomatic, memory-safe Rust — replacing the raw-pointer /
unsafeC-isms with safe equivalents where behavior allows. - Keep it building (
cargo build) and lint-clean (cargo clippy), and green against the parity suite (bash parity/run_parity.sh) at every step.
[0x08] EXTENSIONS
Beyond the port, ztmux adds original subcommands with no upstream counterpart, under
src/extensions/. They live apart from the ported core — and are exempt
from the anti-drift gate ([0x05]) — precisely because they are not tmux. Each is either a
read-only query over the running server (built on the same structured list-* -o json
output) or a small mutating helper, and every one accepts -o json / --json for scripting.
They fall into a few families:
- Inspection — one-shot, pipeable views of the live server: process tables (
ps,pstree,mem,state,elapsed), geometry (size,density,layouts,solo), directories and repositories (cwd,project,git,remote,ahead,changes,stash,commit,conflicts,vcs,worktree,submodules,gone), network (ssh,net,ports), clients (who,readonly,idle,viewers,connected,constrain,keytable,control,utf8), and configuration (hooks,keys,monitor,remain,sync,limit,visual,mouse, …). - Live TUIs —
dashboard(full-screen server monitor),switcher(fuzzy session/window/ pane picker),watch(top-like per-pane process monitor). - Actions —
prune,equalize,revive,clearall,retitle,bcast,layout, andpick(batch sync/unmark/clear over a multi-pane mark set). - Automation —
triggersruns any ztmux command when a regex matches a pane's output (rules in~/.ztmux/triggers.json, armed withztmux triggers arm), reviving tmux's removedmonitor-contentas a general sense→act loop. Add rules without touching the JSON via the inline wizard:ztmux triggers wizard(orztmux triggers add <name> <pane> <match> <action>). - Ratatui UI (on by default) — original interactive surfaces rendered with
ratatui rather than tmux's server draw: a which-key hint bar on the prefix, a floating
command palette with inline Tab/arrow completion, ratatui clock and display-panes,
edit-scrollback-in-
$EDITOR(prefix e), and multi-pane selective sync — mark panes (prefix C-s), sync the set (prefix M). Sync state is shown on the pane border — synced (red), selected (orange), trigger-armed (cyan) — which output can never overwrite. Opt into zellij-style pane frames with@ztmux-zellij-mode on(off by default): every pane is inset by a one-cell ring (like zellij, so a program can never draw on the frame) and gets a rounded box with its name in the top border; the box recolours for sync state. In this modeprefix +toggles a zellij-style pane stack — the focused pane fills the column, the rest collapse to one-row title bars (ztmux stack/:stack). A zellij-style tab bar of windows along the top (session badge, active tab highlighted) is a separate toggle —ztmux tabs on/:tabs— which restyles the status line and restores your prior status settings ontabs off. A zellij-style session manager (ztmux sessions/:sessions) opens a ratatui list of sessions: type to filter, Enter switches,Ctrl-rrenames,Ctrl-xkills (with confirm),Ctrl-nmakes a new one.prefix C-ftoggles a zellij-style floating pane — a persistent pane that floats above the tiled layout in a popup (kept live in a hidden holding session, so its state survives between toggles; pressprefix C-fagain to hide it).ztmux modal on(opt-in) installs zellij-style modal keybindings:Ctrl-ppane mode,Ctrl-ttab,Ctrl-nresize,Ctrl-sscroll,Ctrl-osession,Ctrl-glock — each a sticky key table entered without a prefix; the hint bar (turned on automatically) shows the current mode's keys.modal offremoves the entry keys and restores the prefix. Since theCtrl-*keys are intercepted globally (the zellij trade-off), it is off by default.ztmux resurrect save/ztmux resurrect restorepersist the whole server across restarts (zellij-style resurrectable sessions):savewrites every session/window/pane — layout, cwd and command — to~/.ztmux/resurrect/, andrestorerecreates them (windows, panes and exact tiled geometry; shell panes come back in their cwd,--runalso re-runs saved commands). Existing sessions are never clobbered;resurrect listshows saved snapshots. For continuum-style automatic persistence,set -g @ztmux-resurrect-auto on: the first client to attach spawns a detached daemon that re-saves every 15 minutes (pidfile-guarded, one per server); addset -g @ztmux-resurrect-restore onand it also restores the last snapshot once on a fresh server start.ztmux open/:open(also in the pane menu) scans the current pane for URLs and file paths and shows a ratatui picker — Enter opens the selection (a URL inopen/xdg-open, a file in$EDITORat itsfile:line, a directory revealed),ycopies it (tmux buffer + OS clipboard). Like tmux-open / tmux-urlview, built in. Settings (allset -g):@ztmux-ratatui offdisables the whole ratatui renderer for a classic plain-tmux server (on by default; takes effect on the next redraw);@ztmux-hint onshows the prefix hint bar (off by default);@ztmux-zellij-mode onenables the framed/inset mode (off by default;@ztmux-pane-namesis a back-compat alias);@ztmux-pane-name-formatoverrides the frame name with a tmux format (e.g.#{pane_index}: #{pane_current_command}). With@ztmux-ratatui offthe default draw path and the byte-for-byte parity suite are untouched.
Run ztmux --help for the current list, or man ztmux for the full reference — each
extension has its own entry under the EXTENSIONS section, and the zsh completion
(completions/_ztmux) describes every one inline.
[0xFF] LICENSE
MIT — see LICENSE. ztmux is a derivative work of tmux (Nicholas Marriott et al.),
ISC; the original notices are retained in COPYING and under vendor/.