1mod capture;
2mod feedback;
3mod ipc;
4mod protocols;
5#[cfg(test)]
6mod transport_tests;
7
8use std::{error::Error, io::Write, path::Path};
9type Result<T> = std::result::Result<T, Box<dyn Error>>;
10
11fn emit(value: serde_json::Value) -> Result<()> {
12 println!("{value}");
13 std::io::stdout().flush()?;
14 Ok(())
15}
16
17fn run() -> Result<()> {
18 ipc::session_directory()?;
19 let args: Vec<String> = std::env::args().collect();
20 match args.get(1).map(String::as_str) {
21 Some("check") => emit(serde_json::json!({"feedbackAvailable": capture::check()?})),
22 Some("capture") if args.len() == 3 => {
23 ipc::ensure_unlocked()?;
24 let window = ipc::active_window()?;
25 capture::capture(window.address()?, Path::new(&args[2]))?;
26 ipc::ensure_unlocked()?;
27 let after = ipc::windows()?.into_iter().find(|w| w.address == window.address);
28 // Never pair text with a different/closed window or stale geometry.
29 let metadata = after.filter(|w| w == &window).map(|w| w.metadata());
30 emit(serde_json::json!({"window": metadata}))
31 }
32 Some("activate") if args.len() == 4 => {
33 ipc::activate(args[2].parse()?, &args[3])?;
34 Ok(())
35 }
36 Some("feedback") if args.len() == 4 => {
37 feedback::run(Path::new(&args[2]), serde_json::from_str(&args[3])?)
38 }
39 _ => Err("Expected check, capture <directory>, activate <pid> <title>, or feedback <directory> <options>".into()),
40 }
41}
42
43fn main() {
44 if let Err(error) = run() {
45 eprintln!("{error}");
46 std::process::exit(1);
47 }
48}