use std::fs::File;
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd};
use std::os::unix::fs::OpenOptionsExt;
use std::thread;
use bytes::{Buf, BytesMut};
use async_trait::async_trait;
use nix::errno::Errno;
use nix::pty::Winsize;
use nix::sys::select;
use nix::sys::termios::{self, SetArg, Termios};
use nix::{libc, unistd};
use tokio::io::unix::AsyncFd;
use tokio::io::{self, Interest};
use super::{RawTty, TtySize};
use crate::fd::FdExt;
const BUF_SIZE: usize = 128 * 1024;
pub struct DevTty {
file: File,
read_r_fd: AsyncFd<OwnedFd>,
write_w_fd: AsyncFd<OwnedFd>,
settings: libc::termios,
}
impl DevTty {
pub async fn open() -> anyhow::Result<Self> {
let file = File::options()
.read(true)
.write(true)
.custom_flags(libc::O_NONBLOCK)
.open("/dev/tty")?;
let settings = super::make_raw(&file)?;
let (read_r_fd, read_w_fd) = unistd::pipe()?;
read_r_fd.set_nonblocking()?;
let read_r_fd = AsyncFd::new(read_r_fd)?;
let (write_r_fd, write_w_fd) = unistd::pipe()?;
write_w_fd.set_nonblocking()?;
let write_w_fd = AsyncFd::new(write_w_fd)?;
let tty_fd = unsafe { BorrowedFd::borrow_raw(file.as_raw_fd()) };
thread::spawn(move || {
copy(tty_fd, read_w_fd);
});
let tty_fd = unsafe { BorrowedFd::borrow_raw(file.as_raw_fd()) };
thread::spawn(move || {
copy(write_r_fd, tty_fd);
});
Ok(Self {
file,
read_r_fd,
write_w_fd,
settings,
})
}
pub async fn resize(&mut self, size: TtySize) -> io::Result<()> {
let xtwinops_seq = format!("\x1b[8;{};{}t", size.1, size.0);
self.write_all(xtwinops_seq.as_bytes()).await?;
Ok(())
}
}
impl Drop for DevTty {
fn drop(&mut self) {
let termios = Termios::from(self.settings);
let _ = termios::tcsetattr(self.file.as_fd(), SetArg::TCSANOW, &termios);
}
}
#[async_trait(?Send)]
impl RawTty for DevTty {
fn get_size(&self) -> Winsize {
let mut winsize = Winsize {
ws_row: 24,
ws_col: 80,
ws_xpixel: 0,
ws_ypixel: 0,
};
unsafe { libc::ioctl(self.file.as_raw_fd(), libc::TIOCGWINSZ, &mut winsize) };
winsize
}
async fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
self.read_r_fd
.async_io(Interest::READABLE, |fd| {
unistd::read(fd, buf).map_err(|e| e.into())
})
.await
}
async fn write(&self, buf: &[u8]) -> io::Result<usize> {
self.write_w_fd
.async_io(Interest::WRITABLE, |fd| {
unistd::write(fd, buf).map_err(|e| e.into())
})
.await
}
}
fn copy<F: AsFd, G: AsFd>(src_fd: F, dst_fd: G) {
let src_fd = src_fd.as_fd();
let dst_fd = dst_fd.as_fd();
let mut buf = [0u8; BUF_SIZE];
let mut data = BytesMut::with_capacity(BUF_SIZE);
loop {
let mut read_fds = select::FdSet::new();
let mut write_fds = select::FdSet::new();
read_fds.insert(src_fd);
if !data.is_empty() {
write_fds.insert(dst_fd);
}
match select::select(None, Some(&mut read_fds), Some(&mut write_fds), None, None) {
Ok(0) | Err(Errno::EINTR) => {
continue;
}
Ok(_) => {}
Err(_) => {
break;
}
}
if read_fds.contains(src_fd) {
match unistd::read(src_fd, &mut buf) {
Ok(0) => break,
Ok(n) => {
data.extend_from_slice(&buf[..n]);
}
Err(Errno::EWOULDBLOCK) => {}
Err(_) => {
break;
}
}
}
if write_fds.contains(dst_fd) {
match unistd::write(dst_fd, &data) {
Ok(n) => {
data.advance(n);
}
Err(Errno::EWOULDBLOCK) => {}
Err(_) => {
break;
}
}
}
}
while !data.is_empty() {
let mut write_fds = select::FdSet::new();
write_fds.insert(dst_fd);
match select::select(None, None, Some(&mut write_fds), None, None) {
Ok(1) => {}
Ok(0) | Err(Errno::EINTR) => {
continue;
}
Ok(_) => {
unreachable!();
}
Err(_) => {
break;
}
}
match unistd::write(dst_fd, &data) {
Ok(n) => {
data.advance(n);
}
Err(Errno::EWOULDBLOCK) => {}
Err(_) => {
break;
}
}
}
}