Synchronized Output

Synchronized Output

Synchronized output (Mode 2026) prevents screen tearing during rapid terminal redraws. When enabled, the terminal buffers all output until the mode is disabled, then paints the entire frame atomically. This is particularly useful for readline — completion lists, multi-line prompts, and screen repaints all cause multiple write calls that can tear without synchronization.

How It Works

Mode 2026 uses two escape sequences:

SequenceNamePurpose
ESC[?2026hBSU (Begin Synchronized Update)Start buffering output
ESC[?2026lESU (End Synchronized Update)Flush buffer and paint

Everything written between BSU and ESU is held by the terminal and rendered in a single operation. On terminals that don’t recognize Mode 2026, the sequences are silently ignored — so it’s always safe to send them.

Quick Start

import org.aesh.terminal.tty.TerminalConnection;
import org.aesh.terminal.utils.ANSI;

TerminalConnection conn = new TerminalConnection();

// Check if terminal supports synchronized output
if (conn.supportsSynchronizedOutput()) {
    conn.enableSynchronizedOutput();

    // All writes here are buffered by the terminal
    conn.write("Line 1\n");
    conn.write("Line 2\n");
    conn.write("Line 3\n");

    conn.disableSynchronizedOutput();
    // Terminal paints all three lines at once
}

Terminal Support

Mode 2026 is supported by the following terminals:

TerminalSupportedNotes
KittyYesSince early versions
GhosttyYesSince 1.0.0
WezTermYesFull support
footYesFull support
ContourYesOrigin of the specification
iTerm2YesFull support
MinttyYesFull support
xtermNoSequences ignored safely
GNOME TerminalNoSequences ignored safely
KonsoleNoSequences ignored safely
AlacrittyNoSequences ignored safely

On unsupported terminals, the BSU/ESU sequences are silently ignored, so applications can always send them without feature detection.

Connection API

The Connection interface provides four methods for synchronized output:

Checking Support

// Heuristic check based on terminal type detection
boolean supported = connection.supportsSynchronizedOutput();

This uses environment-based terminal detection via TerminalEnvironment and the Device.TerminalType enum. No terminal query is sent.

Runtime Query (DECRPM)

For authoritative detection, query the terminal directly using DECRQM/DECRPM:

// Send CSI ? 2026 $ p and parse the DECRPM response
// Returns true (supported), false (not supported), or null (timeout)
Boolean result = connection.querySynchronizedOutput(500);

if (Boolean.TRUE.equals(result)) {
    // Terminal definitively supports Mode 2026
}

The DECRPM response CSI ? 2026 ; Ps $ y uses these Ps values:

PsMeaningResult
0Not recognizedfalse
1Set (enabled)true
2Reset (recognized but disabled)true
3Permanently settrue
4Permanently resetfalse

Enable / Disable

connection.enableSynchronizedOutput();   // Send ESC[?2026h
connection.disableSynchronizedOutput();  // Send ESC[?2026l

Both methods return the Connection for chaining.

Automatic Readline Integration

When using the Readline class, synchronized output is automatically applied. If the terminal supports Mode 2026, readline wraps prompt drawing, action execution, and resize redraws with BSU/ESU sequences. No application code is needed.

Readline readline = new Readline();

// Synchronized output is enabled automatically for supporting terminals
readline.readline(connection, new Prompt("$ "), input -> {
    // handle input
});

Opting Out

To disable automatic synchronized output, set the NO_SYNCHRONIZED_OUTPUT flag:

import org.aesh.readline.ReadlineFlag;

EnumMap<ReadlineFlag, Integer> flags = new EnumMap<>(ReadlineFlag.class);
flags.put(ReadlineFlag.NO_SYNCHRONIZED_OUTPUT, 0);

readline.readline(connection, new Prompt("$ "), input -> {
    // handle input
}, null, null, null, null, flags);

Manual Usage

For applications that manage their own rendering loop (outside of Readline), use the Connection methods directly or the ANSI constants:

// Begin synchronized update — terminal starts buffering
connection.enableSynchronizedOutput();

// Each write is buffered by the terminal, not painted yet
connection.write("\u001B[2J");           // Clear screen
connection.write("\u001B[1;1H");         // Move to top-left
connection.write("Header line");
connection.write("\u001B[2;1H");
connection.write("Content line");
// ... more rendering ...

// End synchronized update — terminal paints everything at once
connection.disableSynchronizedOutput();

There is no need to batch writes into a StringBuilder — the terminal itself buffers all output between BSU and ESU. Multiple write() calls are fine.

ANSI Constants

ConstantValueDescription
ANSI.MODE_2026_QUERYESC[?2026$pDECRQM query
ANSI.MODE_2026_ENABLEESC[?2026hBegin synchronized update (BSU)
ANSI.MODE_2026_DISABLEESC[?2026lEnd synchronized update (ESU)

DECRPM Response Parser

Parse a raw terminal DECRPM response:

// Parse ESC[?2026;Ps$y response
String response = "\u001B[?2026;2$y";
int[] codePoints = response.codePoints().toArray();

Boolean supported = ANSI.parseMode2026Response(codePoints);
// true for Ps=1,2,3; false for Ps=0,4; null for invalid

The parser handles leading noise and trailing data, which is common in real terminal I/O.

Example: Bouncing Image Demo

The SyncImageDemoExample demonstrates synchronized output with inline image display. An image bounces around the screen like a screensaver — tearing is immediately visible when synchronized output is toggled off.

mvn exec:java -pl terminal-tty \
  -Dexec.mainClass="org.aesh.terminal.tty.example.SyncImageDemoExample" \
  -Dexec.args="/path/to/image.jpg"
KeyAction
SToggle synchronized output on/off
+ / -Zoom image in/out
< / >Decrease/increase bounce speed
QQuit

Source: SyncImageDemoExample.java

There is also a simpler dashboard demo without images:

mvn exec:java -pl terminal-tty \
  -Dexec.mainClass="org.aesh.terminal.tty.example.SynchronizedOutputExample"

Source: SynchronizedOutputExample.java

Comparison with Mode 2027

Mode 2026 (synchronized output) and Mode 2027 (grapheme cluster segmentation) are independent features that address different problems:

Mode 2026Mode 2027
PurposePrevent screen tearingCorrect cursor positioning for complex characters
ScopePer-frame toggle (BSU/ESU)Persistent mode (enable once at startup)
CleanupNone needed — each ESU completesMust disable before exit
EffectTerminal buffers outputTerminal uses UAX #29 segmentation

Both modes are automatically managed by Readline when supported.

Specification

The synchronized output protocol was originally specified by the Contour terminal:

Contour VT Extension: Synchronized Output

See Also