mod util {
use std::io;
pub trait IsMinusOne {
fn is_minus_one(&self) -> bool;
}
macro_rules! impl_is_minus_one {
($($t:ident)*) => ($(impl IsMinusOne for $t {
fn is_minus_one(&self) -> bool {
*self == -1
}
})*)
}
impl_is_minus_one! { i8 i16 i32 i64 isize }
pub fn convert_to_result<T: IsMinusOne>(t: T) -> io::Result<T> {
if t.is_minus_one() {
Err(io::Error::last_os_error())
} else {
Ok(t)
}
}
}
mod attr {
#[cfg(unix)]
pub mod unix {
use crate::util::*;
use libc::c_int;
pub type Termios = libc::termios;
use std::os::unix::io::RawFd;
use std::{io, mem};
pub fn get_terminal_attr(fd: RawFd) -> io::Result<Termios> {
extern "C" {
pub fn tcgetattr(fd: c_int, termptr: *mut Termios) -> c_int;
}
unsafe {
let mut termios = mem::zeroed();
convert_to_result(tcgetattr(fd, &mut termios))?;
Ok(termios)
}
}
pub fn set_terminal_attr(fd: RawFd, termios: &Termios) -> io::Result<()> {
extern "C" {
pub fn tcsetattr(fd: c_int, opt: c_int, termptr: *const Termios) -> c_int;
}
convert_to_result(unsafe { tcsetattr(fd, 0, termios) }).and(Ok(()))
}
pub fn raw_terminal_attr(termios: &mut Termios) {
extern "C" {
pub fn cfmakeraw(termptr: *mut Termios);
}
unsafe { cfmakeraw(termios) }
}
}
#[cfg(unix)]
pub use unix::*;
}
pub use attr::Termios;
use attr::{get_terminal_attr, raw_terminal_attr, set_terminal_attr};
use derive_more::{Deref, DerefMut};
use std::io;
use std::os::unix::io::{AsRawFd, RawFd};
pub struct TtyModeGuard {
ios: Termios,
fd: RawFd,
}
impl Drop for TtyModeGuard {
fn drop(&mut self) {
set_terminal_attr(self.fd, &self.ios).unwrap();
}
}
impl TtyModeGuard {
pub fn new(fd: RawFd) -> io::Result<TtyModeGuard> {
let ios = get_terminal_attr(fd)?;
Ok(Self { ios, fd })
}
pub fn set_raw_mode(&mut self) -> io::Result<()> {
let mut ios = self.ios;
raw_terminal_attr(&mut ios);
set_terminal_attr(self.fd, &ios)?;
Ok(())
}
pub fn modify_mode<F>(&mut self, f: F) -> io::Result<()>
where
F: FnOnce(Termios) -> Termios,
{
let ios = f(self.ios);
set_terminal_attr(self.fd, &ios)?;
Ok(())
}
}
use std::io::Read;
use std::ops;
pub struct TtyWithGuard<T: AsRawFd> {
guard: TtyModeGuard,
inner: T,
}
impl<R: AsRawFd> ops::Deref for TtyWithGuard<R> {
type Target = R;
#[inline]
fn deref(&self) -> &R {
&self.inner
}
}
impl<R: AsRawFd> ops::DerefMut for TtyWithGuard<R> {
#[inline]
fn deref_mut(&mut self) -> &mut R {
&mut self.inner
}
}
impl<T: AsRawFd> TtyWithGuard<T> {
pub fn new(tty: T) -> io::Result<TtyWithGuard<T>> {
Ok(Self {
guard: TtyModeGuard::new(tty.as_raw_fd())?,
inner: tty,
})
}
pub fn modify_mode<F>(&mut self, f: F) -> io::Result<()>
where
F: FnOnce(Termios) -> Termios,
{
self.guard.modify_mode(f)
}
pub fn set_raw_mode(&mut self) -> io::Result<()> {
self.guard.set_raw_mode()
}
}
pub trait GuardMode: AsRawFd + Sized {
fn guard_mode(self) -> io::Result<TtyWithGuard<Self>>;
}
impl<T: AsRawFd> GuardMode for T {
fn guard_mode(self) -> io::Result<TtyWithGuard<T>> {
TtyWithGuard::new(self)
}
}
impl<R: io::Read + AsRawFd> io::Read for TtyWithGuard<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.inner.read(buf)
}
}
impl<R: io::Write + AsRawFd> io::Write for TtyWithGuard<R> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.inner.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}
#[derive(Deref, DerefMut)]
pub struct RawReader<T: Read + AsRawFd>(TtyWithGuard<T>);
impl<R: Read + AsRawFd> Read for RawReader<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.0.read(buf)
}
}
pub trait IntoRawMode: Read + AsRawFd + Sized {
fn into_raw_mode(self) -> io::Result<RawReader<Self>>;
}
impl<T: Read + AsRawFd> IntoRawMode for T {
fn into_raw_mode(self) -> io::Result<RawReader<T>> {
let mut x = TtyWithGuard::new(self)?;
x.set_raw_mode()?;
Ok(RawReader(x))
}
}
#[cfg(test)]
mod test {
use super::*;
use std::io::{self, stdin, stdout, Write};
use std::os::unix::io::AsRawFd;
#[test]
fn test_into_raw_mode() -> io::Result<()> {
let mut stdin = stdin().guard_mode()?;
stdin.set_raw_mode()?;
let mut out = stdout();
out.write_all(b"testing, 123\r\n")?;
drop(out);
Ok(())
}
}