use std::io::{BufRead, Read, Write};
use std::path::PathBuf;
use std::time::Duration;
fn main() {
let args: Vec<String> = std::env::args().skip(1).collect();
match args.first().map(String::as_str) {
Some("echo") => echo(),
Some("hid") => hid(PathBuf::from(&args[1])),
Some("lines") => {
let code: i32 = args[1].parse().unwrap();
let mut out = std::io::stdout().lock();
for l in &args[2..] {
writeln!(out, "{l}").unwrap();
}
out.flush().unwrap();
std::process::exit(code);
}
Some("fail") => {
if let Some(ms) = args.get(3) {
std::thread::sleep(Duration::from_millis(ms.parse().unwrap()));
}
eprintln!("{}", args[2]);
std::process::exit(args[1].parse().unwrap());
}
Some("frames") => frames(&args[1], &args[2], &args[3]),
Some("ax") => ax(args.get(1).map(String::as_str)),
Some("stubborn") => {
unsafe {
libc::signal(libc::SIGINT, libc::SIG_IGN);
}
println!("{{\"t\":\"note\",\"detail\":\"stubborn\"}}");
loop {
std::thread::sleep(Duration::from_secs(60));
}
}
other => {
eprintln!("fake_helper: unknown mode {other:?}");
std::process::exit(2);
}
}
}
fn echo() {
let mut inp = std::io::stdin().lock();
let mut out = std::io::stdout().lock();
let mut buf = [0u8; 65536];
loop {
match inp.read(&mut buf) {
Ok(0) | Err(_) => return,
Ok(n) => {
if out.write_all(&buf[..n]).and_then(|_| out.flush()).is_err() {
return;
}
}
}
}
}
fn exit_code(line: &str) -> Option<i32> {
line.strip_prefix(r#"{"t":"_exit","code":"#)?
.trim_end_matches('}')
.parse()
.ok()
}
fn frames(name: &str, slots: &str, cap: &str) {
std::thread::spawn(|| {
for line in std::io::stdin().lock().lines() {
let Ok(line) = line else { break };
if let Some(code) = exit_code(&line) {
eprintln!("fake_helper: exiting {code}");
std::process::exit(code);
}
}
std::process::exit(0);
});
let mut out = std::io::stdout().lock();
writeln!(
out,
r#"{{"t":"shm","name":"{name}","slots":{slots},"cap":{cap}}}"#
)
.unwrap();
loop {
for (slot, seq) in [(0, 2), (1, 4), (2, 6)] {
if writeln!(out, r#"{{"t":"frame","slot":{slot},"seq":{seq}}}"#)
.and_then(|_| out.flush())
.is_err()
{
std::process::exit(0);
}
std::thread::sleep(Duration::from_millis(16));
}
}
}
fn hid(dir: PathBuf) {
while !dir.join("go").exists() {
std::thread::sleep(Duration::from_millis(5));
}
let mut out = std::io::stdout().lock();
writeln!(out, r#"{{"t":"ready","w":393,"h":852}}"#).unwrap();
out.flush().unwrap();
let mut log = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(dir.join("log"))
.unwrap();
let mut displaced = false;
for line in std::io::stdin().lock().lines() {
let Ok(line) = line else { return };
if line.contains(r#""t":"_invalidate""#) {
displaced = true;
writeln!(out, r#"{{"t":"displaced"}}"#).unwrap();
out.flush().unwrap();
continue;
}
if let Some(code) = exit_code(&line) {
eprintln!("fake_helper: exiting {code}");
std::process::exit(code);
}
let start = line.contains(r#""p":"down""#)
|| [r#""t":"button""#, r#""t":"key""#, r#""t":"text""#]
.iter()
.any(|t| line.contains(t));
if displaced && start {
displaced = false;
writeln!(out, r#"{{"t":"reclaimed"}}"#).unwrap();
out.flush().unwrap();
}
writeln!(log, "{line}").unwrap();
log.flush().unwrap();
}
}
fn ax(mode: Option<&str>) {
let mut out = std::io::stdout().lock();
if mode == Some("unsupported") {
writeln!(out, r#"{{"t":"error","detail":"AXPTranslator lacks frontmostApplicationWithDisplayId:bridgeDelegateToken:"}}"#).unwrap();
out.flush().unwrap();
std::process::exit(4);
}
writeln!(out, r#"{{"t":"ready"}}"#).unwrap();
out.flush().unwrap();
for line in std::io::stdin().lock().lines() {
let Ok(line) = line else { return };
if mode == Some("hang") || !line.contains(r#""t":"snapshot""#) {
continue;
}
let id = line
.split(r#""id":"#)
.nth(1)
.and_then(|r| r.trim_end_matches('}').parse::<u64>().ok())
.unwrap_or(0);
writeln!(
out,
r#"{{"t":"ax","id":{id},"ms":1.0,"requests":4,"root":{{"role":"AXApplication","label":"Fake","enabled":true,"frame":[0,0,393,852],"children":[{{"role":"AXButton","label":"Continue","id":"go","enabled":true,"frame":[20,700,353,50]}}]}}}}"#
)
.unwrap();
out.flush().unwrap();
}
}