Merge branch 'dev' into release-candidate
This commit is contained in:
		
						commit
						24e7bda854
					
				
							
								
								
									
										83
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										83
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							| @ -14,8 +14,8 @@ env: | |||||||
|   DEFAULT_TARGET: f7 |   DEFAULT_TARGET: f7 | ||||||
| 
 | 
 | ||||||
| jobs: | jobs: | ||||||
|   build: |   main: | ||||||
|     runs-on: [self-hosted] |     runs-on: [self-hosted,Office] | ||||||
|     steps: |     steps: | ||||||
|       - name: 'Cleanup workspace' |       - name: 'Cleanup workspace' | ||||||
|         uses: AutoModality/action-clean@v1 |         uses: AutoModality/action-clean@v1 | ||||||
| @ -35,13 +35,6 @@ jobs: | |||||||
|           submodules: true |           submodules: true | ||||||
|           ref: ${{ github.event.pull_request.head.sha }} |           ref: ${{ github.event.pull_request.head.sha }} | ||||||
| 
 | 
 | ||||||
|       - name: 'Docker cache' |  | ||||||
|         uses: satackey/action-docker-layer-caching@v0.0.11 |  | ||||||
|         continue-on-error: true |  | ||||||
|         with: |  | ||||||
|           key: docker-cache-${{ hashFiles('docker/**') }}-{hash} |  | ||||||
|           restore-keys: docker-cache-${{ hashFiles('docker/**') }}- |  | ||||||
| 
 |  | ||||||
|       - name: 'Build docker image' |       - name: 'Build docker image' | ||||||
|         uses: ./.github/actions/docker |         uses: ./.github/actions/docker | ||||||
| 
 | 
 | ||||||
| @ -78,6 +71,14 @@ jobs: | |||||||
|         run: | |         run: | | ||||||
|           tar czpf artifacts/flipper-z-any-scripts-${{steps.names.outputs.suffix}}.tgz scripts |           tar czpf artifacts/flipper-z-any-scripts-${{steps.names.outputs.suffix}}.tgz scripts | ||||||
| 
 | 
 | ||||||
|  |       - name: 'Rebuild Assets' | ||||||
|  |         uses: ./.github/actions/docker | ||||||
|  |         with: | ||||||
|  |           run: | | ||||||
|  |             set -e | ||||||
|  |             make -C assets clean | ||||||
|  |             make -C assets | ||||||
|  | 
 | ||||||
|       - name: 'Build the firmware in docker' |       - name: 'Build the firmware in docker' | ||||||
|         uses: ./.github/actions/docker |         uses: ./.github/actions/docker | ||||||
|         with: |         with: | ||||||
| @ -85,7 +86,7 @@ jobs: | |||||||
|             set -e |             set -e | ||||||
|             for TARGET in ${TARGETS} |             for TARGET in ${TARGETS} | ||||||
|             do |             do | ||||||
|               make TARGET=${TARGET} |               make TARGET=${TARGET} ${{ startsWith(github.ref, 'refs/tags') && 'DEBUG=0 COMPACT=1' || '' }} | ||||||
|             done |             done | ||||||
| 
 | 
 | ||||||
|       - name: 'Move upload files' |       - name: 'Move upload files' | ||||||
| @ -149,3 +150,65 @@ jobs: | |||||||
|           body: | |           body: | | ||||||
|             [Click here](https://update.flipperzero.one/?url=https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.artifacts-path}}/flipper-z-${{steps.names.outputs.default-target}}-full-${{steps.names.outputs.suffix}}.dfu&channel=${{steps.names.outputs.artifacts-path}}&version=${{steps.names.outputs.short-hash}}&target=${{steps.names.outputs.default-target}}) to flash the `${{steps.names.outputs.short-hash}}` version of this branch via WebUSB. |             [Click here](https://update.flipperzero.one/?url=https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.artifacts-path}}/flipper-z-${{steps.names.outputs.default-target}}-full-${{steps.names.outputs.suffix}}.dfu&channel=${{steps.names.outputs.artifacts-path}}&version=${{steps.names.outputs.short-hash}}&target=${{steps.names.outputs.default-target}}) to flash the `${{steps.names.outputs.short-hash}}` version of this branch via WebUSB. | ||||||
|           edit-mode: replace |           edit-mode: replace | ||||||
|  | 
 | ||||||
|  |   compact: | ||||||
|  |     if: ${{ !startsWith(github.ref, 'refs/tags') }} | ||||||
|  |     runs-on: [self-hosted,koteeq] | ||||||
|  |     steps: | ||||||
|  |       - name: 'Cleanup workspace' | ||||||
|  |         uses: AutoModality/action-clean@v1 | ||||||
|  | 
 | ||||||
|  |       - name: 'Decontaminate previous build leftovers' | ||||||
|  |         run: | | ||||||
|  |           if [ -d .git ] | ||||||
|  |           then | ||||||
|  |             git submodule status \ | ||||||
|  |               || git checkout `git rev-list --max-parents=0 HEAD | tail -n 1` | ||||||
|  |           fi | ||||||
|  | 
 | ||||||
|  |       - name: 'Checkout code' | ||||||
|  |         uses: actions/checkout@v2 | ||||||
|  |         with: | ||||||
|  |           fetch-depth: 0 | ||||||
|  |           submodules: true | ||||||
|  |           ref: ${{ github.event.pull_request.head.sha }} | ||||||
|  | 
 | ||||||
|  |       - name: 'Build docker image' | ||||||
|  |         uses: ./.github/actions/docker | ||||||
|  |        | ||||||
|  |       - name: 'Generate suffix and folder name' | ||||||
|  |         id: names | ||||||
|  |         run: | | ||||||
|  |           REF=${{ github.ref }} | ||||||
|  |           if [[ ${{ github.event_name }} == 'pull_request' ]]; then | ||||||
|  |             REF=${{ github.head_ref }} | ||||||
|  |           fi | ||||||
|  |           BRANCH_OR_TAG=${REF#refs/*/} | ||||||
|  |           SHA=$(git rev-parse --short HEAD) | ||||||
|  |            | ||||||
|  |           if [[ "${{ github.ref }}" == "refs/tags/"* ]]; then | ||||||
|  |             SUFFIX=${BRANCH_OR_TAG//\//_} | ||||||
|  |           else | ||||||
|  |             SUFFIX=${BRANCH_OR_TAG//\//_}-$(date +'%d%m%Y')-${SHA} | ||||||
|  |           fi | ||||||
|  |            | ||||||
|  |           echo "WORKFLOW_BRANCH_OR_TAG=${BRANCH_OR_TAG}" >> $GITHUB_ENV | ||||||
|  |           echo "DIST_SUFFIX=${SUFFIX}" >> $GITHUB_ENV | ||||||
|  | 
 | ||||||
|  |       - name: 'Rebuild Assets' | ||||||
|  |         uses: ./.github/actions/docker | ||||||
|  |         with: | ||||||
|  |           run: | | ||||||
|  |             set -e | ||||||
|  |             make -C assets clean | ||||||
|  |             make -C assets | ||||||
|  | 
 | ||||||
|  |       - name: 'Build the firmware in docker' | ||||||
|  |         uses: ./.github/actions/docker | ||||||
|  |         with: | ||||||
|  |           run: | | ||||||
|  |             set -e | ||||||
|  |             for TARGET in ${TARGETS} | ||||||
|  |             do | ||||||
|  |               make TARGET=${TARGET} DEBUG=0 COMPACT=1 | ||||||
|  |             done | ||||||
|  | |||||||
							
								
								
									
										17
									
								
								.github/workflows/check_submodules.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								.github/workflows/check_submodules.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | |||||||
|  | name: 'Check submodules' | ||||||
|  | 
 | ||||||
|  | on: | ||||||
|  |   pull_request: | ||||||
|  | 
 | ||||||
|  | jobs: | ||||||
|  |   protobuf: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     steps: | ||||||
|  |     - name: 'Checkout code' | ||||||
|  |       uses: actions/checkout@v2 | ||||||
|  |     - name: 'Check submodule commit branch' | ||||||
|  |       uses: jtmullen/submodule-branch-check-action@v1 | ||||||
|  |       with: | ||||||
|  |         path: assets/protobuf | ||||||
|  |         branch: dev | ||||||
|  |         fetch_depth: 50 | ||||||
							
								
								
									
										6
									
								
								.github/workflows/lint_c.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/lint_c.yml
									
									
									
									
										vendored
									
									
								
							| @ -14,7 +14,7 @@ env: | |||||||
| 
 | 
 | ||||||
| jobs: | jobs: | ||||||
|   lint_c_cpp: |   lint_c_cpp: | ||||||
|     runs-on: [self-hosted] |     runs-on: [self-hosted,Office] | ||||||
|     steps: |     steps: | ||||||
|       - name: 'Cleanup workspace' |       - name: 'Cleanup workspace' | ||||||
|         uses: AutoModality/action-clean@v1 |         uses: AutoModality/action-clean@v1 | ||||||
| @ -47,7 +47,7 @@ jobs: | |||||||
|         id: syntax_check |         id: syntax_check | ||||||
|         uses: ./.github/actions/docker |         uses: ./.github/actions/docker | ||||||
|         with: |         with: | ||||||
|           run: SET_GH_OUTPUT=1 /syntax_check.sh |           run: SET_GH_OUTPUT=1 make lint | ||||||
| 
 | 
 | ||||||
|       - name: Report code formatting errors |       - name: Report code formatting errors | ||||||
|         if: failure() && steps.syntax_check.outputs.errors && github.event.pull_request |         if: failure() && steps.syntax_check.outputs.errors && github.event.pull_request | ||||||
| @ -59,4 +59,4 @@ jobs: | |||||||
|             ``` |             ``` | ||||||
|             ${{ steps.syntax_check.outputs.errors }} |             ${{ steps.syntax_check.outputs.errors }} | ||||||
|             ``` |             ``` | ||||||
|             You might want to run `docker compose exec dev /syntax_check.sh` for an auto-fix. |             You might want to run `docker compose exec dev make format` for an auto-fix. | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								.github/workflows/reindex.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/reindex.yml
									
									
									
									
										vendored
									
									
								
							| @ -7,7 +7,7 @@ on: | |||||||
| jobs: | jobs: | ||||||
|   reindex: |   reindex: | ||||||
|     name: 'Reindex updates' |     name: 'Reindex updates' | ||||||
|     runs-on: [self-hosted] |     runs-on: [self-hosted,Office] | ||||||
|     steps: |     steps: | ||||||
|       - name: Trigger reindex |       - name: Trigger reindex | ||||||
|         uses: wei/curl@master |         uses: wei/curl@master | ||||||
|  | |||||||
							
								
								
									
										101
									
								
								CODING_STYLE.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								CODING_STYLE.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,101 @@ | |||||||
|  | # Intro | ||||||
|  | 
 | ||||||
|  | Nice to see you reading this document, we really appreciate it. | ||||||
|  | 
 | ||||||
|  | As all documents of this kind it's unable to cover everything. | ||||||
|  | But it will cover general rules that we enforcing on PR review. | ||||||
|  | 
 | ||||||
|  | Also we already have automatic rules checking and formatting, | ||||||
|  | but it got it's limitations and this guide is still mandatory. | ||||||
|  | 
 | ||||||
|  | Some part of this project do have it's own naming and coding guides. | ||||||
|  | For example: assets. Take a look into `ReadMe.md` in assets folder for more details. | ||||||
|  | 
 | ||||||
|  | Also 3rd party libraries are none of our concern. | ||||||
|  | 
 | ||||||
|  | And yes, this set is not final and we are open to discussion. | ||||||
|  | If you want to add/remove/change something here please feel free to open new ticket. | ||||||
|  | 
 | ||||||
|  | # Inspiration | ||||||
|  | 
 | ||||||
|  | Our guide is inspired by, but not claiming to be compatible with: | ||||||
|  | 
 | ||||||
|  | - https://www.kernel.org/doc/html/v4.10/process/coding-style.html | ||||||
|  | - https://docs.unrealengine.com/en-US/Programming/Development/CodingStandard | ||||||
|  | - https://webkit.org/code-style-guidelines/ | ||||||
|  | 
 | ||||||
|  | # General rules | ||||||
|  | 
 | ||||||
|  | ## Readability and Simplicity first | ||||||
|  | 
 | ||||||
|  | Code we write is intended to be public. | ||||||
|  | Avoid one-liners from hell and keep code complexity under control. | ||||||
|  | Try to make code self explanatory and add comments if needed. | ||||||
|  | Leave references to standards that you are implementing. | ||||||
|  | Use project wiki to document new/reverse engineered standards. | ||||||
|  | 
 | ||||||
|  | ## Variable and function names must clearly define what it's doing | ||||||
|  | 
 | ||||||
|  | It's ok if it will be long, but it should clearly state what it's doing, without need to dive into code. | ||||||
|  | This also applies to function/method's code. | ||||||
|  | Try to avoid one letter variables. | ||||||
|  | 
 | ||||||
|  | ## Encapsulation | ||||||
|  | 
 | ||||||
|  | Don't expose raw data, provide methods to work with it. | ||||||
|  | Almost everything in flipper firmware is built around this concept. | ||||||
|  | 
 | ||||||
|  | # C coding style | ||||||
|  | 
 | ||||||
|  | - Tab is 4 spaces | ||||||
|  | - Use `make format` to reformat source code and check style guide before commit | ||||||
|  | 
 | ||||||
|  | ## Naming | ||||||
|  | 
 | ||||||
|  | ### Type names are CamelCase | ||||||
|  | 
 | ||||||
|  | Examples: | ||||||
|  | 
 | ||||||
|  | 	FuriHalUsb | ||||||
|  | 	Gui | ||||||
|  | 	SubghzKeystore | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | ### Functions are snake_case | ||||||
|  | 
 | ||||||
|  | 	furi_hal_usb_init | ||||||
|  | 	gui_add_view_port | ||||||
|  | 	subghz_keystore_read | ||||||
|  | 
 | ||||||
|  | ### File and Package name is a prefix for it's content | ||||||
|  | 
 | ||||||
|  | This rule makes easier to locate types, functions and sources. | ||||||
|  | 
 | ||||||
|  | For example: | ||||||
|  | 
 | ||||||
|  | We have abstraction that we call `Subghz Keystore`, so there will be: | ||||||
|  | file `subghz_keystore.h` we have type `SubghzKeystore` and function `subghz_keystore_read`. | ||||||
|  | 
 | ||||||
|  | ### File names | ||||||
|  | 
 | ||||||
|  | - Directories: `^[0-9A-Za-z_]+$` | ||||||
|  | - File names: `^[0-9A-Za-z_]+\.[a-z]+$` | ||||||
|  | - File extensions: `[ ".h", ".c", ".cpp", ".cxx", ".hpp" ]` | ||||||
|  | 
 | ||||||
|  | Enforced by linter. | ||||||
|  | 
 | ||||||
|  | ### Standard function/method names | ||||||
|  | 
 | ||||||
|  | Suffixes: | ||||||
|  | 
 | ||||||
|  | - `alloc` - allocate and init instance. C style constructor. Returns pointer to instance. | ||||||
|  | - `free` - deinit and release instance. C style destructor. Takes pointer to instance. | ||||||
|  | 
 | ||||||
|  | # C++ coding style | ||||||
|  | 
 | ||||||
|  | Work In Progress. Use C style guide as a base. | ||||||
|  | 
 | ||||||
|  | # Python coding style | ||||||
|  | 
 | ||||||
|  | - Tab is 4 spaces | ||||||
|  | - Use [black](https://pypi.org/project/black/) to reformat source code before commit | ||||||
| @ -21,6 +21,7 @@ Before writing code and creating PR make sure that it aligns with our mission an | |||||||
| 
 | 
 | ||||||
| - All our devices are intended for research and education. | - All our devices are intended for research and education. | ||||||
| - PR that contains code intended to commit crimes is not going to be accepted. | - PR that contains code intended to commit crimes is not going to be accepted. | ||||||
|  | - Your PR must comply with our [Coding Style](CODING_STYLE.md) | ||||||
| - Your PR must contain code compatiable with project [LICENSE](LICENSE). | - Your PR must contain code compatiable with project [LICENSE](LICENSE). | ||||||
| - PR will only be merged if it pass CI/CD. | - PR will only be merged if it pass CI/CD. | ||||||
| - PR will only be merged if it pass review by code owner. | - PR will only be merged if it pass review by code owner. | ||||||
|  | |||||||
							
								
								
									
										33
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										33
									
								
								Makefile
									
									
									
									
									
								
							| @ -1,7 +1,28 @@ | |||||||
| PROJECT_ROOT := $(abspath $(dir $(abspath $(firstword $(MAKEFILE_LIST))))) | PROJECT_ROOT := $(abspath $(dir $(abspath $(firstword $(MAKEFILE_LIST))))) | ||||||
| COPRO_DIR := $(PROJECT_ROOT)/lib/STM32CubeWB/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x | COPRO_DIR := $(PROJECT_ROOT)/lib/STM32CubeWB/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x | ||||||
| 
 | 
 | ||||||
| NPROCS := 1 | PROJECT_SOURCE_DIRECTORIES := \
 | ||||||
|  | 	$(PROJECT_ROOT)/applications \
 | ||||||
|  | 	$(PROJECT_ROOT)/bootloader/src \
 | ||||||
|  | 	$(PROJECT_ROOT)/bootloader/targets \
 | ||||||
|  | 	$(PROJECT_ROOT)/core \
 | ||||||
|  | 	$(PROJECT_ROOT)/firmware/targets \
 | ||||||
|  | 	$(PROJECT_ROOT)/lib/app-template \
 | ||||||
|  | 	$(PROJECT_ROOT)/lib/app-scened-template \
 | ||||||
|  | 	$(PROJECT_ROOT)/lib/common-api \
 | ||||||
|  | 	$(PROJECT_ROOT)/lib/cyfral \
 | ||||||
|  | 	$(PROJECT_ROOT)/lib/drivers \
 | ||||||
|  | 	$(PROJECT_ROOT)/lib/flipper_file \
 | ||||||
|  | 	$(PROJECT_ROOT)/lib/irda \
 | ||||||
|  | 	$(PROJECT_ROOT)/lib/nfc_protocols \
 | ||||||
|  | 	$(PROJECT_ROOT)/lib/ST25RFAL002 \
 | ||||||
|  | 	$(PROJECT_ROOT)/lib/onewire \
 | ||||||
|  | 	$(PROJECT_ROOT)/lib/qrcode \
 | ||||||
|  | 	$(PROJECT_ROOT)/lib/subghz \
 | ||||||
|  | 	$(PROJECT_ROOT)/lib/toolbox \
 | ||||||
|  | 	$(PROJECT_ROOT)/lib/u8g2 | ||||||
|  | 
 | ||||||
|  | NPROCS := 3 | ||||||
| OS := $(shell uname -s) | OS := $(shell uname -s) | ||||||
| 
 | 
 | ||||||
| ifeq ($(OS), Linux) | ifeq ($(OS), Linux) | ||||||
| @ -91,10 +112,12 @@ flash_radio_fus_please_i_m_not_going_to_complain: | |||||||
| 	@$(PROJECT_ROOT)/scripts/flash.py core2fus 0x080EC000 --statement=AGREE_TO_LOOSE_FLIPPER_FEATURES_THAT_USES_CRYPTO_ENCLAVE $(COPRO_DIR)/stm32wb5x_FUS_fw.bin | 	@$(PROJECT_ROOT)/scripts/flash.py core2fus 0x080EC000 --statement=AGREE_TO_LOOSE_FLIPPER_FEATURES_THAT_USES_CRYPTO_ENCLAVE $(COPRO_DIR)/stm32wb5x_FUS_fw.bin | ||||||
| 	@$(PROJECT_ROOT)/scripts/ob.py set | 	@$(PROJECT_ROOT)/scripts/ob.py set | ||||||
| 
 | 
 | ||||||
| FORMAT_SOURCES = $(shell find applications bootloader core -iname "*.h" -o -iname "*.c" -o -iname "*.cpp") | .PHONY: lint | ||||||
|  | lint: | ||||||
|  | 	@echo "Checking source code formatting" | ||||||
|  | 	@$(PROJECT_ROOT)/scripts/lint.py check $(PROJECT_SOURCE_DIRECTORIES) | ||||||
| 
 | 
 | ||||||
| .PHONY: format | .PHONY: format | ||||||
| format: | format: | ||||||
| 	@echo "Formatting sources with clang-format" | 	@echo "Reformating sources code" | ||||||
| 	@clang-format -style=file -i $(FORMAT_SOURCES) | 	@$(PROJECT_ROOT)/scripts/lint.py format $(PROJECT_SOURCE_DIRECTORIES) | ||||||
| 
 |  | ||||||
|  | |||||||
							
								
								
									
										79
									
								
								ReadMe.md
									
									
									
									
									
								
							
							
						
						
									
										79
									
								
								ReadMe.md
									
									
									
									
									
								
							| @ -11,7 +11,6 @@ Our goal is to create nice and clean code with good documentation, to make it a | |||||||
| 
 | 
 | ||||||
| [Get Latest Firmware from Update Server](https://update.flipperzero.one/) | [Get Latest Firmware from Update Server](https://update.flipperzero.one/) | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| Flipper Zero's firmware consists of three components: | Flipper Zero's firmware consists of three components: | ||||||
| 
 | 
 | ||||||
| - Core2 firmware set - proprietary components by ST: FUS + radio stack. FUS is flashed at factory and you should never update it. | - Core2 firmware set - proprietary components by ST: FUS + radio stack. FUS is flashed at factory and you should never update it. | ||||||
| @ -135,72 +134,24 @@ make whole | |||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| # Links | # Links | ||||||
|  | 
 | ||||||
| * Discord: [flipp.dev/discord](https://flipp.dev/discord) | * Discord: [flipp.dev/discord](https://flipp.dev/discord) | ||||||
| * Website: [flipperzero.one](https://flipperzero.one) | * Website: [flipperzero.one](https://flipperzero.one) | ||||||
| * Kickstarter page: [kickstarter.com](https://www.kickstarter.com/projects/flipper-devices/flipper-zero-tamagochi-for-hackers) | * Kickstarter page: [kickstarter.com](https://www.kickstarter.com/projects/flipper-devices/flipper-zero-tamagochi-for-hackers) | ||||||
| * Forum: [forum.flipperzero.one](https://forum.flipperzero.one/) | * Forum: [forum.flipperzero.one](https://forum.flipperzero.one/) | ||||||
| 
 | 
 | ||||||
| # Folders structure | # Project structure | ||||||
| 
 | 
 | ||||||
| - applications - application and services | - `applications`    - Applications and services used in firmware | ||||||
|   * accessor - Wiegand server | - `assets`          - Assets used by applications and services | ||||||
|   * archive - Archive and file manager  | - `bootloader`      - Bootloader source code | ||||||
|   * bt - BLE service and application | - `core`            - Furi Core: os level primitives and helpers | ||||||
|   * cli - Console service | - `debug`           - Debug tool: GDB-plugins, SVD-file and etc | ||||||
|   * debug_tools - different tools that we use on factory and for debug | - `docker`          - Docker image sources (used for firmware build automation) | ||||||
|   * dialogs - service for showing GUI dialogs | - `documentation`   - Documentation generation system configs and input files | ||||||
|   * dolphin - dolphin service and supplementary apps | - `firmware`        - Firmware source code | ||||||
|   * gpio-tester - GPIO control application | - `lib`             - Our and 3rd party libraries, drivers and etc... | ||||||
|   * gui - GUI service | - `make`            - Make helpers | ||||||
|   * ibutton - ibutton application, onewire keys and more | - `scripts`         - Supplementary scripts and python libraries home | ||||||
|   * input - input service | 
 | ||||||
|   * irda - irda application, controls your IR devices  | Also pay attention to `ReadMe.md` files inside of those directories. | ||||||
|   * irda_monitor - irda debug tool  |  | ||||||
|   * lfrfid - LF RFID application |  | ||||||
|   * lfrfid-debug - LF RFID debug tool |  | ||||||
|   * loader - application loader service |  | ||||||
|   * menu - main menu service |  | ||||||
|   * music-player - music player app (demo) |  | ||||||
|   * nfc - NFC application, HF rfid, EMV and etc |  | ||||||
|   * notification - notification service  |  | ||||||
|   * power - power service |  | ||||||
|   * power-observer - power debug tool |  | ||||||
|   * scened-app-example - c++ application example  |  | ||||||
|   * storage - storage service, internal + sdcard |  | ||||||
|   * storage_settings - storage settings app |  | ||||||
|   * subghz - subghz application, 433 fobs and etc |  | ||||||
|   * tests - unit tests and etc |  | ||||||
| - assets - assets used by applications and services |  | ||||||
|   * compiled - compilation results |  | ||||||
|   * icons - source icons images |  | ||||||
| - bootloader - bootloader for flipper |  | ||||||
|   * src - bootloader sources |  | ||||||
|   * targets - targets' hal and implementation |  | ||||||
| - core - core libraries: home for furi |  | ||||||
| - debug - debug helpers, plugins and tools |  | ||||||
| - docker - docker image sources (used for automated firmware build) |  | ||||||
| - documentation - documentation generation system configs and input files |  | ||||||
| - firmware - firmware for flipper |  | ||||||
|   * targets - targets' hal and implementation |  | ||||||
| - lib - different libraries and drivers that apps and firmware uses |  | ||||||
|   * ST25RFAL002 - ST253916 driver and NFC hal |  | ||||||
|   * STM32CubeWB - STM32WB hal |  | ||||||
|   * app-scened-template - scened template app library |  | ||||||
|   * app-template - template app library |  | ||||||
|   * callback-connector - callback connector library |  | ||||||
|   * common-api - common api declaration library |  | ||||||
|   * cyfral - cyfral library |  | ||||||
|   * drivers - drivers that we wrote |  | ||||||
|   * fatfs - external storage file system |  | ||||||
|   * fnv1a-hash - fnv1a hash library  |  | ||||||
|   * irda - irda library |  | ||||||
|   * littlefs - internal storage file system |  | ||||||
|   * mlib - algorithms and containers  |  | ||||||
|   * nfc_protocols - nfc protocols library |  | ||||||
|   * onewire - one wire library  |  | ||||||
|   * qrcode - qr code generator library |  | ||||||
|   * subghz - subghz library |  | ||||||
|   * toolbox - toolbox of things that we are using but don't place in core |  | ||||||
|   * u8g2 - graphics library that we use to draw GUI |  | ||||||
| - make - make helpers |  | ||||||
| - scripts - supplementary scripts |  | ||||||
|  | |||||||
							
								
								
									
										40
									
								
								applications/ReadMe.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								applications/ReadMe.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,40 @@ | |||||||
|  | # Structure | ||||||
|  | 
 | ||||||
|  | - `about`               - Small About application that shows flipper info | ||||||
|  | - `accessor`            - Wiegand server | ||||||
|  | - `archive`             - Archive and file manager  | ||||||
|  | - `bad_usb`             - Bad USB application | ||||||
|  | - `bt`                  - BLE service and application | ||||||
|  | - `cli`                 - Console service and API | ||||||
|  | - `crypto`              - Crypto cli tools | ||||||
|  | - `debug_tools`         - Different tools that we use for debug | ||||||
|  | - `desktop`             - Desktop service | ||||||
|  | - `dialogs`             - Dialogs service: GUI Dialogs for your app | ||||||
|  | - `dolphin`             - Dolphin service and supplementary apps | ||||||
|  | - `gpio`                - GPIO application: includes USART bridge and GPIO control | ||||||
|  | - `gui`                 - GUI service and API | ||||||
|  | - `ibutton`             - iButton application, onewire keys and more | ||||||
|  | - `input`               - Input service | ||||||
|  | - `irda`                - Irda application, controls your IR devices | ||||||
|  | - `irda_monitor`        - Irda debug tool | ||||||
|  | - `lfrfid`              - LF RFID application | ||||||
|  | - `lfrfid_debug`        - LF RFID debug tool | ||||||
|  | - `loader`              - Application loader service | ||||||
|  | - `music_player`        - Music player app (demo) | ||||||
|  | - `nfc`                 - NFC application, HF rfid, EMV and etc | ||||||
|  | - `notification`        - Notification service  | ||||||
|  | - `power`               - Power service | ||||||
|  | - `power_observer`      - Power debug tool | ||||||
|  | - `rpc`                 - RPC service and API | ||||||
|  | - `scened_app_example`  - C++ application example | ||||||
|  | - `snake_game`          - Snake game application | ||||||
|  | - `storage`             - Storage service, internal + sdcard | ||||||
|  | - `storage_settings`    - Storage settings app | ||||||
|  | - `subghz`              - Subghz application, 433 fobs and etc | ||||||
|  | - `system`              - System settings, tools and API | ||||||
|  | - `tests`               - Unit tests and etc | ||||||
|  | - `u2f`                 - U2F Application | ||||||
|  | 
 | ||||||
|  | - `application.c`       - Firmware application list source | ||||||
|  | - `application.h`       - Firmware application list header | ||||||
|  | - `application.mk`      - Makefile helper | ||||||
| @ -4,7 +4,7 @@ | |||||||
| #include <gui/view_dispatcher.h> | #include <gui/view_dispatcher.h> | ||||||
| #include <gui/modules/empty_screen.h> | #include <gui/modules/empty_screen.h> | ||||||
| #include <m-string.h> | #include <m-string.h> | ||||||
| #include <furi-hal-version.h> | #include <furi_hal_version.h> | ||||||
| 
 | 
 | ||||||
| typedef DialogMessageButton (*AboutDialogScreen)(DialogsApp* dialogs, DialogMessage* message); | typedef DialogMessageButton (*AboutDialogScreen)(DialogsApp* dialogs, DialogMessage* message); | ||||||
| 
 | 
 | ||||||
| @ -14,7 +14,7 @@ static DialogMessageButton product_screen(DialogsApp* dialogs, DialogMessage* me | |||||||
|     const char* screen_header = "Product: Flipper Zero\n" |     const char* screen_header = "Product: Flipper Zero\n" | ||||||
|                                 "Model: FZ.1\n"; |                                 "Model: FZ.1\n"; | ||||||
|     const char* screen_text = "FCC ID: 2A2V6-FZ\n" |     const char* screen_text = "FCC ID: 2A2V6-FZ\n" | ||||||
|                               "IC ID: 27624-FZ"; |                               "IC: 27624-FZ"; | ||||||
| 
 | 
 | ||||||
|     dialog_message_set_header(message, screen_header, 0, 0, AlignLeft, AlignTop); |     dialog_message_set_header(message, screen_header, 0, 0, AlignLeft, AlignTop); | ||||||
|     dialog_message_set_text(message, screen_text, 0, 26, AlignLeft, AlignTop); |     dialog_message_set_text(message, screen_text, 0, 26, AlignLeft, AlignTop); | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| #include "accessor-app.h" | #include "accessor_app.h" | ||||||
| 
 | 
 | ||||||
| // app enter function
 | // app enter function
 | ||||||
| extern "C" int32_t accessor_app(void* p) { | extern "C" int32_t accessor_app(void* p) { | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| #include "accessor-app.h" | #include "accessor_app.h" | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <furi-hal.h> | #include <furi_hal.h> | ||||||
| #include <stdarg.h> | #include <stdarg.h> | ||||||
| 
 | 
 | ||||||
| void AccessorApp::run(void) { | void AccessorApp::run(void) { | ||||||
| @ -1,15 +1,15 @@ | |||||||
| #pragma once | #pragma once | ||||||
| #include <map> | #include <map> | ||||||
| #include <list> | #include <list> | ||||||
| #include "accessor-view-manager.h" | #include "accessor_view_manager.h" | ||||||
| 
 | 
 | ||||||
| #include "scene/accessor-scene-start.h" | #include "scene/accessor_scene_start.h" | ||||||
| 
 | 
 | ||||||
| #include "helpers/wiegand.h" | #include "helpers/wiegand.h" | ||||||
| 
 | 
 | ||||||
| #include <one_wire_master.h> | #include <one_wire_master.h> | ||||||
| 
 | 
 | ||||||
| #include <notification/notification-messages.h> | #include <notification/notification_messages.h> | ||||||
| 
 | 
 | ||||||
| class AccessorApp { | class AccessorApp { | ||||||
| public: | public: | ||||||
| @ -1,5 +1,5 @@ | |||||||
| #include "accessor-view-manager.h" | #include "accessor_view_manager.h" | ||||||
| #include "accessor-event.h" | #include "accessor_event.h" | ||||||
| #include <callback-connector.h> | #include <callback-connector.h> | ||||||
| 
 | 
 | ||||||
| AccessorAppViewManager::AccessorAppViewManager() { | AccessorAppViewManager::AccessorAppViewManager() { | ||||||
| @ -3,7 +3,7 @@ | |||||||
| #include <gui/view_dispatcher.h> | #include <gui/view_dispatcher.h> | ||||||
| #include <gui/modules/submenu.h> | #include <gui/modules/submenu.h> | ||||||
| #include <gui/modules/popup.h> | #include <gui/modules/popup.h> | ||||||
| #include "accessor-event.h" | #include "accessor_event.h" | ||||||
| 
 | 
 | ||||||
| class AccessorAppViewManager { | class AccessorAppViewManager { | ||||||
| public: | public: | ||||||
| @ -1,6 +1,6 @@ | |||||||
| #include "wiegand.h" | #include "wiegand.h" | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <furi-hal.h> | #include <furi_hal.h> | ||||||
| 
 | 
 | ||||||
| volatile unsigned long WIEGAND::_cardTempHigh = 0; | volatile unsigned long WIEGAND::_cardTempHigh = 0; | ||||||
| volatile unsigned long WIEGAND::_cardTemp = 0; | volatile unsigned long WIEGAND::_cardTemp = 0; | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| #pragma once | #pragma once | ||||||
| #include "../accessor-app.h" | #include "../accessor_app.h" | ||||||
| 
 | 
 | ||||||
| class AccessorApp; | class AccessorApp; | ||||||
| 
 | 
 | ||||||
| @ -1,8 +1,8 @@ | |||||||
| #include "../accessor-app.h" | #include "../accessor_app.h" | ||||||
| #include "../accessor-view-manager.h" | #include "../accessor_view_manager.h" | ||||||
| #include "../accessor-event.h" | #include "../accessor_event.h" | ||||||
| #include <callback-connector.h> | #include <callback-connector.h> | ||||||
| #include "accessor-scene-start.h" | #include "accessor_scene_start.h" | ||||||
| 
 | 
 | ||||||
| void AccessorSceneStart::on_enter(AccessorApp* app) { | void AccessorSceneStart::on_enter(AccessorApp* app) { | ||||||
|     AccessorAppViewManager* view_manager = app->get_view_manager(); |     AccessorAppViewManager* view_manager = app->get_view_manager(); | ||||||
| @ -1,5 +1,5 @@ | |||||||
| #pragma once | #pragma once | ||||||
| #include "accessor-scene-generic.h" | #include "accessor_scene_generic.h" | ||||||
| 
 | 
 | ||||||
| class AccessorSceneStart : public AccessorScene { | class AccessorSceneStart : public AccessorScene { | ||||||
| public: | public: | ||||||
| @ -42,22 +42,23 @@ extern int32_t usb_mouse_app(void* p); | |||||||
| extern int32_t usb_test_app(void* p); | extern int32_t usb_test_app(void* p); | ||||||
| extern int32_t vibro_test_app(void* p); | extern int32_t vibro_test_app(void* p); | ||||||
| extern int32_t bt_hid_app(void* p); | extern int32_t bt_hid_app(void* p); | ||||||
|  | extern int32_t battery_test_app(void* p); | ||||||
| 
 | 
 | ||||||
| // Plugins
 | // Plugins
 | ||||||
| extern int32_t music_player_app(void* p); | extern int32_t music_player_app(void* p); | ||||||
| extern int32_t snake_game_app(void* p); | extern int32_t snake_game_app(void* p); | ||||||
| 
 | 
 | ||||||
| // On system start hooks declaration
 | // On system start hooks declaration
 | ||||||
| extern void bt_cli_init(); | extern void bt_on_system_start(); | ||||||
| extern void crypto_cli_init(); | extern void crypto_on_system_start(); | ||||||
| extern void ibutton_cli_init(); | extern void ibutton_on_system_start(); | ||||||
| extern void irda_cli_init(); | extern void irda_on_system_start(); | ||||||
| extern void lfrfid_cli_init(); | extern void lfrfid_on_system_start(); | ||||||
| extern void nfc_cli_init(); | extern void nfc_on_system_start(); | ||||||
| extern void storage_cli_init(); | extern void storage_on_system_start(); | ||||||
| extern void subghz_cli_init(); | extern void subghz_on_system_start(); | ||||||
| extern void power_cli_init(); | extern void power_on_system_start(); | ||||||
| extern void unit_tests_cli_init(); | extern void unit_tests_on_system_start(); | ||||||
| 
 | 
 | ||||||
| // Settings
 | // Settings
 | ||||||
| extern int32_t notification_settings_app(void* p); | extern int32_t notification_settings_app(void* p); | ||||||
| @ -166,44 +167,42 @@ const size_t FLIPPER_APPS_COUNT = sizeof(FLIPPER_APPS) / sizeof(FlipperApplicati | |||||||
| 
 | 
 | ||||||
| // On system start hooks
 | // On system start hooks
 | ||||||
| const FlipperOnStartHook FLIPPER_ON_SYSTEM_START[] = { | const FlipperOnStartHook FLIPPER_ON_SYSTEM_START[] = { | ||||||
| #ifdef SRV_CLI |     crypto_on_system_start, | ||||||
|     crypto_cli_init, |  | ||||||
| #endif |  | ||||||
| 
 | 
 | ||||||
| #ifdef APP_IRDA | #ifdef APP_IRDA | ||||||
|     irda_cli_init, |     irda_on_system_start, | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| #ifdef APP_NFC | #ifdef APP_NFC | ||||||
|     nfc_cli_init, |     nfc_on_system_start, | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| #ifdef APP_SUBGHZ | #ifdef APP_SUBGHZ | ||||||
|     subghz_cli_init, |     subghz_on_system_start, | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| #ifdef APP_LF_RFID | #ifdef APP_LF_RFID | ||||||
|     lfrfid_cli_init, |     lfrfid_on_system_start, | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| #ifdef APP_IBUTTON | #ifdef APP_IBUTTON | ||||||
|     ibutton_cli_init, |     ibutton_on_system_start, | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| #ifdef SRV_BT | #ifdef SRV_BT | ||||||
|     bt_cli_init, |     bt_on_system_start, | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| #ifdef SRV_POWER | #ifdef SRV_POWER | ||||||
|     power_cli_init, |     power_on_system_start, | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| #ifdef SRV_STORAGE | #ifdef SRV_STORAGE | ||||||
|     storage_cli_init, |     storage_on_system_start, | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| #ifdef APP_UNIT_TESTS | #ifdef APP_UNIT_TESTS | ||||||
|     unit_tests_cli_init, |     unit_tests_on_system_start, | ||||||
| #endif | #endif | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| @ -280,6 +279,10 @@ const FlipperApplication FLIPPER_DEBUG_APPS[] = { | |||||||
| #ifdef APP_DISPLAY_TEST | #ifdef APP_DISPLAY_TEST | ||||||
|     {.app = display_test_app, .name = "Display Test", .stack_size = 1024, .icon = NULL}, |     {.app = display_test_app, .name = "Display Test", .stack_size = 1024, .icon = NULL}, | ||||||
| #endif | #endif | ||||||
|  | 
 | ||||||
|  | #ifdef APP_BATTERY_TEST | ||||||
|  |     {.app = battery_test_app, .name = "Battery Test", .stack_size = 1024, .icon = NULL}, | ||||||
|  | #endif | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const size_t FLIPPER_DEBUG_APPS_COUNT = sizeof(FLIPPER_DEBUG_APPS) / sizeof(FlipperApplication); | const size_t FLIPPER_DEBUG_APPS_COUNT = sizeof(FLIPPER_DEBUG_APPS) / sizeof(FlipperApplication); | ||||||
|  | |||||||
| @ -155,6 +155,11 @@ CFLAGS		+= -DAPP_DISPLAY_TEST | |||||||
| SRV_GUI = 1 | SRV_GUI = 1 | ||||||
| endif | endif | ||||||
| 
 | 
 | ||||||
|  | APP_BATTERY_TEST ?= 0 | ||||||
|  | ifeq ($(APP_BATTERY_TEST), 1) | ||||||
|  | CFLAGS		+= -DAPP_BATTERY_TEST | ||||||
|  | SRV_GUI = 1 | ||||||
|  | endif | ||||||
| 
 | 
 | ||||||
| APP_USB_MOUSE ?= 0 | APP_USB_MOUSE ?= 0 | ||||||
| ifeq ($(APP_USB_MOUSE), 1) | ifeq ($(APP_USB_MOUSE), 1) | ||||||
| @ -298,6 +303,7 @@ SRV_GUI	?= 0 | |||||||
| ifeq ($(SRV_GUI), 1) | ifeq ($(SRV_GUI), 1) | ||||||
| CFLAGS		+= -DSRV_GUI | CFLAGS		+= -DSRV_GUI | ||||||
| SRV_INPUT	= 1 | SRV_INPUT	= 1 | ||||||
|  | SRV_NOTIFICATION = 1 | ||||||
| endif | endif | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| #pragma once | #pragma once | ||||||
| #include "file-worker.h" | #include "file_worker.h" | ||||||
| 
 | 
 | ||||||
| #define ARCHIVE_FAV_PATH "/any/favorites.txt" | #define ARCHIVE_FAV_PATH "/any/favorites.txt" | ||||||
| #define ARCHIVE_FAV_TEMP_PATH "/any/favorites.tmp" | #define ARCHIVE_FAV_TEMP_PATH "/any/favorites.tmp" | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| #pragma once | #pragma once | ||||||
| #include "file-worker.h" | #include "file_worker.h" | ||||||
| 
 | 
 | ||||||
| #define MAX_FILES 100 //temp
 | #define MAX_FILES 100 //temp
 | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| #include "bad_usb_app_i.h" | #include "bad_usb_app_i.h" | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <furi-hal.h> | #include <furi_hal.h> | ||||||
| 
 | 
 | ||||||
| static bool bad_usb_app_custom_event_callback(void* context, uint32_t event) { | static bool bad_usb_app_custom_event_callback(void* context, uint32_t event) { | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
|  | |||||||
| @ -9,8 +9,8 @@ | |||||||
| #include <gui/scene_manager.h> | #include <gui/scene_manager.h> | ||||||
| #include <gui/modules/submenu.h> | #include <gui/modules/submenu.h> | ||||||
| #include <dialogs/dialogs.h> | #include <dialogs/dialogs.h> | ||||||
| #include <notification/notification-messages.h> | #include <notification/notification_messages.h> | ||||||
| #include <gui/modules/variable-item-list.h> | #include <gui/modules/variable_item_list.h> | ||||||
| #include "views/bad_usb_view.h" | #include "views/bad_usb_view.h" | ||||||
| 
 | 
 | ||||||
| #define BAD_USB_APP_PATH_FOLDER "/any/badusb" | #define BAD_USB_APP_PATH_FOLDER "/any/badusb" | ||||||
|  | |||||||
| @ -1,9 +1,9 @@ | |||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <furi-hal.h> | #include <furi_hal.h> | ||||||
| #include <gui/gui.h> | #include <gui/gui.h> | ||||||
| #include <input/input.h> | #include <input/input.h> | ||||||
| #include <lib/toolbox/args.h> | #include <lib/toolbox/args.h> | ||||||
| #include <furi-hal-usb-hid.h> | #include <furi_hal_usb_hid.h> | ||||||
| #include <storage/storage.h> | #include <storage/storage.h> | ||||||
| #include "bad_usb_script.h" | #include "bad_usb_script.h" | ||||||
| 
 | 
 | ||||||
| @ -103,7 +103,24 @@ static const char ducky_cmd_defdelay_1[] = {"DEFAULT_DELAY"}; | |||||||
| static const char ducky_cmd_defdelay_2[] = {"DEFAULTDELAY "}; | static const char ducky_cmd_defdelay_2[] = {"DEFAULTDELAY "}; | ||||||
| static const char ducky_cmd_repeat[] = {"REPEAT "}; | static const char ducky_cmd_repeat[] = {"REPEAT "}; | ||||||
| 
 | 
 | ||||||
| static bool ducky_get_number(char* param, uint32_t* val) { | static const char ducky_cmd_altchar[] = {"ALTCHAR "}; | ||||||
|  | static const char ducky_cmd_altstr_1[] = {"ALTSTRING "}; | ||||||
|  | static const char ducky_cmd_altstr_2[] = {"ALTCODE "}; | ||||||
|  | 
 | ||||||
|  | static const uint8_t numpad_keys[10] = { | ||||||
|  |     KEYPAD_0, | ||||||
|  |     KEYPAD_1, | ||||||
|  |     KEYPAD_2, | ||||||
|  |     KEYPAD_3, | ||||||
|  |     KEYPAD_4, | ||||||
|  |     KEYPAD_5, | ||||||
|  |     KEYPAD_6, | ||||||
|  |     KEYPAD_7, | ||||||
|  |     KEYPAD_8, | ||||||
|  |     KEYPAD_9, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static bool ducky_get_number(const char* param, uint32_t* val) { | ||||||
|     uint32_t value = 0; |     uint32_t value = 0; | ||||||
|     if(sscanf(param, "%lu", &value) == 1) { |     if(sscanf(param, "%lu", &value) == 1) { | ||||||
|         *val = value; |         *val = value; | ||||||
| @ -112,7 +129,7 @@ static bool ducky_get_number(char* param, uint32_t* val) { | |||||||
|     return false; |     return false; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static uint32_t ducky_get_command_len(char* line) { | static uint32_t ducky_get_command_len(const char* line) { | ||||||
|     uint32_t len = strlen(line); |     uint32_t len = strlen(line); | ||||||
|     for(uint32_t i = 0; i < len; i++) { |     for(uint32_t i = 0; i < len; i++) { | ||||||
|         if(line[i] == ' ') return i; |         if(line[i] == ' ') return i; | ||||||
| @ -120,7 +137,64 @@ static uint32_t ducky_get_command_len(char* line) { | |||||||
|     return 0; |     return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static bool ducky_string(char* param) { | static void ducky_numlock_on() { | ||||||
|  |     if((furi_hal_hid_get_led_state() & HID_KB_LED_NUM) == 0) { | ||||||
|  |         furi_hal_hid_kb_press(KEY_NUM_LOCK); | ||||||
|  |         furi_hal_hid_kb_release(KEY_NUM_LOCK); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool ducky_numpad_press(const char num) { | ||||||
|  |     if((num < '0') || (num > '9')) return false; | ||||||
|  | 
 | ||||||
|  |     uint16_t key = numpad_keys[num - '0']; | ||||||
|  |     furi_hal_hid_kb_press(key); | ||||||
|  |     furi_hal_hid_kb_release(key); | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool ducky_altchar(const char* charcode) { | ||||||
|  |     uint8_t i = 0; | ||||||
|  |     bool state = false; | ||||||
|  | 
 | ||||||
|  |     //TODO: numlock
 | ||||||
|  | 
 | ||||||
|  |     FURI_LOG_I(WORKER_TAG, "char %s", charcode); | ||||||
|  | 
 | ||||||
|  |     furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT); | ||||||
|  | 
 | ||||||
|  |     while((charcode[i] != ' ') && (charcode[i] != '\n') && (charcode[i] != '\0')) { | ||||||
|  |         state = ducky_numpad_press(charcode[i]); | ||||||
|  |         if(state == false) break; | ||||||
|  |         i++; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     furi_hal_hid_kb_release(KEY_MOD_LEFT_ALT); | ||||||
|  |     return state; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool ducky_altstring(const char* param) { | ||||||
|  |     uint32_t i = 0; | ||||||
|  |     bool state = false; | ||||||
|  | 
 | ||||||
|  |     while(param[i] != '\0') { | ||||||
|  |         if((param[i] < ' ') || (param[i] > '~')) { | ||||||
|  |             i++; | ||||||
|  |             continue; // Skip non-printable chars
 | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         char temp_str[4]; | ||||||
|  |         snprintf(temp_str, 4, "%u", param[i]); | ||||||
|  | 
 | ||||||
|  |         state = ducky_altchar(temp_str); | ||||||
|  |         if(state == false) break; | ||||||
|  |         i++; | ||||||
|  |     } | ||||||
|  |     return state; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool ducky_string(const char* param) { | ||||||
|     uint32_t i = 0; |     uint32_t i = 0; | ||||||
|     while(param[i] != '\0') { |     while(param[i] != '\0') { | ||||||
|         furi_hal_hid_kb_press(HID_ASCII_TO_KEY(param[i])); |         furi_hal_hid_kb_press(HID_ASCII_TO_KEY(param[i])); | ||||||
| @ -130,11 +204,15 @@ static bool ducky_string(char* param) { | |||||||
|     return true; |     return true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static uint16_t ducky_get_keycode(char* param, bool accept_chars) { | static uint16_t ducky_get_keycode(const char* param, bool accept_chars) { | ||||||
|     for(uint8_t i = 0; i < (sizeof(ducky_keys) / sizeof(ducky_keys[0])); i++) { |     for(uint8_t i = 0; i < (sizeof(ducky_keys) / sizeof(ducky_keys[0])); i++) { | ||||||
|         if(strncmp(param, ducky_keys[i].name, strlen(ducky_keys[i].name)) == 0) |         uint8_t key_cmd_len = strlen(ducky_keys[i].name); | ||||||
|  |         if((strncmp(param, ducky_keys[i].name, key_cmd_len) == 0) && | ||||||
|  |            ((param[key_cmd_len] == ' ') || (param[key_cmd_len] == '\n') || | ||||||
|  |             (param[key_cmd_len] == '\0'))) { | ||||||
|             return ducky_keys[i].keycode; |             return ducky_keys[i].keycode; | ||||||
|         } |         } | ||||||
|  |     } | ||||||
|     if((accept_chars) && (strlen(param) > 0)) { |     if((accept_chars) && (strlen(param) > 0)) { | ||||||
|         return (HID_ASCII_TO_KEY(param[0]) & 0xFF); |         return (HID_ASCII_TO_KEY(param[0]) & 0xFF); | ||||||
|     } |     } | ||||||
| @ -143,57 +221,71 @@ static uint16_t ducky_get_keycode(char* param, bool accept_chars) { | |||||||
| 
 | 
 | ||||||
| static int32_t ducky_parse_line(BadUsbScript* bad_usb, string_t line) { | static int32_t ducky_parse_line(BadUsbScript* bad_usb, string_t line) { | ||||||
|     uint32_t line_len = string_size(line); |     uint32_t line_len = string_size(line); | ||||||
|     char* line_t = (char*)string_get_cstr(line); |     const char* line_tmp = string_get_cstr(line); | ||||||
|     bool state = false; |     bool state = false; | ||||||
| 
 | 
 | ||||||
|     for(uint32_t i = 0; i < line_len; i++) { |     for(uint32_t i = 0; i < line_len; i++) { | ||||||
|         if((line_t[i] != ' ') && (line_t[i] != '\t') && (line_t[i] != '\n')) { |         if((line_tmp[i] != ' ') && (line_tmp[i] != '\t') && (line_tmp[i] != '\n')) { | ||||||
|             line_t = &line_t[i]; |             line_tmp = &line_tmp[i]; | ||||||
|             break; // Skip spaces and tabs
 |             break; // Skip spaces and tabs
 | ||||||
|         } |         } | ||||||
|         if(i == line_len - 1) return 0; // Skip empty lines
 |         if(i == line_len - 1) return 0; // Skip empty lines
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     FURI_LOG_I(WORKER_TAG, "line:%s", line_t); |     FURI_LOG_I(WORKER_TAG, "line:%s", line_tmp); | ||||||
| 
 | 
 | ||||||
|     // General commands
 |     // General commands
 | ||||||
|     if(strncmp(line_t, ducky_cmd_comment, strlen(ducky_cmd_comment)) == 0) { |     if(strncmp(line_tmp, ducky_cmd_comment, strlen(ducky_cmd_comment)) == 0) { | ||||||
|         // REM - comment line
 |         // REM - comment line
 | ||||||
|         return (0); |         return (0); | ||||||
|     } else if(strncmp(line_t, ducky_cmd_delay, strlen(ducky_cmd_delay)) == 0) { |     } else if(strncmp(line_tmp, ducky_cmd_delay, strlen(ducky_cmd_delay)) == 0) { | ||||||
|         // DELAY
 |         // DELAY
 | ||||||
|         line_t = &line_t[ducky_get_command_len(line_t) + 1]; |         line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; | ||||||
|         uint32_t delay_val = 0; |         uint32_t delay_val = 0; | ||||||
|         state = ducky_get_number(line_t, &delay_val); |         state = ducky_get_number(line_tmp, &delay_val); | ||||||
|         if((state) && (delay_val > 0)) { |         if((state) && (delay_val > 0)) { | ||||||
|             return (int32_t)delay_val; |             return (int32_t)delay_val; | ||||||
|         } |         } | ||||||
|         return (-1); |         return (-1); | ||||||
|     } else if( |     } else if( | ||||||
|         (strncmp(line_t, ducky_cmd_defdelay_1, strlen(ducky_cmd_defdelay_1)) == 0) || |         (strncmp(line_tmp, ducky_cmd_defdelay_1, strlen(ducky_cmd_defdelay_1)) == 0) || | ||||||
|         (strncmp(line_t, ducky_cmd_defdelay_2, strlen(ducky_cmd_defdelay_2)) == 0)) { |         (strncmp(line_tmp, ducky_cmd_defdelay_2, strlen(ducky_cmd_defdelay_2)) == 0)) { | ||||||
|         // DEFAULT_DELAY
 |         // DEFAULT_DELAY
 | ||||||
|         line_t = &line_t[ducky_get_command_len(line_t) + 1]; |         line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; | ||||||
|         state = ducky_get_number(line_t, &bad_usb->defdelay); |         state = ducky_get_number(line_tmp, &bad_usb->defdelay); | ||||||
|         return (state) ? (0) : (-1); |         return (state) ? (0) : (-1); | ||||||
|     } else if(strncmp(line_t, ducky_cmd_string, strlen(ducky_cmd_string)) == 0) { |     } else if(strncmp(line_tmp, ducky_cmd_string, strlen(ducky_cmd_string)) == 0) { | ||||||
|         // STRING
 |         // STRING
 | ||||||
|         line_t = &line_t[ducky_get_command_len(line_t) + 1]; |         line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; | ||||||
|         state = ducky_string(line_t); |         state = ducky_string(line_tmp); | ||||||
|         return (state) ? (0) : (-1); |         return (state) ? (0) : (-1); | ||||||
|     } else if(strncmp(line_t, ducky_cmd_repeat, strlen(ducky_cmd_repeat)) == 0) { |     } else if(strncmp(line_tmp, ducky_cmd_altchar, strlen(ducky_cmd_altchar)) == 0) { | ||||||
|  |         // ALTCHAR
 | ||||||
|  |         line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; | ||||||
|  |         ducky_numlock_on(); | ||||||
|  |         state = ducky_altchar(line_tmp); | ||||||
|  |         return (state) ? (0) : (-1); | ||||||
|  |     } else if( | ||||||
|  |         (strncmp(line_tmp, ducky_cmd_altstr_1, strlen(ducky_cmd_altstr_1)) == 0) || | ||||||
|  |         (strncmp(line_tmp, ducky_cmd_altstr_2, strlen(ducky_cmd_altstr_2)) == 0)) { | ||||||
|  |         // ALTSTRING
 | ||||||
|  |         line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; | ||||||
|  |         ducky_numlock_on(); | ||||||
|  |         state = ducky_altstring(line_tmp); | ||||||
|  |         return (state) ? (0) : (-1); | ||||||
|  |     } else if(strncmp(line_tmp, ducky_cmd_repeat, strlen(ducky_cmd_repeat)) == 0) { | ||||||
|         // REPEAT
 |         // REPEAT
 | ||||||
|         line_t = &line_t[ducky_get_command_len(line_t) + 1]; |         line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; | ||||||
|         state = ducky_get_number(line_t, &bad_usb->repeat_cnt); |         state = ducky_get_number(line_tmp, &bad_usb->repeat_cnt); | ||||||
|         return (state) ? (0) : (-1); |         return (state) ? (0) : (-1); | ||||||
|     } else { |     } else { | ||||||
|         // Special keys + modifiers
 |         // Special keys + modifiers
 | ||||||
|         uint16_t key = ducky_get_keycode(line_t, false); |         uint16_t key = ducky_get_keycode(line_tmp, false); | ||||||
|         if(key == KEY_NONE) return (-1); |         if(key == KEY_NONE) return (-1); | ||||||
|         if((key & 0xFF00) != 0) { |         if((key & 0xFF00) != 0) { | ||||||
|             // It's a modifier key
 |             // It's a modifier key
 | ||||||
|             line_t = &line_t[ducky_get_command_len(line_t) + 1]; |             line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; | ||||||
|             key |= ducky_get_keycode(line_t, true); |             key |= ducky_get_keycode(line_tmp, true); | ||||||
|         } |         } | ||||||
|         furi_hal_hid_kb_press(key); |         furi_hal_hid_kb_press(key); | ||||||
|         furi_hal_hid_kb_release(key); |         furi_hal_hid_kb_release(key); | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| #include "../bad_usb_app_i.h" | #include "../bad_usb_app_i.h" | ||||||
| #include "furi-hal-power.h" | #include "furi_hal_power.h" | ||||||
| 
 | 
 | ||||||
| static bool bad_usb_file_select(BadUsbApp* bad_usb) { | static bool bad_usb_file_select(BadUsbApp* bad_usb) { | ||||||
|     furi_assert(bad_usb); |     furi_assert(bad_usb); | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| #include "../bad_usb_script.h" | #include "../bad_usb_script.h" | ||||||
| #include "../bad_usb_app_i.h" | #include "../bad_usb_app_i.h" | ||||||
| #include "../views/bad_usb_view.h" | #include "../views/bad_usb_view.h" | ||||||
| #include "furi-hal.h" | #include "furi_hal.h" | ||||||
| 
 | 
 | ||||||
| void bad_usb_scene_work_ok_callback(InputType type, void* context) { | void bad_usb_scene_work_ok_callback(InputType type, void* context) { | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
|  | |||||||
| @ -1,21 +1,18 @@ | |||||||
| #include "bt_cli.h" |  | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <furi-hal.h> | #include <furi_hal.h> | ||||||
|  | #include <applications/cli/cli.h> | ||||||
|  | #include <lib/toolbox/args.h> | ||||||
|  | 
 | ||||||
| #include "bt_settings.h" | #include "bt_settings.h" | ||||||
| 
 | 
 | ||||||
| void bt_cli_init() { | static const char* bt_cli_address_types[] = { | ||||||
|     Cli* cli = furi_record_open("cli"); |     "Public Device Address", | ||||||
|  |     "Random Device Address", | ||||||
|  |     "Public Identity Address", | ||||||
|  |     "Random (Static) Identity Address", | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
|     cli_add_command(cli, "bt_info", CliCommandFlagDefault, bt_cli_command_info, NULL); | static void bt_cli_command_hci_info(Cli* cli, string_t args, void* context) { | ||||||
|     cli_add_command(cli, "bt_tx_carrier", CliCommandFlagDefault, bt_cli_command_carrier_tx, NULL); |  | ||||||
|     cli_add_command(cli, "bt_rx_carrier", CliCommandFlagDefault, bt_cli_command_carrier_rx, NULL); |  | ||||||
|     cli_add_command(cli, "bt_tx_pt", CliCommandFlagDefault, bt_cli_command_packet_tx, NULL); |  | ||||||
|     cli_add_command(cli, "bt_rx_pt", CliCommandFlagDefault, bt_cli_command_packet_rx, NULL); |  | ||||||
| 
 |  | ||||||
|     furi_record_close("cli"); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void bt_cli_command_info(Cli* cli, string_t args, void* context) { |  | ||||||
|     string_t buffer; |     string_t buffer; | ||||||
|     string_init(buffer); |     string_init(buffer); | ||||||
|     furi_hal_bt_dump_state(buffer); |     furi_hal_bt_dump_state(buffer); | ||||||
| @ -23,29 +20,22 @@ void bt_cli_command_info(Cli* cli, string_t args, void* context) { | |||||||
|     string_clear(buffer); |     string_clear(buffer); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void bt_cli_command_carrier_tx(Cli* cli, string_t args, void* context) { | static void bt_cli_command_carrier_tx(Cli* cli, string_t args, void* context) { | ||||||
|     uint16_t channel; |     int channel = 0; | ||||||
|     uint16_t power; |     int power = 0; | ||||||
|     BtSettings bt_settings; |  | ||||||
|     bt_settings_load(&bt_settings); |  | ||||||
| 
 | 
 | ||||||
|     int ret = sscanf(string_get_cstr(args), "%hu %hu", &channel, &power); |     do { | ||||||
|     if(ret != 2) { |         if(!args_read_int_and_trim(args, &channel) && (channel < 0 || channel > 39)) { | ||||||
|         printf("sscanf returned %d, channel: %hu, power: %hu\r\n", ret, channel, power); |             printf("Incorrect or missing channel, expected int 0-39"); | ||||||
|         cli_print_usage("bt_tx_carrier", "<Channel number> <Power>", string_get_cstr(args)); |             break; | ||||||
|         return; |  | ||||||
|         } |         } | ||||||
|     if(channel > 39) { |         if(!args_read_int_and_trim(args, &power) && (power < 0 || power > 6)) { | ||||||
|         printf("Channel number must be in 0...39 range, not %hu\r\n", channel); |             printf("Incorrect or missing power, expected int 0-6"); | ||||||
|         return; |             break; | ||||||
|     } |  | ||||||
|     if(power > 6) { |  | ||||||
|         printf("Power must be in 0...6 dB range, not %hu\r\n", power); |  | ||||||
|         return; |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         furi_hal_bt_stop_advertising(); |         furi_hal_bt_stop_advertising(); | ||||||
|     printf("Transmitting carrier at %hu channel at %hu dB power\r\n", channel, power); |         printf("Transmitting carrier at %d channel at %d dB power\r\n", channel, power); | ||||||
|         printf("Press CTRL+C to stop\r\n"); |         printf("Press CTRL+C to stop\r\n"); | ||||||
|         furi_hal_bt_start_tone_tx(channel, 0x19 + power); |         furi_hal_bt_start_tone_tx(channel, 0x19 + power); | ||||||
| 
 | 
 | ||||||
| @ -53,79 +43,62 @@ void bt_cli_command_carrier_tx(Cli* cli, string_t args, void* context) { | |||||||
|             osDelay(250); |             osDelay(250); | ||||||
|         } |         } | ||||||
|         furi_hal_bt_stop_tone_tx(); |         furi_hal_bt_stop_tone_tx(); | ||||||
|     if(bt_settings.enabled) { |     } while(false); | ||||||
|         furi_hal_bt_start_advertising(); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void bt_cli_command_carrier_rx(Cli* cli, string_t args, void* context) { | static void bt_cli_command_carrier_rx(Cli* cli, string_t args, void* context) { | ||||||
|     uint16_t channel; |     int channel = 0; | ||||||
|     BtSettings bt_settings; | 
 | ||||||
|     bt_settings_load(&bt_settings); |     do { | ||||||
|     int ret = sscanf(string_get_cstr(args), "%hu", &channel); |         if(!args_read_int_and_trim(args, &channel) && (channel < 0 || channel > 39)) { | ||||||
|     if(ret != 1) { |             printf("Incorrect or missing channel, expected int 0-39"); | ||||||
|         printf("sscanf returned %d, channel: %hu\r\n", ret, channel); |             break; | ||||||
|         cli_print_usage("bt_rx_carrier", "<Channel number>", string_get_cstr(args)); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|     if(channel > 39) { |  | ||||||
|         printf("Channel number must be in 0...39 range, not %hu\r\n", channel); |  | ||||||
|         return; |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         furi_hal_bt_stop_advertising(); |         furi_hal_bt_stop_advertising(); | ||||||
|     printf("Receiving carrier at %hu channel\r\n", channel); |         printf("Receiving carrier at %d channel\r\n", channel); | ||||||
|         printf("Press CTRL+C to stop\r\n"); |         printf("Press CTRL+C to stop\r\n"); | ||||||
| 
 | 
 | ||||||
|         furi_hal_bt_start_packet_rx(channel, 1); |         furi_hal_bt_start_packet_rx(channel, 1); | ||||||
| 
 | 
 | ||||||
|         while(!cli_cmd_interrupt_received(cli)) { |         while(!cli_cmd_interrupt_received(cli)) { | ||||||
|         osDelay(1024 / 4); |             osDelay(250); | ||||||
|             printf("RSSI: %6.1f dB\r", furi_hal_bt_get_rssi()); |             printf("RSSI: %6.1f dB\r", furi_hal_bt_get_rssi()); | ||||||
|             fflush(stdout); |             fflush(stdout); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         furi_hal_bt_stop_packet_test(); |         furi_hal_bt_stop_packet_test(); | ||||||
|     if(bt_settings.enabled) { |     } while(false); | ||||||
|         furi_hal_bt_start_advertising(); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void bt_cli_command_packet_tx(Cli* cli, string_t args, void* context) { | static void bt_cli_command_packet_tx(Cli* cli, string_t args, void* context) { | ||||||
|     uint16_t channel; |     int channel = 0; | ||||||
|     uint16_t pattern; |     int pattern = 0; | ||||||
|     uint16_t datarate; |     int datarate = 1; | ||||||
|     BtSettings bt_settings; | 
 | ||||||
|     bt_settings_load(&bt_settings); |     do { | ||||||
|     int ret = sscanf(string_get_cstr(args), "%hu %hu %hu", &channel, &pattern, &datarate); |         if(!args_read_int_and_trim(args, &channel) && (channel < 0 || channel > 39)) { | ||||||
|     if(ret != 3) { |             printf("Incorrect or missing channel, expected int 0-39"); | ||||||
|         printf("sscanf returned %d, channel: %hu %hu %hu\r\n", ret, channel, pattern, datarate); |             break; | ||||||
|         cli_print_usage( |  | ||||||
|             "bt_tx_pt", "<Channel number> <Pattern> <Datarate>", string_get_cstr(args)); |  | ||||||
|         return; |  | ||||||
|         } |         } | ||||||
|     if(channel > 39) { |         if(!args_read_int_and_trim(args, &pattern) && (pattern < 0 || pattern > 5)) { | ||||||
|         printf("Channel number must be in 0...39 range, not %hu\r\n", channel); |             printf("Incorrect or missing pattern, expected int 0-5 \r\n"); | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|     if(pattern > 5) { |  | ||||||
|         printf("Pattern must be in 0...5 range, not %hu\r\n", pattern); |  | ||||||
|             printf("0 - Pseudo-Random bit sequence 9\r\n"); |             printf("0 - Pseudo-Random bit sequence 9\r\n"); | ||||||
|             printf("1 - Pattern of alternating bits '11110000'\r\n"); |             printf("1 - Pattern of alternating bits '11110000'\r\n"); | ||||||
|             printf("2 - Pattern of alternating bits '10101010'\r\n"); |             printf("2 - Pattern of alternating bits '10101010'\r\n"); | ||||||
|             printf("3 - Pseudo-Random bit sequence 15\r\n"); |             printf("3 - Pseudo-Random bit sequence 15\r\n"); | ||||||
|             printf("4 - Pattern of All '1' bits\r\n"); |             printf("4 - Pattern of All '1' bits\r\n"); | ||||||
|             printf("5 - Pattern of All '0' bits\r\n"); |             printf("5 - Pattern of All '0' bits\r\n"); | ||||||
|         return; |             break; | ||||||
|         } |         } | ||||||
|     if(datarate < 1 || datarate > 2) { |         if(!args_read_int_and_trim(args, &datarate) && (datarate < 1 || datarate > 2)) { | ||||||
|         printf("Datarate must be in 1 or 2 Mb, not %hu\r\n", datarate); |             printf("Incorrect or missing datarate, expected int 1-2"); | ||||||
|         return; |             break; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         furi_hal_bt_stop_advertising(); |         furi_hal_bt_stop_advertising(); | ||||||
|         printf( |         printf( | ||||||
|         "Transmitting %hu pattern packet at %hu channel at %hu M datarate\r\n", |             "Transmitting %d pattern packet at %d channel at %d M datarate\r\n", | ||||||
|             pattern, |             pattern, | ||||||
|             channel, |             channel, | ||||||
|             datarate); |             datarate); | ||||||
| @ -137,33 +110,26 @@ void bt_cli_command_packet_tx(Cli* cli, string_t args, void* context) { | |||||||
|         } |         } | ||||||
|         furi_hal_bt_stop_packet_test(); |         furi_hal_bt_stop_packet_test(); | ||||||
|         printf("Transmitted %lu packets", furi_hal_bt_get_transmitted_packets()); |         printf("Transmitted %lu packets", furi_hal_bt_get_transmitted_packets()); | ||||||
|     if(bt_settings.enabled) { | 
 | ||||||
|         furi_hal_bt_start_advertising(); |     } while(false); | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void bt_cli_command_packet_rx(Cli* cli, string_t args, void* context) { | static void bt_cli_command_packet_rx(Cli* cli, string_t args, void* context) { | ||||||
|     uint16_t channel; |     int channel = 0; | ||||||
|     uint16_t datarate; |     int datarate = 1; | ||||||
|     BtSettings bt_settings; | 
 | ||||||
|     bt_settings_load(&bt_settings); |     do { | ||||||
|     int ret = sscanf(string_get_cstr(args), "%hu %hu", &channel, &datarate); |         if(!args_read_int_and_trim(args, &channel) && (channel < 0 || channel > 39)) { | ||||||
|     if(ret != 2) { |             printf("Incorrect or missing channel, expected int 0-39"); | ||||||
|         printf("sscanf returned %d, channel: %hu datarate: %hu\r\n", ret, channel, datarate); |             break; | ||||||
|         cli_print_usage("bt_rx_pt", "<Channel number> <Datarate>", string_get_cstr(args)); |  | ||||||
|         return; |  | ||||||
|         } |         } | ||||||
|     if(channel > 39) { |         if(!args_read_int_and_trim(args, &datarate) && (datarate < 1 || datarate > 2)) { | ||||||
|         printf("Channel number must be in 0...39 range, not %hu\r\n", channel); |             printf("Incorrect or missing datarate, expected int 1-2"); | ||||||
|         return; |             break; | ||||||
|     } |  | ||||||
|     if(datarate < 1 || datarate > 2) { |  | ||||||
|         printf("Datarate must be in 1 or 2 Mb, not %hu\r\n", datarate); |  | ||||||
|         return; |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         furi_hal_bt_stop_advertising(); |         furi_hal_bt_stop_advertising(); | ||||||
|     printf("Receiving packets at %hu channel at %hu M datarate\r\n", channel, datarate); |         printf("Receiving packets at %d channel at %d M datarate\r\n", channel, datarate); | ||||||
|         printf("Press CTRL+C to stop\r\n"); |         printf("Press CTRL+C to stop\r\n"); | ||||||
|         furi_hal_bt_start_packet_rx(channel, datarate); |         furi_hal_bt_start_packet_rx(channel, datarate); | ||||||
| 
 | 
 | ||||||
| @ -176,7 +142,107 @@ void bt_cli_command_packet_rx(Cli* cli, string_t args, void* context) { | |||||||
|         } |         } | ||||||
|         uint16_t packets_received = furi_hal_bt_stop_packet_test(); |         uint16_t packets_received = furi_hal_bt_stop_packet_test(); | ||||||
|         printf("Received %hu packets", packets_received); |         printf("Received %hu packets", packets_received); | ||||||
|  |     } while(false); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void bt_cli_scan_callback(GapAddress address, void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     osMessageQueueId_t queue = context; | ||||||
|  |     osMessageQueuePut(queue, &address, NULL, 250); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void bt_cli_command_scan(Cli* cli, string_t args, void* context) { | ||||||
|  |     osMessageQueueId_t queue = osMessageQueueNew(20, sizeof(GapAddress), NULL); | ||||||
|  |     furi_hal_bt_start_scan(bt_cli_scan_callback, queue); | ||||||
|  | 
 | ||||||
|  |     GapAddress address = {}; | ||||||
|  |     bool exit = false; | ||||||
|  |     while(!exit) { | ||||||
|  |         if(osMessageQueueGet(queue, &address, NULL, 250) == osOK) { | ||||||
|  |             if(address.type < sizeof(bt_cli_address_types)) { | ||||||
|  |                 printf("Found new device. Type: %s, MAC: ", bt_cli_address_types[address.type]); | ||||||
|  |                 for(uint8_t i = 0; i < sizeof(address.mac) - 1; i++) { | ||||||
|  |                     printf("%02X:", address.mac[i]); | ||||||
|  |                 } | ||||||
|  |                 printf("%02X\r\n", address.mac[sizeof(address.mac) - 1]); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         exit = cli_cmd_interrupt_received(cli); | ||||||
|  |     } | ||||||
|  |     furi_hal_bt_stop_scan(); | ||||||
|  |     osMessageQueueDelete(queue); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void bt_cli_print_usage() { | ||||||
|  |     printf("Usage:\r\n"); | ||||||
|  |     printf("bt <cmd> <args>\r\n"); | ||||||
|  |     printf("Cmd list:\r\n"); | ||||||
|  |     printf("\thci_info\t - HCI info\r\n"); | ||||||
|  |     if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug) && | ||||||
|  |        furi_hal_bt_get_radio_stack() == FuriHalBtStackHciLayer) { | ||||||
|  |         printf("\ttx_carrier <channel:0-39> <power:0-6>\t - start tx carrier test\r\n"); | ||||||
|  |         printf("\trx_carrier <channel:0-39>\t - start rx carrier test\r\n"); | ||||||
|  |         printf("\ttx_pt <channel:0-39> <pattern:0-5> <datarate:1-2>\t - start tx packet test\r\n"); | ||||||
|  |         printf("\trx_pt <channel:0-39> <datarate:1-2>\t - start rx packer test\r\n"); | ||||||
|  |         printf("\tscan\t - start scanner\r\n"); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void bt_cli(Cli* cli, string_t args, void* context) { | ||||||
|  |     string_t cmd; | ||||||
|  |     string_init(cmd); | ||||||
|  |     BtSettings bt_settings; | ||||||
|  |     bt_settings_load(&bt_settings); | ||||||
|  | 
 | ||||||
|  |     do { | ||||||
|  |         if(!args_read_string_and_trim(args, cmd)) { | ||||||
|  |             bt_cli_print_usage(); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         if(string_cmp_str(cmd, "hci_info") == 0) { | ||||||
|  |             bt_cli_command_hci_info(cli, args, NULL); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug) && | ||||||
|  |            furi_hal_bt_get_radio_stack() == FuriHalBtStackHciLayer) { | ||||||
|  |             if(string_cmp_str(cmd, "carrier_tx") == 0) { | ||||||
|  |                 bt_cli_command_carrier_tx(cli, args, NULL); | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |             if(string_cmp_str(cmd, "carrier_rx") == 0) { | ||||||
|  |                 bt_cli_command_carrier_rx(cli, args, NULL); | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |             if(string_cmp_str(cmd, "packet_tx") == 0) { | ||||||
|  |                 bt_cli_command_packet_tx(cli, args, NULL); | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |             if(string_cmp_str(cmd, "packet_rx") == 0) { | ||||||
|  |                 bt_cli_command_packet_rx(cli, args, NULL); | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |             if(string_cmp_str(cmd, "scan") == 0) { | ||||||
|  |                 bt_cli_command_scan(cli, args, NULL); | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         bt_cli_print_usage(); | ||||||
|  |     } while(false); | ||||||
|  | 
 | ||||||
|     if(bt_settings.enabled) { |     if(bt_settings.enabled) { | ||||||
|         furi_hal_bt_start_advertising(); |         furi_hal_bt_start_advertising(); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     string_clear(cmd); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void bt_on_system_start() { | ||||||
|  | #ifdef SRV_CLI | ||||||
|  |     Cli* cli = furi_record_open("cli"); | ||||||
|  |     furi_record_open("bt"); | ||||||
|  |     cli_add_command(cli, "bt", CliCommandFlagDefault, bt_cli, NULL); | ||||||
|  |     furi_record_close("bt"); | ||||||
|  |     furi_record_close("cli"); | ||||||
|  | #endif | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,15 +0,0 @@ | |||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <cli/cli.h> |  | ||||||
| 
 |  | ||||||
| void bt_cli_init(); |  | ||||||
| 
 |  | ||||||
| void bt_cli_command_info(Cli* cli, string_t args, void* context); |  | ||||||
| 
 |  | ||||||
| void bt_cli_command_carrier_tx(Cli* cli, string_t args, void* context); |  | ||||||
| 
 |  | ||||||
| void bt_cli_command_carrier_rx(Cli* cli, string_t args, void* context); |  | ||||||
| 
 |  | ||||||
| void bt_cli_command_packet_tx(Cli* cli, string_t args, void* context); |  | ||||||
| 
 |  | ||||||
| void bt_cli_command_packet_rx(Cli* cli, string_t args, void* context); |  | ||||||
							
								
								
									
										11
									
								
								applications/bt/bt_debug_app/bt_debug_app.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										11
									
								
								applications/bt/bt_debug_app/bt_debug_app.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							| @ -1,5 +1,7 @@ | |||||||
| #include "bt_debug_app.h" | #include "bt_debug_app.h" | ||||||
| #include <furi-hal-bt.h> | #include <furi_hal_bt.h> | ||||||
|  | 
 | ||||||
|  | #define TAG "BtDebugApp" | ||||||
| 
 | 
 | ||||||
| enum BtDebugSubmenuIndex { | enum BtDebugSubmenuIndex { | ||||||
|     BtDebugSubmenuIndexCarrierTest, |     BtDebugSubmenuIndexCarrierTest, | ||||||
| @ -92,6 +94,13 @@ void bt_debug_app_free(BtDebugApp* app) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| int32_t bt_debug_app(void* p) { | int32_t bt_debug_app(void* p) { | ||||||
|  |     if(furi_hal_bt_get_radio_stack() != FuriHalBtStackHciLayer) { | ||||||
|  |         FURI_LOG_E(TAG, "Incorrect radio stack, replace with HciLayer for tests."); | ||||||
|  |         DialogsApp* dialogs = furi_record_open("dialogs"); | ||||||
|  |         dialog_message_show_storage_error(dialogs, "Incorrect\nRadioStack"); | ||||||
|  |         return 255; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     BtDebugApp* app = bt_debug_app_alloc(); |     BtDebugApp* app = bt_debug_app_alloc(); | ||||||
|     // Stop advertising
 |     // Stop advertising
 | ||||||
|     furi_hal_bt_stop_advertising(); |     furi_hal_bt_stop_advertising(); | ||||||
|  | |||||||
| @ -4,6 +4,7 @@ | |||||||
| #include <gui/gui.h> | #include <gui/gui.h> | ||||||
| #include <gui/view.h> | #include <gui/view.h> | ||||||
| #include <gui/view_dispatcher.h> | #include <gui/view_dispatcher.h> | ||||||
|  | #include <dialogs/dialogs.h> | ||||||
| 
 | 
 | ||||||
| #include <gui/modules/submenu.h> | #include <gui/modules/submenu.h> | ||||||
| #include "views/bt_carrier_test.h" | #include "views/bt_carrier_test.h" | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| #include "bt_carrier_test.h" | #include "bt_carrier_test.h" | ||||||
| #include "bt_test.h" | #include "bt_test.h" | ||||||
| #include "bt_test_types.h" | #include "bt_test_types.h" | ||||||
| #include "furi-hal-bt.h" | #include "furi_hal_bt.h" | ||||||
| 
 | 
 | ||||||
| struct BtCarrierTest { | struct BtCarrierTest { | ||||||
|     BtTest* bt_test; |     BtTest* bt_test; | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| #include "bt_packet_test.h" | #include "bt_packet_test.h" | ||||||
| #include "bt_test.h" | #include "bt_test.h" | ||||||
| #include "bt_test_types.h" | #include "bt_test_types.h" | ||||||
| #include "furi-hal-bt.h" | #include "furi_hal_bt.h" | ||||||
| 
 | 
 | ||||||
| struct BtPacketTest { | struct BtPacketTest { | ||||||
|     BtTest* bt_test; |     BtTest* bt_test; | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| #include "bt_hid.h" | #include "bt_hid.h" | ||||||
| #include <furi-hal-bt.h> | #include <furi_hal_bt.h> | ||||||
| #include <applications/notification/notification-messages.h> | #include <applications/notification/notification_messages.h> | ||||||
| 
 | 
 | ||||||
| #define TAG "BtHidApp" | #define TAG "BtHidApp" | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| #include "bt_hid_keynote.h" | #include "bt_hid_keynote.h" | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <furi-hal-bt-hid.h> | #include <furi_hal_bt_hid.h> | ||||||
| #include <furi-hal-usb-hid.h> | #include <furi_hal_usb_hid.h> | ||||||
| #include <gui/elements.h> | #include <gui/elements.h> | ||||||
| 
 | 
 | ||||||
| struct BtHidKeynote { | struct BtHidKeynote { | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| #include "bt_hid_media.h" | #include "bt_hid_media.h" | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <furi-hal-bt-hid.h> | #include <furi_hal_bt_hid.h> | ||||||
| #include <furi-hal-usb-hid.h> | #include <furi_hal_usb_hid.h> | ||||||
| #include <gui/elements.h> | #include <gui/elements.h> | ||||||
| 
 | 
 | ||||||
| struct BtHidMedia { | struct BtHidMedia { | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ | |||||||
| #include "battery_service.h" | #include "battery_service.h" | ||||||
| #include "bt_keys_storage.h" | #include "bt_keys_storage.h" | ||||||
| 
 | 
 | ||||||
| #include <applications/notification/notification-messages.h> | #include <applications/notification/notification_messages.h> | ||||||
| 
 | 
 | ||||||
| #define TAG "BtSrv" | #define TAG "BtSrv" | ||||||
| 
 | 
 | ||||||
| @ -154,12 +154,12 @@ static void bt_rpc_send_bytes_callback(void* context, uint8_t* bytes, size_t byt | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Called from GAP thread
 | // Called from GAP thread
 | ||||||
| static bool bt_on_gap_event_callback(BleEvent event, void* context) { | static bool bt_on_gap_event_callback(GapEvent event, void* context) { | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
|     Bt* bt = context; |     Bt* bt = context; | ||||||
|     bool ret = false; |     bool ret = false; | ||||||
| 
 | 
 | ||||||
|     if(event.type == BleEventTypeConnected) { |     if(event.type == GapEventTypeConnected) { | ||||||
|         // Update status bar
 |         // Update status bar
 | ||||||
|         bt->status = BtStatusConnected; |         bt->status = BtStatusConnected; | ||||||
|         BtMessage message = {.type = BtMessageTypeUpdateStatusbar}; |         BtMessage message = {.type = BtMessageTypeUpdateStatusbar}; | ||||||
| @ -181,7 +181,7 @@ static bool bt_on_gap_event_callback(BleEvent event, void* context) { | |||||||
|         message.data.battery_level = info.charge; |         message.data.battery_level = info.charge; | ||||||
|         furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); |         furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); | ||||||
|         ret = true; |         ret = true; | ||||||
|     } else if(event.type == BleEventTypeDisconnected) { |     } else if(event.type == GapEventTypeDisconnected) { | ||||||
|         if(bt->profile == BtProfileSerial && bt->rpc_session) { |         if(bt->profile == BtProfileSerial && bt->rpc_session) { | ||||||
|             FURI_LOG_I(TAG, "Close RPC connection"); |             FURI_LOG_I(TAG, "Close RPC connection"); | ||||||
|             osEventFlagsSet(bt->rpc_event, BT_RPC_EVENT_DISCONNECTED); |             osEventFlagsSet(bt->rpc_event, BT_RPC_EVENT_DISCONNECTED); | ||||||
| @ -190,24 +190,24 @@ static bool bt_on_gap_event_callback(BleEvent event, void* context) { | |||||||
|             bt->rpc_session = NULL; |             bt->rpc_session = NULL; | ||||||
|         } |         } | ||||||
|         ret = true; |         ret = true; | ||||||
|     } else if(event.type == BleEventTypeStartAdvertising) { |     } else if(event.type == GapEventTypeStartAdvertising) { | ||||||
|         bt->status = BtStatusAdvertising; |         bt->status = BtStatusAdvertising; | ||||||
|         BtMessage message = {.type = BtMessageTypeUpdateStatusbar}; |         BtMessage message = {.type = BtMessageTypeUpdateStatusbar}; | ||||||
|         furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); |         furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); | ||||||
|         ret = true; |         ret = true; | ||||||
|     } else if(event.type == BleEventTypeStopAdvertising) { |     } else if(event.type == GapEventTypeStopAdvertising) { | ||||||
|         bt->status = BtStatusOff; |         bt->status = BtStatusOff; | ||||||
|         BtMessage message = {.type = BtMessageTypeUpdateStatusbar}; |         BtMessage message = {.type = BtMessageTypeUpdateStatusbar}; | ||||||
|         furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); |         furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); | ||||||
|         ret = true; |         ret = true; | ||||||
|     } else if(event.type == BleEventTypePinCodeShow) { |     } else if(event.type == GapEventTypePinCodeShow) { | ||||||
|         BtMessage message = { |         BtMessage message = { | ||||||
|             .type = BtMessageTypePinCodeShow, .data.pin_code = event.data.pin_code}; |             .type = BtMessageTypePinCodeShow, .data.pin_code = event.data.pin_code}; | ||||||
|         furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); |         furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); | ||||||
|         ret = true; |         ret = true; | ||||||
|     } else if(event.type == BleEventTypePinCodeVerify) { |     } else if(event.type == GapEventTypePinCodeVerify) { | ||||||
|         ret = bt_pin_code_verify_event_handler(bt, event.data.pin_code); |         ret = bt_pin_code_verify_event_handler(bt, event.data.pin_code); | ||||||
|     } else if(event.type == BleEventTypeUpdateMTU) { |     } else if(event.type == GapEventTypeUpdateMTU) { | ||||||
|         bt->max_packet_size = event.data.max_packet_size; |         bt->max_packet_size = event.data.max_packet_size; | ||||||
|         ret = true; |         ret = true; | ||||||
|     } |     } | ||||||
| @ -234,7 +234,15 @@ static void bt_statusbar_update(Bt* bt) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static void bt_show_warning(Bt* bt, const char* text) { | ||||||
|  |     dialog_message_set_text(bt->dialog_message, text, 64, 28, AlignCenter, AlignCenter); | ||||||
|  |     dialog_message_set_buttons(bt->dialog_message, "Quit", NULL, NULL); | ||||||
|  |     dialog_message_show(bt->dialogs, bt->dialog_message); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static void bt_change_profile(Bt* bt, BtMessage* message) { | static void bt_change_profile(Bt* bt, BtMessage* message) { | ||||||
|  |     FuriHalBtStack stack = furi_hal_bt_get_radio_stack(); | ||||||
|  |     if(stack == FuriHalBtStackLight) { | ||||||
|         bt_settings_load(&bt->bt_settings); |         bt_settings_load(&bt->bt_settings); | ||||||
|         if(bt->profile == BtProfileSerial && bt->rpc_session) { |         if(bt->profile == BtProfileSerial && bt->rpc_session) { | ||||||
|             FURI_LOG_I(TAG, "Close RPC connection"); |             FURI_LOG_I(TAG, "Close RPC connection"); | ||||||
| @ -263,31 +271,44 @@ static void bt_change_profile(Bt* bt, BtMessage* message) { | |||||||
|             FURI_LOG_E(TAG, "Failed to start Bt App"); |             FURI_LOG_E(TAG, "Failed to start Bt App"); | ||||||
|             *message->result = false; |             *message->result = false; | ||||||
|         } |         } | ||||||
|  |     } else { | ||||||
|  |         bt_show_warning(bt, "Radio stack doesn't support this app"); | ||||||
|  |         *message->result = false; | ||||||
|  |     } | ||||||
|     osEventFlagsSet(bt->api_event, BT_API_UNLOCK_EVENT); |     osEventFlagsSet(bt->api_event, BT_API_UNLOCK_EVENT); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| int32_t bt_srv() { | int32_t bt_srv() { | ||||||
|     Bt* bt = bt_alloc(); |     Bt* bt = bt_alloc(); | ||||||
|     furi_record_create("bt", bt); |  | ||||||
| 
 | 
 | ||||||
|     // Read keys
 |     // Read keys
 | ||||||
|     if(!bt_load_key_storage(bt)) { |     if(!bt_load_key_storage(bt)) { | ||||||
|         FURI_LOG_W(TAG, "Failed to load bonding keys"); |         FURI_LOG_W(TAG, "Failed to load bonding keys"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Start BLE stack
 |     // Start radio stack
 | ||||||
|     if(furi_hal_bt_start_app(FuriHalBtProfileSerial, bt_on_gap_event_callback, bt)) { |     if(!furi_hal_bt_start_radio_stack()) { | ||||||
|         FURI_LOG_I(TAG, "BLE stack started"); |         FURI_LOG_E(TAG, "Radio stack start failed"); | ||||||
|  |     } | ||||||
|  |     FuriHalBtStack stack_type = furi_hal_bt_get_radio_stack(); | ||||||
|  | 
 | ||||||
|  |     if(stack_type == FuriHalBtStackUnknown) { | ||||||
|  |         bt_show_warning(bt, "Unsupported radio stack"); | ||||||
|  |         bt->status = BtStatusUnavailable; | ||||||
|  |     } else if(stack_type == FuriHalBtStackHciLayer) { | ||||||
|  |         bt->status = BtStatusUnavailable; | ||||||
|  |     } else if(stack_type == FuriHalBtStackLight) { | ||||||
|  |         if(!furi_hal_bt_start_app(FuriHalBtProfileSerial, bt_on_gap_event_callback, bt)) { | ||||||
|  |             FURI_LOG_E(TAG, "BLE App start failed"); | ||||||
|  |         } else { | ||||||
|             if(bt->bt_settings.enabled) { |             if(bt->bt_settings.enabled) { | ||||||
|                 furi_hal_bt_start_advertising(); |                 furi_hal_bt_start_advertising(); | ||||||
|             } |             } | ||||||
|             furi_hal_bt_set_key_storage_change_callback(bt_on_key_storage_change_callback, bt); |             furi_hal_bt_set_key_storage_change_callback(bt_on_key_storage_change_callback, bt); | ||||||
|     } else { |         } | ||||||
|         FURI_LOG_E(TAG, "BT App start failed"); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Update statusbar
 |     furi_record_create("bt", bt); | ||||||
|     bt_statusbar_update(bt); |  | ||||||
| 
 | 
 | ||||||
|     BtMessage message; |     BtMessage message; | ||||||
|     while(1) { |     while(1) { | ||||||
|  | |||||||
| @ -10,6 +10,7 @@ extern "C" { | |||||||
| typedef struct Bt Bt; | typedef struct Bt Bt; | ||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|  |     BtStatusUnavailable, | ||||||
|     BtStatusOff, |     BtStatusOff, | ||||||
|     BtStatusAdvertising, |     BtStatusAdvertising, | ||||||
|     BtStatusConnected, |     BtStatusConnected, | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ | |||||||
| #include "bt.h" | #include "bt.h" | ||||||
| 
 | 
 | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <furi-hal.h> | #include <furi_hal.h> | ||||||
| 
 | 
 | ||||||
| #include <gui/gui.h> | #include <gui/gui.h> | ||||||
| #include <gui/view_port.h> | #include <gui/view_port.h> | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| #include "bt_keys_storage.h" | #include "bt_keys_storage.h" | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <file-worker.h> | #include <file_worker.h> | ||||||
| 
 | 
 | ||||||
| #define BT_KEYS_STORAGE_TAG "bt keys storage" | #define BT_KEYS_STORAGE_TAG "bt keys storage" | ||||||
| #define BT_KEYS_STORAGE_PATH "/int/bt.keys" | #define BT_KEYS_STORAGE_PATH "/int/bt.keys" | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| #include "bt_settings.h" | #include "bt_settings.h" | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <file-worker.h> | #include <file_worker.h> | ||||||
| 
 | 
 | ||||||
| #define TAG "BtSettings" | #define TAG "BtSettings" | ||||||
| #define BT_SETTINGS_PATH "/int/bt.settings" | #define BT_SETTINGS_PATH "/int/bt.settings" | ||||||
|  | |||||||
| @ -6,7 +6,7 @@ | |||||||
| #include <gui/view_dispatcher.h> | #include <gui/view_dispatcher.h> | ||||||
| #include <gui/scene_manager.h> | #include <gui/scene_manager.h> | ||||||
| 
 | 
 | ||||||
| #include <gui/modules/variable-item-list.h> | #include <gui/modules/variable_item_list.h> | ||||||
| 
 | 
 | ||||||
| #include "../bt_settings.h" | #include "../bt_settings.h" | ||||||
| #include "scenes/bt_settings_scene.h" | #include "scenes/bt_settings_scene.h" | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| #include "../bt_settings_app.h" | #include "../bt_settings_app.h" | ||||||
| #include "furi-hal-bt.h" | #include "furi_hal_bt.h" | ||||||
| 
 | 
 | ||||||
| enum BtSetting { | enum BtSetting { | ||||||
|     BtSettingOff, |     BtSettingOff, | ||||||
| @ -23,8 +23,10 @@ static void bt_settings_scene_start_var_list_change_callback(VariableItem* item) | |||||||
| void bt_settings_scene_start_on_enter(void* context) { | void bt_settings_scene_start_on_enter(void* context) { | ||||||
|     BtSettingsApp* app = context; |     BtSettingsApp* app = context; | ||||||
|     VariableItemList* var_item_list = app->var_item_list; |     VariableItemList* var_item_list = app->var_item_list; | ||||||
| 
 |  | ||||||
|     VariableItem* item; |     VariableItem* item; | ||||||
|  | 
 | ||||||
|  |     FuriHalBtStack stack_type = furi_hal_bt_get_radio_stack(); | ||||||
|  |     if(stack_type == FuriHalBtStackLight) { | ||||||
|         item = variable_item_list_add( |         item = variable_item_list_add( | ||||||
|             var_item_list, |             var_item_list, | ||||||
|             "Bluetooth", |             "Bluetooth", | ||||||
| @ -38,6 +40,10 @@ void bt_settings_scene_start_on_enter(void* context) { | |||||||
|             variable_item_set_current_value_index(item, BtSettingOff); |             variable_item_set_current_value_index(item, BtSettingOff); | ||||||
|             variable_item_set_current_value_text(item, bt_settings_text[BtSettingOff]); |             variable_item_set_current_value_text(item, bt_settings_text[BtSettingOff]); | ||||||
|         } |         } | ||||||
|  |     } else { | ||||||
|  |         item = variable_item_list_add(var_item_list, "Bluetooth", 1, NULL, NULL); | ||||||
|  |         variable_item_set_current_value_text(item, "Broken"); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     view_dispatcher_switch_to_view(app->view_dispatcher, BtSettingsAppViewVarItemList); |     view_dispatcher_switch_to_view(app->view_dispatcher, BtSettingsAppViewVarItemList); | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| #include "cli_i.h" | #include "cli_i.h" | ||||||
| #include "cli_commands.h" | #include "cli_commands.h" | ||||||
| 
 | 
 | ||||||
| #include <furi-hal-version.h> | #include <furi_hal_version.h> | ||||||
| #include <loader/loader.h> | #include <loader/loader.h> | ||||||
| 
 | 
 | ||||||
| Cli* cli_alloc() { | Cli* cli_alloc() { | ||||||
|  | |||||||
| @ -1,10 +1,10 @@ | |||||||
| #include "cli_commands.h" | #include "cli_commands.h" | ||||||
| #include <furi-hal.h> | #include <furi_hal.h> | ||||||
| #include <furi-hal-gpio.h> | #include <furi_hal_gpio.h> | ||||||
| #include <furi-hal-info.h> | #include <furi_hal_info.h> | ||||||
| #include <task-control-block.h> | #include <task_control_block.h> | ||||||
| #include <time.h> | #include <time.h> | ||||||
| #include <notification/notification-messages.h> | #include <notification/notification_messages.h> | ||||||
| 
 | 
 | ||||||
| // Close to ISO, `date +'%Y-%m-%d %H:%M:%S %u'`
 | // Close to ISO, `date +'%Y-%m-%d %H:%M:%S %u'`
 | ||||||
| #define CLI_DATE_FORMAT "%.4d-%.2d-%.2d %.2d:%.2d:%.2d %d" | #define CLI_DATE_FORMAT "%.4d-%.2d-%.2d %.2d:%.2d:%.2d %d" | ||||||
| @ -322,7 +322,6 @@ void cli_command_free_blocks(Cli* cli, string_t args, void* context) { | |||||||
| 
 | 
 | ||||||
| void cli_command_i2c(Cli* cli, string_t args, void* context) { | void cli_command_i2c(Cli* cli, string_t args, void* context) { | ||||||
|     furi_hal_i2c_acquire(&furi_hal_i2c_handle_external); |     furi_hal_i2c_acquire(&furi_hal_i2c_handle_external); | ||||||
|     uint8_t test = 0; |  | ||||||
|     printf("Scanning external i2c on PC0(SCL)/PC1(SDA)\r\n" |     printf("Scanning external i2c on PC0(SCL)/PC1(SDA)\r\n" | ||||||
|            "Clock: 100khz, 7bit address\r\n" |            "Clock: 100khz, 7bit address\r\n" | ||||||
|            "\r\n"); |            "\r\n"); | ||||||
| @ -331,8 +330,8 @@ void cli_command_i2c(Cli* cli, string_t args, void* context) { | |||||||
|     for(uint8_t row = 0; row < 0x8; row++) { |     for(uint8_t row = 0; row < 0x8; row++) { | ||||||
|         printf("%x | ", row); |         printf("%x | ", row); | ||||||
|         for(uint8_t column = 0; column <= 0xF; column++) { |         for(uint8_t column = 0; column <= 0xF; column++) { | ||||||
|             bool ret = furi_hal_i2c_rx( |             bool ret = furi_hal_i2c_is_device_ready( | ||||||
|                 &furi_hal_i2c_handle_external, ((row << 4) + column) << 1, &test, 1, 2); |                 &furi_hal_i2c_handle_external, ((row << 4) + column) << 1, 2); | ||||||
|             printf("%c ", ret ? '#' : '-'); |             printf("%c ", ret ? '#' : '-'); | ||||||
|         } |         } | ||||||
|         printf("\r\n"); |         printf("\r\n"); | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ | |||||||
| #include "cli.h" | #include "cli.h" | ||||||
| 
 | 
 | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <furi-hal.h> | #include <furi_hal.h> | ||||||
| 
 | 
 | ||||||
| #include <m-dict.h> | #include <m-dict.h> | ||||||
| #include <m-bptree.h> | #include <m-bptree.h> | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| #include <furi-hal.h> | #include <furi_hal.h> | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| 
 | 
 | ||||||
| #include <lib/toolbox/args.h> | #include <lib/toolbox/args.h> | ||||||
| @ -312,8 +312,10 @@ void crypto_cli(Cli* cli, string_t args, void* context) { | |||||||
|     string_clear(cmd); |     string_clear(cmd); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void crypto_cli_init() { | void crypto_on_system_start() { | ||||||
|  | #ifdef SRV_CLI | ||||||
|     Cli* cli = furi_record_open("cli"); |     Cli* cli = furi_record_open("cli"); | ||||||
|     cli_add_command(cli, "crypto", CliCommandFlagDefault, crypto_cli, NULL); |     cli_add_command(cli, "crypto", CliCommandFlagDefault, crypto_cli, NULL); | ||||||
|     furi_record_close("cli"); |     furi_record_close("cli"); | ||||||
|  | #endif | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,10 +1,10 @@ | |||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <furi-hal.h> | #include <furi_hal.h> | ||||||
| 
 | 
 | ||||||
| #include <gui/gui.h> | #include <gui/gui.h> | ||||||
| #include <input/input.h> | #include <input/input.h> | ||||||
| 
 | 
 | ||||||
| #include <notification/notification-messages.h> | #include <notification/notification_messages.h> | ||||||
| 
 | 
 | ||||||
| #define BLINK_COLOR_COUNT 7 | #define BLINK_COLOR_COUNT 7 | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| #include "display_test.h" | #include "display_test.h" | ||||||
| 
 | 
 | ||||||
| #include <furi-hal.h> | #include <furi_hal.h> | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| 
 | 
 | ||||||
| // Need access to u8g2
 | // Need access to u8g2
 | ||||||
| @ -10,7 +10,7 @@ | |||||||
| 
 | 
 | ||||||
| #include <gui/view_dispatcher.h> | #include <gui/view_dispatcher.h> | ||||||
| #include <gui/modules/submenu.h> | #include <gui/modules/submenu.h> | ||||||
| #include <gui/modules/variable-item-list.h> | #include <gui/modules/variable_item_list.h> | ||||||
| 
 | 
 | ||||||
| #include "view_display_test.h" | #include "view_display_test.h" | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -2,11 +2,11 @@ | |||||||
| #include <m-string.h> | #include <m-string.h> | ||||||
| #include <gui/gui.h> | #include <gui/gui.h> | ||||||
| #include <notification/notification.h> | #include <notification/notification.h> | ||||||
| #include <notification/notification-messages.h> | #include <notification/notification_messages.h> | ||||||
| #include <gui/elements.h> | #include <gui/elements.h> | ||||||
| #include <stream_buffer.h> | #include <stream_buffer.h> | ||||||
| #include <furi-hal-uart.h> | #include <furi_hal_uart.h> | ||||||
| #include <furi-hal-console.h> | #include <furi_hal_console.h> | ||||||
| #include <gui/view_dispatcher.h> | #include <gui/view_dispatcher.h> | ||||||
| #include <gui/modules/dialog_ex.h> | #include <gui/modules/dialog_ex.h> | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <furi-hal.h> | #include <furi_hal.h> | ||||||
| #include <gui/gui.h> | #include <gui/gui.h> | ||||||
| #include <input/input.h> | #include <input/input.h> | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <furi-hal.h> | #include <furi_hal.h> | ||||||
| 
 | 
 | ||||||
| #include <gui/view.h> | #include <gui/view.h> | ||||||
| #include <gui/view_dispatcher.h> | #include <gui/view_dispatcher.h> | ||||||
|  | |||||||
| @ -1,9 +1,9 @@ | |||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <furi-hal.h> | #include <furi_hal.h> | ||||||
| 
 | 
 | ||||||
| #include <gui/gui.h> | #include <gui/gui.h> | ||||||
| #include <input/input.h> | #include <input/input.h> | ||||||
| #include <notification/notification-messages.h> | #include <notification/notification_messages.h> | ||||||
| 
 | 
 | ||||||
| void vibro_test_draw_callback(Canvas* canvas, void* ctx) { | void vibro_test_draw_callback(Canvas* canvas, void* ctx) { | ||||||
|     canvas_clear(canvas); |     canvas_clear(canvas); | ||||||
|  | |||||||
							
								
								
									
										434
									
								
								applications/desktop/animations/animation_manager.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										434
									
								
								applications/desktop/animations/animation_manager.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,434 @@ | |||||||
|  | #include "animation_manager.h" | ||||||
|  | #include "furi_hal_delay.h" | ||||||
|  | #include "portmacro.h" | ||||||
|  | #include "views/bubble_animation_view.h" | ||||||
|  | #include "animation_storage.h" | ||||||
|  | 
 | ||||||
|  | #include <cmsis_os2.h> | ||||||
|  | #include <dolphin/dolphin.h> | ||||||
|  | #include <furi/check.h> | ||||||
|  | #include <furi/pubsub.h> | ||||||
|  | #include <furi/record.h> | ||||||
|  | #include <m-string.h> | ||||||
|  | #include <power/power_service/power.h> | ||||||
|  | #include <stdint.h> | ||||||
|  | #include <storage/storage.h> | ||||||
|  | #include <dolphin/dolphin_i.h> | ||||||
|  | #include <storage/filesystem_api_defines.h> | ||||||
|  | 
 | ||||||
|  | #define TAG "AnimationManager" | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     AnimationManagerStateIdle, | ||||||
|  |     AnimationManagerStateBlocked, | ||||||
|  |     AnimationManagerStateFreezedIdle, | ||||||
|  |     AnimationManagerStateFreezedBlocked, | ||||||
|  | } AnimationManagerState; | ||||||
|  | 
 | ||||||
|  | struct AnimationManager { | ||||||
|  |     bool sd_show_url; | ||||||
|  |     bool sd_shown_no_db; | ||||||
|  |     bool sd_shown_sd_ok; | ||||||
|  |     AnimationManagerState state; | ||||||
|  |     FuriPubSubSubscription* pubsub_subscription_storage; | ||||||
|  |     FuriPubSubSubscription* pubsub_subscription_dolphin; | ||||||
|  |     BubbleAnimationView* animation_view; | ||||||
|  |     osTimerId_t idle_animation_timer; | ||||||
|  |     StorageAnimation* current_animation; | ||||||
|  |     AnimationManagerInteractCallback interact_callback; | ||||||
|  |     AnimationManagerSetNewIdleAnimationCallback new_idle_callback; | ||||||
|  |     AnimationManagerSetNewIdleAnimationCallback check_blocking_callback; | ||||||
|  |     void* context; | ||||||
|  |     string_t freezed_animation_name; | ||||||
|  |     int32_t freezed_animation_time_left; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static StorageAnimation* | ||||||
|  |     animation_manager_select_idle_animation(AnimationManager* animation_manager); | ||||||
|  | static void animation_manager_replace_current_animation( | ||||||
|  |     AnimationManager* animation_manager, | ||||||
|  |     StorageAnimation* storage_animation); | ||||||
|  | static void animation_manager_start_new_idle(AnimationManager* animation_manager); | ||||||
|  | static bool animation_manager_check_blocking(AnimationManager* animation_manager); | ||||||
|  | 
 | ||||||
|  | void animation_manager_set_context(AnimationManager* animation_manager, void* context) { | ||||||
|  |     furi_assert(animation_manager); | ||||||
|  |     animation_manager->context = context; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void animation_manager_set_new_idle_callback( | ||||||
|  |     AnimationManager* animation_manager, | ||||||
|  |     AnimationManagerSetNewIdleAnimationCallback callback) { | ||||||
|  |     furi_assert(animation_manager); | ||||||
|  |     animation_manager->new_idle_callback = callback; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void animation_manager_set_check_callback( | ||||||
|  |     AnimationManager* animation_manager, | ||||||
|  |     AnimationManagerCheckBlockingCallback callback) { | ||||||
|  |     furi_assert(animation_manager); | ||||||
|  |     animation_manager->check_blocking_callback = callback; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void animation_manager_set_interact_callback( | ||||||
|  |     AnimationManager* animation_manager, | ||||||
|  |     AnimationManagerInteractCallback callback) { | ||||||
|  |     furi_assert(animation_manager); | ||||||
|  |     animation_manager->interact_callback = callback; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void animation_manager_check_blocking_callback(const void* message, void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     AnimationManager* animation_manager = context; | ||||||
|  |     if(animation_manager->check_blocking_callback) { | ||||||
|  |         animation_manager->check_blocking_callback(animation_manager->context); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void animation_manager_timer_callback(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     AnimationManager* animation_manager = context; | ||||||
|  |     if(animation_manager->new_idle_callback) { | ||||||
|  |         animation_manager->new_idle_callback(animation_manager->context); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void animation_manager_interact_callback(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     AnimationManager* animation_manager = context; | ||||||
|  |     if(animation_manager->interact_callback) { | ||||||
|  |         animation_manager->interact_callback(animation_manager->context); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* reaction to animation_manager->interact_callback() */ | ||||||
|  | void animation_manager_check_blocking_process(AnimationManager* animation_manager) { | ||||||
|  |     furi_assert(animation_manager); | ||||||
|  | 
 | ||||||
|  |     if(animation_manager->state == AnimationManagerStateIdle) { | ||||||
|  |         animation_manager_check_blocking(animation_manager); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* reaction to animation_manager->new_idle_callback() */ | ||||||
|  | void animation_manager_new_idle_process(AnimationManager* animation_manager) { | ||||||
|  |     furi_assert(animation_manager); | ||||||
|  | 
 | ||||||
|  |     if(animation_manager->state == AnimationManagerStateIdle) { | ||||||
|  |         animation_manager_start_new_idle(animation_manager); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* reaction to animation_manager->check_blocking_callback() */ | ||||||
|  | void animation_manager_interact_process(AnimationManager* animation_manager) { | ||||||
|  |     furi_assert(animation_manager); | ||||||
|  | 
 | ||||||
|  |     if(animation_manager->state == AnimationManagerStateBlocked) { | ||||||
|  |         /* check if new blocking animation has to be displayed */ | ||||||
|  |         bool blocked = animation_manager_check_blocking(animation_manager); | ||||||
|  |         if(!blocked) { | ||||||
|  |             animation_manager_start_new_idle(animation_manager); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void animation_manager_start_new_idle(AnimationManager* animation_manager) { | ||||||
|  |     furi_assert(animation_manager); | ||||||
|  | 
 | ||||||
|  |     StorageAnimation* new_animation = animation_manager_select_idle_animation(animation_manager); | ||||||
|  |     animation_manager_replace_current_animation(animation_manager, new_animation); | ||||||
|  |     const BubbleAnimation* bubble_animation = | ||||||
|  |         animation_storage_get_bubble_animation(animation_manager->current_animation); | ||||||
|  |     animation_manager->state = AnimationManagerStateIdle; | ||||||
|  |     osTimerStart(animation_manager->idle_animation_timer, bubble_animation->duration * 1000); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool animation_manager_check_blocking(AnimationManager* animation_manager) { | ||||||
|  |     furi_assert(animation_manager); | ||||||
|  | 
 | ||||||
|  |     StorageAnimation* blocking_animation = NULL; | ||||||
|  |     Storage* storage = furi_record_open("storage"); | ||||||
|  |     FS_Error sd_status = storage_sd_status(storage); | ||||||
|  | 
 | ||||||
|  |     if(sd_status == FSE_INTERNAL) { | ||||||
|  |         blocking_animation = animation_storage_find_animation(BAD_SD_ANIMATION_NAME); | ||||||
|  |     } else if(sd_status == FSE_NOT_READY) { | ||||||
|  |         animation_manager->sd_shown_sd_ok = false; | ||||||
|  |         animation_manager->sd_shown_no_db = false; | ||||||
|  |     } else if(sd_status == FSE_OK) { | ||||||
|  |         if(!animation_manager->sd_shown_sd_ok) { | ||||||
|  |             blocking_animation = animation_storage_find_animation(SD_OK_ANIMATION_NAME); | ||||||
|  |             animation_manager->sd_shown_sd_ok = true; | ||||||
|  |         } else if(!animation_manager->sd_shown_no_db) { | ||||||
|  |             bool db_exists = storage_common_stat(storage, "/ext/Manifest", NULL) == FSE_OK; | ||||||
|  |             if(!db_exists) { | ||||||
|  |                 blocking_animation = animation_storage_find_animation(NO_DB_ANIMATION_NAME); | ||||||
|  |                 animation_manager->sd_shown_no_db = true; | ||||||
|  |                 animation_manager->sd_show_url = true; | ||||||
|  |             } | ||||||
|  |         } else if(animation_manager->sd_show_url) { | ||||||
|  |             blocking_animation = animation_storage_find_animation(URL_ANIMATION_NAME); | ||||||
|  |             animation_manager->sd_show_url = false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     Dolphin* dolphin = furi_record_open("dolphin"); | ||||||
|  |     DolphinStats stats = dolphin_stats(dolphin); | ||||||
|  |     furi_record_close("dolphin"); | ||||||
|  |     if(!blocking_animation && stats.level_up_is_pending) { | ||||||
|  |         blocking_animation = animation_storage_find_animation(LEVELUP_ANIMATION_NAME); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(blocking_animation) { | ||||||
|  |         osTimerStop(animation_manager->idle_animation_timer); | ||||||
|  |         animation_manager_replace_current_animation(animation_manager, blocking_animation); | ||||||
|  |         /* no starting timer because its blocking animation */ | ||||||
|  |         animation_manager->state = AnimationManagerStateBlocked; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     furi_record_close("storage"); | ||||||
|  | 
 | ||||||
|  |     return !!blocking_animation; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void animation_manager_replace_current_animation( | ||||||
|  |     AnimationManager* animation_manager, | ||||||
|  |     StorageAnimation* storage_animation) { | ||||||
|  |     furi_assert(storage_animation); | ||||||
|  |     StorageAnimation* previous_animation = animation_manager->current_animation; | ||||||
|  | 
 | ||||||
|  |     const BubbleAnimation* animation = animation_storage_get_bubble_animation(storage_animation); | ||||||
|  |     bubble_animation_view_set_animation(animation_manager->animation_view, animation); | ||||||
|  |     const char* new_name = string_get_cstr(animation_storage_get_meta(storage_animation)->name); | ||||||
|  |     FURI_LOG_I(TAG, "Select \'%s\' animation", new_name); | ||||||
|  |     animation_manager->current_animation = storage_animation; | ||||||
|  | 
 | ||||||
|  |     if(previous_animation) { | ||||||
|  |         animation_storage_free_storage_animation(&previous_animation); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | AnimationManager* animation_manager_alloc(void) { | ||||||
|  |     animation_storage_initialize_internal_animations(); | ||||||
|  |     AnimationManager* animation_manager = furi_alloc(sizeof(AnimationManager)); | ||||||
|  |     animation_manager->animation_view = bubble_animation_view_alloc(); | ||||||
|  |     string_init(animation_manager->freezed_animation_name); | ||||||
|  | 
 | ||||||
|  |     animation_manager->idle_animation_timer = | ||||||
|  |         osTimerNew(animation_manager_timer_callback, osTimerOnce, animation_manager, NULL); | ||||||
|  |     bubble_animation_view_set_interact_callback( | ||||||
|  |         animation_manager->animation_view, animation_manager_interact_callback, animation_manager); | ||||||
|  | 
 | ||||||
|  |     Storage* storage = furi_record_open("storage"); | ||||||
|  |     animation_manager->pubsub_subscription_storage = furi_pubsub_subscribe( | ||||||
|  |         storage_get_pubsub(storage), animation_manager_check_blocking_callback, animation_manager); | ||||||
|  |     furi_record_close("storage"); | ||||||
|  | 
 | ||||||
|  |     Dolphin* dolphin = furi_record_open("dolphin"); | ||||||
|  |     animation_manager->pubsub_subscription_dolphin = furi_pubsub_subscribe( | ||||||
|  |         dolphin_get_pubsub(dolphin), animation_manager_check_blocking_callback, animation_manager); | ||||||
|  |     furi_record_close("dolphin"); | ||||||
|  | 
 | ||||||
|  |     animation_manager->sd_shown_sd_ok = true; | ||||||
|  |     if(!animation_manager_check_blocking(animation_manager)) { | ||||||
|  |         animation_manager_start_new_idle(animation_manager); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return animation_manager; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void animation_manager_free(AnimationManager* animation_manager) { | ||||||
|  |     furi_assert(animation_manager); | ||||||
|  | 
 | ||||||
|  |     Dolphin* dolphin = furi_record_open("dolphin"); | ||||||
|  |     furi_pubsub_unsubscribe( | ||||||
|  |         dolphin_get_pubsub(dolphin), animation_manager->pubsub_subscription_dolphin); | ||||||
|  |     furi_record_close("dolphin"); | ||||||
|  | 
 | ||||||
|  |     Storage* storage = furi_record_open("storage"); | ||||||
|  |     furi_pubsub_unsubscribe( | ||||||
|  |         storage_get_pubsub(storage), animation_manager->pubsub_subscription_storage); | ||||||
|  |     furi_record_close("storage"); | ||||||
|  | 
 | ||||||
|  |     string_clear(animation_manager->freezed_animation_name); | ||||||
|  |     bubble_animation_view_free(animation_manager->animation_view); | ||||||
|  |     osTimerDelete(animation_manager->idle_animation_timer); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | View* animation_manager_get_animation_view(AnimationManager* animation_manager) { | ||||||
|  |     furi_assert(animation_manager); | ||||||
|  | 
 | ||||||
|  |     return bubble_animation_get_view(animation_manager->animation_view); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static StorageAnimation* | ||||||
|  |     animation_manager_select_idle_animation(AnimationManager* animation_manager) { | ||||||
|  |     StorageAnimationList_t animation_list; | ||||||
|  |     StorageAnimationList_init(animation_list); | ||||||
|  |     animation_storage_fill_animation_list(&animation_list); | ||||||
|  | 
 | ||||||
|  |     Power* power = furi_record_open("power"); | ||||||
|  |     bool battery_is_well = power_is_battery_healthy(power); | ||||||
|  |     furi_record_close("power"); | ||||||
|  | 
 | ||||||
|  |     Storage* storage = furi_record_open("storage"); | ||||||
|  |     FS_Error sd_status = storage_sd_status(storage); | ||||||
|  |     furi_record_close("storage"); | ||||||
|  | 
 | ||||||
|  |     Dolphin* dolphin = furi_record_open("dolphin"); | ||||||
|  |     DolphinStats stats = dolphin_stats(dolphin); | ||||||
|  |     uint32_t whole_weight = 0; | ||||||
|  | 
 | ||||||
|  |     StorageAnimationList_it_t it; | ||||||
|  |     for(StorageAnimationList_it(it, animation_list); !StorageAnimationList_end_p(it);) { | ||||||
|  |         StorageAnimation* storage_animation = *StorageAnimationList_ref(it); | ||||||
|  |         const StorageAnimationMeta* meta = animation_storage_get_meta(storage_animation); | ||||||
|  |         bool skip_animation = false; | ||||||
|  |         if(battery_is_well && !string_cmp_str(meta->name, BAD_BATTERY_ANIMATION_NAME)) { | ||||||
|  |             skip_animation = true; | ||||||
|  |         } else if((sd_status != FSE_NOT_READY) && !string_cmp_str(meta->name, NO_SD_ANIMATION_NAME)) { | ||||||
|  |             skip_animation = true; | ||||||
|  |         } else if((stats.butthurt < meta->min_butthurt) || (stats.butthurt > meta->max_butthurt)) { | ||||||
|  |             skip_animation = true; | ||||||
|  |         } else if((stats.level < meta->min_level) || (stats.level > meta->max_level)) { | ||||||
|  |             skip_animation = true; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if(skip_animation) { | ||||||
|  |             animation_storage_free_storage_animation(&storage_animation); | ||||||
|  |             /* remove and increase iterator */ | ||||||
|  |             StorageAnimationList_remove(animation_list, it); | ||||||
|  |         } else { | ||||||
|  |             whole_weight += meta->weight; | ||||||
|  |             StorageAnimationList_next(it); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     uint32_t lucky_number = random() % whole_weight; | ||||||
|  |     uint32_t weight = 0; | ||||||
|  | 
 | ||||||
|  |     StorageAnimation* selected = NULL; | ||||||
|  |     for | ||||||
|  |         M_EACH(item, animation_list, StorageAnimationList_t) { | ||||||
|  |             if(lucky_number < weight) { | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |             weight += animation_storage_get_meta(*item)->weight; | ||||||
|  |             selected = *item; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     for | ||||||
|  |         M_EACH(item, animation_list, StorageAnimationList_t) { | ||||||
|  |             if(*item != selected) { | ||||||
|  |                 animation_storage_free_storage_animation(item); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     StorageAnimationList_clear(animation_list); | ||||||
|  |     furi_record_close("dolphin"); | ||||||
|  | 
 | ||||||
|  |     /* cache animation, if failed - choose reliable animation */ | ||||||
|  |     if(!animation_storage_get_bubble_animation(selected)) { | ||||||
|  |         const char* name = string_get_cstr(animation_storage_get_meta(selected)->name); | ||||||
|  |         FURI_LOG_E(TAG, "Can't upload animation described in manifest: \'%s\'", name); | ||||||
|  |         animation_storage_free_storage_animation(&selected); | ||||||
|  |         selected = animation_storage_find_animation(HARDCODED_ANIMATION_NAME); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     furi_assert(selected); | ||||||
|  |     return selected; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void animation_manager_unload_and_stall_animation(AnimationManager* animation_manager) { | ||||||
|  |     furi_assert(animation_manager); | ||||||
|  |     furi_assert(animation_manager->current_animation); | ||||||
|  |     furi_assert(!string_size(animation_manager->freezed_animation_name)); | ||||||
|  |     furi_assert( | ||||||
|  |         (animation_manager->state == AnimationManagerStateIdle) || | ||||||
|  |         (animation_manager->state == AnimationManagerStateBlocked)); | ||||||
|  | 
 | ||||||
|  |     if(animation_manager->state == AnimationManagerStateBlocked) { | ||||||
|  |         animation_manager->state = AnimationManagerStateFreezedBlocked; | ||||||
|  |     } else if(animation_manager->state == AnimationManagerStateIdle) { | ||||||
|  |         animation_manager->state = AnimationManagerStateFreezedIdle; | ||||||
|  | 
 | ||||||
|  |         animation_manager->freezed_animation_time_left = | ||||||
|  |             xTimerGetExpiryTime(animation_manager->idle_animation_timer) - xTaskGetTickCount(); | ||||||
|  |         if(animation_manager->freezed_animation_time_left < 0) { | ||||||
|  |             animation_manager->freezed_animation_time_left = 0; | ||||||
|  |         } | ||||||
|  |         osTimerStop(animation_manager->idle_animation_timer); | ||||||
|  |     } else { | ||||||
|  |         furi_assert(0); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     StorageAnimationMeta* meta = animation_storage_get_meta(animation_manager->current_animation); | ||||||
|  |     /* copy str, not move, because it can be internal animation */ | ||||||
|  |     string_set(animation_manager->freezed_animation_name, meta->name); | ||||||
|  | 
 | ||||||
|  |     bubble_animation_freeze(animation_manager->animation_view); | ||||||
|  |     animation_storage_free_storage_animation(&animation_manager->current_animation); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void animation_manager_load_and_continue_animation(AnimationManager* animation_manager) { | ||||||
|  |     furi_assert(animation_manager); | ||||||
|  |     furi_assert(!animation_manager->current_animation); | ||||||
|  |     furi_assert(string_size(animation_manager->freezed_animation_name)); | ||||||
|  |     furi_assert( | ||||||
|  |         (animation_manager->state == AnimationManagerStateFreezedIdle) || | ||||||
|  |         (animation_manager->state == AnimationManagerStateFreezedBlocked)); | ||||||
|  | 
 | ||||||
|  |     if(animation_manager->state == AnimationManagerStateFreezedBlocked) { | ||||||
|  |         StorageAnimation* restore_animation = animation_storage_find_animation( | ||||||
|  |             string_get_cstr(animation_manager->freezed_animation_name)); | ||||||
|  |         /* all blocked animations must be in flipper -> we can
 | ||||||
|  |          * always find blocking animation */ | ||||||
|  |         furi_assert(restore_animation); | ||||||
|  |         animation_manager_replace_current_animation(animation_manager, restore_animation); | ||||||
|  |         animation_manager->state = AnimationManagerStateBlocked; | ||||||
|  |     } else if(animation_manager->state == AnimationManagerStateFreezedIdle) { | ||||||
|  |         /* check if we missed some system notifications, and set current_animation */ | ||||||
|  |         bool blocked = animation_manager_check_blocking(animation_manager); | ||||||
|  |         if(!blocked) { | ||||||
|  |             /* if no blocking - try restore last one idle */ | ||||||
|  |             StorageAnimation* restore_animation = animation_storage_find_animation( | ||||||
|  |                 string_get_cstr(animation_manager->freezed_animation_name)); | ||||||
|  |             if(restore_animation) { | ||||||
|  |                 animation_manager_replace_current_animation(animation_manager, restore_animation); | ||||||
|  |                 animation_manager->state = AnimationManagerStateIdle; | ||||||
|  | 
 | ||||||
|  |                 if(animation_manager->freezed_animation_time_left) { | ||||||
|  |                     osTimerStart( | ||||||
|  |                         animation_manager->idle_animation_timer, | ||||||
|  |                         animation_manager->freezed_animation_time_left); | ||||||
|  |                 } else { | ||||||
|  |                     const BubbleAnimation* animation = animation_storage_get_bubble_animation( | ||||||
|  |                         animation_manager->current_animation); | ||||||
|  |                     osTimerStart( | ||||||
|  |                         animation_manager->idle_animation_timer, animation->duration * 1000); | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 FURI_LOG_E( | ||||||
|  |                     TAG, | ||||||
|  |                     "Failed to restore \'%s\'", | ||||||
|  |                     string_get_cstr(animation_manager->freezed_animation_name)); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         /* Unknown state is an error. But not in release version.*/ | ||||||
|  |         furi_assert(0); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /* if can't restore previous animation - select new */ | ||||||
|  |     if(!animation_manager->current_animation) { | ||||||
|  |         animation_manager_start_new_idle(animation_manager); | ||||||
|  |     } | ||||||
|  |     FURI_LOG_D( | ||||||
|  |         TAG, | ||||||
|  |         "Load & Continue with \'%s\'", | ||||||
|  |         string_get_cstr(animation_storage_get_meta(animation_manager->current_animation)->name)); | ||||||
|  | 
 | ||||||
|  |     bubble_animation_unfreeze(animation_manager->animation_view); | ||||||
|  |     string_reset(animation_manager->freezed_animation_name); | ||||||
|  |     furi_assert(animation_manager->current_animation); | ||||||
|  | } | ||||||
							
								
								
									
										151
									
								
								applications/desktop/animations/animation_manager.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								applications/desktop/animations/animation_manager.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,151 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include "dolphin/dolphin.h" | ||||||
|  | #include <gui/view.h> | ||||||
|  | #include <stdint.h> | ||||||
|  | 
 | ||||||
|  | typedef struct AnimationManager AnimationManager; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     uint8_t x; | ||||||
|  |     uint8_t y; | ||||||
|  |     const char* str; | ||||||
|  |     Align horizontal; | ||||||
|  |     Align vertical; | ||||||
|  | } Bubble; | ||||||
|  | 
 | ||||||
|  | typedef struct FrameBubble { | ||||||
|  |     Bubble bubble; | ||||||
|  |     uint8_t starts_at_frame; | ||||||
|  |     uint8_t ends_at_frame; | ||||||
|  |     struct FrameBubble* next_bubble; | ||||||
|  | } FrameBubble; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     FrameBubble** frame_bubbles; | ||||||
|  |     uint8_t frame_bubbles_count; | ||||||
|  |     const Icon** icons; | ||||||
|  |     uint8_t passive_frames; | ||||||
|  |     uint8_t active_frames; | ||||||
|  |     uint8_t active_cycles; | ||||||
|  |     uint8_t frame_rate; | ||||||
|  |     uint16_t duration; | ||||||
|  |     uint16_t active_cooldown; | ||||||
|  | } BubbleAnimation; | ||||||
|  | 
 | ||||||
|  | typedef void (*AnimationManagerSetNewIdleAnimationCallback)(void* context); | ||||||
|  | typedef void (*AnimationManagerCheckBlockingCallback)(void* context); | ||||||
|  | typedef void (*AnimationManagerInteractCallback)(void*); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Allocate Animation Manager | ||||||
|  |  * | ||||||
|  |  * @return animation manager instance | ||||||
|  |  */ | ||||||
|  | AnimationManager* animation_manager_alloc(void); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Free Animation Manager | ||||||
|  |  * | ||||||
|  |  * @animation_manager   instance | ||||||
|  |  */ | ||||||
|  | void animation_manager_free(AnimationManager* animation_manager); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Get View of Animation Manager | ||||||
|  |  * | ||||||
|  |  * @animation_manager   instance | ||||||
|  |  * @return      view | ||||||
|  |  */ | ||||||
|  | View* animation_manager_get_animation_view(AnimationManager* animation_manager); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Set context for all callbacks for Animation Manager | ||||||
|  |  * | ||||||
|  |  * @animation_manager   instance | ||||||
|  |  * @context             context | ||||||
|  |  */ | ||||||
|  | void animation_manager_set_context(AnimationManager* animation_manager, void* context); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Set callback for Animation Manager for defered calls | ||||||
|  |  * for animation_manager_new_idle_process(). | ||||||
|  |  * Animation Manager doesn't have it's own thread, so main thread gives | ||||||
|  |  * callbacks to A.M. to call when it should perform some inner manipulations. | ||||||
|  |  * This callback is called from other threads and should notify main thread | ||||||
|  |  * when to call animation_manager_new_idle_process(). | ||||||
|  |  * So scheme is this: | ||||||
|  |  * A.M. sets callbacks, | ||||||
|  |  * callbacks notifies main thread | ||||||
|  |  * main thread in its own context calls appropriate *_process() function. | ||||||
|  |  * | ||||||
|  |  * @animation_manager   instance | ||||||
|  |  * @callback            callback | ||||||
|  |  */ | ||||||
|  | void animation_manager_set_new_idle_callback( | ||||||
|  |     AnimationManager* animation_manager, | ||||||
|  |     AnimationManagerSetNewIdleAnimationCallback callback); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Function to call in main thread as a response to | ||||||
|  |  * set_new_idle_callback's call. | ||||||
|  |  * | ||||||
|  |  * @animation_manager   instance | ||||||
|  |  */ | ||||||
|  | void animation_manager_new_idle_process(AnimationManager* animation_manager); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Set callback for Animation Manager for defered calls | ||||||
|  |  * for animation_manager_check_blocking_process(). | ||||||
|  |  * | ||||||
|  |  * @animation_manager   instance | ||||||
|  |  * @callback            callback | ||||||
|  |  */ | ||||||
|  | void animation_manager_set_check_callback( | ||||||
|  |     AnimationManager* animation_manager, | ||||||
|  |     AnimationManagerCheckBlockingCallback callback); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Function to call in main thread as a response to | ||||||
|  |  * set_new_idle_callback's call. | ||||||
|  |  * | ||||||
|  |  * @animation_manager   instance | ||||||
|  |  */ | ||||||
|  | void animation_manager_check_blocking_process(AnimationManager* animation_manager); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Set callback for Animation Manager for defered calls | ||||||
|  |  * for animation_manager_interact_process(). | ||||||
|  |  * | ||||||
|  |  * @animation_manager   instance | ||||||
|  |  * @callback            callback | ||||||
|  |  */ | ||||||
|  | void animation_manager_set_interact_callback( | ||||||
|  |     AnimationManager* animation_manager, | ||||||
|  |     AnimationManagerInteractCallback callback); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Function to call in main thread as a response to | ||||||
|  |  * set_new_idle_callback's call. | ||||||
|  |  * | ||||||
|  |  * @animation_manager   instance | ||||||
|  |  */ | ||||||
|  | void animation_manager_interact_process(AnimationManager* animation_manager); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Unload and Stall animation actions. Draw callback in view | ||||||
|  |  * paints first frame of current animation until | ||||||
|  |  * animation_manager_load_and_continue_animation() is called. | ||||||
|  |  * Can't be called multiple times. Every Stall has to be finished | ||||||
|  |  * with Continue. | ||||||
|  |  * | ||||||
|  |  * @animation_manager   instance | ||||||
|  |  */ | ||||||
|  | void animation_manager_unload_and_stall_animation(AnimationManager* animation_manager); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Load and Contunue execution of animation manager. | ||||||
|  |  * | ||||||
|  |  * @animation_manager   instance | ||||||
|  |  */ | ||||||
|  | void animation_manager_load_and_continue_animation(AnimationManager* animation_manager); | ||||||
							
								
								
									
										485
									
								
								applications/desktop/animations/animation_storage.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										485
									
								
								applications/desktop/animations/animation_storage.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,485 @@ | |||||||
|  | #include "animation_manager.h" | ||||||
|  | #include "file_worker.h" | ||||||
|  | #include "flipper_file.h" | ||||||
|  | #include "furi/common_defines.h" | ||||||
|  | #include "furi/memmgr.h" | ||||||
|  | #include "furi/record.h" | ||||||
|  | #include "animation_storage.h" | ||||||
|  | #include "gui/canvas.h" | ||||||
|  | #include "m-string.h" | ||||||
|  | #include "pb.h" | ||||||
|  | #include "pb_decode.h" | ||||||
|  | #include "storage/filesystem_api_defines.h" | ||||||
|  | #include "storage/storage.h" | ||||||
|  | #include "animation_storage_i.h" | ||||||
|  | #include <stdint.h> | ||||||
|  | #include <gui/icon_i.h> | ||||||
|  | 
 | ||||||
|  | // Read documentation before using it
 | ||||||
|  | #include <furi/dangerous_defines.h> | ||||||
|  | 
 | ||||||
|  | #define ANIMATION_META_FILE "meta.txt" | ||||||
|  | #define ANIMATION_DIR "/ext/dolphin/animations" | ||||||
|  | #define ANIMATION_MANIFEST_FILE ANIMATION_DIR "/manifest.txt" | ||||||
|  | #define TAG "AnimationStorage" | ||||||
|  | #define DEBUG_PB 0 | ||||||
|  | 
 | ||||||
|  | static void animation_storage_free_bubbles(BubbleAnimation* animation); | ||||||
|  | static void animation_storage_free_frames(BubbleAnimation* animation); | ||||||
|  | static void animation_storage_free_animation(BubbleAnimation** storage_animation); | ||||||
|  | static BubbleAnimation* animation_storage_load_animation(const char* name); | ||||||
|  | 
 | ||||||
|  | void animation_storage_fill_animation_list(StorageAnimationList_t* animation_list) { | ||||||
|  |     furi_assert(sizeof(StorageAnimationList_t) == sizeof(void*)); | ||||||
|  |     furi_assert(!StorageAnimationList_size(*animation_list)); | ||||||
|  | 
 | ||||||
|  |     Storage* storage = furi_record_open("storage"); | ||||||
|  |     FlipperFile* file = flipper_file_alloc(storage); | ||||||
|  |     /* Forbid skipping fields */ | ||||||
|  |     flipper_file_set_strict_mode(file, true); | ||||||
|  |     string_t header; | ||||||
|  |     string_init(header); | ||||||
|  | 
 | ||||||
|  |     do { | ||||||
|  |         uint32_t u32value; | ||||||
|  |         StorageAnimation* storage_animation = NULL; | ||||||
|  | 
 | ||||||
|  |         if(FSE_OK != storage_sd_status(storage)) break; | ||||||
|  |         if(!flipper_file_open_existing(file, ANIMATION_MANIFEST_FILE)) break; | ||||||
|  |         if(!flipper_file_read_header(file, header, &u32value)) break; | ||||||
|  |         if(string_cmp_str(header, "Flipper Animation Manifest")) break; | ||||||
|  |         do { | ||||||
|  |             storage_animation = furi_alloc(sizeof(StorageAnimation)); | ||||||
|  |             storage_animation->external = true; | ||||||
|  |             storage_animation->animation = NULL; | ||||||
|  | 
 | ||||||
|  |             if(!flipper_file_read_string(file, "Name", storage_animation->meta.name)) break; | ||||||
|  |             if(!flipper_file_read_uint32(file, "Min butthurt", &u32value, 1)) break; | ||||||
|  |             storage_animation->meta.min_butthurt = u32value; | ||||||
|  |             if(!flipper_file_read_uint32(file, "Max butthurt", &u32value, 1)) break; | ||||||
|  |             storage_animation->meta.max_butthurt = u32value; | ||||||
|  |             if(!flipper_file_read_uint32(file, "Min level", &u32value, 1)) break; | ||||||
|  |             storage_animation->meta.min_level = u32value; | ||||||
|  |             if(!flipper_file_read_uint32(file, "Max level", &u32value, 1)) break; | ||||||
|  |             storage_animation->meta.max_level = u32value; | ||||||
|  |             if(!flipper_file_read_uint32(file, "Weight", &u32value, 1)) break; | ||||||
|  |             storage_animation->meta.weight = u32value; | ||||||
|  | 
 | ||||||
|  |             StorageAnimationList_push_back(*animation_list, storage_animation); | ||||||
|  |         } while(1); | ||||||
|  | 
 | ||||||
|  |         animation_storage_free_storage_animation(&storage_animation); | ||||||
|  |     } while(0); | ||||||
|  | 
 | ||||||
|  |     string_clear(header); | ||||||
|  |     flipper_file_close(file); | ||||||
|  |     flipper_file_free(file); | ||||||
|  | 
 | ||||||
|  |     // add hard-coded animations
 | ||||||
|  |     for(int i = 0; i < COUNT_OF(StorageAnimationInternal); ++i) { | ||||||
|  |         StorageAnimationList_push_back(*animation_list, &StorageAnimationInternal[i]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     furi_record_close("storage"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | StorageAnimation* animation_storage_find_animation(const char* name) { | ||||||
|  |     furi_assert(name); | ||||||
|  |     furi_assert(strlen(name)); | ||||||
|  |     StorageAnimation* storage_animation = NULL; | ||||||
|  | 
 | ||||||
|  |     /* look through internal animations */ | ||||||
|  |     for(int i = 0; i < COUNT_OF(StorageAnimationInternal); ++i) { | ||||||
|  |         if(!string_cmp_str(StorageAnimationInternal[i].meta.name, name)) { | ||||||
|  |             storage_animation = &StorageAnimationInternal[i]; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /* look through external animations */ | ||||||
|  |     if(!storage_animation) { | ||||||
|  |         BubbleAnimation* animation = animation_storage_load_animation(name); | ||||||
|  | 
 | ||||||
|  |         if(animation != NULL) { | ||||||
|  |             storage_animation = furi_alloc(sizeof(StorageAnimation)); | ||||||
|  |             storage_animation->animation = animation; | ||||||
|  |             storage_animation->external = true; | ||||||
|  |             /* meta data takes part in random animation selection, so it
 | ||||||
|  |              * doesn't need here as we exactly know which animation we need, | ||||||
|  |              * that's why we can ignore reading manifest.txt file | ||||||
|  |              * filling meta data by zeroes */ | ||||||
|  |             storage_animation->meta.min_butthurt = 0; | ||||||
|  |             storage_animation->meta.max_butthurt = 0; | ||||||
|  |             storage_animation->meta.min_level = 0; | ||||||
|  |             storage_animation->meta.max_level = 0; | ||||||
|  |             storage_animation->meta.weight = 0; | ||||||
|  |             string_init_set_str(storage_animation->meta.name, name); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return storage_animation; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | StorageAnimationMeta* animation_storage_get_meta(StorageAnimation* storage_animation) { | ||||||
|  |     furi_assert(storage_animation); | ||||||
|  |     return &storage_animation->meta; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const BubbleAnimation* | ||||||
|  |     animation_storage_get_bubble_animation(StorageAnimation* storage_animation) { | ||||||
|  |     furi_assert(storage_animation); | ||||||
|  |     animation_storage_cache_animation(storage_animation); | ||||||
|  |     return storage_animation->animation; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void animation_storage_cache_animation(StorageAnimation* storage_animation) { | ||||||
|  |     furi_assert(storage_animation); | ||||||
|  | 
 | ||||||
|  |     if(storage_animation->external) { | ||||||
|  |         if(!storage_animation->animation) { | ||||||
|  |             storage_animation->animation = | ||||||
|  |                 animation_storage_load_animation(string_get_cstr(storage_animation->meta.name)); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void animation_storage_free_animation(BubbleAnimation** animation) { | ||||||
|  |     furi_assert(animation); | ||||||
|  | 
 | ||||||
|  |     if(*animation) { | ||||||
|  |         animation_storage_free_bubbles(*animation); | ||||||
|  |         animation_storage_free_frames(*animation); | ||||||
|  |         free(*animation); | ||||||
|  |         *animation = NULL; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void animation_storage_free_storage_animation(StorageAnimation** storage_animation) { | ||||||
|  |     furi_assert(storage_animation); | ||||||
|  |     furi_assert(*storage_animation); | ||||||
|  | 
 | ||||||
|  |     if((*storage_animation)->external) { | ||||||
|  |         animation_storage_free_animation((BubbleAnimation**)&(*storage_animation)->animation); | ||||||
|  | 
 | ||||||
|  |         string_clear((*storage_animation)->meta.name); | ||||||
|  |         free(*storage_animation); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     *storage_animation = NULL; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool animation_storage_cast_align(string_t align_str, Align* align) { | ||||||
|  |     if(!string_cmp_str(align_str, "Bottom")) { | ||||||
|  |         *align = AlignBottom; | ||||||
|  |     } else if(!string_cmp_str(align_str, "Top")) { | ||||||
|  |         *align = AlignTop; | ||||||
|  |     } else if(!string_cmp_str(align_str, "Left")) { | ||||||
|  |         *align = AlignLeft; | ||||||
|  |     } else if(!string_cmp_str(align_str, "Right")) { | ||||||
|  |         *align = AlignRight; | ||||||
|  |     } else if(!string_cmp_str(align_str, "Center")) { | ||||||
|  |         *align = AlignCenter; | ||||||
|  |     } else { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void animation_storage_free_frames(BubbleAnimation* animation) { | ||||||
|  |     furi_assert(animation); | ||||||
|  |     furi_assert(animation->icons); | ||||||
|  | 
 | ||||||
|  |     const Icon** icons = animation->icons; | ||||||
|  |     uint16_t frames = animation->active_frames + animation->passive_frames; | ||||||
|  |     furi_assert(frames > 0); | ||||||
|  | 
 | ||||||
|  |     for(int i = 0; i < frames; ++i) { | ||||||
|  |         if(!icons[i]) continue; | ||||||
|  | 
 | ||||||
|  |         const Icon* icon = icons[i]; | ||||||
|  |         free((void*)icon->frames[0]); | ||||||
|  |         free(icon->frames); | ||||||
|  |         free((void*)icon); | ||||||
|  |         for(int j = i; j < frames; ++j) { | ||||||
|  |             if(icons[j] == icon) { | ||||||
|  |                 icons[j] = NULL; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     free(animation->icons); | ||||||
|  |     animation->icons = NULL; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static Icon* animation_storage_alloc_icon(size_t frame_size) { | ||||||
|  |     Icon* icon = furi_alloc(sizeof(Icon)); | ||||||
|  |     icon->frames = furi_alloc(sizeof(const uint8_t*)); | ||||||
|  |     icon->frames[0] = furi_alloc(frame_size); | ||||||
|  |     return icon; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void animation_storage_free_icon(Icon* icon) { | ||||||
|  |     free((void*)icon->frames[0]); | ||||||
|  |     free(icon->frames); | ||||||
|  |     free(icon); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool animation_storage_load_frames( | ||||||
|  |     Storage* storage, | ||||||
|  |     const char* name, | ||||||
|  |     BubbleAnimation* animation, | ||||||
|  |     uint32_t* frame_order, | ||||||
|  |     uint32_t width, | ||||||
|  |     uint32_t height) { | ||||||
|  |     furi_assert(!animation->icons); | ||||||
|  |     uint16_t frame_order_size = animation->passive_frames + animation->active_frames; | ||||||
|  | 
 | ||||||
|  |     bool frames_ok = false; | ||||||
|  |     animation->icons = furi_alloc(sizeof(const Icon*) * frame_order_size); | ||||||
|  |     File* file = storage_file_alloc(storage); | ||||||
|  |     FileInfo file_info; | ||||||
|  |     string_t filename; | ||||||
|  |     string_init(filename); | ||||||
|  |     size_t max_filesize = ROUND_UP_TO(width, 8) * height + 1; | ||||||
|  | 
 | ||||||
|  |     for(int i = 0; i < frame_order_size; ++i) { | ||||||
|  |         if(animation->icons[i]) continue; | ||||||
|  | 
 | ||||||
|  |         frames_ok = false; | ||||||
|  |         string_printf(filename, ANIMATION_DIR "/%s/frame_%d.bm", name, frame_order[i]); | ||||||
|  | 
 | ||||||
|  |         if(storage_common_stat(storage, string_get_cstr(filename), &file_info) != FSE_OK) break; | ||||||
|  |         if(file_info.size > max_filesize) { | ||||||
|  |             FURI_LOG_E( | ||||||
|  |                 TAG, | ||||||
|  |                 "Filesize %d, max: %d (width %d, height %d)", | ||||||
|  |                 file_info.size, | ||||||
|  |                 max_filesize, | ||||||
|  |                 width, | ||||||
|  |                 height); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         if(!storage_file_open(file, string_get_cstr(filename), FSAM_READ, FSOM_OPEN_EXISTING)) { | ||||||
|  |             FURI_LOG_E(TAG, "Can't open file \'%s\'", string_get_cstr(filename)); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         Icon* icon = animation_storage_alloc_icon(file_info.size); | ||||||
|  |         if(storage_file_read(file, (void*)icon->frames[0], file_info.size) != file_info.size) { | ||||||
|  |             FURI_LOG_E(TAG, "Read failed: \'%s\'", string_get_cstr(filename)); | ||||||
|  |             animation_storage_free_icon(icon); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         storage_file_close(file); | ||||||
|  |         FURI_CONST_ASSIGN(icon->frame_count, 1); | ||||||
|  |         FURI_CONST_ASSIGN(icon->frame_rate, 0); | ||||||
|  |         FURI_CONST_ASSIGN(icon->height, height); | ||||||
|  |         FURI_CONST_ASSIGN(icon->width, width); | ||||||
|  | 
 | ||||||
|  |         /* Claim 1 allocation for 1 files blob and several links to it */ | ||||||
|  |         for(int j = i; j < frame_order_size; ++j) { | ||||||
|  |             if(frame_order[i] == frame_order[j]) { | ||||||
|  |                 animation->icons[j] = icon; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         frames_ok = true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(!frames_ok) { | ||||||
|  |         FURI_LOG_E( | ||||||
|  |             TAG, | ||||||
|  |             "Load \'%s\' failed, %dx%d, size: %d", | ||||||
|  |             string_get_cstr(filename), | ||||||
|  |             width, | ||||||
|  |             height, | ||||||
|  |             file_info.size); | ||||||
|  |         animation_storage_free_frames(animation); | ||||||
|  |         animation->icons = NULL; | ||||||
|  |     } else { | ||||||
|  |         for(int i = 0; i < frame_order_size; ++i) { | ||||||
|  |             furi_check(animation->icons[i]); | ||||||
|  |             furi_check(animation->icons[i]->frames[0]); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     storage_file_free(file); | ||||||
|  |     string_clear(filename); | ||||||
|  | 
 | ||||||
|  |     return frames_ok; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool animation_storage_load_bubbles(BubbleAnimation* animation, FlipperFile* ff) { | ||||||
|  |     uint32_t u32value; | ||||||
|  |     string_t str; | ||||||
|  |     string_init(str); | ||||||
|  |     bool success = false; | ||||||
|  |     furi_assert(!animation->frame_bubbles); | ||||||
|  | 
 | ||||||
|  |     do { | ||||||
|  |         if(!flipper_file_read_uint32(ff, "Bubble slots", &u32value, 1)) break; | ||||||
|  |         if(u32value > 20) break; | ||||||
|  |         animation->frame_bubbles_count = u32value; | ||||||
|  |         if(animation->frame_bubbles_count == 0) { | ||||||
|  |             animation->frame_bubbles = NULL; | ||||||
|  |             success = true; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         animation->frame_bubbles = | ||||||
|  |             furi_alloc(sizeof(FrameBubble*) * animation->frame_bubbles_count); | ||||||
|  | 
 | ||||||
|  |         uint32_t current_slot = 0; | ||||||
|  |         for(int i = 0; i < animation->frame_bubbles_count; ++i) { | ||||||
|  |             animation->frame_bubbles[i] = furi_alloc(sizeof(FrameBubble)); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         FrameBubble* bubble = animation->frame_bubbles[0]; | ||||||
|  |         int8_t index = -1; | ||||||
|  |         for(;;) { | ||||||
|  |             if(!flipper_file_read_uint32(ff, "Slot", ¤t_slot, 1)) break; | ||||||
|  |             if((current_slot != 0) && (index == -1)) break; | ||||||
|  | 
 | ||||||
|  |             if(current_slot == index) { | ||||||
|  |                 bubble->next_bubble = furi_alloc(sizeof(FrameBubble)); | ||||||
|  |                 bubble = bubble->next_bubble; | ||||||
|  |             } else if(current_slot == index + 1) { | ||||||
|  |                 ++index; | ||||||
|  |                 bubble = animation->frame_bubbles[index]; | ||||||
|  |             } else { | ||||||
|  |                 /* slots have to start from 0, be ascending sorted, and
 | ||||||
|  |                  * have exact number of slots as specified in "Bubble slots" */ | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |             if(index >= animation->frame_bubbles_count) break; | ||||||
|  | 
 | ||||||
|  |             if(!flipper_file_read_uint32(ff, "X", &u32value, 1)) break; | ||||||
|  |             bubble->bubble.x = u32value; | ||||||
|  |             if(!flipper_file_read_uint32(ff, "Y", &u32value, 1)) break; | ||||||
|  |             bubble->bubble.y = u32value; | ||||||
|  | 
 | ||||||
|  |             if(!flipper_file_read_string(ff, "Text", str)) break; | ||||||
|  |             if(string_size(str) > 100) break; | ||||||
|  | 
 | ||||||
|  |             string_replace_all_str(str, "\\n", "\n"); | ||||||
|  | 
 | ||||||
|  |             bubble->bubble.str = furi_alloc(string_size(str) + 1); | ||||||
|  |             strcpy((char*)bubble->bubble.str, string_get_cstr(str)); | ||||||
|  | 
 | ||||||
|  |             if(!flipper_file_read_string(ff, "AlignH", str)) break; | ||||||
|  |             if(!animation_storage_cast_align(str, &bubble->bubble.horizontal)) break; | ||||||
|  |             if(!flipper_file_read_string(ff, "AlignV", str)) break; | ||||||
|  |             if(!animation_storage_cast_align(str, &bubble->bubble.vertical)) break; | ||||||
|  | 
 | ||||||
|  |             if(!flipper_file_read_uint32(ff, "StartFrame", &u32value, 1)) break; | ||||||
|  |             bubble->starts_at_frame = u32value; | ||||||
|  |             if(!flipper_file_read_uint32(ff, "EndFrame", &u32value, 1)) break; | ||||||
|  |             bubble->ends_at_frame = u32value; | ||||||
|  |         } | ||||||
|  |         success = (index + 1) == animation->frame_bubbles_count; | ||||||
|  |     } while(0); | ||||||
|  | 
 | ||||||
|  |     if(!success) { | ||||||
|  |         if(animation->frame_bubbles) { | ||||||
|  |             FURI_LOG_E(TAG, "Failed to load animation bubbles"); | ||||||
|  |             animation_storage_free_bubbles(animation); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     string_clear(str); | ||||||
|  |     return success; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static BubbleAnimation* animation_storage_load_animation(const char* name) { | ||||||
|  |     furi_assert(name); | ||||||
|  |     BubbleAnimation* animation = furi_alloc(sizeof(BubbleAnimation)); | ||||||
|  | 
 | ||||||
|  |     uint32_t height = 0; | ||||||
|  |     uint32_t width = 0; | ||||||
|  |     uint32_t* u32array = NULL; | ||||||
|  |     Storage* storage = furi_record_open("storage"); | ||||||
|  |     FlipperFile* ff = flipper_file_alloc(storage); | ||||||
|  |     /* Forbid skipping fields */ | ||||||
|  |     flipper_file_set_strict_mode(ff, true); | ||||||
|  |     string_t str; | ||||||
|  |     string_init(str); | ||||||
|  |     animation->frame_bubbles = NULL; | ||||||
|  | 
 | ||||||
|  |     bool success = false; | ||||||
|  |     do { | ||||||
|  |         uint32_t u32value; | ||||||
|  | 
 | ||||||
|  |         if(FSE_OK != storage_sd_status(storage)) break; | ||||||
|  | 
 | ||||||
|  |         string_printf(str, ANIMATION_DIR "/%s/" ANIMATION_META_FILE, name); | ||||||
|  |         if(!flipper_file_open_existing(ff, string_get_cstr(str))) break; | ||||||
|  |         if(!flipper_file_read_header(ff, str, &u32value)) break; | ||||||
|  |         if(string_cmp_str(str, "Flipper Animation")) break; | ||||||
|  | 
 | ||||||
|  |         if(!flipper_file_read_uint32(ff, "Width", &width, 1)) break; | ||||||
|  |         if(!flipper_file_read_uint32(ff, "Height", &height, 1)) break; | ||||||
|  | 
 | ||||||
|  |         if(!flipper_file_read_uint32(ff, "Passive frames", &u32value, 1)) break; | ||||||
|  |         animation->passive_frames = u32value; | ||||||
|  |         if(!flipper_file_read_uint32(ff, "Active frames", &u32value, 1)) break; | ||||||
|  |         animation->active_frames = u32value; | ||||||
|  | 
 | ||||||
|  |         uint8_t frames = animation->passive_frames + animation->active_frames; | ||||||
|  |         u32array = furi_alloc(sizeof(uint32_t) * frames); | ||||||
|  |         if(!flipper_file_read_uint32(ff, "Frames order", u32array, frames)) break; | ||||||
|  | 
 | ||||||
|  |         /* passive and active frames must be loaded up to this point */ | ||||||
|  |         if(!animation_storage_load_frames(storage, name, animation, u32array, width, height)) | ||||||
|  |             break; | ||||||
|  | 
 | ||||||
|  |         if(!flipper_file_read_uint32(ff, "Active cycles", &u32value, 1)) break; | ||||||
|  |         animation->active_cycles = u32value; | ||||||
|  |         if(!flipper_file_read_uint32(ff, "Frame rate", &u32value, 1)) break; | ||||||
|  |         animation->frame_rate = u32value; | ||||||
|  |         if(!flipper_file_read_uint32(ff, "Duration", &u32value, 1)) break; | ||||||
|  |         animation->duration = u32value; | ||||||
|  |         if(!flipper_file_read_uint32(ff, "Active cooldown", &u32value, 1)) break; | ||||||
|  |         animation->active_cooldown = u32value; | ||||||
|  | 
 | ||||||
|  |         if(!animation_storage_load_bubbles(animation, ff)) break; | ||||||
|  |         success = true; | ||||||
|  |     } while(0); | ||||||
|  | 
 | ||||||
|  |     string_clear(str); | ||||||
|  |     flipper_file_close(ff); | ||||||
|  |     flipper_file_free(ff); | ||||||
|  |     if(u32array) { | ||||||
|  |         free(u32array); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(!success) { | ||||||
|  |         free(animation); | ||||||
|  |         animation = NULL; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return animation; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void animation_storage_free_bubbles(BubbleAnimation* animation) { | ||||||
|  |     if(!animation->frame_bubbles) return; | ||||||
|  | 
 | ||||||
|  |     for(int i = 0; i < animation->frame_bubbles_count;) { | ||||||
|  |         FrameBubble** bubble = &animation->frame_bubbles[i]; | ||||||
|  | 
 | ||||||
|  |         if((*bubble) == NULL) break; | ||||||
|  | 
 | ||||||
|  |         while((*bubble)->next_bubble != NULL) { | ||||||
|  |             bubble = &(*bubble)->next_bubble; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if((*bubble)->bubble.str) { | ||||||
|  |             free((void*)(*bubble)->bubble.str); | ||||||
|  |         } | ||||||
|  |         if((*bubble) == animation->frame_bubbles[i]) { | ||||||
|  |             ++i; | ||||||
|  |         } | ||||||
|  |         free(*bubble); | ||||||
|  |         *bubble = NULL; | ||||||
|  |     } | ||||||
|  |     free(animation->frame_bubbles); | ||||||
|  |     animation->frame_bubbles = NULL; | ||||||
|  | } | ||||||
							
								
								
									
										98
									
								
								applications/desktop/animations/animation_storage.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								applications/desktop/animations/animation_storage.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,98 @@ | |||||||
|  | #pragma once | ||||||
|  | #include <stdint.h> | ||||||
|  | #include <m-list.h> | ||||||
|  | #include "views/bubble_animation_view.h" | ||||||
|  | #include <m-string.h> | ||||||
|  | 
 | ||||||
|  | #define HARDCODED_ANIMATION_NAME "tv" | ||||||
|  | #define NO_SD_ANIMATION_NAME "no_sd" | ||||||
|  | #define BAD_BATTERY_ANIMATION_NAME "bad_battery" | ||||||
|  | #define NO_DB_ANIMATION_NAME "no_db" | ||||||
|  | #define BAD_SD_ANIMATION_NAME "bad_sd" | ||||||
|  | #define SD_OK_ANIMATION_NAME "sd_ok" | ||||||
|  | #define URL_ANIMATION_NAME "url" | ||||||
|  | #define LEVELUP_ANIMATION_NAME "level" | ||||||
|  | 
 | ||||||
|  | /** Main structure to handle animation data.
 | ||||||
|  |  * Contains all, including animation playing data (BubbleAnimation), | ||||||
|  |  * data for random animation selection (StorageAnimationMeta) and | ||||||
|  |  * flag of location internal/external */ | ||||||
|  | typedef struct StorageAnimation StorageAnimation; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     string_t name; | ||||||
|  |     uint8_t min_butthurt; | ||||||
|  |     uint8_t max_butthurt; | ||||||
|  |     uint8_t min_level; | ||||||
|  |     uint8_t max_level; | ||||||
|  |     uint8_t weight; | ||||||
|  | } StorageAnimationMeta; | ||||||
|  | 
 | ||||||
|  | /** Container to return available animations list */ | ||||||
|  | LIST_DEF(StorageAnimationList, StorageAnimation*, M_PTR_OPLIST) | ||||||
|  | #define M_OPL_StorageAnimationList_t() LIST_OPLIST(StorageAnimationList) | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Fill list of available animations. | ||||||
|  |  * List will contain all idle animations on inner flash | ||||||
|  |  * and all available on SD-card, mentioned in manifest.txt. | ||||||
|  |  * Performs caching of animation. If fail - falls back to | ||||||
|  |  * inner animation. | ||||||
|  |  * List has to be initialized. | ||||||
|  |  * | ||||||
|  |  * @list        list to fill with animations data | ||||||
|  |  */ | ||||||
|  | void animation_storage_fill_animation_list(StorageAnimationList_t* list); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Get bubble animation of storage animation. | ||||||
|  |  * Bubble Animation is a structure which describes animation | ||||||
|  |  * independent of it's place of storage and meta data. | ||||||
|  |  * It contain all what is need to be played. | ||||||
|  |  * If storage_animation is not cached - caches it. | ||||||
|  |  * | ||||||
|  |  * @storage_animation       animation from which extract bubble animation | ||||||
|  |  * @return                  bubble_animation, NULL if failed to cache data. | ||||||
|  |  */ | ||||||
|  | const BubbleAnimation* animation_storage_get_bubble_animation(StorageAnimation* storage_animation); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Performs caching animation data (Bubble Animation) | ||||||
|  |  * if this is not done yet. | ||||||
|  |  * | ||||||
|  |  * @storage_animation       animation to cache | ||||||
|  |  */ | ||||||
|  | void animation_storage_cache_animation(StorageAnimation* storage_animation); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Find animation by name. | ||||||
|  |  * Search through the inner flash, and SD-card if has. | ||||||
|  |  * | ||||||
|  |  * @name        name of animation | ||||||
|  |  * @return      found animation. NULL if nothing found. | ||||||
|  |  */ | ||||||
|  | StorageAnimation* animation_storage_find_animation(const char* name); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Get meta information of storage animation. | ||||||
|  |  * This information allows to randomly select animation. | ||||||
|  |  * Also it contains name. Never returns NULL. | ||||||
|  |  * | ||||||
|  |  * @storage_animation       item of whom we have to extract meta. | ||||||
|  |  * @return                  meta itself | ||||||
|  |  */ | ||||||
|  | StorageAnimationMeta* animation_storage_get_meta(StorageAnimation* storage_animation); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Free storage_animation, which previously acquired | ||||||
|  |  * by Animation Storage. | ||||||
|  |  * | ||||||
|  |  * @storage_animation   item to free. NULL-ed after all. | ||||||
|  |  */ | ||||||
|  | void animation_storage_free_storage_animation(StorageAnimation** storage_animation); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Has to be called at least 1 time to initialize runtime structures | ||||||
|  |  * of animations in inner flash. | ||||||
|  |  */ | ||||||
|  | void animation_storage_initialize_internal_animations(void); | ||||||
							
								
								
									
										195
									
								
								applications/desktop/animations/animation_storage_i.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										195
									
								
								applications/desktop/animations/animation_storage_i.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,195 @@ | |||||||
|  | #pragma once | ||||||
|  | #include "animation_storage.h" | ||||||
|  | #include "assets_icons.h" | ||||||
|  | #include "animation_manager.h" | ||||||
|  | #include "gui/canvas.h" | ||||||
|  | 
 | ||||||
|  | struct StorageAnimation { | ||||||
|  |     const BubbleAnimation* animation; | ||||||
|  |     bool external; | ||||||
|  |     StorageAnimationMeta meta; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | // Hard-coded, always available idle animation
 | ||||||
|  | FrameBubble tv_bubble1 = { | ||||||
|  |     .bubble = | ||||||
|  |         {.x = 1, | ||||||
|  |          .y = 23, | ||||||
|  |          .str = "Take the red pill", | ||||||
|  |          .horizontal = AlignRight, | ||||||
|  |          .vertical = AlignBottom}, | ||||||
|  |     .starts_at_frame = 7, | ||||||
|  |     .ends_at_frame = 9, | ||||||
|  |     .next_bubble = NULL, | ||||||
|  | }; | ||||||
|  | FrameBubble tv_bubble2 = { | ||||||
|  |     .bubble = | ||||||
|  |         {.x = 1, | ||||||
|  |          .y = 23, | ||||||
|  |          .str = "I can joke better", | ||||||
|  |          .horizontal = AlignRight, | ||||||
|  |          .vertical = AlignBottom}, | ||||||
|  |     .starts_at_frame = 7, | ||||||
|  |     .ends_at_frame = 9, | ||||||
|  |     .next_bubble = NULL, | ||||||
|  | }; | ||||||
|  | FrameBubble* tv_bubbles[] = {&tv_bubble1, &tv_bubble2}; | ||||||
|  | const Icon* tv_icons[] = { | ||||||
|  |     &I_tv1, | ||||||
|  |     &I_tv2, | ||||||
|  |     &I_tv3, | ||||||
|  |     &I_tv4, | ||||||
|  |     &I_tv5, | ||||||
|  |     &I_tv6, | ||||||
|  |     &I_tv7, | ||||||
|  |     &I_tv8, | ||||||
|  | }; | ||||||
|  | const BubbleAnimation tv_bubble_animation = { | ||||||
|  |     .icons = tv_icons, | ||||||
|  |     .frame_bubbles = tv_bubbles, | ||||||
|  |     .frame_bubbles_count = COUNT_OF(tv_bubbles), | ||||||
|  |     .passive_frames = 6, | ||||||
|  |     .active_frames = 2, | ||||||
|  |     .active_cycles = 2, | ||||||
|  |     .frame_rate = 2, | ||||||
|  |     .duration = 3600, | ||||||
|  |     .active_cooldown = 5, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | // System animation - no SD card
 | ||||||
|  | const Icon* no_sd_icons[] = { | ||||||
|  |     &I_no_sd1, | ||||||
|  |     &I_no_sd2, | ||||||
|  |     &I_no_sd1, | ||||||
|  |     &I_no_sd2, | ||||||
|  |     &I_no_sd1, | ||||||
|  |     &I_no_sd3, | ||||||
|  |     &I_no_sd4, | ||||||
|  |     &I_no_sd5, | ||||||
|  |     &I_no_sd4, | ||||||
|  |     &I_no_sd6, | ||||||
|  | }; | ||||||
|  | FrameBubble no_sd_bubble = { | ||||||
|  |     .bubble = | ||||||
|  |         {.x = 40, | ||||||
|  |          .y = 18, | ||||||
|  |          .str = "Need an\nSD card", | ||||||
|  |          .horizontal = AlignRight, | ||||||
|  |          .vertical = AlignBottom}, | ||||||
|  |     .starts_at_frame = 0, | ||||||
|  |     .ends_at_frame = 9, | ||||||
|  |     .next_bubble = NULL, | ||||||
|  | }; | ||||||
|  | FrameBubble* no_sd_bubbles[] = {&no_sd_bubble}; | ||||||
|  | const BubbleAnimation no_sd_bubble_animation = { | ||||||
|  |     .icons = no_sd_icons, | ||||||
|  |     .frame_bubbles = no_sd_bubbles, | ||||||
|  |     .frame_bubbles_count = COUNT_OF(no_sd_bubbles), | ||||||
|  |     .passive_frames = 10, | ||||||
|  |     .active_frames = 0, | ||||||
|  |     .frame_rate = 2, | ||||||
|  |     .duration = 3600, | ||||||
|  |     .active_cooldown = 0, | ||||||
|  |     .active_cycles = 0, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | // BLOCKING ANIMATION - no_db, bad_sd, sd_ok, url
 | ||||||
|  | const Icon* no_db_icons[] = { | ||||||
|  |     &I_no_databases1, | ||||||
|  |     &I_no_databases2, | ||||||
|  |     &I_no_databases3, | ||||||
|  |     &I_no_databases4, | ||||||
|  | }; | ||||||
|  | const BubbleAnimation no_db_bubble_animation = { | ||||||
|  |     .icons = no_db_icons, | ||||||
|  |     .passive_frames = COUNT_OF(no_db_icons), | ||||||
|  |     .frame_rate = 2, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const Icon* bad_sd_icons[] = { | ||||||
|  |     &I_card_bad1, | ||||||
|  |     &I_card_bad2, | ||||||
|  | }; | ||||||
|  | const BubbleAnimation bad_sd_bubble_animation = { | ||||||
|  |     .icons = bad_sd_icons, | ||||||
|  |     .passive_frames = COUNT_OF(bad_sd_icons), | ||||||
|  |     .frame_rate = 2, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const Icon* url_icons[] = { | ||||||
|  |     &I_url1, | ||||||
|  |     &I_url2, | ||||||
|  |     &I_url3, | ||||||
|  |     &I_url4, | ||||||
|  | }; | ||||||
|  | const BubbleAnimation url_bubble_animation = { | ||||||
|  |     .icons = url_icons, | ||||||
|  |     .passive_frames = COUNT_OF(url_icons), | ||||||
|  |     .frame_rate = 2, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const Icon* sd_ok_icons[] = { | ||||||
|  |     &I_card_ok1, | ||||||
|  |     &I_card_ok2, | ||||||
|  |     &I_card_ok3, | ||||||
|  |     &I_card_ok4, | ||||||
|  | }; | ||||||
|  | const BubbleAnimation sd_ok_bubble_animation = { | ||||||
|  |     .icons = sd_ok_icons, | ||||||
|  |     .passive_frames = COUNT_OF(sd_ok_icons), | ||||||
|  |     .frame_rate = 2, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static StorageAnimation StorageAnimationInternal[] = { | ||||||
|  |     {.animation = &tv_bubble_animation, | ||||||
|  |      .external = false, | ||||||
|  |      .meta = | ||||||
|  |          { | ||||||
|  |              .min_butthurt = 0, | ||||||
|  |              .max_butthurt = 11, | ||||||
|  |              .min_level = 1, | ||||||
|  |              .max_level = 3, | ||||||
|  |              .weight = 3, | ||||||
|  |          }}, | ||||||
|  |     {.animation = &no_sd_bubble_animation, | ||||||
|  |      .external = false, | ||||||
|  |      .meta = | ||||||
|  |          { | ||||||
|  |              .min_butthurt = 0, | ||||||
|  |              .max_butthurt = 14, | ||||||
|  |              .min_level = 1, | ||||||
|  |              .max_level = 3, | ||||||
|  |              .weight = 6, | ||||||
|  |          }}, | ||||||
|  |     { | ||||||
|  |         .animation = &no_db_bubble_animation, | ||||||
|  |         .external = false, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         .animation = &bad_sd_bubble_animation, | ||||||
|  |         .external = false, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         .animation = &sd_ok_bubble_animation, | ||||||
|  |         .external = false, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         .animation = &url_bubble_animation, | ||||||
|  |         .external = false, | ||||||
|  |     }, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | void animation_storage_initialize_internal_animations(void) { | ||||||
|  |     /* not in constructor - no memory pool yet */ | ||||||
|  |     /* called in 1 thread - no need in double check */ | ||||||
|  |     static bool initialized = false; | ||||||
|  |     if(!initialized) { | ||||||
|  |         initialized = true; | ||||||
|  |         string_init_set_str(StorageAnimationInternal[0].meta.name, HARDCODED_ANIMATION_NAME); | ||||||
|  |         string_init_set_str(StorageAnimationInternal[1].meta.name, NO_SD_ANIMATION_NAME); | ||||||
|  |         string_init_set_str(StorageAnimationInternal[2].meta.name, NO_DB_ANIMATION_NAME); | ||||||
|  |         string_init_set_str(StorageAnimationInternal[3].meta.name, BAD_SD_ANIMATION_NAME); | ||||||
|  |         string_init_set_str(StorageAnimationInternal[4].meta.name, SD_OK_ANIMATION_NAME); | ||||||
|  |         string_init_set_str(StorageAnimationInternal[5].meta.name, URL_ANIMATION_NAME); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										410
									
								
								applications/desktop/animations/views/bubble_animation_view.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										410
									
								
								applications/desktop/animations/views/bubble_animation_view.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,410 @@ | |||||||
|  | 
 | ||||||
|  | #include "cmsis_os2.h" | ||||||
|  | #include "../animation_manager.h" | ||||||
|  | #include "../animation_storage.h" | ||||||
|  | #include "furi_hal_delay.h" | ||||||
|  | #include "furi_hal_resources.h" | ||||||
|  | #include "furi/check.h" | ||||||
|  | #include "furi/memmgr.h" | ||||||
|  | #include "gui/canvas.h" | ||||||
|  | #include "gui/elements.h" | ||||||
|  | #include "gui/view.h" | ||||||
|  | #include "input/input.h" | ||||||
|  | #include <furi.h> | ||||||
|  | #include "portmacro.h" | ||||||
|  | #include <gui/icon.h> | ||||||
|  | #include <stdint.h> | ||||||
|  | #include <FreeRTOS.h> | ||||||
|  | #include <timers.h> | ||||||
|  | #include "bubble_animation_view.h" | ||||||
|  | #include <gui/icon_i.h> | ||||||
|  | 
 | ||||||
|  | #define ACTIVE_SHIFT 2 | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     const BubbleAnimation* current; | ||||||
|  |     const FrameBubble* current_bubble; | ||||||
|  |     uint8_t current_frame; | ||||||
|  |     uint8_t active_cycle; | ||||||
|  |     uint8_t active_bubbles; | ||||||
|  |     uint8_t passive_bubbles; | ||||||
|  |     uint8_t active_shift; | ||||||
|  |     TickType_t active_ended_at; | ||||||
|  |     Icon* freeze_frame; | ||||||
|  | } BubbleAnimationViewModel; | ||||||
|  | 
 | ||||||
|  | struct BubbleAnimationView { | ||||||
|  |     View* view; | ||||||
|  |     osTimerId_t timer; | ||||||
|  |     BubbleAnimationInteractCallback interact_callback; | ||||||
|  |     void* interact_callback_context; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static void bubble_animation_activate(BubbleAnimationView* view, bool force); | ||||||
|  | static void bubble_animation_activate_right_now(BubbleAnimationView* view); | ||||||
|  | 
 | ||||||
|  | static uint8_t bubble_animation_get_icon_index(BubbleAnimationViewModel* model) { | ||||||
|  |     furi_assert(model); | ||||||
|  |     uint8_t icon_index = 0; | ||||||
|  |     const BubbleAnimation* animation = model->current; | ||||||
|  | 
 | ||||||
|  |     if(model->current_frame < animation->passive_frames) { | ||||||
|  |         icon_index = model->current_frame; | ||||||
|  |     } else { | ||||||
|  |         icon_index = | ||||||
|  |             (model->current_frame - animation->passive_frames) % animation->active_frames + | ||||||
|  |             animation->passive_frames; | ||||||
|  |     } | ||||||
|  |     furi_assert(icon_index < (animation->passive_frames + animation->active_frames)); | ||||||
|  | 
 | ||||||
|  |     return icon_index; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void bubble_animation_draw_callback(Canvas* canvas, void* model_) { | ||||||
|  |     furi_assert(model_); | ||||||
|  |     furi_assert(canvas); | ||||||
|  | 
 | ||||||
|  |     BubbleAnimationViewModel* model = model_; | ||||||
|  |     const BubbleAnimation* animation = model->current; | ||||||
|  | 
 | ||||||
|  |     if(model->freeze_frame) { | ||||||
|  |         uint8_t y_offset = canvas_height(canvas) - icon_get_height(model->freeze_frame); | ||||||
|  |         canvas_draw_icon(canvas, 0, y_offset, model->freeze_frame); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(!animation) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     furi_assert(model->current_frame < 255); | ||||||
|  | 
 | ||||||
|  |     const Icon* icon = animation->icons[bubble_animation_get_icon_index(model)]; | ||||||
|  |     furi_assert(icon); | ||||||
|  |     uint8_t y_offset = canvas_height(canvas) - icon_get_height(icon); | ||||||
|  |     canvas_draw_icon(canvas, 0, y_offset, icon); | ||||||
|  | 
 | ||||||
|  |     const FrameBubble* bubble = model->current_bubble; | ||||||
|  |     if(bubble) { | ||||||
|  |         if((model->current_frame >= bubble->starts_at_frame) && | ||||||
|  |            (model->current_frame <= bubble->ends_at_frame)) { | ||||||
|  |             const Bubble* b = &bubble->bubble; | ||||||
|  |             elements_bubble_str(canvas, b->x, b->y, b->str, b->horizontal, b->vertical); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static FrameBubble* bubble_animation_pick_bubble(BubbleAnimationViewModel* model, bool active) { | ||||||
|  |     FrameBubble* bubble = NULL; | ||||||
|  | 
 | ||||||
|  |     if((model->active_bubbles == 0) && (model->passive_bubbles == 0)) { | ||||||
|  |         return NULL; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     uint8_t index = random() % (active ? model->active_bubbles : model->passive_bubbles); | ||||||
|  |     const BubbleAnimation* animation = model->current; | ||||||
|  | 
 | ||||||
|  |     for(int i = 0; i < animation->frame_bubbles_count; ++i) { | ||||||
|  |         if((animation->frame_bubbles[i]->starts_at_frame < animation->passive_frames) ^ active) { | ||||||
|  |             if(!index) { | ||||||
|  |                 bubble = animation->frame_bubbles[i]; | ||||||
|  |             } | ||||||
|  |             --index; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return bubble; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool bubble_animation_input_callback(InputEvent* event, void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     furi_assert(event); | ||||||
|  | 
 | ||||||
|  |     BubbleAnimationView* animation_view = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event->type == InputTypePress) { | ||||||
|  |         bubble_animation_activate(animation_view, false); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(event->key == InputKeyRight) { | ||||||
|  |         /* Right button reserved for animation activation, so consume */ | ||||||
|  |         consumed = true; | ||||||
|  |         if(event->type == InputTypeShort) { | ||||||
|  |             if(animation_view->interact_callback) { | ||||||
|  |                 animation_view->interact_callback(animation_view->interact_callback_context); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } else if(event->key == InputKeyBack) { | ||||||
|  |         /* Prevent back button to fall down to common handler - leaving
 | ||||||
|  |          * application, so consume */ | ||||||
|  |         consumed = true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void bubble_animation_activate(BubbleAnimationView* view, bool force) { | ||||||
|  |     furi_assert(view); | ||||||
|  |     bool activate = true; | ||||||
|  |     BubbleAnimationViewModel* model = view_get_model(view->view); | ||||||
|  |     if(!model->current) { | ||||||
|  |         activate = false; | ||||||
|  |     } else if(model->freeze_frame) { | ||||||
|  |         activate = false; | ||||||
|  |     } else if(model->current->active_frames == 0) { | ||||||
|  |         activate = false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(!force) { | ||||||
|  |         if((model->active_ended_at + model->current->active_cooldown * 1000) > | ||||||
|  |            xTaskGetTickCount()) { | ||||||
|  |             activate = false; | ||||||
|  |         } else if(model->active_shift) { | ||||||
|  |             activate = false; | ||||||
|  |         } else if(model->current_frame >= model->current->passive_frames) { | ||||||
|  |             activate = false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     view_commit_model(view->view, false); | ||||||
|  | 
 | ||||||
|  |     if(!activate && !force) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(ACTIVE_SHIFT > 0) { | ||||||
|  |         BubbleAnimationViewModel* model = view_get_model(view->view); | ||||||
|  |         model->active_shift = ACTIVE_SHIFT; | ||||||
|  |         view_commit_model(view->view, false); | ||||||
|  |     } else { | ||||||
|  |         bubble_animation_activate_right_now(view); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void bubble_animation_activate_right_now(BubbleAnimationView* view) { | ||||||
|  |     furi_assert(view); | ||||||
|  | 
 | ||||||
|  |     uint8_t frame_rate = 0; | ||||||
|  | 
 | ||||||
|  |     BubbleAnimationViewModel* model = view_get_model(view->view); | ||||||
|  |     if(model->current && (model->current->active_frames > 0) && (!model->freeze_frame)) { | ||||||
|  |         model->current_frame = model->current->passive_frames; | ||||||
|  |         model->current_bubble = bubble_animation_pick_bubble(model, true); | ||||||
|  |         frame_rate = model->current->frame_rate; | ||||||
|  |     } | ||||||
|  |     view_commit_model(view->view, true); | ||||||
|  | 
 | ||||||
|  |     if(frame_rate) { | ||||||
|  |         osTimerStart(view->timer, 1000 / frame_rate); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void bubble_animation_next_frame(BubbleAnimationViewModel* model) { | ||||||
|  |     furi_assert(model); | ||||||
|  | 
 | ||||||
|  |     if(!model->current) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(model->current_frame < model->current->passive_frames) { | ||||||
|  |         model->current_frame = (model->current_frame + 1) % model->current->passive_frames; | ||||||
|  |     } else { | ||||||
|  |         ++model->current_frame; | ||||||
|  |         model->active_cycle += | ||||||
|  |             !((model->current_frame - model->current->passive_frames) % | ||||||
|  |               model->current->active_frames); | ||||||
|  |         if(model->active_cycle >= model->current->active_cycles) { | ||||||
|  |             // switch to passive
 | ||||||
|  |             model->active_cycle = 0; | ||||||
|  |             model->current_frame = 0; | ||||||
|  |             model->current_bubble = bubble_animation_pick_bubble(model, false); | ||||||
|  |             model->active_ended_at = xTaskGetTickCount(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if(model->current_bubble) { | ||||||
|  |             if(model->current_frame > model->current_bubble->ends_at_frame) { | ||||||
|  |                 model->current_bubble = model->current_bubble->next_bubble; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void bubble_animation_timer_callback(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     BubbleAnimationView* view = context; | ||||||
|  |     bool activate = false; | ||||||
|  | 
 | ||||||
|  |     BubbleAnimationViewModel* model = view_get_model(view->view); | ||||||
|  | 
 | ||||||
|  |     if(model->active_shift > 0) { | ||||||
|  |         activate = (--model->active_shift == 0); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(!model->freeze_frame && !activate) { | ||||||
|  |         bubble_animation_next_frame(model); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     view_commit_model(view->view, !activate); | ||||||
|  | 
 | ||||||
|  |     if(activate) { | ||||||
|  |         bubble_animation_activate_right_now(view); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static Icon* bubble_animation_clone_frame(const Icon* icon_orig) { | ||||||
|  |     furi_assert(icon_orig); | ||||||
|  |     furi_assert(icon_orig->frames); | ||||||
|  |     furi_assert(icon_orig->frames[0]); | ||||||
|  | 
 | ||||||
|  |     Icon* icon_clone = furi_alloc(sizeof(Icon)); | ||||||
|  |     memcpy(icon_clone, icon_orig, sizeof(Icon)); | ||||||
|  | 
 | ||||||
|  |     icon_clone->frames = furi_alloc(sizeof(uint8_t*)); | ||||||
|  |     /* icon bitmap can be either compressed or not. It is compressed if
 | ||||||
|  |      * compressed size is less than original, so max size for bitmap is | ||||||
|  |      * uncompressed (width * height) + 1 byte (in uncompressed case) | ||||||
|  |      * for compressed header | ||||||
|  |      */ | ||||||
|  |     size_t max_bitmap_size = ROUND_UP_TO(icon_orig->width, 8) * icon_orig->height + 1; | ||||||
|  |     icon_clone->frames[0] = furi_alloc(max_bitmap_size); | ||||||
|  |     memcpy((void*)icon_clone->frames[0], icon_orig->frames[0], max_bitmap_size); | ||||||
|  | 
 | ||||||
|  |     return icon_clone; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void bubble_animation_release_frame(Icon** icon) { | ||||||
|  |     furi_assert(icon); | ||||||
|  |     furi_assert(*icon); | ||||||
|  | 
 | ||||||
|  |     free((void*)(*icon)->frames[0]); | ||||||
|  |     free((*icon)->frames); | ||||||
|  |     free(*icon); | ||||||
|  |     *icon = NULL; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void bubble_animation_enter(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     BubbleAnimationView* view = context; | ||||||
|  |     bubble_animation_activate(view, false); | ||||||
|  | 
 | ||||||
|  |     BubbleAnimationViewModel* model = view_get_model(view->view); | ||||||
|  |     uint8_t frame_rate = model->current->frame_rate; | ||||||
|  |     view_commit_model(view->view, false); | ||||||
|  | 
 | ||||||
|  |     if(frame_rate) { | ||||||
|  |         osTimerStart(view->timer, 1000 / frame_rate); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void bubble_animation_exit(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     BubbleAnimationView* view = context; | ||||||
|  |     osTimerStop(view->timer); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | BubbleAnimationView* bubble_animation_view_alloc(void) { | ||||||
|  |     BubbleAnimationView* view = furi_alloc(sizeof(BubbleAnimationView)); | ||||||
|  |     view->view = view_alloc(); | ||||||
|  |     view->interact_callback = NULL; | ||||||
|  |     view->timer = osTimerNew(bubble_animation_timer_callback, osTimerPeriodic, view, NULL); | ||||||
|  | 
 | ||||||
|  |     view_allocate_model(view->view, ViewModelTypeLocking, sizeof(BubbleAnimationViewModel)); | ||||||
|  |     view_set_context(view->view, view); | ||||||
|  |     view_set_draw_callback(view->view, bubble_animation_draw_callback); | ||||||
|  |     view_set_input_callback(view->view, bubble_animation_input_callback); | ||||||
|  |     view_set_enter_callback(view->view, bubble_animation_enter); | ||||||
|  |     view_set_exit_callback(view->view, bubble_animation_exit); | ||||||
|  | 
 | ||||||
|  |     return view; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void bubble_animation_view_free(BubbleAnimationView* view) { | ||||||
|  |     furi_assert(view); | ||||||
|  | 
 | ||||||
|  |     view_set_draw_callback(view->view, NULL); | ||||||
|  |     view_set_input_callback(view->view, NULL); | ||||||
|  |     view_set_context(view->view, NULL); | ||||||
|  | 
 | ||||||
|  |     view_free(view->view); | ||||||
|  |     view->view = NULL; | ||||||
|  |     free(view); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void bubble_animation_view_set_interact_callback( | ||||||
|  |     BubbleAnimationView* view, | ||||||
|  |     BubbleAnimationInteractCallback callback, | ||||||
|  |     void* context) { | ||||||
|  |     furi_assert(view); | ||||||
|  | 
 | ||||||
|  |     view->interact_callback_context = context; | ||||||
|  |     view->interact_callback = callback; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void bubble_animation_view_set_animation( | ||||||
|  |     BubbleAnimationView* view, | ||||||
|  |     const BubbleAnimation* new_animation) { | ||||||
|  |     furi_assert(view); | ||||||
|  |     furi_assert(new_animation); | ||||||
|  | 
 | ||||||
|  |     BubbleAnimationViewModel* model = view_get_model(view->view); | ||||||
|  |     furi_assert(model); | ||||||
|  |     model->current = new_animation; | ||||||
|  | 
 | ||||||
|  |     model->active_ended_at = xTaskGetTickCount() - (model->current->active_cooldown * 1000); | ||||||
|  |     model->active_bubbles = 0; | ||||||
|  |     model->passive_bubbles = 0; | ||||||
|  |     for(int i = 0; i < new_animation->frame_bubbles_count; ++i) { | ||||||
|  |         if(new_animation->frame_bubbles[i]->starts_at_frame < new_animation->passive_frames) { | ||||||
|  |             ++model->passive_bubbles; | ||||||
|  |         } else { | ||||||
|  |             ++model->active_bubbles; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /* select bubble sequence */ | ||||||
|  |     model->current_bubble = bubble_animation_pick_bubble(model, false); | ||||||
|  |     model->current_frame = 0; | ||||||
|  |     model->active_cycle = 0; | ||||||
|  |     view_commit_model(view->view, true); | ||||||
|  | 
 | ||||||
|  |     osTimerStart(view->timer, 1000 / new_animation->frame_rate); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void bubble_animation_freeze(BubbleAnimationView* view) { | ||||||
|  |     furi_assert(view); | ||||||
|  | 
 | ||||||
|  |     BubbleAnimationViewModel* model = view_get_model(view->view); | ||||||
|  |     furi_assert(model->current); | ||||||
|  |     furi_assert(!model->freeze_frame); | ||||||
|  |     /* always freeze first passive frame, because
 | ||||||
|  |      * animation is always activated at unfreezing and played | ||||||
|  |      * passive frame first, and 2 frames after - active | ||||||
|  |      */ | ||||||
|  |     uint8_t icon_index = 0; | ||||||
|  |     model->freeze_frame = bubble_animation_clone_frame(model->current->icons[icon_index]); | ||||||
|  |     model->current = NULL; | ||||||
|  |     view_commit_model(view->view, false); | ||||||
|  |     osTimerStop(view->timer); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void bubble_animation_unfreeze(BubbleAnimationView* view) { | ||||||
|  |     furi_assert(view); | ||||||
|  |     uint8_t frame_rate; | ||||||
|  | 
 | ||||||
|  |     BubbleAnimationViewModel* model = view_get_model(view->view); | ||||||
|  |     furi_assert(model->freeze_frame); | ||||||
|  |     bubble_animation_release_frame(&model->freeze_frame); | ||||||
|  |     furi_assert(model->current); | ||||||
|  |     furi_assert(model->current->icons); | ||||||
|  |     frame_rate = model->current->frame_rate; | ||||||
|  |     view_commit_model(view->view, true); | ||||||
|  | 
 | ||||||
|  |     osTimerStart(view->timer, 1000 / frame_rate); | ||||||
|  |     bubble_animation_activate(view, false); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | View* bubble_animation_get_view(BubbleAnimationView* view) { | ||||||
|  |     furi_assert(view); | ||||||
|  | 
 | ||||||
|  |     return view->view; | ||||||
|  | } | ||||||
| @ -0,0 +1,89 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <gui/view.h> | ||||||
|  | #include "../animation_manager.h" | ||||||
|  | 
 | ||||||
|  | /** Bubble Animation instance */ | ||||||
|  | typedef struct BubbleAnimationView BubbleAnimationView; | ||||||
|  | 
 | ||||||
|  | /** Callback type to be called when interact button pressed */ | ||||||
|  | typedef void (*BubbleAnimationInteractCallback)(void*); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Allocate bubble animation view. | ||||||
|  |  * This is animation with bubbles, and 2 phases: | ||||||
|  |  * active and passive. | ||||||
|  |  * | ||||||
|  |  * @return instance of new bubble animation | ||||||
|  |  */ | ||||||
|  | BubbleAnimationView* bubble_animation_view_alloc(void); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Free bubble animation view. | ||||||
|  |  * | ||||||
|  |  * @view        bubble animation view instance | ||||||
|  |  */ | ||||||
|  | void bubble_animation_view_free(BubbleAnimationView* view); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Set callback for interact action for animation. | ||||||
|  |  * Currently this is right button. | ||||||
|  |  * | ||||||
|  |  * @view        bubble animation view instance | ||||||
|  |  * @callback    callback to call when button pressed | ||||||
|  |  * @context     context | ||||||
|  |  */ | ||||||
|  | void bubble_animation_view_set_interact_callback( | ||||||
|  |     BubbleAnimationView* view, | ||||||
|  |     BubbleAnimationInteractCallback callback, | ||||||
|  |     void* context); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Set new animation. | ||||||
|  |  * BubbleAnimation doesn't posses Bubble Animation object | ||||||
|  |  * so it doesn't handle any memory manipulation on Bubble Animations. | ||||||
|  |  * | ||||||
|  |  * @view                    bubble animation view instance | ||||||
|  |  * @new_bubble_animation    new animation to set | ||||||
|  |  */ | ||||||
|  | void bubble_animation_view_set_animation( | ||||||
|  |     BubbleAnimationView* view, | ||||||
|  |     const BubbleAnimation* new_bubble_animation); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Get view of bubble animation. | ||||||
|  |  * | ||||||
|  |  * @view        bubble animation view instance | ||||||
|  |  * @return      view | ||||||
|  |  */ | ||||||
|  | View* bubble_animation_get_view(BubbleAnimationView* view); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Freeze current playing animation. Saves a frame to be shown | ||||||
|  |  * during next unfreeze called. | ||||||
|  |  * bubble_animation_freeze() stops any reference to 'current' animation | ||||||
|  |  * so it can be freed. Therefore lock unfreeze should be preceeded with | ||||||
|  |  * new animation set. | ||||||
|  |  * | ||||||
|  |  * Freeze/Unfreeze usage example: | ||||||
|  |  * | ||||||
|  |  *  animation_view_alloc() | ||||||
|  |  *  set_animation() | ||||||
|  |  *  ... | ||||||
|  |  *  freeze_animation() | ||||||
|  |  *   // release animation
 | ||||||
|  |  *  ... | ||||||
|  |  *   // allocate animation
 | ||||||
|  |  *  set_animation() | ||||||
|  |  *  unfreeze() | ||||||
|  |  * | ||||||
|  |  * @view        bubble animation view instance | ||||||
|  |  */ | ||||||
|  | void bubble_animation_freeze(BubbleAnimationView* view); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Starts bubble animation after freezing. | ||||||
|  |  * | ||||||
|  |  * @view        bubble animation view instance | ||||||
|  |  */ | ||||||
|  | void bubble_animation_unfreeze(BubbleAnimationView* view); | ||||||
| @ -2,15 +2,16 @@ | |||||||
| #include "cmsis_os2.h" | #include "cmsis_os2.h" | ||||||
| #include "desktop/desktop.h" | #include "desktop/desktop.h" | ||||||
| #include "desktop_i.h" | #include "desktop_i.h" | ||||||
|  | #include "gui/view_composed.h" | ||||||
| #include <dolphin/dolphin.h> | #include <dolphin/dolphin.h> | ||||||
| #include <furi/pubsub.h> | #include <furi/pubsub.h> | ||||||
| #include <furi/record.h> | #include <furi/record.h> | ||||||
| #include "portmacro.h" | #include "portmacro.h" | ||||||
| #include "storage/filesystem-api-defines.h" | #include "storage/filesystem_api_defines.h" | ||||||
| #include "storage/storage.h" | #include "storage/storage.h" | ||||||
| #include <stdint.h> | #include <stdint.h> | ||||||
| #include <power/power_service/power.h> | #include <power/power_service/power.h> | ||||||
| #include "helpers/desktop_animation.h" | #include "animations/animation_manager.h" | ||||||
| 
 | 
 | ||||||
| static void desktop_lock_icon_callback(Canvas* canvas, void* context) { | static void desktop_lock_icon_callback(Canvas* canvas, void* context) { | ||||||
|     furi_assert(canvas); |     furi_assert(canvas); | ||||||
| @ -32,11 +33,12 @@ bool desktop_back_event_callback(void* context) { | |||||||
| Desktop* desktop_alloc() { | Desktop* desktop_alloc() { | ||||||
|     Desktop* desktop = furi_alloc(sizeof(Desktop)); |     Desktop* desktop = furi_alloc(sizeof(Desktop)); | ||||||
| 
 | 
 | ||||||
|  |     desktop->unload_animation_semaphore = osSemaphoreNew(1, 0, NULL); | ||||||
|  |     desktop->animation_manager = animation_manager_alloc(); | ||||||
|     desktop->gui = furi_record_open("gui"); |     desktop->gui = furi_record_open("gui"); | ||||||
|     desktop->scene_thread = furi_thread_alloc(); |     desktop->scene_thread = furi_thread_alloc(); | ||||||
|     desktop->view_dispatcher = view_dispatcher_alloc(); |     desktop->view_dispatcher = view_dispatcher_alloc(); | ||||||
|     desktop->scene_manager = scene_manager_alloc(&desktop_scene_handlers, desktop); |     desktop->scene_manager = scene_manager_alloc(&desktop_scene_handlers, desktop); | ||||||
|     desktop->animation = desktop_animation_alloc(); |  | ||||||
| 
 | 
 | ||||||
|     view_dispatcher_enable_queue(desktop->view_dispatcher); |     view_dispatcher_enable_queue(desktop->view_dispatcher); | ||||||
|     view_dispatcher_attach_to_gui( |     view_dispatcher_attach_to_gui( | ||||||
| @ -48,16 +50,34 @@ Desktop* desktop_alloc() { | |||||||
|     view_dispatcher_set_navigation_event_callback( |     view_dispatcher_set_navigation_event_callback( | ||||||
|         desktop->view_dispatcher, desktop_back_event_callback); |         desktop->view_dispatcher, desktop_back_event_callback); | ||||||
| 
 | 
 | ||||||
|  |     desktop->dolphin_view = animation_manager_get_animation_view(desktop->animation_manager); | ||||||
|  | 
 | ||||||
|  |     desktop->main_view_composed = view_composed_alloc(); | ||||||
|     desktop->main_view = desktop_main_alloc(); |     desktop->main_view = desktop_main_alloc(); | ||||||
|     desktop->lock_menu = desktop_lock_menu_alloc(); |     view_composed_tie_views( | ||||||
|  |         desktop->main_view_composed, | ||||||
|  |         desktop->dolphin_view, | ||||||
|  |         desktop_main_get_view(desktop->main_view)); | ||||||
|  |     view_composed_top_enable(desktop->main_view_composed, true); | ||||||
|  | 
 | ||||||
|  |     desktop->locked_view_composed = view_composed_alloc(); | ||||||
|     desktop->locked_view = desktop_locked_alloc(); |     desktop->locked_view = desktop_locked_alloc(); | ||||||
|  |     view_composed_tie_views( | ||||||
|  |         desktop->locked_view_composed, | ||||||
|  |         desktop->dolphin_view, | ||||||
|  |         desktop_locked_get_view(desktop->locked_view)); | ||||||
|  |     view_composed_top_enable(desktop->locked_view_composed, true); | ||||||
|  | 
 | ||||||
|  |     desktop->lock_menu = desktop_lock_menu_alloc(); | ||||||
|     desktop->debug_view = desktop_debug_alloc(); |     desktop->debug_view = desktop_debug_alloc(); | ||||||
|     desktop->first_start_view = desktop_first_start_alloc(); |     desktop->first_start_view = desktop_first_start_alloc(); | ||||||
|     desktop->hw_mismatch_popup = popup_alloc(); |     desktop->hw_mismatch_popup = popup_alloc(); | ||||||
|     desktop->code_input = code_input_alloc(); |     desktop->code_input = code_input_alloc(); | ||||||
| 
 | 
 | ||||||
|     view_dispatcher_add_view( |     view_dispatcher_add_view( | ||||||
|         desktop->view_dispatcher, DesktopViewMain, desktop_main_get_view(desktop->main_view)); |         desktop->view_dispatcher, | ||||||
|  |         DesktopViewMain, | ||||||
|  |         view_composed_get_view(desktop->main_view_composed)); | ||||||
|     view_dispatcher_add_view( |     view_dispatcher_add_view( | ||||||
|         desktop->view_dispatcher, |         desktop->view_dispatcher, | ||||||
|         DesktopViewLockMenu, |         DesktopViewLockMenu, | ||||||
| @ -67,7 +87,7 @@ Desktop* desktop_alloc() { | |||||||
|     view_dispatcher_add_view( |     view_dispatcher_add_view( | ||||||
|         desktop->view_dispatcher, |         desktop->view_dispatcher, | ||||||
|         DesktopViewLocked, |         DesktopViewLocked, | ||||||
|         desktop_locked_get_view(desktop->locked_view)); |         view_composed_get_view(desktop->locked_view_composed)); | ||||||
|     view_dispatcher_add_view( |     view_dispatcher_add_view( | ||||||
|         desktop->view_dispatcher, |         desktop->view_dispatcher, | ||||||
|         DesktopViewFirstStart, |         DesktopViewFirstStart, | ||||||
| @ -91,7 +111,6 @@ Desktop* desktop_alloc() { | |||||||
| void desktop_free(Desktop* desktop) { | void desktop_free(Desktop* desktop) { | ||||||
|     furi_assert(desktop); |     furi_assert(desktop); | ||||||
| 
 | 
 | ||||||
|     desktop_animation_free(desktop->animation); |  | ||||||
|     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewMain); |     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewMain); | ||||||
|     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewLockMenu); |     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewLockMenu); | ||||||
|     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewLocked); |     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewLocked); | ||||||
| @ -103,6 +122,9 @@ void desktop_free(Desktop* desktop) { | |||||||
|     view_dispatcher_free(desktop->view_dispatcher); |     view_dispatcher_free(desktop->view_dispatcher); | ||||||
|     scene_manager_free(desktop->scene_manager); |     scene_manager_free(desktop->scene_manager); | ||||||
| 
 | 
 | ||||||
|  |     animation_manager_free(desktop->animation_manager); | ||||||
|  |     view_composed_free(desktop->main_view_composed); | ||||||
|  |     view_composed_free(desktop->locked_view_composed); | ||||||
|     desktop_main_free(desktop->main_view); |     desktop_main_free(desktop->main_view); | ||||||
|     desktop_lock_menu_free(desktop->lock_menu); |     desktop_lock_menu_free(desktop->lock_menu); | ||||||
|     desktop_locked_free(desktop->locked_view); |     desktop_locked_free(desktop->locked_view); | ||||||
| @ -111,6 +133,8 @@ void desktop_free(Desktop* desktop) { | |||||||
|     popup_free(desktop->hw_mismatch_popup); |     popup_free(desktop->hw_mismatch_popup); | ||||||
|     code_input_free(desktop->code_input); |     code_input_free(desktop->code_input); | ||||||
| 
 | 
 | ||||||
|  |     osSemaphoreDelete(desktop->unload_animation_semaphore); | ||||||
|  | 
 | ||||||
|     furi_record_close("gui"); |     furi_record_close("gui"); | ||||||
|     desktop->gui = NULL; |     desktop->gui = NULL; | ||||||
| 
 | 
 | ||||||
| @ -129,29 +153,9 @@ static bool desktop_is_first_start() { | |||||||
|     return exists; |     return exists; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void desktop_dolphin_state_changed_callback(const void* message, void* context) { |  | ||||||
|     Desktop* desktop = context; |  | ||||||
|     view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopMainEventUpdateAnimation); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void desktop_storage_state_changed_callback(const void* message, void* context) { |  | ||||||
|     Desktop* desktop = context; |  | ||||||
|     view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopMainEventUpdateAnimation); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| int32_t desktop_srv(void* p) { | int32_t desktop_srv(void* p) { | ||||||
|     Desktop* desktop = desktop_alloc(); |     Desktop* desktop = desktop_alloc(); | ||||||
| 
 | 
 | ||||||
|     Dolphin* dolphin = furi_record_open("dolphin"); |  | ||||||
|     FuriPubSub* dolphin_pubsub = dolphin_get_pubsub(dolphin); |  | ||||||
|     FuriPubSubSubscription* dolphin_subscription = |  | ||||||
|         furi_pubsub_subscribe(dolphin_pubsub, desktop_dolphin_state_changed_callback, desktop); |  | ||||||
| 
 |  | ||||||
|     Storage* storage = furi_record_open("storage"); |  | ||||||
|     FuriPubSub* storage_pubsub = storage_get_pubsub(storage); |  | ||||||
|     FuriPubSubSubscription* storage_subscription = |  | ||||||
|         furi_pubsub_subscribe(storage_pubsub, desktop_storage_state_changed_callback, desktop); |  | ||||||
| 
 |  | ||||||
|     bool loaded = LOAD_DESKTOP_SETTINGS(&desktop->settings); |     bool loaded = LOAD_DESKTOP_SETTINGS(&desktop->settings); | ||||||
|     if(!loaded) { |     if(!loaded) { | ||||||
|         furi_hal_rtc_reset_flag(FuriHalRtcFlagLock); |         furi_hal_rtc_reset_flag(FuriHalRtcFlagLock); | ||||||
| @ -176,9 +180,11 @@ int32_t desktop_srv(void* p) { | |||||||
|         scene_manager_next_scene(desktop->scene_manager, DesktopSceneHwMismatch); |         scene_manager_next_scene(desktop->scene_manager, DesktopSceneHwMismatch); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     if(furi_hal_rtc_get_fault_data()) { | ||||||
|  |         scene_manager_next_scene(desktop->scene_manager, DesktopSceneFault); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     view_dispatcher_run(desktop->view_dispatcher); |     view_dispatcher_run(desktop->view_dispatcher); | ||||||
|     furi_pubsub_unsubscribe(dolphin_pubsub, dolphin_subscription); |  | ||||||
|     furi_pubsub_unsubscribe(storage_pubsub, storage_subscription); |  | ||||||
|     desktop_free(desktop); |     desktop_free(desktop); | ||||||
| 
 | 
 | ||||||
|     return 0; |     return 0; | ||||||
|  | |||||||
| @ -1,9 +1,12 @@ | |||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
|  | #include "cmsis_os2.h" | ||||||
| #include "desktop.h" | #include "desktop.h" | ||||||
| 
 | 
 | ||||||
|  | #include "animations/animation_manager.h" | ||||||
|  | #include "gui/view_composed.h" | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <furi-hal.h> | #include <furi_hal.h> | ||||||
| 
 | 
 | ||||||
| #include <gui/gui.h> | #include <gui/gui.h> | ||||||
| #include <gui/view_dispatcher.h> | #include <gui/view_dispatcher.h> | ||||||
| @ -21,7 +24,6 @@ | |||||||
| #include "views/desktop_debug.h" | #include "views/desktop_debug.h" | ||||||
| 
 | 
 | ||||||
| #include "scenes/desktop_scene.h" | #include "scenes/desktop_scene.h" | ||||||
| #include "helpers/desktop_animation.h" |  | ||||||
| #include "desktop/desktop_settings/desktop_settings.h" | #include "desktop/desktop_settings/desktop_settings.h" | ||||||
| #include <gui/icon.h> | #include <gui/icon.h> | ||||||
| 
 | 
 | ||||||
| @ -46,19 +48,29 @@ struct Desktop { | |||||||
|     ViewDispatcher* view_dispatcher; |     ViewDispatcher* view_dispatcher; | ||||||
|     SceneManager* scene_manager; |     SceneManager* scene_manager; | ||||||
| 
 | 
 | ||||||
|     DesktopAnimation* animation; |  | ||||||
|     DesktopFirstStartView* first_start_view; |     DesktopFirstStartView* first_start_view; | ||||||
|     Popup* hw_mismatch_popup; |     Popup* hw_mismatch_popup; | ||||||
|     DesktopMainView* main_view; |  | ||||||
|     DesktopLockMenuView* lock_menu; |     DesktopLockMenuView* lock_menu; | ||||||
|     DesktopLockedView* locked_view; |  | ||||||
|     DesktopDebugView* debug_view; |     DesktopDebugView* debug_view; | ||||||
|     CodeInput* code_input; |     CodeInput* code_input; | ||||||
| 
 | 
 | ||||||
|  |     View* dolphin_view; | ||||||
|  |     DesktopMainView* main_view; | ||||||
|  |     DesktopLockedView* locked_view; | ||||||
|  | 
 | ||||||
|  |     ViewComposed* main_view_composed; | ||||||
|  |     ViewComposed* locked_view_composed; | ||||||
|  | 
 | ||||||
|     DesktopSettings settings; |     DesktopSettings settings; | ||||||
|     PinCode pincode_buffer; |     PinCode pincode_buffer; | ||||||
| 
 | 
 | ||||||
|     ViewPort* lock_viewport; |     ViewPort* lock_viewport; | ||||||
|  | 
 | ||||||
|  |     AnimationManager* animation_manager; | ||||||
|  |     osSemaphoreId_t unload_animation_semaphore; | ||||||
|  |     FuriPubSubSubscription* app_start_stop_subscription; | ||||||
|  | 
 | ||||||
|  |     char* text_buffer; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| Desktop* desktop_alloc(); | Desktop* desktop_alloc(); | ||||||
|  | |||||||
| @ -1,382 +0,0 @@ | |||||||
| #include "desktop/helpers/desktop_animation.h" |  | ||||||
| #include "assets_icons.h" |  | ||||||
| #include "desktop_animation_i.h" |  | ||||||
| #include "cmsis_os2.h" |  | ||||||
| #include "furi/common_defines.h" |  | ||||||
| #include "furi/record.h" |  | ||||||
| #include "storage/filesystem-api-defines.h" |  | ||||||
| #include <power/power_service/power.h> |  | ||||||
| #include <m-list.h> |  | ||||||
| #include <storage/storage.h> |  | ||||||
| #include <desktop/desktop.h> |  | ||||||
| #include <dolphin/dolphin.h> |  | ||||||
| 
 |  | ||||||
| #define KEEP_ONLY_CALM_BASIC_ANIMATIONS 1 |  | ||||||
| 
 |  | ||||||
| LIST_DEF(AnimationList, const PairedAnimation*, M_PTR_OPLIST) |  | ||||||
| #define M_OPL_AnimationList_t() LIST_OPLIST(AnimationList) |  | ||||||
| 
 |  | ||||||
| #define PUSH_BACK_ANIMATIONS(listname, animations, butthurt)                          \ |  | ||||||
|     for(int i = 0; i < COUNT_OF(animations); ++i) {                                   \ |  | ||||||
|         if(!(animations)[i].basic->butthurt_level_mask ||                             \ |  | ||||||
|            ((animations)[i].basic->butthurt_level_mask & BUTTHURT_LEVEL(butthurt))) { \ |  | ||||||
|             AnimationList_push_back(animation_list, &(animations)[i]);                \ |  | ||||||
|         }                                                                             \ |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| #define IS_BLOCKING_ANIMATION(x) \ |  | ||||||
|     (((x) != DesktopAnimationStateBasic) && ((x) != DesktopAnimationStateActive)) |  | ||||||
| #define IS_ONESHOT_ANIMATION(x) ((x) == DesktopAnimationStateLevelUpIsPending) |  | ||||||
| 
 |  | ||||||
| static void desktop_animation_timer_callback(void* context); |  | ||||||
| 
 |  | ||||||
| struct DesktopAnimation { |  | ||||||
|     bool sd_shown_error_db; |  | ||||||
|     bool sd_shown_error_card_bad; |  | ||||||
|     osTimerId_t timer; |  | ||||||
|     const PairedAnimation* current; |  | ||||||
|     const Icon* current_blocking_icon; |  | ||||||
|     const Icon** current_one_shot_icons; |  | ||||||
|     uint8_t one_shot_animation_counter; |  | ||||||
|     uint8_t one_shot_animation_size; |  | ||||||
|     DesktopAnimationState state; |  | ||||||
|     TickType_t basic_started_at; |  | ||||||
|     TickType_t active_finished_at; |  | ||||||
|     AnimationChangedCallback animation_changed_callback; |  | ||||||
|     void* animation_changed_callback_context; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| DesktopAnimation* desktop_animation_alloc(void) { |  | ||||||
|     DesktopAnimation* animation = furi_alloc(sizeof(DesktopAnimation)); |  | ||||||
| 
 |  | ||||||
|     animation->timer = osTimerNew( |  | ||||||
|         desktop_animation_timer_callback, osTimerPeriodic /* osTimerOnce */, animation, NULL); |  | ||||||
|     animation->active_finished_at = (TickType_t)(-30); |  | ||||||
|     animation->basic_started_at = 0; |  | ||||||
|     animation->animation_changed_callback = NULL; |  | ||||||
|     animation->animation_changed_callback_context = NULL; |  | ||||||
|     desktop_start_new_idle_animation(animation); |  | ||||||
| 
 |  | ||||||
|     return animation; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void desktop_animation_free(DesktopAnimation* animation) { |  | ||||||
|     furi_assert(animation); |  | ||||||
| 
 |  | ||||||
|     osTimerDelete(animation->timer); |  | ||||||
|     free(animation); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void desktop_animation_set_animation_changed_callback( |  | ||||||
|     DesktopAnimation* animation, |  | ||||||
|     AnimationChangedCallback callback, |  | ||||||
|     void* context) { |  | ||||||
|     furi_assert(animation); |  | ||||||
| 
 |  | ||||||
|     animation->animation_changed_callback = callback; |  | ||||||
|     animation->animation_changed_callback_context = context; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void desktop_start_new_idle_animation(DesktopAnimation* animation) { |  | ||||||
|     Dolphin* dolphin = furi_record_open("dolphin"); |  | ||||||
|     DolphinStats stats = dolphin_stats(dolphin); |  | ||||||
|     furi_record_close("dolphin"); |  | ||||||
| 
 |  | ||||||
|     furi_check((stats.level >= 1) && (stats.level <= 3)); |  | ||||||
| 
 |  | ||||||
|     AnimationList_t animation_list; |  | ||||||
|     AnimationList_init(animation_list); |  | ||||||
| 
 |  | ||||||
| #if KEEP_ONLY_CALM_BASIC_ANIMATIONS |  | ||||||
|     PUSH_BACK_ANIMATIONS(animation_list, calm_animation, 0); |  | ||||||
| #else |  | ||||||
|     PUSH_BACK_ANIMATIONS(animation_list, calm_animation, stats.butthurt); |  | ||||||
|     PUSH_BACK_ANIMATIONS(animation_list, mad_animation, stats.butthurt); |  | ||||||
|     switch(stats.level) { |  | ||||||
|     case 1: |  | ||||||
|         PUSH_BACK_ANIMATIONS(animation_list, level_1_animation, stats.butthurt); |  | ||||||
|         break; |  | ||||||
|     case 2: |  | ||||||
|         PUSH_BACK_ANIMATIONS(animation_list, level_2_animation, stats.butthurt); |  | ||||||
|         break; |  | ||||||
|     case 3: |  | ||||||
|         PUSH_BACK_ANIMATIONS(animation_list, level_3_animation, stats.butthurt); |  | ||||||
|         break; |  | ||||||
|     default: |  | ||||||
|         furi_crash("Dolphin level is out of bounds"); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     Power* power = furi_record_open("power"); |  | ||||||
|     PowerInfo info; |  | ||||||
|     power_get_info(power, &info); |  | ||||||
| 
 |  | ||||||
|     if(!power_is_battery_well(&info)) { |  | ||||||
|         PUSH_BACK_ANIMATIONS(animation_list, check_battery_animation, stats.butthurt); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     Storage* storage = furi_record_open("storage"); |  | ||||||
|     FS_Error sd_status = storage_sd_status(storage); |  | ||||||
|     animation->current = NULL; |  | ||||||
| 
 |  | ||||||
|     if(sd_status == FSE_NOT_READY) { |  | ||||||
|         PUSH_BACK_ANIMATIONS(animation_list, no_sd_animation, stats.butthurt); |  | ||||||
|         animation->sd_shown_error_card_bad = false; |  | ||||||
|         animation->sd_shown_error_db = false; |  | ||||||
|     } |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
|     uint32_t whole_weight = 0; |  | ||||||
|     for |  | ||||||
|         M_EACH(item, animation_list, AnimationList_t) { |  | ||||||
|             whole_weight += (*item)->basic->weight; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|     uint32_t lucky_number = random() % whole_weight; |  | ||||||
|     uint32_t weight = 0; |  | ||||||
| 
 |  | ||||||
|     const PairedAnimation* selected = NULL; |  | ||||||
|     for |  | ||||||
|         M_EACH(item, animation_list, AnimationList_t) { |  | ||||||
|             if(lucky_number < weight) { |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|             weight += (*item)->basic->weight; |  | ||||||
|             selected = *item; |  | ||||||
|         } |  | ||||||
|     animation->basic_started_at = osKernelGetTickCount(); |  | ||||||
|     animation->current = selected; |  | ||||||
|     osTimerStart(animation->timer, animation->current->basic->duration * 1000); |  | ||||||
|     animation->state = DesktopAnimationStateBasic; |  | ||||||
|     furi_assert(selected); |  | ||||||
|     AnimationList_clear(animation_list); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void desktop_animation_timer_callback(void* context) { |  | ||||||
|     furi_assert(context); |  | ||||||
|     DesktopAnimation* animation = context; |  | ||||||
|     TickType_t now_ms = osKernelGetTickCount(); |  | ||||||
|     AnimationList_t animation_list; |  | ||||||
|     AnimationList_init(animation_list); |  | ||||||
|     bool new_basic_animation = false; |  | ||||||
| 
 |  | ||||||
|     if(animation->state == DesktopAnimationStateActive) { |  | ||||||
|         animation->state = DesktopAnimationStateBasic; |  | ||||||
|         TickType_t basic_lasts_ms = now_ms - animation->basic_started_at; |  | ||||||
|         animation->active_finished_at = now_ms; |  | ||||||
|         TickType_t basic_duration_ms = animation->current->basic->duration * 1000; |  | ||||||
|         if(basic_lasts_ms > basic_duration_ms) { |  | ||||||
|             // if active animation finished, and basic duration came to an end
 |  | ||||||
|             // select new idle animation
 |  | ||||||
|             new_basic_animation = true; |  | ||||||
|         } else { |  | ||||||
|             // if active animation finished, but basic duration is not finished
 |  | ||||||
|             // play current animation for the rest of time
 |  | ||||||
|             furi_assert(basic_duration_ms != basic_lasts_ms); |  | ||||||
|             osTimerStart(animation->timer, basic_duration_ms - basic_lasts_ms); |  | ||||||
|         } |  | ||||||
|     } else if(animation->state == DesktopAnimationStateBasic) { |  | ||||||
|         // if basic animation finished
 |  | ||||||
|         // select new idle animation
 |  | ||||||
|         new_basic_animation = true; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if(new_basic_animation) { |  | ||||||
|         animation->basic_started_at = now_ms; |  | ||||||
|         desktop_start_new_idle_animation(animation); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // for oneshot generate events every time
 |  | ||||||
|     if(animation->animation_changed_callback) { |  | ||||||
|         animation->animation_changed_callback(animation->animation_changed_callback_context); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void desktop_animation_activate(DesktopAnimation* animation) { |  | ||||||
|     furi_assert(animation); |  | ||||||
| 
 |  | ||||||
|     if(animation->state != DesktopAnimationStateBasic) { |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if(animation->state == DesktopAnimationStateActive) { |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if(!animation->current->active) { |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     TickType_t now = osKernelGetTickCount(); |  | ||||||
|     TickType_t time_since_last_active = now - animation->active_finished_at; |  | ||||||
| 
 |  | ||||||
|     if(time_since_last_active > (animation->current->basic->active_cooldown * 1000)) { |  | ||||||
|         animation->state = DesktopAnimationStateActive; |  | ||||||
|         furi_assert(animation->current->active->duration > 0); |  | ||||||
|         osTimerStart(animation->timer, animation->current->active->duration * 1000); |  | ||||||
|         if(animation->animation_changed_callback) { |  | ||||||
|             animation->animation_changed_callback(animation->animation_changed_callback_context); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static const Icon* desktop_animation_get_current_idle_animation( |  | ||||||
|     DesktopAnimation* animation, |  | ||||||
|     bool* status_bar_background_black) { |  | ||||||
|     const ActiveAnimation* active = animation->current->active; |  | ||||||
|     const BasicAnimation* basic = animation->current->basic; |  | ||||||
|     if(animation->state == DesktopAnimationStateActive && active->icon) { |  | ||||||
|         *status_bar_background_black = active->black_status_bar; |  | ||||||
|         return active->icon; |  | ||||||
|     } else { |  | ||||||
|         *status_bar_background_black = basic->black_status_bar; |  | ||||||
|         return basic->icon; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Every time somebody starts 'desktop_animation_get_animation()'
 |  | ||||||
| // 1) check if there is a new level
 |  | ||||||
| // 2) check if there is SD card corruption
 |  | ||||||
| // 3) check if the SD card is empty
 |  | ||||||
| // 4) if all false - get idle animation
 |  | ||||||
| 
 |  | ||||||
| const Icon* desktop_animation_get_animation( |  | ||||||
|     DesktopAnimation* animation, |  | ||||||
|     bool* status_bar_background_black) { |  | ||||||
|     Dolphin* dolphin = furi_record_open("dolphin"); |  | ||||||
|     Storage* storage = furi_record_open("storage"); |  | ||||||
|     const Icon* icon = NULL; |  | ||||||
|     furi_assert(animation); |  | ||||||
|     FS_Error sd_status = storage_sd_status(storage); |  | ||||||
| 
 |  | ||||||
|     if(IS_BLOCKING_ANIMATION(animation->state)) { |  | ||||||
|         // don't give new animation till blocked animation
 |  | ||||||
|         // is reseted
 |  | ||||||
|         icon = animation->current_blocking_icon; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if(!icon) { |  | ||||||
|         if(sd_status == FSE_INTERNAL) { |  | ||||||
|             osTimerStop(animation->timer); |  | ||||||
|             icon = &A_CardBad_128x51; |  | ||||||
|             animation->current_blocking_icon = icon; |  | ||||||
|             animation->state = DesktopAnimationStateSDCorrupted; |  | ||||||
|             animation->sd_shown_error_card_bad = true; |  | ||||||
|             animation->sd_shown_error_db = false; |  | ||||||
|         } else if(sd_status == FSE_NOT_READY) { |  | ||||||
|             animation->sd_shown_error_card_bad = false; |  | ||||||
|             animation->sd_shown_error_db = false; |  | ||||||
|         } else if(sd_status == FSE_OK) { |  | ||||||
|             bool db_exists = storage_common_stat(storage, "/ext/manifest.txt", NULL) == FSE_OK; |  | ||||||
|             if(db_exists && !animation->sd_shown_error_db) { |  | ||||||
|                 osTimerStop(animation->timer); |  | ||||||
|                 icon = &A_CardNoDB_128x51; |  | ||||||
|                 animation->current_blocking_icon = icon; |  | ||||||
|                 animation->state = DesktopAnimationStateSDEmpty; |  | ||||||
|                 animation->sd_shown_error_db = true; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     DolphinStats stats = dolphin_stats(dolphin); |  | ||||||
|     if(!icon && stats.level_up_is_pending) { |  | ||||||
|         osTimerStop(animation->timer); |  | ||||||
|         icon = &A_LevelUpPending_128x51; |  | ||||||
|         animation->current_blocking_icon = icon; |  | ||||||
|         animation->state = DesktopAnimationStateLevelUpIsPending; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if(!icon) { |  | ||||||
|         icon = |  | ||||||
|             desktop_animation_get_current_idle_animation(animation, status_bar_background_black); |  | ||||||
|     } else { |  | ||||||
|         status_bar_background_black = false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     furi_record_close("storage"); |  | ||||||
|     furi_record_close("dolphin"); |  | ||||||
| 
 |  | ||||||
|     return icon; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| DesktopAnimationState desktop_animation_handle_right(DesktopAnimation* animation) { |  | ||||||
|     furi_assert(animation); |  | ||||||
| 
 |  | ||||||
|     bool reset_animation = false; |  | ||||||
|     bool update_animation = false; |  | ||||||
| 
 |  | ||||||
|     switch(animation->state) { |  | ||||||
|     case DesktopAnimationStateActive: |  | ||||||
|     case DesktopAnimationStateBasic: |  | ||||||
|         /* nothing */ |  | ||||||
|         break; |  | ||||||
|     case DesktopAnimationStateLevelUpIsPending: |  | ||||||
|         /* do nothing, main scene should change itself */ |  | ||||||
|         break; |  | ||||||
|     case DesktopAnimationStateSDCorrupted: |  | ||||||
|         reset_animation = true; |  | ||||||
|         break; |  | ||||||
|     case DesktopAnimationStateSDEmpty: |  | ||||||
|         animation->state = DesktopAnimationStateSDEmptyURL; |  | ||||||
|         animation->current_blocking_icon = &A_CardNoDBUrl_128x51; |  | ||||||
|         update_animation = true; |  | ||||||
|         break; |  | ||||||
|     case DesktopAnimationStateSDEmptyURL: |  | ||||||
|         reset_animation = true; |  | ||||||
|         break; |  | ||||||
|     default: |  | ||||||
|         furi_crash("Unhandled desktop animation state"); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if(reset_animation) { |  | ||||||
|         desktop_start_new_idle_animation(animation); |  | ||||||
|         update_animation = true; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if(update_animation) { |  | ||||||
|         if(animation->animation_changed_callback) { |  | ||||||
|             animation->animation_changed_callback(animation->animation_changed_callback_context); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return animation->state; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #define LEVELUP_FRAME_RATE (0.2) |  | ||||||
| 
 |  | ||||||
| void desktop_animation_start_oneshot_levelup(DesktopAnimation* animation) { |  | ||||||
|     animation->one_shot_animation_counter = 0; |  | ||||||
|     animation->state = DesktopAnimationStateLevelUpIsPending; |  | ||||||
| 
 |  | ||||||
|     Dolphin* dolphin = furi_record_open("dolphin"); |  | ||||||
|     DolphinStats stats = dolphin_stats(dolphin); |  | ||||||
|     furi_record_close("dolphin"); |  | ||||||
|     furi_assert(stats.level_up_is_pending); |  | ||||||
|     if(stats.level == 1) { |  | ||||||
|         animation->current_one_shot_icons = animation_level2up; |  | ||||||
|         animation->one_shot_animation_size = COUNT_OF(animation_level2up); |  | ||||||
|     } else if(stats.level == 2) { |  | ||||||
|         animation->current_one_shot_icons = animation_level3up; |  | ||||||
|         animation->one_shot_animation_size = COUNT_OF(animation_level3up); |  | ||||||
|     } else { |  | ||||||
|         furi_crash("Dolphin level is out of bounds"); |  | ||||||
|     } |  | ||||||
|     osTimerStart(animation->timer, LEVELUP_FRAME_RATE * 1000); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const Icon* desktop_animation_get_oneshot_frame(DesktopAnimation* animation) { |  | ||||||
|     furi_assert(IS_ONESHOT_ANIMATION(animation->state)); |  | ||||||
|     furi_assert(animation->one_shot_animation_size > 0); |  | ||||||
|     const Icon* icon = NULL; |  | ||||||
| 
 |  | ||||||
|     if(animation->one_shot_animation_counter < animation->one_shot_animation_size) { |  | ||||||
|         icon = animation->current_one_shot_icons[animation->one_shot_animation_counter]; |  | ||||||
|         ++animation->one_shot_animation_counter; |  | ||||||
|     } else { |  | ||||||
|         animation->state = DesktopAnimationStateBasic; |  | ||||||
|         animation->one_shot_animation_size = 0; |  | ||||||
|         osTimerStop(animation->timer); |  | ||||||
|         icon = NULL; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return icon; |  | ||||||
| } |  | ||||||
| @ -1,59 +0,0 @@ | |||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <stdbool.h> |  | ||||||
| #include <stdint.h> |  | ||||||
| #include <gui/icon.h> |  | ||||||
| 
 |  | ||||||
| typedef struct DesktopAnimation DesktopAnimation; |  | ||||||
| 
 |  | ||||||
| typedef struct ActiveAnimation ActiveAnimation; |  | ||||||
| typedef struct BasicAnimation BasicAnimation; |  | ||||||
| 
 |  | ||||||
| typedef enum { |  | ||||||
|     DesktopAnimationStateBasic, |  | ||||||
|     DesktopAnimationStateActive, |  | ||||||
|     DesktopAnimationStateLevelUpIsPending, |  | ||||||
|     DesktopAnimationStateSDEmpty, |  | ||||||
|     DesktopAnimationStateSDEmptyURL, |  | ||||||
|     DesktopAnimationStateSDCorrupted, |  | ||||||
| } DesktopAnimationState; |  | ||||||
| 
 |  | ||||||
| struct BasicAnimation { |  | ||||||
|     const Icon* icon; |  | ||||||
|     uint16_t duration; // sec
 |  | ||||||
|     uint16_t active_cooldown; |  | ||||||
|     uint8_t weight; |  | ||||||
|     bool black_status_bar; |  | ||||||
|     uint16_t butthurt_level_mask; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| struct ActiveAnimation { |  | ||||||
|     const Icon* icon; |  | ||||||
|     bool black_status_bar; |  | ||||||
|     uint16_t duration; // sec
 |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| typedef struct { |  | ||||||
|     const BasicAnimation* basic; |  | ||||||
|     const ActiveAnimation* active; |  | ||||||
| } PairedAnimation; |  | ||||||
| 
 |  | ||||||
| typedef void (*AnimationChangedCallback)(void*); |  | ||||||
| 
 |  | ||||||
| DesktopAnimation* desktop_animation_alloc(void); |  | ||||||
| void desktop_animation_free(DesktopAnimation*); |  | ||||||
| void desktop_animation_activate(DesktopAnimation* instance); |  | ||||||
| void desktop_animation_set_animation_changed_callback( |  | ||||||
|     DesktopAnimation* instance, |  | ||||||
|     AnimationChangedCallback callback, |  | ||||||
|     void* context); |  | ||||||
| 
 |  | ||||||
| DesktopAnimationState desktop_animation_handle_right(DesktopAnimation* animation); |  | ||||||
| 
 |  | ||||||
| void desktop_animation_start_oneshot_levelup(DesktopAnimation* animation); |  | ||||||
| 
 |  | ||||||
| const Icon* |  | ||||||
|     desktop_animation_get_animation(DesktopAnimation* animation, bool* status_bar_background_black); |  | ||||||
| const Icon* desktop_animation_get_oneshot_frame(DesktopAnimation* animation); |  | ||||||
| 
 |  | ||||||
| void desktop_start_new_idle_animation(DesktopAnimation* animation); |  | ||||||
| @ -1,332 +0,0 @@ | |||||||
| #include <assets_icons.h> |  | ||||||
| #include <stddef.h> |  | ||||||
| #include <stdint.h> |  | ||||||
| #include <gui/icon.h> |  | ||||||
| #include "desktop_animation.h" |  | ||||||
| 
 |  | ||||||
| // Calm/Mad Basic Idle Animations
 |  | ||||||
| 
 |  | ||||||
| #define COMMON_BASIC_DURATION (2 * 60 * 60) |  | ||||||
| #define COMMON_ACTIVE_CYCLES 1 |  | ||||||
| #define COMMON_ACTIVE_COOLDOWN 15 |  | ||||||
| #define COMMON_WEIGHT 3 |  | ||||||
| 
 |  | ||||||
| #define BUTTHURT_LEVEL(x) (1UL << (x)) |  | ||||||
| #define BUTTHURT_LEVEL_0 0 |  | ||||||
| 
 |  | ||||||
| // frames * cycles / frame_rate
 |  | ||||||
| #define COMMON_ACTIVE_DURATION(x) ((x)*COMMON_ACTIVE_CYCLES / 2) |  | ||||||
| 
 |  | ||||||
| static const BasicAnimation animation_TV = { |  | ||||||
|     .icon = &A_Tv_128x52, |  | ||||||
|     .duration = COMMON_BASIC_DURATION, |  | ||||||
|     .weight = COMMON_WEIGHT, |  | ||||||
|     .active_cooldown = COMMON_ACTIVE_COOLDOWN, |  | ||||||
|     .butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) | |  | ||||||
|                            BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) | |  | ||||||
|                            BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7)}; |  | ||||||
| 
 |  | ||||||
| static const ActiveAnimation animation_TV_active = { |  | ||||||
|     .icon = &A_TvActive_128x52, |  | ||||||
|     .duration = COMMON_ACTIVE_DURATION(6), |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| static const BasicAnimation animation_sleep = { |  | ||||||
|     .icon = &A_Sleep_128x52, |  | ||||||
|     .black_status_bar = true, |  | ||||||
|     .duration = COMMON_BASIC_DURATION, |  | ||||||
|     .weight = COMMON_WEIGHT, |  | ||||||
|     .active_cooldown = COMMON_ACTIVE_COOLDOWN, |  | ||||||
|     .butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) | |  | ||||||
|                            BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) | |  | ||||||
|                            BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7) | BUTTHURT_LEVEL(8) | |  | ||||||
|                            BUTTHURT_LEVEL(9) | BUTTHURT_LEVEL(10)}; |  | ||||||
| 
 |  | ||||||
| static const ActiveAnimation animation_sleep_active = { |  | ||||||
|     .icon = &A_SleepActive_128x52, |  | ||||||
|     .black_status_bar = true, |  | ||||||
|     .duration = COMMON_ACTIVE_DURATION(5), |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| static const BasicAnimation animation_leaving = { |  | ||||||
|     .icon = &A_Leaving_128x51, |  | ||||||
|     .duration = COMMON_BASIC_DURATION, |  | ||||||
|     .weight = COMMON_WEIGHT, |  | ||||||
|     .active_cooldown = COMMON_ACTIVE_COOLDOWN, |  | ||||||
|     .butthurt_level_mask = BUTTHURT_LEVEL(13) | BUTTHURT_LEVEL(14), |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| static const ActiveAnimation animation_leaving_active = { |  | ||||||
|     .icon = &A_LeavingActive_128x51, |  | ||||||
|     .duration = COMMON_ACTIVE_DURATION(2), |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| static const BasicAnimation animation_laptop = { |  | ||||||
|     .icon = &A_Laptop_128x52, |  | ||||||
|     .duration = COMMON_BASIC_DURATION, |  | ||||||
|     .weight = COMMON_WEIGHT, |  | ||||||
|     .active_cooldown = COMMON_ACTIVE_COOLDOWN, |  | ||||||
|     .butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) | |  | ||||||
|                            BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5)}; |  | ||||||
| 
 |  | ||||||
| static const ActiveAnimation animation_laptop_active = { |  | ||||||
|     .icon = &A_LaptopActive_128x52, |  | ||||||
|     .duration = COMMON_ACTIVE_DURATION(8), |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| static const BasicAnimation animation_knife = { |  | ||||||
|     .icon = &A_Knife_128x51, |  | ||||||
|     .duration = COMMON_BASIC_DURATION, |  | ||||||
|     .weight = COMMON_WEIGHT, |  | ||||||
|     .active_cooldown = COMMON_ACTIVE_COOLDOWN, |  | ||||||
|     .butthurt_level_mask = BUTTHURT_LEVEL(5) | BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7) | |  | ||||||
|                            BUTTHURT_LEVEL(8) | BUTTHURT_LEVEL(9) | BUTTHURT_LEVEL(10) | |  | ||||||
|                            BUTTHURT_LEVEL(11) | BUTTHURT_LEVEL(12) | BUTTHURT_LEVEL(13)}; |  | ||||||
| 
 |  | ||||||
| static const ActiveAnimation animation_knife_active = { |  | ||||||
|     .icon = &A_KnifeActive_128x51, |  | ||||||
|     .duration = COMMON_ACTIVE_DURATION(2), |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| static const BasicAnimation animation_cry = { |  | ||||||
|     .icon = &A_Cry_128x51, |  | ||||||
|     .duration = COMMON_BASIC_DURATION, |  | ||||||
|     .weight = COMMON_WEIGHT, |  | ||||||
|     .active_cooldown = COMMON_ACTIVE_COOLDOWN, |  | ||||||
|     .butthurt_level_mask = BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) | |  | ||||||
|                            BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7) | BUTTHURT_LEVEL(8) | |  | ||||||
|                            BUTTHURT_LEVEL(9) | BUTTHURT_LEVEL(10) | BUTTHURT_LEVEL(11) | |  | ||||||
|                            BUTTHURT_LEVEL(12) | BUTTHURT_LEVEL(13)}; |  | ||||||
| 
 |  | ||||||
| static const ActiveAnimation animation_cry_active = { |  | ||||||
|     .icon = &A_CryActive_128x51, |  | ||||||
|     .duration = COMMON_ACTIVE_DURATION(3), |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| static const BasicAnimation animation_box = { |  | ||||||
|     .icon = &A_Box_128x51, |  | ||||||
|     .duration = COMMON_BASIC_DURATION, |  | ||||||
|     .weight = COMMON_WEIGHT, |  | ||||||
|     .active_cooldown = COMMON_ACTIVE_COOLDOWN, |  | ||||||
|     .butthurt_level_mask = BUTTHURT_LEVEL(7) | BUTTHURT_LEVEL(8) | BUTTHURT_LEVEL(9) | |  | ||||||
|                            BUTTHURT_LEVEL(10) | BUTTHURT_LEVEL(11) | BUTTHURT_LEVEL(12) | |  | ||||||
|                            BUTTHURT_LEVEL(13)}; |  | ||||||
| 
 |  | ||||||
| static const ActiveAnimation animation_box_active = { |  | ||||||
|     .icon = &A_BoxActive_128x51, |  | ||||||
|     .duration = COMMON_ACTIVE_DURATION(2), |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| static const BasicAnimation animation_waves = { |  | ||||||
|     .icon = &A_Waves_128x52, |  | ||||||
|     .duration = COMMON_BASIC_DURATION, |  | ||||||
|     .weight = COMMON_WEIGHT, |  | ||||||
|     .active_cooldown = COMMON_ACTIVE_COOLDOWN, |  | ||||||
|     .butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2)}; |  | ||||||
| 
 |  | ||||||
| static const ActiveAnimation animation_waves_active = { |  | ||||||
|     .icon = &A_WavesActive_128x52, |  | ||||||
|     .duration = COMMON_ACTIVE_DURATION(7), |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // Level Idle Animations
 |  | ||||||
| 
 |  | ||||||
| static const BasicAnimation animation_level1furippa = { |  | ||||||
|     .icon = &A_Level1Furippa_128x51, |  | ||||||
|     .duration = COMMON_BASIC_DURATION, |  | ||||||
|     .weight = COMMON_WEIGHT, |  | ||||||
|     .active_cooldown = COMMON_ACTIVE_COOLDOWN, |  | ||||||
|     .butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) | |  | ||||||
|                            BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) | |  | ||||||
|                            BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7)}; |  | ||||||
| 
 |  | ||||||
| static const ActiveAnimation animation_level1furippa_active = { |  | ||||||
|     .icon = &A_Level1FurippaActive_128x51, |  | ||||||
|     .duration = COMMON_ACTIVE_DURATION(6), |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| static const BasicAnimation animation_level1read = { |  | ||||||
|     .icon = &A_Level1Read_128x51, |  | ||||||
|     .duration = COMMON_BASIC_DURATION, |  | ||||||
|     .weight = COMMON_WEIGHT, |  | ||||||
|     .active_cooldown = COMMON_ACTIVE_COOLDOWN, |  | ||||||
|     .butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2)}; |  | ||||||
| 
 |  | ||||||
| static const ActiveAnimation animation_level1read_active = { |  | ||||||
|     .icon = &A_Level1ReadActive_128x51, |  | ||||||
|     .duration = COMMON_ACTIVE_DURATION(2), |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| static const BasicAnimation animation_level1toys = { |  | ||||||
|     .icon = &A_Level1Toys_128x51, |  | ||||||
|     .duration = COMMON_BASIC_DURATION, |  | ||||||
|     .weight = COMMON_WEIGHT, |  | ||||||
|     .active_cooldown = COMMON_ACTIVE_COOLDOWN, |  | ||||||
|     .butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) | |  | ||||||
|                            BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) | |  | ||||||
|                            BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7) | BUTTHURT_LEVEL(8)}; |  | ||||||
| 
 |  | ||||||
| static const ActiveAnimation animation_level1toys_active = { |  | ||||||
|     .icon = &A_Level1ToysActive_128x51, |  | ||||||
|     .duration = COMMON_ACTIVE_DURATION(2), |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| static const BasicAnimation animation_level2furippa = { |  | ||||||
|     .icon = &A_Level2Furippa_128x51, |  | ||||||
|     .duration = COMMON_BASIC_DURATION, |  | ||||||
|     .weight = COMMON_WEIGHT, |  | ||||||
|     .active_cooldown = COMMON_ACTIVE_COOLDOWN, |  | ||||||
|     .butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) | |  | ||||||
|                            BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) | |  | ||||||
|                            BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7)}; |  | ||||||
| 
 |  | ||||||
| static const ActiveAnimation animation_level2furippa_active = { |  | ||||||
|     .icon = &A_Level2FurippaActive_128x51, |  | ||||||
|     .duration = COMMON_ACTIVE_DURATION(6), |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| static const BasicAnimation animation_level2soldering = { |  | ||||||
|     .icon = &A_Level2Soldering_128x51, |  | ||||||
|     .duration = COMMON_BASIC_DURATION, |  | ||||||
|     .weight = COMMON_WEIGHT, |  | ||||||
|     .active_cooldown = COMMON_ACTIVE_COOLDOWN, |  | ||||||
|     .butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) | |  | ||||||
|                            BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) | |  | ||||||
|                            BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7) | BUTTHURT_LEVEL(8) | |  | ||||||
|                            BUTTHURT_LEVEL(9)}; |  | ||||||
| 
 |  | ||||||
| static const ActiveAnimation animation_level2soldering_active = { |  | ||||||
|     .icon = &A_Level2SolderingActive_128x51, |  | ||||||
|     .duration = COMMON_ACTIVE_DURATION(2), |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| static const BasicAnimation animation_level2hack = { |  | ||||||
|     .icon = &A_Level2Hack_128x51, |  | ||||||
|     .duration = COMMON_BASIC_DURATION, |  | ||||||
|     .weight = COMMON_WEIGHT, |  | ||||||
|     .active_cooldown = COMMON_ACTIVE_COOLDOWN, |  | ||||||
|     .butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) | |  | ||||||
|                            BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) | |  | ||||||
|                            BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7) | BUTTHURT_LEVEL(8)}; |  | ||||||
| 
 |  | ||||||
| static const ActiveAnimation animation_level2hack_active = { |  | ||||||
|     .icon = &A_Level2HackActive_128x51, |  | ||||||
|     .duration = COMMON_ACTIVE_DURATION(2), |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| static const BasicAnimation animation_level3furippa = { |  | ||||||
|     .icon = &A_Level3Furippa_128x51, |  | ||||||
|     .duration = COMMON_BASIC_DURATION, |  | ||||||
|     .weight = COMMON_WEIGHT, |  | ||||||
|     .active_cooldown = COMMON_ACTIVE_COOLDOWN, |  | ||||||
|     .butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) | |  | ||||||
|                            BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) | |  | ||||||
|                            BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7)}; |  | ||||||
| 
 |  | ||||||
| static const ActiveAnimation animation_level3furippa_active = { |  | ||||||
|     .icon = &A_Level3FurippaActive_128x51, |  | ||||||
|     .duration = COMMON_ACTIVE_DURATION(6), |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| static const BasicAnimation animation_level3hijack = { |  | ||||||
|     .icon = &A_Level3Hijack_128x51, |  | ||||||
|     .duration = COMMON_BASIC_DURATION, |  | ||||||
|     .weight = COMMON_WEIGHT, |  | ||||||
|     .active_cooldown = COMMON_ACTIVE_COOLDOWN, |  | ||||||
|     .butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) | |  | ||||||
|                            BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) | |  | ||||||
|                            BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7) | BUTTHURT_LEVEL(8) | |  | ||||||
|                            BUTTHURT_LEVEL(9)}; |  | ||||||
| 
 |  | ||||||
| static const ActiveAnimation animation_level3hijack_active = { |  | ||||||
|     .icon = &A_Level3HijackActive_128x51, |  | ||||||
|     .duration = COMMON_ACTIVE_DURATION(2), |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| static const BasicAnimation animation_level3lab = { |  | ||||||
|     .icon = &A_Level3Lab_128x51, |  | ||||||
|     .duration = COMMON_BASIC_DURATION, |  | ||||||
|     .weight = COMMON_WEIGHT, |  | ||||||
|     .active_cooldown = COMMON_ACTIVE_COOLDOWN, |  | ||||||
|     .butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) | |  | ||||||
|                            BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) | |  | ||||||
|                            BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7) | BUTTHURT_LEVEL(8)}; |  | ||||||
| 
 |  | ||||||
| static const ActiveAnimation animation_level3lab_active = { |  | ||||||
|     .icon = &A_Level3LabActive_128x51, |  | ||||||
|     .duration = COMMON_ACTIVE_DURATION(2), |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // System Idle Animations
 |  | ||||||
| 
 |  | ||||||
| static const BasicAnimation animation_bad_battery = { |  | ||||||
|     .icon = &A_BadBattery_128x51, |  | ||||||
|     .duration = COMMON_BASIC_DURATION, |  | ||||||
|     .weight = 7, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| static const BasicAnimation animation_no_sd_card = { |  | ||||||
|     .icon = &A_NoSdCard_128x51, |  | ||||||
|     .duration = COMMON_BASIC_DURATION, |  | ||||||
|     .weight = 7, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| const Icon* animation_level2up[] = { |  | ||||||
|     &I_LevelUp2_01, |  | ||||||
|     &I_LevelUp2_02, |  | ||||||
|     &I_LevelUp2_03, |  | ||||||
|     &I_LevelUp2_04, |  | ||||||
|     &I_LevelUp2_05, |  | ||||||
|     &I_LevelUp2_06, |  | ||||||
|     &I_LevelUp2_07}; |  | ||||||
| 
 |  | ||||||
| const Icon* animation_level3up[] = { |  | ||||||
|     &I_LevelUp3_01, |  | ||||||
|     &I_LevelUp3_02, |  | ||||||
|     &I_LevelUp3_03, |  | ||||||
|     &I_LevelUp3_04, |  | ||||||
|     &I_LevelUp3_05, |  | ||||||
|     &I_LevelUp3_06, |  | ||||||
|     &I_LevelUp3_07}; |  | ||||||
| 
 |  | ||||||
| // Blocking Idle Animations & One shot Animations represented as naked Icon
 |  | ||||||
| 
 |  | ||||||
| static const PairedAnimation calm_animation[] = { |  | ||||||
|     {.basic = &animation_TV, .active = &animation_TV_active}, |  | ||||||
|     {.basic = &animation_waves, .active = &animation_waves_active}, |  | ||||||
|     {.basic = &animation_sleep, .active = &animation_sleep_active}, |  | ||||||
|     {.basic = &animation_laptop, .active = &animation_laptop_active}, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| static const PairedAnimation mad_animation[] = { |  | ||||||
|     {.basic = &animation_cry, .active = &animation_cry_active}, |  | ||||||
|     {.basic = &animation_knife, .active = &animation_knife_active}, |  | ||||||
|     {.basic = &animation_box, .active = &animation_box_active}, |  | ||||||
|     {.basic = &animation_leaving, .active = &animation_leaving_active}, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| static const PairedAnimation level_1_animation[] = { |  | ||||||
|     {.basic = &animation_level1furippa, .active = &animation_level1furippa_active}, |  | ||||||
|     {.basic = &animation_level1read, .active = &animation_level1read_active}, |  | ||||||
|     {.basic = &animation_level1toys, .active = &animation_level1toys_active}, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| static const PairedAnimation level_2_animation[] = { |  | ||||||
|     {.basic = &animation_level2furippa, .active = &animation_level2furippa_active}, |  | ||||||
|     {.basic = &animation_level2soldering, .active = &animation_level2soldering_active}, |  | ||||||
|     {.basic = &animation_level2hack, .active = &animation_level2hack_active}, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| static const PairedAnimation level_3_animation[] = { |  | ||||||
|     {.basic = &animation_level3furippa, .active = &animation_level3furippa_active}, |  | ||||||
|     {.basic = &animation_level3hijack, .active = &animation_level3hijack_active}, |  | ||||||
|     {.basic = &animation_level3lab, .active = &animation_level3lab_active}, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| static const PairedAnimation no_sd_animation[] = { |  | ||||||
|     {.basic = &animation_no_sd_card, .active = NULL}, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| static const PairedAnimation check_battery_animation[] = { |  | ||||||
|     {.basic = &animation_bad_battery, .active = NULL}, |  | ||||||
| }; |  | ||||||
| @ -5,4 +5,4 @@ ADD_SCENE(desktop, debug, Debug) | |||||||
| ADD_SCENE(desktop, first_start, FirstStart) | ADD_SCENE(desktop, first_start, FirstStart) | ||||||
| ADD_SCENE(desktop, hw_mismatch, HwMismatch) | ADD_SCENE(desktop, hw_mismatch, HwMismatch) | ||||||
| ADD_SCENE(desktop, pinsetup, PinSetup) | ADD_SCENE(desktop, pinsetup, PinSetup) | ||||||
| ADD_SCENE(desktop, levelup, LevelUp) | ADD_SCENE(desktop, fault, Fault) | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ | |||||||
| #include <dolphin/dolphin.h> | #include <dolphin/dolphin.h> | ||||||
| #include <dolphin/helpers/dolphin_deed.h> | #include <dolphin/helpers/dolphin_deed.h> | ||||||
| 
 | 
 | ||||||
| void desktop_scene_debug_callback(DesktopDebugEvent event, void* context) { | void desktop_scene_debug_callback(DesktopEvent event, void* context) { | ||||||
|     Desktop* desktop = (Desktop*)context; |     Desktop* desktop = (Desktop*)context; | ||||||
|     view_dispatcher_send_custom_event(desktop->view_dispatcher, event); |     view_dispatcher_send_custom_event(desktop->view_dispatcher, event); | ||||||
| } | } | ||||||
| @ -33,14 +33,12 @@ bool desktop_scene_debug_on_event(void* context, SceneManagerEvent event) { | |||||||
|         case DesktopDebugEventDeed: |         case DesktopDebugEventDeed: | ||||||
|             dolphin_deed(dolphin, DolphinDeedIButtonEmulate); |             dolphin_deed(dolphin, DolphinDeedIButtonEmulate); | ||||||
|             desktop_debug_get_dolphin_data(desktop->debug_view); |             desktop_debug_get_dolphin_data(desktop->debug_view); | ||||||
|             desktop_start_new_idle_animation(desktop->animation); |  | ||||||
|             consumed = true; |             consumed = true; | ||||||
|             break; |             break; | ||||||
| 
 | 
 | ||||||
|         case DesktopDebugEventWrongDeed: |         case DesktopDebugEventWrongDeed: | ||||||
|             dolphin_deed(dolphin, DolphinDeedWrong); |             dolphin_deed(dolphin, DolphinDeedWrong); | ||||||
|             desktop_debug_get_dolphin_data(desktop->debug_view); |             desktop_debug_get_dolphin_data(desktop->debug_view); | ||||||
|             desktop_start_new_idle_animation(desktop->animation); |  | ||||||
|             consumed = true; |             consumed = true; | ||||||
|             break; |             break; | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										49
									
								
								applications/desktop/scenes/desktop_scene_fault.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								applications/desktop/scenes/desktop_scene_fault.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,49 @@ | |||||||
|  | #include "../desktop_i.h" | ||||||
|  | 
 | ||||||
|  | #define DesktopFaultEventExit 0x00FF00FF | ||||||
|  | 
 | ||||||
|  | void desktop_scene_fault_callback(void* context) { | ||||||
|  |     Desktop* desktop = (Desktop*)context; | ||||||
|  |     view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopFaultEventExit); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_scene_fault_on_enter(void* context) { | ||||||
|  |     Desktop* desktop = (Desktop*)context; | ||||||
|  | 
 | ||||||
|  |     Popup* popup = desktop->hw_mismatch_popup; | ||||||
|  |     popup_set_context(popup, desktop); | ||||||
|  |     popup_set_header( | ||||||
|  |         popup, | ||||||
|  |         "Flipper crashed\n and was rebooted", | ||||||
|  |         60, | ||||||
|  |         14 + STATUS_BAR_Y_SHIFT, | ||||||
|  |         AlignCenter, | ||||||
|  |         AlignCenter); | ||||||
|  | 
 | ||||||
|  |     char* message = (char*)furi_hal_rtc_get_fault_data(); | ||||||
|  |     popup_set_text(popup, message, 60, 37 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter); | ||||||
|  |     popup_set_callback(popup, desktop_scene_fault_callback); | ||||||
|  |     view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewHwMismatch); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool desktop_scene_fault_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     Desktop* desktop = (Desktop*)context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         switch(event.event) { | ||||||
|  |         case DesktopFaultEventExit: | ||||||
|  |             scene_manager_previous_scene(desktop->scene_manager); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  |         default: | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_scene_fault_on_exit(void* context) { | ||||||
|  |     furi_hal_rtc_set_fault_data(0); | ||||||
|  | } | ||||||
| @ -1,7 +1,8 @@ | |||||||
| #include "../desktop_i.h" | #include "../desktop_i.h" | ||||||
| #include "../views/desktop_first_start.h" | #include "../views/desktop_first_start.h" | ||||||
|  | #include "../views/desktop_events.h" | ||||||
| 
 | 
 | ||||||
| void desktop_scene_first_start_callback(DesktopFirstStartEvent event, void* context) { | void desktop_scene_first_start_callback(DesktopEvent event, void* context) { | ||||||
|     Desktop* desktop = (Desktop*)context; |     Desktop* desktop = (Desktop*)context; | ||||||
|     view_dispatcher_send_custom_event(desktop->view_dispatcher, event); |     view_dispatcher_send_custom_event(desktop->view_dispatcher, event); | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| #include "../desktop_i.h" | #include "../desktop_i.h" | ||||||
| #include <furi-hal-version.h> | #include <furi_hal_version.h> | ||||||
| 
 | 
 | ||||||
| #define HW_MISMATCH_BACK_EVENT (0UL) | #define HW_MISMATCH_BACK_EVENT (0UL) | ||||||
| 
 | 
 | ||||||
| @ -10,18 +10,21 @@ void desktop_scene_hw_mismatch_callback(void* context) { | |||||||
| 
 | 
 | ||||||
| void desktop_scene_hw_mismatch_on_enter(void* context) { | void desktop_scene_hw_mismatch_on_enter(void* context) { | ||||||
|     Desktop* desktop = (Desktop*)context; |     Desktop* desktop = (Desktop*)context; | ||||||
|  |     furi_assert(desktop); | ||||||
|  |     furi_assert(!desktop->text_buffer); | ||||||
|     Popup* popup = desktop->hw_mismatch_popup; |     Popup* popup = desktop->hw_mismatch_popup; | ||||||
|     char buffer[256]; // strange but smaller buffer not making it
 |     desktop->text_buffer = furi_alloc(256); | ||||||
|     snprintf( |     snprintf( | ||||||
|         buffer, |         desktop->text_buffer, | ||||||
|         sizeof(buffer), |         256, | ||||||
|         "HW target: %d\nFW target: %d", |         "HW target: %d\nFW target: %d", | ||||||
|         furi_hal_version_get_hw_target(), |         furi_hal_version_get_hw_target(), | ||||||
|         version_get_target(NULL)); |         version_get_target(NULL)); | ||||||
|     popup_set_context(popup, desktop); |     popup_set_context(popup, desktop); | ||||||
|     popup_set_header( |     popup_set_header( | ||||||
|         popup, "!!!! HW Mismatch !!!!", 60, 14 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter); |         popup, "!!!! HW Mismatch !!!!", 60, 14 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter); | ||||||
|     popup_set_text(popup, buffer, 60, 37 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter); |     popup_set_text( | ||||||
|  |         popup, desktop->text_buffer, 60, 37 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter); | ||||||
|     popup_set_callback(popup, desktop_scene_hw_mismatch_callback); |     popup_set_callback(popup, desktop_scene_hw_mismatch_callback); | ||||||
|     view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewHwMismatch); |     view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewHwMismatch); | ||||||
| } | } | ||||||
| @ -46,9 +49,13 @@ bool desktop_scene_hw_mismatch_on_event(void* context, SceneManagerEvent event) | |||||||
| 
 | 
 | ||||||
| void desktop_scene_hw_mismatch_on_exit(void* context) { | void desktop_scene_hw_mismatch_on_exit(void* context) { | ||||||
|     Desktop* desktop = (Desktop*)context; |     Desktop* desktop = (Desktop*)context; | ||||||
|  |     furi_assert(desktop); | ||||||
|  |     furi_assert(desktop->text_buffer); | ||||||
|     Popup* popup = desktop->hw_mismatch_popup; |     Popup* popup = desktop->hw_mismatch_popup; | ||||||
|     popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom); |     popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom); | ||||||
|     popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop); |     popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop); | ||||||
|     popup_set_callback(popup, NULL); |     popup_set_callback(popup, NULL); | ||||||
|     popup_set_context(popup, NULL); |     popup_set_context(popup, NULL); | ||||||
|  |     free(desktop->text_buffer); | ||||||
|  |     desktop->text_buffer = NULL; | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,79 +0,0 @@ | |||||||
| #include "../desktop_i.h" |  | ||||||
| #include "../views/desktop_main.h" |  | ||||||
| #include "applications.h" |  | ||||||
| #include "assets_icons.h" |  | ||||||
| #include "desktop/desktop.h" |  | ||||||
| #include "desktop/helpers/desktop_animation.h" |  | ||||||
| #include "dolphin/dolphin.h" |  | ||||||
| #include "furi/pubsub.h" |  | ||||||
| #include "furi/record.h" |  | ||||||
| #include "storage/storage-glue.h" |  | ||||||
| #include <loader/loader.h> |  | ||||||
| #include <m-list.h> |  | ||||||
| 
 |  | ||||||
| #define LEVELUP_SCENE_PLAYING 0 |  | ||||||
| #define LEVELUP_SCENE_STOPPED 1 |  | ||||||
| 
 |  | ||||||
| static void desktop_scene_levelup_callback(DesktopMainEvent event, void* context) { |  | ||||||
|     Desktop* desktop = (Desktop*)context; |  | ||||||
|     view_dispatcher_send_custom_event(desktop->view_dispatcher, event); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void desktop_scene_levelup_animation_changed_callback(void* context) { |  | ||||||
|     furi_assert(context); |  | ||||||
|     Desktop* desktop = context; |  | ||||||
|     view_dispatcher_send_custom_event( |  | ||||||
|         desktop->view_dispatcher, DesktopMainEventUpdateOneShotAnimation); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void desktop_scene_levelup_on_enter(void* context) { |  | ||||||
|     Desktop* desktop = (Desktop*)context; |  | ||||||
|     DesktopMainView* main_view = desktop->main_view; |  | ||||||
| 
 |  | ||||||
|     desktop_main_set_callback(main_view, desktop_scene_levelup_callback, desktop); |  | ||||||
|     desktop_animation_set_animation_changed_callback( |  | ||||||
|         desktop->animation, desktop_scene_levelup_animation_changed_callback, desktop); |  | ||||||
| 
 |  | ||||||
|     desktop_animation_start_oneshot_levelup(desktop->animation); |  | ||||||
|     const Icon* icon = desktop_animation_get_oneshot_frame(desktop->animation); |  | ||||||
|     desktop_main_switch_dolphin_icon(desktop->main_view, icon); |  | ||||||
|     view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewMain); |  | ||||||
|     scene_manager_set_scene_state( |  | ||||||
|         desktop->scene_manager, DesktopSceneLevelUp, LEVELUP_SCENE_PLAYING); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool desktop_scene_levelup_on_event(void* context, SceneManagerEvent event) { |  | ||||||
|     Desktop* desktop = (Desktop*)context; |  | ||||||
|     bool consumed = false; |  | ||||||
|     DesktopMainEvent main_event = event.event; |  | ||||||
| 
 |  | ||||||
|     if(event.type == SceneManagerEventTypeCustom) { |  | ||||||
|         if(main_event == DesktopMainEventUpdateOneShotAnimation) { |  | ||||||
|             const Icon* icon = desktop_animation_get_oneshot_frame(desktop->animation); |  | ||||||
|             if(icon) { |  | ||||||
|                 desktop_main_switch_dolphin_icon(desktop->main_view, icon); |  | ||||||
|             } else { |  | ||||||
|                 scene_manager_set_scene_state( |  | ||||||
|                     desktop->scene_manager, DesktopSceneLevelUp, LEVELUP_SCENE_STOPPED); |  | ||||||
|             } |  | ||||||
|             consumed = true; |  | ||||||
|         } else { |  | ||||||
|             if(scene_manager_get_scene_state(desktop->scene_manager, DesktopSceneLevelUp) == |  | ||||||
|                LEVELUP_SCENE_STOPPED) { |  | ||||||
|                 scene_manager_previous_scene(desktop->scene_manager); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return consumed; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void desktop_scene_levelup_on_exit(void* context) { |  | ||||||
|     Desktop* desktop = (Desktop*)context; |  | ||||||
| 
 |  | ||||||
|     Dolphin* dolphin = furi_record_open("dolphin"); |  | ||||||
|     dolphin_upgrade_level(dolphin); |  | ||||||
|     furi_record_close("dolphin"); |  | ||||||
|     desktop_animation_set_animation_changed_callback(desktop->animation, NULL, NULL); |  | ||||||
|     desktop_start_new_idle_animation(desktop->animation); |  | ||||||
| } |  | ||||||
| @ -3,7 +3,7 @@ | |||||||
| #include <toolbox/saved_struct.h> | #include <toolbox/saved_struct.h> | ||||||
| #include <stdbool.h> | #include <stdbool.h> | ||||||
| 
 | 
 | ||||||
| void desktop_scene_lock_menu_callback(DesktopLockMenuEvent event, void* context) { | void desktop_scene_lock_menu_callback(DesktopEvent event, void* context) { | ||||||
|     Desktop* desktop = (Desktop*)context; |     Desktop* desktop = (Desktop*)context; | ||||||
|     view_dispatcher_send_custom_event(desktop->view_dispatcher, event); |     view_dispatcher_send_custom_event(desktop->view_dispatcher, event); | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,34 +1,28 @@ | |||||||
| #include "../desktop_i.h" | #include "../desktop_i.h" | ||||||
| #include "../views/desktop_locked.h" | #include "../views/desktop_locked.h" | ||||||
| #include "desktop/helpers/desktop_animation.h" |  | ||||||
| #include "desktop/views/desktop_main.h" | #include "desktop/views/desktop_main.h" | ||||||
| 
 | 
 | ||||||
| void desktop_scene_locked_callback(DesktopLockedEvent event, void* context) { | void desktop_scene_locked_callback(DesktopEvent event, void* context) { | ||||||
|     Desktop* desktop = (Desktop*)context; |     Desktop* desktop = (Desktop*)context; | ||||||
|     view_dispatcher_send_custom_event(desktop->view_dispatcher, event); |     view_dispatcher_send_custom_event(desktop->view_dispatcher, event); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void desktop_scene_locked_animation_changed_callback(void* context) { | static void desktop_scene_locked_new_idle_animation_callback(void* context) { | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
|     Desktop* desktop = context; |     Desktop* desktop = context; | ||||||
|     view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopMainEventUpdateAnimation); |     view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopLockedEventCheckAnimation); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void desktop_scene_locked_on_enter(void* context) { | void desktop_scene_locked_on_enter(void* context) { | ||||||
|     Desktop* desktop = (Desktop*)context; |     Desktop* desktop = (Desktop*)context; | ||||||
|     DesktopLockedView* locked_view = desktop->locked_view; |     DesktopLockedView* locked_view = desktop->locked_view; | ||||||
| 
 | 
 | ||||||
|  |     animation_manager_set_new_idle_callback( | ||||||
|  |         desktop->animation_manager, desktop_scene_locked_new_idle_animation_callback); | ||||||
|     desktop_locked_set_callback(locked_view, desktop_scene_locked_callback, desktop); |     desktop_locked_set_callback(locked_view, desktop_scene_locked_callback, desktop); | ||||||
|     desktop_locked_reset_door_pos(locked_view); |     desktop_locked_reset_door_pos(locked_view); | ||||||
|     desktop_locked_update_hint_timeout(locked_view); |     desktop_locked_update_hint_timeout(locked_view); | ||||||
| 
 | 
 | ||||||
|     desktop_animation_set_animation_changed_callback( |  | ||||||
|         desktop->animation, desktop_scene_locked_animation_changed_callback, desktop); |  | ||||||
|     bool status_bar_background_black = false; |  | ||||||
|     const Icon* icon = |  | ||||||
|         desktop_animation_get_animation(desktop->animation, &status_bar_background_black); |  | ||||||
|     desktop_locked_set_dolphin_animation(locked_view, icon, status_bar_background_black); |  | ||||||
| 
 |  | ||||||
|     uint32_t state = scene_manager_get_scene_state(desktop->scene_manager, DesktopViewLocked); |     uint32_t state = scene_manager_get_scene_state(desktop->scene_manager, DesktopViewLocked); | ||||||
| 
 | 
 | ||||||
|     desktop_locked_with_pin(desktop->locked_view, state == DesktopLockedWithPin); |     desktop_locked_with_pin(desktop->locked_view, state == DesktopLockedWithPin); | ||||||
| @ -39,7 +33,7 @@ void desktop_scene_locked_on_enter(void* context) { | |||||||
|     view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewLocked); |     view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewLocked); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static bool desktop_scene_locked_check_pin(Desktop* desktop, DesktopMainEvent event) { | static bool desktop_scene_locked_check_pin(Desktop* desktop, DesktopEvent event) { | ||||||
|     bool match = false; |     bool match = false; | ||||||
| 
 | 
 | ||||||
|     size_t length = desktop->pincode_buffer.length; |     size_t length = desktop->pincode_buffer.length; | ||||||
| @ -81,15 +75,10 @@ bool desktop_scene_locked_on_event(void* context, SceneManagerEvent event) { | |||||||
|         case DesktopLockedEventInputReset: |         case DesktopLockedEventInputReset: | ||||||
|             desktop->pincode_buffer.length = 0; |             desktop->pincode_buffer.length = 0; | ||||||
|             break; |             break; | ||||||
|         case DesktopMainEventUpdateAnimation: { |         case DesktopLockedEventCheckAnimation: | ||||||
|             bool status_bar_background_black = false; |             animation_manager_check_blocking_process(desktop->animation_manager); | ||||||
|             const Icon* icon = |  | ||||||
|                 desktop_animation_get_animation(desktop->animation, &status_bar_background_black); |  | ||||||
|             desktop_locked_set_dolphin_animation( |  | ||||||
|                 desktop->locked_view, icon, status_bar_background_black); |  | ||||||
|             consumed = true; |             consumed = true; | ||||||
|             break; |             break; | ||||||
|         } |  | ||||||
|         default: |         default: | ||||||
|             if(desktop_scene_locked_check_pin(desktop, event.event)) { |             if(desktop_scene_locked_check_pin(desktop, event.event)) { | ||||||
|                 scene_manager_set_scene_state( |                 scene_manager_set_scene_state( | ||||||
| @ -106,7 +95,7 @@ bool desktop_scene_locked_on_event(void* context, SceneManagerEvent event) { | |||||||
| 
 | 
 | ||||||
| void desktop_scene_locked_on_exit(void* context) { | void desktop_scene_locked_on_exit(void* context) { | ||||||
|     Desktop* desktop = (Desktop*)context; |     Desktop* desktop = (Desktop*)context; | ||||||
|     desktop_animation_set_animation_changed_callback(desktop->animation, NULL, NULL); |     animation_manager_set_new_idle_callback(desktop->animation_manager, NULL); | ||||||
|     desktop_locked_reset_counter(desktop->locked_view); |     desktop_locked_reset_counter(desktop->locked_view); | ||||||
|     osTimerStop(desktop->locked_view->timer); |     osTimerStop(desktop->locked_view->timer); | ||||||
| } | } | ||||||
|  | |||||||
| @ -2,14 +2,51 @@ | |||||||
| #include "../views/desktop_main.h" | #include "../views/desktop_main.h" | ||||||
| #include "applications.h" | #include "applications.h" | ||||||
| #include "assets_icons.h" | #include "assets_icons.h" | ||||||
|  | #include "cmsis_os2.h" | ||||||
|  | #include "desktop/desktop.h" | ||||||
|  | #include "desktop/views/desktop_events.h" | ||||||
| #include "dolphin/dolphin.h" | #include "dolphin/dolphin.h" | ||||||
| #include "furi/pubsub.h" | #include "furi/pubsub.h" | ||||||
| #include "furi/record.h" | #include "furi/record.h" | ||||||
| #include "storage/storage-glue.h" | #include "furi/thread.h" | ||||||
|  | #include "storage/storage_glue.h" | ||||||
| #include <loader/loader.h> | #include <loader/loader.h> | ||||||
| #include <m-list.h> | #include <m-list.h> | ||||||
| #define MAIN_VIEW_DEFAULT (0UL) | #define MAIN_VIEW_DEFAULT (0UL) | ||||||
| 
 | 
 | ||||||
|  | static void desktop_scene_main_app_started_callback(const void* message, void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     Desktop* desktop = context; | ||||||
|  |     const LoaderEvent* event = message; | ||||||
|  | 
 | ||||||
|  |     if(event->type == LoaderEventTypeApplicationStarted) { | ||||||
|  |         view_dispatcher_send_custom_event( | ||||||
|  |             desktop->view_dispatcher, DesktopMainEventBeforeAppStarted); | ||||||
|  |         osSemaphoreAcquire(desktop->unload_animation_semaphore, osWaitForever); | ||||||
|  |     } else if(event->type == LoaderEventTypeApplicationStopped) { | ||||||
|  |         view_dispatcher_send_custom_event( | ||||||
|  |             desktop->view_dispatcher, DesktopMainEventAfterAppFinished); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void desktop_scene_main_new_idle_animation_callback(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     Desktop* desktop = context; | ||||||
|  |     view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopMainEventNewIdleAnimation); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void desktop_scene_main_check_animation_callback(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     Desktop* desktop = context; | ||||||
|  |     view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopMainEventCheckAnimation); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void desktop_scene_main_interact_animation_callback(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     Desktop* desktop = context; | ||||||
|  |     view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopMainEventInteractAnimation); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static void desktop_switch_to_app(Desktop* desktop, const FlipperApplication* flipper_app) { | static void desktop_switch_to_app(Desktop* desktop, const FlipperApplication* flipper_app) { | ||||||
|     furi_assert(desktop); |     furi_assert(desktop); | ||||||
|     furi_assert(flipper_app); |     furi_assert(flipper_app); | ||||||
| @ -28,21 +65,27 @@ static void desktop_switch_to_app(Desktop* desktop, const FlipperApplication* fl | |||||||
|     furi_thread_start(desktop->scene_thread); |     furi_thread_start(desktop->scene_thread); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void desktop_scene_main_callback(DesktopMainEvent event, void* context) { | void desktop_scene_main_callback(DesktopEvent event, void* context) { | ||||||
|     Desktop* desktop = (Desktop*)context; |     Desktop* desktop = (Desktop*)context; | ||||||
|     view_dispatcher_send_custom_event(desktop->view_dispatcher, event); |     view_dispatcher_send_custom_event(desktop->view_dispatcher, event); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void desktop_scene_main_animation_changed_callback(void* context) { |  | ||||||
|     furi_assert(context); |  | ||||||
|     Desktop* desktop = context; |  | ||||||
|     view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopMainEventUpdateAnimation); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void desktop_scene_main_on_enter(void* context) { | void desktop_scene_main_on_enter(void* context) { | ||||||
|     Desktop* desktop = (Desktop*)context; |     Desktop* desktop = (Desktop*)context; | ||||||
|     DesktopMainView* main_view = desktop->main_view; |     DesktopMainView* main_view = desktop->main_view; | ||||||
| 
 | 
 | ||||||
|  |     animation_manager_set_context(desktop->animation_manager, desktop); | ||||||
|  |     animation_manager_set_new_idle_callback( | ||||||
|  |         desktop->animation_manager, desktop_scene_main_new_idle_animation_callback); | ||||||
|  |     animation_manager_set_check_callback( | ||||||
|  |         desktop->animation_manager, desktop_scene_main_check_animation_callback); | ||||||
|  |     animation_manager_set_interact_callback( | ||||||
|  |         desktop->animation_manager, desktop_scene_main_interact_animation_callback); | ||||||
|  | 
 | ||||||
|  |     furi_assert(osSemaphoreGetCount(desktop->unload_animation_semaphore) == 0); | ||||||
|  |     desktop->app_start_stop_subscription = furi_pubsub_subscribe( | ||||||
|  |         loader_get_pubsub(), desktop_scene_main_app_started_callback, desktop); | ||||||
|  | 
 | ||||||
|     desktop_main_set_callback(main_view, desktop_scene_main_callback, desktop); |     desktop_main_set_callback(main_view, desktop_scene_main_callback, desktop); | ||||||
|     view_port_enabled_set(desktop->lock_viewport, false); |     view_port_enabled_set(desktop->lock_viewport, false); | ||||||
| 
 | 
 | ||||||
| @ -51,13 +94,6 @@ void desktop_scene_main_on_enter(void* context) { | |||||||
|         desktop_main_unlocked(desktop->main_view); |         desktop_main_unlocked(desktop->main_view); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     desktop_animation_activate(desktop->animation); |  | ||||||
|     desktop_animation_set_animation_changed_callback( |  | ||||||
|         desktop->animation, desktop_scene_main_animation_changed_callback, desktop); |  | ||||||
|     bool status_bar_background_black = false; |  | ||||||
|     const Icon* icon = |  | ||||||
|         desktop_animation_get_animation(desktop->animation, &status_bar_background_black); |  | ||||||
|     desktop_main_switch_dolphin_animation(desktop->main_view, icon, status_bar_background_black); |  | ||||||
|     view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewMain); |     view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewMain); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -83,43 +119,51 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { | |||||||
|             break; |             break; | ||||||
| 
 | 
 | ||||||
|         case DesktopMainEventOpenArchive: |         case DesktopMainEventOpenArchive: | ||||||
|  | #ifdef APP_ARCHIVE | ||||||
|  |             animation_manager_unload_and_stall_animation(desktop->animation_manager); | ||||||
|             desktop_switch_to_app(desktop, &FLIPPER_ARCHIVE); |             desktop_switch_to_app(desktop, &FLIPPER_ARCHIVE); | ||||||
|  |             animation_manager_load_and_continue_animation(desktop->animation_manager); | ||||||
|  | #endif | ||||||
|             consumed = true; |             consumed = true; | ||||||
|             break; |             break; | ||||||
| 
 | 
 | ||||||
|         case DesktopMainEventOpenFavorite: |         case DesktopMainEventOpenFavorite: | ||||||
|             LOAD_DESKTOP_SETTINGS(&desktop->settings); |             LOAD_DESKTOP_SETTINGS(&desktop->settings); | ||||||
|  |             animation_manager_unload_and_stall_animation(desktop->animation_manager); | ||||||
|  |             if(desktop->settings.favorite < FLIPPER_APPS_COUNT) { | ||||||
|                 desktop_switch_to_app(desktop, &FLIPPER_APPS[desktop->settings.favorite]); |                 desktop_switch_to_app(desktop, &FLIPPER_APPS[desktop->settings.favorite]); | ||||||
|  |             } else { | ||||||
|  |                 FURI_LOG_E("DesktopSrv", "Can't find favorite application"); | ||||||
|  |             } | ||||||
|  |             animation_manager_load_and_continue_animation(desktop->animation_manager); | ||||||
|             consumed = true; |             consumed = true; | ||||||
|             break; |             break; | ||||||
| 
 | 
 | ||||||
|         case DesktopMainEventUpdateAnimation: { |         case DesktopMainEventCheckAnimation: | ||||||
|             bool status_bar_background_black = false; |             animation_manager_check_blocking_process(desktop->animation_manager); | ||||||
|             const Icon* icon = |  | ||||||
|                 desktop_animation_get_animation(desktop->animation, &status_bar_background_black); |  | ||||||
|             desktop_main_switch_dolphin_animation( |  | ||||||
|                 desktop->main_view, icon, status_bar_background_black); |  | ||||||
|             consumed = true; |             consumed = true; | ||||||
|             break; |             break; | ||||||
|         } |         case DesktopMainEventNewIdleAnimation: | ||||||
| 
 |             animation_manager_new_idle_process(desktop->animation_manager); | ||||||
|         case DesktopMainEventRightShort: { |             consumed = true; | ||||||
|             DesktopAnimationState state = desktop_animation_handle_right(desktop->animation); |             break; | ||||||
|             if(state == DesktopAnimationStateLevelUpIsPending) { |         case DesktopMainEventInteractAnimation: | ||||||
|                 scene_manager_next_scene(desktop->scene_manager, DesktopSceneLevelUp); |             animation_manager_interact_process(desktop->animation_manager); | ||||||
|             } |             consumed = true; | ||||||
|  |             break; | ||||||
|  |         case DesktopMainEventBeforeAppStarted: | ||||||
|  |             animation_manager_unload_and_stall_animation(desktop->animation_manager); | ||||||
|  |             osSemaphoreRelease(desktop->unload_animation_semaphore); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  |         case DesktopMainEventAfterAppFinished: | ||||||
|  |             animation_manager_load_and_continue_animation(desktop->animation_manager); | ||||||
|  |             consumed = true; | ||||||
|             break; |             break; | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         default: |         default: | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|         if(event.event != DesktopMainEventUpdateAnimation) { |  | ||||||
|             desktop_animation_activate(desktop->animation); |  | ||||||
|         } |  | ||||||
|     } else if(event.type != SceneManagerEventTypeTick) { |  | ||||||
|         desktop_animation_activate(desktop->animation); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return consumed; |     return consumed; | ||||||
| @ -128,7 +172,18 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { | |||||||
| void desktop_scene_main_on_exit(void* context) { | void desktop_scene_main_on_exit(void* context) { | ||||||
|     Desktop* desktop = (Desktop*)context; |     Desktop* desktop = (Desktop*)context; | ||||||
| 
 | 
 | ||||||
|     desktop_animation_set_animation_changed_callback(desktop->animation, NULL, NULL); |     /**
 | ||||||
|  |      * We're allowed to leave this scene only when any other app & loader | ||||||
|  |      * is finished, that's why we can be sure there is no task waiting | ||||||
|  |      * for start/stop semaphore | ||||||
|  |      */ | ||||||
|  |     furi_pubsub_unsubscribe(loader_get_pubsub(), desktop->app_start_stop_subscription); | ||||||
|  |     furi_assert(osSemaphoreGetCount(desktop->unload_animation_semaphore) == 0); | ||||||
|  | 
 | ||||||
|  |     animation_manager_set_new_idle_callback(desktop->animation_manager, NULL); | ||||||
|  |     animation_manager_set_check_callback(desktop->animation_manager, NULL); | ||||||
|  |     animation_manager_set_interact_callback(desktop->animation_manager, NULL); | ||||||
|  |     animation_manager_set_context(desktop->animation_manager, desktop); | ||||||
|     scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneMain, MAIN_VIEW_DEFAULT); |     scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneMain, MAIN_VIEW_DEFAULT); | ||||||
|     desktop_main_reset_hint(desktop->main_view); |     desktop_main_reset_hint(desktop->main_view); | ||||||
| } | } | ||||||
|  | |||||||
| @ -7,17 +7,11 @@ | |||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <storage/storage.h> | #include <storage/storage.h> | ||||||
| #include <time.h> | #include <time.h> | ||||||
| 
 | #include "desktop_events.h" | ||||||
| typedef enum { |  | ||||||
|     DesktopDebugEventDeed, |  | ||||||
|     DesktopDebugEventWrongDeed, |  | ||||||
|     DesktopDebugEventSaveState, |  | ||||||
|     DesktopDebugEventExit, |  | ||||||
| } DesktopDebugEvent; |  | ||||||
| 
 | 
 | ||||||
| typedef struct DesktopDebugView DesktopDebugView; | typedef struct DesktopDebugView DesktopDebugView; | ||||||
| 
 | 
 | ||||||
| typedef void (*DesktopDebugViewCallback)(DesktopDebugEvent event, void* context); | typedef void (*DesktopDebugViewCallback)(DesktopEvent event, void* context); | ||||||
| 
 | 
 | ||||||
| // Debug info
 | // Debug info
 | ||||||
| typedef enum { | typedef enum { | ||||||
|  | |||||||
							
								
								
									
										30
									
								
								applications/desktop/views/desktop_events.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								applications/desktop/views/desktop_events.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     DesktopMainEventOpenLockMenu, | ||||||
|  |     DesktopMainEventOpenArchive, | ||||||
|  |     DesktopMainEventOpenFavorite, | ||||||
|  |     DesktopMainEventOpenMenu, | ||||||
|  |     DesktopMainEventOpenDebug, | ||||||
|  |     DesktopMainEventUnlocked, | ||||||
|  |     DesktopMainEventRightShort, | ||||||
|  |     DesktopMainEventCheckAnimation, | ||||||
|  |     DesktopMainEventNewIdleAnimation, | ||||||
|  |     DesktopMainEventInteractAnimation, | ||||||
|  |     DesktopMainEventBeforeAppStarted, | ||||||
|  |     DesktopMainEventAfterAppFinished, | ||||||
|  |     DesktopLockedEventUnlock, | ||||||
|  |     DesktopLockedEventUpdate, | ||||||
|  |     DesktopLockedEventInputReset, | ||||||
|  |     DesktopLockedEventCheckAnimation, | ||||||
|  |     DesktopLockedEventMax, | ||||||
|  |     DesktopDebugEventDeed, | ||||||
|  |     DesktopDebugEventWrongDeed, | ||||||
|  |     DesktopDebugEventSaveState, | ||||||
|  |     DesktopDebugEventExit, | ||||||
|  |     DesktopFirstStartCompleted, | ||||||
|  |     DesktopFirstStartPoweroff, | ||||||
|  |     DesktopLockMenuEventLock, | ||||||
|  |     DesktopLockMenuEventPinLock, | ||||||
|  |     DesktopLockMenuEventExit, | ||||||
|  | } DesktopEvent; | ||||||
| @ -5,15 +5,11 @@ | |||||||
| #include <gui/canvas.h> | #include <gui/canvas.h> | ||||||
| #include <gui/elements.h> | #include <gui/elements.h> | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| 
 | #include "desktop_events.h" | ||||||
| typedef enum { |  | ||||||
|     DesktopFirstStartCompleted, |  | ||||||
|     DesktopFirstStartPoweroff, |  | ||||||
| } DesktopFirstStartEvent; |  | ||||||
| 
 | 
 | ||||||
| typedef struct DesktopFirstStartView DesktopFirstStartView; | typedef struct DesktopFirstStartView DesktopFirstStartView; | ||||||
| 
 | 
 | ||||||
| typedef void (*DesktopFirstStartViewCallback)(DesktopFirstStartEvent event, void* context); | typedef void (*DesktopFirstStartViewCallback)(DesktopEvent event, void* context); | ||||||
| 
 | 
 | ||||||
| DesktopFirstStartView* desktop_first_start_alloc(); | DesktopFirstStartView* desktop_first_start_alloc(); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -5,18 +5,13 @@ | |||||||
| #include <gui/canvas.h> | #include <gui/canvas.h> | ||||||
| #include <gui/elements.h> | #include <gui/elements.h> | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
|  | #include "desktop_events.h" | ||||||
| 
 | 
 | ||||||
| #define HINT_TIMEOUT 2 | #define HINT_TIMEOUT 2 | ||||||
| 
 | 
 | ||||||
| typedef enum { |  | ||||||
|     DesktopLockMenuEventLock, |  | ||||||
|     DesktopLockMenuEventPinLock, |  | ||||||
|     DesktopLockMenuEventExit, |  | ||||||
| } DesktopLockMenuEvent; |  | ||||||
| 
 |  | ||||||
| typedef struct DesktopLockMenuView DesktopLockMenuView; | typedef struct DesktopLockMenuView DesktopLockMenuView; | ||||||
| 
 | 
 | ||||||
| typedef void (*DesktopLockMenuViewCallback)(DesktopLockMenuEvent event, void* context); | typedef void (*DesktopLockMenuViewCallback)(DesktopEvent event, void* context); | ||||||
| 
 | 
 | ||||||
| struct DesktopLockMenuView { | struct DesktopLockMenuView { | ||||||
|     View* view; |     View* view; | ||||||
|  | |||||||
| @ -17,21 +17,6 @@ void locked_view_timer_callback(void* context) { | |||||||
|     locked_view->callback(DesktopLockedEventUpdate, locked_view->context); |     locked_view->callback(DesktopLockedEventUpdate, locked_view->context); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void desktop_locked_set_dolphin_animation( |  | ||||||
|     DesktopLockedView* locked_view, |  | ||||||
|     const Icon* icon, |  | ||||||
|     bool status_bar_background_black) { |  | ||||||
|     with_view_model( |  | ||||||
|         locked_view->view, (DesktopLockedViewModel * model) { |  | ||||||
|             if(model->animation) icon_animation_free(model->animation); |  | ||||||
|             model->animation = icon_animation_alloc(icon); |  | ||||||
|             view_tie_icon_animation(locked_view->view, model->animation); |  | ||||||
|             icon_animation_start(model->animation); |  | ||||||
|             model->status_bar_background_black = status_bar_background_black; |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void desktop_locked_update_hint_timeout(DesktopLockedView* locked_view) { | void desktop_locked_update_hint_timeout(DesktopLockedView* locked_view) { | ||||||
|     with_view_model( |     with_view_model( | ||||||
|         locked_view->view, (DesktopLockedViewModel * model) { |         locked_view->view, (DesktopLockedViewModel * model) { | ||||||
| @ -95,7 +80,6 @@ void desktop_locked_with_pin(DesktopLockedView* locked_view, bool locked) { | |||||||
| void desktop_locked_render(Canvas* canvas, void* model) { | void desktop_locked_render(Canvas* canvas, void* model) { | ||||||
|     DesktopLockedViewModel* m = model; |     DesktopLockedViewModel* m = model; | ||||||
|     uint32_t now = osKernelGetTickCount(); |     uint32_t now = osKernelGetTickCount(); | ||||||
|     canvas_clear(canvas); |  | ||||||
|     canvas_set_color(canvas, ColorBlack); |     canvas_set_color(canvas, ColorBlack); | ||||||
| 
 | 
 | ||||||
|     if(!m->animation_seq_end) { |     if(!m->animation_seq_end) { | ||||||
|  | |||||||
| @ -5,6 +5,7 @@ | |||||||
| #include <gui/canvas.h> | #include <gui/canvas.h> | ||||||
| #include <gui/elements.h> | #include <gui/elements.h> | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
|  | #include "desktop_events.h" | ||||||
| 
 | 
 | ||||||
| #define UNLOCK_RST_TIMEOUT 300 | #define UNLOCK_RST_TIMEOUT 300 | ||||||
| #define UNLOCK_CNT 2 // 3 actually
 | #define UNLOCK_CNT 2 // 3 actually
 | ||||||
| @ -14,12 +15,6 @@ | |||||||
| #define DOOR_R_POS 115 | #define DOOR_R_POS 115 | ||||||
| #define DOOR_R_POS_MIN 60 | #define DOOR_R_POS_MIN 60 | ||||||
| 
 | 
 | ||||||
| typedef enum { |  | ||||||
|     DesktopLockedEventUnlock = 10U, |  | ||||||
|     DesktopLockedEventUpdate = 11U, |  | ||||||
|     DesktopLockedEventInputReset = 12U, |  | ||||||
| } DesktopLockedEvent; |  | ||||||
| 
 |  | ||||||
| typedef enum { | typedef enum { | ||||||
|     DesktopLockedWithPin, |     DesktopLockedWithPin, | ||||||
|     DesktopLockedNoPin, |     DesktopLockedNoPin, | ||||||
| @ -27,7 +22,7 @@ typedef enum { | |||||||
| 
 | 
 | ||||||
| typedef struct DesktopLockedView DesktopLockedView; | typedef struct DesktopLockedView DesktopLockedView; | ||||||
| 
 | 
 | ||||||
| typedef void (*DesktopLockedViewCallback)(DesktopLockedEvent event, void* context); | typedef void (*DesktopLockedViewCallback)(DesktopEvent event, void* context); | ||||||
| 
 | 
 | ||||||
| struct DesktopLockedView { | struct DesktopLockedView { | ||||||
|     View* view; |     View* view; | ||||||
| @ -57,10 +52,6 @@ void desktop_locked_set_callback( | |||||||
|     DesktopLockedViewCallback callback, |     DesktopLockedViewCallback callback, | ||||||
|     void* context); |     void* context); | ||||||
| 
 | 
 | ||||||
| void desktop_locked_set_dolphin_animation( |  | ||||||
|     DesktopLockedView* locked_view, |  | ||||||
|     const Icon* icon, |  | ||||||
|     bool status_bar_background_black); |  | ||||||
| void desktop_locked_update_hint_timeout(DesktopLockedView* locked_view); | void desktop_locked_update_hint_timeout(DesktopLockedView* locked_view); | ||||||
| void desktop_locked_reset_counter(DesktopLockedView* locked_view); | void desktop_locked_reset_counter(DesktopLockedView* locked_view); | ||||||
| void desktop_locked_reset_door_pos(DesktopLockedView* locked_view); | void desktop_locked_reset_door_pos(DesktopLockedView* locked_view); | ||||||
|  | |||||||
| @ -1,8 +1,13 @@ | |||||||
|  | #include "dolphin/dolphin.h" | ||||||
|  | #include "furi/record.h" | ||||||
| #include "gui/canvas.h" | #include "gui/canvas.h" | ||||||
|  | #include "gui/view.h" | ||||||
|  | #include "gui/view_composed.h" | ||||||
| #include "input/input.h" | #include "input/input.h" | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include "../desktop_i.h" | #include "../desktop_i.h" | ||||||
| #include "desktop_main.h" | #include "desktop_main.h" | ||||||
|  | //#include "../animations/views/bubble_animation_view.h"
 | ||||||
| 
 | 
 | ||||||
| void desktop_main_set_callback( | void desktop_main_set_callback( | ||||||
|     DesktopMainView* main_view, |     DesktopMainView* main_view, | ||||||
| @ -49,7 +54,6 @@ void desktop_main_switch_dolphin_icon(DesktopMainView* main_view, const Icon* ic | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void desktop_main_render(Canvas* canvas, void* model) { | void desktop_main_render(Canvas* canvas, void* model) { | ||||||
|     canvas_clear(canvas); |  | ||||||
|     DesktopMainViewModel* m = model; |     DesktopMainViewModel* m = model; | ||||||
|     uint32_t now = osKernelGetTickCount(); |     uint32_t now = osKernelGetTickCount(); | ||||||
| 
 | 
 | ||||||
| @ -78,6 +82,7 @@ bool desktop_main_input(InputEvent* event, void* context) { | |||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
| 
 | 
 | ||||||
|     DesktopMainView* main_view = context; |     DesktopMainView* main_view = context; | ||||||
|  |     bool consumed = false; | ||||||
| 
 | 
 | ||||||
|     if(event->key == InputKeyOk && event->type == InputTypeShort) { |     if(event->key == InputKeyOk && event->type == InputTypeShort) { | ||||||
|         main_view->callback(DesktopMainEventOpenMenu, main_view->context); |         main_view->callback(DesktopMainEventOpenMenu, main_view->context); | ||||||
| @ -91,11 +96,13 @@ bool desktop_main_input(InputEvent* event, void* context) { | |||||||
|         main_view->callback(DesktopMainEventOpenFavorite, main_view->context); |         main_view->callback(DesktopMainEventOpenFavorite, main_view->context); | ||||||
|     } else if(event->key == InputKeyRight && event->type == InputTypeShort) { |     } else if(event->key == InputKeyRight && event->type == InputTypeShort) { | ||||||
|         main_view->callback(DesktopMainEventRightShort, main_view->context); |         main_view->callback(DesktopMainEventRightShort, main_view->context); | ||||||
|  |     } else if(event->key == InputKeyBack && event->type == InputTypeShort) { | ||||||
|  |         consumed = true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     desktop_main_reset_hint(main_view); |     desktop_main_reset_hint(main_view); | ||||||
| 
 | 
 | ||||||
|     return true; |     return consumed; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void desktop_main_enter(void* context) { | void desktop_main_enter(void* context) { | ||||||
| @ -119,6 +126,7 @@ void desktop_main_exit(void* context) { | |||||||
| 
 | 
 | ||||||
| DesktopMainView* desktop_main_alloc() { | DesktopMainView* desktop_main_alloc() { | ||||||
|     DesktopMainView* main_view = furi_alloc(sizeof(DesktopMainView)); |     DesktopMainView* main_view = furi_alloc(sizeof(DesktopMainView)); | ||||||
|  | 
 | ||||||
|     main_view->view = view_alloc(); |     main_view->view = view_alloc(); | ||||||
|     view_allocate_model(main_view->view, ViewModelTypeLocking, sizeof(DesktopMainViewModel)); |     view_allocate_model(main_view->view, ViewModelTypeLocking, sizeof(DesktopMainViewModel)); | ||||||
|     view_set_context(main_view->view, main_view); |     view_set_context(main_view->view, main_view); | ||||||
|  | |||||||
| @ -1,26 +1,16 @@ | |||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
|  | #include "gui/view_composed.h" | ||||||
| #include <gui/gui_i.h> | #include <gui/gui_i.h> | ||||||
| #include <gui/view.h> | #include <gui/view.h> | ||||||
| #include <gui/canvas.h> | #include <gui/canvas.h> | ||||||
| #include <gui/elements.h> | #include <gui/elements.h> | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| 
 | #include "desktop_events.h" | ||||||
| typedef enum { |  | ||||||
|     DesktopMainEventOpenLockMenu, |  | ||||||
|     DesktopMainEventOpenArchive, |  | ||||||
|     DesktopMainEventOpenFavorite, |  | ||||||
|     DesktopMainEventOpenMenu, |  | ||||||
|     DesktopMainEventOpenDebug, |  | ||||||
|     DesktopMainEventUnlocked, |  | ||||||
|     DesktopMainEventRightShort, |  | ||||||
|     DesktopMainEventUpdateAnimation, |  | ||||||
|     DesktopMainEventUpdateOneShotAnimation, |  | ||||||
| } DesktopMainEvent; |  | ||||||
| 
 | 
 | ||||||
| typedef struct DesktopMainView DesktopMainView; | typedef struct DesktopMainView DesktopMainView; | ||||||
| 
 | 
 | ||||||
| typedef void (*DesktopMainViewCallback)(DesktopMainEvent event, void* context); | typedef void (*DesktopMainViewCallback)(DesktopEvent event, void* context); | ||||||
| 
 | 
 | ||||||
| struct DesktopMainView { | struct DesktopMainView { | ||||||
|     View* view; |     View* view; | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| #include "dialogs-i.h" | #include "dialogs_i.h" | ||||||
| #include "dialogs-api-lock.h" | #include "dialogs_api_lock.h" | ||||||
| #include "dialogs-module-file-select.h" | #include "dialogs_module_file_select.h" | ||||||
| #include "dialogs-module-message.h" | #include "dialogs_module_message.h" | ||||||
| 
 | 
 | ||||||
| static DialogsApp* dialogs_app_alloc() { | static DialogsApp* dialogs_app_alloc() { | ||||||
|     DialogsApp* app = malloc(sizeof(DialogsApp)); |     DialogsApp* app = malloc(sizeof(DialogsApp)); | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| #include "dialogs-i.h" | #include "dialogs_i.h" | ||||||
| #include "dialogs-api-lock.h" | #include "dialogs_api_lock.h" | ||||||
| 
 | 
 | ||||||
| /****************** File select ******************/ | /****************** File select ******************/ | ||||||
| 
 | 
 | ||||||
| @ -1,7 +1,7 @@ | |||||||
| #pragma once | #pragma once | ||||||
| #include "dialogs.h" | #include "dialogs.h" | ||||||
| #include "dialogs-message.h" | #include "dialogs_message.h" | ||||||
| #include "view-holder.h" | #include "view_holder.h" | ||||||
| 
 | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| extern "C" { | extern "C" { | ||||||
| @ -1,7 +1,7 @@ | |||||||
| #pragma once | #pragma once | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include "dialogs-i.h" | #include "dialogs_i.h" | ||||||
| #include "dialogs-api-lock.h" | #include "dialogs_api_lock.h" | ||||||
| 
 | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| extern "C" { | extern "C" { | ||||||
| @ -1,5 +1,5 @@ | |||||||
| #include "dialogs-i.h" | #include "dialogs_i.h" | ||||||
| #include "dialogs-api-lock.h" | #include "dialogs_api_lock.h" | ||||||
| #include <gui/modules/file_select.h> | #include <gui/modules/file_select.h> | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
| @ -1,5 +1,5 @@ | |||||||
| #pragma once | #pragma once | ||||||
| #include "dialogs-message.h" | #include "dialogs_message.h" | ||||||
| 
 | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| extern "C" { | extern "C" { | ||||||
| @ -1,5 +1,5 @@ | |||||||
| #include "dialogs-i.h" | #include "dialogs_i.h" | ||||||
| #include "dialogs-api-lock.h" | #include "dialogs_api_lock.h" | ||||||
| #include <gui/modules/dialog_ex.h> | #include <gui/modules/dialog_ex.h> | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
| @ -1,5 +1,5 @@ | |||||||
| #pragma once | #pragma once | ||||||
| #include "dialogs-message.h" | #include "dialogs_message.h" | ||||||
| 
 | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| extern "C" { | extern "C" { | ||||||
| @ -1,4 +1,4 @@ | |||||||
| #include "view-holder.h" | #include "view_holder.h" | ||||||
| #include <gui/view_i.h> | #include <gui/view_i.h> | ||||||
| 
 | 
 | ||||||
| #define TAG "ViewHolder" | #define TAG "ViewHolder" | ||||||
|  | |||||||
| @ -111,13 +111,16 @@ int32_t dolphin_srv(void* p) { | |||||||
|                     furi_pubsub_publish(dolphin->pubsub, &event); |                     furi_pubsub_publish(dolphin->pubsub, &event); | ||||||
|                 } |                 } | ||||||
|             } else if(event.type == DolphinEventTypeStats) { |             } else if(event.type == DolphinEventTypeStats) { | ||||||
|                 event.stats->icounter = dolphin->state->data.icounter; |                 // TODO: correct icounter/butthurt changing, stub till then
 | ||||||
|                 event.stats->butthurt = dolphin->state->data.butthurt; |                 event.stats->icounter = 0; | ||||||
|  |                 event.stats->butthurt = 0; | ||||||
|                 event.stats->timestamp = dolphin->state->data.timestamp; |                 event.stats->timestamp = dolphin->state->data.timestamp; | ||||||
|                 event.stats->level = dolphin_get_level(dolphin->state->data.icounter); |                 event.stats->level = 1; | ||||||
|                 event.stats->level_up_is_pending = |                 event.stats->level_up_is_pending = 0; | ||||||
|                     !dolphin_state_xp_to_levelup(dolphin->state->data.icounter); |  | ||||||
|             } else if(event.type == DolphinEventTypeFlush) { |             } else if(event.type == DolphinEventTypeFlush) { | ||||||
|  |                 // TODO: correct icounter/butthurt changing, stub till then
 | ||||||
|  |                 dolphin->state->data.butthurt = 0; | ||||||
|  |                 dolphin->state->data.icounter = 0; | ||||||
|                 dolphin_state_save(dolphin->state); |                 dolphin_state_save(dolphin->state); | ||||||
|             } |             } | ||||||
|             dolphin_event_release(dolphin, &event); |             dolphin_event_release(dolphin, &event); | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include "furi/pubsub.h" | #include "furi/pubsub.h" | ||||||
|  | #include "gui/view.h" | ||||||
| #include "helpers/dolphin_deed.h" | #include "helpers/dolphin_deed.h" | ||||||
| #include <stdbool.h> | #include <stdbool.h> | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ | |||||||
| 
 | 
 | ||||||
| #include "furi/pubsub.h" | #include "furi/pubsub.h" | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <furi-hal.h> | #include <furi_hal.h> | ||||||
| 
 | 
 | ||||||
| #include "dolphin.h" | #include "dolphin.h" | ||||||
| #include "helpers/dolphin_state.h" | #include "helpers/dolphin_state.h" | ||||||
| @ -11,6 +11,9 @@ typedef enum { | |||||||
|     DolphinEventTypeDeed, |     DolphinEventTypeDeed, | ||||||
|     DolphinEventTypeStats, |     DolphinEventTypeStats, | ||||||
|     DolphinEventTypeFlush, |     DolphinEventTypeFlush, | ||||||
|  |     DolphinEventTypeAnimationStartNewIdle, | ||||||
|  |     DolphinEventTypeAnimationCheckBlocking, | ||||||
|  |     DolphinEventTypeAnimationInteract, | ||||||
| } DolphinEventType; | } DolphinEventType; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ | |||||||
| #include <stdint.h> | #include <stdint.h> | ||||||
| #include <storage/storage.h> | #include <storage/storage.h> | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <furi-hal.h> | #include <furi_hal.h> | ||||||
| #include <math.h> | #include <math.h> | ||||||
| #include <toolbox/saved_struct.h> | #include <toolbox/saved_struct.h> | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -5,7 +5,7 @@ | |||||||
| #include "furi/record.h" | #include "furi/record.h" | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <gui/gui.h> | #include <gui/gui.h> | ||||||
| #include <furi-hal-version.h> | #include <furi_hal_version.h> | ||||||
| #include "dolphin/dolphin.h" | #include "dolphin/dolphin.h" | ||||||
| #include "math.h" | #include "math.h" | ||||||
| 
 | 
 | ||||||
|  | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Aleksandr Kutuzov
						Aleksandr Kutuzov