aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2021-12-04 02:08:27 -0500
committerJesse Luehrs <doy@tozt.net>2021-12-04 02:08:27 -0500
commit33cc13b554e6f4cc6e139b0eecf5c6e697433a9d (patch)
tree59a6558bb7f824852f823d6a9d47c9034c3ab613 /src
parent922151a0ad78027a54e6d0da6555117493e67ef8 (diff)
downloadttyrec-bin-33cc13b554e6f4cc6e139b0eecf5c6e697433a9d.tar.gz
ttyrec-bin-33cc13b554e6f4cc6e139b0eecf5c6e697433a9d.zip
initial implementation
Diffstat (limited to 'src')
-rw-r--r--src/bin/ttyplay/main.rs43
-rw-r--r--src/bin/ttyrec/main.rs135
-rw-r--r--src/main.rs3
3 files changed, 178 insertions, 3 deletions
diff --git a/src/bin/ttyplay/main.rs b/src/bin/ttyplay/main.rs
new file mode 100644
index 0000000..f30cda4
--- /dev/null
+++ b/src/bin/ttyplay/main.rs
@@ -0,0 +1,43 @@
+use textmode::Textmode as _;
+
+#[derive(Debug, structopt::StructOpt)]
+#[structopt(about = "ttyplay")]
+struct Opt {
+ #[structopt(short, long, default_value = "ttyrec")]
+ file: std::ffi::OsString,
+}
+
+async fn async_main(opt: Opt) -> anyhow::Result<()> {
+ let Opt { file } = opt;
+
+ let fh = async_std::fs::File::open(file).await?;
+ let mut reader = ttyrec::Reader::new(fh);
+
+ let mut input = textmode::Input::new().await?;
+ let mut output = textmode::Output::new().await?;
+ let _input_guard = input.take_raw_guard();
+ let _output_guard = output.take_screen_guard();
+
+ let mut last_frame_time = None;
+ while let Ok(frame) = reader.read_frame().await {
+ if let Some(time) = last_frame_time {
+ async_std::task::sleep(frame.time - time).await;
+ }
+ output.write(&frame.data);
+ output.refresh().await?;
+ last_frame_time = Some(frame.time);
+ }
+
+ Ok(())
+}
+
+#[paw::main]
+fn main(opt: Opt) {
+ match async_std::task::block_on(async_main(opt)) {
+ Ok(_) => (),
+ Err(e) => {
+ eprintln!("ttyplay: {}", e);
+ std::process::exit(1);
+ }
+ };
+}
diff --git a/src/bin/ttyrec/main.rs b/src/bin/ttyrec/main.rs
new file mode 100644
index 0000000..1db1504
--- /dev/null
+++ b/src/bin/ttyrec/main.rs
@@ -0,0 +1,135 @@
+use async_std::io::{ReadExt as _, WriteExt as _};
+use async_std::prelude::FutureExt as _;
+use pty_process::Command as _;
+
+#[derive(Debug, structopt::StructOpt)]
+#[structopt(about = "ttyrec")]
+struct Opt {
+ #[structopt(short, long, default_value = "ttyrec")]
+ file: std::ffi::OsString,
+ #[structopt(short, long)]
+ cmd: Option<std::ffi::OsString>,
+}
+
+fn get_cmd(
+ cmd: Option<std::ffi::OsString>,
+) -> (std::ffi::OsString, Vec<std::ffi::OsString>) {
+ if let Some(cmd) = cmd {
+ ("/bin/sh".into(), vec!["-c".into(), cmd])
+ } else {
+ let shell =
+ std::env::var_os("SHELL").unwrap_or_else(|| "/bin/sh".into());
+ (shell, vec![])
+ }
+}
+
+enum Event {
+ Key(textmode::Result<Option<textmode::Key>>),
+ Stdout(std::io::Result<Vec<u8>>),
+}
+
+async fn async_main(opt: Opt) -> anyhow::Result<()> {
+ let Opt { cmd, file } = opt;
+ let (cmd, args) = get_cmd(cmd);
+
+ let size = terminal_size::terminal_size().map_or(
+ (24, 80),
+ |(terminal_size::Width(w), terminal_size::Height(h))| (h, w),
+ );
+ let fh = async_std::fs::File::create(file).await?;
+ let mut input = textmode::Input::new().await?;
+ let _input_guard = input.take_raw_guard();
+ let mut stdout = async_std::io::stdout();
+ let child = async_std::process::Command::new(cmd)
+ .args(args)
+ .spawn_pty(Some(&pty_process::Size::new(size.0, size.1)))?;
+
+ let (event_w, event_r) = async_std::channel::unbounded();
+ let (input_w, input_r) = async_std::channel::unbounded();
+
+ {
+ let event_w = event_w.clone();
+ async_std::task::spawn(async move {
+ loop {
+ event_w
+ .send(Event::Key(input.read_key().await))
+ .await
+ .unwrap();
+ }
+ });
+ }
+
+ {
+ let event_w = event_w.clone();
+ async_std::task::spawn(async move {
+ loop {
+ enum Res {
+ Read(Result<usize, std::io::Error>),
+ Write(Result<Vec<u8>, async_std::channel::RecvError>),
+ }
+ let mut buf = [0_u8; 4096];
+ let mut pty = child.pty();
+ let read = async { Res::Read(pty.read(&mut buf).await) };
+ let write = async { Res::Write(input_r.recv().await) };
+ match read.race(write).await {
+ Res::Read(res) => {
+ let res = res.map(|n| buf[..n].to_vec());
+ let err = res.is_err();
+ event_w.send(Event::Stdout(res)).await.unwrap();
+ if err {
+ break;
+ }
+ }
+ Res::Write(res) => {
+ let bytes = res.unwrap();
+ pty.write(&bytes).await.unwrap();
+ }
+ }
+ }
+ });
+ }
+
+ let mut writer = ttyrec::Writer::new(fh);
+ loop {
+ match event_r.recv().await? {
+ Event::Key(key) => {
+ let key = key?;
+ if let Some(key) = key {
+ input_w.send(key.into_bytes()).await?;
+ } else {
+ break;
+ }
+ }
+ Event::Stdout(bytes) => match bytes {
+ Ok(bytes) => {
+ writer.frame(&bytes).await?;
+ stdout.write_all(&bytes).await?;
+ stdout.flush().await?;
+ }
+ Err(e) => {
+ if e.raw_os_error() == Some(libc::EIO) {
+ break;
+ } else {
+ anyhow::bail!(
+ "failed to read from child process: {}",
+ e
+ );
+ }
+ }
+ },
+ }
+ }
+
+ Ok(())
+}
+
+#[paw::main]
+fn main(opt: Opt) {
+ match async_std::task::block_on(async_main(opt)) {
+ Ok(_) => (),
+ Err(e) => {
+ eprintln!("ttyrec: {}", e);
+ std::process::exit(1);
+ }
+ };
+}
diff --git a/src/main.rs b/src/main.rs
deleted file mode 100644
index e7a11a9..0000000
--- a/src/main.rs
+++ /dev/null
@@ -1,3 +0,0 @@
-fn main() {
- println!("Hello, world!");
-}