# Ghostty tmux Integration and Control Mode: Architecture Deep Dive

> Explore Ghostty's unique three-layer architecture for seamless tmux integration and control mode. Understand how it parses DCS 1000p for efficient terminal state management.

- Repository: [Ghostty/ghostty](https://github.com/ghostty-org/ghostty)
- Tags: architecture
- Published: 2026-05-01

---

**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

```zig
// 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

```zig
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

```zig
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.