Skip to content

Commit

Permalink
Emit the entire set of terminal reset sequences
Browse files Browse the repository at this point in the history
  • Loading branch information
lif committed Feb 9, 2024
1 parent 5b75309 commit bbcdf62
Showing 1 changed file with 54 additions and 11 deletions.
65 changes: 54 additions & 11 deletions src/raw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
);
}
}
}
Expand All @@ -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<Self, Error> {
let termios = unsafe {
let mut curr_termios = std::mem::zeroed();
Expand All @@ -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)
Expand All @@ -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::<terminfo::capability::ExitCaMode>() {
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::<terminfo::capability::Reset1String>() {
rs1.expand().to(&mut stdout).unwrap();
}
if let Some(rs2) = info.get::<terminfo::capability::Reset2String>() {
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::<terminfo::capability::ResetFile>() {
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::<terminfo::capability::Reset3String>() {
rs3.expand().to(&mut stdout).unwrap();
}
// relinquish ownership - we may not want to close on drop
stdout.into_raw_fd();
}
}
}
Expand Down

0 comments on commit bbcdf62

Please sign in to comment.