114 lines
3.3 KiB
Rust
114 lines
3.3 KiB
Rust
//! 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<Termios>,
|
|
}
|
|
|
|
type Result<T, E = std::io::Error> = std::result::Result<T, E>;
|
|
|
|
fn assert_io(result: i32) -> Result<i32> {
|
|
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<Self> {
|
|
// 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<Termios> {
|
|
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))
|
|
}
|
|
}
|