From 1146f42174d8feda1fbe590757b0fd1651b8e3d7 Mon Sep 17 00:00:00 2001 From: chris west Date: Thu, 14 May 2020 11:25:49 -0700 Subject: [PATCH] raw mode in main. fixes panics --- src/main.rs | 42 +++++++++++++++++++++++++++++++-- src/ui.rs | 68 +++++++++-------------------------------------------- 2 files changed, 51 insertions(+), 59 deletions(-) diff --git a/src/main.rs b/src/main.rs index 82d8ca0..b347be3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,13 @@ use phetch::{ - args, gopher, menu, + args, color, gopher, menu, terminal, ui::{Mode, UI}, }; -use std::{env, error::Error, io, process}; +use std::{ + env, + error::Error, + io::{self, stdout, Write}, + panic, process, +}; fn main() { if let Err(e) = run() { @@ -39,7 +44,9 @@ fn run() -> Result<(), Box> { } // run app + setup_terminal(); ui.run()?; + cleanup_terminal(); Ok(()) } @@ -119,3 +126,34 @@ fn print_plain(url: &str, tls: bool, tor: bool) -> Result<(), Box> { print!("{}", out); Ok(()) } + +/// Put the terminal into raw mode, enter the alternate screen, and +/// setup the panic handler. +fn setup_terminal() { + let old_handler = panic::take_hook(); + panic::set_hook(Box::new(move |info| { + cleanup_terminal(); + old_handler(info); + })); + + terminal::enable_raw_mode().expect("Fatal Error entering Raw Mode."); + write!(stdout(), "{}", terminal::ToAlternateScreen) + .expect("Fatal Error entering Alternate Mode."); +} + +/// Leave raw mode. Need to always do this, even on panic. +fn cleanup_terminal() { + let mut stdout = stdout(); + write!( + stdout, + "{}{}{}{}{}", + color::Reset, + terminal::ClearAll, + terminal::Goto(1, 1), + terminal::ShowCursor, + terminal::ToMainScreen + ) + .expect("Fatal Error cleaning up terminal."); + stdout.flush().expect("Fatal Error cleaning up terminal."); + terminal::disable_raw_mode().expect("Fatal Error leaving Raw Mode."); +} diff --git a/src/ui.rs b/src/ui.rs index 3dc1101..89562c4 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -29,8 +29,7 @@ use crate::{ use lazy_static; use libc; use std::{ - cell::RefCell, - io::{stdin, stdout, Result, Stdout, Write}, + io::{stdin, stdout, Result, Write}, process::{self, Stdio}, sync::{ mpsc::{channel, Receiver, Sender}, @@ -39,11 +38,7 @@ use std::{ thread, time::Duration, }; -use termion::{ - input::TermRead, - raw::{IntoRawMode, RawTerminal}, - terminal_size, -}; +use termion::{input::TermRead, terminal_size}; /// Alias for a termion Key event. pub type Key = termion::event::Key; @@ -63,7 +58,6 @@ pub const MAX_COLS: usize = 77; /// (network, parsing gopher response, etc) and just show an error /// message in the status bar, but if we can't write to STDOUT or /// control the screen, we need to just crash. -const ERR_RAW_MODE: &str = "Fatal Error using Raw Mode."; const ERR_SCREEN: &str = "Fatal Error using Alternate Screen."; const ERR_STDOUT: &str = "Fatal Error writing to STDOUT."; @@ -96,8 +90,6 @@ pub struct UI { status: String, /// User config. Command line options + phetch.conf config: Config, - /// Reference to our wrapped Stdout. - out: RefCell>, /// Channel where UI events are sent. keys: KeyReceiver, } @@ -110,12 +102,6 @@ impl UI { size = (cols as usize, rows as usize); }; - // Store raw terminal but don't enable it yet or switch the - // screen. We don't want to stare at a fully blank screen - // while waiting for a slow page to load. - let out = stdout().into_raw_mode().expect(ERR_RAW_MODE); - out.suspend_raw_mode().expect(ERR_RAW_MODE); - UI { views: vec![], focused: 0, @@ -124,51 +110,25 @@ impl UI { size, config, status: String::new(), - out: RefCell::new(out), keys: Self::spawn_keyboard_listener(), } } - /// Prepare stdout for writing. Should be used in interactive - /// mode, eg inside run() - pub fn startup(&mut self) { - let mut out = self.out.borrow_mut(); - out.activate_raw_mode().expect(ERR_RAW_MODE); - write!(out, "{}", terminal::ToAlternateScreen).expect(ERR_SCREEN); - } - - /// Clean up after ourselves. Should only be used after running in - /// interactive mode. - pub fn shutdown(&mut self) { - let mut out = self.out.borrow_mut(); - write!( - out, - "{}{}{}", - color::Reset, - terminal::ShowCursor, - terminal::ToMainScreen - ) - .expect(ERR_STDOUT); - out.flush().expect(ERR_STDOUT); - } - /// Main loop. pub fn run(&mut self) -> Result<()> { - self.startup(); while self.running { self.draw()?; self.update(); } - self.shutdown(); Ok(()) } /// Print the current view to the screen in rendered form. pub fn draw(&mut self) -> Result<()> { let status = self.render_status(); + let mut out = stdout(); if self.dirty { let screen = self.render()?; - let mut out = self.out.borrow_mut(); write!( out, "{}{}{}{}", @@ -180,7 +140,6 @@ impl UI { out.flush()?; self.dirty = false; } else { - let mut out = self.out.borrow_mut(); out.write_all(status.as_ref())?; out.flush()?; } @@ -431,7 +390,7 @@ impl UI { fn confirm(&self, question: &str) -> bool { let rows = self.rows(); - let mut out = self.out.borrow_mut(); + let mut out = stdout(); write!( out, "{}{}{}{} [Y/n]: {}", @@ -460,7 +419,7 @@ impl UI { let rows = self.rows(); let mut input = value.to_string(); - let mut out = self.out.borrow_mut(); + let mut out = stdout(); write!( out, "{}{}{}{}{}{}", @@ -528,8 +487,8 @@ impl UI { /// Opens an interactive telnet session. fn telnet(&mut self, url: &str) -> Result<()> { let gopher::Url { host, port, .. } = gopher::parse_url(url); - let out = self.out.borrow_mut(); - out.suspend_raw_mode().expect(ERR_RAW_MODE); + + terminal::disable_raw_mode()?; let mut cmd = process::Command::new("telnet") .arg(host) .arg(port) @@ -537,8 +496,9 @@ impl UI { .stdout(Stdio::inherit()) .spawn()?; cmd.wait()?; - out.activate_raw_mode().expect(ERR_RAW_MODE); + terminal::enable_raw_mode()?; self.dirty = true; // redraw when finished with session + Ok(()) } @@ -576,7 +536,7 @@ impl UI { /// Ctrl-Z: Suspend Unix process w/ SIGTSTP. fn suspend(&mut self) { - let mut out = self.out.borrow_mut(); + let mut out = stdout(); write!(out, "{}", terminal::ToMainScreen).expect(ERR_SCREEN); out.flush().expect(ERR_STDOUT); unsafe { libc::raise(libc::SIGTSTP) }; @@ -602,7 +562,7 @@ impl UI { Action::Error(e) => return Err(error!(e)), Action::Redraw => self.dirty = true, Action::Draw(s) => { - let mut out = self.out.borrow_mut(); + let mut out = stdout(); out.write_all(s.as_ref())?; out.flush()?; } @@ -691,9 +651,3 @@ impl UI { Ok(()) } } - -impl Drop for UI { - fn drop(&mut self) { - self.shutdown(); - } -}