From 254ee18cab45982f0f909e5811fdcafc3e3c2ab2 Mon Sep 17 00:00:00 2001 From: Charles-Edouard de la Vergne Date: Tue, 14 Jan 2025 17:28:46 +0100 Subject: [PATCH] Add useCase Keypad for Nano --- lib_nbgl/include/nbgl_layout.h | 8 + lib_nbgl/include/nbgl_obj.h | 2 +- lib_nbgl/include/nbgl_use_case.h | 46 ++++-- lib_nbgl/src/nbgl_layout_keypad_nanos.c | 207 ++++++++++++++++-------- lib_nbgl/src/nbgl_use_case_nanos.c | 174 ++++++++++++++++++++ 5 files changed, 355 insertions(+), 82 deletions(-) diff --git a/lib_nbgl/include/nbgl_layout.h b/lib_nbgl/include/nbgl_layout.h index bb3d3557..a2765fdd 100644 --- a/lib_nbgl/include/nbgl_layout.h +++ b/lib_nbgl/include/nbgl_layout.h @@ -757,6 +757,14 @@ int nbgl_layoutUpdateKeypad(nbgl_layout_t *layout, bool enableBackspace); int nbgl_layoutAddHiddenDigits(nbgl_layout_t *layout, uint8_t nbDigits); int nbgl_layoutUpdateHiddenDigits(nbgl_layout_t *layout, uint8_t index, uint8_t nbActive); +int nbgl_layoutAddKeypadContent(nbgl_layout_t *layout, + bool hidden, + uint8_t nbDigits, + const char *text); +int nbgl_layoutUpdateKeypadContent(nbgl_layout_t *layout, + bool hidden, + uint8_t nbActiveDigits, + const char *text); #endif // HAVE_SE_TOUCH #endif // NBGL_KEYPAD diff --git a/lib_nbgl/include/nbgl_obj.h b/lib_nbgl/include/nbgl_obj.h index c5b4d83b..be04ef29 100644 --- a/lib_nbgl/include/nbgl_obj.h +++ b/lib_nbgl/include/nbgl_obj.h @@ -56,11 +56,11 @@ extern "C" { #else #define KEYPAD_KEY_HEIGHT 104 #endif -#define KEYPAD_MAX_DIGITS 12 #else // HAVE_SE_TOUCH #define KEYPAD_WIDTH 114 #define KEYPAD_HEIGHT 18 #endif // HAVE_SE_TOUCH +#define KEYPAD_MAX_DIGITS 12 #ifdef HAVE_SE_TOUCH // external margin in pixels diff --git a/lib_nbgl/include/nbgl_use_case.h b/lib_nbgl/include/nbgl_use_case.h index 6408d230..527d2952 100644 --- a/lib_nbgl/include/nbgl_use_case.h +++ b/lib_nbgl/include/nbgl_use_case.h @@ -431,30 +431,42 @@ DEPRECATED void nbgl_useCaseAddressConfirmationExt(const char #define nbgl_useCaseAddressConfirmation(__address, __callback) \ nbgl_useCaseAddressConfirmationExt(__address, __callback, NULL) -#endif // HAVE_SE_TOUCH #ifdef NBGL_KEYPAD -void nbgl_useCaseKeypadDigits(const char *title, - uint8_t minDigits, - uint8_t maxDigits, - uint8_t backToken, - bool shuffled, -#ifdef HAVE_PIEZO_SOUND - tune_index_e tuneId, -#endif +void nbgl_useCaseKeypadDigits(const char *title, + uint8_t minDigits, + uint8_t maxDigits, + uint8_t backToken, + bool shuffled, + tune_index_e tuneId, nbgl_pinValidCallback_t validatePinCallback, nbgl_layoutTouchCallback_t actionCallback); -void nbgl_useCaseKeypadPIN(const char *title, - uint8_t minDigits, - uint8_t maxDigits, - uint8_t backToken, - bool shuffled, -#ifdef HAVE_PIEZO_SOUND - tune_index_e tuneId, -#endif +void nbgl_useCaseKeypadPIN(const char *title, + uint8_t minDigits, + uint8_t maxDigits, + uint8_t backToken, + bool shuffled, + tune_index_e tuneId, nbgl_pinValidCallback_t validatePinCallback, nbgl_layoutTouchCallback_t actionCallback); #endif // NBGL_KEYPAD +#else // HAVE_SE_TOUCH +#ifdef NBGL_KEYPAD +void nbgl_useCaseKeypadDigits(const char *title, + uint8_t minDigits, + uint8_t maxDigits, + bool shuffled, + nbgl_pinValidCallback_t validatePinCallback, + nbgl_callback_t backCallbackk); +void nbgl_useCaseKeypadPIN(const char *title, + uint8_t minDigits, + uint8_t maxDigits, + bool shuffled, + nbgl_pinValidCallback_t validatePinCallback, + nbgl_callback_t backCallback); +#endif // NBGL_KEYPAD +#endif // HAVE_SE_TOUCH + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/lib_nbgl/src/nbgl_layout_keypad_nanos.c b/lib_nbgl/src/nbgl_layout_keypad_nanos.c index ecc680b8..c1ecc67a 100644 --- a/lib_nbgl/src/nbgl_layout_keypad_nanos.c +++ b/lib_nbgl/src/nbgl_layout_keypad_nanos.c @@ -160,109 +160,188 @@ int nbgl_layoutUpdateKeypad(nbgl_layout_t *layout, * @return the index of digits set, to use in @ref nbgl_layoutUpdateHiddenDigits() */ int nbgl_layoutAddHiddenDigits(nbgl_layout_t *layout, uint8_t nbDigits) +{ + return nbgl_layoutAddKeypadContent(layout, true, nbDigits, NULL); +} + +/** + * @brief Updates an existing set of hidden digits, with the given configuration + * + * @param layout the current layout + * @param index index returned by @ref nbgl_layoutAddHiddenDigits() + * @param nbActive number of "active" digits (represented by discs instead of circles) + * @return >=0 if OK + */ +int nbgl_layoutUpdateHiddenDigits(nbgl_layout_t *layout, uint8_t index, uint8_t nbActive) +{ + UNUSED(index); + if (nbgl_layoutUpdateKeypadContent(layout, true, nbActive, NULL) < 0) { + return -1; + } + return 0; +} + +/** + * @brief Adds an area with a title and a placeholder for hidden digits on top of a keypad, to + * represent the entered digits as small discs. + * + * @note It must be the only object added in the layout, after the keypad itself + * + * @param layout the current layout + * @param hidden if set to true, digits appear as discs, otherwise as visible digits (given in text + * param) + * @param nbDigits number of digits to be displayed (only used if hidden is true) + * @param text only used if hidden is false + * @return the height of this area, if no error, < 0 otherwise + */ +int nbgl_layoutAddKeypadContent(nbgl_layout_t *layout, + bool hidden, + uint8_t nbDigits, + const char *text) { nbgl_layoutInternal_t *layoutInt = (nbgl_layoutInternal_t *) layout; nbgl_container_t *container; + nbgl_text_area_t *textArea; - LOG_DEBUG(LAYOUT_LOGGER, "nbgl_layoutAddHiddenDigits():\n"); + LOG_DEBUG(LAYOUT_LOGGER, "nbgl_layoutAddKeypadContent():\n"); if (layout == NULL) { return -1; } - // create a container, invisible or bordered - container = (nbgl_container_t *) nbgl_objPoolGet(CONTAINER, layoutInt->layer); - container->nbChildren = nbDigits; - container->children = nbgl_containerPoolGet(container->nbChildren, layoutInt->layer); - // 1 pixel between each icon (knowing that the effective bullets are 8px large) - container->obj.area.width = nbDigits * C_pin_bullet_empty.width + (nbDigits - 1); - container->obj.area.height = C_pin_bullet_empty.height; - // distance from top to digits is fixed to 24 px - container->obj.alignmentMarginY = 24; - container->obj.alignTo = NULL; - container->obj.alignment = TOP_MIDDLE; - - // set this new container as child of the main container - layoutAddObject(layoutInt, (nbgl_obj_t *) container); - - // create children of the container, as images (empty circles) - nbgl_objPoolGetArray(IMAGE, nbDigits, layoutInt->layer, (nbgl_obj_t **) container->children); - for (int i = 0; i < nbDigits; i++) { - nbgl_image_t *image = (nbgl_image_t *) container->children[i]; - image->buffer = &C_pin_bullet_empty; - image->foregroundColor = WHITE; - if (i > 0) { - image->obj.alignment = MID_RIGHT; - image->obj.alignTo = (nbgl_obj_t *) container->children[i - 1]; - image->obj.alignmentMarginX = 1; - } - else { - image->obj.alignment = NO_ALIGNMENT; + if (hidden) { + // create a container, invisible + container = (nbgl_container_t *) nbgl_objPoolGet(CONTAINER, layoutInt->layer); + container->nbChildren = nbDigits; + container->children = nbgl_containerPoolGet(container->nbChildren, layoutInt->layer); + // 1 pixel between each icon (knowing that the effective bullets are 8px large) + container->obj.area.width = nbDigits * C_pin_bullet_empty.width + (nbDigits - 1); + container->obj.area.height = C_pin_bullet_empty.height; + // distance from top to digits is fixed to 24 px + container->obj.alignmentMarginY = 24; + container->obj.alignTo = NULL; + container->obj.alignment = TOP_MIDDLE; + + // set this new container as child of the main container + layoutAddObject(layoutInt, (nbgl_obj_t *) container); + + // create children of the container, as images (empty circles) + nbgl_objPoolGetArray( + IMAGE, nbDigits, layoutInt->layer, (nbgl_obj_t **) container->children); + for (int i = 0; i < nbDigits; i++) { + nbgl_image_t *image = (nbgl_image_t *) container->children[i]; + image->buffer = &C_pin_bullet_empty; + image->foregroundColor = WHITE; + if (i > 0) { + image->obj.alignment = MID_RIGHT; + image->obj.alignTo = (nbgl_obj_t *) container->children[i - 1]; + image->obj.alignmentMarginX = 1; + } + else { + image->obj.alignment = NO_ALIGNMENT; + } } } - // return index of container to be modified later on - return (layoutInt->nbChildren - 1); + else { + // create text area + textArea = (nbgl_text_area_t *) nbgl_objPoolGet(TEXT_AREA, layoutInt->layer); + textArea->textColor = WHITE; + textArea->text = text; + textArea->textAlignment = CENTER; + textArea->fontId = BAGL_FONT_OPEN_SANS_EXTRABOLD_11px_1bpp; + textArea->obj.area.width = AVAILABLE_WIDTH; + textArea->obj.area.height = nbgl_getFontLineHeight(textArea->fontId); + textArea->autoHideLongLine = true; + // distance from top to digits is fixed to 20 px + textArea->obj.alignmentMarginY = 20; + textArea->obj.alignTo = NULL; + textArea->obj.alignment = TOP_MIDDLE; + // set this new text area as child of the main container + layoutAddObject(layoutInt, (nbgl_obj_t *) textArea); + } + + // return 0 + return 0; } /** * @brief Updates an existing set of hidden digits, with the given configuration * * @param layout the current layout - * @param index index returned by @ref nbgl_layoutAddHiddenDigits() - * @param nbActive number of "active" digits (represented by discs instead of circles) + * @param hidden if set to true, digits appear as discs, otherwise as visible digits (given in text + * param) + * @param nbActiveDigits number of "active" digits (represented by discs instead of circles) (only + * used if hidden is true) + * @param text only used if hidden is false + * * @return >=0 if OK */ -int nbgl_layoutUpdateHiddenDigits(nbgl_layout_t *layout, uint8_t index, uint8_t nbActive) +int nbgl_layoutUpdateKeypadContent(nbgl_layout_t *layout, + bool hidden, + uint8_t nbActiveDigits, + const char *text) { nbgl_layoutInternal_t *layoutInt = (nbgl_layoutInternal_t *) layout; nbgl_container_t *container; nbgl_image_t *image; - LOG_DEBUG(LAYOUT_LOGGER, "nbgl_layoutUpdateHiddenDigits(): nbActive = %d\n", nbActive); + LOG_DEBUG(LAYOUT_LOGGER, "nbgl_layoutUpdateHiddenDigits(): nbActive = %d\n", nbActiveDigits); if (layout == NULL) { return -1; } - // get container - container = (nbgl_container_t *) layoutInt->children[index]; - // sanity check - if ((container == NULL) || (container->obj.type != CONTAINER)) { - return -1; - } - if (nbActive > container->nbChildren) { - return -1; - } - if (nbActive == 0) { - // deactivate the first digit - image = (nbgl_image_t *) container->children[0]; - if ((image == NULL) || (image->obj.type != IMAGE)) { + if (hidden) { + // get container (3rd child of main container) + container = (nbgl_container_t *) layoutInt->children[2]; + // sanity check + if ((container == NULL) || (container->obj.type != CONTAINER)) { return -1; } - image->buffer = &C_pin_bullet_empty; - } - else { - image = (nbgl_image_t *) container->children[nbActive - 1]; - if ((image == NULL) || (image->obj.type != IMAGE)) { + if (nbActiveDigits > container->nbChildren) { return -1; } - // if the last "active" is already active, it means that we are decreasing the number of - // active otherwise we are increasing it - if (image->buffer == &C_pin_bullet_filled) { - // all digits are already active - if (nbActive == container->nbChildren) { - return 0; + if (nbActiveDigits == 0) { + // deactivate the first digit + image = (nbgl_image_t *) container->children[0]; + if ((image == NULL) || (image->obj.type != IMAGE)) { + return -1; } - // deactivate the next digit - image = (nbgl_image_t *) container->children[nbActive]; image->buffer = &C_pin_bullet_empty; } else { - image->buffer = &C_pin_bullet_filled; + image = (nbgl_image_t *) container->children[nbActiveDigits - 1]; + if ((image == NULL) || (image->obj.type != IMAGE)) { + return -1; + } + // if the last "active" is already active, it means that we are decreasing the number of + // active otherwise we are increasing it + if (image->buffer == &C_pin_bullet_filled) { + // all digits are already active + if (nbActiveDigits == container->nbChildren) { + return 0; + } + // deactivate the next digit + image = (nbgl_image_t *) container->children[nbActiveDigits]; + image->buffer = &C_pin_bullet_empty; + } + else { + image->buffer = &C_pin_bullet_filled; + } } - } - nbgl_objDraw((nbgl_obj_t *) image); + nbgl_objDraw((nbgl_obj_t *) image); + } + else { + // update main text area (second child of the main container) + nbgl_text_area_t *textArea = (nbgl_text_area_t *) layoutInt->children[2]; + if ((textArea == NULL) || (textArea->obj.type != TEXT_AREA)) { + return -1; + } + textArea->text = text; + nbgl_objDraw((nbgl_obj_t *) textArea); + } return 0; } + #endif // NBGL_KEYPAD #endif // HAVE_SE_TOUCH diff --git a/lib_nbgl/src/nbgl_use_case_nanos.c b/lib_nbgl/src/nbgl_use_case_nanos.c index b91f62c7..69609f8a 100644 --- a/lib_nbgl/src/nbgl_use_case_nanos.c +++ b/lib_nbgl/src/nbgl_use_case_nanos.c @@ -71,9 +71,24 @@ typedef struct HomeContext_s { nbgl_callback_t quitCallback; } HomeContext_t; +#ifdef NBGL_KEYPAD +typedef struct KeypadContext_s { + uint8_t pinEntry[8]; + uint8_t pinLen; + uint8_t pinMinDigits; + uint8_t pinMaxDigits; + nbgl_layout_t *layoutCtx; + bool hidden; + uint8_t keypadIndex; + nbgl_pinValidCallback_t validatePin; + nbgl_callback_t backCallback; +} KeypadContext_t; +#endif + typedef enum { NONE_USE_CASE, REVIEW_USE_CASE, + KEYPAD_USE_CASE, REVIEW_BLIND_SIGN_USE_CASE, ADDRESS_REVIEW_USE_CASE, STREAMING_BLIND_SIGN_START_REVIEW_USE_CASE, @@ -102,6 +117,9 @@ typedef struct UseCaseContext_s { ConfirmContext_t confirm; HomeContext_t home; ActionContext_t action; +#ifdef NBGL_KEYPAD + KeypadContext_t keypad; +#endif }; } UseCaseContext_t; @@ -1122,6 +1140,103 @@ static void useCaseReview(ContextType_t type, displayReviewPage(FORWARD_DIRECTION); } +#ifdef NBGL_KEYPAD +static void setPinCodeText(void) +{ + bool enableValidate = false; + bool enableBackspace = true; + + // pin can be validated when min digits is entered + enableValidate = (context.keypad.pinLen >= context.keypad.pinMinDigits); + // backspace is disabled when no digit is entered and back vallback is not provided + enableBackspace = (context.keypad.pinLen > 0) || (context.keypad.backCallback != NULL); + nbgl_layoutUpdateKeypadContent(context.keypad.layoutCtx, + context.keypad.hidden, + context.keypad.pinLen, + (const char *) context.keypad.pinEntry); + nbgl_layoutUpdateKeypad( + context.keypad.layoutCtx, context.keypad.keypadIndex, enableValidate, enableBackspace); + nbgl_layoutDraw(context.keypad.layoutCtx); + nbgl_refresh(); +} + +// called when a key is touched on the keypad +static void keypadCallback(char touchedKey) +{ + switch (touchedKey) { + case BACKSPACE_KEY: + if (context.keypad.pinLen > 0) { + context.keypad.pinLen--; + context.keypad.pinEntry[context.keypad.pinLen] = 0; + } + else if (context.keypad.backCallback != NULL) { + context.keypad.backCallback(); + break; + } + setPinCodeText(); + break; + + case VALIDATE_KEY: + context.keypad.validatePin(context.keypad.pinEntry, context.keypad.pinLen); + break; + + default: + if ((touchedKey >= 0x30) && (touchedKey < 0x40)) { + if (context.keypad.pinLen < context.keypad.pinMaxDigits) { + context.keypad.pinEntry[context.keypad.pinLen] = touchedKey; + context.keypad.pinLen++; + } + setPinCodeText(); + } + break; + } +} + +// called to create a keypad, with either hidden or visible digits +static void keypadGenericUseCase(const char *title, + uint8_t minDigits, + uint8_t maxDigits, + bool shuffled, + bool hidden, + nbgl_pinValidCallback_t validatePinCallback, + nbgl_callback_t backCallback) +{ + nbgl_layoutDescription_t layoutDescription = {0}; + int status = -1; + + // reset the keypad context + memset(&context, 0, sizeof(KeypadContext_t)); + context.type = KEYPAD_USE_CASE; + context.currentPage = 0; + context.nbPages = 1; + context.keypad.validatePin = validatePinCallback; + context.keypad.backCallback = backCallback; + context.keypad.pinMinDigits = minDigits; + context.keypad.pinMaxDigits = maxDigits; + context.keypad.hidden = hidden; + context.keypad.layoutCtx = nbgl_layoutGet(&layoutDescription); + + // add keypad + status = nbgl_layoutAddKeypad(context.keypad.layoutCtx, keypadCallback, title, shuffled); + if (status < 0) { + return; + } + context.keypad.keypadIndex = status; + // add digits + status = nbgl_layoutAddKeypadContent(context.keypad.layoutCtx, hidden, maxDigits, ""); + if (status < 0) { + return; + } + + nbgl_layoutDraw(context.keypad.layoutCtx); + if (context.keypad.backCallback != NULL) { + // force backspace to be visible at first digit, to be used as quit + nbgl_layoutUpdateKeypad(context.keypad.layoutCtx, context.keypad.keypadIndex, false, true); + } + nbgl_refresh(); +} +#endif // NBGL_KEYPAD + /********************** * GLOBAL FUNCTIONS **********************/ @@ -1901,5 +2016,64 @@ void nbgl_useCaseConfirm(const char *message, displayConfirm(FORWARD_DIRECTION); } +#ifdef NBGL_KEYPAD +/** + * @brief draws a standard keypad modal page with visible digits. It contains + * - a navigation bar at the top + * - a title for the pin code + * - a visible digit entry + * - the keypad at the bottom + * + * @note callbacks allow to control the behavior. + * backspace and validation button are shown/hidden automatically + * + * @param title string to set in pin code title + * @param minDigits pin minimum number of digits + * @param maxDigits maximum number of digits to be displayed + * @param shuffled if set to true, digits are shuffled in keypad + * @param validatePinCallback function calledto validate the pin code + * @param backCallback callback called on backspace is "pressed" in first digit + */ +void nbgl_useCaseKeypadDigits(const char *title, + uint8_t minDigits, + uint8_t maxDigits, + bool shuffled, + nbgl_pinValidCallback_t validatePinCallback, + nbgl_callback_t backCallback) +{ + keypadGenericUseCase( + title, minDigits, maxDigits, shuffled, false, validatePinCallback, backCallback); +} + +/** + * @brief draws a standard keypad modal page with hidden digits. It contains + * - a navigation bar at the top + * - a title for the pin code + * - a hidden digit entry + * - the keypad at the bottom + * + * @note callbacks allow to control the behavior. + * backspace and validation button are shown/hidden automatically + * + * @param title string to set in pin code title + * @param minDigits pin minimum number of digits + * @param maxDigits maximum number of digits to be displayed + * @param backToken token used with actionCallback (0 if unused)) + * @param shuffled if set to true, digits are shuffled in keypad + * @param validatePinCallback function calledto validate the pin code + * @param backCallback callback called on backspace is "pressed" in first digit + */ +void nbgl_useCaseKeypadPIN(const char *title, + uint8_t minDigits, + uint8_t maxDigits, + bool shuffled, + nbgl_pinValidCallback_t validatePinCallback, + nbgl_callback_t backCallback) +{ + keypadGenericUseCase( + title, minDigits, maxDigits, shuffled, true, validatePinCallback, backCallback); +} +#endif // NBGL_KEYPAD + #endif // HAVE_SE_TOUCH #endif // NBGL_USE_CASE