Bring the headless rig into the repository
A rendering claim about iris was verified by hand from another checkout, because the compositor script and the input replay lived in ai-app's submodule and not here. `scripts/run-headless.sh` starts a headless sway on its own socket, runs an example against it and screenshots the result. `rig-input`'s `replay-touch` drives a recorded gesture in through Wayland's virtual pointer, since a headless compositor has no input device to move. `iris::harness` gains the `.touch` parser, so a recording means the same thing replayed into a harness as into a window rather than being read twice by two parsers. The ai-app copy's `--phone` became `--mode`, since which phone is not iris's business; its `IRIS_SCALE` has nothing to hand a density to here, so it waits for one.
This commit is contained in:
1 parent
43ce8c7d02
commit
9d13f15bee
8 files changed
+526
-18
No files matched your search
+140
@@ -37,6 +37,81 @@ macro_rules! assert_corners {
|
||||
}
|
||||
pub use crate::assert_corners;
|
||||
|
||||
/// One replayed pointer sample, cut down to what a window delivers: where the
|
||||
/// pointer is, and whether the button changed.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum TouchAction {
|
||||
Down,
|
||||
Move,
|
||||
Up,
|
||||
}
|
||||
|
||||
impl TouchAction {
|
||||
fn parse(word: &str) -> Option<Self> {
|
||||
match word {
|
||||
"down" => Some(Self::Down),
|
||||
"move" => Some(Self::Move),
|
||||
"up" => Some(Self::Up),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct TouchSample {
|
||||
pub t_ms: u64,
|
||||
pub action: TouchAction,
|
||||
pub pos: Vec2,
|
||||
}
|
||||
|
||||
/// A recorded gesture, in the output's own pixels: `<ms> down|move|up <x> <y>`
|
||||
/// a line, `#` to end of line ignored.
|
||||
///
|
||||
/// One parser for both ways of replaying a recording -- into a harness, and
|
||||
/// into a real window -- so the two cannot read the same file differently.
|
||||
pub struct TouchScript {
|
||||
pub samples: Vec<TouchSample>,
|
||||
}
|
||||
|
||||
impl TouchScript {
|
||||
pub fn parse(text: &str) -> Result<Self, String> {
|
||||
let mut samples: Vec<TouchSample> = Vec::new();
|
||||
for (i, line) in text.lines().enumerate() {
|
||||
let line = line.split('#').next().unwrap_or("").trim();
|
||||
if line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let at = |what: &str| format!("touch script line {}: {what}: {line:?}", i + 1);
|
||||
let mut words = line.split_whitespace();
|
||||
let (Some(t), Some(action), Some(x), Some(y), None) = (
|
||||
words.next(),
|
||||
words.next(),
|
||||
words.next(),
|
||||
words.next(),
|
||||
words.next(),
|
||||
) else {
|
||||
return Err(at("expected `t_ms action x y`"));
|
||||
};
|
||||
let t_ms: u64 = t.parse().map_err(|_| at("t_ms is not a whole number"))?;
|
||||
let action =
|
||||
TouchAction::parse(action).ok_or_else(|| at("action is not down/move/up"))?;
|
||||
let x: f32 = x.parse().map_err(|_| at("x is not a number"))?;
|
||||
let y: f32 = y.parse().map_err(|_| at("y is not a number"))?;
|
||||
if let Some(last) = samples.last()
|
||||
&& t_ms < last.t_ms
|
||||
{
|
||||
return Err(at("samples must be in time order"));
|
||||
}
|
||||
samples.push(TouchSample {
|
||||
t_ms,
|
||||
action,
|
||||
pos: Vec2::new(x, y),
|
||||
});
|
||||
}
|
||||
Ok(Self { samples })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct HarnessState {
|
||||
pub root: Option<StrongWidget>,
|
||||
@@ -160,6 +235,23 @@ impl Harness {
|
||||
self.release(CursorButton::Left);
|
||||
}
|
||||
|
||||
/// Drives a recorded gesture through the harness.
|
||||
pub fn replay(&mut self, script: &TouchScript) {
|
||||
for sample in &script.samples {
|
||||
match sample.action {
|
||||
TouchAction::Down => {
|
||||
self.move_to(sample.pos);
|
||||
self.press(CursorButton::Left);
|
||||
}
|
||||
TouchAction::Move => self.move_to(sample.pos),
|
||||
TouchAction::Up => {
|
||||
self.move_to(sample.pos);
|
||||
self.release(CursorButton::Left);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn button(&mut self, button: CursorButton) -> &mut ActivationState {
|
||||
let buttons = &mut self.cursor.buttons;
|
||||
match button {
|
||||
@@ -178,3 +270,51 @@ impl Harness {
|
||||
self.cursor.end_frame();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn parse(text: &str) -> Result<Vec<(u64, TouchAction, f32, f32)>, String> {
|
||||
Ok(TouchScript::parse(text)?
|
||||
.samples
|
||||
.iter()
|
||||
.map(|s| (s.t_ms, s.action, s.pos.x, s.pos.y))
|
||||
.collect())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_recording_is_time_action_and_a_point() {
|
||||
assert_eq!(
|
||||
parse("0 down 10 20\n16 move 10.5 24\n32 up 10.5 24").unwrap(),
|
||||
[
|
||||
(0, TouchAction::Down, 10.0, 20.0),
|
||||
(16, TouchAction::Move, 10.5, 24.0),
|
||||
(32, TouchAction::Up, 10.5, 24.0),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blank_lines_and_comments_are_not_samples() {
|
||||
assert_eq!(
|
||||
parse("# a flick\n\n 0 down 1 2 # the finger lands\n\n").unwrap(),
|
||||
[(0, TouchAction::Down, 1.0, 2.0)]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_recording_that_goes_backwards_is_rejected() {
|
||||
// Replay waits out the gap between samples, so time running backwards
|
||||
// would silently become no wait at all.
|
||||
let err = parse("16 down 1 2\n0 up 1 2").unwrap_err();
|
||||
assert!(err.contains("time order"), "{err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_line_that_is_not_a_sample_says_which_line() {
|
||||
let err = parse("0 down 1 2\n16 wiggle 1 2").unwrap_err();
|
||||
assert!(err.contains("line 2"), "{err}");
|
||||
assert!(err.contains("down/move/up"), "{err}");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user