use std::time::Duration;
use wdotool_core::recorder::RecEvent;
use wdotool_core::{Backend, KeyDirection, MouseButton, Result, WdoError};
use crate::run_key;
pub async fn run(backend: &dyn Backend, file: &str, speed: f64) -> Result<()> {
if speed <= 0.0 {
return Err(WdoError::InvalidArg(format!(
"--speed must be positive, got {speed}"
)));
}
let trace = read_trace(file)?;
let events: Vec<RecEvent> = serde_json::from_str(&trace).map_err(|e| {
WdoError::InvalidArg(format!("failed to parse trace as RecEvent JSON: {e}"))
})?;
for event in events {
dispatch_event(backend, &event, speed).await?;
}
Ok(())
}
fn read_trace(file: &str) -> Result<String> {
if file == "-" {
use std::io::Read;
let mut buf = String::new();
std::io::stdin()
.read_to_string(&mut buf)
.map_err(|e| WdoError::InvalidArg(format!("failed to read stdin: {e}")))?;
Ok(buf)
} else {
std::fs::read_to_string(file)
.map_err(|e| WdoError::InvalidArg(format!("failed to read {file}: {e}")))
}
}
async fn dispatch_event(backend: &dyn Backend, event: &RecEvent, speed: f64) -> Result<()> {
match event {
RecEvent::Gap { ms, .. } => {
let scaled = (*ms as f64 / speed).round() as u64;
if scaled > 0 {
tokio::time::sleep(Duration::from_millis(scaled)).await;
}
}
RecEvent::Key { chord, .. } => {
run_key(backend, chord, KeyDirection::PressRelease).await?;
}
RecEvent::Click { button, .. } => {
backend
.mouse_button(
MouseButton::from_index(*button as u32),
KeyDirection::PressRelease,
)
.await?;
}
RecEvent::MoveAbs { x, y, .. } => {
backend.mouse_move(*x, *y, true).await?;
}
RecEvent::MoveDelta { dx, dy, .. } => {
backend.mouse_move(*dx, *dy, false).await?;
}
RecEvent::Scroll { dx, dy, .. } => {
backend.scroll(*dx as f64, *dy as f64).await?;
}
}
Ok(())
}