raw mode in main. fixes panics

pull/15/head
chris west 4 years ago
parent fb3e3940db
commit 1146f42174

@ -1,8 +1,13 @@
use phetch::{ use phetch::{
args, gopher, menu, args, color, gopher, menu, terminal,
ui::{Mode, UI}, ui::{Mode, UI},
}; };
use std::{env, error::Error, io, process}; use std::{
env,
error::Error,
io::{self, stdout, Write},
panic, process,
};
fn main() { fn main() {
if let Err(e) = run() { if let Err(e) = run() {
@ -39,7 +44,9 @@ fn run() -> Result<(), Box<dyn Error>> {
} }
// run app // run app
setup_terminal();
ui.run()?; ui.run()?;
cleanup_terminal();
Ok(()) Ok(())
} }
@ -119,3 +126,34 @@ fn print_plain(url: &str, tls: bool, tor: bool) -> Result<(), Box<dyn Error>> {
print!("{}", out); print!("{}", out);
Ok(()) 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.");
}

@ -29,8 +29,7 @@ use crate::{
use lazy_static; use lazy_static;
use libc; use libc;
use std::{ use std::{
cell::RefCell, io::{stdin, stdout, Result, Write},
io::{stdin, stdout, Result, Stdout, Write},
process::{self, Stdio}, process::{self, Stdio},
sync::{ sync::{
mpsc::{channel, Receiver, Sender}, mpsc::{channel, Receiver, Sender},
@ -39,11 +38,7 @@ use std::{
thread, thread,
time::Duration, time::Duration,
}; };
use termion::{ use termion::{input::TermRead, terminal_size};
input::TermRead,
raw::{IntoRawMode, RawTerminal},
terminal_size,
};
/// Alias for a termion Key event. /// Alias for a termion Key event.
pub type Key = termion::event::Key; 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 /// (network, parsing gopher response, etc) and just show an error
/// message in the status bar, but if we can't write to STDOUT or /// message in the status bar, but if we can't write to STDOUT or
/// control the screen, we need to just crash. /// 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_SCREEN: &str = "Fatal Error using Alternate Screen.";
const ERR_STDOUT: &str = "Fatal Error writing to STDOUT."; const ERR_STDOUT: &str = "Fatal Error writing to STDOUT.";
@ -96,8 +90,6 @@ pub struct UI {
status: String, status: String,
/// User config. Command line options + phetch.conf /// User config. Command line options + phetch.conf
config: Config, config: Config,
/// Reference to our wrapped Stdout.
out: RefCell<RawTerminal<Stdout>>,
/// Channel where UI events are sent. /// Channel where UI events are sent.
keys: KeyReceiver, keys: KeyReceiver,
} }
@ -110,12 +102,6 @@ impl UI {
size = (cols as usize, rows as usize); 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 { UI {
views: vec![], views: vec![],
focused: 0, focused: 0,
@ -124,51 +110,25 @@ impl UI {
size, size,
config, config,
status: String::new(), status: String::new(),
out: RefCell::new(out),
keys: Self::spawn_keyboard_listener(), 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. /// Main loop.
pub fn run(&mut self) -> Result<()> { pub fn run(&mut self) -> Result<()> {
self.startup();
while self.running { while self.running {
self.draw()?; self.draw()?;
self.update(); self.update();
} }
self.shutdown();
Ok(()) Ok(())
} }
/// Print the current view to the screen in rendered form. /// Print the current view to the screen in rendered form.
pub fn draw(&mut self) -> Result<()> { pub fn draw(&mut self) -> Result<()> {
let status = self.render_status(); let status = self.render_status();
let mut out = stdout();
if self.dirty { if self.dirty {
let screen = self.render()?; let screen = self.render()?;
let mut out = self.out.borrow_mut();
write!( write!(
out, out,
"{}{}{}{}", "{}{}{}{}",
@ -180,7 +140,6 @@ impl UI {
out.flush()?; out.flush()?;
self.dirty = false; self.dirty = false;
} else { } else {
let mut out = self.out.borrow_mut();
out.write_all(status.as_ref())?; out.write_all(status.as_ref())?;
out.flush()?; out.flush()?;
} }
@ -431,7 +390,7 @@ impl UI {
fn confirm(&self, question: &str) -> bool { fn confirm(&self, question: &str) -> bool {
let rows = self.rows(); let rows = self.rows();
let mut out = self.out.borrow_mut(); let mut out = stdout();
write!( write!(
out, out,
"{}{}{}{} [Y/n]: {}", "{}{}{}{} [Y/n]: {}",
@ -460,7 +419,7 @@ impl UI {
let rows = self.rows(); let rows = self.rows();
let mut input = value.to_string(); let mut input = value.to_string();
let mut out = self.out.borrow_mut(); let mut out = stdout();
write!( write!(
out, out,
"{}{}{}{}{}{}", "{}{}{}{}{}{}",
@ -528,8 +487,8 @@ impl UI {
/// Opens an interactive telnet session. /// Opens an interactive telnet session.
fn telnet(&mut self, url: &str) -> Result<()> { fn telnet(&mut self, url: &str) -> Result<()> {
let gopher::Url { host, port, .. } = gopher::parse_url(url); 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") let mut cmd = process::Command::new("telnet")
.arg(host) .arg(host)
.arg(port) .arg(port)
@ -537,8 +496,9 @@ impl UI {
.stdout(Stdio::inherit()) .stdout(Stdio::inherit())
.spawn()?; .spawn()?;
cmd.wait()?; cmd.wait()?;
out.activate_raw_mode().expect(ERR_RAW_MODE); terminal::enable_raw_mode()?;
self.dirty = true; // redraw when finished with session self.dirty = true; // redraw when finished with session
Ok(()) Ok(())
} }
@ -576,7 +536,7 @@ impl UI {
/// Ctrl-Z: Suspend Unix process w/ SIGTSTP. /// Ctrl-Z: Suspend Unix process w/ SIGTSTP.
fn suspend(&mut self) { fn suspend(&mut self) {
let mut out = self.out.borrow_mut(); let mut out = stdout();
write!(out, "{}", terminal::ToMainScreen).expect(ERR_SCREEN); write!(out, "{}", terminal::ToMainScreen).expect(ERR_SCREEN);
out.flush().expect(ERR_STDOUT); out.flush().expect(ERR_STDOUT);
unsafe { libc::raise(libc::SIGTSTP) }; unsafe { libc::raise(libc::SIGTSTP) };
@ -602,7 +562,7 @@ impl UI {
Action::Error(e) => return Err(error!(e)), Action::Error(e) => return Err(error!(e)),
Action::Redraw => self.dirty = true, Action::Redraw => self.dirty = true,
Action::Draw(s) => { Action::Draw(s) => {
let mut out = self.out.borrow_mut(); let mut out = stdout();
out.write_all(s.as_ref())?; out.write_all(s.as_ref())?;
out.flush()?; out.flush()?;
} }
@ -691,9 +651,3 @@ impl UI {
Ok(()) Ok(())
} }
} }
impl Drop for UI {
fn drop(&mut self) {
self.shutdown();
}
}

Loading…
Cancel
Save