From 681b1cd0699e2c9b8d64b08845a780e6365cc233 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Wed, 26 Jun 2024 02:53:39 +0200 Subject: [PATCH 1/4] JS: Refactor widget with ViewHolder, fix crash Based on textbox refactor by nminaylov --- .../system/js_app/modules/js_widget.c | 172 +++++++----------- 1 file changed, 69 insertions(+), 103 deletions(-) diff --git a/applications/system/js_app/modules/js_widget.c b/applications/system/js_app/modules/js_widget.c index 69279bb7c..0d6aeb1db 100644 --- a/applications/system/js_app/modules/js_widget.c +++ b/applications/system/js_app/modules/js_widget.c @@ -1,6 +1,5 @@ #include -#include -#include +#include #include #include #include @@ -113,8 +112,8 @@ typedef struct { typedef struct { View* view; - ViewDispatcher* view_dispatcher; - FuriThread* thread; + ViewHolder* view_holder; + bool is_shown; } JsWidgetInst; static JsWidgetInst* get_this_ctx(struct mjs* mjs) { @@ -754,60 +753,34 @@ static void js_widget_is_open(struct mjs* mjs) { JsWidgetInst* widget = get_this_ctx(mjs); if(!check_arg_count(mjs, 0)) return; - mjs_return(mjs, mjs_mk_boolean(mjs, !!widget->thread)); -} - -static void widget_deinit(void* context) { - JsWidgetInst* widget = context; - if(widget->thread) { - furi_thread_join(widget->thread); - furi_thread_free(widget->thread); - widget->thread = NULL; - - furi_assert(widget->view_dispatcher); - view_dispatcher_remove_view(widget->view_dispatcher, 0); - view_dispatcher_free(widget->view_dispatcher); - widget->view_dispatcher = NULL; - - furi_record_close(RECORD_GUI); - } + mjs_return(mjs, mjs_mk_boolean(mjs, widget->is_shown)); } static void widget_callback(void* context, uint32_t arg) { UNUSED(arg); - widget_deinit(context); -} - -static bool widget_exit(void* context) { JsWidgetInst* widget = context; - view_dispatcher_stop(widget->view_dispatcher); - furi_timer_pending_callback(widget_callback, widget, 0); - return true; + view_holder_stop(widget->view_holder); + widget->is_shown = false; } -static int32_t widget_thread(void* context) { - ViewDispatcher* view_dispatcher = context; - view_dispatcher_run(view_dispatcher); - return 0; +static void widget_exit(void* context) { + JsWidgetInst* widget = context; + // Using timer to schedule view_holder stop, will not work under high CPU load + furi_timer_pending_callback(widget_callback, widget, 0); } static void js_widget_show(struct mjs* mjs) { JsWidgetInst* widget = get_this_ctx(mjs); if(!check_arg_count(mjs, 0)) return; - Gui* gui = furi_record_open(RECORD_GUI); + if(widget->is_shown) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Widget is already shown"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } - widget->view_dispatcher = view_dispatcher_alloc(); - view_dispatcher_enable_queue(widget->view_dispatcher); - view_dispatcher_add_view(widget->view_dispatcher, 0, widget->view); - view_dispatcher_set_event_callback_context(widget->view_dispatcher, widget); - view_dispatcher_set_navigation_event_callback(widget->view_dispatcher, widget_exit); - view_dispatcher_attach_to_gui(widget->view_dispatcher, gui, ViewDispatcherTypeFullscreen); - view_dispatcher_switch_to_view(widget->view_dispatcher, 0); - - widget->thread = - furi_thread_alloc_ex("JsWidget", 1024, widget_thread, widget->view_dispatcher); - furi_thread_start(widget->thread); + view_holder_start(widget->view_holder); + widget->is_shown = true; mjs_return(mjs, MJS_UNDEFINED); } @@ -816,10 +789,8 @@ static void js_widget_close(struct mjs* mjs) { JsWidgetInst* widget = get_this_ctx(mjs); if(!check_arg_count(mjs, 0)) return; - if(widget->thread) { - view_dispatcher_stop(widget->view_dispatcher); - widget_deinit(widget); - } + view_holder_stop(widget->view_holder); + widget->is_shown = false; mjs_return(mjs, MJS_UNDEFINED); } @@ -839,57 +810,9 @@ static void widget_draw_callback(Canvas* canvas, void* model) { } } -static void widget_remove_view(void* context) { - JsWidgetInst* widget = context; - - if(widget->view) { - with_view_model( - widget->view, - WidgetModel * model, - { - ComponentArray_it_t it; - ComponentArray_it(it, model->component); - while(!ComponentArray_end_p(it)) { - WidgetComponent* component = *ComponentArray_ref(it); - if(component && component->free) { - component->free(component); - } - ComponentArray_next(it); - } - ComponentArray_reset(model->component); - ComponentArray_clear(model->component); - }, - false); - with_view_model( - widget->view, WidgetModel * model, { XbmImageList_clear(model->image); }, false); - view_free(widget->view); - widget->view = NULL; - } -} - -static JsWidgetInst* widget_alloc(void) { - JsWidgetInst* widget = malloc(sizeof(JsWidgetInst)); - widget->thread = NULL; - widget->view_dispatcher = NULL; - - widget->view = view_alloc(); - view_allocate_model(widget->view, ViewModelTypeLockFree, sizeof(WidgetModel)); - view_set_draw_callback(widget->view, widget_draw_callback); - with_view_model( - widget->view, - WidgetModel * model, - { - ComponentArray_init(model->component); - XbmImageList_init(model->image); - model->max_assigned_id = 0; - }, - true); - - return widget; -} - static void* js_widget_create(struct mjs* mjs, mjs_val_t* object) { - JsWidgetInst* widget = widget_alloc(); + JsWidgetInst* widget = malloc(sizeof(JsWidgetInst)); + mjs_val_t widget_obj = mjs_mk_object(mjs); mjs_set(mjs, widget_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, widget)); // addBox(x: number, y: number, w: number, h: number): number (returns id of the added component) @@ -924,17 +847,60 @@ static void* js_widget_create(struct mjs* mjs, mjs_val_t* object) { mjs_set(mjs, widget_obj, "show", ~0, MJS_MK_FN(js_widget_show)); // close(): void (closes the widget) mjs_set(mjs, widget_obj, "close", ~0, MJS_MK_FN(js_widget_close)); + + widget->view = view_alloc(); + view_allocate_model(widget->view, ViewModelTypeLockFree, sizeof(WidgetModel)); + view_set_draw_callback(widget->view, widget_draw_callback); + with_view_model( + widget->view, + WidgetModel * model, + { + ComponentArray_init(model->component); + XbmImageList_init(model->image); + model->max_assigned_id = 0; + }, + true); + + Gui* gui = furi_record_open(RECORD_GUI); + widget->view_holder = view_holder_alloc(); + view_holder_attach_to_gui(widget->view_holder, gui); + view_holder_set_back_callback(widget->view_holder, widget_exit, widget); + view_holder_set_view(widget->view_holder, widget->view); + *object = widget_obj; return widget; } static void js_widget_destroy(void* inst) { JsWidgetInst* widget = inst; - if(widget->thread) { - view_dispatcher_stop(widget->view_dispatcher); - widget_deinit(widget); - } - widget_remove_view(widget); + + view_holder_stop(widget->view_holder); + view_holder_free(widget->view_holder); + widget->view_holder = NULL; + + furi_record_close(RECORD_GUI); + + with_view_model( + widget->view, + WidgetModel * model, + { + ComponentArray_it_t it; + ComponentArray_it(it, model->component); + while(!ComponentArray_end_p(it)) { + WidgetComponent* component = *ComponentArray_ref(it); + if(component && component->free) { + component->free(component); + } + ComponentArray_next(it); + } + ComponentArray_reset(model->component); + ComponentArray_clear(model->component); + XbmImageList_clear(model->image); + }, + false); + view_free(widget->view); + widget->view = NULL; + free(widget); } From 8148c9b5a901e264eafa62eced172fd2d9afc083 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Wed, 26 Jun 2024 03:11:26 +0200 Subject: [PATCH 2/4] JS: Refactor keyboard with ViewHolder Based on submenu refactor by nminaylov --- .../system/js_app/modules/js_keyboard.c | 59 +++++++++---------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/applications/system/js_app/modules/js_keyboard.c b/applications/system/js_app/modules/js_keyboard.c index 2bfb296e0..fc2f76135 100644 --- a/applications/system/js_app/modules/js_keyboard.c +++ b/applications/system/js_app/modules/js_keyboard.c @@ -1,14 +1,16 @@ #include "../js_modules.h" #include #include -#include +#include +#include #define membersof(x) (sizeof(x) / sizeof(x[0])) typedef struct { TextInput* text_input; ByteInput* byte_input; - ViewDispatcher* view_dispatcher; + ViewHolder* view_holder; + FuriApiLock lock; char* header; bool accepted; } JsKeyboardInst; @@ -28,14 +30,13 @@ static JsKeyboardInst* get_this_ctx(struct mjs* mjs) { static void keyboard_callback(void* context) { JsKeyboardInst* keyboard = (JsKeyboardInst*)context; keyboard->accepted = true; - view_dispatcher_stop(keyboard->view_dispatcher); + api_lock_unlock(keyboard->lock); } -static bool keyboard_exit(void* context) { +static void keyboard_exit(void* context) { JsKeyboardInst* keyboard = (JsKeyboardInst*)context; keyboard->accepted = false; - view_dispatcher_stop(keyboard->view_dispatcher); - return true; + api_lock_unlock(keyboard->lock); } static void js_keyboard_set_header(struct mjs* mjs) { @@ -84,22 +85,21 @@ static void js_keyboard_text(struct mjs* mjs) { text_input_set_minimum_length(keyboard->text_input, 0); + keyboard->lock = api_lock_alloc_locked(); Gui* gui = furi_record_open(RECORD_GUI); - keyboard->view_dispatcher = view_dispatcher_alloc(); - view_dispatcher_enable_queue(keyboard->view_dispatcher); - view_dispatcher_add_view( - keyboard->view_dispatcher, 0, text_input_get_view(keyboard->text_input)); - view_dispatcher_set_event_callback_context(keyboard->view_dispatcher, keyboard); - view_dispatcher_set_navigation_event_callback(keyboard->view_dispatcher, keyboard_exit); - view_dispatcher_attach_to_gui(keyboard->view_dispatcher, gui, ViewDispatcherTypeFullscreen); - view_dispatcher_switch_to_view(keyboard->view_dispatcher, 0); + keyboard->view_holder = view_holder_alloc(); + view_holder_attach_to_gui(keyboard->view_holder, gui); + view_holder_set_back_callback(keyboard->view_holder, keyboard_exit, keyboard); - view_dispatcher_run(keyboard->view_dispatcher); + view_holder_set_view(keyboard->view_holder, text_input_get_view(keyboard->text_input)); + view_holder_start(keyboard->view_holder); + api_lock_wait_unlock(keyboard->lock); + + view_holder_stop(keyboard->view_holder); + view_holder_free(keyboard->view_holder); - view_dispatcher_remove_view(keyboard->view_dispatcher, 0); - view_dispatcher_free(keyboard->view_dispatcher); - keyboard->view_dispatcher = NULL; furi_record_close(RECORD_GUI); + api_lock_free(keyboard->lock); text_input_reset(keyboard->text_input); if(keyboard->header) { @@ -141,22 +141,21 @@ static void js_keyboard_byte(struct mjs* mjs) { byte_input_set_result_callback( keyboard->byte_input, keyboard_callback, NULL, keyboard, buffer, input_length); + keyboard->lock = api_lock_alloc_locked(); Gui* gui = furi_record_open(RECORD_GUI); - keyboard->view_dispatcher = view_dispatcher_alloc(); - view_dispatcher_enable_queue(keyboard->view_dispatcher); - view_dispatcher_add_view( - keyboard->view_dispatcher, 0, byte_input_get_view(keyboard->byte_input)); - view_dispatcher_set_event_callback_context(keyboard->view_dispatcher, keyboard); - view_dispatcher_set_navigation_event_callback(keyboard->view_dispatcher, keyboard_exit); - view_dispatcher_attach_to_gui(keyboard->view_dispatcher, gui, ViewDispatcherTypeFullscreen); - view_dispatcher_switch_to_view(keyboard->view_dispatcher, 0); + keyboard->view_holder = view_holder_alloc(); + view_holder_attach_to_gui(keyboard->view_holder, gui); + view_holder_set_back_callback(keyboard->view_holder, keyboard_exit, keyboard); - view_dispatcher_run(keyboard->view_dispatcher); + view_holder_set_view(keyboard->view_holder, byte_input_get_view(keyboard->byte_input)); + view_holder_start(keyboard->view_holder); + api_lock_wait_unlock(keyboard->lock); + + view_holder_stop(keyboard->view_holder); + view_holder_free(keyboard->view_holder); - view_dispatcher_remove_view(keyboard->view_dispatcher, 0); - view_dispatcher_free(keyboard->view_dispatcher); - keyboard->view_dispatcher = NULL; furi_record_close(RECORD_GUI); + api_lock_free(keyboard->lock); if(keyboard->header) { free(keyboard->header); From 1d41944182e5d687615ca21c6376baa1a6f13e12 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Tue, 25 Jun 2024 04:35:07 +0200 Subject: [PATCH 3/4] NFC: EMV Transactions less nested, hide if unavailable --- .../nfc/helpers/protocol_support/emv/emv.c | 44 ++++++++--- .../main/nfc/scenes/nfc_scene_config.h | 2 +- .../main/nfc/scenes/nfc_scene_emv_more_info.c | 76 ------------------- .../nfc/scenes/nfc_scene_emv_transactions.c | 31 ++++++++ 4 files changed, 67 insertions(+), 86 deletions(-) delete mode 100644 applications/main/nfc/scenes/nfc_scene_emv_more_info.c create mode 100644 applications/main/nfc/scenes/nfc_scene_emv_transactions.c diff --git a/applications/main/nfc/helpers/protocol_support/emv/emv.c b/applications/main/nfc/helpers/protocol_support/emv/emv.c index 728aabefe..06e2ca624 100644 --- a/applications/main/nfc/helpers/protocol_support/emv/emv.c +++ b/applications/main/nfc/helpers/protocol_support/emv/emv.c @@ -9,6 +9,10 @@ #include "../nfc_protocol_support_gui_common.h" #include "../iso14443_4a/iso14443_4a_i.h" +enum { + SubmenuIndexTransactions = SubmenuIndexCommonMax, +}; + static void nfc_scene_info_on_enter_emv(NfcApp* instance) { const NfcDevice* device = instance->nfc_device; const EmvData* data = nfc_device_get_data(device, NfcProtocolEmv); @@ -24,11 +28,6 @@ static void nfc_scene_info_on_enter_emv(NfcApp* instance) { furi_string_free(temp_str); } -static void nfc_scene_more_info_on_enter_emv(NfcApp* instance) { - // Jump to advanced scene right away - scene_manager_next_scene(instance->scene_manager, NfcSceneEmvMoreInfo); -} - static NfcCommand nfc_scene_read_poller_callback_emv(NfcGenericEvent event, void* context) { furi_assert(event.protocol == NfcProtocolEmv); @@ -49,6 +48,20 @@ static void nfc_scene_read_on_enter_emv(NfcApp* instance) { nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_emv, instance); } +static void nfc_scene_read_menu_on_enter_emv(NfcApp* instance) { + Submenu* submenu = instance->submenu; + const EmvData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolEmv); + + if(data->emv_application.active_tr > 0) { + submenu_add_item( + submenu, + "Transactions", + SubmenuIndexTransactions, + nfc_protocol_support_common_submenu_callback, + instance); + } +} + static void nfc_scene_read_success_on_enter_emv(NfcApp* instance) { const NfcDevice* device = instance->nfc_device; const EmvData* data = nfc_device_get_data(device, NfcProtocolEmv); @@ -64,8 +77,21 @@ static void nfc_scene_read_success_on_enter_emv(NfcApp* instance) { furi_string_free(temp_str); } +static bool nfc_scene_read_menu_on_event_emv(NfcApp* instance, SceneManagerEvent event) { + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexTransactions) { + scene_manager_next_scene(instance->scene_manager, NfcSceneEmvTransactions); + consumed = true; + } + } + + return consumed; +} + const NfcProtocolSupportBase nfc_protocol_support_emv = { - .features = NfcProtocolFeatureMoreInfo, + .features = NfcProtocolFeatureNone, .scene_info = { @@ -74,7 +100,7 @@ const NfcProtocolSupportBase nfc_protocol_support_emv = { }, .scene_more_info = { - .on_enter = nfc_scene_more_info_on_enter_emv, + .on_enter = nfc_protocol_support_common_on_enter_empty, .on_event = nfc_protocol_support_common_on_event_empty, }, .scene_read = @@ -84,8 +110,8 @@ const NfcProtocolSupportBase nfc_protocol_support_emv = { }, .scene_read_menu = { - .on_enter = nfc_protocol_support_common_on_enter_empty, - .on_event = nfc_protocol_support_common_on_event_empty, + .on_enter = nfc_scene_read_menu_on_enter_emv, + .on_event = nfc_scene_read_menu_on_event_emv, }, .scene_read_success = { diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index 2457fb156..dd63dd3da 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -39,7 +39,7 @@ ADD_SCENE(nfc, felica_unlock_warn, FelicaUnlockWarn) ADD_SCENE(nfc, mf_desfire_more_info, MfDesfireMoreInfo) ADD_SCENE(nfc, mf_desfire_app, MfDesfireApp) -ADD_SCENE(nfc, emv_more_info, EmvMoreInfo) +ADD_SCENE(nfc, emv_transactions, EmvTransactions) ADD_SCENE(nfc, mf_classic_dict_attack, MfClassicDictAttack) ADD_SCENE(nfc, mf_classic_detect_reader, MfClassicDetectReader) diff --git a/applications/main/nfc/scenes/nfc_scene_emv_more_info.c b/applications/main/nfc/scenes/nfc_scene_emv_more_info.c deleted file mode 100644 index 08f373496..000000000 --- a/applications/main/nfc/scenes/nfc_scene_emv_more_info.c +++ /dev/null @@ -1,76 +0,0 @@ -#include "../nfc_app_i.h" - -#include "../helpers/protocol_support/nfc_protocol_support_gui_common.h" -#include "../helpers/protocol_support/emv/emv_render.h" - -enum { - EmvMoreInfoStateMenu, - EmvMoreInfoStateItem, // MUST be last, states >= this correspond with submenu index -}; - -enum SubmenuIndex { - SubmenuIndexTransactions, - SubmenuIndexDynamic, // dynamic indices start here -}; - -void nfc_scene_emv_more_info_on_enter(void* context) { - NfcApp* nfc = context; - Submenu* submenu = nfc->submenu; - - text_box_set_font(nfc->text_box, TextBoxFontHex); - - submenu_add_item( - submenu, - "Transactions", - SubmenuIndexTransactions, - nfc_protocol_support_common_submenu_callback, - nfc); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); -} - -bool nfc_scene_emv_more_info_on_event(void* context, SceneManagerEvent event) { - NfcApp* nfc = context; - bool consumed = false; - - const uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneEmvMoreInfo); - const EmvData* data = nfc_device_get_data(nfc->nfc_device, NfcProtocolEmv); - - if(event.type == SceneManagerEventTypeCustom) { - widget_reset(nfc->widget); - - if(event.event == SubmenuIndexTransactions) { - FuriString* temp_str = furi_string_alloc(); - nfc_render_emv_transactions(&data->emv_application, temp_str); - widget_add_text_scroll_element( - nfc->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); - furi_string_free(temp_str); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); - scene_manager_set_scene_state( - nfc->scene_manager, - NfcSceneEmvMoreInfo, - EmvMoreInfoStateItem + SubmenuIndexTransactions); - consumed = true; - } - } else if(event.type == SceneManagerEventTypeBack) { - if(state >= EmvMoreInfoStateItem) { - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneEmvMoreInfo, EmvMoreInfoStateMenu); - } else { - // Return directly to the Info scene - scene_manager_search_and_switch_to_previous_scene(nfc->scene_manager, NfcSceneInfo); - } - consumed = true; - } - - return consumed; -} - -void nfc_scene_emv_more_info_on_exit(void* context) { - NfcApp* nfc = context; - - // Clear views - widget_reset(nfc->widget); - submenu_reset(nfc->submenu); -} diff --git a/applications/main/nfc/scenes/nfc_scene_emv_transactions.c b/applications/main/nfc/scenes/nfc_scene_emv_transactions.c new file mode 100644 index 000000000..e04434831 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_emv_transactions.c @@ -0,0 +1,31 @@ +#include "../nfc_app_i.h" + +#include "../helpers/protocol_support/nfc_protocol_support_gui_common.h" +#include "../helpers/protocol_support/emv/emv_render.h" + +void nfc_scene_emv_transactions_on_enter(void* context) { + NfcApp* nfc = context; + Widget* widget = nfc->widget; + const EmvData* data = nfc_device_get_data(nfc->nfc_device, NfcProtocolEmv); + + FuriString* temp_str = furi_string_alloc(); + nfc_render_emv_transactions(&data->emv_application, temp_str); + + widget_add_text_scroll_element(widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); +} + +bool nfc_scene_emv_transactions_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} + +void nfc_scene_emv_transactions_on_exit(void* context) { + NfcApp* nfc = context; + + widget_reset(nfc->widget); +} From 160d6c319cf6952e7f0e608addd9d6ad8a688d45 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 27 Jun 2024 14:18:09 +0300 Subject: [PATCH 4/4] upd changelog --- CHANGELOG.md | 56 +++++----------------------------------------------- 1 file changed, 5 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa7375309..43225cd10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,57 +1,11 @@ ## Main changes -- SubGHz: - - Add new protocol - legrand 18bit (by @user890104) - - OFW: Princeton protocol add custom guard time support - - Princeton fix guard time bounds and show guard time multiplier in UI -- NFC: - - Fix Mifare DESFire reading (revert of buffer check workaround for rare emv cases) (some emv cards can be read only via Extra Actions -> Read specific card type -> EMV) - - Better plugins(parsers) loading - much faster emulation launch from favourites, no more lags in Saved menu - - OFW: MF Ultralight Original write support - - OFW: Mifare Plus detection support - - OFW: Felica emulation - - OFW: Write to ultralight cards is now possible (no UID writing) - - OFW: Fixed infinite loop in dictionary attack scene -* LF RFID: OFW: Added Support for Securakey Protocol -* JS: `adc` support in `gpio` module (by @jamisonderek) -* JS: `storage` module (without virtual mount API at the moment) (by @Willy-JL) -* BadUSB: Add Finnish keyboard layout (by @nicou | PR #761) -* Archive: Fix SubGHz Remote files in favourites falling into non working and non removable state +* NFC: EMV Transactions less nested, hide if unavailable (by @Willy-JL | PR #771) +* JS: Refactor widget and keyboard modules, fix crash (by @Willy-JL | PR #770) * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes -* SubGHz: Fix add manually princeton -* SubGHz: Sync signal delete scene with OFW -* SubGHz: Fix incorrect rx key state when opening Read menu -* SubGHz: Fix incorrect state in decode raw exit - causing keys to be not removed from history and showing up in Read menu after exit from decode raw -* Misc: Remove outdated brew sdk install files -* Misc: Revert USB CDC changes to fix usb serial -* Misc: Fix usage of deprecated `icon_get_data` -* Loader: Better API Mismatch message (by @Willy-JL) -* CLI: Move part of the CLI to microsd to free up space for COMPACT 0 builds (by @Willy-JL) -* NFC: Fix typo in parsers -* Apps: Fix `input_callback` and `timer_callback` usage of non `void` argument as input -* LF RFID: OFW PR 3728: Securakey - Add Support for RKKTH Plain Text Format (by @zinongli) -* OFW: ReadMe: update outdated bits and pieces -* OFW: Debug: backup openocd work area, fix crash after fresh debugger connect and continue -* OFW: ELF, Flipper application: do not crash on "out of memory" -* OFW: MF Plus - Don't crash on reading weird cards -* OFW: SubGhz: fix Missed the "Deleted" screen when deleting RAW Subghz (by @Skorpionm) -* OFW: JS: Disable logging in mjs +2k free flash (by @hedger) -* OFW: Archive: fix memory leak in favorites add/remove -* OFW: Furi: Fix EventLoop state persisting on same thread after free -* OFW: Cli: top -* OFW: Desktop lockup fix, GUI improvements -* OFW: Loader: fix crash on "locked via cli loader" -* OFW: SubGhz: fix navigation GUI -* OFW: Furi: event loop -* OFW: Code Cleanup: unused includes, useless checks, unused variables, etc... -* OFW: SubGhz: fix gui "No transition to the "Saved" menu when deleting a SubGHz RAW file" -* OFW: RPC: Add TarExtract command, some small fixes -* OFW: Use static synchronisation primitives -* OFW: cleanup of various warnings from clangd -* OFW: Add initial ISO7816 support -* OFW: fbt, vscode: tweaks for cdb generation for clangd -* OFW: Updater: fix inability to update with bigger updater.bin -* OFW: Furi: wrap message queue in container, prepare it for epoll. Accessor: disable expansion service on start. +* OFW: NFC: Desfire Renderer Minor Debug +* OFW: RPC: Fix input lockup on disconnect +* OFW: Thread Signals

#### Known NFC post-refactor regressions list: - Mifare Mini clones reading is broken (original mini working fine) (OFW)