diff --git a/.ci_files/anims_ofw.txt b/.ci_files/anims_ofw.txt index c0e1741a0c..42e18ad3f2 100644 --- a/.ci_files/anims_ofw.txt +++ b/.ci_files/anims_ofw.txt @@ -189,3 +189,10 @@ Max butthurt: 12 Min level: 3 Max level: 3 Weight: 5 + +Name: L1_Akira_128x64 +Min butthurt: 0 +Max butthurt: 8 +Min level: 1 +Max level: 3 +Weight: 5 diff --git a/.vscode/example/clangd/extensions.json b/.vscode/example/clangd/extensions.json index daab417cd0..4f24dd7b5d 100644 --- a/.vscode/example/clangd/extensions.json +++ b/.vscode/example/clangd/extensions.json @@ -8,7 +8,8 @@ "amiralizadeh9480.cpp-helper", "marus25.cortex-debug", "zxh404.vscode-proto3", - "augustocdias.tasks-shell-input" + "augustocdias.tasks-shell-input", + "rioj7.command-variable" ], // List of extensions recommended by VS Code that should not be recommended for users of this workspace. "unwantedRecommendations": [ @@ -16,4 +17,4 @@ "ms-vscode.cpptools", "ms-vscode.cmake-tools" ] -} +} \ No newline at end of file diff --git a/.vscode/example/cpptools/c_cpp_properties.json b/.vscode/example/cpptools/c_cpp_properties.json index 3f8d15a5d4..245d44ca1c 100644 --- a/.vscode/example/cpptools/c_cpp_properties.json +++ b/.vscode/example/cpptools/c_cpp_properties.json @@ -2,7 +2,7 @@ "configurations": [ { "name": "Win32", - "compilerPath": "${workspaceFolder}/toolchain/x86_64-windows/bin/arm-none-eabi-gcc.exe", + "compilerPath": "${workspaceFolder}/toolchain/current/bin/arm-none-eabi-gcc.exe", "intelliSenseMode": "gcc-arm", "compileCommands": "${workspaceFolder}/build/latest/compile_commands.json", "cStandard": "gnu23", @@ -10,7 +10,7 @@ }, { "name": "Linux", - "compilerPath": "${workspaceFolder}/toolchain/x86_64-linux/bin/arm-none-eabi-gcc", + "compilerPath": "${workspaceFolder}/toolchain/current/bin/arm-none-eabi-gcc", "intelliSenseMode": "gcc-arm", "compileCommands": "${workspaceFolder}/build/latest/compile_commands.json", "cStandard": "gnu23", @@ -18,7 +18,7 @@ }, { "name": "Mac", - "compilerPath": "${workspaceFolder}/toolchain/x86_64-darwin/bin/arm-none-eabi-gcc", + "compilerPath": "${workspaceFolder}/toolchain/current/bin/arm-none-eabi-gcc", "intelliSenseMode": "gcc-arm", "compileCommands": "${workspaceFolder}/build/latest/compile_commands.json", "cStandard": "gnu23", diff --git a/.vscode/example/tasks.json b/.vscode/example/tasks.json index 9861afa1e5..c0b9e8867e 100644 --- a/.vscode/example/tasks.json +++ b/.vscode/example/tasks.json @@ -91,7 +91,7 @@ "label": "[Debug:unit_tests] Flash (USB)", "group": "build", "type": "shell", - "command": "./fbt FIRMWARE_APP_SET=unit_tests FORCE=1 flash_usb" + "command": "./fbt FIRMWARE_APP_SET=unit_tests FORCE=1 flash_usb_full" }, { "label": "[Debug] Flash (USB, with resources)", diff --git a/CHANGELOG.md b/CHANGELOG.md index 72b83ae12b..fd085a6efe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,75 +1,42 @@ ## New changes -* LFRFID: **Electra intercom protocol support** (Romania) (by @Leptopt1los | PR #750) -* NFC: Temp fix for `iso14443_4_layer_decode_block` crash -* NFC: CharlieCard parser (by @zacharyweiss) -* SubGHz: FAAC RC XT - add 0xB button code on arrow buttons for programming mode -* SubGHz: Add Manually - Sommer FM fixes -* SubGHz: Enabled tx-rx state on unused gpio pin by default (**external amp option was removed and is enabled by default now**) -* SubGHz: **Status output !TX/RX on the GDO2 CC1101 pin** (by @quen0n | PR #742) -* SubGHz: Reworked saved settings (by @xMasterX and @Willy-JL) -* Desktop: Fixes for animation unload (by @Willy-JL) -* iButton: Updated DS1420 for latest ibutton changes -* Misc: Allow no prefix usage of name_generator_make_detailed_datetime -* Misc: Allow setting view dispatcher callbacks to NULL -* Misc: Added `void` due to `-Wstrict-prototypes` -* Misc: Some code cleanup and proper log levels in nfc parsers -* Infrared: Allow external apps to use infrared settings (by @Willy-JL) -* JS & HAL: Various fixes and FURI_HAL_RANDOM_MAX define added (by @Willy-JL) -* JS: **BadUSB layout support** (by @Willy-JL) -* JS: New Modules `widget`, `vgm` and path globals (by @jamisonderek) -* Apps: Enhance Random Interval and Movement Functionality in HID Mouse Jiggler for Improved Stealth and Human-Like Behavior (by @gushmazuko | PR #746) -* Apps: NFC Magic - **Gen2 writing support, Gen4 NTAG password and PACK fixes** (by @Astrrra) -* Apps: MFKey - **fixed crashes**, add more free ram (by @noproto & @Willy-JL) -* Apps: **Check out Apps updates by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) -* OFW PR 3616: NFC: Mf Desfire fix reading big files (by gornekich) -* OFW: iButton: fix crash when deleting some keys -* OFW: Desktop: cleanup error popups -* OFW: Troika parser visual fixes -* OFW: Fix the retry/exit confirmation prompts in iButton -* OFW: nfc app: add legacy keys for plantain cards -* OFW: GUI: Fix array out of bounds in menu exit -* OFW: add support for S(WTX) request in iso14443_4a_poller -* OFW: Mosgortrans parser output fixes -* OFW: BLE: Add GapPairingNone support -* OFW: iButton new UI -* OFW: FuriHal: add ADC API -* OFW: Mf Desfire multiple file rights support -* OFW: **Felica poller** (NFC-F) -* OFW: Desktop/Loader: Unload animations before loading FAPs -* OFW: JS Documentation -* OFW: **Update radio stack to v1.19.0** -* OFW: **Move crypto1 to helpers, add it to the public API** -* OFW: Explain RNG differences, add FURI_HAL_RANDOM_MAX -* OFW: Furi: Add "out of memory" and "malloc(0)" crash messages -* OFW: IR: Fix crash on duty_cycle=1 -* OFW: **Desktop: ensure that animation is unloaded before app start (fixes some out of memory crashes)** -* OFW: Hide unlock with reader for MFU-C -* OFW: fbt: fixed missing FBT_FAP_DEBUG_ELF_ROOT to dist env -* OFW: fbt: added -Wstrict-prototypes for main firmware -* OFW: Mifare Ultralight naming fix -* OFW: IR: Remember OTG state -* OFW: Bad USB: fix crash when selecting a keyboard layout -* OFW: L1_Mods animation update : adding VGM visual -* OFW: RFID Improvements -* OFW: Fixed plugins and UI -* OFW: **NFC: Fix mf desfire detect** -* OFW: infrared_transmit.h was missing `#pragma once` -* OFW: Show the wrong PIN Attempt count on the login screen -* OFW: SavedStruct: Introduce saved_struct_get_metadata -* OFW: JS CLI command -* OFW: Add ChromeOS Bad USB demo -* OFW: **Configurable Infrared TX output** (previous UL version is replaced with OFW version, new features added "AutoDetect" and saving settings) -* OFW: BadUSB: BLE, media keys, Fn/Globe key commands -* OFW: NFC: Slix privacy password reveal ->(was included in previous UL release) and **Desfire detect fix** -* OFW: github: additional pre-upload checks for doxygen workflow -* OFW: NFC UI fixes -* OFW: Gui: unicode support, new canvas API -* OFW: **Api Symbols: replace asserts with checks** +* Apps: **Mifare Nested - ported to latest API** using old nfc lib (by @xMasterX) (+ mem management fix by @Willy-JL) +* LFRFID: **Electra fix** non-initialized encoded epilogue on render (by @Leptopt1los) +* JS: Move examples to subfolder `js_examples` +* Apps: HID app improvements and fixes
+`- Move new mouse jiggler into mouse jiggler stealth and bring back previous version of mouse jiggler too`
+`- Set stealth jiggler max time default value to 4 min and min value to 1 min`
+`- Merge OFW changes`
+`- More OFW merge fixes` (by @Willy-JL | PR #753)
+* Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) +* OFW (TLSF branch): SubGHz: fix memory corrupt in read raw view +* OFW: **NFC App: fix changing UID** +* OFW: Replaced obsolete-format delay +* OFW: **Archive: fix condition race on exit** +* OFW: Text Box: fix displaying text with end text focus +* OFW: FuriHal: add flash ops stats, workaround bug in SHCI_C2_SetSystemClock +* OFW: Icons: compression fixes & larger dimension support +* OFW: **Text Box rework** +* OFW: Fix calling both `view_free_model()` and `view_free()` +* OFW: JS: Add textbox module +* OFW: JS: Add math module +* OFW: **NFC: add Slix capabilities** +* OFW: Settings refactor fixes +* OFW: JS: Add submenu module +* OFW: **Skylanders plugin** +* OFW: Settings menu refactoring +* OFW: NFC: Mf Desfire fix reading big files +* OFW: Infrared: Add Toshiba RAS-2518D +* OFW: **vscode: config fixes** +* OFW: Ble: new connection parameters negotiation scheme +* OFW: FuriHal: move version init to early stage +* OFW: Add support for R_ARM_REL32 relocations. +* OFW: Remove unused DolphinWait_61x59 icon +* OFW: Add the Akira animation +* OFW: **Desktop: fix crash on autolock after restart in locked state**

#### Known NFC post-refactor regressions list: - Mifare Mini clones reading is broken (original mini working fine) (OFW) - NFC CLI was removed with refactoring (OFW) (will be back soon) -- Mifare Nested not ported to latest API yet, `unlshd-065` is the latest version on old NFC API that works with "nested app" ---- @@ -95,7 +62,7 @@ |TON||`UQCOqcnYkvzOZUV_9bPE_8oTbOrOF03MnF-VcJyjisTZmsxa`| #### Thanks to our sponsors who supported project in the past and special thanks to sponsors who supports us on regular basis: -ClaraCrazy, Pathfinder [Count Zero cDc], callmezimbra, Quen0n, MERRON, grvpvl (lvpvrg), art_col, ThurstonWaffles, Moneron, UterGrooll, LUCFER, Northpirate, zloepuzo, T.Rat, Alexey B., ionelife, ... +@mishamyte, ClaraCrazy, Pathfinder [Count Zero cDc], callmezimbra, Quen0n, MERRON, grvpvl (lvpvrg), art_col, ThurstonWaffles, Moneron, UterGrooll, LUCFER, Northpirate, zloepuzo, T.Rat, Alexey B., ionelife, ... and all other great people who supported our project and me (xMasterX), thanks to you all! diff --git a/applications/debug/text_box_element_test/application.fam b/applications/debug/text_box_element_test/application.fam new file mode 100644 index 0000000000..5e1abcddc8 --- /dev/null +++ b/applications/debug/text_box_element_test/application.fam @@ -0,0 +1,10 @@ +App( + appid="text_box_element_test", + name="Text Box Element Test", + apptype=FlipperAppType.DEBUG, + entry_point="text_box_element_test_app", + requires=["gui"], + stack_size=1 * 1024, + order=140, + fap_category="Debug", +) diff --git a/applications/debug/text_box_test/text_box_test.c b/applications/debug/text_box_element_test/text_box_element_test.c similarity index 98% rename from applications/debug/text_box_test/text_box_test.c rename to applications/debug/text_box_element_test/text_box_element_test.c index b980f686e1..2b9475d2b2 100644 --- a/applications/debug/text_box_test/text_box_test.c +++ b/applications/debug/text_box_element_test/text_box_element_test.c @@ -71,7 +71,7 @@ static void text_box_test_input_callback(InputEvent* input_event, void* ctx) { furi_message_queue_put(event_queue, input_event, FuriWaitForever); } -int32_t text_box_test_app(void* p) { +int32_t text_box_element_test_app(void* p) { UNUSED(p); FuriMessageQueue* event_queue = furi_message_queue_alloc(32, sizeof(InputEvent)); furi_check(event_queue); diff --git a/applications/debug/text_box_test/application.fam b/applications/debug/text_box_view_test/application.fam similarity index 55% rename from applications/debug/text_box_test/application.fam rename to applications/debug/text_box_view_test/application.fam index 823c21d068..e356a278ea 100644 --- a/applications/debug/text_box_test/application.fam +++ b/applications/debug/text_box_view_test/application.fam @@ -1,8 +1,8 @@ App( - appid="text_box_test", - name="Text Box Test", + appid="text_box_view_test", + name="Text Box View Test", apptype=FlipperAppType.DEBUG, - entry_point="text_box_test_app", + entry_point="text_box_view_test_app", requires=["gui"], stack_size=1 * 1024, order=140, diff --git a/applications/debug/text_box_view_test/text_box_view_test.c b/applications/debug/text_box_view_test/text_box_view_test.c new file mode 100644 index 0000000000..7bbcb285b8 --- /dev/null +++ b/applications/debug/text_box_view_test/text_box_view_test.c @@ -0,0 +1,161 @@ +#include +#include +#include +#include +#include + +#define TAG "TextBoxViewTest" + +typedef struct { + TextBoxFont font; + TextBoxFocus focus; + const char* text; +} TextBoxViewTestContent; + +static const TextBoxViewTestContent text_box_view_test_content_arr[] = { + { + .font = TextBoxFontText, + .focus = TextBoxFocusStart, + .text = "Hello, let's test text box. Press Right and Left to switch content", + }, + { + .font = TextBoxFontText, + .focus = TextBoxFocusEnd, + .text = "First test to add dynamically lines with EndFocus set\nLine 0", + }, + { + .font = TextBoxFontText, + .focus = TextBoxFocusEnd, + .text = "First test to add dynamically lines with EndFocus set\nLine 0\nLine 1", + }, + { + .font = TextBoxFontText, + .focus = TextBoxFocusEnd, + .text = "First test to add dynamically lines with EndFocus set\nLine 0\nLine 1\nLine 2", + }, + { + .font = TextBoxFontText, + .focus = TextBoxFocusEnd, + .text = + "First test to add dynamically lines with EndFocus set\nLine 0\nLine 1\nLine 2\nLine 3", + }, + { + .font = TextBoxFontText, + .focus = TextBoxFocusEnd, + .text = + "First test to add dynamically lines with EndFocus set\nLine 0\nLine 1\nLine 2\nLine 3\nLine 4", + }, + { + .font = TextBoxFontText, + .focus = TextBoxFocusStart, + .text = + "Verify that symbols don't overlap borders: llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllend", + }, + { + .font = TextBoxFontText, + .focus = TextBoxFocusStart, + .text = + "\n\n\n Start from several newline chars. Verify that scrolling doesn't break.\n\n\n\n\nThe end", + }, + { + .font = TextBoxFontText, + .focus = TextBoxFocusStart, + .text = + "Let's test big text.\n\n The ARM Cortex-M is a group of 32-bit RISC ARM processor cores licensed by ARM Limited. These cores are optimized for low-cost and energy-efficient integrated circuits, which have been embedded in tens of billions of consumer devices.[1] Though they are most often the main component of microcontroller chips, sometimes they are embedded inside other types of chips too. The Cortex-M family consists of Cortex-M0,[2] Cortex-M0+,[3] Cortex-M1,[4] Cortex-M3,[5] Cortex-M4,[6] Cortex-M7,[7] Cortex-M23,[8] Cortex-M33,[9] Cortex-M35P,[10] Cortex-M52,[11] Cortex-M55,[12] Cortex-M85.[13] A floating-point unit (FPU) option is available for Cortex-M4 / M7 / M33 / M35P / M52 / M55 / M85 cores, and when included in the silicon these cores are sometimes known as \"Cortex-MxF\", where 'x' is the core variant.\n\nThe ARM Cortex-M family are ARM microprocessor cores that are designed for use in microcontrollers, ASICs, ASSPs, FPGAs, and SoCs. Cortex-M cores are commonly used as dedicated microcontroller chips, but also are hidden inside of SoC chips as power management controllers, I/O controllers, system controllers, touch screen controllers, smart battery controllers, and sensor controllers. The main difference from Cortex-A cores is that Cortex-M cores have no memory management unit (MMU) for virtual memory, considered essential for full-fledged operating systems. Cortex-M programs instead run bare metal or on one of the many real-time operating systems which support a Cortex-M.Though 8-bit microcontrollers were very popular in the past, Cortex-M has slowly been chipping away at the 8-bit market as the prices of low-end Cortex-M chips have moved downward. Cortex-M have become a popular replacements for 8-bit chips in applications that benefit from 32-bit math operations, and replacing older legacy ARM cores such as ARM7 and ARM9.", + }, + { + .font = TextBoxFontText, + .focus = TextBoxFocusEnd, + .text = + "The same but with EndFocus\n\n The ARM Cortex-M is a group of 32-bit RISC ARM processor cores licensed by ARM Limited. These cores are optimized for low-cost and energy-efficient integrated circuits, which have been embedded in tens of billions of consumer devices.[1] Though they are most often the main component of microcontroller chips, sometimes they are embedded inside other types of chips too. The Cortex-M family consists of Cortex-M0,[2] Cortex-M0+,[3] Cortex-M1,[4] Cortex-M3,[5] Cortex-M4,[6] Cortex-M7,[7] Cortex-M23,[8] Cortex-M33,[9] Cortex-M35P,[10] Cortex-M52,[11] Cortex-M55,[12] Cortex-M85.[13] A floating-point unit (FPU) option is available for Cortex-M4 / M7 / M33 / M35P / M52 / M55 / M85 cores, and when included in the silicon these cores are sometimes known as \"Cortex-MxF\", where 'x' is the core variant.\n\nThe ARM Cortex-M family are ARM microprocessor cores that are designed for use in microcontrollers, ASICs, ASSPs, FPGAs, and SoCs. Cortex-M cores are commonly used as dedicated microcontroller chips, but also are hidden inside of SoC chips as power management controllers, I/O controllers, system controllers, touch screen controllers, smart battery controllers, and sensor controllers. The main difference from Cortex-A cores is that Cortex-M cores have no memory management unit (MMU) for virtual memory, considered essential for full-fledged operating systems. Cortex-M programs instead run bare metal or on one of the many real-time operating systems which support a Cortex-M.Though 8-bit microcontrollers were very popular in the past, Cortex-M has slowly been chipping away at the 8-bit market as the prices of low-end Cortex-M chips have moved downward. Cortex-M have become a popular replacements for 8-bit chips in applications that benefit from 32-bit math operations, and replacing older legacy ARM cores such as ARM7 and ARM9.", + }, + { + .font = TextBoxFontHex, + .focus = TextBoxFocusEnd, + .text = + "0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999", + }, +}; + +typedef struct { + TextBox* text_box; + ViewDispatcher* view_dispatcher; + size_t current_content_i; +} TextBoxViewTest; + +static void text_box_update_view(TextBoxViewTest* instance) { + // Intentional incorrect way to reset text box to verify that state resets if text changes + text_box_set_text(instance->text_box, ""); + + const TextBoxViewTestContent* content = + &text_box_view_test_content_arr[instance->current_content_i]; + text_box_set_font(instance->text_box, content->font); + text_box_set_focus(instance->text_box, content->focus); + text_box_set_text(instance->text_box, content->text); +} + +static bool text_box_switch_view_input_callback(InputEvent* event, void* context) { + bool consumed = false; + TextBoxViewTest* instance = context; + size_t contents_cnt = COUNT_OF(text_box_view_test_content_arr); + + if(event->type == InputTypeShort) { + if(event->key == InputKeyRight) { + if(instance->current_content_i < contents_cnt - 1) { + instance->current_content_i++; + text_box_update_view(instance); + consumed = true; + } + } else if(event->key == InputKeyLeft) { + if(instance->current_content_i > 0) { + instance->current_content_i--; + text_box_update_view(instance); + consumed = true; + } + } else if(event->key == InputKeyBack) { + view_dispatcher_stop(instance->view_dispatcher); + } + } + + return consumed; +} + +int32_t text_box_view_test_app(void* p) { + UNUSED(p); + + Gui* gui = furi_record_open(RECORD_GUI); + ViewDispatcher* view_dispatcher = view_dispatcher_alloc(); + view_dispatcher_attach_to_gui(view_dispatcher, gui, ViewDispatcherTypeFullscreen); + view_dispatcher_enable_queue(view_dispatcher); + + TextBoxViewTest instance = { + .text_box = text_box_alloc(), + .current_content_i = 0, + .view_dispatcher = view_dispatcher, + }; + + text_box_update_view(&instance); + + View* text_box_switch_view = view_alloc(); + view_set_input_callback(text_box_switch_view, text_box_switch_view_input_callback); + view_set_context(text_box_switch_view, &instance); + + ViewStack* view_stack = view_stack_alloc(); + view_stack_add_view(view_stack, text_box_switch_view); + view_stack_add_view(view_stack, text_box_get_view(instance.text_box)); + + view_dispatcher_add_view(view_dispatcher, 0, view_stack_get_view(view_stack)); + view_dispatcher_switch_to_view(view_dispatcher, 0); + + view_dispatcher_run(view_dispatcher); + + view_dispatcher_remove_view(view_dispatcher, 0); + view_dispatcher_free(view_dispatcher); + view_stack_free(view_stack); + view_free(text_box_switch_view); + text_box_free(instance.text_box); + + furi_record_close(RECORD_GUI); + + return 0; +} diff --git a/applications/debug/unit_tests/application.fam b/applications/debug/unit_tests/application.fam index aa25dab279..f5f84ead79 100644 --- a/applications/debug/unit_tests/application.fam +++ b/applications/debug/unit_tests/application.fam @@ -2,8 +2,9 @@ App( appid="unit_tests", apptype=FlipperAppType.STARTUP, entry_point="unit_tests_on_system_start", + sources=["unit_tests.c", "test_runner.c", "unit_test_api_table.cpp"], cdefines=["APP_UNIT_TESTS"], - requires=["system_settings"], + requires=["system_settings", "subghz_start"], provides=["delay_test"], resources="resources", order=100, @@ -12,9 +13,210 @@ App( App( appid="delay_test", name="Delay Test", + sources=["tests/common/*.c", "tests/rpc/*.c"], apptype=FlipperAppType.SYSTEM, entry_point="delay_test_app", stack_size=1 * 1024, requires=["unit_tests"], order=110, ) + +App( + appid="test_varint", + sources=["tests/common/*.c", "tests/varint/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_furi", + sources=["tests/common/*.c", "tests/furi/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_furi_hal", + sources=["tests/common/*.c", "tests/furi_hal/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_furi_hal_crypto", + sources=["tests/common/*.c", "tests/furi_hal_crypto/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_furi_string", + sources=["tests/common/*.c", "tests/furi_string/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_storage", + sources=["tests/common/*.c", "tests/storage/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_stream", + sources=["tests/common/*.c", "tests/stream/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_dirwalk", + sources=["tests/common/*.c", "tests/dirwalk/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_manifest", + sources=["tests/common/*.c", "tests/manifest/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_flipper_format", + sources=["tests/common/*.c", "tests/flipper_format/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_flipper_format_string", + sources=["tests/common/*.c", "tests/flipper_format_string/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_rpc", + sources=["tests/common/*.c", "tests/rpc/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_subghz", + sources=["tests/common/*.c", "tests/subghz/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_infrared", + sources=["tests/common/*.c", "tests/infrared/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_nfc", + sources=["tests/common/*.c", "tests/nfc/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_power", + sources=["tests/common/*.c", "tests/power/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_protocol_dict", + sources=["tests/common/*.c", "tests/protocol_dict/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_lfrfid", + sources=["tests/common/*.c", "tests/lfrfid/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_bit_lib", + sources=["tests/common/*.c", "tests/bit_lib/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_datetime", + sources=["tests/common/*.c", "tests/datetime/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_float_tools", + sources=["tests/common/*.c", "tests/float_tools/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_bt", + sources=["tests/common/*.c", "tests/bt/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_dialogs_file_browser_options", + sources=["tests/common/*.c", "tests/dialogs_file_browser_options/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_expansion", + sources=["tests/common/*.c", "tests/expansion/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_compress", + sources=["tests/common/*.c", "tests/compress/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) diff --git a/applications/debug/unit_tests/resources/unit_tests/compress/compressed.bin b/applications/debug/unit_tests/resources/unit_tests/compress/compressed.bin new file mode 100644 index 0000000000..95d729295a Binary files /dev/null and b/applications/debug/unit_tests/resources/unit_tests/compress/compressed.bin differ diff --git a/applications/debug/unit_tests/resources/unit_tests/compress/uncompressed.bin b/applications/debug/unit_tests/resources/unit_tests/compress/uncompressed.bin new file mode 100644 index 0000000000..c4d02a5d20 Binary files /dev/null and b/applications/debug/unit_tests/resources/unit_tests/compress/uncompressed.bin differ 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 0000000000..0a8db9ae97 --- /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 0000000000..d1af957dc3 --- /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 0000000000..35650b0e82 --- /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/debug/unit_tests/test_index.c b/applications/debug/unit_tests/test_index.c deleted file mode 100644 index 5d0282bd77..0000000000 --- a/applications/debug/unit_tests/test_index.c +++ /dev/null @@ -1,168 +0,0 @@ -#include -#include -#include -#include "minunit_vars.h" -#include -#include -#include - -#define TAG "UnitTests" - -int run_minunit_test_furi(void); -int run_minunit_test_furi_hal(void); -int run_minunit_test_furi_hal_crypto(void); -int run_minunit_test_furi_string(void); -int run_minunit_test_infrared(void); -int run_minunit_test_rpc(void); -int run_minunit_test_manifest(void); -int run_minunit_test_flipper_format(void); -int run_minunit_test_flipper_format_string(void); -int run_minunit_test_stream(void); -int run_minunit_test_storage(void); -int run_minunit_test_subghz(void); -int run_minunit_test_dirwalk(void); -int run_minunit_test_power(void); -int run_minunit_test_protocol_dict(void); -int run_minunit_test_lfrfid_protocols(void); -int run_minunit_test_nfc(void); -int run_minunit_test_bit_lib(void); -int run_minunit_test_datetime(void); -int run_minunit_test_float_tools(void); -int run_minunit_test_bt(void); -int run_minunit_test_dialogs_file_browser_options(void); -int run_minunit_test_expansion(void); - -typedef int (*UnitTestEntry)(void); - -typedef struct { - const char* name; - const UnitTestEntry entry; -} UnitTest; - -const UnitTest unit_tests[] = { - {.name = "furi", .entry = run_minunit_test_furi}, - {.name = "furi_hal", .entry = run_minunit_test_furi_hal}, - {.name = "furi_hal_crypto", .entry = run_minunit_test_furi_hal_crypto}, - {.name = "furi_string", .entry = run_minunit_test_furi_string}, - {.name = "storage", .entry = run_minunit_test_storage}, - {.name = "stream", .entry = run_minunit_test_stream}, - {.name = "dirwalk", .entry = run_minunit_test_dirwalk}, - {.name = "manifest", .entry = run_minunit_test_manifest}, - {.name = "flipper_format", .entry = run_minunit_test_flipper_format}, - {.name = "flipper_format_string", .entry = run_minunit_test_flipper_format_string}, - {.name = "rpc", .entry = run_minunit_test_rpc}, - {.name = "subghz", .entry = run_minunit_test_subghz}, - {.name = "infrared", .entry = run_minunit_test_infrared}, - {.name = "nfc", .entry = run_minunit_test_nfc}, - {.name = "power", .entry = run_minunit_test_power}, - {.name = "protocol_dict", .entry = run_minunit_test_protocol_dict}, - {.name = "lfrfid", .entry = run_minunit_test_lfrfid_protocols}, - {.name = "bit_lib", .entry = run_minunit_test_bit_lib}, - {.name = "datetime", .entry = run_minunit_test_datetime}, - {.name = "float_tools", .entry = run_minunit_test_float_tools}, - {.name = "bt", .entry = run_minunit_test_bt}, - {.name = "dialogs_file_browser_options", - .entry = run_minunit_test_dialogs_file_browser_options}, - {.name = "expansion", .entry = run_minunit_test_expansion}, -}; - -void minunit_print_progress(void) { - static const char progress[] = {'\\', '|', '/', '-'}; - static uint8_t progress_counter = 0; - static uint32_t last_tick = 0; - uint32_t current_tick = furi_get_tick(); - if(current_tick - last_tick > 20) { - last_tick = current_tick; - printf("[%c]\033[3D", progress[++progress_counter % COUNT_OF(progress)]); - fflush(stdout); - } -} - -void minunit_print_fail(const char* str) { - printf(_FURI_LOG_CLR_E "%s\r\n" _FURI_LOG_CLR_RESET, str); -} - -void minunit_printf_warning(const char* format, ...) { - FuriString* str = furi_string_alloc(); - va_list args; - va_start(args, format); - furi_string_vprintf(str, format, args); - va_end(args); - printf(_FURI_LOG_CLR_W "%s\r\n" _FURI_LOG_CLR_RESET, furi_string_get_cstr(str)); - furi_string_free(str); -} - -void unit_tests_cli(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); - UNUSED(args); - UNUSED(context); - minunit_run = 0; - minunit_assert = 0; - minunit_fail = 0; - minunit_status = 0; - - Loader* loader = furi_record_open(RECORD_LOADER); - NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); - - // TODO FL-3491: lock device while test running - if(loader_is_locked(loader)) { - printf("RPC: stop all applications to run tests\r\n"); - notification_message(notification, &sequence_blink_magenta_100); - } else { - notification_message_block(notification, &sequence_set_only_blue_255); - - uint32_t heap_before = memmgr_get_free_heap(); - uint32_t cycle_counter = furi_get_tick(); - - for(size_t i = 0; i < COUNT_OF(unit_tests); i++) { - if(cli_cmd_interrupt_received(cli)) { - break; - } - - if(furi_string_size(args)) { - if(furi_string_cmp_str(args, unit_tests[i].name) == 0) { - unit_tests[i].entry(); - } else { - printf("Skipping %s\r\n", unit_tests[i].name); - } - } else { - unit_tests[i].entry(); - } - } - - if(minunit_run != 0) { - printf("\r\nFailed tests: %u\r\n", minunit_fail); - - // Time report - cycle_counter = (furi_get_tick() - cycle_counter); - printf("Consumed: %lu ms\r\n", cycle_counter); - - // Wait for tested services and apps to deallocate memory - furi_delay_ms(200); - uint32_t heap_after = memmgr_get_free_heap(); - printf("Leaked: %ld\r\n", heap_before - heap_after); - - // Final Report - if(minunit_fail == 0) { - notification_message(notification, &sequence_success); - printf("Status: PASSED\r\n"); - } else { - notification_message(notification, &sequence_error); - printf("Status: FAILED\r\n"); - } - } - } - - furi_record_close(RECORD_NOTIFICATION); - furi_record_close(RECORD_LOADER); -} - -void unit_tests_on_system_start(void) { -#ifdef SRV_CLI - Cli* cli = furi_record_open(RECORD_CLI); - - // We need to launch apps from tests, so we cannot lock loader - cli_add_command(cli, "unit_tests", CliCommandFlagParallelSafe, unit_tests_cli, NULL); - furi_record_close(RECORD_CLI); -#endif -} diff --git a/applications/debug/unit_tests/test_runner.c b/applications/debug/unit_tests/test_runner.c new file mode 100644 index 0000000000..6af807086e --- /dev/null +++ b/applications/debug/unit_tests/test_runner.c @@ -0,0 +1,216 @@ +#include "test_runner.h" + +#include "tests/test_api.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +extern const ElfApiInterface* const unit_tests_api_interface; + +#define TAG "TestRunner" + +#define PLUGINS_PATH "/ext/apps_data/unit_tests/plugins" + +struct TestRunner { + Storage* storage; + Loader* loader; + NotificationApp* notification; + + // Temporary used things + Cli* cli; + FuriString* args; + + // ELF related stuff + CompositeApiResolver* composite_resolver; + + // Report data + int minunit_run; + int minunit_assert; + int minunit_fail; + int minunit_status; +}; + +TestRunner* test_runner_alloc(Cli* cli, FuriString* args) { + TestRunner* instance = malloc(sizeof(TestRunner)); + + instance->storage = furi_record_open(RECORD_STORAGE); + instance->loader = furi_record_open(RECORD_LOADER); + instance->notification = furi_record_open(RECORD_NOTIFICATION); + + instance->cli = cli; + instance->args = args; + + instance->composite_resolver = composite_api_resolver_alloc(); + composite_api_resolver_add(instance->composite_resolver, firmware_api_interface); + composite_api_resolver_add(instance->composite_resolver, unit_tests_api_interface); + + return instance; +} + +void test_runner_free(TestRunner* instance) { + furi_assert(instance); + + composite_api_resolver_free(instance->composite_resolver); + + furi_record_close(RECORD_NOTIFICATION); + instance->notification = NULL; + + furi_record_close(RECORD_LOADER); + instance->loader = NULL; + + furi_record_close(RECORD_STORAGE); + instance->storage = NULL; + + free(instance); +} + +static bool test_runner_run_plugin(TestRunner* instance, const char* path) { + furi_assert(instance); + + FURI_LOG_D(TAG, "Loading %s", path); + FlipperApplication* lib = flipper_application_alloc( + instance->storage, composite_api_resolver_get(instance->composite_resolver)); + + bool result = false; + instance->minunit_fail = -1; + do { + FlipperApplicationPreloadStatus preload_res = flipper_application_preload(lib, path); + + if(preload_res != FlipperApplicationPreloadStatusSuccess) { + FURI_LOG_E(TAG, "Failed to preload %s, %d", path, preload_res); + break; + } + + if(!flipper_application_is_plugin(lib)) { + FURI_LOG_E(TAG, "Not a plugin %s", path); + break; + } + + FlipperApplicationLoadStatus load_status = flipper_application_map_to_memory(lib); + if(load_status != FlipperApplicationLoadStatusSuccess) { + FURI_LOG_E(TAG, "Failed to load %s", path); + break; + } + + const FlipperAppPluginDescriptor* app_descriptor = + flipper_application_plugin_get_descriptor(lib); + + const TestApi* test = app_descriptor->entry_point; + + instance->minunit_fail = test->run(); + + instance->minunit_run += test->get_minunit_run(); + instance->minunit_assert += test->get_minunit_assert(); + instance->minunit_status += test->get_minunit_status(); + + result = (instance->minunit_fail == 0); + } while(false); + + flipper_application_free(lib); + + return result; +} + +static void test_runner_run_internal(TestRunner* instance) { + furi_assert(instance); + + char file_name_buffer[256]; + FuriString* file_name = furi_string_alloc(); + FuriString* file_basename = furi_string_alloc(); + File* directory = storage_file_alloc(instance->storage); + + do { + if(!storage_dir_open(directory, PLUGINS_PATH)) { + FURI_LOG_E(TAG, "Failed to open directory %s", PLUGINS_PATH); + break; + } + + while(true) { + if(cli_cmd_interrupt_received(instance->cli)) { + break; + } + + if(!storage_dir_read(directory, NULL, file_name_buffer, sizeof(file_name_buffer))) { + break; + } + + furi_string_set(file_name, file_name_buffer); + if(!furi_string_end_with_str(file_name, ".fal")) { + continue; + } + + path_concat(PLUGINS_PATH, file_name_buffer, file_name); + + path_extract_filename(file_name, file_basename, true); + const char* file_basename_cstr = furi_string_get_cstr(file_basename); + + bool result = true; + if(furi_string_size(instance->args)) { + if(furi_string_cmp_str(instance->args, file_basename_cstr) == 0) { + result = test_runner_run_plugin(instance, furi_string_get_cstr(file_name)); + } else { + printf("Skipping %s\r\n", file_basename_cstr); + } + } else { + result = test_runner_run_plugin(instance, furi_string_get_cstr(file_name)); + } + + if(!result) { + printf("Failed to execute test: %s\r\n", file_basename_cstr); + break; + } + } + } while(false); + + storage_dir_close(directory); + storage_file_free(directory); + furi_string_free(file_name); + furi_string_free(file_basename); +} + +void test_runner_run(TestRunner* instance) { + furi_assert(instance); + + // TODO FL-3491: lock device while test running + if(loader_is_locked(instance->loader)) { + printf("RPC: stop all applications to run tests\r\n"); + notification_message(instance->notification, &sequence_blink_magenta_100); + } else { + notification_message_block(instance->notification, &sequence_set_only_blue_255); + + uint32_t heap_before = memmgr_get_free_heap(); + uint32_t cycle_counter = furi_get_tick(); + + test_runner_run_internal(instance); + + if(instance->minunit_run != 0) { + printf("\r\nFailed tests: %d\r\n", instance->minunit_fail); + + // Time report + cycle_counter = (furi_get_tick() - cycle_counter); + printf("Consumed: %lu ms\r\n", cycle_counter); + + // Wait for tested services and apps to deallocate memory + furi_delay_ms(200); + uint32_t heap_after = memmgr_get_free_heap(); + printf("Leaked: %ld\r\n", heap_before - heap_after); + + // Final Report + if(instance->minunit_fail == 0) { + notification_message(instance->notification, &sequence_success); + printf("Status: PASSED\r\n"); + } else { + notification_message(instance->notification, &sequence_error); + printf("Status: FAILED\r\n"); + } + } + } +} diff --git a/applications/debug/unit_tests/test_runner.h b/applications/debug/unit_tests/test_runner.h new file mode 100644 index 0000000000..024799ebe5 --- /dev/null +++ b/applications/debug/unit_tests/test_runner.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +typedef struct TestRunner TestRunner; +typedef struct Cli Cli; + +TestRunner* test_runner_alloc(Cli* cli, FuriString* args); + +void test_runner_free(TestRunner* isntance); + +void test_runner_run(TestRunner* isntance); \ No newline at end of file diff --git a/applications/debug/unit_tests/bit_lib/bit_lib_test.c b/applications/debug/unit_tests/tests/bit_lib/bit_lib_test.c similarity index 99% rename from applications/debug/unit_tests/bit_lib/bit_lib_test.c rename to applications/debug/unit_tests/tests/bit_lib/bit_lib_test.c index 239a3b562a..d12aeb2aa1 100644 --- a/applications/debug/unit_tests/bit_lib/bit_lib_test.c +++ b/applications/debug/unit_tests/tests/bit_lib/bit_lib_test.c @@ -1,5 +1,5 @@ #include -#include "../minunit.h" +#include "../test.h" #include MU_TEST(test_bit_lib_increment_index) { @@ -737,4 +737,6 @@ MU_TEST_SUITE(test_bit_lib) { int run_minunit_test_bit_lib(void) { MU_RUN_SUITE(test_bit_lib); return MU_EXIT_CODE; -} \ No newline at end of file +} + +TEST_API_DEFINE(run_minunit_test_bit_lib) diff --git a/applications/debug/unit_tests/bt/bt_test.c b/applications/debug/unit_tests/tests/bt/bt_test.c similarity index 98% rename from applications/debug/unit_tests/bt/bt_test.c rename to applications/debug/unit_tests/tests/bt/bt_test.c index a09b9894ba..e7704381d0 100644 --- a/applications/debug/unit_tests/bt/bt_test.c +++ b/applications/debug/unit_tests/tests/bt/bt_test.c @@ -1,6 +1,6 @@ #include #include -#include "../minunit.h" +#include "../test.h" #include #include @@ -108,3 +108,5 @@ int run_minunit_test_bt(void) { MU_RUN_SUITE(test_bt); return MU_EXIT_CODE; } + +TEST_API_DEFINE(run_minunit_test_bt) diff --git a/applications/debug/unit_tests/tests/common/common.c b/applications/debug/unit_tests/tests/common/common.c new file mode 100644 index 0000000000..aef6aa1a4b --- /dev/null +++ b/applications/debug/unit_tests/tests/common/common.c @@ -0,0 +1,42 @@ +#include "../test.h" +#include "../minunit_vars.h" + +#include + +void minunit_print_progress(void) { + static const char progress[] = {'\\', '|', '/', '-'}; + static uint8_t progress_counter = 0; + static uint32_t last_tick = 0; + uint32_t current_tick = furi_get_tick(); + if(current_tick - last_tick > 20) { + last_tick = current_tick; + printf("[%c]\033[3D", progress[++progress_counter % COUNT_OF(progress)]); + fflush(stdout); + } +} + +void minunit_print_fail(const char* str) { + printf(_FURI_LOG_CLR_E "%s\r\n" _FURI_LOG_CLR_RESET, str); +} + +void minunit_printf_warning(const char* format, ...) { + FuriString* str = furi_string_alloc(); + va_list args; + va_start(args, format); + furi_string_vprintf(str, format, args); + va_end(args); + printf(_FURI_LOG_CLR_W "%s\r\n" _FURI_LOG_CLR_RESET, furi_string_get_cstr(str)); + furi_string_free(str); +} + +int get_minunit_run(void) { + return minunit_run; +} + +int get_minunit_assert(void) { + return minunit_assert; +} + +int get_minunit_status(void) { + return minunit_status; +} diff --git a/applications/debug/unit_tests/tests/compress/compress_test.c b/applications/debug/unit_tests/tests/compress/compress_test.c new file mode 100644 index 0000000000..8f61532971 --- /dev/null +++ b/applications/debug/unit_tests/tests/compress/compress_test.c @@ -0,0 +1,159 @@ +#include "../test.h" + +#include + +#include +#include +#include + +#include + +#include + +#define COMPRESS_UNIT_TESTS_PATH(path) EXT_PATH("unit_tests/compress/" path) + +static void compress_test_reference_comp_decomp() { + Storage* storage = furi_record_open(RECORD_STORAGE); + + File* compressed_file = storage_file_alloc(storage); + File* decompressed_file = storage_file_alloc(storage); + + mu_assert( + storage_file_open( + compressed_file, + COMPRESS_UNIT_TESTS_PATH("compressed.bin"), + FSAM_READ, + FSOM_OPEN_EXISTING), + "Failed to open compressed file"); + mu_assert( + storage_file_open( + decompressed_file, + COMPRESS_UNIT_TESTS_PATH("uncompressed.bin"), + FSAM_READ, + FSOM_OPEN_EXISTING), + "Failed to open decompressed file"); + + uint64_t compressed_ref_size = storage_file_size(compressed_file); + uint64_t decompressed_ref_size = storage_file_size(decompressed_file); + + mu_assert(compressed_ref_size > 0 && decompressed_ref_size > 0, "Invalid file sizes"); + + uint8_t* compressed_ref_buff = malloc(compressed_ref_size); + uint8_t* decompressed_ref_buff = malloc(decompressed_ref_size); + + mu_assert( + storage_file_read(compressed_file, compressed_ref_buff, compressed_ref_size) == + compressed_ref_size, + "Failed to read compressed file"); + + mu_assert( + storage_file_read(decompressed_file, decompressed_ref_buff, decompressed_ref_size) == + decompressed_ref_size, + "Failed to read decompressed file"); + + storage_file_free(compressed_file); + storage_file_free(decompressed_file); + furi_record_close(RECORD_STORAGE); + + uint8_t* temp_buffer = malloc(1024); + Compress* comp = compress_alloc(1024); + + size_t encoded_size = 0; + mu_assert( + compress_encode( + comp, decompressed_ref_buff, decompressed_ref_size, temp_buffer, 1024, &encoded_size), + "Compress failed"); + + mu_assert(encoded_size == compressed_ref_size, "Encoded size is not equal to reference size"); + + mu_assert( + memcmp(temp_buffer, compressed_ref_buff, compressed_ref_size) == 0, + "Encoded buffer is not equal to reference"); + + size_t decoded_size = 0; + mu_assert( + compress_decode( + comp, compressed_ref_buff, compressed_ref_size, temp_buffer, 1024, &decoded_size), + "Decompress failed"); + + mu_assert( + decoded_size == decompressed_ref_size, "Decoded size is not equal to reference size"); + + mu_assert( + memcmp(temp_buffer, decompressed_ref_buff, decompressed_ref_size) == 0, + "Decoded buffer is not equal to reference"); + + compress_free(comp); + + free(temp_buffer); + free(compressed_ref_buff); + free(decompressed_ref_buff); +} + +static void compress_test_random_comp_decomp() { + static const size_t src_buffer_size = 1024; + static const size_t encoded_buffer_size = 1024; + static const size_t small_buffer_size = src_buffer_size / 32; + + // We only fill half of the buffer with random data, so if anything goes wrong, there's no overflow + static const size_t src_data_size = src_buffer_size / 2; + + Compress* comp = compress_alloc(src_buffer_size); + uint8_t* src_buff = malloc(src_buffer_size); + uint8_t* encoded_buff = malloc(encoded_buffer_size); + uint8_t* decoded_buff = malloc(src_buffer_size); + uint8_t* small_buff = malloc(small_buffer_size); + + furi_hal_random_fill_buf(src_buff, src_data_size); + + size_t encoded_size = 0; + + mu_assert( + compress_encode( + comp, src_buff, src_data_size, encoded_buff, encoded_buffer_size, &encoded_size), + "Compress failed"); + + mu_assert(encoded_size > 0, "Encoded size is zero"); + + size_t small_enc_dec_size = 0; + mu_assert( + compress_encode( + comp, src_buff, src_data_size, small_buff, small_buffer_size, &small_enc_dec_size) == + false, + "Compress to small buffer failed"); + + size_t decoded_size = 0; + mu_assert( + compress_decode( + comp, encoded_buff, encoded_size, decoded_buff, src_buffer_size, &decoded_size), + "Decompress failed"); + mu_assert(decoded_size == src_data_size, "Decoded size is not equal to source size"); + + mu_assert( + memcmp(src_buff, decoded_buff, src_data_size) == 0, + "Decoded buffer is not equal to source"); + + mu_assert( + compress_decode( + comp, encoded_buff, encoded_size, small_buff, small_buffer_size, &small_enc_dec_size) == + false, + "Decompress to small buffer failed"); + + free(small_buff); + free(src_buff); + free(encoded_buff); + free(decoded_buff); + compress_free(comp); +} + +MU_TEST_SUITE(test_compress) { + MU_RUN_TEST(compress_test_random_comp_decomp); + MU_RUN_TEST(compress_test_reference_comp_decomp); +} + +int run_minunit_test_compress(void) { + MU_RUN_SUITE(test_compress); + return MU_EXIT_CODE; +} + +TEST_API_DEFINE(run_minunit_test_compress) diff --git a/applications/debug/unit_tests/datetimelib/datetimelib_test.c b/applications/debug/unit_tests/tests/datetime/datetimelib_test.c similarity index 98% rename from applications/debug/unit_tests/datetimelib/datetimelib_test.c rename to applications/debug/unit_tests/tests/datetime/datetimelib_test.c index c171a4413b..7d90f18de1 100644 --- a/applications/debug/unit_tests/datetimelib/datetimelib_test.c +++ b/applications/debug/unit_tests/tests/datetime/datetimelib_test.c @@ -1,5 +1,5 @@ #include -#include "../minunit.h" +#include "../test.h" #include @@ -188,4 +188,6 @@ int run_minunit_test_datetime(void) { MU_RUN_SUITE(test_datetime_validate_datetime); return MU_EXIT_CODE; -} \ No newline at end of file +} + +TEST_API_DEFINE(run_minunit_test_datetime) diff --git a/applications/debug/unit_tests/dialogs/dialogs_file_browser_options.c b/applications/debug/unit_tests/tests/dialogs_file_browser_options/dialogs_file_browser_options.c similarity index 93% rename from applications/debug/unit_tests/dialogs/dialogs_file_browser_options.c rename to applications/debug/unit_tests/tests/dialogs_file_browser_options/dialogs_file_browser_options.c index 657b933dfc..e42e00f9a7 100644 --- a/applications/debug/unit_tests/dialogs/dialogs_file_browser_options.c +++ b/applications/debug/unit_tests/tests/dialogs_file_browser_options/dialogs_file_browser_options.c @@ -1,6 +1,6 @@ #include -#include "../minunit.h" +#include "../test.h" MU_TEST(test_dialog_file_browser_set_basic_options_should_init_all_fields) { mu_assert( @@ -30,3 +30,5 @@ int run_minunit_test_dialogs_file_browser_options(void) { return MU_EXIT_CODE; } + +TEST_API_DEFINE(run_minunit_test_dialogs_file_browser_options) diff --git a/applications/debug/unit_tests/storage/dirwalk_test.c b/applications/debug/unit_tests/tests/dirwalk/dirwalk_test.c similarity index 99% rename from applications/debug/unit_tests/storage/dirwalk_test.c rename to applications/debug/unit_tests/tests/dirwalk/dirwalk_test.c index 415ec7dd43..c2474e7554 100644 --- a/applications/debug/unit_tests/storage/dirwalk_test.c +++ b/applications/debug/unit_tests/tests/dirwalk/dirwalk_test.c @@ -1,4 +1,4 @@ -#include "../minunit.h" +#include "../test.h" #include #include #include @@ -269,4 +269,6 @@ MU_TEST_SUITE(test_dirwalk_suite) { int run_minunit_test_dirwalk(void) { MU_RUN_SUITE(test_dirwalk_suite); return MU_EXIT_CODE; -} \ No newline at end of file +} + +TEST_API_DEFINE(run_minunit_test_dirwalk) diff --git a/applications/debug/unit_tests/expansion/expansion_test.c b/applications/debug/unit_tests/tests/expansion/expansion_test.c similarity index 99% rename from applications/debug/unit_tests/expansion/expansion_test.c rename to applications/debug/unit_tests/tests/expansion/expansion_test.c index c6a143ed34..79d9b0a57c 100644 --- a/applications/debug/unit_tests/expansion/expansion_test.c +++ b/applications/debug/unit_tests/tests/expansion/expansion_test.c @@ -1,4 +1,4 @@ -#include "../minunit.h" +#include "../test.h" #include #include @@ -198,3 +198,5 @@ int run_minunit_test_expansion(void) { MU_RUN_SUITE(test_expansion_suite); return MU_EXIT_CODE; } + +TEST_API_DEFINE(run_minunit_test_expansion) diff --git a/applications/debug/unit_tests/flipper_format/flipper_format_test.c b/applications/debug/unit_tests/tests/flipper_format/flipper_format_test.c similarity index 99% rename from applications/debug/unit_tests/flipper_format/flipper_format_test.c rename to applications/debug/unit_tests/tests/flipper_format/flipper_format_test.c index 471055fb0c..1a2eaf222d 100644 --- a/applications/debug/unit_tests/flipper_format/flipper_format_test.c +++ b/applications/debug/unit_tests/tests/flipper_format/flipper_format_test.c @@ -2,7 +2,7 @@ #include #include #include -#include "../minunit.h" +#include "../test.h" #define TEST_DIR TEST_DIR_NAME "/" #define TEST_DIR_NAME EXT_PATH("unit_tests_tmp") @@ -549,3 +549,5 @@ int run_minunit_test_flipper_format(void) { MU_RUN_SUITE(flipper_format); return MU_EXIT_CODE; } + +TEST_API_DEFINE(run_minunit_test_flipper_format) diff --git a/applications/debug/unit_tests/flipper_format/flipper_format_string_test.c b/applications/debug/unit_tests/tests/flipper_format_string/flipper_format_string_test.c similarity index 99% rename from applications/debug/unit_tests/flipper_format/flipper_format_string_test.c rename to applications/debug/unit_tests/tests/flipper_format_string/flipper_format_string_test.c index 99bb5dda59..821b1ba22a 100644 --- a/applications/debug/unit_tests/flipper_format/flipper_format_string_test.c +++ b/applications/debug/unit_tests/tests/flipper_format_string/flipper_format_string_test.c @@ -3,7 +3,7 @@ #include #include #include -#include "../minunit.h" +#include "../test.h" static const char* test_filetype = "Flipper Format test"; static const uint32_t test_version = 666; @@ -335,3 +335,5 @@ int run_minunit_test_flipper_format_string(void) { MU_RUN_SUITE(flipper_format_string_suite); return MU_EXIT_CODE; } + +TEST_API_DEFINE(run_minunit_test_flipper_format_string) diff --git a/applications/debug/unit_tests/float_tools/float_tools_test.c b/applications/debug/unit_tests/tests/float_tools/float_tools_test.c similarity index 97% rename from applications/debug/unit_tests/float_tools/float_tools_test.c rename to applications/debug/unit_tests/tests/float_tools/float_tools_test.c index 91ac469378..d016ca4a2d 100644 --- a/applications/debug/unit_tests/float_tools/float_tools_test.c +++ b/applications/debug/unit_tests/tests/float_tools/float_tools_test.c @@ -1,7 +1,7 @@ #include #include -#include "../minunit.h" +#include "../test.h" MU_TEST(float_tools_equal_test) { mu_check(float_is_equal(FLT_MAX, FLT_MAX)); @@ -58,3 +58,5 @@ int run_minunit_test_float_tools(void) { MU_RUN_SUITE(float_tools_suite); return MU_EXIT_CODE; } + +TEST_API_DEFINE(run_minunit_test_float_tools) diff --git a/applications/debug/unit_tests/furi/furi_memmgr_test.c b/applications/debug/unit_tests/tests/furi/furi_memmgr_test.c similarity index 97% rename from applications/debug/unit_tests/furi/furi_memmgr_test.c rename to applications/debug/unit_tests/tests/furi/furi_memmgr_test.c index 01e2c17f66..837fd60859 100644 --- a/applications/debug/unit_tests/furi/furi_memmgr_test.c +++ b/applications/debug/unit_tests/tests/furi/furi_memmgr_test.c @@ -1,4 +1,4 @@ -#include "../minunit.h" +#include "../test.h" #include #include #include diff --git a/applications/debug/unit_tests/furi/furi_pubsub_test.c b/applications/debug/unit_tests/tests/furi/furi_pubsub_test.c similarity index 98% rename from applications/debug/unit_tests/furi/furi_pubsub_test.c rename to applications/debug/unit_tests/tests/furi/furi_pubsub_test.c index 8925ff42ec..fdf15e6f58 100644 --- a/applications/debug/unit_tests/furi/furi_pubsub_test.c +++ b/applications/debug/unit_tests/tests/furi/furi_pubsub_test.c @@ -1,7 +1,7 @@ #include #include #include -#include "../minunit.h" +#include "../test.h" const uint32_t context_value = 0xdeadbeef; const uint32_t notify_value_0 = 0x12345678; diff --git a/applications/debug/unit_tests/furi/furi_record_test.c b/applications/debug/unit_tests/tests/furi/furi_record_test.c similarity index 96% rename from applications/debug/unit_tests/furi/furi_record_test.c rename to applications/debug/unit_tests/tests/furi/furi_record_test.c index 10a5a83933..6a2b5a3d26 100644 --- a/applications/debug/unit_tests/furi/furi_record_test.c +++ b/applications/debug/unit_tests/tests/furi/furi_record_test.c @@ -1,7 +1,7 @@ #include #include #include -#include "../minunit.h" +#include "../test.h" #define TEST_RECORD_NAME "test/holding" diff --git a/applications/debug/unit_tests/furi/furi_test.c b/applications/debug/unit_tests/tests/furi/furi_test.c similarity index 94% rename from applications/debug/unit_tests/furi/furi_test.c rename to applications/debug/unit_tests/tests/furi/furi_test.c index e287f9927f..eab9a917d8 100644 --- a/applications/debug/unit_tests/furi/furi_test.c +++ b/applications/debug/unit_tests/tests/furi/furi_test.c @@ -1,7 +1,7 @@ #include #include #include -#include "../minunit.h" +#include "../test.h" // v2 tests void test_furi_create_open(void); @@ -55,3 +55,5 @@ int run_minunit_test_furi(void) { return MU_EXIT_CODE; } + +TEST_API_DEFINE(run_minunit_test_furi) diff --git a/applications/debug/unit_tests/furi_hal/furi_hal_tests.c b/applications/debug/unit_tests/tests/furi_hal/furi_hal_tests.c similarity index 99% rename from applications/debug/unit_tests/furi_hal/furi_hal_tests.c rename to applications/debug/unit_tests/tests/furi_hal/furi_hal_tests.c index 0c5cec8a61..985daa03d7 100644 --- a/applications/debug/unit_tests/furi_hal/furi_hal_tests.c +++ b/applications/debug/unit_tests/tests/furi_hal/furi_hal_tests.c @@ -4,7 +4,7 @@ #include #include #include -#include "../minunit.h" +#include "../test.h" #include #define DATA_SIZE 4 @@ -232,3 +232,5 @@ int run_minunit_test_furi_hal(void) { MU_RUN_SUITE(furi_hal_i2c_ext_suite); return MU_EXIT_CODE; } + +TEST_API_DEFINE(run_minunit_test_furi_hal) diff --git a/applications/debug/unit_tests/furi_hal/furi_hal_crypto_tests.c b/applications/debug/unit_tests/tests/furi_hal_crypto/furi_hal_crypto_tests.c similarity index 99% rename from applications/debug/unit_tests/furi_hal/furi_hal_crypto_tests.c rename to applications/debug/unit_tests/tests/furi_hal_crypto/furi_hal_crypto_tests.c index c2bd6c5f81..0a264a18aa 100644 --- a/applications/debug/unit_tests/furi_hal/furi_hal_crypto_tests.c +++ b/applications/debug/unit_tests/tests/furi_hal_crypto/furi_hal_crypto_tests.c @@ -1,7 +1,7 @@ #include #include #include -#include "../minunit.h" +#include "../test.h" static const uint8_t key_ctr_1[32] = { 0x77, 0x6B, 0xEF, 0xF2, 0x85, 0x1D, 0xB0, 0x6F, 0x4C, 0x8A, 0x05, 0x42, 0xC8, 0x69, 0x6F, 0x6C, @@ -600,3 +600,5 @@ int run_minunit_test_furi_hal_crypto(void) { MU_RUN_SUITE(furi_hal_crypto_gcm_test); return MU_EXIT_CODE; } + +TEST_API_DEFINE(run_minunit_test_furi_hal_crypto) diff --git a/applications/debug/unit_tests/furi/furi_string_test.c b/applications/debug/unit_tests/tests/furi_string/furi_string_test.c similarity index 99% rename from applications/debug/unit_tests/furi/furi_string_test.c rename to applications/debug/unit_tests/tests/furi_string/furi_string_test.c index 853076b67b..949d8c0488 100644 --- a/applications/debug/unit_tests/furi/furi_string_test.c +++ b/applications/debug/unit_tests/tests/furi_string/furi_string_test.c @@ -1,5 +1,5 @@ #include -#include "../minunit.h" +#include "../test.h" static void test_setup(void) { } @@ -466,4 +466,6 @@ int run_minunit_test_furi_string(void) { MU_RUN_SUITE(test_suite); return MU_EXIT_CODE; -} \ No newline at end of file +} + +TEST_API_DEFINE(run_minunit_test_furi_string) diff --git a/applications/debug/unit_tests/infrared/infrared_test.c b/applications/debug/unit_tests/tests/infrared/infrared_test.c similarity index 99% rename from applications/debug/unit_tests/infrared/infrared_test.c rename to applications/debug/unit_tests/tests/infrared/infrared_test.c index 4442fedb30..922577aa86 100644 --- a/applications/debug/unit_tests/infrared/infrared_test.c +++ b/applications/debug/unit_tests/tests/infrared/infrared_test.c @@ -2,7 +2,7 @@ #include #include #include -#include "../minunit.h" +#include "../test.h" #define IR_TEST_FILES_DIR EXT_PATH("unit_tests/infrared/") #define IR_TEST_FILE_PREFIX "test_" @@ -549,3 +549,5 @@ int run_minunit_test_infrared(void) { MU_RUN_SUITE(infrared_test); return MU_EXIT_CODE; } + +TEST_API_DEFINE(run_minunit_test_infrared) diff --git a/applications/debug/unit_tests/lfrfid/lfrfid_protocols.c b/applications/debug/unit_tests/tests/lfrfid/lfrfid_protocols.c similarity index 99% rename from applications/debug/unit_tests/lfrfid/lfrfid_protocols.c rename to applications/debug/unit_tests/tests/lfrfid/lfrfid_protocols.c index 6a7dbab9cb..fc380ecc49 100644 --- a/applications/debug/unit_tests/lfrfid/lfrfid_protocols.c +++ b/applications/debug/unit_tests/tests/lfrfid/lfrfid_protocols.c @@ -1,5 +1,5 @@ #include -#include "../minunit.h" +#include "../test.h" #include #include #include @@ -550,4 +550,6 @@ MU_TEST_SUITE(test_lfrfid_protocols_suite) { int run_minunit_test_lfrfid_protocols(void) { MU_RUN_SUITE(test_lfrfid_protocols_suite); return MU_EXIT_CODE; -} \ No newline at end of file +} + +TEST_API_DEFINE(run_minunit_test_lfrfid_protocols) diff --git a/applications/debug/unit_tests/manifest/manifest.c b/applications/debug/unit_tests/tests/manifest/manifest.c similarity index 97% rename from applications/debug/unit_tests/manifest/manifest.c rename to applications/debug/unit_tests/tests/manifest/manifest.c index e8ac93d7c0..fcbab0ae8a 100644 --- a/applications/debug/unit_tests/manifest/manifest.c +++ b/applications/debug/unit_tests/tests/manifest/manifest.c @@ -1,5 +1,5 @@ #include -#include "../minunit.h" +#include "../test.h" #include #define TAG "Manifest" @@ -72,4 +72,6 @@ MU_TEST_SUITE(manifest_suite) { int run_minunit_test_manifest(void) { MU_RUN_SUITE(manifest_suite); return MU_EXIT_CODE; -} \ No newline at end of file +} + +TEST_API_DEFINE(run_minunit_test_manifest) diff --git a/applications/debug/unit_tests/minunit.h b/applications/debug/unit_tests/tests/minunit.h similarity index 100% rename from applications/debug/unit_tests/minunit.h rename to applications/debug/unit_tests/tests/minunit.h diff --git a/applications/debug/unit_tests/minunit_vars.h b/applications/debug/unit_tests/tests/minunit_vars.h similarity index 100% rename from applications/debug/unit_tests/minunit_vars.h rename to applications/debug/unit_tests/tests/minunit_vars.h diff --git a/applications/debug/unit_tests/minunit_vars_ex.h b/applications/debug/unit_tests/tests/minunit_vars_ex.h similarity index 100% rename from applications/debug/unit_tests/minunit_vars_ex.h rename to applications/debug/unit_tests/tests/minunit_vars_ex.h diff --git a/applications/debug/unit_tests/nfc/nfc_test.c b/applications/debug/unit_tests/tests/nfc/nfc_test.c similarity index 80% rename from applications/debug/unit_tests/nfc/nfc_test.c rename to applications/debug/unit_tests/tests/nfc/nfc_test.c index c6304d53ca..4de364a09d 100644 --- a/applications/debug/unit_tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/tests/nfc/nfc_test.c @@ -13,12 +13,18 @@ #include #include #include +#include +#include +#include +#include +#include + #include #include #include -#include "../minunit.h" +#include "../test.h" #define TAG "NfcTest" @@ -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(); } @@ -675,3 +820,5 @@ int run_minunit_test_nfc(void) { MU_RUN_SUITE(nfc); return MU_EXIT_CODE; } + +TEST_API_DEFINE(run_minunit_test_nfc) diff --git a/applications/debug/unit_tests/power/power_test.c b/applications/debug/unit_tests/tests/power/power_test.c similarity index 98% rename from applications/debug/unit_tests/power/power_test.c rename to applications/debug/unit_tests/tests/power/power_test.c index 03e0ad269f..d121acba64 100644 --- a/applications/debug/unit_tests/power/power_test.c +++ b/applications/debug/unit_tests/tests/power/power_test.c @@ -1,6 +1,6 @@ #include #include -#include "../minunit.h" +#include "../test.h" static void power_test_deinit(void) { // Try to reset to default charge voltage limit @@ -67,3 +67,5 @@ int run_minunit_test_power(void) { MU_RUN_SUITE(test_power_suite); return MU_EXIT_CODE; } + +TEST_API_DEFINE(run_minunit_test_power) diff --git a/applications/debug/unit_tests/protocol_dict/protocol_dict_test.c b/applications/debug/unit_tests/tests/protocol_dict/protocol_dict_test.c similarity index 98% rename from applications/debug/unit_tests/protocol_dict/protocol_dict_test.c rename to applications/debug/unit_tests/tests/protocol_dict/protocol_dict_test.c index 6ecaa1a52f..9ced106abd 100644 --- a/applications/debug/unit_tests/protocol_dict/protocol_dict_test.c +++ b/applications/debug/unit_tests/tests/protocol_dict/protocol_dict_test.c @@ -1,5 +1,5 @@ #include -#include "../minunit.h" +#include "../test.h" #include typedef enum { @@ -219,4 +219,6 @@ MU_TEST_SUITE(test_protocol_dict_suite) { int run_minunit_test_protocol_dict(void) { MU_RUN_SUITE(test_protocol_dict_suite); return MU_EXIT_CODE; -} \ No newline at end of file +} + +TEST_API_DEFINE(run_minunit_test_protocol_dict) diff --git a/applications/debug/unit_tests/rpc/rpc_test.c b/applications/debug/unit_tests/tests/rpc/rpc_test.c similarity index 99% rename from applications/debug/unit_tests/rpc/rpc_test.c rename to applications/debug/unit_tests/tests/rpc/rpc_test.c index cd692d69de..be7b331f96 100644 --- a/applications/debug/unit_tests/rpc/rpc_test.c +++ b/applications/debug/unit_tests/tests/rpc/rpc_test.c @@ -17,7 +17,7 @@ #include #include -#include "../minunit.h" +#include "../test.h" #include #include @@ -1864,3 +1864,5 @@ int32_t delay_test_app(void* p) { return 0; } + +TEST_API_DEFINE(run_minunit_test_rpc) diff --git a/applications/debug/unit_tests/storage/storage_test.c b/applications/debug/unit_tests/tests/storage/storage_test.c similarity index 99% rename from applications/debug/unit_tests/storage/storage_test.c rename to applications/debug/unit_tests/tests/storage/storage_test.c index e4361f06c3..1d887ced06 100644 --- a/applications/debug/unit_tests/storage/storage_test.c +++ b/applications/debug/unit_tests/tests/storage/storage_test.c @@ -1,4 +1,4 @@ -#include "../minunit.h" +#include "../test.h" #include #include @@ -712,3 +712,5 @@ int run_minunit_test_storage(void) { MU_RUN_SUITE(test_md5_calc_suite); return MU_EXIT_CODE; } + +TEST_API_DEFINE(run_minunit_test_storage) diff --git a/applications/debug/unit_tests/stream/stream_test.c b/applications/debug/unit_tests/tests/stream/stream_test.c similarity index 99% rename from applications/debug/unit_tests/stream/stream_test.c rename to applications/debug/unit_tests/tests/stream/stream_test.c index 3e31773b4f..a9c22c7232 100644 --- a/applications/debug/unit_tests/stream/stream_test.c +++ b/applications/debug/unit_tests/tests/stream/stream_test.c @@ -4,7 +4,7 @@ #include #include #include -#include "../minunit.h" +#include "../test.h" static const char* stream_test_data = "I write differently from what I speak, " "I speak differently from what I think, " @@ -530,3 +530,5 @@ int run_minunit_test_stream(void) { MU_RUN_SUITE(stream_suite); return MU_EXIT_CODE; } + +TEST_API_DEFINE(run_minunit_test_stream) diff --git a/applications/debug/unit_tests/subghz/subghz_test.c b/applications/debug/unit_tests/tests/subghz/subghz_test.c similarity index 99% rename from applications/debug/unit_tests/subghz/subghz_test.c rename to applications/debug/unit_tests/tests/subghz/subghz_test.c index adb8b4965f..a1bdeca422 100644 --- a/applications/debug/unit_tests/subghz/subghz_test.c +++ b/applications/debug/unit_tests/tests/subghz/subghz_test.c @@ -1,6 +1,6 @@ #include #include -#include "../minunit.h" +#include "../test.h" #include #include #include @@ -906,3 +906,5 @@ int run_minunit_test_subghz(void) { MU_RUN_SUITE(subghz); return MU_EXIT_CODE; } + +TEST_API_DEFINE(run_minunit_test_subghz) diff --git a/applications/debug/unit_tests/tests/test.h b/applications/debug/unit_tests/tests/test.h new file mode 100644 index 0000000000..bd7acc00e7 --- /dev/null +++ b/applications/debug/unit_tests/tests/test.h @@ -0,0 +1,12 @@ +#pragma once + +// Framework +#include "minunit.h" + +#include "test_api.h" + +int get_minunit_run(void); + +int get_minunit_assert(void); + +int get_minunit_status(void); diff --git a/applications/debug/unit_tests/tests/test_api.h b/applications/debug/unit_tests/tests/test_api.h new file mode 100644 index 0000000000..55b9f7885b --- /dev/null +++ b/applications/debug/unit_tests/tests/test_api.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#define APPID "UnitTest" +#define API_VERSION (0u) + +typedef struct { + int (*run)(void); + int (*get_minunit_run)(void); + int (*get_minunit_assert)(void); + int (*get_minunit_status)(void); +} TestApi; + +#define TEST_API_DEFINE(entrypoint) \ + const TestApi test_api = { \ + .run = entrypoint, \ + .get_minunit_run = get_minunit_run, \ + .get_minunit_assert = get_minunit_assert, \ + .get_minunit_status = get_minunit_status, \ + }; \ + const FlipperAppPluginDescriptor app_descriptor = { \ + .appid = APPID, \ + .ep_api_version = API_VERSION, \ + .entry_point = &test_api, \ + }; \ + const FlipperAppPluginDescriptor* get_api(void) { \ + return &app_descriptor; \ + } diff --git a/applications/debug/unit_tests/varint/varint_test.c b/applications/debug/unit_tests/tests/varint/varint_test.c similarity index 97% rename from applications/debug/unit_tests/varint/varint_test.c rename to applications/debug/unit_tests/tests/varint/varint_test.c index ac444013d3..3fdf5115ab 100644 --- a/applications/debug/unit_tests/varint/varint_test.c +++ b/applications/debug/unit_tests/tests/varint/varint_test.c @@ -1,6 +1,8 @@ #include #include -#include "../minunit.h" + +#include "../test.h" + #include #include @@ -85,4 +87,6 @@ MU_TEST_SUITE(test_varint_suite) { int run_minunit_test_varint(void) { MU_RUN_SUITE(test_varint_suite); return MU_EXIT_CODE; -} \ No newline at end of file +} + +TEST_API_DEFINE(run_minunit_test_varint) diff --git a/applications/debug/unit_tests/unit_test_api_table.cpp b/applications/debug/unit_tests/unit_test_api_table.cpp new file mode 100644 index 0000000000..8205d43722 --- /dev/null +++ b/applications/debug/unit_tests/unit_test_api_table.cpp @@ -0,0 +1,19 @@ +#include +#include + +#include "unit_test_api_table_i.h" + +static_assert(!has_hash_collisions(unit_tests_api_table), "Detected API method hash collision!"); + +constexpr HashtableApiInterface unit_tests_hashtable_api_interface{ + { + .api_version_major = 0, + .api_version_minor = 0, + .resolver_callback = &elf_resolve_from_hashtable, + }, + unit_tests_api_table.cbegin(), + unit_tests_api_table.cend(), +}; + +extern "C" const ElfApiInterface* const unit_tests_api_interface = + &unit_tests_hashtable_api_interface; diff --git a/applications/debug/unit_tests/unit_test_api_table_i.h b/applications/debug/unit_tests/unit_test_api_table_i.h new file mode 100644 index 0000000000..8c2fa4687f --- /dev/null +++ b/applications/debug/unit_tests/unit_test_api_table_i.h @@ -0,0 +1,29 @@ +#include +#include +#include +#include +#include + +#include +#include + +static constexpr auto unit_tests_api_table = sort(create_array_t( + API_METHOD(resource_manifest_reader_alloc, ResourceManifestReader*, (Storage*)), + API_METHOD(resource_manifest_reader_free, void, (ResourceManifestReader*)), + API_METHOD(resource_manifest_reader_open, bool, (ResourceManifestReader*, const char* filename)), + API_METHOD(resource_manifest_reader_next, ResourceManifestEntry*, (ResourceManifestReader*)), + API_METHOD(resource_manifest_reader_previous, ResourceManifestEntry*, (ResourceManifestReader*)), + API_METHOD(slix_process_iso15693_3_error, SlixError, (Iso15693_3Error)), + API_METHOD(iso15693_3_poller_get_data, const Iso15693_3Data*, (Iso15693_3Poller*)), + API_METHOD(rpc_system_storage_get_error, PB_CommandStatus, (FS_Error)), + API_METHOD(xQueueSemaphoreTake, BaseType_t, (QueueHandle_t, TickType_t)), + API_METHOD(vQueueDelete, void, (QueueHandle_t)), + API_METHOD( + xQueueGenericCreate, + QueueHandle_t, + (const UBaseType_t, const UBaseType_t, const uint8_t)), + API_METHOD( + xQueueGenericSend, + BaseType_t, + (QueueHandle_t, const void* const, TickType_t, const BaseType_t)), + API_VARIABLE(PB_Main_msg, PB_Main_msg_t))); diff --git a/applications/debug/unit_tests/unit_tests.c b/applications/debug/unit_tests/unit_tests.c new file mode 100644 index 0000000000..237cb9080e --- /dev/null +++ b/applications/debug/unit_tests/unit_tests.c @@ -0,0 +1,21 @@ +#include +#include + +#include "test_runner.h" + +void unit_tests_cli(Cli* cli, FuriString* args, void* context) { + UNUSED(cli); + UNUSED(context); + + TestRunner* test_runner = test_runner_alloc(cli, args); + test_runner_run(test_runner); + test_runner_free(test_runner); +} + +void unit_tests_on_system_start(void) { +#ifdef SRV_CLI + Cli* cli = furi_record_open(RECORD_CLI); + cli_add_command(cli, "unit_tests", CliCommandFlagParallelSafe, unit_tests_cli, NULL); + furi_record_close(RECORD_CLI); +#endif +} diff --git a/applications/main/archive/archive.c b/applications/main/archive/archive.c index 7f01b2f750..2345d3f7d4 100644 --- a/applications/main/archive/archive.c +++ b/applications/main/archive/archive.c @@ -21,14 +21,14 @@ static void archive_tick_event_callback(void* context) { ArchiveApp* archive_alloc(void) { ArchiveApp* archive = malloc(sizeof(ArchiveApp)); + archive->gui = furi_record_open(RECORD_GUI); + archive->loader = furi_record_open(RECORD_LOADER); archive->fav_move_str = furi_string_alloc(); archive->dst_path = furi_string_alloc(); archive->scene_manager = scene_manager_alloc(&archive_scene_handlers, archive); archive->view_dispatcher = view_dispatcher_alloc(); - archive->gui = furi_record_open(RECORD_GUI); - ViewDispatcher* view_dispatcher = archive->view_dispatcher; view_dispatcher_enable_queue(view_dispatcher); view_dispatcher_set_event_callback_context(view_dispatcher, archive); @@ -88,6 +88,8 @@ void archive_free(ArchiveApp* archive) { furi_record_close(RECORD_DIALOGS); archive->dialogs = NULL; + furi_record_close(RECORD_LOADER); + archive->loader = NULL; furi_record_close(RECORD_GUI); archive->gui = NULL; diff --git a/applications/main/archive/archive_i.h b/applications/main/archive/archive_i.h index 7d612687cd..3ae549ca69 100644 --- a/applications/main/archive/archive_i.h +++ b/applications/main/archive/archive_i.h @@ -26,6 +26,7 @@ typedef enum { struct ArchiveApp { Gui* gui; + Loader* loader; ViewDispatcher* view_dispatcher; ViewStack* view_stack; SceneManager* scene_manager; diff --git a/applications/main/archive/scenes/archive_scene_browser.c b/applications/main/archive/scenes/archive_scene_browser.c index ff96d4b13a..d2750a2713 100644 --- a/applications/main/archive/scenes/archive_scene_browser.c +++ b/applications/main/archive/scenes/archive_scene_browser.c @@ -88,10 +88,8 @@ void archive_scene_browser_on_enter(void* context) { archive_update_focus(browser, archive->text_store); view_dispatcher_switch_to_view(archive->view_dispatcher, ArchiveViewBrowser); - Loader* loader = furi_record_open(RECORD_LOADER); - archive->loader_stop_subscription = - furi_pubsub_subscribe(loader_get_pubsub(loader), archive_loader_callback, archive); - furi_record_close(RECORD_LOADER); + archive->loader_stop_subscription = furi_pubsub_subscribe( + loader_get_pubsub(archive->loader), archive_loader_callback, archive); uint32_t state = scene_manager_get_scene_state(archive->scene_manager, ArchiveAppSceneBrowser); @@ -302,10 +300,11 @@ bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) { if(!archive_is_home(browser)) { archive_leave_dir(browser); } else { - Loader* loader = furi_record_open(RECORD_LOADER); - furi_pubsub_unsubscribe( - loader_get_pubsub(loader), archive->loader_stop_subscription); - furi_record_close(RECORD_LOADER); + if(archive->loader_stop_subscription) { + furi_pubsub_unsubscribe( + loader_get_pubsub(archive->loader), archive->loader_stop_subscription); + archive->loader_stop_subscription = NULL; + } view_dispatcher_stop(archive->view_dispatcher); } @@ -321,8 +320,9 @@ bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) { void archive_scene_browser_on_exit(void* context) { ArchiveApp* archive = (ArchiveApp*)context; - - Loader* loader = furi_record_open(RECORD_LOADER); - furi_pubsub_unsubscribe(loader_get_pubsub(loader), archive->loader_stop_subscription); - furi_record_close(RECORD_LOADER); + if(archive->loader_stop_subscription) { + furi_pubsub_unsubscribe( + loader_get_pubsub(archive->loader), archive->loader_stop_subscription); + archive->loader_stop_subscription = NULL; + } } diff --git a/applications/main/infrared/resources/infrared/assets/ac.ir b/applications/main/infrared/resources/infrared/assets/ac.ir index 38db599b23..a007b12b7e 100644 --- a/applications/main/infrared/resources/infrared/assets/ac.ir +++ b/applications/main/infrared/resources/infrared/assets/ac.ir @@ -1021,3 +1021,35 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 3302 1640 404 423 407 420 410 1212 437 390 440 1234 405 395 435 392 438 415 415 1207 432 1242 407 420 410 391 439 414 405 1243 406 1241 408 392 438 415 415 386 433 393 437 390 440 414 405 396 434 419 411 389 441 412 407 420 410 390 440 387 432 1242 407 393 437 390 440 414 405 395 435 392 438 389 430 396 434 1240 409 417 413 414 405 395 435 419 411 1237 412 389 430 396 434 393 437 416 414 387 432 394 436 1212 437 389 441 1234 405 1217 432 1241 408 1213 436 1212 437 1210 439 +# +# Model: Toshiba RAS-2518D +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4349 4437 549 1615 551 1614 551 1614 551 1617 549 531 550 530 551 1615 550 531 550 532 549 530 551 531 550 530 551 1615 550 1614 551 531 550 1615 551 529 552 531 550 530 551 533 548 530 551 530 551 1616 549 1615 550 1616 550 1615 550 1614 551 1616 550 1615 551 1615 550 531 550 531 550 530 551 529 552 530 551 530 551 529 552 530 551 531 550 1615 550 532 549 1615 550 1616 550 531 550 531 550 530 551 530 551 529 552 532 549 530 551 530 551 531 550 529 552 531 550 1615 551 530 551 530 551 530 551 531 550 530 551 531 550 530 551 531 550 531 550 531 550 1616 550 1618 547 532 549 529 552 530 551 1615 551 1615 550 5379 4350 4436 550 1616 549 1615 551 1614 552 1615 550 529 552 530 551 1614 552 530 551 529 552 531 550 531 550 531 550 1614 552 1614 551 530 551 1615 550 530 551 530 551 530 551 530 551 531 550 532 549 1616 549 1615 551 1614 552 1615 550 1614 551 1616 550 1614 552 1615 550 529 552 530 551 530 551 530 551 531 550 531 550 530 551 530 551 531 550 1615 550 530 551 1615 550 1615 551 530 551 530 551 530 551 530 551 530 551 530 551 529 552 530 551 531 550 532 549 530 551 1615 551 531 550 530 551 530 551 530 551 530 551 531 550 531 550 531 550 530 551 531 550 1615 551 1615 551 532 549 531 550 531 550 1616 549 1614 552 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4350 4438 549 1615 551 1614 552 1616 549 1616 550 530 551 531 550 1615 551 530 551 529 552 531 550 530 551 531 550 1614 551 1616 550 531 550 1616 549 530 551 531 550 530 551 529 552 530 551 531 550 1616 549 1616 550 1616 549 1616 550 1615 551 1614 551 1614 552 1615 551 530 551 531 550 530 551 531 550 531 550 529 552 532 549 531 550 530 551 1613 552 530 551 531 550 529 552 532 549 530 551 530 551 531 550 531 550 530 551 530 551 530 551 531 550 530 551 531 550 531 550 1615 551 529 552 530 551 530 551 530 551 530 551 530 551 530 551 530 551 532 549 531 550 531 550 532 549 531 550 531 550 530 551 530 551 5132 4351 4435 552 1616 550 1615 550 1615 551 1613 553 531 550 530 551 1615 550 530 551 531 550 531 550 530 551 532 549 1616 550 1616 549 530 551 1615 551 530 551 531 550 530 551 530 551 530 551 531 550 1615 551 1615 551 1614 551 1615 550 1615 551 1615 550 1615 550 1616 550 530 551 530 551 531 550 532 549 530 551 530 551 531 550 531 550 531 550 1615 550 530 551 530 551 530 551 529 552 531 550 530 551 531 550 531 550 530 551 530 551 531 550 530 551 530 551 530 551 531 550 1616 550 530 551 529 552 530 551 531 550 532 549 530 551 530 551 529 552 531 550 529 552 530 551 530 551 531 550 531 550 529 552 531 550 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4350 4436 550 1617 549 1615 550 1615 550 1617 548 530 551 531 550 1615 551 531 550 531 550 530 551 530 551 531 550 1614 552 1615 550 530 551 1614 551 531 550 531 550 531 550 529 552 532 549 530 551 1617 549 1616 549 1615 551 1619 547 1615 550 1615 550 1616 549 1616 550 530 551 531 550 530 551 530 551 531 550 530 551 529 552 529 552 530 551 1617 548 533 548 1615 551 1613 552 530 551 531 550 531 550 530 551 530 551 532 549 531 550 531 550 530 551 531 550 531 550 531 550 1615 551 531 550 531 550 532 549 531 550 530 551 531 550 533 548 531 550 530 551 1617 548 1616 549 530 551 531 550 532 549 532 549 532 549 5200 4349 4436 550 1615 551 1615 551 1615 550 1616 550 531 550 530 551 1615 551 531 550 530 551 530 551 530 551 530 551 1616 549 1615 551 530 551 1615 551 531 550 531 550 530 551 531 550 531 550 531 550 1615 551 1616 550 1616 550 1615 550 1617 548 1616 549 1616 550 1615 550 531 550 530 551 531 550 531 550 532 549 530 551 531 550 531 550 532 549 1616 550 531 550 1616 550 1615 550 531 550 530 551 531 550 531 550 531 550 531 550 531 550 532 549 532 549 531 550 532 549 531 550 1616 550 531 550 530 551 532 549 532 549 530 551 532 549 531 550 532 549 531 550 1616 549 1617 549 531 550 530 551 531 550 532 549 532 549 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4350 4437 547 1618 548 1620 546 1620 546 1619 547 534 547 535 546 1619 547 534 547 536 545 536 545 535 546 535 546 1619 547 1620 545 534 523 1644 546 535 522 559 546 535 546 534 547 535 546 535 545 1620 546 1620 546 1620 546 1619 547 1619 546 1619 547 1620 545 1620 546 535 546 534 547 537 520 558 523 558 547 534 547 536 521 559 522 559 522 1644 546 535 546 535 522 560 545 536 521 559 522 559 522 558 523 559 522 560 521 559 522 559 522 560 521 559 522 561 520 1644 521 1645 520 559 522 559 522 559 522 559 522 559 522 560 521 560 521 560 521 561 520 559 522 560 521 559 522 559 522 559 522 1644 522 559 522 5341 4349 4439 520 1645 521 1645 521 1646 519 1645 521 560 521 561 520 1645 521 560 521 560 521 559 522 560 521 561 520 1646 520 1645 521 561 520 1645 521 561 520 560 521 560 521 560 521 560 521 561 520 1644 522 1644 522 1645 520 1645 521 1645 521 1645 520 1646 520 1644 522 561 520 560 521 560 521 561 520 560 521 561 520 561 520 561 520 560 521 1646 520 562 519 561 520 561 520 562 519 560 521 560 521 561 520 561 520 560 521 560 521 561 520 560 521 560 521 562 519 1646 520 1645 521 561 520 561 520 561 520 560 521 560 521 561 520 560 521 559 522 560 521 561 520 561 520 560 521 562 519 559 522 1645 521 561 520 +# +name: Heat_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4348 4439 520 1646 520 1646 520 1646 519 1646 520 561 520 561 520 1646 519 561 520 561 520 562 519 562 519 561 520 1646 520 1647 518 563 518 1646 519 562 519 561 520 561 520 562 519 562 519 561 520 1648 517 1647 519 1646 519 1647 519 1646 520 1646 520 1645 520 1647 519 561 520 561 520 562 519 562 519 562 519 562 519 561 520 562 519 561 520 1646 520 562 519 1647 518 1646 520 562 519 560 521 561 520 561 520 561 520 562 519 562 519 560 521 562 519 562 519 560 521 1646 520 1646 520 561 520 562 519 561 520 562 519 561 520 561 520 561 520 561 520 561 520 1647 518 1646 520 562 519 562 519 561 520 1646 520 561 520 5409 4348 4440 519 1645 521 1646 519 1645 521 1645 521 561 520 561 520 1644 522 561 520 561 520 561 520 560 521 562 519 1646 520 1646 520 562 519 1644 522 561 520 561 520 561 520 561 520 561 520 561 520 1646 520 1645 520 1646 520 1645 521 1646 520 1646 520 1644 522 1645 521 560 521 560 521 561 520 561 520 560 521 560 521 561 520 561 520 561 520 1645 521 562 519 1645 521 1645 520 561 520 562 519 561 520 561 520 561 520 560 521 560 521 560 521 560 521 561 520 560 521 1646 520 1646 520 561 520 560 521 559 522 560 521 561 520 561 520 560 521 560 521 560 521 1646 520 1645 520 561 520 560 521 560 521 1645 521 561 520 diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 2b4b906d6d..4c347b2d85 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -263,6 +263,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 0000000000..fca1c3185f --- /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/main/nfc/scenes/nfc_scene_set_uid.c b/applications/main/nfc/scenes/nfc_scene_set_uid.c index f09d0a4303..f702fa05b3 100644 --- a/applications/main/nfc/scenes/nfc_scene_set_uid.c +++ b/applications/main/nfc/scenes/nfc_scene_set_uid.c @@ -2,22 +2,12 @@ #include "../helpers/protocol_support/nfc_protocol_support_gui_common.h" -static void nfc_scene_set_uid_byte_input_changed_callback(void* context) { - NfcApp* instance = context; - // Retrieve previously saved UID length - const size_t uid_len = scene_manager_get_scene_state(instance->scene_manager, NfcSceneSetUid); - nfc_device_set_uid(instance->nfc_device, instance->byte_input_store, uid_len); -} - void nfc_scene_set_uid_on_enter(void* context) { NfcApp* instance = context; size_t uid_len; const uint8_t* uid = nfc_device_get_uid(instance->nfc_device, &uid_len); - memcpy(instance->byte_input_store, uid, uid_len); - // Save UID length for use in callback - scene_manager_set_scene_state(instance->scene_manager, NfcSceneSetUid, uid_len); // Setup view ByteInput* byte_input = instance->byte_input; @@ -25,7 +15,7 @@ void nfc_scene_set_uid_on_enter(void* context) { byte_input_set_result_callback( byte_input, nfc_protocol_support_common_byte_input_done_callback, - nfc_scene_set_uid_byte_input_changed_callback, + NULL, instance, instance->byte_input_store, uid_len); @@ -39,6 +29,9 @@ bool nfc_scene_set_uid_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventByteInputDone) { + size_t uid_len = 0; + nfc_device_get_uid(instance->nfc_device, &uid_len); + nfc_device_set_uid(instance->nfc_device, instance->byte_input_store, uid_len); if(scene_manager_has_previous_scene(instance->scene_manager, NfcSceneSavedMenu)) { if(nfc_save(instance)) { scene_manager_next_scene(instance->scene_manager, NfcSceneSaveSuccess); diff --git a/applications/main/subghz/views/subghz_read_raw.c b/applications/main/subghz/views/subghz_read_raw.c index f2a0160457..1b9e1d3163 100644 --- a/applications/main/subghz/views/subghz_read_raw.c +++ b/applications/main/subghz/views/subghz_read_raw.c @@ -88,7 +88,7 @@ void subghz_read_raw_add_data_rssi(SubGhzReadRAW* instance, float rssi, bool tra model->rssi_history[model->ind_write] = u_rssi; } - if(model->ind_write > SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE) { + if(model->ind_write >= SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE) { model->rssi_history_end = true; model->ind_write = 0; } diff --git a/applications/services/bt/application.fam b/applications/services/bt/application.fam index 2e97dc1d65..2d2840e3a5 100644 --- a/applications/services/bt/application.fam +++ b/applications/services/bt/application.fam @@ -14,7 +14,7 @@ App( ], stack_size=1 * 1024, order=20, - sdk_headers=["bt_service/bt.h"], + sdk_headers=["bt_service/bt.h", "bt_service/bt_keys_storage.h"], ) App( diff --git a/applications/services/bt/bt_service/bt_keys_storage.h b/applications/services/bt/bt_service/bt_keys_storage.h index cb808ca304..587dd570dd 100644 --- a/applications/services/bt/bt_service/bt_keys_storage.h +++ b/applications/services/bt/bt_service/bt_keys_storage.h @@ -3,6 +3,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + typedef struct BtKeysStorage BtKeysStorage; BtKeysStorage* bt_keys_storage_alloc(const char* keys_storage_path); @@ -18,3 +22,7 @@ bool bt_keys_storage_load(BtKeysStorage* instance); bool bt_keys_storage_update(BtKeysStorage* instance, uint8_t* start_addr, uint32_t size); bool bt_keys_storage_delete(BtKeysStorage* instance); + +#ifdef __cplusplus +} +#endif diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index 9ade1cba51..cc108188da 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -61,11 +61,11 @@ static void desktop_clock_update(Desktop* desktop) { furi_hal_rtc_get_datetime(&curr_dt); bool time_format_12 = locale_get_time_format() == LocaleTimeFormat12h; - if(desktop->time_hour != curr_dt.hour || desktop->time_minute != curr_dt.minute || - desktop->time_format_12 != time_format_12) { - desktop->time_format_12 = time_format_12; - desktop->time_hour = curr_dt.hour; - desktop->time_minute = curr_dt.minute; + if(desktop->clock.hour != curr_dt.hour || desktop->clock.minute != curr_dt.minute || + desktop->clock.format_12 != time_format_12) { + desktop->clock.format_12 = time_format_12; + desktop->clock.hour = curr_dt.hour; + desktop->clock.minute = curr_dt.minute; view_port_update(desktop->clock_viewport); } } @@ -92,8 +92,8 @@ static void desktop_clock_draw_callback(Canvas* canvas, void* context) { canvas_set_font(canvas, FontPrimary); - uint8_t hour = desktop->time_hour; - if(desktop->time_format_12) { + uint8_t hour = desktop->clock.hour; + if(desktop->clock.format_12) { if(hour > 12) { hour -= 12; } @@ -103,11 +103,11 @@ static void desktop_clock_draw_callback(Canvas* canvas, void* context) { } char buffer[20]; - snprintf(buffer, sizeof(buffer), "%02u:%02u", hour, desktop->time_minute); + snprintf(buffer, sizeof(buffer), "%02u:%02u", hour, desktop->clock.minute); view_port_set_width( desktop->clock_viewport, - canvas_string_width(canvas, buffer) - 1 + (desktop->time_minute % 10 == 1)); + canvas_string_width(canvas, buffer) - 1 + (desktop->clock.minute % 10 == 1)); canvas_draw_str_aligned(canvas, 0, 8, AlignLeft, AlignBottom, buffer); } @@ -140,7 +140,7 @@ static bool desktop_custom_event_callback(void* context, uint32_t event) { } return true; case DesktopGlobalAutoLock: - if(!loader_is_locked(desktop->loader)) { + if(!loader_is_locked(desktop->loader) && !desktop->locked) { desktop_lock(desktop); } return true; @@ -216,6 +216,8 @@ static void desktop_clock_timer_callback(void* context) { } void desktop_lock(Desktop* desktop) { + furi_assert(!desktop->locked); + furi_hal_rtc_set_flag(FuriHalRtcFlagLock); if(desktop->settings.pin_code.length) { @@ -231,9 +233,13 @@ void desktop_lock(Desktop* desktop) { DesktopStatus status = {.locked = true}; furi_pubsub_publish(desktop->status_pubsub, &status); + + desktop->locked = true; } void desktop_unlock(Desktop* desktop) { + furi_assert(desktop->locked); + view_port_enabled_set(desktop->lock_icon_viewport, false); Gui* gui = furi_record_open(RECORD_GUI); gui_set_lockdown(gui, false); @@ -252,6 +258,8 @@ void desktop_unlock(Desktop* desktop) { DesktopStatus status = {.locked = false}; furi_pubsub_publish(desktop->status_pubsub, &status); + + desktop->locked = false; } void desktop_set_dummy_mode_state(Desktop* desktop, bool enabled) { diff --git a/applications/services/desktop/desktop_i.h b/applications/services/desktop/desktop_i.h index b694e05f83..634f0ee00d 100644 --- a/applications/services/desktop/desktop_i.h +++ b/applications/services/desktop/desktop_i.h @@ -35,6 +35,12 @@ typedef enum { DesktopViewIdTotal, } DesktopViewId; +typedef struct { + uint8_t hour; + uint8_t minute; + bool format_12; // 1 - 12 hour, 0 - 24H +} DesktopClock; + struct Desktop { // Scene FuriThread* scene_thread; @@ -75,11 +81,10 @@ struct Desktop { FuriPubSub* status_pubsub; - uint8_t time_hour; - uint8_t time_minute; - bool time_format_12 : 1; // 1 - 12 hour, 0 - 24H + DesktopClock clock; bool in_transition : 1; + bool locked : 1; FuriSemaphore* animation_semaphore; }; diff --git a/applications/services/desktop/helpers/slideshow.c b/applications/services/desktop/helpers/slideshow.c index efd02c4bc8..ec02f27a1d 100644 --- a/applications/services/desktop/helpers/slideshow.c +++ b/applications/services/desktop/helpers/slideshow.c @@ -1,6 +1,5 @@ #include "slideshow.h" -#include #include #include #include diff --git a/applications/services/gui/application.fam b/applications/services/gui/application.fam index 869d964dd0..b7dd18baa1 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/canvas.c b/applications/services/gui/canvas.c index a4932225e9..45a52d18b4 100644 --- a/applications/services/gui/canvas.c +++ b/applications/services/gui/canvas.c @@ -16,7 +16,7 @@ const CanvasFontParameters canvas_font_params[FontTotalNumber] = { Canvas* canvas_init(void) { Canvas* canvas = malloc(sizeof(Canvas)); - canvas->compress_icon = compress_icon_alloc(); + canvas->compress_icon = compress_icon_alloc(ICON_DECOMPRESSOR_BUFFER_SIZE); // Initialize mutex canvas->mutex = furi_mutex_alloc(FuriMutexTypeNormal); @@ -398,7 +398,7 @@ void canvas_draw_icon_ex( x += canvas->offset_x; y += canvas->offset_y; uint8_t* icon_data = NULL; - compress_icon_decode(canvas->compress_icon, icon_get_data(icon), &icon_data); + compress_icon_decode(canvas->compress_icon, icon_get_frame_data(icon, 0), &icon_data); canvas_draw_u8g2_bitmap( &canvas->fb, x, y, icon_get_width(icon), icon_get_height(icon), icon_data, rotation); } @@ -410,7 +410,7 @@ void canvas_draw_icon(Canvas* canvas, int32_t x, int32_t y, const Icon* icon) { x += canvas->offset_x; y += canvas->offset_y; uint8_t* icon_data = NULL; - compress_icon_decode(canvas->compress_icon, icon_get_data(icon), &icon_data); + compress_icon_decode(canvas->compress_icon, icon_get_frame_data(icon, 0), &icon_data); canvas_draw_u8g2_bitmap( &canvas->fb, x, y, icon_get_width(icon), icon_get_height(icon), icon_data, IconRotation0); } diff --git a/applications/services/gui/canvas_i.h b/applications/services/gui/canvas_i.h index c204b970fb..449db71dbc 100644 --- a/applications/services/gui/canvas_i.h +++ b/applications/services/gui/canvas_i.h @@ -12,6 +12,8 @@ #include #include +#define ICON_DECOMPRESSOR_BUFFER_SIZE (128u * 64 / 8) + #ifdef __cplusplus extern "C" { #endif diff --git a/applications/services/gui/elements.h b/applications/services/gui/elements.h index 8cc9123621..8270a93191 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/icon.c b/applications/services/gui/icon.c index e216b92551..6e015ed75a 100644 --- a/applications/services/gui/icon.c +++ b/applications/services/gui/icon.c @@ -1,13 +1,16 @@ +#include "icon.h" #include "icon_i.h" #include -uint8_t icon_get_width(const Icon* instance) { +#include + +uint16_t icon_get_width(const Icon* instance) { furi_check(instance); return instance->width; } -uint8_t icon_get_height(const Icon* instance) { +uint16_t icon_get_height(const Icon* instance) { furi_check(instance); return instance->height; @@ -16,5 +19,14 @@ uint8_t icon_get_height(const Icon* instance) { const uint8_t* icon_get_data(const Icon* instance) { furi_check(instance); - return instance->frames[0]; + return icon_get_frame_data(instance, 0); +} + +uint32_t icon_get_frame_count(const Icon* instance) { + return instance->frame_count; +} + +const uint8_t* icon_get_frame_data(const Icon* instance, uint32_t frame) { + furi_check(frame < instance->frame_count); + return instance->frames[frame]; } diff --git a/applications/services/gui/icon.h b/applications/services/gui/icon.h index e33c4d34d2..fc19c0a7df 100644 --- a/applications/services/gui/icon.h +++ b/applications/services/gui/icon.h @@ -6,6 +6,7 @@ #pragma once #include +#include #ifdef __cplusplus extern "C" { @@ -19,7 +20,7 @@ typedef struct Icon Icon; * * @return width in pixels */ -uint8_t icon_get_width(const Icon* instance); +uint16_t icon_get_width(const Icon* instance); /** Get icon height * @@ -27,15 +28,32 @@ uint8_t icon_get_width(const Icon* instance); * * @return height in pixels */ -uint8_t icon_get_height(const Icon* instance); +uint16_t icon_get_height(const Icon* instance); -/** Get Icon XBM bitmap data +/** Get Icon XBM bitmap data for the first frame * * @param[in] instance pointer to Icon data * - * @return pointer to XBM bitmap data + * @return pointer to compressed XBM bitmap data */ -const uint8_t* icon_get_data(const Icon* instance); +FURI_DEPRECATED const uint8_t* icon_get_data(const Icon* instance); + +/** Get Icon frame count + * + * @param[in] instance pointer to Icon data + * + * @return frame count + */ +uint32_t icon_get_frame_count(const Icon* instance); + +/** Get Icon XBM bitmap data for a particular frame + * + * @param[in] instance pointer to Icon data + * @param[in] frame frame index + * + * @return pointer to compressed XBM bitmap data + */ +const uint8_t* icon_get_frame_data(const Icon* instance, uint32_t frame); #ifdef __cplusplus } diff --git a/applications/services/gui/icon_i.h b/applications/services/gui/icon_i.h index 58cac8b412..e33cdbf236 100644 --- a/applications/services/gui/icon_i.h +++ b/applications/services/gui/icon_i.h @@ -4,11 +4,11 @@ */ #pragma once -#include "icon.h" +#include struct Icon { - const uint8_t width; - const uint8_t height; + const uint16_t width; + const uint16_t height; const uint8_t frame_count; const uint8_t frame_rate; const uint8_t* const* frames; diff --git a/applications/services/gui/modules/submenu.c b/applications/services/gui/modules/submenu.c index a4250c2e10..74f93320f9 100644 --- a/applications/services/gui/modules/submenu.c +++ b/applications/services/gui/modules/submenu.c @@ -318,6 +318,26 @@ void submenu_add_lockable_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); view_set_orientation(submenu->view, ViewOrientationHorizontal); @@ -335,6 +355,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 676e99d299..d77f570c26 100644 --- a/applications/services/gui/modules/submenu.h +++ b/applications/services/gui/modules/submenu.h @@ -73,16 +73,32 @@ void submenu_add_lockable_item( bool locked, const char* locked_message); +/** 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 + * + * @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 + * @param index The index of the selected item */ void submenu_set_selected_item(Submenu* submenu, uint32_t index); diff --git a/applications/services/gui/modules/text_box.c b/applications/services/gui/modules/text_box.c index 9c2959b301..c3bff00d08 100644 --- a/applications/services/gui/modules/text_box.c +++ b/applications/services/gui/modules/text_box.c @@ -4,8 +4,13 @@ #include #include -#define TEXT_BOX_MAX_SYMBOL_WIDTH (10) -#define TEXT_BOX_LINE_WIDTH (120) +#define TEXT_BOX_TEXT_WIDTH (120) +#define TEXT_BOX_TEXT_HEIGHT (56) +#define TEXT_BOX_MAX_LINES_PER_SCREEN (10) + +#define TEXT_BOX_LINES_SCROLL_SPEED_MEDIUM (3) +#define TEXT_BOX_LINES_SCROLL_SPEED_FAST (5) +#define TEXT_BOX_LINES_SCROLL_SPEED_SATURATION (9) struct TextBox { View* view; @@ -14,13 +19,19 @@ struct TextBox { }; typedef struct { + TextBoxFont font; + TextBoxFocus focus; const char* text; - char* text_pos; - FuriString* text_formatted; + int32_t scroll_pos; int32_t scroll_num; - TextBoxFont font; - TextBoxFocus focus; + int32_t lines_on_screen; + + int32_t line_offset; + int32_t text_offset; + FuriString* text_on_screen; + FuriString* text_line; + bool formatted; } TextBoxModel; @@ -29,20 +40,11 @@ static void text_box_process_down(TextBox* text_box, uint8_t lines) { text_box->view, TextBoxModel * model, { - if(model->scroll_pos < model->scroll_num - lines) { + if(model->scroll_pos + lines < model->scroll_num) { model->scroll_pos += lines; - for(uint8_t i = 0; i < lines; i++) { - // Search next line start - while(*model->text_pos++ != '\n') - ; - } - } else if(lines > 1) { - lines = model->scroll_num - model->scroll_pos - 1; - model->scroll_pos = model->scroll_num - 1; - for(uint8_t i = 0; i < lines; i++) { - // Search next line start - while(*model->text_pos++ != '\n') - ; + } else { + if(model->scroll_num > 0) { + model->scroll_pos = model->scroll_num - 1; } } }, @@ -54,69 +56,195 @@ static void text_box_process_up(TextBox* text_box, uint8_t lines) { text_box->view, TextBoxModel * model, { - if(model->scroll_pos > lines - 1) { + if(model->scroll_pos - lines > 0) { model->scroll_pos -= lines; - for(uint8_t i = 0; i < lines; i++) { - // Reach last symbol of previous line - model->text_pos--; - // Search previous line start - while((model->text_pos != model->text) && (*(--model->text_pos) != '\n')) - ; - if(*model->text_pos == '\n') { - model->text_pos++; - } - } - } else if(lines > 1) { - lines = model->scroll_pos; + } else { model->scroll_pos = 0; - model->text_pos = (char*)model->text; } }, true); } -static void text_box_insert_endline(Canvas* canvas, TextBoxModel* model) { - size_t i = 0; +static bool text_box_view_input_callback(InputEvent* event, void* context) { + furi_assert(context); + + TextBox* text_box = context; + bool consumed = false; + + if(event->type == InputTypeShort || event->type == InputTypeRepeat) { + int32_t scroll_speed = 1; + if(text_box->button_held_for_ticks > TEXT_BOX_LINES_SCROLL_SPEED_FAST) { + if(text_box->button_held_for_ticks % 2) { + scroll_speed = 0; + } else { + scroll_speed = + (text_box->button_held_for_ticks > TEXT_BOX_LINES_SCROLL_SPEED_SATURATION) ? + TEXT_BOX_LINES_SCROLL_SPEED_FAST : + TEXT_BOX_LINES_SCROLL_SPEED_MEDIUM; + } + } + + if(event->key == InputKeyDown) { + text_box_process_down(text_box, scroll_speed); + consumed = true; + } else if(event->key == InputKeyUp) { + text_box_process_up(text_box, scroll_speed); + consumed = true; + } + + text_box->button_held_for_ticks++; + } else if(event->type == InputTypeRelease) { + text_box->button_held_for_ticks = 0; + consumed = true; + } + + return consumed; +} + +static bool text_box_end_of_text_reached(TextBoxModel* model) { + return model->text[model->text_offset] == '\0'; +} + +static bool text_box_start_of_text_reached(TextBoxModel* model) { + return model->text_offset == 0; +} + +static void text_box_seek_next_line(Canvas* canvas, TextBoxModel* model) { size_t line_width = 0; - const char* str = model->text; - size_t line_num = 0; - while(str[i] != '\0') { - char symb = str[i++]; - if(symb != '\n') { + while(!text_box_end_of_text_reached(model)) { + char symb = model->text[model->text_offset]; + if(symb == '\n') { + model->text_offset++; + break; + } else { size_t glyph_width = canvas_glyph_width(canvas, symb); - if(line_width + glyph_width > TEXT_BOX_LINE_WIDTH) { - line_num++; - line_width = 0; - furi_string_push_back(model->text_formatted, '\n'); + if(line_width + glyph_width > TEXT_BOX_TEXT_WIDTH) { + break; } line_width += glyph_width; - } else { - line_num++; - line_width = 0; + model->text_offset++; } - furi_string_push_back(model->text_formatted, symb); } - line_num++; - model->text = furi_string_get_cstr(model->text_formatted); - model->text_pos = (char*)model->text; - size_t lines_on_screen = 56 / canvas_current_font_height(canvas); - if(model->focus == TextBoxFocusEnd && line_num > lines_on_screen) { - // Set text position to 5th line from the end - const char* end = model->text + furi_string_size(model->text_formatted); - for(size_t i = 0; i < line_num - lines_on_screen; i++) { - while(model->text_pos < end) { - if(*model->text_pos++ == '\n') break; - } +} + +static void text_box_seek_end_of_prev_line(TextBoxModel* model) { + do { + if(text_box_start_of_text_reached(model)) break; + model->text_offset--; + if(text_box_start_of_text_reached(model)) break; + if(model->text[model->text_offset] == '\n') { + model->text_offset--; + } + } while(false); +} + +static void text_box_seek_prev_paragraph(TextBoxModel* model) { + while(!text_box_start_of_text_reached(model)) { + if(model->text[model->text_offset] == '\n') { + model->text_offset++; + break; + } + model->text_offset--; + } +} + +static void text_box_seek_prev_line(Canvas* canvas, TextBoxModel* model) { + int32_t start_text_offset = model->text_offset; + + text_box_seek_end_of_prev_line(model); + text_box_seek_prev_paragraph(model); + + int32_t current_text_offset = model->text_offset; + while(true) { + text_box_seek_next_line(canvas, model); + if(model->text_offset == start_text_offset) { + break; + } + current_text_offset = model->text_offset; + } + model->text_offset = current_text_offset; +} + +static void text_box_move_line_offset(Canvas* canvas, TextBoxModel* model, int32_t line_offset) { + if(line_offset >= 0) { + for(int32_t i = 0; i < line_offset; i++) { + text_box_seek_next_line(canvas, model); } - model->scroll_num = line_num - (lines_on_screen - 1); - model->scroll_pos = line_num - lines_on_screen; } else { - model->scroll_num = MAX(line_num - (lines_on_screen - 1), 0u); - model->scroll_pos = 0; + for(int32_t i = 0; i < (-line_offset); i++) { + text_box_seek_prev_line(canvas, model); + } } } +static void text_box_update_screen_text(Canvas* canvas, TextBoxModel* model) { + furi_string_reset(model->text_on_screen); + furi_string_reset(model->text_line); + + int32_t start_text_offset = model->text_offset; + + for(int32_t i = 0; i < model->lines_on_screen; i++) { + int32_t current_line_text_offset = model->text_offset; + text_box_seek_next_line(canvas, model); + int32_t next_line_text_offset = model->text_offset; + furi_string_set_strn( + model->text_line, + &model->text[current_line_text_offset], + next_line_text_offset - current_line_text_offset); + size_t str_len = furi_string_size(model->text_line); + if(furi_string_get_char(model->text_line, str_len - 1) != '\n') { + furi_string_push_back(model->text_line, '\n'); + } + furi_string_cat(model->text_on_screen, model->text_line); + + if(text_box_end_of_text_reached(model)) break; + current_line_text_offset = next_line_text_offset; + } + + model->text_offset = start_text_offset; +} + +static void text_box_update_text_on_screen(Canvas* canvas, TextBoxModel* model) { + int32_t line_offset = model->scroll_pos - model->line_offset; + text_box_move_line_offset(canvas, model, line_offset); + text_box_update_screen_text(canvas, model); + model->line_offset = model->scroll_pos; +} + +static void text_box_prepare_model(Canvas* canvas, TextBoxModel* model) { + int32_t lines_num = 0; + model->text_offset = 0; + model->scroll_num = 0; + model->scroll_pos = 0; + model->line_offset = 0; + model->lines_on_screen = TEXT_BOX_TEXT_HEIGHT / canvas_current_font_height(canvas); + + // Cache text offset to quick final text offset update if TextBoxFocusEnd is set + int32_t window_offset[TEXT_BOX_MAX_LINES_PER_SCREEN] = {}; + do { + window_offset[lines_num % model->lines_on_screen] = model->text_offset; + text_box_seek_next_line(canvas, model); + lines_num++; + } while(!text_box_end_of_text_reached(model)); + model->text_offset = 0; + lines_num++; + + if(model->focus == TextBoxFocusEnd) { + if(lines_num > model->lines_on_screen) { + model->text_offset = window_offset[(lines_num - 1) % model->lines_on_screen]; + } + } + + if(lines_num > model->lines_on_screen) { + model->scroll_num = lines_num - model->lines_on_screen; + model->scroll_pos = (model->focus == TextBoxFocusEnd) ? model->scroll_num - 1 : 0; + } + + text_box_update_screen_text(canvas, model); + model->line_offset = model->scroll_pos; +} + static void text_box_view_draw_callback(Canvas* canvas, void* _model) { TextBoxModel* model = _model; @@ -132,44 +260,17 @@ static void text_box_view_draw_callback(Canvas* canvas, void* _model) { } if(!model->formatted) { - text_box_insert_endline(canvas, model); + text_box_prepare_model(canvas, model); model->formatted = true; } elements_slightly_rounded_frame(canvas, 0, 0, 124, 64); - elements_multiline_text(canvas, 3, 11, model->text_pos); elements_scrollbar(canvas, model->scroll_pos, model->scroll_num); -} - -static bool text_box_view_input_callback(InputEvent* event, void* context) { - furi_assert(context); - TextBox* text_box = context; - bool consumed = false; - if(event->type == InputTypeShort || event->type == InputTypeRepeat) { - int32_t scroll_speed = 1; - if(text_box->button_held_for_ticks > 5) { - if(text_box->button_held_for_ticks % 2) { - scroll_speed = 0; - } else { - scroll_speed = text_box->button_held_for_ticks > 9 ? 5 : 3; - } - } - - if(event->key == InputKeyDown) { - text_box_process_down(text_box, scroll_speed); - consumed = true; - } else if(event->key == InputKeyUp) { - text_box_process_up(text_box, scroll_speed); - consumed = true; - } - - text_box->button_held_for_ticks++; - } else if(event->type == InputTypeRelease) { - text_box->button_held_for_ticks = 0; - consumed = true; + if(model->line_offset != model->scroll_pos) { + text_box_update_text_on_screen(canvas, model); } - return consumed; + elements_multiline_text(canvas, 3, 11, furi_string_get_cstr(model->text_on_screen)); } TextBox* text_box_alloc(void) { @@ -185,7 +286,8 @@ TextBox* text_box_alloc(void) { TextBoxModel * model, { model->text = NULL; - model->text_formatted = furi_string_alloc_set(""); + model->text_on_screen = furi_string_alloc(); + model->text_line = furi_string_alloc(); model->formatted = false; model->font = TextBoxFontText; }, @@ -198,7 +300,13 @@ void text_box_free(TextBox* text_box) { furi_check(text_box); with_view_model( - text_box->view, TextBoxModel * model, { furi_string_free(model->text_formatted); }, true); + text_box->view, + TextBoxModel * model, + { + furi_string_free(model->text_on_screen); + furi_string_free(model->text_line); + }, + true); view_free(text_box->view); free(text_box); } @@ -216,9 +324,15 @@ void text_box_reset(TextBox* text_box) { TextBoxModel * model, { model->text = NULL; - furi_string_set(model->text_formatted, ""); model->font = TextBoxFontText; model->focus = TextBoxFocusStart; + furi_string_reset(model->text_line); + furi_string_reset(model->text_on_screen); + model->line_offset = 0; + model->text_offset = 0; + model->lines_on_screen = 0; + model->scroll_num = 0; + model->scroll_pos = 0; model->formatted = false; }, true); @@ -227,16 +341,12 @@ void text_box_reset(TextBox* text_box) { void text_box_set_text(TextBox* text_box, const char* text) { furi_check(text_box); furi_check(text); - size_t str_length = strlen(text); - size_t formating_margin = str_length * TEXT_BOX_MAX_SYMBOL_WIDTH / TEXT_BOX_LINE_WIDTH; with_view_model( text_box->view, TextBoxModel * model, { model->text = text; - furi_string_reset(model->text_formatted); - furi_string_reserve(model->text_formatted, str_length + formating_margin); model->formatted = false; }, true); diff --git a/applications/services/gui/view.c b/applications/services/gui/view.c index 8fc5c26997..a35c2fa388 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/services/loader/firmware_api/firmware_api.cpp b/applications/services/loader/firmware_api/firmware_api.cpp index 833f99abe4..45953eddf4 100644 --- a/applications/services/loader/firmware_api/firmware_api.cpp +++ b/applications/services/loader/firmware_api/firmware_api.cpp @@ -10,19 +10,6 @@ static_assert(!has_hash_collisions(elf_api_table), "Detected API method hash collision!"); -#ifdef APP_UNIT_TESTS -constexpr HashtableApiInterface mock_elf_api_interface{ - { - .api_version_major = 0, - .api_version_minor = 0, - .resolver_callback = &elf_resolve_from_hashtable, - }, - nullptr, - nullptr, -}; - -const ElfApiInterface* const firmware_api_interface = &mock_elf_api_interface; -#else constexpr HashtableApiInterface elf_api_interface{ { .api_version_major = (elf_api_version >> 16), @@ -33,7 +20,6 @@ constexpr HashtableApiInterface elf_api_interface{ elf_api_table.cend(), }; const ElfApiInterface* const firmware_api_interface = &elf_api_interface; -#endif extern "C" void furi_hal_info_get_api_version(uint16_t* major, uint16_t* minor) { *major = firmware_api_interface->api_version_major; diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index 8c8a46589b..55f620293e 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -388,6 +388,12 @@ static LoaderStatus loader_start_external_app( FURI_LOG_I(TAG, "Loaded in %zums", (size_t)(furi_get_tick() - start)); + if(flipper_application_is_plugin(loader->app.fap)) { + status = loader_make_status_error( + LoaderStatusErrorInternal, error_message, "Plugin %s is not runnable", path); + break; + } + loader->app.thread = flipper_application_alloc_thread(loader->app.fap, args); FuriString* app_name = furi_string_alloc(); path_extract_filename_no_ext(path, app_name); diff --git a/applications/services/loader/loader_applications.c b/applications/services/loader/loader_applications.c index d52aec097d..0c5d93cc8e 100644 --- a/applications/services/loader/loader_applications.c +++ b/applications/services/loader/loader_applications.c @@ -92,7 +92,7 @@ static bool loader_applications_item_callback( path, loader_applications_app->storage, icon_ptr, item_name); } else { path_extract_filename(path, item_name, false); - memcpy(*icon_ptr, icon_get_data(&I_js_script_10px), FAP_MANIFEST_MAX_ICON_SIZE); + memcpy(*icon_ptr, icon_get_frame_data(&I_js_script_10px, 0), FAP_MANIFEST_MAX_ICON_SIZE); return true; } } diff --git a/applications/services/rpc/rpc_i.h b/applications/services/rpc/rpc_i.h index ffca50231c..20baca7b12 100644 --- a/applications/services/rpc/rpc_i.h +++ b/applications/services/rpc/rpc_i.h @@ -7,6 +7,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + typedef void* (*RpcSystemAlloc)(RpcSession* session); typedef void (*RpcSystemFree)(void* context); typedef void (*PBMessageHandler)(const PB_Main* msg_request, void* context); @@ -45,3 +49,7 @@ void rpc_debug_print_data(const char* prefix, uint8_t* buffer, size_t size); void rpc_cli_command_start_session(Cli* cli, FuriString* args, void* context); PB_CommandStatus rpc_system_storage_get_error(FS_Error fs_error); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/applications/settings/about/about.c b/applications/settings/about/about.c index 6b7c9f976a..1669e335a6 100644 --- a/applications/settings/about/about.c +++ b/applications/settings/about/about.c @@ -43,7 +43,7 @@ static DialogMessageButton product_screen(DialogsApp* dialogs, DialogMessage* me static DialogMessageButton address_screen(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"; @@ -59,7 +59,7 @@ static DialogMessageButton compliance_screen(DialogsApp* dialogs, DialogMessage* 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); @@ -226,9 +226,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 150c3ef9f1..d341595f89 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 @@ -11,10 +11,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 c148f09435..1d72a9e6fa 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 15936bcb80..35ee2a3f1f 100644 --- a/applications/settings/desktop_settings/desktop_settings_app.c +++ b/applications/settings/desktop_settings/desktop_settings_app.c @@ -133,6 +133,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 { @@ -140,6 +141,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 76f23e22f9..48adb9cfb0 100644 --- a/applications/settings/desktop_settings/desktop_settings_app.h +++ b/applications/settings/desktop_settings/desktop_settings_app.h @@ -47,4 +47,5 @@ typedef struct { char device_name[FURI_HAL_VERSION_ARRAY_NAME_LENGTH]; uint8_t menu_idx; + uint32_t pin_menu_idx; } DesktopSettingsApp; 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 c3e22805d6..2f21fd999f 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 86e756ede8..950be4c5a3 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 1603aa3372..5b8aa8638a 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 b829f995bc..4cef4ba986 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/views/desktop_settings_view_pin_setup_howto2.c b/applications/settings/desktop_settings/views/desktop_settings_view_pin_setup_howto2.c index b09b0b95f1..6148747a26 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 eb9ffcf67d..6ee1a0a086 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 cc8656dcfc..f57071b9b0 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 c3f5d5ad80..6cd9c5c676 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 2d5dedfd42..187d969ed9 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 0000000000..62e06de927 --- /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 e734c78e03..bfc9ac9c93 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 0000000000..2f86447618 --- /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 18e7ba5aa3..d6e76894ba 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 5832c65893..2d977176a0 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 862f55a464..13b368d3f4 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 f107aaceae..6a958610e5 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 f205efc0ae..b7620b6e82 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 aa9662a714..cad3fbfafa 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 0e667024f1..e351a2ef71 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/hid_app/assets/DolphinDone_80x58.png b/applications/system/hid_app/assets/DolphinDone_80x58.png new file mode 100644 index 0000000000..594d62d529 Binary files /dev/null and b/applications/system/hid_app/assets/DolphinDone_80x58.png differ diff --git a/applications/system/hid_app/assets/DolphinNice_96x59.png b/applications/system/hid_app/assets/DolphinNice_96x59.png deleted file mode 100644 index a299d36302..0000000000 Binary files a/applications/system/hid_app/assets/DolphinNice_96x59.png and /dev/null differ diff --git a/applications/system/hid_app/assets/Ok_btn_pressed_13x13.png b/applications/system/hid_app/assets/Ok_btn_pressed_13x13.png deleted file mode 100644 index 6b46ba3a82..0000000000 Binary files a/applications/system/hid_app/assets/Ok_btn_pressed_13x13.png and /dev/null differ diff --git a/applications/system/hid_app/hid.c b/applications/system/hid_app/hid.c index 1b0667bbff..586d198a9b 100644 --- a/applications/system/hid_app/hid.c +++ b/applications/system/hid_app/hid.c @@ -4,26 +4,9 @@ #include "views.h" #include #include -#include "hid_icons.h" #define TAG "HidApp" -enum HidDebugSubmenuIndex { - HidSubmenuIndexKeynote, - HidSubmenuIndexKeynoteVertical, - HidSubmenuIndexKeyboard, - HidSubmenuIndexNumpad, - HidSubmenuIndexMedia, - HidSubmenuIndexMusicMacOs, - HidSubmenuIndexMovie, - HidSubmenuIndexTikTok, - HidSubmenuIndexMouse, - HidSubmenuIndexMouseClicker, - HidSubmenuIndexMouseJiggler, - HidSubmenuIndexPushToTalk, - HidSubmenuIndexRemovePairing, -}; - bool hid_custom_event_callback(void* context, uint32_t event) { furi_assert(context); Hid* app = context; @@ -33,10 +16,7 @@ bool hid_custom_event_callback(void* context, uint32_t event) { bool hid_back_event_callback(void* context) { furi_assert(context); Hid* app = context; - FURI_LOG_D("HID", "Back event"); - app->view_id = HidViewSubmenu; - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewSubmenu); - return true; + return scene_manager_handle_back_event(app->scene_manager); } void bt_hid_remove_pairing(Hid* app) { @@ -53,63 +33,12 @@ void bt_hid_remove_pairing(Hid* app) { furi_hal_bt_start_advertising(); } -static void hid_submenu_callback(void* context, uint32_t index) { - furi_assert(context); - Hid* app = context; - if(index == HidSubmenuIndexKeynote) { - app->view_id = HidViewKeynote; - hid_keynote_set_orientation(app->hid_keynote, false); - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewKeynote); - } else if(index == HidSubmenuIndexKeynoteVertical) { - app->view_id = HidViewKeynote; - hid_keynote_set_orientation(app->hid_keynote, true); - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewKeynote); - } else if(index == HidSubmenuIndexKeyboard) { - app->view_id = HidViewKeyboard; - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewKeyboard); - } else if(index == HidSubmenuIndexNumpad) { - app->view_id = HidViewNumpad; - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewNumpad); - } else if(index == HidSubmenuIndexMedia) { - app->view_id = HidViewMedia; - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMedia); - } else if(index == HidSubmenuIndexMusicMacOs) { - app->view_id = HidViewMusicMacOs; - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMusicMacOs); - } else if(index == HidSubmenuIndexMovie) { - app->view_id = HidViewMovie; - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMovie); - } else if(index == HidSubmenuIndexMouse) { - app->view_id = HidViewMouse; - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouse); - } else if(index == HidSubmenuIndexTikTok) { - app->view_id = BtHidViewTikTok; - view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewTikTok); - } else if(index == HidSubmenuIndexMouseClicker) { - app->view_id = HidViewMouseClicker; - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouseClicker); - } else if(index == HidSubmenuIndexMouseJiggler) { - app->view_id = HidViewMouseJiggler; - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouseJiggler); - } else if(index == HidSubmenuIndexPushToTalk) { - app->view_id = HidViewPushToTalkMenu; - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewPushToTalkMenu); - } else if(index == HidSubmenuIndexRemovePairing) { - scene_manager_next_scene(app->scene_manager, HidSceneUnpair); - } -} - static void bt_hid_connection_status_changed_callback(BtStatus status, void* context) { furi_assert(context); Hid* hid = context; - bool connected = (status == BtStatusConnected); -#ifdef HID_TRANSPORT_BLE - if(connected) { - notification_internal_message(hid->notifications, &sequence_set_blue_255); - } else { - notification_internal_message(hid->notifications, &sequence_reset_blue); - } -#endif + const bool connected = (status == BtStatusConnected); + notification_internal_message( + hid->notifications, connected ? &sequence_set_blue_255 : &sequence_reset_blue); hid_keynote_set_connected_status(hid->hid_keynote, connected); hid_keyboard_set_connected_status(hid->hid_keyboard, connected); hid_numpad_set_connected_status(hid->hid_numpad, connected); @@ -119,21 +48,17 @@ static void bt_hid_connection_status_changed_callback(BtStatus status, void* con hid_mouse_set_connected_status(hid->hid_mouse, connected); hid_mouse_clicker_set_connected_status(hid->hid_mouse_clicker, connected); hid_mouse_jiggler_set_connected_status(hid->hid_mouse_jiggler, connected); + hid_mouse_jiggler_stealth_set_connected_status(hid->hid_mouse_jiggler_stealth, connected); hid_ptt_set_connected_status(hid->hid_ptt, connected); hid_tiktok_set_connected_status(hid->hid_tiktok, connected); } -static uint32_t hid_exit(void* context) { - UNUSED(context); - return VIEW_NONE; -} - static uint32_t hid_ptt_menu_view(void* context) { UNUSED(context); return HidViewPushToTalkMenu; } -Hid* hid_alloc(void) { +Hid* hid_alloc() { Hid* app = malloc(sizeof(Hid)); // Gui @@ -148,81 +73,18 @@ Hid* hid_alloc(void) { // View dispatcher app->view_dispatcher = view_dispatcher_alloc(); view_dispatcher_enable_queue(app->view_dispatcher); - view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); - view_dispatcher_set_navigation_event_callback(app->view_dispatcher, hid_back_event_callback); view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_custom_event_callback(app->view_dispatcher, hid_custom_event_callback); + view_dispatcher_set_navigation_event_callback(app->view_dispatcher, hid_back_event_callback); + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); // Scene Manager app->scene_manager = scene_manager_alloc(&hid_scene_handlers, app); // Device Type Submenu view - app->device_type_submenu = submenu_alloc(); - submenu_add_item( - app->device_type_submenu, "Keynote", HidSubmenuIndexKeynote, hid_submenu_callback, app); - submenu_add_item( - app->device_type_submenu, - "Keynote Vertical", - HidSubmenuIndexKeynoteVertical, - hid_submenu_callback, - app); - submenu_add_item( - app->device_type_submenu, "Keyboard", HidSubmenuIndexKeyboard, hid_submenu_callback, app); - submenu_add_item( - app->device_type_submenu, "Numpad", HidSubmenuIndexNumpad, hid_submenu_callback, app); - submenu_add_item( - app->device_type_submenu, "Media", HidSubmenuIndexMedia, hid_submenu_callback, app); - submenu_add_item( - app->device_type_submenu, - "Apple Music macOS", - HidSubmenuIndexMusicMacOs, - hid_submenu_callback, - app); - submenu_add_item( - app->device_type_submenu, "Movie", HidSubmenuIndexMovie, hid_submenu_callback, app); - submenu_add_item( - app->device_type_submenu, "Mouse", HidSubmenuIndexMouse, hid_submenu_callback, app); - submenu_add_item( - app->device_type_submenu, - "TikTok / YT Shorts", - HidSubmenuIndexTikTok, - hid_submenu_callback, - app); - submenu_add_item( - app->device_type_submenu, - "Mouse Clicker", - HidSubmenuIndexMouseClicker, - hid_submenu_callback, - app); - submenu_add_item( - app->device_type_submenu, - "Mouse Jiggler", - HidSubmenuIndexMouseJiggler, - hid_submenu_callback, - app); - submenu_add_item( - app->device_type_submenu, - "PushToTalk", - HidSubmenuIndexPushToTalk, - hid_submenu_callback, - app); -#ifdef HID_TRANSPORT_BLE - submenu_add_item( - app->device_type_submenu, - "Remove Pairing", - HidSubmenuIndexRemovePairing, - hid_submenu_callback, - app); -#endif - view_set_previous_callback(submenu_get_view(app->device_type_submenu), hid_exit); - view_dispatcher_add_view( - app->view_dispatcher, HidViewSubmenu, submenu_get_view(app->device_type_submenu)); - app->view_id = HidViewSubmenu; - return app; -} + app->submenu = submenu_alloc(); -Hid* hid_app_alloc_view(void* context) { - furi_assert(context); - Hid* app = context; + view_dispatcher_add_view(app->view_dispatcher, HidViewSubmenu, submenu_get_view(app->submenu)); // Dialog view app->dialog = dialog_ex_alloc(); @@ -285,6 +147,12 @@ Hid* hid_app_alloc_view(void* context) { app->view_dispatcher, HidViewMouseJiggler, hid_mouse_jiggler_get_view(app->hid_mouse_jiggler)); + // Mouse jiggler stealth view + app->hid_mouse_jiggler_stealth = hid_mouse_jiggler_stealth_alloc(app); + view_dispatcher_add_view( + app->view_dispatcher, + HidViewMouseJigglerStealth, + hid_mouse_jiggler_stealth_get_view(app->hid_mouse_jiggler_stealth)); // PushToTalk view app->hid_ptt_menu = hid_ptt_menu_alloc(app); @@ -307,7 +175,7 @@ void hid_free(Hid* app) { #endif // Free views view_dispatcher_remove_view(app->view_dispatcher, HidViewSubmenu); - submenu_free(app->device_type_submenu); + submenu_free(app->submenu); view_dispatcher_remove_view(app->view_dispatcher, HidViewDialog); dialog_ex_free(app->dialog); view_dispatcher_remove_view(app->view_dispatcher, HidViewPopup); @@ -330,6 +198,8 @@ void hid_free(Hid* app) { hid_mouse_clicker_free(app->hid_mouse_clicker); view_dispatcher_remove_view(app->view_dispatcher, HidViewMouseJiggler); hid_mouse_jiggler_free(app->hid_mouse_jiggler); + view_dispatcher_remove_view(app->view_dispatcher, HidViewMouseJigglerStealth); + hid_mouse_jiggler_stealth_free(app->hid_mouse_jiggler_stealth); view_dispatcher_remove_view(app->view_dispatcher, HidViewPushToTalkMenu); hid_ptt_menu_free(app->hid_ptt_menu); view_dispatcher_remove_view(app->view_dispatcher, HidViewPushToTalk); @@ -354,18 +224,16 @@ void hid_free(Hid* app) { int32_t hid_usb_app(void* p) { UNUSED(p); Hid* app = hid_alloc(); - app = hid_app_alloc_view(app); + FURI_LOG_D("HID", "Starting as USB app"); FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config(); furi_hal_usb_unlock(); furi_check(furi_hal_usb_set_config(&usb_hid, NULL) == true); - bt_hid_connection_status_changed_callback(BtStatusConnected, app); - dolphin_deed(DolphinDeedPluginStart); - scene_manager_next_scene(app->scene_manager, HidSceneMain); + scene_manager_next_scene(app->scene_manager, HidSceneStart); view_dispatcher_run(app->view_dispatcher); @@ -379,7 +247,6 @@ int32_t hid_usb_app(void* p) { int32_t hid_ble_app(void* p) { UNUSED(p); Hid* app = hid_alloc(); - app = hid_app_alloc_view(app); FURI_LOG_D("HID", "Starting as BLE app"); @@ -409,7 +276,7 @@ int32_t hid_ble_app(void* p) { dolphin_deed(DolphinDeedPluginStart); - scene_manager_next_scene(app->scene_manager, HidSceneMain); + scene_manager_next_scene(app->scene_manager, HidSceneStart); view_dispatcher_run(app->view_dispatcher); diff --git a/applications/system/hid_app/hid.h b/applications/system/hid_app/hid.h index 70a73e2ec7..ac565217a8 100644 --- a/applications/system/hid_app/hid.h +++ b/applications/system/hid_app/hid.h @@ -27,6 +27,7 @@ #include "views/hid_mouse.h" #include "views/hid_mouse_clicker.h" #include "views/hid_mouse_jiggler.h" +#include "views/hid_mouse_jiggler_stealth.h" #include "views/hid_tiktok.h" #include "views/hid_ptt.h" #include "views/hid_ptt_menu.h" @@ -35,11 +36,6 @@ #define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys" -typedef enum { - HidTransportUsb, - HidTransportBle, -} HidTransport; - typedef struct Hid Hid; struct Hid { @@ -49,7 +45,7 @@ struct Hid { NotificationApp* notifications; ViewDispatcher* view_dispatcher; SceneManager* scene_manager; - Submenu* device_type_submenu; + Submenu* submenu; DialogEx* dialog; Popup* popup; HidKeynote* hid_keynote; @@ -61,13 +57,12 @@ struct Hid { HidMouse* hid_mouse; HidMouseClicker* hid_mouse_clicker; HidMouseJiggler* hid_mouse_jiggler; + HidMouseJigglerStealth* hid_mouse_jiggler_stealth; HidTikTok* hid_tiktok; HidPushToTalk* hid_ptt; HidPushToTalkMenu* hid_ptt_menu; - - HidTransport transport; - uint32_t view_id; }; + void bt_hid_remove_pairing(Hid* app); void hid_hal_keyboard_press(Hid* instance, uint16_t event); @@ -82,4 +77,4 @@ void hid_hal_mouse_move(Hid* instance, int8_t dx, int8_t dy); void hid_hal_mouse_scroll(Hid* instance, int8_t delta); void hid_hal_mouse_press(Hid* instance, uint16_t event); void hid_hal_mouse_release(Hid* instance, uint16_t event); -void hid_hal_mouse_release_all(Hid* instance); \ No newline at end of file +void hid_hal_mouse_release_all(Hid* instance); diff --git a/applications/system/hid_app/scenes/hid_scene_config.h b/applications/system/hid_app/scenes/hid_scene_config.h index 8f3a788d1f..d18b155582 100644 --- a/applications/system/hid_app/scenes/hid_scene_config.h +++ b/applications/system/hid_app/scenes/hid_scene_config.h @@ -1,3 +1,3 @@ +ADD_SCENE(hid, start, Start) ADD_SCENE(hid, main, Main) ADD_SCENE(hid, unpair, Unpair) -ADD_SCENE(hid, exit_confirm, ExitConfirm) \ No newline at end of file diff --git a/applications/system/hid_app/scenes/hid_scene_exit_confirm.c b/applications/system/hid_app/scenes/hid_scene_exit_confirm.c deleted file mode 100644 index 94e783e939..0000000000 --- a/applications/system/hid_app/scenes/hid_scene_exit_confirm.c +++ /dev/null @@ -1,45 +0,0 @@ -#include "../hid.h" -#include "../views.h" - -static void hid_scene_exit_confirm_dialog_callback(DialogExResult result, void* context) { - furi_assert(context); - Hid* app = context; - if(result == DialogExResultLeft) { - view_dispatcher_stop(app->view_dispatcher); - } else if(result == DialogExResultRight) { - scene_manager_previous_scene(app->scene_manager); - } else if(result == DialogExResultCenter) { - scene_manager_search_and_switch_to_previous_scene(app->scene_manager, HidSceneMain); - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewSubmenu); - } -} - -void hid_scene_exit_confirm_on_enter(void* context) { - Hid* app = context; - - // Exit dialog view - dialog_ex_reset(app->dialog); - dialog_ex_set_result_callback(app->dialog, hid_scene_exit_confirm_dialog_callback); - dialog_ex_set_context(app->dialog, app); - dialog_ex_set_left_button_text(app->dialog, "Exit"); - dialog_ex_set_right_button_text(app->dialog, "Stay"); - dialog_ex_set_center_button_text(app->dialog, "Menu"); - dialog_ex_set_header(app->dialog, "Close Current App?", 16, 12, AlignLeft, AlignTop); - - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewDialog); -} - -bool hid_scene_exit_confirm_on_event(void* context, SceneManagerEvent event) { - Hid* app = context; - bool consumed = false; - UNUSED(app); - UNUSED(event); - - return consumed; -} - -void hid_scene_exit_confirm_on_exit(void* context) { - Hid* app = context; - - dialog_ex_reset(app->dialog); -} diff --git a/applications/system/hid_app/scenes/hid_scene_main.c b/applications/system/hid_app/scenes/hid_scene_main.c index 6c4a116820..1d0ca5ef4b 100644 --- a/applications/system/hid_app/scenes/hid_scene_main.c +++ b/applications/system/hid_app/scenes/hid_scene_main.c @@ -3,8 +3,8 @@ void hid_scene_main_on_enter(void* context) { Hid* app = context; - - view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); + view_dispatcher_switch_to_view( + app->view_dispatcher, scene_manager_get_scene_state(app->scene_manager, HidSceneMain)); } bool hid_scene_main_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/system/hid_app/scenes/hid_scene_start.c b/applications/system/hid_app/scenes/hid_scene_start.c new file mode 100644 index 0000000000..61d340eec7 --- /dev/null +++ b/applications/system/hid_app/scenes/hid_scene_start.c @@ -0,0 +1,167 @@ +#include "../hid.h" +#include "../views.h" + +enum HidSubmenuIndex { + HidSubmenuIndexKeynote, + HidSubmenuIndexKeynoteVertical, + HidSubmenuIndexKeyboard, + HidSubmenuIndexNumpad, + HidSubmenuIndexMedia, + HidSubmenuIndexMusicMacOs, + HidSubmenuIndexMovie, + HidSubmenuIndexTikTok, + HidSubmenuIndexMouse, + HidSubmenuIndexMouseClicker, + HidSubmenuIndexMouseJiggler, + HidSubmenuIndexMouseJigglerStealth, + HidSubmenuIndexPushToTalk, + HidSubmenuIndexRemovePairing, +}; + +static void hid_scene_start_submenu_callback(void* context, uint32_t index) { + furi_assert(context); + Hid* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void hid_scene_start_on_enter(void* context) { + Hid* app = context; + submenu_add_item( + app->submenu, "Keynote", HidSubmenuIndexKeynote, hid_scene_start_submenu_callback, app); + submenu_add_item( + app->submenu, + "Keynote Vertical", + HidSubmenuIndexKeynoteVertical, + hid_scene_start_submenu_callback, + app); + submenu_add_item( + app->submenu, "Keyboard", HidSubmenuIndexKeyboard, hid_scene_start_submenu_callback, app); + submenu_add_item( + app->submenu, "Numpad", HidSubmenuIndexNumpad, hid_scene_start_submenu_callback, app); + submenu_add_item( + app->submenu, "Media", HidSubmenuIndexMedia, hid_scene_start_submenu_callback, app); + submenu_add_item( + app->submenu, + "Apple Music macOS", + HidSubmenuIndexMusicMacOs, + hid_scene_start_submenu_callback, + app); + submenu_add_item( + app->submenu, "Movie", HidSubmenuIndexMovie, hid_scene_start_submenu_callback, app); + submenu_add_item( + app->submenu, "Mouse", HidSubmenuIndexMouse, hid_scene_start_submenu_callback, app); + submenu_add_item( + app->submenu, + "TikTok / YT Shorts", + HidSubmenuIndexTikTok, + hid_scene_start_submenu_callback, + app); + submenu_add_item( + app->submenu, + "Mouse Clicker", + HidSubmenuIndexMouseClicker, + hid_scene_start_submenu_callback, + app); + submenu_add_item( + app->submenu, + "Mouse Jiggler", + HidSubmenuIndexMouseJiggler, + hid_scene_start_submenu_callback, + app); + submenu_add_item( + app->submenu, + "Mouse Jiggler Stealth", + HidSubmenuIndexMouseJigglerStealth, + hid_scene_start_submenu_callback, + app); + submenu_add_item( + app->submenu, + "PushToTalk", + HidSubmenuIndexPushToTalk, + hid_scene_start_submenu_callback, + app); +#ifdef HID_TRANSPORT_BLE + submenu_add_item( + app->submenu, + "Bluetooth Unpairing", + HidSubmenuIndexRemovePairing, + hid_scene_start_submenu_callback, + app); +#endif + + submenu_set_selected_item( + app->submenu, scene_manager_get_scene_state(app->scene_manager, HidSceneStart)); + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewSubmenu); +} + +bool hid_scene_start_on_event(void* context, SceneManagerEvent event) { + Hid* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == HidSubmenuIndexRemovePairing) { + scene_manager_next_scene(app->scene_manager, HidSceneUnpair); + } else { + HidView view_id; + + switch(event.event) { + case HidSubmenuIndexKeynote: + view_id = HidViewKeynote; + hid_keynote_set_orientation(app->hid_keynote, false); + break; + case HidSubmenuIndexKeynoteVertical: + view_id = HidViewKeynote; + hid_keynote_set_orientation(app->hid_keynote, true); + break; + case HidSubmenuIndexKeyboard: + view_id = HidViewKeyboard; + break; + case HidSubmenuIndexNumpad: + view_id = HidViewNumpad; + break; + case HidSubmenuIndexMedia: + view_id = HidViewMedia; + break; + case HidSubmenuIndexMusicMacOs: + view_id = HidViewMusicMacOs; + break; + case HidSubmenuIndexMovie: + view_id = HidViewMovie; + break; + case HidSubmenuIndexTikTok: + view_id = BtHidViewTikTok; + break; + case HidSubmenuIndexMouse: + view_id = HidViewMouse; + break; + case HidSubmenuIndexMouseClicker: + view_id = HidViewMouseClicker; + break; + case HidSubmenuIndexMouseJiggler: + view_id = HidViewMouseJiggler; + break; + case HidSubmenuIndexMouseJigglerStealth: + view_id = HidViewMouseJigglerStealth; + break; + case HidSubmenuIndexPushToTalk: + view_id = HidViewPushToTalkMenu; + break; + default: + furi_crash(); + } + + scene_manager_set_scene_state(app->scene_manager, HidSceneMain, view_id); + scene_manager_next_scene(app->scene_manager, HidSceneMain); + } + + scene_manager_set_scene_state(app->scene_manager, HidSceneStart, event.event); + consumed = true; + } + + return consumed; +} + +void hid_scene_start_on_exit(void* context) { + Hid* app = context; + submenu_reset(app->submenu); +} diff --git a/applications/system/hid_app/scenes/hid_scene_unpair.c b/applications/system/hid_app/scenes/hid_scene_unpair.c index 7b0bbd9e62..0f9387fcf3 100644 --- a/applications/system/hid_app/scenes/hid_scene_unpair.c +++ b/applications/system/hid_app/scenes/hid_scene_unpair.c @@ -36,7 +36,7 @@ void hid_scene_unpair_on_enter(void* context) { dialog_ex_set_right_button_text(app->dialog, "Unpair"); // Un-pair success popup view - popup_set_icon(app->popup, 32, 5, &I_DolphinNice_96x59); + popup_set_icon(app->popup, 48, 6, &I_DolphinDone_80x58); popup_set_header(app->popup, "Done", 14, 15, AlignLeft, AlignTop); popup_set_timeout(app->popup, 1500); popup_set_context(app->popup, app); diff --git a/applications/system/hid_app/views.h b/applications/system/hid_app/views.h index 6fc8531ae1..71cd30e5dd 100644 --- a/applications/system/hid_app/views.h +++ b/applications/system/hid_app/views.h @@ -9,6 +9,7 @@ typedef enum { HidViewMouse, HidViewMouseClicker, HidViewMouseJiggler, + HidViewMouseJigglerStealth, BtHidViewTikTok, HidViewPushToTalk, HidViewPushToTalkMenu, diff --git a/applications/system/hid_app/views/hid_keyboard.c b/applications/system/hid_app/views/hid_keyboard.c index 1ee2c01c9c..9dd2ad6552 100644 --- a/applications/system/hid_app/views/hid_keyboard.c +++ b/applications/system/hid_app/views/hid_keyboard.c @@ -23,7 +23,6 @@ typedef struct { bool ok_pressed; bool back_pressed; bool connected; - HidTransport transport; } HidKeyboardModel; typedef struct { @@ -230,7 +229,8 @@ static void hid_keyboard_draw_callback(Canvas* canvas, void* context) { HidKeyboardModel* model = context; // Header - if((!model->connected) && (model->transport == HidTransportBle)) { +#ifdef HID_TRANSPORT_BLE + if(!model->connected) { canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); canvas_set_font(canvas, FontPrimary); elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Keyboard"); @@ -243,6 +243,7 @@ static void hid_keyboard_draw_callback(Canvas* canvas, void* context) { canvas, 4, 60, AlignLeft, AlignBottom, "Waiting for Connection..."); return; // Dont render the keyboard if we are not yet connected } +#endif canvas_set_font(canvas, FontKeyboard); // Start shifting the all keys up if on the next row (Scrolling) @@ -400,13 +401,7 @@ HidKeyboard* hid_keyboard_alloc(Hid* bt_hid) { view_set_input_callback(hid_keyboard->view, hid_keyboard_input_callback); with_view_model( - hid_keyboard->view, - HidKeyboardModel * model, - { - model->transport = bt_hid->transport; - model->y = 1; - }, - true); + hid_keyboard->view, HidKeyboardModel * model, { model->y = 1; }, true); return hid_keyboard; } diff --git a/applications/system/hid_app/views/hid_keynote.c b/applications/system/hid_app/views/hid_keynote.c index 543363bf67..793f6e5d35 100644 --- a/applications/system/hid_app/views/hid_keynote.c +++ b/applications/system/hid_app/views/hid_keynote.c @@ -19,7 +19,6 @@ typedef struct { bool ok_pressed; bool back_pressed; bool connected; - HidTransport transport; } HidKeynoteModel; static void hid_keynote_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) { @@ -40,13 +39,13 @@ static void hid_keynote_draw_callback(Canvas* canvas, void* context) { HidKeynoteModel* model = context; // Header - if(model->transport == HidTransportBle) { - if(model->connected) { - canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); - } else { - canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); - } +#ifdef HID_TRANSPORT_BLE + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); } +#endif canvas_set_font(canvas, FontPrimary); elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Keynote"); @@ -92,12 +91,12 @@ static void hid_keynote_draw_callback(Canvas* canvas, void* context) { canvas_set_color(canvas, ColorBlack); // Ok - canvas_draw_icon(canvas, 63, 25, &I_Space_65x18); + canvas_draw_icon(canvas, 63, 24, &I_Space_65x18); if(model->ok_pressed) { - elements_slightly_rounded_box(canvas, 66, 27, 60, 13); + elements_slightly_rounded_box(canvas, 66, 26, 60, 13); canvas_set_color(canvas, ColorWhite); } - canvas_draw_icon(canvas, 74, 29, &I_Ok_btn_9x9); + canvas_draw_icon(canvas, 74, 28, &I_Ok_btn_9x9); elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Space"); canvas_set_color(canvas, ColorBlack); @@ -116,18 +115,18 @@ static void hid_keynote_draw_vertical_callback(Canvas* canvas, void* context) { HidKeynoteModel* model = context; // Header - if(model->transport == HidTransportBle) { - if(model->connected) { - canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); - } else { - canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); - } - canvas_set_font(canvas, FontPrimary); - elements_multiline_text_aligned(canvas, 20, 3, AlignLeft, AlignTop, "Keynote"); +#ifdef HID_TRANSPORT_BLE + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); } else { - canvas_set_font(canvas, FontPrimary); - elements_multiline_text_aligned(canvas, 12, 3, AlignLeft, AlignTop, "Keynote"); + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); } + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 20, 3, AlignLeft, AlignTop, "Keynote"); +#else + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 12, 3, AlignLeft, AlignTop, "Keynote"); +#endif canvas_draw_icon(canvas, 2, 18, &I_Pin_back_arrow_10x8); canvas_set_font(canvas, FontSecondary); @@ -274,10 +273,6 @@ HidKeynote* hid_keynote_alloc(Hid* hid) { view_allocate_model(hid_keynote->view, ViewModelTypeLocking, sizeof(HidKeynoteModel)); view_set_draw_callback(hid_keynote->view, hid_keynote_draw_callback); view_set_input_callback(hid_keynote->view, hid_keynote_input_callback); - - with_view_model( - hid_keynote->view, HidKeynoteModel * model, { model->transport = hid->transport; }, true); - return hid_keynote; } diff --git a/applications/system/hid_app/views/hid_media.c b/applications/system/hid_app/views/hid_media.c index af213eb03c..10109b3a36 100644 --- a/applications/system/hid_app/views/hid_media.c +++ b/applications/system/hid_app/views/hid_media.c @@ -22,7 +22,6 @@ typedef struct { bool ok_pressed; bool connected; bool back_pressed; - HidTransport transport; } HidMediaModel; static void hid_media_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) { @@ -43,13 +42,13 @@ static void hid_media_draw_callback(Canvas* canvas, void* context) { HidMediaModel* model = context; // Header - if(model->transport == HidTransportBle) { - if(model->connected) { - canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); - } else { - canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); - } +#ifdef HID_TRANSPORT_BLE + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); } +#endif canvas_set_font(canvas, FontPrimary); elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Media"); @@ -60,9 +59,9 @@ static void hid_media_draw_callback(Canvas* canvas, void* context) { // Up if(model->up_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 68, 6, &I_S_UP_31x15); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 79, 9, &I_Volup_8x6); @@ -70,9 +69,9 @@ static void hid_media_draw_callback(Canvas* canvas, void* context) { // Down if(model->down_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 68, 36, &I_S_DOWN_31x15); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 80, 41, &I_Voldwn_6x6); @@ -80,9 +79,9 @@ static void hid_media_draw_callback(Canvas* canvas, void* context) { // Left if(model->left_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 61, 13, &I_S_LEFT_15x31); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } hid_media_draw_arrow(canvas, 67, 28, CanvasDirectionRightToLeft); @@ -92,9 +91,9 @@ static void hid_media_draw_callback(Canvas* canvas, void* context) { // Right if(model->right_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 91, 13, &I_S_RIGHT_15x31); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } hid_media_draw_arrow(canvas, 96, 28, CanvasDirectionLeftToRight); @@ -104,9 +103,9 @@ static void hid_media_draw_callback(Canvas* canvas, void* context) { // Ok if(model->ok_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 74, 19, &I_Pressed_Button_19x19); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } hid_media_draw_arrow(canvas, 80, 28, CanvasDirectionLeftToRight); @@ -116,9 +115,9 @@ static void hid_media_draw_callback(Canvas* canvas, void* context) { // Exit if(model->back_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 107, 33, &I_Pressed_Button_19x19); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 111, 38, &I_Pin_back_arrow_10x10); @@ -198,6 +197,7 @@ static bool hid_media_input_callback(InputEvent* event, void* context) { hid_media_process_release(hid_media, event); } } + return consumed; } @@ -209,10 +209,6 @@ HidMedia* hid_media_alloc(Hid* hid) { view_allocate_model(hid_media->view, ViewModelTypeLocking, sizeof(HidMediaModel)); view_set_draw_callback(hid_media->view, hid_media_draw_callback); view_set_input_callback(hid_media->view, hid_media_input_callback); - - with_view_model( - hid_media->view, HidMediaModel * model, { model->transport = hid->transport; }, true); - return hid_media; } diff --git a/applications/system/hid_app/views/hid_media.h b/applications/system/hid_app/views/hid_media.h index 2356734b3e..faf88301f4 100644 --- a/applications/system/hid_app/views/hid_media.h +++ b/applications/system/hid_app/views/hid_media.h @@ -3,7 +3,6 @@ #include typedef struct Hid Hid; - typedef struct HidMedia HidMedia; HidMedia* hid_media_alloc(Hid* hid); diff --git a/applications/system/hid_app/views/hid_mouse.c b/applications/system/hid_app/views/hid_mouse.c index 3ae7c81454..4f568b26c7 100644 --- a/applications/system/hid_app/views/hid_mouse.c +++ b/applications/system/hid_app/views/hid_mouse.c @@ -21,7 +21,6 @@ typedef struct { bool right_mouse_pressed; bool connected; uint8_t acceleration; - HidTransport transport; } HidMouseModel; static void hid_mouse_draw_callback(Canvas* canvas, void* context) { @@ -29,13 +28,13 @@ static void hid_mouse_draw_callback(Canvas* canvas, void* context) { HidMouseModel* model = context; // Header - if(model->transport == HidTransportBle) { - if(model->connected) { - canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); - } else { - canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); - } +#ifdef HID_TRANSPORT_BLE + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); } +#endif canvas_set_font(canvas, FontPrimary); elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Mouse"); @@ -54,9 +53,9 @@ static void hid_mouse_draw_callback(Canvas* canvas, void* context) { // Up if(model->up_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 68, 6, &I_S_UP_31x15); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 80, 8, &I_Pin_arrow_up_7x9); @@ -64,9 +63,9 @@ static void hid_mouse_draw_callback(Canvas* canvas, void* context) { // Down if(model->down_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 68, 36, &I_S_DOWN_31x15); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 80, 40, &I_Pin_arrow_down_7x9); @@ -74,9 +73,9 @@ static void hid_mouse_draw_callback(Canvas* canvas, void* context) { // Left if(model->left_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 61, 13, &I_S_LEFT_15x31); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 63, 25, &I_Pin_arrow_left_9x7); @@ -84,9 +83,9 @@ static void hid_mouse_draw_callback(Canvas* canvas, void* context) { // Right if(model->right_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 91, 13, &I_S_RIGHT_15x31); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 95, 25, &I_Pin_arrow_right_9x7); @@ -94,9 +93,9 @@ static void hid_mouse_draw_callback(Canvas* canvas, void* context) { // Ok if(model->left_mouse_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 74, 19, &I_Pressed_Button_19x19); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 79, 24, &I_Left_mouse_icon_9x9); @@ -104,9 +103,9 @@ static void hid_mouse_draw_callback(Canvas* canvas, void* context) { // Back if(model->right_mouse_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 107, 33, &I_Pressed_Button_19x19); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 112, 38, &I_Right_mouse_icon_9x9); @@ -202,6 +201,15 @@ static bool hid_mouse_input_callback(InputEvent* event, void* context) { if(event->type == InputTypeLong && event->key == InputKeyBack) { hid_hal_mouse_release_all(hid_mouse->hid); + + with_view_model( + hid_mouse->view, + HidMouseModel * model, + { + model->left_mouse_held = false; + model->left_mouse_pressed = false; + }, + false); } else { hid_mouse_process(hid_mouse, event); consumed = true; @@ -218,10 +226,6 @@ HidMouse* hid_mouse_alloc(Hid* hid) { view_allocate_model(hid_mouse->view, ViewModelTypeLocking, sizeof(HidMouseModel)); view_set_draw_callback(hid_mouse->view, hid_mouse_draw_callback); view_set_input_callback(hid_mouse->view, hid_mouse_input_callback); - - with_view_model( - hid_mouse->view, HidMouseModel * model, { model->transport = hid->transport; }, true); - return hid_mouse; } diff --git a/applications/system/hid_app/views/hid_mouse_clicker.c b/applications/system/hid_app/views/hid_mouse_clicker.c index d85affc433..3a2a064cbb 100644 --- a/applications/system/hid_app/views/hid_mouse_clicker.c +++ b/applications/system/hid_app/views/hid_mouse_clicker.c @@ -18,7 +18,6 @@ typedef struct { bool connected; bool running; int rate; - HidTransport transport; } HidMouseClickerModel; static void hid_mouse_clicker_start_or_restart_timer(void* context) { @@ -44,46 +43,46 @@ static void hid_mouse_clicker_draw_callback(Canvas* canvas, void* context) { HidMouseClickerModel* model = context; // Header - if(model->transport == HidTransportBle) { - if(model->connected) { - canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); - } else { - canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); - } +#ifdef HID_TRANSPORT_BLE + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); } +#endif canvas_set_font(canvas, FontPrimary); elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Mouse Clicker"); + canvas_set_font(canvas, FontSecondary); // Ok - canvas_draw_icon(canvas, 63, 25, &I_Space_65x18); - if(model->running) { - canvas_set_font(canvas, FontPrimary); - - FuriString* rate_label = furi_string_alloc(); - furi_string_printf(rate_label, "%d clicks/s\n\nUp / Down", model->rate); - elements_multiline_text(canvas, AlignLeft, 35, furi_string_get_cstr(rate_label)); - canvas_set_font(canvas, FontSecondary); - furi_string_free(rate_label); + canvas_draw_icon(canvas, 58, 25, &I_Space_65x18); - elements_slightly_rounded_box(canvas, 66, 27, 60, 13); + if(model->running) { + elements_slightly_rounded_box(canvas, 61, 27, 60, 13); canvas_set_color(canvas, ColorWhite); - } else { - canvas_set_font(canvas, FontPrimary); - elements_multiline_text(canvas, AlignLeft, 35, "Press Start\nto start\nclicking"); - canvas_set_font(canvas, FontSecondary); } - canvas_draw_icon(canvas, 74, 29, &I_Ok_btn_9x9); + + canvas_draw_icon(canvas, 69, 29, &I_Ok_btn_9x9); + if(model->running) { - elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Stop"); + elements_multiline_text_aligned(canvas, 86, 37, AlignLeft, AlignBottom, "Stop"); } else { - elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Start"); + elements_multiline_text_aligned(canvas, 86, 37, AlignLeft, AlignBottom, "Start"); } canvas_set_color(canvas, ColorBlack); + // Clicks/s + char label[20]; + snprintf(label, sizeof(label), "%d clicks/s", model->rate); + elements_multiline_text_aligned(canvas, 28, 37, AlignCenter, AlignBottom, label); + + canvas_draw_icon(canvas, 25, 20, &I_ButtonUp_7x4); + canvas_draw_icon(canvas, 25, 44, &I_ButtonDown_7x4); + // Back - canvas_draw_icon(canvas, 74, 49, &I_Pin_back_arrow_10x8); - elements_multiline_text_aligned(canvas, 91, 57, AlignLeft, AlignBottom, "Quit"); + canvas_draw_icon(canvas, 0, 54, &I_Pin_back_arrow_10x8); + elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Exit"); } static void hid_mouse_clicker_timer_callback(void* context) { @@ -145,6 +144,9 @@ static bool hid_mouse_clicker_input_callback(InputEvent* event, void* context) { rate_changed = true; consumed = true; break; + case InputKeyBack: + model->running = false; + break; default: consumed = true; break; @@ -179,10 +181,7 @@ HidMouseClicker* hid_mouse_clicker_alloc(Hid* hid) { with_view_model( hid_mouse_clicker->view, HidMouseClickerModel * model, - { - model->transport = hid->transport; - model->rate = DEFAULT_CLICK_RATE; - }, + { model->rate = DEFAULT_CLICK_RATE; }, true); return hid_mouse_clicker; diff --git a/applications/system/hid_app/views/hid_mouse_jiggler.c b/applications/system/hid_app/views/hid_mouse_jiggler.c index e36fa09666..41743b0432 100644 --- a/applications/system/hid_app/views/hid_mouse_jiggler.c +++ b/applications/system/hid_app/views/hid_mouse_jiggler.c @@ -15,44 +15,41 @@ struct HidMouseJiggler { typedef struct { bool connected; bool running; - int min_interval; // Minimum interval for random range - int max_interval; // Maximum interval for random range - HidTransport transport; + int interval_idx; + uint8_t counter; } HidMouseJigglerModel; +const int intervals[6] = {500, 2000, 5000, 10000, 30000, 60000}; + static void hid_mouse_jiggler_draw_callback(Canvas* canvas, void* context) { furi_assert(context); HidMouseJigglerModel* model = context; // Header - if(model->transport == HidTransportBle) { - if(model->connected) { - canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); - } else { - canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); - } +#ifdef HID_TRANSPORT_BLE + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); } +#endif - // Title "Mouse Jiggler" canvas_set_font(canvas, FontPrimary); - elements_multiline_text_aligned(canvas, 17, 2, AlignLeft, AlignTop, "Mouse Jiggler"); - - // Display the current min interval in minutes - canvas_set_font(canvas, FontSecondary); // Assuming there's a smaller font available - FuriString* min_interval_str = furi_string_alloc_printf("Min: %d min", model->min_interval); - elements_multiline_text_aligned( - canvas, 0, 16, AlignLeft, AlignTop, furi_string_get_cstr(min_interval_str)); - furi_string_free(min_interval_str); - - // Display the current max interval in minutes - FuriString* max_interval_str = furi_string_alloc_printf("Max: %d min", model->max_interval); - elements_multiline_text_aligned( - canvas, 0, 28, AlignLeft, AlignTop, furi_string_get_cstr(max_interval_str)); - furi_string_free(max_interval_str); - - // "Press Start to jiggle" + elements_multiline_text_aligned(canvas, 27, 2, AlignLeft, AlignTop, "Mouse Jiggler"); + + // Timeout + elements_multiline_text(canvas, AlignLeft, 26, "Interval (ms):"); + canvas_set_font(canvas, FontSecondary); + if(model->interval_idx != 0) canvas_draw_icon(canvas, 74, 19, &I_ButtonLeft_4x7); + if(model->interval_idx != (int)COUNT_OF(intervals) - 1) + canvas_draw_icon(canvas, 80, 19, &I_ButtonRight_4x7); + FuriString* interval_str = furi_string_alloc_printf("%d", intervals[model->interval_idx]); + elements_multiline_text(canvas, 91, 26, furi_string_get_cstr(interval_str)); + furi_string_free(interval_str); + canvas_set_font(canvas, FontPrimary); - elements_multiline_text(canvas, AlignLeft, 50, "Press Start\nto jiggle"); + elements_multiline_text(canvas, AlignLeft, 40, "Press Start\nto jiggle"); + canvas_set_font(canvas, FontSecondary); // Ok canvas_draw_icon(canvas, 63, 30, &I_Space_65x18); @@ -81,20 +78,11 @@ static void hid_mouse_jiggler_timer_callback(void* context) { HidMouseJigglerModel * model, { if(model->running) { - // Generate a random interval in minutes and convert to milliseconds - int randomIntervalMinutes = - model->min_interval + rand() % (model->max_interval - model->min_interval + 1); - - // Randomize the mouse movement distance and direction - int move_x = (rand() % 2001) - 1000; // Randomly between -1000 and 1000 - int move_y = (rand() % 2001) - 1000; // Randomly between -1000 and 1000 - - // Perform the mouse move with the randomized values - hid_hal_mouse_move(hid_mouse_jiggler->hid, move_x, move_y); - - // Restart timer with the new random interval - furi_timer_stop(hid_mouse_jiggler->timer); - furi_timer_start(hid_mouse_jiggler->timer, randomIntervalMinutes * 60000); + model->counter++; + hid_hal_mouse_move( + hid_mouse_jiggler->hid, + (model->counter % 2 == 0) ? MOUSE_MOVE_SHORT : -MOUSE_MOVE_SHORT, + 0); } }, false); @@ -116,51 +104,23 @@ static bool hid_mouse_jiggler_input_callback(InputEvent* event, void* context) { hid_mouse_jiggler->view, HidMouseJigglerModel * model, { - if(event->type == InputTypePress) { - switch(event->key) { - case InputKeyOk: - model->running = !model->running; - if(model->running) { - furi_timer_stop(hid_mouse_jiggler->timer); - int randomIntervalMinutes = - model->min_interval + - rand() % (model->max_interval - model->min_interval + 1); - furi_timer_start(hid_mouse_jiggler->timer, randomIntervalMinutes * 60000); - } - consumed = true; - break; - - case InputKeyUp: - if(!model->running && model->min_interval < model->max_interval) { - model->min_interval++; // Increment min interval by 1 minute - } - consumed = true; - break; - - case InputKeyDown: - if(!model->running && model->min_interval > 1) { // Minimum 1 minute - model->min_interval--; // Decrement min interval by 1 minute - } - consumed = true; - break; - - case InputKeyRight: - if(!model->running && model->max_interval < 30) { // Maximum 30 minutes - model->max_interval++; // Increment max interval by 1 minute - } - consumed = true; - break; - - case InputKeyLeft: - if(!model->running && model->max_interval > model->min_interval + 1) { - model->max_interval--; // Decrement max interval by 1 minute - } - consumed = true; - break; - - default: - break; - } + if(event->type == InputTypePress && event->key == InputKeyOk) { + model->running = !model->running; + if(model->running) { + furi_timer_stop(hid_mouse_jiggler->timer); + furi_timer_start(hid_mouse_jiggler->timer, intervals[model->interval_idx]); + }; + consumed = true; + } + if(event->type == InputTypePress && event->key == InputKeyRight && !model->running && + model->interval_idx < (int)COUNT_OF(intervals) - 1) { + model->interval_idx++; + consumed = true; + } + if(event->type == InputTypePress && event->key == InputKeyLeft && !model->running && + model->interval_idx > 0) { + model->interval_idx--; + consumed = true; } }, true); @@ -185,14 +145,7 @@ HidMouseJiggler* hid_mouse_jiggler_alloc(Hid* hid) { hid_mouse_jiggler_timer_callback, FuriTimerTypePeriodic, hid_mouse_jiggler); with_view_model( - hid_mouse_jiggler->view, - HidMouseJigglerModel * model, - { - // Initialize the min and max interval values - model->min_interval = 2; // 2 minutes - model->max_interval = 15; // 15 minutes - }, - true); + hid_mouse_jiggler->view, HidMouseJigglerModel * model, { model->interval_idx = 2; }, true); return hid_mouse_jiggler; } diff --git a/applications/system/hid_app/views/hid_mouse_jiggler.h b/applications/system/hid_app/views/hid_mouse_jiggler.h index 4361e0bde6..025a863852 100644 --- a/applications/system/hid_app/views/hid_mouse_jiggler.h +++ b/applications/system/hid_app/views/hid_mouse_jiggler.h @@ -2,6 +2,8 @@ #include +#define MOUSE_MOVE_SHORT 5 + typedef struct Hid Hid; typedef struct HidMouseJiggler HidMouseJiggler; diff --git a/applications/system/hid_app/views/hid_mouse_jiggler_stealth.c b/applications/system/hid_app/views/hid_mouse_jiggler_stealth.c new file mode 100644 index 0000000000..d031b4c5de --- /dev/null +++ b/applications/system/hid_app/views/hid_mouse_jiggler_stealth.c @@ -0,0 +1,228 @@ +#include "hid_mouse_jiggler_stealth.h" +#include +#include "../hid.h" + +#include "hid_icons.h" + +#define TAG "HidMouseJigglerStealth" + +struct HidMouseJigglerStealth { + View* view; + Hid* hid; + FuriTimer* timer; +}; + +typedef struct { + bool connected; + bool running; + int min_interval; // Minimum interval for random range + int max_interval; // Maximum interval for random range +} HidMouseJigglerStealthModel; + +static void hid_mouse_jiggler_stealth_draw_callback(Canvas* canvas, void* context) { + furi_assert(context); + HidMouseJigglerStealthModel* model = context; + +// Header +#ifdef HID_TRANSPORT_BLE + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + } +#endif + + // Title "Mouse Jiggler" + canvas_set_font(canvas, FontPrimary); +#ifdef HID_TRANSPORT_BLE + elements_multiline_text_aligned(canvas, 17, 4, AlignLeft, AlignTop, "Mouse Jiggler Stealth"); +#else + elements_multiline_text_aligned(canvas, 10, 2, AlignLeft, AlignTop, "Mouse Jiggler Stealth"); +#endif + + // Display the current min interval in minutes + canvas_set_font(canvas, FontSecondary); // Assuming there's a smaller font available + FuriString* min_interval_str = furi_string_alloc_printf("Min: %d min", model->min_interval); + elements_multiline_text_aligned( + canvas, 0, 16, AlignLeft, AlignTop, furi_string_get_cstr(min_interval_str)); + furi_string_free(min_interval_str); + + // Display the current max interval in minutes + FuriString* max_interval_str = furi_string_alloc_printf("Max: %d min", model->max_interval); + elements_multiline_text_aligned( + canvas, 0, 28, AlignLeft, AlignTop, furi_string_get_cstr(max_interval_str)); + furi_string_free(max_interval_str); + + // "Press Start to jiggle" + canvas_set_font(canvas, FontPrimary); + elements_multiline_text(canvas, AlignLeft, 50, "Press Start\nto jiggle"); + + // Ok + canvas_draw_icon(canvas, 63, 30, &I_Space_65x18); + if(model->running) { + elements_slightly_rounded_box(canvas, 66, 32, 60, 13); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 74, 34, &I_Ok_btn_9x9); + if(model->running) { + elements_multiline_text_aligned(canvas, 91, 41, AlignLeft, AlignBottom, "Stop"); + } else { + elements_multiline_text_aligned(canvas, 91, 41, AlignLeft, AlignBottom, "Start"); + } + canvas_set_color(canvas, ColorBlack); + + // Back + canvas_draw_icon(canvas, 74, 54, &I_Pin_back_arrow_10x8); + elements_multiline_text_aligned(canvas, 91, 62, AlignLeft, AlignBottom, "Quit"); +} + +static void hid_mouse_jiggler_stealth_timer_callback(void* context) { + furi_assert(context); + HidMouseJigglerStealth* hid_mouse_jiggler = context; + with_view_model( + hid_mouse_jiggler->view, + HidMouseJigglerStealthModel * model, + { + if(model->running) { + // Generate a random interval in minutes and convert to milliseconds + int randomIntervalMinutes = + model->min_interval + rand() % (model->max_interval - model->min_interval + 1); + + // Randomize the mouse movement distance and direction + int move_x = (rand() % 2001) - 1000; // Randomly between -1000 and 1000 + int move_y = (rand() % 2001) - 1000; // Randomly between -1000 and 1000 + + // Perform the mouse move with the randomized values + hid_hal_mouse_move(hid_mouse_jiggler->hid, move_x, move_y); + + // Restart timer with the new random interval + furi_timer_stop(hid_mouse_jiggler->timer); + furi_timer_start(hid_mouse_jiggler->timer, randomIntervalMinutes * 60000); + } + }, + false); +} + +static void hid_mouse_jiggler_stealth_exit_callback(void* context) { + furi_assert(context); + HidMouseJigglerStealth* hid_mouse_jiggler = context; + furi_timer_stop(hid_mouse_jiggler->timer); +} + +static bool hid_mouse_jiggler_stealth_input_callback(InputEvent* event, void* context) { + furi_assert(context); + HidMouseJigglerStealth* hid_mouse_jiggler = context; + + bool consumed = false; + + with_view_model( + hid_mouse_jiggler->view, + HidMouseJigglerStealthModel * model, + { + if(event->type == InputTypePress) { + switch(event->key) { + case InputKeyOk: + model->running = !model->running; + if(model->running) { + furi_timer_stop(hid_mouse_jiggler->timer); + int randomIntervalMinutes = + model->min_interval + + rand() % (model->max_interval - model->min_interval + 1); + furi_timer_start(hid_mouse_jiggler->timer, randomIntervalMinutes * 60000); + } + consumed = true; + break; + + case InputKeyUp: + if(!model->running && model->min_interval < model->max_interval) { + model->min_interval++; // Increment min interval by 1 minute + } + consumed = true; + break; + + case InputKeyDown: + if(!model->running && model->min_interval > 1) { // Minimum 1 minute + model->min_interval--; // Decrement min interval by 1 minute + } + consumed = true; + break; + + case InputKeyRight: + if(!model->running && model->max_interval < 30) { // Maximum 30 minutes + model->max_interval++; // Increment max interval by 1 minute + } + consumed = true; + break; + + case InputKeyLeft: + if(!model->running && model->max_interval > model->min_interval + 1) { + model->max_interval--; // Decrement max interval by 1 minute + } + consumed = true; + break; + + default: + break; + } + } + }, + true); + + return consumed; +} + +HidMouseJigglerStealth* hid_mouse_jiggler_stealth_alloc(Hid* hid) { + HidMouseJigglerStealth* hid_mouse_jiggler = malloc(sizeof(HidMouseJigglerStealth)); + + hid_mouse_jiggler->view = view_alloc(); + view_set_context(hid_mouse_jiggler->view, hid_mouse_jiggler); + view_allocate_model( + hid_mouse_jiggler->view, ViewModelTypeLocking, sizeof(HidMouseJigglerStealthModel)); + view_set_draw_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_stealth_draw_callback); + view_set_input_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_stealth_input_callback); + view_set_exit_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_stealth_exit_callback); + + hid_mouse_jiggler->hid = hid; + + hid_mouse_jiggler->timer = furi_timer_alloc( + hid_mouse_jiggler_stealth_timer_callback, FuriTimerTypePeriodic, hid_mouse_jiggler); + + with_view_model( + hid_mouse_jiggler->view, + HidMouseJigglerStealthModel * model, + { + // Initialize the min and max interval values + model->min_interval = 1; // 1 minutes + model->max_interval = 4; // 4 minutes + }, + true); + + return hid_mouse_jiggler; +} + +void hid_mouse_jiggler_stealth_free(HidMouseJigglerStealth* hid_mouse_jiggler) { + furi_assert(hid_mouse_jiggler); + + furi_timer_stop(hid_mouse_jiggler->timer); + furi_timer_free(hid_mouse_jiggler->timer); + + view_free(hid_mouse_jiggler->view); + + free(hid_mouse_jiggler); +} + +View* hid_mouse_jiggler_stealth_get_view(HidMouseJigglerStealth* hid_mouse_jiggler) { + furi_assert(hid_mouse_jiggler); + return hid_mouse_jiggler->view; +} + +void hid_mouse_jiggler_stealth_set_connected_status( + HidMouseJigglerStealth* hid_mouse_jiggler, + bool connected) { + furi_assert(hid_mouse_jiggler); + with_view_model( + hid_mouse_jiggler->view, + HidMouseJigglerStealthModel * model, + { model->connected = connected; }, + true); +} diff --git a/applications/system/hid_app/views/hid_mouse_jiggler_stealth.h b/applications/system/hid_app/views/hid_mouse_jiggler_stealth.h new file mode 100644 index 0000000000..61b52b3cb7 --- /dev/null +++ b/applications/system/hid_app/views/hid_mouse_jiggler_stealth.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +typedef struct Hid Hid; +typedef struct HidMouseJigglerStealth HidMouseJigglerStealth; + +HidMouseJigglerStealth* hid_mouse_jiggler_stealth_alloc(Hid* bt_hid); + +void hid_mouse_jiggler_stealth_free(HidMouseJigglerStealth* hid_mouse_jiggler); + +View* hid_mouse_jiggler_stealth_get_view(HidMouseJigglerStealth* hid_mouse_jiggler); + +void hid_mouse_jiggler_stealth_set_connected_status( + HidMouseJigglerStealth* hid_mouse_jiggler, + bool connected); diff --git a/applications/system/hid_app/views/hid_movie.c b/applications/system/hid_app/views/hid_movie.c index 0a91b7f3be..2ee7b08388 100644 --- a/applications/system/hid_app/views/hid_movie.c +++ b/applications/system/hid_app/views/hid_movie.c @@ -22,7 +22,6 @@ typedef struct { bool ok_pressed; bool connected; bool back_pressed; - HidTransport transport; } HidMovieModel; static void hid_movie_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) { @@ -43,13 +42,13 @@ static void hid_movie_draw_callback(Canvas* canvas, void* context) { HidMovieModel* model = context; // Header - if(model->transport == HidTransportBle) { - if(model->connected) { - canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); - } else { - canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); - } +#ifdef HID_TRANSPORT_BLE + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); } +#endif canvas_set_font(canvas, FontPrimary); elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Movie"); @@ -60,9 +59,9 @@ static void hid_movie_draw_callback(Canvas* canvas, void* context) { // Up if(model->up_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 68, 6, &I_S_UP_31x15); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 79, 9, &I_Volup_8x6); @@ -70,9 +69,9 @@ static void hid_movie_draw_callback(Canvas* canvas, void* context) { // Down if(model->down_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 68, 36, &I_S_DOWN_31x15); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 80, 41, &I_Voldwn_6x6); @@ -80,9 +79,9 @@ static void hid_movie_draw_callback(Canvas* canvas, void* context) { // Left if(model->left_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 61, 13, &I_S_LEFT_15x31); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } hid_movie_draw_arrow(canvas, 65, 28, CanvasDirectionRightToLeft); @@ -91,9 +90,9 @@ static void hid_movie_draw_callback(Canvas* canvas, void* context) { // Right if(model->right_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 91, 13, &I_S_RIGHT_15x31); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } hid_movie_draw_arrow(canvas, 96, 28, CanvasDirectionLeftToRight); @@ -102,9 +101,9 @@ static void hid_movie_draw_callback(Canvas* canvas, void* context) { // Ok if(model->ok_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 74, 19, &I_Pressed_Button_19x19); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } hid_movie_draw_arrow(canvas, 80, 28, CanvasDirectionLeftToRight); @@ -114,9 +113,9 @@ static void hid_movie_draw_callback(Canvas* canvas, void* context) { // Exit if(model->back_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 107, 33, &I_Pressed_Button_19x19); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 111, 38, &I_Pin_back_arrow_10x10); @@ -211,9 +210,6 @@ HidMovie* hid_movie_alloc(Hid* hid) { view_set_draw_callback(hid_movie->view, hid_movie_draw_callback); view_set_input_callback(hid_movie->view, hid_movie_input_callback); - with_view_model( - hid_movie->view, HidMovieModel * model, { model->transport = hid->transport; }, true); - return hid_movie; } diff --git a/applications/system/hid_app/views/hid_music_macos.c b/applications/system/hid_app/views/hid_music_macos.c index 68d738fd73..1675b6dd52 100644 --- a/applications/system/hid_app/views/hid_music_macos.c +++ b/applications/system/hid_app/views/hid_music_macos.c @@ -22,7 +22,6 @@ typedef struct { bool ok_pressed; bool connected; bool back_pressed; - HidTransport transport; } HidMusicMacosModel; static void hid_music_macos_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) { @@ -43,13 +42,13 @@ static void hid_music_macos_draw_callback(Canvas* canvas, void* context) { HidMusicMacosModel* model = context; // Header - if(model->transport == HidTransportBle) { - if(model->connected) { - canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); - } else { - canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); - } +#ifdef HID_TRANSPORT_BLE + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); } +#endif canvas_set_font(canvas, FontPrimary); elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Music"); @@ -60,9 +59,9 @@ static void hid_music_macos_draw_callback(Canvas* canvas, void* context) { // Up if(model->up_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 68, 6, &I_S_UP_31x15); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 79, 9, &I_Volup_8x6); @@ -70,9 +69,9 @@ static void hid_music_macos_draw_callback(Canvas* canvas, void* context) { // Down if(model->down_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 68, 36, &I_S_DOWN_31x15); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 80, 41, &I_Voldwn_6x6); @@ -80,9 +79,9 @@ static void hid_music_macos_draw_callback(Canvas* canvas, void* context) { // Left if(model->left_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 61, 13, &I_S_LEFT_15x31); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } hid_music_macos_draw_arrow(canvas, 67, 28, CanvasDirectionRightToLeft); @@ -92,9 +91,9 @@ static void hid_music_macos_draw_callback(Canvas* canvas, void* context) { // Right if(model->right_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 91, 13, &I_S_RIGHT_15x31); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } hid_music_macos_draw_arrow(canvas, 96, 28, CanvasDirectionLeftToRight); @@ -104,9 +103,9 @@ static void hid_music_macos_draw_callback(Canvas* canvas, void* context) { // Ok if(model->ok_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 74, 19, &I_Pressed_Button_19x19); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } hid_music_macos_draw_arrow(canvas, 80, 28, CanvasDirectionLeftToRight); @@ -116,9 +115,9 @@ static void hid_music_macos_draw_callback(Canvas* canvas, void* context) { // Exit if(model->back_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 107, 33, &I_Pressed_Button_19x19); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 111, 38, &I_Pin_back_arrow_10x10); @@ -215,12 +214,6 @@ HidMusicMacos* hid_music_macos_alloc(Hid* hid) { view_set_draw_callback(hid_music_macos->view, hid_music_macos_draw_callback); view_set_input_callback(hid_music_macos->view, hid_music_macos_input_callback); - with_view_model( - hid_music_macos->view, - HidMusicMacosModel * model, - { model->transport = hid->transport; }, - true); - return hid_music_macos; } diff --git a/applications/system/hid_app/views/hid_numpad.c b/applications/system/hid_app/views/hid_numpad.c index bd4788b83a..7e528e3fbd 100644 --- a/applications/system/hid_app/views/hid_numpad.c +++ b/applications/system/hid_app/views/hid_numpad.c @@ -23,7 +23,6 @@ typedef struct { bool back_pressed; bool connected; char key_string[5]; - HidTransport transport; } HidNumpadModel; typedef struct { @@ -135,27 +134,29 @@ static void hid_numpad_draw_callback(Canvas* canvas, void* context) { // Header canvas_set_font(canvas, FontPrimary); - if(model->transport == HidTransportBle) { - if(model->connected) { - canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); - } else { - canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); - elements_multiline_text_aligned( - canvas, 7, 60, AlignLeft, AlignBottom, "Waiting for\nConnection..."); - } - elements_multiline_text_aligned(canvas, 20, 3, AlignLeft, AlignTop, "Numpad"); - +#ifdef HID_TRANSPORT_BLE + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); } else { - elements_multiline_text_aligned(canvas, 12, 3, AlignLeft, AlignTop, "Numpad"); + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + elements_multiline_text_aligned( + canvas, 7, 60, AlignLeft, AlignBottom, "Waiting for\nConnection..."); } + elements_multiline_text_aligned(canvas, 20, 3, AlignLeft, AlignTop, "Numpad"); + +#else + elements_multiline_text_aligned(canvas, 12, 3, AlignLeft, AlignTop, "Numpad"); +#endif canvas_draw_icon(canvas, 3, 18, &I_Pin_back_arrow_10x8); canvas_set_font(canvas, FontSecondary); elements_multiline_text_aligned(canvas, 15, 19, AlignLeft, AlignTop, "Hold to exit"); - if(!model->connected && (model->transport == HidTransportBle)) { +#ifdef HID_TRANSPORT_BLE + if(!model->connected) { return; } +#endif canvas_set_font(canvas, FontKeyboard); uint8_t initY = 0; // = model->y == 0 ? 0 : 1; @@ -289,13 +290,7 @@ HidNumpad* hid_numpad_alloc(Hid* bt_hid) { view_set_input_callback(hid_numpad->view, hid_numpad_input_callback); with_view_model( - hid_numpad->view, - HidNumpadModel * model, - { - model->transport = bt_hid->transport; - model->y = 0; - }, - true); + hid_numpad->view, HidNumpadModel * model, { model->y = 0; }, true); return hid_numpad; } diff --git a/applications/system/hid_app/views/hid_ptt.c b/applications/system/hid_app/views/hid_ptt.c index 3b7031a36f..59643abac1 100644 --- a/applications/system/hid_app/views/hid_ptt.c +++ b/applications/system/hid_app/views/hid_ptt.c @@ -32,7 +32,6 @@ typedef struct { size_t osIndex; size_t appIndex; size_t window_position; - HidTransport transport; PushToTalkActionCallback callback_trigger_mute; PushToTalkActionCallback callback_trigger_camera; PushToTalkActionCallback callback_trigger_hand; @@ -575,13 +574,13 @@ static void hid_ptt_draw_callback(Canvas* canvas, void* context) { // Header canvas_set_font(canvas, FontPrimary); - if(model->transport == HidTransportBle) { - if(model->connected) { - canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); - } else { - canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); - } +#ifdef HID_TRANSPORT_BLE + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); } +#endif // OS and App labels canvas_set_font(canvas, FontSecondary); @@ -816,7 +815,6 @@ HidPushToTalk* hid_ptt_alloc(Hid* hid) { hid_ptt->view, HidPushToTalkModel * model, { - model->transport = hid->transport; model->muted = true; // assume we're muted model->os = furi_string_alloc(); model->app = furi_string_alloc(); diff --git a/applications/system/hid_app/views/hid_tiktok.c b/applications/system/hid_app/views/hid_tiktok.c index e9198791d8..7e8e287226 100644 --- a/applications/system/hid_app/views/hid_tiktok.c +++ b/applications/system/hid_app/views/hid_tiktok.c @@ -20,7 +20,6 @@ typedef struct { bool connected; bool is_cursor_set; bool back_mouse_pressed; - HidTransport transport; } HidTikTokModel; static void hid_tiktok_draw_callback(Canvas* canvas, void* context) { @@ -28,13 +27,13 @@ static void hid_tiktok_draw_callback(Canvas* canvas, void* context) { HidTikTokModel* model = context; // Header - if(model->transport == HidTransportBle) { - if(model->connected) { - canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); - } else { - canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); - } +#ifdef HID_TRANSPORT_BLE + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); } +#endif canvas_set_font(canvas, FontPrimary); elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "TikTok /"); @@ -46,9 +45,9 @@ static void hid_tiktok_draw_callback(Canvas* canvas, void* context) { // Pause if(model->back_mouse_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 107, 33, &I_Pressed_Button_19x19); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 113, 37, &I_Pause_icon_9x9); @@ -56,9 +55,9 @@ static void hid_tiktok_draw_callback(Canvas* canvas, void* context) { // Up if(model->up_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 68, 6, &I_S_UP_31x15); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 80, 8, &I_Arr_up_7x9); @@ -66,9 +65,9 @@ static void hid_tiktok_draw_callback(Canvas* canvas, void* context) { // Down if(model->down_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 68, 36, &I_S_DOWN_31x15); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 80, 40, &I_Arr_dwn_7x9); @@ -76,9 +75,9 @@ static void hid_tiktok_draw_callback(Canvas* canvas, void* context) { // Left if(model->left_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 61, 13, &I_S_LEFT_15x31); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 64, 25, &I_Voldwn_6x6); @@ -86,9 +85,9 @@ static void hid_tiktok_draw_callback(Canvas* canvas, void* context) { // Right if(model->right_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 91, 13, &I_S_RIGHT_15x31); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 95, 25, &I_Volup_8x6); @@ -96,9 +95,9 @@ static void hid_tiktok_draw_callback(Canvas* canvas, void* context) { // Ok if(model->ok_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 74, 19, &I_Pressed_Button_19x19); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 78, 25, &I_Like_def_11x9); @@ -234,9 +233,6 @@ HidTikTok* hid_tiktok_alloc(Hid* bt_hid) { view_set_draw_callback(hid_tiktok->view, hid_tiktok_draw_callback); view_set_input_callback(hid_tiktok->view, hid_tiktok_input_callback); - with_view_model( - hid_tiktok->view, HidTikTokModel * model, { model->transport = bt_hid->transport; }, true); - return hid_tiktok; } diff --git a/applications/system/js_app/examples/apps/Scripts/array_buf_test.js b/applications/system/js_app/examples/apps/Scripts/js_examples/array_buf_test.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/array_buf_test.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/array_buf_test.js diff --git a/applications/system/js_app/examples/apps/Scripts/bad_uart.js b/applications/system/js_app/examples/apps/Scripts/js_examples/bad_uart.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/bad_uart.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/bad_uart.js diff --git a/applications/system/js_app/examples/apps/Scripts/badusb_demo.js b/applications/system/js_app/examples/apps/Scripts/js_examples/badusb_demo.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/badusb_demo.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/badusb_demo.js diff --git a/applications/system/js_app/examples/apps/Scripts/blebeacon.js b/applications/system/js_app/examples/apps/Scripts/js_examples/blebeacon.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/blebeacon.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/blebeacon.js diff --git a/applications/system/js_app/examples/apps/Scripts/console.js b/applications/system/js_app/examples/apps/Scripts/js_examples/console.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/console.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/console.js diff --git a/applications/system/js_app/examples/apps/Scripts/delay.js b/applications/system/js_app/examples/apps/Scripts/js_examples/delay.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/delay.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/delay.js diff --git a/applications/system/js_app/examples/apps/Scripts/dialog.js b/applications/system/js_app/examples/apps/Scripts/js_examples/dialog.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/dialog.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/dialog.js diff --git a/applications/system/js_app/examples/apps/Scripts/gpio.js b/applications/system/js_app/examples/apps/Scripts/js_examples/gpio.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/gpio.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/gpio.js diff --git a/applications/system/js_app/examples/apps/Scripts/keyboard.js b/applications/system/js_app/examples/apps/Scripts/js_examples/keyboard.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/keyboard.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/keyboard.js diff --git a/applications/system/js_app/examples/apps/Scripts/load.js b/applications/system/js_app/examples/apps/Scripts/js_examples/load.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/load.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/load.js diff --git a/applications/system/js_app/examples/apps/Scripts/load_api.js b/applications/system/js_app/examples/apps/Scripts/js_examples/load_api.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/load_api.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/load_api.js diff --git a/applications/system/js_app/examples/apps/Scripts/js_examples/math.js b/applications/system/js_app/examples/apps/Scripts/js_examples/math.js new file mode 100644 index 0000000000..c5a0bf18d0 --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/js_examples/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/notify.js b/applications/system/js_app/examples/apps/Scripts/js_examples/notify.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/notify.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/notify.js diff --git a/applications/system/js_app/examples/apps/Scripts/path.js b/applications/system/js_app/examples/apps/Scripts/js_examples/path.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/path.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/path.js diff --git a/applications/system/js_app/examples/apps/Scripts/stringutils.js b/applications/system/js_app/examples/apps/Scripts/js_examples/stringutils.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/stringutils.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/stringutils.js diff --git a/applications/system/js_app/examples/apps/Scripts/subghz.js b/applications/system/js_app/examples/apps/Scripts/js_examples/subghz.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/subghz.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/subghz.js diff --git a/applications/system/js_app/examples/apps/Scripts/submenu.js b/applications/system/js_app/examples/apps/Scripts/js_examples/submenu.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/submenu.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/submenu.js diff --git a/applications/system/js_app/examples/apps/Scripts/textbox.js b/applications/system/js_app/examples/apps/Scripts/js_examples/textbox.js similarity index 76% rename from applications/system/js_app/examples/apps/Scripts/textbox.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/textbox.js index bb6c4fc23a..6caf372347 100644 --- a/applications/system/js_app/examples/apps/Scripts/textbox.js +++ b/applications/system/js_app/examples/apps/Scripts/js_examples/textbox.js @@ -4,9 +4,9 @@ let textbox = require("textbox"); // Focus (start / end), Font (text / hex) textbox.setConfig("end", "text"); -// Can make sure it's empty before showing, in case of reusing in same script -// (Closing textbox already empties the text, but maybe you added more in a loop for example) -textbox.emptyText(); +// 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"); diff --git a/applications/system/js_app/examples/apps/Scripts/uart_echo.js b/applications/system/js_app/examples/apps/Scripts/js_examples/uart_echo.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/uart_echo.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/uart_echo.js diff --git a/applications/system/js_app/examples/apps/Scripts/usbdisk.js b/applications/system/js_app/examples/apps/Scripts/js_examples/usbdisk.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/usbdisk.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/usbdisk.js diff --git a/applications/system/js_app/examples/apps/Scripts/widget-js.fxbm b/applications/system/js_app/examples/apps/Scripts/js_examples/widget-js.fxbm similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/widget-js.fxbm rename to applications/system/js_app/examples/apps/Scripts/js_examples/widget-js.fxbm diff --git a/applications/system/js_app/examples/apps/Scripts/widget.js b/applications/system/js_app/examples/apps/Scripts/js_examples/widget.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/widget.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/widget.js diff --git a/applications/system/js_app/examples/apps/Scripts/math.js b/applications/system/js_app/examples/apps/Scripts/math.js deleted file mode 100644 index 49212f904a..0000000000 --- a/applications/system/js_app/examples/apps/Scripts/math.js +++ /dev/null @@ -1,47 +0,0 @@ -let math = require("math"); - -let absResult = math.abs(-5); -let acosResult = math.acos(0.5); -let acoshResult = math.acosh(2); -let asinResult = math.asin(0.5); -let asinhResult = math.asinh(2); -let atanResult = math.atan(1); -let atan2Result = math.atan2(1, 1); -let atanhResult = math.atanh(0.5); -let cbrtResult = math.cbrt(27); -let ceilResult = math.ceil(5.3); -let clz32Result = math.clz32(1); -let cosResult = math.cos(math.PI); -let expResult = math.exp(1); -let floorResult = math.floor(5.7); -let maxResult = math.max(3, 5); -let minResult = math.min(3, 5); -let powResult = math.pow(2, 3); -let randomResult = math.random(); -let signResult = math.sign(-5); -let sinResult = math.sin(math.PI / 2); -let sqrtResult = math.sqrt(25); -let truncResult = math.trunc(5.7); - -print("math.abs(-5):", absResult); -print("math.acos(0.5):", acosResult); -print("math.acosh(2):", acoshResult); -print("math.asin(0.5):", asinResult); -print("math.asinh(2):", asinhResult); -print("math.atan(1):", atanResult); -print("math.atan2(1, 1):", atan2Result); -print("math.atanh(0.5):", atanhResult); -print("math.cbrt(27):", cbrtResult); -print("math.ceil(5.3):", ceilResult); -print("math.clz32(1):", clz32Result); -print("math.cos(math.PI):", cosResult); -print("math.exp(1):", expResult); -print("math.floor(5.7):", floorResult); -print("math.max(3, 5):", maxResult); -print("math.min(3, 5):", minResult); -print("math.pow(2, 3):", powResult); -print("math.random():", randomResult); -print("math.sign(-5):", signResult); -print("math.sin(math.PI/2):", sinResult); -print("math.sqrt(25):", sqrtResult); -print("math.trunc(5.7):", truncResult); \ No newline at end of file diff --git a/applications/system/js_app/js_thread.c b/applications/system/js_app/js_thread.c index 1dd34d57c4..e1c21e9fbd 100644 --- a/applications/system/js_app/js_thread.c +++ b/applications/system/js_app/js_thread.c @@ -389,7 +389,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 index e7daae41b8..b4c1cdca23 100644 --- a/applications/system/js_app/modules/js_math.c +++ b/applications/system/js_app/modules/js_math.c @@ -1,268 +1,313 @@ #include "../js_modules.h" #include "furi_hal_random.h" +#include -#define JS_MATH_PI (double)3.14159265358979323846 -#define JS_MATH_E (double)2.7182818284590452354 +#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_mk_undefined()); + mjs_return(mjs, MJS_UNDEFINED); } -static bool check_arg_count(struct mjs* mjs, size_t count) { +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_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - mjs_return(mjs, x < 0 ? mjs_mk_number(mjs, -x) : mjs_arg(mjs, 0)); + + mjs_return(mjs, mjs_mk_number(mjs, fabs(x))); } void js_math_acos(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - if(x < -1 || x > 1) { - ret_bad_args(mjs, "Invalid input value for Math.acos"); - mjs_return(mjs, MJS_UNDEFINED); + 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, JS_MATH_PI / (double)2 - atan(x / sqrt(1 - x * x)))); + + mjs_return(mjs, mjs_mk_number(mjs, acos(x))); } void js_math_acosh(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - if(x < 1) { - ret_bad_args(mjs, "Invalid input value for Math.acosh"); - mjs_return(mjs, MJS_UNDEFINED); + 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 - 1)))); + + mjs_return(mjs, mjs_mk_number(mjs, log(x + sqrt(x * x - (double)1.)))); } void js_math_asin(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + 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 / sqrt(1 - x * x)))); + + mjs_return(mjs, mjs_mk_number(mjs, asin(x))); } void js_math_asinh(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + 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 + 1)))); + + mjs_return(mjs, mjs_mk_number(mjs, log(x + sqrt(x * x + (double)1.)))); } void js_math_atan(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + 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_arg_count(mjs, 2) || !mjs_is_number(mjs_arg(mjs, 0)) || - !mjs_is_number(mjs_arg(mjs, 1))) { - mjs_return(mjs, MJS_UNDEFINED); + 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_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - if(x <= -1 || x >= 1) { - ret_bad_args(mjs, "Invalid input value for Math.atanh"); - mjs_return(mjs, MJS_UNDEFINED); + 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((1 + x) / (1 - x)))); + + 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_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - mjs_return(mjs, mjs_mk_number(mjs, pow(x, 1.0 / 3.0))); + + mjs_return(mjs, mjs_mk_number(mjs, cbrt(x))); } void js_math_ceil(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - mjs_return(mjs, mjs_mk_number(mjs, (int)(x + (double)0.5))); + mjs_return(mjs, mjs_mk_number(mjs, ceil(x))); } void js_math_clz32(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + 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_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + 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_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - double result = 1; - double term = 1; - for(int i = 1; i < 100; i++) { - term *= x / i; - result += term; - } - mjs_return(mjs, mjs_mk_number(mjs, result)); + + mjs_return(mjs, mjs_mk_number(mjs, exp(x))); } void js_math_floor(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - mjs_return(mjs, mjs_mk_number(mjs, (int)x)); + + mjs_return(mjs, mjs_mk_number(mjs, floor(x))); } void js_math_log(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + 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"); - mjs_return(mjs, MJS_UNDEFINED); - } - double result = 0; - while(x >= JS_MATH_E) { - x /= JS_MATH_E; - result++; + ret_bad_args(mjs, "Invalid input value for math.log"); + return; } - mjs_return(mjs, mjs_mk_number(mjs, result + log(x))); + + mjs_return(mjs, mjs_mk_number(mjs, log(x))); } void js_math_max(struct mjs* mjs) { - if(!check_arg_count(mjs, 2) || !mjs_is_number(mjs_arg(mjs, 0)) || - !mjs_is_number(mjs_arg(mjs, 1))) { - mjs_return(mjs, MJS_UNDEFINED); + 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_arg_count(mjs, 2) || !mjs_is_number(mjs_arg(mjs, 0)) || - !mjs_is_number(mjs_arg(mjs, 1))) { - mjs_return(mjs, MJS_UNDEFINED); + 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_arg_count(mjs, 2) || !mjs_is_number(mjs_arg(mjs, 0)) || - !mjs_is_number(mjs_arg(mjs, 1))) { - mjs_return(mjs, MJS_UNDEFINED); + 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)); - double result = 1; - for(int i = 0; i < exponent; i++) { - result *= base; - } - mjs_return(mjs, mjs_mk_number(mjs, result)); + + mjs_return(mjs, mjs_mk_number(mjs, pow(base, exponent))); } void js_math_random(struct mjs* mjs) { - if(!check_arg_count(mjs, 0)) { - mjs_return(mjs, MJS_UNDEFINED); + 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 / FURI_HAL_RANDOM_MAX; + 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_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - mjs_return(mjs, mjs_mk_number(mjs, x == 0 ? 0 : (x < 0 ? -1 : 1))); + + 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_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - double result = x; - double term = x; - for(int i = 1; i < 10; i++) { - term *= -x * x / ((2 * i) * (2 * i + 1)); - result += term; - } - mjs_return(mjs, mjs_mk_number(mjs, result)); + + mjs_return(mjs, mjs_mk_number(mjs, sin(x))); } void js_math_sqrt(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + 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.sqrt"); - mjs_return(mjs, MJS_UNDEFINED); + if(x < (double)0.) { + ret_bad_args(mjs, "Invalid input value for math.sqrt"); + return; } - double result = 1; - while(result * result < x) { - result += (double)0.001; - } - mjs_return(mjs, mjs_mk_number(mjs, result)); + + mjs_return(mjs, mjs_mk_number(mjs, sqrt(x))); } void js_math_trunc(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - mjs_return(mjs, mjs_mk_number(mjs, x < 0 ? ceil(x) : floor(x))); + + 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)); @@ -288,6 +333,7 @@ static void* js_math_create(struct mjs* mjs, mjs_val_t* object) { 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; } @@ -306,4 +352,4 @@ static const FlipperAppPluginDescriptor plugin_descriptor = { const FlipperAppPluginDescriptor* js_math_ep(void) { return &plugin_descriptor; -} \ No newline at end of file +} diff --git a/applications/system/js_app/modules/js_submenu.c b/applications/system/js_app/modules/js_submenu.c index 8c8d95fab1..058b32fd09 100644 --- a/applications/system/js_app/modules/js_submenu.c +++ b/applications/system/js_app/modules/js_submenu.c @@ -1,11 +1,13 @@ #include -#include +#include #include +#include #include "../js_modules.h" typedef struct { Submenu* submenu; - ViewDispatcher* view_dispatcher; + ViewHolder* view_holder; + FuriApiLock lock; uint32_t result; bool accepted; } JsSubmenuInst; @@ -35,15 +37,14 @@ static void submenu_callback(void* context, uint32_t id) { JsSubmenuInst* submenu = context; submenu->result = id; submenu->accepted = true; - view_dispatcher_stop(submenu->view_dispatcher); + api_lock_unlock(submenu->lock); } -static bool submenu_exit(void* context) { +static void submenu_exit(void* context) { JsSubmenuInst* submenu = context; submenu->result = 0; submenu->accepted = false; - view_dispatcher_stop(submenu->view_dispatcher); - return true; + api_lock_unlock(submenu->lock); } static void js_submenu_add_item(struct mjs* mjs) { @@ -89,21 +90,20 @@ 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_dispatcher = view_dispatcher_alloc(); - view_dispatcher_enable_queue(submenu->view_dispatcher); - view_dispatcher_add_view(submenu->view_dispatcher, 0, submenu_get_view(submenu->submenu)); - view_dispatcher_set_event_callback_context(submenu->view_dispatcher, submenu); - view_dispatcher_set_navigation_event_callback(submenu->view_dispatcher, submenu_exit); - view_dispatcher_attach_to_gui(submenu->view_dispatcher, gui, ViewDispatcherTypeFullscreen); - view_dispatcher_switch_to_view(submenu->view_dispatcher, 0); - - view_dispatcher_run(submenu->view_dispatcher); - - view_dispatcher_remove_view(submenu->view_dispatcher, 0); - view_dispatcher_free(submenu->view_dispatcher); - submenu->view_dispatcher = NULL; + 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) { @@ -145,4 +145,4 @@ static const FlipperAppPluginDescriptor submenu_plugin_descriptor = { const FlipperAppPluginDescriptor* js_submenu_ep(void) { return &submenu_plugin_descriptor; -} \ No newline at end of file +} diff --git a/applications/system/js_app/modules/js_textbox.c b/applications/system/js_app/modules/js_textbox.c index cf4e8dbbfc..d15cd2779d 100644 --- a/applications/system/js_app/modules/js_textbox.c +++ b/applications/system/js_app/modules/js_textbox.c @@ -1,13 +1,12 @@ #include -#include -#include +#include #include "../js_modules.h" typedef struct { TextBox* text_box; - ViewDispatcher* view_dispatcher; - FuriThread* thread; + ViewHolder* view_holder; FuriString* text; + bool is_shown; } JsTextboxInst; static JsTextboxInst* get_this_ctx(struct mjs* mjs) { @@ -87,6 +86,9 @@ static void js_textbox_add_text(struct mjs* mjs) { 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); @@ -99,10 +101,13 @@ static void js_textbox_add_text(struct mjs* mjs) { mjs_return(mjs, MJS_UNDEFINED); } -static void js_textbox_empty_text(struct mjs* mjs) { +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)); @@ -114,58 +119,34 @@ 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->thread)); -} - -static void textbox_deinit(void* context) { - JsTextboxInst* textbox = context; - furi_thread_join(textbox->thread); - furi_thread_free(textbox->thread); - textbox->thread = NULL; - - view_dispatcher_remove_view(textbox->view_dispatcher, 0); - view_dispatcher_free(textbox->view_dispatcher); - textbox->view_dispatcher = NULL; - furi_record_close(RECORD_GUI); - - text_box_reset(textbox->text_box); - furi_string_reset(textbox->text); + mjs_return(mjs, mjs_mk_boolean(mjs, textbox->is_shown)); } static void textbox_callback(void* context, uint32_t arg) { UNUSED(arg); - textbox_deinit(context); + JsTextboxInst* textbox = context; + view_holder_stop(textbox->view_holder); + textbox->is_shown = false; } -static bool textbox_exit(void* context) { +static void textbox_exit(void* context) { JsTextboxInst* textbox = context; - view_dispatcher_stop(textbox->view_dispatcher); + // Using timer to schedule view_holder stop, will not work under high CPU load furi_timer_pending_callback(textbox_callback, textbox, 0); - return true; -} - -static int32_t textbox_thread(void* context) { - ViewDispatcher* view_dispatcher = context; - view_dispatcher_run(view_dispatcher); - return 0; } static void js_textbox_show(struct mjs* mjs) { JsTextboxInst* textbox = get_this_ctx(mjs); if(!check_arg_count(mjs, 0)) return; - Gui* gui = furi_record_open(RECORD_GUI); - textbox->view_dispatcher = view_dispatcher_alloc(); - view_dispatcher_enable_queue(textbox->view_dispatcher); - view_dispatcher_add_view(textbox->view_dispatcher, 0, text_box_get_view(textbox->text_box)); - view_dispatcher_set_event_callback_context(textbox->view_dispatcher, textbox); - view_dispatcher_set_navigation_event_callback(textbox->view_dispatcher, textbox_exit); - view_dispatcher_attach_to_gui(textbox->view_dispatcher, gui, ViewDispatcherTypeFullscreen); - view_dispatcher_switch_to_view(textbox->view_dispatcher, 0); - - textbox->thread = - furi_thread_alloc_ex("JsTextbox", 1024, textbox_thread, textbox->view_dispatcher); - furi_thread_start(textbox->thread); + 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); } @@ -174,36 +155,49 @@ static void js_textbox_close(struct mjs* mjs) { JsTextboxInst* textbox = get_this_ctx(mjs); if(!check_arg_count(mjs, 0)) return; - if(textbox->thread) { - view_dispatcher_stop(textbox->view_dispatcher); - textbox_deinit(textbox); - } + 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, "emptyText", ~0, MJS_MK_FN(js_textbox_empty_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_box = text_box_alloc(); + 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; - if(textbox->thread) { - view_dispatcher_stop(textbox->view_dispatcher); - textbox_deinit(textbox); - } + + 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); @@ -223,4 +217,4 @@ static const FlipperAppPluginDescriptor textbox_plugin_descriptor = { const FlipperAppPluginDescriptor* js_textbox_ep(void) { return &textbox_plugin_descriptor; -} +} \ No newline at end of file diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_0.png b/assets/dolphin/external/L1_Akira_128x64/frame_0.png new file mode 100755 index 0000000000..36c1bbd499 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_0.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_1.png b/assets/dolphin/external/L1_Akira_128x64/frame_1.png new file mode 100755 index 0000000000..1950347a63 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_1.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_10.png b/assets/dolphin/external/L1_Akira_128x64/frame_10.png new file mode 100755 index 0000000000..65a0154d49 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_10.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_11.png b/assets/dolphin/external/L1_Akira_128x64/frame_11.png new file mode 100755 index 0000000000..f7c0864314 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_11.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_12.png b/assets/dolphin/external/L1_Akira_128x64/frame_12.png new file mode 100755 index 0000000000..e3bfd179d9 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_12.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_13.png b/assets/dolphin/external/L1_Akira_128x64/frame_13.png new file mode 100755 index 0000000000..0094ab5ae4 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_13.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_14.png b/assets/dolphin/external/L1_Akira_128x64/frame_14.png new file mode 100755 index 0000000000..b36fd051fc Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_14.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_15.png b/assets/dolphin/external/L1_Akira_128x64/frame_15.png new file mode 100755 index 0000000000..33607328a0 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_15.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_16.png b/assets/dolphin/external/L1_Akira_128x64/frame_16.png new file mode 100755 index 0000000000..e115834eff Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_16.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_17.png b/assets/dolphin/external/L1_Akira_128x64/frame_17.png new file mode 100755 index 0000000000..8e5fa20d8d Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_17.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_18.png b/assets/dolphin/external/L1_Akira_128x64/frame_18.png new file mode 100755 index 0000000000..6a658e0e22 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_18.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_19.png b/assets/dolphin/external/L1_Akira_128x64/frame_19.png new file mode 100755 index 0000000000..cba72f16f7 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_19.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_2.png b/assets/dolphin/external/L1_Akira_128x64/frame_2.png new file mode 100755 index 0000000000..9f4cc1fb9e Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_2.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_20.png b/assets/dolphin/external/L1_Akira_128x64/frame_20.png new file mode 100755 index 0000000000..457fa7a00e Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_20.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_21.png b/assets/dolphin/external/L1_Akira_128x64/frame_21.png new file mode 100755 index 0000000000..727511100d Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_21.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_22.png b/assets/dolphin/external/L1_Akira_128x64/frame_22.png new file mode 100755 index 0000000000..213e05771f Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_22.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_23.png b/assets/dolphin/external/L1_Akira_128x64/frame_23.png new file mode 100755 index 0000000000..5d3571db86 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_23.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_24.png b/assets/dolphin/external/L1_Akira_128x64/frame_24.png new file mode 100755 index 0000000000..e54d47e29f Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_24.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_25.png b/assets/dolphin/external/L1_Akira_128x64/frame_25.png new file mode 100755 index 0000000000..26c944d16e Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_25.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_26.png b/assets/dolphin/external/L1_Akira_128x64/frame_26.png new file mode 100755 index 0000000000..2972318cda Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_26.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_27.png b/assets/dolphin/external/L1_Akira_128x64/frame_27.png new file mode 100755 index 0000000000..397face78d Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_27.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_28.png b/assets/dolphin/external/L1_Akira_128x64/frame_28.png new file mode 100755 index 0000000000..6ca246f57c Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_28.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_29.png b/assets/dolphin/external/L1_Akira_128x64/frame_29.png new file mode 100755 index 0000000000..8bcc83a1db Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_29.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_3.png b/assets/dolphin/external/L1_Akira_128x64/frame_3.png new file mode 100755 index 0000000000..7a1d2b36e8 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_3.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_30.png b/assets/dolphin/external/L1_Akira_128x64/frame_30.png new file mode 100755 index 0000000000..9f477f8d7f Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_30.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_31.png b/assets/dolphin/external/L1_Akira_128x64/frame_31.png new file mode 100755 index 0000000000..4f2deb5c58 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_31.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_32.png b/assets/dolphin/external/L1_Akira_128x64/frame_32.png new file mode 100755 index 0000000000..4f59f99555 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_32.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_33.png b/assets/dolphin/external/L1_Akira_128x64/frame_33.png new file mode 100755 index 0000000000..1adf3d3512 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_33.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_34.png b/assets/dolphin/external/L1_Akira_128x64/frame_34.png new file mode 100755 index 0000000000..5ffc55f75f Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_34.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_35.png b/assets/dolphin/external/L1_Akira_128x64/frame_35.png new file mode 100755 index 0000000000..9a101f0aa3 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_35.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_4.png b/assets/dolphin/external/L1_Akira_128x64/frame_4.png new file mode 100755 index 0000000000..ea42d75d60 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_4.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_5.png b/assets/dolphin/external/L1_Akira_128x64/frame_5.png new file mode 100755 index 0000000000..c347d30353 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_5.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_6.png b/assets/dolphin/external/L1_Akira_128x64/frame_6.png new file mode 100755 index 0000000000..07a7c70c02 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_6.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_7.png b/assets/dolphin/external/L1_Akira_128x64/frame_7.png new file mode 100755 index 0000000000..275f2a3577 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_7.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_8.png b/assets/dolphin/external/L1_Akira_128x64/frame_8.png new file mode 100755 index 0000000000..1feb3e8751 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_8.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_9.png b/assets/dolphin/external/L1_Akira_128x64/frame_9.png new file mode 100755 index 0000000000..e0ba8250fc Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_9.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/meta.txt b/assets/dolphin/external/L1_Akira_128x64/meta.txt new file mode 100755 index 0000000000..4ac9b38b34 --- /dev/null +++ b/assets/dolphin/external/L1_Akira_128x64/meta.txt @@ -0,0 +1,14 @@ +Filetype: Flipper Animation +Version: 1 + +Width: 128 +Height: 64 +Passive frames: 15 +Active frames: 21 +Frames order: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 +Active cycles: 1 +Frame rate: 2 +Duration: 3600 +Active cooldown: 7 + +Bubble slots: 0 \ No newline at end of file diff --git a/assets/dolphin/external/manifest.txt b/assets/dolphin/external/manifest.txt index d9488cbba5..70b2b7159e 100644 --- a/assets/dolphin/external/manifest.txt +++ b/assets/dolphin/external/manifest.txt @@ -190,6 +190,13 @@ Min level: 3 Max level: 3 Weight: 5 +Name: L1_Akira_128x64 +Min butthurt: 0 +Max butthurt: 8 +Min level: 1 +Max level: 3 +Weight: 5 + Name: L3_Fireplace_128x64 Min butthurt: 0 Max butthurt: 13 diff --git a/assets/icons/Dolphin/DolphinWait_61x59.png b/assets/icons/Dolphin/DolphinWait_61x59.png deleted file mode 100644 index 423e079199..0000000000 Binary files a/assets/icons/Dolphin/DolphinWait_61x59.png and /dev/null differ diff --git a/assets/icons/Settings/Cry_dolph_55x52.png b/assets/icons/Settings/Cry_dolph_55x52.png deleted file mode 100644 index 86d9db1b49..0000000000 Binary files a/assets/icons/Settings/Cry_dolph_55x52.png and /dev/null differ diff --git a/applications/system/hid_app/assets/Circles_47x47.png b/assets/icons/Settings/LoadingHourglass_24x24.png similarity index 73% rename from applications/system/hid_app/assets/Circles_47x47.png rename to assets/icons/Settings/LoadingHourglass_24x24.png index 6a16ebf7bb..9c49dcad1c 100644 Binary files a/applications/system/hid_app/assets/Circles_47x47.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 0000000000..351a849b09 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 0000000000..c5f9df1195 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 3f62b518c2..3cee0d3779 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/ble_profile/extra_profiles/hid_profile.c b/lib/ble_profile/extra_profiles/hid_profile.c index 7231fdb195..85fb101b8c 100644 --- a/lib/ble_profile/extra_profiles/hid_profile.c +++ b/lib/ble_profile/extra_profiles/hid_profile.c @@ -381,7 +381,7 @@ static GapConfig template_config = { .conn_param = { .conn_int_min = 0x18, // AN5289: 4.7, we need at least 25ms + advertisement, which is 30 ms - .conn_int_max = 0x18, // 30 ms + .conn_int_max = 0x24, // 45 ms .slave_latency = 0, .supervisor_timeout = 0, }, diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index 6543168662..398f25209a 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -202,6 +202,7 @@ __attribute__((unused)) static const char* elf_reloc_type_to_str(int symt) { STRCASE(R_ARM_NONE) STRCASE(R_ARM_TARGET1) STRCASE(R_ARM_ABS32) + STRCASE(R_ARM_REL32) STRCASE(R_ARM_THM_PC22) STRCASE(R_ARM_THM_JUMP24) default: @@ -329,6 +330,10 @@ static bool elf_relocate_symbol(ELFFile* elf, Elf32_Addr relAddr, int type, Elf3 *((uint32_t*)relAddr) += symAddr; FURI_LOG_D(TAG, " R_ARM_ABS32 relocated is 0x%08X", (unsigned int)*((uint32_t*)relAddr)); break; + case R_ARM_REL32: + *((uint32_t*)relAddr) += symAddr - relAddr; + FURI_LOG_D(TAG, " R_ARM_REL32 relocated is 0x%08X", (unsigned int)*((uint32_t*)relAddr)); + break; case R_ARM_THM_PC22: case R_ARM_CALL: case R_ARM_THM_JUMP24: diff --git a/lib/lfrfid/protocols/protocol_electra.c b/lib/lfrfid/protocols/protocol_electra.c index fabf7ce6bb..29223a43d6 100644 --- a/lib/lfrfid/protocols/protocol_electra.c +++ b/lib/lfrfid/protocols/protocol_electra.c @@ -411,6 +411,7 @@ bool protocol_electra_write_data(ProtocolElectra* protocol, void* data) { }; void protocol_electra_render_data(ProtocolElectra* protocol, FuriString* result) { + protocol_electra_encoder_start(protocol); furi_string_printf(result, "Epilogue: %016llX", protocol->encoded_epilogue); }; diff --git a/lib/mjs/mjs_core.c b/lib/mjs/mjs_core.c index aae196599f..bcdcb364ab 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/SConscript b/lib/nfc/SConscript index ff41a9bc93..4fb49f792d 100644 --- a/lib/nfc/SConscript +++ b/lib/nfc/SConscript @@ -34,6 +34,7 @@ env.Append( File("protocols/mf_ultralight/mf_ultralight_poller.h"), File("protocols/mf_classic/mf_classic_poller.h"), File("protocols/mf_desfire/mf_desfire_poller.h"), + File("protocols/slix/slix_poller.h"), File("protocols/emv/emv_poller.h"), File("protocols/st25tb/st25tb_poller.h"), File("protocols/felica/felica_poller.h"), diff --git a/lib/nfc/nfc.c b/lib/nfc/nfc.c index 59d254592c..d0d342a6bc 100644 --- a/lib/nfc/nfc.c +++ b/lib/nfc/nfc.c @@ -660,4 +660,4 @@ NfcError nfc_felica_listener_set_sensf_res_data( return nfc_process_hal_error(error); } -#endif // APP_UNIT_TESTS +#endif // FW_CFG_unit_tests diff --git a/applications/debug/unit_tests/nfc/nfc_transport.c b/lib/nfc/nfc_mock.c similarity index 98% rename from applications/debug/unit_tests/nfc/nfc_transport.c rename to lib/nfc/nfc_mock.c index 6886ef66da..df0e009e79 100644 --- a/applications/debug/unit_tests/nfc/nfc_transport.c +++ b/lib/nfc/nfc_mock.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/lib/nfc/protocols/iso15693_3/iso15693_3_listener.c b/lib/nfc/protocols/iso15693_3/iso15693_3_listener.c index 84e7508585..151e4ae4a6 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 a8dec7ae33..6132fbf47f 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/mf_desfire/mf_desfire_poller_i.c b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c index deae2fa071..1dd6c50e10 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c @@ -75,7 +75,7 @@ MfDesfireError mf_desfire_send_chunks( const size_t rx_capacity_remaining = bit_buffer_get_capacity_bytes(rx_buffer) - bit_buffer_get_size_bytes(rx_buffer); - if(rx_size - 1 <= rx_capacity_remaining) { + if(rx_size <= rx_capacity_remaining + 1) { bit_buffer_append_right(rx_buffer, instance->rx_buffer, sizeof(uint8_t)); } else { FURI_LOG_W(TAG, "RX buffer overflow: ignoring %zu bytes", rx_size - 1); diff --git a/lib/nfc/protocols/slix/slix.c b/lib/nfc/protocols/slix/slix.c index 533ecff741..f6ce885d4b 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 2de26847a0..cc2390c6ee 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 15ab2cd3c8..66c4241cb8 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 3c9a7cce4b..aeb1180cfd 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 4ea7f880d7..e78f7882a8 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 9b0b5ec553..ee6912cc4a 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/lib/subghz/SConscript b/lib/subghz/SConscript index be676bef55..cd09b4e1a1 100644 --- a/lib/subghz/SConscript +++ b/lib/subghz/SConscript @@ -27,6 +27,7 @@ env.Append( File("subghz_protocol_registry.h"), File("devices/cc1101_configs.h"), File("devices/cc1101_int/cc1101_int_interconnect.h"), + File("subghz_file_encoder_worker.h"), ], ) diff --git a/lib/subghz/devices/registry.c b/lib/subghz/devices/registry.c index 779ba81d7a..cf044c98a3 100644 --- a/lib/subghz/devices/registry.c +++ b/lib/subghz/devices/registry.c @@ -23,8 +23,8 @@ void subghz_device_registry_init(void) { firmware_api_interface); //TODO FL-3556: fix path to plugins - if(plugin_manager_load_all(subghz_device->manager, "/any/apps_data/subghz/plugins") != - //if(plugin_manager_load_all(subghz_device->manager, APP_DATA_PATH("plugins")) != + //if(plugin_manager_load_all(subghz_device->manager, APP_DATA_PATH("plugins")) != + if(plugin_manager_load_all(subghz_device->manager, "/ext/apps_data/subghz/plugins") != PluginManagerErrorNone) { FURI_LOG_E(TAG, "Failed to load all libs"); } diff --git a/lib/toolbox/SConscript b/lib/toolbox/SConscript index 22b2ef3eab..b5909b6e99 100644 --- a/lib/toolbox/SConscript +++ b/lib/toolbox/SConscript @@ -35,6 +35,9 @@ env.Append( File("simple_array.h"), File("bit_buffer.h"), File("keys_dict.h"), + File("pulse_protocols/pulse_glue.h"), + File("md5_calc.h"), + File("varint.h"), ], ) diff --git a/lib/toolbox/compress.c b/lib/toolbox/compress.c index 70db479680..780bea27ab 100644 --- a/lib/toolbox/compress.c +++ b/lib/toolbox/compress.c @@ -3,6 +3,9 @@ #include #include #include +#include + +#define TAG "Compress" /** Defines encoder and decoder window size */ #define COMPRESS_EXP_BUFF_SIZE_LOG (8u) @@ -10,9 +13,16 @@ /** Defines encoder and decoder lookahead buffer size */ #define COMPRESS_LOOKAHEAD_BUFF_SIZE_LOG (4u) -/** Buffer sizes for input and output data */ -#define COMPRESS_ICON_ENCODED_BUFF_SIZE (1024u) -#define COMPRESS_ICON_DECODED_BUFF_SIZE (1024u) +/** Buffer size for input data */ +#define COMPRESS_ICON_ENCODED_BUFF_SIZE (256u) + +static bool compress_decode_internal( + heatshrink_decoder* decoder, + const uint8_t* data_in, + size_t data_in_size, + uint8_t* data_out, + size_t data_out_size, + size_t* data_res_size); typedef struct { uint8_t is_compressed; @@ -24,55 +34,51 @@ _Static_assert(sizeof(CompressHeader) == 4, "Incorrect CompressHeader size"); struct CompressIcon { heatshrink_decoder* decoder; - uint8_t decoded_buff[COMPRESS_ICON_DECODED_BUFF_SIZE]; + uint8_t* buffer; + size_t buffer_size; }; -CompressIcon* compress_icon_alloc(void) { +CompressIcon* compress_icon_alloc(size_t decode_buf_size) { CompressIcon* instance = malloc(sizeof(CompressIcon)); instance->decoder = heatshrink_decoder_alloc( COMPRESS_ICON_ENCODED_BUFF_SIZE, COMPRESS_EXP_BUFF_SIZE_LOG, COMPRESS_LOOKAHEAD_BUFF_SIZE_LOG); heatshrink_decoder_reset(instance->decoder); - memset(instance->decoded_buff, 0, sizeof(instance->decoded_buff)); + + instance->buffer_size = decode_buf_size + 4; /* To account for heatshrink's poller quirks */ + instance->buffer = malloc(instance->buffer_size); return instance; } void compress_icon_free(CompressIcon* instance) { furi_check(instance); + free(instance->buffer); heatshrink_decoder_free(instance->decoder); free(instance); } -void compress_icon_decode(CompressIcon* instance, const uint8_t* icon_data, uint8_t** decoded_buff) { +void compress_icon_decode(CompressIcon* instance, const uint8_t* icon_data, uint8_t** output) { furi_check(instance); furi_check(icon_data); - furi_check(decoded_buff); + furi_check(output); CompressHeader* header = (CompressHeader*)icon_data; if(header->is_compressed) { - size_t data_processed = 0; - heatshrink_decoder_sink( + size_t decoded_size = 0; + /* If decompression fails - check that decode_buf_size is large enough */ + furi_check(compress_decode_internal( instance->decoder, - (uint8_t*)&icon_data[sizeof(CompressHeader)], - header->compressed_buff_size, - &data_processed); - while(1) { - HSD_poll_res res = heatshrink_decoder_poll( - instance->decoder, - instance->decoded_buff, - sizeof(instance->decoded_buff), - &data_processed); - furi_check((res == HSDR_POLL_EMPTY) || (res == HSDR_POLL_MORE)); - if(res != HSDR_POLL_MORE) { - break; - } - } - heatshrink_decoder_reset(instance->decoder); - *decoded_buff = instance->decoded_buff; + icon_data, + /* Decoder will check/process headers again - need to pass them */ + sizeof(CompressHeader) + header->compressed_buff_size, + instance->buffer, + instance->buffer_size, + &decoded_size)); + *output = instance->buffer; } else { - *decoded_buff = (uint8_t*)&icon_data[1]; + *output = (uint8_t*)&icon_data[1]; } } @@ -81,12 +87,6 @@ struct Compress { heatshrink_decoder* decoder; }; -static void compress_reset(Compress* compress) { - furi_assert(compress); - heatshrink_encoder_reset(compress->encoder); - heatshrink_decoder_reset(compress->decoder); -} - Compress* compress_alloc(uint16_t compress_buff_size) { Compress* compress = malloc(sizeof(Compress)); compress->encoder = @@ -105,16 +105,16 @@ void compress_free(Compress* compress) { free(compress); } -bool compress_encode( - Compress* compress, +static bool compress_encode_internal( + heatshrink_encoder* encoder, uint8_t* data_in, size_t data_in_size, uint8_t* data_out, size_t data_out_size, size_t* data_res_size) { - furi_assert(compress); - furi_assert(data_in); - furi_assert(data_in_size); + furi_check(encoder); + furi_check(data_in); + furi_check(data_in_size); size_t sink_size = 0; size_t poll_size = 0; @@ -125,10 +125,10 @@ bool compress_encode( size_t sunk = 0; size_t res_buff_size = sizeof(CompressHeader); - // Sink data to encoding buffer + /* Sink data to encoding buffer */ while((sunk < data_in_size) && !encode_failed) { - sink_res = heatshrink_encoder_sink( - compress->encoder, &data_in[sunk], data_in_size - sunk, &sink_size); + sink_res = + heatshrink_encoder_sink(encoder, &data_in[sunk], data_in_size - sunk, &sink_size); if(sink_res != HSER_SINK_OK) { encode_failed = true; break; @@ -136,10 +136,7 @@ bool compress_encode( sunk += sink_size; do { poll_res = heatshrink_encoder_poll( - compress->encoder, - &data_out[res_buff_size], - data_out_size - res_buff_size, - &poll_size); + encoder, &data_out[res_buff_size], data_out_size - res_buff_size, &poll_size); if(poll_res < 0) { encode_failed = true; break; @@ -148,31 +145,30 @@ bool compress_encode( } while(poll_res == HSER_POLL_MORE); } - // Notify sinking complete and poll encoded data - finish_res = heatshrink_encoder_finish(compress->encoder); + /* Notify sinking complete and poll encoded data */ + finish_res = heatshrink_encoder_finish(encoder); if(finish_res < 0) { encode_failed = true; } else { do { poll_res = heatshrink_encoder_poll( - compress->encoder, - &data_out[res_buff_size], - data_out_size - 4 - res_buff_size, - &poll_size); + encoder, &data_out[res_buff_size], data_out_size - res_buff_size, &poll_size); if(poll_res < 0) { encode_failed = true; break; } res_buff_size += poll_size; - finish_res = heatshrink_encoder_finish(compress->encoder); + finish_res = heatshrink_encoder_finish(encoder); } while(finish_res != HSER_FINISH_DONE); } bool result = true; - // Write encoded data to output buffer if compression is efficient. Else - write header and original data + /* Write encoded data to output buffer if compression is efficient. Otherwise, write header and original data */ if(!encode_failed && (res_buff_size < data_in_size + 1)) { CompressHeader header = { - .is_compressed = 0x01, .reserved = 0x00, .compressed_buff_size = res_buff_size}; + .is_compressed = 0x01, + .reserved = 0x00, + .compressed_buff_size = res_buff_size - sizeof(CompressHeader)}; memcpy(data_out, &header, sizeof(header)); *data_res_size = res_buff_size; } else if(data_out_size > data_in_size) { @@ -183,22 +179,21 @@ bool compress_encode( *data_res_size = 0; result = false; } - compress_reset(compress); - + heatshrink_encoder_reset(encoder); return result; } -bool compress_decode( - Compress* compress, - uint8_t* data_in, +static bool compress_decode_internal( + heatshrink_decoder* decoder, + const uint8_t* data_in, size_t data_in_size, uint8_t* data_out, size_t data_out_size, size_t* data_res_size) { - furi_assert(compress); - furi_assert(data_in); - furi_assert(data_out); - furi_assert(data_res_size); + furi_check(decoder); + furi_check(data_in); + furi_check(data_out); + furi_check(data_res_size); bool result = false; bool decode_failed = false; @@ -211,12 +206,15 @@ bool compress_decode( CompressHeader* header = (CompressHeader*)data_in; if(header->is_compressed) { - // Sink data to decoding buffer + /* Sink data to decoding buffer */ size_t compressed_size = header->compressed_buff_size; - size_t sunk = sizeof(CompressHeader); + size_t sunk = 0; while(sunk < compressed_size && !decode_failed) { sink_res = heatshrink_decoder_sink( - compress->decoder, &data_in[sunk], compressed_size - sunk, &sink_size); + decoder, + (uint8_t*)&data_in[sizeof(CompressHeader) + sunk], + compressed_size - sunk, + &sink_size); if(sink_res < 0) { decode_failed = true; break; @@ -224,25 +222,28 @@ bool compress_decode( sunk += sink_size; do { poll_res = heatshrink_decoder_poll( - compress->decoder, &data_out[res_buff_size], data_out_size, &poll_size); - if(poll_res < 0) { + decoder, &data_out[res_buff_size], data_out_size - res_buff_size, &poll_size); + if((poll_res < 0) || ((data_out_size - res_buff_size) == 0)) { decode_failed = true; break; } res_buff_size += poll_size; } while(poll_res == HSDR_POLL_MORE); } - // Notify sinking complete and poll decoded data + /* Notify sinking complete and poll decoded data */ if(!decode_failed) { - finish_res = heatshrink_decoder_finish(compress->decoder); + finish_res = heatshrink_decoder_finish(decoder); if(finish_res < 0) { decode_failed = true; } else { do { poll_res = heatshrink_decoder_poll( - compress->decoder, &data_out[res_buff_size], data_out_size, &poll_size); + decoder, + &data_out[res_buff_size], + data_out_size - res_buff_size, + &poll_size); res_buff_size += poll_size; - finish_res = heatshrink_decoder_finish(compress->decoder); + finish_res = heatshrink_decoder_finish(decoder); } while(finish_res != HSDR_FINISH_DONE); } } @@ -253,9 +254,31 @@ bool compress_decode( *data_res_size = data_in_size - 1; result = true; } else { + /* Not enough space in output buffer */ result = false; } - compress_reset(compress); - + heatshrink_decoder_reset(decoder); return result; } + +bool compress_encode( + Compress* compress, + uint8_t* data_in, + size_t data_in_size, + uint8_t* data_out, + size_t data_out_size, + size_t* data_res_size) { + return compress_encode_internal( + compress->encoder, data_in, data_in_size, data_out, data_out_size, data_res_size); +} + +bool compress_decode( + Compress* compress, + uint8_t* data_in, + size_t data_in_size, + uint8_t* data_out, + size_t data_out_size, + size_t* data_res_size) { + return compress_decode_internal( + compress->decoder, data_in, data_in_size, data_out, data_out_size, data_res_size); +} diff --git a/lib/toolbox/compress.h b/lib/toolbox/compress.h index f844802ec5..f08e175840 100644 --- a/lib/toolbox/compress.h +++ b/lib/toolbox/compress.h @@ -16,10 +16,14 @@ extern "C" { typedef struct CompressIcon CompressIcon; /** Initialize icon compressor + * + * @param[in] decode_buf_size The icon buffer size for decoding. Ensure that + * it's big enough for any icons that you are + * planning to decode with it. * * @return Compress Icon instance */ -CompressIcon* compress_icon_alloc(void); +CompressIcon* compress_icon_alloc(size_t decode_buf_size); /** Free icon compressor * @@ -29,14 +33,16 @@ void compress_icon_free(CompressIcon* instance); /** Decompress icon * - * @warning decoded_buff pointer set by this function is valid till next + * @warning output pointer set by this function is valid till next * `compress_icon_decode` or `compress_icon_free` call * - * @param instance The Compress Icon instance - * @param icon_data pointer to icon data - * @param[in] decoded_buff pointer to decoded buffer pointer + * @param instance The Compress Icon instance + * @param icon_data pointer to icon data. + * @param[in] output pointer to decoded buffer pointer. Data in buffer is + * valid till next call. If icon data was not compressed, + * pointer within icon_data is returned */ -void compress_icon_decode(CompressIcon* instance, const uint8_t* icon_data, uint8_t** decoded_buff); +void compress_icon_decode(CompressIcon* instance, const uint8_t* icon_data, uint8_t** output); /** Compress control structure */ typedef struct Compress Compress; diff --git a/scripts/assets.py b/scripts/assets.py index 711c1b440b..c3d6081c88 100755 --- a/scripts/assets.py +++ b/scripts/assets.py @@ -24,8 +24,8 @@ ICONS_TEMPLATE_C_DATA = "const uint8_t* const {name}[] = {data};\n" ICONS_TEMPLATE_C_ICONS = "const Icon {name} = {{.width={width},.height={height},.frame_count={frame_count},.frame_rate={frame_rate},.frames=_{name}}};\n" -MAX_IMAGE_WIDTH = 128 -MAX_IMAGE_HEIGHT = 64 +MAX_IMAGE_WIDTH = 2**16 - 1 +MAX_IMAGE_HEIGHT = 2**16 - 1 class Main(App): diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index 04aafb1f99..9b3b53b040 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -354,10 +354,10 @@ def _process_ext_apps(self): ).append(app) def get_ext_apps(self): - return self.extapps + return list(self.extapps) def get_incompatible_ext_apps(self): - return self.incompatible_extapps + return list(self.incompatible_extapps) def _check_conflicts(self): conflicts = [] @@ -402,14 +402,30 @@ def _check_target_match(self): def _group_plugins(self): known_extensions = self.get_apps_of_type(FlipperAppType.PLUGIN, all_known=True) for extension_app in known_extensions: + keep_app = False for parent_app_id in extension_app.requires: try: parent_app = self.appmgr.get(parent_app_id) parent_app._plugins.append(extension_app) + + if ( + parent_app.apptype in self.BUILTIN_APP_TYPES + and parent_app_id in self.appnames + ) or parent_app.apptype not in self.BUILTIN_APP_TYPES: + keep_app |= True + except FlipperManifestException: self._writer( f"Module {extension_app.appid} has unknown parent {parent_app_id}" ) + keep_app = True + # Debug output for plugin parentage + # print( + # f"Module {extension_app.appid} has parents {extension_app.requires} keep={keep_app}" + # ) + if not keep_app and extension_app in self.extapps: + # print(f"Excluding plugin {extension_app.appid}") + self.extapps.remove(extension_app) def get_apps_cdefs(self): cdefs = set() @@ -435,9 +451,11 @@ def get_apps_of_type(self, apptype: FlipperAppType, all_known: bool = False): return sorted( filter( lambda app: app.apptype == apptype, - self.appmgr.known_apps.values() - if all_known - else map(self.appmgr.get, self.appnames), + ( + self.appmgr.known_apps.values() + if all_known + else map(self.appmgr.get, self.appnames) + ), ), key=lambda app: app.order, ) diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index a7914c4f87..540510b0d4 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -461,7 +461,8 @@ def _add_host_app_to_targets(host_app): else: # host app is a built-in app components.add_app(artifacts_app_to_run) - components.extra_launch_args = f"-a {host_app.name}" + if host_app.name: + components.extra_launch_args = f"-a {host_app.name}" else: raise UserError("Host app is unknown") else: diff --git a/scripts/fbt_tools/fbt_sdk.py b/scripts/fbt_tools/fbt_sdk.py index a3f7faa577..ef2aa91461 100644 --- a/scripts/fbt_tools/fbt_sdk.py +++ b/scripts/fbt_tools/fbt_sdk.py @@ -198,6 +198,7 @@ def gen_sdk_data(sdk_cache: SdkCache): api_def.extend( (f"#include <{h.name}>" for h in sdk_cache.get_headers()), ) + api_def.append('#pragma GCC diagnostic ignored "-Wdeprecated-declarations"') api_def.append(f"const int elf_api_version = {sdk_cache.version.as_int()};") diff --git a/scripts/map_analyse_upload.py b/scripts/map_analyse_upload.py new file mode 100755 index 0000000000..38d9618796 --- /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 a4c9ed5c78..0000000000 --- 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 1efc4fe82f..0000000000 --- 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 3cee044aa7..04ceb928cb 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,6 +1,7 @@ entry,status,name,type,params -Version,+,61.2,, +Version,+,62.3,, Header,+,applications/services/bt/bt_service/bt.h,, +Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, Header,+,applications/services/dialogs/dialogs.h,, @@ -27,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,, @@ -155,10 +157,12 @@ Header,+,lib/toolbox/hex.h,, Header,+,lib/toolbox/keys_dict.h,, Header,+,lib/toolbox/manchester_decoder.h,, Header,+,lib/toolbox/manchester_encoder.h,, +Header,+,lib/toolbox/md5_calc.h,, Header,+,lib/toolbox/name_generator.h,, Header,+,lib/toolbox/path.h,, Header,+,lib/toolbox/pretty_format.h,, Header,+,lib/toolbox/protocols/protocol_dict.h,, +Header,+,lib/toolbox/pulse_protocols/pulse_glue.h,, Header,+,lib/toolbox/saved_struct.h,, Header,+,lib/toolbox/simple_array.h,, Header,+,lib/toolbox/stream/buffered_file_stream.h,, @@ -167,6 +171,7 @@ Header,+,lib/toolbox/stream/stream.h,, Header,+,lib/toolbox/stream/string_stream.h,, Header,+,lib/toolbox/tar/tar_archive.h,, Header,+,lib/toolbox/value_index.h,, +Header,+,lib/toolbox/varint.h,, Header,+,lib/toolbox/version.h,, Header,+,targets/f18/furi_hal/furi_hal_resources.h,, Header,+,targets/f18/furi_hal/furi_hal_spi_config.h,, @@ -679,8 +684,15 @@ Function,+,ble_svc_serial_update_tx,_Bool,"BleServiceSerial*, uint8_t*, uint16_t Function,-,bsearch,void*,"const void*, const void*, size_t, size_t, __compar_fn_t" Function,+,bt_disconnect,void,Bt* Function,+,bt_forget_bonded_devices,void,Bt* +Function,+,bt_keys_storage_alloc,BtKeysStorage*,const char* +Function,+,bt_keys_storage_delete,_Bool,BtKeysStorage* +Function,+,bt_keys_storage_free,void,BtKeysStorage* +Function,+,bt_keys_storage_load,_Bool,BtKeysStorage* Function,+,bt_keys_storage_set_default_path,void,Bt* +Function,+,bt_keys_storage_set_file_path,void,"BtKeysStorage*, const char*" +Function,+,bt_keys_storage_set_ram_params,void,"BtKeysStorage*, uint8_t*, uint16_t" Function,+,bt_keys_storage_set_storage_path,void,"Bt*, const char*" +Function,+,bt_keys_storage_update,_Bool,"BtKeysStorage*, uint8_t*, uint32_t" Function,+,bt_profile_restore_default,_Bool,Bt* Function,+,bt_profile_start,FuriHalBleProfileBase*,"Bt*, const FuriHalBleProfileTemplate*, FuriHalBleProfileParams" Function,+,bt_set_status_changed_callback,void,"Bt*, BtStatusChangedCallback, void*" @@ -772,7 +784,7 @@ Function,+,compress_alloc,Compress*,uint16_t Function,+,compress_decode,_Bool,"Compress*, uint8_t*, size_t, uint8_t*, size_t, size_t*" Function,+,compress_encode,_Bool,"Compress*, uint8_t*, size_t, uint8_t*, size_t, size_t*" Function,+,compress_free,void,Compress* -Function,+,compress_icon_alloc,CompressIcon*, +Function,+,compress_icon_alloc,CompressIcon*,size_t Function,+,compress_icon_decode,void,"CompressIcon*, const uint8_t*, uint8_t**" Function,+,compress_icon_free,void,CompressIcon* Function,-,copysign,double,"double, double" @@ -1489,7 +1501,7 @@ Function,+,furi_mutex_free,void,FuriMutex* Function,+,furi_mutex_get_owner,FuriThreadId,FuriMutex* Function,+,furi_mutex_release,FuriStatus,FuriMutex* Function,+,furi_pubsub_alloc,FuriPubSub*, -Function,-,furi_pubsub_free,void,FuriPubSub* +Function,+,furi_pubsub_free,void,FuriPubSub* Function,+,furi_pubsub_publish,void,"FuriPubSub*, void*" Function,+,furi_pubsub_subscribe,FuriPubSubSubscription*,"FuriPubSub*, FuriPubSubCallback, void*" Function,+,furi_pubsub_unsubscribe,void,"FuriPubSub*, FuriPubSubSubscription*" @@ -1674,8 +1686,10 @@ Function,+,icon_animation_set_update_callback,void,"IconAnimation*, IconAnimatio Function,+,icon_animation_start,void,IconAnimation* Function,+,icon_animation_stop,void,IconAnimation* Function,+,icon_get_data,const uint8_t*,const Icon* -Function,+,icon_get_height,uint8_t,const Icon* -Function,+,icon_get_width,uint8_t,const Icon* +Function,+,icon_get_frame_count,uint32_t,const Icon* +Function,+,icon_get_frame_data,const uint8_t*,"const Icon*, uint32_t" +Function,+,icon_get_height,uint16_t,const Icon* +Function,+,icon_get_width,uint16_t,const Icon* Function,-,ilogb,int,double Function,-,ilogbf,int,float Function,-,ilogbl,int,long double @@ -1979,6 +1993,8 @@ Function,-,mbedtls_sha256_update,int,"mbedtls_sha256_context*, const unsigned ch Function,-,mblen,int,"const char*, size_t" Function,-,mbstowcs,size_t,"wchar_t*, const char*, size_t" Function,-,mbtowc,int,"wchar_t*, const char*, size_t" +Function,+,md5_calc_file,_Bool,"File*, const char*, unsigned char[16], FS_Error*" +Function,+,md5_string_calc_file,_Bool,"File*, const char*, FuriString*, FS_Error*" Function,-,memccpy,void*,"void*, const void*, int, size_t" Function,+,memchr,void*,"const void*, int, size_t" Function,+,memcmp,int,"const void*, const void*, size_t" @@ -2253,6 +2269,11 @@ Function,+,protocol_dict_render_brief_data,void,"ProtocolDict*, FuriString*, siz Function,+,protocol_dict_render_data,void,"ProtocolDict*, FuriString*, size_t" Function,+,protocol_dict_render_uid,void,"ProtocolDict*, FuriString*, size_t" Function,+,protocol_dict_set_data,void,"ProtocolDict*, size_t, const uint8_t*, size_t" +Function,+,pulse_glue_alloc,PulseGlue*, +Function,+,pulse_glue_free,void,PulseGlue* +Function,+,pulse_glue_pop,void,"PulseGlue*, uint32_t*, uint32_t*" +Function,+,pulse_glue_push,_Bool,"PulseGlue*, _Bool, uint32_t" +Function,+,pulse_glue_reset,void,PulseGlue* Function,-,pulse_reader_alloc,PulseReader*,"const GpioPin*, uint32_t" Function,-,pulse_reader_free,void,PulseReader* Function,-,pulse_reader_receive,uint32_t,"PulseReader*, int" @@ -2554,7 +2575,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*" @@ -2639,6 +2662,12 @@ Function,+,variable_item_list_set_selected_item,void,"VariableItemList*, uint8_t Function,+,variable_item_set_current_value_index,void,"VariableItem*, uint8_t" Function,+,variable_item_set_current_value_text,void,"VariableItem*, const char*" Function,+,variable_item_set_values_count,void,"VariableItem*, uint8_t" +Function,+,varint_int32_length,size_t,int32_t +Function,+,varint_int32_pack,size_t,"int32_t, uint8_t*" +Function,+,varint_int32_unpack,size_t,"int32_t*, const uint8_t*, size_t" +Function,+,varint_uint32_length,size_t,uint32_t +Function,+,varint_uint32_pack,size_t,"uint32_t, uint8_t*" +Function,+,varint_uint32_unpack,size_t,"uint32_t*, const uint8_t*, size_t" Function,-,vasiprintf,int,"char**, const char*, __gnuc_va_list" Function,-,vasniprintf,char*,"char*, size_t*, const char*, __gnuc_va_list" Function,-,vasnprintf,char*,"char*, size_t*, const char*, __gnuc_va_list" @@ -2681,6 +2710,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/f18/furi_hal/furi_hal.c b/targets/f18/furi_hal/furi_hal.c index 6cfc939b8c..247c2ee2d5 100644 --- a/targets/f18/furi_hal/furi_hal.c +++ b/targets/f18/furi_hal/furi_hal.c @@ -17,6 +17,7 @@ void furi_hal_init_early(void) { furi_hal_i2c_init_early(); furi_hal_light_init(); furi_hal_rtc_init_early(); + furi_hal_version_init(); } void furi_hal_deinit_early(void) { @@ -39,7 +40,6 @@ void furi_hal_init(void) { furi_hal_interrupt_init(); furi_hal_flash_init(); furi_hal_resources_init(); - furi_hal_version_init(); furi_hal_spi_config_init(); furi_hal_spi_dma_init(); furi_hal_speaker_init(); diff --git a/targets/f18/target.json b/targets/f18/target.json index 43e9254cd4..229ec0a7ad 100644 --- a/targets/f18/target.json +++ b/targets/f18/target.json @@ -30,7 +30,6 @@ "mjs", "mbedtls", "flipper_application", - "toolbox", "u8g2", "nanopb", "update_util", @@ -38,6 +37,7 @@ "flipperformat", "flipper18", "bit_lib", + "toolbox", "datetime" ], "excluded_sources": [ diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 2181661002..9071f63b88 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,7 +1,8 @@ entry,status,name,type,params -Version,+,61.2,, +Version,+,62.3,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, +Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, Header,+,applications/services/dialogs/dialogs.h,, @@ -28,6 +29,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,, @@ -159,6 +161,7 @@ Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.h,, Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h,, Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h,, Header,+,lib/nfc/protocols/slix/slix.h,, +Header,+,lib/nfc/protocols/slix/slix_poller.h,, Header,+,lib/nfc/protocols/st25tb/st25tb.h,, Header,+,lib/nfc/protocols/st25tb/st25tb_poller.h,, Header,+,lib/nfc/protocols/st25tb/st25tb_poller_sync.h,, @@ -225,10 +228,12 @@ Header,+,lib/toolbox/hex.h,, Header,+,lib/toolbox/keys_dict.h,, Header,+,lib/toolbox/manchester_decoder.h,, Header,+,lib/toolbox/manchester_encoder.h,, +Header,+,lib/toolbox/md5_calc.h,, Header,+,lib/toolbox/name_generator.h,, Header,+,lib/toolbox/path.h,, Header,+,lib/toolbox/pretty_format.h,, Header,+,lib/toolbox/protocols/protocol_dict.h,, +Header,+,lib/toolbox/pulse_protocols/pulse_glue.h,, Header,+,lib/toolbox/saved_struct.h,, Header,+,lib/toolbox/simple_array.h,, Header,+,lib/toolbox/stream/buffered_file_stream.h,, @@ -237,6 +242,7 @@ Header,+,lib/toolbox/stream/stream.h,, Header,+,lib/toolbox/stream/string_stream.h,, Header,+,lib/toolbox/tar/tar_archive.h,, Header,+,lib/toolbox/value_index.h,, +Header,+,lib/toolbox/varint.h,, Header,+,lib/toolbox/version.h,, Header,+,targets/f7/ble_glue/furi_ble/event_dispatcher.h,, Header,+,targets/f7/ble_glue/furi_ble/gatt.h,, @@ -755,9 +761,16 @@ Function,-,bsearch,void*,"const void*, const void*, size_t, size_t, __compar_fn_ Function,-,bt_close_rpc_connection,void,Bt* Function,+,bt_disconnect,void,Bt* Function,+,bt_forget_bonded_devices,void,Bt* +Function,+,bt_keys_storage_alloc,BtKeysStorage*,const char* +Function,+,bt_keys_storage_delete,_Bool,BtKeysStorage* +Function,+,bt_keys_storage_free,void,BtKeysStorage* +Function,+,bt_keys_storage_load,_Bool,BtKeysStorage* Function,+,bt_keys_storage_set_default_path,void,Bt* +Function,+,bt_keys_storage_set_file_path,void,"BtKeysStorage*, const char*" +Function,+,bt_keys_storage_set_ram_params,void,"BtKeysStorage*, uint8_t*, uint16_t" Function,+,bt_keys_storage_set_storage_path,void,"Bt*, const char*" Function,-,bt_open_rpc_connection,void,Bt* +Function,+,bt_keys_storage_update,_Bool,"BtKeysStorage*, uint8_t*, uint32_t" Function,+,bt_profile_restore_default,_Bool,Bt* Function,+,bt_profile_start,FuriHalBleProfileBase*,"Bt*, const FuriHalBleProfileTemplate*, FuriHalBleProfileParams" Function,+,bt_remote_rssi,_Bool,"Bt*, uint8_t*" @@ -853,7 +866,7 @@ Function,+,compress_alloc,Compress*,uint16_t Function,+,compress_decode,_Bool,"Compress*, uint8_t*, size_t, uint8_t*, size_t, size_t*" Function,+,compress_encode,_Bool,"Compress*, uint8_t*, size_t, uint8_t*, size_t, size_t*" Function,+,compress_free,void,Compress* -Function,+,compress_icon_alloc,CompressIcon*, +Function,+,compress_icon_alloc,CompressIcon*,size_t Function,+,compress_icon_decode,void,"CompressIcon*, const uint8_t*, uint8_t**" Function,+,compress_icon_free,void,CompressIcon* Function,-,copysign,double,"double, double" @@ -1736,7 +1749,7 @@ Function,+,furi_mutex_free,void,FuriMutex* Function,+,furi_mutex_get_owner,FuriThreadId,FuriMutex* Function,+,furi_mutex_release,FuriStatus,FuriMutex* Function,+,furi_pubsub_alloc,FuriPubSub*, -Function,-,furi_pubsub_free,void,FuriPubSub* +Function,+,furi_pubsub_free,void,FuriPubSub* Function,+,furi_pubsub_publish,void,"FuriPubSub*, void*" Function,+,furi_pubsub_subscribe,FuriPubSubSubscription*,"FuriPubSub*, FuriPubSubCallback, void*" Function,+,furi_pubsub_unsubscribe,void,"FuriPubSub*, FuriPubSubSubscription*" @@ -1961,8 +1974,10 @@ Function,+,icon_animation_set_update_callback,void,"IconAnimation*, IconAnimatio Function,+,icon_animation_start,void,IconAnimation* Function,+,icon_animation_stop,void,IconAnimation* Function,+,icon_get_data,const uint8_t*,const Icon* -Function,+,icon_get_height,uint8_t,const Icon* -Function,+,icon_get_width,uint8_t,const Icon* +Function,+,icon_get_frame_count,uint32_t,const Icon* +Function,+,icon_get_frame_data,const uint8_t*,"const Icon*, uint32_t" +Function,+,icon_get_height,uint16_t,const Icon* +Function,+,icon_get_width,uint16_t,const Icon* Function,-,ilogb,int,double Function,-,ilogbf,int,float Function,-,ilogbl,int,long double @@ -2433,6 +2448,8 @@ Function,-,mbedtls_sha256_update,int,"mbedtls_sha256_context*, const unsigned ch Function,-,mblen,int,"const char*, size_t" Function,-,mbstowcs,size_t,"wchar_t*, const char*, size_t" Function,-,mbtowc,int,"wchar_t*, const char*, size_t" +Function,+,md5_calc_file,_Bool,"File*, const char*, unsigned char[16], FS_Error*" +Function,+,md5_string_calc_file,_Bool,"File*, const char*, FuriString*, FS_Error*" Function,-,memccpy,void*,"void*, const void*, int, size_t" Function,+,memchr,void*,"const void*, int, size_t" Function,+,memcmp,int,"const void*, const void*, size_t" @@ -2900,6 +2917,11 @@ Function,+,protocol_dict_render_brief_data,void,"ProtocolDict*, FuriString*, siz Function,+,protocol_dict_render_data,void,"ProtocolDict*, FuriString*, size_t" Function,+,protocol_dict_render_uid,void,"ProtocolDict*, FuriString*, size_t" Function,+,protocol_dict_set_data,void,"ProtocolDict*, size_t, const uint8_t*, size_t" +Function,+,pulse_glue_alloc,PulseGlue*, +Function,+,pulse_glue_free,void,PulseGlue* +Function,+,pulse_glue_pop,void,"PulseGlue*, uint32_t*, uint32_t*" +Function,+,pulse_glue_push,_Bool,"PulseGlue*, _Bool, uint32_t" +Function,+,pulse_glue_reset,void,PulseGlue* Function,-,pulse_reader_alloc,PulseReader*,"const GpioPin*, uint32_t" Function,-,pulse_reader_free,void,PulseReader* Function,-,pulse_reader_receive,uint32_t,"PulseReader*, int" @@ -3040,6 +3062,11 @@ Function,+,slix_is_counter_increment_protected,_Bool,const SlixData* Function,+,slix_is_equal,_Bool,"const SlixData*, const SlixData*" Function,+,slix_is_privacy_mode,_Bool,const SlixData* Function,+,slix_load,_Bool,"SlixData*, FlipperFormat*, uint32_t" +Function,+,slix_poller_get_nxp_system_info,SlixError,"SlixPoller*, SlixSystemInfo*" +Function,+,slix_poller_get_random_number,SlixError,"SlixPoller*, SlixRandomNumber*" +Function,+,slix_poller_read_signature,SlixError,"SlixPoller*, SlixSignature*" +Function,+,slix_poller_send_frame,SlixError,"SlixPoller*, const BitBuffer*, BitBuffer*, uint32_t" +Function,+,slix_poller_set_password,SlixError,"SlixPoller*, SlixPasswordType, SlixPassword, SlixRandomNumber" Function,+,slix_reset,void,SlixData* Function,+,slix_save,_Bool,"const SlixData*, FlipperFormat*" Function,+,slix_set_uid,_Bool,"SlixData*, const uint8_t*, size_t" @@ -3426,7 +3453,9 @@ Function,+,subghz_worker_stop,void,SubGhzWorker* Function,+,submenu_add_item,void,"Submenu*, const char*, uint32_t, SubmenuItemCallback, void*" Function,+,submenu_add_lockable_item,void,"Submenu*, const char*, uint32_t, SubmenuItemCallback, void*, _Bool, const char*" 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*" @@ -3518,6 +3547,12 @@ Function,+,variable_item_set_current_value_index,void,"VariableItem*, uint8_t" Function,+,variable_item_set_current_value_text,void,"VariableItem*, const char*" Function,+,variable_item_set_locked,void,"VariableItem*, _Bool, const char*" Function,+,variable_item_set_values_count,void,"VariableItem*, uint8_t" +Function,+,varint_int32_length,size_t,int32_t +Function,+,varint_int32_pack,size_t,"int32_t, uint8_t*" +Function,+,varint_int32_unpack,size_t,"int32_t*, const uint8_t*, size_t" +Function,+,varint_uint32_length,size_t,uint32_t +Function,+,varint_uint32_pack,size_t,"uint32_t, uint8_t*" +Function,+,varint_uint32_unpack,size_t,"uint32_t*, const uint8_t*, size_t" Function,-,vasiprintf,int,"char**, const char*, __gnuc_va_list" Function,-,vasniprintf,char*,"char*, size_t*, const char*, __gnuc_va_list" Function,-,vasnprintf,char*,"char*, size_t*, const char*, __gnuc_va_list" @@ -3562,6 +3597,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/ble_glue/gap.c b/targets/f7/ble_glue/gap.c index 5a95cff263..bdef259e33 100644 --- a/targets/f7/ble_glue/gap.c +++ b/targets/f7/ble_glue/gap.c @@ -40,6 +40,8 @@ typedef struct { FuriThread* thread; FuriMessageQueue* command_queue; bool enable_adv; + bool is_secure; + uint8_t negotiation_round; } Gap; typedef enum { @@ -87,17 +89,46 @@ static void gap_verify_connection_parameters(Gap* gap) { // Send connection parameters request update if necessary GapConnectionParamsRequest* params = &gap->config->conn_param; - if(params->conn_int_min > gap->connection_params.conn_interval || - params->conn_int_max < gap->connection_params.conn_interval) { - FURI_LOG_W(TAG, "Unsupported connection interval. Request connection parameters update"); + + // Desired max connection interval depends on how many negotiation rounds we had in the past + // In the first negotiation round we want connection interval to be minimum + // If platform disagree then we request wider range + uint16_t connection_interval_max = gap->negotiation_round ? params->conn_int_max : + params->conn_int_min; + + // We do care about lower connection interval bound a lot: if it's lower than 30ms 2nd core will not allow us to use flash controller + bool negotiation_failed = params->conn_int_min > gap->connection_params.conn_interval; + + // We don't care about upper bound till connection become secure + if(gap->is_secure) { + negotiation_failed |= connection_interval_max < gap->connection_params.conn_interval; + } + + if(negotiation_failed) { + FURI_LOG_W( + TAG, + "Connection interval doesn't suite us. Trying to negotiate, round %u", + gap->negotiation_round + 1); if(aci_l2cap_connection_parameter_update_req( gap->service.connection_handle, params->conn_int_min, - params->conn_int_max, + connection_interval_max, gap->connection_params.slave_latency, gap->connection_params.supervisor_timeout)) { FURI_LOG_E(TAG, "Failed to request connection parameters update"); + // The other side is not in the mood + // But we are open to try it again + gap->negotiation_round = 0; + } else { + gap->negotiation_round++; } + } else { + FURI_LOG_I( + TAG, + "Connection interval suits us. Spent %u rounds to negotiate", + gap->negotiation_round); + // Looks like the other side is open to negotiation + gap->negotiation_round = 0; } } @@ -112,9 +143,9 @@ BleEventFlowStatus ble_event_app_notification(void* pckt) { event_pckt = (hci_event_pckt*)((hci_uart_pckt*)pckt)->data; - if(gap) { - furi_mutex_acquire(gap->state_mutex, FuriWaitForever); - } + furi_check(gap); + furi_mutex_acquire(gap->state_mutex, FuriWaitForever); + switch(event_pckt->evt) { case HCI_DISCONNECTION_COMPLETE_EVT_CODE: { hci_disconnection_complete_event_rp0* disconnection_complete_event = @@ -125,6 +156,8 @@ BleEventFlowStatus ble_event_app_notification(void* pckt) { FURI_LOG_I( TAG, "Disconnect from client. Reason: %02X", disconnection_complete_event->Reason); } + gap->is_secure = false; + gap->negotiation_round = 0; // Enterprise sleep furi_delay_us(666 + 666); if(gap->enable_adv) { @@ -232,6 +265,7 @@ BleEventFlowStatus ble_event_app_notification(void* pckt) { case ACI_GAP_SLAVE_SECURITY_INITIATED_VSEVT_CODE: FURI_LOG_D(TAG, "Slave security initiated"); + gap->is_secure = true; break; case ACI_GAP_BOND_LOST_VSEVT_CODE: @@ -293,9 +327,9 @@ BleEventFlowStatus ble_event_app_notification(void* pckt) { default: break; } - if(gap) { - furi_mutex_release(gap->state_mutex); - } + + furi_mutex_release(gap->state_mutex); + return BleEventFlowEnable; } @@ -539,6 +573,10 @@ bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) { gap->thread = furi_thread_alloc_ex("BleGapDriver", 1024, gap_app, gap); furi_thread_start(gap->thread); + // Set initial state + gap->is_secure = false; + gap->negotiation_round = 0; + uint8_t adv_service_uid[2]; gap->service.adv_svc_uuid_len = 1; adv_service_uid[0] = gap->config->adv_service_uuid & 0xff; diff --git a/targets/f7/ble_glue/profiles/serial_profile.c b/targets/f7/ble_glue/profiles/serial_profile.c index 165b813307..118a76e8c3 100644 --- a/targets/f7/ble_glue/profiles/serial_profile.c +++ b/targets/f7/ble_glue/profiles/serial_profile.c @@ -47,7 +47,7 @@ static GapConfig serial_template_config = { .pairing_method = GapPairingPinCodeShow, .conn_param = { .conn_int_min = 0x18, // AN5289: 4.7, we need at least 25ms + advertisement, which is 30 ms - .conn_int_max = 0x18, // 30 ms + .conn_int_max = 0x24, // 45 ms .slave_latency = 0, .supervisor_timeout = 0, }}; diff --git a/targets/f7/furi_hal/furi_hal.c b/targets/f7/furi_hal/furi_hal.c index 3686658f7e..7e713a08d1 100644 --- a/targets/f7/furi_hal/furi_hal.c +++ b/targets/f7/furi_hal/furi_hal.c @@ -17,6 +17,7 @@ void furi_hal_init_early(void) { furi_hal_i2c_init_early(); furi_hal_light_init(); furi_hal_rtc_init_early(); + furi_hal_version_init(); } void furi_hal_deinit_early(void) { @@ -39,7 +40,6 @@ void furi_hal_init(void) { furi_hal_interrupt_init(); furi_hal_flash_init(); furi_hal_resources_init(); - furi_hal_version_init(); furi_hal_spi_config_init(); furi_hal_spi_dma_init(); furi_hal_ibutton_init(); diff --git a/targets/f7/furi_hal/furi_hal_clock.c b/targets/f7/furi_hal/furi_hal_clock.c index 9184fa7157..ca4c7ad4d7 100644 --- a/targets/f7/furi_hal/furi_hal_clock.c +++ b/targets/f7/furi_hal/furi_hal_clock.c @@ -133,7 +133,7 @@ void furi_hal_clock_switch_hse2hsi(void) { ; LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_HSI); - furi_assert(LL_RCC_GetSMPSClockSelection() == LL_RCC_SMPS_CLKSOURCE_HSI); + furi_check(LL_RCC_GetSMPSClockSelection() == LL_RCC_SMPS_CLKSOURCE_HSI); while(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_HSI) ; @@ -170,7 +170,7 @@ void furi_hal_clock_switch_hsi2hse(void) { } bool furi_hal_clock_switch_hse2pll(void) { - furi_assert(LL_RCC_GetSysClkSource() == LL_RCC_SYS_CLKSOURCE_STATUS_HSE); + furi_check(LL_RCC_GetSysClkSource() == LL_RCC_SYS_CLKSOURCE_STATUS_HSE); LL_RCC_PLL_Enable(); LL_RCC_PLLSAI1_Enable(); @@ -180,12 +180,13 @@ bool furi_hal_clock_switch_hse2pll(void) { while(!LL_RCC_PLLSAI1_IsReady()) ; - if(SHCI_C2_SetSystemClock(SET_SYSTEM_CLOCK_HSE_TO_PLL) != SHCI_Success) { + // This API returns garbage if stack version < 1.20.0 + SHCI_C2_SetSystemClock(SET_SYSTEM_CLOCK_HSE_TO_PLL); + // So we'll check results by asking hardware directly + if(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_PLL) { return false; } - furi_check(LL_RCC_GetSysClkSource() == LL_RCC_SYS_CLKSOURCE_STATUS_PLL); - LL_SetSystemCoreClock(CPU_CLOCK_PLL_HZ); SysTick->LOAD = (uint32_t)((SystemCoreClock / 1000) - 1UL); @@ -193,18 +194,19 @@ bool furi_hal_clock_switch_hse2pll(void) { } bool furi_hal_clock_switch_pll2hse(void) { - furi_assert(LL_RCC_GetSysClkSource() == LL_RCC_SYS_CLKSOURCE_STATUS_PLL); + furi_check(LL_RCC_GetSysClkSource() == LL_RCC_SYS_CLKSOURCE_STATUS_PLL); LL_RCC_HSE_Enable(); while(!LL_RCC_HSE_IsReady()) ; - if(SHCI_C2_SetSystemClock(SET_SYSTEM_CLOCK_PLL_ON_TO_HSE) != SHCI_Success) { + // This API returns garbage if stack version < 1.20.0 + SHCI_C2_SetSystemClock(SET_SYSTEM_CLOCK_PLL_ON_TO_HSE); + // So we'll check results by asking hardware directly + if(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_HSE) { return false; } - furi_check(LL_RCC_GetSysClkSource() == LL_RCC_SYS_CLKSOURCE_STATUS_HSE); - LL_SetSystemCoreClock(CPU_CLOCK_HSE_HZ); SysTick->LOAD = (uint32_t)((SystemCoreClock / 1000) - 1UL); diff --git a/targets/f7/furi_hal/furi_hal_flash.c b/targets/f7/furi_hal/furi_hal_flash.c index 63932ac613..cde9f9a7e0 100644 --- a/targets/f7/furi_hal/furi_hal_flash.c +++ b/targets/f7/furi_hal/furi_hal_flash.c @@ -16,6 +16,11 @@ #define TAG "FuriHalFlash" +#ifdef FLASH_OP_DEBUG +#undef FURI_LOG_T +#define FURI_LOG_T(...) +#endif + #define FURI_HAL_CRITICAL_MSG "Critical flash operation fail" #define FURI_HAL_FLASH_READ_BLOCK (8U) #define FURI_HAL_FLASH_WRITE_BLOCK (8U) @@ -148,9 +153,8 @@ static void furi_hal_flash_begin_with_core2(bool erase_flag) { /* Erase activity notification */ if(erase_flag) SHCI_C2_FLASH_EraseActivity(ERASE_ACTIVITY_ON); - /* 64mHz 5us core2 flag protection */ - for(volatile uint32_t i = 0; i < 35; i++) - ; + /* 5us core2 flag protection */ + furi_delay_us(5); FuriHalCortexTimer timer = furi_hal_cortex_timer_get(FURI_HAL_FLASH_C2_LOCK_TIMEOUT_MS * 1000); while(true) { @@ -295,6 +299,7 @@ bool furi_hal_flash_wait_last_operation(uint32_t timeout) { } void furi_hal_flash_erase(uint8_t page) { + uint32_t op_stat = DWT->CYCCNT; furi_hal_flash_begin(true); /* Ensure that controller state is valid */ @@ -317,6 +322,12 @@ void furi_hal_flash_erase(uint8_t page) { furi_hal_flush_cache(); furi_hal_flash_end(true); + op_stat = DWT->CYCCNT - op_stat; + FURI_LOG_T( + TAG, + "erase took %lu clocks or %fus", + op_stat, + (double)((float)op_stat / (float)furi_hal_cortex_instructions_per_microsecond())); } static inline void furi_hal_flash_write_dword_internal_nowait(size_t address, uint64_t* data) { @@ -339,6 +350,7 @@ static inline void furi_hal_flash_write_dword_internal(size_t address, uint64_t* } void furi_hal_flash_write_dword(size_t address, uint64_t data) { + uint32_t op_stat = DWT->CYCCNT; furi_hal_flash_begin(false); /* Ensure that controller state is valid */ @@ -361,6 +373,12 @@ void furi_hal_flash_write_dword(size_t address, uint64_t data) { /* Wait for last operation to be completed */ furi_check(furi_hal_flash_wait_last_operation(FURI_HAL_FLASH_TIMEOUT)); + op_stat = DWT->CYCCNT - op_stat; + FURI_LOG_T( + TAG, + "write_dword took %lu clocks or %fus", + op_stat, + (double)((float)op_stat / (float)furi_hal_cortex_instructions_per_microsecond())); } static size_t furi_hal_flash_get_page_address(uint8_t page) { @@ -373,6 +391,7 @@ void furi_hal_flash_program_page(const uint8_t page, const uint8_t* data, uint16 furi_hal_flash_erase(page); + uint32_t op_stat = DWT->CYCCNT; furi_hal_flash_begin(false); furi_check(furi_hal_flash_wait_last_operation(FURI_HAL_FLASH_TIMEOUT)); @@ -432,6 +451,12 @@ void furi_hal_flash_program_page(const uint8_t page, const uint8_t* data, uint16 CLEAR_BIT(FLASH->CR, FLASH_CR_PG); furi_hal_flash_end(false); + op_stat = DWT->CYCCNT - op_stat; + FURI_LOG_T( + TAG, + "program_page took %lu clocks or %fus", + op_stat, + (double)((float)op_stat / (float)furi_hal_cortex_instructions_per_microsecond())); } int16_t furi_hal_flash_get_page_number(size_t address) { diff --git a/targets/f7/target.json b/targets/f7/target.json index 25872198bf..eae92a5cd6 100644 --- a/targets/f7/target.json +++ b/targets/f7/target.json @@ -45,7 +45,6 @@ "mbedtls", "lfrfid", "flipper_application", - "toolbox", "u8g2", "nanopb", "update_util", @@ -53,6 +52,7 @@ "flipperformat", "flipper7", "bit_lib", + "toolbox", "datetime" ] }