qemu: scroll and home verbs in the driver
scroll X Y DY and home are judged by the channel's own ack, like nav and wake: the deterministic stand-in for a swipe that only brings a control into view (flare-edge #174), and the fresh-boot state a live panel needs before each flow (flare-edge #173). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N3G6m9Aw5RyVY4ZowtKzEj
This commit is contained in:
@@ -26,6 +26,12 @@ warden_debug.c. The same channel is what tools/warden-ctl reaches over SSH on
|
|||||||
a real panel, so these verbs mean the same thing on the rig and on hardware:
|
a real panel, so these verbs mean the same thing on the rig and on hardware:
|
||||||
|
|
||||||
nav MENU[/TAB] jump to a page; fails if the page is unknown
|
nav MENU[/TAB] jump to a page; fails if the page is unknown
|
||||||
|
scroll X Y DY scroll the scrollable under pixel X,Y by DY
|
||||||
|
pixels (positive reveals content below), no
|
||||||
|
animation: the deterministic stand-in for a
|
||||||
|
swipe that only brings a control into view
|
||||||
|
home close every popup and go to Dashboard/Dashboard,
|
||||||
|
the state a fresh boot starts in
|
||||||
wake wake the UI as if touched, minus the swallow
|
wake wake the UI as if touched, minus the swallow
|
||||||
(sleep.c dims and then blanks an idle panel and
|
(sleep.c dims and then blanks an idle panel and
|
||||||
eats the touch that ends either); every compiled
|
eats the touch that ends either); every compiled
|
||||||
@@ -538,6 +544,16 @@ def drive(s, f, script_path, outdir, size, ctl_path=None, console_path=None, ref
|
|||||||
need_ctl(lineno, cmd)
|
need_ctl(lineno, cmd)
|
||||||
reply = ctl.send("wake")
|
reply = ctl.send("wake")
|
||||||
record(lineno, line, "ok" if reply == "wake: ok" else "fail", reply)
|
record(lineno, line, "ok" if reply == "wake: ok" else "fail", reply)
|
||||||
|
elif cmd in ("scroll", "home"):
|
||||||
|
# scroll X Y DY: the scrollable under the pixel moves DY pixels
|
||||||
|
# with no animation (warden_debug.c), the deterministic stand-in
|
||||||
|
# for a swipe whose only job was to bring a control into view.
|
||||||
|
# home: close every popup and return to Dashboard/Dashboard, the
|
||||||
|
# state a fresh boot starts in, so a live panel can run one flow
|
||||||
|
# after another. Both are judged by the channel's own ack.
|
||||||
|
need_ctl(lineno, cmd)
|
||||||
|
reply = ctl.send(line)
|
||||||
|
record(lineno, line, "ok" if reply.startswith(f"{cmd}: ok") else "fail", reply)
|
||||||
elif cmd in ("page", "hit", "stats", "ctl"):
|
elif cmd in ("page", "hit", "stats", "ctl"):
|
||||||
# Query verbs: print the reply, never judge it. `ctl` is a raw
|
# Query verbs: print the reply, never judge it. `ctl` is a raw
|
||||||
# passthrough for anything the channel grows later.
|
# passthrough for anything the channel grows later.
|
||||||
|
|||||||
@@ -42,6 +42,10 @@ class FakeCtl:
|
|||||||
return "page: Demo/Rows\ncpu: 12%\nfps: 10\nrender: 3.20 ms/frame\nrga: 0%\nidle: 0"
|
return "page: Demo/Rows\ncpu: 12%\nfps: 10\nrender: 3.20 ms/frame\nrga: 0%\nidle: 0"
|
||||||
if cmd == "wake":
|
if cmd == "wake":
|
||||||
return "wake: ok"
|
return "wake: ok"
|
||||||
|
if cmd == "home":
|
||||||
|
return "home: ok"
|
||||||
|
if cmd.startswith("scroll "):
|
||||||
|
return "scroll: ok y=300 of 900" if not cmd.endswith(" 0") else "scroll: expected X Y DY (DY non-zero pixels)"
|
||||||
if cmd.startswith("@cat "):
|
if cmd.startswith("@cat "):
|
||||||
return json.dumps({"a": {"b": 1}, "list": [1, 2], "name": "warden"})
|
return json.dumps({"a": {"b": 1}, "list": [1, 2], "name": "warden"})
|
||||||
if cmd.startswith("hit "):
|
if cmd.startswith("hit "):
|
||||||
@@ -129,9 +133,11 @@ class DriveVerbs(unittest.TestCase):
|
|||||||
"capture_region r1 0 0 8 8 exact\n"
|
"capture_region r1 0 0 8 8 exact\n"
|
||||||
"assert_region r1 exact\n"
|
"assert_region r1 exact\n"
|
||||||
"assert_region r1\n"
|
"assert_region r1\n"
|
||||||
|
"scroll 360 400 300\n"
|
||||||
|
"home\n"
|
||||||
)
|
)
|
||||||
self.assertIsNone(rc, [r for r in rows if r["status"] != "ok"])
|
self.assertIsNone(rc, [r for r in rows if r["status"] != "ok"])
|
||||||
self.assertEqual(len(rows), 12)
|
self.assertEqual(len(rows), 14)
|
||||||
self.assertTrue(all(r["status"] == "ok" for r in rows))
|
self.assertTrue(all(r["status"] == "ok" for r in rows))
|
||||||
|
|
||||||
def test_mismatches_are_fails_not_stops(self):
|
def test_mismatches_are_fails_not_stops(self):
|
||||||
@@ -142,11 +148,12 @@ class DriveVerbs(unittest.TestCase):
|
|||||||
"wait_json a.b eq 2 1\n"
|
"wait_json a.b eq 2 1\n"
|
||||||
"assert_json a.zz eq 1\n"
|
"assert_json a.zz eq 1\n"
|
||||||
"assert_stat fps lt 0\n"
|
"assert_stat fps lt 0\n"
|
||||||
|
"scroll 360 400 0\n"
|
||||||
"assert_page Demo/Rows\n"
|
"assert_page Demo/Rows\n"
|
||||||
)
|
)
|
||||||
self.assertEqual(rc, 1)
|
self.assertEqual(rc, 1)
|
||||||
self.assertEqual([r["status"] for r in rows],
|
self.assertEqual([r["status"] for r in rows],
|
||||||
["fail", "fail", "fail", "fail", "fail", "ok"])
|
["fail", "fail", "fail", "fail", "fail", "fail", "ok"])
|
||||||
self.assertIn("moved", by["assert_hit 47 676 obj box=0,0,1x1"]["detail"])
|
self.assertIn("moved", by["assert_hit 47 676 obj box=0,0,1x1"]["detail"])
|
||||||
self.assertGreaterEqual(time.monotonic() - t0, 1.0, "wait_json must honour its timeout")
|
self.assertGreaterEqual(time.monotonic() - t0, 1.0, "wait_json must honour its timeout")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user