Screenshot generator fixes, refactor

* New `ScreenshotConfig` utility class.
* Proper reset & isolation when looping to generate screenshots for multiple locales.
* Bugfix for unpredictable race condition when rendering screenshots.
* Improved screenshots due to the above.
This commit is contained in:
kdmukai
2024-12-11 10:38:13 -06:00
parent 6ee36a40ef
commit e8f26b1e30
5 changed files with 300 additions and 244 deletions
+10
View File
@@ -147,6 +147,16 @@ class Controller(Singleton):
else:
# Instantiate the one and only Controller instance
return cls.configure_instance()
@classmethod
def reset_instance(cls):
"""
Currently used by the screenshot generator, but could potentially be used to
wipe and reset the state of the device.
"""
cls._instance = None
cls.configure_instance()
@classmethod
+9 -5
View File
@@ -189,12 +189,16 @@ class BaseToastOverlayManagerThread(BaseThread):
class RemoveSDCardToastManagerThread(BaseToastOverlayManagerThread):
def __init__(self, activation_delay=3):
# Note: activation_delay is configurable so the screenshot generator can get the
# toast to immediately render.
def __init__(self, activation_delay: int = 3, duration: int = 1e6):
"""
* activation_delay: configurable so the screenshot generator can get the
toast to immediately render.
* duration: default value is essentially forever. Overrideable for the
screenshot generator.
"""
super().__init__(
activation_delay=activation_delay, # seconds
duration=1e6, # seconds ("forever")
activation_delay=activation_delay,
duration=duration,
)
+4 -10
View File
@@ -77,11 +77,8 @@ class SeedSelectSeedView(View):
TYPE_ELECTRUM = ButtonOption("Enter Electrum seed", FontAwesomeIconConstants.KEYBOARD)
def __init__(self, flow: str = None):
from seedsigner.controller import Controller
def __init__(self, flow: str):
super().__init__()
if not flow:
flow = Controller.FLOW__VERIFY_SINGLESIG_ADDR
self.flow = flow
@@ -572,6 +569,7 @@ class SeedOptionsView(View):
button_data = []
if self.controller.unverified_address:
# TODO: Verify that an addr verification flow can actually reach this code
addr = self.controller.unverified_address["address"][:7]
self.VERIFY_ADDRESS.button_label += f" {addr}"
button_data.append(self.VERIFY_ADDRESS)
@@ -1701,7 +1699,7 @@ class AddressVerificationStartView(View):
if self.controller.unverified_address["script_type"] == SettingsConstants.LEGACY_P2PKH:
# Legacy P2PKH addresses are always singlesig
sig_type = SettingsConstants.SINGLE_SIG
destination = Destination(SeedSelectSeedView, skip_current_view=True)
destination = Destination(SeedSelectSeedView, view_args=dict(flow=Controller.FLOW__VERIFY_SINGLESIG_ADDR), skip_current_view=True)
if self.controller.unverified_address["script_type"] == SettingsConstants.NESTED_SEGWIT:
# No way to differentiate single sig from multisig
@@ -1721,7 +1719,7 @@ class AddressVerificationStartView(View):
else:
sig_type = SettingsConstants.SINGLE_SIG
destination = Destination(SeedSelectSeedView, skip_current_view=True)
destination = Destination(SeedSelectSeedView, view_args=dict(flow=Controller.FLOW__VERIFY_SINGLESIG_ADDR), skip_current_view=True)
elif self.controller.unverified_address["script_type"] == SettingsConstants.TAPROOT:
# TODO: add Taproot support
@@ -2158,10 +2156,6 @@ class SeedSignMessageConfirmMessageView(View):
super().__init__()
self.page_num = page_num # Note: zero-indexed numbering!
self.seed_num = self.controller.sign_message_data.get("seed_num")
if self.seed_num is None:
raise Exception("Routing error: seed_num hasn't been set")
def run(self):
from seedsigner.gui.screens.seed_screens import SeedSignMessageConfirmMessageScreen
+242 -228
View File
@@ -37,7 +37,7 @@ from seedsigner.views import (MainMenuView, PowerOptionsView, RestartView, NotYe
from seedsigner.views.screensaver import OpeningSplashView
from seedsigner.views.view import ErrorView, NetworkMismatchErrorView, OptionDisabledView, PowerOffView, View
from .utils import ScreenshotComplete, ScreenshotRenderer
from .utils import ScreenshotComplete, ScreenshotConfig, ScreenshotRenderer
import warnings; warnings.warn = lambda *args, **kwargs: None
@@ -58,6 +58,51 @@ def test_generate_all(locale, target_locale):
"""**************************************************************************************
Set up global test data that will be re-used across a variety of screenshots and for
all locales.
**************************************************************************************"""
BASE64_PSBT_1 = """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"""
mnemonic_12b = ["abandon"] * 11 + ["about"]
seed_12b = Seed(mnemonic=mnemonic_12b, wordlist_language_code=SettingsConstants.WORDLIST_LANGUAGE__ENGLISH)
def add_op_return_to_psbt(psbt: PSBT, raw_payload_data: bytes):
data = (compact.to_bytes(OPCODES.OP_RETURN) +
compact.to_bytes(OPCODES.OP_PUSHDATA1) +
compact.to_bytes(len(raw_payload_data)) +
raw_payload_data)
script = Script(data)
output = OutputScope()
output.script_pubkey = script
output.value = 0
psbt.outputs.append(output)
return psbt.to_string()
# Prep a PSBT with a human-readable OP_RETURN
raw_payload_data = "Chancellor on the brink of third bailout for banks".encode()
psbt = PSBT.from_base64(BASE64_PSBT_1)
# Simplify the output side
output = psbt.outputs[-1]
psbt.outputs.clear()
psbt.outputs.append(output)
assert len(psbt.outputs) == 1
BASE64_PSBT_WITH_OP_RETURN_TEXT = add_op_return_to_psbt(psbt, raw_payload_data)
# Prep a PSBT with a (repeatably) random 80-byte OP_RETURN
random.seed(6102)
BASE64_PSBT_WITH_OP_RETURN_RAW_BYTES = add_op_return_to_psbt(PSBT.from_base64(BASE64_PSBT_1), random.randbytes(80))
mnemonic_12 = "forum undo fragile fade shy sign arrest garment culture tube off merit".split()
mnemonic_24 = "attack pizza motion avocado network gather crop fresh patrol unusual wild holiday candy pony ranch winter theme error hybrid van cereal salon goddess expire".split()
seed_12 = Seed(mnemonic=mnemonic_12, passphrase="cap*BRACKET3stove", wordlist_language_code=SettingsConstants.WORDLIST_LANGUAGE__ENGLISH)
seed_24 = Seed(mnemonic=mnemonic_24, passphrase="some-PASS*phrase9", wordlist_language_code=SettingsConstants.WORDLIST_LANGUAGE__ENGLISH)
seed_24_w_passphrase = Seed(mnemonic=mnemonic_24, passphrase="some-PASS*phrase9", wordlist_language_code=SettingsConstants.WORDLIST_LANGUAGE__ENGLISH)
MULTISIG_WALLET_DESCRIPTOR = """wsh(sortedmulti(1,[22bde1a9/48h/1h/0h/2h]tpubDFfsBrmpj226ZYiRszYi2qK6iGvh2vkkghfGB2YiRUVY4rqqedHCFEgw12FwDkm7rUoVtq9wLTKc6BN2sxswvQeQgp7m8st4FP8WtP8go76/{0,1}/*,[73c5da0a/48h/1h/0h/2h]tpubDFH9dgzveyD8zTbPUFuLrGmCydNvxehyNdUXKJAQN8x4aZ4j6UZqGfnqFrD4NqyaTVGKbvEW54tsvPTK2UoSbCC1PJY8iCNiwTL3RWZEheQ/{0,1}/*))#3jhtf6yx"""
def generate_screenshots(locale):
"""
The `Renderer` class is mocked so that calls in the normal code are ignored
@@ -75,69 +120,21 @@ def generate_screenshots(locale):
Renderer.configure_instance = Mock()
Renderer.get_instance = Mock(return_value=screenshot_renderer)
controller = Controller.get_instance()
BASE64_PSBT_1 = """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"""
mnemonic_12b = ["abandon"] * 11 + ["about"]
seed_12b = Seed(mnemonic=mnemonic_12b, wordlist_language_code=SettingsConstants.WORDLIST_LANGUAGE__ENGLISH)
def add_op_return_to_psbt(psbt: PSBT, raw_payload_data: bytes):
data = (compact.to_bytes(OPCODES.OP_RETURN) +
compact.to_bytes(OPCODES.OP_PUSHDATA1) +
compact.to_bytes(len(raw_payload_data)) +
raw_payload_data)
script = Script(data)
output = OutputScope()
output.script_pubkey = script
output.value = 0
psbt.outputs.append(output)
return psbt.to_string()
# Prep a PSBT with a human-readable OP_RETURN
raw_payload_data = "Chancellor on the brink of third bailout for banks".encode()
psbt = PSBT.from_base64(BASE64_PSBT_1)
# Simplify the output side
output = psbt.outputs[-1]
psbt.outputs.clear()
psbt.outputs.append(output)
assert len(psbt.outputs) == 1
BASE64_PSBT_WITH_OP_RETURN_TEXT = add_op_return_to_psbt(psbt, raw_payload_data)
# Prep a PSBT with a (repeatably) random 80-byte OP_RETURN
random.seed(6102)
BASE64_PSBT_WITH_OP_RETURN_RAW_BYTES = add_op_return_to_psbt(PSBT.from_base64(BASE64_PSBT_1), random.randbytes(80))
def setup_screenshots(locale: str) -> dict:
# Set up some test data that we'll need in the `Controller` for certain Views
def setup_screenshots(locale: str) -> dict[str, list[ScreenshotConfig]]:
""" Set up some test data that we'll need in the `Controller` for certain Views """
# Must reset the Controller so each locale gets a fresh start
Controller.reset_instance()
controller = Controller.get_instance()
controller.settings.set_value(SettingsConstants.SETTING__SIG_TYPES, [attr for attr, name in SettingsConstants.ALL_SIG_TYPES])
controller.settings.set_value(SettingsConstants.SETTING__SCRIPT_TYPES, [attr for attr, name in SettingsConstants.ALL_SCRIPT_TYPES])
mnemonic_12 = "forum undo fragile fade shy sign arrest garment culture tube off merit".split()
mnemonic_24 = "attack pizza motion avocado network gather crop fresh patrol unusual wild holiday candy pony ranch winter theme error hybrid van cereal salon goddess expire".split()
seed_12 = Seed(mnemonic=mnemonic_12, passphrase="cap*BRACKET3stove", wordlist_language_code=SettingsConstants.WORDLIST_LANGUAGE__ENGLISH)
seed_24 = Seed(mnemonic=mnemonic_24, passphrase="some-PASS*phrase9", wordlist_language_code=SettingsConstants.WORDLIST_LANGUAGE__ENGLISH)
seed_24_w_passphrase = Seed(mnemonic=mnemonic_24, passphrase="some-PASS*phrase9", wordlist_language_code=SettingsConstants.WORDLIST_LANGUAGE__ENGLISH)
controller.storage.seeds.append(seed_12)
controller.storage.seeds.append(seed_12b)
controller.storage.seeds.append(seed_24)
controller.storage.set_pending_seed(seed_24_w_passphrase)
# Seed: Verify Address data
controller.unverified_address = dict(
# These are all totally fake data
address="bc1q6p00wazu4nnqac29fvky6vhjnnhku5u2g9njss62rvy7e0yuperq86f5ek",
network=SettingsConstants.MAINNET,
sig_type=SettingsConstants.SINGLE_SIG,
script_type=SettingsConstants.NATIVE_SEGWIT,
derivation_path = "m/84h/0h/0h",
verified_index=5,
verified_index_is_change=False
)
# Pending mnemonic for ToolsCalcFinalWordShowFinalWordView
controller.storage.init_pending_mnemonic(num_words=12)
for i, word in enumerate(mnemonic_12[:11]):
@@ -150,10 +147,6 @@ def generate_screenshots(locale):
controller.psbt = decoder.get_psbt()
controller.psbt_seed = seed_12b
# Multisig wallet descriptor for the multisig in the above PSBT
MULTISIG_WALLET_DESCRIPTOR = """wsh(sortedmulti(1,[22bde1a9/48h/1h/0h/2h]tpubDFfsBrmpj226ZYiRszYi2qK6iGvh2vkkghfGB2YiRUVY4rqqedHCFEgw12FwDkm7rUoVtq9wLTKc6BN2sxswvQeQgp7m8st4FP8WtP8go76/{0,1}/*,[73c5da0a/48h/1h/0h/2h]tpubDFH9dgzveyD8zTbPUFuLrGmCydNvxehyNdUXKJAQN8x4aZ4j6UZqGfnqFrD4NqyaTVGKbvEW54tsvPTK2UoSbCC1PJY8iCNiwTL3RWZEheQ/{0,1}/*))#3jhtf6yx"""
controller.multisig_wallet_descriptor = embit.descriptor.Descriptor.from_string(MULTISIG_WALLET_DESCRIPTOR)
# Message signing data
derivation_path = "m/84h/0h/0h/0/0"
controller.sign_message_data = {
@@ -165,17 +158,18 @@ def generate_screenshots(locale):
# Automatically populate all Settings options Views
settings_views_list = []
settings_views_list.append(settings_views.SettingsMenuView)
settings_views_list.append((
settings_views.SettingsMenuView,
dict(
visibility=SettingsConstants.VISIBILITY__ADVANCED,
selected_attr=SettingsConstants.SETTING__ELECTRUM_SEEDS,
initial_scroll=240, # Just guessing how many pixels to scroll down
),
"SettingsMenuView__Advanced"
))
settings_views_list.append(ScreenshotConfig(settings_views.SettingsMenuView))
settings_views_list.append(
ScreenshotConfig(
settings_views.SettingsMenuView,
dict(
visibility=SettingsConstants.VISIBILITY__ADVANCED,
selected_attr=SettingsConstants.SETTING__ELECTRUM_SEEDS,
initial_scroll=240, # Just guessing how many pixels to scroll down
),
screenshot_name="SettingsMenuView__Advanced"
)
)
# so we get a choice for transcribe seed qr format
controller.settings.set_value(
@@ -186,135 +180,183 @@ def generate_screenshots(locale):
if settings_entry.visibility == SettingsConstants.VISIBILITY__HIDDEN:
continue
settings_views_list.append((settings_views.SettingsEntryUpdateSelectionView, dict(attr_name=settings_entry.attr_name), f"SettingsEntryUpdateSelectionView_{settings_entry.attr_name}"))
settings_views_list.append(ScreenshotConfig(settings_views.SettingsEntryUpdateSelectionView, dict(attr_name=settings_entry.attr_name), screenshot_name=f"SettingsEntryUpdateSelectionView_{settings_entry.attr_name}"))
settingsqr_data_persistent = f"settings::v1 name=English_noob_mode persistent=E coords=spa,spd denom=thr network=M qr_density=M xpub_export=E sigs=ss scripts=nat xpub_details=E passphrase=E camera=0 compact_seedqr=E bip85=D priv_warn=E dire_warn=E partners=E locale={locale}"
settingsqr_data_not_persistent = f"settings::v1 name=Mode_Ephemeral persistent=D coords=spa,spd denom=thr network=M qr_density=M xpub_export=E sigs=ss scripts=nat xpub_details=E passphrase=E camera=0 compact_seedqr=E bip85=D priv_warn=E dire_warn=E partners=E locale={locale}"
# Set up screenshot-specific callbacks to inject data before the View is run and
# reset data after the View is run.
def load_basic_psbt_cb():
decoder = DecodeQR()
decoder.add_data(BASE64_PSBT_1)
controller.psbt = decoder.get_psbt()
controller.psbt_seed = seed_12b
controller.multisig_wallet_descriptor = None
def load_multisig_wallet_descriptor_cb():
controller.multisig_wallet_descriptor = embit.descriptor.Descriptor.from_string(MULTISIG_WALLET_DESCRIPTOR)
def load_address_verification_data_cb():
controller.unverified_address = dict(
# These are all totally fake data
address="bc1q6p00wazu4nnqac29fvky6vhjnnhku5u2g9njss62rvy7e0yuperq86f5ek",
network=SettingsConstants.MAINNET,
sig_type=SettingsConstants.SINGLE_SIG,
script_type=SettingsConstants.NATIVE_SEGWIT,
derivation_path = "m/84h/0h/0h",
verified_index=5,
verified_index_is_change=False
)
def PSBTSelectSeedView_cb_before():
# Have to ensure this is cleared out in order to get the seed selection screen
controller.psbt_seed = None
def PSBTOverviewView_op_return_cb_before():
controller.psbt_seed = seed_12b
decoder = DecodeQR()
decoder.add_data(BASE64_PSBT_WITH_OP_RETURN_TEXT)
controller.psbt = decoder.get_psbt()
controller.psbt_parser = PSBTParser(p=controller.psbt, seed=seed_12b)
def PSBTOpReturnView_raw_hex_data_cb_before():
decoder.add_data(BASE64_PSBT_WITH_OP_RETURN_RAW_BYTES)
controller.psbt = decoder.get_psbt()
controller.psbt_parser = PSBTParser(p=controller.psbt, seed=seed_12b)
screenshot_sections = {
"Main Menu Views": [
(OpeningSplashView, dict(is_screenshot_renderer=True, force_partner_logos=True)),
(OpeningSplashView, dict(is_screenshot_renderer=True, force_partner_logos=False), "OpeningSplashView_no_partner_logos"),
MainMenuView,
(MainMenuView, {}, 'MainMenuView_SDCardStateChangeToast_removed', SDCardStateChangeToastManagerThread(action=MicroSD.ACTION__REMOVED)),
(MainMenuView, {}, 'MainMenuView_SDCardStateChangeToast_inserted', SDCardStateChangeToastManagerThread(action=MicroSD.ACTION__INSERTED)),
(MainMenuView, {}, 'MainMenuView_RemoveSDCardToast', RemoveSDCardToastManagerThread(activation_delay=0)),
PowerOptionsView,
RestartView,
PowerOffView,
ScreenshotConfig(OpeningSplashView, dict(is_screenshot_renderer=True, force_partner_logos=True)),
ScreenshotConfig(OpeningSplashView, dict(is_screenshot_renderer=True, force_partner_logos=False), screenshot_name="OpeningSplashView_no_partner_logos"),
ScreenshotConfig(MainMenuView),
ScreenshotConfig(MainMenuView, screenshot_name='MainMenuView_SDCardStateChangeToast_removed', toast_thread=SDCardStateChangeToastManagerThread(action=MicroSD.ACTION__REMOVED, activation_delay=0, duration=0)),
ScreenshotConfig(MainMenuView, screenshot_name='MainMenuView_SDCardStateChangeToast_inserted', toast_thread=SDCardStateChangeToastManagerThread(action=MicroSD.ACTION__INSERTED, activation_delay=0, duration=0)),
ScreenshotConfig(MainMenuView, screenshot_name='MainMenuView_RemoveSDCardToast', toast_thread=RemoveSDCardToastManagerThread(activation_delay=0, duration=0)),
ScreenshotConfig(PowerOptionsView),
ScreenshotConfig(RestartView),
ScreenshotConfig(PowerOffView),
],
"Seed Views": [
seed_views.SeedsMenuView,
seed_views.LoadSeedView,
seed_views.SeedMnemonicEntryView,
seed_views.SeedMnemonicInvalidView,
seed_views.SeedFinalizeView,
(seed_views.SeedAddPassphraseView, {}, "SeedAddPassphraseView_lowercase"),
(seed_views.SeedAddPassphraseView, dict(initial_keyboard=SeedAddPassphraseScreen.KEYBOARD__UPPERCASE_BUTTON_TEXT), "SeedAddPassphraseView_uppercase"),
(seed_views.SeedAddPassphraseView, dict(initial_keyboard=SeedAddPassphraseScreen.KEYBOARD__DIGITS_BUTTON_TEXT), "SeedAddPassphraseView_digits"),
(seed_views.SeedAddPassphraseView, dict(initial_keyboard=SeedAddPassphraseScreen.KEYBOARD__SYMBOLS_1_BUTTON_TEXT), "SeedAddPassphraseView_symbols_1"),
(seed_views.SeedAddPassphraseView, dict(initial_keyboard=SeedAddPassphraseScreen.KEYBOARD__SYMBOLS_2_BUTTON_TEXT), "SeedAddPassphraseView_symbols_2"),
seed_views.SeedAddPassphraseExitDialogView,
seed_views.SeedReviewPassphraseView,
ScreenshotConfig(seed_views.SeedsMenuView),
ScreenshotConfig(seed_views.LoadSeedView),
ScreenshotConfig(seed_views.SeedMnemonicEntryView),
ScreenshotConfig(seed_views.SeedMnemonicInvalidView),
ScreenshotConfig(seed_views.SeedFinalizeView),
ScreenshotConfig(seed_views.SeedAddPassphraseView, screenshot_name="SeedAddPassphraseView_lowercase"),
ScreenshotConfig(seed_views.SeedAddPassphraseView, dict(initial_keyboard=SeedAddPassphraseScreen.KEYBOARD__UPPERCASE_BUTTON_TEXT), screenshot_name="SeedAddPassphraseView_uppercase"),
ScreenshotConfig(seed_views.SeedAddPassphraseView, dict(initial_keyboard=SeedAddPassphraseScreen.KEYBOARD__DIGITS_BUTTON_TEXT), screenshot_name="SeedAddPassphraseView_digits"),
ScreenshotConfig(seed_views.SeedAddPassphraseView, dict(initial_keyboard=SeedAddPassphraseScreen.KEYBOARD__SYMBOLS_1_BUTTON_TEXT), screenshot_name="SeedAddPassphraseView_symbols_1"),
ScreenshotConfig(seed_views.SeedAddPassphraseView, dict(initial_keyboard=SeedAddPassphraseScreen.KEYBOARD__SYMBOLS_2_BUTTON_TEXT), screenshot_name="SeedAddPassphraseView_symbols_2"),
ScreenshotConfig(seed_views.SeedAddPassphraseExitDialogView),
ScreenshotConfig(seed_views.SeedReviewPassphraseView),
(seed_views.SeedOptionsView, dict(seed_num=0)),
(seed_views.SeedBackupView, dict(seed_num=0)),
(seed_views.SeedExportXpubSigTypeView, dict(seed_num=0)),
(seed_views.SeedExportXpubScriptTypeView, dict(seed_num=0, sig_type="msig")),
(seed_views.SeedExportXpubCustomDerivationView, dict(seed_num=0, sig_type="ss", script_type="")),
(seed_views.SeedExportXpubCoordinatorView, dict(seed_num=0, sig_type="ss", script_type="nat")),
(seed_views.SeedExportXpubWarningView, dict(seed_num=0, sig_type="msig", script_type="nes", coordinator="spd", custom_derivation="")),
(seed_views.SeedExportXpubDetailsView, dict(seed_num=0, sig_type="ss", script_type="nat", coordinator="bw", custom_derivation="")),
#SeedExportXpubQRDisplayView,
(seed_views.SeedWordsWarningView, dict(seed_num=0)),
(seed_views.SeedWordsView, dict(seed_num=0)),
(seed_views.SeedWordsView, dict(seed_num=0, page_index=2), "SeedWordsView_2"),
(seed_views.SeedBIP85ApplicationModeView, dict(seed_num=0)),
(seed_views.SeedBIP85SelectChildIndexView, dict(seed_num=0, num_words=24)),
(seed_views.SeedBIP85InvalidChildIndexView, dict(seed_num=0, num_words=12)),
(seed_views.SeedWordsBackupTestPromptView, dict(seed_num=0)),
(seed_views.SeedWordsBackupTestView, dict(seed_num=0, rand_seed=6102)),
(seed_views.SeedWordsBackupTestMistakeView, dict(seed_num=0, cur_index=7, wrong_word="satoshi")),
(seed_views.SeedWordsBackupTestSuccessView, dict(seed_num=0)),
(seed_views.SeedTranscribeSeedQRFormatView, dict(seed_num=0)),
(seed_views.SeedTranscribeSeedQRWarningView, dict(seed_num=0)),
(seed_views.SeedTranscribeSeedQRWholeQRView, dict(seed_num=0, seedqr_format=QRType.SEED__COMPACTSEEDQR, num_modules=21), "SeedTranscribeSeedQRWholeQRView_12_Compact"),
(seed_views.SeedTranscribeSeedQRWholeQRView, dict(seed_num=0, seedqr_format=QRType.SEED__SEEDQR, num_modules=25), "SeedTranscribeSeedQRWholeQRView_12_Standard"),
(seed_views.SeedTranscribeSeedQRWholeQRView, dict(seed_num=2, seedqr_format=QRType.SEED__COMPACTSEEDQR, num_modules=25), "SeedTranscribeSeedQRWholeQRView_24_Compact"),
(seed_views.SeedTranscribeSeedQRWholeQRView, dict(seed_num=2, seedqr_format=QRType.SEED__SEEDQR, num_modules=29), "SeedTranscribeSeedQRWholeQRView_24_Standard"),
(seed_views.SeedTranscribeSeedQRZoomedInView, dict(seed_num=0, seedqr_format=QRType.SEED__COMPACTSEEDQR, initial_block_x=1, initial_block_y=1), "SeedTranscribeSeedQRZoomedInView_12_Compact"),
(seed_views.SeedTranscribeSeedQRZoomedInView, dict(seed_num=0, seedqr_format=QRType.SEED__SEEDQR, initial_block_x=2, initial_block_y=2), "SeedTranscribeSeedQRZoomedInView_12_Standard"),
ScreenshotConfig(seed_views.SeedOptionsView, dict(seed_num=0)),
ScreenshotConfig(seed_views.SeedBackupView, dict(seed_num=0)),
ScreenshotConfig(seed_views.SeedExportXpubSigTypeView, dict(seed_num=0)),
ScreenshotConfig(seed_views.SeedExportXpubScriptTypeView, dict(seed_num=0, sig_type="msig")),
ScreenshotConfig(seed_views.SeedExportXpubCustomDerivationView, dict(seed_num=0, sig_type="ss", script_type="")),
ScreenshotConfig(seed_views.SeedExportXpubCoordinatorView, dict(seed_num=0, sig_type="ss", script_type="nat")),
ScreenshotConfig(seed_views.SeedExportXpubWarningView, dict(seed_num=0, sig_type="msig", script_type="nes", coordinator="spd", custom_derivation="")),
ScreenshotConfig(seed_views.SeedExportXpubDetailsView, dict(seed_num=0, sig_type="ss", script_type="nat", coordinator="bw", custom_derivation="")),
#ScreenshotConfig(SeedExportXpubQRDisplayView),
ScreenshotConfig(seed_views.SeedWordsWarningView, dict(seed_num=0)),
ScreenshotConfig(seed_views.SeedWordsView, dict(seed_num=0)),
ScreenshotConfig(seed_views.SeedWordsView, dict(seed_num=0, page_index=2), screenshot_name="SeedWordsView_2"),
ScreenshotConfig(seed_views.SeedBIP85ApplicationModeView, dict(seed_num=0)),
ScreenshotConfig(seed_views.SeedBIP85SelectChildIndexView, dict(seed_num=0, num_words=24)),
ScreenshotConfig(seed_views.SeedBIP85InvalidChildIndexView, dict(seed_num=0, num_words=12)),
ScreenshotConfig(seed_views.SeedWordsBackupTestPromptView, dict(seed_num=0)),
ScreenshotConfig(seed_views.SeedWordsBackupTestView, dict(seed_num=0, rand_seed=6102)),
ScreenshotConfig(seed_views.SeedWordsBackupTestMistakeView, dict(seed_num=0, cur_index=7, wrong_word="satoshi")),
ScreenshotConfig(seed_views.SeedWordsBackupTestSuccessView, dict(seed_num=0)),
ScreenshotConfig(seed_views.SeedTranscribeSeedQRFormatView, dict(seed_num=0)),
ScreenshotConfig(seed_views.SeedTranscribeSeedQRWarningView, dict(seed_num=0)),
ScreenshotConfig(seed_views.SeedTranscribeSeedQRWholeQRView, dict(seed_num=0, seedqr_format=QRType.SEED__COMPACTSEEDQR, num_modules=21), screenshot_name="SeedTranscribeSeedQRWholeQRView_12_Compact"),
ScreenshotConfig(seed_views.SeedTranscribeSeedQRWholeQRView, dict(seed_num=0, seedqr_format=QRType.SEED__SEEDQR, num_modules=25), screenshot_name="SeedTranscribeSeedQRWholeQRView_12_Standard"),
ScreenshotConfig(seed_views.SeedTranscribeSeedQRWholeQRView, dict(seed_num=2, seedqr_format=QRType.SEED__COMPACTSEEDQR, num_modules=25), screenshot_name="SeedTranscribeSeedQRWholeQRView_24_Compact"),
ScreenshotConfig(seed_views.SeedTranscribeSeedQRWholeQRView, dict(seed_num=2, seedqr_format=QRType.SEED__SEEDQR, num_modules=29), screenshot_name="SeedTranscribeSeedQRWholeQRView_24_Standard"),
ScreenshotConfig(seed_views.SeedTranscribeSeedQRZoomedInView, dict(seed_num=0, seedqr_format=QRType.SEED__COMPACTSEEDQR, initial_block_x=1, initial_block_y=1), screenshot_name="SeedTranscribeSeedQRZoomedInView_12_Compact"),
ScreenshotConfig(seed_views.SeedTranscribeSeedQRZoomedInView, dict(seed_num=0, seedqr_format=QRType.SEED__SEEDQR, initial_block_x=2, initial_block_y=2), screenshot_name="SeedTranscribeSeedQRZoomedInView_12_Standard"),
(seed_views.SeedTranscribeSeedQRConfirmQRPromptView, dict(seed_num=0)),
ScreenshotConfig(seed_views.SeedTranscribeSeedQRConfirmQRPromptView, dict(seed_num=0)),
# Screenshot can't render live preview screens
# (seed_views.SeedTranscribeSeedQRConfirmScanView, dict(seed_num=0)),
# ScreenshotConfig(seed_views.SeedTranscribeSeedQRConfirmScanView, dict(seed_num=0)),
seed_views.SeedSelectSeedView,
seed_views.AddressVerificationSigTypeView,
(seed_views.SeedAddressVerificationView, dict(seed_num=0)),
(seed_views.SeedAddressVerificationSuccessView, dict(seed_num=0)),
ScreenshotConfig(seed_views.SeedSelectSeedView, dict(flow=Controller.FLOW__VERIFY_SINGLESIG_ADDR), screenshot_name="SeedSelectSeedView_address_verification"),
ScreenshotConfig(seed_views.AddressVerificationSigTypeView),
ScreenshotConfig(seed_views.SeedAddressVerificationView, dict(seed_num=0), run_before=load_address_verification_data_cb),
ScreenshotConfig(seed_views.SeedAddressVerificationSuccessView, dict(seed_num=0)), # Relies on callback above
seed_views.LoadMultisigWalletDescriptorView,
seed_views.MultisigWalletDescriptorView,
(seed_views.SeedDiscardView, dict(seed_num=0)),
ScreenshotConfig(seed_views.LoadMultisigWalletDescriptorView),
ScreenshotConfig(seed_views.MultisigWalletDescriptorView, run_before=load_multisig_wallet_descriptor_cb),
ScreenshotConfig(seed_views.SeedDiscardView, dict(seed_num=0)),
seed_views.SeedSignMessageConfirmMessageView,
seed_views.SeedSignMessageConfirmAddressView,
ScreenshotConfig(seed_views.SeedSelectSeedView, dict(flow=Controller.FLOW__SIGN_MESSAGE), screenshot_name="SeedSelectSeedView_sign_message"),
ScreenshotConfig(seed_views.SeedSignMessageConfirmMessageView),
ScreenshotConfig(seed_views.SeedSignMessageConfirmAddressView),
seed_views.SeedElectrumMnemonicStartView,
ScreenshotConfig(seed_views.SeedElectrumMnemonicStartView),
],
"PSBT Views": [
psbt_views.PSBTSelectSeedView, # this will fail, be rerun below
psbt_views.PSBTOverviewView,
psbt_views.PSBTUnsupportedScriptTypeWarningView,
psbt_views.PSBTNoChangeWarningView,
psbt_views.PSBTMathView,
(psbt_views.PSBTAddressDetailsView, dict(address_num=0)),
ScreenshotConfig(psbt_views.PSBTSelectSeedView, run_before=PSBTSelectSeedView_cb_before),
ScreenshotConfig(psbt_views.PSBTOverviewView, run_before=load_basic_psbt_cb),
ScreenshotConfig(psbt_views.PSBTUnsupportedScriptTypeWarningView),
ScreenshotConfig(psbt_views.PSBTNoChangeWarningView),
ScreenshotConfig(psbt_views.PSBTMathView),
ScreenshotConfig(psbt_views.PSBTAddressDetailsView, dict(address_num=0)),
(NotYetImplementedView, {}, "PSBTChangeDetailsView_multisig_unverified"), # Must manually re-run this below
(psbt_views.PSBTChangeDetailsView, dict(change_address_num=0), "PSBTChangeDetailsView_multisig_verified"),
(NotYetImplementedView, {}, "PSBTOverviewView_op_return"), # Placeholder
(NotYetImplementedView, {}, "PSBTOpReturnView_text"), # Placeholder
(NotYetImplementedView, {}, "PSBTOpReturnView_raw_hex_data"), # Placeholder
(psbt_views.PSBTAddressVerificationFailedView, dict(is_change=True, is_multisig=False), "PSBTAddressVerificationFailedView_singlesig_change"),
(psbt_views.PSBTAddressVerificationFailedView, dict(is_change=False, is_multisig=False), "PSBTAddressVerificationFailedView_singlesig_selftransfer"),
(psbt_views.PSBTAddressVerificationFailedView, dict(is_change=True, is_multisig=True), "PSBTAddressVerificationFailedView_multisig_change"),
(psbt_views.PSBTAddressVerificationFailedView, dict(is_change=False, is_multisig=True), "PSBTAddressVerificationFailedView_multisig_selftransfer"),
psbt_views.PSBTFinalizeView,
#PSBTSignedQRDisplayView
psbt_views.PSBTSigningErrorView,
ScreenshotConfig(psbt_views.PSBTChangeDetailsView, dict(change_address_num=0), screenshot_name="PSBTChangeDetailsView_multisig_unverified", run_before=load_basic_psbt_cb),
ScreenshotConfig(psbt_views.PSBTChangeDetailsView, dict(change_address_num=0), screenshot_name="PSBTChangeDetailsView_multisig_verified", run_before=load_multisig_wallet_descriptor_cb),
ScreenshotConfig(psbt_views.PSBTOverviewView, screenshot_name="PSBTOverviewView_op_return", run_before=PSBTOverviewView_op_return_cb_before),
ScreenshotConfig(psbt_views.PSBTOpReturnView, screenshot_name="PSBTOpReturnView_text"), # Relies on callback above
ScreenshotConfig(psbt_views.PSBTOpReturnView, screenshot_name="PSBTOpReturnView_raw_hex_data", run_before=PSBTOpReturnView_raw_hex_data_cb_before),
ScreenshotConfig(psbt_views.PSBTAddressVerificationFailedView, dict(is_change=True, is_multisig=False), screenshot_name="PSBTAddressVerificationFailedView_singlesig_change"),
ScreenshotConfig(psbt_views.PSBTAddressVerificationFailedView, dict(is_change=False, is_multisig=False), screenshot_name="PSBTAddressVerificationFailedView_singlesig_selftransfer"),
ScreenshotConfig(psbt_views.PSBTAddressVerificationFailedView, dict(is_change=True, is_multisig=True), screenshot_name="PSBTAddressVerificationFailedView_multisig_change"),
ScreenshotConfig(psbt_views.PSBTAddressVerificationFailedView, dict(is_change=False, is_multisig=True), screenshot_name="PSBTAddressVerificationFailedView_multisig_selftransfer"),
ScreenshotConfig(psbt_views.PSBTFinalizeView),
#ScreenshotConfig(PSBTSignedQRDisplayViewScreenshotConfig),
ScreenshotConfig(psbt_views.PSBTSigningErrorView),
],
"Tools Views": [
tools_views.ToolsMenuView,
#ToolsImageEntropyLivePreviewView
#ToolsImageEntropyFinalImageView
tools_views.ToolsImageEntropyMnemonicLengthView,
tools_views.ToolsDiceEntropyMnemonicLengthView,
(tools_views.ToolsDiceEntropyEntryView, dict(total_rolls=50)),
tools_views.ToolsCalcFinalWordNumWordsView,
tools_views.ToolsCalcFinalWordFinalizePromptView,
tools_views.ToolsCalcFinalWordCoinFlipsView,
(tools_views.ToolsCalcFinalWordShowFinalWordView, {}, "ToolsCalcFinalWordShowFinalWordView_pick_word"),
(tools_views.ToolsCalcFinalWordShowFinalWordView, dict(coin_flips="0010101"), "ToolsCalcFinalWordShowFinalWordView_coin_flips"),
tools_views.ToolsCalcFinalWordDoneView,
tools_views.ToolsAddressExplorerSelectSourceView,
tools_views.ToolsAddressExplorerAddressTypeView,
tools_views.ToolsAddressExplorerAddressListView,
# tools_views.ToolsAddressExplorerAddressView,
ScreenshotConfig(tools_views.ToolsMenuView),
#ScreenshotConfig(ToolsImageEntropyLivePreviewView),
#ScreenshotConfig(ToolsImageEntropyFinalImageView),
ScreenshotConfig(tools_views.ToolsImageEntropyMnemonicLengthView),
ScreenshotConfig(tools_views.ToolsDiceEntropyMnemonicLengthView),
ScreenshotConfig(tools_views.ToolsDiceEntropyEntryView, dict(total_rolls=50)),
ScreenshotConfig(tools_views.ToolsCalcFinalWordNumWordsView),
ScreenshotConfig(tools_views.ToolsCalcFinalWordFinalizePromptView),
ScreenshotConfig(tools_views.ToolsCalcFinalWordCoinFlipsView),
ScreenshotConfig(tools_views.ToolsCalcFinalWordShowFinalWordView, screenshot_name="ToolsCalcFinalWordShowFinalWordView_pick_word"),
ScreenshotConfig(tools_views.ToolsCalcFinalWordShowFinalWordView, dict(coin_flips="0010101"), screenshot_name="ToolsCalcFinalWordShowFinalWordView_coin_flips"),
ScreenshotConfig(tools_views.ToolsCalcFinalWordDoneView),
ScreenshotConfig(tools_views.ToolsAddressExplorerSelectSourceView),
ScreenshotConfig(tools_views.ToolsAddressExplorerAddressTypeView),
ScreenshotConfig(tools_views.ToolsAddressExplorerAddressListView),
# ScreenshotConfig(tools_views.ToolsAddressExplorerAddressView),
],
"Settings Views": settings_views_list + [
settings_views.IOTestView,
settings_views.DonateView,
(settings_views.SettingsIngestSettingsQRView, dict(data=settingsqr_data_persistent), "SettingsIngestSettingsQRView_persistent"),
(settings_views.SettingsIngestSettingsQRView, dict(data=settingsqr_data_not_persistent), "SettingsIngestSettingsQRView_not_persistent"),
ScreenshotConfig(settings_views.IOTestView),
ScreenshotConfig(settings_views.DonateView),
ScreenshotConfig(settings_views.SettingsIngestSettingsQRView, dict(data=settingsqr_data_persistent), screenshot_name="SettingsIngestSettingsQRView_persistent"),
ScreenshotConfig(settings_views.SettingsIngestSettingsQRView, dict(data=settingsqr_data_not_persistent), screenshot_name="SettingsIngestSettingsQRView_not_persistent"),
],
"Misc Error Views": [
NotYetImplementedView,
(UnhandledExceptionView, dict(error=["IndexError", "line 1, in some_buggy_code.py", "list index out of range"])),
(NetworkMismatchErrorView, dict(derivation_path="m/84'/1'/0'")),
(OptionDisabledView, dict(settings_attr=SettingsConstants.SETTING__MESSAGE_SIGNING)),
(ErrorView, dict(
ScreenshotConfig(NotYetImplementedView),
ScreenshotConfig(UnhandledExceptionView, dict(error=["IndexError", "line 1, in some_buggy_code.py", "list index out of range"])),
ScreenshotConfig(NetworkMismatchErrorView, dict(derivation_path="m/84'/1'/0'")),
ScreenshotConfig(OptionDisabledView, dict(settings_attr=SettingsConstants.SETTING__MESSAGE_SIGNING)),
ScreenshotConfig(ErrorView, dict(
title="Error",
status_headline="Unknown QR Type",
text="QRCode is invalid or is a data format not yet supported.",
@@ -326,15 +368,22 @@ def generate_screenshots(locale):
return screenshot_sections
def screencap_view(view_cls: View, view_args: dict = {}, view_name: str = None, toast_thread: BaseToastOverlayManagerThread = None):
if not view_name:
view_name = view_cls.__name__
screenshot_renderer.set_screenshot_filename(f"{view_name}.png")
def screencap_view(screenshot_config: ScreenshotConfig):
# Block until we have exclusive access to the screenshot renderer. Without this
# we were occasionally running into confusing race conditions where the next
# screenshot would begin rendering over the previous one. Claiming the lock
# guarantees that the previous screenshot has been fully rendered and saved.
screenshot_renderer.lock.acquire()
screenshot_renderer.set_screenshot_filename(f"{screenshot_config.screenshot_name}.png")
screenshot_renderer.lock.release()
controller = Controller.get_instance()
toast_thread = screenshot_config.toast_thread
try:
print(f"Running {view_name}")
print(f"Running {screenshot_config.screenshot_name}")
try:
view_cls(**view_args).run()
screenshot_config.run_callback_before()
screenshot_config.View_cls(**screenshot_config.view_kwargs).run()
except ScreenshotComplete:
# The target View has run and its Screen has rendered what it needs to
if toast_thread is not None:
@@ -342,20 +391,29 @@ def generate_screenshots(locale):
controller.activate_toast(toast_thread)
while controller.toast_notification_thread.is_alive():
# Give the Toast a moment to complete its work
time.sleep(0.01)
# TODO: Necessary now that the lock is in place?
# Whenever possible, clean up toast thread HERE before killing the
# main thread with ScreenshotComplete.
toast_thread.stop()
toast_thread.join()
raise ScreenshotComplete()
except ScreenshotComplete:
# Slightly hacky way to exit ScreenshotRenderer as expected
print(f"Completed {view_name}")
print(f"Completed {screenshot_config.screenshot_name}")
except Exception as e:
# Something else went wrong
from traceback import print_exc
print_exc()
raise e
finally:
if toast_thread:
if toast_thread and toast_thread.is_alive():
toast_thread.stop()
time.sleep(0.1) #jdlcdl
toast_thread.join()
screenshot_config.run_callback_after()
# Parse the main `l10n/messages.pot` for overall stats
@@ -370,7 +428,6 @@ def generate_screenshots(locale):
locale, display_name = locale_tuple_list[0]
Settings.get_instance().set_value(SettingsConstants.SETTING__LOCALE, value=locale)
screenshot_renderer.set_screenshot_path(os.path.join(screenshot_root, locale))
locale_readme = f"""# SeedSigner Screenshots: {display_name}\n"""
@@ -396,57 +453,14 @@ def generate_screenshots(locale):
locale_readme += f"## {section_name}\n\n"
locale_readme += """<table style="border: 0;">"""
locale_readme += f"""<tr><td align="center">"""
for screenshot in screenshot_list:
if type(screenshot) == tuple:
toast_thread = None
if len(screenshot) == 2:
view_cls, view_args = screenshot
view_name = view_cls.__name__
elif len(screenshot) == 3:
view_cls, view_args, view_name = screenshot
elif len(screenshot) == 4:
view_cls, view_args, view_name, toast_thread = screenshot
else:
view_cls = screenshot
view_args = {}
view_name = view_cls.__name__
toast_thread = None
screencap_view(view_cls, view_args, view_name, toast_thread=toast_thread)
for screenshot_config in screenshot_list:
screencap_view(screenshot_config)
locale_readme += """ <table align="left" style="border: 1px solid gray;">"""
locale_readme += f"""<tr><td align="center">{view_name}<br/><br/><img src="{subdir}/{view_name}.png"></td></tr>"""
locale_readme += f"""<tr><td align="center">{screenshot_config.screenshot_name}<br/><br/><img src="{subdir}/{screenshot_config.screenshot_name}.png"></td></tr>"""
locale_readme += """</table>\n"""
locale_readme += "</td></tr></table>"
# many screens don't work, leaving a missing image, re-run here for now
controller.psbt_seed = None
screenshot_renderer.set_screenshot_path(os.path.join(screenshot_root, locale, "psbt_views"))
decoder = DecodeQR()
decoder.add_data(BASE64_PSBT_1)
controller.psbt = decoder.get_psbt()
controller.psbt_seed = seed_12b
controller.multisig_wallet_descriptor = None
screencap_view(psbt_views.PSBTChangeDetailsView, view_name='PSBTChangeDetailsView_multisig_unverified', view_args=dict(change_address_num=0))
controller.psbt_seed = None
screencap_view(psbt_views.PSBTSelectSeedView, view_name='PSBTSelectSeedView')
# Render OP_RETURN screens for real
controller.psbt_seed = seed_12b
decoder = DecodeQR()
decoder.add_data(BASE64_PSBT_WITH_OP_RETURN_TEXT)
controller.psbt = decoder.get_psbt()
controller.psbt_parser = PSBTParser(p=controller.psbt, seed=seed_12b)
screencap_view(psbt_views.PSBTOverviewView, view_name='PSBTOverviewView_op_return')
screencap_view(psbt_views.PSBTOpReturnView, view_name="PSBTOpReturnView_text")
decoder.add_data(BASE64_PSBT_WITH_OP_RETURN_RAW_BYTES)
controller.psbt = decoder.get_psbt()
controller.psbt_parser = PSBTParser(p=controller.psbt, seed=seed_12b)
screencap_view(psbt_views.PSBTOpReturnView, view_name="PSBTOpReturnView_raw_hex_data")
with open(os.path.join(screenshot_root, locale, "README.md"), 'w') as readme_file:
readme_file.write(locale_readme)
+35 -1
View File
@@ -1,12 +1,19 @@
import os
from dataclasses import dataclass
from PIL import Image, ImageDraw
from seedsigner.gui.renderer import Renderer
from seedsigner.gui.toast import BaseToastOverlayManagerThread
from seedsigner.views.view import View
class ScreenshotComplete(Exception):
pass
class ScreenshotRenderer(Renderer):
screenshot_path: str = None
screenshot_filename: str = None
@@ -35,7 +42,7 @@ class ScreenshotRenderer(Renderer):
self.screenshot_path = path
def show_image(self, image=None, alpha_overlay=None, is_background_thread: bool = False):
def show_image(self, image=None, alpha_overlay=None, is_background_thread: bool = False):
if is_background_thread:
return
@@ -51,3 +58,30 @@ class ScreenshotRenderer(Renderer):
self.canvas.save(os.path.join(self.screenshot_path, self.screenshot_filename))
raise ScreenshotComplete()
@dataclass
class ScreenshotConfig:
View_cls: View
view_kwargs: dict = None
screenshot_name: str = None
toast_thread: BaseToastOverlayManagerThread = None
run_before: callable = None
run_after: callable = None
def __post_init__(self):
if not self.view_kwargs:
self.view_kwargs = {}
if not self.screenshot_name:
self.screenshot_name = self.View_cls.__name__
def run_callback_before(self):
if self.run_before:
self.run_before()
def run_callback_after(self):
if self.run_after:
self.run_after()