# How Ghostty Handles Focus Tracking and Focus-In/Focus-Out Events

> Discover how Ghostty handles focus tracking and focus-in/focus-out events using VT mode 1004 and CSI sequences for seamless terminal integration. Learn more now.

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

---

**Ghostty implements focus tracking through VT mode 1004, encoding focus changes as CSI sequences (`\x1b[I` for focus-in, `\x1b[O` for focus-out) written directly to the PTY when enabled by client applications.**

Ghostty treats window focus as a first-class terminal state, exposing it to running applications through standard VT focus reporting. This cross-platform terminal emulator monitors native window system focus changes and translates them into escape sequences that console applications can consume through standard input.

## How Focus Reporting Works in Ghostty

### Enabling Focus Event Mode (VT 1004)

Applications enable focus tracking by sending the control sequence `CSI ? 1004 h` to standard output. Ghostty stores this state in the terminal mode table located in `src/terminal/modes.zig` using the flag `.focus_event`. Disabling the mode uses the sequence `CSI ? 1004 l`.

### Detecting OS Window Focus Changes

Platform-specific window backends (macOS, GTK, etc.) monitor native focus events. When the window gains or loses focus, they invoke `Termio.focusGained(td, focused)` defined in `src/termio/Termio.zig`. This function serves as the central entry point for all focus state transitions in the terminal emulator.

### Encoding and Delivering Focus Events

If the `.focus_event` mode is active, Ghostty constructs the appropriate CSI sequence. The encoding logic resides in `src/terminal/focus.zig` within the `encode` function:

- `\x1b[I` represents focus-in
- `\x1b[O` represents focus-out

These bytes are queued for PTY output using `self.queueWrite(td, writer.buffered(), false)`, making the events available to the running application through its standard input stream.

### Backend and Renderer Coordination

After writing to the PTY, `Termio.focusGained` forwards the change to the backend via `self.backend.focusGained(td, focused)` in `src/termio/backend.zig`. Simultaneously, the rendering layer maintains its own focus flag in `src/renderer/generic.zig` (`renderer.generic.Self.focused`) and receives updates through `src/renderer/message.zig`, enabling UI decorations like cursor visibility changes when unfocused.

## Implementation Details: Key Components

The focus tracking system spans multiple modules across the Ghostty codebase:

- **`src/terminal/modes.zig`**: Defines the `.focus_event` mode constant (VT 1004)
- **`src/termio/Termio.zig`**: Central dispatch for focus changes and PTY writing
- **`src/terminal/focus.zig`**: Contains the `Event` enum and `encode` function
- **`src/termio/backend.zig`**: Propagates focus state to concrete backends via `backend.focusGained`
- **`src/terminal/Terminal.zig`**: Stores the global `focused: bool` state
- **`src/renderer/generic.zig`**: Maintains renderer-side focus awareness
- **`src/renderer/message.zig`**: Carries focus state through the render pipeline via `Message.focus`

The `src/terminal/Terminal.zig` object stores `focused: bool = true` as a persistent property, allowing any component to query focus state without backend round-trips.

## Practical Examples

### Enabling Focus Reporting from a Child Process

Programs running inside Ghostty can enable focus reporting and react to events using standard input/output:

```c
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int main(void) {
    /* Enable focus reporting (mode 1004) */
    write(STDOUT_FILENO, "\x1b[?1004h", 8);

    /* Read input and react to focus events */
    char buf[32];
    while (read(STDIN_FILENO, buf, sizeof(buf)) > 0) {
        if (memcmp(buf, "\x1b[I", 3) == 0) {
            printf("Focus gained\n");
        } else if (memcmp(buf, "\x1b[O", 3) == 0) {
            printf("Focus lost\n");
        }
    }
    return 0;
}

```

### Querying Focus State Internally

Ghostty's internal components can check the current focus state through the terminal object:

```zig
// In renderer or UI code
if (self.renderer_state.terminal.modes.get(.focus_event)) {
    const isFocused = self.renderer_state.terminal.focused;
    // Use isFocused to adjust UI (e.g., dim when unfocused)
}

```

## Summary

- Ghostty implements VT 1004 focus reporting mode through the `.focus_event` flag in `src/terminal/modes.zig`
- Platform backends trigger `Termio.focusGained(td, focused)` in `src/termio/Termio.zig` when window focus changes
- Events encode as `\x1b[I` (focus-in) or `\x1b[O` (focus-out) via `src/terminal/focus.zig`
- The PTY receives events through `queueWrite`, allowing child processes to read them from stdin
- Backend propagation occurs through `src/termio/backend.zig` while the renderer tracks state in `src/renderer/generic.zig`
- Applications enable reporting with `CSI ? 1004 h` and disable with `CSI ? 1004 l`

## Frequently Asked Questions

### How do I enable focus event reporting in Ghostty?

Applications enable focus tracking by writing the escape sequence `CSI ? 1004 h` (typically `\x1b[?1004h`) to stdout. This sets the `.focus_event` mode flag in Ghostty's terminal state table. To disable reporting, send `CSI ? 1004 l` according to the implementation in `src/terminal/modes.zig`.

### What escape sequences does Ghostty send for focus events?

Ghostty sends `\x1b[I` when the window gains focus and `\x1b[O` when it loses focus. These CSI sequences are generated by the `encode` function in `src/terminal/focus.zig` and written to the application's stdin through the PTY via `Termio.queueWrite`.

### How does Ghostty store the current focus state?

The focus state exists at multiple levels: the OS window manager reports changes to `Termio.focusGained`, `src/terminal/Terminal.zig` stores `focused: bool` as a persistent property, and `src/renderer/generic.zig` maintains a copy for UI rendering. The mode table in `src/terminal/modes.zig` tracks whether reporting is enabled via the `.focus_event` flag.

### Why doesn't my application receive focus events in Ghostty?

Ensure your application sends the enable sequence (`\x1b[?1004h`) to stdout before expecting events. Ghostty only writes focus CSI sequences to the PTY when the `.focus_event` mode is active. Also verify your application reads from stdin, as focus events arrive as ordinary input data, not as signals or out-of-band messages.