use std::io::StdinLock;
use std::mem::zeroed;
use std::os::unix::io::RawFd;
use libc::{
c_int, fcntl, isatty, read, tcgetattr, tcsetattr, termios, ECHO, F_GETFL, F_SETFL, ICANON,
ISIG, O_NONBLOCK, STDIN_FILENO, TCSANOW,
};
use crate::errno::{errno_result, Result};
fn modify_mode<F: FnOnce(&mut termios)>(fd: RawFd, f: F) -> Result<()> {
if unsafe { isatty(fd) } != 1 {
return Ok(());
}
let mut termios: termios = unsafe { zeroed() };
let ret = unsafe { tcgetattr(fd, &mut termios as *mut _) };
if ret < 0 {
return errno_result();
}
let mut new_termios = termios;
f(&mut new_termios);
let ret = unsafe { tcsetattr(fd, TCSANOW, &new_termios as *const _) };
if ret < 0 {
return errno_result();
}
Ok(())
}
fn get_flags(fd: RawFd) -> Result<c_int> {
let ret = unsafe { fcntl(fd, F_GETFL) };
if ret < 0 {
return errno_result();
}
Ok(ret)
}
fn set_flags(fd: RawFd, flags: c_int) -> Result<()> {
let ret = unsafe { fcntl(fd, F_SETFL, flags) };
if ret < 0 {
return errno_result();
}
Ok(())
}
pub unsafe trait Terminal {
fn tty_fd(&self) -> RawFd;
fn set_canon_mode(&self) -> Result<()> {
modify_mode(self.tty_fd(), |t| t.c_lflag |= ICANON | ECHO | ISIG)
}
fn set_raw_mode(&self) -> Result<()> {
modify_mode(self.tty_fd(), |t| t.c_lflag &= !(ICANON | ECHO | ISIG))
}
fn set_non_block(&self, non_block: bool) -> Result<()> {
let old_flags = get_flags(self.tty_fd())?;
let new_flags = if non_block {
old_flags | O_NONBLOCK
} else {
old_flags & !O_NONBLOCK
};
if new_flags != old_flags {
set_flags(self.tty_fd(), new_flags)?
}
Ok(())
}
fn read_raw(&self, out: &mut [u8]) -> Result<usize> {
let ret = unsafe { read(self.tty_fd(), out.as_mut_ptr() as *mut _, out.len()) };
if ret < 0 {
return errno_result();
}
Ok(ret as usize)
}
}
unsafe impl<'a> Terminal for StdinLock<'a> {
fn tty_fd(&self) -> RawFd {
STDIN_FILENO
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::File;
use std::io;
use std::os::unix::io::AsRawFd;
use std::path::Path;
unsafe impl Terminal for File {
fn tty_fd(&self) -> RawFd {
self.as_raw_fd()
}
}
#[test]
fn test_a_tty() {
let stdin_handle = io::stdin();
let stdin = stdin_handle.lock();
assert!(stdin.set_canon_mode().is_ok());
assert!(stdin.set_raw_mode().is_ok());
assert!(stdin.set_raw_mode().is_ok());
assert!(stdin.set_canon_mode().is_ok());
assert!(stdin.set_non_block(true).is_ok());
let mut out = [0u8; 0];
assert!(stdin.read_raw(&mut out[..]).is_ok());
}
#[test]
fn test_a_non_tty() {
let file = File::open(Path::new("/dev/zero")).unwrap();
assert!(file.set_canon_mode().is_ok());
}
}