Fix mnw-cli TUI: alternate screen, terminal init, cleanup - Enter alternate screen buffer and hide cursor on TUI launch - Use Terminal::with_options(Fixed viewport) instead of Terminal::new to avoid crossterm ioctl size query on SSH pipe (EAGAIN) - Add cleanup (restore cursor, leave alternate screen) on all exit paths - Add channel_success response to shell_request
- Co-Authored-By
Author: Max J. <87768334+MaxJMath@users.noreply.github.com> - 2026-04-22 23:18 UTC
Commit:
3297262b7a8f26a34199c234516631d396a2b369Parent:
2 files changed,
+35 insertions,
-7 deletions
OldNewLine
@@ -180,7 +180,8 @@
180
180
181
181
let user_clone = user.clone();182
182
let staging_dir = staging::user_staging_dir(&self.staging_dir, &user.user_id);183
let app_handle = tui::launch(183
184
match tui::launch(184
185
terminal_handle,185
186
user_clone,186
187
cols,@@ -189,9 +190,16 @@
189
190
channel,190
191
self.api.clone(),191
192
staging_dir,192
)?;193
self.app = Some(app_handle);194
session.channel_success(channel)?;193
) {194
Ok(app_handle) => {195
self.app = Some(app_handle);196
session.channel_success(channel)?;197
}198
Err(e) => {199
tracing::error!(error = ?e, "TUI launch failed");200
session.close(channel)?;201
}202
}195
203
196
204
Ok(())197
205
}OldNewLine
@@ -341,9 +341,16 @@
341
341
api: MnwApiClient,342
342
staging_dir: PathBuf,343
343
) -> anyhow::Result<AppHandle> {344
let mut writer = writer;345
// Enter alternate screen, hide cursor, enable raw-like mode346
use std::io::Write;347
let _ = writer.write_all(b"\x1b[?1049h\x1b[?25l\x1b[2J\x1b[H");348
let _ = writer.flush();344
349
let backend = CrosstermBackend::new(writer);345
let mut terminal = Terminal::new(backend)?;346
terminal.resize(ratatui::layout::Rect::new(0, 0, cols, rows))?;350
let options = ratatui::TerminalOptions {351
viewport: ratatui::Viewport::Fixed(ratatui::layout::Rect::new(0, 0, cols, rows)),352
};353
let mut terminal = Terminal::with_options(backend, options)?;347
354
348
355
let (tx, mut rx) = mpsc::channel::<AppEvent>(64);349
356
let handle = AppHandle { tx: tx.clone() };@@ -361,9 +368,18 @@
361
368
let mut screen = Screen::Home;362
369
let staging_dir = staging_dir;363
370
371
/// Write escape codes to leave alternate screen and restore cursor.372
fn cleanup(terminal: &mut Terminal<CrosstermBackend<TerminalHandle>>) {373
use std::io::Write;374
let be = terminal.backend_mut();375
let _ = be.write_all(b"\x1b[?25h\x1b[?1049l");376
let _ = be.flush();377
}378
364
379
// Initial render (loading state)365
380
if let Err(e) = terminal.draw(|frame| home::render(frame, &app)) {366
tracing::error!(error = ?e, "initial render failed");381
tracing::error!(error = ?e, "TUI: initial render failed");382
cleanup(&mut terminal);367
383
return;368
384
}369
385
@@ -380,6 +396,7 @@
380
396
)381
397
{382
398
tracing::info!(user = %app.user.username, "user quit");399
cleanup(&mut terminal);383
400
let _ = session_handle.close(channel_id).await;384
401
return;385
402
}@@ -640,9 +657,12 @@
640
657
Screen::Settings => settings::render(frame, &app),641
658
}) {642
659
tracing::error!(error = ?e, "render failed");660
cleanup(&mut terminal);643
661
return;644
662
}645
663
}664
// Event loop ended (channel dropped)665
cleanup(&mut terminal);646
666
});647
667
648
668
Ok(handle)