use std::fs::File;
use std::io::{self, BufRead, Read, Write};
use std::os::fd::{AsRawFd, BorrowedFd, RawFd};
use nix::poll::{poll, PollFd, PollFlags, PollTimeout};
use super::pty::{contains_alt_screen_seq, get_terminal_winsize};
pub(super) fn forward_stdin(
mut master_write: File,
shutdown_read: os_pipe::PipeReader,
pty_master_fd: RawFd,
) {
let stdin_fd = io::stdin().as_raw_fd();
let shutdown_fd = shutdown_read.as_raw_fd();
let mut last_ws = get_terminal_winsize();
let mut read_buf = [0u8; 4096];
loop {
let mut fds = [
PollFd::new(
unsafe { BorrowedFd::borrow_raw(stdin_fd) },
PollFlags::POLLIN,
),
PollFd::new(
unsafe { BorrowedFd::borrow_raw(shutdown_fd) },
PollFlags::POLLIN,
),
];
match poll(&mut fds, PollTimeout::from(100u16)) {
Ok(_) => {}
Err(nix::errno::Errno::EINTR) => continue,
Err(_) => break,
}
if let Some(revents) = fds[1].revents() {
if revents.intersects(PollFlags::POLLIN | PollFlags::POLLHUP) {
break;
}
}
let current_ws = get_terminal_winsize();
if current_ws.ws_row != last_ws.ws_row || current_ws.ws_col != last_ws.ws_col {
unsafe {
libc::ioctl(pty_master_fd, libc::TIOCSWINSZ, ¤t_ws);
}
last_ws = current_ws;
}
if let Some(revents) = fds[0].revents() {
if revents.contains(PollFlags::POLLIN) {
let n = unsafe {
libc::read(
stdin_fd,
read_buf.as_mut_ptr() as *mut libc::c_void,
read_buf.len(),
)
};
if n <= 0 {
break;
}
let _ = master_write.write_all(&read_buf[..n as usize]);
}
if revents.contains(PollFlags::POLLHUP) {
break;
}
}
}
}
#[derive(Default)]
pub(super) struct CaptureResult {
pub bytes: Vec<u8>,
pub used_alt_screen: bool,
}
pub(super) fn capture_pty_output(mut master: File) -> CaptureResult {
let mut result = CaptureResult::default();
let mut read_buf = [0u8; 4096];
loop {
match master.read(&mut read_buf) {
Ok(0) => break,
Ok(n) => {
let chunk = &read_buf[..n];
if !result.used_alt_screen && contains_alt_screen_seq(chunk) {
result.used_alt_screen = true;
}
let mut out = io::stdout().lock();
let _ = out.write_all(chunk);
let _ = out.flush();
if !result.used_alt_screen {
result.bytes.extend_from_slice(chunk);
}
}
Err(e) => {
if e.raw_os_error() == Some(libc::EIO) {
break;
}
break;
}
}
}
result
}
pub(super) fn tee_to_terminal<R: Read>(read: R, is_stderr: bool) -> Vec<u8> {
let mut buf = Vec::new();
let reader = io::BufReader::new(read);
for line in reader.split(b'\n') {
match line {
Ok(mut bytes) => {
bytes.push(b'\n');
buf.extend_from_slice(&bytes);
if is_stderr {
let mut err = io::stderr().lock();
let _ = err.write_all(&bytes[..bytes.len() - 1]); let _ = err.write_all(b"\r\n"); let _ = err.flush();
} else {
let mut out = io::stdout().lock();
let _ = out.write_all(&bytes[..bytes.len() - 1]); let _ = out.write_all(b"\r\n"); let _ = out.flush();
}
}
Err(_) => break,
}
}
buf
}
pub(super) fn tee_stderr(read: os_pipe::PipeReader) -> Vec<u8> {
let mut buf = Vec::new();
let mut reader = io::BufReader::new(read);
let mut read_buf = [0u8; 4096];
loop {
match reader.read(&mut read_buf) {
Ok(0) => break,
Ok(n) => {
let chunk = &read_buf[..n];
buf.extend_from_slice(chunk);
let converted = convert_lf_to_crlf(chunk);
let mut err = io::stderr().lock();
let _ = err.write_all(&converted);
let _ = err.flush();
}
Err(_) => break,
}
}
buf
}
fn convert_lf_to_crlf(input: &[u8]) -> Vec<u8> {
let mut output = Vec::with_capacity(input.len());
let mut prev = 0u8;
for &byte in input {
if byte == b'\n' && prev != b'\r' {
output.push(b'\r');
}
output.push(byte);
prev = byte;
}
output
}