use cosh_tools::computer::keyboard::parse_key;
use cosh_tools::computer::mouse::{MIN_DRAG_MS, validate};
use cosh_tools::computer::types::{ComputerControl, PointerAction, PointerAnchor};
fn main() {
let rename_file = ComputerControl {
app: Some("Files".into()),
selector: Some("text_field[name='Filename']".into()),
..Default::default()
};
let type_new_name = ComputerControl {
text: Some("quarterly-report".into()),
..Default::default()
};
let settle = ComputerControl {
wait: Some(600),
..Default::default()
};
let confirm = ComputerControl {
key: Some("enter".into()),
..Default::default()
};
let pipeline = rename_file
.clone()
.chain(type_new_name)
.chain(settle)
.chain(confirm);
println!("pipeline: {}", describe(&pipeline));
let drag = ComputerControl {
action: Some(PointerAction::Drag),
x: Some(100),
y: Some(100),
x2: Some(400),
y2: Some(300),
duration_ms: Some(MIN_DRAG_MS),
..Default::default()
};
match validate(&drag, "computer_control") {
Ok(()) => println!("drag step: valid"),
Err(e) => println!("drag step rejected: {e}"),
}
let half_drag = ComputerControl {
action: Some(PointerAction::Drag),
x: Some(100),
y: Some(100),
..Default::default()
};
if let Err(e) = validate(&half_drag, "computer_control") {
println!("half drag rejected: {e}");
}
if let Err(e) = parse_key("A", "computer_control") {
println!("uppercase key rejected: {e}");
}
println!("'a' parses to: {:?}", parse_key("a", "computer_control"));
let _canvas_click = ComputerControl {
action: Some(PointerAction::Click),
x: Some(512),
y: Some(384),
..Default::default()
};
let _ctrl_click_corner = ComputerControl {
app: Some("Files".into()),
selector: Some("list_item[name='report.csv']".into()),
anchor: Some(PointerAnchor::TopRight),
held: Some(vec!["ctrl".into()]),
..Default::default()
};
}
pub trait Chain {
fn chain(self, next: ComputerControl) -> Self;
}
impl Chain for ComputerControl {
fn chain(mut self, next: ComputerControl) -> Self {
let mut tail = &mut self;
while tail.then.is_some() {
tail = tail.then.as_deref_mut().unwrap();
}
tail.then = Some(Box::new(next));
self
}
}
fn describe(step: &ComputerControl) -> String {
let mut out = Vec::new();
let mut current = Some(step);
while let Some(s) = current {
if let Some(text) = &s.text {
out.push(format!("typed {text:?}"));
} else if let Some(key) = &s.key {
out.push(format!("pressed {key}"));
} else if let Some(ms) = s.wait {
out.push(format!("waited {ms} ms"));
} else if let Some(sel) = &s.selector {
out.push(format!("click `{sel}`"));
} else {
out.push(format!("click @{},{}", s.x.unwrap_or(0), s.y.unwrap_or(0)));
}
current = s.then.as_deref();
}
out.join(" → ")
}
#[cfg(test)]
mod doc_tests {
use super::*;
#[test]
fn the_pipeline_reads_as_documented() {
let pipeline = ComputerControl {
app: Some("Files".into()),
selector: Some("text_field[name='Filename']".into()),
..Default::default()
}
.chain(ComputerControl {
text: Some("quarterly-report".into()),
..Default::default()
})
.chain(ComputerControl {
wait: Some(600),
..Default::default()
})
.chain(ComputerControl {
key: Some("enter".into()),
..Default::default()
});
assert_eq!(
describe(&pipeline),
"click `text_field[name='Filename']` → \
typed \"quarterly-report\" → waited 600 ms → pressed enter"
);
}
}