diff --git a/src/raw.rs b/src/raw.rs index c615726..4eee0fa 100644 --- a/src/raw.rs +++ b/src/raw.rs @@ -90,11 +90,17 @@ mod platform_impl { unsafe { let r = SetConsoleMode(self.in_handle, self.in_mode); if r == 0 { - Err::<(), _>(std::io::Error::last_os_error()).unwrap(); + panic!( + "\r\n{}\r\n", + Error::SetConsoleMode(std::io::Error::last_os_error()) + ); } let r = SetConsoleMode(self.out_handle, self.out_mode); if r == 0 { - Err::<(), _>(std::io::Error::last_os_error()).unwrap(); + panic!( + "\r\n{}\r\n", + Error::SetConsoleMode(std::io::Error::last_os_error()) + ); } } } @@ -103,22 +109,28 @@ mod platform_impl { #[cfg(target_family = "unix")] mod platform_impl { - use std::os::fd::RawFd; + use std::os::fd::{FromRawFd, IntoRawFd, RawFd}; use thiserror::Error; #[derive(Error, Debug)] pub enum Error { #[error("tcgetattr(stdout, termios) call failed: {0}")] TcGetAttr(std::io::Error), - #[error("tcsetattr(stdout, TCSAFLUSH, termios) call failed: {0}")] - TcSetAttr(std::io::Error), + #[error("tcsetattr(stdout, {1}, termios) call failed: {0}")] + TcSetAttr(std::io::Error, &'static str), } /// Guard object that will set the terminal to raw mode and restore it - /// to its previous state when it's dropped. + /// to its previous state when it's dropped. If it is unable to restore + /// the previous termcap state, [Drop::drop] will panic. + /// + /// Additionally, if the terminfo database for the current terminal is + /// available, the reset sequences from it are emitted to return it from + /// any unknown state. Failures in finding or outputting these are ignored. pub struct RawModeGuard(libc::c_int, libc::termios); impl RawModeGuard { + /// Attach to the terminal whose stdout is the given fd. pub(crate) fn new(fd: RawFd) -> Result { let termios = unsafe { let mut curr_termios = std::mem::zeroed(); @@ -134,7 +146,10 @@ mod platform_impl { libc::cfmakeraw(&mut raw_termios); let r = libc::tcsetattr(fd, libc::TCSAFLUSH, &raw_termios); if r == -1 { - return Err(Error::TcSetAttr(std::io::Error::last_os_error())); + return Err(Error::TcSetAttr( + std::io::Error::last_os_error(), + "TCSAFLUSH", + )); } } Ok(guard) @@ -143,16 +158,44 @@ mod platform_impl { impl Drop for RawModeGuard { fn drop(&mut self) { + // reset the termcaps to what they were before we were constructed. + // (similar to `stty sane` if the tty was that way to begin with) let r = unsafe { libc::tcsetattr(self.0, libc::TCSADRAIN, &self.1) }; if r == -1 { - Err::<(), _>(std::io::Error::last_os_error()).unwrap(); + // some \r\n because we might still be in a raw mode... + panic!( + "\r\n{}\r\n", + Error::TcSetAttr(std::io::Error::last_os_error(), "TCSADRAIN") + ); } // if we have a terminfo database and this terminal is in it, - // try to exit the alternate buffer in case we were left there. + // try to emit the strings to reset from an indeterminate state. + // (similar to `tput reset`) if let Ok(info) = terminfo::Database::from_env() { - if let Some(rmcup) = info.get::() { - rmcup.expand().to(std::io::stdout()).unwrap(); + let mut stdout = unsafe { std::fs::File::from_raw_fd(self.0) }; + if let Some(rs1) = info.get::() { + rs1.expand().to(&mut stdout).unwrap(); + } + if let Some(rs2) = info.get::() { + rs2.expand().to(&mut stdout).unwrap(); + } + // the order here comes from the X/Open Curses Issue 4 Version 2 + // technical standard, repeated in the terminfo(5) manpage: + // "Sequences that do a reset from a totally unknown state + // can be given as rs1, rs2, rf and rs3" + if let Some(rf) = info.get::() { + rf.expand() + .to_vec() + .ok() + .and_then(|path_vec| String::from_utf8(path_vec).ok()) + .and_then(|path| std::fs::File::open(path).ok()) + .and_then(|mut f| std::io::copy(&mut f, &mut stdout).ok()); + } + if let Some(rs3) = info.get::() { + rs3.expand().to(&mut stdout).unwrap(); } + // relinquish ownership - we may not want to close on drop + stdout.into_raw_fd(); } } }