summaryrefslogtreecommitdiffstats
path: root/vendor/console/src/unix_term.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/console/src/unix_term.rs')
-rw-r--r--vendor/console/src/unix_term.rs362
1 files changed, 362 insertions, 0 deletions
diff --git a/vendor/console/src/unix_term.rs b/vendor/console/src/unix_term.rs
new file mode 100644
index 000000000..8e1e59259
--- /dev/null
+++ b/vendor/console/src/unix_term.rs
@@ -0,0 +1,362 @@
+use std::env;
+use std::fmt::Display;
+use std::fs;
+use std::io;
+use std::io::{BufRead, BufReader};
+use std::mem;
+use std::os::unix::io::AsRawFd;
+use std::ptr;
+use std::str;
+
+use crate::kb::Key;
+use crate::term::Term;
+
+pub use crate::common_term::*;
+
+pub const DEFAULT_WIDTH: u16 = 80;
+
+#[inline]
+pub fn is_a_terminal(out: &Term) -> bool {
+ unsafe { libc::isatty(out.as_raw_fd()) != 0 }
+}
+
+pub fn is_a_color_terminal(out: &Term) -> bool {
+ if !is_a_terminal(out) {
+ return false;
+ }
+
+ if env::var("NO_COLOR").is_ok() {
+ return false;
+ }
+
+ match env::var("TERM") {
+ Ok(term) => term != "dumb",
+ Err(_) => false,
+ }
+}
+
+pub fn c_result<F: FnOnce() -> libc::c_int>(f: F) -> io::Result<()> {
+ let res = f();
+ if res != 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(())
+ }
+}
+
+pub fn terminal_size(out: &Term) -> Option<(u16, u16)> {
+ unsafe {
+ if libc::isatty(libc::STDOUT_FILENO) != 1 {
+ return None;
+ }
+
+ let mut winsize: libc::winsize = std::mem::zeroed();
+
+ // FIXME: ".into()" used as a temporary fix for a libc bug
+ // https://github.com/rust-lang/libc/pull/704
+ #[allow(clippy::useless_conversion)]
+ libc::ioctl(out.as_raw_fd(), libc::TIOCGWINSZ.into(), &mut winsize);
+ if winsize.ws_row > 0 && winsize.ws_col > 0 {
+ Some((winsize.ws_row as u16, winsize.ws_col as u16))
+ } else {
+ None
+ }
+ }
+}
+
+pub fn read_secure() -> io::Result<String> {
+ let f_tty;
+ let fd = unsafe {
+ if libc::isatty(libc::STDIN_FILENO) == 1 {
+ f_tty = None;
+ libc::STDIN_FILENO
+ } else {
+ let f = fs::OpenOptions::new()
+ .read(true)
+ .write(true)
+ .open("/dev/tty")?;
+ let fd = f.as_raw_fd();
+ f_tty = Some(BufReader::new(f));
+ fd
+ }
+ };
+
+ let mut termios = core::mem::MaybeUninit::uninit();
+ c_result(|| unsafe { libc::tcgetattr(fd, termios.as_mut_ptr()) })?;
+ let mut termios = unsafe { termios.assume_init() };
+ let original = termios;
+ termios.c_lflag &= !libc::ECHO;
+ c_result(|| unsafe { libc::tcsetattr(fd, libc::TCSAFLUSH, &termios) })?;
+ let mut rv = String::new();
+
+ let read_rv = if let Some(mut f) = f_tty {
+ f.read_line(&mut rv)
+ } else {
+ io::stdin().read_line(&mut rv)
+ };
+
+ c_result(|| unsafe { libc::tcsetattr(fd, libc::TCSAFLUSH, &original) })?;
+
+ read_rv.map(|_| {
+ let len = rv.trim_end_matches(&['\r', '\n'][..]).len();
+ rv.truncate(len);
+ rv
+ })
+}
+
+fn poll_fd(fd: i32, timeout: i32) -> io::Result<bool> {
+ let mut pollfd = libc::pollfd {
+ fd,
+ events: libc::POLLIN,
+ revents: 0,
+ };
+ let ret = unsafe { libc::poll(&mut pollfd as *mut _, 1, timeout) };
+ if ret < 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(pollfd.revents & libc::POLLIN != 0)
+ }
+}
+
+#[cfg(target_os = "macos")]
+fn select_fd(fd: i32, timeout: i32) -> io::Result<bool> {
+ unsafe {
+ let mut read_fd_set: libc::fd_set = mem::zeroed();
+
+ let mut timeout_val;
+ let timeout = if timeout < 0 {
+ ptr::null_mut()
+ } else {
+ timeout_val = libc::timeval {
+ tv_sec: (timeout / 1000) as _,
+ tv_usec: (timeout * 1000) as _,
+ };
+ &mut timeout_val
+ };
+
+ libc::FD_ZERO(&mut read_fd_set);
+ libc::FD_SET(fd, &mut read_fd_set);
+ let ret = libc::select(
+ fd + 1,
+ &mut read_fd_set,
+ ptr::null_mut(),
+ ptr::null_mut(),
+ timeout,
+ );
+ if ret < 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(libc::FD_ISSET(fd, &read_fd_set))
+ }
+ }
+}
+
+fn select_or_poll_term_fd(fd: i32, timeout: i32) -> io::Result<bool> {
+ // There is a bug on macos that ttys cannot be polled, only select()
+ // works. However given how problematic select is in general, we
+ // normally want to use poll there too.
+ #[cfg(target_os = "macos")]
+ {
+ if unsafe { libc::isatty(fd) == 1 } {
+ return select_fd(fd, timeout);
+ }
+ }
+ poll_fd(fd, timeout)
+}
+
+fn read_single_char(fd: i32) -> io::Result<Option<char>> {
+ // timeout of zero means that it will not block
+ let is_ready = select_or_poll_term_fd(fd, 0)?;
+
+ if is_ready {
+ // if there is something to be read, take 1 byte from it
+ let mut buf: [u8; 1] = [0];
+
+ read_bytes(fd, &mut buf, 1)?;
+ Ok(Some(buf[0] as char))
+ } else {
+ //there is nothing to be read
+ Ok(None)
+ }
+}
+
+// Similar to libc::read. Read count bytes into slice buf from descriptor fd.
+// If successful, return the number of bytes read.
+// Will return an error if nothing was read, i.e when called at end of file.
+fn read_bytes(fd: i32, buf: &mut [u8], count: u8) -> io::Result<u8> {
+ let read = unsafe { libc::read(fd, buf.as_mut_ptr() as *mut _, count as usize) };
+ if read < 0 {
+ Err(io::Error::last_os_error())
+ } else if read == 0 {
+ Err(io::Error::new(
+ io::ErrorKind::UnexpectedEof,
+ "Reached end of file",
+ ))
+ } else if buf[0] == b'\x03' {
+ Err(io::Error::new(
+ io::ErrorKind::Interrupted,
+ "read interrupted",
+ ))
+ } else {
+ Ok(read as u8)
+ }
+}
+
+fn read_single_key_impl(fd: i32) -> Result<Key, io::Error> {
+ loop {
+ match read_single_char(fd)? {
+ Some('\x1b') => {
+ // Escape was read, keep reading in case we find a familiar key
+ break if let Some(c1) = read_single_char(fd)? {
+ if c1 == '[' {
+ if let Some(c2) = read_single_char(fd)? {
+ match c2 {
+ 'A' => Ok(Key::ArrowUp),
+ 'B' => Ok(Key::ArrowDown),
+ 'C' => Ok(Key::ArrowRight),
+ 'D' => Ok(Key::ArrowLeft),
+ 'H' => Ok(Key::Home),
+ 'F' => Ok(Key::End),
+ 'Z' => Ok(Key::BackTab),
+ _ => {
+ let c3 = read_single_char(fd)?;
+ if let Some(c3) = c3 {
+ if c3 == '~' {
+ match c2 {
+ '1' => Ok(Key::Home), // tmux
+ '2' => Ok(Key::Insert),
+ '3' => Ok(Key::Del),
+ '4' => Ok(Key::End), // tmux
+ '5' => Ok(Key::PageUp),
+ '6' => Ok(Key::PageDown),
+ '7' => Ok(Key::Home), // xrvt
+ '8' => Ok(Key::End), // xrvt
+ _ => Ok(Key::UnknownEscSeq(vec![c1, c2, c3])),
+ }
+ } else {
+ Ok(Key::UnknownEscSeq(vec![c1, c2, c3]))
+ }
+ } else {
+ // \x1b[ and 1 more char
+ Ok(Key::UnknownEscSeq(vec![c1, c2]))
+ }
+ }
+ }
+ } else {
+ // \x1b[ and no more input
+ Ok(Key::UnknownEscSeq(vec![c1]))
+ }
+ } else {
+ // char after escape is not [
+ Ok(Key::UnknownEscSeq(vec![c1]))
+ }
+ } else {
+ //nothing after escape
+ Ok(Key::Escape)
+ };
+ }
+ Some(c) => {
+ let byte = c as u8;
+ let mut buf: [u8; 4] = [byte, 0, 0, 0];
+
+ break if byte & 224u8 == 192u8 {
+ // a two byte unicode character
+ read_bytes(fd, &mut buf[1..], 1)?;
+ Ok(key_from_utf8(&buf[..2]))
+ } else if byte & 240u8 == 224u8 {
+ // a three byte unicode character
+ read_bytes(fd, &mut buf[1..], 2)?;
+ Ok(key_from_utf8(&buf[..3]))
+ } else if byte & 248u8 == 240u8 {
+ // a four byte unicode character
+ read_bytes(fd, &mut buf[1..], 3)?;
+ Ok(key_from_utf8(&buf[..4]))
+ } else {
+ Ok(match c {
+ '\n' | '\r' => Key::Enter,
+ '\x7f' => Key::Backspace,
+ '\t' => Key::Tab,
+ '\x01' => Key::Home, // Control-A (home)
+ '\x05' => Key::End, // Control-E (end)
+ '\x08' => Key::Backspace, // Control-H (8) (Identical to '\b')
+ _ => Key::Char(c),
+ })
+ };
+ }
+ None => {
+ // there is no subsequent byte ready to be read, block and wait for input
+ // negative timeout means that it will block indefinitely
+ match select_or_poll_term_fd(fd, -1) {
+ Ok(_) => continue,
+ Err(_) => break Err(io::Error::last_os_error()),
+ }
+ }
+ }
+ }
+}
+
+pub fn read_single_key() -> io::Result<Key> {
+ let tty_f;
+ let fd = unsafe {
+ if libc::isatty(libc::STDIN_FILENO) == 1 {
+ libc::STDIN_FILENO
+ } else {
+ tty_f = fs::OpenOptions::new()
+ .read(true)
+ .write(true)
+ .open("/dev/tty")?;
+ tty_f.as_raw_fd()
+ }
+ };
+ let mut termios = core::mem::MaybeUninit::uninit();
+ c_result(|| unsafe { libc::tcgetattr(fd, termios.as_mut_ptr()) })?;
+ let mut termios = unsafe { termios.assume_init() };
+ let original = termios;
+ unsafe { libc::cfmakeraw(&mut termios) };
+ termios.c_oflag = original.c_oflag;
+ c_result(|| unsafe { libc::tcsetattr(fd, libc::TCSADRAIN, &termios) })?;
+ let rv: io::Result<Key> = read_single_key_impl(fd);
+ c_result(|| unsafe { libc::tcsetattr(fd, libc::TCSADRAIN, &original) })?;
+
+ // if the user hit ^C we want to signal SIGINT to outselves.
+ if let Err(ref err) = rv {
+ if err.kind() == io::ErrorKind::Interrupted {
+ unsafe {
+ libc::raise(libc::SIGINT);
+ }
+ }
+ }
+
+ rv
+}
+
+pub fn key_from_utf8(buf: &[u8]) -> Key {
+ if let Ok(s) = str::from_utf8(buf) {
+ if let Some(c) = s.chars().next() {
+ return Key::Char(c);
+ }
+ }
+ Key::Unknown
+}
+
+#[cfg(not(target_os = "macos"))]
+lazy_static::lazy_static! {
+ static ref IS_LANG_UTF8: bool = match std::env::var("LANG") {
+ Ok(lang) => lang.to_uppercase().ends_with("UTF-8"),
+ _ => false,
+ };
+}
+
+#[cfg(target_os = "macos")]
+pub fn wants_emoji() -> bool {
+ true
+}
+
+#[cfg(not(target_os = "macos"))]
+pub fn wants_emoji() -> bool {
+ *IS_LANG_UTF8
+}
+
+pub fn set_title<T: Display>(title: T) {
+ print!("\x1b]0;{}\x07", title);
+}