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