1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
//! This crate can be used for generally interacting with a tty's mode safely, but was
//! created originally to solve the problem of using raw mode with /dev/tty while reading
//! stdin for data.
//!
//! # Raw Mode
//!
//! Description from the `termion` crate:
//! >Managing raw mode.
//!
//! >Raw mode is a particular state a TTY can have. It signifies that:
//!
//! >1. No line buffering (the input is given byte-by-byte).
//! >2. The input is not written out, instead it has to be done manually by the programmer.
//! >3. The output is not canonicalized (for example, `\n` means "go one line down", not "line
//! >   break").
//!
//! >It is essential to design terminal programs.
//!
//! ## Example
//!
//! ```no_run
//! use raw_tty::IntoRawMode;
//! use std::io::{Write, stdin, stdout};
//!
//! fn main() {
//!     let stdin = stdin().into_raw_mode().unwrap();
//!     let mut stdout = stdout();
//!
//!     write!(stdout, "Hey there.").unwrap();
//! }
//! ```
//!
//! ## Example with /dev/tty
//!
//! ```
//! use raw_tty::IntoRawMode;
//! use std::io::{self, Read, Write, stdin, stdout};
//! use std::fs;
//!
//! fn main() -> io::Result<()> {
//!     let mut tty = fs::OpenOptions::new().read(true).write(true).open("/dev/tty")?;
//!     // Can use the tty_input for keys while also reading stdin for data.
//!     let mut tty_input = tty.try_clone()?.into_raw_mode();
//!     let mut buffer = String::new();
//!     stdin().read_to_string(&mut buffer)?;
//!
//!     write!(tty, "Hey there.")
//! }
//! ```
//!
//! # General example
//!
//! ```no_run
//! use raw_tty::GuardMode;
//! use std::io::{self, Write, stdin, stdout};
//!
//! 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"this is a test, muahhahahah\r\n")?;
//!
//!     drop(out);
//!     Ok(())
//! }
//!
//! fn main() -> io::Result<()> {
//!     let mut stdout = stdout().guard_mode()?;
//!     stdout.modify_mode(|ios| /* do stuff with termios here */ ios)?;
//!
//!     // Have to use &* since TtyModeGuard only implements
//!     // deref, unlike RawReader which implements read specifically.
//!     // Otherwise, it wouldn't be recognized as `Write`able.
//!     write!(&mut *stdout, "Hey there.")
//! }
//!
//! ```

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;

        /// Export of libc::termios
        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::*;
}

/// Export of libc::termios
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};

/// A terminal restorer, which keeps the previous state of the terminal, and restores it, when
/// dropped.
///
/// Restoring will entirely bring back the old TTY state.
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 })
    }

    /// Switch to raw mode.
    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(())
    }

    /// Creates a copy of the saved termios and passes it to `f`
    /// which should return the new termios to apply.
    ///
    /// This method can be used to restore the saved ios afterwards
    /// by using the identity function.
    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;

/// Wraps a file descriptor for a TTY with a guard which saves
/// the terminal mode on creation and restores it on drop.
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,
        })
    }

    /// Creates a copy of the saved termios and passes it to `f`
    /// which should return the new termios to apply.
    ///
    /// This method can be used to restore the saved ios afterwards
    /// by using the identity function.
    pub fn modify_mode<F>(&mut self, f: F) -> io::Result<()>
    where
        F: FnOnce(Termios) -> Termios,
    {
        self.guard.modify_mode(f)
    }

    /// Switch to raw mode.
    pub fn set_raw_mode(&mut self) -> io::Result<()> {
        self.guard.set_raw_mode()
    }
}

/// Types which can save a termios.
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)
    }
}

/// Types which can be converted into "raw mode".
///
pub trait IntoRawMode: Read + AsRawFd + Sized {
    /// Switch to raw mode.
    ///
    /// Raw mode means that stdin won't be printed (it will instead have to be written manually by
    /// the program). Furthermore, the input isn't canonicalised or buffered (that is, you can
    /// read from stdin one byte of a time). The output is neither modified in any way.
    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(())
    }
}