Files
asciinema/src/recorder.rs

212 lines
5.7 KiB
Rust
Raw Normal View History

use crate::config::Key;
2023-10-24 10:29:09 +02:00
use crate::pty;
2023-10-28 13:36:54 +02:00
use std::collections::HashMap;
use std::io;
use std::sync::mpsc;
use std::thread;
2023-10-28 11:29:04 +02:00
use std::time::{Instant, SystemTime, UNIX_EPOCH};
2023-10-24 10:29:09 +02:00
pub struct Recorder {
2024-01-11 11:09:10 +01:00
writer: Option<Box<dyn EventWriter + Send>>,
2023-10-28 11:29:04 +02:00
start_time: Instant,
pause_time: Option<Instant>,
2023-10-24 10:29:09 +02:00
append: bool,
record_input: bool,
2024-01-11 11:09:10 +01:00
metadata: Metadata,
keys: KeyBindings,
sender: mpsc::Sender<Message>,
receiver: Option<mpsc::Receiver<Message>>,
handle: Option<JoinHandle>,
prefix_mode: bool,
2023-10-24 10:29:09 +02:00
}
2024-01-11 11:09:10 +01:00
pub trait EventWriter {
fn start(&mut self, header: &Header, append: bool) -> io::Result<()>;
fn output(&mut self, time: u64, data: &[u8]) -> io::Result<()>;
fn input(&mut self, time: u64, data: &[u8]) -> io::Result<()>;
fn resize(&mut self, time: u64, size: (u16, u16)) -> io::Result<()>;
}
pub struct Header {
pub cols: u16,
pub rows: u16,
pub timestamp: Option<u64>,
pub idle_time_limit: Option<f64>,
pub command: Option<String>,
pub title: Option<String>,
pub env: HashMap<String, String>,
}
pub struct Metadata {
pub idle_time_limit: Option<f64>,
pub command: Option<String>,
pub title: Option<String>,
pub env: HashMap<String, String>,
}
enum Message {
Output(u64, Vec<u8>),
Input(u64, Vec<u8>),
Resize(u64, (u16, u16)),
}
struct JoinHandle(Option<thread::JoinHandle<()>>);
2023-10-26 12:02:53 +02:00
impl Recorder {
pub fn new(
2024-01-11 11:09:10 +01:00
writer: Box<dyn EventWriter + Send>,
append: bool,
record_input: bool,
2024-01-11 11:09:10 +01:00
metadata: Metadata,
keys: KeyBindings,
) -> Self {
let (sender, receiver) = mpsc::channel();
Recorder {
writer: Some(writer),
2023-10-28 11:29:04 +02:00
start_time: Instant::now(),
pause_time: None,
append,
record_input,
2024-01-11 11:09:10 +01:00
metadata,
keys,
sender,
receiver: Some(receiver),
handle: None,
prefix_mode: false,
}
}
fn elapsed_time(&self) -> u64 {
self.start_time.elapsed().as_micros() as u64
2023-10-26 12:02:53 +02:00
}
}
2023-10-24 10:29:09 +02:00
2023-10-26 12:02:53 +02:00
impl pty::Recorder for Recorder {
fn start(&mut self, size: (u16, u16)) -> io::Result<()> {
let timestamp = SystemTime::now()
2023-10-28 11:29:04 +02:00
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let mut writer = self.writer.take().unwrap();
let receiver = self.receiver.take().unwrap();
2024-01-11 11:09:10 +01:00
let header = Header {
cols: size.0,
rows: size.1,
timestamp: Some(timestamp),
idle_time_limit: self.metadata.idle_time_limit,
command: self.metadata.command.clone(),
title: self.metadata.title.clone(),
env: self.metadata.env.clone(),
};
writer.start(&header, self.append)?;
let handle = thread::spawn(move || {
for msg in receiver {
match msg {
Message::Output(time, data) => {
let _ = writer.output(time, &data);
}
Message::Input(time, data) => {
let _ = writer.input(time, &data);
}
Message::Resize(time, size) => {
let _ = writer.resize(time, size);
}
}
}
});
self.handle = Some(JoinHandle(Some(handle)));
self.start_time = Instant::now();
Ok(())
2023-10-26 12:02:53 +02:00
}
fn output(&mut self, data: &[u8]) {
if self.pause_time.is_some() {
return;
}
let msg = Message::Output(self.elapsed_time(), data.into());
let _ = self.sender.send(msg);
2023-10-26 12:02:53 +02:00
// TODO use notifier for error reporting
}
fn input(&mut self, data: &[u8]) -> bool {
let prefix_key = self.keys.prefix.as_ref();
let pause_key = self.keys.pause.as_ref();
let add_marker_key = self.keys.add_marker.as_ref();
if !self.prefix_mode && prefix_key.is_some_and(|key| data == key) {
self.prefix_mode = true;
return false;
}
if self.prefix_mode || prefix_key.is_none() {
self.prefix_mode = false;
if pause_key.is_some_and(|key| data == key) {
if let Some(pt) = self.pause_time {
self.start_time += pt.elapsed();
self.pause_time = None;
// notify("Resumed recording")
} else {
self.pause_time = Some(Instant::now());
// notify("Paused recording")
}
return false;
} else if add_marker_key.is_some_and(|key| data == key) {
// TODO
// let msg = Message::AddMarker(self.elapsed_time());
// let _ = self.sender.send(msg);
// notify("Marker added")
return false;
}
}
if self.record_input && self.pause_time.is_none() {
// TODO ignore OSC responses
let msg = Message::Input(self.elapsed_time(), data.into());
let _ = self.sender.send(msg);
2023-10-26 12:02:53 +02:00
// TODO use notifier for error reporting
}
true
2023-10-26 12:02:53 +02:00
}
2023-10-30 13:40:53 +01:00
fn resize(&mut self, size: (u16, u16)) {
let msg = Message::Resize(self.elapsed_time(), size);
let _ = self.sender.send(msg);
// TODO use notifier for error reporting
}
}
impl Drop for JoinHandle {
fn drop(&mut self) {
self.0.take().unwrap().join().expect("Thread panicked");
2023-10-30 13:40:53 +01:00
}
2023-10-26 12:02:53 +02:00
}
pub struct KeyBindings {
pub prefix: Key,
pub pause: Key,
pub add_marker: Key,
}
impl Default for KeyBindings {
fn default() -> Self {
Self {
prefix: None,
pause: Some(vec![0x1c]), // ^\
add_marker: None,
}
}
}