diff --git a/src/lib.rs b/src/lib.rs index ea35d6d..09fcfe6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ extern crate tracing; pub mod asciicast; pub mod recorder; +mod raw_term; // pub mod recorder; // pub mod term; // pub mod writer; diff --git a/src/raw_term.rs b/src/raw_term.rs new file mode 100644 index 0000000..9423dcf --- /dev/null +++ b/src/raw_term.rs @@ -0,0 +1,38 @@ +use std::os::unix::prelude::RawFd; + +use anyhow::Result; +use nix::sys::termios::Termios; + +/// Wraps a RawFd in order to enable raw mode. +pub struct RawTerm(RawFd, Termios); + +impl RawTerm { + pub fn init(fd: RawFd) -> Result { + use nix::sys::termios::*; + let saved_mode = tcgetattr(fd)?; + let mut mode = saved_mode.clone(); + mode.input_flags &= !(InputFlags::BRKINT + | InputFlags::ICRNL + | InputFlags::INPCK + | InputFlags::ISTRIP + | InputFlags::IXON); + mode.output_flags &= !OutputFlags::OPOST; + mode.control_flags &= !(ControlFlags::CSIZE | ControlFlags::PARENB); + mode.control_flags |= ControlFlags::CS8; + mode.local_flags &= !(LocalFlags::ECHO + | LocalFlags::ICANON + | LocalFlags::IEXTEN + | LocalFlags::ISIG); + mode.control_chars[libc::VMIN] = 1; + mode.control_chars[libc::VTIME] = 0; + tcsetattr(fd, SetArg::TCSAFLUSH, &mode)?; + Ok(RawTerm(fd, saved_mode)) + } +} + +impl Drop for RawTerm { + fn drop(&mut self) { + use nix::sys::termios::*; + tcsetattr(self.0, SetArg::TCSAFLUSH, &self.1).unwrap(); + } +} diff --git a/src/recorder.rs b/src/recorder.rs index ce0da83..ff49b73 100644 --- a/src/recorder.rs +++ b/src/recorder.rs @@ -2,7 +2,8 @@ use std::env; use std::ffi::CString; -use std::os::unix::prelude::{AsRawFd, OsStrExt, RawFd}; +use std::io::stdin; +use std::os::unix::prelude::{AsFd, AsRawFd, OsStrExt, RawFd}; use std::process::{Command, Stdio}; use std::sync::mpsc::{self, Receiver, Sender}; use std::time::Instant; @@ -11,14 +12,21 @@ use anyhow::{Context, Result}; use libc::{O_RDWR, STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO}; use nix::fcntl::{open, OFlag}; use nix::pty::{openpty, OpenptyResult}; -use nix::sys::select::{select, FdSet}; -use nix::sys::stat::Mode; +use nix::sys::termios::{ + ControlFlags, InputFlags, LocalFlags, OutputFlags, SetArg, +}; +use nix::sys::{ + select::{select, FdSet}, + stat::Mode, + termios, +}; use nix::unistd::{ close, dup, dup2, execvp, execvpe, fork, fsync, read, setsid, ttyname, write, ForkResult, }; use crate::asciicast::{Event, EventKind}; +use crate::raw_term::RawTerm; pub struct Terminal { event_tx: Sender, @@ -56,6 +64,10 @@ impl Terminal { command.env(key, val); } + // Set raw mode + let stdin = stdin(); + let _term = RawTerm::init(stdin.as_raw_fd())?; + // Open a pty let pty = openpty(None, None)?; let parent_stdout_dup = dup(STDOUT_FILENO)?; @@ -135,6 +147,9 @@ impl Terminal { // Master is ready for read, which means child process stdout has data // (child process stdout) -> (pty.slave) -> (pty.master) if read_fd_set.contains(pty.master) { + // TODO: This line trips if the child process dies from, say, Ctrl+D. Need to catch this + // somehow and gracefully react to it. + // Possibly signals would be a good solution here? let bytes_read = read(pty.master, &mut buf).context("Read from master")?; if bytes_read == 0 {