Ghostty tmux Integration and Control Mode: Architecture Deep Dive
Ghostty implements tmux integration through a three-layer architecture comprising a low-level control-mode parser, a viewer state machine, and output parsing utilities that translate tmux's DCS 1000p protocol into actionable terminal state.
Ghostty, the Zig-based terminal emulator, communicates with tmux sessions directly through tmux control mode using the "DCS 1000p" sequence. Unlike traditional wrapper scripts, this native integration parses raw byte streams from tmux to mirror sessions, capture pane history, and respond to layout changes in real time. The implementation spans three specialized components in src/terminal/tmux/ that handle everything from protocol parsing to UI synchronization.
The Three-Layer Architecture
The integration splits responsibilities across distinct layers to maintain clean separation between protocol handling and state management.
Control-Mode Parser
The foundation resides in src/terminal/tmux/control.zig, which implements a state machine that consumes raw tmux bytes and emits typed Notification unions. This parser tracks four distinct states—idle, broken, notification, and block—to handle the asynchronous nature of the DCS 1000p protocol.
When tmux sends notifications like %output, %layout-change, or %session-changed, the parser uses the oniguruma regex library to extract fields from the byte stream. Each successfully parsed sequence returns a Notification union variant that the higher layers consume.
Viewer State Machine
Built atop the parser, src/terminal/tmux/viewer.zig implements the viewer state machine that maintains Ghostty's internal model of tmux windows and panes. The viewer stores a circular command buffer (using CircBuf from src/datastruct/main.zig), a map of active panes, and a list of windows.
The viewer operates in distinct phases:
- Startup phase: Discards initial
%begin/%endblocks while waiting for%session-changed - Command queue state: Queues tmux commands like
list-windows,list-panes, andcapture-pane - Live updates: Processes real-time notifications to update terminal state
When command output arrives, the viewer parses results and emits Action enums—specifically .command, .windows, or .exit—for the outer UI to handle.
Output Parsing Utilities
The src/terminal/tmux/output.zig module provides strongly-typed parsing for tmux's formatted strings. It defines the Variable enum covering all tmux format variables Ghostty cares about, plus a generic parseFormatStruct function that builds structs dynamically based on format lists.
For example, when parsing list-windows output, the module handles delimiters and field mapping automatically, converting strings like "$1 @2 80 24 abc123" into structured data containing session IDs, window IDs, and dimensions.
How the Control Mode Protocol Works
Tmux control mode uses DCS (Device Control String) sequences to push events asynchronously. Ghostty's parser handles this stream by transitioning between states as escape sequences arrive.
The notification state processes single-line events like %session-changed, while the block state handles multi-line responses wrapped in %begin ... %end markers. If the parser encounters unrecoverable errors or receives a %exit notification, the viewer transitions to the defunct state and emits a single .exit action to terminate the connection cleanly.
The Viewer Workflow
Initial Synchronization
Upon connection, the viewer enters startup_block state and filters the initial handshake. Once %session-changed arrives, it records the session ID and immediately queues two commands: tmux_version and list-windows. This establishes the baseline for the session model.
Command Queue Management
Each outgoing command enters a circular buffer (CircBuf). When tmux returns a block (delimited by %begin and %end), the viewer calls receivedCommandOutput to parse the results. Based on the data, it may enqueue additional commands like capture-pane -p to fetch historical buffer contents.
Live Updates and Layout Changes
After initial sync, the viewer remains in command_queue state, reacting to live notifications. The %output notification routes terminal data to specific panes via Pane.terminal.vtStream().nextSlice(), while %layout-change triggers updates through src/terminal/tmux/layout.zig to rebuild pane trees.
Practical Implementation Examples
Creating and Running a Viewer
// Create a Viewer with the default allocator
var viewer = try Viewer.init(allocator);
defer viewer.deinit();
// Feed tmux control‑mode bytes into the parser
var parser = Parser{ .buffer = .init(allocator) };
defer parser.deinit();
for (tmux_bytes) |b| {
const maybe_notification = try parser.put(b);
if (maybe_notification) |n| {
// Pass the notification to the viewer and get actions
const actions = viewer.next(.{ .tmux = n });
// Process actions (example: send command to tmux)
for (actions) |act| switch (act) {
.command => |cmd| send_to_tmux(cmd),
.windows => |wins| update_ui_with_windows(wins),
.exit => return,
else => {},
};
}
}
Parsing Formatted Tmux Output
const line = "$1 @2 80 24 abc123";
const data = try output.parseFormatStruct(
Format.list_windows.Struct(),
line,
Format.list_windows.delim,
);
// data.session_id == 1, data.window_id == 2, …
Handling Real-Time Pane Output
fn handle_output(notif: control.Notification) void {
if (notif == .output) |out| {
// `out.pane_id` is the numeric pane ID, `out.data` is raw output
const pane = viewer.panes.get(out.pane_id) orelse return;
try pane.terminal.vtStream().nextSlice(out.data);
}
}
Key Source Files
src/terminal/tmux/control.zig: Low-level parser andNotificationunion definitionssrc/terminal/tmux/viewer.zig: State machine, command queue, and window/pane model managementsrc/terminal/tmux/output.zig: Format string parsing andVariableenum definitionssrc/terminal/tmux/layout.zig: Layout string parsing and pane tree constructionsrc/terminal/Terminal.zig: Core VT stream handling for individual panessrc/datastruct/main.zig: Circular buffer implementation (CircBuf) for command queuing
Summary
- Ghostty implements tmux control mode through a dedicated three-layer architecture in
src/terminal/tmux/ - The control-mode parser (
control.zig) uses oniguruma regex to convert DCS 1000p byte streams into typedNotificationunions - The viewer state machine (
viewer.zig) manages session state with a circular command buffer and emitsActionenums for UI updates - Output utilities (
output.zig) provide strongly-typed parsing for tmux format strings likelist-windowsandlist-panes - The implementation handles asynchronous notifications (
%output,%layout-change) and synchronous block commands (%begin/%end) without external process dependencies
Frequently Asked Questions
What is tmux control mode and how does Ghostty use it?
Tmux control mode is a protocol activated by the -CC flag that outputs DCS 1000p sequences, allowing bidirectional communication with tmux. Ghostty uses this mode to receive real-time notifications about pane output, layout changes, and session updates directly through a raw byte stream parser, eliminating the need for polling or wrapper scripts.
How does Ghostty handle tmux command queuing?
Ghostty maintains a circular buffer (CircBuf from src/datastruct/main.zig) within the viewer state machine to queue commands like list-windows and capture-pane. Each command waits for its corresponding %begin ... %end block response before the viewer parses the output and updates the internal model, ensuring synchronous state management despite the asynchronous protocol.
What happens when Ghostty receives a %layout-change notification?
When the control-mode parser detects a %layout-change notification, the viewer passes it to the layout handler in src/terminal/tmux/layout.zig. This module parses tmux's layout strings and reconstructs the pane tree, updating Ghostty's internal representation to match the current tmux window structure without requiring a full resync.
How does Ghostty recover from tmux control mode errors?
If the parser encounters an unrecoverable error or receives a %exit notification, the viewer transitions to the defunct state. In this state, it emits a single .exit action that signals the outer UI to terminate the tmux connection cleanly, preventing partial state corruption or ghost sessions.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →