2018-07-21 14:39:56 +03:00
|
|
|
use failure::Error;
|
|
|
|
use istty::IsTty;
|
|
|
|
use libc::{self, winsize};
|
2018-07-22 17:03:11 +03:00
|
|
|
use std::fs::OpenOptions;
|
2018-07-22 21:30:26 +03:00
|
|
|
use std::io::{stdin, stdout, Error as IOError, ErrorKind, Read, Write};
|
2018-07-21 14:39:56 +03:00
|
|
|
use std::mem;
|
2018-07-22 21:30:26 +03:00
|
|
|
use std::ops::Deref;
|
2018-07-21 14:39:56 +03:00
|
|
|
use std::os::unix::io::{AsRawFd, RawFd};
|
2018-07-21 20:59:41 +03:00
|
|
|
use termios::{
|
2018-07-21 21:18:24 +03:00
|
|
|
cfmakeraw, tcdrain, tcflush, tcsetattr, Termios, TCIFLUSH, TCIOFLUSH, TCOFLUSH, TCSADRAIN,
|
|
|
|
TCSAFLUSH, TCSANOW,
|
2018-07-21 20:59:41 +03:00
|
|
|
};
|
2018-07-21 14:39:56 +03:00
|
|
|
|
2018-07-21 23:19:38 +03:00
|
|
|
use caps::Capabilities;
|
|
|
|
use render::terminfo::TerminfoRenderer;
|
2018-07-22 15:44:51 +03:00
|
|
|
use surface::Change;
|
2018-07-22 21:30:26 +03:00
|
|
|
use terminal::{cast, ScreenSize, Terminal};
|
|
|
|
|
|
|
|
const BUF_SIZE: usize = 128;
|
2018-07-21 15:12:56 +03:00
|
|
|
|
2018-07-21 20:30:15 +03:00
|
|
|
/// Helper function to duplicate a file descriptor.
|
|
|
|
/// The duplicated descriptor will have the close-on-exec flag set.
|
|
|
|
fn dup(fd: RawFd) -> Result<RawFd, Error> {
|
|
|
|
let new_fd = unsafe { libc::fcntl(fd, libc::F_DUPFD_CLOEXEC, 0) };
|
|
|
|
if new_fd == -1 {
|
|
|
|
bail!("dup of pty fd failed: {:?}", IOError::last_os_error())
|
|
|
|
}
|
|
|
|
Ok(new_fd)
|
2018-07-21 15:12:56 +03:00
|
|
|
}
|
|
|
|
|
2018-07-21 20:59:41 +03:00
|
|
|
pub enum Purge {
|
|
|
|
InputQueue,
|
|
|
|
OutputQueue,
|
|
|
|
InputAndOutputQueue,
|
|
|
|
}
|
|
|
|
|
2018-07-21 21:18:24 +03:00
|
|
|
pub enum SetAttributeWhen {
|
|
|
|
/// changes are applied immediately
|
|
|
|
Now,
|
|
|
|
/// Apply once the current output queue has drained
|
|
|
|
AfterDrainOutputQueue,
|
|
|
|
/// Wait for the current output queue to drain, then
|
|
|
|
/// discard any unread input
|
|
|
|
AfterDrainOutputQueuePurgeInputQueue,
|
|
|
|
}
|
|
|
|
|
2018-07-21 20:30:15 +03:00
|
|
|
pub trait UnixTty {
|
|
|
|
fn get_size(&mut self) -> Result<winsize, Error>;
|
|
|
|
fn set_size(&mut self, size: winsize) -> Result<(), Error>;
|
|
|
|
fn get_termios(&mut self) -> Result<Termios, Error>;
|
2018-07-21 21:18:24 +03:00
|
|
|
fn set_termios(&mut self, termios: &Termios, when: SetAttributeWhen) -> Result<(), Error>;
|
2018-07-21 20:59:41 +03:00
|
|
|
/// Waits until all written data has been transmitted.
|
|
|
|
fn drain(&mut self) -> Result<(), Error>;
|
|
|
|
fn purge(&mut self, purge: Purge) -> Result<(), Error>;
|
2018-07-21 20:30:15 +03:00
|
|
|
}
|
|
|
|
|
2018-07-22 21:30:26 +03:00
|
|
|
struct Fd {
|
2018-07-21 20:30:15 +03:00
|
|
|
fd: RawFd,
|
|
|
|
}
|
|
|
|
|
2018-07-22 21:30:26 +03:00
|
|
|
impl Drop for Fd {
|
2018-07-21 20:30:15 +03:00
|
|
|
fn drop(&mut self) {
|
|
|
|
unsafe {
|
|
|
|
libc::close(self.fd);
|
2018-07-21 15:12:56 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-07-22 21:30:26 +03:00
|
|
|
|
|
|
|
impl Fd {
|
|
|
|
pub fn new<S: AsRawFd>(s: &S) -> Result<Self, Error> {
|
|
|
|
ensure!(s.is_tty(), "Can only construct a TtyHandle from a tty");
|
|
|
|
let fd = dup(s.as_raw_fd())?;
|
|
|
|
Ok(Self { fd })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Deref for Fd {
|
|
|
|
type Target = RawFd;
|
|
|
|
fn deref(&self) -> &RawFd {
|
|
|
|
&self.fd
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct TtyReadHandle {
|
|
|
|
fd: Fd,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TtyReadHandle {
|
|
|
|
fn new(fd: Fd) -> Self {
|
|
|
|
Self { fd }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Read for TtyReadHandle {
|
|
|
|
fn read(&mut self, buf: &mut [u8]) -> Result<usize, IOError> {
|
|
|
|
let size = unsafe { libc::read(*self.fd, buf.as_mut_ptr() as *mut _, buf.len()) };
|
2018-07-21 20:30:15 +03:00
|
|
|
if size == -1 {
|
|
|
|
Err(IOError::last_os_error())
|
|
|
|
} else {
|
|
|
|
Ok(size as usize)
|
2018-07-21 15:12:56 +03:00
|
|
|
}
|
|
|
|
}
|
2018-07-22 21:30:26 +03:00
|
|
|
}
|
2018-07-21 15:12:56 +03:00
|
|
|
|
2018-07-22 21:30:26 +03:00
|
|
|
pub struct TtyWriteHandle {
|
|
|
|
fd: Fd,
|
|
|
|
write_buffer: Vec<u8>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TtyWriteHandle {
|
|
|
|
fn new(fd: Fd) -> Self {
|
|
|
|
Self {
|
|
|
|
fd,
|
|
|
|
write_buffer: Vec::with_capacity(BUF_SIZE),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn flush_local_buffer(&mut self) -> Result<(), IOError> {
|
|
|
|
if self.write_buffer.len() > 0 {
|
|
|
|
do_write(*self.fd, &self.write_buffer)?;
|
|
|
|
self.write_buffer.clear();
|
|
|
|
}
|
2018-07-21 20:30:15 +03:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-22 21:30:26 +03:00
|
|
|
fn do_write(fd: RawFd, buf: &[u8]) -> Result<usize, IOError> {
|
|
|
|
let size = unsafe { libc::write(fd, buf.as_ptr() as *const _, buf.len()) };
|
|
|
|
if size == -1 {
|
|
|
|
Err(IOError::last_os_error())
|
|
|
|
} else {
|
|
|
|
Ok(size as usize)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Write for TtyWriteHandle {
|
|
|
|
fn write(&mut self, buf: &[u8]) -> Result<usize, IOError> {
|
|
|
|
if self.write_buffer.len() + buf.len() > self.write_buffer.capacity() {
|
|
|
|
self.flush()?;
|
|
|
|
}
|
|
|
|
if buf.len() >= self.write_buffer.capacity() {
|
|
|
|
do_write(*self.fd, buf)
|
2018-07-21 20:30:15 +03:00
|
|
|
} else {
|
2018-07-22 21:30:26 +03:00
|
|
|
self.write_buffer.write(buf)
|
2018-07-21 15:12:56 +03:00
|
|
|
}
|
|
|
|
}
|
2018-07-21 14:39:56 +03:00
|
|
|
|
2018-07-22 21:30:26 +03:00
|
|
|
fn flush(&mut self) -> Result<(), IOError> {
|
|
|
|
self.flush_local_buffer()?;
|
|
|
|
self.drain()
|
|
|
|
.map_err(|e| IOError::new(ErrorKind::Other, format!("{}", e)))?;
|
|
|
|
Ok(())
|
2018-07-21 20:30:15 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-22 21:30:26 +03:00
|
|
|
impl UnixTty for TtyWriteHandle {
|
2018-07-21 20:30:15 +03:00
|
|
|
fn get_size(&mut self) -> Result<winsize, Error> {
|
|
|
|
let mut size: winsize = unsafe { mem::zeroed() };
|
2018-07-22 21:30:26 +03:00
|
|
|
if unsafe { libc::ioctl(*self.fd, libc::TIOCGWINSZ, &mut size) } != 0 {
|
2018-07-21 20:30:15 +03:00
|
|
|
bail!("failed to ioctl(TIOCGWINSZ): {}", IOError::last_os_error());
|
|
|
|
}
|
|
|
|
Ok(size)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn set_size(&mut self, size: winsize) -> Result<(), Error> {
|
2018-07-22 21:30:26 +03:00
|
|
|
if unsafe { libc::ioctl(*self.fd, libc::TIOCSWINSZ, &size as *const _) } != 0 {
|
2018-07-21 20:30:15 +03:00
|
|
|
bail!(
|
|
|
|
"failed to ioctl(TIOCSWINSZ): {:?}",
|
|
|
|
IOError::last_os_error()
|
|
|
|
);
|
2018-07-21 14:39:56 +03:00
|
|
|
}
|
2018-07-21 20:30:15 +03:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_termios(&mut self) -> Result<Termios, Error> {
|
2018-07-22 21:30:26 +03:00
|
|
|
Termios::from_fd(*self.fd).map_err(|e| format_err!("get_termios failed: {}", e))
|
2018-07-21 20:30:15 +03:00
|
|
|
}
|
|
|
|
|
2018-07-21 21:18:24 +03:00
|
|
|
fn set_termios(&mut self, termios: &Termios, when: SetAttributeWhen) -> Result<(), Error> {
|
|
|
|
let when = match when {
|
|
|
|
SetAttributeWhen::Now => TCSANOW,
|
|
|
|
SetAttributeWhen::AfterDrainOutputQueue => TCSADRAIN,
|
|
|
|
SetAttributeWhen::AfterDrainOutputQueuePurgeInputQueue => TCSAFLUSH,
|
|
|
|
};
|
2018-07-22 21:30:26 +03:00
|
|
|
tcsetattr(*self.fd, when, termios).map_err(|e| format_err!("set_termios failed: {}", e))
|
2018-07-21 14:39:56 +03:00
|
|
|
}
|
2018-07-21 20:59:41 +03:00
|
|
|
|
|
|
|
fn drain(&mut self) -> Result<(), Error> {
|
2018-07-22 21:30:26 +03:00
|
|
|
tcdrain(*self.fd).map_err(|e| format_err!("tcdrain failed: {}", e))
|
2018-07-21 20:59:41 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
fn purge(&mut self, purge: Purge) -> Result<(), Error> {
|
|
|
|
let param = match purge {
|
|
|
|
Purge::InputQueue => TCIFLUSH,
|
|
|
|
Purge::OutputQueue => TCOFLUSH,
|
|
|
|
Purge::InputAndOutputQueue => TCIOFLUSH,
|
|
|
|
};
|
2018-07-22 21:30:26 +03:00
|
|
|
tcflush(*self.fd, param).map_err(|e| format_err!("tcflush failed: {}", e))
|
2018-07-21 20:59:41 +03:00
|
|
|
}
|
2018-07-21 14:39:56 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/// A unix style terminal
|
|
|
|
pub struct UnixTerminal {
|
2018-07-22 21:30:26 +03:00
|
|
|
read: TtyReadHandle,
|
|
|
|
write: TtyWriteHandle,
|
2018-07-21 14:39:56 +03:00
|
|
|
saved_termios: Termios,
|
2018-07-21 23:19:38 +03:00
|
|
|
renderer: TerminfoRenderer,
|
2018-07-21 14:39:56 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
impl UnixTerminal {
|
|
|
|
/// Attempt to create an instance from the stdin and stdout of the
|
|
|
|
/// process. This will fail unless both are associated with a tty.
|
2018-07-21 20:30:15 +03:00
|
|
|
/// Note that this will duplicate the underlying file descriptors
|
|
|
|
/// and will no longer participate in the stdin/stdout locking
|
|
|
|
/// provided by the rust standard library.
|
2018-07-21 23:19:38 +03:00
|
|
|
pub fn new_from_stdio(caps: Capabilities) -> Result<UnixTerminal, Error> {
|
|
|
|
Self::new_with(caps, &stdin(), &stdout())
|
|
|
|
}
|
2018-07-21 14:39:56 +03:00
|
|
|
|
2018-07-21 23:19:38 +03:00
|
|
|
pub fn new_with<A: AsRawFd, B: AsRawFd>(
|
|
|
|
caps: Capabilities,
|
|
|
|
read: &A,
|
|
|
|
write: &B,
|
|
|
|
) -> Result<UnixTerminal, Error> {
|
2018-07-22 21:30:26 +03:00
|
|
|
let read = TtyReadHandle::new(Fd::new(read)?);
|
|
|
|
let mut write = TtyWriteHandle::new(Fd::new(write)?);
|
2018-07-21 20:30:15 +03:00
|
|
|
let saved_termios = write.get_termios()?;
|
2018-07-21 23:19:38 +03:00
|
|
|
let renderer = TerminfoRenderer::new(caps);
|
2018-07-21 14:39:56 +03:00
|
|
|
|
|
|
|
Ok(UnixTerminal {
|
2018-07-21 20:30:15 +03:00
|
|
|
read,
|
|
|
|
write,
|
2018-07-21 14:39:56 +03:00
|
|
|
saved_termios,
|
2018-07-21 23:19:38 +03:00
|
|
|
renderer,
|
2018-07-21 14:39:56 +03:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Attempt to explicitly open a handle to the terminal device
|
|
|
|
/// (/dev/tty) and build a `UnixTerminal` from there. This will
|
|
|
|
/// yield a terminal even if the stdio streams have been redirected,
|
|
|
|
/// provided that the process has an associated controlling terminal.
|
2018-07-21 23:19:38 +03:00
|
|
|
pub fn new(caps: Capabilities) -> Result<UnixTerminal, Error> {
|
2018-07-21 14:39:56 +03:00
|
|
|
let file = OpenOptions::new().read(true).write(true).open("/dev/tty")?;
|
2018-07-21 23:19:38 +03:00
|
|
|
Self::new_with(caps, &file, &file)
|
2018-07-21 14:39:56 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Terminal for UnixTerminal {
|
|
|
|
fn set_raw_mode(&mut self) -> Result<(), Error> {
|
2018-07-21 20:30:15 +03:00
|
|
|
let mut raw = self.write.get_termios()?;
|
2018-07-21 14:39:56 +03:00
|
|
|
cfmakeraw(&mut raw);
|
2018-07-21 20:30:15 +03:00
|
|
|
self.write
|
2018-07-21 21:18:24 +03:00
|
|
|
.set_termios(&raw, SetAttributeWhen::AfterDrainOutputQueuePurgeInputQueue)
|
2018-07-21 20:30:15 +03:00
|
|
|
.map_err(|e| format_err!("failed to set raw mode: {}", e))
|
2018-07-21 14:39:56 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
fn get_screen_size(&mut self) -> Result<ScreenSize, Error> {
|
2018-07-21 20:30:15 +03:00
|
|
|
let size = self.write.get_size()?;
|
2018-07-21 14:39:56 +03:00
|
|
|
Ok(ScreenSize {
|
|
|
|
rows: cast(size.ws_row)?,
|
|
|
|
cols: cast(size.ws_col)?,
|
|
|
|
xpixel: cast(size.ws_xpixel)?,
|
|
|
|
ypixel: cast(size.ws_ypixel)?,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
fn set_screen_size(&mut self, size: ScreenSize) -> Result<(), Error> {
|
|
|
|
let size = winsize {
|
|
|
|
ws_row: cast(size.rows)?,
|
|
|
|
ws_col: cast(size.cols)?,
|
|
|
|
ws_xpixel: cast(size.xpixel)?,
|
|
|
|
ws_ypixel: cast(size.ypixel)?,
|
|
|
|
};
|
|
|
|
|
2018-07-21 20:30:15 +03:00
|
|
|
self.write.set_size(size)
|
2018-07-21 14:39:56 +03:00
|
|
|
}
|
2018-07-21 23:19:38 +03:00
|
|
|
fn render(&mut self, changes: &[Change]) -> Result<(), Error> {
|
|
|
|
self.renderer
|
|
|
|
.render_to(changes, &mut self.read, &mut self.write)
|
|
|
|
}
|
2018-07-22 21:30:26 +03:00
|
|
|
fn flush(&mut self) -> Result<(), Error> {
|
|
|
|
self.write
|
|
|
|
.flush()
|
|
|
|
.map_err(|e| format_err!("flush failed: {}", e))
|
|
|
|
}
|
2018-07-21 14:39:56 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Drop for UnixTerminal {
|
|
|
|
fn drop(&mut self) {
|
2018-07-21 20:30:15 +03:00
|
|
|
self.write
|
2018-07-21 21:18:24 +03:00
|
|
|
.set_termios(&self.saved_termios, SetAttributeWhen::Now)
|
2018-07-21 14:39:56 +03:00
|
|
|
.expect("failed to restore original termios state");
|
|
|
|
}
|
|
|
|
}
|