From 1a45a695a38691f1fb92790bb817dbc4d79f7dde Mon Sep 17 00:00:00 2001 From: Xavier Chapron Date: Wed, 7 Feb 2024 11:13:23 +0100 Subject: [PATCH 01/13] Makefiles: Remove DISABLE_UI feature (cherry picked from commit 2b4d13134fc2571660a269b6e5eac4a293a06090) --- Makefile.defines | 3 --- Makefile.rules | 6 ++---- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/Makefile.defines b/Makefile.defines index 3d539c60e..0158c8c8b 100644 --- a/Makefile.defines +++ b/Makefile.defines @@ -133,13 +133,10 @@ else LDFLAGS += -nostartfiles endif -DISABLE_UI ?= 0 -ifeq ($(DISABLE_UI), 0) ifeq ($(TARGET_NAME),TARGET_NANOS) DEFINES += HAVE_BAGL DEFINES += BAGL_WIDTH=128 BAGL_HEIGHT=32 endif -endif ifeq ($(TARGET_NAME),TARGET_NANOX) # Screen is directly connected to the SE diff --git a/Makefile.rules b/Makefile.rules index 98ebb81f8..f69f82a79 100644 --- a/Makefile.rules +++ b/Makefile.rules @@ -16,10 +16,8 @@ #******************************************************************************* # temporary redef, to ensure wider compliance of the SDK with pre-1.6 apps -ifeq ($(DISABLE_UI),0) - ifeq ($(TARGET_NAME),$(filter $(TARGET_NAME),TARGET_NANOS TARGET_NANOX TARGET_NANOS2)) - SDK_SOURCE_PATH += lib_bagl lib_ux - endif +ifeq ($(TARGET_NAME),$(filter $(TARGET_NAME),TARGET_NANOS TARGET_NANOX TARGET_NANOS2)) + SDK_SOURCE_PATH += lib_bagl lib_ux endif define uniq = From 480681f65fd4a3f5498297cb4b7c0216abe88ae5 Mon Sep 17 00:00:00 2001 From: Xavier Chapron Date: Tue, 6 Feb 2024 18:11:12 +0100 Subject: [PATCH 02/13] ux.h/ux_loc.h: Add redirection to avoid name collision (cherry picked from commit b106c7b5791e3eff0f3bcacfdb6f59c6a75bab8b) --- include/ux.h | 26 ++++++++++++++++++++++++++ lib_ux/include/{ux.h => ux_bagl.h} | 0 2 files changed, 26 insertions(+) create mode 100644 include/ux.h rename lib_ux/include/{ux.h => ux_bagl.h} (100%) diff --git a/include/ux.h b/include/ux.h new file mode 100644 index 000000000..c927ab5a8 --- /dev/null +++ b/include/ux.h @@ -0,0 +1,26 @@ + +/***************************************************************************** + * (c) 2024 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. + *****************************************************************************/ + +#pragma once + +#ifdef HAVE_BAGL +#include "ux_bagl.h" +#endif + +#ifdef HAVE_NBGL +#include "ux_nbgl.h" +#endif diff --git a/lib_ux/include/ux.h b/lib_ux/include/ux_bagl.h similarity index 100% rename from lib_ux/include/ux.h rename to lib_ux/include/ux_bagl.h From 4cf50cb6bfc0cde4aa2d2b13c5bf8e2792733635 Mon Sep 17 00:00:00 2001 From: Xavier Chapron Date: Wed, 7 Feb 2024 16:29:36 +0100 Subject: [PATCH 03/13] os_io_seproxyhal: Introduce new IO_RECEIVE_DATA flag for io_exchange() (cherry picked from commit aaded6ed5bc4afa90da423692552a8169fd36eaf) --- include/os_io.h | 3 +- src/os_io_seproxyhal.c | 83 +++++++++++++++++++++++++----------------- 2 files changed, 52 insertions(+), 34 deletions(-) diff --git a/include/os_io.h b/include/os_io.h index 2b2858d9a..abcc624e0 100644 --- a/include/os_io.h +++ b/include/os_io.h @@ -38,7 +38,8 @@ extern unsigned char G_io_apdu_buffer[IO_APDU_BUFFER_SIZE]; #define IO_RETURN_AFTER_TX 0x20 #define IO_ASYNCH_REPLY 0x10 // avoid apdu state reset if tx_len == 0 when we're expected to reply #define IO_FINISHED 0x08 // inter task communication value -#define IO_FLAGS 0xF8 +#define IO_CONTINUE_RX 0x04 +#define IO_FLAGS 0xFC unsigned short io_exchange(unsigned char channel_and_flags, unsigned short tx_len); typedef enum { diff --git a/src/os_io_seproxyhal.c b/src/os_io_seproxyhal.c index 5e235a01a..1385f8210 100644 --- a/src/os_io_seproxyhal.c +++ b/src/os_io_seproxyhal.c @@ -1412,48 +1412,46 @@ unsigned short io_exchange(unsigned char channel, unsigned short tx_len) } } - if (!(channel & IO_ASYNCH_REPLY)) { - // already received the data of the apdu when received the whole apdu - if ((channel & (CHANNEL_APDU | IO_RECEIVE_DATA)) - == (CHANNEL_APDU | IO_RECEIVE_DATA)) { - // return apdu data - header - return G_io_app.apdu_length - 5; + // When IO_CONTINUE_RX is used we don't reset potentially already received APDU. + // Instead we directly process them. + // Use case is: + // - First APDU received (call to io_exchange()) + // - UX waiting for user approval + // - First APDU response sent (call to io_exchange() with flag IO_RETURN_AFTER_TX) + // - UX with transient message like ("Transaction signed") + // This need to loop on os_io_seph_recv_and_process() to process tick and UX + // events, but a second APDU can be received here too. + // - Then a call to io_exchange() with flag IO_CONTINUE_RX will allow processing + // the APDU now that the app is ready. + // + // Note that a received APDU will be cleared out (reset of G_io_app.apdu_state / + // G_io_app.apdu_media / G_io_app.apdu_length) during the APDU response sending. + // Therefore there is no risk to process an APDU twice. + if (!(channel & IO_CONTINUE_RX)) { + if (!(channel & IO_ASYNCH_REPLY)) { + // already received the data of the apdu when received the whole apdu + if ((channel & (CHANNEL_APDU | IO_RECEIVE_DATA)) + == (CHANNEL_APDU | IO_RECEIVE_DATA)) { + // return apdu data - header + return G_io_app.apdu_length - 5; + } + + // reply has ended, proceed to next apdu reception (reset status only after + // asynch reply) + G_io_app.apdu_state = APDU_IDLE; + G_io_app.apdu_media = IO_APDU_MEDIA_NONE; } - // reply has ended, proceed to next apdu reception (reset status only after asynch - // reply) - G_io_app.apdu_state = APDU_IDLE; - G_io_app.apdu_media = IO_APDU_MEDIA_NONE; + // reset the received apdu length + G_io_app.apdu_length = 0; } - // reset the received apdu length - G_io_app.apdu_length = 0; - // ensure ready to receive an event (after an apdu processing with asynch flag, it may // occur if the channel is not correctly managed) // until a new whole CAPDU is received for (;;) { - io_seproxyhal_general_status(); - // wait until a SPI packet is available - // NOTE: on ST31, dual wait ISO & RF (ISO instead of SPI) - rx_len = io_seproxyhal_spi_recv( - G_io_seproxyhal_spi_buffer, sizeof(G_io_seproxyhal_spi_buffer), 0); - - // can't process split TLV, continue - if (rx_len < 3 - || rx_len - != U2(G_io_seproxyhal_spi_buffer[1], G_io_seproxyhal_spi_buffer[2]) - + 3U) { - LOG("invalid TLV format\n"); - G_io_app.apdu_state = APDU_IDLE; - G_io_app.apdu_length = 0; - continue; - } - - io_seproxyhal_handle_event(); - - // An apdu has been received asynchroneously. + // An apdu has been received asynchronously. if (G_io_app.apdu_state != APDU_IDLE && G_io_app.apdu_length > 0) { if (os_perso_isonboarded() == BOLOS_TRUE && os_global_pin_is_validated() != BOLOS_TRUE) { @@ -1473,6 +1471,25 @@ unsigned short io_exchange(unsigned char channel, unsigned short tx_len) return G_io_app.apdu_length; } + + io_seproxyhal_general_status(); + // wait until a SPI packet is available + // NOTE: on ST31, dual wait ISO & RF (ISO instead of SPI) + rx_len = io_seproxyhal_spi_recv( + G_io_seproxyhal_spi_buffer, sizeof(G_io_seproxyhal_spi_buffer), 0); + + // can't process split TLV, continue + if (rx_len < 3 + || rx_len + != U2(G_io_seproxyhal_spi_buffer[1], G_io_seproxyhal_spi_buffer[2]) + + 3U) { + LOG("invalid TLV format\n"); + G_io_app.apdu_state = APDU_IDLE; + G_io_app.apdu_length = 0; + continue; + } + + io_seproxyhal_handle_event(); } break; From cd40d499a19b4bd65e60ab48c93e18cad1dd9259 Mon Sep 17 00:00:00 2001 From: Xavier Chapron Date: Wed, 7 Feb 2024 17:44:11 +0100 Subject: [PATCH 04/13] lib_standard_app/io.c: Send RAPDU immediately and use IO_CONTINUE_RX flag (cherry picked from commit e255f0ff7e6bfc0e2cd9dc2d008a28ace81c3641) --- Makefile.standard_app | 6 ++++++ lib_standard_app/io.c | 10 +++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Makefile.standard_app b/Makefile.standard_app index dc41951c9..69b4aa2e7 100644 --- a/Makefile.standard_app +++ b/Makefile.standard_app @@ -124,6 +124,12 @@ ifneq ($(DISABLE_STANDARD_APP_FILES), 1) SDK_SOURCE_PATH += lib_standard_app endif +ifneq ($(DISABLE_STANDARD_APP_SYNC_RAPDU), 1) +ifneq ($(TARGET_NAME),TARGET_NANOS) +DEFINES += STANDARD_APP_SYNC_RAPDU +endif +endif + ##################################################################### # NBGL # ##################################################################### diff --git a/lib_standard_app/io.c b/lib_standard_app/io.c index 90b8f4105..9b6d5d7b9 100644 --- a/lib_standard_app/io.c +++ b/lib_standard_app/io.c @@ -136,8 +136,8 @@ WEAK int io_recv_command() switch (G_io_state) { case READY: + ret = io_exchange(CHANNEL_APDU | IO_CONTINUE_RX, G_output_len); G_io_state = RECEIVED; - ret = io_exchange(CHANNEL_APDU, G_output_len); break; case RECEIVED: G_io_state = WAITING; @@ -200,9 +200,17 @@ WEAK int io_send_response_buffers(const buffer_t *rdatalist, size_t count, uint1 ret = -1; break; case RECEIVED: +#ifdef STANDARD_APP_SYNC_RAPDU + // Send synchronously the APDU response. + // This is needed to send the response before displaying synchronous + // status message on the screen. + // This is not always done to spare the RAM (stack) on LNS. + __attribute__((fallthrough)); +#else G_io_state = READY; ret = 0; break; +#endif case WAITING: ret = io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, G_output_len); G_output_len = 0; From 9398eb4866b079477613de82aa14cdcfbeb4e03f Mon Sep 17 00:00:00 2001 From: Xavier Chapron Date: Mon, 8 Apr 2024 15:17:43 +0200 Subject: [PATCH 05/13] lib_ux_sync: First version (cherry picked from commit cbe3ba5343d7ae0c64b8f80aa929acdf15dd1ae0) --- lib_standard_app/io.c | 17 ++ lib_ux_sync/include/ux_sync.h | 64 ++++++++ lib_ux_sync/src/ux_sync.c | 295 ++++++++++++++++++++++++++++++++++ 3 files changed, 376 insertions(+) create mode 100644 lib_ux_sync/include/ux_sync.h create mode 100644 lib_ux_sync/src/ux_sync.c diff --git a/lib_standard_app/io.c b/lib_standard_app/io.c index 9b6d5d7b9..ba4173d7f 100644 --- a/lib_standard_app/io.c +++ b/lib_standard_app/io.c @@ -220,3 +220,20 @@ WEAK int io_send_response_buffers(const buffer_t *rdatalist, size_t count, uint1 return ret; } + +#ifdef STANDARD_APP_SYNC_RAPDU +WEAK bool io_recv_and_process_event(void) +{ + int apdu_state = G_io_app.apdu_state; + + os_io_seph_recv_and_process(0); + + // If an APDU was received in previous os_io_seph_recv_and_process call and + // is waiting to be processed, return true + if (apdu_state == APDU_IDLE && G_io_app.apdu_state != APDU_IDLE) { + return true; + } + + return false; +} +#endif diff --git a/lib_ux_sync/include/ux_sync.h b/lib_ux_sync/include/ux_sync.h new file mode 100644 index 000000000..c03003df5 --- /dev/null +++ b/lib_ux_sync/include/ux_sync.h @@ -0,0 +1,64 @@ +#ifdef HAVE_NBGL + +#include "nbgl_use_case.h" + +typedef enum { + UX_SYNC_RET_APPROVED, + UX_SYNC_RET_REJECTED, + UX_SYNC_RET_QUITTED, + UX_SYNC_RET_APDU_RECEIVED, + UX_SYNC_RET_ERROR +} ux_sync_ret_t; + +ux_sync_ret_t ux_sync_homeAndSettings(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); + +ux_sync_ret_t ux_sync_review(nbgl_operationType_t operationType, + const nbgl_layoutTagValueList_t *tagValueList, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle, + const char *finishTitle); + +ux_sync_ret_t ux_sync_addressReview(const char *address, + const nbgl_layoutTagValueList_t *additionalTagValueList, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle); + +ux_sync_ret_t ux_sync_reviewStatus(nbgl_reviewStatusType_t reviewStatusType); + +ux_sync_ret_t ux_sync_status(const char *message, bool isSuccess); + +ux_sync_ret_t ux_sync_reviewStreamingStart(nbgl_operationType_t operationType, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle); + +ux_sync_ret_t ux_sync_reviewStreamingContinue(const nbgl_layoutTagValueList_t *tagValueList); + +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. + * It must return: + * - true when an APDU has been received in the processed event + * - false otherwise + * + * Note on C apps using SDK lib_standard_app, this is already provided in io.c by the lib. + */ +extern bool io_recv_and_process_event(void); + +#endif diff --git a/lib_ux_sync/src/ux_sync.c b/lib_ux_sync/src/ux_sync.c new file mode 100644 index 000000000..a58691ae0 --- /dev/null +++ b/lib_ux_sync/src/ux_sync.c @@ -0,0 +1,295 @@ +#ifdef HAVE_NBGL + +#include "ux_sync.h" + +static ux_sync_ret_t g_ret; +static bool g_ended; + +static void choice_callback(bool confirm) +{ + if (confirm) { + g_ret = UX_SYNC_RET_APPROVED; + } + else { + g_ret = UX_SYNC_RET_REJECTED; + } + + g_ended = true; +} + +static void quit_callback(void) +{ + g_ret = UX_SYNC_RET_QUITTED; + 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; + g_ret = UX_SYNC_RET_ERROR; +} + +static ux_sync_ret_t ux_sync_wait(bool exitOnApdu) +{ + bool apduReceived; + + while (!g_ended) { + apduReceived = io_recv_and_process_event(); + if (exitOnApdu && apduReceived) { + return UX_SYNC_RET_APDU_RECEIVED; + } + } + + return g_ret; +} + +/** + * @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. + * @note it enables to use an action button (black on Stax, white on Flex) + * + * @param appName app name + * @param appIcon app icon + * @param tagline text under app name (if NULL, it will be "This app enables signing transactions on + * the network.") + * @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) + * + * @return ret code: + * - UX_SYNC_RET_QUITTED + * - UX_SYNC_RET_APDU_RECEIVED + */ +ux_sync_ret_t ux_sync_homeAndSettings(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) +{ + ux_sync_init(); + nbgl_useCaseHomeAndSettings(appName, + appIcon, + tagline, + initSettingPage, + settingContents, + infosList, + action, + quit_callback); + return ux_sync_wait(true); +} + +/** + * @brief Draws a flow of pages of a review. A back key is available on top-left of the screen, + * except in first page It is possible to go to next page thanks to "tap to continue". + * @note All tag/value pairs are provided in the API and the number of pages is automatically + * computed, the last page being a long press one + * + * @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 + * + * @return ret code: + * - UX_SYNC_RET_APPROVED + * - UX_SYNC_RET_REJECTED + */ +ux_sync_ret_t ux_sync_review(nbgl_operationType_t operationType, + const nbgl_layoutTagValueList_t *tagValueList, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle, + const char *finishTitle) +{ + ux_sync_init(); + nbgl_useCaseReview(operationType, + tagValueList, + icon, + reviewTitle, + reviewSubTitle, + finishTitle, + choice_callback); + return ux_sync_wait(false); +} + +/** + * @brief Draws a flow of pages of an extended address verification page. + * A back key is available on top-left of the screen, + * except in first page It is possible to go to next page thanks to "tap to continue". + * @note All tag/value pairs are provided in the API and the number of pages is automatically + * computed, the last page being a long press one + * + * @param address address to confirm (NULL terminated string) + * @param additionalTagValueList list of tag/value pairs (can be NULL) (must fit in a single page, + * and 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) + * + * @return ret code: + * - UX_SYNC_RET_APPROVED + * - UX_SYNC_RET_REJECTED + */ +ux_sync_ret_t ux_sync_addressReview(const char *address, + const nbgl_layoutTagValueList_t *additionalTagValueList, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle) +{ + ux_sync_init(); + nbgl_useCaseAddressReview( + address, additionalTagValueList, icon, reviewTitle, reviewSubTitle, choice_callback); + return ux_sync_wait(false); +} + +/** + * @brief Draws a transient (3s) status page for the reviewStatusType + * + * @param reviewStatusType type of status to display + * + * @return ret code: + * - UX_SYNC_RET_QUITTED + */ +ux_sync_ret_t ux_sync_reviewStatus(nbgl_reviewStatusType_t reviewStatusType) +{ + ux_sync_init(); + nbgl_useCaseReviewStatus(reviewStatusType, quit_callback); + return ux_sync_wait(false); +} + +/** + * @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) + * + * @return ret code: + * - UX_SYNC_RET_QUITTED + */ +ux_sync_ret_t ux_sync_status(const char *message, bool isSuccess) +{ + ux_sync_init(); + nbgl_useCaseStatus(message, isSuccess, quit_callback); + return ux_sync_wait(false); +} + +/** + * @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) + * + * @return ret code: + * - UX_SYNC_RET_APPROVED + * - UX_SYNC_RET_REJECTED + */ +ux_sync_ret_t ux_sync_reviewStreamingStart(nbgl_operationType_t operationType, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle) + +{ + ux_sync_init(); + nbgl_useCaseReviewStreamingStart( + operationType, icon, reviewTitle, reviewSubTitle, choice_callback); + return ux_sync_wait(false); +} + +/** + * @brief Continue drawing the flow of pages of a review. + * @note This should be called after a call to nbgl_useCaseReviewStreamingStart and can be followed + * by others calls to nbgl_useCaseReviewStreamingContinue and finally to + * nbgl_useCaseReviewStreamingFinish. + * + * @param tagValueList list of tag/value pairs + * + * @return ret code: + * - UX_SYNC_RET_APPROVED + * - UX_SYNC_RET_REJECTED + */ +ux_sync_ret_t ux_sync_reviewStreamingContinue(const nbgl_layoutTagValueList_t *tagValueList) + +{ + ux_sync_init(); + nbgl_useCaseReviewStreamingContinue(tagValueList, choice_callback); + return ux_sync_wait(false); +} + +/** + * @brief finish drawing the flow of pages of a review. + * @note This should be called after a call to nbgl_useCaseReviewStreamingContinue. + * + * @param finishTitle string used in the last review page + * + * @return ret code: + * - UX_SYNC_RET_APPROVED + * - UX_SYNC_RET_REJECTED + */ +ux_sync_ret_t ux_sync_reviewStreamingFinish(const char *finishTitle) + +{ + ux_sync_init(); + nbgl_useCaseReviewStreamingFinish(finishTitle, choice_callback); + 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 From 6b2093489557d7ba7614a481288f731736c0a591 Mon Sep 17 00:00:00 2001 From: Sarah GLINER Date: Thu, 18 Apr 2024 15:12:11 +0200 Subject: [PATCH 06/13] ux_sync: add nbgl_useCaseReviewLight (cherry picked from commit c951b1c0b8948e2c215fa79fa140d42c5a165329) --- lib_ux_sync/include/ux_sync.h | 7 +++++++ lib_ux_sync/src/ux_sync.c | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/lib_ux_sync/include/ux_sync.h b/lib_ux_sync/include/ux_sync.h index c03003df5..bba5145f6 100644 --- a/lib_ux_sync/include/ux_sync.h +++ b/lib_ux_sync/include/ux_sync.h @@ -25,6 +25,13 @@ ux_sync_ret_t ux_sync_review(nbgl_operationType_t operationType, const char *reviewSubTitle, const char *finishTitle); +ux_sync_ret_t ux_sync_reviewLight(nbgl_operationType_t operationType, + const nbgl_layoutTagValueList_t *tagValueList, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle, + const char *finishTitle); + ux_sync_ret_t ux_sync_addressReview(const char *address, const nbgl_layoutTagValueList_t *additionalTagValueList, const nbgl_icon_details_t *icon, diff --git a/lib_ux_sync/src/ux_sync.c b/lib_ux_sync/src/ux_sync.c index a58691ae0..7a2a50604 100644 --- a/lib_ux_sync/src/ux_sync.c +++ b/lib_ux_sync/src/ux_sync.c @@ -123,6 +123,40 @@ ux_sync_ret_t ux_sync_review(nbgl_operationType_t operationType, return ux_sync_wait(false); } +/** + * @brief Draws a flow of pages of a light review. A back key is available on top-left of the + * screen,except in first page It is possible to go to next page thanks to "tap to continue". + * @note All tag/value pairs are provided in the API and the number of pages is automatically + * computed, the last page being a short press one + * + * @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 + * + * @return ret code: + * - UX_SYNC_RET_APPROVED + * - UX_SYNC_RET_REJECTED + */ +ux_sync_ret_t ux_sync_reviewLight(nbgl_operationType_t operationType, + const nbgl_layoutTagValueList_t *tagValueList, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle, + const char *finishTitle) +{ + ux_sync_init(); + nbgl_useCaseReviewLight(operationType, + tagValueList, + icon, + reviewTitle, + reviewSubTitle, + finishTitle, + choice_callback); + return ux_sync_wait(false); +} /** * @brief Draws a flow of pages of an extended address verification page. * A back key is available on top-left of the screen, From e7de8c204cb72b67d57cd366ed6b0f10a817d373 Mon Sep 17 00:00:00 2001 From: Xavier Chapron Date: Tue, 23 Apr 2024 11:21:01 +0200 Subject: [PATCH 07/13] lib_nbgl / lib_ux_sync: Replace nbgl_layoutTagValueList_t with nbgl_contentTagValueList_t nbgl_layoutTagValueList_t was already an alias of nbgl_contentTagValueList_t. Now with choose to use this directly. (cherry picked from commit 2e7edf5300c24e69a3a8a1e8eed37798ca85c521) --- lib_ux_sync/include/ux_sync.h | 36 +++++++++++++++++------------------ lib_ux_sync/src/ux_sync.c | 36 +++++++++++++++++------------------ 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/lib_ux_sync/include/ux_sync.h b/lib_ux_sync/include/ux_sync.h index bba5145f6..938ffe10e 100644 --- a/lib_ux_sync/include/ux_sync.h +++ b/lib_ux_sync/include/ux_sync.h @@ -18,25 +18,25 @@ ux_sync_ret_t ux_sync_homeAndSettings(const char *appName, const nbgl_contentInfoList_t *infosList, const nbgl_homeAction_t *action); -ux_sync_ret_t ux_sync_review(nbgl_operationType_t operationType, - const nbgl_layoutTagValueList_t *tagValueList, - const nbgl_icon_details_t *icon, - const char *reviewTitle, - const char *reviewSubTitle, - const char *finishTitle); +ux_sync_ret_t ux_sync_review(nbgl_operationType_t operationType, + const nbgl_contentTagValueList_t *tagValueList, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle, + const char *finishTitle); -ux_sync_ret_t ux_sync_reviewLight(nbgl_operationType_t operationType, - const nbgl_layoutTagValueList_t *tagValueList, - const nbgl_icon_details_t *icon, - const char *reviewTitle, - const char *reviewSubTitle, - const char *finishTitle); +ux_sync_ret_t ux_sync_reviewLight(nbgl_operationType_t operationType, + const nbgl_contentTagValueList_t *tagValueList, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle, + const char *finishTitle); -ux_sync_ret_t ux_sync_addressReview(const char *address, - const nbgl_layoutTagValueList_t *additionalTagValueList, - const nbgl_icon_details_t *icon, - const char *reviewTitle, - const char *reviewSubTitle); +ux_sync_ret_t ux_sync_addressReview(const char *address, + const nbgl_contentTagValueList_t *additionalTagValueList, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle); ux_sync_ret_t ux_sync_reviewStatus(nbgl_reviewStatusType_t reviewStatusType); @@ -47,7 +47,7 @@ ux_sync_ret_t ux_sync_reviewStreamingStart(nbgl_operationType_t operationT const char *reviewTitle, const char *reviewSubTitle); -ux_sync_ret_t ux_sync_reviewStreamingContinue(const nbgl_layoutTagValueList_t *tagValueList); +ux_sync_ret_t ux_sync_reviewStreamingContinue(const nbgl_contentTagValueList_t *tagValueList); ux_sync_ret_t ux_sync_reviewStreamingFinish(const char *finishTitle); diff --git a/lib_ux_sync/src/ux_sync.c b/lib_ux_sync/src/ux_sync.c index 7a2a50604..ffc977e8f 100644 --- a/lib_ux_sync/src/ux_sync.c +++ b/lib_ux_sync/src/ux_sync.c @@ -105,12 +105,12 @@ ux_sync_ret_t ux_sync_homeAndSettings(const char *appName, * - UX_SYNC_RET_APPROVED * - UX_SYNC_RET_REJECTED */ -ux_sync_ret_t ux_sync_review(nbgl_operationType_t operationType, - const nbgl_layoutTagValueList_t *tagValueList, - const nbgl_icon_details_t *icon, - const char *reviewTitle, - const char *reviewSubTitle, - const char *finishTitle) +ux_sync_ret_t ux_sync_review(nbgl_operationType_t operationType, + const nbgl_contentTagValueList_t *tagValueList, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle, + const char *finishTitle) { ux_sync_init(); nbgl_useCaseReview(operationType, @@ -140,12 +140,12 @@ ux_sync_ret_t ux_sync_review(nbgl_operationType_t operationType, * - UX_SYNC_RET_APPROVED * - UX_SYNC_RET_REJECTED */ -ux_sync_ret_t ux_sync_reviewLight(nbgl_operationType_t operationType, - const nbgl_layoutTagValueList_t *tagValueList, - const nbgl_icon_details_t *icon, - const char *reviewTitle, - const char *reviewSubTitle, - const char *finishTitle) +ux_sync_ret_t ux_sync_reviewLight(nbgl_operationType_t operationType, + const nbgl_contentTagValueList_t *tagValueList, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle, + const char *finishTitle) { ux_sync_init(); nbgl_useCaseReviewLight(operationType, @@ -177,11 +177,11 @@ ux_sync_ret_t ux_sync_reviewLight(nbgl_operationType_t operationType * - UX_SYNC_RET_APPROVED * - UX_SYNC_RET_REJECTED */ -ux_sync_ret_t ux_sync_addressReview(const char *address, - const nbgl_layoutTagValueList_t *additionalTagValueList, - const nbgl_icon_details_t *icon, - const char *reviewTitle, - const char *reviewSubTitle) +ux_sync_ret_t ux_sync_addressReview(const char *address, + const nbgl_contentTagValueList_t *additionalTagValueList, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle) { ux_sync_init(); nbgl_useCaseAddressReview( @@ -258,7 +258,7 @@ ux_sync_ret_t ux_sync_reviewStreamingStart(nbgl_operationType_t operationT * - UX_SYNC_RET_APPROVED * - UX_SYNC_RET_REJECTED */ -ux_sync_ret_t ux_sync_reviewStreamingContinue(const nbgl_layoutTagValueList_t *tagValueList) +ux_sync_ret_t ux_sync_reviewStreamingContinue(const nbgl_contentTagValueList_t *tagValueList) { ux_sync_init(); From 8fb43ea8a39ba1844103a1c05d61b9c4387c3f0e Mon Sep 17 00:00:00 2001 From: Xavier Chapron Date: Thu, 25 Apr 2024 09:52:16 +0200 Subject: [PATCH 08/13] src: os_io_seproxyhal.c: Remove unused code under TARGET_BLUE --- src/os_io_seproxyhal.c | 209 ----------------------------------------- 1 file changed, 209 deletions(-) diff --git a/src/os_io_seproxyhal.c b/src/os_io_seproxyhal.c index 1385f8210..a44ff08fc 100644 --- a/src/os_io_seproxyhal.c +++ b/src/os_io_seproxyhal.c @@ -402,215 +402,6 @@ void io_seproxyhal_init_button(void) G_ux_os.button_same_mask_counter = 0; } -#ifdef TARGET_BLUE -unsigned int io_seproxyhal_touch_out(const bagl_element_t *element, - bagl_element_callback_t before_display) -{ - const bagl_element_t *el; - if (element->out != NULL) { - el = (const bagl_element_t *) PIC(((bagl_element_callback_t) PIC(element->out))(element)); - // backward compatible with samples and such - if (!el) { - return 0; - } - if ((unsigned int) el != 1) { - element = el; - } - } - - // out function might have triggered a draw of its own during a display callback - if (before_display) { - el = before_display(element); - if (!el) { - return 0; - } - if ((unsigned int) el != 1) { - element = el; - } - } - - io_seproxyhal_display(element); - return 1; -} - -unsigned int io_seproxyhal_touch_over(const bagl_element_t *element, - bagl_element_callback_t before_display) -{ - bagl_element_t e; - const bagl_element_t *el; - if (element->over != NULL) { - el = (const bagl_element_t *) PIC(((bagl_element_callback_t) PIC(element->over))(element)); - // backward compatible with samples and such - if (!el) { - return 0; - } - if ((unsigned int) el != 1) { - element = el; - } - } - - // over function might have triggered a draw of its own during a display callback - if (before_display) { - el = before_display(element); - element = &e; - if (!el) { - return 0; - } - // problem for default screen_before_before_display where we return the given element, it - // have could been modified. but we don't know here - if ((unsigned int) el != 1) { - element = el; - } - } - - // swap colors - memcpy(&e, (void *) element, sizeof(bagl_element_t)); - e.component.fgcolor = element->overfgcolor; - e.component.bgcolor = element->overbgcolor; - - io_seproxyhal_display(&e); - return 1; -} - -unsigned int io_seproxyhal_touch_tap(const bagl_element_t *element, - bagl_element_callback_t before_display) -{ - const bagl_element_t *el; - if (element->tap != NULL) { - el = (const bagl_element_t *) PIC(((bagl_element_callback_t) PIC(element->tap))(element)); - // backward compatible with samples and such - if (!el) { - return 0; - } - if ((unsigned int) el != 1) { - element = el; - } - } - - // tap function might have triggered a draw of its own during a display callback - if (before_display) { - el = before_display(element); - if (!el) { - return 0; - } - if ((unsigned int) el != 1) { - element = el; - } - } - io_seproxyhal_display(element); - return 1; -} - -void io_seproxyhal_touch(const bagl_element_t *elements, - unsigned short element_count, - unsigned short x, - unsigned short y, - unsigned char event_kind) -{ - io_seproxyhal_touch_element_callback(elements, element_count, x, y, event_kind, NULL); -} - -// browse all elements and until an element has changed state, continue browsing -// return if processed or not -void io_seproxyhal_touch_element_callback(const bagl_element_t *elements, - unsigned short element_count, - unsigned short x, - unsigned short y, - unsigned char event_kind, - bagl_element_callback_t before_display) -{ - unsigned char comp_idx; - unsigned char last_touched_not_released_component_was_in_current_array = 0; - - // find the first empty entry - for (comp_idx = 0; comp_idx < element_count; comp_idx++) { - // process all components matching the x/y/w/h (no break) => fishy for the released out of - // zone continue processing only if a status has not been sent - if (io_seproxyhal_spi_is_status_sent()) { - // continue instead of return to process all elemnts and therefore discard last touched - // element - break; - } - - // only perform out callback when element was in the current array, else, leave it be - if (&elements[comp_idx] == G_ux_os.last_touched_not_released_component) { - last_touched_not_released_component_was_in_current_array = 1; - } - - // the first component drawn with a - if ((elements[comp_idx].component.type & BAGL_FLAG_TOUCHABLE) - && elements[comp_idx].component.x - elements[comp_idx].touch_area_brim <= x - && x < elements[comp_idx].component.x + elements[comp_idx].component.width - + elements[comp_idx].touch_area_brim - && elements[comp_idx].component.y - elements[comp_idx].touch_area_brim <= y - && y < elements[comp_idx].component.y + elements[comp_idx].component.height - + elements[comp_idx].touch_area_brim) { - // outing the previous over'ed component - if (&elements[comp_idx] != G_ux_os.last_touched_not_released_component - && G_ux_os.last_touched_not_released_component != NULL) { - // only out the previous element if the newly matching will be displayed - if (!before_display || before_display(&elements[comp_idx])) { - if (io_seproxyhal_touch_out(G_ux_os.last_touched_not_released_component, - before_display)) { - // previous component is considered released - G_ux_os.last_touched_not_released_component = NULL; - // a display has been issued, avoid double display, wait for another touch - // event (20ms) - return; - } - } - // avoid a non displayed new element to pop out of the blue - continue; - } - - /* - if (io_seproxyhal_spi_is_status_sent()) { - // continue instead of return to process all elements and therefore discard last - touched element continue; - } - */ - - // callback the hal to notify the component impacted by the user input - else if (event_kind == SEPROXYHAL_TAG_FINGER_EVENT_RELEASE) { - if (io_seproxyhal_touch_tap(&elements[comp_idx], before_display)) { - // unmark the last component, we've been notified TOUCH - G_ux_os.last_touched_not_released_component = NULL; - return; - } - } - else if (event_kind == SEPROXYHAL_TAG_FINGER_EVENT_TOUCH) { - // ask for overing - if (io_seproxyhal_touch_over(&elements[comp_idx], before_display)) { - // remember the last touched component - G_ux_os.last_touched_not_released_component - = (bagl_element_t *) &elements[comp_idx]; - return; - } - } - } - } - - // if overing out of component or over another component, the out event is sent after the over - // event of the previous component - if (last_touched_not_released_component_was_in_current_array - && G_ux_os.last_touched_not_released_component != NULL) { - // we won't be able to notify the out, don't do it, in case a diplay refused the dra of the - // relased element and the position matched another element of the array (in autocomplete - // for example) - if (io_seproxyhal_spi_is_status_sent()) { - return; - } - - if (io_seproxyhal_touch_out(G_ux_os.last_touched_not_released_component, before_display)) { - // ok component out has been emitted - G_ux_os.last_touched_not_released_component = NULL; - } - } - - // not processed -} -#endif // TARGET_BLUE - void io_seproxyhal_display_bitmap(int x, int y, unsigned int w, From 5704f6337858d8785db2a1bd105dd44554067b26 Mon Sep 17 00:00:00 2001 From: Xavier Chapron Date: Thu, 25 Apr 2024 09:53:15 +0200 Subject: [PATCH 09/13] src: os_io_seproxyhal.c: Remove unused code under SEPROXYHAL_TAG_SCREEN_DISPLAY_RAW_STATUS --- src/os_io_seproxyhal.c | 101 ----------------------------------------- 1 file changed, 101 deletions(-) diff --git a/src/os_io_seproxyhal.c b/src/os_io_seproxyhal.c index a44ff08fc..7b9d770de 100644 --- a/src/os_io_seproxyhal.c +++ b/src/os_io_seproxyhal.c @@ -452,75 +452,6 @@ void io_seproxyhal_display_bitmap(int x, } } -#ifdef SEPROXYHAL_TAG_SCREEN_DISPLAY_RAW_STATUS -unsigned int io_seproxyhal_display_icon_header_and_colors(bagl_component_t *icon_component, - bagl_icon_details_t *icon_details, - unsigned int *icon_len) -{ - unsigned int len; - - struct display_raw_s { - struct { - struct { - unsigned char tag; - unsigned char len[2]; - } seph; - unsigned char type; - } header; - union { - short val; - char b[2]; - } x; - union { - short val; - char b[2]; - } y; - union { - unsigned short val; - char b[2]; - } w; - union { - unsigned short val; - char b[2]; - } h; - unsigned char bpp; - } __attribute__((packed)) raw; - - raw.header.seph.tag = SEPROXYHAL_TAG_SCREEN_DISPLAY_RAW_STATUS; - raw.header.type = SEPROXYHAL_TAG_SCREEN_DISPLAY_RAW_STATUS_START; - raw.x.val = icon_component->x; - raw.y.val = icon_component->y; - raw.w.val = icon_component->width; - raw.h.val = icon_component->height; - raw.bpp = icon_details->bpp; - - *icon_len - = raw.w.val * raw.h.val * raw.bpp / 8 + (((raw.w.val * raw.h.val * raw.bpp) % 8) ? 1 : 0); - - // optional, don't send too much on a single packet for MCU to receive it. when stream mode will - // be on, this will be useless min of remaining space in the packet vs. total icon size + color - // index size - len = MIN(sizeof(G_io_seproxyhal_spi_buffer) - sizeof(raw), *icon_len + (1 << raw.bpp) * 4); - - // sizeof packet - raw.header.seph.len[0] = (len + sizeof(raw) - sizeof(raw.header.seph)) >> 8; - raw.header.seph.len[1] = (len + sizeof(raw) - sizeof(raw.header.seph)); - - // swap endianess of coordinates (make it big endian) - SWAP(raw.x.b[0], raw.x.b[1]); - SWAP(raw.y.b[0], raw.y.b[1]); - SWAP(raw.w.b[0], raw.w.b[1]); - SWAP(raw.h.b[0], raw.h.b[1]); - - io_seproxyhal_spi_send((unsigned char *) &raw, sizeof(raw)); - io_seproxyhal_spi_send((unsigned char *) (PIC(icon_details->colors)), (1 << raw.bpp) * 4); - len -= (1 << raw.bpp) * 4; - - // remaining length of bitmap bits to be displayed - return len; -} -#endif // SEPROXYHAL_TAG_SCREEN_DISPLAY_RAW_STATUS - void io_seproxyhal_display_icon(bagl_component_t *icon_component, bagl_icon_details_t *icon_det) { bagl_component_t icon_component_mod; @@ -533,37 +464,6 @@ void io_seproxyhal_display_icon(bagl_component_t *icon_component, bagl_icon_deta icon_component_mod.height = icon_details->height; icon_component = &icon_component_mod; -#ifdef SEPROXYHAL_TAG_SCREEN_DISPLAY_RAW_STATUS - unsigned int len; - unsigned int icon_len; - unsigned int icon_off = 0; - - len = io_seproxyhal_display_icon_header_and_colors( - icon_component, (bagl_icon_details_t *) icon_details, &icon_len); - io_seproxyhal_spi_send(PIC(icon_details->bitmap), len); - // advance in the bitmap to be transmitted - icon_len -= len; - icon_off += len; - - // still some bitmap data to transmit - while (icon_len) { - // wait displayed event - io_seproxyhal_spi_recv( - G_io_seproxyhal_spi_buffer, sizeof(G_io_seproxyhal_spi_buffer), 0); - - G_io_seproxyhal_spi_buffer[0] = SEPROXYHAL_TAG_SCREEN_DISPLAY_RAW_STATUS; - G_io_seproxyhal_spi_buffer[3] = SEPROXYHAL_TAG_SCREEN_DISPLAY_RAW_STATUS_CONT; - - len = MIN((sizeof(G_io_seproxyhal_spi_buffer) - 4), icon_len); - G_io_seproxyhal_spi_buffer[1] = (len + 1) >> 8; - G_io_seproxyhal_spi_buffer[2] = (len + 1); - io_seproxyhal_spi_send(G_io_seproxyhal_spi_buffer, 4); - io_seproxyhal_spi_send(PIC(icon_details->bitmap) + icon_off, len); - - icon_len -= len; - icon_off += len; - } -#else // !SEPROXYHAL_TAG_SCREEN_DISPLAY_RAW_STATUS #ifdef HAVE_SE_SCREEN bagl_draw_glyph(&icon_component_mod, icon_details); #endif // HAVE_SE_SCREEN @@ -593,7 +493,6 @@ void io_seproxyhal_display_icon(bagl_component_t *icon_component, bagl_icon_deta io_seproxyhal_spi_send((unsigned char *) PIC(icon_details->colors), h); io_seproxyhal_spi_send((unsigned char *) PIC(icon_details->bitmap), w); #endif // !HAVE_SE_SCREEN || (HAVE_SE_SCREEN && HAVE_PRINTF) -#endif // !SEPROXYHAL_TAG_SCREEN_DISPLAY_RAW_STATUS } } From 5da9af66da0a3212a077279a3cc07ad1fa9bfc29 Mon Sep 17 00:00:00 2001 From: Xavier Chapron Date: Thu, 25 Apr 2024 09:55:52 +0200 Subject: [PATCH 10/13] src: os_io_seproxyhal.c: Simplify code considering HAVE_SE_SCREEN is not defined --- src/os_io_seproxyhal.c | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/src/os_io_seproxyhal.c b/src/os_io_seproxyhal.c index 7b9d770de..401f66fe9 100644 --- a/src/os_io_seproxyhal.c +++ b/src/os_io_seproxyhal.c @@ -464,10 +464,6 @@ void io_seproxyhal_display_icon(bagl_component_t *icon_component, bagl_icon_deta icon_component_mod.height = icon_details->height; icon_component = &icon_component_mod; -#ifdef HAVE_SE_SCREEN - bagl_draw_glyph(&icon_component_mod, icon_details); -#endif // HAVE_SE_SCREEN -#if !defined(HAVE_SE_SCREEN) || (defined(HAVE_SE_SCREEN) && defined(HAVE_PRINTF)) if (io_seproxyhal_spi_is_status_sent()) { return; } @@ -481,9 +477,6 @@ void io_seproxyhal_display_icon(bagl_component_t *icon_component, bagl_icon_deta + h /* color index */ + w; /* image bitmap size */ G_io_seproxyhal_spi_buffer[0] = SEPROXYHAL_TAG_SCREEN_DISPLAY_STATUS; -#if defined(HAVE_SE_SCREEN) && defined(HAVE_PRINTF) - G_io_seproxyhal_spi_buffer[0] = SEPROXYHAL_TAG_DBG_SCREEN_DISPLAY_STATUS; -#endif // HAVE_SE_SCREEN && HAVE_PRINTF G_io_seproxyhal_spi_buffer[1] = length >> 8; G_io_seproxyhal_spi_buffer[2] = length; io_seproxyhal_spi_send(G_io_seproxyhal_spi_buffer, 3); @@ -492,7 +485,6 @@ void io_seproxyhal_display_icon(bagl_component_t *icon_component, bagl_icon_deta io_seproxyhal_spi_send(G_io_seproxyhal_spi_buffer, 1); io_seproxyhal_spi_send((unsigned char *) PIC(icon_details->colors), h); io_seproxyhal_spi_send((unsigned char *) PIC(icon_details->bitmap), w); -#endif // !HAVE_SE_SCREEN || (HAVE_SE_SCREEN && HAVE_PRINTF) } } @@ -515,44 +507,28 @@ void io_seproxyhal_display_default(const bagl_element_t *element) (bagl_icon_details_t *) txt); } else { -#ifdef HAVE_SE_SCREEN - bagl_draw_with_context(&el->component, txt, strlen(txt), BAGL_ENCODING_LATIN1); -#endif // HAVE_SE_SCREEN -#if !defined(HAVE_SE_SCREEN) || (defined(HAVE_SE_SCREEN) && defined(HAVE_PRINTF)) if (io_seproxyhal_spi_is_status_sent()) { return; } unsigned short length = sizeof(bagl_component_t) + strlen((const char *) txt); G_io_seproxyhal_spi_buffer[0] = SEPROXYHAL_TAG_SCREEN_DISPLAY_STATUS; -#if defined(HAVE_SE_SCREEN) && defined(HAVE_PRINTF) - G_io_seproxyhal_spi_buffer[0] = SEPROXYHAL_TAG_DBG_SCREEN_DISPLAY_STATUS; -#endif // HAVE_SE_SCREEN && HAVE_PRINTF G_io_seproxyhal_spi_buffer[1] = length >> 8; G_io_seproxyhal_spi_buffer[2] = length; io_seproxyhal_spi_send(G_io_seproxyhal_spi_buffer, 3); io_seproxyhal_spi_send((unsigned char *) &el->component, sizeof(bagl_component_t)); io_seproxyhal_spi_send((unsigned char *) txt, length - sizeof(bagl_component_t)); -#endif // !HAVE_SE_SCREEN || (HAVE_SE_SCREEN && HAVE_PRINTF) } } else { -#ifdef HAVE_SE_SCREEN - bagl_draw_with_context(&el->component, NULL, 0, 0); -#endif // HAVE_SE_SCREEN -#if !defined(HAVE_SE_SCREEN) || (defined(HAVE_SE_SCREEN) && defined(HAVE_PRINTF)) if (io_seproxyhal_spi_is_status_sent()) { return; } unsigned short length = sizeof(bagl_component_t); G_io_seproxyhal_spi_buffer[0] = SEPROXYHAL_TAG_SCREEN_DISPLAY_STATUS; -#if defined(HAVE_SE_SCREEN) && defined(HAVE_PRINTF) - G_io_seproxyhal_spi_buffer[0] = SEPROXYHAL_TAG_DBG_SCREEN_DISPLAY_STATUS; -#endif // HAVE_SE_SCREEN && HAVE_PRINTF G_io_seproxyhal_spi_buffer[1] = length >> 8; G_io_seproxyhal_spi_buffer[2] = length; io_seproxyhal_spi_send(G_io_seproxyhal_spi_buffer, 3); io_seproxyhal_spi_send((unsigned char *) &el->component, sizeof(bagl_component_t)); -#endif // !HAVE_SE_SCREEN || (HAVE_SE_SCREEN && HAVE_PRINTF) } } } From 0f6d743097e3300919c166b87b4905e69b8a515c Mon Sep 17 00:00:00 2001 From: Xavier Chapron Date: Sun, 28 Apr 2024 14:48:36 +0200 Subject: [PATCH 11/13] Makefiles: Extract Makefile.target from Makefile.defines --- Makefile.defines | 33 +++++---------------------- Makefile.standard_app | 4 ++++ Makefile.target | 52 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 27 deletions(-) create mode 100644 Makefile.target diff --git a/Makefile.defines b/Makefile.defines index 0158c8c8b..07dc9e7a5 100644 --- a/Makefile.defines +++ b/Makefile.defines @@ -1,6 +1,6 @@ #******************************************************************************* # Ledger SDK -# (c) 2017 Ledger +# (c) 2024 Ledger # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,22 +15,10 @@ # limitations under the License. #******************************************************************************* -#extract TARGET_NAME/TARGET_ID from the SDK to allow for makefile choices -TARGET := nanos -TARGET_ID:=$(shell cat $(BOLOS_SDK)/include/bolos_target.h | grep TARGET_ID | cut -f3 -d' ') -TARGET_NAME:=$(shell cat $(BOLOS_SDK)/include/bolos_target.h | grep TARGET_ | grep -v TARGET_ID | cut -f2 -d' ') -TARGET_VERSION:=$(shell cat $(BOLOS_SDK)/include/bolos_version.h | grep define | cut -f2 -d'"') -SDK_NAME := "nanos-secure-sdk" -SDK_VERSION := $(shell git -C $(BOLOS_SDK) describe --tags --exact-match --match "v[0-9]*" --dirty) -SDK_HASH := $(shell git -C $(BOLOS_SDK) describe --always --dirty --exclude '*' --abbrev=40) -ifeq ($(SDK_VERSION),) -SDK_VERSION := "None" -endif -ifeq ($(SDK_HASH),) -SDK_HASH := "None" -endif +ifeq ($(__MAKEFILE_DEFINES__),) +__MAKEFILE_DEFINES__ := 1 -$(info TARGET_NAME=$(TARGET_NAME) TARGET_ID=$(TARGET_ID) TARGET_VERSION=$(TARGET_VERSION)) +include $(BOLOS_SDK)/Makefile.target # APPNAME exposed to the app as a CFLAG because it might contain spaces CFLAGS += -DAPPNAME=\"$(APPNAME)\" @@ -41,23 +29,12 @@ APP_METADATA_LIST := TARGET TARGET_NAME APPVERSION SDK_NAME SDK_VERSION SDK_HASH DEFINES += $(foreach item,$(APP_METADATA_LIST), $(item)=\"$($(item))\") DEFINES += TARGET_VERSION=$(TARGET_VERSION) -BUILD_DIR := build -TARGET_BUILD_DIR := $(BUILD_DIR)/$(TARGET) -BIN_DIR := $(TARGET_BUILD_DIR)/bin -OBJ_DIR := $(TARGET_BUILD_DIR)/obj -DBG_DIR := $(TARGET_BUILD_DIR)/dbg -DEP_DIR := $(TARGET_BUILD_DIR)/dep -GEN_SRC_DIR := $(TARGET_BUILD_DIR)/gen_src - ### platform definitions DEFINES += gcc __IO=volatile # no assert by default DEFINES += NDEBUG -# Debug mode disabled by default -DEBUG:=0 - # default is not to display make commands log = $(if $(strip $(VERBOSE)),$1,@$1) # kept for retrocompat L = $(if $(strip $(VERBOSE)),,@) @@ -170,3 +147,5 @@ endif # define the default makefile target (high in include to avoid glyph.h or what not specific target to be the default one when no target passed on the make command line) all: default + +endif diff --git a/Makefile.standard_app b/Makefile.standard_app index 69b4aa2e7..fb587e827 100644 --- a/Makefile.standard_app +++ b/Makefile.standard_app @@ -15,6 +15,8 @@ # limitations under the License. #******************************************************************************* +include $(BOLOS_SDK)/Makefile.target + ##################################################################### # BLUETOOTH # ##################################################################### @@ -210,6 +212,8 @@ ifeq ($(TARGET_NAME),TARGET_STAX) ICONNAME ?= $(ICON_STAX) endif +include $(BOLOS_SDK)/Makefile.defines + include $(BOLOS_SDK)/Makefile.glyphs load: all diff --git a/Makefile.target b/Makefile.target new file mode 100644 index 000000000..71e758712 --- /dev/null +++ b/Makefile.target @@ -0,0 +1,52 @@ +#******************************************************************************* +# Ledger SDK +# (c) 2024 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. +#******************************************************************************* + +ifeq ($(__MAKEFILE_TARGET__),) +__MAKEFILE_TARGET__ := 1 + +#extract TARGET_NAME/TARGET_ID from the SDK to allow for makefile choices +TARGET := nanos +TARGET_ID:=$(shell cat $(BOLOS_SDK)/include/bolos_target.h | grep TARGET_ID | cut -f3 -d' ') +TARGET_NAME:=$(shell cat $(BOLOS_SDK)/include/bolos_target.h | grep TARGET_ | grep -v TARGET_ID | cut -f2 -d' ') +TARGET_VERSION:=$(shell cat $(BOLOS_SDK)/include/bolos_version.h | grep define | cut -f2 -d'"') +SDK_NAME := "nanos-secure-sdk" +SDK_VERSION := $(shell git -C $(BOLOS_SDK) describe --tags --exact-match --match "v[0-9]*" --dirty) +SDK_HASH := $(shell git -C $(BOLOS_SDK) describe --always --dirty --exclude '*' --abbrev=40) +ifeq ($(SDK_VERSION),) +SDK_VERSION := "None" +endif +ifeq ($(SDK_HASH),) +SDK_HASH := "None" +endif + +$(info TARGET_NAME=$(TARGET_NAME) TARGET_ID=$(TARGET_ID) TARGET_VERSION=$(TARGET_VERSION)) + +BUILD_DIR := build +TARGET_BUILD_DIR := $(BUILD_DIR)/$(TARGET) +BIN_DIR := $(TARGET_BUILD_DIR)/bin +OBJ_DIR := $(TARGET_BUILD_DIR)/obj +DBG_DIR := $(TARGET_BUILD_DIR)/dbg +DEP_DIR := $(TARGET_BUILD_DIR)/dep +GEN_SRC_DIR := $(TARGET_BUILD_DIR)/gen_src + +# Debug mode disabled by default +DEBUG:=0 + +# define the default makefile target (high in include to avoid glyph.h or what not specific target to be the default one when no target passed on the make command line) +all: default + +endif From 41c55f2695f7e985144d52259543a0ee1a2dbb18 Mon Sep 17 00:00:00 2001 From: Xavier Chapron Date: Fri, 3 May 2024 09:51:49 +0200 Subject: [PATCH 12/13] Makefile.standard_app: Align on SDK master one --- Makefile.standard_app | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/Makefile.standard_app b/Makefile.standard_app index fb587e827..770d23423 100644 --- a/Makefile.standard_app +++ b/Makefile.standard_app @@ -21,7 +21,7 @@ include $(BOLOS_SDK)/Makefile.target # BLUETOOTH # ##################################################################### ifeq ($(ENABLE_BLUETOOTH), 1) -ifeq ($(TARGET_NAME),$(filter $(TARGET_NAME),TARGET_NANOX TARGET_STAX)) +ifeq ($(TARGET_NAME),$(filter $(TARGET_NAME),TARGET_NANOX TARGET_STAX TARGET_FLEX)) HAVE_APPLICATION_FLAG_BOLOS_SETTINGS = 1 DEFINES += HAVE_BLE BLE_COMMAND_TIMEOUT_MS=2000 HAVE_BLE_APDU DEFINES += BLE_SEGMENT_SIZE=32 @@ -33,10 +33,9 @@ endif # NFC # ##################################################################### ifeq ($(ENABLE_NFC), 1) -ifeq ($(TARGET_NAME),$(filter $(TARGET_NAME), TARGET_STAX)) - STANDARD_APP_FLAGS = 0x200 # APPLICATION_FLAG_BOLOS_SETTINGS +ifeq ($(TARGET_NAME),$(filter $(TARGET_NAME), TARGET_STAX TARGET_FLEX)) + HAVE_APPLICATION_FLAG_BOLOS_SETTINGS = 1 DEFINES += HAVE_NFC - SDK_SOURCE_PATH += lib_nfc endif endif @@ -113,7 +112,7 @@ ifneq ($(DISABLE_STANDARD_WEBUSB), 1) endif ifneq ($(DISABLE_STANDARD_BAGL_UX_FLOW), 1) -ifneq ($(TARGET_NAME), TARGET_STAX) +ifeq ($(USE_NBGL),0) DEFINES += HAVE_UX_FLOW endif endif @@ -136,20 +135,20 @@ endif # NBGL # ##################################################################### ifeq ($(ENABLE_NBGL_QRCODE), 1) -ifeq ($(TARGET_NAME), TARGET_STAX) +ifeq ($(TARGET_NAME),$(filter $(TARGET_NAME),TARGET_STAX TARGET_FLEX)) DEFINES += NBGL_QRCODE SDK_SOURCE_PATH += qrcode endif endif ifeq ($(ENABLE_NBGL_KEYBOARD), 1) -ifeq ($(TARGET_NAME), TARGET_STAX) +ifeq ($(TARGET_NAME),$(filter $(TARGET_NAME),TARGET_STAX TARGET_FLEX)) DEFINES += NBGL_KEYBOARD endif endif ifeq ($(ENABLE_NBGL_KEYPAD), 1) -ifeq ($(TARGET_NAME), TARGET_STAX) +ifeq ($(TARGET_NAME),$(filter $(TARGET_NAME),TARGET_STAX TARGET_FLEX)) DEFINES += NBGL_KEYPAD endif endif @@ -208,22 +207,25 @@ endif ifeq ($(TARGET_NAME), TARGET_NANOS2) ICONNAME ?= $(ICON_NANOSP) endif -ifeq ($(TARGET_NAME),TARGET_STAX) +ifeq ($(TARGET_NAME), TARGET_STAX) ICONNAME ?= $(ICON_STAX) endif +ifeq ($(TARGET_NAME), TARGET_FLEX) +ICONNAME ?= $(ICON_FLEX) +endif include $(BOLOS_SDK)/Makefile.defines include $(BOLOS_SDK)/Makefile.glyphs load: all - python3 -m ledgerblue.loadApp $(APP_LOAD_PARAMS) + python3 -m ledgerblue.loadApp $(APP_LOAD_PARAMS) load-offline: all - python3 -m ledgerblue.loadApp $(APP_LOAD_PARAMS) --offline + python3 -m ledgerblue.loadApp $(APP_LOAD_PARAMS) --offline delete: - python3 -m ledgerblue.deleteApp $(COMMON_DELETE_PARAMS) + python3 -m ledgerblue.deleteApp $(COMMON_DELETE_PARAMS) include $(BOLOS_SDK)/Makefile.rules @@ -240,4 +242,4 @@ include $(BOLOS_SDK)/Makefile.rules # available variants and then call `make -j =` for each # in . listvariants: - @echo VARIANTS $(VARIANT_PARAM) $(VARIANT_VALUES) + @echo VARIANTS $(VARIANT_PARAM) $(VARIANT_VALUES) From 68c1e173d95d7f9eecf41da1bdb8a54f71c3e50c Mon Sep 17 00:00:00 2001 From: Xavier Chapron Date: Mon, 22 Apr 2024 17:30:09 +0200 Subject: [PATCH 13/13] nbgl: Implement NBGL for LNS --- Makefile.defines | 11 +- Makefile.rules | 4 +- Makefile.standard_app | 68 +- icon3.py | 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 | 12 - lib_ux/include/ux_bagl.h | 4 +- lib_ux_nbgl/ux.c | 122 ++++ lib_ux_nbgl/ux.h | 106 +++ 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 | 8 +- src/os_io_seproxyhal.c | 12 +- 23 files changed, 3071 insertions(+), 108 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.defines b/Makefile.defines index 07dc9e7a5..4b5b38a56 100644 --- a/Makefile.defines +++ b/Makefile.defines @@ -110,9 +110,18 @@ else LDFLAGS += -nostartfiles endif +# For LNS devices, by default NBGL is not used +USE_NBGL ?= 0 + ifeq ($(TARGET_NAME),TARGET_NANOS) -DEFINES += HAVE_BAGL DEFINES += BAGL_WIDTH=128 BAGL_HEIGHT=32 + +ifeq ($(USE_NBGL),0) +DEFINES += HAVE_BAGL +else +DEFINES += HAVE_NBGL +DEFINES += NBGL_USE_CASE +endif # USE_NBGL endif ifeq ($(TARGET_NAME),TARGET_NANOX) diff --git a/Makefile.rules b/Makefile.rules index f69f82a79..766fe7ef7 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)) +ifeq ($(USE_NBGL),0) 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 770d23423..5254f607b 100644 --- a/Makefile.standard_app +++ b/Makefile.standard_app @@ -79,6 +79,38 @@ ifneq ($(DISABLE_DEFAULT_IO_SEPROXY_BUFFER_SIZE), 1) endif endif +##################################################################### +# NBGL # +##################################################################### +ifeq ($(TARGET_NAME),$(filter $(TARGET_NAME),TARGET_NANOS TARGET_NANOX TARGET_NANOS2)) +ifeq ($(ENABLE_NBGL_FOR_NANO_DEVICES), 1) +USE_NBGL = 1 +else +USE_NBGL = 0 +endif +else +USE_NBGL = 1 +endif + +ifeq ($(ENABLE_NBGL_QRCODE), 1) +ifeq ($(TARGET_NAME),$(filter $(TARGET_NAME),TARGET_STAX TARGET_FLEX)) + DEFINES += NBGL_QRCODE + SDK_SOURCE_PATH += qrcode +endif +endif + +ifeq ($(ENABLE_NBGL_KEYBOARD), 1) +ifeq ($(TARGET_NAME),$(filter $(TARGET_NAME),TARGET_STAX TARGET_FLEX)) + DEFINES += NBGL_KEYBOARD +endif +endif + +ifeq ($(ENABLE_NBGL_KEYPAD), 1) +ifeq ($(TARGET_NAME),$(filter $(TARGET_NAME),TARGET_STAX TARGET_FLEX)) + DEFINES += NBGL_KEYPAD +endif +endif + ##################################################################### # STANDARD DEFINES # ##################################################################### @@ -112,7 +144,7 @@ ifneq ($(DISABLE_STANDARD_WEBUSB), 1) endif ifneq ($(DISABLE_STANDARD_BAGL_UX_FLOW), 1) -ifeq ($(USE_NBGL),0) +ifneq ($(USE_NBGL), 1) DEFINES += HAVE_UX_FLOW endif endif @@ -126,31 +158,15 @@ 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_FOR_NANO_DEVICES), 1) +DEFINES += STANDARD_APP_SYNC_RAPDU endif endif - -##################################################################### -# NBGL # -##################################################################### -ifeq ($(ENABLE_NBGL_QRCODE), 1) -ifeq ($(TARGET_NAME),$(filter $(TARGET_NAME),TARGET_STAX TARGET_FLEX)) - DEFINES += NBGL_QRCODE - SDK_SOURCE_PATH += qrcode -endif -endif - -ifeq ($(ENABLE_NBGL_KEYBOARD), 1) -ifeq ($(TARGET_NAME),$(filter $(TARGET_NAME),TARGET_STAX TARGET_FLEX)) - DEFINES += NBGL_KEYBOARD -endif -endif - -ifeq ($(ENABLE_NBGL_KEYPAD), 1) -ifeq ($(TARGET_NAME),$(filter $(TARGET_NAME),TARGET_STAX TARGET_FLEX)) - DEFINES += NBGL_KEYPAD -endif endif ##################################################################### @@ -219,13 +235,13 @@ include $(BOLOS_SDK)/Makefile.defines include $(BOLOS_SDK)/Makefile.glyphs load: all - python3 -m ledgerblue.loadApp $(APP_LOAD_PARAMS) + python3 -m ledgerblue.loadApp $(APP_LOAD_PARAMS) load-offline: all - python3 -m ledgerblue.loadApp $(APP_LOAD_PARAMS) --offline + python3 -m ledgerblue.loadApp $(APP_LOAD_PARAMS) --offline delete: - python3 -m ledgerblue.deleteApp $(COMMON_DELETE_PARAMS) + python3 -m ledgerblue.deleteApp $(COMMON_DELETE_PARAMS) include $(BOLOS_SDK)/Makefile.rules @@ -242,4 +258,4 @@ include $(BOLOS_SDK)/Makefile.rules # available variants and then call `make -j =` for each # in . listvariants: - @echo VARIANTS $(VARIANT_PARAM) $(VARIANT_VALUES) + @echo VARIANTS $(VARIANT_PARAM) $(VARIANT_VALUES) diff --git a/icon3.py b/icon3.py index db9adba16..d4b4b85e3 100644 --- a/icon3.py +++ b/icon3.py @@ -206,20 +206,20 @@ def main(): print("};") if args.glyphcheader: - print("""#ifdef HAVE_BAGL + print("""#if defined(HAVE_BAGL) || defined(HAVE_NBGL) #include \"bagl.h\" extern const bagl_icon_details_t C_{0}; #endif // GLYPH_{0}_BPP - #endif // HAVE_BAGL""".format(image_name)) + #endif // HAVE_BAGL || HAVE_NBGL""".format(image_name)) elif args.glyphcfile: color_ref = image_name if args.factorize: if color_array_serialized in colors_array: color_ref = colors_array[color_array_serialized] - print("""#ifdef HAVE_BAGL + print("""#if defined(HAVE_BAGL) || defined(HAVE_NBGL) #include \"bagl.h\" const bagl_icon_details_t C_{0} = {{ GLYPH_{0}_WIDTH, GLYPH_{0}_HEIGHT, {1}, C_{2}_colors, C_{0}_bitmap }}; - #endif // HAVE_BAGL""".format(image_name, int(math.log(num_colors, 2)), color_ref)) + #endif // HAVE_BAGL || HAVE_NBGL""".format(image_name, int(math.log(num_colors, 2)), color_ref)) else: # Origin 0,0 is left top for blue, instead of left bottom for all image encodings print("{{ {0:d}, {1:d}, {2:d}, C_{3}_colors, C_{3}_bitmap }},".format( diff --git a/lib_nbgl/include/nbgl_buttons.h b/lib_nbgl/include/nbgl_buttons.h new file mode 100644 index 000000000..c71b25b75 --- /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 Right 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 000000000..4420b0ae6 --- /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 000000000..e0a69a887 --- /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 000000000..b890a3963 --- /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 000000000..137adaeef --- /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 000000000..3e3121eea --- /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 000000000..f87295723 --- /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 000000000..1e61a79b1 --- /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 000000000..e4d16169c --- /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 != SINGLE_STEP) && (pos != FIRST_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 != SINGLE_STEP) && (pos != 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 000000000..507aa4992 --- /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 ba4173d7f..da413550a 100644 --- a/lib_standard_app/io.c +++ b/lib_standard_app/io.c @@ -58,9 +58,7 @@ WEAK uint8_t io_event(uint8_t channel) switch (G_io_seproxyhal_spi_buffer[0]) { case SEPROXYHAL_TAG_BUTTON_PUSH_EVENT: -#ifdef HAVE_BAGL UX_BUTTON_PUSH_EVENT(G_io_seproxyhal_spi_buffer); -#endif // HAVE_BAGL break; case SEPROXYHAL_TAG_STATUS_EVENT: if (G_io_apdu_media == IO_APDU_MEDIA_USB_HID && // @@ -70,18 +68,8 @@ WEAK uint8_t io_event(uint8_t channel) } __attribute__((fallthrough)); case SEPROXYHAL_TAG_DISPLAY_PROCESSED_EVENT: -#ifdef HAVE_BAGL UX_DISPLAYED_EVENT({}); -#endif // HAVE_BAGL -#ifdef HAVE_NBGL - UX_DEFAULT_EVENT(); -#endif // HAVE_NBGL - break; -#ifdef HAVE_NBGL - case SEPROXYHAL_TAG_FINGER_EVENT: - UX_FINGER_EVENT(G_io_seproxyhal_spi_buffer); break; -#endif // HAVE_NBGL case SEPROXYHAL_TAG_TICKER_EVENT: app_ticker_event_callback(); UX_TICKER_EVENT(G_io_seproxyhal_spi_buffer, {}); diff --git a/lib_ux/include/ux_bagl.h b/lib_ux/include/ux_bagl.h index 9d8f95054..2c4f91c65 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 000000000..33317dfe2 --- /dev/null +++ b/lib_ux_nbgl/ux.c @@ -0,0 +1,122 @@ + +/******************************************************************************* + * 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 + * + * @return true if event is to be processed by the application + */ +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 000000000..17c5f4c8f --- /dev/null +++ b/lib_ux_nbgl/ux.h @@ -0,0 +1,106 @@ + +/******************************************************************************* + * 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" + +/* On LNS, NBGL is exposed to the app, but under the hood, the SDK still sends + * BAGL commands to the MCU + */ + +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 938ffe10e..c233975f2 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 ffc977e8f..b4cc3cbb0 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 15c3fce85..c3afc8f86 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 3ba6619c9..820267877 100644 --- a/src/ledger_assert.c +++ b/src/ledger_assert.c @@ -134,8 +134,14 @@ void __attribute__((noreturn)) assert_display_exit(void) #endif #ifdef HAVE_NBGL +#if defined(TARGET_NANOS) || defined(TARGET_NANOX) || defined(TARGET_NANOS2) +#define ICON_APP_WARNING C_icon_warning +#elif 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 401f66fe9..f89b125eb 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) ux_seph_os_and_app_t G_ux_os; #endif @@ -377,8 +377,10 @@ void io_seproxyhal_init(void) io_usb_hid_init(); #endif // HAVE_USB_APDU +#ifdef HAVE_BAGL io_seproxyhal_init_ux(); io_seproxyhal_init_button(); +#endif // HAVE_BAGL #if !defined(HAVE_BOLOS) && defined(HAVE_PENDING_REVIEW_SCREEN) check_audited_app(); @@ -451,8 +453,12 @@ void io_seproxyhal_display_bitmap(int x, */ } } +#endif // HAVE_BAGL -void io_seproxyhal_display_icon(bagl_component_t *icon_component, bagl_icon_details_t *icon_det) +#if defined(HAVE_BAGL) || defined(HAVE_NBGL) + +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 || HAVE_NBGL +#ifdef HAVE_BAGL unsigned int bagl_label_roundtrip_duration_ms(const bagl_element_t *e, unsigned int average_char_width) {