use std::io::{self, Write};
use std::os::unix::io::{BorrowedFd, RawFd};
use std::thread;
use std::time::Duration;
use crossterm::terminal;
use nix::sys::termios::{tcflush, tcgetattr, tcsetattr, FlushArg, SetArg, Termios};
use nix::sys::termios::{
ControlFlags, InputFlags, LocalFlags, OutputFlags, SpecialCharacterIndices,
};
use crate::error::{NdsError, Result};
use crate::terminal_state::TerminalState;
pub fn save_terminal_state(stdin_fd: RawFd) -> Result<Termios> {
let stdin = unsafe { BorrowedFd::borrow_raw(stdin_fd) };
tcgetattr(&stdin)
.map_err(|e| NdsError::TerminalError(format!("Failed to get terminal attributes: {}", e)))
}
pub fn set_raw_mode(stdin_fd: RawFd, original: &Termios) -> Result<()> {
let stdin = unsafe { BorrowedFd::borrow_raw(stdin_fd) };
let mut raw = original.clone();
raw.input_flags = InputFlags::empty();
raw.output_flags = OutputFlags::empty();
raw.control_flags |= ControlFlags::CS8;
raw.local_flags = LocalFlags::empty();
raw.control_chars[SpecialCharacterIndices::VMIN as usize] = 1;
raw.control_chars[SpecialCharacterIndices::VTIME as usize] = 0;
tcsetattr(&stdin, SetArg::TCSANOW, &raw)
.map_err(|e| NdsError::TerminalError(format!("Failed to set raw mode: {}", e)))
}
pub fn restore_terminal(stdin_fd: RawFd, original: &Termios) -> Result<()> {
let stdin = unsafe { BorrowedFd::borrow_raw(stdin_fd) };
unsafe {
let flags = libc::fcntl(stdin_fd, libc::F_GETFL);
libc::fcntl(stdin_fd, libc::F_SETFL, flags & !libc::O_NONBLOCK);
}
tcflush(&stdin, FlushArg::TCIFLUSH)
.map_err(|e| NdsError::TerminalError(format!("Failed to flush stdin: {}", e)))?;
tcsetattr(&stdin, SetArg::TCSANOW, original)
.map_err(|e| NdsError::TerminalError(format!("Failed to restore terminal: {}", e)))?;
terminal::disable_raw_mode().ok();
let reset_sequences = [
"\x1b[m", "\x1b[?1000l", "\x1b[?1002l", "\x1b[?1003l", "\x1b[?1006l", "\x1b[?1015l", "\x1b[?1049l", "\x1b[?47l", "\x1b[?1l", "\x1b[?7h", "\x1b[?25h", "\x1b[0;0r", ]
.join("");
let _ = io::stdout().write_all(reset_sequences.as_bytes());
let _ = io::stdout().flush();
tcflush(&stdin, FlushArg::TCIFLUSH).map_err(|e| {
NdsError::TerminalError(format!("Failed to flush stdin after restore: {}", e))
})?;
thread::sleep(Duration::from_millis(50));
Ok(())
}
pub fn get_terminal_size() -> Result<(u16, u16)> {
terminal::size().map_err(|e| NdsError::TerminalError(e.to_string()))
}
pub fn set_terminal_size(fd: RawFd, cols: u16, rows: u16) -> Result<()> {
unsafe {
let winsize = libc::winsize {
ws_row: rows,
ws_col: cols,
ws_xpixel: 0,
ws_ypixel: 0,
};
if libc::ioctl(fd, libc::TIOCSWINSZ as u64, &winsize) < 0 {
return Err(NdsError::PtyError(
"Failed to set terminal size".to_string(),
));
}
}
Ok(())
}
#[allow(dead_code)]
pub fn set_stdin_nonblocking(stdin_fd: RawFd) -> Result<()> {
unsafe {
let flags = libc::fcntl(stdin_fd, libc::F_GETFL);
if libc::fcntl(stdin_fd, libc::F_SETFL, flags | libc::O_NONBLOCK) < 0 {
return Err(NdsError::TerminalError(
"Failed to set stdin non-blocking".to_string(),
));
}
}
Ok(())
}
pub fn set_stdin_blocking(stdin_fd: RawFd) -> Result<()> {
unsafe {
let flags = libc::fcntl(stdin_fd, libc::F_GETFL);
if flags < 0 {
return Err(NdsError::TerminalError(
"Failed to get stdin flags".to_string(),
));
}
let new_flags = flags & !libc::O_NONBLOCK;
if libc::fcntl(stdin_fd, libc::F_SETFL, new_flags) < 0 {
return Err(NdsError::TerminalError(
"Failed to set stdin blocking".to_string(),
));
}
}
Ok(())
}
pub fn send_refresh(stream: &mut impl Write) -> io::Result<()> {
stream.write_all(b"\x0c")?;
stream.flush()
}
pub fn send_terminal_refresh_sequences(stream: &mut impl Write) -> io::Result<()> {
let refresh_sequences = [
"\x1b[m", "\x1b[?25h", "\x1b[?7h", "\x1b[?1000l", "\x1b[?1002l", "\x1b[?1003l", "\x1b[?2004l", "\x1b[0;0r", "\x0c", ]
.join("");
stream.write_all(refresh_sequences.as_bytes())?;
stream.flush()
}
pub fn capture_terminal_state(stdin_fd: RawFd) -> Result<TerminalState> {
TerminalState::capture(stdin_fd)
}