bugfix: Mk4: fix extended keys not fully visible in stories
This commit is contained in:
parent
f40d16b76b
commit
51bbee9eb1
@ -16,6 +16,7 @@ This lists the new changes that have not yet been published in a normal release.
|
||||
in export loop and needs reboot to escape.
|
||||
- Bugfix: PUSHDATA2 in bitcoin script caused yikes.
|
||||
- Bugfix: Warning for unknown scripts was not shown at the top of the signing story.
|
||||
- Bugfix: Part of extended keys in stories were not visible
|
||||
|
||||
|
||||
# Q Specific Changes
|
||||
|
||||
@ -460,28 +460,38 @@ def call_later_ms(delay, cb, *args, **kws):
|
||||
|
||||
uasyncio.create_task(doit())
|
||||
|
||||
def txtlen(s):
|
||||
# width of string in chars, accounting for
|
||||
# double-wide characters which happen on Q.
|
||||
rv = len(s)
|
||||
|
||||
if DOUBLE_WIDE:
|
||||
rv += sum(1 for ch in s if ch in DOUBLE_WIDE)
|
||||
|
||||
return rv
|
||||
|
||||
def word_wrap(ln, w):
|
||||
# Generate the lines needed to wrap one line into X "width"-long lines.
|
||||
# - tests in testing/test_unit.py
|
||||
while True:
|
||||
# ln_len considers DOUBLE_WIDTH chars
|
||||
ln_len = 0
|
||||
idx = 0
|
||||
sp = None
|
||||
for idx, ch in enumerate(ln):
|
||||
if ch == ' ':
|
||||
# split point on space if possible
|
||||
sp = idx
|
||||
if ln_len < w:
|
||||
ln_len += 1
|
||||
if ch in DOUBLE_WIDE:
|
||||
ln_len += 1
|
||||
else:
|
||||
if (ln_len == w) and (ch in ".,:;"):
|
||||
# boundary of allowed width
|
||||
# if . or , allow one more character
|
||||
# even if only half visible on Mk4
|
||||
# on Q it's OK as (CHARS_W-1) is used as w
|
||||
sp = None
|
||||
idx += 1
|
||||
|
||||
if txtlen(ln) <= w:
|
||||
yield ln
|
||||
return
|
||||
break
|
||||
else:
|
||||
yield ln
|
||||
return
|
||||
|
||||
while ln:
|
||||
# find a space in (width) first part of remainder
|
||||
sp = ln.rfind(' ', 0, w-1)
|
||||
if sp == -1:
|
||||
if sp is None:
|
||||
if ln[0] == OUT_CTRL_ADDRESS:
|
||||
# special handling for lines w/ payment address in them
|
||||
# - add same marker to newly split lines
|
||||
@ -497,8 +507,7 @@ def word_wrap(ln, w):
|
||||
return
|
||||
|
||||
# bad-break the line
|
||||
sp = min(txtlen(ln), w)
|
||||
nsp = sp
|
||||
sp = nsp = idx
|
||||
if ln[nsp:nsp+1] == ' ':
|
||||
nsp += 1
|
||||
else:
|
||||
@ -506,14 +515,10 @@ def word_wrap(ln, w):
|
||||
nsp = sp+1
|
||||
|
||||
left = ln[0:sp]
|
||||
ln = ln[nsp:]
|
||||
|
||||
if txtlen(left) + 1 + txtlen(ln) <= w:
|
||||
# not clear when this would happen? final bit??
|
||||
left = left + ' ' + ln
|
||||
ln = ''
|
||||
|
||||
yield left
|
||||
ln = ln[nsp:]
|
||||
if not ln: return
|
||||
|
||||
|
||||
def parse_extended_key(ln, private=False):
|
||||
# read an xpub/ypub/etc and return BIP-32 node and what chain it's on.
|
||||
|
||||
@ -17,7 +17,8 @@ DEFAULT_IDLE_TIMEOUT = const(4*3600) # (seconds) 4 hours
|
||||
# See ux_mk or ux_q1 for some display functions now
|
||||
if version.has_qwerty:
|
||||
from lcd_display import CHARS_W, CHARS_H
|
||||
CH_PER_W = CHARS_W
|
||||
# stories look nicer if we do not use the whole width
|
||||
CH_PER_W = (CHARS_W - 1)
|
||||
STORY_H = CHARS_H
|
||||
from ux_q1 import PressRelease, ux_enter_number, ux_input_text, ux_show_pin
|
||||
from ux_q1 import ux_login_countdown, ux_dice_rolling, ux_render_words
|
||||
@ -28,9 +29,9 @@ if version.has_qwerty:
|
||||
else:
|
||||
# How many characters can we fit on each line? How many lines?
|
||||
# (using FontSmall) .. except it's an approximation since variable-width font.
|
||||
# - even 19 could work sometimes, but not when line is completely full
|
||||
# - 18 can work but rightmost spot is half-width. We allow . and , in that spot.
|
||||
# - really should look at rendered-width of text
|
||||
CH_PER_W = 19
|
||||
CH_PER_W = 17
|
||||
STORY_H = 5
|
||||
from ux_mk4 import PressRelease, ux_enter_number, ux_input_text, ux_show_pin
|
||||
from ux_mk4 import ux_login_countdown, ux_dice_rolling, ux_render_words
|
||||
|
||||
@ -5,6 +5,8 @@
|
||||
|
||||
import pytest, os, shutil
|
||||
from helpers import B2A
|
||||
from constants import AF_P2WSH, AF_P2SH, AF_P2WSH_P2SH, AF_CLASSIC, AF_P2WPKH, AF_P2WPKH_P2SH
|
||||
from charcodes import *
|
||||
|
||||
|
||||
def test_remote_exec(sim_exec):
|
||||
@ -274,28 +276,55 @@ def test_is_dir(microsd_path, sim_exec):
|
||||
assert rv == "False"
|
||||
shutil.rmtree(microsd_path("my_dir"))
|
||||
|
||||
@pytest.mark.parametrize('txt, x_line2', [
|
||||
('Disk, press \x0e to share via NFC, \x11 to share', '\x11 to share'),
|
||||
])
|
||||
def test_word_wrap(txt, x_line2, sim_exec, only_q1, width=34):
|
||||
# one tricky double-wide char word-wrapping case .. but add others
|
||||
assert '\n' not in txt
|
||||
DOUBLE_W = ['⋯', '✔', '✓', '→', '←', '↦', '◉', '◯', '◌', '※', '—', '\x0e', '\x11', '\t', '\x0f', '\x12', '\x13', '\x14', '\x16', '\x17']
|
||||
|
||||
@pytest.mark.parametrize('txt, target', [
|
||||
('Disk, press \x0e to share via NFC, \x11 to share', ['Disk, press \x0e to share via NFC,', '\x11 to share']),
|
||||
((KEY_NFC * 17)+".", [KEY_NFC * 17, '.']),
|
||||
((KEY_NFC * 17)+(17*KEY_QR), [KEY_NFC * 17, KEY_QR * 17]),
|
||||
((KEY_NFC * 17)+" "+(17*KEY_QR), [KEY_NFC * 17, KEY_QR * 17]),
|
||||
((KEY_NFC * 16)+".", [(KEY_NFC * 16)+'.']),
|
||||
(f"Use {KEY_NFC}, or {KEY_F1}, {KEY_F2}, {KEY_F3}, or or or {KEY_F4}", [f"Use {KEY_NFC}, or {KEY_F1}, {KEY_F2}, {KEY_F3}, or or or {KEY_F4}"]),
|
||||
("".join(DOUBLE_W), ["".join(DOUBLE_W[:17]), "".join(DOUBLE_W[17:])]),
|
||||
("".join(6*DOUBLE_W), ["".join(6*DOUBLE_W)[i:i + 17] for i in range(0, len(6*DOUBLE_W), 17)]),
|
||||
])
|
||||
def test_word_wrap_double_wide(only_q1, txt, target, sim_exec):
|
||||
width = 33 # check shared/ux.py CHAR_PER_W
|
||||
cmd = f'from utils import word_wrap; RV.write("\\n".join(word_wrap({txt!r}, {width})))'
|
||||
got = sim_exec(cmd)
|
||||
assert 'Traceback' not in got
|
||||
|
||||
lines = got.split('\n')
|
||||
|
||||
assert width*2//3 <= len(lines[0]) <= width
|
||||
assert lines[1] == x_line2
|
||||
assert lines == target
|
||||
|
||||
want_words = [i.strip() for i in txt.split()]
|
||||
got_words = [i.strip() for i in got.split()]
|
||||
@pytest.mark.parametrize('txt, target, width', [
|
||||
((17*'a')+". ccc", [(17*'a')+".", "ccc"], 17),
|
||||
((17*'a')+".", [(17*'a')+"."], 17),
|
||||
((17*'-')+". ccc", [(17*'-')+".", "ccc"], 17),
|
||||
((34 * 'A'), [33 * "A", "A"], 33),
|
||||
((33 * 'A')+". ccc", [(33 * "A")+".", "ccc"], 33),
|
||||
('Coldcard is ready to sign spending transactions!', ['Coldcard is ready to sign', 'spending transactions!'], 33),
|
||||
('Coldcard is ready to sign spending transactions!', ['Coldcard is ready', 'to sign spending', 'transactions!'], 17),
|
||||
((16*"B")+ " AAAA", [16*"B", "AAAA"], 17),
|
||||
((16*"B")+ " AAAA", [(16*"B")+" ", "AAAA"], 17),
|
||||
((17*"B")+ " AAAA", [17*"B", "AAAA"], 17),
|
||||
((17*"B")+ " AAAA", [17*"B", " AAAA"], 17),
|
||||
("(recommended), or by typing numbers.", ["(recommended), or", "by typing numbers."], 17),
|
||||
("difficult to recover your funds.", ["difficult to", "recover your", "funds."], 17),
|
||||
("USB Serial Number:", ["USB Serial Number:"], 17),
|
||||
("USB Serial Number;", ["USB Serial Number;"], 17),
|
||||
("USB Serial Number/", ["USB Serial", "Number/"], 17),
|
||||
])
|
||||
def test_word_wrap(txt, target, width, sim_exec):
|
||||
cmd = f'from utils import word_wrap; RV.write("\\n".join(word_wrap({txt!r}, {width})))'
|
||||
got = sim_exec(cmd)
|
||||
assert 'Traceback' not in got
|
||||
|
||||
assert want_words == got_words
|
||||
lines = got.split('\n')
|
||||
|
||||
assert lines == target
|
||||
|
||||
from constants import AF_P2WSH, AF_P2SH, AF_P2WSH_P2SH, AF_CLASSIC, AF_P2WPKH, AF_P2WPKH_P2SH
|
||||
|
||||
@pytest.mark.parametrize('addr,net,fmt', [
|
||||
( 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4', 'BTC', AF_P2WPKH ),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user