From 5315527d6d793ea9757c815fb4b51531043439e8 Mon Sep 17 00:00:00 2001 From: Nicolas Roggeman Date: Mon, 3 Feb 2025 16:41:18 +0100 Subject: [PATCH] Add screenshots generation feature in SDK --- Makefile | 8 +- lib_nbgl/src/nbgl_obj.c | 3 - tests/screenshots/Makefile | 299 +++++ tests/screenshots/README.md | 42 + tests/screenshots/flex.properties | 16 + .../wallet/app-sdk/flow_address_verif.json | 123 ++ .../app-sdk/flow_app_home_settings.json | 108 ++ .../wallet/app-sdk/flow_tx_msg_signing.json | 366 ++++++ .../flows/wallet/app-sdk/flow_w3c_blind.json | 186 +++ tests/screenshots/hexbitmap2c.py | 59 + tests/screenshots/launch_screenshots.py | 324 +++++ tests/screenshots/nanosp.properties | 11 + tests/screenshots/nanox.properties | 12 + .../src/app/nano/app_bitcoin_start.c | 87 ++ .../src/app/nano/app_ethereum_settings.c | 66 + .../src/app/nano/app_ethereum_start.c | 88 ++ .../src/app/nano/app_ethereum_verify_addr.c | 55 + tests/screenshots/src/app/nano/apps_api.h | 31 + .../src/app/nano/glyphs/bitcoin_logo.gif | Bin 0 -> 68 bytes .../src/app/nano/glyphs/nanox_app_bitcoin.gif | Bin 0 -> 1122 bytes .../app/nano/glyphs/nanox_app_ethereum.gif | Bin 0 -> 1116 bytes .../src/app/wallet/app_algorand_start.c | 154 +++ .../src/app/wallet/app_bitcoin_info.c | 56 + .../src/app/wallet/app_bitcoin_start.c | 85 ++ .../src/app/wallet/app_bitcoin_verify_addr.c | 61 + .../src/app/wallet/app_cardano_start.c | 116 ++ .../src/app/wallet/app_cardano_verify_addr.c | 75 ++ .../src/app/wallet/app_dogecoin_start.c | 162 +++ .../src/app/wallet/app_ethereum_settings.c | 85 ++ .../app/wallet/app_ethereum_share_address.c | 47 + .../src/app/wallet/app_ethereum_start.c | 153 +++ .../src/app/wallet/app_ethereum_verify_addr.c | 69 + .../src/app/wallet/app_ledger_market.c | 62 + .../src/app/wallet/app_monero_settings.c | 108 ++ .../src/app/wallet/app_monero_start.c | 185 +++ .../src/app/wallet/app_recovery_check_start.c | 188 +++ .../src/app/wallet/app_stellar_start.c | 143 +++ .../src/app/wallet/app_tron_settings.c | 190 +++ .../src/app/wallet/app_tron_start.c | 45 + .../src/app/wallet/app_xrp_start.c | 194 +++ tests/screenshots/src/app/wallet/apps_api.h | 59 + .../src/app/wallet/glyphs/Algorand32px.bmp | Bin 0 -> 3126 bytes .../app/wallet/glyphs/LedgerMarket32px.bmp | Bin 0 -> 3126 bytes .../src/app/wallet/glyphs/Monero32px.bmp | Bin 0 -> 4150 bytes .../app/wallet/glyphs/RecoveryCheck32px.bmp | Bin 0 -> 3126 bytes .../app/wallet/glyphs/Recovery_Check_64px.png | Bin 0 -> 818 bytes .../src/app/wallet/glyphs/Tron32px.bmp | Bin 0 -> 3126 bytes .../src/app/wallet/glyphs/ada_32px.png | Bin 0 -> 568 bytes .../src/app/wallet/glyphs/ada_40px.png | Bin 0 -> 649 bytes .../src/app/wallet/glyphs/bnb_32px.png | Bin 0 -> 337 bytes .../src/app/wallet/glyphs/bnb_40px.png | Bin 0 -> 367 bytes .../src/app/wallet/glyphs/btc_32px.png | Bin 0 -> 656 bytes .../src/app/wallet/glyphs/btc_40px.png | Bin 0 -> 836 bytes .../src/app/wallet/glyphs/cardano_32px.png | Bin 0 -> 568 bytes .../src/app/wallet/glyphs/doge_32px.png | Bin 0 -> 460 bytes .../src/app/wallet/glyphs/doge_40px.png | Bin 0 -> 498 bytes .../src/app/wallet/glyphs/dot_32px.png | Bin 0 -> 494 bytes .../src/app/wallet/glyphs/dot_40px.png | Bin 0 -> 582 bytes .../src/app/wallet/glyphs/eth_32px.png | Bin 0 -> 872 bytes .../src/app/wallet/glyphs/eth_40px.png | Bin 0 -> 1072 bytes .../src/app/wallet/glyphs/ic_asset_bnb_64.png | Bin 0 -> 593 bytes .../src/app/wallet/glyphs/ic_asset_btc_64.png | Bin 0 -> 1257 bytes .../app/wallet/glyphs/ic_asset_cardano_64.png | Bin 0 -> 1041 bytes .../app/wallet/glyphs/ic_asset_doge_64.png | Bin 0 -> 1318 bytes .../src/app/wallet/glyphs/ic_asset_eth_64.png | Bin 0 -> 1532 bytes .../app/wallet/glyphs/ic_asset_monero_64.png | Bin 0 -> 860 bytes .../app/wallet/glyphs/ic_asset_stellar_64.png | Bin 0 -> 846 bytes .../src/app/wallet/glyphs/ltc_40px.png | Bin 0 -> 606 bytes .../src/app/wallet/glyphs/stellar_32px.png | Bin 0 -> 447 bytes .../src/app/wallet/glyphs/stellar_40px.png | Bin 0 -> 567 bytes .../src/app/wallet/glyphs/xrp_32px.png | Bin 0 -> 369 bytes .../src/app/wallet/glyphs/xrp_40px.png | Bin 0 -> 433 bytes tests/screenshots/src/main/json_scenario.c | 910 ++++++++++++++ tests/screenshots/src/main/json_scenario.h | 147 +++ tests/screenshots/src/main/main.c | 160 +++ tests/screenshots/src/main/nbgl_driver.c | 1114 +++++++++++++++++ tests/screenshots/src/main/nbgl_driver.h | 101 ++ tests/screenshots/src/main/nbgl_front.c | 141 +++ tests/screenshots/src/main/png.c | 164 +++ tests/screenshots/src/main/properties.c | 171 +++ tests/screenshots/src/main/properties.h | 24 + tests/screenshots/src/main/stubs.c | 294 +++++ tests/screenshots/src/uzlib/adler32.c | 103 ++ tests/screenshots/src/uzlib/crc32.c | 72 ++ tests/screenshots/src/uzlib/defl_static.c | 310 +++++ tests/screenshots/src/uzlib/defl_static.h | 37 + tests/screenshots/src/uzlib/genlz77.c | 127 ++ tests/screenshots/src/uzlib/tinf.h | 3 + tests/screenshots/src/uzlib/tinf_compat.h | 9 + tests/screenshots/src/uzlib/tinfgzip.c | 122 ++ tests/screenshots/src/uzlib/tinflate.c | 685 ++++++++++ tests/screenshots/src/uzlib/tinfzlib.c | 74 ++ tests/screenshots/src/uzlib/uzlib.h | 177 +++ tests/screenshots/src/uzlib/uzlib_conf.h | 32 + tests/screenshots/stax.properties | 13 + 95 files changed, 9104 insertions(+), 7 deletions(-) create mode 100644 tests/screenshots/Makefile create mode 100644 tests/screenshots/README.md create mode 100644 tests/screenshots/flex.properties create mode 100644 tests/screenshots/flows/wallet/app-sdk/flow_address_verif.json create mode 100644 tests/screenshots/flows/wallet/app-sdk/flow_app_home_settings.json create mode 100644 tests/screenshots/flows/wallet/app-sdk/flow_tx_msg_signing.json create mode 100644 tests/screenshots/flows/wallet/app-sdk/flow_w3c_blind.json create mode 100755 tests/screenshots/hexbitmap2c.py create mode 100755 tests/screenshots/launch_screenshots.py create mode 100644 tests/screenshots/nanosp.properties create mode 100644 tests/screenshots/nanox.properties create mode 100644 tests/screenshots/src/app/nano/app_bitcoin_start.c create mode 100644 tests/screenshots/src/app/nano/app_ethereum_settings.c create mode 100644 tests/screenshots/src/app/nano/app_ethereum_start.c create mode 100644 tests/screenshots/src/app/nano/app_ethereum_verify_addr.c create mode 100644 tests/screenshots/src/app/nano/apps_api.h create mode 100644 tests/screenshots/src/app/nano/glyphs/bitcoin_logo.gif create mode 100755 tests/screenshots/src/app/nano/glyphs/nanox_app_bitcoin.gif create mode 100644 tests/screenshots/src/app/nano/glyphs/nanox_app_ethereum.gif create mode 100644 tests/screenshots/src/app/wallet/app_algorand_start.c create mode 100644 tests/screenshots/src/app/wallet/app_bitcoin_info.c create mode 100644 tests/screenshots/src/app/wallet/app_bitcoin_start.c create mode 100644 tests/screenshots/src/app/wallet/app_bitcoin_verify_addr.c create mode 100644 tests/screenshots/src/app/wallet/app_cardano_start.c create mode 100644 tests/screenshots/src/app/wallet/app_cardano_verify_addr.c create mode 100644 tests/screenshots/src/app/wallet/app_dogecoin_start.c create mode 100644 tests/screenshots/src/app/wallet/app_ethereum_settings.c create mode 100644 tests/screenshots/src/app/wallet/app_ethereum_share_address.c create mode 100644 tests/screenshots/src/app/wallet/app_ethereum_start.c create mode 100644 tests/screenshots/src/app/wallet/app_ethereum_verify_addr.c create mode 100644 tests/screenshots/src/app/wallet/app_ledger_market.c create mode 100644 tests/screenshots/src/app/wallet/app_monero_settings.c create mode 100644 tests/screenshots/src/app/wallet/app_monero_start.c create mode 100644 tests/screenshots/src/app/wallet/app_recovery_check_start.c create mode 100644 tests/screenshots/src/app/wallet/app_stellar_start.c create mode 100644 tests/screenshots/src/app/wallet/app_tron_settings.c create mode 100644 tests/screenshots/src/app/wallet/app_tron_start.c create mode 100644 tests/screenshots/src/app/wallet/app_xrp_start.c create mode 100644 tests/screenshots/src/app/wallet/apps_api.h create mode 100755 tests/screenshots/src/app/wallet/glyphs/Algorand32px.bmp create mode 100755 tests/screenshots/src/app/wallet/glyphs/LedgerMarket32px.bmp create mode 100755 tests/screenshots/src/app/wallet/glyphs/Monero32px.bmp create mode 100755 tests/screenshots/src/app/wallet/glyphs/RecoveryCheck32px.bmp create mode 100755 tests/screenshots/src/app/wallet/glyphs/Recovery_Check_64px.png create mode 100755 tests/screenshots/src/app/wallet/glyphs/Tron32px.bmp create mode 100755 tests/screenshots/src/app/wallet/glyphs/ada_32px.png create mode 100755 tests/screenshots/src/app/wallet/glyphs/ada_40px.png create mode 100755 tests/screenshots/src/app/wallet/glyphs/bnb_32px.png create mode 100755 tests/screenshots/src/app/wallet/glyphs/bnb_40px.png create mode 100755 tests/screenshots/src/app/wallet/glyphs/btc_32px.png create mode 100755 tests/screenshots/src/app/wallet/glyphs/btc_40px.png create mode 100755 tests/screenshots/src/app/wallet/glyphs/cardano_32px.png create mode 100755 tests/screenshots/src/app/wallet/glyphs/doge_32px.png create mode 100755 tests/screenshots/src/app/wallet/glyphs/doge_40px.png create mode 100755 tests/screenshots/src/app/wallet/glyphs/dot_32px.png create mode 100755 tests/screenshots/src/app/wallet/glyphs/dot_40px.png create mode 100755 tests/screenshots/src/app/wallet/glyphs/eth_32px.png create mode 100755 tests/screenshots/src/app/wallet/glyphs/eth_40px.png create mode 100755 tests/screenshots/src/app/wallet/glyphs/ic_asset_bnb_64.png create mode 100755 tests/screenshots/src/app/wallet/glyphs/ic_asset_btc_64.png create mode 100755 tests/screenshots/src/app/wallet/glyphs/ic_asset_cardano_64.png create mode 100755 tests/screenshots/src/app/wallet/glyphs/ic_asset_doge_64.png create mode 100755 tests/screenshots/src/app/wallet/glyphs/ic_asset_eth_64.png create mode 100755 tests/screenshots/src/app/wallet/glyphs/ic_asset_monero_64.png create mode 100755 tests/screenshots/src/app/wallet/glyphs/ic_asset_stellar_64.png create mode 100755 tests/screenshots/src/app/wallet/glyphs/ltc_40px.png create mode 100755 tests/screenshots/src/app/wallet/glyphs/stellar_32px.png create mode 100755 tests/screenshots/src/app/wallet/glyphs/stellar_40px.png create mode 100755 tests/screenshots/src/app/wallet/glyphs/xrp_32px.png create mode 100755 tests/screenshots/src/app/wallet/glyphs/xrp_40px.png create mode 100644 tests/screenshots/src/main/json_scenario.c create mode 100644 tests/screenshots/src/main/json_scenario.h create mode 100644 tests/screenshots/src/main/main.c create mode 100644 tests/screenshots/src/main/nbgl_driver.c create mode 100644 tests/screenshots/src/main/nbgl_driver.h create mode 100644 tests/screenshots/src/main/nbgl_front.c create mode 100644 tests/screenshots/src/main/png.c create mode 100644 tests/screenshots/src/main/properties.c create mode 100644 tests/screenshots/src/main/properties.h create mode 100644 tests/screenshots/src/main/stubs.c create mode 100644 tests/screenshots/src/uzlib/adler32.c create mode 100644 tests/screenshots/src/uzlib/crc32.c create mode 100644 tests/screenshots/src/uzlib/defl_static.c create mode 100644 tests/screenshots/src/uzlib/defl_static.h create mode 100644 tests/screenshots/src/uzlib/genlz77.c create mode 100644 tests/screenshots/src/uzlib/tinf.h create mode 100644 tests/screenshots/src/uzlib/tinf_compat.h create mode 100644 tests/screenshots/src/uzlib/tinfgzip.c create mode 100644 tests/screenshots/src/uzlib/tinflate.c create mode 100644 tests/screenshots/src/uzlib/tinfzlib.c create mode 100644 tests/screenshots/src/uzlib/uzlib.h create mode 100644 tests/screenshots/src/uzlib/uzlib_conf.h create mode 100644 tests/screenshots/stax.properties diff --git a/Makefile b/Makefile index 894fae9bf..0f8438810 100644 --- a/Makefile +++ b/Makefile @@ -8,13 +8,13 @@ TARGET ?= stax NBGL_DEFINES_stax := \ HAVE_SE_TOUCH \ NBGL_PAGE \ - NBGL_QRCODE \ - SCREEN_SIZE_WALLET + NBGL_QRCODE \ + SCREEN_SIZE_WALLET NBGL_DEFINES_nano := \ NBGL_STEP \ - NBGL_FLOW \ - SCREEN_SIZE_NANO + NBGL_FLOW \ + SCREEN_SIZE_NANO NBGL_DEFINES := $(NBGL_DEFINES_$(TARGET)) diff --git a/lib_nbgl/src/nbgl_obj.c b/lib_nbgl/src/nbgl_obj.c index 318aac79d..99b116fdf 100644 --- a/lib_nbgl/src/nbgl_obj.c +++ b/lib_nbgl/src/nbgl_obj.c @@ -125,9 +125,6 @@ static const draw_function_t draw_functions[NB_OBJ_TYPES] = { uint8_t ramBuffer[GZLIB_UNCOMPRESSED_CHUNK]; #ifdef BUILD_SCREENSHOTS -// Contains the last string index used -extern UX_LOC_STRINGS_INDEX last_string_id; - // Variables used to store important values (nb lines, bold state etc) extern uint16_t last_nb_lines, last_nb_pages; extern bool last_bold_state, verbose; diff --git a/tests/screenshots/Makefile b/tests/screenshots/Makefile new file mode 100644 index 000000000..fd4aab084 --- /dev/null +++ b/tests/screenshots/Makefile @@ -0,0 +1,299 @@ +# Makefile to build and launch screenshots +# The generated screenshots can be used to validate NBGL Application Use Cases +GCC ?= gcc +PRODUCT_NAME ?=$(word 1, $(subst _, ,$(MAKECMDGOALS))) + +# possible products +PRODUCT_NAMES = stax nanox flex nanosp + +ifeq (, $(filter $(PRODUCT_NAMES), $(PRODUCT_NAME))) +$(error "Product $(PRODUCT_NAME) not found") +endif + +SRC_DIR := src +PRODUCT_DIR := build/$(PRODUCT_NAME) +BUILD_DIR := $(PRODUCT_DIR) +GEN_DIR := $(BUILD_DIR)/generated +OBJ_DIR := $(BUILD_DIR)/obj +BIN_DIR := $(BUILD_DIR)/bin +PUBLIC_SDK_DIR := ../.. + +SCREENSHOTS_SRC_DIR := . + + +WARNINGS := -Wall -Wextra \ + -Wshadow -Wundef -Wmaybe-uninitialized -Wmissing-prototypes -Wno-discarded-qualifiers \ + -Wunused-function -Wno-error=strict-prototypes -Wpointer-arith -fno-strict-aliasing -Wno-error=cpp -Wuninitialized \ + -Wunused-parameter -Wno-missing-field-initializers -Wno-format-nonliteral -Wno-cast-qual -Wunreachable-code -Wno-switch-default \ + -Wreturn-type -Wmultichar -Wformat-security -Wno-ignored-qualifiers -Wno-error=pedantic -Wsign-compare -Wdouble-promotion -Wclobbered -Wdeprecated \ + -Wempty-body -Wshift-negative-value -Wstack-usage=2048 \ + -Wtype-limits -Wsizeof-pointer-memaccess -Wpointer-arith \ + -Werror + +CFLAGS := -O0 -g $(WARNINGS) + +# Add simulator define to allow modification of source +DEFINES+= \ + LINUX_SIMU=1 \ + VERSION=\"$(VERSION)\" \ + WITH_STDIO \ + HAVE_NBGL \ + HAVE_SEED_COOKIE \ + HAVE_ECC \ + HAVE_SHA256 \ + HAVE_SHA512 \ + HAVE_PBKDF2 \ + HAVE_RNG \ + HAVE_HASH \ + HAVE_INDEXED_STRINGS \ + NBGL_KEYBOARD \ + NBGL_KEYPAD \ + HAVE_RECOVER \ + OS_IO_SEPROXYHAL \ + USB_SEGMENT_SIZE=64 \ + IO_HID_EP_LENGTH=64 \ + IO_SEPROXYHAL_BUFFER_SIZE_B=300 \ + BUILD_SCREENSHOTS + +ifeq ($(PRODUCT_NAME),stax) +TARGET_DEFINES += \ + TARGET_STAX \ + HAVE_SIDE_SCREEN \ + HAVE_BAGL_FONT_INTER_REGULAR_24PX \ + HAVE_BAGL_FONT_INTER_SEMIBOLD_24PX \ + HAVE_BAGL_FONT_INTER_MEDIUM_32PX \ + SCREEN_SIZE_WALLET + +TARGET_ID := 0x33200004 +endif + +ifeq ($(PRODUCT_NAME),flex) +TARGET_DEFINES += \ + TARGET_FLEX \ + HAVE_BAGL_FONT_INTER_REGULAR_28PX \ + HAVE_BAGL_FONT_INTER_SEMIBOLD_28PX \ + HAVE_BAGL_FONT_INTER_MEDIUM_36PX \ + SCREEN_SIZE_WALLET + +TARGET_ID := 0x33300004 +endif + +ifeq ($(PRODUCT_NAME),nanox) +TARGET_DEFINES += \ + TARGET_NANOX \ + HAVE_BATTERY \ + HAVE_HARDWARE_VERSIONS \ + SCREEN_SIZE_NANO + +TARGET_ID := 0x33000004 +endif + +ifeq ($(PRODUCT_NAME),nanosp) +TARGET_DEFINES += \ + TARGET_NANOSP \ + SCREEN_SIZE_NANO + +TARGET_ID := 0x33100004 +endif + +#if Nano +ifeq (, $(filter $(TARGET_DEFINES), SCREEN_SIZE_WALLET)) +TARGET_DEFINES += \ + MONITOR_ZOOM=4 \ + HAVE_BRIGHTNESS_SETTING \ + NBGL_USE_CASE \ + NBGL_STEP \ + HAVE_BAGL_FONT_OPEN_SANS_REGULAR_11PX \ + HAVE_BAGL_FONT_OPEN_SANS_EXTRABOLD_11PX \ + HAVE_BAGL_FONT_OPEN_SANS_LIGHT_16PX \ + HAVE_SE_SCREEN \ + HAVE_SEPROXYHAL_MCU + +APP_PATH := $(SRC_DIR)/app/nano +FLOWS_PATH := $(SCREENSHOTS_SRC_DIR)/flows/nano + +GLYPH_OPT := --reverse + +else #if wallet +TARGET_DEFINES += \ + HAVE_BLE \ + HAVE_BATTERY \ + HAVE_SE_TOUCH \ + HAVE_NFC \ + HAVE_PIEZO_SOUND \ + HAVE_BACKGROUND_IMG \ + NBGL_QRCODE \ + NBGL_PAGE \ + NBGL_USE_CASE \ + HAVE_SE_EINK_DISPLAY + +APP_PATH := $(SRC_DIR)/app/wallet +FLOWS_PATH := $(SCREENSHOTS_SRC_DIR)/flows/wallet +endif + +DEFINES += $(TARGET_DEFINES) + +PROPERTIES_FILENAME = $(PRODUCT_NAME).properties + +GLYPH_DESTC := $(GEN_DIR)/glyphs.c +GLYPH_DESTH := $(GEN_DIR)/glyphs.h + +APP_ICON_DESTC := $(GEN_DIR)/app_icons.c +APP_ICON_DESTH := $(GEN_DIR)/app_icons.h + +NBGL_PATH := $(PUBLIC_SDK_DIR)/lib_nbgl + +INC:= \ + /usr/include/SDL2 \ + $(APP_PATH) \ + $(SRC_DIR)/main \ + $(SRC_DIR)/uzlib \ + $(PUBLIC_SDK_DIR)/target/$(PRODUCT_NAME)/include \ + $(PUBLIC_SDK_DIR)/include \ + $(PUBLIC_SDK_DIR)/lib_ux_nbgl \ + $(PUBLIC_SDK_DIR)/lib_cxng/include \ + $(NBGL_PATH)/include/fonts \ + $(NBGL_PATH)/include \ + $(PUBLIC_SDK_DIR)/qrcode/include \ + $(GEN_DIR) + +LDLIBS := -lSDL2 -lm -lbsd +LDFLAGS := -Xlinker -Map=$(BUILD_DIR)/memory.map +BIN := $(BIN_DIR)/simulator + +COMPILE = $(GCC) $(CFLAGS) $(addprefix -I,$(INC)) $(addprefix -D,$(DEFINES)) + +# Automatically include all source files +SRCS := $(shell find $(APP_PATH) -type f -name '*.c') +SRCS += $(shell find $(SRC_DIR)/uzlib -type f -name '*.c') +SRCS += $(shell find $(SRC_DIR)/main -type f -name '*.c') +LIB_SRCS := $(shell find $(NBGL_PATH)/src -type f -name '*.c') +LIB_SRCS += $(shell find $(NBGL_PATH)/fonts -type f -name '*.c') +LIB_SRCS += $(shell find $(PUBLIC_SDK_DIR)/qrcode/src -type f -name '*.c') + +APP_ICON_FILES = $(addprefix $(APP_PATH)/glyphs/,$(sort $(notdir $(shell find $(APP_PATH)/glyphs/)))) +APP_ICON_FILENAMES = $(sort $(notdir $(shell find $(APP_PATH)/glyphs/))) +GLYPH_FILES = $(addprefix $(APP_PATH)/glyphs/,$(sort $(notdir $(shell find $(APP_PATH)/glyphs/)))) + +ifneq (, $(filter $(PRODUCT_NAME), stax flex)) +GLYPH_FILES += $(addprefix $(NBGL_PATH)/glyphs/wallet/,$(sort $(notdir $(shell find $(NBGL_PATH)/glyphs/wallet/)))) +GLYPH_FILES += $(addprefix $(NBGL_PATH)/glyphs/64px/,$(sort $(notdir $(shell find $(NBGL_PATH)/glyphs/64px/)))) +else +GLYPH_FILES += $(addprefix $(NBGL_PATH)/glyphs/nano/,$(sort $(notdir $(shell find $(NBGL_PATH)/glyphs/nano/)))) +endif + +ifeq ($(PRODUCT_NAME),stax) +GLYPH_FILES += $(addprefix $(NBGL_PATH)/glyphs/32px/,$(sort $(notdir $(shell find $(NBGL_PATH)/glyphs/32px/)))) +endif +ifeq ($(PRODUCT_NAME),flex) +GLYPH_FILES += $(addprefix $(NBGL_PATH)/glyphs/40px/,$(sort $(notdir $(shell find $(NBGL_PATH)/glyphs/40px/)))) +endif + +ICON_SCRIPT := $(NBGL_PATH)/tools/icon2glyph.py + +OBJECTS := $(patsubst $(SRC_DIR)/%,$(OBJ_DIR)/%,$(SRCS:.c=.o)) +OBJECTS += $(patsubst $(PUBLIC_SDK_DIR)/%,$(OBJ_DIR)/%,$(LIB_SRCS:.c=.o)) +OBJECTS += $(OBJ_DIR)/glyphs.o +OBJECTS += $(OBJ_DIR)/app_icons.o + +# Creation of glyphs.c, containing all icons, from all +$(GLYPH_DESTH): $(GLYPH_FILES) $(ICON_SCRIPT) | $(GEN_DIR) + @echo [GLYPH] Compiling... $@ + @python3 $(ICON_SCRIPT) $(GLYPH_OPT) --glyphcheader $(GLYPH_DESTH) --glyphcfile $(GLYPH_DESTC) $(GLYPH_FILES) + +$(GLYPH_DESTC): $(GLYPH_DESTH) + +# Creation of special app_icon.h, containing all app icons, +$(APP_ICON_DESTH): $(APP_ICON_FILES) hexbitmap2c.py | $(GEN_DIR) + @echo [ICON] Creating... $@ + @rm -f $(ICON_HEX_FILE) + @echo "/* Generated */" > $(APP_ICON_DESTC) + @echo "/* Generated */" > $(APP_ICON_DESTH) + @for file in $(APP_ICON_FILENAMES) ; do \ + python3 $(ICON_SCRIPT) $(GLYPH_OPT) --hexbitmap tmp_$$file.hex $(APP_PATH)/glyphs/$$file ; \ + python3 hexbitmap2c.py --hexbitmap tmp_$$file.hex --inc $(APP_ICON_DESTH) --src $(APP_ICON_DESTC) --variable $$file; \ + rm -f tmp_$$file.hex; \ + done + +$(APP_ICON_DESTC): $(APP_ICON_DESTH) + +# Compile all C source files, except generated ones into .o (and .d for dependencies) +$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c $(GLYPH_DESTH) $(APP_ICON_DESTH) | $(OBJ_DIR) + @echo 'Compiling source file: $<' + @mkdir -p $(dir $@) + @$(COMPILE) -c -MMD -MP -MF"$(OBJ_DIR)/$*.d" -o "$@" "$<" + +# Compile lib C source files, except generated ones into .o (and .d for dependencies) +$(OBJ_DIR)/%.o: $(PUBLIC_SDK_DIR)/%.c $(GLYPH_DESTH) $(APP_ICON_DESTH) | $(OBJ_DIR) + @echo 'Compiling source file: $<' + @mkdir -p $(dir $@) + @$(COMPILE) -c -MMD -MP -MF"$(OBJ_DIR)/$*.d" -o "$@" "$<" + +# Compile generated C source files into .o (and .d for dependencies) +$(OBJ_DIR)/%.o: $(GEN_DIR)/%.c | $(OBJ_DIR) + @echo 'Compiling generated file: $<' + @$(COMPILE) -c -MMD -MP -MF"$(OBJ_DIR)/$*.d" -o "$@" "$<" + +# Rule to link all objects into the final binary +$(BIN): $(OBJECTS) | $(BIN_DIR) + @echo 'Linking $@' + @$(GCC) -o $@ $+ $(LDFLAGS) -ljson-c -lpng ${LDLIBS} + +# Rule to create directories +$(BIN_DIR) $(OBJ_DIR) $(GEN_DIR) $(BUILD_DIR) $(BUILD_DIR)/screenshots: + @mkdir -p $@ + +.PHONY: screenshots + +# how to generate all screenshots for all scenarios (flows) +# at the end, it creates a top level index +%_screenshots: $(BIN) | $(BUILD_DIR)/screenshots + @echo "Building... "$@ + @python3 $(SCREENSHOTS_SRC_DIR)/launch_screenshots.py -b $(BIN) -w $(BUILD_DIR)/screenshots -f $(FLOWS_PATH) -p $(PROPERTIES_FILENAME) -n $(PRODUCT_NAME) + +%_clean: + rm -rf $(PRODUCT_DIR) + + +## +## Nanox +## + +nanox: $(BIN) + +nanox_clean: + +nanox_screenshots: $(BIN) + +# +## +## NanoSP +## + +nanosp: $(BIN) + +nanosp_clean: + +nanosp_screenshots: $(BIN) + +# +## Stax +## + +stax: $(BIN) + +stax_clean: + +stax_screenshots: $(BIN) + +## +## Flex +## + +flex: $(BIN) + +flex_clean: + +flex_screenshots: $(BIN) + +-include $(OBJECTS:%.o=%.d) diff --git a/tests/screenshots/README.md b/tests/screenshots/README.md new file mode 100644 index 000000000..21b08e448 --- /dev/null +++ b/tests/screenshots/README.md @@ -0,0 +1,42 @@ +# Screenshots generation + +## Prerequisite + +TBC + +## Overview + +The goal of this mechanism is to generate screenshots of usual Applications scenarios. + +It can be used for any product (Stax, Flex, NanoX, NanoS+) + +## Launch screenshots generation + +The screenshots generator can be built and launched in the same command, for a given product + +``` +make _screenshots +``` + +where `` can be: + +- `flex` +- `stax` +- `nanox` +- `nanosp` + +so for example to generate screenshots for Flex: + +``` +make flex_screenshots +``` + +The result can be found in `build//screenshots` + +## Clean + +The environment can be cleaned-up with: + +``` +make _clean +``` diff --git a/tests/screenshots/flex.properties b/tests/screenshots/flex.properties new file mode 100644 index 000000000..cc37a0573 --- /dev/null +++ b/tests/screenshots/flex.properties @@ -0,0 +1,16 @@ +{ + "devicename": "Lily's Crypto", + "bootloader_version": "2.2", + "seph_version": "1.95", + "seph_serial": "2100200782107028", + "os_version": "2.0.1", + "os_flags": 0, + + "language": 0, + + "autoboot": true, + + "piezosound": 3, + + "features": 4, +} diff --git a/tests/screenshots/flows/wallet/app-sdk/flow_address_verif.json b/tests/screenshots/flows/wallet/app-sdk/flow_address_verif.json new file mode 100644 index 000000000..cb45fae41 --- /dev/null +++ b/tests/screenshots/flows/wallet/app-sdk/flow_address_verif.json @@ -0,0 +1,123 @@ +{ + "title": "address_verif", + "pages": [ + { + "name": "Entry", + "targets": [ + { + "app_event": "BTC_VERIFY_ADDR", + "page": "classic-01" + } + ] + }, + { + "name": "classic-01", + "targets": [ + { + "object": "RIGHT_BUTTON", + "page": "classic-02", + "comment": "accept" + }, + { + "object": "BOTTOM_BUTTON", + "page": "classic-cancelled", + "comment": "reject" + } + ] + }, + { + "name": "classic-02", + "targets": [ + { + "object": "CHOICE_2", + "page": "classic-qr_code", + "comment": "view QR" + }, + { + "object": "CHOICE_1", + "page": "classic-verified", + "comment": "confirm" + } + ] + }, + { + "name": "classic-qr_code", + "targets": [ + { + "object": "BOTTOM_BUTTON", + "page": "classic-02" + } + ] + }, + { + "name": "classic-verified", + "targets": [ + { + "object": "WHOLE_SCREEN", + "page": "without_settings-01" + } + ] + }, + { + "name": "classic-cancelled", + "targets": [ + { + "object": "WHOLE_SCREEN", + "page": "without_settings-01" + } + ] + }, + { + "name": "without_settings-01", + "targets": [ + { + "app_event": "BTC_VERIFY_ADDR", + "page": "classic-01", + "comment": "2nd classic with reject" + }, + { + "app_event": "CARDANO_VERIFY_ADDR", + "page": "long_address-01", + "comment": "veridy address+info" + } + ] + }, + { + "name": "long_address-01", + "targets": [ + { + "object": "RIGHT_BUTTON", + "page": "long_address-02" + } + ] + }, + { + "name": "long_address-02", + "targets": [ + { + "object": "EXTRA_BUTTON", + "page": "long_address-qr_code", + "comment": "view QR" + }, + { + "object": "RIGHT_BUTTON", + "page": "long_address-03" + } + ] + }, + { + "name": "long_address-qr_code", + "targets": [ + { + "object": "BOTTOM_BUTTON", + "page": "long_address-02" + } + ] + }, + { + "name": "long_address-03", + "targets": [ + ] + } + ] +} diff --git a/tests/screenshots/flows/wallet/app-sdk/flow_app_home_settings.json b/tests/screenshots/flows/wallet/app-sdk/flow_app_home_settings.json new file mode 100644 index 000000000..a70735e52 --- /dev/null +++ b/tests/screenshots/flows/wallet/app-sdk/flow_app_home_settings.json @@ -0,0 +1,108 @@ +{ + "title": "app_home_settings", + "pages": [ + { + "name": "Entry", + "targets": [ + { + "app_event": "ETH_OPEN", + "page": "with_settings-01" + } + ] + }, + { + "name": "with_settings-01", + "targets": [ + { + "object": "TOP_RIGHT_BUTTON", + "page": "with_settings-02" + }, + { + "app_event": "BTC_OPEN", + "page": "without_settings-01" + } + ] + }, + { + "name": "with_settings-02", + "targets": [ + { + "object": "RIGHT_BUTTON", + "page": "with_settings-03" + } + ] + }, + { + "name": "with_settings-03", + "targets": [ + { + "object": "RIGHT_BUTTON", + "page": "with_settings-04" + } + ] + }, + { + "name": "with_settings-04", + "targets": [ + { + "object": "BACK_BUTTON", + "page": "with_settings-01" + } + ] + }, + { + "name": "without_settings-01", + "targets": [ + { + "object": "TOP_RIGHT_BUTTON", + "page": "without_settings-02" + }, + { + "app_event": "MONERO_OPEN", + "page": "advanced_settings-01" + } + ] + }, + { + "name": "without_settings-02", + "targets": [ + { + "object": "BACK_BUTTON", + "page": "without_settings-01" + } + ] + }, + { + "name": "advanced_settings-01", + "targets": [ + { + "object": "TOP_RIGHT_BUTTON", + "page": "advanced_settings-02" + } + ] + }, + { + "name": "advanced_settings-02", + "targets": [ + { + "object": "CONTROLS,0", + "page": "advanced_settings-03" + } + ] + }, + { + "name": "advanced_settings-03", + "targets": [ + { + "app_event": "RECOV_OPEN", + "page": "app-with-cta-action-01" + } + ] + }, + { + "name": "app-with-cta-action-01", + "targets": [ + ] + } + ] +} diff --git a/tests/screenshots/flows/wallet/app-sdk/flow_tx_msg_signing.json b/tests/screenshots/flows/wallet/app-sdk/flow_tx_msg_signing.json new file mode 100644 index 000000000..a757bcf3a --- /dev/null +++ b/tests/screenshots/flows/wallet/app-sdk/flow_tx_msg_signing.json @@ -0,0 +1,366 @@ +{ + "title": "tx_msg_signing", + "pages": [ + { + "name": "Entry", + "targets": [ + { + "app_event": "BTC_SIGN", + "page": "basic_tx-02" + } + ] + }, + { + "name": "basic_tx-02", + "targets": [ + { + "object": "RIGHT_BUTTON", + "page": "basic_tx-03", + "comment": "accept" + }, + { + "object": "BOTTOM_BUTTON", + "page": "basic_tx-reject_confirm-01", + "comment": "reject" + } + ] + }, + { + "name": "basic_tx-03", + "targets": [ + { + "object": "RIGHT_BUTTON", + "page": "basic_tx-04" + } + ] + }, + { + "name": "basic_tx-04", + "targets": [ + { + "object": "LONG_PRESS_BUTTON", + "page": "basic_tx-05" + } + ] + }, + { + "name": "basic_tx-05", + "targets": [ + { + "object": "WHOLE_SCREEN", + "page": "basic_tx-05", + "comment": "trick to be sure we are in transcient page" + }, + { + "object": "WHOLE_SCREEN", + "page": "without_settings-01" + } + ] + }, + { + "name": "without_settings-01", + "targets": [ + { + "app_event": "BTC_SIGN", + "page": "basic_tx-02" + }, + { + "app_event": "CARDANO_SIGN", + "page": "streamed_tx-01" + } + ] + }, + { + "name": "basic_tx-reject_confirm-01", + "targets": [ + { + "object": "CHOICE_1", + "page": "basic_tx-reject_confirm-02" + } + ] + }, + { + "name": "basic_tx-reject_confirm-02", + "targets": [ + { + "object": "WHOLE_SCREEN", + "page": "without_settings-01" + } + ] + }, + { + "name": "streamed_tx-01", + "targets": [ + { + "object": "RIGHT_BUTTON", + "page": "streamed_tx-02" + } + ] + }, + { + "name": "streamed_tx-02", + "targets": [ + { + "object": "RIGHT_BUTTON", + "page": "streamed_tx-03", + "product": "flex" + }, + { + "object": "RIGHT_BUTTON", + "page": "streamed_tx-04", + "product": "stax" + } + ] + }, + { + "name": "streamed_tx-03", + "product": "flex", + "targets": [ + { + "object": "RIGHT_BUTTON", + "page": "streamed_tx-04" + } + ] + }, + { + "name": "streamed_tx-04", + "targets": [ + { + "app_event": "STELLAR_SIGN", + "page": "segmented_tx-01" + } + ] + }, + { + "name": "segmented_tx-01", + "targets": [ + { + "object": "RIGHT_BUTTON", + "page": "segmented_tx-02", + "comment": "accept" + } + ] + }, + { + "name": "segmented_tx-02", + "targets": [ + { + "object": "RIGHT_BUTTON", + "page": "segmented_tx-03", + "comment": "accept" + } + ] + }, + { + "name": "segmented_tx-03", + "targets": [ + { + "object": "RIGHT_BUTTON", + "page": "segmented_tx-04", + "comment": "accept" + } + ] + }, + { + "name": "segmented_tx-04", + "targets": [ + { + "object": "RIGHT_BUTTON", + "page": "segmented_tx-05", + "comment": "accept" + } + ] + }, + { + "name": "segmented_tx-05", + "targets": [ + { + "object": "RIGHT_BUTTON", + "page": "segmented_tx-06", + "comment": "accept" + } + ] + }, + { + "name": "segmented_tx-06", + "targets": [ + { + "object": "RIGHT_BUTTON", + "page": "segmented_tx-07", + "comment": "accept" + } + ] + }, + { + "name": "segmented_tx-07", + "targets": [ + { + "app_event": "STELLAR_STREAM_SIGN", + "page": "segmented_tx-streamed-01" + } + ] + }, + { + "name": "segmented_tx-streamed-01", + "targets": [ + { + "object": "RIGHT_BUTTON", + "page": "segmented_tx-streamed-02", + "comment": "accept" + } + ] + }, + { + "name": "segmented_tx-streamed-02", + "targets": [ + { + "object": "RIGHT_BUTTON", + "page": "segmented_tx-streamed-03", + "comment": "accept" + } + ] + }, + { + "name": "segmented_tx-streamed-03", + "targets": [ + { + "object": "RIGHT_BUTTON", + "page": "segmented_tx-streamed-04", + "comment": "accept" + } + ] + }, + { + "name": "segmented_tx-streamed-04", + "targets": [ + { + "object": "RIGHT_BUTTON", + "page": "segmented_tx-streamed-05", + "comment": "accept" + } + ] + }, + { + "name": "segmented_tx-streamed-05", + "targets": [ + { + "object": "RIGHT_BUTTON", + "page": "segmented_tx-streamed-06", + "comment": "accept" + } + ] + }, + { + "name": "segmented_tx-streamed-06", + "targets": [ + { + "object": "RIGHT_BUTTON", + "page": "segmented_tx-streamed-07" + } + ] + }, + { + "name": "segmented_tx-streamed-07", + "targets": [ + { + "app_event": "ETH_MESSAGE_SKIP", + "page": "op_skip-multiple_pages-00" + } + ] + }, + { + "name": "op_skip-multiple_pages-00", + "targets": [ + { + "object": "RIGHT_BUTTON", + "page": "op_skip-multiple_pages-01" + } + ] + }, + { + "name": "op_skip-multiple_pages-01", + "targets": [ + { + "object": "TOP_RIGHT_BUTTON", + "page": "op_skip-multiple_pages-skip_confirm" + }, + { + "object": "RIGHT_BUTTON", + "page": "op_skip-multiple_pages-02" + } + ] + }, + { + "name": "op_skip-multiple_pages-skip_confirm", + "targets": [ + { + "object": "CHOICE_2", + "page": "op_skip-multiple_pages-01" + } + ] + }, + { + "name": "op_skip-multiple_pages-02", + "targets": [ + { + "object": "RIGHT_BUTTON", + "page": "op_sign-message" + } + ] + }, + { + "name": "op_sign-message", + "targets": [ + { + "app_event": "ETH_SIGN", + "page": "eth_sign1" + } + ] + }, + { + "name": "eth_sign1", + "targets": [ + { + "object": "RIGHT_BUTTON", + "page": "op_fields-view_more-full_compact-01" + } + ] + }, + { + "name": "op_fields-view_more-full_compact-01", + "targets": [ + { + "object": "VALUE_BUTTON_3", + "page": "op_fields-view_more-full_compact-02" + }, + { + "object": "RIGHT_BUTTON", + "page": "op_fields-view_more-full_width-1" + } + ] + }, + { + "name": "op_fields-view_more-full_compact-02", + "targets": [ + { + "object": "BACK_BUTTON", + "page": "op_fields-view_more-full_compact-01" + } + ] + }, + { + "name": "op_fields-view_more-full_width-1", + "targets": [ + { + "object": "EXTRA_BUTTON", + "page": "op_fields-view_more-full_width-2" + } + ] + }, + { + "name": "op_fields-view_more-full_width-2", + "targets": [ + ] + } + ] +} diff --git a/tests/screenshots/flows/wallet/app-sdk/flow_w3c_blind.json b/tests/screenshots/flows/wallet/app-sdk/flow_w3c_blind.json new file mode 100644 index 000000000..08ad8421e --- /dev/null +++ b/tests/screenshots/flows/wallet/app-sdk/flow_w3c_blind.json @@ -0,0 +1,186 @@ +{ + "title": "w3c_blind", + "pages": [ + { + "name": "Entry", + "targets": [ + { + "app_event": "DOGE_SIGN", + "param": "1", + "page": "w3c-disabled-blind-01", + "comments": "blind=1, dApp=0, w3c_enabled=0, w3c_issue=0, w3c_threat=0" + } + ] + }, + { + "name": "w3c-disabled-blind-01", + "targets": [ + { + "object": "CHOICE_2", + "page": "w3c-disabled-blind-02" + } + ] + }, + { + "name": "w3c-disabled-blind-02", + "targets": [ + { + "object": "TOP_RIGHT_BUTTON", + "page": "w3c-disabled-blind-03" + }, + { + "app_event": "DOGE_SIGN", + "param": "5", + "page": "w3c-no-threat-blind-01", + "comments": "blind=1, dApp=0, w3c_enabled=1, w3c_issue=0, w3c_threat=0" + } + ] + }, + { + "name": "w3c-disabled-blind-03", + "targets": [ + { + "app_event": "BACK_BUTTON", + "page": "w3c-disabled-blind-02" + } + ] + }, + { + "name": "w3c-no-threat-blind-01", + "targets": [ + { + "object": "CHOICE_2", + "page": "w3c-no-threat-blind-02" + } + ] + }, + { + "name": "w3c-no-threat-blind-02", + "targets": [ + { + "object": "TOP_RIGHT_BUTTON", + "page": "w3c-no-threat-blind-03" + }, + { + "app_event": "DOGE_SIGN", + "param": "21", + "page": "w3c-threat-blind-01", + "comments": "blind=1, dApp=0, w3c_enabled=1, w3c_issue=0, w3c_threat=1" + } + ] + }, + { + "name": "w3c-no-threat-blind-03", + "targets": [ + { + "app_event": "BACK_BUTTON", + "page": "w3c-no-threat-blind-02" + } + ] + }, + { + "name": "w3c-threat-blind-01", + "targets": [ + { + "object": "CHOICE_2", + "page": "w3c-threat-blind-02" + } + ] + }, + { + "name": "w3c-threat-blind-02", + "targets": [ + { + "object": "TOP_RIGHT_BUTTON", + "page": "w3c-threat-blind-03" + }, + { + "app_event": "DOGE_SIGN", + "param": "13", + "page": "w3c-issue-blind-01", + "comments": "blind=1, dApp=0, w3c_enabled=1, w3c_issue=1, w3c_threat=0" + } + ] + }, + { + "name": "w3c-threat-blind-03", + "targets": [ + { + "object": "CONTROLS,1", + "page": "w3c-threat-blind-04" + }, + { + "object": "CONTROLS,0", + "page": "w3c-threat-blind-05" + }, + { + "app_event": "BACK_BUTTON", + "page": "w3c-threat-blind-02" + } + ] + }, + { + "name": "w3c-threat-blind-04", + "targets": [ + { + "app_event": "BACK_BUTTON", + "page": "w3c-threat-blind-03" + } + ] + }, + { + "name": "w3c-threat-blind-05", + "targets": [ + { + "app_event": "BACK_BUTTON", + "page": "w3c-threat-blind-03" + } + ] + }, + { + "name": "w3c-issue-blind-01", + "targets": [ + { + "object": "CHOICE_2", + "page": "w3c-issue-blind-02" + } + ] + }, + { + "name": "w3c-issue-blind-02", + "targets": [ + { + "object": "TOP_RIGHT_BUTTON", + "page": "w3c-issue-blind-03" + } + ] + }, + { + "name": "w3c-issue-blind-03", + "targets": [ + { + "object": "CONTROLS,1", + "page": "w3c-issue-blind-04" + }, + { + "object": "CONTROLS,0", + "page": "w3c-issue-blind-05" + } + ] + }, + { + "name": "w3c-issue-blind-04", + "targets": [ + { + "app_event": "BACK_BUTTON", + "page": "w3c-issue-blind-03" + } + ] + }, + { + "name": "w3c-issue-blind-05", + "targets": [ + ] + } + ] +} diff --git a/tests/screenshots/hexbitmap2c.py b/tests/screenshots/hexbitmap2c.py new file mode 100755 index 000000000..8adc1d06b --- /dev/null +++ b/tests/screenshots/hexbitmap2c.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 + +""" +Converts the given hexbitmap to a C array with the given name +""" + +import argparse +import math +import os +import binascii +import sys +import traceback +import gzip + +def main(): + parser = argparse.ArgumentParser( + description='Converts the given hexbitmap to a C array with the given name and creates (or appends) in 2 files (C and H)') + parser.add_argument('--hexbitmap', help="hexbitmap file", required = True) + parser.add_argument('--src', help="output C file name") + parser.add_argument('--inc', help="output H file name") + parser.add_argument('--variable', help="name of C variable", required = True) + args = parser.parse_args() + + # remove potential suffix + mVar = args.variable.split('.')[0] + # open hexbitmap file + with open(args.hexbitmap, 'r') as hexbitmap_file: + hexbitmap = hexbitmap_file.read() + + if args.src != None: + # init string for .c file + output = "const unsigned char %s[%d] = {"%(mVar, (len(hexbitmap)+1)/2) + + for i in range(0, len(hexbitmap)-1, 2): + pair = hexbitmap[i:i+2] + output += f"0x{pair}, " + + output = output[:-2] + + output += "};\n" + + # save .c file + with open(args.src, 'a+') as output_file: + output_file.write(output) + + if args.inc != None: + # init string for .h file + output = "extern const unsigned char %s[%d];\n"%(mVar, (len(hexbitmap)+1)/2) + + # add "#pragma once" if file is new + if not os.path.exists(args.inc): + with open(args.inc, 'w') as output_file: + output_file.write("#pragma once\n\n") + # append in .h file + with open(args.inc, 'a+') as output_file: + output_file.write(output) + +if __name__ == "__main__": + main() diff --git a/tests/screenshots/launch_screenshots.py b/tests/screenshots/launch_screenshots.py new file mode 100755 index 000000000..4558002e6 --- /dev/null +++ b/tests/screenshots/launch_screenshots.py @@ -0,0 +1,324 @@ +#!/usr/bin/env python3 +# coding: utf-8 +""" +@BANNER@ +""" +# ----------------------------------------------------------------------------- +from sys import stderr +from os import listdir, makedirs, mkdir, path, remove +from json import load, dump +from shutil import rmtree, copyfile, copy2 +from subprocess import run, CalledProcessError +from argparse import ArgumentParser + +def run_command(command, timeout=None): + """ + Run a command and return the generated output and an error flag. + command is an array with command and args, like: ['ls', '-l'] + + Return stdout+stderr concatenated, and error=False if no error occurred + """ + try: + # Avoid shell injection by using parameters in a string array. + # subprocess will take care of adding necessary escape characters + # around provided arguments. + completed_process = run(command, + capture_output=True, + timeout=timeout, + check=True) + + # Everything went fine, return value is 0 + if completed_process.stdout: + output = completed_process.stdout.decode("utf-8", 'ignore') + output += completed_process.stderr.decode("utf-8", 'ignore') + else: + output = "" + err = False + + # Do not handle subprocess.TimeoutExpired, voluntarily. + # If a timeout is raised, check logs! + # except subprocess.TimeoutExpired as error: + # # Timeout expired while waiting for the child process + # output = f"A timeout error occurred while sending command {command}: {error}" + # err = True + + except CalledProcessError as error: + # This exception is called when returncode !=0 + output = f"Return code {error.returncode} when sending command {command}: {error}" + output += '\nstderr = \n' + error.stderr.decode("utf-8", 'ignore') + output += '\nstdout = \n' + error.stdout.decode("utf-8", 'ignore') + err = True + + return output, err + + +# ----------------------------------------------------------------------------- +def build_png(bin_file, json_file, work_dir, debug_file, properties_file, + product_name, verbose=False): + """ + Will run the build_png program with json_file and saves PNGs in work_dir. + The output.txt file will be created in work_dir too. + """ + # Call the program build_png with the JSON file provided by email + cmd = [bin_file, '-d', work_dir, '-p', properties_file, + '-n', product_name] + if verbose: + cmd += ['-v'] + if json_file: + cmd += ['-j', json_file] + + output, error = run_command(cmd, timeout=60.0) + if error: + # An error occurred: log all details + print(output) + return None + + # Save the output to output.txt file + debug_file.write(output) + + return output + + +# ----------------------------------------------------------------------------- +def get_existing_flows(flow_dir): + """ + get all existing flows from input flows directory. + """ + # first level is the type of flow (os or app-sdk) + type_dirs = listdir(flow_dir) + existing_flows = {} + for type_dir in type_dirs: + if path.isdir(path.join(flow_dir, type_dir)): + dirs = listdir(path.join(flow_dir, type_dir)) + existing_screenshot_flows = [] + for file in dirs: + if path.isfile(path.join(flow_dir, type_dir, file)) and \ + file.endswith('.json') and \ + file.startswith('flow_'): + flow_name = file.replace('flow_', '').replace('.json', '') + if flow_name != 'unused': + existing_screenshot_flows.append(flow_name) + existing_flows[type_dir] = sorted(existing_screenshot_flows) + + return existing_flows + + +# ----------------------------------------------------------------------------- +def get_displayable_string(text): + """ + Replace specific characters by corresponding displayable ones. + """ + text = text.replace('\x08', "\\b") + text = text.replace('\n', "\\n") + text = text.replace('\f', "\\f") + return text + + +# ----------------------------------------------------------------------------- +def screenshots2html(build_dir, flow, product_name, all_errors): + """ + This function creates the screenshots HTML file for one flow, using + the input json file for this flow + """ + input_json_file = path.join(build_dir, flow+'_flow.json') + output_html_file = path.join(build_dir, flow+'_flow.html') + + # Can't generate screenshots for unused IDs, for now... + if flow == "unused": + return all_errors + + # Check input file does exists + if not path.isfile(input_json_file): + stderr.write(f"Can't find input file {input_json_file}!\n") + return all_errors + + infile = open(input_json_file, "r", encoding="utf-8") + data = load(infile) + + # do not create html file if no page in json file + if len(data['pages']) == 0: + infile.close() + return all_errors + + outfile = open(output_html_file, "w", encoding="utf-8") + outfile.write("\n") + outfile.write("\n") + outfile.write("\n") + outfile.write("\n") + outfile.write(f" {data['name']} flow\n") + outfile.write("\n") + outfile.write("\n") + outfile.write("\n") + outfile.write(f"

{data['name']} flow

\n") + outfile.write(" \n") + outfile.write(" \n") + outfile.write(" \n") + outfile.write(" \n") + outfile.write(" \n") + for page in data['pages']: + if 'Entry' in page['name']: + continue + outfile.write(" \n") + outfile.write(f" \n") + outfile.write(" \n".format(page['name'], page['image'])) + outfile.write(" \n") + outfile.write("
NameScreen
{page['name']}
\n") + outfile.write("\n") + outfile.write("\n") + + outfile.close() + infile.close() + + return all_errors + +def create_index_file(index_html, dir_flows): + outfile = open(index_html, "w", encoding="utf-8") + outfile.write('\n') + outfile.write('\n') + outfile.write('\n') + outfile.write(' Index page for all flows\n') + outfile.write('\n') + outfile.write('\n') + outfile.write('\n') + outfile.write('

Index page for all flows

\n') + for flow_type, flows in dir_flows.items(): + outfile.write(f'

{flow_type}

\n') + outfile.write('


\n') + for flow in flows: + outfile.write(f' {flow}
\n') + outfile.write('
\n') + outfile.write('

\n') + outfile.write(' \n') + outfile.write('\n') + + outfile.close() + +def main(binary, build_dir, flow_dir, properties_file, + product_name, verbose) -> int: + """ + Main function + """ + # main index file to produce + index_html = path.join(build_dir, 'index.html') + + # debug file to produce + output_txt = path.join(build_dir, 'output.txt') + + # delete whole directory and recreate it + rmtree(build_dir, ignore_errors=True) + makedirs(build_dir) + + # delete debug file + if path.exists(output_txt): + remove(output_txt) + + debug_file = open(output_txt, "w", encoding="utf-8") + # get all existing flows from input flows directories + existing_screenshot_flows = get_existing_flows(flow_dir) + + all_errors = [] + # how to generate screenshots for a given scenario (flow): it produces the .html + for flow_type, flows in existing_screenshot_flows.items(): + type_build_dir = path.join(build_dir, flow_type) + mkdir(type_build_dir) + for flow in flows: + if verbose: + debug_file.write(f'\nGenerating screenshots for ... {flow_type}/{flow}\n\n') + mkdir(path.join(type_build_dir, flow)) + output = build_png(binary, + path.join(flow_dir, flow_type, 'flow_'+flow+'.json'), + type_build_dir, + debug_file, + properties_file, + product_name, + verbose) + if output is None: + return -1 + all_errors = screenshots2html( + type_build_dir, flow, product_name, all_errors) + + # clean-up non existing flows (because not concerned for this product) + for flow_type, flows in existing_screenshot_flows.items(): + for flow in flows: + if not path.exists(path.join(build_dir, flow_type, flow+'_flow.html')): + existing_screenshot_flows[flow_type].remove(flow) + + if verbose: + debug_file.write('Generating index file...\n') + create_index_file(index_html, existing_screenshot_flows) + + if verbose: + debug_file.write('\n') + + # Display errors + for error_msg in all_errors: + debug_file.write(error_msg) + + if verbose: + debug_file.write('\n') + + if len(all_errors) != 0: + debug_file.write(f"{len(all_errors)} error(s) found.\n") + else: + debug_file.write("No error found, congratulations!\n") + + debug_file.close() + + return 0 + + +# ------------------------------------------------------------------------- +# Program entry point: +# ----------------------------------------------------------------------------- +if __name__ == "__main__": + # ------------------------------------------------------------------------- + # Parse arguments: + parser = ArgumentParser( + description="generate screenshots for all flows" + "in the given work dir") + + parser.add_argument( + "-b", "--binary", + dest="binary", type=str, + help="binary file") + + parser.add_argument( + "-f", "--flow_dir", + dest="flow_dir", type=str, + required=True, + help="directory where to find flow Jsons") + + parser.add_argument( + "-w", "--work_dir", + dest="work_dir", type=str, + required=True, + help="working directory") + + parser.add_argument( + "-p", "--properties_file", + dest="properties_file", type=str, + help=".properties file path") + + parser.add_argument( + "-n", "--product_name", + dest="product_name", type=str, + help="product name (stax, nanox, flex...)") + + parser.add_argument( + "-v", "--verbose", + action='store_true', + help="Add verbosity to output ('%(default)s' by default)") + + args = parser.parse_args() + + # ------------------------------------------------------------------------- + # Call main function: + EXIT_CODE = main(args.binary, args.work_dir, + args.flow_dir, args.properties_file, args.product_name, args.verbose) + + exit(EXIT_CODE) diff --git a/tests/screenshots/nanosp.properties b/tests/screenshots/nanosp.properties new file mode 100644 index 000000000..d3596dea3 --- /dev/null +++ b/tests/screenshots/nanosp.properties @@ -0,0 +1,11 @@ +{ + "devicename": "LNSP-9DB", + "bootloader_version": "2.2", + "seph_version": "1.95", + "os_version": "2.0.1", + "autoboot": false, + + "piezosound": 3, + + "features": 6, +} diff --git a/tests/screenshots/nanox.properties b/tests/screenshots/nanox.properties new file mode 100644 index 000000000..49264d421 --- /dev/null +++ b/tests/screenshots/nanox.properties @@ -0,0 +1,12 @@ +{ + "devicename": "LNX-9DAB", + "bootloader_version": "2.2", + "seph_version": "1.95", + "os_version": "2.0.1", + + "autoboot": true, + + "piezosound": 3, + + "features": 6, +} diff --git a/tests/screenshots/src/app/nano/app_bitcoin_start.c b/tests/screenshots/src/app/nano/app_bitcoin_start.c new file mode 100644 index 000000000..f6664e42a --- /dev/null +++ b/tests/screenshots/src/app/nano/app_bitcoin_start.c @@ -0,0 +1,87 @@ + +/** + * @file app_bitcoin_start.c + * @brief Entry point of Bitcoin application, using predefined layout + * + */ + +/********************* + * INCLUDES + *********************/ +#include "nbgl_use_case.h" +#include "bolos_ux_common.h" +#include "apps_api.h" + +/********************* + * DEFINES + *********************/ +#define NB_PAIRS 3 + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ +static nbgl_contentTagValue_t pairs[] = { + {.item = "Amount", .value = "0.0001 BTC" }, + {.item = "To", .value = "bc1pkdcufjh6dxjaaa05hudvxqg5fhspfmwmp8g92gq8cv4gwwnmgrfqfd4jlg"}, + {.item = "Fees", .value = "0.000000698 BTC" }, +}; + +static nbgl_contentTagValueList_t pairList = {.nbMaxLinesForValue = 0, + .nbPairs = 3, + .wrapping = true, + .pairs = (nbgl_contentTagValue_t *) pairs}; + +static const char *infoTypes[] = {"Version", "Developer", "Copyright"}; +static const char *infoContents[] = {"2.0.5", "Ledger", "(c) 2024 Ledger"}; + +static const nbgl_contentInfoList_t infoContentsList + = {.nbInfos = 3, .infoTypes = infoTypes, .infoContents = infoContents}; + +/********************** + * VARIABLES + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +static void choiceCallback(bool confirm) +{ + UNUSED(confirm); + bolos_ux_dashboard(); +} + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void app_bitcoinSignTransaction(void) +{ + nbgl_useCaseReview(TYPE_TRANSACTION, + &pairList, + &C_nanox_app_bitcoin, + "Review transaction", + NULL, + "Accept and\nSend", + choiceCallback); +} + +/** + * @brief Bitcoin application start page + * + */ +void app_fullBitcoin(void) +{ + nbgl_useCaseHomeAndSettings("Bitcoin", + &C_bitcoin_logo, + NULL, + INIT_HOME_PAGE, + NULL, + &infoContentsList, + NULL, + bolos_ux_dashboard); +} diff --git a/tests/screenshots/src/app/nano/app_ethereum_settings.c b/tests/screenshots/src/app/nano/app_ethereum_settings.c new file mode 100644 index 000000000..131ddbfc8 --- /dev/null +++ b/tests/screenshots/src/app/nano/app_ethereum_settings.c @@ -0,0 +1,66 @@ + +/** + * @file app_ethereum_settings.c + * @brief Settings page of Ethereum application, using predefined pages + * + */ + +/********************* + * INCLUDES + *********************/ +#include "nbgl_debug.h" +#include "nbgl_use_case.h" +#include "bolos_ux_common.h" +#include "apps_api.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ +typedef struct { + const char *text; ///< main text for the switch + nbgl_state_t initState; ///< initial state of the switch +} Switch_t; + +/********************** + * STATIC VARIABLES + **********************/ +static void contentActionCallback(int token, uint8_t index, int page); + +static nbgl_contentSwitch_t switches[] = { + {.initState = false, .text = "Blind signing", .token = 0}, + {.initState = true, .text = "Debug", .token = 1}, + {.initState = true, .text = "Nonce", .token = 2}, +}; + +static const nbgl_contentSwitchesList_t switchesList = {.nbSwitches = 3, .switches = switches}; + +static nbgl_content_t contents[] = { + {.type = SWITCHES_LIST, + .content.switchesList = switchesList, + .contentActionCallback = contentActionCallback} +}; + +/********************** + * VARIABLES + **********************/ +nbgl_genericContents_t eth_settingContents = {.contentsList = contents, .nbContents = 1}; + +/********************** + * STATIC PROTOTYPES + **********************/ + +static void contentActionCallback(int token, uint8_t index, int page) +{ + UNUSED(index); + UNUSED(page); + switches[token].initState = !switches[token].initState; + // nbgl_useCaseSettings(page, 3, app_fullEthereum, navCallback, actionCallback); +} + +/********************** + * GLOBAL FUNCTIONS + **********************/ diff --git a/tests/screenshots/src/app/nano/app_ethereum_start.c b/tests/screenshots/src/app/nano/app_ethereum_start.c new file mode 100644 index 000000000..814287b0e --- /dev/null +++ b/tests/screenshots/src/app/nano/app_ethereum_start.c @@ -0,0 +1,88 @@ + +/** + * @file app_ethereum_start.c + * @brief Entry point of Ethereum application, using predefined pages + * + */ + +/********************* + * INCLUDES + *********************/ +#include "nbgl_use_case.h" +#include "bolos_ux_common.h" +#include "apps_api.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ +static const nbgl_layoutTagValue_t pairs[] = { + {.item = "Output", .value = "1"}, + {.item = "Amount", .value = "BTC 0.0011"}, + { + .item = "To", + .value = "CELO 0.013563782407139377", + }, + {.item = "Amount2", .value = "BTC 0.0011"}, + { + .item = "To2", + .value = "bc1pkdcufjh6dxjaaa05hudvxqg5fhspfmwmp8g92gq8cv4gwwnmgrfqfd4jlg", + }, + {.item = "msg/value/funds", + .value = "{{\"start\":\"{amount\":\"111000000000\",\"proof\"[\"166a71e8dd11c23sfs64804"} +}; + +static nbgl_contentTagValueList_t pairList = {.nbMaxLinesForValue = 0, + .nbPairs = 6, + .wrapping = true, + .pairs = (nbgl_layoutTagValue_t *) pairs}; + +static void onTransactionAccept(bool confirm) +{ + app_fullEthereum(); +} +static const char *infoTypes[] = {"Version", "Developer", "Copyright"}; +static const char *infoContents[] = {"2.0.5", "Ledger", "(c) 2024 Ledger"}; + +static const nbgl_contentInfoList_t infoContentsList + = {.nbInfos = 3, .infoTypes = infoTypes, .infoContents = infoContents}; + +extern nbgl_genericContents_t eth_settingContents; + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void app_ethereumSignMessage(void) +{ + nbgl_useCaseReview(TYPE_TRANSACTION, + &pairList, + &C_icon_eye, + "Review transaction", + NULL, + "Accept\nand send", + onTransactionAccept); +} + +/** + * @brief Ethereum application start page + * + */ +void app_fullEthereum(void) +{ + nbgl_useCaseHomeAndSettings("Ethereum", + &C_nanox_app_ethereum, + NULL, + INIT_HOME_PAGE, + ð_settingContents, + &infoContentsList, + NULL, + bolos_ux_dashboard); +} diff --git a/tests/screenshots/src/app/nano/app_ethereum_verify_addr.c b/tests/screenshots/src/app/nano/app_ethereum_verify_addr.c new file mode 100644 index 000000000..68705088b --- /dev/null +++ b/tests/screenshots/src/app/nano/app_ethereum_verify_addr.c @@ -0,0 +1,55 @@ + +/** + * @file app_ethereum_verify_addr.c + * @brief Verify Ethereum application, using predefined use cases + * + */ + +/********************* + * INCLUDES + *********************/ +#include "nbgl_debug.h" +#include "nbgl_use_case.h" +#include "apps_api.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * VARIABLES + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void displayAddressCallback(bool confirm) +{ + app_fullEthereum(); +} + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * @brief Ethereum application verify address page + * + */ +void app_ethereumVerifyAddress(void) +{ + nbgl_useCaseAddressReview("bc1pkdcufjh6dxjaaa05hudvxqg5fhspfmwmp8g92gq8cv4gwwnmgrfqfd4jlg", + NULL, + &C_nanox_app_ethereum, + "Verify address", + NULL, + displayAddressCallback); +} diff --git a/tests/screenshots/src/app/nano/apps_api.h b/tests/screenshots/src/app/nano/apps_api.h new file mode 100644 index 000000000..0fc2a21c4 --- /dev/null +++ b/tests/screenshots/src/app/nano/apps_api.h @@ -0,0 +1,31 @@ +/** + * @file apps_api.h + * @brief Header for Full app + * + */ + +#ifndef APP_FULL_H +#define APP_FULL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "nbgl_types.h" +#include "glyphs.h" + +// Fake coin apps +void app_fullBitcoin(void); +void app_bitcoinInfos(void); +void app_bitcoinSignTransaction(void); +void app_bitcoinVerifyAddress(void); +void app_fullEthereum(void); +void app_ethereumSettings(void); +void app_ethereumSignMessage(void); +void app_ethereumVerifyAddress(void); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* APP_FULL_H */ diff --git a/tests/screenshots/src/app/nano/glyphs/bitcoin_logo.gif b/tests/screenshots/src/app/nano/glyphs/bitcoin_logo.gif new file mode 100644 index 0000000000000000000000000000000000000000..d43e12f59dd2c522385aa55c73167b4ce91b1bde GIT binary patch literal 68 zcmZ?wbhEHbtP)JEENd(gW?JEirle1Gx6p~WY zGxKbf-tXS8q>!0ns}yePYv5bpoSKp8QB{;0T;&&%T$P<{nWAKGr(jcI1=O2clBiIT zo0C^;Rbi`?n3A8AY6WEHrj{h?D=C0glw{i~If5hXl~0)0b01CWOxKFuxg^~J9=Hy5lL z7!<`NL8%DWVl}roq_QAYKPa_0zqBYh6{uVpWK)5ab5UwyNq$jCetr%t2m>EvqSYHn<1 zXl!WcYGz?-Xy#;TU~X*eW^Vf5%*ha@*(E=@G&eP`1g1F!q1ghe8AUHhD=001f&>`A zMVV!(DQ-pixe8!^TV=xCg6@{ z(ZdJ#@7=v~`_|1H*RNf@a{1E53+K6ZM9qnzc zEzM1h4fS=kHPuy>73F26CB;RB1^IcoIoVm68R==MDalER3Gs2UG0{A;Cd` z0selzKHgrQ9`0_gF3wJl4)%7oHr7^_7UpKACdNjF2KsusI@(&A8tQ7QD#}WV3i5KY zGSX6#65?W_BEmv~0{ncuJUrZ7oE+?ItSrn-z=A>tR6u}A4F)FJmj0Eq8P7G%dE+Rl YA2L}lCzm6mc2-8oz3(CKxtP)JEENd(gW?JEirle1Gx6p~WY zGxKbf-tXS8q>!0ns}yePYv5bpoSKp8QB{;0T;&&%T$P<{nWAKGr(jcI1=O2clBiIT zo0C^;Rbi`?n3A8AY6WEHrj{h?D=C0glw{i~If5hXl~0)0b01CWOxKFuxg^~J9=Hy5lL z7!<`NL8%DWVl}roq_QAYKPa_0zqBYh6{uVpWK)5ab5UwyNq$jCetr%t2m>`A zMVV!(DQ-pixe8!^TV=xC0(Xj`i=~sRi7U)BRHuMl0@0d+@{(ZdJ#@7=v~`_|1H*RNf@a{1E53+K6ZM9qnzcEzM1h4fS=kHPuy>73F26CB;RB1^IcoIoVm68R==MDalER3Gs2U zG0{A;Cd`0selzKHgrQ9`0_gF3wJl4)%7oHr7^_7UpKACdNjF2KsusI@(&A z8tQ7QD#}WV3i5KYGSX6#65?W_BEmv~0{ncuJUrZ7oE+?ItSrn-z=A>tR6u}A4F)Fh emj0Ei1$$&n{0{w+*v5UoB6PBO+T}_y25SKJ#)+{2 literal 0 HcmV?d00001 diff --git a/tests/screenshots/src/app/wallet/app_algorand_start.c b/tests/screenshots/src/app/wallet/app_algorand_start.c new file mode 100644 index 000000000..09527406e --- /dev/null +++ b/tests/screenshots/src/app/wallet/app_algorand_start.c @@ -0,0 +1,154 @@ + +/** + * @file app_algorand_start.c + * @brief Entry point of Algorand application, using predefined layout + * + */ + +/********************* + * INCLUDES + *********************/ +#include "nbgl_debug.h" +#include "nbgl_layout.h" +#include "nbgl_obj.h" +#include "apps_api.h" +#include +#include +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ +enum { + QUIT_TOKEN, + SETTINGS_TOKEN, + KBD_BACK_TOKEN, + KBD_CONFIRM_TOKEN, + KBD_TEXT_TOKEN +}; + +/********************** + * STATIC VARIABLES + **********************/ +static nbgl_layout_t *layout; +static int textIndex, keyboardIndex; +static char textToEnter[32] = ""; + +/********************** + * VARIABLES + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void keyboardCallback(char touchedKey) +{ + LOG_DEBUG(UX_LOGGER, "keyboardCallback(): touchedKey = %d\n", touchedKey); + // if not Backspace + if (touchedKey != BACKSPACE_KEY) { + int textLen = strlen(textToEnter); + textToEnter[textLen] = touchedKey; + textToEnter[textLen + 1] = '\0'; + nbgl_layoutUpdateEnteredText(layout, textIndex, true, 12, textToEnter, false); + + if (textLen == 2) { + nbgl_layoutUpdateKeyboard(layout, keyboardIndex, 0x3456, false, LOWER_CASE); + } + } + else { // backspace + int textLen = strlen(textToEnter); + if (textLen == 0) { + return; + } + textToEnter[textLen - 1] = '\0'; + if (textLen > 1) { + nbgl_layoutUpdateEnteredText(layout, textIndex, true, 12, textToEnter, false); + } + else { + nbgl_layoutUpdateEnteredText(layout, textIndex, true, 12, "Current text", true); + } + } +} + +static void layoutTouchCallback(int token, uint8_t index) +{ + LOG_DEBUG(UX_LOGGER, "layoutTouchCallback(): token = %d, index = %d\n", token, index); + if (token == QUIT_TOKEN) { + nbgl_layoutRelease(layout); + } + else if (token == SETTINGS_TOKEN) { + // + nbgl_layoutDescription_t layoutDescription + = {.modal = false, .onActionCallback = &layoutTouchCallback}; + nbgl_layoutKbd_t kbdInfo = {.callback = keyboardCallback, + .keyMask = 0, + .lettersOnly = false, + .mode = MODE_LETTERS, + .casing = UPPER_CASE}; + nbgl_layoutCenteredInfo_t centeredInfo = {.text1 = NULL, + .text2 = "Enter passphrase:", + .text3 = NULL, + .style = LARGE_CASE_INFO, + .icon = NULL, + .offsetY = 0, + .onTop = true}; + layout = nbgl_layoutGet(&layoutDescription); + nbgl_layoutAddCenteredInfo(layout, ¢eredInfo); + keyboardIndex = nbgl_layoutAddKeyboard(layout, &kbdInfo); + textIndex + = nbgl_layoutAddEnteredText(layout, true, 12, "current text", true, 32, KBD_TEXT_TOKEN); + nbgl_layoutDraw(layout); + } + else if (token == 142) { + LOG_WARN(APP_LOGGER, "layoutTouchCallback(): toto\n"); + } + else if (token == KBD_BACK_TOKEN) { + nbgl_layoutRelease(layout); + app_fullAlgorand(); + } + else if (token == KBD_CONFIRM_TOKEN) { + nbgl_layoutRelease(layout); + app_fullAlgorand(); + } +} + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * @brief Algorand application start page + * + */ +void app_fullAlgorand(void) +{ + nbgl_layoutDescription_t layoutDescription + = {.modal = false, .onActionCallback = &layoutTouchCallback}; + nbgl_layoutCenteredInfo_t centeredInfo = { + .text1 = "Algorand", + .text2 = "Go to Ledger Live to create a\ntransaction. You will approve it\non Fatstacks.", + .text3 = NULL, + .style = LARGE_CASE_INFO, + .icon = &C_Algorand32px, + .offsetY = 0}; + + if (layout) { + nbgl_layoutRelease(layout); + } + layout = nbgl_layoutGet(&layoutDescription); + nbgl_layoutAddTopRightButton(layout, &CLOSE_ICON, QUIT_TOKEN, TUNE_TAP_NEXT); + nbgl_layoutAddBottomButton(layout, &WHEEL_ICON, SETTINGS_TOKEN, false, TUNE_TAP_NEXT); + nbgl_layoutAddCenteredInfo(layout, ¢eredInfo); + + nbgl_layoutDraw(layout); +} +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif diff --git a/tests/screenshots/src/app/wallet/app_bitcoin_info.c b/tests/screenshots/src/app/wallet/app_bitcoin_info.c new file mode 100644 index 000000000..29f7671a8 --- /dev/null +++ b/tests/screenshots/src/app/wallet/app_bitcoin_info.c @@ -0,0 +1,56 @@ + +/** + * @file app_bitcoin_info.c + * @brief Info page of Bitcoin application, using use case API + * + */ + +/********************* + * INCLUDES + *********************/ +#include "nbgl_use_case.h" +#include "apps_api.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ +static const char *infoTypes[] = {"Version", "Developer", "Copyright"}; +static const char *infoContents[] = {"2.0.5", "Ledger", "(c) 2024 Ledger"}; + +/********************** + * VARIABLES + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static bool navCallback(uint8_t page, nbgl_pageContent_t *content) +{ + UNUSED(page); + content->type = INFOS_LIST; + content->infosList.nbInfos = 3; + content->infosList.infoTypes = infoTypes; + content->infosList.infoContents = infoContents; + return true; +} + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * @brief Bitcoin application infos page + * + */ +void app_bitcoinInfos(void) +{ + nbgl_useCaseNavigableContent("Bitcoin", 0, 1, app_fullBitcoin, navCallback, NULL); +} diff --git a/tests/screenshots/src/app/wallet/app_bitcoin_start.c b/tests/screenshots/src/app/wallet/app_bitcoin_start.c new file mode 100644 index 000000000..4ab55d8d6 --- /dev/null +++ b/tests/screenshots/src/app/wallet/app_bitcoin_start.c @@ -0,0 +1,85 @@ + +/** + * @file app_bitcoin_start.c + * @brief Entry point of Bitcoin application, using use case API + * + */ + +/********************* + * INCLUDES + *********************/ +#include "nbgl_debug.h" +#include "nbgl_use_case.h" +#include "apps_api.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ +static nbgl_contentTagValue_t pairs[] = { + {.item = "Amount", .value = "0.0001 BTC" }, + {.item = "To", .value = "bc1pkdcufjh6dxjaaa05hudvxqg5fhspfmwmp8g92gq8cv4gwwnmgrfqfd4jlg"}, + {.item = "Fees", .value = "0.000000698 BTC" }, +}; + +static nbgl_contentTagValueList_t pairList = {.nbMaxLinesForValue = 0, + .nbPairs = 3, + .wrapping = true, + .pairs = (nbgl_contentTagValue_t *) pairs}; + +static const char *infoTypes[] = {"Version", "Developer", "Copyright"}; +static const char *infoContents[] = {"2.0.5", "Ledger", "(c) 2024 Ledger"}; + +static const nbgl_contentInfoList_t infoContentsList + = {.nbInfos = 3, .infoTypes = infoTypes, .infoContents = infoContents}; + +/********************** + * VARIABLES + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +static void onTransactionAccept(bool confirm) +{ + if (confirm) { + nbgl_useCaseStatus("Transaction signed", true, app_fullBitcoin); + } + else { + nbgl_useCaseStatus("Transaction rejected", false, app_fullBitcoin); + } +} + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void app_bitcoinSignTransaction(void) +{ + // Start review + nbgl_useCaseReview(TYPE_TRANSACTION, + &pairList, + &C_ic_asset_btc_64, + "Review transaction\nto send Bitcoin", + NULL, + "Sign transaction to\nsend Bitcoin?", + onTransactionAccept); +} + +/** + * @brief Bitcoin application start page + * + */ +void app_fullBitcoin(void) +{ + nbgl_useCaseHomeAndSettings( + "Bitcoin", &C_ic_asset_btc_64, NULL, INIT_HOME_PAGE, NULL, &infoContentsList, NULL, NULL); +} diff --git a/tests/screenshots/src/app/wallet/app_bitcoin_verify_addr.c b/tests/screenshots/src/app/wallet/app_bitcoin_verify_addr.c new file mode 100644 index 000000000..949533043 --- /dev/null +++ b/tests/screenshots/src/app/wallet/app_bitcoin_verify_addr.c @@ -0,0 +1,61 @@ + +/** + * @file app_bitcoin_verify_addr.c + * @brief Entry point of Bitcoin application, using predefined layout + * + */ + +/********************* + * INCLUDES + *********************/ +#include "nbgl_debug.h" +#include "nbgl_use_case.h" +#include "apps_api.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * VARIABLES + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +static void displayAddressCallback(bool confirm) +{ + if (confirm) { + nbgl_useCaseStatus("Address verified", true, app_fullBitcoin); + } + else { + nbgl_useCaseStatus("Address verification\ncancelled", false, app_fullBitcoin); + } +} + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * @brief Bitcoin application address verification + * + */ +void app_bitcoinVerifyAddress(void) +{ + nbgl_useCaseAddressReview("bc1pkdcufjh6dxjaaa05hudvxqg5fhspfmwmp8g92gq8cv4gwwnmgrfqfd4jlg", + NULL, + &C_ic_asset_btc_64, + "Verify Bitcoin address", + NULL, + displayAddressCallback); +} diff --git a/tests/screenshots/src/app/wallet/app_cardano_start.c b/tests/screenshots/src/app/wallet/app_cardano_start.c new file mode 100644 index 000000000..36cf21160 --- /dev/null +++ b/tests/screenshots/src/app/wallet/app_cardano_start.c @@ -0,0 +1,116 @@ + +/** + * @file app_cardano_start.c + * @brief Entry point of Cardano application, using use case API + * + */ + +/********************* + * INCLUDES + *********************/ +#include "nbgl_debug.h" +#include "nbgl_use_case.h" +#include "apps_api.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ +static nbgl_contentTagValue_t pairs[] = { + { + .item = "To", + .value = "addr1qyyp7u0s2zcxadzd5w9w8rzpcg0rzmw07v0tkurg32y8agjnfsh6w2r9tvuq0lrsktztgj845qdc" + "z4lpa2x4e0me68mqpp4tzn", }, + {.item = "Amount", .value = "8.127467 ADA"}, + {.item = "Fees", .value = "0.175157 ADA"} +}; + +static nbgl_contentTagValueList_t pairList = {.nbMaxLinesForValue = 0, .wrapping = true}; + +static const char *infoTypes[] = {"Version", "Developer", "Copyright"}; +static const char *infoContents[] = {"2.0.5", "Ledger", "(c) 2024 Ledger"}; + +static const nbgl_contentInfoList_t infoContentsList + = {.nbInfos = 3, .infoTypes = infoTypes, .infoContents = infoContents}; + +static uint8_t nbPairsSent; + +/********************** + * VARIABLES + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void onTransactionAccept(bool confirm) +{ + if (confirm) { + nbgl_useCaseStatus("Transaction signed", true, app_fullCardano); + } + else { + nbgl_useCaseStatus("Transaction rejected", false, app_fullCardano); + } +} + +static void onTransactionContinue(bool askMore) +{ + if (askMore) { + if (nbPairsSent < 3) { + if (nbPairsSent == 0) { + pairList.nbPairs = 3; + pairList.pairs = pairs; + } + else { + pairList.nbPairs = 1; + pairList.pairs = &pairs[2]; + } + nbPairsSent += pairList.nbPairs; + nbgl_useCaseReviewStreamingContinue(&pairList, onTransactionContinue); + } + else { + nbgl_useCaseReviewStreamingFinish("Sign transaction to transfer Cardano?", + onTransactionAccept); + } + } + else { + onTransactionAccept(false); + } +} + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void app_cardanoSignTransaction(void) +{ + nbPairsSent = 0; + nbgl_useCaseReviewStreamingStart(TYPE_TRANSACTION, + &C_ic_asset_cardano_64, + "Review transaction", + NULL, + onTransactionContinue); +} + +/** + * @brief Cardano application start page + * + */ +void app_fullCardano(void) +{ + nbgl_useCaseHomeAndSettings( + "Cardano", + &C_ic_asset_cardano_64, + "Use Ledger Live to create transactions and confirm them on your Europa.", + INIT_HOME_PAGE, + NULL, + &infoContentsList, + NULL, + NULL); +} diff --git a/tests/screenshots/src/app/wallet/app_cardano_verify_addr.c b/tests/screenshots/src/app/wallet/app_cardano_verify_addr.c new file mode 100644 index 000000000..3876fec37 --- /dev/null +++ b/tests/screenshots/src/app/wallet/app_cardano_verify_addr.c @@ -0,0 +1,75 @@ + +/** + * @file app_cardano_verify_addr.c + * @brief Entry point of Cardano address verification + * + */ + +/********************* + * INCLUDES + *********************/ +#include "nbgl_debug.h" +#include "nbgl_use_case.h" +#include "apps_api.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ +static nbgl_contentTagValue_t pairs[] = { + { + .item = "Derivation path", + .value = "m/44'/60'/0'/0", + }, + {.item = "Staking path", .value = "m/1852'/1815'/0'/2/0"}, +}; + +static nbgl_contentTagValueList_t pairList = {.nbMaxLinesForValue = 0, + .nbPairs = 2, + .wrapping = true, + .pairs = (nbgl_contentTagValue_t *) pairs}; + +/********************** + * VARIABLES + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +static void displayAddressCallback(bool confirm) +{ + if (confirm) { + nbgl_useCaseStatus("Address verified", true, app_fullCardano); + } + else { + nbgl_useCaseStatus("Address verification\ncancelled", false, app_fullCardano); + } +} + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * @brief Cardano application address verification with extra info + * + */ +void app_cardanoVerifyAddress(void) +{ + nbgl_useCaseAddressReview( + "addr1qyyp7u0s2zcxadzd5w9w8rzpcg0rzmw07v0tkurg32y8agjnfsh6w2r9tvuq0lrsktztgj845qdcz4lpa2x4e" + "0me68mqpp4tzn", + &pairList, + &C_ic_asset_cardano_64, + "Verify Cardano\naddress", + NULL, + displayAddressCallback); +} diff --git a/tests/screenshots/src/app/wallet/app_dogecoin_start.c b/tests/screenshots/src/app/wallet/app_dogecoin_start.c new file mode 100644 index 000000000..a2c9735d8 --- /dev/null +++ b/tests/screenshots/src/app/wallet/app_dogecoin_start.c @@ -0,0 +1,162 @@ + +/** + * @file app_dogecoin_start.c + * @brief Entry point of Dogecoin application, using use case API + * + */ + +/********************* + * INCLUDES + *********************/ +#include "nbgl_debug.h" +#include "nbgl_use_case.h" +#include "apps_api.h" + +/********************* + * DEFINES + *********************/ +#define VALUE_TOKEN (FIRST_USER_TOKEN + 1) + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ +static void contentActionCallback(int token, uint8_t index, int page); + +static nbgl_contentValueExt_t extension + = {.fullValue = "bc1pkdcufjh6dxjaaa05hudvxqg5fhspfmwmp8g92gq8cv4gwwnmgrfqfd4jlg", + .explanation = NULL, + .aliasType = ENS_ALIAS}; + +static nbgl_contentTagValue_t pairs[] = { + {.item = "Amount", .value = "0.0001 BTC"}, + {.item = "To", .value = "toto.eth", .extension = &extension, .aliasValue = 1}, + {.item = "Fees", .value = "0.000000698 BTC"}, +}; + +static nbgl_contentTagValueList_t pairList = {.nbMaxLinesForValue = 0, + .nbPairs = 3, + .wrapping = true, + .pairs = (nbgl_contentTagValue_t *) pairs, + .token = VALUE_TOKEN, + .actionCallback = contentActionCallback}; + +static const char *infoTypes[] = {"Version", "Developer", "Copyright"}; +static const char *infoContents[] = {"2.0.5", "Ledger", "(c) 2024 Ledger"}; + +static const nbgl_contentInfoList_t infoContentsList + = {.nbInfos = 3, .infoTypes = infoTypes, .infoContents = infoContents}; + +static nbgl_warning_t warning = {0}; + +/********************** + * VARIABLES + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +static void validatePinCallback(const uint8_t *content, uint8_t page) +{ + UNUSED(content); + UNUSED(page); + app_fullDogecoin(); +} + +static void touchCallback(int token, uint8_t index) +{ + UNUSED(token); + UNUSED(index); +} + +static void actionCallback(void) +{ + nbgl_useCaseKeypadDigits("my title", + 4, + 8, + FIRST_USER_TOKEN, + false, + NBGL_NO_TUNE, + validatePinCallback, + touchCallback); +} + +static void contentActionCallback(int token, uint8_t index, int page) +{ + printf("contentActionCallback : token = %d, index = %d, page = %d\n", token, index, page); +} + +static void onTransactionAccept(bool confirm) +{ + if (confirm) { + nbgl_useCaseStatus("Transaction signed", true, app_fullDogecoin); + } + else { + nbgl_useCaseStatus("Transaction rejected", false, app_fullDogecoin); + } +} + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void app_dogecoinSignTransaction(bool blind, + bool dApp, + bool w3c_enabled, + bool w3c_issue, + bool w3c_threat) +{ + warning.predefinedSet = 0; + + if (blind) { + warning.predefinedSet |= 1 << BLIND_SIGNING_WARN; + } + if (w3c_enabled) { + if (w3c_issue) { + warning.predefinedSet |= 1 << W3C_ISSUE_WARN; + } + else if (w3c_threat) { + warning.predefinedSet |= 1 << W3C_THREAT_DETECTED_WARN; + } + } + UNUSED(dApp); + warning.reportProvider = "Blockaid"; + warning.providerMessage = "Losing swap"; + warning.reportUrl = "url.com/od24xz"; + + // Start review + nbgl_useCaseReviewWithWarning(TYPE_TRANSACTION, + &pairList, + &C_ic_asset_doge_64, + "Review transaction\nto send Dogecoin", + NULL, + "Sign transaction to\nsend Dogecoin?", + NULL, + &warning, + onTransactionAccept); +} + +/** + * @brief Dogecoin application start page + * + */ +void app_fullDogecoin(void) +{ + nbgl_homeAction_t homeAction = {.callback = actionCallback, + .icon = &WHEEL_ICON, + .text = "action", + .style = STRONG_HOME_ACTION}; + nbgl_useCaseHomeAndSettings( + "Dogecoin", + &C_ic_asset_doge_64, + "Use Ledger Live to create transactions and confirm them on your Europa.", + INIT_HOME_PAGE, + NULL, + &infoContentsList, + &homeAction, + NULL); +} diff --git a/tests/screenshots/src/app/wallet/app_ethereum_settings.c b/tests/screenshots/src/app/wallet/app_ethereum_settings.c new file mode 100644 index 000000000..e326e13f4 --- /dev/null +++ b/tests/screenshots/src/app/wallet/app_ethereum_settings.c @@ -0,0 +1,85 @@ + +/** + * @file app_ethereum_settings.c + * @brief Settings page of Ethereum application, using predefined pages + * + */ + +/********************* + * INCLUDES + *********************/ +#include "nbgl_debug.h" +#include "nbgl_use_case.h" +#include "apps_api.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ +enum { + SWITCH1_TOKEN = FIRST_USER_TOKEN, + SWITCH2_TOKEN, + SWITCH3_TOKEN, + SWITCH4_TOKEN +}; + +/********************** + * STATIC VARIABLES + **********************/ +static const nbgl_layoutSwitch_t switches[] = { + {.initState = false, + .text = "ENS addresses", + .subText = "Displays the resolved address of ENS domains.", + .token = SWITCH1_TOKEN, + .tuneId = TUNE_TAP_CASUAL}, + {.initState = true, + .text = "Raw messages", + .subText = "Displays raw content from EIP712 messages.", + .token = SWITCH2_TOKEN, + .tuneId = TUNE_TAP_CASUAL}, + {.initState = true, + .text = "Nonce", + .subText = "Displays nonce information in transactions.", + .token = SWITCH3_TOKEN, + .tuneId = TUNE_TAP_CASUAL}, + {.initState = true, + .text = "Debug smart contracts", + .subText = "Displays contract data details.", + .token = SWITCH4_TOKEN, + .tuneId = TUNE_TAP_CASUAL}, +}; +static const char *infoTypes[] = {"Version", "Developer"}; +static const char *infoContents[] = {"1.9.18", "Ledger"}; + +static void controlsCallback(int token, uint8_t index, int page); + +static nbgl_content_t contentsList = {.content.switchesList.nbSwitches = 4, + .content.switchesList.switches = switches, + .type = SWITCHES_LIST, + .contentActionCallback = controlsCallback}; + +/********************** + * VARIABLES + **********************/ +nbgl_genericContents_t eth_settingContents = {.contentsList = &contentsList, .nbContents = 1}; +nbgl_contentInfoList_t eth_infosList + = {.nbInfos = 2, .infoTypes = infoTypes, .infoContents = infoContents}; + +/********************** + * STATIC PROTOTYPES + **********************/ + +static void controlsCallback(int token, uint8_t index, int page) +{ + UNUSED(page); + if (token == SWITCH1_TOKEN) { + LOG_WARN(APP_LOGGER, "First switch in position %d\n", index); + } +} + +/********************** + * GLOBAL FUNCTIONS + **********************/ diff --git a/tests/screenshots/src/app/wallet/app_ethereum_share_address.c b/tests/screenshots/src/app/wallet/app_ethereum_share_address.c new file mode 100644 index 000000000..e8aa03023 --- /dev/null +++ b/tests/screenshots/src/app/wallet/app_ethereum_share_address.c @@ -0,0 +1,47 @@ + +/** + * @file app_ethereum_share_address.c + * Share address page of ethereum application + * + */ + +/********************* + * INCLUDES + *********************/ +#include +#include "nbgl_debug.h" +#include "nbgl_page.h" +#include "nbgl_obj.h" +#include "nbgl_fonts.h" +#include "apps_api.h" +#include "glyphs.h" + +/********************* + * DEFINES + *********************/ +#define BACK_TOKEN 1 +#define SETTINGS_TOKEN 2 +#define CHOICE_TOKEN 3 +#define BACK_TOKEN_2 4 + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * VARIABLES + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * @brief Ethereum Share Address page + * + */ +void app_fullEthereumShareAddress(void) {} diff --git a/tests/screenshots/src/app/wallet/app_ethereum_start.c b/tests/screenshots/src/app/wallet/app_ethereum_start.c new file mode 100644 index 000000000..0461575c6 --- /dev/null +++ b/tests/screenshots/src/app/wallet/app_ethereum_start.c @@ -0,0 +1,153 @@ + +/** + * @file app_ethereum_start.c + * @brief Entry point of Ethereum application, using predefined pages + * + */ + +/********************* + * INCLUDES + *********************/ +#include "nbgl_debug.h" +#include "nbgl_use_case.h" +#include "apps_api.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ + +static nbgl_contentTagValue_t skipPairs[] = { + {.item = "Message", + .value + = "I confirm that I have read and agreed to the terms and conditions (paraswap.io/tos3). By " + "signing this message, you confirm that you are not accessing this app from, or are a..."}, + { + .item = "Message", + .value = "...resident of the USA or any other restricted country.", + } +}; + +static nbgl_contentValueExt_t ensExtension + = {.fullValue = "0x8aE57A027c63fcA8070D1Bf38622321dE8004c67", + .explanation = NULL, + .aliasType = ENS_ALIAS}; + +static nbgl_contentTagValue_t pairs[] = { + {.item = "Send", .value = "2.325196105098179\n072 ETH"}, + {.item = "Receive Min", .value = "38,287.689054894232100909 RLB"}, + {.item = "From", .value = "rollbot.eth", .aliasValue = 1, .extension = &ensExtension}, + // {.item = "To contract", .value = "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45"}, + {.item = "Input data", + .value + = "0x04e45aaf000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000" + "0000000046eee2cc3188071c02bfc1745a6b17c656e3f3d0000000000000000000000000000000000004e45aaf0" + "00000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000GREGEGEGGGGEGEGE"}, + {.item = "Amount", .value = "8.127467 ETH"} +}; + +static nbgl_contentTagValueList_t pairList + = {.wrapping = true, .pairs = (nbgl_contentTagValue_t *) pairs}; + +static uint8_t nbPairsSent; + +/********************** + * VARIABLES + **********************/ +extern nbgl_genericContents_t eth_settingContents; +extern nbgl_contentInfoList_t eth_infosList; + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +static void onTransactionAccept(bool confirm) +{ + if (confirm) { + nbgl_useCaseReviewStatus(STATUS_TYPE_MESSAGE_SIGNED, app_fullEthereum); + } + else { + nbgl_useCaseReviewStatus(STATUS_TYPE_MESSAGE_REJECTED, app_fullEthereum); + } +} + +static void onSkip(void) +{ + nbgl_useCaseReviewStreamingFinish("Sign message?", onTransactionAccept); +} + +static void onTransactionContinue(bool askMore) +{ + if (askMore) { + if (nbPairsSent == 0) { + pairList.nbPairs = 2; + pairList.pairs = &skipPairs[nbPairsSent]; + nbPairsSent += pairList.nbPairs; + nbgl_useCaseReviewStreamingContinueExt(&pairList, onTransactionContinue, onSkip); + } + else { + nbgl_useCaseReviewStreamingFinish("Sign message?", onTransactionAccept); + } + } + else { + onTransactionAccept(false); + } +} + +void app_ethereumSignMessage(void) +{ + pairList.pairs = pairs; + pairList.nbPairs = 5; + nbgl_useCaseReview(TYPE_TRANSACTION, + &pairList, + &C_ic_asset_eth_64, + "Review transaction\nto send Etherum", + NULL, + "Sign transaction to\nsend Etherum?", + onTransactionAccept); +} + +void app_ethereumSignForwardOnlyMessage(void) +{ + nbPairsSent = 0; + pairList.pairs = skipPairs; + nbgl_useCaseReviewStreamingStart(TYPE_MESSAGE | SKIPPABLE_OPERATION, + &C_ic_asset_eth_64, + "Review message", + NULL, + onTransactionContinue); +} + +void app_ethereumSignUnknownLengthMessage(void) +{ + nbPairsSent = 0; + nbgl_useCaseReviewStreamingStart( + TYPE_MESSAGE, &C_ic_asset_eth_64, "Review message", NULL, onTransactionContinue); +} + +/** + * @brief Ethereum application start page + * + */ +void app_fullEthereum(void) +{ + nbgl_useCaseHomeAndSettings("Ethereum", + &C_ic_asset_eth_64, + NULL, + INIT_HOME_PAGE, + ð_settingContents, + ð_infosList, + NULL, + NULL); +} diff --git a/tests/screenshots/src/app/wallet/app_ethereum_verify_addr.c b/tests/screenshots/src/app/wallet/app_ethereum_verify_addr.c new file mode 100644 index 000000000..8b30a72e1 --- /dev/null +++ b/tests/screenshots/src/app/wallet/app_ethereum_verify_addr.c @@ -0,0 +1,69 @@ + +/** + * @file app_ethereum_verify_addr.c + * @brief Verify Ethereum application, using predefined use cases + * + */ + +/********************* + * INCLUDES + *********************/ +#include "nbgl_debug.h" +#include "nbgl_use_case.h" +#include "apps_api.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * VARIABLES + **********************/ +static nbgl_layoutTagValue_t pairs[] = { + {.item = "tutu", .value = "myvalue1"}, + {.item = "tutu2", .value = "myvalue2"} +}; +static nbgl_contentTagValueList_t tagValueList = {.nbMaxLinesForValue = 6, + .nbPairs = 2, + .pairs = pairs, + .smallCaseForValue = false, + .wrapping = false}; + +/********************** + * STATIC PROTOTYPES + **********************/ +static void displayAddressCallback(bool confirm) +{ + if (confirm) { + nbgl_useCaseStatus("ADDRESS\nVERIFIED", true, app_fullEthereum); + } + else { + nbgl_useCaseStatus("Address verification\nrejected", false, app_fullEthereum); + } +} + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * @brief Ethereum application verify address page + * + */ +void app_ethereumVerifyAddress(void) +{ + nbgl_useCaseAddressReview("bc1pkdcufjh6dxjaaa05hudvxqg5fhspfmwmp8g92gq8cv4gwwnmgrfqfd4jlg", + &tagValueList, + &C_ic_asset_eth_64, + "Verify Ethereum\naddress", + NULL, + displayAddressCallback); +} diff --git a/tests/screenshots/src/app/wallet/app_ledger_market.c b/tests/screenshots/src/app/wallet/app_ledger_market.c new file mode 100644 index 000000000..4d7857b55 --- /dev/null +++ b/tests/screenshots/src/app/wallet/app_ledger_market.c @@ -0,0 +1,62 @@ + +/** + * @file app_ledger_market.c + * @brief Entry point of Ledger Market application, using Use Cases + * + */ + +/********************* + * INCLUDES + *********************/ +#include "nbgl_debug.h" +#include "nbgl_use_case.h" +#include "apps_api.h" + +/********************* + * DEFINES + *********************/ + +#define NB_PAGES 2 + +#define VALIDATE_TRANSACTION_TOKEN 15 + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ +static const char *infoTypes[] = {"Version", "Developer", "Copyright"}; +static const char *infoContents[] = {"2.0.5", "Ledger", "(c) 2024 Ledger"}; + +static const nbgl_contentInfoList_t infoContentsList + = {.nbInfos = 3, .infoTypes = infoTypes, .infoContents = infoContents}; + +/********************** + * VARIABLES + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * @brief Ledger Market application start page + * + */ +void app_fullLedgerMarket(void) +{ + nbgl_useCaseHomeAndSettings("Ledger Market", + &C_ic_asset_eth_64, + NULL, + INIT_HOME_PAGE, + NULL, + &infoContentsList, + NULL, + NULL); +} diff --git a/tests/screenshots/src/app/wallet/app_monero_settings.c b/tests/screenshots/src/app/wallet/app_monero_settings.c new file mode 100644 index 000000000..aff64eed7 --- /dev/null +++ b/tests/screenshots/src/app/wallet/app_monero_settings.c @@ -0,0 +1,108 @@ + +/** + * @file app_monero_start.c + * @brief Entry point of Monero application, using predefined pages + * + */ + +/********************* + * INCLUDES + *********************/ +#include "nbgl_debug.h" +#include "nbgl_use_case.h" +#include "apps_api.h" + +/********************* + * DEFINES + *********************/ +enum { + BAR_ACCOUNT_TOKEN = FIRST_USER_TOKEN, + BAR_NETWORK_TOKEN, + BAR_24_WORDS_TOKEN, + BAR_RESET_TOKEN, + ACCOUNT_TOKEN +}; + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ + +static const char *infoTypes[] = {"Version", "Developer"}; +static const char *infoContents[] = {"1.9.18", "Ledger"}; + +static const char *barTexts[] = {"Select account", "Select network", "Show 25 words", "Reset"}; +static const uint8_t barTokens[] + = {BAR_ACCOUNT_TOKEN, BAR_NETWORK_TOKEN, BAR_24_WORDS_TOKEN, BAR_RESET_TOKEN}; +static const char *accountNames[] = {"0+", "1", "2", "3", "4", "5", "6", "7"}; + +/********************** + * VARIABLES + **********************/ + +/********************** + * STATIC FUNCTIONS + **********************/ + +static bool navCallback(uint8_t page, nbgl_pageContent_t *content) +{ + if (page == 0) { + content->type = BARS_LIST; + content->barsList.nbBars = 4; + content->barsList.barTexts = barTexts; + content->barsList.tokens = (uint8_t *) barTokens; + } + else if (page == 1) { + content->type = INFOS_LIST; + content->infosList.nbInfos = 2; + content->infosList.infoTypes = infoTypes; + content->infosList.infoContents = infoContents; + } + return true; +} + +static bool accountNavCallback(uint8_t page, nbgl_pageContent_t *content) +{ + if (page == 0) { + content->type = CHOICES_LIST; + content->choicesList.nbChoices = 4; + content->choicesList.localized = false; + content->choicesList.names = accountNames; + content->choicesList.token = ACCOUNT_TOKEN; + } + else if (page == 1) { + content->type = CHOICES_LIST; + content->choicesList.nbChoices = 4; + content->choicesList.localized = false; + content->choicesList.names = accountNames + 4; + content->choicesList.token = ACCOUNT_TOKEN; + } + return true; +} + +static void controlsCallback(int token, uint8_t index) +{ + UNUSED(index); + if (token == BAR_ACCOUNT_TOKEN) { + nbgl_useCaseNavigableContent( + "Select account", 0, 2, app_moneroSettings, accountNavCallback, controlsCallback); + } + else if (token == ACCOUNT_TOKEN) { + } +} + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * @brief Monero application settings page + * + */ +void app_moneroSettings(void) +{ + nbgl_useCaseNavigableContent("Monero", 0, 2, app_fullMonero, navCallback, controlsCallback); +} diff --git a/tests/screenshots/src/app/wallet/app_monero_start.c b/tests/screenshots/src/app/wallet/app_monero_start.c new file mode 100644 index 000000000..5b9577224 --- /dev/null +++ b/tests/screenshots/src/app/wallet/app_monero_start.c @@ -0,0 +1,185 @@ + +/** + * @file app_monero_start.c + * @brief Entry point of Monero application, using predefined pages + * + */ + +/********************* + * INCLUDES + *********************/ +#include "nbgl_debug.h" +#include "nbgl_use_case.h" +#include "apps_api.h" + +/********************* + * DEFINES + *********************/ +enum { + BAR_ACCOUNT_TOKEN = FIRST_USER_TOKEN, + BAR_NETWORK_TOKEN, + BAR_24_WORDS_TOKEN, + BAR_RESET_TOKEN, + ACCOUNT_TOKEN +}; + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ +static nbgl_contentTagValue_t *getPair(uint8_t pairIndex); + +static uint8_t nbPairsSent = 0; +static uint8_t currentPairIndex = 0; + +static nbgl_contentValueExt_t extension + = {.fullValue = "bc1pkdcufjh6dxjaaa05hudvxqg5fhspfmwmp8g92gq8cv4gwwnmgrfqfd4jlg", + .explanation = NULL, + .aliasType = ENS_ALIAS}; + +static nbgl_contentValueExt_t extension2 + = {.fullValue = "arersnjvchvbhjvsbjdbvnfdlbvnscknblnldkfnlkbndfnblfdknblkndknb", + .explanation = NULL, + .aliasType = ADDRESS_BOOK_ALIAS}; + +static nbgl_contentTagValue_t pairs[] = { + {.item = "To", .value = "toto.mon", .extension = &extension, .aliasValue = 1}, + {.item = "Amount", .value = "8.127467 MON"}, + {.item = "To2", .value = "toto2.mon", .extension = &extension2, .aliasValue = 1}, + {.item = "Amount2", .value = "8.127467 MON"}, + {.item = "Fees", .value = "0.175157 MON"} +}; + +static nbgl_contentTagValueList_t pairList + = {.nbMaxLinesForValue = 0, .wrapping = true, .callback = getPair}; + +static const char *infoTypes[] = {"Version", "Developer"}; +static const char *infoContents[] = {"1.9.18", "Ledger"}; + +static const char *barTexts[] = {"Select account", "Select network", "Show 25 words", "Reset"}; +static const uint8_t barTokens[] + = {BAR_ACCOUNT_TOKEN, BAR_NETWORK_TOKEN, BAR_24_WORDS_TOKEN, BAR_RESET_TOKEN}; +static const char *accountNames[] = {"0+", "1", "2", "3", "4", "5", "6", "7"}; + +static void barControlsCallback(int token, uint8_t index, int page); + +static nbgl_content_t contentsList = {.type = BARS_LIST, + .content.barsList.nbBars = 4, + .content.barsList.tokens = barTokens, + .content.barsList.barTexts = barTexts, + + .contentActionCallback = barControlsCallback}; +static nbgl_content_t accountContentsList = {.type = CHOICES_LIST, + .content.choicesList.initChoice = 0, + .content.choicesList.names = accountNames, + .content.choicesList.nbChoices = 8, + .content.choicesList.token = ACCOUNT_TOKEN, + .contentActionCallback = barControlsCallback}; +static nbgl_genericContents_t accountContents + = {.contentsList = &accountContentsList, .nbContents = 1}; + +static nbgl_genericContents_t mon_settingContents + = {.contentsList = &contentsList, .nbContents = 1}; +static nbgl_contentInfoList_t mon_infosList + = {.nbInfos = 3, .infoTypes = infoTypes, .infoContents = infoContents}; + +/********************** + * VARIABLES + **********************/ + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void onTransactionAccept(bool confirm) +{ + if (confirm) { + nbgl_useCaseStatus("Transaction signed", true, app_fullCardano); + } + else { + nbgl_useCaseStatus("Transaction rejected", false, app_fullCardano); + } +} + +static nbgl_contentTagValue_t *getPair(uint8_t pairIndex) +{ + return &pairs[currentPairIndex + pairIndex]; +} + +static void onSkip(void) +{ + nbgl_useCaseReviewStreamingFinish("Sign transaction to transfer Monero?", onTransactionAccept); +} + +static void onTransactionContinue(bool askMore) +{ + if (askMore) { + if (nbPairsSent < 5) { + if (nbPairsSent == 0) { + pairList.nbPairs = 4; + } + else { + currentPairIndex += nbPairsSent; + pairList.nbPairs = 1; + } + nbPairsSent += pairList.nbPairs; + nbgl_useCaseReviewStreamingContinueExt(&pairList, onTransactionContinue, onSkip); + } + else { + nbgl_useCaseReviewStreamingFinish("Sign transaction to transfer Monero?", + onTransactionAccept); + } + } + else { + onTransactionAccept(false); + } +} + +static void quitAccountCallback(void) +{ + nbgl_useCaseHomeAndSettings( + "Monero", &C_ic_asset_monero_64, NULL, 0, &mon_settingContents, &mon_infosList, NULL, NULL); +} + +static void barControlsCallback(int token, uint8_t index, int page) +{ + UNUSED(page); + if (token == BAR_ACCOUNT_TOKEN) { + LOG_WARN(APP_LOGGER, "First switch in position %d\n", index); + nbgl_useCaseGenericConfiguration( + "Select account", 0, &accountContents, quitAccountCallback); + } + else if (token == ACCOUNT_TOKEN) { + } +} + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void app_moneroSignForwardOnlyMessage(void) +{ + nbPairsSent = 0; + currentPairIndex = 0; + nbgl_useCaseReviewStreamingStart( + TYPE_TRANSACTION, &C_ic_asset_monero_64, "Review transaction", NULL, onTransactionContinue); +} + +/** + * @brief Monero application start page + * + */ +void app_fullMonero(void) +{ + nbgl_useCaseHomeAndSettings("Monero", + &C_ic_asset_monero_64, + NULL, + INIT_HOME_PAGE, + &mon_settingContents, + &mon_infosList, + NULL, + NULL); +} diff --git a/tests/screenshots/src/app/wallet/app_recovery_check_start.c b/tests/screenshots/src/app/wallet/app_recovery_check_start.c new file mode 100644 index 000000000..1d625fcd9 --- /dev/null +++ b/tests/screenshots/src/app/wallet/app_recovery_check_start.c @@ -0,0 +1,188 @@ + +/** + * @file app_xrp_start.c + * @brief Entry point of Xrp application, using predefined pages + * + */ + +/********************* + * INCLUDES + *********************/ +#include "nbgl_debug.h" +#include "nbgl_draw.h" +#include "nbgl_front.h" +#include "nbgl_touch.h" +#include "nbgl_use_case.h" +#include "apps_api.h" + +/********************* + * DEFINES + *********************/ +#define NB_COLS 4 +#define NB_ROWS 6 +#define KEY_WIDTH (SCREEN_WIDTH / NB_COLS) +#define KEY_HEIGHT (SCREEN_HEIGHT / NB_ROWS) + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ +static uint32_t mask; +static const char *infoTypes[] = {"Version", "Developer", "Copyright"}; +static const char *infoContents[] = {"2.0.5", "Ledger", "(c) 2024 Ledger"}; + +static const nbgl_contentInfoList_t infoContentsList + = {.nbInfos = 3, .infoTypes = infoTypes, .infoContents = infoContents}; +/********************** + * VARIABLES + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void buildScreen(uint32_t digitsMask); + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +static uint8_t getKeyIndex(uint16_t x, uint16_t y) +{ + uint8_t row = 0; + + while (row < NB_ROWS) { + if (y < ((row + 1) * KEY_HEIGHT)) { + return row * NB_COLS + x / KEY_WIDTH; + } + row++; + } + return 0xFF; +} + +static void touchCallback(nbgl_obj_t *obj, nbgl_touchType_t eventType) +{ + uint8_t firstIndex, lastIndex; + nbgl_touchStatePosition_t *firstPosition, *lastPosition; + + LOG_DEBUG(MISC_LOGGER, "touchCallback(): eventType = %d\n", eventType); + if ((eventType != TOUCHING) && (eventType != TOUCH_RELEASED)) { + return; + } + + if (eventType == TOUCH_RELEASED) { + LOG_WARN(MISC_LOGGER, "touchCallback(): mask = 0x%X\n", mask); + buildScreen(mask); + return; + } + if (nbgl_touchGetTouchedPosition(obj, &firstPosition, &lastPosition) == false) { + return; + } + + // use positions relative to keypad position + firstIndex = getKeyIndex(firstPosition->x - obj->area.x0, firstPosition->y - obj->area.y0); + if (firstIndex > ((NB_COLS * NB_ROWS) - 1)) { + return; + } + lastIndex = getKeyIndex(lastPosition->x - obj->area.x0, lastPosition->y - obj->area.y0); + if (lastIndex > ((NB_COLS * NB_ROWS) - 1)) { + return; + } + + // LOG_WARN(MISC_LOGGER,"touchCallback(): firstIndex = %d, lastIndex = %d\n",firstIndex, + // lastIndex); + mask &= ~(1 << lastIndex); +} + +static void keypadDrawGrid(void) +{ + nbgl_area_t rectArea; + uint8_t i; + + // clean full area + rectArea.backgroundColor = WHITE; + rectArea.x0 = 0; + rectArea.y0 = 0; + rectArea.width = SCREEN_WIDTH; + rectArea.height = SCREEN_HEIGHT; + nbgl_frontDrawRect(&rectArea); + + /// draw horizontal lines + rectArea.backgroundColor = WHITE; + rectArea.x0 = 0; + rectArea.y0 = 0; + rectArea.width = SCREEN_WIDTH; + rectArea.height = 4; + for (i = 0; i < NB_ROWS - 1; i++) { + rectArea.y0 += KEY_HEIGHT; + nbgl_frontDrawHorizontalLine(&rectArea, 0x1, BLACK); + } + + /// then draw vertical lines + rectArea.backgroundColor = BLACK; + rectArea.x0 = 0; + rectArea.y0 = 0; + rectArea.width = 1; + rectArea.height = SCREEN_HEIGHT; + for (i = 0; i < NB_COLS - 1; i++) { + rectArea.x0 += KEY_WIDTH; + nbgl_frontDrawRect(&rectArea); // 1st full line, on the left + } +} + +static void buildScreen(uint32_t digitsMask) +{ + nbgl_container_t *container; + nbgl_obj_t **screenChildren; + + mask = digitsMask; + nbgl_screenSet(&screenChildren, 1, NULL, (nbgl_touchCallback_t) touchCallback); + + container = (nbgl_container_t *) nbgl_objPoolGet(CONTAINER, 0); + screenChildren[0] = (nbgl_obj_t *) container; + + container->nbChildren = 0; + container->children = NULL; + + container->obj.area.width = SCREEN_WIDTH; + container->obj.area.height = SCREEN_HEIGHT; + container->layout = HORIZONTAL; + container->obj.alignmentMarginX = 0; + container->obj.alignmentMarginY = 0; + container->obj.alignment = NO_ALIGNMENT; + container->obj.alignTo = NULL; + container->obj.touchMask = (1 << TOUCHING) | (1 << TOUCH_RELEASED); + + nbgl_screenRedraw(); + + // At first, draw grid + keypadDrawGrid(); +} + +static void onAction(void) +{ + // 24 bits for 24 digits + buildScreen(0xFFFFFF); +} + +/** + * @brief Recovery Check application start page + * + */ +void app_fullRecoveryCheck(void) +{ + nbgl_homeAction_t homeAction + = {.callback = onAction, .icon = NULL, .text = "Start check", .style = STRONG_HOME_ACTION}; + nbgl_useCaseHomeAndSettings( + "Recovery Check", + &C_Recovery_Check_64px, + "This app lets you enter a Secret Recovery Phrase and test if it matches " + "the one present on this Ledger.", + INIT_HOME_PAGE, + NULL, + &infoContentsList, + &homeAction, + NULL); +} diff --git a/tests/screenshots/src/app/wallet/app_stellar_start.c b/tests/screenshots/src/app/wallet/app_stellar_start.c new file mode 100644 index 000000000..f63f8e4c7 --- /dev/null +++ b/tests/screenshots/src/app/wallet/app_stellar_start.c @@ -0,0 +1,143 @@ + +/** + * @file app_stellar_start.c + * @brief Entry point of Stellar application, using predefined layout + * + */ + +/********************* + * INCLUDES + *********************/ +#include "nbgl_debug.h" +#include "nbgl_use_case.h" +#include "apps_api.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ +static nbgl_contentTagValue_t pairs[] = { + {.item = "Memo Text", .value = "hello world"}, + {.item = "Max Fee", .value = "0.00003 XLM"}, + {.item = "Valid Before (UTC)", .value = "2024-12-12 04:12:12"}, + {.item = "Tc Source", .value = "GDUTHC...XM2FN7"}, + {.centeredInfo = true, .item = "Review transaction info", .value = "1 of 2"}, + {.item = "Send", .value = "922,337,203,685 XLM"}, + {.item = "Destination", .value = "GDRMNAIPTNIJWJSL6JOF76CJORN47TDVMWERTXO2G2WKOMXGNHUFL5QX"}, + {.item = "Op Source", .value = "GDUTHC...XM2FN7"}, + {.centeredInfo = true, .item = "Review operation type", .value = "2 of 2"}, + {.item = "Operation Type", .value = "Set Options"}, + {.item = "Home Domain", .value = "stellar.org"} +}; + +static nbgl_contentTagValueList_t pairList = {.nbMaxLinesForValue = 0, + .nbPairs = 11, + .wrapping = true, + .pairs = (nbgl_contentTagValue_t *) pairs}; + +static const char *infoTypes[] = {"Version", "Developer", "Copyright"}; +static const char *infoContents[] = {"2.0.5", "Ledger", "(c) 2024 Ledger"}; + +static const nbgl_contentInfoList_t infoContentsList + = {.nbInfos = 3, .infoTypes = infoTypes, .infoContents = infoContents}; + +static uint8_t nbPairsSent; + +/********************** + * VARIABLES + **********************/ + +/********************** + * STATIC FUNCTIONS + **********************/ +static void onTransactionAccept(bool confirm) +{ + if (confirm) { + nbgl_useCaseStatus("Transaction signed", true, app_fullStellar); + } + else { + nbgl_useCaseStatus("Transaction rejected", false, app_fullStellar); + } +} + +static void onSkip(void) +{ + nbgl_useCaseReviewStreamingFinish("Sign transaction to transfer Stellar?", onTransactionAccept); +} + +static void onTransactionContinue(bool askMore) +{ + if (askMore) { + if (nbPairsSent < 11) { + if (nbPairsSent == 0) { + pairList.nbPairs = 5; + } + else if (nbPairsSent == 5) { + pairList.nbPairs = 4; + } + else if (nbPairsSent == 9) { + pairList.nbPairs = 2; + } + pairList.pairs = &pairs[nbPairsSent]; + nbPairsSent += pairList.nbPairs; + nbgl_useCaseReviewStreamingContinueExt(&pairList, onTransactionContinue, onSkip); + } + else { + nbgl_useCaseReviewStreamingFinish("Sign transaction to transfer Stellar?", + onTransactionAccept); + } + } + else { + onTransactionAccept(false); + } +} + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void app_stellarSignTransaction(void) +{ + // Start review + nbgl_useCaseReview(TYPE_TRANSACTION, + &pairList, + &C_ic_asset_stellar_64, + "Review transaction", + NULL, + "Sign transaction?", + onTransactionAccept); +} + +void app_stellarSignStreamedTransaction(void) +{ + nbPairsSent = 0; + nbgl_useCaseReviewStreamingStart(TYPE_TRANSACTION, + &C_ic_asset_stellar_64, + "Review transaction", + NULL, + onTransactionContinue); +} + +/** + * @brief Stellar application start page + * + */ +void app_fullStellar(void) +{ + nbgl_useCaseHomeAndSettings( + "Stellar", + &C_ic_asset_stellar_64, + "Use Ledger Live to create transactions and confirm them on your Europa.", + INIT_HOME_PAGE, + NULL, + &infoContentsList, + NULL, + NULL); +} diff --git a/tests/screenshots/src/app/wallet/app_tron_settings.c b/tests/screenshots/src/app/wallet/app_tron_settings.c new file mode 100644 index 000000000..1826f64cd --- /dev/null +++ b/tests/screenshots/src/app/wallet/app_tron_settings.c @@ -0,0 +1,190 @@ + +/** + * @file app_tron_settings.c + * @brief Settings page of Tron application, using predefined pages + * + */ + +/********************* + * INCLUDES + *********************/ +#include "nbgl_debug.h" +#include "nbgl_page.h" +#include "nbgl_obj.h" +#include "apps_api.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ +enum { + QUIT_TOKEN, + NAV_TOKEN, + BAR_ACCOUNT_TOKEN, + BAR_NETWORK_TOKEN, + QUIT_ACCOUNTS_TOKEN, + ACCOUNT_TOKEN, + NAV_ACCOUNTS_TOKEN, + QUIT_NETWORKS_TOKEN, + NETWORK_TOKEN +}; + +/********************** + * STATIC VARIABLES + **********************/ +static nbgl_page_t *pageContext; + +static const char *infoTypes[] = {"Tron", "Spec", "App"}; +static const char *infoContents[] = {"(c) 2022 Ledger SAS", "1.0", "1.7.8"}; +static const char *accountNames[] = {"0+", "1", "2", "3", "4", "5", "6", "7", "8", "9"}; +static const char *networkNames[] = {"Main network", "Stage network", "Test network"}; +static const char *barTexts[] = {"Select account", "Select network"}; +static const uint8_t barTokens[] = {BAR_ACCOUNT_TOKEN, BAR_NETWORK_TOKEN}; +static uint8_t currentPage; +static uint8_t currentAccountPage; +static uint8_t chosenAccount = 0; + +/********************** + * VARIABLES + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void pageCallback(int token, uint8_t index); + +static void displayPage(uint8_t page) +{ + nbgl_pageNavigationInfo_t info = {.activePage = page, + .nbPages = 2, + .navType = NAV_WITH_BUTTONS, + .navWithButtons.navToken = NAV_TOKEN, + .navWithButtons.quitButton = true, + .quitToken = QUIT_TOKEN}; + nbgl_pageContent_t content = {.title = "Tron settings", .isTouchableTitle = false}; + if (page == 0) { + content.type = BARS_LIST; + content.barsList.nbBars = 2; + content.barsList.barTexts = barTexts; + content.barsList.tokens = (uint8_t *) barTokens; + } + else if (page == 1) { + content.type = INFOS_LIST; + content.infosList.nbInfos = 3; + content.infosList.infoTypes = infoTypes; + content.infosList.infoContents = infoContents; + } + if (pageContext != NULL) { + nbgl_pageRelease(pageContext); + } + pageContext = nbgl_pageDrawGenericContent(&pageCallback, &info, &content); +} + +static void displayAccountPage(uint8_t page) +{ + nbgl_pageNavigationInfo_t info = {.activePage = page, + .nbPages = 2, + .navType = NAV_WITH_BUTTONS, + .navWithButtons.navToken = NAV_ACCOUNTS_TOKEN, + .navWithButtons.quitButton = false}; + nbgl_pageContent_t content = { + .title = "Select account", + .isTouchableTitle = true, + .titleToken = QUIT_ACCOUNTS_TOKEN, + .type = CHOICES_LIST, + .choicesList.nbChoices = 5, + .choicesList.localized = false, + .choicesList.names = accountNames, + .choicesList.token = ACCOUNT_TOKEN, + }; + if (page == 0) { + if (chosenAccount >= 5) { + content.choicesList.initChoice = 0xFF; // no choice + } + else { + content.choicesList.initChoice = chosenAccount; + } + } + else if (page == 1) { + if (chosenAccount < 5) { + content.choicesList.initChoice = 0xFF; // no choice + } + else { + content.choicesList.initChoice = chosenAccount - 5; + } + content.choicesList.nbChoices = 5; + content.choicesList.names = &accountNames[5]; + } + currentAccountPage = page; + if (pageContext != NULL) { + nbgl_pageRelease(pageContext); + } + pageContext = nbgl_pageDrawGenericContent(&pageCallback, &info, &content); +} + +static void pageCallback(int token, uint8_t index) +{ + LOG_WARN(APP_LOGGER, "pageTouchCallback(): token = %d, index = %d\n", token, index); + if (token == QUIT_TOKEN) { + nbgl_pageRelease(pageContext); + app_fullTron(); + } + else if (token == NAV_TOKEN) { + if (index == EXIT_PAGE) { + nbgl_pageRelease(pageContext); + app_fullTron(); + } + else if (index < 2) { + displayPage(index); + } + } + else if (token == BAR_ACCOUNT_TOKEN) { + displayAccountPage(0); + } + else if (token == BAR_NETWORK_TOKEN) { + nbgl_pageContent_t content = {.title = "Select network", + .isTouchableTitle = true, + .titleToken = QUIT_NETWORKS_TOKEN, + .type = CHOICES_LIST, + .choicesList.nbChoices = 3, + .choicesList.localized = false, + .choicesList.names = networkNames, + .choicesList.token = NETWORK_TOKEN}; + if (pageContext != NULL) { + nbgl_pageRelease(pageContext); + } + pageContext = nbgl_pageDrawGenericContent(&pageCallback, NULL, &content); + } + else if (token == ACCOUNT_TOKEN) { + chosenAccount = index + currentAccountPage * 5; + } + else if (token == QUIT_ACCOUNTS_TOKEN) { + displayPage(0); + } + else if (token == NAV_ACCOUNTS_TOKEN) { + displayAccountPage(index); + } + else if (token == NETWORK_TOKEN) { + } + else if (token == QUIT_NETWORKS_TOKEN) { + displayPage(0); + } +} + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * @brief Tron application settings page + * + */ +void app_tronSettings(void) +{ + currentPage = 0; + pageContext = NULL; + displayPage(0); +} diff --git a/tests/screenshots/src/app/wallet/app_tron_start.c b/tests/screenshots/src/app/wallet/app_tron_start.c new file mode 100644 index 000000000..c297aff42 --- /dev/null +++ b/tests/screenshots/src/app/wallet/app_tron_start.c @@ -0,0 +1,45 @@ + +/** + * @file app_tron_start.c + * @brief Entry point of Tron application, using predefined pages + * + */ + +/********************* + * INCLUDES + *********************/ +#include "nbgl_debug.h" +#include "nbgl_page.h" +#include "apps_api.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * VARIABLES + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void app_tronSignMessage(void) {} + +/** + * @brief Tron application start page + * + */ +void app_fullTron(void) {} diff --git a/tests/screenshots/src/app/wallet/app_xrp_start.c b/tests/screenshots/src/app/wallet/app_xrp_start.c new file mode 100644 index 000000000..533a7d8bc --- /dev/null +++ b/tests/screenshots/src/app/wallet/app_xrp_start.c @@ -0,0 +1,194 @@ + +/** + * @file app_xrp_start.c + * @brief Entry point of Xrp application, using predefined pages + * + */ + +/********************* + * INCLUDES + *********************/ +#include "nbgl_debug.h" +#include "nbgl_use_case.h" +#include "apps_api.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ +static const nbgl_layoutTagValue_t pairs[] = { + {.item = "Output", .value = "1"}, + {.item = "Amount", .value = "BTC 0.0011"}, + { + .item = "To", + .value = "CELO 0.013563782407139377", + }, + {.item = "Amount2", .value = "BTC 0.0011"}, + { + .item = "To2", + .value = "bc1pkdcufjh6dxjaaa05hudvxqg5fhspfmwmp8g92gq8cv4gwwnmgrfqfd4jlg", + }, + {.item = "msg/value/funds", + .value + = "{{\"start\":\"{amount\":\"111000000000\",\"proof\"[\"166a71e8dd11c23sfs64804{\"claim\":\"{" + "am{\"claim\":\"{amount\":\"" + "112000000000\",\"proof\"[\"166a71e8dd11c2304{\"claim\":\"{am{\"claim\":\"{amount\":\"" + "113000000000\",\"proof\"[\"166a71e8dd11c2304{\"claim\":\"{am{\"claim\":\"{amount\":\"" + "114000000000\",\"proof\"[\"166a71e8dd11c2304{\"claim\":\"{am{\"claim\":\"{amount\":\"" + "115000000000\",\"proof\"[\"166a71e8dd11c2304{\"claim\":\"{am{\"claim\":\"{amount\":\"" + "116000000000\",\"proof\"[\"166a71e8dd11c2304{\"claim\":\"{am{\"claim\":\"{amount\":\"" + "117000000000\",\"proof\"[\"166a71e8dd11c2304{\"claim\":\"{am{\"claim\":\"{amount\":\"" + "118000000000\",\"proof\"[\"166a71e8dd11c2304{\"claim\":\"{am{\"claim\":\"{amount\":\"" + "119000000000\",\"proof\"[\"166a71e8dd11c2304{\"claim\":\"{am{\"claim\":\"{amount\":\"" + "11A000000000\",\"proof\"[\"166a71e8dd11c2304{\"claim\":\"{am{\"claim\":\"{amount\":\"" + "11B000000000\",\"proof\"[\"166a71e8dd11c2304{\"claim\":\"{am{\"claim\":\"{amount\":\"" + "11C000000000\",\"proof\"[\"166a71e8dd11c2304{\"claim\":\"{am{\"claim\":\"{amount\":\"" + "11D000000000\",\"proof\"[\"166a71e8dd11c2304{\"claim\":\"{am{\"claim\":\"{amount\":\"" + "11E000000000\",\"proof\"[\"166a71e8dd11c2304{\"claim\":\"{am{\"claim\":\"{amount\":\"" + "11F000000000\",\"proof\"[\"166a71e8dd11c2304{\"claim\":\"{am{\"claim\":\"{amount\":\"" + "120000000000\",\"proof\"[\"166a71e8dd11c2304{\"claipra"} +}; + +static const char *infoTypes[2] = {"Contract owner", "Contract address"}; +static const char *infoValues[2] + = {"Uniswap Labs\nwww.uniswap.org", "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"}; + +static void onTipBox(int token, uint8_t index, int page); + +static void long_press_callback(int token, uint8_t index, int page) +{ + (void) token; + (void) index; + (void) page; + nbgl_useCaseReviewStatus(STATUS_TYPE_TRANSACTION_SIGNED, app_fullXrp); +} + +static nbgl_content_t contentsList[] = { + {.type = EXTENDED_CENTER, + .contentActionCallback = onTipBox, + .content.extendedCenter.contentCenter.icon = &C_ic_asset_eth_64, + .content.extendedCenter.contentCenter.title = "Review swap with Uniswap"}, + {.type = TAG_VALUE_LIST, + .contentActionCallback = NULL, + .content.tagValueList.nbMaxLinesForValue = 0, + .content.tagValueList.nbPairs = 6, + .content.tagValueList.wrapping = true, + .content.tagValueList.pairs = (nbgl_layoutTagValue_t *) pairs}, + {.type = INFO_LONG_PRESS, + .contentActionCallback = long_press_callback, + .content.infoLongPress.icon = &C_ic_asset_eth_64, + .content.infoLongPress.text = "Confirm transaction\nXrp send", + .content.infoLongPress.longPressText = "Hold to send", + .content.infoLongPress.longPressToken = FIRST_USER_TOKEN}, +}; + +static nbgl_contentTagValueList_t tagValueList = {.nbMaxLinesForValue = 0, + .nbPairs = 6, + .wrapping = true, + .pairs = (nbgl_layoutTagValue_t *) pairs}; + +static bool lightReview; + +/********************** + * VARIABLES + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +static void onConfirmAbandon(void) +{ + nbgl_useCaseStatus("Transaction rejected", false, app_fullXrp); +} + +static void onCancel(void) +{ + nbgl_useCaseConfirm( + "Reject transaction?", NULL, "Yes, Reject", "Go back to transaction", onConfirmAbandon); +} + +static void onTransactionAccept(bool confirm) +{ + if (confirm) { + nbgl_useCaseStatus("Transaction signed", true, app_fullXrp); + } + else { + nbgl_useCaseStatus("Transaction rejected", false, app_fullXrp); + } +} + +static void onTipBox(int token, uint8_t index, int page) +{ + static nbgl_genericContents_t contentsInfos = {0}; + static nbgl_content_t contentsInfosList = {0}; + + UNUSED(token); + UNUSED(index); + UNUSED(page); + + // Values to be reviewed + contentsInfosList.type = INFOS_LIST; + contentsInfosList.content.infosList.infoTypes = infoTypes; + contentsInfosList.content.infosList.infoContents = infoValues; + contentsInfosList.content.infosList.nbInfos = 2; + + contentsInfos.contentsList = &contentsInfosList; + contentsInfos.nbContents = 1; + nbgl_useCaseGenericConfiguration("Privacy Report", 0, &contentsInfos, app_xrpSignMessageLight); +} + +void app_xrpSignMessage(void) +{ + nbgl_tipBox_t tipBox = {.icon = &C_Important_Circle_64px, + .text = "You're interacting with a smart contract from Uniswap Labs.", + .modalTitle = "Smart contract information", + .infos.nbInfos = 2, + .infos.infoTypes = infoTypes, + .infos.infoContents = infoValues, + .type = INFOS_LIST}; + + lightReview = false; + nbgl_useCaseAdvancedReview(TYPE_TRANSACTION, + &tagValueList, + &C_ic_asset_eth_64, + "Review swap with Uniswap", + NULL, + "Sign transaction to\nsend Etherum?", + &tipBox, + onTransactionAccept); +} + +void app_xrpSignMessageLight(void) +{ + nbgl_genericContents_t contents + = {.callbackCallNeeded = false, .contentsList = contentsList, .nbContents = 3}; + + lightReview = true; + contentsList[0].content.extendedCenter.tipBox.text + = "You're interacting with a smart contract from Uniswap Labs."; + contentsList[0].content.extendedCenter.tipBox.icon = &C_Important_Circle_64px; + contentsList[0].content.extendedCenter.tipBox.token = FIRST_USER_TOKEN; + + nbgl_useCaseGenericReview(&contents, "Reject", onCancel); +} + +/** + * @brief Xrp application start page + * + */ +void app_fullXrp(void) +{ + nbgl_useCaseHomeAndSettings("Xrp", &C_xrp_32px, NULL, INIT_HOME_PAGE, NULL, NULL, NULL, NULL); +} diff --git a/tests/screenshots/src/app/wallet/apps_api.h b/tests/screenshots/src/app/wallet/apps_api.h new file mode 100644 index 000000000..d57d2f8d6 --- /dev/null +++ b/tests/screenshots/src/app/wallet/apps_api.h @@ -0,0 +1,59 @@ +/** + * @file apps_api.h + * @brief Header for Full app + * + */ + +#ifndef APP_FULL_H +#define APP_FULL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "nbgl_types.h" +#include "glyphs.h" +#include "os_helpers.h" + +// Fake coin apps +void app_fullBitcoin(void); +void app_bitcoinInfos(void); +void app_bitcoinSignTransaction(void); +void app_bitcoinVerifyAddress(void); +void app_fullCardano(void); +void app_cardanoSignTransaction(void); +void app_cardanoVerifyAddress(void); +void app_fullEthereum(void); +void app_ethereumSettings(void); +void app_ethereumSignMessage(void); +void app_ethereumSignForwardOnlyMessage(void); +void app_ethereumSignUnknownLengthMessage(void); +void app_ethereumVerifyAddress(void); +void app_fullEthereumShareAddress(void); +void app_fullAlgorand(void); +void app_fullStellar(void); +void app_stellarSignTransaction(void); +void app_stellarSignStreamedTransaction(void); +void app_fullTron(void); +void app_fullXrp(void); +void app_xrpSignMessage(void); +void app_xrpSignMessageLight(void); +void app_tronSignMessage(void); +void app_tronSettings(void); +void app_fullMonero(void); +void app_moneroSignForwardOnlyMessage(void); +void app_moneroSettings(void); +void app_fullRecoveryCheck(void); +void app_fullLedgerMarket(void); +void app_fullDogecoin(void); +void app_dogecoinSignTransaction(bool blind, + bool dApp, + bool w3c_enabled, + bool w3c_issue, + bool w3c_threat); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* APP_FULL_H */ diff --git a/tests/screenshots/src/app/wallet/glyphs/Algorand32px.bmp b/tests/screenshots/src/app/wallet/glyphs/Algorand32px.bmp new file mode 100755 index 0000000000000000000000000000000000000000..02106a0a6d68123c06c479eba4c0baaf0c69b0d1 GIT binary patch literal 3126 zcmdr`F%rTs3=1<%OpJ`Ypbuc@|IYzkU3@+%ahsGb^e#PFk|jT1)1}Pcg#QEYzPwYp z0pWvwJ=-&)l=u97r)4zK|GBzq^K}F6H;}7tPHZG$^eq5VqZDysBPq`()hj5(c>|+i0qi&#rR_JCO$&WF5R3#>wtC;*h&qwK= zit5cuj#X$*i#a+Uy#p1kYg;^3L>oyqM#C1YA#p2Lqcg!th)Q6RsH&tPZc}L>A8DFn z70Mgq2W$tes4Blb)}7Uy7U^4Epp~Y4S8OzElrzybj6F@ka3(EkG8ppg9JOL+Agv#r MkGfbv?SD3Y07~yr;s5{u literal 0 HcmV?d00001 diff --git a/tests/screenshots/src/app/wallet/glyphs/LedgerMarket32px.bmp b/tests/screenshots/src/app/wallet/glyphs/LedgerMarket32px.bmp new file mode 100755 index 0000000000000000000000000000000000000000..be9f6f49d2d79416d7a8c2f3cbbf9f665f41f76c GIT binary patch literal 3126 zcmeH_u?~PB3`B7^&W?_Lfgj-RSNmbwfa2wV+PGR0%->H= zrW&K?L;1*-o)7{pt5wIdSrhzWn_IO+t0(dn26@ueF40WsZ0Qqj}|NyS3U Os8ctD%L=-#?FpQi_O#~! literal 0 HcmV?d00001 diff --git a/tests/screenshots/src/app/wallet/glyphs/Monero32px.bmp b/tests/screenshots/src/app/wallet/glyphs/Monero32px.bmp new file mode 100755 index 0000000000000000000000000000000000000000..30cadc79949fd491b25aa531e663713d41414617 GIT binary patch literal 4150 zcmcJS%T5$Q6oz%}#`pv-eF7f9z0csrg)vGrve1Nu8Wx}~G$AYqNIOV^f>DqKf*4G6 zXVe7=1Tc=5C<-E1xr^UF_L-bC)iXT;C;8`8)w%qqs;jGOj-RMHe4rpz{2l{GV;n5B z=wLr`=pX16WILOz;kF9?5Of(_0F9s{MiaUQRLcjLKh0gq3eijC?F7x>2iPuwEo|R_ z>S`o*Uh)4~!5TS+Krc`YrFcWO8Dj2$`|xY+vdRLEqr~NbbSQTvW0T~VBsMv?X#>Bk)s<}Ua$KYf0SbD zhd^gQSs}jGgMbbTVmtyN{$6}WfZoxEz}Ka=*39$E$MakyR_B|1-zD&Xep76VXear5 zu#JK(`5+VcbOB#Ok)QFqpdEOg*TiiyrnB6~sr?kFKD8s@`Nch^SlMIIng1R?UxVkr zmdIgz)r_A7);FETuQk`aJ$GC}VJ&d7 z?N@#GcL=sp{4Fj|IsRpQbr;atX1=ENy~;tplq`VpH2w&i?k$$XbZGvXZx8qy@VB@b zY~Aqx@-;A)#xL&kfqY5+S$wpgJ_US@+w>GZ+N(2G1N}7qI{9G#naJz;HK!r)A>m_u zA^ip40aAaD^N09#7t#Bo_*^2l@#{Gc1HIQFo>F64tLLQGdW87fu@&|!cAZ-&`^%NX?S-|HI0-9T=gkLB)Os^KI4dS-fuDp}uG8<(PN5~njy-zfiX zhB!iF`c^yvIt!b6x-d literal 0 HcmV?d00001 diff --git a/tests/screenshots/src/app/wallet/glyphs/RecoveryCheck32px.bmp b/tests/screenshots/src/app/wallet/glyphs/RecoveryCheck32px.bmp new file mode 100755 index 0000000000000000000000000000000000000000..75c88d6c8c05ad9acc0162a162af4bc3e57f91ef GIT binary patch literal 3126 zcmeH@Ne;p=3`N5pbaM_3wEzB<%;W+~FNzKgVdkzF& zk_^>OkwMf2C#!1MNd^@{DkCyJ_9l={L}=NN?_ZI@?FFp1H$?Y{kY7*sVCku&N>%K2 z5!$h6mCy<)*)Y^0+!`x<0oqQ_FTpL79&gu-ZMGSVhZJnXKc7-XG?>2K~#7F?VEvd zn=lxKUws|GnV@VCkH82XffYP~?Fpb0*qy*5zy`Pz$en<60?N-pDii|(34~<6yLVV8 zAbL+oKu!QFD=*DAsIe`ytMp!#y=q(IL5+_}#G~5Y3;n6KcdmXm_J!g?`9ef(2`~eG zAqF5{sHklLCV(#K5I}B;LNte=5?-McKyH~B)aRe_1PnlKsTdSO*urk}wNi{64mOSX zK7|-a0Kjykb<4*97+|!qQ;0#505IOzskDa#G0Zn~D#fB%@m+vs%M5Pw2JYQqBUJ9- zJ`Im+079h)_j%_Fo32VndE*NkydFRJf-^(F^Sn|LbiNlb*mYkoRT$41W2NYK&;G0{ zfXtK$Rr56xA+W9h7HziA{D2TXv33%q@gB#q?6{X^@x6|Yo|o+X#uZ>B0BYcFw=-ql zYybmi?1r}6ZJws7s(Et(jF_>d%x76PG|c$}pi6$_jNNN~#BiEFfWEOyCfkm^-EKO~ zn+c#V1nS{uAzUt(yp6FFsj&cthR|g`2m+OMmF zr^$RH0P&xqt?Br_UrLoENlFog85BJ(eeC&s#_4o|23i+}w5C@4;amrF9{#-4qhpR! z)F;MYhOXBu4u?bMJ{o)e3yXLgjH0MC`HU&e6Jh`l7=lcSAxvbxv7qWZB&jDZRl3eo zn>`+nQ}%r4iZX_YZ+|k{@Ap%lpLO`RaxKtM#k}Fg%mJnl1NaN~`OO!^0Pb*~2VZ!_ z02&_G1rkCfRU~ki1j2K`vq#~daF<`QJ2KdHZ&!W*67d0;Zm@0z7)S)bbfa}C!U*V& z;KHLZgTl819$t94yzL>R(75D>E+L!dga{fxnc)er{Ew!5%s_zUeKsex5?n;*hm#)u wFSPWydeuwu!TtWI%Cx1|T4NthuB;M1&07*qoM6N<$g6;@uX#fBK literal 0 HcmV?d00001 diff --git a/tests/screenshots/src/app/wallet/glyphs/Tron32px.bmp b/tests/screenshots/src/app/wallet/glyphs/Tron32px.bmp new file mode 100755 index 0000000000000000000000000000000000000000..1096b61f4c1bd6de12a0cf8e3a4b42f4806971ce GIT binary patch literal 3126 zcmd6kL2klO3`EmCV%=qzoPYzc=hPgmb#ePNvcFA~{76I!%pcD@Cvdx8Ps{VV+ULdY zufO|a`7~vh%^zpK-B_0I_qgnF4t)@R-Gf&A|CYbsN32l(^c4Z_$BAz-*xZ4CFi|z)~E-EWs&)(IeTvWM@?vscBCiK)q; f$sjC%eklSiA72tIkX!4Wioc%FB!M@Xw;lZe{WNzo literal 0 HcmV?d00001 diff --git a/tests/screenshots/src/app/wallet/glyphs/ada_32px.png b/tests/screenshots/src/app/wallet/glyphs/ada_32px.png new file mode 100755 index 0000000000000000000000000000000000000000..2e883fc59f9fcb07ea4459932276afa228badf82 GIT binary patch literal 568 zcmV-80>}M{P)T6C2I2;xqfALhZV=x9bAoUKCMQVf=#r8yX?rK-N7I4H7acBqThr~%h)Mc&NGiwgbx?% z8~OyH2+R-$?`$MvF4ZOR9@Utk0l4=8e!4Yp#Hk)4vCDmcBf)L?%2~&2S~xV^!GFL) z7Z;xEBLXpH$Y_bNl{3#Ln}29-)8Tb%`~xf+xc?ooSNsCTfNVcqF)wNW0000qlgyg?Zu8RxvCY-)mVnnD5~gahPivXc^rY-)K1n<}bDU2Ie;gX-QBYgc@0VN^qTV zy}8*a$_`oEB|5}W%h@`YfgIrno><#GIt0P5#gPq!MHSe$UVBQ*7I96HTB(Y}`cStF zLNr6D_U5UzTWMKXozn85!lqzx=aK!MO=oXK*H(R>h?}B zOymnkmp4ZybnmE$7gPuD5C{9avbuv~!#l~&(S6z9m9^*lA9MtQ;Wy4wCiF9y;$P#R zb`N%(FJGw3c`WBlev!t3@&n~YKD9xR(51Mpv2EaaZjFw`7{LTT6}nHyzgL3_h8|?V zgzGGkGsnG`2yKOA@0HNKA^bcKY@8J6WM;C!{4uV(nVfNjl4T#D9tbAz6E1|#UAhyF zbD}Z{ZX53@s9M%#r^9vrGvtMC~rw=Egk6 z0%qBHPAf!BFd3l|n+1HL=4(mRO?=!$zUjw&;N-Lv>mx-?#GD0{b;jfyvttnM0A3dN zXs>W-0&)WA{F@{vYUSuh1-gg%CMmP;{6zDR$Z+AnBBTg+{k&v;V-NygU@#pVI({W+ j_SZ>c5c*o{@nrY|>I$HfWJj`B00000NkvXXu0mjf^xqlR literal 0 HcmV?d00001 diff --git a/tests/screenshots/src/app/wallet/glyphs/bnb_32px.png b/tests/screenshots/src/app/wallet/glyphs/bnb_32px.png new file mode 100755 index 0000000000000000000000000000000000000000..c3e48554e0ed931d2f2f55eb3f7ad527708b0a75 GIT binary patch literal 337 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?3oVGw3ym^DWND9BhG zd@T(<=m^Jf0I%~zW z*no-Sq+sCd8-ACM2t88y-#Ou)^3FQO|6iUS5@xIa;4}Ti2l4YEou)ObTzKtV4>j>m zU2$-}jkBuBkE!V^TG-A@YB{pSOjeK)F?ukGB{AJDdRqdwQljq1HLC9p-C{akIHh&- zzuZIRt0#ZjT@-%B&m(uer%Dd@be``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eBITlYB$B+ufw=?eYHXHD?-1WO4DACya=5M?e zlW5QcEsqW7|2&bi(A0NPuXieY6!GMuvPW{{lvxM&rCxN@D+rDZnUHOvbwW&rbqV`* zQ6sMFtU|53MK3jP-n7fX?Sa_MyA$LY^tgUboSe#VNASF2q*A~!;Y)MWcL`t6s-E@b zfaTA4fvpYB$`_`5acB)*9ad_ib=$_-R`JB%#V(9H)}%B4oVFwG!S^-CL*MGpdTe;G zf6`Xxf4P%quxbIDFD>SwCw`ONV9Ry)5c`FpKBb$PO_X64x?rh@6F+;O{? zGQ@137f^l3RdM&BRK=C?Wmoc(U%h1dTGLi_$dz+@M^(emjv)JE4K~x1olo}w!-v7s L)z4*}Q$iB}66KJ# literal 0 HcmV?d00001 diff --git a/tests/screenshots/src/app/wallet/glyphs/btc_32px.png b/tests/screenshots/src/app/wallet/glyphs/btc_32px.png new file mode 100755 index 0000000000000000000000000000000000000000..5abf8ab1861940d078aa3184cabef6eae94c0269 GIT binary patch literal 656 zcmV;B0&o3^P)F*H7)FMwy9eA1FC9;YU$h`#BsHuJwUAXeN70k`^fWvrB%ue9KVf*x zoIJ5z(75GRYFxbYG);AuWs7zZZ`^opc&&giXu85k0d~0xTm&s2S`b<<1w)FJkOi)X0wrcFPlqeptJ*SJQga~VHge!#&N96vK$COUYM}B){1Bp7m)z&0h1H`eWg?*$U;yZut~3- zfOisNyjiX1LF_fn97awrjL&~u3ye+eUeOE^a#RBd55PD?JRl$; z2gC=HN5io!NQlyNVF^p}gxC{H?tF}dgl|eStm%ml6AMLc+4u;lyE%btai5P<0NpF1 q_5~)S;m7Hfem_hvH1XE^FYyC;Po6m{Gz)V80000Da^0I0yZ6)0nb0ZIIXZ)P#J z!7L}~P7?6uG;kknQ6y@Kl&E>Uz7rLqKcZB$#tVr=Q_)sbIw}{%IQ)sH!DuBOrY`9z zMyC%jX6q9VK#8&^m@~?7M>G`3N!=Z0$B$v!7*M~Hl&2l!I>Hc<$0E1K%96i(wj9c)J zcp9D68O%crR$;TUd1=Npah36S?E2iTZLS?+&Wuz8o4tCUhHTh99G_25_;xfHApkRS z4a?>7ONze^qe^8WNn(bfCHn^2#rPqtyb&g`ew7K z{X7Q*2a2IOU>qo_2Gd)g=EIrS>bTUm>@>tt>_KW@xVnsF`MNeaU>{=>ayCQNl{$}l zoYy&a>W8M*W?5GHz(41)B#7$_MvhN3Dl%9ygZJ`yJDM^oZFs|T5jeq6fgG^@F^{$Lbcz7NOf5xbIbIQ|lia0Nz%kuqt+ zR1VhRJNXI6l;^eVWEEkZWh8yRXUTclNfchQt58iV3Z5vu@zzJx&CT#& z6b?D+USkQ43!-PCjz)72Q>^ohSOiZVwTZ59Sl3iH(Yb+M;6H4lzm&hR1n#V<1`a;} O0000}M{P)T6C2I2;xqfALhZV=x9bAoUKCMQVf=#r8yX?rK-N7I4H7acBqThr~%h)Mc&NGiwgbx?% z8~OyH2+R-$?`$MvF4ZOR9@Utk0l4=8e!4Yp#Hk)4vCDmcBf)L?%2~&2S~xV^!GFL) z7Z;xEBLXpH$Y_bNl{3#Ln}29-)8Tb%`~xf+xc?ooSNsCTfNVcqF)wNW0000E)%&~$@tPoP$*R>fK%5;4(0s{?C|2t=fTR$qOSykI=U`t#gd zvQ}{COwHP|Bmxn*f0O?a&y>)iH=}TZCnuhf7Rh>p9Vs>WCjt=&k3g3l>?l~IjzE`< zF;-D1k#pL1I_3h1|;-)S5b%PAJ#FB~_B*bM<>$O56TqbQ1kjaoi;z--G z*OL;z93&aK_Nt&`y{^1EM8N)iy+?oJ)j-c=jYv{`X$`&9ORdv6=A%acYWzja-6MjO zWf3!5@GMg1%%C8HLCYeYcIGxHG)jBL8cP_o;M2+_3oc<$u%wNacmYm}aqhfs|B0#4 z?`9YqVu(&Pnn8ncepZXAbfLdls>H!dTPcIVXeGA4J{$79L)-!%^Lp%%V}XAI-y`l& zs>CdVmIanF$mTrD0!tV);2GSo;1UKarTqgFNxj`_5ja4ExictBq#^CEh#*x4AE2L0 zjMiy^{226y^-6jEh>>fNsRl1FzM)tR&qI!#H6n4D6m)PeWw6BoUwy1`pqEEttiqmz o43Y-ZkuAUVdPn5_%Za1q8%y#RMb7mZl>h($07*qoM6N<$f;~XdZvX%Q literal 0 HcmV?d00001 diff --git a/tests/screenshots/src/app/wallet/glyphs/dot_32px.png b/tests/screenshots/src/app/wallet/glyphs/dot_32px.png new file mode 100755 index 0000000000000000000000000000000000000000..c3ac4cfa1e18e8b3fadb4daacaef8ba627ef1bc8 GIT binary patch literal 494 zcmV z#4r$rAIpW~mM|r32|jX62ZRb(DoE&n4zev!K|}@6!RE*O|Wg@^F#%~uW20B90EdcxU7qM33bsv%@2YQ%5-3ub|#3DIs-mQ_Yg3E zUt0q?hm4P5&5)2!E>ZLW*LIfpNt4iI9GA(+YDj7Kg?)aRcKZGKL4pF{r$ZV)SQha~%_e5EDc+ zZs8vvTnFu95OZzk%OG0#X)~d$gZxq21lu0E{3$XD0vI(8wMVh$QMwsD_#|~}uKp*u kKs|0-68!qU2K}Sv6X(*$Rn_SZwg3PC07*qoM6N<$f}<(PWdHyG literal 0 HcmV?d00001 diff --git a/tests/screenshots/src/app/wallet/glyphs/dot_40px.png b/tests/screenshots/src/app/wallet/glyphs/dot_40px.png new file mode 100755 index 0000000000000000000000000000000000000000..b65750c3885f9b83cbaa75d55cad849f1d8355bf GIT binary patch literal 582 zcmV-M0=fN(P)`XqjOoX5by`%Mv4S2#_5jnFNfT z_I`)N*8YO6B)V}%F_qc>3;3^IVRcA%g8}1n?U-=Yxu0pN1jopk*UtFTF>Z}Wmf%mO ziyUU93hR_>8*9d9i9i(iEvpevBiJp5GW3af){t}N8Lm^D<9HBUq&T=0?qzoFi_jsE zo-!pxkgimDR(*vDV}xUYwA#A2HaMQ)K?V2IMW=&+IZ7X81-ElUX8$SN2S)iCn8U=| zCPXO$1(UWKJLiLcV+OPAwEPO!L&1bmP~$*KH+@JdTtCflCGRZFgfy&F%-@lYLO^5S za!{%i6ep=$EydaoJjfw;$SJF+F4)d}4K=utc8jAJ%21D5u&WSA1^}Xc2pGG-NX#K`b#j~y^#L=t(!TUi$@+##>Z5+Zd~oyp0(+-N U;v>!2Gynhq07*qoM6N<$f_akzDgXcg literal 0 HcmV?d00001 diff --git a/tests/screenshots/src/app/wallet/glyphs/eth_32px.png b/tests/screenshots/src/app/wallet/glyphs/eth_32px.png new file mode 100755 index 0000000000000000000000000000000000000000..32e97efdf96c52cfd99153fe0dcf22bc3574375f GIT binary patch literal 872 zcmV-u1DE`XP)A^r2PW*58w-gg(j|mxG?dN_y^z*4D$s-45KD4aOWhN zOs_QY6=4Ac8kloCV4$Jts$!CvlXR%QRNZ^}K8xrdBS=%=@bED9{{C(}Jw5#*MFdC@ z2L}g6Fc?f}nwAZR!#XKKnZWGqY^vMs8d&O~P$)%;P$Iythw<2eD6z7#Vv+)+3G`j> zmaB$Bn!teThrlwGP)HFtIyy34u6IK^6v713>9igQ1hRhnWJ4iLU}0fltn1y74TTT^ zPCO`1kT`@xAw&R)S0G7DN zW`6hITdfv7JUozPSyZdl9NGQ-z4HrWr0Y7(&CNOMPXn43xw*Ny^|BG731HXbOPsI8 zz;O&SiE*%Pn_gdEo%Q_uyhDbG;5=7O#LI@~G~D0cPr<#L<#L(s?(PUvk2;;s$Uc_4 z(QG!UTCMg&^VKj6?eX!kIL?lz2MUG4mwLTU(|`#;$48}7`S9A~nS|%(=LxuP5ziSZ z0%vpWb~}Rbosuj8OuX>M#s*zqU-RT!gy;u}qljny?d>hTyu7SOqtQ*0C4lU=I0;dP zStfFIb+rXCKNk5IMl!B1EiGAVYin6lK_1hy=ygD(49i&RP?Sb;o$c-IS*xq7F}!oT zHMk7nAq(pazBp%^rf%EKZK!s7fNi%U@S>$efN^|$%EcsOf$Q?yE%SmBIpYS zO7Ik2j|L1o9kA2s#xE}~bCdM@66lfOB-;Kw~&sLqs1rL>=JAp$)T zJP*vF!yuB+f4AIn;I>ry86g1(k7IPeI2#<4^=5=QEF%kX-y%T>6X4`SCye{;1F5Y^ zNvBMJv9q&Ngn^{@4lRSEyCn%oUrIrO6LO>}X&U83l5T1O3^bOZeBijZTI~!eK#4#y ynfyQ*a-#8&;q|&q-2WPIO;4cR%G?$IKl}mVM|Cy;5w@oQ0000t<=7E#A0Ft!`r#fyGGMS7F9K;2X ztaaUzlF4KdI4Cxt!&j?BE-x?5pJ5p0y|c43^GJY_B*F0TunFvv(Qdb2G#U*fsBfln%WW-13nKu!uA7iv zLIAkCyEEYkl3*QCC95fy7a%}^5hQD|lcEiFa&lrG{bWbbOX+kPMn*==pQV|DgM%_e z1+>=Mz4P;PnL7517Av4FisabX7)4gc%gI4j548B=;zB+?J_g<*`yU=2;Qsy|Mn^|w zyw&h)%QQ%`*({A<${9JRElUJ3pAG!pHs+`r)Rt+Qxw^V)OiWCC!qXr4VP#NTicA35 z`%b6x2HSmB6h)F{IgNwur{6n)5yXPDh#&m2yu4hZYmBCh2NgSd>($;bf)p{#Vb!qa zqv`2axMafXYPCKK0~82o&w+G~pjr^92VfaSJz1|4wn+ec@oJWwY zKQ9VG>FMc7bAXxy>GbrJp5<*YigYq57NM`W6|`!#s6_bh?{rAnoi2S$aeX0X(vDC*98R2x!`((cX%%jI4b+U-`;4TsmdD5e6JH>W~H zkVJz9H%mu&`mC-a_??}PlGigZ3xv*O(4cTDbVW^8cDr5AC|CuB~dYl)UTv2E^3T(oz{AwdYR+tx~D%LQHY_`wbyg_`S4Q>g~qKJEyq)1&ovUEKWa1 qn0HuIbVE(Xe@(c5$yim+|D#`*!or@2b3@Po0000``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eBxo4g(jv*C{Z)ZQuZ86|!jkiA7`#^A4WIpqr zME4gQvI|9p{MYuiSsdKSa8CVce9OC9_ats60|_3s=Da<+$|}Vl)~B{iKJelGX%5wg zhBX?TD?cczJUJ8})nxidRdq_+{wXcDK1`pm!Tirk)rwa8r7Z71ET0r0^{-fR`h)=C zxM_t|oLe7uJ~$%utxdg2*lWi@bsNDc-ZysL3l8|vqRzCN<)|Zw(X@x<=s_@J3s58w zB(jyoRsEUlcjliCT-84^C8Ig&R97Uv`y+jK_2K(L>6~Tlx;FnqyIXcA^11GucH;l5 zKlh@m4^_oJSIJR~&=(cwjP~_g+rs&B?q0Vhg}ssQCMCT5;HBzw;`%os&Ze53YbFI; zx)-GyacH`SzzykTOIpHh`lq`U&%T(%sZnz~+HtX}LsU)^*ZdDI7czuZbWbff1a!EG zR`ZfAf>rH%lee@?e>!WY<&+CgA{)1g&Pxzqx}i0F!%2hfp4vIB5BE;%N~q0#u2S;Y z@J}3P)0byu7Qk@ZD#Y^hQ<((k&JRn4SibSC-_7FqzkQK;iQ1RsLZM$rIXM4j^|aJk zdO7WvQ(wsMG2w!HipLA*AeAqMmO{T4sBqSXOlz^z@pY1)uD;NIhQdPonTiYTXGsBL c>fIlP#nM*yUheuL0!#r6p00i_>zopr01AToegFUf literal 0 HcmV?d00001 diff --git a/tests/screenshots/src/app/wallet/glyphs/ic_asset_btc_64.png b/tests/screenshots/src/app/wallet/glyphs/ic_asset_btc_64.png new file mode 100755 index 0000000000000000000000000000000000000000..321c0f1bcfc81a102ffc9df93b8fd98246f2d135 GIT binary patch literal 1257 zcmV=P)NpgJ|9LY-x{xlV%XE)29o|8*1IP-(4)Ao-C0PONfTWua(xn4dV6p;2m(EW=;5=b0 zBU@MCoX2<0e|#`9_Px52t|SATIPpjG7yQ#uYHsv%bL6=oQ*OS|htjLkRB4QF3JvTk zjg%HjiG%nX9qc-+mV`#ug8%_Mba;=Ck4eg8GWj~+Y&H|+9Ep1@AT&C}0uQsJ(P;GL02K*4=NF4b5=D{g zeI{_E=kfqS5agb3G#ZJ{?d|Q?cQibu)9FY!J3>VQSK~<>$H{OwTp7dg=64Knozzj; z^J#fURc;D>mm9NI^Poehy!PMicDKs8+&`lsN)ou;yihV%?7IdT?K$*~c{UUn3p2TxlO_)Z~#sWo}+}G8+dZU2o{Lf zzTUg*@O-u`!(AUGvN&dmdz(S%!vV|#@-U6_Oq5Jc(6h#h3X2@j_2GcLcFuFC5H$k% z!B#);BjbP;gi*B&RiiuSUteEQ1%YAlr~V=90IOl540J#qa;9sDzyv8SazG+2+i~6o!TG^q9AFy%tH2yVC?ni*z(4Q^ zm%Z{B3Iy0358?P5igto{w0mjyJpO>;Q^lf1KRq5~gMkI99~+ccj&jBN^4ni4ZKIgD}3OJCiUL?9FFSlC~!8RR*nXi`%afo(D> zfF+LtH`~hY(%m5*%TADljPIiaq3aE8_y6c5Dm#ksGn5IAAszU8P+t;%aWVpsd=o|RLkX?lon zK!A0pG=jU){0(-x;#>FR5PzH`A=dlyE&Tq|WRNK{$L}@C+2%OioI7#igdctZW9R-L T&6THJ00000NkvXXu0mjf>fu?z literal 0 HcmV?d00001 diff --git a/tests/screenshots/src/app/wallet/glyphs/ic_asset_cardano_64.png b/tests/screenshots/src/app/wallet/glyphs/ic_asset_cardano_64.png new file mode 100755 index 0000000000000000000000000000000000000000..4944aa136edc9dbde8af6c0b2c079c3b64fb3851 GIT binary patch literal 1041 zcmV+s1n&EZP)28t@ZU)_=tSSHa)hfNswn*YSUe08J3+T$X^uJ ze>`_aANA==zF> z(1o|1P9h-NARIOBjC!_+k1)~X41+!PpnRQvKsP8(Q;!Tq)Ker+=(M(gyWVWfE{i`!)&$^c~BU%KgH?r3*`Q#tVWZN7_^mZA#%(KrXVr&FZzXk#^n&` z!Xt;WB@V+HzX+*+mpY}eF|N-RE?M&e>F^TSc6z22-D5v=a(YBM6P+#&Ver;7D^KFk z;hCPF%dR~#%G1UR3Uiu$*mHG!0r%kgo#R~~e{R&Hth^^=AlE{u3EY#v*xOtUY4S^; zjiTuH_B$M&@F@7F0LwKvoc}IwPJuRxfY3Qemo>QNxHM#@j{JqEoe45p!v~xX(rKfU z#NeTy+w{jF&oX@&oMRYlU&qZb!@f58K1FMUL+6=V$Jt=7%_d8pe2(9)`TT--W}Z5H zYXj1G5}DvZ^1mVmqS zv0=?eGa}D?Zm$G;KSD@=grwYNq*J}dGahxb}uz};t7XjcCbs!YJ$ z`IH@`Yjwy2yvjO%41_M!u}0YW3FCPP(^SnmX1JljyAar)8g@wIYjelQ(p@D%ava3X5)Sh-vPJ00000 LNkvXXu0mjfiM#JG literal 0 HcmV?d00001 diff --git a/tests/screenshots/src/app/wallet/glyphs/ic_asset_doge_64.png b/tests/screenshots/src/app/wallet/glyphs/ic_asset_doge_64.png new file mode 100755 index 0000000000000000000000000000000000000000..caf0c2ca129f8f846c27bf93c5019db6feafeb75 GIT binary patch literal 1318 zcmV+>1=;$EP)2lJwO*FP>0C=w`lD8{UY6A|(=tifLS-t*5{hYg6-=NA@&)Mo+x_0b z?(R9~xw|{};B(J!`~1h{Ip_PF=lAyf48t?eJo79%8v_a*WEPkPt_N3wVIXxN3Gg7O zaF9*lEwBPS1PZ{lU<619L);eN84z%gKfrI`FxU^Ozz3iN%mi1sEx?PQH_nJKyTJyq z2;2xR1!;*QK!0jL8JNyvk#GW>1_!~%UzdLjuRbW@10K@k|sNRgB%0tn|RLOoaj zvcceiAb@!BoeXDXHmn8jfcLK06#K|8x_ME$rNQWSO`7>4SZ3vb(?vF<}h1Kx{w73H>nZIL7RnBd0I^(p`fs?~Jtph#18;!Qx(SfR$RRKH4@HQy zz8r&}8HKbGAc@&t5%Y}G7QUc?e#57e03o{tyk_AGLe-r!JbX;x< zW221#`&jACj__M6@h%6;CJv!tODExD#JKxBS1Twn!iN&trhR`Y-cEL z%nNLoMtTOR9*?uT9Fsi^TEnob(`E5a3eg$`-!sIi#O~qXY4C$ZS}cBGeXIf$#j(Rv z#XA5M0sLSo*lP1X%XJlC7~5c55>)^npY01z0EY#b3tE_3hUipqf*mYYPDR01R&c65 z;7OLojtX!a8w~w9WV2{9bAc@pertuq>~V^h9ClG4lnZQ#Re+0`b@gX;6kXxmD9DcD zazp<1CFUAtC2N6aEwGpEc`XHy)>wr_S4gVF z9bmDJ0t}(E+y>5A)FYoTifJc+kNHBCVSQC5M`Wgs0!W{4zJ&wqWuM?VFhM5)2C-Lg zJNpey79p^apZ`dm1dx}Pi_~IJ$80mw`hW9yOfwvs7jR1eKQq&fbaG}_=Jjl@!I!4tXm9-H;Q15dDVs*L~zT(^eZBdhi03z2K}%nMlnsRv|$;%Xx}IxF{qyDZw^ zp;mYuizL6{;6!OW1Q6#XgOHD~;tMGB6Pg$uW*82eod9w(Tf)yUOA)!smieAL8RJXA zW+OOm6Hw?u$ZhdEOYcu)=pH&F3e*cRvF8`xjL4dQ2`=zW8QU`(n@gUUquQvMTh9yZ#P6PqYbJwLc z_qGw7GxQm8G`9sf!ASEI9jEMkitYlmv&AJ%4jHYLTcPXt@^Lpe1gK|=yU^iIlak4( zTBU)rsbqjM>+=B9;XJo?Jv=X81$IbdUBBUi82llYAgNh{XXbD=Tbx-m_>H zQIMn#JRN?>@K1^i(=$xvXB_$4Ugioe|vmh!-1?diOYJP^~^G<~zN_T)@&2I@6H%n3- zAW`^yQPDk;<^Z2APbk;d*Yxo4KyO59_o#~`;Rs*ocF)hxbar+|r>Cd({LRe`RjXA> zBofru*GC;4q1VLi9;HbF21$U04?nzVORLdv7yj-7!>G5n*LDWahmbxzJS4L>fdVsLQKP7g00LCjMGB1HfTA3D6>-R~UQ?$zeJZiS!-XoMqyBZ4EtORZKLMif{i zNTdjW@%GJ5P=3n z!VgDr(#_-Jva;C%0)3)9%YOc9VW5Msv1$CG@sLjI4v z0uj)FG7Qf`3@ao7;S^X~TbpwlA2tUE2iE@nzOuByaV<>Js03-R@spF|=G4@b@H0i> z2-w`*OuW3je8E+(K#U5@%gdH`Jt88Hb)mP$VG+Q2?u7eR)iby-O*4VY!%Y%%cXxM~ z1d%Zi`eq-cV{Rf zQW_#?gH7|*cfWH_kna(=L)0LYC9Gqi{Lpv|&T~Z1u-AAF_sPkL9E|7t4fEgQ6Yd0G z3M2t(We};rVgA1-z)jME%jPFUK-I6TO-)UpxoW4DPY=*Y@9gZ9NdnRwz#tX)>dpF| z2#!Enq#M(s5zceO6sL*lzlBkN@p+PjR0l9{0#}8=QDKlixMdBwoeA2PLqAH%^Fw-D z97gcCJg^zS`oJ$saP@GPah}&|&)=IF&&Nb6N$CT6ksbkzw=_0AInv_e$G^xeYt4{R z^88TpXl!wDk$>ptuCelD0@1L(m3NSjiSgbd44E!KsKALqEQlgpaLh( zL>cOaq4Wg_q(T{!_auy6^2($T2PGxX4=oPh zK@SwaWg&Zgt!Gu{y%%k7iaMq7L)dm)^7i)jN~8&`FH55|&NVBIs!N_9S{)Ep8j&W` zx1kn>^qTAm_z@Wa&Ch=L%Pfd z&Y^IFnUX)ejiG+U7chm#rj|?yr0Tp2n6`J&sphsMz+lewVB)}KK(WDbWPpJFR zmf?zkF=*n8peNS(*_L4wK*B~b9!)$EbcDJTmb-yXfEcSP&Gd$A;t5%q5>pBwXvRCB zi93{(De)$x@JKY_j*L0Sg)$`$%>gmd_&XF*HiVC+M4z=lEHrM%e9A;6g#a7ubBM<7 z;7}fIoza%K9VrCpw#q|9Fs1c*XzPr=(_<50>JO74BJ@9-h~>O#$!rxsGaX+Ma`n8i zQ&S2c_=un_d9lNOZ%VWY@Ju{hBA7@q=7R)~aM8pgM1)k%2Mqz?7jf|sA=PZZw`JU7 zr)KzWuqT{Ogs+YLyvUp4yws%!@@q0uU%sZkdm|J^mrlLyiw~3Lwk9!bC7AKO+$W&@nB#I__r#_9n$ z7j6d<&MS(tML3Y*jWI=;A|!o3=n_R)B1l_8R+~{!yyF)oJ7lt%^X{-=<_D>c6?vmU zu2oSWYtjmFY_Cy7uiKEut%xhcZ?3>`6EZW)INcHMw4Adhaj56~)09y0+Kb&S>%Kky m<^Mk9zCE5;SXfwCNXtL&ekQT%#M;gP0000JJATKjm84)xf{20)k&e^=q-RN@}g7*BM!$GaxN4#RVu&sB(r zN5m~;go`DB8+t&0<*u+SQ-yPE3Ig;MyIYL+k|a}wOO3|=9C{kjSdJ{h8HK#!vk7Mi z{#Bhuda??49C{i|iL~$vfiV;O6_t>NRKkOAgGW!}d&rYocyI!0eC&Nm2+fKzweX+> z+;|))$UyLqu2jN<65tDv5CCr5nFkjRWq(9 zIrC_kS7@Xph4o2>FX!mIM&iQ`KuEOu)Hwp0K{#S$8p|5rKw( z1Q6DHA5T67rab!2A)N8uwiP0n`=(bT~ zCWPgSn|s+oh=bry=qz#wmwJq@JZ;=yCR-Hls1Z5*fo%$B6aVhHM2VT>geIU%Vml&X{m}x>3272> z!t%S%q98QWr$)GFzE&e)kBe>@9e~jE9@mH~drYnKt{%8;wEXuT8{aNpC=?2XLSfzf Y0}Iv1ko7$zdjJ3c07*qoM6N<$f=Xh2cmMzZ literal 0 HcmV?d00001 diff --git a/tests/screenshots/src/app/wallet/glyphs/ltc_40px.png b/tests/screenshots/src/app/wallet/glyphs/ltc_40px.png new file mode 100755 index 0000000000000000000000000000000000000000..27822a459e2115b9ef947c83a7574a11ab4e25ce GIT binary patch literal 606 zcmV-k0-^nhP)jK~#7F&6x2~ z!Y~-cpPc`T6Y!0|2;D$7kPUPLa01w%I)OSty8&*HG66aP=CjlbF{R0uwDx!Ra+g3K zuSpux03+uLam=+ct(g{SO;7EgT9wvbtJM0yfMi-5t%KIlQ%WmGeG|RGy;TH+YEv&k zHwl8-S|I_Xwd)~Vqa7l8lTgo{W6x3$grtGOi9_}|te1w{iukDA4AT|llvaI#IGooj$nN&d;T5|I9BjjR zgxS*aqR4P(4@)z|8RV2!JwqBYE}DP@8NAP{thRg9`@z^fAaT(IoI%EmqwPuS7InxN zh>IrR6yb1yvZehI@k4!MRtkhIq`VBcZt4}X2cqqFetR%vICuzoev#v|*!9oki;E`U z-v7Fvzcq!wJ(w~aQ*kjdE1uBl`=!->hqP2?C3K zb%J}zF=6iq_))>Jj&9+9camj^0{mSg)oF)RGTA^M4xA1WX>wm&S>C( zop(oP!Hkf@~Y9lK) zFeo3)K9|~XfUf&zgI5nBo7xS6gU(nu3$-v-1%u`L1`9O8SSpx(*#(peX0i(?70h1t z)T!`blK1iw!Li1ov?$y0lpgozNfftHaKMOlHf8EI2xhqZQM;g2F!1<9wQJ^*!P9WJ zW}Y6xF;cOCejpjdUMIzx+aZhvhwghk-pd{CqgYK195J68J1DVOzD5T5Lum~KxnsGZ pLJt(cXX8Q&WBDikNA#T?6@Q;7{F!tXk#hh5002ovPDHLkV1hA8xd8wG literal 0 HcmV?d00001 diff --git a/tests/screenshots/src/app/wallet/glyphs/stellar_40px.png b/tests/screenshots/src/app/wallet/glyphs/stellar_40px.png new file mode 100755 index 0000000000000000000000000000000000000000..5c8fc69669b2024c15c13c40c9dfbf906599e59a GIT binary patch literal 567 zcmV-70?7S|P)jt^6o1*PLfN2}u|46a+7wh{<`e>RHa zgcz4KI?g689@!)OVrNrthX|C_(3U!xPJIu2HS*kMY81)QzgSRN59p&pdkAS1usd4~jR%eR^BS#hL}gIGzz;KYBnev3?;BLRNzl))i|;_H(21apZoC5Ds8OOa8GQJA?~qLsv4a&=5&T2YMI@Lv;!p+<9fCAfEwXrCyTX?{uLrnkfpMMdm(3m3x)^xsRMp+zvp6V7Q0aB4&$*` z9~Ba=Gv;0%5xO174&4N>FORM|KG9twy&>b}(ZSX3S)SU4O~ zIUsU|NvObKSwisvnH;`tyDy|U1i9UQEjH=j6LsAw|E{;iHLh4;uO0a%yJ*?1@=u&b zw`}zJrB3KSoj2!D?8;fr=gl85H?0*2-{H9ARrKltO^)>i@f;Hi!Z|K}Fzt8~{!oo0 zZ~jTvokuFp$SpkNE1<^CIp1l^jP+r&=bLV@*~EG0h@$1Qt8d-vlJbl0Ha689*ulI< zde1NJoz4lO5&s2$#`Za^`tX+}BV6E`(VpWwq@(WSIXmhco;`JAuStvV4vQlf74NVF z9uC^-zQer5WruM~$`7@UOw}h{VSc}2t}oS!?)<5GHL?G@TZq73#suSqb?h|{i+~}- N;OXk;vd$@?2>=itksbg5 literal 0 HcmV?d00001 diff --git a/tests/screenshots/src/app/wallet/glyphs/xrp_40px.png b/tests/screenshots/src/app/wallet/glyphs/xrp_40px.png new file mode 100755 index 0000000000000000000000000000000000000000..9abe42c062f807d1faf891449005a98698befa4c GIT binary patch literal 433 zcmV;i0Z#sjP)-Jss2Y*036Hh>KvFaAaPN#XUeKOl0FrM61ti1y zr!+!mlK&J+WA>EGJ+TtPXJzu|EIpq`7B=@Cc7(%{UR6DQ1IF*ds)C_%9k&$>Bhwoz z(+zRv{^2+?o%fm$Ds`az6d!2F+`L#4MZ>6vTxchIZre#X(MP83$5pas(iSSm`WAA{ zBrSFn!Aa6`ff9@*J+?c+NYZol6c$Pf>+HovIja9yft2LRhlTOTl<<7%3)7qL`7fi| b#)btSAou)mq2(U-00000NkvXXu0mjfFUh%N literal 0 HcmV?d00001 diff --git a/tests/screenshots/src/main/json_scenario.c b/tests/screenshots/src/main/json_scenario.c new file mode 100644 index 000000000..ed9002b93 --- /dev/null +++ b/tests/screenshots/src/main/json_scenario.c @@ -0,0 +1,910 @@ +/** + * @file json_scenario.h + * @brief screenshots engine implementation + * + */ + +/********************* + * INCLUDES + *********************/ +#include +#include +#include +#include + +// You may have to install packet libjson-c-dev +#include + +#include "nbgl_use_case.h" +#include "json_scenario.h" +#include "nbgl_touch.h" +#include "nbgl_driver.h" +#include "os_registry.h" +#include "os_settings.h" +#include "os_id.h" +#include "apps_api.h" + +Event_t appEvents[] = { + {"BTC_SIGN", BTC_SIGN }, + {"CARDANO_SIGN", CARDANO_SIGN }, + {"STELLAR_SIGN", STELLAR_SIGN }, + {"STELLAR_STREAM_SIGN", STELLAR_STREAM_SIGN}, + {"BTC_VERIFY_ADDR", BTC_VERIFY_ADDR }, + {"CARDANO_VERIFY_ADDR", CARDANO_VERIFY_ADDR}, + {"BTC_OPEN", BTC_OPEN }, + {"ETH_OPEN", ETH_OPEN }, + {"MONERO_OPEN", MONERO_OPEN }, + {"ETH_MESSAGE_SKIP", ETH_MESSAGE_SKIP }, + {"ETH_SIGN", ETH_SIGN }, + {"RECOV_OPEN", RECOV_OPEN }, + {"DOGE_SIGN", DOGE_SIGN } +}; + +// =--------------------------------------------------------------------------= +// Variables +// =--------------------------------------------------------------------------= +static uint32_t nb_pages; +static ScenarioPage_t pages[NB_MAX_PAGES]; +ScenarioPage_t *current_scenario_page; +char scenario_name[100]; +static char *productName; + +static char *jsonName; +extern char *dirName; +extern char *propertiesName; + +// Set this flag to true if you want a verbose output +bool verbose = false; + +const char *control_names[] + = {"BOTTOM_BUTTON", "LEFT_BUTTON", "RIGHT_BUTTON", "WHOLE_SCREEN", "TOP_RIGHT_BUTTON", + "BACK_BUTTON", "SINGLE_BUTTON", "EXTRA_BUTTON", "CHOICE_1", "CHOICE_2", + "CHOICE_3", "KEYPAD", "KEYBOARD", "ENTERED_TEXT", "VALUE_BUTTON_1", + "VALUE_BUTTON_2", "VALUE_BUTTON_3", "LONG_PRESS_BUTTON", "TIP_BOX", "CONTROLS"}; + +extern void delete_app_storage(unsigned int app_idx); + +// =--------------------------------------------------------------------------= +// Return 0 if both strings are identicall, up to % character in str1 + +uint8_t strxcmp(char *str1, char *str2) +{ + char c1, c2; + + if (!str1 || !str2) { + return 1; + } + + do { + c1 = *str1++; + c2 = *str2++; + if (c1 == '%') { + return 0; + } + if (c1 != c2) { + return 1; + } + } while (c1); + + return 0; +} + +// =--------------------------------------------------------------------------= +static int add_name(ScenarioPage_t *page, char *text) +{ + page->name = malloc(strlen(text) + 1); + if (page->name == NULL) { + fprintf(stdout, "No enough memory to store the page name \"%s\"!\n", text); + return 22; + } + strcpy(page->name, text); + + return 0; +} + +#ifdef HAVE_SE_TOUCH +static uint8_t control_name_to_id(char *control_name) +{ + uint8_t i; + for (i = 0; i < (NB_CONTROL_IDS - 1); i++) { + if (!memcmp(control_names[i], control_name, strlen(control_names[i]))) { + return i + 1; // ids start at 1 + } + } + return 0; +} +static char *getControlName(uint8_t objTouchId, char objTouchSubId) +{ + static char controlName[100]; + if (objTouchId == KEYBOARD_ID) { + sprintf(controlName, "KEYBOARD_key[%c]", objTouchSubId); + } + else if (objTouchId == KEYPAD_ID) { + sprintf(controlName, "KEYPAD_key[%c]", objTouchSubId); + } + else if (objTouchId >= CONTROLS_ID) { + sprintf(controlName, "GENERIC_CONTROL[%c]", objTouchSubId); + } + else if (objTouchId >= 1) { + sprintf(controlName, "%s", control_names[objTouchId - 1]); + } + else { + sprintf(controlName, "NO_CONTROL"); + } + return controlName; +} +#endif // HAVE_SE_TOUCH + +static int add_targets(ScenarioPage_t *page, struct json_object *value) +{ + // Strings are stored in an array + if (json_object_get_type(value) != json_type_array) { + return 23; + } + // fprintf(stdout, "Will handle %d steps...\n", (int)json_object_array_length(value)); + page->steps = malloc((int) json_object_array_length(value) * sizeof(PageStep_t)); + page->nbSteps = 0; + for (int i = 0; i < (int) json_object_array_length(value); i++) { + bool notConcerned = false; + char *name = NULL; + PageStep_t *target = &page->steps[page->nbSteps]; + + struct json_object *page_info = json_object_array_get_idx(value, i); + + target->wait = 0; + target->wait_initial = 0; +#ifdef HAVE_FAST_HOLD_TO_APPROVE + target->long_press_wait = 1500; +#else + target->long_press_wait = 1200; +#endif +#ifdef HAVE_SE_TOUCH + target->x = 0; + target->y = 0; + target->objTouchId = 0; +#endif // HAVE_SE_TOUCH + target->power_press = 0; + target->forced_key_press = 0; + target->ble_event_idx = 0xFF; // not valid + target->batt_level = DEFAULT_BATT_LEVEL; + target->auto_transition = false; + target->batt_event_idx = 0xFF; // not valid + target->ux_event_idx = 0xFF; // not valid + target->app_event_idx = 0xFF; // not valid + target->param = 0; + target->reset = false; + target->os_flags = 0; + target->seed_algorithm = 0xFF; + json_object_object_foreach(page_info, k, v) + { + if (!strcmp(k, "page")) { + name = (char *) json_object_get_string(v); + target->name = malloc(strlen(name) + 1); + strcpy(target->name, name); + } + else if (!strcmp(k, "product")) { // to restrict a target to a specific product + name = (char *) json_object_get_string(v); + if (strcmp(name, productName)) { + notConcerned = true; + continue; + } + } +#ifdef HAVE_SE_TOUCH + else if (!strcmp(k, "touch")) { + name = (char *) json_object_get_string(v); + char *comma = strchr(name, ','); + target->x = atoi(name); + target->y = atoi(comma + 1); + } + else if (!strcmp(k, "object")) { + name = (char *) json_object_get_string(v); + char *comma = strchr(name, ','); + target->objTouchId = control_name_to_id(name); + if (comma) { + target->objTouchSubId = comma[1]; + } + } +#else // HAVE_SE_TOUCH + else if (!strcmp(k, "key")) { + name = (char *) json_object_get_string(v); + if (strcmp("left", name) == 0) { + target->keyState = BUTTON_LEFT; + } + else if (strcmp("right", name) == 0) { + target->keyState = BUTTON_RIGHT; + } + else if (strcmp("both", name) == 0) { + target->keyState = BUTTON_LEFT | BUTTON_RIGHT; + } + } +#endif // HAVE_SE_TOUCH + else if (!strcmp(k, "wait")) { + name = (char *) json_object_get_string(v); + target->wait = atoi(name); + target->wait_initial = target->wait; + } + else if (!strcmp(k, "forced_key_press")) { + name = (char *) json_object_get_string(v); + target->forced_key_press = atoi(name); + } + else if (!strcmp(k, "param")) { + name = (char *) json_object_get_string(v); + target->param = atoi(name); + } + else if (!strcmp(k, "auto_transition")) { + name = (char *) json_object_get_string(v); + target->auto_transition = (strcmp("true", name) == 0) ? true : false; + } + else if (!strcmp(k, "reset")) { + name = (char *) json_object_get_string(v); + target->reset = (strcmp("true", name) == 0) ? true : false; + } + else if (!strcmp(k, "os_flags")) { + name = (char *) json_object_get_string(v); + target->os_flags = atoi(name); + } + else if (!strcmp(k, "seed_algorithm")) { + name = (char *) json_object_get_string(v); + target->seed_algorithm = atoi(name); + } + else if (!strcmp(k, "app_event")) { + name = (char *) json_object_get_string(v); + for (uint32_t j = 0; j < sizeof(appEvents) / sizeof(Event_t); j++) { + if (strcmp(appEvents[j].name, name) == 0) { + target->app_event_idx = j; + break; + } + } + } + } + if (!notConcerned) { + page->nbSteps++; + } + } + page->currentStep = 0; + page->saved = false; + + return 0; +} + +// =--------------------------------------------------------------------------= + +static char *read_json_data(char *path, char *filename) +{ + FILE *json_file; + size_t size; + char *buffer; + char fullname[MAX_PATH]; + + snprintf(fullname, sizeof(fullname), "%s%s", path, filename); + + if ((json_file = fopen(fullname, "r")) == NULL) { + fprintf(stdout, "Unable to read flow JSON file %s!\n", fullname); + return NULL; + } + + // Determine file size + if (fseek(json_file, 0, SEEK_END) != 0) { + fprintf(stdout, "Error seeking to the end of JSON file %s!\n", fullname); + fclose(json_file); + return NULL; + } + + if ((size = ftell(json_file)) == 0) { + fprintf(stdout, "Error getting size of JSON file %s!\n", fullname); + fclose(json_file); + return NULL; + } + + if (fseek(json_file, 0, SEEK_SET) != 0) { + fprintf(stdout, "Error seeking to the start of JSON file %s!\n", fullname); + fclose(json_file); + return NULL; + } + + // Allocate memory and read the file + if ((buffer = malloc(size + 1)) == NULL) { + fprintf(stdout, "Error allocating %lu bytes to read JSON file %s!\n", size, fullname); + fclose(json_file); + return NULL; + } + if (fread(buffer, 1, size, json_file) != size) { + fprintf(stdout, "Error reading %lu bytes from JSON file %s!\n", size, fullname); + free(buffer); + fclose(json_file); + return NULL; + } + buffer[size] = 0; + fclose(json_file); + + return buffer; +} + +// =--------------------------------------------------------------------------= + +void scenario_parse_args(int argc, char **argv) +{ + int i = 1; + if (argc < 7) { + fprintf(stderr, "wrong number of args command line \n"); + exit(-1); + } + while (i < argc) { + if (!strcmp("-j", argv[i])) { + jsonName = argv[i + 1]; + i += 2; + } + else if (!strcmp("-d", argv[i])) { + dirName = argv[i + 1]; + i += 2; + } + else if (!strcmp("-p", argv[i])) { + propertiesName = argv[i + 1]; + i += 2; + } + else if (!strcmp("-n", argv[i])) { + productName = argv[i + 1]; + i += 2; + } + else if (!strcmp("-v", argv[i])) { + verbose = true; + i += 1; + } + else { + printf("Unknown arg [%s] in command line \n", argv[i]); + exit(-1); + } + } +} + +int scenario_parse_json(void) +{ + int error_code; + char *buffer; + struct json_object *json_root; + + // Read the JSON file + if ((buffer = read_json_data("", jsonName)) == NULL) { + // Error message is displayed in the called function + return 10; + } + if ((json_root = json_tokener_parse(buffer)) == NULL) { + fprintf(stdout, "An error occurred while parsing JSON file %s!\n", jsonName); + free(buffer); + return 11; + } + + if (json_type_object != json_object_get_type(json_root)) { + fprintf(stdout, "There is no valid entry in file %s!\n", jsonName); + json_object_put(json_root); + free(buffer); + return 12; + } + // Next line is a macro that iterate through keys/values of that JSON object + json_object_object_foreach(json_root, key, value) + { + if (!strcmp(key, "title")) { + char *title = (char *) json_object_get_string(value); + strcpy(scenario_name, title); + } + else if (!strcmp(key, "product")) { + char *name = (char *) json_object_get_string(value); + if (strcmp(name, productName)) { + break; + } + } + else { + // Strings are stored in an array + if (json_object_get_type(value) != json_type_array) { + continue; + } + for (int i = 0; i < (int) json_object_array_length(value); i++) { + bool notConcerned = false; + char *name = NULL; + ScenarioPage_t *page = &pages[nb_pages]; + + struct json_object *page_info = json_object_array_get_idx(value, i); + page->optional = false; // by default + page->nb_sub_pages = 1; // by default + page->cur_sub_page = 0; // by default + json_object_object_foreach(page_info, k, v) + { + if (!strcmp(k, "name")) { + name = (char *) json_object_get_string(v); + if ((error_code = add_name(page, name)) != 0) { + json_object_put(json_root); + free(buffer); + return -error_code; + } + } + else if (!strcmp(k, "product")) { // to restrict a page to a specific product + name = (char *) json_object_get_string(v); + if (strcmp(name, productName)) { + notConcerned = true; + continue; + } + } + else if (!strcmp(k, "optional")) { + name = (char *) json_object_get_string(v); + page->optional = (strcmp("true", name) == 0) ? true : false; + } + else if (!strcmp(k, "targets")) { + if ((error_code = add_targets(page, v)) != 0) { + json_object_put(json_root); + free(buffer); + return -error_code; + } + } + else { + fprintf( + stdout, "WARNING: for page #%d - Ignoring unknown Key \"%s\"!\n", i, k); + } + } + if (!notConcerned) { + nb_pages++; + } + } + } + } + json_object_put(json_root); + free(buffer); + current_scenario_page = &pages[0]; + + return 0; +} + +static ScenarioPage_t *scenario_get_page_from_name(char *page_name) +{ + // Next line is a macro that iterate through keys/values of that JSON object + for (uint32_t i = 0; i < nb_pages; i++) { + if (!strcmp(pages[i].name, page_name)) { + return &pages[i]; + } + } + printf("ERROR: scenario_get_page_from_name() impossible to find page %s\n", page_name); + return NULL; +} + +static bool get_next_page(const char *eventType, const char *eventName) +{ + ScenarioPage_t *next_page; + PageStep_t *cur_target; + + cur_target = ¤t_scenario_page->steps[current_scenario_page->currentStep]; + + // print transition if to a different page + if (verbose && strcmp(current_scenario_page->name, cur_target->name)) { + printf("\t%s --> %s: %s[%s]\n", + current_scenario_page->name, + cur_target->name, + eventType, + eventName); + } + // get the page corresponding to the current step in current page + next_page = scenario_get_page_from_name(cur_target->name); + // if this page is NULL, it means that name is malformed. + if (next_page == NULL) { + fprintf(stderr, "ERROR: Unknown page name %s\n", cur_target->name); + current_scenario_page = NULL; + return false; + } + if ((next_page != current_scenario_page) && (current_scenario_page->saved != true)) { + if ((current_scenario_page->optional == false) + && (strcmp(current_scenario_page->name, "Entry"))) { + fprintf(stderr, + "ERROR: current page [%s] not saved before moving to a new one [%s]\n", + current_scenario_page->name, + cur_target->name); + current_scenario_page = NULL; + return false; + } + // + } + // if the reset param was set for the current step in current page, it means that we will + // restart at step 0 in current_scenario_page + if (cur_target->reset) { + next_page->currentStep = 0; + for (uint32_t i = 0; i < next_page->nbSteps; i++) { + next_page->steps[i].wait = next_page->steps[i].wait_initial; + } + } + + // printf("get_next_page: batt_level %d%%, next_page = %s, pos = + // [%d,%d]\n",current_scenario_page->steps[current_scenario_page->currentStep].batt_level, + // current_scenario_page->name,cur_page->steps[cur_page->currentStep].x,cur_page->steps[cur_page->currentStep].y); + // update step in current page + current_scenario_page->currentStep++; + // update current page + current_scenario_page = next_page; + current_scenario_page->cur_sub_page = 0; + return true; +} + +static int scenario_get_action(void) +{ + // if the last target of the current page has been consumed, stop the simulation + if (current_scenario_page->currentStep >= current_scenario_page->nbSteps) { + return 1; + } + if ((current_scenario_page->currentStep < current_scenario_page->nbSteps) + && (current_scenario_page->nb_sub_pages > 1) + && (current_scenario_page->cur_sub_page < current_scenario_page->nb_sub_pages)) { + // if multi-page text, automatically press "right" to parse all pages + return 3; + } + // if it's a wait step, simply decrease counter + if (current_scenario_page->steps[current_scenario_page->currentStep].wait > 0) { + // printf("scenario_get_action wait %d + // ms\n",current_scenario_page->steps[current_scenario_page->currentStep].wait); + current_scenario_page->steps[current_scenario_page->currentStep].wait -= 100; + } + // if it's a App event, get value + else if (current_scenario_page->steps[current_scenario_page->currentStep].app_event_idx + != 0xFF) { + uint8_t app_event_idx + = current_scenario_page->steps[current_scenario_page->currentStep].app_event_idx; + uint32_t param = current_scenario_page->steps[current_scenario_page->currentStep].param; + + if (!get_next_page("APP", appEvents[app_event_idx].name)) { + return -1; + } + // printf("scenario_get_action app_event %d\n",app_event_idx); + switch (appEvents[app_event_idx].id) { + case BTC_SIGN: + app_bitcoinSignTransaction(); + break; +#ifdef SCREEN_SIZE_WALLET + case CARDANO_SIGN: + app_cardanoSignTransaction(); + break; + case STELLAR_SIGN: + app_stellarSignTransaction(); + break; + case STELLAR_STREAM_SIGN: + app_stellarSignStreamedTransaction(); + break; + case BTC_VERIFY_ADDR: + app_bitcoinVerifyAddress(); + break; + case CARDANO_VERIFY_ADDR: + app_cardanoVerifyAddress(); + break; + case BTC_OPEN: + app_fullBitcoin(); + break; + case ETH_OPEN: + app_fullEthereum(); + break; + case MONERO_OPEN: + app_fullMonero(); + break; + case ETH_MESSAGE_SKIP: + app_ethereumSignForwardOnlyMessage(); + break; + case ETH_SIGN: + app_ethereumSignMessage(); + break; + case RECOV_OPEN: + app_fullRecoveryCheck(); + break; + case DOGE_SIGN: + app_dogecoinSignTransaction(param & 1, param & 2, param & 4, param & 8, param & 16); + break; +#endif // SCREEN_SIZE_WALLET + default: + // error + return -1; + } + } + else { + // not consumed + return 2; + } + // consumed + return 0; +} + +#ifdef HAVE_SE_TOUCH +int scenario_get_position(nbgl_touchStatePosition_t *touchStatePosition, bool previousState) +{ + // printf("scenario_get_position: %s, %d/%d, batt_level = %d%%\n", + // current_scenario_page->name, + // current_scenario_page->currentStep, + // current_scenario_page->nbSteps, + // current_scenario_page->steps[current_scenario_page->currentStep].batt_level); + touchStatePosition->state = previousState; + + int ret = scenario_get_action(); + // if not consumed by non-touch action, it's a touch action + if (ret == 2) { + uint8_t id = current_scenario_page->steps[current_scenario_page->currentStep].objTouchId; + if (id != 0) { + if (id == CONTROLS_ID) { + uint8_t index + = current_scenario_page->steps[current_scenario_page->currentStep].objTouchSubId + - '0'; + id += index; + } + nbgl_obj_t *obj = nbgl_touchGetObjectFromId(nbgl_screenGetTop(), id); + if (obj == NULL) { + printf("scenario_get_position: in %s, Impossible to find object with ID = %d\n", + current_scenario_page->name, + id); + return -1; + } + // if obj is Keypad or Keyboard, use subObjectId to get the position of the key inside + if (obj->type == KEYBOARD) { + if (!nbgl_keyboardGetPosition( + (nbgl_keyboard_t *) obj, + current_scenario_page->steps[current_scenario_page->currentStep] + .objTouchSubId, + (uint16_t *) ¤t_scenario_page + ->steps[current_scenario_page->currentStep] + .x, + (uint16_t *) ¤t_scenario_page + ->steps[current_scenario_page->currentStep] + .y)) { + printf("scenario_get_position: in %s, Impossible to find key with ID = %d\n", + current_scenario_page->name, + id); + return -1; + } + } + else if (obj->type == KEYPAD) { + char key = current_scenario_page->steps[current_scenario_page->currentStep] + .objTouchSubId; + if (key == 'b') { + key = BACKSPACE_KEY; + } + else if (key == 'v') { + key = VALIDATE_KEY; + } + if (!nbgl_keypadGetPosition((nbgl_keypad_t *) obj, + key, + (uint16_t *) ¤t_scenario_page + ->steps[current_scenario_page->currentStep] + .x, + (uint16_t *) ¤t_scenario_page + ->steps[current_scenario_page->currentStep] + .y)) { + printf("scenario_get_position: in %s, Impossible to find key with ID = %d\n", + current_scenario_page->name, + id); + return -1; + } + // printf("scenario_get_position: %s, x = %d, y = %d\n",current_scenario_page->name, + // touchStatePosition->x, touchStatePosition->y); + } + else { + // center of the object + current_scenario_page->steps[current_scenario_page->currentStep].x + = obj->area.x0 + obj->area.width / 2; + current_scenario_page->steps[current_scenario_page->currentStep].y + = obj->area.y0 + obj->area.height / 2; + // printf("scenario_get_position: %s, x = %d, y = %d, type = + // %d\n",current_scenario_page->name, touchStatePosition->x, touchStatePosition->y, + // obj->type); + } + } + touchStatePosition->x = current_scenario_page->steps[current_scenario_page->currentStep].x; + touchStatePosition->y = current_scenario_page->steps[current_scenario_page->currentStep].y; + + touchStatePosition->x += (FULL_SCREEN_WIDTH - SCREEN_WIDTH); + if (current_scenario_page->steps[current_scenario_page->currentStep].objTouchId + != LONG_PRESS_BUTTON_ID) { + touchStatePosition->state = (previousState == PRESSED) ? RELEASED : PRESSED; + } + else { + touchStatePosition->state = PRESSED; + if (current_scenario_page->steps[current_scenario_page->currentStep].long_press_wait + > 0) { + current_scenario_page->steps[current_scenario_page->currentStep].long_press_wait + -= 100; + } + } + + if ((touchStatePosition->state == RELEASED) + || ((current_scenario_page->steps[current_scenario_page->currentStep].objTouchId + == LONG_PRESS_BUTTON_ID) + && (current_scenario_page->steps[current_scenario_page->currentStep].long_press_wait + == 0))) { + if (!get_next_page( + "TOUCH", + getControlName( + current_scenario_page->steps[current_scenario_page->currentStep].objTouchId, + current_scenario_page->steps[current_scenario_page->currentStep] + .objTouchSubId))) { + return -1; + } + } + return 0; + } + else { + return ret; + } +} +#else // HAVE_SE_TOUCH + +int scenario_get_keys(uint8_t *state, uint8_t previousState) +{ + // printf("scenario_get_keys: %s, %d/%d, batt_level = + // %d%%\n",current_scenario_page->name,current_scenario_page->currentStep,current_scenario_page->nbSteps,current_scenario_page->steps[current_scenario_page->currentStep].batt_level); + *state = previousState; + + int ret = scenario_get_action(); + // if not consumed by non-key action, it's a key action + if (ret == 2) { + if (current_scenario_page->steps[current_scenario_page->currentStep].forced_key_press + == 0) { + *state + = (previousState != 0) + ? 0 + : current_scenario_page->steps[current_scenario_page->currentStep].keyState; + if ((*state == 0) && (!get_next_page("KEY", ""))) { + return -1; + } + } + else if (current_scenario_page->steps[current_scenario_page->currentStep].forced_key_press + == 1) { // release + *state = 0; + if (!get_next_page("KEY", "")) { + return -1; + } + } + else if (current_scenario_page->steps[current_scenario_page->currentStep].forced_key_press + == 2) { // press + *state = current_scenario_page->steps[current_scenario_page->currentStep].keyState; + if (!get_next_page("KEY", "")) { + return -1; + } + } + + return 0; + } + else if (ret == 3) { + // navigation across the sub-pages of a string, for Nano + *state = (previousState != 0) ? 0 : BUTTON_RIGHT; + return 0; + } + else { + return ret; + } +} +#endif // HAVE_SE_TOUCH + +int scenario_save_screen(char *framebuffer, nbgl_area_t *area) +{ + if (current_scenario_page != NULL) { + char fullPath[256]; + if (verbose) { + fprintf(stdout, + "scenario_name=%s, current_scenario_page->name=%s, area->width=%d, " + "area->height=%d\n", + scenario_name, + current_scenario_page->name, + area->width, + area->height); + } + if (current_scenario_page->name) { + if (current_scenario_page->cur_sub_page < current_scenario_page->nb_sub_pages) { + current_scenario_page->cur_sub_page++; + } + snprintf(fullPath, + sizeof(fullPath), +#ifdef SCREEN_SIZE_WALLET + "%s/%s/%s.png", +#else // SCREEN_SIZE_WALLET + "%s/%s/%s.%d.png", +#endif // SCREEN_SIZE_WALLET + dirName, + scenario_name, + current_scenario_page->name +#ifdef SCREEN_SIZE_NANO + , + current_scenario_page->cur_sub_page +#endif // SCREEN_SIZE_NANO + ); + if (framebuffer && verbose) { + fprintf(stdout, "(saving %s)\n", fullPath); + } +#ifndef NO_SCREENSHOTS_PNG + save_png((char *) framebuffer, fullPath, FULL_SCREEN_WIDTH, SCREEN_HEIGHT); +#endif // NO_SCREENSHOTS_PNG + current_scenario_page->saved = true; + } + // special case when a screen is transient and automatically closed, without user action + if ((current_scenario_page->currentStep < current_scenario_page->nbSteps) + && (current_scenario_page->steps[current_scenario_page->currentStep].auto_transition) + && (current_scenario_page->cur_sub_page == current_scenario_page->nb_sub_pages)) { + ScenarioPage_t *cur_page = current_scenario_page; + current_scenario_page = scenario_get_page_from_name( + current_scenario_page->steps[current_scenario_page->currentStep].name); + if (verbose) { + printf("\t%s --> %s: AUTOMATIC\n", cur_page->name, current_scenario_page->name); + } + cur_page->currentStep++; + } + } + return 0; +} + +char *scenario_get_current_page(void) +{ + return current_scenario_page->name; +} + +void scenario_save_json(void) +{ + char fullPath[256]; + uint32_t i, j; + bool first = true; + + // this is the end of the scenario so reset current_scenario_page to avoid + // adding string_ids in this page + current_scenario_page = NULL; + snprintf(fullPath, sizeof(fullPath), "%s/%s_flow.json", dirName, scenario_name); + FILE *fptr = fopen(fullPath, "w"); + if (!fptr) { + fprintf(stderr, "Error creating file %s.\n", fullPath); + return; + } + + fprintf(fptr, "{\n"); + fprintf(fptr, "\t\"name\":\"%s\",\n", scenario_name); + fprintf(fptr, "\t\"pages\":[\n"); + for (i = 0; i < nb_pages; i++) { + for (uint32_t k = 1; k <= pages[i].nb_sub_pages; k++) { + fprintf(fptr, "\t\t{\n"); +#ifdef SCREEN_SIZE_WALLET + fprintf(fptr, "\t\t\t\"name\": \"%s\",\n", pages[i].name); + fprintf(fptr, "\t\t\t\"image\": \"%s/%s.png\",\n", scenario_name, pages[i].name); +#else // SCREEN_SIZE_WALLET + fprintf(fptr, "\t\t\t\"name\": \"%s.%d\",\n", pages[i].name, k); + fprintf(fptr, "\t\t\t\"image\": \"%s/%s.%d.png\",\n", scenario_name, pages[i].name, k); +#endif // SCREEN_SIZE_WALLET + fprintf(fptr, "\t\t\t\"transitions\": [\n"); +#ifdef SCREEN_SIZE_WALLET + first = true; + for (j = 0; j < pages[i].nbSteps; j++) { + PageStep_t *step = &pages[i].steps[j]; + if (step->objTouchId > 0) { + if (first) { + first = false; + } + else { + fprintf(fptr, ","); + } + fprintf(fptr, "\n\t\t\t\t{\"dest_page\":\"%s\",", step->name); + fprintf(fptr, + "\n\t\t\t\t \"objectId\":\"%s\",", + getControlName(step->objTouchId, step->objTouchSubId)); + fprintf(fptr, "\n\t\t\t\t \"coords\":\"%d,%d\"}", step->x, step->y); + } + } +#endif // SCREEN_SIZE_WALLET + fprintf(fptr, "\n\t\t\t]\n"); + fprintf(fptr, "\t\t}"); + if (k < pages[i].nb_sub_pages) { + fprintf(fptr, ",\n"); + } + else { + fprintf(fptr, "\n"); + } + } + + if (i < (nb_pages - 1)) { + fprintf(fptr, ",\n"); + } + else { + fprintf(fptr, "\n"); + } + } + fprintf(fptr, "\t]\n}\n"); + fclose(fptr); + + snprintf(fullPath, sizeof(fullPath), "%s/%s_strings.json", dirName, scenario_name); + fptr = fopen(fullPath, "w"); + fprintf(fptr, "{\n"); + fprintf(fptr, "\t\"flow\":\"%s\",\n", scenario_name); + fclose(fptr); +} diff --git a/tests/screenshots/src/main/json_scenario.h b/tests/screenshots/src/main/json_scenario.h new file mode 100644 index 000000000..7e591622b --- /dev/null +++ b/tests/screenshots/src/main/json_scenario.h @@ -0,0 +1,147 @@ +#pragma once +#include +#include +#include +#include +#include +#include "nbgl_touch.h" + +/********************** + * DEFINE + **********************/ +#ifndef MAX_MATH +#define MAX_PATH 255 +#endif // MAX_PATH + +#define NB_MAX_PAGES 1024 + +#define NB_MAX_STRINGS_PER_PAGE 20 + +#define DEFAULT_BATT_LEVEL 100 +#define NB_MAX_PAGE_IDS 256 + +#ifdef SCREEN_SIZE_WALLET +#define INVALID_STRING_ID INVALID_LOC_STRING +#else // SCREEN_SIZE_WALLET +#define INVALID_STRING_ID INVALID_ID +#endif // SCREEN_SIZE_WALLET + +#define MAX_TEXT_ISSUES (NB_MAX_PAGES * NB_MAX_STRINGS_PER_PAGE) + +/********************** + * ENUMS + **********************/ + +// Battery events +enum { + NO_BATT_EVENT = 0, + CHARGING_ISSUE_EVENT, + NO_CHARGING_ISSUE_EVENT, + CHARGING_START, + CHARGING_TEMP_TOO_LOW, + NO_CHARGING_TEMP_TOO_LOW, + CHARGING_TEMP_TOO_HIGH, + NO_CHARGING_TEMP_TOO_HIGH, + BATT_TEMP_TOO_CRITICAL +}; + +// Application events +enum { + BTC_SIGN = 0, + CARDANO_SIGN, + STELLAR_SIGN, + STELLAR_STREAM_SIGN, + BTC_VERIFY_ADDR, + CARDANO_VERIFY_ADDR, + BTC_OPEN, + ETH_OPEN, + MONERO_OPEN, + ETH_MESSAGE_SKIP, + ETH_SIGN, + RECOV_OPEN, + DOGE_SIGN, +}; + +/********************** + * TYPEDEF + **********************/ + +typedef struct { + const char *name; + uint8_t id; +} Event_t; + +// list of page indexes (in pages[] variable) for a given string Id (in BOLOS_UX_LOC_STRINGS enum) +typedef struct { + uint32_t nbPages; + uint32_t Ids[NB_MAX_PAGE_IDS]; +} PageIds_t; + +typedef struct { +#ifdef HAVE_SE_TOUCH + uint16_t x; + uint16_t y; + uint8_t objTouchId; + char objTouchSubId; +#else // HAVE_SE_TOUCH + uint8_t keyState; +#endif // HAVE_SE_TOUCH + char *name; + uint16_t wait; + uint16_t wait_initial; + uint8_t power_press; + uint8_t forced_key_press; + uint8_t ble_event_idx; + uint8_t batt_level; + bool auto_transition; + bool reset; // if true, reset the counter of parent + uint8_t batt_event_idx; + uint8_t ux_event_idx; + uint8_t app_event_idx; + uint8_t seed_algorithm; + uint32_t param; + uint32_t os_flags; + uint16_t long_press_wait; +} PageStep_t; + +typedef struct { + void *next_issue; + char *name; // name of this scenario page + bool optional; // if set to true, it means it's a page only existing for some langs, so saving + // is not mandatory + uint32_t nbSteps; // number of steps for this scenario page + PageStep_t *steps; // dynamic array of steps for this scenario page + uint32_t currentStep; // current step in steps[] array + bool saved; // set to true once the page is saved (.png file created) + uint32_t nb_sub_pages; // for Nanos, when a string is plit on several sub pages + uint32_t cur_sub_page; +} ScenarioPage_t; + +typedef struct { + uint16_t id; + const char *text; +} StringDigits_t; + +/********************** + * GLOBAL VARIABLES + **********************/ +extern ScenarioPage_t *current_scenario_page; + +/********************** + * GLOBAL PROTOTYPES + **********************/ +#define store_string_infos(text, font_id, area, wrapping, nb_lines, nb_pages, bold) + +uint8_t strxcmp(char *str1, char *str2); +bool same_string(uint16_t string_id, char *text); + +void scenario_parse_args(int argc, char **argv); +int scenario_parse_json(void); +int scenario_get_position(nbgl_touchStatePosition_t *touchStatePosition, bool previousState); +int scenario_get_keys(uint8_t *state, uint8_t previousState); +int scenario_get_features(void); +void scenario_save_json(void); +char *scenario_get_current_page(void); + +int save_png(char *framebuffer, char *fullPath, uint16_t width, uint16_t height); +int scenario_save_screen(char *framebuffer, nbgl_area_t *area); diff --git a/tests/screenshots/src/main/main.c b/tests/screenshots/src/main/main.c new file mode 100644 index 000000000..1f1d18e8e --- /dev/null +++ b/tests/screenshots/src/main/main.c @@ -0,0 +1,160 @@ + +/** + * @file main + * + */ + +/********************* + * INCLUDES + *********************/ +#define _DEFAULT_SOURCE /* needed for usleep() */ +#include +#include +#include +#define SDL_MAIN_HANDLED /*To fix SDL's "undefined reference to WinMain" issue*/ +#include +#include "properties.h" +#include "apps_api.h" +#include "glyphs.h" +#include "os_settings.h" +#include "os_helpers.h" +#include "json_scenario.h" +#include "app_icons.h" +#include "nbgl_debug.h" +#include "nbgl_driver.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * VARIABLES + **********************/ + +extern nbgl_touchStatePosition_t gTouchStatePosition; +extern uint32_t G_interval_ms; + +char *dirName; +char *propertiesName; + +unsigned long gLogger = 0 + // | (1<= (FULL_SCREEN_WIDTH - SCREEN_WIDTH)) { + gTouchStatePosition.x -= FULL_SCREEN_WIDTH - SCREEN_WIDTH; + } + else { + gTouchStatePosition.state = RELEASED; + } + + if (gTouchStatePosition.state != previousState) { + nbgl_touchHandler(false, &gTouchStatePosition, currentTime); + nbgl_refresh(); + } + previousState = gTouchStatePosition.state; +#else // HAVE_SE_TOUCH + if ((previousKeyState != keyState) || (keyState != 0)) { + nbgl_buttonsHandler(keyState, currentTime); + nbgl_refresh(); + } + previousKeyState = keyState; +#endif // HAVE_SE_TOUCH + + currentTime += G_interval_ms; + } + + return 0; +} diff --git a/tests/screenshots/src/main/nbgl_driver.c b/tests/screenshots/src/main/nbgl_driver.c new file mode 100644 index 000000000..832483ec1 --- /dev/null +++ b/tests/screenshots/src/main/nbgl_driver.c @@ -0,0 +1,1114 @@ + +/** + * @file nbgl_low.c + * @brief Low-Level driver, to draw elementary forms + * + */ + +/********************* + * INCLUDES + *********************/ +#include "bolos_target.h" +#include "nbgl_driver.h" +#include "nbgl_debug.h" +#include "nbgl_image_utils.h" +#ifndef BUILD_SCREENSHOTS +#include "monitor.h" +#endif // BUILD_SCREENSHOTS +#include "uzlib.h" +#include "os_helpers.h" +#include "os_screen.h" +#include "json_scenario.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ +typedef struct KeepOutArea_s { + uint16_t x_start; + uint16_t x_end; + uint16_t y_start; + uint16_t y_end; +} KeepOutArea_t; + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ +#ifdef SCREEN_SIZE_NANO +static KeepOutArea_t keepOutArea; +#endif + +/********************** + * MACROS + **********************/ +#ifdef SCREEN_SIZE_WALLET +#define CHECK_PARAMS() \ + { \ + if (area->height & 0x3) { \ + LOG_FATAL(LOW_LOGGER, "%s: Bad height %d\n", __FUNCTION__, area->height); \ + } \ + } +#define CHECK_PARAMS_ROTATED() \ + { \ + if (area->width & 0x3) { \ + LOG_FATAL(LOW_LOGGER, "%s: Bad width %d\n", __FUNCTION__, area->width); \ + } \ + } + +#define IS_NOT_KEEP_OUT(x, y) (true) +#define IS_IN_SCREEN(x, y) \ + (((y * FULL_SCREEN_WIDTH + x) >= 0) \ + && ((y * FULL_SCREEN_WIDTH + x) < (FULL_SCREEN_WIDTH * SCREEN_HEIGHT))) +#else // SCREEN_SIZE_WALLET +#define CHECK_PARAMS() +#define CHECK_PARAMS_ROTATED() + +// ensure the pixel is not in the keepout area, and not above height limit +#define IS_NOT_KEEP_OUT(x, y) \ + (((x < keepOutArea.x_start) || (x >= keepOutArea.x_end) || (y < keepOutArea.y_start) \ + || (y >= keepOutArea.y_end))) + +#define IS_IN_SCREEN(x, y) \ + (((y * FULL_SCREEN_WIDTH + x) >= 0) \ + && ((y * FULL_SCREEN_WIDTH + x) < (FULL_SCREEN_WIDTH * SCREEN_HEIGHT))) + +#endif // SCREEN_SIZE_WALLET + +// expands a 2BPP color into a 4BPP color +#define EXPAND_TO_4BPP(__color2bpp) (((__color2bpp) << 2) | (__color2bpp)) + +/********************** + * VARIABLES + **********************/ +static uint8_t framebuffer[FULL_SCREEN_WIDTH * SCREEN_HEIGHT]; + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * @brief Draws a plain rectangle with the given parameters + * + * @param area position, size and color of the rectangle to draw + */ +void nbgl_driver_drawRect(nbgl_area_t *area) +{ + int16_t x, y; + // LOG_DEBUG(LOW_LOGGER,"nbgl_driver_drawRect: x0 = %d, y0=%d, width=%d, height=%d, + // color=%d\n",area->x0, area->y0, area->width, area->height, area->backgroundColor); + CHECK_PARAMS(); + + for (y = MAX(0, area->y0); y < MIN(SCREEN_HEIGHT, area->y0 + area->height); y++) { + for (x = area->x0; x < (area->x0 + area->width); x++) { + if (IS_IN_SCREEN(x, y) && IS_NOT_KEEP_OUT(x, y)) { + framebuffer[y * FULL_SCREEN_WIDTH + x] = EXPAND_TO_4BPP(area->backgroundColor); + } + } + } +} + +/** + * @brief Draws a horizontal line with the given parameters + * + * @note height is fixed to 4 pixels, and the mask provides the real thickness of the line + * + * @param area position, size and color of the line to draw + * @param mask bit mask to tell which pixels (in vertical axis) are to be painted in lineColor. + * bit[0] is the upper line on 4, and bit[3] is the bottom line + * @param lineColor color to be applied to the line + */ +void nbgl_driver_drawHorizontalLine(nbgl_area_t *area, uint8_t mask, color_t lineColor) +{ + CHECK_PARAMS(); + if (area->height & 0x3) { + LOG_DEBUG(LOW_LOGGER, "Forbidden height: %d\n", area->height); + return; + } + // LOG_DEBUG(LOW_LOGGER,"nbgl_ll_drawHorizontalLine: area->x0 = %d\n",area->x0); + if (area->height == 4) { + uint8_t bit; + for (bit = 0; bit < 4; bit++) { + if (((mask >> bit) & 0x1) == 1) { + memset(&framebuffer[(area->y0 + bit) * FULL_SCREEN_WIDTH + area->x0], + EXPAND_TO_4BPP(lineColor), + area->width); + } + else { + memset(&framebuffer[(area->y0 + bit) * FULL_SCREEN_WIDTH + area->x0], + EXPAND_TO_4BPP(area->backgroundColor), + area->width); + } + } + } +} + +/** + * @brief Draws the given 1 BPP bitmap with the given parameters + * + * @note y0 and height must be multiple 4 pixels, and background color is applied to 0's in bitmap + * + * @param area position, size and color of the bitmap to draw + * @param buffer bitmap buffer + * @param foreColor color to be applied to the 1's in bitmap + */ +static void nbgl_driver_draw1BPPImage(nbgl_area_t *area, uint8_t *buffer, color_t foreColor) +{ + int16_t x, y; + uint8_t shift = 0; + uint8_t *end = buffer + ((area->width * area->height + 7) / 8); + CHECK_PARAMS(); + x = area->x0 + area->width - 1; + y = area->y0; + while (buffer < end) { + uint8_t pixel_val = (*buffer >> (7 - shift)) & 0x1; + if (IS_IN_SCREEN(x, y) && IS_NOT_KEEP_OUT(x, y)) { + framebuffer[y * FULL_SCREEN_WIDTH + x] + = EXPAND_TO_4BPP(pixel_val ? foreColor : area->backgroundColor); + } + + shift++; + if (shift == 8) { + shift = 0; + buffer++; + } + if (y < (area->y0 + area->height - 1)) { + y++; + } + else { + y = area->y0; + if (x == area->x0) { + return; + } + x--; + } + } +} + +/** + * @brief Draws the given 1 BPP bitmap with the given parameters, but reading pixels from right to + * left + * + * @note y0 and height must be multiple 4 pixels, and background color is applied to 0's in bitmap + * + * @param area position, size and color of the bitmap to draw + * @param buffer bitmap buffer + * @param foreColor color to be applied to the 1's in bitmap + */ +static void nbgl_driver_draw1BPPImageVerticalMirror(nbgl_area_t *area, + uint8_t *buffer, + color_t foreColor) +{ + int16_t x, y; + uint8_t shift = 0; + uint8_t *end = buffer + (area->width * area->height / 8); + CHECK_PARAMS(); + x = area->x0; + y = area->y0; + while (buffer < end) { + uint8_t pixel_val = (*buffer >> (7 - shift)) & 0x1; + if (IS_IN_SCREEN(x, y) && IS_NOT_KEEP_OUT(x, y)) { + framebuffer[y * FULL_SCREEN_WIDTH + x] + = EXPAND_TO_4BPP(pixel_val ? foreColor : area->backgroundColor); + } + shift++; + if (shift == 8) { + shift = 0; + buffer++; + } + if (y < (area->y0 + area->height - 1)) { + y++; + } + else { + y = area->y0; + x++; + } + } +} + +/** + * @brief Draws the given 1 BPP bitmap with the given parameters, but reading pixels from top right + * to bottom left (rotation of 90 degrees, clockwise) + * + * @note y0 and height must be multiple 4 pixels, and background color is applied to 0's in bitmap + * + * @param area position, size and color of the bitmap to draw + * @param buffer bitmap buffer + * @param foreColor color to be applied to the 1's in bitmap + */ +static void nbgl_driver_draw1BPPImageRotate90(nbgl_area_t *area, uint8_t *buffer, color_t foreColor) +{ + int16_t x, y; + uint8_t shift = 0; + uint8_t *end = buffer + (area->width * area->height / 8); + CHECK_PARAMS_ROTATED(); + + x = area->x0 + area->height - 1; + y = area->y0 + area->width - 1; + while (buffer < end) { + uint8_t pixel_val = (*buffer >> (7 - shift)) & 0x1; + if (IS_IN_SCREEN(x, y) && IS_NOT_KEEP_OUT(x, y)) { + framebuffer[y * FULL_SCREEN_WIDTH + x] + = EXPAND_TO_4BPP(pixel_val ? foreColor : area->backgroundColor); + } + + shift++; + if (shift == 8) { + shift = 0; + buffer++; + } + if (x > area->x0) { + x--; + } + else { + x = area->x0 + area->height - 1; + y--; + } + } +} + +/** + * @brief Uncompress a 1BPP RLE-encoded glyph + * + * 1BPP RLE Encoder: + * + * compressed bytes contains ZZZZOOOO nibbles, with + * - ZZZZ: number of consecutives zeros (from 0 to 15) + * - OOOO: number of consecutives ones (from 0 to 15) + * + * @param area + * @param buffer buffer of RLE-encoded data + * @param buffer_len length of buffer + */ +static void nbgl_driver_draw1BPPImageRle(nbgl_area_t *area, + uint8_t *buffer, + uint32_t buffer_len, + color_t foreColor, + uint8_t nb_skipped_bytes) +{ + int16_t x, y; + uint8_t pixel; + size_t index = 0; + // Set the initial number of transparent pixels + size_t nb_zeros = (size_t) nb_skipped_bytes * 8; + size_t nb_ones = 0; + size_t remaining_pixels = area->width * area->height; + + CHECK_PARAMS(); + x = area->x0 + area->width - 1; + y = area->y0; + while (remaining_pixels && (index < buffer_len || nb_zeros || nb_ones)) { + // Reload nb_zeros & nb_ones if needed + while (!nb_zeros && !nb_ones && index < buffer_len) { + uint8_t byte = buffer[index++]; + nb_ones = byte & 0x0F; + nb_zeros = byte >> 4; + } + // Get next pixel + if (nb_zeros) { + --nb_zeros; + pixel = 0; + } + else if (nb_ones) { + --nb_ones; + pixel = 1; + } + --remaining_pixels; + + if (IS_IN_SCREEN(x, y) && IS_NOT_KEEP_OUT(x, y)) { + framebuffer[y * FULL_SCREEN_WIDTH + x] + = EXPAND_TO_4BPP(pixel ? foreColor : area->backgroundColor); + } + if (y < (area->y0 + area->height - 1)) { + y++; + } + else { + y = area->y0; + x--; + } + } +} + +/** + * @brief Draws the given 2 BPP bitmap with the given parameters. The colorMap is applied if not + * null. + * + * @note y0 and height must be multiple 4 pixels, and background color is applied to 0's in bitmap. + * + * @param area position, size and color of the bitmap to draw + * @param buffer bitmap buffer + * @param colorMap color map to be applied on given 2BPP pixels, that may be different from desired + * colors + */ +static void nbgl_driver_draw2BPPImage(nbgl_area_t *area, uint8_t *buffer, nbgl_color_map_t colorMap) +{ + int16_t x, y; + uint8_t shift = 0; + uint8_t *end = buffer + (area->width * area->height / 4); + CHECK_PARAMS(); + x = area->x0 + area->width - 1; + y = area->y0; + while (buffer < end) { + uint8_t pixel_val = (*buffer >> (6 - shift)) & 0x3; + if (IS_IN_SCREEN(x, y) && IS_NOT_KEEP_OUT(x, y)) { + if (colorMap == INVALID_COLOR_MAP) { + framebuffer[y * FULL_SCREEN_WIDTH + x] = EXPAND_TO_4BPP(pixel_val); + } + else { + framebuffer[y * FULL_SCREEN_WIDTH + x] + = EXPAND_TO_4BPP(GET_COLOR_MAP(colorMap, pixel_val)); + } + } + shift += 2; + if (shift == 8) { + shift = 0; + buffer++; + } + if (y < (area->y0 + area->height - 1)) { + y++; + } + else { + y = area->y0; + x--; + } + } +} + +/** + * @brief Draws the given 2 BPP bitmap with the given parameters, but from right to left. The + * colorMap is applied if not null. + * + * @note x0 and height must be multiple 4 pixels, and background color is applied to 0's in bitmap. + * + * @param area position, size and color of the bitmap to draw + * @param buffer bitmap buffer + * @param colorMap color map to be applied on given 2BPP pixels, that may be different from desired + * colors + */ +static void nbgl_driver_draw2BPPImageVerticalMirror(nbgl_area_t *area, + uint8_t *buffer, + nbgl_color_map_t colorMap) +{ + int16_t x, y; + uint8_t shift = 0; + uint8_t *end = buffer + (area->width * area->height / 4); + CHECK_PARAMS(); + x = area->x0; + y = area->y0; + while (buffer < end) { + uint8_t pixel_val = (*buffer >> (6 - shift)) & 0x3; + if (IS_IN_SCREEN(x, y) && IS_NOT_KEEP_OUT(x, y)) { + if (colorMap == INVALID_COLOR_MAP) { + framebuffer[y * FULL_SCREEN_WIDTH + x] = EXPAND_TO_4BPP(pixel_val); + } + else { + framebuffer[y * FULL_SCREEN_WIDTH + x] + = EXPAND_TO_4BPP(GET_COLOR_MAP(colorMap, pixel_val)); + } + } + shift += 2; + if (shift == 8) { + shift = 0; + buffer++; + } + if (y < (area->y0 + area->height - 1)) { + y++; + } + else { + y = area->y0; + x++; + } + } +} + +// 4BPP Color maps + +// Extract a color from color_map, if color_map is not NULL +#define GET_MAPPED_COLOR(color_map, color_index) \ + ((color_map == NULL) ? (color_index) : color_map[(color_index)]) + +// Return the 4BPP value corresponding to input color +static uint8_t get_4bpp_value(color_t color) +{ + switch (color) { + case BLACK: + return BLACK_4BPP; + case WHITE: + return WHITE_4BPP; + case LIGHT_GRAY: + return LIGHT_GRAY_4BPP; + case DARK_GRAY: + return DARK_GRAY_4BPP; + default: + return WHITE_4BPP; + } +} + +// Size of 4BPP color maps +#define COLOR_MAP_SIZE_4BPP 16 + +// Compute a default color map, starting from input color to area background color. +// The values are evenly distributed whenever possible. +// The map is written to output_map. +static void compute_default_color_map(uint8_t output_map[COLOR_MAP_SIZE_4BPP], + color_t color, + nbgl_area_t *area) +{ + uint8_t start_val = get_4bpp_value(color); + uint8_t end_val = get_4bpp_value(area->backgroundColor); + + int nb_values; // Number of different values to be written in the output map + int8_t incr; + if (end_val >= start_val) { + nb_values = (end_val - start_val + 1); + incr = -1; + } + else { + nb_values = (start_val - end_val + 1); + incr = 1; + } + + // Whenever (cnt << 8) reaches next_step value + // we increment the value written in the output map. + uint32_t cnt_step = ((COLOR_MAP_SIZE_4BPP << 8) / nb_values); + uint32_t next_step = cnt_step; + + // The output map is filled from the end to the start + output_map[COLOR_MAP_SIZE_4BPP - 1] = end_val; + for (uint32_t cnt = 1; cnt < COLOR_MAP_SIZE_4BPP; cnt++) { + uint8_t arr_index = COLOR_MAP_SIZE_4BPP - 1 - cnt; + if ((cnt << 8) >= next_step) { + next_step += cnt_step; + output_map[arr_index] = output_map[arr_index + 1] + incr; + } + else { + output_map[arr_index] = output_map[arr_index + 1]; + } + } +} + +// These hardcoded color maps have been manually tuned with the designer. + +static const uint8_t WHITE_ON_BLACK[] + = {WHITE_4BPP, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, BLACK_4BPP}; + +static const uint8_t DARK_GRAY_ON_WHITE[COLOR_MAP_SIZE_4BPP] + = {DARK_GRAY_4BPP, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 11, 12, 13, 14, WHITE_4BPP}; + +static const uint8_t LIGHT_GRAY_ON_WHITE[COLOR_MAP_SIZE_4BPP] + = {LIGHT_GRAY_4BPP, 10, 11, 11, 12, 12, 13, 13, 13, 14, 14, 14, 15, 15, 15, WHITE_4BPP}; + +static const uint8_t LIGHT_GRAY_ON_BLACK[COLOR_MAP_SIZE_4BPP] + = {LIGHT_GRAY_4BPP, 9, 8, 7, 6, 5, 4, 4, 3, 3, 2, 2, 1, 1, 0, BLACK_4BPP}; + +static const uint8_t *DARK_GRAY_ON_BLACK = LIGHT_GRAY_ON_BLACK; + +// Buffer where non-hardcoded color maps are computed + +static uint8_t default_color_map[16]; + +// Return a pointer to the color map corresponding to input area and color +static const uint8_t *get_color_map_array(nbgl_area_t *area, color_t color) +{ + if ((color == BLACK) && (area->backgroundColor == WHITE)) { + return NULL; // No color map to apply + } + else if ((color == WHITE) && (area->backgroundColor == BLACK)) { + return WHITE_ON_BLACK; + } + else if ((color == DARK_GRAY) && (area->backgroundColor == WHITE)) { + return DARK_GRAY_ON_WHITE; + } + else if ((color == LIGHT_GRAY) && (area->backgroundColor == WHITE)) { + return LIGHT_GRAY_ON_WHITE; + } + else if ((color == LIGHT_GRAY) && (area->backgroundColor == BLACK)) { + return LIGHT_GRAY_ON_BLACK; + } + else if ((color == DARK_GRAY) && (area->backgroundColor == BLACK)) { + return DARK_GRAY_ON_BLACK; + } + + compute_default_color_map(default_color_map, color, area); + return default_color_map; +} + +/** + * @brief Draws the given 4 BPP bitmap with the given parameters. The colorMap is applied if not + * null. + * + * @note y0 and height must be multiple 4 pixels, and background color is applied to 0's in bitmap. + * + * @param area position, size and color of the bitmap to draw + * @param buffer bitmap buffer + */ +static void nbgl_driver_draw4BPPImage(nbgl_area_t *area, uint8_t *buffer, color_t color) +{ + int16_t x, y; + uint8_t shift = 0; + uint8_t *end = buffer + (area->width * area->height / 2); + CHECK_PARAMS(); + x = area->x0 + area->width - 1; + y = area->y0; + const uint8_t *color_map = get_color_map_array(area, color); + while (buffer < end) { + uint8_t color_index = ((*buffer) >> (4 - shift)) & 0x0F; + uint8_t pixel_val = GET_MAPPED_COLOR(color_map, color_index); + + if (IS_IN_SCREEN(x, y) && IS_NOT_KEEP_OUT(x, y)) { + framebuffer[y * FULL_SCREEN_WIDTH + x] = pixel_val; + } + + shift += 4; + if (shift == 8) { + shift = 0; + buffer++; + } + if (y < (area->y0 + area->height - 1)) { + y++; + } + else { + y = area->y0; + x--; + } + } +} + +/** + * @brief Draws the given 1 BPP compressed bitmap with the given parameters. The colorMap is applied + * if not null. + * + * @note y0 and height must be multiple 4 pixels, and background color is applied to 0's in bitmap. + * + * @param area position, size and color of the bitmap to draw + * @param buffer bitmap buffer + */ +static void nbgl_ll_draw1BPPCompressedImage(nbgl_area_t *area, + uint8_t *buffer, + uint32_t bufferLen, + uint8_t *uzlibChunkBuffer, + color_t foreColor) +{ + int16_t x, y; + uint8_t shift = 0; + unsigned int dlen; + struct uzlib_uncomp d; + int res; + CHECK_PARAMS(); + x = area->x0 + area->width - 1; + y = area->y0; + + uzlib_init(); + while (bufferLen > 0) { + uint16_t compressed_chunk_len; + + // read length of compressed chunk on 2 bytes + compressed_chunk_len = 256 * buffer[1] + buffer[0]; + buffer += 2; + /* -- get decompressed length -- */ + dlen = buffer[compressed_chunk_len - 1]; + dlen = 256 * dlen + buffer[compressed_chunk_len - 2]; + dlen = 256 * dlen + buffer[compressed_chunk_len - 3]; + dlen = 256 * dlen + buffer[compressed_chunk_len - 4]; + bufferLen -= compressed_chunk_len + 2; + + /* there can be mismatch between length in the trailer and actual + data stream; to avoid buffer overruns on overlong streams, reserve + one extra byte */ + dlen++; + + /* -- decompress data -- */ + uzlib_uncompress_init(&d, NULL, 0); + + /* all 3 fields below must be initialized by user */ + d.source = buffer; + d.source_limit = buffer + compressed_chunk_len - 4; + d.source_read_cb = NULL; + + // position buffer to the end of the current compressed chunk + buffer += compressed_chunk_len; + + res = uzlib_gzip_parse_header(&d); + if (res != TINF_OK) { + printf("Error parsing header: %d\n", res); + exit(1); + } + + d.dest_start = d.dest = uzlibChunkBuffer; + + d.dest_limit = d.dest + dlen; + res = uzlib_uncompress_chksum(&d); + if (res != TINF_DONE) { + printf("error: %d\n", res); + break; + } + unsigned int i = 0; + + // write pixels + while (i < (dlen - 1)) { + uint8_t pixel_val = (uzlibChunkBuffer[i] >> (7 - shift)) & 0x1; + if (IS_IN_SCREEN(x, y) && IS_NOT_KEEP_OUT(x, y)) { + framebuffer[y * FULL_SCREEN_WIDTH + x] + = EXPAND_TO_4BPP(pixel_val ? foreColor : area->backgroundColor); + } + shift++; + if (shift == 8) { + shift = 0; + i++; + } + if (y < (area->y0 + area->height - 1)) { + y++; + } + else { + y = area->y0; + x--; + } + } + } +} + +/** + * @brief Draws the given 4 BPP compressed bitmap with the given parameters. The colorMap is applied + * if not null. + * + * @note y0 and height must be multiple 4 pixels, and background color is applied to 0's in bitmap. + * + * @param area position, size and color of the bitmap to draw + * @param buffer bitmap buffer + */ +static void nbgl_ll_draw4BPPCompressedImage(nbgl_area_t *area, + uint8_t *buffer, + uint32_t bufferLen, + uint8_t *uzlibChunkBuffer, + color_t foreColor) +{ + int16_t x, y; + uint8_t shift = 0; + unsigned int dlen; + struct uzlib_uncomp d; + int res; + CHECK_PARAMS(); + x = area->x0 + area->width - 1; + y = area->y0; + const uint8_t *color_map = get_color_map_array(area, foreColor); + + uzlib_init(); + while (bufferLen > 0) { + uint16_t compressed_chunk_len; + + // read length of compressed chunk on 2 bytes + compressed_chunk_len = 256 * buffer[1] + buffer[0]; + buffer += 2; + /* -- get decompressed length -- */ + dlen = buffer[compressed_chunk_len - 1]; + dlen = 256 * dlen + buffer[compressed_chunk_len - 2]; + dlen = 256 * dlen + buffer[compressed_chunk_len - 3]; + dlen = 256 * dlen + buffer[compressed_chunk_len - 4]; + bufferLen -= compressed_chunk_len + 2; + + /* there can be mismatch between length in the trailer and actual + data stream; to avoid buffer overruns on overlong streams, reserve + one extra byte */ + dlen++; + + /* -- decompress data -- */ + uzlib_uncompress_init(&d, NULL, 0); + + /* all 3 fields below must be initialized by user */ + d.source = buffer; + d.source_limit = buffer + compressed_chunk_len - 4; + d.source_read_cb = NULL; + + // position buffer to the end of the current compressed chunk + buffer += compressed_chunk_len; + + res = uzlib_gzip_parse_header(&d); + if (res != TINF_OK) { + printf("Error parsing header: %d\n", res); + exit(1); + } + + d.dest_start = d.dest = uzlibChunkBuffer; + + d.dest_limit = d.dest + dlen; + res = uzlib_uncompress_chksum(&d); + if (res != TINF_DONE) { + printf("error: %d\n", res); + break; + } + unsigned int i = 0; + // write pixels + while (i < (dlen - 1)) { + uint8_t pixel_val = (uzlibChunkBuffer[i] >> (4 - shift)) & 0xF; + if (IS_IN_SCREEN(x, y) && IS_NOT_KEEP_OUT(x, y)) { + framebuffer[y * FULL_SCREEN_WIDTH + x] = GET_MAPPED_COLOR(color_map, pixel_val); + } + shift += 4; + if (shift == 8) { + shift = 0; + i++; + } + if (y < (area->y0 + area->height - 1)) { + y++; + } + else { + y = area->y0; + x--; + } + } + } +} + +/** + * @brief Draws the given given bitmap with the given parameters. The colorMap is applied on 2BPP + * image if not @ref INVALID_COLOR_MAP. + * + * @note y0 and height must be multiple 4 pixels, and background color is applied to 0's in 1BPP + * bitmap. + * + * @param area position, size and color of the bitmap to draw + * @param buffer bitmap buffer + * @param transformation transformatin to be applied. This is a bit field, filled with @ref + * VERTICAL_MIRROR for example + * @param colorMap color map to be applied on given image, for 2 BPP image. For 1BPP, only the + * color[0] is used, for foreground color + */ +void nbgl_driver_drawImage(nbgl_area_t *area, + uint8_t *buffer, + nbgl_transformation_t transformation, + nbgl_color_map_t colorMap) +{ + if (area->bpp == NBGL_BPP_1) { + if (transformation == NO_TRANSFORMATION) { + nbgl_driver_draw1BPPImage(area, buffer, GET_COLOR_MAP(colorMap, 0)); + } + else if (transformation == VERTICAL_MIRROR) { + nbgl_driver_draw1BPPImageVerticalMirror(area, buffer, GET_COLOR_MAP(colorMap, 0)); + } + else if (transformation == ROTATE_90_CLOCKWISE) { + nbgl_driver_draw1BPPImageRotate90(area, buffer, GET_COLOR_MAP(colorMap, 0)); + } + } + else if (area->bpp == NBGL_BPP_2) { + if (transformation == NO_TRANSFORMATION) { + nbgl_driver_draw2BPPImage(area, buffer, colorMap); + } + else if (transformation == VERTICAL_MIRROR) { + nbgl_driver_draw2BPPImageVerticalMirror(area, buffer, colorMap); + } + } + else if (area->bpp == NBGL_BPP_4) { + nbgl_driver_draw4BPPImage(area, buffer, colorMap); + } + else { + LOG_FATAL(LOW_LOGGER, "Forbidden bpp: %d\n", area->bpp); + } +} + +/////// RLE functions + +uint8_t rle_4bpp_uncompress_buffer[SCREEN_WIDTH * SCREEN_HEIGHT / 2]; + +// Write nb_pix 4BPP pixels of the same color to the display +static inline void fill_4bpp_pixels_color(uint8_t color, + uint8_t nb_pix, + uint32_t *pix_cnt, + uint8_t *remaining, + uint32_t max_pix_cnt) +{ + // Check max + if ((*pix_cnt + nb_pix) > max_pix_cnt) { + return; + } + + uint8_t double_pix = (color << 4) | color; + bool first_non_aligned = (*pix_cnt % 2); + bool last_non_aligned = (*pix_cnt + nb_pix) % 2; + + // If first pixel to write is non aligned + if (first_non_aligned) { + *remaining |= (0x0F & color); + // Remaining byte is now full: send it + // spi_queue_byte(*remaining); + rle_4bpp_uncompress_buffer[(*pix_cnt) / 2] = *remaining; + *remaining = 0; + (*pix_cnt)++; + nb_pix--; + } + + // Write pixels 2 by 2 + for (uint32_t i = 0; i < nb_pix / 2; i++) { + rle_4bpp_uncompress_buffer[(*pix_cnt) / 2] = double_pix; + (*pix_cnt) += 2; + } + + // If last pixel to write is non aligned + if (last_non_aligned) { + // Save remaining pixel + *remaining = color << 4; + (*pix_cnt)++; + } +} + +// Handle 'Copy white' RLE instruction +static uint32_t handle_4bpp_repeat_white(uint8_t byte_in, + uint32_t *pix_cnt, + const uint8_t *color_map, + uint8_t *remaining_byte, + uint32_t max_pix_cnt) +{ + uint8_t nb_pix = (byte_in & 0x3F) + 1; + uint8_t color = GET_MAPPED_COLOR(color_map, 0x0F); + fill_4bpp_pixels_color(color, nb_pix, pix_cnt, remaining_byte, max_pix_cnt); + return 1; +} + +// Handle 'Repeat color' RLE instruction +static uint32_t handle_4bpp_repeat_color(uint8_t byte_in, + uint32_t *pix_cnt, + const uint8_t *color_map, + uint8_t *remaining_byte, + uint32_t max_pix_cnt) +{ + uint8_t nb_pix = ((byte_in & 0x70) >> 4) + 1; + uint8_t color = GET_MAPPED_COLOR(color_map, byte_in & 0x0F); + fill_4bpp_pixels_color(color, nb_pix, pix_cnt, remaining_byte, max_pix_cnt); + return 1; +} + +// Handle 'Copy' RLE instruction +static uint32_t handle_4bpp_copy(uint8_t *bytes_in, + uint32_t bytes_in_len, + uint32_t *pix_cnt, + const uint8_t *color_map, + uint8_t *remaining_byte, + uint32_t max_pix_cnt) +{ + uint8_t nb_pix = ((bytes_in[0] & 0x30) >> 4) + 3; + uint8_t nb_bytes_read = (nb_pix / 2) + 1; + + // Do not read outside of bytes_in + if (nb_bytes_read > bytes_in_len) { + return nb_bytes_read; + } + + for (uint8_t i = 0; i < nb_pix; i++) { + // Write pix by pix + uint8_t index = (i + 1) / 2; + uint8_t color_index; + if ((i % 2) == 0) { + color_index = bytes_in[index] & 0x0F; + } + else { + color_index = bytes_in[index] >> 4; + } + uint8_t color = GET_MAPPED_COLOR(color_map, color_index); + fill_4bpp_pixels_color(color, 1, pix_cnt, remaining_byte, max_pix_cnt); + } + + return nb_bytes_read; +} + +/** + * @brief Uncompress a 4BPP RLE-encoded glyph + * + * 4BPP RLE Encoder: + * + * 'Repeat white' byte + * - [11][number of whites to write - 1 (6 bits)] + * + * 'Copy' byte + * - [10][number of nibbles to copy - 3 (2)][nib0 (4 bits)][nib1 (4 bits)]... + * + * 'Repeat color' byte + * - [0][number of bytes to write - 1 (3 bits)][color index (4 bits)] + * + * @param area + * @param buffer buffer of RLE-encoded data + * @param len length of buffer + */ +static void nbgl_driver_draw4BPPImageRle(nbgl_area_t *area, + uint8_t *buffer, + uint32_t buffer_len, + color_t fore_color, + uint8_t nb_skipped_bytes) +{ + CHECK_PARAMS(); + + if (area->bpp != NBGL_BPP_4) { + return; + } + + const uint8_t *color_map = get_color_map_array(area, fore_color); + // Fill buffer with background color + uint8_t background = GET_MAPPED_COLOR(color_map, 0xF); + background |= background << 4; + memset(rle_4bpp_uncompress_buffer, background, sizeof(rle_4bpp_uncompress_buffer)); + uint32_t max_pix_cnt = (area->width * area->height); + // 'transparent' bytes was set (with memset), not need to write them again + uint32_t pix_cnt = nb_skipped_bytes * 2; + uint32_t read_cnt = 0; + uint8_t remaining = 0; + + while (read_cnt < buffer_len) { + uint8_t byte = buffer[read_cnt]; + if (byte & 0x80) { + if (byte & 0x40) { + read_cnt + += handle_4bpp_repeat_white(byte, &pix_cnt, color_map, &remaining, max_pix_cnt); + } + else { + uint8_t *bytes_in = buffer + read_cnt; + uint32_t max_len = buffer_len - read_cnt; + read_cnt += handle_4bpp_copy( + bytes_in, max_len, &pix_cnt, color_map, &remaining, max_pix_cnt); + } + } + else { + read_cnt + += handle_4bpp_repeat_color(byte, &pix_cnt, color_map, &remaining, max_pix_cnt); + } + } + + if ((pix_cnt % 2) != 0) { + rle_4bpp_uncompress_buffer[(pix_cnt) / 2] = remaining; + } + + // Now send it as if it was a 4BPP uncompressed image + color_t background_color = area->backgroundColor; + area->backgroundColor = WHITE; + nbgl_driver_draw4BPPImage(area, rle_4bpp_uncompress_buffer, BLACK); + area->backgroundColor = background_color; +} + +/** + * @brief Draws the given bitmap file with the given parameters. The colorMap is applied on 2BPP + * image if not @ref INVALID_COLOR_MAP. + * + * @note y0 and height must be multiple 4 pixels, and background color is applied to 0's in 1BPP + * bitmap. + * + * @param area position and background color of the bitmap to draw + * @param buffer bitmap file buffer, with ledger format + * @param colorMap color map to be applied on given image, for 2 BPP image. For 1BPP, only the + * color[0] is used, for foreground color + * @param uzlib_chunk_buffer Work buffer of size GZLIB_UNCOMPRESSED_CHUNK + */ +void nbgl_driver_drawImageFile(nbgl_area_t *area, + uint8_t *buffer, + nbgl_color_map_t colorMap, + uint8_t *uzlib_chunk_buffer) +{ + // uzlib_chunk_buffer is ignored in simulation + uint8_t compression = GET_IMAGE_FILE_COMPRESSION(buffer); + + area->width = GET_IMAGE_FILE_WIDTH(buffer); + area->height = GET_IMAGE_FILE_HEIGHT(buffer); + area->bpp = GET_IMAGE_FILE_BPP(buffer); + if (compression == NBGL_NO_COMPRESSION) { + nbgl_driver_drawImage(area, GET_IMAGE_FILE_BUFFER(buffer), NO_TRANSFORMATION, colorMap); + } + else if (compression == NBGL_GZLIB_COMPRESSION) { + // TODO: add support of compression at least for 2BPP + if (area->bpp == NBGL_BPP_1) { + nbgl_ll_draw1BPPCompressedImage(area, + GET_IMAGE_FILE_BUFFER(buffer), + GET_IMAGE_FILE_BUFFER_LEN(buffer), + uzlib_chunk_buffer, + GET_COLOR_MAP(colorMap, 0)); + } + else if (area->bpp == NBGL_BPP_4) { + nbgl_ll_draw4BPPCompressedImage(area, + GET_IMAGE_FILE_BUFFER(buffer), + GET_IMAGE_FILE_BUFFER_LEN(buffer), + uzlib_chunk_buffer, + (color_t) GET_COLOR_MAP(colorMap, 0)); + } + } + else if (compression == NBGL_RLE_COMPRESSION) { + nbgl_driver_drawImageRle(area, + GET_IMAGE_FILE_BUFFER(buffer), + GET_IMAGE_FILE_BUFFER_LEN(buffer), + (color_t) GET_COLOR_MAP(colorMap, 0), + 0); + } +} + +/** + * @brief function to actually refresh the given area on screen + * + * @param area the area to refresh + */ +void nbgl_driver_refreshArea(nbgl_area_t *area, + nbgl_refresh_mode_t mode, + nbgl_post_refresh_t post_refresh) +{ + UNUSED(mode); + UNUSED(post_refresh); + // LOG_WARN(LOW_LOGGER,"nbgl_driver_refreshArea(): x0:%d,y0:%d,width:%d,height:%d\n", area->x0, + // area->y0, area->width, area->height); + if ((area->width == 0) || (area->height == 0)) { + return; + } + // LOG_DEBUG(LOW_LOGGER,"nbgl_driver_refreshArea(): \n"); +#ifndef BUILD_SCREENSHOTS + monitor_flush(area, framebuffer); +#else // BUILD_SCREENSHOTS + scenario_save_screen((char *) framebuffer, area); +#endif // BUILD_SCREENSHOTS +} + +void nbgl_driver_drawImageRle(nbgl_area_t *area, + uint8_t *buffer, + uint32_t buffer_len, + color_t fore_color, + uint8_t nb_skipped_bytes) +{ + if (area->bpp == NBGL_BPP_4) { + nbgl_driver_draw4BPPImageRle(area, buffer, buffer_len, fore_color, nb_skipped_bytes); + } + else if (area->bpp == NBGL_BPP_1) { + nbgl_driver_draw1BPPImageRle(area, buffer, buffer_len, fore_color, nb_skipped_bytes); + } +} + +#ifdef HAVE_SE_SCREEN +void screen_set_keepout(unsigned int x, unsigned int y, unsigned int width, unsigned int height) +{ + if (!width || !height) { + keepOutArea.x_start = 0xFFFE; + keepOutArea.x_end = 0xFFFF; + keepOutArea.y_start = 0xFFFE; + keepOutArea.y_end = 0xFFFF; + return; + } + + if (x > SCREEN_WIDTH) { + x = SCREEN_WIDTH; + } + if (y > SCREEN_HEIGHT) { + y = SCREEN_HEIGHT; + } + if (width > SCREEN_WIDTH) { + width = SCREEN_WIDTH; + } + if (height > SCREEN_HEIGHT) { + height = SCREEN_HEIGHT; + } + + keepOutArea.x_start = x; + keepOutArea.x_end = x + width; + keepOutArea.y_start = y; + keepOutArea.y_end = y + height; +} +#endif // HAVE_SE_SCREEN diff --git a/tests/screenshots/src/main/nbgl_driver.h b/tests/screenshots/src/main/nbgl_driver.h new file mode 100644 index 000000000..5ff047a26 --- /dev/null +++ b/tests/screenshots/src/main/nbgl_driver.h @@ -0,0 +1,101 @@ +/** + * @file nbgl_driver.h + * @brief Low-Level driver API, to draw elementary forms + * + */ + +#ifndef NBGL_DRIVER_H +#define NBGL_DRIVER_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "bolos_target.h" +#include "nbgl_types.h" + +/********************* + * DEFINES + *********************/ +/** + * Width of the full screen in pixels, including side, front and unusable edge + */ +#ifdef TARGET_STAX +#define FULL_SCREEN_WIDTH 496 +#endif // TARGET_STAX +#ifdef TARGET_FLEX +#define FULL_SCREEN_WIDTH 480 +#endif // TARGET_FLEX +#ifndef SCREEN_SIZE_WALLET +#define FULL_SCREEN_WIDTH 128 +#endif // SCREEN_SIZE_WALLET + +/** + * @brief macro to get the color from color_map for the __code__ input + * @param color_map u8 representing the color map + * @param color input code (from @ref color_t) + */ +#define GET_COLOR_MAP(color_map, color) ((color_map >> (color * 2)) & 0x3) + +/** + * Value on 4BPP of WHITE + * + */ +#define WHITE_4BPP 0xF + +/** + * Value on 4BPP of LIGHT_GRAY + * + */ +#define LIGHT_GRAY_4BPP 0xA + +/** + * Value on 4BPP of DARK_GRAY + * + */ +#define DARK_GRAY_4BPP 0x5 + +/** + * Value on 4BPP of BLACK + * + */ +#define BLACK_4BPP 0x0 + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ +void nbgl_driver_drawRect(nbgl_area_t *area); +void nbgl_driver_drawHorizontalLine(nbgl_area_t *area, uint8_t mask, color_t lineColor); +void nbgl_driver_drawImage(nbgl_area_t *area, + uint8_t *buffer, + nbgl_transformation_t transformation, + nbgl_color_map_t colorMap); +void nbgl_driver_drawImageFile(nbgl_area_t *area, + uint8_t *buffer, + nbgl_color_map_t colorMap, + uint8_t *uzlib_chunk_buffer); +void nbgl_driver_drawImageRle(nbgl_area_t *area, + uint8_t *buffer, + uint32_t buffer_len, + color_t fore_color, + uint8_t nb_skipped_bytes); +void nbgl_driver_refreshArea(nbgl_area_t *area, + nbgl_refresh_mode_t mode, + nbgl_post_refresh_t post_refresh); + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* NBGL_DRIVER_H */ diff --git a/tests/screenshots/src/main/nbgl_front.c b/tests/screenshots/src/main/nbgl_front.c new file mode 100644 index 000000000..0f982da76 --- /dev/null +++ b/tests/screenshots/src/main/nbgl_front.c @@ -0,0 +1,141 @@ + +/** + * @file nbgl_front.c + * @brief Low-Level driver for front screen, to draw elementary shapes + * + */ + +/********************* + * INCLUDES + *********************/ +#include "nbgl_driver.h" +#include "nbgl_front.h" +#include "nbgl_debug.h" +#include "os_helpers.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * VARIABLES + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * @brief Draws a plain rectangle with the given parameters + * + * @param area position, size and color of the rectangle to draw + */ +void nbgl_frontDrawRect(const nbgl_area_t *area) +{ + ((nbgl_area_t *) area)->x0 += FULL_SCREEN_WIDTH - SCREEN_WIDTH; + nbgl_driver_drawRect((nbgl_area_t *) area); + ((nbgl_area_t *) area)->x0 -= FULL_SCREEN_WIDTH - SCREEN_WIDTH; +} + +/** + * @brief Draws a horizontal line with the given parameters + * + * @note height is fixed to 4 pixels, and the mask provides the real thickness of the line + * + * @param area position, size and color of the line to draw + * @param mask bit mask to tell which pixels (in vertical axis) are to be painted in lineColor. + * bit[0] is the upper line on 4, and bit[3] is the bottom line + * @param lineColor color to be applied to the line + */ +void nbgl_frontDrawHorizontalLine(const nbgl_area_t *area, uint8_t mask, color_t lineColor) +{ + ((nbgl_area_t *) area)->x0 += FULL_SCREEN_WIDTH - SCREEN_WIDTH; + nbgl_driver_drawHorizontalLine((nbgl_area_t *) area, mask, lineColor); + ((nbgl_area_t *) area)->x0 -= FULL_SCREEN_WIDTH - SCREEN_WIDTH; +} + +/** + * @brief Draws the given given bitmap with the given parameters. The colorMap is applied on 2BPP + * image if not @ref INVALID_COLOR_MAP. + * + * @note y0 and height must be multiple 4 pixels, and background color is applied to 0's in 1BPP + * bitmap. + * + * @param area position, size and color of the bitmap to draw + * @param buffer bitmap buffer + * @param transformation transformatin to be applied. This is a bit field, filled with @ref + * VERTICAL_MIRROR for example + * @param colorMap color map to be applied on given image, for 2 BPP image. For 1BPP, only the + * color[0] is used, for foreground color + */ +void nbgl_frontDrawImage(const nbgl_area_t *area, + const uint8_t *buffer, + nbgl_transformation_t transformation, + nbgl_color_map_t colorMap) +{ + ((nbgl_area_t *) area)->x0 += FULL_SCREEN_WIDTH - SCREEN_WIDTH; + nbgl_driver_drawImage((nbgl_area_t *) area, (uint8_t *) buffer, transformation, colorMap); + ((nbgl_area_t *) area)->x0 -= FULL_SCREEN_WIDTH - SCREEN_WIDTH; +} + +/** + * @brief Draws the given bitmap file with the given parameters. The colorMap is applied on 2BPP + * image if not @ref INVALID_COLOR_MAP. + * + * @note y0 and height must be multiple 4 pixels, and background color is applied to 0's in 1BPP + * bitmap. + * + * @param area position and background color of the bitmap to draw + * @param buffer bitmap file buffer, with ledger format + * @param colorMap color map to be applied on given image, for 2 BPP image. For 1BPP, only the + * color[0] is used, for foreground color + * @param uzlib_chunk_buffer Work buffer of size GZLIB_UNCOMPRESSED_CHUNK + */ +void nbgl_frontDrawImageFile(const nbgl_area_t *area, + const uint8_t *buffer, + nbgl_color_map_t colorMap, + const uint8_t *uzlib_chunk_buffer) +{ + UNUSED(uzlib_chunk_buffer); + ((nbgl_area_t *) area)->x0 += FULL_SCREEN_WIDTH - SCREEN_WIDTH; + nbgl_driver_drawImageFile( + (nbgl_area_t *) area, (uint8_t *) buffer, colorMap, (uint8_t *) uzlib_chunk_buffer); + ((nbgl_area_t *) area)->x0 -= FULL_SCREEN_WIDTH - SCREEN_WIDTH; +} +/** + * @brief function to actually refresh the given area on screen + * + * @param area the area to refresh + */ +void nbgl_frontRefreshArea(const nbgl_area_t *area, + nbgl_refresh_mode_t mode, + nbgl_post_refresh_t post_refresh) +{ + ((nbgl_area_t *) area)->x0 += FULL_SCREEN_WIDTH - SCREEN_WIDTH; + nbgl_driver_refreshArea((nbgl_area_t *) area, mode, post_refresh); + ((nbgl_area_t *) area)->x0 -= FULL_SCREEN_WIDTH - SCREEN_WIDTH; +} + +void nbgl_frontDrawImageRle(const nbgl_area_t *area, + const uint8_t *buffer, + uint32_t buffer_len, + color_t fore_color, + uint8_t nb_skipped_bytes) +{ + ((nbgl_area_t *) area)->x0 += FULL_SCREEN_WIDTH - SCREEN_WIDTH; + nbgl_driver_drawImageRle( + (nbgl_area_t *) area, (uint8_t *) buffer, buffer_len, fore_color, nb_skipped_bytes); + ((nbgl_area_t *) area)->x0 -= FULL_SCREEN_WIDTH - SCREEN_WIDTH; +} diff --git a/tests/screenshots/src/main/png.c b/tests/screenshots/src/main/png.c new file mode 100644 index 000000000..f6246f28d --- /dev/null +++ b/tests/screenshots/src/main/png.c @@ -0,0 +1,164 @@ +#include +#include +#include +#include +#include "json_scenario.h" + +/* A coloured pixel. */ + +typedef struct { + uint8_t red; + uint8_t green; + uint8_t blue; +} pixel_t; + +/* A picture. */ + +typedef struct { + pixel_t *pixels; + size_t width; + size_t height; +} bitmap_t; + +/* Given "bitmap", this returns the pixel of bitmap at the point + ("x", "y"). */ + +static pixel_t *pixel_at(bitmap_t *bitmap, int x, int y) +{ + return bitmap->pixels + bitmap->width * y + x; +} + +/* Write "bitmap" to a PNG file specified by "path"; returns 0 on + success, non-zero on error. */ + +static int save_png_to_file(bitmap_t *bitmap, const char *path) +{ + FILE *fp; + png_structp png_ptr = NULL; + png_infop info_ptr = NULL; + size_t x, y; + png_byte **row_pointers = NULL; + /* "status" contains the return value of this function. At first + it is set to a value which means 'failure'. When the routine + has finished its work, it is set to a value which means + 'success'. */ + int status = -1; + /* The following number is set by trial and error only. I cannot + see where it it is documented in the libpng manual. + */ + int pixel_size = 3; + int depth = 8; + + fp = fopen(path, "wb"); + if (!fp) { + goto fopen_failed; + } + + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (png_ptr == NULL) { + goto png_create_write_struct_failed; + } + + info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == NULL) { + goto png_create_info_struct_failed; + } + + /* Set up error handling. */ + + if (setjmp(png_jmpbuf(png_ptr))) { + goto png_failure; + } + + /* Set image attributes. */ + + png_set_IHDR(png_ptr, + info_ptr, + bitmap->width, + bitmap->height, + depth, + PNG_COLOR_TYPE_RGB, + PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT); + + /* Initialize rows of PNG. */ + + row_pointers = png_malloc(png_ptr, bitmap->height * sizeof(png_byte *)); + for (y = 0; y < bitmap->height; y++) { + png_byte *row = png_malloc(png_ptr, sizeof(uint8_t) * bitmap->width * pixel_size); + row_pointers[y] = row; + for (x = 0; x < bitmap->width; x++) { + pixel_t *pixel = pixel_at(bitmap, x, y); + *row++ = pixel->red; + *row++ = pixel->green; + *row++ = pixel->blue; + } + } + + /* Write the image data to "fp". */ + + png_init_io(png_ptr, fp); + png_set_rows(png_ptr, info_ptr, row_pointers); + png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL); + + /* The routine has successfully written the file, so we set + "status" to a value which indicates success. */ + + status = 0; + + for (y = 0; y < bitmap->height; y++) { + png_free(png_ptr, row_pointers[y]); + } + png_free(png_ptr, row_pointers); + +png_failure: +png_create_info_struct_failed: + png_destroy_write_struct(&png_ptr, &info_ptr); +png_create_write_struct_failed: + fclose(fp); +fopen_failed: + return status; +} + +int save_png(char *framebuffer, char *fullPath, uint16_t width, uint16_t height) +{ + bitmap_t bitmap; + uint32_t x; + uint32_t y; + int status; + + status = 0; + + /* Create an image. */ + + bitmap.width = width; + bitmap.height = height; + + bitmap.pixels = calloc(bitmap.width * bitmap.height, sizeof(pixel_t)); + + if (!bitmap.pixels) { + return -1; + } + + for (y = 0; y < bitmap.height; y++) { + for (x = 0; x < bitmap.width; x++) { + pixel_t *pixel = pixel_at(&bitmap, x, y); + pixel->red + = framebuffer[(y * bitmap.width) + x] + (framebuffer[(y * bitmap.width) + x] << 4); + pixel->green = pixel->red; + pixel->blue = pixel->red; + } + } + + /* Write the image to a file 'bitmap.png'. */ + // printf("saving to %s\n",fullPath); + if (save_png_to_file(&bitmap, fullPath)) { + fprintf(stderr, "Error writing file %s.\n", fullPath); + status = -1; + } + + free(bitmap.pixels); + + return status; +} diff --git a/tests/screenshots/src/main/properties.c b/tests/screenshots/src/main/properties.c new file mode 100644 index 000000000..077f24f7b --- /dev/null +++ b/tests/screenshots/src/main/properties.c @@ -0,0 +1,171 @@ +/** + * @file properties.c + * @brief read properties from a file.properties file, in JSON format + * + */ + +/********************* + * INCLUDES + *********************/ +#include +#include +#include +#include "properties.h" +// You may have to install packet libjson-c-dev +#include + +static char *buffer = NULL; + +void properties_init(char *filename) +{ + FILE *json_file; + size_t size; + + if ((json_file = fopen(filename, "r")) == NULL) { + fprintf(stdout, "Unable to read properties JSON file %s!\n", filename); + return; + } + + // Determine file size + if (fseek(json_file, 0, SEEK_END) != 0) { + fprintf(stdout, "Error seeking to the end of JSON file %s!\n", filename); + fclose(json_file); + return; + } + + if ((size = ftell(json_file)) == 0) { + fprintf(stdout, "Error getting size of JSON file %s!\n", filename); + fclose(json_file); + return; + } + + if (fseek(json_file, 0, SEEK_SET) != 0) { + fprintf(stdout, "Error seeking to the start of JSON file %s!\n", filename); + fclose(json_file); + return; + } + + // Allocate memory and read the file + if ((buffer = malloc(size + 1)) == NULL) { + fprintf(stdout, "Error allocating %lu bytes to read JSON file %s!\n", size, filename); + fclose(json_file); + return; + } + if (fread(buffer, 1, size, json_file) != size) { + fprintf(stdout, "Error reading %lu bytes from JSON file %s!\n", size, filename); + free(buffer); + fclose(json_file); + return; + } + buffer[size] = 0; + fclose(json_file); +} + +static bool search_string_property(char *prop_name, char **val) +{ + struct json_object *json_root; + + if ((json_root = json_tokener_parse(buffer)) == NULL) { + fprintf(stdout, "An error occurred while parsing properties JSON file!\n"); + return false; + } + + if (json_type_object != json_object_get_type(json_root)) { + fprintf(stdout, "There is no valid entry in file!\n"); + json_object_put(json_root); + return false; + } + // Next line is a macro that iterate through keys/values of that JSON object + json_object_object_foreach(json_root, key, value) + { + if (!strcmp(key, prop_name)) { + *val = (char *) json_object_get_string(value); + return true; + } + } + json_object_put(json_root); + return false; +} + +static bool search_int_property(char *prop_name, long *val) +{ + struct json_object *json_root; + + if ((json_root = json_tokener_parse(buffer)) == NULL) { + fprintf(stdout, "An error occurred while parsing properties JSON file!\n"); + return false; + } + + if (json_type_object != json_object_get_type(json_root)) { + fprintf(stdout, "There is no valid entry in file!\n"); + json_object_put(json_root); + return false; + } + // Next line is a macro that iterate through keys/values of that JSON object + json_object_object_foreach(json_root, key, value) + { + if (!strcmp(key, prop_name)) { + *val = json_object_get_int(value); + return true; + } + } + json_object_put(json_root); + return false; +} + +long properties_read_int(char *name, long defaultValue) +{ + long val; + bool found; + + // search property name + found = search_int_property(name, &val); + if (!found) { + return defaultValue; + } + return val; +} + +char *properties_read_string(char *name, char *defaultValue) +{ + bool found; + char *ret, *val; + + // search property name + found = search_string_property(name, &val); + if (found == false) { + return defaultValue; + } + ret = (char *) malloc(strlen(val) + 1); + memcpy(ret, val, strlen(val) + 1); + + return ret; +} + +bool properties_read_bool(char *name, bool defaultValue) +{ + bool ret; + struct json_object *json_root; + + if ((json_root = json_tokener_parse(buffer)) == NULL) { + fprintf(stdout, "An error occurred while parsing properties JSON file!\n"); + return NULL; + } + + if (json_type_object != json_object_get_type(json_root)) { + fprintf(stdout, "There is no valid entry in file!\n"); + json_object_put(json_root); + return NULL; + } + // Next line is a macro that iterate through keys/values of that JSON object + json_object_object_foreach(json_root, key, value) + { + if (!strcmp(key, name)) { + ret = (json_object_get_boolean(value) != 0); + return ret; + } + } + json_object_put(json_root); + + return defaultValue; +} diff --git a/tests/screenshots/src/main/properties.h b/tests/screenshots/src/main/properties.h new file mode 100644 index 000000000..36eeb1d2b --- /dev/null +++ b/tests/screenshots/src/main/properties.h @@ -0,0 +1,24 @@ +/** + * @file properties.h + * + */ + +#ifndef PROPERTIES_H +#define PROPERTIES_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void properties_init(char *filename); +long properties_read_int(char *name, long defaultValue); +char *properties_read_string(char *name, char *defaultValue); +bool properties_read_bool(char *name, bool defaultValue); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* PROPERTIES_H */ diff --git a/tests/screenshots/src/main/stubs.c b/tests/screenshots/src/main/stubs.c new file mode 100644 index 000000000..8fd56359d --- /dev/null +++ b/tests/screenshots/src/main/stubs.c @@ -0,0 +1,294 @@ + +/** + * @file stubs.c + * @brief bolos functions stubs + * + */ + +/********************* + * INCLUDES + *********************/ +#define _DEFAULT_SOURCE /* needed for usleep() */ +#include +#include +#include "properties.h" +#include "nbgl_driver.h" +#include "nbgl_side.h" +#include "nbgl_draw.h" +#include "nbgl_screen.h" +#include "nbgl_fonts.h" +#include "nbgl_debug.h" +#include "uzlib.h" +#include "glyphs.h" +#include "os_pin.h" +#include "os_endorsement.h" +#include "os_settings.h" +#include "os_seed.h" +#include "os_id.h" +#include "os_nvm.h" +#include "os_pic.h" +#include "os_app.h" +#include "os_halt.h" +#include "os_io_seproxyhal.h" +#include "os_memory.h" +#include "os_registry.h" +#include "os_screen.h" +#include "os_task.h" +#include "os_helpers.h" +#include "appflags.h" +#include "cx_errors.h" +#include "lcx_rng.h" +#include "ux_loc.h" +#include "sha256.h" + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * VARIABLES + **********************/ +#ifdef HAVE_SE_TOUCH +nbgl_touchStatePosition_t gTouchStatePosition; +#endif +unsigned char G_io_seproxyhal_spi_buffer[IO_SEPROXYHAL_BUFFER_SIZE_B]; +extern bool verbose; + +bool globalError = false; +extern char *dirName; + +unsigned char G_io_apdu_buffer[IO_APDU_BUFFER_SIZE]; + +uint32_t G_interval_ms = 100; + +#ifdef HAVE_SE_TOUCH +bool touchEnabled = true; +#endif +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ +/////////////////////////////////////////////////////////////////////////////////////////// +// fake implementation of os functions +/////////////////////////////////////////////////////////////////////////////////////////// + +#ifdef HAVE_BLE +void io_seph_ble_enable(unsigned char enable) +{ + UNUSED(enable); +} +void io_seph_ble_clear_bond_db(void) {} +void io_seph_ble_name_changed(void) {} + +void io_seph_ux_accept_pairing(unsigned char status) +{ + UNUSED(status); +} +#endif // HAVE_BLE + +unsigned int io_button_read(void); + +unsigned int io_button_read(void) +{ + return 0; +} + +int bytes_to_hex(char *out, size_t outl, const void *value, size_t len) +{ + const uint8_t *bytes = (const uint8_t *) value; + const char *hex = "0123456789ABCDEF"; + + if (outl < 2 * len + 1) { + *out = '\0'; + return -1; + } + + for (size_t i = 0; i < len; i++) { + *out++ = hex[(bytes[i] >> 4) & 0xf]; + *out++ = hex[bytes[i] & 0xf]; + } + *out = 0; + return 0; +} + +unsigned short io_seph_recv(unsigned char *buffer, unsigned short maxlength, unsigned int flags) +{ + UNUSED(buffer); + UNUSED(flags); + return maxlength; +} + +void io_seph_send(const unsigned char *buffer, unsigned short length) +{ + UNUSED(buffer); + UNUSED(length); +} + +#ifdef HAVE_PIEZO_SOUND +void io_seproxyhal_play_tune(tune_index_e tune_index) +{ + UNUSED(tune_index); +} +#endif // HAVE_PIEZO_SOUND + +#ifdef HAVE_SERIALIZED_NBGL +void io_seproxyhal_send_nbgl_serialized(nbgl_serialized_event_type_e event, nbgl_obj_t *obj) +{ + UNUSED(event); + UNUSED(obj); +} +#endif // HAVE_SERIALIZED_NBGL + +void io_seproxyhal_se_reset(void) +{ + if (verbose) { + printf("io_seproxyhal_se_reset\n"); + } +} + +void io_seproxyhal_disable_io(void) {} + +void io_seproxyhal_general_status(void) {} + +#ifdef HAVE_NFC +void io_seproxyhal_nfc_power(bool forceInit) +{ + UNUSED(forceInit); +} +#endif // HAVE_NFC + +#ifdef HAVE_BLE +void os_ux_set_status(unsigned int ux_id, unsigned int status) +{ + UNUSED(ux_id); + UNUSED(status); +} +#endif // HAVE_BLE + +void io_seph_ux_redisplay(void) +{ + nbgl_screenRedraw(); +} + +void halt(void) {} + +void cx_rng_no_throw(uint8_t *buffer, size_t len) +{ + size_t i; + for (i = 0; i < len; i++) { +#ifdef BUILD_SCREENSHOTS + buffer[i] = 0; +#else // BUILD_SCREENSHOTS + buffer[i] = rand() & 0xFF; +#endif // BUILD_SCREENSHOTS + } +} + +uint32_t cx_rng_u32_range_func(uint32_t a, uint32_t b, cx_rng_u32_range_randfunc_t randfunc) +{ + uint32_t range = b - a; + uint32_t r; + + if ((range & (range - 1)) == 0) { // special case: range is a power of 2 + r = randfunc(); + return a + r % range; + } + + uint32_t chunk_size = UINT32_MAX / range; + uint32_t last_chunk_value = chunk_size * range; + r = randfunc(); + while (r >= last_chunk_value) { + r = randfunc(); + } + return a + r / chunk_size; +} + +void os_get_memory_info(meminfo_t *meminfo) +{ + UNUSED(meminfo); +} + +#ifdef HAVE_SE_TOUCH +void touch_get_last_info(io_touch_info_t *info) +{ + info->state = (gTouchStatePosition.state == PRESSED) ? true : false; + info->x = gTouchStatePosition.x; + info->y = gTouchStatePosition.y; +} +#endif // HAVE_SE_TOUCH + +void os_sched_exit(bolos_task_status_t status_code) +{ + UNUSED(status_code); + exit(-1); +} + +unsigned int os_sched_current_task(void) +{ + return TASK_USER; +} + +void nbgl_screen_reinit(void) {} + +#ifdef HAVE_STAX_CONFIG_DISPLAY_FAST_MODE +void nbgl_screen_config_fast_mode(uint8_t setting) +{ + UNUSED(setting); +} +#endif // HAVE_STAX_CONFIG_DISPLAY_FAST_MODE + +#ifdef HAVE_STAX_DISPLAY_FAST_MODE +void nbgl_screen_update_temperature(uint8_t temp_degrees) +{ + UNUSED(temp_degrees); +} +#endif // HAVE_STAX_DISPLAY_FAST_MODE + +#ifdef HAVE_SE_EINK_DISPLAY +void nbgl_wait_pipeline(void) {} +#endif + +#ifdef HAVE_SE_TOUCH +void touch_set_state(bool enable) +{ + if (enable == touchEnabled) { + return; + } + if (verbose) { + printf("touch_set_state %s\n", enable ? "ENABLED" : "DISABLED"); + } + touchEnabled = enable; +} + +uint8_t touch_exclude_borders(uint8_t excluded_borders) +{ + return excluded_borders; +} +#endif // HAVE_SE_TOUCH + +#ifdef HAVE_SE_SCREEN +void screen_set_brightness(unsigned int percent) +{ + UNUSED(percent); +} +#endif // HAVE_SE_SCREEN + +void *pic(void *linked_address) +{ + return linked_address; +} diff --git a/tests/screenshots/src/uzlib/adler32.c b/tests/screenshots/src/uzlib/adler32.c new file mode 100644 index 000000000..bc984269e --- /dev/null +++ b/tests/screenshots/src/uzlib/adler32.c @@ -0,0 +1,103 @@ +/* + * Adler-32 checksum + * + * Copyright (c) 2003 by Joergen Ibsen / Jibz + * All Rights Reserved + * + * http://www.ibsensoftware.com/ + * + * This software is provided 'as-is', without any express + * or implied warranty. In no event will the authors be + * held liable for any damages arising from the use of + * this software. + * + * Permission is granted to anyone to use this software + * for any purpose, including commercial applications, + * and to alter it and redistribute it freely, subject to + * the following restrictions: + * + * 1. The origin of this software must not be + * misrepresented; you must not claim that you + * wrote the original software. If you use this + * software in a product, an acknowledgment in + * the product documentation would be appreciated + * but is not required. + * + * 2. Altered source versions must be plainly marked + * as such, and must not be misrepresented as + * being the original software. + * + * 3. This notice may not be removed or altered from + * any source distribution. + */ + +/* + * Adler-32 algorithm taken from the zlib source, which is + * Copyright (C) 1995-1998 Jean-loup Gailly and Mark Adler + */ + +#include "tinf.h" + +#define A32_BASE 65521 +#define A32_NMAX 5552 + +uint32_t uzlib_adler32(const void *data, unsigned int length, uint32_t prev_sum /* 1 */) +{ + const unsigned char *buf = (const unsigned char *) data; + + unsigned int s1 = prev_sum & 0xffff; + unsigned int s2 = prev_sum >> 16; + + while (length > 0) { + int k = length < A32_NMAX ? length : A32_NMAX; + int i; + + for (i = k / 16; i; --i, buf += 16) { + s1 += buf[0]; + s2 += s1; + s1 += buf[1]; + s2 += s1; + s1 += buf[2]; + s2 += s1; + s1 += buf[3]; + s2 += s1; + s1 += buf[4]; + s2 += s1; + s1 += buf[5]; + s2 += s1; + s1 += buf[6]; + s2 += s1; + s1 += buf[7]; + s2 += s1; + + s1 += buf[8]; + s2 += s1; + s1 += buf[9]; + s2 += s1; + s1 += buf[10]; + s2 += s1; + s1 += buf[11]; + s2 += s1; + s1 += buf[12]; + s2 += s1; + s1 += buf[13]; + s2 += s1; + s1 += buf[14]; + s2 += s1; + s1 += buf[15]; + s2 += s1; + } + + for (i = k % 16; i; --i) { + s1 += *buf++; + s2 += s1; + } + + s1 %= A32_BASE; + s2 %= A32_BASE; + + length -= k; + } + + return ((uint32_t) s2 << 16) | s1; +} diff --git a/tests/screenshots/src/uzlib/crc32.c b/tests/screenshots/src/uzlib/crc32.c new file mode 100644 index 000000000..c04645c73 --- /dev/null +++ b/tests/screenshots/src/uzlib/crc32.c @@ -0,0 +1,72 @@ +/* + * CRC32 checksum + * + * Copyright (c) 1998-2003 by Joergen Ibsen / Jibz + * All Rights Reserved + * + * http://www.ibsensoftware.com/ + * + * This software is provided 'as-is', without any express + * or implied warranty. In no event will the authors be + * held liable for any damages arising from the use of + * this software. + * + * Permission is granted to anyone to use this software + * for any purpose, including commercial applications, + * and to alter it and redistribute it freely, subject to + * the following restrictions: + * + * 1. The origin of this software must not be + * misrepresented; you must not claim that you + * wrote the original software. If you use this + * software in a product, an acknowledgment in + * the product documentation would be appreciated + * but is not required. + * + * 2. Altered source versions must be plainly marked + * as such, and must not be misrepresented as + * being the original software. + * + * 3. This notice may not be removed or altered from + * any source distribution. + */ + +/* + * CRC32 algorithm taken from the zlib source, which is + * Copyright (C) 1995-1998 Jean-loup Gailly and Mark Adler + */ + +#include "tinf.h" + +static const uint32_t tinf_crc32tab[16] = {0x00000000, + 0x1db71064, + 0x3b6e20c8, + 0x26d930ac, + 0x76dc4190, + 0x6b6b51f4, + 0x4db26158, + 0x5005713c, + 0xedb88320, + 0xf00f9344, + 0xd6d6a3e8, + 0xcb61b38c, + 0x9b64c2b0, + 0x86d3d2d4, + 0xa00ae278, + 0xbdbdf21c}; + +/* crc is previous value for incremental computation, 0xffffffff initially */ +uint32_t uzlib_crc32(const void *data, unsigned int length, uint32_t crc) +{ + const unsigned char *buf = (const unsigned char *) data; + unsigned int i; + + for (i = 0; i < length; ++i) { + crc ^= buf[i]; + crc = tinf_crc32tab[crc & 0x0f] ^ (crc >> 4); + crc = tinf_crc32tab[crc & 0x0f] ^ (crc >> 4); + } + + // return value suitable for passing in next time, for final value invert it + return crc /* ^ 0xffffffff*/; +} diff --git a/tests/screenshots/src/uzlib/defl_static.c b/tests/screenshots/src/uzlib/defl_static.c new file mode 100644 index 000000000..75952f83c --- /dev/null +++ b/tests/screenshots/src/uzlib/defl_static.c @@ -0,0 +1,310 @@ +/* + +Routines in this file are based on: +Zlib (RFC1950 / RFC1951) compression for PuTTY. + +PuTTY is copyright 1997-2014 Simon Tatham. + +Portions copyright Robert de Bath, Joris van Rantwijk, Delian +Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas Barry, +Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, Markus +Kuhn, Colin Watson, and CORE SDI S.A. + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE +FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include +#include +#include +#include +#include "uzlib.h" +#include "defl_static.h" + +#define snew(type) ((type *) malloc(sizeof(type))) +#define snewn(n, type) ((type *) malloc((n) * sizeof(type))) +#define sresize(x, n, type) ((type *) realloc((x), (n) * sizeof(type))) +#define sfree(x) (free((x))) + +#ifndef FALSE +#define FALSE 0 +#define TRUE (!FALSE) +#endif + +/* ---------------------------------------------------------------------- + * Zlib compression. We always use the static Huffman tree option. + * Mostly this is because it's hard to scan a block in advance to + * work out better trees; dynamic trees are great when you're + * compressing a large file under no significant time constraint, + * but when you're compressing little bits in real time, things get + * hairier. + * + * I suppose it's possible that I could compute Huffman trees based + * on the frequencies in the _previous_ block, as a sort of + * heuristic, but I'm not confident that the gain would balance out + * having to transmit the trees. + */ + +void outbits(struct uzlib_comp *out, unsigned long bits, int nbits) +{ + assert(out->noutbits + nbits <= 32); + out->outbits |= bits << out->noutbits; + out->noutbits += nbits; + while (out->noutbits >= 8) { + if (out->outlen >= out->outsize) { + out->outsize = out->outlen + 64; + out->outbuf = sresize(out->outbuf, out->outsize, unsigned char); + } + out->outbuf[out->outlen++] = (unsigned char) (out->outbits & 0xFF); + out->outbits >>= 8; + out->noutbits -= 8; + } +} + +static const unsigned char mirrorbytes[256] = { + 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0, + 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, + 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4, + 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc, + 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2, + 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa, + 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6, + 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, + 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1, + 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9, + 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, + 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd, + 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3, + 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, + 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7, + 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff, +}; + +typedef struct { + uint8_t extrabits; + uint8_t min, max; +} len_coderecord; + +typedef struct { + uint8_t code, extrabits; + uint16_t min, max; +} dist_coderecord; + +#define TO_LCODE(x, y) x - 3, y - 3 +#define FROM_LCODE(x) (x + 3) + +static const len_coderecord lencodes[] = { + {0, TO_LCODE(3, 3) }, + {0, TO_LCODE(4, 4) }, + {0, TO_LCODE(5, 5) }, + {0, TO_LCODE(6, 6) }, + {0, TO_LCODE(7, 7) }, + {0, TO_LCODE(8, 8) }, + {0, TO_LCODE(9, 9) }, + {0, TO_LCODE(10, 10) }, + {1, TO_LCODE(11, 12) }, + {1, TO_LCODE(13, 14) }, + {1, TO_LCODE(15, 16) }, + {1, TO_LCODE(17, 18) }, + {2, TO_LCODE(19, 22) }, + {2, TO_LCODE(23, 26) }, + {2, TO_LCODE(27, 30) }, + {2, TO_LCODE(31, 34) }, + {3, TO_LCODE(35, 42) }, + {3, TO_LCODE(43, 50) }, + {3, TO_LCODE(51, 58) }, + {3, TO_LCODE(59, 66) }, + {4, TO_LCODE(67, 82) }, + {4, TO_LCODE(83, 98) }, + {4, TO_LCODE(99, 114)}, + {4, TO_LCODE(115, 130)}, + {5, TO_LCODE(131, 162)}, + {5, TO_LCODE(163, 194)}, + {5, TO_LCODE(195, 226)}, + {5, TO_LCODE(227, 257)}, + {0, TO_LCODE(258, 258)}, +}; + +static const dist_coderecord distcodes[] = { + {0, 0, 1, 1 }, + {1, 0, 2, 2 }, + {2, 0, 3, 3 }, + {3, 0, 4, 4 }, + {4, 1, 5, 6 }, + {5, 1, 7, 8 }, + {6, 2, 9, 12 }, + {7, 2, 13, 16 }, + {8, 3, 17, 24 }, + {9, 3, 25, 32 }, + {10, 4, 33, 48 }, + {11, 4, 49, 64 }, + {12, 5, 65, 96 }, + {13, 5, 97, 128 }, + {14, 6, 129, 192 }, + {15, 6, 193, 256 }, + {16, 7, 257, 384 }, + {17, 7, 385, 512 }, + {18, 8, 513, 768 }, + {19, 8, 769, 1024 }, + {20, 9, 1025, 1536 }, + {21, 9, 1537, 2048 }, + {22, 10, 2049, 3072 }, + {23, 10, 3073, 4096 }, + {24, 11, 4097, 6144 }, + {25, 11, 6145, 8192 }, + {26, 12, 8193, 12288}, + {27, 12, 12289, 16384}, + {28, 13, 16385, 24576}, + {29, 13, 24577, 32768}, +}; + +void zlib_literal(struct uzlib_comp *out, unsigned char c) +{ + if (out->comp_disabled) { + /* + * We're in an uncompressed block, so just output the byte. + */ + outbits(out, c, 8); + return; + } + + if (c <= 143) { + /* 0 through 143 are 8 bits long starting at 00110000. */ + outbits(out, mirrorbytes[0x30 + c], 8); + } + else { + /* 144 through 255 are 9 bits long starting at 110010000. */ + outbits(out, 1 + 2 * mirrorbytes[0x90 - 144 + c], 9); + } +} + +void zlib_match(struct uzlib_comp *out, int distance, int len) +{ + const dist_coderecord *d; + const len_coderecord *l; + int i, j, k; + int lcode; + + assert(!out->comp_disabled); + + while (len > 0) { + int thislen; + + /* + * We can transmit matches of lengths 3 through 258 + * inclusive. So if len exceeds 258, we must transmit in + * several steps, with 258 or less in each step. + * + * Specifically: if len >= 261, we can transmit 258 and be + * sure of having at least 3 left for the next step. And if + * len <= 258, we can just transmit len. But if len == 259 + * or 260, we must transmit len-3. + */ + thislen = (len > 260 ? 258 : len <= 258 ? len : len - 3); + len -= thislen; + + /* + * Binary-search to find which length code we're + * transmitting. + */ + i = -1; + j = sizeof(lencodes) / sizeof(*lencodes); + while (1) { + assert(j - i >= 2); + k = (j + i) / 2; + if (thislen < FROM_LCODE(lencodes[k].min)) { + j = k; + } + else if (thislen > FROM_LCODE(lencodes[k].max)) { + i = k; + } + else { + l = &lencodes[k]; + break; /* found it! */ + } + } + + lcode = l - lencodes + 257; + + /* + * Transmit the length code. 256-279 are seven bits + * starting at 0000000; 280-287 are eight bits starting at + * 11000000. + */ + if (lcode <= 279) { + outbits(out, mirrorbytes[(lcode - 256) * 2], 7); + } + else { + outbits(out, mirrorbytes[0xc0 - 280 + lcode], 8); + } + + /* + * Transmit the extra bits. + */ + if (l->extrabits) { + outbits(out, thislen - FROM_LCODE(l->min), l->extrabits); + } + + /* + * Binary-search to find which distance code we're + * transmitting. + */ + i = -1; + j = sizeof(distcodes) / sizeof(*distcodes); + while (1) { + assert(j - i >= 2); + k = (j + i) / 2; + if (distance < distcodes[k].min) { + j = k; + } + else if (distance > distcodes[k].max) { + i = k; + } + else { + d = &distcodes[k]; + break; /* found it! */ + } + } + + /* + * Transmit the distance code. Five bits starting at 00000. + */ + outbits(out, mirrorbytes[d->code * 8], 5); + + /* + * Transmit the extra bits. + */ + if (d->extrabits) { + outbits(out, distance - d->min, d->extrabits); + } + } +} + +void zlib_start_block(struct uzlib_comp *out) +{ + // outbits(out, 0x9C78, 16); + outbits(out, 1, 1); /* Final block */ + outbits(out, 1, 2); /* Static huffman block */ +} + +void zlib_finish_block(struct uzlib_comp *out) +{ + outbits(out, 0, 7); /* close block */ + outbits(out, 0, 7); /* Make sure all bits are flushed */ +} diff --git a/tests/screenshots/src/uzlib/defl_static.h b/tests/screenshots/src/uzlib/defl_static.h new file mode 100644 index 000000000..cdfa973ba --- /dev/null +++ b/tests/screenshots/src/uzlib/defl_static.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) uzlib authors + * + * This software is provided 'as-is', without any express + * or implied warranty. In no event will the authors be + * held liable for any damages arising from the use of + * this software. + * + * Permission is granted to anyone to use this software + * for any purpose, including commercial applications, + * and to alter it and redistribute it freely, subject to + * the following restrictions: + * + * 1. The origin of this software must not be + * misrepresented; you must not claim that you + * wrote the original software. If you use this + * software in a product, an acknowledgment in + * the product documentation would be appreciated + * but is not required. + * + * 2. Altered source versions must be plainly marked + * as such, and must not be misrepresented as + * being the original software. + * + * 3. This notice may not be removed or altered from + * any source distribution. + */ + +/* This files contains type declaration and prototypes for defl_static.c. + They may be altered/distinct from the originals used in PuTTY source + code. */ + +void outbits(struct uzlib_comp *ctx, unsigned long bits, int nbits); +void zlib_start_block(struct uzlib_comp *ctx); +void zlib_finish_block(struct uzlib_comp *ctx); +void zlib_literal(struct uzlib_comp *ctx, unsigned char c); +void zlib_match(struct uzlib_comp *ctx, int distance, int len); diff --git a/tests/screenshots/src/uzlib/genlz77.c b/tests/screenshots/src/uzlib/genlz77.c new file mode 100644 index 000000000..0407263f9 --- /dev/null +++ b/tests/screenshots/src/uzlib/genlz77.c @@ -0,0 +1,127 @@ +/* + * genlz77 - Generic LZ77 compressor + * + * Copyright (c) 2014 by Paul Sokolovsky + * + * This software is provided 'as-is', without any express + * or implied warranty. In no event will the authors be + * held liable for any damages arising from the use of + * this software. + * + * Permission is granted to anyone to use this software + * for any purpose, including commercial applications, + * and to alter it and redistribute it freely, subject to + * the following restrictions: + * + * 1. The origin of this software must not be + * misrepresented; you must not claim that you + * wrote the original software. If you use this + * software in a product, an acknowledgment in + * the product documentation would be appreciated + * but is not required. + * + * 2. Altered source versions must be plainly marked + * as such, and must not be misrepresented as + * being the original software. + * + * 3. This notice may not be removed or altered from + * any source distribution. + */ +#include +#include +#include +#include "uzlib.h" + +#if 0 +#define HASH_BITS 12 +#else +#define HASH_BITS data->hash_bits +#endif + +#define HASH_SIZE (1 << HASH_BITS) + +/* Minimum and maximum length of matches to look for, inclusive */ +#define MIN_MATCH 3 +#define MAX_MATCH 258 + +/* Max offset of the match to look for, inclusive */ +#if 0 +#define MAX_OFFSET 32768 +#else +#define MAX_OFFSET data->dict_size +#endif + +/* Hash function can be defined as macro or as inline function */ + +/*#define HASH(p) (p[0] + p[1] + p[2])*/ + +/* This is hash function from liblzf */ +static inline int HASH(struct uzlib_comp *data, const uint8_t *p) +{ + int v = (p[0] << 16) | (p[1] << 8) | p[2]; + int hash = ((v >> (3 * 8 - HASH_BITS)) - v) & (HASH_SIZE - 1); + return hash; +} + +#ifdef DUMP_LZTXT + +/* Counter for approximate compressed length in LZTXT mode. */ +/* Literal is counted as 1, copy as 2 bytes. */ +unsigned approx_compressed_len; + +void literal(void *data, uint8_t val) +{ + printf("L%02x # %c\n", val, (val >= 0x20 && val <= 0x7e) ? val : '?'); + approx_compressed_len++; +} + +void copy(void *data, unsigned offset, unsigned len) +{ + printf("C-%u,%u\n", offset, len); + approx_compressed_len += 2; +} + +#else + +static inline void literal(void *data, uint8_t val) +{ + zlib_literal(data, val); +} + +static inline void copy(void *data, unsigned offset, unsigned len) +{ + zlib_match(data, offset, len); +} + +#endif + +void uzlib_compress(struct uzlib_comp *data, const uint8_t *src, unsigned slen) +{ + const uint8_t *top = src + slen - MIN_MATCH; + while (src < top) { + int h = HASH(data, src); + const uint8_t **bucket = &data->hash_table[h & (HASH_SIZE - 1)]; + const uint8_t *subs = *bucket; + *bucket = src; + if (subs && src > subs && (src - subs) <= MAX_OFFSET && !memcmp(src, subs, MIN_MATCH)) { + src += MIN_MATCH; + const uint8_t *m = subs + MIN_MATCH; + int len = MIN_MATCH; + while (*src == *m && len < MAX_MATCH && src < top) { + src++; + m++; + len++; + } + copy(data, src - len - subs, len); + } + else { + literal(data, *src++); + } + } + // Process buffer tail, which is less than MIN_MATCH + // (and so it doesn't make sense to look for matches there) + top += MIN_MATCH; + while (src < top) { + literal(data, *src++); + } +} diff --git a/tests/screenshots/src/uzlib/tinf.h b/tests/screenshots/src/uzlib/tinf.h new file mode 100644 index 000000000..ae6e1c407 --- /dev/null +++ b/tests/screenshots/src/uzlib/tinf.h @@ -0,0 +1,3 @@ +/* Compatibility header for the original tinf lib/older versions of uzlib. + Note: may be removed in the future, please migrate to uzlib.h. */ +#include "uzlib.h" diff --git a/tests/screenshots/src/uzlib/tinf_compat.h b/tests/screenshots/src/uzlib/tinf_compat.h new file mode 100644 index 000000000..240d19541 --- /dev/null +++ b/tests/screenshots/src/uzlib/tinf_compat.h @@ -0,0 +1,9 @@ +/* This header contains compatibility defines for the original tinf API + and uzlib 2.x and below API. These defines are deprecated and going + to be removed in the future, so applications should migrate to new + uzlib API. */ +#define TINF_DATA struct uzlib_uncomp + +#define destSize dest_size +#define destStart dest_start +#define readSource source_read_cb diff --git a/tests/screenshots/src/uzlib/tinfgzip.c b/tests/screenshots/src/uzlib/tinfgzip.c new file mode 100644 index 000000000..cd0c923a7 --- /dev/null +++ b/tests/screenshots/src/uzlib/tinfgzip.c @@ -0,0 +1,122 @@ +/* + * uzlib - tiny deflate/inflate library (deflate, gzip, zlib) + * + * Copyright (c) 2003 by Joergen Ibsen / Jibz + * All Rights Reserved + * + * http://www.ibsensoftware.com/ + * + * Copyright (c) 2014-2018 by Paul Sokolovsky + * + * This software is provided 'as-is', without any express + * or implied warranty. In no event will the authors be + * held liable for any damages arising from the use of + * this software. + * + * Permission is granted to anyone to use this software + * for any purpose, including commercial applications, + * and to alter it and redistribute it freely, subject to + * the following restrictions: + * + * 1. The origin of this software must not be + * misrepresented; you must not claim that you + * wrote the original software. If you use this + * software in a product, an acknowledgment in + * the product documentation would be appreciated + * but is not required. + * + * 2. Altered source versions must be plainly marked + * as such, and must not be misrepresented as + * being the original software. + * + * 3. This notice may not be removed or altered from + * any source distribution. + */ + +#include "tinf.h" + +#define FTEXT 1 +#define FHCRC 2 +#define FEXTRA 4 +#define FNAME 8 +#define FCOMMENT 16 + +void tinf_skip_bytes(TINF_DATA *d, int num); +uint16_t tinf_get_uint16(TINF_DATA *d); + +void tinf_skip_bytes(TINF_DATA *d, int num) +{ + while (num--) { + uzlib_get_byte(d); + } +} + +uint16_t tinf_get_uint16(TINF_DATA *d) +{ + unsigned int v = uzlib_get_byte(d); + v = (uzlib_get_byte(d) << 8) | v; + return v; +} + +int uzlib_gzip_parse_header(TINF_DATA *d) +{ + unsigned char flg; + + /* -- check format -- */ + + /* check id bytes */ + if (uzlib_get_byte(d) != 0x1f || uzlib_get_byte(d) != 0x8b) { + return TINF_DATA_ERROR; + } + + /* check method is deflate */ + if (uzlib_get_byte(d) != 8) { + return TINF_DATA_ERROR; + } + + /* get flag byte */ + flg = uzlib_get_byte(d); + + /* check that reserved bits are zero */ + if (flg & 0xe0) { + return TINF_DATA_ERROR; + } + + /* -- find start of compressed data -- */ + + /* skip rest of base header of 10 bytes */ + tinf_skip_bytes(d, 6); + + /* skip extra data if present */ + if (flg & FEXTRA) { + unsigned int xlen = tinf_get_uint16(d); + tinf_skip_bytes(d, xlen); + } + + /* skip file name if present */ + if (flg & FNAME) { + while (uzlib_get_byte(d)) + ; + } + + /* skip file comment if present */ + if (flg & FCOMMENT) { + while (uzlib_get_byte(d)) + ; + } + + /* check header crc if present */ + if (flg & FHCRC) { + /*unsigned int hcrc =*/tinf_get_uint16(d); + + // TODO: Check! + // if (hcrc != (tinf_crc32(src, start - src) & 0x0000ffff)) + // return TINF_DATA_ERROR; + } + + /* initialize for crc32 checksum */ + d->checksum_type = TINF_CHKSUM_CRC; + d->checksum = ~0; + + return TINF_OK; +} diff --git a/tests/screenshots/src/uzlib/tinflate.c b/tests/screenshots/src/uzlib/tinflate.c new file mode 100644 index 000000000..d6cb75a26 --- /dev/null +++ b/tests/screenshots/src/uzlib/tinflate.c @@ -0,0 +1,685 @@ +/* + * uzlib - tiny deflate/inflate library (deflate, gzip, zlib) + * + * Copyright (c) 2003 by Joergen Ibsen / Jibz + * All Rights Reserved + * http://www.ibsensoftware.com/ + * + * Copyright (c) 2014-2018 by Paul Sokolovsky + * + * This software is provided 'as-is', without any express + * or implied warranty. In no event will the authors be + * held liable for any damages arising from the use of + * this software. + * + * Permission is granted to anyone to use this software + * for any purpose, including commercial applications, + * and to alter it and redistribute it freely, subject to + * the following restrictions: + * + * 1. The origin of this software must not be + * misrepresented; you must not claim that you + * wrote the original software. If you use this + * software in a product, an acknowledgment in + * the product documentation would be appreciated + * but is not required. + * + * 2. Altered source versions must be plainly marked + * as such, and must not be misrepresented as + * being the original software. + * + * 3. This notice may not be removed or altered from + * any source distribution. + */ + +#include +#include +#include "tinf.h" + +#define UZLIB_DUMP_ARRAY(heading, arr, size) \ + { \ + printf("%s", heading); \ + for (int i = 0; i < size; ++i) { \ + printf(" %d", (arr)[i]); \ + } \ + printf("\n"); \ + } + +uint32_t tinf_get_le_uint32(TINF_DATA *d); +uint32_t tinf_get_be_uint32(TINF_DATA *d); + +/* --------------------------------------------------- * + * -- uninitialized global data (static structures) -- * + * --------------------------------------------------- */ + +#ifdef RUNTIME_BITS_TABLES + +/* extra bits and base tables for length codes */ +unsigned char length_bits[30]; +unsigned short length_base[30]; + +/* extra bits and base tables for distance codes */ +unsigned char dist_bits[30]; +unsigned short dist_base[30]; + +#else + +const unsigned char length_bits[30] + = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5}; +const unsigned short length_base[30] + = {3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, + 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258}; + +const unsigned char dist_bits[30] = {0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, + 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13}; +const unsigned short dist_base[30] + = {1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, + 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577}; + +#endif + +/* special ordering of code length codes */ +const unsigned char clcidx[] = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + +/* ----------------------- * + * -- utility functions -- * + * ----------------------- */ + +#ifdef RUNTIME_BITS_TABLES +/* build extra bits and base tables */ +static void tinf_build_bits_base(unsigned char *bits, unsigned short *base, int delta, int first) +{ + int i, sum; + + /* build bits table */ + for (i = 0; i < delta; ++i) { + bits[i] = 0; + } + for (i = 0; i < 30 - delta; ++i) { + bits[i + delta] = i / delta; + } + + /* build base table */ + for (sum = first, i = 0; i < 30; ++i) { + base[i] = sum; + sum += 1 << bits[i]; + } +} +#endif + +/* build the fixed huffman trees */ +static void tinf_build_fixed_trees(TINF_TREE *lt, TINF_TREE *dt) +{ + int i; + + /* build fixed length tree */ + for (i = 0; i < 7; ++i) { + lt->table[i] = 0; + } + + lt->table[7] = 24; + lt->table[8] = 152; + lt->table[9] = 112; + + for (i = 0; i < 24; ++i) { + lt->trans[i] = 256 + i; + } + for (i = 0; i < 144; ++i) { + lt->trans[24 + i] = i; + } + for (i = 0; i < 8; ++i) { + lt->trans[24 + 144 + i] = 280 + i; + } + for (i = 0; i < 112; ++i) { + lt->trans[24 + 144 + 8 + i] = 144 + i; + } + + /* build fixed distance tree */ + for (i = 0; i < 5; ++i) { + dt->table[i] = 0; + } + + dt->table[5] = 32; + + for (i = 0; i < 32; ++i) { + dt->trans[i] = i; + } +} + +/* given an array of code lengths, build a tree */ +static void tinf_build_tree(TINF_TREE *t, const unsigned char *lengths, unsigned int num) +{ + unsigned short offs[16]; + unsigned int i, sum; + + /* clear code length count table */ + for (i = 0; i < 16; ++i) { + t->table[i] = 0; + } + + /* scan symbol lengths, and sum code length counts */ + for (i = 0; i < num; ++i) { + t->table[lengths[i]]++; + } + +#if UZLIB_CONF_DEBUG_LOG >= 2 + UZLIB_DUMP_ARRAY("codelen counts:", t->table, TINF_ARRAY_SIZE(t->table)); +#endif + + /* In the lengths array, 0 means unused code. So, t->table[0] now contains + number of unused codes. But table's purpose is to contain # of codes of + particular length, and there're 0 codes of length 0. */ + t->table[0] = 0; + + /* compute offset table for distribution sort */ + for (sum = 0, i = 0; i < 16; ++i) { + offs[i] = sum; + sum += t->table[i]; + } + +#if UZLIB_CONF_DEBUG_LOG >= 2 + UZLIB_DUMP_ARRAY("codelen offsets:", offs, TINF_ARRAY_SIZE(offs)); +#endif + + /* create code->symbol translation table (symbols sorted by code) */ + for (i = 0; i < num; ++i) { + if (lengths[i]) { + t->trans[offs[lengths[i]]++] = i; + } + } +} + +/* ---------------------- * + * -- decode functions -- * + * ---------------------- */ + +unsigned char uzlib_get_byte(TINF_DATA *d) +{ + /* If end of source buffer is not reached, return next byte from source + buffer. */ + if (d->source < d->source_limit) { + return *d->source++; + } + + /* Otherwise if there's callback and we haven't seen EOF yet, try to + read next byte using it. (Note: the callback can also update ->source + and ->source_limit). */ + if (d->readSource && !d->eof) { + int val = d->readSource(d); + if (val >= 0) { + return (unsigned char) val; + } + } + + /* Otherwise, we hit EOF (either from ->readSource() or from exhaustion + of the buffer), and it will be "sticky", i.e. further calls to this + function will end up here too. */ + d->eof = true; + + return 0; +} + +uint32_t tinf_get_le_uint32(TINF_DATA *d) +{ + uint32_t val = 0; + int i; + for (i = 4; i--;) { + val = val >> 8 | ((uint32_t) uzlib_get_byte(d)) << 24; + } + return val; +} + +uint32_t tinf_get_be_uint32(TINF_DATA *d) +{ + uint32_t val = 0; + int i; + for (i = 4; i--;) { + val = val << 8 | uzlib_get_byte(d); + } + return val; +} + +/* get one bit from source stream */ +static int tinf_getbit(TINF_DATA *d) +{ + unsigned int bit; + + /* check if tag is empty */ + if (!d->bitcount--) { + /* load next tag */ + d->tag = uzlib_get_byte(d); + d->bitcount = 7; + } + + /* shift bit out of tag */ + bit = d->tag & 0x01; + d->tag >>= 1; + + return bit; +} + +/* read a num bit value from a stream and add base */ +static unsigned int tinf_read_bits(TINF_DATA *d, int num, int base) +{ + unsigned int val = 0; + + /* read num bits */ + if (num) { + unsigned int limit = 1 << (num); + unsigned int mask; + + for (mask = 1; mask < limit; mask *= 2) { + if (tinf_getbit(d)) { + val += mask; + } + } + } + + return val + base; +} + +/* given a data stream and a tree, decode a symbol */ +static int tinf_decode_symbol(TINF_DATA *d, TINF_TREE *t) +{ + int sum = 0, cur = 0, len = 0; + + /* get more bits while code value is above sum */ + do { + cur = 2 * cur + tinf_getbit(d); + + if (++len == TINF_ARRAY_SIZE(t->table)) { + return TINF_DATA_ERROR; + } + + sum += t->table[len]; + cur -= t->table[len]; + + } while (cur >= 0); + + sum += cur; +#if UZLIB_CONF_PARANOID_CHECKS + if (sum < 0 || sum >= TINF_ARRAY_SIZE(t->trans)) { + return TINF_DATA_ERROR; + } +#endif + + return t->trans[sum]; +} + +/* given a data stream, decode dynamic trees from it */ +static int tinf_decode_trees(TINF_DATA *d, TINF_TREE *lt, TINF_TREE *dt) +{ + /* code lengths for 288 literal/len symbols and 32 dist symbols */ + unsigned char lengths[288 + 32]; + unsigned int hlit, hdist, hclen, hlimit; + unsigned int i, num, length; + + /* get 5 bits HLIT (257-286) */ + hlit = tinf_read_bits(d, 5, 257); + + /* get 5 bits HDIST (1-32) */ + hdist = tinf_read_bits(d, 5, 1); + + /* get 4 bits HCLEN (4-19) */ + hclen = tinf_read_bits(d, 4, 4); + + for (i = 0; i < 19; ++i) { + lengths[i] = 0; + } + + /* read code lengths for code length alphabet */ + for (i = 0; i < hclen; ++i) { + /* get 3 bits code length (0-7) */ + unsigned int clen = tinf_read_bits(d, 3, 0); + + lengths[clcidx[i]] = clen; + } + + /* build code length tree, temporarily use length tree */ + tinf_build_tree(lt, lengths, 19); + + /* decode code lengths for the dynamic trees */ + hlimit = hlit + hdist; + for (num = 0; num < hlimit;) { + int sym = tinf_decode_symbol(d, lt); + unsigned char fill_value = 0; + int lbits, lbase = 3; + + /* error decoding */ + if (sym < 0) { + return sym; + } + + switch (sym) { + case 16: + /* copy previous code length 3-6 times (read 2 bits) */ + if (num == 0) { + return TINF_DATA_ERROR; + } + fill_value = lengths[num - 1]; + lbits = 2; + break; + case 17: + /* repeat code length 0 for 3-10 times (read 3 bits) */ + lbits = 3; + break; + case 18: + /* repeat code length 0 for 11-138 times (read 7 bits) */ + lbits = 7; + lbase = 11; + break; + default: + /* values 0-15 represent the actual code lengths */ + lengths[num++] = sym; + /* continue the for loop */ + continue; + } + + /* special code length 16-18 are handled here */ + length = tinf_read_bits(d, lbits, lbase); + if (num + length > hlimit) { + return TINF_DATA_ERROR; + } + for (; length; --length) { + lengths[num++] = fill_value; + } + } + +#if UZLIB_CONF_DEBUG_LOG >= 2 + printf("lit code lengths (%d):", hlit); + UZLIB_DUMP_ARRAY("", lengths, hlit); + printf("dist code lengths (%d):", hdist); + UZLIB_DUMP_ARRAY("", lengths + hlit, hdist); +#endif + +#if UZLIB_CONF_PARANOID_CHECKS + /* Check that there's "end of block" symbol */ + if (lengths[256] == 0) { + return TINF_DATA_ERROR; + } +#endif + + /* build dynamic trees */ + tinf_build_tree(lt, lengths, hlit); + tinf_build_tree(dt, lengths + hlit, hdist); + + return TINF_OK; +} + +/* ----------------------------- * + * -- block inflate functions -- * + * ----------------------------- */ + +/* given a stream and two trees, inflate next chunk of output (a byte or more) */ +static int tinf_inflate_block_data(TINF_DATA *d, TINF_TREE *lt, TINF_TREE *dt) +{ + if (d->curlen == 0) { + unsigned int offs; + int dist; + int sym = tinf_decode_symbol(d, lt); + // printf("huff sym: %02x\n", sym); + + if (d->eof) { + return TINF_DATA_ERROR; + } + + /* literal byte */ + if (sym < 256) { + TINF_PUT(d, sym); + return TINF_OK; + } + + /* end of block */ + if (sym == 256) { + return TINF_DONE; + } + + /* substring from sliding dictionary */ + sym -= 257; + if (sym >= 29) { + return TINF_DATA_ERROR; + } + + /* possibly get more bits from length code */ + d->curlen = tinf_read_bits(d, length_bits[sym], length_base[sym]); + + dist = tinf_decode_symbol(d, dt); + if (dist >= 30) { + return TINF_DATA_ERROR; + } + + /* possibly get more bits from distance code */ + offs = tinf_read_bits(d, dist_bits[dist], dist_base[dist]); + + /* calculate and validate actual LZ offset to use */ + if (d->dict_ring) { + if (offs > d->dict_size) { + return TINF_DICT_ERROR; + } + /* Note: unlike full-dest-in-memory case below, we don't + try to catch offset which points to not yet filled + part of the dictionary here. Doing so would require + keeping another variable to track "filled in" size + of the dictionary. Appearance of such an offset cannot + lead to accessing memory outside of the dictionary + buffer, and clients which don't want to leak unrelated + information, should explicitly initialize dictionary + buffer passed to uzlib. */ + + d->lzOff = d->dict_idx - offs; + if (d->lzOff < 0) { + d->lzOff += d->dict_size; + } + } + else { + /* catch trying to point before the start of dest buffer */ + if (offs > d->dest - d->destStart) { + return TINF_DATA_ERROR; + } + d->lzOff = -offs; + } + } + + /* copy next byte from dict substring */ + if (d->dict_ring) { + TINF_PUT(d, d->dict_ring[d->lzOff]); + if ((unsigned) ++d->lzOff == d->dict_size) { + d->lzOff = 0; + } + } + else { +#if UZLIB_CONF_USE_MEMCPY + /* copy as much as possible, in one memcpy() call */ + unsigned int to_copy = d->curlen, dest_len = d->dest_limit - d->dest; + if (to_copy > dest_len) { + to_copy = dest_len; + } + memcpy(d->dest, d->dest + d->lzOff, to_copy); + d->dest += to_copy; + d->curlen -= to_copy; + return TINF_OK; +#else + d->dest[0] = d->dest[d->lzOff]; + d->dest++; +#endif + } + d->curlen--; + return TINF_OK; +} + +/* inflate next byte from uncompressed block of data */ +static int tinf_inflate_uncompressed_block(TINF_DATA *d) +{ + if (d->curlen == 0) { + unsigned int length, invlength; + + /* get length */ + length = uzlib_get_byte(d); + length += 256 * uzlib_get_byte(d); + /* get one's complement of length */ + invlength = uzlib_get_byte(d); + invlength += 256 * uzlib_get_byte(d); + /* check length */ + if (length != (~invlength & 0x0000ffff)) { + return TINF_DATA_ERROR; + } + + /* increment length to properly return TINF_DONE below, without + producing data at the same time */ + d->curlen = length + 1; + + /* make sure we start next block on a byte boundary */ + d->bitcount = 0; + } + + if (--d->curlen == 0) { + return TINF_DONE; + } + + unsigned char c = uzlib_get_byte(d); + TINF_PUT(d, c); + return TINF_OK; +} + +/* ---------------------- * + * -- public functions -- * + * ---------------------- */ + +/* initialize global (static) data */ +void uzlib_init(void) +{ +#ifdef RUNTIME_BITS_TABLES + /* build extra bits and base tables */ + tinf_build_bits_base(length_bits, length_base, 4, 3); + tinf_build_bits_base(dist_bits, dist_base, 2, 1); + + /* fix a special case */ + length_bits[28] = 0; + length_base[28] = 258; +#endif +} + +/* initialize decompression structure */ +void uzlib_uncompress_init(TINF_DATA *d, void *dict, unsigned int dictLen) +{ + d->eof = 0; + d->bitcount = 0; + d->bfinal = 0; + d->btype = -1; + d->dict_size = dictLen; + d->dict_ring = dict; + d->dict_idx = 0; + d->curlen = 0; +} + +/* inflate next output bytes from compressed stream */ +int uzlib_uncompress(TINF_DATA *d) +{ + do { + int res; + + /* start a new block */ + if (d->btype == -1) { + int old_btype; + next_blk: + old_btype = d->btype; + /* read final block flag */ + d->bfinal = tinf_getbit(d); + /* read block type (2 bits) */ + d->btype = tinf_read_bits(d, 2, 0); + +#if UZLIB_CONF_DEBUG_LOG >= 1 + printf("Started new block: type=%d final=%d\n", d->btype, d->bfinal); +#endif + + if (d->btype == 1 && old_btype != 1) { + /* build fixed huffman trees */ + tinf_build_fixed_trees(&d->ltree, &d->dtree); + } + else if (d->btype == 2) { + /* decode trees from stream */ + res = tinf_decode_trees(d, &d->ltree, &d->dtree); + if (res != TINF_OK) { + return res; + } + } + } + + /* process current block */ + switch (d->btype) { + case 0: + /* decompress uncompressed block */ + res = tinf_inflate_uncompressed_block(d); + break; + case 1: + case 2: + /* decompress block with fixed/dynamic huffman trees */ + /* trees were decoded previously, so it's the same routine for both */ + res = tinf_inflate_block_data(d, &d->ltree, &d->dtree); + break; + default: + return TINF_DATA_ERROR; + } + + if (res == TINF_DONE && !d->bfinal) { + /* the block has ended (without producing more data), but we + can't return without data, so start processing next block */ + goto next_blk; + } + + if (res != TINF_OK) { + return res; + } + + } while (d->dest < d->dest_limit); + + return TINF_OK; +} + +/* inflate next output bytes from compressed stream, updating + checksum, and at the end of stream, verify it */ +int uzlib_uncompress_chksum(TINF_DATA *d) +{ + int res; + unsigned char *data = d->dest; + + res = uzlib_uncompress(d); + + if (res < 0) { + return res; + } + + switch (d->checksum_type) { + case TINF_CHKSUM_ADLER: + d->checksum = uzlib_adler32(data, d->dest - data, d->checksum); + break; + + case TINF_CHKSUM_CRC: + d->checksum = uzlib_crc32(data, d->dest - data, d->checksum); + break; + } + + if (res == TINF_DONE) { + unsigned int val; + + switch (d->checksum_type) { + case TINF_CHKSUM_ADLER: + val = tinf_get_be_uint32(d); + if (d->checksum != val) { + return TINF_CHKSUM_ERROR; + } + break; + + case TINF_CHKSUM_CRC: + val = tinf_get_le_uint32(d); + if (~d->checksum != val) { + return TINF_CHKSUM_ERROR; + } + // Uncompressed size. TODO: Check + val = tinf_get_le_uint32(d); + break; + } + } + + return res; +} diff --git a/tests/screenshots/src/uzlib/tinfzlib.c b/tests/screenshots/src/uzlib/tinfzlib.c new file mode 100644 index 000000000..b6efff3fa --- /dev/null +++ b/tests/screenshots/src/uzlib/tinfzlib.c @@ -0,0 +1,74 @@ +/* + * uzlib - tiny deflate/inflate library (deflate, gzip, zlib) + * + * Copyright (c) 2003 by Joergen Ibsen / Jibz + * All Rights Reserved + * + * http://www.ibsensoftware.com/ + * + * Copyright (c) 2014-2018 by Paul Sokolovsky + * + * This software is provided 'as-is', without any express + * or implied warranty. In no event will the authors be + * held liable for any damages arising from the use of + * this software. + * + * Permission is granted to anyone to use this software + * for any purpose, including commercial applications, + * and to alter it and redistribute it freely, subject to + * the following restrictions: + * + * 1. The origin of this software must not be + * misrepresented; you must not claim that you + * wrote the original software. If you use this + * software in a product, an acknowledgment in + * the product documentation would be appreciated + * but is not required. + * + * 2. Altered source versions must be plainly marked + * as such, and must not be misrepresented as + * being the original software. + * + * 3. This notice may not be removed or altered from + * any source distribution. + */ + +#include "tinf.h" + +int uzlib_zlib_parse_header(TINF_DATA *d) +{ + unsigned char cmf, flg; + + /* -- get header bytes -- */ + + cmf = uzlib_get_byte(d); + flg = uzlib_get_byte(d); + + /* -- check format -- */ + + /* check checksum */ + if ((256 * cmf + flg) % 31) { + return TINF_DATA_ERROR; + } + + /* check method is deflate */ + if ((cmf & 0x0f) != 8) { + return TINF_DATA_ERROR; + } + + /* check window size is valid */ + if ((cmf >> 4) > 7) { + return TINF_DATA_ERROR; + } + + /* check there is no preset dictionary */ + if (flg & 0x20) { + return TINF_DATA_ERROR; + } + + /* initialize for adler32 checksum */ + d->checksum_type = TINF_CHKSUM_ADLER; + d->checksum = 1; + + return cmf >> 4; +} diff --git a/tests/screenshots/src/uzlib/uzlib.h b/tests/screenshots/src/uzlib/uzlib.h new file mode 100644 index 000000000..afbdae141 --- /dev/null +++ b/tests/screenshots/src/uzlib/uzlib.h @@ -0,0 +1,177 @@ +/* + * uzlib - tiny deflate/inflate library (deflate, gzip, zlib) + * + * Copyright (c) 2003 by Joergen Ibsen / Jibz + * All Rights Reserved + * http://www.ibsensoftware.com/ + * + * Copyright (c) 2014-2018 by Paul Sokolovsky + * + * This software is provided 'as-is', without any express + * or implied warranty. In no event will the authors be + * held liable for any damages arising from the use of + * this software. + * + * Permission is granted to anyone to use this software + * for any purpose, including commercial applications, + * and to alter it and redistribute it freely, subject to + * the following restrictions: + * + * 1. The origin of this software must not be + * misrepresented; you must not claim that you + * wrote the original software. If you use this + * software in a product, an acknowledgment in + * the product documentation would be appreciated + * but is not required. + * + * 2. Altered source versions must be plainly marked + * as such, and must not be misrepresented as + * being the original software. + * + * 3. This notice may not be removed or altered from + * any source distribution. + */ + +#ifndef UZLIB_H_INCLUDED +#define UZLIB_H_INCLUDED + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#include "uzlib_conf.h" +#if UZLIB_CONF_DEBUG_LOG +#include +#endif + +/* calling convention */ +#ifndef TINFCC +#ifdef __WATCOMC__ +#define TINFCC __cdecl +#else +#define TINFCC +#endif +#endif + +/* ok status, more data produced */ +#define TINF_OK 0 +/* end of compressed stream reached */ +#define TINF_DONE 1 +#define TINF_DATA_ERROR (-3) +#define TINF_CHKSUM_ERROR (-4) +#define TINF_DICT_ERROR (-5) + +/* checksum types */ +#define TINF_CHKSUM_NONE 0 +#define TINF_CHKSUM_ADLER 1 +#define TINF_CHKSUM_CRC 2 + +/* helper macros */ +#define TINF_ARRAY_SIZE(arr) (sizeof(arr) / sizeof(*(arr))) + +/* data structures */ + +typedef struct { + unsigned short table[16]; /* table of code length counts */ + unsigned short trans[288]; /* code -> symbol translation table */ +} TINF_TREE; + +struct uzlib_uncomp { + /* Pointer to the next byte in the input buffer */ + const unsigned char *source; + /* Pointer to the next byte past the input buffer (source_limit = source + len) */ + const unsigned char *source_limit; + /* If source_limit == NULL, or source >= source_limit, this function + will be used to read next byte from source stream. The function may + also return -1 in case of EOF (or irrecoverable error). Note that + besides returning the next byte, it may also update source and + source_limit fields, thus allowing for buffered operation. */ + int (*source_read_cb)(struct uzlib_uncomp *uncomp); + + unsigned int tag; + unsigned int bitcount; + + /* Destination (output) buffer start */ + unsigned char *dest_start; + /* Current pointer in dest buffer */ + unsigned char *dest; + /* Pointer past the end of the dest buffer, similar to source_limit */ + unsigned char *dest_limit; + + /* Accumulating checksum */ + unsigned int checksum; + char checksum_type; + bool eof; + + int btype; + int bfinal; + unsigned int curlen; + int lzOff; + unsigned char *dict_ring; + unsigned int dict_size; + unsigned int dict_idx; + + TINF_TREE ltree; /* dynamic length/symbol tree */ + TINF_TREE dtree; /* dynamic distance tree */ +}; + +#include "tinf_compat.h" + +#define TINF_PUT(d, c) \ + { \ + *d->dest++ = c; \ + if (d->dict_ring) { \ + d->dict_ring[d->dict_idx++] = c; \ + if (d->dict_idx == d->dict_size) \ + d->dict_idx = 0; \ + } \ + } + +unsigned char TINFCC uzlib_get_byte(TINF_DATA *d); + +/* Decompression API */ + +void TINFCC uzlib_init(void); +void TINFCC uzlib_uncompress_init(TINF_DATA *d, void *dict, unsigned int dictLen); +int TINFCC uzlib_uncompress(TINF_DATA *d); +int TINFCC uzlib_uncompress_chksum(TINF_DATA *d); + +int TINFCC uzlib_zlib_parse_header(TINF_DATA *d); +int TINFCC uzlib_gzip_parse_header(TINF_DATA *d); + +/* Compression API */ + +typedef const uint8_t *uzlib_hash_entry_t; + +struct uzlib_comp { + unsigned char *outbuf; + int outlen, outsize; + unsigned long outbits; + int noutbits; + int comp_disabled; + + uzlib_hash_entry_t *hash_table; + unsigned int hash_bits; + unsigned int dict_size; +}; + +void TINFCC uzlib_compress(struct uzlib_comp *c, const uint8_t *src, unsigned slen); + +#include "defl_static.h" + +/* Checksum API */ + +/* prev_sum is previous value for incremental computation, 1 initially */ +uint32_t TINFCC uzlib_adler32(const void *data, unsigned int length, uint32_t prev_sum); +/* crc is previous value for incremental computation, 0xffffffff initially */ +uint32_t TINFCC uzlib_crc32(const void *data, unsigned int length, uint32_t crc); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* UZLIB_H_INCLUDED */ diff --git a/tests/screenshots/src/uzlib/uzlib_conf.h b/tests/screenshots/src/uzlib/uzlib_conf.h new file mode 100644 index 000000000..fd67dec8a --- /dev/null +++ b/tests/screenshots/src/uzlib/uzlib_conf.h @@ -0,0 +1,32 @@ +/* + * uzlib - tiny deflate/inflate library (deflate, gzip, zlib) + * + * Copyright (c) 2014-2018 by Paul Sokolovsky + */ + +#ifndef UZLIB_CONF_H_INCLUDED +#define UZLIB_CONF_H_INCLUDED + +#ifndef UZLIB_CONF_DEBUG_LOG +/* Debug logging level 0, 1, 2, etc. */ +#define UZLIB_CONF_DEBUG_LOG 0 +#endif + +#ifndef UZLIB_CONF_PARANOID_CHECKS +/* Perform extra checks on the input stream, even if they aren't proven + to be strictly required (== lack of them wasn't proven to lead to + crashes). */ +#define UZLIB_CONF_PARANOID_CHECKS 0 +#endif + +#ifndef UZLIB_CONF_USE_MEMCPY +/* Use memcpy() for copying data out of LZ window or uncompressed blocks, + instead of doing this byte by byte. For well-compressed data, this + may noticeably increase decompression speed. But for less compressed, + it can actually deteriorate it (due to the fact that many memcpy() + implementations are optimized for large blocks of data, and have + too much overhead for short strings of just a few bytes). */ +#define UZLIB_CONF_USE_MEMCPY 0 +#endif + +#endif /* UZLIB_CONF_H_INCLUDED */ diff --git a/tests/screenshots/stax.properties b/tests/screenshots/stax.properties new file mode 100644 index 000000000..cc256df8e --- /dev/null +++ b/tests/screenshots/stax.properties @@ -0,0 +1,13 @@ +{ + "devicename": "STAX-9DAB", + "bootloader_version": "2.2", + "seph_version": "1.95", + "seph_serial": "2100200782107028", + "os_version": "2.0.1", + + "autoboot": true, + + "piezosound": 0, + + "features": 4, +}