use std::io; use std::io::{Read, stdin, stdout, Write}; use std::os::unix::io::RawFd; use std::thread::JoinHandle; use std::time::Duration; use termios::{tcgetattr, tcsetattr, Termios}; use timeout_readwrite::TimeoutReader; use crate::{InputEvent, TerminalInterface}; const STDIN_FILENO : RawFd = 0; struct StdinNonblockingGuard { original_termios : Termios, } impl StdinNonblockingGuard { pub fn new() -> std::io::Result { let mut old_tio = unsafe { std::mem::zeroed::() }; /* get the terminal settings for stdin */ tcgetattr(STDIN_FILENO, &mut old_tio)?; /* we want to keep the old setting to restore them a the end */ let mut new_tio = old_tio; /* disable canonical mode (buffered i/o) and local echo */ new_tio.c_lflag &= !termios::ICANON & !termios::ECHO; /* set the new settings immediately */ tcsetattr(STDIN_FILENO, termios::TCSANOW, &new_tio)?; Ok(Self { original_termios: old_tio, }) } } impl Drop for StdinNonblockingGuard { fn drop(&mut self) { /* restore the former settings */ let _ = tcsetattr(STDIN_FILENO, termios::TCSANOW, &self.original_termios); } } #[derive(SmartDefault)] pub struct StdinInterfaceOptions { #[default(Duration::from_millis(250))] poll_interval: Duration, } pub struct StdinInterface { receiver : Option>, shutdown_sender : crossbeam_channel::Sender<()>, recv_thread_handle: Option>, } impl StdinInterface { pub fn new(opts : StdinInterfaceOptions) -> Self { let (sender, receiver) = crossbeam_channel::bounded(0); let (shutdown_sender, shutdown_receiver) = crossbeam_channel::bounded(0); let handle = std::thread::spawn(move || { let stdin_guard = StdinNonblockingGuard::new().unwrap(); let mut sin = TimeoutReader::new(stdin().lock(), opts.poll_interval); let mut parser = anes::parser::Parser::default(); 'input: loop { match shutdown_receiver.try_recv() { Ok(_) | Err(crossbeam_channel::TryRecvError::Disconnected) => { break 'input; } Err(crossbeam_channel::TryRecvError::Empty) => { // } } let mut buf = [0u8; 64]; // TODO add a way to kill the thread from outside let numch = match sin.read(&mut buf[..]) { Err(e) => { if e.kind() != io::ErrorKind::TimedOut { let _ = sender.send(InputEvent::EndOfStream); break 'input; } continue 'input; } Ok(0) => { let _ = sender.send(InputEvent::EndOfStream); break 'input; }, Ok(numch) => numch }; let received_slice = &buf[0..numch]; parser.advance(received_slice, numch == buf.len()); while let Some(item) = parser.next() { if sender.send(InputEvent::Key(item)).is_err() { break 'input; } } } drop(stdin_guard); }); Self { receiver: Some(receiver), shutdown_sender, recv_thread_handle: Some(handle), } } } impl Write for StdinInterface { fn write(&mut self, buf: &[u8]) -> std::io::Result { stdout().write(buf) } fn flush(&mut self) -> std::io::Result<()> { stdout().flush() } } impl TerminalInterface for StdinInterface { fn get_receiver(&mut self) -> Option> { self.receiver.take() } fn shutdown(&mut self) { if self.shutdown_sender.send(()).is_ok() { if let Some(handle) = self.recv_thread_handle.take() { let _ = handle.join(); } } } }