use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd};
use std::sync::atomic::Ordering;
use std::sync::mpsc;
use std::time::Instant;
pub struct KeyTick {
pub timestamp: Instant,
}
pub struct KeystrokeDriver {
rx: mpsc::Receiver<KeyTick>,
_saved_stdin: OwnedFd,
_pty_secondary: std::fs::File,
prev_shutdown_flag: bool,
saved_termios: libc::termios,
}
impl KeystrokeDriver {
pub fn new(bytes_per_sec: usize) -> Option<Self> {
let saved_stdin = unsafe { OwnedFd::from_raw_fd(libc::dup(0)) };
if saved_stdin.as_raw_fd() < 0 {
return None;
}
let saved_termios = save_termios()?;
let pair = open_pty()?;
redirect_stdin(&pair.secondary)?;
make_raw_terminal()?;
let (tx, rx) = mpsc::channel::<KeyTick>();
let (tokio_tx, mut tokio_rx) =
tokio::sync::mpsc::unbounded_channel::<crate::event::UserEvent>();
crate::ui::input_reader::spawn_input_reader(tokio_tx);
let tx2 = tx.clone();
std::thread::spawn(move || {
loop {
match tokio_rx.try_recv() {
Ok(crate::event::UserEvent::Key(_)) => {
if tx2
.send(KeyTick {
timestamp: Instant::now(),
})
.is_err()
{
break;
}
}
Ok(_) => {}
Err(tokio::sync::mpsc::error::TryRecvError::Empty) => {
std::thread::yield_now();
}
Err(tokio::sync::mpsc::error::TryRecvError::Disconnected) => break,
}
}
});
let charset: Vec<u8> = (b'a'..=b'z')
.chain(b'A'..=b'Z')
.chain(b'0'..=b'9')
.collect();
let interval = std::time::Duration::from_secs_f64(1.0 / bytes_per_sec as f64);
let mut primary = pair.primary;
std::thread::spawn(move || {
for i in 0u64.. {
let b = charset[i as usize % charset.len()];
let _ = std::io::Write::write_all(&mut primary, &[b]);
let _ = std::io::Write::flush(&mut primary);
std::thread::sleep(interval);
}
});
Some(Self {
rx,
_saved_stdin: saved_stdin,
_pty_secondary: pair.secondary,
prev_shutdown_flag: crate::ui::terminal::EVENT_READER_SHUTDOWN.load(Ordering::Relaxed),
saved_termios,
})
}
pub fn receiver(&self) -> &mpsc::Receiver<KeyTick> {
&self.rx
}
}
impl Drop for KeystrokeDriver {
fn drop(&mut self) {
crate::ui::terminal::EVENT_READER_SHUTDOWN.store(true, Ordering::Relaxed);
let saved = self._saved_stdin.as_raw_fd();
unsafe {
libc::dup2(saved, 0);
}
unsafe {
libc::tcsetattr(0, libc::TCSANOW, &self.saved_termios);
}
crate::ui::terminal::EVENT_READER_SHUTDOWN
.store(self.prev_shutdown_flag, Ordering::Relaxed);
}
}
struct PtyPair {
primary: std::fs::File,
secondary: std::fs::File,
}
fn open_pty() -> Option<PtyPair> {
let primary_fd = unsafe { libc::posix_openpt(libc::O_RDWR | libc::O_NOCTTY) };
if primary_fd < 0 {
return None;
}
if unsafe { libc::grantpt(primary_fd) } < 0 || unsafe { libc::unlockpt(primary_fd) } < 0 {
unsafe { libc::close(primary_fd) };
return None;
}
let secondary_name = unsafe { libc::ptsname(primary_fd) };
if secondary_name.is_null() {
unsafe { libc::close(primary_fd) };
return None;
}
let secondary_fd = unsafe { libc::open(secondary_name, libc::O_RDWR | libc::O_NOCTTY) };
if secondary_fd < 0 {
unsafe { libc::close(primary_fd) };
return None;
}
let primary = unsafe { std::fs::File::from_raw_fd(primary_fd) };
let secondary = unsafe { std::fs::File::from_raw_fd(secondary_fd) };
Some(PtyPair { primary, secondary })
}
fn redirect_stdin(secondary: &std::fs::File) -> Option<()> {
if unsafe { libc::dup2(secondary.as_raw_fd(), 0) } < 0 {
return None;
}
Some(())
}
fn save_termios() -> Option<libc::termios> {
let mut termios: libc::termios = unsafe { std::mem::zeroed() };
if unsafe { libc::tcgetattr(0, &mut termios) } < 0 {
return None;
}
Some(termios)
}
fn make_raw_terminal() -> Option<()> {
let mut termios: libc::termios = unsafe { std::mem::zeroed() };
if unsafe { libc::tcgetattr(0, &mut termios) } < 0 {
return None;
}
unsafe { libc::cfmakeraw(&mut termios) };
if unsafe { libc::tcsetattr(0, libc::TCSANOW, &termios) } < 0 {
return None;
}
Some(())
}