diff options
Diffstat (limited to 'src/sys')
-rw-r--r-- | src/sys/mod.rs | 11 | ||||
-rw-r--r-- | src/sys/unix.rs | 142 | ||||
-rw-r--r-- | src/sys/windows.rs | 117 |
3 files changed, 270 insertions, 0 deletions
diff --git a/src/sys/mod.rs b/src/sys/mod.rs new file mode 100644 index 0000000..3525e01 --- /dev/null +++ b/src/sys/mod.rs @@ -0,0 +1,11 @@ +#[cfg(unix)] +mod unix; + +#[cfg(unix)] +pub use unix::*; + +#[cfg(windows)] +mod windows; + +#[cfg(windows)] +pub use windows::*; diff --git a/src/sys/unix.rs b/src/sys/unix.rs new file mode 100644 index 0000000..67bc1da --- /dev/null +++ b/src/sys/unix.rs @@ -0,0 +1,142 @@ +use std::fs::File; +use std::io::{Read, Write}; +use std::mem::ManuallyDrop; +use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, RawFd}; + +/// Unix handle to an open terminal. +pub enum Terminal { + /// Non-owning file for one of the standard I/O streams. + Stdio(ManuallyDrop<File>), + + /// Owned file for `/dev/tty`. + File(File), +} + +#[derive(Copy, Clone)] +pub struct TerminalMode { + termios: libc::termios, +} + +impl Terminal { + pub fn open() -> std::io::Result<Self> { + if let Some(terminal) = open_fd_terminal(2) { + Ok(terminal) + } else if let Some(terminal) = open_fd_terminal(0) { + Ok(terminal) + } else if let Some(terminal) = open_fd_terminal(1) { + Ok(terminal) + } else { + let file = std::fs::OpenOptions::new() + .read(true) + .write(true) + .open("/dev/tty")?; + if is_terminal(file.as_fd()) { + Ok(Self::File(file)) + } else { + Err(std::io::Error::from_raw_os_error(libc::ENOTTY)) + } + } + } + + pub fn get_terminal_mode(&self) -> std::io::Result<TerminalMode> { + unsafe { + let mut termios = std::mem::zeroed(); + check_ret(libc::tcgetattr(self.as_fd().as_raw_fd(), &mut termios))?; + Ok(TerminalMode { termios }) + } + } + + pub fn set_terminal_mode(&self, mode: &TerminalMode) -> std::io::Result<()> { + unsafe { + check_ret(libc::tcsetattr( + self.as_fd().as_raw_fd(), + libc::TCSANOW, + &mode.termios, + ))?; + Ok(()) + } + } + + fn as_file(&self) -> &File { + match self { + Self::Stdio(io) => io, + Self::File(io) => io, + } + } +} + +fn open_fd_terminal(fd: RawFd) -> Option<Terminal> { + let file = unsafe { ManuallyDrop::new(File::from_raw_fd(fd)) }; + if is_terminal(file.as_fd()) { + Some(Terminal::Stdio(file)) + } else { + None + } +} + +impl TerminalMode { + pub fn enable_line_editing(&mut self) { + self.termios.c_lflag |= libc::ICANON; + } + + pub fn disable_echo(&mut self) { + self.termios.c_lflag &= !libc::ECHO; + self.termios.c_lflag &= !libc::ICANON; + } + + pub fn enable_echo(&mut self) { + self.termios.c_lflag |= libc::ECHO; + self.termios.c_lflag |= !libc::ICANON; + } + + pub fn is_echo_enabled(&self) -> bool { + self.termios.c_lflag & libc::ECHO != 0 + } +} + +fn is_terminal(fd: BorrowedFd) -> bool { + unsafe { + libc::isatty(fd.as_raw_fd()) == 1 + } +} + +fn check_ret(input: i32) -> std::io::Result<()> { + if input == 0 { + Ok(()) + } else { + Err(std::io::Error::last_os_error()) + } +} + +impl AsFd for Terminal { + fn as_fd(&self) -> std::os::fd::BorrowedFd<'_> { + match self { + Self::Stdio(stdin) => stdin.as_fd(), + Self::File(file) => file.as_fd(), + } + } +} + +impl Read for Terminal { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { + self.as_file().read(buf) + } + + fn read_vectored(&mut self, bufs: &mut [std::io::IoSliceMut<'_>]) -> std::io::Result<usize> { + self.as_file().read_vectored(bufs) + } +} + +impl Write for Terminal { + fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { + self.as_file().write(buf) + } + + fn flush(&mut self) -> std::io::Result<()> { + self.as_file().flush() + } + + fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result<usize> { + self.as_file().write_vectored(bufs) + } +} diff --git a/src/sys/windows.rs b/src/sys/windows.rs new file mode 100644 index 0000000..958f993 --- /dev/null +++ b/src/sys/windows.rs @@ -0,0 +1,117 @@ +use std::io::{Read, Write}; +use std::os::windows::io::{AsHandle, AsRawHandle, BorrowedHandle}; + +use winapi::um::consoleapi::{ + GetConsoleMode, + SetConsoleMode, +}; +use winapi::um::wincon::{ + ENABLE_LINE_INPUT, + ENABLE_ECHO_INPUT, +}; + +use winapi::shared::minwindef::{BOOL, DWORD}; + +pub struct Terminal { + input: std::io::Stdin, + output: std::io::Stderr, +} + +#[derive(Copy, Clone)] +pub struct TerminalMode { + input_mode: DWORD, +} + +impl Terminal { + pub fn open() -> std::io::Result<Self> { + let input = std::io::stdin(); + let output = std::io::stderr(); + if !is_terminal(input.as_handle()) { + return Err(std::io::Error::new(std::io::ErrorKind::Other, "stdin is not a terminal")); + } + if !is_terminal(output.as_handle()) { + return Err(std::io::Error::new(std::io::ErrorKind::Other, "stderr is not a terminal")); + } + Ok(Self { + input, + output, + }) + } + + pub fn get_terminal_mode(&self) -> std::io::Result<TerminalMode> { + unsafe { + let mut input_mode = 0; + check_ret(GetConsoleMode(self.input.as_raw_handle().cast(), &mut input_mode))?; + Ok(TerminalMode { + input_mode, + }) + } + } + + pub fn set_terminal_mode(&self, mode: &TerminalMode) -> std::io::Result<()> { + unsafe { + check_ret(SetConsoleMode( + self.input.as_raw_handle().cast(), + mode.input_mode, + ))?; + Ok(()) + } + } +} + +impl TerminalMode { + pub fn enable_line_editing(&mut self) { + self.input_mode |= ENABLE_LINE_INPUT; + } + + pub fn disable_echo(&mut self) { + self.input_mode &= !ENABLE_ECHO_INPUT; + } + + pub fn enable_echo(&mut self) { + self.input_mode |= ENABLE_ECHO_INPUT; + } + + pub fn is_echo_enabled(&self) -> bool { + self.input_mode & ENABLE_ECHO_INPUT != 0 + } +} + +fn is_terminal(handle: BorrowedHandle) -> bool { + unsafe { + let mut mode = 0; + GetConsoleMode(handle.as_raw_handle().cast(), &mut mode) != 0 + } +} + +fn check_ret(input: BOOL) -> std::io::Result<()> { + if input != 0 { + Ok(()) + } else { + Err(std::io::Error::last_os_error()) + } +} + +impl Read for Terminal { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { + self.input.read(buf) + } + + fn read_vectored(&mut self, bufs: &mut [std::io::IoSliceMut<'_>]) -> std::io::Result<usize> { + self.input.read_vectored(bufs) + } +} + +impl Write for Terminal { + fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { + self.output.write(buf) + } + + fn flush(&mut self) -> std::io::Result<()> { + self.output.flush() + } + + fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result<usize> { + self.output.write_vectored(bufs) + } +} |