use std::{
io,
os::{fd::BorrowedFd, unix::io::RawFd},
};
use anyhow::Context;
use nix::{
sys::{
termios,
termios::{ControlFlags, InputFlags, LocalFlags, OutputFlags, SetArg},
},
unistd::isatty,
};
use shpool_protocol::TtySize;
use tracing::error;
use crate::consts;
nix::ioctl_read_bad!(tiocgwinsz, libc::TIOCGWINSZ, libc::winsize);
nix::ioctl_write_ptr_bad!(tiocswinsz, libc::TIOCSWINSZ, libc::winsize);
pub trait TtySizeExt {
fn from_fd(fd: RawFd) -> anyhow::Result<TtySize>;
fn set_fd(&self, fd: RawFd) -> anyhow::Result<()>;
}
impl TtySizeExt for TtySize {
fn from_fd(fd: RawFd) -> anyhow::Result<TtySize> {
let mut term_size = libc::winsize { ws_row: 0, ws_col: 0, ws_xpixel: 0, ws_ypixel: 0 };
unsafe {
tiocgwinsz(fd, &mut term_size).context("fetching term size")?;
}
Ok(TtySize {
rows: term_size.ws_row,
cols: term_size.ws_col,
xpixel: term_size.ws_xpixel,
ypixel: term_size.ws_ypixel,
})
}
fn set_fd(&self, fd: RawFd) -> anyhow::Result<()> {
let term_size = libc::winsize {
ws_row: self.rows,
ws_col: self.cols,
ws_xpixel: self.xpixel,
ws_ypixel: self.ypixel,
};
unsafe {
tiocswinsz(fd, &term_size).context("setting term size")?;
}
Ok(())
}
}
pub fn disable_echo(fd: BorrowedFd<'_>) -> anyhow::Result<()> {
let mut term = termios::tcgetattr(fd).context("grabbing term flags")?;
term.local_flags &= !LocalFlags::ECHO;
termios::tcsetattr(fd, SetArg::TCSANOW, &term)?;
Ok(())
}
pub fn set_attach_flags() -> anyhow::Result<AttachFlagsGuard<'static>> {
let fd = unsafe { BorrowedFd::borrow_raw(consts::STDIN_FD) };
if !isatty(io::stdin())? || !isatty(io::stdout())? || !isatty(io::stderr())? {
return Ok(AttachFlagsGuard { fd, old: None });
}
let old = termios::tcgetattr(fd).context("grabbing term flags")?;
let mut new = old.clone();
new.input_flags &= !(InputFlags::IGNBRK
| InputFlags::BRKINT
| InputFlags::PARMRK
| InputFlags::ISTRIP
| InputFlags::INLCR
| InputFlags::IGNCR
| InputFlags::ICRNL
| InputFlags::IXON);
new.output_flags &= !OutputFlags::OPOST;
new.local_flags &= !(LocalFlags::ECHO
| LocalFlags::ECHONL
| LocalFlags::ICANON
| LocalFlags::ISIG
| LocalFlags::IEXTEN);
new.control_flags &= !(ControlFlags::CSIZE | ControlFlags::PARENB);
new.control_flags |= ControlFlags::CS8;
termios::tcsetattr(fd, SetArg::TCSANOW, &new)?;
Ok(AttachFlagsGuard { fd, old: Some(old) })
}
pub struct AttachFlagsGuard<'fd> {
fd: BorrowedFd<'fd>,
old: Option<termios::Termios>,
}
impl std::ops::Drop for AttachFlagsGuard<'_> {
fn drop(&mut self) {
if let Some(old) = &self.old {
if let Err(e) = termios::tcsetattr(self.fd, SetArg::TCSANOW, old) {
error!("error restoring terminal settings: {:?}", e);
}
}
}
}