pub mod tty;
use super::Control;
use crate::{
input::{async_input, DefaultStdIn},
prelude::Point,
Terminal,
};
use log::{trace, warn};
use rustix::fd::AsFd;
use rustix::termios::Termios;
use std::{fs, io, sync::Arc};
pub type DefaultControl = NixTty;
pub type ControlOut = fs::File;
pub type ControlIn = fs::File;
pub(crate) fn terminal() -> io::Result<Terminal<DefaultControl, DefaultStdIn, io::Stdout>> {
let control = get_tty()?;
Ok(Terminal::new(control, async_input(), io::stdout()))
}
pub fn get_tty() -> io::Result<DefaultControl> {
let input = None
.or_else(|| tty::is_tty(io::stdin()))
.and_then(|stdin| stdin.as_fd().try_clone_to_owned().ok())
.map(fs::File::from)
.or_else(|| tty::is_tty(tty::open_tty().ok()?))
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "no tty was found"))?;
let output = None
.or_else(|| tty::is_tty(io::stdout()))
.and_then(|stdin| stdin.as_fd().try_clone_to_owned().ok())
.map(fs::File::from)
.or_else(|| tty::is_tty(tty::open_tty().ok()?))
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "no tty was found"))?;
NixTty::try_from((input, output))
}
pub struct NixTty {
reset: Arc<Reset>,
}
impl TryFrom<(fs::File, fs::File)> for NixTty {
type Error = io::Error;
fn try_from(value: (fs::File, fs::File)) -> Result<Self, Self::Error> {
let (input, output) = value;
let state =
tty::get_tty_attributes(&input).or_else(|_| tty::get_tty_attributes(&output))?;
Ok(NixTty {
reset: Arc::new(Reset {
state,
input,
output,
}),
})
}
}
impl Control for NixTty {
fn enable_raw_mode(&self) -> std::io::Result<()> {
tty::enable_raw(&self.reset.input).or_else(|_| tty::enable_raw(&self.reset.output))
}
fn get_size(&self) -> std::io::Result<Point> {
tty::size_from_fd(&self.reset.output).or_else(|_| tty::size_from_fd(&self.reset.input))
}
fn is_tty(&self) -> bool {
tty::is_tty(&self.reset.output).is_some() || tty::is_tty(&self.reset.input).is_some()
}
fn is_raw(&self) -> bool {
tty::is_raw(&self.reset.output)
.or_else(|_| tty::is_raw(&self.reset.input))
.unwrap_or_default()
}
fn is_ansi(&self) -> bool {
tty::is_raw(&self.reset.output)
.or_else(|_| tty::is_raw(&self.reset.input))
.unwrap_or_default()
}
fn output(&self) -> &ControlOut {
&self.reset.output
}
fn input(&self) -> &ControlIn {
&self.reset.input
}
fn clone(&self) -> Self {
Self {
reset: self.reset.clone(),
}
}
}
struct Reset {
state: Termios,
input: fs::File,
output: fs::File,
}
impl Drop for Reset {
fn drop(&mut self) {
reset(&self.state, &self.input, &self.output)
}
}
fn reset(tios: &Termios, i: &fs::File, o: &fs::File) {
trace!("Resetting tty atts for {i:?} and {o:?}");
tty::set_tty_attributes(i, tios).unwrap_or_else(|e| warn!("could not reset in attrs - {e}"));
tty::set_tty_attributes(o, tios).unwrap_or_else(|e| warn!("could not reset out attrs - {e}"));
}