mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-28 17:18:29 +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/build.yml
|
||||||
- ci/test.yml
|
- ci/test.yml
|
||||||
- ci/test-hw.yml
|
- ci/test-hw.yml
|
||||||
|
- ci/test-nonenglish.yml
|
||||||
- ci/posttest.yml
|
- ci/posttest.yml
|
||||||
- ci/deploy.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
|
vendorheader_check: ## check that vendor header is up to date
|
||||||
./core/embed/vendorheader/generate.sh --quiet --check
|
./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:
|
tags:
|
||||||
- deploy
|
- 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 to aws
|
||||||
|
|
||||||
sync emulators 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
|
bash
|
||||||
bloaty # for binsize
|
bloaty # for binsize
|
||||||
check
|
check
|
||||||
|
crowdin-cli # for translations
|
||||||
curl # for connect tests
|
curl # for connect tests
|
||||||
editorconfig-checker
|
editorconfig-checker
|
||||||
gcc-arm-embedded
|
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
|
* @start
|
||||||
* @next Success
|
* @next Success
|
||||||
* @next Failure
|
* @next Failure
|
||||||
@ -180,6 +180,47 @@ message ApplySettings {
|
|||||||
optional bool hide_passphrase_from_host = 11; // do not show passphrase coming from host
|
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
|
* Request: set flags of the device
|
||||||
* @start
|
* @start
|
||||||
@ -487,11 +528,17 @@ message CancelAuthorization {
|
|||||||
* @next Success
|
* @next Success
|
||||||
*/
|
*/
|
||||||
message RebootToBootloader {
|
message RebootToBootloader {
|
||||||
|
// Action to be performed after rebooting to bootloader
|
||||||
optional BootCommand boot_command = 1 [default=STOP_AND_WAIT];
|
optional BootCommand boot_command = 1 [default=STOP_AND_WAIT];
|
||||||
|
// Firmware header to be flashed after rebooting to bootloader
|
||||||
optional bytes firmware_header = 2;
|
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 {
|
enum BootCommand {
|
||||||
|
// Go to bootloader menu
|
||||||
STOP_AND_WAIT = 0;
|
STOP_AND_WAIT = 0;
|
||||||
|
// Connect to host and wait for firmware update
|
||||||
INSTALL_UPGRADE = 1;
|
INSTALL_UPGRADE = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,6 +124,9 @@ enum MessageType {
|
|||||||
MessageType_UnlockBootloader = 96 [(bitcoin_only) = true, (wire_in) = true];
|
MessageType_UnlockBootloader = 96 [(bitcoin_only) = true, (wire_in) = true];
|
||||||
MessageType_AuthenticateDevice = 97 [(bitcoin_only) = true, (wire_out) = true];
|
MessageType_AuthenticateDevice = 97 [(bitcoin_only) = true, (wire_out) = true];
|
||||||
MessageType_AuthenticityProof = 98 [(bitcoin_only) = true, (wire_in) = 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_SetU2FCounter = 63 [(wire_in) = true];
|
||||||
MessageType_GetNextU2FCounter = 80 [(wire_in) = true];
|
MessageType_GetNextU2FCounter = 80 [(wire_in) = true];
|
||||||
|
@ -116,6 +116,10 @@ def ascii_filter(s: str) -> str:
|
|||||||
return re.sub("[^ -\x7e]", "_", s)
|
return re.sub("[^ -\x7e]", "_", s)
|
||||||
|
|
||||||
|
|
||||||
|
def utf8_str_filter(s: str) -> str:
|
||||||
|
return '"' + repr(s)[1:-1] + '"'
|
||||||
|
|
||||||
|
|
||||||
def make_support_filter(
|
def make_support_filter(
|
||||||
support_info: SupportInfo,
|
support_info: SupportInfo,
|
||||||
) -> Callable[[str, Coins], Iterator[Coin]]:
|
) -> Callable[[str, Coins], Iterator[Coin]]:
|
||||||
@ -126,6 +130,7 @@ def make_support_filter(
|
|||||||
|
|
||||||
|
|
||||||
MAKO_FILTERS = {
|
MAKO_FILTERS = {
|
||||||
|
"utf8_str": utf8_str_filter,
|
||||||
"c_str": c_str_filter,
|
"c_str": c_str_filter,
|
||||||
"ascii": ascii_filter,
|
"ascii": ascii_filter,
|
||||||
"black_repr": black_repr_filter,
|
"black_repr": black_repr_filter,
|
||||||
|
@ -34,7 +34,8 @@ TREZOR_MODEL ?= T
|
|||||||
TREZOR_MEMPERF ?= 0
|
TREZOR_MEMPERF ?= 0
|
||||||
ADDRESS_SANITIZER ?= 0
|
ADDRESS_SANITIZER ?= 0
|
||||||
CMAKELISTS ?= 0
|
CMAKELISTS ?= 0
|
||||||
PYTEST_TIMEOUT ?= 400
|
PYTEST_TIMEOUT ?= 500
|
||||||
|
TEST_LANG ?= "en"
|
||||||
|
|
||||||
# OpenOCD interface default. Alternative: ftdi/olimex-arm-usb-tiny-h
|
# OpenOCD interface default. Alternative: ftdi/olimex-arm-usb-tiny-h
|
||||||
OPENOCD_INTERFACE ?= stlink
|
OPENOCD_INTERFACE ?= stlink
|
||||||
@ -101,11 +102,12 @@ test_rust: ## run rs unit tests
|
|||||||
-- --test-threads=1 --nocapture
|
-- --test-threads=1 --nocapture
|
||||||
|
|
||||||
test_emu: ## run selected device tests from python-trezor
|
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
|
test_emu_multicore: ## run device tests using multiple cores
|
||||||
$(PYTEST) -n $(MULTICORE) $(TESTPATH)/device_tests $(TESTOPTS) --timeout $(PYTEST_TIMEOUT) \
|
$(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
|
test_emu_monero: ## run selected monero device tests from monero-agent
|
||||||
cd tests ; $(EMU_TEST) ./run_tests_device_emu_monero.sh $(TESTOPTS)
|
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)
|
$(EMU_TEST) --slip0014 $(PYTEST) --maxfail=5 --sim tests/standard/ --vendor trezor $(TESTOPTS)
|
||||||
|
|
||||||
test_emu_click: ## run click tests
|
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
|
test_emu_click_ui: ## run click tests with UI testing
|
||||||
$(EMU_TEST) $(PYTEST) $(TESTPATH)/click_tests $(TESTOPTS) \
|
$(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
|
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
|
test_emu_persistence_ui: ## run persistence tests with UI testing
|
||||||
$(PYTEST) $(TESTPATH)/persistence_tests $(TESTOPTS) \
|
$(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
|
test_emu_ui: ## run ui integration tests
|
||||||
$(EMU_TEST) $(PYTEST) $(TESTPATH)/device_tests $(TESTOPTS) \
|
$(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
|
test_emu_ui_multicore: ## run ui integration tests using multiple cores
|
||||||
$(PYTEST) -n $(MULTICORE) $(TESTPATH)/device_tests $(TESTOPTS) --timeout $(PYTEST_TIMEOUT) \
|
$(PYTEST) -n $(MULTICORE) $(TESTPATH)/device_tests $(TESTOPTS) --timeout $(PYTEST_TIMEOUT) \
|
||||||
--ui=test --ui-check-missing --record-text-layout --do-master-diff \
|
--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
|
test_emu_ui_record: ## record and hash screens for ui integration tests
|
||||||
$(EMU_TEST) $(PYTEST) $(TESTPATH)/device_tests $(TESTOPTS) \
|
$(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
|
test_emu_ui_record_multicore: ## quickly record all screens
|
||||||
make test_emu_ui_multicore || echo "All errors are recorded in fixtures.json"
|
make test_emu_ui_multicore || echo "All errors are recorded in fixtures.json"
|
||||||
|
@ -204,6 +204,7 @@ SOURCE_MOD += [
|
|||||||
|
|
||||||
CPPDEFINES_MOD += [
|
CPPDEFINES_MOD += [
|
||||||
'TREZOR_UI2',
|
'TREZOR_UI2',
|
||||||
|
'TRANSLATIONS',
|
||||||
]
|
]
|
||||||
|
|
||||||
if TREZOR_MODEL not in ('1', ):
|
if TREZOR_MODEL not in ('1', ):
|
||||||
@ -717,6 +718,7 @@ def cargo_build():
|
|||||||
if BITCOIN_ONLY == '1':
|
if BITCOIN_ONLY == '1':
|
||||||
features.append('bitcoin_only')
|
features.append('bitcoin_only')
|
||||||
features.append('ui')
|
features.append('ui')
|
||||||
|
features.append('translations')
|
||||||
if PYOPT == '0':
|
if PYOPT == '0':
|
||||||
features.append('ui_debug')
|
features.append('ui_debug')
|
||||||
|
|
||||||
|
@ -219,6 +219,7 @@ elif TREZOR_MODEL in ('R', ):
|
|||||||
|
|
||||||
CPPDEFINES_MOD += [
|
CPPDEFINES_MOD += [
|
||||||
'TREZOR_UI2',
|
'TREZOR_UI2',
|
||||||
|
'TRANSLATIONS',
|
||||||
]
|
]
|
||||||
if TREZOR_MODEL not in ('1', ):
|
if TREZOR_MODEL not in ('1', ):
|
||||||
CPPDEFINES_MOD += [
|
CPPDEFINES_MOD += [
|
||||||
@ -372,6 +373,7 @@ SOURCE_MICROPYTHON = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
SOURCE_UNIX = [
|
SOURCE_UNIX = [
|
||||||
|
'embed/trezorhal/unix/translations.c',
|
||||||
'embed/trezorhal/unix/common.c',
|
'embed/trezorhal/unix/common.c',
|
||||||
'embed/trezorhal/unix/display-unix.c',
|
'embed/trezorhal/unix/display-unix.c',
|
||||||
'embed/trezorhal/unix/flash.c',
|
'embed/trezorhal/unix/flash.c',
|
||||||
@ -795,6 +797,7 @@ def cargo_build():
|
|||||||
if BITCOIN_ONLY == '1':
|
if BITCOIN_ONLY == '1':
|
||||||
features.append('bitcoin_only')
|
features.append('bitcoin_only')
|
||||||
features.append('ui')
|
features.append('ui')
|
||||||
|
features.append('translations')
|
||||||
if PYOPT == '0':
|
if PYOPT == '0':
|
||||||
features.append('debug')
|
features.append('debug')
|
||||||
if DMA2D:
|
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_obj, 0, 2,
|
||||||
mod_trezorutils_reboot_to_bootloader);
|
mod_trezorutils_reboot_to_bootloader);
|
||||||
|
|
||||||
/// def check_firmware_header(
|
/// VersionTuple = Tuple[int, int, int, int]
|
||||||
/// header : bytes
|
///
|
||||||
/// ) -> dict:
|
/// class FirmwareHeaderInfo(NamedTuple):
|
||||||
/// """
|
/// version: VersionTuple
|
||||||
/// Checks firmware image and vendor header and returns
|
/// vendor: str
|
||||||
/// { "version": (major, minor, patch),
|
/// fingerprint: bytes
|
||||||
/// "vendor": string,
|
/// hash: bytes
|
||||||
/// "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) {
|
STATIC mp_obj_t mod_trezorutils_check_firmware_header(mp_obj_t header) {
|
||||||
mp_buffer_info_t header_buf = {0};
|
mp_buffer_info_t header_buf = {0};
|
||||||
mp_get_buffer_raise(header, &header_buf, MP_BUFFER_READ);
|
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;
|
firmware_header_info_t info;
|
||||||
|
|
||||||
if (sectrue == check_firmware_header(header_buf.buf, header_buf.len, &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_t version[4] = {
|
||||||
mp_obj_new_int(info.ver_minor),
|
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_patch), mp_obj_new_int(info.ver_build)};
|
||||||
|
|
||||||
mp_obj_t result = mp_obj_new_dict(4);
|
static const qstr fields[4] = {MP_QSTR_version, MP_QSTR_vendor,
|
||||||
mp_obj_dict_store(result, MP_ROM_QSTR(MP_QSTR_version),
|
MP_QSTR_fingerprint, MP_QSTR_hash};
|
||||||
mp_obj_new_tuple(MP_ARRAY_SIZE(version), version));
|
const mp_obj_t values[4] = {
|
||||||
mp_obj_dict_store(
|
mp_obj_new_tuple(MP_ARRAY_SIZE(version), version),
|
||||||
result, MP_ROM_QSTR(MP_QSTR_vendor),
|
mp_obj_new_str_copy(&mp_type_str, info.vstr, info.vstr_len),
|
||||||
mp_obj_new_str_copy(&mp_type_str, info.vstr, info.vstr_len));
|
mp_obj_new_bytes(info.fingerprint, sizeof(info.fingerprint)),
|
||||||
mp_obj_dict_store(
|
mp_obj_new_bytes(info.hash, sizeof(info.hash))};
|
||||||
result, MP_ROM_QSTR(MP_QSTR_fingerprint),
|
return mp_obj_new_attrtuple(fields, MP_ARRAY_SIZE(fields), values);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mp_raise_ValueError("Invalid value.");
|
mp_raise_ValueError("Invalid value.");
|
||||||
@ -370,14 +366,16 @@ STATIC mp_obj_str_t mod_trezorutils_full_name_obj = {
|
|||||||
sizeof(MODEL_FULL_NAME) - 1,
|
sizeof(MODEL_FULL_NAME) - 1,
|
||||||
(const byte *)MODEL_FULL_NAME};
|
(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
|
/// SCM_REVISION: bytes
|
||||||
/// """Git commit hash of the firmware."""
|
/// """Git commit hash of the firmware."""
|
||||||
/// VERSION_MAJOR: int
|
/// VERSION: VersionTuple
|
||||||
/// """Major version."""
|
/// """Firmware version as a tuple (major, minor, patch, build)."""
|
||||||
/// VERSION_MINOR: int
|
|
||||||
/// """Minor version."""
|
|
||||||
/// VERSION_PATCH: int
|
|
||||||
/// """Patch version."""
|
|
||||||
/// USE_SD_CARD: bool
|
/// USE_SD_CARD: bool
|
||||||
/// """Whether the hardware supports SD card."""
|
/// """Whether the hardware supports SD card."""
|
||||||
/// USE_BACKLIGHT: bool
|
/// USE_BACKLIGHT: bool
|
||||||
@ -419,9 +417,7 @@ STATIC const mp_rom_map_elem_t mp_module_trezorutils_globals_table[] = {
|
|||||||
// various built-in constants
|
// various built-in constants
|
||||||
{MP_ROM_QSTR(MP_QSTR_SCM_REVISION),
|
{MP_ROM_QSTR(MP_QSTR_SCM_REVISION),
|
||||||
MP_ROM_PTR(&mod_trezorutils_revision_obj)},
|
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), MP_ROM_PTR(&mod_trezorutils_version_obj)},
|
||||||
{MP_ROM_QSTR(MP_QSTR_VERSION_MINOR), MP_ROM_INT(VERSION_MINOR)},
|
|
||||||
{MP_ROM_QSTR(MP_QSTR_VERSION_PATCH), MP_ROM_INT(VERSION_PATCH)},
|
|
||||||
#ifdef USE_SD_CARD
|
#ifdef USE_SD_CARD
|
||||||
{MP_ROM_QSTR(MP_QSTR_USE_SD_CARD), mp_const_true},
|
{MP_ROM_QSTR(MP_QSTR_USE_SD_CARD), mp_const_true},
|
||||||
#else
|
#else
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "librust.h"
|
#include "librust.h"
|
||||||
|
#include "librust_fonts.h"
|
||||||
#include "py/runtime.h"
|
#include "py/runtime.h"
|
||||||
|
|
||||||
#if MICROPY_PY_TREZORUI2
|
#if MICROPY_PY_TREZORUI2
|
||||||
@ -27,3 +28,7 @@ MP_REGISTER_MODULE(MP_QSTR_trezorui2, mp_module_trezorui2);
|
|||||||
#if MICROPY_PY_TREZORPROTO
|
#if MICROPY_PY_TREZORPROTO
|
||||||
MP_REGISTER_MODULE(MP_QSTR_trezorproto, mp_module_trezorproto);
|
MP_REGISTER_MODULE(MP_QSTR_trezorproto, mp_module_trezorproto);
|
||||||
#endif
|
#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_TREZORUI (1)
|
||||||
#define MICROPY_PY_TREZORUTILS (1)
|
#define MICROPY_PY_TREZORUTILS (1)
|
||||||
#define MICROPY_PY_TREZORPROTO (1)
|
#define MICROPY_PY_TREZORPROTO (1)
|
||||||
|
#define MICROPY_PY_TREZORTRANSLATE (1)
|
||||||
#define MICROPY_PY_TREZORUI2 (1)
|
#define MICROPY_PY_TREZORUI2 (1)
|
||||||
|
|
||||||
#ifdef SYSTEM_VIEW
|
#ifdef SYSTEM_VIEW
|
||||||
|
@ -99,9 +99,9 @@ void display_text_render_buffer(const char *text, int textlen, int font,
|
|||||||
int baseline = font_baseline(font);
|
int baseline = font_baseline(font);
|
||||||
|
|
||||||
// render glyphs
|
// render glyphs
|
||||||
for (int c_idx = 0; c_idx < textlen; c_idx++) {
|
font_glyph_iter_t iter = font_glyph_iter_init(font, (uint8_t *)text, textlen);
|
||||||
const uint8_t *g = font_get_glyph(font, (uint8_t)text[c_idx]);
|
const uint8_t *g = NULL;
|
||||||
if (!g) continue;
|
while (font_next_glyph(&iter, &g)) {
|
||||||
const uint8_t w = g[0]; // width
|
const uint8_t w = g[0]; // width
|
||||||
const uint8_t h = g[1]; // height
|
const uint8_t h = g[1]; // height
|
||||||
const uint8_t adv = g[2]; // advance
|
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);
|
set_color_table(colortable, fgcolor, bgcolor);
|
||||||
|
|
||||||
// render glyphs
|
// render glyphs
|
||||||
for (int c_idx = 0; c_idx < textlen; c_idx++) {
|
font_glyph_iter_t iter = font_glyph_iter_init(font, (uint8_t *)text, textlen);
|
||||||
const uint8_t *g = font_get_glyph(font, (uint8_t)text[c_idx]);
|
const uint8_t *g = NULL;
|
||||||
if (!g) continue;
|
while (font_next_glyph(&iter, &g)) {
|
||||||
const uint8_t w = g[0]; // width
|
const uint8_t w = g[0]; // width
|
||||||
const uint8_t h = g[1]; // height
|
const uint8_t h = g[1]; // height
|
||||||
const uint8_t adv = g[2]; // advance
|
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);
|
set_color_table(colortable, fgcolor, bgcolor);
|
||||||
|
|
||||||
// render glyphs
|
// render glyphs
|
||||||
for (int i = 0; i < textlen; i++) {
|
font_glyph_iter_t iter = font_glyph_iter_init(font, (uint8_t *)text, textlen);
|
||||||
const uint8_t *g = font_get_glyph(font, (uint8_t)text[i]);
|
const uint8_t *g = NULL;
|
||||||
if (!g) continue;
|
while (font_next_glyph(&iter, &g)) {
|
||||||
const uint8_t w = g[0]; // width
|
const uint8_t w = g[0]; // width
|
||||||
const uint8_t h = g[1]; // height
|
const uint8_t h = g[1]; // height
|
||||||
const uint8_t adv = g[2]; // advance
|
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_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 };
|
/* ~ */ 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] = {
|
const uint8_t * const Font_PixelOperator_Bold_8[126 + 1 - 32] = {
|
||||||
Font_PixelOperator_Bold_8_glyph_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_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 };
|
/* ~ */ 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] = {
|
const uint8_t * const Font_PixelOperator_Regular_8[126 + 1 - 32] = {
|
||||||
Font_PixelOperator_Regular_8_glyph_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_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 };
|
/* ~ */ 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] = {
|
const uint8_t * const Font_PixelOperatorMono_Regular_8[126 + 1 - 32] = {
|
||||||
Font_PixelOperatorMono_Regular_8_glyph_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_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 };
|
/* ~ */ 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] = {
|
const uint8_t * const Font_Roboto_Bold_20[126 + 1 - 32] = {
|
||||||
Font_Roboto_Bold_20_glyph_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_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 };
|
/* ~ */ 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] = {
|
const uint8_t * const Font_Roboto_Regular_20[126 + 1 - 32] = {
|
||||||
Font_Roboto_Regular_20_glyph_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_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 };
|
/* ~ */ 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] = {
|
const uint8_t * const Font_RobotoMono_Medium_20[126 + 1 - 32] = {
|
||||||
Font_RobotoMono_Medium_20_glyph_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_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 };
|
/* ~ */ 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] = {
|
const uint8_t * const Font_TTHoves_Bold_17[126 + 1 - 32] = {
|
||||||
Font_TTHoves_Bold_17_glyph_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_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 };
|
/* ~ */ 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] = {
|
const uint8_t * const Font_TTHoves_DemiBold_21[126 + 1 - 32] = {
|
||||||
Font_TTHoves_DemiBold_21_glyph_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_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 };
|
/* ~ */ 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] = {
|
const uint8_t * const Font_TTHoves_Regular_21[126 + 1 - 32] = {
|
||||||
Font_TTHoves_Regular_21_glyph_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_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 };
|
/* ~ */ 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] = {
|
const uint8_t * const Font_Unifont_Bold_16[126 + 1 - 32] = {
|
||||||
Font_Unifont_Bold_16_glyph_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_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
|
/* ~ */ 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] = {
|
const uint8_t * const Font_Unifont_Regular_16[126 + 1 - 32] = {
|
||||||
Font_Unifont_Regular_16_glyph_32,
|
Font_Unifont_Regular_16_glyph_32,
|
||||||
|
@ -18,39 +18,15 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "fonts.h"
|
#include "fonts.h"
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#ifdef TRANSLATIONS
|
||||||
|
#include "librust_fonts.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
static uint8_t convert_char(const uint8_t c) {
|
#define UNICODE_BADCHAR 0xFFFD
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int font_height(int font) {
|
int font_height(int font) {
|
||||||
switch (font) {
|
switch (font) {
|
||||||
@ -130,9 +106,117 @@ int font_baseline(int font) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint8_t *font_get_glyph(int font, uint8_t c) {
|
font_glyph_iter_t font_glyph_iter_init(const int font, const uint8_t *text,
|
||||||
c = convert_char(c);
|
const int len) {
|
||||||
if (!c) return 0;
|
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
|
// printable ASCII character
|
||||||
if (c >= ' ' && c < 0x7F) {
|
if (c >= ' ' && c < 0x7F) {
|
||||||
@ -161,29 +245,7 @@ const uint8_t *font_get_glyph(int font, uint8_t c) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// non-printable character
|
return font_nonprintable_glyph(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
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// compute the width of the text (in pixels)
|
// 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) {
|
if (textlen < 0) {
|
||||||
textlen = strlen(text);
|
textlen = strlen(text);
|
||||||
}
|
}
|
||||||
for (int i = 0; i < textlen; i++) {
|
font_glyph_iter_t iter = font_glyph_iter_init(font, (uint8_t *)text, textlen);
|
||||||
const uint8_t *g = font_get_glyph(font, (uint8_t)text[i]);
|
const uint8_t *g = NULL;
|
||||||
if (!g) continue;
|
while (font_next_glyph(&iter, &g)) {
|
||||||
const uint8_t adv = g[2]; // advance
|
const uint8_t adv = g[2]; // advance
|
||||||
width += adv;
|
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;
|
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
|
#ifndef _FONTS_H
|
||||||
#define _FONTS_H
|
#define _FONTS_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
#include "fonts/font_bitmap.h"
|
#include "fonts/font_bitmap.h"
|
||||||
#include TREZOR_BOARD
|
#include TREZOR_BOARD
|
||||||
|
|
||||||
@ -117,9 +119,18 @@
|
|||||||
int font_height(int font);
|
int font_height(int font);
|
||||||
int font_max_height(int font);
|
int font_max_height(int font);
|
||||||
int font_baseline(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_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
|
#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_major = ihdr->version & 0xFF;
|
||||||
info->ver_minor = (ihdr->version >> 8) & 0xFF;
|
info->ver_minor = (ihdr->version >> 8) & 0xFF;
|
||||||
info->ver_patch = (ihdr->version >> 16) & 0xFF;
|
info->ver_patch = (ihdr->version >> 16) & 0xFF;
|
||||||
|
info->ver_build = (ihdr->version >> 24) & 0xFF;
|
||||||
|
|
||||||
// calculate and copy the image fingerprint
|
// calculate and copy the image fingerprint
|
||||||
get_image_fingerprint(ihdr, info->fingerprint);
|
get_image_fingerprint(ihdr, info->fingerprint);
|
||||||
|
@ -88,6 +88,7 @@ typedef struct {
|
|||||||
uint8_t ver_major;
|
uint8_t ver_major;
|
||||||
uint8_t ver_minor;
|
uint8_t ver_minor;
|
||||||
uint8_t ver_patch;
|
uint8_t ver_patch;
|
||||||
|
uint8_t ver_build;
|
||||||
// firmware fingerprint
|
// firmware fingerprint
|
||||||
uint8_t fingerprint[BLAKE2S_DIGEST_LENGTH];
|
uint8_t fingerprint[BLAKE2S_DIGEST_LENGTH];
|
||||||
// hash of vendor and image header
|
// 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 STORAGE_AREAS[STORAGE_AREAS_COUNT];
|
||||||
extern const flash_area_t BOARDLOADER_AREA;
|
extern const flash_area_t BOARDLOADER_AREA;
|
||||||
extern const flash_area_t SECRET_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 BOOTLOADER_AREA;
|
||||||
extern const flash_area_t FIRMWARE_AREA;
|
extern const flash_area_t FIRMWARE_AREA;
|
||||||
extern const flash_area_t WIPE_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 = {
|
const flash_area_t BOOTLOADER_AREA = {
|
||||||
.num_subareas = 1,
|
.num_subareas = 1,
|
||||||
.subarea[0] =
|
.subarea[0] =
|
||||||
@ -62,7 +71,7 @@ const flash_area_t FIRMWARE_AREA = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const flash_area_t WIPE_AREA = {
|
const flash_area_t WIPE_AREA = {
|
||||||
.num_subareas = 4,
|
.num_subareas = 3,
|
||||||
.subarea[0] =
|
.subarea[0] =
|
||||||
{
|
{
|
||||||
.first_sector = 4,
|
.first_sector = 4,
|
||||||
@ -74,12 +83,6 @@ const flash_area_t WIPE_AREA = {
|
|||||||
.num_sectors = 6,
|
.num_sectors = 6,
|
||||||
},
|
},
|
||||||
.subarea[2] =
|
.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,
|
.first_sector = 16,
|
||||||
.num_sectors = 8,
|
.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 = {
|
const flash_area_t BOOTLOADER_AREA = {
|
||||||
.num_subareas = 1,
|
.num_subareas = 1,
|
||||||
.subarea[0] =
|
.subarea[0] =
|
||||||
|
@ -32,6 +32,7 @@ rgb_led = []
|
|||||||
backlight = []
|
backlight = []
|
||||||
usb = []
|
usb = []
|
||||||
optiga = []
|
optiga = []
|
||||||
|
translations = []
|
||||||
test = [
|
test = [
|
||||||
"button",
|
"button",
|
||||||
"cc",
|
"cc",
|
||||||
@ -43,7 +44,8 @@ test = [
|
|||||||
"dma2d",
|
"dma2d",
|
||||||
"touch",
|
"touch",
|
||||||
"backlight",
|
"backlight",
|
||||||
"optiga"
|
"optiga",
|
||||||
|
"translations",
|
||||||
]
|
]
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
|
@ -289,6 +289,10 @@ fn generate_trezorhal_bindings() {
|
|||||||
.allowlist_function("storage_delete")
|
.allowlist_function("storage_delete")
|
||||||
.allowlist_function("storage_set_counter")
|
.allowlist_function("storage_set_counter")
|
||||||
.allowlist_function("storage_next_counter")
|
.allowlist_function("storage_next_counter")
|
||||||
|
.allowlist_function("translations_read")
|
||||||
|
.allowlist_function("translations_write")
|
||||||
|
.allowlist_function("translations_erase")
|
||||||
|
.allowlist_function("translations_area_bytesize")
|
||||||
// display
|
// display
|
||||||
.allowlist_function("display_clear")
|
.allowlist_function("display_clear")
|
||||||
.allowlist_function("display_offset")
|
.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_trezorproto;
|
||||||
extern mp_obj_module_t mp_module_trezorui2;
|
extern mp_obj_module_t mp_module_trezorui2;
|
||||||
|
extern mp_obj_module_t mp_module_trezortranslate;
|
||||||
|
|
||||||
#ifdef TREZOR_EMULATOR
|
#ifdef TREZOR_EMULATOR
|
||||||
mp_obj_t ui_debug_layout_type();
|
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"
|
#pragma GCC diagnostic ignored "-Wunused-function"
|
||||||
|
|
||||||
static void _librust_qstrs(void) {
|
static void _librust_qstrs(void) {
|
||||||
|
MP_QSTR_;
|
||||||
MP_QSTR_CANCELLED;
|
MP_QSTR_CANCELLED;
|
||||||
MP_QSTR_CONFIRMED;
|
MP_QSTR_CONFIRMED;
|
||||||
MP_QSTR_INFO;
|
MP_QSTR_INFO;
|
||||||
@ -13,6 +14,8 @@ static void _librust_qstrs(void) {
|
|||||||
MP_QSTR_MESSAGE_WIRE_TYPE;
|
MP_QSTR_MESSAGE_WIRE_TYPE;
|
||||||
MP_QSTR_Msg;
|
MP_QSTR_Msg;
|
||||||
MP_QSTR_MsgDef;
|
MP_QSTR_MsgDef;
|
||||||
|
MP_QSTR_TR;
|
||||||
|
MP_QSTR_TranslationsHeader;
|
||||||
MP_QSTR___dict__;
|
MP_QSTR___dict__;
|
||||||
MP_QSTR___name__;
|
MP_QSTR___name__;
|
||||||
MP_QSTR_account;
|
MP_QSTR_account;
|
||||||
@ -20,7 +23,20 @@ static void _librust_qstrs(void) {
|
|||||||
MP_QSTR_accounts;
|
MP_QSTR_accounts;
|
||||||
MP_QSTR_action;
|
MP_QSTR_action;
|
||||||
MP_QSTR_active;
|
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;
|
||||||
|
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_label;
|
||||||
MP_QSTR_address_title;
|
MP_QSTR_address_title;
|
||||||
MP_QSTR_allow_cancel;
|
MP_QSTR_allow_cancel;
|
||||||
@ -31,16 +47,224 @@ static void _librust_qstrs(void) {
|
|||||||
MP_QSTR_amount_title;
|
MP_QSTR_amount_title;
|
||||||
MP_QSTR_amount_value;
|
MP_QSTR_amount_value;
|
||||||
MP_QSTR_app_name;
|
MP_QSTR_app_name;
|
||||||
|
MP_QSTR_area_bytesize;
|
||||||
MP_QSTR_attach_timer_fn;
|
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_bootscreen;
|
||||||
MP_QSTR_bounds;
|
MP_QSTR_bounds;
|
||||||
MP_QSTR_button;
|
MP_QSTR_button;
|
||||||
MP_QSTR_button_event;
|
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_arrow;
|
||||||
MP_QSTR_cancel_cross;
|
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_case_sensitive;
|
||||||
MP_QSTR_check_homescreen_format;
|
MP_QSTR_check_homescreen_format;
|
||||||
MP_QSTR_chunkify;
|
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_coinjoin_authorized;
|
||||||
MP_QSTR_confirm_action;
|
MP_QSTR_confirm_action;
|
||||||
MP_QSTR_confirm_address;
|
MP_QSTR_confirm_address;
|
||||||
@ -61,60 +285,507 @@ static void _librust_qstrs(void) {
|
|||||||
MP_QSTR_confirm_recovery;
|
MP_QSTR_confirm_recovery;
|
||||||
MP_QSTR_confirm_reset_device;
|
MP_QSTR_confirm_reset_device;
|
||||||
MP_QSTR_confirm_total;
|
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_value;
|
||||||
MP_QSTR_confirm_with_info;
|
MP_QSTR_confirm_with_info;
|
||||||
MP_QSTR_count;
|
MP_QSTR_count;
|
||||||
MP_QSTR_data;
|
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_decode;
|
||||||
|
MP_QSTR_deinit;
|
||||||
MP_QSTR_description;
|
MP_QSTR_description;
|
||||||
MP_QSTR_details_title;
|
MP_QSTR_details_title;
|
||||||
|
MP_QSTR_device_name__change_template;
|
||||||
|
MP_QSTR_device_name__title;
|
||||||
MP_QSTR_disable_animation;
|
MP_QSTR_disable_animation;
|
||||||
MP_QSTR_dry_run;
|
MP_QSTR_dry_run;
|
||||||
MP_QSTR_encode;
|
MP_QSTR_encode;
|
||||||
MP_QSTR_encoded_length;
|
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_extra;
|
||||||
MP_QSTR_fee_amount;
|
MP_QSTR_fee_amount;
|
||||||
MP_QSTR_fee_label;
|
MP_QSTR_fee_label;
|
||||||
MP_QSTR_fee_rate_amount;
|
MP_QSTR_fee_rate_amount;
|
||||||
MP_QSTR_fee_title;
|
MP_QSTR_fee_title;
|
||||||
MP_QSTR_fee_value;
|
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_fingerprint;
|
||||||
|
MP_QSTR_firmware_update__title;
|
||||||
|
MP_QSTR_firmware_update__title_fingerprint;
|
||||||
|
MP_QSTR_get_language;
|
||||||
MP_QSTR_hold;
|
MP_QSTR_hold;
|
||||||
MP_QSTR_hold_danger;
|
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_horizontal;
|
||||||
MP_QSTR_icon_name;
|
MP_QSTR_icon_name;
|
||||||
MP_QSTR_image;
|
MP_QSTR_image;
|
||||||
MP_QSTR_indeterminate;
|
MP_QSTR_indeterminate;
|
||||||
MP_QSTR_info_button;
|
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_is_type_of;
|
||||||
MP_QSTR_items;
|
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_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_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_count;
|
||||||
MP_QSTR_max_feerate;
|
MP_QSTR_max_feerate;
|
||||||
MP_QSTR_max_len;
|
MP_QSTR_max_len;
|
||||||
MP_QSTR_max_rounds;
|
MP_QSTR_max_rounds;
|
||||||
MP_QSTR_min_count;
|
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_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;
|
||||||
MP_QSTR_notification_level;
|
MP_QSTR_notification_level;
|
||||||
MP_QSTR_page_count;
|
MP_QSTR_page_count;
|
||||||
MP_QSTR_pages;
|
MP_QSTR_pages;
|
||||||
MP_QSTR_paint;
|
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_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_progress_event;
|
||||||
MP_QSTR_prompt;
|
MP_QSTR_prompt;
|
||||||
MP_QSTR_qr_title;
|
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_bip39;
|
||||||
MP_QSTR_request_complete_repaint;
|
MP_QSTR_request_complete_repaint;
|
||||||
MP_QSTR_request_number;
|
MP_QSTR_request_number;
|
||||||
MP_QSTR_request_passphrase;
|
MP_QSTR_request_passphrase;
|
||||||
MP_QSTR_request_pin;
|
MP_QSTR_request_pin;
|
||||||
MP_QSTR_request_slip39;
|
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_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;
|
||||||
MP_QSTR_select_word_count;
|
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;
|
||||||
|
MP_QSTR_share_words__words_in_order;
|
||||||
|
MP_QSTR_share_words__wrote_down_all;
|
||||||
MP_QSTR_show_address_details;
|
MP_QSTR_show_address_details;
|
||||||
MP_QSTR_show_checklist;
|
MP_QSTR_show_checklist;
|
||||||
MP_QSTR_show_error;
|
MP_QSTR_show_error;
|
||||||
@ -131,32 +802,211 @@ static void _librust_qstrs(void) {
|
|||||||
MP_QSTR_show_share_words;
|
MP_QSTR_show_share_words;
|
||||||
MP_QSTR_show_simple;
|
MP_QSTR_show_simple;
|
||||||
MP_QSTR_show_success;
|
MP_QSTR_show_success;
|
||||||
|
MP_QSTR_show_wait_text;
|
||||||
MP_QSTR_show_warning;
|
MP_QSTR_show_warning;
|
||||||
MP_QSTR_sign;
|
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_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_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_subprompt;
|
||||||
MP_QSTR_subtitle;
|
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_time_ms;
|
||||||
MP_QSTR_timer;
|
MP_QSTR_timer;
|
||||||
MP_QSTR_title;
|
MP_QSTR_title;
|
||||||
MP_QSTR_total_amount;
|
MP_QSTR_total_amount;
|
||||||
MP_QSTR_total_fee_new;
|
MP_QSTR_total_fee_new;
|
||||||
MP_QSTR_total_label;
|
MP_QSTR_total_label;
|
||||||
|
MP_QSTR_total_len;
|
||||||
MP_QSTR_touch_event;
|
MP_QSTR_touch_event;
|
||||||
MP_QSTR_trace;
|
MP_QSTR_trace;
|
||||||
MP_QSTR_trezorproto;
|
MP_QSTR_trezorproto;
|
||||||
MP_QSTR_trezorui2;
|
MP_QSTR_trezorui2;
|
||||||
MP_QSTR_tutorial;
|
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_name;
|
||||||
MP_QSTR_type_for_wire;
|
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_usb_event;
|
||||||
MP_QSTR_user_fee_change;
|
MP_QSTR_user_fee_change;
|
||||||
MP_QSTR_value;
|
MP_QSTR_value;
|
||||||
MP_QSTR_verb;
|
MP_QSTR_verb;
|
||||||
MP_QSTR_verb_cancel;
|
MP_QSTR_verb_cancel;
|
||||||
|
MP_QSTR_verify;
|
||||||
|
MP_QSTR_version;
|
||||||
MP_QSTR_warning;
|
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;
|
||||||
|
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_wrong_pin;
|
||||||
MP_QSTR_xpubs;
|
MP_QSTR_xpubs;
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,8 @@ mod storage;
|
|||||||
mod time;
|
mod time;
|
||||||
#[cfg(feature = "ui_debug")]
|
#[cfg(feature = "ui_debug")]
|
||||||
mod trace;
|
mod trace;
|
||||||
|
#[cfg(feature = "translations")]
|
||||||
|
pub mod translations;
|
||||||
|
|
||||||
#[cfg(feature = "ui")]
|
#[cfg(feature = "ui")]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
@ -24,7 +24,7 @@ use super::ffi;
|
|||||||
/// The `off` field represents offset from the `ptr` and allows us to do
|
/// The `off` field represents offset from the `ptr` and allows us to do
|
||||||
/// substring slices while keeping the head pointer as required by GC.
|
/// substring slices while keeping the head pointer as required by GC.
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct StrBuffer {
|
pub struct StrBuffer {
|
||||||
ptr: *const u8,
|
ptr: *const u8,
|
||||||
len: u16,
|
len: u16,
|
||||||
|
@ -137,8 +137,9 @@ macro_rules! obj_dict {
|
|||||||
macro_rules! obj_type {
|
macro_rules! obj_type {
|
||||||
(name: $name:expr,
|
(name: $name:expr,
|
||||||
$(locals: $locals:expr,)?
|
$(locals: $locals:expr,)?
|
||||||
$(attr_fn: $attr_fn:ident,)?
|
$(make_new_fn: $make_new_fn:path,)?
|
||||||
$(call_fn: $call_fn:ident,)?
|
$(attr_fn: $attr_fn:path,)?
|
||||||
|
$(call_fn: $call_fn:path,)?
|
||||||
) => {{
|
) => {{
|
||||||
#[allow(unused_unsafe)]
|
#[allow(unused_unsafe)]
|
||||||
unsafe {
|
unsafe {
|
||||||
@ -156,6 +157,11 @@ macro_rules! obj_type {
|
|||||||
let mut call: ffi::mp_call_fun_t = None;
|
let mut call: ffi::mp_call_fun_t = None;
|
||||||
$(call = Some($call_fn);)?
|
$(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
|
// 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
|
// `Map::fixed()`, usually through `obj_map!`), because only then will
|
||||||
// MicroPython treat `locals_dict` as immutable, and make the mutable cast safe.
|
// MicroPython treat `locals_dict` as immutable, and make the mutable cast safe.
|
||||||
@ -171,7 +177,7 @@ macro_rules! obj_type {
|
|||||||
flags: 0,
|
flags: 0,
|
||||||
name,
|
name,
|
||||||
print: None,
|
print: None,
|
||||||
make_new: None,
|
make_new,
|
||||||
call,
|
call,
|
||||||
unary_op: None,
|
unary_op: None,
|
||||||
binary_op: None,
|
binary_op: None,
|
||||||
|
@ -15,7 +15,7 @@ impl Qstr {
|
|||||||
// micropython/py/obj.h #define MP_OBJ_NEW_QSTR(qst)
|
// micropython/py/obj.h #define MP_OBJ_NEW_QSTR(qst)
|
||||||
// ((mp_obj_t)((((mp_uint_t)(qst)) << 3) | 2))
|
// ((mp_obj_t)((((mp_uint_t)(qst)) << 3) | 2))
|
||||||
let bits = (self.0 << 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 {
|
pub const fn from_obj_bits(bits: cty::uintptr_t) -> Self {
|
||||||
|
@ -17,6 +17,14 @@ impl Type {
|
|||||||
ObjBase { type_: self }
|
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")]
|
#[cfg(feature = "debug")]
|
||||||
pub fn name(&self) -> &'static str {
|
pub fn name(&self) -> &'static str {
|
||||||
use super::qstr::Qstr;
|
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 INITIALIZED: u16 = FLAG_PUBLIC | APP_DEVICE | 0x0013;
|
||||||
const SAFETY_CHECK_LEVEL: u16 = APP_DEVICE | 0x0014;
|
const SAFETY_CHECK_LEVEL: u16 = APP_DEVICE | 0x0014;
|
||||||
const EXPERIMENTAL_FEATURES: u16 = APP_DEVICE | 0x0015;
|
const EXPERIMENTAL_FEATURES: u16 = APP_DEVICE | 0x0015;
|
||||||
|
const HIDE_PASSPHRASE_FROM_HOST: u16 = APP_DEVICE | 0x0016;
|
||||||
|
|
||||||
pub fn get_avatar_len() -> StorageResult<usize> {
|
pub fn get_avatar_len() -> StorageResult<usize> {
|
||||||
get_length(HOMESCREEN)
|
get_length(HOMESCREEN)
|
||||||
|
@ -1,5 +1,14 @@
|
|||||||
use heapless::String;
|
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.
|
/// Trait for slicing off string prefix by a specified number of bytes.
|
||||||
/// See `StringType` for deeper explanation.
|
/// See `StringType` for deeper explanation.
|
||||||
pub trait SkipPrefix {
|
pub trait SkipPrefix {
|
||||||
@ -23,9 +32,15 @@ impl SkipPrefix for &str {
|
|||||||
/// - create a new string by skipping some number of bytes (SkipPrefix) - used
|
/// - create a new string by skipping some number of bytes (SkipPrefix) - used
|
||||||
/// when rendering continuations of long strings
|
/// when rendering continuations of long strings
|
||||||
/// - create a new string from a string literal (From<&'static str>)
|
/// - 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.
|
/// Unified-length String type, long enough for most simple use-cases.
|
||||||
pub type ShortString = String<50>;
|
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 {
|
pub trait Tracer {
|
||||||
fn child(&mut self, key: &str, value: &dyn Trace);
|
fn child(&mut self, key: &str, value: &dyn Trace);
|
||||||
fn int(&mut self, key: &str, i: i64);
|
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 bool(&mut self, key: &str, b: bool);
|
||||||
fn null(&mut self, key: &str);
|
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 in_list(&mut self, key: &str, block: &dyn Fn(&mut dyn ListTracer));
|
||||||
|
|
||||||
fn component(&mut self, name: &str) {
|
fn component(&mut self, name: &str) {
|
||||||
self.string("component", name);
|
self.string("component", name.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ListTracer {
|
pub trait ListTracer {
|
||||||
fn child(&mut self, value: &dyn Trace);
|
fn child(&mut self, value: &dyn Trace);
|
||||||
fn int(&mut self, i: i64);
|
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 bool(&mut self, b: bool);
|
||||||
|
|
||||||
fn in_child(&mut self, block: &dyn Fn(&mut dyn Tracer));
|
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);
|
self.write_int(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn string(&mut self, s: &str) {
|
fn string(&mut self, s: &TString<'_>) {
|
||||||
self.maybe_comma();
|
self.maybe_comma();
|
||||||
self.write_str_quoted(s);
|
s.map(|s| self.write_str_quoted(s));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bool(&mut self, b: bool) {
|
fn bool(&mut self, b: bool) {
|
||||||
@ -160,9 +160,9 @@ impl<F: FnMut(&str)> Tracer for JsonTracer<F> {
|
|||||||
self.write_int(i);
|
self.write_int(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn string(&mut self, key: &str, s: &str) {
|
fn string(&mut self, key: &str, s: TString<'_>) {
|
||||||
self.key(key);
|
self.key(key);
|
||||||
self.write_str_quoted(s);
|
s.map(|s| self.write_str_quoted(s));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bool(&mut self, key: &str, b: bool) {
|
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)
|
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) }
|
unsafe { ffi::font_get_glyph(font, ch) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,8 @@ pub mod random;
|
|||||||
pub mod rgb_led;
|
pub mod rgb_led;
|
||||||
pub mod slip39;
|
pub mod slip39;
|
||||||
pub mod storage;
|
pub mod storage;
|
||||||
|
#[cfg(feature = "translations")]
|
||||||
|
pub mod translations;
|
||||||
pub mod usb;
|
pub mod usb;
|
||||||
pub mod uzlib;
|
pub mod uzlib;
|
||||||
pub mod wordlist;
|
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) {
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
t.component("Label");
|
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) {
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
t.component("Marquee");
|
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 {
|
impl crate::trace::Trace for Qr {
|
||||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
t.component("Qr");
|
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> {
|
impl<const L: usize> crate::trace::Trace for TextBox<L> {
|
||||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
t.component("TextBox");
|
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<'_> {
|
impl LayoutSink for TraceSink<'_> {
|
||||||
fn text(&mut self, _cursor: Point, _layout: &TextLayout, text: &str) {
|
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) {
|
fn hyphen(&mut self, _cursor: Point, _layout: &TextLayout) {
|
||||||
self.0.string("-");
|
self.0.string(&"-".into());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ellipsis(&mut self, _cursor: Point, _layout: &TextLayout) {
|
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) {
|
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) {
|
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> {
|
impl<T: ParagraphSource> crate::trace::Trace for Paragraphs<T> {
|
||||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
t.string("component", "Paragraphs");
|
t.string("component", "Paragraphs".into());
|
||||||
t.in_list("paragraphs", &|par_list| {
|
t.in_list("paragraphs", &|par_list| {
|
||||||
Self::foreach_visible(
|
Self::foreach_visible(
|
||||||
&self.source,
|
&self.source,
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
use crate::ui::{
|
use crate::{
|
||||||
display::{Color, Font},
|
strutil::TString,
|
||||||
geometry::{Alignment, Rect},
|
ui::{
|
||||||
|
display::{Color, Font},
|
||||||
|
geometry::{Alignment, Rect},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
@ -17,7 +20,7 @@ use super::{
|
|||||||
/// If it does not fit, returns `None`.
|
/// If it does not fit, returns `None`.
|
||||||
pub fn text_multiline(
|
pub fn text_multiline(
|
||||||
area: Rect,
|
area: Rect,
|
||||||
text: &str,
|
text: TString<'_>,
|
||||||
font: Font,
|
font: Font,
|
||||||
fg_color: Color,
|
fg_color: Color,
|
||||||
bg_color: Color,
|
bg_color: Color,
|
||||||
@ -27,7 +30,7 @@ pub fn text_multiline(
|
|||||||
let text_layout = TextLayout::new(text_style)
|
let text_layout = TextLayout::new(text_style)
|
||||||
.with_bounds(area)
|
.with_bounds(area)
|
||||||
.with_align(alignment);
|
.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 {
|
match layout_fit {
|
||||||
LayoutFit::Fitting { height, .. } => Some(area.split_top(height).1),
|
LayoutFit::Fitting { height, .. } => Some(area.split_top(height).1),
|
||||||
LayoutFit::OutOfBounds { .. } => None,
|
LayoutFit::OutOfBounds { .. } => None,
|
||||||
@ -38,7 +41,7 @@ pub fn text_multiline(
|
|||||||
/// area.
|
/// area.
|
||||||
pub fn text_multiline_bottom(
|
pub fn text_multiline_bottom(
|
||||||
area: Rect,
|
area: Rect,
|
||||||
text: &str,
|
text: TString<'_>,
|
||||||
font: Font,
|
font: Font,
|
||||||
fg_color: Color,
|
fg_color: Color,
|
||||||
bg_color: Color,
|
bg_color: Color,
|
||||||
@ -50,16 +53,16 @@ pub fn text_multiline_bottom(
|
|||||||
.with_align(alignment);
|
.with_align(alignment);
|
||||||
// When text fits the area, displaying it in the bottom part.
|
// When text fits the area, displaying it in the bottom part.
|
||||||
// When not, render it "normally".
|
// When not, render it "normally".
|
||||||
match text_layout.fit_text(text) {
|
text.map(|t| match text_layout.fit_text(t) {
|
||||||
LayoutFit::Fitting { height, .. } => {
|
LayoutFit::Fitting { height, .. } => {
|
||||||
let (top, bottom) = area.split_bottom(height);
|
let (top, bottom) = area.split_bottom(height);
|
||||||
text_layout = text_layout.with_bounds(bottom);
|
text_layout = text_layout.with_bounds(bottom);
|
||||||
text_layout.render_text(text);
|
text_layout.render_text(t);
|
||||||
Some(top)
|
Some(top)
|
||||||
}
|
}
|
||||||
LayoutFit::OutOfBounds { .. } => {
|
LayoutFit::OutOfBounds { .. } => {
|
||||||
text_layout.render_text(text);
|
text_layout.render_text(t);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
@ -144,6 +144,16 @@ impl Font {
|
|||||||
display::text_width(text, self.into())
|
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.
|
/// Width of the text that is visible.
|
||||||
/// Not including the spaces before the first and after the last character.
|
/// Not including the spaces before the first and after the last character.
|
||||||
pub fn visible_text_width(self, text: &str) -> i16 {
|
pub fn visible_text_width(self, text: &str) -> i16 {
|
||||||
@ -152,14 +162,20 @@ impl Font {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let first_char = unwrap!(text.chars().next());
|
let first_char_bearing = if let Some(glyph) = self.get_first_glyph_from_text(text) {
|
||||||
let first_char_glyph = unwrap!(self.get_glyph(first_char as u8));
|
glyph.bearing_x
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
let last_char = unwrap!(text.chars().last());
|
let last_char_bearing = if let Some(glyph) = self.get_last_glyph_from_text(text) {
|
||||||
let last_char_glyph = unwrap!(self.get_glyph(last_char as u8));
|
glyph.right_side_bearing()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
// Strip leftmost and rightmost spaces/bearings/margins.
|
// 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.
|
/// Returning the x-bearing (offset) of the first character.
|
||||||
@ -169,9 +185,11 @@ impl Font {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let first_char = unwrap!(text.chars().next());
|
if let Some(glyph) = self.get_first_glyph_from_text(text) {
|
||||||
let first_char_glyph = unwrap!(self.get_glyph(first_char as u8));
|
glyph.bearing_x
|
||||||
first_char_glyph.bearing_x
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn char_width(self, ch: char) -> i16 {
|
pub fn char_width(self, ch: char) -> i16 {
|
||||||
@ -198,25 +216,21 @@ impl Font {
|
|||||||
constant::LINE_SPACE + self.text_height()
|
constant::LINE_SPACE + self.text_height()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_glyph(self, char_byte: u8) -> Option<Glyph> {
|
pub fn get_glyph(self, ch: char) -> Glyph {
|
||||||
let gl_data = display::get_char_glyph(char_byte, self.into());
|
let gl_data = display::get_char_glyph(ch as u16, self.into());
|
||||||
|
|
||||||
if gl_data.is_null() {
|
ensure!(!gl_data.is_null(), "Failed to load glyph");
|
||||||
return None;
|
|
||||||
}
|
|
||||||
// SAFETY: Glyph::load is valid for data returned by get_char_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) {
|
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 colortable = get_color_table(fg_color, bg_color);
|
||||||
let mut adv_total = 0;
|
let mut adv_total = 0;
|
||||||
for c in text.bytes() {
|
for c in text.chars() {
|
||||||
let g = self.get_glyph(c);
|
let gly = self.get_glyph(c);
|
||||||
if let Some(gly) = g {
|
let adv = gly.print(baseline + Offset::new(adv_total, 0), colortable);
|
||||||
let adv = gly.print(baseline + Offset::new(adv_total, 0), colortable);
|
adv_total += adv;
|
||||||
adv_total += adv;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ use crate::ui::geometry::Alignment2D;
|
|||||||
use crate::{time::Duration, trezorhal::time};
|
use crate::{time::Duration, trezorhal::time};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
strutil::TString,
|
||||||
trezorhal::{buffers, display, uzlib::UzlibContext},
|
trezorhal::{buffers, display, uzlib::UzlibContext},
|
||||||
ui::lerp::Lerp,
|
ui::lerp::Lerp,
|
||||||
};
|
};
|
||||||
@ -346,39 +347,39 @@ pub fn clear() {
|
|||||||
display::clear();
|
display::clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
#[derive(Clone)]
|
||||||
pub struct TextOverlay<T> {
|
pub struct TextOverlay {
|
||||||
area: Rect,
|
area: Rect,
|
||||||
text: T,
|
text: TString<'static>,
|
||||||
font: Font,
|
font: Font,
|
||||||
max_height: i16,
|
max_height: i16,
|
||||||
baseline: i16,
|
baseline: i16,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: AsRef<str>> TextOverlay<T> {
|
impl TextOverlay {
|
||||||
pub fn new(text: T, font: Font) -> Self {
|
pub fn new<T: Into<TString<'static>>>(text: T, font: Font) -> Self {
|
||||||
let area = Rect::zero();
|
let area = Rect::zero();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
area,
|
area,
|
||||||
text,
|
text: text.into(),
|
||||||
font,
|
font,
|
||||||
max_height: font.max_height(),
|
max_height: font.max_height(),
|
||||||
baseline: font.text_baseline(),
|
baseline: font.text_baseline(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_text(&mut self, text: T) {
|
pub fn set_text<T: Into<TString<'static>>>(&mut self, text: T) {
|
||||||
self.text = text;
|
self.text = text.into();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_text(&self) -> &T {
|
pub fn get_text(&self) -> TString<'static> {
|
||||||
&self.text
|
self.text
|
||||||
}
|
}
|
||||||
|
|
||||||
// baseline relative to the underlying render area
|
// baseline relative to the underlying render area
|
||||||
pub fn place(&mut self, baseline: Point) {
|
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_height = self.font.text_height();
|
||||||
|
|
||||||
let text_area_start = baseline + Offset::new(-(text_width / 2), -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);
|
let p_rel = Point::new(p.x - self.area.x0, p.y - self.area.y0);
|
||||||
|
|
||||||
for g in self
|
let color = self.text.map(|t| {
|
||||||
.text
|
for g in t.chars().map(|c| self.font.get_glyph(c)) {
|
||||||
.as_ref()
|
let top = self.max_height - self.baseline - g.bearing_y;
|
||||||
.bytes()
|
let char_area = Rect::new(
|
||||||
.filter_map(|c| self.font.get_glyph(c))
|
Point::new(tot_adv + g.bearing_x, top),
|
||||||
{
|
Point::new(tot_adv + g.bearing_x + g.width, top + g.height),
|
||||||
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) {
|
if !char_area.contains(p_rel) {
|
||||||
continue;
|
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();
|
color.unwrap_or(underlying)
|
||||||
let overlay_data = g.get_pixel_data(p_inner);
|
|
||||||
return Color::lerp(underlying, fg, overlay_data as f32 / 15_f32);
|
|
||||||
}
|
|
||||||
|
|
||||||
underlying
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -995,9 +994,9 @@ fn rect_rounded2_get_pixel(
|
|||||||
/// Optionally draws a text inside the rectangle and adjusts its color to match
|
/// 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
|
/// the fill. The coordinates of the text are specified in the TextOverlay
|
||||||
/// struct.
|
/// struct.
|
||||||
pub fn bar_with_text_and_fill<T: AsRef<str>>(
|
pub fn bar_with_text_and_fill(
|
||||||
area: Rect,
|
area: Rect,
|
||||||
overlay: Option<&TextOverlay<T>>,
|
overlay: Option<&TextOverlay>,
|
||||||
fg_color: Color,
|
fg_color: Color,
|
||||||
bg_color: Color,
|
bg_color: Color,
|
||||||
fill_from: i16,
|
fill_from: i16,
|
||||||
|
@ -32,7 +32,7 @@ impl ReturnToC for IntroMsg {
|
|||||||
pub struct Intro<'a> {
|
pub struct Intro<'a> {
|
||||||
bg: Pad,
|
bg: Pad,
|
||||||
title: Child<Label<&'a str>>,
|
title: Child<Label<&'a str>>,
|
||||||
buttons: Child<ButtonController<&'static str>>,
|
buttons: Child<ButtonController>,
|
||||||
text: Child<Label<&'a str>>,
|
text: Child<Label<&'a str>>,
|
||||||
warn: Option<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(),
|
bg: Pad::with_background(BLD_BG).with_clear(),
|
||||||
title: Child::new(Label::centered(title, TEXT_NORMAL).vertically_centered()),
|
title: Child::new(Label::centered(title, TEXT_NORMAL).vertically_centered()),
|
||||||
buttons: Child::new(ButtonController::new(ButtonLayout::text_none_text(
|
buttons: Child::new(ButtonController::new(ButtonLayout::text_none_text(
|
||||||
LEFT_BUTTON_TEXT,
|
LEFT_BUTTON_TEXT.into(),
|
||||||
RIGHT_BUTTON_TEXT,
|
RIGHT_BUTTON_TEXT.into(),
|
||||||
))),
|
))),
|
||||||
text: Child::new(Label::left_aligned(content, TEXT_NORMAL).vertically_centered()),
|
text: Child::new(Label::left_aligned(content, TEXT_NORMAL).vertically_centered()),
|
||||||
warn: (!fw_ok).then_some(Child::new(
|
warn: (!fw_ok).then_some(Child::new(
|
||||||
|
@ -13,7 +13,7 @@ use crate::{
|
|||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
super::{
|
super::{
|
||||||
component::{Choice, ChoiceFactory, ChoicePage},
|
component::{ButtonLayout, Choice, ChoiceFactory, ChoicePage},
|
||||||
theme::bootloader::{BLD_BG, BLD_FG, ICON_EXIT, ICON_REDO, ICON_TRASH},
|
theme::bootloader::{BLD_BG, BLD_FG, ICON_EXIT, ICON_REDO, ICON_TRASH},
|
||||||
},
|
},
|
||||||
ReturnToC,
|
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) {
|
fn paint_center(&self, _area: Rect, _inverse: bool) {
|
||||||
// Icon on top and two lines of text below
|
// Icon on top and two lines of text below
|
||||||
self.icon.draw(
|
self.icon.draw(
|
||||||
@ -70,6 +70,10 @@ impl Choice<&'static str> for MenuChoice {
|
|||||||
BLD_BG,
|
BLD_BG,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn btn_layout(&self) -> ButtonLayout {
|
||||||
|
ButtonLayout::arrow_armed_arrow("SELECT".into())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "ui_debug")]
|
#[cfg(feature = "ui_debug")]
|
||||||
@ -95,7 +99,7 @@ impl MenuChoiceFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChoiceFactory<&'static str> for MenuChoiceFactory {
|
impl ChoiceFactory for MenuChoiceFactory {
|
||||||
type Action = MenuMsg;
|
type Action = MenuMsg;
|
||||||
type Item = MenuChoice;
|
type Item = MenuChoice;
|
||||||
|
|
||||||
@ -125,7 +129,7 @@ impl ChoiceFactory<&'static str> for MenuChoiceFactory {
|
|||||||
|
|
||||||
pub struct Menu {
|
pub struct Menu {
|
||||||
pad: Pad,
|
pad: Pad,
|
||||||
choice_page: Child<ChoicePage<MenuChoiceFactory, &'static str, MenuMsg>>,
|
choice_page: Child<ChoicePage<MenuChoiceFactory, MenuMsg>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Menu {
|
impl Menu {
|
||||||
|
@ -2,7 +2,8 @@ use heapless::Vec;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::Error,
|
error::Error,
|
||||||
strutil::StringType,
|
micropython::buffer::StrBuffer,
|
||||||
|
translations::TR,
|
||||||
ui::{
|
ui::{
|
||||||
component::{
|
component::{
|
||||||
text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs, VecExt},
|
text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs, VecExt},
|
||||||
@ -19,40 +20,40 @@ use super::{
|
|||||||
const MAX_XPUBS: usize = 16;
|
const MAX_XPUBS: usize = 16;
|
||||||
const QR_BORDER: i16 = 3;
|
const QR_BORDER: i16 = 3;
|
||||||
|
|
||||||
pub struct AddressDetails<T>
|
pub struct AddressDetails {
|
||||||
where
|
|
||||||
T: StringType,
|
|
||||||
{
|
|
||||||
qr_code: Qr,
|
qr_code: Qr,
|
||||||
details_view: Paragraphs<ParagraphVecShort<T>>,
|
details_view: Paragraphs<ParagraphVecShort<StrBuffer>>,
|
||||||
xpub_view: Frame<Paragraphs<Paragraph<T>>, T>,
|
xpub_view: Frame<Paragraphs<Paragraph<StrBuffer>>, StrBuffer>,
|
||||||
xpubs: Vec<(T, T), MAX_XPUBS>,
|
xpubs: Vec<(StrBuffer, StrBuffer), MAX_XPUBS>,
|
||||||
current_page: usize,
|
current_page: usize,
|
||||||
current_subpage: usize,
|
current_subpage: usize,
|
||||||
area: Rect,
|
area: Rect,
|
||||||
pad: Pad,
|
pad: Pad,
|
||||||
buttons: Child<ButtonController<T>>,
|
buttons: Child<ButtonController>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> AddressDetails<T>
|
impl AddressDetails {
|
||||||
where
|
|
||||||
T: StringType + Clone,
|
|
||||||
{
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
qr_address: T,
|
qr_address: StrBuffer,
|
||||||
case_sensitive: bool,
|
case_sensitive: bool,
|
||||||
account: Option<T>,
|
account: Option<StrBuffer>,
|
||||||
path: Option<T>,
|
path: Option<StrBuffer>,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
let qr_code = Qr::new(qr_address, case_sensitive)?.with_border(QR_BORDER);
|
let qr_code = Qr::new(qr_address, case_sensitive)?.with_border(QR_BORDER);
|
||||||
let details_view = {
|
let details_view = {
|
||||||
let mut para = ParagraphVecShort::new();
|
let mut para = ParagraphVecShort::new();
|
||||||
if let Some(account) = account {
|
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));
|
para.add(Paragraph::new(&theme::TEXT_MONO, account));
|
||||||
}
|
}
|
||||||
if let Some(path) = path {
|
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));
|
para.add(Paragraph::new(&theme::TEXT_MONO, path));
|
||||||
}
|
}
|
||||||
Paragraphs::new(para)
|
Paragraphs::new(para)
|
||||||
@ -76,7 +77,7 @@ where
|
|||||||
Ok(result)
|
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
|
self.xpubs
|
||||||
.push((title, xpub))
|
.push((title, xpub))
|
||||||
.map_err(|_| Error::OutOfRange)
|
.map_err(|_| Error::OutOfRange)
|
||||||
@ -111,7 +112,7 @@ where
|
|||||||
/// last page. On xpub pages there is SHOW ALL middle button when it
|
/// 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
|
/// cannot fit one page. On xpub subpages there are wide arrows to
|
||||||
/// scroll.
|
/// 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, middle, right) = if self.is_in_subpage() {
|
||||||
let left = Some(ButtonDetails::up_arrow_icon_wide());
|
let left = Some(ButtonDetails::up_arrow_icon_wide());
|
||||||
let right = if self.is_last_subpage() {
|
let right = if self.is_last_subpage() {
|
||||||
@ -123,7 +124,7 @@ where
|
|||||||
} else {
|
} else {
|
||||||
let left = Some(ButtonDetails::left_arrow_icon());
|
let left = Some(ButtonDetails::left_arrow_icon());
|
||||||
let middle = if self.is_xpub_page() && self.subpages_in_current_page() > 1 {
|
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 {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
@ -151,9 +152,9 @@ where
|
|||||||
|
|
||||||
fn fill_xpub_page(&mut self, ctx: &mut EventCtx) {
|
fn fill_xpub_page(&mut self, ctx: &mut EventCtx) {
|
||||||
let i = self.current_page - 2;
|
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| {
|
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)
|
p.change_page(0)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -175,10 +176,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Component for AddressDetails<T>
|
impl Component for AddressDetails {
|
||||||
where
|
|
||||||
T: StringType + Clone,
|
|
||||||
{
|
|
||||||
type Msg = ();
|
type Msg = ();
|
||||||
|
|
||||||
fn place(&mut self, bounds: Rect) -> Rect {
|
fn place(&mut self, bounds: Rect) -> Rect {
|
||||||
@ -265,10 +263,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "ui_debug")]
|
#[cfg(feature = "ui_debug")]
|
||||||
impl<T> crate::trace::Trace for AddressDetails<T>
|
impl crate::trace::Trace for AddressDetails {
|
||||||
where
|
|
||||||
T: StringType + Clone,
|
|
||||||
{
|
|
||||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
t.component("AddressDetails");
|
t.component("AddressDetails");
|
||||||
match self.current_page {
|
match self.current_page {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
strutil::StringType,
|
strutil::TString,
|
||||||
ui::{
|
ui::{
|
||||||
component::{Child, Component, ComponentExt, Event, EventCtx, Label, Pad},
|
component::{Child, Component, ComponentExt, Event, EventCtx, Label, Pad},
|
||||||
display::{self, Color, Font},
|
display::{self, Color, Font},
|
||||||
@ -20,40 +20,40 @@ pub enum ConfirmMsg {
|
|||||||
Confirm = 2,
|
Confirm = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Confirm<T: StringType, U> {
|
pub struct Confirm<U> {
|
||||||
bg: Pad,
|
bg: Pad,
|
||||||
bg_color: Color,
|
bg_color: Color,
|
||||||
title: &'static str,
|
title: TString<'static>,
|
||||||
message: Child<Label<U>>,
|
message: Child<Label<U>>,
|
||||||
alert: Option<Label<T>>,
|
alert: Option<Label<U>>,
|
||||||
info_title: Option<T>,
|
info_title: Option<TString<'static>>,
|
||||||
info_text: Option<Label<U>>,
|
info_text: Option<Label<U>>,
|
||||||
button_text: T,
|
button_text: TString<'static>,
|
||||||
buttons: ButtonController<T>,
|
buttons: ButtonController,
|
||||||
/// Whether we are on the info screen (optional extra screen)
|
/// Whether we are on the info screen (optional extra screen)
|
||||||
showing_info_screen: bool,
|
showing_info_screen: bool,
|
||||||
two_btn_confirm: bool,
|
two_btn_confirm: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, U> Confirm<T, U>
|
impl<U> Confirm<U>
|
||||||
where
|
where
|
||||||
T: StringType + Clone,
|
|
||||||
U: AsRef<str>,
|
U: AsRef<str>,
|
||||||
{
|
{
|
||||||
pub fn new(
|
pub fn new<T: Into<TString<'static>>>(
|
||||||
bg_color: Color,
|
bg_color: Color,
|
||||||
title: &'static str,
|
title: T,
|
||||||
message: Label<U>,
|
message: Label<U>,
|
||||||
alert: Option<Label<T>>,
|
alert: Option<Label<U>>,
|
||||||
button_text: T,
|
button_text: T,
|
||||||
two_btn_confirm: bool,
|
two_btn_confirm: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
let button_text = button_text.into();
|
||||||
let btn_layout =
|
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 {
|
Self {
|
||||||
bg: Pad::with_background(bg_color).with_clear(),
|
bg: Pad::with_background(bg_color).with_clear(),
|
||||||
bg_color,
|
bg_color,
|
||||||
title,
|
title: title.into(),
|
||||||
message: Child::new(message),
|
message: Child::new(message),
|
||||||
alert,
|
alert,
|
||||||
info_title: None,
|
info_title: None,
|
||||||
@ -66,8 +66,12 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Adding optional info screen
|
/// Adding optional info screen
|
||||||
pub fn with_info_screen(mut self, info_title: T, info_text: Label<U>) -> Self {
|
pub fn with_info_screen<T: Into<TString<'static>>>(
|
||||||
self.info_title = Some(info_title);
|
mut self,
|
||||||
|
info_title: T,
|
||||||
|
info_text: Label<U>,
|
||||||
|
) -> Self {
|
||||||
|
self.info_title = Some(info_title.into());
|
||||||
self.info_text = Some(info_text);
|
self.info_text = Some(info_text);
|
||||||
self.buttons = ButtonController::new(self.get_button_layout());
|
self.buttons = ButtonController::new(self.get_button_layout());
|
||||||
self
|
self
|
||||||
@ -77,10 +81,10 @@ where
|
|||||||
self.info_title.is_some()
|
self.info_title.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_button_layout(&self) -> ButtonLayout<T> {
|
fn get_button_layout(&self) -> ButtonLayout {
|
||||||
Self::get_button_layout_general(
|
Self::get_button_layout_general(
|
||||||
self.showing_info_screen,
|
self.showing_info_screen,
|
||||||
self.button_text.clone(),
|
self.button_text,
|
||||||
self.has_info_screen(),
|
self.has_info_screen(),
|
||||||
self.two_btn_confirm,
|
self.two_btn_confirm,
|
||||||
)
|
)
|
||||||
@ -89,10 +93,10 @@ where
|
|||||||
/// Not relying on self here, to call it in constructor.
|
/// Not relying on self here, to call it in constructor.
|
||||||
fn get_button_layout_general(
|
fn get_button_layout_general(
|
||||||
showing_info_screen: bool,
|
showing_info_screen: bool,
|
||||||
button_text: T,
|
button_text: TString<'static>,
|
||||||
has_info_screen: bool,
|
has_info_screen: bool,
|
||||||
two_btn_confirm: bool,
|
two_btn_confirm: bool,
|
||||||
) -> ButtonLayout<T> {
|
) -> ButtonLayout {
|
||||||
if showing_info_screen {
|
if showing_info_screen {
|
||||||
ButtonLayout::arrow_none_none()
|
ButtonLayout::arrow_none_none()
|
||||||
} else if has_info_screen {
|
} 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
|
where
|
||||||
T: StringType + Clone,
|
|
||||||
U: AsRef<str>,
|
U: AsRef<str>,
|
||||||
{
|
{
|
||||||
type Msg = ConfirmMsg;
|
type Msg = ConfirmMsg;
|
||||||
@ -204,13 +207,17 @@ where
|
|||||||
fn paint(&mut self) {
|
fn paint(&mut self) {
|
||||||
self.bg.paint();
|
self.bg.paint();
|
||||||
|
|
||||||
let display_top_left = |text: &str| {
|
let display_top_left = |text: TString<'static>| {
|
||||||
display::text_top_left(Point::zero(), text, Font::BOLD, WHITE, self.bg_color);
|
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
|
// We are either on the info screen or on the "main" screen
|
||||||
if self.showing_info_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();
|
self.info_text.paint();
|
||||||
} else {
|
} else {
|
||||||
display_top_left(self.title);
|
display_top_left(self.title);
|
||||||
@ -227,9 +234,8 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "ui_debug")]
|
#[cfg(feature = "ui_debug")]
|
||||||
impl<T, U> crate::trace::Trace for Confirm<T, U>
|
impl<U> crate::trace::Trace for Confirm<U>
|
||||||
where
|
where
|
||||||
T: StringType + Clone,
|
|
||||||
U: AsRef<str>,
|
U: AsRef<str>,
|
||||||
{
|
{
|
||||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
strutil::StringType,
|
strutil::TString,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
ui::{
|
ui::{
|
||||||
component::{Component, Event, EventCtx, Never},
|
component::{Component, Event, EventCtx, Never},
|
||||||
@ -30,22 +30,16 @@ impl From<PhysicalButton> for ButtonPos {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Button<T>
|
pub struct Button {
|
||||||
where
|
|
||||||
T: StringType,
|
|
||||||
{
|
|
||||||
bounds: Rect,
|
bounds: Rect,
|
||||||
pos: ButtonPos,
|
pos: ButtonPos,
|
||||||
content: ButtonContent<T>,
|
content: ButtonContent,
|
||||||
styles: ButtonStyleSheet,
|
styles: ButtonStyleSheet,
|
||||||
state: State,
|
state: State,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Button<T>
|
impl Button {
|
||||||
where
|
pub fn new(pos: ButtonPos, content: ButtonContent, styles: ButtonStyleSheet) -> Self {
|
||||||
T: StringType,
|
|
||||||
{
|
|
||||||
pub fn new(pos: ButtonPos, content: ButtonContent<T>, styles: ButtonStyleSheet) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
pos,
|
pos,
|
||||||
content,
|
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
|
// Deciding between text and icon
|
||||||
let style = btn_details.style();
|
let style = btn_details.style();
|
||||||
match btn_details.content {
|
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)
|
Self::new(pos, ButtonContent::Text(text), styles)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,7 +66,7 @@ where
|
|||||||
Self::new(pos, ButtonContent::Icon(image), styles)
|
Self::new(pos, ButtonContent::Icon(image), styles)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn content(&self) -> &ButtonContent<T> {
|
pub fn content(&self) -> &ButtonContent {
|
||||||
&self.content
|
&self.content
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,7 +83,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Changing the text content of the button.
|
/// 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);
|
self.content = ButtonContent::Text(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,7 +121,7 @@ where
|
|||||||
} else {
|
} else {
|
||||||
match &self.content {
|
match &self.content {
|
||||||
ButtonContent::Text(text) => {
|
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 {
|
if style.with_outline {
|
||||||
text_width + 2 * theme::BUTTON_OUTLINE
|
text_width + 2 * theme::BUTTON_OUTLINE
|
||||||
} else if style.with_arms {
|
} else if style.with_arms {
|
||||||
@ -176,7 +170,7 @@ where
|
|||||||
// Centering the text in case of fixed width.
|
// Centering the text in case of fixed width.
|
||||||
if let ButtonContent::Text(text) = &self.content {
|
if let ButtonContent::Text(text) = &self.content {
|
||||||
if let Some(fixed_width) = style.fixed_width {
|
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;
|
offset_x = diff / 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -185,10 +179,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Component for Button<T>
|
impl Component for Button {
|
||||||
where
|
|
||||||
T: StringType,
|
|
||||||
{
|
|
||||||
type Msg = Never;
|
type Msg = Never;
|
||||||
|
|
||||||
fn place(&mut self, bounds: Rect) -> Rect {
|
fn place(&mut self, bounds: Rect) -> Rect {
|
||||||
@ -237,16 +228,15 @@ where
|
|||||||
|
|
||||||
// Painting the content
|
// Painting the content
|
||||||
match &self.content {
|
match &self.content {
|
||||||
ButtonContent::Text(text) => {
|
ButtonContent::Text(text) => text.map(|t| {
|
||||||
display::text_left(
|
display::text_left(
|
||||||
self.get_text_baseline(style)
|
self.get_text_baseline(style) - Offset::x(style.font.start_x_bearing(t)),
|
||||||
- Offset::x(style.font.start_x_bearing(text.as_ref())),
|
t,
|
||||||
text.as_ref(),
|
|
||||||
style.font,
|
style.font,
|
||||||
fg_color,
|
fg_color,
|
||||||
bg_color,
|
bg_color,
|
||||||
);
|
);
|
||||||
}
|
}),
|
||||||
ButtonContent::Icon(icon) => {
|
ButtonContent::Icon(icon) => {
|
||||||
// Allowing for possible offset of the area from current style
|
// Allowing for possible offset of the area from current style
|
||||||
let icon_area = area.translate(style.offset);
|
let icon_area = area.translate(style.offset);
|
||||||
@ -284,8 +274,8 @@ enum State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum ButtonContent<T> {
|
pub enum ButtonContent {
|
||||||
Text(T),
|
Text(TString<'static>),
|
||||||
Icon(Icon),
|
Icon(Icon),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -352,8 +342,8 @@ impl ButtonStyleSheet {
|
|||||||
|
|
||||||
/// Describing the button on the screen - only visuals.
|
/// Describing the button on the screen - only visuals.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ButtonDetails<T> {
|
pub struct ButtonDetails {
|
||||||
pub content: ButtonContent<T>,
|
pub content: ButtonContent,
|
||||||
pub duration: Option<Duration>,
|
pub duration: Option<Duration>,
|
||||||
with_outline: bool,
|
with_outline: bool,
|
||||||
with_arms: bool,
|
with_arms: bool,
|
||||||
@ -362,12 +352,9 @@ pub struct ButtonDetails<T> {
|
|||||||
pub send_long_press: bool,
|
pub send_long_press: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> ButtonDetails<T>
|
impl ButtonDetails {
|
||||||
where
|
|
||||||
T: StringType,
|
|
||||||
{
|
|
||||||
/// Text button.
|
/// Text button.
|
||||||
pub fn text(text: T) -> Self {
|
pub fn text(text: TString<'static>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
content: ButtonContent::Text(text),
|
content: ButtonContent::Text(text),
|
||||||
duration: None,
|
duration: None,
|
||||||
@ -393,17 +380,17 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Resolves text and finds possible icon names.
|
/// Resolves text and finds possible icon names.
|
||||||
pub fn from_text_possible_icon(text: T) -> Self {
|
pub fn from_text_possible_icon(text: TString<'static>) -> Self {
|
||||||
match text.as_ref() {
|
text.map(|t| match t {
|
||||||
"" => Self::cancel_icon(),
|
"" => Self::cancel_icon(),
|
||||||
"<" => Self::left_arrow_icon(),
|
"<" => Self::left_arrow_icon(),
|
||||||
"^" => Self::up_arrow_icon(),
|
"^" => Self::up_arrow_icon(),
|
||||||
_ => Self::text(text),
|
_ => Self::text(text),
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Text with arms signalling double press.
|
/// 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()
|
Self::text(text).with_arms()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -495,20 +482,17 @@ where
|
|||||||
|
|
||||||
/// Holding the button details for all three possible buttons.
|
/// Holding the button details for all three possible buttons.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ButtonLayout<T> {
|
pub struct ButtonLayout {
|
||||||
pub btn_left: Option<ButtonDetails<T>>,
|
pub btn_left: Option<ButtonDetails>,
|
||||||
pub btn_middle: Option<ButtonDetails<T>>,
|
pub btn_middle: Option<ButtonDetails>,
|
||||||
pub btn_right: Option<ButtonDetails<T>>,
|
pub btn_right: Option<ButtonDetails>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> ButtonLayout<T>
|
impl ButtonLayout {
|
||||||
where
|
|
||||||
T: StringType,
|
|
||||||
{
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
btn_left: Option<ButtonDetails<T>>,
|
btn_left: Option<ButtonDetails>,
|
||||||
btn_middle: Option<ButtonDetails<T>>,
|
btn_middle: Option<ButtonDetails>,
|
||||||
btn_right: Option<ButtonDetails<T>>,
|
btn_right: Option<ButtonDetails>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
btn_left,
|
btn_left,
|
||||||
@ -523,13 +507,8 @@ where
|
|||||||
Self::new(None, None, None)
|
Self::new(None, None, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Default button layout for all three buttons - icons.
|
/// Arrows at sides, armed text in the middle.
|
||||||
pub fn default_three_icons() -> Self {
|
pub fn arrow_armed_arrow(text: TString<'static>) -> Self {
|
||||||
Self::arrow_armed_arrow("SELECT".into())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Special middle text for default icon layout.
|
|
||||||
pub fn arrow_armed_arrow(text: T) -> Self {
|
|
||||||
Self::new(
|
Self::new(
|
||||||
Some(ButtonDetails::left_arrow_icon()),
|
Some(ButtonDetails::left_arrow_icon()),
|
||||||
Some(ButtonDetails::armed_text(text)),
|
Some(ButtonDetails::armed_text(text)),
|
||||||
@ -538,7 +517,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Left cancel, armed text and next right arrow.
|
/// 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(
|
Self::new(
|
||||||
Some(ButtonDetails::cancel_icon()),
|
Some(ButtonDetails::cancel_icon()),
|
||||||
Some(ButtonDetails::armed_text(text)),
|
Some(ButtonDetails::armed_text(text)),
|
||||||
@ -547,7 +526,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Middle armed text and next right arrow.
|
/// 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(
|
Self::new(
|
||||||
None,
|
None,
|
||||||
Some(ButtonDetails::armed_text(text)),
|
Some(ButtonDetails::armed_text(text)),
|
||||||
@ -556,7 +535,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Left text, armed text and right info icon/text.
|
/// 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(
|
Self::new(
|
||||||
Some(ButtonDetails::from_text_possible_icon(left)),
|
Some(ButtonDetails::from_text_possible_icon(left)),
|
||||||
Some(ButtonDetails::armed_text(middle)),
|
Some(ButtonDetails::armed_text(middle)),
|
||||||
@ -565,7 +544,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Left cancel, armed text and right info icon/text.
|
/// 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(
|
Self::new(
|
||||||
Some(ButtonDetails::cancel_icon()),
|
Some(ButtonDetails::cancel_icon()),
|
||||||
Some(ButtonDetails::armed_text(middle)),
|
Some(ButtonDetails::armed_text(middle)),
|
||||||
@ -574,7 +553,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Left cancel, armed text and blank on right.
|
/// 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(
|
Self::new(
|
||||||
Some(ButtonDetails::cancel_icon()),
|
Some(ButtonDetails::cancel_icon()),
|
||||||
Some(ButtonDetails::armed_text(middle)),
|
Some(ButtonDetails::armed_text(middle)),
|
||||||
@ -583,7 +562,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Left back arrow and middle armed text.
|
/// 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(
|
Self::new(
|
||||||
Some(ButtonDetails::left_arrow_icon()),
|
Some(ButtonDetails::left_arrow_icon()),
|
||||||
Some(ButtonDetails::armed_text(text)),
|
Some(ButtonDetails::armed_text(text)),
|
||||||
@ -592,7 +571,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Left and right texts.
|
/// 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(
|
Self::new(
|
||||||
Some(ButtonDetails::from_text_possible_icon(left)),
|
Some(ButtonDetails::from_text_possible_icon(left)),
|
||||||
None,
|
None,
|
||||||
@ -601,7 +580,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Left text and right arrow.
|
/// Left text and right arrow.
|
||||||
pub fn text_none_arrow(text: T) -> Self {
|
pub fn text_none_arrow(text: TString<'static>) -> Self {
|
||||||
Self::new(
|
Self::new(
|
||||||
Some(ButtonDetails::from_text_possible_icon(text)),
|
Some(ButtonDetails::from_text_possible_icon(text)),
|
||||||
None,
|
None,
|
||||||
@ -610,7 +589,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Left text and WIDE right arrow.
|
/// 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(
|
Self::new(
|
||||||
Some(ButtonDetails::from_text_possible_icon(text)),
|
Some(ButtonDetails::from_text_possible_icon(text)),
|
||||||
None,
|
None,
|
||||||
@ -619,7 +598,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Only right text.
|
/// Only right text.
|
||||||
pub fn none_none_text(text: T) -> Self {
|
pub fn none_none_text(text: TString<'static>) -> Self {
|
||||||
Self::new(
|
Self::new(
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
@ -637,7 +616,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Left arrow and right text.
|
/// Left arrow and right text.
|
||||||
pub fn arrow_none_text(text: T) -> Self {
|
pub fn arrow_none_text(text: TString<'static>) -> Self {
|
||||||
Self::new(
|
Self::new(
|
||||||
Some(ButtonDetails::left_arrow_icon()),
|
Some(ButtonDetails::left_arrow_icon()),
|
||||||
None,
|
None,
|
||||||
@ -646,7 +625,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Up arrow left and right text.
|
/// 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(
|
Self::new(
|
||||||
Some(ButtonDetails::up_arrow_icon()),
|
Some(ButtonDetails::up_arrow_icon()),
|
||||||
None,
|
None,
|
||||||
@ -682,7 +661,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Up arrow on left, middle text and info on the right.
|
/// 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(
|
Self::new(
|
||||||
Some(ButtonDetails::up_arrow_icon()),
|
Some(ButtonDetails::up_arrow_icon()),
|
||||||
Some(ButtonDetails::armed_text(text)),
|
Some(ButtonDetails::armed_text(text)),
|
||||||
@ -700,7 +679,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Cancel cross on left and text on the right.
|
/// 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(
|
Self::new(
|
||||||
Some(ButtonDetails::cancel_icon()),
|
Some(ButtonDetails::cancel_icon()),
|
||||||
None,
|
None,
|
||||||
@ -709,7 +688,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Cancel cross on left and hold-to-confirm text on the right.
|
/// 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(
|
Self::new(
|
||||||
Some(ButtonDetails::cancel_icon()),
|
Some(ButtonDetails::cancel_icon()),
|
||||||
None,
|
None,
|
||||||
@ -718,7 +697,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Arrow back on left and hold-to-confirm text on the right.
|
/// 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(
|
Self::new(
|
||||||
Some(ButtonDetails::left_arrow_icon()),
|
Some(ButtonDetails::left_arrow_icon()),
|
||||||
None,
|
None,
|
||||||
@ -727,12 +706,12 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Only armed text in the middle.
|
/// 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)
|
Self::new(None, Some(ButtonDetails::armed_text(text)), None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// HTC on both sides.
|
/// 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(
|
Self::new(
|
||||||
Some(ButtonDetails::text(left).with_default_duration()),
|
Some(ButtonDetails::text(left).with_default_duration()),
|
||||||
None,
|
None,
|
||||||
@ -945,33 +924,30 @@ impl ButtonActions {
|
|||||||
// DEBUG-ONLY SECTION BELOW
|
// DEBUG-ONLY SECTION BELOW
|
||||||
|
|
||||||
#[cfg(feature = "ui_debug")]
|
#[cfg(feature = "ui_debug")]
|
||||||
use crate::strutil::ShortString;
|
impl crate::trace::Trace for Button {
|
||||||
|
|
||||||
#[cfg(feature = "ui_debug")]
|
|
||||||
impl<T: StringType> crate::trace::Trace for Button<T> {
|
|
||||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
t.component("Button");
|
t.component("Button");
|
||||||
match &self.content {
|
match self.content {
|
||||||
ButtonContent::Text(text) => t.string("text", text.as_ref()),
|
ButtonContent::Text(text) => t.string("text", text),
|
||||||
ButtonContent::Icon(icon) => {
|
ButtonContent::Icon(icon) => {
|
||||||
t.null("text");
|
t.null("text");
|
||||||
t.string("icon", icon.name);
|
t.string("icon", icon.name.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "ui_debug")]
|
#[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) {
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
t.component("ButtonDetails");
|
t.component("ButtonDetails");
|
||||||
match &self.content {
|
match self.content {
|
||||||
ButtonContent::Text(text) => {
|
ButtonContent::Text(text) => {
|
||||||
t.string("text", text.as_ref());
|
t.string("text", text);
|
||||||
}
|
}
|
||||||
ButtonContent::Icon(icon) => {
|
ButtonContent::Icon(icon) => {
|
||||||
t.null("text");
|
t.null("text");
|
||||||
t.string("icon", icon.name);
|
t.string("icon", icon.name.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(duration) = &self.duration {
|
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,
|
theme, Button, ButtonDetails, ButtonLayout, ButtonPos, HoldToConfirm, HoldToConfirmMsg,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
strutil::StringType,
|
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
ui::{
|
ui::{
|
||||||
component::{base::Event, Component, EventCtx, Pad, TimerToken},
|
component::{base::Event, Component, EventCtx, Pad, TimerToken},
|
||||||
@ -52,20 +51,14 @@ pub enum ButtonControllerMsg {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Defines what kind of button should be currently used.
|
/// Defines what kind of button should be currently used.
|
||||||
pub enum ButtonType<T>
|
pub enum ButtonType {
|
||||||
where
|
Button(Button),
|
||||||
T: StringType,
|
HoldToConfirm(HoldToConfirm),
|
||||||
{
|
|
||||||
Button(Button<T>),
|
|
||||||
HoldToConfirm(HoldToConfirm<T>),
|
|
||||||
Nothing,
|
Nothing,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> ButtonType<T>
|
impl ButtonType {
|
||||||
where
|
pub fn from_button_details(pos: ButtonPos, btn_details: Option<ButtonDetails>) -> Self {
|
||||||
T: StringType,
|
|
||||||
{
|
|
||||||
pub fn from_button_details(pos: ButtonPos, btn_details: Option<ButtonDetails<T>>) -> Self {
|
|
||||||
if let Some(btn_details) = btn_details {
|
if let Some(btn_details) = btn_details {
|
||||||
if btn_details.duration.is_some() {
|
if btn_details.duration.is_some() {
|
||||||
Self::HoldToConfirm(HoldToConfirm::from_button_details(pos, btn_details))
|
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.
|
/// Users have a choice of a normal button or Hold-to-confirm button.
|
||||||
/// `button_type` specified what from those two is used, if anything.
|
/// `button_type` specified what from those two is used, if anything.
|
||||||
pub struct ButtonContainer<T>
|
pub struct ButtonContainer {
|
||||||
where
|
|
||||||
T: StringType,
|
|
||||||
{
|
|
||||||
pos: ButtonPos,
|
pos: ButtonPos,
|
||||||
button_type: ButtonType<T>,
|
button_type: ButtonType,
|
||||||
/// Holds the timestamp of when the button was pressed.
|
/// Holds the timestamp of when the button was pressed.
|
||||||
pressed_since: Option<Instant>,
|
pressed_since: Option<Instant>,
|
||||||
/// How long the button should be pressed to send `long_press=true` in
|
/// How long the button should be pressed to send `long_press=true` in
|
||||||
@ -125,13 +115,10 @@ where
|
|||||||
send_long_press: bool,
|
send_long_press: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> ButtonContainer<T>
|
impl ButtonContainer {
|
||||||
where
|
|
||||||
T: StringType,
|
|
||||||
{
|
|
||||||
/// Supplying `None` as `btn_details` marks the button inactive
|
/// Supplying `None` as `btn_details` marks the button inactive
|
||||||
/// (it can be later activated in `set()`).
|
/// (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;
|
const DEFAULT_LONG_PRESS_MS: u32 = 1000;
|
||||||
let send_long_press = btn_details
|
let send_long_press = btn_details
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@ -149,7 +136,7 @@ where
|
|||||||
/// Changing the state of the button.
|
/// Changing the state of the button.
|
||||||
///
|
///
|
||||||
/// Passing `None` as `btn_details` will mark the button as inactive.
|
/// 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
|
self.send_long_press = btn_details
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or(false, |btn| btn.send_long_press);
|
.map_or(false, |btn| btn.send_long_press);
|
||||||
@ -258,14 +245,11 @@ where
|
|||||||
///
|
///
|
||||||
/// There is optional complexity with IgnoreButtonDelay, which gets executed
|
/// There is optional complexity with IgnoreButtonDelay, which gets executed
|
||||||
/// only in cases where clients request it.
|
/// only in cases where clients request it.
|
||||||
pub struct ButtonController<T>
|
pub struct ButtonController {
|
||||||
where
|
|
||||||
T: StringType,
|
|
||||||
{
|
|
||||||
pad: Pad,
|
pad: Pad,
|
||||||
left_btn: ButtonContainer<T>,
|
left_btn: ButtonContainer,
|
||||||
middle_btn: ButtonContainer<T>,
|
middle_btn: ButtonContainer,
|
||||||
right_btn: ButtonContainer<T>,
|
right_btn: ButtonContainer,
|
||||||
state: ButtonState,
|
state: ButtonState,
|
||||||
/// Button area is needed so the buttons
|
/// Button area is needed so the buttons
|
||||||
/// can be "re-placed" after their text is changed
|
/// can be "re-placed" after their text is changed
|
||||||
@ -277,11 +261,8 @@ where
|
|||||||
handle_middle_button: bool,
|
handle_middle_button: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> ButtonController<T>
|
impl ButtonController {
|
||||||
where
|
pub fn new(btn_layout: ButtonLayout) -> Self {
|
||||||
T: StringType,
|
|
||||||
{
|
|
||||||
pub fn new(btn_layout: ButtonLayout<T>) -> Self {
|
|
||||||
let handle_middle_button = btn_layout.btn_middle.is_some();
|
let handle_middle_button = btn_layout.btn_middle.is_some();
|
||||||
Self {
|
Self {
|
||||||
pad: Pad::with_background(theme::BG).with_clear(),
|
pad: Pad::with_background(theme::BG).with_clear(),
|
||||||
@ -303,7 +284,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Updating all the three buttons to the wanted states.
|
/// 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.handle_middle_button = btn_layout.btn_middle.is_some();
|
||||||
self.pad.clear();
|
self.pad.clear();
|
||||||
self.left_btn.set(btn_layout.btn_left, self.button_area);
|
self.left_btn.set(btn_layout.btn_left, self.button_area);
|
||||||
@ -406,10 +387,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Component for ButtonController<T>
|
impl Component for ButtonController {
|
||||||
where
|
|
||||||
T: StringType,
|
|
||||||
{
|
|
||||||
type Msg = ButtonControllerMsg;
|
type Msg = ButtonControllerMsg;
|
||||||
|
|
||||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||||
@ -794,7 +772,7 @@ impl Component for AutomaticMover {
|
|||||||
// DEBUG-ONLY SECTION BELOW
|
// DEBUG-ONLY SECTION BELOW
|
||||||
|
|
||||||
#[cfg(feature = "ui_debug")]
|
#[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) {
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
if let ButtonType::Button(btn) = &self.button_type {
|
if let ButtonType::Button(btn) = &self.button_type {
|
||||||
btn.trace(t);
|
btn.trace(t);
|
||||||
@ -805,7 +783,7 @@ impl<T: StringType> crate::trace::Trace for ButtonContainer<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "ui_debug")]
|
#[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) {
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
t.component("ButtonController");
|
t.component("ButtonController");
|
||||||
t.child("left_btn", &self.left_btn);
|
t.child("left_btn", &self.left_btn);
|
||||||
|
@ -2,6 +2,7 @@ use core::mem;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
strutil::StringType,
|
strutil::StringType,
|
||||||
|
translations::TR,
|
||||||
ui::{
|
ui::{
|
||||||
component::{
|
component::{
|
||||||
base::Never,
|
base::Never,
|
||||||
@ -16,8 +17,6 @@ use crate::{
|
|||||||
|
|
||||||
use super::theme;
|
use super::theme;
|
||||||
|
|
||||||
const HEADER: &str = "COINJOIN IN PROGRESS";
|
|
||||||
const FOOTER: &str = "Do not disconnect your Trezor!";
|
|
||||||
const FOOTER_TEXT_MARGIN: i16 = 8;
|
const FOOTER_TEXT_MARGIN: i16 = 8;
|
||||||
const LOADER_OFFSET: i16 = -15;
|
const LOADER_OFFSET: i16 = -15;
|
||||||
const LOADER_SPEED: u16 = 10;
|
const LOADER_SPEED: u16 = 10;
|
||||||
@ -89,7 +88,7 @@ where
|
|||||||
if self.indeterminate {
|
if self.indeterminate {
|
||||||
text_multiline(
|
text_multiline(
|
||||||
self.area,
|
self.area,
|
||||||
HEADER,
|
TR::coinjoin__title_progress.into(),
|
||||||
Font::BOLD,
|
Font::BOLD,
|
||||||
theme::FG,
|
theme::FG,
|
||||||
theme::BG,
|
theme::BG,
|
||||||
@ -114,7 +113,7 @@ where
|
|||||||
// BOTTOM
|
// BOTTOM
|
||||||
let top_rest = text_multiline_bottom(
|
let top_rest = text_multiline_bottom(
|
||||||
self.area,
|
self.area,
|
||||||
FOOTER,
|
TR::coinjoin__do_not_disconnect.into(),
|
||||||
Font::BOLD,
|
Font::BOLD,
|
||||||
theme::FG,
|
theme::FG,
|
||||||
theme::BG,
|
theme::BG,
|
||||||
@ -123,7 +122,7 @@ where
|
|||||||
if let Some(rest) = top_rest {
|
if let Some(rest) = top_rest {
|
||||||
text_multiline_bottom(
|
text_multiline_bottom(
|
||||||
rest.inset(Insets::bottom(FOOTER_TEXT_MARGIN)),
|
rest.inset(Insets::bottom(FOOTER_TEXT_MARGIN)),
|
||||||
self.text.as_ref(),
|
self.text.as_ref().into(),
|
||||||
Font::NORMAL,
|
Font::NORMAL,
|
||||||
theme::FG,
|
theme::FG,
|
||||||
theme::BG,
|
theme::BG,
|
||||||
@ -140,8 +139,8 @@ where
|
|||||||
{
|
{
|
||||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
t.component("CoinJoinProgress");
|
t.component("CoinJoinProgress");
|
||||||
t.string("header", HEADER);
|
t.string("header", TR::coinjoin__title_progress.into());
|
||||||
t.string("text", self.text.as_ref());
|
t.string("text", self.text.as_ref().into());
|
||||||
t.string("footer", FOOTER);
|
t.string("footer", TR::coinjoin__do_not_disconnect.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,23 +6,23 @@ use crate::ui::{
|
|||||||
use super::theme;
|
use super::theme;
|
||||||
|
|
||||||
/// Display white text on black background
|
/// 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::text_left(baseline, text.as_ref(), font, theme::FG, theme::BG);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Display black text on white background
|
/// 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::text_left(baseline, text.as_ref(), font, theme::BG, theme::FG);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Display white text on black background,
|
/// Display white text on black background,
|
||||||
/// centered around a baseline Point
|
/// 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::text_center(baseline, text.as_ref(), font, theme::FG, theme::BG);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Display white text on black background,
|
/// Display white text on black background,
|
||||||
/// with right boundary at a baseline Point
|
/// 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);
|
display::text_right(baseline, text.as_ref(), font, theme::FG, theme::BG);
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ where
|
|||||||
content_area: Rect,
|
content_area: Rect,
|
||||||
title_area: Rect,
|
title_area: Rect,
|
||||||
pad: Pad,
|
pad: Pad,
|
||||||
buttons: Child<ButtonController<T>>,
|
buttons: Child<ButtonController>,
|
||||||
page_counter: usize,
|
page_counter: usize,
|
||||||
return_confirmed_index: bool,
|
return_confirmed_index: bool,
|
||||||
show_scrollbar: bool,
|
show_scrollbar: bool,
|
||||||
|
@ -79,7 +79,7 @@ where
|
|||||||
T: StringType + Clone,
|
T: StringType + Clone,
|
||||||
{
|
{
|
||||||
formatted: FormattedText<T>,
|
formatted: FormattedText<T>,
|
||||||
btn_layout: ButtonLayout<T>,
|
btn_layout: ButtonLayout,
|
||||||
btn_actions: ButtonActions,
|
btn_actions: ButtonActions,
|
||||||
current_page: usize,
|
current_page: usize,
|
||||||
page_count: usize,
|
page_count: usize,
|
||||||
@ -93,7 +93,7 @@ where
|
|||||||
T: StringType + Clone,
|
T: StringType + Clone,
|
||||||
{
|
{
|
||||||
pub fn new(
|
pub fn new(
|
||||||
btn_layout: ButtonLayout<T>,
|
btn_layout: ButtonLayout,
|
||||||
btn_actions: ButtonActions,
|
btn_actions: ButtonActions,
|
||||||
formatted: FormattedText<T>,
|
formatted: FormattedText<T>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@ -137,7 +137,7 @@ where
|
|||||||
bounds
|
bounds
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn btn_layout(&self) -> ButtonLayout<T> {
|
pub fn btn_layout(&self) -> ButtonLayout {
|
||||||
// When we are in pagination inside this flow,
|
// When we are in pagination inside this flow,
|
||||||
// show the up and down arrows on appropriate sides.
|
// show the up and down arrows on appropriate sides.
|
||||||
let current = self.btn_layout.clone();
|
let current = self.btn_layout.clone();
|
||||||
@ -235,7 +235,7 @@ where
|
|||||||
t.component("Page");
|
t.component("Page");
|
||||||
if let Some(title) = &self.title {
|
if let Some(title) = &self.title {
|
||||||
// Not calling it "title" as that is already traced by FlowPage
|
// 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("active_page", self.current_page as i64);
|
||||||
t.int("page_count", self.page_count as i64);
|
t.int("page_count", self.page_count as i64);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
strutil::StringType,
|
strutil::TString,
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
ui::{
|
ui::{
|
||||||
component::{Component, Event, EventCtx},
|
component::{Component, Event, EventCtx},
|
||||||
@ -18,21 +18,21 @@ pub enum HoldToConfirmMsg {
|
|||||||
FailedToConfirm,
|
FailedToConfirm,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct HoldToConfirm<T>
|
pub struct HoldToConfirm {
|
||||||
where
|
|
||||||
T: StringType,
|
|
||||||
{
|
|
||||||
pos: ButtonPos,
|
pos: ButtonPos,
|
||||||
loader: Loader<T>,
|
loader: Loader,
|
||||||
text_width: i16,
|
text_width: i16,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> HoldToConfirm<T>
|
impl HoldToConfirm {
|
||||||
where
|
pub fn text<T: Into<TString<'static>>>(
|
||||||
T: StringType,
|
pos: ButtonPos,
|
||||||
{
|
text: T,
|
||||||
pub fn text(pos: ButtonPos, text: T, styles: LoaderStyleSheet, duration: Duration) -> Self {
|
styles: LoaderStyleSheet,
|
||||||
let text_width = styles.normal.font.visible_text_width(text.as_ref());
|
duration: Duration,
|
||||||
|
) -> Self {
|
||||||
|
let text = text.into();
|
||||||
|
let text_width = text.map(|t| styles.normal.font.visible_text_width(t));
|
||||||
Self {
|
Self {
|
||||||
pos,
|
pos,
|
||||||
loader: Loader::text(text, styles).with_growing_duration(duration),
|
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
|
let duration = btn_details
|
||||||
.duration
|
.duration
|
||||||
.unwrap_or_else(|| Duration::from_millis(DEFAULT_DURATION_MS));
|
.unwrap_or_else(|| Duration::from_millis(DEFAULT_DURATION_MS));
|
||||||
@ -53,7 +53,8 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Updating the text of the component and re-placing it.
|
/// 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.text_width = self.loader.get_text_width(&text);
|
||||||
self.loader.set_text(text);
|
self.loader.set_text(text);
|
||||||
self.place(button_area);
|
self.place(button_area);
|
||||||
@ -71,7 +72,7 @@ where
|
|||||||
self.loader.get_duration()
|
self.loader.get_duration()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_text(&self) -> &T {
|
pub fn get_text(&self) -> TString<'static> {
|
||||||
self.loader.get_text()
|
self.loader.get_text()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,10 +86,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Component for HoldToConfirm<T>
|
impl Component for HoldToConfirm {
|
||||||
where
|
|
||||||
T: StringType,
|
|
||||||
{
|
|
||||||
type Msg = HoldToConfirmMsg;
|
type Msg = HoldToConfirmMsg;
|
||||||
|
|
||||||
fn place(&mut self, bounds: Rect) -> Rect {
|
fn place(&mut self, bounds: Rect) -> Rect {
|
||||||
@ -129,10 +127,7 @@ where
|
|||||||
// DEBUG-ONLY SECTION BELOW
|
// DEBUG-ONLY SECTION BELOW
|
||||||
|
|
||||||
#[cfg(feature = "ui_debug")]
|
#[cfg(feature = "ui_debug")]
|
||||||
impl<T> crate::trace::Trace for HoldToConfirm<T>
|
impl crate::trace::Trace for HoldToConfirm {
|
||||||
where
|
|
||||||
T: StringType,
|
|
||||||
{
|
|
||||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
t.component("HoldToConfirm");
|
t.component("HoldToConfirm");
|
||||||
t.child("loader", &self.loader);
|
t.child("loader", &self.loader);
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
|
error::Error,
|
||||||
|
micropython::buffer::StrBuffer,
|
||||||
strutil::StringType,
|
strutil::StringType,
|
||||||
|
translations::TR,
|
||||||
trezorhal::usb::usb_configured,
|
trezorhal::usb::usb_configured,
|
||||||
ui::{
|
ui::{
|
||||||
component::{Child, Component, Event, EventCtx, Label},
|
component::{Child, Component, Event, EventCtx, Label},
|
||||||
@ -60,7 +63,7 @@ where
|
|||||||
label: Label<T>,
|
label: Label<T>,
|
||||||
notification: Option<(T, u8)>,
|
notification: Option<(T, u8)>,
|
||||||
/// Used for HTC functionality to lock device from homescreen
|
/// Used for HTC functionality to lock device from homescreen
|
||||||
invisible_buttons: Child<ButtonController<T>>,
|
invisible_buttons: Child<ButtonController>,
|
||||||
/// Holds the loader component
|
/// Holds the loader component
|
||||||
loader: Option<Child<ProgressLoader<T>>>,
|
loader: Option<Child<ProgressLoader<T>>>,
|
||||||
/// Whether to show the loader or not
|
/// Whether to show the loader or not
|
||||||
@ -107,10 +110,11 @@ where
|
|||||||
if !usb_configured() {
|
if !usb_configured() {
|
||||||
self.fill_notification_background();
|
self.fill_notification_background();
|
||||||
// TODO: fill warning icons here as well?
|
// 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 {
|
} else if let Some((notification, _level)) = &self.notification {
|
||||||
self.fill_notification_background();
|
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
|
// Painting warning icons in top corners when the text is short enough not to
|
||||||
// collide with them
|
// collide with them
|
||||||
let icon_width = NOTIFICATION_ICON.toif.width();
|
let icon_width = NOTIFICATION_ICON.toif.width();
|
||||||
@ -239,9 +243,9 @@ where
|
|||||||
T: StringType,
|
T: StringType,
|
||||||
{
|
{
|
||||||
label: Child<Label<T>>,
|
label: Child<Label<T>>,
|
||||||
instruction: Child<Label<T>>,
|
instruction: Child<Label<StrBuffer>>,
|
||||||
/// Used for unlocking the device from lockscreen
|
/// Used for unlocking the device from lockscreen
|
||||||
invisible_buttons: Child<ButtonController<T>>,
|
invisible_buttons: Child<ButtonController>,
|
||||||
/// Display coinjoin icon?
|
/// Display coinjoin icon?
|
||||||
coinjoin_icon: Option<Icon>,
|
coinjoin_icon: Option<Icon>,
|
||||||
/// Screensaver mode (keep screen black)
|
/// Screensaver mode (keep screen black)
|
||||||
@ -252,22 +256,25 @@ impl<T> Lockscreen<T>
|
|||||||
where
|
where
|
||||||
T: StringType + Clone,
|
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,
|
// Buttons will not be visible, we only need all three of them to be present,
|
||||||
// so that even middle-click triggers the event.
|
// so that even middle-click triggers the event.
|
||||||
let invisible_btn_layout = ButtonLayout::arrow_armed_arrow("".into());
|
let invisible_btn_layout = ButtonLayout::arrow_armed_arrow("".into());
|
||||||
let instruction_str = if bootscreen {
|
let instruction_str = if bootscreen {
|
||||||
"Click to Connect"
|
TR::homescreen__click_to_connect
|
||||||
} else {
|
} else {
|
||||||
"Click to Unlock"
|
TR::homescreen__click_to_unlock
|
||||||
};
|
};
|
||||||
Lockscreen {
|
Ok(Lockscreen {
|
||||||
label: Child::new(Label::centered(label, theme::TEXT_BIG)),
|
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)),
|
invisible_buttons: Child::new(ButtonController::new(invisible_btn_layout)),
|
||||||
coinjoin_icon: coinjoin_authorized.then_some(theme::ICON_COINJOIN),
|
coinjoin_icon: coinjoin_authorized.then_some(theme::ICON_COINJOIN),
|
||||||
screensaver: !bootscreen,
|
screensaver: !bootscreen,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -321,7 +328,7 @@ where
|
|||||||
{
|
{
|
||||||
title: Child<Label<T>>,
|
title: Child<Label<T>>,
|
||||||
buffer_func: F,
|
buffer_func: F,
|
||||||
buttons: Child<ButtonController<T>>,
|
buttons: Child<ButtonController>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, F> ConfirmHomescreen<T, F>
|
impl<T, F> ConfirmHomescreen<T, F>
|
||||||
@ -329,7 +336,7 @@ where
|
|||||||
T: StringType + Clone,
|
T: StringType + Clone,
|
||||||
{
|
{
|
||||||
pub fn new(title: T, buffer_func: F) -> Self {
|
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 {
|
ConfirmHomescreen {
|
||||||
title: Child::new(Label::centered(title, theme::TEXT_BOLD)),
|
title: Child::new(Label::centered(title, theme::TEXT_BOLD)),
|
||||||
buffer_func,
|
buffer_func,
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
use crate::{
|
use crate::ui::{
|
||||||
strutil::StringType,
|
component::{Child, Component, Event, EventCtx, Pad},
|
||||||
ui::{
|
geometry::{Insets, Offset, Rect},
|
||||||
component::{Child, Component, Event, EventCtx, Pad},
|
util::animation_disabled,
|
||||||
geometry::{Insets, Offset, Rect},
|
|
||||||
util::animation_disabled,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::super::{
|
use super::super::{
|
||||||
@ -13,7 +10,7 @@ use super::super::{
|
|||||||
|
|
||||||
const DEFAULT_ITEMS_DISTANCE: i16 = 10;
|
const DEFAULT_ITEMS_DISTANCE: i16 = 10;
|
||||||
|
|
||||||
pub trait Choice<T: StringType> {
|
pub trait Choice {
|
||||||
// Only `paint_center` is required, the rest is optional
|
// Only `paint_center` is required, the rest is optional
|
||||||
// and therefore has a default implementation.
|
// and therefore has a default implementation.
|
||||||
fn paint_center(&self, area: Rect, inverse: bool);
|
fn paint_center(&self, area: Rect, inverse: bool);
|
||||||
@ -26,9 +23,7 @@ pub trait Choice<T: StringType> {
|
|||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn btn_layout(&self) -> ButtonLayout<T> {
|
fn btn_layout(&self) -> ButtonLayout;
|
||||||
ButtonLayout::default_three_icons()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether it is possible to do the middle action event without
|
/// Whether it is possible to do the middle action event without
|
||||||
/// releasing the button - after long-press duration is reached.
|
/// 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
|
/// but offers a "lazy-loading" way of requesting the
|
||||||
/// items only when they are needed, one-by-one.
|
/// items only when they are needed, one-by-one.
|
||||||
/// This way, no more than one item is stored in memory at any time.
|
/// 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 Action;
|
||||||
type Item: Choice<T>;
|
type Item: Choice;
|
||||||
|
|
||||||
fn count(&self) -> usize;
|
fn count(&self) -> usize;
|
||||||
fn get(&self, index: usize) -> (Self::Item, Self::Action);
|
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" -
|
/// `is_carousel` can be used to make the choice page "infinite" -
|
||||||
/// after reaching one end, users will appear at the other end.
|
/// after reaching one end, users will appear at the other end.
|
||||||
pub struct ChoicePage<F, T, A>
|
pub struct ChoicePage<F, A>
|
||||||
where
|
where
|
||||||
F: ChoiceFactory<T, Action = A>,
|
F: ChoiceFactory<Action = A>,
|
||||||
T: StringType,
|
|
||||||
{
|
{
|
||||||
choices: F,
|
choices: F,
|
||||||
pad: Pad,
|
pad: Pad,
|
||||||
buttons: Child<ButtonController<T>>,
|
buttons: Child<ButtonController>,
|
||||||
page_counter: usize,
|
page_counter: usize,
|
||||||
/// How many pixels are between the items.
|
/// How many pixels are between the items.
|
||||||
items_distance: i16,
|
items_distance: i16,
|
||||||
@ -97,10 +91,9 @@ where
|
|||||||
animated_steps_to_do: i16,
|
animated_steps_to_do: i16,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<F, T, A> ChoicePage<F, T, A>
|
impl<F, A> ChoicePage<F, A>
|
||||||
where
|
where
|
||||||
F: ChoiceFactory<T, Action = A>,
|
F: ChoiceFactory<Action = A>,
|
||||||
T: StringType + Clone,
|
|
||||||
{
|
{
|
||||||
pub fn new(choices: F) -> Self {
|
pub fn new(choices: F) -> Self {
|
||||||
let initial_btn_layout = choices.get(0).0.btn_layout();
|
let initial_btn_layout = choices.get(0).0.btn_layout();
|
||||||
@ -283,12 +276,12 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Getting the choice on the current index
|
/// 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)
|
self.choices.get(self.page_counter)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Getting the current item
|
/// 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
|
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
|
where
|
||||||
F: ChoiceFactory<T, Action = A>,
|
F: ChoiceFactory<Action = A>,
|
||||||
T: StringType + Clone,
|
|
||||||
{
|
{
|
||||||
type Msg = (A, bool);
|
type Msg = (A, bool);
|
||||||
|
|
||||||
@ -599,11 +591,10 @@ where
|
|||||||
// DEBUG-ONLY SECTION BELOW
|
// DEBUG-ONLY SECTION BELOW
|
||||||
|
|
||||||
#[cfg(feature = "ui_debug")]
|
#[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
|
where
|
||||||
F: ChoiceFactory<T, Action = A>,
|
F: ChoiceFactory<Action = A>,
|
||||||
T: StringType + Clone,
|
<F as ChoiceFactory>::Item: crate::trace::Trace,
|
||||||
<F as ChoiceFactory<T>>::Item: crate::trace::Trace,
|
|
||||||
{
|
{
|
||||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
t.component("ChoicePage");
|
t.component("ChoicePage");
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
strutil::{ShortString, StringType},
|
strutil::ShortString,
|
||||||
ui::{
|
ui::{
|
||||||
display::{self, rect_fill, rect_fill_corners, rect_outline_rounded, Font, Icon},
|
display::{self, rect_fill, rect_fill_corners, rect_outline_rounded, Font, Icon},
|
||||||
geometry::{Alignment2D, Offset, Rect},
|
geometry::{Alignment2D, Offset, Rect},
|
||||||
@ -14,16 +14,16 @@ const ICON_RIGHT_PADDING: i16 = 2;
|
|||||||
|
|
||||||
/// Simple string component used as a choice item.
|
/// Simple string component used as a choice item.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ChoiceItem<T: StringType> {
|
pub struct ChoiceItem {
|
||||||
text: ShortString,
|
text: ShortString,
|
||||||
icon: Option<Icon>,
|
icon: Option<Icon>,
|
||||||
btn_layout: ButtonLayout<T>,
|
btn_layout: ButtonLayout,
|
||||||
font: Font,
|
font: Font,
|
||||||
middle_action_without_release: bool,
|
middle_action_without_release: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: StringType> ChoiceItem<T> {
|
impl ChoiceItem {
|
||||||
pub fn new<U: AsRef<str>>(text: U, btn_layout: ButtonLayout<T>) -> Self {
|
pub fn new<U: AsRef<str>>(text: U, btn_layout: ButtonLayout) -> Self {
|
||||||
Self {
|
Self {
|
||||||
text: String::from(text.as_ref()),
|
text: String::from(text.as_ref()),
|
||||||
icon: None,
|
icon: None,
|
||||||
@ -55,17 +55,17 @@ impl<T: StringType> ChoiceItem<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Setting left button.
|
/// 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;
|
self.btn_layout.btn_left = btn_left;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Setting middle button.
|
/// 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;
|
self.btn_layout.btn_middle = btn_middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Setting right button.
|
/// 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;
|
self.btn_layout.btn_right = btn_right;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,10 +87,7 @@ impl<T: StringType> ChoiceItem<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Choice<T> for ChoiceItem<T>
|
impl Choice for ChoiceItem {
|
||||||
where
|
|
||||||
T: StringType + Clone,
|
|
||||||
{
|
|
||||||
/// Painting the item as the main choice in the middle.
|
/// Painting the item as the main choice in the middle.
|
||||||
/// Showing both the icon and text, if the icon is available.
|
/// Showing both the icon and text, if the icon is available.
|
||||||
fn paint_center(&self, area: Rect, inverse: bool) {
|
fn paint_center(&self, area: Rect, inverse: bool) {
|
||||||
@ -125,7 +122,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Getting current button layout.
|
/// Getting current button layout.
|
||||||
fn btn_layout(&self) -> ButtonLayout<T> {
|
fn btn_layout(&self) -> ButtonLayout {
|
||||||
self.btn_layout.clone()
|
self.btn_layout.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,9 +193,9 @@ fn paint_text_icon(
|
|||||||
// DEBUG-ONLY SECTION BELOW
|
// DEBUG-ONLY SECTION BELOW
|
||||||
|
|
||||||
#[cfg(feature = "ui_debug")]
|
#[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) {
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
t.component("ChoiceItem");
|
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;
|
||||||
pub mod choice_item;
|
pub mod choice_item;
|
||||||
|
|
||||||
|
#[cfg(feature = "translations")]
|
||||||
pub mod number_input;
|
pub mod number_input;
|
||||||
|
#[cfg(feature = "translations")]
|
||||||
pub mod passphrase;
|
pub mod passphrase;
|
||||||
|
#[cfg(feature = "translations")]
|
||||||
pub mod pin;
|
pub mod pin;
|
||||||
|
#[cfg(feature = "translations")]
|
||||||
pub mod simple_choice;
|
pub mod simple_choice;
|
||||||
|
#[cfg(feature = "translations")]
|
||||||
pub mod wordlist;
|
pub mod wordlist;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
strutil::StringType,
|
translations::TR,
|
||||||
ui::{
|
ui::{
|
||||||
component::{Component, Event, EventCtx},
|
component::{Component, Event, EventCtx},
|
||||||
geometry::Rect,
|
geometry::Rect,
|
||||||
@ -20,9 +20,9 @@ impl ChoiceFactoryNumberInput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: StringType + Clone> ChoiceFactory<T> for ChoiceFactoryNumberInput {
|
impl ChoiceFactory for ChoiceFactoryNumberInput {
|
||||||
type Action = u32;
|
type Action = u32;
|
||||||
type Item = ChoiceItem<T>;
|
type Item = ChoiceItem;
|
||||||
|
|
||||||
fn count(&self) -> usize {
|
fn count(&self) -> usize {
|
||||||
(self.max - self.min + 1) as 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) {
|
fn get(&self, choice_index: usize) -> (Self::Item, Self::Action) {
|
||||||
let num = self.min + choice_index as u32;
|
let num = self.min + choice_index as u32;
|
||||||
let text: String<10> = String::from(num);
|
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.
|
// Disabling prev/next buttons for the first/last choice.
|
||||||
// (could be done to the same button if there is only one)
|
// (could be done to the same button if there is only one)
|
||||||
if choice_index == 0 {
|
if choice_index == 0 {
|
||||||
choice_item.set_left_btn(None);
|
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);
|
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
|
/// Simple wrapper around `ChoicePage` that allows for
|
||||||
/// inputting a list of values and receiving the chosen one.
|
/// inputting a list of values and receiving the chosen one.
|
||||||
pub struct NumberInput<T: StringType + Clone> {
|
pub struct NumberInput {
|
||||||
choice_page: ChoicePage<ChoiceFactoryNumberInput, T, u32>,
|
choice_page: ChoicePage<ChoiceFactoryNumberInput, u32>,
|
||||||
min: u32,
|
min: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> NumberInput<T>
|
impl NumberInput {
|
||||||
where
|
|
||||||
T: StringType + Clone,
|
|
||||||
{
|
|
||||||
pub fn new(min: u32, max: u32, init_value: u32) -> Self {
|
pub fn new(min: u32, max: u32, init_value: u32) -> Self {
|
||||||
let choices = ChoiceFactoryNumberInput::new(min, max);
|
let choices = ChoiceFactoryNumberInput::new(min, max);
|
||||||
let initial_page = init_value - min;
|
let initial_page = init_value - min;
|
||||||
@ -67,10 +67,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Component for NumberInput<T>
|
impl Component for NumberInput {
|
||||||
where
|
|
||||||
T: StringType + Clone,
|
|
||||||
{
|
|
||||||
type Msg = u32;
|
type Msg = u32;
|
||||||
|
|
||||||
fn place(&mut self, bounds: Rect) -> Rect {
|
fn place(&mut self, bounds: Rect) -> Rect {
|
||||||
@ -89,10 +86,7 @@ where
|
|||||||
// DEBUG-ONLY SECTION BELOW
|
// DEBUG-ONLY SECTION BELOW
|
||||||
|
|
||||||
#[cfg(feature = "ui_debug")]
|
#[cfg(feature = "ui_debug")]
|
||||||
impl<T> crate::trace::Trace for NumberInput<T>
|
impl crate::trace::Trace for NumberInput {
|
||||||
where
|
|
||||||
T: StringType + Clone,
|
|
||||||
{
|
|
||||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
t.component("NumberInput");
|
t.component("NumberInput");
|
||||||
t.child("choice_page", &self.choice_page);
|
t.child("choice_page", &self.choice_page);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
strutil::StringType,
|
strutil::TString,
|
||||||
|
translations::TR,
|
||||||
trezorhal::random,
|
trezorhal::random,
|
||||||
ui::{
|
ui::{
|
||||||
component::{text::common::TextBox, Child, Component, ComponentExt, Event, EventCtx},
|
component::{text::common::TextBox, Child, Component, ComponentExt, Event, EventCtx},
|
||||||
@ -43,64 +44,72 @@ const DIGITS_INDEX: usize = 5;
|
|||||||
const SPECIAL_INDEX: usize = 6;
|
const SPECIAL_INDEX: usize = 6;
|
||||||
const SPACE_INDEX: usize = 7;
|
const SPACE_INDEX: usize = 7;
|
||||||
|
|
||||||
/// Menu text, action, icon data, middle button with CONFIRM, without_release
|
#[derive(Clone)]
|
||||||
const MENU: [(&str, PassphraseAction, Option<Icon>, bool, bool); MENU_LENGTH] = [
|
struct MenuItem {
|
||||||
(
|
text: TString<'static>,
|
||||||
"SHOW",
|
action: PassphraseAction,
|
||||||
PassphraseAction::Show,
|
icon: Option<Icon>,
|
||||||
Some(theme::ICON_EYE),
|
show_confirm: bool,
|
||||||
true,
|
without_release: bool,
|
||||||
false,
|
}
|
||||||
),
|
|
||||||
(
|
const MENU: [MenuItem; MENU_LENGTH] = [
|
||||||
"CANCEL_OR_DELETE", // will be chosen dynamically
|
MenuItem {
|
||||||
PassphraseAction::CancelOrDelete,
|
text: TR::inputs__show.as_tstring(),
|
||||||
None,
|
action: PassphraseAction::Show,
|
||||||
true,
|
icon: Some(theme::ICON_EYE),
|
||||||
true, // without_release
|
show_confirm: true,
|
||||||
),
|
without_release: false,
|
||||||
(
|
},
|
||||||
"ENTER",
|
MenuItem {
|
||||||
PassphraseAction::Enter,
|
text: TString::from_str("CANCEL OR DELETE"),
|
||||||
Some(theme::ICON_TICK),
|
action: PassphraseAction::CancelOrDelete,
|
||||||
true,
|
icon: None,
|
||||||
false,
|
show_confirm: true,
|
||||||
),
|
without_release: true,
|
||||||
(
|
},
|
||||||
"abc",
|
MenuItem {
|
||||||
PassphraseAction::Category(ChoiceCategory::LowercaseLetter),
|
text: TR::inputs__enter.as_tstring(),
|
||||||
None,
|
action: PassphraseAction::Enter,
|
||||||
false,
|
icon: Some(theme::ICON_TICK),
|
||||||
false,
|
show_confirm: true,
|
||||||
),
|
without_release: false,
|
||||||
(
|
},
|
||||||
"ABC",
|
MenuItem {
|
||||||
PassphraseAction::Category(ChoiceCategory::UppercaseLetter),
|
text: TString::from_str("abc"),
|
||||||
None,
|
action: PassphraseAction::Category(ChoiceCategory::LowercaseLetter),
|
||||||
false,
|
icon: None,
|
||||||
false,
|
show_confirm: false,
|
||||||
),
|
without_release: false,
|
||||||
(
|
},
|
||||||
"123",
|
MenuItem {
|
||||||
PassphraseAction::Category(ChoiceCategory::Digit),
|
text: TString::from_str("ABC"),
|
||||||
None,
|
action: PassphraseAction::Category(ChoiceCategory::UppercaseLetter),
|
||||||
false,
|
icon: None,
|
||||||
false,
|
show_confirm: false,
|
||||||
),
|
without_release: false,
|
||||||
(
|
},
|
||||||
"#$!",
|
MenuItem {
|
||||||
PassphraseAction::Category(ChoiceCategory::SpecialSymbol),
|
text: TString::from_str("123"),
|
||||||
None,
|
action: PassphraseAction::Category(ChoiceCategory::Digit),
|
||||||
false,
|
icon: None,
|
||||||
false,
|
show_confirm: false,
|
||||||
),
|
without_release: false,
|
||||||
(
|
},
|
||||||
"SPACE",
|
MenuItem {
|
||||||
PassphraseAction::Character(' '),
|
text: TString::from_str("#$!"),
|
||||||
Some(theme::ICON_SPACE),
|
action: PassphraseAction::Category(ChoiceCategory::SpecialSymbol),
|
||||||
false,
|
icon: None,
|
||||||
false,
|
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)]
|
#[derive(Clone, Copy)]
|
||||||
@ -172,66 +181,74 @@ impl ChoiceFactoryPassphrase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// MENU choices with accept and cancel hold-to-confirm side buttons.
|
/// MENU choices with accept and cancel hold-to-confirm side buttons.
|
||||||
fn get_menu_item<T: StringType>(
|
fn get_menu_item(&self, choice_index: usize) -> (ChoiceItem, PassphraseAction) {
|
||||||
&self,
|
#[allow(const_item_mutation)]
|
||||||
choice_index: usize,
|
let current_item = &mut MENU[choice_index];
|
||||||
) -> (ChoiceItem<T>, PassphraseAction) {
|
|
||||||
// More options for CANCEL/DELETE button
|
// More options for CANCEL/DELETE button
|
||||||
let (mut text, action, mut icon, show_confirm, without_release) = MENU[choice_index];
|
if matches!(current_item.action, PassphraseAction::CancelOrDelete) {
|
||||||
if matches!(action, PassphraseAction::CancelOrDelete) {
|
|
||||||
if self.is_empty {
|
if self.is_empty {
|
||||||
text = "CANCEL";
|
current_item.text = TR::inputs__cancel.into();
|
||||||
icon = Some(theme::ICON_CANCEL);
|
current_item.icon = Some(theme::ICON_CANCEL);
|
||||||
} else {
|
} else {
|
||||||
text = "DELETE";
|
current_item.text = TR::inputs__delete.into();
|
||||||
icon = Some(theme::ICON_DELETE);
|
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
|
// Action buttons have different middle button text
|
||||||
if show_confirm {
|
if current_item.show_confirm {
|
||||||
let confirm_btn = ButtonDetails::armed_text("CONFIRM".into());
|
let confirm_btn = ButtonDetails::armed_text(TR::buttons__confirm.into());
|
||||||
menu_item.set_middle_btn(Some(confirm_btn));
|
menu_item.set_middle_btn(Some(confirm_btn));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Making middle button create LongPress events
|
// Making middle button create LongPress events
|
||||||
if without_release {
|
if current_item.without_release {
|
||||||
menu_item = menu_item.with_middle_action_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 = 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
|
/// Character choices with a BACK to MENU choice at the end (visible from
|
||||||
/// start) to return back
|
/// start) to return back
|
||||||
fn get_character_item<T: StringType>(
|
fn get_character_item(&self, choice_index: usize) -> (ChoiceItem, PassphraseAction) {
|
||||||
&self,
|
|
||||||
choice_index: usize,
|
|
||||||
) -> (ChoiceItem<T>, PassphraseAction) {
|
|
||||||
if is_menu_choice(&self.current_category, choice_index) {
|
if is_menu_choice(&self.current_category, choice_index) {
|
||||||
(
|
(
|
||||||
ChoiceItem::new("BACK", ButtonLayout::arrow_armed_arrow("RETURN".into()))
|
TR::inputs__back.map_translated(|t| {
|
||||||
.with_icon(theme::ICON_ARROW_BACK_UP),
|
ChoiceItem::new(
|
||||||
|
t,
|
||||||
|
ButtonLayout::arrow_armed_arrow(TR::inputs__return.into()),
|
||||||
|
)
|
||||||
|
.with_icon(theme::ICON_ARROW_BACK_UP)
|
||||||
|
}),
|
||||||
PassphraseAction::Menu,
|
PassphraseAction::Menu,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
let ch = get_char(&self.current_category, choice_index);
|
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),
|
PassphraseAction::Character(ch),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: StringType + Clone> ChoiceFactory<T> for ChoiceFactoryPassphrase {
|
impl ChoiceFactory for ChoiceFactoryPassphrase {
|
||||||
type Action = PassphraseAction;
|
type Action = PassphraseAction;
|
||||||
type Item = ChoiceItem<T>;
|
type Item = ChoiceItem;
|
||||||
|
|
||||||
fn count(&self) -> usize {
|
fn count(&self) -> usize {
|
||||||
let length = get_category_length(&self.current_category);
|
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.
|
/// Component for entering a passphrase.
|
||||||
pub struct PassphraseEntry<T: StringType + Clone> {
|
pub struct PassphraseEntry {
|
||||||
choice_page: ChoicePage<ChoiceFactoryPassphrase, T, PassphraseAction>,
|
choice_page: ChoicePage<ChoiceFactoryPassphrase, PassphraseAction>,
|
||||||
passphrase_dots: Child<ChangingTextLine<String<MAX_PASSPHRASE_LENGTH>>>,
|
passphrase_dots: Child<ChangingTextLine<String<MAX_PASSPHRASE_LENGTH>>>,
|
||||||
show_plain_passphrase: bool,
|
show_plain_passphrase: bool,
|
||||||
show_last_digit: bool,
|
show_last_digit: bool,
|
||||||
@ -259,10 +276,7 @@ pub struct PassphraseEntry<T: StringType + Clone> {
|
|||||||
current_category: ChoiceCategory,
|
current_category: ChoiceCategory,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> PassphraseEntry<T>
|
impl PassphraseEntry {
|
||||||
where
|
|
||||||
T: StringType + Clone,
|
|
||||||
{
|
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
choice_page: ChoicePage::new(ChoiceFactoryPassphrase::new(ChoiceCategory::Menu, true))
|
choice_page: ChoicePage::new(ChoiceFactoryPassphrase::new(ChoiceCategory::Menu, true))
|
||||||
@ -353,10 +367,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Component for PassphraseEntry<T>
|
impl Component for PassphraseEntry {
|
||||||
where
|
|
||||||
T: StringType + Clone,
|
|
||||||
{
|
|
||||||
type Msg = CancelConfirmMsg;
|
type Msg = CancelConfirmMsg;
|
||||||
|
|
||||||
fn place(&mut self, bounds: Rect) -> Rect {
|
fn place(&mut self, bounds: Rect) -> Rect {
|
||||||
@ -442,21 +453,18 @@ where
|
|||||||
// DEBUG-ONLY SECTION BELOW
|
// DEBUG-ONLY SECTION BELOW
|
||||||
|
|
||||||
#[cfg(feature = "ui_debug")]
|
#[cfg(feature = "ui_debug")]
|
||||||
impl<T> crate::trace::Trace for PassphraseEntry<T>
|
impl crate::trace::Trace for PassphraseEntry {
|
||||||
where
|
|
||||||
T: StringType + Clone,
|
|
||||||
{
|
|
||||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
t.component("PassphraseKeyboard");
|
t.component("PassphraseKeyboard");
|
||||||
t.string("passphrase", self.textbox.content());
|
t.string("passphrase", self.textbox.content().into());
|
||||||
t.string(
|
t.string(
|
||||||
"current_category",
|
"current_category",
|
||||||
match self.current_category {
|
match self.current_category {
|
||||||
ChoiceCategory::Menu => "MENU",
|
ChoiceCategory::Menu => "MENU".into(),
|
||||||
ChoiceCategory::LowercaseLetter => MENU[LOWERCASE_INDEX].0,
|
ChoiceCategory::LowercaseLetter => MENU[LOWERCASE_INDEX].text,
|
||||||
ChoiceCategory::UppercaseLetter => MENU[UPPERCASE_INDEX].0,
|
ChoiceCategory::UppercaseLetter => MENU[UPPERCASE_INDEX].text,
|
||||||
ChoiceCategory::Digit => MENU[DIGITS_INDEX].0,
|
ChoiceCategory::Digit => MENU[DIGITS_INDEX].text,
|
||||||
ChoiceCategory::SpecialSymbol => MENU[SPECIAL_INDEX].0,
|
ChoiceCategory::SpecialSymbol => MENU[SPECIAL_INDEX].text,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
t.child("choice_page", &self.choice_page);
|
t.child("choice_page", &self.choice_page);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
strutil::StringType,
|
strutil::{StringType, TString},
|
||||||
|
translations::TR,
|
||||||
trezorhal::random,
|
trezorhal::random,
|
||||||
ui::{
|
ui::{
|
||||||
component::{text::common::TextBox, Child, Component, ComponentExt, Event, EventCtx},
|
component::{text::common::TextBox, Child, Component, ComponentExt, Event, EventCtx},
|
||||||
@ -22,27 +23,65 @@ enum PinAction {
|
|||||||
Digit(char),
|
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 MAX_PIN_LENGTH: usize = 50;
|
||||||
const EMPTY_PIN_STR: &str = "_";
|
const EMPTY_PIN_STR: &str = "_";
|
||||||
|
|
||||||
const CHOICE_LENGTH: usize = 13;
|
const CHOICE_LENGTH: usize = 13;
|
||||||
const NUMBER_START_INDEX: usize = 3;
|
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 should be triggerable without release (after long-press)
|
||||||
("DELETE", PinAction::Delete, Some(theme::ICON_DELETE), true),
|
PinChoice::new(
|
||||||
("SHOW", PinAction::Show, Some(theme::ICON_EYE), false),
|
TR::inputs__delete.as_tstring(),
|
||||||
("ENTER", PinAction::Enter, Some(theme::ICON_TICK), false),
|
PinAction::Delete,
|
||||||
("0", PinAction::Digit('0'), None, false),
|
Some(theme::ICON_DELETE),
|
||||||
("1", PinAction::Digit('1'), None, false),
|
true, // without_release
|
||||||
("2", PinAction::Digit('2'), None, false),
|
),
|
||||||
("3", PinAction::Digit('3'), None, false),
|
PinChoice::new(
|
||||||
("4", PinAction::Digit('4'), None, false),
|
TR::inputs__show.as_tstring(),
|
||||||
("5", PinAction::Digit('5'), None, false),
|
PinAction::Show,
|
||||||
("6", PinAction::Digit('6'), None, false),
|
Some(theme::ICON_EYE),
|
||||||
("7", PinAction::Digit('7'), None, false),
|
false,
|
||||||
("8", PinAction::Digit('8'), None, false),
|
),
|
||||||
("9", PinAction::Digit('9'), None, 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 {
|
fn get_random_digit_position() -> usize {
|
||||||
@ -51,32 +90,37 @@ fn get_random_digit_position() -> usize {
|
|||||||
|
|
||||||
struct ChoiceFactoryPIN;
|
struct ChoiceFactoryPIN;
|
||||||
|
|
||||||
impl<T: StringType + Clone> ChoiceFactory<T> for ChoiceFactoryPIN {
|
impl ChoiceFactory for ChoiceFactoryPIN {
|
||||||
type Action = PinAction;
|
type Action = PinAction;
|
||||||
type Item = ChoiceItem<T>;
|
type Item = ChoiceItem;
|
||||||
|
|
||||||
fn get(&self, choice_index: usize) -> (Self::Item, Self::Action) {
|
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
|
// Action buttons have different middle button text
|
||||||
if !matches!(action, PinAction::Digit(_)) {
|
if !matches!(choice.action, PinAction::Digit(_)) {
|
||||||
let confirm_btn = ButtonDetails::armed_text("CONFIRM".into());
|
let confirm_btn = ButtonDetails::armed_text(TR::buttons__confirm.into());
|
||||||
choice_item.set_middle_btn(Some(confirm_btn));
|
choice_item.set_middle_btn(Some(confirm_btn));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Making middle button create LongPress events
|
// Making middle button create LongPress events
|
||||||
if without_release {
|
if choice.without_release {
|
||||||
choice_item = choice_item.with_middle_action_without_release();
|
choice_item = choice_item.with_middle_action_without_release();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adding icons for appropriate items
|
// 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 = choice_item.with_icon(icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
(choice_item, action)
|
(choice_item, choice.action)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn count(&self) -> usize {
|
fn count(&self) -> usize {
|
||||||
@ -86,7 +130,7 @@ impl<T: StringType + Clone> ChoiceFactory<T> for ChoiceFactoryPIN {
|
|||||||
|
|
||||||
/// Component for entering a PIN.
|
/// Component for entering a PIN.
|
||||||
pub struct PinEntry<T: StringType + Clone> {
|
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>>>,
|
header_line: Child<ChangingTextLine<String<MAX_PIN_LENGTH>>>,
|
||||||
pin_line: Child<ChangingTextLine<String<MAX_PIN_LENGTH>>>,
|
pin_line: Child<ChangingTextLine<String<MAX_PIN_LENGTH>>>,
|
||||||
prompt: T,
|
prompt: T,
|
||||||
@ -111,7 +155,7 @@ where
|
|||||||
let (showing_real_prompt, header_line_content, pin_line_content) = if show_subprompt {
|
let (showing_real_prompt, header_line_content, pin_line_content) = if show_subprompt {
|
||||||
(
|
(
|
||||||
false,
|
false,
|
||||||
String::from("WRONG PIN"),
|
TR::pin__title_wrong_pin.map_translated(|t| String::from(t)),
|
||||||
String::from(subprompt.as_ref()),
|
String::from(subprompt.as_ref()),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@ -296,8 +340,8 @@ where
|
|||||||
{
|
{
|
||||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
t.component("PinKeyboard");
|
t.component("PinKeyboard");
|
||||||
t.string("subprompt", self.subprompt.as_ref());
|
t.string("subprompt", self.subprompt.as_ref().into());
|
||||||
t.string("pin", self.textbox.content());
|
t.string("pin", self.textbox.content().into());
|
||||||
t.child("choice_page", &self.choice_page);
|
t.child("choice_page", &self.choice_page);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
strutil::StringType,
|
strutil::TString,
|
||||||
|
translations::TR,
|
||||||
ui::{
|
ui::{
|
||||||
component::{Component, Event, EventCtx},
|
component::{Component, Event, EventCtx},
|
||||||
geometry::Rect,
|
geometry::Rect,
|
||||||
@ -13,24 +14,24 @@ use heapless::Vec;
|
|||||||
// as would be via `const N: usize` generics.
|
// as would be via `const N: usize` generics.
|
||||||
const MAX_LENGTH: usize = 5;
|
const MAX_LENGTH: usize = 5;
|
||||||
|
|
||||||
struct ChoiceFactorySimple<T: StringType> {
|
struct ChoiceFactorySimple {
|
||||||
choices: Vec<T, MAX_LENGTH>,
|
choices: Vec<TString<'static>, MAX_LENGTH>,
|
||||||
carousel: bool,
|
carousel: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: StringType> ChoiceFactorySimple<T> {
|
impl ChoiceFactorySimple {
|
||||||
fn new(choices: Vec<T, MAX_LENGTH>, carousel: bool) -> Self {
|
fn new(choices: Vec<TString<'static>, MAX_LENGTH>, carousel: bool) -> Self {
|
||||||
Self { choices, carousel }
|
Self { choices, carousel }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_string(&self, choice_index: usize) -> &str {
|
fn get_string(&self, choice_index: usize) -> TString<'static> {
|
||||||
self.choices[choice_index].as_ref()
|
self.choices[choice_index]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: StringType + Clone> ChoiceFactory<T> for ChoiceFactorySimple<T> {
|
impl ChoiceFactory for ChoiceFactorySimple {
|
||||||
type Action = usize;
|
type Action = usize;
|
||||||
type Item = ChoiceItem<T>;
|
type Item = ChoiceItem;
|
||||||
|
|
||||||
fn count(&self) -> usize {
|
fn count(&self) -> usize {
|
||||||
self.choices.len()
|
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) {
|
fn get(&self, choice_index: usize) -> (Self::Item, Self::Action) {
|
||||||
let text = &self.choices[choice_index];
|
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.
|
// 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)
|
// (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
|
/// Simple wrapper around `ChoicePage` that allows for
|
||||||
/// inputting a list of values and receiving the chosen one.
|
/// inputting a list of values and receiving the chosen one.
|
||||||
pub struct SimpleChoice<T>
|
pub struct SimpleChoice {
|
||||||
where
|
choice_page: ChoicePage<ChoiceFactorySimple, usize>,
|
||||||
T: StringType + Clone,
|
|
||||||
{
|
|
||||||
choice_page: ChoicePage<ChoiceFactorySimple<T>, T, usize>,
|
|
||||||
pub return_index: bool,
|
pub return_index: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> SimpleChoice<T>
|
impl SimpleChoice {
|
||||||
where
|
pub fn new(str_choices: Vec<TString<'static>, MAX_LENGTH>, carousel: bool) -> Self {
|
||||||
T: StringType + Clone,
|
|
||||||
{
|
|
||||||
pub fn new(str_choices: Vec<T, MAX_LENGTH>, carousel: bool) -> Self {
|
|
||||||
let choices = ChoiceFactorySimple::new(str_choices, carousel);
|
let choices = ChoiceFactorySimple::new(str_choices, carousel);
|
||||||
Self {
|
Self {
|
||||||
choice_page: ChoicePage::new(choices).with_carousel(carousel),
|
choice_page: ChoicePage::new(choices).with_carousel(carousel),
|
||||||
@ -96,15 +96,12 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Translating the resulting index into actual string choice.
|
/// 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)
|
self.choice_page.choice_factory().get_string(index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Component for SimpleChoice<T>
|
impl Component for SimpleChoice {
|
||||||
where
|
|
||||||
T: StringType + Clone,
|
|
||||||
{
|
|
||||||
type Msg = usize;
|
type Msg = usize;
|
||||||
|
|
||||||
fn place(&mut self, bounds: Rect) -> Rect {
|
fn place(&mut self, bounds: Rect) -> Rect {
|
||||||
@ -123,10 +120,7 @@ where
|
|||||||
// DEBUG-ONLY SECTION BELOW
|
// DEBUG-ONLY SECTION BELOW
|
||||||
|
|
||||||
#[cfg(feature = "ui_debug")]
|
#[cfg(feature = "ui_debug")]
|
||||||
impl<T> crate::trace::Trace for SimpleChoice<T>
|
impl crate::trace::Trace for SimpleChoice {
|
||||||
where
|
|
||||||
T: StringType + Clone,
|
|
||||||
{
|
|
||||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
t.component("SimpleChoice");
|
t.component("SimpleChoice");
|
||||||
t.child("choice_page", &self.choice_page);
|
t.child("choice_page", &self.choice_page);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
strutil::StringType,
|
translations::TR,
|
||||||
trezorhal::{random, wordlist::Wordlist},
|
trezorhal::{random, wordlist::Wordlist},
|
||||||
ui::{
|
ui::{
|
||||||
component::{text::common::TextBox, Child, Component, ComponentExt, Event, EventCtx},
|
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 Action = WordlistAction;
|
||||||
type Item = ChoiceItem<T>;
|
type Item = ChoiceItem;
|
||||||
|
|
||||||
fn count(&self) -> usize {
|
fn count(&self) -> usize {
|
||||||
// Accounting for the DELETE option (+1)
|
// 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)
|
// (is a requirement for WORDS, doing it for LETTERS as well to unite it)
|
||||||
if choice_index == DELETE_INDEX {
|
if choice_index == DELETE_INDEX {
|
||||||
return (
|
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_icon(theme::ICON_DELETE)
|
||||||
.with_middle_action_without_release(),
|
.with_middle_action_without_release()
|
||||||
|
}),
|
||||||
WordlistAction::Delete,
|
WordlistAction::Delete,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -105,7 +110,10 @@ impl<T: StringType + Clone> ChoiceFactory<T> for ChoiceFactoryWordlist {
|
|||||||
let index = self.word_random_order[choice_index - 1];
|
let index = self.word_random_order[choice_index - 1];
|
||||||
let word = self.wordlist.get(index).unwrap_or_default();
|
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),
|
WordlistAction::Word(word),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@ -115,7 +123,10 @@ impl<T: StringType + Clone> ChoiceFactory<T> for ChoiceFactoryWordlist {
|
|||||||
.nth(choice_index - 1)
|
.nth(choice_index - 1)
|
||||||
.unwrap_or_default();
|
.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),
|
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.
|
/// Component for entering a mnemonic from a wordlist - BIP39 or SLIP39.
|
||||||
pub struct WordlistEntry<T: StringType + Clone> {
|
pub struct WordlistEntry {
|
||||||
choice_page: ChoicePage<ChoiceFactoryWordlist, T, WordlistAction>,
|
choice_page: ChoicePage<ChoiceFactoryWordlist, WordlistAction>,
|
||||||
chosen_letters: Child<ChangingTextLine<String<{ MAX_WORD_LENGTH + 1 }>>>,
|
chosen_letters: Child<ChangingTextLine<String<{ MAX_WORD_LENGTH + 1 }>>>,
|
||||||
textbox: TextBox<MAX_WORD_LENGTH>,
|
textbox: TextBox<MAX_WORD_LENGTH>,
|
||||||
offer_words: bool,
|
offer_words: bool,
|
||||||
wordlist_type: WordlistType,
|
wordlist_type: WordlistType,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> WordlistEntry<T>
|
impl WordlistEntry {
|
||||||
where
|
|
||||||
T: StringType + Clone,
|
|
||||||
{
|
|
||||||
pub fn new(wordlist_type: WordlistType) -> Self {
|
pub fn new(wordlist_type: WordlistType) -> Self {
|
||||||
let choices = ChoiceFactoryWordlist::new(wordlist_type, "");
|
let choices = ChoiceFactoryWordlist::new(wordlist_type, "");
|
||||||
let choices_count = <ChoiceFactoryWordlist as ChoiceFactory<T>>::count(&choices);
|
let choices_count = <ChoiceFactoryWordlist as ChoiceFactory>::count(&choices);
|
||||||
Self {
|
Self {
|
||||||
// Starting at random letter position
|
// Starting at random letter position
|
||||||
choice_page: ChoicePage::new(choices)
|
choice_page: ChoicePage::new(choices)
|
||||||
@ -167,7 +175,7 @@ where
|
|||||||
if self.offer_words {
|
if self.offer_words {
|
||||||
INITIAL_PAGE_COUNTER
|
INITIAL_PAGE_COUNTER
|
||||||
} else {
|
} 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
|
// There should be always DELETE and at least one letter
|
||||||
assert!(choices_count > 1);
|
assert!(choices_count > 1);
|
||||||
if choices_count == 2 {
|
if choices_count == 2 {
|
||||||
@ -179,8 +187,7 @@ where
|
|||||||
loop {
|
loop {
|
||||||
let random_position = get_random_position(choices_count);
|
let random_position = get_random_position(choices_count);
|
||||||
let current_action =
|
let current_action =
|
||||||
<ChoiceFactoryWordlist as ChoiceFactory<T>>::get(new_choices, random_position)
|
<ChoiceFactoryWordlist as ChoiceFactory>::get(new_choices, random_position).1;
|
||||||
.1;
|
|
||||||
if let WordlistAction::Letter(current_letter) = current_action {
|
if let WordlistAction::Letter(current_letter) = current_action {
|
||||||
if let Some(last_letter) = self.get_last_textbox_letter() {
|
if let Some(last_letter) = self.get_last_textbox_letter() {
|
||||||
if current_letter == last_letter {
|
if current_letter == last_letter {
|
||||||
@ -217,10 +224,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Component for WordlistEntry<T>
|
impl Component for WordlistEntry {
|
||||||
where
|
|
||||||
T: StringType + Clone,
|
|
||||||
{
|
|
||||||
type Msg = &'static str;
|
type Msg = &'static str;
|
||||||
|
|
||||||
fn place(&mut self, bounds: Rect) -> Rect {
|
fn place(&mut self, bounds: Rect) -> Rect {
|
||||||
@ -264,13 +268,10 @@ where
|
|||||||
// DEBUG-ONLY SECTION BELOW
|
// DEBUG-ONLY SECTION BELOW
|
||||||
|
|
||||||
#[cfg(feature = "ui_debug")]
|
#[cfg(feature = "ui_debug")]
|
||||||
impl<T> crate::trace::Trace for WordlistEntry<T>
|
impl crate::trace::Trace for WordlistEntry {
|
||||||
where
|
|
||||||
T: StringType + Clone,
|
|
||||||
{
|
|
||||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
t.component("MnemonicKeyboard"); // unified with TT
|
t.component("MnemonicKeyboard"); // unified with TT
|
||||||
t.string("textbox", self.textbox.content());
|
t.string("textbox", self.textbox.content().into());
|
||||||
|
|
||||||
if self.offer_words {
|
if self.offer_words {
|
||||||
t.bool("word_choices", true);
|
t.bool("word_choices", true);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
strutil::StringType,
|
strutil::{StringType, TString},
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
ui::{
|
ui::{
|
||||||
animation::Animation,
|
animation::Animation,
|
||||||
@ -28,25 +28,19 @@ enum State {
|
|||||||
Grown,
|
Grown,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Loader<T>
|
pub struct Loader {
|
||||||
where
|
|
||||||
T: StringType,
|
|
||||||
{
|
|
||||||
area: Rect,
|
area: Rect,
|
||||||
state: State,
|
state: State,
|
||||||
growing_duration: Duration,
|
growing_duration: Duration,
|
||||||
shrinking_duration: Duration,
|
shrinking_duration: Duration,
|
||||||
text_overlay: display::TextOverlay<T>,
|
text_overlay: display::TextOverlay,
|
||||||
styles: LoaderStyleSheet,
|
styles: LoaderStyleSheet,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Loader<T>
|
impl Loader {
|
||||||
where
|
|
||||||
T: StringType,
|
|
||||||
{
|
|
||||||
pub const SIZE: Offset = Offset::new(120, 120);
|
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 {
|
Self {
|
||||||
area: Rect::zero(),
|
area: Rect::zero(),
|
||||||
state: State::Initial,
|
state: State::Initial,
|
||||||
@ -57,8 +51,8 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn text(text: T, styles: LoaderStyleSheet) -> Self {
|
pub fn text<T: Into<TString<'static>>>(text: T, styles: LoaderStyleSheet) -> Self {
|
||||||
let text_overlay = display::TextOverlay::new(text, styles.normal.font);
|
let text_overlay = display::TextOverlay::new(text.into(), styles.normal.font);
|
||||||
|
|
||||||
Self::new(text_overlay, styles)
|
Self::new(text_overlay, styles)
|
||||||
}
|
}
|
||||||
@ -77,18 +71,18 @@ where
|
|||||||
self.growing_duration
|
self.growing_duration
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_text(&self) -> &T {
|
pub fn get_text(&self) -> TString<'static> {
|
||||||
self.text_overlay.get_text()
|
self.text_overlay.get_text()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Change the text of the loader.
|
/// Change the text of the loader.
|
||||||
pub fn set_text(&mut self, text: T) {
|
pub fn set_text<T: Into<TString<'static>>>(&mut self, text: T) {
|
||||||
self.text_overlay.set_text(text);
|
self.text_overlay.set_text(text.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return width of given text according to current style.
|
/// Return width of given text according to current style.
|
||||||
pub fn get_text_width(&self, text: &T) -> i16 {
|
pub fn get_text_width(&self, text: &TString<'static>) -> i16 {
|
||||||
self.styles.normal.font.text_width(text.as_ref())
|
text.map(|t| self.styles.normal.font.text_width(t))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_growing(&mut self, ctx: &mut EventCtx, now: Instant) {
|
pub fn start_growing(&mut self, ctx: &mut EventCtx, now: Instant) {
|
||||||
@ -172,10 +166,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Component for Loader<T>
|
impl Component for Loader {
|
||||||
where
|
|
||||||
T: StringType,
|
|
||||||
{
|
|
||||||
type Msg = LoaderMsg;
|
type Msg = LoaderMsg;
|
||||||
|
|
||||||
fn place(&mut self, bounds: Rect) -> Rect {
|
fn place(&mut self, bounds: Rect) -> Rect {
|
||||||
@ -346,13 +337,10 @@ where
|
|||||||
// DEBUG-ONLY SECTION BELOW
|
// DEBUG-ONLY SECTION BELOW
|
||||||
|
|
||||||
#[cfg(feature = "ui_debug")]
|
#[cfg(feature = "ui_debug")]
|
||||||
impl<T> crate::trace::Trace for Loader<T>
|
impl crate::trace::Trace for Loader {
|
||||||
where
|
|
||||||
T: StringType,
|
|
||||||
{
|
|
||||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
t.component("Loader");
|
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);
|
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 result::ResultScreen;
|
||||||
pub use welcome_screen::WelcomeScreen;
|
pub use welcome_screen::WelcomeScreen;
|
||||||
|
|
||||||
|
#[cfg(feature = "translations")]
|
||||||
mod address_details;
|
mod address_details;
|
||||||
mod changing_text;
|
mod changing_text;
|
||||||
|
#[cfg(feature = "translations")]
|
||||||
mod coinjoin_progress;
|
mod coinjoin_progress;
|
||||||
mod flow;
|
mod flow;
|
||||||
mod flow_pages;
|
mod flow_pages;
|
||||||
mod frame;
|
mod frame;
|
||||||
#[cfg(feature = "micropython")]
|
#[cfg(feature = "micropython")]
|
||||||
mod homescreen;
|
mod homescreen;
|
||||||
|
#[cfg(feature = "translations")]
|
||||||
mod page;
|
mod page;
|
||||||
mod progress;
|
mod progress;
|
||||||
mod result_anim;
|
mod result_anim;
|
||||||
mod result_popup;
|
mod result_popup;
|
||||||
mod scrollbar;
|
mod scrollbar;
|
||||||
|
#[cfg(feature = "translations")]
|
||||||
mod share_words;
|
mod share_words;
|
||||||
mod show_more;
|
mod show_more;
|
||||||
mod title;
|
mod title;
|
||||||
|
|
||||||
|
#[cfg(feature = "translations")]
|
||||||
pub use address_details::AddressDetails;
|
pub use address_details::AddressDetails;
|
||||||
|
|
||||||
pub use changing_text::ChangingTextLine;
|
pub use changing_text::ChangingTextLine;
|
||||||
|
#[cfg(feature = "translations")]
|
||||||
pub use coinjoin_progress::CoinJoinProgress;
|
pub use coinjoin_progress::CoinJoinProgress;
|
||||||
pub use flow::Flow;
|
pub use flow::Flow;
|
||||||
pub use flow_pages::{FlowPages, Page};
|
pub use flow_pages::{FlowPages, Page};
|
||||||
pub use frame::{Frame, ScrollableContent, ScrollableFrame};
|
pub use frame::{Frame, ScrollableContent, ScrollableFrame};
|
||||||
#[cfg(feature = "micropython")]
|
#[cfg(feature = "micropython")]
|
||||||
pub use homescreen::{check_homescreen_format, ConfirmHomescreen, Homescreen, Lockscreen};
|
pub use homescreen::{check_homescreen_format, ConfirmHomescreen, Homescreen, Lockscreen};
|
||||||
|
#[cfg(feature = "translations")]
|
||||||
pub use input_methods::{
|
pub use input_methods::{
|
||||||
number_input::NumberInput,
|
number_input::NumberInput,
|
||||||
passphrase::PassphraseEntry,
|
passphrase::PassphraseEntry,
|
||||||
@ -59,10 +66,12 @@ pub use input_methods::{
|
|||||||
simple_choice::SimpleChoice,
|
simple_choice::SimpleChoice,
|
||||||
wordlist::{WordlistEntry, WordlistType},
|
wordlist::{WordlistEntry, WordlistType},
|
||||||
};
|
};
|
||||||
|
#[cfg(feature = "translations")]
|
||||||
pub use page::ButtonPage;
|
pub use page::ButtonPage;
|
||||||
pub use progress::Progress;
|
pub use progress::Progress;
|
||||||
pub use result_anim::{ResultAnim, ResultAnimMsg};
|
pub use result_anim::{ResultAnim, ResultAnimMsg};
|
||||||
pub use result_popup::{ResultPopup, ResultPopupMsg};
|
pub use result_popup::{ResultPopup, ResultPopupMsg};
|
||||||
pub use scrollbar::ScrollBar;
|
pub use scrollbar::ScrollBar;
|
||||||
|
#[cfg(feature = "translations")]
|
||||||
pub use share_words::ShareWords;
|
pub use share_words::ShareWords;
|
||||||
pub use show_more::{CancelInfoConfirmMsg, ShowMore};
|
pub use show_more::{CancelInfoConfirmMsg, ShowMore};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
strutil::StringType,
|
translations::TR,
|
||||||
ui::{
|
ui::{
|
||||||
component::{Child, Component, ComponentExt, Event, EventCtx, Pad, PageMsg, Paginate},
|
component::{Child, Component, ComponentExt, Event, EventCtx, Pad, PageMsg, Paginate},
|
||||||
display::Color,
|
display::Color,
|
||||||
@ -12,30 +12,28 @@ use super::{
|
|||||||
ButtonDetails, ButtonLayout, ButtonPos,
|
ButtonDetails, ButtonLayout, ButtonPos,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct ButtonPage<T, U>
|
pub struct ButtonPage<T>
|
||||||
where
|
where
|
||||||
T: Component + Paginate,
|
T: Component + Paginate,
|
||||||
U: StringType,
|
|
||||||
{
|
{
|
||||||
page_count: usize,
|
page_count: usize,
|
||||||
active_page: usize,
|
active_page: usize,
|
||||||
content: Child<T>,
|
content: Child<T>,
|
||||||
pad: Pad,
|
pad: Pad,
|
||||||
/// Left button of the first screen
|
/// Left button of the first screen
|
||||||
cancel_btn_details: Option<ButtonDetails<U>>,
|
cancel_btn_details: Option<ButtonDetails>,
|
||||||
/// Right button of the last screen
|
/// Right button of the last screen
|
||||||
confirm_btn_details: Option<ButtonDetails<U>>,
|
confirm_btn_details: Option<ButtonDetails>,
|
||||||
/// Left button of every screen
|
/// 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
|
/// Right button of every screen apart the last one
|
||||||
next_btn_details: Option<ButtonDetails<U>>,
|
next_btn_details: Option<ButtonDetails>,
|
||||||
buttons: Child<ButtonController<U>>,
|
buttons: Child<ButtonController>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, U> ButtonPage<T, U>
|
impl<T> ButtonPage<T>
|
||||||
where
|
where
|
||||||
T: Component + Paginate,
|
T: Component + Paginate,
|
||||||
U: StringType + Clone,
|
|
||||||
{
|
{
|
||||||
pub fn new(content: T, background: Color) -> Self {
|
pub fn new(content: T, background: Color) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -44,7 +42,7 @@ where
|
|||||||
content: Child::new(content),
|
content: Child::new(content),
|
||||||
pad: Pad::with_background(background).with_clear(),
|
pad: Pad::with_background(background).with_clear(),
|
||||||
cancel_btn_details: Some(ButtonDetails::cancel_icon()),
|
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()),
|
back_btn_details: Some(ButtonDetails::up_arrow_icon()),
|
||||||
next_btn_details: Some(ButtonDetails::down_arrow_icon_wide()),
|
next_btn_details: Some(ButtonDetails::down_arrow_icon_wide()),
|
||||||
// Setting empty layout for now, we do not yet know the page count.
|
// 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.cancel_btn_details = btn_details;
|
||||||
self
|
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.confirm_btn_details = btn_details;
|
||||||
self
|
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.back_btn_details = btn_details;
|
||||||
self
|
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.next_btn_details = btn_details;
|
||||||
self
|
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_left = self.get_left_button_details(!has_prev);
|
||||||
let btn_right = self.get_right_button_details(has_next);
|
let btn_right = self.get_right_button_details(has_next);
|
||||||
ButtonLayout::new(btn_left, None, btn_right)
|
ButtonLayout::new(btn_left, None, btn_right)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the left button details, depending whether the page is first or not.
|
/// 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 {
|
if is_first {
|
||||||
self.cancel_btn_details.clone()
|
self.cancel_btn_details.clone()
|
||||||
} else {
|
} else {
|
||||||
@ -135,7 +133,7 @@ where
|
|||||||
|
|
||||||
/// Get the right button details, depending on whether there is a next
|
/// Get the right button details, depending on whether there is a next
|
||||||
/// page.
|
/// 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 {
|
if has_next_page {
|
||||||
self.next_btn_details.clone()
|
self.next_btn_details.clone()
|
||||||
} else {
|
} else {
|
||||||
@ -144,10 +142,9 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, U> ScrollableContent for ButtonPage<T, U>
|
impl<T> ScrollableContent for ButtonPage<T>
|
||||||
where
|
where
|
||||||
T: Component + Paginate,
|
T: Component + Paginate,
|
||||||
U: StringType,
|
|
||||||
{
|
{
|
||||||
fn page_count(&self) -> usize {
|
fn page_count(&self) -> usize {
|
||||||
self.page_count
|
self.page_count
|
||||||
@ -157,10 +154,9 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, U> Component for ButtonPage<T, U>
|
impl<T> Component for ButtonPage<T>
|
||||||
where
|
where
|
||||||
T: Component + Paginate,
|
T: Component + Paginate,
|
||||||
U: StringType + Clone,
|
|
||||||
{
|
{
|
||||||
type Msg = PageMsg<T::Msg>;
|
type Msg = PageMsg<T::Msg>;
|
||||||
|
|
||||||
@ -224,10 +220,9 @@ where
|
|||||||
// DEBUG-ONLY SECTION BELOW
|
// DEBUG-ONLY SECTION BELOW
|
||||||
|
|
||||||
#[cfg(feature = "ui_debug")]
|
#[cfg(feature = "ui_debug")]
|
||||||
impl<T, U> crate::trace::Trace for ButtonPage<T, U>
|
impl<T> crate::trace::Trace for ButtonPage<T>
|
||||||
where
|
where
|
||||||
T: crate::trace::Trace + Paginate + Component,
|
T: crate::trace::Trace + Paginate + Component,
|
||||||
U: StringType + Clone,
|
|
||||||
{
|
{
|
||||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
t.component("ButtonPage");
|
t.component("ButtonPage");
|
||||||
|
@ -30,7 +30,7 @@ where
|
|||||||
result_anim: Child<ResultAnim>,
|
result_anim: Child<ResultAnim>,
|
||||||
headline: Option<Label<&'static str>>,
|
headline: Option<Label<&'static str>>,
|
||||||
text: Child<Paragraphs<Paragraph<T>>>,
|
text: Child<Paragraphs<Paragraph<T>>>,
|
||||||
buttons: Option<Child<ButtonController<T>>>,
|
buttons: Option<Child<ButtonController>>,
|
||||||
autoclose: bool,
|
autoclose: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
strutil::StringType,
|
strutil::StringType,
|
||||||
|
translations::TR,
|
||||||
ui::{
|
ui::{
|
||||||
component::{
|
component::{
|
||||||
text::util::text_multiline, Child, Component, Event, EventCtx, Never, Paginate,
|
text::util::text_multiline, Child, Component, Event, EventCtx, Never, Paginate,
|
||||||
@ -69,19 +70,24 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_final_text(&self) -> String<50> {
|
fn get_final_text(&self) -> String<50> {
|
||||||
build_string!(
|
TR::share_words__wrote_down_all.map_translated(|wrote_down_all| {
|
||||||
50,
|
TR::share_words__words_in_order.map_translated(|in_order| {
|
||||||
"I wrote down all ",
|
build_string!(
|
||||||
inttostr!(self.share_words.len() as u8),
|
50,
|
||||||
" words in order."
|
wrote_down_all,
|
||||||
)
|
inttostr!(self.share_words.len() as u8),
|
||||||
|
in_order
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Display the final page with user confirmation.
|
/// Display the final page with user confirmation.
|
||||||
fn paint_final_page(&mut self) {
|
fn paint_final_page(&mut self) {
|
||||||
|
let final_text = self.get_final_text();
|
||||||
text_multiline(
|
text_multiline(
|
||||||
self.area.split_top(INFO_TOP_OFFSET).1,
|
self.area.split_top(INFO_TOP_OFFSET).1,
|
||||||
&self.get_final_text(),
|
final_text.as_str().into(),
|
||||||
Font::NORMAL,
|
Font::NORMAL,
|
||||||
theme::FG,
|
theme::FG,
|
||||||
theme::BG,
|
theme::BG,
|
||||||
@ -103,7 +109,7 @@ where
|
|||||||
let baseline = self.area.top_left() + Offset::y(y_offset);
|
let baseline = self.area.top_left() + Offset::y(y_offset);
|
||||||
let ordinal = build_string!(5, inttostr!(index as u8 + 1), ".");
|
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(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
|
content
|
||||||
};
|
};
|
||||||
t.string("screen_content", &content);
|
t.string("screen_content", content.as_str().into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
strutil::StringType,
|
strutil::TString,
|
||||||
ui::{
|
ui::{
|
||||||
component::{Child, Component, Event, EventCtx},
|
component::{Child, Component, Event, EventCtx},
|
||||||
geometry::{Insets, Rect},
|
geometry::{Insets, Rect},
|
||||||
@ -14,20 +14,20 @@ pub enum CancelInfoConfirmMsg {
|
|||||||
Confirmed,
|
Confirmed,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ShowMore<T, U>
|
pub struct ShowMore<T> {
|
||||||
where
|
|
||||||
U: StringType,
|
|
||||||
{
|
|
||||||
content: Child<T>,
|
content: Child<T>,
|
||||||
buttons: Child<ButtonController<U>>,
|
buttons: Child<ButtonController>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, U> ShowMore<T, U>
|
impl<T> ShowMore<T>
|
||||||
where
|
where
|
||||||
T: Component,
|
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 {
|
let btn_layout = if let Some(cancel_text) = cancel_button {
|
||||||
ButtonLayout::text_armed_info(cancel_text, button)
|
ButtonLayout::text_armed_info(cancel_text, button)
|
||||||
} else {
|
} else {
|
||||||
@ -40,10 +40,9 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, U> Component for ShowMore<T, U>
|
impl<T> Component for ShowMore<T>
|
||||||
where
|
where
|
||||||
T: Component,
|
T: Component,
|
||||||
U: StringType + Clone,
|
|
||||||
{
|
{
|
||||||
type Msg = CancelInfoConfirmMsg;
|
type Msg = CancelInfoConfirmMsg;
|
||||||
|
|
||||||
@ -83,10 +82,9 @@ where
|
|||||||
// DEBUG-ONLY SECTION BELOW
|
// DEBUG-ONLY SECTION BELOW
|
||||||
|
|
||||||
#[cfg(feature = "ui_debug")]
|
#[cfg(feature = "ui_debug")]
|
||||||
impl<T, U> crate::trace::Trace for ShowMore<T, U>
|
impl<T> crate::trace::Trace for ShowMore<T>
|
||||||
where
|
where
|
||||||
T: crate::trace::Trace + Component,
|
T: crate::trace::Trace + Component,
|
||||||
U: StringType + Clone,
|
|
||||||
{
|
{
|
||||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
t.component("ShowMore");
|
t.component("ShowMore");
|
||||||
|
@ -127,6 +127,6 @@ where
|
|||||||
{
|
{
|
||||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
t.component("Title");
|
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 {
|
impl crate::trace::Trace for WelcomeScreen {
|
||||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
t.component("WelcomeScreen");
|
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::{
|
use crate::{
|
||||||
error::Error,
|
error::Error,
|
||||||
|
micropython::buffer::StrBuffer,
|
||||||
strutil::StringType,
|
strutil::StringType,
|
||||||
|
translations::TR,
|
||||||
ui::{
|
ui::{
|
||||||
component::{
|
component::{
|
||||||
text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs, VecExt},
|
text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs, VecExt},
|
||||||
@ -18,7 +20,7 @@ const MAX_XPUBS: usize = 16;
|
|||||||
|
|
||||||
pub struct AddressDetails<T> {
|
pub struct AddressDetails<T> {
|
||||||
qr_code: Frame<Qr, 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>,
|
xpub_view: Frame<Paragraphs<Paragraph<T>>, T>,
|
||||||
xpubs: Vec<(T, T), MAX_XPUBS>,
|
xpubs: Vec<(T, T), MAX_XPUBS>,
|
||||||
xpub_page_count: Vec<u8, MAX_XPUBS>,
|
xpub_page_count: Vec<u8, MAX_XPUBS>,
|
||||||
@ -34,21 +36,24 @@ where
|
|||||||
qr_address: T,
|
qr_address: T,
|
||||||
case_sensitive: bool,
|
case_sensitive: bool,
|
||||||
details_title: T,
|
details_title: T,
|
||||||
account: Option<T>,
|
account: Option<StrBuffer>,
|
||||||
path: Option<T>,
|
path: Option<StrBuffer>,
|
||||||
) -> Result<Self, Error>
|
) -> Result<Self, Error>
|
||||||
where
|
where
|
||||||
T: From<&'static str>,
|
T: From<&'static str>,
|
||||||
{
|
{
|
||||||
let mut para = ParagraphVecShort::new();
|
let mut para = ParagraphVecShort::new();
|
||||||
if let Some(a) = account {
|
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));
|
para.add(Paragraph::new(&theme::TEXT_MONO, a));
|
||||||
}
|
}
|
||||||
if let Some(p) = path {
|
if let Some(p) = path {
|
||||||
para.add(Paragraph::new(
|
para.add(Paragraph::new(
|
||||||
&theme::TEXT_NORMAL,
|
&theme::TEXT_NORMAL,
|
||||||
"Derivation path:".into(),
|
TR::address_details__derivation_path.try_into()?,
|
||||||
));
|
));
|
||||||
para.add(Paragraph::new(&theme::TEXT_MONO, p));
|
para.add(Paragraph::new(&theme::TEXT_MONO, p));
|
||||||
}
|
}
|
||||||
|
@ -50,8 +50,8 @@ pub struct Confirm<T> {
|
|||||||
title: ConfirmTitle<T>,
|
title: ConfirmTitle<T>,
|
||||||
message: Child<Label<T>>,
|
message: Child<Label<T>>,
|
||||||
alert: Option<Child<Label<T>>>,
|
alert: Option<Child<Label<T>>>,
|
||||||
left_button: Child<Button<&'static str>>,
|
left_button: Child<Button<T>>,
|
||||||
right_button: Child<Button<&'static str>>,
|
right_button: Child<Button<T>>,
|
||||||
info: Option<ConfirmInfo<T>>,
|
info: Option<ConfirmInfo<T>>,
|
||||||
show_info: bool,
|
show_info: bool,
|
||||||
}
|
}
|
||||||
@ -62,8 +62,8 @@ where
|
|||||||
{
|
{
|
||||||
pub fn new(
|
pub fn new(
|
||||||
bg_color: Color,
|
bg_color: Color,
|
||||||
left_button: Button<&'static str>,
|
left_button: Button<T>,
|
||||||
right_button: Button<&'static str>,
|
right_button: Button<T>,
|
||||||
title: ConfirmTitle<T>,
|
title: ConfirmTitle<T>,
|
||||||
message: Label<T>,
|
message: Label<T>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
@ -329,10 +329,10 @@ where
|
|||||||
t.component("Button");
|
t.component("Button");
|
||||||
match &self.content {
|
match &self.content {
|
||||||
ButtonContent::Empty => {}
|
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::Icon(_) => t.bool("icon", true),
|
||||||
ButtonContent::IconAndText(content) => {
|
ButtonContent::IconAndText(content) => {
|
||||||
t.string("text", content.text);
|
t.string("text", content.text.into());
|
||||||
t.bool("icon", true);
|
t.bool("icon", true);
|
||||||
}
|
}
|
||||||
ButtonContent::IconBlend(_, _, _) => 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