use crate::interrupt;
use anyhow::{Context, Result};
pub fn line() -> Result<Option<String>> {
let mut line = Vec::new();
loop {
interrupt::check()?;
let mut stdin = libc::pollfd {
fd: 0,
events: libc::POLLIN,
revents: 0,
};
let ready = unsafe { libc::poll(&mut stdin, 1, 100) };
interrupt::check()?;
if ready < 0 {
let error = std::io::Error::last_os_error();
if error.kind() == std::io::ErrorKind::Interrupted {
continue;
}
return Err(error.into());
}
if ready == 0 {
continue;
}
if stdin.revents & libc::POLLNVAL != 0 {
return Err(std::io::Error::from_raw_os_error(libc::EBADF).into());
}
let mut byte = 0u8;
let read = unsafe { libc::read(0, (&mut byte as *mut u8).cast(), 1) };
interrupt::check()?;
if read < 0 {
let error = std::io::Error::last_os_error();
if error.kind() == std::io::ErrorKind::Interrupted {
continue;
}
return Err(error.into());
}
if read == 0 {
if line.is_empty() {
return Ok(None);
}
break;
}
line.push(byte);
if byte == b'\n' {
break;
}
}
let line = String::from_utf8(line).context("terminal input is not UTF-8")?;
interrupt::check()?;
Ok(Some(line.trim_end_matches(['\r', '\n']).to_owned()))
}
pub struct EchoOff {
saved: libc::termios,
}
impl EchoOff {
pub fn new() -> Result<Self> {
let mut saved: libc::termios = unsafe { std::mem::zeroed() };
if unsafe { libc::tcgetattr(0, &mut saved) } != 0 {
return Err(std::io::Error::last_os_error())
.context("can't read the terminal's settings");
}
let echo_off = Self { saved };
let mut hidden = saved;
hidden.c_lflag &= !libc::ECHO;
if unsafe { libc::tcsetattr(0, libc::TCSANOW, &hidden) } != 0 {
return Err(std::io::Error::last_os_error())
.context("can't turn the terminal's echo off");
}
Ok(echo_off)
}
}
impl Drop for EchoOff {
fn drop(&mut self) {
unsafe { libc::tcsetattr(0, libc::TCSANOW, &self.saved) };
}
}