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:

  1. Startup phase: Discards initial %begin/%end blocks while waiting for %session-changed
  2. Command queue state: Queues tmux commands like list-windows, list-panes, and capture-pane
  3. 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 and Notification union definitions
  • src/terminal/tmux/viewer.zig: State machine, command queue, and window/pane model management
  • src/terminal/tmux/output.zig: Format string parsing and Variable enum definitions
  • src/terminal/tmux/layout.zig: Layout string parsing and pane tree construction
  • src/terminal/Terminal.zig: Core VT stream handling for individual panes
  • src/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 typed Notification unions
  • The viewer state machine (viewer.zig) manages session state with a circular command buffer and emits Action enums for UI updates
  • Output utilities (output.zig) provide strongly-typed parsing for tmux format strings like list-windows and list-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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →