From 7673f297791d1f2e45795dc55280f343dc02568c Mon Sep 17 00:00:00 2001 From: nkraetzschmar <9020053+nkraetzschmar@users.noreply.github.com> Date: Sun, 28 Sep 2025 18:45:43 +0200 Subject: [PATCH] feat: term_emu: allow limited CSI character set to make backspace and cursor work in shell --- term_emu.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/term_emu.py b/term_emu.py index df1376f..4dc8afa 100755 --- a/term_emu.py +++ b/term_emu.py @@ -11,6 +11,32 @@ class EscSeqFilter: def __init__(self): self.state = "TXT" + self.csi = bytearray() + + def _allow_csi(self, seq: bytes) -> bool: + # Allow only: ESC [ [D|C|P] ; ESC [ <0?> K ; ESC [ <0?> J + if not seq.startswith(b"\x1b["): + return False + final = seq[-1:] + body = seq[2:-1] # bytes between '[' and final + + # reject anything with multiple params or private markers + if any(ch in b";?:" for ch in body): + return False + + if final in b"DCP": + # digits-only (or empty) count is fine + return body.isdigit() or body == b"" + + if final == b"K": + # EL: allow empty or 0/1/2 + return body in (b"", b"0", b"1", b"2") or body.isdigit() + + if final == b"J": + # ED: allow only J0 or bare J (default 0). Block J1/J2/J3. + return body in (b"", b"0") + + return False def feed(self, data): out = bytearray() @@ -18,11 +44,12 @@ def feed(self, data): if self.state == "TXT": if ch == 0x1B: self.state = "ESC" - elif 0x20 <= ch <= 0x7F or ch == 0x0A or ch == 0x0D or ch == 0x08: + elif 0x20 <= ch <= 0x7F or ch in (0x0A, 0x0D, 0x08): out.append(ch) elif self.state == "ESC": if ch == ord("["): self.state = "CSI" + self.csi = bytearray(b"\x1b[") elif ch == ord("]"): self.state = "OSC" elif ch == ord("P"): @@ -30,7 +57,10 @@ def feed(self, data): else: self.state = "TXT" elif self.state == "CSI": + self.csi.append(ch) if 0x40 <= ch <= 0x7E: + if self._allow_csi(bytes(self.csi)): + out.extend(self.csi) self.state = "TXT" elif self.state == "OSC": if ch == 0x07: