parent
99759854e2
commit
35c1818320
@ -0,0 +1,136 @@ |
||||
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<Self> { |
||||
let mut old_tio = unsafe { std::mem::zeroed::<Termios>() }; |
||||
|
||||
/* 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<crossbeam_channel::Receiver<InputEvent>>, |
||||
shutdown_sender : crossbeam_channel::Sender<()>, |
||||
recv_thread_handle: Option<JoinHandle<()>>, |
||||
} |
||||
|
||||
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<usize> { |
||||
stdout().write(buf) |
||||
} |
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> { |
||||
stdout().flush() |
||||
} |
||||
} |
||||
|
||||
impl TerminalInterface for StdinInterface { |
||||
fn get_receiver(&mut self) -> Option<crossbeam_channel::Receiver<InputEvent>> { |
||||
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(); |
||||
} |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue