use signal_hook::consts::SIGWINCH;
use signal_hook::low_level::pipe;
use termion::event::{parse_event, Event, Key, MouseEvent};
use std::io;
use std::io::{stdin, Read, Stdin};
use std::os::unix::io::AsRawFd;
use std::os::unix::net::UnixStream;
const POLL_INFINITE_TIMEOUT: i32 = -1;
const SIGWINCH_PIPE_INDEX: usize = 0;
const BUFFER_SIZE: usize = 1024;
const ESCAPE: u8 = 0o33;
pub fn remap_dev_tty_to_stdin() {
unsafe {
let filename = std::ffi::CString::new("/dev/tty").unwrap();
let path = std::ffi::CString::new("r").unwrap();
let _ = libc::freopen(filename.as_ptr(), path.as_ptr(), libc_stdhandle::stdin());
}
}
pub fn get_input() -> impl Iterator<Item = io::Result<TuiEvent>> {
let (sigwinch_read, sigwinch_write) = UnixStream::pair().unwrap();
pipe::register(SIGWINCH, sigwinch_write).unwrap();
TuiInput::new(stdin(), sigwinch_read)
}
fn read_and_retry_on_interrupt(input: &mut Stdin, buf: &mut [u8]) -> io::Result<usize> {
loop {
match input.read(buf) {
res @ Ok(_) => {
return res;
}
Err(err) => {
if err.kind() != io::ErrorKind::Interrupted {
return Err(err);
}
}
}
}
}
struct BufferedInput<const N: usize> {
input: Stdin,
buffer: [u8; N],
buffer_size: usize,
buffer_index: usize,
might_have_more_data: bool,
}
impl<const N: usize> BufferedInput<N> {
fn new(input: Stdin) -> BufferedInput<N> {
BufferedInput {
input,
buffer: [0; N],
buffer_size: 0,
buffer_index: 0,
might_have_more_data: false,
}
}
fn next_u8(&mut self) -> u8 {
if self.buffer_index >= self.buffer_size {
panic!("No data in buffer");
}
let val = self.buffer[self.buffer_index];
self.buffer_index += 1;
val
}
fn clear(&mut self) {
if cfg!(debug_assertions) {
for elem in self.buffer.iter_mut() {
*elem = 0;
}
}
self.buffer_size = 0;
self.buffer_index = 0;
self.might_have_more_data = false;
}
fn might_have_buffered_data(&self) -> bool {
self.might_have_more_data || self.has_buffered_data()
}
fn has_buffered_data(&self) -> bool {
self.buffer_index < self.buffer_size
}
fn take_pure_escape(&mut self) -> bool {
if self.buffer_index == 0 && self.buffer_size == 1 && self.buffer[0] == ESCAPE {
self.clear();
return true;
}
false
}
fn read_more_if_needed(&mut self) -> Option<io::Error> {
if self.has_buffered_data() {
return None;
}
self.clear();
match read_and_retry_on_interrupt(&mut self.input, &mut self.buffer) {
Ok(bytes_read) => {
self.buffer_size = bytes_read;
self.might_have_more_data = bytes_read == N;
None
}
Err(err) => Some(err),
}
}
}
impl<const N: usize> Iterator for BufferedInput<N> {
type Item = io::Result<u8>;
fn next(&mut self) -> Option<io::Result<u8>> {
if !self.has_buffered_data() {
return None;
}
Some(Ok(self.next_u8()))
}
}
struct TuiInput {
poll_fds: [libc::pollfd; 2],
sigwinch_pipe: UnixStream,
buffered_input: BufferedInput<BUFFER_SIZE>,
}
impl TuiInput {
fn new(input: Stdin, sigwinch_pipe: UnixStream) -> TuiInput {
let sigwinch_fd = sigwinch_pipe.as_raw_fd();
let stdin_fd = input.as_raw_fd();
let poll_fds: [libc::pollfd; 2] = [
libc::pollfd {
fd: sigwinch_fd,
events: libc::POLLIN,
revents: 0,
},
libc::pollfd {
fd: stdin_fd,
events: libc::POLLIN,
revents: 0,
},
];
TuiInput {
poll_fds,
sigwinch_pipe,
buffered_input: BufferedInput::new(input),
}
}
fn get_event_from_buffered_input(&mut self) -> Option<io::Result<TuiEvent>> {
if !self.buffered_input.has_buffered_data() {
if let Some(err) = self.buffered_input.read_more_if_needed() {
return Some(Err(err));
}
}
if self.buffered_input.take_pure_escape() {
return Some(Ok(TuiEvent::KeyEvent(Key::Esc)));
}
match self.buffered_input.next() {
Some(Ok(byte)) => match parse_event(byte, &mut self.buffered_input) {
Ok(Event::Key(k)) => Some(Ok(TuiEvent::KeyEvent(k))),
Ok(Event::Mouse(m)) => Some(Ok(TuiEvent::MouseEvent(m))),
Ok(Event::Unsupported(bytes)) => Some(Ok(TuiEvent::Unknown(bytes))),
Err(err) => Some(Err(err)),
},
Some(Err(err)) => Some(Err(err)),
None => None,
}
}
}
impl Iterator for TuiInput {
type Item = io::Result<TuiEvent>;
fn next(&mut self) -> Option<io::Result<TuiEvent>> {
if self.buffered_input.might_have_buffered_data() {
return self.get_event_from_buffered_input();
}
let poll_res: Option<io::Error>;
loop {
match unsafe { libc::poll(self.poll_fds.as_mut_ptr(), 2, POLL_INFINITE_TIMEOUT) } {
-1 => {
let err = io::Error::last_os_error();
if err.kind() != io::ErrorKind::Interrupted {
poll_res = Some(err);
break;
}
}
_ => {
poll_res = None;
break;
}
};
}
if let Some(poll_err) = poll_res {
return Some(Err(poll_err));
}
if self.poll_fds[SIGWINCH_PIPE_INDEX].revents & libc::POLLIN != 0 {
let mut buf = [0; 32];
let _ = self.sigwinch_pipe.read(&mut buf);
return Some(Ok(TuiEvent::WinChEvent));
}
self.get_event_from_buffered_input()
}
}
#[derive(Debug)]
pub enum TuiEvent {
WinChEvent,
KeyEvent(Key),
MouseEvent(MouseEvent),
Unknown(Vec<u8>),
}