How Ghostty Handles Terminal Window Resizing and Grid Reflow
Ghostty processes terminal window resizing through a coordinated pipeline that atomically updates PTY dimensions, reallocates internal grid buffers, and triggers renderer reflow to prevent visual flicker.
Ghostty, the cross-platform terminal emulator written in Zig, implements a sophisticated resize handling mechanism that bridges UI events with the underlying pseudo-terminal (PTY). When the operating system reports a window size change, the terminal grid reflow occurs atomically across the Termio layer, ensuring the rendered output always matches the new dimensions.
The Resize Event Pipeline
Ghostty's resize handling follows a strict five-stage pipeline that separates UI events from computational updates and rendering.
Capture and Coalescing in the Termio Thread
When the GTK or macOS frontend detects a size change, it posts a resize message to the termio thread. According to the Ghostty source code in src/termio/Thread.zig, the thread loop coalesces rapid successive events to avoid redundant calculations:
while (true) {
const msg = cb.io.wait();
switch (msg) {
.resize => |size| {
// Stores only the latest size during the debounce interval
thread.coalesce_data.resize = size;
},
.timer => {
if (thread.coalesce_data.resize) |sz| {
thread.coalesce_data.resize = null;
cb.io.resize(&cb.data, sz) catch |err| log.warn("resize error {}", .{err});
}
},
// … other messages …
}
}
This debouncing mechanism collapses multiple rapid resize events into a single Termio.resize call.
State Update and PTY Synchronization
Once coalesced, the resize function in src/termio/Termio.zig (lines 62-101) performs three critical operations:
- Stores the new
renderer.Sizecontaining pixel and cell dimensions - Calculates the grid size using the
grid()method - Invokes
backend.resizeto synchronize the underlying PTY file descriptors
The implementation ensures the operating system's view of the terminal size stays locked to Ghostty's internal state.
Grid Reallocation Under Mutex Lock
Inside the critical section guarded by renderer_state.mutex, Ghostty calls terminal.resize as implemented in src/terminal/c/terminal.zig (lines 467-484). This function:
- Reallocates the logical grid buffers to match new cell counts
- Updates pixel dimensions for font rendering calculations
- Clears any pending "synchronized output" mode (SM?2026) to force immediate visual updates
- Handles edge cases including zero-size windows and dimension overflow
The mutex ensures grid reflow happens atomically without race conditions between the terminal state and the rendering thread.
Renderer Notification and Redraw
After updating the grid, the termio thread enqueues a resize message and wakes the renderer. As found in src/termio/Termio.zig (lines 99-102):
renderer_mailbox.push(.{ .resize = size }, .{ .forever = {} });
renderer_wakeup.notify();
The renderer thread consumes this message, recomputes cell layout based on the new grid dimensions, and redraws the entire screen surface.
Size Reporting and Synchronization
In-Band Size Reports
When configured, Ghostty emits in-band size reports using CSI sequences. The sizeReportLocked function in src/termio/Termio.zig (lines 12-20) formats and sends OSC 1337-style reports (CSI = 3 ; … c) to applications requesting window dimension notifications.
Forced Resize Fallback
In src/termio/stream_handler.zig (line 723), Ghostty implements a consistency check: if the terminal reports dimensions differing from the UI window size, the system forces the UI to adopt the terminal's size. This prevents desynchronization between the PTY state and the visual window bounds.
Practical Code Examples
Manual Resize Invocation
For testing or custom UI integrations, developers can trigger the resize pipeline directly:
const newSize = renderer.Size{
.cols = 120,
.rows = 35,
.cell = .{ .width = 8, .height = 16 },
};
try termio.resize(td, newSize);
This call executes the full sequence: PTY resize, grid reallocation, and renderer notification.
Understanding the Coalescing Behavior
The debounce logic in src/termio/Thread.zig ensures that during animated window resizes, only the final dimensions trigger expensive grid reallocation operations. This optimization prevents memory thrashing and reduces CPU usage during continuous resize events.
Summary
- Event Coalescing: Rapid resize events collapse into single updates via
src/termio/Thread.zigto optimize performance - Atomic Updates: Grid reallocation occurs under
renderer_state.mutexinsrc/terminal/c/terminal.zigto prevent race conditions - PTY Synchronization:
backend.resizeinsrc/termio/Termio.zigensures the operating system's pseudo-terminal matches the UI dimensions - Renderer Integration: The mailbox push pattern decouples terminal state updates from GPU rendering operations
- Consistency Guarantees: Fallback logic in
src/termio/stream_handler.zigresynchronizes the UI if terminal reports diverge from window bounds
Frequently Asked Questions
How does Ghostty prevent flicker during window resizing?
Ghostty guards grid reallocation with a mutex (renderer_state.mutex) and clears synchronized output mode before updating dimensions. This ensures the terminal buffer atomically switches to the new size before the renderer redraws, eliminating intermediate frames that could cause visual tearing.
What happens if I resize the window rapidly?
The termio thread coalesces multiple resize events into a single operation. In src/termio/Thread.zig, intermediate sizes are stored temporarily in coalesce_data and only the final dimensions processed after a brief debounce interval, preventing redundant grid reallocations and PTY updates.
Does Ghostty support in-band size reporting to applications?
Yes. When enabled, Ghostty emits CSI sequences via sizeReportLocked in src/termio/Termio.zig, sending OSC 1337-style notifications that allow terminal applications to react programmatically to dimension changes without relying on SIGWINCH signals alone.
How does Ghostty handle size mismatches between the UI and PTY?
If the terminal reports dimensions different from the window size, the stream handler in src/termio/stream_handler.zig (line 723) forces the UI to adopt the terminal's size. This ensures consistency between the logical grid and the visual window, preventing display corruption or input coordinate misalignment.
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 →