Implement keyboard control and --pause-on-markers in play command

This commit is contained in:
Marcin Kulik
2024-01-03 16:59:42 +01:00
parent b2b612cd92
commit 6fb090b9bb
6 changed files with 174 additions and 46 deletions

View File

@@ -30,7 +30,14 @@ impl Cli {
loop { loop {
let file = fs::File::open(&self.filename)?; let file = fs::File::open(&self.filename)?;
player::play(file, io::stdout(), speed, self.idle_time_limit)?;
player::play(
file,
io::stdout(),
speed,
self.idle_time_limit,
self.pause_on_markers,
)?;
if !self.loop_ { if !self.loop_ {
break; break;

View File

@@ -330,21 +330,6 @@ pub fn accelerate(
}) })
} }
pub fn output(
events: impl Iterator<Item = Result<Event>>,
) -> impl Iterator<Item = Result<(u64, String)>> {
events.filter_map(|e| match e {
Ok(Event {
code: EventCode::Output,
time,
data,
}) => Some(Ok((time, data))),
Ok(_) => None,
Err(e) => Some(Err(e)),
})
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{Event, EventCode, Header, Writer}; use super::{Event, EventCode, Header, Writer};

14
src/io.rs Normal file
View File

@@ -0,0 +1,14 @@
use anyhow::Result;
use std::io;
use std::os::fd::RawFd;
pub fn set_non_blocking(fd: &RawFd) -> Result<(), io::Error> {
use nix::fcntl::{fcntl, FcntlArg::*, OFlag};
let flags = fcntl(*fd, F_GETFL)?;
let mut oflags = OFlag::from_bits_truncate(flags);
oflags |= OFlag::O_NONBLOCK;
fcntl(*fd, F_SETFL(oflags))?;
Ok(())
}

View File

@@ -1,6 +1,7 @@
mod cmd; mod cmd;
mod config; mod config;
mod format; mod format;
mod io;
mod locale; mod locale;
mod player; mod player;
mod pty; mod pty;

View File

@@ -1,15 +1,142 @@
use crate::format::asciicast; use crate::format::asciicast::{self, Event, EventCode};
use crate::io::set_non_blocking;
use anyhow::Result; use anyhow::Result;
use std::io; use nix::sys::select::{pselect, FdSet};
use std::thread; use nix::sys::time::{TimeSpec, TimeValLike};
use std::io::Read;
use std::os::unix::io::AsRawFd;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use std::{fs, io};
use termion::raw::{IntoRawMode, RawTerminal};
pub fn play( pub fn play(
recording: impl io::Read, recording: impl io::Read,
mut output: impl io::Write, mut output: impl io::Write,
speed: f64, speed: f64,
idle_time_limit: Option<f64>, idle_time_limit: Option<f64>,
pause_on_markers: bool,
) -> Result<()> { ) -> Result<()> {
let mut tty = open_tty()?;
let mut events = open_recording(recording, speed, idle_time_limit)?;
let mut epoch = Instant::now();
let mut pause_elapsed_time: Option<u64> = None;
let mut next_event = events.next().transpose()?;
while let Some(Event { time, code, data }) = &next_event {
if let Some(pet) = pause_elapsed_time {
match read_key(&mut tty, 1_000_000)? {
Some(0x03) => {
// ctrl+c - stop
output.write_all("\r\n".as_bytes())?;
return Ok(());
}
Some(0x20) => {
// space - resume
epoch = Instant::now() - Duration::from_micros(pet);
pause_elapsed_time = None;
}
Some(0x2e) => {
// . - step
pause_elapsed_time = Some(*time);
if code == &EventCode::Output {
output.write_all(data.as_bytes())?;
output.flush()?;
}
next_event = events.next().transpose()?;
}
Some(0x5d) => {
// [ - next marker
while let Some(Event { time, code, data }) = next_event {
next_event = events.next().transpose()?;
match code {
EventCode::Output => {
output.write_all(data.as_bytes())?;
}
EventCode::Marker => {
pause_elapsed_time = Some(time);
break;
}
_ => {}
}
}
output.flush()?;
}
_ => (),
}
} else {
while let Some(Event { time, code, data }) = &next_event {
let delay = *time as i64 - epoch.elapsed().as_micros() as i64;
if delay > 0 {
output.flush()?;
match read_key(&mut tty, delay)? {
Some(0x03) => {
// ctrl+c - stop
output.write_all("\r\n".as_bytes())?;
return Ok(());
}
Some(0x20) => {
// space - pause
pause_elapsed_time = Some(epoch.elapsed().as_micros() as u64);
break;
}
Some(_) => {
continue;
}
None => (),
}
}
match code {
EventCode::Output => {
output.write_all(data.as_bytes())?;
}
EventCode::Marker => {
if pause_on_markers {
pause_elapsed_time = Some(*time);
next_event = events.next().transpose()?;
break;
}
}
_ => (),
}
next_event = events.next().transpose()?;
}
}
}
Ok(())
}
fn open_tty() -> Result<RawTerminal<fs::File>> {
let tty = fs::File::open("/dev/tty")?.into_raw_mode()?;
set_non_blocking(&tty.as_raw_fd())?;
Ok(tty)
}
fn open_recording(
recording: impl io::Read,
speed: f64,
idle_time_limit: Option<f64>,
) -> Result<impl Iterator<Item = Result<Event>>> {
let reader = io::BufReader::new(recording); let reader = io::BufReader::new(recording);
let (header, events) = asciicast::open(reader)?; let (header, events) = asciicast::open(reader)?;
@@ -19,20 +146,24 @@ pub fn play(
let events = asciicast::limit_idle_time(events, idle_time_limit); let events = asciicast::limit_idle_time(events, idle_time_limit);
let events = asciicast::accelerate(events, speed); let events = asciicast::accelerate(events, speed);
let events = asciicast::output(events);
let epoch = Instant::now();
for event in events { Ok(events)
let (time, data) = event?; }
let diff = time as i64 - epoch.elapsed().as_micros() as i64;
fn read_key(input: &mut fs::File, timeout: i64) -> Result<Option<u8>> {
if diff > 0 { let nfds = Some(input.as_raw_fd() + 1);
output.flush()?; let mut rfds = FdSet::new();
thread::sleep(Duration::from_micros(diff as u64)); rfds.insert(input);
} let timeout = TimeSpec::microseconds(timeout);
output.write_all(data.as_bytes())?; pselect(nfds, &mut rfds, None, None, &timeout, None)?;
}
if rfds.contains(input) {
Ok(()) let mut buf = [0u8; 1];
input.read_exact(&mut buf)?;
Ok(Some(buf[0]))
} else {
Ok(None)
}
} }

View File

@@ -1,6 +1,7 @@
use crate::io::set_non_blocking;
use anyhow::bail; use anyhow::bail;
use mio::unix::SourceFd; use mio::unix::SourceFd;
use nix::{fcntl, libc, pty, sys::signal, sys::wait, unistd, unistd::ForkResult}; use nix::{libc, pty, sys::signal, sys::wait, unistd, unistd::ForkResult};
use signal_hook::consts::signal::*; use signal_hook::consts::signal::*;
use signal_hook_mio::v0_8::Signals; use signal_hook_mio::v0_8::Signals;
use std::collections::HashMap; use std::collections::HashMap;
@@ -268,17 +269,6 @@ fn set_pty_size(pty_fd: i32, winsize: &pty::Winsize) {
unsafe { libc::ioctl(pty_fd, libc::TIOCSWINSZ, winsize) }; unsafe { libc::ioctl(pty_fd, libc::TIOCSWINSZ, winsize) };
} }
fn set_non_blocking(fd: &RawFd) -> Result<(), io::Error> {
use fcntl::{fcntl, FcntlArg::*, OFlag};
let flags = fcntl(*fd, F_GETFL)?;
let mut oflags = OFlag::from_bits_truncate(flags);
oflags |= OFlag::O_NONBLOCK;
fcntl(*fd, F_SETFL(oflags))?;
Ok(())
}
fn read_all<R: Read>(source: &mut R, buf: &mut [u8], out: &mut Vec<u8>) -> io::Result<usize> { fn read_all<R: Read>(source: &mut R, buf: &mut [u8], out: &mut Vec<u8>) -> io::Result<usize> {
let mut read = 0; let mut read = 0;