mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-28 00:58:09 +00:00
feat(all): implement translations into Trezor
Co-authored-by matejcik <ja@matejcik.cz>
This commit is contained in:
parent
b5c86a45ed
commit
b8ea21d24a
@ -46,5 +46,6 @@ include:
|
||||
- ci/build.yml
|
||||
- ci/test.yml
|
||||
- ci/test-hw.yml
|
||||
- ci/test-nonenglish.yml
|
||||
- ci/posttest.yml
|
||||
- ci/deploy.yml
|
||||
|
4
Makefile
4
Makefile
@ -144,6 +144,6 @@ vendorheader: ## generate vendor header
|
||||
vendorheader_check: ## check that vendor header is up to date
|
||||
./core/embed/vendorheader/generate.sh --quiet --check
|
||||
|
||||
gen: mocks icons templates protobuf ci_docs vendorheader solana_templates ## regenerate auto-generated files from sources
|
||||
gen: templates mocks icons protobuf ci_docs vendorheader solana ## regenerate auto-generated files from sources
|
||||
|
||||
gen_check: mocks_check icons_check templates_check protobuf_check ci_docs_check vendorheader_check solana_templates_check ## check validity of auto-generated files
|
||||
gen_check: templates_check mocks_check icons_check protobuf_check ci_docs_check vendorheader_check solana_templates_check ## check validity of auto-generated files
|
||||
|
@ -261,6 +261,44 @@ ui tests fixtures deploy:
|
||||
tags:
|
||||
- deploy
|
||||
|
||||
ui tests fixtures deploy nonenglish:
|
||||
stage: deploy
|
||||
variables:
|
||||
DEPLOY_PATH: "${DEPLOY_BASE_DIR}/ui_tests/"
|
||||
BUCKET: "data.trezor.io"
|
||||
GIT_SUBMODULE_STRATEGY: "none"
|
||||
only:
|
||||
- schedules # nightly build
|
||||
- /translations/ # translations branches
|
||||
before_script: [] # no poetry
|
||||
interruptible: false
|
||||
needs:
|
||||
- core click test czech
|
||||
- core device test czech
|
||||
- core device R test czech
|
||||
- core click R test czech
|
||||
- core click test german
|
||||
- core device test german
|
||||
- core device R test german
|
||||
- core click R test german
|
||||
- core click test french
|
||||
- core device test french
|
||||
- core device R test french
|
||||
- core click R test french
|
||||
- core click test spanish
|
||||
- core device test spanish
|
||||
- core device R test spanish
|
||||
- core click R test spanish
|
||||
script:
|
||||
- echo "Deploying to $DEPLOY_PATH"
|
||||
- rsync --delete -va ci/ui_test_records/* "$DEPLOY_PATH"
|
||||
- source ${AWS_DEPLOY_DATA}
|
||||
- aws s3 sync $DEPLOY_PATH s3://$BUCKET/dev/firmware/ui_tests
|
||||
# This "hack" is needed because aws does not have an easy option to generate autoindex. We fetch the one autogenerated by nginx on local server.
|
||||
- wget https://firmware.corp.sldev.cz/ui_tests/ -O index.html && aws s3 cp index.html s3://$BUCKET/dev/firmware/ui_tests/
|
||||
tags:
|
||||
- deploy
|
||||
|
||||
# sync to aws
|
||||
|
||||
sync emulators to aws:
|
||||
|
166
ci/make_nonenglish_tests.py
Normal file
166
ci/make_nonenglish_tests.py
Normal file
@ -0,0 +1,166 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Callable
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
|
||||
TEST_FILE = HERE / "test-nonenglish.yml"
|
||||
|
||||
LANGS = {
|
||||
"cs": "czech",
|
||||
"fr": "french",
|
||||
"de": "german",
|
||||
"es": "spanish",
|
||||
}
|
||||
|
||||
MODELS = ["T", "R"]
|
||||
|
||||
|
||||
@dataclass
|
||||
class Replacement:
|
||||
start: str
|
||||
end: str
|
||||
replacement: str
|
||||
|
||||
|
||||
def replace_content_between_markers(
|
||||
file_path: Path | str, replacements: list[Replacement]
|
||||
) -> None:
|
||||
with open(file_path, "r") as file:
|
||||
content = file.read()
|
||||
|
||||
for replace in replacements:
|
||||
pattern = rf"({replace.start}.*?{replace.end})"
|
||||
content = re.sub(
|
||||
pattern,
|
||||
f"{replace.start}\n{replace.replacement}\n{replace.end}",
|
||||
content,
|
||||
flags=re.DOTALL,
|
||||
)
|
||||
|
||||
with open(file_path, "w") as file:
|
||||
file.write(content)
|
||||
|
||||
|
||||
def get_device_test(lang: str, model: str) -> str:
|
||||
lang_long = LANGS[lang]
|
||||
|
||||
model_or_empty = f" {model}" if model != "T" else ""
|
||||
model_needs_or_empty = f" {model}" if model != "T" else ""
|
||||
|
||||
return f"""\
|
||||
core device{model_or_empty} test {lang_long}:
|
||||
stage: test
|
||||
<<: *gitlab_caching
|
||||
needs:
|
||||
- core unix frozen{model_needs_or_empty} debug build
|
||||
variables:
|
||||
TREZOR_PROFILING: "1" # so that we get coverage data
|
||||
TREZOR_MODEL: "{model}"
|
||||
MULTICORE: "4" # more could interfere with other jobs
|
||||
TEST_LANG: "{lang}" # {lang_long}
|
||||
only:
|
||||
- schedules # nightly build
|
||||
- /translations/ # translations branches
|
||||
script:
|
||||
- $NIX_SHELL --run "poetry run make -C core test_emu_ui_multicore | ts -s"
|
||||
after_script:
|
||||
- mv core/src/.coverage.* core # there will be more coverage files (one per core)
|
||||
- mv tests/ui_tests/reports/test/ test_ui_report
|
||||
- $NIX_SHELL --run "poetry run python ci/prepare_ui_artifacts.py | ts -s"
|
||||
- diff -u tests/ui_tests/fixtures.json tests/ui_tests/fixtures.suggestion.json
|
||||
artifacts:
|
||||
name: "$CI_JOB_NAME-$CI_COMMIT_SHORT_SHA"
|
||||
paths:
|
||||
- ci/ui_test_records/
|
||||
- test_ui_report
|
||||
- tests/ui_tests/screens/
|
||||
- tests/ui_tests/fixtures.suggestion.json
|
||||
- tests/ui_tests/fixtures.results.json
|
||||
- tests/junit.xml
|
||||
- tests/trezor.log
|
||||
- core/.coverage.*
|
||||
when: always
|
||||
expire_in: 1 week
|
||||
reports:
|
||||
junit: tests/junit.xml
|
||||
"""
|
||||
|
||||
|
||||
def get_click_test(lang: str, model: str) -> str:
|
||||
lang_long = LANGS[lang]
|
||||
|
||||
model_or_empty = f" {model}" if model != "T" else ""
|
||||
model_needs_or_empty = f" {model}" if model != "T" else ""
|
||||
|
||||
return f"""\
|
||||
core click{model_or_empty} test {lang_long}:
|
||||
stage: test
|
||||
<<: *gitlab_caching
|
||||
needs:
|
||||
- core unix frozen{model_needs_or_empty} debug build
|
||||
variables:
|
||||
TREZOR_PROFILING: "1" # so that we get coverage data
|
||||
TREZOR_MODEL: "{model}"
|
||||
TEST_LANG: "{lang}" # {lang_long}
|
||||
only:
|
||||
- schedules # nightly build
|
||||
- /translations/ # translations branches
|
||||
script:
|
||||
- $NIX_SHELL --run "poetry run make -C core test_emu_click_ui | ts -s"
|
||||
after_script:
|
||||
- mv core/src/.coverage core/.coverage.test_click
|
||||
- mv tests/ui_tests/reports/test/ test_ui_report
|
||||
- $NIX_SHELL --run "poetry run python ci/prepare_ui_artifacts.py | ts -s"
|
||||
- diff -u tests/ui_tests/fixtures.json tests/ui_tests/fixtures.suggestion.json
|
||||
artifacts:
|
||||
name: "$CI_JOB_NAME-$CI_COMMIT_SHORT_SHA"
|
||||
paths:
|
||||
- ci/ui_test_records/
|
||||
- test_ui_report
|
||||
- tests/ui_tests/screens/
|
||||
- tests/ui_tests/fixtures.suggestion.json
|
||||
- tests/ui_tests/fixtures.results.json
|
||||
- tests/trezor.log
|
||||
- tests/junit.xml
|
||||
- core/.coverage.*
|
||||
reports:
|
||||
junit: tests/junit.xml
|
||||
expire_in: 1 week
|
||||
when: always
|
||||
"""
|
||||
|
||||
|
||||
def get_all_tests_text(func: Callable[[str, str], str]) -> str:
|
||||
text = ""
|
||||
for model in MODELS:
|
||||
for lang in LANGS:
|
||||
content = func(lang, model)
|
||||
text += content + "\n"
|
||||
return text
|
||||
|
||||
|
||||
def fill_device_tests() -> None:
|
||||
replacement = Replacement(
|
||||
start=r"##START_DEVICE_TESTS",
|
||||
end=r"##END_DEVICE_TESTS",
|
||||
replacement=get_all_tests_text(get_device_test),
|
||||
)
|
||||
replace_content_between_markers(TEST_FILE, [replacement])
|
||||
|
||||
|
||||
def fill_click_tests() -> None:
|
||||
replacement = Replacement(
|
||||
start=r"##START_CLICK_TESTS",
|
||||
end=r"##END_CLICK_TESTS",
|
||||
replacement=get_all_tests_text(get_click_test),
|
||||
)
|
||||
replace_content_between_markers(TEST_FILE, [replacement])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
fill_device_tests()
|
||||
fill_click_tests()
|
@ -97,6 +97,7 @@ stdenvNoCC.mkDerivation ({
|
||||
bash
|
||||
bloaty # for binsize
|
||||
check
|
||||
crowdin-cli # for translations
|
||||
curl # for connect tests
|
||||
editorconfig-checker
|
||||
gcc-arm-embedded
|
||||
|
587
ci/test-nonenglish.yml
Normal file
587
ci/test-nonenglish.yml
Normal file
@ -0,0 +1,587 @@
|
||||
# Tests for non-english languages, that run only nightly
|
||||
# - apart from that, they run also for every branch containing "translations" in its name
|
||||
|
||||
image: registry.gitlab.com/satoshilabs/trezor/trezor-firmware/trezor-firmware-env.nix
|
||||
|
||||
# Caching
|
||||
.gitlab_caching: &gitlab_caching
|
||||
cache:
|
||||
key: "$CI_COMMIT_REF_SLUG"
|
||||
paths:
|
||||
- .venv/
|
||||
|
||||
##START_DEVICE_TESTS
|
||||
core device test czech:
|
||||
stage: test
|
||||
<<: *gitlab_caching
|
||||
needs:
|
||||
- core unix frozen debug build
|
||||
variables:
|
||||
TREZOR_PROFILING: "1" # so that we get coverage data
|
||||
TREZOR_MODEL: "T"
|
||||
MULTICORE: "4" # more could interfere with other jobs
|
||||
TEST_LANG: "cs" # czech
|
||||
only:
|
||||
- schedules # nightly build
|
||||
- /translations/ # translations branches
|
||||
script:
|
||||
- $NIX_SHELL --run "poetry run make -C core test_emu_ui_multicore | ts -s"
|
||||
after_script:
|
||||
- mv core/src/.coverage.* core # there will be more coverage files (one per core)
|
||||
- mv tests/ui_tests/reports/test/ test_ui_report
|
||||
- $NIX_SHELL --run "poetry run python ci/prepare_ui_artifacts.py | ts -s"
|
||||
- diff -u tests/ui_tests/fixtures.json tests/ui_tests/fixtures.suggestion.json
|
||||
artifacts:
|
||||
name: "$CI_JOB_NAME-$CI_COMMIT_SHORT_SHA"
|
||||
paths:
|
||||
- ci/ui_test_records/
|
||||
- test_ui_report
|
||||
- tests/ui_tests/screens/
|
||||
- tests/ui_tests/fixtures.suggestion.json
|
||||
- tests/ui_tests/fixtures.results.json
|
||||
- tests/junit.xml
|
||||
- tests/trezor.log
|
||||
- core/.coverage.*
|
||||
when: always
|
||||
expire_in: 1 week
|
||||
reports:
|
||||
junit: tests/junit.xml
|
||||
|
||||
core device test french:
|
||||
stage: test
|
||||
<<: *gitlab_caching
|
||||
needs:
|
||||
- core unix frozen debug build
|
||||
variables:
|
||||
TREZOR_PROFILING: "1" # so that we get coverage data
|
||||
TREZOR_MODEL: "T"
|
||||
MULTICORE: "4" # more could interfere with other jobs
|
||||
TEST_LANG: "fr" # french
|
||||
only:
|
||||
- schedules # nightly build
|
||||
- /translations/ # translations branches
|
||||
script:
|
||||
- $NIX_SHELL --run "poetry run make -C core test_emu_ui_multicore | ts -s"
|
||||
after_script:
|
||||
- mv core/src/.coverage.* core # there will be more coverage files (one per core)
|
||||
- mv tests/ui_tests/reports/test/ test_ui_report
|
||||
- $NIX_SHELL --run "poetry run python ci/prepare_ui_artifacts.py | ts -s"
|
||||
- diff -u tests/ui_tests/fixtures.json tests/ui_tests/fixtures.suggestion.json
|
||||
artifacts:
|
||||
name: "$CI_JOB_NAME-$CI_COMMIT_SHORT_SHA"
|
||||
paths:
|
||||
- ci/ui_test_records/
|
||||
- test_ui_report
|
||||
- tests/ui_tests/screens/
|
||||
- tests/ui_tests/fixtures.suggestion.json
|
||||
- tests/ui_tests/fixtures.results.json
|
||||
- tests/junit.xml
|
||||
- tests/trezor.log
|
||||
- core/.coverage.*
|
||||
when: always
|
||||
expire_in: 1 week
|
||||
reports:
|
||||
junit: tests/junit.xml
|
||||
|
||||
core device test german:
|
||||
stage: test
|
||||
<<: *gitlab_caching
|
||||
needs:
|
||||
- core unix frozen debug build
|
||||
variables:
|
||||
TREZOR_PROFILING: "1" # so that we get coverage data
|
||||
TREZOR_MODEL: "T"
|
||||
MULTICORE: "4" # more could interfere with other jobs
|
||||
TEST_LANG: "de" # german
|
||||
only:
|
||||
- schedules # nightly build
|
||||
- /translations/ # translations branches
|
||||
script:
|
||||
- $NIX_SHELL --run "poetry run make -C core test_emu_ui_multicore | ts -s"
|
||||
after_script:
|
||||
- mv core/src/.coverage.* core # there will be more coverage files (one per core)
|
||||
- mv tests/ui_tests/reports/test/ test_ui_report
|
||||
- $NIX_SHELL --run "poetry run python ci/prepare_ui_artifacts.py | ts -s"
|
||||
- diff -u tests/ui_tests/fixtures.json tests/ui_tests/fixtures.suggestion.json
|
||||
artifacts:
|
||||
name: "$CI_JOB_NAME-$CI_COMMIT_SHORT_SHA"
|
||||
paths:
|
||||
- ci/ui_test_records/
|
||||
- test_ui_report
|
||||
- tests/ui_tests/screens/
|
||||
- tests/ui_tests/fixtures.suggestion.json
|
||||
- tests/ui_tests/fixtures.results.json
|
||||
- tests/junit.xml
|
||||
- tests/trezor.log
|
||||
- core/.coverage.*
|
||||
when: always
|
||||
expire_in: 1 week
|
||||
reports:
|
||||
junit: tests/junit.xml
|
||||
|
||||
core device test spanish:
|
||||
stage: test
|
||||
<<: *gitlab_caching
|
||||
needs:
|
||||
- core unix frozen debug build
|
||||
variables:
|
||||
TREZOR_PROFILING: "1" # so that we get coverage data
|
||||
TREZOR_MODEL: "T"
|
||||
MULTICORE: "4" # more could interfere with other jobs
|
||||
TEST_LANG: "es" # spanish
|
||||
only:
|
||||
- schedules # nightly build
|
||||
- /translations/ # translations branches
|
||||
script:
|
||||
- $NIX_SHELL --run "poetry run make -C core test_emu_ui_multicore | ts -s"
|
||||
after_script:
|
||||
- mv core/src/.coverage.* core # there will be more coverage files (one per core)
|
||||
- mv tests/ui_tests/reports/test/ test_ui_report
|
||||
- $NIX_SHELL --run "poetry run python ci/prepare_ui_artifacts.py | ts -s"
|
||||
- diff -u tests/ui_tests/fixtures.json tests/ui_tests/fixtures.suggestion.json
|
||||
artifacts:
|
||||
name: "$CI_JOB_NAME-$CI_COMMIT_SHORT_SHA"
|
||||
paths:
|
||||
- ci/ui_test_records/
|
||||
- test_ui_report
|
||||
- tests/ui_tests/screens/
|
||||
- tests/ui_tests/fixtures.suggestion.json
|
||||
- tests/ui_tests/fixtures.results.json
|
||||
- tests/junit.xml
|
||||
- tests/trezor.log
|
||||
- core/.coverage.*
|
||||
when: always
|
||||
expire_in: 1 week
|
||||
reports:
|
||||
junit: tests/junit.xml
|
||||
|
||||
core device R test czech:
|
||||
stage: test
|
||||
<<: *gitlab_caching
|
||||
needs:
|
||||
- core unix frozen R debug build
|
||||
variables:
|
||||
TREZOR_PROFILING: "1" # so that we get coverage data
|
||||
TREZOR_MODEL: "R"
|
||||
MULTICORE: "4" # more could interfere with other jobs
|
||||
TEST_LANG: "cs" # czech
|
||||
only:
|
||||
- schedules # nightly build
|
||||
- /translations/ # translations branches
|
||||
script:
|
||||
- $NIX_SHELL --run "poetry run make -C core test_emu_ui_multicore | ts -s"
|
||||
after_script:
|
||||
- mv core/src/.coverage.* core # there will be more coverage files (one per core)
|
||||
- mv tests/ui_tests/reports/test/ test_ui_report
|
||||
- $NIX_SHELL --run "poetry run python ci/prepare_ui_artifacts.py | ts -s"
|
||||
- diff -u tests/ui_tests/fixtures.json tests/ui_tests/fixtures.suggestion.json
|
||||
artifacts:
|
||||
name: "$CI_JOB_NAME-$CI_COMMIT_SHORT_SHA"
|
||||
paths:
|
||||
- ci/ui_test_records/
|
||||
- test_ui_report
|
||||
- tests/ui_tests/screens/
|
||||
- tests/ui_tests/fixtures.suggestion.json
|
||||
- tests/ui_tests/fixtures.results.json
|
||||
- tests/junit.xml
|
||||
- tests/trezor.log
|
||||
- core/.coverage.*
|
||||
when: always
|
||||
expire_in: 1 week
|
||||
reports:
|
||||
junit: tests/junit.xml
|
||||
|
||||
core device R test french:
|
||||
stage: test
|
||||
<<: *gitlab_caching
|
||||
needs:
|
||||
- core unix frozen R debug build
|
||||
variables:
|
||||
TREZOR_PROFILING: "1" # so that we get coverage data
|
||||
TREZOR_MODEL: "R"
|
||||
MULTICORE: "4" # more could interfere with other jobs
|
||||
TEST_LANG: "fr" # french
|
||||
only:
|
||||
- schedules # nightly build
|
||||
- /translations/ # translations branches
|
||||
script:
|
||||
- $NIX_SHELL --run "poetry run make -C core test_emu_ui_multicore | ts -s"
|
||||
after_script:
|
||||
- mv core/src/.coverage.* core # there will be more coverage files (one per core)
|
||||
- mv tests/ui_tests/reports/test/ test_ui_report
|
||||
- $NIX_SHELL --run "poetry run python ci/prepare_ui_artifacts.py | ts -s"
|
||||
- diff -u tests/ui_tests/fixtures.json tests/ui_tests/fixtures.suggestion.json
|
||||
artifacts:
|
||||
name: "$CI_JOB_NAME-$CI_COMMIT_SHORT_SHA"
|
||||
paths:
|
||||
- ci/ui_test_records/
|
||||
- test_ui_report
|
||||
- tests/ui_tests/screens/
|
||||
- tests/ui_tests/fixtures.suggestion.json
|
||||
- tests/ui_tests/fixtures.results.json
|
||||
- tests/junit.xml
|
||||
- tests/trezor.log
|
||||
- core/.coverage.*
|
||||
when: always
|
||||
expire_in: 1 week
|
||||
reports:
|
||||
junit: tests/junit.xml
|
||||
|
||||
core device R test german:
|
||||
stage: test
|
||||
<<: *gitlab_caching
|
||||
needs:
|
||||
- core unix frozen R debug build
|
||||
variables:
|
||||
TREZOR_PROFILING: "1" # so that we get coverage data
|
||||
TREZOR_MODEL: "R"
|
||||
MULTICORE: "4" # more could interfere with other jobs
|
||||
TEST_LANG: "de" # german
|
||||
only:
|
||||
- schedules # nightly build
|
||||
- /translations/ # translations branches
|
||||
script:
|
||||
- $NIX_SHELL --run "poetry run make -C core test_emu_ui_multicore | ts -s"
|
||||
after_script:
|
||||
- mv core/src/.coverage.* core # there will be more coverage files (one per core)
|
||||
- mv tests/ui_tests/reports/test/ test_ui_report
|
||||
- $NIX_SHELL --run "poetry run python ci/prepare_ui_artifacts.py | ts -s"
|
||||
- diff -u tests/ui_tests/fixtures.json tests/ui_tests/fixtures.suggestion.json
|
||||
artifacts:
|
||||
name: "$CI_JOB_NAME-$CI_COMMIT_SHORT_SHA"
|
||||
paths:
|
||||
- ci/ui_test_records/
|
||||
- test_ui_report
|
||||
- tests/ui_tests/screens/
|
||||
- tests/ui_tests/fixtures.suggestion.json
|
||||
- tests/ui_tests/fixtures.results.json
|
||||
- tests/junit.xml
|
||||
- tests/trezor.log
|
||||
- core/.coverage.*
|
||||
when: always
|
||||
expire_in: 1 week
|
||||
reports:
|
||||
junit: tests/junit.xml
|
||||
|
||||
core device R test spanish:
|
||||
stage: test
|
||||
<<: *gitlab_caching
|
||||
needs:
|
||||
- core unix frozen R debug build
|
||||
variables:
|
||||
TREZOR_PROFILING: "1" # so that we get coverage data
|
||||
TREZOR_MODEL: "R"
|
||||
MULTICORE: "4" # more could interfere with other jobs
|
||||
TEST_LANG: "es" # spanish
|
||||
only:
|
||||
- schedules # nightly build
|
||||
- /translations/ # translations branches
|
||||
script:
|
||||
- $NIX_SHELL --run "poetry run make -C core test_emu_ui_multicore | ts -s"
|
||||
after_script:
|
||||
- mv core/src/.coverage.* core # there will be more coverage files (one per core)
|
||||
- mv tests/ui_tests/reports/test/ test_ui_report
|
||||
- $NIX_SHELL --run "poetry run python ci/prepare_ui_artifacts.py | ts -s"
|
||||
- diff -u tests/ui_tests/fixtures.json tests/ui_tests/fixtures.suggestion.json
|
||||
artifacts:
|
||||
name: "$CI_JOB_NAME-$CI_COMMIT_SHORT_SHA"
|
||||
paths:
|
||||
- ci/ui_test_records/
|
||||
- test_ui_report
|
||||
- tests/ui_tests/screens/
|
||||
- tests/ui_tests/fixtures.suggestion.json
|
||||
- tests/ui_tests/fixtures.results.json
|
||||
- tests/junit.xml
|
||||
- tests/trezor.log
|
||||
- core/.coverage.*
|
||||
when: always
|
||||
expire_in: 1 week
|
||||
reports:
|
||||
junit: tests/junit.xml
|
||||
|
||||
|
||||
##END_DEVICE_TESTS
|
||||
|
||||
##START_CLICK_TESTS
|
||||
core click test czech:
|
||||
stage: test
|
||||
<<: *gitlab_caching
|
||||
needs:
|
||||
- core unix frozen debug build
|
||||
variables:
|
||||
TREZOR_PROFILING: "1" # so that we get coverage data
|
||||
TREZOR_MODEL: "T"
|
||||
TEST_LANG: "cs" # czech
|
||||
only:
|
||||
- schedules # nightly build
|
||||
- /translations/ # translations branches
|
||||
script:
|
||||
- $NIX_SHELL --run "poetry run make -C core test_emu_click_ui | ts -s"
|
||||
after_script:
|
||||
- mv core/src/.coverage core/.coverage.test_click
|
||||
- mv tests/ui_tests/reports/test/ test_ui_report
|
||||
- $NIX_SHELL --run "poetry run python ci/prepare_ui_artifacts.py | ts -s"
|
||||
- diff -u tests/ui_tests/fixtures.json tests/ui_tests/fixtures.suggestion.json
|
||||
artifacts:
|
||||
name: "$CI_JOB_NAME-$CI_COMMIT_SHORT_SHA"
|
||||
paths:
|
||||
- ci/ui_test_records/
|
||||
- test_ui_report
|
||||
- tests/ui_tests/screens/
|
||||
- tests/ui_tests/fixtures.suggestion.json
|
||||
- tests/ui_tests/fixtures.results.json
|
||||
- tests/trezor.log
|
||||
- tests/junit.xml
|
||||
- core/.coverage.*
|
||||
reports:
|
||||
junit: tests/junit.xml
|
||||
expire_in: 1 week
|
||||
when: always
|
||||
|
||||
core click test french:
|
||||
stage: test
|
||||
<<: *gitlab_caching
|
||||
needs:
|
||||
- core unix frozen debug build
|
||||
variables:
|
||||
TREZOR_PROFILING: "1" # so that we get coverage data
|
||||
TREZOR_MODEL: "T"
|
||||
TEST_LANG: "fr" # french
|
||||
only:
|
||||
- schedules # nightly build
|
||||
- /translations/ # translations branches
|
||||
script:
|
||||
- $NIX_SHELL --run "poetry run make -C core test_emu_click_ui | ts -s"
|
||||
after_script:
|
||||
- mv core/src/.coverage core/.coverage.test_click
|
||||
- mv tests/ui_tests/reports/test/ test_ui_report
|
||||
- $NIX_SHELL --run "poetry run python ci/prepare_ui_artifacts.py | ts -s"
|
||||
- diff -u tests/ui_tests/fixtures.json tests/ui_tests/fixtures.suggestion.json
|
||||
artifacts:
|
||||
name: "$CI_JOB_NAME-$CI_COMMIT_SHORT_SHA"
|
||||
paths:
|
||||
- ci/ui_test_records/
|
||||
- test_ui_report
|
||||
- tests/ui_tests/screens/
|
||||
- tests/ui_tests/fixtures.suggestion.json
|
||||
- tests/ui_tests/fixtures.results.json
|
||||
- tests/trezor.log
|
||||
- tests/junit.xml
|
||||
- core/.coverage.*
|
||||
reports:
|
||||
junit: tests/junit.xml
|
||||
expire_in: 1 week
|
||||
when: always
|
||||
|
||||
core click test german:
|
||||
stage: test
|
||||
<<: *gitlab_caching
|
||||
needs:
|
||||
- core unix frozen debug build
|
||||
variables:
|
||||
TREZOR_PROFILING: "1" # so that we get coverage data
|
||||
TREZOR_MODEL: "T"
|
||||
TEST_LANG: "de" # german
|
||||
only:
|
||||
- schedules # nightly build
|
||||
- /translations/ # translations branches
|
||||
script:
|
||||
- $NIX_SHELL --run "poetry run make -C core test_emu_click_ui | ts -s"
|
||||
after_script:
|
||||
- mv core/src/.coverage core/.coverage.test_click
|
||||
- mv tests/ui_tests/reports/test/ test_ui_report
|
||||
- $NIX_SHELL --run "poetry run python ci/prepare_ui_artifacts.py | ts -s"
|
||||
- diff -u tests/ui_tests/fixtures.json tests/ui_tests/fixtures.suggestion.json
|
||||
artifacts:
|
||||
name: "$CI_JOB_NAME-$CI_COMMIT_SHORT_SHA"
|
||||
paths:
|
||||
- ci/ui_test_records/
|
||||
- test_ui_report
|
||||
- tests/ui_tests/screens/
|
||||
- tests/ui_tests/fixtures.suggestion.json
|
||||
- tests/ui_tests/fixtures.results.json
|
||||
- tests/trezor.log
|
||||
- tests/junit.xml
|
||||
- core/.coverage.*
|
||||
reports:
|
||||
junit: tests/junit.xml
|
||||
expire_in: 1 week
|
||||
when: always
|
||||
|
||||
core click test spanish:
|
||||
stage: test
|
||||
<<: *gitlab_caching
|
||||
needs:
|
||||
- core unix frozen debug build
|
||||
variables:
|
||||
TREZOR_PROFILING: "1" # so that we get coverage data
|
||||
TREZOR_MODEL: "T"
|
||||
TEST_LANG: "es" # spanish
|
||||
only:
|
||||
- schedules # nightly build
|
||||
- /translations/ # translations branches
|
||||
script:
|
||||
- $NIX_SHELL --run "poetry run make -C core test_emu_click_ui | ts -s"
|
||||
after_script:
|
||||
- mv core/src/.coverage core/.coverage.test_click
|
||||
- mv tests/ui_tests/reports/test/ test_ui_report
|
||||
- $NIX_SHELL --run "poetry run python ci/prepare_ui_artifacts.py | ts -s"
|
||||
- diff -u tests/ui_tests/fixtures.json tests/ui_tests/fixtures.suggestion.json
|
||||
artifacts:
|
||||
name: "$CI_JOB_NAME-$CI_COMMIT_SHORT_SHA"
|
||||
paths:
|
||||
- ci/ui_test_records/
|
||||
- test_ui_report
|
||||
- tests/ui_tests/screens/
|
||||
- tests/ui_tests/fixtures.suggestion.json
|
||||
- tests/ui_tests/fixtures.results.json
|
||||
- tests/trezor.log
|
||||
- tests/junit.xml
|
||||
- core/.coverage.*
|
||||
reports:
|
||||
junit: tests/junit.xml
|
||||
expire_in: 1 week
|
||||
when: always
|
||||
|
||||
core click R test czech:
|
||||
stage: test
|
||||
<<: *gitlab_caching
|
||||
needs:
|
||||
- core unix frozen R debug build
|
||||
variables:
|
||||
TREZOR_PROFILING: "1" # so that we get coverage data
|
||||
TREZOR_MODEL: "R"
|
||||
TEST_LANG: "cs" # czech
|
||||
only:
|
||||
- schedules # nightly build
|
||||
- /translations/ # translations branches
|
||||
script:
|
||||
- $NIX_SHELL --run "poetry run make -C core test_emu_click_ui | ts -s"
|
||||
after_script:
|
||||
- mv core/src/.coverage core/.coverage.test_click
|
||||
- mv tests/ui_tests/reports/test/ test_ui_report
|
||||
- $NIX_SHELL --run "poetry run python ci/prepare_ui_artifacts.py | ts -s"
|
||||
- diff -u tests/ui_tests/fixtures.json tests/ui_tests/fixtures.suggestion.json
|
||||
artifacts:
|
||||
name: "$CI_JOB_NAME-$CI_COMMIT_SHORT_SHA"
|
||||
paths:
|
||||
- ci/ui_test_records/
|
||||
- test_ui_report
|
||||
- tests/ui_tests/screens/
|
||||
- tests/ui_tests/fixtures.suggestion.json
|
||||
- tests/ui_tests/fixtures.results.json
|
||||
- tests/trezor.log
|
||||
- tests/junit.xml
|
||||
- core/.coverage.*
|
||||
reports:
|
||||
junit: tests/junit.xml
|
||||
expire_in: 1 week
|
||||
when: always
|
||||
|
||||
core click R test french:
|
||||
stage: test
|
||||
<<: *gitlab_caching
|
||||
needs:
|
||||
- core unix frozen R debug build
|
||||
variables:
|
||||
TREZOR_PROFILING: "1" # so that we get coverage data
|
||||
TREZOR_MODEL: "R"
|
||||
TEST_LANG: "fr" # french
|
||||
only:
|
||||
- schedules # nightly build
|
||||
- /translations/ # translations branches
|
||||
script:
|
||||
- $NIX_SHELL --run "poetry run make -C core test_emu_click_ui | ts -s"
|
||||
after_script:
|
||||
- mv core/src/.coverage core/.coverage.test_click
|
||||
- mv tests/ui_tests/reports/test/ test_ui_report
|
||||
- $NIX_SHELL --run "poetry run python ci/prepare_ui_artifacts.py | ts -s"
|
||||
- diff -u tests/ui_tests/fixtures.json tests/ui_tests/fixtures.suggestion.json
|
||||
artifacts:
|
||||
name: "$CI_JOB_NAME-$CI_COMMIT_SHORT_SHA"
|
||||
paths:
|
||||
- ci/ui_test_records/
|
||||
- test_ui_report
|
||||
- tests/ui_tests/screens/
|
||||
- tests/ui_tests/fixtures.suggestion.json
|
||||
- tests/ui_tests/fixtures.results.json
|
||||
- tests/trezor.log
|
||||
- tests/junit.xml
|
||||
- core/.coverage.*
|
||||
reports:
|
||||
junit: tests/junit.xml
|
||||
expire_in: 1 week
|
||||
when: always
|
||||
|
||||
core click R test german:
|
||||
stage: test
|
||||
<<: *gitlab_caching
|
||||
needs:
|
||||
- core unix frozen R debug build
|
||||
variables:
|
||||
TREZOR_PROFILING: "1" # so that we get coverage data
|
||||
TREZOR_MODEL: "R"
|
||||
TEST_LANG: "de" # german
|
||||
only:
|
||||
- schedules # nightly build
|
||||
- /translations/ # translations branches
|
||||
script:
|
||||
- $NIX_SHELL --run "poetry run make -C core test_emu_click_ui | ts -s"
|
||||
after_script:
|
||||
- mv core/src/.coverage core/.coverage.test_click
|
||||
- mv tests/ui_tests/reports/test/ test_ui_report
|
||||
- $NIX_SHELL --run "poetry run python ci/prepare_ui_artifacts.py | ts -s"
|
||||
- diff -u tests/ui_tests/fixtures.json tests/ui_tests/fixtures.suggestion.json
|
||||
artifacts:
|
||||
name: "$CI_JOB_NAME-$CI_COMMIT_SHORT_SHA"
|
||||
paths:
|
||||
- ci/ui_test_records/
|
||||
- test_ui_report
|
||||
- tests/ui_tests/screens/
|
||||
- tests/ui_tests/fixtures.suggestion.json
|
||||
- tests/ui_tests/fixtures.results.json
|
||||
- tests/trezor.log
|
||||
- tests/junit.xml
|
||||
- core/.coverage.*
|
||||
reports:
|
||||
junit: tests/junit.xml
|
||||
expire_in: 1 week
|
||||
when: always
|
||||
|
||||
core click R test spanish:
|
||||
stage: test
|
||||
<<: *gitlab_caching
|
||||
needs:
|
||||
- core unix frozen R debug build
|
||||
variables:
|
||||
TREZOR_PROFILING: "1" # so that we get coverage data
|
||||
TREZOR_MODEL: "R"
|
||||
TEST_LANG: "es" # spanish
|
||||
only:
|
||||
- schedules # nightly build
|
||||
- /translations/ # translations branches
|
||||
script:
|
||||
- $NIX_SHELL --run "poetry run make -C core test_emu_click_ui | ts -s"
|
||||
after_script:
|
||||
- mv core/src/.coverage core/.coverage.test_click
|
||||
- mv tests/ui_tests/reports/test/ test_ui_report
|
||||
- $NIX_SHELL --run "poetry run python ci/prepare_ui_artifacts.py | ts -s"
|
||||
- diff -u tests/ui_tests/fixtures.json tests/ui_tests/fixtures.suggestion.json
|
||||
artifacts:
|
||||
name: "$CI_JOB_NAME-$CI_COMMIT_SHORT_SHA"
|
||||
paths:
|
||||
- ci/ui_test_records/
|
||||
- test_ui_report
|
||||
- tests/ui_tests/screens/
|
||||
- tests/ui_tests/fixtures.suggestion.json
|
||||
- tests/ui_tests/fixtures.results.json
|
||||
- tests/trezor.log
|
||||
- tests/junit.xml
|
||||
- core/.coverage.*
|
||||
reports:
|
||||
junit: tests/junit.xml
|
||||
expire_in: 1 week
|
||||
when: always
|
||||
|
||||
|
||||
##END_CLICK_TESTS
|
@ -161,7 +161,7 @@ message EndSession {
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: change language and/or label of the device
|
||||
* Request: change some property of the device, e.g. label or homescreen
|
||||
* @start
|
||||
* @next Success
|
||||
* @next Failure
|
||||
@ -180,6 +180,47 @@ message ApplySettings {
|
||||
optional bool hide_passphrase_from_host = 11; // do not show passphrase coming from host
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: change the device language via translation data.
|
||||
* Does not send the translation data itself, as they are too large for one message.
|
||||
* Device will request the translation data in chunks.
|
||||
* @start
|
||||
* @next TranslationDataRequest
|
||||
* @next Failure
|
||||
*/
|
||||
message ChangeLanguage {
|
||||
// byte length of the whole translation blob (set to 0 for default language - english)
|
||||
required uint32 data_length = 1;
|
||||
// Prompt the user on screen.
|
||||
// In certain conditions (such as freshly installed device), the confirmation prompt
|
||||
// is not mandatory. Setting show_display=false will skip the prompt if that's
|
||||
// the case. If the device does not allow skipping the prompt, a request with
|
||||
// show_display=false will return a failure. (This way the host can safely try
|
||||
// to change the language without invoking a prompt.)
|
||||
// Setting show_display to true will always show the prompt.
|
||||
// Leaving the option unset will show the prompt only when necessary.
|
||||
optional bool show_display = 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Device asks for more data from transaction payload.
|
||||
* @end
|
||||
* @next TranslationDataAck
|
||||
*/
|
||||
message TranslationDataRequest {
|
||||
required uint32 data_length = 1; // Number of bytes being requested
|
||||
required uint32 data_offset = 2; // Offset of the first byte being requested
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Translation payload data.
|
||||
* @next TranslationDataRequest
|
||||
* @next Success
|
||||
*/
|
||||
message TranslationDataAck {
|
||||
required bytes data_chunk = 1; // Bytes from translation payload
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: set flags of the device
|
||||
* @start
|
||||
@ -487,11 +528,17 @@ message CancelAuthorization {
|
||||
* @next Success
|
||||
*/
|
||||
message RebootToBootloader {
|
||||
// Action to be performed after rebooting to bootloader
|
||||
optional BootCommand boot_command = 1 [default=STOP_AND_WAIT];
|
||||
// Firmware header to be flashed after rebooting to bootloader
|
||||
optional bytes firmware_header = 2;
|
||||
// Length of language blob to be installed before upgrading firmware
|
||||
optional uint32 language_data_length = 3 [default=0];
|
||||
|
||||
enum BootCommand {
|
||||
// Go to bootloader menu
|
||||
STOP_AND_WAIT = 0;
|
||||
// Connect to host and wait for firmware update
|
||||
INSTALL_UPGRADE = 1;
|
||||
}
|
||||
}
|
||||
|
@ -124,6 +124,9 @@ enum MessageType {
|
||||
MessageType_UnlockBootloader = 96 [(bitcoin_only) = true, (wire_in) = true];
|
||||
MessageType_AuthenticateDevice = 97 [(bitcoin_only) = true, (wire_out) = true];
|
||||
MessageType_AuthenticityProof = 98 [(bitcoin_only) = true, (wire_in) = true];
|
||||
MessageType_ChangeLanguage = 990 [(bitcoin_only) = true, (wire_in) = true];
|
||||
MessageType_TranslationDataRequest = 991 [(bitcoin_only) = true, (wire_out) = true];
|
||||
MessageType_TranslationDataAck = 992 [(bitcoin_only) = true, (wire_in) = true];
|
||||
|
||||
MessageType_SetU2FCounter = 63 [(wire_in) = true];
|
||||
MessageType_GetNextU2FCounter = 80 [(wire_in) = true];
|
||||
|
@ -116,6 +116,10 @@ def ascii_filter(s: str) -> str:
|
||||
return re.sub("[^ -\x7e]", "_", s)
|
||||
|
||||
|
||||
def utf8_str_filter(s: str) -> str:
|
||||
return '"' + repr(s)[1:-1] + '"'
|
||||
|
||||
|
||||
def make_support_filter(
|
||||
support_info: SupportInfo,
|
||||
) -> Callable[[str, Coins], Iterator[Coin]]:
|
||||
@ -126,6 +130,7 @@ def make_support_filter(
|
||||
|
||||
|
||||
MAKO_FILTERS = {
|
||||
"utf8_str": utf8_str_filter,
|
||||
"c_str": c_str_filter,
|
||||
"ascii": ascii_filter,
|
||||
"black_repr": black_repr_filter,
|
||||
|
@ -34,7 +34,8 @@ TREZOR_MODEL ?= T
|
||||
TREZOR_MEMPERF ?= 0
|
||||
ADDRESS_SANITIZER ?= 0
|
||||
CMAKELISTS ?= 0
|
||||
PYTEST_TIMEOUT ?= 400
|
||||
PYTEST_TIMEOUT ?= 500
|
||||
TEST_LANG ?= "en"
|
||||
|
||||
# OpenOCD interface default. Alternative: ftdi/olimex-arm-usb-tiny-h
|
||||
OPENOCD_INTERFACE ?= stlink
|
||||
@ -101,11 +102,12 @@ test_rust: ## run rs unit tests
|
||||
-- --test-threads=1 --nocapture
|
||||
|
||||
test_emu: ## run selected device tests from python-trezor
|
||||
$(EMU_TEST) $(PYTEST) $(TESTPATH)/device_tests $(TESTOPTS)
|
||||
$(EMU_TEST) $(PYTEST) $(TESTPATH)/device_tests $(TESTOPTS) --lang=$(TEST_LANG)
|
||||
|
||||
test_emu_multicore: ## run device tests using multiple cores
|
||||
$(PYTEST) -n $(MULTICORE) $(TESTPATH)/device_tests $(TESTOPTS) --timeout $(PYTEST_TIMEOUT) \
|
||||
--control-emulators --model=core --random-order-seed=$(shell echo $$RANDOM)
|
||||
--control-emulators --model=core --random-order-seed=$(shell echo $$RANDOM) \
|
||||
--lang=$(TEST_LANG)
|
||||
|
||||
test_emu_monero: ## run selected monero device tests from monero-agent
|
||||
cd tests ; $(EMU_TEST) ./run_tests_device_emu_monero.sh $(TESTOPTS)
|
||||
@ -119,31 +121,33 @@ test_emu_fido2: ## run fido2 device tests
|
||||
$(EMU_TEST) --slip0014 $(PYTEST) --maxfail=5 --sim tests/standard/ --vendor trezor $(TESTOPTS)
|
||||
|
||||
test_emu_click: ## run click tests
|
||||
$(EMU_TEST) $(PYTEST) $(TESTPATH)/click_tests $(TESTOPTS)
|
||||
$(EMU_TEST) $(PYTEST) $(TESTPATH)/click_tests $(TESTOPTS) --lang=$(TEST_LANG)
|
||||
|
||||
test_emu_click_ui: ## run click tests with UI testing
|
||||
$(EMU_TEST) $(PYTEST) $(TESTPATH)/click_tests $(TESTOPTS) \
|
||||
--ui=test --ui-check-missing --do-master-diff
|
||||
--ui=test --ui-check-missing --do-master-diff --lang=$(TEST_LANG)
|
||||
|
||||
test_emu_persistence: ## run persistence tests
|
||||
$(PYTEST) $(TESTPATH)/persistence_tests $(TESTOPTS)
|
||||
$(PYTEST) $(TESTPATH)/persistence_tests $(TESTOPTS) --lang=$(TEST_LANG)
|
||||
|
||||
test_emu_persistence_ui: ## run persistence tests with UI testing
|
||||
$(PYTEST) $(TESTPATH)/persistence_tests $(TESTOPTS) \
|
||||
--ui=test --ui-check-missing --do-master-diff
|
||||
--ui=test --ui-check-missing --do-master-diff --lang=$(TEST_LANG)
|
||||
|
||||
test_emu_ui: ## run ui integration tests
|
||||
$(EMU_TEST) $(PYTEST) $(TESTPATH)/device_tests $(TESTOPTS) \
|
||||
--ui=test --ui-check-missing --record-text-layout --do-master-diff
|
||||
--ui=test --ui-check-missing --record-text-layout --do-master-diff \
|
||||
--lang=$(TEST_LANG)
|
||||
|
||||
test_emu_ui_multicore: ## run ui integration tests using multiple cores
|
||||
$(PYTEST) -n $(MULTICORE) $(TESTPATH)/device_tests $(TESTOPTS) --timeout $(PYTEST_TIMEOUT) \
|
||||
--ui=test --ui-check-missing --record-text-layout --do-master-diff \
|
||||
--control-emulators --model=core --random-order-seed=$(shell echo $$RANDOM)
|
||||
--control-emulators --model=core --random-order-seed=$(shell echo $$RANDOM) \
|
||||
--lang=$(TEST_LANG)
|
||||
|
||||
test_emu_ui_record: ## record and hash screens for ui integration tests
|
||||
$(EMU_TEST) $(PYTEST) $(TESTPATH)/device_tests $(TESTOPTS) \
|
||||
--ui=record --ui-check-missing --do-master-diff
|
||||
--ui=record --ui-check-missing --do-master-diff --lang=$(TEST_LANG)
|
||||
|
||||
test_emu_ui_record_multicore: ## quickly record all screens
|
||||
make test_emu_ui_multicore || echo "All errors are recorded in fixtures.json"
|
||||
|
@ -204,6 +204,7 @@ SOURCE_MOD += [
|
||||
|
||||
CPPDEFINES_MOD += [
|
||||
'TREZOR_UI2',
|
||||
'TRANSLATIONS',
|
||||
]
|
||||
|
||||
if TREZOR_MODEL not in ('1', ):
|
||||
@ -717,6 +718,7 @@ def cargo_build():
|
||||
if BITCOIN_ONLY == '1':
|
||||
features.append('bitcoin_only')
|
||||
features.append('ui')
|
||||
features.append('translations')
|
||||
if PYOPT == '0':
|
||||
features.append('ui_debug')
|
||||
|
||||
|
@ -219,6 +219,7 @@ elif TREZOR_MODEL in ('R', ):
|
||||
|
||||
CPPDEFINES_MOD += [
|
||||
'TREZOR_UI2',
|
||||
'TRANSLATIONS',
|
||||
]
|
||||
if TREZOR_MODEL not in ('1', ):
|
||||
CPPDEFINES_MOD += [
|
||||
@ -372,6 +373,7 @@ SOURCE_MICROPYTHON = [
|
||||
]
|
||||
|
||||
SOURCE_UNIX = [
|
||||
'embed/trezorhal/unix/translations.c',
|
||||
'embed/trezorhal/unix/common.c',
|
||||
'embed/trezorhal/unix/display-unix.c',
|
||||
'embed/trezorhal/unix/flash.c',
|
||||
@ -795,6 +797,7 @@ def cargo_build():
|
||||
if BITCOIN_ONLY == '1':
|
||||
features.append('bitcoin_only')
|
||||
features.append('ui')
|
||||
features.append('translations')
|
||||
if PYOPT == '0':
|
||||
features.append('debug')
|
||||
if DMA2D:
|
||||
|
@ -295,17 +295,18 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(
|
||||
mod_trezorutils_reboot_to_bootloader_obj, 0, 2,
|
||||
mod_trezorutils_reboot_to_bootloader);
|
||||
|
||||
/// def check_firmware_header(
|
||||
/// header : bytes
|
||||
/// ) -> dict:
|
||||
/// """
|
||||
/// Checks firmware image and vendor header and returns
|
||||
/// { "version": (major, minor, patch),
|
||||
/// "vendor": string,
|
||||
/// "fingerprint": bytes,
|
||||
/// "hash": bytes
|
||||
/// }
|
||||
/// """
|
||||
/// VersionTuple = Tuple[int, int, int, int]
|
||||
///
|
||||
/// class FirmwareHeaderInfo(NamedTuple):
|
||||
/// version: VersionTuple
|
||||
/// vendor: str
|
||||
/// fingerprint: bytes
|
||||
/// hash: bytes
|
||||
///
|
||||
/// mock:global
|
||||
///
|
||||
/// def check_firmware_header(header : bytes) -> FirmwareHeaderInfo:
|
||||
/// """Parses incoming firmware header and returns information about it."""
|
||||
STATIC mp_obj_t mod_trezorutils_check_firmware_header(mp_obj_t header) {
|
||||
mp_buffer_info_t header_buf = {0};
|
||||
mp_get_buffer_raise(header, &header_buf, MP_BUFFER_READ);
|
||||
@ -313,23 +314,18 @@ STATIC mp_obj_t mod_trezorutils_check_firmware_header(mp_obj_t header) {
|
||||
firmware_header_info_t info;
|
||||
|
||||
if (sectrue == check_firmware_header(header_buf.buf, header_buf.len, &info)) {
|
||||
mp_obj_t version[3] = {mp_obj_new_int(info.ver_major),
|
||||
mp_obj_new_int(info.ver_minor),
|
||||
mp_obj_new_int(info.ver_patch)};
|
||||
mp_obj_t version[4] = {
|
||||
mp_obj_new_int(info.ver_major), mp_obj_new_int(info.ver_minor),
|
||||
mp_obj_new_int(info.ver_patch), mp_obj_new_int(info.ver_build)};
|
||||
|
||||
mp_obj_t result = mp_obj_new_dict(4);
|
||||
mp_obj_dict_store(result, MP_ROM_QSTR(MP_QSTR_version),
|
||||
mp_obj_new_tuple(MP_ARRAY_SIZE(version), version));
|
||||
mp_obj_dict_store(
|
||||
result, MP_ROM_QSTR(MP_QSTR_vendor),
|
||||
mp_obj_new_str_copy(&mp_type_str, info.vstr, info.vstr_len));
|
||||
mp_obj_dict_store(
|
||||
result, MP_ROM_QSTR(MP_QSTR_fingerprint),
|
||||
mp_obj_new_bytes(info.fingerprint, sizeof(info.fingerprint)));
|
||||
mp_obj_dict_store(result, MP_ROM_QSTR(MP_QSTR_hash),
|
||||
mp_obj_new_bytes(info.hash, sizeof(info.hash)));
|
||||
|
||||
return result;
|
||||
static const qstr fields[4] = {MP_QSTR_version, MP_QSTR_vendor,
|
||||
MP_QSTR_fingerprint, MP_QSTR_hash};
|
||||
const mp_obj_t values[4] = {
|
||||
mp_obj_new_tuple(MP_ARRAY_SIZE(version), version),
|
||||
mp_obj_new_str_copy(&mp_type_str, info.vstr, info.vstr_len),
|
||||
mp_obj_new_bytes(info.fingerprint, sizeof(info.fingerprint)),
|
||||
mp_obj_new_bytes(info.hash, sizeof(info.hash))};
|
||||
return mp_obj_new_attrtuple(fields, MP_ARRAY_SIZE(fields), values);
|
||||
}
|
||||
|
||||
mp_raise_ValueError("Invalid value.");
|
||||
@ -370,14 +366,16 @@ STATIC mp_obj_str_t mod_trezorutils_full_name_obj = {
|
||||
sizeof(MODEL_FULL_NAME) - 1,
|
||||
(const byte *)MODEL_FULL_NAME};
|
||||
|
||||
STATIC mp_obj_tuple_t mod_trezorutils_version_obj = {
|
||||
{&mp_type_tuple},
|
||||
4,
|
||||
{MP_OBJ_NEW_SMALL_INT(VERSION_MAJOR), MP_OBJ_NEW_SMALL_INT(VERSION_MINOR),
|
||||
MP_OBJ_NEW_SMALL_INT(VERSION_PATCH), MP_OBJ_NEW_SMALL_INT(VERSION_BUILD)}};
|
||||
|
||||
/// SCM_REVISION: bytes
|
||||
/// """Git commit hash of the firmware."""
|
||||
/// VERSION_MAJOR: int
|
||||
/// """Major version."""
|
||||
/// VERSION_MINOR: int
|
||||
/// """Minor version."""
|
||||
/// VERSION_PATCH: int
|
||||
/// """Patch version."""
|
||||
/// VERSION: VersionTuple
|
||||
/// """Firmware version as a tuple (major, minor, patch, build)."""
|
||||
/// USE_SD_CARD: bool
|
||||
/// """Whether the hardware supports SD card."""
|
||||
/// USE_BACKLIGHT: bool
|
||||
@ -419,9 +417,7 @@ STATIC const mp_rom_map_elem_t mp_module_trezorutils_globals_table[] = {
|
||||
// various built-in constants
|
||||
{MP_ROM_QSTR(MP_QSTR_SCM_REVISION),
|
||||
MP_ROM_PTR(&mod_trezorutils_revision_obj)},
|
||||
{MP_ROM_QSTR(MP_QSTR_VERSION_MAJOR), MP_ROM_INT(VERSION_MAJOR)},
|
||||
{MP_ROM_QSTR(MP_QSTR_VERSION_MINOR), MP_ROM_INT(VERSION_MINOR)},
|
||||
{MP_ROM_QSTR(MP_QSTR_VERSION_PATCH), MP_ROM_INT(VERSION_PATCH)},
|
||||
{MP_ROM_QSTR(MP_QSTR_VERSION), MP_ROM_PTR(&mod_trezorutils_version_obj)},
|
||||
#ifdef USE_SD_CARD
|
||||
{MP_ROM_QSTR(MP_QSTR_USE_SD_CARD), mp_const_true},
|
||||
#else
|
||||
|
@ -18,6 +18,7 @@
|
||||
*/
|
||||
|
||||
#include "librust.h"
|
||||
#include "librust_fonts.h"
|
||||
#include "py/runtime.h"
|
||||
|
||||
#if MICROPY_PY_TREZORUI2
|
||||
@ -27,3 +28,7 @@ MP_REGISTER_MODULE(MP_QSTR_trezorui2, mp_module_trezorui2);
|
||||
#if MICROPY_PY_TREZORPROTO
|
||||
MP_REGISTER_MODULE(MP_QSTR_trezorproto, mp_module_trezorproto);
|
||||
#endif
|
||||
|
||||
#if MICROPY_PY_TREZORTRANSLATE
|
||||
MP_REGISTER_MODULE(MP_QSTR_trezortranslate, mp_module_trezortranslate);
|
||||
#endif
|
||||
|
@ -163,6 +163,7 @@
|
||||
#define MICROPY_PY_TREZORUI (1)
|
||||
#define MICROPY_PY_TREZORUTILS (1)
|
||||
#define MICROPY_PY_TREZORPROTO (1)
|
||||
#define MICROPY_PY_TREZORTRANSLATE (1)
|
||||
#define MICROPY_PY_TREZORUI2 (1)
|
||||
|
||||
#ifdef SYSTEM_VIEW
|
||||
|
@ -99,9 +99,9 @@ void display_text_render_buffer(const char *text, int textlen, int font,
|
||||
int baseline = font_baseline(font);
|
||||
|
||||
// render glyphs
|
||||
for (int c_idx = 0; c_idx < textlen; c_idx++) {
|
||||
const uint8_t *g = font_get_glyph(font, (uint8_t)text[c_idx]);
|
||||
if (!g) continue;
|
||||
font_glyph_iter_t iter = font_glyph_iter_init(font, (uint8_t *)text, textlen);
|
||||
const uint8_t *g = NULL;
|
||||
while (font_next_glyph(&iter, &g)) {
|
||||
const uint8_t w = g[0]; // width
|
||||
const uint8_t h = g[1]; // height
|
||||
const uint8_t adv = g[2]; // advance
|
||||
@ -171,9 +171,9 @@ static void display_text_render(int x, int y, const char *text, int textlen,
|
||||
set_color_table(colortable, fgcolor, bgcolor);
|
||||
|
||||
// render glyphs
|
||||
for (int c_idx = 0; c_idx < textlen; c_idx++) {
|
||||
const uint8_t *g = font_get_glyph(font, (uint8_t)text[c_idx]);
|
||||
if (!g) continue;
|
||||
font_glyph_iter_t iter = font_glyph_iter_init(font, (uint8_t *)text, textlen);
|
||||
const uint8_t *g = NULL;
|
||||
while (font_next_glyph(&iter, &g)) {
|
||||
const uint8_t w = g[0]; // width
|
||||
const uint8_t h = g[1]; // height
|
||||
const uint8_t adv = g[2]; // advance
|
||||
@ -234,9 +234,9 @@ static void display_text_render(int x, int y, const char *text, int textlen,
|
||||
set_color_table(colortable, fgcolor, bgcolor);
|
||||
|
||||
// render glyphs
|
||||
for (int i = 0; i < textlen; i++) {
|
||||
const uint8_t *g = font_get_glyph(font, (uint8_t)text[i]);
|
||||
if (!g) continue;
|
||||
font_glyph_iter_t iter = font_glyph_iter_init(font, (uint8_t *)text, textlen);
|
||||
const uint8_t *g = NULL;
|
||||
while (font_next_glyph(&iter, &g)) {
|
||||
const uint8_t w = g[0]; // width
|
||||
const uint8_t h = g[1]; // height
|
||||
const uint8_t adv = g[2]; // advance
|
||||
|
@ -102,7 +102,7 @@
|
||||
/* } */ static const uint8_t Font_PixelOperator_Bold_8_glyph_125[] = { 5, 7, 6, 0, 7, 225, 140, 51, 27, 128 };
|
||||
/* ~ */ static const uint8_t Font_PixelOperator_Bold_8_glyph_126[] = { 7, 2, 8, 0, 7, 119, 184 };
|
||||
|
||||
const uint8_t Font_PixelOperator_Bold_8_glyph_nonprintable[] = { 6, 7, 7, 0, 7, 132, 207, 57, 207, 252, 255 };
|
||||
/* ? */ const uint8_t Font_PixelOperator_Bold_8_glyph_nonprintable[] = { 6, 7, 7, 0, 7, 132, 207, 57, 207, 252, 255 };
|
||||
|
||||
const uint8_t * const Font_PixelOperator_Bold_8[126 + 1 - 32] = {
|
||||
Font_PixelOperator_Bold_8_glyph_32,
|
||||
|
@ -102,7 +102,7 @@
|
||||
/* } */ static const uint8_t Font_PixelOperator_Regular_8_glyph_125[] = { 4, 7, 6, 0, 7, 194, 33, 34, 192 };
|
||||
/* ~ */ static const uint8_t Font_PixelOperator_Regular_8_glyph_126[] = { 6, 2, 7, 0, 7, 102, 96 };
|
||||
|
||||
const uint8_t Font_PixelOperator_Regular_8_glyph_nonprintable[] = { 5, 7, 6, 0, 7, 139, 189, 221, 255, 127 };
|
||||
/* ? */ const uint8_t Font_PixelOperator_Regular_8_glyph_nonprintable[] = { 5, 7, 6, 0, 7, 139, 189, 221, 255, 127 };
|
||||
|
||||
const uint8_t * const Font_PixelOperator_Regular_8[126 + 1 - 32] = {
|
||||
Font_PixelOperator_Regular_8_glyph_32,
|
||||
|
@ -105,7 +105,7 @@
|
||||
/* } */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_125[] = { 4, 7, 7, 0, 7, 194, 33, 34, 192 };
|
||||
/* ~ */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_126[] = { 6, 2, 7, 0, 7, 102, 96 };
|
||||
|
||||
const uint8_t Font_PixelOperatorMono_Regular_8_glyph_nonprintable[] = { 5, 7, 7, 0, 7, 139, 189, 221, 255, 127 };
|
||||
/* ? */ const uint8_t Font_PixelOperatorMono_Regular_8_glyph_nonprintable[] = { 5, 7, 7, 0, 7, 139, 189, 221, 255, 127 };
|
||||
|
||||
const uint8_t * const Font_PixelOperatorMono_Regular_8[126 + 1 - 32] = {
|
||||
Font_PixelOperatorMono_Regular_8_glyph_32,
|
||||
|
@ -102,7 +102,7 @@
|
||||
/* } */ static const uint8_t Font_Roboto_Bold_20_glyph_125[] = { 7, 20, 7, 0, 16, 180, 3, 0, 0, 248, 79, 0, 0, 176, 239, 0, 0, 64, 255, 3, 0, 32, 255, 5, 0, 32, 255, 6, 0, 32, 255, 6, 0, 16, 255, 7, 0, 0, 252, 45, 0, 0, 210, 255, 2, 0, 210, 255, 2, 0, 252, 45, 0, 16, 255, 7, 0, 32, 255, 6, 0, 32, 255, 6, 0, 32, 255, 5, 0, 64, 255, 3, 0, 176, 239, 0, 0, 248, 79, 0, 0, 180, 3, 0, 0 };
|
||||
/* ~ */ static const uint8_t Font_Roboto_Bold_20_glyph_126[] = { 11, 5, 13, 1, 8, 96, 254, 60, 0, 162, 9, 245, 255, 255, 6, 249, 12, 252, 108, 252, 255, 255, 6, 237, 3, 128, 255, 175, 0, 0, 0, 0, 65, 2, 0 };
|
||||
|
||||
const uint8_t Font_Roboto_Bold_20_glyph_nonprintable[] = { 10, 14, 10, 0, 14, 255, 40, 16, 163, 255, 79, 0, 0, 0, 247, 11, 16, 137, 0, 240, 8, 160, 255, 5, 192, 255, 255, 255, 4, 208, 255, 255, 175, 0, 243, 255, 255, 10, 16, 253, 255, 239, 0, 209, 255, 255, 143, 0, 251, 255, 255, 111, 16, 255, 255, 255, 255, 255, 255, 255, 255, 159, 65, 255, 255, 255, 47, 0, 252, 255, 255, 143, 64, 255, 255 };
|
||||
/* ? */ const uint8_t Font_Roboto_Bold_20_glyph_nonprintable[] = { 10, 14, 10, 0, 14, 255, 40, 16, 163, 255, 79, 0, 0, 0, 247, 11, 16, 137, 0, 240, 8, 160, 255, 5, 192, 255, 255, 255, 4, 208, 255, 255, 175, 0, 243, 255, 255, 10, 16, 253, 255, 239, 0, 209, 255, 255, 143, 0, 251, 255, 255, 111, 16, 255, 255, 255, 255, 255, 255, 255, 255, 159, 65, 255, 255, 255, 47, 0, 252, 255, 255, 143, 64, 255, 255 };
|
||||
|
||||
const uint8_t * const Font_Roboto_Bold_20[126 + 1 - 32] = {
|
||||
Font_Roboto_Bold_20_glyph_32,
|
||||
|
@ -102,7 +102,7 @@
|
||||
/* } */ static const uint8_t Font_Roboto_Regular_20_glyph_125[] = { 7, 20, 7, 0, 16, 152, 1, 0, 0, 228, 29, 0, 0, 48, 159, 0, 0, 0, 238, 0, 0, 0, 252, 0, 0, 0, 252, 0, 0, 0, 252, 0, 0, 0, 250, 2, 0, 0, 244, 27, 0, 0, 112, 255, 1, 0, 209, 143, 0, 0, 248, 5, 0, 0, 251, 1, 0, 0, 252, 0, 0, 0, 252, 0, 0, 0, 252, 0, 0, 0, 222, 0, 0, 80, 143, 0, 0, 229, 29, 0, 0, 152, 1, 0, 0 };
|
||||
/* ~ */ static const uint8_t Font_Roboto_Regular_20_glyph_126[] = { 12, 4, 14, 1, 8, 64, 253, 109, 0, 32, 59, 243, 173, 254, 43, 161, 31, 249, 1, 144, 255, 255, 7, 50, 0, 0, 115, 56, 0 };
|
||||
|
||||
const uint8_t Font_Roboto_Regular_20_glyph_nonprintable[] = { 9, 14, 9, 0, 14, 255, 57, 16, 213, 255, 111, 16, 69, 16, 254, 14, 242, 255, 8, 247, 125, 250, 255, 12, 245, 255, 255, 255, 10, 248, 255, 255, 239, 1, 254, 255, 255, 46, 160, 255, 255, 255, 3, 250, 255, 255, 191, 96, 255, 255, 255, 143, 160, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 159, 160, 255, 255, 255, 159, 160, 255, 255 };
|
||||
/* ? */ const uint8_t Font_Roboto_Regular_20_glyph_nonprintable[] = { 9, 14, 9, 0, 14, 255, 57, 16, 213, 255, 111, 16, 69, 16, 254, 14, 242, 255, 8, 247, 125, 250, 255, 12, 245, 255, 255, 255, 10, 248, 255, 255, 239, 1, 254, 255, 255, 46, 160, 255, 255, 255, 3, 250, 255, 255, 191, 96, 255, 255, 255, 143, 160, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 159, 160, 255, 255, 255, 159, 160, 255, 255 };
|
||||
|
||||
const uint8_t * const Font_Roboto_Regular_20[126 + 1 - 32] = {
|
||||
Font_Roboto_Regular_20_glyph_32,
|
||||
|
@ -102,7 +102,7 @@
|
||||
/* } */ static const uint8_t Font_RobotoMono_Medium_20_glyph_125[] = { 7, 20, 12, 3, 16, 117, 1, 0, 0, 249, 30, 0, 0, 112, 175, 0, 0, 16, 255, 0, 0, 0, 255, 2, 0, 0, 255, 3, 0, 0, 255, 3, 0, 0, 254, 4, 0, 0, 248, 28, 0, 0, 144, 255, 11, 0, 144, 255, 10, 0, 248, 28, 0, 0, 254, 4, 0, 0, 255, 3, 0, 0, 255, 3, 0, 0, 255, 2, 0, 32, 255, 0, 0, 144, 159, 0, 0, 250, 29, 0, 0, 100, 0, 0, 0 };
|
||||
/* ~ */ static const uint8_t Font_RobotoMono_Medium_20_glyph_126[] = { 12, 5, 12, 0, 8, 64, 237, 76, 0, 32, 22, 242, 255, 255, 8, 144, 63, 248, 6, 229, 239, 253, 14, 149, 0, 32, 251, 239, 3, 0, 0, 0, 16, 2, 0 };
|
||||
|
||||
const uint8_t Font_RobotoMono_Medium_20_glyph_nonprintable[] = { 10, 16, 12, 1, 15, 255, 207, 153, 251, 255, 239, 3, 0, 16, 250, 47, 0, 102, 2, 208, 12, 176, 255, 14, 128, 174, 250, 255, 47, 112, 255, 255, 255, 13, 160, 255, 255, 255, 3, 242, 255, 255, 79, 16, 253, 255, 255, 5, 209, 255, 255, 255, 0, 251, 255, 255, 255, 34, 254, 255, 255, 255, 255, 255, 255, 255, 255, 153, 255, 255, 255, 175, 0, 250, 255, 255, 207, 0, 253, 255, 255, 255, 255, 255, 255 };
|
||||
/* ? */ const uint8_t Font_RobotoMono_Medium_20_glyph_nonprintable[] = { 10, 16, 12, 1, 15, 255, 207, 153, 251, 255, 239, 3, 0, 16, 250, 47, 0, 102, 2, 208, 12, 176, 255, 14, 128, 174, 250, 255, 47, 112, 255, 255, 255, 13, 160, 255, 255, 255, 3, 242, 255, 255, 79, 16, 253, 255, 255, 5, 209, 255, 255, 255, 0, 251, 255, 255, 255, 34, 254, 255, 255, 255, 255, 255, 255, 255, 255, 153, 255, 255, 255, 175, 0, 250, 255, 255, 207, 0, 253, 255, 255, 255, 255, 255, 255 };
|
||||
|
||||
const uint8_t * const Font_RobotoMono_Medium_20[126 + 1 - 32] = {
|
||||
Font_RobotoMono_Medium_20_glyph_32,
|
||||
|
@ -102,7 +102,7 @@
|
||||
/* } */ static const uint8_t Font_TTHoves_Bold_17_glyph_125[] = { 7, 17, 7, 0, 13, 65, 3, 0, 0, 246, 223, 1, 0, 246, 255, 10, 0, 16, 254, 13, 0, 0, 251, 13, 0, 0, 251, 13, 0, 0, 251, 14, 0, 0, 248, 207, 0, 0, 128, 255, 0, 0, 210, 255, 0, 0, 249, 127, 0, 0, 251, 14, 0, 0, 251, 13, 0, 0, 251, 13, 0, 81, 255, 12, 0, 246, 255, 8, 0, 246, 158, 0, 0 };
|
||||
/* ~ */ static const uint8_t Font_TTHoves_Bold_17_glyph_126[] = { 10, 4, 10, 0, 7, 0, 98, 2, 0, 0, 128, 255, 159, 246, 143, 242, 255, 255, 255, 79, 245, 63, 180, 239, 7 };
|
||||
|
||||
const uint8_t Font_TTHoves_Bold_17_glyph_nonprintable[] = { 9, 12, 9, 0, 12, 255, 40, 32, 230, 255, 63, 0, 0, 16, 254, 10, 32, 39, 0, 248, 90, 213, 207, 0, 246, 255, 255, 95, 0, 249, 255, 223, 2, 96, 255, 255, 95, 0, 250, 255, 255, 47, 64, 255, 255, 255, 239, 238, 255, 255, 255, 63, 99, 255, 255, 255, 15, 48, 255, 255, 255, 15, 48, 255, 255 };
|
||||
/* ? */ const uint8_t Font_TTHoves_Bold_17_glyph_nonprintable[] = { 9, 12, 9, 0, 12, 255, 40, 32, 230, 255, 63, 0, 0, 16, 254, 10, 32, 39, 0, 248, 90, 213, 207, 0, 246, 255, 255, 95, 0, 249, 255, 223, 2, 96, 255, 255, 95, 0, 250, 255, 255, 47, 64, 255, 255, 255, 239, 238, 255, 255, 255, 63, 99, 255, 255, 255, 15, 48, 255, 255, 255, 15, 48, 255, 255 };
|
||||
|
||||
const uint8_t * const Font_TTHoves_Bold_17[126 + 1 - 32] = {
|
||||
Font_TTHoves_Bold_17_glyph_32,
|
||||
|
@ -102,7 +102,7 @@
|
||||
/* } */ static const uint8_t Font_TTHoves_DemiBold_21_glyph_125[] = { 7, 20, 8, 0, 16, 97, 37, 0, 0, 244, 255, 8, 0, 244, 255, 47, 0, 0, 248, 79, 0, 0, 244, 95, 0, 0, 244, 95, 0, 0, 244, 95, 0, 0, 244, 95, 0, 0, 242, 175, 1, 0, 160, 255, 12, 0, 0, 249, 12, 0, 208, 255, 10, 0, 243, 143, 0, 0, 244, 95, 0, 0, 244, 95, 0, 0, 244, 95, 0, 0, 245, 95, 0, 97, 252, 79, 0, 244, 255, 14, 0, 244, 207, 3, 0 };
|
||||
/* ~ */ static const uint8_t Font_TTHoves_DemiBold_21_glyph_126[] = { 12, 5, 12, 0, 8, 0, 215, 158, 1, 144, 73, 112, 255, 255, 61, 245, 111, 224, 255, 254, 255, 255, 47, 241, 143, 64, 254, 255, 9, 64, 37, 0, 113, 72, 0 };
|
||||
|
||||
const uint8_t Font_TTHoves_DemiBold_21_glyph_nonprintable[] = { 11, 15, 11, 0, 15, 255, 93, 2, 98, 254, 255, 175, 0, 0, 0, 192, 255, 30, 0, 100, 2, 16, 255, 11, 80, 255, 63, 0, 252, 157, 217, 255, 127, 0, 250, 255, 255, 255, 30, 0, 253, 255, 255, 159, 0, 80, 255, 255, 255, 9, 0, 248, 255, 255, 255, 2, 176, 255, 255, 255, 255, 0, 244, 255, 255, 255, 255, 85, 249, 255, 255, 255, 255, 255, 255, 255, 255, 255, 239, 17, 245, 255, 255, 255, 239, 0, 244, 255, 255, 255, 239, 0, 244, 255, 255 };
|
||||
/* ? */ const uint8_t Font_TTHoves_DemiBold_21_glyph_nonprintable[] = { 11, 15, 11, 0, 15, 255, 93, 2, 98, 254, 255, 175, 0, 0, 0, 192, 255, 30, 0, 100, 2, 16, 255, 11, 80, 255, 63, 0, 252, 157, 217, 255, 127, 0, 250, 255, 255, 255, 30, 0, 253, 255, 255, 159, 0, 80, 255, 255, 255, 9, 0, 248, 255, 255, 255, 2, 176, 255, 255, 255, 255, 0, 244, 255, 255, 255, 255, 85, 249, 255, 255, 255, 255, 255, 255, 255, 255, 255, 239, 17, 245, 255, 255, 255, 239, 0, 244, 255, 255, 255, 239, 0, 244, 255, 255 };
|
||||
|
||||
const uint8_t * const Font_TTHoves_DemiBold_21[126 + 1 - 32] = {
|
||||
Font_TTHoves_DemiBold_21_glyph_32,
|
||||
|
@ -102,7 +102,7 @@
|
||||
/* } */ static const uint8_t Font_TTHoves_Regular_21_glyph_125[] = { 6, 20, 7, 0, 16, 81, 3, 0, 244, 143, 0, 0, 250, 0, 0, 247, 1, 0, 247, 1, 0, 247, 1, 0, 247, 1, 0, 247, 1, 0, 246, 3, 0, 209, 77, 0, 48, 126, 0, 243, 25, 0, 247, 1, 0, 247, 1, 0, 247, 1, 0, 247, 1, 0, 247, 1, 0, 247, 1, 97, 237, 0, 244, 77, 0 };
|
||||
/* ~ */ static const uint8_t Font_TTHoves_Regular_21_glyph_126[] = { 11, 4, 12, 0, 8, 0, 97, 21, 0, 48, 5, 64, 255, 239, 3, 208, 12, 192, 61, 211, 159, 251, 7, 224, 8, 0, 233, 142, 0 };
|
||||
|
||||
const uint8_t Font_TTHoves_Regular_21_glyph_nonprintable[] = { 10, 15, 11, 0, 15, 255, 76, 17, 163, 255, 159, 0, 84, 1, 246, 14, 226, 255, 79, 176, 10, 250, 255, 239, 96, 239, 255, 255, 255, 64, 255, 255, 255, 207, 96, 255, 255, 255, 46, 192, 255, 255, 175, 1, 251, 255, 255, 10, 211, 255, 255, 255, 4, 254, 255, 255, 255, 19, 255, 255, 255, 255, 119, 255, 255, 255, 255, 255, 255, 255, 255, 255, 35, 255, 255, 255, 255, 1, 255, 255 };
|
||||
/* ? */ const uint8_t Font_TTHoves_Regular_21_glyph_nonprintable[] = { 10, 15, 11, 0, 15, 255, 76, 17, 163, 255, 159, 0, 84, 1, 246, 14, 226, 255, 79, 176, 10, 250, 255, 239, 96, 239, 255, 255, 255, 64, 255, 255, 255, 207, 96, 255, 255, 255, 46, 192, 255, 255, 175, 1, 251, 255, 255, 10, 211, 255, 255, 255, 4, 254, 255, 255, 255, 19, 255, 255, 255, 255, 119, 255, 255, 255, 255, 255, 255, 255, 255, 255, 35, 255, 255, 255, 255, 1, 255, 255 };
|
||||
|
||||
const uint8_t * const Font_TTHoves_Regular_21[126 + 1 - 32] = {
|
||||
Font_TTHoves_Regular_21_glyph_32,
|
||||
|
@ -102,7 +102,7 @@
|
||||
/* } */ static const uint8_t Font_Unifont_Bold_16_glyph_125[] = { 5, 13, 7, 0, 11, 225, 140, 198, 24, 102, 99, 12, 110, 0 };
|
||||
/* ~ */ static const uint8_t Font_Unifont_Bold_16_glyph_126[] = { 7, 3, 8, 0, 11, 99, 118, 48 };
|
||||
|
||||
const uint8_t Font_Unifont_Bold_16_glyph_nonprintable[] = { 7, 10, 8, 0, 10, 130, 112, 231, 207, 60, 249, 255, 231, 207 };
|
||||
/* ? */ const uint8_t Font_Unifont_Bold_16_glyph_nonprintable[] = { 6, 10, 7, 0, 10, 5, 199, 60, 231, 60, 255, 207, 63 };
|
||||
|
||||
const uint8_t * const Font_Unifont_Bold_16[126 + 1 - 32] = {
|
||||
Font_Unifont_Bold_16_glyph_32,
|
||||
|
@ -106,7 +106,7 @@
|
||||
/* } */ static const uint8_t Font_Unifont_Regular_16_glyph_125[] = { 4, 13, 7, 1, 11, 194, 36, 66, 18, 68, 34, 192 };
|
||||
/* ~ */ static const uint8_t Font_Unifont_Regular_16_glyph_126[] = { 7, 3, 8, 0, 11, 99, 38, 48 }; // < --- advanced changed from 7 to 8
|
||||
|
||||
const uint8_t Font_Unifont_Regular_16_glyph_nonprintable[] = { 6, 10, 7, 0, 10, 133, 231, 190, 247, 190, 255, 239, 191 };
|
||||
/* ? */ const uint8_t Font_Unifont_Regular_16_glyph_nonprintable[] = { 6, 10, 7, 0, 10, 133, 231, 190, 247, 190, 255, 239, 191 };
|
||||
|
||||
const uint8_t * const Font_Unifont_Regular_16[126 + 1 - 32] = {
|
||||
Font_Unifont_Regular_16_glyph_32,
|
||||
|
@ -18,39 +18,15 @@
|
||||
*/
|
||||
|
||||
#include "fonts.h"
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#ifdef TRANSLATIONS
|
||||
#include "librust_fonts.h"
|
||||
#endif
|
||||
|
||||
static uint8_t convert_char(const uint8_t c) {
|
||||
static char last_was_utf8 = 0;
|
||||
|
||||
// non-printable ASCII character
|
||||
if (c < ' ') {
|
||||
last_was_utf8 = 0;
|
||||
return 0x7F;
|
||||
}
|
||||
|
||||
// regular ASCII character
|
||||
if (c < 0x80) {
|
||||
last_was_utf8 = 0;
|
||||
return c;
|
||||
}
|
||||
|
||||
// UTF-8 handling: https://en.wikipedia.org/wiki/UTF-8#Encoding
|
||||
|
||||
// bytes 11xxxxxx are first bytes of UTF-8 characters
|
||||
if (c >= 0xC0) {
|
||||
last_was_utf8 = 1;
|
||||
return 0x7F;
|
||||
}
|
||||
|
||||
if (last_was_utf8) {
|
||||
// bytes 10xxxxxx can be successive UTF-8 characters ...
|
||||
return 0; // skip glyph
|
||||
} else {
|
||||
// ... or they are just non-printable ASCII characters
|
||||
return 0x7F;
|
||||
}
|
||||
}
|
||||
#define UNICODE_BADCHAR 0xFFFD
|
||||
|
||||
int font_height(int font) {
|
||||
switch (font) {
|
||||
@ -130,9 +106,117 @@ int font_baseline(int font) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const uint8_t *font_get_glyph(int font, uint8_t c) {
|
||||
c = convert_char(c);
|
||||
if (!c) return 0;
|
||||
font_glyph_iter_t font_glyph_iter_init(const int font, const uint8_t *text,
|
||||
const int len) {
|
||||
return (font_glyph_iter_t){
|
||||
.font = font,
|
||||
.text = text,
|
||||
.remaining = len,
|
||||
};
|
||||
}
|
||||
|
||||
#define IS_UTF8_CONTINUE(c) (((c)&0b11000000) == 0b10000000)
|
||||
|
||||
static uint16_t next_utf8_codepoint(font_glyph_iter_t *iter) {
|
||||
uint16_t out;
|
||||
assert(iter->remaining > 0);
|
||||
// 1-byte UTF-8 character
|
||||
if (iter->text[0] < 0x7f) {
|
||||
out = iter->text[0];
|
||||
++iter->text;
|
||||
--iter->remaining;
|
||||
return out;
|
||||
}
|
||||
// 2-byte UTF-8 character
|
||||
if (iter->remaining >= 2 && ((iter->text[0] & 0b11100000) == 0b11000000) &&
|
||||
IS_UTF8_CONTINUE(iter->text[1])) {
|
||||
out = (((uint16_t)iter->text[0] & 0b00011111) << 6) |
|
||||
(iter->text[1] & 0b00111111);
|
||||
iter->text += 2;
|
||||
iter->remaining -= 2;
|
||||
return out;
|
||||
}
|
||||
// 3-byte UTF-8 character
|
||||
if (iter->remaining >= 3 && ((iter->text[0] & 0b11110000) == 0b11100000) &&
|
||||
IS_UTF8_CONTINUE(iter->text[1]) && IS_UTF8_CONTINUE(iter->text[2])) {
|
||||
out = (((uint16_t)iter->text[0] & 0b00001111) << 12) |
|
||||
(((uint16_t)iter->text[1] & 0b00111111) << 6) |
|
||||
(iter->text[2] & 0b00111111);
|
||||
iter->text += 3;
|
||||
iter->remaining -= 3;
|
||||
return out;
|
||||
}
|
||||
// 4-byte UTF-8 character
|
||||
if (iter->remaining >= 4 && ((iter->text[0] & 0b11111000) == 0b11110000) &&
|
||||
IS_UTF8_CONTINUE(iter->text[1]) && IS_UTF8_CONTINUE(iter->text[2]) &&
|
||||
IS_UTF8_CONTINUE(iter->text[3])) {
|
||||
// we use 16-bit codepoints, so we can't represent 4-byte UTF-8 characters
|
||||
iter->text += 4;
|
||||
iter->remaining -= 4;
|
||||
return UNICODE_BADCHAR;
|
||||
}
|
||||
|
||||
++iter->text;
|
||||
--iter->remaining;
|
||||
return UNICODE_BADCHAR;
|
||||
}
|
||||
|
||||
bool font_next_glyph(font_glyph_iter_t *iter, const uint8_t **out) {
|
||||
if (iter->remaining <= 0) {
|
||||
return false;
|
||||
}
|
||||
uint16_t c = next_utf8_codepoint(iter);
|
||||
*out = font_get_glyph(iter->font, c);
|
||||
if (*out == NULL) {
|
||||
// should not happen but ¯\_(ツ)_/¯
|
||||
return font_next_glyph(iter, out);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const uint8_t *font_nonprintable_glyph(int font) {
|
||||
#define PASTER(s) s##_glyph_nonprintable
|
||||
#define NONPRINTABLE_GLYPH(s) PASTER(s)
|
||||
|
||||
switch (font) {
|
||||
#ifdef TREZOR_FONT_NORMAL_ENABLE
|
||||
case FONT_NORMAL:
|
||||
return NONPRINTABLE_GLYPH(FONT_NORMAL_DATA);
|
||||
#endif
|
||||
#ifdef TREZOR_FONT_DEMIBOLD_ENABLE
|
||||
case FONT_DEMIBOLD:
|
||||
return NONPRINTABLE_GLYPH(FONT_DEMIBOLD_DATA);
|
||||
#endif
|
||||
#ifdef TREZOR_FONT_BOLD_ENABLE
|
||||
case FONT_BOLD:
|
||||
return NONPRINTABLE_GLYPH(FONT_BOLD_DATA);
|
||||
#endif
|
||||
#ifdef TREZOR_FONT_MONO_ENABLE
|
||||
case FONT_MONO:
|
||||
return NONPRINTABLE_GLYPH(FONT_MONO_DATA);
|
||||
#endif
|
||||
#ifdef TREZOR_FONT_BIG_ENABLE
|
||||
case FONT_BIG:
|
||||
return NONPRINTABLE_GLYPH(FONT_BIG_DATA);
|
||||
#endif
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
const uint8_t *font_get_glyph(int font, uint16_t c) {
|
||||
#ifdef TRANSLATIONS
|
||||
// found UTF8 character
|
||||
// it is not hardcoded in firmware fonts, it must be extracted from the
|
||||
// embedded blob
|
||||
if (c >= 0x7F) {
|
||||
const uint8_t *g = get_utf8_glyph(c, font);
|
||||
if (g != NULL) {
|
||||
return g;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// printable ASCII character
|
||||
if (c >= ' ' && c < 0x7F) {
|
||||
@ -161,29 +245,7 @@ const uint8_t *font_get_glyph(int font, uint8_t c) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// non-printable character
|
||||
#define PASTER(s) s##_glyph_nonprintable
|
||||
#define NONPRINTABLE_GLYPH(s) PASTER(s)
|
||||
|
||||
switch (font) {
|
||||
#ifdef TREZOR_FONT_NORMAL_ENABLE
|
||||
case FONT_NORMAL:
|
||||
return NONPRINTABLE_GLYPH(FONT_NORMAL_DATA);
|
||||
#endif
|
||||
#ifdef TREZOR_FONT_DEMIBOLD_ENABLE
|
||||
case FONT_DEMIBOLD:
|
||||
return NONPRINTABLE_GLYPH(FONT_DEMIBOLD_DATA);
|
||||
#endif
|
||||
#ifdef TREZOR_FONT_BOLD_ENABLE
|
||||
case FONT_BOLD:
|
||||
return NONPRINTABLE_GLYPH(FONT_BOLD_DATA);
|
||||
#endif
|
||||
#ifdef TREZOR_FONT_MONO_ENABLE
|
||||
case FONT_MONO:
|
||||
return NONPRINTABLE_GLYPH(FONT_MONO_DATA);
|
||||
#endif
|
||||
}
|
||||
return 0;
|
||||
return font_nonprintable_glyph(font);
|
||||
}
|
||||
|
||||
// compute the width of the text (in pixels)
|
||||
@ -193,50 +255,11 @@ int font_text_width(int font, const char *text, int textlen) {
|
||||
if (textlen < 0) {
|
||||
textlen = strlen(text);
|
||||
}
|
||||
for (int i = 0; i < textlen; i++) {
|
||||
const uint8_t *g = font_get_glyph(font, (uint8_t)text[i]);
|
||||
if (!g) continue;
|
||||
font_glyph_iter_t iter = font_glyph_iter_init(font, (uint8_t *)text, textlen);
|
||||
const uint8_t *g = NULL;
|
||||
while (font_next_glyph(&iter, &g)) {
|
||||
const uint8_t adv = g[2]; // advance
|
||||
width += adv;
|
||||
/*
|
||||
if (i != textlen - 1) {
|
||||
const uint8_t adv = g[2]; // advance
|
||||
width += adv;
|
||||
} else { // last character
|
||||
const uint8_t w = g[0]; // width
|
||||
const uint8_t bearX = g[3]; // bearingX
|
||||
width += (bearX + w);
|
||||
}
|
||||
*/
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
// Returns how many characters of the string can be used before exceeding
|
||||
// the requested width. Tries to avoid breaking words if possible.
|
||||
int font_text_split(int font, const char *text, int textlen,
|
||||
int requested_width) {
|
||||
int width = 0;
|
||||
int lastspace = 0;
|
||||
// determine text length if not provided
|
||||
if (textlen < 0) {
|
||||
textlen = strlen(text);
|
||||
}
|
||||
for (int i = 0; i < textlen; i++) {
|
||||
if (text[i] == ' ') {
|
||||
lastspace = i;
|
||||
}
|
||||
const uint8_t *g = font_get_glyph(font, (uint8_t)text[i]);
|
||||
if (!g) continue;
|
||||
const uint8_t adv = g[2]; // advance
|
||||
width += adv;
|
||||
if (width > requested_width) {
|
||||
if (lastspace > 0) {
|
||||
return lastspace;
|
||||
} else {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return textlen;
|
||||
}
|
||||
|
@ -20,6 +20,8 @@
|
||||
#ifndef _FONTS_H
|
||||
#define _FONTS_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "fonts/font_bitmap.h"
|
||||
#include TREZOR_BOARD
|
||||
|
||||
@ -117,9 +119,18 @@
|
||||
int font_height(int font);
|
||||
int font_max_height(int font);
|
||||
int font_baseline(int font);
|
||||
const uint8_t *font_get_glyph(int font, uint8_t c);
|
||||
const uint8_t *font_get_glyph(int font, uint16_t c);
|
||||
const uint8_t *font_nonprintable_glyph(int font);
|
||||
|
||||
typedef struct {
|
||||
const int font;
|
||||
const uint8_t *text;
|
||||
int remaining;
|
||||
} font_glyph_iter_t;
|
||||
|
||||
font_glyph_iter_t font_glyph_iter_init(const int font, const uint8_t *text,
|
||||
const int len);
|
||||
bool font_next_glyph(font_glyph_iter_t *iter, const uint8_t **out);
|
||||
int font_text_width(int font, const char *text, int textlen);
|
||||
int font_text_split(int font, const char *text, int textlen,
|
||||
int requested_width);
|
||||
|
||||
#endif //_FONTS_H
|
||||
|
@ -334,6 +334,7 @@ secbool check_firmware_header(const uint8_t *header, size_t header_size,
|
||||
info->ver_major = ihdr->version & 0xFF;
|
||||
info->ver_minor = (ihdr->version >> 8) & 0xFF;
|
||||
info->ver_patch = (ihdr->version >> 16) & 0xFF;
|
||||
info->ver_build = (ihdr->version >> 24) & 0xFF;
|
||||
|
||||
// calculate and copy the image fingerprint
|
||||
get_image_fingerprint(ihdr, info->fingerprint);
|
||||
|
@ -88,6 +88,7 @@ typedef struct {
|
||||
uint8_t ver_major;
|
||||
uint8_t ver_minor;
|
||||
uint8_t ver_patch;
|
||||
uint8_t ver_build;
|
||||
// firmware fingerprint
|
||||
uint8_t fingerprint[BLAKE2S_DIGEST_LENGTH];
|
||||
// hash of vendor and image header
|
||||
|
@ -15,6 +15,7 @@
|
||||
extern const flash_area_t STORAGE_AREAS[STORAGE_AREAS_COUNT];
|
||||
extern const flash_area_t BOARDLOADER_AREA;
|
||||
extern const flash_area_t SECRET_AREA;
|
||||
extern const flash_area_t TRANSLATIONS_AREA;
|
||||
extern const flash_area_t BOOTLOADER_AREA;
|
||||
extern const flash_area_t FIRMWARE_AREA;
|
||||
extern const flash_area_t WIPE_AREA;
|
||||
|
@ -38,6 +38,15 @@ const flash_area_t SECRET_AREA = {
|
||||
},
|
||||
};
|
||||
|
||||
const flash_area_t TRANSLATIONS_AREA = {
|
||||
.num_subareas = 1,
|
||||
.subarea[0] =
|
||||
{
|
||||
.first_sector = 13,
|
||||
.num_sectors = 2,
|
||||
},
|
||||
};
|
||||
|
||||
const flash_area_t BOOTLOADER_AREA = {
|
||||
.num_subareas = 1,
|
||||
.subarea[0] =
|
||||
@ -62,7 +71,7 @@ const flash_area_t FIRMWARE_AREA = {
|
||||
};
|
||||
|
||||
const flash_area_t WIPE_AREA = {
|
||||
.num_subareas = 4,
|
||||
.num_subareas = 3,
|
||||
.subarea[0] =
|
||||
{
|
||||
.first_sector = 4,
|
||||
@ -74,12 +83,6 @@ const flash_area_t WIPE_AREA = {
|
||||
.num_sectors = 6,
|
||||
},
|
||||
.subarea[2] =
|
||||
{
|
||||
.first_sector = 13,
|
||||
.num_sectors = 2, // sector 15 skipped due to bootloader MPU
|
||||
// settings, sector 12 is secret
|
||||
},
|
||||
.subarea[3] =
|
||||
{
|
||||
.first_sector = 16,
|
||||
.num_sectors = 8,
|
||||
|
@ -29,6 +29,15 @@ const flash_area_t BOARDLOADER_AREA = {
|
||||
},
|
||||
};
|
||||
|
||||
const flash_area_t TRANSLATIONS_AREA = {
|
||||
.num_subareas = 1,
|
||||
.subarea[0] =
|
||||
{
|
||||
.first_sector = 12,
|
||||
.num_sectors = 3,
|
||||
},
|
||||
};
|
||||
|
||||
const flash_area_t BOOTLOADER_AREA = {
|
||||
.num_subareas = 1,
|
||||
.subarea[0] =
|
||||
|
@ -32,6 +32,7 @@ rgb_led = []
|
||||
backlight = []
|
||||
usb = []
|
||||
optiga = []
|
||||
translations = []
|
||||
test = [
|
||||
"button",
|
||||
"cc",
|
||||
@ -43,7 +44,8 @@ test = [
|
||||
"dma2d",
|
||||
"touch",
|
||||
"backlight",
|
||||
"optiga"
|
||||
"optiga",
|
||||
"translations",
|
||||
]
|
||||
|
||||
[lib]
|
||||
|
@ -289,6 +289,10 @@ fn generate_trezorhal_bindings() {
|
||||
.allowlist_function("storage_delete")
|
||||
.allowlist_function("storage_set_counter")
|
||||
.allowlist_function("storage_next_counter")
|
||||
.allowlist_function("translations_read")
|
||||
.allowlist_function("translations_write")
|
||||
.allowlist_function("translations_erase")
|
||||
.allowlist_function("translations_area_bytesize")
|
||||
// display
|
||||
.allowlist_function("display_clear")
|
||||
.allowlist_function("display_offset")
|
||||
|
@ -9,6 +9,7 @@ mp_obj_t protobuf_debug_msg_def_type();
|
||||
|
||||
extern mp_obj_module_t mp_module_trezorproto;
|
||||
extern mp_obj_module_t mp_module_trezorui2;
|
||||
extern mp_obj_module_t mp_module_trezortranslate;
|
||||
|
||||
#ifdef TREZOR_EMULATOR
|
||||
mp_obj_t ui_debug_layout_type();
|
||||
|
1
core/embed/rust/librust_fonts.h
Normal file
1
core/embed/rust/librust_fonts.h
Normal file
@ -0,0 +1 @@
|
||||
const uint8_t *get_utf8_glyph(uint16_t char_code, int font);
|
@ -5,6 +5,7 @@
|
||||
#pragma GCC diagnostic ignored "-Wunused-function"
|
||||
|
||||
static void _librust_qstrs(void) {
|
||||
MP_QSTR_;
|
||||
MP_QSTR_CANCELLED;
|
||||
MP_QSTR_CONFIRMED;
|
||||
MP_QSTR_INFO;
|
||||
@ -13,6 +14,8 @@ static void _librust_qstrs(void) {
|
||||
MP_QSTR_MESSAGE_WIRE_TYPE;
|
||||
MP_QSTR_Msg;
|
||||
MP_QSTR_MsgDef;
|
||||
MP_QSTR_TR;
|
||||
MP_QSTR_TranslationsHeader;
|
||||
MP_QSTR___dict__;
|
||||
MP_QSTR___name__;
|
||||
MP_QSTR_account;
|
||||
@ -20,7 +23,20 @@ static void _librust_qstrs(void) {
|
||||
MP_QSTR_accounts;
|
||||
MP_QSTR_action;
|
||||
MP_QSTR_active;
|
||||
MP_QSTR_addr_mismatch__contact_support_at;
|
||||
MP_QSTR_addr_mismatch__key_mismatch;
|
||||
MP_QSTR_addr_mismatch__mismatch;
|
||||
MP_QSTR_addr_mismatch__support_url;
|
||||
MP_QSTR_addr_mismatch__wrong_derivation_path;
|
||||
MP_QSTR_addr_mismatch__xpub_mismatch;
|
||||
MP_QSTR_address;
|
||||
MP_QSTR_address__public_key;
|
||||
MP_QSTR_address__title_cosigner;
|
||||
MP_QSTR_address__title_receive_address;
|
||||
MP_QSTR_address__title_yours;
|
||||
MP_QSTR_address_details__derivation_path;
|
||||
MP_QSTR_address_details__title_receive_address;
|
||||
MP_QSTR_address_details__title_receiving_to;
|
||||
MP_QSTR_address_label;
|
||||
MP_QSTR_address_title;
|
||||
MP_QSTR_allow_cancel;
|
||||
@ -31,16 +47,224 @@ static void _librust_qstrs(void) {
|
||||
MP_QSTR_amount_title;
|
||||
MP_QSTR_amount_value;
|
||||
MP_QSTR_app_name;
|
||||
MP_QSTR_area_bytesize;
|
||||
MP_QSTR_attach_timer_fn;
|
||||
MP_QSTR_authenticate__confirm_template;
|
||||
MP_QSTR_authenticate__header;
|
||||
MP_QSTR_auto_lock__change_template;
|
||||
MP_QSTR_auto_lock__title;
|
||||
MP_QSTR_backup__can_back_up_anytime;
|
||||
MP_QSTR_backup__it_should_be_backed_up;
|
||||
MP_QSTR_backup__it_should_be_backed_up_now;
|
||||
MP_QSTR_backup__new_wallet_created;
|
||||
MP_QSTR_backup__new_wallet_successfully_created;
|
||||
MP_QSTR_backup__recover_anytime;
|
||||
MP_QSTR_backup__title_backup_wallet;
|
||||
MP_QSTR_backup__title_skip;
|
||||
MP_QSTR_backup__want_to_skip;
|
||||
MP_QSTR_binance__buy;
|
||||
MP_QSTR_binance__confirm_cancel;
|
||||
MP_QSTR_binance__confirm_input;
|
||||
MP_QSTR_binance__confirm_order;
|
||||
MP_QSTR_binance__confirm_output;
|
||||
MP_QSTR_binance__order_id;
|
||||
MP_QSTR_binance__pair;
|
||||
MP_QSTR_binance__price;
|
||||
MP_QSTR_binance__quantity;
|
||||
MP_QSTR_binance__sell;
|
||||
MP_QSTR_binance__sender_address;
|
||||
MP_QSTR_binance__side;
|
||||
MP_QSTR_bitcoin__commitment_data;
|
||||
MP_QSTR_bitcoin__confirm_locktime;
|
||||
MP_QSTR_bitcoin__create_proof_of_ownership;
|
||||
MP_QSTR_bitcoin__high_mining_fee_template;
|
||||
MP_QSTR_bitcoin__locktime_no_effect;
|
||||
MP_QSTR_bitcoin__locktime_set_to;
|
||||
MP_QSTR_bitcoin__locktime_set_to_blockheight;
|
||||
MP_QSTR_bitcoin__lot_of_change_outputs;
|
||||
MP_QSTR_bitcoin__multiple_accounts;
|
||||
MP_QSTR_bitcoin__new_fee_rate;
|
||||
MP_QSTR_bitcoin__simple_send_of;
|
||||
MP_QSTR_bitcoin__ticket_amount;
|
||||
MP_QSTR_bitcoin__title_confirm_details;
|
||||
MP_QSTR_bitcoin__title_finalize_transaction;
|
||||
MP_QSTR_bitcoin__title_high_mining_fee;
|
||||
MP_QSTR_bitcoin__title_meld_transaction;
|
||||
MP_QSTR_bitcoin__title_modify_amount;
|
||||
MP_QSTR_bitcoin__title_payjoin;
|
||||
MP_QSTR_bitcoin__title_proof_of_ownership;
|
||||
MP_QSTR_bitcoin__title_purchase_ticket;
|
||||
MP_QSTR_bitcoin__title_update_transaction;
|
||||
MP_QSTR_bitcoin__unknown_path;
|
||||
MP_QSTR_bitcoin__unknown_transaction;
|
||||
MP_QSTR_bitcoin__unusually_high_fee;
|
||||
MP_QSTR_bitcoin__unverified_external_inputs;
|
||||
MP_QSTR_bitcoin__valid_signature;
|
||||
MP_QSTR_bitcoin__voting_rights;
|
||||
MP_QSTR_bootscreen;
|
||||
MP_QSTR_bounds;
|
||||
MP_QSTR_button;
|
||||
MP_QSTR_button_event;
|
||||
MP_QSTR_buttons__abort;
|
||||
MP_QSTR_buttons__access;
|
||||
MP_QSTR_buttons__again;
|
||||
MP_QSTR_buttons__allow;
|
||||
MP_QSTR_buttons__back;
|
||||
MP_QSTR_buttons__back_up;
|
||||
MP_QSTR_buttons__cancel;
|
||||
MP_QSTR_buttons__change;
|
||||
MP_QSTR_buttons__check;
|
||||
MP_QSTR_buttons__check_again;
|
||||
MP_QSTR_buttons__close;
|
||||
MP_QSTR_buttons__confirm;
|
||||
MP_QSTR_buttons__continue;
|
||||
MP_QSTR_buttons__details;
|
||||
MP_QSTR_buttons__enable;
|
||||
MP_QSTR_buttons__enter;
|
||||
MP_QSTR_buttons__enter_share;
|
||||
MP_QSTR_buttons__export;
|
||||
MP_QSTR_buttons__format;
|
||||
MP_QSTR_buttons__go_back;
|
||||
MP_QSTR_buttons__hold_to_confirm;
|
||||
MP_QSTR_buttons__info;
|
||||
MP_QSTR_buttons__install;
|
||||
MP_QSTR_buttons__more_info;
|
||||
MP_QSTR_buttons__ok_i_understand;
|
||||
MP_QSTR_buttons__purchase;
|
||||
MP_QSTR_buttons__quit;
|
||||
MP_QSTR_buttons__restart;
|
||||
MP_QSTR_buttons__retry;
|
||||
MP_QSTR_buttons__select;
|
||||
MP_QSTR_buttons__set;
|
||||
MP_QSTR_buttons__show_all;
|
||||
MP_QSTR_buttons__show_details;
|
||||
MP_QSTR_buttons__show_words;
|
||||
MP_QSTR_buttons__skip;
|
||||
MP_QSTR_buttons__try_again;
|
||||
MP_QSTR_buttons__turn_off;
|
||||
MP_QSTR_buttons__turn_on;
|
||||
MP_QSTR_cancel_arrow;
|
||||
MP_QSTR_cancel_cross;
|
||||
MP_QSTR_cardano__addr_base;
|
||||
MP_QSTR_cardano__addr_enterprise;
|
||||
MP_QSTR_cardano__addr_legacy;
|
||||
MP_QSTR_cardano__addr_pointer;
|
||||
MP_QSTR_cardano__addr_reward;
|
||||
MP_QSTR_cardano__address_no_staking;
|
||||
MP_QSTR_cardano__amount_burned_decimals_unknown;
|
||||
MP_QSTR_cardano__amount_minted_decimals_unknown;
|
||||
MP_QSTR_cardano__amount_sent_decimals_unknown;
|
||||
MP_QSTR_cardano__anonymous_pool;
|
||||
MP_QSTR_cardano__asset_fingerprint;
|
||||
MP_QSTR_cardano__auxiliary_data_hash;
|
||||
MP_QSTR_cardano__block;
|
||||
MP_QSTR_cardano__catalyst;
|
||||
MP_QSTR_cardano__certificate;
|
||||
MP_QSTR_cardano__change_output;
|
||||
MP_QSTR_cardano__check_all_items;
|
||||
MP_QSTR_cardano__choose_level_of_details;
|
||||
MP_QSTR_cardano__collateral_input_id;
|
||||
MP_QSTR_cardano__collateral_input_index;
|
||||
MP_QSTR_cardano__collateral_output_contains_tokens;
|
||||
MP_QSTR_cardano__collateral_return;
|
||||
MP_QSTR_cardano__confirm;
|
||||
MP_QSTR_cardano__confirm_signing_stake_pool;
|
||||
MP_QSTR_cardano__confirm_transaction;
|
||||
MP_QSTR_cardano__confirming_a_multisig_transaction;
|
||||
MP_QSTR_cardano__confirming_a_plutus_transaction;
|
||||
MP_QSTR_cardano__confirming_pool_registration;
|
||||
MP_QSTR_cardano__confirming_transction;
|
||||
MP_QSTR_cardano__cost;
|
||||
MP_QSTR_cardano__credential_mismatch;
|
||||
MP_QSTR_cardano__datum_hash;
|
||||
MP_QSTR_cardano__delegating_to;
|
||||
MP_QSTR_cardano__for_account_and_index_template;
|
||||
MP_QSTR_cardano__for_account_template;
|
||||
MP_QSTR_cardano__for_key_hash;
|
||||
MP_QSTR_cardano__for_script;
|
||||
MP_QSTR_cardano__inline_datum;
|
||||
MP_QSTR_cardano__input_id;
|
||||
MP_QSTR_cardano__input_index;
|
||||
MP_QSTR_cardano__intro_text_change;
|
||||
MP_QSTR_cardano__intro_text_owned_by_device;
|
||||
MP_QSTR_cardano__intro_text_registration_payment;
|
||||
MP_QSTR_cardano__key_hash;
|
||||
MP_QSTR_cardano__margin;
|
||||
MP_QSTR_cardano__multisig_path;
|
||||
MP_QSTR_cardano__nested_scripts_template;
|
||||
MP_QSTR_cardano__network;
|
||||
MP_QSTR_cardano__no_output_tx;
|
||||
MP_QSTR_cardano__nonce;
|
||||
MP_QSTR_cardano__other;
|
||||
MP_QSTR_cardano__path;
|
||||
MP_QSTR_cardano__pledge;
|
||||
MP_QSTR_cardano__pointer;
|
||||
MP_QSTR_cardano__policy_id;
|
||||
MP_QSTR_cardano__pool_metadata_hash;
|
||||
MP_QSTR_cardano__pool_metadata_url;
|
||||
MP_QSTR_cardano__pool_owner;
|
||||
MP_QSTR_cardano__pool_reward_account;
|
||||
MP_QSTR_cardano__reference_input_id;
|
||||
MP_QSTR_cardano__reference_input_index;
|
||||
MP_QSTR_cardano__reference_script;
|
||||
MP_QSTR_cardano__required_signer;
|
||||
MP_QSTR_cardano__reward;
|
||||
MP_QSTR_cardano__reward_address;
|
||||
MP_QSTR_cardano__reward_eligibility_warning;
|
||||
MP_QSTR_cardano__rewards_go_to;
|
||||
MP_QSTR_cardano__script;
|
||||
MP_QSTR_cardano__script_all;
|
||||
MP_QSTR_cardano__script_any;
|
||||
MP_QSTR_cardano__script_data_hash;
|
||||
MP_QSTR_cardano__script_hash;
|
||||
MP_QSTR_cardano__script_invalid_before;
|
||||
MP_QSTR_cardano__script_invalid_hereafter;
|
||||
MP_QSTR_cardano__script_key;
|
||||
MP_QSTR_cardano__script_n_of_k;
|
||||
MP_QSTR_cardano__script_reward;
|
||||
MP_QSTR_cardano__sending;
|
||||
MP_QSTR_cardano__show_simple;
|
||||
MP_QSTR_cardano__sign_tx_path_template;
|
||||
MP_QSTR_cardano__stake_delegation;
|
||||
MP_QSTR_cardano__stake_deregistration;
|
||||
MP_QSTR_cardano__stake_pool_registration;
|
||||
MP_QSTR_cardano__stake_pool_registration_pool_id;
|
||||
MP_QSTR_cardano__stake_registration;
|
||||
MP_QSTR_cardano__staking_key_for_account;
|
||||
MP_QSTR_cardano__to_pool;
|
||||
MP_QSTR_cardano__token_minting_path;
|
||||
MP_QSTR_cardano__total_collateral;
|
||||
MP_QSTR_cardano__transaction;
|
||||
MP_QSTR_cardano__transaction_contains_minting_or_burning;
|
||||
MP_QSTR_cardano__transaction_contains_script_address_no_datum;
|
||||
MP_QSTR_cardano__transaction_fee;
|
||||
MP_QSTR_cardano__transaction_id;
|
||||
MP_QSTR_cardano__transaction_no_collateral_input;
|
||||
MP_QSTR_cardano__transaction_no_script_data_hash;
|
||||
MP_QSTR_cardano__transaction_output_contains_tokens;
|
||||
MP_QSTR_cardano__ttl;
|
||||
MP_QSTR_cardano__unknown_collateral_amount;
|
||||
MP_QSTR_cardano__unusual_path;
|
||||
MP_QSTR_cardano__valid_since;
|
||||
MP_QSTR_cardano__verify_script;
|
||||
MP_QSTR_cardano__vote_key_registration;
|
||||
MP_QSTR_cardano__vote_public_key;
|
||||
MP_QSTR_cardano__voting_purpose;
|
||||
MP_QSTR_cardano__warning;
|
||||
MP_QSTR_cardano__weight;
|
||||
MP_QSTR_cardano__withdrawal_for_address_template;
|
||||
MP_QSTR_cardano__x_of_y_signatures_template;
|
||||
MP_QSTR_case_sensitive;
|
||||
MP_QSTR_check_homescreen_format;
|
||||
MP_QSTR_chunkify;
|
||||
MP_QSTR_coinjoin__access_account;
|
||||
MP_QSTR_coinjoin__do_not_disconnect;
|
||||
MP_QSTR_coinjoin__max_mining_fee;
|
||||
MP_QSTR_coinjoin__max_rounds;
|
||||
MP_QSTR_coinjoin__title;
|
||||
MP_QSTR_coinjoin__title_do_not_disconnect;
|
||||
MP_QSTR_coinjoin__title_progress;
|
||||
MP_QSTR_coinjoin__waiting_for_others;
|
||||
MP_QSTR_coinjoin_authorized;
|
||||
MP_QSTR_confirm_action;
|
||||
MP_QSTR_confirm_address;
|
||||
@ -61,60 +285,507 @@ static void _librust_qstrs(void) {
|
||||
MP_QSTR_confirm_recovery;
|
||||
MP_QSTR_confirm_reset_device;
|
||||
MP_QSTR_confirm_total;
|
||||
MP_QSTR_confirm_total__fee_rate;
|
||||
MP_QSTR_confirm_total__sending_from_account;
|
||||
MP_QSTR_confirm_total__title_fee;
|
||||
MP_QSTR_confirm_total__title_sending_from;
|
||||
MP_QSTR_confirm_value;
|
||||
MP_QSTR_confirm_with_info;
|
||||
MP_QSTR_count;
|
||||
MP_QSTR_data;
|
||||
MP_QSTR_data_hash;
|
||||
MP_QSTR_data_len;
|
||||
MP_QSTR_debug__loading_seed;
|
||||
MP_QSTR_debug__loading_seed_not_recommended;
|
||||
MP_QSTR_decode;
|
||||
MP_QSTR_deinit;
|
||||
MP_QSTR_description;
|
||||
MP_QSTR_details_title;
|
||||
MP_QSTR_device_name__change_template;
|
||||
MP_QSTR_device_name__title;
|
||||
MP_QSTR_disable_animation;
|
||||
MP_QSTR_dry_run;
|
||||
MP_QSTR_encode;
|
||||
MP_QSTR_encoded_length;
|
||||
MP_QSTR_entropy__send;
|
||||
MP_QSTR_entropy__title;
|
||||
MP_QSTR_entropy__title_confirm;
|
||||
MP_QSTR_eos__about_to_sign_template;
|
||||
MP_QSTR_eos__action_name;
|
||||
MP_QSTR_eos__arbitrary_data;
|
||||
MP_QSTR_eos__buy_ram;
|
||||
MP_QSTR_eos__bytes;
|
||||
MP_QSTR_eos__cancel_vote;
|
||||
MP_QSTR_eos__checksum;
|
||||
MP_QSTR_eos__code;
|
||||
MP_QSTR_eos__contract;
|
||||
MP_QSTR_eos__cpu;
|
||||
MP_QSTR_eos__creator;
|
||||
MP_QSTR_eos__delegate;
|
||||
MP_QSTR_eos__delete_auth;
|
||||
MP_QSTR_eos__from;
|
||||
MP_QSTR_eos__link_auth;
|
||||
MP_QSTR_eos__memo;
|
||||
MP_QSTR_eos__name;
|
||||
MP_QSTR_eos__net;
|
||||
MP_QSTR_eos__new_account;
|
||||
MP_QSTR_eos__owner;
|
||||
MP_QSTR_eos__parent;
|
||||
MP_QSTR_eos__payer;
|
||||
MP_QSTR_eos__permission;
|
||||
MP_QSTR_eos__proxy;
|
||||
MP_QSTR_eos__receiver;
|
||||
MP_QSTR_eos__refund;
|
||||
MP_QSTR_eos__requirement;
|
||||
MP_QSTR_eos__sell_ram;
|
||||
MP_QSTR_eos__sender;
|
||||
MP_QSTR_eos__sign_transaction;
|
||||
MP_QSTR_eos__threshold;
|
||||
MP_QSTR_eos__to;
|
||||
MP_QSTR_eos__transfer;
|
||||
MP_QSTR_eos__type;
|
||||
MP_QSTR_eos__undelegate;
|
||||
MP_QSTR_eos__unlink_auth;
|
||||
MP_QSTR_eos__update_auth;
|
||||
MP_QSTR_eos__vote_for_producers;
|
||||
MP_QSTR_eos__vote_for_proxy;
|
||||
MP_QSTR_eos__voter;
|
||||
MP_QSTR_erase;
|
||||
MP_QSTR_ethereum__amount_sent;
|
||||
MP_QSTR_ethereum__contract;
|
||||
MP_QSTR_ethereum__data_size_template;
|
||||
MP_QSTR_ethereum__gas_limit;
|
||||
MP_QSTR_ethereum__gas_price;
|
||||
MP_QSTR_ethereum__max_gas_price;
|
||||
MP_QSTR_ethereum__name_and_version;
|
||||
MP_QSTR_ethereum__new_contract;
|
||||
MP_QSTR_ethereum__no_message_field;
|
||||
MP_QSTR_ethereum__priority_fee;
|
||||
MP_QSTR_ethereum__show_full_array;
|
||||
MP_QSTR_ethereum__show_full_domain;
|
||||
MP_QSTR_ethereum__show_full_message;
|
||||
MP_QSTR_ethereum__show_full_struct;
|
||||
MP_QSTR_ethereum__sign_eip712;
|
||||
MP_QSTR_ethereum__title_confirm_data;
|
||||
MP_QSTR_ethereum__title_confirm_domain;
|
||||
MP_QSTR_ethereum__title_confirm_message;
|
||||
MP_QSTR_ethereum__title_confirm_struct;
|
||||
MP_QSTR_ethereum__title_confirm_typed_data;
|
||||
MP_QSTR_ethereum__title_signing_address;
|
||||
MP_QSTR_ethereum__units_template;
|
||||
MP_QSTR_ethereum__unknown_token;
|
||||
MP_QSTR_ethereum__valid_signature;
|
||||
MP_QSTR_experimental_mode__enable;
|
||||
MP_QSTR_experimental_mode__only_for_dev;
|
||||
MP_QSTR_experimental_mode__title;
|
||||
MP_QSTR_extra;
|
||||
MP_QSTR_fee_amount;
|
||||
MP_QSTR_fee_label;
|
||||
MP_QSTR_fee_rate_amount;
|
||||
MP_QSTR_fee_title;
|
||||
MP_QSTR_fee_value;
|
||||
MP_QSTR_fido__already_registered;
|
||||
MP_QSTR_fido__device_already_registered;
|
||||
MP_QSTR_fido__device_already_registered_with_template;
|
||||
MP_QSTR_fido__device_not_registered;
|
||||
MP_QSTR_fido__does_not_belong;
|
||||
MP_QSTR_fido__erase_credentials;
|
||||
MP_QSTR_fido__export_credentials;
|
||||
MP_QSTR_fido__not_registered;
|
||||
MP_QSTR_fido__not_registered_with_template;
|
||||
MP_QSTR_fido__please_enable_pin_protection;
|
||||
MP_QSTR_fido__title_authenticate;
|
||||
MP_QSTR_fido__title_import_credential;
|
||||
MP_QSTR_fido__title_list_credentials;
|
||||
MP_QSTR_fido__title_register;
|
||||
MP_QSTR_fido__title_remove_credential;
|
||||
MP_QSTR_fido__title_reset;
|
||||
MP_QSTR_fido__title_u2f_auth;
|
||||
MP_QSTR_fido__title_u2f_register;
|
||||
MP_QSTR_fido__title_verify_user;
|
||||
MP_QSTR_fido__unable_to_verify_user;
|
||||
MP_QSTR_fido__wanna_erase_credentials;
|
||||
MP_QSTR_fingerprint;
|
||||
MP_QSTR_firmware_update__title;
|
||||
MP_QSTR_firmware_update__title_fingerprint;
|
||||
MP_QSTR_get_language;
|
||||
MP_QSTR_hold;
|
||||
MP_QSTR_hold_danger;
|
||||
MP_QSTR_homescreen__click_to_connect;
|
||||
MP_QSTR_homescreen__click_to_unlock;
|
||||
MP_QSTR_homescreen__title_backup_failed;
|
||||
MP_QSTR_homescreen__title_backup_needed;
|
||||
MP_QSTR_homescreen__title_coinjoin_authorized;
|
||||
MP_QSTR_homescreen__title_experimental_mode;
|
||||
MP_QSTR_homescreen__title_no_usb_connection;
|
||||
MP_QSTR_homescreen__title_pin_not_set;
|
||||
MP_QSTR_homescreen__title_seedless;
|
||||
MP_QSTR_homescreen__title_set;
|
||||
MP_QSTR_horizontal;
|
||||
MP_QSTR_icon_name;
|
||||
MP_QSTR_image;
|
||||
MP_QSTR_indeterminate;
|
||||
MP_QSTR_info_button;
|
||||
MP_QSTR_init;
|
||||
MP_QSTR_inputs__back;
|
||||
MP_QSTR_inputs__cancel;
|
||||
MP_QSTR_inputs__delete;
|
||||
MP_QSTR_inputs__enter;
|
||||
MP_QSTR_inputs__return;
|
||||
MP_QSTR_inputs__show;
|
||||
MP_QSTR_inputs__space;
|
||||
MP_QSTR_is_type_of;
|
||||
MP_QSTR_items;
|
||||
MP_QSTR_joint__title;
|
||||
MP_QSTR_joint__to_the_total_amount;
|
||||
MP_QSTR_joint__you_are_contributing;
|
||||
MP_QSTR_label;
|
||||
MP_QSTR_language;
|
||||
MP_QSTR_language__change_to;
|
||||
MP_QSTR_language__changed;
|
||||
MP_QSTR_language__progress;
|
||||
MP_QSTR_language__title;
|
||||
MP_QSTR_lines;
|
||||
MP_QSTR_load_from_flash;
|
||||
MP_QSTR_lockscreen__tap_to_connect;
|
||||
MP_QSTR_lockscreen__tap_to_unlock;
|
||||
MP_QSTR_lockscreen__title_locked;
|
||||
MP_QSTR_lockscreen__title_not_connected;
|
||||
MP_QSTR_max_count;
|
||||
MP_QSTR_max_feerate;
|
||||
MP_QSTR_max_len;
|
||||
MP_QSTR_max_rounds;
|
||||
MP_QSTR_min_count;
|
||||
MP_QSTR_misc__decrypt_value;
|
||||
MP_QSTR_misc__encrypt_value;
|
||||
MP_QSTR_misc__title_suite_labeling;
|
||||
MP_QSTR_modify_amount__decrease_amount;
|
||||
MP_QSTR_modify_amount__increase_amount;
|
||||
MP_QSTR_modify_amount__new_amount;
|
||||
MP_QSTR_modify_amount__title;
|
||||
MP_QSTR_modify_fee__decrease_fee;
|
||||
MP_QSTR_modify_fee__fee_rate;
|
||||
MP_QSTR_modify_fee__increase_fee;
|
||||
MP_QSTR_modify_fee__new_transaction_fee;
|
||||
MP_QSTR_modify_fee__no_change;
|
||||
MP_QSTR_modify_fee__title;
|
||||
MP_QSTR_modify_fee__transaction_fee;
|
||||
MP_QSTR_monero__confirm_export;
|
||||
MP_QSTR_monero__confirm_ki_sync;
|
||||
MP_QSTR_monero__confirm_refresh;
|
||||
MP_QSTR_monero__confirm_unlock_time;
|
||||
MP_QSTR_monero__hashing_inputs;
|
||||
MP_QSTR_monero__payment_id;
|
||||
MP_QSTR_monero__postprocessing;
|
||||
MP_QSTR_monero__processing;
|
||||
MP_QSTR_monero__processing_inputs;
|
||||
MP_QSTR_monero__processing_outputs;
|
||||
MP_QSTR_monero__signing;
|
||||
MP_QSTR_monero__signing_inputs;
|
||||
MP_QSTR_monero__unlock_time_set_template;
|
||||
MP_QSTR_monero__wanna_export_tx_der;
|
||||
MP_QSTR_monero__wanna_export_tx_key;
|
||||
MP_QSTR_monero__wanna_export_watchkey;
|
||||
MP_QSTR_monero__wanna_start_refresh;
|
||||
MP_QSTR_monero__wanna_sync_key_images;
|
||||
MP_QSTR_multiple_pages_texts;
|
||||
MP_QSTR_nem__absolute;
|
||||
MP_QSTR_nem__activate;
|
||||
MP_QSTR_nem__add;
|
||||
MP_QSTR_nem__confirm_action;
|
||||
MP_QSTR_nem__confirm_address;
|
||||
MP_QSTR_nem__confirm_creation_fee;
|
||||
MP_QSTR_nem__confirm_mosaic;
|
||||
MP_QSTR_nem__confirm_multisig_fee;
|
||||
MP_QSTR_nem__confirm_namespace;
|
||||
MP_QSTR_nem__confirm_payload;
|
||||
MP_QSTR_nem__confirm_properties;
|
||||
MP_QSTR_nem__confirm_rental_fee;
|
||||
MP_QSTR_nem__confirm_transfer_of;
|
||||
MP_QSTR_nem__convert_account_to_multisig;
|
||||
MP_QSTR_nem__cosign_transaction_for;
|
||||
MP_QSTR_nem__cosignatory;
|
||||
MP_QSTR_nem__create_mosaic;
|
||||
MP_QSTR_nem__create_namespace;
|
||||
MP_QSTR_nem__deactivate;
|
||||
MP_QSTR_nem__decrease;
|
||||
MP_QSTR_nem__description;
|
||||
MP_QSTR_nem__divisibility_and_levy_cannot_be_shown;
|
||||
MP_QSTR_nem__encrypted;
|
||||
MP_QSTR_nem__final_confirm;
|
||||
MP_QSTR_nem__immutable;
|
||||
MP_QSTR_nem__increase;
|
||||
MP_QSTR_nem__initial_supply;
|
||||
MP_QSTR_nem__initiate_transaction_for;
|
||||
MP_QSTR_nem__levy_divisibility;
|
||||
MP_QSTR_nem__levy_fee;
|
||||
MP_QSTR_nem__levy_fee_of;
|
||||
MP_QSTR_nem__levy_mosaic;
|
||||
MP_QSTR_nem__levy_namespace;
|
||||
MP_QSTR_nem__levy_recipient;
|
||||
MP_QSTR_nem__levy_type;
|
||||
MP_QSTR_nem__modify_supply_for;
|
||||
MP_QSTR_nem__modify_the_number_of_cosignatories_by;
|
||||
MP_QSTR_nem__mutable;
|
||||
MP_QSTR_nem__of;
|
||||
MP_QSTR_nem__percentile;
|
||||
MP_QSTR_nem__raw_units_template;
|
||||
MP_QSTR_nem__remote_harvesting;
|
||||
MP_QSTR_nem__remove;
|
||||
MP_QSTR_nem__set_minimum_cosignatories_to;
|
||||
MP_QSTR_nem__sign_tx_fee_template;
|
||||
MP_QSTR_nem__supply_change;
|
||||
MP_QSTR_nem__supply_units_template;
|
||||
MP_QSTR_nem__transferable;
|
||||
MP_QSTR_nem__under_namespace;
|
||||
MP_QSTR_nem__unencrypted;
|
||||
MP_QSTR_nem__unknown_mosaic;
|
||||
MP_QSTR_notification;
|
||||
MP_QSTR_notification_level;
|
||||
MP_QSTR_page_count;
|
||||
MP_QSTR_pages;
|
||||
MP_QSTR_paint;
|
||||
MP_QSTR_passphrase__access_hidden_wallet;
|
||||
MP_QSTR_passphrase__always_on_device;
|
||||
MP_QSTR_passphrase__from_host_not_shown;
|
||||
MP_QSTR_passphrase__hidden_wallet;
|
||||
MP_QSTR_passphrase__hide;
|
||||
MP_QSTR_passphrase__next_screen_will_show_passphrase;
|
||||
MP_QSTR_passphrase__please_enter;
|
||||
MP_QSTR_passphrase__revoke_on_device;
|
||||
MP_QSTR_passphrase__title_confirm;
|
||||
MP_QSTR_passphrase__title_enter;
|
||||
MP_QSTR_passphrase__title_hide;
|
||||
MP_QSTR_passphrase__title_settings;
|
||||
MP_QSTR_passphrase__title_source;
|
||||
MP_QSTR_passphrase__turn_off;
|
||||
MP_QSTR_passphrase__turn_on;
|
||||
MP_QSTR_path;
|
||||
MP_QSTR_pin__change;
|
||||
MP_QSTR_pin__changed;
|
||||
MP_QSTR_pin__cursor_will_change;
|
||||
MP_QSTR_pin__diff_from_wipe_code;
|
||||
MP_QSTR_pin__disabled;
|
||||
MP_QSTR_pin__enabled;
|
||||
MP_QSTR_pin__enter;
|
||||
MP_QSTR_pin__enter_new;
|
||||
MP_QSTR_pin__entered_not_valid;
|
||||
MP_QSTR_pin__info;
|
||||
MP_QSTR_pin__invalid_pin;
|
||||
MP_QSTR_pin__last_attempt;
|
||||
MP_QSTR_pin__mismatch;
|
||||
MP_QSTR_pin__pin_mismatch;
|
||||
MP_QSTR_pin__please_check_again;
|
||||
MP_QSTR_pin__reenter_new;
|
||||
MP_QSTR_pin__reenter_to_confirm;
|
||||
MP_QSTR_pin__should_be_long;
|
||||
MP_QSTR_pin__title_check_pin;
|
||||
MP_QSTR_pin__title_settings;
|
||||
MP_QSTR_pin__title_wrong_pin;
|
||||
MP_QSTR_pin__tries_left;
|
||||
MP_QSTR_pin__turn_off;
|
||||
MP_QSTR_pin__turn_on;
|
||||
MP_QSTR_pin__wrong_pin;
|
||||
MP_QSTR_plurals__contains_x_keys;
|
||||
MP_QSTR_plurals__lock_after_x_hours;
|
||||
MP_QSTR_plurals__lock_after_x_milliseconds;
|
||||
MP_QSTR_plurals__lock_after_x_minutes;
|
||||
MP_QSTR_plurals__lock_after_x_seconds;
|
||||
MP_QSTR_plurals__sign_x_actions;
|
||||
MP_QSTR_plurals__transaction_of_x_operations;
|
||||
MP_QSTR_plurals__x_groups_needed;
|
||||
MP_QSTR_plurals__x_shares_needed;
|
||||
MP_QSTR_progress__authenticity_check;
|
||||
MP_QSTR_progress__done;
|
||||
MP_QSTR_progress__loading_transaction;
|
||||
MP_QSTR_progress__locking_device;
|
||||
MP_QSTR_progress__one_second_left;
|
||||
MP_QSTR_progress__please_wait;
|
||||
MP_QSTR_progress__processing;
|
||||
MP_QSTR_progress__refreshing;
|
||||
MP_QSTR_progress__signing_transaction;
|
||||
MP_QSTR_progress__syncing;
|
||||
MP_QSTR_progress__x_seconds_left_template;
|
||||
MP_QSTR_progress_event;
|
||||
MP_QSTR_prompt;
|
||||
MP_QSTR_qr_title;
|
||||
MP_QSTR_reboot_to_bootloader__just_a_moment;
|
||||
MP_QSTR_reboot_to_bootloader__restart;
|
||||
MP_QSTR_reboot_to_bootloader__title;
|
||||
MP_QSTR_reboot_to_bootloader__version_by_template;
|
||||
MP_QSTR_recovery__cancel_dry_run;
|
||||
MP_QSTR_recovery__check_dry_run;
|
||||
MP_QSTR_recovery__cursor_will_change;
|
||||
MP_QSTR_recovery__dry_run_bip39_valid_match;
|
||||
MP_QSTR_recovery__dry_run_bip39_valid_mismatch;
|
||||
MP_QSTR_recovery__dry_run_slip39_valid_match;
|
||||
MP_QSTR_recovery__dry_run_slip39_valid_mismatch;
|
||||
MP_QSTR_recovery__enter_any_share;
|
||||
MP_QSTR_recovery__enter_backup;
|
||||
MP_QSTR_recovery__enter_different_share;
|
||||
MP_QSTR_recovery__enter_share_from_diff_group;
|
||||
MP_QSTR_recovery__group_num_template;
|
||||
MP_QSTR_recovery__group_threshold_reached;
|
||||
MP_QSTR_recovery__invalid_seed_entered;
|
||||
MP_QSTR_recovery__invalid_share_entered;
|
||||
MP_QSTR_recovery__more_shares_needed;
|
||||
MP_QSTR_recovery__num_of_words;
|
||||
MP_QSTR_recovery__only_first_n_letters;
|
||||
MP_QSTR_recovery__progress_will_be_lost;
|
||||
MP_QSTR_recovery__select_num_of_words;
|
||||
MP_QSTR_recovery__share_already_entered;
|
||||
MP_QSTR_recovery__share_from_another_shamir;
|
||||
MP_QSTR_recovery__share_num_template;
|
||||
MP_QSTR_recovery__title;
|
||||
MP_QSTR_recovery__title_cancel_dry_run;
|
||||
MP_QSTR_recovery__title_cancel_recovery;
|
||||
MP_QSTR_recovery__title_dry_run;
|
||||
MP_QSTR_recovery__title_recover;
|
||||
MP_QSTR_recovery__title_remaining_shares;
|
||||
MP_QSTR_recovery__type_word_x_of_y_template;
|
||||
MP_QSTR_recovery__wallet_recovered;
|
||||
MP_QSTR_recovery__wanna_cancel_dry_run;
|
||||
MP_QSTR_recovery__wanna_cancel_recovery;
|
||||
MP_QSTR_recovery__word_count_template;
|
||||
MP_QSTR_recovery__word_x_of_y_template;
|
||||
MP_QSTR_recovery__x_more_items_starting_template_plural;
|
||||
MP_QSTR_recovery__x_more_shares_needed_template_plural;
|
||||
MP_QSTR_recovery__x_of_y_entered_template;
|
||||
MP_QSTR_recovery__you_have_entered;
|
||||
MP_QSTR_request_bip39;
|
||||
MP_QSTR_request_complete_repaint;
|
||||
MP_QSTR_request_number;
|
||||
MP_QSTR_request_passphrase;
|
||||
MP_QSTR_request_pin;
|
||||
MP_QSTR_request_slip39;
|
||||
MP_QSTR_reset__advanced_group_threshold_info;
|
||||
MP_QSTR_reset__all_x_of_y_template;
|
||||
MP_QSTR_reset__any_x_of_y_template;
|
||||
MP_QSTR_reset__button_create;
|
||||
MP_QSTR_reset__button_recover;
|
||||
MP_QSTR_reset__by_continuing;
|
||||
MP_QSTR_reset__check_backup_title;
|
||||
MP_QSTR_reset__check_group_share_title_template;
|
||||
MP_QSTR_reset__check_seed_title;
|
||||
MP_QSTR_reset__check_share_title_template;
|
||||
MP_QSTR_reset__continue_with_next_share;
|
||||
MP_QSTR_reset__continue_with_share_template;
|
||||
MP_QSTR_reset__finished_verifying_group_template;
|
||||
MP_QSTR_reset__finished_verifying_seed;
|
||||
MP_QSTR_reset__finished_verifying_shares;
|
||||
MP_QSTR_reset__group_description;
|
||||
MP_QSTR_reset__group_info;
|
||||
MP_QSTR_reset__group_share_checked_successfully_template;
|
||||
MP_QSTR_reset__group_share_title_template;
|
||||
MP_QSTR_reset__more_info_at;
|
||||
MP_QSTR_reset__need_all_share_template;
|
||||
MP_QSTR_reset__need_any_share_template;
|
||||
MP_QSTR_reset__needed_to_form_a_group;
|
||||
MP_QSTR_reset__needed_to_recover_your_wallet;
|
||||
MP_QSTR_reset__never_make_digital_copy;
|
||||
MP_QSTR_reset__num_of_share_holders_template;
|
||||
MP_QSTR_reset__num_of_shares_advanced_info_template;
|
||||
MP_QSTR_reset__num_of_shares_basic_info;
|
||||
MP_QSTR_reset__num_shares_for_group_template;
|
||||
MP_QSTR_reset__number_of_shares_info;
|
||||
MP_QSTR_reset__one_share;
|
||||
MP_QSTR_reset__only_one_share_will_be_created;
|
||||
MP_QSTR_reset__recovery_seed_title;
|
||||
MP_QSTR_reset__recovery_share_title_template;
|
||||
MP_QSTR_reset__required_number_of_groups;
|
||||
MP_QSTR_reset__select_correct_word;
|
||||
MP_QSTR_reset__select_word_template;
|
||||
MP_QSTR_reset__select_word_x_of_y_template;
|
||||
MP_QSTR_reset__set_it_to_count_template;
|
||||
MP_QSTR_reset__share_checked_successfully_template;
|
||||
MP_QSTR_reset__share_words_title;
|
||||
MP_QSTR_reset__slip39_checklist_num_groups;
|
||||
MP_QSTR_reset__slip39_checklist_num_shares;
|
||||
MP_QSTR_reset__slip39_checklist_set_num_groups;
|
||||
MP_QSTR_reset__slip39_checklist_set_num_shares;
|
||||
MP_QSTR_reset__slip39_checklist_set_sizes;
|
||||
MP_QSTR_reset__slip39_checklist_set_sizes_longer;
|
||||
MP_QSTR_reset__slip39_checklist_set_threshold;
|
||||
MP_QSTR_reset__slip39_checklist_title;
|
||||
MP_QSTR_reset__slip39_checklist_write_down;
|
||||
MP_QSTR_reset__slip39_checklist_write_down_recovery;
|
||||
MP_QSTR_reset__the_threshold_sets_the_number_of_shares;
|
||||
MP_QSTR_reset__threshold_info;
|
||||
MP_QSTR_reset__title_backup_is_done;
|
||||
MP_QSTR_reset__title_create_wallet;
|
||||
MP_QSTR_reset__title_create_wallet_shamir;
|
||||
MP_QSTR_reset__title_group_threshold;
|
||||
MP_QSTR_reset__title_number_of_groups;
|
||||
MP_QSTR_reset__title_number_of_shares;
|
||||
MP_QSTR_reset__title_set_group_threshold;
|
||||
MP_QSTR_reset__title_set_number_of_groups;
|
||||
MP_QSTR_reset__title_set_number_of_shares;
|
||||
MP_QSTR_reset__title_set_threshold;
|
||||
MP_QSTR_reset__to_form_group_template;
|
||||
MP_QSTR_reset__tos_link;
|
||||
MP_QSTR_reset__total_number_of_shares_in_group_template;
|
||||
MP_QSTR_reset__use_your_backup;
|
||||
MP_QSTR_reset__write_down_words_template;
|
||||
MP_QSTR_reset__wrong_word_selected;
|
||||
MP_QSTR_reset__you_need_one_share;
|
||||
MP_QSTR_reset__your_backup_is_done;
|
||||
MP_QSTR_reverse;
|
||||
MP_QSTR_ripple__confirm_tag;
|
||||
MP_QSTR_ripple__destination_tag_template;
|
||||
MP_QSTR_rotation__change_template;
|
||||
MP_QSTR_rotation__east;
|
||||
MP_QSTR_rotation__north;
|
||||
MP_QSTR_rotation__south;
|
||||
MP_QSTR_rotation__title_change;
|
||||
MP_QSTR_rotation__west;
|
||||
MP_QSTR_safety_checks__approve_unsafe_always;
|
||||
MP_QSTR_safety_checks__approve_unsafe_temporary;
|
||||
MP_QSTR_safety_checks__enforce_strict;
|
||||
MP_QSTR_safety_checks__title;
|
||||
MP_QSTR_safety_checks__title_safety_override;
|
||||
MP_QSTR_sd_card__all_data_will_be_lost;
|
||||
MP_QSTR_sd_card__card_required;
|
||||
MP_QSTR_sd_card__disable;
|
||||
MP_QSTR_sd_card__disabled;
|
||||
MP_QSTR_sd_card__enable;
|
||||
MP_QSTR_sd_card__enabled;
|
||||
MP_QSTR_sd_card__error;
|
||||
MP_QSTR_sd_card__format_card;
|
||||
MP_QSTR_sd_card__insert_correct_card;
|
||||
MP_QSTR_sd_card__please_insert;
|
||||
MP_QSTR_sd_card__please_unplug_and_insert;
|
||||
MP_QSTR_sd_card__problem_accessing;
|
||||
MP_QSTR_sd_card__refresh;
|
||||
MP_QSTR_sd_card__refreshed;
|
||||
MP_QSTR_sd_card__restart;
|
||||
MP_QSTR_sd_card__title;
|
||||
MP_QSTR_sd_card__title_problem;
|
||||
MP_QSTR_sd_card__unknown_filesystem;
|
||||
MP_QSTR_sd_card__unplug_and_insert_correct;
|
||||
MP_QSTR_sd_card__use_different_card;
|
||||
MP_QSTR_sd_card__wanna_format;
|
||||
MP_QSTR_sd_card__wrong_sd_card;
|
||||
MP_QSTR_select_word;
|
||||
MP_QSTR_select_word_count;
|
||||
MP_QSTR_send__address_path;
|
||||
MP_QSTR_send__confirm_sending;
|
||||
MP_QSTR_send__from_multiple_accounts;
|
||||
MP_QSTR_send__including_fee;
|
||||
MP_QSTR_send__maximum_fee;
|
||||
MP_QSTR_send__receiving_to_multisig;
|
||||
MP_QSTR_send__title_confirm_sending;
|
||||
MP_QSTR_send__title_joint_transaction;
|
||||
MP_QSTR_send__title_receiving_to;
|
||||
MP_QSTR_send__title_sending;
|
||||
MP_QSTR_send__title_sending_amount;
|
||||
MP_QSTR_send__title_sending_to;
|
||||
MP_QSTR_send__to_the_total_amount;
|
||||
MP_QSTR_send__total_amount;
|
||||
MP_QSTR_send__transaction_id;
|
||||
MP_QSTR_send__you_are_contributing;
|
||||
MP_QSTR_share_words;
|
||||
MP_QSTR_share_words__words_in_order;
|
||||
MP_QSTR_share_words__wrote_down_all;
|
||||
MP_QSTR_show_address_details;
|
||||
MP_QSTR_show_checklist;
|
||||
MP_QSTR_show_error;
|
||||
@ -131,32 +802,211 @@ static void _librust_qstrs(void) {
|
||||
MP_QSTR_show_share_words;
|
||||
MP_QSTR_show_simple;
|
||||
MP_QSTR_show_success;
|
||||
MP_QSTR_show_wait_text;
|
||||
MP_QSTR_show_warning;
|
||||
MP_QSTR_sign;
|
||||
MP_QSTR_sign_message__bytes_template;
|
||||
MP_QSTR_sign_message__confirm_address;
|
||||
MP_QSTR_sign_message__confirm_message;
|
||||
MP_QSTR_sign_message__message_size;
|
||||
MP_QSTR_sign_message__verify_address;
|
||||
MP_QSTR_skip_first_paint;
|
||||
MP_QSTR_solana__account_index;
|
||||
MP_QSTR_solana__associated_token_account;
|
||||
MP_QSTR_solana__confirm_multisig;
|
||||
MP_QSTR_solana__expected_fee;
|
||||
MP_QSTR_solana__instruction_accounts_template;
|
||||
MP_QSTR_solana__instruction_data;
|
||||
MP_QSTR_solana__instruction_is_multisig;
|
||||
MP_QSTR_solana__is_provided_via_lookup_table_template;
|
||||
MP_QSTR_solana__lookup_table_address;
|
||||
MP_QSTR_solana__multiple_signers;
|
||||
MP_QSTR_solana__token_address;
|
||||
MP_QSTR_solana__transaction_contains_unknown_instructions;
|
||||
MP_QSTR_solana__transaction_requires_x_signers_template;
|
||||
MP_QSTR_spending_amount;
|
||||
MP_QSTR_stellar__account_merge;
|
||||
MP_QSTR_stellar__account_thresholds;
|
||||
MP_QSTR_stellar__add_signer;
|
||||
MP_QSTR_stellar__add_trust;
|
||||
MP_QSTR_stellar__all_will_be_sent_to;
|
||||
MP_QSTR_stellar__allow_trust;
|
||||
MP_QSTR_stellar__asset;
|
||||
MP_QSTR_stellar__balance_id;
|
||||
MP_QSTR_stellar__bump_sequence;
|
||||
MP_QSTR_stellar__buying;
|
||||
MP_QSTR_stellar__claim_claimable_balance;
|
||||
MP_QSTR_stellar__clear_data;
|
||||
MP_QSTR_stellar__clear_flags;
|
||||
MP_QSTR_stellar__confirm_issuer;
|
||||
MP_QSTR_stellar__confirm_memo;
|
||||
MP_QSTR_stellar__confirm_network;
|
||||
MP_QSTR_stellar__confirm_operation;
|
||||
MP_QSTR_stellar__confirm_stellar;
|
||||
MP_QSTR_stellar__confirm_timebounds;
|
||||
MP_QSTR_stellar__create_account;
|
||||
MP_QSTR_stellar__debited_amount;
|
||||
MP_QSTR_stellar__delete;
|
||||
MP_QSTR_stellar__delete_passive_offer;
|
||||
MP_QSTR_stellar__delete_trust;
|
||||
MP_QSTR_stellar__destination;
|
||||
MP_QSTR_stellar__exchanges_require_memo;
|
||||
MP_QSTR_stellar__final_confirm;
|
||||
MP_QSTR_stellar__hash;
|
||||
MP_QSTR_stellar__high;
|
||||
MP_QSTR_stellar__home_domain;
|
||||
MP_QSTR_stellar__inflation;
|
||||
MP_QSTR_stellar__initial_balance;
|
||||
MP_QSTR_stellar__initialize_signing_with;
|
||||
MP_QSTR_stellar__issuer_template;
|
||||
MP_QSTR_stellar__key;
|
||||
MP_QSTR_stellar__limit;
|
||||
MP_QSTR_stellar__low;
|
||||
MP_QSTR_stellar__master_weight;
|
||||
MP_QSTR_stellar__medium;
|
||||
MP_QSTR_stellar__new_offer;
|
||||
MP_QSTR_stellar__new_passive_offer;
|
||||
MP_QSTR_stellar__no_memo_set;
|
||||
MP_QSTR_stellar__no_restriction;
|
||||
MP_QSTR_stellar__on_network_template;
|
||||
MP_QSTR_stellar__path_pay;
|
||||
MP_QSTR_stellar__path_pay_at_least;
|
||||
MP_QSTR_stellar__pay;
|
||||
MP_QSTR_stellar__pay_at_most;
|
||||
MP_QSTR_stellar__preauth_transaction;
|
||||
MP_QSTR_stellar__price_per_template;
|
||||
MP_QSTR_stellar__private_network;
|
||||
MP_QSTR_stellar__remove_signer;
|
||||
MP_QSTR_stellar__revoke_trust;
|
||||
MP_QSTR_stellar__selling;
|
||||
MP_QSTR_stellar__set_data;
|
||||
MP_QSTR_stellar__set_flags;
|
||||
MP_QSTR_stellar__set_sequence_to_template;
|
||||
MP_QSTR_stellar__sign_tx_count_template;
|
||||
MP_QSTR_stellar__sign_tx_fee_template;
|
||||
MP_QSTR_stellar__source_account;
|
||||
MP_QSTR_stellar__testnet_network;
|
||||
MP_QSTR_stellar__trusted_account;
|
||||
MP_QSTR_stellar__update;
|
||||
MP_QSTR_stellar__valid_from;
|
||||
MP_QSTR_stellar__valid_to;
|
||||
MP_QSTR_stellar__value_sha256;
|
||||
MP_QSTR_stellar__wanna_clean_value_key_template;
|
||||
MP_QSTR_stellar__your_account;
|
||||
MP_QSTR_subprompt;
|
||||
MP_QSTR_subtitle;
|
||||
MP_QSTR_tezos__baker_address;
|
||||
MP_QSTR_tezos__balance;
|
||||
MP_QSTR_tezos__ballot;
|
||||
MP_QSTR_tezos__confirm_delegation;
|
||||
MP_QSTR_tezos__confirm_origination;
|
||||
MP_QSTR_tezos__delegator;
|
||||
MP_QSTR_tezos__proposal;
|
||||
MP_QSTR_tezos__register_delegate;
|
||||
MP_QSTR_tezos__remove_delegation;
|
||||
MP_QSTR_tezos__submit_ballot;
|
||||
MP_QSTR_tezos__submit_proposal;
|
||||
MP_QSTR_tezos__submit_proposals;
|
||||
MP_QSTR_time_ms;
|
||||
MP_QSTR_timer;
|
||||
MP_QSTR_title;
|
||||
MP_QSTR_total_amount;
|
||||
MP_QSTR_total_fee_new;
|
||||
MP_QSTR_total_label;
|
||||
MP_QSTR_total_len;
|
||||
MP_QSTR_touch_event;
|
||||
MP_QSTR_trace;
|
||||
MP_QSTR_trezorproto;
|
||||
MP_QSTR_trezorui2;
|
||||
MP_QSTR_tutorial;
|
||||
MP_QSTR_tutorial__middle_click;
|
||||
MP_QSTR_tutorial__press_and_hold;
|
||||
MP_QSTR_tutorial__ready_to_use;
|
||||
MP_QSTR_tutorial__scroll_down;
|
||||
MP_QSTR_tutorial__sure_you_want_skip;
|
||||
MP_QSTR_tutorial__title_hello;
|
||||
MP_QSTR_tutorial__title_screen_scroll;
|
||||
MP_QSTR_tutorial__title_skip;
|
||||
MP_QSTR_tutorial__title_tutorial_complete;
|
||||
MP_QSTR_tutorial__use_trezor;
|
||||
MP_QSTR_tutorial__welcome_press_right;
|
||||
MP_QSTR_type_for_name;
|
||||
MP_QSTR_type_for_wire;
|
||||
MP_QSTR_u2f__get;
|
||||
MP_QSTR_u2f__set_template;
|
||||
MP_QSTR_u2f__title_get;
|
||||
MP_QSTR_u2f__title_set;
|
||||
MP_QSTR_usb_event;
|
||||
MP_QSTR_user_fee_change;
|
||||
MP_QSTR_value;
|
||||
MP_QSTR_verb;
|
||||
MP_QSTR_verb_cancel;
|
||||
MP_QSTR_verify;
|
||||
MP_QSTR_version;
|
||||
MP_QSTR_warning;
|
||||
MP_QSTR_wipe__info;
|
||||
MP_QSTR_wipe__title;
|
||||
MP_QSTR_wipe__want_to_wipe;
|
||||
MP_QSTR_wipe_code__change;
|
||||
MP_QSTR_wipe_code__changed;
|
||||
MP_QSTR_wipe_code__diff_from_pin;
|
||||
MP_QSTR_wipe_code__disabled;
|
||||
MP_QSTR_wipe_code__enabled;
|
||||
MP_QSTR_wipe_code__enter_new;
|
||||
MP_QSTR_wipe_code__info;
|
||||
MP_QSTR_wipe_code__invalid;
|
||||
MP_QSTR_wipe_code__mismatch;
|
||||
MP_QSTR_wipe_code__reenter;
|
||||
MP_QSTR_wipe_code__reenter_to_confirm;
|
||||
MP_QSTR_wipe_code__title_check;
|
||||
MP_QSTR_wipe_code__title_invalid;
|
||||
MP_QSTR_wipe_code__title_settings;
|
||||
MP_QSTR_wipe_code__turn_off;
|
||||
MP_QSTR_wipe_code__turn_on;
|
||||
MP_QSTR_wipe_code__wipe_code_mismatch;
|
||||
MP_QSTR_word_count__title;
|
||||
MP_QSTR_words;
|
||||
MP_QSTR_words__account;
|
||||
MP_QSTR_words__account_colon;
|
||||
MP_QSTR_words__address;
|
||||
MP_QSTR_words__amount;
|
||||
MP_QSTR_words__are_you_sure;
|
||||
MP_QSTR_words__array_of;
|
||||
MP_QSTR_words__blockhash;
|
||||
MP_QSTR_words__buying;
|
||||
MP_QSTR_words__confirm;
|
||||
MP_QSTR_words__confirm_fee;
|
||||
MP_QSTR_words__contains;
|
||||
MP_QSTR_words__continue_anyway;
|
||||
MP_QSTR_words__continue_with;
|
||||
MP_QSTR_words__error;
|
||||
MP_QSTR_words__fee;
|
||||
MP_QSTR_words__from;
|
||||
MP_QSTR_words__keep_it_safe;
|
||||
MP_QSTR_words__know_what_your_doing;
|
||||
MP_QSTR_words__my_trezor;
|
||||
MP_QSTR_words__no;
|
||||
MP_QSTR_words__outputs;
|
||||
MP_QSTR_words__please_check_again;
|
||||
MP_QSTR_words__please_try_again;
|
||||
MP_QSTR_words__really_wanna;
|
||||
MP_QSTR_words__recipient;
|
||||
MP_QSTR_words__sign;
|
||||
MP_QSTR_words__signer;
|
||||
MP_QSTR_words__title_check;
|
||||
MP_QSTR_words__title_group;
|
||||
MP_QSTR_words__title_information;
|
||||
MP_QSTR_words__title_remember;
|
||||
MP_QSTR_words__title_share;
|
||||
MP_QSTR_words__title_shares;
|
||||
MP_QSTR_words__title_success;
|
||||
MP_QSTR_words__title_summary;
|
||||
MP_QSTR_words__title_threshold;
|
||||
MP_QSTR_words__unknown;
|
||||
MP_QSTR_words__warning;
|
||||
MP_QSTR_words__writable;
|
||||
MP_QSTR_words__yes;
|
||||
MP_QSTR_write;
|
||||
MP_QSTR_wrong_pin;
|
||||
MP_QSTR_xpubs;
|
||||
}
|
||||
|
@ -27,6 +27,8 @@ mod storage;
|
||||
mod time;
|
||||
#[cfg(feature = "ui_debug")]
|
||||
mod trace;
|
||||
#[cfg(feature = "translations")]
|
||||
pub mod translations;
|
||||
|
||||
#[cfg(feature = "ui")]
|
||||
#[macro_use]
|
||||
|
@ -24,7 +24,7 @@ use super::ffi;
|
||||
/// The `off` field represents offset from the `ptr` and allows us to do
|
||||
/// substring slices while keeping the head pointer as required by GC.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct StrBuffer {
|
||||
ptr: *const u8,
|
||||
len: u16,
|
||||
|
@ -137,8 +137,9 @@ macro_rules! obj_dict {
|
||||
macro_rules! obj_type {
|
||||
(name: $name:expr,
|
||||
$(locals: $locals:expr,)?
|
||||
$(attr_fn: $attr_fn:ident,)?
|
||||
$(call_fn: $call_fn:ident,)?
|
||||
$(make_new_fn: $make_new_fn:path,)?
|
||||
$(attr_fn: $attr_fn:path,)?
|
||||
$(call_fn: $call_fn:path,)?
|
||||
) => {{
|
||||
#[allow(unused_unsafe)]
|
||||
unsafe {
|
||||
@ -156,6 +157,11 @@ macro_rules! obj_type {
|
||||
let mut call: ffi::mp_call_fun_t = None;
|
||||
$(call = Some($call_fn);)?
|
||||
|
||||
#[allow(unused_mut)]
|
||||
#[allow(unused_assignments)]
|
||||
let mut make_new: ffi::mp_make_new_fun_t = None;
|
||||
$(make_new = Some($make_new_fn);)?
|
||||
|
||||
// TODO: This is safe only if we pass in `Dict` with fixed `Map` (created by
|
||||
// `Map::fixed()`, usually through `obj_map!`), because only then will
|
||||
// MicroPython treat `locals_dict` as immutable, and make the mutable cast safe.
|
||||
@ -171,7 +177,7 @@ macro_rules! obj_type {
|
||||
flags: 0,
|
||||
name,
|
||||
print: None,
|
||||
make_new: None,
|
||||
make_new,
|
||||
call,
|
||||
unary_op: None,
|
||||
binary_op: None,
|
||||
|
@ -15,7 +15,7 @@ impl Qstr {
|
||||
// micropython/py/obj.h #define MP_OBJ_NEW_QSTR(qst)
|
||||
// ((mp_obj_t)((((mp_uint_t)(qst)) << 3) | 2))
|
||||
let bits = (self.0 << 3) | 2;
|
||||
unsafe { Obj::from_bits(bits as usize) }
|
||||
unsafe { Obj::from_bits(bits) }
|
||||
}
|
||||
|
||||
pub const fn from_obj_bits(bits: cty::uintptr_t) -> Self {
|
||||
|
@ -17,6 +17,14 @@ impl Type {
|
||||
ObjBase { type_: self }
|
||||
}
|
||||
|
||||
pub const fn as_obj(&'static self) -> Obj {
|
||||
// SAFETY:
|
||||
// - We are an object struct with a base and a type.
|
||||
// - 'static lifetime holds us in place.
|
||||
// - MicroPython is smart enough not to mutate `mp_obj_type_t` objects.
|
||||
unsafe { Obj::from_ptr(self as *const _ as *mut _) }
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
pub fn name(&self) -> &'static str {
|
||||
use super::qstr::Qstr;
|
||||
|
@ -35,6 +35,7 @@ const SD_SALT_AUTH_KEY: u16 = FLAG_PUBLIC | APP_DEVICE | 0x0012;
|
||||
const INITIALIZED: u16 = FLAG_PUBLIC | APP_DEVICE | 0x0013;
|
||||
const SAFETY_CHECK_LEVEL: u16 = APP_DEVICE | 0x0014;
|
||||
const EXPERIMENTAL_FEATURES: u16 = APP_DEVICE | 0x0015;
|
||||
const HIDE_PASSPHRASE_FROM_HOST: u16 = APP_DEVICE | 0x0016;
|
||||
|
||||
pub fn get_avatar_len() -> StorageResult<usize> {
|
||||
get_length(HOMESCREEN)
|
||||
|
@ -1,5 +1,14 @@
|
||||
use heapless::String;
|
||||
|
||||
#[cfg(feature = "micropython")]
|
||||
use crate::error::Error;
|
||||
|
||||
#[cfg(feature = "micropython")]
|
||||
use crate::micropython::{buffer::StrBuffer, obj::Obj};
|
||||
|
||||
#[cfg(feature = "translations")]
|
||||
use crate::translations::TR;
|
||||
|
||||
/// Trait for slicing off string prefix by a specified number of bytes.
|
||||
/// See `StringType` for deeper explanation.
|
||||
pub trait SkipPrefix {
|
||||
@ -23,9 +32,15 @@ impl SkipPrefix for &str {
|
||||
/// - create a new string by skipping some number of bytes (SkipPrefix) - used
|
||||
/// when rendering continuations of long strings
|
||||
/// - create a new string from a string literal (From<&'static str>)
|
||||
pub trait StringType: AsRef<str> + From<&'static str> + SkipPrefix {}
|
||||
pub trait StringType:
|
||||
AsRef<str> + From<&'static str> + Into<TString<'static>> + SkipPrefix
|
||||
{
|
||||
}
|
||||
|
||||
impl<T> StringType for T where T: AsRef<str> + From<&'static str> + SkipPrefix {}
|
||||
impl<T> StringType for T where
|
||||
T: AsRef<str> + From<&'static str> + Into<TString<'static>> + SkipPrefix
|
||||
{
|
||||
}
|
||||
|
||||
/// Unified-length String type, long enough for most simple use-cases.
|
||||
pub type ShortString = String<50>;
|
||||
@ -72,3 +87,92 @@ pub fn format_i64(num: i64, buffer: &mut [u8]) -> Option<&str> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum TString<'a> {
|
||||
#[cfg(feature = "micropython")]
|
||||
Allocated(StrBuffer),
|
||||
#[cfg(feature = "translations")]
|
||||
Translation(TR),
|
||||
Str(&'a str),
|
||||
}
|
||||
|
||||
impl TString<'_> {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.map(|s| s.is_empty())
|
||||
}
|
||||
|
||||
pub fn map<F, T>(&self, fun: F) -> T
|
||||
where
|
||||
F: for<'a> FnOnce(&'a str) -> T,
|
||||
T: 'static,
|
||||
{
|
||||
match self {
|
||||
#[cfg(feature = "micropython")]
|
||||
Self::Allocated(buf) => fun(buf.as_ref()),
|
||||
#[cfg(feature = "translations")]
|
||||
Self::Translation(tr) => tr.map_translated(fun),
|
||||
Self::Str(s) => fun(s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TString<'static> {
|
||||
#[cfg(feature = "translations")]
|
||||
pub const fn from_translation(tr: TR) -> Self {
|
||||
Self::Translation(tr)
|
||||
}
|
||||
|
||||
#[cfg(feature = "micropython")]
|
||||
pub const fn from_strbuffer(buf: StrBuffer) -> Self {
|
||||
Self::Allocated(buf)
|
||||
}
|
||||
|
||||
pub const fn empty() -> Self {
|
||||
Self::Str("")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TString<'a> {
|
||||
pub const fn from_str(s: &'a str) -> Self {
|
||||
Self::Str(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for TString<'a> {
|
||||
fn from(s: &'a str) -> Self {
|
||||
Self::Str(s)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "translations")]
|
||||
impl From<TR> for TString<'static> {
|
||||
fn from(tr: TR) -> Self {
|
||||
Self::from_translation(tr)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "micropython")]
|
||||
impl From<StrBuffer> for TString<'static> {
|
||||
fn from(buf: StrBuffer) -> Self {
|
||||
Self::from_strbuffer(buf)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "micropython")]
|
||||
impl TryFrom<Obj> for TString<'static> {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(obj: Obj) -> Result<Self, Self::Error> {
|
||||
Ok(StrBuffer::try_from(obj)?.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "micropython")]
|
||||
impl<'a> TryFrom<TString<'a>> for Obj {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(s: TString<'a>) -> Result<Self, Self::Error> {
|
||||
s.map(|t| t.try_into())
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
use crate::strutil::format_i64;
|
||||
use crate::strutil::{format_i64, TString};
|
||||
|
||||
pub trait Tracer {
|
||||
fn child(&mut self, key: &str, value: &dyn Trace);
|
||||
fn int(&mut self, key: &str, i: i64);
|
||||
fn string(&mut self, key: &str, s: &str);
|
||||
fn string(&mut self, key: &str, s: TString<'_>);
|
||||
fn bool(&mut self, key: &str, b: bool);
|
||||
fn null(&mut self, key: &str);
|
||||
|
||||
@ -11,14 +11,14 @@ pub trait Tracer {
|
||||
fn in_list(&mut self, key: &str, block: &dyn Fn(&mut dyn ListTracer));
|
||||
|
||||
fn component(&mut self, name: &str) {
|
||||
self.string("component", name);
|
||||
self.string("component", name.into());
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ListTracer {
|
||||
fn child(&mut self, value: &dyn Trace);
|
||||
fn int(&mut self, i: i64);
|
||||
fn string(&mut self, s: &str);
|
||||
fn string(&mut self, s: &TString<'_>);
|
||||
fn bool(&mut self, b: bool);
|
||||
|
||||
fn in_child(&mut self, block: &dyn Fn(&mut dyn Tracer));
|
||||
@ -121,9 +121,9 @@ impl<F: FnMut(&str)> ListTracer for JsonTracer<F> {
|
||||
self.write_int(i);
|
||||
}
|
||||
|
||||
fn string(&mut self, s: &str) {
|
||||
fn string(&mut self, s: &TString<'_>) {
|
||||
self.maybe_comma();
|
||||
self.write_str_quoted(s);
|
||||
s.map(|s| self.write_str_quoted(s));
|
||||
}
|
||||
|
||||
fn bool(&mut self, b: bool) {
|
||||
@ -160,9 +160,9 @@ impl<F: FnMut(&str)> Tracer for JsonTracer<F> {
|
||||
self.write_int(i);
|
||||
}
|
||||
|
||||
fn string(&mut self, key: &str, s: &str) {
|
||||
fn string(&mut self, key: &str, s: TString<'_>) {
|
||||
self.key(key);
|
||||
self.write_str_quoted(s);
|
||||
s.map(|s| self.write_str_quoted(s));
|
||||
}
|
||||
|
||||
fn bool(&mut self, key: &str, b: bool) {
|
||||
|
386
core/embed/rust/src/translations/blob.rs
Normal file
386
core/embed/rust/src/translations/blob.rs
Normal file
@ -0,0 +1,386 @@
|
||||
use core::{mem, str};
|
||||
|
||||
use crate::{
|
||||
crypto::{cosi, ed25519, merkle::merkle_root, sha256},
|
||||
error::Error,
|
||||
io::InputStream,
|
||||
};
|
||||
|
||||
use super::public_keys;
|
||||
|
||||
pub const MAX_HEADER_LEN: u16 = 1024;
|
||||
pub const EMPTY_BYTE: u8 = 0xFF;
|
||||
const SENTINEL_ID: u16 = 0xFFFF;
|
||||
|
||||
const SIGNATURE_THRESHOLD: u8 = 2;
|
||||
|
||||
// Maximum padding at the end of an offsets table (typically for alignment
|
||||
// purposes). We allow at most 3 for alignment 4. In practice right now this
|
||||
// should be max 1.
|
||||
const MAX_TABLE_PADDING: usize = 3;
|
||||
|
||||
const INVALID_TRANSLATIONS_BLOB: Error = value_error!("Invalid translations blob");
|
||||
|
||||
#[repr(packed)]
|
||||
struct OffsetEntry {
|
||||
pub id: u16,
|
||||
pub offset: u16,
|
||||
}
|
||||
|
||||
pub struct Table<'a> {
|
||||
offsets: &'a [OffsetEntry],
|
||||
data: &'a [u8],
|
||||
}
|
||||
|
||||
fn validate_offset_table(
|
||||
data_len: usize,
|
||||
mut iter: impl Iterator<Item = u16>,
|
||||
) -> Result<(), Error> {
|
||||
// every offset table must have at least the sentinel
|
||||
let mut prev = iter.next().ok_or(INVALID_TRANSLATIONS_BLOB)?;
|
||||
if prev != 0 {
|
||||
// first offset must always be 0 (even as a sentinel, indicating no data)
|
||||
return Err(INVALID_TRANSLATIONS_BLOB);
|
||||
}
|
||||
for next in iter {
|
||||
// offsets must be in ascending order
|
||||
if prev > next {
|
||||
return Err(INVALID_TRANSLATIONS_BLOB);
|
||||
}
|
||||
prev = next;
|
||||
}
|
||||
// sentinel needs to be at least data_len - MAX_TABLE_PADDING, and at most
|
||||
// data_len
|
||||
let sentinel = prev as usize;
|
||||
if sentinel < data_len - MAX_TABLE_PADDING || sentinel > data_len {
|
||||
return Err(INVALID_TRANSLATIONS_BLOB);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl<'a> Table<'a> {
|
||||
pub fn new(mut reader: InputStream<'a>) -> Result<Self, Error> {
|
||||
let count = reader.read_u16_le()?;
|
||||
// The offsets table is (count + 1) entries long, the last entry is a sentinel.
|
||||
let offsets_data = reader.read((count + 1) as usize * mem::size_of::<OffsetEntry>())?;
|
||||
// SAFETY: OffsetEntry is repr(packed) of two u16 values, so any four bytes are
|
||||
// a valid OffsetEntry value.
|
||||
let (_prefix, offsets, _suffix) = unsafe { offsets_data.align_to::<OffsetEntry>() };
|
||||
if !_prefix.is_empty() || !_suffix.is_empty() {
|
||||
return Err(INVALID_TRANSLATIONS_BLOB);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
offsets,
|
||||
data: reader.rest(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn validate(&self) -> Result<(), Error> {
|
||||
validate_offset_table(self.data.len(), self.offsets.iter().map(|it| it.offset))?;
|
||||
if !matches!(
|
||||
self.offsets.iter().last().map(|it| it.id),
|
||||
Some(SENTINEL_ID)
|
||||
) {
|
||||
return Err(INVALID_TRANSLATIONS_BLOB);
|
||||
}
|
||||
// check that the ids are sorted
|
||||
let Some(first_entry) = self.offsets.first() else {
|
||||
// empty table is sorted
|
||||
return Ok(());
|
||||
};
|
||||
let mut prev_id = first_entry.id;
|
||||
for entry in self.offsets.iter().skip(1) {
|
||||
if entry.id <= prev_id {
|
||||
return Err(INVALID_TRANSLATIONS_BLOB);
|
||||
}
|
||||
prev_id = entry.id;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get(&self, id: u16) -> Option<&'a [u8]> {
|
||||
self.offsets
|
||||
.binary_search_by_key(&id, |it| it.id)
|
||||
.ok()
|
||||
.and_then(|idx| {
|
||||
let start = self.offsets[idx].offset as usize;
|
||||
let end = self.offsets[idx + 1].offset as usize;
|
||||
self.data.get(start..end)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (u16, &'a [u8])> + '_ {
|
||||
let mut prev_offset = 0usize;
|
||||
self.offsets.iter().skip(1).map(move |entry| {
|
||||
let start = prev_offset;
|
||||
let end = entry.offset as usize;
|
||||
prev_offset = end;
|
||||
(entry.id, &self.data[start..end])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Translations<'a> {
|
||||
pub header: TranslationsHeader<'a>,
|
||||
translations: &'a [u8],
|
||||
translations_offsets: &'a [u16],
|
||||
fonts: Table<'a>,
|
||||
}
|
||||
|
||||
fn read_u16_prefixed_block<'a>(reader: &mut InputStream<'a>) -> Result<InputStream<'a>, Error> {
|
||||
let len = reader.read_u16_le()? as usize;
|
||||
reader.read_stream(len)
|
||||
}
|
||||
|
||||
impl<'a> Translations<'a> {
|
||||
const MAGIC: &'static [u8] = b"TRTR00";
|
||||
|
||||
pub fn new(blob: &'a [u8]) -> Result<Self, Error> {
|
||||
let mut blob_reader = InputStream::new(blob);
|
||||
|
||||
let (header, payload_reader) = TranslationsHeader::parse_from(&mut blob_reader)?;
|
||||
|
||||
// validate that the trailing bytes, if any, are empty
|
||||
let remaining = blob_reader.rest();
|
||||
if !remaining.iter().all(|&b| b == EMPTY_BYTE) {
|
||||
// TODO optimize to quadwords?
|
||||
return Err(value_error!("Trailing data in translations blob"));
|
||||
}
|
||||
|
||||
let payload_bytes = payload_reader.rest();
|
||||
|
||||
let payload_digest = sha256::digest(payload_bytes);
|
||||
if payload_digest != header.data_hash {
|
||||
return Err(INVALID_TRANSLATIONS_BLOB);
|
||||
}
|
||||
|
||||
let mut payload_reader = InputStream::new(payload_bytes);
|
||||
|
||||
let mut translations_reader = read_u16_prefixed_block(&mut payload_reader)?;
|
||||
let fonts_reader = read_u16_prefixed_block(&mut payload_reader)?;
|
||||
|
||||
if payload_reader.remaining() > 0 {
|
||||
return Err(INVALID_TRANSLATIONS_BLOB);
|
||||
}
|
||||
|
||||
// construct translations data
|
||||
let translations_count = translations_reader.read_u16_le()? as usize;
|
||||
let translations_offsets_bytes =
|
||||
translations_reader.read((translations_count + 1) * mem::size_of::<u16>())?;
|
||||
// SAFETY: any bytes are valid u16 values, so casting any data to
|
||||
// a sequence of u16 values is safe.
|
||||
let (_prefix, translations_offsets, _suffix) =
|
||||
unsafe { translations_offsets_bytes.align_to::<u16>() };
|
||||
if !_prefix.is_empty() || !_suffix.is_empty() {
|
||||
return Err(INVALID_TRANSLATIONS_BLOB);
|
||||
}
|
||||
let translations = translations_reader.rest();
|
||||
validate_offset_table(translations.len(), translations_offsets.iter().copied())?;
|
||||
|
||||
// construct and validate font table
|
||||
let fonts = Table::new(fonts_reader)?;
|
||||
fonts.validate()?;
|
||||
for (_, font_data) in fonts.iter() {
|
||||
let reader = InputStream::new(font_data);
|
||||
let font_table = Table::new(reader)?;
|
||||
font_table.validate()?;
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
header,
|
||||
translations,
|
||||
translations_offsets,
|
||||
fonts,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the translation at the given index.
|
||||
pub fn translation(&self, index: usize) -> Option<&str> {
|
||||
if index + 1 >= self.translations_offsets.len() {
|
||||
// The index is out of bounds.
|
||||
// (The last entry is a sentinel, so the last valid index is len - 2)
|
||||
// May happen when new firmware is using older translations and the string
|
||||
// is not defined yet.
|
||||
// Fallback to english.
|
||||
return None;
|
||||
}
|
||||
|
||||
let start_offset = self.translations_offsets[index] as usize;
|
||||
let end_offset = self.translations_offsets[index + 1] as usize;
|
||||
|
||||
// Construct the relevant slice
|
||||
let string = &self.translations[start_offset..end_offset];
|
||||
|
||||
if string.is_empty() {
|
||||
// The string is not defined in the blob.
|
||||
// May happen when old firmware is using newer translations and the string
|
||||
// was deleted in the newer version.
|
||||
// Fallback to english.
|
||||
return None;
|
||||
}
|
||||
|
||||
str::from_utf8(string).ok()
|
||||
}
|
||||
|
||||
pub fn font(&'a self, index: u16) -> Option<Table<'a>> {
|
||||
self.fonts
|
||||
.get(index)
|
||||
.and_then(|data| Table::new(InputStream::new(data)).ok())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TranslationsHeader<'a> {
|
||||
/// Raw content of the header, for signature verification
|
||||
pub header_bytes: &'a [u8],
|
||||
/// BCP 47 language tag (cs-CZ, en-US, ...)
|
||||
pub language: &'a str,
|
||||
/// 4 bytes of version (major, minor, patch, build)
|
||||
pub version: [u8; 4],
|
||||
/// Length of the raw data, i.e. translations section + fonts section
|
||||
pub data_len: usize,
|
||||
/// Hash of the data blob (excluding the header)
|
||||
pub data_hash: sha256::Digest,
|
||||
/// Merkle proof items
|
||||
pub merkle_proof: &'a [sha256::Digest],
|
||||
/// CoSi signature
|
||||
pub signature: cosi::Signature,
|
||||
/// Expected total length of the blob
|
||||
pub total_len: usize,
|
||||
}
|
||||
|
||||
fn read_fixedsize_str<'a>(reader: &mut InputStream<'a>, len: usize) -> Result<&'a str, Error> {
|
||||
let bytes = reader.read(len)?;
|
||||
let find_zero = bytes.iter().position(|&b| b == 0).unwrap_or(len);
|
||||
let bytes_trimmed = &bytes[..find_zero];
|
||||
core::str::from_utf8(bytes_trimmed).map_err(|_| INVALID_TRANSLATIONS_BLOB)
|
||||
}
|
||||
|
||||
impl<'a> TranslationsHeader<'a> {
|
||||
const BLOB_MAGIC: &'static [u8] = b"TRTR00";
|
||||
const HEADER_MAGIC: &'static [u8] = b"TR";
|
||||
const LANGUAGE_TAG_LEN: usize = 8;
|
||||
|
||||
/// Parse a translations header out of a stream.
|
||||
///
|
||||
/// The returned tuple consists of:
|
||||
/// (a) the parsed header and
|
||||
/// (b) reader of the payload section of the translations blob.
|
||||
/// The caller can use the returned reader to parse the payload.
|
||||
///
|
||||
/// The input stream is positioned at the end of the translations blob (or
|
||||
/// at the end of stream, whichever comes sooner). The caller can use this
|
||||
/// to verify that there is no unexpected trailing data in the input
|
||||
/// stream. (Also, you cannot make a mistake and read the payload out of
|
||||
/// the input stream).
|
||||
pub fn parse_from(reader: &mut InputStream<'a>) -> Result<(Self, InputStream<'a>), Error> {
|
||||
//
|
||||
// 1. parse outer container
|
||||
//
|
||||
let magic = reader.read(Self::BLOB_MAGIC.len())?;
|
||||
if magic != Self::BLOB_MAGIC {
|
||||
return Err(INVALID_TRANSLATIONS_BLOB);
|
||||
}
|
||||
|
||||
// read length of contained data
|
||||
let container_length = reader.read_u16_le()? as usize;
|
||||
// continue working on the contained data (i.e., read beyond the bounds of
|
||||
// container_length will result in EOF).
|
||||
let mut reader = reader.read_stream(container_length.min(reader.remaining()))?;
|
||||
|
||||
//
|
||||
// 2. parse the header section
|
||||
//
|
||||
let header_bytes = read_u16_prefixed_block(&mut reader)?.rest();
|
||||
|
||||
let mut header_reader = InputStream::new(header_bytes);
|
||||
|
||||
let magic = header_reader.read(Self::HEADER_MAGIC.len())?;
|
||||
if magic != Self::HEADER_MAGIC {
|
||||
return Err(INVALID_TRANSLATIONS_BLOB);
|
||||
}
|
||||
|
||||
let language = read_fixedsize_str(&mut header_reader, Self::LANGUAGE_TAG_LEN)?;
|
||||
if language.is_empty() {
|
||||
return Err(INVALID_TRANSLATIONS_BLOB);
|
||||
}
|
||||
|
||||
let model = read_fixedsize_str(&mut header_reader, 4)?;
|
||||
if model != crate::trezorhal::model::INTERNAL_NAME {
|
||||
return Err(value_error!("Wrong Trezor model"));
|
||||
}
|
||||
|
||||
let version_bytes = header_reader.read(4)?;
|
||||
let version = unwrap!(version_bytes.try_into());
|
||||
|
||||
let data_len = header_reader.read_u16_le()? as usize;
|
||||
let data_hash: sha256::Digest =
|
||||
unwrap!(header_reader.read(sha256::DIGEST_SIZE)?.try_into());
|
||||
|
||||
// ignore the rest of the header reader - this allows older firmware to
|
||||
// understand newer header if there are only added items
|
||||
_ = header_reader.rest();
|
||||
|
||||
//
|
||||
// 3. parse the proof section
|
||||
//
|
||||
let mut proof_reader = read_u16_prefixed_block(&mut reader)?;
|
||||
let proof_count = proof_reader.read_byte()? as usize;
|
||||
let proof_length = proof_count * sha256::DIGEST_SIZE;
|
||||
let proof_bytes = proof_reader.read(proof_length)?;
|
||||
|
||||
// create a list of the proof items
|
||||
// SAFETY: sha256::Digest is a plain array of u8, so any bytes are valid
|
||||
let (_prefix, merkle_proof, _suffix) = unsafe { proof_bytes.align_to::<sha256::Digest>() };
|
||||
if !_prefix.is_empty() || !_suffix.is_empty() {
|
||||
return Err(INVALID_TRANSLATIONS_BLOB);
|
||||
}
|
||||
let signature = cosi::Signature::new(
|
||||
proof_reader.read_byte()?,
|
||||
unwrap!(proof_reader.read(ed25519::SIGNATURE_SIZE)?.try_into()),
|
||||
);
|
||||
|
||||
// check that there is no trailing data in the proof section
|
||||
if proof_reader.remaining() > 0 {
|
||||
return Err(INVALID_TRANSLATIONS_BLOB);
|
||||
}
|
||||
|
||||
// check that the declared data section length matches the container size
|
||||
if container_length - reader.tell() != data_len {
|
||||
return Err(INVALID_TRANSLATIONS_BLOB);
|
||||
}
|
||||
|
||||
let new = Self {
|
||||
header_bytes,
|
||||
language,
|
||||
version,
|
||||
data_len,
|
||||
data_hash,
|
||||
merkle_proof,
|
||||
signature,
|
||||
total_len: container_length + Self::BLOB_MAGIC.len() + mem::size_of::<u16>(),
|
||||
};
|
||||
new.verify()?;
|
||||
Ok((new, reader))
|
||||
}
|
||||
|
||||
fn verify_with_keys(&self, public_keys: &[ed25519::PublicKey]) -> Result<(), Error> {
|
||||
let merkle_root = merkle_root(self.header_bytes, self.merkle_proof);
|
||||
Ok(cosi::verify(
|
||||
SIGNATURE_THRESHOLD,
|
||||
&merkle_root,
|
||||
public_keys,
|
||||
&self.signature,
|
||||
)?)
|
||||
}
|
||||
|
||||
pub fn verify(&self) -> Result<(), Error> {
|
||||
let mut result = self.verify_with_keys(&public_keys::PUBLIC_KEYS);
|
||||
#[cfg(feature = "debug")]
|
||||
if result.is_err() {
|
||||
// allow development keys
|
||||
result = self.verify_with_keys(&public_keys::PUBLIC_KEYS_DEVEL);
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
80
core/embed/rust/src/translations/flash.rs
Normal file
80
core/embed/rust/src/translations/flash.rs
Normal file
@ -0,0 +1,80 @@
|
||||
use crate::{error::Error, trezorhal::translations};
|
||||
|
||||
use super::blob::Translations;
|
||||
|
||||
static mut TRANSLATIONS_ON_FLASH: Option<Translations> = None;
|
||||
|
||||
pub fn erase() -> Result<(), Error> {
|
||||
// SAFETY: Looking is safe (in a single threaded environment).
|
||||
if unsafe { TRANSLATIONS_ON_FLASH.is_some() } {
|
||||
return Err(value_error!("Translations blob already set"));
|
||||
}
|
||||
|
||||
// SAFETY: The blob is not set, so there are no references to it.
|
||||
unsafe { translations::erase() };
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write(data: &[u8], offset: usize) -> Result<(), Error> {
|
||||
// SAFETY: Looking is safe (in a single threaded environment).
|
||||
if unsafe { TRANSLATIONS_ON_FLASH.is_some() } {
|
||||
return Err(value_error!("Translations blob already set"));
|
||||
}
|
||||
|
||||
// SAFETY: The blob is not set, so there are no references to it.
|
||||
let result = unsafe { translations::write(data, offset) };
|
||||
if result {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(value_error!("Failed to write translations blob"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Load translations from flash, validate, and cache references to lookup
|
||||
/// tables.
|
||||
unsafe fn try_init<'a>() -> Result<Option<Translations<'a>>, Error> {
|
||||
// load from flash
|
||||
let flash_data = unsafe { translations::get_blob() };
|
||||
// check if flash is empty
|
||||
// TODO perhaps we should check the full area?
|
||||
if flash_data[0..16] == [super::blob::EMPTY_BYTE; 16] {
|
||||
return Ok(None);
|
||||
}
|
||||
// try to parse the data
|
||||
Translations::new(flash_data).map(Some)
|
||||
}
|
||||
|
||||
pub fn init() {
|
||||
// unsafe block because every individual operation here is unsafe
|
||||
unsafe {
|
||||
// SAFETY: it is OK to look
|
||||
if TRANSLATIONS_ON_FLASH.is_some() {
|
||||
return;
|
||||
}
|
||||
// SAFETY: try_init unconditionally loads the translations from flash.
|
||||
// No other reference exists (TRANSLATIONS_ON_FLASH is None) so this is safe.
|
||||
match try_init() {
|
||||
// SAFETY: We are in a single-threaded environment so setting is OK.
|
||||
// (note that from this point on a reference to flash data is held)
|
||||
Ok(Some(t)) => TRANSLATIONS_ON_FLASH = Some(t),
|
||||
Ok(None) => {}
|
||||
// SAFETY: No reference to flash data exists so it is OK to erase it.
|
||||
Err(_) => translations::erase(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: Invalidates all references coming from the flash-based blob.
|
||||
// In other words, none should exist when this function is called.
|
||||
pub unsafe fn deinit() {
|
||||
// SAFETY: Given the above, we can safely clear the cached object.
|
||||
unsafe { TRANSLATIONS_ON_FLASH = None };
|
||||
}
|
||||
|
||||
// SAFETY: Gives out a reference to a TranslationsBlob which can be invalidated
|
||||
// by calling `erase()`. The caller must not store this reference, nor any that
|
||||
// come from it, beyond the lifetime of the current function.
|
||||
pub unsafe fn get<'a>() -> Option<&'a Translations<'a>> {
|
||||
// SAFETY: We are in a single-threaded environment.
|
||||
unsafe { TRANSLATIONS_ON_FLASH.as_ref() }
|
||||
}
|
1
core/embed/rust/src/translations/generated/mod.rs
Normal file
1
core/embed/rust/src/translations/generated/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod translated_string;
|
2526
core/embed/rust/src/translations/generated/translated_string.rs
Normal file
2526
core/embed/rust/src/translations/generated/translated_string.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,50 @@
|
||||
//! generated from ${THIS_FILE.name}
|
||||
//! (by running `make templates` in `core`)
|
||||
//! do not edit manually!
|
||||
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
<%
|
||||
import json
|
||||
|
||||
TR_DIR = ROOT / "core" / "translations"
|
||||
|
||||
order_file = TR_DIR / "order.json"
|
||||
order_index_name = json.loads(order_file.read_text())
|
||||
order = {int(k): v for k, v in order_index_name.items()}
|
||||
|
||||
|
||||
en_file = TR_DIR / "en.json"
|
||||
en_data = json.loads(en_file.read_text())["translations"]
|
||||
|
||||
%>\
|
||||
#[cfg(feature = "micropython")]
|
||||
use crate::micropython::qstr::Qstr;
|
||||
|
||||
#[derive(Debug, Copy, Clone, FromPrimitive)]
|
||||
#[repr(u16)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum TranslatedString {
|
||||
% for idx, name in order.items():
|
||||
${name} = ${idx},
|
||||
% endfor
|
||||
}
|
||||
|
||||
impl TranslatedString {
|
||||
pub fn untranslated(self) -> &'static str {
|
||||
match self {
|
||||
% for name in order.values():
|
||||
Self::${name} => ${json.dumps(en_data.get(name, '""'))},
|
||||
% endfor
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "micropython")]
|
||||
pub fn from_qstr(qstr: Qstr) -> Option<Self> {
|
||||
match qstr {
|
||||
% for name in order.values():
|
||||
Qstr::MP_QSTR_${name} => Some(Self::${name}),
|
||||
% endfor
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
33
core/embed/rust/src/translations/mod.rs
Normal file
33
core/embed/rust/src/translations/mod.rs
Normal file
@ -0,0 +1,33 @@
|
||||
mod blob;
|
||||
mod flash;
|
||||
mod generated;
|
||||
#[cfg(feature = "micropython")]
|
||||
mod obj;
|
||||
mod public_keys;
|
||||
mod translated_string;
|
||||
|
||||
pub use blob::MAX_HEADER_LEN;
|
||||
pub use translated_string::TranslatedString as TR;
|
||||
pub const DEFAULT_LANGUAGE: &str = "en-US";
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// Returned pointer will only point to valid font data for as long as
|
||||
/// the flash content is not invalidated by `erase()` or `write()`.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn get_utf8_glyph(codepoint: cty::uint16_t, font: cty::c_int) -> *const u8 {
|
||||
// C will send a negative number
|
||||
let font_abs = font.unsigned_abs() as u16;
|
||||
|
||||
// SAFETY: Reference is discarded at the end of the function.
|
||||
// We do return a _pointer_ to the same memory location, but the pointer is
|
||||
// always valid.
|
||||
let Some(tr) = (unsafe { flash::get() }) else {
|
||||
return core::ptr::null();
|
||||
};
|
||||
if let Some(glyph) = tr.font(font_abs).and_then(|t| t.get(codepoint)) {
|
||||
glyph.as_ptr()
|
||||
} else {
|
||||
core::ptr::null()
|
||||
}
|
||||
}
|
241
core/embed/rust/src/translations/obj.rs
Normal file
241
core/embed/rust/src/translations/obj.rs
Normal file
@ -0,0 +1,241 @@
|
||||
use crate::{
|
||||
error::Error,
|
||||
io::InputStream,
|
||||
micropython::{
|
||||
buffer::{get_buffer, StrBuffer},
|
||||
ffi,
|
||||
map::Map,
|
||||
module::Module,
|
||||
obj::Obj,
|
||||
qstr::Qstr,
|
||||
simple_type::SimpleTypeObj,
|
||||
typ::Type,
|
||||
util,
|
||||
},
|
||||
trezorhal::translations,
|
||||
};
|
||||
|
||||
use super::translated_string::TranslatedString;
|
||||
|
||||
impl TryFrom<TranslatedString> for StrBuffer {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: TranslatedString) -> Result<Self, Self::Error> {
|
||||
// SAFETY: The translated string is copied into a new memory. Reference to flash
|
||||
// data is discarded at the end of this function.
|
||||
let translated = value.translate(unsafe { super::flash::get() });
|
||||
StrBuffer::alloc(translated)
|
||||
// TODO fall back to English (which is static and can be converted
|
||||
// infallibly) if the allocation fails?
|
||||
}
|
||||
}
|
||||
|
||||
fn translate(translation: TranslatedString) -> Result<Obj, Error> {
|
||||
// SAFETY: TryFrom<&str> for Obj allocates a copy of the passed in string.
|
||||
// The reference to flash data is discarded at the end of this function.
|
||||
let stored_translations = unsafe { super::flash::get() };
|
||||
translation.translate(stored_translations).try_into()
|
||||
}
|
||||
|
||||
// SAFETY: Caller is supposed to be MicroPython, or copy MicroPython contracts
|
||||
// about the meaning of arguments.
|
||||
unsafe extern "C" fn tr_attr_fn(_self_in: Obj, attr: ffi::qstr, dest: *mut Obj) {
|
||||
let block = || {
|
||||
let arg = unsafe { dest.read() };
|
||||
if !arg.is_null() {
|
||||
// Null destination would mean a `setattr`.
|
||||
return Err(Error::TypeError);
|
||||
}
|
||||
let attr = Qstr::from_u16(attr as u16);
|
||||
let result = if let Some(translation) = TranslatedString::from_qstr(attr) {
|
||||
translate(translation)?
|
||||
} else {
|
||||
return Err(Error::AttributeError(attr));
|
||||
};
|
||||
unsafe { dest.write(result) };
|
||||
Ok(())
|
||||
};
|
||||
unsafe { util::try_or_raise(block) }
|
||||
}
|
||||
|
||||
static TR_TYPE: Type = obj_type! {
|
||||
name: Qstr::MP_QSTR_TR,
|
||||
attr_fn: tr_attr_fn,
|
||||
};
|
||||
|
||||
static TR_OBJ: SimpleTypeObj = SimpleTypeObj::new(&TR_TYPE);
|
||||
|
||||
fn make_translations_header(header: &super::blob::TranslationsHeader<'_>) -> Result<Obj, Error> {
|
||||
let version_objs: [Obj; 4] = {
|
||||
let v = header.version;
|
||||
[v[0].into(), v[1].into(), v[2].into(), v[3].into()]
|
||||
};
|
||||
attr_tuple! {
|
||||
Qstr::MP_QSTR_language => header.language.try_into()?,
|
||||
Qstr::MP_QSTR_version => util::new_tuple(&version_objs)?,
|
||||
Qstr::MP_QSTR_data_len => header.data_len.try_into()?,
|
||||
Qstr::MP_QSTR_data_hash => header.data_hash.as_ref().try_into()?,
|
||||
Qstr::MP_QSTR_total_len => header.total_len.try_into()?,
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn translations_header_new(
|
||||
_self_in: Obj,
|
||||
n_args: usize,
|
||||
n_kw: usize,
|
||||
args: *const Obj,
|
||||
) -> Obj {
|
||||
let block = |args: &[Obj], kwargs: &Map| {
|
||||
if args.len() != 1 || !kwargs.is_empty() {
|
||||
return Err(Error::TypeError);
|
||||
}
|
||||
// SAFETY: reference is discarded at the end of this function.
|
||||
let buffer = unsafe { get_buffer(args[0])? };
|
||||
let (header, _) =
|
||||
super::blob::TranslationsHeader::parse_from(&mut InputStream::new(buffer))?;
|
||||
make_translations_header(&header)
|
||||
};
|
||||
unsafe { util::try_with_args_and_kwargs_inline(n_args, n_kw, args, block) }
|
||||
}
|
||||
|
||||
pub extern "C" fn translations_header_from_flash(_cls_in: Obj) -> Obj {
|
||||
let block = || {
|
||||
// SAFETY: reference is discarded at the end of this function.
|
||||
match unsafe { super::flash::get() } {
|
||||
Some(translations) => make_translations_header(&translations.header),
|
||||
None => Ok(Obj::const_none()),
|
||||
}
|
||||
};
|
||||
unsafe { util::try_or_raise(block) }
|
||||
}
|
||||
|
||||
static TRANSLATIONS_HEADER_TYPE: Type = obj_type! {
|
||||
name: Qstr::MP_QSTR_TranslationsHeader,
|
||||
locals: &obj_dict!(obj_map! {
|
||||
Qstr::MP_QSTR_load_from_flash => obj_fn_1!(translations_header_from_flash).as_obj(),
|
||||
}),
|
||||
call_fn: translations_header_new,
|
||||
};
|
||||
|
||||
static TRANSLATIONS_HEADER_OBJ: SimpleTypeObj = SimpleTypeObj::new(&TRANSLATIONS_HEADER_TYPE);
|
||||
|
||||
extern "C" fn area_bytesize() -> Obj {
|
||||
let bytesize = translations::area_bytesize();
|
||||
unsafe { util::try_or_raise(|| bytesize.try_into()) }
|
||||
}
|
||||
|
||||
extern "C" fn get_language() -> Obj {
|
||||
let block = || {
|
||||
// SAFETY: reference is discarded at the end of the block
|
||||
let lang_name = unsafe { super::flash::get() }.map(|t| t.header.language);
|
||||
lang_name.unwrap_or(super::DEFAULT_LANGUAGE).try_into()
|
||||
};
|
||||
unsafe { util::try_or_raise(block) }
|
||||
}
|
||||
|
||||
extern "C" fn init() -> Obj {
|
||||
let block = || {
|
||||
super::flash::init();
|
||||
Ok(Obj::const_none())
|
||||
};
|
||||
unsafe { util::try_or_raise(block) }
|
||||
}
|
||||
|
||||
extern "C" fn deinit() -> Obj {
|
||||
// SAFETY: Safe by itself. Any unsafety stems from some other piece of code
|
||||
// not upholding the safety parameters.
|
||||
unsafe { super::flash::deinit() };
|
||||
Obj::const_none()
|
||||
}
|
||||
|
||||
extern "C" fn erase() -> Obj {
|
||||
let block = || {
|
||||
super::flash::erase()?;
|
||||
Ok(Obj::const_none())
|
||||
};
|
||||
unsafe { util::try_or_raise(block) }
|
||||
}
|
||||
|
||||
extern "C" fn write(data: Obj, offset: Obj) -> Obj {
|
||||
let block = || {
|
||||
// SAFETY: reference is discarded at the end of the block
|
||||
let data = unsafe { get_buffer(data)? };
|
||||
let offset: usize = offset.try_into()?;
|
||||
super::flash::write(data, offset)?;
|
||||
Ok(Obj::const_none())
|
||||
};
|
||||
unsafe { util::try_or_raise(block) }
|
||||
}
|
||||
|
||||
extern "C" fn verify(data: Obj) -> Obj {
|
||||
let block = || {
|
||||
// SAFETY: reference is discarded at the end of the block
|
||||
let data = unsafe { get_buffer(data)? };
|
||||
super::blob::Translations::new(data)?;
|
||||
Ok(Obj::const_none())
|
||||
};
|
||||
|
||||
unsafe { util::try_or_raise(block) }
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[rustfmt::skip]
|
||||
pub static mp_module_trezortranslate: Module = obj_module! {
|
||||
/// from trezortranslate_keys import TR as TR # noqa: F401
|
||||
/// """Translation object with attributes."""
|
||||
Qstr::MP_QSTR_TR => TR_OBJ.as_obj(),
|
||||
|
||||
/// def area_bytesize() -> int:
|
||||
/// """Maximum size of the translation blob that can be stored."""
|
||||
Qstr::MP_QSTR_area_bytesize => obj_fn_0!(area_bytesize).as_obj(),
|
||||
|
||||
/// def get_language() -> str:
|
||||
/// """Get the current language."""
|
||||
Qstr::MP_QSTR_get_language => obj_fn_0!(get_language).as_obj(),
|
||||
|
||||
/// def init() -> None:
|
||||
/// """Initialize the translations system.
|
||||
///
|
||||
/// Loads and verifies translation data from flash. If the verification passes,
|
||||
/// Trezor UI is translated from that point forward.
|
||||
/// """
|
||||
Qstr::MP_QSTR_init => obj_fn_0!(init).as_obj(),
|
||||
|
||||
/// def deinit() -> None:
|
||||
/// """Deinitialize the translations system.
|
||||
///
|
||||
/// Translations must be deinitialized before erasing or writing to flash.
|
||||
/// """
|
||||
Qstr::MP_QSTR_deinit => obj_fn_0!(deinit).as_obj(),
|
||||
|
||||
/// def erase() -> None:
|
||||
/// """Erase the translations blob from flash."""
|
||||
Qstr::MP_QSTR_erase => obj_fn_0!(erase).as_obj(),
|
||||
|
||||
/// def write(data: bytes, offset: int) -> None:
|
||||
/// """Write data to the translations blob in flash."""
|
||||
Qstr::MP_QSTR_write => obj_fn_2!(write).as_obj(),
|
||||
|
||||
/// def verify(data: bytes) -> None:
|
||||
/// """Verify the translations blob."""
|
||||
Qstr::MP_QSTR_verify => obj_fn_1!(verify).as_obj(),
|
||||
|
||||
/// class TranslationsHeader:
|
||||
/// """Metadata about the translations blob."""
|
||||
///
|
||||
/// language: str
|
||||
/// version: tuple[int, int, int, int]
|
||||
/// data_len: int
|
||||
/// data_hash: bytes
|
||||
/// total_len: int
|
||||
///
|
||||
/// def __init__(self, header_bytes: bytes) -> None:
|
||||
/// """Parse header from bytes.
|
||||
/// The header has variable length.
|
||||
/// """
|
||||
///
|
||||
/// @staticmethod
|
||||
/// def load_from_flash() -> TranslationsHeader | None:
|
||||
/// """Load translations from flash."""
|
||||
Qstr::MP_QSTR_TranslationsHeader => TRANSLATIONS_HEADER_OBJ.as_obj(),
|
||||
};
|
15
core/embed/rust/src/translations/public_keys.rs
Normal file
15
core/embed/rust/src/translations/public_keys.rs
Normal file
@ -0,0 +1,15 @@
|
||||
use crate::crypto::ed25519;
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
pub const PUBLIC_KEYS_DEVEL: [ed25519::PublicKey; 3] = [
|
||||
*b"\x68\x46\x0e\xbe\xf3\xb1\x38\x16\x4e\xc7\xfd\x86\x10\xe9\x58\x00\xdf\x75\x98\xf7\x0f\x2f\x2e\xa7\xdb\x51\x72\xac\x74\xeb\xc1\x44",
|
||||
*b"\x8d\x4a\xbe\x07\x4f\xef\x92\x29\xd3\xb4\x41\xdf\xea\x4f\x98\xf8\x05\xb1\xa2\xb3\xa0\x6a\xe6\x45\x81\x0e\xfe\xce\x77\xfd\x50\x44",
|
||||
*b"\x97\xf7\x13\x5a\x9a\x26\x90\xe7\x3b\xeb\x26\x55\x6f\x1c\xb1\x63\xbe\xa2\x53\x2a\xff\xa1\xe7\x78\x24\x30\xbe\x98\xc0\xe5\x68\x12",
|
||||
];
|
||||
|
||||
pub const PUBLIC_KEYS: [ed25519::PublicKey; 3] = [
|
||||
// TODO replace with production keys
|
||||
*b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f",
|
||||
*b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f",
|
||||
*b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f",
|
||||
];
|
41
core/embed/rust/src/translations/translated_string.rs
Normal file
41
core/embed/rust/src/translations/translated_string.rs
Normal file
@ -0,0 +1,41 @@
|
||||
use crate::strutil::TString;
|
||||
|
||||
use super::blob::Translations;
|
||||
pub use super::generated::translated_string::TranslatedString;
|
||||
|
||||
impl TranslatedString {
|
||||
pub(super) fn translate<'a>(self, source: Option<&'a Translations>) -> &'a str {
|
||||
source
|
||||
.and_then(|s| s.translation(self as _))
|
||||
.unwrap_or(self.untranslated())
|
||||
}
|
||||
|
||||
pub fn map_translated<F, T>(self, fun: F) -> T
|
||||
where
|
||||
F: for<'a> FnOnce(&'a str) -> T,
|
||||
T: 'static,
|
||||
{
|
||||
// SAFETY: The bound on F _somehow_ ensures that the reference cannot escape
|
||||
// the closure. (I don't understand how, but it does), see soundness test below.
|
||||
// For good measure, we limit the return value to 'static.
|
||||
let translations = unsafe { super::flash::get() };
|
||||
fun(self.translate(translations))
|
||||
}
|
||||
|
||||
pub const fn as_tstring(self) -> TString<'static> {
|
||||
TString::Translation(self)
|
||||
}
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use super::TranslatedString;
|
||||
|
||||
// #[test]
|
||||
// fn test_soundness() {
|
||||
// let tr = TranslatedString::address__public_key;
|
||||
// let mut opt: Option<&str> = None;
|
||||
// tr.map_translated(|s| opt = Some(s));
|
||||
// assert!(matches!(opt, Some("Address / Public key")));
|
||||
// }
|
||||
// }
|
@ -63,7 +63,7 @@ pub fn char_width(ch: char, font: i32) -> i16 {
|
||||
text_width(encoding, font)
|
||||
}
|
||||
|
||||
pub fn get_char_glyph(ch: u8, font: i32) -> *const u8 {
|
||||
pub fn get_char_glyph(ch: u16, font: i32) -> *const u8 {
|
||||
unsafe { ffi::font_get_glyph(font, ch) }
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,8 @@ pub mod random;
|
||||
pub mod rgb_led;
|
||||
pub mod slip39;
|
||||
pub mod storage;
|
||||
#[cfg(feature = "translations")]
|
||||
pub mod translations;
|
||||
pub mod usb;
|
||||
pub mod uzlib;
|
||||
pub mod wordlist;
|
||||
|
31
core/embed/rust/src/trezorhal/translations.rs
Normal file
31
core/embed/rust/src/trezorhal/translations.rs
Normal file
@ -0,0 +1,31 @@
|
||||
use super::ffi;
|
||||
|
||||
// SAFETY: Returned slice is valid and immutable until a call to `erase()`
|
||||
// and/or `set_blob()`. Caller is responsible for disposing of all references to
|
||||
// the slice before touching the flash contents.
|
||||
pub unsafe fn get_blob<'a>() -> &'a [u8] {
|
||||
let mut len: u32 = 0;
|
||||
let ptr = unsafe { ffi::translations_read(&mut len, 0) };
|
||||
if ptr.is_null() {
|
||||
fatal_error!("Translations read failed", "");
|
||||
}
|
||||
// SAFETY: The pointer is always valid.
|
||||
unsafe { core::slice::from_raw_parts(ptr, len as usize) }
|
||||
}
|
||||
|
||||
// SAFETY: This call invalidates the reference to the blob returned by
|
||||
// `get_blob()`.
|
||||
pub unsafe fn erase() {
|
||||
unsafe { ffi::translations_erase() };
|
||||
}
|
||||
|
||||
pub fn area_bytesize() -> usize {
|
||||
// SAFETY: Safe, no side effects.
|
||||
unsafe { ffi::translations_area_bytesize() as usize }
|
||||
}
|
||||
|
||||
// SAFETY: This call may invalidate the reference to the blob returned by
|
||||
// `get_blob()`.
|
||||
pub unsafe fn write(data: &[u8], offset: usize) -> bool {
|
||||
unsafe { ffi::translations_write(data.as_ptr(), offset as u32, data.len() as u32) }
|
||||
}
|
@ -133,6 +133,6 @@ where
|
||||
{
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.component("Label");
|
||||
t.string("text", self.text.as_ref());
|
||||
t.string("text", self.text.as_ref().into());
|
||||
}
|
||||
}
|
||||
|
@ -234,6 +234,6 @@ where
|
||||
{
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.component("Marquee");
|
||||
t.string("text", self.text.as_ref());
|
||||
t.string("text", self.text.as_ref().into());
|
||||
}
|
||||
}
|
||||
|
@ -151,6 +151,6 @@ impl Component for Qr {
|
||||
impl crate::trace::Trace for Qr {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.component("Qr");
|
||||
t.string("text", self.text.as_ref());
|
||||
t.string("text", self.text.as_str().into());
|
||||
}
|
||||
}
|
||||
|
@ -110,6 +110,6 @@ impl<const L: usize> TextBox<L> {
|
||||
impl<const L: usize> crate::trace::Trace for TextBox<L> {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.component("TextBox");
|
||||
t.string("text", &self.text);
|
||||
t.string("text", self.text.as_str().into());
|
||||
}
|
||||
}
|
||||
|
@ -541,23 +541,23 @@ pub mod trace {
|
||||
|
||||
impl LayoutSink for TraceSink<'_> {
|
||||
fn text(&mut self, _cursor: Point, _layout: &TextLayout, text: &str) {
|
||||
self.0.string(text);
|
||||
self.0.string(&text.into());
|
||||
}
|
||||
|
||||
fn hyphen(&mut self, _cursor: Point, _layout: &TextLayout) {
|
||||
self.0.string("-");
|
||||
self.0.string(&"-".into());
|
||||
}
|
||||
|
||||
fn ellipsis(&mut self, _cursor: Point, _layout: &TextLayout) {
|
||||
self.0.string(ELLIPSIS);
|
||||
self.0.string(&ELLIPSIS.into());
|
||||
}
|
||||
|
||||
fn prev_page_ellipsis(&mut self, _cursor: Point, _layout: &TextLayout) {
|
||||
self.0.string(ELLIPSIS);
|
||||
self.0.string(&ELLIPSIS.into());
|
||||
}
|
||||
|
||||
fn line_break(&mut self, _cursor: Point) {
|
||||
self.0.string("\n");
|
||||
self.0.string(&"\n".into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -225,7 +225,7 @@ pub mod trace {
|
||||
|
||||
impl<T: ParagraphSource> crate::trace::Trace for Paragraphs<T> {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.string("component", "Paragraphs");
|
||||
t.string("component", "Paragraphs".into());
|
||||
t.in_list("paragraphs", &|par_list| {
|
||||
Self::foreach_visible(
|
||||
&self.source,
|
||||
|
@ -1,6 +1,9 @@
|
||||
use crate::ui::{
|
||||
display::{Color, Font},
|
||||
geometry::{Alignment, Rect},
|
||||
use crate::{
|
||||
strutil::TString,
|
||||
ui::{
|
||||
display::{Color, Font},
|
||||
geometry::{Alignment, Rect},
|
||||
},
|
||||
};
|
||||
|
||||
use super::{
|
||||
@ -17,7 +20,7 @@ use super::{
|
||||
/// If it does not fit, returns `None`.
|
||||
pub fn text_multiline(
|
||||
area: Rect,
|
||||
text: &str,
|
||||
text: TString<'_>,
|
||||
font: Font,
|
||||
fg_color: Color,
|
||||
bg_color: Color,
|
||||
@ -27,7 +30,7 @@ pub fn text_multiline(
|
||||
let text_layout = TextLayout::new(text_style)
|
||||
.with_bounds(area)
|
||||
.with_align(alignment);
|
||||
let layout_fit = text_layout.render_text(text);
|
||||
let layout_fit = text.map(|t| text_layout.render_text(t));
|
||||
match layout_fit {
|
||||
LayoutFit::Fitting { height, .. } => Some(area.split_top(height).1),
|
||||
LayoutFit::OutOfBounds { .. } => None,
|
||||
@ -38,7 +41,7 @@ pub fn text_multiline(
|
||||
/// area.
|
||||
pub fn text_multiline_bottom(
|
||||
area: Rect,
|
||||
text: &str,
|
||||
text: TString<'_>,
|
||||
font: Font,
|
||||
fg_color: Color,
|
||||
bg_color: Color,
|
||||
@ -50,16 +53,16 @@ pub fn text_multiline_bottom(
|
||||
.with_align(alignment);
|
||||
// When text fits the area, displaying it in the bottom part.
|
||||
// When not, render it "normally".
|
||||
match text_layout.fit_text(text) {
|
||||
text.map(|t| match text_layout.fit_text(t) {
|
||||
LayoutFit::Fitting { height, .. } => {
|
||||
let (top, bottom) = area.split_bottom(height);
|
||||
text_layout = text_layout.with_bounds(bottom);
|
||||
text_layout.render_text(text);
|
||||
text_layout.render_text(t);
|
||||
Some(top)
|
||||
}
|
||||
LayoutFit::OutOfBounds { .. } => {
|
||||
text_layout.render_text(text);
|
||||
text_layout.render_text(t);
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -144,6 +144,16 @@ impl Font {
|
||||
display::text_width(text, self.into())
|
||||
}
|
||||
|
||||
/// Supports UTF8 characters
|
||||
fn get_first_glyph_from_text(self, text: &str) -> Option<Glyph> {
|
||||
text.chars().next().map(|c| self.get_glyph(c))
|
||||
}
|
||||
|
||||
/// Supports UTF8 characters
|
||||
fn get_last_glyph_from_text(self, text: &str) -> Option<Glyph> {
|
||||
text.chars().next_back().map(|c| self.get_glyph(c))
|
||||
}
|
||||
|
||||
/// Width of the text that is visible.
|
||||
/// Not including the spaces before the first and after the last character.
|
||||
pub fn visible_text_width(self, text: &str) -> i16 {
|
||||
@ -152,14 +162,20 @@ impl Font {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let first_char = unwrap!(text.chars().next());
|
||||
let first_char_glyph = unwrap!(self.get_glyph(first_char as u8));
|
||||
let first_char_bearing = if let Some(glyph) = self.get_first_glyph_from_text(text) {
|
||||
glyph.bearing_x
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let last_char = unwrap!(text.chars().last());
|
||||
let last_char_glyph = unwrap!(self.get_glyph(last_char as u8));
|
||||
let last_char_bearing = if let Some(glyph) = self.get_last_glyph_from_text(text) {
|
||||
glyph.right_side_bearing()
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
// Strip leftmost and rightmost spaces/bearings/margins.
|
||||
self.text_width(text) - first_char_glyph.bearing_x - last_char_glyph.right_side_bearing()
|
||||
self.text_width(text) - first_char_bearing - last_char_bearing
|
||||
}
|
||||
|
||||
/// Returning the x-bearing (offset) of the first character.
|
||||
@ -169,9 +185,11 @@ impl Font {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let first_char = unwrap!(text.chars().next());
|
||||
let first_char_glyph = unwrap!(self.get_glyph(first_char as u8));
|
||||
first_char_glyph.bearing_x
|
||||
if let Some(glyph) = self.get_first_glyph_from_text(text) {
|
||||
glyph.bearing_x
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn char_width(self, ch: char) -> i16 {
|
||||
@ -198,25 +216,21 @@ impl Font {
|
||||
constant::LINE_SPACE + self.text_height()
|
||||
}
|
||||
|
||||
pub fn get_glyph(self, char_byte: u8) -> Option<Glyph> {
|
||||
let gl_data = display::get_char_glyph(char_byte, self.into());
|
||||
pub fn get_glyph(self, ch: char) -> Glyph {
|
||||
let gl_data = display::get_char_glyph(ch as u16, self.into());
|
||||
|
||||
if gl_data.is_null() {
|
||||
return None;
|
||||
}
|
||||
ensure!(!gl_data.is_null(), "Failed to load glyph");
|
||||
// SAFETY: Glyph::load is valid for data returned by get_char_glyph
|
||||
unsafe { Some(Glyph::load(gl_data)) }
|
||||
unsafe { Glyph::load(gl_data) }
|
||||
}
|
||||
|
||||
pub fn display_text(self, text: &str, baseline: Point, fg_color: Color, bg_color: Color) {
|
||||
let colortable = get_color_table(fg_color, bg_color);
|
||||
let mut adv_total = 0;
|
||||
for c in text.bytes() {
|
||||
let g = self.get_glyph(c);
|
||||
if let Some(gly) = g {
|
||||
let adv = gly.print(baseline + Offset::new(adv_total, 0), colortable);
|
||||
adv_total += adv;
|
||||
}
|
||||
for c in text.chars() {
|
||||
let gly = self.get_glyph(c);
|
||||
let adv = gly.print(baseline + Offset::new(adv_total, 0), colortable);
|
||||
adv_total += adv;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,7 @@ use crate::ui::geometry::Alignment2D;
|
||||
use crate::{time::Duration, trezorhal::time};
|
||||
|
||||
use crate::{
|
||||
strutil::TString,
|
||||
trezorhal::{buffers, display, uzlib::UzlibContext},
|
||||
ui::lerp::Lerp,
|
||||
};
|
||||
@ -346,39 +347,39 @@ pub fn clear() {
|
||||
display::clear();
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub struct TextOverlay<T> {
|
||||
#[derive(Clone)]
|
||||
pub struct TextOverlay {
|
||||
area: Rect,
|
||||
text: T,
|
||||
text: TString<'static>,
|
||||
font: Font,
|
||||
max_height: i16,
|
||||
baseline: i16,
|
||||
}
|
||||
|
||||
impl<T: AsRef<str>> TextOverlay<T> {
|
||||
pub fn new(text: T, font: Font) -> Self {
|
||||
impl TextOverlay {
|
||||
pub fn new<T: Into<TString<'static>>>(text: T, font: Font) -> Self {
|
||||
let area = Rect::zero();
|
||||
|
||||
Self {
|
||||
area,
|
||||
text,
|
||||
text: text.into(),
|
||||
font,
|
||||
max_height: font.max_height(),
|
||||
baseline: font.text_baseline(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_text(&mut self, text: T) {
|
||||
self.text = text;
|
||||
pub fn set_text<T: Into<TString<'static>>>(&mut self, text: T) {
|
||||
self.text = text.into();
|
||||
}
|
||||
|
||||
pub fn get_text(&self) -> &T {
|
||||
&self.text
|
||||
pub fn get_text(&self) -> TString<'static> {
|
||||
self.text
|
||||
}
|
||||
|
||||
// baseline relative to the underlying render area
|
||||
pub fn place(&mut self, baseline: Point) {
|
||||
let text_width = self.font.text_width(self.text.as_ref());
|
||||
let text_width = self.text.map(|t| self.font.text_width(t));
|
||||
let text_height = self.font.text_height();
|
||||
|
||||
let text_area_start = baseline + Offset::new(-(text_width / 2), -text_height);
|
||||
@ -397,30 +398,28 @@ impl<T: AsRef<str>> TextOverlay<T> {
|
||||
|
||||
let p_rel = Point::new(p.x - self.area.x0, p.y - self.area.y0);
|
||||
|
||||
for g in self
|
||||
.text
|
||||
.as_ref()
|
||||
.bytes()
|
||||
.filter_map(|c| self.font.get_glyph(c))
|
||||
{
|
||||
let top = self.max_height - self.baseline - g.bearing_y;
|
||||
let char_area = Rect::new(
|
||||
Point::new(tot_adv + g.bearing_x, top),
|
||||
Point::new(tot_adv + g.bearing_x + g.width, top + g.height),
|
||||
);
|
||||
let color = self.text.map(|t| {
|
||||
for g in t.chars().map(|c| self.font.get_glyph(c)) {
|
||||
let top = self.max_height - self.baseline - g.bearing_y;
|
||||
let char_area = Rect::new(
|
||||
Point::new(tot_adv + g.bearing_x, top),
|
||||
Point::new(tot_adv + g.bearing_x + g.width, top + g.height),
|
||||
);
|
||||
|
||||
tot_adv += g.adv;
|
||||
tot_adv += g.adv;
|
||||
|
||||
if !char_area.contains(p_rel) {
|
||||
continue;
|
||||
if !char_area.contains(p_rel) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let p_inner = p_rel - char_area.top_left();
|
||||
let overlay_data = g.get_pixel_data(p_inner);
|
||||
return Some(Color::lerp(underlying, fg, overlay_data as f32 / 15_f32));
|
||||
}
|
||||
None
|
||||
});
|
||||
|
||||
let p_inner = p_rel - char_area.top_left();
|
||||
let overlay_data = g.get_pixel_data(p_inner);
|
||||
return Color::lerp(underlying, fg, overlay_data as f32 / 15_f32);
|
||||
}
|
||||
|
||||
underlying
|
||||
color.unwrap_or(underlying)
|
||||
}
|
||||
}
|
||||
|
||||
@ -995,9 +994,9 @@ fn rect_rounded2_get_pixel(
|
||||
/// Optionally draws a text inside the rectangle and adjusts its color to match
|
||||
/// the fill. The coordinates of the text are specified in the TextOverlay
|
||||
/// struct.
|
||||
pub fn bar_with_text_and_fill<T: AsRef<str>>(
|
||||
pub fn bar_with_text_and_fill(
|
||||
area: Rect,
|
||||
overlay: Option<&TextOverlay<T>>,
|
||||
overlay: Option<&TextOverlay>,
|
||||
fg_color: Color,
|
||||
bg_color: Color,
|
||||
fill_from: i16,
|
||||
|
@ -32,7 +32,7 @@ impl ReturnToC for IntroMsg {
|
||||
pub struct Intro<'a> {
|
||||
bg: Pad,
|
||||
title: Child<Label<&'a str>>,
|
||||
buttons: Child<ButtonController<&'static str>>,
|
||||
buttons: Child<ButtonController>,
|
||||
text: Child<Label<&'a str>>,
|
||||
warn: Option<Child<Label<&'a str>>>,
|
||||
}
|
||||
@ -43,8 +43,8 @@ impl<'a> Intro<'a> {
|
||||
bg: Pad::with_background(BLD_BG).with_clear(),
|
||||
title: Child::new(Label::centered(title, TEXT_NORMAL).vertically_centered()),
|
||||
buttons: Child::new(ButtonController::new(ButtonLayout::text_none_text(
|
||||
LEFT_BUTTON_TEXT,
|
||||
RIGHT_BUTTON_TEXT,
|
||||
LEFT_BUTTON_TEXT.into(),
|
||||
RIGHT_BUTTON_TEXT.into(),
|
||||
))),
|
||||
text: Child::new(Label::left_aligned(content, TEXT_NORMAL).vertically_centered()),
|
||||
warn: (!fw_ok).then_some(Child::new(
|
||||
|
@ -13,7 +13,7 @@ use crate::{
|
||||
|
||||
use super::{
|
||||
super::{
|
||||
component::{Choice, ChoiceFactory, ChoicePage},
|
||||
component::{ButtonLayout, Choice, ChoiceFactory, ChoicePage},
|
||||
theme::bootloader::{BLD_BG, BLD_FG, ICON_EXIT, ICON_REDO, ICON_TRASH},
|
||||
},
|
||||
ReturnToC,
|
||||
@ -51,7 +51,7 @@ impl MenuChoice {
|
||||
}
|
||||
}
|
||||
|
||||
impl Choice<&'static str> for MenuChoice {
|
||||
impl Choice for MenuChoice {
|
||||
fn paint_center(&self, _area: Rect, _inverse: bool) {
|
||||
// Icon on top and two lines of text below
|
||||
self.icon.draw(
|
||||
@ -70,6 +70,10 @@ impl Choice<&'static str> for MenuChoice {
|
||||
BLD_BG,
|
||||
);
|
||||
}
|
||||
|
||||
fn btn_layout(&self) -> ButtonLayout {
|
||||
ButtonLayout::arrow_armed_arrow("SELECT".into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
@ -95,7 +99,7 @@ impl MenuChoiceFactory {
|
||||
}
|
||||
}
|
||||
|
||||
impl ChoiceFactory<&'static str> for MenuChoiceFactory {
|
||||
impl ChoiceFactory for MenuChoiceFactory {
|
||||
type Action = MenuMsg;
|
||||
type Item = MenuChoice;
|
||||
|
||||
@ -125,7 +129,7 @@ impl ChoiceFactory<&'static str> for MenuChoiceFactory {
|
||||
|
||||
pub struct Menu {
|
||||
pad: Pad,
|
||||
choice_page: Child<ChoicePage<MenuChoiceFactory, &'static str, MenuMsg>>,
|
||||
choice_page: Child<ChoicePage<MenuChoiceFactory, MenuMsg>>,
|
||||
}
|
||||
|
||||
impl Menu {
|
||||
|
@ -2,7 +2,8 @@ use heapless::Vec;
|
||||
|
||||
use crate::{
|
||||
error::Error,
|
||||
strutil::StringType,
|
||||
micropython::buffer::StrBuffer,
|
||||
translations::TR,
|
||||
ui::{
|
||||
component::{
|
||||
text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs, VecExt},
|
||||
@ -19,40 +20,40 @@ use super::{
|
||||
const MAX_XPUBS: usize = 16;
|
||||
const QR_BORDER: i16 = 3;
|
||||
|
||||
pub struct AddressDetails<T>
|
||||
where
|
||||
T: StringType,
|
||||
{
|
||||
pub struct AddressDetails {
|
||||
qr_code: Qr,
|
||||
details_view: Paragraphs<ParagraphVecShort<T>>,
|
||||
xpub_view: Frame<Paragraphs<Paragraph<T>>, T>,
|
||||
xpubs: Vec<(T, T), MAX_XPUBS>,
|
||||
details_view: Paragraphs<ParagraphVecShort<StrBuffer>>,
|
||||
xpub_view: Frame<Paragraphs<Paragraph<StrBuffer>>, StrBuffer>,
|
||||
xpubs: Vec<(StrBuffer, StrBuffer), MAX_XPUBS>,
|
||||
current_page: usize,
|
||||
current_subpage: usize,
|
||||
area: Rect,
|
||||
pad: Pad,
|
||||
buttons: Child<ButtonController<T>>,
|
||||
buttons: Child<ButtonController>,
|
||||
}
|
||||
|
||||
impl<T> AddressDetails<T>
|
||||
where
|
||||
T: StringType + Clone,
|
||||
{
|
||||
impl AddressDetails {
|
||||
pub fn new(
|
||||
qr_address: T,
|
||||
qr_address: StrBuffer,
|
||||
case_sensitive: bool,
|
||||
account: Option<T>,
|
||||
path: Option<T>,
|
||||
account: Option<StrBuffer>,
|
||||
path: Option<StrBuffer>,
|
||||
) -> Result<Self, Error> {
|
||||
let qr_code = Qr::new(qr_address, case_sensitive)?.with_border(QR_BORDER);
|
||||
let details_view = {
|
||||
let mut para = ParagraphVecShort::new();
|
||||
if let Some(account) = account {
|
||||
para.add(Paragraph::new(&theme::TEXT_BOLD, "Account:".into()));
|
||||
para.add(Paragraph::new(
|
||||
&theme::TEXT_BOLD,
|
||||
TR::words__account_colon.try_into()?,
|
||||
));
|
||||
para.add(Paragraph::new(&theme::TEXT_MONO, account));
|
||||
}
|
||||
if let Some(path) = path {
|
||||
para.add(Paragraph::new(&theme::TEXT_BOLD, "Derivation path:".into()));
|
||||
para.add(Paragraph::new(
|
||||
&theme::TEXT_BOLD,
|
||||
TR::address_details__derivation_path.try_into()?,
|
||||
));
|
||||
para.add(Paragraph::new(&theme::TEXT_MONO, path));
|
||||
}
|
||||
Paragraphs::new(para)
|
||||
@ -76,7 +77,7 @@ where
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn add_xpub(&mut self, title: T, xpub: T) -> Result<(), Error> {
|
||||
pub fn add_xpub(&mut self, title: StrBuffer, xpub: StrBuffer) -> Result<(), Error> {
|
||||
self.xpubs
|
||||
.push((title, xpub))
|
||||
.map_err(|_| Error::OutOfRange)
|
||||
@ -111,7 +112,7 @@ where
|
||||
/// last page. On xpub pages there is SHOW ALL middle button when it
|
||||
/// cannot fit one page. On xpub subpages there are wide arrows to
|
||||
/// scroll.
|
||||
fn get_button_layout(&mut self) -> ButtonLayout<T> {
|
||||
fn get_button_layout(&mut self) -> ButtonLayout {
|
||||
let (left, middle, right) = if self.is_in_subpage() {
|
||||
let left = Some(ButtonDetails::up_arrow_icon_wide());
|
||||
let right = if self.is_last_subpage() {
|
||||
@ -123,7 +124,7 @@ where
|
||||
} else {
|
||||
let left = Some(ButtonDetails::left_arrow_icon());
|
||||
let middle = if self.is_xpub_page() && self.subpages_in_current_page() > 1 {
|
||||
Some(ButtonDetails::armed_text("SHOW ALL".into()))
|
||||
Some(ButtonDetails::armed_text(TR::buttons__show_all.into()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@ -151,9 +152,9 @@ where
|
||||
|
||||
fn fill_xpub_page(&mut self, ctx: &mut EventCtx) {
|
||||
let i = self.current_page - 2;
|
||||
self.xpub_view.update_title(ctx, self.xpubs[i].0.clone());
|
||||
self.xpub_view.update_title(ctx, self.xpubs[i].0);
|
||||
self.xpub_view.update_content(ctx, |p| {
|
||||
p.inner_mut().update(self.xpubs[i].1.clone());
|
||||
p.inner_mut().update(self.xpubs[i].1);
|
||||
p.change_page(0)
|
||||
});
|
||||
}
|
||||
@ -175,10 +176,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Component for AddressDetails<T>
|
||||
where
|
||||
T: StringType + Clone,
|
||||
{
|
||||
impl Component for AddressDetails {
|
||||
type Msg = ();
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
@ -265,10 +263,7 @@ where
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl<T> crate::trace::Trace for AddressDetails<T>
|
||||
where
|
||||
T: StringType + Clone,
|
||||
{
|
||||
impl crate::trace::Trace for AddressDetails {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.component("AddressDetails");
|
||||
match self.current_page {
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
strutil::StringType,
|
||||
strutil::TString,
|
||||
ui::{
|
||||
component::{Child, Component, ComponentExt, Event, EventCtx, Label, Pad},
|
||||
display::{self, Color, Font},
|
||||
@ -20,40 +20,40 @@ pub enum ConfirmMsg {
|
||||
Confirm = 2,
|
||||
}
|
||||
|
||||
pub struct Confirm<T: StringType, U> {
|
||||
pub struct Confirm<U> {
|
||||
bg: Pad,
|
||||
bg_color: Color,
|
||||
title: &'static str,
|
||||
title: TString<'static>,
|
||||
message: Child<Label<U>>,
|
||||
alert: Option<Label<T>>,
|
||||
info_title: Option<T>,
|
||||
alert: Option<Label<U>>,
|
||||
info_title: Option<TString<'static>>,
|
||||
info_text: Option<Label<U>>,
|
||||
button_text: T,
|
||||
buttons: ButtonController<T>,
|
||||
button_text: TString<'static>,
|
||||
buttons: ButtonController,
|
||||
/// Whether we are on the info screen (optional extra screen)
|
||||
showing_info_screen: bool,
|
||||
two_btn_confirm: bool,
|
||||
}
|
||||
|
||||
impl<T, U> Confirm<T, U>
|
||||
impl<U> Confirm<U>
|
||||
where
|
||||
T: StringType + Clone,
|
||||
U: AsRef<str>,
|
||||
{
|
||||
pub fn new(
|
||||
pub fn new<T: Into<TString<'static>>>(
|
||||
bg_color: Color,
|
||||
title: &'static str,
|
||||
title: T,
|
||||
message: Label<U>,
|
||||
alert: Option<Label<T>>,
|
||||
alert: Option<Label<U>>,
|
||||
button_text: T,
|
||||
two_btn_confirm: bool,
|
||||
) -> Self {
|
||||
let button_text = button_text.into();
|
||||
let btn_layout =
|
||||
Self::get_button_layout_general(false, button_text.clone(), false, two_btn_confirm);
|
||||
Self::get_button_layout_general(false, button_text, false, two_btn_confirm);
|
||||
Self {
|
||||
bg: Pad::with_background(bg_color).with_clear(),
|
||||
bg_color,
|
||||
title,
|
||||
title: title.into(),
|
||||
message: Child::new(message),
|
||||
alert,
|
||||
info_title: None,
|
||||
@ -66,8 +66,12 @@ where
|
||||
}
|
||||
|
||||
/// Adding optional info screen
|
||||
pub fn with_info_screen(mut self, info_title: T, info_text: Label<U>) -> Self {
|
||||
self.info_title = Some(info_title);
|
||||
pub fn with_info_screen<T: Into<TString<'static>>>(
|
||||
mut self,
|
||||
info_title: T,
|
||||
info_text: Label<U>,
|
||||
) -> Self {
|
||||
self.info_title = Some(info_title.into());
|
||||
self.info_text = Some(info_text);
|
||||
self.buttons = ButtonController::new(self.get_button_layout());
|
||||
self
|
||||
@ -77,10 +81,10 @@ where
|
||||
self.info_title.is_some()
|
||||
}
|
||||
|
||||
fn get_button_layout(&self) -> ButtonLayout<T> {
|
||||
fn get_button_layout(&self) -> ButtonLayout {
|
||||
Self::get_button_layout_general(
|
||||
self.showing_info_screen,
|
||||
self.button_text.clone(),
|
||||
self.button_text,
|
||||
self.has_info_screen(),
|
||||
self.two_btn_confirm,
|
||||
)
|
||||
@ -89,10 +93,10 @@ where
|
||||
/// Not relying on self here, to call it in constructor.
|
||||
fn get_button_layout_general(
|
||||
showing_info_screen: bool,
|
||||
button_text: T,
|
||||
button_text: TString<'static>,
|
||||
has_info_screen: bool,
|
||||
two_btn_confirm: bool,
|
||||
) -> ButtonLayout<T> {
|
||||
) -> ButtonLayout {
|
||||
if showing_info_screen {
|
||||
ButtonLayout::arrow_none_none()
|
||||
} else if has_info_screen {
|
||||
@ -121,9 +125,8 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Component for Confirm<T, U>
|
||||
impl<U> Component for Confirm<U>
|
||||
where
|
||||
T: StringType + Clone,
|
||||
U: AsRef<str>,
|
||||
{
|
||||
type Msg = ConfirmMsg;
|
||||
@ -204,13 +207,17 @@ where
|
||||
fn paint(&mut self) {
|
||||
self.bg.paint();
|
||||
|
||||
let display_top_left = |text: &str| {
|
||||
display::text_top_left(Point::zero(), text, Font::BOLD, WHITE, self.bg_color);
|
||||
let display_top_left = |text: TString<'static>| {
|
||||
text.map(|t| {
|
||||
display::text_top_left(Point::zero(), t, Font::BOLD, WHITE, self.bg_color)
|
||||
});
|
||||
};
|
||||
|
||||
// We are either on the info screen or on the "main" screen
|
||||
if self.showing_info_screen {
|
||||
display_top_left(unwrap!(self.info_title.clone()).as_ref());
|
||||
if let Some(title) = self.info_title {
|
||||
display_top_left(title);
|
||||
}
|
||||
self.info_text.paint();
|
||||
} else {
|
||||
display_top_left(self.title);
|
||||
@ -227,9 +234,8 @@ where
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl<T, U> crate::trace::Trace for Confirm<T, U>
|
||||
impl<U> crate::trace::Trace for Confirm<U>
|
||||
where
|
||||
T: StringType + Clone,
|
||||
U: AsRef<str>,
|
||||
{
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
strutil::StringType,
|
||||
strutil::TString,
|
||||
time::Duration,
|
||||
ui::{
|
||||
component::{Component, Event, EventCtx, Never},
|
||||
@ -30,22 +30,16 @@ impl From<PhysicalButton> for ButtonPos {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Button<T>
|
||||
where
|
||||
T: StringType,
|
||||
{
|
||||
pub struct Button {
|
||||
bounds: Rect,
|
||||
pos: ButtonPos,
|
||||
content: ButtonContent<T>,
|
||||
content: ButtonContent,
|
||||
styles: ButtonStyleSheet,
|
||||
state: State,
|
||||
}
|
||||
|
||||
impl<T> Button<T>
|
||||
where
|
||||
T: StringType,
|
||||
{
|
||||
pub fn new(pos: ButtonPos, content: ButtonContent<T>, styles: ButtonStyleSheet) -> Self {
|
||||
impl Button {
|
||||
pub fn new(pos: ButtonPos, content: ButtonContent, styles: ButtonStyleSheet) -> Self {
|
||||
Self {
|
||||
pos,
|
||||
content,
|
||||
@ -55,7 +49,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_button_details(pos: ButtonPos, btn_details: ButtonDetails<T>) -> Self {
|
||||
pub fn from_button_details(pos: ButtonPos, btn_details: ButtonDetails) -> Self {
|
||||
// Deciding between text and icon
|
||||
let style = btn_details.style();
|
||||
match btn_details.content {
|
||||
@ -64,7 +58,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_text(pos: ButtonPos, text: T, styles: ButtonStyleSheet) -> Self {
|
||||
pub fn with_text(pos: ButtonPos, text: TString<'static>, styles: ButtonStyleSheet) -> Self {
|
||||
Self::new(pos, ButtonContent::Text(text), styles)
|
||||
}
|
||||
|
||||
@ -72,7 +66,7 @@ where
|
||||
Self::new(pos, ButtonContent::Icon(image), styles)
|
||||
}
|
||||
|
||||
pub fn content(&self) -> &ButtonContent<T> {
|
||||
pub fn content(&self) -> &ButtonContent {
|
||||
&self.content
|
||||
}
|
||||
|
||||
@ -89,7 +83,7 @@ where
|
||||
}
|
||||
|
||||
/// Changing the text content of the button.
|
||||
pub fn set_text(&mut self, text: T) {
|
||||
pub fn set_text(&mut self, text: TString<'static>) {
|
||||
self.content = ButtonContent::Text(text);
|
||||
}
|
||||
|
||||
@ -127,7 +121,7 @@ where
|
||||
} else {
|
||||
match &self.content {
|
||||
ButtonContent::Text(text) => {
|
||||
let text_width = style.font.visible_text_width(text.as_ref());
|
||||
let text_width = text.map(|t| style.font.visible_text_width(t));
|
||||
if style.with_outline {
|
||||
text_width + 2 * theme::BUTTON_OUTLINE
|
||||
} else if style.with_arms {
|
||||
@ -176,7 +170,7 @@ where
|
||||
// Centering the text in case of fixed width.
|
||||
if let ButtonContent::Text(text) = &self.content {
|
||||
if let Some(fixed_width) = style.fixed_width {
|
||||
let diff = fixed_width - style.font.visible_text_width(text.as_ref());
|
||||
let diff = fixed_width - text.map(|t| style.font.visible_text_width(t));
|
||||
offset_x = diff / 2;
|
||||
}
|
||||
}
|
||||
@ -185,10 +179,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Component for Button<T>
|
||||
where
|
||||
T: StringType,
|
||||
{
|
||||
impl Component for Button {
|
||||
type Msg = Never;
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
@ -237,16 +228,15 @@ where
|
||||
|
||||
// Painting the content
|
||||
match &self.content {
|
||||
ButtonContent::Text(text) => {
|
||||
ButtonContent::Text(text) => text.map(|t| {
|
||||
display::text_left(
|
||||
self.get_text_baseline(style)
|
||||
- Offset::x(style.font.start_x_bearing(text.as_ref())),
|
||||
text.as_ref(),
|
||||
self.get_text_baseline(style) - Offset::x(style.font.start_x_bearing(t)),
|
||||
t,
|
||||
style.font,
|
||||
fg_color,
|
||||
bg_color,
|
||||
);
|
||||
}
|
||||
}),
|
||||
ButtonContent::Icon(icon) => {
|
||||
// Allowing for possible offset of the area from current style
|
||||
let icon_area = area.translate(style.offset);
|
||||
@ -284,8 +274,8 @@ enum State {
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum ButtonContent<T> {
|
||||
Text(T),
|
||||
pub enum ButtonContent {
|
||||
Text(TString<'static>),
|
||||
Icon(Icon),
|
||||
}
|
||||
|
||||
@ -352,8 +342,8 @@ impl ButtonStyleSheet {
|
||||
|
||||
/// Describing the button on the screen - only visuals.
|
||||
#[derive(Clone)]
|
||||
pub struct ButtonDetails<T> {
|
||||
pub content: ButtonContent<T>,
|
||||
pub struct ButtonDetails {
|
||||
pub content: ButtonContent,
|
||||
pub duration: Option<Duration>,
|
||||
with_outline: bool,
|
||||
with_arms: bool,
|
||||
@ -362,12 +352,9 @@ pub struct ButtonDetails<T> {
|
||||
pub send_long_press: bool,
|
||||
}
|
||||
|
||||
impl<T> ButtonDetails<T>
|
||||
where
|
||||
T: StringType,
|
||||
{
|
||||
impl ButtonDetails {
|
||||
/// Text button.
|
||||
pub fn text(text: T) -> Self {
|
||||
pub fn text(text: TString<'static>) -> Self {
|
||||
Self {
|
||||
content: ButtonContent::Text(text),
|
||||
duration: None,
|
||||
@ -393,17 +380,17 @@ where
|
||||
}
|
||||
|
||||
/// Resolves text and finds possible icon names.
|
||||
pub fn from_text_possible_icon(text: T) -> Self {
|
||||
match text.as_ref() {
|
||||
pub fn from_text_possible_icon(text: TString<'static>) -> Self {
|
||||
text.map(|t| match t {
|
||||
"" => Self::cancel_icon(),
|
||||
"<" => Self::left_arrow_icon(),
|
||||
"^" => Self::up_arrow_icon(),
|
||||
_ => Self::text(text),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Text with arms signalling double press.
|
||||
pub fn armed_text(text: T) -> Self {
|
||||
pub fn armed_text(text: TString<'static>) -> Self {
|
||||
Self::text(text).with_arms()
|
||||
}
|
||||
|
||||
@ -495,20 +482,17 @@ where
|
||||
|
||||
/// Holding the button details for all three possible buttons.
|
||||
#[derive(Clone)]
|
||||
pub struct ButtonLayout<T> {
|
||||
pub btn_left: Option<ButtonDetails<T>>,
|
||||
pub btn_middle: Option<ButtonDetails<T>>,
|
||||
pub btn_right: Option<ButtonDetails<T>>,
|
||||
pub struct ButtonLayout {
|
||||
pub btn_left: Option<ButtonDetails>,
|
||||
pub btn_middle: Option<ButtonDetails>,
|
||||
pub btn_right: Option<ButtonDetails>,
|
||||
}
|
||||
|
||||
impl<T> ButtonLayout<T>
|
||||
where
|
||||
T: StringType,
|
||||
{
|
||||
impl ButtonLayout {
|
||||
pub fn new(
|
||||
btn_left: Option<ButtonDetails<T>>,
|
||||
btn_middle: Option<ButtonDetails<T>>,
|
||||
btn_right: Option<ButtonDetails<T>>,
|
||||
btn_left: Option<ButtonDetails>,
|
||||
btn_middle: Option<ButtonDetails>,
|
||||
btn_right: Option<ButtonDetails>,
|
||||
) -> Self {
|
||||
Self {
|
||||
btn_left,
|
||||
@ -523,13 +507,8 @@ where
|
||||
Self::new(None, None, None)
|
||||
}
|
||||
|
||||
/// Default button layout for all three buttons - icons.
|
||||
pub fn default_three_icons() -> Self {
|
||||
Self::arrow_armed_arrow("SELECT".into())
|
||||
}
|
||||
|
||||
/// Special middle text for default icon layout.
|
||||
pub fn arrow_armed_arrow(text: T) -> Self {
|
||||
/// Arrows at sides, armed text in the middle.
|
||||
pub fn arrow_armed_arrow(text: TString<'static>) -> Self {
|
||||
Self::new(
|
||||
Some(ButtonDetails::left_arrow_icon()),
|
||||
Some(ButtonDetails::armed_text(text)),
|
||||
@ -538,7 +517,7 @@ where
|
||||
}
|
||||
|
||||
/// Left cancel, armed text and next right arrow.
|
||||
pub fn cancel_armed_arrow(text: T) -> Self {
|
||||
pub fn cancel_armed_arrow(text: TString<'static>) -> Self {
|
||||
Self::new(
|
||||
Some(ButtonDetails::cancel_icon()),
|
||||
Some(ButtonDetails::armed_text(text)),
|
||||
@ -547,7 +526,7 @@ where
|
||||
}
|
||||
|
||||
/// Middle armed text and next right arrow.
|
||||
pub fn none_armed_arrow(text: T) -> Self {
|
||||
pub fn none_armed_arrow(text: TString<'static>) -> Self {
|
||||
Self::new(
|
||||
None,
|
||||
Some(ButtonDetails::armed_text(text)),
|
||||
@ -556,7 +535,7 @@ where
|
||||
}
|
||||
|
||||
/// Left text, armed text and right info icon/text.
|
||||
pub fn text_armed_info(left: T, middle: T) -> Self {
|
||||
pub fn text_armed_info(left: TString<'static>, middle: TString<'static>) -> Self {
|
||||
Self::new(
|
||||
Some(ButtonDetails::from_text_possible_icon(left)),
|
||||
Some(ButtonDetails::armed_text(middle)),
|
||||
@ -565,7 +544,7 @@ where
|
||||
}
|
||||
|
||||
/// Left cancel, armed text and right info icon/text.
|
||||
pub fn cancel_armed_info(middle: T) -> Self {
|
||||
pub fn cancel_armed_info(middle: TString<'static>) -> Self {
|
||||
Self::new(
|
||||
Some(ButtonDetails::cancel_icon()),
|
||||
Some(ButtonDetails::armed_text(middle)),
|
||||
@ -574,7 +553,7 @@ where
|
||||
}
|
||||
|
||||
/// Left cancel, armed text and blank on right.
|
||||
pub fn cancel_armed_none(middle: T) -> Self {
|
||||
pub fn cancel_armed_none(middle: TString<'static>) -> Self {
|
||||
Self::new(
|
||||
Some(ButtonDetails::cancel_icon()),
|
||||
Some(ButtonDetails::armed_text(middle)),
|
||||
@ -583,7 +562,7 @@ where
|
||||
}
|
||||
|
||||
/// Left back arrow and middle armed text.
|
||||
pub fn arrow_armed_none(text: T) -> Self {
|
||||
pub fn arrow_armed_none(text: TString<'static>) -> Self {
|
||||
Self::new(
|
||||
Some(ButtonDetails::left_arrow_icon()),
|
||||
Some(ButtonDetails::armed_text(text)),
|
||||
@ -592,7 +571,7 @@ where
|
||||
}
|
||||
|
||||
/// Left and right texts.
|
||||
pub fn text_none_text(left: T, right: T) -> Self {
|
||||
pub fn text_none_text(left: TString<'static>, right: TString<'static>) -> Self {
|
||||
Self::new(
|
||||
Some(ButtonDetails::from_text_possible_icon(left)),
|
||||
None,
|
||||
@ -601,7 +580,7 @@ where
|
||||
}
|
||||
|
||||
/// Left text and right arrow.
|
||||
pub fn text_none_arrow(text: T) -> Self {
|
||||
pub fn text_none_arrow(text: TString<'static>) -> Self {
|
||||
Self::new(
|
||||
Some(ButtonDetails::from_text_possible_icon(text)),
|
||||
None,
|
||||
@ -610,7 +589,7 @@ where
|
||||
}
|
||||
|
||||
/// Left text and WIDE right arrow.
|
||||
pub fn text_none_arrow_wide(text: T) -> Self {
|
||||
pub fn text_none_arrow_wide(text: TString<'static>) -> Self {
|
||||
Self::new(
|
||||
Some(ButtonDetails::from_text_possible_icon(text)),
|
||||
None,
|
||||
@ -619,7 +598,7 @@ where
|
||||
}
|
||||
|
||||
/// Only right text.
|
||||
pub fn none_none_text(text: T) -> Self {
|
||||
pub fn none_none_text(text: TString<'static>) -> Self {
|
||||
Self::new(
|
||||
None,
|
||||
None,
|
||||
@ -637,7 +616,7 @@ where
|
||||
}
|
||||
|
||||
/// Left arrow and right text.
|
||||
pub fn arrow_none_text(text: T) -> Self {
|
||||
pub fn arrow_none_text(text: TString<'static>) -> Self {
|
||||
Self::new(
|
||||
Some(ButtonDetails::left_arrow_icon()),
|
||||
None,
|
||||
@ -646,7 +625,7 @@ where
|
||||
}
|
||||
|
||||
/// Up arrow left and right text.
|
||||
pub fn up_arrow_none_text(text: T) -> Self {
|
||||
pub fn up_arrow_none_text(text: TString<'static>) -> Self {
|
||||
Self::new(
|
||||
Some(ButtonDetails::up_arrow_icon()),
|
||||
None,
|
||||
@ -682,7 +661,7 @@ where
|
||||
}
|
||||
|
||||
/// Up arrow on left, middle text and info on the right.
|
||||
pub fn up_arrow_armed_info(text: T) -> Self {
|
||||
pub fn up_arrow_armed_info(text: TString<'static>) -> Self {
|
||||
Self::new(
|
||||
Some(ButtonDetails::up_arrow_icon()),
|
||||
Some(ButtonDetails::armed_text(text)),
|
||||
@ -700,7 +679,7 @@ where
|
||||
}
|
||||
|
||||
/// Cancel cross on left and text on the right.
|
||||
pub fn cancel_none_text(text: T) -> Self {
|
||||
pub fn cancel_none_text(text: TString<'static>) -> Self {
|
||||
Self::new(
|
||||
Some(ButtonDetails::cancel_icon()),
|
||||
None,
|
||||
@ -709,7 +688,7 @@ where
|
||||
}
|
||||
|
||||
/// Cancel cross on left and hold-to-confirm text on the right.
|
||||
pub fn cancel_none_htc(text: T) -> Self {
|
||||
pub fn cancel_none_htc(text: TString<'static>) -> Self {
|
||||
Self::new(
|
||||
Some(ButtonDetails::cancel_icon()),
|
||||
None,
|
||||
@ -718,7 +697,7 @@ where
|
||||
}
|
||||
|
||||
/// Arrow back on left and hold-to-confirm text on the right.
|
||||
pub fn arrow_none_htc(text: T) -> Self {
|
||||
pub fn arrow_none_htc(text: TString<'static>) -> Self {
|
||||
Self::new(
|
||||
Some(ButtonDetails::left_arrow_icon()),
|
||||
None,
|
||||
@ -727,12 +706,12 @@ where
|
||||
}
|
||||
|
||||
/// Only armed text in the middle.
|
||||
pub fn none_armed_none(text: T) -> Self {
|
||||
pub fn none_armed_none(text: TString<'static>) -> Self {
|
||||
Self::new(None, Some(ButtonDetails::armed_text(text)), None)
|
||||
}
|
||||
|
||||
/// HTC on both sides.
|
||||
pub fn htc_none_htc(left: T, right: T) -> Self {
|
||||
pub fn htc_none_htc(left: TString<'static>, right: TString<'static>) -> Self {
|
||||
Self::new(
|
||||
Some(ButtonDetails::text(left).with_default_duration()),
|
||||
None,
|
||||
@ -945,33 +924,30 @@ impl ButtonActions {
|
||||
// DEBUG-ONLY SECTION BELOW
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
use crate::strutil::ShortString;
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl<T: StringType> crate::trace::Trace for Button<T> {
|
||||
impl crate::trace::Trace for Button {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.component("Button");
|
||||
match &self.content {
|
||||
ButtonContent::Text(text) => t.string("text", text.as_ref()),
|
||||
match self.content {
|
||||
ButtonContent::Text(text) => t.string("text", text),
|
||||
ButtonContent::Icon(icon) => {
|
||||
t.null("text");
|
||||
t.string("icon", icon.name);
|
||||
t.string("icon", icon.name.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl<T: StringType> crate::trace::Trace for ButtonDetails<T> {
|
||||
impl crate::trace::Trace for ButtonDetails {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.component("ButtonDetails");
|
||||
match &self.content {
|
||||
match self.content {
|
||||
ButtonContent::Text(text) => {
|
||||
t.string("text", text.as_ref());
|
||||
t.string("text", text);
|
||||
}
|
||||
ButtonContent::Icon(icon) => {
|
||||
t.null("text");
|
||||
t.string("icon", icon.name);
|
||||
t.string("icon", icon.name.into());
|
||||
}
|
||||
}
|
||||
if let Some(duration) = &self.duration {
|
||||
@ -979,19 +955,3 @@ impl<T: StringType> crate::trace::Trace for ButtonDetails<T> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl ButtonAction {
|
||||
/// Describing the action as a string. Debug-only.
|
||||
pub fn string(&self) -> ShortString {
|
||||
match self {
|
||||
ButtonAction::NextPage => "Next".into(),
|
||||
ButtonAction::PrevPage => "Prev".into(),
|
||||
ButtonAction::FirstPage => "First".into(),
|
||||
ButtonAction::LastPage => "Last".into(),
|
||||
ButtonAction::Cancel => "Cancel".into(),
|
||||
ButtonAction::Confirm => "Confirm".into(),
|
||||
ButtonAction::Info => "Info".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ use super::{
|
||||
theme, Button, ButtonDetails, ButtonLayout, ButtonPos, HoldToConfirm, HoldToConfirmMsg,
|
||||
};
|
||||
use crate::{
|
||||
strutil::StringType,
|
||||
time::{Duration, Instant},
|
||||
ui::{
|
||||
component::{base::Event, Component, EventCtx, Pad, TimerToken},
|
||||
@ -52,20 +51,14 @@ pub enum ButtonControllerMsg {
|
||||
}
|
||||
|
||||
/// Defines what kind of button should be currently used.
|
||||
pub enum ButtonType<T>
|
||||
where
|
||||
T: StringType,
|
||||
{
|
||||
Button(Button<T>),
|
||||
HoldToConfirm(HoldToConfirm<T>),
|
||||
pub enum ButtonType {
|
||||
Button(Button),
|
||||
HoldToConfirm(HoldToConfirm),
|
||||
Nothing,
|
||||
}
|
||||
|
||||
impl<T> ButtonType<T>
|
||||
where
|
||||
T: StringType,
|
||||
{
|
||||
pub fn from_button_details(pos: ButtonPos, btn_details: Option<ButtonDetails<T>>) -> Self {
|
||||
impl ButtonType {
|
||||
pub fn from_button_details(pos: ButtonPos, btn_details: Option<ButtonDetails>) -> Self {
|
||||
if let Some(btn_details) = btn_details {
|
||||
if btn_details.duration.is_some() {
|
||||
Self::HoldToConfirm(HoldToConfirm::from_button_details(pos, btn_details))
|
||||
@ -107,12 +100,9 @@ where
|
||||
///
|
||||
/// Users have a choice of a normal button or Hold-to-confirm button.
|
||||
/// `button_type` specified what from those two is used, if anything.
|
||||
pub struct ButtonContainer<T>
|
||||
where
|
||||
T: StringType,
|
||||
{
|
||||
pub struct ButtonContainer {
|
||||
pos: ButtonPos,
|
||||
button_type: ButtonType<T>,
|
||||
button_type: ButtonType,
|
||||
/// Holds the timestamp of when the button was pressed.
|
||||
pressed_since: Option<Instant>,
|
||||
/// How long the button should be pressed to send `long_press=true` in
|
||||
@ -125,13 +115,10 @@ where
|
||||
send_long_press: bool,
|
||||
}
|
||||
|
||||
impl<T> ButtonContainer<T>
|
||||
where
|
||||
T: StringType,
|
||||
{
|
||||
impl ButtonContainer {
|
||||
/// Supplying `None` as `btn_details` marks the button inactive
|
||||
/// (it can be later activated in `set()`).
|
||||
pub fn new(pos: ButtonPos, btn_details: Option<ButtonDetails<T>>) -> Self {
|
||||
pub fn new(pos: ButtonPos, btn_details: Option<ButtonDetails>) -> Self {
|
||||
const DEFAULT_LONG_PRESS_MS: u32 = 1000;
|
||||
let send_long_press = btn_details
|
||||
.as_ref()
|
||||
@ -149,7 +136,7 @@ where
|
||||
/// Changing the state of the button.
|
||||
///
|
||||
/// Passing `None` as `btn_details` will mark the button as inactive.
|
||||
pub fn set(&mut self, btn_details: Option<ButtonDetails<T>>, button_area: Rect) {
|
||||
pub fn set(&mut self, btn_details: Option<ButtonDetails>, button_area: Rect) {
|
||||
self.send_long_press = btn_details
|
||||
.as_ref()
|
||||
.map_or(false, |btn| btn.send_long_press);
|
||||
@ -258,14 +245,11 @@ where
|
||||
///
|
||||
/// There is optional complexity with IgnoreButtonDelay, which gets executed
|
||||
/// only in cases where clients request it.
|
||||
pub struct ButtonController<T>
|
||||
where
|
||||
T: StringType,
|
||||
{
|
||||
pub struct ButtonController {
|
||||
pad: Pad,
|
||||
left_btn: ButtonContainer<T>,
|
||||
middle_btn: ButtonContainer<T>,
|
||||
right_btn: ButtonContainer<T>,
|
||||
left_btn: ButtonContainer,
|
||||
middle_btn: ButtonContainer,
|
||||
right_btn: ButtonContainer,
|
||||
state: ButtonState,
|
||||
/// Button area is needed so the buttons
|
||||
/// can be "re-placed" after their text is changed
|
||||
@ -277,11 +261,8 @@ where
|
||||
handle_middle_button: bool,
|
||||
}
|
||||
|
||||
impl<T> ButtonController<T>
|
||||
where
|
||||
T: StringType,
|
||||
{
|
||||
pub fn new(btn_layout: ButtonLayout<T>) -> Self {
|
||||
impl ButtonController {
|
||||
pub fn new(btn_layout: ButtonLayout) -> Self {
|
||||
let handle_middle_button = btn_layout.btn_middle.is_some();
|
||||
Self {
|
||||
pad: Pad::with_background(theme::BG).with_clear(),
|
||||
@ -303,7 +284,7 @@ where
|
||||
}
|
||||
|
||||
/// Updating all the three buttons to the wanted states.
|
||||
pub fn set(&mut self, btn_layout: ButtonLayout<T>) {
|
||||
pub fn set(&mut self, btn_layout: ButtonLayout) {
|
||||
self.handle_middle_button = btn_layout.btn_middle.is_some();
|
||||
self.pad.clear();
|
||||
self.left_btn.set(btn_layout.btn_left, self.button_area);
|
||||
@ -406,10 +387,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Component for ButtonController<T>
|
||||
where
|
||||
T: StringType,
|
||||
{
|
||||
impl Component for ButtonController {
|
||||
type Msg = ButtonControllerMsg;
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
@ -794,7 +772,7 @@ impl Component for AutomaticMover {
|
||||
// DEBUG-ONLY SECTION BELOW
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl<T: StringType> crate::trace::Trace for ButtonContainer<T> {
|
||||
impl crate::trace::Trace for ButtonContainer {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
if let ButtonType::Button(btn) = &self.button_type {
|
||||
btn.trace(t);
|
||||
@ -805,7 +783,7 @@ impl<T: StringType> crate::trace::Trace for ButtonContainer<T> {
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl<T: StringType> crate::trace::Trace for ButtonController<T> {
|
||||
impl crate::trace::Trace for ButtonController {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.component("ButtonController");
|
||||
t.child("left_btn", &self.left_btn);
|
||||
|
@ -2,6 +2,7 @@ use core::mem;
|
||||
|
||||
use crate::{
|
||||
strutil::StringType,
|
||||
translations::TR,
|
||||
ui::{
|
||||
component::{
|
||||
base::Never,
|
||||
@ -16,8 +17,6 @@ use crate::{
|
||||
|
||||
use super::theme;
|
||||
|
||||
const HEADER: &str = "COINJOIN IN PROGRESS";
|
||||
const FOOTER: &str = "Do not disconnect your Trezor!";
|
||||
const FOOTER_TEXT_MARGIN: i16 = 8;
|
||||
const LOADER_OFFSET: i16 = -15;
|
||||
const LOADER_SPEED: u16 = 10;
|
||||
@ -89,7 +88,7 @@ where
|
||||
if self.indeterminate {
|
||||
text_multiline(
|
||||
self.area,
|
||||
HEADER,
|
||||
TR::coinjoin__title_progress.into(),
|
||||
Font::BOLD,
|
||||
theme::FG,
|
||||
theme::BG,
|
||||
@ -114,7 +113,7 @@ where
|
||||
// BOTTOM
|
||||
let top_rest = text_multiline_bottom(
|
||||
self.area,
|
||||
FOOTER,
|
||||
TR::coinjoin__do_not_disconnect.into(),
|
||||
Font::BOLD,
|
||||
theme::FG,
|
||||
theme::BG,
|
||||
@ -123,7 +122,7 @@ where
|
||||
if let Some(rest) = top_rest {
|
||||
text_multiline_bottom(
|
||||
rest.inset(Insets::bottom(FOOTER_TEXT_MARGIN)),
|
||||
self.text.as_ref(),
|
||||
self.text.as_ref().into(),
|
||||
Font::NORMAL,
|
||||
theme::FG,
|
||||
theme::BG,
|
||||
@ -140,8 +139,8 @@ where
|
||||
{
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.component("CoinJoinProgress");
|
||||
t.string("header", HEADER);
|
||||
t.string("text", self.text.as_ref());
|
||||
t.string("footer", FOOTER);
|
||||
t.string("header", TR::coinjoin__title_progress.into());
|
||||
t.string("text", self.text.as_ref().into());
|
||||
t.string("footer", TR::coinjoin__do_not_disconnect.into());
|
||||
}
|
||||
}
|
||||
|
@ -6,23 +6,23 @@ use crate::ui::{
|
||||
use super::theme;
|
||||
|
||||
/// Display white text on black background
|
||||
pub fn display_left<T: AsRef<str>>(baseline: Point, text: &T, font: Font) {
|
||||
pub fn display_left<T: AsRef<str>>(baseline: Point, text: T, font: Font) {
|
||||
display::text_left(baseline, text.as_ref(), font, theme::FG, theme::BG);
|
||||
}
|
||||
|
||||
/// Display black text on white background
|
||||
pub fn display_inverse<T: AsRef<str>>(baseline: Point, text: &T, font: Font) {
|
||||
pub fn display_inverse<T: AsRef<str>>(baseline: Point, text: T, font: Font) {
|
||||
display::text_left(baseline, text.as_ref(), font, theme::BG, theme::FG);
|
||||
}
|
||||
|
||||
/// Display white text on black background,
|
||||
/// centered around a baseline Point
|
||||
pub fn display_center<T: AsRef<str>>(baseline: Point, text: &T, font: Font) {
|
||||
pub fn display_center<T: AsRef<str>>(baseline: Point, text: T, font: Font) {
|
||||
display::text_center(baseline, text.as_ref(), font, theme::FG, theme::BG);
|
||||
}
|
||||
|
||||
/// Display white text on black background,
|
||||
/// with right boundary at a baseline Point
|
||||
pub fn display_right<T: AsRef<str>>(baseline: Point, text: &T, font: Font) {
|
||||
pub fn display_right<T: AsRef<str>>(baseline: Point, text: T, font: Font) {
|
||||
display::text_right(baseline, text.as_ref(), font, theme::FG, theme::BG);
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ where
|
||||
content_area: Rect,
|
||||
title_area: Rect,
|
||||
pad: Pad,
|
||||
buttons: Child<ButtonController<T>>,
|
||||
buttons: Child<ButtonController>,
|
||||
page_counter: usize,
|
||||
return_confirmed_index: bool,
|
||||
show_scrollbar: bool,
|
||||
|
@ -79,7 +79,7 @@ where
|
||||
T: StringType + Clone,
|
||||
{
|
||||
formatted: FormattedText<T>,
|
||||
btn_layout: ButtonLayout<T>,
|
||||
btn_layout: ButtonLayout,
|
||||
btn_actions: ButtonActions,
|
||||
current_page: usize,
|
||||
page_count: usize,
|
||||
@ -93,7 +93,7 @@ where
|
||||
T: StringType + Clone,
|
||||
{
|
||||
pub fn new(
|
||||
btn_layout: ButtonLayout<T>,
|
||||
btn_layout: ButtonLayout,
|
||||
btn_actions: ButtonActions,
|
||||
formatted: FormattedText<T>,
|
||||
) -> Self {
|
||||
@ -137,7 +137,7 @@ where
|
||||
bounds
|
||||
}
|
||||
|
||||
pub fn btn_layout(&self) -> ButtonLayout<T> {
|
||||
pub fn btn_layout(&self) -> ButtonLayout {
|
||||
// When we are in pagination inside this flow,
|
||||
// show the up and down arrows on appropriate sides.
|
||||
let current = self.btn_layout.clone();
|
||||
@ -235,7 +235,7 @@ where
|
||||
t.component("Page");
|
||||
if let Some(title) = &self.title {
|
||||
// Not calling it "title" as that is already traced by FlowPage
|
||||
t.string("page_title", title.as_ref());
|
||||
t.string("page_title", title.as_ref().into());
|
||||
}
|
||||
t.int("active_page", self.current_page as i64);
|
||||
t.int("page_count", self.page_count as i64);
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
strutil::StringType,
|
||||
strutil::TString,
|
||||
time::{Duration, Instant},
|
||||
ui::{
|
||||
component::{Component, Event, EventCtx},
|
||||
@ -18,21 +18,21 @@ pub enum HoldToConfirmMsg {
|
||||
FailedToConfirm,
|
||||
}
|
||||
|
||||
pub struct HoldToConfirm<T>
|
||||
where
|
||||
T: StringType,
|
||||
{
|
||||
pub struct HoldToConfirm {
|
||||
pos: ButtonPos,
|
||||
loader: Loader<T>,
|
||||
loader: Loader,
|
||||
text_width: i16,
|
||||
}
|
||||
|
||||
impl<T> HoldToConfirm<T>
|
||||
where
|
||||
T: StringType,
|
||||
{
|
||||
pub fn text(pos: ButtonPos, text: T, styles: LoaderStyleSheet, duration: Duration) -> Self {
|
||||
let text_width = styles.normal.font.visible_text_width(text.as_ref());
|
||||
impl HoldToConfirm {
|
||||
pub fn text<T: Into<TString<'static>>>(
|
||||
pos: ButtonPos,
|
||||
text: T,
|
||||
styles: LoaderStyleSheet,
|
||||
duration: Duration,
|
||||
) -> Self {
|
||||
let text = text.into();
|
||||
let text_width = text.map(|t| styles.normal.font.visible_text_width(t));
|
||||
Self {
|
||||
pos,
|
||||
loader: Loader::text(text, styles).with_growing_duration(duration),
|
||||
@ -40,7 +40,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_button_details(pos: ButtonPos, btn_details: ButtonDetails<T>) -> Self {
|
||||
pub fn from_button_details(pos: ButtonPos, btn_details: ButtonDetails) -> Self {
|
||||
let duration = btn_details
|
||||
.duration
|
||||
.unwrap_or_else(|| Duration::from_millis(DEFAULT_DURATION_MS));
|
||||
@ -53,7 +53,8 @@ where
|
||||
}
|
||||
|
||||
/// Updating the text of the component and re-placing it.
|
||||
pub fn set_text(&mut self, text: T, button_area: Rect) {
|
||||
pub fn set_text<T: Into<TString<'static>>>(&mut self, text: T, button_area: Rect) {
|
||||
let text = text.into();
|
||||
self.text_width = self.loader.get_text_width(&text);
|
||||
self.loader.set_text(text);
|
||||
self.place(button_area);
|
||||
@ -71,7 +72,7 @@ where
|
||||
self.loader.get_duration()
|
||||
}
|
||||
|
||||
pub fn get_text(&self) -> &T {
|
||||
pub fn get_text(&self) -> TString<'static> {
|
||||
self.loader.get_text()
|
||||
}
|
||||
|
||||
@ -85,10 +86,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Component for HoldToConfirm<T>
|
||||
where
|
||||
T: StringType,
|
||||
{
|
||||
impl Component for HoldToConfirm {
|
||||
type Msg = HoldToConfirmMsg;
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
@ -129,10 +127,7 @@ where
|
||||
// DEBUG-ONLY SECTION BELOW
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl<T> crate::trace::Trace for HoldToConfirm<T>
|
||||
where
|
||||
T: StringType,
|
||||
{
|
||||
impl crate::trace::Trace for HoldToConfirm {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.component("HoldToConfirm");
|
||||
t.child("loader", &self.loader);
|
||||
|
@ -1,5 +1,8 @@
|
||||
use crate::{
|
||||
error::Error,
|
||||
micropython::buffer::StrBuffer,
|
||||
strutil::StringType,
|
||||
translations::TR,
|
||||
trezorhal::usb::usb_configured,
|
||||
ui::{
|
||||
component::{Child, Component, Event, EventCtx, Label},
|
||||
@ -60,7 +63,7 @@ where
|
||||
label: Label<T>,
|
||||
notification: Option<(T, u8)>,
|
||||
/// Used for HTC functionality to lock device from homescreen
|
||||
invisible_buttons: Child<ButtonController<T>>,
|
||||
invisible_buttons: Child<ButtonController>,
|
||||
/// Holds the loader component
|
||||
loader: Option<Child<ProgressLoader<T>>>,
|
||||
/// Whether to show the loader or not
|
||||
@ -107,10 +110,11 @@ where
|
||||
if !usb_configured() {
|
||||
self.fill_notification_background();
|
||||
// TODO: fill warning icons here as well?
|
||||
display_center(baseline, &"NO USB CONNECTION", NOTIFICATION_FONT);
|
||||
TR::homescreen__title_no_usb_connection
|
||||
.map_translated(|t| display_center(baseline, t, NOTIFICATION_FONT));
|
||||
} else if let Some((notification, _level)) = &self.notification {
|
||||
self.fill_notification_background();
|
||||
display_center(baseline, ¬ification.as_ref(), NOTIFICATION_FONT);
|
||||
display_center(baseline, notification.as_ref(), NOTIFICATION_FONT);
|
||||
// Painting warning icons in top corners when the text is short enough not to
|
||||
// collide with them
|
||||
let icon_width = NOTIFICATION_ICON.toif.width();
|
||||
@ -239,9 +243,9 @@ where
|
||||
T: StringType,
|
||||
{
|
||||
label: Child<Label<T>>,
|
||||
instruction: Child<Label<T>>,
|
||||
instruction: Child<Label<StrBuffer>>,
|
||||
/// Used for unlocking the device from lockscreen
|
||||
invisible_buttons: Child<ButtonController<T>>,
|
||||
invisible_buttons: Child<ButtonController>,
|
||||
/// Display coinjoin icon?
|
||||
coinjoin_icon: Option<Icon>,
|
||||
/// Screensaver mode (keep screen black)
|
||||
@ -252,22 +256,25 @@ impl<T> Lockscreen<T>
|
||||
where
|
||||
T: StringType + Clone,
|
||||
{
|
||||
pub fn new(label: T, bootscreen: bool, coinjoin_authorized: bool) -> Self {
|
||||
pub fn new(label: T, bootscreen: bool, coinjoin_authorized: bool) -> Result<Self, Error> {
|
||||
// Buttons will not be visible, we only need all three of them to be present,
|
||||
// so that even middle-click triggers the event.
|
||||
let invisible_btn_layout = ButtonLayout::arrow_armed_arrow("".into());
|
||||
let instruction_str = if bootscreen {
|
||||
"Click to Connect"
|
||||
TR::homescreen__click_to_connect
|
||||
} else {
|
||||
"Click to Unlock"
|
||||
TR::homescreen__click_to_unlock
|
||||
};
|
||||
Lockscreen {
|
||||
Ok(Lockscreen {
|
||||
label: Child::new(Label::centered(label, theme::TEXT_BIG)),
|
||||
instruction: Child::new(Label::centered(instruction_str.into(), theme::TEXT_NORMAL)),
|
||||
instruction: Child::new(Label::centered(
|
||||
instruction_str.try_into()?,
|
||||
theme::TEXT_NORMAL,
|
||||
)),
|
||||
invisible_buttons: Child::new(ButtonController::new(invisible_btn_layout)),
|
||||
coinjoin_icon: coinjoin_authorized.then_some(theme::ICON_COINJOIN),
|
||||
screensaver: !bootscreen,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -321,7 +328,7 @@ where
|
||||
{
|
||||
title: Child<Label<T>>,
|
||||
buffer_func: F,
|
||||
buttons: Child<ButtonController<T>>,
|
||||
buttons: Child<ButtonController>,
|
||||
}
|
||||
|
||||
impl<T, F> ConfirmHomescreen<T, F>
|
||||
@ -329,7 +336,7 @@ where
|
||||
T: StringType + Clone,
|
||||
{
|
||||
pub fn new(title: T, buffer_func: F) -> Self {
|
||||
let btn_layout = ButtonLayout::cancel_none_text("CHANGE".into());
|
||||
let btn_layout = ButtonLayout::cancel_none_text(TR::buttons__change.into());
|
||||
ConfirmHomescreen {
|
||||
title: Child::new(Label::centered(title, theme::TEXT_BOLD)),
|
||||
buffer_func,
|
||||
|
@ -1,10 +1,7 @@
|
||||
use crate::{
|
||||
strutil::StringType,
|
||||
ui::{
|
||||
component::{Child, Component, Event, EventCtx, Pad},
|
||||
geometry::{Insets, Offset, Rect},
|
||||
util::animation_disabled,
|
||||
},
|
||||
use crate::ui::{
|
||||
component::{Child, Component, Event, EventCtx, Pad},
|
||||
geometry::{Insets, Offset, Rect},
|
||||
util::animation_disabled,
|
||||
};
|
||||
|
||||
use super::super::{
|
||||
@ -13,7 +10,7 @@ use super::super::{
|
||||
|
||||
const DEFAULT_ITEMS_DISTANCE: i16 = 10;
|
||||
|
||||
pub trait Choice<T: StringType> {
|
||||
pub trait Choice {
|
||||
// Only `paint_center` is required, the rest is optional
|
||||
// and therefore has a default implementation.
|
||||
fn paint_center(&self, area: Rect, inverse: bool);
|
||||
@ -26,9 +23,7 @@ pub trait Choice<T: StringType> {
|
||||
0
|
||||
}
|
||||
|
||||
fn btn_layout(&self) -> ButtonLayout<T> {
|
||||
ButtonLayout::default_three_icons()
|
||||
}
|
||||
fn btn_layout(&self) -> ButtonLayout;
|
||||
|
||||
/// Whether it is possible to do the middle action event without
|
||||
/// releasing the button - after long-press duration is reached.
|
||||
@ -46,9 +41,9 @@ pub trait Choice<T: StringType> {
|
||||
/// but offers a "lazy-loading" way of requesting the
|
||||
/// items only when they are needed, one-by-one.
|
||||
/// This way, no more than one item is stored in memory at any time.
|
||||
pub trait ChoiceFactory<T: StringType> {
|
||||
pub trait ChoiceFactory {
|
||||
type Action;
|
||||
type Item: Choice<T>;
|
||||
type Item: Choice;
|
||||
|
||||
fn count(&self) -> usize;
|
||||
fn get(&self, index: usize) -> (Self::Item, Self::Action);
|
||||
@ -67,14 +62,13 @@ pub trait ChoiceFactory<T: StringType> {
|
||||
///
|
||||
/// `is_carousel` can be used to make the choice page "infinite" -
|
||||
/// after reaching one end, users will appear at the other end.
|
||||
pub struct ChoicePage<F, T, A>
|
||||
pub struct ChoicePage<F, A>
|
||||
where
|
||||
F: ChoiceFactory<T, Action = A>,
|
||||
T: StringType,
|
||||
F: ChoiceFactory<Action = A>,
|
||||
{
|
||||
choices: F,
|
||||
pad: Pad,
|
||||
buttons: Child<ButtonController<T>>,
|
||||
buttons: Child<ButtonController>,
|
||||
page_counter: usize,
|
||||
/// How many pixels are between the items.
|
||||
items_distance: i16,
|
||||
@ -97,10 +91,9 @@ where
|
||||
animated_steps_to_do: i16,
|
||||
}
|
||||
|
||||
impl<F, T, A> ChoicePage<F, T, A>
|
||||
impl<F, A> ChoicePage<F, A>
|
||||
where
|
||||
F: ChoiceFactory<T, Action = A>,
|
||||
T: StringType + Clone,
|
||||
F: ChoiceFactory<Action = A>,
|
||||
{
|
||||
pub fn new(choices: F) -> Self {
|
||||
let initial_btn_layout = choices.get(0).0.btn_layout();
|
||||
@ -283,12 +276,12 @@ where
|
||||
}
|
||||
|
||||
/// Getting the choice on the current index
|
||||
fn get_current_choice(&self) -> (<F as ChoiceFactory<T>>::Item, A) {
|
||||
fn get_current_choice(&self) -> (<F as ChoiceFactory>::Item, A) {
|
||||
self.choices.get(self.page_counter)
|
||||
}
|
||||
|
||||
/// Getting the current item
|
||||
pub fn get_current_item(&self) -> <F as ChoiceFactory<T>>::Item {
|
||||
pub fn get_current_item(&self) -> <F as ChoiceFactory>::Item {
|
||||
self.get_current_choice().0
|
||||
}
|
||||
|
||||
@ -484,10 +477,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, T, A> Component for ChoicePage<F, T, A>
|
||||
impl<F, A> Component for ChoicePage<F, A>
|
||||
where
|
||||
F: ChoiceFactory<T, Action = A>,
|
||||
T: StringType + Clone,
|
||||
F: ChoiceFactory<Action = A>,
|
||||
{
|
||||
type Msg = (A, bool);
|
||||
|
||||
@ -599,11 +591,10 @@ where
|
||||
// DEBUG-ONLY SECTION BELOW
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl<F, T, A> crate::trace::Trace for ChoicePage<F, T, A>
|
||||
impl<F, A> crate::trace::Trace for ChoicePage<F, A>
|
||||
where
|
||||
F: ChoiceFactory<T, Action = A>,
|
||||
T: StringType + Clone,
|
||||
<F as ChoiceFactory<T>>::Item: crate::trace::Trace,
|
||||
F: ChoiceFactory<Action = A>,
|
||||
<F as ChoiceFactory>::Item: crate::trace::Trace,
|
||||
{
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.component("ChoicePage");
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
strutil::{ShortString, StringType},
|
||||
strutil::ShortString,
|
||||
ui::{
|
||||
display::{self, rect_fill, rect_fill_corners, rect_outline_rounded, Font, Icon},
|
||||
geometry::{Alignment2D, Offset, Rect},
|
||||
@ -14,16 +14,16 @@ const ICON_RIGHT_PADDING: i16 = 2;
|
||||
|
||||
/// Simple string component used as a choice item.
|
||||
#[derive(Clone)]
|
||||
pub struct ChoiceItem<T: StringType> {
|
||||
pub struct ChoiceItem {
|
||||
text: ShortString,
|
||||
icon: Option<Icon>,
|
||||
btn_layout: ButtonLayout<T>,
|
||||
btn_layout: ButtonLayout,
|
||||
font: Font,
|
||||
middle_action_without_release: bool,
|
||||
}
|
||||
|
||||
impl<T: StringType> ChoiceItem<T> {
|
||||
pub fn new<U: AsRef<str>>(text: U, btn_layout: ButtonLayout<T>) -> Self {
|
||||
impl ChoiceItem {
|
||||
pub fn new<U: AsRef<str>>(text: U, btn_layout: ButtonLayout) -> Self {
|
||||
Self {
|
||||
text: String::from(text.as_ref()),
|
||||
icon: None,
|
||||
@ -55,17 +55,17 @@ impl<T: StringType> ChoiceItem<T> {
|
||||
}
|
||||
|
||||
/// Setting left button.
|
||||
pub fn set_left_btn(&mut self, btn_left: Option<ButtonDetails<T>>) {
|
||||
pub fn set_left_btn(&mut self, btn_left: Option<ButtonDetails>) {
|
||||
self.btn_layout.btn_left = btn_left;
|
||||
}
|
||||
|
||||
/// Setting middle button.
|
||||
pub fn set_middle_btn(&mut self, btn_middle: Option<ButtonDetails<T>>) {
|
||||
pub fn set_middle_btn(&mut self, btn_middle: Option<ButtonDetails>) {
|
||||
self.btn_layout.btn_middle = btn_middle;
|
||||
}
|
||||
|
||||
/// Setting right button.
|
||||
pub fn set_right_btn(&mut self, btn_right: Option<ButtonDetails<T>>) {
|
||||
pub fn set_right_btn(&mut self, btn_right: Option<ButtonDetails>) {
|
||||
self.btn_layout.btn_right = btn_right;
|
||||
}
|
||||
|
||||
@ -87,10 +87,7 @@ impl<T: StringType> ChoiceItem<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Choice<T> for ChoiceItem<T>
|
||||
where
|
||||
T: StringType + Clone,
|
||||
{
|
||||
impl Choice for ChoiceItem {
|
||||
/// Painting the item as the main choice in the middle.
|
||||
/// Showing both the icon and text, if the icon is available.
|
||||
fn paint_center(&self, area: Rect, inverse: bool) {
|
||||
@ -125,7 +122,7 @@ where
|
||||
}
|
||||
|
||||
/// Getting current button layout.
|
||||
fn btn_layout(&self) -> ButtonLayout<T> {
|
||||
fn btn_layout(&self) -> ButtonLayout {
|
||||
self.btn_layout.clone()
|
||||
}
|
||||
|
||||
@ -196,9 +193,9 @@ fn paint_text_icon(
|
||||
// DEBUG-ONLY SECTION BELOW
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl<T: StringType> crate::trace::Trace for ChoiceItem<T> {
|
||||
impl crate::trace::Trace for ChoiceItem {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.component("ChoiceItem");
|
||||
t.string("content", self.text.as_ref());
|
||||
t.string("content", self.text.as_str().into());
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,13 @@
|
||||
pub mod choice;
|
||||
pub mod choice_item;
|
||||
|
||||
#[cfg(feature = "translations")]
|
||||
pub mod number_input;
|
||||
#[cfg(feature = "translations")]
|
||||
pub mod passphrase;
|
||||
#[cfg(feature = "translations")]
|
||||
pub mod pin;
|
||||
#[cfg(feature = "translations")]
|
||||
pub mod simple_choice;
|
||||
#[cfg(feature = "translations")]
|
||||
pub mod wordlist;
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
strutil::StringType,
|
||||
translations::TR,
|
||||
ui::{
|
||||
component::{Component, Event, EventCtx},
|
||||
geometry::Rect,
|
||||
@ -20,9 +20,9 @@ impl ChoiceFactoryNumberInput {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: StringType + Clone> ChoiceFactory<T> for ChoiceFactoryNumberInput {
|
||||
impl ChoiceFactory for ChoiceFactoryNumberInput {
|
||||
type Action = u32;
|
||||
type Item = ChoiceItem<T>;
|
||||
type Item = ChoiceItem;
|
||||
|
||||
fn count(&self) -> usize {
|
||||
(self.max - self.min + 1) as usize
|
||||
@ -31,14 +31,17 @@ impl<T: StringType + Clone> ChoiceFactory<T> for ChoiceFactoryNumberInput {
|
||||
fn get(&self, choice_index: usize) -> (Self::Item, Self::Action) {
|
||||
let num = self.min + choice_index as u32;
|
||||
let text: String<10> = String::from(num);
|
||||
let mut choice_item = ChoiceItem::new(text, ButtonLayout::default_three_icons());
|
||||
let mut choice_item = ChoiceItem::new(
|
||||
text,
|
||||
ButtonLayout::arrow_armed_arrow(TR::buttons__select.into()),
|
||||
);
|
||||
|
||||
// Disabling prev/next buttons for the first/last choice.
|
||||
// (could be done to the same button if there is only one)
|
||||
if choice_index == 0 {
|
||||
choice_item.set_left_btn(None);
|
||||
}
|
||||
if choice_index == <ChoiceFactoryNumberInput as ChoiceFactory<T>>::count(self) - 1 {
|
||||
if choice_index == <ChoiceFactoryNumberInput as ChoiceFactory>::count(self) - 1 {
|
||||
choice_item.set_right_btn(None);
|
||||
}
|
||||
|
||||
@ -48,15 +51,12 @@ impl<T: StringType + Clone> ChoiceFactory<T> for ChoiceFactoryNumberInput {
|
||||
|
||||
/// Simple wrapper around `ChoicePage` that allows for
|
||||
/// inputting a list of values and receiving the chosen one.
|
||||
pub struct NumberInput<T: StringType + Clone> {
|
||||
choice_page: ChoicePage<ChoiceFactoryNumberInput, T, u32>,
|
||||
pub struct NumberInput {
|
||||
choice_page: ChoicePage<ChoiceFactoryNumberInput, u32>,
|
||||
min: u32,
|
||||
}
|
||||
|
||||
impl<T> NumberInput<T>
|
||||
where
|
||||
T: StringType + Clone,
|
||||
{
|
||||
impl NumberInput {
|
||||
pub fn new(min: u32, max: u32, init_value: u32) -> Self {
|
||||
let choices = ChoiceFactoryNumberInput::new(min, max);
|
||||
let initial_page = init_value - min;
|
||||
@ -67,10 +67,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Component for NumberInput<T>
|
||||
where
|
||||
T: StringType + Clone,
|
||||
{
|
||||
impl Component for NumberInput {
|
||||
type Msg = u32;
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
@ -89,10 +86,7 @@ where
|
||||
// DEBUG-ONLY SECTION BELOW
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl<T> crate::trace::Trace for NumberInput<T>
|
||||
where
|
||||
T: StringType + Clone,
|
||||
{
|
||||
impl crate::trace::Trace for NumberInput {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.component("NumberInput");
|
||||
t.child("choice_page", &self.choice_page);
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::{
|
||||
strutil::StringType,
|
||||
strutil::TString,
|
||||
translations::TR,
|
||||
trezorhal::random,
|
||||
ui::{
|
||||
component::{text::common::TextBox, Child, Component, ComponentExt, Event, EventCtx},
|
||||
@ -43,64 +44,72 @@ const DIGITS_INDEX: usize = 5;
|
||||
const SPECIAL_INDEX: usize = 6;
|
||||
const SPACE_INDEX: usize = 7;
|
||||
|
||||
/// Menu text, action, icon data, middle button with CONFIRM, without_release
|
||||
const MENU: [(&str, PassphraseAction, Option<Icon>, bool, bool); MENU_LENGTH] = [
|
||||
(
|
||||
"SHOW",
|
||||
PassphraseAction::Show,
|
||||
Some(theme::ICON_EYE),
|
||||
true,
|
||||
false,
|
||||
),
|
||||
(
|
||||
"CANCEL_OR_DELETE", // will be chosen dynamically
|
||||
PassphraseAction::CancelOrDelete,
|
||||
None,
|
||||
true,
|
||||
true, // without_release
|
||||
),
|
||||
(
|
||||
"ENTER",
|
||||
PassphraseAction::Enter,
|
||||
Some(theme::ICON_TICK),
|
||||
true,
|
||||
false,
|
||||
),
|
||||
(
|
||||
"abc",
|
||||
PassphraseAction::Category(ChoiceCategory::LowercaseLetter),
|
||||
None,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
(
|
||||
"ABC",
|
||||
PassphraseAction::Category(ChoiceCategory::UppercaseLetter),
|
||||
None,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
(
|
||||
"123",
|
||||
PassphraseAction::Category(ChoiceCategory::Digit),
|
||||
None,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
(
|
||||
"#$!",
|
||||
PassphraseAction::Category(ChoiceCategory::SpecialSymbol),
|
||||
None,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
(
|
||||
"SPACE",
|
||||
PassphraseAction::Character(' '),
|
||||
Some(theme::ICON_SPACE),
|
||||
false,
|
||||
false,
|
||||
),
|
||||
#[derive(Clone)]
|
||||
struct MenuItem {
|
||||
text: TString<'static>,
|
||||
action: PassphraseAction,
|
||||
icon: Option<Icon>,
|
||||
show_confirm: bool,
|
||||
without_release: bool,
|
||||
}
|
||||
|
||||
const MENU: [MenuItem; MENU_LENGTH] = [
|
||||
MenuItem {
|
||||
text: TR::inputs__show.as_tstring(),
|
||||
action: PassphraseAction::Show,
|
||||
icon: Some(theme::ICON_EYE),
|
||||
show_confirm: true,
|
||||
without_release: false,
|
||||
},
|
||||
MenuItem {
|
||||
text: TString::from_str("CANCEL OR DELETE"),
|
||||
action: PassphraseAction::CancelOrDelete,
|
||||
icon: None,
|
||||
show_confirm: true,
|
||||
without_release: true,
|
||||
},
|
||||
MenuItem {
|
||||
text: TR::inputs__enter.as_tstring(),
|
||||
action: PassphraseAction::Enter,
|
||||
icon: Some(theme::ICON_TICK),
|
||||
show_confirm: true,
|
||||
without_release: false,
|
||||
},
|
||||
MenuItem {
|
||||
text: TString::from_str("abc"),
|
||||
action: PassphraseAction::Category(ChoiceCategory::LowercaseLetter),
|
||||
icon: None,
|
||||
show_confirm: false,
|
||||
without_release: false,
|
||||
},
|
||||
MenuItem {
|
||||
text: TString::from_str("ABC"),
|
||||
action: PassphraseAction::Category(ChoiceCategory::UppercaseLetter),
|
||||
icon: None,
|
||||
show_confirm: false,
|
||||
without_release: false,
|
||||
},
|
||||
MenuItem {
|
||||
text: TString::from_str("123"),
|
||||
action: PassphraseAction::Category(ChoiceCategory::Digit),
|
||||
icon: None,
|
||||
show_confirm: false,
|
||||
without_release: false,
|
||||
},
|
||||
MenuItem {
|
||||
text: TString::from_str("#$!"),
|
||||
action: PassphraseAction::Category(ChoiceCategory::SpecialSymbol),
|
||||
icon: None,
|
||||
show_confirm: false,
|
||||
without_release: false,
|
||||
},
|
||||
MenuItem {
|
||||
text: TR::inputs__space.as_tstring(),
|
||||
action: PassphraseAction::Character(' '),
|
||||
icon: Some(theme::ICON_SPACE),
|
||||
show_confirm: false,
|
||||
without_release: false,
|
||||
},
|
||||
];
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
@ -172,66 +181,74 @@ impl ChoiceFactoryPassphrase {
|
||||
}
|
||||
|
||||
/// MENU choices with accept and cancel hold-to-confirm side buttons.
|
||||
fn get_menu_item<T: StringType>(
|
||||
&self,
|
||||
choice_index: usize,
|
||||
) -> (ChoiceItem<T>, PassphraseAction) {
|
||||
fn get_menu_item(&self, choice_index: usize) -> (ChoiceItem, PassphraseAction) {
|
||||
#[allow(const_item_mutation)]
|
||||
let current_item = &mut MENU[choice_index];
|
||||
// More options for CANCEL/DELETE button
|
||||
let (mut text, action, mut icon, show_confirm, without_release) = MENU[choice_index];
|
||||
if matches!(action, PassphraseAction::CancelOrDelete) {
|
||||
if matches!(current_item.action, PassphraseAction::CancelOrDelete) {
|
||||
if self.is_empty {
|
||||
text = "CANCEL";
|
||||
icon = Some(theme::ICON_CANCEL);
|
||||
current_item.text = TR::inputs__cancel.into();
|
||||
current_item.icon = Some(theme::ICON_CANCEL);
|
||||
} else {
|
||||
text = "DELETE";
|
||||
icon = Some(theme::ICON_DELETE);
|
||||
current_item.text = TR::inputs__delete.into();
|
||||
current_item.icon = Some(theme::ICON_DELETE);
|
||||
}
|
||||
}
|
||||
|
||||
let mut menu_item = ChoiceItem::new(text, ButtonLayout::default_three_icons());
|
||||
let mut menu_item = current_item.text.map(|t| {
|
||||
ChoiceItem::new(
|
||||
t,
|
||||
ButtonLayout::arrow_armed_arrow(TR::buttons__select.into()),
|
||||
)
|
||||
});
|
||||
|
||||
// Action buttons have different middle button text
|
||||
if show_confirm {
|
||||
let confirm_btn = ButtonDetails::armed_text("CONFIRM".into());
|
||||
if current_item.show_confirm {
|
||||
let confirm_btn = ButtonDetails::armed_text(TR::buttons__confirm.into());
|
||||
menu_item.set_middle_btn(Some(confirm_btn));
|
||||
}
|
||||
|
||||
// Making middle button create LongPress events
|
||||
if without_release {
|
||||
if current_item.without_release {
|
||||
menu_item = menu_item.with_middle_action_without_release();
|
||||
}
|
||||
|
||||
if let Some(icon) = icon {
|
||||
if let Some(icon) = current_item.icon {
|
||||
menu_item = menu_item.with_icon(icon);
|
||||
}
|
||||
(menu_item, action)
|
||||
(menu_item, current_item.action)
|
||||
}
|
||||
|
||||
/// Character choices with a BACK to MENU choice at the end (visible from
|
||||
/// start) to return back
|
||||
fn get_character_item<T: StringType>(
|
||||
&self,
|
||||
choice_index: usize,
|
||||
) -> (ChoiceItem<T>, PassphraseAction) {
|
||||
fn get_character_item(&self, choice_index: usize) -> (ChoiceItem, PassphraseAction) {
|
||||
if is_menu_choice(&self.current_category, choice_index) {
|
||||
(
|
||||
ChoiceItem::new("BACK", ButtonLayout::arrow_armed_arrow("RETURN".into()))
|
||||
.with_icon(theme::ICON_ARROW_BACK_UP),
|
||||
TR::inputs__back.map_translated(|t| {
|
||||
ChoiceItem::new(
|
||||
t,
|
||||
ButtonLayout::arrow_armed_arrow(TR::inputs__return.into()),
|
||||
)
|
||||
.with_icon(theme::ICON_ARROW_BACK_UP)
|
||||
}),
|
||||
PassphraseAction::Menu,
|
||||
)
|
||||
} else {
|
||||
let ch = get_char(&self.current_category, choice_index);
|
||||
(
|
||||
ChoiceItem::new(char_to_string(ch), ButtonLayout::default_three_icons()),
|
||||
ChoiceItem::new(
|
||||
char_to_string(ch),
|
||||
ButtonLayout::arrow_armed_arrow(TR::buttons__select.into()),
|
||||
),
|
||||
PassphraseAction::Character(ch),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: StringType + Clone> ChoiceFactory<T> for ChoiceFactoryPassphrase {
|
||||
impl ChoiceFactory for ChoiceFactoryPassphrase {
|
||||
type Action = PassphraseAction;
|
||||
type Item = ChoiceItem<T>;
|
||||
type Item = ChoiceItem;
|
||||
|
||||
fn count(&self) -> usize {
|
||||
let length = get_category_length(&self.current_category);
|
||||
@ -250,8 +267,8 @@ impl<T: StringType + Clone> ChoiceFactory<T> for ChoiceFactoryPassphrase {
|
||||
}
|
||||
|
||||
/// Component for entering a passphrase.
|
||||
pub struct PassphraseEntry<T: StringType + Clone> {
|
||||
choice_page: ChoicePage<ChoiceFactoryPassphrase, T, PassphraseAction>,
|
||||
pub struct PassphraseEntry {
|
||||
choice_page: ChoicePage<ChoiceFactoryPassphrase, PassphraseAction>,
|
||||
passphrase_dots: Child<ChangingTextLine<String<MAX_PASSPHRASE_LENGTH>>>,
|
||||
show_plain_passphrase: bool,
|
||||
show_last_digit: bool,
|
||||
@ -259,10 +276,7 @@ pub struct PassphraseEntry<T: StringType + Clone> {
|
||||
current_category: ChoiceCategory,
|
||||
}
|
||||
|
||||
impl<T> PassphraseEntry<T>
|
||||
where
|
||||
T: StringType + Clone,
|
||||
{
|
||||
impl PassphraseEntry {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
choice_page: ChoicePage::new(ChoiceFactoryPassphrase::new(ChoiceCategory::Menu, true))
|
||||
@ -353,10 +367,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Component for PassphraseEntry<T>
|
||||
where
|
||||
T: StringType + Clone,
|
||||
{
|
||||
impl Component for PassphraseEntry {
|
||||
type Msg = CancelConfirmMsg;
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
@ -442,21 +453,18 @@ where
|
||||
// DEBUG-ONLY SECTION BELOW
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl<T> crate::trace::Trace for PassphraseEntry<T>
|
||||
where
|
||||
T: StringType + Clone,
|
||||
{
|
||||
impl crate::trace::Trace for PassphraseEntry {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.component("PassphraseKeyboard");
|
||||
t.string("passphrase", self.textbox.content());
|
||||
t.string("passphrase", self.textbox.content().into());
|
||||
t.string(
|
||||
"current_category",
|
||||
match self.current_category {
|
||||
ChoiceCategory::Menu => "MENU",
|
||||
ChoiceCategory::LowercaseLetter => MENU[LOWERCASE_INDEX].0,
|
||||
ChoiceCategory::UppercaseLetter => MENU[UPPERCASE_INDEX].0,
|
||||
ChoiceCategory::Digit => MENU[DIGITS_INDEX].0,
|
||||
ChoiceCategory::SpecialSymbol => MENU[SPECIAL_INDEX].0,
|
||||
ChoiceCategory::Menu => "MENU".into(),
|
||||
ChoiceCategory::LowercaseLetter => MENU[LOWERCASE_INDEX].text,
|
||||
ChoiceCategory::UppercaseLetter => MENU[UPPERCASE_INDEX].text,
|
||||
ChoiceCategory::Digit => MENU[DIGITS_INDEX].text,
|
||||
ChoiceCategory::SpecialSymbol => MENU[SPECIAL_INDEX].text,
|
||||
},
|
||||
);
|
||||
t.child("choice_page", &self.choice_page);
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::{
|
||||
strutil::StringType,
|
||||
strutil::{StringType, TString},
|
||||
translations::TR,
|
||||
trezorhal::random,
|
||||
ui::{
|
||||
component::{text::common::TextBox, Child, Component, ComponentExt, Event, EventCtx},
|
||||
@ -22,27 +23,65 @@ enum PinAction {
|
||||
Digit(char),
|
||||
}
|
||||
|
||||
struct PinChoice {
|
||||
text: TString<'static>,
|
||||
action: PinAction,
|
||||
icon: Option<Icon>,
|
||||
without_release: bool,
|
||||
}
|
||||
|
||||
impl PinChoice {
|
||||
pub const fn new(
|
||||
text: TString<'static>,
|
||||
action: PinAction,
|
||||
icon: Option<Icon>,
|
||||
without_release: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
text,
|
||||
action,
|
||||
icon,
|
||||
without_release,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_PIN_LENGTH: usize = 50;
|
||||
const EMPTY_PIN_STR: &str = "_";
|
||||
|
||||
const CHOICE_LENGTH: usize = 13;
|
||||
const NUMBER_START_INDEX: usize = 3;
|
||||
/// Text, action, icon, without_release
|
||||
const CHOICES: [(&str, PinAction, Option<Icon>, bool); CHOICE_LENGTH] = [
|
||||
|
||||
const CHOICES: [PinChoice; CHOICE_LENGTH] = [
|
||||
// DELETE should be triggerable without release (after long-press)
|
||||
("DELETE", PinAction::Delete, Some(theme::ICON_DELETE), true),
|
||||
("SHOW", PinAction::Show, Some(theme::ICON_EYE), false),
|
||||
("ENTER", PinAction::Enter, Some(theme::ICON_TICK), false),
|
||||
("0", PinAction::Digit('0'), None, false),
|
||||
("1", PinAction::Digit('1'), None, false),
|
||||
("2", PinAction::Digit('2'), None, false),
|
||||
("3", PinAction::Digit('3'), None, false),
|
||||
("4", PinAction::Digit('4'), None, false),
|
||||
("5", PinAction::Digit('5'), None, false),
|
||||
("6", PinAction::Digit('6'), None, false),
|
||||
("7", PinAction::Digit('7'), None, false),
|
||||
("8", PinAction::Digit('8'), None, false),
|
||||
("9", PinAction::Digit('9'), None, false),
|
||||
PinChoice::new(
|
||||
TR::inputs__delete.as_tstring(),
|
||||
PinAction::Delete,
|
||||
Some(theme::ICON_DELETE),
|
||||
true, // without_release
|
||||
),
|
||||
PinChoice::new(
|
||||
TR::inputs__show.as_tstring(),
|
||||
PinAction::Show,
|
||||
Some(theme::ICON_EYE),
|
||||
false,
|
||||
),
|
||||
PinChoice::new(
|
||||
TR::inputs__enter.as_tstring(),
|
||||
PinAction::Enter,
|
||||
Some(theme::ICON_TICK),
|
||||
false,
|
||||
),
|
||||
PinChoice::new(TString::from_str("0"), PinAction::Digit('0'), None, false),
|
||||
PinChoice::new(TString::from_str("1"), PinAction::Digit('1'), None, false),
|
||||
PinChoice::new(TString::from_str("2"), PinAction::Digit('2'), None, false),
|
||||
PinChoice::new(TString::from_str("3"), PinAction::Digit('3'), None, false),
|
||||
PinChoice::new(TString::from_str("4"), PinAction::Digit('4'), None, false),
|
||||
PinChoice::new(TString::from_str("5"), PinAction::Digit('5'), None, false),
|
||||
PinChoice::new(TString::from_str("6"), PinAction::Digit('6'), None, false),
|
||||
PinChoice::new(TString::from_str("7"), PinAction::Digit('7'), None, false),
|
||||
PinChoice::new(TString::from_str("8"), PinAction::Digit('8'), None, false),
|
||||
PinChoice::new(TString::from_str("9"), PinAction::Digit('9'), None, false),
|
||||
];
|
||||
|
||||
fn get_random_digit_position() -> usize {
|
||||
@ -51,32 +90,37 @@ fn get_random_digit_position() -> usize {
|
||||
|
||||
struct ChoiceFactoryPIN;
|
||||
|
||||
impl<T: StringType + Clone> ChoiceFactory<T> for ChoiceFactoryPIN {
|
||||
impl ChoiceFactory for ChoiceFactoryPIN {
|
||||
type Action = PinAction;
|
||||
type Item = ChoiceItem<T>;
|
||||
type Item = ChoiceItem;
|
||||
|
||||
fn get(&self, choice_index: usize) -> (Self::Item, Self::Action) {
|
||||
let (choice_str, action, icon, without_release) = CHOICES[choice_index];
|
||||
let choice = &CHOICES[choice_index];
|
||||
|
||||
let mut choice_item = ChoiceItem::new(choice_str, ButtonLayout::default_three_icons());
|
||||
let mut choice_item = choice.text.map(|t| {
|
||||
ChoiceItem::new(
|
||||
t,
|
||||
ButtonLayout::arrow_armed_arrow(TR::buttons__select.into()),
|
||||
)
|
||||
});
|
||||
|
||||
// Action buttons have different middle button text
|
||||
if !matches!(action, PinAction::Digit(_)) {
|
||||
let confirm_btn = ButtonDetails::armed_text("CONFIRM".into());
|
||||
if !matches!(choice.action, PinAction::Digit(_)) {
|
||||
let confirm_btn = ButtonDetails::armed_text(TR::buttons__confirm.into());
|
||||
choice_item.set_middle_btn(Some(confirm_btn));
|
||||
}
|
||||
|
||||
// Making middle button create LongPress events
|
||||
if without_release {
|
||||
if choice.without_release {
|
||||
choice_item = choice_item.with_middle_action_without_release();
|
||||
}
|
||||
|
||||
// Adding icons for appropriate items
|
||||
if let Some(icon) = icon {
|
||||
if let Some(icon) = choice.icon {
|
||||
choice_item = choice_item.with_icon(icon);
|
||||
}
|
||||
|
||||
(choice_item, action)
|
||||
(choice_item, choice.action)
|
||||
}
|
||||
|
||||
fn count(&self) -> usize {
|
||||
@ -86,7 +130,7 @@ impl<T: StringType + Clone> ChoiceFactory<T> for ChoiceFactoryPIN {
|
||||
|
||||
/// Component for entering a PIN.
|
||||
pub struct PinEntry<T: StringType + Clone> {
|
||||
choice_page: ChoicePage<ChoiceFactoryPIN, T, PinAction>,
|
||||
choice_page: ChoicePage<ChoiceFactoryPIN, PinAction>,
|
||||
header_line: Child<ChangingTextLine<String<MAX_PIN_LENGTH>>>,
|
||||
pin_line: Child<ChangingTextLine<String<MAX_PIN_LENGTH>>>,
|
||||
prompt: T,
|
||||
@ -111,7 +155,7 @@ where
|
||||
let (showing_real_prompt, header_line_content, pin_line_content) = if show_subprompt {
|
||||
(
|
||||
false,
|
||||
String::from("WRONG PIN"),
|
||||
TR::pin__title_wrong_pin.map_translated(|t| String::from(t)),
|
||||
String::from(subprompt.as_ref()),
|
||||
)
|
||||
} else {
|
||||
@ -296,8 +340,8 @@ where
|
||||
{
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.component("PinKeyboard");
|
||||
t.string("subprompt", self.subprompt.as_ref());
|
||||
t.string("pin", self.textbox.content());
|
||||
t.string("subprompt", self.subprompt.as_ref().into());
|
||||
t.string("pin", self.textbox.content().into());
|
||||
t.child("choice_page", &self.choice_page);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::{
|
||||
strutil::StringType,
|
||||
strutil::TString,
|
||||
translations::TR,
|
||||
ui::{
|
||||
component::{Component, Event, EventCtx},
|
||||
geometry::Rect,
|
||||
@ -13,24 +14,24 @@ use heapless::Vec;
|
||||
// as would be via `const N: usize` generics.
|
||||
const MAX_LENGTH: usize = 5;
|
||||
|
||||
struct ChoiceFactorySimple<T: StringType> {
|
||||
choices: Vec<T, MAX_LENGTH>,
|
||||
struct ChoiceFactorySimple {
|
||||
choices: Vec<TString<'static>, MAX_LENGTH>,
|
||||
carousel: bool,
|
||||
}
|
||||
|
||||
impl<T: StringType> ChoiceFactorySimple<T> {
|
||||
fn new(choices: Vec<T, MAX_LENGTH>, carousel: bool) -> Self {
|
||||
impl ChoiceFactorySimple {
|
||||
fn new(choices: Vec<TString<'static>, MAX_LENGTH>, carousel: bool) -> Self {
|
||||
Self { choices, carousel }
|
||||
}
|
||||
|
||||
fn get_string(&self, choice_index: usize) -> &str {
|
||||
self.choices[choice_index].as_ref()
|
||||
fn get_string(&self, choice_index: usize) -> TString<'static> {
|
||||
self.choices[choice_index]
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: StringType + Clone> ChoiceFactory<T> for ChoiceFactorySimple<T> {
|
||||
impl ChoiceFactory for ChoiceFactorySimple {
|
||||
type Action = usize;
|
||||
type Item = ChoiceItem<T>;
|
||||
type Item = ChoiceItem;
|
||||
|
||||
fn count(&self) -> usize {
|
||||
self.choices.len()
|
||||
@ -38,7 +39,12 @@ impl<T: StringType + Clone> ChoiceFactory<T> for ChoiceFactorySimple<T> {
|
||||
|
||||
fn get(&self, choice_index: usize) -> (Self::Item, Self::Action) {
|
||||
let text = &self.choices[choice_index];
|
||||
let mut choice_item = ChoiceItem::new(text, ButtonLayout::default_three_icons());
|
||||
let mut choice_item = text.map(|t| {
|
||||
ChoiceItem::new(
|
||||
t,
|
||||
ButtonLayout::arrow_armed_arrow(TR::buttons__select.into()),
|
||||
)
|
||||
});
|
||||
|
||||
// Disabling prev/next buttons for the first/last choice when not in carousel.
|
||||
// (could be done to the same button if there is only one)
|
||||
@ -57,19 +63,13 @@ impl<T: StringType + Clone> ChoiceFactory<T> for ChoiceFactorySimple<T> {
|
||||
|
||||
/// Simple wrapper around `ChoicePage` that allows for
|
||||
/// inputting a list of values and receiving the chosen one.
|
||||
pub struct SimpleChoice<T>
|
||||
where
|
||||
T: StringType + Clone,
|
||||
{
|
||||
choice_page: ChoicePage<ChoiceFactorySimple<T>, T, usize>,
|
||||
pub struct SimpleChoice {
|
||||
choice_page: ChoicePage<ChoiceFactorySimple, usize>,
|
||||
pub return_index: bool,
|
||||
}
|
||||
|
||||
impl<T> SimpleChoice<T>
|
||||
where
|
||||
T: StringType + Clone,
|
||||
{
|
||||
pub fn new(str_choices: Vec<T, MAX_LENGTH>, carousel: bool) -> Self {
|
||||
impl SimpleChoice {
|
||||
pub fn new(str_choices: Vec<TString<'static>, MAX_LENGTH>, carousel: bool) -> Self {
|
||||
let choices = ChoiceFactorySimple::new(str_choices, carousel);
|
||||
Self {
|
||||
choice_page: ChoicePage::new(choices).with_carousel(carousel),
|
||||
@ -96,15 +96,12 @@ where
|
||||
}
|
||||
|
||||
/// Translating the resulting index into actual string choice.
|
||||
pub fn result_by_index(&self, index: usize) -> &str {
|
||||
pub fn result_by_index(&self, index: usize) -> TString<'static> {
|
||||
self.choice_page.choice_factory().get_string(index)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Component for SimpleChoice<T>
|
||||
where
|
||||
T: StringType + Clone,
|
||||
{
|
||||
impl Component for SimpleChoice {
|
||||
type Msg = usize;
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
@ -123,10 +120,7 @@ where
|
||||
// DEBUG-ONLY SECTION BELOW
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl<T> crate::trace::Trace for SimpleChoice<T>
|
||||
where
|
||||
T: StringType + Clone,
|
||||
{
|
||||
impl crate::trace::Trace for SimpleChoice {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.component("SimpleChoice");
|
||||
t.child("choice_page", &self.choice_page);
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
strutil::StringType,
|
||||
translations::TR,
|
||||
trezorhal::{random, wordlist::Wordlist},
|
||||
ui::{
|
||||
component::{text::common::TextBox, Child, Component, ComponentExt, Event, EventCtx},
|
||||
@ -76,9 +76,9 @@ impl ChoiceFactoryWordlist {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: StringType + Clone> ChoiceFactory<T> for ChoiceFactoryWordlist {
|
||||
impl ChoiceFactory for ChoiceFactoryWordlist {
|
||||
type Action = WordlistAction;
|
||||
type Item = ChoiceItem<T>;
|
||||
type Item = ChoiceItem;
|
||||
|
||||
fn count(&self) -> usize {
|
||||
// Accounting for the DELETE option (+1)
|
||||
@ -94,9 +94,14 @@ impl<T: StringType + Clone> ChoiceFactory<T> for ChoiceFactoryWordlist {
|
||||
// (is a requirement for WORDS, doing it for LETTERS as well to unite it)
|
||||
if choice_index == DELETE_INDEX {
|
||||
return (
|
||||
ChoiceItem::new("DELETE", ButtonLayout::arrow_armed_arrow("CONFIRM".into()))
|
||||
TR::inputs__delete.map_translated(|t| {
|
||||
ChoiceItem::new(
|
||||
t,
|
||||
ButtonLayout::arrow_armed_arrow(TR::buttons__confirm.into()),
|
||||
)
|
||||
.with_icon(theme::ICON_DELETE)
|
||||
.with_middle_action_without_release(),
|
||||
.with_middle_action_without_release()
|
||||
}),
|
||||
WordlistAction::Delete,
|
||||
);
|
||||
}
|
||||
@ -105,7 +110,10 @@ impl<T: StringType + Clone> ChoiceFactory<T> for ChoiceFactoryWordlist {
|
||||
let index = self.word_random_order[choice_index - 1];
|
||||
let word = self.wordlist.get(index).unwrap_or_default();
|
||||
(
|
||||
ChoiceItem::new(word, ButtonLayout::default_three_icons()),
|
||||
ChoiceItem::new(
|
||||
word,
|
||||
ButtonLayout::arrow_armed_arrow(TR::buttons__select.into()),
|
||||
),
|
||||
WordlistAction::Word(word),
|
||||
)
|
||||
} else {
|
||||
@ -115,7 +123,10 @@ impl<T: StringType + Clone> ChoiceFactory<T> for ChoiceFactoryWordlist {
|
||||
.nth(choice_index - 1)
|
||||
.unwrap_or_default();
|
||||
(
|
||||
ChoiceItem::new(char_to_string(letter), ButtonLayout::default_three_icons()),
|
||||
ChoiceItem::new(
|
||||
char_to_string(letter),
|
||||
ButtonLayout::arrow_armed_arrow(TR::buttons__select.into()),
|
||||
),
|
||||
WordlistAction::Letter(letter),
|
||||
)
|
||||
}
|
||||
@ -123,21 +134,18 @@ impl<T: StringType + Clone> ChoiceFactory<T> for ChoiceFactoryWordlist {
|
||||
}
|
||||
|
||||
/// Component for entering a mnemonic from a wordlist - BIP39 or SLIP39.
|
||||
pub struct WordlistEntry<T: StringType + Clone> {
|
||||
choice_page: ChoicePage<ChoiceFactoryWordlist, T, WordlistAction>,
|
||||
pub struct WordlistEntry {
|
||||
choice_page: ChoicePage<ChoiceFactoryWordlist, WordlistAction>,
|
||||
chosen_letters: Child<ChangingTextLine<String<{ MAX_WORD_LENGTH + 1 }>>>,
|
||||
textbox: TextBox<MAX_WORD_LENGTH>,
|
||||
offer_words: bool,
|
||||
wordlist_type: WordlistType,
|
||||
}
|
||||
|
||||
impl<T> WordlistEntry<T>
|
||||
where
|
||||
T: StringType + Clone,
|
||||
{
|
||||
impl WordlistEntry {
|
||||
pub fn new(wordlist_type: WordlistType) -> Self {
|
||||
let choices = ChoiceFactoryWordlist::new(wordlist_type, "");
|
||||
let choices_count = <ChoiceFactoryWordlist as ChoiceFactory<T>>::count(&choices);
|
||||
let choices_count = <ChoiceFactoryWordlist as ChoiceFactory>::count(&choices);
|
||||
Self {
|
||||
// Starting at random letter position
|
||||
choice_page: ChoicePage::new(choices)
|
||||
@ -167,7 +175,7 @@ where
|
||||
if self.offer_words {
|
||||
INITIAL_PAGE_COUNTER
|
||||
} else {
|
||||
let choices_count = <ChoiceFactoryWordlist as ChoiceFactory<T>>::count(new_choices);
|
||||
let choices_count = <ChoiceFactoryWordlist as ChoiceFactory>::count(new_choices);
|
||||
// There should be always DELETE and at least one letter
|
||||
assert!(choices_count > 1);
|
||||
if choices_count == 2 {
|
||||
@ -179,8 +187,7 @@ where
|
||||
loop {
|
||||
let random_position = get_random_position(choices_count);
|
||||
let current_action =
|
||||
<ChoiceFactoryWordlist as ChoiceFactory<T>>::get(new_choices, random_position)
|
||||
.1;
|
||||
<ChoiceFactoryWordlist as ChoiceFactory>::get(new_choices, random_position).1;
|
||||
if let WordlistAction::Letter(current_letter) = current_action {
|
||||
if let Some(last_letter) = self.get_last_textbox_letter() {
|
||||
if current_letter == last_letter {
|
||||
@ -217,10 +224,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Component for WordlistEntry<T>
|
||||
where
|
||||
T: StringType + Clone,
|
||||
{
|
||||
impl Component for WordlistEntry {
|
||||
type Msg = &'static str;
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
@ -264,13 +268,10 @@ where
|
||||
// DEBUG-ONLY SECTION BELOW
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl<T> crate::trace::Trace for WordlistEntry<T>
|
||||
where
|
||||
T: StringType + Clone,
|
||||
{
|
||||
impl crate::trace::Trace for WordlistEntry {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.component("MnemonicKeyboard"); // unified with TT
|
||||
t.string("textbox", self.textbox.content());
|
||||
t.string("textbox", self.textbox.content().into());
|
||||
|
||||
if self.offer_words {
|
||||
t.bool("word_choices", true);
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
strutil::StringType,
|
||||
strutil::{StringType, TString},
|
||||
time::{Duration, Instant},
|
||||
ui::{
|
||||
animation::Animation,
|
||||
@ -28,25 +28,19 @@ enum State {
|
||||
Grown,
|
||||
}
|
||||
|
||||
pub struct Loader<T>
|
||||
where
|
||||
T: StringType,
|
||||
{
|
||||
pub struct Loader {
|
||||
area: Rect,
|
||||
state: State,
|
||||
growing_duration: Duration,
|
||||
shrinking_duration: Duration,
|
||||
text_overlay: display::TextOverlay<T>,
|
||||
text_overlay: display::TextOverlay,
|
||||
styles: LoaderStyleSheet,
|
||||
}
|
||||
|
||||
impl<T> Loader<T>
|
||||
where
|
||||
T: StringType,
|
||||
{
|
||||
impl Loader {
|
||||
pub const SIZE: Offset = Offset::new(120, 120);
|
||||
|
||||
pub fn new(text_overlay: display::TextOverlay<T>, styles: LoaderStyleSheet) -> Self {
|
||||
pub fn new(text_overlay: display::TextOverlay, styles: LoaderStyleSheet) -> Self {
|
||||
Self {
|
||||
area: Rect::zero(),
|
||||
state: State::Initial,
|
||||
@ -57,8 +51,8 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub fn text(text: T, styles: LoaderStyleSheet) -> Self {
|
||||
let text_overlay = display::TextOverlay::new(text, styles.normal.font);
|
||||
pub fn text<T: Into<TString<'static>>>(text: T, styles: LoaderStyleSheet) -> Self {
|
||||
let text_overlay = display::TextOverlay::new(text.into(), styles.normal.font);
|
||||
|
||||
Self::new(text_overlay, styles)
|
||||
}
|
||||
@ -77,18 +71,18 @@ where
|
||||
self.growing_duration
|
||||
}
|
||||
|
||||
pub fn get_text(&self) -> &T {
|
||||
pub fn get_text(&self) -> TString<'static> {
|
||||
self.text_overlay.get_text()
|
||||
}
|
||||
|
||||
/// Change the text of the loader.
|
||||
pub fn set_text(&mut self, text: T) {
|
||||
self.text_overlay.set_text(text);
|
||||
pub fn set_text<T: Into<TString<'static>>>(&mut self, text: T) {
|
||||
self.text_overlay.set_text(text.into());
|
||||
}
|
||||
|
||||
/// Return width of given text according to current style.
|
||||
pub fn get_text_width(&self, text: &T) -> i16 {
|
||||
self.styles.normal.font.text_width(text.as_ref())
|
||||
pub fn get_text_width(&self, text: &TString<'static>) -> i16 {
|
||||
text.map(|t| self.styles.normal.font.text_width(t))
|
||||
}
|
||||
|
||||
pub fn start_growing(&mut self, ctx: &mut EventCtx, now: Instant) {
|
||||
@ -172,10 +166,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Component for Loader<T>
|
||||
where
|
||||
T: StringType,
|
||||
{
|
||||
impl Component for Loader {
|
||||
type Msg = LoaderMsg;
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
@ -346,13 +337,10 @@ where
|
||||
// DEBUG-ONLY SECTION BELOW
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl<T> crate::trace::Trace for Loader<T>
|
||||
where
|
||||
T: StringType,
|
||||
{
|
||||
impl crate::trace::Trace for Loader {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.component("Loader");
|
||||
t.string("text", self.get_text().as_ref());
|
||||
t.string("text", self.get_text());
|
||||
t.int("duration", self.get_duration().to_millis() as i64);
|
||||
}
|
||||
}
|
||||
|
@ -26,32 +26,39 @@ pub use loader::{Loader, LoaderMsg, LoaderStyle, LoaderStyleSheet, ProgressLoade
|
||||
pub use result::ResultScreen;
|
||||
pub use welcome_screen::WelcomeScreen;
|
||||
|
||||
#[cfg(feature = "translations")]
|
||||
mod address_details;
|
||||
mod changing_text;
|
||||
#[cfg(feature = "translations")]
|
||||
mod coinjoin_progress;
|
||||
mod flow;
|
||||
mod flow_pages;
|
||||
mod frame;
|
||||
#[cfg(feature = "micropython")]
|
||||
mod homescreen;
|
||||
#[cfg(feature = "translations")]
|
||||
mod page;
|
||||
mod progress;
|
||||
mod result_anim;
|
||||
mod result_popup;
|
||||
mod scrollbar;
|
||||
#[cfg(feature = "translations")]
|
||||
mod share_words;
|
||||
mod show_more;
|
||||
mod title;
|
||||
|
||||
#[cfg(feature = "translations")]
|
||||
pub use address_details::AddressDetails;
|
||||
|
||||
pub use changing_text::ChangingTextLine;
|
||||
#[cfg(feature = "translations")]
|
||||
pub use coinjoin_progress::CoinJoinProgress;
|
||||
pub use flow::Flow;
|
||||
pub use flow_pages::{FlowPages, Page};
|
||||
pub use frame::{Frame, ScrollableContent, ScrollableFrame};
|
||||
#[cfg(feature = "micropython")]
|
||||
pub use homescreen::{check_homescreen_format, ConfirmHomescreen, Homescreen, Lockscreen};
|
||||
#[cfg(feature = "translations")]
|
||||
pub use input_methods::{
|
||||
number_input::NumberInput,
|
||||
passphrase::PassphraseEntry,
|
||||
@ -59,10 +66,12 @@ pub use input_methods::{
|
||||
simple_choice::SimpleChoice,
|
||||
wordlist::{WordlistEntry, WordlistType},
|
||||
};
|
||||
#[cfg(feature = "translations")]
|
||||
pub use page::ButtonPage;
|
||||
pub use progress::Progress;
|
||||
pub use result_anim::{ResultAnim, ResultAnimMsg};
|
||||
pub use result_popup::{ResultPopup, ResultPopupMsg};
|
||||
pub use scrollbar::ScrollBar;
|
||||
#[cfg(feature = "translations")]
|
||||
pub use share_words::ShareWords;
|
||||
pub use show_more::{CancelInfoConfirmMsg, ShowMore};
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
strutil::StringType,
|
||||
translations::TR,
|
||||
ui::{
|
||||
component::{Child, Component, ComponentExt, Event, EventCtx, Pad, PageMsg, Paginate},
|
||||
display::Color,
|
||||
@ -12,30 +12,28 @@ use super::{
|
||||
ButtonDetails, ButtonLayout, ButtonPos,
|
||||
};
|
||||
|
||||
pub struct ButtonPage<T, U>
|
||||
pub struct ButtonPage<T>
|
||||
where
|
||||
T: Component + Paginate,
|
||||
U: StringType,
|
||||
{
|
||||
page_count: usize,
|
||||
active_page: usize,
|
||||
content: Child<T>,
|
||||
pad: Pad,
|
||||
/// Left button of the first screen
|
||||
cancel_btn_details: Option<ButtonDetails<U>>,
|
||||
cancel_btn_details: Option<ButtonDetails>,
|
||||
/// Right button of the last screen
|
||||
confirm_btn_details: Option<ButtonDetails<U>>,
|
||||
confirm_btn_details: Option<ButtonDetails>,
|
||||
/// Left button of every screen
|
||||
back_btn_details: Option<ButtonDetails<U>>,
|
||||
back_btn_details: Option<ButtonDetails>,
|
||||
/// Right button of every screen apart the last one
|
||||
next_btn_details: Option<ButtonDetails<U>>,
|
||||
buttons: Child<ButtonController<U>>,
|
||||
next_btn_details: Option<ButtonDetails>,
|
||||
buttons: Child<ButtonController>,
|
||||
}
|
||||
|
||||
impl<T, U> ButtonPage<T, U>
|
||||
impl<T> ButtonPage<T>
|
||||
where
|
||||
T: Component + Paginate,
|
||||
U: StringType + Clone,
|
||||
{
|
||||
pub fn new(content: T, background: Color) -> Self {
|
||||
Self {
|
||||
@ -44,7 +42,7 @@ where
|
||||
content: Child::new(content),
|
||||
pad: Pad::with_background(background).with_clear(),
|
||||
cancel_btn_details: Some(ButtonDetails::cancel_icon()),
|
||||
confirm_btn_details: Some(ButtonDetails::text("CONFIRM".into())),
|
||||
confirm_btn_details: Some(ButtonDetails::text(TR::buttons__confirm.into())),
|
||||
back_btn_details: Some(ButtonDetails::up_arrow_icon()),
|
||||
next_btn_details: Some(ButtonDetails::down_arrow_icon_wide()),
|
||||
// Setting empty layout for now, we do not yet know the page count.
|
||||
@ -54,22 +52,22 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_cancel_btn(mut self, btn_details: Option<ButtonDetails<U>>) -> Self {
|
||||
pub fn with_cancel_btn(mut self, btn_details: Option<ButtonDetails>) -> Self {
|
||||
self.cancel_btn_details = btn_details;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_confirm_btn(mut self, btn_details: Option<ButtonDetails<U>>) -> Self {
|
||||
pub fn with_confirm_btn(mut self, btn_details: Option<ButtonDetails>) -> Self {
|
||||
self.confirm_btn_details = btn_details;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_back_btn(mut self, btn_details: Option<ButtonDetails<U>>) -> Self {
|
||||
pub fn with_back_btn(mut self, btn_details: Option<ButtonDetails>) -> Self {
|
||||
self.back_btn_details = btn_details;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_next_btn(mut self, btn_details: Option<ButtonDetails<U>>) -> Self {
|
||||
pub fn with_next_btn(mut self, btn_details: Option<ButtonDetails>) -> Self {
|
||||
self.next_btn_details = btn_details;
|
||||
self
|
||||
}
|
||||
@ -118,14 +116,14 @@ where
|
||||
});
|
||||
}
|
||||
|
||||
fn get_button_layout(&self, has_prev: bool, has_next: bool) -> ButtonLayout<U> {
|
||||
fn get_button_layout(&self, has_prev: bool, has_next: bool) -> ButtonLayout {
|
||||
let btn_left = self.get_left_button_details(!has_prev);
|
||||
let btn_right = self.get_right_button_details(has_next);
|
||||
ButtonLayout::new(btn_left, None, btn_right)
|
||||
}
|
||||
|
||||
/// Get the left button details, depending whether the page is first or not.
|
||||
fn get_left_button_details(&self, is_first: bool) -> Option<ButtonDetails<U>> {
|
||||
fn get_left_button_details(&self, is_first: bool) -> Option<ButtonDetails> {
|
||||
if is_first {
|
||||
self.cancel_btn_details.clone()
|
||||
} else {
|
||||
@ -135,7 +133,7 @@ where
|
||||
|
||||
/// Get the right button details, depending on whether there is a next
|
||||
/// page.
|
||||
fn get_right_button_details(&self, has_next_page: bool) -> Option<ButtonDetails<U>> {
|
||||
fn get_right_button_details(&self, has_next_page: bool) -> Option<ButtonDetails> {
|
||||
if has_next_page {
|
||||
self.next_btn_details.clone()
|
||||
} else {
|
||||
@ -144,10 +142,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> ScrollableContent for ButtonPage<T, U>
|
||||
impl<T> ScrollableContent for ButtonPage<T>
|
||||
where
|
||||
T: Component + Paginate,
|
||||
U: StringType,
|
||||
{
|
||||
fn page_count(&self) -> usize {
|
||||
self.page_count
|
||||
@ -157,10 +154,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Component for ButtonPage<T, U>
|
||||
impl<T> Component for ButtonPage<T>
|
||||
where
|
||||
T: Component + Paginate,
|
||||
U: StringType + Clone,
|
||||
{
|
||||
type Msg = PageMsg<T::Msg>;
|
||||
|
||||
@ -224,10 +220,9 @@ where
|
||||
// DEBUG-ONLY SECTION BELOW
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl<T, U> crate::trace::Trace for ButtonPage<T, U>
|
||||
impl<T> crate::trace::Trace for ButtonPage<T>
|
||||
where
|
||||
T: crate::trace::Trace + Paginate + Component,
|
||||
U: StringType + Clone,
|
||||
{
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.component("ButtonPage");
|
||||
|
@ -30,7 +30,7 @@ where
|
||||
result_anim: Child<ResultAnim>,
|
||||
headline: Option<Label<&'static str>>,
|
||||
text: Child<Paragraphs<Paragraph<T>>>,
|
||||
buttons: Option<Child<ButtonController<T>>>,
|
||||
buttons: Option<Child<ButtonController>>,
|
||||
autoclose: bool,
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::{
|
||||
strutil::StringType,
|
||||
translations::TR,
|
||||
ui::{
|
||||
component::{
|
||||
text::util::text_multiline, Child, Component, Event, EventCtx, Never, Paginate,
|
||||
@ -69,19 +70,24 @@ where
|
||||
}
|
||||
|
||||
fn get_final_text(&self) -> String<50> {
|
||||
build_string!(
|
||||
50,
|
||||
"I wrote down all ",
|
||||
inttostr!(self.share_words.len() as u8),
|
||||
" words in order."
|
||||
)
|
||||
TR::share_words__wrote_down_all.map_translated(|wrote_down_all| {
|
||||
TR::share_words__words_in_order.map_translated(|in_order| {
|
||||
build_string!(
|
||||
50,
|
||||
wrote_down_all,
|
||||
inttostr!(self.share_words.len() as u8),
|
||||
in_order
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Display the final page with user confirmation.
|
||||
fn paint_final_page(&mut self) {
|
||||
let final_text = self.get_final_text();
|
||||
text_multiline(
|
||||
self.area.split_top(INFO_TOP_OFFSET).1,
|
||||
&self.get_final_text(),
|
||||
final_text.as_str().into(),
|
||||
Font::NORMAL,
|
||||
theme::FG,
|
||||
theme::BG,
|
||||
@ -103,7 +109,7 @@ where
|
||||
let baseline = self.area.top_left() + Offset::y(y_offset);
|
||||
let ordinal = build_string!(5, inttostr!(index as u8 + 1), ".");
|
||||
display_left(baseline + Offset::x(NUMBER_X_OFFSET), &ordinal, NUMBER_FONT);
|
||||
display_left(baseline + Offset::x(WORD_X_OFFSET), &word, WORD_FONT);
|
||||
display_left(baseline + Offset::x(WORD_X_OFFSET), word, WORD_FONT);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -183,6 +189,6 @@ where
|
||||
}
|
||||
content
|
||||
};
|
||||
t.string("screen_content", &content);
|
||||
t.string("screen_content", content.as_str().into());
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
strutil::StringType,
|
||||
strutil::TString,
|
||||
ui::{
|
||||
component::{Child, Component, Event, EventCtx},
|
||||
geometry::{Insets, Rect},
|
||||
@ -14,20 +14,20 @@ pub enum CancelInfoConfirmMsg {
|
||||
Confirmed,
|
||||
}
|
||||
|
||||
pub struct ShowMore<T, U>
|
||||
where
|
||||
U: StringType,
|
||||
{
|
||||
pub struct ShowMore<T> {
|
||||
content: Child<T>,
|
||||
buttons: Child<ButtonController<U>>,
|
||||
buttons: Child<ButtonController>,
|
||||
}
|
||||
|
||||
impl<T, U> ShowMore<T, U>
|
||||
impl<T> ShowMore<T>
|
||||
where
|
||||
T: Component,
|
||||
U: StringType + Clone,
|
||||
{
|
||||
pub fn new(content: T, cancel_button: Option<U>, button: U) -> Self {
|
||||
pub fn new(
|
||||
content: T,
|
||||
cancel_button: Option<TString<'static>>,
|
||||
button: TString<'static>,
|
||||
) -> Self {
|
||||
let btn_layout = if let Some(cancel_text) = cancel_button {
|
||||
ButtonLayout::text_armed_info(cancel_text, button)
|
||||
} else {
|
||||
@ -40,10 +40,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Component for ShowMore<T, U>
|
||||
impl<T> Component for ShowMore<T>
|
||||
where
|
||||
T: Component,
|
||||
U: StringType + Clone,
|
||||
{
|
||||
type Msg = CancelInfoConfirmMsg;
|
||||
|
||||
@ -83,10 +82,9 @@ where
|
||||
// DEBUG-ONLY SECTION BELOW
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl<T, U> crate::trace::Trace for ShowMore<T, U>
|
||||
impl<T> crate::trace::Trace for ShowMore<T>
|
||||
where
|
||||
T: crate::trace::Trace + Component,
|
||||
U: StringType + Clone,
|
||||
{
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.component("ShowMore");
|
||||
|
@ -127,6 +127,6 @@ where
|
||||
{
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.component("Title");
|
||||
t.string("text", self.title.as_ref());
|
||||
t.string("text", self.title.as_ref().into());
|
||||
}
|
||||
}
|
||||
|
@ -58,6 +58,6 @@ impl Component for WelcomeScreen {
|
||||
impl crate::trace::Trace for WelcomeScreen {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.component("WelcomeScreen");
|
||||
t.string("model_name", "Trezor Safe 3");
|
||||
t.string("model_name", "Trezor Safe 3".into());
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -2,7 +2,9 @@ use heapless::Vec;
|
||||
|
||||
use crate::{
|
||||
error::Error,
|
||||
micropython::buffer::StrBuffer,
|
||||
strutil::StringType,
|
||||
translations::TR,
|
||||
ui::{
|
||||
component::{
|
||||
text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs, VecExt},
|
||||
@ -18,7 +20,7 @@ const MAX_XPUBS: usize = 16;
|
||||
|
||||
pub struct AddressDetails<T> {
|
||||
qr_code: Frame<Qr, T>,
|
||||
details: Frame<Paragraphs<ParagraphVecShort<T>>, T>,
|
||||
details: Frame<Paragraphs<ParagraphVecShort<StrBuffer>>, T>,
|
||||
xpub_view: Frame<Paragraphs<Paragraph<T>>, T>,
|
||||
xpubs: Vec<(T, T), MAX_XPUBS>,
|
||||
xpub_page_count: Vec<u8, MAX_XPUBS>,
|
||||
@ -34,21 +36,24 @@ where
|
||||
qr_address: T,
|
||||
case_sensitive: bool,
|
||||
details_title: T,
|
||||
account: Option<T>,
|
||||
path: Option<T>,
|
||||
account: Option<StrBuffer>,
|
||||
path: Option<StrBuffer>,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
T: From<&'static str>,
|
||||
{
|
||||
let mut para = ParagraphVecShort::new();
|
||||
if let Some(a) = account {
|
||||
para.add(Paragraph::new(&theme::TEXT_NORMAL, "Account:".into()));
|
||||
para.add(Paragraph::new(
|
||||
&theme::TEXT_NORMAL,
|
||||
TR::words__account_colon.try_into()?,
|
||||
));
|
||||
para.add(Paragraph::new(&theme::TEXT_MONO, a));
|
||||
}
|
||||
if let Some(p) = path {
|
||||
para.add(Paragraph::new(
|
||||
&theme::TEXT_NORMAL,
|
||||
"Derivation path:".into(),
|
||||
TR::address_details__derivation_path.try_into()?,
|
||||
));
|
||||
para.add(Paragraph::new(&theme::TEXT_MONO, p));
|
||||
}
|
||||
|
@ -50,8 +50,8 @@ pub struct Confirm<T> {
|
||||
title: ConfirmTitle<T>,
|
||||
message: Child<Label<T>>,
|
||||
alert: Option<Child<Label<T>>>,
|
||||
left_button: Child<Button<&'static str>>,
|
||||
right_button: Child<Button<&'static str>>,
|
||||
left_button: Child<Button<T>>,
|
||||
right_button: Child<Button<T>>,
|
||||
info: Option<ConfirmInfo<T>>,
|
||||
show_info: bool,
|
||||
}
|
||||
@ -62,8 +62,8 @@ where
|
||||
{
|
||||
pub fn new(
|
||||
bg_color: Color,
|
||||
left_button: Button<&'static str>,
|
||||
right_button: Button<&'static str>,
|
||||
left_button: Button<T>,
|
||||
right_button: Button<T>,
|
||||
title: ConfirmTitle<T>,
|
||||
message: Label<T>,
|
||||
) -> Self {
|
||||
|
@ -329,10 +329,10 @@ where
|
||||
t.component("Button");
|
||||
match &self.content {
|
||||
ButtonContent::Empty => {}
|
||||
ButtonContent::Text(text) => t.string("text", text.as_ref()),
|
||||
ButtonContent::Text(text) => t.string("text", text.as_ref().into()),
|
||||
ButtonContent::Icon(_) => t.bool("icon", true),
|
||||
ButtonContent::IconAndText(content) => {
|
||||
t.string("text", content.text);
|
||||
t.string("text", content.text.into());
|
||||
t.bool("icon", true);
|
||||
}
|
||||
ButtonContent::IconBlend(_, _, _) => t.bool("icon", true),
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user