Fix TUI apps (mc, htop, vim) freezing in SSH terminal
- Remove LNM mode that corrupted cursor positioning for TUI programs - Add render debouncing (~60fps) to prevent UI thread blocking - Add data batching in terminal tab to reduce render calls - Increase SSH recv buffer from 4KB to 64KB Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -114,7 +114,7 @@ class ShellSession:
|
||||
try:
|
||||
while self._running:
|
||||
try:
|
||||
data = self._channel.recv(4096)
|
||||
data = self._channel.recv(65536)
|
||||
if not data:
|
||||
break
|
||||
if self.on_data:
|
||||
|
||||
@@ -30,6 +30,10 @@ class TerminalTab(ctk.CTkFrame):
|
||||
self._terminal.pack(fill="both", expand=True, padx=5, pady=5)
|
||||
self._terminal.set_status(t("term_disconnected"), "#888888")
|
||||
|
||||
# Data batching buffer
|
||||
self._data_buffer = bytearray()
|
||||
self._flush_after_id = None
|
||||
|
||||
def set_server(self, alias: str | None):
|
||||
if alias == self._current_alias:
|
||||
return
|
||||
@@ -84,7 +88,16 @@ class TerminalTab(ctk.CTkFrame):
|
||||
self._session = None
|
||||
|
||||
def _on_data_received(self, data: bytes):
|
||||
self.after(0, lambda d=data: self._terminal.feed(d))
|
||||
self._data_buffer.extend(data)
|
||||
if self._flush_after_id is None:
|
||||
self._flush_after_id = self.after(8, self._flush_data_buffer)
|
||||
|
||||
def _flush_data_buffer(self):
|
||||
self._flush_after_id = None
|
||||
if self._data_buffer:
|
||||
chunk = bytes(self._data_buffer)
|
||||
self._data_buffer.clear()
|
||||
self._terminal.feed(chunk)
|
||||
|
||||
def _on_disconnected(self):
|
||||
if self._intentional_disconnect:
|
||||
|
||||
@@ -73,12 +73,15 @@ class TerminalWidget(tk.Frame):
|
||||
|
||||
# pyte screen + stream
|
||||
self._screen = pyte.Screen(cols, rows)
|
||||
self._screen.set_mode(pyte.modes.LNM)
|
||||
self._stream = pyte.Stream(self._screen)
|
||||
|
||||
# Previous buffer for diff rendering
|
||||
self._prev_buffer: dict[int, list] = {}
|
||||
|
||||
# Render debouncing (~60fps)
|
||||
self._render_pending = False
|
||||
self._render_after_id = None
|
||||
|
||||
# Font
|
||||
self._font = tkfont.Font(family="Consolas", size=11)
|
||||
self._bold_font = tkfont.Font(family="Consolas", size=11, weight="bold")
|
||||
@@ -147,12 +150,20 @@ class TerminalWidget(tk.Frame):
|
||||
self._status_label.configure(text=text, fg=color)
|
||||
|
||||
def feed(self, data: bytes):
|
||||
"""Feed raw bytes from SSH into pyte and re-render."""
|
||||
"""Feed raw bytes from SSH into pyte, schedule debounced render."""
|
||||
try:
|
||||
text = data.decode("utf-8", errors="replace")
|
||||
except Exception:
|
||||
text = data.decode("latin-1", errors="replace")
|
||||
self._stream.feed(text)
|
||||
if not self._render_pending:
|
||||
self._render_pending = True
|
||||
self._render_after_id = self.after(16, self._debounced_render)
|
||||
|
||||
def _debounced_render(self):
|
||||
"""Execute pending render."""
|
||||
self._render_pending = False
|
||||
self._render_after_id = None
|
||||
self._render()
|
||||
|
||||
def reset(self):
|
||||
|
||||
Reference in New Issue
Block a user