keyfork/crates/util/keyfork-crossterm-ioctl-shim/src/lib.rs

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))
}
}