Skip to content
This repository has been archived by the owner on Oct 22, 2019. It is now read-only.

Commit

Permalink
unix: Allow changing any fd to raw mode
Browse files Browse the repository at this point in the history
As discussed in crossterm-rs/crossterm#188,
the terminal might not be stdin.

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
  • Loading branch information
andersk committed Oct 5, 2019
1 parent 152cd56 commit a790285
Showing 1 changed file with 22 additions and 17 deletions.
39 changes: 22 additions & 17 deletions src/sys/unix.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
//! This module contains all `unix` specific terminal related logic.

use std::collections::HashMap;
use std::os::unix::io::{AsRawFd, RawFd};
use std::sync::Mutex;
use std::{io, mem};

pub use libc::termios as Termios;
use libc::{cfmakeraw, tcgetattr, tcsetattr, STDIN_FILENO, TCSANOW};
use libc::{cfmakeraw, tcgetattr, tcsetattr, TCSANOW};

use lazy_static::lazy_static;

Expand All @@ -13,11 +15,14 @@ use crate::{ErrorKind, 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<Option<Termios>> = Mutex::new(None);
static ref TERMINAL_MODE_PRIOR_RAW_MODE: Mutex<HashMap<RawFd, Termios>> = Mutex::new(HashMap::new());
}

pub fn is_raw_mode_enabled() -> bool {
TERMINAL_MODE_PRIOR_RAW_MODE.lock().unwrap().is_some()
pub fn is_raw_mode_enabled(file: &impl AsRawFd) -> bool {
TERMINAL_MODE_PRIOR_RAW_MODE
.lock()
.unwrap()
.contains_key(&file.as_raw_fd())
}

fn wrap_with_result(t: i32) -> Result<()> {
Expand All @@ -33,44 +38,44 @@ pub fn raw_terminal_attr(termios: &mut Termios) {
unsafe { cfmakeraw(termios) }
}

pub fn get_terminal_attr() -> Result<Termios> {
pub fn get_terminal_attr(file: &impl AsRawFd) -> Result<Termios> {
unsafe {
let mut termios = mem::zeroed();
wrap_with_result(tcgetattr(STDIN_FILENO, &mut termios))?;
wrap_with_result(tcgetattr(file.as_raw_fd(), &mut termios))?;
Ok(termios)
}
}

pub fn set_terminal_attr(termios: &Termios) -> Result<()> {
wrap_with_result(unsafe { tcsetattr(STDIN_FILENO, TCSANOW, termios) })
pub fn set_terminal_attr(file: &mut impl AsRawFd, termios: &Termios) -> Result<()> {
wrap_with_result(unsafe { tcsetattr(file.as_raw_fd(), TCSANOW, termios) })
}

pub fn enable_raw_mode() -> Result<()> {
pub fn enable_raw_mode(file: &mut impl AsRawFd) -> Result<()> {
let mut original_mode = TERMINAL_MODE_PRIOR_RAW_MODE.lock().unwrap();

if original_mode.is_some() {
if original_mode.contains_key(&file.as_raw_fd()) {
return Ok(());
}

let mut ios = get_terminal_attr()?;
let mut ios = get_terminal_attr(file)?;
let original_mode_ios = ios;

raw_terminal_attr(&mut ios);
set_terminal_attr(&ios)?;
set_terminal_attr(file, &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);
original_mode.insert(file.as_raw_fd(), original_mode_ios);

Ok(())
}

pub fn disable_raw_mode() -> Result<()> {
pub fn disable_raw_mode(file: &mut impl AsRawFd) -> 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)?;
if let Some(original_mode_ios) = original_mode.get(&file.as_raw_fd()) {
set_terminal_attr(file, original_mode_ios)?;
// Keep it last - remove the original mode only if we were able to switch back
*original_mode = None;
original_mode.remove(&file.as_raw_fd());
}

Ok(())
Expand Down

0 comments on commit a790285

Please sign in to comment.