use std::io;
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};
use chrono::{DateTime, Local};
use crate::model::{Action, Step, WindowInfo};
use crate::text::KeyToken;
pub struct Session {
pub dir: PathBuf,
pub started_at: DateTime<Local>,
pub steps: Vec<Step>,
pub recording: bool,
pub shift_down: bool,
pub lang: crate::i18n::Lang,
start_ms: u128,
pending_text: String,
screenshot_counter: usize,
}
impl Session {
pub fn new(dir: impl AsRef<Path>, lang: crate::i18n::Lang) -> io::Result<Self> {
let dir = dir.as_ref().to_path_buf();
std::fs::create_dir_all(&dir)?;
Ok(Self {
dir,
started_at: Local::now(),
steps: Vec::new(),
recording: true,
shift_down: false,
lang,
start_ms: now_ms(),
pending_text: String::new(),
screenshot_counter: 0,
})
}
pub fn apply_key(&mut self, token: KeyToken) {
match token {
KeyToken::Insert(s) => self.pending_text.push_str(&s),
KeyToken::Backspace => {
self.pending_text.pop();
}
}
}
pub fn flush_text(&mut self, window: Option<WindowInfo>) {
let content = std::mem::take(&mut self.pending_text);
if content.trim().is_empty() {
return;
}
let action = Action::Text { content };
self.push_step(action, window, None);
}
pub fn record_click(
&mut self,
button: crate::input::Button,
global: Option<(i32, i32)>,
window: Option<WindowInfo>,
screenshot: Option<String>,
) {
self.flush_text(window.clone());
let pos = global.map(|(x, y)| {
let (rx, ry) = match &window {
Some(w) => (x - w.x, y - w.y),
None => (x, y),
};
(x, y, rx, ry)
});
let action = match pos {
Some((x, y, rel_x, rel_y)) => Action::Click {
button,
x: Some(x),
y: Some(y),
rel_x: Some(rel_x),
rel_y: Some(rel_y),
},
None => Action::Click {
button,
x: None,
y: None,
rel_x: None,
rel_y: None,
},
};
self.push_step(action, window, screenshot);
}
pub fn next_screenshot_name(&mut self) -> String {
self.screenshot_counter += 1;
format!("step-{:04}.webp", self.screenshot_counter)
}
pub fn stop(&mut self) {
self.flush_text(None);
self.recording = false;
}
fn push_step(
&mut self,
action: Action,
window: Option<WindowInfo>,
screenshot: Option<String>,
) {
let now = now_ms();
let description = Step::build_description(&action, window.as_ref(), self.lang);
self.steps.push(Step {
index: self.steps.len() + 1,
timestamp_ms: now,
elapsed_ms: now.saturating_sub(self.start_ms),
action,
window,
screenshot,
description,
});
}
}
pub(crate) fn now_ms() -> u128 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis())
.unwrap_or(0)
}