Problem / Background
After running all-smi on Linux and quitting with q, the terminal cursor remains hidden in the host shell. The user must manually run tput cnorm or reset to restore it. htop does not exhibit this behavior because it explicitly re-enables cursor visibility on exit (via ncurses endwin() which emits \e[?25h), and additionally installs signal handlers so SIGINT/SIGTERM/SIGSEGV still restore the cursor.
The bug is invisible on macOS and on some terminal emulators (notably iTerm2 and certain xterm builds) because those terminals happen to save/restore cursor visibility together with the alternate-screen buffer. On the Linux console (TTY), VTE-based terminals (GNOME Terminal, Konsole, etc.), and inside tmux/screen, the two states are independent — so the cursor stays hidden after exit.
Root Cause
src/view/ui_loop.rs lines 105-114 send cursor::Hide once at session start. The inline comment claims the cursor will be restored by TerminalManager::drop() via LeaveAlternateScreen:
// Hide cursor once at session start. The cursor is restored in
// TerminalManager::drop() (LeaveAlternateScreen resets it).
// This avoids per-frame Hide/Show churn.
But src/view/terminal_manager.rs lines 64-73 only send LeaveAlternateScreen + DisableMouseCapture + disable_raw_mode() — there is no cursor::Show:
impl Drop for TerminalManager {
fn drop(&mut self) {
if self.initialized {
let mut stdout = stdout();
let _ = execute!(stdout, LeaveAlternateScreen, DisableMouseCapture);
let _ = disable_raw_mode();
}
}
}
The assumption that LeaveAlternateScreen (CSI ?1049l) restores cursor visibility (CSI ?25h) is wrong in the general case. By the DEC spec these are independent terminal modes, and only some terminals couple them. The current code relies on that coincidence, so the bug only manifests on terminals where the modes are truly independent — Linux TTY, VTE-based terminals, tmux/screen, etc.
Proposed Solution
-
In src/view/terminal_manager.rs Drop impl, explicitly send cursor::Show along with LeaveAlternateScreen and DisableMouseCapture. Example:
let _ = execute!(stdout, cursor::Show, LeaveAlternateScreen, DisableMouseCapture);
Import crossterm::cursor accordingly.
-
Make sure cursor restoration also runs on abnormal exit paths so the terminal isn't left broken when Drop doesn't run:
- Install a panic hook that emits
cursor::Show, LeaveAlternateScreen, DisableMouseCapture, and disable_raw_mode() before delegating to the default panic handler.
- Install a Ctrl-C / signal handler (e.g.
ctrlc crate or a signal stream) that performs the same cleanup before exiting. Keep this idempotent so the normal Drop path is still safe.
-
Verify on Linux TTY, GNOME Terminal, and inside tmux that the cursor is visible after q, after Ctrl-C, and after an induced panic.
Acceptance Criteria
Technical Considerations
crossterm::cursor::Show maps to CSI ?25h and is safe to emit unconditionally on exit, including in environments where the cursor is already visible.
- Panic hook should preserve and delegate to the existing default panic handler so backtraces and debug output are not lost.
- Signal handler should set a flag and trigger cleanup rather than performing IO directly inside the handler when possible, but
crossterm IO and disable_raw_mode are commonly used in such handlers and are acceptable here.
- Reference:
htop does the equivalent via ncurses endwin() (which calls curs_set(1)) and registers handlers for SIGINT/SIGTERM/SIGSEGV.
Priority
Marked priority:medium — the cursor is recoverable with tput cnorm or reset, but it's a visible regression that affects every Linux/tmux user on every exit.
Problem / Background
After running
all-smion Linux and quitting withq, the terminal cursor remains hidden in the host shell. The user must manually runtput cnormorresetto restore it.htopdoes not exhibit this behavior because it explicitly re-enables cursor visibility on exit (via ncursesendwin()which emits\e[?25h), and additionally installs signal handlers so SIGINT/SIGTERM/SIGSEGV still restore the cursor.The bug is invisible on macOS and on some terminal emulators (notably iTerm2 and certain xterm builds) because those terminals happen to save/restore cursor visibility together with the alternate-screen buffer. On the Linux console (TTY), VTE-based terminals (GNOME Terminal, Konsole, etc.), and inside tmux/screen, the two states are independent — so the cursor stays hidden after exit.
Root Cause
src/view/ui_loop.rslines 105-114 sendcursor::Hideonce at session start. The inline comment claims the cursor will be restored byTerminalManager::drop()viaLeaveAlternateScreen:But
src/view/terminal_manager.rslines 64-73 only sendLeaveAlternateScreen+DisableMouseCapture+disable_raw_mode()— there is nocursor::Show:The assumption that
LeaveAlternateScreen(CSI ?1049l) restores cursor visibility (CSI ?25h) is wrong in the general case. By the DEC spec these are independent terminal modes, and only some terminals couple them. The current code relies on that coincidence, so the bug only manifests on terminals where the modes are truly independent — Linux TTY, VTE-based terminals, tmux/screen, etc.Proposed Solution
In
src/view/terminal_manager.rsDropimpl, explicitly sendcursor::Showalong withLeaveAlternateScreenandDisableMouseCapture. Example:Import
crossterm::cursoraccordingly.Make sure cursor restoration also runs on abnormal exit paths so the terminal isn't left broken when
Dropdoesn't run:cursor::Show,LeaveAlternateScreen,DisableMouseCapture, anddisable_raw_mode()before delegating to the default panic handler.ctrlccrate or a signal stream) that performs the same cleanup before exiting. Keep this idempotent so the normalDroppath is still safe.Verify on Linux TTY, GNOME Terminal, and inside tmux that the cursor is visible after
q, after Ctrl-C, and after an induced panic.Acceptance Criteria
all-smiwithqon Linux (TTY + at least one VTE terminal + tmux), the shell cursor is visible again.reset).Show/Hidechurn is introduced — the fix only affects setup/teardown paths, not the render loop.Showis safe to emit even when cursor is already visible).Technical Considerations
crossterm::cursor::Showmaps toCSI ?25hand is safe to emit unconditionally on exit, including in environments where the cursor is already visible.crosstermIO anddisable_raw_modeare commonly used in such handlers and are acceptable here.htopdoes the equivalent via ncursesendwin()(which callscurs_set(1)) and registers handlers for SIGINT/SIGTERM/SIGSEGV.Priority
Marked
priority:medium— the cursor is recoverable withtput cnormorreset, but it's a visible regression that affects every Linux/tmux user on every exit.