#![cfg(unix)]
#![allow(unsafe_code)]
use std::os::fd::AsRawFd;
pub(super) struct RawMode {
fd: i32,
original: libc::termios,
}
impl RawMode {
pub(super) fn enable() -> Option<Self> {
Self::enable_on(std::io::stdin().as_raw_fd())
}
pub(super) fn enable_on(fd: i32) -> Option<Self> {
if unsafe { libc::isatty(fd) } != 1 {
return None;
}
let mut original: libc::termios = unsafe { std::mem::zeroed() };
if unsafe { libc::tcgetattr(fd, &mut original) } != 0 {
return None;
}
let mut raw = original;
unsafe { libc::cfmakeraw(&mut raw) };
if unsafe { libc::tcsetattr(fd, libc::TCSANOW, &raw) } != 0 {
return None;
}
Some(Self { fd, original })
}
}
impl Drop for RawMode {
fn drop(&mut self) {
unsafe {
libc::tcsetattr(self.fd, libc::TCSANOW, &self.original);
}
}
}
pub(super) fn window_size() -> Option<(u16, u16)> {
size_of(std::io::stdin().as_raw_fd()).or_else(|| size_of(std::io::stdout().as_raw_fd()))
}
fn size_of(fd: i32) -> Option<(u16, u16)> {
let mut ws: libc::winsize = unsafe { std::mem::zeroed() };
if unsafe { libc::ioctl(fd, libc::TIOCGWINSZ, &mut ws) } != 0 {
return None;
}
if ws.ws_row == 0 || ws.ws_col == 0 {
return None;
}
Some((ws.ws_row, ws.ws_col))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_non_terminal_descriptor_is_declined() {
let devnull = std::fs::File::open("/dev/null").expect("/dev/null opens");
assert!(
RawMode::enable_on(devnull.as_raw_fd()).is_none(),
"a non-terminal descriptor must not be switched to raw mode"
);
}
#[test]
fn a_non_terminal_descriptor_has_no_size() {
let devnull = std::fs::File::open("/dev/null").expect("/dev/null opens");
assert_eq!(size_of(devnull.as_raw_fd()), None);
}
}