use std::path::PathBuf;
use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::{Arc, Mutex};
use std::thread::JoinHandle;
use crate::capture::Screen;
use crate::i18n::Lang;
use crate::input::{self, Button, InputEvent, InputHandle, Keycode};
use crate::progress::Progress;
use crate::session::Session;
use crate::text;
use crate::writer::{Job, Writer};
pub struct Recorder {
pub session: Arc<Mutex<Session>>,
backend_name: &'static str,
input: Option<InputHandle>,
consumer: Option<JoinHandle<()>>,
writer: Option<Writer>,
}
impl Recorder {
pub fn start(dir: PathBuf, progress: Progress, lang: Lang) -> std::io::Result<Self> {
let session = Arc::new(Mutex::new(Session::new(dir, lang)?));
let backend = input::select();
let backend_name = backend.name();
let screen = Screen::new();
let writer = Writer::new(progress.clone(), lang);
let jobs = writer.sender();
let (tx, rx) = channel::<InputEvent>();
let input = backend.start(tx)?;
let consumer = {
let session = session.clone();
std::thread::Builder::new()
.name("ssr-consumer".into())
.spawn(move || consume(rx, session, progress, screen, jobs, lang))
.map_err(std::io::Error::other)?
};
Ok(Self {
session,
backend_name,
input: Some(input),
consumer: Some(consumer),
writer: Some(writer),
})
}
pub fn backend_name(&self) -> &'static str {
self.backend_name
}
pub fn stop(mut self) -> Arc<Mutex<Session>> {
if let Ok(mut s) = self.session.lock() {
s.recording = false;
}
self.shutdown();
if let Ok(mut s) = self.session.lock() {
s.stop();
}
self.session.clone()
}
fn shutdown(&mut self) {
self.input = None;
if let Some(c) = self.consumer.take() {
let _ = c.join();
}
self.writer = None;
}
}
impl Drop for Recorder {
fn drop(&mut self) {
self.shutdown();
}
}
fn consume(
rx: Receiver<InputEvent>,
session: Arc<Mutex<Session>>,
progress: Progress,
screen: Screen,
jobs: Sender<Job>,
lang: Lang,
) {
while let Ok(event) = rx.recv() {
match event {
InputEvent::MouseUp { button, pos } => {
on_click(&session, button, pos, &progress, &screen, &jobs, lang)
}
InputEvent::KeyDown(key) => on_key_down(&session, key),
InputEvent::KeyUp(key) => on_key_up(&session, key),
}
}
}
#[allow(clippy::too_many_arguments)]
fn on_click(
session: &Arc<Mutex<Session>>,
button: Button,
pos: Option<(i32, i32)>,
progress: &Progress,
screen: &Screen,
jobs: &Sender<Job>,
lang: Lang,
) {
if !session.lock().map(|s| s.recording).unwrap_or(false) {
return;
}
progress.set(lang.msg_capturing());
let capture = screen.capture_for_click(pos);
progress.clear();
let Some((pixels, info)) = capture else {
return;
};
let mut s = match session.lock() {
Ok(s) => s,
Err(_) => return,
};
if !s.recording {
return;
}
let name = s.next_screenshot_name();
let path = s.dir.join(&name);
s.record_click(button, pos, info, Some(name));
drop(s);
let _ = jobs.send(Job {
pixels,
path,
quality: 55.0,
});
}
fn on_key_down(session: &Arc<Mutex<Session>>, key: Keycode) {
let mut s = match session.lock() {
Ok(s) => s,
Err(_) => return,
};
if !s.recording {
return;
}
if text::is_shift(&key) {
s.shift_down = true;
return;
}
if let Some(token) = text::key_to_token(&key, s.shift_down) {
s.apply_key(token);
}
}
fn on_key_up(session: &Arc<Mutex<Session>>, key: Keycode) {
if !text::is_shift(&key) {
return;
}
if let Ok(mut s) = session.lock() {
s.shift_down = false;
}
}