2024-01-12 12:18:04 +01:00
|
|
|
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;
|
2023-10-27 22:25:28 +02:00
|
|
|
use std::io;
|
2023-10-31 17:17:45 +01:00
|
|
|
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,
|
2024-01-12 12:18:04 +01:00
|
|
|
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,
|
2024-01-12 12:18:04 +01:00
|
|
|
keys: KeyBindings,
|
2023-10-31 17:17:45 +01:00
|
|
|
sender: mpsc::Sender<Message>,
|
|
|
|
|
receiver: Option<mpsc::Receiver<Message>>,
|
|
|
|
|
handle: Option<JoinHandle>,
|
2024-01-12 12:18:04 +01:00
|
|
|
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>,
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-31 17:17:45 +01:00
|
|
|
enum Message {
|
2024-01-02 12:21:58 +01:00
|
|
|
Output(u64, Vec<u8>),
|
|
|
|
|
Input(u64, Vec<u8>),
|
|
|
|
|
Resize(u64, (u16, u16)),
|
2023-10-31 17:17:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct JoinHandle(Option<thread::JoinHandle<()>>);
|
|
|
|
|
|
2023-10-26 12:02:53 +02:00
|
|
|
impl Recorder {
|
2023-10-28 10:44:12 +02:00
|
|
|
pub fn new(
|
2024-01-11 11:09:10 +01:00
|
|
|
writer: Box<dyn EventWriter + Send>,
|
2023-10-28 10:44:12 +02:00
|
|
|
append: bool,
|
|
|
|
|
record_input: bool,
|
2024-01-11 11:09:10 +01:00
|
|
|
metadata: Metadata,
|
2024-01-12 12:18:04 +01:00
|
|
|
keys: KeyBindings,
|
2023-10-28 10:44:12 +02:00
|
|
|
) -> Self {
|
2023-10-31 17:17:45 +01:00
|
|
|
let (sender, receiver) = mpsc::channel();
|
|
|
|
|
|
2023-10-27 22:25:28 +02:00
|
|
|
Recorder {
|
2023-10-31 17:17:45 +01:00
|
|
|
writer: Some(writer),
|
2023-10-28 11:29:04 +02:00
|
|
|
start_time: Instant::now(),
|
2024-01-12 12:18:04 +01:00
|
|
|
pause_time: None,
|
2023-10-27 22:25:28 +02:00
|
|
|
append,
|
|
|
|
|
record_input,
|
2024-01-11 11:09:10 +01:00
|
|
|
metadata,
|
2024-01-12 12:18:04 +01:00
|
|
|
keys,
|
2023-10-31 17:17:45 +01:00
|
|
|
sender,
|
|
|
|
|
receiver: Some(receiver),
|
|
|
|
|
handle: None,
|
2024-01-12 12:18:04 +01:00
|
|
|
prefix_mode: false,
|
2023-10-27 22:25:28 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-02 12:21:58 +01:00
|
|
|
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<()> {
|
2023-10-28 11:46:01 +02:00
|
|
|
let timestamp = SystemTime::now()
|
2023-10-28 11:29:04 +02:00
|
|
|
.duration_since(UNIX_EPOCH)
|
|
|
|
|
.unwrap()
|
|
|
|
|
.as_secs();
|
|
|
|
|
|
2023-10-31 17:17:45 +01:00
|
|
|
let mut writer = self.writer.take().unwrap();
|
|
|
|
|
let receiver = self.receiver.take().unwrap();
|
2023-10-27 22:25:28 +02:00
|
|
|
|
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)?;
|
2023-10-31 17:17:45 +01:00
|
|
|
|
|
|
|
|
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]) {
|
2024-01-12 12:18:04 +01:00
|
|
|
if self.pause_time.is_some() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-31 17:17:45 +01:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-12 12:18:04 +01:00
|
|
|
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
|
2023-10-31 17:17:45 +01:00
|
|
|
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
|
|
|
|
|
}
|
2024-01-12 12:18:04 +01:00
|
|
|
|
|
|
|
|
true
|
2023-10-26 12:02:53 +02:00
|
|
|
}
|
2023-10-30 13:40:53 +01:00
|
|
|
|
|
|
|
|
fn resize(&mut self, size: (u16, u16)) {
|
2023-10-31 17:17:45 +01:00
|
|
|
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
|
|
|
}
|
2024-01-12 12:18:04 +01: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,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|