From e671ce64d32d89eb6d7679278686c97f1db643af Mon Sep 17 00:00:00 2001 From: Xavier Chapron Date: Fri, 2 Feb 2024 18:36:25 +0100 Subject: [PATCH] wip lns --- Makefile | 14 +- sdk/nbgl_wrapper.c | 17 +- sdk_lib_nbgl/include/nbgl_fonts.h | 2 + sdk_lib_nbgl/include/nbgl_layout.h | 19 +- sdk_lib_nbgl/include/nbgl_obj.h | 2 + sdk_lib_nbgl/include/nbgl_step.h | 14 +- sdk_lib_nbgl/src/nbgl_buttons.c | 5 + sdk_lib_nbgl/src/nbgl_fonts_lns.c | 289 +++++++++++++++++++++++++ sdk_lib_nbgl/src/nbgl_step.c | 106 ++++++++- sdk_lib_nbgl/src/nbgl_step_lns.c | 288 ++++++++++++++++++++++++ sdk_lib_nbgl/src/nbgl_use_case_nanos.c | 28 ++- sdk_lib_ux_stax/doc/mainpage.dox | 9 + sdk_lib_ux_stax/ux.c | 157 ++++++++++++++ sdk_lib_ux_stax/ux.h | 229 ++++++++++++++++++++ sdk_lib_ux_stax/ux_loc.h | 62 ++++++ src/app_main.c | 9 + src/ui/bagl_display.c | 173 --------------- src/ui/menu_bagl.c | 64 ------ src/ui/menu_nbgl.c | 20 ++ src/ui/nbgl_display_address.c | 1 - 20 files changed, 1256 insertions(+), 252 deletions(-) create mode 100644 sdk_lib_nbgl/src/nbgl_fonts_lns.c create mode 100644 sdk_lib_nbgl/src/nbgl_step_lns.c create mode 100644 sdk_lib_ux_stax/doc/mainpage.dox create mode 100644 sdk_lib_ux_stax/ux.c create mode 100644 sdk_lib_ux_stax/ux.h create mode 100644 sdk_lib_ux_stax/ux_loc.h delete mode 100644 src/ui/bagl_display.c delete mode 100644 src/ui/menu_bagl.c diff --git a/Makefile b/Makefile index 04b66402..ab249aeb 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ ifeq ($(BOLOS_SDK),) $(error Environment variable BOLOS_SDK is not set) endif - +DISABLE_UI = 1 include $(BOLOS_SDK)/Makefile.defines ######################################## @@ -35,6 +35,18 @@ APPVERSION = "$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P)" # Application source files APP_SOURCE_PATH += src sdk +ifeq ($(TARGET_NAME),TARGET_NANOS) +DEFINES += HAVE_NBGL NBGL_USE_CASE NBGL_STEP +DEFINES += TARGET_NANOS=1 +DEFINES += HAVE_BAGL +DEFINES += BAGL_WIDTH=128 BAGL_HEIGHT=32 +DISABLE_STANDARD_BAGL_UX_FLOW = 1 +SDK_SOURCE_PATH += lib_bagl +APP_SOURCE_PATH += sdk_lib_nbgl/src sdk_lib_ux_stax + +INCLUDES_PATH += sdk_lib_nbgl/include sdk_lib_ux_stax +CFLAGS += -Wno-macro-redefined +endif # Application icons following guidelines: # https://developers.ledger.com/docs/embedded-app/design-requirements/#device-icon diff --git a/sdk/nbgl_wrapper.c b/sdk/nbgl_wrapper.c index 659df602..5a03e438 100644 --- a/sdk/nbgl_wrapper.c +++ b/sdk/nbgl_wrapper.c @@ -5,7 +5,7 @@ #include "os.h" #include "glyphs.h" - +#ifndef TARGET_NANOS static nbgl_pageInfoLongPress_t infoLongPress; static const nbgl_layoutTagValueList_t *review_tagValueList; @@ -46,6 +46,7 @@ static void tx_review_continue(void) { nbgl_useCaseStaticReview(review_tagValueList, &infoLongPress, "Reject transaction", tx_review_choice); } +#endif void nbgl_useCaseTransactionReview( const nbgl_layoutTagValueList_t *tagValueList, @@ -55,6 +56,7 @@ void nbgl_useCaseTransactionReview( const char *finish_page_text, /*unused on Nano*/ nbgl_choiceCallback_t choice_callback) { +#ifndef TARGET_NANOS review_tagValueList = tagValueList; review_icon = icon; review_finish_page_text = finish_page_text; @@ -66,8 +68,15 @@ void nbgl_useCaseTransactionReview( "Reject transaction", tx_review_continue, tx_ask_transaction_rejection_confirmation); +#else + UNUSED(reviewSubTitle); + UNUSED(finish_page_text); + nbgl_useCaseStaticReview(tagValueList, icon, reviewTitle, "Approve", "Reject", choice_callback); +#endif + } +#ifndef TARGET_NANOS static void addr_review_continue(void) { nbgl_useCaseAddressConfirmation(review_address, review_choice_callback); } @@ -75,6 +84,7 @@ static void addr_review_continue(void) { static void addr_review_rejection(void) { review_choice_callback(false); } +#endif void nbgl_useCaseAddressReview( const char *address, @@ -83,6 +93,7 @@ void nbgl_useCaseAddressReview( const char *reviewSubTitle, nbgl_choiceCallback_t choice_callback) { +#ifndef TARGET_NANOS review_address = address; review_choice_callback = choice_callback; @@ -92,4 +103,8 @@ void nbgl_useCaseAddressReview( "Cancel", addr_review_continue, addr_review_rejection); +#else + UNUSED(reviewSubTitle); + nbgl_useCaseAddressConfirmation(icon, reviewTitle, address, choice_callback); +#endif } diff --git a/sdk_lib_nbgl/include/nbgl_fonts.h b/sdk_lib_nbgl/include/nbgl_fonts.h index 689fd11d..213e73fc 100644 --- a/sdk_lib_nbgl/include/nbgl_fonts.h +++ b/sdk_lib_nbgl/include/nbgl_fonts.h @@ -133,7 +133,9 @@ typedef enum { BAGL_FONT_INTER_REGULAR_28px_1bpp = 14u, BAGL_FONT_INTER_SEMIBOLD_28px_1bpp = 15u, BAGL_FONT_INTER_MEDIUM_36px_1bpp = 16u, +#ifndef TARGET_NANOS BAGL_FONT_LAST // MUST ALWAYS BE THE LAST, FOR AUTOMATED INVALID VALUE CHECKS +#endif } nbgl_font_id_e; typedef struct nbgl_unicode_ctx_s { diff --git a/sdk_lib_nbgl/include/nbgl_layout.h b/sdk_lib_nbgl/include/nbgl_layout.h index 29020d14..81b88d44 100644 --- a/sdk_lib_nbgl/include/nbgl_layout.h +++ b/sdk_lib_nbgl/include/nbgl_layout.h @@ -61,7 +61,11 @@ typedef void (*nbgl_layoutTouchCallback_t)(int token, uint8_t index); * @param layout layout concerned by the event * @param event type of button event */ +#ifndef TARGET_NANOS typedef void (*nbgl_layoutButtonCallback_t)(nbgl_layout_t *layout, nbgl_buttonEvent_t event); +#else +typedef void (*nbgl_layoutButtonCallback_t)(nbgl_buttonEvent_t event); +#endif /** * @brief This structure contains info to build a navigation bar at the bottom of the screen @@ -392,6 +396,7 @@ typedef struct { /********************** * GLOBAL PROTOTYPES **********************/ +#ifndef TARGET_NANOS nbgl_layout_t *nbgl_layoutGet(const nbgl_layoutDescription_t *description); int nbgl_layoutAddCenteredInfo(nbgl_layout_t *layout, const nbgl_layoutCenteredInfo_t *info); int nbgl_layoutAddProgressBar(nbgl_layout_t *layout, const nbgl_layoutProgressBar_t *barLayout); @@ -525,7 +530,19 @@ int nbgl_layoutUpdateHiddenDigits(nbgl_layout_t *layout, uint8_t index, uint8_t /* generic functions */ int nbgl_layoutDraw(nbgl_layout_t *layout); int nbgl_layoutRelease(nbgl_layout_t *layout); - +#else // TARGET_NANOS +void nbgl_screenHandler(uint32_t intervaleMs); +void nbgl_refresh(void); +void nbgl_layoutGet(const nbgl_layoutDescription_t *description); +void nbgl_layoutAddNavigation(nbgl_layoutNavigation_t *info); +void nbgl_layoutAddText(const char *text, + int line, + bool bold, + bool centered); +void nbgl_layoutAddCenteredInfo(const nbgl_layoutCenteredInfo_t *info); +void nbgl_layoutDraw(void); +void nbgl_layoutRelease(void); +#endif /********************** * MACROS **********************/ diff --git a/sdk_lib_nbgl/include/nbgl_obj.h b/sdk_lib_nbgl/include/nbgl_obj.h index 02ce9606..8341fd70 100644 --- a/sdk_lib_nbgl/include/nbgl_obj.h +++ b/sdk_lib_nbgl/include/nbgl_obj.h @@ -13,7 +13,9 @@ extern "C" { #include "nbgl_types.h" #include "nbgl_fonts.h" +#ifndef TARGET_NANOS #include "ux_loc.h" +#endif /********************* * INCLUDES diff --git a/sdk_lib_nbgl/include/nbgl_step.h b/sdk_lib_nbgl/include/nbgl_step.h index a4ed419e..4edde5c8 100644 --- a/sdk_lib_nbgl/include/nbgl_step.h +++ b/sdk_lib_nbgl/include/nbgl_step.h @@ -84,7 +84,7 @@ typedef uint8_t nbgl_stepPosition_t; /********************** * GLOBAL PROTOTYPES **********************/ - +#ifndef TARGET_NANOS nbgl_step_t nbgl_stepDrawText(nbgl_stepPosition_t pos, nbgl_stepButtonCallback_t onActionCallback, nbgl_screenTickerConfiguration_t *ticker, @@ -103,7 +103,17 @@ nbgl_step_t nbgl_stepDrawMenuList(nbgl_stepMenuListCallback_t onActionCall bool modal); uint8_t nbgl_stepGetMenuListCurrent(nbgl_step_t step); int nbgl_stepRelease(nbgl_step_t step); - +#else +void nbgl_screenDraw(nbgl_stepPosition_t pos, + nbgl_stepButtonCallback_t onActionCallback, + nbgl_screenTickerConfiguration_t *ticker, + const char *text, + const char *subText, + const nbgl_icon_details_t *icon, // TODO change for bagl_icon_details_t + bool centered, + bool text_bold, + bool horizontal_nav); +#endif /********************** * MACROS **********************/ diff --git a/sdk_lib_nbgl/src/nbgl_buttons.c b/sdk_lib_nbgl/src/nbgl_buttons.c index b10e9207..29e737a1 100644 --- a/sdk_lib_nbgl/src/nbgl_buttons.c +++ b/sdk_lib_nbgl/src/nbgl_buttons.c @@ -132,6 +132,7 @@ void nbgl_buttonsHandler(uint8_t buttonState, uint32_t currentTimeMs) button_mask &= ~RELEASED_MASK; } +#ifndef TARGET_NANOS nbgl_screen_t *topScreen = (nbgl_screen_t *) nbgl_screenGetTop(); if ((topScreen != NULL) && (topScreen->buttonCallback != NULL)) { nbgl_buttonEvent_t event = maskToEvent(button_mask); @@ -139,6 +140,10 @@ void nbgl_buttonsHandler(uint8_t buttonState, uint32_t currentTimeMs) topScreen->buttonCallback(topScreen, event); } } +#else + nbgl_buttonEvent_t event = maskToEvent(button_mask); + layout_buttonCallback(event); +#endif } void nbgl_buttonsReset(void) diff --git a/sdk_lib_nbgl/src/nbgl_fonts_lns.c b/sdk_lib_nbgl/src/nbgl_fonts_lns.c new file mode 100644 index 00000000..bad58ca0 --- /dev/null +++ b/sdk_lib_nbgl/src/nbgl_fonts_lns.c @@ -0,0 +1,289 @@ + +/** + * @file nbgl_fonts.c + * Implementation of fonts array + */ +#include +#include +#include +#include "nbgl_fonts.h" + +#define PAGING_FORMAT_NB 1 + +// We implement a light mecanism 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 char nanos_characters_width[96] = { + 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 */ +}; + +// This function is used to retrieve the length of a string (expressed in bytes) delimited with a +// boundary width (expressed in pixels). +uint8_t se_get_cropped_length(const char *text, + uint8_t text_length, + uint32_t width_limit_in_pixels, + uint8_t text_format) +{ + char current_char; + uint8_t length; + uint32_t current_width_in_pixels = 0; + + for (length = 0; length < text_length; length++) { + current_char = text[length]; + + if ((text_format & PAGING_FORMAT_NB) == PAGING_FORMAT_NB) { + // Bold. + current_width_in_pixels + += nanos_characters_width[current_char - NANOS_FIRST_CHAR] & 0x0F; + } + else { + // Regular. + current_width_in_pixels + += (nanos_characters_width[current_char - NANOS_FIRST_CHAR] >> 0x04) & 0x0F; + } + + // We stop the processing when we reached the limit. + if (current_width_in_pixels > width_limit_in_pixels) { + break; + } + } + + return length; +} + +// This function is used to retrieve the width of a line of text. +static uint32_t se_compute_line_width_light(const char *text, + uint8_t text_length, + uint8_t text_format) +{ + char current_char; + uint32_t line_width = 0; + + // We parse the characters of the input text on all the input length. + while (text_length--) { + current_char = *text; + + if (current_char < NANOS_FIRST_CHAR || current_char > NANOS_LAST_CHAR) { + if (current_char == '\n' || current_char == '\r') { + break; + } + } + else { + // We retrieve the character width, and the paging format indicates whether we are + // processing bold characters or not. + if ((text_format & PAGING_FORMAT_NB) == PAGING_FORMAT_NB) { + // Bold. + line_width += nanos_characters_width[current_char - NANOS_FIRST_CHAR] & 0x0F; + } + else { + // Regular. + line_width + += (nanos_characters_width[current_char - NANOS_FIRST_CHAR] >> 0x04) & 0x0F; + } + } + text++; + } + return line_width; +} + +/** + * @brief compute the len of the given text (in bytes) fitting in the given maximum nb lines, with + * the given maximum width + * + * @param fontId font ID + * @param text input UTF-8 string, possibly multi-line + * @param maxWidth maximum width in bytes, if text is greater than that the parsing is escaped + * @param maxNbLines maximum number of lines, if text is greater than that the parsing is escaped + * @param len (output) consumed bytes in text fitting in maxWidth + * @param wrapping if true, lines are split on separators like spaces, \n... + * + * @return true if maxNbLines is reached, false otherwise + * + */ +bool nbgl_getTextMaxLenInNbLines(nbgl_font_id_e fontId, + const char *text, + uint16_t maxWidth, + uint16_t maxNbLines, + uint16_t *len, + bool wrapping) +{ + uint16_t textLen = strlen(text); + const char *origText = text; + + while ((textLen) && (maxNbLines > 0)) { + + uint8_t line_len = se_get_cropped_length(text, textLen, maxWidth, 0); + + text += line_len; + textLen -= line_len; + maxNbLines--; + } + *len = text - origText; + return (maxNbLines == 0); +} + +/** + * @brief compute the number of lines of the given text fitting in the given maxWidth + * + * @param fontId font ID + * @param text UTF-8 text to get the number of lines from + * @param maxWidth maximum width in which the text must fit + * @param wrapping if true, lines are split on separators like spaces, \n... + * @return the number of lines in the given text + */ +uint16_t nbgl_getTextNbLinesInWidth(nbgl_font_id_e fontId, + const char *text, + uint16_t maxWidth, + bool wrapping) +{ + uint16_t textLen = strlen(text); + const char *origText = text; + uint16_t nbLines = 0; + + while (textLen) { + uint8_t char_width; + + uint8_t line_len = se_get_cropped_length(text, textLen, maxWidth, 0); + + text += line_len; + textLen -= line_len; + nbLines++; + } + return nbLines; +} + +/** + * @brief compute the number of pages of nbLinesPerPage lines per page of the given text fitting in + * the given maxWidth + * + * @param fontId font ID + * @param text UTF-8 text to get the number of pages from + * @param nbLinesPerPage number of lines in a page + * @param maxWidth maximum width in which the text must fit + * @return the number of pages in the given text + */ +uint8_t nbgl_getTextNbPagesInWidth(nbgl_font_id_e fontId, + const char *text, + uint8_t nbLinesPerPage, + uint16_t maxWidth) +{ + return (nbgl_getTextNbLinesInWidth(fontId, text, maxWidth, false) + nbLinesPerPage - 1 )/ nbLinesPerPage; +} + +/** + * @brief return the height of the given multiline text, with the given font. + * + * @param fontId font ID + * @param text text to get the height from + * @param maxWidth maximum width in which the text must fit + * @param wrapping if true, lines are split on separators like spaces, \n... + * @return the height in pixels + */ +uint16_t nbgl_getTextHeightInWidth(nbgl_font_id_e fontId, + const char *text, + uint16_t maxWidth, + bool wrapping) +{ + const nbgl_font_t *font = nbgl_getFont(fontId); + return (nbgl_getTextNbLinesInWidth(fontId, text, maxWidth, wrapping) * font->line_height); +} diff --git a/sdk_lib_nbgl/src/nbgl_step.c b/sdk_lib_nbgl/src/nbgl_step.c index ac96ec37..9f13c239 100644 --- a/sdk_lib_nbgl/src/nbgl_step.c +++ b/sdk_lib_nbgl/src/nbgl_step.c @@ -3,7 +3,7 @@ * @brief Implementation of predefined pages management for Applications */ -#ifdef NBGL_STEP +#ifdef NBGL_STEP2 /********************* * INCLUDES *********************/ @@ -21,7 +21,13 @@ #define TMP_STRING_MAX_LEN 24 ///< Maximum number of layers for steps, cannot be greater than max number of layout layers +#ifdef TARGET_NANOS +#undef NB_MAX_LINES +#define NB_MAX_LINES 2 +#define NB_MAX_LAYERS 1 +#else #define NB_MAX_LAYERS 3 +#endif /********************** * TYPEDEFS @@ -83,14 +89,22 @@ static StepContext_t contexts[NB_MAX_LAYERS]; * STATIC PROTOTYPES **********************/ +#ifdef TARGET_NANOS +static void actionCallback(nbgl_buttonEvent_t event); +static void menuListActionCallback(nbgl_buttonEvent_t event); +#else static void actionCallback(nbgl_layout_t *layout, nbgl_buttonEvent_t event); static void menuListActionCallback(nbgl_layout_t *layout, nbgl_buttonEvent_t event); +#endif // returns a non-used step context from the contexts[] array, or NULL if not found static StepContext_t *getFreeContext(StepStype_t type, bool modal) { StepContext_t *ctx = NULL; +#ifdef TARGET_NANOS + ctx = &contexts[0]; +#else if (!modal) { // Index 0 is reserved for background ctx = &contexts[0]; @@ -105,6 +119,7 @@ static StepContext_t *getFreeContext(StepStype_t type, bool modal) i++; } } +#endif if (ctx == NULL) { LOG_FATAL(STEP_LOGGER, "getFreeContext(): no available context\n"); } @@ -116,6 +131,7 @@ static StepContext_t *getFreeContext(StepStype_t type, bool modal) return ctx; } +#ifndef TARGET_NANOS // returns the step context from the contexts[] array matching with the given layout handler, or // NULL if not found static StepContext_t *getContextFromLayout(nbgl_layout_t layout) @@ -134,6 +150,7 @@ static StepContext_t *getContextFromLayout(nbgl_layout_t layout) } return ctx; } +#endif // from the current details context, return a pointer on the details at the given page static const char *getTextPageAt(StepContext_t *ctx, uint8_t textPage) @@ -247,11 +264,13 @@ static void displayTextPage(StepContext_t *ctx, uint8_t textPage) layoutDescription.ticker.tickerCallback = ctx->ticker.tickerCallback; layoutDescription.ticker.tickerIntervale = ctx->ticker.tickerIntervale; layoutDescription.ticker.tickerValue = ctx->ticker.tickerValue; - ctx->layout = nbgl_layoutGet(&layoutDescription); navInfo.indication = getNavigationInfo( ctx->textContext.pos, ctx->textContext.nbPages, ctx->textContext.currentPage); +#ifndef TARGET_NANOS + ctx->layout = nbgl_layoutGet(&layoutDescription); + if (ctx->textContext.subTxtStart == NULL) { nbgl_layoutAddText(ctx->layout, txt, NULL, ctx->textContext.style); } @@ -270,16 +289,54 @@ static void displayTextPage(StepContext_t *ctx, uint8_t textPage) } } if (navInfo.indication != NO_ARROWS) { - nbgl_layoutAddNavigation(ctx->layout, &navInfo); + nbgl_layoutAddNavigation(&navInfo); } nbgl_layoutDraw(ctx->layout); nbgl_refresh(); +#else + nbgl_layoutGet(&layoutDescription); + + if (ctx->textContext.subTxtStart == NULL) { + // TODO handle multiples lines + nbgl_layoutAddText(txt, 1, ctx->textContext.style == BOLD_TEXT1_INFO, true); + } + else { + if (ctx->textContext.nbPages == 1) { + nbgl_layoutAddText(ctx->textContext.txtStart, 1, ctx->textContext.style == BOLD_TEXT1_INFO, true); + nbgl_layoutAddText(txt, 1, ctx->textContext.style == BOLD_TEXT1_INFO, true); + } + else { + SPRINTF(ctx->textContext.tmpString, + "%s (%d/%d)", + ctx->textContext.txtStart, + ctx->textContext.currentPage + 1, + ctx->textContext.nbPages); + nbgl_layoutAddText(ctx->textContext.tmpString, 1, ctx->textContext.style == BOLD_TEXT1_INFO, true); + nbgl_layoutAddText(txt, 1, ctx->textContext.style == BOLD_TEXT1_INFO, true); + } + } + if (navInfo.indication != NO_ARROWS) { + nbgl_layoutAddNavigation(&navInfo); + } + nbgl_layoutDraw(); + nbgl_refresh(); +#endif } // callback on key touch +#ifdef TARGET_NANOS +static void actionCallback(nbgl_buttonEvent_t event) +#else static void actionCallback(nbgl_layout_t *layout, nbgl_buttonEvent_t event) +#endif { - StepContext_t *ctx = getContextFromLayout(layout); + StepContext_t *ctx; + +#ifdef TARGET_NANOS + ctx = &contexts[0]; +#else + ctx = getContextFromLayout(layout); +#endif if (!ctx) { return; @@ -319,7 +376,13 @@ static void displayMenuList(StepContext_t *ctx) layoutDescription.ticker.tickerIntervale = ctx->ticker.tickerIntervale; layoutDescription.ticker.tickerValue = ctx->ticker.tickerValue; +#ifdef TARGET_NANOS + nbgl_layoutGet(&layoutDescription); +#else ctx->layout = nbgl_layoutGet(&layoutDescription); +#endif + +#if 0 nbgl_layoutAddMenuList(ctx->layout, list); if (list->nbChoices > 1) { nbgl_layoutNavigation_t navInfo = {.direction = VERTICAL_NAV}; @@ -337,12 +400,24 @@ static void displayMenuList(StepContext_t *ctx) } nbgl_layoutDraw(ctx->layout); nbgl_refresh(); +#endif } // callback on key touch +#ifdef TARGET_NANOS +static void menuListActionCallback(nbgl_buttonEvent_t event) +#else static void menuListActionCallback(nbgl_layout_t *layout, nbgl_buttonEvent_t event) +#endif { - StepContext_t *ctx = getContextFromLayout(layout); + StepContext_t *ctx; + +#ifdef TARGET_NANOS + ctx = &contexts[0]; +#else + ctx = getContextFromLayout(layout); +#endif + if (!ctx) { return; } @@ -368,6 +443,8 @@ static void menuListActionCallback(nbgl_layout_t *layout, nbgl_buttonEvent_t eve * GLOBAL FUNCTIONS **********************/ + + /** * @brief draws a text type step, that can be multi-pages, depending of the length of text and * subText. The navigation arrows are displayed depending of the given position of this step in a @@ -437,7 +514,7 @@ nbgl_step_t nbgl_stepDrawText(nbgl_stepPosition_t pos, * @param pos position of this step in the flow (first, last, single, not_first_nor_last) * @param onActionCallback common callback for all actions on this page * @param ticker ticker configuration, set to NULL to disable it - * @param info all information about the cenetered info to be displayed + * @param info all information about the centered info to be displayed * @param modal if true, means this step shall be displayed on top of existing one * @return the step context (or NULL if error) */ @@ -471,6 +548,15 @@ nbgl_step_t nbgl_stepDrawCenteredInfo(nbgl_stepPosition_t pos, navInfo.indication = getNavigationInfo( ctx->textContext.pos, ctx->textContext.nbPages, ctx->textContext.currentPage); +#ifdef TARGET_NANOS + nbgl_layoutGet(&layoutDescription); + nbgl_layoutAddCenteredInfo(info); + if (navInfo.indication != NO_ARROWS) { + nbgl_layoutAddNavigation(&navInfo); + } + nbgl_layoutDraw(); + nbgl_refresh(); +#else ctx->layout = nbgl_layoutGet(&layoutDescription); nbgl_layoutAddCenteredInfo(ctx->layout, info); if (navInfo.indication != NO_ARROWS) { @@ -478,6 +564,7 @@ nbgl_step_t nbgl_stepDrawCenteredInfo(nbgl_stepPosition_t pos, } nbgl_layoutDraw(ctx->layout); nbgl_refresh(); +#endif LOG_DEBUG(STEP_LOGGER, "nbgl_stepDrawCenteredInfo(): step = %p\n", ctx); return (nbgl_step_t) ctx; @@ -552,9 +639,14 @@ int nbgl_stepRelease(nbgl_step_t step) if (!ctx) { return -1; } - ret = nbgl_layoutRelease((nbgl_layout_t *) ctx->layout); +#ifdef TARGET_NANOS + nbgl_layoutRelease(); + ret = 0; +#else + ret = nbgl_layoutRelease((nbgl_layout_t *) ctx->layout); ctx->layout = NULL; +#endif return ret; } diff --git a/sdk_lib_nbgl/src/nbgl_step_lns.c b/sdk_lib_nbgl/src/nbgl_step_lns.c new file mode 100644 index 00000000..ce14398a --- /dev/null +++ b/sdk_lib_nbgl/src/nbgl_step_lns.c @@ -0,0 +1,288 @@ +/** + * @file nbgl_step.c + * @brief Implementation of predefined pages management for Applications + */ + +#ifdef NBGL_STEP +/********************* + * INCLUDES + *********************/ +#include +#include + +#include "glyphs.h" +#include "os_pic.h" +#include "lib_ux/include/ux.h" +#include "nbgl_step.h" +#include "nbgl_layout.h" + + +/********************* + * DEFINES + *********************/ +// string to store the title for a multi-pages text+subtext +#define TMP_STRING_MAX_LEN 24 + +///< Maximum number of layers for steps, cannot be greater than max number of layout layers +#ifdef TARGET_NANOS +#undef NB_MAX_LINES +#define NB_MAX_LINES 2 +#define NB_MAX_LAYERS 1 +#else +#define NB_MAX_LAYERS 3 +#endif + +static nbgl_layoutButtonCallback_t ctx_callback; +static nbgl_screenTickerConfiguration_t ctx_ticker; +static bool active; +static bool displayed; + + +void nbgl_process_ux_displayed_event(void) +{ + displayed = true; + PRINTF("Displayed\n"); +} + +static void wait_displayed(void) +{ + displayed = false; + while (!displayed) { + // - Looping on os_io_seph_recv_and_process(0); + // - This will send a general_status and then wait for an event. + // - Upon event reception this will call io_seproxyhal_handle_event() + // - On some case this will call io_event() which usually forward the + // event to the UX lib. + io_seproxyhal_spi_recv(G_io_seproxyhal_spi_buffer, sizeof(G_io_seproxyhal_spi_buffer), 0); \ + io_seproxyhal_handle_event(); + } +} + +static void display_clear(void) { + bagl_element_t element = { + .component = { + .type = BAGL_RECTANGLE, + .userid = 0, + .x = 0, + .y = 0, + .width = 128, + .height = 32, + .stroke = 0, + .radius = 0, + .fill = BAGL_FILL, + .fgcolor = 0x000000, + .bgcolor = 0xFFFFFF, + .icon_id = 0, + .font_id = 0, + }, + .text = NULL, + }; + + io_seproxyhal_display_default(&element); + wait_displayed(); + PRINTF("screen reset\n"); +} + +static void display_text_line(const char *text, bool bold, bool centered, uint8_t x, uint8_t y) { + bagl_element_t element = { + .component = { + .type = BAGL_LABELINE, + .userid = 0, + .x = x, + .y = y, + .height = 32, + .stroke = 0, + .radius = 0, + .fill = 0, + .fgcolor = 0xFFFFFF, + .bgcolor = 0x000000, + .icon_id = 0, + }, + .text = text + }; + + element.component.x = x; + element.component.y = y; + element.component.width = 128 - 2 * x; + + 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.font_id |= BAGL_FONT_ALIGNMENT_CENTER; + } + io_seproxyhal_display_default(&element); + wait_displayed(); + PRINTF("text displayed\n"); + +} + +static void display_icon(const bagl_icon_details_t *icon_det, uint8_t x, uint8_t y, uint8_t width, uint8_t height) { + bagl_component_t component = { + .type = BAGL_ICON, + .userid = 0, + .stroke = 0, + .radius = 0, + .fill = 0, + .fgcolor = 0xFFFFFF, + .bgcolor = 0x000000, + .font_id = 0, + .icon_id = 0, + }; + + component.x = x; + component.y = y; + component.width = width; + component.height = height; + + io_seproxyhal_display_icon(&component, icon_det); + wait_displayed(); + PRINTF("icon displayed\n"); +} + +// utility function to compute navigation arrows +static void display_nav_info(nbgl_stepPosition_t pos, + uint8_t nbPages, + uint8_t currentPage, + bool horizontal_nav) +{ + bool right = false; + bool left = false; + + if (nbPages > 1) { + if (currentPage > 0) { + left = true; + } + if (currentPage < (nbPages - 1)) { + right = true; + } + } + if (pos == FIRST_STEP) { + right = true; + } + else if (pos == LAST_STEP) { + left = true; + } + else if (pos == NEITHER_FIRST_NOR_LAST_STEP) { + right = true; + left = true; + } + + if (horizontal_nav) { + if (left) { + display_icon(&C_icon_left, 2, 12, 4, 7); + } + if (right) { + display_icon(&C_icon_right, 122, 12, 4, 7); + } + } else { + if (left) { + display_icon(&C_icon_up, 2, 12, 7, 4); + } + if (right) { + display_icon(&C_icon_down, 122, 12, 7, 4); + } + } +} + +void layout_buttonCallback(nbgl_buttonEvent_t buttonEvent) +{ + if (!active) { + return; + } + + if (ctx_callback != NULL) { + ctx_callback(buttonEvent); + } +} + +void nbgl_screenHandler(uint32_t intervaleMs) +{ +#if 0 + // ensure a screen exists + if (nbScreensOnStack == 0) { + return; + } + // call ticker callback of top of stack if active and not expired yet (for a non periodic) + if ((topOfStack->ticker.tickerCallback != NULL) && (topOfStack->ticker.tickerValue != 0)) { + topOfStack->ticker.tickerValue -= MIN(topOfStack->ticker.tickerValue, intervaleMs); + if (topOfStack->ticker.tickerValue == 0) { + // rearm if intervale is not null, and call the registered function + topOfStack->ticker.tickerValue = topOfStack->ticker.tickerIntervale; + topOfStack->ticker.tickerCallback(); + } + } +#endif +} + +void nbgl_screenDraw(nbgl_stepPosition_t pos, + nbgl_stepButtonCallback_t onActionCallback, + nbgl_screenTickerConfiguration_t *ticker, + const char *text, + const char *subText, + const nbgl_icon_details_t *icon, + bool centered, + bool text_bold, + bool horizontal_nav) +{ + PRINTF("nbgl_screenDraw %d %d %d\n", centered, icon != NULL, subText != NULL); + if (icon != NULL) + PRINTF("icon <%p>\n", PIC(icon)); + if (text != NULL) + PRINTF("text <%s>\n", PIC(text)); + if (subText != NULL) + PRINTF("subText <%s>\n", PIC(subText)); + active = false; + + ctx_callback = (nbgl_layoutButtonCallback_t) PIC(onActionCallback); + if (ticker != NULL) { + memcpy(&ctx_ticker, ticker, sizeof(ctx_ticker)); + } else { + memset(&ctx_ticker, 0, sizeof(ctx_ticker)); + } + + display_clear(); + + uint8_t text_x = 6; + if (icon != NULL) { + if (centered) { + // Consider text is single line and subText is Null + display_icon(icon, 56, 2, 16, 16); + } else { + text_x = 41; + display_icon(icon, 16, 8, 16, 16); + } + } + + if ((icon != NULL) && (centered)) { + // Consider text is single line and subText is Null + display_text_line(text, text_bold, centered, text_x, 28); + } else { + if (subText != NULL) { + // Consider text exist and is single line + display_text_line(text, text_bold, centered, text_x, 12); + + // TODO handle paging + // For now simply display subText, paging is not handled + display_text_line(subText, false, centered, text_x, 26); + } else { + // TODO handle `\n` and paging? or align in middle if really single line? + // Consider text exist and is single line + display_text_line(text, text_bold, centered, text_x, 12); + } + } + + display_nav_info(pos, 1, 1, horizontal_nav); +} + +void nbgl_refresh(void) { + active = true; + if (!io_seproxyhal_spi_is_status_sent()) { + io_seproxyhal_general_status(); + } +} + +#endif // NBGL_STEP diff --git a/sdk_lib_nbgl/src/nbgl_use_case_nanos.c b/sdk_lib_nbgl/src/nbgl_use_case_nanos.c index 7740308f..7ed2b70f 100644 --- a/sdk_lib_nbgl/src/nbgl_use_case_nanos.c +++ b/sdk_lib_nbgl/src/nbgl_use_case_nanos.c @@ -82,8 +82,11 @@ static UseCaseContext_t context; /********************** * STATIC FUNCTIONS **********************/ - +#ifndef TARGET_NANOS static void buttonCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event); +#else +static void buttonCallback(nbgl_buttonEvent_t event); +#endif static void displayReviewPage(nbgl_stepPosition_t pos); static void displayHomePage(nbgl_stepPosition_t pos); @@ -120,6 +123,7 @@ static void drawStep(nbgl_stepPosition_t pos, pos |= GET_POS_OF_STEP(context.currentPage, context.nbPages); } +#ifndef TARGET_NANOS if (icon == NULL) { context.stepCtx = nbgl_stepDrawText(pos, buttonCallback, NULL, txt, subTxt, BOLD_TEXT1_INFO, false); @@ -133,13 +137,22 @@ static void drawStep(nbgl_stepPosition_t pos, info.style = BOLD_TEXT1_INFO; context.stepCtx = nbgl_stepDrawCenteredInfo(pos, buttonCallback, NULL, &info, false); } +#else + nbgl_screenDraw(pos, buttonCallback, NULL, txt, subTxt, icon, true, true, true); +#endif } +#ifndef TARGET_NANOS static void buttonCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event) +#else +static void buttonCallback(nbgl_buttonEvent_t event) +#endif { nbgl_stepPosition_t pos; +#ifndef TARGET_NANOS UNUSED(stepCtx); +#endif // create text_area for main text if (event == BUTTON_LEFT_PRESSED) { if (context.currentPage > 0) { @@ -306,7 +319,7 @@ void nbgl_useCaseHome(const char *appName, context.home.quitCallback = quitCallback; if (tagline == NULL) { - snprintf(appDescription, APP_DESCRIPTION_MAX_LEN, "%s\nis ready", appName); + snprintf(appDescription, APP_DESCRIPTION_MAX_LEN, "%s", appName); context.home.tagline = appDescription; } else { @@ -503,7 +516,18 @@ void nbgl_useCaseSpinner(const char *text) // pageContext = nbgl_pageDrawSpinner(NULL, (const char*)text); UNUSED(text); nbgl_refresh(); + // TODO } +void nbgl_useCaseStatus(const char *message, bool isSuccess, nbgl_callback_t quitCallback) +{ + UNUSED(message); + UNUSED(isSuccess); + UNUSED(quitCallback); + quitCallback(); + // TODO +} + + #endif // HAVE_SE_TOUCH #endif // NBGL_USE_CASE diff --git a/sdk_lib_ux_stax/doc/mainpage.dox b/sdk_lib_ux_stax/doc/mainpage.dox new file mode 100644 index 00000000..84b54bf1 --- /dev/null +++ b/sdk_lib_ux_stax/doc/mainpage.dox @@ -0,0 +1,9 @@ +/** @page ux_stax_mainpage User Experience library for Stax + +@section mainpage_intro Introduction + +This page describes the UX (User eXperience) library, available on \b Stax product. + +@note TO BE COMPLETED + +*/ diff --git a/sdk_lib_ux_stax/ux.c b/sdk_lib_ux_stax/ux.c new file mode 100644 index 00000000..25816ddb --- /dev/null +++ b/sdk_lib_ux_stax/ux.c @@ -0,0 +1,157 @@ + +/******************************************************************************* + * 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_touch.h" +#include "nbgl_buttons.h" +#include "os_io.h" +#ifndef HAVE_BOLOS +// number of 100ms ticks since the start-up of the app +static uint32_t nbTicks; + +/** + * @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(bool ignoring_app_if_ux_busy) +{ + 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); + if (G_ux_params.len == BOLOS_UX_REDRAW) { + // enable drawing according to UX decision + //nbgl_objAllowDrawing(true); + //nbgl_screenRedraw(); + nbgl_refresh(); + } + else if (!ignoring_app_if_ux_busy + || ((G_ux_params.len != BOLOS_UX_IGNORE) && (G_ux_params.len != BOLOS_UX_CONTINUE))) { + return true; + } + return false; +} + +#ifdef HAVE_SE_TOUCH +static nbgl_touchStatePosition_t pos; + +/** + * @brief Process finger event. + * @note Application's finger event handler is called only if the ux app does not deny it (finger + * event caught by BOLOS UX page). + * + * @param seph_packet received SEPH packet + */ +void ux_process_finger_event(uint8_t seph_packet[]) +{ + bool displayEnabled = ux_forward_event(true); + // enable/disable drawing according to UX decision + //nbgl_objAllowDrawing(displayEnabled); + + // if the event is not fully consumed by UX, use it for NBGL + if (displayEnabled) { + pos.state = (seph_packet[3] == SEPROXYHAL_TAG_FINGER_EVENT_TOUCH) ? PRESSED : RELEASED; + pos.x = (seph_packet[4] << 8) + seph_packet[5]; + pos.y = (seph_packet[6] << 8) + seph_packet[7]; + nbgl_touchHandler(&pos, nbTicks * 100); + nbgl_refresh(); + } +} +#else // HAVE_SE_TOUCH +/** + * @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(uint8_t seph_packet[]) +{ + bool displayEnabled = ux_forward_event(true); + // enable/disable drawing according to UX decision + //nbgl_objAllowDrawing(displayEnabled); + + // if the event is not fully consumed by UX, use it for NBGL + if (displayEnabled) { + uint8_t buttons_state = seph_packet[3] >> 1; + nbgl_buttonsHandler(buttons_state, nbTicks * 100); + nbgl_refresh(); + } +} +#endif // HAVE_SE_TOUCH + +/** + * @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) +{ + return; + nbTicks++; + // forward to UX + bool displayEnabled = ux_forward_event(true); + + // enable/disable drawing according to UX decision + //nbgl_objAllowDrawing(displayEnabled); + // update ticker in NBGL + nbgl_screenHandler(100); + + if (!displayEnabled) { + return; + } + +#ifdef HAVE_SE_TOUCH + // handle touch only if detected as pressed in last touch message + if (pos.state == PRESSED) { + io_touch_info_t touch_info; + touch_get_last_info(&touch_info); + pos.state = (touch_info.state == SEPROXYHAL_TAG_FINGER_EVENT_TOUCH) ? PRESSED : RELEASED; + pos.x = touch_info.x; + pos.y = touch_info.y; + // Send current touch position to nbgl + nbgl_touchHandler(&pos, nbTicks * 100); + } +#endif // HAVE_SE_TOUCH + nbgl_refresh(); +} + +/** + * Forwards the event to UX + */ +void ux_process_default_event(void) +{ + // forward to UX + ux_forward_event(false); +} + +/** + * Forwards the event to UX + */ +void ux_process_displayed_event(void) +{ + // forward to UX + nbgl_process_ux_displayed_event(); + //ux_forward_event(false); +} + +#endif // HAVE_BOLOS diff --git a/sdk_lib_ux_stax/ux.h b/sdk_lib_ux_stax/ux.h new file mode 100644 index 00000000..6cdcf6bc --- /dev/null +++ b/sdk_lib_ux_stax/ux.h @@ -0,0 +1,229 @@ + +/******************************************************************************* + * 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 + +#if defined(HAVE_BOLOS) +#include "bolos_privileged_ux.h" +#endif // HAVE_BOLOS + +#include "os_math.h" +#include "os_ux.h" +#include "os_task.h" +#include "nbgl_screen.h" +#include "nbgl_touch.h" + +#include + +#ifdef TARGET_NANOS +#include "bagl.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; + +#if defined(HAVE_INDEXED_STRINGS) + // Nameless union, to be able to access one member of the union or the other. + // No space won when using index with bagl_element_e, but headaches are avoided :) + union { + const char *text; + UX_LOC_STRINGS_INDEX index; + }; +#else // defined(HAVE_INDEXED_STRINGS) + const char *text; +#endif // defined(HAVE_INDEXED_STRINGS) +}; + +void io_seproxyhal_display_icon(bagl_component_t *icon_component, + bagl_icon_details_t *icon_details); + +// Helper function that give a realistic timing of scrolling for label with text larger than screen +unsigned int bagl_label_roundtrip_duration_ms(const bagl_element_t *e, + unsigned int average_char_width); +unsigned int bagl_label_roundtrip_duration_ms_buf(const bagl_element_t *e, + const char *str, + unsigned int average_char_width); + +#ifndef BUTTON_FAST_THRESHOLD_CS +#define BUTTON_FAST_THRESHOLD_CS 8 // x100MS +#endif // BUTTON_FAST_THRESHOLD_CS +#ifndef BUTTON_FAST_ACTION_CS +#define BUTTON_FAST_ACTION_CS 3 // x100MS +#endif // BUTTON_FAST_ACTION_CS + +typedef unsigned int (*button_push_callback_t)(unsigned int button_mask, + unsigned int button_mask_counter); +// flag set when fast threshold is reached and above +#define BUTTON_EVT_FAST 0x40000000UL +#define BUTTON_EVT_RELEASED 0x80000000UL +#endif + +#define BUTTON_LEFT 1 +#define BUTTON_RIGHT 2 + +typedef void (*asynchmodal_end_callback_t)(unsigned int ux_status); + +/** + * Common structure for applications to perform asynchronous UX aside IO operations + */ +typedef struct ux_state_s ux_state_t; + +struct ux_state_s { + bolos_task_status_t exit_code; + bool validate_pin_from_dashboard; // set to true when BOLOS_UX_VALIDATE_PIN is received from + // Dashboard task + + asynchmodal_end_callback_t asynchmodal_end_callback; + + char string_buffer[128]; +}; + +extern ux_state_t G_ux; +#if !defined(APP_UX) +extern bolos_ux_params_t G_ux_params; + +extern void ux_process_finger_event(uint8_t seph_packet[]); +extern void ux_process_button_event(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); +#endif // !defined(APP_UX) + +/** + * Initialize the user experience structure + */ +#ifdef TARGET_NANOS +#define UX_INIT() +#else +#define UX_INIT() nbgl_objInit(); +#endif + +#ifdef HAVE_BOLOS +// to be used only by hal_io.c in BOLOS, for compatibility +#define UX_FORWARD_EVENT_REDRAWCB(bypasspincheck, \ + ux_params, \ + ux, \ + os_ux, \ + os_sched_last_status, \ + callback, \ + redraw_cb, \ + ignoring_app_if_ux_busy) \ + ux_params.ux_id = BOLOS_UX_EVENT; \ + ux_params.len = 0; \ + os_ux(&ux_params); \ + ux_params.len = os_sched_last_status(TASK_BOLOS_UX); \ + if (ux.asynchmodal_end_callback \ + && os_ux_get_status(BOLOS_UX_ASYNCHMODAL_PAIRING_REQUEST) != 0) { \ + asynchmodal_end_callback_t cb = ux.asynchmodal_end_callback; \ + ux.asynchmodal_end_callback = NULL; \ + cb(os_ux_get_status(BOLOS_UX_ASYNCHMODAL_PAIRING_REQUEST)); \ + } +#endif // HAVE_BOLOS + +/** + * 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 + */ +#ifdef HAVE_SE_TOUCH +#define UX_BUTTON_PUSH_EVENT(seph_packet) +#else // HAVE_SE_TOUCH +#define UX_BUTTON_PUSH_EVENT(seph_packet) ux_process_button_event(seph_packet) +#endif // HAVE_SE_TOUCH + +/** + * forward the finger_event to the os ux handler. if not used by it, it will + * be used by App controls + */ +#ifdef HAVE_SE_TOUCH +#define UX_FINGER_EVENT(seph_packet) ux_process_finger_event(seph_packet) +#else // HAVE_SE_TOUCH +#define UX_FINGER_EVENT(seph_packet) +#endif // HAVE_SE_TOUCH + +/** + * 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() +#define UX_DISPLAYED_EVENT(...) \ + ux_process_displayed_event(); \ + return 1 /* this one is really messy, sorry... */ + +// discriminated from io to allow for different memory placement +typedef struct ux_seph_s { + unsigned int button_mask; + unsigned int button_same_mask_counter; +#ifdef HAVE_BOLOS + unsigned int ux_id; + unsigned int ux_status; +#endif // HAVE_BOLOS +} ux_seph_os_and_app_t; + +#ifdef HAVE_BACKGROUND_IMG +SYSCALL PERMISSION(APPLICATION_FLAG_BOLOS_UX) +uint8_t *fetch_background_img(bool allow_candidate); +SYSCALL PERMISSION(APPLICATION_FLAG_BOLOS_UX) +bolos_err_t delete_background_img(void); +#endif + +extern ux_seph_os_and_app_t G_ux_os; + +void io_seproxyhal_request_mcu_status(void); +#ifndef TARGET_NANOS +void io_seproxyhal_power_off(bool criticalBattery); +#endif + +#if defined(HAVE_LANGUAGE_PACK) +const char *get_ux_loc_string(UX_LOC_STRINGS_INDEX index); +void bolos_ux_select_language(uint16_t language); +void bolos_ux_refresh_language(void); + +typedef struct ux_loc_language_pack_infos { + unsigned char available; + +} UX_LOC_LANGUAGE_PACK_INFO; + +// To populate infos about language packs +SYSCALL PERMISSION(APPLICATION_FLAG_BOLOS_UX) void list_language_packs( + UX_LOC_LANGUAGE_PACK_INFO *packs PLENGTH(NB_LANG * sizeof(UX_LOC_LANGUAGE_PACK_INFO))); +SYSCALL PERMISSION(APPLICATION_FLAG_BOLOS_UX) const LANGUAGE_PACK *get_language_pack( + unsigned int language); +#endif // defined(HAVE_LANGUAGE_PACK) + +#include "glyphs.h" diff --git a/sdk_lib_ux_stax/ux_loc.h b/sdk_lib_ux_stax/ux_loc.h new file mode 100644 index 00000000..c3cd93ed --- /dev/null +++ b/sdk_lib_ux_stax/ux_loc.h @@ -0,0 +1,62 @@ + +/******************************************************************************* + * 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 "nbgl_types.h" + +#if defined(HAVE_LANGUAGE_PACK) +// Structure used for language packs: +typedef struct language_pack { + uint32_t target_id; // 0x33000004:NanoX, 0x31100004:NanoS 1.5.x etc + uint16_t language; // Language contained in this pack (7000) => use a u_int16_t; +- we may store some CRC, to check language pack's integrity. +*/ + +typedef unsigned short UX_LOC_STRINGS_INDEX; + +#endif // defined(HAVE_LANGUAGE_PACK) diff --git a/src/app_main.c b/src/app_main.c index c48bd60c..98319441 100644 --- a/src/app_main.c +++ b/src/app_main.c @@ -40,6 +40,15 @@ void app_main() { io_init(); + //for (int i = 0; i < 3; i++) { + // // - Looping on os_io_seph_recv_and_process(0); + // // - This will send a general_status and then wait for an event. + // // - Upon event reception this will call io_seproxyhal_handle_event() + // // - On some case this will call io_event() which usually forward the + // // event to the UX lib. + // os_io_seph_recv_and_process(0); + //} + ui_menu_main(); // Initialize the NVM data if required diff --git a/src/ui/bagl_display.c b/src/ui/bagl_display.c deleted file mode 100644 index cf60c3b4..00000000 --- a/src/ui/bagl_display.c +++ /dev/null @@ -1,173 +0,0 @@ -/***************************************************************************** - * Ledger App Boilerplate. - * (c) 2020 Ledger SAS. - * - * 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. - *****************************************************************************/ - -#ifdef HAVE_BAGL - -#include // bool -#include // memset - -#include "os.h" -#include "ux.h" -#include "glyphs.h" -#include "io.h" -#include "bip32.h" -#include "format.h" - -#include "display.h" -#include "constants.h" -#include "../globals.h" -#include "../sw.h" -#include "../address.h" -#include "action/validate.h" -#include "../transaction/types.h" -#include "../menu.h" - -static action_validate_cb g_validate_callback; -static char g_amount[30]; -static char g_address[43]; - -// Validate/Invalidate public key and go back to home -static void ui_action_validate_pubkey(bool choice) { - validate_pubkey(choice); - ui_menu_main(); -} - -// Validate/Invalidate transaction and go back to home -static void ui_action_validate_transaction(bool choice) { - validate_transaction(choice); - ui_menu_main(); -} - -// Step with icon and text -UX_STEP_NOCB(ux_display_confirm_addr_step, pn, {&C_icon_eye, "Confirm Address"}); -// Step with title/text for address -UX_STEP_NOCB(ux_display_address_step, - bnnn_paging, - { - .title = "Address", - .text = g_address, - }); -// Step with approve button -UX_STEP_CB(ux_display_approve_step, - pb, - (*g_validate_callback)(true), - { - &C_icon_validate_14, - "Approve", - }); -// Step with reject button -UX_STEP_CB(ux_display_reject_step, - pb, - (*g_validate_callback)(false), - { - &C_icon_crossmark, - "Reject", - }); - -// FLOW to display address: -// #1 screen: eye icon + "Confirm Address" -// #2 screen: display address -// #3 screen: approve button -// #4 screen: reject button -UX_FLOW(ux_display_pubkey_flow, - &ux_display_confirm_addr_step, - &ux_display_address_step, - &ux_display_approve_step, - &ux_display_reject_step); - -int ui_display_address() { - if (G_context.req_type != CONFIRM_ADDRESS || G_context.state != STATE_NONE) { - G_context.state = STATE_NONE; - return io_send_sw(SW_BAD_STATE); - } - memset(g_address, 0, sizeof(g_address)); - uint8_t address[ADDRESS_LEN] = {0}; - if (!address_from_pubkey(G_context.pk_info.raw_public_key, address, sizeof(address))) { - return io_send_sw(SW_DISPLAY_ADDRESS_FAIL); - } - - if (format_hex(address, sizeof(address), g_address, sizeof(g_address)) == -1) { - return io_send_sw(SW_DISPLAY_ADDRESS_FAIL); - } - - g_validate_callback = &ui_action_validate_pubkey; - - ux_flow_init(0, ux_display_pubkey_flow, NULL); - return 0; -} - -// Step with icon and text -UX_STEP_NOCB(ux_display_review_step, - pnn, - { - &C_icon_eye, - "Review", - "Transaction", - }); -// Step with title/text for amount -UX_STEP_NOCB(ux_display_amount_step, - bnnn_paging, - { - .title = "Amount", - .text = g_amount, - }); - -// FLOW to display transaction information: -// #1 screen : eye icon + "Review Transaction" -// #2 screen : display amount -// #3 screen : display destination address -// #4 screen : approve button -// #5 screen : reject button -UX_FLOW(ux_display_transaction_flow, - &ux_display_review_step, - &ux_display_address_step, - &ux_display_amount_step, - &ux_display_approve_step, - &ux_display_reject_step); - -int ui_display_transaction() { - if (G_context.req_type != CONFIRM_TRANSACTION || G_context.state != STATE_PARSED) { - G_context.state = STATE_NONE; - return io_send_sw(SW_BAD_STATE); - } - - memset(g_amount, 0, sizeof(g_amount)); - char amount[30] = {0}; - if (!format_fpu64(amount, - sizeof(amount), - G_context.tx_info.transaction.value, - EXPONENT_SMALLEST_UNIT)) { - return io_send_sw(SW_DISPLAY_AMOUNT_FAIL); - } - snprintf(g_amount, sizeof(g_amount), "BOL %.*s", sizeof(amount), amount); - PRINTF("Amount: %s\n", g_amount); - - memset(g_address, 0, sizeof(g_address)); - - if (format_hex(G_context.tx_info.transaction.to, ADDRESS_LEN, g_address, sizeof(g_address)) == - -1) { - return io_send_sw(SW_DISPLAY_ADDRESS_FAIL); - } - - g_validate_callback = &ui_action_validate_transaction; - - ux_flow_init(0, ux_display_transaction_flow, NULL); - - return 0; -} - -#endif diff --git a/src/ui/menu_bagl.c b/src/ui/menu_bagl.c deleted file mode 100644 index 91062a6b..00000000 --- a/src/ui/menu_bagl.c +++ /dev/null @@ -1,64 +0,0 @@ -/***************************************************************************** - * Ledger App Boilerplate. - * (c) 2020 Ledger SAS. - * - * 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. - *****************************************************************************/ - -#ifdef HAVE_BAGL - -#include "os.h" -#include "ux.h" -#include "glyphs.h" - -#include "../globals.h" -#include "menu.h" - -UX_STEP_NOCB(ux_menu_ready_step, pnn, {&C_app_boilerplate_16px, "Boilerplate", "is ready"}); -UX_STEP_NOCB(ux_menu_version_step, bn, {"Version", APPVERSION}); -UX_STEP_CB(ux_menu_about_step, pb, ui_menu_about(), {&C_icon_certificate, "About"}); -UX_STEP_VALID(ux_menu_exit_step, pb, os_sched_exit(-1), {&C_icon_dashboard_x, "Quit"}); - -// FLOW for the main menu: -// #1 screen: ready -// #2 screen: version of the app -// #3 screen: about submenu -// #4 screen: quit -UX_FLOW(ux_menu_main_flow, - &ux_menu_ready_step, - &ux_menu_version_step, - &ux_menu_about_step, - &ux_menu_exit_step, - FLOW_LOOP); - -void ui_menu_main() { - if (G_ux.stack_count == 0) { - ux_stack_push(); - } - - ux_flow_init(0, ux_menu_main_flow, NULL); -} - -UX_STEP_NOCB(ux_menu_info_step, bn, {"Boilerplate App", "(c) 2020 Ledger"}); -UX_STEP_CB(ux_menu_back_step, pb, ui_menu_main(), {&C_icon_back, "Back"}); - -// FLOW for the about submenu: -// #1 screen: app info -// #2 screen: back button to main menu -UX_FLOW(ux_menu_about_flow, &ux_menu_info_step, &ux_menu_back_step, FLOW_LOOP); - -void ui_menu_about() { - ux_flow_init(0, ux_menu_about_flow, NULL); -} - -#endif diff --git a/src/ui/menu_nbgl.c b/src/ui/menu_nbgl.c index 33109094..5e4a3c44 100644 --- a/src/ui/menu_nbgl.c +++ b/src/ui/menu_nbgl.c @@ -34,6 +34,7 @@ void app_quit(void) { os_sched_exit(-1); } +#ifndef TARGET_NANOS // home page definition void ui_menu_main(void) { // This parameter shall be set to false if the settings page contains only information @@ -157,4 +158,23 @@ void ui_menu_settings() { controls_callback); } +#else +// home page definition +void ui_menu_main(void) { +// This parameter shall be set to false if the settings page contains only information +// about the application (version , developer name, ...). It shall be set to +// true if the settings page also contains user configurable parameters related to the +// operation of the application. +#define SETTINGS_BUTTON_ENABLED (true) + + nbgl_useCaseHome(APPNAME, + &C_app_boilerplate_16px, + APPVERSION, + NULL, + NULL, + app_quit); +} + +#endif + #endif diff --git a/src/ui/nbgl_display_address.c b/src/ui/nbgl_display_address.c index cfa7a4a5..b6e54d06 100644 --- a/src/ui/nbgl_display_address.c +++ b/src/ui/nbgl_display_address.c @@ -64,7 +64,6 @@ void ui_display_address_status(ui_ret_e ret) { // If an APDU is received before the timer ends, a new UX might be shown on // the screen, and therefore the modal will be dismissed and its callback // will never be called. - if (ret == UI_RET_APPROVED) { nbgl_useCaseStatus("ADDRESS\nVERIFIED", true, ui_menu_main); } else if (ret == UI_RET_REJECTED) {