Skip to main content

max / alloy

Read the phrase off a rendered pane, borders and all
Author: Max Johnson <me@maxj.phd> · 2026-09-03 18:46 UTC
Signed with PGP, not checked
Commit: fdfccf32618678ac60ba55d51296273bae88cbde
Parent: 809bde7
1 file changed, +13 insertions, -2 deletions
@@ -57,7 +57,17 @@
57 57 # their own indented line, in the action color, between the prose above and the
58 58 # field below. Anchored on the shape rather than on a line number, because the
59 59 # prose around it is the part likely to be reworded.
60 - PHRASE = re.compile(r"^\s{2,}([a-z]+(?: [a-z]+){7})\s*$", re.M)
60 + #
61 + # The bounds are `[^A-Za-z]*` rather than `\s*` because this is read off a
62 + # rendered TUI and every line carries the pane's own border characters. A
63 + # whitespace-anchored pattern finds nothing here, which is a wrong answer that
64 + # looks like a missing phrase.
65 + PHRASE = re.compile(r"^[^A-Za-z]*([a-z]+(?: [a-z]+){7})[^A-Za-z]*$", re.M)
66 +
67 + # Where the phrase is, said in the prose above it. Searching after this rather
68 + # than over the whole screen keeps the pattern from matching an unlucky line of
69 + # lowercase prose somewhere else on the page.
70 + PHRASE_AFTER = "is not stored anywhere"
61 71
62 72
63 73 def finish_encrypted(s, timeout):
@@ -76,7 +86,8 @@
76 86 screen = s.screen.text()
77 87 check("Installation finished" in screen,
78 88 "the recovery screen says the install did not finish", s)
79 - hit = PHRASE.search(screen)
89 + tail = screen.split(PHRASE_AFTER, 1)[-1]
90 + hit = PHRASE.search(tail)
80 91 check(hit is not None, "no eight-word recovery phrase on the screen that asks for one", s)
81 92 s.send(hit.group(1))
82 93 s.send(ENTER)