Skip to content

display issues with double-width characters (CJK characters) #55

Description

@draculalaa
  • 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:

12345678
一二一二一

but renders as something like below:

12345678
-2=4-6=8

or maybe more clearly:

2323
终端

turns into:

2323
纟3立3

...Where the digit occupies the right-half position of the preceding CJK character.

Reproduction

  1. Run kmscon
  2. Start tmux or zellij
  3. Display a file with dense CJK text (cat some-chinese-document.txt)
  4. Observe rendering corruption as described above
  5. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions