Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
231 changes: 141 additions & 90 deletions src/unix_term.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ 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;
Expand Down Expand Up @@ -69,7 +71,10 @@ pub fn read_secure() -> io::Result<String> {
f_tty = None;
libc::STDIN_FILENO
} else {
let f = fs::File::open("/dev/tty")?;
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
Expand Down Expand Up @@ -99,20 +104,69 @@ pub fn read_secure() -> io::Result<String> {
})
}

fn read_single_char(fd: i32) -> io::Result<Option<char>> {
fn poll_fd(fd: i32, timeout: i32) -> io::Result<bool> {
let mut pollfd = libc::pollfd {
fd,
events: libc::POLLIN,
revents: 0,
};

// timeout of zero means that it will not block
let ret = unsafe { libc::poll(&mut pollfd as *mut _, 1, 0) };
let ret = unsafe { libc::poll(&mut pollfd as *mut _, 1, timeout) };
if ret < 0 {
return Err(io::Error::last_os_error());
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)
}

let is_ready = pollfd.revents & libc::POLLIN != 0;
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
Expand Down Expand Up @@ -154,7 +208,10 @@ pub fn read_single_key() -> io::Result<Key> {
if libc::isatty(libc::STDIN_FILENO) == 1 {
libc::STDIN_FILENO
} else {
tty_f = fs::File::open("/dev/tty")?;
tty_f = fs::OpenOptions::new()
.read(true)
.write(true)
.open("/dev/tty")?;
tty_f.as_raw_fd()
}
};
Expand All @@ -165,103 +222,97 @@ pub fn read_single_key() -> io::Result<Key> {
unsafe { libc::cfmakeraw(&mut termios) };
c_result(|| unsafe { libc::tcsetattr(fd, libc::TCSADRAIN, &termios) })?;

let rv = match read_single_char(fd)? {
Some('\x1b') => {
// Escape was read, keep reading in case we find a familiar key
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])),
let rv: io::Result<Key> = 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 {
Ok(Key::UnknownEscSeq(vec![c1, c2, c3]))
// \x1b[ and 1 more char
Ok(Key::UnknownEscSeq(vec![c1, c2]))
}
} else {
// \x1b[ and 1 more char
Ok(Key::UnknownEscSeq(vec![c1, c2]))
}
}
} else {
// \x1b[ and no more input
Ok(Key::UnknownEscSeq(vec![c1]))
}
} else {
// \x1b[ and no more input
// char after escape is not [
Ok(Key::UnknownEscSeq(vec![c1]))
}
} else {
// char after escape is not [
Ok(Key::UnknownEscSeq(vec![c1]))
}
} else {
//nothing after escape
Ok(Key::Escape)
//nothing after escape
Ok(Key::Escape)
};
}
}
Some(c) => {
let byte = c as u8;
let mut buf: [u8; 4] = [byte, 0, 0, 0];

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),
})
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

let mut pollfd = libc::pollfd {
fd,
events: libc::POLLIN,
revents: 0,
};

// negative timeout means that it will block indefinitely
let ret = unsafe { libc::poll(&mut pollfd as *mut _, 1, -1) };
if ret < 0 {
return Err(io::Error::last_os_error());
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()),
}
}

read_single_key()
}
};

c_result(|| unsafe { libc::tcsetattr(fd, libc::TCSADRAIN, &original) })?;

// if the user hit ^C we want to signal SIGINT to outselves.
Expand Down