mirror of
https://github.com/asciinema/asciinema.git
synced 2025-12-16 11:48:13 +01:00
Implement keyboard control and --pause-on-markers in play command
This commit is contained in:
@@ -30,7 +30,14 @@ impl Cli {
|
||||
|
||||
loop {
|
||||
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_ {
|
||||
break;
|
||||
|
||||
@@ -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)]
|
||||
mod tests {
|
||||
use super::{Event, EventCode, Header, Writer};
|
||||
|
||||
14
src/io.rs
Normal file
14
src/io.rs
Normal 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(())
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
mod cmd;
|
||||
mod config;
|
||||
mod format;
|
||||
mod io;
|
||||
mod locale;
|
||||
mod player;
|
||||
mod pty;
|
||||
|
||||
167
src/player.rs
167
src/player.rs
@@ -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 std::io;
|
||||
use std::thread;
|
||||
use nix::sys::select::{pselect, FdSet};
|
||||
use nix::sys::time::{TimeSpec, TimeValLike};
|
||||
use std::io::Read;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::{fs, io};
|
||||
use termion::raw::{IntoRawMode, RawTerminal};
|
||||
|
||||
pub fn play(
|
||||
recording: impl io::Read,
|
||||
mut output: impl io::Write,
|
||||
speed: f64,
|
||||
idle_time_limit: Option<f64>,
|
||||
pause_on_markers: bool,
|
||||
) -> 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 (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::accelerate(events, speed);
|
||||
let events = asciicast::output(events);
|
||||
let epoch = Instant::now();
|
||||
|
||||
for event in events {
|
||||
let (time, data) = event?;
|
||||
let diff = time as i64 - epoch.elapsed().as_micros() as i64;
|
||||
|
||||
if diff > 0 {
|
||||
output.flush()?;
|
||||
thread::sleep(Duration::from_micros(diff as u64));
|
||||
}
|
||||
|
||||
output.write_all(data.as_bytes())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(events)
|
||||
}
|
||||
|
||||
fn read_key(input: &mut fs::File, timeout: i64) -> Result<Option<u8>> {
|
||||
let nfds = Some(input.as_raw_fd() + 1);
|
||||
let mut rfds = FdSet::new();
|
||||
rfds.insert(input);
|
||||
let timeout = TimeSpec::microseconds(timeout);
|
||||
|
||||
pselect(nfds, &mut rfds, None, None, &timeout, None)?;
|
||||
|
||||
if rfds.contains(input) {
|
||||
let mut buf = [0u8; 1];
|
||||
input.read_exact(&mut buf)?;
|
||||
|
||||
Ok(Some(buf[0]))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
14
src/pty.rs
14
src/pty.rs
@@ -1,6 +1,7 @@
|
||||
use crate::io::set_non_blocking;
|
||||
use anyhow::bail;
|
||||
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_mio::v0_8::Signals;
|
||||
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) };
|
||||
}
|
||||
|
||||
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> {
|
||||
let mut read = 0;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user