use std::{
error::Error as StdError,
ffi::CStr,
fmt::{self, Display},
os::unix::io::AsRawFd,
};
use bstr::{BStr, BString, ByteSlice};
use libc::{ioctl, ttyname, winsize, TIOCGWINSZ};
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash)]
pub enum Error {
NotTty,
LibcCall(String, i32),
}
impl Display for Error {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::NotTty => write!(f, "Not a TTY"),
Self::LibcCall(fn_name, err_code) => {
write!(f, "Failed calling {} with this error code: {}", fn_name, err_code)
},
}
}
}
impl StdError for Error {}
#[derive(Clone, Debug, PartialOrd, PartialEq, Ord, Eq, Hash)]
pub struct TtyName(BString);
impl TtyName {
#[inline]
pub fn new(file_descriptor: &impl AsRawFd) -> Result<Self, Error> {
let name = unsafe { ttyname(file_descriptor.as_raw_fd()) };
let name = if name.is_null() {
return Err(Error::NotTty);
} else {
let name_cstr = unsafe { CStr::from_ptr(name) };
BString::from(name_cstr.to_bytes())
};
Ok(TtyName(name))
}
#[inline]
pub fn as_bstr(&self) -> &BStr {
self.0.as_bstr()
}
}
impl Display for TtyName {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt(&self.0, f)
}
}
pub trait IsTty: AsRawFd {
fn is_tty(&self) -> bool;
}
impl<T: AsRawFd> IsTty for T {
#[inline]
fn is_tty(&self) -> bool {
is_tty(self)
}
}
#[inline]
pub fn is_tty(file_descriptor: &impl AsRawFd) -> bool {
unsafe { libc::isatty(file_descriptor.as_raw_fd()) == 1 }
}
#[inline]
pub fn tty_dimensions(file_descriptor: &impl AsRawFd) -> Option<(u16, u16)> {
if !is_tty(file_descriptor) {
return None;
}
let mut size = winsize { ws_row: 0, ws_col: 0, ws_xpixel: 0, ws_ypixel: 0 };
let tiocgwinsz = TIOCGWINSZ;
#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
let tiocgwinsz: u64 = tiocgwinsz.into();
if unsafe { ioctl(file_descriptor.as_raw_fd(), tiocgwinsz, &mut size) } == -1 {
return None;
}
Some((size.ws_col, size.ws_row))
}