diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 252310af6..2bc2178ae 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,7 +19,7 @@ jobs: strategy: fail-fast: false matrix: - target: [f7, f18] + target: [f7, f18] steps: - name: 'Wipe workspace' run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; @@ -103,29 +103,12 @@ jobs: run: | cp build/${DEFAULT_TARGET}-firmware-*/firmware.elf.map map_analyser_files/firmware.elf.map cp build/${DEFAULT_TARGET}-firmware-*/firmware.elf map_analyser_files/firmware.elf - cp ${{ github.event_path }} map_analyser_files/event.json source scripts/toolchain/fbtenv.sh - get_size() - { - SECTION="$1"; - arm-none-eabi-size \ - -A map_analyser_files/firmware.elf \ - | grep "^$SECTION" | awk '{print $2}' - } - export BSS_SIZE="$(get_size ".bss")" - export TEXT_SIZE="$(get_size ".text")" - export RODATA_SIZE="$(get_size ".rodata")" - export DATA_SIZE="$(get_size ".data")" - export FREE_FLASH_SIZE="$(get_size ".free_flash")" - python3 -m pip install mariadb==1.1.6 cxxfilt==0.3.0 - python3 scripts/map_parser.py map_analyser_files/firmware.elf.map map_analyser_files/firmware.elf.map.all - python3 scripts/map_mariadb_insert.py \ - ${{ secrets.AMAP_MARIADB_USER }} \ - ${{ secrets.AMAP_MARIADB_PASSWORD }} \ - ${{ secrets.AMAP_MARIADB_HOST }} \ - ${{ secrets.AMAP_MARIADB_PORT }} \ - ${{ secrets.AMAP_MARIADB_DATABASE }} \ - map_analyser_files/firmware.elf.map.all + python3 scripts/map_analyse_upload.py \ + "--elf_file=map_analyser_files/firmware.elf" \ + "--map_file=map_analyser_files/firmware.elf.map" \ + "--analyser_url=${{ secrets.ANALYSER_URL }}" \ + "--analyser_token=${{ secrets.ANALYSER_TOKEN }}"; - name: 'Find previous comment' if: ${{ !github.event.pull_request.head.repo.fork && matrix.target == env.DEFAULT_TARGET && github.event.pull_request }} diff --git a/.github/workflows/lint_and_submodule_check.yml b/.github/workflows/lint_and_submodule_check.yml index 3f4d8c79b..3063d943d 100644 --- a/.github/workflows/lint_and_submodule_check.yml +++ b/.github/workflows/lint_and_submodule_check.yml @@ -54,6 +54,8 @@ jobs: echo "\`\`\`" >> $GITHUB_STEP_SUMMARY; echo "$MISSING_TICKETS" >> $GITHUB_STEP_SUMMARY; echo "\`\`\`" >> $GITHUB_STEP_SUMMARY; + echo "Error: Missing issue number in comment(s):"; + echo "$MISSING_TICKETS"; exit 1; else echo "No new TODOs without tickets found" >> $GITHUB_STEP_SUMMARY; diff --git a/applications/debug/unit_tests/nfc/nfc_test.c b/applications/debug/unit_tests/nfc/nfc_test.c index c6304d53c..8bb88df9a 100644 --- a/applications/debug/unit_tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/nfc/nfc_test.c @@ -13,6 +13,12 @@ #include #include #include +#include +#include +#include +#include +#include + #include #include @@ -42,6 +48,19 @@ typedef struct { FuriThreadId thread_id; } NfcTestMfClassicSendFrameTest; +typedef enum { + NfcTestSlixPollerSetPasswordStateGetRandomNumber, + NfcTestSlixPollerSetPasswordStateSetPassword, +} NfcTestSlixPollerSetPasswordState; + +typedef struct { + FuriThreadId thread_id; + NfcTestSlixPollerSetPasswordState state; + SlixRandomNumber random_number; + SlixPassword password; + SlixError error; +} NfcTestSlixPollerSetPasswordContext; + typedef struct { Storage* storage; } NfcTest; @@ -627,6 +646,127 @@ MU_TEST(mf_classic_dict_test) { "Remove test dict failed"); } +MU_TEST(slix_file_with_capabilities_test) { + NfcDevice* nfc_device_missed_cap = nfc_device_alloc(); + mu_assert( + nfc_device_load(nfc_device_missed_cap, EXT_PATH("unit_tests/nfc/Slix_cap_missed.nfc")), + "nfc_device_load() failed\r\n"); + + NfcDevice* nfc_device_default_cap = nfc_device_alloc(); + mu_assert( + nfc_device_load(nfc_device_default_cap, EXT_PATH("unit_tests/nfc/Slix_cap_default.nfc")), + "nfc_device_load() failed\r\n"); + + mu_assert( + nfc_device_is_equal(nfc_device_missed_cap, nfc_device_default_cap), + "nfc_device_is_equal() failed\r\n"); + + nfc_device_free(nfc_device_default_cap); + nfc_device_free(nfc_device_missed_cap); +} + +NfcCommand slix_poller_set_password_callback(NfcGenericEventEx event, void* context) { + furi_check(event.poller); + furi_check(event.parent_event_data); + furi_check(context); + + NfcCommand command = NfcCommandContinue; + Iso15693_3PollerEvent* iso15_event = event.parent_event_data; + SlixPoller* poller = event.poller; + NfcTestSlixPollerSetPasswordContext* slix_ctx = context; + + if(iso15_event->type == Iso15693_3PollerEventTypeReady) { + iso15693_3_copy( + poller->data->iso15693_3_data, iso15693_3_poller_get_data(poller->iso15693_3_poller)); + + if(slix_ctx->state == NfcTestSlixPollerSetPasswordStateGetRandomNumber) { + slix_ctx->error = slix_poller_get_random_number(poller, &slix_ctx->random_number); + if(slix_ctx->error != SlixErrorNone) { + furi_thread_flags_set(slix_ctx->thread_id, NFC_TEST_FLAG_WORKER_DONE); + command = NfcCommandStop; + } else { + slix_ctx->state = NfcTestSlixPollerSetPasswordStateSetPassword; + } + } else if(slix_ctx->state == NfcTestSlixPollerSetPasswordStateSetPassword) { + slix_ctx->error = slix_poller_set_password( + poller, SlixPasswordTypeRead, slix_ctx->password, slix_ctx->random_number); + furi_thread_flags_set(slix_ctx->thread_id, NFC_TEST_FLAG_WORKER_DONE); + command = NfcCommandStop; + } + } else { + slix_ctx->error = slix_process_iso15693_3_error(iso15_event->data->error); + furi_thread_flags_set(slix_ctx->thread_id, NFC_TEST_FLAG_WORKER_DONE); + command = NfcCommandStop; + } + + return command; +} + +static void slix_set_password_test(const char* file_path, SlixPassword pass, bool correct_pass) { + FURI_LOG_I(TAG, "Testing file: %s", file_path); + + Nfc* poller = nfc_alloc(); + Nfc* listener = nfc_alloc(); + + NfcDevice* nfc_device = nfc_device_alloc(); + mu_assert(nfc_device_load(nfc_device, file_path), "nfc_device_load() failed\r\n"); + + const SlixData* slix_data = nfc_device_get_data(nfc_device, NfcProtocolSlix); + NfcListener* slix_listener = nfc_listener_alloc(listener, NfcProtocolSlix, slix_data); + nfc_listener_start(slix_listener, NULL, NULL); + + SlixCapabilities slix_capabilities = slix_data->capabilities; + + NfcPoller* slix_poller = nfc_poller_alloc(poller, NfcProtocolSlix); + + NfcTestSlixPollerSetPasswordContext slix_poller_context = { + .thread_id = furi_thread_get_current_id(), + .state = NfcTestSlixPollerSetPasswordStateGetRandomNumber, + .password = pass, + .error = SlixErrorNone, + }; + + nfc_poller_start_ex(slix_poller, slix_poller_set_password_callback, &slix_poller_context); + + uint32_t flag = + furi_thread_flags_wait(NFC_TEST_FLAG_WORKER_DONE, FuriFlagWaitAny, FuriWaitForever); + mu_assert(flag == NFC_TEST_FLAG_WORKER_DONE, "Wrong thread flag\r\n"); + + nfc_poller_stop(slix_poller); + nfc_poller_free(slix_poller); + nfc_listener_stop(slix_listener); + nfc_listener_free(slix_listener); + + mu_assert( + slix_poller_context.state == NfcTestSlixPollerSetPasswordStateSetPassword, + "Poller failed before setting password\r\n"); + + if((slix_capabilities == SlixCapabilitiesAcceptAllPasswords) || (correct_pass)) { + mu_assert(slix_poller_context.error == SlixErrorNone, "Failed to set password\r\n"); + } else { + mu_assert( + slix_poller_context.error == SlixErrorTimeout, + "Must have received SlixErrorTimeout\r\n"); + } + + nfc_device_free(nfc_device); + nfc_free(listener); + nfc_free(poller); +} + +MU_TEST(slix_set_password_default_cap_correct_pass) { + slix_set_password_test(EXT_PATH("unit_tests/nfc/Slix_cap_default.nfc"), 0x00000000, true); +} + +MU_TEST(slix_set_password_default_cap_incorrect_pass) { + slix_set_password_test(EXT_PATH("unit_tests/nfc/Slix_cap_default.nfc"), 0x12341234, false); +} + +MU_TEST(slix_set_password_access_all_passwords_cap) { + slix_set_password_test( + EXT_PATH("unit_tests/nfc/Slix_cap_accept_all_pass.nfc"), 0x12341234, false); +} + MU_TEST_SUITE(nfc) { nfc_test_alloc(); @@ -668,6 +808,11 @@ MU_TEST_SUITE(nfc) { MU_RUN_TEST(mf_classic_send_frame_test); MU_RUN_TEST(mf_classic_dict_test); + MU_RUN_TEST(slix_file_with_capabilities_test); + MU_RUN_TEST(slix_set_password_default_cap_correct_pass); + MU_RUN_TEST(slix_set_password_default_cap_incorrect_pass); + MU_RUN_TEST(slix_set_password_access_all_passwords_cap); + nfc_test_free(); } diff --git a/applications/debug/unit_tests/nfc/nfc_transport.c b/applications/debug/unit_tests/nfc/nfc_transport.c index 6886ef66d..df0e009e7 100644 --- a/applications/debug/unit_tests/nfc/nfc_transport.c +++ b/applications/debug/unit_tests/nfc/nfc_transport.c @@ -55,6 +55,7 @@ struct Nfc { Iso14443_3aColResStatus col_res_status; Iso14443_3aColResData col_res_data; + bool software_col_res_required; NfcEventCallback callback; void* context; @@ -170,6 +171,7 @@ NfcError nfc_iso14443a_listener_set_col_res_data( furi_check(atqa); nfc_prepare_col_res_data(instance, uid, uid_len, atqa, sak); + instance->software_col_res_required = true; return NfcErrorNone; } @@ -275,7 +277,8 @@ static int32_t nfc_worker_listener(void* context) { } else if(message.type == NfcMessageTypeTx) { nfc_test_print( NfcTransportLogLevelInfo, "RDR", message.data.data, message.data.data_bits); - if(instance->col_res_status != Iso14443_3aColResStatusDone) { + if(instance->software_col_res_required && + (instance->col_res_status != Iso14443_3aColResStatusDone)) { nfc_worker_listener_pass_col_res( instance, message.data.data, message.data.data_bits); } else { diff --git a/applications/debug/unit_tests/resources/unit_tests/nfc/Slix_cap_accept_all_pass.nfc b/applications/debug/unit_tests/resources/unit_tests/nfc/Slix_cap_accept_all_pass.nfc new file mode 100644 index 000000000..0a8db9ae9 --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/nfc/Slix_cap_accept_all_pass.nfc @@ -0,0 +1,41 @@ +Filetype: Flipper NFC device +Version: 4 +# Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, ISO14443-4B, ISO15693-3, FeliCa, NTAG/Ultralight, Mifare Classic, Mifare DESFire, SLIX, ST25TB +Device type: SLIX +# UID is common for all formats +UID: E0 04 01 08 49 D0 DC 81 +# ISO15693-3 specific data +# Data Storage Format Identifier +DSFID: 01 +# Application Family Identifier +AFI: 3D +# IC Reference - Vendor specific meaning +IC Reference: 01 +# Lock Bits +Lock DSFID: true +Lock AFI: true +# Number of memory blocks, valid range = 1..256 +Block Count: 80 +# Size of a single memory block, valid range = 01...20 (hex) +Block Size: 04 +Data Content: 03 0A 82 ED 86 39 61 D2 03 14 1E 32 B6 CA 00 3C 36 42 0C 33 53 30 37 32 32 34 30 30 00 00 00 00 00 FF 04 01 01 00 00 00 A3 03 1E 00 26 00 00 00 00 00 0F 00 76 03 65 01 00 00 00 00 85 01 34 00 75 09 05 00 01 00 00 00 00 00 00 00 00 00 00 00 D7 FA 00 1C 9E 1C 67 27 00 30 30 30 30 30 30 30 30 30 30 00 00 00 97 25 55 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 32 8C 00 30 53 48 80 DE 00 00 00 00 F4 C3 58 2B 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 11 F3 00 2C DD C3 3E 91 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E5 FF 00 01 +# Block Security Status: 01 = locked, 00 = not locked +Security Status: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# SLIX specific data +# SLIX capabilities field affects emulation modes. Possible options: Default, AcceptAllPasswords +Capabilities: AcceptAllPasswords +# Passwords are optional. If a password is omitted, a default value will be used +Password Read: 00 00 00 00 +Password Write: 00 00 00 00 +Password Privacy: 0F 0F 0F 0F +Password Destroy: 0F 0F 0F 0F +Password EAS: 00 00 00 00 +# This is the card's secp128r1 elliptic curve signature. It can not be calculated without knowing NXP's private key. +Signature: A6 25 54 03 74 24 C4 38 36 F4 89 70 76 1A 72 27 54 D9 E7 3D 38 CB 4C 1B 3E FD 0E DF 8A F6 7E 3D +Privacy Mode: false +# Protection pointer configuration +Protection Pointer: 32 +Protection Condition: 02 +# SLIX Lock Bits +Lock EAS: true +Lock PPL: true diff --git a/applications/debug/unit_tests/resources/unit_tests/nfc/Slix_cap_default.nfc b/applications/debug/unit_tests/resources/unit_tests/nfc/Slix_cap_default.nfc new file mode 100644 index 000000000..d1af957dc --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/nfc/Slix_cap_default.nfc @@ -0,0 +1,41 @@ +Filetype: Flipper NFC device +Version: 4 +# Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, ISO14443-4B, ISO15693-3, FeliCa, NTAG/Ultralight, Mifare Classic, Mifare DESFire, SLIX, ST25TB +Device type: SLIX +# UID is common for all formats +UID: E0 04 01 08 49 D0 DC 81 +# ISO15693-3 specific data +# Data Storage Format Identifier +DSFID: 01 +# Application Family Identifier +AFI: 3D +# IC Reference - Vendor specific meaning +IC Reference: 01 +# Lock Bits +Lock DSFID: true +Lock AFI: true +# Number of memory blocks, valid range = 1..256 +Block Count: 80 +# Size of a single memory block, valid range = 01...20 (hex) +Block Size: 04 +Data Content: 03 0A 82 ED 86 39 61 D2 03 14 1E 32 B6 CA 00 3C 36 42 0C 33 53 30 37 32 32 34 30 30 00 00 00 00 00 FF 04 01 01 00 00 00 A3 03 1E 00 26 00 00 00 00 00 0F 00 76 03 65 01 00 00 00 00 85 01 34 00 75 09 05 00 01 00 00 00 00 00 00 00 00 00 00 00 D7 FA 00 1C 9E 1C 67 27 00 30 30 30 30 30 30 30 30 30 30 00 00 00 97 25 55 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 32 8C 00 30 53 48 80 DE 00 00 00 00 F4 C3 58 2B 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 11 F3 00 2C DD C3 3E 91 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E5 FF 00 01 +# Block Security Status: 01 = locked, 00 = not locked +Security Status: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# SLIX specific data +# SLIX capabilities field affects emulation modes. Possible options: Default, AcceptAllPasswords +Capabilities: Default +# Passwords are optional. If a password is omitted, a default value will be used +Password Read: 00 00 00 00 +Password Write: 00 00 00 00 +Password Privacy: 0F 0F 0F 0F +Password Destroy: 0F 0F 0F 0F +Password EAS: 00 00 00 00 +# This is the card's secp128r1 elliptic curve signature. It can not be calculated without knowing NXP's private key. +Signature: A6 25 54 03 74 24 C4 38 36 F4 89 70 76 1A 72 27 54 D9 E7 3D 38 CB 4C 1B 3E FD 0E DF 8A F6 7E 3D +Privacy Mode: false +# Protection pointer configuration +Protection Pointer: 32 +Protection Condition: 02 +# SLIX Lock Bits +Lock EAS: true +Lock PPL: true diff --git a/applications/debug/unit_tests/resources/unit_tests/nfc/Slix_cap_missed.nfc b/applications/debug/unit_tests/resources/unit_tests/nfc/Slix_cap_missed.nfc new file mode 100644 index 000000000..35650b0e8 --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/nfc/Slix_cap_missed.nfc @@ -0,0 +1,39 @@ +Filetype: Flipper NFC device +Version: 4 +# Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, ISO14443-4B, ISO15693-3, FeliCa, NTAG/Ultralight, Mifare Classic, Mifare DESFire, SLIX, ST25TB +Device type: SLIX +# UID is common for all formats +UID: E0 04 01 08 49 D0 DC 81 +# ISO15693-3 specific data +# Data Storage Format Identifier +DSFID: 01 +# Application Family Identifier +AFI: 3D +# IC Reference - Vendor specific meaning +IC Reference: 01 +# Lock Bits +Lock DSFID: true +Lock AFI: true +# Number of memory blocks, valid range = 1..256 +Block Count: 80 +# Size of a single memory block, valid range = 01...20 (hex) +Block Size: 04 +Data Content: 03 0A 82 ED 86 39 61 D2 03 14 1E 32 B6 CA 00 3C 36 42 0C 33 53 30 37 32 32 34 30 30 00 00 00 00 00 FF 04 01 01 00 00 00 A3 03 1E 00 26 00 00 00 00 00 0F 00 76 03 65 01 00 00 00 00 85 01 34 00 75 09 05 00 01 00 00 00 00 00 00 00 00 00 00 00 D7 FA 00 1C 9E 1C 67 27 00 30 30 30 30 30 30 30 30 30 30 00 00 00 97 25 55 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 32 8C 00 30 53 48 80 DE 00 00 00 00 F4 C3 58 2B 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 11 F3 00 2C DD C3 3E 91 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E5 FF 00 01 +# Block Security Status: 01 = locked, 00 = not locked +Security Status: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# SLIX specific data +# Passwords are optional. If a password is omitted, a default value will be used +Password Read: 00 00 00 00 +Password Write: 00 00 00 00 +Password Privacy: 0F 0F 0F 0F +Password Destroy: 0F 0F 0F 0F +Password EAS: 00 00 00 00 +# This is the card's secp128r1 elliptic curve signature. It can not be calculated without knowing NXP's private key. +Signature: A6 25 54 03 74 24 C4 38 36 F4 89 70 76 1A 72 27 54 D9 E7 3D 38 CB 4C 1B 3E FD 0E DF 8A F6 7E 3D +Privacy Mode: false +# Protection pointer configuration +Protection Pointer: 32 +Protection Condition: 02 +# SLIX Lock Bits +Lock EAS: true +Lock PPL: true diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index b6a0f09d3..898434bb7 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -182,6 +182,15 @@ App( sources=["plugins/supported_cards/itso.c"], ) +App( + appid="skylanders_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="skylanders_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/skylanders.c"], +) + App( appid="nfc_start", targets=["f7"], diff --git a/applications/main/nfc/plugins/supported_cards/skylanders.c b/applications/main/nfc/plugins/supported_cards/skylanders.c new file mode 100644 index 000000000..fca1c3185 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/skylanders.c @@ -0,0 +1,865 @@ +#include "nfc_supported_card_plugin.h" + +#include + +#include +#include +#include + +#define TAG "Skylanders" + +static const uint64_t skylanders_key = 0x4b0b20107ccb; + +bool skylanders_verify(Nfc* nfc) { + bool verified = false; + + do { + const uint8_t verify_sector = 0; + uint8_t block_num = mf_classic_get_first_block_num_of_sector(verify_sector); + FURI_LOG_D(TAG, "Verifying sector %u", verify_sector); + + MfClassicKey key = {}; + bit_lib_num_to_bytes_be(skylanders_key, COUNT_OF(key.data), key.data); + + MfClassicAuthContext auth_ctx = {}; + MfClassicError error = + mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_ctx); + + if(error != MfClassicErrorNone) { + FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error); + break; + } + + verified = true; + } while(false); + + return verified; +} + +static bool skylanders_read(Nfc* nfc, NfcDevice* device) { + furi_assert(nfc); + furi_assert(device); + + bool is_read = false; + + MfClassicData* data = mf_classic_alloc(); + nfc_device_copy_data(device, NfcProtocolMfClassic, data); + + do { + MfClassicType type = MfClassicType1k; + MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); + if(error != MfClassicErrorNone) break; + + data->type = type; + MfClassicDeviceKeys keys = {}; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + bit_lib_num_to_bytes_be(skylanders_key, sizeof(MfClassicKey), keys.key_a[i].data); + FURI_BIT_SET(keys.key_a_mask, i); + bit_lib_num_to_bytes_be(skylanders_key, sizeof(MfClassicKey), keys.key_b[i].data); + FURI_BIT_SET(keys.key_b_mask, i); + } + + error = mf_classic_poller_sync_read(nfc, &keys, data); + if(error != MfClassicErrorNone) { + FURI_LOG_W(TAG, "Failed to read data"); + break; + } + + nfc_device_set_data(device, NfcProtocolMfClassic, data); + + is_read = mf_classic_is_card_read(data); + } while(false); + + mf_classic_free(data); + + return is_read; +} + +static uint8_t fill_name(const uint16_t id, FuriString* name) { + // USED RESEARCH FROM https://github.com/silicontrip/SkyReader/blob/master/toynames.cpp#L15C1-L163C1 + // AND https://github.com/bettse/Solarbreeze/blob/master/Solarbreeze/ThePoster.swift#L438C1-L681C1 + switch(id) { + case 0x0000: + furi_string_cat_printf(name, "Whirlwind"); + break; + case 0x0001: + furi_string_cat_printf(name, "Sonic Boom"); + break; + case 0x0002: + furi_string_cat_printf(name, "Warnado"); + break; + case 0x0003: + furi_string_cat_printf(name, "Lightning Rod"); + break; + case 0x0004: + case 0x0194: + furi_string_cat_printf(name, "Bash"); + break; + case 0x0005: + furi_string_cat_printf(name, "Terrafin"); + break; + case 0x0006: + furi_string_cat_printf(name, "Dino-Rang"); + break; + case 0x0007: + furi_string_cat_printf(name, "Prism Break"); + break; + case 0x0008: + furi_string_cat_printf(name, "Sunburn"); + break; + case 0x0009: + furi_string_cat_printf(name, "Eruptor"); + break; + case 0x000A: + furi_string_cat_printf(name, "Ignitor"); + break; + case 0x000B: + furi_string_cat_printf(name, "Flameslinger"); + break; + case 0x000C: + furi_string_cat_printf(name, "Zap"); + break; + case 0x000D: + furi_string_cat_printf(name, "Wham-Shell"); + break; + case 0x000E: + furi_string_cat_printf(name, "Gill Grunt"); + break; + case 0x000F: + furi_string_cat_printf(name, "Slam Bam"); + break; + case 0x0010: + case 0x01A0: + furi_string_cat_printf(name, "Spyro"); + break; + case 0x0011: + furi_string_cat_printf(name, "Voodood"); + break; + case 0x0012: + furi_string_cat_printf(name, "Double Trouble"); + break; + case 0x0013: + case 0x01A3: + furi_string_cat_printf(name, "Trigger Happy"); + break; + case 0x0014: + furi_string_cat_printf(name, "Drobot"); + break; + case 0x0015: + furi_string_cat_printf(name, "Drill Sergeant"); + break; + case 0x0016: + furi_string_cat_printf(name, "Boomer"); + break; + case 0x0017: + furi_string_cat_printf(name, "Wrecking Ball"); + break; + case 0x0018: + furi_string_cat_printf(name, "Camo"); + break; + case 0x0019: + furi_string_cat_printf(name, "Zook"); + break; + case 0x001A: + furi_string_cat_printf(name, "Stealth Elf"); + break; + case 0x001B: + furi_string_cat_printf(name, "Stump Smash"); + break; + case 0x001C: + furi_string_cat_printf(name, "Dark Spyro"); + break; + case 0x001D: + furi_string_cat_printf(name, "Hex"); + break; + case 0x001E: + case 0x01AE: + furi_string_cat_printf(name, "Chop Chop"); + break; + case 0x001F: + furi_string_cat_printf(name, "Ghost Roaster"); + break; + case 0x0020: + furi_string_cat_printf(name, "Cynder"); + break; + case 0x0064: + furi_string_cat_printf(name, "Jet Vac"); + break; + case 0x0065: + furi_string_cat_printf(name, "Swarm"); + break; + case 0x0066: + furi_string_cat_printf(name, "Crusher"); + break; + case 0x0067: + furi_string_cat_printf(name, "Flashwing"); + break; + case 0x0068: + furi_string_cat_printf(name, "Hot Head"); + break; + case 0x0069: + furi_string_cat_printf(name, "Hot Dog"); + break; + case 0x006A: + furi_string_cat_printf(name, "Chill"); + break; + case 0x006B: + furi_string_cat_printf(name, "Thumpback"); + break; + case 0x006C: + furi_string_cat_printf(name, "Pop Fizz"); + break; + case 0x006D: + furi_string_cat_printf(name, "Ninjini"); + break; + case 0x006E: + furi_string_cat_printf(name, "Bouncer"); + break; + case 0x006F: + furi_string_cat_printf(name, "Sprocket"); + break; + case 0x0070: + furi_string_cat_printf(name, "Tree Rex"); + break; + case 0x0071: + furi_string_cat_printf(name, "Shroomboom"); + break; + case 0x0072: + furi_string_cat_printf(name, "Eye-Brawl"); + break; + case 0x0073: + furi_string_cat_printf(name, "Fright Rider"); + break; + case 0x00C8: + furi_string_cat_printf(name, "Anvil Rain"); + break; + case 0x00C9: + furi_string_cat_printf(name, "Treasure Chest"); + break; + case 0x00CA: + furi_string_cat_printf(name, "Healing Elixer"); + break; + case 0x00CB: + furi_string_cat_printf(name, "Ghost Swords"); + break; + case 0x00CC: + furi_string_cat_printf(name, "Time Twister"); + break; + case 0x00CD: + furi_string_cat_printf(name, "Sky-Iron Shield"); + break; + case 0x00CE: + furi_string_cat_printf(name, "Winged Boots"); + break; + case 0x00CF: + furi_string_cat_printf(name, "Sparx Dragonfly"); + break; + case 0x00D0: + furi_string_cat_printf(name, "Dragonfire Cannon"); + break; + case 0x00D1: + furi_string_cat_printf(name, "Scorpion Striker Catapult"); + break; + case 0x00D2: + furi_string_cat_printf(name, "Trap - Magic"); + break; + case 0x00D3: + furi_string_cat_printf(name, "Trap - Water"); + break; + case 0x00D4: + furi_string_cat_printf(name, "Trap - Air"); + break; + case 0x00D5: + furi_string_cat_printf(name, "Trap - Undead"); + break; + case 0x00D6: + furi_string_cat_printf(name, "Trap - Tech"); + break; + case 0x00D7: + furi_string_cat_printf(name, "Trap - Fire"); + break; + case 0x00D8: + furi_string_cat_printf(name, "Trap - Earth"); + break; + case 0x00D9: + furi_string_cat_printf(name, "Trap - Life"); + break; + case 0x00DA: + furi_string_cat_printf(name, "Trap - Light"); + break; + case 0x00DB: + furi_string_cat_printf(name, "Trap - Dark"); + break; + case 0x00DC: + furi_string_cat_printf(name, "Trap - Kaos"); + break; + case 0x00E6: + furi_string_cat_printf(name, "Hand Of Fate"); + break; + case 0x00E7: + furi_string_cat_printf(name, "Piggy Bank"); + break; + case 0x00E8: + furi_string_cat_printf(name, "Rocket Ram"); + break; + case 0x00E9: + furi_string_cat_printf(name, "Tiki Speaky"); + break; + case 0x00EB: + furi_string_cat_printf(name, "Imaginite Mystery Chest"); + break; + case 0x012C: + furi_string_cat_printf(name, "Dragons Peak"); + break; + case 0x012D: + furi_string_cat_printf(name, "Empire of Ice"); + break; + case 0x012E: + furi_string_cat_printf(name, "Pirate Seas"); + break; + case 0x012F: + furi_string_cat_printf(name, "Darklight Crypt"); + break; + case 0x0130: + furi_string_cat_printf(name, "Volcanic Vault"); + break; + case 0x0131: + furi_string_cat_printf(name, "Mirror Of Mystery"); + break; + case 0x0132: + furi_string_cat_printf(name, "Nightmare Express"); + break; + case 0x0133: + furi_string_cat_printf(name, "Sunscraper Spire"); + break; + case 0x0134: + furi_string_cat_printf(name, "Midnight Museum"); + break; + case 0x01C2: + furi_string_cat_printf(name, "Gusto"); + break; + case 0x01C3: + furi_string_cat_printf(name, "Thunderbolt"); + break; + case 0x01C4: + furi_string_cat_printf(name, "Fling Kong"); + break; + case 0x01C5: + furi_string_cat_printf(name, "Blades"); + break; + case 0x01C6: + furi_string_cat_printf(name, "Wallop"); + break; + case 0x01C7: + furi_string_cat_printf(name, "Head Rush"); + break; + case 0x01C8: + furi_string_cat_printf(name, "Fist Bump"); + break; + case 0x01C9: + furi_string_cat_printf(name, "Rocky Roll"); + break; + case 0x01CA: + furi_string_cat_printf(name, "Wildfire"); + break; + case 0x01CB: + furi_string_cat_printf(name, "Ka Boom"); + break; + case 0x01CC: + furi_string_cat_printf(name, "Trail Blazer"); + break; + case 0x01CD: + furi_string_cat_printf(name, "Torch"); + break; + case 0x01CE: + furi_string_cat_printf(name, "Snap Shot"); + break; + case 0x01CF: + furi_string_cat_printf(name, "Lob Star"); + break; + case 0x01D0: + furi_string_cat_printf(name, "Flip Wreck"); + break; + case 0x01D1: + furi_string_cat_printf(name, "Echo"); + break; + case 0x01D2: + furi_string_cat_printf(name, "Blastermind"); + break; + case 0x01D3: + furi_string_cat_printf(name, "Enigma"); + break; + case 0x01D4: + furi_string_cat_printf(name, "Deja Vu"); + break; + case 0x01D5: + furi_string_cat_printf(name, "Cobra Cadabra"); + break; + case 0x01D6: + furi_string_cat_printf(name, "Jawbreaker"); + break; + case 0x01D7: + furi_string_cat_printf(name, "Gearshift"); + break; + case 0x01D8: + furi_string_cat_printf(name, "Chopper"); + break; + case 0x01D9: + furi_string_cat_printf(name, "Tread Head"); + break; + case 0x01DA: + furi_string_cat_printf(name, "Bushwhack"); + break; + case 0x01DB: + furi_string_cat_printf(name, "Tuff Luck"); + break; + case 0x01DC: + furi_string_cat_printf(name, "Food Fight"); + break; + case 0x01DD: + furi_string_cat_printf(name, "High Five"); + break; + case 0x01DE: + furi_string_cat_printf(name, "Krypt King"); + break; + case 0x01DF: + furi_string_cat_printf(name, "Short Cut"); + break; + case 0x01E0: + furi_string_cat_printf(name, "Bat Spin"); + break; + case 0x01E1: + furi_string_cat_printf(name, "Funny Bone"); + break; + case 0x01E2: + furi_string_cat_printf(name, "Knight light"); + break; + case 0x01E3: + furi_string_cat_printf(name, "Spotlight"); + break; + case 0x01E4: + furi_string_cat_printf(name, "Knight Mare"); + break; + case 0x01E5: + furi_string_cat_printf(name, "Blackout"); + break; + case 0x01F6: + furi_string_cat_printf(name, "Bop"); + break; + case 0x01F7: + furi_string_cat_printf(name, "Spry"); + break; + case 0x01F8: + furi_string_cat_printf(name, "Hijinx"); + break; + case 0x01F9: + furi_string_cat_printf(name, "Terrabite"); + break; + case 0x01FA: + furi_string_cat_printf(name, "Breeze"); + break; + case 0x01FB: + furi_string_cat_printf(name, "Weeruptor"); + break; + case 0x01FC: + furi_string_cat_printf(name, "Pet Vac"); + break; + case 0x01FD: + furi_string_cat_printf(name, "Small Fry"); + break; + case 0x01FE: + furi_string_cat_printf(name, "Drobit"); + break; + case 0x0202: + furi_string_cat_printf(name, "Gill Runt"); + break; + case 0x0207: + furi_string_cat_printf(name, "Trigger Snappy"); + break; + case 0x020E: + furi_string_cat_printf(name, "Whisper Elf"); + break; + case 0x021C: + furi_string_cat_printf(name, "Barkley"); + break; + case 0x021D: + furi_string_cat_printf(name, "Thumpling"); + break; + case 0x021E: + furi_string_cat_printf(name, "Mini Jini"); + break; + case 0x021F: + furi_string_cat_printf(name, "Eye Small"); + break; + case 0x0259: + furi_string_cat_printf(name, "King Pen"); + break; + case 0x0265: + furi_string_cat_printf(name, "Golden Queen"); + break; + case 0x02AD: + furi_string_cat_printf(name, "Fire Acorn"); + break; + case 0x03E8: + furi_string_cat_printf(name, "(Boom) Jet"); + break; + case 0x03E9: + furi_string_cat_printf(name, "(Free) Ranger"); + break; + case 0x03EA: + furi_string_cat_printf(name, "(Rubble) Rouser"); + break; + case 0x03EB: + furi_string_cat_printf(name, "(Doom) Stone"); + break; + case 0x03EC: + furi_string_cat_printf(name, "Blast Zone"); + break; + case 0x03ED: + furi_string_cat_printf(name, "(Fire) Kraken"); + break; + case 0x03EE: + furi_string_cat_printf(name, "(Stink) Bomb"); + break; + case 0x03EF: + furi_string_cat_printf(name, "(Grilla) Drilla"); + break; + case 0x03F0: + furi_string_cat_printf(name, "(Hoot) Loop"); + break; + case 0x03F1: + furi_string_cat_printf(name, "(Trap) Shadow"); + break; + case 0x03F2: + furi_string_cat_printf(name, "(Magna) Charge"); + break; + case 0x03F3: + furi_string_cat_printf(name, "(Spy) Rise"); + break; + case 0x03F4: + furi_string_cat_printf(name, "(Night) Shift"); + break; + case 0x03F5: + furi_string_cat_printf(name, "(Rattle) Shake"); + break; + case 0x03F6: + furi_string_cat_printf(name, "(Freeze) Blade"); + break; + case 0x03F7: + furi_string_cat_printf(name, "Wash Buckler"); + break; + case 0x07D0: + furi_string_cat_printf(name, "Boom (Jet)"); + break; + case 0x07D1: + furi_string_cat_printf(name, "Free (Ranger)"); + break; + case 0x07D2: + furi_string_cat_printf(name, "Rubble (Rouser)"); + break; + case 0x07D3: + furi_string_cat_printf(name, "Doom (Stone)"); + break; + case 0x07D4: + furi_string_cat_printf(name, "Blast Zone (Head)"); + break; + case 0x07D5: + furi_string_cat_printf(name, "Fire (Kraken)"); + break; + case 0x07D6: + furi_string_cat_printf(name, "Stink (Bomb)"); + break; + case 0x07D7: + furi_string_cat_printf(name, "Grilla (Drilla)"); + break; + case 0x07D8: + furi_string_cat_printf(name, "Hoot (Loop)"); + break; + case 0x07D9: + furi_string_cat_printf(name, "Trap (Shadow)"); + break; + case 0x07DA: + furi_string_cat_printf(name, "Magna (Charge)"); + break; + case 0x07DB: + furi_string_cat_printf(name, "Spy (Rise)"); + break; + case 0x07DC: + furi_string_cat_printf(name, "Night (Shift)"); + break; + case 0x07DD: + furi_string_cat_printf(name, "Rattle (Shake)"); + break; + case 0x07DE: + furi_string_cat_printf(name, "Freeze (Blade)"); + break; + case 0x07DF: + furi_string_cat_printf(name, "Wash Buckler (Head)"); + break; + case 0x0BB8: + furi_string_cat_printf(name, "Scratch"); + break; + case 0x0BB9: + furi_string_cat_printf(name, "Pop Thorn"); + break; + case 0x0BBA: + furi_string_cat_printf(name, "Slobber Tooth"); + break; + case 0x0BBB: + furi_string_cat_printf(name, "Scorp"); + break; + case 0x0BBC: + furi_string_cat_printf(name, "Fryno"); + break; + case 0x0BBD: + furi_string_cat_printf(name, "Smolderdash"); + break; + case 0x0BBE: + furi_string_cat_printf(name, "Bumble Blast"); + break; + case 0x0BBF: + furi_string_cat_printf(name, "Zoo Lou"); + break; + case 0x0BC0: + furi_string_cat_printf(name, "Dune Bug"); + break; + case 0x0BC1: + furi_string_cat_printf(name, "Star Strike"); + break; + case 0x0BC2: + furi_string_cat_printf(name, "Countdown"); + break; + case 0x0BC3: + furi_string_cat_printf(name, "Wind Up"); + break; + case 0x0BC4: + furi_string_cat_printf(name, "Roller Brawl"); + break; + case 0x0BC5: + furi_string_cat_printf(name, "Grim Creeper"); + break; + case 0x0BC6: + furi_string_cat_printf(name, "Rip Tide"); + break; + case 0x0BC7: + furi_string_cat_printf(name, "Punk Shock"); + break; + case 0x0C80: + furi_string_cat_printf(name, "Battle Hammer"); + break; + case 0x0C81: + furi_string_cat_printf(name, "Sky Diamond"); + break; + case 0x0C82: + furi_string_cat_printf(name, "Platinum Sheep"); + break; + case 0x0C83: + furi_string_cat_printf(name, "Groove Machine"); + break; + case 0x0C84: + furi_string_cat_printf(name, "UFO Hat"); + break; + case 0x0C94: + furi_string_cat_printf(name, "Jet Stream"); + break; + case 0x0C95: + furi_string_cat_printf(name, "Tomb Buggy"); + break; + case 0x0C96: + furi_string_cat_printf(name, "Reef Ripper"); + break; + case 0x0C97: + furi_string_cat_printf(name, "Burn Cycle"); + break; + case 0x0C98: + furi_string_cat_printf(name, "Hot Streak"); + break; + case 0x0C99: + furi_string_cat_printf(name, "Shark Tank"); + break; + case 0x0C9A: + furi_string_cat_printf(name, "Thump Truck"); + break; + case 0x0C9B: + furi_string_cat_printf(name, "Crypt Crusher"); + break; + case 0x0C9C: + furi_string_cat_printf(name, "Stealth Stinger"); + break; + case 0x0C9F: + furi_string_cat_printf(name, "Dive Bomber"); + break; + case 0x0CA0: + furi_string_cat_printf(name, "Sky Slicer"); + break; + case 0x0CA1: + furi_string_cat_printf(name, "Clown Cruiser"); + break; + case 0x0CA2: + furi_string_cat_printf(name, "Gold Rusher"); + break; + case 0x0CA3: + furi_string_cat_printf(name, "Shield Striker"); + break; + case 0x0CA4: + furi_string_cat_printf(name, "Sun Runner"); + break; + case 0x0CA5: + furi_string_cat_printf(name, "Sea Shadow"); + break; + case 0x0CA6: + furi_string_cat_printf(name, "Splatter Splasher"); + break; + case 0x0CA7: + furi_string_cat_printf(name, "Soda Skimmer"); + break; + case 0x0CA8: + furi_string_cat_printf(name, "Barrel Blaster"); + break; + case 0x0CA9: + furi_string_cat_printf(name, "Buzz Wing"); + break; + case 0x0CE4: + furi_string_cat_printf(name, "Sheep Wreck Island"); + break; + case 0x0CE5: + furi_string_cat_printf(name, "Tower of Time"); + break; + case 0x0CE6: + furi_string_cat_printf(name, "Fiery Forge"); + break; + case 0x0CE7: + furi_string_cat_printf(name, "Arkeyan Crossbow"); + break; + case 0x0D48: + furi_string_cat_printf(name, "Fiesta"); + break; + case 0x0D49: + furi_string_cat_printf(name, "High Volt"); + break; + case 0x0D4A: + furi_string_cat_printf(name, "Splat"); + break; + case 0x0D4E: + furi_string_cat_printf(name, "Stormblade"); + break; + case 0x0D53: + furi_string_cat_printf(name, "Smash It"); + break; + case 0x0D54: + furi_string_cat_printf(name, "Spitfire"); + break; + case 0x0D55: + furi_string_cat_printf(name, "Hurricane Jet-Vac"); + break; + case 0x0D56: + furi_string_cat_printf(name, "Double Dare Trigger Happy"); + break; + case 0x0D57: + furi_string_cat_printf(name, "Super Shot Stealth Elf"); + break; + case 0x0D58: + furi_string_cat_printf(name, "Shark Shooter Terrafin"); + break; + case 0x0D59: + furi_string_cat_printf(name, "Bone Bash Roller Brawl"); + break; + case 0x0D5C: + furi_string_cat_printf(name, "Big Bubble Pop Fizz"); + break; + case 0x0D5D: + furi_string_cat_printf(name, "Lava Lance Eruptor"); + break; + case 0x0D5E: + furi_string_cat_printf(name, "Deep Dive Gill Grunt"); + break; + case 0x0D5F: + furi_string_cat_printf(name, "Turbo Charge Donkey Kong"); + break; + case 0x0D60: + furi_string_cat_printf(name, "Hammer Slam Bowser"); + break; + case 0x0D61: + furi_string_cat_printf(name, "Dive-Clops"); + break; + case 0x0D62: + furi_string_cat_printf(name, "Astroblast"); + break; + case 0x0D63: + furi_string_cat_printf(name, "Nightfall"); + break; + case 0x0D64: + furi_string_cat_printf(name, "Thrillipede"); + break; + case 0x0DAC: + furi_string_cat_printf(name, "Sky Trophy"); + break; + case 0x0DAD: + furi_string_cat_printf(name, "Land Trophy"); + break; + case 0x0DAE: + furi_string_cat_printf(name, "Sea Trophy"); + break; + case 0x0DAF: + furi_string_cat_printf(name, "Kaos Trophy"); + break; + default: + furi_string_cat_printf(name, "Unknown"); + break; + } + + return true; +} + +static bool skylanders_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + bool parsed = false; + FuriString* name = furi_string_alloc(); + + do { + // verify key + const uint8_t verify_sector = 0; + MfClassicSectorTrailer* sec_tr = + mf_classic_get_sector_trailer_by_sector(data, verify_sector); + uint64_t key = bit_lib_bytes_to_num_be(sec_tr->key_a.data, 6); + if(key != skylanders_key) break; + + const uint16_t id = (uint16_t)*data->block[1].data; + if(id == 0) break; + + bool success = fill_name(id, name); + if(!success) break; + + furi_string_printf(parsed_data, "\e#Skylanders\n%s", furi_string_get_cstr(name)); + + parsed = true; + + } while(false); + + furi_string_free(name); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin skylanders_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = skylanders_verify, + .read = skylanders_read, + .parse = skylanders_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor skylanders_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &skylanders_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* skylanders_plugin_ep(void) { + return &skylanders_plugin_descriptor; +} diff --git a/applications/services/gui/application.fam b/applications/services/gui/application.fam index 869d964dd..b7dd18baa 100644 --- a/applications/services/gui/application.fam +++ b/applications/services/gui/application.fam @@ -16,6 +16,7 @@ App( "elements.h", "view_dispatcher.h", "view_stack.h", + "view_holder.h", "modules/button_menu.h", "modules/byte_input.h", "modules/popup.h", diff --git a/applications/services/gui/elements.h b/applications/services/gui/elements.h index 833d5d6ee..54d78420e 100644 --- a/applications/services/gui/elements.h +++ b/applications/services/gui/elements.h @@ -121,7 +121,8 @@ void elements_multiline_text_aligned( /** Draw multiline text * * @param canvas Canvas instance - * @param x, y top left corner coordinates + * @param x top left corner coordinates + * @param y top left corner coordinates * @param text string (possible multiline) */ void elements_multiline_text(Canvas* canvas, int32_t x, int32_t y, const char* text); @@ -129,7 +130,8 @@ void elements_multiline_text(Canvas* canvas, int32_t x, int32_t y, const char* t /** Draw framed multiline text * * @param canvas Canvas instance - * @param x, y top left corner coordinates + * @param x top left corner coordinates + * @param y top left corner coordinates * @param text string (possible multiline) */ void elements_multiline_text_framed(Canvas* canvas, int32_t x, int32_t y, const char* text); @@ -137,8 +139,10 @@ void elements_multiline_text_framed(Canvas* canvas, int32_t x, int32_t y, const /** Draw slightly rounded frame * * @param canvas Canvas instance - * @param x, y top left corner coordinates - * @param width, height size of frame + * @param x top left corner coordinates + * @param y top left corner coordinates + * @param width width of frame + * @param height height of frame */ void elements_slightly_rounded_frame( Canvas* canvas, @@ -150,8 +154,10 @@ void elements_slightly_rounded_frame( /** Draw slightly rounded box * * @param canvas Canvas instance - * @param x, y top left corner coordinates - * @param width, height size of box + * @param x top left corner coordinates + * @param y top left corner coordinates + * @param width height of box + * @param height height of box */ void elements_slightly_rounded_box( Canvas* canvas, @@ -163,8 +169,10 @@ void elements_slightly_rounded_box( /** Draw bold rounded frame * * @param canvas Canvas instance - * @param x, y top left corner coordinates - * @param width, height size of frame + * @param x top left corner coordinates + * @param y top left corner coordinates + * @param width width of frame + * @param height height of frame */ void elements_bold_rounded_frame(Canvas* canvas, int32_t x, int32_t y, size_t width, size_t height); diff --git a/applications/services/gui/modules/submenu.c b/applications/services/gui/modules/submenu.c index 35916fe23..5b1bdccda 100644 --- a/applications/services/gui/modules/submenu.c +++ b/applications/services/gui/modules/submenu.c @@ -215,6 +215,26 @@ void submenu_add_item( true); } +void submenu_change_item_label(Submenu* submenu, uint32_t index, const char* label) { + furi_check(submenu); + furi_check(label); + + with_view_model( + submenu->view, + SubmenuModel * model, + { + SubmenuItemArray_it_t it; + for(SubmenuItemArray_it(it, model->items); !SubmenuItemArray_end_p(it); + SubmenuItemArray_next(it)) { + if(index == SubmenuItemArray_cref(it)->index) { + furi_string_set_str(SubmenuItemArray_cref(it)->label, label); + break; + } + } + }, + true); +} + void submenu_reset(Submenu* submenu) { furi_check(submenu); @@ -230,6 +250,25 @@ void submenu_reset(Submenu* submenu) { true); } +uint32_t submenu_get_selected_item(Submenu* submenu) { + furi_check(submenu); + + uint32_t selected_item_index = 0; + + with_view_model( + submenu->view, + SubmenuModel * model, + { + if(model->position < SubmenuItemArray_size(model->items)) { + const SubmenuItem* item = SubmenuItemArray_cget(model->items, model->position); + selected_item_index = item->index; + } + }, + false); + + return selected_item_index; +} + void submenu_set_selected_item(Submenu* submenu, uint32_t index) { furi_check(submenu); with_view_model( diff --git a/applications/services/gui/modules/submenu.h b/applications/services/gui/modules/submenu.h index 6ae148eb5..e435f94a2 100644 --- a/applications/services/gui/modules/submenu.h +++ b/applications/services/gui/modules/submenu.h @@ -53,16 +53,32 @@ void submenu_add_item( SubmenuItemCallback callback, void* callback_context); +/** Change label of an existing item + * + * @param submenu Submenu instance + * @param index The index of the item + * @param label The new label + */ +void submenu_change_item_label(Submenu* submenu, uint32_t index, const char* label); + /** Remove all items from submenu * * @param submenu Submenu instance */ void submenu_reset(Submenu* submenu); -/** Set submenu item selector +/** Get submenu selected item index * * @param submenu Submenu instance - * @param index The index + * + * @return Index of the selected item + */ +uint32_t submenu_get_selected_item(Submenu* submenu); + +/** Set submenu selected item by index + * + * @param submenu Submenu instance + * @param index The index of the selected item */ void submenu_set_selected_item(Submenu* submenu, uint32_t index); diff --git a/applications/services/gui/view.c b/applications/services/gui/view.c index 8fc5c2699..a35c2fa38 100644 --- a/applications/services/gui/view.c +++ b/applications/services/gui/view.c @@ -97,10 +97,11 @@ void view_free_model(View* view) { furi_mutex_free(model->mutex); free(model->data); free(model); - view->model = NULL; } else { furi_crash(); } + view->model = NULL; + view->model_type = ViewModelTypeNone; } void* view_get_model(View* view) { diff --git a/applications/settings/about/about.c b/applications/settings/about/about.c index 8f0798d9c..973d1f481 100644 --- a/applications/settings/about/about.c +++ b/applications/settings/about/about.c @@ -41,7 +41,7 @@ static DialogMessageButton about_screen_product(DialogsApp* dialogs, DialogMessa static DialogMessageButton about_screen_address(DialogsApp* dialogs, DialogMessage* message) { DialogMessageButton result; - const char* screen_text = "Flipper Devices Inc\n" + const char* screen_text = "Flipper Devices Inc.\n" "Suite B #551, 2803\n" "Philadelphia Pike, Claymont\n" "DE, USA 19703\n"; @@ -56,7 +56,7 @@ static DialogMessageButton about_screen_compliance(DialogsApp* dialogs, DialogMe DialogMessageButton result; const char* screen_text = "For all compliance\n" - "certificates please visit:\n" + "certificates, please visit:\n" "www.flipp.dev/compliance"; dialog_message_set_text(message, screen_text, 0, 0, AlignLeft, AlignTop); @@ -97,7 +97,7 @@ static DialogMessageButton about_screen_cert_china_0(DialogsApp* dialogs, Dialog static DialogMessageButton about_screen_cert_china_1(DialogsApp* dialogs, DialogMessage* message) { DialogMessageButton result; - dialog_message_set_icon(message, &I_CertificationChina1_122x47, 3, 3); + dialog_message_set_icon(message, &I_CertificationChina1_124x47, 3, 3); dialog_message_set_text( message, furi_hal_version_get_srrc_id(), 55, 11, AlignLeft, AlignBottom); result = dialog_message_show(dialogs, message); @@ -227,9 +227,11 @@ int32_t about_settings_app(void* p) { while(1) { if(screen_index >= COUNT_OF(about_screens) - 1) { - dialog_message_set_buttons(message, "Back", NULL, NULL); + dialog_message_set_buttons(message, "Prev.", NULL, NULL); + } else if(screen_index == 0) { + dialog_message_set_buttons(message, NULL, NULL, "Next"); } else { - dialog_message_set_buttons(message, "Back", NULL, "Next"); + dialog_message_set_buttons(message, "Prev.", NULL, "Next"); } screen_result = about_screens[screen_index](dialogs, message); diff --git a/applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_confirm.c b/applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_confirm.c index 31921b9f3..d72cb95e7 100644 --- a/applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_confirm.c +++ b/applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_confirm.c @@ -10,10 +10,10 @@ void bt_settings_scene_forget_dev_confirm_dialog_callback(DialogExResult result, void bt_settings_scene_forget_dev_confirm_on_enter(void* context) { BtSettingsApp* app = context; DialogEx* dialog = app->dialog; - dialog_ex_set_header(dialog, "Unpair All Devices?", 64, 3, AlignCenter, AlignTop); + dialog_ex_set_header(dialog, "Unpair All Devices?", 64, 0, AlignCenter, AlignTop); dialog_ex_set_text( - dialog, "All previous pairings\nwill be lost!", 64, 22, AlignCenter, AlignTop); - dialog_ex_set_left_button_text(dialog, "Back"); + dialog, "All previous pairings\nwill be lost!", 64, 14, AlignCenter, AlignTop); + dialog_ex_set_left_button_text(dialog, "Cancel"); dialog_ex_set_right_button_text(dialog, "Unpair"); dialog_ex_set_context(dialog, app); dialog_ex_set_result_callback(dialog, bt_settings_scene_forget_dev_confirm_dialog_callback); diff --git a/applications/settings/bt_settings_app/scenes/bt_settings_scene_start.c b/applications/settings/bt_settings_app/scenes/bt_settings_scene_start.c index c148f0943..1d72a9e6f 100644 --- a/applications/settings/bt_settings_app/scenes/bt_settings_scene_start.c +++ b/applications/settings/bt_settings_app/scenes/bt_settings_scene_start.c @@ -53,7 +53,7 @@ void bt_settings_scene_start_on_enter(void* context) { variable_item_set_current_value_index(item, BtSettingOff); variable_item_set_current_value_text(item, bt_settings_text[BtSettingOff]); } - variable_item_list_add(var_item_list, "Forget All Paired Devices", 1, NULL, NULL); + variable_item_list_add(var_item_list, "Unpair All Devices", 1, NULL, NULL); variable_item_list_set_enter_callback( var_item_list, bt_settings_scene_start_var_list_enter_callback, app); } else { diff --git a/applications/settings/desktop_settings/desktop_settings_app.c b/applications/settings/desktop_settings/desktop_settings_app.c index b030656f7..238d866f2 100644 --- a/applications/settings/desktop_settings/desktop_settings_app.c +++ b/applications/settings/desktop_settings/desktop_settings_app.c @@ -92,6 +92,7 @@ void desktop_settings_app_free(DesktopSettingsApp* app) { extern int32_t desktop_settings_app(void* p) { DesktopSettingsApp* app = desktop_settings_app_alloc(); DESKTOP_SETTINGS_LOAD(&app->settings); + if(p && (strcmp(p, DESKTOP_SETTINGS_RUN_PIN_SETUP_ARG) == 0)) { scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinSetupHowto); } else { @@ -99,6 +100,9 @@ extern int32_t desktop_settings_app(void* p) { } view_dispatcher_run(app->view_dispatcher); + + DESKTOP_SETTINGS_SAVE(&app->settings); desktop_settings_app_free(app); + return 0; } diff --git a/applications/settings/desktop_settings/desktop_settings_app.h b/applications/settings/desktop_settings/desktop_settings_app.h index 6f97564c9..1a2c733ed 100644 --- a/applications/settings/desktop_settings/desktop_settings_app.h +++ b/applications/settings/desktop_settings/desktop_settings_app.h @@ -40,5 +40,7 @@ typedef struct { PinCode pincode_buffer; bool pincode_buffer_filled; - uint8_t menu_idx; + uint32_t pin_menu_idx; + uint32_t quick_apps_menu_idx; + uint32_t quick_apps_direction_menu_idx; } DesktopSettingsApp; diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_config.h b/applications/settings/desktop_settings/scenes/desktop_settings_scene_config.h index 5bc52172b..52b8829be 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_config.h +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_config.h @@ -9,3 +9,6 @@ ADD_SCENE(desktop_settings, pin_setup, PinSetup) ADD_SCENE(desktop_settings, pin_setup_howto, PinSetupHowto) ADD_SCENE(desktop_settings, pin_setup_howto2, PinSetupHowto2) ADD_SCENE(desktop_settings, pin_setup_done, PinSetupDone) + +ADD_SCENE(desktop_settings, quick_apps_menu, QuickAppsMenu) +ADD_SCENE(desktop_settings, quick_apps_direction_menu, QuickAppsDirectionMenu) \ No newline at end of file diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c index db5d231ff..d77c8adda 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c @@ -9,11 +9,17 @@ #define APPS_COUNT (FLIPPER_APPS_COUNT + FLIPPER_EXTERNAL_APPS_COUNT) #define DEFAULT_INDEX (0) -#define EXTERNAL_BROWSER_NAME ("Apps Menu (Default)") -#define PASSPORT_NAME ("Passport (Default)") +#define EXTERNAL_BROWSER_NAME ("( ) Apps Menu (Default)") +#define EXTERNAL_BROWSER_NAME_SELECTED ("(*) Apps Menu (Default)") +#define PASSPORT_NAME ("( ) Passport (Default)") +#define PASSPORT_NAME_SELECTED ("(*) Passport (Default)") + +#define SELECTED_PREFIX ("(*) ") +#define NOT_SELECTED_PREFIX ("( ) ") #define EXTERNAL_APPLICATION_INDEX (1) -#define EXTERNAL_APPLICATION_NAME ("[Select App]") +#define EXTERNAL_APPLICATION_NAME ("( ) [Select App]") +#define EXTERNAL_APPLICATION_NAME_SELECTED ("(*) [Select App]") #define PRESELECTED_SPECIAL 0xffffffff @@ -61,7 +67,6 @@ void desktop_settings_scene_favorite_on_enter(void* context) { scene_manager_get_scene_state(app->scene_manager, DesktopSettingsAppSceneFavorite); uint32_t pre_select_item = PRESELECTED_SPECIAL; FavoriteApp* curr_favorite_app = NULL; - bool is_dummy_app = false; bool default_passport = false; if((favorite_id & SCENE_STATE_SET_DUMMY_APP) == 0) { @@ -74,7 +79,6 @@ void desktop_settings_scene_favorite_on_enter(void* context) { favorite_id &= ~(SCENE_STATE_SET_DUMMY_APP); furi_assert(favorite_id < DummyAppNumber); curr_favorite_app = &app->settings.dummy_apps[favorite_id]; - is_dummy_app = true; default_passport = true; } @@ -94,29 +98,76 @@ void desktop_settings_scene_favorite_on_enter(void* context) { desktop_settings_scene_favorite_submenu_callback, app); - if(!is_dummy_app) { - for(size_t i = 0; i < APPS_COUNT; i++) { - const char* name = favorite_fap_get_app_name(i); + FuriString* full_name = furi_string_alloc(); - submenu_add_item( - submenu, name, i + 2, desktop_settings_scene_favorite_submenu_callback, app); + for(size_t i = 0; i < APPS_COUNT; i++) { + const char* name = favorite_fap_get_app_name(i); - // Select favorite item in submenu - if(!strcmp(name, curr_favorite_app->name_or_path)) { - pre_select_item = i + 2; - } + // Add the prefix + furi_string_reset(full_name); + if(!strcmp(name, curr_favorite_app->name_or_path)) { + furi_string_set_str(full_name, SELECTED_PREFIX); + } else { + furi_string_set_str(full_name, NOT_SELECTED_PREFIX); + } + furi_string_cat_str(full_name, name); + + submenu_add_item( + submenu, + furi_string_get_cstr(full_name), + i + 2, + desktop_settings_scene_favorite_submenu_callback, + app); + + // Select favorite item in submenu + if(!strcmp(name, curr_favorite_app->name_or_path)) { + pre_select_item = i + 2; } } if(pre_select_item == PRESELECTED_SPECIAL) { if(curr_favorite_app->name_or_path[0] == '\0') { pre_select_item = DEFAULT_INDEX; + submenu_change_item_label( + submenu, + DEFAULT_INDEX, + default_passport ? (PASSPORT_NAME_SELECTED) : (EXTERNAL_BROWSER_NAME_SELECTED)); } else { pre_select_item = EXTERNAL_APPLICATION_INDEX; + submenu_change_item_label( + submenu, EXTERNAL_APPLICATION_INDEX, EXTERNAL_APPLICATION_NAME_SELECTED); } } - submenu_set_header(submenu, is_dummy_app ? ("Dummy Mode App") : ("Favorite App")); + switch(favorite_id) { + case SCENE_STATE_SET_FAVORITE_APP | FavoriteAppLeftShort: + submenu_set_header(submenu, "Left - Short"); + break; + case SCENE_STATE_SET_FAVORITE_APP | FavoriteAppLeftLong: + submenu_set_header(submenu, "Left - Long"); + break; + case SCENE_STATE_SET_FAVORITE_APP | FavoriteAppRightShort: + submenu_set_header(submenu, "Right - Short"); + break; + case SCENE_STATE_SET_FAVORITE_APP | FavoriteAppRightLong: + submenu_set_header(submenu, "Right - Long"); + break; + case SCENE_STATE_SET_DUMMY_APP | DummyAppLeft: + submenu_set_header(submenu, "Left"); + break; + case SCENE_STATE_SET_DUMMY_APP | DummyAppRight: + submenu_set_header(submenu, "Right"); + break; + case SCENE_STATE_SET_DUMMY_APP | DummyAppDown: + submenu_set_header(submenu, "Down"); + break; + case SCENE_STATE_SET_DUMMY_APP | DummyAppOk: + submenu_set_header(submenu, "Middle"); + break; + default: + break; + } + submenu_set_selected_item(submenu, pre_select_item); // If set during loop, visual glitch. view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMenu); @@ -177,6 +228,8 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e scene_manager_previous_scene(app->scene_manager); }; consumed = true; + + DESKTOP_SETTINGS_SAVE(&app->settings); } furi_string_free(temp_path); @@ -185,6 +238,5 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e void desktop_settings_scene_favorite_on_exit(void* context) { DesktopSettingsApp* app = context; - DESKTOP_SETTINGS_SAVE(&app->settings); submenu_reset(app->submenu); } diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_disable.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_disable.c index c3e22805d..2f21fd999 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_disable.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_disable.c @@ -25,7 +25,7 @@ void desktop_settings_scene_pin_disable_on_enter(void* context) { popup_set_context(app->popup, app); popup_set_callback(app->popup, pin_disable_back_callback); popup_set_icon(app->popup, 0, 2, &I_DolphinMafia_119x62); - popup_set_header(app->popup, "PIN\nDeleted!", 100, 0, AlignCenter, AlignTop); + popup_set_header(app->popup, "Removed", 100, 10, AlignCenter, AlignTop); popup_set_timeout(app->popup, 1500); popup_enable_timeout(app->popup); view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewIdPopup); diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_menu.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_menu.c index 86e756ede..950be4c5a 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_menu.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_menu.c @@ -37,14 +37,14 @@ void desktop_settings_scene_pin_menu_on_enter(void* context) { submenu_add_item( submenu, - "Disable", + "Remove PIN", SCENE_EVENT_DISABLE_PIN, desktop_settings_scene_pin_menu_submenu_callback, app); } submenu_set_header(app->submenu, "PIN Code Settings"); - submenu_set_selected_item(app->submenu, app->menu_idx); + submenu_set_selected_item(app->submenu, app->pin_menu_idx); view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMenu); } @@ -76,11 +76,16 @@ bool desktop_settings_scene_pin_menu_on_event(void* context, SceneManagerEvent e consumed = true; break; } + } else if(event.type == SceneManagerEventTypeBack) { + submenu_set_selected_item(app->submenu, 0); } + return consumed; } void desktop_settings_scene_pin_menu_on_exit(void* context) { DesktopSettingsApp* app = context; + + app->pin_menu_idx = submenu_get_selected_item(app->submenu); submenu_reset(app->submenu); } diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup.c index 1603aa337..5b8aa8638 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup.c @@ -97,6 +97,7 @@ bool desktop_settings_scene_pin_setup_on_event(void* context, SceneManagerEvent break; } } + return consumed; } diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_done.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_done.c index b829f995b..4cef4ba98 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_done.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_done.c @@ -27,6 +27,7 @@ void desktop_settings_scene_pin_setup_done_on_enter(void* context) { DESKTOP_SETTINGS_SAVE(&app->settings); NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); notification_message(notification, &sequence_single_vibro); + notification_message(notification, &sequence_blink_green_10); furi_record_close(RECORD_NOTIFICATION); desktop_view_pin_input_set_context(app->pin_input_view, app); diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_quick_apps_direction_menu.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_quick_apps_direction_menu.c new file mode 100644 index 000000000..dbc877ede --- /dev/null +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_quick_apps_direction_menu.c @@ -0,0 +1,187 @@ +#include +#include + +#include "../desktop_settings_app.h" +#include "desktop_settings_scene.h" +#include "desktop_settings_scene_i.h" + +enum QuickAppsSubmenuIndex { + QuickAppsSubmenuIndexFavoriteLeftClick, + QuickAppsSubmenuIndexFavoriteRightClick, + QuickAppsSubmenuIndexFavoriteLeftHold, + QuickAppsSubmenuIndexFavoriteRightHold, + QuickAppsSubmenuIndexDummyLeftClick, + QuickAppsSubmenuIndexDummyRightClick, + QuickAppsSubmenuIndexDummyDownClick, + QuickAppsSubmenuIndexDummyMiddleClick, +}; + +static void desktop_settings_scene_quick_apps_direction_menu_submenu_callback( + void* context, + uint32_t index) { + DesktopSettingsApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void desktop_settings_scene_quick_apps_direction_menu_on_enter(void* context) { + DesktopSettingsApp* app = context; + Submenu* submenu = app->submenu; + submenu_reset(submenu); + + uint32_t favorite_id = scene_manager_get_scene_state( + app->scene_manager, DesktopSettingsAppSceneQuickAppsDirectionMenu); + + if(favorite_id == SCENE_STATE_SET_FAVORITE_APP) { + submenu_add_item( + submenu, + "Left - Click", + QuickAppsSubmenuIndexFavoriteLeftClick, + desktop_settings_scene_quick_apps_direction_menu_submenu_callback, + app); + + submenu_add_item( + submenu, + "Right - Click", + QuickAppsSubmenuIndexFavoriteRightClick, + desktop_settings_scene_quick_apps_direction_menu_submenu_callback, + app); + + submenu_add_item( + submenu, + "Left - Hold", + QuickAppsSubmenuIndexFavoriteLeftHold, + desktop_settings_scene_quick_apps_direction_menu_submenu_callback, + app); + + submenu_add_item( + submenu, + "Right - Hold", + QuickAppsSubmenuIndexFavoriteRightHold, + desktop_settings_scene_quick_apps_direction_menu_submenu_callback, + app); + + submenu_set_header(app->submenu, "Default Mode"); + } else { + submenu_add_item( + submenu, + "Left - Click", + QuickAppsSubmenuIndexDummyLeftClick, + desktop_settings_scene_quick_apps_direction_menu_submenu_callback, + app); + + submenu_add_item( + submenu, + "Right - Click", + QuickAppsSubmenuIndexDummyRightClick, + desktop_settings_scene_quick_apps_direction_menu_submenu_callback, + app); + + submenu_add_item( + submenu, + "Down - Click", + QuickAppsSubmenuIndexDummyDownClick, + desktop_settings_scene_quick_apps_direction_menu_submenu_callback, + app); + + submenu_add_item( + submenu, + "Middle - Click", + QuickAppsSubmenuIndexDummyMiddleClick, + desktop_settings_scene_quick_apps_direction_menu_submenu_callback, + app); + + submenu_set_header(app->submenu, "Dummy Mode"); + } + + submenu_set_selected_item(app->submenu, app->quick_apps_direction_menu_idx); + view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMenu); +} + +bool desktop_settings_scene_quick_apps_direction_menu_on_event( + void* context, + SceneManagerEvent event) { + DesktopSettingsApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case QuickAppsSubmenuIndexFavoriteLeftClick: + scene_manager_set_scene_state( + app->scene_manager, + DesktopSettingsAppSceneFavorite, + SCENE_STATE_SET_FAVORITE_APP | FavoriteAppLeftShort); + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); + consumed = true; + break; + case QuickAppsSubmenuIndexFavoriteRightClick: + scene_manager_set_scene_state( + app->scene_manager, + DesktopSettingsAppSceneFavorite, + SCENE_STATE_SET_FAVORITE_APP | FavoriteAppRightShort); + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); + consumed = true; + break; + case QuickAppsSubmenuIndexFavoriteLeftHold: + scene_manager_set_scene_state( + app->scene_manager, + DesktopSettingsAppSceneFavorite, + SCENE_STATE_SET_FAVORITE_APP | FavoriteAppLeftLong); + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); + consumed = true; + break; + case QuickAppsSubmenuIndexFavoriteRightHold: + scene_manager_set_scene_state( + app->scene_manager, + DesktopSettingsAppSceneFavorite, + SCENE_STATE_SET_FAVORITE_APP | FavoriteAppRightLong); + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); + consumed = true; + break; + case QuickAppsSubmenuIndexDummyLeftClick: + scene_manager_set_scene_state( + app->scene_manager, + DesktopSettingsAppSceneFavorite, + SCENE_STATE_SET_DUMMY_APP | DummyAppLeft); + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); + consumed = true; + break; + case QuickAppsSubmenuIndexDummyRightClick: + scene_manager_set_scene_state( + app->scene_manager, + DesktopSettingsAppSceneFavorite, + SCENE_STATE_SET_DUMMY_APP | DummyAppRight); + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); + consumed = true; + break; + case QuickAppsSubmenuIndexDummyDownClick: + scene_manager_set_scene_state( + app->scene_manager, + DesktopSettingsAppSceneFavorite, + SCENE_STATE_SET_DUMMY_APP | DummyAppDown); + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); + consumed = true; + break; + case QuickAppsSubmenuIndexDummyMiddleClick: + scene_manager_set_scene_state( + app->scene_manager, + DesktopSettingsAppSceneFavorite, + SCENE_STATE_SET_DUMMY_APP | DummyAppOk); + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); + consumed = true; + break; + default: + consumed = true; + break; + } + } else if(event.type == SceneManagerEventTypeBack) { + submenu_set_selected_item(app->submenu, 0); + } + + return consumed; +} + +void desktop_settings_scene_quick_apps_direction_menu_on_exit(void* context) { + DesktopSettingsApp* app = context; + app->quick_apps_direction_menu_idx = submenu_get_selected_item(app->submenu); + submenu_reset(app->submenu); +} diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_quick_apps_menu.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_quick_apps_menu.c new file mode 100644 index 000000000..acba4fa25 --- /dev/null +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_quick_apps_menu.c @@ -0,0 +1,80 @@ +#include +#include + +#include "../desktop_settings_app.h" +#include "desktop_settings_scene.h" +#include "desktop_settings_scene_i.h" + +#define SCENE_EVENT_SET_DEFAULT (0U) +#define SCENE_EVENT_SET_DUMMY (1U) + +static void + desktop_settings_scene_quick_apps_menu_submenu_callback(void* context, uint32_t index) { + DesktopSettingsApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void desktop_settings_scene_quick_apps_menu_on_enter(void* context) { + DesktopSettingsApp* app = context; + Submenu* submenu = app->submenu; + submenu_reset(submenu); + + submenu_add_item( + submenu, + "Default Mode", + SCENE_EVENT_SET_DEFAULT, + desktop_settings_scene_quick_apps_menu_submenu_callback, + app); + + submenu_add_item( + submenu, + "Dummy Mode", + SCENE_EVENT_SET_DUMMY, + desktop_settings_scene_quick_apps_menu_submenu_callback, + app); + + submenu_set_header(app->submenu, "Set Quick Access Apps"); + submenu_set_selected_item(app->submenu, app->quick_apps_menu_idx); + view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMenu); +} + +bool desktop_settings_scene_quick_apps_menu_on_event(void* context, SceneManagerEvent event) { + DesktopSettingsApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case SCENE_EVENT_SET_DEFAULT: + scene_manager_set_scene_state( + app->scene_manager, + DesktopSettingsAppSceneQuickAppsDirectionMenu, + SCENE_STATE_SET_FAVORITE_APP); + scene_manager_next_scene( + app->scene_manager, DesktopSettingsAppSceneQuickAppsDirectionMenu); + consumed = true; + break; + case SCENE_EVENT_SET_DUMMY: + scene_manager_set_scene_state( + app->scene_manager, + DesktopSettingsAppSceneQuickAppsDirectionMenu, + SCENE_STATE_SET_DUMMY_APP); + scene_manager_next_scene( + app->scene_manager, DesktopSettingsAppSceneQuickAppsDirectionMenu); + consumed = true; + break; + default: + consumed = true; + break; + } + } else if(event.type == SceneManagerEventTypeBack) { + submenu_set_selected_item(app->submenu, 0); + } + + return consumed; +} + +void desktop_settings_scene_quick_apps_menu_on_exit(void* context) { + DesktopSettingsApp* app = context; + app->quick_apps_menu_idx = submenu_get_selected_item(app->submenu); + submenu_reset(app->submenu); +} diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c index 3e77bd8a1..8dec26016 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c @@ -9,14 +9,7 @@ typedef enum { DesktopSettingsPinSetup = 0, DesktopSettingsAutoLockDelay, DesktopSettingsClockDisplay, - DesktopSettingsFavoriteLeftShort, - DesktopSettingsFavoriteLeftLong, - DesktopSettingsFavoriteRightShort, - DesktopSettingsFavoriteRightLong, - DesktopSettingsDummyLeft, - DesktopSettingsDummyRight, - DesktopSettingsDummyDown, - DesktopSettingsDummyOk, + DesktopSettingsFavoriteApps, } DesktopSettingsEntry; #define AUTO_LOCK_DELAY_COUNT 6 @@ -93,15 +86,7 @@ void desktop_settings_scene_start_on_enter(void* context) { variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, clock_enable_text[value_index]); - variable_item_list_add(variable_item_list, "Favorite App - Left Short", 1, NULL, NULL); - variable_item_list_add(variable_item_list, "Favorite App - Left Long", 1, NULL, NULL); - variable_item_list_add(variable_item_list, "Favorite App - Right Short", 1, NULL, NULL); - variable_item_list_add(variable_item_list, "Favorite App - Right Long", 1, NULL, NULL); - - variable_item_list_add(variable_item_list, "Dummy Mode App - Left", 1, NULL, NULL); - variable_item_list_add(variable_item_list, "Dummy Mode App - Right", 1, NULL, NULL); - variable_item_list_add(variable_item_list, "Dummy Mode App - Down", 1, NULL, NULL); - variable_item_list_add(variable_item_list, "Dummy Mode App - Ok", 1, NULL, NULL); + variable_item_list_add(variable_item_list, "Set Quick Access Apps", 1, NULL, NULL); variable_item_list_set_enter_callback( variable_item_list, desktop_settings_scene_start_var_list_enter_callback, app); @@ -119,62 +104,8 @@ bool desktop_settings_scene_start_on_event(void* context, SceneManagerEvent even scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinMenu); break; - case DesktopSettingsFavoriteLeftShort: - scene_manager_set_scene_state( - app->scene_manager, - DesktopSettingsAppSceneFavorite, - SCENE_STATE_SET_FAVORITE_APP | FavoriteAppLeftShort); - scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); - break; - case DesktopSettingsFavoriteLeftLong: - scene_manager_set_scene_state( - app->scene_manager, - DesktopSettingsAppSceneFavorite, - SCENE_STATE_SET_FAVORITE_APP | FavoriteAppLeftLong); - scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); - break; - case DesktopSettingsFavoriteRightShort: - scene_manager_set_scene_state( - app->scene_manager, - DesktopSettingsAppSceneFavorite, - SCENE_STATE_SET_FAVORITE_APP | FavoriteAppRightShort); - scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); - break; - case DesktopSettingsFavoriteRightLong: - scene_manager_set_scene_state( - app->scene_manager, - DesktopSettingsAppSceneFavorite, - SCENE_STATE_SET_FAVORITE_APP | FavoriteAppRightLong); - scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); - break; - - case DesktopSettingsDummyLeft: - scene_manager_set_scene_state( - app->scene_manager, - DesktopSettingsAppSceneFavorite, - SCENE_STATE_SET_DUMMY_APP | DummyAppLeft); - scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); - break; - case DesktopSettingsDummyRight: - scene_manager_set_scene_state( - app->scene_manager, - DesktopSettingsAppSceneFavorite, - SCENE_STATE_SET_DUMMY_APP | DummyAppRight); - scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); - break; - case DesktopSettingsDummyDown: - scene_manager_set_scene_state( - app->scene_manager, - DesktopSettingsAppSceneFavorite, - SCENE_STATE_SET_DUMMY_APP | DummyAppDown); - scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); - break; - case DesktopSettingsDummyOk: - scene_manager_set_scene_state( - app->scene_manager, - DesktopSettingsAppSceneFavorite, - SCENE_STATE_SET_DUMMY_APP | DummyAppOk); - scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); + case DesktopSettingsFavoriteApps: + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneQuickAppsMenu); break; default: @@ -188,5 +119,4 @@ bool desktop_settings_scene_start_on_event(void* context, SceneManagerEvent even void desktop_settings_scene_start_on_exit(void* context) { DesktopSettingsApp* app = context; variable_item_list_reset(app->variable_item_list); - DESKTOP_SETTINGS_SAVE(&app->settings); } diff --git a/applications/settings/desktop_settings/views/desktop_settings_view_pin_setup_howto2.c b/applications/settings/desktop_settings/views/desktop_settings_view_pin_setup_howto2.c index b09b0b95f..6148747a2 100644 --- a/applications/settings/desktop_settings/views/desktop_settings_view_pin_setup_howto2.c +++ b/applications/settings/desktop_settings/views/desktop_settings_view_pin_setup_howto2.c @@ -24,9 +24,9 @@ static void desktop_settings_view_pin_setup_howto2_draw(Canvas* canvas, void* mo elements_multiline_text_aligned( canvas, 64, - 24, - AlignCenter, + 0, AlignCenter, + AlignTop, "Forgotten PIN can only be\n" "reset with entire device.\n" "Read docs How to reset PIN."); diff --git a/applications/settings/power_settings_app/power_settings_app.h b/applications/settings/power_settings_app/power_settings_app.h index cd05846c0..09cc5af7a 100644 --- a/applications/settings/power_settings_app/power_settings_app.h +++ b/applications/settings/power_settings_app/power_settings_app.h @@ -30,3 +30,5 @@ typedef enum { PowerSettingsAppViewSubmenu, PowerSettingsAppViewDialog, } PowerSettingsAppView; + +typedef enum { RebootTypeDFU, RebootTypeNormal } RebootType; diff --git a/applications/settings/power_settings_app/scenes/power_settings_scene_config.h b/applications/settings/power_settings_app/scenes/power_settings_scene_config.h index cc8656dcf..f57071b9b 100644 --- a/applications/settings/power_settings_app/scenes/power_settings_scene_config.h +++ b/applications/settings/power_settings_app/scenes/power_settings_scene_config.h @@ -1,4 +1,5 @@ ADD_SCENE(power_settings, start, Start) ADD_SCENE(power_settings, battery_info, BatteryInfo) ADD_SCENE(power_settings, reboot, Reboot) +ADD_SCENE(power_settings, reboot_confirm, RebootConfirm) ADD_SCENE(power_settings, power_off, PowerOff) diff --git a/applications/settings/power_settings_app/scenes/power_settings_scene_power_off.c b/applications/settings/power_settings_app/scenes/power_settings_scene_power_off.c index c3f5d5ad8..6cd9c5c67 100644 --- a/applications/settings/power_settings_app/scenes/power_settings_scene_power_off.c +++ b/applications/settings/power_settings_app/scenes/power_settings_scene_power_off.c @@ -10,12 +10,12 @@ void power_settings_scene_power_off_on_enter(void* context) { PowerSettingsApp* app = context; DialogEx* dialog = app->dialog; - dialog_ex_set_header(dialog, "Turn OFF Device?", 64, 2, AlignCenter, AlignTop); + dialog_ex_set_header(dialog, "Turn Off Device?", 64, 0, AlignCenter, AlignTop); dialog_ex_set_text( - dialog, " I will be\nwaiting for\n you here...", 78, 16, AlignLeft, AlignTop); - dialog_ex_set_icon(dialog, 21, 13, &I_Cry_dolph_55x52); - dialog_ex_set_left_button_text(dialog, "Back"); - dialog_ex_set_right_button_text(dialog, "OFF"); + dialog, " I will be\nwaiting for\n you here...", 78, 14, AlignLeft, AlignTop); + dialog_ex_set_icon(dialog, 14, 10, &I_dolph_cry_49x54); + dialog_ex_set_left_button_text(dialog, "Cancel"); + dialog_ex_set_right_button_text(dialog, "Power Off"); dialog_ex_set_result_callback(dialog, power_settings_scene_power_off_dialog_callback); dialog_ex_set_context(dialog, app); diff --git a/applications/settings/power_settings_app/scenes/power_settings_scene_reboot.c b/applications/settings/power_settings_app/scenes/power_settings_scene_reboot.c index 2d5dedfd4..187d969ed 100644 --- a/applications/settings/power_settings_app/scenes/power_settings_scene_reboot.c +++ b/applications/settings/power_settings_app/scenes/power_settings_scene_reboot.c @@ -24,7 +24,7 @@ void power_settings_scene_reboot_on_enter(void* context) { app); submenu_add_item( submenu, - "Flipper OS", + "Reboot Flipper", PowerSettingsRebootSubmenuIndexOs, power_settings_scene_reboot_submenu_callback, app); @@ -33,14 +33,18 @@ void power_settings_scene_reboot_on_enter(void* context) { } bool power_settings_scene_reboot_on_event(void* context, SceneManagerEvent event) { - UNUSED(context); + PowerSettingsApp* app = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == PowerSettingsRebootSubmenuIndexDfu) { - power_reboot(PowerBootModeDfu); + scene_manager_set_scene_state( + app->scene_manager, PowerSettingsAppSceneRebootConfirm, RebootTypeDFU); + scene_manager_next_scene(app->scene_manager, PowerSettingsAppSceneRebootConfirm); } else if(event.event == PowerSettingsRebootSubmenuIndexOs) { - power_reboot(PowerBootModeNormal); + scene_manager_set_scene_state( + app->scene_manager, PowerSettingsAppSceneRebootConfirm, RebootTypeNormal); + scene_manager_next_scene(app->scene_manager, PowerSettingsAppSceneRebootConfirm); } consumed = true; } diff --git a/applications/settings/power_settings_app/scenes/power_settings_scene_reboot_confirm.c b/applications/settings/power_settings_app/scenes/power_settings_scene_reboot_confirm.c new file mode 100644 index 000000000..62e06de92 --- /dev/null +++ b/applications/settings/power_settings_app/scenes/power_settings_scene_reboot_confirm.c @@ -0,0 +1,66 @@ +#include "../power_settings_app.h" + +void power_settings_scene_reboot_confirm_dialog_callback(DialogExResult result, void* context) { + furi_assert(context); + PowerSettingsApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, result); +} + +void power_settings_scene_reboot_confirm_on_enter(void* context) { + PowerSettingsApp* app = context; + DialogEx* dialog = app->dialog; + + RebootType reboot_type = + scene_manager_get_scene_state(app->scene_manager, PowerSettingsAppSceneRebootConfirm); + + if(reboot_type == RebootTypeDFU) { + dialog_ex_set_header(dialog, "Reboot to DFU Mode?", 64, 0, AlignCenter, AlignTop); + dialog_ex_set_text( + dialog, + "Needed for device maintenance\nor firmware upgrades", + 64, + 14, + AlignCenter, + AlignTop); + } else if(reboot_type == RebootTypeNormal) { + dialog_ex_set_header(dialog, "Reboot Flipper?", 64, 0, AlignCenter, AlignTop); + dialog_ex_set_text( + dialog, "May help with some firmware\n issues", 64, 14, AlignCenter, AlignTop); + } else { + furi_crash("Invalid reboot type"); + } + + dialog_ex_set_left_button_text(dialog, "Cancel"); + dialog_ex_set_right_button_text(dialog, "Reboot"); + + dialog_ex_set_result_callback(dialog, power_settings_scene_reboot_confirm_dialog_callback); + dialog_ex_set_context(dialog, app); + + view_dispatcher_switch_to_view(app->view_dispatcher, PowerSettingsAppViewDialog); +} + +bool power_settings_scene_reboot_confirm_on_event(void* context, SceneManagerEvent event) { + PowerSettingsApp* app = context; + bool consumed = false; + RebootType reboot_type = + scene_manager_get_scene_state(app->scene_manager, PowerSettingsAppSceneRebootConfirm); + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == DialogExResultLeft) { + scene_manager_previous_scene(app->scene_manager); + } else if(event.event == DialogExResultRight) { + if(reboot_type == RebootTypeDFU) { + power_reboot(PowerBootModeDfu); + } else { + power_reboot(PowerBootModeNormal); + } + } + consumed = true; + } + return consumed; +} + +void power_settings_scene_reboot_confirm_on_exit(void* context) { + PowerSettingsApp* app = context; + dialog_ex_reset(app->dialog); +} diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c b/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c index e734c78e0..bfc9ac9c9 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c @@ -1,5 +1,7 @@ #include "../storage_settings.h" #include +#include +#include #define BENCH_DATA_SIZE 4096 #define BENCH_COUNT 6 @@ -86,7 +88,8 @@ static void storage_settings_scene_benchmark(StorageSettings* app) { uint32_t bench_w_speed[BENCH_COUNT] = {0, 0, 0, 0, 0, 0}; uint32_t bench_r_speed[BENCH_COUNT] = {0, 0, 0, 0, 0, 0}; - dialog_ex_set_header(dialog_ex, "Benchmarking...", 64, 32, AlignCenter, AlignCenter); + dialog_ex_set_header(dialog_ex, "Benchmarking...", 74, 32, AlignCenter, AlignCenter); + dialog_ex_set_icon(dialog_ex, 12, 20, &I_LoadingHourglass_24x24); for(size_t i = 0; i < BENCH_COUNT; i++) { if(!storage_settings_scene_bench_write( app->fs_api, bench_size[i], bench_data, &bench_w_speed[i])) @@ -95,6 +98,7 @@ static void storage_settings_scene_benchmark(StorageSettings* app) { if(i > 0) furi_string_cat_printf(app->text_string, "\n"); furi_string_cat_printf(app->text_string, "%ub : W %luK ", bench_size[i], bench_w_speed[i]); dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); + dialog_ex_set_icon(dialog_ex, 0, 0, NULL); dialog_ex_set_text( dialog_ex, furi_string_get_cstr(app->text_string), 0, 32, AlignLeft, AlignCenter); @@ -110,6 +114,12 @@ static void storage_settings_scene_benchmark(StorageSettings* app) { dialog_ex, furi_string_get_cstr(app->text_string), 0, 32, AlignLeft, AlignCenter); } + NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); + notification_message(notification, &sequence_single_vibro); + notification_message(notification, &sequence_set_green_255); + notification_message(notification, &sequence_success); + furi_record_close(RECORD_NOTIFICATION); + free(bench_data); } @@ -146,11 +156,17 @@ bool storage_settings_scene_benchmark_on_event(void* context, SceneManagerEvent if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { case DialogExResultCenter: - consumed = scene_manager_previous_scene(app->scene_manager); + consumed = scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, StorageSettingsStart); break; } - } else if(event.type == SceneManagerEventTypeBack && sd_status != FSE_OK) { - consumed = true; + } else if(event.type == SceneManagerEventTypeBack) { + if(sd_status == FSE_OK) { + consumed = scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, StorageSettingsStart); + } else { + consumed = true; + } } return consumed; @@ -160,6 +176,10 @@ void storage_settings_scene_benchmark_on_exit(void* context) { StorageSettings* app = context; DialogEx* dialog_ex = app->dialog_ex; + NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); + notification_message(notification, &sequence_reset_green); + furi_record_close(RECORD_NOTIFICATION); + dialog_ex_reset(dialog_ex); furi_string_reset(app->text_string); diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark_confirm.c b/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark_confirm.c new file mode 100644 index 000000000..2f8644761 --- /dev/null +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark_confirm.c @@ -0,0 +1,70 @@ +#include "../storage_settings.h" + +static void + storage_settings_scene_benchmark_confirm_dialog_callback(DialogExResult result, void* context) { + StorageSettings* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, result); +} + +void storage_settings_scene_benchmark_confirm_on_enter(void* context) { + StorageSettings* app = context; + DialogEx* dialog_ex = app->dialog_ex; + + FS_Error sd_status = storage_sd_status(app->fs_api); + + if(sd_status == FSE_NOT_READY) { + dialog_ex_set_icon(dialog_ex, 83, 22, &I_WarningDolphinFlip_45x42); + dialog_ex_set_header(dialog_ex, "SD Card Not Mounted", 64, 3, AlignCenter, AlignTop); + dialog_ex_set_text( + dialog_ex, "Try to reinsert\nor format SD\ncard.", 3, 19, AlignLeft, AlignTop); + dialog_ex_set_center_button_text(dialog_ex, "Ok"); + } else { + dialog_ex_set_header(dialog_ex, "Benchmark SD Card?", 64, 0, AlignCenter, AlignTop); + dialog_ex_set_text( + dialog_ex, + "SD will be tested in SPI\nmode. Learn more:\nr.flipper.net/sd_test", + 0, + 12, + AlignLeft, + AlignTop); + dialog_ex_set_icon(dialog_ex, 103, 12, &I_qr_benchmark_25x25); + dialog_ex_set_left_button_text(dialog_ex, "Cancel"); + dialog_ex_set_right_button_text(dialog_ex, "Benchmark"); + } + + dialog_ex_set_context(dialog_ex, app); + dialog_ex_set_result_callback( + dialog_ex, storage_settings_scene_benchmark_confirm_dialog_callback); + + view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx); +} + +bool storage_settings_scene_benchmark_confirm_on_event(void* context, SceneManagerEvent event) { + StorageSettings* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case DialogExResultLeft: + case DialogExResultCenter: + consumed = scene_manager_previous_scene(app->scene_manager); + break; + case DialogExResultRight: + scene_manager_next_scene(app->scene_manager, StorageSettingsBenchmark); + consumed = true; + break; + } + } else if(event.type == SceneManagerEventTypeBack) { + consumed = true; + } + + return consumed; +} + +void storage_settings_scene_benchmark_confirm_on_exit(void* context) { + StorageSettings* app = context; + DialogEx* dialog_ex = app->dialog_ex; + + dialog_ex_reset(dialog_ex); +} diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_config.h b/applications/settings/storage_settings/scenes/storage_settings_scene_config.h index 18e7ba5aa..d6e76894b 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_config.h +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_config.h @@ -5,5 +5,6 @@ ADD_SCENE(storage_settings, format_confirm, FormatConfirm) ADD_SCENE(storage_settings, formatting, Formatting) ADD_SCENE(storage_settings, sd_info, SDInfo) ADD_SCENE(storage_settings, internal_info, InternalInfo) +ADD_SCENE(storage_settings, benchmark_confirm, BenchmarkConfirm) ADD_SCENE(storage_settings, benchmark, Benchmark) ADD_SCENE(storage_settings, factory_reset, FactoryReset) diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_factory_reset.c b/applications/settings/storage_settings/scenes/storage_settings_scene_factory_reset.c index 5832c6589..2d977176a 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_factory_reset.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_factory_reset.c @@ -21,14 +21,14 @@ void storage_settings_scene_factory_reset_on_enter(void* context) { dialog_ex_set_left_button_text(dialog_ex, "Cancel"); dialog_ex_set_right_button_text(dialog_ex, "Erase"); - dialog_ex_set_header(dialog_ex, "Confirm Factory Reset", 64, 10, AlignCenter, AlignCenter); + dialog_ex_set_header(dialog_ex, "Confirm Factory Reset?", 64, 0, AlignCenter, AlignTop); dialog_ex_set_text( dialog_ex, - "Internal storage will be erased\r\nData and settings will be lost!", + "Internal storage will be erased\ndata and settings will be lost!", 64, - 32, + 14, AlignCenter, - AlignCenter); + AlignTop); view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx); } diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_format_confirm.c b/applications/settings/storage_settings/scenes/storage_settings_scene_format_confirm.c index 862f55a46..13b368d3f 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_format_confirm.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_format_confirm.c @@ -20,8 +20,8 @@ void storage_settings_scene_format_confirm_on_enter(void* context) { dialog_ex, "Try to reinsert\nor format SD\ncard.", 3, 19, AlignLeft, AlignTop); dialog_ex_set_center_button_text(dialog_ex, "Ok"); } else { - dialog_ex_set_header(dialog_ex, "Format SD Card?", 64, 10, AlignCenter, AlignCenter); - dialog_ex_set_text(dialog_ex, "All data will be lost!", 64, 32, AlignCenter, AlignCenter); + dialog_ex_set_header(dialog_ex, "Format SD Card?", 64, 0, AlignCenter, AlignTop); + dialog_ex_set_text(dialog_ex, "All data will be lost!", 64, 12, AlignCenter, AlignTop); dialog_ex_set_left_button_text(dialog_ex, "Cancel"); dialog_ex_set_right_button_text(dialog_ex, "Format"); } diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_formatting.c b/applications/settings/storage_settings/scenes/storage_settings_scene_formatting.c index f107aacea..6a958610e 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_formatting.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_formatting.c @@ -1,4 +1,6 @@ #include "../storage_settings.h" +#include +#include static const NotificationMessage message_green_165 = { .type = NotificationMessageTypeLedGreen, @@ -31,7 +33,8 @@ void storage_settings_scene_formatting_on_enter(void* context) { FS_Error error; DialogEx* dialog_ex = app->dialog_ex; - dialog_ex_set_header(dialog_ex, "Formatting...", 64, 32, AlignCenter, AlignCenter); + dialog_ex_set_header(dialog_ex, "Formatting...", 70, 32, AlignCenter, AlignCenter); + dialog_ex_set_icon(dialog_ex, 15, 20, &I_LoadingHourglass_24x24); view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx); notification_message_block(app->notification, &sequence_set_formatting_leds); @@ -44,13 +47,19 @@ void storage_settings_scene_formatting_on_enter(void* context) { if(error != FSE_OK) { dialog_ex_set_header(dialog_ex, "Cannot Format SD Card", 64, 10, AlignCenter, AlignCenter); + dialog_ex_set_icon(dialog_ex, 0, 0, NULL); dialog_ex_set_text( dialog_ex, storage_error_get_desc(error), 64, 32, AlignCenter, AlignCenter); } else { - dialog_ex_set_icon(dialog_ex, 83, 22, &I_WarningDolphinFlip_45x42); - dialog_ex_set_header(dialog_ex, "Format\ncomplete!", 14, 15, AlignLeft, AlignTop); + dialog_ex_set_icon(dialog_ex, 48, 6, &I_DolphinDone_80x58); + dialog_ex_set_header(dialog_ex, "Formatted", 5, 10, AlignLeft, AlignTop); + NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); + notification_message(notification, &sequence_single_vibro); + notification_message(notification, &sequence_set_green_255); + notification_message(notification, &sequence_success); + furi_record_close(RECORD_NOTIFICATION); } - dialog_ex_set_center_button_text(dialog_ex, "OK"); + dialog_ex_set_left_button_text(dialog_ex, "Finish"); } bool storage_settings_scene_formatting_on_event(void* context, SceneManagerEvent event) { @@ -59,7 +68,7 @@ bool storage_settings_scene_formatting_on_event(void* context, SceneManagerEvent if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { - case DialogExResultCenter: + case DialogExResultLeft: consumed = scene_manager_search_and_switch_to_previous_scene( app->scene_manager, StorageSettingsStart); break; @@ -75,5 +84,9 @@ void storage_settings_scene_formatting_on_exit(void* context) { StorageSettings* app = context; DialogEx* dialog_ex = app->dialog_ex; + NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); + notification_message(notification, &sequence_reset_green); + furi_record_close(RECORD_NOTIFICATION); + dialog_ex_reset(dialog_ex); } diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_internal_info.c b/applications/settings/storage_settings/scenes/storage_settings_scene_internal_info.c index f205efc0a..b7620b6e8 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_internal_info.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_internal_info.c @@ -27,7 +27,7 @@ void storage_settings_scene_internal_info_on_enter(void* context) { } else { furi_string_printf( app->text_string, - "Label: %s\nType: LittleFS\n%lu KiB total\n%lu KiB free", + "Name: %s\nType: LittleFS\nTotal: %lu KiB\nFree: %lu KiB", furi_hal_version_get_name_ptr() ? furi_hal_version_get_name_ptr() : "Unknown", (uint32_t)(total_space / 1024), (uint32_t)(free_space / 1024)); diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_sd_info.c b/applications/settings/storage_settings/scenes/storage_settings_scene_sd_info.c index aa9662a71..cad3fbfaf 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_sd_info.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_sd_info.c @@ -27,12 +27,31 @@ void storage_settings_scene_sd_info_on_enter(void* context) { } else { furi_string_printf( app->text_string, - "Label: %s\nType: %s\n%lu KiB total\n%lu KiB free\n" - "%02X%s %s v%i.%i\nSN:%04lX %02i/%i", + "Label: %s\nType: %s\n", sd_info.label, - sd_api_get_fs_type_text(sd_info.fs_type), - sd_info.kb_total, - sd_info.kb_free, + sd_api_get_fs_type_text(sd_info.fs_type)); + + if(sd_info.kb_total < 1024) { + furi_string_cat_printf(app->text_string, "Total: %lu KiB\n", sd_info.kb_total); + } else if(sd_info.kb_total < 1024 * 1024) { + furi_string_cat_printf(app->text_string, "Total: %lu MiB\n", sd_info.kb_total / 1024); + } else { + furi_string_cat_printf( + app->text_string, "Total: %lu GiB\n", sd_info.kb_total / (1024 * 1024)); + } + + if(sd_info.kb_free < 1024) { + furi_string_cat_printf(app->text_string, "Free: %lu KiB\n", sd_info.kb_free); + } else if(sd_info.kb_free < 1024 * 1024) { + furi_string_cat_printf(app->text_string, "Free: %lu MiB\n", sd_info.kb_free / 1024); + } else { + furi_string_cat_printf( + app->text_string, "Free: %lu GiB\n", sd_info.kb_free / (1024 * 1024)); + } + + furi_string_cat_printf( + app->text_string, + "%02X%s %s v%i.%i\nSN:%04lX %02i/%i", sd_info.manufacturer_id, sd_info.oem_id, sd_info.product_name, @@ -41,6 +60,7 @@ void storage_settings_scene_sd_info_on_enter(void* context) { sd_info.product_serial_number, sd_info.manufacturing_month, sd_info.manufacturing_year); + dialog_ex_set_text( dialog_ex, furi_string_get_cstr(app->text_string), 4, 1, AlignLeft, AlignTop); } diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_start.c b/applications/settings/storage_settings/scenes/storage_settings_scene_start.c index 0e667024f..e351a2ef7 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_start.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_start.c @@ -109,7 +109,7 @@ bool storage_settings_scene_start_on_event(void* context, SceneManagerEvent even case StorageSettingsStartSubmenuIndexBenchy: scene_manager_set_scene_state( app->scene_manager, StorageSettingsStart, StorageSettingsStartSubmenuIndexBenchy); - scene_manager_next_scene(app->scene_manager, StorageSettingsBenchmark); + scene_manager_next_scene(app->scene_manager, StorageSettingsBenchmarkConfirm); consumed = true; break; case StorageSettingsStartSubmenuIndexFactoryReset: diff --git a/applications/system/js_app/application.fam b/applications/system/js_app/application.fam index 114bec55f..a7ae5c7c7 100644 --- a/applications/system/js_app/application.fam +++ b/applications/system/js_app/application.fam @@ -46,3 +46,27 @@ App( requires=["js_app"], sources=["modules/js_serial.c"], ) + +App( + appid="js_submenu", + apptype=FlipperAppType.PLUGIN, + entry_point="js_submenu_ep", + requires=["js_app"], + sources=["modules/js_submenu.c"], +) + +App( + appid="js_math", + apptype=FlipperAppType.PLUGIN, + entry_point="js_math_ep", + requires=["js_app"], + sources=["modules/js_math.c"], +) + +App( + appid="js_textbox", + apptype=FlipperAppType.PLUGIN, + entry_point="js_textbox_ep", + requires=["js_app"], + sources=["modules/js_textbox.c"], +) diff --git a/applications/system/js_app/examples/apps/Scripts/math.js b/applications/system/js_app/examples/apps/Scripts/math.js new file mode 100644 index 000000000..c5a0bf18d --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/math.js @@ -0,0 +1,69 @@ +let math = require("math"); + +print("math.abs(-5):", math.abs(-5)); +print("math.acos(0.5):", math.acos(0.5)); +print("math.acosh(2):", math.acosh(2)); +print("math.asin(0.5):", math.asin(0.5)); +print("math.asinh(2):", math.asinh(2)); +print("math.atan(1):", math.atan(1)); +print("math.atan2(1, 1):", math.atan2(1, 1)); +print("math.atanh(0.5):", math.atanh(0.5)); +print("math.cbrt(27):", math.cbrt(27)); +print("math.ceil(5.3):", math.ceil(5.3)); +print("math.clz32(1):", math.clz32(1)); +print("math.cos(math.PI):", math.cos(math.PI)); +print("math.exp(1):", math.exp(1)); +print("math.floor(5.7):", math.floor(5.7)); +print("math.max(3, 5):", math.max(3, 5)); +print("math.min(3, 5):", math.min(3, 5)); +print("math.pow(2, 3):", math.pow(2, 3)); +print("math.random():", math.random()); +print("math.sign(-5):", math.sign(-5)); +print("math.sin(math.PI/2):", math.sin(math.PI / 2)); +print("math.sqrt(25):", math.sqrt(25)); +print("math.trunc(5.7):", math.trunc(5.7)); + +// Unit tests. Please add more if you have time and knowledge. +// math.EPSILON on Flipper Zero is 2.22044604925031308085e-16 + +let succeeded = 0; +let failed = 0; + +function test(text, result, expected, epsilon) { + let is_equal = math.is_equal(result, expected, epsilon); + if (is_equal) { + succeeded += 1; + } else { + failed += 1; + print(text, "expected", expected, "got", result); + } +} + +test("math.abs(5)", math.abs(-5), 5, math.EPSILON); +test("math.abs(0.5)", math.abs(-0.5), 0.5, math.EPSILON); +test("math.abs(5)", math.abs(5), 5, math.EPSILON); +test("math.abs(-0.5)", math.abs(0.5), 0.5, math.EPSILON); +test("math.acos(0.5)", math.acos(0.5), 1.0471975511965976, math.EPSILON); +test("math.acosh(2)", math.acosh(2), 1.3169578969248166, math.EPSILON); +test("math.asin(0.5)", math.asin(0.5), 0.5235987755982988, math.EPSILON); +test("math.asinh(2)", math.asinh(2), 1.4436354751788103, math.EPSILON); +test("math.atan(1)", math.atan(1), 0.7853981633974483, math.EPSILON); +test("math.atan2(1, 1)", math.atan2(1, 1), 0.7853981633974483, math.EPSILON); +test("math.atanh(0.5)", math.atanh(0.5), 0.5493061443340549, math.EPSILON); +test("math.cbrt(27)", math.cbrt(27), 3, math.EPSILON); +test("math.ceil(5.3)", math.ceil(5.3), 6, math.EPSILON); +test("math.clz32(1)", math.clz32(1), 31, math.EPSILON); +test("math.floor(5.7)", math.floor(5.7), 5, math.EPSILON); +test("math.max(3, 5)", math.max(3, 5), 5, math.EPSILON); +test("math.min(3, 5)", math.min(3, 5), 3, math.EPSILON); +test("math.pow(2, 3)", math.pow(2, 3), 8, math.EPSILON); +test("math.sign(-5)", math.sign(-5), -1, math.EPSILON); +test("math.sqrt(25)", math.sqrt(25), 5, math.EPSILON); +test("math.trunc(5.7)", math.trunc(5.7), 5, math.EPSILON); +test("math.cos(math.PI)", math.cos(math.PI), -1, math.EPSILON * 18); // Error 3.77475828372553223744e-15 +test("math.exp(1)", math.exp(1), 2.718281828459045, math.EPSILON * 2); // Error 4.44089209850062616169e-16 +test("math.sin(math.PI / 2)", math.sin(math.PI / 2), 1, math.EPSILON * 4.5); // Error 9.99200722162640886381e-16 + +if (failed > 0) { + print("!!!", failed, "Unit tests failed !!!"); +} \ No newline at end of file diff --git a/applications/system/js_app/examples/apps/Scripts/submenu.js b/applications/system/js_app/examples/apps/Scripts/submenu.js new file mode 100644 index 000000000..245551309 --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/submenu.js @@ -0,0 +1,11 @@ +let submenu = require("submenu"); + +submenu.addItem("Item 1", 0); +submenu.addItem("Item 2", 1); +submenu.addItem("Item 3", 2); + +submenu.setHeader("Select an option:"); + +let result = submenu.show(); +// Returns undefined when pressing back +print("Result:", result); diff --git a/applications/system/js_app/examples/apps/Scripts/textbox.js b/applications/system/js_app/examples/apps/Scripts/textbox.js new file mode 100644 index 000000000..6caf37234 --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/textbox.js @@ -0,0 +1,30 @@ +let textbox = require("textbox"); + +// You should set config before adding text +// Focus (start / end), Font (text / hex) +textbox.setConfig("end", "text"); + +// Can make sure it's cleared before showing, in case of reusing in same script +// (Closing textbox already clears the text, but maybe you added more in a loop for example) +textbox.clearText(); + +// Add default text +textbox.addText("Example dynamic updating textbox\n"); + +// Non-blocking, can keep updating text after, can close in JS or in GUI +textbox.show(); + +let i = 0; +while (textbox.isOpen() && i < 20) { + print("console", i++); + + // Add text to textbox buffer + textbox.addText("textbox " + to_string(i) + "\n"); + + delay(500); +} + +// If not closed by user (instead i < 20 is false above), close forcefully +if (textbox.isOpen()) { + textbox.close(); +} diff --git a/applications/system/js_app/js_thread.c b/applications/system/js_app/js_thread.c index 759d63b0e..78b6f6ff4 100644 --- a/applications/system/js_app/js_thread.c +++ b/applications/system/js_app/js_thread.c @@ -285,7 +285,7 @@ static int32_t js_thread(void* arg) { } const char* stack_trace = mjs_get_stack_trace(mjs); if(stack_trace != NULL) { - FURI_LOG_E(TAG, "Stack trace:\n%s", stack_trace); + FURI_LOG_E(TAG, "Stack trace:\r\n%s", stack_trace); if(worker->app_callback) { worker->app_callback(JsThreadEventErrorTrace, stack_trace, worker->context); } diff --git a/applications/system/js_app/modules/js_math.c b/applications/system/js_app/modules/js_math.c new file mode 100644 index 000000000..b4c1cdca2 --- /dev/null +++ b/applications/system/js_app/modules/js_math.c @@ -0,0 +1,355 @@ +#include "../js_modules.h" +#include "furi_hal_random.h" +#include + +#define JS_MATH_PI ((double)M_PI) +#define JS_MATH_E ((double)M_E) +#define JS_MATH_EPSILON ((double)DBL_EPSILON) + +#define TAG "JsMath" + +static void ret_bad_args(struct mjs* mjs, const char* error) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "%s", error); + mjs_return(mjs, MJS_UNDEFINED); +} + +static bool check_args(struct mjs* mjs, size_t count) { + size_t num_args = mjs_nargs(mjs); + if(num_args != count) { + ret_bad_args(mjs, "Wrong argument count"); + return false; + } + for(size_t i = 0; i < count; i++) { + if(!mjs_is_number(mjs_arg(mjs, i))) { + ret_bad_args(mjs, "Wrong argument type"); + return false; + } + } + return true; +} + +void js_math_is_equal(struct mjs* mjs) { + if(!check_args(mjs, 3)) { + return; + } + + double a = mjs_get_double(mjs, mjs_arg(mjs, 0)); + double b = mjs_get_double(mjs, mjs_arg(mjs, 1)); + double e = mjs_get_double(mjs, mjs_arg(mjs, 2)); + double f = fabs(a - b); + + mjs_return(mjs, mjs_mk_boolean(mjs, (f <= e))); +} + +void js_math_abs(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + + mjs_return(mjs, mjs_mk_number(mjs, fabs(x))); +} + +void js_math_acos(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + if(x < (double)-1. || x > (double)1.) { + ret_bad_args(mjs, "Invalid input value for math.acos"); + return; + } + + mjs_return(mjs, mjs_mk_number(mjs, acos(x))); +} + +void js_math_acosh(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + if(x < (double)1.) { + ret_bad_args(mjs, "Invalid input value for math.acosh"); + return; + } + + mjs_return(mjs, mjs_mk_number(mjs, log(x + sqrt(x * x - (double)1.)))); +} + +void js_math_asin(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + + mjs_return(mjs, mjs_mk_number(mjs, asin(x))); +} + +void js_math_asinh(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + + mjs_return(mjs, mjs_mk_number(mjs, log(x + sqrt(x * x + (double)1.)))); +} + +void js_math_atan(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + + mjs_return(mjs, mjs_mk_number(mjs, atan(x))); +} + +void js_math_atan2(struct mjs* mjs) { + if(!check_args(mjs, 2)) { + return; + } + + double y = mjs_get_double(mjs, mjs_arg(mjs, 0)); + double x = mjs_get_double(mjs, mjs_arg(mjs, 1)); + + mjs_return(mjs, mjs_mk_number(mjs, atan2(y, x))); +} + +void js_math_atanh(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + if(x < (double)-1. || x > (double)1.) { + ret_bad_args(mjs, "Invalid input value for math.atanh"); + return; + } + + mjs_return(mjs, mjs_mk_number(mjs, (double)0.5 * log(((double)1. + x) / ((double)1. - x)))); +} + +void js_math_cbrt(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + + mjs_return(mjs, mjs_mk_number(mjs, cbrt(x))); +} + +void js_math_ceil(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + mjs_return(mjs, mjs_mk_number(mjs, ceil(x))); +} + +void js_math_clz32(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + unsigned int x = (unsigned int)mjs_get_int(mjs, mjs_arg(mjs, 0)); + int count = 0; + while(x) { + x >>= 1; + count++; + } + + mjs_return(mjs, mjs_mk_number(mjs, 32 - count)); +} + +void js_math_cos(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + + mjs_return(mjs, mjs_mk_number(mjs, cos(x))); +} + +void js_math_exp(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + + mjs_return(mjs, mjs_mk_number(mjs, exp(x))); +} + +void js_math_floor(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + + mjs_return(mjs, mjs_mk_number(mjs, floor(x))); +} + +void js_math_log(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + if(x <= 0) { + ret_bad_args(mjs, "Invalid input value for math.log"); + return; + } + + mjs_return(mjs, mjs_mk_number(mjs, log(x))); +} + +void js_math_max(struct mjs* mjs) { + if(!check_args(mjs, 2)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + double y = mjs_get_double(mjs, mjs_arg(mjs, 1)); + + mjs_return(mjs, mjs_mk_number(mjs, x > y ? x : y)); +} + +void js_math_min(struct mjs* mjs) { + if(!check_args(mjs, 2)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + double y = mjs_get_double(mjs, mjs_arg(mjs, 1)); + + mjs_return(mjs, mjs_mk_number(mjs, x < y ? x : y)); +} + +void js_math_pow(struct mjs* mjs) { + if(!check_args(mjs, 2)) { + return; + } + + double base = mjs_get_double(mjs, mjs_arg(mjs, 0)); + double exponent = mjs_get_double(mjs, mjs_arg(mjs, 1)); + + mjs_return(mjs, mjs_mk_number(mjs, pow(base, exponent))); +} + +void js_math_random(struct mjs* mjs) { + if(!check_args(mjs, 0)) { + return; + } + + // double clearly provides more bits for entropy then we pack + // 32bit should be enough for now, but fix it maybe + const uint32_t random_val = furi_hal_random_get(); + double rnd = (double)random_val / (double)FURI_HAL_RANDOM_MAX; + + mjs_return(mjs, mjs_mk_number(mjs, rnd)); +} + +void js_math_sign(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + + mjs_return( + mjs, + mjs_mk_number( + mjs, fabs(x) <= JS_MATH_EPSILON ? 0 : (x < (double)0. ? (double)-1.0 : (double)1.0))); +} + +void js_math_sin(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + + mjs_return(mjs, mjs_mk_number(mjs, sin(x))); +} + +void js_math_sqrt(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + if(x < (double)0.) { + ret_bad_args(mjs, "Invalid input value for math.sqrt"); + return; + } + + mjs_return(mjs, mjs_mk_number(mjs, sqrt(x))); +} + +void js_math_trunc(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + + mjs_return(mjs, mjs_mk_number(mjs, x < (double)0. ? ceil(x) : floor(x))); +} + +static void* js_math_create(struct mjs* mjs, mjs_val_t* object) { + mjs_val_t math_obj = mjs_mk_object(mjs); + mjs_set(mjs, math_obj, "is_equal", ~0, MJS_MK_FN(js_math_is_equal)); + mjs_set(mjs, math_obj, "abs", ~0, MJS_MK_FN(js_math_abs)); + mjs_set(mjs, math_obj, "acos", ~0, MJS_MK_FN(js_math_acos)); + mjs_set(mjs, math_obj, "acosh", ~0, MJS_MK_FN(js_math_acosh)); + mjs_set(mjs, math_obj, "asin", ~0, MJS_MK_FN(js_math_asin)); + mjs_set(mjs, math_obj, "asinh", ~0, MJS_MK_FN(js_math_asinh)); + mjs_set(mjs, math_obj, "atan", ~0, MJS_MK_FN(js_math_atan)); + mjs_set(mjs, math_obj, "atan2", ~0, MJS_MK_FN(js_math_atan2)); + mjs_set(mjs, math_obj, "atanh", ~0, MJS_MK_FN(js_math_atanh)); + mjs_set(mjs, math_obj, "cbrt", ~0, MJS_MK_FN(js_math_cbrt)); + mjs_set(mjs, math_obj, "ceil", ~0, MJS_MK_FN(js_math_ceil)); + mjs_set(mjs, math_obj, "clz32", ~0, MJS_MK_FN(js_math_clz32)); + mjs_set(mjs, math_obj, "cos", ~0, MJS_MK_FN(js_math_cos)); + mjs_set(mjs, math_obj, "exp", ~0, MJS_MK_FN(js_math_exp)); + mjs_set(mjs, math_obj, "floor", ~0, MJS_MK_FN(js_math_floor)); + mjs_set(mjs, math_obj, "log", ~0, MJS_MK_FN(js_math_log)); + mjs_set(mjs, math_obj, "max", ~0, MJS_MK_FN(js_math_max)); + mjs_set(mjs, math_obj, "min", ~0, MJS_MK_FN(js_math_min)); + mjs_set(mjs, math_obj, "pow", ~0, MJS_MK_FN(js_math_pow)); + mjs_set(mjs, math_obj, "random", ~0, MJS_MK_FN(js_math_random)); + mjs_set(mjs, math_obj, "sign", ~0, MJS_MK_FN(js_math_sign)); + mjs_set(mjs, math_obj, "sin", ~0, MJS_MK_FN(js_math_sin)); + mjs_set(mjs, math_obj, "sqrt", ~0, MJS_MK_FN(js_math_sqrt)); + mjs_set(mjs, math_obj, "trunc", ~0, MJS_MK_FN(js_math_trunc)); + mjs_set(mjs, math_obj, "PI", ~0, mjs_mk_number(mjs, JS_MATH_PI)); + mjs_set(mjs, math_obj, "E", ~0, mjs_mk_number(mjs, JS_MATH_E)); + mjs_set(mjs, math_obj, "EPSILON", ~0, mjs_mk_number(mjs, JS_MATH_EPSILON)); + *object = math_obj; + return (void*)1; +} + +static const JsModuleDescriptor js_math_desc = { + "math", + js_math_create, + NULL, +}; + +static const FlipperAppPluginDescriptor plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &js_math_desc, +}; + +const FlipperAppPluginDescriptor* js_math_ep(void) { + return &plugin_descriptor; +} diff --git a/applications/system/js_app/modules/js_submenu.c b/applications/system/js_app/modules/js_submenu.c new file mode 100644 index 000000000..058b32fd0 --- /dev/null +++ b/applications/system/js_app/modules/js_submenu.c @@ -0,0 +1,148 @@ +#include +#include +#include +#include +#include "../js_modules.h" + +typedef struct { + Submenu* submenu; + ViewHolder* view_holder; + FuriApiLock lock; + uint32_t result; + bool accepted; +} JsSubmenuInst; + +static JsSubmenuInst* get_this_ctx(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsSubmenuInst* submenu = mjs_get_ptr(mjs, obj_inst); + furi_assert(submenu); + return submenu; +} + +static void ret_bad_args(struct mjs* mjs, const char* error) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "%s", error); + mjs_return(mjs, MJS_UNDEFINED); +} + +static bool check_arg_count(struct mjs* mjs, size_t count) { + size_t num_args = mjs_nargs(mjs); + if(num_args != count) { + ret_bad_args(mjs, "Wrong argument count"); + return false; + } + return true; +} + +static void submenu_callback(void* context, uint32_t id) { + JsSubmenuInst* submenu = context; + submenu->result = id; + submenu->accepted = true; + api_lock_unlock(submenu->lock); +} + +static void submenu_exit(void* context) { + JsSubmenuInst* submenu = context; + submenu->result = 0; + submenu->accepted = false; + api_lock_unlock(submenu->lock); +} + +static void js_submenu_add_item(struct mjs* mjs) { + JsSubmenuInst* submenu = get_this_ctx(mjs); + if(!check_arg_count(mjs, 2)) return; + + mjs_val_t label_arg = mjs_arg(mjs, 0); + const char* label = mjs_get_string(mjs, &label_arg, NULL); + if(!label) { + ret_bad_args(mjs, "Label must be a string"); + return; + } + + mjs_val_t id_arg = mjs_arg(mjs, 1); + if(!mjs_is_number(id_arg)) { + ret_bad_args(mjs, "Id must be a number"); + return; + } + int32_t id = mjs_get_int32(mjs, id_arg); + + submenu_add_item(submenu->submenu, label, id, submenu_callback, submenu); + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_submenu_set_header(struct mjs* mjs) { + JsSubmenuInst* submenu = get_this_ctx(mjs); + if(!check_arg_count(mjs, 1)) return; + + mjs_val_t header_arg = mjs_arg(mjs, 0); + const char* header = mjs_get_string(mjs, &header_arg, NULL); + if(!header) { + ret_bad_args(mjs, "Header must be a string"); + return; + } + + submenu_set_header(submenu->submenu, header); + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_submenu_show(struct mjs* mjs) { + JsSubmenuInst* submenu = get_this_ctx(mjs); + if(!check_arg_count(mjs, 0)) return; + + submenu->lock = api_lock_alloc_locked(); + Gui* gui = furi_record_open(RECORD_GUI); + submenu->view_holder = view_holder_alloc(); + view_holder_attach_to_gui(submenu->view_holder, gui); + view_holder_set_back_callback(submenu->view_holder, submenu_exit, submenu); + + view_holder_set_view(submenu->view_holder, submenu_get_view(submenu->submenu)); + view_holder_start(submenu->view_holder); + api_lock_wait_unlock(submenu->lock); + + view_holder_stop(submenu->view_holder); + view_holder_free(submenu->view_holder); + furi_record_close(RECORD_GUI); + api_lock_free(submenu->lock); + + submenu_reset(submenu->submenu); + if(submenu->accepted) { + mjs_return(mjs, mjs_mk_number(mjs, submenu->result)); + } else { + mjs_return(mjs, MJS_UNDEFINED); + } +} + +static void* js_submenu_create(struct mjs* mjs, mjs_val_t* object) { + JsSubmenuInst* submenu = malloc(sizeof(JsSubmenuInst)); + mjs_val_t submenu_obj = mjs_mk_object(mjs); + mjs_set(mjs, submenu_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, submenu)); + mjs_set(mjs, submenu_obj, "addItem", ~0, MJS_MK_FN(js_submenu_add_item)); + mjs_set(mjs, submenu_obj, "setHeader", ~0, MJS_MK_FN(js_submenu_set_header)); + mjs_set(mjs, submenu_obj, "show", ~0, MJS_MK_FN(js_submenu_show)); + submenu->submenu = submenu_alloc(); + *object = submenu_obj; + return submenu; +} + +static void js_submenu_destroy(void* inst) { + JsSubmenuInst* submenu = inst; + submenu_free(submenu->submenu); + free(submenu); +} + +static const JsModuleDescriptor js_submenu_desc = { + "submenu", + js_submenu_create, + js_submenu_destroy, +}; + +static const FlipperAppPluginDescriptor submenu_plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &js_submenu_desc, +}; + +const FlipperAppPluginDescriptor* js_submenu_ep(void) { + return &submenu_plugin_descriptor; +} diff --git a/applications/system/js_app/modules/js_textbox.c b/applications/system/js_app/modules/js_textbox.c new file mode 100644 index 000000000..d15cd2779 --- /dev/null +++ b/applications/system/js_app/modules/js_textbox.c @@ -0,0 +1,220 @@ +#include +#include +#include "../js_modules.h" + +typedef struct { + TextBox* text_box; + ViewHolder* view_holder; + FuriString* text; + bool is_shown; +} JsTextboxInst; + +static JsTextboxInst* get_this_ctx(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsTextboxInst* textbox = mjs_get_ptr(mjs, obj_inst); + furi_assert(textbox); + return textbox; +} + +static void ret_bad_args(struct mjs* mjs, const char* error) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "%s", error); + mjs_return(mjs, MJS_UNDEFINED); +} + +static bool check_arg_count(struct mjs* mjs, size_t count) { + size_t num_args = mjs_nargs(mjs); + if(num_args != count) { + ret_bad_args(mjs, "Wrong argument count"); + return false; + } + return true; +} + +static void js_textbox_set_config(struct mjs* mjs) { + JsTextboxInst* textbox = get_this_ctx(mjs); + if(!check_arg_count(mjs, 2)) return; + + TextBoxFocus set_focus = TextBoxFocusStart; + mjs_val_t focus_arg = mjs_arg(mjs, 0); + const char* focus = mjs_get_string(mjs, &focus_arg, NULL); + if(!focus) { + ret_bad_args(mjs, "Focus must be a string"); + return; + } else { + if(!strncmp(focus, "start", strlen("start"))) { + set_focus = TextBoxFocusStart; + } else if(!strncmp(focus, "end", strlen("end"))) { + set_focus = TextBoxFocusEnd; + } else { + ret_bad_args(mjs, "Bad focus value"); + return; + } + } + + TextBoxFont set_font = TextBoxFontText; + mjs_val_t font_arg = mjs_arg(mjs, 1); + const char* font = mjs_get_string(mjs, &font_arg, NULL); + if(!font) { + ret_bad_args(mjs, "Font must be a string"); + return; + } else { + if(!strncmp(font, "text", strlen("text"))) { + set_font = TextBoxFontText; + } else if(!strncmp(font, "hex", strlen("hex"))) { + set_font = TextBoxFontHex; + } else { + ret_bad_args(mjs, "Bad font value"); + return; + } + } + + text_box_set_focus(textbox->text_box, set_focus); + text_box_set_font(textbox->text_box, set_font); + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_textbox_add_text(struct mjs* mjs) { + JsTextboxInst* textbox = get_this_ctx(mjs); + if(!check_arg_count(mjs, 1)) return; + + mjs_val_t text_arg = mjs_arg(mjs, 0); + size_t text_len = 0; + const char* text = mjs_get_string(mjs, &text_arg, &text_len); + if(!text) { + ret_bad_args(mjs, "Text must be a string"); + return; + } + + // Avoid condition race between GUI and JS thread + text_box_set_text(textbox->text_box, ""); + + size_t new_len = furi_string_size(textbox->text) + text_len; + if(new_len >= 4096) { + furi_string_right(textbox->text, new_len / 2); + } + + furi_string_cat(textbox->text, text); + + text_box_set_text(textbox->text_box, furi_string_get_cstr(textbox->text)); + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_textbox_clear_text(struct mjs* mjs) { + JsTextboxInst* textbox = get_this_ctx(mjs); + if(!check_arg_count(mjs, 0)) return; + + // Avoid condition race between GUI and JS thread + text_box_set_text(textbox->text_box, ""); + + furi_string_reset(textbox->text); + + text_box_set_text(textbox->text_box, furi_string_get_cstr(textbox->text)); + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_textbox_is_open(struct mjs* mjs) { + JsTextboxInst* textbox = get_this_ctx(mjs); + if(!check_arg_count(mjs, 0)) return; + + mjs_return(mjs, mjs_mk_boolean(mjs, textbox->is_shown)); +} + +static void textbox_callback(void* context, uint32_t arg) { + UNUSED(arg); + JsTextboxInst* textbox = context; + view_holder_stop(textbox->view_holder); + textbox->is_shown = false; +} + +static void textbox_exit(void* context) { + JsTextboxInst* textbox = context; + // Using timer to schedule view_holder stop, will not work under high CPU load + furi_timer_pending_callback(textbox_callback, textbox, 0); +} + +static void js_textbox_show(struct mjs* mjs) { + JsTextboxInst* textbox = get_this_ctx(mjs); + if(!check_arg_count(mjs, 0)) return; + + if(textbox->is_shown) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Textbox is already shown"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + view_holder_start(textbox->view_holder); + textbox->is_shown = true; + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_textbox_close(struct mjs* mjs) { + JsTextboxInst* textbox = get_this_ctx(mjs); + if(!check_arg_count(mjs, 0)) return; + + view_holder_stop(textbox->view_holder); + textbox->is_shown = false; + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void* js_textbox_create(struct mjs* mjs, mjs_val_t* object) { + JsTextboxInst* textbox = malloc(sizeof(JsTextboxInst)); + + mjs_val_t textbox_obj = mjs_mk_object(mjs); + mjs_set(mjs, textbox_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, textbox)); + mjs_set(mjs, textbox_obj, "setConfig", ~0, MJS_MK_FN(js_textbox_set_config)); + mjs_set(mjs, textbox_obj, "addText", ~0, MJS_MK_FN(js_textbox_add_text)); + mjs_set(mjs, textbox_obj, "clearText", ~0, MJS_MK_FN(js_textbox_clear_text)); + mjs_set(mjs, textbox_obj, "isOpen", ~0, MJS_MK_FN(js_textbox_is_open)); + mjs_set(mjs, textbox_obj, "show", ~0, MJS_MK_FN(js_textbox_show)); + mjs_set(mjs, textbox_obj, "close", ~0, MJS_MK_FN(js_textbox_close)); + + textbox->text = furi_string_alloc(); + textbox->text_box = text_box_alloc(); + + Gui* gui = furi_record_open(RECORD_GUI); + textbox->view_holder = view_holder_alloc(); + view_holder_attach_to_gui(textbox->view_holder, gui); + view_holder_set_back_callback(textbox->view_holder, textbox_exit, textbox); + view_holder_set_view(textbox->view_holder, text_box_get_view(textbox->text_box)); + + *object = textbox_obj; + return textbox; +} + +static void js_textbox_destroy(void* inst) { + JsTextboxInst* textbox = inst; + + view_holder_stop(textbox->view_holder); + view_holder_free(textbox->view_holder); + textbox->view_holder = NULL; + + furi_record_close(RECORD_GUI); + + text_box_reset(textbox->text_box); + furi_string_reset(textbox->text); + + text_box_free(textbox->text_box); + furi_string_free(textbox->text); + free(textbox); +} + +static const JsModuleDescriptor js_textbox_desc = { + "textbox", + js_textbox_create, + js_textbox_destroy, +}; + +static const FlipperAppPluginDescriptor textbox_plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &js_textbox_desc, +}; + +const FlipperAppPluginDescriptor* js_textbox_ep(void) { + return &textbox_plugin_descriptor; +} \ No newline at end of file diff --git a/assets/icons/About/CertificationChina1_122x47.png b/assets/icons/About/CertificationChina1_122x47.png deleted file mode 100644 index c5eebec67..000000000 Binary files a/assets/icons/About/CertificationChina1_122x47.png and /dev/null differ diff --git a/assets/icons/About/CertificationChina1_124x47.png b/assets/icons/About/CertificationChina1_124x47.png new file mode 100644 index 000000000..f40f53394 Binary files /dev/null and b/assets/icons/About/CertificationChina1_124x47.png differ diff --git a/assets/icons/Settings/Cry_dolph_55x52.png b/assets/icons/Settings/LoadingHourglass_24x24.png similarity index 70% rename from assets/icons/Settings/Cry_dolph_55x52.png rename to assets/icons/Settings/LoadingHourglass_24x24.png index 86d9db1b4..9c49dcad1 100644 Binary files a/assets/icons/Settings/Cry_dolph_55x52.png and b/assets/icons/Settings/LoadingHourglass_24x24.png differ diff --git a/assets/icons/Settings/dolph_cry_49x54.png b/assets/icons/Settings/dolph_cry_49x54.png new file mode 100644 index 000000000..351a849b0 Binary files /dev/null and b/assets/icons/Settings/dolph_cry_49x54.png differ diff --git a/assets/icons/Settings/qr_benchmark_25x25.png b/assets/icons/Settings/qr_benchmark_25x25.png new file mode 100644 index 000000000..c5f9df119 Binary files /dev/null and b/assets/icons/Settings/qr_benchmark_25x25.png differ diff --git a/furi/core/memmgr_heap.c b/furi/core/memmgr_heap.c index 3f62b518c..3cee0d377 100644 --- a/furi/core/memmgr_heap.c +++ b/furi/core/memmgr_heap.c @@ -243,7 +243,7 @@ size_t memmgr_heap_get_max_free_block(void) { void memmgr_heap_printf_free_blocks(void) { BlockLink_t* pxBlock; - //TODO enable when we can do printf with a locked scheduler + //can be enabled once we can do printf with a locked scheduler //vTaskSuspendAll(); pxBlock = xStart.pxNextFreeBlock; diff --git a/lib/mjs/mjs_core.c b/lib/mjs/mjs_core.c index aae196599..bcdcb364a 100644 --- a/lib/mjs/mjs_core.c +++ b/lib/mjs/mjs_core.c @@ -280,7 +280,7 @@ static void mjs_append_stack_trace_line(struct mjs* mjs, size_t offset) { const char* filename = mjs_get_bcode_filename_by_offset(mjs, offset); int line_no = mjs_get_lineno_by_offset(mjs, offset); char* new_line = NULL; - const char* fmt = "at %s:%d\n"; + const char* fmt = "\tat %s:%d\r\n"; if(filename == NULL) { // fprintf( // stderr, diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_listener.c b/lib/nfc/protocols/iso15693_3/iso15693_3_listener.c index 84e750858..151e4ae4a 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3_listener.c +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_listener.c @@ -8,7 +8,7 @@ #define TAG "Iso15693_3Listener" -#define ISO15693_3_LISTENER_BUFFER_SIZE (64U) +#define ISO15693_3_LISTENER_BUFFER_SIZE (256U) Iso15693_3Listener* iso15693_3_listener_alloc(Nfc* nfc, Iso15693_3Data* data) { furi_assert(nfc); @@ -67,6 +67,7 @@ NfcCommand iso15693_3_listener_run(NfcGenericEvent event, void* context) { if(nfc_event->type == NfcEventTypeRxEnd) { BitBuffer* rx_buffer = nfc_event->data.buffer; + bit_buffer_reset(instance->tx_buffer); if(iso13239_crc_check(Iso13239CrcTypeDefault, rx_buffer)) { iso13239_crc_trim(rx_buffer); diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.c b/lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.c index a8dec7ae3..6132fbf47 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.c +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.c @@ -64,7 +64,9 @@ static Iso15693_3Error iso15693_3_listener_inventory_handler( if(afi_flag) { const uint8_t afi = *data++; // When AFI flag is set, ignore non-matching requests - if(afi != instance->data->system_info.afi) break; + if(afi != 0) { + if(afi != instance->data->system_info.afi) break; + } } const uint8_t mask_len = *data++; @@ -260,16 +262,9 @@ static Iso15693_3Error iso15693_3_listener_read_multi_blocks_handler( } const uint32_t block_index_start = request->first_block_num; - const uint32_t block_index_end = block_index_start + request->block_count; - - const uint32_t block_count = request->block_count + 1; - const uint32_t block_count_max = instance->data->system_info.block_count; - const uint32_t block_count_available = block_count_max - block_index_start; - - if(block_count > block_count_available) { - error = Iso15693_3ErrorInternal; - break; - } + const uint32_t block_index_end = + MIN((block_index_start + request->block_count + 1), + ((uint32_t)instance->data->system_info.block_count - 1)); error = iso15693_3_listener_extension_handler( instance, diff --git a/lib/nfc/protocols/slix/slix.c b/lib/nfc/protocols/slix/slix.c index 533ecff74..f6ce885d4 100644 --- a/lib/nfc/protocols/slix/slix.c +++ b/lib/nfc/protocols/slix/slix.c @@ -14,6 +14,7 @@ #define SLIX_TYPE_INDICATOR_SLIX (0x02U) #define SLIX_TYPE_INDICATOR_SLIX2 (0x01U) +#define SLIX_CAPABILITIES_KEY "Capabilities" #define SLIX_PASSWORD_READ_KEY "Password Read" #define SLIX_PASSWORD_WRITE_KEY "Password Write" #define SLIX_PASSWORD_PRIVACY_KEY "Password Privacy" @@ -69,6 +70,11 @@ static const SlixTypeFeatures slix_type_features[] = { [SlixTypeSlix2] = SLIX_TYPE_FEATURES_SLIX2, }; +static const char* slix_capabilities_names[SlixCapabilitiesCount] = { + [SlixCapabilitiesDefault] = "Default", + [SlixCapabilitiesAcceptAllPasswords] = "AcceptAllPasswords", +}; + typedef struct { const char* key; SlixTypeFeatures feature_flag; @@ -110,6 +116,7 @@ void slix_reset(SlixData* data) { furi_check(data); iso15693_3_reset(data->iso15693_3_data); + data->capabilities = SlixCapabilitiesDefault; slix_password_set_defaults(data->passwords); memset(&data->system_info, 0, sizeof(SlixSystemInfo)); @@ -123,6 +130,7 @@ void slix_copy(SlixData* data, const SlixData* other) { furi_check(other); iso15693_3_copy(data->iso15693_3_data, other->iso15693_3_data); + data->capabilities = other->capabilities; memcpy(data->passwords, other->passwords, sizeof(SlixPassword) * SlixPasswordTypeCount); memcpy(data->signature, other->signature, sizeof(SlixSignature)); @@ -138,6 +146,30 @@ bool slix_verify(SlixData* data, const FuriString* device_type) { return false; } +static bool slix_load_capabilities(SlixData* data, FlipperFormat* ff) { + bool capabilities_loaded = false; + FuriString* capabilities_str = furi_string_alloc(); + + if(!flipper_format_read_string(ff, SLIX_CAPABILITIES_KEY, capabilities_str)) { + if(flipper_format_rewind(ff)) { + data->capabilities = SlixCapabilitiesDefault; + capabilities_loaded = true; + } + } else { + for(size_t i = 0; i < COUNT_OF(slix_capabilities_names); i++) { + if(furi_string_cmp_str(capabilities_str, slix_capabilities_names[i]) == 0) { + data->capabilities = i; + capabilities_loaded = true; + break; + } + } + } + + furi_string_free(capabilities_str); + + return capabilities_loaded; +} + static bool slix_load_passwords(SlixPassword* passwords, SlixType slix_type, FlipperFormat* ff) { bool ret = true; @@ -164,13 +196,14 @@ bool slix_load(SlixData* data, FlipperFormat* ff, uint32_t version) { furi_check(ff); bool loaded = false; - do { if(!iso15693_3_load(data->iso15693_3_data, ff, version)) break; const SlixType slix_type = slix_get_type(data); if(slix_type >= SlixTypeCount) break; + if(!slix_load_capabilities(data, ff)) break; + if(!slix_load_passwords(data->passwords, slix_type, ff)) break; if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_SIGNATURE)) { @@ -220,6 +253,33 @@ bool slix_load(SlixData* data, FlipperFormat* ff, uint32_t version) { return loaded; } +static bool slix_save_capabilities(const SlixData* data, FlipperFormat* ff) { + bool save_success = false; + + FuriString* tmp_str = furi_string_alloc(); + do { + furi_string_set_str( + tmp_str, "SLIX capabilities field affects emulation modes. Possible options: "); + for(size_t i = 0; i < SlixCapabilitiesCount; i++) { + furi_string_cat_str(tmp_str, slix_capabilities_names[i]); + if(i < SlixCapabilitiesCount - 1) { + furi_string_cat(tmp_str, ", "); + } + } + if(!flipper_format_write_comment_cstr(ff, furi_string_get_cstr(tmp_str))) break; + + if(!flipper_format_write_string_cstr( + ff, SLIX_CAPABILITIES_KEY, slix_capabilities_names[data->capabilities])) + break; + + save_success = true; + } while(false); + + furi_string_free(tmp_str); + + return save_success; +} + static bool slix_save_passwords(const SlixPassword* passwords, SlixType slix_type, FlipperFormat* ff) { bool ret = true; @@ -251,6 +311,8 @@ bool slix_save(const SlixData* data, FlipperFormat* ff) { if(!iso15693_3_save(data->iso15693_3_data, ff)) break; if(!flipper_format_write_comment_cstr(ff, SLIX_PROTOCOL_NAME " specific data")) break; + if(!slix_save_capabilities(data, ff)) break; + if(!flipper_format_write_comment_cstr( ff, "Passwords are optional. If a password is omitted, a default value will be used")) diff --git a/lib/nfc/protocols/slix/slix.h b/lib/nfc/protocols/slix/slix.h index 2de26847a..cc2390c6e 100644 --- a/lib/nfc/protocols/slix/slix.h +++ b/lib/nfc/protocols/slix/slix.h @@ -91,12 +91,20 @@ typedef struct { SlixLockBits lock_bits; } SlixSystemInfo; +typedef enum { + SlixCapabilitiesDefault, + SlixCapabilitiesAcceptAllPasswords, + + SlixCapabilitiesCount, +} SlixCapabilities; + typedef struct { Iso15693_3Data* iso15693_3_data; SlixSystemInfo system_info; SlixSignature signature; SlixPassword passwords[SlixPasswordTypeCount]; SlixPrivacy privacy; + SlixCapabilities capabilities; } SlixData; SlixData* slix_alloc(void); diff --git a/lib/nfc/protocols/slix/slix_listener_i.c b/lib/nfc/protocols/slix/slix_listener_i.c index 15ab2cd3c..66c4241cb 100644 --- a/lib/nfc/protocols/slix/slix_listener_i.c +++ b/lib/nfc/protocols/slix/slix_listener_i.c @@ -54,6 +54,13 @@ static SlixError slix_listener_set_password( } SlixListenerSessionState* session_state = &instance->session_state; + + // With AcceptAllPassword capability set skip password validation + if(instance->data->capabilities == SlixCapabilitiesAcceptAllPasswords) { + session_state->password_match[password_type] = true; + break; + } + session_state->password_match[password_type] = (password == slix_get_password(slix_data, password_type)); diff --git a/lib/nfc/protocols/slix/slix_poller.c b/lib/nfc/protocols/slix/slix_poller.c index 3c9a7cce4..aeb1180cf 100644 --- a/lib/nfc/protocols/slix/slix_poller.c +++ b/lib/nfc/protocols/slix/slix_poller.c @@ -114,7 +114,8 @@ static NfcCommand slix_poller_handler_check_privacy_password(SlixPoller* instanc break; } - instance->error = slix_poller_set_password(instance, SlixPasswordTypePrivacy, pwd); + instance->error = slix_poller_set_password( + instance, SlixPasswordTypePrivacy, pwd, instance->random_number); if(instance->error != SlixErrorNone) { command = NfcCommandReset; break; @@ -145,7 +146,8 @@ static NfcCommand slix_poller_handler_privacy_unlock(SlixPoller* instance) { instance->error = slix_poller_get_random_number(instance, &instance->random_number); if(instance->error != SlixErrorNone) break; - instance->error = slix_poller_set_password(instance, SlixPasswordTypePrivacy, pwd); + instance->error = slix_poller_set_password( + instance, SlixPasswordTypePrivacy, pwd, instance->random_number); if(instance->error != SlixErrorNone) { command = NfcCommandReset; break; diff --git a/lib/nfc/protocols/slix/slix_poller.h b/lib/nfc/protocols/slix/slix_poller.h index 4ea7f880d..e78f7882a 100644 --- a/lib/nfc/protocols/slix/slix_poller.h +++ b/lib/nfc/protocols/slix/slix_poller.h @@ -107,12 +107,16 @@ SlixError slix_poller_get_random_number(SlixPoller* instance, SlixRandomNumber* * Must ONLY be used inside the callback function. * * @param[in, out] instance pointer to the instance to be used in the transaction. - * @param[out] type SlixPasswordType instance. - * @param[out] password SlixPassword instance. + * @param[in] type SlixPasswordType instance. + * @param[in] password SlixPassword instance. + * @param[in] random_number SlixRandomNumber instance. * @return SlixErrorNone on success, an error code on failure. */ -SlixError - slix_poller_set_password(SlixPoller* instance, SlixPasswordType type, SlixPassword password); +SlixError slix_poller_set_password( + SlixPoller* instance, + SlixPasswordType type, + SlixPassword password, + SlixRandomNumber random_number); #ifdef __cplusplus } diff --git a/lib/nfc/protocols/slix/slix_poller_i.c b/lib/nfc/protocols/slix/slix_poller_i.c index 9b0b5ec55..ee6912cc4 100644 --- a/lib/nfc/protocols/slix/slix_poller_i.c +++ b/lib/nfc/protocols/slix/slix_poller_i.c @@ -92,8 +92,11 @@ SlixError slix_poller_get_random_number(SlixPoller* instance, SlixRandomNumber* return error; } -SlixError - slix_poller_set_password(SlixPoller* instance, SlixPasswordType type, SlixPassword password) { +SlixError slix_poller_set_password( + SlixPoller* instance, + SlixPasswordType type, + SlixPassword password, + SlixRandomNumber random_number) { furi_assert(instance); bool skip_uid = (type == SlixPasswordTypePrivacy); @@ -102,8 +105,8 @@ SlixError uint8_t password_type = (0x01 << type); bit_buffer_append_byte(instance->tx_buffer, password_type); - uint8_t rn_l = instance->random_number >> 8; - uint8_t rn_h = instance->random_number; + uint8_t rn_l = random_number >> 8; + uint8_t rn_h = random_number; uint32_t double_rand_num = (rn_h << 24) | (rn_l << 16) | (rn_h << 8) | rn_l; uint32_t xored_password = double_rand_num ^ password; uint8_t xored_password_arr[4] = {}; diff --git a/scripts/map_analyse_upload.py b/scripts/map_analyse_upload.py new file mode 100755 index 000000000..38d961879 --- /dev/null +++ b/scripts/map_analyse_upload.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 + +import os +import requests +import argparse +import subprocess + +# usage: +# COMMIT_HASH, COMMIT_MSG, BRANCH_NAME, +# PULL_ID(optional), PULL_NAME(optional) must be set as envs +# maybe from sctipts/get_env.py +# other args must be set via command line args + + +class AnalyseRequest: + def __init__(self): + self.commit_hash = os.environ["COMMIT_HASH"] + self.commit_msg = os.environ["COMMIT_MSG"] + self.branch_name = os.environ["BRANCH_NAME"] + self.pull_id = os.getenv("PULL_ID", default=None) + self.pull_name = os.getenv("PULL_NAME", default=None) + + def get_payload(self): + return vars(self) + + +class AnalyseUploader: + def __init__(self): + self.args = self.parse_args() + + @staticmethod + def get_sections_size(elf_file) -> dict: + ret = dict() + all_sizes = subprocess.check_output( + ["arm-none-eabi-size", "-A", elf_file], shell=False + ) + all_sizes = all_sizes.splitlines() + + sections_to_keep = (".text", ".rodata", ".data", ".bss", ".free_flash") + for line in all_sizes: + line = line.decode("utf-8") + parts = line.split() + if len(parts) != 3: + continue + section, size, _ = parts + if section not in sections_to_keep: + continue + section_size_payload_name = ( + section[1:] if section.startswith(".") else section + ) + section_size_payload_name += "_size" + ret[section_size_payload_name] = size + return ret + + @staticmethod + def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument("--elf_file", help="Firmware ELF file", required=True) + parser.add_argument("--map_file", help="Firmware MAP file", required=True) + parser.add_argument( + "--analyser_token", help="Analyser auth token", required=True + ) + parser.add_argument( + "--analyser_url", help="Analyser analyse url", required=True + ) + args = parser.parse_args() + return args + + def upload_analyse_request(self): + payload = AnalyseRequest().get_payload() | self.get_sections_size( + self.args.elf_file + ) + headers = {"Authorization": f"Bearer {self.args.analyser_token}"} + file = {"map_file": open(self.args.map_file, "rb")} + response = requests.post( + self.args.analyser_url, data=payload, files=file, headers=headers + ) + if not response.ok: + raise Exception( + f"Failed to upload map file, code: {response.status_code}, reason: {response.text}" + ) + + +if __name__ == "__main__": + analyzer = AnalyseUploader() + analyzer.upload_analyse_request() diff --git a/scripts/map_mariadb_insert.py b/scripts/map_mariadb_insert.py deleted file mode 100755 index a4c9ed5c7..000000000 --- a/scripts/map_mariadb_insert.py +++ /dev/null @@ -1,139 +0,0 @@ -#!/usr/bin/env python3 - -# Requiremets: -# mariadb==1.1.6 - -from datetime import datetime -import argparse -import mariadb -import sys -import os - - -def parseArgs(): - parser = argparse.ArgumentParser() - parser.add_argument("db_user", help="MariaDB user") - parser.add_argument("db_pass", help="MariaDB password") - parser.add_argument("db_host", help="MariaDB hostname") - parser.add_argument("db_port", type=int, help="MariaDB port") - parser.add_argument("db_name", help="MariaDB database") - parser.add_argument("report_file", help="Report file(.map.all)") - args = parser.parse_args() - return args - - -def mariadbConnect(args): - try: - conn = mariadb.connect( - user=args.db_user, - password=args.db_pass, - host=args.db_host, - port=args.db_port, - database=args.db_name, - ) - except mariadb.Error as e: - print(f"Error connecting to MariaDB: {e}") - sys.exit(1) - return conn - - -def parseEnv(): - outArr = [] - outArr.append(datetime.now().strftime("%Y-%m-%d %H:%M:%S")) - outArr.append(os.getenv("COMMIT_HASH", default=None)) - outArr.append(os.getenv("COMMIT_MSG", default=None)) - outArr.append(os.getenv("BRANCH_NAME", default=None)) - outArr.append(os.getenv("BSS_SIZE", default=None)) - outArr.append(os.getenv("TEXT_SIZE", default=None)) - outArr.append(os.getenv("RODATA_SIZE", default=None)) - outArr.append(os.getenv("DATA_SIZE", default=None)) - outArr.append(os.getenv("FREE_FLASH_SIZE", default=None)) - outArr.append(os.getenv("PULL_ID", default=None)) - outArr.append(os.getenv("PULL_NAME", default=None)) - return outArr - - -def createTables(cur, conn): - headerTable = "CREATE TABLE IF NOT EXISTS `header` ( \ - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, \ - `datetime` datetime NOT NULL, \ - `commit` varchar(40) NOT NULL, \ - `commit_msg` text NOT NULL, \ - `branch_name` text NOT NULL, \ - `bss_size` int(10) unsigned NOT NULL, \ - `text_size` int(10) unsigned NOT NULL, \ - `rodata_size` int(10) unsigned NOT NULL, \ - `data_size` int(10) unsigned NOT NULL, \ - `free_flash_size` int(10) unsigned NOT NULL, \ - `pullrequest_id` int(10) unsigned DEFAULT NULL, \ - `pullrequest_name` text DEFAULT NULL, \ - PRIMARY KEY (`id`), \ - KEY `header_id_index` (`id`) )" - dataTable = "CREATE TABLE IF NOT EXISTS `data` ( \ - `header_id` int(10) unsigned NOT NULL, \ - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, \ - `section` text NOT NULL, \ - `address` text NOT NULL, \ - `size` int(10) unsigned NOT NULL, \ - `name` text NOT NULL, \ - `lib` text NOT NULL, \ - `obj_name` text NOT NULL, \ - PRIMARY KEY (`id`), \ - KEY `data_id_index` (`id`), \ - KEY `data_header_id_index` (`header_id`), \ - CONSTRAINT `data_header_id_foreign` FOREIGN KEY (`header_id`) REFERENCES `header` (`id`) )" - cur.execute(headerTable) - cur.execute(dataTable) - conn.commit() - - -def insertHeader(data, cur, conn): - query = "INSERT INTO `header` ( \ - datetime, commit, commit_msg, branch_name, bss_size, text_size, \ - rodata_size, data_size, free_flash_size, pullrequest_id, pullrequest_name) \ - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" - cur.execute(query, data) - conn.commit() - return cur.lastrowid - - -def parseFile(fileObj, headerID): - arr = [] - fileLines = fileObj.readlines() - for line in fileLines: - lineArr = [] - tempLineArr = line.split("\t") - lineArr.append(headerID) - lineArr.append(tempLineArr[0]) # section - lineArr.append(int(tempLineArr[2], 16)) # address hex - lineArr.append(int(tempLineArr[3])) # size - lineArr.append(tempLineArr[4]) # name - lineArr.append(tempLineArr[5]) # lib - lineArr.append(tempLineArr[6]) # obj_name - arr.append(tuple(lineArr)) - return arr - - -def insertData(data, cur, conn): - query = "INSERT INTO `data` ( \ - header_id, section, address, size, \ - name, lib, obj_name) \ - VALUES (?, ?, ?, ?, ? ,?, ?)" - cur.executemany(query, data) - conn.commit() - - -def main(): - args = parseArgs() - dbConn = mariadbConnect(args) - reportFile = open(args.report_file) - dbCurs = dbConn.cursor() - createTables(dbCurs, dbConn) - headerID = insertHeader(parseEnv(), dbCurs, dbConn) - insertData(parseFile(reportFile, headerID), dbCurs, dbConn) - reportFile.close() - dbCurs.close() - - -if __name__ == "__main__": - main() diff --git a/scripts/map_parser.py b/scripts/map_parser.py deleted file mode 100755 index 1efc4fe82..000000000 --- a/scripts/map_parser.py +++ /dev/null @@ -1,274 +0,0 @@ -#!/usr/bin/env python3 - -# Requiremets: -# cxxfilt==0.3.0 - -# Most part of this code written by Lars-Dominik Braun https://github.com/PromyLOPh/linkermapviz -# and distributes under MIT licence - -# Copyright (c) 2017 Lars-Dominik Braun -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - -import sys -import re -import os -from typing import TextIO -from cxxfilt import demangle - - -class Objectfile: - def __init__(self, section: str, offset: int, size: int, comment: str): - self.section = section.strip() - self.offset = offset - self.size = size - self.path = (None, None) - self.basepath = None - - if comment: - self.path = re.match(r"^(.+?)(?:\(([^\)]+)\))?$", comment).groups() - self.basepath = os.path.basename(self.path[0]) - - self.children = [] - - def __repr__(self) -> str: - return f"" - - -def update_children_size(children: list[list], subsection_size: int) -> list: - # set subsection size to an only child - if len(children) == 1: - children[0][1] = subsection_size - return children - - rest_size = subsection_size - - for index in range(1, len(children)): - if rest_size > 0: - # current size = current address - previous child address - child_size = children[index][0] - children[index - 1][0] - rest_size -= child_size - children[index - 1][1] = child_size - - # if there is rest size, set it to the last child element - if rest_size > 0: - children[-1][1] = rest_size - - return children - - -def parse_sections(file_name: str) -> list: - """ - Quick&Dirty parsing for GNU ld’s linker map output, needs LANG=C, because - some messages are localized. - """ - - sections = [] - with open(file_name, "r") as file: - # skip until memory map is found - found = False - - while True: - line = file.readline() - if not line: - break - if line.strip() == "Memory Configuration": - found = True - break - - if not found: - raise Exception(f"Memory configuration is not found in the{input_file}") - - # long section names result in a linebreak afterwards - sectionre = re.compile( - "(?P
.+?|.{14,}\n)[ ]+0x(?P[0-9a-f]+)[ ]+0x(?P[0-9a-f]+)(?:[ ]+(?P.+))?\n+", - re.I, - ) - subsectionre = re.compile( - "[ ]{16}0x(?P[0-9a-f]+)[ ]+(?P.+)\n+", re.I - ) - s = file.read() - pos = 0 - - while True: - m = sectionre.match(s, pos) - if not m: - # skip that line - try: - nextpos = s.index("\n", pos) + 1 - pos = nextpos - continue - except ValueError: - break - - pos = m.end() - section = m.group("section") - v = m.group("offset") - offset = int(v, 16) if v is not None else None - v = m.group("size") - size = int(v, 16) if v is not None else None - comment = m.group("comment") - - if section != "*default*" and size > 0: - of = Objectfile(section, offset, size, comment) - - if section.startswith(" "): - children = [] - sections[-1].children.append(of) - - while True: - m = subsectionre.match(s, pos) - if not m: - break - pos = m.end() - offset, function = m.groups() - offset = int(offset, 16) - if sections and sections[-1].children: - children.append([offset, 0, function]) - - if children: - children = update_children_size( - children=children, subsection_size=of.size - ) - - sections[-1].children[-1].children.extend(children) - - else: - sections.append(of) - - return sections - - -def get_subsection_name(section_name: str, subsection: Objectfile) -> str: - subsection_split_names = subsection.section.split(".") - if subsection.section.startswith("."): - subsection_split_names = subsection_split_names[1:] - - return ( - f".{subsection_split_names[1]}" - if len(subsection_split_names) > 2 - else section_name - ) - - -def write_subsection( - section_name: str, - subsection_name: str, - address: str, - size: int, - demangled_name: str, - module_name: str, - file_name: str, - mangled_name: str, - write_file_object: TextIO, -) -> None: - write_file_object.write( - f"{section_name}\t" - f"{subsection_name}\t" - f"{address}\t" - f"{size}\t" - f"{demangled_name}\t" - f"{module_name}\t" - f"{file_name}\t" - f"{mangled_name}\n" - ) - - -def save_subsection( - section_name: str, subsection: Objectfile, write_file_object: TextIO -) -> None: - subsection_name = get_subsection_name(section_name, subsection) - module_name = subsection.path[0] - file_name = subsection.path[1] - - if not file_name: - file_name, module_name = module_name, "" - - if not subsection.children: - address = f"{subsection.offset:x}" - size = subsection.size - mangled_name = ( - "" - if subsection.section == section_name - else subsection.section.split(".")[-1] - ) - demangled_name = demangle(mangled_name) if mangled_name else mangled_name - - write_subsection( - section_name=section_name, - subsection_name=subsection_name, - address=address, - size=size, - demangled_name=demangled_name, - module_name=module_name, - file_name=file_name, - mangled_name=mangled_name, - write_file_object=write_file_object, - ) - return - - for subsection_child in subsection.children: - address = f"{subsection_child[0]:x}" - size = subsection_child[1] - mangled_name = subsection_child[2] - demangled_name = demangle(mangled_name) - - write_subsection( - section_name=section_name, - subsection_name=subsection_name, - address=address, - size=size, - demangled_name=demangled_name, - module_name=module_name, - file_name=file_name, - mangled_name=mangled_name, - write_file_object=write_file_object, - ) - - -def save_section(section: Objectfile, write_file_object: TextIO) -> None: - section_name = section.section - for subsection in section.children: - save_subsection( - section_name=section_name, - subsection=subsection, - write_file_object=write_file_object, - ) - - -def save_parsed_data(parsed_data: list[Objectfile], output_file_name: str) -> None: - with open(output_file_name, "w") as write_file_object: - for section in parsed_data: - if section.children: - save_section(section=section, write_file_object=write_file_object) - - -if __name__ == "__main__": - if len(sys.argv) < 3: - raise Exception(f"Usage: {sys.argv[0]} ") - - input_file = sys.argv[1] - output_file = sys.argv[2] - - parsed_sections = parse_sections(input_file) - - if parsed_sections is None: - raise Exception(f"Memory configuration is not {input_file}") - - save_parsed_data(parsed_sections, output_file) diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 3cee044aa..ce7ad2536 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,61.2,, +Version,+,61.4,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -27,6 +27,7 @@ Header,+,applications/services/gui/modules/variable_item_list.h,, Header,+,applications/services/gui/modules/widget.h,, Header,+,applications/services/gui/modules/widget_elements/widget_element.h,, Header,+,applications/services/gui/view_dispatcher.h,, +Header,+,applications/services/gui/view_holder.h,, Header,+,applications/services/gui/view_stack.h,, Header,+,applications/services/input/input.h,, Header,+,applications/services/loader/firmware_api/firmware_api.h,, @@ -2554,7 +2555,9 @@ Function,-,strxfrm,size_t,"char*, const char*, size_t" Function,-,strxfrm_l,size_t,"char*, const char*, size_t, locale_t" Function,+,submenu_add_item,void,"Submenu*, const char*, uint32_t, SubmenuItemCallback, void*" Function,+,submenu_alloc,Submenu*, +Function,+,submenu_change_item_label,void,"Submenu*, uint32_t, const char*" Function,+,submenu_free,void,Submenu* +Function,+,submenu_get_selected_item,uint32_t,Submenu* Function,+,submenu_get_view,View*,Submenu* Function,+,submenu_reset,void,Submenu* Function,+,submenu_set_header,void,"Submenu*, const char*" @@ -2681,6 +2684,16 @@ Function,+,view_dispatcher_switch_to_view,void,"ViewDispatcher*, uint32_t" Function,+,view_free,void,View* Function,+,view_free_model,void,View* Function,+,view_get_model,void*,View* +Function,+,view_holder_alloc,ViewHolder*, +Function,+,view_holder_attach_to_gui,void,"ViewHolder*, Gui*" +Function,+,view_holder_free,void,ViewHolder* +Function,+,view_holder_get_free_context,void*,ViewHolder* +Function,+,view_holder_set_back_callback,void,"ViewHolder*, BackCallback, void*" +Function,+,view_holder_set_free_callback,void,"ViewHolder*, FreeCallback, void*" +Function,+,view_holder_set_view,void,"ViewHolder*, View*" +Function,+,view_holder_start,void,ViewHolder* +Function,+,view_holder_stop,void,ViewHolder* +Function,+,view_holder_update,void,"View*, void*" Function,+,view_port_alloc,ViewPort*, Function,+,view_port_draw_callback_set,void,"ViewPort*, ViewPortDrawCallback, void*" Function,+,view_port_enabled_set,void,"ViewPort*, _Bool" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index b7d608d0b..190995b2d 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,61.2,, +Version,+,61.4,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -28,6 +28,7 @@ Header,+,applications/services/gui/modules/variable_item_list.h,, Header,+,applications/services/gui/modules/widget.h,, Header,+,applications/services/gui/modules/widget_elements/widget_element.h,, Header,+,applications/services/gui/view_dispatcher.h,, +Header,+,applications/services/gui/view_holder.h,, Header,+,applications/services/gui/view_stack.h,, Header,+,applications/services/input/input.h,, Header,+,applications/services/loader/firmware_api/firmware_api.h,, @@ -3366,7 +3367,9 @@ Function,+,subghz_worker_start,void,SubGhzWorker* Function,+,subghz_worker_stop,void,SubGhzWorker* Function,+,submenu_add_item,void,"Submenu*, const char*, uint32_t, SubmenuItemCallback, void*" Function,+,submenu_alloc,Submenu*, +Function,+,submenu_change_item_label,void,"Submenu*, uint32_t, const char*" Function,+,submenu_free,void,Submenu* +Function,+,submenu_get_selected_item,uint32_t,Submenu* Function,+,submenu_get_view,View*,Submenu* Function,+,submenu_reset,void,Submenu* Function,+,submenu_set_header,void,"Submenu*, const char*" @@ -3496,6 +3499,16 @@ Function,+,view_dispatcher_switch_to_view,void,"ViewDispatcher*, uint32_t" Function,+,view_free,void,View* Function,+,view_free_model,void,View* Function,+,view_get_model,void*,View* +Function,+,view_holder_alloc,ViewHolder*, +Function,+,view_holder_attach_to_gui,void,"ViewHolder*, Gui*" +Function,+,view_holder_free,void,ViewHolder* +Function,+,view_holder_get_free_context,void*,ViewHolder* +Function,+,view_holder_set_back_callback,void,"ViewHolder*, BackCallback, void*" +Function,+,view_holder_set_free_callback,void,"ViewHolder*, FreeCallback, void*" +Function,+,view_holder_set_view,void,"ViewHolder*, View*" +Function,+,view_holder_start,void,ViewHolder* +Function,+,view_holder_stop,void,ViewHolder* +Function,+,view_holder_update,void,"View*, void*" Function,+,view_port_alloc,ViewPort*, Function,+,view_port_draw_callback_set,void,"ViewPort*, ViewPortDrawCallback, void*" Function,+,view_port_enabled_set,void,"ViewPort*, _Bool"