From fb3e3940dbe49032809d5b7f353ffd2015a11a8c Mon Sep 17 00:00:00 2001 From: chris west Date: Thu, 14 May 2020 11:24:23 -0700 Subject: [PATCH] enter raw mode on our own termion wants you to pass around a RawTerminal struct that wraps Stdout, which is probably "right" but it's easier to do it the C way and treat stdout as a shared global resource. --- src/terminal.rs | 81 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 77 insertions(+), 4 deletions(-) diff --git a/src/terminal.rs b/src/terminal.rs index a564996..ad61381 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -1,9 +1,10 @@ //! The terminal module mostly provides terminal escape sequences for -//! things like clearing the screen or going into alternate mode. -//! -//! It wraps termion for now, but we may move away from termion in the -//! future and this will help. +//! things like clearing the screen or going into alternate mode, as +//! well as raw mode borrowed from crossterm. +use lazy_static::lazy_static; +use libc::{cfmakeraw, tcgetattr, tcsetattr, termios as Termios, STDIN_FILENO, TCSANOW}; +use std::{io, sync::Mutex}; use termion; pub use termion::cursor::Goto; @@ -14,5 +15,77 @@ pub use termion::screen::ToAlternateScreen; pub use termion::screen::ToMainScreen; pub use termion::clear::AfterCursor as ClearAfterCursor; +pub use termion::clear::All as ClearAll; pub use termion::clear::CurrentLine as ClearCurrentLine; pub use termion::clear::UntilNewline as ClearUntilNewline; + +type Result = std::result::Result; + +lazy_static! { + // Some(Termios) -> we're in the raw mode and this is the previous mode + // None -> we're not in the raw mode + static ref TERMINAL_MODE_PRIOR_RAW_MODE: Mutex> = Mutex::new(None); +} + +/// Are we in raw mode? +pub fn is_raw_mode_enabled() -> bool { + TERMINAL_MODE_PRIOR_RAW_MODE.lock().unwrap().is_some() +} + +/// Go into "raw" mode, courtesy of crossterm. +pub fn enable_raw_mode() -> Result<()> { + let mut original_mode = TERMINAL_MODE_PRIOR_RAW_MODE.lock().unwrap(); + + if original_mode.is_some() { + return Ok(()); + } + + let mut ios = get_terminal_attr()?; + let original_mode_ios = ios; + + raw_terminal_attr(&mut ios); + set_terminal_attr(&ios)?; + + // Keep it last - set the original mode only if we were able to switch to the raw mode + *original_mode = Some(original_mode_ios); + + Ok(()) +} + +/// Back it up. +pub fn disable_raw_mode() -> Result<()> { + let mut original_mode = TERMINAL_MODE_PRIOR_RAW_MODE.lock().unwrap(); + + if let Some(original_mode_ios) = original_mode.as_ref() { + set_terminal_attr(original_mode_ios)?; + // Keep it last - remove the original mode only if we were able to switch back + *original_mode = None; + } + + Ok(()) +} + +// Transform the given mode into an raw mode (non-canonical) mode. +fn raw_terminal_attr(termios: &mut Termios) { + unsafe { cfmakeraw(termios) } +} + +fn get_terminal_attr() -> Result { + unsafe { + let mut termios = std::mem::zeroed(); + wrap_with_result(tcgetattr(STDIN_FILENO, &mut termios))?; + Ok(termios) + } +} + +fn set_terminal_attr(termios: &Termios) -> Result { + wrap_with_result(unsafe { tcsetattr(STDIN_FILENO, TCSANOW, termios) }) +} + +fn wrap_with_result(result: i32) -> Result { + if result == -1 { + Err(io::Error::last_os_error()) + } else { + Ok(true) + } +}