From d34d8ea66a7918ab0fcf5a28cec14730d310941e Mon Sep 17 00:00:00 2001 From: Xavier Chapron Date: Mon, 22 Apr 2024 17:30:09 +0200 Subject: [PATCH] nbgl: Implement NBGL for LNS --- Makefile.rules | 4 +- Makefile.standard_app | 13 + include/ux.h | 8 +- lib_nbgl/include/nbgl_buttons.h | 70 ++ lib_nbgl/include/nbgl_content.h | 309 +++++++++ lib_nbgl/include/nbgl_debug.h | 95 +++ lib_nbgl/include/nbgl_fonts.h | 55 ++ lib_nbgl/include/nbgl_lns.h | 145 ++++ lib_nbgl/include/nbgl_use_case.h | 177 +++++ lib_nbgl/src/nbgl_buttons.c | 144 ++++ lib_nbgl/src/nbgl_fonts_lns.c | 212 ++++++ lib_nbgl/src/nbgl_lns.c | 542 +++++++++++++++ lib_nbgl/src/nbgl_use_case_nanos.c | 1014 ++++++++++++++++++++++++++++ lib_standard_app/io.c | 2 +- lib_ux/include/ux_bagl.h | 4 +- lib_ux_nbgl/ux.c | 124 ++++ lib_ux_nbgl/ux.h | 101 +++ lib_ux_sync/include/ux_sync.h | 6 - lib_ux_sync/src/ux_sync.c | 48 -- src/app_metadata.c | 7 +- src/ledger_assert.c | 14 +- src/os_io_seproxyhal.c | 17 +- 22 files changed, 3035 insertions(+), 76 deletions(-) create mode 100644 lib_nbgl/include/nbgl_buttons.h create mode 100644 lib_nbgl/include/nbgl_content.h create mode 100644 lib_nbgl/include/nbgl_debug.h create mode 100644 lib_nbgl/include/nbgl_fonts.h create mode 100644 lib_nbgl/include/nbgl_lns.h create mode 100644 lib_nbgl/include/nbgl_use_case.h create mode 100644 lib_nbgl/src/nbgl_buttons.c create mode 100644 lib_nbgl/src/nbgl_fonts_lns.c create mode 100644 lib_nbgl/src/nbgl_lns.c create mode 100644 lib_nbgl/src/nbgl_use_case_nanos.c create mode 100644 lib_ux_nbgl/ux.c create mode 100644 lib_ux_nbgl/ux.h diff --git a/Makefile.rules b/Makefile.rules index f69f82a7..c8cb6329 100644 --- a/Makefile.rules +++ b/Makefile.rules @@ -16,8 +16,10 @@ #******************************************************************************* # temporary redef, to ensure wider compliance of the SDK with pre-1.6 apps -ifeq ($(TARGET_NAME),$(filter $(TARGET_NAME),TARGET_NANOS TARGET_NANOX TARGET_NANOS2)) +ifneq ($(USE_NBGL),1) SDK_SOURCE_PATH += lib_bagl lib_ux +else + SDK_SOURCE_PATH += lib_nbgl lib_ux_nbgl lib_ux_sync endif define uniq = diff --git a/Makefile.standard_app b/Makefile.standard_app index 69b4aa2e..8ad37a6c 100644 --- a/Makefile.standard_app +++ b/Makefile.standard_app @@ -111,10 +111,12 @@ ifneq ($(DISABLE_STANDARD_WEBUSB), 1) endif ifneq ($(DISABLE_STANDARD_BAGL_UX_FLOW), 1) +ifneq ($(ENABLE_NBGL), 1) ifneq ($(TARGET_NAME), TARGET_STAX) DEFINES += HAVE_UX_FLOW endif endif +endif ifneq ($(DISABLE_STANDARD_SEPROXYHAL), 1) DEFINES += OS_IO_SEPROXYHAL @@ -125,14 +127,25 @@ SDK_SOURCE_PATH += lib_standard_app endif ifneq ($(DISABLE_STANDARD_APP_SYNC_RAPDU), 1) +# On LNS only activate it by default if using NBGL. +# This impact stack usage and shouldn't be activated on all apps silently ifneq ($(TARGET_NAME),TARGET_NANOS) DEFINES += STANDARD_APP_SYNC_RAPDU +else +ifeq ($(ENABLE_NBGL), 1) +DEFINES += STANDARD_APP_SYNC_RAPDU +endif endif endif ##################################################################### # NBGL # ##################################################################### +ifeq ($(ENABLE_NBGL), 1) +USE_NBGL = 1 +DEFINES += HAVE_NBGL NBGL_USE_CASE +endif + ifeq ($(ENABLE_NBGL_QRCODE), 1) ifeq ($(TARGET_NAME), TARGET_STAX) DEFINES += NBGL_QRCODE diff --git a/include/ux.h b/include/ux.h index c927ab5a..eadcb9d1 100644 --- a/include/ux.h +++ b/include/ux.h @@ -17,10 +17,8 @@ #pragma once -#ifdef HAVE_BAGL -#include "ux_bagl.h" -#endif - -#ifdef HAVE_NBGL +#if defined(HAVE_NBGL) #include "ux_nbgl.h" +#elif defined(HAVE_BAGL) +#include "ux_bagl.h" #endif diff --git a/lib_nbgl/include/nbgl_buttons.h b/lib_nbgl/include/nbgl_buttons.h new file mode 100644 index 00000000..77e6980c --- /dev/null +++ b/lib_nbgl/include/nbgl_buttons.h @@ -0,0 +1,70 @@ +/** + * @file nbgl_buttons.h + * Buttons management of NBGL + * + */ + +#ifndef NBGL_BUTTONS_H +#define NBGL_BUTTONS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include + +/********************* + * DEFINES + *********************/ +///< Time after the beginning of continuous press on button(s) after which "continuous press" event +///< start to be sent (in 100ms) +#define CONTINOUS_PRESS_THRESHOLD 8 +///< Periodicity of "continuous press" events (in 100ms) +#define CONTINUOUS_PRESS_PERIOD 3 + +/********************** + * TYPEDEFS + **********************/ + +/** + * @brief The different pressed buttons + * + */ +#define LEFT_BUTTON 0x01 ///< Left button event +#define RIGHT_BUTTON 0x02 ///< Right button event +#define BOTH_BUTTONS 0x03 ///< Both buttons event +#define RELEASED_MASK 0x80 ///< released (see LSB bits to know what buttons are released) +#define CONTINUOUS_MASK \ + 0x40 ///< if set, means that the button(s) is continuously pressed (this event is sent every + ///< 300ms after the first 800ms) + +typedef enum { + BUTTON_LEFT_PRESSED = 0, ///< Sent when Left button is released + BUTTON_RIGHT_PRESSED, ///< Send when Right button is released + BUTTON_LEFT_CONTINUOUS_PRESSED, ///< Send when Left button is continuouly pressed (sent every + ///< 300ms after the first 800ms) + BUTTON_RIGHT_CONTINUOUS_PRESSED, ///< Send when Left button is continuouly pressed (sent every + ///< 300ms after the first 800ms) + BUTTON_BOTH_PRESSED, ///< Sent when both buttons are released + BUTTON_BOTH_TOUCHED, ///< Sent when both buttons are touched + INVALID_BUTTON_EVENT +} nbgl_buttonEvent_t; + +/********************** + * GLOBAL PROTOTYPES + **********************/ +void nbgl_buttonsHandler(uint8_t buttonState); +void nbgl_buttonsReset(void); + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* NBGL_BUTTONS_H */ diff --git a/lib_nbgl/include/nbgl_content.h b/lib_nbgl/include/nbgl_content.h new file mode 100644 index 00000000..4420b0ae --- /dev/null +++ b/lib_nbgl/include/nbgl_content.h @@ -0,0 +1,309 @@ +/** + * @file nbgl_content.h + * @brief common content for Graphical Library + * + */ + +#ifndef NBGL_CONTENT_H +#define NBGL_CONTENT_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include +#include + +#include "bolos_target.h" + +#ifdef TARGET_NANOS +#include "nbgl_lns.h" +#else +#include "nbgl_types.h" +#include "nbgl_obj.h" +#ifdef HAVE_PIEZO_SOUND +#include "os_io_seproxyhal.h" +#endif +#endif + +/********************** + * TYPEDEFS + **********************/ + +/** + * @brief possible styles for Centered Info Area + * + */ +typedef enum { +#ifdef HAVE_SE_TOUCH + LARGE_CASE_INFO, ///< text in BLACK and large case (INTER 32px), subText in black in Inter24px + LARGE_CASE_BOLD_INFO, ///< text in BLACK and large case (INTER 32px), subText in black bold + ///< Inter24px, text3 in black Inter24px + LARGE_CASE_GRAY_INFO, ///< text in BLACK and large case (INTER 32px), subText in black + ///< Inter24px text3 in dark gray Inter24px + NORMAL_INFO, ///< Icon in black, a potential text in black bold 24px under it, a potential text + ///< in dark gray (24px) under it, a potential text in black (24px) under it + PLUGIN_INFO ///< A potential text in black 32px, a potential text in black (24px) under it, a + ///< small horizontal line under it, a potential icon under it, a potential text in + ///< black (24px) under it +#else // HAVE_SE_TOUCH + REGULAR_INFO = 0, ///< both texts regular (but '\\b' can switch to bold) + BOLD_TEXT1_INFO ///< bold is used for text1 (but '\\b' can switch to regular) +#endif // HAVE_SE_TOUCH +} nbgl_contentCenteredInfoStyle_t; + +/** + * @brief This structure contains info to build a centered (vertically and horizontally) area, with + * a possible Icon, a possible text under it, and a possible sub-text gray under it. + * + */ +typedef struct { + const char *text1; ///< first text (can be null) + const char *text2; ///< second text (can be null) +#ifdef HAVE_SE_TOUCH + const char *text3; ///< third text (can be null) +#endif // HAVE_SE_TOUCH + const nbgl_icon_details_t *icon; ///< a buffer containing the 1BPP icon + bool onTop; ///< if set to true, align only horizontally + nbgl_contentCenteredInfoStyle_t style; ///< style to apply to this info +#ifdef HAVE_SE_TOUCH + int16_t offsetY; ///< vertical shift to apply to this info (if >0, shift to bottom) +#endif // HAVE_SE_TOUCH +} nbgl_contentCenteredInfo_t; + +/** + * @brief This structure contains data to build a centered info + long press button content + */ +typedef struct { + const char *text; ///< centered text in large case + const nbgl_icon_details_t *icon; ///< a buffer containing the 1BPP icon + const char *longPressText; ///< text of the long press button + uint8_t longPressToken; ///< the token used as argument of the onActionCallback when button is + ///< long pressed +#ifdef HAVE_PIEZO_SOUND + tune_index_e + tuneId; ///< if not @ref NBGL_NO_TUNE, a tune will be played when button is touched +#endif +} nbgl_contentInfoLongPress_t; + +/** + * @brief This structure contains data to build a centered info + simple black button content + */ +typedef struct { + const char *text; ///< centered text in large case + const nbgl_icon_details_t *icon; ///< a buffer containing the 1BPP icon + const char *buttonText; ///< text of the long press button + uint8_t buttonToken; ///< the token used as argument of the onActionCallback when button is + ///< long pressed +#ifdef HAVE_PIEZO_SOUND + tune_index_e + tuneId; ///< if not @ref NBGL_NO_TUNE, a tune will be played when button is touched +#endif +} nbgl_contentInfoButton_t; + +/** + * @brief This structure contains a [tag,value] pair + */ +typedef struct { + const char *item; ///< string giving the tag name + const char *value; ///< string giving the value name +#ifdef SCREEN_SIZE_WALLET + const nbgl_icon_details_t *valueIcon; ///< a buffer containing the 32px 1BPP icon for icon on + ///< right of value (can be NULL) + int8_t forcePageStart : 1; ///< if set to 1, the tag will be displayed at the top of a new + ///< review page + int8_t centeredInfo : 1; ///< if set to 1, the tag will be displayed as a centered info +#endif +} nbgl_contentTagValue_t; + +/** + * @brief prototype of tag/value pair retrieval callback + * @param pairIndex index of the tag/value pair to retrieve (from 0 (to nbPairs-1)) + * @return a pointer on a static tag/value pair + */ +typedef nbgl_contentTagValue_t *(*nbgl_contentTagValueCallback_t)(uint8_t pairIndex); + +/** + * @brief This structure contains a list of [tag,value] pairs + */ +typedef struct { + const nbgl_contentTagValue_t + *pairs; ///< array of [tag,value] pairs (nbPairs items). If NULL, callback is used instead + nbgl_contentTagValueCallback_t callback; ///< function to call to retrieve a given pair + uint8_t nbPairs; ///< number of pairs in pairs array (or max number of pairs to retrieve with + ///< callback) + uint8_t startIndex; ///< index of the first pair to get with callback + uint8_t nbMaxLinesForValue; ///< if > 0, set the max number of lines for value field. And the + ///< last line is ended with "..." instead of the 3 last chars + uint8_t token; ///< the token that will be used as argument of the callback if icon in any + ///< tag/value pair is touched (index is the index of the pair in pairs[]) + bool smallCaseForValue; ///< if set to true, a 24px font is used for value text, otherwise a + ///< 32px font is used + bool wrapping; ///< if set to true, value text will be wrapped on ' ' to avoid cutting words +} nbgl_contentTagValueList_t; + +/** + * @brief This structure contains a [item,value] pair and info about "details" button + */ +typedef struct { + nbgl_contentTagValueList_t tagValueList; ///< list of tag/value pairs + const nbgl_icon_details_t *detailsButtonIcon; ///< icon to use in details button + const char *detailsButtonText; ///< this text is used for "details" button + uint8_t detailsButtonToken; ///< the token used as argument of the actionCallback when the + ///< "details" button is touched +#ifdef HAVE_PIEZO_SOUND + tune_index_e + tuneId; ///< if not @ref NBGL_NO_TUNE, a tune will be played when details button is touched +#endif +} nbgl_contentTagValueDetails_t; + +/** + * @brief This structure contains [item,value] pair(s) and info about a potential "details" button, + * but also a black button + footer to confirm/cancel + */ +typedef struct { + nbgl_contentTagValueList_t tagValueList; ///< list of tag/value pairs + const nbgl_icon_details_t *detailsButtonIcon; ///< icon to use in details button + const char *detailsButtonText; ///< this text is used for "details" button (if NULL, no button) + uint8_t detailsButtonToken; ///< the token used as argument of the actionCallback when the + ///< "details" button is touched +#ifdef HAVE_PIEZO_SOUND + tune_index_e + tuneId; ///< if not @ref NBGL_NO_TUNE, a tune will be played when details button is touched +#endif + const char + *confirmationText; ///< text of the confirmation button, if NULL "It matches" is used + const char + *cancelText; ///< the text used for cancel action, if NULL "It doesn't matches" is used + uint8_t confirmationToken; ///< the token used as argument of the onActionCallback + uint8_t cancelToken; ///< the token used as argument of the onActionCallback when the cancel + ///< button is pressed +} nbgl_contentTagValueConfirm_t; + +/** + * @brief This structure contains info to build a switch (on the right) with a description (on the + * left), with a potential sub-description (in gray) + * + */ +typedef struct { + const char *text; ///< main text for the switch + const char + *subText; ///< description under main text (NULL terminated, single line, may be null) + nbgl_state_t initState; ///< initial state of the switch + uint8_t token; ///< the token that will be used as argument of the callback +#ifdef HAVE_PIEZO_SOUND + tune_index_e tuneId; ///< if not @ref NBGL_NO_TUNE, a tune will be played +#endif // HAVE_PIEZO_SOUND +} nbgl_contentSwitch_t; + +/** + * @brief This structure contains data to build a @ref SWITCHES_LIST content + */ +typedef struct nbgl_pageSwitchesList_s { + const nbgl_contentSwitch_t *switches; ///< array of switches (nbSwitches items) + uint8_t nbSwitches; ///< number of elements in switches and tokens array +} nbgl_contentSwitchesList_t; + +/** + * @brief This structure contains data to build a @ref INFOS_LIST content + */ +typedef struct { + const char *const *infoTypes; ///< array of types of infos (in black/bold) + const char *const *infoContents; ///< array of contents of infos (in black) + uint8_t nbInfos; ///< number of elements in infoTypes and infoContents array +} nbgl_contentInfoList_t; + +/** + * @brief This structure contains a list of names to build a list of radio + * buttons (on the right part of screen), with for each a description (names array) + * The chosen item index is provided is the "index" argument of the callback + */ +typedef struct { + union { + const char *const *names; ///< array of strings giving the choices (nbChoices) +#if defined(HAVE_LANGUAGE_PACK) + UX_LOC_STRINGS_INDEX *nameIds; ///< array of string Ids giving the choices (nbChoices) +#endif // HAVE_LANGUAGE_PACK + }; + bool localized; ///< if set to true, use nameIds and not names + uint8_t nbChoices; ///< number of choices + uint8_t initChoice; ///< index of the current choice + uint8_t token; ///< the token that will be used as argument of the callback +#ifdef HAVE_PIEZO_SOUND + tune_index_e + tuneId; ///< if not @ref NBGL_NO_TUNE, a tune will be played when selecting a radio button) +#endif // HAVE_PIEZO_SOUND +} nbgl_contentRadioChoice_t; + +/** + * @brief This structure contains data to build a @ref BARS_LIST content + */ +typedef struct { + const char *const *barTexts; ///< array of texts for each bar (nbBars items, in black/bold) + const uint8_t *tokens; ///< array of tokens, one for each bar (nbBars items) + uint8_t nbBars; ///< number of elements in barTexts and tokens array +#ifdef HAVE_PIEZO_SOUND + tune_index_e tuneId; ///< if not @ref NBGL_NO_TUNE, a tune will be played when a bar is touched +#endif // HAVE_PIEZO_SOUND +} nbgl_contentBarsList_t; + +/** + * @brief The different types of predefined contents + * + */ +typedef enum { + CENTERED_INFO = 0, ///< a centered info + INFO_LONG_PRESS, ///< a centered info and a long press button + INFO_BUTTON, ///< a centered info and a simple black button + TAG_VALUE_LIST, ///< list of tag/value pairs + TAG_VALUE_DETAILS, ///< a tag/value pair and a small button to get details. + TAG_VALUE_CONFIRM, ///< tag/value pairs and a black button/footer to confirm/cancel. + SWITCHES_LIST, ///< list of switches with descriptions + INFOS_LIST, ///< list of infos with titles + CHOICES_LIST, ///< list of choices through radio buttons + BARS_LIST ///< list of touchable bars (with > on the right to go to sub-pages) +} nbgl_contentType_t; + +/** + * @brief Union of the different type of contents + */ +typedef union { + nbgl_contentCenteredInfo_t centeredInfo; ///< @ref CENTERED_INFO type + nbgl_contentInfoLongPress_t infoLongPress; ///< @ref INFO_LONG_PRESS type + nbgl_contentInfoButton_t infoButton; ///< @ref INFO_BUTTON type + nbgl_contentTagValueList_t tagValueList; ///< @ref TAG_VALUE_LIST type + nbgl_contentTagValueDetails_t tagValueDetails; ///< @ref TAG_VALUE_DETAILS type + nbgl_contentTagValueConfirm_t tagValueConfirm; ///< @ref TAG_VALUE_CONFIRM type + nbgl_contentSwitchesList_t switchesList; ///< @ref SWITCHES_LIST type + nbgl_contentInfoList_t infosList; ///< @ref INFOS_LIST type + nbgl_contentRadioChoice_t choicesList; ///< @ref CHOICES_LIST type + nbgl_contentBarsList_t barsList; ///< @ref BARS_LIST type +} nbgl_content_u; + +/** + * @brief prototype of function to be called when an action on a content object occurs + * @param token integer passed at content object initialization + * @param index when the object touched is a list of radio buttons, gives the index of the activated + * @param page index of the current page, can be used to restart the use_case directly at the right + * page button + */ +typedef void (*nbgl_contentActionCallback_t)(int token, uint8_t index, int page); + +/** + * @brief This structure contains data to build a content + */ +typedef struct { + nbgl_contentType_t type; ///< type of page content in the content union + nbgl_content_u content; + nbgl_contentActionCallback_t + contentActionCallback; ///< callback to be called when an action on an object occurs +} nbgl_content_t; + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* NBGL_CONTENT_H */ diff --git a/lib_nbgl/include/nbgl_debug.h b/lib_nbgl/include/nbgl_debug.h new file mode 100644 index 00000000..e0a69a88 --- /dev/null +++ b/lib_nbgl/include/nbgl_debug.h @@ -0,0 +1,95 @@ +/** + * @file nbgl_debug.h + * @brief debug traces management + * + */ + +#ifndef NBGL_DEBUG_H +#define NBGL_DEBUG_H + +#ifdef __cplusplus +extern "C" { +#endif + +// #define WITH_STDIO 1 + +/********************* + * INCLUDES + *********************/ +#ifdef WITH_STDIO +#include +#include +#endif + +/********************* + * DEFINES + *********************/ +enum { + LOW_LOGGER, + DRAW_LOGGER, + OBJ_LOGGER, + OBJ_POOL_LOGGER, + SCREEN_LOGGER, + LAYOUT_LOGGER, + PAGE_LOGGER, + TOUCH_LOGGER, + APP_LOGGER, + UX_LOGGER, + MISC_LOGGER, + STEP_LOGGER, + FLOW_LOGGER +}; +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ +extern unsigned long gLogger; + +/********************** + * MACROS + **********************/ +#ifdef WITH_STDIO +extern void mainExit(int exitCode); +#define LOG_DEBUG(__logger, ...) \ + { \ + if (gLogger & (1 << __logger)) \ + printf(__VA_ARGS__); \ + } +#define LOG_WARN(__logger, ...) printf(__VA_ARGS__) +#define LOG_FATAL(__logger, ...) \ + { \ + printf(__VA_ARGS__); \ + mainExit(-1); \ + } + +#else // WITH_STDIO +#ifdef NBGL_DEBUG +#include +#define LOG_DEBUG(__logger, ...) \ + do { \ + PRINTF(__VA_ARGS__); \ + } while (0) +#define LOG_WARN(__logger, ...) \ + do { \ + PRINTF(__VA_ARGS__); \ + } while (0) +#define LOG_FATAL(__logger, ...) \ + do { \ + PRINTF(__VA_ARGS__); \ + halt(); \ + } while (0) +#else +#define LOG_DEBUG(__logger, ...) +#define LOG_WARN(__logger, ...) +#define LOG_FATAL(__logger, ...) +#endif // NBGL_DEBUG +#endif // WITH_STDIO + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* NBGL_DEBUG_H */ diff --git a/lib_nbgl/include/nbgl_fonts.h b/lib_nbgl/include/nbgl_fonts.h new file mode 100644 index 00000000..b890a396 --- /dev/null +++ b/lib_nbgl/include/nbgl_fonts.h @@ -0,0 +1,55 @@ +/** + * @file nbgl_fonts.h + * Fonts types of the new BOLOS Graphical Library + * + */ + +#ifndef NBGL_FONTS_H +#define NBGL_FONTS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include +#include + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ +// return the number of pages to be displayed when current page to show is 0 +int nbgl_font_compute_paging(const char *text_to_split, + uint32_t line_to_display, + uint32_t width_limit_in_pixels, + bool bold, + const char **line_start, + uint8_t *line_len); + +static inline int nbgl_font_compute_nb_page(const char *text_to_split, + uint32_t width_limit_in_pixels, + bool bold) +{ + return nbgl_font_compute_paging(text_to_split, 0, width_limit_in_pixels, bold, NULL, NULL); +} + +/********************** + * MACROS + **********************/ +#define IS_UNICODE(__value) ((__value) > 0xF0) + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* NBGL_FONTS_H */ diff --git a/lib_nbgl/include/nbgl_lns.h b/lib_nbgl/include/nbgl_lns.h new file mode 100644 index 00000000..137adaee --- /dev/null +++ b/lib_nbgl/include/nbgl_lns.h @@ -0,0 +1,145 @@ +/** + * @file nbgl_lns.h + * @brief API to manage screens + * + */ + +#ifndef NBGL_LNS_H +#define NBGL_LNS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ + +// #include "nbgl_obj.h" +#include +#include +#include "bagl.h" +#include "nbgl_buttons.h" + +/********************** + * Replace nbgl_obj.h + **********************/ +/** + * @brief to represent a boolean state. + */ +typedef enum { + OFF_STATE, + ON_STATE +} nbgl_state_t; + +/********************** + * Replace nbgl_types.h + **********************/ +typedef bagl_icon_details_t nbgl_icon_details_t; + +/********************** + * Replace nbgl_step.h + **********************/ + +/** + * get the "position" of a step within a flow of several steps + * @param _step step index from which to get the position + * @param _nb_steps number of steps in the flow + */ +#define GET_POS_OF_STEP(_step, _nb_steps) \ + (_nb_steps < 2) \ + ? SINGLE_STEP \ + : ((_step == 0) ? FIRST_STEP \ + : ((_step == (_nb_steps - 1)) ? LAST_STEP : NEITHER_FIRST_NOR_LAST_STEP)) + +/** + * @brief possible position for a step in a flow + * + */ +enum { + SINGLE_STEP, ///< single step flow + FIRST_STEP, ///< first in a multiple steps flow + LAST_STEP, ///< last in a multiple steps flow + NEITHER_FIRST_NOR_LAST_STEP, ///< neither first nor last in a multiple steps flow +}; + +///< When the flow is navigated from first to last step +#define FORWARD_DIRECTION 0x00 +///< When the flow is navigated from last to first step +#define BACKWARD_DIRECTION 0x08 + +#define STEP_POSITION_MASK 0x07 + +/** + * @brief this type contains nbgl_layoutNavIndication_t in its LSBs + * and direction in its MSB (using @ref FORWARD_DIRECTION and @ref BACKWARD_DIRECTION) + * + */ +typedef uint8_t nbgl_stepPosition_t; + +/********************** + * Replace nbgl_screen.h + **********************/ +/** + * @brief prototype of function to be called when a timer on screen is fired + */ +typedef void (*nbgl_tickerCallback_t)(void); + +/** + * @brief struct to configure a screen layer + * + */ +typedef struct nbgl_screenTickerConfiguration_s { + nbgl_tickerCallback_t + tickerCallback; ///< callback called when ticker timer is fired. Set to NULL for no ticker + uint32_t tickerValue; ///< timer initial value, in ms (should be multiple of 100 ms). Set to 0 + ///< for no ticker + uint32_t tickerIntervale; ///< for periodic timers, the intervale in ms to rearm the timer + ///< (should be multiple of 100 ms). Set to 0 for one-shot timers +} nbgl_screenTickerConfiguration_t; + +/********************** + * TYPEDEFS + **********************/ + +/** + * @brief prototype of function to be called when buttons are touched on a screen + * @param event type of button event + */ +typedef void (*nbgl_lnsButtonCallback_t)(nbgl_buttonEvent_t event); + +typedef struct { + const char *text; + const char *subtext; + const nbgl_icon_details_t *icon; + nbgl_stepPosition_t pos; + uint8_t centered : 1; + uint8_t bold : 1; + uint8_t vertical_nav : 1; +} nbgl_lnsScreenContent_t; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +void nbgl_screenRedraw(void); +void nbgl_refresh(void); +void nbgl_objAllowDrawing(bool enable); +void nbgl_processUxDisplayedEvent(void); + +void nbgl_lns_buttonCallback(nbgl_buttonEvent_t buttonEvent); +void nbgl_screenHandler(uint32_t intervaleMs); + +void nbgl_screenDraw(nbgl_lnsScreenContent_t *content, + nbgl_lnsButtonCallback_t onActionCallback, + nbgl_screenTickerConfiguration_t *ticker); + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* NBGL_LNS_H */ diff --git a/lib_nbgl/include/nbgl_use_case.h b/lib_nbgl/include/nbgl_use_case.h new file mode 100644 index 00000000..3e3121ee --- /dev/null +++ b/lib_nbgl/include/nbgl_use_case.h @@ -0,0 +1,177 @@ +/** + * @file nbgl_use_case.h + * @brief API of the Advanced BOLOS Graphical Library, for typical application use-cases + * + */ + +#ifndef NBGL_USE_CASE_H +#define NBGL_USE_CASE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ + +#include "nbgl_content.h" +#include "nbgl_lns.h" + +/********************* + * DEFINES + *********************/ +/** + * @brief when using controls in page content (@ref nbgl_pageContent_t), this is the first token + * value usable for these controls + */ +#define FIRST_USER_TOKEN 20 + +/** + * @brief maximum number of simultaneously displayed pairs in review pages. + * Can be useful when using nbgl_useCaseStaticReview() with the + * callback mechanism to retrieve the item/value pairs. + */ +#define NB_MAX_DISPLAYED_PAIRS_IN_REVIEW 1 + +/** + * @brief Value to pass to nbgl_useCaseHomeAndSettings() initSettingPage parameter + * to initialize the use case on the Home page and not on a specific setting + * page. + */ +#define INIT_HOME_PAGE 0xff + +/********************** + * MACROS + **********************/ + +/********************** + * TYPEDEFS + **********************/ +/** + * @brief prototype of generic callback function + */ +typedef void (*nbgl_callback_t)(void); + +/** + * @brief prototype of choice callback function + * @param confirm if true, means that the confirmation button has been pressed + */ +typedef void (*nbgl_choiceCallback_t)(bool confirm); + +/** + * @brief prototype of content navigation callback function + * @param contentIndex content index (0->(nbContents-1)) that is needed by the lib + * @param content content to fill + */ +typedef void (*nbgl_contentCallback_t)(uint8_t contentIndex, nbgl_content_t *content); + +typedef struct { + bool callbackCallNeeded; ///< indicates whether contents should be retrieved using + ///< contentsList or contentGetterCallback + union { + const nbgl_content_t *contentsList; ///< array of nbgl_content_t (nbContents items). + nbgl_contentCallback_t + contentGetterCallback; ///< function to call to retrieve a given content + }; + uint8_t nbContents; ///< number of contents +} nbgl_genericContents_t; + +typedef struct { + const char *text; + const nbgl_icon_details_t *icon; + nbgl_callback_t callback; +} nbgl_homeAction_t; + +/** + * @brief The different types of operation to review + * + */ +typedef enum { + TYPE_TRANSACTION = 0, // For operations transferring a coin or taken from an account to another + TYPE_MESSAGE, // For operations signing a message that will not be broadcast on the blockchain + TYPE_OPERATION, // For other types of operation (generic type) +} nbgl_operationType_t; + +/** + * @brief The different types of review status + * + */ +typedef enum { + STATUS_TYPE_TRANSACTION_SIGNED = 0, + STATUS_TYPE_TRANSACTION_REJECTED, + STATUS_TYPE_MESSAGE_SIGNED, + STATUS_TYPE_MESSAGE_REJECTED, + STATUS_TYPE_OPERATION_SIGNED, + STATUS_TYPE_OPERATION_REJECTED, + STATUS_TYPE_ADDRESS_VERIFIED, + STATUS_TYPE_ADDRESS_REJECTED, +} nbgl_reviewStatusType_t; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +void nbgl_useCaseHomeAndSettings(const char *appName, + const nbgl_icon_details_t *appIcon, + const char *tagline, + const uint8_t initSettingPage, + const nbgl_genericContents_t *settingContents, + const nbgl_contentInfoList_t *infosList, + const nbgl_homeAction_t *action, + nbgl_callback_t quitCallback); + +void nbgl_useCaseReview(nbgl_operationType_t operationType, + const nbgl_contentTagValueList_t *tagValueList, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle, + const char *finishTitle, + nbgl_choiceCallback_t choiceCallback); + +void nbgl_useCaseReviewLight(nbgl_operationType_t operationType, + const nbgl_contentTagValueList_t *tagValueList, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle, + const char *finishTitle, + nbgl_choiceCallback_t choiceCallback); + +void nbgl_useCaseAddressReview(const char *address, + const nbgl_contentTagValueList_t *additionalTagValueList, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle, + nbgl_choiceCallback_t choiceCallback); + +void nbgl_useCaseStatus(const char *message, bool isSuccess, nbgl_callback_t quitCallback); + +void nbgl_useCaseReviewStatus(nbgl_reviewStatusType_t reviewStatusType, + nbgl_callback_t quitCallback); + +void nbgl_useCaseReviewStreamingStart(nbgl_operationType_t operationType, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle, + nbgl_choiceCallback_t choiceCallback); + +void nbgl_useCaseReviewStreamingContinue(const nbgl_contentTagValueList_t *tagValueList, + nbgl_choiceCallback_t choiceCallback); + +void nbgl_useCaseReviewStreamingFinish(const char *finishTitle, + nbgl_choiceCallback_t choiceCallback); + +void nbgl_useCaseSpinner(const char *text); + +void nbgl_useCaseChoice(const nbgl_icon_details_t *icon, + const char *message, + const char *subMessage, + const char *confirmText, + const char *rejectString, + nbgl_choiceCallback_t callback); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* NBGL_USE_CASE_H */ diff --git a/lib_nbgl/src/nbgl_buttons.c b/lib_nbgl/src/nbgl_buttons.c new file mode 100644 index 00000000..f8729572 --- /dev/null +++ b/lib_nbgl/src/nbgl_buttons.c @@ -0,0 +1,144 @@ +/** + * @file nbgl_buttons.c + * Implementation of buttons management in NBGL + */ + +#ifndef HAVE_SE_TOUCH +/********************* + * INCLUDES + *********************/ +#include +#include "nbgl_debug.h" +#include "nbgl_buttons.h" +#include "nbgl_lns.h" +#include "os_pic.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ +static uint8_t gButtonMask = 0; +static uint8_t gButtonSameMaskCounter = 0; + +/********************** + * VARIABLES + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static nbgl_buttonEvent_t maskToEvent(uint8_t mask) +{ + nbgl_buttonEvent_t event = INVALID_BUTTON_EVENT; + switch (mask) { + case RELEASED_MASK | LEFT_BUTTON | RIGHT_BUTTON: + event = BUTTON_BOTH_PRESSED; + break; + + case LEFT_BUTTON | RIGHT_BUTTON: + event = BUTTON_BOTH_TOUCHED; + break; + + case CONTINUOUS_MASK | LEFT_BUTTON: + event = BUTTON_LEFT_CONTINUOUS_PRESSED; + break; + + case RELEASED_MASK | LEFT_BUTTON: + event = BUTTON_LEFT_PRESSED; + break; + + case CONTINUOUS_MASK | RIGHT_BUTTON: + event = BUTTON_RIGHT_CONTINUOUS_PRESSED; + break; + + case RELEASED_MASK | RIGHT_BUTTON: + event = BUTTON_RIGHT_PRESSED; + break; + } + + return event; +} +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * @brief Function to be called periodically to check touchscreen state + * and coordinates + * @param buttonState state of both buttons (only 2 LSB are used) + */ +void nbgl_buttonsHandler(uint8_t buttonState) +{ + uint8_t button_mask; + uint8_t button_same_mask_counter; + + // enable speeded up long push (continuous) + if (buttonState == gButtonMask) { + if (buttonState == 0) { + // nothing to be done when both buttons released twice in a row + return; + } + // each 100ms ~ + if (gButtonSameMaskCounter < 0xff) { + gButtonSameMaskCounter++; + } + } + + // append the button mask + button_mask = gButtonMask | buttonState; + + // pre reset variable due to os_sched_exit + button_same_mask_counter = gButtonSameMaskCounter; + + if (buttonState == 0) { + // reset next state when both buttons are released + gButtonMask = 0; + gButtonSameMaskCounter = 0; + + // notify button released event + button_mask |= RELEASED_MASK; + } + else { + gButtonMask = button_mask; + } + + // reset counter when button mask changes + if (buttonState != gButtonMask) { + gButtonSameMaskCounter = 0; + } + + // if the same button(s) is pressed more than 800 ms + if (button_same_mask_counter >= CONTINOUS_PRESS_THRESHOLD) { + // fast bit when pressing and timing is right (tag the event every 300ms) + if ((button_same_mask_counter % CONTINUOUS_PRESS_PERIOD) == 0) { + button_mask |= CONTINUOUS_MASK; + } + + // discard the release event after a fastskip has been detected, to avoid strange at release + // behavior and also to enable user to cancel an operation by starting triggering the fast + // skip + button_mask &= ~RELEASED_MASK; + } + + nbgl_buttonEvent_t event = maskToEvent(button_mask); + nbgl_lns_buttonCallback(event); +} + +void nbgl_buttonsReset(void) +{ + // no button push so far + gButtonMask = 0; + gButtonSameMaskCounter = 0; +} +#endif // HAVE_SE_TOUCH diff --git a/lib_nbgl/src/nbgl_fonts_lns.c b/lib_nbgl/src/nbgl_fonts_lns.c new file mode 100644 index 00000000..1e61a79b --- /dev/null +++ b/lib_nbgl/src/nbgl_fonts_lns.c @@ -0,0 +1,212 @@ + +/** + * @file nbgl_fonts.c + * Implementation of fonts array + */ +#include +#include + +#include "os.h" + +// Heavily based on lib_ux/src/ux_layout_paging_compute.c + +// We implement a light mechanism in order to be able to retrieve the width of +// nano S characters, in the two possible fonts: +// - BAGL_FONT_OPEN_SANS_EXTRABOLD_11px, +// - BAGL_FONT_OPEN_SANS_REGULAR_11px. +#define NANOS_FIRST_CHAR 0x20 +#define NANOS_LAST_CHAR 0x7F + +// OPEN_SANS_REGULAR_11PX << 4 | OPEN_SANS_EXTRABOLD_11PX +const uint8_t nanos_characters_width[NANOS_LAST_CHAR - NANOS_FIRST_CHAR + 1] = { + 3 << 4 | 3, /* code 0020 */ + 3 << 4 | 3, /* code 0021 */ + 4 << 4 | 6, /* code 0022 */ + 7 << 4 | 7, /* code 0023 */ + 6 << 4 | 6, /* code 0024 */ + 9 << 4 | 10, /* code 0025 */ + 8 << 4 | 9, /* code 0026 */ + 2 << 4 | 3, /* code 0027 */ + 3 << 4 | 4, /* code 0028 */ + 3 << 4 | 4, /* code 0029 */ + 6 << 4 | 6, /* code 002A */ + 6 << 4 | 6, /* code 002B */ + 3 << 4 | 3, /* code 002C */ + 4 << 4 | 4, /* code 002D */ + 3 << 4 | 3, /* code 002E */ + 4 << 4 | 5, /* code 002F */ + 6 << 4 | 8, /* code 0030 */ + 6 << 4 | 6, /* code 0031 */ + 6 << 4 | 7, /* code 0032 */ + 6 << 4 | 7, /* code 0033 */ + 8 << 4 | 8, /* code 0034 */ + 6 << 4 | 6, /* code 0035 */ + 6 << 4 | 8, /* code 0036 */ + 6 << 4 | 7, /* code 0037 */ + 6 << 4 | 8, /* code 0038 */ + 6 << 4 | 8, /* code 0039 */ + 3 << 4 | 3, /* code 003A */ + 3 << 4 | 3, /* code 003B */ + 6 << 4 | 5, /* code 003C */ + 6 << 4 | 6, /* code 003D */ + 6 << 4 | 5, /* code 003E */ + 5 << 4 | 6, /* code 003F */ + 10 << 4 | 10, /* code 0040 */ + 7 << 4 | 8, /* code 0041 */ + 7 << 4 | 7, /* code 0042 */ + 7 << 4 | 7, /* code 0043 */ + 8 << 4 | 8, /* code 0044 */ + 6 << 4 | 6, /* code 0045 */ + 6 << 4 | 6, /* code 0046 */ + 8 << 4 | 8, /* code 0047 */ + 8 << 4 | 8, /* code 0048 */ + 3 << 4 | 4, /* code 0049 */ + 4 << 4 | 5, /* code 004A */ + 7 << 4 | 8, /* code 004B */ + 6 << 4 | 6, /* code 004C */ + 10 << 4 | 11, /* code 004D */ + 8 << 4 | 9, /* code 004E */ + 9 << 4 | 9, /* code 004F */ + 7 << 4 | 7, /* code 0050 */ + 9 << 4 | 9, /* code 0051 */ + 7 << 4 | 8, /* code 0052 */ + 6 << 4 | 6, /* code 0053 */ + 7 << 4 | 6, /* code 0054 */ + 8 << 4 | 8, /* code 0055 */ + 7 << 4 | 6, /* code 0056 */ + 10 << 4 | 11, /* code 0057 */ + 6 << 4 | 8, /* code 0058 */ + 6 << 4 | 7, /* code 0059 */ + 6 << 4 | 7, /* code 005A */ + 4 << 4 | 5, /* code 005B */ + 4 << 4 | 5, /* code 005C */ + 4 << 4 | 5, /* code 005D */ + 6 << 4 | 7, /* code 005E */ + 5 << 4 | 6, /* code 005F */ + 6 << 4 | 7, /* code 0060 */ + 6 << 4 | 7, /* code 0061 */ + 7 << 4 | 7, /* code 0062 */ + 5 << 4 | 6, /* code 0063 */ + 7 << 4 | 7, /* code 0064 */ + 6 << 4 | 7, /* code 0065 */ + 5 << 4 | 6, /* code 0066 */ + 6 << 4 | 7, /* code 0067 */ + 7 << 4 | 7, /* code 0068 */ + 3 << 4 | 4, /* code 0069 */ + 4 << 4 | 5, /* code 006A */ + 6 << 4 | 7, /* code 006B */ + 3 << 4 | 4, /* code 006C */ + 10 << 4 | 10, /* code 006D */ + 7 << 4 | 7, /* code 006E */ + 7 << 4 | 7, /* code 006F */ + 7 << 4 | 7, /* code 0070 */ + 7 << 4 | 7, /* code 0071 */ + 4 << 4 | 5, /* code 0072 */ + 5 << 4 | 6, /* code 0073 */ + 4 << 4 | 5, /* code 0074 */ + 7 << 4 | 7, /* code 0075 */ + 6 << 4 | 7, /* code 0076 */ + 9 << 4 | 10, /* code 0077 */ + 6 << 4 | 7, /* code 0078 */ + 6 << 4 | 7, /* code 0079 */ + 5 << 4 | 6, /* code 007A */ + 4 << 4 | 5, /* code 007B */ + 6 << 4 | 6, /* code 007C */ + 4 << 4 | 5, /* code 007D */ + 6 << 4 | 6, /* code 007E */ + 7 << 4 | 6, /* code 007F */ +}; + +static uint8_t get_character_width(char character, bool bold) +{ + if (bold) { + // Bold. + return nanos_characters_width[character - NANOS_FIRST_CHAR] & 0x0F; + } + else { + // Regular. + return (nanos_characters_width[character - NANOS_FIRST_CHAR] >> 0x04) & 0x0F; + } +} + +static unsigned int is_word_delim(unsigned char c) +{ + // return !((c >= 'a' && c <= 'z') + // || (c >= 'A' && c <= 'Z') + // || (c >= '0' && c <= '9')); + return c == ' ' || c == '\n' || c == '\t' || c == '-' || c == '_'; +} + +// return the number of pages to be displayed when current page to show is 0 +unsigned int nbgl_font_compute_paging(const char *text_to_split, + uint32_t line_to_display, + uint32_t width_limit_in_pixels, + bool bold, + const char **line_start, + uint8_t *line_len) +{ + // compute start/length of text for the current line + uint32_t line = 1; + const char *start = text_to_split; + const char *end = start + strlen(start); + while (start < end) { + if (line != 0 && ((start[0] == ' ') || (start[0] == '\n'))) { + // Skip ' ' and '\n' if at a start of a new line + start++; + } + char current_char; + unsigned int len = 0; + unsigned int linew = 0; + const char *last_word_delim = start; + // not reached end of content + while (start + len < end + // line is not full + && linew <= width_limit_in_pixels) { + // compute new line length + current_char = start[len + 1]; + if (current_char >= NANOS_FIRST_CHAR || current_char <= NANOS_LAST_CHAR) { + linew += get_character_width(current_char, bold); + } + if (linew > width_limit_in_pixels) { + // we got a full line + break; + } + unsigned char c = start[len]; + if (is_word_delim(c)) { + last_word_delim = &start[len]; + } + len++; + // new line, don't go further + if (c == '\n') { + break; + } + } + + // if not splitting line onto a word delimiter, then cut at the previous word_delim, adjust + // len accordingly (and a word delim has been found already) + if (start + len < end && last_word_delim != start && len) { + // if line split within a word + if ((!is_word_delim(start[len - 1]) && !is_word_delim(start[len]))) { + len = last_word_delim - start; + } + } + + // fill up the paging structure + if (line_to_display != 0 && line_to_display == line) { + *line_start = start; + *line_len = len; + + // won't compute all pages, we reached the one to display + return 1; + } + + // prepare for next line + start += len; + + // skip to next line + line++; + } + + // return total number of line detected + return line - 1; +} diff --git a/lib_nbgl/src/nbgl_lns.c b/lib_nbgl/src/nbgl_lns.c new file mode 100644 index 00000000..2f2d6a41 --- /dev/null +++ b/lib_nbgl/src/nbgl_lns.c @@ -0,0 +1,542 @@ +/** + * @file nbgl_lns.c + * @brief Implementation of screen management + */ + +/********************* + * INCLUDES + *********************/ +#include +#include + +#include "glyphs.h" +#include "os_pic.h" +#include "ux.h" +#include "io.h" +#include "nbgl_lns.h" +#include "nbgl_fonts.h" + +/********************* + * DEFINES + *********************/ +// Maximum typical string length that fit in a screen line +// Technically we can fit more "'" but let's not consider this. +#define SCREEN_LINE_MAX_STRING_LEN 35 + +#define COLOR_EMPTY 0x000000 +#define COLOR_FILLED 0xFFFFFF + +/* Values for positioning elements, most comes from lib_ux/src/ux_layout_*.c */ +#define SCREEN_WIDTH 128 +#define SCREEN_HEIGHT 32 + +#define ICON_WIDTH 16 +#define ICON_HEIGHT 16 +#define ICON_X_BORDER 16 +#define ICON_Y_BORDER 2 + +#define TEXT_X_BORDER 6 +#define TEXT_X_OFFSET TEXT_X_BORDER +#define TEXT_X_OFFSET_BESIDE_ICON 41 + +#define TEXT_Y_OFFSET_FIRST_LINE 12 +#define TEXT_Y_OFFSET_SECOND_LINE 26 +#define TEXT_Y_OFFSET_BELOW_ICON 28 +#define TEXT_Y_OFFSET_MIDDLE_LINE 19 + +#define TEXT_WIDTH_BESIDE_ICON (SCREEN_WIDTH - TEXT_X_OFFSET_BESIDE_ICON - TEXT_X_BORDER) + +#define TEXT_WIDTH 114 + +typedef enum { + STATE_START_DISPLAY, + STATE_CLEAR_SCREEN, + STATE_DRAW_ICON, + STATE_PROCESS_TEXT_AND_DRAW_LINE_1, + STATE_DRAW_LINE_2, + STATE_DRAW_LEFT_NAV, + STATE_DRAW_RIGHT_NAV, + STATE_DONE, +} DisplayState_e; + +typedef struct { + const char *line2; + uint8_t line2_bold : 1; + uint8_t line2_len; + uint8_t pagination_pages; + uint8_t pagination_current_page; + DisplayState_e display_state; +} ScreenCxt_t; + +// boolean used to enable/forbid drawing/refresh +static bool objDrawingDisabled; + +static ScreenCxt_t screen_ctx; +static nbgl_lnsScreenContent_t screen_content; +static nbgl_lnsButtonCallback_t callback_ctx; +static nbgl_screenTickerConfiguration_t ticker_ctx; + +static bool clear_screen(void) +{ + bagl_element_t element = {0}; + element.component.type = BAGL_RECTANGLE; + element.component.width = SCREEN_WIDTH; + element.component.height = SCREEN_HEIGHT; + element.component.fill = BAGL_FILL; + element.component.fgcolor = COLOR_EMPTY; + element.component.bgcolor = COLOR_FILLED; + + io_seproxyhal_display_default(&element); + return true; +} + +static void display_glyph(const bagl_icon_details_t *icon_det, + uint8_t x, + uint8_t y, + uint8_t width, + uint8_t height) +{ + bagl_component_t component = {0}; + component.type = BAGL_ICON; + component.fill = BAGL_FILL; + component.x = x; + component.y = y; + component.width = width; + component.height = height; + component.fgcolor = COLOR_FILLED; + component.bgcolor = COLOR_EMPTY; + + io_seproxyhal_display_icon(&component, icon_det); +} + +static void display_string(const char *text, bool bold, bool centered, uint8_t x, uint8_t y) +{ + bagl_element_t element = {0}; + element.component.type = BAGL_LABELINE; + element.component.x = x; + element.component.y = y; + element.component.height = 32; + element.component.fgcolor = COLOR_FILLED; + element.component.bgcolor = COLOR_EMPTY; + element.text = text; + + if (bold) { + element.component.font_id = BAGL_FONT_OPEN_SANS_EXTRABOLD_11px; + } + else { + element.component.font_id = BAGL_FONT_OPEN_SANS_REGULAR_11px; + } + + if (centered) { + element.component.width = SCREEN_WIDTH - 2 * x; + element.component.font_id |= BAGL_FONT_ALIGNMENT_CENTER; + } + else { + element.component.width = SCREEN_WIDTH - x - TEXT_X_OFFSET; + } + + io_seproxyhal_display_default(&element); +} + +static void display_string_with_len(const char *text, + uint8_t len, + bool bold, + bool centered, + uint8_t x, + uint8_t y) +{ + if (len == 0) { + display_string(text, bold, centered, x, y); + } + else { + char buffer[SCREEN_LINE_MAX_STRING_LEN + 1]; + + if (len > SCREEN_LINE_MAX_STRING_LEN) { + len = SCREEN_LINE_MAX_STRING_LEN; + } + + memcpy(buffer, text, len); + buffer[len] = '\0'; + + display_string(buffer, bold, centered, x, y); + } +} + +static bool draw_icon(void) +{ + uint8_t x, y; + + if (screen_content.icon == NULL) { + return false; + } + if (screen_content.centered) { + x = (SCREEN_WIDTH - ICON_WIDTH) / 2; + y = ICON_Y_BORDER; + } + else { + x = ICON_X_BORDER; + y = (SCREEN_HEIGHT - ICON_HEIGHT) / 2; + } + + display_glyph(screen_content.icon, x, y, ICON_WIDTH, ICON_HEIGHT); + + return true; +} + +static void u8toa(uint8_t i, char buff[static 4]) +{ + uint8_t div; + uint8_t offset = 0; + + div = i / 100; + if (div) { + buff[offset++] = '0' + div; + i = i % 100; + } + + div = i / 10; + if (offset != 0 || div) { + buff[offset++] = '0' + div; + i = i % 10; + } + + buff[offset++] = '0' + i; + buff[offset++] = '\0'; +} + +static bool process_text_and_draw_line_1(void) +{ + const char *line1 = NULL; + uint8_t line1_len = 0; + uint8_t line1_x = TEXT_X_OFFSET; + uint8_t line1_y = TEXT_Y_OFFSET_FIRST_LINE; + uint8_t lines_width = TEXT_WIDTH; + + screen_ctx.line2 = NULL; + screen_ctx.line2_len = 0; + + if (screen_content.text == NULL) { + return false; + } + + if (screen_content.icon != NULL) { + if (screen_content.centered) { + // Consider subtext is NULL + // Consider text fit in a single line + display_string(screen_content.text, + screen_content.bold, + screen_content.centered, + TEXT_X_OFFSET, + TEXT_Y_OFFSET_BELOW_ICON); + return true; + } + else { + // Override default line positioning to support icon on the left of the screen + lines_width = TEXT_WIDTH_BESIDE_ICON; + line1_x = TEXT_X_OFFSET_BESIDE_ICON; + line1_y = TEXT_Y_OFFSET_FIRST_LINE; + } + } + + if (screen_content.subtext == NULL) { + uint8_t lines_nb + = nbgl_font_compute_nb_page(screen_content.text, lines_width, screen_content.bold); + if (lines_nb == 1) { + // Center the text on screen + line1_y = TEXT_Y_OFFSET_MIDDLE_LINE; + } + else if (lines_nb > 2) { + // No pagination supported in this mode + lines_nb = 2; + } + + for (int line = 1; line <= lines_nb; line++) { + const char *start; + uint8_t len; + + nbgl_font_compute_paging( + screen_content.text, line, lines_width, screen_content.bold, &start, &len); + if (line == 1) { + line1 = screen_content.text; + line1_len = len; + } + else { + // line == 2 + screen_ctx.line2 = start; + screen_ctx.line2_len = len; + screen_ctx.line2_bold = screen_content.bold; + } + } + } + else { + // Consider text is single line + // subtext is forced to regular font + screen_ctx.line2_bold = false; + + // Compute pagination parameters + if (screen_ctx.pagination_pages == 0) { + screen_ctx.pagination_pages + = nbgl_font_compute_nb_page(screen_content.subtext, lines_width, false); + if (screen_content.pos & BACKWARD_DIRECTION) { + screen_ctx.pagination_current_page = screen_ctx.pagination_pages; + } + else { + screen_ctx.pagination_current_page = 1; + } + } + + if (screen_ctx.pagination_pages < 2) { + // No paging needed + line1 = screen_content.text; + if (screen_ctx.pagination_pages == 1) { + screen_ctx.line2 = screen_content.subtext; + } + } + else { + // Generate title with pagination + // Consider the resulting text fit in screen + char buffer[SCREEN_LINE_MAX_STRING_LEN]; + +#if 1 + // Spare flash by not using snprintf here + char page[4]; + + strlcpy(buffer, screen_content.text, sizeof(buffer)); + strlcat(buffer, " (", sizeof(buffer)); + u8toa(screen_ctx.pagination_current_page, page); + strlcat(buffer, page, sizeof(buffer)); + strlcat(buffer, "/", sizeof(buffer)); + u8toa(screen_ctx.pagination_pages, page); + strlcat(buffer, page, sizeof(buffer)); + strlcat(buffer, ")", sizeof(buffer)); +#else + snprintf(buffer, + sizeof(buffer), + "%s (%d/%d)", + screen_content.text, + screen_ctx.pagination_current_page, + screen_ctx.pagination_pages); +#endif + + line1 = buffer; + + const char *start; + uint8_t len; + + nbgl_font_compute_paging(screen_content.subtext, + screen_ctx.pagination_current_page, + lines_width, + false, + &start, + &len); + screen_ctx.line2 = start; + screen_ctx.line2_len = len; + } + } + + if (line1 != NULL) { + display_string_with_len( + line1, line1_len, screen_content.bold, screen_content.centered, line1_x, line1_y); + } + return true; +} + +static bool draw_line_2(void) +{ + if (screen_ctx.line2 == NULL) { + return false; + } + + uint8_t text_x = TEXT_X_OFFSET; + uint8_t text_y = TEXT_Y_OFFSET_SECOND_LINE; + if (screen_content.icon != NULL) { + // icon present, text is shifted to right to give space to the icon + text_x = TEXT_X_OFFSET_BESIDE_ICON; + } + display_string_with_len(screen_ctx.line2, + screen_ctx.line2_len, + screen_ctx.line2_bold, + screen_content.centered, + text_x, + text_y); + return true; +} + +static bool draw_left_nav(void) +{ + bool left = false; + nbgl_stepPosition_t pos = screen_content.pos & STEP_POSITION_MASK; + + if ((screen_ctx.pagination_pages > 1) && (screen_ctx.pagination_current_page > 1)) { + left = true; + } + + if ((pos == NEITHER_FIRST_NOR_LAST_STEP) || (pos == LAST_STEP)) { + left = true; + } + + if (left) { + if (screen_content.vertical_nav) { + display_glyph(&C_icon_up, 2, 15, 7, 4); + } + else { + display_glyph(&C_icon_left, 2, 12, 4, 7); + } + return true; + } + + return false; +} + +static bool draw_right_nav(void) +{ + bool right = false; + nbgl_stepPosition_t pos = screen_content.pos & STEP_POSITION_MASK; + + if ((screen_ctx.pagination_pages > 1) + && (screen_ctx.pagination_current_page < screen_ctx.pagination_pages)) { + right = true; + } + if ((pos == FIRST_STEP) || (pos == NEITHER_FIRST_NOR_LAST_STEP)) { + right = true; + } + + if (right) { + if (screen_content.vertical_nav) { + display_glyph(&C_icon_down, 119, 15, 7, 4); + } + else { + display_glyph(&C_icon_right, 122, 12, 4, 7); + } + return true; + } + + return false; +} + +static void display_screen_ctx(void) +{ + bool should_wait = false; + + if (io_seproxyhal_spi_is_status_sent()) { + // Can't send a SEPROXYHAL STATUS, wait for later + return; + } + + if (objDrawingDisabled) { + // Screen is requested by the OS + return; + } + + if (screen_ctx.display_state == STATE_DONE) { + // Nothing to do anymore + return; + } + + while (should_wait == false) { + screen_ctx.display_state++; + + switch (screen_ctx.display_state) { + case STATE_CLEAR_SCREEN: + should_wait = clear_screen(); + break; + case STATE_DRAW_ICON: + should_wait = draw_icon(); + break; + case STATE_PROCESS_TEXT_AND_DRAW_LINE_1: + should_wait = process_text_and_draw_line_1(); + break; + case STATE_DRAW_LINE_2: + should_wait = draw_line_2(); + break; + case STATE_DRAW_LEFT_NAV: + should_wait = draw_left_nav(); + break; + case STATE_DRAW_RIGHT_NAV: + should_wait = draw_right_nav(); + break; + case STATE_DONE: + return; + default: + PRINTF("Error invalid state %d\n", screen_ctx.display_state); + return; + } + } +} + +void nbgl_screenRedraw(void) +{ + screen_ctx.display_state = STATE_START_DISPLAY; + display_screen_ctx(); +} + +void nbgl_refresh(void) +{ + display_screen_ctx(); +} + +void nbgl_objAllowDrawing(bool enable) +{ + objDrawingDisabled = !enable; +} + +void nbgl_processUxDisplayedEvent(void) +{ + display_screen_ctx(); +} + +void nbgl_lns_buttonCallback(nbgl_buttonEvent_t buttonEvent) +{ + if (screen_ctx.pagination_pages > 1) { + if ((buttonEvent == BUTTON_LEFT_PRESSED) && (screen_ctx.pagination_current_page > 1)) { + screen_ctx.pagination_current_page--; + nbgl_screenRedraw(); + return; + } + else if ((buttonEvent == BUTTON_RIGHT_PRESSED) + && (screen_ctx.pagination_current_page < screen_ctx.pagination_pages)) { + screen_ctx.pagination_current_page++; + nbgl_screenRedraw(); + return; + } + } + + if (callback_ctx != NULL) { + callback_ctx(buttonEvent); + } +} + +void nbgl_screenHandler(uint32_t intervaleMs) +{ + // call ticker callback of top of stack if active and not expired yet (for a non periodic) + if ((ticker_ctx.tickerCallback != NULL) && (ticker_ctx.tickerValue != 0)) { + ticker_ctx.tickerValue -= MIN(ticker_ctx.tickerValue, intervaleMs); + if (ticker_ctx.tickerValue == 0) { + // rearm if intervale is not null, and call the registered function + ticker_ctx.tickerValue = ticker_ctx.tickerIntervale; + ticker_ctx.tickerCallback(); + } + } +} + +void nbgl_screenDraw(nbgl_lnsScreenContent_t *content, + nbgl_lnsButtonCallback_t onActionCallback, + nbgl_screenTickerConfiguration_t *ticker) +{ + memcpy(&screen_content, content, sizeof(screen_content)); + screen_content.text = PIC(screen_content.text); + screen_content.subtext = PIC(screen_content.subtext); + + screen_ctx.pagination_pages = 0; + screen_ctx.display_state = STATE_START_DISPLAY; + + callback_ctx = onActionCallback; + if (ticker != NULL) { + memcpy(&ticker_ctx, ticker, sizeof(ticker_ctx)); + } + else { + memset(&ticker_ctx, 0, sizeof(ticker_ctx)); + } + + display_screen_ctx(); +} diff --git a/lib_nbgl/src/nbgl_use_case_nanos.c b/lib_nbgl/src/nbgl_use_case_nanos.c new file mode 100644 index 00000000..507aa499 --- /dev/null +++ b/lib_nbgl/src/nbgl_use_case_nanos.c @@ -0,0 +1,1014 @@ +/** + * @file nbgl_use_case_nanos.c + * @brief Implementation of typical pages (or sets of pages) for Applications for NanoS + */ + +#ifdef NBGL_USE_CASE +/********************* + * INCLUDES + *********************/ +#include +#include +#include "nbgl_debug.h" +#include "nbgl_use_case.h" +#include "glyphs.h" +#include "os_pic.h" +#include "os_helpers.h" +#include "ux.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/** + * @brief prototype of function to be called when a step is using a callback on "double-key" action + */ +typedef void (*nbgl_stepCallback_t)(void); + +typedef struct ReviewContext_s { + nbgl_choiceCallback_t onChoice; + const nbgl_contentTagValueList_t *tagValueList; + const nbgl_icon_details_t *icon; + const char *reviewTitle; + const char *address; // for address confirmation review +} ReviewContext_t; + +typedef struct ChoiceContext_s { + const nbgl_icon_details_t *icon; + const char *message; + const char *subMessage; + const char *confirmText; + const char *cancelText; + nbgl_choiceCallback_t onChoice; +} ChoiceContext_t; + +typedef struct HomeContext_s { + const char *appName; + const nbgl_icon_details_t *appIcon; + const char *tagline; + const nbgl_genericContents_t *settingContents; + const nbgl_contentInfoList_t *infosList; + nbgl_callback_t quitCallback; +} HomeContext_t; + +typedef enum { + NONE_USE_CASE, + REVIEW_USE_CASE, + ADDRESS_REVIEW_USE_CASE, + STREAMING_START_REVIEW_USE_CASE, + STREAMING_CONTINUE_REVIEW_USE_CASE, + STREAMING_FINISH_REVIEW_USE_CASE, + CHOICE_USE_CASE, + HOME_USE_CASE, + INFO_USE_CASE, + SETTINGS_USE_CASE, +} ContextType_t; + +typedef struct UseCaseContext_s { + ContextType_t type; + uint8_t nbPages; + int8_t currentPage; + nbgl_stepCallback_t + stepCallback; ///< if not NULL, function to be called on "double-key" action + union { + ReviewContext_t review; + ChoiceContext_t choice; + HomeContext_t home; + }; +} UseCaseContext_t; + +/********************** + * STATIC VARIABLES + **********************/ +static UseCaseContext_t context; + +/********************** + * STATIC FUNCTIONS + **********************/ +static void displayReviewPage(nbgl_stepPosition_t pos); +static void displayStreamingReviewPage(nbgl_stepPosition_t pos); +static void displayHomePage(nbgl_stepPosition_t pos); +static void displayInfoPage(nbgl_stepPosition_t pos); +static void displaySettingsPage(nbgl_stepPosition_t pos, bool toogle_state); +static void displayChoicePage(nbgl_stepPosition_t pos); + +static void startUseCaseHome(void); +static void startUseCaseInfo(void); +static void startUseCaseSettings(void); +static void startUseCaseSettingsAtPage(uint8_t initSettingPage); + +// Simple helper to get the number of elements inside a nbgl_content_t +static uint8_t getContentNbElement(const nbgl_content_t *content) +{ + switch (content->type) { + case TAG_VALUE_LIST: + return content->content.tagValueList.nbPairs; + case SWITCHES_LIST: + return content->content.switchesList.nbSwitches; + case INFOS_LIST: + return content->content.infosList.nbInfos; + default: + return 0; + } +} + +// Helper to retrieve the content inside a nbgl_genericContents_t using +// either the contentsList or using the contentGetterCallback +static const nbgl_content_t *getContentAtIdx(const nbgl_genericContents_t *genericContents, + int8_t contentIdx, + nbgl_content_t *content) +{ + if (contentIdx < 0 || contentIdx >= genericContents->nbContents) { + LOG_DEBUG(USE_CASE_LOGGER, "No content available at %d\n", contentIdx); + return NULL; + } + + if (genericContents->callbackCallNeeded) { + // Retrieve content through callback, but first memset the content. + memset(content, 0, sizeof(nbgl_content_t)); + genericContents->contentGetterCallback(contentIdx, content); + return content; + } + else { + // Retrieve content through list + return PIC(&genericContents->contentsList[contentIdx]); + } +} + +// Helper to retrieve the content inside a nbgl_genericContents_t using +// either the contentsList or using the contentGetterCallback +static const nbgl_content_t *getContentElemAtIdx(const nbgl_genericContents_t *genericContents, + uint8_t elemIdx, + uint8_t *elemContentIdx, + nbgl_content_t *content) +{ + const nbgl_content_t *p_content; + uint8_t nbPages = 0; + uint8_t elemNbPages = 0; + + for (int i = 0; i < genericContents->nbContents; i++) { + p_content = getContentAtIdx(genericContents, i, content); + elemNbPages = getContentNbElement(p_content); + if (nbPages + elemNbPages > elemIdx) { + *elemContentIdx = context.currentPage - nbPages; + break; + } + nbPages += elemNbPages; + } + + return p_content; +} + +static void getPairData(const nbgl_contentTagValueList_t *tagValueList, + uint8_t index, + const char **item, + const char **value) +{ + const nbgl_contentTagValue_t *pair; + + if (tagValueList->pairs != NULL) { + pair = PIC(&tagValueList->pairs[index]); + } + else { + pair = PIC(tagValueList->callback(index)); + } + *item = pair->item; + *value = pair->value; +} + +static void onReviewAccept(void) +{ + if (context.review.onChoice) { + context.review.onChoice(true); + } +} + +static void onReviewReject(void) +{ + if (context.review.onChoice) { + context.review.onChoice(false); + } +} + +static void onChoiceAccept(void) +{ + if (context.choice.onChoice) { + context.choice.onChoice(true); + } +} + +static void onChoiceReject(void) +{ + if (context.choice.onChoice) { + context.choice.onChoice(false); + } +} + +static void onSettingsAction(void) +{ + nbgl_content_t content; + uint8_t elemIdx; + + const nbgl_content_t *p_content = getContentElemAtIdx( + context.home.settingContents, context.currentPage, &elemIdx, &content); + + switch (p_content->type) { + case SWITCHES_LIST: { + const nbgl_contentSwitch_t *contentSwitch = &((const nbgl_contentSwitch_t *) PIC( + p_content->content.switchesList.switches))[elemIdx]; + nbgl_state_t state = (contentSwitch->initState == ON_STATE) ? OFF_STATE : ON_STATE; + displaySettingsPage(FORWARD_DIRECTION, true); + if (p_content->contentActionCallback != NULL) { + nbgl_contentActionCallback_t onContentAction + = PIC(p_content->contentActionCallback); + onContentAction(contentSwitch->token, state, context.currentPage); + } + break; + } + default: + break; + } +} + +static bool buttonGenericCallback(nbgl_buttonEvent_t event, nbgl_stepPosition_t *pos) +{ + if (event == BUTTON_LEFT_PRESSED) { + if (context.currentPage > 0) { + context.currentPage--; + } + else { + // Drop the event + return false; + } + *pos = BACKWARD_DIRECTION; + } + else if (event == BUTTON_RIGHT_PRESSED) { + if (context.currentPage < (int) (context.nbPages - 1)) { + context.currentPage++; + } + else { + // Drop the event + return false; + } + *pos = FORWARD_DIRECTION; + } + else { + if ((event == BUTTON_BOTH_PRESSED) && (context.stepCallback != NULL)) { + context.stepCallback(); + } + return false; + } + return true; +} + +static void reviewCallback(nbgl_buttonEvent_t event) +{ + nbgl_stepPosition_t pos; + + if (!buttonGenericCallback(event, &pos)) { + return; + } + + displayReviewPage(pos); +} + +static void streamingReviewCallback(nbgl_buttonEvent_t event) +{ + nbgl_stepPosition_t pos; + + if (!buttonGenericCallback(event, &pos)) { + return; + } + + displayStreamingReviewPage(pos); +} + +static void settingsCallback(nbgl_buttonEvent_t event) +{ + nbgl_stepPosition_t pos; + + if (!buttonGenericCallback(event, &pos)) { + return; + } + + displaySettingsPage(pos, false); +} + +static void infoCallback(nbgl_buttonEvent_t event) +{ + nbgl_stepPosition_t pos; + + if (!buttonGenericCallback(event, &pos)) { + return; + } + + displayInfoPage(pos); +} + +static void homeCallback(nbgl_buttonEvent_t event) +{ + nbgl_stepPosition_t pos; + + if (!buttonGenericCallback(event, &pos)) { + return; + } + + displayHomePage(pos); +} + +static void choiceCallback(nbgl_buttonEvent_t event) +{ + nbgl_stepPosition_t pos; + + if (!buttonGenericCallback(event, &pos)) { + return; + } + + displayChoicePage(pos); +} + +static void statusButtonCallback(nbgl_buttonEvent_t event) +{ + if (event == BUTTON_BOTH_PRESSED) { + if (context.stepCallback != NULL) { + context.stepCallback(); + } + } +} + +// callback used for timeout +static void statusTickerCallback(void) +{ + if (context.stepCallback != NULL) { + context.stepCallback(); + } +} + +static nbgl_stepPosition_t get_step_pos(void) +{ + return GET_POS_OF_STEP(context.currentPage, context.nbPages); +} + +// function used to display the current page in review +static void displayReviewPage(nbgl_stepPosition_t pos) +{ + nbgl_lnsScreenContent_t content = {0}; + content.centered = true; + + context.stepCallback = NULL; + + if (context.currentPage == 0) { // title page + content.icon = context.review.icon; + content.text = context.review.reviewTitle; + content.centered = false; + } + else if (context.currentPage == (context.nbPages - 2)) { // accept page + content.icon = &C_icon_validate_14; + content.text = "Approve"; + context.stepCallback = onReviewAccept; + content.bold = true; + } + else if (context.currentPage == (context.nbPages - 1)) { // reject page + content.icon = &C_icon_crossmark; + content.text = "Reject"; + context.stepCallback = onReviewReject; + content.bold = true; + } + else if ((context.review.address != NULL) + && (context.currentPage == 1)) { // address confirmation and 2nd page + content.text = "Address"; + content.subtext = context.review.address; + } + else { + uint8_t pairIndex = (context.review.address != NULL) ? (context.currentPage - 2) + : (context.currentPage - 1); + getPairData(context.review.tagValueList, pairIndex, &content.text, &content.subtext); + } + + content.pos = pos | get_step_pos(); + nbgl_screenDraw(&content, reviewCallback, NULL); +} + +// function used to display the current page in review +static void displayStreamingReviewPage(nbgl_stepPosition_t pos) +{ + nbgl_lnsScreenContent_t content = {0}; + content.centered = true; + + context.stepCallback = NULL; + + if (context.type == STREAMING_START_REVIEW_USE_CASE) { + if (context.currentPage == 0) { // title page + content.icon = context.review.icon; + content.text = context.review.reviewTitle; + content.centered = false; + } + else { + nbgl_useCaseSpinner("Processing"); + onReviewAccept(); + return; + } + } + else if (context.type == STREAMING_CONTINUE_REVIEW_USE_CASE) { + if (context.currentPage < context.review.tagValueList->nbPairs) { + getPairData( + context.review.tagValueList, context.currentPage, &content.text, &content.subtext); + } + else { + nbgl_useCaseSpinner("Processing"); + onReviewAccept(); + return; + } + } + else { + if (context.currentPage == 0) { // accept page + content.icon = &C_icon_validate_14; + content.text = "Approve"; + context.stepCallback = onReviewAccept; + content.bold = true; + } + else { // reject page + content.icon = &C_icon_crossmark; + content.text = "Reject"; + context.stepCallback = onReviewReject; + content.bold = true; + } + } + + content.pos = pos | get_step_pos(); + nbgl_screenDraw(&content, streamingReviewCallback, NULL); +} + +// function used to display the current page in info +static void displayInfoPage(nbgl_stepPosition_t pos) +{ + nbgl_lnsScreenContent_t content = {0}; + content.centered = true; + content.bold = true; + + context.stepCallback = NULL; + + if (context.currentPage < (context.nbPages - 1)) { + content.text = PIC( + ((const char *const *) PIC(context.home.infosList->infoTypes))[context.currentPage]); + content.subtext = PIC( + ((const char *const *) PIC(context.home.infosList->infoContents))[context.currentPage]); + } + else { + content.icon = &C_icon_back_x; + content.text = "Back"; + context.stepCallback = startUseCaseHome; + } + + content.pos = pos | get_step_pos(); + nbgl_screenDraw(&content, infoCallback, NULL); +} + +// function used to display the current page in settings +static void displaySettingsPage(nbgl_stepPosition_t pos, bool toogle_state) +{ + nbgl_lnsScreenContent_t content = {0}; + content.centered = true; + content.bold = true; + + context.stepCallback = NULL; + + if (context.currentPage < (context.nbPages - 1)) { + nbgl_content_t nbgl_content; + uint8_t elemIdx; + + const nbgl_content_t *p_nbgl_content = getContentElemAtIdx( + context.home.settingContents, context.currentPage, &elemIdx, &nbgl_content); + + switch (p_nbgl_content->type) { + case TAG_VALUE_LIST: + getPairData(&p_nbgl_content->content.tagValueList, + elemIdx, + &content.text, + &content.subtext); + break; + case SWITCHES_LIST: { + const nbgl_contentSwitch_t *contentSwitch = &((const nbgl_contentSwitch_t *) PIC( + p_nbgl_content->content.switchesList.switches))[elemIdx]; + content.text = contentSwitch->text; + // switch subtext is ignored + nbgl_state_t state = contentSwitch->initState; + if (toogle_state) { + state = (state == ON_STATE) ? OFF_STATE : ON_STATE; + } + if (state == ON_STATE) { + content.subtext = "Enabled"; + } + else { + content.subtext = "Disabled"; + } + context.stepCallback = onSettingsAction; + break; + } + case INFOS_LIST: + content.text = ((const char *const *) PIC( + p_nbgl_content->content.infosList.infoTypes))[elemIdx]; + content.subtext = ((const char *const *) PIC( + p_nbgl_content->content.infosList.infoContents))[elemIdx]; + break; + default: + break; + } + } + else { // last page is for quit + content.icon = &C_icon_back_x; + content.text = "Back"; + context.stepCallback = startUseCaseHome; + } + + content.pos = pos | get_step_pos(); + nbgl_screenDraw(&content, settingsCallback, NULL); +} + +static void startUseCaseHome(void) +{ + if (context.type == SETTINGS_USE_CASE) { + context.currentPage = 1; + } + else if (context.type == INFO_USE_CASE) { + context.currentPage = 2; + } + else { + context.currentPage = 0; + } + context.type = HOME_USE_CASE; + context.nbPages = 4; + + displayHomePage(FORWARD_DIRECTION); +} + +static void startUseCaseInfo(void) +{ + context.type = INFO_USE_CASE; + context.nbPages = context.home.infosList->nbInfos + 1; // For back screen + context.currentPage = 0; + + displayInfoPage(FORWARD_DIRECTION); +} + +static void startUseCaseSettingsAtPage(uint8_t initSettingPage) +{ + nbgl_content_t content; + const nbgl_content_t *p_content; + + context.type = SETTINGS_USE_CASE; + context.nbPages = 1; // For back screen + for (int i = 0; i < context.home.settingContents->nbContents; i++) { + p_content = getContentAtIdx(context.home.settingContents, i, &content); + context.nbPages += getContentNbElement(p_content); + } + context.currentPage = initSettingPage; + + displaySettingsPage(FORWARD_DIRECTION, false); +} + +static void startUseCaseSettings(void) +{ + startUseCaseSettingsAtPage(0); +} + +// function used to display the current page in home +static void displayHomePage(nbgl_stepPosition_t pos) +{ + nbgl_lnsScreenContent_t content = {0}; + content.centered = true; + content.bold = true; + + context.stepCallback = NULL; + + // Handle case where there is no settings + if (context.home.settingContents == NULL && context.currentPage == 1) { + if (pos & BACKWARD_DIRECTION) { + context.currentPage -= 1; + } + else { + context.currentPage += 1; + if (context.home.infosList == NULL) { + context.currentPage += 1; + } + } + } + + // Handle case where there is no info + if (context.home.infosList == NULL && context.currentPage == 2) { + if (pos & BACKWARD_DIRECTION) { + context.currentPage -= 1; + if (context.home.settingContents == NULL) { + context.currentPage -= 1; + } + } + else { + context.currentPage += 1; + } + } + + switch (context.currentPage) { + case 0: + content.icon = context.home.appIcon; + if (context.home.tagline != NULL) { + content.text = context.home.tagline; + } + else { + content.text = context.home.appName; + content.subtext = "is ready"; + } + content.centered = false; + content.bold = false; + break; + case 1: + content.icon = &C_icon_coggle; + content.text = "Settings"; + context.stepCallback = startUseCaseSettings; + break; + case 2: + content.icon = &C_icon_certificate; + content.text = "About"; + context.stepCallback = startUseCaseInfo; + break; + default: + content.icon = &C_icon_dashboard_x; + content.text = "Quit"; + context.stepCallback = context.home.quitCallback; + break; + } + + content.pos = pos | get_step_pos(); + nbgl_screenDraw(&content, homeCallback, NULL); +} + +// function used to display the current page in choice +static void displayChoicePage(nbgl_stepPosition_t pos) +{ + nbgl_lnsScreenContent_t content = {0}; + content.centered = true; + + context.stepCallback = NULL; + + if (context.currentPage == 0) { // title page + content.icon = context.choice.icon; + content.text = context.choice.message; + content.subtext = context.choice.subMessage; + content.centered = false; + } + else if (context.currentPage == 1) { // confirm page + content.icon = &C_icon_validate_14; + content.text = context.choice.confirmText; + context.stepCallback = onChoiceAccept; + content.bold = true; + } + else { // cancel page + content.icon = &C_icon_crossmark; + content.text = context.choice.cancelText; + context.stepCallback = onChoiceReject; + content.bold = true; + } + + content.pos = pos | get_step_pos(); + nbgl_screenDraw(&content, choiceCallback, NULL); +} + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * @brief Draws the extended version of home page of an app (page on which we land when launching it + * from dashboard) with automatic support of setting display. + * + * @param appName app name + * @param appIcon app icon + * @param tagline text under app name (if NULL, it will be "\n is ready") + * @param initSettingPage if not INIT_HOME_PAGE, start directly the corresponding setting page + * @param settingContents setting contents to be displayed + * @param infosList infos to be displayed (version, license, developer, ...) + * @param action if not NULL, info used for an action button (on top of "Quit + * App" button/footer) + * @param quitCallback callback called when quit button is touched + */ +void nbgl_useCaseHomeAndSettings(const char *appName, + const nbgl_icon_details_t *appIcon, + const char *tagline, + const uint8_t initSettingPage, + const nbgl_genericContents_t *settingContents, + const nbgl_contentInfoList_t *infosList, + const nbgl_homeAction_t *action, + nbgl_callback_t quitCallback) +{ + UNUSED(action); // TODO support it at some point? + + memset(&context, 0, sizeof(UseCaseContext_t)); + context.home.appName = appName; + context.home.appIcon = appIcon; + context.home.tagline = tagline; + context.home.settingContents = PIC(settingContents); + context.home.infosList = PIC(infosList); + context.home.quitCallback = quitCallback; + + if (initSettingPage != INIT_HOME_PAGE) { + startUseCaseSettingsAtPage(initSettingPage); + } + else { + startUseCaseHome(); + } +} + +/** + * @brief Draws a flow of pages of a review. + * @note All tag/value pairs are provided in the API and the number of pages is automatically + * computed. + * + * @param operationType type of operation (Operation, Transaction, Message) + * @param tagValueList list of tag/value pairs + * @param icon icon used on first and last review page + * @param reviewTitle string used in the first review page + * @param reviewSubTitle string to set under reviewTitle (can be NULL) + * @param finishTitle string used in the last review page + * @param choiceCallback callback called when operation is accepted (param is true) or rejected + * (param is false) + */ +void nbgl_useCaseReview(nbgl_operationType_t operationType, + const nbgl_contentTagValueList_t *tagValueList, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle, + const char *finishTitle, + nbgl_choiceCallback_t choiceCallback) +{ + UNUSED(operationType); // TODO adapt accept and reject text depending on this value? + UNUSED(reviewSubTitle); // TODO dedicated screen for it? + UNUSED(finishTitle); // TODO dedicated screen for it? + + memset(&context, 0, sizeof(UseCaseContext_t)); + context.type = REVIEW_USE_CASE; + context.review.tagValueList = tagValueList; + context.review.reviewTitle = reviewTitle; + context.review.icon = icon; + context.review.onChoice = choiceCallback; + context.currentPage = 0; + // + 3 because 1 page for title and 2 pages at the end for accept/reject + context.nbPages = tagValueList->nbPairs + 3; + + displayReviewPage(FORWARD_DIRECTION); +} + +/** + * @brief Draws a flow of pages of a review. + * @note All tag/value pairs are provided in the API and the number of pages is automatically + * computed. + * + * @param operationType type of operation (Operation, Transaction, Message) + * @param tagValueList list of tag/value pairs + * @param icon icon used on first and last review page + * @param reviewTitle string used in the first review page + * @param reviewSubTitle string to set under reviewTitle (can be NULL) + * @param finishTitle string used in the last review page + * @param choiceCallback callback called when operation is accepted (param is true) or rejected + * (param is false) + */ +void nbgl_useCaseReviewLight(nbgl_operationType_t operationType, + const nbgl_contentTagValueList_t *tagValueList, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle, + const char *finishTitle, + nbgl_choiceCallback_t choiceCallback) +{ + return nbgl_useCaseReview(operationType, + tagValueList, + icon, + reviewTitle, + reviewSubTitle, + finishTitle, + choiceCallback); +} + +/** + * @brief Draws a flow of pages of an extended address verification page. + * @note All tag/value pairs are provided in the API and the number of pages is automatically + * computed. + * + * @param address address to confirm (NULL terminated string) + * @param additionalTagValueList list of tag/value pairs (can be NULL) (must be persistent because + * no copy) + * @param callback callback called when button or footer is touched (if true, button, if false + * footer) + * @param icon icon used on the first review page + * @param reviewTitle string used in the first review page + * @param reviewSubTitle string to set under reviewTitle (can be NULL) + * @param choiceCallback callback called when transaction is accepted (param is true) or rejected + * (param is false) + */ +void nbgl_useCaseAddressReview(const char *address, + const nbgl_contentTagValueList_t *additionalTagValueList, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle, + nbgl_choiceCallback_t choiceCallback) +{ + UNUSED(reviewSubTitle); // TODO dedicated screen for it? + + memset(&context, 0, sizeof(UseCaseContext_t)); + context.type = ADDRESS_REVIEW_USE_CASE; + context.review.address = address; + context.review.reviewTitle = reviewTitle; + context.review.icon = icon; + context.review.onChoice = choiceCallback; + context.currentPage = 0; + // + 4 because 1 page for title, 1 for address and 2 pages at the end for approve/reject + context.nbPages = 4; + if (additionalTagValueList) { + memcpy(&context.review.tagValueList, + additionalTagValueList, + sizeof(nbgl_contentTagValueList_t)); + context.nbPages += additionalTagValueList->nbPairs; + } + + displayReviewPage(FORWARD_DIRECTION); +} + +/** + * @brief Draws a transient (3s) status page, either of success or failure, with the given message + * + * @param message string to set in middle of page (Upper case for success) + * @param isSuccess if true, message is drawn in a Ledger style (with corners) + * @param quitCallback callback called when quit timer times out or status is manually dismissed + */ +void nbgl_useCaseStatus(const char *message, bool isSuccess, nbgl_callback_t quitCallback) +{ + UNUSED(isSuccess); // TODO add icon depending on isSuccess? + + memset(&context, 0, sizeof(UseCaseContext_t)); + context.stepCallback = quitCallback; + context.currentPage = 0; + context.nbPages = 1; + + nbgl_screenTickerConfiguration_t ticker = { + .tickerCallback = PIC(statusTickerCallback), + .tickerIntervale = 0, // not periodic + .tickerValue = 3000 // 3 seconds + }; + + nbgl_lnsScreenContent_t content = {0}; + content.text = message; + content.centered = true; + content.bold = true; + + nbgl_screenDraw(&content, statusButtonCallback, &ticker); +} + +/** + * @brief Draws a transient (3s) status page for the reviewStatusType + * + * @param reviewStatusType type of status to display + * @param quitCallback callback called when quit timer times out or status is manually dismissed + */ +void nbgl_useCaseReviewStatus(nbgl_reviewStatusType_t reviewStatusType, + nbgl_callback_t quitCallback) +{ + const char *msg; + bool isSuccess; + switch (reviewStatusType) { + case STATUS_TYPE_OPERATION_SIGNED: + msg = "Operation signed"; + isSuccess = true; + break; + case STATUS_TYPE_OPERATION_REJECTED: + msg = "Operation rejected"; + isSuccess = false; + break; + case STATUS_TYPE_TRANSACTION_SIGNED: + msg = "Transaction signed"; + isSuccess = true; + break; + case STATUS_TYPE_TRANSACTION_REJECTED: + msg = "Transaction rejected"; + isSuccess = false; + break; + case STATUS_TYPE_MESSAGE_SIGNED: + msg = "Message signed"; + isSuccess = true; + break; + case STATUS_TYPE_MESSAGE_REJECTED: + msg = "Message rejected"; + isSuccess = false; + break; + case STATUS_TYPE_ADDRESS_VERIFIED: + msg = "Address verified"; + isSuccess = true; + break; + case STATUS_TYPE_ADDRESS_REJECTED: + msg = "Verification\ncancelled"; + isSuccess = false; + break; + default: + return; + } + nbgl_useCaseStatus(msg, isSuccess, quitCallback); +} + +/** + * @brief Start drawing the flow of pages of a review. + * @note This should be followed by calls to nbgl_useCaseReviewStreamingContinue and finally to + * nbgl_useCaseReviewStreamingFinish. + * + * @param operationType type of operation (Operation, Transaction, Message) + * @param icon icon used on first and last review page + * @param reviewTitle string used in the first review page + * @param reviewSubTitle string to set under reviewTitle (can be NULL) + * @param choiceCallback callback called when more operation data are needed (param is true) or + * operation is rejected (param is false) + */ +void nbgl_useCaseReviewStreamingStart(nbgl_operationType_t operationType, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle, + nbgl_choiceCallback_t choiceCallback) +{ + UNUSED(operationType); // TODO adapt accept and reject text depending on this value? + UNUSED(reviewSubTitle); // TODO dedicated screen for it? + + memset(&context, 0, sizeof(UseCaseContext_t)); + context.type = STREAMING_START_REVIEW_USE_CASE; + context.review.reviewTitle = reviewTitle; + context.review.icon = icon; + context.review.onChoice = choiceCallback; + context.currentPage = 0; + context.nbPages = 1 + 1; // Start page + trick for review continue + + displayStreamingReviewPage(FORWARD_DIRECTION); +} + +void nbgl_useCaseReviewStreamingContinue(const nbgl_contentTagValueList_t *tagValueList, + nbgl_choiceCallback_t choiceCallback) +{ + memset(&context, 0, sizeof(UseCaseContext_t)); + context.type = STREAMING_CONTINUE_REVIEW_USE_CASE; + context.review.tagValueList = tagValueList; + context.review.onChoice = choiceCallback; + context.currentPage = 0; + context.nbPages = tagValueList->nbPairs + 1; // data + trick for review continue + + displayStreamingReviewPage(FORWARD_DIRECTION); +} + +void nbgl_useCaseReviewStreamingFinish(const char *finishTitle, + nbgl_choiceCallback_t choiceCallback) +{ + UNUSED(finishTitle); // TODO dedicated screen for it? + + memset(&context, 0, sizeof(UseCaseContext_t)); + context.type = STREAMING_FINISH_REVIEW_USE_CASE; + context.review.onChoice = choiceCallback; + context.currentPage = 0; + context.nbPages = 2; // 2 pages at the end for accept/reject + + displayStreamingReviewPage(FORWARD_DIRECTION); +} + +/** + * @brief draw a spinner page with the given parameters. + * + * @param text text to use with the spinner + */ +void nbgl_useCaseSpinner(const char *text) +{ + nbgl_lnsScreenContent_t content = {0}; + content.text = text; + content.icon = &C_icon_processing; + content.centered = true; + content.bold = true; + + nbgl_screenDraw(&content, NULL, NULL); +} + +void nbgl_useCaseChoice(const nbgl_icon_details_t *icon, + const char *message, + const char *subMessage, + const char *confirmText, + const char *cancelText, + nbgl_choiceCallback_t callback) +{ + memset(&context, 0, sizeof(UseCaseContext_t)); + context.type = CHOICE_USE_CASE; + context.choice.icon = icon; + context.choice.message = message; + context.choice.subMessage = subMessage; + context.choice.confirmText = confirmText; + context.choice.cancelText = cancelText; + context.choice.onChoice = callback; + context.currentPage = 0; + context.nbPages = 1 + 2; // 2 pages at the end for confirm/cancel + + displayChoicePage(FORWARD_DIRECTION); +}; + +#endif // NBGL_USE_CASE diff --git a/lib_standard_app/io.c b/lib_standard_app/io.c index ba4173d7..d0e6b725 100644 --- a/lib_standard_app/io.c +++ b/lib_standard_app/io.c @@ -73,7 +73,7 @@ WEAK uint8_t io_event(uint8_t channel) #ifdef HAVE_BAGL UX_DISPLAYED_EVENT({}); #endif // HAVE_BAGL -#ifdef HAVE_NBGL +#if defined(HAVE_NBGL) && !defined(HAVE_BAGL) UX_DEFAULT_EVENT(); #endif // HAVE_NBGL break; diff --git a/lib_ux/include/ux_bagl.h b/lib_ux/include/ux_bagl.h index 9d8f9505..2c4f91c6 100644 --- a/lib_ux/include/ux_bagl.h +++ b/lib_ux/include/ux_bagl.h @@ -708,8 +708,8 @@ void io_seproxyhal_backlight(unsigned int flags, unsigned int backlight_percenta * Helper function to send the given bitmap splitting into multiple DISPLAY_RAW packet as the bitmap * is not meant to fit in a single SEPROXYHAL packet. */ -void io_seproxyhal_display_icon(bagl_component_t *icon_component, - bagl_icon_details_t *icon_details); +void io_seproxyhal_display_icon(const bagl_component_t *icon_component, + const bagl_icon_details_t *icon_details); /** * Helper method on the Blue to output icon header to the MCU and allow for bitmap transformation diff --git a/lib_ux_nbgl/ux.c b/lib_ux_nbgl/ux.c new file mode 100644 index 00000000..15b3cfc7 --- /dev/null +++ b/lib_ux_nbgl/ux.c @@ -0,0 +1,124 @@ + +/******************************************************************************* + * Ledger Nano S - Secure firmware + * (c) 2022 Ledger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ********************************************************************************/ + +#include "seproxyhal_protocol.h" +#include "ux.h" +#include "nbgl_buttons.h" +#include "os_io.h" +#ifndef HAVE_BOLOS + +/** + * @brief internal bolos ux event processing with callback in case event is to be processed by the + * application + * + * @param ignoring_app_if_ux_busy if set to false, function returns true if no REDRAW needed + * @return true if ignoring_app_if_ux_busy is false or UX is not busy + */ +static bool ux_forward_event(void) +{ + bool app_event = false; + + G_ux_params.ux_id = BOLOS_UX_EVENT; + G_ux_params.len = 0; + os_ux(&G_ux_params); + G_ux_params.len = os_sched_last_status(TASK_BOLOS_UX); + + switch (G_ux_params.len) { + case BOLOS_UX_REDRAW: { + // enable drawing according to UX decision + nbgl_objAllowDrawing(true); + nbgl_screenRedraw(); + nbgl_refresh(); + break; + } + case BOLOS_UX_IGNORE: + case BOLOS_UX_CONTINUE: { + nbgl_objAllowDrawing(false); + break; + } + default: { + app_event = true; + nbgl_objAllowDrawing(true); + break; + } + } + + return app_event; +} + +/** + * @brief Process button push event. + * @note Application's button push/release event handler is called only if the ux app does not deny + * it (button event caught by BOLOS UX page). + * + * @param seph_packet received SEPH packet + */ +void ux_process_button_event(const uint8_t seph_packet[]) +{ + bool app_event = ux_forward_event(); + + // if the event is not fully consumed by UX, use it for NBGL + if (app_event) { + uint8_t buttons_state = seph_packet[3] >> 1; + nbgl_buttonsHandler(buttons_state); + } +} + +/** + * @brief Process the ticker_event to the os ux handler. Ticker event callback is always called + * whatever the return code of the ux app. + * @note Ticker event interval is assumed to be 100 ms. + */ +void ux_process_ticker_event(void) +{ + // forward to UX + bool app_event = ux_forward_event(); + + // update ticker in NBGL + nbgl_screenHandler(100); + + if (!app_event) { + return; + } + + nbgl_refresh(); +} + +/** + * Forwards the event to UX + */ +void ux_process_default_event(void) +{ + // forward to UX + ux_forward_event(); +} + +/** + * Forwards the event to UX + */ + +void ux_process_displayed_event(void) +{ + // forward to UX + bool app_event = ux_forward_event(); + if (app_event) { + nbgl_processUxDisplayedEvent(); + } +} + +#endif // HAVE_BOLOS diff --git a/lib_ux_nbgl/ux.h b/lib_ux_nbgl/ux.h new file mode 100644 index 00000000..0f67c41c --- /dev/null +++ b/lib_ux_nbgl/ux.h @@ -0,0 +1,101 @@ + +/******************************************************************************* + * Ledger Nano S - Secure firmware + * (c) 2022 Ledger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ********************************************************************************/ + +#pragma once + +#include + +#include "os_math.h" +#include "os_ux.h" +#include "os_task.h" +#include "nbgl_lns.h" +typedef struct bagl_element_e bagl_element_t; + +// callback returns NULL when element must not be redrawn (with a changing color or what so ever) +typedef const bagl_element_t *(*bagl_element_callback_t)(const bagl_element_t *element); + +// a graphic element is an element with defined text and actions depending on user touches +struct bagl_element_e { + bagl_component_t component; + const char *text; +}; + +void io_seproxyhal_display_icon(const bagl_component_t *icon_component, + const bagl_icon_details_t *icon_details); + +void io_seproxyhal_display_default(const bagl_element_t *element); + +// Kept for retro-compatibility +typedef struct { +} ux_state_t; + +extern ux_state_t G_ux; +extern bolos_ux_params_t G_ux_params; + +extern void ux_process_button_event(const uint8_t seph_packet[]); +extern void ux_process_ticker_event(void); +extern void ux_process_default_event(void); +extern void ux_process_displayed_event(void); + +/** + * Initialize the user experience structure + */ +#define UX_INIT() + +/** + * Request a wake up of the device (pin lock screen, ...) to display a new interface to the user. + * Wake up prevents power-off features. Therefore, security wise, this function shall only + * be called to request direct user interaction. + */ +#define UX_WAKE_UP() \ + G_ux_params.ux_id = BOLOS_UX_WAKE_UP; \ + G_ux_params.len = 0; \ + os_ux(&G_ux_params); \ + G_ux_params.len = os_sched_last_status(TASK_BOLOS_UX); + +/** + * forward the button push/release events to the os ux handler. if not used by it, it will + * be used by App controls + */ +#define UX_BUTTON_PUSH_EVENT(seph_packet) ux_process_button_event(seph_packet) + +/** + * forward the finger_event to the os ux handler. if not used by it, it will + * be used by App controls + */ +#define UX_FINGER_EVENT(seph_packet) + +/** + * forward the ticker_event to the os ux handler. Ticker event callback is always called whatever + * the return code of the ux app. Ticker event interval is assumed to be 100 ms. + */ +#define UX_TICKER_EVENT(seph_packet, callback) ux_process_ticker_event() + +/** + * Forward the event, ignoring the UX return code, the event must therefore be either not processed + * or processed with extreme care by the application afterwards + */ +#define UX_DEFAULT_EVENT() ux_process_default_event() + +/** + * forward the button push/release events to the os ux handler. if not used by it, it will + * be used by App controls + */ +#define UX_DISPLAYED_EVENT(...) ux_process_displayed_event() + +#include "glyphs.h" diff --git a/lib_ux_sync/include/ux_sync.h b/lib_ux_sync/include/ux_sync.h index 938ffe10..c233975f 100644 --- a/lib_ux_sync/include/ux_sync.h +++ b/lib_ux_sync/include/ux_sync.h @@ -51,12 +51,6 @@ ux_sync_ret_t ux_sync_reviewStreamingContinue(const nbgl_contentTagValueList_t * ux_sync_ret_t ux_sync_reviewStreamingFinish(const char *finishTitle); -ux_sync_ret_t ux_sync_genericReview(const nbgl_genericContents_t *contents, const char *rejectText); - -ux_sync_ret_t ux_sync_genericConfiguration(const char *title, - uint8_t initPage, - const nbgl_genericContents_t *contents); - /* * This function must be implemented by the caller. * It must wait for the next seph event and process it except for APDU events. diff --git a/lib_ux_sync/src/ux_sync.c b/lib_ux_sync/src/ux_sync.c index ffc977e8..b4cc3cbb 100644 --- a/lib_ux_sync/src/ux_sync.c +++ b/lib_ux_sync/src/ux_sync.c @@ -23,12 +23,6 @@ static void quit_callback(void) g_ended = true; } -static void rejected_callback(void) -{ - g_ret = UX_SYNC_RET_REJECTED; - g_ended = true; -} - static void ux_sync_init(void) { g_ended = false; @@ -284,46 +278,4 @@ ux_sync_ret_t ux_sync_reviewStreamingFinish(const char *finishTitle) return ux_sync_wait(false); } -/** - * @brief Draws a flow of pages of a review with automatic pagination depending on content - * to be displayed that is passed through contents. - * - * @param contents contents to be displayed - * @param rejectText text to use in footer - * - * @return ret code: - * - UX_SYNC_RET_REJECTED - */ -ux_sync_ret_t ux_sync_genericReview(const nbgl_genericContents_t *contents, const char *rejectText) - -{ - ux_sync_init(); - nbgl_useCaseGenericReview(contents, rejectText, rejected_callback); - return ux_sync_wait(false); -} - -/** - * @brief Draws a set of pages with automatic pagination depending on content - * to be displayed that is passed through contents. - * - * @param title string to use as title - * @param initPage page on which to start, can be != 0 if you want to display a specific page - * after a confirmation change or something. Then the value should be taken from the - * nbgl_contentActionCallback_t callback call. - * @param contents contents to be displayed - * - * @return ret code: - * - UX_SYNC_RET_QUITTED - * - UX_SYNC_RET_APDU_RECEIVED - */ -ux_sync_ret_t ux_sync_genericConfiguration(const char *title, - uint8_t initPage, - const nbgl_genericContents_t *contents) - -{ - ux_sync_init(); - nbgl_useCaseGenericConfiguration(title, initPage, contents, quit_callback); - return ux_sync_wait(true); -} - #endif diff --git a/src/app_metadata.c b/src/app_metadata.c index 15c3fce8..c3afc8f8 100644 --- a/src/app_metadata.c +++ b/src/app_metadata.c @@ -72,14 +72,11 @@ CREATE_METADATA_STRING_ITEM(SDK_VERSION, sdk_version) CREATE_METADATA_STRING_ITEM(SDK_HASH, sdk_hash) #endif -#if defined(HAVE_BAGL) +#if defined(HAVE_BAGL) || defined(HAVE_NBGL) +// On MCU side, LNS only use bagl API CREATE_METADATA_STRING_ITEM("bagl", sdk_graphics) #endif -#if defined(HAVE_NBGL) -CREATE_METADATA_STRING_ITEM("nbgl", sdk_graphics) -#endif - #ifdef APP_INSTALL_PARAMS_DATA __attribute__((section(".install_parameters"))) const uint8_t install_parameters[] = {APP_INSTALL_PARAMS_DATA}; diff --git a/src/ledger_assert.c b/src/ledger_assert.c index 3ba6619c..d6c20f6b 100644 --- a/src/ledger_assert.c +++ b/src/ledger_assert.c @@ -116,7 +116,7 @@ void assert_exit(bool confirm) * Display info on screen mechanism * ***********************************/ #if defined(HAVE_LEDGER_ASSERT_DISPLAY) || defined(HAVE_DEBUG_THROWS) -#ifdef HAVE_BAGL +#if defined(HAVE_BAGL) && !defined(HAVE_NBGL) UX_STEP_CB(ux_error, bnnn_paging, assert_exit(true), @@ -129,13 +129,21 @@ UX_FLOW(ux_error_flow, &ux_error); void __attribute__((noreturn)) assert_display_exit(void) { -#ifdef HAVE_BAGL +#if defined(HAVE_BAGL) && !defined(HAVE_NBGL) ux_flow_init(0, ux_error_flow, NULL); #endif #ifdef HAVE_NBGL +#if defined(TARGET_NANOS) +#define ICON_APP_WARNING C_icon_warning +#elif defined(TARGET_NANOX) || defined(TARGET_NANOS2) +#define ICON_APP_WARNING C_icon_warning +#else defined(TARGET_STAX) || defined(TARGET_FLEX) +#define ICON_APP_WARNING C_round_warning_64px +#endif + nbgl_useCaseChoice( - &C_round_warning_64px, "App error", assert_buffer, "Exit app", "Exit app", assert_exit); + &ICON_APP_WARNING, "App error", assert_buffer, "Exit app", "Exit app", assert_exit); #endif // Block until the user approve and the app is quit diff --git a/src/os_io_seproxyhal.c b/src/os_io_seproxyhal.c index 401f66fe..6b74d963 100644 --- a/src/os_io_seproxyhal.c +++ b/src/os_io_seproxyhal.c @@ -109,7 +109,7 @@ unsigned int os_io_seph_recv_and_process(unsigned int dont_process_ux_events); io_seph_app_t G_io_app; #endif // ! HAVE_BOLOS -#if defined(HAVE_BAGL) || defined(HAVE_NBGL) +#if defined(HAVE_BAGL) && !defined(HAVE_NBGL) ux_seph_os_and_app_t G_ux_os; #endif @@ -377,15 +377,17 @@ void io_seproxyhal_init(void) io_usb_hid_init(); #endif // HAVE_USB_APDU +#if defined(HAVE_BAGL) && !defined(HAVE_NBGL) io_seproxyhal_init_ux(); io_seproxyhal_init_button(); +#endif // HAVE_BAGL && !HAVE_NBGL #if !defined(HAVE_BOLOS) && defined(HAVE_PENDING_REVIEW_SCREEN) check_audited_app(); #endif // !defined(HAVE_BOLOS) && defined(HAVE_PENDING_REVIEW_SCREEN) } -#ifdef HAVE_BAGL +#if defined(HAVE_BAGL) && !defined(HAVE_NBGL) void io_seproxyhal_init_ux(void) { @@ -451,8 +453,12 @@ void io_seproxyhal_display_bitmap(int x, */ } } +#endif // HAVE_BAGL && !HAVE_NBGL + +#ifdef HAVE_BAGL -void io_seproxyhal_display_icon(bagl_component_t *icon_component, bagl_icon_details_t *icon_det) +void io_seproxyhal_display_icon(const bagl_component_t *icon_component, + const bagl_icon_details_t *icon_det) { bagl_component_t icon_component_mod; const bagl_icon_details_t *icon_details = (bagl_icon_details_t *) PIC(icon_det); @@ -532,7 +538,9 @@ void io_seproxyhal_display_default(const bagl_element_t *element) } } } +#endif // HAVE_BAGL +#if defined(HAVE_BAGL) && !defined(HAVE_NBGL) unsigned int bagl_label_roundtrip_duration_ms(const bagl_element_t *e, unsigned int average_char_width) { @@ -628,8 +636,7 @@ void io_seproxyhal_button_push(button_push_callback_t button_callback, unsigned button_callback(button_mask, button_same_mask_counter); } } - -#endif // HAVE_BAGL +#endif // HAVE_BAGL && !HAVE_NBGL void io_seproxyhal_setup_ticker(unsigned int interval_ms) {