diff --git a/.ci_files/rgb.patch b/.ci_files/rgb.patch
index 7108901093..51a305aec3 100644
--- a/.ci_files/rgb.patch
+++ b/.ci_files/rgb.patch
@@ -1,5 +1,5 @@
diff --git a/applications/services/notification/notification_app.c b/applications/services/notification/notification_app.c
-index d4c5b91..8b43599 100644
+index 35d2fe6..1af97e2 100644
--- a/applications/services/notification/notification_app.c
+++ b/applications/services/notification/notification_app.c
@@ -9,6 +9,7 @@
@@ -10,16 +10,16 @@ index d4c5b91..8b43599 100644
#define TAG "NotificationSrv"
-@@ -588,6 +589,7 @@ int32_t notification_srv(void* p) {
+@@ -616,6 +617,7 @@ int32_t notification_srv(void* p) {
break;
case SaveSettingsMessage:
notification_save_settings(app);
+ rgb_backlight_save_settings();
break;
- }
-
+ case LoadSettingsMessage:
+ notification_load_settings(app);
diff --git a/applications/settings/notification_settings/notification_settings_app.c b/applications/settings/notification_settings/notification_settings_app.c
-index 7576dcf..ae022e2 100644
+index 2462b32..8e045ce 100644
--- a/applications/settings/notification_settings/notification_settings_app.c
+++ b/applications/settings/notification_settings/notification_settings_app.c
@@ -3,6 +3,7 @@
diff --git a/.editorconfig b/.editorconfig
index a31ef8e753..1fdc58bc6f 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -8,6 +8,3 @@ charset = utf-8
[*.{cpp,h,c,py,sh}]
indent_style = space
indent_size = 4
-
-[{Makefile,*.mk}]
-indent_size = tab
diff --git a/.gitmodules b/.gitmodules
index df5bf648fa..c9373c0939 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,9 +1,6 @@
[submodule "lib/mlib"]
path = lib/mlib
url = https://github.com/P-p-H-d/mlib.git
-[submodule "lib/littlefs"]
- path = lib/littlefs
- url = https://github.com/littlefs-project/littlefs.git
[submodule "lib/nanopb"]
path = lib/nanopb
url = https://github.com/nanopb/nanopb.git
diff --git a/.pvsoptions b/.pvsoptions
index 8606eef154..4040dcb91a 100644
--- a/.pvsoptions
+++ b/.pvsoptions
@@ -1 +1 @@
---ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/microtar -e lib/mlib -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e lib/mjs -e */arm-none-eabi/*
+--ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/mbedtls -e lib/microtar -e lib/mlib -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e lib/mjs -e */arm-none-eabi/*
diff --git a/.sublime-project b/.sublime-project
index da2ef41a12..0ae007b341 100644
--- a/.sublime-project
+++ b/.sublime-project
@@ -10,10 +10,8 @@
"clangd": {
"enabled": true,
"initializationOptions": {
- // Use with toolchain version 39+
// Set `"binary": "custom",` option in LSP-clangd config to use toolchain clangd
- // "custom_command": ["toolchain/current/bin/clangd"],
-
+ "custom_command": ["toolchain/current/bin/clangd"],
"clangd.compile-commands-dir": "build/latest",
"clangd.header-insertion": "never",
"clangd.query-driver": "**/arm-none-eabi-*",
diff --git a/.vscode/example/settings.json.tmpl b/.vscode/example/settings.json.tmpl
index 5e0da68977..5e5b5dcf41 100644
--- a/.vscode/example/settings.json.tmpl
+++ b/.vscode/example/settings.json.tmpl
@@ -12,7 +12,7 @@
"SConstruct": "python",
"*.fam": "python"
},
- // "clangd.path": "${workspaceFolder}/toolchain/current/bin/clangd@FBT_PLATFORM_EXECUTABLE_EXT@",
+ "clangd.path": "${workspaceFolder}/toolchain/current/bin/clangd@FBT_PLATFORM_EXECUTABLE_EXT@",
"clangd.arguments": [
"--query-driver=**/arm-none-eabi-*",
"--compile-commands-dir=${workspaceFolder}/build/latest",
diff --git a/.vscode/example/tasks.json b/.vscode/example/tasks.json
index c0b9e8867e..681544c321 100644
--- a/.vscode/example/tasks.json
+++ b/.vscode/example/tasks.json
@@ -75,12 +75,6 @@
"type": "shell",
"command": "./fbt updater_all"
},
- {
- "label": "[Debug] Flash (USB, w/o resources)",
- "group": "build",
- "type": "shell",
- "command": "./fbt FORCE=1 flash_usb"
- },
{
"label": "[Release] Flash (USB, w/o resources)",
"group": "build",
@@ -88,16 +82,16 @@
"command": "./fbt COMPACT=1 DEBUG=0 FORCE=1 flash_usb"
},
{
- "label": "[Debug:unit_tests] Flash (USB)",
+ "label": "[Debug] Flash (USB, w/o resources)",
"group": "build",
"type": "shell",
- "command": "./fbt FIRMWARE_APP_SET=unit_tests FORCE=1 flash_usb_full"
+ "command": "./fbt FORCE=1 flash_usb"
},
{
- "label": "[Debug] Flash (USB, with resources)",
+ "label": "[Debug:unit_tests] Flash (USB)",
"group": "build",
"type": "shell",
- "command": "./fbt FORCE=1 flash_usb_full"
+ "command": "./fbt FIRMWARE_APP_SET=unit_tests FORCE=1 flash_usb_full"
},
{
"label": "[Release] Flash (USB, with resources)",
@@ -106,16 +100,16 @@
"command": "./fbt COMPACT=1 DEBUG=0 FORCE=1 flash_usb_full"
},
{
- "label": "[Debug] Create PVS-Studio report",
+ "label": "[Debug] Flash (USB, with resources)",
"group": "build",
"type": "shell",
- "command": "./fbt firmware_pvs"
+ "command": "./fbt FORCE=1 flash_usb_full"
},
{
- "label": "[Debug] Build FAPs",
+ "label": "[Debug] Create PVS-Studio report",
"group": "build",
"type": "shell",
- "command": "./fbt fap_dist"
+ "command": "./fbt firmware_pvs"
},
{
"label": "[Release] Build FAPs",
@@ -124,10 +118,10 @@
"command": "./fbt COMPACT=1 DEBUG=0 fap_dist"
},
{
- "label": "[Debug] Build App",
+ "label": "[Debug] Build FAPs",
"group": "build",
"type": "shell",
- "command": "./fbt build APPSRC=${relativeFileDirname}"
+ "command": "./fbt fap_dist"
},
{
"label": "[Release] Build App",
@@ -136,10 +130,10 @@
"command": "./fbt COMPACT=1 DEBUG=0 build APPSRC=${relativeFileDirname}"
},
{
- "label": "[Debug] Launch App on Flipper",
+ "label": "[Debug] Build App",
"group": "build",
"type": "shell",
- "command": "./fbt launch APPSRC=${relativeFileDirname}"
+ "command": "./fbt build APPSRC=${relativeFileDirname}"
},
{
"label": "[Release] Launch App on Flipper",
@@ -147,6 +141,12 @@
"type": "shell",
"command": "./fbt COMPACT=1 DEBUG=0 launch APPSRC=${relativeFileDirname}"
},
+ {
+ "label": "[Debug] Launch App on Flipper",
+ "group": "build",
+ "type": "shell",
+ "command": "./fbt launch APPSRC=${relativeFileDirname}"
+ },
{
"label": "[Debug] Launch App on Flipper with Serial Console",
"dependsOrder": "sequence",
@@ -157,16 +157,16 @@
]
},
{
- "label": "[Debug] Build and upload all FAPs to Flipper over USB",
+ "label": "[Release] Build and upload all FAPs to Flipper over USB",
"group": "build",
"type": "shell",
- "command": "./fbt fap_deploy"
+ "command": "./fbt COMPACT=1 DEBUG=0 fap_deploy"
},
{
- "label": "[Release] Build and upload all FAPs to Flipper over USB",
+ "label": "[Debug] Build and upload all FAPs to Flipper over USB",
"group": "build",
"type": "shell",
- "command": "./fbt COMPACT=1 DEBUG=0 fap_deploy"
+ "command": "./fbt fap_deploy"
},
{
// Press Ctrl+] to quit
@@ -192,4 +192,4 @@
}
}
]
-}
\ No newline at end of file
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0bd2bf2516..b51403f7c0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,64 +1,39 @@
## Main changes
- SubGHz:
- - **Novoferm** remotes full support
- - Fix Decode scene in RAW files
- - Add manually -> Add Sommer FM238 option for cases when default option doesn't work (named as Sommer fm2)
- - Remove broken preset modulation
- - Normstahl, Sommer, MHouse, Aprimatic -> Fixes for button codes and more in Add manually
- - Custom button improvements for MHouse, Novoferm, Nice Smilo
- - Hormann EcoStar -> Add manually support, and custom button support
- - Hormann HSM 44bit static -> Button code decoding fix
- - Choose RSSI threshold for Hopping mode (by @Willy-JL)
-- NFC:
- - OFW: Ultralight C authentication with des key
- - EMV Transactions less nested, hide if unavailable (by @Willy-JL | PR #771)
- - Update Mifare Classic default keys dict with new keys from proxmark3 repo and UberGuidoZ repo
-- LF RFID:
- - Update T5577 password list (by @korden32 | PR #774)
- - Add DEZ 8 display form for EM4100 (by @korden32 | PR #776 & (#777 by @mishamyte))
-- JS:
- - Refactor widget and keyboard modules, fix crash (by @Willy-JL | PR #770)
- - SubGHz module fixes and improvements (by @Willy-JL)
-* OFW: Infrared: check for negative timings
-* OFW: Fix iButton/LFRFID Add Manually results being discarded
-* OFW: Event Loop Timers
-* OFW: Updater: resource compression
+ - OFW: Added protocol for Dickert MAHS garage door remote control
+ - Fix rare crash when opening Read mode via Frequency analyzer
+ - Refactor frequency analyzer code for better readability (by @derskythe | PR #782)
+- 125kHz RFID:
+ - OFW: Add lfrfid GProxII support
+- NFC:
+ - OFW: Fix plantain balance string
+ - OFW: Now fifo size in ST25 chip is calculated properly
+* Docs: Remove not printable symbols and update docs (by @derskythe | PR #783)
+* OFW: Fix cumulative error in infrared signals
+* OFW: iButton ID writing (Enable ID writing for ds1971 and ds1996)
* Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev)
## Other changes
-* OFW: HID/BLE Keyboard UI refactoring
-* OFW: CCID: Add CCIDWorker
-* OFW: Disabled ISR runtime stats collection for updater builds
-* OFW: VSCode fixes: .gitignore & clangd
-* OFW: ufbt: synced .clang-format rules with main
-* OFW: Code formatting update
-* OFW: scripts: runfap: fixed starting apps with spaces in path
-* OFW: toolchain: v38. clangd as default language server
-* OFW: NFC: ISO15693 Render Typo Fix
-* OFW: tar archive: fix double free
-* OFW: ufbt: added ARGS to commandline parser
-* OFW: lib: sconscript todo cleanup
-* OFW: Intruder animation
-* OFW: Desktop: allow to close blocking bad sd animation
-* OFW: Updater: reset various debug flags on production build flash (was done in same way in UL before)
-* OFW: Fix PVS Warnings
-* OFW: CCID: Improve request and response data handling
-* OFW: Furi: count ISR time. Cli: show ISR time in top.
-* OFW: toolchain: v37
-* OFW: NFC: Cache plugin name not full path, saves some RAM (by @Willy-JL)
-* OFW: copro: bumped to 1.20.0
-* OFW: input_srv: Put input state data on the stack of the service
-* OFW: Coalesce some allocations
-* OFW: updater: slightly smaller image
-* OFW: Updater: Fix double dir cleanup
-* OFW: cli: storage: minor subcommand lookup refactor
-* OFW: LFRFID Securakey: Add Support for RKKTH Plain Text Format
-* OFW: NFC: Add mf_classic_set_sector_trailer_read function
-* OFW: Separate editing and renaming in iButton and LFRFID
-* OFW: New js modules documentation added
-* OFW: Update link to mfkey32
-* OFW: NFC: Desfire Renderer Minor Debug
-* OFW: RPC: Fix input lockup on disconnect
-* OFW: Thread Signals
+* Misc: Fix typo in comment in QueueTools.py (by @eltociear | PR #785)
+* OFW PR 3840: GUI: NumberInput small improvements (by @Willy-JL)
+* OFW PR 3838: SubGhz: Fix RPC status for ButtonRelease event (by @Skorpionm)
+* OFW: scripts: improved size validator for updater image
+* OFW: Desktop: seaprate callbacks for dolphin and storage subscriptions
+* OFW: Make file extensions case-insensitive
+* OFW: Remove internal storage folder if corresponding flag set
+* OFW: **Added a text input that only accepts full numbers (int)**
+* OFW: FuriEventLoop Pt.2
+* OFW: Images linting: ensure that all images conform specification
+* OFW: **Storage: remove LFS**
+* OFW: NFC: Change the plantain last number display from "?" to "X"
+* OFW: CCID App: Refactor
+* OFW: Refactor detected protocols list
+* OFW: fix: Ensure proper closure of variadic function in `mjs_array`
+* OFW: **Added** `-Wundef` **to compiler options**
+* OFW: toolchain: v39
+* OFW: Furi: update string documentation
+* OFW: Fix typo in "charge me" screen.
+* OFW: Reordered VS-Code Tasks to follow the `Release` > `Debug` schema
+* OFW: Remove unused entries from .editorconfig
#### Known NFC post-refactor regressions list:
- Mifare Mini clones reading is broken (original mini working fine) (OFW)
@@ -71,21 +46,21 @@
[-> Download qFlipper (official link)](https://flipperzero.one/update)
## Please support development of the project
-|Service|Remark|Link/Wallet|
-|-|-|-|
-|**Patreon**||https://patreon.com/mmxdev|
-|**Boosty**|patreon alternative|https://boosty.to/mmxdev|
-|cloudtips|only RU payments accepted|https://pay.cloudtips.ru/p/7b3e9d65|
-|YooMoney|only RU payments accepted|https://yoomoney.ru/fundraise/XA49mgQLPA0.221209|
-|USDT|(TRC20)|`TSXcitMSnWXUFqiUfEXrTVpVewXy2cYhrs`|
-|ETH|(BSC/ERC20-Tokens)|`darkflippers.eth` (or `0xFebF1bBc8229418FF2408C07AF6Afa49152fEc6a`)|
-|BTC||`bc1q0np836jk9jwr4dd7p6qv66d04vamtqkxrecck9`|
-|SOL|(Solana/Tokens)|`DSgwouAEgu8iP5yr7EHHDqMNYWZxAqXWsTEeqCAXGLj8`|
-|DOGE||`D6R6gYgBn5LwTNmPyvAQR6bZ9EtGgFCpvv`|
-|LTC||`ltc1q3ex4ejkl0xpx3znwrmth4lyuadr5qgv8tmq8z9`|
-|BCH||`qquxfyzntuqufy2dx0hrfr4sndp0tucvky4sw8qyu3`|
-|XMR|(Monero)| `41xUz92suUu1u5Mu4qkrcs52gtfpu9rnZRdBpCJ244KRHf6xXSvVFevdf2cnjS7RAeYr5hn9MsEfxKoFDRSctFjG5fv1Mhn`|
-|TON||`UQCOqcnYkvzOZUV_9bPE_8oTbOrOF03MnF-VcJyjisTZmsxa`|
+|Service|Remark|QR Code|Link/Wallet|
+|-|-|-|-|
+|**Patreon**||
|https://patreon.com/mmxdev|
+|**Boosty**|patreon alternative| |https://boosty.to/mmxdev|
+|cloudtips|only RU payments accepted| |https://pay.cloudtips.ru/p/7b3e9d65|
+|YooMoney|only RU payments accepted| |https://yoomoney.ru/fundraise/XA49mgQLPA0.221209|
+|USDT|(TRC20)| |`TSXcitMSnWXUFqiUfEXrTVpVewXy2cYhrs`|
+|ETH|(BSC/ERC20-Tokens)| |`darkflippers.eth` (or `0xFebF1bBc8229418FF2408C07AF6Afa49152fEc6a`)|
+|BTC|| |`bc1q0np836jk9jwr4dd7p6qv66d04vamtqkxrecck9`|
+|SOL|(Solana/Tokens)| |`DSgwouAEgu8iP5yr7EHHDqMNYWZxAqXWsTEeqCAXGLj8`|
+|DOGE|| |`D6R6gYgBn5LwTNmPyvAQR6bZ9EtGgFCpvv`|
+|LTC|| |`ltc1q3ex4ejkl0xpx3znwrmth4lyuadr5qgv8tmq8z9`|
+|BCH|| |`qquxfyzntuqufy2dx0hrfr4sndp0tucvky4sw8qyu3`|
+|XMR|(Monero)| |`41xUz92suUu1u5Mu4qkrcs52gtfpu9rnZRdBpCJ244KRHf6xXSvVFevdf2cnjS7RAeYr5hn9MsEfxKoFDRSctFjG5fv1Mhn`|
+|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:
@mishamyte, ClaraCrazy, Pathfinder [Count Zero cDc], callmezimbra, Quen0n, MERRON, grvpvl (lvpvrg), art_col, ThurstonWaffles, Moneron, UterGrooll, LUCFER, Northpirate, zloepuzo, T.Rat, Alexey B., ionelife, ...
diff --git a/ReadMe.md b/ReadMe.md
index 7eb98aeed3..b2911d14d8 100644
--- a/ReadMe.md
+++ b/ReadMe.md
@@ -139,21 +139,21 @@ Our team is small and the guys are working on this project as much as they can s
The amount of work done on this project is huge and we need your support, no matter how large or small. Even if you just say, "Thank you Unleashed firmware developers!" somewhere. Doing so will help us continue our work and will help drive us to make the firmware better every time.
Also, regarding our releases, every build has and always will be free and open-source. There will be no paywall releases or closed-source apps within the firmware. As long as I am working on this project it will never happen.
You can support us by using links or addresses below:
-|Service|Remark|Link/Wallet|
-|-|-|-|
-|**Patreon**||https://patreon.com/mmxdev|
-|**Boosty**|patreon alternative|https://boosty.to/mmxdev|
-|cloudtips|only RU payments accepted|https://pay.cloudtips.ru/p/7b3e9d65|
-|YooMoney|only RU payments accepted|https://yoomoney.ru/fundraise/XA49mgQLPA0.221209|
-|USDT|(TRC20)|`TSXcitMSnWXUFqiUfEXrTVpVewXy2cYhrs`|
-|ETH|(BSC/ERC20-Tokens)|`darkflippers.eth` (or `0xFebF1bBc8229418FF2408C07AF6Afa49152fEc6a`)|
-|BTC||`bc1q0np836jk9jwr4dd7p6qv66d04vamtqkxrecck9`|
-|SOL|(Solana/Tokens)|`DSgwouAEgu8iP5yr7EHHDqMNYWZxAqXWsTEeqCAXGLj8`|
-|DOGE||`D6R6gYgBn5LwTNmPyvAQR6bZ9EtGgFCpvv`|
-|LTC||`ltc1q3ex4ejkl0xpx3znwrmth4lyuadr5qgv8tmq8z9`|
-|BCH||`qquxfyzntuqufy2dx0hrfr4sndp0tucvky4sw8qyu3`|
-|XMR|(Monero)| `41xUz92suUu1u5Mu4qkrcs52gtfpu9rnZRdBpCJ244KRHf6xXSvVFevdf2cnjS7RAeYr5hn9MsEfxKoFDRSctFjG5fv1Mhn`|
-|TON||`UQCOqcnYkvzOZUV_9bPE_8oTbOrOF03MnF-VcJyjisTZmsxa`|
+|Service|Remark|QR Code|Link/Wallet|
+|-|-|-|-|
+|**Patreon**|| |https://patreon.com/mmxdev|
+|**Boosty**|patreon alternative| |https://boosty.to/mmxdev|
+|cloudtips|only RU payments accepted| |https://pay.cloudtips.ru/p/7b3e9d65|
+|YooMoney|only RU payments accepted| |https://yoomoney.ru/fundraise/XA49mgQLPA0.221209|
+|USDT|(TRC20)| |`TSXcitMSnWXUFqiUfEXrTVpVewXy2cYhrs`|
+|ETH|(BSC/ERC20-Tokens)| |`darkflippers.eth` (or `0xFebF1bBc8229418FF2408C07AF6Afa49152fEc6a`)|
+|BTC|| |`bc1q0np836jk9jwr4dd7p6qv66d04vamtqkxrecck9`|
+|SOL|(Solana/Tokens)| |`DSgwouAEgu8iP5yr7EHHDqMNYWZxAqXWsTEeqCAXGLj8`|
+|DOGE|| |`D6R6gYgBn5LwTNmPyvAQR6bZ9EtGgFCpvv`|
+|LTC|| |`ltc1q3ex4ejkl0xpx3znwrmth4lyuadr5qgv8tmq8z9`|
+|BCH|| |`qquxfyzntuqufy2dx0hrfr4sndp0tucvky4sw8qyu3`|
+|XMR|(Monero)| |`41xUz92suUu1u5Mu4qkrcs52gtfpu9rnZRdBpCJ244KRHf6xXSvVFevdf2cnjS7RAeYr5hn9MsEfxKoFDRSctFjG5fv1Mhn`|
+|TON|| |`UQCOqcnYkvzOZUV_9bPE_8oTbOrOF03MnF-VcJyjisTZmsxa`|
## Community apps included
diff --git a/SConstruct b/SConstruct
index 48baaa95ef..298954fd6e 100644
--- a/SConstruct
+++ b/SConstruct
@@ -322,7 +322,12 @@ firmware_env.Append(
"SConstruct",
"firmware.scons",
"fbt_options.py",
- ]
+ ],
+ IMG_LINT_SOURCES=[
+ # Image assets
+ "applications",
+ "assets",
+ ],
)
@@ -359,6 +364,39 @@ distenv.PhonyTarget(
PY_LINT_SOURCES=firmware_env["PY_LINT_SOURCES"],
)
+# Image assets linting
+distenv.PhonyTarget(
+ "lint_img",
+ [
+ [
+ "${PYTHON3}",
+ "${FBT_SCRIPT_DIR}/imglint.py",
+ "check",
+ "${IMG_LINT_SOURCES}",
+ "${ARGS}",
+ ]
+ ],
+ IMG_LINT_SOURCES=firmware_env["IMG_LINT_SOURCES"],
+)
+
+distenv.PhonyTarget(
+ "format_img",
+ [
+ [
+ "${PYTHON3}",
+ "${FBT_SCRIPT_DIR}/imglint.py",
+ "format",
+ "${IMG_LINT_SOURCES}",
+ "${ARGS}",
+ ]
+ ],
+ IMG_LINT_SOURCES=firmware_env["IMG_LINT_SOURCES"],
+)
+
+distenv.Alias("lint_all", ["lint", "lint_py", "lint_img"])
+distenv.Alias("format_all", ["format", "format_py", "format_img"])
+
+
# Start Flipper CLI via PySerial's miniterm
distenv.PhonyTarget(
"cli",
diff --git a/applications/debug/accessor/accessor_view_manager.cpp b/applications/debug/accessor/accessor_view_manager.cpp
index 955c0b2867..aeb90c2974 100644
--- a/applications/debug/accessor/accessor_view_manager.cpp
+++ b/applications/debug/accessor/accessor_view_manager.cpp
@@ -5,45 +5,49 @@
AccessorAppViewManager::AccessorAppViewManager() {
event_queue = furi_message_queue_alloc(10, sizeof(AccessorEvent));
- view_dispatcher = view_dispatcher_alloc();
- auto callback = cbc::obtain_connector(this, &AccessorAppViewManager::previous_view_callback);
+ view_holder = view_holder_alloc();
+ auto callback =
+ cbc::obtain_connector(this, &AccessorAppViewManager::view_holder_back_callback);
// allocate views
submenu = submenu_alloc();
- add_view(ViewType::Submenu, submenu_get_view(submenu));
-
popup = popup_alloc();
- add_view(ViewType::Popup, popup_get_view(popup));
- gui = static_cast(furi_record_open(RECORD_GUI));
- view_dispatcher_attach_to_gui(view_dispatcher, gui, ViewDispatcherTypeFullscreen);
+ // set back callback
+ view_holder_set_back_callback(view_holder, callback, NULL);
- // set previous view callback for all views
- view_set_previous_callback(submenu_get_view(submenu), callback);
- view_set_previous_callback(popup_get_view(popup), callback);
+ gui = static_cast(furi_record_open(RECORD_GUI));
+ view_holder_attach_to_gui(view_holder, gui);
}
AccessorAppViewManager::~AccessorAppViewManager() {
- // remove views
- view_dispatcher_remove_view(
- view_dispatcher, static_cast(AccessorAppViewManager::ViewType::Submenu));
- view_dispatcher_remove_view(
- view_dispatcher, static_cast(AccessorAppViewManager::ViewType::Popup));
-
+ // remove current view
+ view_holder_set_view(view_holder, NULL);
// free view modules
furi_record_close(RECORD_GUI);
submenu_free(submenu);
popup_free(popup);
-
- // free dispatcher
- view_dispatcher_free(view_dispatcher);
-
+ // free view holder
+ view_holder_free(view_holder);
// free event queue
furi_message_queue_free(event_queue);
}
void AccessorAppViewManager::switch_to(ViewType type) {
- view_dispatcher_switch_to_view(view_dispatcher, static_cast(type));
+ View* view;
+
+ switch(type) {
+ case ViewType::Submenu:
+ view = submenu_get_view(submenu);
+ break;
+ case ViewType::Popup:
+ view = popup_get_view(popup);
+ break;
+ default:
+ furi_crash();
+ }
+
+ view_holder_set_view(view_holder, view);
}
Submenu* AccessorAppViewManager::get_submenu() {
@@ -65,16 +69,10 @@ void AccessorAppViewManager::send_event(AccessorEvent* event) {
furi_check(result == FuriStatusOk);
}
-uint32_t AccessorAppViewManager::previous_view_callback(void*) {
+void AccessorAppViewManager::view_holder_back_callback(void*) {
if(event_queue != NULL) {
AccessorEvent event;
event.type = AccessorEvent::Type::Back;
send_event(&event);
}
-
- return VIEW_IGNORE;
-}
-
-void AccessorAppViewManager::add_view(ViewType view_type, View* view) {
- view_dispatcher_add_view(view_dispatcher, static_cast(view_type), view);
}
diff --git a/applications/debug/accessor/accessor_view_manager.h b/applications/debug/accessor/accessor_view_manager.h
index 66e54e41ce..c0a12cbe8e 100644
--- a/applications/debug/accessor/accessor_view_manager.h
+++ b/applications/debug/accessor/accessor_view_manager.h
@@ -1,6 +1,6 @@
#pragma once
#include
-#include
+#include
#include
#include
#include "accessor_event.h"
@@ -10,7 +10,6 @@ class AccessorAppViewManager {
enum class ViewType : uint8_t {
Submenu,
Popup,
- Tune,
};
FuriMessageQueue* event_queue;
@@ -27,11 +26,10 @@ class AccessorAppViewManager {
Popup* get_popup(void);
private:
- ViewDispatcher* view_dispatcher;
Gui* gui;
+ ViewHolder* view_holder;
- uint32_t previous_view_callback(void* context);
- void add_view(ViewType view_type, View* view);
+ void view_holder_back_callback(void* context);
// view elements
Submenu* submenu;
diff --git a/applications/debug/battery_test_app/battery_test_app.c b/applications/debug/battery_test_app/battery_test_app.c
index 5f9934e777..363c8f4d52 100644
--- a/applications/debug/battery_test_app/battery_test_app.c
+++ b/applications/debug/battery_test_app/battery_test_app.c
@@ -42,7 +42,6 @@ BatteryTestApp* battery_test_alloc(void) {
// View dispatcher
app->view_dispatcher = view_dispatcher_alloc();
- view_dispatcher_enable_queue(app->view_dispatcher);
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
view_dispatcher_set_tick_event_callback(
app->view_dispatcher, battery_test_battery_info_update_model, 500);
diff --git a/applications/debug/bt_debug_app/bt_debug_app.c b/applications/debug/bt_debug_app/bt_debug_app.c
index 109feee602..56c67e3e66 100644
--- a/applications/debug/bt_debug_app/bt_debug_app.c
+++ b/applications/debug/bt_debug_app/bt_debug_app.c
@@ -36,7 +36,6 @@ BtDebugApp* bt_debug_app_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);
// Views
diff --git a/applications/debug/ccid_test/ccid_test_app.c b/applications/debug/ccid_test/ccid_test_app.c
index 46a1237f93..abb8ad3dd3 100644
--- a/applications/debug/ccid_test/ccid_test_app.c
+++ b/applications/debug/ccid_test/ccid_test_app.c
@@ -6,10 +6,13 @@
#include
#include
#include
-#include "iso7816_callbacks.h"
-#include "iso7816_t0_apdu.h"
-#include "iso7816_atr.h"
-#include "iso7816_response.h"
+
+#include "iso7816/iso7816_handler.h"
+#include "iso7816/iso7816_t0_apdu.h"
+#include "iso7816/iso7816_atr.h"
+#include "iso7816/iso7816_response.h"
+
+#include "ccid_test_app_commands.h"
typedef enum {
EventTypeInput,
@@ -20,6 +23,7 @@ typedef struct {
ViewPort* view_port;
FuriMessageQueue* event_queue;
FuriHalUsbCcidConfig ccid_cfg;
+ Iso7816Handler* iso7816_handler;
} CcidTestApp;
typedef struct {
@@ -63,6 +67,15 @@ uint32_t ccid_test_exit(void* context) {
CcidTestApp* ccid_test_app_alloc(void) {
CcidTestApp* app = malloc(sizeof(CcidTestApp));
+ //setup CCID USB
+ // On linux: set VID PID using: /usr/lib/pcsc/drivers/ifd-ccid.bundle/Contents/Info.plist
+ app->ccid_cfg.vid = 0x076B;
+ app->ccid_cfg.pid = 0x3A21;
+
+ app->iso7816_handler = iso7816_handler_alloc();
+ app->iso7816_handler->iso7816_answer_to_reset = iso7816_answer_to_reset;
+ app->iso7816_handler->iso7816_process_command = iso7816_process_command;
+
// Gui
app->gui = furi_record_open(RECORD_GUI);
@@ -92,174 +105,26 @@ void ccid_test_app_free(CcidTestApp* app) {
furi_record_close(RECORD_GUI);
app->gui = NULL;
+ free(app->iso7816_handler);
+
// Free rest
free(app);
}
-void ccid_icc_power_on_callback(uint8_t* atrBuffer, uint32_t* atrlen, void* context) {
- UNUSED(context);
-
- iso7816_icc_power_on_callback(atrBuffer, atrlen);
-}
-
-void ccid_xfr_datablock_callback(
- const uint8_t* pcToReaderDataBlock,
- uint32_t pcToReaderDataBlockLen,
- uint8_t* readerToPcDataBlock,
- uint32_t* readerToPcDataBlockLen,
- void* context) {
- UNUSED(context);
-
- iso7816_xfr_datablock_callback(
- pcToReaderDataBlock, pcToReaderDataBlockLen, readerToPcDataBlock, readerToPcDataBlockLen);
-}
-
-static const CcidCallbacks ccid_cb = {
- ccid_icc_power_on_callback,
- ccid_xfr_datablock_callback,
-};
-
-//Instruction 1: returns an OK response unconditionally
-//APDU example: 0x01:0x01:0x00:0x00
-//response: SW1=0x90, SW2=0x00
-void handle_instruction_01(ISO7816_Response_APDU* responseAPDU) {
- responseAPDU->DataLen = 0;
- iso7816_set_response(responseAPDU, ISO7816_RESPONSE_OK);
-}
-
-//Instruction 2: expect command with no body, replies wit with a body with two bytes
-//APDU example: 0x01:0x02:0x00:0x00:0x02
-//response: 'bc' (0x62, 0x63) SW1=0x90, SW2=0x00
-void handle_instruction_02(
- uint8_t p1,
- uint8_t p2,
- uint16_t lc,
- uint16_t le,
- ISO7816_Response_APDU* responseAPDU) {
- if(p1 == 0 && p2 == 0 && lc == 0 && le >= 2) {
- responseAPDU->Data[0] = 0x62;
- responseAPDU->Data[1] = 0x63;
-
- responseAPDU->DataLen = 2;
-
- iso7816_set_response(responseAPDU, ISO7816_RESPONSE_OK);
- } else if(p1 != 0 || p2 != 0) {
- iso7816_set_response(responseAPDU, ISO7816_RESPONSE_WRONG_PARAMETERS_P1_P2);
- } else {
- iso7816_set_response(responseAPDU, ISO7816_RESPONSE_WRONG_LENGTH);
- }
-}
-
-//Instruction 3: sends a command with a body with two bytes, receives a response with no bytes
-//APDU example: 0x01:0x03:0x00:0x00:0x02:CA:FE
-//response SW1=0x90, SW2=0x00
-void handle_instruction_03(
- uint8_t p1,
- uint8_t p2,
- uint16_t lc,
- ISO7816_Response_APDU* responseAPDU) {
- if(p1 == 0 && p2 == 0 && lc == 2) {
- responseAPDU->DataLen = 0;
- iso7816_set_response(responseAPDU, ISO7816_RESPONSE_OK);
- } else if(p1 != 0 || p2 != 0) {
- iso7816_set_response(responseAPDU, ISO7816_RESPONSE_WRONG_PARAMETERS_P1_P2);
- } else {
- iso7816_set_response(responseAPDU, ISO7816_RESPONSE_WRONG_LENGTH);
- }
-}
-
-//instruction 4: sends a command with a body with 'n' bytes, receives a response with 'n' bytes
-//APDU example: 0x01:0x04:0x00:0x00:0x04:0x01:0x02:0x03:0x04:0x04
-//receives (0x01, 0x02, 0x03, 0x04) SW1=0x90, SW2=0x00
-void handle_instruction_04(
- uint8_t p1,
- uint8_t p2,
- uint16_t lc,
- uint16_t le,
- const uint8_t* commandApduDataBuffer,
- ISO7816_Response_APDU* responseAPDU) {
- if(p1 == 0 && p2 == 0 && lc > 0 && le > 0 && le >= lc) {
- for(uint16_t i = 0; i < lc; i++) {
- responseAPDU->Data[i] = commandApduDataBuffer[i];
- }
-
- responseAPDU->DataLen = lc;
-
- iso7816_set_response(responseAPDU, ISO7816_RESPONSE_OK);
- } else if(p1 != 0 || p2 != 0) {
- iso7816_set_response(responseAPDU, ISO7816_RESPONSE_WRONG_PARAMETERS_P1_P2);
- } else {
- iso7816_set_response(responseAPDU, ISO7816_RESPONSE_WRONG_LENGTH);
- }
-}
-
-void iso7816_answer_to_reset(Iso7816Atr* atr) {
- //minimum valid ATR: https://smartcard-atr.apdu.fr/parse?ATR=3B+00
- atr->TS = 0x3B;
- atr->T0 = 0x00;
-}
-
-void iso7816_process_command(
- const ISO7816_Command_APDU* commandAPDU,
- ISO7816_Response_APDU* responseAPDU) {
- //example 1: sends a command with no body, receives a response with no body
- //sends APDU 0x01:0x01:0x00:0x00
- //receives SW1=0x90, SW2=0x00
-
- if(commandAPDU->CLA == 0x01) {
- switch(commandAPDU->INS) {
- case 0x01:
- handle_instruction_01(responseAPDU);
- break;
- case 0x02:
- handle_instruction_02(
- commandAPDU->P1, commandAPDU->P2, commandAPDU->Lc, commandAPDU->Le, responseAPDU);
- break;
- case 0x03:
- handle_instruction_03(commandAPDU->P1, commandAPDU->P2, commandAPDU->Lc, responseAPDU);
- break;
- case 0x04:
- handle_instruction_04(
- commandAPDU->P1,
- commandAPDU->P2,
- commandAPDU->Lc,
- commandAPDU->Le,
- commandAPDU->Data,
- responseAPDU);
- break;
- default:
- iso7816_set_response(responseAPDU, ISO7816_RESPONSE_INSTRUCTION_NOT_SUPPORTED);
- }
- } else {
- iso7816_set_response(responseAPDU, ISO7816_RESPONSE_CLASS_NOT_SUPPORTED);
- }
-}
-
-static const Iso7816Callbacks iso87816_cb = {
- iso7816_answer_to_reset,
- iso7816_process_command,
-};
-
int32_t ccid_test_app(void* p) {
UNUSED(p);
//setup view
CcidTestApp* app = ccid_test_app_alloc();
- //setup CCID USB
- // On linux: set VID PID using: /usr/lib/pcsc/drivers/ifd-ccid.bundle/Contents/Info.plist
- app->ccid_cfg.vid = 0x076B;
- app->ccid_cfg.pid = 0x3A21;
-
FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config();
furi_hal_usb_unlock();
furi_check(furi_hal_usb_set_config(&usb_ccid, &app->ccid_cfg) == true);
- furi_hal_usb_ccid_set_callbacks((CcidCallbacks*)&ccid_cb, NULL);
+ furi_hal_usb_ccid_set_callbacks(
+ (CcidCallbacks*)&app->iso7816_handler->ccid_callbacks, app->iso7816_handler);
furi_hal_usb_ccid_insert_smartcard();
- iso7816_set_callbacks((Iso7816Callbacks*)&iso87816_cb);
-
//handle button events
CcidTestAppEvent event;
while(1) {
@@ -280,8 +145,6 @@ int32_t ccid_test_app(void* p) {
furi_hal_usb_ccid_set_callbacks(NULL, NULL);
furi_hal_usb_set_config(usb_mode_prev, NULL);
- iso7816_set_callbacks(NULL);
-
//teardown view
ccid_test_app_free(app);
return 0;
diff --git a/applications/debug/ccid_test/ccid_test_app_commands.c b/applications/debug/ccid_test/ccid_test_app_commands.c
new file mode 100644
index 0000000000..1daaa70c39
--- /dev/null
+++ b/applications/debug/ccid_test/ccid_test_app_commands.c
@@ -0,0 +1,123 @@
+#include "iso7816/iso7816_t0_apdu.h"
+#include "iso7816/iso7816_response.h"
+
+//Instruction 1: returns an OK response unconditionally
+//APDU example: 0x01:0x01:0x00:0x00
+//response: SW1=0x90, SW2=0x00
+void handle_instruction_01(ISO7816_Response_APDU* response_apdu) {
+ response_apdu->DataLen = 0;
+ iso7816_set_response(response_apdu, ISO7816_RESPONSE_OK);
+}
+
+//Instruction 2: expect command with no body, replies wit with a body with two bytes
+//APDU example: 0x01:0x02:0x00:0x00:0x02
+//response: 'bc' (0x62, 0x63) SW1=0x90, SW2=0x00
+void handle_instruction_02(
+ uint8_t p1,
+ uint8_t p2,
+ uint16_t lc,
+ uint16_t le,
+ ISO7816_Response_APDU* response_apdu) {
+ if(p1 == 0 && p2 == 0 && lc == 0 && le >= 2) {
+ response_apdu->Data[0] = 0x62;
+ response_apdu->Data[1] = 0x63;
+
+ response_apdu->DataLen = 2;
+
+ iso7816_set_response(response_apdu, ISO7816_RESPONSE_OK);
+ } else if(p1 != 0 || p2 != 0) {
+ iso7816_set_response(response_apdu, ISO7816_RESPONSE_WRONG_PARAMETERS_P1_P2);
+ } else {
+ iso7816_set_response(response_apdu, ISO7816_RESPONSE_WRONG_LENGTH);
+ }
+}
+
+//Instruction 3: sends a command with a body with two bytes, receives a response with no bytes
+//APDU example: 0x01:0x03:0x00:0x00:0x02:CA:FE
+//response SW1=0x90, SW2=0x00
+void handle_instruction_03(
+ uint8_t p1,
+ uint8_t p2,
+ uint16_t lc,
+ ISO7816_Response_APDU* response_apdu) {
+ if(p1 == 0 && p2 == 0 && lc == 2) {
+ response_apdu->DataLen = 0;
+ iso7816_set_response(response_apdu, ISO7816_RESPONSE_OK);
+ } else if(p1 != 0 || p2 != 0) {
+ iso7816_set_response(response_apdu, ISO7816_RESPONSE_WRONG_PARAMETERS_P1_P2);
+ } else {
+ iso7816_set_response(response_apdu, ISO7816_RESPONSE_WRONG_LENGTH);
+ }
+}
+
+//instruction 4: sends a command with a body with 'n' bytes, receives a response with 'n' bytes
+//APDU example: 0x01:0x04:0x00:0x00:0x04:0x01:0x02:0x03:0x04:0x04
+//receives (0x01, 0x02, 0x03, 0x04) SW1=0x90, SW2=0x00
+void handle_instruction_04(
+ uint8_t p1,
+ uint8_t p2,
+ uint16_t lc,
+ uint16_t le,
+ const uint8_t* command_apdu_data_buffer,
+ ISO7816_Response_APDU* response_apdu) {
+ if(p1 == 0 && p2 == 0 && lc > 0 && le > 0 && le >= lc) {
+ for(uint16_t i = 0; i < lc; i++) {
+ response_apdu->Data[i] = command_apdu_data_buffer[i];
+ }
+
+ response_apdu->DataLen = lc;
+
+ iso7816_set_response(response_apdu, ISO7816_RESPONSE_OK);
+ } else if(p1 != 0 || p2 != 0) {
+ iso7816_set_response(response_apdu, ISO7816_RESPONSE_WRONG_PARAMETERS_P1_P2);
+ } else {
+ iso7816_set_response(response_apdu, ISO7816_RESPONSE_WRONG_LENGTH);
+ }
+}
+
+void iso7816_answer_to_reset(Iso7816Atr* atr) {
+ //minimum valid ATR: https://smartcard-atr.apdu.fr/parse?ATR=3B+00
+ atr->TS = 0x3B;
+ atr->T0 = 0x00;
+}
+
+void iso7816_process_command(
+ const ISO7816_Command_APDU* command_apdu,
+ ISO7816_Response_APDU* response_apdu) {
+ //example 1: sends a command with no body, receives a response with no body
+ //sends APDU 0x01:0x01:0x00:0x00
+ //receives SW1=0x90, SW2=0x00
+
+ if(command_apdu->CLA == 0x01) {
+ switch(command_apdu->INS) {
+ case 0x01:
+ handle_instruction_01(response_apdu);
+ break;
+ case 0x02:
+ handle_instruction_02(
+ command_apdu->P1,
+ command_apdu->P2,
+ command_apdu->Lc,
+ command_apdu->Le,
+ response_apdu);
+ break;
+ case 0x03:
+ handle_instruction_03(
+ command_apdu->P1, command_apdu->P2, command_apdu->Lc, response_apdu);
+ break;
+ case 0x04:
+ handle_instruction_04(
+ command_apdu->P1,
+ command_apdu->P2,
+ command_apdu->Lc,
+ command_apdu->Le,
+ command_apdu->Data,
+ response_apdu);
+ break;
+ default:
+ iso7816_set_response(response_apdu, ISO7816_RESPONSE_INSTRUCTION_NOT_SUPPORTED);
+ }
+ } else {
+ iso7816_set_response(response_apdu, ISO7816_RESPONSE_CLASS_NOT_SUPPORTED);
+ }
+}
diff --git a/applications/debug/ccid_test/ccid_test_app_commands.h b/applications/debug/ccid_test/ccid_test_app_commands.h
new file mode 100644
index 0000000000..ca3275aec1
--- /dev/null
+++ b/applications/debug/ccid_test/ccid_test_app_commands.h
@@ -0,0 +1,7 @@
+#include "iso7816/iso7816_t0_apdu.h"
+
+void iso7816_answer_to_reset(Iso7816Atr* atr);
+
+void iso7816_process_command(
+ const ISO7816_Command_APDU* command_apdu,
+ ISO7816_Response_APDU* response_apdu);
diff --git a/applications/debug/ccid_test/iso7816_atr.h b/applications/debug/ccid_test/iso7816/iso7816_atr.h
similarity index 100%
rename from applications/debug/ccid_test/iso7816_atr.h
rename to applications/debug/ccid_test/iso7816/iso7816_atr.h
diff --git a/applications/debug/ccid_test/iso7816/iso7816_handler.c b/applications/debug/ccid_test/iso7816/iso7816_handler.c
new file mode 100644
index 0000000000..97214d1b22
--- /dev/null
+++ b/applications/debug/ccid_test/iso7816/iso7816_handler.c
@@ -0,0 +1,68 @@
+// transforms low level calls such as XFRCallback or ICC Power on to a structured one
+// an application can register these calls and listen for the callbacks defined in Iso7816Callbacks
+
+#include
+#include
+#include
+#include
+
+#include "iso7816_t0_apdu.h"
+#include "iso7816_atr.h"
+#include "iso7816_handler.h"
+#include "iso7816_response.h"
+
+void iso7816_icc_power_on_callback(uint8_t* atr_data, uint32_t* atr_data_len, void* context) {
+ furi_check(context);
+
+ Iso7816Handler* handler = (Iso7816Handler*)context;
+
+ Iso7816Atr iso7816_atr;
+ handler->iso7816_answer_to_reset(&iso7816_atr);
+
+ furi_assert(iso7816_atr.T0 == 0x00);
+
+ uint8_t atr_buffer[2] = {iso7816_atr.TS, iso7816_atr.T0};
+
+ *atr_data_len = 2;
+
+ memcpy(atr_data, atr_buffer, sizeof(uint8_t) * (*atr_data_len));
+}
+
+//dataBlock points to the buffer
+//dataBlockLen tells reader how nany bytes should be read
+void iso7816_xfr_datablock_callback(
+ const uint8_t* pc_to_reader_datablock,
+ uint32_t pc_to_reader_datablock_len,
+ uint8_t* reader_to_pc_datablock,
+ uint32_t* reader_to_pc_datablock_len,
+ void* context) {
+ furi_check(context);
+
+ Iso7816Handler* handler = (Iso7816Handler*)context;
+
+ ISO7816_Response_APDU* response_apdu = (ISO7816_Response_APDU*)&handler->response_apdu_buffer;
+
+ ISO7816_Command_APDU* command_apdu = (ISO7816_Command_APDU*)&handler->command_apdu_buffer;
+
+ uint8_t result = iso7816_read_command_apdu(
+ command_apdu, pc_to_reader_datablock, pc_to_reader_datablock_len);
+
+ if(result == ISO7816_READ_COMMAND_APDU_OK) {
+ handler->iso7816_process_command(command_apdu, response_apdu);
+
+ furi_assert(response_apdu->DataLen < CCID_SHORT_APDU_SIZE);
+ } else if(result == ISO7816_READ_COMMAND_APDU_ERROR_WRONG_LE) {
+ iso7816_set_response(response_apdu, ISO7816_RESPONSE_WRONG_LE);
+ } else if(result == ISO7816_READ_COMMAND_APDU_ERROR_WRONG_LENGTH) {
+ iso7816_set_response(response_apdu, ISO7816_RESPONSE_WRONG_LENGTH);
+ }
+
+ iso7816_write_response_apdu(response_apdu, reader_to_pc_datablock, reader_to_pc_datablock_len);
+}
+
+Iso7816Handler* iso7816_handler_alloc() {
+ Iso7816Handler* handler = malloc(sizeof(Iso7816Handler));
+ handler->ccid_callbacks.icc_power_on_callback = iso7816_icc_power_on_callback;
+ handler->ccid_callbacks.xfr_datablock_callback = iso7816_xfr_datablock_callback;
+ return handler;
+}
diff --git a/applications/debug/ccid_test/iso7816/iso7816_handler.h b/applications/debug/ccid_test/iso7816/iso7816_handler.h
new file mode 100644
index 0000000000..d67118ce6e
--- /dev/null
+++ b/applications/debug/ccid_test/iso7816/iso7816_handler.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include
+#include "iso7816_atr.h"
+#include "iso7816_t0_apdu.h"
+
+typedef struct {
+ CcidCallbacks ccid_callbacks;
+ void (*iso7816_answer_to_reset)(Iso7816Atr* atr);
+ void (*iso7816_process_command)(
+ const ISO7816_Command_APDU* command,
+ ISO7816_Response_APDU* response);
+
+ uint8_t command_apdu_buffer[sizeof(ISO7816_Command_APDU) + CCID_SHORT_APDU_SIZE];
+ uint8_t response_apdu_buffer[sizeof(ISO7816_Response_APDU) + CCID_SHORT_APDU_SIZE];
+} Iso7816Handler;
+
+Iso7816Handler* iso7816_handler_alloc();
diff --git a/applications/debug/ccid_test/iso7816_response.c b/applications/debug/ccid_test/iso7816/iso7816_response.c
similarity index 100%
rename from applications/debug/ccid_test/iso7816_response.c
rename to applications/debug/ccid_test/iso7816/iso7816_response.c
diff --git a/applications/debug/ccid_test/iso7816_response.h b/applications/debug/ccid_test/iso7816/iso7816_response.h
similarity index 100%
rename from applications/debug/ccid_test/iso7816_response.h
rename to applications/debug/ccid_test/iso7816/iso7816_response.h
diff --git a/applications/debug/ccid_test/iso7816_t0_apdu.c b/applications/debug/ccid_test/iso7816/iso7816_t0_apdu.c
similarity index 85%
rename from applications/debug/ccid_test/iso7816_t0_apdu.c
rename to applications/debug/ccid_test/iso7816/iso7816_t0_apdu.c
index 3de5555f49..216f2582f1 100644
--- a/applications/debug/ccid_test/iso7816_t0_apdu.c
+++ b/applications/debug/ccid_test/iso7816/iso7816_t0_apdu.c
@@ -61,24 +61,25 @@ uint8_t iso7816_read_command_apdu(
//data buffer contains the whole APU response (response + trailer (SW1+SW2))
void iso7816_write_response_apdu(
const ISO7816_Response_APDU* response,
- uint8_t* readerToPcDataBlock,
- uint32_t* readerToPcDataBlockLen) {
+ uint8_t* reader_to_pc_datablock,
+ uint32_t* reader_to_pc_datablock_len) {
uint32_t responseDataBufferIndex = 0;
//response body
if(response->DataLen > 0) {
while(responseDataBufferIndex < response->DataLen) {
- readerToPcDataBlock[responseDataBufferIndex] = response->Data[responseDataBufferIndex];
+ reader_to_pc_datablock[responseDataBufferIndex] =
+ response->Data[responseDataBufferIndex];
responseDataBufferIndex++;
}
}
//trailer
- readerToPcDataBlock[responseDataBufferIndex] = response->SW1;
+ reader_to_pc_datablock[responseDataBufferIndex] = response->SW1;
responseDataBufferIndex++;
- readerToPcDataBlock[responseDataBufferIndex] = response->SW2;
+ reader_to_pc_datablock[responseDataBufferIndex] = response->SW2;
responseDataBufferIndex++;
- *readerToPcDataBlockLen = responseDataBufferIndex;
+ *reader_to_pc_datablock_len = responseDataBufferIndex;
}
diff --git a/applications/debug/ccid_test/iso7816_t0_apdu.h b/applications/debug/ccid_test/iso7816/iso7816_t0_apdu.h
similarity index 81%
rename from applications/debug/ccid_test/iso7816_t0_apdu.h
rename to applications/debug/ccid_test/iso7816/iso7816_t0_apdu.h
index 50eb476a9c..a21dfbafc3 100644
--- a/applications/debug/ccid_test/iso7816_t0_apdu.h
+++ b/applications/debug/ccid_test/iso7816/iso7816_t0_apdu.h
@@ -31,12 +31,11 @@ typedef struct {
uint8_t Data[0];
} FURI_PACKED ISO7816_Response_APDU;
-void iso7816_answer_to_reset(Iso7816Atr* atr);
uint8_t iso7816_read_command_apdu(
ISO7816_Command_APDU* command,
- const uint8_t* pcToReaderDataBlock,
- uint32_t pcToReaderDataBlockLen);
+ const uint8_t* pc_to_reader_datablock,
+ uint32_t pc_to_reader_datablock_len);
void iso7816_write_response_apdu(
const ISO7816_Response_APDU* response,
- uint8_t* readerToPcDataBlock,
- uint32_t* readerToPcDataBlockLen);
+ uint8_t* reader_to_pc_datablock,
+ uint32_t* reader_to_pc_datablock_len);
diff --git a/applications/debug/ccid_test/iso7816_callbacks.c b/applications/debug/ccid_test/iso7816_callbacks.c
deleted file mode 100644
index 6c1bb106a9..0000000000
--- a/applications/debug/ccid_test/iso7816_callbacks.c
+++ /dev/null
@@ -1,65 +0,0 @@
-// transforms low level calls such as XFRCallback or ICC Power on to a structured one
-// an application can register these calls and listen for the callbacks defined in Iso7816Callbacks
-
-#include
-#include
-#include
-#include
-
-#include "iso7816_t0_apdu.h"
-#include "iso7816_atr.h"
-#include "iso7816_callbacks.h"
-#include "iso7816_response.h"
-
-static Iso7816Callbacks* callbacks = NULL;
-
-static uint8_t commandApduBuffer[sizeof(ISO7816_Command_APDU) + CCID_SHORT_APDU_SIZE];
-static uint8_t responseApduBuffer[sizeof(ISO7816_Response_APDU) + CCID_SHORT_APDU_SIZE];
-
-void iso7816_set_callbacks(Iso7816Callbacks* cb) {
- callbacks = cb;
-}
-
-void iso7816_icc_power_on_callback(uint8_t* atrBuffer, uint32_t* atrlen) {
- Iso7816Atr atr;
- callbacks->iso7816_answer_to_reset(&atr);
-
- furi_assert(atr.T0 == 0x00);
-
- uint8_t AtrBuffer[2] = {atr.TS, atr.T0};
-
- *atrlen = 2;
-
- memcpy(atrBuffer, AtrBuffer, sizeof(uint8_t) * (*atrlen));
-}
-
-//dataBlock points to the buffer
-//dataBlockLen tells reader how nany bytes should be read
-void iso7816_xfr_datablock_callback(
- const uint8_t* pcToReaderDataBlock,
- uint32_t pcToReaderDataBlockLen,
- uint8_t* readerToPcDataBlock,
- uint32_t* readerToPcDataBlockLen) {
- ISO7816_Response_APDU* responseAPDU = (ISO7816_Response_APDU*)&responseApduBuffer;
-
- if(callbacks != NULL) {
- ISO7816_Command_APDU* commandAPDU = (ISO7816_Command_APDU*)&commandApduBuffer;
-
- uint8_t result =
- iso7816_read_command_apdu(commandAPDU, pcToReaderDataBlock, pcToReaderDataBlockLen);
-
- if(result == ISO7816_READ_COMMAND_APDU_OK) {
- callbacks->iso7816_process_command(commandAPDU, responseAPDU);
-
- furi_assert(responseAPDU->DataLen < CCID_SHORT_APDU_SIZE);
- } else if(result == ISO7816_READ_COMMAND_APDU_ERROR_WRONG_LE) {
- iso7816_set_response(responseAPDU, ISO7816_RESPONSE_WRONG_LE);
- } else if(result == ISO7816_READ_COMMAND_APDU_ERROR_WRONG_LENGTH) {
- iso7816_set_response(responseAPDU, ISO7816_RESPONSE_WRONG_LENGTH);
- }
- } else {
- iso7816_set_response(responseAPDU, ISO7816_RESPONSE_INTERNAL_EXCEPTION);
- }
-
- iso7816_write_response_apdu(responseAPDU, readerToPcDataBlock, readerToPcDataBlockLen);
-}
diff --git a/applications/debug/ccid_test/iso7816_callbacks.h b/applications/debug/ccid_test/iso7816_callbacks.h
deleted file mode 100644
index 6b408c7f50..0000000000
--- a/applications/debug/ccid_test/iso7816_callbacks.h
+++ /dev/null
@@ -1,21 +0,0 @@
-#pragma once
-
-#include
-#include "iso7816_atr.h"
-#include "iso7816_t0_apdu.h"
-
-typedef struct {
- void (*iso7816_answer_to_reset)(Iso7816Atr* atr);
- void (*iso7816_process_command)(
- const ISO7816_Command_APDU* command,
- ISO7816_Response_APDU* response);
-} Iso7816Callbacks;
-
-void iso7816_set_callbacks(Iso7816Callbacks* cb);
-
-void iso7816_icc_power_on_callback(uint8_t* atrBuffer, uint32_t* atrlen);
-void iso7816_xfr_datablock_callback(
- const uint8_t* dataBlock,
- uint32_t dataBlockLen,
- uint8_t* responseDataBlock,
- uint32_t* responseDataBlockLen);
diff --git a/applications/debug/crash_test/crash_test.c b/applications/debug/crash_test/crash_test.c
index ae0074fe1c..2b2be13d62 100644
--- a/applications/debug/crash_test/crash_test.c
+++ b/applications/debug/crash_test/crash_test.c
@@ -66,7 +66,6 @@ CrashTest* crash_test_alloc(void) {
instance->gui = furi_record_open(RECORD_GUI);
instance->view_dispatcher = view_dispatcher_alloc();
- view_dispatcher_enable_queue(instance->view_dispatcher);
view_dispatcher_attach_to_gui(
instance->view_dispatcher, instance->gui, ViewDispatcherTypeFullscreen);
diff --git a/applications/debug/display_test/display_test.c b/applications/debug/display_test/display_test.c
index 3028a13b90..3b742906d6 100644
--- a/applications/debug/display_test/display_test.c
+++ b/applications/debug/display_test/display_test.c
@@ -126,7 +126,6 @@ DisplayTest* display_test_alloc(void) {
instance->gui = furi_record_open(RECORD_GUI);
instance->view_dispatcher = view_dispatcher_alloc();
- view_dispatcher_enable_queue(instance->view_dispatcher);
view_dispatcher_attach_to_gui(
instance->view_dispatcher, instance->gui, ViewDispatcherTypeFullscreen);
diff --git a/applications/debug/event_loop_blink_test/event_loop_blink_test.c b/applications/debug/event_loop_blink_test/event_loop_blink_test.c
index 5c7e0ce558..7f00e63f2e 100644
--- a/applications/debug/event_loop_blink_test/event_loop_blink_test.c
+++ b/applications/debug/event_loop_blink_test/event_loop_blink_test.c
@@ -82,7 +82,8 @@ static void view_port_input_callback(InputEvent* input_event, void* context) {
furi_message_queue_put(app->input_queue, input_event, 0);
}
-static bool input_queue_callback(FuriMessageQueue* queue, void* context) {
+static bool input_queue_callback(FuriEventLoopObject* object, void* context) {
+ FuriMessageQueue* queue = object;
EventLoopBlinkTestApp* app = context;
InputEvent event;
@@ -144,7 +145,7 @@ int32_t event_loop_blink_test_app(void* arg) {
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
furi_event_loop_tick_set(app.event_loop, 500, event_loop_tick_callback, &app);
- furi_event_loop_message_queue_subscribe(
+ furi_event_loop_subscribe_message_queue(
app.event_loop, app.input_queue, FuriEventLoopEventIn, input_queue_callback, &app);
furi_event_loop_run(app.event_loop);
@@ -154,7 +155,7 @@ int32_t event_loop_blink_test_app(void* arg) {
furi_record_close(RECORD_GUI);
- furi_event_loop_message_queue_unsubscribe(app.event_loop, app.input_queue);
+ furi_event_loop_unsubscribe(app.event_loop, app.input_queue);
furi_message_queue_free(app.input_queue);
for(size_t i = 0; i < TIMER_COUNT; ++i) {
diff --git a/applications/debug/file_browser_test/file_browser_app.c b/applications/debug/file_browser_test/file_browser_app.c
index c3e7c898bf..a502a8a90b 100644
--- a/applications/debug/file_browser_test/file_browser_app.c
+++ b/applications/debug/file_browser_test/file_browser_app.c
@@ -33,8 +33,6 @@ FileBrowserApp* file_browser_app_alloc(char* arg) {
app->dialogs = furi_record_open(RECORD_DIALOGS);
app->view_dispatcher = view_dispatcher_alloc();
- view_dispatcher_enable_queue(app->view_dispatcher);
-
app->scene_manager = scene_manager_alloc(&file_browser_scene_handlers, app);
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
diff --git a/applications/debug/file_browser_test/icons/badusb_10px.png b/applications/debug/file_browser_test/icons/badusb_10px.png
index 037474aa3b..2b5a3bf973 100644
Binary files a/applications/debug/file_browser_test/icons/badusb_10px.png and b/applications/debug/file_browser_test/icons/badusb_10px.png differ
diff --git a/applications/debug/file_browser_test/scenes/file_browser_scene_start.c b/applications/debug/file_browser_test/scenes/file_browser_scene_start.c
index 9eb26944ff..0ff6303bf5 100644
--- a/applications/debug/file_browser_test/scenes/file_browser_scene_start.c
+++ b/applications/debug/file_browser_test/scenes/file_browser_scene_start.c
@@ -19,7 +19,7 @@ bool file_browser_scene_start_on_event(void* context, SceneManagerEvent event) {
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
- furi_string_set(app->file_path, ANY_PATH("badusb/demo_windows.txt"));
+ furi_string_set(app->file_path, EXT_PATH("badusb/demo_windows.txt"));
scene_manager_next_scene(app->scene_manager, FileBrowserSceneBrowser);
consumed = true;
} else if(event.type == SceneManagerEventTypeTick) {
diff --git a/applications/debug/infrared_test/application.fam b/applications/debug/infrared_test/application.fam
new file mode 100644
index 0000000000..bfd7cd5d45
--- /dev/null
+++ b/applications/debug/infrared_test/application.fam
@@ -0,0 +1,8 @@
+App(
+ appid="infrared_test",
+ name="Infrared Test",
+ apptype=FlipperAppType.DEBUG,
+ entry_point="infrared_test_app",
+ fap_category="Debug",
+ targets=["f7"],
+)
diff --git a/applications/debug/infrared_test/infrared_test.c b/applications/debug/infrared_test/infrared_test.c
new file mode 100644
index 0000000000..0187bd49d1
--- /dev/null
+++ b/applications/debug/infrared_test/infrared_test.c
@@ -0,0 +1,61 @@
+#include
+#include
+
+#define TAG "InfraredTest"
+
+#define CARRIER_FREQ_HZ (38000UL)
+#define CARRIER_DUTY (0.33f)
+
+#define BURST_DURATION_US (600UL)
+#define BURST_COUNT (50UL)
+
+typedef struct {
+ bool level;
+ uint32_t count;
+} InfraredTestApp;
+
+static FuriHalInfraredTxGetDataState
+ infrared_test_app_tx_data_callback(void* context, uint32_t* duration, bool* level) {
+ furi_assert(context);
+ furi_assert(duration);
+ furi_assert(level);
+
+ InfraredTestApp* app = context;
+
+ *duration = BURST_DURATION_US;
+ *level = app->level;
+
+ app->level = !app->level;
+ app->count += 1;
+
+ if(app->count < BURST_COUNT * 2) {
+ return FuriHalInfraredTxGetDataStateOk;
+ } else {
+ return FuriHalInfraredTxGetDataStateLastDone;
+ }
+}
+
+int32_t infrared_test_app(void* arg) {
+ UNUSED(arg);
+
+ InfraredTestApp app = {
+ .level = true,
+ };
+
+ FURI_LOG_I(TAG, "Starting test signal on PA7");
+
+ furi_hal_infrared_set_tx_output(FuriHalInfraredTxPinExtPA7);
+ furi_hal_infrared_async_tx_set_data_isr_callback(infrared_test_app_tx_data_callback, &app);
+ furi_hal_infrared_async_tx_start(CARRIER_FREQ_HZ, CARRIER_DUTY);
+ furi_hal_infrared_async_tx_wait_termination();
+ furi_hal_infrared_set_tx_output(FuriHalInfraredTxPinInternal);
+
+ FURI_LOG_I(TAG, "Test signal end");
+ FURI_LOG_I(
+ TAG,
+ "The measured signal should be %luus +-%.1fus",
+ (app.count - 1) * BURST_DURATION_US,
+ (double)1000000.0 / CARRIER_FREQ_HZ);
+
+ return 0;
+}
diff --git a/applications/debug/lfrfid_debug/lfrfid_debug.c b/applications/debug/lfrfid_debug/lfrfid_debug.c
index 13c0b299fa..962afd1c30 100644
--- a/applications/debug/lfrfid_debug/lfrfid_debug.c
+++ b/applications/debug/lfrfid_debug/lfrfid_debug.c
@@ -17,7 +17,6 @@ static LfRfidDebug* lfrfid_debug_alloc(void) {
app->view_dispatcher = view_dispatcher_alloc();
app->scene_manager = scene_manager_alloc(&lfrfid_debug_scene_handlers, app);
- view_dispatcher_enable_queue(app->view_dispatcher);
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
view_dispatcher_set_custom_event_callback(
app->view_dispatcher, lfrfid_debug_custom_event_callback);
diff --git a/applications/debug/locale_test/locale_test.c b/applications/debug/locale_test/locale_test.c
index 1ca077db1f..51d45a6b05 100644
--- a/applications/debug/locale_test/locale_test.c
+++ b/applications/debug/locale_test/locale_test.c
@@ -61,7 +61,6 @@ static LocaleTestApp* locale_test_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);
// Views
diff --git a/applications/debug/rpc_debug_app/rpc_debug_app.c b/applications/debug/rpc_debug_app/rpc_debug_app.c
index 5e53c221e1..1536b8918e 100644
--- a/applications/debug/rpc_debug_app/rpc_debug_app.c
+++ b/applications/debug/rpc_debug_app/rpc_debug_app.c
@@ -99,7 +99,6 @@ static RpcDebugApp* rpc_debug_app_alloc(void) {
view_dispatcher_set_tick_event_callback(
app->view_dispatcher, rpc_debug_app_tick_event_callback, 100);
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
- view_dispatcher_enable_queue(app->view_dispatcher);
app->widget = widget_alloc();
view_dispatcher_add_view(
diff --git a/applications/debug/subghz_test/images/DolphinCommon_56x48.png b/applications/debug/subghz_test/images/DolphinCommon_56x48.png
index 089aaed835..9cdc2e448e 100644
Binary files a/applications/debug/subghz_test/images/DolphinCommon_56x48.png and b/applications/debug/subghz_test/images/DolphinCommon_56x48.png differ
diff --git a/applications/debug/subghz_test/subghz_test_10px.png b/applications/debug/subghz_test/subghz_test_10px.png
index 10dac0ecaa..77dc6d3829 100644
Binary files a/applications/debug/subghz_test/subghz_test_10px.png and b/applications/debug/subghz_test/subghz_test_10px.png differ
diff --git a/applications/debug/subghz_test/subghz_test_app.c b/applications/debug/subghz_test/subghz_test_app.c
index 6eba864f6e..dccdac213d 100644
--- a/applications/debug/subghz_test/subghz_test_app.c
+++ b/applications/debug/subghz_test/subghz_test_app.c
@@ -30,7 +30,6 @@ SubGhzTestApp* subghz_test_app_alloc(void) {
// View Dispatcher
app->view_dispatcher = view_dispatcher_alloc();
app->scene_manager = scene_manager_alloc(&subghz_test_scene_handlers, app);
- view_dispatcher_enable_queue(app->view_dispatcher);
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
view_dispatcher_set_custom_event_callback(
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
index 7bbcb285b8..4d63e37793 100644
--- a/applications/debug/text_box_view_test/text_box_view_test.c
+++ b/applications/debug/text_box_view_test/text_box_view_test.c
@@ -126,7 +126,6 @@ int32_t text_box_view_test_app(void* 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(),
diff --git a/applications/debug/uart_echo/uart_echo.c b/applications/debug/uart_echo/uart_echo.c
index 8e1884e9a2..bf38ba4c27 100644
--- a/applications/debug/uart_echo/uart_echo.c
+++ b/applications/debug/uart_echo/uart_echo.c
@@ -242,7 +242,6 @@ static UartEchoApp* uart_echo_app_alloc(uint32_t baudrate) {
// 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);
// Views
diff --git a/applications/debug/unit_tests/resources/unit_tests/subghz/dickert_mahs.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/dickert_mahs.sub
new file mode 100644
index 0000000000..9737b71a69
--- /dev/null
+++ b/applications/debug/unit_tests/resources/unit_tests/subghz/dickert_mahs.sub
@@ -0,0 +1,7 @@
+Filetype: Flipper SubGhz Key File
+Version: 1
+Frequency: 433920000
+Preset: FuriHalSubGhzPresetOok650Async
+Protocol: Dickert_MAHS
+Bit: 36
+Key: 00 00 00 01 55 57 55 15
diff --git a/applications/debug/unit_tests/resources/unit_tests/subghz/dickert_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/dickert_raw.sub
new file mode 100644
index 0000000000..544fc7a1d2
--- /dev/null
+++ b/applications/debug/unit_tests/resources/unit_tests/subghz/dickert_raw.sub
@@ -0,0 +1,7 @@
+Filetype: Flipper SubGhz RAW File
+Version: 1
+Frequency: 433920000
+Preset: FuriHalSubGhzPresetOok650Async
+Protocol: RAW
+RAW_Data: 112254 -62882 64 -8912 798 -844 416 -418 806 -850 396 -45206 440 -428 794 -442 804 -422 822 -810 414 -414 824 -832 412 -416 808 -848 376 -446 792 -846 382 -448 816 -828 410 -416 810 -844 382 -416 834 -818 410 -414 810 -856 408 -810 412 -836 384 -442 808 -814 402 -844 414 -834 378 -436 808 -844 396 -422 798 -844 416 -416 814 -404 812 -440 810 -842 396 -422 798 -840 414 -414 806 -850 398 -45210 450 -420 796 -436 780 -446 802 -848 380 -434 806 -846 400 -422 800 -840 410 -408 836 -812 414 -410 826 -840 378 -440 804 -848 396 -426 812 -810 426 -394 826 -844 414 -810 420 -834 378 -442 808 -832 412 -812 416 -830 410 -406 810 -844 400 -420 832 -810 414 -416 800 -446 798 -440 812 -808 426 -410 800 -836 412 -414 806 -836 412 -45216 450 -420 798 -434 806 -414 802 -846 382 -438 814 -832 410 -410 838 -834 396 -430 810 -842 394 -392 826 -840 414 -414 802 -850 396 -428 812 -842 394 -394 828 -842 414 -810 424 -812 392 -434 812 -844 398 -848 380 -844 408 -416 820 -810 414 -406 816 -836 412 -416 836 -414 816 -398 816 -840 420 -410 802 -844 416 -416 804 -824 410 -45232 446 -400 802 -442 810 -432 804 -842 396 -392 826 -842 410 -410 834 -818 378 -442 804 -854 406 -408 806 -838 408 -428 804 -844 396 -392 826 -840 410 -410 834 -810 414 -832 408 -834 380 -440 802 -826 410 -836 412 -838 396 -424 796 -842 414 -414 804 -848 396 -426 812 -412 814 -414 824 -832 410 -416 806 -848 382 -420 834 -814 422 -45228 416 -422 802 -446 810 -420 790 -846 382 -448 818 -828 408 -416 808 -848 382 -418 830 -816 410 -412 812 -856 410 -382 834 -846 382 -418 832 -818 408 -412 812 -856 408 -814 414 -838 396 -428 810 -808 424 -836 380 -844 404 -416 802 -840 424 -394 826 -840 414 -382 836 -412 822 -436 812 -806 424 -394 826 -844 416 -382 838 -816 402 -45228 438 -430 796 -444 806 -424 822 -810 412 -416 822 -832 412 -416 804 -844 408 -414 824 -812 412 -408 812 -834 410 -414 804 -848 408 -412 802 -840 424 -412 802 -834 412 -842 384 -848 396 -426 814 -808 424 -816 392 -866 382 -414 838 -816 414 -428 792 -846 380 -440 810 -438 812 -412 802 -846 380 -438 826 -840 380 -416 838 -814 404 -45226 450 -404 820 -408 806 -452 792 -848 382 -440 814 -832 410 -416 810 -846 378 -450 792 -846 380 -446 816 -830 410 -386 836 -846 376 -410 828 -846 380 -446 814 -828 410 -814 414 -836 396 -428 810 -842 394 -816 410 -836 406 -430 812 -810 426 -394 826 -838
+RAW_Data: 414 -414 808 -416 826 -438 814 -816 420 -414 834 -814 418 -418 808 -848 398 -45218 412 -438 824 -412 812 -418 832 -852 378 -446 782 -862 410 -386 838 -848 384 -420 836 -820 418 -414 814 -854 408 -388 838 -814 418 -422 836 -816 394 -434 812 -846 398 -850 380 -848 410 -418 822 -812 416 -850 368 -854 412 -418 810 -850 384 -422 834 -820 416 -414 812 -428 836 -412 804 -848 382 -450 818 -828 412 -418 808 -850 380 -45228 452 -420 798 -434 806 -416 834 -818 384 -440 810 -820 404 -420 834 -814 416 -418 834 -824 386 -442 810 -818 404 -420 834 -814 416 -418 834 -820 410 -414 810 -850 406 -812 414 -816 404 -420 818 -838 386 -848 394 -828 414 -414 838 -814 406 -420 820 -842 384 -446 794 -438 810 -412 802 -848 394 -432 812 -842 394 -392 830 -842 414 -105578 64 -1760 130 -196 130 -832 160 -128 62 -1278 194 -1316 230 -96 362 -64 64 -398
diff --git a/applications/debug/unit_tests/tests/furi/furi_event_loop.c b/applications/debug/unit_tests/tests/furi/furi_event_loop.c
index 4eeecb2b83..291181c77f 100644
--- a/applications/debug/unit_tests/tests/furi/furi_event_loop.c
+++ b/applications/debug/unit_tests/tests/furi/furi_event_loop.c
@@ -19,25 +19,24 @@ typedef struct {
uint32_t consumer_counter;
} TestFuriData;
-bool test_furi_event_loop_producer_mq_callback(FuriMessageQueue* queue, void* context) {
+bool test_furi_event_loop_producer_mq_callback(FuriEventLoopObject* object, void* context) {
furi_check(context);
TestFuriData* data = context;
- furi_check(data->mq == queue, "Invalid queue");
+ furi_check(data->mq == object, "Invalid queue");
FURI_LOG_I(
TAG, "producer_mq_callback: %lu %lu", data->producer_counter, data->consumer_counter);
- // Remove and add should not cause crash
- // if(data->producer_counter == EVENT_LOOP_EVENT_COUNT/2) {
- // furi_event_loop_message_queue_remove(data->producer_event_loop, data->mq);
- // furi_event_loop_message_queue_add(
- // data->producer_event_loop,
- // data->mq,
- // FuriEventLoopEventOut,
- // test_furi_event_loop_producer_mq_callback,
- // data);
- // }
+ if(data->producer_counter == EVENT_LOOP_EVENT_COUNT / 2) {
+ furi_event_loop_unsubscribe(data->producer_event_loop, data->mq);
+ furi_event_loop_subscribe_message_queue(
+ data->producer_event_loop,
+ data->mq,
+ FuriEventLoopEventOut,
+ test_furi_event_loop_producer_mq_callback,
+ data);
+ }
if(data->producer_counter == EVENT_LOOP_EVENT_COUNT) {
furi_event_loop_stop(data->producer_event_loop);
@@ -61,7 +60,7 @@ int32_t test_furi_event_loop_producer(void* p) {
FURI_LOG_I(TAG, "producer start 1st run");
data->producer_event_loop = furi_event_loop_alloc();
- furi_event_loop_message_queue_subscribe(
+ furi_event_loop_subscribe_message_queue(
data->producer_event_loop,
data->mq,
FuriEventLoopEventOut,
@@ -73,7 +72,7 @@ int32_t test_furi_event_loop_producer(void* p) {
// 2 EventLoop index, 0xFFFFFFFF - all possible flags, emulate uncleared flags
xTaskNotifyIndexed(xTaskGetCurrentTaskHandle(), 2, 0xFFFFFFFF, eSetBits);
- furi_event_loop_message_queue_unsubscribe(data->producer_event_loop, data->mq);
+ furi_event_loop_unsubscribe(data->producer_event_loop, data->mq);
furi_event_loop_free(data->producer_event_loop);
FURI_LOG_I(TAG, "producer start 2nd run");
@@ -81,7 +80,7 @@ int32_t test_furi_event_loop_producer(void* p) {
data->producer_counter = 0;
data->producer_event_loop = furi_event_loop_alloc();
- furi_event_loop_message_queue_subscribe(
+ furi_event_loop_subscribe_message_queue(
data->producer_event_loop,
data->mq,
FuriEventLoopEventOut,
@@ -90,7 +89,7 @@ int32_t test_furi_event_loop_producer(void* p) {
furi_event_loop_run(data->producer_event_loop);
- furi_event_loop_message_queue_unsubscribe(data->producer_event_loop, data->mq);
+ furi_event_loop_unsubscribe(data->producer_event_loop, data->mq);
furi_event_loop_free(data->producer_event_loop);
FURI_LOG_I(TAG, "producer end");
@@ -98,11 +97,11 @@ int32_t test_furi_event_loop_producer(void* p) {
return 0;
}
-bool test_furi_event_loop_consumer_mq_callback(FuriMessageQueue* queue, void* context) {
+bool test_furi_event_loop_consumer_mq_callback(FuriEventLoopObject* object, void* context) {
furi_check(context);
TestFuriData* data = context;
- furi_check(data->mq == queue);
+ furi_check(data->mq == object);
furi_delay_us(furi_hal_random_get() % 1000);
furi_check(furi_message_queue_get(data->mq, &data->consumer_counter, 0) == FuriStatusOk);
@@ -110,16 +109,15 @@ bool test_furi_event_loop_consumer_mq_callback(FuriMessageQueue* queue, void* co
FURI_LOG_I(
TAG, "consumer_mq_callback: %lu %lu", data->producer_counter, data->consumer_counter);
- // Remove and add should not cause crash
- // if(data->producer_counter == EVENT_LOOP_EVENT_COUNT/2) {
- // furi_event_loop_message_queue_remove(data->consumer_event_loop, data->mq);
- // furi_event_loop_message_queue_add(
- // data->consumer_event_loop,
- // data->mq,
- // FuriEventLoopEventIn,
- // test_furi_event_loop_producer_mq_callback,
- // data);
- // }
+ if(data->consumer_counter == EVENT_LOOP_EVENT_COUNT / 2) {
+ furi_event_loop_unsubscribe(data->consumer_event_loop, data->mq);
+ furi_event_loop_subscribe_message_queue(
+ data->consumer_event_loop,
+ data->mq,
+ FuriEventLoopEventIn,
+ test_furi_event_loop_consumer_mq_callback,
+ data);
+ }
if(data->consumer_counter == EVENT_LOOP_EVENT_COUNT) {
furi_event_loop_stop(data->consumer_event_loop);
@@ -137,7 +135,7 @@ int32_t test_furi_event_loop_consumer(void* p) {
FURI_LOG_I(TAG, "consumer start 1st run");
data->consumer_event_loop = furi_event_loop_alloc();
- furi_event_loop_message_queue_subscribe(
+ furi_event_loop_subscribe_message_queue(
data->consumer_event_loop,
data->mq,
FuriEventLoopEventIn,
@@ -149,14 +147,14 @@ int32_t test_furi_event_loop_consumer(void* p) {
// 2 EventLoop index, 0xFFFFFFFF - all possible flags, emulate uncleared flags
xTaskNotifyIndexed(xTaskGetCurrentTaskHandle(), 2, 0xFFFFFFFF, eSetBits);
- furi_event_loop_message_queue_unsubscribe(data->consumer_event_loop, data->mq);
+ furi_event_loop_unsubscribe(data->consumer_event_loop, data->mq);
furi_event_loop_free(data->consumer_event_loop);
FURI_LOG_I(TAG, "consumer start 2nd run");
data->consumer_counter = 0;
data->consumer_event_loop = furi_event_loop_alloc();
- furi_event_loop_message_queue_subscribe(
+ furi_event_loop_subscribe_message_queue(
data->consumer_event_loop,
data->mq,
FuriEventLoopEventIn,
@@ -165,7 +163,7 @@ int32_t test_furi_event_loop_consumer(void* p) {
furi_event_loop_run(data->consumer_event_loop);
- furi_event_loop_message_queue_unsubscribe(data->consumer_event_loop, data->mq);
+ furi_event_loop_unsubscribe(data->consumer_event_loop, data->mq);
furi_event_loop_free(data->consumer_event_loop);
FURI_LOG_I(TAG, "consumer end");
diff --git a/applications/debug/unit_tests/tests/rpc/rpc_test.c b/applications/debug/unit_tests/tests/rpc/rpc_test.c
index 63ea706ed6..5d26bdb306 100644
--- a/applications/debug/unit_tests/tests/rpc/rpc_test.c
+++ b/applications/debug/unit_tests/tests/rpc/rpc_test.c
@@ -8,6 +8,7 @@
#include
#include
+#include
#include
#include
@@ -35,8 +36,8 @@ static uint32_t command_id = 0;
typedef struct {
RpcSession* session;
FuriStreamBuffer* output_stream;
- FuriSemaphore* close_session_semaphore;
- FuriSemaphore* terminate_semaphore;
+ FuriApiLock session_close_lock;
+ FuriApiLock session_terminate_lock;
uint32_t timeout;
} RpcSessionContext;
@@ -92,8 +93,8 @@ static void test_rpc_setup(void) {
rpc_session[0].output_stream = furi_stream_buffer_alloc(4096, 1);
rpc_session_set_send_bytes_callback(rpc_session[0].session, output_bytes_callback);
- rpc_session[0].close_session_semaphore = furi_semaphore_alloc(1, 0);
- rpc_session[0].terminate_semaphore = furi_semaphore_alloc(1, 0);
+ rpc_session[0].session_close_lock = api_lock_alloc_locked();
+ rpc_session[0].session_terminate_lock = api_lock_alloc_locked();
rpc_session_set_close_callback(rpc_session[0].session, test_rpc_session_close_callback);
rpc_session_set_terminated_callback(
rpc_session[0].session, test_rpc_session_terminated_callback);
@@ -112,8 +113,8 @@ static void test_rpc_setup_second_session(void) {
rpc_session[1].output_stream = furi_stream_buffer_alloc(1000, 1);
rpc_session_set_send_bytes_callback(rpc_session[1].session, output_bytes_callback);
- rpc_session[1].close_session_semaphore = furi_semaphore_alloc(1, 0);
- rpc_session[1].terminate_semaphore = furi_semaphore_alloc(1, 0);
+ rpc_session[1].session_close_lock = api_lock_alloc_locked();
+ rpc_session[1].session_terminate_lock = api_lock_alloc_locked();
rpc_session_set_close_callback(rpc_session[1].session, test_rpc_session_close_callback);
rpc_session_set_terminated_callback(
rpc_session[1].session, test_rpc_session_terminated_callback);
@@ -121,36 +122,32 @@ static void test_rpc_setup_second_session(void) {
}
static void test_rpc_teardown(void) {
- furi_check(rpc_session[0].close_session_semaphore);
- furi_semaphore_acquire(rpc_session[0].terminate_semaphore, 0);
+ furi_check(rpc_session[0].session_close_lock);
+ api_lock_relock(rpc_session[0].session_terminate_lock);
rpc_session_close(rpc_session[0].session);
- furi_check(
- furi_semaphore_acquire(rpc_session[0].terminate_semaphore, FuriWaitForever) ==
- FuriStatusOk);
+ api_lock_wait_unlock(rpc_session[0].session_terminate_lock);
furi_record_close(RECORD_RPC);
furi_stream_buffer_free(rpc_session[0].output_stream);
- furi_semaphore_free(rpc_session[0].close_session_semaphore);
- furi_semaphore_free(rpc_session[0].terminate_semaphore);
+ api_lock_free(rpc_session[0].session_close_lock);
+ api_lock_free(rpc_session[0].session_terminate_lock);
++command_id;
rpc_session[0].output_stream = NULL;
- rpc_session[0].close_session_semaphore = NULL;
+ rpc_session[0].session_close_lock = NULL;
rpc = NULL;
rpc_session[0].session = NULL;
}
static void test_rpc_teardown_second_session(void) {
- furi_check(rpc_session[1].close_session_semaphore);
- furi_semaphore_acquire(rpc_session[1].terminate_semaphore, 0);
+ furi_check(rpc_session[1].session_close_lock);
+ api_lock_relock(rpc_session[1].session_terminate_lock);
rpc_session_close(rpc_session[1].session);
- furi_check(
- furi_semaphore_acquire(rpc_session[1].terminate_semaphore, FuriWaitForever) ==
- FuriStatusOk);
+ api_lock_wait_unlock(rpc_session[1].session_terminate_lock);
furi_stream_buffer_free(rpc_session[1].output_stream);
- furi_semaphore_free(rpc_session[1].close_session_semaphore);
- furi_semaphore_free(rpc_session[1].terminate_semaphore);
+ api_lock_free(rpc_session[1].session_close_lock);
+ api_lock_free(rpc_session[1].session_terminate_lock);
++command_id;
rpc_session[1].output_stream = NULL;
- rpc_session[1].close_session_semaphore = NULL;
+ rpc_session[1].session_close_lock = NULL;
rpc_session[1].session = NULL;
}
@@ -204,14 +201,14 @@ static void test_rpc_session_close_callback(void* context) {
furi_check(context);
RpcSessionContext* callbacks_context = context;
- furi_check(furi_semaphore_release(callbacks_context->close_session_semaphore) == FuriStatusOk);
+ api_lock_unlock(callbacks_context->session_close_lock);
}
static void test_rpc_session_terminated_callback(void* context) {
furi_check(context);
RpcSessionContext* callbacks_context = context;
- furi_check(furi_semaphore_release(callbacks_context->terminate_semaphore) == FuriStatusOk);
+ api_lock_unlock(callbacks_context->session_terminate_lock);
}
static void test_rpc_print_message_list(MsgList_t msg_list) {
@@ -1645,7 +1642,7 @@ static void test_rpc_feed_rubbish_run(
test_rpc_add_empty_to_list(expected, PB_CommandStatus_ERROR_DECODE, 0);
- furi_check(furi_semaphore_acquire(rpc_session[0].close_session_semaphore, 0) != FuriStatusOk);
+ furi_check(api_lock_is_locked(rpc_session[0].session_close_lock));
test_rpc_encode_and_feed(input_before, 0);
test_send_rubbish(rpc_session[0].session, pattern, pattern_size, size);
test_rpc_encode_and_feed(input_after, 0);
diff --git a/applications/debug/unit_tests/tests/subghz/subghz_test.c b/applications/debug/unit_tests/tests/subghz/subghz_test.c
index 3e93ac4c3b..abbcbd2b08 100644
--- a/applications/debug/unit_tests/tests/subghz/subghz_test.c
+++ b/applications/debug/unit_tests/tests/subghz/subghz_test.c
@@ -663,6 +663,13 @@ MU_TEST(subghz_decoder_mastercode_test) {
"Test decoder " SUBGHZ_PROTOCOL_MASTERCODE_NAME " error\r\n");
}
+MU_TEST(subghz_decoder_dickert_test) {
+ mu_assert(
+ subghz_decoder_test(
+ EXT_PATH("unit_tests/subghz/dickert_raw.sub"), SUBGHZ_PROTOCOL_DICKERT_MAHS_NAME),
+ "Test decoder " SUBGHZ_PROTOCOL_DICKERT_MAHS_NAME " error\r\n");
+}
+
//test encoders
MU_TEST(subghz_encoder_princeton_test) {
mu_assert(
@@ -820,6 +827,12 @@ MU_TEST(subghz_encoder_mastercode_test) {
"Test encoder " SUBGHZ_PROTOCOL_MASTERCODE_NAME " error\r\n");
}
+MU_TEST(subghz_encoder_dickert_test) {
+ mu_assert(
+ subghz_encoder_test(EXT_PATH("unit_tests/subghz/dickert_mahs.sub")),
+ "Test encoder " SUBGHZ_PROTOCOL_DICKERT_MAHS_NAME " error\r\n");
+}
+
MU_TEST(subghz_random_test) {
mu_assert(subghz_decode_random_test(TEST_RANDOM_DIR_NAME), "Random test error\r\n");
}
@@ -871,6 +884,7 @@ MU_TEST_SUITE(subghz) {
MU_RUN_TEST(subghz_decoder_nice_one_test);
MU_RUN_TEST(subghz_decoder_kinggates_stylo4k_test);
MU_RUN_TEST(subghz_decoder_mastercode_test);
+ MU_RUN_TEST(subghz_decoder_dickert_test);
MU_RUN_TEST(subghz_encoder_princeton_test);
MU_RUN_TEST(subghz_encoder_came_test);
@@ -898,6 +912,7 @@ MU_TEST_SUITE(subghz) {
MU_RUN_TEST(subghz_encoder_holtek_ht12x_test);
MU_RUN_TEST(subghz_encoder_dooya_test);
MU_RUN_TEST(subghz_encoder_mastercode_test);
+ MU_RUN_TEST(subghz_encoder_dickert_test);
MU_RUN_TEST(subghz_random_test);
subghz_test_deinit();
diff --git a/applications/debug/unit_tests/unit_test_api_table_i.h b/applications/debug/unit_tests/unit_test_api_table_i.h
index 1adec4db26..50524e5b7d 100644
--- a/applications/debug/unit_tests/unit_test_api_table_i.h
+++ b/applications/debug/unit_tests/unit_test_api_table_i.h
@@ -36,14 +36,10 @@ static constexpr auto unit_tests_api_table = sort(create_array_t(
API_METHOD(furi_event_loop_alloc, FuriEventLoop*, (void)),
API_METHOD(furi_event_loop_free, void, (FuriEventLoop*)),
API_METHOD(
- furi_event_loop_message_queue_subscribe,
+ furi_event_loop_subscribe_message_queue,
void,
- (FuriEventLoop*,
- FuriMessageQueue*,
- FuriEventLoopEvent,
- FuriEventLoopMessageQueueCallback,
- void*)),
- API_METHOD(furi_event_loop_message_queue_unsubscribe, void, (FuriEventLoop*, FuriMessageQueue*)),
+ (FuriEventLoop*, FuriMessageQueue*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*)),
+ API_METHOD(furi_event_loop_unsubscribe, void, (FuriEventLoop*, FuriEventLoopObject*)),
API_METHOD(furi_event_loop_run, void, (FuriEventLoop*)),
API_METHOD(furi_event_loop_stop, void, (FuriEventLoop*)),
API_VARIABLE(PB_Main_msg, PB_Main_msg_t)));
diff --git a/applications/debug/usb_test/usb_test.c b/applications/debug/usb_test/usb_test.c
index ddec9d9b05..a71ac3c6e4 100644
--- a/applications/debug/usb_test/usb_test.c
+++ b/applications/debug/usb_test/usb_test.c
@@ -63,7 +63,6 @@ UsbTestApp* usb_test_app_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);
// Views
diff --git a/applications/examples/example_ble_beacon/ble_beacon_app.c b/applications/examples/example_ble_beacon/ble_beacon_app.c
index faa3feb915..16979543c8 100644
--- a/applications/examples/example_ble_beacon/ble_beacon_app.c
+++ b/applications/examples/example_ble_beacon/ble_beacon_app.c
@@ -75,7 +75,6 @@ static BleBeaconApp* ble_beacon_app_alloc(void) {
view_dispatcher_set_tick_event_callback(
app->view_dispatcher, ble_beacon_app_tick_event_callback, 100);
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
- view_dispatcher_enable_queue(app->view_dispatcher);
app->submenu = submenu_alloc();
view_dispatcher_add_view(
diff --git a/applications/examples/example_ble_beacon/example_ble_beacon_10px.png b/applications/examples/example_ble_beacon/example_ble_beacon_10px.png
index 7060e893db..c6aff41893 100644
Binary files a/applications/examples/example_ble_beacon/example_ble_beacon_10px.png and b/applications/examples/example_ble_beacon/example_ble_beacon_10px.png differ
diff --git a/applications/examples/example_ble_beacon/images/lighthouse_35x44.png b/applications/examples/example_ble_beacon/images/lighthouse_35x44.png
index 4cf4d19c57..8ca6d664d0 100644
Binary files a/applications/examples/example_ble_beacon/images/lighthouse_35x44.png and b/applications/examples/example_ble_beacon/images/lighthouse_35x44.png differ
diff --git a/applications/examples/example_event_loop/application.fam b/applications/examples/example_event_loop/application.fam
new file mode 100644
index 0000000000..a37ffb1a04
--- /dev/null
+++ b/applications/examples/example_event_loop/application.fam
@@ -0,0 +1,36 @@
+App(
+ appid="example_event_loop_timer",
+ name="Example: Event Loop Timer",
+ apptype=FlipperAppType.EXTERNAL,
+ sources=["example_event_loop_timer.c"],
+ entry_point="example_event_loop_timer_app",
+ fap_category="Examples",
+)
+
+App(
+ appid="example_event_loop_mutex",
+ name="Example: Event Loop Mutex",
+ apptype=FlipperAppType.EXTERNAL,
+ sources=["example_event_loop_mutex.c"],
+ entry_point="example_event_loop_mutex_app",
+ fap_category="Examples",
+)
+
+App(
+ appid="example_event_loop_stream_buffer",
+ name="Example: Event Loop Stream Buffer",
+ apptype=FlipperAppType.EXTERNAL,
+ sources=["example_event_loop_stream_buffer.c"],
+ entry_point="example_event_loop_stream_buffer_app",
+ fap_category="Examples",
+)
+
+App(
+ appid="example_event_loop_multi",
+ name="Example: Event Loop Multi",
+ apptype=FlipperAppType.EXTERNAL,
+ sources=["example_event_loop_multi.c"],
+ entry_point="example_event_loop_multi_app",
+ requires=["gui"],
+ fap_category="Examples",
+)
diff --git a/applications/examples/example_event_loop/example_event_loop_multi.c b/applications/examples/example_event_loop/example_event_loop_multi.c
new file mode 100644
index 0000000000..ebfb009118
--- /dev/null
+++ b/applications/examples/example_event_loop/example_event_loop_multi.c
@@ -0,0 +1,342 @@
+/**
+ * @file example_event_loop_multi.c
+ * @brief Example application that demonstrates multiple primitives used with two FuriEventLoop instances.
+ *
+ * This application simulates a complex use case of having two concurrent event loops (each one executing in
+ * its own thread) using a stream buffer for communication and additional timers and message passing to handle
+ * the keypad input. Additionally, it shows how to use thread signals to stop an event loop in another thread.
+ * The GUI functionality is there only for the purpose of exclusive access to the input events.
+ *
+ * The application's functionality consists of the following:
+ * - Print keypad key names and types when pressed,
+ * - If the Back key is long-pressed, a countdown starts upon completion of which the app exits,
+ * - The countdown can be cancelled by long-pressing the Ok button, it also resets the counter,
+ * - Blocks of random data are periodically generated in a separate thread,
+ * - When ready, the main application thread gets notified and prints the data.
+ */
+
+#include
+#include
+#include
+
+#include
+
+#define TAG "ExampleEventLoopMulti"
+
+#define COUNTDOWN_START_VALUE (5UL)
+#define COUNTDOWN_INTERVAL_MS (1000UL)
+#define WORKER_DATA_INTERVAL_MS (1500UL)
+
+#define INPUT_QUEUE_SIZE (8)
+#define STREAM_BUFFER_SIZE (16)
+
+typedef struct {
+ FuriEventLoop* event_loop;
+ FuriEventLoopTimer* timer;
+ FuriStreamBuffer* stream_buffer;
+} EventLoopMultiAppWorker;
+
+typedef struct {
+ Gui* gui;
+ ViewPort* view_port;
+ FuriThread* worker_thread;
+ FuriEventLoop* event_loop;
+ FuriMessageQueue* input_queue;
+ FuriEventLoopTimer* exit_timer;
+ FuriStreamBuffer* stream_buffer;
+ uint32_t exit_countdown_value;
+} EventLoopMultiApp;
+
+/*
+ * Worker functions
+ */
+
+// This function is executed each time the data is taken out of the stream buffer. It is used to restart the worker timer.
+static bool
+ event_loop_multi_app_stream_buffer_worker_callback(FuriEventLoopObject* object, void* context) {
+ furi_assert(context);
+ EventLoopMultiAppWorker* worker = context;
+
+ furi_assert(object == worker->stream_buffer);
+
+ FURI_LOG_I(TAG, "Data was removed from buffer");
+ // Restart the timer to generate another block of random data.
+ furi_event_loop_timer_start(worker->timer, WORKER_DATA_INTERVAL_MS);
+
+ return true;
+}
+
+// This function is executed when the worker timer expires. The timer will NOT restart automatically
+// since it is of one-shot type.
+static void event_loop_multi_app_worker_timer_callback(void* context) {
+ furi_assert(context);
+ EventLoopMultiAppWorker* worker = context;
+
+ // Generate a block of random data.
+ uint8_t data[STREAM_BUFFER_SIZE];
+ furi_hal_random_fill_buf(data, sizeof(data));
+ // Put the generated data in the stream buffer.
+ // IMPORTANT: No waiting in the event handlers!
+ furi_check(
+ furi_stream_buffer_send(worker->stream_buffer, &data, sizeof(data), 0) == sizeof(data));
+}
+
+static EventLoopMultiAppWorker*
+ event_loop_multi_app_worker_alloc(FuriStreamBuffer* stream_buffer) {
+ EventLoopMultiAppWorker* worker = malloc(sizeof(EventLoopMultiAppWorker));
+ // Create the worker event loop.
+ worker->event_loop = furi_event_loop_alloc();
+ // Create the timer governing the data generation.
+ // It is of one-shot type, i.e. it will not restart automatically upon expiration.
+ worker->timer = furi_event_loop_timer_alloc(
+ worker->event_loop,
+ event_loop_multi_app_worker_timer_callback,
+ FuriEventLoopTimerTypeOnce,
+ worker);
+
+ // Using the same stream buffer as the main thread (it was already created beforehand).
+ worker->stream_buffer = stream_buffer;
+ // Notify the worker event loop about data being taken out of the stream buffer.
+ furi_event_loop_subscribe_stream_buffer(
+ worker->event_loop,
+ worker->stream_buffer,
+ FuriEventLoopEventOut | FuriEventLoopEventFlagEdge,
+ event_loop_multi_app_stream_buffer_worker_callback,
+ worker);
+
+ return worker;
+}
+
+static void event_loop_multi_app_worker_free(EventLoopMultiAppWorker* worker) {
+ // IMPORTANT: The user code MUST unsubscribe from all events before deleting the event loop.
+ // Failure to do so will result in a crash.
+ furi_event_loop_unsubscribe(worker->event_loop, worker->stream_buffer);
+ // IMPORTANT: All timers MUST be deleted before deleting the associated event loop.
+ // Failure to do so will result in a crash.
+ furi_event_loop_timer_free(worker->timer);
+ // Now it is okay to delete the event loop.
+ furi_event_loop_free(worker->event_loop);
+
+ free(worker);
+}
+
+static void event_loop_multi_app_worker_run(EventLoopMultiAppWorker* worker) {
+ furi_event_loop_timer_start(worker->timer, WORKER_DATA_INTERVAL_MS);
+ furi_event_loop_run(worker->event_loop);
+}
+
+// This function is the worker thread body and (obviously) is executed in the worker thread.
+static int32_t event_loop_multi_app_worker_thread(void* context) {
+ furi_assert(context);
+ EventLoopMultiApp* app = context;
+
+ // Because an event loop is used, it MUST be created in the thread it will be run in.
+ // Therefore, the worker creation and deletion is handled in the worker thread.
+ EventLoopMultiAppWorker* worker = event_loop_multi_app_worker_alloc(app->stream_buffer);
+ event_loop_multi_app_worker_run(worker);
+ event_loop_multi_app_worker_free(worker);
+
+ return 0;
+}
+
+/*
+ * Main application functions
+ */
+
+// This function is executed in the GUI context each time an input event occurs (e.g. the user pressed a key)
+static void event_loop_multi_app_input_callback(InputEvent* event, void* context) {
+ furi_assert(context);
+ EventLoopMultiApp* app = context;
+ // Pass the event to the the application's input queue
+ furi_check(furi_message_queue_put(app->input_queue, event, FuriWaitForever) == FuriStatusOk);
+}
+
+// This function is executed each time new data is available in the stream buffer.
+static bool
+ event_loop_multi_app_stream_buffer_callback(FuriEventLoopObject* object, void* context) {
+ furi_assert(context);
+ EventLoopMultiApp* app = context;
+
+ furi_assert(object == app->stream_buffer);
+ // Get the data from the stream buffer
+ uint8_t data[STREAM_BUFFER_SIZE];
+ // IMPORTANT: No waiting in the event handlers!
+ furi_check(
+ furi_stream_buffer_receive(app->stream_buffer, &data, sizeof(data), 0) == sizeof(data));
+
+ // Format the data for printing and print it to the debug output.
+ FuriString* tmp_str = furi_string_alloc();
+ for(uint32_t i = 0; i < sizeof(data); ++i) {
+ furi_string_cat_printf(tmp_str, "%02X ", data[i]);
+ }
+
+ FURI_LOG_I(TAG, "Received data: %s", furi_string_get_cstr(tmp_str));
+ furi_string_free(tmp_str);
+
+ return true;
+}
+
+// This function is executed each time a new message is inserted in the input queue.
+static bool event_loop_multi_app_input_queue_callback(FuriEventLoopObject* object, void* context) {
+ furi_assert(context);
+ EventLoopMultiApp* app = context;
+
+ furi_assert(object == app->input_queue);
+
+ InputEvent event;
+ // IMPORTANT: No waiting in the event handlers!
+ furi_check(furi_message_queue_get(app->input_queue, &event, 0) == FuriStatusOk);
+
+ if(event.type == InputTypeLong) {
+ // The user has long-pressed the Back key, try starting the countdown.
+ if(event.key == InputKeyBack) {
+ if(!furi_event_loop_timer_is_running(app->exit_timer)) {
+ // Actually start the countdown
+ FURI_LOG_I(TAG, "Starting exit countdown!");
+ furi_event_loop_timer_start(app->exit_timer, COUNTDOWN_INTERVAL_MS);
+
+ } else {
+ // The countdown is already in progress, print a warning message
+ FURI_LOG_W(TAG, "Countdown has already been started");
+ }
+
+ // The user has long-pressed the Ok key, try stopping the countdown.
+ } else if(event.key == InputKeyOk) {
+ if(furi_event_loop_timer_is_running(app->exit_timer)) {
+ // Actually cancel the countdown
+ FURI_LOG_I(TAG, "Exit countdown cancelled!");
+ app->exit_countdown_value = COUNTDOWN_START_VALUE;
+ furi_event_loop_timer_stop(app->exit_timer);
+
+ } else {
+ // The countdown is not running, print a warning message
+ FURI_LOG_W(TAG, "Countdown has not been started yet");
+ }
+
+ } else {
+ // Not a Back or Ok key, just print its name.
+ FURI_LOG_I(TAG, "Long press: %s", input_get_key_name(event.key));
+ }
+
+ } else if(event.type == InputTypeShort) {
+ // Not a long press, just print the key's name.
+ FURI_LOG_I(TAG, "Short press: %s", input_get_key_name(event.key));
+ }
+
+ return true;
+}
+
+// This function is executed each time the countdown timer expires.
+static void event_loop_multi_app_exit_timer_callback(void* context) {
+ furi_assert(context);
+ EventLoopMultiApp* app = context;
+
+ FURI_LOG_I(TAG, "Exiting in %lu ...", app->exit_countdown_value);
+
+ // If the coundown value has reached 0, exit the application
+ if(app->exit_countdown_value == 0) {
+ FURI_LOG_I(TAG, "Exiting NOW!");
+
+ // Send a signal to the worker thread to exit.
+ // A signal handler that handles FuriSignalExit is already set by default.
+ furi_thread_signal(app->worker_thread, FuriSignalExit, NULL);
+ // Request the application event loop to stop.
+ furi_event_loop_stop(app->event_loop);
+
+ // Otherwise just decrement it and wait for the next time the timer expires.
+ } else {
+ app->exit_countdown_value -= 1;
+ }
+}
+
+static EventLoopMultiApp* event_loop_multi_app_alloc(void) {
+ EventLoopMultiApp* app = malloc(sizeof(EventLoopMultiApp));
+ // Create event loop instances.
+ app->event_loop = furi_event_loop_alloc();
+
+ // Create a worker thread instance. The worker event loop will execute inside it.
+ app->worker_thread = furi_thread_alloc_ex(
+ "EventLoopMultiWorker", 1024, event_loop_multi_app_worker_thread, app);
+ // Create a message queue to receive the input events.
+ app->input_queue = furi_message_queue_alloc(INPUT_QUEUE_SIZE, sizeof(InputEvent));
+ // Create a stream buffer to receive the generated data.
+ app->stream_buffer = furi_stream_buffer_alloc(STREAM_BUFFER_SIZE, STREAM_BUFFER_SIZE);
+ // Create a timer to run the countdown.
+ app->exit_timer = furi_event_loop_timer_alloc(
+ app->event_loop,
+ event_loop_multi_app_exit_timer_callback,
+ FuriEventLoopTimerTypePeriodic,
+ app);
+
+ app->gui = furi_record_open(RECORD_GUI);
+ app->view_port = view_port_alloc();
+ // Start the countdown from this value
+ app->exit_countdown_value = COUNTDOWN_START_VALUE;
+ // Gain exclusive access to the input events
+ view_port_input_callback_set(app->view_port, event_loop_multi_app_input_callback, app);
+ gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
+ // Notify the event loop about incoming messages in the queue
+ furi_event_loop_subscribe_message_queue(
+ app->event_loop,
+ app->input_queue,
+ FuriEventLoopEventIn,
+ event_loop_multi_app_input_queue_callback,
+ app);
+ // Notify the event loop about new data in the stream buffer
+ furi_event_loop_subscribe_stream_buffer(
+ app->event_loop,
+ app->stream_buffer,
+ FuriEventLoopEventIn | FuriEventLoopEventFlagEdge,
+ event_loop_multi_app_stream_buffer_callback,
+ app);
+
+ return app;
+}
+
+static void event_loop_multi_app_free(EventLoopMultiApp* app) {
+ gui_remove_view_port(app->gui, app->view_port);
+ furi_record_close(RECORD_GUI);
+ // IMPORTANT: The user code MUST unsubscribe from all events before deleting the event loop.
+ // Failure to do so will result in a crash.
+ furi_event_loop_unsubscribe(app->event_loop, app->input_queue);
+ furi_event_loop_unsubscribe(app->event_loop, app->stream_buffer);
+ // Delete all instances
+ view_port_free(app->view_port);
+ furi_message_queue_free(app->input_queue);
+ furi_stream_buffer_free(app->stream_buffer);
+ // IMPORTANT: All timers MUST be deleted before deleting the associated event loop.
+ // Failure to do so will result in a crash.
+ furi_event_loop_timer_free(app->exit_timer);
+ furi_thread_free(app->worker_thread);
+ furi_event_loop_free(app->event_loop);
+
+ free(app);
+}
+
+static void event_loop_multi_app_run(EventLoopMultiApp* app) {
+ FURI_LOG_I(TAG, "Press keys to see them printed here.");
+ FURI_LOG_I(TAG, "Long press \"Back\" to exit after %lu seconds.", COUNTDOWN_START_VALUE);
+ FURI_LOG_I(TAG, "Long press \"Ok\" to cancel the countdown.");
+
+ // Start the worker thread
+ furi_thread_start(app->worker_thread);
+ // Run the application event loop. This call will block until the application is about to exit.
+ furi_event_loop_run(app->event_loop);
+ // Wait for the worker thread to finish.
+ furi_thread_join(app->worker_thread);
+}
+
+/*******************************************************************
+ * vvv START HERE vvv
+ *
+ * The application's entry point - referenced in application.fam
+ *******************************************************************/
+int32_t example_event_loop_multi_app(void* arg) {
+ UNUSED(arg);
+
+ EventLoopMultiApp* app = event_loop_multi_app_alloc();
+ event_loop_multi_app_run(app);
+ event_loop_multi_app_free(app);
+
+ return 0;
+}
diff --git a/applications/examples/example_event_loop/example_event_loop_mutex.c b/applications/examples/example_event_loop/example_event_loop_mutex.c
new file mode 100644
index 0000000000..d043f3f899
--- /dev/null
+++ b/applications/examples/example_event_loop/example_event_loop_mutex.c
@@ -0,0 +1,140 @@
+/**
+ * @file example_event_loop_mutex.c
+ * @brief Example application that demonstrates the FuriEventLoop and FuriMutex integration.
+ *
+ * This application simulates a use case where a time-consuming blocking operation is executed
+ * in a separate thread and a mutex is being used for synchronization. The application runs 10 iterations
+ * of the above mentioned simulated work and prints the results to the debug output each time, then exits.
+ */
+
+#include
+#include
+
+#define TAG "ExampleEventLoopMutex"
+
+#define WORKER_ITERATION_COUNT (10)
+// We are interested in IN events (for the mutex, that means that the mutex has been released),
+// using edge trigger mode (reacting only to changes in mutex state) and
+// employing one-shot mode to automatically unsubscribe before the event is processed.
+#define MUTEX_EVENT_AND_FLAGS \
+ (FuriEventLoopEventIn | FuriEventLoopEventFlagEdge | FuriEventLoopEventFlagOnce)
+
+typedef struct {
+ FuriEventLoop* event_loop;
+ FuriThread* worker_thread;
+ FuriMutex* worker_mutex;
+ uint8_t worker_result;
+} EventLoopMutexApp;
+
+// This funciton is being run in a separate thread to simulate lenghty blocking operations
+static int32_t event_loop_mutex_app_worker_thread(void* context) {
+ furi_assert(context);
+ EventLoopMutexApp* app = context;
+
+ FURI_LOG_I(TAG, "Worker thread started");
+
+ // Run 10 iterations of simulated work
+ for(uint32_t i = 0; i < WORKER_ITERATION_COUNT; ++i) {
+ FURI_LOG_I(TAG, "Doing work ...");
+ // Take the mutex so that no-one can access the worker_result variable
+ furi_check(furi_mutex_acquire(app->worker_mutex, FuriWaitForever) == FuriStatusOk);
+ // Simulate a blocking operation with a random delay between 900 and 1100 ms
+ const uint32_t work_time_ms = 900 + furi_hal_random_get() % 200;
+ furi_delay_ms(work_time_ms);
+ // Simulate a result with a random number between 0 and 255
+ app->worker_result = furi_hal_random_get() % 0xFF;
+
+ FURI_LOG_I(TAG, "Work done in %lu ms", work_time_ms);
+ // Release the mutex, which will notify the event loop that the result is ready
+ furi_check(furi_mutex_release(app->worker_mutex) == FuriStatusOk);
+ // Return control to the scheduler so that the event loop can take the mutex in its turn
+ furi_thread_yield();
+ }
+
+ FURI_LOG_I(TAG, "All work done, worker thread out!");
+ // Request the event loop to stop
+ furi_event_loop_stop(app->event_loop);
+
+ return 0;
+}
+
+// This function is being run each time when the mutex gets released
+static bool event_loop_mutex_app_event_callback(FuriEventLoopObject* object, void* context) {
+ furi_assert(context);
+
+ EventLoopMutexApp* app = context;
+ furi_assert(object == app->worker_mutex);
+
+ // Take the mutex so that no-one can access the worker_result variable
+ // IMPORTANT: the wait time MUST be 0, i.e. the event loop event callbacks
+ // must NOT ever block. If it is possible that the mutex will be taken by
+ // others, then the event callback code must take it into account.
+ furi_check(furi_mutex_acquire(app->worker_mutex, 0) == FuriStatusOk);
+ // Access the worker_result variable and print it.
+ FURI_LOG_I(TAG, "Result available! Value: %u", app->worker_result);
+ // Release the mutex, enabling the worker thread to continue when it's ready
+ furi_check(furi_mutex_release(app->worker_mutex) == FuriStatusOk);
+ // Subscribe for the mutex release events again, since we were unsubscribed automatically
+ // before processing the event.
+ furi_event_loop_subscribe_mutex(
+ app->event_loop,
+ app->worker_mutex,
+ MUTEX_EVENT_AND_FLAGS,
+ event_loop_mutex_app_event_callback,
+ app);
+
+ return true;
+}
+
+static EventLoopMutexApp* event_loop_mutex_app_alloc(void) {
+ EventLoopMutexApp* app = malloc(sizeof(EventLoopMutexApp));
+
+ // Create an event loop instance.
+ app->event_loop = furi_event_loop_alloc();
+ // Create a worker thread instance.
+ app->worker_thread = furi_thread_alloc_ex(
+ "EventLoopMutexWorker", 1024, event_loop_mutex_app_worker_thread, app);
+ // Create a mutex instance.
+ app->worker_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
+ // Subscribe for the mutex release events.
+ // Note that since FuriEventLoopEventFlagOneShot is used, we will be automatically unsubscribed
+ // from events before entering the event processing callback. This is necessary in order to not
+ // trigger on events caused by releasing the mutex in the callback.
+ furi_event_loop_subscribe_mutex(
+ app->event_loop,
+ app->worker_mutex,
+ MUTEX_EVENT_AND_FLAGS,
+ event_loop_mutex_app_event_callback,
+ app);
+
+ return app;
+}
+
+static void event_loop_mutex_app_free(EventLoopMutexApp* app) {
+ // IMPORTANT: The user code MUST unsubscribe from all events before deleting the event loop.
+ // Failure to do so will result in a crash.
+ furi_event_loop_unsubscribe(app->event_loop, app->worker_mutex);
+ // Delete all instances
+ furi_thread_free(app->worker_thread);
+ furi_mutex_free(app->worker_mutex);
+ furi_event_loop_free(app->event_loop);
+
+ free(app);
+}
+
+static void event_loop_mutex_app_run(EventLoopMutexApp* app) {
+ furi_thread_start(app->worker_thread);
+ furi_event_loop_run(app->event_loop);
+ furi_thread_join(app->worker_thread);
+}
+
+// The application's entry point - referenced in application.fam
+int32_t example_event_loop_mutex_app(void* arg) {
+ UNUSED(arg);
+
+ EventLoopMutexApp* app = event_loop_mutex_app_alloc();
+ event_loop_mutex_app_run(app);
+ event_loop_mutex_app_free(app);
+
+ return 0;
+}
diff --git a/applications/examples/example_event_loop/example_event_loop_stream_buffer.c b/applications/examples/example_event_loop/example_event_loop_stream_buffer.c
new file mode 100644
index 0000000000..65dbd83cf5
--- /dev/null
+++ b/applications/examples/example_event_loop/example_event_loop_stream_buffer.c
@@ -0,0 +1,131 @@
+/**
+ * @file example_event_loop_stream_buffer.c
+ * @brief Example application that demonstrates the FuriEventLoop and FuriStreamBuffer integration.
+ *
+ * This application simulates a use case where some data data stream comes from a separate thread (or hardware)
+ * and a stream buffer is used to act as an intermediate buffer. The worker thread produces 10 iterations of 32
+ * bytes of simulated data, and each time when the buffer is half-filled, the data is taken out of it and printed
+ * to the debug output. After completing all iterations, the application exits.
+ */
+
+#include
+#include
+
+#define TAG "ExampleEventLoopStreamBuffer"
+
+#define WORKER_ITERATION_COUNT (10)
+
+#define STREAM_BUFFER_SIZE (32)
+#define STREAM_BUFFER_TRIG_LEVEL (STREAM_BUFFER_SIZE / 2)
+#define STREAM_BUFFER_EVENT_AND_FLAGS (FuriEventLoopEventIn | FuriEventLoopEventFlagEdge)
+
+typedef struct {
+ FuriEventLoop* event_loop;
+ FuriThread* worker_thread;
+ FuriStreamBuffer* stream_buffer;
+} EventLoopStreamBufferApp;
+
+// This funciton is being run in a separate thread to simulate data coming from a producer thread or some device.
+static int32_t event_loop_stream_buffer_app_worker_thread(void* context) {
+ furi_assert(context);
+ EventLoopStreamBufferApp* app = context;
+
+ FURI_LOG_I(TAG, "Worker thread started");
+
+ for(uint32_t i = 0; i < WORKER_ITERATION_COUNT; ++i) {
+ // Produce 32 bytes of simulated data.
+ for(uint32_t j = 0; j < STREAM_BUFFER_SIZE; ++j) {
+ // Simulate incoming data by generating a random byte.
+ uint8_t data = furi_hal_random_get() % 0xFF;
+ // Put the byte in the buffer. Depending on the use case, it may or may be not acceptable
+ // to wait for free space to become available.
+ furi_check(
+ furi_stream_buffer_send(app->stream_buffer, &data, 1, FuriWaitForever) == 1);
+ // Delay between 30 and 50 ms to slow down the output for clarity.
+ furi_delay_ms(30 + furi_hal_random_get() % 20);
+ }
+ }
+
+ FURI_LOG_I(TAG, "All work done, worker thread out!");
+ // Request the event loop to stop
+ furi_event_loop_stop(app->event_loop);
+
+ return 0;
+}
+
+// This function is being run each time when the number of bytes in the buffer is above its trigger level.
+static bool
+ event_loop_stream_buffer_app_event_callback(FuriEventLoopObject* object, void* context) {
+ furi_assert(context);
+ EventLoopStreamBufferApp* app = context;
+
+ furi_assert(object == app->stream_buffer);
+
+ // Temporary buffer that can hold at most half of the stream buffer's capacity.
+ uint8_t data[STREAM_BUFFER_TRIG_LEVEL];
+ // Receive the data. It is guaranteed that the amount of data in the buffer will be equal to
+ // or greater than the trigger level, therefore, no waiting delay is necessary.
+ furi_check(
+ furi_stream_buffer_receive(app->stream_buffer, data, sizeof(data), 0) == sizeof(data));
+
+ // Format the data for printing and print it to the debug output.
+ FuriString* tmp_str = furi_string_alloc();
+ for(uint32_t i = 0; i < sizeof(data); ++i) {
+ furi_string_cat_printf(tmp_str, "%02X ", data[i]);
+ }
+
+ FURI_LOG_I(TAG, "Received data: %s", furi_string_get_cstr(tmp_str));
+ furi_string_free(tmp_str);
+
+ return true;
+}
+
+static EventLoopStreamBufferApp* event_loop_stream_buffer_app_alloc(void) {
+ EventLoopStreamBufferApp* app = malloc(sizeof(EventLoopStreamBufferApp));
+
+ // Create an event loop instance.
+ app->event_loop = furi_event_loop_alloc();
+ // Create a worker thread instance.
+ app->worker_thread = furi_thread_alloc_ex(
+ "EventLoopStreamBufferWorker", 1024, event_loop_stream_buffer_app_worker_thread, app);
+ // Create a stream_buffer instance.
+ app->stream_buffer = furi_stream_buffer_alloc(STREAM_BUFFER_SIZE, STREAM_BUFFER_TRIG_LEVEL);
+ // Subscribe for the stream buffer IN events in edge triggered mode.
+ furi_event_loop_subscribe_stream_buffer(
+ app->event_loop,
+ app->stream_buffer,
+ STREAM_BUFFER_EVENT_AND_FLAGS,
+ event_loop_stream_buffer_app_event_callback,
+ app);
+
+ return app;
+}
+
+static void event_loop_stream_buffer_app_free(EventLoopStreamBufferApp* app) {
+ // IMPORTANT: The user code MUST unsubscribe from all events before deleting the event loop.
+ // Failure to do so will result in a crash.
+ furi_event_loop_unsubscribe(app->event_loop, app->stream_buffer);
+ // Delete all instances
+ furi_thread_free(app->worker_thread);
+ furi_stream_buffer_free(app->stream_buffer);
+ furi_event_loop_free(app->event_loop);
+
+ free(app);
+}
+
+static void event_loop_stream_buffer_app_run(EventLoopStreamBufferApp* app) {
+ furi_thread_start(app->worker_thread);
+ furi_event_loop_run(app->event_loop);
+ furi_thread_join(app->worker_thread);
+}
+
+// The application's entry point - referenced in application.fam
+int32_t example_event_loop_stream_buffer_app(void* arg) {
+ UNUSED(arg);
+
+ EventLoopStreamBufferApp* app = event_loop_stream_buffer_app_alloc();
+ event_loop_stream_buffer_app_run(app);
+ event_loop_stream_buffer_app_free(app);
+
+ return 0;
+}
diff --git a/applications/examples/example_event_loop/example_event_loop_timer.c b/applications/examples/example_event_loop/example_event_loop_timer.c
new file mode 100644
index 0000000000..e255f6b61b
--- /dev/null
+++ b/applications/examples/example_event_loop/example_event_loop_timer.c
@@ -0,0 +1,87 @@
+/**
+ * @file example_event_loop_timer.c
+ * @brief Example application that demonstrates FuriEventLoop's software timer capability.
+ *
+ * This application prints a countdown from 10 to 0 to the debug output and then exits.
+ * Despite only one timer being used in this example for clarity, an event loop instance can have
+ * an arbitrary number of independent timers of any type (periodic or one-shot).
+ *
+ */
+#include
+
+#define TAG "ExampleEventLoopTimer"
+
+#define COUNTDOWN_START_VALUE (10)
+#define COUNTDOWN_INTERVAL_MS (1000)
+
+typedef struct {
+ FuriEventLoop* event_loop;
+ FuriEventLoopTimer* timer;
+ uint32_t countdown_value;
+} EventLoopTimerApp;
+
+// This function is called each time the timer expires (i.e. once per 1000 ms (1s) in this example)
+static void event_loop_timer_callback(void* context) {
+ furi_assert(context);
+ EventLoopTimerApp* app = context;
+
+ // Print the countdown value
+ FURI_LOG_I(TAG, "T-00:00:%02lu", app->countdown_value);
+
+ if(app->countdown_value == 0) {
+ // If the countdown reached 0, print the final line and stop the event loop
+ FURI_LOG_I(TAG, "Blast off to adventure!");
+ // After this call, the control will be returned back to event_loop_timers_app_run()
+ furi_event_loop_stop(app->event_loop);
+
+ } else {
+ // Decrement the countdown value
+ app->countdown_value -= 1;
+ }
+}
+
+static EventLoopTimerApp* event_loop_timer_app_alloc(void) {
+ EventLoopTimerApp* app = malloc(sizeof(EventLoopTimerApp));
+
+ // Create an event loop instance.
+ app->event_loop = furi_event_loop_alloc();
+ // Create a software timer instance.
+ // The timer is bound to the event loop instance and will execute in its context.
+ // Here, the timer type is periodic, i.e. it will restart automatically after expiring.
+ app->timer = furi_event_loop_timer_alloc(
+ app->event_loop, event_loop_timer_callback, FuriEventLoopTimerTypePeriodic, app);
+ // The countdown value will be tracked in this variable.
+ app->countdown_value = COUNTDOWN_START_VALUE;
+
+ return app;
+}
+
+static void event_loop_timer_app_free(EventLoopTimerApp* app) {
+ // IMPORTANT: All event loop timers MUST be deleted BEFORE deleting the event loop itself.
+ // Failure to do so will result in a crash.
+ furi_event_loop_timer_free(app->timer);
+ // With all timers deleted, it's safe to delete the event loop.
+ furi_event_loop_free(app->event_loop);
+ free(app);
+}
+
+static void event_loop_timer_app_run(EventLoopTimerApp* app) {
+ FURI_LOG_I(TAG, "All systems go! Prepare for countdown!");
+
+ // Timers can be started either before the event loop is run, or in any
+ // callback function called by a running event loop.
+ furi_event_loop_timer_start(app->timer, COUNTDOWN_INTERVAL_MS);
+ // This call will block until furi_event_loop_stop() is called.
+ furi_event_loop_run(app->event_loop);
+}
+
+// The application's entry point - referenced in application.fam
+int32_t example_event_loop_timer_app(void* arg) {
+ UNUSED(arg);
+
+ EventLoopTimerApp* app = event_loop_timer_app_alloc();
+ event_loop_timer_app_run(app);
+ event_loop_timer_app_free(app);
+
+ return 0;
+}
diff --git a/applications/examples/example_images/images/dolphin_71x25.png b/applications/examples/example_images/images/dolphin_71x25.png
index 6b3f8aa59b..26c21e2352 100644
Binary files a/applications/examples/example_images/images/dolphin_71x25.png and b/applications/examples/example_images/images/dolphin_71x25.png differ
diff --git a/applications/examples/example_number_input/ReadMe.md b/applications/examples/example_number_input/ReadMe.md
new file mode 100644
index 0000000000..9d5a0a9e5e
--- /dev/null
+++ b/applications/examples/example_number_input/ReadMe.md
@@ -0,0 +1,7 @@
+# Number Input
+
+Simple keyboard that limits user inputs to a full number (integer). Useful to enforce correct entries without the need of intense validations after a user input.
+
+Definition of min/max values is required. Numbers are of type int32_t. If negative numbers are allowed withing min - max, an additional button is displayed to switch the sign between + and -.
+
+It is also possible to define a header text, shown in this example app with the 3 different input options.
\ No newline at end of file
diff --git a/applications/examples/example_number_input/application.fam b/applications/examples/example_number_input/application.fam
new file mode 100644
index 0000000000..58cff44962
--- /dev/null
+++ b/applications/examples/example_number_input/application.fam
@@ -0,0 +1,10 @@
+App(
+ appid="example_number_input",
+ name="Example: Number Input",
+ apptype=FlipperAppType.EXTERNAL,
+ entry_point="example_number_input",
+ requires=["gui"],
+ stack_size=1 * 1024,
+ fap_icon="example_number_input_10px.png",
+ fap_category="Examples",
+)
diff --git a/applications/examples/example_number_input/example_number_input.c b/applications/examples/example_number_input/example_number_input.c
new file mode 100644
index 0000000000..19d787ef5f
--- /dev/null
+++ b/applications/examples/example_number_input/example_number_input.c
@@ -0,0 +1,79 @@
+#include "example_number_input.h"
+
+bool example_number_input_custom_event_callback(void* context, uint32_t event) {
+ furi_assert(context);
+ ExampleNumberInput* app = context;
+ return scene_manager_handle_custom_event(app->scene_manager, event);
+}
+
+static bool example_number_input_back_event_callback(void* context) {
+ furi_assert(context);
+ ExampleNumberInput* app = context;
+ return scene_manager_handle_back_event(app->scene_manager);
+}
+
+static ExampleNumberInput* example_number_input_alloc() {
+ ExampleNumberInput* app = malloc(sizeof(ExampleNumberInput));
+ app->gui = furi_record_open(RECORD_GUI);
+
+ app->view_dispatcher = view_dispatcher_alloc();
+
+ app->scene_manager = scene_manager_alloc(&example_number_input_scene_handlers, app);
+ view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
+ view_dispatcher_set_custom_event_callback(
+ app->view_dispatcher, example_number_input_custom_event_callback);
+ view_dispatcher_set_navigation_event_callback(
+ app->view_dispatcher, example_number_input_back_event_callback);
+
+ app->number_input = number_input_alloc();
+ view_dispatcher_add_view(
+ app->view_dispatcher,
+ ExampleNumberInputViewIdNumberInput,
+ number_input_get_view(app->number_input));
+
+ app->dialog_ex = dialog_ex_alloc();
+ view_dispatcher_add_view(
+ app->view_dispatcher,
+ ExampleNumberInputViewIdShowNumber,
+ dialog_ex_get_view(app->dialog_ex));
+
+ app->current_number = 5;
+ app->min_value = INT32_MIN;
+ app->max_value = INT32_MAX;
+
+ return app;
+}
+
+static void example_number_input_free(ExampleNumberInput* app) {
+ furi_assert(app);
+
+ view_dispatcher_remove_view(app->view_dispatcher, ExampleNumberInputViewIdShowNumber);
+ dialog_ex_free(app->dialog_ex);
+
+ view_dispatcher_remove_view(app->view_dispatcher, ExampleNumberInputViewIdNumberInput);
+ number_input_free(app->number_input);
+
+ scene_manager_free(app->scene_manager);
+ view_dispatcher_free(app->view_dispatcher);
+
+ furi_record_close(RECORD_GUI);
+ app->gui = NULL;
+
+ //Remove whatever is left
+ free(app);
+}
+
+int32_t example_number_input(void* p) {
+ UNUSED(p);
+ ExampleNumberInput* app = example_number_input_alloc();
+
+ view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
+
+ scene_manager_next_scene(app->scene_manager, ExampleNumberInputSceneShowNumber);
+
+ view_dispatcher_run(app->view_dispatcher);
+
+ example_number_input_free(app);
+
+ return 0;
+}
diff --git a/applications/examples/example_number_input/example_number_input.h b/applications/examples/example_number_input/example_number_input.h
new file mode 100644
index 0000000000..8d944e6fda
--- /dev/null
+++ b/applications/examples/example_number_input/example_number_input.h
@@ -0,0 +1,35 @@
+#pragma once
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "scenes/example_number_input_scene.h"
+
+typedef struct ExampleNumberInputShowNumber ExampleNumberInputShowNumber;
+
+typedef enum {
+ ExampleNumberInputViewIdShowNumber,
+ ExampleNumberInputViewIdNumberInput,
+} ExampleNumberInputViewId;
+
+typedef struct {
+ Gui* gui;
+ SceneManager* scene_manager;
+ ViewDispatcher* view_dispatcher;
+
+ NumberInput* number_input;
+ DialogEx* dialog_ex;
+
+ int32_t current_number;
+ int32_t min_value;
+ int32_t max_value;
+} ExampleNumberInput;
diff --git a/applications/examples/example_number_input/example_number_input_10px.png b/applications/examples/example_number_input/example_number_input_10px.png
new file mode 100644
index 0000000000..bdb494fcd0
Binary files /dev/null and b/applications/examples/example_number_input/example_number_input_10px.png differ
diff --git a/applications/examples/example_number_input/scenes/example_number_input_scene.c b/applications/examples/example_number_input/scenes/example_number_input_scene.c
new file mode 100644
index 0000000000..caf77fa8ce
--- /dev/null
+++ b/applications/examples/example_number_input/scenes/example_number_input_scene.c
@@ -0,0 +1,30 @@
+#include "example_number_input_scene.h"
+
+// Generate scene on_enter handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
+void (*const example_number_input_on_enter_handlers[])(void*) = {
+#include "example_number_input_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_event handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
+bool (*const example_number_input_on_event_handlers[])(void* context, SceneManagerEvent event) = {
+#include "example_number_input_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
+void (*const example_number_input_on_exit_handlers[])(void* context) = {
+#include "example_number_input_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Initialize scene handlers configuration structure
+const SceneManagerHandlers example_number_input_scene_handlers = {
+ .on_enter_handlers = example_number_input_on_enter_handlers,
+ .on_event_handlers = example_number_input_on_event_handlers,
+ .on_exit_handlers = example_number_input_on_exit_handlers,
+ .scene_num = ExampleNumberInputSceneNum,
+};
diff --git a/applications/system/storage_move_to_sd/scenes/storage_move_to_sd_scene.h b/applications/examples/example_number_input/scenes/example_number_input_scene.h
similarity index 61%
rename from applications/system/storage_move_to_sd/scenes/storage_move_to_sd_scene.h
rename to applications/examples/example_number_input/scenes/example_number_input_scene.h
index bdeb4a8433..49fcd256fb 100644
--- a/applications/system/storage_move_to_sd/scenes/storage_move_to_sd_scene.h
+++ b/applications/examples/example_number_input/scenes/example_number_input_scene.h
@@ -3,27 +3,27 @@
#include
// Generate scene id and total number
-#define ADD_SCENE(prefix, name, id) StorageMoveToSd##id,
+#define ADD_SCENE(prefix, name, id) ExampleNumberInputScene##id,
typedef enum {
-#include "storage_move_to_sd_scene_config.h"
- StorageMoveToSdSceneNum,
-} StorageMoveToSdScene;
+#include "example_number_input_scene_config.h"
+ ExampleNumberInputSceneNum,
+} ExampleNumberInputScene;
#undef ADD_SCENE
-extern const SceneManagerHandlers storage_move_to_sd_scene_handlers;
+extern const SceneManagerHandlers example_number_input_scene_handlers;
// Generate scene on_enter handlers declaration
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
-#include "storage_move_to_sd_scene_config.h"
+#include "example_number_input_scene_config.h"
#undef ADD_SCENE
// Generate scene on_event handlers declaration
#define ADD_SCENE(prefix, name, id) \
bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
-#include "storage_move_to_sd_scene_config.h"
+#include "example_number_input_scene_config.h"
#undef ADD_SCENE
// Generate scene on_exit handlers declaration
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
-#include "storage_move_to_sd_scene_config.h"
+#include "example_number_input_scene_config.h"
#undef ADD_SCENE
diff --git a/applications/examples/example_number_input/scenes/example_number_input_scene_config.h b/applications/examples/example_number_input/scenes/example_number_input_scene_config.h
new file mode 100644
index 0000000000..71acbda528
--- /dev/null
+++ b/applications/examples/example_number_input/scenes/example_number_input_scene_config.h
@@ -0,0 +1,4 @@
+ADD_SCENE(example_number_input, input_number, InputNumber)
+ADD_SCENE(example_number_input, show_number, ShowNumber)
+ADD_SCENE(example_number_input, input_max, InputMax)
+ADD_SCENE(example_number_input, input_min, InputMin)
diff --git a/applications/examples/example_number_input/scenes/example_number_input_scene_input_max.c b/applications/examples/example_number_input/scenes/example_number_input_scene_input_max.c
new file mode 100644
index 0000000000..7478f58a70
--- /dev/null
+++ b/applications/examples/example_number_input/scenes/example_number_input_scene_input_max.c
@@ -0,0 +1,39 @@
+#include "../example_number_input.h"
+
+void example_number_input_scene_input_max_callback(void* context, int32_t number) {
+ ExampleNumberInput* app = context;
+ app->max_value = number;
+ view_dispatcher_send_custom_event(app->view_dispatcher, 0);
+}
+
+void example_number_input_scene_input_max_on_enter(void* context) {
+ furi_assert(context);
+ ExampleNumberInput* app = context;
+ NumberInput* number_input = app->number_input;
+
+ number_input_set_header_text(number_input, "Enter the maximum value");
+ number_input_set_result_callback(
+ number_input,
+ example_number_input_scene_input_max_callback,
+ context,
+ app->max_value,
+ app->min_value,
+ INT32_MAX);
+
+ view_dispatcher_switch_to_view(app->view_dispatcher, ExampleNumberInputViewIdNumberInput);
+}
+
+bool example_number_input_scene_input_max_on_event(void* context, SceneManagerEvent event) {
+ ExampleNumberInput* app = context;
+ bool consumed = false;
+
+ if(event.type == SceneManagerEventTypeCustom) {
+ scene_manager_previous_scene(app->scene_manager);
+ return true;
+ }
+ return consumed;
+}
+
+void example_number_input_scene_input_max_on_exit(void* context) {
+ UNUSED(context);
+}
diff --git a/applications/examples/example_number_input/scenes/example_number_input_scene_input_min.c b/applications/examples/example_number_input/scenes/example_number_input_scene_input_min.c
new file mode 100644
index 0000000000..ad76562328
--- /dev/null
+++ b/applications/examples/example_number_input/scenes/example_number_input_scene_input_min.c
@@ -0,0 +1,39 @@
+#include "../example_number_input.h"
+
+void example_number_input_scene_input_min_callback(void* context, int32_t number) {
+ ExampleNumberInput* app = context;
+ app->min_value = number;
+ view_dispatcher_send_custom_event(app->view_dispatcher, 0);
+}
+
+void example_number_input_scene_input_min_on_enter(void* context) {
+ furi_assert(context);
+ ExampleNumberInput* app = context;
+ NumberInput* number_input = app->number_input;
+
+ number_input_set_header_text(number_input, "Enter the minimum value");
+ number_input_set_result_callback(
+ number_input,
+ example_number_input_scene_input_min_callback,
+ context,
+ app->min_value,
+ INT32_MIN,
+ app->max_value);
+
+ view_dispatcher_switch_to_view(app->view_dispatcher, ExampleNumberInputViewIdNumberInput);
+}
+
+bool example_number_input_scene_input_min_on_event(void* context, SceneManagerEvent event) {
+ ExampleNumberInput* app = context;
+ bool consumed = false;
+
+ if(event.type == SceneManagerEventTypeCustom) {
+ scene_manager_previous_scene(app->scene_manager);
+ return true;
+ }
+ return consumed;
+}
+
+void example_number_input_scene_input_min_on_exit(void* context) {
+ UNUSED(context);
+}
diff --git a/applications/examples/example_number_input/scenes/example_number_input_scene_input_number.c b/applications/examples/example_number_input/scenes/example_number_input_scene_input_number.c
new file mode 100644
index 0000000000..d9b1fd52f4
--- /dev/null
+++ b/applications/examples/example_number_input/scenes/example_number_input_scene_input_number.c
@@ -0,0 +1,42 @@
+#include "../example_number_input.h"
+
+void example_number_input_scene_input_number_callback(void* context, int32_t number) {
+ ExampleNumberInput* app = context;
+ app->current_number = number;
+ view_dispatcher_send_custom_event(app->view_dispatcher, 0);
+}
+
+void example_number_input_scene_input_number_on_enter(void* context) {
+ furi_assert(context);
+ ExampleNumberInput* app = context;
+ NumberInput* number_input = app->number_input;
+
+ char str[50];
+ snprintf(str, sizeof(str), "Set Number (%ld - %ld)", app->min_value, app->max_value);
+
+ number_input_set_header_text(number_input, str);
+ number_input_set_result_callback(
+ number_input,
+ example_number_input_scene_input_number_callback,
+ context,
+ app->current_number,
+ app->min_value,
+ app->max_value);
+
+ view_dispatcher_switch_to_view(app->view_dispatcher, ExampleNumberInputViewIdNumberInput);
+}
+
+bool example_number_input_scene_input_number_on_event(void* context, SceneManagerEvent event) {
+ ExampleNumberInput* app = context;
+ bool consumed = false;
+
+ if(event.type == SceneManagerEventTypeCustom) { //Back button pressed
+ scene_manager_previous_scene(app->scene_manager);
+ return true;
+ }
+ return consumed;
+}
+
+void example_number_input_scene_input_number_on_exit(void* context) {
+ UNUSED(context);
+}
diff --git a/applications/examples/example_number_input/scenes/example_number_input_scene_show_number.c b/applications/examples/example_number_input/scenes/example_number_input_scene_show_number.c
new file mode 100644
index 0000000000..2afdaf5c10
--- /dev/null
+++ b/applications/examples/example_number_input/scenes/example_number_input_scene_show_number.c
@@ -0,0 +1,66 @@
+#include "../example_number_input.h"
+
+static void
+ example_number_input_scene_confirm_dialog_callback(DialogExResult result, void* context) {
+ ExampleNumberInput* app = context;
+
+ view_dispatcher_send_custom_event(app->view_dispatcher, result);
+}
+
+static void example_number_input_scene_update_view(void* context) {
+ ExampleNumberInput* app = context;
+ DialogEx* dialog_ex = app->dialog_ex;
+
+ dialog_ex_set_header(dialog_ex, "The number is", 64, 0, AlignCenter, AlignTop);
+
+ static char buffer[12]; //needs static for extended lifetime
+
+ snprintf(buffer, sizeof(buffer), "%ld", app->current_number);
+ dialog_ex_set_text(dialog_ex, buffer, 64, 29, AlignCenter, AlignCenter);
+
+ dialog_ex_set_left_button_text(dialog_ex, "Min");
+ dialog_ex_set_right_button_text(dialog_ex, "Max");
+ dialog_ex_set_center_button_text(dialog_ex, "Change");
+
+ dialog_ex_set_result_callback(dialog_ex, example_number_input_scene_confirm_dialog_callback);
+ dialog_ex_set_context(dialog_ex, app);
+}
+
+void example_number_input_scene_show_number_on_enter(void* context) {
+ furi_assert(context);
+ ExampleNumberInput* app = context;
+
+ example_number_input_scene_update_view(app);
+
+ view_dispatcher_switch_to_view(app->view_dispatcher, ExampleNumberInputViewIdShowNumber);
+}
+
+bool example_number_input_scene_show_number_on_event(void* context, SceneManagerEvent event) {
+ ExampleNumberInput* app = context;
+ bool consumed = false;
+
+ if(event.type == SceneManagerEventTypeCustom) {
+ switch(event.event) {
+ case DialogExResultCenter:
+ scene_manager_next_scene(app->scene_manager, ExampleNumberInputSceneInputNumber);
+ consumed = true;
+ break;
+ case DialogExResultLeft:
+ scene_manager_next_scene(app->scene_manager, ExampleNumberInputSceneInputMin);
+ consumed = true;
+ break;
+ case DialogExResultRight:
+ scene_manager_next_scene(app->scene_manager, ExampleNumberInputSceneInputMax);
+ consumed = true;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return consumed;
+}
+
+void example_number_input_scene_show_number_on_exit(void* context) {
+ UNUSED(context);
+}
diff --git a/applications/examples/example_thermo/example_thermo_10px.png b/applications/examples/example_thermo/example_thermo_10px.png
index 3d527f306c..bc42d190c5 100644
Binary files a/applications/examples/example_thermo/example_thermo_10px.png and b/applications/examples/example_thermo/example_thermo_10px.png differ
diff --git a/applications/examples/example_view_dispatcher/application.fam b/applications/examples/example_view_dispatcher/application.fam
new file mode 100644
index 0000000000..f7b743bcf5
--- /dev/null
+++ b/applications/examples/example_view_dispatcher/application.fam
@@ -0,0 +1,8 @@
+App(
+ appid="example_view_dispatcher",
+ name="Example: ViewDispatcher",
+ apptype=FlipperAppType.EXTERNAL,
+ entry_point="example_view_dispatcher_app",
+ requires=["gui"],
+ fap_category="Examples",
+)
diff --git a/applications/examples/example_view_dispatcher/example_view_dispatcher.c b/applications/examples/example_view_dispatcher/example_view_dispatcher.c
new file mode 100644
index 0000000000..71d29edfd9
--- /dev/null
+++ b/applications/examples/example_view_dispatcher/example_view_dispatcher.c
@@ -0,0 +1,173 @@
+/**
+ * @file example_view_dispatcher.c
+ * @brief Example application demonstrating the usage of the ViewDispatcher library.
+ *
+ * This application can display one of two views: either a Widget or a Submenu.
+ * Each view has its own way of switching to another one:
+ *
+ * - A center button in the Widget view.
+ * - A submenu item in the Submenu view
+ *
+ * Press either to switch to a different view. Press Back to exit the application.
+ *
+ */
+
+#include
+#include
+
+#include
+#include
+
+// Enumeration of the view indexes.
+typedef enum {
+ ViewIndexWidget,
+ ViewIndexSubmenu,
+ ViewIndexCount,
+} ViewIndex;
+
+// Enumeration of submenu items.
+typedef enum {
+ SubmenuIndexNothing,
+ SubmenuIndexSwitchView,
+} SubmenuIndex;
+
+// Main application structure.
+typedef struct {
+ ViewDispatcher* view_dispatcher;
+ Widget* widget;
+ Submenu* submenu;
+} ExampleViewDispatcherApp;
+
+// This function is called when the user has pressed the Back key.
+static bool example_view_dispatcher_app_navigation_callback(void* context) {
+ furi_assert(context);
+ ExampleViewDispatcherApp* app = context;
+ // Back means exit the application, which can be done by stopping the ViewDispatcher.
+ view_dispatcher_stop(app->view_dispatcher);
+ return true;
+}
+
+// This function is called when there are custom events to process.
+static bool example_view_dispatcher_app_custom_event_callback(void* context, uint32_t event) {
+ furi_assert(context);
+ ExampleViewDispatcherApp* app = context;
+ // The event numerical value can mean different things (the application is responsible to uphold its chosen convention)
+ // In this example, the only possible meaning is the view index to switch to.
+ furi_assert(event < ViewIndexCount);
+ // Switch to the requested view.
+ view_dispatcher_switch_to_view(app->view_dispatcher, event);
+
+ return true;
+}
+
+// This function is called when the user presses the "Switch View" button on the Widget view.
+static void example_view_dispatcher_app_button_callback(
+ GuiButtonType button_type,
+ InputType input_type,
+ void* context) {
+ furi_assert(context);
+ ExampleViewDispatcherApp* app = context;
+ // Only request the view switch if the user short-presses the Center button.
+ if(button_type == GuiButtonTypeCenter && input_type == InputTypeShort) {
+ // Request switch to the Submenu view via the custom event queue.
+ view_dispatcher_send_custom_event(app->view_dispatcher, ViewIndexSubmenu);
+ }
+}
+
+// This function is called when the user activates the "Switch View" submenu item.
+static void example_view_dispatcher_app_submenu_callback(void* context, uint32_t index) {
+ furi_assert(context);
+ ExampleViewDispatcherApp* app = context;
+ // Only request the view switch if the user activates the "Switch View" item.
+ if(index == SubmenuIndexSwitchView) {
+ // Request switch to the Widget view via the custom event queue.
+ view_dispatcher_send_custom_event(app->view_dispatcher, ViewIndexWidget);
+ }
+}
+
+// Application constructor function.
+static ExampleViewDispatcherApp* example_view_dispatcher_app_alloc() {
+ ExampleViewDispatcherApp* app = malloc(sizeof(ExampleViewDispatcherApp));
+ // Access the GUI API instance.
+ Gui* gui = furi_record_open(RECORD_GUI);
+ // Create and initialize the Widget view.
+ app->widget = widget_alloc();
+ widget_add_string_multiline_element(
+ app->widget, 64, 32, AlignCenter, AlignCenter, FontSecondary, "Press the Button below");
+ widget_add_button_element(
+ app->widget,
+ GuiButtonTypeCenter,
+ "Switch View",
+ example_view_dispatcher_app_button_callback,
+ app);
+ // Create and initialize the Submenu view.
+ app->submenu = submenu_alloc();
+ submenu_add_item(app->submenu, "Do Nothing", SubmenuIndexNothing, NULL, NULL);
+ submenu_add_item(
+ app->submenu,
+ "Switch View",
+ SubmenuIndexSwitchView,
+ example_view_dispatcher_app_submenu_callback,
+ app);
+ // Create the ViewDispatcher instance.
+ app->view_dispatcher = view_dispatcher_alloc();
+ // Let the GUI know about this ViewDispatcher instance.
+ view_dispatcher_attach_to_gui(app->view_dispatcher, gui, ViewDispatcherTypeFullscreen);
+ // Register the views within the ViewDispatcher instance. This alone will not show any of them on the screen.
+ // Each view must have its own index to refer to it later (it is best done via an enumeration as shown here).
+ view_dispatcher_add_view(app->view_dispatcher, ViewIndexWidget, widget_get_view(app->widget));
+ view_dispatcher_add_view(
+ app->view_dispatcher, ViewIndexSubmenu, submenu_get_view(app->submenu));
+ // Set the custom event callback. It will be called each time a custom event is scheduled
+ // using the view_dispatcher_send_custom_callback() function.
+ view_dispatcher_set_custom_event_callback(
+ app->view_dispatcher, example_view_dispatcher_app_custom_event_callback);
+ // Set the navigation, or back button callback. It will be called if the user pressed the Back button
+ // and the event was not handled in the currently displayed view.
+ view_dispatcher_set_navigation_event_callback(
+ app->view_dispatcher, example_view_dispatcher_app_navigation_callback);
+ // The context will be passed to the callbacks as a parameter, so we have access to our application object.
+ view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
+
+ return app;
+}
+
+// Application destructor function.
+static void example_view_dispatcher_app_free(ExampleViewDispatcherApp* app) {
+ // All views must be un-registered (removed) from a ViewDispatcher instance
+ // before deleting it. Failure to do so will result in a crash.
+ view_dispatcher_remove_view(app->view_dispatcher, ViewIndexWidget);
+ view_dispatcher_remove_view(app->view_dispatcher, ViewIndexSubmenu);
+ // Now it is safe to delete the ViewDispatcher instance.
+ view_dispatcher_free(app->view_dispatcher);
+ // Delete the views
+ widget_free(app->widget);
+ submenu_free(app->submenu);
+ // End access to hte the GUI API.
+ furi_record_close(RECORD_GUI);
+ // Free the remaining memory.
+ free(app);
+}
+
+static void example_view_dispatcher_app_run(ExampleViewDispatcherApp* app) {
+ // Display the Widget view on the screen.
+ view_dispatcher_switch_to_view(app->view_dispatcher, ViewIndexWidget);
+ // This function will block until view_dispatcher_stop() is called.
+ // Internally, it uses a FuriEventLoop (see FuriEventLoop examples for more info on this).
+ view_dispatcher_run(app->view_dispatcher);
+}
+
+/*******************************************************************
+ * vvv START HERE vvv
+ *
+ * The application's entry point - referenced in application.fam
+ *******************************************************************/
+int32_t example_view_dispatcher_app(void* arg) {
+ UNUSED(arg);
+
+ ExampleViewDispatcherApp* app = example_view_dispatcher_app_alloc();
+ example_view_dispatcher_app_run(app);
+ example_view_dispatcher_app_free(app);
+
+ return 0;
+}
diff --git a/applications/examples/example_view_holder/application.fam b/applications/examples/example_view_holder/application.fam
new file mode 100644
index 0000000000..19ad8d2ac4
--- /dev/null
+++ b/applications/examples/example_view_holder/application.fam
@@ -0,0 +1,8 @@
+App(
+ appid="example_view_holder",
+ name="Example: ViewHolder",
+ apptype=FlipperAppType.EXTERNAL,
+ entry_point="example_view_holder_app",
+ requires=["gui"],
+ fap_category="Examples",
+)
diff --git a/applications/examples/example_view_holder/example_view_holder.c b/applications/examples/example_view_holder/example_view_holder.c
new file mode 100644
index 0000000000..24907dbc26
--- /dev/null
+++ b/applications/examples/example_view_holder/example_view_holder.c
@@ -0,0 +1,78 @@
+/**
+ * @file example_view_holder.c
+ * @brief Example application demonstrating the usage of the ViewHolder library.
+ *
+ * This application will display a text box with some scrollable text in it.
+ * Press the Back key to exit the application.
+ */
+
+#include
+#include
+#include
+
+#include
+
+// This function will be called when the user presses the Back button.
+static void example_view_holder_back_callback(void* context) {
+ FuriApiLock exit_lock = context;
+ // Unlock the exit lock, thus enabling the app to exit.
+ api_lock_unlock(exit_lock);
+}
+
+int32_t example_view_holder_app(void* arg) {
+ UNUSED(arg);
+
+ // Access the GUI API instance.
+ Gui* gui = furi_record_open(RECORD_GUI);
+ // Create a TextBox view. The Gui object only accepts
+ // ViewPort instances, so we will need to address that later.
+ TextBox* text_box = text_box_alloc();
+ // Set some text so that the text box is not empty.
+ text_box_set_text(
+ text_box,
+ "ViewHolder is being used\n"
+ "to show this TextBox view.\n\n"
+ "Scroll down to see more.\n\n\n"
+ "Press \"Back\" to exit.");
+
+ // Create a ViewHolder instance. It will serve as an adapter to convert
+ // between the View type provided by the TextBox view and the ViewPort type
+ // that the GUI can actually display.
+ ViewHolder* view_holder = view_holder_alloc();
+ // Let the GUI know about this ViewHolder instance.
+ view_holder_attach_to_gui(view_holder, gui);
+ // Set the view that we want to display.
+ view_holder_set_view(view_holder, text_box_get_view(text_box));
+
+ // The part below is not really related to this example, but is necessary for it to function.
+ // We need to somehow stall the application thread so that the view stays on the screen (otherwise
+ // the app will just exit and won't display anything) and at the same time we need a way to quit out
+ // of the application.
+
+ // In this example, a simple FuriApiLock instance is used. A real-world application is likely to have some
+ // kind of event handling loop here instead. (see the ViewDispatcher example or one of FuriEventLoop
+ // examples for that).
+
+ // Create a pre-locked FuriApiLock instance.
+ FuriApiLock exit_lock = api_lock_alloc_locked();
+ // Set a Back event callback for the ViewHolder instance. It will be called when the user
+ // presses the Back button. We pass the exit lock instance as the context to be able to access
+ // it inside the callback function.
+ view_holder_set_back_callback(view_holder, example_view_holder_back_callback, exit_lock);
+
+ // This call will block the application thread from running until the exit lock gets unlocked somehow
+ // (the only way it can happen in this example is via the back callback).
+ api_lock_wait_unlock_and_free(exit_lock);
+
+ // The back key has been pressed, which unlocked the exit lock. The application is about to exit.
+
+ // The view must be removed from a ViewHolder instance before deleting it.
+ view_holder_set_view(view_holder, NULL);
+ // Delete everything to prevent memory leaks.
+ view_holder_free(view_holder);
+ text_box_free(text_box);
+ // End access to the GUI API.
+ furi_record_close(RECORD_GUI);
+
+ return 0;
+}
diff --git a/applications/main/archive/archive.c b/applications/main/archive/archive.c
index 2345d3f7d4..7c23ee3154 100644
--- a/applications/main/archive/archive.c
+++ b/applications/main/archive/archive.c
@@ -30,7 +30,6 @@ ArchiveApp* archive_alloc(void) {
archive->view_dispatcher = view_dispatcher_alloc();
ViewDispatcher* view_dispatcher = archive->view_dispatcher;
- view_dispatcher_enable_queue(view_dispatcher);
view_dispatcher_set_event_callback_context(view_dispatcher, archive);
view_dispatcher_set_custom_event_callback(view_dispatcher, archive_custom_event_callback);
view_dispatcher_set_navigation_event_callback(view_dispatcher, archive_back_event_callback);
diff --git a/applications/main/archive/helpers/archive_apps.c b/applications/main/archive/helpers/archive_apps.c
index 43befc055b..7aca293649 100644
--- a/applications/main/archive/helpers/archive_apps.c
+++ b/applications/main/archive/helpers/archive_apps.c
@@ -30,8 +30,8 @@ bool archive_app_is_available(void* context, const char* path) {
bool file_exists = false;
Storage* storage = furi_record_open(RECORD_STORAGE);
- if(storage_file_exists(storage, ANY_PATH("u2f/key.u2f"))) {
- file_exists = storage_file_exists(storage, ANY_PATH("u2f/cnt.u2f"));
+ if(storage_file_exists(storage, EXT_PATH("u2f/key.u2f"))) {
+ file_exists = storage_file_exists(storage, EXT_PATH("u2f/cnt.u2f"));
}
furi_record_close(RECORD_STORAGE);
@@ -68,8 +68,8 @@ void archive_app_delete_file(void* context, const char* path) {
if(app == ArchiveAppTypeU2f) {
Storage* fs_api = furi_record_open(RECORD_STORAGE);
- res = (storage_common_remove(fs_api, ANY_PATH("u2f/key.u2f")) == FSE_OK);
- res |= (storage_common_remove(fs_api, ANY_PATH("u2f/cnt.u2f")) == FSE_OK);
+ res = (storage_common_remove(fs_api, EXT_PATH("u2f/key.u2f")) == FSE_OK);
+ res |= (storage_common_remove(fs_api, EXT_PATH("u2f/cnt.u2f")) == FSE_OK);
furi_record_close(RECORD_STORAGE);
if(archive_is_favorite("/app:u2f/U2F Token")) {
diff --git a/applications/main/archive/helpers/archive_browser.c b/applications/main/archive/helpers/archive_browser.c
index bd9b007456..96518cd7af 100644
--- a/applications/main/archive/helpers/archive_browser.c
+++ b/applications/main/archive/helpers/archive_browser.c
@@ -450,16 +450,14 @@ void archive_favorites_move_mode(ArchiveBrowserView* browser, bool active) {
}
static bool archive_is_dir_exists(FuriString* path) {
- if(furi_string_equal(path, STORAGE_ANY_PATH_PREFIX)) {
- return true;
- }
bool state = false;
FileInfo file_info;
Storage* storage = furi_record_open(RECORD_STORAGE);
- if(storage_common_stat(storage, furi_string_get_cstr(path), &file_info) == FSE_OK) {
- if(file_info_is_dir(&file_info)) {
- state = true;
- }
+
+ if(furi_string_equal(path, STORAGE_EXT_PATH_PREFIX)) {
+ state = storage_sd_status(storage) == FSE_OK;
+ } else if(storage_common_stat(storage, furi_string_get_cstr(path), &file_info) == FSE_OK) {
+ state = file_info_is_dir(&file_info);
}
furi_record_close(RECORD_STORAGE);
return state;
diff --git a/applications/main/archive/helpers/archive_browser.h b/applications/main/archive/helpers/archive_browser.h
index 1e1cdc8818..bdfeba035c 100644
--- a/applications/main/archive/helpers/archive_browser.h
+++ b/applications/main/archive/helpers/archive_browser.h
@@ -9,17 +9,17 @@
static const char* tab_default_paths[] = {
[ArchiveTabFavorites] = "/app:favorites",
- [ArchiveTabIButton] = ANY_PATH("ibutton"),
- [ArchiveTabNFC] = ANY_PATH("nfc"),
- [ArchiveTabSubGhz] = ANY_PATH("subghz"),
+ [ArchiveTabIButton] = EXT_PATH("ibutton"),
+ [ArchiveTabNFC] = EXT_PATH("nfc"),
+ [ArchiveTabSubGhz] = EXT_PATH("subghz"),
[ArchiveTabSubGhzRemote] = EXT_PATH("subghz_remote"),
- [ArchiveTabLFRFID] = ANY_PATH("lfrfid"),
- [ArchiveTabInfrared] = ANY_PATH("infrared"),
- [ArchiveTabBadUsb] = ANY_PATH("badusb"),
+ [ArchiveTabLFRFID] = EXT_PATH("lfrfid"),
+ [ArchiveTabInfrared] = EXT_PATH("infrared"),
+ [ArchiveTabBadUsb] = EXT_PATH("badusb"),
[ArchiveTabU2f] = "/app:u2f",
- [ArchiveTabApplications] = ANY_PATH("apps"),
+ [ArchiveTabApplications] = EXT_PATH("apps"),
[ArchiveTabInternal] = STORAGE_INT_PATH_PREFIX,
- [ArchiveTabBrowser] = STORAGE_ANY_PATH_PREFIX,
+ [ArchiveTabBrowser] = STORAGE_EXT_PATH_PREFIX,
};
static const char* known_ext[] = {
diff --git a/applications/main/archive/helpers/archive_favorites.h b/applications/main/archive/helpers/archive_favorites.h
index 64ffcdd7bd..75070c44d5 100644
--- a/applications/main/archive/helpers/archive_favorites.h
+++ b/applications/main/archive/helpers/archive_favorites.h
@@ -2,8 +2,8 @@
#include
-#define ARCHIVE_FAV_PATH ANY_PATH("favorites.txt")
-#define ARCHIVE_FAV_TEMP_PATH ANY_PATH("favorites.tmp")
+#define ARCHIVE_FAV_PATH EXT_PATH("favorites.txt")
+#define ARCHIVE_FAV_TEMP_PATH EXT_PATH("favorites.tmp")
uint16_t archive_favorites_count(void* context);
bool archive_favorites_read(void* context);
diff --git a/applications/main/archive/helpers/archive_files.c b/applications/main/archive/helpers/archive_files.c
index 3684fe6430..868fa8a117 100644
--- a/applications/main/archive/helpers/archive_files.c
+++ b/applications/main/archive/helpers/archive_files.c
@@ -15,7 +15,7 @@ void archive_set_file_type(ArchiveFile_t* file, const char* path, bool is_folder
} else {
for(size_t i = 0; i < COUNT_OF(known_ext); i++) {
if((known_ext[i][0] == '?') || (known_ext[i][0] == '*')) continue;
- if(furi_string_end_with(file->path, known_ext[i])) {
+ if(furi_string_end_withi(file->path, known_ext[i])) {
if((i == ArchiveFileTypeBadUsb) || (i == ArchiveFileTypeSubGhzRemote)) {
if(furi_string_search(
file->path, archive_get_default_path(ArchiveTabBadUsb)) == 0) {
diff --git a/applications/main/bad_usb/bad_usb_app.c b/applications/main/bad_usb/bad_usb_app.c
index 0f10d60d8f..2d2d4be86c 100644
--- a/applications/main/bad_usb/bad_usb_app.c
+++ b/applications/main/bad_usb/bad_usb_app.c
@@ -112,8 +112,6 @@ BadUsbApp* bad_usb_app_alloc(char* arg) {
app->dialogs = furi_record_open(RECORD_DIALOGS);
app->view_dispatcher = view_dispatcher_alloc();
- view_dispatcher_enable_queue(app->view_dispatcher);
-
app->scene_manager = scene_manager_alloc(&bad_usb_scene_handlers, app);
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
diff --git a/applications/main/bad_usb/icon.png b/applications/main/bad_usb/icon.png
index 037474aa3b..2b5a3bf973 100644
Binary files a/applications/main/bad_usb/icon.png and b/applications/main/bad_usb/icon.png differ
diff --git a/applications/main/gpio/gpio_app.c b/applications/main/gpio/gpio_app.c
index 217423ecc3..234cc793a8 100644
--- a/applications/main/gpio/gpio_app.c
+++ b/applications/main/gpio/gpio_app.c
@@ -32,7 +32,6 @@ GpioApp* gpio_app_alloc(void) {
app->view_dispatcher = view_dispatcher_alloc();
app->scene_manager = scene_manager_alloc(&gpio_scene_handlers, app);
- view_dispatcher_enable_queue(app->view_dispatcher);
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
view_dispatcher_set_custom_event_callback(
diff --git a/applications/main/gpio/icon.png b/applications/main/gpio/icon.png
index 4a6eccf058..7b54bb5cb3 100644
Binary files a/applications/main/gpio/icon.png and b/applications/main/gpio/icon.png differ
diff --git a/applications/main/ibutton/ibutton.c b/applications/main/ibutton/ibutton.c
index b6e8a20cf9..765d536125 100644
--- a/applications/main/ibutton/ibutton.c
+++ b/applications/main/ibutton/ibutton.c
@@ -85,7 +85,6 @@ iButton* ibutton_alloc(void) {
ibutton->scene_manager = scene_manager_alloc(&ibutton_scene_handlers, ibutton);
ibutton->view_dispatcher = view_dispatcher_alloc();
- view_dispatcher_enable_queue(ibutton->view_dispatcher);
view_dispatcher_set_event_callback_context(ibutton->view_dispatcher, ibutton);
view_dispatcher_set_custom_event_callback(
ibutton->view_dispatcher, ibutton_custom_event_callback);
diff --git a/applications/main/ibutton/ibutton_cli.c b/applications/main/ibutton/ibutton_cli.c
index 12e237e8a7..dcac8f9632 100644
--- a/applications/main/ibutton/ibutton_cli.c
+++ b/applications/main/ibutton/ibutton_cli.c
@@ -143,7 +143,7 @@ void ibutton_cli_write(Cli* cli, FuriString* args) {
}
if(!(ibutton_protocols_get_features(protocols, ibutton_key_get_protocol_id(key)) &
- iButtonProtocolFeatureWriteBlank)) {
+ iButtonProtocolFeatureWriteId)) {
ibutton_cli_print_usage();
break;
}
@@ -152,7 +152,7 @@ void ibutton_cli_write(Cli* cli, FuriString* args) {
ibutton_cli_print_key(protocols, key);
printf("Press Ctrl+C to abort\r\n");
- ibutton_worker_write_blank_start(worker, key);
+ ibutton_worker_write_id_start(worker, key);
while(true) {
uint32_t flags = furi_event_flag_wait(
write_context.event, EVENT_FLAG_IBUTTON_COMPLETE, FuriFlagWaitAny, 100);
diff --git a/applications/main/ibutton/ibutton_i.h b/applications/main/ibutton/ibutton_i.h
index d355a4ea5f..fc2324c635 100644
--- a/applications/main/ibutton/ibutton_i.h
+++ b/applications/main/ibutton/ibutton_i.h
@@ -28,7 +28,7 @@
#include "ibutton_custom_event.h"
#include "scenes/ibutton_scene.h"
-#define IBUTTON_APP_FOLDER ANY_PATH("ibutton")
+#define IBUTTON_APP_FOLDER EXT_PATH("ibutton")
#define IBUTTON_APP_FILENAME_PREFIX "iBtn"
#define IBUTTON_APP_FILENAME_EXTENSION ".ibtn"
@@ -36,7 +36,7 @@
typedef enum {
iButtonWriteModeInvalid,
- iButtonWriteModeBlank,
+ iButtonWriteModeId,
iButtonWriteModeCopy,
} iButtonWriteMode;
diff --git a/applications/main/ibutton/icon.png b/applications/main/ibutton/icon.png
index 2fdaf123a6..f73af065f4 100644
Binary files a/applications/main/ibutton/icon.png and b/applications/main/ibutton/icon.png differ
diff --git a/applications/main/ibutton/scenes/ibutton_scene_read_key_menu.c b/applications/main/ibutton/scenes/ibutton_scene_read_key_menu.c
index 890e0a2848..94ad4b69ee 100644
--- a/applications/main/ibutton/scenes/ibutton_scene_read_key_menu.c
+++ b/applications/main/ibutton/scenes/ibutton_scene_read_key_menu.c
@@ -5,7 +5,7 @@ typedef enum {
SubmenuIndexSave,
SubmenuIndexEmulate,
SubmenuIndexViewData,
- SubmenuIndexWriteBlank,
+ SubmenuIndexWriteId,
SubmenuIndexWriteCopy,
} SubmenuIndex;
@@ -30,11 +30,11 @@ void ibutton_scene_read_key_menu_on_enter(void* context) {
ibutton_scene_read_key_menu_submenu_callback,
ibutton);
- if(features & iButtonProtocolFeatureWriteBlank) {
+ if(features & iButtonProtocolFeatureWriteId) {
submenu_add_item(
submenu,
"Write ID",
- SubmenuIndexWriteBlank,
+ SubmenuIndexWriteId,
ibutton_scene_read_key_menu_submenu_callback,
ibutton);
}
@@ -78,8 +78,8 @@ bool ibutton_scene_read_key_menu_on_event(void* context, SceneManagerEvent event
dolphin_deed(DolphinDeedIbuttonEmulate);
} else if(event.event == SubmenuIndexViewData) {
scene_manager_next_scene(scene_manager, iButtonSceneViewData);
- } else if(event.event == SubmenuIndexWriteBlank) {
- ibutton->write_mode = iButtonWriteModeBlank;
+ } else if(event.event == SubmenuIndexWriteId) {
+ ibutton->write_mode = iButtonWriteModeId;
scene_manager_next_scene(scene_manager, iButtonSceneWrite);
} else if(event.event == SubmenuIndexWriteCopy) {
ibutton->write_mode = iButtonWriteModeCopy;
diff --git a/applications/main/ibutton/scenes/ibutton_scene_saved_key_menu.c b/applications/main/ibutton/scenes/ibutton_scene_saved_key_menu.c
index 668b79ae3c..6727c5458c 100644
--- a/applications/main/ibutton/scenes/ibutton_scene_saved_key_menu.c
+++ b/applications/main/ibutton/scenes/ibutton_scene_saved_key_menu.c
@@ -3,7 +3,7 @@
enum SubmenuIndex {
SubmenuIndexEmulate,
- SubmenuIndexWriteBlank,
+ SubmenuIndexWriteId,
SubmenuIndexWriteCopy,
SubmenuIndexEdit,
SubmenuIndexRename,
@@ -20,9 +20,9 @@ void ibutton_scene_saved_key_menu_on_enter(void* context) {
submenu_add_item(submenu, "Emulate", SubmenuIndexEmulate, ibutton_submenu_callback, ibutton);
- if(features & iButtonProtocolFeatureWriteBlank) {
+ if(features & iButtonProtocolFeatureWriteId) {
submenu_add_item(
- submenu, "Write ID", SubmenuIndexWriteBlank, ibutton_submenu_callback, ibutton);
+ submenu, "Write ID", SubmenuIndexWriteId, ibutton_submenu_callback, ibutton);
}
if(features & iButtonProtocolFeatureWriteCopy) {
@@ -55,8 +55,8 @@ bool ibutton_scene_saved_key_menu_on_event(void* context, SceneManagerEvent even
if(event.event == SubmenuIndexEmulate) {
scene_manager_next_scene(scene_manager, iButtonSceneEmulate);
dolphin_deed(DolphinDeedIbuttonEmulate);
- } else if(event.event == SubmenuIndexWriteBlank) {
- ibutton->write_mode = iButtonWriteModeBlank;
+ } else if(event.event == SubmenuIndexWriteId) {
+ ibutton->write_mode = iButtonWriteModeId;
scene_manager_next_scene(scene_manager, iButtonSceneWrite);
} else if(event.event == SubmenuIndexWriteCopy) {
ibutton->write_mode = iButtonWriteModeCopy;
diff --git a/applications/main/ibutton/scenes/ibutton_scene_write.c b/applications/main/ibutton/scenes/ibutton_scene_write.c
index 0fc073fdae..4525eae7b8 100644
--- a/applications/main/ibutton/scenes/ibutton_scene_write.c
+++ b/applications/main/ibutton/scenes/ibutton_scene_write.c
@@ -51,9 +51,9 @@ void ibutton_scene_write_on_enter(void* context) {
ibutton_worker_write_set_callback(worker, ibutton_scene_write_callback, ibutton);
- if(ibutton->write_mode == iButtonWriteModeBlank) {
+ if(ibutton->write_mode == iButtonWriteModeId) {
furi_string_set(tmp, "Writing ID");
- ibutton_worker_write_blank_start(worker, key);
+ ibutton_worker_write_id_start(worker, key);
} else if(ibutton->write_mode == iButtonWriteModeCopy) {
furi_string_set(tmp, "Full Writing");
diff --git a/applications/main/infrared/icon.png b/applications/main/infrared/icon.png
index 22c986180a..36c214f3b8 100644
Binary files a/applications/main/infrared/icon.png and b/applications/main/infrared/icon.png differ
diff --git a/applications/main/infrared/infrared_app.c b/applications/main/infrared/infrared_app.c
index d860aa3b23..db178fb42b 100644
--- a/applications/main/infrared/infrared_app.c
+++ b/applications/main/infrared/infrared_app.c
@@ -150,7 +150,6 @@ static InfraredApp* infrared_alloc(void) {
infrared->gui = furi_record_open(RECORD_GUI);
ViewDispatcher* view_dispatcher = infrared->view_dispatcher;
- view_dispatcher_enable_queue(view_dispatcher);
view_dispatcher_set_event_callback_context(view_dispatcher, infrared);
view_dispatcher_set_custom_event_callback(view_dispatcher, infrared_custom_event_callback);
view_dispatcher_set_navigation_event_callback(view_dispatcher, infrared_back_event_callback);
diff --git a/applications/main/infrared/infrared_app_i.h b/applications/main/infrared/infrared_app_i.h
index d353b2503b..75d4e230d2 100644
--- a/applications/main/infrared/infrared_app_i.h
+++ b/applications/main/infrared/infrared_app_i.h
@@ -46,7 +46,7 @@
#define INFRARED_MAX_BUTTON_NAME_LENGTH 22
#define INFRARED_MAX_REMOTE_NAME_LENGTH 22
-#define INFRARED_APP_FOLDER ANY_PATH("infrared")
+#define INFRARED_APP_FOLDER EXT_PATH("infrared")
#define INFRARED_APP_EXTENSION ".ir"
#define INFRARED_DEFAULT_REMOTE_NAME "Remote"
diff --git a/applications/main/lfrfid/icon.png b/applications/main/lfrfid/icon.png
index ce01284a2c..fd3947ff36 100644
Binary files a/applications/main/lfrfid/icon.png and b/applications/main/lfrfid/icon.png differ
diff --git a/applications/main/lfrfid/lfrfid.c b/applications/main/lfrfid/lfrfid.c
index a405c0f853..b51a0da260 100644
--- a/applications/main/lfrfid/lfrfid.c
+++ b/applications/main/lfrfid/lfrfid.c
@@ -77,7 +77,6 @@ static LfRfid* lfrfid_alloc(void) {
lfrfid->view_dispatcher = view_dispatcher_alloc();
lfrfid->scene_manager = scene_manager_alloc(&lfrfid_scene_handlers, lfrfid);
- view_dispatcher_enable_queue(lfrfid->view_dispatcher);
view_dispatcher_set_event_callback_context(lfrfid->view_dispatcher, lfrfid);
view_dispatcher_set_custom_event_callback(
lfrfid->view_dispatcher, lfrfid_debug_custom_event_callback);
diff --git a/applications/main/lfrfid/lfrfid_i.h b/applications/main/lfrfid/lfrfid_i.h
index f949e73e66..913b7358bd 100644
--- a/applications/main/lfrfid/lfrfid_i.h
+++ b/applications/main/lfrfid/lfrfid_i.h
@@ -38,7 +38,7 @@
#define LFRFID_KEY_NAME_SIZE 22
#define LFRFID_TEXT_STORE_SIZE 40
-#define LFRFID_APP_FOLDER ANY_PATH("lfrfid")
+#define LFRFID_APP_FOLDER EXT_PATH("lfrfid")
#define LFRFID_SD_FOLDER EXT_PATH("lfrfid")
#define LFRFID_APP_FILENAME_PREFIX "RFID"
#define LFRFID_APP_FILENAME_EXTENSION ".rfid"
diff --git a/applications/main/nfc/helpers/mf_user_dict.c b/applications/main/nfc/helpers/mf_user_dict.c
index 70b1114722..7f60d339e6 100644
--- a/applications/main/nfc/helpers/mf_user_dict.c
+++ b/applications/main/nfc/helpers/mf_user_dict.c
@@ -4,7 +4,7 @@
#include
#include
-#define NFC_APP_FOLDER ANY_PATH("nfc")
+#define NFC_APP_FOLDER EXT_PATH("nfc")
#define NFC_APP_MF_CLASSIC_DICT_USER_PATH (NFC_APP_FOLDER "/assets/mf_classic_dict_user.nfc")
struct MfUserDict {
diff --git a/applications/main/nfc/helpers/nfc_detected_protocols.c b/applications/main/nfc/helpers/nfc_detected_protocols.c
new file mode 100644
index 0000000000..339d4ddc23
--- /dev/null
+++ b/applications/main/nfc/helpers/nfc_detected_protocols.c
@@ -0,0 +1,85 @@
+#include "nfc_detected_protocols.h"
+
+#include
+
+struct NfcDetectedProtocols {
+ uint32_t protocols_detected_num;
+ NfcProtocol protocols_detected[NfcProtocolNum];
+ uint32_t selected_idx;
+};
+
+NfcDetectedProtocols* nfc_detected_protocols_alloc(void) {
+ NfcDetectedProtocols* instance = malloc(sizeof(NfcDetectedProtocols));
+
+ instance->protocols_detected_num = 0;
+ instance->selected_idx = 0;
+
+ return instance;
+}
+
+void nfc_detected_protocols_free(NfcDetectedProtocols* instance) {
+ furi_assert(instance);
+
+ free(instance);
+}
+
+void nfc_detected_protocols_reset(NfcDetectedProtocols* instance) {
+ furi_assert(instance);
+
+ instance->protocols_detected_num = 0;
+ memset(instance->protocols_detected, 0, sizeof(instance->protocols_detected));
+ instance->selected_idx = 0;
+}
+
+void nfc_detected_protocols_select(NfcDetectedProtocols* instance, uint32_t idx) {
+ furi_assert(instance);
+
+ instance->selected_idx = idx;
+}
+
+void nfc_detected_protocols_set(
+ NfcDetectedProtocols* instance,
+ const NfcProtocol* types,
+ uint32_t count) {
+ furi_assert(instance);
+ furi_assert(types);
+ furi_assert(count < NfcProtocolNum);
+
+ memcpy(instance->protocols_detected, types, count);
+ instance->protocols_detected_num = count;
+ instance->selected_idx = 0;
+}
+
+uint32_t nfc_detected_protocols_get_num(NfcDetectedProtocols* instance) {
+ furi_assert(instance);
+
+ return instance->protocols_detected_num;
+}
+
+NfcProtocol nfc_detected_protocols_get_protocol(NfcDetectedProtocols* instance, uint32_t idx) {
+ furi_assert(instance);
+ furi_assert(idx < instance->protocols_detected_num);
+
+ return instance->protocols_detected[idx];
+}
+
+void nfc_detected_protocols_fill_all_protocols(NfcDetectedProtocols* instance) {
+ furi_assert(instance);
+
+ instance->protocols_detected_num = NfcProtocolNum;
+ for(uint32_t i = 0; i < NfcProtocolNum; i++) {
+ instance->protocols_detected[i] = i;
+ }
+}
+
+NfcProtocol nfc_detected_protocols_get_selected(NfcDetectedProtocols* instance) {
+ furi_assert(instance);
+
+ return instance->protocols_detected[instance->selected_idx];
+}
+
+uint32_t nfc_detected_protocols_get_selected_idx(NfcDetectedProtocols* instance) {
+ furi_assert(instance);
+
+ return instance->selected_idx;
+}
diff --git a/applications/main/nfc/helpers/nfc_detected_protocols.h b/applications/main/nfc/helpers/nfc_detected_protocols.h
new file mode 100644
index 0000000000..2ab46add39
--- /dev/null
+++ b/applications/main/nfc/helpers/nfc_detected_protocols.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include
+#include
+
+typedef struct NfcDetectedProtocols NfcDetectedProtocols;
+
+NfcDetectedProtocols* nfc_detected_protocols_alloc(void);
+
+void nfc_detected_protocols_free(NfcDetectedProtocols* instance);
+
+void nfc_detected_protocols_reset(NfcDetectedProtocols* instance);
+
+void nfc_detected_protocols_select(NfcDetectedProtocols* instance, uint32_t idx);
+
+void nfc_detected_protocols_set(
+ NfcDetectedProtocols* instance,
+ const NfcProtocol* types,
+ uint32_t count);
+
+uint32_t nfc_detected_protocols_get_num(NfcDetectedProtocols* instance);
+
+NfcProtocol nfc_detected_protocols_get_protocol(NfcDetectedProtocols* instance, uint32_t idx);
+
+void nfc_detected_protocols_fill_all_protocols(NfcDetectedProtocols* instance);
+
+NfcProtocol nfc_detected_protocols_get_selected(NfcDetectedProtocols* instance);
+
+uint32_t nfc_detected_protocols_get_selected_idx(NfcDetectedProtocols* instance);
diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c
index 063e0ece37..c56b553903 100644
--- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c
+++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c
@@ -150,8 +150,7 @@ static void nfc_protocol_support_scene_read_on_enter(NfcApp* instance) {
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup);
- const NfcProtocol protocol =
- instance->protocols_detected[instance->protocols_detected_selected_idx];
+ const NfcProtocol protocol = nfc_detected_protocols_get_selected(instance->detected_protocols);
instance->poller = nfc_poller_alloc(instance->nfc, protocol);
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup);
@@ -186,7 +185,7 @@ static bool nfc_protocol_support_scene_read_on_event(NfcApp* instance, SceneMana
consumed = true;
} else {
const NfcProtocol protocol =
- instance->protocols_detected[instance->protocols_detected_selected_idx];
+ nfc_detected_protocols_get_selected(instance->detected_protocols);
consumed = nfc_protocol_support[protocol]->scene_read.on_event(instance, event);
}
} else if(event.event == NfcCustomEventPollerFailure) {
@@ -199,7 +198,7 @@ static bool nfc_protocol_support_scene_read_on_event(NfcApp* instance, SceneMana
consumed = true;
} else if(event.event == NfcCustomEventCardDetected) {
const NfcProtocol protocol =
- instance->protocols_detected[instance->protocols_detected_selected_idx];
+ nfc_detected_protocols_get_selected(instance->detected_protocols);
consumed = nfc_protocol_support[protocol]->scene_read.on_event(instance, event);
}
} else if(event.type == SceneManagerEventTypeBack) {
diff --git a/applications/main/nfc/icon.png b/applications/main/nfc/icon.png
index 6bc027111a..e998d291ee 100644
Binary files a/applications/main/nfc/icon.png and b/applications/main/nfc/icon.png differ
diff --git a/applications/main/nfc/nfc_app.c b/applications/main/nfc/nfc_app.c
index ce444982dd..9f855f9e9f 100644
--- a/applications/main/nfc/nfc_app.c
+++ b/applications/main/nfc/nfc_app.c
@@ -41,7 +41,6 @@ NfcApp* nfc_app_alloc(void) {
instance->view_dispatcher = view_dispatcher_alloc();
instance->scene_manager = scene_manager_alloc(&nfc_scene_handlers, instance);
- view_dispatcher_enable_queue(instance->view_dispatcher);
view_dispatcher_set_event_callback_context(instance->view_dispatcher, instance);
view_dispatcher_set_custom_event_callback(
instance->view_dispatcher, nfc_custom_event_callback);
@@ -50,6 +49,7 @@ NfcApp* nfc_app_alloc(void) {
instance->nfc = nfc_alloc();
+ instance->detected_protocols = nfc_detected_protocols_alloc();
instance->felica_auth = felica_auth_alloc();
instance->mf_ul_auth = mf_ultralight_auth_alloc();
instance->slix_unlock = slix_unlock_alloc();
@@ -142,6 +142,7 @@ void nfc_app_free(NfcApp* instance) {
nfc_free(instance->nfc);
+ nfc_detected_protocols_free(instance->detected_protocols);
felica_auth_free(instance->felica_auth);
mf_ultralight_auth_free(instance->mf_ul_auth);
slix_unlock_free(instance->slix_unlock);
@@ -433,23 +434,6 @@ void nfc_show_loading_popup(void* context, bool show) {
}
}
-void nfc_app_set_detected_protocols(NfcApp* instance, const NfcProtocol* types, uint32_t count) {
- furi_assert(instance);
- furi_assert(types);
- furi_assert(count < NfcProtocolNum);
-
- memcpy(instance->protocols_detected, types, count);
- instance->protocols_detected_num = count;
- instance->protocols_detected_selected_idx = 0;
-}
-
-void nfc_app_reset_detected_protocols(NfcApp* instance) {
- furi_assert(instance);
-
- instance->protocols_detected_selected_idx = 0;
- instance->protocols_detected_num = 0;
-}
-
void nfc_append_filename_string_when_present(NfcApp* instance, FuriString* string) {
furi_assert(instance);
furi_assert(string);
diff --git a/applications/main/nfc/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h
index 9a9c81004d..1c174986ea 100644
--- a/applications/main/nfc/nfc_app_i.h
+++ b/applications/main/nfc/nfc_app_i.h
@@ -26,6 +26,7 @@
#include "views/dict_attack.h"
#include
+#include "helpers/nfc_detected_protocols.h"
#include "helpers/nfc_custom_event.h"
#include "helpers/mf_ultralight_auth.h"
#include "helpers/mf_user_dict.h"
@@ -66,7 +67,7 @@
#define NFC_TEXT_STORE_SIZE 128
#define NFC_BYTE_INPUT_STORE_SIZE 10
#define NFC_LOG_SIZE_MAX (1024)
-#define NFC_APP_FOLDER ANY_PATH("nfc")
+#define NFC_APP_FOLDER EXT_PATH("nfc")
#define NFC_APP_EXTENSION ".nfc"
#define NFC_APP_SHADOW_EXTENSION ".shd"
#define NFC_APP_FILENAME_PREFIX "NFC"
@@ -107,9 +108,7 @@ struct NfcApp {
FuriString* text_box_store;
uint8_t byte_input_store[NFC_BYTE_INPUT_STORE_SIZE];
- uint32_t protocols_detected_num;
- NfcProtocol protocols_detected[NfcProtocolNum];
- uint32_t protocols_detected_selected_idx;
+ NfcDetectedProtocols* detected_protocols;
RpcAppSystem* rpc_ctx;
NfcRpcState rpc_state;
@@ -194,8 +193,4 @@ bool nfc_save_file(NfcApp* instance, FuriString* path);
void nfc_make_app_folder(NfcApp* instance);
-void nfc_app_set_detected_protocols(NfcApp* instance, const NfcProtocol* types, uint32_t count);
-
-void nfc_app_reset_detected_protocols(NfcApp* instance);
-
void nfc_append_filename_string_when_present(NfcApp* instance, FuriString* string);
diff --git a/applications/main/nfc/plugins/supported_cards/plantain.c b/applications/main/nfc/plugins/supported_cards/plantain.c
index 11b60d6fa0..36f745697d 100644
--- a/applications/main/nfc/plugins/supported_cards/plantain.c
+++ b/applications/main/nfc/plugins/supported_cards/plantain.c
@@ -229,7 +229,7 @@ static bool plantain_parse(const NfcDevice* device, FuriString* parsed_data) {
}
furi_string_printf(
- parsed_data, "\e#Plantain\nNo.: %llu?\nBalance:%lu\n", card_number, balance);
+ parsed_data, "\e#Plantain\nNo.: %lluX\nBalance: %lu\n", card_number, balance);
parsed = true;
} while(false);
diff --git a/applications/main/nfc/plugins/supported_cards/two_cities.c b/applications/main/nfc/plugins/supported_cards/two_cities.c
index 2f92030d95..dc87d3072f 100644
--- a/applications/main/nfc/plugins/supported_cards/two_cities.c
+++ b/applications/main/nfc/plugins/supported_cards/two_cities.c
@@ -157,7 +157,7 @@ static bool two_cities_parse(const NfcDevice* device, FuriString* parsed_data) {
furi_string_printf(
parsed_data,
- "\e#Troika+Plantain\nPN: %llu?\nPB: %lu rur.\nTN: %lu\nTB: %u rur.\n",
+ "\e#Troika+Plantain\nPN: %lluX\nPB: %lu rur.\nTN: %lu\nTB: %u rur.\n",
card_number,
balance,
troika_number,
diff --git a/applications/main/nfc/scenes/nfc_scene_detect.c b/applications/main/nfc/scenes/nfc_scene_detect.c
index 3ef153657d..7ef3f9d870 100644
--- a/applications/main/nfc/scenes/nfc_scene_detect.c
+++ b/applications/main/nfc/scenes/nfc_scene_detect.c
@@ -7,7 +7,8 @@ void nfc_scene_detect_scan_callback(NfcScannerEvent event, void* context) {
NfcApp* instance = context;
if(event.type == NfcScannerEventTypeDetected) {
- nfc_app_set_detected_protocols(instance, event.data.protocols, event.data.protocol_num);
+ nfc_detected_protocols_set(
+ instance->detected_protocols, event.data.protocols, event.data.protocol_num);
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventWorkerExit);
}
}
@@ -23,7 +24,7 @@ void nfc_scene_detect_on_enter(void* context) {
popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50);
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup);
- nfc_app_reset_detected_protocols(instance);
+ nfc_detected_protocols_reset(instance->detected_protocols);
instance->scanner = nfc_scanner_alloc(instance->nfc);
nfc_scanner_start(instance->scanner, nfc_scene_detect_scan_callback, instance);
@@ -37,7 +38,7 @@ bool nfc_scene_detect_on_event(void* context, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == NfcCustomEventWorkerExit) {
- if(instance->protocols_detected_num > 1) {
+ if(nfc_detected_protocols_get_num(instance->detected_protocols) > 1) {
notification_message(instance->notifications, &sequence_single_vibro);
scene_manager_next_scene(instance->scene_manager, NfcSceneSelectProtocol);
} else {
diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c
index 4df8a62899..a0b6986d1e 100644
--- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c
+++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c
@@ -57,7 +57,8 @@ bool nfc_scene_mf_ultralight_unlock_warn_on_event(void* context, SceneManagerEve
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == DialogExResultRight) {
const NfcProtocol mfu_protocol[] = {NfcProtocolMfUltralight};
- nfc_app_set_detected_protocols(nfc, mfu_protocol, COUNT_OF(mfu_protocol));
+ nfc_detected_protocols_set(
+ nfc->detected_protocols, mfu_protocol, COUNT_OF(mfu_protocol));
scene_manager_next_scene(nfc->scene_manager, NfcSceneRead);
dolphin_deed(DolphinDeedNfcRead);
consumed = true;
@@ -77,7 +78,8 @@ bool nfc_scene_mf_ultralight_unlock_warn_on_event(void* context, SceneManagerEve
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == DialogExResultCenter) {
const NfcProtocol mfu_protocol[] = {NfcProtocolMfUltralight};
- nfc_app_set_detected_protocols(nfc, mfu_protocol, COUNT_OF(mfu_protocol));
+ nfc_detected_protocols_set(
+ nfc->detected_protocols, mfu_protocol, COUNT_OF(mfu_protocol));
scene_manager_next_scene(nfc->scene_manager, NfcSceneRead);
dolphin_deed(DolphinDeedNfcRead);
consumed = true;
diff --git a/applications/main/nfc/scenes/nfc_scene_select_protocol.c b/applications/main/nfc/scenes/nfc_scene_select_protocol.c
index af644035e8..f2c92b6313 100644
--- a/applications/main/nfc/scenes/nfc_scene_select_protocol.c
+++ b/applications/main/nfc/scenes/nfc_scene_select_protocol.c
@@ -14,21 +14,19 @@ void nfc_scene_select_protocol_on_enter(void* context) {
const char* prefix;
if(scene_manager_has_previous_scene(instance->scene_manager, NfcSceneExtraActions)) {
prefix = "Read";
- instance->protocols_detected_num = NfcProtocolNum;
- for(uint32_t i = 0; i < NfcProtocolNum; i++) {
- instance->protocols_detected[i] = i;
- }
+ nfc_detected_protocols_fill_all_protocols(instance->detected_protocols);
} else {
prefix = "Read as";
submenu_set_header(submenu, "Multi-protocol card");
}
- for(uint32_t i = 0; i < instance->protocols_detected_num; i++) {
+ for(uint32_t i = 0; i < nfc_detected_protocols_get_num(instance->detected_protocols); i++) {
furi_string_printf(
temp_str,
"%s %s",
prefix,
- nfc_device_get_protocol_name(instance->protocols_detected[i]));
+ nfc_device_get_protocol_name(
+ nfc_detected_protocols_get_protocol(instance->detected_protocols, i)));
furi_string_replace_str(temp_str, "Mifare", "MIFARE");
submenu_add_item(
@@ -40,9 +38,8 @@ void nfc_scene_select_protocol_on_enter(void* context) {
}
furi_string_free(temp_str);
- const uint32_t state =
- scene_manager_get_scene_state(instance->scene_manager, NfcSceneSelectProtocol);
- submenu_set_selected_item(submenu, state);
+ submenu_set_selected_item(
+ submenu, nfc_detected_protocols_get_selected_idx(instance->detected_protocols));
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewMenu);
}
@@ -52,10 +49,8 @@ bool nfc_scene_select_protocol_on_event(void* context, SceneManagerEvent event)
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
- instance->protocols_detected_selected_idx = event.event;
+ nfc_detected_protocols_select(instance->detected_protocols, event.event);
scene_manager_next_scene(instance->scene_manager, NfcSceneRead);
- scene_manager_set_scene_state(
- instance->scene_manager, NfcSceneSelectProtocol, event.event);
consumed = true;
} else if(event.type == SceneManagerEventTypeBack) {
if(scene_manager_has_previous_scene(instance->scene_manager, NfcSceneDetect)) {
diff --git a/applications/main/nfc/scenes/nfc_scene_start.c b/applications/main/nfc/scenes/nfc_scene_start.c
index e8774b4aa5..53857b8495 100644
--- a/applications/main/nfc/scenes/nfc_scene_start.c
+++ b/applications/main/nfc/scenes/nfc_scene_start.c
@@ -25,7 +25,7 @@ void nfc_scene_start_on_enter(void* context) {
nfc_device_clear(nfc->nfc_device);
iso14443_3a_reset(nfc->iso14443_3a_edit_data);
// Reset detected protocols list
- nfc_app_reset_detected_protocols(nfc);
+ nfc_detected_protocols_reset(nfc->detected_protocols);
submenu_add_item(submenu, "Read", SubmenuIndexRead, nfc_scene_start_submenu_callback, nfc);
submenu_add_item(
diff --git a/applications/main/subghz/icon.png b/applications/main/subghz/icon.png
index 5a25fdf4ef..70940ad779 100644
Binary files a/applications/main/subghz/icon.png and b/applications/main/subghz/icon.png differ
diff --git a/applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c b/applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c
index 2a0b5e498a..9e5289c548 100644
--- a/applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c
+++ b/applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c
@@ -69,8 +69,9 @@ bool subghz_scene_frequency_analyzer_on_event(void* context, SceneManagerEvent e
return true;
} else if(event.event == SubGhzCustomEventViewFreqAnalOkLong) {
- // Don't need to save, we already saved on short event
- //scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneStart, 10);
+ // Don't need to save, we already saved on short event (and on exit event too)
+ subghz_rx_key_state_set(subghz, SubGhzRxKeyStateIDLE);
+ scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneStart, 10);
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiver);
return true;
}
diff --git a/applications/main/subghz/scenes/subghz_scene_receiver.c b/applications/main/subghz/scenes/subghz_scene_receiver.c
index 01b46d2486..1a00634180 100644
--- a/applications/main/subghz/scenes/subghz_scene_receiver.c
+++ b/applications/main/subghz/scenes/subghz_scene_receiver.c
@@ -146,11 +146,11 @@ static void subghz_scene_add_to_history_callback(
if(subghz_history_get_text_space_left(subghz->history, NULL)) {
notification_message(subghz->notifications, &sequence_error);
}
+ subghz_rx_key_state_set(subghz, SubGhzRxKeyStateAddKey);
}
subghz_receiver_reset(receiver);
furi_string_free(item_name);
furi_string_free(item_time);
- subghz_rx_key_state_set(subghz, SubGhzRxKeyStateAddKey);
} else {
FURI_LOG_D(TAG, "%s protocol ignored", decoder_base->protocol->name);
}
diff --git a/applications/main/subghz/scenes/subghz_scene_rpc.c b/applications/main/subghz/scenes/subghz_scene_rpc.c
index 2f8a1f03f7..4675afaebf 100644
--- a/applications/main/subghz/scenes/subghz_scene_rpc.c
+++ b/applications/main/subghz/scenes/subghz_scene_rpc.c
@@ -59,7 +59,8 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) {
default: //if(SubGhzTxRxStartTxStateOk)
result = true;
subghz_blink_start(subghz);
- state = SubGhzRpcStateTx;
+ scene_manager_set_scene_state(
+ subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateTx);
break;
}
}
@@ -71,7 +72,8 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) {
subghz_blink_stop(subghz);
result = true;
}
- state = SubGhzRpcStateIdle;
+ scene_manager_set_scene_state(
+ subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateIdle);
rpc_system_app_confirm(subghz->rpc_ctx, result);
} else if(event.event == SubGhzCustomEventSceneRpcLoad) {
bool result = false;
diff --git a/applications/main/subghz/subghz.c b/applications/main/subghz/subghz.c
index 7a5fad74c9..30480054ec 100644
--- a/applications/main/subghz/subghz.c
+++ b/applications/main/subghz/subghz.c
@@ -97,7 +97,6 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) {
// View Dispatcher
subghz->view_dispatcher = view_dispatcher_alloc();
- view_dispatcher_enable_queue(subghz->view_dispatcher);
subghz->scene_manager = scene_manager_alloc(&subghz_scene_handlers, subghz);
view_dispatcher_set_event_callback_context(subghz->view_dispatcher, subghz);
diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c
index 43bb25ab83..43ba9c6440 100644
--- a/applications/main/subghz/subghz_cli.c
+++ b/applications/main/subghz/subghz_cli.c
@@ -478,7 +478,7 @@ void subghz_cli_command_rx_raw(Cli* cli, FuriString* args, void* context) {
void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) {
UNUSED(context);
FuriString* file_name = furi_string_alloc();
- furi_string_set(file_name, ANY_PATH("subghz/test.sub"));
+ furi_string_set(file_name, EXT_PATH("subghz/test.sub"));
Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* fff_data_file = flipper_format_file_alloc(storage);
@@ -592,7 +592,7 @@ void subghz_cli_command_tx_from_file(Cli* cli, FuriString* args, void* context)
UNUSED(context);
FuriString* file_name;
file_name = furi_string_alloc();
- furi_string_set(file_name, ANY_PATH("subghz/test.sub"));
+ furi_string_set(file_name, EXT_PATH("subghz/test.sub"));
uint32_t repeat = 10;
uint32_t device_ind = 0; // 0 - CC1101_INT, 1 - CC1101_EXT
diff --git a/applications/main/subghz/views/subghz_frequency_analyzer.c b/applications/main/subghz/views/subghz_frequency_analyzer.c
index 1bb2f044fe..fde3a1f613 100644
--- a/applications/main/subghz/views/subghz_frequency_analyzer.c
+++ b/applications/main/subghz/views/subghz_frequency_analyzer.c
@@ -1,7 +1,6 @@
#include "subghz_frequency_analyzer.h"
#include
-#include
#include
#include
#include
@@ -12,20 +11,79 @@
#define TAG "frequency_analyzer"
-#define RSSI_MIN -97
-#define RSSI_MAX -60
-#define RSSI_SCALE 2.3
+#define RSSI_MIN (-97.0f)
+#define RSSI_MAX (-60.0f)
+#define RSSI_SCALE 2.3f
#define TRIGGER_STEP 1
#define MAX_HISTORY 4
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
+#endif
static const uint32_t subghz_frequency_list[] = {
- 300000000, 302757000, 303875000, 303900000, 304250000, 307000000, 307500000, 307800000,
- 309000000, 310000000, 312000000, 312100000, 313000000, 313850000, 314000000, 314350000,
- 314980000, 315000000, 318000000, 330000000, 345000000, 348000000, 350000000, 387000000,
- 390000000, 418000000, 430000000, 431000000, 431500000, 433075000, 433220000, 433420000,
- 433657070, 433889000, 433920000, 434075000, 434176948, 434390000, 434420000, 434775000,
- 438900000, 440175000, 464000000, 779000000, 868350000, 868400000, 868800000, 868950000,
- 906400000, 915000000, 925000000, 928000000};
+ /* 300 - 348 */
+ 300000000,
+ 302757000,
+ 303875000,
+ 303900000,
+ 304250000,
+ 307000000,
+ 307500000,
+ 307800000,
+ 309000000,
+ 310000000,
+ 312000000,
+ 312100000,
+ 312200000,
+ 313000000,
+ 313850000,
+ 314000000,
+ 314350000,
+ 314980000,
+ 315000000,
+ 318000000,
+ 330000000,
+ 345000000,
+ 348000000,
+ 350000000,
+
+ /* 387 - 464 */
+ 387000000,
+ 390000000,
+ 418000000,
+ 430000000,
+ 430500000,
+ 431000000,
+ 431500000,
+ 433075000, /* LPD433 first */
+ 433220000,
+ 433420000,
+ 433657070,
+ 433889000,
+ 433920000, /* LPD433 mid */
+ 434075000,
+ 434176948,
+ 434190000,
+ 434390000,
+ 434420000,
+ 434620000,
+ 434775000, /* LPD433 last channels */
+ 438900000,
+ 440175000,
+ 464000000,
+ 467750000,
+
+ /* 779 - 928 */
+ 779000000,
+ 868350000,
+ 868400000,
+ 868800000,
+ 868950000,
+ 906400000,
+ 915000000,
+ 925000000,
+ 928000000,
+};
typedef enum {
SubGhzFrequencyAnalyzerStatusIDLE,
@@ -80,7 +138,7 @@ void subghz_frequency_analyzer_draw_rssi(
uint8_t x,
uint8_t y) {
// Current RSSI
- if(rssi) {
+ if(!float_is_equal(rssi, 0.f)) {
if(rssi > RSSI_MAX) {
rssi = RSSI_MAX;
}
@@ -95,7 +153,7 @@ void subghz_frequency_analyzer_draw_rssi(
}
// Last RSSI
- if(rssi_last) {
+ if(!float_is_equal(rssi_last, 0.f)) {
if(rssi_last > RSSI_MAX) {
rssi_last = RSSI_MAX;
}
@@ -108,7 +166,7 @@ void subghz_frequency_analyzer_draw_rssi(
// Trigger cursor
trigger = (trigger - RSSI_MIN) / RSSI_SCALE;
- uint8_t tr_x = x + 2 * trigger;
+ uint8_t tr_x = (uint8_t)((float)x + (2 * trigger));
canvas_draw_dot(canvas, tr_x, y + 4);
canvas_draw_line(canvas, tr_x - 1, y + 5, tr_x + 1, y + 5);
@@ -118,7 +176,7 @@ void subghz_frequency_analyzer_draw_rssi(
static void subghz_frequency_analyzer_history_frequency_draw(
Canvas* canvas,
SubGhzFrequencyAnalyzerModel* model) {
- char buffer[64];
+ char buffer[64] = {0};
const uint8_t x1 = 2;
const uint8_t x2 = 66;
const uint8_t y = 37;
@@ -161,7 +219,7 @@ static void subghz_frequency_analyzer_history_frequency_draw(
}
void subghz_frequency_analyzer_draw(Canvas* canvas, SubGhzFrequencyAnalyzerModel* model) {
- char buffer[64];
+ char buffer[64] = {0};
// Title
canvas_set_color(canvas, ColorBlack);
@@ -190,9 +248,7 @@ void subghz_frequency_analyzer_draw(Canvas* canvas, SubGhzFrequencyAnalyzerModel
canvas_draw_box(canvas, 4, 10, 121, 19);
canvas_set_color(canvas, ColorWhite);
} else {
- // TODO: Disable this
- //canvas_draw_box(canvas, 4, 11, 121, 19);
- //canvas_set_color(canvas, ColorWhite);
+ canvas_set_color(canvas, ColorBlack);
}
canvas_draw_str(canvas, 8, 26, buffer);
@@ -224,11 +280,14 @@ void subghz_frequency_analyzer_draw(Canvas* canvas, SubGhzFrequencyAnalyzerModel
uint32_t subghz_frequency_find_correct(uint32_t input) {
uint32_t prev_freq = 0;
- uint32_t current = 0;
uint32_t result = 0;
+ uint32_t current;
- for(size_t i = 0; i < sizeof(subghz_frequency_list); i++) {
+ for(size_t i = 0; i < ARRAY_SIZE(subghz_frequency_list) - 1; i++) {
current = subghz_frequency_list[i];
+ if(current == 0) {
+ continue;
+ }
if(current == input) {
result = current;
break;
@@ -249,47 +308,40 @@ uint32_t subghz_frequency_find_correct(uint32_t input) {
bool subghz_frequency_analyzer_input(InputEvent* event, void* context) {
furi_assert(context);
- SubGhzFrequencyAnalyzer* instance = context;
+ SubGhzFrequencyAnalyzer* instance = (SubGhzFrequencyAnalyzer*)context;
bool need_redraw = false;
-
if(event->key == InputKeyBack) {
- return false;
+ return need_redraw;
}
- if(((event->type == InputTypePress) || (event->type == InputTypeRepeat)) &&
- ((event->key == InputKeyLeft) || (event->key == InputKeyRight))) {
+ bool is_press_or_repeat = (event->type == InputTypePress) || (event->type == InputTypeRepeat);
+ if(is_press_or_repeat && (event->key == InputKeyLeft || event->key == InputKeyRight)) {
// Trigger setup
float trigger_level = subghz_frequency_analyzer_worker_get_trigger_level(instance->worker);
- switch(event->key) {
- case InputKeyLeft:
+ if(event->key == InputKeyLeft) {
trigger_level -= TRIGGER_STEP;
if(trigger_level < RSSI_MIN) {
trigger_level = RSSI_MIN;
}
- break;
- default:
- case InputKeyRight:
+ } else {
trigger_level += TRIGGER_STEP;
if(trigger_level > RSSI_MAX) {
trigger_level = RSSI_MAX;
}
- break;
}
subghz_frequency_analyzer_worker_set_trigger_level(instance->worker, trigger_level);
FURI_LOG_D(TAG, "trigger = %.1f", (double)trigger_level);
need_redraw = true;
} else if(event->type == InputTypePress && event->key == InputKeyUp) {
- if(instance->feedback_level == 0) {
- instance->feedback_level = 2;
+ if(instance->feedback_level == SubGHzFrequencyAnalyzerFeedbackLevelAll) {
+ instance->feedback_level = SubGHzFrequencyAnalyzerFeedbackLevelMute;
} else {
instance->feedback_level--;
}
need_redraw = true;
- } else if(
- ((event->type == InputTypePress) || (event->type == InputTypeRepeat)) &&
- event->key == InputKeyDown) {
+ } else if(is_press_or_repeat && event->key == InputKeyDown) {
instance->show_frame = instance->max_index > 0;
if(instance->show_frame) {
instance->selected_index = (instance->selected_index + 1) % instance->max_index;
@@ -298,63 +350,32 @@ bool subghz_frequency_analyzer_input(InputEvent* event, void* context) {
} else if(event->key == InputKeyOk) {
need_redraw = true;
bool updated = false;
- uint32_t frequency_to_save = 0;
+ uint32_t frequency_to_save;
with_view_model(
instance->view,
SubGhzFrequencyAnalyzerModel * model,
{
frequency_to_save = model->frequency_to_save;
+ uint32_t prev_freq_to_save = model->frequency_to_save;
+ uint32_t frequency_candidate = 0;
+
if(model->show_frame && !model->signal) {
- uint32_t prev_freq_to_save = model->frequency_to_save;
- uint32_t frequency_candidate = model->history_frequency[model->selected_index];
- if(frequency_candidate == 0 ||
- // !furi_hal_subghz_is_frequency_valid(frequency_candidate) ||
- !subghz_txrx_radio_device_is_frequency_valid(
- instance->txrx, frequency_candidate) ||
- prev_freq_to_save == frequency_candidate) {
- frequency_candidate = 0;
- } else {
- frequency_candidate = subghz_frequency_find_correct(frequency_candidate);
- }
- if(frequency_candidate > 0 &&
- frequency_candidate != model->frequency_to_save) {
- model->frequency_to_save = frequency_candidate;
- updated = true;
- }
- } else if(model->show_frame && model->signal) {
- uint32_t prev_freq_to_save = model->frequency_to_save;
- uint32_t frequency_candidate = subghz_frequency_find_correct(model->frequency);
- if(frequency_candidate == 0 ||
- // !furi_hal_subghz_is_frequency_valid(frequency_candidate) ||
- !subghz_txrx_radio_device_is_frequency_valid(
- instance->txrx, frequency_candidate) ||
- prev_freq_to_save == frequency_candidate) {
- frequency_candidate = 0;
- } else {
- frequency_candidate = subghz_frequency_find_correct(frequency_candidate);
- }
- if(frequency_candidate > 0 &&
- frequency_candidate != model->frequency_to_save) {
- model->frequency_to_save = frequency_candidate;
- updated = true;
- }
- } else if(!model->show_frame && model->signal) {
- uint32_t prev_freq_to_save = model->frequency_to_save;
- uint32_t frequency_candidate = subghz_frequency_find_correct(model->frequency);
- if(frequency_candidate == 0 ||
- // !furi_hal_subghz_is_frequency_valid(frequency_candidate) ||
- !subghz_txrx_radio_device_is_frequency_valid(
- instance->txrx, frequency_candidate) ||
- prev_freq_to_save == frequency_candidate) {
- frequency_candidate = 0;
- } else {
- frequency_candidate = subghz_frequency_find_correct(frequency_candidate);
- }
- if(frequency_candidate > 0 &&
- frequency_candidate != model->frequency_to_save) {
- model->frequency_to_save = frequency_candidate;
- updated = true;
- }
+ frequency_candidate = model->history_frequency[model->selected_index];
+ } else if(
+ (model->show_frame && model->signal) ||
+ (!model->show_frame && model->signal)) {
+ frequency_candidate = subghz_frequency_find_correct(model->frequency);
+ }
+
+ frequency_candidate = frequency_candidate == 0 ||
+ !subghz_txrx_radio_device_is_frequency_valid(
+ instance->txrx, frequency_candidate) ||
+ prev_freq_to_save == frequency_candidate ?
+ 0 :
+ subghz_frequency_find_correct(frequency_candidate);
+ if(frequency_candidate > 0 && frequency_candidate != model->frequency_to_save) {
+ model->frequency_to_save = frequency_candidate;
+ updated = true;
}
},
true);
@@ -363,7 +384,7 @@ bool subghz_frequency_analyzer_input(InputEvent* event, void* context) {
instance->callback(SubGhzCustomEventViewFreqAnalOkShort, instance->context);
}
- // First device receive short, then when user release button we get long
+ // First the device receives short, then when user release button we get long
if(event->type == InputTypeLong && frequency_to_save > 0) {
// Stop worker
if(subghz_frequency_analyzer_worker_is_running(instance->worker)) {
@@ -375,7 +396,6 @@ bool subghz_frequency_analyzer_input(InputEvent* event, void* context) {
}
if(need_redraw) {
- SubGhzFrequencyAnalyzer* instance = context;
with_view_model(
instance->view,
SubGhzFrequencyAnalyzerModel * model,
@@ -412,7 +432,7 @@ void subghz_frequency_analyzer_pair_callback(
uint32_t frequency,
float rssi,
bool signal) {
- SubGhzFrequencyAnalyzer* instance = context;
+ SubGhzFrequencyAnalyzer* instance = (SubGhzFrequencyAnalyzer*)context;
if(float_is_equal(rssi, 0.f) && instance->locked) {
if(instance->callback) {
instance->callback(SubGhzCustomEventSceneAnalyzerUnlock, instance->context);
@@ -477,7 +497,7 @@ void subghz_frequency_analyzer_pair_callback(
},
false);
instance->max_index = max_index;
- } else if((rssi != 0.f) && (!instance->locked)) {
+ } else if(!float_is_equal(rssi, 0.f) && !instance->locked) {
// There is some signal
FURI_LOG_I(TAG, "rssi = %.2f, frequency = %ld Hz", (double)rssi, frequency);
frequency = round_int(frequency, 3); // Round 299999990Hz to 300000000Hz
@@ -490,11 +510,11 @@ void subghz_frequency_analyzer_pair_callback(
}
// Update values
- if(rssi >= instance->rssi_last && (frequency != 0)) {
+ if(rssi >= instance->rssi_last && frequency != 0) {
instance->rssi_last = rssi;
}
- instance->locked = (rssi != 0.f);
+ instance->locked = !float_is_equal(rssi, 0.f);
with_view_model(
instance->view,
SubGhzFrequencyAnalyzerModel * model,
@@ -514,7 +534,7 @@ void subghz_frequency_analyzer_pair_callback(
void subghz_frequency_analyzer_enter(void* context) {
furi_assert(context);
- SubGhzFrequencyAnalyzer* instance = context;
+ SubGhzFrequencyAnalyzer* instance = (SubGhzFrequencyAnalyzer*)context;
//Start worker
instance->worker = subghz_frequency_analyzer_worker_alloc(instance->context);
@@ -560,7 +580,7 @@ void subghz_frequency_analyzer_enter(void* context) {
void subghz_frequency_analyzer_exit(void* context) {
furi_assert(context);
- SubGhzFrequencyAnalyzer* instance = context;
+ SubGhzFrequencyAnalyzer* instance = (SubGhzFrequencyAnalyzer*)context;
// Stop worker
if(subghz_frequency_analyzer_worker_is_running(instance->worker)) {
@@ -574,7 +594,7 @@ void subghz_frequency_analyzer_exit(void* context) {
SubGhzFrequencyAnalyzer* subghz_frequency_analyzer_alloc(SubGhzTxRx* txrx) {
SubGhzFrequencyAnalyzer* instance = malloc(sizeof(SubGhzFrequencyAnalyzer));
- instance->feedback_level = 2;
+ instance->feedback_level = SubGHzFrequencyAnalyzerFeedbackLevelMute;
// View allocation and configuration
instance->view = view_alloc();
diff --git a/applications/main/subghz_remote b/applications/main/subghz_remote
index 6ba386c09e..b631376798 160000
--- a/applications/main/subghz_remote
+++ b/applications/main/subghz_remote
@@ -1 +1 @@
-Subproject commit 6ba386c09e5650f3ea814faee021829f3332ee35
+Subproject commit b63137679870602ac5ba02cba4eb0f4b0efce6fa
diff --git a/applications/main/u2f/icon.png b/applications/main/u2f/icon.png
index fcd87a2ef5..6f46b0e783 100644
Binary files a/applications/main/u2f/icon.png and b/applications/main/u2f/icon.png differ
diff --git a/applications/main/u2f/u2f.c b/applications/main/u2f/u2f.c
index 6a37769a8b..0143eb245f 100644
--- a/applications/main/u2f/u2f.c
+++ b/applications/main/u2f/u2f.c
@@ -4,7 +4,6 @@
#include
#include
#include
-#include // for lfs_tobe32
#include
#include
@@ -319,6 +318,10 @@ static uint16_t u2f_register(U2fData* U2F, uint8_t* buf) {
return sizeof(U2fRegisterResp) + cert_len + signature_len + 2;
}
+static inline uint32_t u2f_to_big_endian(uint32_t a) {
+ return __builtin_bswap32(a);
+}
+
static uint16_t u2f_authenticate(U2fData* U2F, uint8_t* buf) {
U2fAuthReq* req = (U2fAuthReq*)buf;
U2fAuthResp* resp = (U2fAuthResp*)buf;
@@ -348,7 +351,7 @@ static uint16_t u2f_authenticate(U2fData* U2F, uint8_t* buf) {
U2F->user_present = false;
// The 4 byte counter is represented in big endian. Increment it before use
- be_u2f_counter = lfs_tobe32(U2F->counter + 1);
+ be_u2f_counter = u2f_to_big_endian(U2F->counter + 1);
// Generate hash
{
diff --git a/applications/main/u2f/u2f_app.c b/applications/main/u2f/u2f_app.c
index 68966390a9..58af40e7bb 100644
--- a/applications/main/u2f/u2f_app.c
+++ b/applications/main/u2f/u2f_app.c
@@ -29,7 +29,6 @@ U2fApp* u2f_app_alloc(void) {
app->view_dispatcher = view_dispatcher_alloc();
app->scene_manager = scene_manager_alloc(&u2f_scene_handlers, app);
- view_dispatcher_enable_queue(app->view_dispatcher);
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
view_dispatcher_set_tick_event_callback(
app->view_dispatcher, u2f_app_tick_event_callback, 500);
diff --git a/applications/services/bt/bt_service/bt.c b/applications/services/bt/bt_service/bt.c
index 36e7446fb7..db058ce421 100644
--- a/applications/services/bt/bt_service/bt.c
+++ b/applications/services/bt/bt_service/bt.c
@@ -61,6 +61,21 @@ static void bt_pin_code_view_port_input_callback(InputEvent* event, void* contex
}
}
+static void bt_storage_callback(const void* message, void* context) {
+ furi_assert(context);
+ Bt* bt = context;
+ const StorageEvent* event = message;
+
+ if(event->type == StorageEventTypeCardMount) {
+ const BtMessage msg = {
+ .type = BtMessageTypeReloadKeysSettings,
+ };
+
+ furi_check(
+ furi_message_queue_put(bt->message_queue, &msg, FuriWaitForever) == FuriStatusOk);
+ }
+}
+
static ViewPort* bt_pin_code_view_port_alloc(Bt* bt) {
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, bt_pin_code_view_port_draw_callback, bt);
@@ -143,8 +158,6 @@ Bt* bt_alloc(void) {
// Init default maximum packet size
bt->max_packet_size = BLE_PROFILE_SERIAL_PACKET_SIZE_MAX;
bt->current_profile = NULL;
- // Load settings
- bt_settings_load(&bt->bt_settings);
// Keys storage
bt->keys_storage = bt_keys_storage_alloc(BT_KEYS_STORAGE_PATH);
// Alloc queue
@@ -396,6 +409,8 @@ void bt_close_rpc_connection(Bt* bt) {
static void bt_change_profile(Bt* bt, BtMessage* message) {
if(furi_hal_bt_is_gatt_gap_supported()) {
+ bt_settings_load(&bt->bt_settings);
+
bt_close_rpc_connection(bt);
bt_keys_storage_load(bt->keys_storage);
@@ -439,6 +454,87 @@ static void bt_close_connection(Bt* bt, BtMessage* message) {
if(message->lock) api_lock_unlock(message->lock);
}
+static void bt_apply_settings(Bt* bt) {
+ if(bt->bt_settings.enabled) {
+ furi_hal_bt_start_advertising();
+ } else {
+ furi_hal_bt_stop_advertising();
+ }
+}
+
+static void bt_load_keys(Bt* bt) {
+ if(!furi_hal_bt_is_gatt_gap_supported()) {
+ bt_show_warning(bt, "Unsupported radio stack");
+ bt->status = BtStatusUnavailable;
+ return;
+
+ } else if(bt_keys_storage_is_changed(bt->keys_storage)) {
+ FURI_LOG_I(TAG, "Loading new keys");
+
+ bt_close_rpc_connection(bt);
+ bt_keys_storage_load(bt->keys_storage);
+
+ bt->current_profile = NULL;
+
+ } else {
+ FURI_LOG_I(TAG, "Keys unchanged");
+ }
+}
+
+static void bt_start_application(Bt* bt) {
+ if(!bt->current_profile) {
+ bt->current_profile =
+ furi_hal_bt_change_app(ble_profile_serial, NULL, bt_on_gap_event_callback, bt);
+
+ if(!bt->current_profile) {
+ FURI_LOG_E(TAG, "BLE App start failed");
+ bt->status = BtStatusUnavailable;
+ }
+ }
+}
+
+static void bt_load_settings(Bt* bt) {
+ bt_settings_load(&bt->bt_settings);
+ bt_apply_settings(bt);
+}
+
+static void bt_handle_get_settings(Bt* bt, BtMessage* message) {
+ furi_assert(message->lock);
+ *message->data.settings = bt->bt_settings;
+ api_lock_unlock(message->lock);
+}
+
+static void bt_handle_set_settings(Bt* bt, BtMessage* message) {
+ furi_assert(message->lock);
+ bt->bt_settings = *message->data.csettings;
+
+ bt_apply_settings(bt);
+ bt_settings_save(&bt->bt_settings);
+
+ api_lock_unlock(message->lock);
+}
+
+static void bt_handle_reload_keys_settings(Bt* bt) {
+ bt_load_keys(bt);
+ bt_start_application(bt);
+ bt_load_settings(bt);
+}
+
+static void bt_init_keys_settings(Bt* bt) {
+ Storage* storage = furi_record_open(RECORD_STORAGE);
+ furi_pubsub_subscribe(storage_get_pubsub(storage), bt_storage_callback, bt);
+
+ if(storage_sd_status(storage) != FSE_OK) {
+ FURI_LOG_D(TAG, "SD Card not ready, skipping settings");
+
+ // Just start the BLE serial application without loading the keys or settings
+ bt_start_application(bt);
+ return;
+ }
+
+ bt_handle_reload_keys_settings(bt);
+}
+
bool bt_remote_rssi(Bt* bt, uint8_t* rssi) {
furi_assert(bt);
@@ -465,35 +561,18 @@ int32_t bt_srv(void* p) {
return 0;
}
- // Load keys
- if(!bt_keys_storage_load(bt->keys_storage)) {
- FURI_LOG_W(TAG, "Failed to load bonding keys");
- }
-
- // Start radio stack
- if(!furi_hal_bt_start_radio_stack()) {
- FURI_LOG_E(TAG, "Radio stack start failed");
- }
+ if(furi_hal_bt_start_radio_stack()) {
+ bt_init_keys_settings(bt);
+ furi_hal_bt_set_key_storage_change_callback(bt_on_key_storage_change_callback, bt);
- if(furi_hal_bt_is_gatt_gap_supported()) {
- bt->current_profile =
- furi_hal_bt_start_app(ble_profile_serial, NULL, bt_on_gap_event_callback, bt);
- if(!bt->current_profile) {
- FURI_LOG_E(TAG, "BLE App start failed");
- } else {
- if(bt->bt_settings.enabled) {
- furi_hal_bt_start_advertising();
- }
- furi_hal_bt_set_key_storage_change_callback(bt_on_key_storage_change_callback, bt);
- }
} else {
- bt_show_warning(bt, "Unsupported radio stack");
- bt->status = BtStatusUnavailable;
+ FURI_LOG_E(TAG, "Radio stack start failed");
}
furi_record_create(RECORD_BT, bt);
BtMessage message;
+
while(1) {
furi_check(
furi_message_queue_get(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk);
@@ -523,7 +602,14 @@ int32_t bt_srv(void* p) {
bt_close_connection(bt, &message);
} else if(message.type == BtMessageTypeForgetBondedDevices) {
bt_keys_storage_delete(bt->keys_storage);
+ } else if(message.type == BtMessageTypeGetSettings) {
+ bt_handle_get_settings(bt, &message);
+ } else if(message.type == BtMessageTypeSetSettings) {
+ bt_handle_set_settings(bt, &message);
+ } else if(message.type == BtMessageTypeReloadKeysSettings) {
+ bt_handle_reload_keys_settings(bt);
}
}
+
return 0;
}
diff --git a/applications/services/bt/bt_service/bt_api.c b/applications/services/bt/bt_service/bt_api.c
index f0e792d42e..39b9a099da 100644
--- a/applications/services/bt/bt_service/bt_api.c
+++ b/applications/services/bt/bt_service/bt_api.c
@@ -77,3 +77,39 @@ void bt_keys_storage_set_default_path(Bt* bt) {
bt_keys_storage_set_file_path(bt->keys_storage, BT_KEYS_STORAGE_PATH);
}
+
+/*
+ * Private API for the Settings app
+ */
+
+void bt_get_settings(Bt* bt, BtSettings* settings) {
+ furi_assert(bt);
+ furi_assert(settings);
+
+ BtMessage message = {
+ .lock = api_lock_alloc_locked(),
+ .type = BtMessageTypeGetSettings,
+ .data.settings = settings,
+ };
+
+ furi_check(
+ furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk);
+
+ api_lock_wait_unlock_and_free(message.lock);
+}
+
+void bt_set_settings(Bt* bt, const BtSettings* settings) {
+ furi_assert(bt);
+ furi_assert(settings);
+
+ BtMessage message = {
+ .lock = api_lock_alloc_locked(),
+ .type = BtMessageTypeSetSettings,
+ .data.csettings = settings,
+ };
+
+ furi_check(
+ furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk);
+
+ api_lock_wait_unlock_and_free(message.lock);
+}
diff --git a/applications/services/bt/bt_service/bt_i.h b/applications/services/bt/bt_service/bt_i.h
index eb94283881..58a60e2751 100644
--- a/applications/services/bt/bt_service/bt_i.h
+++ b/applications/services/bt/bt_service/bt_i.h
@@ -32,6 +32,9 @@ typedef enum {
BtMessageTypeSetProfile,
BtMessageTypeDisconnect,
BtMessageTypeForgetBondedDevices,
+ BtMessageTypeGetSettings,
+ BtMessageTypeSetSettings,
+ BtMessageTypeReloadKeysSettings,
} BtMessageType;
typedef struct {
@@ -49,6 +52,8 @@ typedef union {
} profile;
FuriHalBleProfileParams profile_params;
BtKeyStorageUpdateData key_storage_data;
+ BtSettings* settings;
+ const BtSettings* csettings;
} BtMessageData;
typedef struct {
diff --git a/applications/services/bt/bt_service/bt_keys_storage.c b/applications/services/bt/bt_service/bt_keys_storage.c
index 6392c2d677..57742e8e26 100644
--- a/applications/services/bt/bt_service/bt_keys_storage.c
+++ b/applications/services/bt/bt_service/bt_keys_storage.c
@@ -13,6 +13,7 @@
struct BtKeysStorage {
uint8_t* nvm_sram_buff;
uint16_t nvm_sram_buff_size;
+ uint16_t current_size;
FuriString* file_path;
};
@@ -66,44 +67,114 @@ void bt_keys_storage_set_ram_params(BtKeysStorage* instance, uint8_t* buff, uint
instance->nvm_sram_buff_size = size;
}
-bool bt_keys_storage_load(BtKeysStorage* instance) {
+static bool bt_keys_storage_file_exists(const char* file_path) {
+ Storage* storage = furi_record_open(RECORD_STORAGE);
+ FileInfo file_info;
+ const bool ret = storage_common_stat(storage, file_path, &file_info) == FSE_OK &&
+ file_info.size != 0;
+ furi_record_close(RECORD_STORAGE);
+ return ret;
+}
+
+static bool bt_keys_storage_validate_file(const char* file_path, size_t* payload_size) {
+ uint8_t magic, version;
+ size_t size;
+
+ if(!saved_struct_get_metadata(file_path, &magic, &version, &size)) {
+ FURI_LOG_E(TAG, "Failed to get metadata");
+ return false;
+
+ } else if(magic != BT_KEYS_STORAGE_MAGIC || version != BT_KEYS_STORAGE_VERSION) {
+ FURI_LOG_E(TAG, "File version mismatch");
+ return false;
+ }
+
+ *payload_size = size;
+ return true;
+}
+
+bool bt_keys_storage_is_changed(BtKeysStorage* instance) {
furi_assert(instance);
- bool loaded = false;
+ bool is_changed = false;
+ uint8_t* data_buffer = NULL;
+
do {
- // Get payload size
- uint8_t magic = 0, version = 0;
- size_t payload_size = 0;
- if(!saved_struct_get_metadata(
- furi_string_get_cstr(instance->file_path), &magic, &version, &payload_size)) {
- FURI_LOG_E(TAG, "Failed to read payload size");
+ const char* file_path = furi_string_get_cstr(instance->file_path);
+ size_t payload_size;
+
+ if(!bt_keys_storage_file_exists(file_path)) {
+ FURI_LOG_W(TAG, "Missing or empty file");
+ break;
+
+ } else if(!bt_keys_storage_validate_file(file_path, &payload_size)) {
+ FURI_LOG_E(TAG, "Invalid or corrupted file");
break;
}
- if(magic != BT_KEYS_STORAGE_MAGIC || version != BT_KEYS_STORAGE_VERSION) {
- FURI_LOG_E(TAG, "Saved data version is mismatched");
+ data_buffer = malloc(payload_size);
+
+ const bool data_loaded = saved_struct_load(
+ file_path, data_buffer, payload_size, BT_KEYS_STORAGE_MAGIC, BT_KEYS_STORAGE_VERSION);
+
+ if(!data_loaded) {
+ FURI_LOG_E(TAG, "Failed to load file");
break;
+
+ } else if(payload_size == instance->current_size) {
+ furi_hal_bt_nvm_sram_sem_acquire();
+ is_changed = memcmp(data_buffer, instance->nvm_sram_buff, payload_size);
+ furi_hal_bt_nvm_sram_sem_release();
+
+ } else {
+ FURI_LOG_D(TAG, "Size mismatch");
+ is_changed = true;
}
+ } while(false);
- if(payload_size > instance->nvm_sram_buff_size) {
- FURI_LOG_E(TAG, "Saved data doesn't fit ram buffer");
+ if(data_buffer) {
+ free(data_buffer);
+ }
+
+ return is_changed;
+}
+
+bool bt_keys_storage_load(BtKeysStorage* instance) {
+ furi_assert(instance);
+
+ bool loaded = false;
+
+ do {
+ const char* file_path = furi_string_get_cstr(instance->file_path);
+
+ // Get payload size
+ size_t payload_size;
+ if(!bt_keys_storage_validate_file(file_path, &payload_size)) {
+ FURI_LOG_E(TAG, "Invalid or corrupted file");
+ break;
+
+ } else if(payload_size > instance->nvm_sram_buff_size) {
+ FURI_LOG_E(TAG, "NVM RAM buffer overflow");
break;
}
// Load saved data to ram
furi_hal_bt_nvm_sram_sem_acquire();
- bool data_loaded = saved_struct_load(
- furi_string_get_cstr(instance->file_path),
+ const bool data_loaded = saved_struct_load(
+ file_path,
instance->nvm_sram_buff,
payload_size,
BT_KEYS_STORAGE_MAGIC,
BT_KEYS_STORAGE_VERSION);
furi_hal_bt_nvm_sram_sem_release();
+
if(!data_loaded) {
- FURI_LOG_E(TAG, "Failed to load struct");
+ FURI_LOG_E(TAG, "Failed to load file");
break;
}
+ instance->current_size = payload_size;
+
loaded = true;
} while(false);
@@ -130,6 +201,8 @@ bool bt_keys_storage_update(BtKeysStorage* instance, uint8_t* start_addr, uint32
break;
}
+ instance->current_size = new_size;
+
furi_hal_bt_nvm_sram_sem_acquire();
bool data_updated = saved_struct_save(
furi_string_get_cstr(instance->file_path),
@@ -138,10 +211,12 @@ bool bt_keys_storage_update(BtKeysStorage* instance, uint8_t* start_addr, uint32
BT_KEYS_STORAGE_MAGIC,
BT_KEYS_STORAGE_VERSION);
furi_hal_bt_nvm_sram_sem_release();
+
if(!data_updated) {
FURI_LOG_E(TAG, "Failed to update key storage");
break;
}
+
updated = true;
} while(false);
diff --git a/applications/services/bt/bt_service/bt_keys_storage.h b/applications/services/bt/bt_service/bt_keys_storage.h
index 587dd570dd..b7a127035d 100644
--- a/applications/services/bt/bt_service/bt_keys_storage.h
+++ b/applications/services/bt/bt_service/bt_keys_storage.h
@@ -17,6 +17,8 @@ void bt_keys_storage_set_file_path(BtKeysStorage* instance, const char* path);
void bt_keys_storage_set_ram_params(BtKeysStorage* instance, uint8_t* buff, uint16_t size);
+bool bt_keys_storage_is_changed(BtKeysStorage* instance);
+
bool bt_keys_storage_load(BtKeysStorage* instance);
bool bt_keys_storage_update(BtKeysStorage* instance, uint8_t* start_addr, uint32_t size);
diff --git a/applications/services/bt/bt_service/bt_settings_api_i.h b/applications/services/bt/bt_service/bt_settings_api_i.h
new file mode 100644
index 0000000000..4412958931
--- /dev/null
+++ b/applications/services/bt/bt_service/bt_settings_api_i.h
@@ -0,0 +1,8 @@
+#pragma once
+
+#include "bt.h"
+#include "../bt_settings.h"
+
+void bt_get_settings(Bt* bt, BtSettings* settings);
+
+void bt_set_settings(Bt* bt, const BtSettings* settings);
diff --git a/applications/services/bt/bt_settings.c b/applications/services/bt/bt_settings.c
index 3602cf4977..abdc97f7e7 100644
--- a/applications/services/bt/bt_settings.c
+++ b/applications/services/bt/bt_settings.c
@@ -1,23 +1,36 @@
#include "bt_settings.h"
+#include "bt_settings_filename.h"
#include
-#include
#include
+#include
+
+#define TAG "BtSettings"
#define BT_SETTINGS_PATH INT_PATH(BT_SETTINGS_FILE_NAME)
#define BT_SETTINGS_VERSION (0)
#define BT_SETTINGS_MAGIC (0x19)
-bool bt_settings_load(BtSettings* bt_settings) {
+void bt_settings_load(BtSettings* bt_settings) {
furi_assert(bt_settings);
- return saved_struct_load(
+ const bool success = saved_struct_load(
BT_SETTINGS_PATH, bt_settings, sizeof(BtSettings), BT_SETTINGS_MAGIC, BT_SETTINGS_VERSION);
+
+ if(!success) {
+ FURI_LOG_W(TAG, "Failed to load settings, using defaults");
+ memset(bt_settings, 0, sizeof(BtSettings));
+ bt_settings_save(bt_settings);
+ }
}
-bool bt_settings_save(const BtSettings* bt_settings) {
+void bt_settings_save(const BtSettings* bt_settings) {
furi_assert(bt_settings);
- return saved_struct_save(
+ const bool success = saved_struct_save(
BT_SETTINGS_PATH, bt_settings, sizeof(BtSettings), BT_SETTINGS_MAGIC, BT_SETTINGS_VERSION);
+
+ if(!success) {
+ FURI_LOG_E(TAG, "Failed to save settings");
+ }
}
diff --git a/applications/services/bt/bt_settings.h b/applications/services/bt/bt_settings.h
index a4e76a12cc..c63220abb2 100644
--- a/applications/services/bt/bt_settings.h
+++ b/applications/services/bt/bt_settings.h
@@ -1,8 +1,5 @@
#pragma once
-#include "bt_settings_filename.h"
-
-#include
#include
#ifdef __cplusplus
@@ -13,9 +10,9 @@ typedef struct {
bool enabled;
} BtSettings;
-bool bt_settings_load(BtSettings* bt_settings);
+void bt_settings_load(BtSettings* bt_settings);
-bool bt_settings_save(const BtSettings* bt_settings);
+void bt_settings_save(const BtSettings* bt_settings);
#ifdef __cplusplus
}
diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c
index 1604f4389d..8fe5643af6 100644
--- a/applications/services/cli/cli_commands.c
+++ b/applications/services/cli/cli_commands.c
@@ -270,7 +270,7 @@ void cli_command_sysctl_heap_track(Cli* cli, FuriString* args, void* context) {
} else if(!furi_string_cmp(args, "main")) {
furi_hal_rtc_set_heap_track_mode(FuriHalRtcHeapTrackModeMain);
printf("Heap tracking enabled for application main thread");
-#if FURI_DEBUG
+#ifdef FURI_DEBUG
} else if(!furi_string_cmp(args, "tree")) {
furi_hal_rtc_set_heap_track_mode(FuriHalRtcHeapTrackModeTree);
printf("Heap tracking enabled for application main and child threads");
@@ -289,7 +289,7 @@ void cli_command_sysctl_print_usage(void) {
printf("Cmd list:\r\n");
printf("\tdebug <0|1>\t - Enable or disable system debug\r\n");
-#if FURI_DEBUG
+#ifdef FURI_DEBUG
printf("\theap_track \t - Set heap allocation tracking mode\r\n");
#else
printf("\theap_track \t - Set heap allocation tracking mode\r\n");
diff --git a/applications/services/desktop/animations/animation_manager.c b/applications/services/desktop/animations/animation_manager.c
index 8e04e7894e..858efb9fe7 100644
--- a/applications/services/desktop/animations/animation_manager.c
+++ b/applications/services/desktop/animations/animation_manager.c
@@ -97,11 +97,14 @@ void animation_manager_set_interact_callback(
void animation_manager_set_dummy_mode_state(AnimationManager* animation_manager, bool enabled) {
furi_assert(animation_manager);
- animation_manager->dummy_mode = enabled;
- animation_manager_start_new_idle(animation_manager);
+ // Prevent change of animations if mode is the same
+ if(animation_manager->dummy_mode != enabled) {
+ animation_manager->dummy_mode = enabled;
+ animation_manager_start_new_idle(animation_manager);
+ }
}
-static void animation_manager_check_blocking_callback(const void* message, void* context) {
+static void animation_manager_storage_callback(const void* message, void* context) {
const StorageEvent* storage_event = message;
switch(storage_event->type) {
@@ -120,6 +123,22 @@ static void animation_manager_check_blocking_callback(const void* message, void*
}
}
+static void animation_manager_dolphin_callback(const void* message, void* context) {
+ const DolphinPubsubEvent* dolphin_event = message;
+
+ switch(*dolphin_event) {
+ case DolphinPubsubEventUpdate:
+ furi_assert(context);
+ AnimationManager* animation_manager = context;
+ if(animation_manager->check_blocking_callback) {
+ animation_manager->check_blocking_callback(animation_manager->context);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
static void animation_manager_timer_callback(void* context) {
furi_assert(context);
AnimationManager* animation_manager = context;
@@ -297,12 +316,12 @@ AnimationManager* animation_manager_alloc(void) {
Storage* storage = furi_record_open(RECORD_STORAGE);
animation_manager->pubsub_subscription_storage = furi_pubsub_subscribe(
- storage_get_pubsub(storage), animation_manager_check_blocking_callback, animation_manager);
+ storage_get_pubsub(storage), animation_manager_storage_callback, animation_manager);
furi_record_close(RECORD_STORAGE);
Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN);
animation_manager->pubsub_subscription_dolphin = furi_pubsub_subscribe(
- dolphin_get_pubsub(dolphin), animation_manager_check_blocking_callback, animation_manager);
+ dolphin_get_pubsub(dolphin), animation_manager_dolphin_callback, animation_manager);
furi_record_close(RECORD_DOLPHIN);
animation_manager->blocking_shown_sd_ok = true;
diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c
index 53e2d29c84..e57e1eb00f 100644
--- a/applications/services/desktop/desktop.c
+++ b/applications/services/desktop/desktop.c
@@ -1,31 +1,24 @@
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
+#include "desktop_i.h"
+
#include
#include
+
+#include
+
#include
+#include
-#include "animations/animation_manager.h"
-#include "desktop/scenes/desktop_scene.h"
-#include "desktop/scenes/desktop_scene_i.h"
-#include "desktop/views/desktop_view_locked.h"
-#include "desktop/views/desktop_view_pin_input.h"
-#include "desktop/views/desktop_view_pin_timeout.h"
-#include "desktop_i.h"
-#include "helpers/pin.h"
-#include "helpers/slideshow_filename.h"
+#include
+
+#include "scenes/desktop_scene.h"
+#include "scenes/desktop_scene_locked.h"
#define TAG "Desktop"
static void desktop_auto_lock_arm(Desktop*);
static void desktop_auto_lock_inhibit(Desktop*);
static void desktop_start_auto_lock_timer(Desktop*);
+static void desktop_apply_settings(Desktop*);
static void desktop_loader_callback(const void* message, void* context) {
furi_assert(context);
@@ -42,6 +35,16 @@ static void desktop_loader_callback(const void* message, void* context) {
}
}
+static void desktop_storage_callback(const void* message, void* context) {
+ furi_assert(context);
+ Desktop* desktop = context;
+ const StorageEvent* event = message;
+
+ if(event->type == StorageEventTypeCardMount) {
+ view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalReloadSettings);
+ }
+}
+
static void desktop_lock_icon_draw_callback(Canvas* canvas, void* context) {
UNUSED(context);
furi_assert(canvas);
@@ -122,31 +125,39 @@ static bool desktop_custom_event_callback(void* context, uint32_t event) {
furi_assert(context);
Desktop* desktop = (Desktop*)context;
- switch(event) {
- case DesktopGlobalBeforeAppStarted:
+ if(event == DesktopGlobalBeforeAppStarted) {
if(animation_manager_is_animation_loaded(desktop->animation_manager)) {
animation_manager_unload_and_stall_animation(desktop->animation_manager);
}
+
desktop_auto_lock_inhibit(desktop);
+ desktop->app_running = true;
+
furi_semaphore_release(desktop->animation_semaphore);
- return true;
- case DesktopGlobalAfterAppFinished:
+
+ } else if(event == DesktopGlobalAfterAppFinished) {
animation_manager_load_and_continue_animation(desktop->animation_manager);
- DESKTOP_SETTINGS_LOAD(&desktop->settings);
+ desktop_auto_lock_arm(desktop);
+ desktop->app_running = false;
- desktop_clock_reconfigure(desktop);
- if(!furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock)) {
- desktop_auto_lock_arm(desktop);
- }
- return true;
- case DesktopGlobalAutoLock:
- if(!loader_is_locked(desktop->loader) && !desktop->locked) {
+ } else if(event == DesktopGlobalAutoLock) {
+ if(!desktop->app_running && !desktop->locked) {
desktop_lock(desktop);
}
- return true;
+
+ } else if(event == DesktopGlobalSaveSettings) {
+ desktop_settings_save(&desktop->settings);
+ desktop_apply_settings(desktop);
+
+ } else if(event == DesktopGlobalReloadSettings) {
+ desktop_settings_load(&desktop->settings);
+ desktop_apply_settings(desktop);
+
+ } else {
+ return scene_manager_handle_custom_event(desktop->scene_manager, event);
}
- return scene_manager_handle_custom_event(desktop->scene_manager, event);
+ return true;
}
static bool desktop_back_event_callback(void* context) {
@@ -206,84 +217,45 @@ static void desktop_clock_timer_callback(void* context) {
furi_assert(context);
Desktop* desktop = context;
- if(gui_active_view_port_count(desktop->gui, GuiLayerStatusBarLeft) < 6) {
- desktop_clock_update(desktop);
-
- view_port_enabled_set(desktop->clock_viewport, true);
- } else {
- view_port_enabled_set(desktop->clock_viewport, false);
- }
-}
-
-void desktop_lock(Desktop* desktop) {
- furi_assert(!desktop->locked);
-
- furi_hal_rtc_set_flag(FuriHalRtcFlagLock);
+ const bool clock_enabled = gui_active_view_port_count(desktop->gui, GuiLayerStatusBarLeft) < 6;
- if(desktop->settings.pin_code.length) {
- Cli* cli = furi_record_open(RECORD_CLI);
- cli_session_close(cli);
- furi_record_close(RECORD_CLI);
+ if(clock_enabled) {
+ desktop_clock_update(desktop);
}
- desktop_auto_lock_inhibit(desktop);
- scene_manager_set_scene_state(
- desktop->scene_manager, DesktopSceneLocked, SCENE_LOCKED_FIRST_ENTER);
- scene_manager_next_scene(desktop->scene_manager, DesktopSceneLocked);
-
- DesktopStatus status = {.locked = true};
- furi_pubsub_publish(desktop->status_pubsub, &status);
-
- desktop->locked = true;
+ view_port_enabled_set(desktop->clock_viewport, clock_enabled);
}
-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);
- furi_record_close(RECORD_GUI);
- desktop_view_locked_unlock(desktop->locked_view);
- scene_manager_search_and_switch_to_previous_scene(desktop->scene_manager, DesktopSceneMain);
- desktop_auto_lock_arm(desktop);
- furi_hal_rtc_reset_flag(FuriHalRtcFlagLock);
- furi_hal_rtc_set_pin_fails(0);
+static void desktop_apply_settings(Desktop* desktop) {
+ desktop->in_transition = true;
- if(desktop->settings.pin_code.length) {
- Cli* cli = furi_record_open(RECORD_CLI);
- cli_session_open(cli, &cli_vcp);
- furi_record_close(RECORD_CLI);
- }
+ desktop_clock_reconfigure(desktop);
- DesktopStatus status = {.locked = false};
- furi_pubsub_publish(desktop->status_pubsub, &status);
+ view_port_enabled_set(desktop->dummy_mode_icon_viewport, desktop->settings.dummy_mode);
+ desktop_main_set_dummy_mode_state(desktop->main_view, desktop->settings.dummy_mode);
+ animation_manager_set_dummy_mode_state(
+ desktop->animation_manager, desktop->settings.dummy_mode);
- desktop->locked = false;
-}
+ if(!desktop->app_running && !desktop->locked) {
+ desktop_auto_lock_arm(desktop);
+ }
-void desktop_set_dummy_mode_state(Desktop* desktop, bool enabled) {
- desktop->in_transition = true;
- view_port_enabled_set(desktop->dummy_mode_icon_viewport, enabled);
- desktop_main_set_dummy_mode_state(desktop->main_view, enabled);
- animation_manager_set_dummy_mode_state(desktop->animation_manager, enabled);
- desktop->settings.dummy_mode = enabled;
- DESKTOP_SETTINGS_SAVE(&desktop->settings);
desktop->in_transition = false;
}
-void desktop_set_stealth_mode_state(Desktop* desktop, bool enabled) {
- desktop->in_transition = true;
- if(enabled) {
- furi_hal_rtc_set_flag(FuriHalRtcFlagStealthMode);
- } else {
- furi_hal_rtc_reset_flag(FuriHalRtcFlagStealthMode);
+static void desktop_init_settings(Desktop* desktop) {
+ furi_pubsub_subscribe(storage_get_pubsub(desktop->storage), desktop_storage_callback, desktop);
+
+ if(storage_sd_status(desktop->storage) != FSE_OK) {
+ FURI_LOG_D(TAG, "SD Card not ready, skipping settings");
+ return;
}
- view_port_enabled_set(desktop->stealth_mode_icon_viewport, enabled);
- desktop->in_transition = false;
+
+ desktop_settings_load(&desktop->settings);
+ desktop_apply_settings(desktop);
}
-Desktop* desktop_alloc(void) {
+static Desktop* desktop_alloc(void) {
Desktop* desktop = malloc(sizeof(Desktop));
desktop->animation_semaphore = furi_semaphore_alloc(1, 0);
@@ -293,7 +265,6 @@ Desktop* desktop_alloc(void) {
desktop->view_dispatcher = view_dispatcher_alloc();
desktop->scene_manager = scene_manager_alloc(&desktop_scene_handlers, desktop);
- view_dispatcher_enable_queue(desktop->view_dispatcher);
view_dispatcher_attach_to_gui(
desktop->view_dispatcher, desktop->gui, ViewDispatcherTypeDesktop);
view_dispatcher_set_tick_event_callback(
@@ -392,14 +363,13 @@ Desktop* desktop_alloc(void) {
}
gui_add_view_port(desktop->gui, desktop->stealth_mode_icon_viewport, GuiLayerStatusBarLeft);
+ // Unload animations before starting an application
desktop->loader = furi_record_open(RECORD_LOADER);
+ furi_pubsub_subscribe(loader_get_pubsub(desktop->loader), desktop_loader_callback, desktop);
+ desktop->storage = furi_record_open(RECORD_STORAGE);
desktop->notification = furi_record_open(RECORD_NOTIFICATION);
- desktop->app_start_stop_subscription = furi_pubsub_subscribe(
- loader_get_pubsub(desktop->loader), desktop_loader_callback, desktop);
-
desktop->input_events_pubsub = furi_record_open(RECORD_INPUT_EVENTS);
- desktop->input_events_subscription = NULL;
desktop->auto_lock_timer =
furi_timer_alloc(desktop_auto_lock_timer_callback, FuriTimerTypeOnce, desktop);
@@ -409,19 +379,95 @@ Desktop* desktop_alloc(void) {
desktop->update_clock_timer =
furi_timer_alloc(desktop_clock_timer_callback, FuriTimerTypePeriodic, desktop);
+ desktop->app_running = loader_is_locked(desktop->loader);
+
furi_record_create(RECORD_DESKTOP, desktop);
return desktop;
}
-static bool desktop_check_file_flag(const char* flag_path) {
- Storage* storage = furi_record_open(RECORD_STORAGE);
- bool exists = storage_common_stat(storage, flag_path, NULL) == FSE_OK;
- furi_record_close(RECORD_STORAGE);
+/*
+ * Private API
+ */
- return exists;
+void desktop_lock(Desktop* desktop) {
+ furi_assert(!desktop->locked);
+
+ furi_hal_rtc_set_flag(FuriHalRtcFlagLock);
+
+ if(desktop_pin_code_is_set()) {
+ Cli* cli = furi_record_open(RECORD_CLI);
+ cli_session_close(cli);
+ furi_record_close(RECORD_CLI);
+ }
+
+ desktop_auto_lock_inhibit(desktop);
+ scene_manager_set_scene_state(
+ desktop->scene_manager, DesktopSceneLocked, DesktopSceneLockedStateFirstEnter);
+ scene_manager_next_scene(desktop->scene_manager, DesktopSceneLocked);
+
+ 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);
+ furi_record_close(RECORD_GUI);
+ desktop_view_locked_unlock(desktop->locked_view);
+ scene_manager_search_and_switch_to_previous_scene(desktop->scene_manager, DesktopSceneMain);
+ desktop_auto_lock_arm(desktop);
+ furi_hal_rtc_reset_flag(FuriHalRtcFlagLock);
+ furi_hal_rtc_set_pin_fails(0);
+
+ if(desktop_pin_code_is_set()) {
+ Cli* cli = furi_record_open(RECORD_CLI);
+ cli_session_open(cli, &cli_vcp);
+ furi_record_close(RECORD_CLI);
+ }
+
+ DesktopStatus status = {.locked = false};
+ furi_pubsub_publish(desktop->status_pubsub, &status);
+
+ desktop->locked = false;
+}
+
+void desktop_set_dummy_mode_state(Desktop* desktop, bool enabled) {
+ desktop->in_transition = true;
+
+ view_port_enabled_set(desktop->dummy_mode_icon_viewport, enabled);
+ desktop_main_set_dummy_mode_state(desktop->main_view, enabled);
+ animation_manager_set_dummy_mode_state(desktop->animation_manager, enabled);
+ desktop->settings.dummy_mode = enabled;
+
+ desktop->in_transition = false;
+
+ desktop_settings_save(&desktop->settings);
+}
+
+void desktop_set_stealth_mode_state(Desktop* desktop, bool enabled) {
+ desktop->in_transition = true;
+
+ if(enabled) {
+ furi_hal_rtc_set_flag(FuriHalRtcFlagStealthMode);
+ } else {
+ furi_hal_rtc_reset_flag(FuriHalRtcFlagStealthMode);
+ }
+
+ view_port_enabled_set(desktop->stealth_mode_icon_viewport, enabled);
+
+ desktop->in_transition = false;
+}
+
+/*
+ * Public API
+ */
+
bool desktop_api_is_locked(Desktop* instance) {
furi_assert(instance);
return furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock);
@@ -437,6 +483,30 @@ FuriPubSub* desktop_api_get_status_pubsub(Desktop* instance) {
return instance->status_pubsub;
}
+void desktop_api_reload_settings(Desktop* instance) {
+ furi_assert(instance);
+ view_dispatcher_send_custom_event(instance->view_dispatcher, DesktopGlobalReloadSettings);
+}
+
+void desktop_api_get_settings(Desktop* instance, DesktopSettings* settings) {
+ furi_assert(instance);
+ furi_assert(settings);
+
+ *settings = instance->settings;
+}
+
+void desktop_api_set_settings(Desktop* instance, const DesktopSettings* settings) {
+ furi_assert(instance);
+ furi_assert(settings);
+
+ instance->settings = *settings;
+ view_dispatcher_send_custom_event(instance->view_dispatcher, DesktopGlobalSaveSettings);
+}
+
+/*
+ * Application thread
+ */
+
int32_t desktop_srv(void* p) {
UNUSED(p);
@@ -449,31 +519,15 @@ int32_t desktop_srv(void* p) {
Desktop* desktop = desktop_alloc();
- bool loaded = DESKTOP_SETTINGS_LOAD(&desktop->settings);
- if(!loaded) {
- memset(&desktop->settings, 0, sizeof(desktop->settings));
- DESKTOP_SETTINGS_SAVE(&desktop->settings);
- }
-
- view_port_enabled_set(desktop->dummy_mode_icon_viewport, desktop->settings.dummy_mode);
-
- desktop_clock_reconfigure(desktop);
-
- desktop_main_set_dummy_mode_state(desktop->main_view, desktop->settings.dummy_mode);
- animation_manager_set_dummy_mode_state(
- desktop->animation_manager, desktop->settings.dummy_mode);
+ desktop_init_settings(desktop);
scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain);
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock)) {
desktop_lock(desktop);
- } else {
- if(!loader_is_locked(desktop->loader)) {
- desktop_auto_lock_arm(desktop);
- }
}
- if(desktop_check_file_flag(SLIDESHOW_FS_PATH)) {
+ if(storage_file_exists(desktop->storage, SLIDESHOW_FS_PATH)) {
scene_manager_next_scene(desktop->scene_manager, DesktopSceneSlideshow);
}
@@ -497,14 +551,12 @@ int32_t desktop_srv(void* p) {
}
// Special case: autostart application is already running
- if(loader_is_locked(desktop->loader) &&
- animation_manager_is_animation_loaded(desktop->animation_manager)) {
+ if(desktop->app_running && animation_manager_is_animation_loaded(desktop->animation_manager)) {
animation_manager_unload_and_stall_animation(desktop->animation_manager);
}
view_dispatcher_run(desktop->view_dispatcher);
- furi_crash("That was unexpected");
-
+ // Should never get here (a service thread will crash automatically if it returns)
return 0;
}
diff --git a/applications/services/desktop/desktop.h b/applications/services/desktop/desktop.h
index 4eab24fcc5..e83bc3ee4d 100644
--- a/applications/services/desktop/desktop.h
+++ b/applications/services/desktop/desktop.h
@@ -2,16 +2,22 @@
#include
-typedef struct Desktop Desktop;
+#include "desktop_settings.h"
#define RECORD_DESKTOP "desktop"
-bool desktop_api_is_locked(Desktop* instance);
-
-void desktop_api_unlock(Desktop* instance);
+typedef struct Desktop Desktop;
typedef struct {
bool locked;
} DesktopStatus;
+bool desktop_api_is_locked(Desktop* instance);
+
+void desktop_api_unlock(Desktop* instance);
+
FuriPubSub* desktop_api_get_status_pubsub(Desktop* instance);
+
+void desktop_api_get_settings(Desktop* instance, DesktopSettings* settings);
+
+void desktop_api_set_settings(Desktop* instance, const DesktopSettings* settings);
diff --git a/applications/services/desktop/desktop_i.h b/applications/services/desktop/desktop_i.h
index 4bcbb6585a..1dc7c7d219 100644
--- a/applications/services/desktop/desktop_i.h
+++ b/applications/services/desktop/desktop_i.h
@@ -1,6 +1,8 @@
#pragma once
#include "desktop.h"
+#include "desktop_settings.h"
+
#include "animations/animation_manager.h"
#include "views/desktop_view_pin_timeout.h"
#include "views/desktop_view_pin_input.h"
@@ -9,9 +11,7 @@
#include "views/desktop_view_lock_menu.h"
#include "views/desktop_view_debug.h"
#include "views/desktop_view_slideshow.h"
-#include
-#include
#include
#include
#include
@@ -42,9 +42,8 @@ typedef struct {
} DesktopClock;
struct Desktop {
- // Scene
FuriThread* scene_thread;
- // GUI
+
Gui* gui;
ViewDispatcher* view_dispatcher;
SceneManager* scene_manager;
@@ -56,42 +55,38 @@ struct Desktop {
DesktopMainView* main_view;
DesktopViewPinTimeout* pin_timeout_view;
DesktopSlideshowView* slideshow_view;
+ DesktopViewPinInput* pin_input_view;
ViewStack* main_view_stack;
ViewStack* locked_view_stack;
- DesktopSettings settings;
- DesktopViewPinInput* pin_input_view;
-
ViewPort* lock_icon_viewport;
ViewPort* dummy_mode_icon_viewport;
ViewPort* clock_viewport;
ViewPort* stealth_mode_icon_viewport;
- AnimationManager* animation_manager;
-
Loader* loader;
+ Storage* storage;
NotificationApp* notification;
- FuriPubSubSubscription* app_start_stop_subscription;
+ FuriPubSub* status_pubsub;
FuriPubSub* input_events_pubsub;
FuriPubSubSubscription* input_events_subscription;
+
FuriTimer* auto_lock_timer;
FuriTimer* update_clock_timer;
- FuriPubSub* status_pubsub;
+ AnimationManager* animation_manager;
+ FuriSemaphore* animation_semaphore;
DesktopClock clock;
+ DesktopSettings settings;
- bool in_transition : 1;
- bool locked : 1;
-
- FuriSemaphore* animation_semaphore;
+ bool in_transition;
+ bool app_running;
+ bool locked;
};
-Desktop* desktop_alloc(void);
-
-void desktop_free(Desktop* desktop);
void desktop_lock(Desktop* desktop);
void desktop_unlock(Desktop* desktop);
void desktop_set_dummy_mode_state(Desktop* desktop, bool enabled);
diff --git a/applications/services/desktop/desktop_settings.c b/applications/services/desktop/desktop_settings.c
new file mode 100644
index 0000000000..828ec5f0d6
--- /dev/null
+++ b/applications/services/desktop/desktop_settings.c
@@ -0,0 +1,79 @@
+#include "desktop_settings.h"
+#include "desktop_settings_filename.h"
+
+#include
+#include
+
+#define TAG "DesktopSettings"
+
+#define DESKTOP_SETTINGS_VER_13 (13)
+#define DESKTOP_SETTINGS_VER (14)
+
+#define DESKTOP_SETTINGS_PATH INT_PATH(DESKTOP_SETTINGS_FILE_NAME)
+#define DESKTOP_SETTINGS_MAGIC (0x17)
+
+typedef struct {
+ uint8_t reserved[11];
+ DesktopSettings settings;
+} DesktopSettingsV13;
+
+// Actual size of DesktopSettings v13
+//static_assert(sizeof(DesktopSettingsV13) == 1234);
+
+void desktop_settings_load(DesktopSettings* settings) {
+ furi_assert(settings);
+
+ bool success = false;
+
+ do {
+ uint8_t version;
+ if(!saved_struct_get_metadata(DESKTOP_SETTINGS_PATH, NULL, &version, NULL)) break;
+
+ if(version == DESKTOP_SETTINGS_VER) {
+ success = saved_struct_load(
+ DESKTOP_SETTINGS_PATH,
+ settings,
+ sizeof(DesktopSettings),
+ DESKTOP_SETTINGS_MAGIC,
+ DESKTOP_SETTINGS_VER);
+
+ } else if(version == DESKTOP_SETTINGS_VER_13) {
+ DesktopSettingsV13* settings_v13 = malloc(sizeof(DesktopSettingsV13));
+
+ success = saved_struct_load(
+ DESKTOP_SETTINGS_PATH,
+ settings_v13,
+ sizeof(DesktopSettingsV13),
+ DESKTOP_SETTINGS_MAGIC,
+ DESKTOP_SETTINGS_VER_13);
+
+ if(success) {
+ *settings = settings_v13->settings;
+ }
+
+ free(settings_v13);
+ }
+
+ } while(false);
+
+ if(!success) {
+ FURI_LOG_W(TAG, "Failed to load file, using defaults");
+ memset(settings, 0, sizeof(DesktopSettings));
+ desktop_settings_save(settings);
+ }
+}
+
+void desktop_settings_save(const DesktopSettings* settings) {
+ furi_assert(settings);
+
+ const bool success = saved_struct_save(
+ DESKTOP_SETTINGS_PATH,
+ settings,
+ sizeof(DesktopSettings),
+ DESKTOP_SETTINGS_MAGIC,
+ DESKTOP_SETTINGS_VER);
+
+ if(!success) {
+ FURI_LOG_E(TAG, "Failed to save file");
+ }
+}
diff --git a/applications/services/desktop/desktop_settings.h b/applications/services/desktop/desktop_settings.h
index 4c848117a2..ba5a78840e 100644
--- a/applications/services/desktop/desktop_settings.h
+++ b/applications/services/desktop/desktop_settings.h
@@ -1,40 +1,6 @@
#pragma once
-#include "desktop_settings_filename.h"
-
-#include
#include
-#include
-#include
-#include
-
-#define DESKTOP_SETTINGS_VER (13)
-
-#define DESKTOP_SETTINGS_PATH INT_PATH(DESKTOP_SETTINGS_FILE_NAME)
-#define DESKTOP_SETTINGS_MAGIC (0x17)
-#define PIN_MAX_LENGTH 12
-
-#define DESKTOP_SETTINGS_RUN_PIN_SETUP_ARG "run_pin_setup"
-
-#define DESKTOP_SETTINGS_SAVE(x) \
- saved_struct_save( \
- DESKTOP_SETTINGS_PATH, \
- (x), \
- sizeof(DesktopSettings), \
- DESKTOP_SETTINGS_MAGIC, \
- DESKTOP_SETTINGS_VER)
-
-#define DESKTOP_SETTINGS_LOAD(x) \
- saved_struct_load( \
- DESKTOP_SETTINGS_PATH, \
- (x), \
- sizeof(DesktopSettings), \
- DESKTOP_SETTINGS_MAGIC, \
- DESKTOP_SETTINGS_VER)
-
-#define MAX_PIN_SIZE 10
-#define MIN_PIN_SIZE 4
-#define MAX_APP_LENGTH 128
#define DISPLAY_BATTERY_BAR 0
#define DISPLAY_BATTERY_PERCENT 1
@@ -44,7 +10,7 @@
#define DISPLAY_BATTERY_BAR_PERCENT 5
typedef enum {
- FavoriteAppLeftShort = 0,
+ FavoriteAppLeftShort,
FavoriteAppLeftLong,
FavoriteAppRightShort,
FavoriteAppRightLong,
@@ -53,30 +19,24 @@ typedef enum {
} FavoriteAppShortcut;
typedef enum {
- DummyAppLeft = 0,
+ DummyAppLeftShort,
DummyAppLeftLong,
- DummyAppRight,
+ DummyAppRightShort,
DummyAppRightLong,
DummyAppUpLong,
- DummyAppDown,
+ DummyAppDownShort,
DummyAppDownLong,
- DummyAppOk,
+ DummyAppOkShort,
DummyAppOkLong,
DummyAppNumber,
} DummyAppShortcut;
typedef struct {
- InputKey data[MAX_PIN_SIZE];
- uint8_t length;
-} PinCode;
-
-typedef struct {
- char name_or_path[MAX_APP_LENGTH];
+ char name_or_path[128];
} FavoriteApp;
typedef struct {
- PinCode pin_code;
uint32_t auto_lock_delay_ms;
uint8_t displayBatteryPercentage;
uint8_t dummy_mode;
@@ -84,3 +44,6 @@ typedef struct {
FavoriteApp favorite_apps[FavoriteAppNumber];
FavoriteApp dummy_apps[DummyAppNumber];
} DesktopSettings;
+
+void desktop_settings_load(DesktopSettings* settings);
+void desktop_settings_save(const DesktopSettings* settings);
diff --git a/applications/services/desktop/helpers/pin.c b/applications/services/desktop/helpers/pin.c
deleted file mode 100644
index 0b1149d6c2..0000000000
--- a/applications/services/desktop/helpers/pin.c
+++ /dev/null
@@ -1,72 +0,0 @@
-#include "pin.h"
-
-#include
-#include
-#include
-#include
-#include
-#include
-
-static const NotificationSequence sequence_pin_fail = {
- &message_display_backlight_on,
-
- &message_red_255,
- &message_vibro_on,
- &message_delay_100,
- &message_vibro_off,
- &message_red_0,
-
- &message_delay_250,
-
- &message_red_255,
- &message_vibro_on,
- &message_delay_100,
- &message_vibro_off,
- &message_red_0,
- NULL,
-};
-
-static const uint8_t desktop_helpers_fails_timeout[] = {
- 0,
- 0,
- 0,
- 0,
- 30,
- 60,
- 90,
- 120,
- 150,
- 180,
- /* +60 for every next fail */
-};
-
-void desktop_pin_lock_error_notify(void) {
- NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
- notification_message(notification, &sequence_pin_fail);
- furi_record_close(RECORD_NOTIFICATION);
-}
-
-uint32_t desktop_pin_lock_get_fail_timeout(void) {
- uint32_t pin_fails = furi_hal_rtc_get_pin_fails();
- uint32_t pin_timeout = 0;
- uint32_t max_index = COUNT_OF(desktop_helpers_fails_timeout) - 1;
- if(pin_fails <= max_index) {
- pin_timeout = desktop_helpers_fails_timeout[pin_fails];
- } else {
- pin_timeout = desktop_helpers_fails_timeout[max_index] + (pin_fails - max_index) * 60;
- }
-
- return pin_timeout;
-}
-
-bool desktop_pin_compare(const PinCode* pin_code1, const PinCode* pin_code2) {
- furi_assert(pin_code1);
- furi_assert(pin_code2);
- bool result = false;
-
- if(pin_code1->length == pin_code2->length) {
- result = !memcmp(pin_code1->data, pin_code2->data, pin_code1->length);
- }
-
- return result;
-}
diff --git a/applications/services/desktop/helpers/pin.h b/applications/services/desktop/helpers/pin.h
deleted file mode 100644
index 23d16b0aa4..0000000000
--- a/applications/services/desktop/helpers/pin.h
+++ /dev/null
@@ -1,11 +0,0 @@
-#pragma once
-#include
-#include
-#include "../desktop.h"
-#include
-
-void desktop_pin_lock_error_notify(void);
-
-uint32_t desktop_pin_lock_get_fail_timeout(void);
-
-bool desktop_pin_compare(const PinCode* pin_code1, const PinCode* pin_code2);
diff --git a/applications/services/desktop/helpers/pin_code.c b/applications/services/desktop/helpers/pin_code.c
new file mode 100644
index 0000000000..d1a37ed24c
--- /dev/null
+++ b/applications/services/desktop/helpers/pin_code.c
@@ -0,0 +1,103 @@
+#include "pin_code.h"
+
+#include
+
+#include
+#include
+
+#define DESKTOP_PIN_CODE_DIGIT_BIT_WIDTH (2)
+#define DESKTOP_PIN_CODE_LENGTH_OFFSET (28)
+
+static const NotificationSequence sequence_pin_fail = {
+ &message_display_backlight_on,
+
+ &message_red_255,
+ &message_vibro_on,
+ &message_delay_100,
+ &message_vibro_off,
+ &message_red_0,
+
+ &message_delay_250,
+
+ &message_red_255,
+ &message_vibro_on,
+ &message_delay_100,
+ &message_vibro_off,
+ &message_red_0,
+ NULL,
+};
+
+static const uint8_t desktop_helpers_fails_timeout[] = {
+ 0,
+ 0,
+ 0,
+ 0,
+ 30,
+ 60,
+ 90,
+ 120,
+ 150,
+ 180,
+ /* +60 for every next fail */
+};
+
+static uint32_t desktop_pin_code_pack(const DesktopPinCode* pin_code) {
+ furi_check(pin_code);
+ furi_check(pin_code->length <= sizeof(pin_code->data));
+
+ uint32_t reg_value = 0;
+
+ for(uint8_t i = 0; i < pin_code->length; ++i) {
+ furi_check(pin_code->data[i] < (1 << DESKTOP_PIN_CODE_DIGIT_BIT_WIDTH));
+ reg_value |= (uint32_t)pin_code->data[i] << (i * DESKTOP_PIN_CODE_DIGIT_BIT_WIDTH);
+ }
+
+ reg_value |= (uint32_t)pin_code->length << DESKTOP_PIN_CODE_LENGTH_OFFSET;
+
+ return reg_value;
+}
+
+bool desktop_pin_code_is_set(void) {
+ return furi_hal_rtc_get_pin_value() >> DESKTOP_PIN_CODE_LENGTH_OFFSET;
+}
+
+void desktop_pin_code_set(const DesktopPinCode* pin_code) {
+ furi_hal_rtc_set_pin_value(desktop_pin_code_pack(pin_code));
+}
+
+void desktop_pin_code_reset(void) {
+ furi_hal_rtc_set_pin_value(0);
+}
+
+bool desktop_pin_code_check(const DesktopPinCode* pin_code) {
+ return furi_hal_rtc_get_pin_value() == desktop_pin_code_pack(pin_code);
+}
+
+bool desktop_pin_code_is_equal(const DesktopPinCode* pin_code1, const DesktopPinCode* pin_code2) {
+ furi_check(pin_code1);
+ furi_check(pin_code1->length <= sizeof(pin_code1->data));
+ furi_check(pin_code2);
+ furi_check(pin_code2->length <= sizeof(pin_code2->data));
+
+ return pin_code1->length == pin_code2->length &&
+ memcmp(pin_code1->data, pin_code2->data, pin_code1->length) == 0;
+}
+
+void desktop_pin_lock_error_notify(void) {
+ NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
+ notification_message(notification, &sequence_pin_fail);
+ furi_record_close(RECORD_NOTIFICATION);
+}
+
+uint32_t desktop_pin_lock_get_fail_timeout(void) {
+ uint32_t pin_fails = furi_hal_rtc_get_pin_fails();
+ uint32_t pin_timeout = 0;
+ uint32_t max_index = COUNT_OF(desktop_helpers_fails_timeout) - 1;
+ if(pin_fails <= max_index) {
+ pin_timeout = desktop_helpers_fails_timeout[pin_fails];
+ } else {
+ pin_timeout = desktop_helpers_fails_timeout[max_index] + (pin_fails - max_index) * 60;
+ }
+
+ return pin_timeout;
+}
diff --git a/applications/services/desktop/helpers/pin_code.h b/applications/services/desktop/helpers/pin_code.h
new file mode 100644
index 0000000000..848c915b6c
--- /dev/null
+++ b/applications/services/desktop/helpers/pin_code.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include
+#include
+
+#define DESKTOP_PIN_CODE_MAX_LEN (10)
+
+typedef struct {
+ uint8_t data[DESKTOP_PIN_CODE_MAX_LEN];
+ uint8_t length;
+} DesktopPinCode;
+
+bool desktop_pin_code_is_set(void);
+
+void desktop_pin_code_set(const DesktopPinCode* pin_code);
+
+void desktop_pin_code_reset(void);
+
+bool desktop_pin_code_check(const DesktopPinCode* pin_code);
+
+bool desktop_pin_code_is_equal(const DesktopPinCode* pin_code1, const DesktopPinCode* pin_code2);
+
+void desktop_pin_lock_error_notify(void);
+
+uint32_t desktop_pin_lock_get_fail_timeout(void);
diff --git a/applications/services/desktop/scenes/desktop_scene_i.h b/applications/services/desktop/scenes/desktop_scene_i.h
deleted file mode 100644
index f481733aca..0000000000
--- a/applications/services/desktop/scenes/desktop_scene_i.h
+++ /dev/null
@@ -1,4 +0,0 @@
-#pragma once
-
-#define SCENE_LOCKED_FIRST_ENTER 0
-#define SCENE_LOCKED_REPEAT_ENTER 1
diff --git a/applications/services/desktop/scenes/desktop_scene_lock_menu.c b/applications/services/desktop/scenes/desktop_scene_lock_menu.c
index 5951a8e4e3..5ca95c4c59 100644
--- a/applications/services/desktop/scenes/desktop_scene_lock_menu.c
+++ b/applications/services/desktop/scenes/desktop_scene_lock_menu.c
@@ -20,7 +20,6 @@ void desktop_scene_lock_menu_callback(DesktopEvent event, void* context) {
void desktop_scene_lock_menu_on_enter(void* context) {
Desktop* desktop = (Desktop*)context;
- DESKTOP_SETTINGS_LOAD(&desktop->settings);
scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneLockMenu, 0);
desktop_lock_menu_set_callback(desktop->lock_menu, desktop_scene_lock_menu_callback, desktop);
desktop_lock_menu_set_dummy_mode_state(desktop->lock_menu, desktop->settings.dummy_mode);
@@ -38,11 +37,8 @@ bool desktop_scene_lock_menu_on_event(void* context, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeTick) {
bool check_pin_changed =
scene_manager_get_scene_state(desktop->scene_manager, DesktopSceneLockMenu);
- if(check_pin_changed) {
- DESKTOP_SETTINGS_LOAD(&desktop->settings);
- if(desktop->settings.pin_code.length > 0) {
- scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneLockMenu, 0);
- }
+ if(check_pin_changed && desktop_pin_code_is_set()) {
+ scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneLockMenu, 0);
}
} else if(event.type == SceneManagerEventTypeCustom) {
switch(event.event) {
diff --git a/applications/services/desktop/scenes/desktop_scene_locked.c b/applications/services/desktop/scenes/desktop_scene_locked.c
index 846b2b5412..e7eeebca6d 100644
--- a/applications/services/desktop/scenes/desktop_scene_locked.c
+++ b/applications/services/desktop/scenes/desktop_scene_locked.c
@@ -6,12 +6,12 @@
#include "../desktop.h"
#include "../desktop_i.h"
-#include "../helpers/pin.h"
+#include "../helpers/pin_code.h"
#include "../animations/animation_manager.h"
#include "../views/desktop_events.h"
#include "../views/desktop_view_locked.h"
#include "desktop_scene.h"
-#include "desktop_scene_i.h"
+#include "desktop_scene_locked.h"
#define WRONG_PIN_HEADER_TIMEOUT 3000
#define INPUT_PIN_VIEW_TIMEOUT 15000
@@ -42,15 +42,13 @@ void desktop_scene_locked_on_enter(void* context) {
bool switch_to_timeout_scene = false;
uint32_t state = scene_manager_get_scene_state(desktop->scene_manager, DesktopSceneLocked);
- if(state == SCENE_LOCKED_FIRST_ENTER) {
- bool pin_locked = desktop->settings.pin_code.length > 0;
+ if(state == DesktopSceneLockedStateFirstEnter) {
view_port_enabled_set(desktop->lock_icon_viewport, true);
Gui* gui = furi_record_open(RECORD_GUI);
gui_set_lockdown(gui, true);
furi_record_close(RECORD_GUI);
- if(pin_locked) {
- DESKTOP_SETTINGS_LOAD(&desktop->settings);
+ if(desktop_pin_code_is_set()) {
desktop_view_locked_lock(desktop->locked_view, true);
uint32_t pin_timeout = desktop_pin_lock_get_fail_timeout();
if(pin_timeout > 0) {
@@ -65,7 +63,7 @@ void desktop_scene_locked_on_enter(void* context) {
desktop_view_locked_close_doors(desktop->locked_view);
}
scene_manager_set_scene_state(
- desktop->scene_manager, DesktopSceneLocked, SCENE_LOCKED_REPEAT_ENTER);
+ desktop->scene_manager, DesktopSceneLocked, DesktopSceneLockedStateRepeatEnter);
}
if(switch_to_timeout_scene) {
diff --git a/applications/services/desktop/scenes/desktop_scene_locked.h b/applications/services/desktop/scenes/desktop_scene_locked.h
new file mode 100644
index 0000000000..7d5b6b7bcd
--- /dev/null
+++ b/applications/services/desktop/scenes/desktop_scene_locked.h
@@ -0,0 +1,6 @@
+#pragma once
+
+typedef enum {
+ DesktopSceneLockedStateFirstEnter,
+ DesktopSceneLockedStateRepeatEnter,
+} DesktopSceneLockedState;
diff --git a/applications/services/desktop/scenes/desktop_scene_main.c b/applications/services/desktop/scenes/desktop_scene_main.c
index 5cc2033c3d..4fdcc34006 100644
--- a/applications/services/desktop/scenes/desktop_scene_main.c
+++ b/applications/services/desktop/scenes/desktop_scene_main.c
@@ -155,25 +155,21 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) {
}
case DesktopMainEventOpenFavoriteLeftShort:
- DESKTOP_SETTINGS_LOAD(&desktop->settings);
desktop_scene_main_start_favorite(
desktop, &desktop->settings.favorite_apps[FavoriteAppLeftShort]);
consumed = true;
break;
case DesktopMainEventOpenFavoriteLeftLong:
- DESKTOP_SETTINGS_LOAD(&desktop->settings);
desktop_scene_main_start_favorite(
desktop, &desktop->settings.favorite_apps[FavoriteAppLeftLong]);
consumed = true;
break;
case DesktopMainEventOpenFavoriteRightShort:
- DESKTOP_SETTINGS_LOAD(&desktop->settings);
desktop_scene_main_start_favorite(
desktop, &desktop->settings.favorite_apps[FavoriteAppRightShort]);
consumed = true;
break;
case DesktopMainEventOpenFavoriteRightLong:
- DESKTOP_SETTINGS_LOAD(&desktop->settings);
desktop_scene_main_start_favorite(
desktop, &desktop->settings.favorite_apps[FavoriteAppRightLong]);
consumed = true;
@@ -189,13 +185,12 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) {
break;
case DesktopAnimationEventInteractAnimation:
if(!animation_manager_interact_process(desktop->animation_manager)) {
- DESKTOP_SETTINGS_LOAD(&desktop->settings);
if(!desktop->settings.dummy_mode) {
desktop_scene_main_open_app_or_profile(
desktop, &desktop->settings.favorite_apps[FavoriteAppRightShort]);
} else {
desktop_scene_main_open_app_or_profile(
- desktop, &desktop->settings.dummy_apps[DummyAppRight]);
+ desktop, &desktop->settings.dummy_apps[DummyAppRightShort]);
}
}
consumed = true;
@@ -203,15 +198,15 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) {
case DesktopDummyEventOpenLeft:
desktop_scene_main_open_app_or_profile(
- desktop, &desktop->settings.dummy_apps[DummyAppLeft]);
+ desktop, &desktop->settings.dummy_apps[DummyAppLeftShort]);
break;
case DesktopDummyEventOpenDown:
desktop_scene_main_open_app_or_profile(
- desktop, &desktop->settings.dummy_apps[DummyAppDown]);
+ desktop, &desktop->settings.dummy_apps[DummyAppDownShort]);
break;
case DesktopDummyEventOpenOk:
desktop_scene_main_open_app_or_profile(
- desktop, &desktop->settings.dummy_apps[DummyAppOk]);
+ desktop, &desktop->settings.dummy_apps[DummyAppOkShort]);
break;
case DesktopDummyEventOpenUpLong:
if(!desktop_scene_main_check_none(
diff --git a/applications/services/desktop/scenes/desktop_scene_pin_input.c b/applications/services/desktop/scenes/desktop_scene_pin_input.c
index 6f5bfe8cb3..449dd97f1a 100644
--- a/applications/services/desktop/scenes/desktop_scene_pin_input.c
+++ b/applications/services/desktop/scenes/desktop_scene_pin_input.c
@@ -10,7 +10,7 @@
#include "../desktop_i.h"
#include "../views/desktop_events.h"
#include "../views/desktop_view_pin_input.h"
-#include "../helpers/pin.h"
+#include "../helpers/pin_code.h"
#include "desktop_scene.h"
#define WRONG_PIN_HEADER_TIMEOUT 3000
@@ -49,10 +49,12 @@ static void desktop_scene_pin_input_back_callback(void* context) {
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopPinInputEventBack);
}
-static void desktop_scene_pin_input_done_callback(const PinCode* pin_code, void* context) {
+static void desktop_scene_pin_input_done_callback(const DesktopPinCode* pin_code, void* context) {
Desktop* desktop = (Desktop*)context;
- if(desktop_pin_compare(&desktop->settings.pin_code, pin_code)) {
+
+ if(desktop_pin_code_check(pin_code)) {
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopPinInputEventUnlocked);
+
} else {
uint32_t pin_fails = furi_hal_rtc_get_pin_fails();
furi_hal_rtc_set_pin_fails(pin_fails + 1);
diff --git a/applications/services/desktop/scenes/desktop_scene_slideshow.c b/applications/services/desktop/scenes/desktop_scene_slideshow.c
index 012aff7519..7599241164 100644
--- a/applications/services/desktop/scenes/desktop_scene_slideshow.c
+++ b/applications/services/desktop/scenes/desktop_scene_slideshow.c
@@ -45,9 +45,6 @@ bool desktop_scene_slideshow_on_event(void* context, SceneManagerEvent event) {
}
void desktop_scene_slideshow_on_exit(void* context) {
- UNUSED(context);
-
- Storage* storage = furi_record_open(RECORD_STORAGE);
- storage_common_remove(storage, SLIDESHOW_FS_PATH);
- furi_record_close(RECORD_STORAGE);
+ Desktop* desktop = context;
+ storage_common_remove(desktop->storage, SLIDESHOW_FS_PATH);
}
diff --git a/applications/services/desktop/views/desktop_events.h b/applications/services/desktop/views/desktop_events.h
index c22b19acc7..ba91a30ccd 100644
--- a/applications/services/desktop/views/desktop_events.h
+++ b/applications/services/desktop/views/desktop_events.h
@@ -60,4 +60,6 @@ typedef enum {
DesktopGlobalAfterAppFinished,
DesktopGlobalAutoLock,
DesktopGlobalApiUnlock,
+ DesktopGlobalSaveSettings,
+ DesktopGlobalReloadSettings,
} DesktopEvent;
diff --git a/applications/services/desktop/views/desktop_view_locked.c b/applications/services/desktop/views/desktop_view_locked.c
index 74b020f45f..2fb89b27e4 100644
--- a/applications/services/desktop/views/desktop_view_locked.c
+++ b/applications/services/desktop/views/desktop_view_locked.c
@@ -1,12 +1,11 @@
-#include
-#include
#include
+
#include
#include
#include
+
#include
-#include
#include "../desktop_i.h"
#include "desktop_view_locked.h"
diff --git a/applications/services/desktop/views/desktop_view_pin_input.c b/applications/services/desktop/views/desktop_view_pin_input.c
index 965b5cceb7..c89a143c87 100644
--- a/applications/services/desktop/views/desktop_view_pin_input.c
+++ b/applications/services/desktop/views/desktop_view_pin_input.c
@@ -6,7 +6,6 @@
#include
#include "desktop_view_pin_input.h"
-#include
#define NO_ACTIVITY_TIMEOUT 15000
@@ -14,6 +13,9 @@
#define DEFAULT_PIN_X 64
#define DEFAULT_PIN_Y 32
+#define MIN_PIN_LENGTH 4
+#define MAX_PIN_LENGTH DESKTOP_PIN_CODE_MAX_LEN
+
struct DesktopViewPinInput {
View* view;
DesktopViewPinInputCallback back_callback;
@@ -24,7 +26,7 @@ struct DesktopViewPinInput {
};
typedef struct {
- PinCode pin;
+ DesktopPinCode pin;
bool pin_hidden;
bool locked_input;
uint8_t pin_x;
@@ -50,7 +52,7 @@ static bool desktop_view_pin_input_input(InputEvent* event, void* context) {
bool call_back_callback = false;
bool call_done_callback = false;
- PinCode pin_code = {0};
+ DesktopPinCode pin_code = {0};
if(event->type == InputTypeShort) {
switch(event->key) {
@@ -59,13 +61,13 @@ static bool desktop_view_pin_input_input(InputEvent* event, void* context) {
case InputKeyDown:
case InputKeyUp:
if(!model->locked_input) {
- if(model->pin.length < MAX_PIN_SIZE) {
+ if(model->pin.length < MAX_PIN_LENGTH) {
model->pin.data[model->pin.length++] = event->key;
}
}
break;
case InputKeyOk:
- if(model->pin.length >= MIN_PIN_SIZE) {
+ if(model->pin.length >= MIN_PIN_LENGTH) {
call_done_callback = true;
pin_code = model->pin;
}
@@ -102,7 +104,7 @@ static void desktop_view_pin_input_draw_cells(Canvas* canvas, DesktopViewPinInpu
furi_assert(model);
uint8_t draw_pin_size = MAX(4, model->pin.length + 1);
- if(model->locked_input || (model->pin.length == MAX_PIN_SIZE)) {
+ if(model->locked_input || (model->pin.length == MAX_PIN_LENGTH)) {
draw_pin_size = model->pin.length;
}
@@ -155,7 +157,7 @@ static void desktop_view_pin_input_draw(Canvas* canvas, void* context) {
canvas_draw_str(canvas, 16, 60, "= clear");
}
- if(model->button_label && ((model->pin.length >= MIN_PIN_SIZE) || model->locked_input)) {
+ if(model->button_label && ((model->pin.length >= MIN_PIN_LENGTH) || model->locked_input)) {
elements_button_center(canvas, model->button_label);
}
@@ -247,7 +249,7 @@ void desktop_view_pin_input_unlock_input(DesktopViewPinInput* pin_input) {
view_commit_model(pin_input->view, true);
}
-void desktop_view_pin_input_set_pin(DesktopViewPinInput* pin_input, const PinCode* pin) {
+void desktop_view_pin_input_set_pin(DesktopViewPinInput* pin_input, const DesktopPinCode* pin) {
furi_assert(pin_input);
furi_assert(pin);
diff --git a/applications/services/desktop/views/desktop_view_pin_input.h b/applications/services/desktop/views/desktop_view_pin_input.h
index c430aff9ff..4605b6ff15 100644
--- a/applications/services/desktop/views/desktop_view_pin_input.h
+++ b/applications/services/desktop/views/desktop_view_pin_input.h
@@ -1,16 +1,17 @@
#pragma once
#include
-#include
+
+#include "../helpers/pin_code.h"
typedef void (*DesktopViewPinInputCallback)(void*);
-typedef void (*DesktopViewPinInputDoneCallback)(const PinCode* pin_code, void*);
+typedef void (*DesktopViewPinInputDoneCallback)(const DesktopPinCode* pin_code, void*);
typedef struct DesktopViewPinInput DesktopViewPinInput;
DesktopViewPinInput* desktop_view_pin_input_alloc(void);
void desktop_view_pin_input_free(DesktopViewPinInput*);
-void desktop_view_pin_input_set_pin(DesktopViewPinInput* pin_input, const PinCode* pin);
+void desktop_view_pin_input_set_pin(DesktopViewPinInput* pin_input, const DesktopPinCode* pin_code);
void desktop_view_pin_input_reset_pin(DesktopViewPinInput* pin_input);
void desktop_view_pin_input_hide_pin(DesktopViewPinInput* pin_input, bool pin_hidden);
void desktop_view_pin_input_set_label_button(DesktopViewPinInput* pin_input, const char* label);
diff --git a/applications/services/desktop/views/desktop_view_pin_timeout.c b/applications/services/desktop/views/desktop_view_pin_timeout.c
index 2811ba7d25..d7e5507a75 100644
--- a/applications/services/desktop/views/desktop_view_pin_timeout.c
+++ b/applications/services/desktop/views/desktop_view_pin_timeout.c
@@ -1,9 +1,5 @@
-
#include
-#include
-#include
-#include
-#include
+
#include
#include
diff --git a/applications/services/dialogs/dialogs_module_file_browser.c b/applications/services/dialogs/dialogs_module_file_browser.c
index b1558f1e95..12a7439e60 100644
--- a/applications/services/dialogs/dialogs_module_file_browser.c
+++ b/applications/services/dialogs/dialogs_module_file_browser.c
@@ -49,12 +49,11 @@ bool dialogs_app_process_module_file_browser(const DialogsAppMessageDataFileBrow
file_browser_start(file_browser, data->preselected_filename);
view_holder_set_view(view_holder, file_browser_get_view(file_browser));
- view_holder_start(view_holder);
api_lock_wait_unlock(file_browser_context->lock);
ret = file_browser_context->result;
- view_holder_stop(view_holder);
+ view_holder_set_view(view_holder, NULL);
view_holder_free(view_holder);
file_browser_stop(file_browser);
file_browser_free(file_browser);
diff --git a/applications/services/dialogs/dialogs_module_message.c b/applications/services/dialogs/dialogs_module_message.c
index a71f403c54..9dc9ff9cb9 100644
--- a/applications/services/dialogs/dialogs_module_message.c
+++ b/applications/services/dialogs/dialogs_module_message.c
@@ -88,12 +88,11 @@ DialogMessageButton dialogs_app_process_module_message(const DialogsAppMessageDa
dialog_ex_set_right_button_text(dialog_ex, message->right_button_text);
view_holder_set_view(view_holder, dialog_ex_get_view(dialog_ex));
- view_holder_start(view_holder);
api_lock_wait_unlock(message_context->lock);
ret = message_context->result;
- view_holder_stop(view_holder);
+ view_holder_set_view(view_holder, NULL);
view_holder_free(view_holder);
dialog_ex_free(dialog_ex);
api_lock_free(message_context->lock);
diff --git a/applications/services/dolphin/dolphin.c b/applications/services/dolphin/dolphin.c
index 95982f1af8..501e37c3c8 100644
--- a/applications/services/dolphin/dolphin.c
+++ b/applications/services/dolphin/dolphin.c
@@ -1,6 +1,7 @@
#include "dolphin_i.h"
#include
+#include
#define TAG "Dolphin"
@@ -191,8 +192,8 @@ static void dolphin_update_clear_limits_timer_period(void* context) {
FURI_LOG_D(TAG, "Daily limits reset in %lu ms", time_to_clear_limits);
}
-static bool dolphin_process_event(FuriMessageQueue* queue, void* context) {
- UNUSED(queue);
+static bool dolphin_process_event(FuriEventLoopObject* object, void* context) {
+ UNUSED(object);
Dolphin* dolphin = context;
DolphinEvent event;
@@ -203,8 +204,8 @@ static bool dolphin_process_event(FuriMessageQueue* queue, void* context) {
if(event.type == DolphinEventTypeDeed) {
dolphin_state_on_deed(dolphin->state, event.deed);
- DolphinPubsubEvent event = DolphinPubsubEventUpdate;
- furi_pubsub_publish(dolphin->pubsub, &event);
+ DolphinPubsubEvent pubsub_event = DolphinPubsubEventUpdate;
+ furi_pubsub_publish(dolphin->pubsub, &pubsub_event);
furi_event_loop_timer_start(dolphin->butthurt_timer, BUTTHURT_INCREASE_PERIOD_TICKS);
furi_event_loop_timer_start(dolphin->flush_timer, FLUSH_TIMEOUT_TICKS);
@@ -223,6 +224,10 @@ static bool dolphin_process_event(FuriMessageQueue* queue, void* context) {
dolphin_state_increase_level(dolphin->state);
furi_event_loop_timer_start(dolphin->flush_timer, FLUSH_TIMEOUT_TICKS);
+ } else if(event.type == DolphinEventTypeReloadState) {
+ dolphin_state_load(dolphin->state);
+ furi_event_loop_timer_start(dolphin->butthurt_timer, BUTTHURT_INCREASE_PERIOD_TICKS);
+
} else {
furi_crash();
}
@@ -232,6 +237,32 @@ static bool dolphin_process_event(FuriMessageQueue* queue, void* context) {
return true;
}
+static void dolphin_storage_callback(const void* message, void* context) {
+ furi_assert(context);
+ Dolphin* dolphin = context;
+ const StorageEvent* event = message;
+
+ if(event->type == StorageEventTypeCardMount) {
+ DolphinEvent event = {
+ .type = DolphinEventTypeReloadState,
+ };
+
+ dolphin_event_send_async(dolphin, &event);
+ }
+}
+
+static void dolphin_init_state(Dolphin* dolphin) {
+ Storage* storage = furi_record_open(RECORD_STORAGE);
+ furi_pubsub_subscribe(storage_get_pubsub(storage), dolphin_storage_callback, dolphin);
+
+ if(storage_sd_status(storage) != FSE_OK) {
+ FURI_LOG_D(TAG, "SD Card not ready, skipping state");
+ return;
+ }
+
+ dolphin_state_load(dolphin->state);
+}
+
// Application thread
int32_t dolphin_srv(void* p) {
@@ -247,9 +278,9 @@ int32_t dolphin_srv(void* p) {
Dolphin* dolphin = dolphin_alloc();
furi_record_create(RECORD_DOLPHIN, dolphin);
- dolphin_state_load(dolphin->state);
+ dolphin_init_state(dolphin);
- furi_event_loop_message_queue_subscribe(
+ furi_event_loop_subscribe_message_queue(
dolphin->event_loop,
dolphin->event_queue,
FuriEventLoopEventIn,
diff --git a/applications/services/dolphin/dolphin_i.h b/applications/services/dolphin/dolphin_i.h
index d4add808ad..6a6b3dfd81 100644
--- a/applications/services/dolphin/dolphin_i.h
+++ b/applications/services/dolphin/dolphin_i.h
@@ -12,6 +12,7 @@ typedef enum {
DolphinEventTypeStats,
DolphinEventTypeFlush,
DolphinEventTypeLevel,
+ DolphinEventTypeReloadState,
} DolphinEventType;
typedef struct {
diff --git a/applications/services/dolphin/helpers/dolphin_state.c b/applications/services/dolphin/helpers/dolphin_state.c
index 5216b961dc..5cbc511458 100644
--- a/applications/services/dolphin/helpers/dolphin_state.c
+++ b/applications/services/dolphin/helpers/dolphin_state.c
@@ -1,11 +1,10 @@
#include "dolphin_state.h"
-#include "dolphin/helpers/dolphin_deed.h"
#include "dolphin_state_filename.h"
-#include
-#include
#include
#include
+
+#include
#include
#define TAG "DolphinState"
@@ -26,29 +25,28 @@ void dolphin_state_free(DolphinState* dolphin_state) {
free(dolphin_state);
}
-bool dolphin_state_save(DolphinState* dolphin_state) {
+void dolphin_state_save(DolphinState* dolphin_state) {
if(!dolphin_state->dirty) {
- return true;
+ return;
}
- bool result = saved_struct_save(
+ bool success = saved_struct_save(
DOLPHIN_STATE_PATH,
&dolphin_state->data,
sizeof(DolphinStoreData),
DOLPHIN_STATE_HEADER_MAGIC,
DOLPHIN_STATE_HEADER_VERSION);
- if(result) {
+ if(success) {
FURI_LOG_I(TAG, "State saved");
dolphin_state->dirty = false;
+
} else {
FURI_LOG_E(TAG, "Failed to save state");
}
-
- return result;
}
-bool dolphin_state_load(DolphinState* dolphin_state) {
+void dolphin_state_load(DolphinState* dolphin_state) {
bool success = saved_struct_load(
DOLPHIN_STATE_PATH,
&dolphin_state->data,
@@ -64,12 +62,12 @@ bool dolphin_state_load(DolphinState* dolphin_state) {
}
if(!success) {
- FURI_LOG_W(TAG, "Reset dolphin-state");
- memset(dolphin_state, 0, sizeof(*dolphin_state));
+ FURI_LOG_W(TAG, "Reset Dolphin state");
+ memset(dolphin_state, 0, sizeof(DolphinState));
+
dolphin_state->dirty = true;
+ dolphin_state_save(dolphin_state);
}
-
- return success;
}
uint64_t dolphin_state_timestamp(void) {
diff --git a/applications/services/dolphin/helpers/dolphin_state.h b/applications/services/dolphin/helpers/dolphin_state.h
index a8d8406bef..bdbd98d473 100644
--- a/applications/services/dolphin/helpers/dolphin_state.h
+++ b/applications/services/dolphin/helpers/dolphin_state.h
@@ -1,9 +1,9 @@
#pragma once
-#include "dolphin_deed.h"
#include
#include
-#include
+
+#include "dolphin_deed.h"
typedef struct DolphinState DolphinState;
typedef struct {
@@ -25,9 +25,9 @@ DolphinState* dolphin_state_alloc(void);
void dolphin_state_free(DolphinState* dolphin_state);
-bool dolphin_state_save(DolphinState* dolphin_state);
+void dolphin_state_save(DolphinState* dolphin_state);
-bool dolphin_state_load(DolphinState* dolphin_state);
+void dolphin_state_load(DolphinState* dolphin_state);
void dolphin_state_clear_limits(DolphinState* dolphin_state);
diff --git a/applications/services/expansion/expansion.c b/applications/services/expansion/expansion.c
index 9b0b31cf71..219bf06414 100644
--- a/applications/services/expansion/expansion.c
+++ b/applications/services/expansion/expansion.c
@@ -1,9 +1,9 @@
#include "expansion.h"
-#include "expansion_i.h"
#include
#include
+#include
#include
#include "expansion_worker.h"
@@ -18,24 +18,19 @@ typedef enum {
ExpansionStateDisabled,
ExpansionStateEnabled,
ExpansionStateRunning,
- ExpansionStateConnectionEstablished,
} ExpansionState;
typedef enum {
ExpansionMessageTypeEnable,
ExpansionMessageTypeDisable,
ExpansionMessageTypeSetListenSerial,
+ ExpansionMessageTypeReloadSettings,
ExpansionMessageTypeModuleConnected,
ExpansionMessageTypeModuleDisconnected,
- ExpansionMessageTypeConnectionEstablished,
- ExpansionMessageTypeIsConnected,
} ExpansionMessageType;
typedef union {
- union {
- FuriHalSerialId serial_id;
- bool* is_connected;
- };
+ FuriHalSerialId serial_id;
} ExpansionMessageData;
typedef struct {
@@ -50,8 +45,6 @@ struct Expansion {
FuriHalSerialId serial_id;
ExpansionWorker* worker;
ExpansionState state;
-
- ExpansionSettings settings;
};
static const char* const expansion_uart_names[] = {
@@ -74,21 +67,13 @@ static void expansion_detect_callback(void* context) {
UNUSED(status);
}
-static void expansion_worker_callback(void* context, ExpansionWorkerCallbackReason reason) {
+static void expansion_worker_callback(void* context) {
furi_assert(context);
Expansion* instance = context;
- ExpansionMessage message;
- switch(reason) {
- case ExpansionWorkerCallbackReasonExit:
- message.type = ExpansionMessageTypeModuleDisconnected;
- message.api_lock = NULL; // Not locking the API here to avoid a deadlock
- break;
-
- case ExpansionWorkerCallbackReasonConnected:
- message.type = ExpansionMessageTypeConnectionEstablished;
- message.api_lock = api_lock_alloc_locked();
- break;
+ ExpansionMessage message = {
+ .type = ExpansionMessageTypeModuleDisconnected,
+ .api_lock = NULL, // Not locking the API here to avoid a deadlock
};
const FuriStatus status = furi_message_queue_put(instance->queue, &message, FuriWaitForever);
@@ -103,9 +88,12 @@ static void
return;
}
- if(instance->settings.uart_index < FuriHalSerialIdMax) {
+ ExpansionSettings settings;
+ expansion_settings_load(&settings);
+
+ if(settings.uart_index < FuriHalSerialIdMax) {
instance->state = ExpansionStateEnabled;
- instance->serial_id = instance->settings.uart_index;
+ instance->serial_id = settings.uart_index;
furi_hal_serial_control_set_expansion_callback(
instance->serial_id, expansion_detect_callback, instance);
@@ -116,12 +104,9 @@ static void
static void
expansion_control_handler_disable(Expansion* instance, const ExpansionMessageData* data) {
UNUSED(data);
-
if(instance->state == ExpansionStateDisabled) {
return;
- } else if(
- instance->state == ExpansionStateRunning ||
- instance->state == ExpansionStateConnectionEstablished) {
+ } else if(instance->state == ExpansionStateRunning) {
expansion_worker_stop(instance->worker);
expansion_worker_free(instance->worker);
} else {
@@ -136,10 +121,10 @@ static void
static void expansion_control_handler_set_listen_serial(
Expansion* instance,
const ExpansionMessageData* data) {
- furi_check(data->serial_id < FuriHalSerialIdMax);
+ if(instance->state != ExpansionStateDisabled && instance->serial_id == data->serial_id) {
+ return;
- if(instance->state == ExpansionStateRunning ||
- instance->state == ExpansionStateConnectionEstablished) {
+ } else if(instance->state == ExpansionStateRunning) {
expansion_worker_stop(instance->worker);
expansion_worker_free(instance->worker);
@@ -156,6 +141,26 @@ static void expansion_control_handler_set_listen_serial(
FURI_LOG_D(TAG, "Listen serial changed to %s", expansion_uart_names[instance->serial_id]);
}
+static void expansion_control_handler_reload_settings(
+ Expansion* instance,
+ const ExpansionMessageData* data) {
+ UNUSED(data);
+
+ ExpansionSettings settings;
+ expansion_settings_load(&settings);
+
+ if(settings.uart_index < FuriHalSerialIdMax) {
+ const ExpansionMessageData data = {
+ .serial_id = settings.uart_index,
+ };
+
+ expansion_control_handler_set_listen_serial(instance, &data);
+
+ } else {
+ expansion_control_handler_disable(instance, NULL);
+ }
+}
+
static void expansion_control_handler_module_connected(
Expansion* instance,
const ExpansionMessageData* data) {
@@ -177,8 +182,7 @@ static void expansion_control_handler_module_disconnected(
Expansion* instance,
const ExpansionMessageData* data) {
UNUSED(data);
- if(instance->state != ExpansionStateRunning &&
- instance->state != ExpansionStateConnectionEstablished) {
+ if(instance->state != ExpansionStateRunning) {
return;
}
@@ -188,33 +192,15 @@ static void expansion_control_handler_module_disconnected(
instance->serial_id, expansion_detect_callback, instance);
}
-static void expansion_control_handler_connection_established(
- Expansion* instance,
- const ExpansionMessageData* data) {
- UNUSED(data);
- if(instance->state != ExpansionStateRunning &&
- instance->state != ExpansionStateConnectionEstablished) {
- return;
- }
-
- instance->state = ExpansionStateConnectionEstablished;
-}
-
-static void
- expansion_control_handler_is_connected(Expansion* instance, const ExpansionMessageData* data) {
- *data->is_connected = instance->state == ExpansionStateConnectionEstablished;
-}
-
typedef void (*ExpansionControlHandler)(Expansion*, const ExpansionMessageData*);
static const ExpansionControlHandler expansion_control_handlers[] = {
[ExpansionMessageTypeEnable] = expansion_control_handler_enable,
[ExpansionMessageTypeDisable] = expansion_control_handler_disable,
[ExpansionMessageTypeSetListenSerial] = expansion_control_handler_set_listen_serial,
+ [ExpansionMessageTypeReloadSettings] = expansion_control_handler_reload_settings,
[ExpansionMessageTypeModuleConnected] = expansion_control_handler_module_connected,
[ExpansionMessageTypeModuleDisconnected] = expansion_control_handler_module_disconnected,
- [ExpansionMessageTypeConnectionEstablished] = expansion_control_handler_connection_established,
- [ExpansionMessageTypeIsConnected] = expansion_control_handler_is_connected,
};
static int32_t expansion_control(void* context) {
@@ -249,6 +235,22 @@ static Expansion* expansion_alloc(void) {
return instance;
}
+static void expansion_storage_callback(const void* message, void* context) {
+ furi_assert(context);
+
+ const StorageEvent* event = message;
+ Expansion* instance = context;
+
+ if(event->type == StorageEventTypeCardMount) {
+ ExpansionMessage em = {
+ .type = ExpansionMessageTypeReloadSettings,
+ .api_lock = NULL,
+ };
+
+ furi_check(furi_message_queue_put(instance->queue, &em, FuriWaitForever) == FuriStatusOk);
+ }
+}
+
void expansion_on_system_start(void* arg) {
UNUSED(arg);
@@ -256,7 +258,14 @@ void expansion_on_system_start(void* arg) {
furi_record_create(RECORD_EXPANSION, instance);
furi_thread_start(instance->thread);
- expansion_settings_load(&instance->settings);
+ Storage* storage = furi_record_open(RECORD_STORAGE);
+ furi_pubsub_subscribe(storage_get_pubsub(storage), expansion_storage_callback, instance);
+
+ if(storage_sd_status(storage) != FSE_OK) {
+ FURI_LOG_D(TAG, "SD Card not ready, skipping settings");
+ return;
+ }
+
expansion_enable(instance);
}
@@ -286,22 +295,6 @@ void expansion_disable(Expansion* instance) {
api_lock_wait_unlock_and_free(message.api_lock);
}
-bool expansion_is_connected(Expansion* instance) {
- furi_check(instance);
- bool is_connected;
-
- ExpansionMessage message = {
- .type = ExpansionMessageTypeIsConnected,
- .data.is_connected = &is_connected,
- .api_lock = api_lock_alloc_locked(),
- };
-
- furi_message_queue_put(instance->queue, &message, FuriWaitForever);
- api_lock_wait_unlock_and_free(message.api_lock);
-
- return is_connected;
-}
-
void expansion_set_listen_serial(Expansion* instance, FuriHalSerialId serial_id) {
furi_check(instance);
furi_check(serial_id < FuriHalSerialIdMax);
@@ -315,7 +308,3 @@ void expansion_set_listen_serial(Expansion* instance, FuriHalSerialId serial_id)
furi_message_queue_put(instance->queue, &message, FuriWaitForever);
api_lock_wait_unlock_and_free(message.api_lock);
}
-
-ExpansionSettings* expansion_get_settings(Expansion* instance) {
- return &instance->settings;
-}
diff --git a/applications/services/expansion/expansion.h b/applications/services/expansion/expansion.h
index 1b0879b1ec..e169b3c15d 100644
--- a/applications/services/expansion/expansion.h
+++ b/applications/services/expansion/expansion.h
@@ -50,15 +50,6 @@ void expansion_enable(Expansion* instance);
*/
void expansion_disable(Expansion* instance);
-/**
- * @brief Check if an expansion module is connected.
- *
- * @param[in,out] instance pointer to the Expansion instance.
- *
- * @returns true if the module is connected and initialized, false otherwise.
- */
-bool expansion_is_connected(Expansion* instance);
-
/**
* @brief Enable support for expansion modules on designated serial port.
*
diff --git a/applications/services/expansion/expansion_i.h b/applications/services/expansion/expansion_i.h
deleted file mode 100644
index 13a4962521..0000000000
--- a/applications/services/expansion/expansion_i.h
+++ /dev/null
@@ -1,6 +0,0 @@
-#pragma once
-
-#include "expansion_settings.h"
-#include "expansion.h"
-
-ExpansionSettings* expansion_get_settings(Expansion* instance);
diff --git a/applications/services/expansion/expansion_settings.c b/applications/services/expansion/expansion_settings.c
index c00b8fe247..274ac74304 100644
--- a/applications/services/expansion/expansion_settings.c
+++ b/applications/services/expansion/expansion_settings.c
@@ -2,33 +2,43 @@
#include
#include
-#include
#include "expansion_settings_filename.h"
+#define TAG "ExpansionSettings"
+
#define EXPANSION_SETTINGS_PATH INT_PATH(EXPANSION_SETTINGS_FILE_NAME)
#define EXPANSION_SETTINGS_VERSION (0)
#define EXPANSION_SETTINGS_MAGIC (0xEA)
-bool expansion_settings_load(ExpansionSettings* settings) {
+void expansion_settings_load(ExpansionSettings* settings) {
furi_assert(settings);
- if(!saved_struct_load(
- EXPANSION_SETTINGS_PATH,
- settings,
- sizeof(ExpansionSettings),
- EXPANSION_SETTINGS_MAGIC,
- EXPANSION_SETTINGS_VERSION)) {
- settings->uart_index = FuriHalSerialIdMax;
+
+ const bool success = saved_struct_load(
+ EXPANSION_SETTINGS_PATH,
+ settings,
+ sizeof(ExpansionSettings),
+ EXPANSION_SETTINGS_MAGIC,
+ EXPANSION_SETTINGS_VERSION);
+
+ if(!success) {
+ FURI_LOG_W(TAG, "Failed to load file, using defaults");
+ memset(settings, 0, sizeof(ExpansionSettings));
+ expansion_settings_save(settings);
}
- return true;
}
-bool expansion_settings_save(const ExpansionSettings* settings) {
+void expansion_settings_save(const ExpansionSettings* settings) {
furi_assert(settings);
- return saved_struct_save(
+
+ const bool success = saved_struct_save(
EXPANSION_SETTINGS_PATH,
settings,
sizeof(ExpansionSettings),
EXPANSION_SETTINGS_MAGIC,
EXPANSION_SETTINGS_VERSION);
+
+ if(!success) {
+ FURI_LOG_E(TAG, "Failed to save file");
+ }
}
diff --git a/applications/services/expansion/expansion_settings.h b/applications/services/expansion/expansion_settings.h
index 38e9f8d025..4594918e33 100644
--- a/applications/services/expansion/expansion_settings.h
+++ b/applications/services/expansion/expansion_settings.h
@@ -25,18 +25,16 @@ typedef struct {
/**
* @brief Load expansion module support settings from file.
*
- * @param[out] settings pointer to an ExpansionSettings instance to load settings into.
- * @returns true if the settings were successfully loaded, false otherwise.
+ * @param[in,out] settings pointer to an ExpansionSettings instance to load settings into.
*/
-bool expansion_settings_load(ExpansionSettings* settings);
+void expansion_settings_load(ExpansionSettings* settings);
/**
* @brief Save expansion module support settings to file.
*
* @param[in] settings pointer to an ExpansionSettings instance to save settings from.
- * @returns true if the settings were successfully saved, false otherwise.
*/
-bool expansion_settings_save(const ExpansionSettings* settings);
+void expansion_settings_save(const ExpansionSettings* settings);
#ifdef __cplusplus
}
diff --git a/applications/services/expansion/expansion_worker.c b/applications/services/expansion/expansion_worker.c
index edc1d09cce..449d02cffc 100644
--- a/applications/services/expansion/expansion_worker.c
+++ b/applications/services/expansion/expansion_worker.c
@@ -223,7 +223,6 @@ static bool expansion_worker_handle_state_handshake(
if(furi_hal_serial_is_baud_rate_supported(instance->serial_handle, baud_rate)) {
instance->state = ExpansionWorkerStateConnected;
- instance->callback(instance->cb_context, ExpansionWorkerCallbackReasonConnected);
// Send response at previous baud rate
if(!expansion_worker_send_status_response(instance, ExpansionFrameErrorNone)) break;
furi_hal_serial_set_br(instance->serial_handle, baud_rate);
@@ -352,7 +351,7 @@ static int32_t expansion_worker(void* context) {
// Do not invoke worker callback on user-requested exit
if((instance->exit_reason != ExpansionWorkerExitReasonUser) && (instance->callback != NULL)) {
- instance->callback(instance->cb_context, ExpansionWorkerCallbackReasonExit);
+ instance->callback(instance->cb_context);
}
return 0;
diff --git a/applications/services/expansion/expansion_worker.h b/applications/services/expansion/expansion_worker.h
index faab2887f3..761f79c1d9 100644
--- a/applications/services/expansion/expansion_worker.h
+++ b/applications/services/expansion/expansion_worker.h
@@ -17,20 +17,14 @@
*/
typedef struct ExpansionWorker ExpansionWorker;
-typedef enum {
- ExpansionWorkerCallbackReasonExit,
- ExpansionWorkerCallbackReasonConnected,
-} ExpansionWorkerCallbackReason;
-
/**
* @brief Worker callback type.
*
* @see expansion_worker_set_callback()
*
* @param[in,out] context pointer to a user-defined object.
- * @param[in] reason reason for the callback.
*/
-typedef void (*ExpansionWorkerCallback)(void* context, ExpansionWorkerCallbackReason reason);
+typedef void (*ExpansionWorkerCallback)(void* context);
/**
* @brief Create an expansion worker instance.
diff --git a/applications/services/gui/application.fam b/applications/services/gui/application.fam
index b7dd18baa1..b24f5bbb6a 100644
--- a/applications/services/gui/application.fam
+++ b/applications/services/gui/application.fam
@@ -19,6 +19,7 @@ App(
"view_holder.h",
"modules/button_menu.h",
"modules/byte_input.h",
+ "modules/number_input.h",
"modules/popup.h",
"modules/text_input.h",
"modules/widget.h",
diff --git a/applications/services/gui/modules/file_browser_worker.c b/applications/services/gui/modules/file_browser_worker.c
index 7092e15da8..78a438ebbb 100644
--- a/applications/services/gui/modules/file_browser_worker.c
+++ b/applications/services/gui/modules/file_browser_worker.c
@@ -15,7 +15,7 @@
#define TAG "BrowserWorker"
#define ASSETS_DIR "assets"
-#define BROWSER_ROOT STORAGE_ANY_PATH_PREFIX
+#define BROWSER_ROOT STORAGE_EXT_PATH_PREFIX
#define FILE_NAME_LEN_MAX 256
#define LONG_LOAD_THRESHOLD 100
@@ -134,7 +134,7 @@ static bool browser_filter_by_name(BrowserWorker* browser, FuriString* name, boo
if((furi_string_empty(ext)) || (furi_string_cmp_str(ext, "*") == 0)) {
return true;
}
- if(furi_string_end_with(name, ext)) {
+ if(furi_string_end_withi(name, ext)) {
return true;
}
}
diff --git a/applications/services/gui/modules/number_input.c b/applications/services/gui/modules/number_input.c
new file mode 100644
index 0000000000..317f22f547
--- /dev/null
+++ b/applications/services/gui/modules/number_input.c
@@ -0,0 +1,449 @@
+#include "number_input.h"
+
+#include
+#include
+#include
+
+struct NumberInput {
+ View* view;
+};
+
+typedef struct {
+ const char text;
+ const size_t x;
+ const size_t y;
+} NumberInputKey;
+
+typedef struct {
+ FuriString* header;
+ FuriString* text_buffer;
+
+ int32_t current_number;
+ int32_t max_value;
+ int32_t min_value;
+
+ NumberInputCallback callback;
+ void* callback_context;
+
+ size_t selected_row;
+ size_t selected_column;
+} NumberInputModel;
+
+static const size_t keyboard_origin_x = 7;
+static const size_t keyboard_origin_y = 31;
+static const size_t keyboard_row_count = 2;
+static const char enter_symbol = '\r';
+static const char backspace_symbol = '\b';
+static const char sign_symbol = '-';
+
+static const NumberInputKey keyboard_keys_row_1[] = {
+ {'0', 0, 12},
+ {'1', 11, 12},
+ {'2', 22, 12},
+ {'3', 33, 12},
+ {'4', 44, 12},
+ {backspace_symbol, 103, 4},
+};
+
+static const NumberInputKey keyboard_keys_row_2[] = {
+ {'5', 0, 26},
+ {'6', 11, 26},
+ {'7', 22, 26},
+ {'8', 33, 26},
+ {'9', 44, 26},
+ {sign_symbol, 55, 17},
+ {enter_symbol, 95, 17},
+};
+
+static size_t number_input_get_row_size(size_t row_index) {
+ size_t row_size = 0;
+
+ switch(row_index + 1) {
+ case 1:
+ row_size = COUNT_OF(keyboard_keys_row_1);
+ break;
+ case 2:
+ row_size = COUNT_OF(keyboard_keys_row_2);
+ break;
+ default:
+ furi_crash();
+ }
+
+ return row_size;
+}
+
+static const NumberInputKey* number_input_get_row(size_t row_index) {
+ const NumberInputKey* row = NULL;
+
+ switch(row_index + 1) {
+ case 1:
+ row = keyboard_keys_row_1;
+ break;
+ case 2:
+ row = keyboard_keys_row_2;
+ break;
+ default:
+ furi_crash();
+ }
+
+ return row;
+}
+
+static void number_input_draw_input(Canvas* canvas, NumberInputModel* model) {
+ const size_t text_x = 8;
+ const size_t text_y = 25;
+
+ elements_slightly_rounded_frame(canvas, 4, 14, 120, 15);
+
+ canvas_draw_str(canvas, text_x, text_y, furi_string_get_cstr(model->text_buffer));
+}
+
+static bool number_input_use_sign(NumberInputModel* model) {
+ //only show sign button if allowed number range needs it
+ if(model->min_value < 0 && model->max_value >= 0) {
+ return true;
+ }
+ return false;
+}
+
+static void number_input_backspace_cb(NumberInputModel* model) {
+ size_t text_length = furi_string_utf8_length(model->text_buffer);
+ if(text_length < 1 || (text_length < 2 && model->current_number <= 0)) {
+ return;
+ }
+ furi_string_set_strn(
+ model->text_buffer, furi_string_get_cstr(model->text_buffer), text_length - 1);
+ model->current_number = strtol(furi_string_get_cstr(model->text_buffer), NULL, 10);
+}
+
+static void number_input_handle_up(NumberInputModel* model) {
+ if(model->selected_row > 0) {
+ model->selected_row--;
+ if(model->selected_column > number_input_get_row_size(model->selected_row) - 1) {
+ model->selected_column = number_input_get_row_size(model->selected_row) - 1;
+ }
+ }
+}
+
+static void number_input_handle_down(NumberInputModel* model) {
+ if(model->selected_row < keyboard_row_count - 1) {
+ if(model->selected_column >= number_input_get_row_size(model->selected_row) - 1) {
+ model->selected_column = number_input_get_row_size(model->selected_row + 1) - 1;
+ }
+ model->selected_row += 1;
+ }
+ const NumberInputKey* keys = number_input_get_row(model->selected_row);
+ if(keys[model->selected_column].text == sign_symbol && !number_input_use_sign(model)) {
+ model->selected_column--;
+ }
+}
+
+static void number_input_handle_left(NumberInputModel* model) {
+ if(model->selected_column > 0) {
+ model->selected_column--;
+ } else {
+ model->selected_column = number_input_get_row_size(model->selected_row) - 1;
+ }
+ const NumberInputKey* keys = number_input_get_row(model->selected_row);
+ if(keys[model->selected_column].text == sign_symbol && !number_input_use_sign(model)) {
+ model->selected_column--;
+ }
+}
+
+static void number_input_handle_right(NumberInputModel* model) {
+ if(model->selected_column < number_input_get_row_size(model->selected_row) - 1) {
+ model->selected_column++;
+ } else {
+ model->selected_column = 0;
+ }
+ const NumberInputKey* keys = number_input_get_row(model->selected_row);
+ if(keys[model->selected_column].text == sign_symbol && !number_input_use_sign(model)) {
+ model->selected_column++;
+ }
+}
+
+static bool is_number_too_large(NumberInputModel* model) {
+ int64_t value = strtoll(furi_string_get_cstr(model->text_buffer), NULL, 10);
+ if(value > (int64_t)model->max_value) {
+ return true;
+ }
+ return false;
+}
+
+static bool is_number_too_small(NumberInputModel* model) {
+ int64_t value = strtoll(furi_string_get_cstr(model->text_buffer), NULL, 10);
+ if(value < (int64_t)model->min_value) {
+ return true;
+ }
+ return false;
+}
+
+static void number_input_sign(NumberInputModel* model) {
+ int32_t number = strtol(furi_string_get_cstr(model->text_buffer), NULL, 10);
+ if(number == 0 && furi_string_cmp_str(model->text_buffer, "-") != 0) {
+ furi_string_set_str(model->text_buffer, "-");
+ return;
+ }
+ number = number * -1;
+ furi_string_printf(model->text_buffer, "%ld", number);
+ if(is_number_too_large(model) || is_number_too_small(model)) {
+ furi_string_printf(model->text_buffer, "%ld", model->current_number);
+ return;
+ }
+ model->current_number = strtol(furi_string_get_cstr(model->text_buffer), NULL, 10);
+ if(model->current_number == 0) {
+ furi_string_set_str(model->text_buffer, ""); //show empty if 0, better for usability
+ }
+}
+
+static void number_input_add_digit(NumberInputModel* model, char* newChar) {
+ furi_string_cat_str(model->text_buffer, newChar);
+ if((model->max_value >= 0 && is_number_too_large(model)) ||
+ (model->min_value < 0 && is_number_too_small(model))) {
+ //you still need to be able to type invalid numbers in some cases to reach valid numbers on later keypress
+ furi_string_printf(model->text_buffer, "%ld", model->current_number);
+ return;
+ }
+ model->current_number = strtol(furi_string_get_cstr(model->text_buffer), NULL, 10);
+ if(model->current_number == 0) {
+ furi_string_set(model->text_buffer, "0");
+ }
+}
+
+static void number_input_handle_ok(NumberInputModel* model) {
+ char selected = number_input_get_row(model->selected_row)[model->selected_column].text;
+ char temp_str[2] = {selected, '\0'};
+ if(selected == enter_symbol) {
+ if(is_number_too_large(model) || is_number_too_small(model)) {
+ return; //Do nothing if number outside allowed range
+ }
+ model->current_number = strtol(furi_string_get_cstr(model->text_buffer), NULL, 10);
+ model->callback(model->callback_context, model->current_number);
+ } else if(selected == backspace_symbol) {
+ number_input_backspace_cb(model);
+ } else if(selected == sign_symbol) {
+ number_input_sign(model);
+ } else {
+ number_input_add_digit(model, temp_str);
+ }
+}
+
+static void number_input_view_draw_callback(Canvas* canvas, void* _model) {
+ NumberInputModel* model = _model;
+
+ number_input_draw_input(canvas, model);
+
+ if(!furi_string_empty(model->header)) {
+ canvas_set_font(canvas, FontSecondary);
+ canvas_draw_str(canvas, 2, 9, furi_string_get_cstr(model->header));
+ }
+ canvas_set_font(canvas, FontKeyboard);
+ // Draw keyboard
+ for(size_t row = 0; row < keyboard_row_count; row++) {
+ const size_t column_count = number_input_get_row_size(row);
+ const NumberInputKey* keys = number_input_get_row(row);
+
+ for(size_t column = 0; column < column_count; column++) {
+ if(keys[column].text == sign_symbol && !number_input_use_sign(model)) {
+ continue;
+ }
+
+ if(keys[column].text == enter_symbol) {
+ if(is_number_too_small(model) || is_number_too_large(model)) {
+ //in some cases you need to be able to type a number smaller/larger than the limits (expl. min = 50, clear all and editor must allow to type 9 and later 0 for 90)
+ if(model->selected_row == row && model->selected_column == column) {
+ canvas_draw_icon(
+ canvas,
+ keyboard_origin_x + keys[column].x,
+ keyboard_origin_y + keys[column].y,
+ &I_KeySaveBlockedSelected_24x11);
+ } else {
+ canvas_draw_icon(
+ canvas,
+ keyboard_origin_x + keys[column].x,
+ keyboard_origin_y + keys[column].y,
+ &I_KeySaveBlocked_24x11);
+ }
+ } else {
+ if(model->selected_row == row && model->selected_column == column) {
+ canvas_draw_icon(
+ canvas,
+ keyboard_origin_x + keys[column].x,
+ keyboard_origin_y + keys[column].y,
+ &I_KeySaveSelected_24x11);
+ } else {
+ canvas_draw_icon(
+ canvas,
+ keyboard_origin_x + keys[column].x,
+ keyboard_origin_y + keys[column].y,
+ &I_KeySave_24x11);
+ }
+ }
+ } else if(keys[column].text == backspace_symbol) {
+ if(model->selected_row == row && model->selected_column == column) {
+ canvas_draw_icon(
+ canvas,
+ keyboard_origin_x + keys[column].x,
+ keyboard_origin_y + keys[column].y,
+ &I_KeyBackspaceSelected_16x9);
+ } else {
+ canvas_draw_icon(
+ canvas,
+ keyboard_origin_x + keys[column].x,
+ keyboard_origin_y + keys[column].y,
+ &I_KeyBackspace_16x9);
+ }
+ } else if(keys[column].text == sign_symbol) {
+ if(model->selected_row == row && model->selected_column == column) {
+ canvas_draw_icon(
+ canvas,
+ keyboard_origin_x + keys[column].x,
+ keyboard_origin_y + keys[column].y,
+ &I_KeySignSelected_21x11);
+ } else {
+ canvas_draw_icon(
+ canvas,
+ keyboard_origin_x + keys[column].x,
+ keyboard_origin_y + keys[column].y,
+ &I_KeySign_21x11);
+ }
+ } else {
+ if(model->selected_row == row && model->selected_column == column) {
+ canvas_draw_box(
+ canvas,
+ keyboard_origin_x + keys[column].x - 3,
+ keyboard_origin_y + keys[column].y - 10,
+ 11,
+ 13);
+ canvas_set_color(canvas, ColorWhite);
+ }
+
+ canvas_draw_glyph(
+ canvas,
+ keyboard_origin_x + keys[column].x,
+ keyboard_origin_y + keys[column].y,
+ keys[column].text);
+ canvas_set_color(canvas, ColorBlack);
+ }
+ }
+ }
+}
+
+static bool number_input_view_input_callback(InputEvent* event, void* context) {
+ furi_assert(context);
+ NumberInput* number_input = context;
+
+ bool consumed = false;
+
+ // Fetch the model
+ NumberInputModel* model = view_get_model(number_input->view);
+
+ if(event->type == InputTypeShort || event->type == InputTypeLong ||
+ event->type == InputTypeRepeat) {
+ consumed = true;
+ switch(event->key) {
+ case InputKeyLeft:
+ number_input_handle_left(model);
+ break;
+ case InputKeyRight:
+ number_input_handle_right(model);
+ break;
+ case InputKeyUp:
+ number_input_handle_up(model);
+ break;
+ case InputKeyDown:
+ number_input_handle_down(model);
+ break;
+ case InputKeyOk:
+ number_input_handle_ok(model);
+ break;
+ default:
+ consumed = false;
+ break;
+ }
+ }
+
+ // commit view
+ view_commit_model(number_input->view, consumed);
+
+ return consumed;
+}
+
+NumberInput* number_input_alloc(void) {
+ NumberInput* number_input = malloc(sizeof(NumberInput));
+ number_input->view = view_alloc();
+ view_set_context(number_input->view, number_input);
+ view_allocate_model(number_input->view, ViewModelTypeLocking, sizeof(NumberInputModel));
+ view_set_draw_callback(number_input->view, number_input_view_draw_callback);
+ view_set_input_callback(number_input->view, number_input_view_input_callback);
+
+ with_view_model(
+ number_input->view,
+ NumberInputModel * model,
+ {
+ model->header = furi_string_alloc();
+ model->text_buffer = furi_string_alloc();
+ },
+ true);
+
+ return number_input;
+}
+
+void number_input_free(NumberInput* number_input) {
+ furi_check(number_input);
+ with_view_model(
+ number_input->view,
+ NumberInputModel * model,
+ {
+ furi_string_free(model->header);
+ furi_string_free(model->text_buffer);
+ },
+ true);
+ view_free(number_input->view);
+ free(number_input);
+}
+
+View* number_input_get_view(NumberInput* number_input) {
+ furi_check(number_input);
+ return number_input->view;
+}
+
+void number_input_set_result_callback(
+ NumberInput* number_input,
+ NumberInputCallback callback,
+ void* callback_context,
+ int32_t current_number,
+ int32_t min_value,
+ int32_t max_value) {
+ furi_check(number_input);
+
+ if(current_number != 0) {
+ current_number = CLAMP(current_number, max_value, min_value);
+ }
+
+ with_view_model(
+ number_input->view,
+ NumberInputModel * model,
+ {
+ model->callback = callback;
+ model->callback_context = callback_context;
+ model->current_number = current_number;
+ if(current_number != 0) {
+ furi_string_printf(model->text_buffer, "%ld", current_number);
+ } else {
+ furi_string_set(model->text_buffer, "");
+ }
+ model->min_value = min_value;
+ model->max_value = max_value;
+ },
+ true);
+}
+
+void number_input_set_header_text(NumberInput* number_input, const char* text) {
+ furi_check(number_input);
+ with_view_model(
+ number_input->view,
+ NumberInputModel * model,
+ { furi_string_set(model->header, text); },
+ true);
+}
diff --git a/applications/services/gui/modules/number_input.h b/applications/services/gui/modules/number_input.h
new file mode 100644
index 0000000000..80e631e9bd
--- /dev/null
+++ b/applications/services/gui/modules/number_input.h
@@ -0,0 +1,69 @@
+/**
+ * @file number_input.h
+ * GUI: Integer string keyboard view module API
+ */
+
+#pragma once
+
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Number input anonymous structure */
+typedef struct NumberInput NumberInput;
+
+/** Callback to be called on save button press */
+typedef void (*NumberInputCallback)(void* context, int32_t number);
+
+/** Allocate and initialize Number input.
+ *
+ * This Number input is used to enter Numbers (Integers).
+ *
+ * @return NumberInput instance pointer
+ */
+NumberInput* number_input_alloc(void);
+
+/** Deinitialize and free byte input
+ *
+ * @param number_input Number input instance
+ */
+void number_input_free(NumberInput* number_input);
+
+/** Get byte input view
+ *
+ * @param number_input byte input instance
+ *
+ * @return View instance that can be used for embedding
+ */
+View* number_input_get_view(NumberInput* number_input);
+
+/** Set byte input result callback
+ *
+ * @param number_input byte input instance
+ * @param input_callback input callback fn
+ * @param callback_context callback context
+ * @param[in] current_number The current number
+ * @param min_value Min number value
+ * @param max_value Max number value
+ */
+
+void number_input_set_result_callback(
+ NumberInput* number_input,
+ NumberInputCallback input_callback,
+ void* callback_context,
+ int32_t current_number,
+ int32_t min_value,
+ int32_t max_value);
+
+/** Set byte input header text
+ *
+ * @param number_input byte input instance
+ * @param text text to be shown
+ */
+void number_input_set_header_text(NumberInput* number_input, const char* text);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/applications/services/gui/view_dispatcher.c b/applications/services/gui/view_dispatcher.c
index b4c534932f..63878fc190 100644
--- a/applications/services/gui/view_dispatcher.c
+++ b/applications/services/gui/view_dispatcher.c
@@ -2,6 +2,8 @@
#define TAG "ViewDispatcher"
+#define VIEW_DISPATCHER_QUEUE_LEN (16U)
+
ViewDispatcher* view_dispatcher_alloc(void) {
ViewDispatcher* view_dispatcher = malloc(sizeof(ViewDispatcher));
@@ -14,6 +16,26 @@ ViewDispatcher* view_dispatcher_alloc(void) {
ViewDict_init(view_dispatcher->views);
+ view_dispatcher->event_loop = furi_event_loop_alloc();
+
+ view_dispatcher->input_queue =
+ furi_message_queue_alloc(VIEW_DISPATCHER_QUEUE_LEN, sizeof(InputEvent));
+ furi_event_loop_subscribe_message_queue(
+ view_dispatcher->event_loop,
+ view_dispatcher->input_queue,
+ FuriEventLoopEventIn,
+ view_dispatcher_run_input_callback,
+ view_dispatcher);
+
+ view_dispatcher->event_queue =
+ furi_message_queue_alloc(VIEW_DISPATCHER_QUEUE_LEN, sizeof(uint32_t));
+ furi_event_loop_subscribe_message_queue(
+ view_dispatcher->event_loop,
+ view_dispatcher->event_queue,
+ FuriEventLoopEventIn,
+ view_dispatcher_run_event_callback,
+ view_dispatcher);
+
return view_dispatcher;
}
@@ -29,44 +51,19 @@ void view_dispatcher_free(ViewDispatcher* view_dispatcher) {
// Free ViewPort
view_port_free(view_dispatcher->view_port);
// Free internal queue
- if(view_dispatcher->input_queue) {
- furi_event_loop_message_queue_unsubscribe(
- view_dispatcher->event_loop, view_dispatcher->input_queue);
- furi_message_queue_free(view_dispatcher->input_queue);
- }
- if(view_dispatcher->event_queue) {
- furi_event_loop_message_queue_unsubscribe(
- view_dispatcher->event_loop, view_dispatcher->event_queue);
- furi_message_queue_free(view_dispatcher->event_queue);
- }
- if(view_dispatcher->event_loop) {
- furi_event_loop_free(view_dispatcher->event_loop);
- }
+ furi_event_loop_unsubscribe(view_dispatcher->event_loop, view_dispatcher->input_queue);
+ furi_event_loop_unsubscribe(view_dispatcher->event_loop, view_dispatcher->event_queue);
+
+ furi_message_queue_free(view_dispatcher->input_queue);
+ furi_message_queue_free(view_dispatcher->event_queue);
+
+ furi_event_loop_free(view_dispatcher->event_loop);
// Free dispatcher
free(view_dispatcher);
}
void view_dispatcher_enable_queue(ViewDispatcher* view_dispatcher) {
- furi_check(view_dispatcher);
- furi_check(view_dispatcher->event_loop == NULL);
-
- view_dispatcher->event_loop = furi_event_loop_alloc();
-
- view_dispatcher->input_queue = furi_message_queue_alloc(16, sizeof(InputEvent));
- furi_event_loop_message_queue_subscribe(
- view_dispatcher->event_loop,
- view_dispatcher->input_queue,
- FuriEventLoopEventIn,
- view_dispatcher_run_input_callback,
- view_dispatcher);
-
- view_dispatcher->event_queue = furi_message_queue_alloc(16, sizeof(uint32_t));
- furi_event_loop_message_queue_subscribe(
- view_dispatcher->event_loop,
- view_dispatcher->event_queue,
- FuriEventLoopEventIn,
- view_dispatcher_run_event_callback,
- view_dispatcher);
+ UNUSED(view_dispatcher);
}
void view_dispatcher_set_navigation_event_callback(
@@ -99,14 +96,12 @@ void view_dispatcher_set_event_callback_context(ViewDispatcher* view_dispatcher,
FuriEventLoop* view_dispatcher_get_event_loop(ViewDispatcher* view_dispatcher) {
furi_check(view_dispatcher);
- furi_check(view_dispatcher->event_loop);
return view_dispatcher->event_loop;
}
void view_dispatcher_run(ViewDispatcher* view_dispatcher) {
furi_check(view_dispatcher);
- furi_check(view_dispatcher->event_loop);
uint32_t tick_period = view_dispatcher->tick_period == 0 ? FuriWaitForever :
view_dispatcher->tick_period;
@@ -134,7 +129,6 @@ void view_dispatcher_run(ViewDispatcher* view_dispatcher) {
void view_dispatcher_stop(ViewDispatcher* view_dispatcher) {
furi_check(view_dispatcher);
- furi_check(view_dispatcher->event_loop);
furi_event_loop_stop(view_dispatcher->event_loop);
}
@@ -242,13 +236,9 @@ void view_dispatcher_draw_callback(Canvas* canvas, void* context) {
void view_dispatcher_input_callback(InputEvent* event, void* context) {
ViewDispatcher* view_dispatcher = context;
- if(view_dispatcher->input_queue) {
- furi_check(
- furi_message_queue_put(view_dispatcher->input_queue, event, FuriWaitForever) ==
- FuriStatusOk);
- } else {
- view_dispatcher_handle_input(view_dispatcher, event);
- }
+ furi_check(
+ furi_message_queue_put(view_dispatcher->input_queue, event, FuriWaitForever) ==
+ FuriStatusOk);
}
void view_dispatcher_handle_input(ViewDispatcher* view_dispatcher, InputEvent* event) {
@@ -328,7 +318,6 @@ void view_dispatcher_handle_custom_event(ViewDispatcher* view_dispatcher, uint32
void view_dispatcher_send_custom_event(ViewDispatcher* view_dispatcher, uint32_t event) {
furi_check(view_dispatcher);
- furi_check(view_dispatcher->event_loop);
furi_check(
furi_message_queue_put(view_dispatcher->event_queue, &event, FuriWaitForever) ==
@@ -364,9 +353,7 @@ void view_dispatcher_set_current_view(ViewDispatcher* view_dispatcher, View* vie
view_port_update(view_dispatcher->view_port);
} else {
view_port_enabled_set(view_dispatcher->view_port, false);
- if(view_dispatcher->event_loop) {
- view_dispatcher_stop(view_dispatcher);
- }
+ view_dispatcher_stop(view_dispatcher);
}
}
@@ -381,10 +368,10 @@ void view_dispatcher_update(View* view, void* context) {
}
}
-bool view_dispatcher_run_event_callback(FuriMessageQueue* queue, void* context) {
+bool view_dispatcher_run_event_callback(FuriEventLoopObject* object, void* context) {
furi_assert(context);
ViewDispatcher* instance = context;
- furi_assert(instance->event_queue == queue);
+ furi_assert(instance->event_queue == object);
uint32_t event;
furi_check(furi_message_queue_get(instance->event_queue, &event, 0) == FuriStatusOk);
@@ -393,10 +380,10 @@ bool view_dispatcher_run_event_callback(FuriMessageQueue* queue, void* context)
return true;
}
-bool view_dispatcher_run_input_callback(FuriMessageQueue* queue, void* context) {
+bool view_dispatcher_run_input_callback(FuriEventLoopObject* object, void* context) {
furi_assert(context);
ViewDispatcher* instance = context;
- furi_assert(instance->input_queue == queue);
+ furi_assert(instance->input_queue == object);
InputEvent input;
furi_check(furi_message_queue_get(instance->input_queue, &input, 0) == FuriStatusOk);
diff --git a/applications/services/gui/view_dispatcher.h b/applications/services/gui/view_dispatcher.h
index 905c60975b..9fbf897918 100644
--- a/applications/services/gui/view_dispatcher.h
+++ b/applications/services/gui/view_dispatcher.h
@@ -2,6 +2,14 @@
* @file view_dispatcher.h
* @brief GUI: ViewDispatcher API
*
+ * ViewDispatcher is used to connect several Views to a Gui instance, switch between them and handle various events.
+ * This is useful in applications featuring an advanced graphical user interface.
+ *
+ * Internally, ViewDispatcher employs a FuriEventLoop instance together with two separate
+ * message queues for input and custom event handling. See FuriEventLoop for more information.
+ *
+ * If no multi-view or complex event handling capabilities are required, consider using ViewHolder instead.
+ *
* @warning Views added to a ViewDispatcher MUST NOT be in a ViewStack at the same time.
*/
@@ -40,6 +48,9 @@ typedef void (*ViewDispatcherTickEventCallback)(void* context);
ViewDispatcher* view_dispatcher_alloc(void);
/** Free ViewDispatcher instance
+ *
+ * @warning All added views MUST be removed using view_dispatcher_remove_view()
+ * before calling this function.
*
* @param view_dispatcher pointer to ViewDispatcher
*/
@@ -47,12 +58,13 @@ void view_dispatcher_free(ViewDispatcher* view_dispatcher);
/** Enable queue support
*
- * Allocates event_loop, input and event message queues. Must be used with
- * `view_dispatcher_run`
+ * @deprecated Do NOT use in new code and remove all calls to it from existing code.
+ * The queue support is now always enabled during construction. If no queue support
+ * is required, consider using ViewHolder instead.
*
* @param view_dispatcher ViewDispatcher instance
*/
-void view_dispatcher_enable_queue(ViewDispatcher* view_dispatcher);
+FURI_DEPRECATED void view_dispatcher_enable_queue(ViewDispatcher* view_dispatcher);
/** Send custom event
*
@@ -103,11 +115,11 @@ void view_dispatcher_set_event_callback_context(ViewDispatcher* view_dispatcher,
/** Get event_loop instance
*
- * event_loop instance is allocated on `view_dispatcher_enable_queue` and used
- * in view_dispatcher_run.
+ * Use the return value to connect additional supported primitives (message queues, timers, etc)
+ * to this ViewDispatcher instance's event loop.
*
- * You can add your objects into event_loop instance, but don't run the loop on
- * your side as it will cause issues with input processing on dispatcher stop.
+ * @warning Do NOT call furi_event_loop_run() on the returned instance, it is done internally
+ * in the view_dispatcher_run() call.
*
* @param view_dispatcher ViewDispatcher instance
*
@@ -117,15 +129,14 @@ FuriEventLoop* view_dispatcher_get_event_loop(ViewDispatcher* view_dispatcher);
/** Run ViewDispatcher
*
- * Use only after queue enabled
+ * This function will start the event loop and block until view_dispatcher_stop() is called
+ * or the current thread receives a FuriSignalExit signal.
*
* @param view_dispatcher ViewDispatcher instance
*/
void view_dispatcher_run(ViewDispatcher* view_dispatcher);
/** Stop ViewDispatcher
- *
- * Use only after queue enabled
*
* @param view_dispatcher ViewDispatcher instance
*/
diff --git a/applications/services/gui/view_dispatcher_i.h b/applications/services/gui/view_dispatcher_i.h
index 46a4ac7fa7..c6c8dc665c 100644
--- a/applications/services/gui/view_dispatcher_i.h
+++ b/applications/services/gui/view_dispatcher_i.h
@@ -56,7 +56,7 @@ void view_dispatcher_set_current_view(ViewDispatcher* view_dispatcher, View* vie
void view_dispatcher_update(View* view, void* context);
/** ViewDispatcher run event loop event callback */
-bool view_dispatcher_run_event_callback(FuriMessageQueue* queue, void* context);
+bool view_dispatcher_run_event_callback(FuriEventLoopObject* object, void* context);
/** ViewDispatcher run event loop input callback */
-bool view_dispatcher_run_input_callback(FuriMessageQueue* queue, void* context);
+bool view_dispatcher_run_input_callback(FuriEventLoopObject* object, void* context);
diff --git a/applications/services/gui/view_holder.c b/applications/services/gui/view_holder.c
index ca2f9b04e1..7d8b5e17c3 100644
--- a/applications/services/gui/view_holder.c
+++ b/applications/services/gui/view_holder.c
@@ -32,7 +32,8 @@ ViewHolder* view_holder_alloc(void) {
}
void view_holder_free(ViewHolder* view_holder) {
- furi_assert(view_holder);
+ furi_check(view_holder);
+ furi_check(view_holder->view == NULL);
if(view_holder->gui) {
gui_remove_view_port(view_holder->gui, view_holder->view_port);
@@ -48,12 +49,14 @@ void view_holder_free(ViewHolder* view_holder) {
}
void view_holder_set_view(ViewHolder* view_holder, View* view) {
- furi_assert(view_holder);
+ furi_check(view_holder);
+
if(view_holder->view) {
- if(view_holder->view->exit_callback) {
- view_holder->view->exit_callback(view_holder->view->context);
+ while(view_holder->ongoing_input) {
+ furi_delay_tick(1);
}
+ view_exit(view_holder->view);
view_set_update_callback(view_holder->view, NULL);
view_set_update_callback_context(view_holder->view, NULL);
}
@@ -61,12 +64,23 @@ void view_holder_set_view(ViewHolder* view_holder, View* view) {
view_holder->view = view;
if(view_holder->view) {
+ const ViewPortOrientation orientation = (ViewPortOrientation)view->orientation;
+ furi_assert(orientation < ViewPortOrientationMAX);
+ if(view_port_get_orientation(view_holder->view_port) != orientation) {
+ view_port_set_orientation(view_holder->view_port, orientation);
+ // we just rotated input keys, now it's time to sacrifice some input
+ view_holder->ongoing_input = 0;
+ }
+
view_set_update_callback(view_holder->view, view_holder_update);
view_set_update_callback_context(view_holder->view, view_holder);
- if(view_holder->view->enter_callback) {
- view_holder->view->enter_callback(view_holder->view->context);
- }
+ view_enter(view_holder->view);
+ view_port_enabled_set(view_holder->view_port, true);
+ view_port_update(view_holder->view_port);
+
+ } else {
+ view_port_enabled_set(view_holder->view_port, false);
}
}
@@ -74,7 +88,7 @@ void view_holder_set_free_callback(
ViewHolder* view_holder,
FreeCallback free_callback,
void* free_context) {
- furi_assert(view_holder);
+ furi_check(view_holder);
view_holder->free_callback = free_callback;
view_holder->free_context = free_context;
}
@@ -87,31 +101,22 @@ void view_holder_set_back_callback(
ViewHolder* view_holder,
BackCallback back_callback,
void* back_context) {
- furi_assert(view_holder);
+ furi_check(view_holder);
view_holder->back_callback = back_callback;
view_holder->back_context = back_context;
}
void view_holder_attach_to_gui(ViewHolder* view_holder, Gui* gui) {
- furi_assert(gui);
- furi_assert(view_holder);
- view_holder->gui = gui;
+ furi_check(view_holder);
+ furi_check(view_holder->gui == NULL);
+ furi_check(gui);
gui_add_view_port(gui, view_holder->view_port, GuiLayerFullscreen);
-}
-
-void view_holder_start(ViewHolder* view_holder) {
- view_port_enabled_set(view_holder->view_port, true);
-}
-
-void view_holder_stop(ViewHolder* view_holder) {
- while(view_holder->ongoing_input)
- furi_delay_tick(1);
- view_port_enabled_set(view_holder->view_port, false);
+ view_holder->gui = gui;
}
void view_holder_update(View* view, void* context) {
- furi_assert(view);
- furi_assert(context);
+ furi_check(view);
+ furi_check(context);
ViewHolder* view_holder = context;
if(view == view_holder->view) {
@@ -119,6 +124,18 @@ void view_holder_update(View* view, void* context) {
}
}
+void view_holder_send_to_front(ViewHolder* view_holder) {
+ furi_check(view_holder);
+ furi_check(view_holder->gui);
+ gui_view_port_send_to_front(view_holder->gui, view_holder->view_port);
+}
+
+void view_holder_send_to_back(ViewHolder* view_holder) {
+ furi_check(view_holder);
+ furi_check(view_holder->gui);
+ gui_view_port_send_to_back(view_holder->gui, view_holder->view_port);
+}
+
static void view_holder_draw_callback(Canvas* canvas, void* context) {
ViewHolder* view_holder = context;
if(view_holder->view) {
diff --git a/applications/services/gui/view_holder.h b/applications/services/gui/view_holder.h
index 90ce82b377..78dbfda0ec 100644
--- a/applications/services/gui/view_holder.h
+++ b/applications/services/gui/view_holder.h
@@ -2,7 +2,10 @@
* @file view_holder.h
* @brief GUI: ViewHolder API
*
- * @warning View added to a ViewHolder MUST NOT be in a ViewStack at the same time.
+ * ViewHolder is used to connect a single View to a Gui instance. This is useful in smaller applications
+ * with a simple user interface. If advanced view switching capabilites are required, consider using ViewDispatcher instead.
+ *
+ * @warning Views added to a ViewHolder MUST NOT be in a ViewStack at the same time.
*/
#pragma once
@@ -22,7 +25,8 @@ typedef void (*FreeCallback)(void* free_context);
/**
* @brief Back callback type
- * @warning comes from GUI thread
+ *
+ * @warning Will be called from the GUI thread
*/
typedef void (*BackCallback)(void* back_context);
@@ -34,12 +38,17 @@ ViewHolder* view_holder_alloc(void);
/**
* @brief Free ViewHolder and call Free callback
+ *
+ * @warning The current view must be unset prior to freeing a ViewHolder instance.
+ *
* @param view_holder pointer to ViewHolder
*/
void view_holder_free(ViewHolder* view_holder);
/**
* @brief Set view for ViewHolder
+ *
+ * Pass NULL as the view parameter to unset the current view.
*
* @param view_holder ViewHolder instance
* @param view View instance
@@ -59,13 +68,25 @@ void view_holder_set_free_callback(
void* free_context);
/**
- * @brief Free callback context getter. Useful if your Free callback is a module destructor, so you can get an instance of the module using this method.
+ * @brief Free callback context getter.
+ *
+ * Useful if your Free callback is a module destructor, so you can get an instance of the module using this method.
*
* @param view_holder ViewHolder instance
* @return void* free callback context
*/
void* view_holder_get_free_context(ViewHolder* view_holder);
+/**
+ * @brief Set the back key callback.
+ *
+ * The callback function will be called if the user has pressed the Back key
+ * and the current view did not handle this event.
+ *
+ * @param view_holder ViewHolder instance
+ * @param back_callback pointer to the callback function
+ * @param back_context pointer to a user-specific object, can be NULL
+ */
void view_holder_set_back_callback(
ViewHolder* view_holder,
BackCallback back_callback,
@@ -80,25 +101,26 @@ void view_holder_set_back_callback(
void view_holder_attach_to_gui(ViewHolder* view_holder, Gui* gui);
/**
- * @brief Enable view processing
- *
- * @param view_holder
+ * @brief View Update Handler
+ *
+ * @param view View Instance
+ * @param context ViewHolder instance
*/
-void view_holder_start(ViewHolder* view_holder);
+void view_holder_update(View* view, void* context);
/**
- * @brief Disable view processing
- *
- * @param view_holder
+ * @brief Send ViewPort of this ViewHolder instance to front
+ *
+ * @param view_holder ViewHolder instance
*/
-void view_holder_stop(ViewHolder* view_holder);
+void view_holder_send_to_front(ViewHolder* view_holder);
-/** View Update Handler
+/**
+ * @brief Send ViewPort of this ViewHolder instance to back
*
- * @param view View Instance
- * @param context ViewHolder instance
+ * @param view_holder ViewHolder instance
*/
-void view_holder_update(View* view, void* context);
+void view_holder_send_to_back(ViewHolder* view_holder);
#ifdef __cplusplus
}
diff --git a/applications/services/loader/loader_applications.c b/applications/services/loader/loader_applications.c
index 232e5314e9..5399ba26fc 100644
--- a/applications/services/loader/loader_applications.c
+++ b/applications/services/loader/loader_applications.c
@@ -61,7 +61,6 @@ static LoaderApplicationsApp* loader_applications_app_alloc(void) {
app->loading = loading_alloc();
view_holder_attach_to_gui(app->view_holder, app->gui);
- view_holder_set_view(app->view_holder, loading_get_view(app->loading));
return app;
} //-V773
@@ -149,7 +148,7 @@ static int32_t loader_applications_thread(void* p) {
LoaderApplicationsApp* app = loader_applications_app_alloc();
// start loading animation
- view_holder_start(app->view_holder);
+ view_holder_set_view(app->view_holder, loading_get_view(app->loading));
while(loader_applications_select_app(app)) {
if(!furi_string_end_with(app->file_path, ".js")) {
@@ -161,7 +160,7 @@ static int32_t loader_applications_thread(void* p) {
}
// stop loading animation
- view_holder_stop(app->view_holder);
+ view_holder_set_view(app->view_holder, NULL);
loader_applications_app_free(app);
diff --git a/applications/services/loader/loader_menu.c b/applications/services/loader/loader_menu.c
index 0ee3cada2d..ad4a4c7d59 100644
--- a/applications/services/loader/loader_menu.c
+++ b/applications/services/loader/loader_menu.c
@@ -160,8 +160,6 @@ static LoaderMenuApp* loader_menu_app_alloc(LoaderMenu* loader_menu) {
view_set_context(settings_view, app->settings_menu);
view_set_previous_callback(settings_view, loader_menu_switch_to_primary);
view_dispatcher_add_view(app->view_dispatcher, LoaderMenuViewSettings, settings_view);
-
- view_dispatcher_enable_queue(app->view_dispatcher);
view_dispatcher_switch_to_view(app->view_dispatcher, LoaderMenuViewPrimary);
return app;
diff --git a/applications/services/notification/notification_app.c b/applications/services/notification/notification_app.c
index d4c5b91c8c..35d2fe675a 100644
--- a/applications/services/notification/notification_app.c
+++ b/applications/services/notification/notification_app.c
@@ -438,7 +438,7 @@ static bool notification_load_settings(NotificationApp* app) {
File* file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
const size_t settings_size = sizeof(NotificationSettings);
- FURI_LOG_I(TAG, "loading settings from \"%s\"", NOTIFICATION_SETTINGS_PATH);
+ FURI_LOG_I(TAG, "Loading \"%s\"", NOTIFICATION_SETTINGS_PATH);
bool fs_result =
storage_file_open(file, NOTIFICATION_SETTINGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING);
@@ -451,8 +451,6 @@ static bool notification_load_settings(NotificationApp* app) {
}
if(fs_result) {
- FURI_LOG_I(TAG, "load success");
-
if(settings.version != NOTIFICATION_SETTINGS_VERSION) {
FURI_LOG_E(
TAG, "version(%d != %d) mismatch", settings.version, NOTIFICATION_SETTINGS_VERSION);
@@ -462,7 +460,7 @@ static bool notification_load_settings(NotificationApp* app) {
furi_kernel_unlock();
}
} else {
- FURI_LOG_E(TAG, "load failed, %s", storage_file_get_error_desc(file));
+ FURI_LOG_E(TAG, "Load failed, %s", storage_file_get_error_desc(file));
}
storage_file_close(file);
@@ -477,7 +475,7 @@ static bool notification_save_settings(NotificationApp* app) {
File* file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
const size_t settings_size = sizeof(NotificationSettings);
- FURI_LOG_I(TAG, "saving settings to \"%s\"", NOTIFICATION_SETTINGS_PATH);
+ FURI_LOG_I(TAG, "Saving \"%s\"", NOTIFICATION_SETTINGS_PATH);
furi_kernel_lock();
memcpy(&settings, &app->settings, settings_size);
@@ -495,9 +493,8 @@ static bool notification_save_settings(NotificationApp* app) {
}
if(fs_result) {
- FURI_LOG_I(TAG, "save success");
} else {
- FURI_LOG_E(TAG, "save failed, %s", storage_file_get_error_desc(file));
+ FURI_LOG_E(TAG, "Save failed, %s", storage_file_get_error_desc(file));
}
storage_file_close(file);
@@ -556,14 +553,46 @@ static NotificationApp* notification_app_alloc(void) {
return app;
}
+static void notification_storage_callback(const void* message, void* context) {
+ furi_assert(context);
+ NotificationApp* app = context;
+ const StorageEvent* event = message;
+
+ if(event->type == StorageEventTypeCardMount) {
+ NotificationAppMessage m = {
+ .type = LoadSettingsMessage,
+ };
+
+ furi_check(furi_message_queue_put(app->queue, &m, FuriWaitForever) == FuriStatusOk);
+ }
+}
+
+static void notification_apply_settings(NotificationApp* app) {
+ if(!notification_load_settings(app)) {
+ notification_save_settings(app);
+ }
+
+ notification_apply_lcd_contrast(app);
+}
+
+static void notification_init_settings(NotificationApp* app) {
+ Storage* storage = furi_record_open(RECORD_STORAGE);
+ furi_pubsub_subscribe(storage_get_pubsub(storage), notification_storage_callback, app);
+
+ if(storage_sd_status(storage) != FSE_OK) {
+ FURI_LOG_D(TAG, "SD Card not ready, skipping settings");
+ return;
+ }
+
+ notification_apply_settings(app);
+}
+
// App
int32_t notification_srv(void* p) {
UNUSED(p);
NotificationApp* app = notification_app_alloc();
- if(!notification_load_settings(app)) {
- notification_save_settings(app);
- }
+ notification_init_settings(app);
notification_vibro_off();
notification_sound_off();
@@ -571,7 +600,6 @@ int32_t notification_srv(void* p) {
notification_apply_internal_led_layer(&app->led[0], 0x00);
notification_apply_internal_led_layer(&app->led[1], 0x00);
notification_apply_internal_led_layer(&app->led[2], 0x00);
- notification_apply_lcd_contrast(app);
furi_record_create(RECORD_NOTIFICATION, app);
@@ -589,6 +617,9 @@ int32_t notification_srv(void* p) {
case SaveSettingsMessage:
notification_save_settings(app);
break;
+ case LoadSettingsMessage:
+ notification_load_settings(app);
+ break;
}
if(message.back_event != NULL) {
diff --git a/applications/services/notification/notification_app.h b/applications/services/notification/notification_app.h
index 434773f2e5..e195465741 100644
--- a/applications/services/notification/notification_app.h
+++ b/applications/services/notification/notification_app.h
@@ -11,6 +11,7 @@ typedef enum {
NotificationLayerMessage,
InternalLayerMessage,
SaveSettingsMessage,
+ LoadSettingsMessage,
} NotificationAppMessageType;
typedef struct {
diff --git a/applications/services/power/power_cli.c b/applications/services/power/power_cli.c
index 6e1e34e67e..93d0f232ac 100644
--- a/applications/services/power/power_cli.c
+++ b/applications/services/power/power_cli.c
@@ -17,13 +17,15 @@ void power_cli_off(Cli* cli, FuriString* args) {
void power_cli_reboot(Cli* cli, FuriString* args) {
UNUSED(cli);
UNUSED(args);
- power_reboot(PowerBootModeNormal);
+ Power* power = furi_record_open(RECORD_POWER);
+ power_reboot(power, PowerBootModeNormal);
}
void power_cli_reboot2dfu(Cli* cli, FuriString* args) {
UNUSED(cli);
UNUSED(args);
- power_reboot(PowerBootModeDfu);
+ Power* power = furi_record_open(RECORD_POWER);
+ power_reboot(power, PowerBootModeDfu);
}
void power_cli_5v(Cli* cli, FuriString* args) {
diff --git a/applications/services/power/power_service/power.c b/applications/services/power/power_service/power.c
index 636a2bc983..2773e9fe80 100644
--- a/applications/services/power/power_service/power.c
+++ b/applications/services/power/power_service/power.c
@@ -4,10 +4,18 @@
#include
#include
-#define POWER_OFF_TIMEOUT 90
-#define TAG "Power"
+#include
+#include
-void power_draw_battery_callback(Canvas* canvas, void* context) {
+#define TAG "Power"
+
+#define POWER_OFF_TIMEOUT_S (90U)
+#define POWER_POLL_PERIOD_MS (1000UL)
+
+#define POWER_VBUS_LOW_THRESHOLD (4.0f)
+#define POWER_HEALTH_LOW_THRESHOLD (70U)
+
+static void power_draw_battery_callback(Canvas* canvas, void* context) {
furi_assert(context);
Power* power = context;
canvas_draw_icon(canvas, 0, 0, &I_Battery_26x8);
@@ -219,6 +227,7 @@ void power_draw_battery_callback(Canvas* canvas, void* context) {
}
canvas_set_bitmap_mode(canvas, 0);
}
+
} else {
canvas_draw_box(canvas, 8, 3, 8, 2);
}
@@ -228,99 +237,61 @@ static ViewPort* power_battery_view_port_alloc(Power* power) {
ViewPort* battery_view_port = view_port_alloc();
view_port_set_width(battery_view_port, icon_get_width(&I_Battery_26x8));
view_port_draw_callback_set(battery_view_port, power_draw_battery_callback, power);
- gui_add_view_port(power->gui, battery_view_port, GuiLayerStatusBarRight);
return battery_view_port;
}
-Power* power_alloc(void) {
- Power* power = malloc(sizeof(Power));
-
- // Records
- power->notification = furi_record_open(RECORD_NOTIFICATION);
- power->gui = furi_record_open(RECORD_GUI);
-
- // Pubsub
- power->event_pubsub = furi_pubsub_alloc();
-
- // State initialization
- power->state = PowerStateNotCharging;
- power->battery_low = false;
- power->power_off_timeout = POWER_OFF_TIMEOUT;
- power->api_mtx = furi_mutex_alloc(FuriMutexTypeNormal);
-
- // Gui
- power->view_dispatcher = view_dispatcher_alloc();
- power->power_off = power_off_alloc();
- view_dispatcher_add_view(
- power->view_dispatcher, PowerViewOff, power_off_get_view(power->power_off));
- power->power_unplug_usb = power_unplug_usb_alloc();
- view_dispatcher_add_view(
- power->view_dispatcher,
- PowerViewUnplugUsb,
- power_unplug_usb_get_view(power->power_unplug_usb));
- view_dispatcher_attach_to_gui(
- power->view_dispatcher, power->gui, ViewDispatcherTypeFullscreen);
-
- // Battery view port
- power->battery_view_port = power_battery_view_port_alloc(power);
- power->show_low_bat_level_message = true;
-
- return power;
+static bool power_update_info(Power* power) {
+ const PowerInfo info = {
+ .is_charging = furi_hal_power_is_charging(),
+ .gauge_is_ok = furi_hal_power_gauge_is_ok(),
+ .is_shutdown_requested = furi_hal_power_is_shutdown_requested(),
+ .charge = furi_hal_power_get_pct(),
+ .health = furi_hal_power_get_bat_health_pct(),
+ .capacity_remaining = furi_hal_power_get_battery_remaining_capacity(),
+ .capacity_full = furi_hal_power_get_battery_full_capacity(),
+ .current_charger = furi_hal_power_get_battery_current(FuriHalPowerICCharger),
+ .current_gauge = furi_hal_power_get_battery_current(FuriHalPowerICFuelGauge),
+ .voltage_battery_charge_limit = furi_hal_power_get_battery_charge_voltage_limit(),
+ .voltage_charger = furi_hal_power_get_battery_voltage(FuriHalPowerICCharger),
+ .voltage_gauge = furi_hal_power_get_battery_voltage(FuriHalPowerICFuelGauge),
+ .voltage_vbus = furi_hal_power_get_usb_voltage(),
+ .temperature_charger = furi_hal_power_get_battery_temperature(FuriHalPowerICCharger),
+ .temperature_gauge = furi_hal_power_get_battery_temperature(FuriHalPowerICFuelGauge),
+ };
+
+ const bool need_refresh = (power->info.charge != info.charge) ||
+ (power->info.is_charging != info.is_charging);
+ power->info = info;
+ return need_refresh;
}
static void power_check_charging_state(Power* power) {
+ NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
+
if(furi_hal_power_is_charging()) {
if((power->info.charge == 100) || (furi_hal_power_is_charging_done())) {
if(power->state != PowerStateCharged) {
- notification_internal_message(power->notification, &sequence_charged);
+ notification_internal_message(notification, &sequence_charged);
power->state = PowerStateCharged;
power->event.type = PowerEventTypeFullyCharged;
furi_pubsub_publish(power->event_pubsub, &power->event);
}
- } else {
- if(power->state != PowerStateCharging) {
- notification_internal_message(power->notification, &sequence_charging);
- power->state = PowerStateCharging;
- power->event.type = PowerEventTypeStartCharging;
- furi_pubsub_publish(power->event_pubsub, &power->event);
- }
- }
- } else {
- if(power->state != PowerStateNotCharging) {
- notification_internal_message(power->notification, &sequence_not_charging);
- power->state = PowerStateNotCharging;
- power->event.type = PowerEventTypeStopCharging;
+
+ } else if(power->state != PowerStateCharging) {
+ notification_internal_message(notification, &sequence_charging);
+ power->state = PowerStateCharging;
+ power->event.type = PowerEventTypeStartCharging;
furi_pubsub_publish(power->event_pubsub, &power->event);
}
- }
-}
-static bool power_update_info(Power* power) {
- PowerInfo info;
-
- info.is_charging = furi_hal_power_is_charging();
- info.gauge_is_ok = furi_hal_power_gauge_is_ok();
- info.is_shutdown_requested = furi_hal_power_is_shutdown_requested();
- info.charge = furi_hal_power_get_pct();
- info.health = furi_hal_power_get_bat_health_pct();
- info.capacity_remaining = furi_hal_power_get_battery_remaining_capacity();
- info.capacity_full = furi_hal_power_get_battery_full_capacity();
- info.current_charger = furi_hal_power_get_battery_current(FuriHalPowerICCharger);
- info.current_gauge = furi_hal_power_get_battery_current(FuriHalPowerICFuelGauge);
- info.voltage_battery_charge_limit = furi_hal_power_get_battery_charge_voltage_limit();
- info.voltage_charger = furi_hal_power_get_battery_voltage(FuriHalPowerICCharger);
- info.voltage_gauge = furi_hal_power_get_battery_voltage(FuriHalPowerICFuelGauge);
- info.voltage_vbus = furi_hal_power_get_usb_voltage();
- info.temperature_charger = furi_hal_power_get_battery_temperature(FuriHalPowerICCharger);
- info.temperature_gauge = furi_hal_power_get_battery_temperature(FuriHalPowerICFuelGauge);
-
- furi_mutex_acquire(power->api_mtx, FuriWaitForever);
- bool need_refresh = power->info.charge != info.charge;
- need_refresh |= power->info.is_charging != info.is_charging;
- power->info = info;
- furi_mutex_release(power->api_mtx);
+ } else if(power->state != PowerStateNotCharging) {
+ notification_internal_message(notification, &sequence_not_charging);
+ power->state = PowerStateNotCharging;
+ power->event.type = PowerEventTypeStopCharging;
+ furi_pubsub_publish(power->event_pubsub, &power->event);
+ }
- return need_refresh;
+ furi_record_close(RECORD_NOTIFICATION);
}
static void power_check_low_battery(Power* power) {
@@ -329,40 +300,41 @@ static void power_check_low_battery(Power* power) {
}
// Check battery charge and vbus voltage
- if((power->info.is_shutdown_requested) && (power->info.voltage_vbus < 4.0f) &&
- power->show_low_bat_level_message) {
+ if((power->info.is_shutdown_requested) &&
+ (power->info.voltage_vbus < POWER_VBUS_LOW_THRESHOLD) && power->show_battery_low_warning) {
if(!power->battery_low) {
- view_dispatcher_send_to_front(power->view_dispatcher);
- view_dispatcher_switch_to_view(power->view_dispatcher, PowerViewOff);
+ view_holder_send_to_front(power->view_holder);
+ view_holder_set_view(power->view_holder, power_off_get_view(power->view_power_off));
}
power->battery_low = true;
} else {
if(power->battery_low) {
- view_dispatcher_switch_to_view(power->view_dispatcher, VIEW_NONE);
- power->power_off_timeout = POWER_OFF_TIMEOUT;
+ // view_dispatcher_switch_to_view(power->view_dispatcher, VIEW_NONE);
+ view_holder_set_view(power->view_holder, NULL);
+ power->power_off_timeout = POWER_OFF_TIMEOUT_S;
}
power->battery_low = false;
}
// If battery low, update view and switch off power after timeout
if(power->battery_low) {
- PowerOffResponse response = power_off_get_response(power->power_off);
+ PowerOffResponse response = power_off_get_response(power->view_power_off);
if(response == PowerOffResponseDefault) {
if(power->power_off_timeout) {
- power_off_set_time_left(power->power_off, power->power_off_timeout--);
+ power_off_set_time_left(power->view_power_off, power->power_off_timeout--);
} else {
power_off(power);
}
} else if(response == PowerOffResponseOk) {
power_off(power);
} else if(response == PowerOffResponseHide) {
- view_dispatcher_switch_to_view(power->view_dispatcher, VIEW_NONE);
+ view_holder_set_view(power->view_holder, NULL);
if(power->power_off_timeout) {
- power_off_set_time_left(power->power_off, power->power_off_timeout--);
+ power_off_set_time_left(power->view_power_off, power->power_off_timeout--);
} else {
power_off(power);
}
} else if(response == PowerOffResponseCancel) {
- view_dispatcher_switch_to_view(power->view_dispatcher, VIEW_NONE);
+ view_holder_set_view(power->view_holder, NULL);
}
}
}
@@ -378,62 +350,148 @@ static void power_check_battery_level_change(Power* power) {
void power_trigger_ui_update(Power* power) {
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
- bool is_loaded = DESKTOP_SETTINGS_LOAD(settings);
- if(is_loaded) {
- power->displayBatteryPercentage = settings->displayBatteryPercentage;
- } else {
- power->displayBatteryPercentage = DISPLAY_BATTERY_BAR;
- }
+ desktop_settings_load(settings);
+ power->displayBatteryPercentage = settings->displayBatteryPercentage;
free(settings);
view_port_update(power->battery_view_port);
}
-int32_t power_srv(void* p) {
- UNUSED(p);
+static void power_handle_shutdown(Power* power) {
+ furi_hal_power_off();
+ // Notify user if USB is plugged
+ view_holder_send_to_front(power->view_holder);
+ view_holder_set_view(
+ power->view_holder, power_unplug_usb_get_view(power->view_power_unplug_usb));
+ furi_delay_ms(100);
+ furi_halt("Disconnect USB for safe shutdown");
+}
- if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) {
- FURI_LOG_W(TAG, "Skipping start in special boot mode");
+static void power_handle_reboot(PowerBootMode mode) {
+ if(mode == PowerBootModeNormal) {
+ update_operation_disarm();
+ } else if(mode == PowerBootModeDfu) {
+ furi_hal_rtc_set_boot_mode(FuriHalRtcBootModeDfu);
+ } else if(mode == PowerBootModeUpdateStart) {
+ furi_hal_rtc_set_boot_mode(FuriHalRtcBootModePreUpdate);
+ } else {
+ furi_crash();
+ }
- furi_thread_suspend(furi_thread_get_current_id());
- return 0;
+ furi_hal_power_reset();
+}
+
+static bool power_message_callback(FuriEventLoopObject* object, void* context) {
+ furi_assert(context);
+ Power* power = context;
+
+ furi_assert(object == power->message_queue);
+
+ PowerMessage msg;
+ furi_check(furi_message_queue_get(power->message_queue, &msg, 0) == FuriStatusOk);
+
+ switch(msg.type) {
+ case PowerMessageTypeShutdown:
+ power_handle_shutdown(power);
+ break;
+ case PowerMessageTypeReboot:
+ power_handle_reboot(msg.boot_mode);
+ break;
+ case PowerMessageTypeGetInfo:
+ *msg.power_info = power->info;
+ break;
+ case PowerMessageTypeIsBatteryHealthy:
+ *msg.bool_param = power->info.health > POWER_HEALTH_LOW_THRESHOLD;
+ break;
+ case PowerMessageTypeShowBatteryLowWarning:
+ power->show_battery_low_warning = *msg.bool_param;
+ break;
+ default:
+ furi_crash();
}
- Power* power = power_alloc();
- power_update_info(power);
- furi_record_create(RECORD_POWER, power);
+ if(msg.lock) {
+ api_lock_unlock(msg.lock);
+ }
+ return true;
+}
+
+static void power_tick_callback(void* context) {
+ furi_assert(context);
+ Power* power = context;
+
+ // Update data from gauge and charger
+ const bool need_refresh = power_update_info(power);
+ // Check low battery level
+ power_check_low_battery(power);
+ // Check and notify about charging state
+ power_check_charging_state(power);
+ // Check and notify about battery level change
+ power_check_battery_level_change(power);
+ // Update battery view port
+ if(need_refresh) {
+ view_port_update(power->battery_view_port);
+ }
+ // Check OTG status and disable it in case of fault
+ if(furi_hal_power_is_otg_enabled()) {
+ furi_hal_power_check_otg_status();
+ }
+}
+
+static Power* power_alloc(void) {
+ Power* power = malloc(sizeof(Power));
+ // Pubsub
+ power->event_pubsub = furi_pubsub_alloc();
+ // State initialization
+ power->power_off_timeout = POWER_OFF_TIMEOUT_S;
+ power->show_battery_low_warning = true;
+
+ // Load UI settings
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
- DESKTOP_SETTINGS_LOAD(settings);
+ desktop_settings_load(settings);
power->displayBatteryPercentage = settings->displayBatteryPercentage;
free(settings);
+ // Gui
+ Gui* gui = furi_record_open(RECORD_GUI);
- while(1) {
- // Update data from gauge and charger
- bool need_refresh = power_update_info(power);
-
- // Check low battery level
- power_check_low_battery(power);
+ power->view_holder = view_holder_alloc();
+ power->view_power_off = power_off_alloc();
+ power->view_power_unplug_usb = power_unplug_usb_alloc();
- // Check and notify about charging state
- power_check_charging_state(power);
+ view_holder_attach_to_gui(power->view_holder, gui);
+ // Battery view port
+ power->battery_view_port = power_battery_view_port_alloc(power);
+ gui_add_view_port(gui, power->battery_view_port, GuiLayerStatusBarRight);
+ // Event loop
+ power->event_loop = furi_event_loop_alloc();
+ power->message_queue = furi_message_queue_alloc(4, sizeof(PowerMessage));
+
+ furi_event_loop_subscribe_message_queue(
+ power->event_loop,
+ power->message_queue,
+ FuriEventLoopEventIn,
+ power_message_callback,
+ power);
+ furi_event_loop_tick_set(power->event_loop, 1000, power_tick_callback, power);
- // Check and notify about battery level change
- power_check_battery_level_change(power);
+ return power;
+}
- // Update battery view port
- if(need_refresh) {
- view_port_update(power->battery_view_port);
- }
+int32_t power_srv(void* p) {
+ UNUSED(p);
- // Check OTG status and disable it in case of fault
- if(furi_hal_power_is_otg_enabled()) {
- furi_hal_power_check_otg_status();
- }
+ if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) {
+ FURI_LOG_W(TAG, "Skipping start in special boot mode");
- furi_delay_ms(1000);
+ furi_thread_suspend(furi_thread_get_current_id());
+ return 0;
}
- furi_crash("That was unexpected");
+ Power* power = power_alloc();
+ power_update_info(power);
+
+ furi_record_create(RECORD_POWER, power);
+ furi_event_loop_run(power->event_loop);
return 0;
}
diff --git a/applications/services/power/power_service/power.h b/applications/services/power/power_service/power.h
index e43651ea28..34d58353a2 100644
--- a/applications/services/power/power_service/power.h
+++ b/applications/services/power/power_service/power.h
@@ -1,9 +1,10 @@
#pragma once
#include
-#include
#include
+#include
+
#ifdef __cplusplus
extern "C" {
#endif
@@ -65,7 +66,7 @@ void power_off(Power* power);
*
* @param mode PowerBootMode
*/
-void power_reboot(PowerBootMode mode);
+void power_reboot(Power* power, PowerBootMode mode);
/** Get power info
*
diff --git a/applications/services/power/power_service/power_api.c b/applications/services/power/power_service/power_api.c
index 1bb482bf52..6f7515f5e4 100644
--- a/applications/services/power/power_service/power_api.c
+++ b/applications/services/power/power_service/power_api.c
@@ -1,41 +1,39 @@
#include "power_i.h"
-#include
-#include
-#include
-
void power_off(Power* power) {
furi_check(power);
- furi_hal_power_off();
- // Notify user if USB is plugged
- view_dispatcher_send_to_front(power->view_dispatcher);
- view_dispatcher_switch_to_view(power->view_dispatcher, PowerViewUnplugUsb);
- furi_delay_ms(100);
- furi_halt("Disconnect USB for safe shutdown");
+ PowerMessage msg = {
+ .type = PowerMessageTypeShutdown,
+ };
+
+ furi_check(
+ furi_message_queue_put(power->message_queue, &msg, FuriWaitForever) == FuriStatusOk);
}
-void power_reboot(PowerBootMode mode) {
- if(mode == PowerBootModeNormal) {
- update_operation_disarm();
- } else if(mode == PowerBootModeDfu) {
- furi_hal_rtc_set_boot_mode(FuriHalRtcBootModeDfu);
- } else if(mode == PowerBootModeUpdateStart) {
- furi_hal_rtc_set_boot_mode(FuriHalRtcBootModePreUpdate);
- } else {
- furi_crash();
- }
-
- furi_hal_power_reset();
+void power_reboot(Power* power, PowerBootMode mode) {
+ PowerMessage msg = {
+ .type = PowerMessageTypeReboot,
+ .boot_mode = mode,
+ };
+
+ furi_check(
+ furi_message_queue_put(power->message_queue, &msg, FuriWaitForever) == FuriStatusOk);
}
void power_get_info(Power* power, PowerInfo* info) {
furi_check(power);
furi_check(info);
- furi_mutex_acquire(power->api_mtx, FuriWaitForever);
- memcpy(info, &power->info, sizeof(power->info));
- furi_mutex_release(power->api_mtx);
+ PowerMessage msg = {
+ .type = PowerMessageTypeGetInfo,
+ .power_info = info,
+ .lock = api_lock_alloc_locked(),
+ };
+
+ furi_check(
+ furi_message_queue_put(power->message_queue, &msg, FuriWaitForever) == FuriStatusOk);
+ api_lock_wait_unlock_and_free(msg.lock);
}
FuriPubSub* power_get_pubsub(Power* power) {
@@ -45,16 +43,30 @@ FuriPubSub* power_get_pubsub(Power* power) {
bool power_is_battery_healthy(Power* power) {
furi_check(power);
- bool is_healthy = false;
- furi_mutex_acquire(power->api_mtx, FuriWaitForever);
- is_healthy = power->info.health > POWER_BATTERY_HEALTHY_LEVEL;
- furi_mutex_release(power->api_mtx);
- return is_healthy;
+
+ bool ret = false;
+
+ PowerMessage msg = {
+ .type = PowerMessageTypeIsBatteryHealthy,
+ .lock = api_lock_alloc_locked(),
+ .bool_param = &ret,
+ };
+
+ furi_check(
+ furi_message_queue_put(power->message_queue, &msg, FuriWaitForever) == FuriStatusOk);
+ api_lock_wait_unlock_and_free(msg.lock);
+
+ return ret;
}
void power_enable_low_battery_level_notification(Power* power, bool enable) {
furi_check(power);
- furi_mutex_acquire(power->api_mtx, FuriWaitForever);
- power->show_low_bat_level_message = enable;
- furi_mutex_release(power->api_mtx);
+
+ PowerMessage msg = {
+ .type = PowerMessageTypeShowBatteryLowWarning,
+ .bool_param = &enable,
+ };
+
+ furi_check(
+ furi_message_queue_put(power->message_queue, &msg, FuriWaitForever) == FuriStatusOk);
}
diff --git a/applications/services/power/power_service/power_i.h b/applications/services/power/power_service/power_i.h
index a09a6f072f..d75071f8f6 100644
--- a/applications/services/power/power_service/power_i.h
+++ b/applications/services/power/power_service/power_i.h
@@ -2,19 +2,15 @@
#include "power.h"
-#include
-#include
#include
+#include
+
+#include
#include
-#include
#include "views/power_off.h"
#include "views/power_unplug_usb.h"
-#include
-
-#define POWER_BATTERY_HEALTHY_LEVEL 70
-
typedef enum {
PowerStateNotCharging,
PowerStateCharging,
@@ -22,29 +18,45 @@ typedef enum {
} PowerState;
struct Power {
- ViewDispatcher* view_dispatcher;
- PowerOff* power_off;
- PowerUnplugUsb* power_unplug_usb;
+ ViewHolder* view_holder;
+ FuriPubSub* event_pubsub;
+ FuriEventLoop* event_loop;
+ FuriMessageQueue* message_queue;
ViewPort* battery_view_port;
- Gui* gui;
- NotificationApp* notification;
- FuriPubSub* event_pubsub;
- PowerEvent event;
+ PowerOff* view_power_off;
+ PowerUnplugUsb* view_power_unplug_usb;
+ PowerEvent event;
PowerState state;
PowerInfo info;
bool battery_low;
- bool show_low_bat_level_message;
+ bool show_battery_low_warning;
uint8_t displayBatteryPercentage;
uint8_t battery_level;
uint8_t power_off_timeout;
-
- FuriMutex* api_mtx;
};
typedef enum {
PowerViewOff,
PowerViewUnplugUsb,
} PowerView;
+
+typedef enum {
+ PowerMessageTypeShutdown,
+ PowerMessageTypeReboot,
+ PowerMessageTypeGetInfo,
+ PowerMessageTypeIsBatteryHealthy,
+ PowerMessageTypeShowBatteryLowWarning,
+} PowerMessageType;
+
+typedef struct {
+ PowerMessageType type;
+ union {
+ PowerBootMode boot_mode;
+ PowerInfo* power_info;
+ bool* bool_param;
+ };
+ FuriApiLock lock;
+} PowerMessage;
diff --git a/applications/services/power/power_service/views/power_off.c b/applications/services/power/power_service/views/power_off.c
index 4da374b31a..dbc233dde5 100644
--- a/applications/services/power/power_service/views/power_off.c
+++ b/applications/services/power/power_service/views/power_off.c
@@ -33,7 +33,7 @@ static void power_off_draw_callback(Canvas* canvas, void* _model) {
elements_button_center(canvas, "OK");
elements_button_right(canvas, "Hide");
} else {
- snprintf(buff, sizeof(buff), "Charge me!\nDont't forget!");
+ snprintf(buff, sizeof(buff), "Charge me!\nDon't forget!");
elements_multiline_text_aligned(canvas, 70, 23, AlignLeft, AlignTop, buff);
canvas_draw_str_aligned(canvas, 64, 60, AlignCenter, AlignBottom, "Hold a second...");
diff --git a/applications/services/region/application.fam b/applications/services/region/application.fam
new file mode 100644
index 0000000000..a4cdc94ea5
--- /dev/null
+++ b/applications/services/region/application.fam
@@ -0,0 +1,10 @@
+App(
+ appid="region",
+ name="RegionSrv",
+ apptype=FlipperAppType.STARTUP,
+ targets=["f7"],
+ entry_point="region_on_system_start",
+ cdefines=["SRV_REGION"],
+ requires=["storage"],
+ order=170,
+)
diff --git a/applications/services/region/region.c b/applications/services/region/region.c
new file mode 100644
index 0000000000..dffcc6b2d5
--- /dev/null
+++ b/applications/services/region/region.c
@@ -0,0 +1,147 @@
+#include
+
+#include
+#include
+
+#include
+#include
+
+#define TAG "RegionSrv"
+
+#define SUBGHZ_REGION_FILENAME INT_PATH(".region_data")
+
+static bool region_istream_read(pb_istream_t* istream, pb_byte_t* buf, size_t count) {
+ File* file = istream->state;
+ size_t ret = storage_file_read(file, buf, count);
+ return count == ret;
+}
+
+static bool region_istream_decode_band(pb_istream_t* stream, const pb_field_t* field, void** arg) {
+ UNUSED(field);
+
+ FuriHalRegion* region = *arg;
+
+ PB_Region_Band band = {0};
+ if(!pb_decode(stream, PB_Region_Band_fields, &band)) {
+ FURI_LOG_E(TAG, "PB Region band decode error: %s", PB_GET_ERROR(stream));
+ return false;
+ }
+
+ region->bands_count += 1;
+ region = realloc( //-V701
+ region,
+ sizeof(FuriHalRegion) + sizeof(FuriHalRegionBand) * region->bands_count);
+ size_t pos = region->bands_count - 1;
+ region->bands[pos].start = band.start;
+ region->bands[pos].end = band.end;
+ region->bands[pos].power_limit = band.power_limit;
+ region->bands[pos].duty_cycle = band.duty_cycle;
+ *arg = region;
+
+ FURI_LOG_I(
+ TAG,
+ "Add allowed band: start %luHz, stop %luHz, power_limit %ddBm, duty_cycle %u%%",
+ band.start,
+ band.end,
+ band.power_limit,
+ band.duty_cycle);
+ return true;
+}
+
+static int32_t region_load_file(void* context) {
+ UNUSED(context);
+
+ Storage* storage = furi_record_open(RECORD_STORAGE);
+ File* file = storage_file_alloc(storage);
+
+ PB_Region pb_region = {0};
+ pb_region.bands.funcs.decode = region_istream_decode_band;
+
+ do {
+ FileInfo fileinfo = {0};
+
+ if(storage_common_stat(storage, SUBGHZ_REGION_FILENAME, &fileinfo) != FSE_OK ||
+ fileinfo.size == 0) {
+ FURI_LOG_W(TAG, "Region file missing or empty");
+ break;
+
+ } else if(!storage_file_open(file, SUBGHZ_REGION_FILENAME, FSAM_READ, FSOM_OPEN_EXISTING)) {
+ FURI_LOG_E(TAG, "Failed to open region file");
+ break;
+ }
+
+ pb_istream_t istream = {
+ .callback = region_istream_read,
+ .state = file,
+ .errmsg = NULL,
+ .bytes_left = fileinfo.size,
+ };
+
+ pb_region.bands.arg = malloc(sizeof(FuriHalRegion));
+
+ if(!pb_decode(&istream, PB_Region_fields, &pb_region)) {
+ FURI_LOG_E(TAG, "Failed to decode region file");
+ free(pb_region.bands.arg);
+ break;
+ }
+
+ FuriHalRegion* region = pb_region.bands.arg;
+
+ memcpy(
+ region->country_code,
+ pb_region.country_code->bytes,
+ MIN(pb_region.country_code->size, sizeof(region->country_code) - 1));
+
+ furi_hal_region_set(region);
+
+ FURI_LOG_I(TAG, "Dynamic region set: %s", region->country_code);
+ } while(0);
+
+ pb_release(PB_Region_fields, &pb_region);
+ storage_file_free(file);
+ furi_record_close(RECORD_STORAGE);
+
+ return 0;
+}
+
+static void region_loader_pending_callback(void* context, uint32_t arg) {
+ UNUSED(arg);
+
+ FuriThread* loader = context;
+ furi_thread_join(loader);
+ furi_thread_free(loader);
+}
+
+static void region_loader_state_callback(FuriThreadState state, void* context) {
+ UNUSED(context);
+
+ if(state == FuriThreadStateStopped) {
+ furi_timer_pending_callback(region_loader_pending_callback, furi_thread_get_current(), 0);
+ }
+}
+
+static void region_storage_callback(const void* message, void* context) {
+ UNUSED(context);
+ const StorageEvent* event = message;
+
+ if(event->type == StorageEventTypeCardMount) {
+ FuriThread* loader = furi_thread_alloc_ex(NULL, 2048, region_load_file, NULL);
+ furi_thread_set_state_callback(loader, region_loader_state_callback);
+ furi_thread_start(loader);
+ }
+}
+
+int32_t region_on_system_start(void* p) {
+ UNUSED(p);
+
+ Storage* storage = furi_record_open(RECORD_STORAGE);
+ furi_pubsub_subscribe(storage_get_pubsub(storage), region_storage_callback, NULL);
+
+ if(storage_sd_status(storage) != FSE_OK) {
+ FURI_LOG_D(TAG, "SD Card not ready, skipping dynamic region");
+ return 0;
+ }
+
+ region_load_file(NULL);
+ return 0;
+}
diff --git a/applications/services/rpc/rpc.c b/applications/services/rpc/rpc.c
index c2aa3e27c3..d5cef52f0a 100644
--- a/applications/services/rpc/rpc.c
+++ b/applications/services/rpc/rpc.c
@@ -228,7 +228,7 @@ bool rpc_pb_stream_read(pb_istream_t* istream, pb_byte_t* buf, size_t count) {
}
}
-#if SRV_RPC_DEBUG
+#ifdef SRV_RPC_DEBUG
rpc_debug_print_data("INPUT", buf, bytes_received);
#endif
@@ -268,7 +268,7 @@ static int32_t rpc_session_worker(void* context) {
bool message_decode_failed = false;
if(pb_decode_ex(&istream, &PB_Main_msg, session->decoded_message, PB_DECODE_DELIMITED)) {
-#if SRV_RPC_DEBUG
+#ifdef SRV_RPC_DEBUG
FURI_LOG_I(TAG, "INPUT:");
rpc_debug_print_message(session->decoded_message);
#endif
@@ -452,7 +452,7 @@ void rpc_send(RpcSession* session, PB_Main* message) {
pb_ostream_t ostream = PB_OSTREAM_SIZING;
-#if SRV_RPC_DEBUG
+#ifdef SRV_RPC_DEBUG
FURI_LOG_I(TAG, "OUTPUT:");
rpc_debug_print_message(message);
#endif
@@ -465,7 +465,7 @@ void rpc_send(RpcSession* session, PB_Main* message) {
pb_encode_ex(&ostream, &PB_Main_msg, message, PB_ENCODE_DELIMITED);
-#if SRV_RPC_DEBUG
+#ifdef SRV_RPC_DEBUG
rpc_debug_print_data("OUTPUT", buffer, ostream.bytes_written);
#endif
diff --git a/applications/services/rpc/rpc_system.c b/applications/services/rpc/rpc_system.c
index 0b9fd33f95..1cc0f90eb2 100644
--- a/applications/services/rpc/rpc_system.c
+++ b/applications/services/rpc/rpc_system.c
@@ -54,18 +54,21 @@ static void rpc_system_system_reboot_process(const PB_Main* request, void* conte
RpcSession* session = (RpcSession*)context;
furi_assert(session);
+ Power* power = furi_record_open(RECORD_POWER);
const int mode = request->content.system_reboot_request.mode;
if(mode == PB_System_RebootRequest_RebootMode_OS) {
- power_reboot(PowerBootModeNormal);
+ power_reboot(power, PowerBootModeNormal);
} else if(mode == PB_System_RebootRequest_RebootMode_DFU) {
- power_reboot(PowerBootModeDfu);
+ power_reboot(power, PowerBootModeDfu);
} else if(mode == PB_System_RebootRequest_RebootMode_UPDATE) {
- power_reboot(PowerBootModeUpdateStart);
+ power_reboot(power, PowerBootModeUpdateStart);
} else {
rpc_send_and_release_empty(
session, request->command_id, PB_CommandStatus_ERROR_INVALID_PARAMETERS);
}
+
+ furi_record_close(RECORD_POWER);
}
static void rpc_system_system_device_info_callback(
@@ -181,9 +184,9 @@ static void rpc_system_system_factory_reset_process(const PB_Main* request, void
furi_hal_rtc_reset_registers();
furi_hal_rtc_set_flag(FuriHalRtcFlagStorageFormatInternal);
- power_reboot(PowerBootModeNormal);
- (void)session;
+ Power* power = furi_record_open(RECORD_POWER);
+ power_reboot(power, PowerBootModeNormal);
}
static void
diff --git a/applications/services/storage/storage.c b/applications/services/storage/storage.c
index 21f8789cec..bfe2a08b21 100644
--- a/applications/services/storage/storage.c
+++ b/applications/services/storage/storage.c
@@ -3,7 +3,6 @@
#include "storage_message.h"
#include "storage_processing.h"
#include "storage/storage_glue.h"
-#include "storages/storage_int.h"
#include "storages/storage_ext.h"
#include
@@ -42,9 +41,6 @@ Storage* storage_app_alloc(void) {
storage_data_timestamp(&app->storage[i]);
}
-#ifndef FURI_RAM_EXEC
- storage_int_init(&app->storage[ST_INT]);
-#endif
storage_ext_init(&app->storage[ST_EXT]);
// sd icon gui
@@ -106,6 +102,11 @@ int32_t storage_srv(void* p) {
Storage* app = storage_app_alloc();
furi_record_create(RECORD_STORAGE, app);
+ if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagStorageFormatInternal)) {
+ FURI_LOG_W(TAG, "Format Internal not supported, clearing flag");
+ furi_hal_rtc_reset_flag(FuriHalRtcFlagStorageFormatInternal);
+ }
+
StorageMessage message;
while(1) {
if(furi_message_queue_get(app->message_queue, &message, STORAGE_TICK) == FuriStatusOk) {
diff --git a/applications/services/storage/storage.h b/applications/services/storage/storage.h
index a4dffe6330..6dbeb0d36b 100644
--- a/applications/services/storage/storage.h
+++ b/applications/services/storage/storage.h
@@ -506,7 +506,7 @@ FS_Error storage_sd_status(Storage* storage);
/******************* Internal LFS Functions *******************/
-typedef void (*Storage_name_converter)(FuriString*);
+typedef void (*StorageNameConverter)(FuriString*);
/**
* @brief Back up the internal storage contents to a *.tar archive.
@@ -526,7 +526,7 @@ FS_Error storage_int_backup(Storage* storage, const char* dstname);
* @return FSE_OK if the storage was successfully restored, any other error code on failure.
*/
FS_Error
- storage_int_restore(Storage* storage, const char* dstname, Storage_name_converter converter);
+ storage_int_restore(Storage* storage, const char* dstname, StorageNameConverter converter);
/***************** Simplified Functions ******************/
diff --git a/applications/services/storage/storage_cli.c b/applications/services/storage/storage_cli.c
index 918e796ce3..a18b289408 100644
--- a/applications/services/storage/storage_cli.c
+++ b/applications/services/storage/storage_cli.c
@@ -33,7 +33,7 @@ static void storage_cli_info(Cli* cli, FuriString* path, FuriString* args) {
storage_cli_print_error(error);
} else {
printf(
- "Label: %s\r\nType: LittleFS\r\n%luKiB total\r\n%luKiB free\r\n",
+ "Label: %s\r\nType: Virtual\r\n%luKiB total\r\n%luKiB free\r\n",
furi_hal_version_get_name_ptr() ? furi_hal_version_get_name_ptr() : "Unknown",
(uint32_t)(total_space / 1024),
(uint32_t)(free_space / 1024));
@@ -675,9 +675,12 @@ static void storage_cli_factory_reset(Cli* cli, FuriString* args, void* context)
char c = cli_getc(cli);
if(c == 'y' || c == 'Y') {
printf("Data will be wiped after reboot.\r\n");
+
furi_hal_rtc_reset_registers();
furi_hal_rtc_set_flag(FuriHalRtcFlagStorageFormatInternal);
- power_reboot(PowerBootModeNormal);
+
+ Power* power = furi_record_open(RECORD_POWER);
+ power_reboot(power, PowerBootModeNormal);
} else {
printf("Safe choice.\r\n");
}
diff --git a/applications/services/storage/storage_internal_api.c b/applications/services/storage/storage_internal_api.c
index 4cbce7546e..defab966ce 100644
--- a/applications/services/storage/storage_internal_api.c
+++ b/applications/services/storage/storage_internal_api.c
@@ -14,7 +14,7 @@ FS_Error storage_int_backup(Storage* storage, const char* dstname) {
}
FS_Error
- storage_int_restore(Storage* storage, const char* srcname, Storage_name_converter converter) {
+ storage_int_restore(Storage* storage, const char* srcname, StorageNameConverter converter) {
furi_check(storage);
TarArchive* archive = tar_archive_alloc(storage);
diff --git a/applications/services/storage/storage_internal_dirname_i.h b/applications/services/storage/storage_internal_dirname_i.h
new file mode 100644
index 0000000000..889bdc4976
--- /dev/null
+++ b/applications/services/storage/storage_internal_dirname_i.h
@@ -0,0 +1,3 @@
+#pragma once
+
+#define STORAGE_INTERNAL_DIR_NAME ".int"
diff --git a/applications/services/storage/storage_processing.c b/applications/services/storage/storage_processing.c
index 9e96765b62..8d86dd3851 100644
--- a/applications/services/storage/storage_processing.c
+++ b/applications/services/storage/storage_processing.c
@@ -1,7 +1,11 @@
-#include "storage_processing.h"
#include
#include
+#include "storage_processing.h"
+#include "storage_internal_dirname_i.h"
+
+#define TAG "Storage"
+
#define STORAGE_PATH_PREFIX_LEN 4u
_Static_assert(
sizeof(STORAGE_ANY_PATH_PREFIX) == STORAGE_PATH_PREFIX_LEN + 1,
@@ -60,36 +64,27 @@ static StorageType storage_get_type_by_path(FuriString* path) {
return type;
}
-static void storage_path_change_to_real_storage(FuriString* path, StorageType real_storage) {
- if(furi_string_search(path, STORAGE_ANY_PATH_PREFIX) == 0) {
- switch(real_storage) {
- case ST_EXT:
- furi_string_replace_at(
- path, 0, strlen(STORAGE_EXT_PATH_PREFIX), STORAGE_EXT_PATH_PREFIX);
- break;
- case ST_INT:
- furi_string_replace_at(
- path, 0, strlen(STORAGE_INT_PATH_PREFIX), STORAGE_INT_PATH_PREFIX);
- break;
- default:
- break;
- }
- }
-}
static FS_Error storage_get_data(Storage* app, FuriString* path, StorageData** storage) {
StorageType type = storage_get_type_by_path(path);
if(storage_type_is_valid(type)) {
+ // Any storage phase-out: redirect "/any" to "/ext"
if(type == ST_ANY) {
- type = ST_INT;
- if(storage_data_status(&app->storage[ST_EXT]) == StorageStatusOK) {
- type = ST_EXT;
- }
- storage_path_change_to_real_storage(path, type);
+ FURI_LOG_W(
+ TAG,
+ STORAGE_ANY_PATH_PREFIX " is deprecated, use " STORAGE_EXT_PATH_PREFIX " instead");
+ furi_string_replace_at(
+ path, 0, strlen(STORAGE_EXT_PATH_PREFIX), STORAGE_EXT_PATH_PREFIX);
+ type = ST_EXT;
+ }
+
+ furi_assert(type == ST_EXT);
+
+ if(storage_data_status(&app->storage[type]) != StorageStatusOK) {
+ return FSE_NOT_READY;
}
- furi_assert(type == ST_EXT || type == ST_INT);
*storage = &app->storage[type];
return FSE_OK;
@@ -559,6 +554,16 @@ void storage_process_alias(
furi_string_get_cstr(apps_assets_path_with_appsid));
furi_string_free(apps_assets_path_with_appsid);
+
+ } else if(furi_string_start_with(path, STORAGE_INT_PATH_PREFIX)) {
+ furi_string_replace_at(
+ path, 0, strlen(STORAGE_INT_PATH_PREFIX), EXT_PATH(STORAGE_INTERNAL_DIR_NAME));
+
+ FuriString* int_on_ext_path = furi_string_alloc_set(EXT_PATH(STORAGE_INTERNAL_DIR_NAME));
+ if(storage_process_common_stat(app, int_on_ext_path, NULL) != FSE_OK) {
+ storage_process_common_mkdir(app, int_on_ext_path);
+ }
+ furi_string_free(int_on_ext_path);
}
}
diff --git a/applications/services/storage/storages/storage_ext.c b/applications/services/storage/storages/storage_ext.c
index 93e06f6632..a945f1cd51 100644
--- a/applications/services/storage/storages/storage_ext.c
+++ b/applications/services/storage/storages/storage_ext.c
@@ -1,10 +1,13 @@
-#include "fatfs.h"
-#include "../filesystem_api_internal.h"
-#include "storage_ext.h"
+#include
#include
-#include "sd_notify.h"
#include
+#include "sd_notify.h"
+#include "storage_ext.h"
+
+#include "../filesystem_api_internal.h"
+#include "../storage_internal_dirname_i.h"
+
typedef FIL SDFile;
typedef DIR SDDir;
typedef FILINFO SDFileInfo;
@@ -93,6 +96,64 @@ static bool sd_mount_card_internal(StorageData* storage, bool notify) {
return result;
}
+static bool sd_remove_recursive(const char* path) {
+ SDDir* current_dir = malloc(sizeof(DIR));
+ SDFileInfo* file_info = malloc(sizeof(FILINFO));
+ FuriString* current_path = furi_string_alloc_set(path);
+
+ bool go_deeper = false;
+ SDError status;
+
+ while(true) {
+ status = f_opendir(current_dir, furi_string_get_cstr(current_path));
+ if(status != FR_OK) break;
+
+ while(true) {
+ status = f_readdir(current_dir, file_info);
+ if(status != FR_OK || !strlen(file_info->fname)) break;
+
+ if(file_info->fattrib & AM_DIR) {
+ furi_string_cat_printf(current_path, "/%s", file_info->fname);
+ go_deeper = true;
+ break;
+
+ } else {
+ FuriString* file_path = furi_string_alloc_printf(
+ "%s/%s", furi_string_get_cstr(current_path), file_info->fname);
+ status = f_unlink(furi_string_get_cstr(file_path));
+ furi_string_free(file_path);
+
+ if(status != FR_OK) break;
+ }
+ }
+
+ status = f_closedir(current_dir);
+ if(status != FR_OK) break;
+
+ if(go_deeper) {
+ go_deeper = false;
+ continue;
+ }
+
+ status = f_unlink(furi_string_get_cstr(current_path));
+ if(status != FR_OK) break;
+
+ if(!furi_string_equal(current_path, path)) {
+ size_t last_char_pos = furi_string_search_rchar(current_path, '/');
+ furi_assert(last_char_pos != FURI_STRING_FAILURE);
+ furi_string_left(current_path, last_char_pos);
+ } else {
+ break;
+ }
+ }
+
+ free(current_dir);
+ free(file_info);
+ furi_string_free(current_path);
+
+ return status == FR_OK;
+}
+
FS_Error sd_unmount_card(StorageData* storage) {
SDData* sd_data = storage->data;
SDError error;
@@ -112,21 +173,32 @@ FS_Error sd_mount_card(StorageData* storage, bool notify) {
if(storage->status != StorageStatusOK) {
FURI_LOG_E(TAG, "sd init error: %s", storage_data_status_text(storage));
- if(notify) {
- NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
- sd_notify_error(notification);
- furi_record_close(RECORD_NOTIFICATION);
- }
error = FSE_INTERNAL;
+
} else {
FURI_LOG_I(TAG, "card mounted");
- if(notify) {
- NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
- sd_notify_success(notification);
- furi_record_close(RECORD_NOTIFICATION);
- }
+#ifndef FURI_RAM_EXEC
+ if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagStorageFormatInternal)) {
+ FURI_LOG_I(TAG, "deleting internal storage directory");
+ error = sd_remove_recursive(STORAGE_INTERNAL_DIR_NAME) ? FSE_OK : FSE_INTERNAL;
+ } else {
+ error = FSE_OK;
+ }
+#else
+ UNUSED(sd_remove_recursive);
error = FSE_OK;
+#endif
+ }
+
+ if(notify) {
+ NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
+ if(error != FSE_OK) {
+ sd_notify_error(notification);
+ } else {
+ sd_notify_success(notification);
+ }
+ furi_record_close(RECORD_NOTIFICATION);
}
return error;
@@ -654,4 +726,8 @@ void storage_ext_init(StorageData* storage) {
// do not notify on first launch, notifications app is waiting for our thread to read settings
storage_ext_tick_internal(storage, false);
+#ifndef FURI_RAM_EXEC
+ // always reset the flag to prevent accidental wipe on SD card insertion
+ furi_hal_rtc_reset_flag(FuriHalRtcFlagStorageFormatInternal);
+#endif
}
diff --git a/applications/services/storage/storages/storage_int.c b/applications/services/storage/storages/storage_int.c
deleted file mode 100644
index 324ce63286..0000000000
--- a/applications/services/storage/storages/storage_int.c
+++ /dev/null
@@ -1,744 +0,0 @@
-#include "storage_int.h"
-#include
-#include
-#include
-
-#define TAG "StorageInt"
-
-#define STORAGE_PATH STORAGE_INT_PATH_PREFIX
-#define LFS_CLEAN_FINGERPRINT 0
-
-/* When less than LFS_RESERVED_PAGES_COUNT are left free, creation &
- * modification of non-dot files is restricted */
-#define LFS_RESERVED_PAGES_COUNT 3
-
-typedef struct {
- const size_t start_address;
- const size_t start_page;
- struct lfs_config config;
- lfs_t lfs;
-} LFSData;
-
-typedef struct {
- void* data;
- bool open;
-} LFSHandle;
-
-static LFSHandle* lfs_handle_alloc_file(void) {
- LFSHandle* handle = malloc(sizeof(LFSHandle));
- handle->data = malloc(sizeof(lfs_file_t));
- return handle;
-}
-
-static LFSHandle* lfs_handle_alloc_dir(void) {
- LFSHandle* handle = malloc(sizeof(LFSHandle));
- handle->data = malloc(sizeof(lfs_dir_t));
- return handle;
-}
-
-/* INTERNALS */
-
-static lfs_dir_t* lfs_handle_get_dir(LFSHandle* handle) {
- return handle->data;
-}
-
-static lfs_file_t* lfs_handle_get_file(LFSHandle* handle) {
- return handle->data;
-}
-
-static void lfs_handle_free(LFSHandle* handle) {
- free(handle->data);
- free(handle);
-}
-
-static void lfs_handle_set_open(LFSHandle* handle) {
- handle->open = true;
-}
-
-static bool lfs_handle_is_open(LFSHandle* handle) {
- return handle->open;
-}
-
-static lfs_t* lfs_get_from_storage(StorageData* storage) {
- return &((LFSData*)storage->data)->lfs;
-}
-
-static LFSData* lfs_data_get_from_storage(StorageData* storage) {
- return (LFSData*)storage->data;
-}
-
-static int storage_int_device_read(
- const struct lfs_config* c,
- lfs_block_t block,
- lfs_off_t off,
- void* buffer,
- lfs_size_t size) {
- LFSData* lfs_data = c->context;
- size_t address = lfs_data->start_address + block * c->block_size + off;
-
- FURI_LOG_T(
- TAG,
- "Device read: block %lu, off %lu, buffer: %p, size %lu, translated address: %p",
- block,
- off,
- buffer,
- size,
- (void*)address);
-
- memcpy(buffer, (void*)address, size);
-
- return 0;
-}
-
-static int storage_int_device_prog(
- const struct lfs_config* c,
- lfs_block_t block,
- lfs_off_t off,
- const void* buffer,
- lfs_size_t size) {
- LFSData* lfs_data = c->context;
- size_t address = lfs_data->start_address + block * c->block_size + off;
-
- FURI_LOG_T(
- TAG,
- "Device prog: block %lu, off %lu, buffer: %p, size %lu, translated address: %p",
- block,
- off,
- buffer,
- size,
- (void*)address);
-
- int ret = 0;
- while(size > 0) {
- furi_hal_flash_write_dword(address, *(uint64_t*)buffer);
- address += c->prog_size;
- buffer += c->prog_size;
- size -= c->prog_size;
- }
-
- return ret;
-}
-
-static int storage_int_device_erase(const struct lfs_config* c, lfs_block_t block) {
- LFSData* lfs_data = c->context;
- size_t page = lfs_data->start_page + block;
-
- FURI_LOG_D(TAG, "Device erase: page %lu, translated page: %zx", block, page);
-
- furi_hal_flash_erase(page);
- return 0;
-}
-
-static int storage_int_device_sync(const struct lfs_config* c) {
- UNUSED(c);
- FURI_LOG_D(TAG, "Device sync: skipping");
- return 0;
-}
-
-static LFSData* storage_int_lfs_data_alloc(void) {
- LFSData* lfs_data = malloc(sizeof(LFSData));
-
- // Internal storage start address
- *(size_t*)(&lfs_data->start_address) = furi_hal_flash_get_free_page_start_address();
- *(size_t*)(&lfs_data->start_page) =
- (lfs_data->start_address - furi_hal_flash_get_base()) / furi_hal_flash_get_page_size();
-
- // LFS configuration
- // Glue and context
- lfs_data->config.context = lfs_data;
- lfs_data->config.read = storage_int_device_read;
- lfs_data->config.prog = storage_int_device_prog;
- lfs_data->config.erase = storage_int_device_erase;
- lfs_data->config.sync = storage_int_device_sync;
-
- // Block device description
- lfs_data->config.read_size = furi_hal_flash_get_read_block_size();
- lfs_data->config.prog_size = furi_hal_flash_get_write_block_size();
- lfs_data->config.block_size = furi_hal_flash_get_page_size();
- lfs_data->config.block_count = furi_hal_flash_get_free_page_count();
- lfs_data->config.block_cycles = furi_hal_flash_get_cycles_count();
- lfs_data->config.cache_size = 16;
- lfs_data->config.lookahead_size = 16;
-
- return lfs_data;
-}
-
-// Returns true if fingerprint was invalid and LFS reformatting is needed
-static bool storage_int_check_and_set_fingerprint(LFSData* lfs_data) {
- bool value = false;
-
- uint32_t os_fingerprint = 0;
- os_fingerprint |= ((lfs_data->start_page & 0xFF) << 0);
- os_fingerprint |= ((lfs_data->config.block_count & 0xFF) << 8);
- os_fingerprint |= ((LFS_DISK_VERSION_MAJOR & 0xFFFF) << 16);
-
- uint32_t rtc_fingerprint = furi_hal_rtc_get_register(FuriHalRtcRegisterLfsFingerprint);
- if(rtc_fingerprint == LFS_CLEAN_FINGERPRINT) {
- FURI_LOG_I(TAG, "Storing LFS fingerprint in RTC");
- furi_hal_rtc_set_register(FuriHalRtcRegisterLfsFingerprint, os_fingerprint);
- } else if(rtc_fingerprint != os_fingerprint) {
- FURI_LOG_E(TAG, "LFS fingerprint mismatch");
- furi_hal_rtc_set_register(FuriHalRtcRegisterLfsFingerprint, os_fingerprint);
- value = true;
- }
-
- return value;
-}
-
-static void storage_int_lfs_mount(LFSData* lfs_data, StorageData* storage) {
- int err;
- lfs_t* lfs = &lfs_data->lfs;
-
- bool was_fingerprint_outdated = storage_int_check_and_set_fingerprint(lfs_data);
- bool need_format = furi_hal_rtc_is_flag_set(FuriHalRtcFlagStorageFormatInternal) ||
- was_fingerprint_outdated;
-
- if(need_format) {
- // Format storage
- err = lfs_format(lfs, &lfs_data->config);
- if(err == 0) {
- FURI_LOG_I(TAG, "Factory reset: Format successful, trying to mount");
- furi_hal_rtc_reset_flag(FuriHalRtcFlagStorageFormatInternal);
- err = lfs_mount(lfs, &lfs_data->config);
- if(err == 0) {
- FURI_LOG_I(TAG, "Factory reset: Mounted");
- storage->status = StorageStatusOK;
- } else {
- FURI_LOG_E(TAG, "Factory reset: Mount after format failed");
- storage->status = StorageStatusNotMounted;
- }
- } else {
- FURI_LOG_E(TAG, "Factory reset: Format failed");
- storage->status = StorageStatusNoFS;
- }
- } else {
- // Normal
- err = lfs_mount(lfs, &lfs_data->config);
- if(err == 0) {
- FURI_LOG_I(TAG, "Mounted");
- storage->status = StorageStatusOK;
- } else {
- FURI_LOG_E(TAG, "Mount failed, formatting");
- err = lfs_format(lfs, &lfs_data->config);
- if(err == 0) {
- FURI_LOG_I(TAG, "Format successful, trying to mount");
- err = lfs_mount(lfs, &lfs_data->config);
- if(err == 0) {
- FURI_LOG_I(TAG, "Mounted");
- storage->status = StorageStatusOK;
- } else {
- FURI_LOG_E(TAG, "Mount after format failed");
- storage->status = StorageStatusNotMounted;
- }
- } else {
- FURI_LOG_E(TAG, "Format failed");
- storage->status = StorageStatusNoFS;
- }
- }
- }
-}
-
-/****************** Common Functions ******************/
-
-static FS_Error storage_int_parse_error(int error) {
- FS_Error result;
-
- if(error >= LFS_ERR_OK) {
- result = FSE_OK;
- } else {
- switch(error) {
- case LFS_ERR_NOENT:
- result = FSE_NOT_EXIST;
- break;
- case LFS_ERR_EXIST:
- result = FSE_EXIST;
- break;
- case LFS_ERR_NOTEMPTY:
- result = FSE_DENIED;
- break;
- case LFS_ERR_INVAL:
- case LFS_ERR_NOATTR:
- result = FSE_INVALID_PARAMETER;
- break;
- case LFS_ERR_BADF:
- case LFS_ERR_ISDIR:
- case LFS_ERR_NOTDIR:
- case LFS_ERR_NAMETOOLONG:
- result = FSE_INVALID_NAME;
- break;
- case LFS_ERR_IO:
- case LFS_ERR_FBIG:
- case LFS_ERR_NOSPC:
- case LFS_ERR_NOMEM:
- case LFS_ERR_CORRUPT:
- default:
- result = FSE_INTERNAL;
- }
- }
-
- return result;
-}
-
-/* Returns false if less than reserved space is left free */
-static bool storage_int_check_for_free_space(StorageData* storage) {
- LFSData* lfs_data = lfs_data_get_from_storage(storage);
-
- lfs_ssize_t result = lfs_fs_size(lfs_get_from_storage(storage));
- if(result >= 0) {
- lfs_size_t free_space =
- (lfs_data->config.block_count - result) * lfs_data->config.block_size;
-
- return free_space > LFS_RESERVED_PAGES_COUNT * furi_hal_flash_get_page_size();
- }
-
- return false;
-}
-/******************* File Functions *******************/
-
-static bool storage_int_file_open(
- void* ctx,
- File* file,
- const char* path,
- FS_AccessMode access_mode,
- FS_OpenMode open_mode) {
- StorageData* storage = ctx;
- lfs_t* lfs = lfs_get_from_storage(storage);
-
- bool enough_free_space = storage_int_check_for_free_space(storage);
-
- int flags = 0;
-
- if(access_mode & FSAM_READ) flags |= LFS_O_RDONLY;
- if(access_mode & FSAM_WRITE) flags |= LFS_O_WRONLY;
-
- if(open_mode & FSOM_OPEN_EXISTING) flags |= 0;
- if(open_mode & FSOM_OPEN_ALWAYS) flags |= LFS_O_CREAT;
- if(open_mode & FSOM_OPEN_APPEND) flags |= LFS_O_CREAT | LFS_O_APPEND;
- if(open_mode & FSOM_CREATE_NEW) flags |= LFS_O_CREAT | LFS_O_EXCL;
- if(open_mode & FSOM_CREATE_ALWAYS) flags |= LFS_O_CREAT | LFS_O_TRUNC;
-
- LFSHandle* handle = lfs_handle_alloc_file();
- storage_set_storage_file_data(file, handle, storage);
-
- if(!enough_free_space) {
- FuriString* filename;
- filename = furi_string_alloc();
- path_extract_basename(path, filename);
- bool is_dot_file =
- (!furi_string_empty(filename) && (furi_string_get_char(filename, 0) == '.'));
- furi_string_free(filename);
-
- /* Restrict write & creation access to all non-dot files */
- if(!is_dot_file && (flags & (LFS_O_CREAT | LFS_O_WRONLY))) {
- file->internal_error_id = LFS_ERR_NOSPC;
- file->error_id = FSE_DENIED;
- FURI_LOG_W(TAG, "Denied access to '%s': no free space", path);
- return false;
- }
- }
-
- file->internal_error_id = lfs_file_open(lfs, lfs_handle_get_file(handle), path, flags);
-
- if(file->internal_error_id >= LFS_ERR_OK) {
- lfs_handle_set_open(handle);
- }
-
- file->error_id = storage_int_parse_error(file->internal_error_id);
-
- return file->error_id == FSE_OK;
-}
-
-static bool storage_int_file_close(void* ctx, File* file) {
- StorageData* storage = ctx;
- lfs_t* lfs = lfs_get_from_storage(storage);
- LFSHandle* handle = storage_get_storage_file_data(file, storage);
-
- if(lfs_handle_is_open(handle)) {
- file->internal_error_id = lfs_file_close(lfs, lfs_handle_get_file(handle));
- } else {
- file->internal_error_id = LFS_ERR_BADF;
- }
-
- file->error_id = storage_int_parse_error(file->internal_error_id);
- lfs_handle_free(handle);
- return file->error_id == FSE_OK;
-}
-
-static uint16_t
- storage_int_file_read(void* ctx, File* file, void* buff, uint16_t const bytes_to_read) {
- StorageData* storage = ctx;
- lfs_t* lfs = lfs_get_from_storage(storage);
- LFSHandle* handle = storage_get_storage_file_data(file, storage);
-
- uint16_t bytes_read = 0;
-
- if(lfs_handle_is_open(handle)) {
- file->internal_error_id =
- lfs_file_read(lfs, lfs_handle_get_file(handle), buff, bytes_to_read);
- } else {
- file->internal_error_id = LFS_ERR_BADF;
- }
-
- file->error_id = storage_int_parse_error(file->internal_error_id);
-
- if(file->error_id == FSE_OK) {
- bytes_read = file->internal_error_id;
- file->internal_error_id = 0;
- }
- return bytes_read;
-}
-
-static uint16_t
- storage_int_file_write(void* ctx, File* file, const void* buff, uint16_t const bytes_to_write) {
- StorageData* storage = ctx;
- lfs_t* lfs = lfs_get_from_storage(storage);
- LFSHandle* handle = storage_get_storage_file_data(file, storage);
-
- uint16_t bytes_written = 0;
-
- if(lfs_handle_is_open(handle)) {
- file->internal_error_id =
- lfs_file_write(lfs, lfs_handle_get_file(handle), buff, bytes_to_write);
- } else {
- file->internal_error_id = LFS_ERR_BADF;
- }
-
- file->error_id = storage_int_parse_error(file->internal_error_id);
-
- if(file->error_id == FSE_OK) {
- bytes_written = file->internal_error_id;
- file->internal_error_id = 0;
- }
- return bytes_written;
-}
-
-static bool
- storage_int_file_seek(void* ctx, File* file, const uint32_t offset, const bool from_start) {
- StorageData* storage = ctx;
- lfs_t* lfs = lfs_get_from_storage(storage);
- LFSHandle* handle = storage_get_storage_file_data(file, storage);
-
- if(lfs_handle_is_open(handle)) {
- if(from_start) {
- file->internal_error_id =
- lfs_file_seek(lfs, lfs_handle_get_file(handle), offset, LFS_SEEK_SET);
- } else {
- file->internal_error_id =
- lfs_file_seek(lfs, lfs_handle_get_file(handle), offset, LFS_SEEK_CUR);
- }
- } else {
- file->internal_error_id = LFS_ERR_BADF;
- }
-
- file->error_id = storage_int_parse_error(file->internal_error_id);
- return file->error_id == FSE_OK;
-}
-
-static uint64_t storage_int_file_tell(void* ctx, File* file) {
- StorageData* storage = ctx;
- lfs_t* lfs = lfs_get_from_storage(storage);
- LFSHandle* handle = storage_get_storage_file_data(file, storage);
-
- if(lfs_handle_is_open(handle)) {
- file->internal_error_id = lfs_file_tell(lfs, lfs_handle_get_file(handle));
- } else {
- file->internal_error_id = LFS_ERR_BADF;
- }
-
- file->error_id = storage_int_parse_error(file->internal_error_id);
-
- int32_t position = 0;
- if(file->error_id == FSE_OK) {
- position = file->internal_error_id;
- file->internal_error_id = 0;
- }
-
- return position;
-}
-
-static bool storage_int_file_truncate(void* ctx, File* file) {
- StorageData* storage = ctx;
- lfs_t* lfs = lfs_get_from_storage(storage);
- LFSHandle* handle = storage_get_storage_file_data(file, storage);
-
- if(lfs_handle_is_open(handle)) {
- file->internal_error_id = lfs_file_tell(lfs, lfs_handle_get_file(handle));
- file->error_id = storage_int_parse_error(file->internal_error_id);
-
- if(file->error_id == FSE_OK) {
- uint32_t position = file->internal_error_id;
- file->internal_error_id =
- lfs_file_truncate(lfs, lfs_handle_get_file(handle), position);
- file->error_id = storage_int_parse_error(file->internal_error_id);
- }
- } else {
- file->internal_error_id = LFS_ERR_BADF;
- file->error_id = storage_int_parse_error(file->internal_error_id);
- }
-
- return file->error_id == FSE_OK;
-}
-
-static bool storage_int_file_sync(void* ctx, File* file) {
- StorageData* storage = ctx;
- lfs_t* lfs = lfs_get_from_storage(storage);
- LFSHandle* handle = storage_get_storage_file_data(file, storage);
-
- if(lfs_handle_is_open(handle)) {
- file->internal_error_id = lfs_file_sync(lfs, lfs_handle_get_file(handle));
- } else {
- file->internal_error_id = LFS_ERR_BADF;
- }
-
- file->error_id = storage_int_parse_error(file->internal_error_id);
- return file->error_id == FSE_OK;
-}
-
-static uint64_t storage_int_file_size(void* ctx, File* file) {
- StorageData* storage = ctx;
- lfs_t* lfs = lfs_get_from_storage(storage);
- LFSHandle* handle = storage_get_storage_file_data(file, storage);
-
- if(lfs_handle_is_open(handle)) {
- file->internal_error_id = lfs_file_size(lfs, lfs_handle_get_file(handle));
- } else {
- file->internal_error_id = LFS_ERR_BADF;
- }
-
- file->error_id = storage_int_parse_error(file->internal_error_id);
-
- uint32_t size = 0;
- if(file->error_id == FSE_OK) {
- size = file->internal_error_id;
- file->internal_error_id = 0;
- }
-
- return size;
-}
-
-static bool storage_int_file_eof(void* ctx, File* file) {
- StorageData* storage = ctx;
- lfs_t* lfs = lfs_get_from_storage(storage);
- LFSHandle* handle = storage_get_storage_file_data(file, storage);
-
- bool eof = true;
-
- if(lfs_handle_is_open(handle)) {
- int32_t position = lfs_file_tell(lfs, lfs_handle_get_file(handle));
- int32_t size = lfs_file_size(lfs, lfs_handle_get_file(handle));
-
- if(position < 0) {
- file->internal_error_id = position;
- } else if(size < 0) {
- file->internal_error_id = size;
- } else {
- file->internal_error_id = LFS_ERR_OK;
- eof = (position >= size);
- }
- } else {
- file->internal_error_id = LFS_ERR_BADF;
- }
-
- file->error_id = storage_int_parse_error(file->internal_error_id);
- return eof;
-}
-
-/******************* Dir Functions *******************/
-
-static bool storage_int_dir_open(void* ctx, File* file, const char* path) {
- StorageData* storage = ctx;
- lfs_t* lfs = lfs_get_from_storage(storage);
-
- LFSHandle* handle = lfs_handle_alloc_dir();
- storage_set_storage_file_data(file, handle, storage);
-
- file->internal_error_id = lfs_dir_open(lfs, lfs_handle_get_dir(handle), path);
- if(file->internal_error_id >= LFS_ERR_OK) {
- lfs_handle_set_open(handle);
- }
-
- file->error_id = storage_int_parse_error(file->internal_error_id);
- return file->error_id == FSE_OK;
-}
-
-static bool storage_int_dir_close(void* ctx, File* file) {
- StorageData* storage = ctx;
- lfs_t* lfs = lfs_get_from_storage(storage);
- LFSHandle* handle = storage_get_storage_file_data(file, storage);
-
- if(lfs_handle_is_open(handle)) {
- file->internal_error_id = lfs_dir_close(lfs, lfs_handle_get_dir(handle));
- } else {
- file->internal_error_id = LFS_ERR_BADF;
- }
-
- file->error_id = storage_int_parse_error(file->internal_error_id);
- lfs_handle_free(handle);
- return file->error_id == FSE_OK;
-}
-
-static bool storage_int_dir_read(
- void* ctx,
- File* file,
- FileInfo* fileinfo,
- char* name,
- const uint16_t name_length) {
- StorageData* storage = ctx;
- lfs_t* lfs = lfs_get_from_storage(storage);
- LFSHandle* handle = storage_get_storage_file_data(file, storage);
-
- if(lfs_handle_is_open(handle)) {
- struct lfs_info _fileinfo;
-
- // LFS returns virtual directories "." and "..", so we read until we get something meaningful or an empty string
- do {
- file->internal_error_id = lfs_dir_read(lfs, lfs_handle_get_dir(handle), &_fileinfo);
- file->error_id = storage_int_parse_error(file->internal_error_id);
- } while(strcmp(_fileinfo.name, ".") == 0 || strcmp(_fileinfo.name, "..") == 0);
-
- if(fileinfo != NULL) {
- fileinfo->size = _fileinfo.size;
- fileinfo->flags = 0;
- if(_fileinfo.type & LFS_TYPE_DIR) fileinfo->flags |= FSF_DIRECTORY;
- }
-
- if(name != NULL) {
- snprintf(name, name_length, "%s", _fileinfo.name);
- }
-
- // set FSE_NOT_EXIST error on end of directory
- if(file->internal_error_id == 0) {
- file->error_id = FSE_NOT_EXIST;
- }
- } else {
- file->internal_error_id = LFS_ERR_BADF;
- file->error_id = storage_int_parse_error(file->internal_error_id);
- }
-
- return file->error_id == FSE_OK;
-}
-
-static bool storage_int_dir_rewind(void* ctx, File* file) {
- StorageData* storage = ctx;
- lfs_t* lfs = lfs_get_from_storage(storage);
- LFSHandle* handle = storage_get_storage_file_data(file, storage);
-
- if(lfs_handle_is_open(handle)) {
- file->internal_error_id = lfs_dir_rewind(lfs, lfs_handle_get_dir(handle));
- } else {
- file->internal_error_id = LFS_ERR_BADF;
- }
-
- file->error_id = storage_int_parse_error(file->internal_error_id);
- return file->error_id == FSE_OK;
-}
-
-/******************* Common FS Functions *******************/
-
-static FS_Error storage_int_common_stat(void* ctx, const char* path, FileInfo* fileinfo) {
- StorageData* storage = ctx;
- lfs_t* lfs = lfs_get_from_storage(storage);
- struct lfs_info _fileinfo;
- int result = lfs_stat(lfs, path, &_fileinfo);
-
- if(fileinfo != NULL) {
- fileinfo->size = _fileinfo.size;
- fileinfo->flags = 0;
- if(_fileinfo.type & LFS_TYPE_DIR) fileinfo->flags |= FSF_DIRECTORY;
- }
-
- return storage_int_parse_error(result);
-}
-
-static FS_Error storage_int_common_remove(void* ctx, const char* path) {
- StorageData* storage = ctx;
- lfs_t* lfs = lfs_get_from_storage(storage);
- int result = lfs_remove(lfs, path);
- return storage_int_parse_error(result);
-}
-
-static FS_Error storage_int_common_mkdir(void* ctx, const char* path) {
- StorageData* storage = ctx;
- lfs_t* lfs = lfs_get_from_storage(storage);
- int result = lfs_mkdir(lfs, path);
- return storage_int_parse_error(result);
-}
-
-static FS_Error storage_int_common_fs_info(
- void* ctx,
- const char* fs_path,
- uint64_t* total_space,
- uint64_t* free_space) {
- UNUSED(fs_path);
- StorageData* storage = ctx;
-
- lfs_t* lfs = lfs_get_from_storage(storage);
- LFSData* lfs_data = lfs_data_get_from_storage(storage);
-
- if(total_space) {
- *total_space = lfs_data->config.block_size * lfs_data->config.block_count;
- }
-
- lfs_ssize_t result = lfs_fs_size(lfs);
- if(free_space && (result >= 0)) {
- *free_space = (lfs_data->config.block_count - result) * lfs_data->config.block_size;
- }
-
- return storage_int_parse_error(result);
-}
-
-static bool storage_int_common_equivalent_path(const char* path1, const char* path2) {
- return strcmp(path1, path2) == 0;
-}
-
-/******************* Init Storage *******************/
-static const FS_Api fs_api = {
- .file =
- {
- .open = storage_int_file_open,
- .close = storage_int_file_close,
- .read = storage_int_file_read,
- .write = storage_int_file_write,
- .seek = storage_int_file_seek,
- .tell = storage_int_file_tell,
- .truncate = storage_int_file_truncate,
- .size = storage_int_file_size,
- .sync = storage_int_file_sync,
- .eof = storage_int_file_eof,
- },
- .dir =
- {
- .open = storage_int_dir_open,
- .close = storage_int_dir_close,
- .read = storage_int_dir_read,
- .rewind = storage_int_dir_rewind,
- },
- .common =
- {
- .stat = storage_int_common_stat,
- .mkdir = storage_int_common_mkdir,
- .remove = storage_int_common_remove,
- .fs_info = storage_int_common_fs_info,
- .equivalent_path = storage_int_common_equivalent_path,
- },
-};
-
-void storage_int_init(StorageData* storage) {
- FURI_LOG_I(TAG, "Starting");
- LFSData* lfs_data = storage_int_lfs_data_alloc();
- FURI_LOG_I(
- TAG,
- "Config: start %p, read %lu, write %lu, page size: %lu, page count: %lu, cycles: %ld",
- (void*)lfs_data->start_address,
- lfs_data->config.read_size,
- lfs_data->config.prog_size,
- lfs_data->config.block_size,
- lfs_data->config.block_count,
- lfs_data->config.block_cycles);
-
- storage_int_lfs_mount(lfs_data, storage);
-
- storage->data = lfs_data;
- storage->api.tick = NULL;
- storage->fs_api = &fs_api;
-}
diff --git a/applications/services/storage/storages/storage_int.h b/applications/services/storage/storages/storage_int.h
deleted file mode 100644
index 456d72408f..0000000000
--- a/applications/services/storage/storages/storage_int.h
+++ /dev/null
@@ -1,13 +0,0 @@
-#pragma once
-#include
-#include "../storage_glue.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-void storage_int_init(StorageData* storage);
-
-#ifdef __cplusplus
-}
-#endif
diff --git a/applications/settings/about/about.c b/applications/settings/about/about.c
index a4c2e5b9ef..d9fbca87ad 100644
--- a/applications/settings/about/about.c
+++ b/applications/settings/about/about.c
@@ -1,9 +1,12 @@
#include
-#include
+
#include
-#include
+#include
#include
+
+#include
#include
+
#include
#include
#include
@@ -202,18 +205,15 @@ int32_t about_settings_app(void* p) {
DialogMessage* message = dialog_message_alloc();
Gui* gui = furi_record_open(RECORD_GUI);
- ViewDispatcher* view_dispatcher = view_dispatcher_alloc();
+ ViewHolder* view_holder = view_holder_alloc();
EmptyScreen* empty_screen = empty_screen_alloc();
- const uint32_t empty_screen_index = 0;
size_t screen_index = 0;
DialogMessageButton screen_result;
// draw empty screen to prevent menu flickering
- view_dispatcher_add_view(
- view_dispatcher, empty_screen_index, empty_screen_get_view(empty_screen));
- view_dispatcher_attach_to_gui(view_dispatcher, gui, ViewDispatcherTypeFullscreen);
- view_dispatcher_switch_to_view(view_dispatcher, empty_screen_index);
+ view_holder_attach_to_gui(view_holder, gui);
+ view_holder_set_view(view_holder, empty_screen_get_view(empty_screen));
while(1) {
if(screen_index >= COUNT_OF(about_screens) - 1) {
@@ -244,8 +244,8 @@ int32_t about_settings_app(void* p) {
dialog_message_free(message);
furi_record_close(RECORD_DIALOGS);
- view_dispatcher_remove_view(view_dispatcher, empty_screen_index);
- view_dispatcher_free(view_dispatcher);
+ view_holder_set_view(view_holder, NULL);
+ view_holder_free(view_holder);
empty_screen_free(empty_screen);
furi_record_close(RECORD_GUI);
diff --git a/applications/settings/bt_settings_app/bt_settings_app.c b/applications/settings/bt_settings_app/bt_settings_app.c
index d86c9df647..174d0bcbb2 100644
--- a/applications/settings/bt_settings_app/bt_settings_app.c
+++ b/applications/settings/bt_settings_app/bt_settings_app.c
@@ -15,15 +15,12 @@ static bool bt_settings_back_event_callback(void* context) {
BtSettingsApp* bt_settings_app_alloc(void) {
BtSettingsApp* app = malloc(sizeof(BtSettingsApp));
- // Load settings
- bt_settings_load(&app->settings);
app->gui = furi_record_open(RECORD_GUI);
app->bt = furi_record_open(RECORD_BT);
// View Dispatcher and Scene Manager
app->view_dispatcher = view_dispatcher_alloc();
app->scene_manager = scene_manager_alloc(&bt_settings_scene_handlers, app);
- view_dispatcher_enable_queue(app->view_dispatcher);
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
view_dispatcher_set_custom_event_callback(
@@ -48,6 +45,8 @@ BtSettingsApp* bt_settings_app_alloc(void) {
view_dispatcher_add_view(
app->view_dispatcher, BtSettingsAppViewPopup, popup_get_view(app->popup));
+ bt_get_settings(app->bt, &app->settings);
+
// Set first scene
scene_manager_next_scene(app->scene_manager, BtSettingsAppSceneStart);
return app;
@@ -55,6 +54,7 @@ BtSettingsApp* bt_settings_app_alloc(void) {
void bt_settings_app_free(BtSettingsApp* app) {
furi_assert(app);
+ bt_set_settings(app->bt, &app->settings);
// Gui modules
view_dispatcher_remove_view(app->view_dispatcher, BtSettingsAppViewVarItemList);
variable_item_list_free(app->var_item_list);
@@ -79,7 +79,6 @@ extern int32_t bt_settings_app(void* p) {
UNUSED(p);
BtSettingsApp* app = bt_settings_app_alloc();
view_dispatcher_run(app->view_dispatcher);
- bt_settings_save(&app->settings);
bt_settings_app_free(app);
return 0;
}
diff --git a/applications/settings/bt_settings_app/bt_settings_app.h b/applications/settings/bt_settings_app/bt_settings_app.h
index b79e369511..5255945ff5 100644
--- a/applications/settings/bt_settings_app/bt_settings_app.h
+++ b/applications/settings/bt_settings_app/bt_settings_app.h
@@ -1,18 +1,21 @@
#pragma once
#include
-#include
+
#include
#include
#include
#include
-#include
#include
#include
#include
-#include
+#include
+#include
+
+#include
+
#include "scenes/bt_settings_scene.h"
enum BtSettingsCustomEvent {
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 1d72a9e6fa..a76740bd1e 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
@@ -70,18 +70,17 @@ bool bt_settings_scene_start_on_event(void* context, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == BtSettingOn) {
- furi_hal_bt_start_advertising();
app->settings.enabled = true;
consumed = true;
} else if(event.event == BtSettingOff) {
app->settings.enabled = false;
- furi_hal_bt_stop_advertising();
consumed = true;
} else if(event.event == BtSettingsCustomEventForgetDevices) {
scene_manager_next_scene(app->scene_manager, BtSettingsAppSceneForgetDevConfirm);
consumed = true;
}
}
+
return consumed;
}
diff --git a/applications/settings/desktop_settings/desktop_settings_app.c b/applications/settings/desktop_settings/desktop_settings_app.c
index 35ee2a3f1f..30d80514ac 100644
--- a/applications/settings/desktop_settings/desktop_settings_app.c
+++ b/applications/settings/desktop_settings/desktop_settings_app.c
@@ -5,9 +5,14 @@
#include
#include
+#include
+#include
+
+#include
+#include
+
#include "desktop_settings_app.h"
#include "scenes/desktop_settings_scene.h"
-#include
static bool desktop_settings_custom_event_callback(void* context, uint32_t event) {
furi_assert(context);
@@ -28,7 +33,6 @@ DesktopSettingsApp* desktop_settings_app_alloc(void) {
app->dialogs = furi_record_open(RECORD_DIALOGS);
app->view_dispatcher = view_dispatcher_alloc();
app->scene_manager = scene_manager_alloc(&desktop_settings_scene_handlers, app);
- view_dispatcher_enable_queue(app->view_dispatcher);
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
view_dispatcher_set_custom_event_callback(
@@ -126,23 +130,26 @@ void desktop_settings_app_free(DesktopSettingsApp* app) {
free(app);
if(temp_save_name) {
- power_reboot(PowerBootModeNormal);
+ Power* power = furi_record_open(RECORD_POWER);
+ power_reboot(power, PowerBootModeNormal);
}
}
extern int32_t desktop_settings_app(void* p) {
+ UNUSED(p);
+
DesktopSettingsApp* app = desktop_settings_app_alloc();
- DESKTOP_SETTINGS_LOAD(&app->settings);
+ Desktop* desktop = furi_record_open(RECORD_DESKTOP);
- if(p && (strcmp(p, DESKTOP_SETTINGS_RUN_PIN_SETUP_ARG) == 0)) {
- scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinSetupHowto);
- } else {
- scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneStart);
- }
+ desktop_api_get_settings(desktop, &app->settings);
+
+ scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneStart);
view_dispatcher_run(app->view_dispatcher);
- DESKTOP_SETTINGS_SAVE(&app->settings);
+ desktop_api_set_settings(desktop, &app->settings);
+ furi_record_close(RECORD_DESKTOP);
+
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 48adb9cfb0..2ce57c1186 100644
--- a/applications/settings/desktop_settings/desktop_settings_app.h
+++ b/applications/settings/desktop_settings/desktop_settings_app.h
@@ -15,6 +15,8 @@
#include "views/desktop_settings_view_pin_setup_howto.h"
#include "views/desktop_settings_view_pin_setup_howto2.h"
+#include
+
typedef enum {
DesktopSettingsAppViewMenu,
DesktopSettingsAppViewVarItemList,
@@ -40,7 +42,7 @@ typedef struct {
DesktopSettingsViewPinSetupHowto* pin_setup_howto_view;
DesktopSettingsViewPinSetupHowto2* pin_setup_howto2_view;
- PinCode pincode_buffer;
+ DesktopPinCode pincode_buffer;
bool pincode_buffer_filled;
bool save_name;
diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c
index ee6af0bd9c..ec970a3155 100644
--- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c
+++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c
@@ -194,19 +194,25 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e
strncpy(
curr_favorite_app->name_or_path,
furi_string_get_cstr(temp_path),
- MAX_APP_LENGTH);
+ sizeof(curr_favorite_app->name_or_path));
consumed = true;
}
} else {
size_t app_index = event.event - MAIN_LIST_APPLICATION_OFFSET;
const char* name = favorite_fap_get_app_name(app_index);
- if(name) strncpy(curr_favorite_app->name_or_path, name, MAX_APP_LENGTH);
+ if(name)
+ strncpy(
+ curr_favorite_app->name_or_path,
+ name,
+ sizeof(curr_favorite_app->name_or_path));
consumed = true;
}
if(consumed) {
scene_manager_previous_scene(app->scene_manager);
};
consumed = true;
+
+ desktop_settings_save(&app->settings);
}
furi_string_free(temp_path);
@@ -215,6 +221,5 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e
void desktop_settings_scene_favorite_on_exit(void* context) {
DesktopSettingsApp* app = context;
- DESKTOP_SETTINGS_SAVE(&app->settings);
submenu_reset(app->submenu);
}
diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_auth.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_auth.c
index 5af25cd614..1e64165314 100644
--- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_auth.c
+++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_auth.c
@@ -1,7 +1,7 @@
#include
#include
#include
-#include
+#include
#include "../desktop_settings_app.h"
#include
#include
@@ -12,13 +12,14 @@
#define SCENE_EVENT_PINS_EQUAL (1U)
#define SCENE_EVENT_PINS_DIFFERENT (2U)
-static void pin_auth_done_callback(const PinCode* pin_code, void* context) {
+static void pin_auth_done_callback(const DesktopPinCode* pin_code, void* context) {
furi_assert(pin_code);
furi_assert(context);
- DesktopSettingsApp* app = context;
+ DesktopSettingsApp* app = context;
app->pincode_buffer = *pin_code;
- if(desktop_pin_compare(&app->settings.pin_code, pin_code)) {
+
+ if(desktop_pin_code_check(pin_code)) {
view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_PINS_EQUAL);
} else {
view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_PINS_DIFFERENT);
@@ -31,10 +32,9 @@ static void pin_auth_back_callback(void* context) {
}
void desktop_settings_scene_pin_auth_on_enter(void* context) {
- DesktopSettingsApp* app = context;
+ furi_assert(desktop_pin_code_is_set());
- DESKTOP_SETTINGS_LOAD(&app->settings);
- furi_assert(app->settings.pin_code.length > 0);
+ DesktopSettingsApp* app = context;
desktop_view_pin_input_set_context(app->pin_input_view, app);
desktop_view_pin_input_set_back_callback(app->pin_input_view, pin_auth_back_callback);
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 f31a968944..abcce66da7 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
@@ -17,9 +17,8 @@ static void pin_disable_back_callback(void* context) {
void desktop_settings_scene_pin_disable_on_enter(void* context) {
furi_assert(context);
DesktopSettingsApp* app = context;
- app->settings.pin_code.length = 0;
- memset(app->settings.pin_code.data, '0', sizeof(app->settings.pin_code.data));
- DESKTOP_SETTINGS_SAVE(&app->settings);
+
+ desktop_pin_code_reset();
popup_set_context(app->popup, app);
popup_set_callback(app->popup, pin_disable_back_callback);
diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_error.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_error.c
index 1ba3c1b2da..711683c3fe 100644
--- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_error.c
+++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_error.c
@@ -6,7 +6,7 @@
#include
#include "desktop_settings_scene.h"
#include "desktop_settings_scene_i.h"
-#include
+#include
#include "../desktop_settings_app.h"
#define SCENE_EVENT_EXIT (0U)
@@ -17,7 +17,7 @@ static void pin_error_back_callback(void* context) {
view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_EXIT);
}
-static void pin_error_done_callback(const PinCode* pin_code, void* context) {
+static void pin_error_done_callback(const DesktopPinCode* pin_code, void* context) {
UNUSED(pin_code);
furi_assert(context);
DesktopSettingsApp* app = context;
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 7375edd3f4..e0c66cb288 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
@@ -19,7 +19,7 @@ void desktop_settings_scene_pin_menu_on_enter(void* context) {
Submenu* submenu = app->submenu;
submenu_reset(submenu);
- if(!app->settings.pin_code.length) {
+ if(!desktop_pin_code_is_set()) {
submenu_add_item(
submenu,
"Set PIN",
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 93012330aa..08f5fcb3fc 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
@@ -7,14 +7,14 @@
#include
#include "desktop_settings_scene.h"
#include "desktop_settings_scene_i.h"
-#include
+#include
#define SCENE_EVENT_EXIT (0U)
#define SCENE_EVENT_1ST_PIN_ENTERED (1U)
#define SCENE_EVENT_PINS_EQUAL (2U)
#define SCENE_EVENT_PINS_DIFFERENT (3U)
-static void pin_setup_done_callback(const PinCode* pin_code, void* context) {
+static void pin_setup_done_callback(const DesktopPinCode* pin_code, void* context) {
furi_assert(pin_code);
furi_assert(context);
DesktopSettingsApp* app = context;
@@ -25,7 +25,7 @@ static void pin_setup_done_callback(const PinCode* pin_code, void* context) {
view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_1ST_PIN_ENTERED);
} else {
app->pincode_buffer_filled = false;
- if(desktop_pin_compare(&app->pincode_buffer, pin_code)) {
+ if(desktop_pin_code_is_equal(&app->pincode_buffer, pin_code)) {
view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_PINS_EQUAL);
} else {
view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_PINS_DIFFERENT);
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 170e6bca56..aa3d2214e5 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
@@ -11,7 +11,7 @@
#define SCENE_EVENT_DONE (0U)
-static void pin_setup_done_callback(const PinCode* pin_code, void* context) {
+static void pin_setup_done_callback(const DesktopPinCode* pin_code, void* context) {
furi_assert(pin_code);
furi_assert(context);
DesktopSettingsApp* app = context;
@@ -22,8 +22,8 @@ static void pin_setup_done_callback(const PinCode* pin_code, void* context) {
void desktop_settings_scene_pin_setup_done_on_enter(void* context) {
DesktopSettingsApp* app = context;
- app->settings.pin_code = app->pincode_buffer;
- DESKTOP_SETTINGS_SAVE(&app->settings);
+ desktop_pin_code_set(&app->pincode_buffer);
+
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
notification_message(notification, &sequence_single_vibro);
notification_message(notification, &sequence_blink_green_10);
@@ -32,7 +32,7 @@ void desktop_settings_scene_pin_setup_done_on_enter(void* context) {
desktop_view_pin_input_set_context(app->pin_input_view, app);
desktop_view_pin_input_set_back_callback(app->pin_input_view, NULL);
desktop_view_pin_input_set_done_callback(app->pin_input_view, pin_setup_done_callback);
- desktop_view_pin_input_set_pin(app->pin_input_view, &app->settings.pin_code);
+ desktop_view_pin_input_set_pin(app->pin_input_view, &app->pincode_buffer);
desktop_view_pin_input_set_label_button(app->pin_input_view, "Done");
desktop_view_pin_input_set_label_primary(app->pin_input_view, 29, 8, "PIN Activated!");
desktop_view_pin_input_set_label_secondary(
diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c
index 52e7769599..fec783a35d 100644
--- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c
+++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c
@@ -215,7 +215,7 @@ bool desktop_settings_scene_start_on_event(void* context, SceneManagerEvent even
scene_manager_set_scene_state(
app->scene_manager,
DesktopSettingsAppSceneFavorite,
- SCENE_STATE_SET_DUMMY_APP | DummyAppLeft);
+ SCENE_STATE_SET_DUMMY_APP | DummyAppLeftShort);
scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite);
break;
case DesktopSettingsDummyLeftLong:
@@ -229,7 +229,7 @@ bool desktop_settings_scene_start_on_event(void* context, SceneManagerEvent even
scene_manager_set_scene_state(
app->scene_manager,
DesktopSettingsAppSceneFavorite,
- SCENE_STATE_SET_DUMMY_APP | DummyAppRight);
+ SCENE_STATE_SET_DUMMY_APP | DummyAppRightShort);
scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite);
break;
case DesktopSettingsDummyRightLong:
@@ -250,7 +250,7 @@ bool desktop_settings_scene_start_on_event(void* context, SceneManagerEvent even
scene_manager_set_scene_state(
app->scene_manager,
DesktopSettingsAppSceneFavorite,
- SCENE_STATE_SET_DUMMY_APP | DummyAppDown);
+ SCENE_STATE_SET_DUMMY_APP | DummyAppDownShort);
scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite);
break;
case DesktopSettingsDummyDownLong:
@@ -264,7 +264,7 @@ bool desktop_settings_scene_start_on_event(void* context, SceneManagerEvent even
scene_manager_set_scene_state(
app->scene_manager,
DesktopSettingsAppSceneFavorite,
- SCENE_STATE_SET_DUMMY_APP | DummyAppOk);
+ SCENE_STATE_SET_DUMMY_APP | DummyAppOkShort);
scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite);
break;
case DesktopSettingsDummyOkLong:
@@ -286,7 +286,7 @@ bool desktop_settings_scene_start_on_event(void* context, SceneManagerEvent even
void desktop_settings_scene_start_on_exit(void* context) {
DesktopSettingsApp* app = context;
variable_item_list_reset(app->variable_item_list);
- DESKTOP_SETTINGS_SAVE(&app->settings);
+ desktop_settings_save(&app->settings);
// Trigger UI update in case we changed battery layout
Power* power = furi_record_open(RECORD_POWER);
diff --git a/applications/settings/expansion_settings_app/expansion_settings_app.c b/applications/settings/expansion_settings_app/expansion_settings_app.c
index fe0e3ca2c0..639f7f23d5 100644
--- a/applications/settings/expansion_settings_app/expansion_settings_app.c
+++ b/applications/settings/expansion_settings_app/expansion_settings_app.c
@@ -10,7 +10,7 @@ static void expansion_settings_app_uart_changed(VariableItem* item) {
ExpansionSettingsApp* app = variable_item_get_context(item);
const uint8_t index = variable_item_get_current_value_index(item);
variable_item_set_current_value_text(item, expansion_uart_text[index]);
- app->settings->uart_index = index;
+ app->settings.uart_index = index;
if(index < FuriHalSerialIdMax) {
expansion_set_listen_serial(app->expansion, index);
@@ -27,12 +27,12 @@ static uint32_t expansion_settings_app_exit(void* context) {
static ExpansionSettingsApp* expansion_settings_app_alloc(void) {
ExpansionSettingsApp* app = malloc(sizeof(ExpansionSettingsApp));
+ expansion_settings_load(&app->settings);
+
app->gui = furi_record_open(RECORD_GUI);
app->expansion = furi_record_open(RECORD_EXPANSION);
- app->settings = expansion_get_settings(app->expansion);
app->view_dispatcher = view_dispatcher_alloc();
- view_dispatcher_enable_queue(app->view_dispatcher);
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
@@ -48,7 +48,7 @@ static ExpansionSettingsApp* expansion_settings_app_alloc(void) {
COUNT_OF(expansion_uart_text),
expansion_settings_app_uart_changed,
app);
- value_index = app->settings->uart_index;
+ value_index = app->settings.uart_index;
variable_item_set_current_value_index(item, value_index);
variable_item_set_current_value_text(item, expansion_uart_text[value_index]);
@@ -67,7 +67,7 @@ static ExpansionSettingsApp* expansion_settings_app_alloc(void) {
static void expansion_settings_app_free(ExpansionSettingsApp* app) {
furi_assert(app);
- expansion_settings_save(app->settings);
+ expansion_settings_save(&app->settings);
view_dispatcher_remove_view(app->view_dispatcher, ExpansionSettingsViewVarItemList);
variable_item_list_free(app->var_item_list);
diff --git a/applications/settings/expansion_settings_app/expansion_settings_app.h b/applications/settings/expansion_settings_app/expansion_settings_app.h
index a404f9c1a5..a43bf853fc 100644
--- a/applications/settings/expansion_settings_app/expansion_settings_app.h
+++ b/applications/settings/expansion_settings_app/expansion_settings_app.h
@@ -8,7 +8,6 @@
#include
#include
-#include
#include
typedef struct {
@@ -16,7 +15,7 @@ typedef struct {
ViewDispatcher* view_dispatcher;
VariableItemList* var_item_list;
Expansion* expansion;
- ExpansionSettings* settings;
+ ExpansionSettings settings;
} ExpansionSettingsApp;
typedef enum {
diff --git a/applications/settings/notification_settings/notification_settings_app.c b/applications/settings/notification_settings/notification_settings_app.c
index 7576dcf3c2..2462b32bde 100644
--- a/applications/settings/notification_settings/notification_settings_app.c
+++ b/applications/settings/notification_settings/notification_settings_app.c
@@ -242,7 +242,6 @@ static NotificationAppSettings* alloc_settings(void) {
}
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_add_view(app->view_dispatcher, 0, view);
view_dispatcher_switch_to_view(app->view_dispatcher, 0);
diff --git a/applications/settings/power_settings_app/power_settings_app.c b/applications/settings/power_settings_app/power_settings_app.c
index d2eaec0a5a..57df1344f3 100644
--- a/applications/settings/power_settings_app/power_settings_app.c
+++ b/applications/settings/power_settings_app/power_settings_app.c
@@ -28,7 +28,6 @@ PowerSettingsApp* power_settings_app_alloc(uint32_t first_scene) {
// View dispatcher
app->view_dispatcher = view_dispatcher_alloc();
app->scene_manager = scene_manager_alloc(&power_settings_scene_handlers, app);
- view_dispatcher_enable_queue(app->view_dispatcher);
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
view_dispatcher_set_custom_event_callback(
app->view_dispatcher, power_settings_custom_event_callback);
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
index 62e06de927..25e7b2bc42 100644
--- 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
@@ -49,10 +49,12 @@ bool power_settings_scene_reboot_confirm_on_event(void* context, SceneManagerEve
if(event.event == DialogExResultLeft) {
scene_manager_previous_scene(app->scene_manager);
} else if(event.event == DialogExResultRight) {
+ Power* power = furi_record_open(RECORD_POWER);
+
if(reboot_type == RebootTypeDFU) {
- power_reboot(PowerBootModeDfu);
+ power_reboot(power, PowerBootModeDfu);
} else {
- power_reboot(PowerBootModeNormal);
+ power_reboot(power, PowerBootModeNormal);
}
}
consumed = true;
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 2d977176a0..0f8e1aa965 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
@@ -65,7 +65,9 @@ bool storage_settings_scene_factory_reset_on_event(void* context, SceneManagerEv
} else {
furi_hal_rtc_reset_registers();
furi_hal_rtc_set_flag(FuriHalRtcFlagStorageFormatInternal);
- power_reboot(PowerBootModeNormal);
+
+ Power* power = furi_record_open(RECORD_POWER);
+ power_reboot(power, PowerBootModeNormal);
}
consumed = true;
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 b7620b6e82..5a367afcec 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,
- "Name: %s\nType: LittleFS\nTotal: %lu KiB\nFree: %lu KiB",
+ "Name: %s\nType: Virtual\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/storage_settings.c b/applications/settings/storage_settings/storage_settings.c
index 0508e8e0fa..3546328904 100644
--- a/applications/settings/storage_settings/storage_settings.c
+++ b/applications/settings/storage_settings/storage_settings.c
@@ -23,7 +23,6 @@ static StorageSettings* storage_settings_alloc(void) {
app->scene_manager = scene_manager_alloc(&storage_settings_scene_handlers, app);
app->text_string = furi_string_alloc();
- view_dispatcher_enable_queue(app->view_dispatcher);
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
view_dispatcher_set_custom_event_callback(
diff --git a/applications/settings/system/system_settings.c b/applications/settings/system/system_settings.c
index 29fe0cd481..1c2654c5e5 100644
--- a/applications/settings/system/system_settings.c
+++ b/applications/settings/system/system_settings.c
@@ -92,7 +92,7 @@ static void debug_changed(VariableItem* item) {
const char* const heap_trace_mode_text[] = {
"None",
"Main",
-#if FURI_DEBUG
+#ifdef FURI_DEBUG
"Tree",
"All",
#endif
@@ -101,7 +101,7 @@ const char* const heap_trace_mode_text[] = {
const uint32_t heap_trace_mode_value[] = {
FuriHalRtcHeapTrackModeNone,
FuriHalRtcHeapTrackModeMain,
-#if FURI_DEBUG
+#ifdef FURI_DEBUG
FuriHalRtcHeapTrackModeTree,
FuriHalRtcHeapTrackModeAll,
#endif
@@ -220,7 +220,6 @@ SystemSettings* system_settings_alloc(void) {
app->gui = furi_record_open(RECORD_GUI);
app->view_dispatcher = view_dispatcher_alloc();
- view_dispatcher_enable_queue(app->view_dispatcher);
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
diff --git a/applications/system/application.fam b/applications/system/application.fam
index 095ca1ab2c..c5f81defa6 100644
--- a/applications/system/application.fam
+++ b/applications/system/application.fam
@@ -4,7 +4,6 @@ App(
apptype=FlipperAppType.METAPACKAGE,
provides=[
"updater_app",
- "storage_move_to_sd",
"js_app",
"js_app_start",
# "archive",
diff --git a/applications/system/hid_app/assets/Alt_17x10.png b/applications/system/hid_app/assets/Alt_17x10.png
index 78529ca07d..54c4557bad 100644
Binary files a/applications/system/hid_app/assets/Alt_17x10.png and b/applications/system/hid_app/assets/Alt_17x10.png differ
diff --git a/applications/system/hid_app/assets/Alt_active_17x9.png b/applications/system/hid_app/assets/Alt_active_17x9.png
old mode 100755
new mode 100644
index 46a21a2e85..bd1adf606d
Binary files a/applications/system/hid_app/assets/Alt_active_17x9.png and b/applications/system/hid_app/assets/Alt_active_17x9.png differ
diff --git a/applications/system/hid_app/assets/Arr_dwn_7x9.png b/applications/system/hid_app/assets/Arr_dwn_7x9.png
index d4034efc43..dc97d3abcd 100644
Binary files a/applications/system/hid_app/assets/Arr_dwn_7x9.png and b/applications/system/hid_app/assets/Arr_dwn_7x9.png differ
diff --git a/applications/system/hid_app/assets/Arr_up_7x9.png b/applications/system/hid_app/assets/Arr_up_7x9.png
index 28b4236a29..4e199c7d05 100644
Binary files a/applications/system/hid_app/assets/Arr_up_7x9.png and b/applications/system/hid_app/assets/Arr_up_7x9.png differ
diff --git a/applications/system/hid_app/assets/Ble_connected_15x15.png b/applications/system/hid_app/assets/Ble_connected_15x15.png
index 64dab9b530..1301399da9 100644
Binary files a/applications/system/hid_app/assets/Ble_connected_15x15.png and b/applications/system/hid_app/assets/Ble_connected_15x15.png differ
diff --git a/applications/system/hid_app/assets/Ble_disconnected_15x15.png b/applications/system/hid_app/assets/Ble_disconnected_15x15.png
index bd54646d89..f926ce2128 100644
Binary files a/applications/system/hid_app/assets/Ble_disconnected_15x15.png and b/applications/system/hid_app/assets/Ble_disconnected_15x15.png differ
diff --git a/applications/system/hid_app/assets/ButtonDown_7x4.png b/applications/system/hid_app/assets/ButtonDown_7x4.png
index 2954bb6a67..0cda838e05 100644
Binary files a/applications/system/hid_app/assets/ButtonDown_7x4.png and b/applications/system/hid_app/assets/ButtonDown_7x4.png differ
diff --git a/applications/system/hid_app/assets/ButtonF10_5x8.png b/applications/system/hid_app/assets/ButtonF10_5x8.png
index d1a7a04f06..a817cce367 100644
Binary files a/applications/system/hid_app/assets/ButtonF10_5x8.png and b/applications/system/hid_app/assets/ButtonF10_5x8.png differ
diff --git a/applications/system/hid_app/assets/ButtonF11_5x8.png b/applications/system/hid_app/assets/ButtonF11_5x8.png
index 7e177358e8..9ebe40150a 100644
Binary files a/applications/system/hid_app/assets/ButtonF11_5x8.png and b/applications/system/hid_app/assets/ButtonF11_5x8.png differ
diff --git a/applications/system/hid_app/assets/ButtonF12_5x8.png b/applications/system/hid_app/assets/ButtonF12_5x8.png
index 50d2a7dc63..a50b16ca31 100644
Binary files a/applications/system/hid_app/assets/ButtonF12_5x8.png and b/applications/system/hid_app/assets/ButtonF12_5x8.png differ
diff --git a/applications/system/hid_app/assets/ButtonF1_5x8.png b/applications/system/hid_app/assets/ButtonF1_5x8.png
index 7394d27105..53a30974ba 100644
Binary files a/applications/system/hid_app/assets/ButtonF1_5x8.png and b/applications/system/hid_app/assets/ButtonF1_5x8.png differ
diff --git a/applications/system/hid_app/assets/ButtonF2_5x8.png b/applications/system/hid_app/assets/ButtonF2_5x8.png
index 9d922a3858..df28654a5e 100644
Binary files a/applications/system/hid_app/assets/ButtonF2_5x8.png and b/applications/system/hid_app/assets/ButtonF2_5x8.png differ
diff --git a/applications/system/hid_app/assets/ButtonF3_5x8.png b/applications/system/hid_app/assets/ButtonF3_5x8.png
index 95c2dd4f41..a36cd56b9c 100644
Binary files a/applications/system/hid_app/assets/ButtonF3_5x8.png and b/applications/system/hid_app/assets/ButtonF3_5x8.png differ
diff --git a/applications/system/hid_app/assets/ButtonF4_5x8.png b/applications/system/hid_app/assets/ButtonF4_5x8.png
index 602466f4b6..535195859d 100644
Binary files a/applications/system/hid_app/assets/ButtonF4_5x8.png and b/applications/system/hid_app/assets/ButtonF4_5x8.png differ
diff --git a/applications/system/hid_app/assets/ButtonF5_5x8.png b/applications/system/hid_app/assets/ButtonF5_5x8.png
index d73b540527..918017f5a7 100644
Binary files a/applications/system/hid_app/assets/ButtonF5_5x8.png and b/applications/system/hid_app/assets/ButtonF5_5x8.png differ
diff --git a/applications/system/hid_app/assets/ButtonF6_5x8.png b/applications/system/hid_app/assets/ButtonF6_5x8.png
index c50748257a..1009799a56 100644
Binary files a/applications/system/hid_app/assets/ButtonF6_5x8.png and b/applications/system/hid_app/assets/ButtonF6_5x8.png differ
diff --git a/applications/system/hid_app/assets/ButtonF7_5x8.png b/applications/system/hid_app/assets/ButtonF7_5x8.png
index 396c98f510..a6c44ddd8e 100644
Binary files a/applications/system/hid_app/assets/ButtonF7_5x8.png and b/applications/system/hid_app/assets/ButtonF7_5x8.png differ
diff --git a/applications/system/hid_app/assets/ButtonF8_5x8.png b/applications/system/hid_app/assets/ButtonF8_5x8.png
index 6304d7fb88..a789a0f364 100644
Binary files a/applications/system/hid_app/assets/ButtonF8_5x8.png and b/applications/system/hid_app/assets/ButtonF8_5x8.png differ
diff --git a/applications/system/hid_app/assets/ButtonF9_5x8.png b/applications/system/hid_app/assets/ButtonF9_5x8.png
index 148e69580f..222b2e151d 100644
Binary files a/applications/system/hid_app/assets/ButtonF9_5x8.png and b/applications/system/hid_app/assets/ButtonF9_5x8.png differ
diff --git a/applications/system/hid_app/assets/ButtonLeft_4x7.png b/applications/system/hid_app/assets/ButtonLeft_4x7.png
index 0b4655d432..7c43f3b04d 100644
Binary files a/applications/system/hid_app/assets/ButtonLeft_4x7.png and b/applications/system/hid_app/assets/ButtonLeft_4x7.png differ
diff --git a/applications/system/hid_app/assets/ButtonRight_4x7.png b/applications/system/hid_app/assets/ButtonRight_4x7.png
index 8e1c74c1c0..31de21c0e2 100644
Binary files a/applications/system/hid_app/assets/ButtonRight_4x7.png and b/applications/system/hid_app/assets/ButtonRight_4x7.png differ
diff --git a/applications/system/hid_app/assets/ButtonUp_7x4.png b/applications/system/hid_app/assets/ButtonUp_7x4.png
index 1be79328b4..48d0f9f018 100644
Binary files a/applications/system/hid_app/assets/ButtonUp_7x4.png and b/applications/system/hid_app/assets/ButtonUp_7x4.png differ
diff --git a/applications/system/hid_app/assets/Button_18x18.png b/applications/system/hid_app/assets/Button_18x18.png
index 30a5b4fab2..2334dd8be0 100644
Binary files a/applications/system/hid_app/assets/Button_18x18.png and b/applications/system/hid_app/assets/Button_18x18.png differ
diff --git a/applications/system/hid_app/assets/Cmd_17x10.png b/applications/system/hid_app/assets/Cmd_17x10.png
index b29da07b71..68b3be6068 100644
Binary files a/applications/system/hid_app/assets/Cmd_17x10.png and b/applications/system/hid_app/assets/Cmd_17x10.png differ
diff --git a/applications/system/hid_app/assets/Cmd_active_17x9.png b/applications/system/hid_app/assets/Cmd_active_17x9.png
old mode 100755
new mode 100644
index 9d31b4eb3e..9f9cdea996
Binary files a/applications/system/hid_app/assets/Cmd_active_17x9.png and b/applications/system/hid_app/assets/Cmd_active_17x9.png differ
diff --git a/applications/system/hid_app/assets/Ctrl_17x10.png b/applications/system/hid_app/assets/Ctrl_17x10.png
index 05be3292eb..c70dd2c16a 100644
Binary files a/applications/system/hid_app/assets/Ctrl_17x10.png and b/applications/system/hid_app/assets/Ctrl_17x10.png differ
diff --git a/applications/system/hid_app/assets/Ctrl_active_17x9.png b/applications/system/hid_app/assets/Ctrl_active_17x9.png
old mode 100755
new mode 100644
index 6fade6dd77..c16df04ee4
Binary files a/applications/system/hid_app/assets/Ctrl_active_17x9.png and b/applications/system/hid_app/assets/Ctrl_active_17x9.png differ
diff --git a/applications/system/hid_app/assets/Del_17x10.png b/applications/system/hid_app/assets/Del_17x10.png
index 95cbf7d5bd..3fd55e92f2 100644
Binary files a/applications/system/hid_app/assets/Del_17x10.png and b/applications/system/hid_app/assets/Del_17x10.png differ
diff --git a/applications/system/hid_app/assets/DolphinDone_80x58.png b/applications/system/hid_app/assets/DolphinDone_80x58.png
index 594d62d529..881aaa8d2a 100644
Binary files a/applications/system/hid_app/assets/DolphinDone_80x58.png and b/applications/system/hid_app/assets/DolphinDone_80x58.png differ
diff --git a/applications/system/hid_app/assets/Enter_11x7.png b/applications/system/hid_app/assets/Enter_11x7.png
old mode 100755
new mode 100644
index d41b8feffb..1b07df2cfa
Binary files a/applications/system/hid_app/assets/Enter_11x7.png and b/applications/system/hid_app/assets/Enter_11x7.png differ
diff --git a/applications/system/hid_app/assets/Esc_17x10.png b/applications/system/hid_app/assets/Esc_17x10.png
index 83a6f225fc..bd7e3cb963 100644
Binary files a/applications/system/hid_app/assets/Esc_17x10.png and b/applications/system/hid_app/assets/Esc_17x10.png differ
diff --git a/applications/system/hid_app/assets/Ok_btn_9x9.png b/applications/system/hid_app/assets/Ok_btn_9x9.png
index 9a1539da20..ceff4e8a88 100644
Binary files a/applications/system/hid_app/assets/Ok_btn_9x9.png and b/applications/system/hid_app/assets/Ok_btn_9x9.png differ
diff --git a/applications/system/hid_app/assets/Pin_arrow_down_7x9.png b/applications/system/hid_app/assets/Pin_arrow_down_7x9.png
index 9687397afa..dc97d3abcd 100644
Binary files a/applications/system/hid_app/assets/Pin_arrow_down_7x9.png and b/applications/system/hid_app/assets/Pin_arrow_down_7x9.png differ
diff --git a/applications/system/hid_app/assets/Pin_arrow_left_9x7.png b/applications/system/hid_app/assets/Pin_arrow_left_9x7.png
index fb4ded78fd..9b6ccb51f5 100644
Binary files a/applications/system/hid_app/assets/Pin_arrow_left_9x7.png and b/applications/system/hid_app/assets/Pin_arrow_left_9x7.png differ
diff --git a/applications/system/hid_app/assets/Pin_arrow_right_9x7.png b/applications/system/hid_app/assets/Pin_arrow_right_9x7.png
index 97648d1761..b79bca2e3e 100644
Binary files a/applications/system/hid_app/assets/Pin_arrow_right_9x7.png and b/applications/system/hid_app/assets/Pin_arrow_right_9x7.png differ
diff --git a/applications/system/hid_app/assets/Pin_arrow_up_7x9.png b/applications/system/hid_app/assets/Pin_arrow_up_7x9.png
index a91a6fd5e9..4e199c7d05 100644
Binary files a/applications/system/hid_app/assets/Pin_arrow_up_7x9.png and b/applications/system/hid_app/assets/Pin_arrow_up_7x9.png differ
diff --git a/applications/system/hid_app/assets/Pin_back_arrow_10x8.png b/applications/system/hid_app/assets/Pin_back_arrow_10x8.png
index 3bafabd144..64b25db5af 100644
Binary files a/applications/system/hid_app/assets/Pin_back_arrow_10x8.png and b/applications/system/hid_app/assets/Pin_back_arrow_10x8.png differ
diff --git a/applications/system/hid_app/assets/Pressed_Button_13x13.png b/applications/system/hid_app/assets/Pressed_Button_13x13.png
index 823926b842..d0e2c3a373 100644
Binary files a/applications/system/hid_app/assets/Pressed_Button_13x13.png and b/applications/system/hid_app/assets/Pressed_Button_13x13.png differ
diff --git a/applications/system/hid_app/assets/Return_10x7.png b/applications/system/hid_app/assets/Return_10x7.png
index ffa1e9acaf..ebf0a77777 100644
Binary files a/applications/system/hid_app/assets/Return_10x7.png and b/applications/system/hid_app/assets/Return_10x7.png differ
diff --git a/applications/system/hid_app/assets/Shift_active_7x9.png b/applications/system/hid_app/assets/Shift_active_7x9.png
index 1ec9ce11e8..1dbb762f95 100644
Binary files a/applications/system/hid_app/assets/Shift_active_7x9.png and b/applications/system/hid_app/assets/Shift_active_7x9.png differ
diff --git a/applications/system/hid_app/assets/Shift_inactive_7x9.png b/applications/system/hid_app/assets/Shift_inactive_7x9.png
index 1cd97076ed..696e7e9eed 100644
Binary files a/applications/system/hid_app/assets/Shift_inactive_7x9.png and b/applications/system/hid_app/assets/Shift_inactive_7x9.png differ
diff --git a/applications/system/hid_app/assets/Space_60x18.png b/applications/system/hid_app/assets/Space_60x18.png
index e29f50ae92..7d2116ad52 100644
Binary files a/applications/system/hid_app/assets/Space_60x18.png and b/applications/system/hid_app/assets/Space_60x18.png differ
diff --git a/applications/system/hid_app/assets/Space_65x18.png b/applications/system/hid_app/assets/Space_65x18.png
index b60ae50970..eb417f6746 100644
Binary files a/applications/system/hid_app/assets/Space_65x18.png and b/applications/system/hid_app/assets/Space_65x18.png differ
diff --git a/applications/system/hid_app/assets/Tab_17x10.png b/applications/system/hid_app/assets/Tab_17x10.png
index 4d8471483e..0be2d938c8 100644
Binary files a/applications/system/hid_app/assets/Tab_17x10.png and b/applications/system/hid_app/assets/Tab_17x10.png differ
diff --git a/applications/system/hid_app/assets/Tab_19x12.png b/applications/system/hid_app/assets/Tab_19x12.png
old mode 100755
new mode 100644
index 4dbde3babf..6748d1f497
Binary files a/applications/system/hid_app/assets/Tab_19x12.png and b/applications/system/hid_app/assets/Tab_19x12.png differ
diff --git a/applications/system/hid_app/assets/Voldwn_6x6.png b/applications/system/hid_app/assets/Voldwn_6x6.png
index d7a82a2df8..d6d7e286a3 100644
Binary files a/applications/system/hid_app/assets/Voldwn_6x6.png and b/applications/system/hid_app/assets/Voldwn_6x6.png differ
diff --git a/applications/system/hid_app/assets/Volup_8x6.png b/applications/system/hid_app/assets/Volup_8x6.png
index 4b7ec66d65..66477bc7b5 100644
Binary files a/applications/system/hid_app/assets/Volup_8x6.png and b/applications/system/hid_app/assets/Volup_8x6.png differ
diff --git a/applications/system/hid_app/assets/apostrophe_button_9x11.png b/applications/system/hid_app/assets/apostrophe_button_9x11.png
index 0f54f0e2b1..a4b2cab8ed 100644
Binary files a/applications/system/hid_app/assets/apostrophe_button_9x11.png and b/applications/system/hid_app/assets/apostrophe_button_9x11.png differ
diff --git a/applications/system/hid_app/assets/backslash_button_9x11.png b/applications/system/hid_app/assets/backslash_button_9x11.png
old mode 100755
new mode 100644
index 6cac74a57a..e579e113ec
Binary files a/applications/system/hid_app/assets/backslash_button_9x11.png and b/applications/system/hid_app/assets/backslash_button_9x11.png differ
diff --git a/applications/system/hid_app/assets/backspace_19x11.png b/applications/system/hid_app/assets/backspace_19x11.png
old mode 100755
new mode 100644
index caf92807b5..80e91c3c21
Binary files a/applications/system/hid_app/assets/backspace_19x11.png and b/applications/system/hid_app/assets/backspace_19x11.png differ
diff --git a/applications/system/hid_app/assets/backspace_hovered_9x11.png b/applications/system/hid_app/assets/backspace_hovered_9x11.png
old mode 100755
new mode 100644
index 17cb1b7403..a4acbf0db8
Binary files a/applications/system/hid_app/assets/backspace_hovered_9x11.png and b/applications/system/hid_app/assets/backspace_hovered_9x11.png differ
diff --git a/applications/system/hid_app/assets/backtick_button_9x11.png b/applications/system/hid_app/assets/backtick_button_9x11.png
old mode 100755
new mode 100644
index 1e5955a03a..0d2b8942c4
Binary files a/applications/system/hid_app/assets/backtick_button_9x11.png and b/applications/system/hid_app/assets/backtick_button_9x11.png differ
diff --git a/applications/system/hid_app/assets/brace_left_button_9x11.png b/applications/system/hid_app/assets/brace_left_button_9x11.png
old mode 100755
new mode 100644
index a61db48f36..56eb690434
Binary files a/applications/system/hid_app/assets/brace_left_button_9x11.png and b/applications/system/hid_app/assets/brace_left_button_9x11.png differ
diff --git a/applications/system/hid_app/assets/brace_right_button_9x11.png b/applications/system/hid_app/assets/brace_right_button_9x11.png
old mode 100755
new mode 100644
index bf6b927f19..8df2c19ce8
Binary files a/applications/system/hid_app/assets/brace_right_button_9x11.png and b/applications/system/hid_app/assets/brace_right_button_9x11.png differ
diff --git a/applications/system/hid_app/assets/equals_button_9x11.png b/applications/system/hid_app/assets/equals_button_9x11.png
old mode 100755
new mode 100644
index 8fe8afe34d..4d3232af24
Binary files a/applications/system/hid_app/assets/equals_button_9x11.png and b/applications/system/hid_app/assets/equals_button_9x11.png differ
diff --git a/applications/system/hid_app/assets/hash_button_9x11.png b/applications/system/hid_app/assets/hash_button_9x11.png
old mode 100755
new mode 100644
index bddc7aacea..0a3974f643
Binary files a/applications/system/hid_app/assets/hash_button_9x11.png and b/applications/system/hid_app/assets/hash_button_9x11.png differ
diff --git a/applications/system/hid_app/assets/percent_button_9x11.png b/applications/system/hid_app/assets/percent_button_9x11.png
old mode 100755
new mode 100644
index ce12dcbf1e..72190605bc
Binary files a/applications/system/hid_app/assets/percent_button_9x11.png and b/applications/system/hid_app/assets/percent_button_9x11.png differ
diff --git a/applications/system/hid_app/assets/quote_button_9x11.png b/applications/system/hid_app/assets/quote_button_9x11.png
old mode 100755
new mode 100644
index e96d29ddcd..9fabcd26cd
Binary files a/applications/system/hid_app/assets/quote_button_9x11.png and b/applications/system/hid_app/assets/quote_button_9x11.png differ
diff --git a/applications/system/hid_app/assets/slash_button_9x11.png b/applications/system/hid_app/assets/slash_button_9x11.png
old mode 100755
new mode 100644
index 60871320ff..b877b9106d
Binary files a/applications/system/hid_app/assets/slash_button_9x11.png and b/applications/system/hid_app/assets/slash_button_9x11.png differ
diff --git a/applications/system/hid_app/assets/sq_bracket_left_button_9x11.png b/applications/system/hid_app/assets/sq_bracket_left_button_9x11.png
old mode 100755
new mode 100644
index 0983db1296..ea50cd657c
Binary files a/applications/system/hid_app/assets/sq_bracket_left_button_9x11.png and b/applications/system/hid_app/assets/sq_bracket_left_button_9x11.png differ
diff --git a/applications/system/hid_app/assets/sq_bracket_right_button_9x11.png b/applications/system/hid_app/assets/sq_bracket_right_button_9x11.png
old mode 100755
new mode 100644
index 48f9c77e4d..bd18bbfad6
Binary files a/applications/system/hid_app/assets/sq_bracket_right_button_9x11.png and b/applications/system/hid_app/assets/sq_bracket_right_button_9x11.png differ
diff --git a/applications/system/hid_app/assets/underscore_button_9x11.png b/applications/system/hid_app/assets/underscore_button_9x11.png
old mode 100755
new mode 100644
index eb000cba55..7ab5cdbab5
Binary files a/applications/system/hid_app/assets/underscore_button_9x11.png and b/applications/system/hid_app/assets/underscore_button_9x11.png differ
diff --git a/applications/system/hid_app/hid.c b/applications/system/hid_app/hid.c
index 586d198a9b..e297e07387 100644
--- a/applications/system/hid_app/hid.c
+++ b/applications/system/hid_app/hid.c
@@ -72,7 +72,6 @@ Hid* hid_alloc() {
// View dispatcher
app->view_dispatcher = view_dispatcher_alloc();
- view_dispatcher_enable_queue(app->view_dispatcher);
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);
diff --git a/applications/system/hid_app/hid_ble_10px.png b/applications/system/hid_app/hid_ble_10px.png
index d4d30afe04..27355f8dba 100644
Binary files a/applications/system/hid_app/hid_ble_10px.png and b/applications/system/hid_app/hid_ble_10px.png differ
diff --git a/applications/system/js_app/icon.png b/applications/system/js_app/icon.png
index 77ac76337e..48210a0497 100644
Binary files a/applications/system/js_app/icon.png and b/applications/system/js_app/icon.png differ
diff --git a/applications/system/js_app/js_app.c b/applications/system/js_app/js_app.c
index 24cb545891..20c4ce7336 100644
--- a/applications/system/js_app/js_app.c
+++ b/applications/system/js_app/js_app.c
@@ -69,7 +69,6 @@ static JsApp* js_app_alloc(void) {
app->loading = loading_alloc();
app->gui = furi_record_open("gui");
- view_dispatcher_enable_queue(app->view_dispatcher);
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
view_dispatcher_add_view(
app->view_dispatcher, JsAppViewLoading, loading_get_view(app->loading));
diff --git a/applications/system/js_app/modules/js_keyboard.c b/applications/system/js_app/modules/js_keyboard.c
index 65373db785..53f48d01de 100644
--- a/applications/system/js_app/modules/js_keyboard.c
+++ b/applications/system/js_app/modules/js_keyboard.c
@@ -92,10 +92,9 @@ static void js_keyboard_text(struct mjs* mjs) {
view_holder_set_back_callback(keyboard->view_holder, keyboard_exit, keyboard);
view_holder_set_view(keyboard->view_holder, text_input_get_view(keyboard->text_input));
- view_holder_start(keyboard->view_holder);
api_lock_wait_unlock(keyboard->lock);
- view_holder_stop(keyboard->view_holder);
+ view_holder_set_view(keyboard->view_holder, NULL);
view_holder_free(keyboard->view_holder);
furi_record_close(RECORD_GUI);
@@ -148,10 +147,9 @@ static void js_keyboard_byte(struct mjs* mjs) {
view_holder_set_back_callback(keyboard->view_holder, keyboard_exit, keyboard);
view_holder_set_view(keyboard->view_holder, byte_input_get_view(keyboard->byte_input));
- view_holder_start(keyboard->view_holder);
api_lock_wait_unlock(keyboard->lock);
- view_holder_stop(keyboard->view_holder);
+ view_holder_set_view(keyboard->view_holder, NULL);
view_holder_free(keyboard->view_holder);
furi_record_close(RECORD_GUI);
diff --git a/applications/system/js_app/modules/js_submenu.c b/applications/system/js_app/modules/js_submenu.c
index 058b32fd09..5ab9bef77c 100644
--- a/applications/system/js_app/modules/js_submenu.c
+++ b/applications/system/js_app/modules/js_submenu.c
@@ -97,10 +97,9 @@ static void js_submenu_show(struct mjs* mjs) {
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_set_view(submenu->view_holder, NULL);
view_holder_free(submenu->view_holder);
furi_record_close(RECORD_GUI);
api_lock_free(submenu->lock);
diff --git a/applications/system/js_app/modules/js_textbox.c b/applications/system/js_app/modules/js_textbox.c
index 33798b2965..b90dbc153a 100644
--- a/applications/system/js_app/modules/js_textbox.c
+++ b/applications/system/js_app/modules/js_textbox.c
@@ -125,7 +125,7 @@ static void js_textbox_is_open(struct mjs* mjs) {
static void textbox_callback(void* context, uint32_t arg) {
UNUSED(arg);
JsTextboxInst* textbox = context;
- view_holder_stop(textbox->view_holder);
+ view_holder_set_view(textbox->view_holder, NULL);
textbox->is_shown = false;
}
@@ -145,7 +145,7 @@ static void js_textbox_show(struct mjs* mjs) {
return;
}
- view_holder_start(textbox->view_holder);
+ view_holder_set_view(textbox->view_holder, text_box_get_view(textbox->text_box));
textbox->is_shown = true;
mjs_return(mjs, MJS_UNDEFINED);
@@ -155,7 +155,7 @@ static void js_textbox_close(struct mjs* mjs) {
JsTextboxInst* textbox = get_this_ctx(mjs);
if(!check_arg_count(mjs, 0)) return;
- view_holder_stop(textbox->view_holder);
+ view_holder_set_view(textbox->view_holder, NULL);
textbox->is_shown = false;
mjs_return(mjs, MJS_UNDEFINED);
@@ -180,7 +180,6 @@ static void* js_textbox_create(struct mjs* mjs, mjs_val_t* object) {
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;
@@ -189,7 +188,7 @@ static void* js_textbox_create(struct mjs* mjs, mjs_val_t* object) {
static void js_textbox_destroy(void* inst) {
JsTextboxInst* textbox = inst;
- view_holder_stop(textbox->view_holder);
+ view_holder_set_view(textbox->view_holder, NULL);
view_holder_free(textbox->view_holder);
textbox->view_holder = NULL;
diff --git a/applications/system/js_app/modules/js_widget.c b/applications/system/js_app/modules/js_widget.c
index 0d6aeb1dba..6c8e79b2ec 100644
--- a/applications/system/js_app/modules/js_widget.c
+++ b/applications/system/js_app/modules/js_widget.c
@@ -759,7 +759,7 @@ static void js_widget_is_open(struct mjs* mjs) {
static void widget_callback(void* context, uint32_t arg) {
UNUSED(arg);
JsWidgetInst* widget = context;
- view_holder_stop(widget->view_holder);
+ view_holder_set_view(widget->view_holder, NULL);
widget->is_shown = false;
}
@@ -779,7 +779,7 @@ static void js_widget_show(struct mjs* mjs) {
return;
}
- view_holder_start(widget->view_holder);
+ view_holder_set_view(widget->view_holder, widget->view);
widget->is_shown = true;
mjs_return(mjs, MJS_UNDEFINED);
@@ -789,7 +789,7 @@ static void js_widget_close(struct mjs* mjs) {
JsWidgetInst* widget = get_this_ctx(mjs);
if(!check_arg_count(mjs, 0)) return;
- view_holder_stop(widget->view_holder);
+ view_holder_set_view(widget->view_holder, NULL);
widget->is_shown = false;
mjs_return(mjs, MJS_UNDEFINED);
@@ -874,7 +874,7 @@ static void* js_widget_create(struct mjs* mjs, mjs_val_t* object) {
static void js_widget_destroy(void* inst) {
JsWidgetInst* widget = inst;
- view_holder_stop(widget->view_holder);
+ view_holder_set_view(widget->view_holder, NULL);
view_holder_free(widget->view_holder);
widget->view_holder = NULL;
diff --git a/applications/system/snake_game/snake_10px.png b/applications/system/snake_game/snake_10px.png
index 52d9fa7e0e..3ace2de40d 100644
Binary files a/applications/system/snake_game/snake_10px.png and b/applications/system/snake_game/snake_10px.png differ
diff --git a/applications/system/storage_move_to_sd/application.fam b/applications/system/storage_move_to_sd/application.fam
deleted file mode 100644
index de47de0551..0000000000
--- a/applications/system/storage_move_to_sd/application.fam
+++ /dev/null
@@ -1,18 +0,0 @@
-App(
- appid="storage_move_to_sd",
- name="StorageMoveToSd",
- apptype=FlipperAppType.SYSTEM,
- entry_point="storage_move_to_sd_app",
- requires=["gui", "storage"],
- provides=["storage_move_to_sd_start"],
- stack_size=2 * 1024,
- order=30,
-)
-
-App(
- appid="storage_move_to_sd_start",
- apptype=FlipperAppType.STARTUP,
- entry_point="storage_move_to_sd_start",
- requires=["storage"],
- order=120,
-)
diff --git a/applications/system/storage_move_to_sd/scenes/storage_move_to_sd_scene.c b/applications/system/storage_move_to_sd/scenes/storage_move_to_sd_scene.c
deleted file mode 100644
index 011bf47df9..0000000000
--- a/applications/system/storage_move_to_sd/scenes/storage_move_to_sd_scene.c
+++ /dev/null
@@ -1,30 +0,0 @@
-#include "storage_move_to_sd_scene.h"
-
-// Generate scene on_enter handlers array
-#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
-void (*const storage_move_to_sd_on_enter_handlers[])(void*) = {
-#include "storage_move_to_sd_scene_config.h"
-};
-#undef ADD_SCENE
-
-// Generate scene on_event handlers array
-#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
-bool (*const storage_move_to_sd_on_event_handlers[])(void* context, SceneManagerEvent event) = {
-#include "storage_move_to_sd_scene_config.h"
-};
-#undef ADD_SCENE
-
-// Generate scene on_exit handlers array
-#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
-void (*const storage_move_to_sd_on_exit_handlers[])(void* context) = {
-#include "storage_move_to_sd_scene_config.h"
-};
-#undef ADD_SCENE
-
-// Initialize scene handlers configuration structure
-const SceneManagerHandlers storage_move_to_sd_scene_handlers = {
- .on_enter_handlers = storage_move_to_sd_on_enter_handlers,
- .on_event_handlers = storage_move_to_sd_on_event_handlers,
- .on_exit_handlers = storage_move_to_sd_on_exit_handlers,
- .scene_num = StorageMoveToSdSceneNum,
-};
diff --git a/applications/system/storage_move_to_sd/scenes/storage_move_to_sd_scene_config.h b/applications/system/storage_move_to_sd/scenes/storage_move_to_sd_scene_config.h
deleted file mode 100644
index 1d7b2d25b8..0000000000
--- a/applications/system/storage_move_to_sd/scenes/storage_move_to_sd_scene_config.h
+++ /dev/null
@@ -1,2 +0,0 @@
-ADD_SCENE(storage_move_to_sd, confirm, Confirm)
-ADD_SCENE(storage_move_to_sd, progress, Progress)
diff --git a/applications/system/storage_move_to_sd/scenes/storage_move_to_sd_scene_confirm.c b/applications/system/storage_move_to_sd/scenes/storage_move_to_sd_scene_confirm.c
deleted file mode 100644
index 08c9e2d7fc..0000000000
--- a/applications/system/storage_move_to_sd/scenes/storage_move_to_sd_scene_confirm.c
+++ /dev/null
@@ -1,70 +0,0 @@
-#include "../storage_move_to_sd.h"
-#include
-#include
-#include
-
-static void storage_move_to_sd_scene_confirm_widget_callback(
- GuiButtonType result,
- InputType type,
- void* context) {
- StorageMoveToSd* app = context;
- furi_assert(app);
- if(type == InputTypeShort) {
- if(result == GuiButtonTypeRight) {
- view_dispatcher_send_custom_event(app->view_dispatcher, MoveToSdCustomEventConfirm);
- } else if(result == GuiButtonTypeLeft) {
- view_dispatcher_send_custom_event(app->view_dispatcher, MoveToSdCustomEventExit);
- }
- }
-}
-
-void storage_move_to_sd_scene_confirm_on_enter(void* context) {
- StorageMoveToSd* app = context;
-
- widget_add_button_element(
- app->widget,
- GuiButtonTypeLeft,
- "Cancel",
- storage_move_to_sd_scene_confirm_widget_callback,
- app);
- widget_add_button_element(
- app->widget,
- GuiButtonTypeRight,
- "Confirm",
- storage_move_to_sd_scene_confirm_widget_callback,
- app);
-
- widget_add_string_element(
- app->widget, 64, 10, AlignCenter, AlignCenter, FontPrimary, "SD card inserted");
- widget_add_string_multiline_element(
- app->widget,
- 64,
- 32,
- AlignCenter,
- AlignCenter,
- FontSecondary,
- "Move data from\ninternal storage to SD card?");
-
- view_dispatcher_switch_to_view(app->view_dispatcher, StorageMoveToSdViewWidget);
-}
-
-bool storage_move_to_sd_scene_confirm_on_event(void* context, SceneManagerEvent event) {
- StorageMoveToSd* app = context;
- bool consumed = false;
-
- if(event.type == SceneManagerEventTypeCustom) {
- if(event.event == MoveToSdCustomEventConfirm) {
- scene_manager_next_scene(app->scene_manager, StorageMoveToSdProgress);
- consumed = true;
- } else if(event.event == MoveToSdCustomEventExit) {
- view_dispatcher_stop(app->view_dispatcher);
- }
- }
-
- return consumed;
-}
-
-void storage_move_to_sd_scene_confirm_on_exit(void* context) {
- StorageMoveToSd* app = context;
- widget_reset(app->widget);
-}
diff --git a/applications/system/storage_move_to_sd/scenes/storage_move_to_sd_scene_progress.c b/applications/system/storage_move_to_sd/scenes/storage_move_to_sd_scene_progress.c
deleted file mode 100644
index 7aa951bd83..0000000000
--- a/applications/system/storage_move_to_sd/scenes/storage_move_to_sd_scene_progress.c
+++ /dev/null
@@ -1,31 +0,0 @@
-#include "../storage_move_to_sd.h"
-
-void storage_move_to_sd_scene_progress_on_enter(void* context) {
- StorageMoveToSd* app = context;
-
- widget_add_string_element(
- app->widget, 64, 10, AlignCenter, AlignCenter, FontPrimary, "Moving...");
-
- view_dispatcher_switch_to_view(app->view_dispatcher, StorageMoveToSdViewWidget);
-
- storage_move_to_sd_perform();
- view_dispatcher_send_custom_event(app->view_dispatcher, MoveToSdCustomEventExit);
-}
-
-bool storage_move_to_sd_scene_progress_on_event(void* context, SceneManagerEvent event) {
- StorageMoveToSd* app = context;
- bool consumed = false;
-
- if(event.type == SceneManagerEventTypeCustom) {
- view_dispatcher_stop(app->view_dispatcher);
- } else if(event.type == SceneManagerEventTypeBack) {
- consumed = true;
- }
-
- return consumed;
-}
-
-void storage_move_to_sd_scene_progress_on_exit(void* context) {
- StorageMoveToSd* app = context;
- widget_reset(app->widget);
-}
diff --git a/applications/system/storage_move_to_sd/storage_move_to_sd.c b/applications/system/storage_move_to_sd/storage_move_to_sd.c
deleted file mode 100644
index 44e73c6895..0000000000
--- a/applications/system/storage_move_to_sd/storage_move_to_sd.c
+++ /dev/null
@@ -1,203 +0,0 @@
-#include "storage_move_to_sd.h"
-
-#include