Skip to content

Commit 18884cd

Browse files
authored
Merge pull request #20 from sandman21vs/address-blocks
Group an address in fours, and say when a row was cut
2 parents cb32724 + 7c8137e commit 18884cd

7 files changed

Lines changed: 331 additions & 8 deletions

File tree

‎host/origo_sdl.c‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,13 @@ void seedtool_display_nav_text(
126126
present();
127127
}
128128

129+
void seedtool_display_nav_grouped(const seedtool_nav_t* nav, const char* title, const char* const* lines,
130+
const size_t* first_group, const size_t count)
131+
{
132+
seedtool_render_nav_grouped(nav, title, lines, first_group, count);
133+
present();
134+
}
135+
129136
void seedtool_display_nav_rows(const seedtool_nav_t* nav, const char* title, const char* const* rows, const size_t count)
130137
{
131138
seedtool_render_nav_rows(nav, title, rows, count);

‎host/origo_simulator.c‎

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1610,6 +1610,139 @@ static bool nav_left_slot_is_drawn(void)
16101610
return false;
16111611
}
16121612

1613+
/* Grouping an address is a drawing decision, so the one thing it must never
1614+
* do is change the address. Walking a value with seedtool_render_fit_grouped
1615+
* the way page_text_impl does has to reproduce it character for character -
1616+
* a group dropped, doubled or reordered at a line break would hand the reader
1617+
* an address that is not theirs, and they would have no way to tell.
1618+
*
1619+
* Also checks the cut lands on a group boundary. That is what keeps the
1620+
* alternating ink meaningful across a break: a line ending mid-group would
1621+
* put two same-coloured groups against each other with only the gap between
1622+
* them, and the gap is the signal the colour is there to back up. */
1623+
/* A truncated row has to say so, and has to pay for saying it out of the same
1624+
* width. Two things are checked, because either alone passes while the other
1625+
* is broken: that the dots stay inside the row's own column rather than
1626+
* running under the scrollbar, and that they are drawn at all.
1627+
*
1628+
* The second is the one worth explaining. A row drawn without the ellipsis
1629+
* would be exactly its own fit_row prefix, so rendering that prefix as a row
1630+
* in its own right and comparing the two framebuffers says whether anything
1631+
* extra reached the screen. Without it this test would pass on a draw_row
1632+
* that silently cut, which is the bug it exists for. */
1633+
static bool truncated_rows_show_an_ellipsis(void)
1634+
{
1635+
static uint16_t truncated[SEEDTOOL_DISPLAY_WIDTH * SEEDTOOL_DISPLAY_HEIGHT];
1636+
/* Longer than any row can hold, which is what every address row is: an
1637+
* index, two spaces and a full address. */
1638+
static const char* const row = " 0 bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4";
1639+
const seedtool_nav_t nav = { .selected = 0, .back = true };
1640+
const char* const items[] = { row };
1641+
seedtool_render_nav_list(&nav, "m/84'/0'/0'/0", items, 1, 0);
1642+
memcpy(truncated, seedtool_render_pixels(), sizeof(truncated));
1643+
1644+
int rightmost = -1;
1645+
for (int y = 21; y < 55; ++y) {
1646+
for (int x = 0; x < SEEDTOOL_DISPLAY_WIDTH; ++x) {
1647+
if (truncated[y * SEEDTOOL_DISPLAY_WIDTH + x] != 0x0000 && x > rightmost) {
1648+
rightmost = x;
1649+
}
1650+
}
1651+
}
1652+
if (rightmost < 0) {
1653+
return false; /* the row never reached the screen */
1654+
}
1655+
/* LIST_TEXT_X + LIST_TEXT_WIDTH in seedtool_render.c, kept in sync with
1656+
* the 6 + 222 below: the row's own column, which the ellipsis has to stay
1657+
* inside rather than run past into the scrollbar's strip. */
1658+
if (rightmost > 6 + 222) {
1659+
return false;
1660+
}
1661+
1662+
/* What a silent cut would have drawn. */
1663+
char prefix[64] = { 0 };
1664+
const size_t fit = seedtool_render_fit_row(row);
1665+
if (fit >= sizeof(prefix) || fit >= strlen(row)) {
1666+
return false; /* the row was meant to be too long for one */
1667+
}
1668+
memcpy(prefix, row, fit);
1669+
const char* const cut[] = { prefix };
1670+
seedtool_render_nav_list(&nav, "m/84'/0'/0'/0", cut, 1, 0);
1671+
return memcmp(truncated, seedtool_render_pixels(), sizeof(truncated)) != 0;
1672+
}
1673+
1674+
static bool grouped_paging_preserves_the_value(void)
1675+
{
1676+
static const char* const values[] = {
1677+
/* Both address shapes this firmware derives, plus lengths that land
1678+
* exactly on a group boundary and one past it. */
1679+
"bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4",
1680+
"bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr",
1681+
"1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2",
1682+
"3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy",
1683+
"abcd",
1684+
"abcde",
1685+
"abcdefgh",
1686+
};
1687+
for (size_t v = 0; v < sizeof(values) / sizeof(values[0]); ++v) {
1688+
const char* const value = values[v];
1689+
const size_t total = strlen(value);
1690+
char rebuilt[128] = { 0 };
1691+
size_t used = 0, offset = 0, lines = 0;
1692+
while (offset < total && lines < 32) {
1693+
const size_t fit = seedtool_render_fit_grouped(value + offset, 48);
1694+
if (!fit) {
1695+
return false; /* no progress: the walk would never end */
1696+
}
1697+
/* A whole number of groups unless this is the value's own tail. */
1698+
if (offset + fit < total && fit % SEEDTOOL_GROUP_LEN) {
1699+
return false;
1700+
}
1701+
if (used + fit >= sizeof(rebuilt)) {
1702+
return false;
1703+
}
1704+
memcpy(rebuilt + used, value + offset, fit);
1705+
used += fit;
1706+
offset += fit;
1707+
++lines;
1708+
}
1709+
rebuilt[used] = '\0';
1710+
if (offset != total || strcmp(rebuilt, value) != 0) {
1711+
return false;
1712+
}
1713+
}
1714+
return true;
1715+
}
1716+
1717+
/* The grouped line is centred with its gaps counted in, so it can run off an
1718+
* edge in a way the plain centred line cannot. Measured, not assumed. */
1719+
static bool grouped_lines_clear_the_edges(void)
1720+
{
1721+
static const char* const lines[] = { "bc1qw508d6qejxtdg4y5r3zarvary0", "bc1p5cyxnuxmeuwuvkwfem96lqzs" };
1722+
static const size_t first_group[] = { 0, 7, 14 };
1723+
for (size_t i = 0; i < sizeof(lines) / sizeof(lines[0]); ++i) {
1724+
/* The widest a line can be: fit_grouped's own answer for this value. */
1725+
char line[64] = { 0 };
1726+
const size_t fit = seedtool_render_fit_grouped(lines[i], 48);
1727+
memcpy(line, lines[i], fit);
1728+
const char* const shown[] = { line, line, line };
1729+
const seedtool_nav_t nav = { .selected = SEEDTOOL_NAV_BODY,
1730+
.confirm = "Continue",
1731+
.confirm_enabled = true,
1732+
.back = true,
1733+
.counter = "1/2" };
1734+
seedtool_render_nav_grouped(&nav, "m/84'/0'/0'/0/0", shown, first_group, 3);
1735+
const uint16_t* const pixels = seedtool_render_pixels();
1736+
for (int y = 30; y < 100; ++y) {
1737+
if (pixels[y * SEEDTOOL_DISPLAY_WIDTH] != 0x0000
1738+
|| pixels[y * SEEDTOOL_DISPLAY_WIDTH + SEEDTOOL_DISPLAY_WIDTH - 1] != 0x0000) {
1739+
return false;
1740+
}
1741+
}
1742+
}
1743+
return true;
1744+
}
1745+
16131746
static bool nav_chrome_bands_do_not_collide(void)
16141747
{
16151748
/* Twelve rows of the widest label review_and_confirm can build: two
@@ -2202,6 +2335,18 @@ static int self_test(void)
22022335
fputs("Origo backup confirmation screen self-test failed\n", stderr);
22032336
return 1;
22042337
}
2338+
if (!truncated_rows_show_an_ellipsis()) {
2339+
fputs("Origo truncated row self-test failed\n", stderr);
2340+
return 1;
2341+
}
2342+
if (!grouped_paging_preserves_the_value()) {
2343+
fputs("Origo grouped address paging self-test failed\n", stderr);
2344+
return 1;
2345+
}
2346+
if (!grouped_lines_clear_the_edges()) {
2347+
fputs("Origo grouped address geometry self-test failed\n", stderr);
2348+
return 1;
2349+
}
22052350
if (!nav_chrome_bands_do_not_collide()) {
22062351
fputs("Origo nav chrome geometry self-test failed\n", stderr);
22072352
return 1;

‎main/seedtool_app.c‎

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -731,13 +731,14 @@ static void hexstr(const uint8_t* bytes, const size_t len, char* output)
731731
* the tighter line pitch the renderer uses for a third line: a third fewer
732732
* pages to review the same value. Returns true when the reader advanced past
733733
* the last page or accepted, false when they backed out. */
734-
static bool page_text_impl(const char* title, const char* text, const bool confirmable)
734+
static bool page_text_impl(const char* title, const char* text, const bool confirmable, const bool grouped)
735735
{
736736
size_t start[MAX_PAGE_LINES], length[MAX_PAGE_LINES];
737737
const size_t total = strlen(text);
738738
size_t lines = 0, offset = 0;
739739
while (lines < MAX_PAGE_LINES && (offset < total || !lines)) {
740-
const size_t fit = seedtool_render_fit(text + offset, MAX_LINE_CHARS);
740+
const size_t fit = grouped ? seedtool_render_fit_grouped(text + offset, MAX_LINE_CHARS)
741+
: seedtool_render_fit(text + offset, MAX_LINE_CHARS);
741742
start[lines] = offset;
742743
length[lines] = fit;
743744
/* A glyph wider than the display would otherwise loop forever. */
@@ -773,7 +774,19 @@ static bool page_text_impl(const char* title, const char* text, const bool confi
773774
}
774775
(void)snprintf(footer, sizeof(footer), "%u/%u", (unsigned)(page + 1), (unsigned)pages);
775776
const seedtool_nav_t nav = page_nav(position, pages, footer, confirmable);
776-
seedtool_display_nav_text(&nav, title, line1, line2, line3);
777+
if (grouped) {
778+
/* Where each line sits in the whole value, in groups, so the
779+
* alternating ink carries across the line break rather than
780+
* restarting three times a page. Every line but a final short one
781+
* is a whole number of groups, which is what makes this exact. */
782+
const char* const shown[] = { line1, line2, line3 };
783+
const size_t first_group[] = { start[first] / SEEDTOOL_GROUP_LEN,
784+
(first + 1 < lines ? start[first + 1] : 0) / SEEDTOOL_GROUP_LEN,
785+
(first + 2 < lines ? start[first + 2] : 0) / SEEDTOOL_GROUP_LEN };
786+
seedtool_display_nav_grouped(&nav, title, shown, first_group, 3);
787+
} else {
788+
seedtool_display_nav_text(&nav, title, line1, line2, line3);
789+
}
777790
switch (wait_key()) {
778791
case KEY_SELECT:
779792
if (!position) {
@@ -814,15 +827,23 @@ static bool page_text_impl(const char* title, const char* text, const bool confi
814827
* caller acts on the answer, so the tick is there to give one. */
815828
static bool page_text(const char* title, const char* text)
816829
{
817-
return page_text_impl(title, text, true);
830+
return page_text_impl(title, text, true, false);
831+
}
832+
833+
/* page_text for a value with no word shapes to read by - an address. Drawn in
834+
* groups of four so the reader transcribing it has somewhere to keep their
835+
* place; the string itself is untouched. */
836+
static bool page_grouped(const char* title, const char* text)
837+
{
838+
return page_text_impl(title, text, true, true);
818839
}
819840

820841
/* Paged text there is nothing to take. Returns nothing because there is
821842
* nothing to return: the reader pages down and leaves by the arrow, and a
822843
* caller that cannot ask for an answer cannot forget to use it. */
823844
static void page_read(const char* title, const char* text)
824845
{
825-
(void)page_text_impl(title, text, false);
846+
(void)page_text_impl(title, text, false, false);
826847
}
827848

828849
/* The account key carries its key origin, so a scan does not have to be told
@@ -2214,7 +2235,7 @@ static void show_addresses(
22142235
* make. */
22152236
char path[sizeof(prefix) + 12];
22162237
(void)snprintf(path, sizeof(path), "%s/%u", prefix, (unsigned)index);
2217-
if (page_text(path, address)) {
2238+
if (page_grouped(path, address)) {
22182239
show_address_qr(path, address);
22192240
}
22202241
seedtool_zero(address, sizeof(address));

‎main/seedtool_display.c‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,13 @@ void seedtool_display_nav_text(
222222
flush();
223223
}
224224

225+
void seedtool_display_nav_grouped(const seedtool_nav_t* nav, const char* title, const char* const* lines,
226+
const size_t* first_group, const size_t count)
227+
{
228+
seedtool_render_nav_grouped(nav, title, lines, first_group, count);
229+
flush();
230+
}
231+
225232
void seedtool_display_nav_rows(const seedtool_nav_t* nav, const char* title, const char* const* rows, const size_t count)
226233
{
227234
seedtool_render_nav_rows(nav, title, rows, count);

‎main/seedtool_display.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ void seedtool_display_splash(void);
3333
* chrome itself is already carried by seedtool_nav_t. */
3434
void seedtool_display_nav_text(
3535
const seedtool_nav_t* nav, const char* title, const char* line1, const char* line2, const char* line3);
36+
void seedtool_display_nav_grouped(const seedtool_nav_t* nav, const char* title, const char* const* lines,
37+
const size_t* first_group, size_t count);
3638
void seedtool_display_nav_rows(const seedtool_nav_t* nav, const char* title, const char* const* rows, size_t count);
3739
void seedtool_display_nav_list(
3840
const seedtool_nav_t* nav, const char* title, const char* const* items, size_t count, size_t top);

0 commit comments

Comments
 (0)