//! A shim for replacing some Crossterm methods with methods tied to a file descriptor. use libc::termios as Termios; use std::{os::fd::RawFd, io::IsTerminal}; /// The provided file descriptor was not a terminal. #[derive(Debug)] pub struct NotATerminal; impl std::fmt::Display for NotATerminal { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str("The provided file descriptor is not a terminal") } } impl std::error::Error for NotATerminal {} /// A terminal controller. pub struct TerminalIoctl { fd: RawFd, stored_termios: Option, } type Result = std::result::Result; fn assert_io(result: i32) -> Result { if result == -1 { Err(std::io::Error::last_os_error()) } else { Ok(result) } } impl TerminalIoctl { /// Construct a new controller for the given file descriptor. /// /// # Errors /// /// The method may return an error if the file descriptor is not a terminal. pub fn new(fd: RawFd) -> Result { // SAFETY: We do not invoke any function that closes the file descriptor, and // the borrowed fd is dropped as the function returns. let borrowed_fd = unsafe { std::os::fd::BorrowedFd::borrow_raw(fd) }; if !borrowed_fd.is_terminal() { return Err(std::io::Error::other(NotATerminal)); } Ok(Self { fd, stored_termios: None, }) } fn get_termios(&self) -> Result { let mut termios = unsafe { std::mem::zeroed() }; assert_io(unsafe { libc::tcgetattr(self.fd, &mut termios) })?; Ok(termios) } /// Enable raw mode for the given terminal. /// /// Replaces: [`crossterm::terminal::enable_raw_mode`]. /// /// # Errors /// /// The method may return an error if pub fn enable_raw_mode(&mut self) -> Result<()> { if self.stored_termios.is_none() { let mut termios = self.get_termios()?; let original_mode_ios = termios; unsafe { libc::cfmakeraw(&mut termios) }; assert_io(unsafe { libc::tcsetattr(self.fd, libc::TCSANOW, &termios) })?; self.stored_termios = Some(original_mode_ios); } Ok(()) } /// Disable raw mode for the given terminal. /// /// Replaces: [`crossterm::terminal::disable_raw_mode`]. /// /// # Errors /// /// The method may propagate errors encountered when interacting with the terminal. pub fn disable_raw_mode(&mut self) -> Result<()> { if let Some(termios) = self.stored_termios.take() { assert_io(unsafe { libc::tcsetattr(self.fd, libc::TCSANOW, &termios) })?; } Ok(()) } /// Return the size for the given terminal. /// /// Replaces: [`crossterm::terminal::size`]. /// /// # Errors /// /// The method may propagate errors encountered when interacting with the terminal. pub fn size(&self) -> Result<(u16, u16)> { let mut size = libc::winsize { ws_row: 0, ws_col: 0, ws_xpixel: 0, ws_ypixel: 0, }; assert_io(unsafe { libc::ioctl(self.fd, libc::TIOCGWINSZ, &mut size) })?; Ok((size.ws_col, size.ws_row)) } }