mirror of
https://github.com/asciinema/asciinema.git
synced 2025-12-16 19:58:03 +01:00
Implement keyboard control and --pause-on-markers in play command
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
@@ -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
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 cmd;
|
||||||
mod config;
|
mod config;
|
||||||
mod format;
|
mod format;
|
||||||
|
mod io;
|
||||||
mod locale;
|
mod locale;
|
||||||
mod player;
|
mod player;
|
||||||
mod pty;
|
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 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
14
src/pty.rs
14
src/pty.rs
@@ -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;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user