- Since I am neither a developer nor a daily English speaker, most of the content in this issue was generated by an LLM. Although I have done my best to polish it, and I think I know what I'm writing down, basically. Some of the "conclusions" are better described as LLM speculations rather than definitive conclusions. I apologize for any potential misleading information or awkward phrasing.
Double-width continuation cell ch not zeroed. screen_write() does not clear ch in continuation cells of double-width characters.
Symptoms
When displaying dense CJK text through a terminal multiplexer (tmux, zellij) in a TSM-based terminal (e.g., kmscon), random ASCII characters (typically the first few lines.) appear overlaying the right half of CJK glyphs. The corruption shifts or temporarily clears with scrolling.
Example:
Some output should be:
but renders as something like below:
or maybe more clearly:
turns into:
...Where the digit occupies the right-half position of the preceding CJK character.
Reproduction
- Run kmscon
- Start tmux or zellij
- Display a file with dense CJK text (
cat some-chinese-document.txt)
- Observe rendering corruption as described above
- The same file displayed directly (without multiplexer) may show the bug less frequently, but the root cause is identical. I guess.
Root Cause
In src/tsm/tsm-screen.c, function screen_write() (~line 400):
for (i = 1; i < len && i + x < con->size_x; ++i) {
line->cells[x + i].age = con->age_cnt;
line->cells[x + i].width = 0;
// Here: ch is not zeroed — retains previous cell content
}
When writing a double-width character (len == 2), the continuation cell at x+1 correctly gets width = 0 but ch is left as whatever value was previously stored there (e.g., ASCII '7' = 0x37).
Later, tsm_screen_draw2() in src/tsm/tsm-render.c outputs:
out->ch = cell->ch ? cell->ch : ' ';
Since cell->ch is non-zero, the stale character is passed to the renderer as a legitimate cell. The renderer then draws it, overwriting the right half of the double-width glyph.
Why multiplexers trigger this more
Terminal multiplexers (tmux, zellij) frequently reposition the cursor and write to arbitrary screen regions. They reuse cells that previously held single-width characters. A CJK character written to such a position inherits the stale ch in its continuation cell.
Non-multiplexer programs doing sequential line output are less affected because continuation cells tend to be naturally cleared by adjacent writes or space-filling, but the bug can still manifest with programs using cursor movement or hitting line-wrap boundaries.
The Fix I tried
One-line addition in src/tsm/tsm-screen.c:
--- a/src/tsm/tsm-screen.c
+++ b/src/tsm/tsm-screen.c
@@ -405,6 +405,7 @@ static void screen_write(struct tsm_screen *con, unsigned int x,
for (i = 1; i < len && i + x < con->size_x; ++i) {
line->cells[x + i].age = con->age_cnt;
line->cells[x + i].width = 0;
+ line->cells[x + i].ch = 0;
}
}
Version
- libtsm 4.6.0 (tag
v4.6.0, commit e1e4d29)
- Also present on
main (commit 4e7200a, 2026-06-25)
Verification
Patched libtsm was tested with kmscon v10.0.1 + tmux and zellij (and without terminal multiplexers). CJK rendering is correct in all scenarios. No regressions observed.
Double-width continuation cell
chnot zeroed.screen_write()does not clearchin continuation cells of double-width characters.Symptoms
When displaying dense CJK text through a terminal multiplexer (tmux, zellij) in a TSM-based terminal (e.g., kmscon), random ASCII characters (typically the first few lines.) appear overlaying the right half of CJK glyphs. The corruption shifts or temporarily clears with scrolling.
Example:
Some output should be:
but renders as something like below:
or maybe more clearly:
turns into:
...Where the digit occupies the right-half position of the preceding CJK character.
Reproduction
cat some-chinese-document.txt)Root Cause
In
src/tsm/tsm-screen.c, functionscreen_write()(~line 400):When writing a double-width character (
len == 2), the continuation cell atx+1correctly getswidth = 0butchis left as whatever value was previously stored there (e.g., ASCII'7'= 0x37).Later,
tsm_screen_draw2()insrc/tsm/tsm-render.coutputs:Since
cell->chis non-zero, the stale character is passed to the renderer as a legitimate cell. The renderer then draws it, overwriting the right half of the double-width glyph.Why multiplexers trigger this more
Terminal multiplexers (tmux, zellij) frequently reposition the cursor and write to arbitrary screen regions. They reuse cells that previously held single-width characters. A CJK character written to such a position inherits the stale
chin its continuation cell.Non-multiplexer programs doing sequential line output are less affected because continuation cells tend to be naturally cleared by adjacent writes or space-filling, but the bug can still manifest with programs using cursor movement or hitting line-wrap boundaries.
The Fix I tried
One-line addition in
src/tsm/tsm-screen.c:Version
v4.6.0, commite1e4d29)main(commit4e7200a, 2026-06-25)Verification
Patched libtsm was tested with kmscon v10.0.1 + tmux and zellij (and without terminal multiplexers). CJK rendering is correct in all scenarios. No regressions observed.