From bbd2d2aa6cdfd0cc3d49347257708cec8d343694 Mon Sep 17 00:00:00 2001 From: Sergey Fedorov Date: Sun, 19 Jul 2026 19:24:02 +0000 Subject: [PATCH 37/71] =?UTF-8?q?fix(macos):=20flush=20queued=20VT=20repli?= =?UTF-8?q?es=20after=20each=20parse=20=E2=80=94=20post-sixel=20garbage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit reply() only queues terminal replies (DA/DA2, DECRQSS, color-register and sixel-geometry reports, ...) into the input generator; they reach the PTY only when flushInput() runs, which reply() does not do (absent the CONTOUR_SYNC_PTY_OUTPUT env var). The macOS session never flushed them, so after a sixel the queued replies sat until the next keystroke — whose own flushInput() then wrote the stale replies ahead of the typed byte. The shell received a burst of escape sequences and echoed them as "garbage" on the first input after displaying a sixel. Flush in the parser thread's loop, right after processInputOnce() returns. At that point the parse is complete and _stateMutex is released, so this is NOT the re-entrant screenUpdated() path (a previous attempt flushed from screenUpdated(), which fires mid-parse and re-entered the parser, breaking even block rendering). echoLocally() sees tlParseDepth == 0 here and takes the normal top-level writeToScreen() path under its own lock, exactly like a keystroke's flush. flushInput() no-ops when the queue is empty, so the common path is free. This also delivers replies promptly to applications that block on them before continuing (chafa/sayaka query the terminal before sending a sixel). Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01AeG2jwYMX5gdvvtUnx3PJa --- src/contour_macos/TerminalSession.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/contour_macos/TerminalSession.cpp b/src/contour_macos/TerminalSession.cpp index 4528544c..4f7f73d2 100644 --- a/src/contour_macos/TerminalSession.cpp +++ b/src/contour_macos/TerminalSession.cpp @@ -140,6 +140,17 @@ void TerminalSession::mainLoop() { if (!_terminal->processInputOnce()) break; + + // Terminal replies to VT queries (primary/secondary DA, color-register and sixel-geometry + // reports, DECRQSS, ...) are only QUEUED in the input generator by reply(); they reach the + // PTY when flushInput() runs. Nothing else flushes them here, so without this they sit until + // the next keystroke — whose own flushInput() then writes the stale replies ahead of the + // typed byte, and the shell echoes them as a burst of escape-sequence "garbage" on first + // input after a sixel. Flush here, on the parser thread, once the parse has completed and + // _stateMutex is released: this is not the re-entrant screenUpdated() path (which fires mid + // parse), and flushInput() no-ops when the queue is empty. Applications that block on a reply + // before continuing (chafa/sayaka querying before a sixel) also get their answer promptly. + _terminal->flushInput(); } }