summaryrefslogtreecommitdiffstats
path: root/src/sys
diff options
context:
space:
mode:
Diffstat (limited to 'src/sys')
-rw-r--r--src/sys/mod.rs11
-rw-r--r--src/sys/unix.rs142
-rw-r--r--src/sys/windows.rs117
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)
+ }
+}